feat: multi-subscription frontend support

- Add SubscriptionListItem and SubscriptionsListResponse types
- Add getSubscriptions/getSubscriptionById API methods
- Add optional subscriptionId parameter to all 30+ API methods
- Create /subscriptions page with subscription list cards
- Add /subscription/:subscriptionId route for per-subscription management
- Dashboard: show multi-tariff navigation block when user has 2+ subscriptions
- Subscription page: read subscriptionId from URL params, pass to all queries
- SubscriptionPurchase: accept subscriptionId from query params
- SubscriptionCardExpired: pass subscription.id to API calls
- Full backward compatibility: without subscriptionId, works as before
This commit is contained in:
c0mrade
2026-03-19 19:26:42 +03:00
parent 84bded7974
commit 820ba46cc4
8 changed files with 562 additions and 173 deletions

View File

@@ -1,7 +1,7 @@
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router';
import { useNavigate, useSearchParams } from 'react-router';
import { AxiosError } from 'axios';
import { subscriptionApi } from '../api/subscription';
import { promoApi } from '../api/promo';
@@ -28,6 +28,10 @@ export default function SubscriptionPurchase() {
const { t } = useTranslation();
const queryClient = useQueryClient();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const subscriptionId = searchParams.get('subscriptionId')
? parseInt(searchParams.get('subscriptionId')!, 10)
: undefined;
const { formatAmount, currencySymbol } = useCurrency();
const { isDark } = useTheme();
const g = getGlassColors(isDark);
@@ -37,7 +41,7 @@ export default function SubscriptionPurchase() {
// Subscription query (shares cache with /subscription page)
const { data: subscriptionResponse, isLoading } = useQuery({
queryKey: ['subscription'],
queryFn: subscriptionApi.getSubscription,
queryFn: () => subscriptionApi.getSubscription(subscriptionId),
retry: false,
staleTime: 0,
refetchOnMount: 'always',
@@ -52,7 +56,7 @@ export default function SubscriptionPurchase() {
refetch: refetchOptions,
} = useQuery({
queryKey: ['purchase-options'],
queryFn: subscriptionApi.getPurchaseOptions,
queryFn: () => subscriptionApi.getPurchaseOptions(subscriptionId),
staleTime: 0,
refetchOnMount: 'always',
});
@@ -217,13 +221,13 @@ export default function SubscriptionPurchase() {
// Preview query (classic)
const { data: preview, isLoading: previewLoading } = useQuery({
queryKey: ['purchase-preview', currentSelection],
queryFn: () => subscriptionApi.previewPurchase(currentSelection),
queryFn: () => subscriptionApi.previewPurchase(currentSelection, subscriptionId),
enabled: !!selectedPeriod && showPurchaseForm && currentStep === 'confirm',
});
// Classic purchase mutation
const purchaseMutation = useMutation({
mutationFn: () => subscriptionApi.submitPurchase(currentSelection),
mutationFn: () => subscriptionApi.submitPurchase(currentSelection, subscriptionId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['subscription'] });
queryClient.invalidateQueries({ queryKey: ['purchase-options'] });
@@ -234,13 +238,13 @@ export default function SubscriptionPurchase() {
// Switch preview query
const { data: switchPreview, isLoading: switchPreviewLoading } = useQuery({
queryKey: ['tariff-switch-preview', switchTariffId],
queryFn: () => subscriptionApi.previewTariffSwitch(switchTariffId!),
queryFn: () => subscriptionApi.previewTariffSwitch(switchTariffId!, subscriptionId),
enabled: !!switchTariffId,
});
// Tariff switch mutation
const switchTariffMutation = useMutation({
mutationFn: (tariffId: number) => subscriptionApi.switchTariff(tariffId),
mutationFn: (tariffId: number) => subscriptionApi.switchTariff(tariffId, subscriptionId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['subscription'] });
queryClient.invalidateQueries({ queryKey: ['purchase-options'] });