Add admin panel

This commit is contained in:
2025-12-19 02:07:25 +07:00
parent 8e634994bd
commit 481bdabaa8
40 changed files with 3526 additions and 112 deletions

View File

@@ -0,0 +1,169 @@
import { Outlet, NavLink, Navigate, Link } from 'react-router-dom'
import { useAuthStore } from '@/store/auth'
import { NeonButton } from '@/components/ui'
import {
LayoutDashboard,
Users,
Trophy,
ScrollText,
Send,
FileText,
ArrowLeft,
Shield,
MessageCircle,
Sparkles,
Lock
} from 'lucide-react'
const navItems = [
{ to: '/admin', icon: LayoutDashboard, label: 'Дашборд', end: true },
{ to: '/admin/users', icon: Users, label: 'Пользователи' },
{ to: '/admin/marathons', icon: Trophy, label: 'Марафоны' },
{ to: '/admin/logs', icon: ScrollText, label: 'Логи' },
{ to: '/admin/broadcast', icon: Send, label: 'Рассылка' },
{ to: '/admin/content', icon: FileText, label: 'Контент' },
]
export function AdminLayout() {
const user = useAuthStore((state) => state.user)
// Only admins can access
if (!user || user.role !== 'admin') {
return <Navigate to="/" replace />
}
// Admin without Telegram - show warning
if (!user.telegram_id) {
return (
<div className="min-h-[70vh] flex flex-col items-center justify-center text-center px-4">
{/* Background effects */}
<div className="fixed inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-1/3 -left-32 w-96 h-96 bg-amber-500/5 rounded-full blur-[100px]" />
<div className="absolute bottom-1/3 -right-32 w-96 h-96 bg-accent-500/5 rounded-full blur-[100px]" />
</div>
{/* Icon */}
<div className="relative mb-8 animate-float">
<div className="w-32 h-32 rounded-3xl bg-dark-700/50 border border-amber-500/30 flex items-center justify-center shadow-[0_0_30px_rgba(245,158,11,0.15)]">
<Lock className="w-16 h-16 text-amber-400" />
</div>
<div className="absolute -bottom-2 -right-2 w-12 h-12 rounded-xl bg-accent-500/20 border border-accent-500/30 flex items-center justify-center">
<Shield className="w-6 h-6 text-accent-400" />
</div>
{/* Decorative dots */}
<div className="absolute -top-2 -left-2 w-3 h-3 rounded-full bg-amber-500/50 animate-pulse" />
<div className="absolute top-4 -right-4 w-2 h-2 rounded-full bg-accent-500/50 animate-pulse" style={{ animationDelay: '0.5s' }} />
</div>
{/* Title with glow */}
<div className="relative mb-4">
<h1 className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-amber-400 via-orange-400 to-accent-400">
Требуется Telegram
</h1>
<div className="absolute inset-0 text-3xl font-bold text-amber-500/20 blur-xl">
Требуется Telegram
</div>
</div>
<p className="text-gray-400 mb-2 max-w-md">
Для доступа к админ-панели необходимо привязать Telegram-аккаунт.
</p>
<p className="text-gray-500 text-sm mb-8 max-w-md">
Это требуется для двухфакторной аутентификации при входе.
</p>
{/* Info card */}
<div className="glass rounded-xl p-4 mb-8 max-w-md border border-amber-500/20">
<div className="flex items-center gap-2 text-amber-400 mb-2">
<Shield className="w-4 h-4" />
<span className="text-sm font-semibold">Двухфакторная аутентификация</span>
</div>
<p className="text-gray-400 text-sm">
После привязки Telegram при входе в админ-панель вам будет отправляться код подтверждения.
</p>
</div>
{/* Buttons */}
<div className="flex gap-4">
<Link to="/profile">
<NeonButton size="lg" color="purple" icon={<MessageCircle className="w-5 h-5" />}>
Привязать Telegram
</NeonButton>
</Link>
<Link to="/marathons">
<NeonButton size="lg" variant="secondary" icon={<ArrowLeft className="w-5 h-5" />}>
На сайт
</NeonButton>
</Link>
</div>
{/* Decorative sparkles */}
<div className="absolute top-1/4 left-1/4 opacity-20">
<Sparkles className="w-6 h-6 text-amber-400 animate-pulse" />
</div>
<div className="absolute bottom-1/3 right-1/4 opacity-20">
<Sparkles className="w-4 h-4 text-accent-400 animate-pulse" style={{ animationDelay: '1s' }} />
</div>
</div>
)
}
return (
<div className="flex h-full min-h-[calc(100vh-64px)]">
{/* Background effects */}
<div className="fixed inset-0 overflow-hidden pointer-events-none">
<div className="absolute top-0 -left-32 w-96 h-96 bg-accent-500/5 rounded-full blur-[100px]" />
<div className="absolute bottom-0 -right-32 w-96 h-96 bg-neon-500/5 rounded-full blur-[100px]" />
</div>
{/* Sidebar */}
<aside className="w-64 glass border-r border-dark-600 flex flex-col relative z-10">
<div className="p-4 border-b border-dark-600">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-accent-500 to-pink-500 flex items-center justify-center">
<Shield className="w-4 h-4 text-white" />
</div>
<h2 className="text-lg font-bold text-transparent bg-clip-text bg-gradient-to-r from-accent-400 to-pink-400">
Админ-панель
</h2>
</div>
</div>
<nav className="flex-1 p-4 space-y-1">
{navItems.map((item) => (
<NavLink
key={item.to}
to={item.to}
end={item.end}
className={({ isActive }) =>
`flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-200 ${
isActive
? 'bg-accent-500/20 text-accent-400 border border-accent-500/30 shadow-[0_0_10px_rgba(139,92,246,0.15)]'
: 'text-gray-400 hover:bg-dark-600/50 hover:text-white border border-transparent'
}`
}
>
<item.icon className="w-5 h-5" />
<span className="font-medium">{item.label}</span>
</NavLink>
))}
</nav>
<div className="p-4 border-t border-dark-600">
<NavLink
to="/marathons"
className="flex items-center gap-3 px-3 py-2.5 text-gray-400 hover:text-neon-400 transition-colors rounded-lg hover:bg-dark-600/50"
>
<ArrowLeft className="w-5 h-5" />
<span className="font-medium">Вернуться на сайт</span>
</NavLink>
</div>
</aside>
{/* Main content */}
<main className="flex-1 p-6 overflow-auto relative z-10">
<Outlet />
</main>
</div>
)
}