Add CABINET_GIFT_ENABLED toggle in Admin Settings > Branding > Interface Options,
alongside existing fullscreen and email auth toggles. Includes updateGiftEnabled
API method and i18n keys for all 4 locales (ru/en/zh/fa).
- Handle expired status in GiftResult (stop polling + show FailedState)
- Add PollErrorState for balance mode poll errors (softer UX)
- Remove non-null assertions in handleSubmit (explicit narrowing)
- Wrap gift routes in ErrorBoundary
- Add pollErrorTitle/pollErrorDesc i18n keys to all 4 locales
TelegramLinkWidget used legacy widget only, ignoring OIDC config.
Now queries widgetConfig from backend, supports OIDC popup flow with
id_token, and falls back to bot_username from server config.
- Move PAID_STATUSES/FAILED_STATUSES from Balance.tsx and TopUpResult.tsx
to src/utils/paymentStatus.ts
- Eliminates code duplication between the two pages
When payment providers redirect to external browser, sessionStorage is
unavailable. TopUpResult now reads method from URL query params and
polls via /latest endpoint as fallback.
Add overpaid (PAL24) to PAID_STATUSES, system_fail and refund_paid
(Heleket) to FAILED_STATUSES. Harmonize Balance.tsx redirect detection
with the same full status sets and case-insensitive matching.
Replace toast-based payment return with animated result page showing
pending/success/failed/timeout states. Extract shared Spinner,
AnimatedCheckmark and AnimatedCrossmark components. Add sessionStorage
persistence with validation and TTL for cross-redirect payment data.
Unify ProtectedRoute with withLayout prop, add aria-live accessibility.
Added staleTime: 0 and refetchOnMount: 'always' to purchase-options
queries in SubscriptionPurchase and Subscription pages to prevent
stale balance_kopeks from causing incorrect insufficient funds display.
- Fix guard: available === false alone now properly gates the unavailability
message (was incorrectly AND-ed with !max_device_limit)
- Remove dead code block that was unreachable after guard fix
- Add device-price cache invalidation after successful purchase
- Show SubscriptionCardExpired for disabled daily subscriptions
- Use togglePause() API for resume instead of renewSubscription()
- Add "Suspended" status badge and balance query invalidation
- Add dashboard.suspended and subscription.pause.suspended translations (ru, en, zh, fa)
- WebSocketNotifications: invalidate purchase-options on all 10 balance-affecting events
- Dashboard: invalidate purchase-options after trial activation
- Balance: invalidate purchase-options after payment return and promocode activation
- PromoOffersSection: invalidate purchase-options and balance after promo claim
- Wrap /connection/qr route in ProtectedRoute with Layout
- Remove fullscreen overlay and custom close button
- Add AdminBackButton with replace prop to avoid history cycles
- Use replace navigation from Connection to QR page
- Add replace prop support to AdminBackButton component
- Add recipient_in_bot and bot_link fields to PurchaseStatus API type
- Show bot link instruction when gift recipient is not in bot
- Show "gift sent via Telegram" message when recipient is in bot
- Fix duplicate text rendering in GiftPendingActivationState
- Handle recipientInBot=null edge case safely
- Add gift notification translations for ru, en, zh, fa locales
Telegram deprecated the data-auth-url redirect flow, which returns a
blank page with "deprecated" text. Switch both TelegramLinkWidget
(account linking) and TelegramLoginButton (legacy login) to use
data-onauth callback approach that works client-side without redirects.
/cabinet/auth/email/register requires authentication (links email to
existing Telegram account) but was in the AUTH_ENDPOINTS skip-list,
causing the request interceptor to omit the Bearer token → 401.
Change the skip-list entry to the specific /standalone path which
genuinely doesn't need a Bearer token.
The root div had an opaque bg-dark-950 that covered the portal-rendered
animated background at zIndex:-2. Remove the opaque background when
background_config is present, matching the AppShell pattern.
The admin.landings namespace had two 'purchases' keys at the same level:
a string for purchase count label and an object for purchases section.
JSON last-key-wins caused the string to be overwritten by the object,
producing "returned an object instead of string" error on landings list.
Extract BackgroundConfigEditor as reusable controlled component from
BackgroundEditor. Add background settings section to landing editor.
Render per-landing backgrounds on public purchase pages.
- Extract BackgroundConfigEditor (value/onChange) from BackgroundEditor
- Refactor BackgroundEditor to thin wrapper with API persistence
- Add StaticBackgroundRenderer for prop-based config (public pages)
- Add background_config to landing API types and editor form
- Render animated background on QuickPurchase page
Add card-based purchase list below charts with:
- Status filter dropdown (all/pending/paid/delivered/etc)
- Paginated card list with contact, gift recipient, tariff, price
- Status badges, responsive mobile/desktop layout
- Translations for ru/en
The campaigns page was fetching only the first 50 items with no way
to load more. Switched from useQuery to useInfiniteQuery with a
"Load more" button so all campaigns are accessible.
Add discount configuration in admin editor and countdown timer
display on public purchase page.
- Add discount section to AdminLandingEditor with toggle, percent
slider, datetime pickers, per-tariff overrides, live preview
- Add DiscountBanner with countdown timer to QuickPurchase page
using AnimatePresence exit animation and proper interval cleanup
- Add strikethrough prices and discount badges to tariff cards,
summary card and pay button with null/equality guards
- Add discount translations for ru and en locales
- Fix delete/toggle API return types to match backend contracts
- Fix isoToDatetimeLocal to preserve seconds on re-edit
- Fix preview rounding to match backend integer division
- Replace user ID text input with checkbox-style user chips extracted from log entries
- Add sales_stats and landings to resource type filter
- Fix export not respecting status filter
- Remove dead auto-refresh interval code
- Add aria-pressed and focus-visible styles for accessibility