- 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
- 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
- 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
- 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
- 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)
Promise.all was used for tariffs, promo groups, campaigns, and partners.
If any one endpoint failed (e.g., partners not configured), all four
setCampaigns/setPartners/etc calls were skipped. Now each loads independently.
Add campaign and partner filter dropdowns for user filtering, implement
delete_user bulk action with delete_from_panel checkbox, fix ActionModal
to use createPortal with viewport-fixed positioning and safe area insets.
- Locale switch now regenerates all stable keys so editors remount fresh
- Fixes same-length locale switch (e.g., ru 3 items → en 3 items) reusing wrong editors
- Move keyCounter increment out of functional updater for StrictMode purity
- Remove void locale — prop is now used for key regeneration
- Use emitUpdate: false instead of fragile suppressUpdate ref
- Stable React keys for FAQ items — survive reorder/delete without editor desync
- Fix onDrop to check file type before preventDefault (non-media drops were swallowed)
- Full WYSIWYG editor for FAQ answers with toolbar (bold, italic, underline,
strike, headings, lists, blockquote, highlight, link, media upload)
- Image/video upload via paste, drag-drop, and file picker
- Upload progress indicator and drag overlay
- Syncs with parent state on reorder and locale switch
- Guard extraPages against slugs colliding with built-in tab IDs
- Handle tabReplacements API failure gracefully (proceed with built-in content)
- Fix overflow: use overflow-x-auto on inner divs instead of fighting bento-card
- Loyalty stat cards: responsive font size + truncate for 320px screens
- Clamp negative "remaining to next tier" amount to zero
- Fix toggleFaq stale closure with functional updater
- Add replaces_tab field to admin editor with conflict detection
- Show custom InfoPages as extra tabs on /info page
- Replace built-in tab content when replacement is configured
- Rich DOMPurify sanitizer for TipTap content (images, video, iframe)
- Fix query race: built-in queries wait for tab-replacements to load
- Horizontal scroll for tabs on mobile, 44px touch targets
- Responsive prose: scrollable tables, h-auto on img/video, light theme borders
- Loyalty tier names truncation, progress bar labels stack on mobile
Split the form population effect into two:
1. Form fields (slug, icon, active, titles, contents) — populated
once when pageData loads, never re-run on locale switch
2. Editor content — set once when editor instance is ready
Previously activeLocale was in the deps array, causing every tab
switch to overwrite all in-memory edits with original server data.
Selection (both user and subscription checkboxes) now persists when:
- Switching pages (prev/next)
- Changing filters (status, tariff, promo group, trial)
- Changing per-page limit
- Searching
- Refreshing data
Selection is only cleared after a successful bulk action execution
(modal close with result).
- Fix allVisibleSubscriptionIds: include ALL users' filtered subs
(was only users with >1 subs — single-sub users were excluded)
- Add per-page selector (25/50/100/200) in pagination bar
- Make limit a state variable instead of constant
- perPage i18n keys (ru + en)
- Send comma-separated tariff_ids to backend (was only single ID)
— server now filters with IN() operator, pagination correct
- Remove client-side tariff filtering (server handles it)
- Add "Только триал" checkbox filter: amber checkbox, filters users
with trial subscriptions client-side
- Remove "trial" from status dropdown (was not working via
subscription_status server filter)
- i18n: trialOnly key (ru + en)
Server only supports single tariff_id filter. When multiple tariffs
are selected, apply client-side filtering to hide users who have no
subscriptions matching any of the selected tariffs.
Add green checkbox in table header next to the user checkbox for
mass-selecting all visible subscriptions. Shows filled/partial/empty
states matching user checkbox pattern. Only visible in multi-tariff
mode. Wider select column (56px) to fit both checkboxes.
- Auto-expand ALL users with subscriptions (was only > 1), so users
with single subscription also show their sub-row for selection
- FloatingActionBar: render via createPortal to document.body to
ensure fixed positioning works correctly regardless of parent
overflow/transform contexts (was trapped inside scrollable area)
- Remove "Days" column from main table (each subscription shows own)
- Tariff column shows ALL subscription tariffs as comma-separated list
- Subscription sub-rows show device count with phone icon
- New bulk action "Изменить устройства" (set_devices) with input 1-50
- Update UserListItemSubscription type with device_limit field
- i18n: setDevices + deviceLimit keys (ru + en)
1. Tariff filter: replace single-select dropdown with MultiSelectDropdown
component — checkboxes for each tariff, "Select all" / "Deselect all"
controls, shows selected names or count in button label
2. Client-side subscription filtering: when tariffs selected in filter,
only show matching subscriptions in expanded sub-rows
3. "Select all subscriptions" button in FloatingActionBar — toggles all
visible subscription checkboxes and auto-expands relevant rows
4. Auto-expand: all user rows with multiple subscriptions are expanded
by default when data loads (no need to click chevron)
5. i18n: new keys for tariff multi-select and subscription select-all
- Detect multi-tariff mode when users have multiple subscriptions
- Expandable user rows with chevron: click to show subscription sub-rows
- Each subscription sub-row shows: tariff name, status badge, days
remaining (color-coded green/amber/red), traffic progress bar
- Independent subscription checkboxes for subscription-level actions
- FloatingActionBar shows dual counters: users (accent) + subscriptions
(green), actions grouped by target type with disabled state
- Subscription-level actions send subscription_ids, user-level send
user_ids to the backend
- Backward compatible: single-tariff mode unchanged (no chevrons,
no sub-rows, user-only selection)
- i18n: subscriptionsSelected, usersSelected, expand/collapse,
daysUnit, trafficOf, trafficGbUnit (ru + en)
- Update UserListItem type to include tariff_id, tariff_name,
traffic_used_gb, traffic_limit_gb, device_limit, days_remaining
from the backend (now returned in user list API)
- Fix tariff column: was showing subscription_status, now shows
actual tariff_name from the API response
The live ProgressView log used entry.error (never sent by backend)
instead of entry.message, falling back to a generic i18n string.
Now checks entry.message first to display the actual error from
the backend (e.g. "No subscription found").
- Real-time progress: SSE streaming with animated progress bar,
live success/error counters, scrollable log of last 10 results
with auto-scroll, percentage display
- New action "Выдать подписку": tariff selector + days input,
warning about users with existing subscriptions being skipped
- Error details: collapsible section with per-user error list,
colored summary line (green successes / red errors)
- Fix: modal cannot be closed during loading (backdrop + Escape)
- Fix: row checkbox aria-labels now user-specific
- i18n: full progress/grant/error translations (ru + en)
- Fix selectedUserIds: was mapping row keys as array indices instead
of using them directly as user IDs (getRowId returns String(user.id))
- Add useEffect cleanup for search debounce timer on unmount
Full-featured admin page for mass operations on users:
- TanStack Table with checkbox selection (select all / individual)
- Server-side pagination, search, and filters (status, tariff, promo group)
- Columns: user, subscription status, tariff, balance, days left, promo group
- Floating glass-effect action bar with 7 bulk operations:
extend subscription, deactivate, activate, change tariff,
add traffic, add balance, assign promo group
- Action modal dialogs with input fields, loading state, result summary
- Responsive design for desktop, mobile, and Telegram Mini App
- Full i18n support (ru, en)
- Dark theme with accent highlights and status color badges