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)