feat: add user authentication UI to header component
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.
This commit is contained in:
1 parent
e641138887
commit
55eebde0ba
55 files changed
+7353
-7
No files matched your search
@@ -11,13 +11,37 @@ 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" },
|
||||
@@ -27,6 +51,10 @@ export default function Header() {
|
||||
{ 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">
|
||||
@@ -67,6 +95,66 @@ export default function Header() {
|
||||
<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 */}
|
||||
@@ -74,6 +162,12 @@ export default function Header() {
|
||||
<MobileMenu links={links} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Login Dialog */}
|
||||
<LoginDialog open={loginDialogOpen} onOpenChange={setLoginDialogOpen} />
|
||||
|
||||
{/* Register Dialog */}
|
||||
<RegisterDialog open={registerDialogOpen} onOpenChange={setRegisterDialogOpen} />
|
||||
</header>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user