Add login and register dialogs, user dropdown menu with profile link and logout option, and integrate with session management hook. Prevent hydration mismatches by conditionally rendering auth elements after mount. Update package-lock.json with new @esbuild dependencies for build tooling.
174 lines
6.0 KiB
TypeScript
174 lines
6.0 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
NavigationMenu,
|
|
NavigationMenuItem,
|
|
NavigationMenuLink,
|
|
NavigationMenuList,
|
|
navigationMenuTriggerStyle,
|
|
} from "@/components/ui/navigation-menu";
|
|
import { WordRotate } from "@/components/ui/word-rotate";
|
|
import { ThemeToggle } from "./theme-toggle";
|
|
import { LanguageSwitcher } from "./language-switcher";
|
|
import { MobileMenu } from "./mobile-menu";
|
|
import { LoginDialog } from "./auth/login-dialog";
|
|
import { RegisterDialog } from "./auth/register-dialog";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
import { cn } from "@/lib/utils";
|
|
import { useTranslations, useLocale } from "next-intl";
|
|
import { useSession } from "@/lib/hooks/use-session";
|
|
import Link from "next/link";
|
|
import { useState, useEffect } from "react";
|
|
import { LogOut, User } from "lucide-react";
|
|
|
|
export default function Header() {
|
|
const t = useTranslations("header.nav");
|
|
const locale = useLocale();
|
|
const { user, isAuthenticated, isLoading, logout } = useSession();
|
|
const [loginDialogOpen, setLoginDialogOpen] = useState(false);
|
|
const [registerDialogOpen, setRegisterDialogOpen] = useState(false);
|
|
const [mounted, setMounted] = useState(false);
|
|
|
|
// Evitar hidratação mismatch
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
|
|
const links = [
|
|
{ href: `/${locale}/library`, label: "Library" },
|
|
{ href: `/${locale}/market`, label: t("market") },
|
|
{ href: `/${locale}/sobre`, label: t("about") },
|
|
{ href: `/${locale}/precos`, label: t("prices") },
|
|
{ href: `/${locale}/apoiar`, label: t("support"), highlight: true },
|
|
];
|
|
|
|
const handleLogout = async () => {
|
|
await logout();
|
|
};
|
|
|
|
return (
|
|
<header className="sticky top-0 z-50 w-full border-b bg-background/60 backdrop-blur supports-backdrop-filter:bg-background/60">
|
|
<div className="mx-auto flex h-16 w-full max-w-6xl items-center justify-between px-4">
|
|
{/* Logo */}
|
|
<div className="flex items-center gap-2">
|
|
<Link href="/">
|
|
<WordRotate
|
|
words={["Tibia Trade"]}
|
|
className="text-lg font-semibold tracking-tight hover:text-primary transition-colors"
|
|
/>
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Desktop Navigation */}
|
|
<NavigationMenu className="hidden md:flex">
|
|
<NavigationMenuList>
|
|
{links.map(({ href, label, highlight }) => (
|
|
<NavigationMenuItem key={href}>
|
|
<NavigationMenuLink asChild>
|
|
<Link
|
|
href={href}
|
|
className={cn(
|
|
navigationMenuTriggerStyle(),
|
|
"bg-transparent",
|
|
highlight &&
|
|
"bg-primary text-primary-foreground hover:bg-primary/90"
|
|
)}
|
|
>
|
|
{label}
|
|
</Link>
|
|
</NavigationMenuLink>
|
|
</NavigationMenuItem>
|
|
))}
|
|
</NavigationMenuList>
|
|
</NavigationMenu>
|
|
|
|
{/* Desktop Controls */}
|
|
<div className="hidden md:flex items-center gap-2">
|
|
<ThemeToggle />
|
|
<LanguageSwitcher />
|
|
|
|
{/* Auth Section */}
|
|
{!mounted ? (
|
|
// Placeholder durante SSR para evitar mismatch
|
|
<div className="w-20 h-8 animate-pulse bg-muted rounded" />
|
|
) : isLoading ? (
|
|
<div className="w-20 h-8 animate-pulse bg-muted rounded" />
|
|
) : isAuthenticated && user ? (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
|
|
<Avatar className="h-8 w-8">
|
|
<AvatarFallback>
|
|
{user.username.charAt(0).toUpperCase()}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
</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.username}
|
|
</p>
|
|
<p className="text-xs leading-none text-muted-foreground">
|
|
{user.email}
|
|
</p>
|
|
</div>
|
|
</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem asChild>
|
|
<Link href={`/${locale}/perfil`} className="cursor-pointer">
|
|
<User className="mr-2 h-4 w-4" />
|
|
Perfil
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem
|
|
className="cursor-pointer text-destructive"
|
|
onClick={handleLogout}
|
|
>
|
|
<LogOut className="mr-2 h-4 w-4" />
|
|
Sair
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
) : (
|
|
<div className="flex items-center gap-2">
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setRegisterDialogOpen(true)}
|
|
size="sm"
|
|
>
|
|
Registrar
|
|
</Button>
|
|
<Button onClick={() => setLoginDialogOpen(true)} size="sm">
|
|
Entrar
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Mobile Menu */}
|
|
<div className="md:hidden">
|
|
<MobileMenu links={links} />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Login Dialog */}
|
|
<LoginDialog open={loginDialogOpen} onOpenChange={setLoginDialogOpen} />
|
|
|
|
{/* Register Dialog */}
|
|
<RegisterDialog open={registerDialogOpen} onOpenChange={setRegisterDialogOpen} />
|
|
</header>
|
|
);
|
|
}
|