Commit Graph

29 Commits

Author SHA1 Message Date
Fringg
0e4dc43335 feat(gift): QR подарка и сканирование в кабинете
Получателю раньше оставалось только скопировать ссылку или код руками.

- QR на экране готового подарка: кодируется bot-ссылка (активация в боте —
  основной путь), при отсутствии username бота — ссылка кабинета. Использован
  уже имеющийся qrcode.react, на белой подложке ради контраста в тёмной теме.
- Кнопка «Отсканировать QR» в форме активации. В Telegram используется
  НАТИВНЫЙ сканер (в WebView камера через getUserMedia работает ненадёжно —
  ровно то, из-за чего скан «в тг» и был проблемой), в вебе — html5-qrcode с
  CDN, с фолбэком на фронтальную камеру и понятной ошибкой, если камеры нет.
- src/utils/qrScanner.ts: логика сканера вынесена из TvQuickConnect, чтобы её
  мог переиспользовать любой экран. parseGiftCode понимает все три способа
  распространения подарка (deep-link бота, ссылка кабинета, голый код с
  префиксом GIFT- и без) и ОТКЛОНЯЕТ посторонние QR — иначе сканер подставил
  бы в поле мусор с любой ссылки или Wi-Fi-кода.
- Веб-сканер гасится при уходе с вкладки и размонтировании, иначе индикатор
  камеры остаётся гореть.

Локали ru/en/zh/fa, +4 теста на парсер.
2026-07-29 22:11:04 +03:00
kewldan
81fd96c2a7 fix(i18n): форматировать даты и числа локалью выбранного языка, а не браузера 2026-07-14 00:04:45 +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
ce5737fcbc fix(payments): open_url_direct payment URL opens externally in Telegram (#654272)
The reported #654272 case ('функция немедленного открытия ссылки', reproduced on RollyPay AND YooKassa, fine on desktop) is the open_url_direct flow — not the connection deep-link opener fixed in 325e221.

When a payment method has open_url_direct, the cabinet did window.location.href = payment_url INSIDE the Telegram in-app WebView. SBP/RollyPay/YooKassa pages then hand off to a bank app via a custom scheme, which the WebView can't open: Android shows net::ERR_UNKNOWN_URL_SCHEME, iOS opens nothing ('приложение не определяется'); link generation logs fine. Desktop works because it's a real browser.

Add openPaymentUrl(): in Telegram open via openLink (external browser — the OS hands off to the bank app, return_url brings the user back); on web keep same-tab navigation (no popup blocker). Applied to TopUpAmount (top-up) and GiftSubscription (gift purchase). QuickPurchase is a web landing page (no platform abstraction) and is unaffected.
2026-06-08 14:21:45 +03:00
c0mrade
50d7d21462 fix(gift): stretch my-gifts cards full-width instead of cramped 2-col grid
The 2-column grid inside the max-w-2xl container made each gift card a narrow ~330px half. Gift cards are content-rich (header, code, share preview, buttons), so stack them single-column full-width — they now stretch to the container width on every breakpoint.
2026-06-01 14:11:50 +03:00
c0mrade
6ea767253f fix(gift): working bot deep-link + desktop layout + barrel icons
- Build the share deep-link as t.me/<bot>?start=GIFT_<code> (literal underscore). The old GIFT%5F encoding was passed to the bot verbatim by Telegram, so its startswith('GIFT_') never matched and activation silently failed.
- Source the bot username from the runtime branding config (env var is only a fallback), so the 'activate via bot' line no longer vanishes on builds without VITE_TELEGRAM_BOT_USERNAME.
- Widen the page on desktop (max-w-2xl) and show sent gifts in a 2-col grid; it was cramped to a mobile column.
- Replace the remaining hand-written SVG icons with the central barrel.
2026-06-01 13:08:18 +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
3df2134cba refactor(theming): orange-* → warning-* for promo/highlight semantics
Sweep orange across SubscriptionPurchase/GiftSubscription/PromoOffersSection/
AdminTrafficUsage/AnalyticsTab/withdrawalUtils to warning-* tokens. Stars brand
yellow→orange gradient kept in TopUpAmount.
2026-05-26 11:22:19 +03:00
Fringg
6ffd0ae824 fix(format): convert price by exchange rate on landings and gift page
formatPrice from utils/format.ts only swapped the currency symbol
(220 ₽ -> ¥220) without converting the underlying amount, because it
had no source of exchange rates and the landing/gift pages never called
the useCurrency hook that knew how to fetch them.

- Add a module-level rates cache in utils/format with setExchangeRates
  setter. formatPrice now converts kopeks/100 from RUB to the target
  currency via currencyApi.convertFromRub when rates are cached.
- useCurrency pushes its loaded rates into that cache so any subsequent
  formatPrice call benefits, including subcomponents that cannot easily
  receive a prop.
- Call useCurrency in QuickPurchase, GiftSubscription, and
  AdminLandingEditor — the only entry points whose subcomponents still
  use the synchronous formatPrice (everything else already routes
  through useCurrency.formatAmount).

For RU locale behavior is unchanged. For EN/ZH/FA the amount is now
divided by the per-currency rate and formatted via Intl.NumberFormat
with 2 fraction digits (0 for IRR since amounts are large).
2026-05-13 04:56:15 +03:00
Fringg
e6403eb7d0 fix: gift page shows only 1 tariff when tariffs have different periods
The gift page filtered tariffs by selected period, hiding tariffs
whose single period didn't match. When each tariff has a unique
period (30d, 60d, 180d, 360d), only the first tariff was visible.

- Remove period-first filtering (visibleTariffs/allPeriods)
- Show all tariffs directly from config.tariffs
- Auto-select the chosen tariff's period when switching tariffs
- Show tariff cards when more than 1 tariff exists
2026-04-23 03:29:00 +03:00
Fringg
859bd24d8e fix: gift code activation URL encoding and prefix handling
- Encode underscores as %5F in share URLs (bot link + cabinet link)
  to prevent Telegram auto-link detection from stripping trailing _
- Strip GIFT-/GIFT_ prefix from URL code param before pre-filling input
- Use ?? instead of || for prefix stripping to preserve empty strings
2026-03-29 06:47:50 +03:00
c0mrade
62188b8d2e fix: resolve all 14 ESLint warnings across the codebase
- Add varsIgnorePattern to no-unused-vars for destructuring patterns
- Fix all react-hooks/exhaustive-deps by adding missing dependencies
- Refactor useAnimatedNumber to use ref instead of stale state closure
- Wrap handleLinkResult in useCallback for stable deps
- Extract OAuth utilities from OAuthCallback.tsx to utils/oauth.ts
- Extract background config utilities to utils/backgroundConfig.ts
- Remove unused catch parameter in GiftSubscription
2026-03-13 20:19:09 +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
01e811bdfc fix: handle Telegram Stars payment for gift subscriptions via openInvoice
- Use openInvoice() with capabilities.hasInvoice guard for Stars payments
- Fall back to redirect for web environments without invoice support
- Add haptic feedback on payment success/failure
2026-03-12 03:32:14 +03:00
Fringg
03c9e73a37 feat: display promo group and active discount banners on gift page
Add promo group banner (green) and active discount banner (orange) to
gift subscription page. PeriodCard now shows original price strikethrough
and discount percentage badge from server-calculated values.
2026-03-11 02:58:43 +03:00
Fringg
39bdf8b5c3 fix: CopiedToast not visible due to CSS transform context
Use createPortal to render toast at document.body level,
escaping framer-motion's transform on the tab content wrapper.
Remove unused useRef import and shareToastTapCopy i18n key.
2026-03-10 07:39:04 +03:00
Fringg
9fbfcf21ff refactor: replace share modal with auto-copy toast
Remove the full-screen ShareModal dialog that appeared when sharing a
gift code. Replace with a bottom toast that automatically copies the
share message to clipboard on button press and slides up from the
bottom. The toast auto-dismisses after 5 seconds and can be tapped
to copy again.
2026-03-10 07:27:39 +03:00
Fringg
7945071893 refactor: rename GIFTCODE_ start parameter prefix to GIFT_ 2026-03-10 06:48:57 +03:00
Fringg
7549ae70eb feat: show localized error for self-activation attempt 2026-03-10 06:45:14 +03:00
Fringg
51ec799c0c feat: split my gifts into Active/Activated/Received sections
Sent gifts are now sorted into "Active gifts" (awaiting activation)
and "Activated" (already used). Received gifts remain separate.
2026-03-10 06:39:50 +03:00
Fringg
73d67bceed fix: use short 12-char code in bot and cabinet share links
GIFTCODE_ prefix (9 chars) + full 64-char token = 73 chars,
exceeding Telegram's 64-char start parameter limit. Now all
share links use the same short code shown in the UI (first 12
chars). Backend already supports prefix-based lookup.
2026-03-10 06:30:49 +03:00
Fringg
b213535738 fix: remove dark backdrop overlay from share modal 2026-03-10 06:25:06 +03:00
Fringg
a627eb0b30 fix: gift code display + share modal backdrop
- Don't uppercase gift codes (tokens are case-sensitive base64)
- Lighten share modal backdrop (bg-dark-900/40 instead of bg-black/60)
2026-03-10 06:20:29 +03:00
Fringg
d852bfe969 fix: activation broken — token uppercased + wrong env var for bot username
- Remove .toUpperCase() from activation code input — tokens are case-sensitive base64
- Fix VITE_BOT_USERNAME → VITE_TELEGRAM_BOT_USERNAME (correct env var name)
- Add Escape key handler, scroll lock, role="dialog" to ShareModal
2026-03-10 06:13:50 +03:00
Fringg
1bafcca1ef fix: gift UI improvements — declension, GB display, share modal, deep links
- Fix Russian device count declension (_one/_few/_many)
- Show traffic GB and tariff description in buy tab
- Replace copy/share buttons with share modal (bot + cabinet links)
- GiftResult: code-only success state with formatted share message
- Support ?tab=activate&code=TOKEN for auto-activation via cabinet link
- Add is_code_only + purchase_token to API types
2026-03-10 06:03:28 +03:00
Fringg
af3e535c69 feat: gift subscription redesign — code-only purchase + 3-tab UI
- Remove recipient input from buy flow (code-only gifts)
- 3-tab UI: Buy, Activate, My Gifts with animations
- After purchase, switch to My Gifts tab to show gift code
- Gift code display with copy/share buttons
- Activate tab: enter gift code to activate subscription
- My Gifts tab: sent/received gifts with status badges
- Fix: use navigator.language instead of hardcoded ru-RU for dates
- Fix: spread array before sort to prevent React Query cache mutation
- Fix: ARIA tab linkage with proper id/aria-labelledby
- Fix: guard VITE_BOT_USERNAME against undefined in share URLs
- Fix: wrap clipboard/share fallbacks in try/catch
- Add error state to MyGiftsTabContent
2026-03-10 05:37:57 +03:00
Fringg
c8ec221111 fix: remove bg-dark-950 from gift pages to preserve animated background 2026-03-09 21:03:05 +03:00
Fringg
6ea1de2e8a fix: harden gift subscription frontend after multi-agent review
- Handle expired status in GiftResult (stop polling + show FailedState)
- Add PollErrorState for balance mode poll errors (softer UX)
- Remove non-null assertions in handleSubmit (explicit narrowing)
- Wrap gift routes in ErrorBoundary
- Add pollErrorTitle/pollErrorDesc i18n keys to all 4 locales
2026-03-09 20:34:41 +03:00
Fringg
814b1f5e96 feat: add GiftSubscription and GiftResult pages 2026-03-09 18:56:16 +03:00