Commit Graph

1591 Commits

Author SHA1 Message Date
Fringg
1ee0f18343 feat: FAQ pages — Q&A builder in admin, accordion view for users
Admin editor:
- page_type selector: "Страница" / "FAQ" radio buttons
- When FAQ: FaqBuilder component replaces TipTap editor
  - Question input + answer textarea per Q&A item
  - Add/remove items, up/down reorder buttons
  - Per-locale Q&A (stored as JSON array in content JSONB)
- "Создать FAQ" button in list alongside "Создать страницу"
- Filter tabs: Все / Страницы / FAQ
- Page type badge on each row (accent for pages, amber for FAQ)

Public view (InfoPageView):
- Auto-detects page_type='faq' and renders FaqView component
- Accordion with smooth height animation (300ms ease-in-out)
- Search filter appears when >3 questions
- DOMPurify sanitization on FAQ answers
- Single-item expand (click to open/close)

i18n: full FAQ keys for all 4 locales (questions, answers,
search, counts, empty states, builder labels)
2026-04-24 14:02:03 +03:00
Fringg
596f638cdd fix: prose content overflow on mobile — add overflow-x-auto 2026-04-24 13:51:08 +03:00
Fringg
3ef54adb3e fix: info page editor — locale switch no longer resets user edits
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.
2026-04-24 08:22:16 +03:00
Fringg
7d6d0ba344 feat: information pages — admin editor with TipTap, public viewer
Admin panel:
- New section "Информационные страницы" in admin nav
- Admin list: slug, title, active toggle, edit/delete, sort order
- Admin editor: full TipTap (bold, italic, headings, lists, links,
  image/video upload, alignment, highlight, code blocks)
  with locale tabs (RU/EN/ZH/FA) for title and content
- Create/edit via URL params, slug auto-generation

Public:
- /info/:slug route with DOMPurify sanitized content
- Locale fallback, Telegram safe area, loading skeleton, 404

i18n: all 4 locales
2026-04-24 08:11:20 +03:00
Fringg
d43638b34e fix: floating action bar covering pagination — add bottom spacer when selection active 2026-04-24 07:36:33 +03:00
Fringg
cb148d181a fix: persist checkbox selection across page changes and filter switches
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).
2026-04-24 07:29:02 +03:00
Fringg
e2706c7c26 fix: select-all subscriptions + per-page selector
- 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)
2026-04-24 07:23:58 +03:00
Fringg
98871af656 fix: multi-tariff filter now works server-side + trial-only checkbox
- 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)
2026-04-24 07:16:53 +03:00
Fringg
efca40d9c8 fix: multi-tariff filter shows all users when >1 tariffs selected
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.
2026-04-24 07:08:40 +03:00
Fringg
c32bcfeb9e fix: floating action bar hidden behind mobile bottom nav — raise z-index + bottom padding 2026-04-24 07:04:44 +03:00
Fringg
eeda567622 feat: second header checkbox for bulk subscription selection
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.
2026-04-24 07:03:53 +03:00
Fringg
3cb15177cc fix: show sub-rows for single-subscription users + fix floating bar position
- 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)
2026-04-24 06:59:55 +03:00
Fringg
c726d3d74e feat: bulk delete subscription + fix sub-rows showing for filtered single subs
- Add "Удалить подписки" action with red trash icon, destructive
  warning dialog (red border, irreversible action notice)
- Fix canExpand: show sub-rows when isMultiTariff && subs > 0
  (was > 1, hiding single filtered subscription results)
- i18n: deleteSubscription action + warning/hint (ru + en)
2026-04-24 06:51:21 +03:00
Fringg
5969c74f92 feat: devices in subscription rows, set_devices action, table columns
- 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)
2026-04-24 06:36:04 +03:00
Fringg
f13b7239a5 feat: multi-select tariff filter, select-all subscriptions, auto-expand
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
2026-04-24 06:20:43 +03:00
Fringg
9673f26469 fix: make subscription checkboxes same size as user checkboxes (h-5 w-5) 2026-04-24 06:11:43 +03:00
Fringg
3801c3626a fix: expand chevron invisible on accent background
ChevronExpandIcon used text-dark-400 (gray) on the accent gradient
circle (green) — completely invisible. Changed to text-white.
2026-04-24 06:08:57 +03:00
Fringg
0844144e23 fix: make checkboxes visible on dark theme in bulk actions
- Increase user checkbox size: h-4.5 → h-5 w-5 with rounded-md border-2
- Increase subscription checkbox: h-3.5 → h-4.5 with rounded-md border-2
- Brighter unselected border: dark-600 → dark-500 with lighter bg
- Hover effect: accent-500/50 border glow on hover
- Selected glow: accent shadow ring for clear visual feedback
- Smooth transition-all duration-150 for all state changes
2026-04-24 06:02:11 +03:00
Fringg
78b41dc338 feat: multi-tariff bulk actions UI — subscription-level selection
- 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)
2026-04-24 05:49:38 +03:00
Fringg
161fde4301 fix: bulk actions — add tariff info to user list, fix tariff column display
- 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
2026-04-24 05:28:08 +03:00
Fringg
8dd5e4985f fix: bulk progress log shows generic error instead of backend message
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").
2026-04-24 05:15:13 +03:00
Fringg
fc8170fa1f fix: align frontend bulk action types with backend API contract
- Fix BulkActionType enum: 'extend' → 'extend_subscription',
  'cancel' → 'cancel_subscription', 'activate' → 'activate_subscription'
- Fix BulkActionParams: 'balance_kopeks' → 'amount_kopeks' to match backend
- Fix SSE complete handler: build errors from progress log (backend
  complete event has no errors array)
- Add skipped_count to all BulkActionResult construction paths
- Add dry_run support to BulkActionRequest type
- Transform backend results[] to frontend errors[] in non-streaming path
- Remove unused errors variable in streaming fallback path
2026-04-24 04:52:30 +03:00
Fringg
312e0b4927 feat: bulk actions — live progress, grant subscription, error details
- 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)
2026-04-24 04:40:53 +03:00
Fringg
ebe9d9be48 fix: bulk actions frontend — selectedUserIds mapping, debounce cleanup
- 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
2026-04-24 04:24:33 +03:00
Fringg
1772d9632e feat: admin bulk actions page with TanStack Table
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
2026-04-24 04:15:28 +03:00
Fringg
33f32648f0 feat: improve squad management UX — rename, swap icons, color gift button
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.
2026-04-23 05:00:36 +03:00
Fringg
2d6815a88e fix: landing page currency symbol not changing with locale
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.
2026-04-23 04:55:36 +03:00
Fringg
1d5ce2d4ea fix: complete is_limited status handling across all views
- 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
2026-04-23 04:04:11 +03:00
Fringg
f68b46699c fix: limited (traffic exhausted) subscription shows as expired
- 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"
2026-04-23 03:56:07 +03:00
Fringg
e6403eb7d0 fix: gift page shows only 1 tariff when tariffs have different periods
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
2026-04-23 03:29:00 +03:00
Egor
848caba6be Merge pull request #402 from BlackRaincoat/fix/twemoji-server-names
Fix/twemoji server names
2026-04-22 06:07:25 +03:00
Egor
4a9a4948b4 Merge pull request #403 from BEDOLAGA-DEV/main
w
2026-04-22 06:06:40 +03:00
Dmitry Lunin
d75440bfe4 fix: wrap server display names with Twemoji for cross-platform emoji rendering 2026-04-21 23:03:20 +03:00
Egor
bfa5f7ae7f Merge pull request #401 from BEDOLAGA-DEV/release-please--branches--main--components--cabinet-frontend
chore(main): release 1.47.0
2026-04-18 04:00:36 +03:00
github-actions[bot]
2fcec89bbf chore(main): release 1.47.0 2026-04-18 00:58:29 +00:00
Egor
52c02d5e39 Merge pull request #400 from BEDOLAGA-DEV/dev
Dev
2026-04-18 03:58:03 +03:00
Fringg
996840f627 fix: change aurapay icon to pink gradient to avoid kassa_ai color collision 2026-04-18 00:12:15 +03:00
Fringg
48827e430d feat: add AuraPay payment provider to cabinet frontend
- Payment method label and icon (violet gradient, AP)
- Admin settings tree entry for AURAPAY category
- Locale strings and descriptions for ru, en, zh, fa
2026-04-18 00:05:56 +03:00
Egor
73bea1a28a Merge pull request #399 from BEDOLAGA-DEV/release-please--branches--main--components--cabinet-frontend
chore(main): release 1.46.0
2026-04-16 06:11:25 +03:00
github-actions[bot]
7d819c6826 chore(main): release 1.46.0 2026-04-16 03:10:37 +00:00
Egor
2fd0961c03 Merge pull request #398 from BEDOLAGA-DEV/dev
Dev
2026-04-16 06:10:11 +03:00
Fringg
4eed0f358e fix: differentiate rollypay icon from riopay (teal gradient, RY text) 2026-04-16 05:58:58 +03:00
Fringg
770a5803d6 feat: add RollyPay payment provider to cabinet frontend
- Payment method label and icon (green gradient, RP)
- Admin settings tree entry for ROLLYPAY category
- Locale strings and descriptions for ru, en, zh, fa
2026-04-16 05:55:21 +03:00
Fringg
5cbf29a1e8 fix: add paypear payment method description to ru/en locales 2026-04-16 05:38:34 +03:00
Fringg
31ac511a09 feat: add PayPear payment provider to cabinet frontend
- Payment method label and icon (amber gradient)
- Admin settings tree entry for PAYPEAR category
- Locale strings for ru, en, zh, fa
2026-04-16 05:26:09 +03:00
github-actions[bot]
e0ed311614 chore(main): release 1.45.1 (#396)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-15 14:16:04 +03:00
Egor
e6cf3af846 fix: period discounts clearing, SBP default, happ cryptolink flow
* 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>
2026-04-15 14:13:24 +03:00
Egor
ac89343cc1 Dev (#394)
* 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>
2026-04-15 14:04:39 +03:00
Fringg
d15c2408df 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.
2026-04-15 04:13:53 +03:00
Fringg
3a865a472b 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.
2026-04-15 03:40:09 +03:00