Commit Graph

451 Commits

Author SHA1 Message Date
c0mrade
5c7d4b4888 feat(a11y): dialog semantics + focus trap for the onboarding tour
- role=dialog/aria-modal/aria-labelledby/aria-describedby on the onboarding tooltip
- trap focus inside the tooltip while the tour runs; Esc skips (lockScroll off so
  scrollIntoView can still bring each step's target into view)
- merge the focus-trap ref with the existing measurement ref via a callback ref
- aria-hidden on the click-to-advance target overlay (redundant with the Next button)
2026-05-25 23:51:26 +03:00
c0mrade
f31160208a feat(auth,a11y): Telegram CloudStorage token recovery + blocking-screen a11y
Telegram CloudStorage token recovery (resilience against WebView localStorage wipes):
- mirror the refresh token to per-user CloudStorage on login, remove it on logout
- restoreRefreshTokenFromCloud() recovers the refresh token in initialize() when
  localStorage is empty, then the normal refresh flow re-establishes the session
- move the initialize() bootstrap call from auth.ts module-load into main.tsx after
  the Telegram SDK init(), since launch params + CloudStorage are unavailable before init
- best-effort: no-ops outside Telegram and on any error -> falls back to prior behavior

Accessibility:
- role=alertdialog + aria-modal + aria-labelledby + focus management (useFocusTrap)
  on Maintenance, Blacklisted, ChannelSubscription and AccountDeleted blocking screens
- aria-label on ColorPicker Hue/Saturation/Lightness range inputs
2026-05-25 23:43:17 +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
Fringg
e44a09312e fix(subscription): forward subscription_id to purchaseTariff to fix renew race
Companion to bedolaga-bot a527df23. Bot's purchase-tariff endpoint
now accepts optional subscription_id to resolve the EXACT target row
by ID instead of doing a (user_id, tariff_id) re-lookup that races
with concurrent panel webhooks (produces 'Тариф уже активен' +
refund + no extension).

Frontend changes:
* subscriptionApi.purchaseTariff() — new optional subscriptionId arg
  appended after trafficGb. Forwarded as subscription_id in POST body.
* SubscriptionPurchase.tsx — forwards URL searchParam subscriptionId
  (set when user lands here from 'Renew this subscription').
* SubscriptionCardExpired.tsx — passes subscription.id when renewing
  an expired daily tariff (the .purchaseTariff(tariff_id, 1) call).
2026-05-16 16:10:32 +03:00
Fringg
8c336d16c7 fix(blocking): open bot deep-link via platform adapter, not window.open
Inside the Telegram WebView, window.open is intercepted by the
client and the new-tab fallback is blocked on most platforms
(Android, iOS). Use usePlatform().openTelegramLink(url) — same
pattern as the rest of the codebase (Support.tsx, etc.). The
TelegramAdapter dispatches to the WebApp SDK's openTelegramLink in
TG and falls back to window.open in the standalone web build, so
both runtimes work.
2026-05-16 05:25:41 +03:00
Fringg
16b47119c9 feat(blocking): account-deleted recovery screen with bot deep-link
Pairs with backend feat(deleted-users) — when the cabinet returns
403 {detail: {code: 'account_deleted', message, bot_username,
telegram_deep_link}} the SPA shows a dedicated screen instead of the
generic auth-error toast.

* Adds AccountDeletedScreen with localized title/description, an
  Open-bot button using the backend-provided telegram_deep_link, and a
  Retry button that clears the block + reloads (so the next request
  observes the now-revived row).
* useBlockingStore gains accountDeleted slice mirroring the existing
  maintenance/channel-subscription/blacklisted slots — consistent with
  the precedent for cross-cutting 403 codes.
* apiClient: AccountDeletedError type + isAccountDeletedError guard +
  response interceptor routes the 403 into the blocking store.
* i18n: ru/en/zh/fa parity for title, description, openBot, retry, hint.

No behavior change for ACTIVE users. BLOCKED users still get the
generic 403 (the backend keeps the message opaque for admin actions).
2026-05-16 05:16:24 +03:00
Fringg
abbbc6a216 fix(onboarding): prevent invisible overlay blocking clicks and stuck tour
- Tie spotlight/tooltip pointer-events to isVisible so opacity:0 stops
  trapping taps during step transitions
- Render target click-catcher only when overlay is fully visible and
  clear stale targetRect on step change
- Retry target lookup up to 6 times and auto-skip step (or complete
  tour) when target is missing, instead of leaving overlay stuck
  invisible-but-interactive
- Persist completion to localStorage before unmounting the tour, so
  the flag survives even if unmount throws
2026-05-15 05:34:38 +03:00
Fringg
4de01ccd1d fix(dashboard): show full name on welcome, fix gender mismatch on trial-expired card
User reported single-letter welcome "Добро пожаловать, О!" (short first_name
"Олег"-style users) and a confused "Истекла" feminine label on the masculine
"Пробный период истёк" trial-expired card.

* Add `src/utils/displayName.ts` helper that composes `first_name + last_name`
  with `username` and `#telegram_id` fallbacks. Single source of truth for
  user-facing name rendering across the app.
* Apply `displayName(user)` in Dashboard welcome, AppHeader mobile drawer,
  and DesktopSidebar profile chip. Now a user with `first_name="О"` and
  `last_name="Иванов"` sees "О Иванов" instead of just "О".
* `SubscriptionCardExpired` — context-aware Russian label: for trial
  subscriptions render masculine "Истёк" (agrees with "пробный период"),
  for paid subscriptions keep feminine "Истекла" (agrees with "подписка").
  Uses i18next `context: subscription.is_trial ? 'trial' : ''` — falls back
  to base key for EN/ZH/FA which are grammatically neutral.
* Add `expiredDate_trial: "Истёк"` only to `ru.json` (no changes needed for
  other locales — i18next context falls back to `expiredDate`).
2026-05-13 09:02:10 +03:00
Fringg
b4eb0fa859 feat: add Lava payment provider support
- METHOD_LABELS entry for lava
- Custom SVG icon: dark base with orange-red lava flow and ember dots
- Admin settings tree node: payments_lava
- Locale strings (ru/en/zh/fa) for descriptions and admin labels
2026-05-04 20:16:01 +03:00
Fringg
7def84718b feat: add Jupiter and Donut payment provider support
- METHOD_LABELS entries for jupiter and donut
- Custom SVG icons: Jupiter (planet with ring), Donut (glazed donut)
- Admin settings tree nodes: payments_jupiter, payments_donut
- Locale strings (ru/en/zh/fa) for descriptions and admin labels
2026-05-04 19:36:35 +03:00
Fringg
7d940091fa feat: add Antilopay payment provider support
- Add antilopay to METHOD_LABELS for stats charts
- Add PaymentMethodIcon case with amber gradient SVG
- Add payments_antilopay to admin settings tree
- Add i18n keys in all 4 locales (en, ru, zh, fa)
2026-05-04 07:44:52 +03:00
Fringg
85a34b1947 feat: add Etoplatezhi payment provider support
- Add etoplatezhi to METHOD_LABELS for stats charts
- Add PaymentMethodIcon case with green gradient SVG
- Add payments_etoplatezhi to admin settings tree
- Add i18n keys in all 4 locales (en, ru, zh, fa)
2026-05-04 07:18:14 +03:00
Fringg
65f94931c5 feat: add TV Quick Connect to connection page for Android TV / Apple TV
When user selects Android TV or Apple TV platform (from non-TV device),
shows 5-char code input + QR scanner to send subscription to TV via
Happ TV API. Hybrid QR strategy: native Telegram scanner on mobile,
html5-qrcode CDN fallback on desktop. i18n keys for ru/en/zh/fa.

Based on PR #415 by @smediainfo.
2026-05-04 06:22:29 +03:00
Fringg
d21c6637bf feat(admin/broadcasts): add preview buttons for Telegram + Email broadcasts
Add BroadcastPreview component with local rendering of Telegram messages
(HTML tokenizer with tag whitelist, no dangerouslySetInnerHTML) and Email
preview (sandboxed iframe). Preview buttons appear in broadcast create page
section headers, disabled when content is empty.

Based on PR #416 by @smediainfo.
2026-05-04 06:18:12 +03:00
Fringg
58887138fc feat: add Apple IAP (apple_iap) payment method support
Add apple_iap across all payment surfaces: method label, icon (Apple
logo SVG), admin settings tree (APPLE_IAP category), and all four
locale files (en, ru, fa, zh) with payment description + tree label.
2026-05-04 06:01:20 +03:00
Fringg
80059681da feat: Yandex Metrika CID tracking, offline conversions UI, sticky pay button
- Pass yandex_cid to all auth endpoints (telegram, email, OIDC, OAuth)
- Add OfflineConvGoal interface and offline_conv_* fields to AnalyticsCounters
- Add storeYandexCid API method for server-side CID persistence
- Add analytics fields to LandingConfig (view/click goals, sticky_pay_button)
- Add yandex_cid/referrer/subid to PurchaseRequest
- Add offline conversions UI block in admin AnalyticsTab
- Add cacheYandexCid, syncYandexCid, fireAnalyticsEvent to analytics hook
- Create yandexCid.ts utility (localStorage get/set helpers)
- Add sticky pay button with portal on mobile in QuickPurchase
- Fire view/click analytics goals on landing pages
- Persist contact value and referrer/subid in session/localStorage
- Add i18n keys for offlineConv and apiKey in all 4 locales
2026-04-29 08:59:19 +03:00
Fringg
6d3010b621 feat: multi-media attachments, linkify URLs, shared MessageMediaGrid
- Add media_items array support to ticket messages (types, API clients)
- Create shared MessageMediaGrid component with photo grid, fullscreen
  viewer, keyboard nav, video/document rendering
- Replace per-page AdminMessageMedia/MessageMedia with MessageMediaGrid
- Add linkifyText util (DOMPurify-sanitized) for auto-linking URLs
- Support multi-file upload (up to 10) in AdminTickets and Support pages
- Remove unused ticketsApi import from AdminUserDetail
2026-04-29 08:45:15 +03:00
Fringg
9b1e26d4ec fix: switch component — replace motion with CSS transition-transform 2026-04-29 08:34:37 +03:00
Fringg
1d5ce2d4ea fix: complete is_limited status handling across all views
- SubscriptionListCard: show traffic progress bar for limited subs
  in multi-tariff list (was hidden because isActive excluded limited)
- SubscriptionListCard: amber border/background for limited status
  cards (was using default neutral style, inconsistent with rest of UI)
- Subscription page: hide delete button for limited subs in
  multi-tariff mode (limited subs still have valid days remaining)
- SubscriptionPurchase: allow tariff switch for limited subs
  (was blocked by is_active-only gate in canSwitch formula)
- AdminUserDetail: include limited status in "already purchased"
  tariff filter to prevent duplicate subscription creation
2026-04-23 04:04:11 +03:00
Fringg
f68b46699c fix: limited (traffic exhausted) subscription shows as expired
- Dashboard card: change date label from "Истекла" to "Активна до"
  for limited subscriptions (traffic exhausted but time remaining)
- Dashboard button: change text from "Трафик исчерпан" (dead-end) to
  "Докупить трафик" (actionable) for limited status
- Subscription page: CountdownTimer shows remaining time for limited
  subs instead of "Истекла" (was passing is_active=false)
- PurchaseCTAButton: don't treat limited as expired — limited subs
  still have time, should show extend CTA not "buy new subscription"
2026-04-23 03:56:07 +03:00
Fringg
996840f627 fix: change aurapay icon to pink gradient to avoid kassa_ai color collision 2026-04-18 00:12:15 +03:00
Fringg
48827e430d feat: add AuraPay payment provider to cabinet frontend
- Payment method label and icon (violet gradient, AP)
- Admin settings tree entry for AURAPAY category
- Locale strings and descriptions for ru, en, zh, fa
2026-04-18 00:05:56 +03:00
Fringg
4eed0f358e fix: differentiate rollypay icon from riopay (teal gradient, RY text) 2026-04-16 05:58:58 +03:00
Fringg
770a5803d6 feat: add RollyPay payment provider to cabinet frontend
- Payment method label and icon (green gradient, RP)
- Admin settings tree entry for ROLLYPAY category
- Locale strings and descriptions for ru, en, zh, fa
2026-04-16 05:55:21 +03:00
Fringg
31ac511a09 feat: add PayPear payment provider to cabinet frontend
- Payment method label and icon (amber gradient)
- Admin settings tree entry for PAYPEAR category
- Locale strings for ru, en, zh, fa
2026-04-16 05:26:09 +03:00
c0mrade
30d8d28475 fix: show API error details in menu editor save + allow tg:// URLs
Save mutation was showing generic error instead of backend validation
detail, making it impossible to diagnose 422 errors. Also added tg://
to frontend URL validation for Telegram deep link support.
2026-04-13 17:07:29 +03:00
c0mrade
e6f6713b0e fix: batch bug fixes matching backend changes
- Promo code errors: add inactive/not_yet_valid translations (4 locales) + Balance.tsx parser
- Daily 100% discount: render price block when originalDailyPrice > 0
- Webvisor: enable in Yandex Metrika init config
- Referral attribution: consume campaign/referral slugs only after successful auth (all login methods)
- registerWithEmail: consume referral code after success
- AdminSettings: move TARIFF_MODE_SETTINGS to module scope (fix eslint warning)
2026-04-08 18:33:47 +03:00
c0mrade
1cf9526713 fix: complete sparkles icon in trial offer card (was showing only 1 of 3 sparkle paths) 2026-04-03 18:36:11 +03:00
Fringg
e2f81ad28d fix: subscription tab not highlighted on detail pages
isActive used exact pathname match (===), so navigating to
/subscriptions/:id or /subscriptions/:id/renew would not highlight
the Subscription tab. Changed to startsWith for non-root paths
in both MobileBottomNav and DesktopSidebar.
2026-04-02 05:44:06 +03:00
c0mrade
826a82aa1c fix: unblock page rendering when Telegram CDN is unavailable
- Google Fonts: non-render-blocking via media="print" onload trick
- telegram-web-app.js: load only inside Telegram environment
- Telegram SDK init: skip entirely outside Telegram
- Script load timeout: 8000ms → 2000ms for faster deep link fallback
- Email login form: expanded by default (80% of registrations)
2026-03-31 14:10:22 +03:00
Fringg
07500ed215 feat: dynamic language list from API instead of hardcoded array
Hide language switcher when only 1 language is available.

Cherry-picked from PR #365
2026-03-29 05:15:31 +03:00
Fringg
3e6c0212a6 fix: desktop vertical alignment regression and touch target sizing
- SettingsTableRow: use max-lg:self-end instead of self-end to prevent
  bottom-aligning controls on desktop (self-end overrides items-center)
- QuickToggles: add min-h-[44px] matching Toggle component pattern
2026-03-29 03:22:45 +03:00
Fringg
e004d8103b fix: mobile layout and touch target improvements for admin settings
- SettingsTableRow: stack name above controls on mobile, show action
  buttons without hover (max-lg:opacity-100)
- SettingsMobileTabs: increase touch targets to 44px, add scroll spacers
- QuickToggles: increase chip touch targets
- SettingInput: increase display button touch target
- AdminSettings: add bottom padding buffer to mobile layout
2026-03-29 03:18:21 +03:00
Fringg
67055d58f7 fix: whitespace search guard and unknown section fallback
- SettingsTab: use searchQuery.trim() instead of searchQuery to prevent
  whitespace-only input from triggering empty search results view
- AdminSettings: fallback to branding when activeSection is unknown
  instead of rendering blank content area
2026-03-29 03:08:02 +03:00
Fringg
28ef6c97f5 fix: three bugs found in second review round
- SettingsSearchMobile: change sm:hidden to lg:hidden to match mobile
  layout breakpoint (search was invisible on 640-1023px tablets)
- SettingsTableRow: sync isLongValue suffixes with SettingInput
  (_list, _json, _periods, _discounts were missing)
- SettingsTableRow: read-only bools now show enabled/disabled labels
  instead of raw "true"/"false" strings
2026-03-29 03:03:26 +03:00
Fringg
ea1c7359ce fix: stabilize useMemo deps and add category search to sidebar
- Use activeSection instead of activeTreeInfo as useMemo dependency
  (activeTreeInfo is derived from activeSection, new object each render)
- Add category label matching to sidebar autocomplete search filter
  to match global search behavior
2026-03-29 02:56:54 +03:00
Fringg
21813ef82e feat: redesign admin settings with tree navigation and compact layout
- Replace flat sidebar with collapsible tree groups (7 groups, 52 sub-items)
- Map all 61 backend category keys to sidebar navigation
- Add QuickToggles panel for boolean settings
- Replace card-style SettingRow with compact SettingsTableRow
- Add breadcrumb navigation for tree sub-items
- Add source badges (modified/DB/ENV) on settings
- Implement two-level mobile navigation with group chips
- Add i18n translations for all new keys (en, ru, zh, fa)
2026-03-29 02:38:52 +03:00
c0mrade
3b659b7450 fix: bottom nav disappears after visiting payment page in Telegram
- Reset isKeyboardOpen on route change so bottom nav always restores
- Disable autoFocus on payment input in Telegram Mini App
- Skip auto-focus on Profile email inputs in Telegram
2026-03-27 13:40:13 +03:00
c0mrade
c697ddb224 fix: auto-reload on stale chunk errors after deploy
When a new build changes chunk hashes, users with cached index.html
get "Failed to fetch dynamically imported module" errors. Now:
- lazyWithRetry() wraps all lazy imports with auto-reload on failure
- ErrorBoundary catches chunk errors and auto-reloads once
- 30s guard in sessionStorage prevents reload loops
2026-03-26 20:55:29 +03:00
c0mrade
e86b214008 fix: resolve eslint warnings in NewsSection and AdminTickets
NewsSection: wrap items in useMemo to stabilize dependency reference.
AdminTickets: copy ref values to local vars for cleanup function.
2026-03-25 11:47:41 +03:00
c0mrade
fd01c0f393 fix: preserve subscription context in navigation and cache keys
SubscriptionCardActive: passes ?sub= to /connection navigation.
Connection: QR state includes subscriptionId for back-navigation.
ConnectionQR: preserves subscription context on back button.
Dashboard + Subscription: localStorage traffic cache keyed per-subscription
to prevent cooldown bleed between different subscriptions.
2026-03-25 11:47:31 +03:00
c0mrade
debee7729f fix: platform-aware delete confirmation + trial CTA to purchase
Delete subscription:
- Telegram mini app: native destructive popup (red button)
- Web (mobile + desktop): Sheet bottom panel with full-width buttons
- Both: proper loading state on delete button

Trial CTA: leads to /subscription/purchase (trial cannot be renewed)
2026-03-24 18:30:59 +03:00
c0mrade
31d0953c23 feat: add WebBackButton to all sub-pages, widen renew page layout
- New WebBackButton component: visible on web, hidden in Telegram
  (native back button handles navigation there)
- Added to: Subscription detail, SubscriptionPurchase, RenewSubscription
- RenewSubscription: removed max-w-lg, grid layout for period cards
2026-03-24 14:01:55 +03:00
c0mrade
871b4769aa feat: show autopay/auto-charge status on subscription cards, invalidate list on toggle
- SubscriptionListCard: always show autopay status (green check / red cross)
  for regular tariffs, show auto-charge status for daily tariffs
- Hide autopay for trial subscriptions
- Autopay mutation: invalidate subscriptions-list for instant UI update
- Types: added is_daily, is_daily_paused to SubscriptionListItem
2026-03-24 12:28:02 +03:00
c0mrade
6a53221cc1 fix: hide renewal button for daily tariffs in multi-tariff mode, fix hint text
- Daily tariffs renew automatically — no manual renewal CTA needed
- Show "Продление подписки" hint in multi-tariff instead of "Продление и смена тарифа"
2026-03-24 11:10:30 +03:00
c0mrade
82eb03dec9 feat: separate renewal and purchase flows in multi-tariff mode
Renewal flow (per-subscription):
- New /subscriptions/:id/renew page showing only period options for
  the specific tariff with balance display and renew button
- PurchaseCTAButton links to /renew in multi-tariff mode

Purchase flow (new tariffs only):
- Hide already purchased tariffs (is_purchased flag from backend)
- Fallback UI when all tariffs are owned
- Types: added is_purchased, all_tariffs_purchased fields
2026-03-24 10:47:49 +03:00
c0mrade
9d3fb37d60 fix: remove duplicate back buttons, improve multi-subscription UX
- Remove custom back buttons from Subscription detail and SubscriptionPurchase
  pages — native Telegram mini-app back button already handles navigation
- SubscriptionListCard: distinct trial badge with star icon and amber styling,
  trial cards get amber border/background for visual differentiation
- SubscriptionPurchase: change "Купить тариф" to "Новый тариф" when adding
  a new tariff in multi-subscription mode
- Subscription detail: show tariff name as title in multi-tariff mode
2026-03-23 21:38:04 +03:00
c0mrade
376e1bb56d feat: enhance subscription list UX with progress bars, status badges, and glass theme
- SubscriptionListCard: replace status dot with localized status badge,
  add traffic progress bar with color coding, use locale-aware date format
- Subscriptions page: apply glass theme colors, improve empty state styling,
  add SVG plus icon for buy button, skeleton loader with glass theme
- Subscription detail: invalidate subscriptions-list cache on mutations
2026-03-23 18:57:05 +03:00
c0mrade
f4de6d8ad8 fix: multi-subscription UI audit fixes and cache invalidation improvements
- Fix AdminUserDetail multi-subscription display with useMemo optimization
- Update Dashboard subscription cards and purchase entry points
- Fix WebSocket notification handlers for multi-subscription context
- Update SubscriptionPurchase cache invalidation for multi-tariff mode
- Fix Subscriptions list page and navigation patterns
- Update App routing, DeepLinkRedirect, and CommandPalette for multi-sub
- Add SubscriptionListCard component
- Fix TopUpResult and PromoOffersSection navigation
2026-03-23 18:57:05 +03:00
c0mrade
a0c21a11aa fix: navigation links point to /subscriptions (list page) instead of legacy /subscription 2026-03-23 18:57:05 +03:00