import { useState, useEffect, useMemo, useRef, useCallback } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { useNavigate, useSearchParams } from 'react-router'; import { AxiosError } from 'axios'; import { subscriptionApi } from '../api/subscription'; import { promoApi } from '../api/promo'; import { WebBackButton } from '../components/WebBackButton'; import { getGlassColors } from '../utils/glassTheme'; import { useTheme } from '../hooks/useTheme'; import type { PurchaseSelection, PeriodOption, Tariff, TariffPeriod, ClassicPurchaseOptions, } from '../types'; import InsufficientBalancePrompt from '../components/InsufficientBalancePrompt'; import { useCurrency } from '../hooks/useCurrency'; import { useCloseOnSuccessNotification } from '../store/successNotification'; import { CheckIcon } from '../components/icons'; import { getErrorMessage, getInsufficientBalanceError, type PurchaseStep, } from '../utils/subscriptionHelpers'; 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); const formatPrice = (kopeks: number) => `${formatAmount(kopeks / 100)} ${currencySymbol}`; // Subscription query (shares cache with /subscription page) const { data: subscriptionResponse, isLoading } = useQuery({ queryKey: ['subscription', subscriptionId], queryFn: () => subscriptionApi.getSubscription(subscriptionId), retry: false, staleTime: 0, refetchOnMount: 'always', }); const subscription = subscriptionResponse?.subscription ?? null; // Purchase options const { data: purchaseOptions, isLoading: optionsLoading, isError: optionsError, refetch: refetchOptions, } = useQuery({ queryKey: ['purchase-options', subscriptionId], queryFn: () => subscriptionApi.getPurchaseOptions(subscriptionId), staleTime: 0, refetchOnMount: 'always', }); // Active promo discount const { data: activeDiscount } = useQuery({ queryKey: ['active-discount'], queryFn: promoApi.getActiveDiscount, staleTime: 30000, }); // Sales mode detection const isTariffsMode = purchaseOptions?.sales_mode === 'tariffs'; const classicOptions = !isTariffsMode ? (purchaseOptions as ClassicPurchaseOptions) : null; const tariffs = isTariffsMode && purchaseOptions && 'tariffs' in purchaseOptions ? purchaseOptions.tariffs : []; // Multi-tariff: check via subscriptions list query const { data: multiSubData } = useQuery({ queryKey: ['subscriptions-list'], queryFn: () => subscriptionApi.getSubscriptions(), staleTime: 60_000, }); const isMultiTariff = multiSubData?.multi_tariff_enabled ?? false; // Helper to apply promo discount const applyPromoDiscount = ( priceKopeks: number, existingOriginalPrice?: number | null, ): { price: number; original: number | null; percent: number | null; isPromoGroup: boolean; } => { const hasExisting = (existingOriginalPrice ?? 0) > priceKopeks; const hasPromo = !!activeDiscount?.is_active && !!activeDiscount.discount_percent; if (!hasExisting && !hasPromo) { return { price: priceKopeks, original: null, percent: null, isPromoGroup: false }; } let finalPrice = priceKopeks; if (hasPromo) { finalPrice = Math.round(priceKopeks * (1 - activeDiscount!.discount_percent! / 100)); } if (hasExisting) { const combinedPercent = hasPromo ? Math.round((1 - finalPrice / existingOriginalPrice!) * 100) : Math.round((1 - priceKopeks / existingOriginalPrice!) * 100); return { price: finalPrice, original: existingOriginalPrice!, percent: combinedPercent, isPromoGroup: true, }; } return { price: finalPrice, original: priceKopeks, percent: activeDiscount!.discount_percent!, isPromoGroup: false, }; }; // Classic mode state const [currentStep, setCurrentStep] = useState('period'); const [selectedPeriod, setSelectedPeriod] = useState(null); const [selectedTraffic, setSelectedTraffic] = useState(null); const [selectedServers, setSelectedServers] = useState([]); const [selectedDevices, setSelectedDevices] = useState(1); const [showPurchaseForm, setShowPurchaseForm] = useState(false); // Tariffs mode state const [selectedTariff, setSelectedTariff] = useState(null); const [selectedTariffPeriod, setSelectedTariffPeriod] = useState(null); const [showTariffPurchase, setShowTariffPurchase] = useState(false); const [customDays, setCustomDays] = useState(30); const [customTrafficGb, setCustomTrafficGb] = useState(50); const [useCustomDays, setUseCustomDays] = useState(false); const [useCustomTraffic, setUseCustomTraffic] = useState(false); // Refs for auto-scroll const switchModalRef = useRef(null); const tariffPurchaseRef = useRef(null); // Tariff switch const [switchTariffId, setSwitchTariffId] = useState(null); // Auto-close all modals on success notification const handleCloseAllModals = () => { setShowPurchaseForm(false); setShowTariffPurchase(false); setSwitchTariffId(null); setSelectedTariff(null); setSelectedTariffPeriod(null); }; useCloseOnSuccessNotification(handleCloseAllModals); // Get available servers const getAvailableServers = useCallback( (period: PeriodOption | null) => { if (!period?.servers.options) return []; return period.servers.options.filter((server) => { if (!server.is_available) return false; if (subscription?.is_trial && server.name.toLowerCase().includes('trial')) return false; return true; }); }, [subscription?.is_trial], ); // Steps for classic mode const steps = useMemo(() => { const result: PurchaseStep[] = ['period']; if (selectedPeriod?.traffic.selectable && (selectedPeriod.traffic.options?.length ?? 0) > 0) { result.push('traffic'); } const availableServers = getAvailableServers(selectedPeriod); if (availableServers.length > 1) { result.push('servers'); } if (selectedPeriod && selectedPeriod.devices.max > selectedPeriod.devices.min) { result.push('devices'); } result.push('confirm'); return result; }, [selectedPeriod, getAvailableServers]); const currentStepIndex = steps.indexOf(currentStep); const isFirstStep = currentStepIndex === 0; const isLastStep = currentStepIndex === steps.length - 1; // Initialize classic mode selection useEffect(() => { if (classicOptions && !selectedPeriod) { const defaultPeriod = classicOptions.periods.find((p) => p.id === classicOptions.selection.period_id) || classicOptions.periods[0]; setSelectedPeriod(defaultPeriod); setSelectedTraffic(classicOptions.selection.traffic_value); const availableServers = getAvailableServers(defaultPeriod); const availableServerUuids = new Set(availableServers.map((s) => s.uuid)); if (availableServers.length === 1) { setSelectedServers([availableServers[0].uuid]); } else { setSelectedServers( classicOptions.selection.servers.filter((uuid) => availableServerUuids.has(uuid)), ); } setSelectedDevices(classicOptions.selection.devices); } }, [classicOptions, selectedPeriod, getAvailableServers]); // Build classic mode selection const currentSelection: PurchaseSelection = useMemo( () => ({ period_id: selectedPeriod?.id, period_days: selectedPeriod?.period_days, traffic_value: selectedTraffic ?? undefined, servers: selectedServers, devices: selectedDevices, }), [selectedPeriod, selectedTraffic, selectedServers, selectedDevices], ); // Preview query (classic) const { data: preview, isLoading: previewLoading } = useQuery({ queryKey: ['purchase-preview', currentSelection], queryFn: () => subscriptionApi.previewPurchase(currentSelection, subscriptionId), enabled: !!selectedPeriod && showPurchaseForm && currentStep === 'confirm', }); // Classic purchase mutation const purchaseMutation = useMutation({ mutationFn: () => subscriptionApi.submitPurchase(currentSelection, subscriptionId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); queryClient.invalidateQueries({ queryKey: ['purchase-options', subscriptionId] }); navigate('/subscriptions', { replace: true }); }, }); // Switch preview query const { data: switchPreview, isLoading: switchPreviewLoading } = useQuery({ queryKey: ['tariff-switch-preview', switchTariffId], queryFn: () => subscriptionApi.previewTariffSwitch(switchTariffId!, subscriptionId), enabled: !!switchTariffId, }); // Tariff switch mutation const switchTariffMutation = useMutation({ mutationFn: (tariffId: number) => subscriptionApi.switchTariff(tariffId, subscriptionId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); queryClient.invalidateQueries({ queryKey: ['purchase-options', subscriptionId] }); setSwitchTariffId(null); navigate('/subscriptions', { replace: true }); }, onError: (error: unknown) => { if (error instanceof AxiosError) { const detail = error.response?.data?.detail; if ( typeof detail === 'object' && detail?.error_code === 'subscription_expired' && detail?.use_purchase_flow === true ) { const targetTariff = tariffs.find((tariff) => tariff.id === switchTariffId); if (targetTariff) { setSwitchTariffId(null); setSelectedTariff(targetTariff); setSelectedTariffPeriod(targetTariff.periods[0] || null); setShowTariffPurchase(true); queryClient.invalidateQueries({ queryKey: ['purchase-options', subscriptionId] }); } } } }, }); // Tariff purchase mutation const tariffPurchaseMutation = useMutation({ mutationFn: () => { if (!selectedTariff) { throw new Error('Tariff not selected'); } const isDailyTariff = selectedTariff.is_daily || (selectedTariff.daily_price_kopeks && selectedTariff.daily_price_kopeks > 0); const days = isDailyTariff ? 1 : useCustomDays ? customDays : selectedTariffPeriod?.days || 30; const trafficGb = useCustomTraffic && selectedTariff.custom_traffic_enabled ? customTrafficGb : undefined; return subscriptionApi.purchaseTariff(selectedTariff.id, days, trafficGb); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['subscription'] }); queryClient.invalidateQueries({ queryKey: ['purchase-options'] }); queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); navigate('/subscriptions', { replace: true }); }, }); // Auto-scroll effects useEffect(() => { if (switchTariffId && switchModalRef.current) { const timer = setTimeout(() => { switchModalRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 100); return () => clearTimeout(timer); } }, [switchTariffId]); useEffect(() => { if (showTariffPurchase && tariffPurchaseRef.current) { const timer = setTimeout(() => { tariffPurchaseRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 100); return () => clearTimeout(timer); } }, [showTariffPurchase]); // Classic mode helpers const toggleServer = (uuid: string) => { if (selectedServers.includes(uuid)) { if (selectedServers.length > 1) { setSelectedServers(selectedServers.filter((s) => s !== uuid)); } } else { setSelectedServers([...selectedServers, uuid]); } }; const goToNextStep = () => { const nextIndex = currentStepIndex + 1; if (nextIndex < steps.length) { setCurrentStep(steps[nextIndex]); } }; const goToPrevStep = () => { const prevIndex = currentStepIndex - 1; if (prevIndex >= 0) { setCurrentStep(steps[prevIndex]); } }; const resetPurchase = () => { setShowPurchaseForm(false); setCurrentStep('period'); }; const getStepLabel = (step: PurchaseStep) => { switch (step) { case 'period': return t('subscription.stepPeriod'); case 'traffic': return t('subscription.stepTraffic'); case 'servers': return t('subscription.stepServers'); case 'devices': return t('subscription.stepDevices'); case 'confirm': return t('subscription.stepConfirm'); } }; if (isLoading || optionsLoading) { return (
); } if (optionsError || (!purchaseOptions && !optionsLoading)) { return (

{t('subscription.extend')}

{t('subscription.loadError', 'Не удалось загрузить варианты подписки')}

); } return (
{/* Header */}

{isMultiTariff && !subscriptionId ? t('subscription.newTariff', 'Новый тариф') : !isMultiTariff && subscription?.is_daily && !subscription?.is_trial ? t('subscription.switchTariff.title') : subscription && !subscription.is_trial ? t('subscription.extend') : t('subscription.getSubscription')}

{/* Tariffs Section */} {isTariffsMode && tariffs.length > 0 && (
{/* Trial upgrade prompt — hidden when expired banner is active */} {subscription?.is_trial && !( isTariffsMode && purchaseOptions && 'subscription_is_expired' in purchaseOptions && purchaseOptions.subscription_is_expired ) && (
{t('subscription.trialUpgrade.title')}
{t('subscription.trialUpgrade.description')}
)} {/* Expired subscription notice */} {isTariffsMode && purchaseOptions && 'subscription_is_expired' in purchaseOptions && purchaseOptions.subscription_is_expired && (
{t('subscription.expiredBanner.title')}
{t('subscription.expiredBanner.selectTariff')}
)} {/* Legacy subscription notice */} {subscription && !subscription.is_trial && !subscription.tariff_id && (
{t('subscription.legacy.selectTariffTitle')}
{t('subscription.legacy.selectTariffDescription')}
{t('subscription.legacy.currentSubContinues')}
)} {/* Switch Tariff Preview Modal */} {switchTariffId && (

{t('subscription.switchTariff.title')}

{switchPreviewLoading ? (
) : ( switchPreview && (() => { const targetTariff = tariffs.find((tariff) => tariff.id === switchTariffId); const dailyPrice = targetTariff?.daily_price_kopeks ?? targetTariff?.price_per_day_kopeks ?? 0; const isDailyTariff = dailyPrice > 0; return ( <>
{t('subscription.switchTariff.currentTariff')} {switchPreview.current_tariff_name || '-'}
{t('subscription.switchTariff.newTariff')} {switchPreview.new_tariff_name}
{t('subscription.switchTariff.remainingDays')} {switchPreview.remaining_days}
{isDailyTariff && (
{t('subscription.switchTariff.dailyPayment')}
{formatPrice(dailyPrice)}
{t('subscription.switchTariff.dailyChargeDescription')}
)}
{t('subscription.switchTariff.upgradeCost')} {switchPreview.discount_percent && switchPreview.discount_percent > 0 && ( -{switchPreview.discount_percent}% )}
{switchPreview.discount_percent && switchPreview.discount_percent > 0 && switchPreview.base_upgrade_cost_kopeks && switchPreview.base_upgrade_cost_kopeks > 0 && ( {formatPrice(switchPreview.base_upgrade_cost_kopeks)} )} {switchPreview.upgrade_cost_kopeks > 0 ? switchPreview.upgrade_cost_label : t('subscription.switchTariff.free')}
{!switchPreview.has_enough_balance && switchPreview.upgrade_cost_kopeks > 0 && ( )} {switchTariffMutation.isError && (() => { const detail = switchTariffMutation.error instanceof AxiosError ? switchTariffMutation.error.response?.data?.detail : null; if ( typeof detail === 'object' && detail?.error_code === 'subscription_expired' ) { return null; } return (
{getErrorMessage(switchTariffMutation.error)}
); })()} ); })() )}
)} {!showTariffPurchase ? ( <> {/* Promo group discount banner */} {tariffs.some((tariff) => tariff.promo_group_name) && (
{t('subscription.promoGroup.yourGroup', { name: tariffs.find((tariff) => tariff.promo_group_name)?.promo_group_name, })}
{t('subscription.promoGroup.personalDiscountsApplied')}
)} {/* Tariff Grid */} {isMultiTariff && purchaseOptions && 'all_tariffs_purchased' in purchaseOptions && purchaseOptions.all_tariffs_purchased && (

{t('subscription.allTariffsPurchased', 'Все тарифы подключены')}

{t( 'subscription.allTariffsPurchasedDesc', 'Вы уже приобрели все доступные тарифы. Продлить подписку можно на странице тарифа.', )}

)}
{[...tariffs] .filter((tariff) => { // In multi-tariff mode: hide already purchased tariffs if (isMultiTariff && tariff.is_purchased) return false; if (subscription?.is_trial && tariff.name.toLowerCase().includes('trial')) { return false; } return true; }) .sort((a, b) => { const aIsCurrent = a.is_current || a.id === subscription?.tariff_id; const bIsCurrent = b.is_current || b.id === subscription?.tariff_id; if (aIsCurrent && !bIsCurrent) return -1; if (!aIsCurrent && bIsCurrent) return 1; return 0; }) .map((tariff) => { const isCurrentTariff = tariff.is_current || tariff.id === subscription?.tariff_id; const isSubscriptionExpired = isTariffsMode && purchaseOptions && 'subscription_is_expired' in purchaseOptions && purchaseOptions.subscription_is_expired === true; const canSwitch = !isMultiTariff && subscription && subscription.tariff_id && !isCurrentTariff && !subscription.is_trial && !isSubscriptionExpired && subscription.is_active; const isLegacySubscription = subscription && !subscription.is_trial && !subscription.tariff_id; return (
{tariff.name}
{tariff.description && (
{tariff.description}
)}
{isCurrentTariff && ( {t('subscription.currentTariff')} )}
{tariff.traffic_limit_label}
{tariff.device_limit === 0 ? '∞' : t('subscription.devices', { count: tariff.device_limit })}
{tariff.traffic_reset_mode && tariff.traffic_reset_mode !== 'NO_RESET' && (
{t(`subscription.trafficReset.${tariff.traffic_reset_mode}`)}
)}
{/* Price info */}
{(() => { const dailyPrice = tariff.daily_price_kopeks ?? tariff.price_per_day_kopeks ?? 0; const originalDailyPrice = tariff.original_daily_price_kopeks || 0; if (dailyPrice > 0) { const promoDaily = applyPromoDiscount( dailyPrice, originalDailyPrice > dailyPrice ? originalDailyPrice : undefined, ); return ( {formatPrice(promoDaily.price)} {promoDaily.original && promoDaily.original > promoDaily.price && ( {formatPrice(promoDaily.original)} )} {t('subscription.tariff.perDay')} {promoDaily.percent && promoDaily.percent > 0 && ( -{promoDaily.percent}% )} ); } if (tariff.periods.length > 0) { const firstPeriod = tariff.periods[0]; const promoPeriod = applyPromoDiscount( firstPeriod?.price_kopeks || 0, firstPeriod?.original_price_kopeks, ); return ( {t('subscription.from')} {formatPrice(promoPeriod.price)} {promoPeriod.original && promoPeriod.original > promoPeriod.price && ( {formatPrice(promoPeriod.original)} )} {promoPeriod.percent && promoPeriod.percent > 0 && ( -{promoPeriod.percent}% )} ); } return ( {t('subscription.tariff.flexiblePayment')} ); })()}
{/* Action Buttons */}
{isCurrentTariff ? ( subscription?.is_daily ? (
{t('subscription.currentTariff')}
) : ( ) ) : isLegacySubscription ? ( ) : canSwitch ? ( ) : ( )}
); })}
) : ( selectedTariff && ( /* Tariff Purchase Form */

{selectedTariff.name}

{/* Tariff Info */}
{t('subscription.traffic')}: {selectedTariff.traffic_limit_label}
{t('subscription.devices')}: {selectedTariff.device_limit === 0 ? '∞' : selectedTariff.device_limit} {selectedTariff.extra_devices_count > 0 && ( (+{selectedTariff.extra_devices_count}) )}
{/* Daily Tariff Purchase */} {selectedTariff.is_daily || (selectedTariff.daily_price_kopeks && selectedTariff.daily_price_kopeks > 0) ? (
{t('subscription.dailyPurchase.costPerDay')}
{formatPrice(selectedTariff.daily_price_kopeks || 0)}
{t('subscription.dailyPurchase.chargedDaily')}
{t('subscription.dailyPurchase.canPause')}
{t('subscription.dailyPurchase.pausedOnLowBalance')}
{(() => { const dailyPrice = selectedTariff.daily_price_kopeks || 0; const hasEnoughBalance = purchaseOptions && dailyPrice <= purchaseOptions.balance_kopeks; return (
{purchaseOptions && !hasEnoughBalance && ( )} {tariffPurchaseMutation.isError && !getInsufficientBalanceError(tariffPurchaseMutation.error) && (
{getErrorMessage(tariffPurchaseMutation.error)}
)} {tariffPurchaseMutation.isError && getInsufficientBalanceError(tariffPurchaseMutation.error) && (
)}
); })()}
) : ( <> {/* Period Selection for non-daily tariffs */}
{t('subscription.selectPeriod')}
{selectedTariff.periods.length > 0 && !useCustomDays && (
{selectedTariff.periods.map((period) => { const promoPeriod = applyPromoDiscount( period.price_kopeks, period.original_price_kopeks, ); const displayDiscount = promoPeriod.percent; const displayOriginal = promoPeriod.original; const displayPrice = promoPeriod.price; const displayPerMonth = displayPrice !== period.price_kopeks ? Math.round(displayPrice / Math.max(1, period.days / 30)) : period.price_per_month_kopeks; return ( ); })}
)} {/* No periods available fallback */} {selectedTariff.periods.length === 0 && !useCustomDays && !( selectedTariff.custom_days_enabled && (selectedTariff.price_per_day_kopeks ?? 0) > 0 ) && (
{t('subscription.noPeriodsAvailable')}
{t('subscription.noPeriodsAvailableHint')}
)} {/* Custom days option */} {selectedTariff.custom_days_enabled && (selectedTariff.price_per_day_kopeks ?? 0) > 0 && (
{t('subscription.customDays.title')}
{useCustomDays && (
setCustomDays(parseInt(e.target.value))} className="flex-1 accent-accent-500" /> setCustomDays( Math.max( selectedTariff.min_days ?? 1, Math.min( selectedTariff.max_days ?? 365, parseInt(e.target.value) || (selectedTariff.min_days ?? 1), ), ), ) } className="w-20 rounded-lg border border-dark-600 bg-dark-700 px-3 py-2 text-center text-dark-100" />
{(() => { const basePrice = customDays * (selectedTariff.price_per_day_kopeks ?? 0); const existingOriginal = selectedTariff.original_price_per_day_kopeks && selectedTariff.original_price_per_day_kopeks > (selectedTariff.price_per_day_kopeks ?? 0) ? customDays * selectedTariff.original_price_per_day_kopeks : undefined; const promoCustom = applyPromoDiscount( basePrice, existingOriginal, ); return (
{t('subscription.days', { count: customDays })} ×{' '} {formatPrice(selectedTariff.price_per_day_kopeks ?? 0)}/ {t('subscription.customDays.perDay')}
{formatPrice(promoCustom.price)} {promoCustom.original && promoCustom.original > promoCustom.price && ( <> {formatPrice(promoCustom.original)} -{promoCustom.percent}% )}
); })()}
)}
)}
{/* Custom traffic option */} {selectedTariff.custom_traffic_enabled && (selectedTariff.traffic_price_per_gb_kopeks ?? 0) > 0 && (
{t('subscription.customTraffic.label')}
{t('subscription.customTraffic.selectVolume')}
{!useCustomTraffic && (
{t('subscription.customTraffic.default', { label: selectedTariff.traffic_limit_label, })}
)} {useCustomTraffic && (
setCustomTrafficGb(parseInt(e.target.value))} className="flex-1 accent-accent-500" />
setCustomTrafficGb( Math.max( selectedTariff.min_traffic_gb ?? 1, Math.min( selectedTariff.max_traffic_gb ?? 1000, parseInt(e.target.value) || (selectedTariff.min_traffic_gb ?? 1), ), ), ) } className="w-20 rounded-lg border border-dark-600 bg-dark-700 px-3 py-2 text-center text-dark-100" /> {t('common.units.gb')}
{customTrafficGb} {t('common.units.gb')} ×{' '} {formatPrice(selectedTariff.traffic_price_per_gb_kopeks ?? 0)}/ {t('common.units.gb')} + {formatPrice( customTrafficGb * (selectedTariff.traffic_price_per_gb_kopeks ?? 0), )}
)}
)} {/* Summary & Purchase */} {(selectedTariffPeriod || useCustomDays) && (
{(() => { const basePeriodPrice = useCustomDays ? customDays * (selectedTariff.price_per_day_kopeks ?? 0) : selectedTariffPeriod?.price_kopeks || 0; const existingPeriodOriginal = useCustomDays ? selectedTariff.original_price_per_day_kopeks && selectedTariff.original_price_per_day_kopeks > (selectedTariff.price_per_day_kopeks ?? 0) ? customDays * selectedTariff.original_price_per_day_kopeks : undefined : selectedTariffPeriod?.original_price_kopeks && selectedTariffPeriod.original_price_kopeks > selectedTariffPeriod.price_kopeks ? selectedTariffPeriod.original_price_kopeks : undefined; const promoPeriod = applyPromoDiscount( basePeriodPrice, existingPeriodOriginal, ); const trafficPrice = useCustomTraffic && selectedTariff.custom_traffic_enabled ? customTrafficGb * (selectedTariff.traffic_price_per_gb_kopeks ?? 0) : 0; const totalPrice = promoPeriod.price + trafficPrice; const originalTotal = promoPeriod.original ? promoPeriod.original + trafficPrice : null; return ( <>
{useCustomDays ? (
{t('subscription.stepPeriod')}:{' '} {t('subscription.days', { count: customDays })}
{formatPrice(promoPeriod.price)} {promoPeriod.original && promoPeriod.original > promoPeriod.price && ( {formatPrice(promoPeriod.original)} )}
) : ( selectedTariffPeriod && ( <> {(selectedTariffPeriod.extra_devices_count ?? 0) > 0 && selectedTariffPeriod.base_tariff_price_kopeks ? ( <>
{t('subscription.baseTariff')}:{' '} {selectedTariffPeriod.label} {formatPrice( selectedTariffPeriod.base_tariff_price_kopeks, )}
{t('subscription.extraDevices')} ( {selectedTariffPeriod.extra_devices_count}) + {formatPrice( selectedTariffPeriod.extra_devices_cost_kopeks ?? 0, )}
) : (
{t('subscription.summary.period', { label: selectedTariffPeriod.label, })}
{formatPrice(promoPeriod.price)} {promoPeriod.original && promoPeriod.original > promoPeriod.price && ( {formatPrice(promoPeriod.original)} )}
)} ) )} {useCustomTraffic && selectedTariff.custom_traffic_enabled && (
{t('subscription.summary.traffic', { gb: customTrafficGb })} +{formatPrice(trafficPrice)}
)}
{promoPeriod.percent && (
{t('promo.discountApplied')} -{promoPeriod.percent}%
)}
{t('subscription.total')}
{formatPrice(totalPrice)} {originalTotal && (
{formatPrice(originalTotal)}
)}
); })()} {tariffPurchaseMutation.isError && !getInsufficientBalanceError(tariffPurchaseMutation.error) && (
{getErrorMessage(tariffPurchaseMutation.error)}
)} {tariffPurchaseMutation.isError && getInsufficientBalanceError(tariffPurchaseMutation.error) && (
)}
)} )}
) )}
)} {/* Purchase/Extend Section - Classic Mode */} {classicOptions && classicOptions.periods.length > 0 && (

{subscription && !subscription.is_trial ? t('subscription.extend') : t('subscription.getSubscription')}

{!showPurchaseForm && ( )}
{showPurchaseForm && (
{/* Step Indicator */}
{t('subscription.step', { current: currentStepIndex + 1, total: steps.length })}
{steps.map((step, idx) => (
))}
{getStepLabel(currentStep)}
{/* Step: Period Selection */} {currentStep === 'period' && classicOptions && (
{classicOptions.periods.map((period) => { const promoPeriod = applyPromoDiscount( period.price_kopeks, period.original_price_kopeks, ); return ( ); })}
)} {/* Step: Traffic Selection */} {currentStep === 'traffic' && selectedPeriod?.traffic.options && (
{selectedPeriod.traffic.options.map((option) => { const promoTraffic = applyPromoDiscount( option.price_kopeks, option.original_price_kopeks, ); return ( ); })}
)} {/* Step: Server Selection */} {currentStep === 'servers' && selectedPeriod?.servers.options && (
{selectedPeriod.servers.options .filter((server) => { if (!server.is_available) return false; if (subscription?.is_trial && server.name.toLowerCase().includes('trial')) { return false; } return true; }) .map((server) => { const promoServer = applyPromoDiscount( server.price_kopeks, server.original_price_kopeks, ); return ( ); })}
)} {/* Step: Device Selection */} {currentStep === 'devices' && selectedPeriod && (
{selectedDevices}
{t('subscription.devices')}
{t('subscription.devicesFree', { count: selectedPeriod.devices.min })}
{selectedPeriod.devices.max > selectedPeriod.devices.min && (
{formatPrice(selectedPeriod.devices.price_per_device_kopeks)}{' '} {t('subscription.perExtraDevice')}
)}
)} {/* Step: Confirm */} {currentStep === 'confirm' && (
{previewLoading ? (
) : preview ? (
{activeDiscount?.is_active && activeDiscount.discount_percent && (
{t('promo.discountApplied')} -{activeDiscount.discount_percent}%
)} {preview.breakdown.map((item, idx) => (
{item.label} {item.value}
))} {(() => { const promoTotal = applyPromoDiscount( preview.total_price_kopeks, preview.original_price_kopeks, ); return (
{t('subscription.total')}
{formatPrice(promoTotal.price)}
{promoTotal.original && promoTotal.original > promoTotal.price && (
{formatPrice(promoTotal.original)}
)}
); })()} {preview.discount_label && (
{preview.discount_label}
)} {!preview.can_purchase && (preview.missing_amount_kopeks > 0 ? ( ) : preview.status_message ? (
{preview.status_message}
) : null)}
) : null}
)} {/* Navigation Buttons */}
{!isFirstStep && ( )} {isFirstStep && ( )} {!isLastStep ? ( ) : ( )}
{purchaseMutation.isError && (
{getErrorMessage(purchaseMutation.error)}
)}
)}
)} {/* No options available fallback */} {purchaseOptions && !optionsLoading && !(isTariffsMode && tariffs.length > 0) && !(classicOptions && classicOptions.periods.length > 0) && (

{t('subscription.noOptionsAvailable', 'Нет доступных вариантов подписки')}

)}
); }