2025-12-17 02:03:33 +07:00
|
|
|
|
import { useState, useEffect } from 'react'
|
|
|
|
|
|
import { Outlet, Link, useNavigate, useLocation } from 'react-router-dom'
|
2025-12-14 02:38:35 +07:00
|
|
|
|
import { useAuthStore } from '@/store/auth'
|
2025-12-17 02:03:33 +07:00
|
|
|
|
import { Gamepad2, LogOut, Trophy, User, Menu, X } from 'lucide-react'
|
2025-12-16 20:06:16 +07:00
|
|
|
|
import { TelegramLink } from '@/components/TelegramLink'
|
2025-12-17 02:03:33 +07:00
|
|
|
|
import { clsx } from 'clsx'
|
2025-12-14 02:38:35 +07:00
|
|
|
|
|
|
|
|
|
|
export function Layout() {
|
|
|
|
|
|
const { user, isAuthenticated, logout } = useAuthStore()
|
|
|
|
|
|
const navigate = useNavigate()
|
2025-12-17 02:03:33 +07:00
|
|
|
|
const location = useLocation()
|
|
|
|
|
|
const [isScrolled, setIsScrolled] = useState(false)
|
|
|
|
|
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const handleScroll = () => {
|
|
|
|
|
|
setIsScrolled(window.scrollY > 10)
|
|
|
|
|
|
}
|
|
|
|
|
|
window.addEventListener('scroll', handleScroll)
|
|
|
|
|
|
return () => window.removeEventListener('scroll', handleScroll)
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
|
|
// Close mobile menu on route change
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
setIsMobileMenuOpen(false)
|
|
|
|
|
|
}, [location])
|
2025-12-14 02:38:35 +07:00
|
|
|
|
|
|
|
|
|
|
const handleLogout = () => {
|
|
|
|
|
|
logout()
|
|
|
|
|
|
navigate('/login')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-17 02:03:33 +07:00
|
|
|
|
const isActiveLink = (path: string) => location.pathname === path
|
|
|
|
|
|
|
2025-12-14 02:38:35 +07:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="min-h-screen flex flex-col">
|
|
|
|
|
|
{/* Header */}
|
2025-12-17 02:03:33 +07:00
|
|
|
|
<header
|
|
|
|
|
|
className={clsx(
|
|
|
|
|
|
'fixed top-0 left-0 right-0 z-50 transition-all duration-300',
|
|
|
|
|
|
isScrolled
|
|
|
|
|
|
? 'bg-dark-900/80 backdrop-blur-lg border-b border-dark-600/50 shadow-lg'
|
|
|
|
|
|
: 'bg-transparent'
|
|
|
|
|
|
)}
|
|
|
|
|
|
>
|
2025-12-14 02:38:35 +07:00
|
|
|
|
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
|
2025-12-17 02:03:33 +07:00
|
|
|
|
{/* Logo */}
|
|
|
|
|
|
<Link
|
|
|
|
|
|
to="/"
|
|
|
|
|
|
className="flex items-center gap-3 group"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="relative">
|
|
|
|
|
|
<Gamepad2 className="w-8 h-8 text-neon-500 transition-all duration-300 group-hover:text-neon-400 group-hover:drop-shadow-[0_0_8px_rgba(0,240,255,0.8)]" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="text-xl font-bold text-white font-display tracking-wider glitch-hover">
|
|
|
|
|
|
МАРАФОН
|
|
|
|
|
|
</span>
|
2025-12-14 02:38:35 +07:00
|
|
|
|
</Link>
|
|
|
|
|
|
|
2025-12-17 02:03:33 +07:00
|
|
|
|
{/* Desktop Navigation */}
|
|
|
|
|
|
<nav className="hidden md:flex items-center gap-6">
|
2025-12-14 02:38:35 +07:00
|
|
|
|
{isAuthenticated ? (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<Link
|
|
|
|
|
|
to="/marathons"
|
2025-12-17 02:03:33 +07:00
|
|
|
|
className={clsx(
|
|
|
|
|
|
'flex items-center gap-2 px-3 py-2 rounded-lg transition-all duration-200',
|
|
|
|
|
|
isActiveLink('/marathons')
|
|
|
|
|
|
? 'text-neon-400 bg-neon-500/10'
|
|
|
|
|
|
: 'text-gray-300 hover:text-white hover:bg-dark-700'
|
|
|
|
|
|
)}
|
2025-12-14 02:38:35 +07:00
|
|
|
|
>
|
|
|
|
|
|
<Trophy className="w-5 h-5" />
|
|
|
|
|
|
<span>Марафоны</span>
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
|
2025-12-17 02:03:33 +07:00
|
|
|
|
<div className="flex items-center gap-3 ml-2 pl-4 border-l border-dark-600">
|
2025-12-16 22:12:12 +07:00
|
|
|
|
<Link
|
|
|
|
|
|
to="/profile"
|
2025-12-17 02:03:33 +07:00
|
|
|
|
className={clsx(
|
|
|
|
|
|
'flex items-center gap-2 px-3 py-2 rounded-lg transition-all duration-200',
|
|
|
|
|
|
isActiveLink('/profile')
|
|
|
|
|
|
? 'text-neon-400 bg-neon-500/10'
|
|
|
|
|
|
: 'text-gray-300 hover:text-white hover:bg-dark-700'
|
|
|
|
|
|
)}
|
2025-12-16 22:12:12 +07:00
|
|
|
|
>
|
2025-12-14 02:38:35 +07:00
|
|
|
|
<User className="w-5 h-5" />
|
|
|
|
|
|
<span>{user?.nickname}</span>
|
2025-12-16 22:12:12 +07:00
|
|
|
|
</Link>
|
2025-12-14 02:38:35 +07:00
|
|
|
|
|
2025-12-16 20:06:16 +07:00
|
|
|
|
<TelegramLink />
|
|
|
|
|
|
|
2025-12-14 02:38:35 +07:00
|
|
|
|
<button
|
|
|
|
|
|
onClick={handleLogout}
|
2025-12-17 02:03:33 +07:00
|
|
|
|
className="p-2 text-gray-400 hover:text-red-400 hover:bg-red-500/10 rounded-lg transition-all duration-200"
|
2025-12-14 02:38:35 +07:00
|
|
|
|
title="Выйти"
|
|
|
|
|
|
>
|
|
|
|
|
|
<LogOut className="w-5 h-5" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
2025-12-17 02:03:33 +07:00
|
|
|
|
<Link
|
|
|
|
|
|
to="/login"
|
|
|
|
|
|
className="text-gray-300 hover:text-white transition-colors px-4 py-2"
|
|
|
|
|
|
>
|
2025-12-14 02:38:35 +07:00
|
|
|
|
Войти
|
|
|
|
|
|
</Link>
|
2025-12-17 02:03:33 +07:00
|
|
|
|
<Link
|
|
|
|
|
|
to="/register"
|
|
|
|
|
|
className="px-4 py-2 bg-neon-500 hover:bg-neon-400 text-dark-900 font-semibold rounded-lg transition-all duration-200 shadow-[0_0_15px_rgba(0,240,255,0.3)] hover:shadow-[0_0_25px_rgba(0,240,255,0.5)]"
|
|
|
|
|
|
>
|
2025-12-14 02:38:35 +07:00
|
|
|
|
Регистрация
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</nav>
|
2025-12-17 02:03:33 +07:00
|
|
|
|
|
|
|
|
|
|
{/* Mobile Menu Button */}
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
|
|
|
|
|
className="md:hidden p-2 text-gray-300 hover:text-white rounded-lg hover:bg-dark-700 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
{isMobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
|
|
|
|
|
|
</button>
|
2025-12-14 02:38:35 +07:00
|
|
|
|
</div>
|
2025-12-17 02:03:33 +07:00
|
|
|
|
|
|
|
|
|
|
{/* Mobile Menu */}
|
|
|
|
|
|
{isMobileMenuOpen && (
|
|
|
|
|
|
<div className="md:hidden bg-dark-800/95 backdrop-blur-lg border-t border-dark-600 animate-slide-in-down">
|
|
|
|
|
|
<div className="container mx-auto px-4 py-4 space-y-2">
|
|
|
|
|
|
{isAuthenticated ? (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<Link
|
|
|
|
|
|
to="/marathons"
|
|
|
|
|
|
className={clsx(
|
|
|
|
|
|
'flex items-center gap-3 px-4 py-3 rounded-lg transition-all',
|
|
|
|
|
|
isActiveLink('/marathons')
|
|
|
|
|
|
? 'text-neon-400 bg-neon-500/10'
|
|
|
|
|
|
: 'text-gray-300 hover:text-white hover:bg-dark-700'
|
|
|
|
|
|
)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Trophy className="w-5 h-5" />
|
|
|
|
|
|
<span>Марафоны</span>
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
<Link
|
|
|
|
|
|
to="/profile"
|
|
|
|
|
|
className={clsx(
|
|
|
|
|
|
'flex items-center gap-3 px-4 py-3 rounded-lg transition-all',
|
|
|
|
|
|
isActiveLink('/profile')
|
|
|
|
|
|
? 'text-neon-400 bg-neon-500/10'
|
|
|
|
|
|
: 'text-gray-300 hover:text-white hover:bg-dark-700'
|
|
|
|
|
|
)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<User className="w-5 h-5" />
|
|
|
|
|
|
<span>{user?.nickname}</span>
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
<div className="pt-2 border-t border-dark-600">
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={handleLogout}
|
|
|
|
|
|
className="flex items-center gap-3 w-full px-4 py-3 text-red-400 hover:bg-red-500/10 rounded-lg transition-all"
|
|
|
|
|
|
>
|
|
|
|
|
|
<LogOut className="w-5 h-5" />
|
|
|
|
|
|
<span>Выйти</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<Link
|
|
|
|
|
|
to="/login"
|
|
|
|
|
|
className="block px-4 py-3 text-gray-300 hover:text-white hover:bg-dark-700 rounded-lg transition-all"
|
|
|
|
|
|
>
|
|
|
|
|
|
Войти
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
<Link
|
|
|
|
|
|
to="/register"
|
|
|
|
|
|
className="block px-4 py-3 text-center bg-neon-500 hover:bg-neon-400 text-dark-900 font-semibold rounded-lg transition-all"
|
|
|
|
|
|
>
|
|
|
|
|
|
Регистрация
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2025-12-14 02:38:35 +07:00
|
|
|
|
</header>
|
|
|
|
|
|
|
2025-12-17 02:03:33 +07:00
|
|
|
|
{/* Spacer for fixed header */}
|
|
|
|
|
|
<div className="h-[72px]" />
|
|
|
|
|
|
|
2025-12-14 02:38:35 +07:00
|
|
|
|
{/* Main content */}
|
|
|
|
|
|
<main className="flex-1 container mx-auto px-4 py-8">
|
|
|
|
|
|
<Outlet />
|
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Footer */}
|
2025-12-17 02:03:33 +07:00
|
|
|
|
<footer className="bg-dark-800/50 border-t border-dark-600/50 py-6">
|
|
|
|
|
|
<div className="container mx-auto px-4">
|
|
|
|
|
|
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
|
|
|
|
|
|
<div className="flex items-center gap-2 text-gray-500">
|
|
|
|
|
|
<Gamepad2 className="w-5 h-5 text-neon-500/50" />
|
|
|
|
|
|
<span className="text-sm">
|
|
|
|
|
|
Игровой Марафон © {new Date().getFullYear()}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="flex items-center gap-4 text-sm text-gray-500">
|
|
|
|
|
|
<span className="text-neon-500/50">v1.0</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-12-14 02:38:35 +07:00
|
|
|
|
</div>
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|