Save mutation was showing generic error instead of backend validation
detail, making it impossible to diagnose 422 errors. Also added tg://
to frontend URL validation for Telegram deep link support.
Frontend now reads campaign slug from localStorage and includes it
in the registration API call, ensuring campaign attribution works
even if email verification happens in a different browser.
- Status badge (e.g. 'ПРИОСТАНОВЛЕНА (НЕДОСТАТОЧНО СРЕДСТВ)') now
constrained with max-w-[55%] to prevent overlapping tariff name
- Password too short error shown inline under password field during
registration instead of at the top of the page (invisible on mobile)
@telegram-apps/sdk v3 depends on valibot which uses Object.hasOwn.
On Safari < 15.4 and old Android WebViews this method is missing,
causing LaunchParamsRetrieveError and broken initData auth.
Ref: https://github.com/Telegram-Mini-Apps/tma.js/issues/683
- Added key="email-link-form" on motion.div inside AnimatePresence
for proper exit animations
- Added htmlFor/id pairs on all three label+input groups for
screen reader accessibility
- Replaced hardcoded "your@email.com" placeholder with neutral format
- Removed hardcoded English fallback from t('profile.invalidEmail')
Users couldn't find the email linking form buried in the Profile page.
Now the Email row in Connected Accounts shows a "Привязать" button
(consistent with Google/Yandex/Discord) that expands an inline form
with email, password, and confirm password fields.
- Added email to isLinkableProvider check
- Inline AnimatePresence form expands under Email card (no popups)
- Removed duplicate registration form from Profile page
- Profile page now shows a redirect button to Connected Accounts
for users without email (email change/verify stays in Profile)
isActive used exact pathname match (===), so navigating to
/subscriptions/:id or /subscriptions/:id/renew would not highlight
the Subscription tab. Changed to startsWith for non-root paths
in both MobileBottomNav and DesktopSidebar.
Separated input state (searchInput/emailSearchInput) from committed
search state (search/emailSearch) to prevent every keystroke from
triggering API calls with stale pagination offset.
- Encode underscores as %5F in share URLs (bot link + cabinet link)
to prevent Telegram auto-link detection from stripping trailing _
- Strip GIFT-/GIFT_ prefix from URL code param before pre-filling input
- Use ?? instead of || for prefix stripping to preserve empty strings
- Add language field to balance topup and landing purchase requests
- Cap news article featured image height with max-h-96
Cherry-picked from PR #362 (without favicon change — files don't exist)
- Add-referral search: exclude users already in referrals list
- Both search debounces: clear stale results immediately when query
changes to prevent previous results flashing during debounce
- New search UI in referrals list section to find and bind users
as this user's referrals (calls assign-referrer on target user)
- Debounced search with cancelled flag and loading state
- Click-outside to close dropdown
- i18n: addReferral, referralAdded keys in all 4 locales
- Commission display: use conditional instead of nullish coalescing
to avoid rendering "default%" (now shows "default" without %)
- loadReferrals: add || [] guard on data.users to prevent crash
- Search debounce: add cancelled flag to prevent stale responses
overwriting fresher results
- Search dropdown: add loading state to prevent "No users found"
flash during debounce wait
- New Рефералы tab with three sections:
- Referred By: shows who referred this user, assign/remove referrer
- Stats grid: total referrals, earnings, commission, referral code
- Referrals list: all referred users with navigate and remove buttons
- User search with debounce for assigning referrer
- Shows warning when selected user already has a referrer
- API methods: assignReferrer, removeReferrer, removeReferral
- i18n translations for all 4 locales (en, ru, zh, fa)
- SettingsTab: use searchQuery.trim() instead of searchQuery to prevent
whitespace-only input from triggering empty search results view
- AdminSettings: fallback to branding when activeSection is unknown
instead of rendering blank content area
- SettingsSearchMobile: change sm:hidden to lg:hidden to match mobile
layout breakpoint (search was invisible on 640-1023px tablets)
- SettingsTableRow: sync isLongValue suffixes with SettingInput
(_list, _json, _periods, _discounts were missing)
- SettingsTableRow: read-only bools now show enabled/disabled labels
instead of raw "true"/"false" strings
- Use activeSection instead of activeTreeInfo as useMemo dependency
(activeTreeInfo is derived from activeSection, new object each render)
- Add category label matching to sidebar autocomplete search filter
to match global search behavior
- Replace flat sidebar with collapsible tree groups (7 groups, 52 sub-items)
- Map all 61 backend category keys to sidebar navigation
- Add QuickToggles panel for boolean settings
- Replace card-style SettingRow with compact SettingsTableRow
- Add breadcrumb navigation for tree sub-items
- Add source badges (modified/DB/ENV) on settings
- Implement two-level mobile navigation with group chips
- Add i18n translations for all new keys (en, ru, zh, fa)
- Add NodeRealtimeStats and InboundTraffic TypeScript interfaces
- Type getNodesRealtime return as NodeRealtimeStats[]
- Add new Traffic tab to AdminRemnawave page showing per-node
inbound/outbound traffic sorted by total bytes
- Display download/upload/total per inbound tag for each node
- ServerInfo: remove cpu_physical_cores, memory_available
- NodeInfo: replace cpu_count/cpu_model/total_ram with versions/system,
xray_uptime changed to number, users_online now required
- NodeStatus: same field updates, remove dead uptime field
- Extract formatUptime to shared utils/format.ts
- Update AdminRemnawave and AdminDashboard components
The CosmosBackground orbs were overlapping the app's existing theme
background, creating a double-layer effect. Removed the component
entirely so only the theme background is visible.
- Rewrite AdminPanel with cosmic floating orbs background using theme colors
- Add stats bar: uptime, bot version, cabinet version, trial/paid counts from API
- Glass morphism cards with 3D tilt effect (rAF-throttled) and entry animations
- Live search with Cmd+K shortcut, highlight matches, permission-aware filtering
- Register Tailwind `light:` variant for proper light theme support
- Mobile optimizations: reduced orbs (3), lower blur (80px), compact 2-col stats
- Telegram MiniApp safe area support via useTelegramSDK
- Full accessibility: aria-hidden on decorative SVGs, aria-labels, role="status"
- React.memo on StatsBar, GlassCard, CosmosBackground for render performance
- Memo-stable section refs via Set<id> pattern to preserve GlassCard memo during search
- i18n keys added to all 4 locales (ru, en, zh, fa)
- Add subscription dropdown to sync tab when user has 2+ subscriptions
- Pass subscription_id to getSyncStatus, syncFromPanel, syncToPanel
- loadSyncStatus re-fetches when selected subscription changes
- Show tariff name in UUID info section from API response
- Add selectSubscription i18n keys to all 4 locales
- device_price_kopeks: change > 0 to >= 0 so 0 (free) is sent instead of undefined
- traffic_topup_packages: change > 0 to >= 0 so free traffic packages can be added
- 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).