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 */}
{visibleItems.length}
{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')}
{t('admin.panel.statsOnline')}
{/* 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 && (
)}
{IS_MAC ? '\u2318' : 'Ctrl+'}K
{/* 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')}
)}
);
}