Commit Graph

1757 Commits

Author SHA1 Message Date
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
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
github-actions[bot]
400d9be345 chore(main): release 1.50.0 2026-04-29 09:13:13 +00:00
Egor
945c829a98 Merge pull request #413 from BEDOLAGA-DEV/dev
Dev
2026-04-29 12:12:34 +03:00
Fringg
afffab17d3 feat: bulk delete subscription protection for active paid subs
Add force_delete_active_paid guard to prevent accidental deletion of
active paid subscriptions. Shows warning with count and requires
explicit checkbox confirmation. Also fixes allVisibleSubscriptionIds
to use filteredUsers and getFilteredSubs to respect trialOnly filter
on subscription sub-rows.
2026-04-29 11:48:59 +03:00
Fringg
ae55a18fc9 feat: dedicated RBAC permissions for bulk actions, info pages, news 2026-04-29 11:14:38 +03:00
Fringg
020f4c95e2 feat: landing analytics goals, daily bar chart, referrer tracking, contact persistence
- Add per-landing analytics goals (view/click) with admin editor toggle
- Add sticky pay button option for mobile landing pages
- Add daily purchases bar chart (created vs paid) to landing stats
- Replace single purchase count with created/paid split in stats summary
- Add referrer tracking to purchases with hostname display in stats
- Add time display to purchase cards alongside date
- Pass user timezone to stats API for correct daily grouping
- Clamp referrer (500 chars) and subid (255 chars) to backend limits
- Persist contact value per-landing-slug in localStorage
- Fire buy_success analytics goal on successful delivery
- Export USER_TIMEZONE from format utils
- Add analytics/stats translations for fa.json and zh.json locales

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 09:17:52 +03:00
Fringg
a50bd39df2 fix: validate counterId/conversionId before script injection (XSS prevention) 2026-04-29 09:06:29 +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
8044b664c3 fix: restore cabinet_last_login in user detail (now shows real data) 2026-04-29 06:46:20 +03:00
Fringg
8fcdbbe53e fix: remove stale cabinet_last_login field from user detail 2026-04-29 06:42:25 +03:00
Fringg
bc37f31350 feat: subscription selector in VPN connection block for multi-tariff
- Show VPN connection block even when panelInfo not yet loaded
- Add subscription dropdown when user has multiple subscriptions
- Show spinner while loading, "no data" when panel returns not found
- Changing subscription in VPN block reloads panel info for that sub
2026-04-29 06:34:41 +03:00
Fringg
853e1c9c84 fix: user detail — separate request history sub selector, split mount effects
- Request history subscription selector no longer mutates shared
  activeSubscriptionId — uses independent requestHistorySubId state
- Request history reloads when subscription selector changes
- Split mount effect: loadPanelInfo in separate effect to avoid
  redundant loadUser calls when activeSubscriptionId changes
2026-04-29 06:22:38 +03:00
Fringg
e1d2f8cee4 feat: show VPN connection info and subscription request history on admin user detail
- Load panelInfo eagerly on page load (not just on subscription tab)
- Add VPN connection card to info tab: last/first connection, online status indicator, last node
- Rename "Last activity" to "Bot activity" to distinguish from VPN connection
- Show cabinet_last_login field that was never rendered
- Add collapsible subscription request history section in subscription tab with paginated table
- Add subscription request history API types and method
- Add i18n keys for all 4 locales (ru, en, zh, fa)
2026-04-29 06:11:12 +03:00
Egor
22aa8be212 Merge pull request #410 from BEDOLAGA-DEV/release-please--branches--main--components--cabinet-frontend
chore(main): release 1.49.0
2026-04-24 17:12:30 +03:00
github-actions[bot]
83d7305276 chore(main): release 1.49.0 2026-04-24 14:12:14 +00:00
Egor
3f47bc8024 Merge pull request #409 from BEDOLAGA-DEV/dev
Dev
2026-04-24 17:11:14 +03:00