Bring every stat strip across the admin panel and user cabinet onto the
canonical StatCard (icon chip + label-on-top + tone-coloured value),
matching the sales/Remnawave style the rest of the app already used.
- Convert ~20 hand-rolled stat strips across 21 pages/components
- Remove 4 divergent local StatCard components (Dashboard, Users,
BanSystem, Payments) and migrate Remnawave's local StatCard (33 cards)
- Extend StatCard with optional subValue, loading skeleton, trailing slot,
and icon-size normalisation (all backward-compatible)
- StatsGrid nav tiles now use StatCard while keeping link/chevron/loading
- Preserve all data, i18n keys, conditionals and interactive filters;
fix the dead 'info' colour by mapping it to accent
Audit-driven sweep: replaced 168 hand-written inline <svg> icons across 77
files with the central Phosphor (react-icons/pi) components from
@/components/icons, preserving each icon's size classes and colour (dynamic
stroke colours via the parent's currentColor, RefreshIcon's spinning state,
conditional rotate-180 chevrons).
Verified: tsc + vite build + eslint clean; an adversarial diff review of all
changed files found the replacements correct (70 files clean, 0 blockers).
Remaining inline <svg> dropped from 262 to ~95 — the survivors are legitimate
non-icons (brand/provider logos, loading spinners & framer-motion animations,
charts/sparklines, background decoration) plus a few ambiguous glyphs.
AdminUserDetail.tsx was 3820 lines — a single page bigger than the
project's 800-line soft cap by 4.7x. Split the two most self-contained
tabs (no mutations / setState from within the tab body) into their
own files under src/components/admin/userDetail/:
GiftsTab.tsx (299 lines)
- GiftStatusBadge (badge for one of 6 lifecycle states)
- GiftCard (single row, sent or received)
- GiftsTab (sent + received sections + counters)
props: giftsLoading, giftsData, locale, onNavigateToUser
SyncTab.tsx (207 lines)
- ArrowDownIcon, ArrowUpIcon (used only by sync action buttons)
- SyncTab (bot vs panel comparison + 2-way sync)
props: user, syncStatus, userSubscriptions, activeSubscriptionId,
onActiveSubscriptionChange, actionLoading, onSyncFromPanel,
onSyncToPanel, locale
Net: AdminUserDetail.tsx down to 3381 lines (-439). No behaviour change:
the tabs render the exact same DOM, same SVG paths, same event handlers
just routed through props.