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
When a gift purchase results in pending_activation (recipient has active
subscription), the buyer was shown an Activate button and could replace
the recipient's subscription without consent.
- Add GiftPendingActivationState component showing "Gift sent" message
- Use ?activate=1 URL hint from recipient email to distinguish viewer
- Buyer sees success message, recipient sees activate button
- Add giftPendingActivationDesc translations (ru, en, zh, fa)
- Add ExternalSquadInfo type and getAvailableExternalSquads API method
- Add external_squad_uuid field to TariffDetail, TariffCreateRequest, TariffUpdateRequest
- Add radio-button external squad picker in Servers tab of tariff create/edit
- Add translations for external squad UI in all 4 locales (en, ru, zh, fa)
- Badges (active/inactive, gift) now wrap on narrow screens
- Action buttons move to a separate row on mobile (hidden/shown via sm:)
- Delete button pushed to the right on mobile for safety
- Reduced padding and gaps on small screens
- Desktop layout unchanged
- Add i18n period labels (ru/en/zh/fa) instead of backend English-only labels
- Period tabs wrap on mobile instead of horizontal scroll
- Prevent horizontal overflow shifting the layout
- Add loading state on button click (not just after callback)
- Add onerror handler for script load failure
- Disable button until OIDC script is loaded
- Replace useCallback with ref pattern to prevent stale closures and
unnecessary Telegram.Login.init() re-calls
- Make isOIDC a proper boolean with Boolean() wrapper
- Clear previous error on successful login attempt
- Remove hardcoded fallback string from t() call
- Add oidc_enabled/oidc_client_id to TelegramWidgetConfig interface and fallback
- Add loginTelegramOIDC API method for id_token auth
- Add loginWithTelegramOIDC to auth store
- Rewrite TelegramLoginButton: OIDC popup when enabled, legacy widget otherwise
- Extend Window.Telegram type with Login SDK in vite-env.d.ts
- Upgrade telegram-widget.js from v22 to v23 (TelegramLoginButton + ConnectedAccounts)
- TelegramLoginButton fetches widget config from API instead of hardcoded values
- Add TelegramWidgetConfig type and API method with fallback to env defaults
- Remove botUsername prop — now sourced from backend API
- Add useEffect cleanup to prevent orphaned iframes on unmount
- Add TELEGRAM_WIDGET category to admin interface settings menu
- Add i18n translations for widget settings (ru, en, zh, fa)
- Auto-select sub-option even when only 1 exists (backend needs suffix)
- Remove return_url from public LandingPaymentMethod (backend doesn't send it)
- Add else branch in auto-select for defensive null reset
Show sub-options (e.g. СБП, Карта) as selectable buttons when a
payment method has multiple enabled variants. Send suffixed
payment_method ID to backend for correct provider routing.
Split LandingPaymentMethod into public and admin types.
Add interactive sub-option toggles (Card/SBP/Crypto) to the landing editor
payment method configuration. Extract SortableFeatureItem and
SortableSelectedMethodCard into dedicated component files to reduce
AdminLandingEditor from ~1160 to ~860 lines.
- CabinetCredentialsState with copyable email/password fields
- Auto-login via JWT token with checkAdminStatus
- /auto-login route with ErrorBoundary and no-referrer meta
- Narrowed contact_type to 'email' | 'telegram' | null
- Disabled button styling for PendingActivation go-to-cabinet
- i18n: added landing.copy key to all 4 locales
- Add PendingActivationState component with activate button
- Add double-click ref guard on activation handler
- Add activatePurchase API function
- Update PurchaseStatus type with new fields
- Show gift message and recipient contact in success state
- Add pending activation i18n translations (ru, en, zh, fa)
- Improve AdminLandingEditor with allowed_periods support
- Update LandingListItem.title type to LocaleDict
- Add resolveLocaleDisplay() helper for extracting display string
- Use resolveLocaleDisplay() in landing list and delete confirm
- Use data.id instead of positional index for tariffPeriodsMap
- Clean up allowedPeriods when tariff is unchecked
- Filter out empty period arrays and orphaned tariff keys on submit
Мультиязычность:
- LanguageSwitcher на публичной странице покупки
- Вкладки локалей в админ-редакторе (ru/en/zh/fa)
- LocaleTabs и LocalizedInput компоненты
- Типы: LocaleDict, AdminLandingFeature, toLocaleDict хелпер
- ?lang= параметр при запросе конфига лендинга
Исправления по ревью:
- contentIndicators включает все локализуемые поля
- LocaleTabs вынесен над секциями аккордеона
- text-left → text-start для RTL
- nonEmptyDict: защита от undefined
- featureIds/methodIds обёрнуты в useMemo
- toggleSection/updateFeature* обёрнуты в useCallback
- formatPrice вынесен в shared utils/format.ts
- RTL через LOCALE_META.rtl вместо хардкода
- Переводы zh/fa для валидации и landing
- Variable shadowing: t → tariff
- Способы оплаты: загрузка из системного API вместо ручного ввода, toggleable чекбоксы
- Периоды тарифов: загрузка из period_prices через useQueries вместо произвольного ввода
- Публичная страница: глобальный список периодов, скрытие тарифов без цены для выбранного периода
- Локализация: добавлены ключи для 4 языков (ru/en/zh/fa)
- QuickPurchase: публичная страница покупки с выбором тарифа/периода/оплаты
- PurchaseSuccess: статус покупки с поллингом и копированием ссылки
- AdminLandings: управление лендингами (создание, сортировка, удаление)
- AdminLandingEditor: полнофункциональный редактор лендинга
- API-клиент landings.ts для всех эндпоинтов
- Роутинг /buy/:slug и /buy/success/:token
- Локализация ru/en/zh/fa для всех текстов лендинга
- Санитизация HTML/CSS, rate limit защита, DOMPurify
- Update route guard and menu item to use sales_stats:read
- Add sales_stats:read to marketer preset
- Add i18n translations for sales_stats section (ru, en, fa, zh)
- Remove LinkOAuthCallback.tsx (dead code) and its route from App.tsx
- Move LINK_OAUTH_* constants to OAuthCallback.tsx
- Replace local ServerLinkResult with LinkCallbackResponse from types
- Move merge navigate() from render body to useEffect
- Track errorMode to show correct CTA (Return to Telegram vs Back to Login)
- Remove non-null assertions with proper narrowing
- Extract getErrorDetail helper for error casting
- Add smart polling success detection (stop + toast when linked count increases)
- Add 5s polling for 90s after opening external browser link (Mini App)
- Show info toast "Continue in browser" when opening external OAuth
- Fix premature sessionStorage cleanup: peek-then-clear pattern
- Use useIsTelegram() hook instead of direct isInTelegramWebApp()
- Add continueInBrowser i18n key (ru, en, zh, fa)
- Rewrite OAuthCallback.tsx to handle 3 modes: login, link-browser,
and link-server (external browser without JWT)
- Add linkServerComplete API method (no JWT, auth via state token)
- Update ConnectedAccounts to use platform.openLink() in Mini App
- Add server-complete endpoint to AUTH_ENDPOINTS (skip Bearer token)
- Enable refetchOnWindowFocus for linked-providers query
- Add returnToTelegram/openTelegram i18n keys (ru, en, zh, fa)