99 lines
3.1 KiB
TypeScript
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>
|
|
);
|
|
}
|