217 lines
8.1 KiB
TypeScript
217 lines
8.1 KiB
TypeScript
import { useState, useEffect } from 'react'
|
||
import { Outlet, Link, useNavigate, useLocation } from 'react-router-dom'
|
||
import { useAuthStore } from '@/store/auth'
|
||
import { Gamepad2, LogOut, Trophy, User, Menu, X } from 'lucide-react'
|
||
import { TelegramLink } from '@/components/TelegramLink'
|
||
import { clsx } from 'clsx'
|
||
|
||
export function Layout() {
|
||
const { user, isAuthenticated, logout } = useAuthStore()
|
||
const navigate = useNavigate()
|
||
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])
|
||
|
||
const handleLogout = () => {
|
||
logout()
|
||
navigate('/login')
|
||
}
|
||
|
||
const isActiveLink = (path: string) => location.pathname === path
|
||
|
||
return (
|
||
<div className="min-h-screen flex flex-col">
|
||
{/* Header */}
|
||
<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'
|
||
)}
|
||
>
|
||
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
|
||
{/* 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>
|
||
</Link>
|
||
|
||
{/* Desktop Navigation */}
|
||
<nav className="hidden md:flex items-center gap-6">
|
||
{isAuthenticated ? (
|
||
<>
|
||
<Link
|
||
to="/marathons"
|
||
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'
|
||
)}
|
||
>
|
||
<Trophy className="w-5 h-5" />
|
||
<span>Марафоны</span>
|
||
</Link>
|
||
|
||
<div className="flex items-center gap-3 ml-2 pl-4 border-l border-dark-600">
|
||
<Link
|
||
to="/profile"
|
||
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'
|
||
)}
|
||
>
|
||
<User className="w-5 h-5" />
|
||
<span>{user?.nickname}</span>
|
||
</Link>
|
||
|
||
<TelegramLink />
|
||
|
||
<button
|
||
onClick={handleLogout}
|
||
className="p-2 text-gray-400 hover:text-red-400 hover:bg-red-500/10 rounded-lg transition-all duration-200"
|
||
title="Выйти"
|
||
>
|
||
<LogOut className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
</>
|
||
) : (
|
||
<>
|
||
<Link
|
||
to="/login"
|
||
className="text-gray-300 hover:text-white transition-colors px-4 py-2"
|
||
>
|
||
Войти
|
||
</Link>
|
||
<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)]"
|
||
>
|
||
Регистрация
|
||
</Link>
|
||
</>
|
||
)}
|
||
</nav>
|
||
|
||
{/* 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>
|
||
</div>
|
||
|
||
{/* 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>
|
||
)}
|
||
</header>
|
||
|
||
{/* Spacer for fixed header */}
|
||
<div className="h-[72px]" />
|
||
|
||
{/* Main content */}
|
||
<main className="flex-1 container mx-auto px-4 py-8">
|
||
<Outlet />
|
||
</main>
|
||
|
||
{/* Footer */}
|
||
<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>
|
||
</div>
|
||
</footer>
|
||
</div>
|
||
)
|
||
}
|