volsu-contests/frontend/src/components/Navbar.tsx
2025-11-30 19:55:50 +03:00

199 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { motion } from "framer-motion";
import { useAuth } from "@/lib/auth-context";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Skeleton } from "@/components/ui/skeleton";
import {
Trophy,
FileCode,
LogOut,
User,
ChevronDown,
LayoutDashboard,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { VolguLogo } from "@/components/VolguLogo";
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
const navLinks = [
{ href: "/contests", label: "Контесты", icon: Trophy },
{ href: "/submissions", label: "Мои решения", icon: FileCode },
];
export function Navbar() {
const { user, logout, isLoading } = useAuth();
const pathname = usePathname();
const isActive = (href: string) => {
if (href === "/") return pathname === href;
return pathname.startsWith(href);
};
return (
<nav className="sticky top-0 z-50 border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container mx-auto px-4 h-16 flex items-center justify-between">
{/* Logo and Nav Links */}
<div className="flex items-center gap-8">
<Link href="/" className="flex items-center gap-2 group">
<motion.div
whileHover={{ scale: 1.05 }}
transition={{ duration: 0.2 }}
>
<VolguLogo className="h-8 w-8" />
</motion.div>
<span className="text-xl font-bold">
<span className="text-[#2B4B7C]">ВолГУ</span>
<span className="text-muted-foreground">.</span>
<span className="text-primary">Контесты</span>
</span>
</Link>
{user && (
<div className="hidden md:flex items-center gap-1">
{navLinks.map((link) => {
const Icon = link.icon;
const active = isActive(link.href);
return (
<Link
key={link.href}
href={link.href}
className={cn(
"flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors",
active
? "bg-primary/10 text-primary"
: "text-muted-foreground hover:text-foreground hover:bg-muted"
)}
>
<Icon className="h-4 w-4" />
{link.label}
</Link>
);
})}
{user.role === "admin" && (
<Link
href="/admin"
className={cn(
"flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-colors",
isActive("/admin")
? "bg-primary/10 text-primary"
: "text-muted-foreground hover:text-foreground hover:bg-muted"
)}
>
<LayoutDashboard className="h-4 w-4" />
Админ
</Link>
)}
</div>
)}
</div>
{/* Right side - User menu */}
<div className="flex items-center gap-4">
{isLoading ? (
<div className="flex items-center gap-3">
<Skeleton className="h-8 w-8 rounded-full" />
<Skeleton className="h-4 w-20" />
</div>
) : user ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="flex items-center gap-2 px-2">
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-primary to-primary/60 flex items-center justify-center text-primary-foreground font-semibold text-sm overflow-hidden">
{user.avatar_url ? (
<img
src={`${API_URL}${user.avatar_url}`}
alt={user.username}
className="w-full h-full object-cover"
/>
) : (
user.username.charAt(0).toUpperCase()
)}
</div>
<span className="hidden sm:inline text-sm font-medium">
{user.username}
</span>
{user.role === "admin" && (
<Badge variant="secondary" className="hidden sm:inline-flex text-xs">
Admin
</Badge>
)}
<ChevronDown className="h-4 w-4 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<div className="px-2 py-1.5">
<p className="text-sm font-medium">{user.username}</p>
<p className="text-xs text-muted-foreground">{user.email}</p>
</div>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link href="/profile" className="flex items-center gap-2">
<User className="h-4 w-4" />
Профиль
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
{/* Mobile nav links */}
<div className="md:hidden">
{navLinks.map((link) => {
const Icon = link.icon;
return (
<DropdownMenuItem key={link.href} asChild>
<Link href={link.href} className="flex items-center gap-2">
<Icon className="h-4 w-4" />
{link.label}
</Link>
</DropdownMenuItem>
);
})}
{user.role === "admin" && (
<DropdownMenuItem asChild>
<Link href="/admin" className="flex items-center gap-2">
<LayoutDashboard className="h-4 w-4" />
Админ
</Link>
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
</div>
<DropdownMenuItem
onClick={logout}
className="text-destructive focus:text-destructive"
>
<LogOut className="h-4 w-4 mr-2" />
Выйти
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : (
<div className="flex items-center gap-2">
<Button variant="ghost" asChild>
<Link href="/login">Войти</Link>
</Button>
<Button asChild>
<Link href="/register">Регистрация</Link>
</Button>
</div>
)}
</div>
</div>
</nav>
);
}