Commit Graph

36 Commits

Author SHA1 Message Date
kewldan
81fd96c2a7 fix(i18n): форматировать даты и числа локалью выбранного языка, а не браузера 2026-07-14 00:04:45 +03:00
Fringg
63b19645e6 Merge PR #478: stagger-анимация скрывала контент, пришедший из API после mount
framer-motion stagger раздаёт детям команду показаться один раз при монтировании
родителя; дети, смонтированные позже (когда resolve-ится react-query), оставались
в initial (opacity:0) — пустые страницы после F5/прямого захода.

- ConnectedAccounts, SavedCards: key={isLoading?...} перемонтирует контейнер
  после загрузки → stagger проигрывается со всеми детьми;
- Balance (2), Profile, Support: явные initial/animate на условных staggerItem —
  элемент самоанимируется при mount, вне оркестрации родителя;
- Wheel: исправлены несуществующие варианты hidden/show → initial/animate
  (stagger истории спинов молча не работал).

Валидация на смердженном дереве: tsc, biome lint/format, build — чисто.
Правки #477 и #478 в общих файлах (ConnectedAccounts/Profile) сосуществуют.
2026-07-12 23:45:08 +03:00
kewldan
adaaa23da3 fix(ui): контент, пришедший из API после mount, оставался невидимым (stagger)
Оркестрация staggerContainer/staggerItem у framer-motion раздаёт детям
команду «показаться» один раз — при анимации родителя на mount. Дети,
смонтированные позже (когда resolve'ится запрос react-query), в
оркестрацию не попадают и навсегда остаются в initial (opacity: 0, y: 8).

Симптом: после жёсткой перезагрузки (F5) на странице виден только
заголовок — карточки есть в DOM, но прозрачны. При SPA-навигации кэш
react-query тёплый, дети монтируются вместе с родителем — поэтому баг
проявлялся только при холодном заходе и маскировался на проде (нет
StrictMode, same-origin API, тёплый кэш почти всегда выигрывают гонку
с ~300-мс окном анимации).

Два способа фикса в зависимости от формы страницы:

1. key={isLoading ? 'loading' : 'ready'} на контейнере — перемонтирование
   после загрузки, stagger проигрывается заново уже со всеми детьми.
   Для страниц, где весь контент асинхронный (каскад сохраняется):
   - ConnectedAccounts — карточки провайдеров
   - SavedCards — список сохранённых карт

2. Явные initial="initial" animate="animate" на условном staggerItem —
   элемент анимирует себя сам, не дожидаясь оркестрации родителя. Для
   страниц, где асинхронны только 1-2 опциональные карточки (не
   перезапускает анимацию всей страницы на каждый resolve):
   - Balance — «Способы пополнения» и ссылка «Сохранённые карты»
   - Support — карточка «Написать нам» (support_type='both')
   - Profile — реферальный виджет

Попутно: в Wheel у контейнера истории спинов стояли несуществующие
варианты initial="hidden"/animate="show" (у staggerContainer/staggerItem
ключи — initial/animate/exit), из-за чего stagger там молча не работал
вовсе; имена исправлены.

Проверено и не требует правок: TopUpAmount (early-return до загрузки
методов), TopUpMethodSelect и MergeAccounts (динамический контент —
обычный HTML внутри всегда смонтированных staggerItem), вложенный
контейнер транзакций в Balance (самоанимирующийся).
2026-07-12 19:08:09 +03:00
kewldan
cf3837163e chore(tooling): миграция с ESLint + Prettier на Biome
Один инструмент вместо девяти dev-зависимостей. Замеры на этом репо
(483 файла, медиана из 3 прогонов, один и тот же компьютер):

- eslint .            ~20.4s  ->  biome lint .    ~2s
- prettier --check .  ~8.8s   ->  biome format .  ~0.2s
- суммарно линт+формат ~29s   ->  biome check .   ~2-3s (~10x)

Паритет с прежними правилами:
- форматирование бинарно совместимо с Prettier-конфигом (biome format
  --write тронул 9 файлов из ~480 — микроразличия);
- запрет window.confirm/alert/open/prompt и navigator.clipboard
  (Telegram WebView) перенесён GritQL-плагином
  biome-plugins/telegram-webview-guards.grit c теми же сообщениями
  и исключениями для src/platform/** и clipboard.ts; плагин сразу
  нашёл window.open в Wheel.tsx под старым eslint-disable —
  переведён на biome-ignore;
- react-hooks-правила: useExhaustiveDependencies/useHookAtTopLevel;
- новые для проекта правила (a11y и пр.) не включались или понижены
  до warning, чтобы миграция не смешивалась с чисткой кода — включать
  можно отдельными PR.

Ограничения (задокументированы): сортировка tailwind-классов
(prettier-plugin-tailwindcss) в Biome пока nursery — не включена;
CSS исключён из Biome (парсер спотыкается о Tailwind-синтаксис
globals.css) — файл один и правится редко.

CI не меняется: имена npm-скриптов lint/format:check сохранены.
lint-staged переведён на biome check --write.
2026-07-12 00:12:15 +03:00
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
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
73ee42a0ef fix(wheel): stop showing fake wins in the browser
After a Stars payment the wheel matched its landing sector to the won prize
by display_name/emoji and, on any miss, span to Math.random()*360 — which
often pointed at a winning slot. The poll result also carried the SPIN id in
prize_id (not the WheelPrize id), so it never matched and always hit that
random fallback. In a backgrounded browser tab the poll often times out,
so the wheel 'landed' on месяц/50₽ while the real result was Ничего; the
Mini App was fine because it gets the result inline.

- Match the sector by prize_id (exact), with name/emoji only as a defensive
  fallback for old payloads.
- Never land on a random angle: when the prize can't be resolved (poll
  timeout/error) land on the neutral 'Nothing' sector instead.
- Carry the real WheelPrize id (now exposed by the bot history endpoint)
  through the poll result.
2026-06-04 22:15:43 +03:00
c0mrade
0a89838926 style(cabinet-icons): use regular Phosphor weight and switch wheel to dice
- Drop the Duotone variants (their translucent secondary fill looked murky on
  the dark theme) in favour of the clean regular weight across all icons
- WheelIcon: PiSteeringWheel → PiDiceFive (a steering wheel made no sense for
  the fortune-wheel feature)
2026-05-31 19:24:29 +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
9c5e38b594 refactor(theming): replace red-* palette with semantic error-* tokens
Sweep 30 files: text/bg/border/ring/from/to/via/fill/stroke/shadow/divide/decoration/
outline/placeholder-red-N → -error-N. All red usages were semantically error/danger
(no brand red), so tokens now flow through the design-system CSS variables and respond
to palette overrides.
2026-05-26 11:08:22 +03:00
c0mrade
8e0b63bac8 feat(a11y): cross-platform hardening + modal focus-trap from impeccable audit
Responsive / viewport:
- add .min-h-viewport / .h-viewport utilities (100dvh + Telegram --tg-viewport-stable-height)
- migrate min-h-screen / h-screen / 100vh across 18 files
- reveal hover-only controls on focus-within + touch (desktop nav, admin settings)
- grid breakpoints (TopUpAmount, Contests), larger touch targets, nav aria-labels

Platform routing (Telegram WebView correctness):
- clipboard -> copyToClipboard util with execCommand fallback (10 files)
- window.open -> openTelegramLink / openLink (Profile, Referral, ChannelSubscriptionScreen)
- window.confirm -> useNativeDialog (admin pages); PromoOffersSection -> useDestructiveConfirm
- window.prompt -> usePrompt / PromptDialogHost (TipTap link editors)
- ESLint no-restricted-properties guard against regressions (adapters/clipboard util exempt)

Modal accessibility:
- new useFocusTrap hook (focus trap, Esc, scroll lock, focus restore)
- role=dialog / aria-modal / focus-trap: Polls, SuccessNotificationModal, AdminPolicies,
  AdminPromoGroups, AdminCampaigns, BroadcastPreview (Telegram + Email)
- new global usePrompt store + PromptDialogHost
2026-05-25 23:16:07 +03:00
c0mrade
16ccf78c95 fix: show warning hint when subscription must be selected before wheel spin
When user has 2+ eligible subscriptions and chooses subscription_days
payment, a visible warning hint now appears explaining they need to
select a subscription before spinning.
2026-03-25 16:49:45 +03:00
c0mrade
2921d8cae6 feat: wheel subscription picker for multi-tariff mode
- API: spin() accepts subscriptionId, WheelConfig has eligible_subscriptions
- Auto-select subscription when only one eligible
- Show subscription picker when multiple eligible (days payment)
- Disable spin button until subscription selected (when >1 eligible)
- Daily tariffs excluded from eligible list
2026-03-24 14:41:20 +03:00
Fringg
914a802c1b fix: добавлена подпись «Стоимость вращения» к блоку оплаты спина 2026-03-18 04:35:32 +03:00
c0mrade
2dab25c5a0 fix: resolve telegram auth token expiration and clean up codebase
- Fix stale initData comparison in clearStaleSessionIfNeeded that destroyed
  valid refresh tokens on mobile WebView reopens
- Restrict X-Telegram-Init-Data header to auth endpoints only
- Close Mini App on auth retry to force fresh initData from Telegram
- Merge Connection page error/not-configured states for better UX
- Remove unnecessary comments across 40+ files (section dividers,
  restating comments, noise catch block comments)
- Configure ESLint allowEmptyCatch to properly handle intentional
  empty catch blocks (62 warnings resolved)
2026-03-13 17:50:49 +03:00
Fringg
821e991f51 fix: block wheel spin without active subscription
- Add has_subscription to WheelConfig type
- Disable spin button when user has no subscription
- Add noSubscription guard in handleUnifiedSpin handler
- Show warning banner directing user to activate subscription
- Prevent banner stacking (noSubscription takes priority over daily limit)
- Add noSubscription i18n keys for ru, en, zh, fa
2026-02-27 00:53:50 +03:00
Fringg
22bda66e81 fix: wheel lands on correct prize sector for Stars payment spins
Stars payment flow previously used Math.random() for wheel rotation,
causing visual mismatch between where the wheel lands and the actual
prize won. Now polls for backend result first, calculates the correct
rotation angle matching the prize sector, then starts the animation.
2026-02-16 02:06:23 +03:00
Fringg
2c0d265ff5 fix: remove incorrect ruble top-up prompt from fortune wheel
Trial users saw an "insufficient balance" prompt suggesting to top up
rubles, but the wheel only accepts Telegram Stars (via invoice) or
subscription days. Stars payment via invoice doesn't require ruble
balance, so the spin button should not be disabled for it.
2026-02-07 03:36:31 +03:00
c0mrade
8068f84724 fix: inline Stars confirmation and unified payment type display
- Replace confirm dialog with inline UI panel to avoid browser popup
  blockers — pay button click is a direct user gesture
- Pre-open browser window synchronously on pay click for fallback
- Unify payment type display: show same styled panel whether one or
  both methods are enabled (single method = full-width button)
- Remove getCostSubtitle text, add payStars translation key (ru, en,
  zh, fa)
2026-02-04 16:53:58 +03:00
c0mrade
e6f8ae6ab0 feat: add Stars payment confirmation and admin validation
- Show confirmation dialog before redirecting to Telegram for Stars
  payment (native popup in Mini App, confirm in browser)
- Warn admin when saving wheel config with Stars enabled but
  Telegram Stars payment method is disabled globally
- Add translation keys: confirmStarsTitle, confirmStarsPayment,
  starsNotEnabledGlobally (ru, en, zh, fa)
2026-02-04 16:41:25 +03:00
c0mrade
02640d1c38 fix: unify wheel Stars payment across desktop and mobile
- Use Telegram Stars invoice on all platforms instead of balance
  payment on desktop
- Show star cost in toggle and subtitle everywhere
- Show popup/toast when Stars are not enabled and user tries to spin
- Remove platform-specific payment type auto-select logic
- Add starsNotAvailable translation key (ru, en, zh, fa)
2026-02-04 16:26:07 +03:00
c0mrade
4499c9ad57 fix: prevent payment type reset after wheel spin
The auto-select useEffect for payment type (days/stars) was running
on every config refetch, resetting the user's manual selection back
to stars. Now it only runs once on initial load using a ref flag.
2026-02-04 16:09:10 +03:00
c0mrade
4fa9d7f47c Improve wheel: fix lights animation and add prize legend
- Fix lights animation to run continuously (even when idle)
- Lights now move sequentially around the circle
- Remove text labels from wheel, keep only emoji
- Add WheelLegend component with color indicators
- Display legend on right (desktop) and bottom (mobile)
- Add 'prizes' translation key for all languages
2026-02-03 23:42:52 +03:00
c0mrade
ef6863081e Redesign wheel UI with linear design system
- Improve wheel rotation animation with accumulated rotation tracking
- Enhance LED lights with running animation effect
- Replace sidebar history with collapsible section
- Unify spin button for all payment methods
- Update admin prize card styles with new borders and backgrounds
- Add improved drag-and-drop collision detection
- Prefer Telegram Stars payment in Mini App by default
2026-02-03 22:27:57 +03:00
c0mrade
494285bcbf feat: redesign fortune wheel with improved UX
Wheel.tsx (user page):
- Always show history sidebar on desktop
- Show placeholder when no history
- Remove history toggle button

AdminWheel.tsx:
- Replace auto-save with form + Save button
- Fix toggle switch styling
- Replace HTML5 drag-drop with @dnd-kit for mobile/Telegram
- Improve mobile responsiveness (tabs, cards, buttons)
- Add live wheel preview on desktop
- Group settings into sections

FortuneWheel.tsx:
- Slow down LED animation (250ms -> 600ms)

Navigation:
- Add wheel to mobile bottom nav (replaces Support when enabled)
2026-02-02 18:21:50 +03:00
c0mrade
7e2802c5b5 feat: redesign fortune wheel UI and add to mobile nav
- Add wheel button to mobile bottom nav (replaces Support when enabled)
- Slow down LED lights animation (250ms -> 600ms)
- Replace result modal with inline card in Wheel.tsx
- Add haptic feedback on spin result
- AdminWheel.tsx: inline prize editing instead of modal
- AdminWheel.tsx: drag-and-drop for prize reordering
- AdminWheel.tsx: live wheel preview on desktop
- AdminWheel.tsx: group settings into sections
- Replace confirm() with native dialog
- Add translations for all languages
2026-02-02 17:51:46 +03:00
c0mrade
edb5be09ae feat: migrate to @tma.js/sdk-react for Telegram Mini App
- Add useTelegramSDK hook with reactive signals for viewport, safe area, fullscreen
- Migrate TelegramAdapter to use SDK components (backButton, mainButton, hapticFeedback, cloudStorage, themeParams, popup)
- Update Login, TelegramRedirect to use SDK helpers
- Update PlatformProvider, api/client to use centralized SDK functions
- Simplify useTelegramWebApp as backward-compatible wrapper
- Add automatic CSS variable binding for theme and viewport
2026-02-01 20:13:50 +03:00
Egor
ef16fb65f0 Update fallback handling for invoice URL in Wheel 2026-01-28 21:12:32 +03:00
Egor
56c2d7be90 Check WebApp version before opening invoice 2026-01-28 21:02:16 +03:00
c0mrade
9707ad97a4 feat(i18n): migrate all hardcoded Russian text to i18n
- Replace all hardcoded Russian strings with t() calls across 30+ files
- Add ~500 new translation keys to all 4 locales (ru, en, zh, fa)
- Convert module-level config objects to labelKey pattern
- Remove Russian fallbacks from t() calls (fallbackLng handles it)
- Replace DeepLinkRedirect custom i18n with standard t() calls
- Fix subscription.servers key collision (string vs object)
2026-01-27 22:19:41 +03:00
c0mrade
bc90ba3779 refactor: migrate to eslint flat config and format codebase with prettier
- Remove legacy .eslintrc.cjs and .eslintignore
- Add eslint.config.js with flat config, security rules (no-eval, no-implied-eval, no-new-func, no-script-url)
- Add .prettierrc and .prettierignore
- Format entire codebase with prettier
2026-01-27 17:37:31 +03:00
Dev
308df174fb Phase 1-6 Complete 2026-01-20 22:54:32 +05:00
PEDZEO
8753000a03 Add Telegram WebApp support: initialize app, enhance layout with fullscreen toggle, and improve polling mechanism in Wheel component 2026-01-20 00:13:11 +03:00
Egor
a7f0d705ff Add files via upload 2026-01-19 00:36:19 +03:00
Egor
92f29db60f Update Wheel.tsx 2026-01-15 19:36:46 +03:00
Egor
7be6b5c0ae Add files via upload 2026-01-15 19:18:17 +03:00