volsu-contests/frontend/src/components/Navbar.tsx

247 lines
9.5 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,
Terminal,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { VolguLogo, CyberBrandText } 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-[var(--color-border)] bg-[var(--color-background)]/90 backdrop-blur-md">
{/* Neon bottom border */}
<div className="absolute bottom-0 left-0 right-0 h-[1px] bg-gradient-to-r from-transparent via-[var(--color-neon-green)]/50 to-transparent" />
<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-3 group">
<motion.div
whileHover={{ scale: 1.05 }}
transition={{ duration: 0.2 }}
className="relative"
>
<VolguLogo className="h-8 w-8" />
{/* Glow effect on hover */}
<div className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300 blur-md">
<VolguLogo className="h-8 w-8" />
</div>
</motion.div>
<CyberBrandText />
</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(
"relative flex items-center gap-2 px-4 py-2 text-sm font-mono uppercase tracking-wider transition-all duration-200",
active
? "text-[var(--color-neon-green)]"
: "text-muted-foreground hover:text-[var(--color-neon-green)]"
)}
>
<Icon className="h-4 w-4" />
{link.label}
{/* Neon underline for active state */}
{active && (
<motion.div
layoutId="navbar-indicator"
className="absolute bottom-0 left-0 right-0 h-[2px] bg-[var(--color-neon-green)]"
style={{
boxShadow: "0 0 10px var(--color-neon-green)",
}}
/>
)}
</Link>
);
})}
{user.role === "admin" && (
<Link
href="/admin"
className={cn(
"relative flex items-center gap-2 px-4 py-2 text-sm font-mono uppercase tracking-wider transition-all duration-200",
isActive("/admin")
? "text-[var(--color-neon-purple)]"
: "text-muted-foreground hover:text-[var(--color-neon-purple)]"
)}
>
<LayoutDashboard className="h-4 w-4" />
Админ
{isActive("/admin") && (
<motion.div
layoutId="navbar-indicator-admin"
className="absolute bottom-0 left-0 right-0 h-[2px] bg-[var(--color-neon-purple)]"
style={{
boxShadow: "0 0 10px var(--color-neon-purple)",
}}
/>
)}
</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 border border-transparent hover:border-[var(--color-neon-green)]/30"
>
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-[var(--color-neon-green)] to-[var(--color-neon-cyan)] p-[2px] overflow-hidden">
<div className="w-full h-full rounded-full bg-[var(--color-background)] flex items-center justify-center text-[var(--color-neon-green)] font-mono text-sm">
{user.avatar_url ? (
<img
src={`${API_URL}${user.avatar_url}`}
alt={user.username}
className="w-full h-full object-cover rounded-full"
/>
) : (
user.username.charAt(0).toUpperCase()
)}
</div>
</div>
<span className="hidden sm:inline text-sm font-mono text-foreground">
{user.username}
</span>
{user.role === "admin" && (
<Badge variant="purple" className="hidden sm:inline-flex">
ADMIN
</Badge>
)}
<ChevronDown className="h-4 w-4 text-muted-foreground" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-48 bg-[var(--color-card)] border-[var(--color-border)]"
>
<div className="px-2 py-1.5">
<p className="text-sm font-mono text-[var(--color-neon-green)]">
{user.username}
</p>
<p className="text-xs text-muted-foreground font-mono">
{user.email}
</p>
</div>
<DropdownMenuSeparator className="bg-[var(--color-border)]" />
<DropdownMenuItem asChild>
<Link
href="/profile"
className="flex items-center gap-2 font-mono text-sm"
>
<User className="h-4 w-4" />
Профиль
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator className="bg-[var(--color-border)]" />
{/* 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 font-mono text-sm"
>
<Icon className="h-4 w-4" />
{link.label}
</Link>
</DropdownMenuItem>
);
})}
{user.role === "admin" && (
<DropdownMenuItem asChild>
<Link
href="/admin"
className="flex items-center gap-2 font-mono text-sm"
>
<LayoutDashboard className="h-4 w-4" />
Админ
</Link>
</DropdownMenuItem>
)}
<DropdownMenuSeparator className="bg-[var(--color-border)]" />
</div>
<DropdownMenuItem
onClick={logout}
className="text-[var(--color-destructive)] focus:text-[var(--color-destructive)] font-mono text-sm"
>
<LogOut className="h-4 w-4 mr-2" />
Выйти
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : (
<div className="flex items-center gap-3">
<Button variant="ghost" asChild className="font-mono">
<Link href="/login">
<Terminal className="h-4 w-4 mr-2" />
Войти
</Link>
</Button>
<Button asChild className="font-mono">
<Link href="/register">Регистрация</Link>
</Button>
</div>
)}
</div>
</div>
</nav>
);
}