В мульти-тарифе кнопка «+ Купить ещё подписку» висела всегда, когда есть
хоть какая-то подписка — даже если это только триал или всё истёкшее.
Теперь она показывается только при наличии НАСТОЯЩЕЙ платной активной
подписки (hasActivePaid: не триал + status active/limited).
А «Посмотреть тарифы и купить подписку» из тусклой (пунктир + opacity-60,
спрятана в триал-блоке) стала ЯВНОЙ primary-кнопкой (solid accent) и
показывается, когда платной активной подписки ещё нет (триал/истёкшие/
новый юзер) — мы продаём подписки, путь к покупке должен быть очевиден.
Затронуты Dashboard и Subscriptions. Добавлен ключ subscriptions.browsePlans
(раньше рендерился только inline-фолбэк) в 4 локали.
The My Subscriptions list is a 2-col grid on sm+, so a single subscription (e.g. one tariff in multi-tariff mode) left an empty cell. Stretch the last card to span both columns when it's the lone one in its row — same orphan-fill used for the news grid and admin stat cards.
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.
Новый пользователь с доступным триалом видел только TrialOfferCard (CTA
активации), без пути в витрину тарифов — приходилось сперва активировать
пробную. Добавлена вторичная кнопка «Посмотреть тарифы и купить подписку»
→ /subscription/purchase под карточкой триала на обеих страницах, где она
рендерится: Subscriptions и Dashboard (вход по умолчанию).
Telegram-баг #605056/#605063.
Refines 3c2f650 — that commit broke the loop by suppressing the single-tariff
redirect on idx=0, but left the BackButton visible. The user still saw Back
on the detail page and needed two taps (Back → Close) to exit. Серёжа's
report was specifically that Back should not appear at all when you land on
your only subscription via a bot button.
Move the decision up to TelegramBackButton:
- Subscribe to the existing ['subscriptions-list'] query (deduped by
React Query, so no extra fetch).
- On /subscriptions/:id, when multi_tariff_enabled is false and there is
no in-app history (idx=0 deep-link entry), hide the BackButton. Telegram
then surfaces its native Close (X) in the header.
- Multi-tariff users still see BackButton on /subscriptions/:id deep-links
because their /subscriptions list is meaningful (they have many subs).
The 2bcba3b fallback-to-parent behaviour remains intact for unrelated
deep-links like /balance/top-up, /admin/*, etc.
Revert the defensive guard in Subscriptions.tsx — the loop can no longer be
triggered because no BackButton is shown to trigger it.
Behaviour summary:
flow BackButton exit cost
-------------------------------------- ---------- ---------
deep-link → /subscriptions/:id (1T) hidden Close, 1 tap
/ → /subscriptions → /subscriptions/:id shown Back → /, 1 tap
deep-link → /balance/top-up shown Back → /balance ✓
deep-link → /subscriptions/:id (multi) shown Back → /subscriptions list
Reported by Серёжа, bugs topic #599679.
User flow that hung:
1. Bot deep-link opens MiniApp directly on /subscriptions/:id (idx=0).
2. Telegram shows BackButton.
3. Tap → TelegramBackButton handler sees no in-app history and falls
back to getFallbackParentPath('/subscriptions/:id') = '/subscriptions'.
4. Subscriptions page renders, sees single-tariff + 1 subscription,
<Navigate replace> redirects right back to /subscriptions/:id.
5. BackButton looks dead — taps do nothing visible.
Fix: only redirect when there is in-app history. When idx=0 (deep-link or
just-arrived-via-back), render the list page instead. The user then sees
their single subscription as a card and can tap into it, and the next
BackButton tap exits the MiniApp as expected.
Reported by Серёжа in bugs topic #599679.
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.
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).
- 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
- 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