Commit Graph

29 Commits

Author SHA1 Message Date
c0mrade
72c325f8cc refactor(purchase): extract ClassicPurchaseWizard from god page
The largest and last structural extraction in the SubscriptionPurchase
decomp. Moves the classic-mode purchase flow (period → traffic →
servers → devices → confirm) into a self-contained step wizard at
src/components/subscription/purchase/ClassicPurchaseWizard.tsx.

The wizard self-owns:
- the preview query (gated on confirm step + form open)
- the submitPurchase mutation
- all six pieces of wizard state (currentStep, selectedPeriod,
  selectedTraffic, selectedServers, selectedDevices, showPurchaseForm)
- the steps memo + currentSelection memo + currentStepIndex/isFirst/isLast
- the init useEffect (seeds from classicOptions.selection)
- toggleServer / goToNextStep / goToPrevStep / resetPurchase / getStepLabel
- its own useCloseOnSuccessNotification handler (closes form + resets
  step on global success notification) — replaces the parent's
  handleCloseAllModals dependency for the classic flow

Parent shrinks to a pure orchestrator: route params, three top-level
queries (subscription, purchase-options, subscriptions-list), mode
detection, two pieces of tariffs-mode state, the success-notification
handler for the remaining tariff modals, and JSX that delegates to
the four extracted sub-components.

SubscriptionPurchase.tsx: 908 → 370 lines (-538).
Cumulative across the 5-step decomp: 2054 → 370 (-1684, -82%).
Drops 11 parent imports (useEffect, useMemo, useCallback, useMutation,
useQueryClient, useNavigate, PurchaseSelection, PeriodOption,
InsufficientBalancePrompt, CheckIcon, Twemoji, useCurrency,
usePromoDiscount, getErrorMessage, PurchaseStep).
2026-05-27 00:02:31 +03:00
c0mrade
cf5299973a refactor(purchase): extract TariffPickerGrid from god page
Move the tariff selection surface (promo-group banner, all-purchased
empty state, and the 1/2-col tariff cards grid with per-tariff CTAs)
into src/components/subscription/purchase/TariffPickerGrid.tsx.

The grid is pure presentation — it owns no queries or mutations.
Two callbacks back into the parent:
- onSelectTariff(tariff)  → parent sets selectedTariff + showForm
- onSwitchTariff(tariffId) → parent sets switchTariffId

Internal helpers (formatPrice via useCurrency, applyPromoDiscount
via usePromoDiscount, glass tokens via useTheme + getGlassColors,
navigate for the 'all purchased' back-to-list button) all derive
inside the component — nothing threaded as props beyond data.

SubscriptionPurchase.tsx: 1202 → 908 lines (-294).
2026-05-26 23:55:23 +03:00
c0mrade
a89ab96953 refactor(purchase): extract TariffPurchaseForm from god page
The single biggest extraction in the SubscriptionPurchase decomp.
Moves the per-tariff purchase form into a self-contained component
at src/components/subscription/purchase/TariffPurchaseForm.tsx.

The form self-owns:
- the purchaseTariff mutation (mode-aware: daily-tariff activate vs
  period-based + custom-days / custom-traffic toggles + summary)
- the auto-scroll-into-view ref + effect on mount
- selectedTariffPeriod / customDays / customTrafficGb / useCustomDays /
  useCustomTraffic — five pieces of state that were parent-level but
  only ever read inside this form. Form remounts with fresh state
  when the parent passes a new tariff (key={selectedTariff.id}).

Parent only owns selectedTariff + showTariffPurchase (the two pieces
the orchestrator needs to decide what to render) and threads them
into <TariffPurchaseForm>. Tariff card click handlers drop their
period-seeding line (the form seeds itself from tariff.periods[0]
on mount). The subscription_expired fallback callback simplifies
for the same reason.

SubscriptionPurchase.tsx: 1838 → 1222 lines (-616). Drops useRef,
TariffPeriod, and getInsufficientBalanceError from parent imports.
2026-05-26 23:52:42 +03:00
c0mrade
349a9ca421 refactor(purchase): extract SwitchTariffSheet from god page
Move the tariff-switch preview modal (137 lines of inline JSX
+ its previewTariffSwitch query + switchTariff mutation + the
auto-scroll-into-view useEffect + the ref) into a self-contained
sheet at src/components/subscription/sheets/SwitchTariffSheet.tsx.

Parent keeps the trigger (switchTariffId selection state lives on
the TariffPickerGrid card click) but no longer holds the query, the
mutation, the ref, or the side-effect. The subscription_expired
backend signal — which used to call three setters at parent level
(setSelectedTariff, setSelectedTariffPeriod, setShowTariffPurchase)
— now flows through a single onExpiredFallback(tariff) callback.

SubscriptionPurchase.tsx: 2010 → 1841 lines (-169).
2026-05-26 23:44:31 +03:00
c0mrade
13b64ab4e7 refactor(subscription): use ChevronRightIcon from icons barrel
The five extracted sheet components each had a hand-inlined chevron
SVG copy-pasted from the original god page. Swap them for the
existing ChevronRightIcon from src/components/icons — same visual,
one source of truth (Heroicons-aligned path), and pre-baked sizing
through the cn() className merge.

Note on the broader 'icon unification' audit recommendation: this
project does not depend on lucide-react. It already ships a 703-line
hand-rolled icon barrel at src/components/icons/index.tsx. The real
cleanup is migrating the ~139 inline SVGs across the app to that
barrel, not adding a new icon library. This commit consolidates the
five duplicated chevrons introduced by the recent sheet extraction;
broader inline-SVG migration is a separate, larger refactor.
2026-05-26 22:28:41 +03:00
c0mrade
bf3c114a72 refactor(subscription): extract DeleteSubscriptionSheet from god page
Move the delete-subscription expand/confirm block into
src/components/subscription/sheets/DeleteSubscriptionSheet.tsx.

The sheet itself decides between the Telegram destructive native dialog
and the inline web confirm panel, so the parent no longer needs the
platform discriminator. It owns deleteLoading and the API call; the
parent passes onDeleted (navigate + invalidate) and the textSecondary
glass color token used in the warning copy.

Subscription.tsx: 1828 → 1750 lines.
Drops now-unused imports (usePlatform; deleteLoading state).
2026-05-26 22:24:20 +03:00
c0mrade
d48d053e6e refactor(subscription): extract ServerManagementSheet from god page
Move the classic-mode manage-servers sheet (~280 lines of inline
JSX, the countries useQuery, its seeding useEffect, and the update
mutation) into src/components/subscription/sheets/ServerManagementSheet.tsx.

Parent keeps selectedServersToUpdate so the global 'close all modals'
callback can still clear it; the sheet owns the rest. The query stays
gated on trial state inside the sheet.

Subscription.tsx: 2117 → 1828 lines (−289).
2026-05-26 22:21:12 +03:00
c0mrade
fbf34a4861 refactor(subscription): extract TrafficTopupSheet from god page
Move the buy-traffic sheet (~170 lines of inline JSX + its
packages useQuery + purchase useMutation) into
src/components/subscription/sheets/TrafficTopupSheet.tsx.

Parent keeps selectedTrafficPackage in its state so the global
'close all modals' callback can still reset it; the sheet owns the
network calls and only fires while open.

Subscription.tsx: 2288 → ~2120 lines.
2026-05-26 22:17:41 +03:00
c0mrade
5902f40187 refactor(subscription): extract DeviceReductionSheet from god page
Move the reduce-devices sheet (~170 lines of inline JSX + its
useQuery + useMutation + seeding useEffect) into
src/components/subscription/sheets/DeviceReductionSheet.tsx.

Parent passes the shared targetDeviceLimit state (also used by the
revoke flow earlier) plus open/close + ids; the sheet owns the
reduction-info query and the reduce mutation, so neither fires until
the sheet is open.

Subscription.tsx: 2477 → 2288 lines (−189).
2026-05-26 22:15:15 +03:00
c0mrade
5e221d471e refactor(subscription): extract DeviceTopupSheet from god page
Move the buy-devices sheet (190 lines of inline JSX + its useQuery +
useMutation) into src/components/subscription/sheets/DeviceTopupSheet.tsx.
Parent passes shared state (purchaseOptions, devicesToAdd, ids); the
sheet self-owns the device-price query and purchase mutation, so the
query only fires when the sheet is actually open.

Subscription.tsx: 2671 → 2477 lines (−194).
2026-05-26 22:12:03 +03:00
c0mrade
cef675825b refactor(tokens): extract subscription urgent + critical semantic colors
The #FFB800 (23 hits) and #FF3B5C (11 hits) raw hex literals across
Subscription / SubscriptionPurchase / TrialOfferCard / useTrafficZone
all represent the same semantic: 'expiring soon' and 'expired'.
Distinct from warning-500 (#f59e0b) and error-500 (#ef4444) — the
subscription timeline needs a sharper, hotter signal than the
system-wide warning/error roles.

Add two semantic tokens:
  --color-urgent-400:   255, 184, 0    (#FFB800)
  --color-critical-500: 255, 59, 92    (#FF3B5C)

Exposed via Tailwind as urgent.400 / critical.500. Sweep all
single-and double-quoted hex literals to rgb(var(--color-...))
form so style attrs, SVG stroke=, and ternary string values all
resolve through CSS vars (auto-respects future light-theme
variants).

Left as-is: 4 hex appearances inside gradient pair strings —
linear-gradient(135deg, #FFB800, #FF8C00) — where the partner
color (#FF8C00 / #FF6B35) isn't in the token set. Partial-replace
inside gradient strings would just add noise; full extraction of
the orange-ramp companion is out of scope for this pass.
2026-05-26 20:20:05 +03:00
c0mrade
bd8a4fad6e refactor(theming): replace amber-* with warning-* tokens
Sweep all amber-N color classes to warning-N (uniformly pending/warning semantics).
Yellow and orange left for per-file review (mixed Stars brand + semantic uses).
2026-05-26 11:10:50 +03:00
c0mrade
cb7693e3ff refactor(theming): replace green-* / emerald-* with success-* tokens
Sweep: text/bg/border/ring/from/to/via/fill/stroke/shadow-{green,emerald}-N → success-N
(all were semantic success/active/paid states).
2026-05-26 11:09:28 +03:00
c0mrade
9c5e38b594 refactor(theming): replace red-* palette with semantic error-* tokens
Sweep 30 files: text/bg/border/ring/from/to/via/fill/stroke/shadow/divide/decoration/
outline/placeholder-red-N → -error-N. All red usages were semantically error/danger
(no brand red), so tokens now flow through the design-system CSS variables and respond
to palette overrides.
2026-05-26 11:08:22 +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
c0mrade
debee7729f fix: platform-aware delete confirmation + trial CTA to purchase
Delete subscription:
- Telegram mini app: native destructive popup (red button)
- Web (mobile + desktop): Sheet bottom panel with full-width buttons
- Both: proper loading state on delete button

Trial CTA: leads to /subscription/purchase (trial cannot be renewed)
2026-03-24 18:30:59 +03:00
c0mrade
871b4769aa feat: show autopay/auto-charge status on subscription cards, invalidate list on toggle
- SubscriptionListCard: always show autopay status (green check / red cross)
  for regular tariffs, show auto-charge status for daily tariffs
- Hide autopay for trial subscriptions
- Autopay mutation: invalidate subscriptions-list for instant UI update
- Types: added is_daily, is_daily_paused to SubscriptionListItem
2026-03-24 12:28:02 +03:00
c0mrade
6a53221cc1 fix: hide renewal button for daily tariffs in multi-tariff mode, fix hint text
- Daily tariffs renew automatically — no manual renewal CTA needed
- Show "Продление подписки" hint in multi-tariff instead of "Продление и смена тарифа"
2026-03-24 11:10:30 +03:00
c0mrade
82eb03dec9 feat: separate renewal and purchase flows in multi-tariff mode
Renewal flow (per-subscription):
- New /subscriptions/:id/renew page showing only period options for
  the specific tariff with balance display and renew button
- PurchaseCTAButton links to /renew in multi-tariff mode

Purchase flow (new tariffs only):
- Hide already purchased tariffs (is_purchased flag from backend)
- Fallback UI when all tariffs are owned
- Types: added is_purchased, all_tariffs_purchased fields
2026-03-24 10:47:49 +03:00
c0mrade
9d3fb37d60 fix: remove duplicate back buttons, improve multi-subscription UX
- Remove custom back buttons from Subscription detail and SubscriptionPurchase
  pages — native Telegram mini-app back button already handles navigation
- SubscriptionListCard: distinct trial badge with star icon and amber styling,
  trial cards get amber border/background for visual differentiation
- SubscriptionPurchase: change "Купить тариф" to "Новый тариф" when adding
  a new tariff in multi-subscription mode
- Subscription detail: show tariff name as title in multi-tariff mode
2026-03-23 21:38:04 +03:00
c0mrade
376e1bb56d feat: enhance subscription list UX with progress bars, status badges, and glass theme
- SubscriptionListCard: replace status dot with localized status badge,
  add traffic progress bar with color coding, use locale-aware date format
- Subscriptions page: apply glass theme colors, improve empty state styling,
  add SVG plus icon for buy button, skeleton loader with glass theme
- Subscription detail: invalidate subscriptions-list cache on mutations
2026-03-23 18:57:05 +03:00
c0mrade
f4de6d8ad8 fix: multi-subscription UI audit fixes and cache invalidation improvements
- Fix AdminUserDetail multi-subscription display with useMemo optimization
- Update Dashboard subscription cards and purchase entry points
- Fix WebSocket notification handlers for multi-subscription context
- Update SubscriptionPurchase cache invalidation for multi-tariff mode
- Fix Subscriptions list page and navigation patterns
- Update App routing, DeepLinkRedirect, and CommandPalette for multi-sub
- Add SubscriptionListCard component
- Fix TopUpResult and PromoOffersSection navigation
2026-03-23 18:57:05 +03:00
c0mrade
71a0111a04 revert: remove device manager redesign, restore original device UI 2026-03-08 01:00:21 +03:00
c0mrade
6dc8ca0d18 fix: unify device manager into additional options card with unbounded dot selector 2026-03-08 00:45:47 +03:00
c0mrade
edb7ef0488 feat: unified device manager with dot-based selector
Replace separate buy/reduce device sections with a single DeviceManager component featuring a dot-step selector for intuitive device limit adjustment.
2026-03-08 00:17:04 +03:00
Fringg
d526d095de fix: заменить хардкодный зелёный (#3EDBB0) на акцентный цвет из темы
Все вхождения #3EDBB0, #2BC49A и rgba(62,219,176,...) заменены на
CSS-переменные --color-accent-400/500 из системы тем.

Затронутые компоненты:
- TrialOfferCard: кнопка, бордер, свечение, иконка
- Subscription: лейблы, иконки, кнопки паузы/копирования
- SubscriptionCardActive: бордер карточки, бейдж статуса
- SubscriptionPurchase: градиент триал-блока
- PurchaseCTAButton: градиент кнопки, иконка
- TrafficProgressBar: цвета прогресс-бара
- StatsGrid: убрана зависимость от useTrafficZone, используется accent напрямую
- trafficZone.ts: добавлены mainVar/mainVarRaw для CSS-переменных
- tailwind.config.js: trialGlow keyframes на accent цвет
2026-03-05 10:56:45 +03:00
Fringg
bdc201b5ea fix: eliminate hover flickering across all pages
- Extract CountdownTimer into React.memo component to isolate 1s
  interval re-renders from the full Subscription page
- Add box-shadow transition to .hover-border-gradient (was instant)
- Remove conflicting p-[1.5px] from PurchaseCTAButton (CSS border
  already handles 1.5px)
- Replace transition-all with specific properties on bento-card-hover,
  bento-card-glow, device dots, toggle switches, progress bars
- Reduce translateY hover from -4px to -2px to prevent cursor
  losing hover target during fast vertical mouse movement
2026-02-27 07:02:59 +03:00
Fringg
126f9ab9b9 refactor: extract purchase/renewal flow to separate page
Move ~1900 lines of purchase/renewal logic from Subscription.tsx into
a new SubscriptionPurchase.tsx page at /subscription/purchase.

- Create SubscriptionPurchase.tsx with tariffs grid, classic mode wizard,
  switch preview modal, and promo handling
- Create PurchaseCTAButton.tsx with animated gradient border, state-aware
  colors and context-aware text
- Create subscriptionHelpers.ts with shared utilities
- Add CopyIcon to shared icons module
- Register /subscription/purchase route with lazy loading
- Update SubscriptionCardExpired and PromoOffersSection navigators
- Add CTA translation keys to ru.json and en.json
- Remove all scrollToExtend references
2026-02-27 06:47:30 +03:00