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
1. Rename "Управление серверами" → "Управление сквадами" across all
4 locales (ru, en, zh, fa) — title, subtitle, nav, subscription
options. The page manages RemnaWave squads, not individual servers.
2. Swap toggle button semantics — the button now shows the ACTION
that will happen on click, not the current state:
- Active squad: red X icon (click to deactivate)
- Inactive squad: green checkmark (click to activate)
3. Gift/trial button: amber/orange when functional (clearly clickable),
gray when disabled. Previously always gray, making it look inactive
even when it could be clicked.
formatPrice() was hardcoded to ru-RU locale and RUB currency.
Now it reads the current language from i18next and maps it to
the appropriate currency (ru→RUB, en→USD, zh→CNY, fa→IRR).
All consumers (QuickPurchase landing, GiftSubscription, admin)
automatically get locale-aware currency formatting without
any component-level changes.
- SubscriptionListCard: show traffic progress bar for limited subs
in multi-tariff list (was hidden because isActive excluded limited)
- SubscriptionListCard: amber border/background for limited status
cards (was using default neutral style, inconsistent with rest of UI)
- Subscription page: hide delete button for limited subs in
multi-tariff mode (limited subs still have valid days remaining)
- SubscriptionPurchase: allow tariff switch for limited subs
(was blocked by is_active-only gate in canSwitch formula)
- AdminUserDetail: include limited status in "already purchased"
tariff filter to prevent duplicate subscription creation
- Dashboard card: change date label from "Истекла" to "Активна до"
for limited subscriptions (traffic exhausted but time remaining)
- Dashboard button: change text from "Трафик исчерпан" (dead-end) to
"Докупить трафик" (actionable) for limited status
- Subscription page: CountdownTimer shows remaining time for limited
subs instead of "Истекла" (was passing is_active=false)
- PurchaseCTAButton: don't treat limited as expired — limited subs
still have time, should show extend CTA not "buy new subscription"
The gift page filtered tariffs by selected period, hiding tariffs
whose single period didn't match. When each tariff has a unique
period (30d, 60d, 180d, 360d), only the first tariff was visible.
- Remove period-first filtering (visibleTariffs/allPeriods)
- Show all tariffs directly from config.tariffs
- Auto-select the chosen tariff's period when switching tariffs
- Show tariff cards when more than 1 tariff exists
- Payment method label and icon (violet gradient, AP)
- Admin settings tree entry for AURAPAY category
- Locale strings and descriptions for ru, en, zh, fa
- Payment method label and icon (green gradient, RP)
- Admin settings tree entry for ROLLYPAY category
- Locale strings and descriptions for ru, en, zh, fa
* Make SBP default and first top-up option (#389)
* fix(connection): happ cryptolink flow + ui fixes (#385)
* fix(connection): respect happ_cryptolink mode
- read connect_mode from connection-link endpoint
- auto-redirect to happ cryptolink flow when mode is HAPP_CRYPTOLINK
- keep installation guide behavior for other modes
* docs(readme): fix source build step 2
- use compose service name for build/create commands
- copy static files from created compose container
- remove compose container via docker compose rm
* fix(connection): handle mode and ws path
- treat any non-guide connect mode as direct happ redirect
- wait for connection-link response before rendering installation guide
- build websocket URL from VITE_API_URL (supports /api and absolute URLs)
* docs(readme): fix docker cp static path
- copy /usr/share/nginx/html contents via html/.
- prevent nested /srv/cabinet/html deployment and 404 on root
* fix(connection): keep guide, use happ links
- restore guide page behavior for /connection (no auto redirect)
- use happ cryptolink URL in /connection/qr when happ crypt mode is active
- replace subscription page connection URL with happ cryptolink from connection-link API
* fix(connection): avoid wrong redirect flow
- force happ scheme deeplink in HAPP_CRYPTOLINK mode for guide connect buttons
- use cabinet redirect page only inside Telegram Mini App
- open deeplink directly in regular browsers
* fix(connection): enforce happ cryptolink
- prioritize backend crypt deeplink fields in HAPP_CRYPTOLINK mode
- fallback to local crypt4/crypt3 generation from subscription URL when backend returns plain happ://sub...
- apply same resolution for guide open action, qr page source and subscription page link display/copy
* fix(subscription): truncate long connect link
- render connection URL in a single line with ellipsis on /subscriptions
- preserve full URL in tooltip for easier manual copy
- keep copy button behavior unchanged
* fix(connection): build cryptolink from happ sub
- allow cryptolink fallback generation from happ://sub... URLs in addition to http(s)
- prevent plain happ://sub links from leaking into UI in HAPP_CRYPTOLINK mode
* fix: allow saving 0% period discount in promo groups
Changed condition from percent > 0 to percent >= 0 so that
admins can explicitly set 0% discount for a period. Previously
0% entries were silently dropped and not sent to the backend.
* fix: always send period_discounts in promo group updates
When all period discounts were removed, the frontend sent undefined
(field absent from JSON), so the backend never cleared them.
Now always sends the field - empty object {} to clear, or populated
object to update.
---------
Co-authored-by: zavul0nn <34007368+zavul0nn@users.noreply.github.com>
Co-authored-by: Dxnil <62987903+D4nilKO@users.noreply.github.com>
* Make SBP default and first top-up option (#389)
* fix(connection): happ cryptolink flow + ui fixes (#385)
* fix(connection): respect happ_cryptolink mode
- read connect_mode from connection-link endpoint
- auto-redirect to happ cryptolink flow when mode is HAPP_CRYPTOLINK
- keep installation guide behavior for other modes
* docs(readme): fix source build step 2
- use compose service name for build/create commands
- copy static files from created compose container
- remove compose container via docker compose rm
* fix(connection): handle mode and ws path
- treat any non-guide connect mode as direct happ redirect
- wait for connection-link response before rendering installation guide
- build websocket URL from VITE_API_URL (supports /api and absolute URLs)
* docs(readme): fix docker cp static path
- copy /usr/share/nginx/html contents via html/.
- prevent nested /srv/cabinet/html deployment and 404 on root
* fix(connection): keep guide, use happ links
- restore guide page behavior for /connection (no auto redirect)
- use happ cryptolink URL in /connection/qr when happ crypt mode is active
- replace subscription page connection URL with happ cryptolink from connection-link API
* fix(connection): avoid wrong redirect flow
- force happ scheme deeplink in HAPP_CRYPTOLINK mode for guide connect buttons
- use cabinet redirect page only inside Telegram Mini App
- open deeplink directly in regular browsers
* fix(connection): enforce happ cryptolink
- prioritize backend crypt deeplink fields in HAPP_CRYPTOLINK mode
- fallback to local crypt4/crypt3 generation from subscription URL when backend returns plain happ://sub...
- apply same resolution for guide open action, qr page source and subscription page link display/copy
* fix(subscription): truncate long connect link
- render connection URL in a single line with ellipsis on /subscriptions
- preserve full URL in tooltip for easier manual copy
- keep copy button behavior unchanged
* fix(connection): build cryptolink from happ sub
- allow cryptolink fallback generation from happ://sub... URLs in addition to http(s)
- prevent plain happ://sub links from leaking into UI in HAPP_CRYPTOLINK mode
* fix: allow saving 0% period discount in promo groups
Changed condition from percent > 0 to percent >= 0 so that
admins can explicitly set 0% discount for a period. Previously
0% entries were silently dropped and not sent to the backend.
* fix: always send period_discounts in promo group updates
When all period discounts were removed, the frontend sent undefined
(field absent from JSON), so the backend never cleared them.
Now always sends the field - empty object {} to clear, or populated
object to update.
---------
Co-authored-by: zavul0nn <34007368+zavul0nn@users.noreply.github.com>
Co-authored-by: Dxnil <62987903+D4nilKO@users.noreply.github.com>
When all period discounts were removed, the frontend sent undefined
(field absent from JSON), so the backend never cleared them.
Now always sends the field - empty object {} to clear, or populated
object to update.
Changed condition from percent > 0 to percent >= 0 so that
admins can explicitly set 0% discount for a period. Previously
0% entries were silently dropped and not sent to the backend.
* fix(connection): respect happ_cryptolink mode
- read connect_mode from connection-link endpoint
- auto-redirect to happ cryptolink flow when mode is HAPP_CRYPTOLINK
- keep installation guide behavior for other modes
* docs(readme): fix source build step 2
- use compose service name for build/create commands
- copy static files from created compose container
- remove compose container via docker compose rm
* fix(connection): handle mode and ws path
- treat any non-guide connect mode as direct happ redirect
- wait for connection-link response before rendering installation guide
- build websocket URL from VITE_API_URL (supports /api and absolute URLs)
* docs(readme): fix docker cp static path
- copy /usr/share/nginx/html contents via html/.
- prevent nested /srv/cabinet/html deployment and 404 on root
* fix(connection): keep guide, use happ links
- restore guide page behavior for /connection (no auto redirect)
- use happ cryptolink URL in /connection/qr when happ crypt mode is active
- replace subscription page connection URL with happ cryptolink from connection-link API
* fix(connection): avoid wrong redirect flow
- force happ scheme deeplink in HAPP_CRYPTOLINK mode for guide connect buttons
- use cabinet redirect page only inside Telegram Mini App
- open deeplink directly in regular browsers
* fix(connection): enforce happ cryptolink
- prioritize backend crypt deeplink fields in HAPP_CRYPTOLINK mode
- fallback to local crypt4/crypt3 generation from subscription URL when backend returns plain happ://sub...
- apply same resolution for guide open action, qr page source and subscription page link display/copy
* fix(subscription): truncate long connect link
- render connection URL in a single line with ellipsis on /subscriptions
- preserve full URL in tooltip for easier manual copy
- keep copy button behavior unchanged
* fix(connection): build cryptolink from happ sub
- allow cryptolink fallback generation from happ://sub... URLs in addition to http(s)
- prevent plain happ://sub links from leaking into UI in HAPP_CRYPTOLINK mode
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