Commit Graph

130 Commits

Author SHA1 Message Date
c0mrade
0d024aec58 fix(i18n): spell the brand "Remnawave" instead of camelCase "RemnaWave"
Replace the user-facing 'RemnaWave' spelling with 'Remnawave' across all locale values (en/ru/zh/fa, 67 strings), t() display fallbacks, and code comments. Translation keys (refreshRemnaWave, ...) and code identifiers (RemnaWaveService, setRemnaWaveUuid, ...) are left untouched.
2026-06-01 13:31:12 +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
e1f401c333 fix(i18n): backfill 120 missing keys + close 2 typo regressions from purchase refactor
Surface: users (and especially Серёжа в bugs topic) were seeing raw i18n keys
like `subscription.promoGroup.title` rendered as literal text on the renewal
screen. Audit found 118 keys referenced in code that were absent from all 4
locale files, plus 140 keys missing only in zh/fa (silently falling back to
Russian for those users).

Root causes:
- cf52999 (extract TariffPickerGrid) introduced `subscription.promoGroup.title`
  that never existed — should have been `subscription.promoGroup.yourGroup`.
- Subscription.tsx used `subscription.autopay.title` (object path) while
  SubscriptionListCard.tsx used `subscription.autopay` (leaf) — i18next can't
  hold both shapes for the same key.
- Many user-flow keys (subscription.notFound, subscriptions.empty, common.ok,
  successNotification.*, etc.) were never added when their components landed.
- Historical drift had ~140 keys in ru/en that never got zh/fa translations.

Fix:
- Rename TariffPickerGrid usage `promoGroup.title` → `promoGroup.yourGroup`.
- Rename Subscription.tsx usage `autopay.title` → `autopay`.
- Add 116 brand-new keys across ru/en/zh/fa (464 entries).
- Add 8 partially-missing keys (en+ru gaps) for landing periods, dashboard
  campaign/referrer stats, and promo offer failure counts.
- Translate 280 entries to close the zh/fa drift.

Final state: 0 keys missing in any of the 4 locale files for any code-referenced
translation key. Build + tsc green.
2026-05-28 18:47:58 +03:00
c0mrade
5784efd7a3 fix(chrome): replace 2 platform emoji + 1 en-dash with on-brand tokens
PRODUCT.md anti-references call out the SaaS-template / emoji-in-chrome
register: the reference products (Linear, Stripe, Things 3) don't put
platform emoji in product chrome. Three small drift spots:

- Balance saved-cards link used a 💳 emoji as the leading icon.
  Replaced with a new CreditCardIcon added to the barrel (Heroicons-
  style stroke, matches the chevron / chat / wallet siblings).

- Subscription paused-info banner used a ⏸️ emoji. Replaced with a
  new PauseIcon added to the barrel. Same 24×24 stroke, currentColor,
  preserves the urgent-400 tint via inline style.

- Balance payment-method min/max range used a literal en-dash ('–')
  in source. PRODUCT.md's anti-em-dash rule covers the en-dash by
  the same logic. Switched to a translated 'to' connector
  (t('common.rangeTo', 'to')) so other locales can translate freely.
2026-05-26 23:33:21 +03:00
c0mrade
75f775064c fix(decoration): strip 4 accent / status-hue leaks across hero surfaces
DESIGN.md 'Tunable-but-Scarce Rule' caps accent at <=10% of any
screen and reserves the accent + status hues for action / status,
never decoration. Four sites were spending those tokens on pure
chrome:

- Subscription.tsx purchased-traffic bar: zone.mainHex linear-gradient
  on a non-status progress bar; the per-purchase bar inherited the
  user's GLOBAL traffic-zone color (a fresh purchase could read
  'critical' just because the user's overall usage was). Replaced
  with solid accent-500 fill — same affordance, honest semantics.

- SubscriptionCardActive tariff badge: zone-colored linear-gradient
  background + zone-colored label. The tariff name has no traffic-zone
  semantics, so tinting it by the global traffic zone was a
  Status-Hue Lockout violation. Switched to glass innerBg/innerBorder.

- Balance hero card: bg-gradient-to-br from-accent-500/10 + glow prop
  on Card. Removed; flat surface, the giant numeric is the affordance.
  Eliminates the SaaS hero-metric template tell.

- Login background: two stacked fixed inset gradients (linear +
  accent radial halo) read as the airdrop / crypto aesthetic
  PRODUCT.md explicitly anti-references. Replaced with the plain
  body bg-dark-950.
2026-05-26 23:26:20 +03:00
c0mrade
312a34f715 fix(subscription): clear critique P0 + P1 + part of P2
Three issues from the post-PRODUCT.md /impeccable critique pass:

P0 — backdrop-blur-xl shipped to mobile on the two hero cards
(SubscriptionCardActive + Subscription detail). DESIGN.md
'Mobile-Without-Blur Rule' restricts backdrop-blur to ≥1024px.
Gate with lg: prefix so phones (half the audience) get the opaque
surface and skip the scroll-jank-inducing GPU blur layer.

P1 — Two destructive device-deletion sites still called the bare
browser confirm(). The codebase already imports useDestructiveConfirm
and uses it correctly for revoke (line 484). Bring the all-devices
and per-device delete paths onto the same platform-aware path, so
inside Telegram the user gets the native destructive popup with
haptic + theme, and on web they get the inline destructive panel.

P2 (partial) — Removed the decorative ambient radial-gradient halo
behind the Subscription detail hero card and the trial-shimmer border
overlay. Same chrome was distilled out of SubscriptionCardActive
earlier in this branch; the rationale is identical (zone/accent hue
leaking into pure decoration violates DESIGN.md Tunable-but-Scarce
+ Status-Hue Lockout rules). Trial state is conveyed by the header
badge.
2026-05-26 23:23:47 +03:00
c0mrade
bf3c114a72 refactor(subscription): extract DeleteSubscriptionSheet from god page
Move the delete-subscription expand/confirm block into
src/components/subscription/sheets/DeleteSubscriptionSheet.tsx.

The sheet itself decides between the Telegram destructive native dialog
and the inline web confirm panel, so the parent no longer needs the
platform discriminator. It owns deleteLoading and the API call; the
parent passes onDeleted (navigate + invalidate) and the textSecondary
glass color token used in the warning copy.

Subscription.tsx: 1828 → 1750 lines.
Drops now-unused imports (usePlatform; deleteLoading state).
2026-05-26 22:24:20 +03:00
c0mrade
d48d053e6e refactor(subscription): extract ServerManagementSheet from god page
Move the classic-mode manage-servers sheet (~280 lines of inline
JSX, the countries useQuery, its seeding useEffect, and the update
mutation) into src/components/subscription/sheets/ServerManagementSheet.tsx.

Parent keeps selectedServersToUpdate so the global 'close all modals'
callback can still clear it; the sheet owns the rest. The query stays
gated on trial state inside the sheet.

Subscription.tsx: 2117 → 1828 lines (−289).
2026-05-26 22:21:12 +03:00
c0mrade
fbf34a4861 refactor(subscription): extract TrafficTopupSheet from god page
Move the buy-traffic sheet (~170 lines of inline JSX + its
packages useQuery + purchase useMutation) into
src/components/subscription/sheets/TrafficTopupSheet.tsx.

Parent keeps selectedTrafficPackage in its state so the global
'close all modals' callback can still reset it; the sheet owns the
network calls and only fires while open.

Subscription.tsx: 2288 → ~2120 lines.
2026-05-26 22:17:41 +03:00
c0mrade
5902f40187 refactor(subscription): extract DeviceReductionSheet from god page
Move the reduce-devices sheet (~170 lines of inline JSX + its
useQuery + useMutation + seeding useEffect) into
src/components/subscription/sheets/DeviceReductionSheet.tsx.

Parent passes the shared targetDeviceLimit state (also used by the
revoke flow earlier) plus open/close + ids; the sheet owns the
reduction-info query and the reduce mutation, so neither fires until
the sheet is open.

Subscription.tsx: 2477 → 2288 lines (−189).
2026-05-26 22:15:15 +03:00
c0mrade
5e221d471e refactor(subscription): extract DeviceTopupSheet from god page
Move the buy-devices sheet (190 lines of inline JSX + its useQuery +
useMutation) into src/components/subscription/sheets/DeviceTopupSheet.tsx.
Parent passes shared state (purchaseOptions, devicesToAdd, ids); the
sheet self-owns the device-price query and purchase mutation, so the
query only fires when the sheet is actually open.

Subscription.tsx: 2671 → 2477 lines (−194).
2026-05-26 22:12:03 +03:00
c0mrade
eb13689ae7 perf(motion): convert layout-property transitions to transforms
Audit P1: 5 of 7 layout-property animations were transition-[width]
or transition-[left] — these trigger reflow + repaint every frame.
Convert to transform-based equivalents that run on the compositor.

Subscription.tsx — 3 progress bars + 1 toggle:
  • Device usage bar (track w-16) — width % → scaleX with a 0.0625
    floor (= 4px on 64px track) preserving the minWidth: 4px guard
    when connectedDevices > 0.
  • Traffic-purchase progress — absolute inset-0 + width % →
    transform: scaleX with origin-left, gradient stretches with the
    fill same as before.
  • Subscription period progress — same conversion.
  • Autopay toggle thumb — left: 3↔26px → translateX(0↔23px) with
    fixed left: 3px. role='switch' + aria-checked unchanged.

globals.css — .progress-fill utility class:
  • transition-property: width, background-color → transform,
    background-color. Width set to w-full + origin-left so consumers
    just set transform: scaleX(0..1). Currently has no .tsx consumers
    (grep clean) but the API stays valid for future use.

Skipped (bounded one-shot, transform alternative has tradeoffs):
  • Info.tsx + InfoPageView.tsx accordion height. Animating from 0
    to content-driven scrollHeight needs layout one way or another —
    grid-template-rows 0fr/1fr and max-height also layout-class
    properties; transform: scaleY squishes text contents. Cost is
    bounded: one animation per user click, not continuous, not on
    critical path. Separate work if priority changes.

Visual parity: scaleX renders identical geometry to width % on the
inner fill. box-shadow on the device bar (0 0 8px 40)
scales proportionally — tiny visual difference at full width, none
visible at partial widths.
2026-05-26 20:27:47 +03:00
c0mrade
cef675825b refactor(tokens): extract subscription urgent + critical semantic colors
The #FFB800 (23 hits) and #FF3B5C (11 hits) raw hex literals across
Subscription / SubscriptionPurchase / TrialOfferCard / useTrafficZone
all represent the same semantic: 'expiring soon' and 'expired'.
Distinct from warning-500 (#f59e0b) and error-500 (#ef4444) — the
subscription timeline needs a sharper, hotter signal than the
system-wide warning/error roles.

Add two semantic tokens:
  --color-urgent-400:   255, 184, 0    (#FFB800)
  --color-critical-500: 255, 59, 92    (#FF3B5C)

Exposed via Tailwind as urgent.400 / critical.500. Sweep all
single-and double-quoted hex literals to rgb(var(--color-...))
form so style attrs, SVG stroke=, and ternary string values all
resolve through CSS vars (auto-respects future light-theme
variants).

Left as-is: 4 hex appearances inside gradient pair strings —
linear-gradient(135deg, #FFB800, #FF8C00) — where the partner
color (#FF8C00 / #FF6B35) isn't in the token set. Partial-replace
inside gradient strings would just add noise; full extraction of
the orange-ramp companion is out of scope for this pass.
2026-05-26 20:20:05 +03:00
c0mrade
9f06526238 fix(a11y): aria-label on icon-only buttons (Subscription copy + InstallationGuide back/QR)
Subscription copy-URL button only had a title attribute; back and QR-open buttons in
InstallationGuide had no accessible name. Add aria-label so screen readers announce
the action (WCAG 2.4.6).
2026-05-26 12:06:15 +03:00
c0mrade
bd8a4fad6e refactor(theming): replace amber-* with warning-* tokens
Sweep all amber-N color classes to warning-N (uniformly pending/warning semantics).
Yellow and orange left for per-file review (mixed Stars brand + semantic uses).
2026-05-26 11:10:50 +03:00
c0mrade
9c5e38b594 refactor(theming): replace red-* palette with semantic error-* tokens
Sweep 30 files: text/bg/border/ring/from/to/via/fill/stroke/shadow/divide/decoration/
outline/placeholder-red-N → -error-N. All red usages were semantically error/danger
(no brand red), so tokens now flow through the design-system CSS variables and respond
to palette overrides.
2026-05-26 11:08:22 +03:00
c0mrade
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
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
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
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
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
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
Dmitry Lunin
d75440bfe4 fix: wrap server display names with Twemoji for cross-platform emoji rendering 2026-04-21 23:03:20 +03:00
Egor
ac89343cc1 Dev (#394)
* Make SBP default and first top-up option (#389)

* fix(connection): happ cryptolink flow + ui fixes (#385)

* fix(connection): respect happ_cryptolink mode

- read connect_mode from connection-link endpoint
- auto-redirect to happ cryptolink flow when mode is HAPP_CRYPTOLINK
- keep installation guide behavior for other modes

* docs(readme): fix source build step 2

- use compose service name for build/create commands
- copy static files from created compose container
- remove compose container via docker compose rm

* fix(connection): handle mode and ws path

- treat any non-guide connect mode as direct happ redirect
- wait for connection-link response before rendering installation guide
- build websocket URL from VITE_API_URL (supports /api and absolute URLs)

* docs(readme): fix docker cp static path

- copy /usr/share/nginx/html contents via html/.
- prevent nested /srv/cabinet/html deployment and 404 on root

* fix(connection): keep guide, use happ links

- restore guide page behavior for /connection (no auto redirect)
- use happ cryptolink URL in /connection/qr when happ crypt mode is active
- replace subscription page connection URL with happ cryptolink from connection-link API

* fix(connection): avoid wrong redirect flow

- force happ scheme deeplink in HAPP_CRYPTOLINK mode for guide connect buttons
- use cabinet redirect page only inside Telegram Mini App
- open deeplink directly in regular browsers

* fix(connection): enforce happ cryptolink

- prioritize backend crypt deeplink fields in HAPP_CRYPTOLINK mode
- fallback to local crypt4/crypt3 generation from subscription URL when backend returns plain happ://sub...
- apply same resolution for guide open action, qr page source and subscription page link display/copy

* fix(subscription): truncate long connect link

- render connection URL in a single line with ellipsis on /subscriptions
- preserve full URL in tooltip for easier manual copy
- keep copy button behavior unchanged

* fix(connection): build cryptolink from happ sub

- allow cryptolink fallback generation from happ://sub... URLs in addition to http(s)
- prevent plain happ://sub links from leaking into UI in HAPP_CRYPTOLINK mode

* fix: allow saving 0% period discount in promo groups

Changed condition from percent > 0 to percent >= 0 so that
admins can explicitly set 0% discount for a period. Previously
0% entries were silently dropped and not sent to the backend.

* fix: always send period_discounts in promo group updates

When all period discounts were removed, the frontend sent undefined
(field absent from JSON), so the backend never cleared them.
Now always sends the field - empty object {} to clear, or populated
object to update.

---------

Co-authored-by: zavul0nn <34007368+zavul0nn@users.noreply.github.com>
Co-authored-by: Dxnil <62987903+D4nilKO@users.noreply.github.com>
2026-04-15 14:04:39 +03:00
c0mrade
3d0fc76ecd fix: subscription status badge overflow + inline password validation
- Status badge (e.g. 'ПРИОСТАНОВЛЕНА (НЕДОСТАТОЧНО СРЕДСТВ)') now
  constrained with max-w-[55%] to prevent overlapping tariff name
- Password too short error shown inline under password field during
  registration instead of at the top of the page (invisible on mobile)
2026-04-13 14:43:29 +03:00
c0mrade
207af81c95 fix: support 100% discount display + fix Telegram link widget race condition
- formatPrice shows "Бесплатно/Free" when price is 0 (SubscriptionPurchase, Subscription, RenewSubscription)
- Add subscription.free locale key (ru/en/zh/fa)
- Fix TelegramLinkWidget NS_BINDING_ABORTED: move legacy widget callback to ref pattern, remove unstable deps (showToast, t, navigate) from useEffect — prevents iframe destruction on re-render
- Clean up OIDC effect deps
2026-04-03 17:23:38 +03:00
Fringg
9546e0ffe6 fix: add missing balance cache invalidation after mutations
- Subscription.tsx: devicePurchase + trafficPurchase onSuccess → invalidate ['balance']
- RenewSubscription.tsx: renewMutation onSuccess → invalidate ['balance']
- SubscriptionPurchase.tsx: purchaseMutation onSuccess → invalidate ['balance'] + ['subscriptions-list']
- subscription.ts: refreshTraffic uses withSubId (already fixed, formatting)
2026-03-26 10:44:00 +03:00
c0mrade
45b7c857fc fix: back button goes to dashboard in single-tariff to prevent redirect loop
In single-tariff mode, Subscriptions list auto-redirects to detail page.
Back button from detail now goes to / (dashboard) instead of /subscriptions,
breaking the redirect loop.
2026-03-25 18:40:50 +03:00
c0mrade
98e9cfadc6 fix: use declarative Navigate instead of navigate() in render + fix useEffect deps
Subscriptions.tsx: navigate() in render → <Navigate> component (React safe).
Subscription.tsx: same fix for multi-tariff redirect.
AdminUserDetail.tsx: useRef guard for one-time auto-select instead of
activeSubscriptionId in dependency array (prevented manual selection).
2026-03-25 18:11:30 +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
4de47cfa94 fix: multi-subscription frontend improvements
- Connection page: reads subscription_id from ?sub= query param
- Subscription detail: passes subscriptionId to /connection URL
- Dashboard: skip single-subscription and devices queries in multi-tariff
  mode (data comes from subscriptions-list instead)
2026-03-24 22:18:02 +03:00
c0mrade
324aaa1f33 redesign: inline delete confirmation on web — expands in place instead of modal/sheet 2026-03-24 19:11:48 +03:00
c0mrade
93fa435ff7 fix: constrain delete Sheet width on desktop — centered max-w-md with rounded corners 2026-03-24 18:57:01 +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
fe75fa45f0 feat: delete expired subscriptions with confirmation dialog
- API: deleteSubscription() calls DELETE /subscriptions/:id
- Subscription detail: "Удалить подписку" button for expired subs
  in multi-tariff mode
- Radix Dialog confirmation with warning text, cancel/confirm buttons
- Navigates to /subscriptions after successful deletion
2026-03-24 15:31:10 +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
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
820ba46cc4 feat: multi-subscription frontend support
- Add SubscriptionListItem and SubscriptionsListResponse types
- Add getSubscriptions/getSubscriptionById API methods
- Add optional subscriptionId parameter to all 30+ API methods
- Create /subscriptions page with subscription list cards
- Add /subscription/:subscriptionId route for per-subscription management
- Dashboard: show multi-tariff navigation block when user has 2+ subscriptions
- Subscription page: read subscriptionId from URL params, pass to all queries
- SubscriptionPurchase: accept subscriptionId from query params
- SubscriptionCardExpired: pass subscription.id to API calls
- Full backward compatibility: without subscriptionId, works as before
2026-03-23 18:57:05 +03:00
Fringg
58b1f96852 feat: show short device identifier (HWID) in device list 2026-03-22 05:06:14 +03:00
Fringg
2d89b5e342 fix: блокировка кнопки устройств на странице подписки
Аналогичная блокировка как на дашборде — disabled + haptic + предупреждение
2026-03-18 04:27:33 +03:00
Fringg
b4f9f332cf feat: add LIMITED subscription status support with traffic-exhausted UX
- Add is_limited to Subscription type
- Show yellow traffic-exhausted badge on Subscription and AdminUsers pages
- SubscriptionCardExpired: amber theme with warning icon and buy-traffic CTA for limited
- Dashboard: route limited subscriptions to SubscriptionCardExpired
- Subscription page: show additional options (buy traffic) for limited subs
- Use is_limited boolean instead of status string comparison
- Add trafficLimited translations for ru, en, zh, fa
- AdminUserDetail StatusBadge: add limited yellow styling
2026-03-12 18:37:22 +03:00