104 lines
3.7 KiB
TypeScript
104 lines
3.7 KiB
TypeScript
import { useState, useRef, useEffect } from 'react';
|
|
import type { AuthUser } from '../../hooks/useAuth';
|
|
import type { Page } from './Header';
|
|
import { PAGE_PATHS } from './Header';
|
|
import { SunIcon } from './icons/SunIcon';
|
|
import { MoonIcon } from './icons/MoonIcon';
|
|
|
|
export default function UserMenu({
|
|
user,
|
|
theme,
|
|
onToggleTheme,
|
|
onLogout,
|
|
onNavigate,
|
|
}: {
|
|
user: AuthUser;
|
|
theme: 'light' | 'dark';
|
|
onToggleTheme: () => void;
|
|
onLogout: () => void;
|
|
onNavigate: (page: Page) => void;
|
|
}) {
|
|
const [open, setOpen] = useState(false);
|
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
|
|
// Close on outside click
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
const handleClick = (e: MouseEvent) => {
|
|
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
setOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener('mousedown', handleClick);
|
|
return () => document.removeEventListener('mousedown', handleClick);
|
|
}, [open]);
|
|
|
|
const initial = user.email[0].toUpperCase();
|
|
|
|
return (
|
|
<div className="relative" ref={menuRef}>
|
|
<button
|
|
onClick={() => setOpen((prev) => !prev)}
|
|
className="flex items-center justify-center w-8 h-8 rounded-full bg-teal-600 text-white text-sm font-medium hover:bg-teal-700"
|
|
title={user.email}
|
|
>
|
|
{initial}
|
|
</button>
|
|
|
|
{open && (
|
|
<div className="absolute right-0 top-10 w-56 bg-white dark:bg-warm-800 border border-warm-200 dark:border-warm-700 rounded-lg shadow-lg z-50">
|
|
<div className="px-4 py-3 border-b border-warm-200 dark:border-warm-700">
|
|
<div className="flex items-center justify-between gap-2">
|
|
<p className="text-sm font-medium text-navy-950 dark:text-warm-100 truncate">
|
|
{user.email}
|
|
</p>
|
|
<span
|
|
className={`shrink-0 text-xs font-medium px-1.5 py-0.5 rounded ${
|
|
user.subscription === 'licensed' || user.isAdmin
|
|
? 'bg-teal-100 text-teal-700 dark:bg-teal-900/30 dark:text-teal-400'
|
|
: 'bg-warm-100 text-warm-500 dark:bg-warm-700 dark:text-warm-400'
|
|
}`}
|
|
>
|
|
{user.subscription === 'licensed' || user.isAdmin ? 'Full Access' : 'Inner London'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="p-1">
|
|
<button
|
|
onClick={onToggleTheme}
|
|
className="w-full flex items-center gap-2 px-3 py-2 text-sm text-warm-700 dark:text-warm-300 hover:bg-warm-50 dark:hover:bg-warm-700 rounded"
|
|
>
|
|
{theme === 'light' ? (
|
|
<SunIcon className="w-4 h-4" />
|
|
) : (
|
|
<MoonIcon className="w-4 h-4" />
|
|
)}
|
|
Theme: {theme === 'light' ? 'Light' : 'Dark'}
|
|
</button>
|
|
<a
|
|
href={PAGE_PATHS.account}
|
|
onClick={(e) => {
|
|
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
|
|
e.preventDefault();
|
|
setOpen(false);
|
|
onNavigate('account');
|
|
}}
|
|
className="block w-full text-left px-3 py-2 text-sm text-warm-700 dark:text-warm-300 hover:bg-warm-50 dark:hover:bg-warm-700 rounded"
|
|
>
|
|
Account
|
|
</a>
|
|
<button
|
|
onClick={() => {
|
|
setOpen(false);
|
|
onLogout();
|
|
}}
|
|
className="w-full text-left px-3 py-2 text-sm text-warm-700 dark:text-warm-300 hover:bg-warm-50 dark:hover:bg-warm-700 rounded"
|
|
>
|
|
Log out
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|