import { useState, useEffect, useRef, useCallback, useMemo, memo } from 'react'; import { Link } from 'react-router'; import { useTranslation } from 'react-i18next'; import { useQuery } from '@tanstack/react-query'; import { usePermissionStore } from '@/store/permissions'; import { statsApi, type SystemInfo, type DashboardStats } from '@/api/admin'; import { useAnimatedNumber } from '@/hooks/useAnimatedNumber'; import { useTelegramSDK } from '@/hooks/useTelegramSDK'; import { cn } from '@/lib/utils'; import { ArrowUpIcon, BroadcastIcon, CabinetIcon, ChartBarIcon, ChevronRightIcon, ClipboardIcon, CreditCardIcon, FileTextIcon, GiftIcon, HistoryIcon, LockIcon, MailIcon, MegaphoneIcon, NewsIcon, PartnerIcon, PercentIcon, PinIcon, RemnawaveIcon, SearchIcon, SendIcon, ServerIcon, SettingsIcon, ShareIcon, ShieldIcon, SparklesIcon, StatBotIcon, StatCabinetIcon, StatPaidIcon, StatsChartIcon, StatTrialIcon, StatUptimeIcon, SyncIcon, TagIcon, TicketIcon, TrafficIcon, UserPlusIcon, UsersIcon, WalletIcon, WheelIcon, XIcon, } from '@/components/icons'; const CABINET_VERSION = __APP_VERSION__; const IS_MAC = /Mac|iPhone|iPod|iPad/i.test(navigator.userAgent); // Section nav icons (24x24 viewBox) const icons = { 'bar-chart': , 'credit-card': , activity: , trending: , users: , ticket: , 'shield-alert': , tag: , gift: , percent: , sparkle: , wallet: , layout: , newspaper: , megaphone: , send: , pin: , 'circle-dot': , handshake: , 'arrow-up': , network: , radio: , settings: , app: , server: , remnawave: , mail: , refresh: , shield: , 'user-check': , lock: , scroll: , 'list-checks': , search: , 'file-text': , chevron: , x: , } as const; type IconName = keyof typeof icons; // ─── Section Data ─── interface AdminNavItem { name: string; icon: IconName; to: string; permission: string; } interface AdminSection { id: string; titleKey: string; accent: string; gradient: string; items: AdminNavItem[]; } const sections: AdminSection[] = [ { id: 'analytics', titleKey: 'admin.groups.analytics', accent: 'rgb(var(--color-success-400))', gradient: 'linear-gradient(135deg, rgb(var(--color-success-400)), rgb(var(--color-accent-500)))', items: [ { name: 'admin.nav.dashboard', icon: 'bar-chart', to: '/admin/dashboard', permission: 'stats:read', }, { name: 'admin.nav.payments', icon: 'credit-card', to: '/admin/payments', permission: 'payments:read', }, { name: 'admin.nav.trafficUsage', icon: 'activity', to: '/admin/traffic-usage', permission: 'traffic:read', }, { name: 'admin.nav.salesStats', icon: 'trending', to: '/admin/sales-stats', permission: 'sales_stats:read', }, ], }, { id: 'users', titleKey: 'admin.groups.users', accent: 'rgb(var(--color-accent-400))', gradient: 'linear-gradient(135deg, rgb(var(--color-accent-400)), rgb(var(--color-error-400)))', items: [ { name: 'admin.nav.users', icon: 'users', to: '/admin/users', permission: 'users:read' }, { name: 'admin.nav.bulkActions', icon: 'list-checks', to: '/admin/bulk-actions', permission: 'bulk_actions:read', }, { name: 'admin.nav.tickets', icon: 'ticket', to: '/admin/tickets', permission: 'tickets:read', }, { name: 'admin.nav.banSystem', icon: 'shield-alert', to: '/admin/ban-system', permission: 'ban_system:read', }, ], }, { id: 'tariffs', titleKey: 'admin.groups.tariffs', accent: 'rgb(var(--color-warning-400))', gradient: 'linear-gradient(135deg, rgb(var(--color-warning-400)), rgb(var(--color-error-300)))', items: [ { name: 'admin.nav.tariffs', icon: 'tag', to: '/admin/tariffs', permission: 'tariffs:read' }, { name: 'admin.nav.promocodes', icon: 'gift', to: '/admin/promocodes', permission: 'promocodes:read', }, { name: 'admin.nav.promoGroups', icon: 'percent', to: '/admin/promo-groups', permission: 'promo_groups:read', }, { name: 'admin.nav.promoOffers', icon: 'sparkle', to: '/admin/promo-offers', permission: 'promo_offers:read', }, { name: 'admin.nav.paymentMethods', icon: 'wallet', to: '/admin/payment-methods', permission: 'payment_methods:read', }, { name: 'admin.nav.landings', icon: 'layout', to: '/admin/landings', permission: 'landings:read', }, ], }, { id: 'marketing', titleKey: 'admin.groups.marketing', accent: 'rgb(var(--color-accent-300))', gradient: 'linear-gradient(135deg, rgb(var(--color-accent-300)), rgb(var(--color-accent-600)))', items: [ { name: 'admin.nav.news', icon: 'newspaper', to: '/admin/news', permission: 'news:read' }, { name: 'admin.nav.campaigns', icon: 'megaphone', to: '/admin/campaigns', permission: 'campaigns:read', }, { name: 'admin.nav.broadcasts', icon: 'send', to: '/admin/broadcasts', permission: 'broadcasts:read', }, { name: 'admin.nav.pinnedMessages', icon: 'pin', to: '/admin/pinned-messages', permission: 'pinned_messages:read', }, { name: 'admin.nav.wheel', icon: 'circle-dot', to: '/admin/wheel', permission: 'wheel:read' }, { name: 'admin.nav.partners', icon: 'handshake', to: '/admin/partners', permission: 'partners:read', }, { name: 'admin.nav.withdrawals', icon: 'arrow-up', to: '/admin/withdrawals', permission: 'withdrawals:read', }, { name: 'admin.nav.referralNetwork', icon: 'network', to: '/admin/referral-network', permission: 'stats:read', }, ], }, { id: 'system', titleKey: 'admin.groups.system', accent: 'rgb(var(--color-accent-500))', gradient: 'linear-gradient(135deg, rgb(var(--color-accent-500)), rgb(var(--color-success-500)))', items: [ { name: 'admin.nav.channelSubscriptions', icon: 'radio', to: '/admin/channel-subscriptions', permission: 'channels:read', }, { name: 'admin.nav.settings', icon: 'settings', to: '/admin/settings', permission: 'settings:read', }, { name: 'admin.nav.apps', icon: 'app', to: '/admin/apps', permission: 'apps:read' }, { name: 'admin.nav.servers', icon: 'server', to: '/admin/servers', permission: 'servers:read', }, { name: 'admin.nav.remnawave', icon: 'remnawave', to: '/admin/remnawave', permission: 'remnawave:read', }, { name: 'admin.nav.emailTemplates', icon: 'mail', to: '/admin/email-templates', permission: 'email_templates:read', }, { name: 'admin.nav.infoPages', icon: 'file-text', to: '/admin/info-pages', permission: 'info_pages:read', }, { name: 'admin.nav.updates', icon: 'refresh', to: '/admin/updates', permission: 'updates:read', }, ], }, { id: 'security', titleKey: 'admin.groups.security', accent: 'rgb(var(--color-error-400))', gradient: 'linear-gradient(135deg, rgb(var(--color-error-400)), rgb(var(--color-accent-600)))', items: [ { name: 'admin.nav.roles', icon: 'shield', to: '/admin/roles', permission: 'roles:read' }, { name: 'admin.nav.roleAssign', icon: 'user-check', to: '/admin/roles/assign', permission: 'roles:assign', }, { name: 'admin.nav.policies', icon: 'lock', to: '/admin/policies', permission: 'roles:read' }, { name: 'admin.nav.auditLog', icon: 'scroll', to: '/admin/audit-log', permission: 'audit_log:read', }, ], }, ]; // ─── Helpers ─── function formatUptime(seconds: number): string { const d = Math.floor(seconds / 86400); const h = Math.floor((seconds % 86400) / 3600); const m = Math.floor((seconds % 3600) / 60); if (d > 0) return `${d}d ${h}h ${m}m`; if (h > 0) return `${h}h ${m}m`; return `${m}m`; } // ─── Animated Stat Number ─── function AnimatedStat({ value, suffix }: { value: number; suffix?: string }) { const animated = useAnimatedNumber(value); return ( {Math.round(animated).toLocaleString()} {suffix} ); } // ─── Stats Bar ─── interface StatsBarProps { systemInfo: SystemInfo | null; dashboardStats: DashboardStats | null; loading: boolean; } const StatsBar = memo(function StatsBar({ systemInfo, dashboardStats, loading }: StatsBarProps) { const { t } = useTranslation(); const stats = useMemo(() => { const uptime = systemInfo?.uptime_seconds ?? 0; const trial = dashboardStats?.subscriptions.trial ?? 0; const paid = dashboardStats?.subscriptions.paid ?? 0; const purchasedToday = dashboardStats?.subscriptions.purchased_today ?? 0; return [ { icon: , label: t('admin.panel.statsUptime'), value: uptime > 0 ? formatUptime(uptime) : '--', colorClass: 'text-success-400 bg-success-400/10 border-success-400/20', }, { icon: , label: t('admin.panel.statsBot'), value: systemInfo?.bot_version ?? '--', colorClass: 'text-accent-400 bg-accent-400/10 border-accent-400/20', }, { icon: , label: t('admin.panel.statsCabinet'), value: `v${CABINET_VERSION}`, colorClass: 'text-accent-300 bg-accent-300/10 border-accent-300/20', }, { icon: , label: t('admin.panel.statsTrials'), numericValue: trial, colorClass: 'text-warning-400 bg-warning-400/10 border-warning-400/20', }, { icon: , label: t('admin.panel.statsPaid'), numericValue: paid, delta: purchasedToday > 0 ? `+${purchasedToday}` : undefined, colorClass: 'text-success-400 bg-success-400/10 border-success-400/20', }, ]; }, [systemInfo, dashboardStats, t]); return (
{stats.map((s, i) => (
{s.icon}
{'numericValue' in s && s.numericValue !== undefined ? ( ) : ( {s.value} )} {s.delta && ( {s.delta} )} {s.label} {s.delta && ` · ${t('admin.panel.statsToday')}`}
))}
); }); // ─── Glass Card (Section) ─── interface GlassCardProps { section: AdminSection; index: number; searchTerm: string; } const GlassCard = memo(function GlassCard({ section, index, searchTerm }: GlassCardProps) { const { t } = useTranslation(); const hasPermission = usePermissionStore((state) => state.hasPermission); const [hoveredItem, setHoveredItem] = useState(null); // 3D mouse-tracking tilt removed: decorative motion that didn't convey state. // Hover styling alone signals interactivity. void index; const visibleItems = useMemo( () => section.items.filter((item) => { if (!hasPermission(item.permission)) return false; if (!searchTerm) return true; return t(item.name).toLowerCase().includes(searchTerm.toLowerCase()); }), [section.items, hasPermission, searchTerm, t], ); const highlightMatch = useCallback( (text: string) => { if (!searchTerm) return text; const idx = text.toLowerCase().indexOf(searchTerm.toLowerCase()); if (idx === -1) return text; return ( <> {text.slice(0, idx)} {text.slice(idx, idx + searchTerm.length)} {text.slice(idx + searchTerm.length)} ); }, [searchTerm], ); if (visibleItems.length === 0) return null; return (
{/* Header */}

{t(section.titleKey)}

{/* Items */}
{visibleItems.map((item, i) => ( setHoveredItem(i)} onMouseLeave={() => setHoveredItem(null)} > {/* Icon */}
{icons[item.icon]}
{/* Label */} {highlightMatch(t(item.name))} {/* Chevron */}
{icons.chevron}
))}
); }); // ─── Main Component ─── export default function AdminPanel() { const { t } = useTranslation(); const [search, setSearch] = useState(''); const inputRef = useRef(null); const { safeAreaInset, contentSafeAreaInset } = useTelegramSDK(); const safeTop = Math.max(safeAreaInset.top, contentSafeAreaInset.top); const safeBottom = Math.max(safeAreaInset.bottom, contentSafeAreaInset.bottom); // System info + dashboard stats — polled every 60s via React Query // (replaces the manual setInterval + useState + cancelled-flag pattern). const systemInfoQuery = useQuery({ queryKey: ['admin-panel-system-info'] as const, queryFn: () => statsApi.getSystemInfo(), refetchInterval: 60_000, refetchOnWindowFocus: false, }); const dashboardStatsQuery = useQuery({ queryKey: ['admin-panel-dashboard-stats'] as const, queryFn: () => statsApi.getDashboardStats(), refetchInterval: 60_000, refetchOnWindowFocus: false, }); const systemInfo = systemInfoQuery.data ?? null; const dashboardStats = dashboardStatsQuery.data ?? null; // "loading" only counts the very first fetch — once we have any data, render it. const loading = systemInfoQuery.isLoading || dashboardStatsQuery.isLoading; // Keyboard shortcuts: Cmd+K to focus search, Escape to clear useEffect(() => { const handler = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); inputRef.current?.focus(); } if (e.key === 'Escape') { setSearch(''); inputRef.current?.blur(); } }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); }, []); // Track which sections have matching items (keeps original section refs for memo stability) const visibleSectionIds = useMemo(() => { if (!search.trim()) return null; // null = show all const lower = search.toLowerCase(); return new Set( sections .filter((s) => s.items.some((item) => t(item.name).toLowerCase().includes(lower))) .map((s) => s.id), ); }, [search, t]); return (
0 ? `${safeTop}px` : 'env(safe-area-inset-top, 0px)', paddingBottom: safeBottom > 0 ? `${safeBottom}px` : 'env(safe-area-inset-bottom, 0px)', }} > {/* Stats Bar */}
{/* Mobile: compact 2-column stats */}
{[ { icon: , label: t('admin.panel.statsUptime'), value: systemInfo?.uptime_seconds ? formatUptime(systemInfo.uptime_seconds) : '--', cls: 'text-success-400', }, { icon: , label: t('admin.panel.statsBot'), value: systemInfo?.bot_version ?? '--', cls: 'text-accent-400', }, { icon: , label: t('admin.panel.statsTrials'), value: dashboardStats?.subscriptions.trial?.toLocaleString() ?? '--', cls: 'text-warning-400', }, { icon: , label: t('admin.panel.statsPaid'), value: dashboardStats?.subscriptions.paid?.toLocaleString() ?? '--', delta: (dashboardStats?.subscriptions.purchased_today ?? 0) > 0 ? `+${dashboardStats?.subscriptions.purchased_today}` : undefined, cls: 'text-success-400', }, ].map((s, i) => (
{s.icon}
{s.value} {'delta' in s && s.delta && ( {s.delta} )} {s.label}
))}
{/* Hero + Search */}

{t('admin.panel.title')}

{/* Search */}
{icons.search}
setSearch(e.target.value)} placeholder={t('admin.panel.searchPlaceholder')} aria-label={t('admin.panel.searchPlaceholder')} className="w-full rounded-xl border border-dark-700/50 bg-dark-800/40 py-2 pl-8 pr-16 font-sans text-xs text-dark-100 outline-none backdrop-blur-lg transition-all placeholder:text-dark-500 focus:border-accent-500/40 focus:shadow-[0_0_0_3px_rgba(var(--color-accent-500),0.08)] light:border-champagne-300/50 light:bg-champagne-100/60 light:text-champagne-900 light:placeholder:text-champagne-500 light:focus:border-accent-500/40" /> {search && ( )}
{/* Grid */}
{visibleSectionIds === null || visibleSectionIds.size > 0 ? (
{sections .filter((s) => !visibleSectionIds || visibleSectionIds.has(s.id)) .map((section, i) => ( ))}
) : (
{icons.search}

{t('admin.panel.searchEmpty')}

{t('admin.panel.searchEmptyHint')}

)}
); }