Files
rxminder/components/auth/AvatarDropdown.tsx
William Valentin e48adbcb00 Initial commit: Complete NodeJS-native setup
- Migrated from Python pre-commit to NodeJS-native solution
- Reorganized documentation structure
- Set up Husky + lint-staged for efficient pre-commit hooks
- Fixed Dockerfile healthcheck issue
- Added comprehensive documentation index
2025-09-06 01:42:48 -07:00

113 lines
3.6 KiB
TypeScript

import React, { useState, useRef, useEffect } from 'react';
import { User, UserRole } from '../../types';
interface AvatarDropdownProps {
user: User;
onLogout: () => void;
onAdmin?: () => void;
onChangePassword?: () => void;
}
const getInitials = (name: string) => {
return name ? name.charAt(0).toUpperCase() : '?';
};
const AvatarDropdown: React.FC<AvatarDropdownProps> = ({
user,
onLogout,
onAdmin,
onChangePassword,
}) => {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
return (
<div className='relative' ref={dropdownRef}>
<button
onClick={() => setIsOpen(!isOpen)}
className='w-10 h-10 rounded-full bg-slate-200 dark:bg-slate-700 flex items-center justify-center text-lg font-bold text-slate-600 dark:text-slate-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-slate-900'
aria-label='User menu'
>
{user.avatar ? (
<img
src={user.avatar}
alt='User avatar'
className='w-full h-full rounded-full object-cover'
/>
) : (
<span>{getInitials(user.username)}</span>
)}
</button>
{isOpen && (
<div className='absolute right-0 mt-2 w-48 bg-white dark:bg-slate-800 rounded-md shadow-lg ring-1 ring-black ring-opacity-5 py-1 z-30 border dark:border-slate-700'>
<div className='px-4 py-2 border-b border-slate-200 dark:border-slate-700'>
<p className='text-sm text-slate-500 dark:text-slate-400'>
Signed in as
</p>
<p className='text-sm font-medium text-slate-800 dark:text-slate-200 truncate'>
{user.username}
</p>
{user.role === UserRole.ADMIN && (
<p className='text-xs text-purple-600 dark:text-purple-400 font-medium'>
Administrator
</p>
)}
</div>
{/* Password Change Option - Only for password-based accounts */}
{user.password && onChangePassword && (
<button
onClick={() => {
onChangePassword();
setIsOpen(false);
}}
className='w-full text-left px-4 py-2 text-sm text-slate-700 dark:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-700'
>
Change Password
</button>
)}
{/* Admin Interface - Only for admins */}
{user.role === UserRole.ADMIN && onAdmin && (
<button
onClick={() => {
onAdmin();
setIsOpen(false);
}}
className='w-full text-left px-4 py-2 text-sm text-purple-700 dark:text-purple-300 hover:bg-slate-100 dark:hover:bg-slate-700 font-medium'
>
Admin Interface
</button>
)}
<button
onClick={() => {
onLogout();
setIsOpen(false);
}}
className='w-full text-left px-4 py-2 text-sm text-slate-700 dark:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-700'
>
Logout
</button>
</div>
)}
</div>
);
};
export default AvatarDropdown;