- Reset isKeyboardOpen on route change so bottom nav always restores
- Disable autoFocus on payment input in Telegram Mini App
- Skip auto-focus on Profile email inputs in Telegram
When a new build changes chunk hashes, users with cached index.html
get "Failed to fetch dynamically imported module" errors. Now:
- lazyWithRetry() wraps all lazy imports with auto-reload on failure
- ErrorBoundary catches chunk errors and auto-reloads once
- 30s guard in sessionStorage prevents reload loops
- Add tariff_id/tariff_name to PromoCode types
- AdminPromocodeCreate: tariff dropdown for trial_subscription type
- Shows default trial tariff or warns if none configured
- Balance page: handle select_subscription response — show subscription
picker when promo code adds days and user has multiple tariffs
- API: activatePromocode now accepts optional subscriptionId
- AdminPromocodeCreate: show trial tariff info when creating
trial_subscription promo code, warn if no trial tariff configured
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.
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).
When user has 2+ eligible subscriptions and chooses subscription_days
payment, a visible warning hint now appears explaining they need to
select a subscription before spinning.
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.
Level 1 (subscription list):
- Cards with tariff name, status badge, traffic, date, devices
- Click to drill into subscription detail
- "Создать подписку" with filtered purchased tariffs
Level 2 (subscription detail):
- Back button to return to list
- All actions scoped to selected subscription
- No "Сменить тариф" in multi-subscription mode
- Panel info, devices, traffic stats per-subscription
Single subscription: keeps current behavior (direct detail view)
- API: spin() accepts subscriptionId, WheelConfig has eligible_subscriptions
- Auto-select subscription when only one eligible
- Show subscription picker when multiple eligible (days payment)
- Disable spin button until subscription selected (when >1 eligible)
- Daily tariffs excluded from eligible list
- 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
- Remove max-w-lg constraint to match dashboard width
- Grid layout: 2 columns on desktop, 1 on mobile
- Move "Новый тариф" button to header row as accent-styled button
- Larger empty state with more breathing room
All backend endpoints accept subscription_id via Query(...) parameter,
but frontend was sending it in the request body via bodyWithSubId helper.
This caused multi-tariff operations (pause, autopay, renew, traffic,
devices, servers, tariff switch) to silently fail — backend couldn't
find subscription_id in query params and fell back to first subscription.
Changed bodyWithSubId to return [body, config] tuple with subscription_id
in params, spread into axios calls as separate arguments.
- 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
- Daily tariffs renew automatically — no manual renewal CTA needed
- Show "Продление подписки" hint in multi-tariff instead of "Продление и смена тарифа"
- RenewSubscription: display actual missing amount instead of {{missing}} placeholder
- SubscriptionPurchase: suppress "switch tariff" title in multi-tariff mode
(only renewal exists, no tariff switching)
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
This package was not imported anywhere in the codebase but pulled in
@excalidraw/excalidraw and old @radix-ui packages that caused peer
dependency warnings on every npm install (React 19 vs React 16-18).
Tiptap is used directly via @tiptap/* packages.
- 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
- 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
- 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
- Add onDelete prop to ColoredItemCombobox with X button per item
- Wire up deleteCategory/deleteTag API calls in AdminNewsCreate
- Auto-clear selection if the deleted item was selected
- Spinner on delete, haptic feedback, i18n aria-label
- Fix tag using category_color instead of its own color when editing
- Add FormData auto-detection interceptor in client.ts (root cause of 422)
- Remove redundant Content-Type overrides from all 5 upload endpoints
- Fix falsy id check (id: 0 treated as null) with strict comparison
- Await invalidateQueries to prevent stale dropdown data