frontend/app/components/user-dropdown.tsx
2025-07-30 13:08:43 +02:00

129 lines
4.2 KiB
TypeScript

import { useState } from "react";
import { Avatar, AvatarFallback } from "~/components/ui/avatar";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu";
import {
ChevronDown,
LineChartIcon,
LogOut,
Moon,
Settings,
Sun,
} from "lucide-react";
// TODO: THEME
// import { useTheme } from "~/components/ui/theme-provider";
import { useNavigate } from "react-router";
import { logout, type User } from "~/lib/api";
import { useQueryClient } from "@tanstack/react-query";
export default function UserDropdown({ user }: { user: User }) {
// TODO: const { theme, setTheme } = useTheme();
const [theme, setTheme] = useState("light");
const [open, setOpen] = useState(false);
const navigate = useNavigate();
const useLogout = () => {
const queryClient = useQueryClient();
// Logout, invalidate user cache, and redirect to login
return async () => {
try {
await logout();
queryClient.removeQueries({ queryKey: ["user"] });
navigate("/login", { replace: true });
} catch (error) {
console.error("Logout failed:", error);
}
};
};
const handleLogout = useLogout();
return (
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild>
<button
className={
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive h-9 px-4 py-2 has-[>svg]:px-3 hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50 flex items-center gap-2 px-2"
}
>
<Avatar className="h-10 w-10">
<AvatarFallback>{getAvatar(user.fullName)}</AvatarFallback>
</Avatar>
<span className="hidden md:inline-block font-medium">
{user.fullName}
</span>
<ChevronDown className="h-4 w-4 text-muted-foreground" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuLabel>
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">{user.fullName}</p>
<p className="text-xs leading-none text-muted-foreground">
{user.className}
</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
>
{theme === "dark" ? (
<>
<Sun className="mr-2 h-4 w-4" />
<span>Mode clair</span>
</>
) : (
<>
<Moon className="mr-2 h-4 w-4" />
<span>Mode sombre</span>
</>
)}
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
navigate("/settings");
}}
>
<Settings className="mr-2 h-4 w-4" />
<span>Paramètres</span>
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
navigate("/progress");
}}
>
<LineChartIcon className="mr-2 h-4 w-4" />
<span>Progression</span>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleLogout}>
<LogOut className="mr-2 h-4 w-4" />
<span>Se déconnecter</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
const getAvatar = (name: string) => {
return name
.split(" ")
.map((word) => word.charAt(0))
.join("")
.toUpperCase();
};