Commit Graph

1824 Commits

Author SHA1 Message Date
c0mrade
fefc52fb20 fix(a11y): label associations for AdminNewsCreate fields
- htmlFor/id pairs for title, slug, read-time, excerpt and featured-image inputs
- add an ariaLabel prop to ColoredItemCombobox (applied to the trigger button) and
  pass the category/tag labels so the comboboxes have an accessible name (WCAG 1.3.1/4.1.2)
2026-05-26 00:20:30 +03:00
c0mrade
1b40c3856d fix(a11y): associate referral form labels with their inputs
Add htmlFor/id pairs across the partner-apply form (company, channel, website,
description, expected referrals, desired commission) and the withdrawal-request form
(amount, payment details) so labels are programmatically tied to their controls (WCAG 1.3.1).
2026-05-26 00:06:59 +03:00
c0mrade
7b0a72db5b fix(a11y): switch semantics for admin form toggles
Add role=switch + aria-checked + aria-label to 12 ad-hoc toggle buttons across admin
create/edit forms (AdminCampaignCreate/Edit, AdminPromocodeCreate, AdminPromoGroupCreate,
AdminPromoOfferTemplateEdit, AdminPaymentMethodEdit, AdminTariffCreate) so screen readers
announce on/off state (WCAG 4.1.2).
2026-05-26 00:03:17 +03:00
c0mrade
491f48a331 fix(a11y): switch semantics for user-flow toggles
Add role=switch + aria-checked + aria-label to the custom toggle buttons so screen
readers announce on/off state (WCAG 4.1.2): autopay (Subscription) and the custom-days
/ custom-traffic toggles (SubscriptionPurchase).
2026-05-25 23:57:36 +03:00
c0mrade
90888db48c fix(a11y): associate Support ticket form labels with their inputs
Add htmlFor/id pairs for the create-ticket subject and message fields so screen
readers announce the label and clicking the label focuses the control (WCAG 1.3.1).
2026-05-25 23:53:42 +03:00
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
0caba3dffb feat(a11y,i18n): more modal focus-traps + Telegram theme/language sync
Modal a11y:
- focus-trap + role=dialog/aria-modal/aria-labelledby on AdminRoles delete confirm
  and AdminBanSystem user-detail modal (+ close aria-label)

Telegram theme/language sync (first run only, explicit choice always wins):
- getTelegramColorScheme() / getTelegramLanguageCode() helpers in useTelegramSDK
- useTheme initial state follows the Telegram client color scheme when no stored theme
- applyTelegramLanguage() adopts the Telegram client language when no stored choice,
  called from main.tsx after SDK init (launch params unavailable before init)
2026-05-25 23:31:38 +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
2bcba3b60f fix(navigation): make Telegram back button work on deep-link entry
Bot deep-links open the Mini App directly on nested routes (/admin,
/balance/top-up, /info, /profile, ...) where React Router history has a
single entry, so navigate(-1) was a no-op and the native back button
looked dead. Fall back to the derived parent route when there is no
in-app history (window.history.state.idx === 0).
2026-05-22 15:02:56 +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
Egor
ec3eebeff3 Merge pull request #430 from BEDOLAGA-DEV/release-please--branches--main--components--cabinet-frontend
chore(main): release 1.53.0
2026-05-16 06:14:38 +03:00
github-actions[bot]
d8720d30aa chore(main): release 1.53.0 2026-05-16 03:13:17 +00:00
Egor
1c17bbe5ad Merge pull request #429 from BEDOLAGA-DEV/dev
Dev
2026-05-16 06:12:38 +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
47f0359ed6 fix(subscription): invalidate devices query after revoke
On revoke, RemnaWave panel clears all device HWIDs along with the
subscription URL rotation, but the cabinet was only invalidating
['subscription'], ['connection-link'], ['subscriptions-list'] — leaving
['devices', subscriptionId] cached. UI showed the now-stale device list
until the user manually refreshed.

The GET /subscription/devices endpoint reads HWIDs live from RemnaWave
(no DB cache to clear), so invalidating the TanStack query is
sufficient — the immediate refetch returns the now-empty device list.
2026-05-16 04:34:32 +03:00
Fringg
e964780e6f refactor(devices): close all MED/LOW review items
- Extract DEVICE_ALIAS_MAX_LENGTH to src/constants/devices.ts so the
  user page and admin page share one source of truth (mirroring the
  bot's ALIAS_MAX_LENGTH=64). Was duplicated as a magic 64 in both
  files (MED-5).
- Replace ✓ / ✕ / ✏️ glyphs in AdminUserDetail.tsx with inline SVGs,
  matching the icon style already used by Subscription.tsx for the
  same rename/save/cancel actions. Eliminates the platform-dependent
  emoji rendering inconsistency (MED-4).
- Add haptic feedback to the user-side renameDeviceMutation —
  notification('success') on save, notification('error') on failure.
  Brings it in line with the other Subscription.tsx mutations
  (LOW-9).
- Translate the new rename keys into zh.json and fa.json (en.json
  already covered in the previous follow-up). Cabinet now ships full
  parity across all 4 locales for the rename UI.
2026-05-16 04:07:25 +03:00
Fringg
d1e5ce873b fix(devices): unicode escape bug + onSuccess race + en locale + apiErr surface
Code-review follow-up on 321c65b.

- prettier ate the raw glyphs (✓/✕/✏️) in AdminUserDetail.tsx and emitted
  \u2713 / \u2715 / \u270F\uFE0F as JSX text children. JSX does NOT
  process \u… escapes inside element children, so admin users saw the
  literal six-character strings on Save/Cancel/Rename buttons. Wrap each
  glyph in a JS expression {'…'} so it survives both prettier and JSX.
- renameDeviceMutation.onSuccess on Subscription.tsx no longer
  unconditionally clears the edit state. If the user already navigated
  to a different device while the previous save was in flight, we keep
  their in-progress input. Same shape applied to AdminUserDetail.tsx —
  the alias is snapshot before the await so a race can't smuggle the
  wrong value into the request, and the post-success reset is guarded.
- AdminUserDetail rename error path now surfaces the backend's
  response.data.detail (e.g. '64-char limit' message) instead of the
  generic localized userActions.error toast.
- Add the new rename i18n keys to src/locales/en.json so English users
  no longer fall back to Russian labels. ua/zh/fa to follow in a
  dedicated translation pass.
2026-05-16 03:53:49 +03:00
Fringg
321c65b68b feat(devices): inline rename UI for connected HWID devices
Pairs with the bot commit that adds the user_device_aliases table.
Surfaces the new local_name field on the existing devices list in
both the user-facing subscription page and the admin user-detail
page, with the same inline-edit pattern in both places.

User-side (src/pages/Subscription.tsx):
- pencil button next to each device row toggles edit mode
- input is focused automatically, capped at 64 chars (matches the
  backend ALIAS_MAX_LENGTH and DB column width)
- Enter saves, Escape cancels, empty input clears the alias
- display priority: local_name → device_model → platform
- works identically in classic / single-tariff / multi-tariff —
  subscriptionId is forwarded as a query param like every other
  device-management endpoint already does

Admin-side (src/pages/AdminUserDetail.tsx):
- same pencil + inline input pattern, admin acts on behalf of the
  user. notify.success on save, loadDevices() refresh.

API (src/api/subscription.ts + src/api/adminUsers.ts):
- new renameDevice(hwid, name, subscriptionId?) on subscriptionApi
- new renameUserDevice(userId, hwid, name) on adminUsersApi
- existing getDevices/getUserDevices contracts widened with
  local_name?: string | null on the returned device shape

Locales (src/locales/ru.json):
- subscription.renameDevice / .renameDeviceSave / .renameDeviceCancel
  / .renameDevicePlaceholder / .deviceRenamed
- admin.users.detail.devices.rename / .renameSave / .renamed
2026-05-16 03:02:45 +03:00
Fringg
d85da9e03f docs(antilopay): document XSS invariant on setAttribute('content', ...)
Security reviewer flagged that the safety of injecting payment-provider
verification tokens into <meta content=...> rests entirely on the use
of Element.setAttribute() (plain-string attribute, browser does not
parse as HTML). A future contributor switching to innerHTML or template-
string concatenation into <head> would turn this hook into an XSS sink.
Add a comment that calls this out explicitly so the invariant survives
maintenance.
2026-05-16 02:25:37 +03:00
Fringg
6fa4afd5c1 feat(antilopay): inject apay-tag meta on cabinet boot
Pairs with the bedolaga-bot commit that exposes
GET /cabinet/public/site-verification (JSON: { apay_tag: string | null }).

New useSiteVerification hook fires once on App mount, fetches the
configured tag value, and upserts <meta name='apay-tag' content='...'>
into document.head. When the bot returns null we proactively remove
any previously-rendered tag so toggling the env var off cleans up the
page.

Failure modes are silent — verification is best-effort and must
never block the cabinet from rendering. No admin UI field is needed:
the value lives in the bot's .env (ANTILOPAY_APAY_VERIFICATION_TAG),
matching how all other Antilopay credentials are configured.
2026-05-16 02:15:17 +03:00
Fringg
f301d44f24 fix(admin): show flag for every country code, not just hardcoded 25-36
User reported the EE (Estonia) flag rendered as plain text 'EE' on the
admin servers page. Root cause: getCountryFlag() in AdminServers.tsx
and AdminServerEdit.tsx hardcoded a 25-entry codeMap that didn't
include EE; falling through to 'return code' produced raw text.

Two other admin pages (AdminRemnawaveSquadDetail, AdminUserDetail)
had bigger 35-entry maps that did include EE — so EE worked there
but MX/AR/EG/ZA and friends still wouldn't. AdminRemnawave repeated
the same 35-entry map. AdminTrafficUsage already had the correct
algorithmic ISO→regional-indicator code but as a local duplicate of
utils/subscriptionHelpers.

Unify all six on the single algorithmic helper:

- getFlagEmoji() in utils/subscriptionHelpers.ts now:
  * accepts string | null | undefined (callers don't need to guard)
  * trims whitespace
  * validates [A-Za-z]{2} before composing regional indicators
- Each admin page now either imports getFlagEmoji directly or wraps
  it with the page's preferred fallback character (e.g. '🌍' for
  empty codes where the UI expects a placeholder).
2026-05-16 01:11:00 +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
Egor
a10f4ea2d9 Merge pull request #426 from BEDOLAGA-DEV/release-please--branches--main--components--cabinet-frontend
chore(main): release 1.52.0
2026-05-13 11:41:39 +03:00
github-actions[bot]
f62607baf4 chore(main): release 1.52.0 2026-05-13 08:38:57 +00:00
Egor
a0243cb307 Merge pull request #425 from BEDOLAGA-DEV/dev
Dev
2026-05-13 11:38:18 +03:00
Fringg
7ae2787596 fix(build): drop redundant dynamic import of branding api
Vite warned that `api/branding.ts` was dynamically imported from
`useAnalyticsCounters.ts` while also being statically imported by 18 other
files. The mixed static+dynamic pattern blocked Vite from splitting the
module into a separate chunk — the dynamic import was a no-op for bundle
size and only added an extra Promise hop.

The same file already had `import { brandingApi } from '../api/branding'`
at the top. Inlined the storeYandexCid call to use it directly.
2026-05-13 11:26:03 +03:00
Fringg
5493c25e5f chore(topup): harden direct-open guard and admin toggle defenses
Post-review nits:
* AdminPaymentMethodEdit.tsx: `config.open_url_direct ?? false` when seeding
  state — defends against stale backend rendering (e.g. cache invalidation
  race before migration applies)
* Same file: aria-label now uses the localized `admin.paymentMethods.openUrlDirect`
  translation key instead of hardcoded English
* TopUpAmount.tsx: case-insensitive guard for Telegram deep-link URLs —
  `https://t.me/`, `http://t.me/`, and `tg://` all match regardless of provider
  casing quirks. Lowercase normalization done once before the comparisons.
2026-05-13 11:13:33 +03:00
Fringg
aa8bfc9d08 feat(topup): direct-open payment page when method.open_url_direct is set
User asked for the gift-style seamless flow on balance top-up: provider
checkout opens inside Telegram MiniApp WebView without a click-to-open link
panel. Made it an admin per-method toggle so it can be enabled selectively.

src/pages/TopUpAmount.tsx — on topup mutation success:
* Move saveTopUpPendingInfo to BEFORE any redirect so /balance/top-up/result
  can still pick up the pending payment after the provider's return_url fires
* If method?.open_url_direct === true AND the URL is not a t.me/ deep link
  (Stars/CryptoBot), window.location.href = redirectUrl and return early
* Otherwise fall through to the existing setPaymentUrl panel — preserves
  current behavior for methods without the flag enabled

The t.me/ guard is important: window.location.href to a Telegram deep link
inside a MiniApp WebView is unreliable (native shell cant always intercept).
Those URLs continue to go through openTelegramLink / openInvoice in the panel
path. Stars never reaches topUpMutation.onSuccess anyway (handled by the
separate starsPaymentMutation); the guard is defense-in-depth for CryptoBot
and any future t.me-deep-link providers.

src/pages/AdminPaymentMethodEdit.tsx — added Open URL directly toggle with
the same slider styling as the is_enabled toggle. Defaults to off.

src/types/index.ts
* PaymentMethod.open_url_direct?: boolean (user-facing)
* PaymentMethodConfig.open_url_direct: boolean (admin shape)

Translations added for ru/en/zh/fa with a hint clarifying behavior and the
t.me/ exemption.
2026-05-13 10:50:31 +03:00
Fringg
2a342f6adc fix(topup): preserve canonical RUB amount to avoid FX round-trip shortfall
User report: a user in EN locale paid 1.65 USD for a 150₽ subscription, but the
bot received 14960 kopeks (149.60₽) — short by 40 kopeks, blocking the
subscription purchase.

Root cause: TopUpAmount.tsx displays the prefilled RUB amount converted via
`.toFixed(2)`. With an exchange rate like 90.66 RUB/USD, 150₽ → 1.6545 USD →
displayed "1.65" (rounded down by 0.0045 USD ≈ 0.4 RUB). When the user submits
without editing, `convertToRub("1.65")` runs again and returns 1.65 × 90.66 =
149.589₽ — less than the 150₽ the user is trying to pay. Math.round/Math.ceil
on this only handles sub-kopek IEEE-754 fractions, not the deeper FX display-
rounding direction.

Fix: when the user does not edit the prefilled amount (`amount === initialDisplayAmount`)
and `initialAmountRubles > 0` is known, bypass the FX round-trip and send
`Math.round(initialAmountRubles * 100)` directly. Math.ceil for non-RUB targets
when the user does type a custom amount still helps with sub-kopek fractions.

`?amount=150` from a renew CTA → "1.65 USD" displayed → user clicks pay → bot
receives 15000 kopeks instead of 14960. Subscription renews as expected.
2026-05-13 10:24:08 +03:00
Fringg
172850e13a fix(profile): use displayName helper for name field
Follow-up to displayName migration. Profile page still rendered
`{user?.first_name} {user?.last_name}` directly, leaving a trailing space when
last_name is null. displayName() handles the joining and fallback uniformly.
2026-05-13 09:20:48 +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
7c10843d9c fix(cabinet): show trial offer in multi-tariff mode
Two cabinet routes never showed TrialOfferCard for users without any
subscription when MULTI_TARIFF was enabled:

1. Dashboard (/)
   hasNoSubscription was derived from subscriptionResponse, but the
   /cabinet/subscription query is disabled in multi-tariff mode
   (`enabled: !isMultiTariff`). subscriptionResponse stayed undefined
   forever, so `has_subscription === false` was never true and the
   trial card never rendered.

   Fix: branch on isMultiTariff — use multiSubData.subscriptions.length
   when in multi-tariff, keep the single-tariff path unchanged.

2. /subscriptions list
   When the array came back empty the page rendered EmptyState directly,
   bypassing trial eligibility entirely. Users who navigated to
   'Подписки' from the menu before opening the dashboard saw the empty
   placeholder and nothing about the trial.

   Fix: fetch trial-info on empty state, render TrialOfferCard if
   trialInfo.is_available, fall back to EmptyState otherwise. Also
   wired the activate mutation locally (mirrors Dashboard) so the
   button works without bouncing through the home screen first.

Single-tariff behavior is unchanged. After the fix both entry points
agree: if you have no subscriptions and trial is available, you see
the offer regardless of which page you opened first.
2026-05-13 05:31:22 +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
1fdafbd0a1 fix(subscription): hide strikethrough free label on device addon price
The per-device price line in the buy-devices modal showed a
strikethrough 'Бесплатно' for users with a promo-group discount,
because original_price_per_device_kopeks was missing in the API
response and formatPrice(0) renders 'Бесплатно'. Now the
strikethrough is rendered only when original_price_per_device_kopeks
is present and non-zero, mirroring the existing guard on the total
price block.
2026-05-10 09:59:57 +03:00
Egor
206926a3a3 Merge pull request #420 from BEDOLAGA-DEV/release-please--branches--main--components--cabinet-frontend
chore(main): release 1.51.0
2026-05-04 21:01:21 +03:00
github-actions[bot]
b63b51bc05 chore(main): release 1.51.0 2026-05-04 17:58:13 +00:00
Egor
0fdfb06554 Merge pull request #419 from BEDOLAGA-DEV/dev
Dev
2026-05-04 20:57:31 +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
60c835301d fix: move reissue button to standalone block outside device_limit guard
The reissue button was inside the Additional Options card which is gated
by device_limit !== 0, so it was hidden when device_limit was 0. Move it
to its own card block with independent visibility condition.
2026-05-04 17:41:21 +03:00
Fringg
f7cc445127 feat: add subscription reissue button with cooldown timer
- Add revokeSubscription API method
- Add reissue button in Additional Options with amber styling,
  destructive confirm dialog, 15-min cooldown with countdown timer,
  localStorage persistence across page reloads
- Add i18n keys in all 4 locales (en, ru, zh, fa)
2026-05-04 17:22:05 +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
7d29285ff6 fix(i18n): add missing broadcast preview locale keys
Add preview, previewEmpty, btnBalance, btnPartners, btnPromocode,
btnConnect, btnSubscription, btnSupport, btnHome keys to en.json
and ru.json under admin.broadcasts.
2026-05-04 06:27:41 +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
Egor
f0c60e0bff Merge pull request #414 from BEDOLAGA-DEV/release-please--branches--main--components--cabinet-frontend
chore(main): release 1.50.0
2026-04-29 12:13:38 +03:00