Commit Graph

13 Commits

Author SHA1 Message Date
c0mrade
7f68e2cd5e fix(theme): readable text on any operator palette + admin consistency pass
Контраст-аудит (WCAG-обход всех текстовых узлов, 4 палитры × юзер+админ
страницы) показал системные провалы читаемости: вторичный текст 1.5-2.5
на светлой/кастомных темах, статусные цвета 1.3-1.6, белый текст на
светлых акцентах 1.9.

Корневые фиксы:
- useThemeColors: контраст-кламп производных серых токенов — dark-400/
  champagne-600 (вторичный текст) держат >=5.0 к поверхности, dark-500/
  champagne-500 (подсказки) >=3.8; палитры с достаточным контрастом
  рендерятся байт-в-байт как раньше
- --color-on-accent/-success/-warning/-error: чёрный или белый текст
  по фактической яркости цвета оператора; text-white на accent-заливках
  заменён на text-on-accent (135 мест + variant примитива Button +
  .btn-primary)
- .light: статусные текстовые оттенки *-300/400 ремапятся на тёмные
  *-700 (жёлтая «Админка» 1.26 -> 3.8+, зелёные суммы 1.4 -> 3.4+,
  бейджи состояний)

Замер после: операторская тёмная палитра — 0 нарушений (всё >=4.5),
худшие точки остальных подняты с 1.26-1.6 до 3.2+.

Консистентность:
- заголовки админ-страниц приведены к text-xl font-bold text-dark-100
  (были 18/20/24/30px и 600/700 вперемешку)
- «Создать FAQ»: warning-500/80+белый -> warning-500+text-on-warning
- formatTraffic: единицы из i18n (убран микс «GB»/«ГБ» на одном экране)
- categoryCanvas -> categoryCANVAS в локалях (битый ключ на настройках)
- «Управление сквадами» -> «Управление локациями» (юзерский экран)
- мин-макс сумм на выборе метода: dark-600 -> dark-400
- неактивные пункты нижнего нава: dark-500 -> dark-400
2026-06-11 15:49:17 +03:00
c0mrade
3b48abbb7b refactor(cabinet): unify all statistics onto the shared StatCard
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
2026-06-06 21:27:57 +03:00
c0mrade
800daf7506 fix(cabinet): sweep mobile flex-overflow across all pages + components
Full scan of every page/component for the Connected-Accounts overflow class
(long user data with no truncation pushing siblings off the card on mobile),
adversarially verified. Applied min-w-0 + truncate (break-all for copyable
values, break-words for titles) on text columns and shrink-0 on fixed
siblings across ~23 files: AppHeader, dashboard/SubscriptionCardActive,
subscription purchase/sheets (TariffPurchaseForm, ClassicPurchaseWizard,
SwitchTariffSheet), SuccessNotificationModal, PurchaseSuccess, Wheel sub
picker, ReferralNetwork UserDetailPanel, and the admin pages (AnalyticsTab,
BrandingTab, SubscriptionSubRow, user-detail Info/Referrals tabs,
ApplicationReview, PartnerCampaignAssign, Partners, PromocodeStats,
PromoOfferSend, RemnawaveSquadDetail, TariffCreate, Tickets, Withdrawals).
2026-06-04 23:32:59 +03:00
c0mrade
b5088c70a1 refactor(cabinet): migrate inline SVG icons to the central react-icons set
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.
2026-05-31 23:37:12 +03:00
c0mrade
d0e0b6b7e3 feat(cabinet): migrate all icons to the panel's Phosphor set
Replace the cabinet's hand-written heroicons-style SVG icon components with
the Remnawave panel's own icon family — Phosphor via react-icons/pi (Duotone).
All icons now live in a single central barrel (src/components/icons:
index + extended-icons + editor-icons, 144 icons); every feature file imports
from it instead of redefining inline SVGs (~440 icon defs removed).

- Add react-icons dependency
- Desktop nav (AppShell) now uses the central Phosphor icons, removing a
  stroke-vs-duotone inconsistency the partial migration introduced
- Icons with custom props (SortIcon's direction, expandable chevrons) kept as
  thin Phosphor wrappers; the RemnawaveIcon brand logo and animated SVGs
  (checkmarks, spinners) are intentionally left as-is
- Restore the original per-call-site icon sizes wherever the centralized
  default differed (211 call sites)
2026-05-31 18:50:50 +03:00
c0mrade
f75b243f03 polish: clear 33/34 detector findings (bg-black, animate-bounce, spinner, transition-width, color)
After re-audit/critique cycle the deterministic detector (npx impeccable)
flagged 34 design-spec violations. Knock out 33 of them.

  • 25 × pure-black-white: sed-sweep bg-black/X → bg-dark-950/X across
    18+ files (modal scrims, photo viewer backdrop, code blocks). The
    base resolves to rgba(10,15,26,X) — visually identical to true
    black, satisfies the 'no #000' impeccable rule.
  • 3 × bounce-easing: SuccessNotificationModal celebration icon and
    SyncTab loading arrows used animate-bounce; replaced with
    animate-pulse. Bounce easing reads dated; pulse conveys 'in
    progress' without the cartoon feel.
  • 3 × border-accent-on-rounded: TelegramCallback + VerifyEmail
    spinners used 'border-b-2 border-accent-500' on rounded-full —
    detector reads it as a side-stripe even though it's a ring loader.
    Switch to canonical 'border-2 border-accent-500 border-t-transparent'
    (3/4 ring colored). Same visual, no spec violation.
  • 1 × ai-color-palette: AdminLandingStats had text-purple-400 on a
    gift-stats heading; purple is not in the brand palette. Swap to
    text-accent-400.
  • 1 × layout-transition: TrafficProgressBar.tsx fill bar still used
    transition: width 1.2s (slipped past the earlier optimize pass).
    Convert to transform: scaleX with origin-left. Same gradient, same
    duration, runs on the compositor.

Remaining: 1 finding in third-party Aceternity background-beams-collision
component (indigo-500 gradient on decorative WebGL background) — left
as-is, it's lifted decorative third-party code.

Detector: 34 → 1.
2026-05-26 21:52:56 +03:00
c0mrade
251c862922 adapt(admin): mobile touch-target floor on shared .btn + ticket chat
Audit P2: admin pages prioritise density but cabinet also runs in the
Telegram Mini App on phones. Small button targets (~24-32px) work for
mouse precision but fail mobile thumbs. Apply targeted bumps that keep
desktop density unchanged.

src/styles/globals.css
  • .btn — add min-h-[44px] on mobile, sm:min-h-0 on desktop.
    Cascades to every .btn-primary / .btn-secondary / .btn-ghost /
    .btn-danger consumer (76 + 48 + N usages app-wide). One change,
    everywhere.
  • .btn-icon — same logic (min-h/min-w 44px mobile). Drops the
    duplicate definition that appeared further down the file.

components/admin/userDetail/TicketsTab.tsx — mobile-critical chat
  • Back button h-8 w-8 → h-10 w-10 sm:h-8 sm:w-8 (40px / 32px).
    Adds aria-label since it had none.
  • Status pills px-2.5 py-1 → +min-h-[36px] py-1.5 sm:min-h-0
    sm:py-1 (36px / 26px). Mis-tap penalty on mobile is high (it
    flips ticket status).
  • Reply send button — add min-h/min-w 44px mobile + aria-label.

Why not blanket bump py-1.5 → py-2.5 across all 40+ admin pages:
visual density patterns were iteratively designed and a global sweep
would regress them. Per-page review can target specific buttons that
deserve mobile touch floor without breaking density elsewhere.

icon-only buttons not using .btn-icon (raw <button><svg/></button>)
left as-is — they're typically in compact toolbars where visual
density is the point. Per-page review under /impeccable adapt
target=<page> if mobile flow analysis flags one.
2026-05-26 20:53:40 +03:00
c0mrade
9afa7f131f refactor(admin-user-detail): extract Subscription tab — page under 1000 lines
Last and biggest tab — extracted via view-facade pattern (like Info).
JSX moves into components/admin/userDetail/SubscriptionTab.tsx;
state, queries, mutation handlers stay in parent because most are
shared with other tabs (panelInfo + devicesQuery used by Info,
inline-confirm armer used by destructive actions, etc.).

The new file is 1223 lines — over the soft cap, but cohesive: one
tab with multi-sub list, sub-detail view, traffic packages,
devices CRUD with inline rename, panel info/config, live traffic,
node usage, and request history table. Sub-extracting (Devices /
RequestHistory / PanelLinks) is the next step but not free —
each adds prop-passing boilerplate.

Wide prop interface (~50 props) but every prop is a single pass-
through line at the call site. No state moves; just JSX reroute.

Parent imports trimmed: DEVICE_ALIAS_MAX_LENGTH, getFlagEmoji,
createNumberInputHandler, getCountryFlag, PlusIcon, MinusIcon,
StatusBadge — all gone, subscription tab was their last consumer.
RefreshIcon kept (page header reload button still uses it).

Net to AdminUserDetail.tsx: down to 970 lines (-1022). Total
across the 7 tab extractions: 3820 → 970 (-2850, -75%).
2026-05-26 18:39:08 +03:00
c0mrade
f6f3c21768 refactor(admin-user-detail): extract Info tab as view facade
The info tab needed a different shape than the prior 5 — its
panelInfo, promoGroups, editingPromoGroup, action handlers
(reset-trial, disable, full-delete) are SHARED with the subscription
tab and with the inline-confirm armer up in the parent. Moving any
of that into the tab would either duplicate the query/state or
fork the confirm state. So the new InfoTab is a 'view facade':
state and handlers stay parent-owned, the JSX moves out.

Prop surface is wide (~25 props) but every prop is a single
'pass through' line at the call site — no logic shifted. Each
handler / state slot still lives in exactly one place; the tab
only renders.

Bonus: the parent's formatWithCurrency / useCurrency import is
dropped — info tab was the last consumer in the page body.

Net to AdminUserDetail.tsx: down to 1992 lines (-403). Total
journey across the 6 tab extractions: 3820 → 1992 (-1828, -48%).
Only the subscription tab JSX (~1043 lines) remains inline.
2026-05-26 18:17:27 +03:00
c0mrade
fab2a504d3 refactor(admin-user-detail): extract Tickets tab (list + chat view)
Pulls the tickets tab into components/admin/userDetail/TicketsTab.tsx:
  - ticketsQuery (list)
  - selectedTicketId / selectedTicket state + detail loader
  - reply form state (replyText, replySending)
  - status-change action handler + actionLoading
  - auto-scroll messagesEndRef
  - JSX is split into ChatView + TicketsList + EmptyState helpers
    inside the new file (file stays under 400 lines despite the
    split-view UI)

Parent now passes only userId + formatDate. Imports for adminApi,
MessageMediaGrid, linkifyText, AdminTicket / AdminTicketDetail types
all drop from AdminUserDetail.tsx — tickets are the only consumers.

Net to AdminUserDetail.tsx: down to 2395 lines (-295). Same DOM,
same event handlers, same queryKey ['admin-user-tickets', userId].
2026-05-26 18:00:02 +03:00
c0mrade
24c57cf985 refactor(admin-user-detail): extract Balance tab with own state + mutations
Pulls the balance tab — current balance, add/subtract form, active
promo offer summary + deactivate, send-offer form, recent
transactions list — into components/admin/userDetail/BalanceTab.tsx.
Form state (balanceAmount, balanceDescription, offerDiscountPercent,
offerValidHours), mutation handlers (handleUpdateBalance,
handleDeactivateOffer, handleSendOffer), and the inline two-click
confirm armer all live local now.

Tab takes a small prop surface — user, userId, hasPermission,
onUserRefresh, formatDate — and owns its own actionLoading /
offerSending flags so the deactivate-offer confirm doesn't dim
buttons on other tabs.

Net to AdminUserDetail.tsx: down to 2690 lines (-212). The
promoOffersApi import is dropped from the parent — it's only the
balance tab that talks to that endpoint now. PlusIcon / MinusIcon
are still used by the subscription tab in the parent, so they stay
there too.
2026-05-26 17:51:24 +03:00
c0mrade
18e030c9b9 refactor(admin-user-detail): extract Referrals tab with own state + query
Pulls the entire referrals tab into components/admin/userDetail/
ReferralsTab.tsx — state, useQuery, debounced search effects,
click-outside listeners, mutation handlers, and the 295-line JSX
block all live there now. The tab owns its referralsListQuery and
calls onUserRefresh() (parent's loadUser) when a mutation needs to
re-fetch the user object.

Net to AdminUserDetail.tsx: down to 2902 lines (-479). Local
'actionLoading' inside the tab means referral-mutation clicks no
longer dim balance/sync buttons. Same DOM, same handlers, same
queryKey ['admin-user-referrals-list', userId].
2026-05-26 17:43:58 +03:00
c0mrade
b9acc822f2 refactor(admin-user-detail): extract Gifts + Sync tabs to own components
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.
2026-05-26 17:12:47 +03:00