247 lines
9.5 KiB
TypeScript
247 lines
9.5 KiB
TypeScript
"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>
|
||
);
|
||
}
|