Files
GearBox/src/client/components/UserMenu.tsx

99 lines
3.1 KiB
TypeScript

import { Link } from "@tanstack/react-router";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useAuth, useLogout } from "../hooks/useAuth";
import { usePublicProfile } from "../hooks/useProfile";
import { LucideIcon } from "../lib/iconData";
export function UserMenu() {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
const { logout } = useLogout();
const { data: auth } = useAuth();
const userId = auth?.user?.id ? Number(auth.user.id) : null;
const { data: profile } = usePublicProfile(userId);
const avatarUrl = profile?.avatarImageUrl;
useEffect(() => {
if (!open) return;
function 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]);
return (
<div ref={menuRef} className="relative">
<button
type="button"
onClick={() => setOpen((prev) => !prev)}
className="flex items-center justify-center w-8 h-8 rounded-full text-gray-500 hover:text-gray-700 hover:bg-gray-100 transition-colors overflow-hidden"
>
{avatarUrl ? (
<img
src={avatarUrl}
alt={t("nav.profile")}
className="w-8 h-8 rounded-full object-cover"
/>
) : (
<LucideIcon name="circle-user" size={22} />
)}
</button>
{open && (
<div className="absolute right-0 mt-1 w-40 bg-white rounded-lg shadow-lg border border-gray-200 py-1 z-50">
{/* Admin link — only visible to admin users */}
{auth?.user?.isAdmin && (
<>
<Link
to="/admin"
onClick={() => setOpen(false)}
className="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 transition-colors"
>
<LucideIcon name="shield" size={16} className="text-gray-400" />
Admin
</Link>
<div className="border-t border-gray-100 my-1" />
</>
)}
<Link
to="/profile"
onClick={() => setOpen(false)}
className="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 transition-colors"
>
<LucideIcon
name="circle-user"
size={16}
className="text-gray-400"
/>
{t("nav.profile")}
</Link>
<Link
to="/settings"
onClick={() => setOpen(false)}
className="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 transition-colors"
>
<LucideIcon name="settings" size={16} className="text-gray-400" />
{t("nav.settings")}
</Link>
<div className="border-t border-gray-100 my-1" />
<button
type="button"
onClick={() => {
setOpen(false);
logout();
}}
className="flex items-center gap-2 w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 transition-colors"
>
<LucideIcon name="log-out" size={16} className="text-gray-400" />
{t("auth.signOut")}
</button>
</div>
)}
</div>
);
}