{t('subscription.title')}
{/* Current Subscription */}
{subscription ? (
(() => {
const usedPercent =
trafficData?.traffic_used_percent ?? subscription.traffic_used_percent;
const usedGb = trafficData?.traffic_used_gb ?? subscription.traffic_used_gb;
const isUnlimited =
(trafficData?.is_unlimited ?? false) || subscription.traffic_limit_gb === 0;
const zone = getTrafficZone(usedPercent);
const connectedDevices = devicesData?.total ?? 0;
const formattedDate = new Date(subscription.end_date).toLocaleDateString();
const daysLeft = subscription.days_left;
return (
{/* Trial shimmer border */}
{subscription.is_trial && (
)}
{/* Background glow */}
{/* ─── Header ─── */}
{/* Zone indicator */}
{isUnlimited ? t('dashboard.unlimited') : t(zone.labelKey)}
{subscription.is_trial && (
{t('subscription.trialStatus')}
)}
{/* Plan name */}
{subscription.tariff_name || t('subscription.currentPlan')}
{/* Status badge */}
{subscription.is_trial
? t('subscription.trialStatus')
: subscription.is_active
? t('subscription.active')
: t('subscription.expired')}
{/* ─── Trial Info Banner ─── */}
{subscription.is_trial && subscription.is_active && (
{t('subscription.trialInfo.title')}
{t('subscription.trialInfo.description')}
{subscription.days_left > 0
? t('subscription.days', { count: subscription.days_left })
: `${subscription.hours_left}${t('subscription.hours')} ${subscription.minutes_left}${t('subscription.minutes')}`}
{t('subscription.trialInfo.remaining')}
{subscription.traffic_limit_gb || '∞'} {t('common.units.gb')}
{t('subscription.traffic')}
{subscription.device_limit}
{t('subscription.devices')}
)}
{/* ─── Traffic Progress ─── */}
{t('subscription.traffic')}
{isUnlimited
? formatTraffic(usedGb)
: `${formatTraffic(usedGb)} / ${formatTraffic(subscription.traffic_limit_gb)}`}
refreshTrafficMutation.mutate()}
disabled={refreshTrafficMutation.isPending || trafficRefreshCooldown > 0}
className="flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium text-dark-50/30 transition-colors hover:bg-dark-50/[0.05] hover:text-dark-50/50 disabled:cursor-not-allowed disabled:opacity-50"
>
{trafficRefreshCooldown > 0
? `${trafficRefreshCooldown}s`
: t('common.refresh')}
{subscription.traffic_reset_mode &&
subscription.traffic_reset_mode !== 'NO_RESET' && (
{t(`subscription.trafficReset.${subscription.traffic_reset_mode}`)}
)}
{/* ─── Connect Device Button ─── */}
{subscription.subscription_url && (
navigate('/connection')}
className="mb-5 flex w-full items-center gap-3.5 rounded-[14px] p-3.5 text-left transition-shadow duration-300"
style={{ fontFamily: 'inherit' }}
>
{t('dashboard.connectDevice')}
{t('dashboard.devicesOfMax', {
used: connectedDevices,
max: subscription.device_limit,
})}
{Array.from({ length: subscription.device_limit }, (_, i) => (
))}
)}
{/* ─── Subscription URL ─── */}
{subscription.subscription_url && !subscription.hide_subscription_link && (
{subscription.subscription_url}
{copied ? : }
)}
{/* ─── Stats Row ─── */}
{/* Days left */}
{t('dashboard.remaining')}
{daysLeft > 0
? daysLeft
: subscription.hours_left > 0
? `${subscription.hours_left}h`
: `${subscription.minutes_left}m`}
{daysLeft > 0 ? t('subscription.daysShort') : ''}
{/* Devices */}
{t('subscription.devices')}
{connectedDevices}
/ {subscription.device_limit}
{/* Expires */}
{t('subscription.expiresAt')}
{formattedDate}
{/* Traffic */}
{t('subscription.traffic')}
{isUnlimited ? '∞' : `${usedPercent.toFixed(0)}%`}
{/* ─── Locations ─── */}
{subscription.servers && subscription.servers.length > 0 && (
{t('subscription.locationsLabel')}
{subscription.servers.map((server) => (
{server.country_code && (
{getFlagEmoji(server.country_code)}
)}
{server.name}
))}
)}
{/* ─── Purchased Traffic Packages ─── */}
{subscription.traffic_purchases && subscription.traffic_purchases.length > 0 && (
{t('subscription.purchasedTraffic')}
{subscription.traffic_purchases.map((purchase) => (
{purchase.traffic_gb} {t('common.units.gb')}
{purchase.days_remaining === 0
? t('subscription.expired')
: t('subscription.days', { count: purchase.days_remaining })}
{t('subscription.trafficResetAt')}:{' '}
{new Date(purchase.expires_at).toLocaleDateString(undefined, {
day: '2-digit',
month: '2-digit',
year: 'numeric',
})}
{new Date(purchase.created_at).toLocaleDateString()}
{new Date(purchase.expires_at).toLocaleDateString()}
))}
)}
{/* ─── Autopay Toggle ─── */}
{!subscription.is_trial && !subscription.is_daily && (
{t('subscription.autoRenewal')}
{t('subscription.daysBeforeExpiry', {
count: subscription.autopay_days_before,
})}
autopayMutation.mutate(!subscription.autopay_enabled)}
disabled={autopayMutation.isPending}
className="relative h-7 w-[52px] rounded-full transition-all duration-300"
style={{
background: subscription.autopay_enabled ? zone.mainHex : g.textGhost,
}}
>
)}
);
})()
) : (
{t('subscription.noSubscription')}
)}
{/* Daily Subscription Pause */}
{subscription && subscription.is_daily && !subscription.is_trial && (
{t('subscription.pause.title')}
{subscription.is_daily_paused
? t('subscription.pause.paused')
: t('subscription.pause.active')}
pauseMutation.mutate()}
disabled={pauseMutation.isPending}
className="rounded-[10px] px-4 py-2 text-sm font-semibold transition-all duration-300"
style={{
background: subscription.is_daily_paused
? 'rgba(62,219,176,0.12)'
: 'rgba(255,184,0,0.12)',
border: subscription.is_daily_paused
? '1px solid rgba(62,219,176,0.2)'
: '1px solid rgba(255,184,0,0.2)',
color: subscription.is_daily_paused ? '#3EDBB0' : '#FFB800',
}}
>
{pauseMutation.isPending ? (
) : subscription.is_daily_paused ? (
t('subscription.pause.resumeBtn')
) : (
t('subscription.pause.pauseBtn')
)}
{/* Pause mutation error */}
{pauseMutation.isError &&
(() => {
const balanceError = getInsufficientBalanceError(pauseMutation.error);
if (balanceError) {
const missingAmount = balanceError.required - balanceError.balance;
return (
);
}
return (
{getErrorMessage(pauseMutation.error)}
);
})()}
{/* Paused info or Next charge progress bar */}
{subscription.is_daily_paused ? (
⏸️
{t('subscription.pause.pausedInfo')}
{t('subscription.pause.pausedDescription')}{' '}
{new Date(subscription.end_date).toLocaleDateString()} (
{t('subscription.pause.days', { count: subscription.days_left })})
) : (
subscription.next_daily_charge_at &&
(() => {
const now = new Date();
const nextChargeStr = subscription.next_daily_charge_at.endsWith('Z')
? subscription.next_daily_charge_at
: subscription.next_daily_charge_at + 'Z';
const nextCharge = new Date(nextChargeStr);
const totalMs = 24 * 60 * 60 * 1000;
const remainingMs = Math.max(0, nextCharge.getTime() - now.getTime());
const elapsedMs = totalMs - remainingMs;
const progress = Math.min(100, (elapsedMs / totalMs) * 100);
const hours = Math.floor(remainingMs / (1000 * 60 * 60));
const minutes = Math.floor((remainingMs % (1000 * 60 * 60)) / (1000 * 60));
return (
{t('subscription.pause.nextCharge')}
{hours > 0
? `${hours}${t('subscription.pause.hours')} ${minutes}${t('subscription.pause.minutes')}`
: `${minutes}${t('subscription.pause.minutes')}`}
{subscription.daily_price_kopeks && (
{t('subscription.pause.willBeCharged')}:{' '}
{formatPrice(subscription.daily_price_kopeks)}
)}
);
})()
)}
)}
{/* Additional Options (Buy Devices) */}
{subscription && subscription.is_active && !subscription.is_trial && (
{t('subscription.additionalOptions.title')}
{/* Buy Devices */}
{!showDeviceTopup ? (
setShowDeviceTopup(true)}
className="w-full rounded-xl border border-dark-700/50 bg-dark-800/50 p-4 text-left transition-colors hover:border-dark-600"
>
{t('subscription.additionalOptions.buyDevices')}
{t('subscription.additionalOptions.currentDeviceLimit', {
count: subscription.device_limit,
})}
) : (
{t('subscription.buyDevices')}
setShowDeviceTopup(false)}
className="text-sm text-dark-400 hover:text-dark-200"
>
✕
{/* Check if completely unavailable (no subscription, price not set, etc.) */}
{devicePriceData?.available === false && !devicePriceData?.max_device_limit ? (
{devicePriceData.reason || t('subscription.additionalOptions.devicesUnavailable')}
) : (
{/* Device selector - show even at max limit */}
setDevicesToAdd(Math.max(1, devicesToAdd - 1))}
disabled={devicesToAdd <= 1}
className="btn-secondary flex h-12 w-12 items-center justify-center !p-0 text-2xl"
>
-
{devicesToAdd}
{t('subscription.additionalOptions.devicesUnit')}
setDevicesToAdd(devicesToAdd + 1)}
disabled={
devicePriceData?.max_device_limit
? (devicePriceData.current_device_limit || 0) + devicesToAdd >=
devicePriceData.max_device_limit
: false
}
className="btn-secondary flex h-12 w-12 items-center justify-center !p-0 text-2xl"
>
+
{/* Show limit info when at or near max */}
{devicePriceData?.max_device_limit && (
{t('subscription.additionalOptions.currentDeviceLimit', {
count: devicePriceData.current_device_limit || subscription.device_limit,
})}{' '}
/{' '}
{t('subscription.additionalOptions.maxDevices', {
count: devicePriceData.max_device_limit,
})}
)}
{/* Show reason if can't add requested amount */}
{devicePriceData?.available === false && devicePriceData?.reason && (
{devicePriceData.reason}
)}
{/* Price info - only when available */}
{devicePriceData?.available && devicePriceData.price_per_device_label && (
{/* Show original price with strikethrough if discount */}
{devicePriceData.discount_percent &&
devicePriceData.discount_percent > 0 ? (
{formatPrice(devicePriceData.original_price_per_device_kopeks || 0)}
{devicePriceData.price_per_device_label}
) : (
devicePriceData.price_per_device_label
)}
/{t('subscription.perDevice').replace('/ ', '')} (
{t('subscription.days', { count: devicePriceData.days_left })})
{/* Discount badge */}
{devicePriceData.discount_percent && devicePriceData.discount_percent > 0 && (
-{devicePriceData.discount_percent}%
)}
{/* Total price - show as free if 100% discount or 0 */}
{devicePriceData.total_price_kopeks === 0 ? (
{t('subscription.switchTariff.free')}
) : (
{/* Show original total with strikethrough if discount */}
{devicePriceData.discount_percent &&
devicePriceData.discount_percent > 0 &&
devicePriceData.base_total_price_kopeks && (
{formatPrice(devicePriceData.base_total_price_kopeks)}
)}
{devicePriceData.total_price_label}
)}
)}
{devicePriceData?.available &&
purchaseOptions &&
devicePriceData.total_price_kopeks &&
devicePriceData.total_price_kopeks > purchaseOptions.balance_kopeks && (
{
await subscriptionApi.saveDevicesCart(devicesToAdd);
}}
/>
)}
devicePurchaseMutation.mutate()}
disabled={
devicePurchaseMutation.isPending ||
!devicePriceData?.available ||
!!(
devicePriceData?.total_price_kopeks &&
purchaseOptions &&
devicePriceData.total_price_kopeks > purchaseOptions.balance_kopeks
)
}
className="btn-primary w-full py-3"
>
{devicePurchaseMutation.isPending ? (
) : (
t('subscription.additionalOptions.buy')
)}
{devicePurchaseMutation.isError && (
{getErrorMessage(devicePurchaseMutation.error)}
)}
)}
)}
{/* Reduce Devices */}
{!showDeviceReduction ? (
setShowDeviceReduction(true)}
className="w-full rounded-xl border border-dark-700/50 bg-dark-800/50 p-4 text-left transition-colors hover:border-dark-600"
>
{t('subscription.additionalOptions.reduceDevices')}
{t('subscription.additionalOptions.reduceDevicesDescription')}
) : (
{t('subscription.additionalOptions.reduceDevicesTitle')}
setShowDeviceReduction(false)}
className="text-sm text-dark-400 hover:text-dark-200"
>
✕
{deviceReductionInfo?.available === false ? (
{deviceReductionInfo.reason ||
t('subscription.additionalOptions.reduceUnavailable')}
) : deviceReductionInfo ? (
{/* Device limit selector */}
setTargetDeviceLimit(
Math.max(
Math.max(
deviceReductionInfo.min_device_limit,
deviceReductionInfo.connected_devices_count,
),
targetDeviceLimit - 1,
),
)
}
disabled={
targetDeviceLimit <=
Math.max(
deviceReductionInfo.min_device_limit,
deviceReductionInfo.connected_devices_count,
)
}
className="btn-secondary flex h-12 w-12 items-center justify-center !p-0 text-2xl"
>
-
{targetDeviceLimit}
{t('subscription.additionalOptions.devicesUnit')}
setTargetDeviceLimit(
Math.min(
deviceReductionInfo.current_device_limit - 1,
targetDeviceLimit + 1,
),
)
}
disabled={targetDeviceLimit >= deviceReductionInfo.current_device_limit - 1}
className="btn-secondary flex h-12 w-12 items-center justify-center !p-0 text-2xl"
>
+
{/* Info */}
{t('subscription.additionalOptions.currentDeviceLimit', {
count: deviceReductionInfo.current_device_limit,
})}
{t('subscription.additionalOptions.minDeviceLimit', {
count: deviceReductionInfo.min_device_limit,
})}
{t('subscription.additionalOptions.connectedDevices', {
count: deviceReductionInfo.connected_devices_count,
})}
{/* Warning if connected devices block reduction */}
{deviceReductionInfo.connected_devices_count >
deviceReductionInfo.min_device_limit && (
{t('subscription.additionalOptions.disconnectDevicesFirst', {
count: deviceReductionInfo.connected_devices_count,
})}
)}
{/* New limit preview */}
{t('subscription.additionalOptions.newDeviceLimit', {
count: targetDeviceLimit,
})}
deviceReductionMutation.mutate()}
disabled={
deviceReductionMutation.isPending ||
targetDeviceLimit >= deviceReductionInfo.current_device_limit ||
targetDeviceLimit < deviceReductionInfo.min_device_limit ||
targetDeviceLimit < deviceReductionInfo.connected_devices_count
}
className="btn-primary w-full py-3"
>
{deviceReductionMutation.isPending ? (
{t('subscription.additionalOptions.reducing')}
) : (
t('subscription.additionalOptions.reduce')
)}
{deviceReductionMutation.isError && (
{getErrorMessage(deviceReductionMutation.error)}
)}
) : (
)}
)}
{/* Buy Traffic */}
{subscription.traffic_limit_gb > 0 && (
{!showTrafficTopup ? (
setShowTrafficTopup(true)}
className="w-full rounded-xl border border-dark-700/50 bg-dark-800/50 p-4 text-left transition-colors hover:border-dark-600"
>
{t('subscription.additionalOptions.buyTraffic')}
{t('subscription.additionalOptions.currentTrafficLimit', {
limit: subscription.traffic_limit_gb,
used: subscription.traffic_used_gb.toFixed(1),
})}
) : (
{t('subscription.additionalOptions.buyTrafficTitle')}
{
setShowTrafficTopup(false);
setSelectedTrafficPackage(null);
}}
className="text-sm text-dark-400 hover:text-dark-200"
>
✕
⚠️ {t('subscription.additionalOptions.trafficWarning')}
{!trafficPackages || trafficPackages.length === 0 ? (
{t('subscription.additionalOptions.trafficUnavailable')}
) : (
{trafficPackages.map((pkg) => (
setSelectedTrafficPackage(pkg.gb)}
className={`rounded-xl border p-4 text-center transition-all ${
selectedTrafficPackage === pkg.gb
? 'border-accent-500 bg-accent-500/10'
: 'border-dark-700/50 bg-dark-800/50 hover:border-dark-600'
}`}
>
{pkg.is_unlimited
? '♾️ ' + t('subscription.additionalOptions.unlimited')
: `${pkg.gb} ${t('common.units.gb')}`}
{/* Discount badge */}
{pkg.discount_percent && pkg.discount_percent > 0 && (
-{pkg.discount_percent}%
)}
{/* Price with original strikethrough if discount */}
{pkg.discount_percent &&
pkg.discount_percent > 0 &&
pkg.base_price_kopeks ? (
<>
{formatPrice(pkg.base_price_kopeks)}
{formatPrice(pkg.price_kopeks)}
>
) : (
formatPrice(pkg.price_kopeks)
)}
))}
{selectedTrafficPackage !== null &&
(() => {
const selectedPkg = trafficPackages.find(
(p) => p.gb === selectedTrafficPackage,
);
const hasEnoughBalance =
!selectedPkg ||
!purchaseOptions ||
selectedPkg.price_kopeks <= purchaseOptions.balance_kopeks;
const missingAmount =
selectedPkg && purchaseOptions
? selectedPkg.price_kopeks - purchaseOptions.balance_kopeks
: 0;
return (
<>
{!hasEnoughBalance && missingAmount > 0 && (
{
await subscriptionApi.saveTrafficCart(selectedTrafficPackage);
}}
/>
)}
trafficPurchaseMutation.mutate(selectedTrafficPackage)
}
disabled={trafficPurchaseMutation.isPending || !hasEnoughBalance}
className="btn-primary w-full py-3"
>
{trafficPurchaseMutation.isPending ? (
) : selectedPkg?.is_unlimited ? (
t('subscription.additionalOptions.buyUnlimited')
) : (
t('subscription.additionalOptions.buyTrafficGb', {
gb: selectedTrafficPackage,
})
)}
>
);
})()}
{trafficPurchaseMutation.isError && (
{getErrorMessage(trafficPurchaseMutation.error)}
)}
)}
)}
)}
{/* Server Management - only in classic mode */}
{!isTariffsMode && (
{!showServerManagement ? (
setShowServerManagement(true)}
className="w-full rounded-xl border border-dark-700/50 bg-dark-800/50 p-4 text-left transition-colors hover:border-dark-600"
>
{t('subscription.additionalOptions.manageServers')}
{t('subscription.servers', { count: subscription.servers?.length || 0 })}
) : (
{t('subscription.additionalOptions.manageServersTitle')}
{
setShowServerManagement(false);
setSelectedServersToUpdate([]);
}}
className="text-sm text-dark-400 hover:text-dark-200"
>
✕
{countriesLoading ? (
) : countriesData && countriesData.countries.length > 0 ? (
{t('subscription.serverManagement.statusLegend')}
{countriesData.discount_percent > 0 && (
🎁{' '}
{t('subscription.serverManagement.discountBanner', {
percent: countriesData.discount_percent,
})}
)}
{countriesData.countries
.filter((country) => country.is_available || country.is_connected)
.map((country) => {
const isCurrentlyConnected = country.is_connected;
const isSelected = selectedServersToUpdate.includes(country.uuid);
const willBeAdded = !isCurrentlyConnected && isSelected;
const willBeRemoved = isCurrentlyConnected && !isSelected;
return (
{
if (isSelected) {
setSelectedServersToUpdate((prev) =>
prev.filter((u) => u !== country.uuid),
);
} else {
setSelectedServersToUpdate((prev) => [...prev, country.uuid]);
}
}}
disabled={!country.is_available && !isCurrentlyConnected}
className={`flex w-full items-center justify-between rounded-xl border p-3 text-left transition-all ${
isSelected
? willBeAdded
? 'border-success-500 bg-success-500/10'
: 'border-accent-500 bg-accent-500/10'
: willBeRemoved
? 'border-error-500/50 bg-error-500/5'
: 'border-dark-700/50 bg-dark-800/50 hover:border-dark-600'
} ${!country.is_available && !isCurrentlyConnected ? 'cursor-not-allowed opacity-50' : ''}`}
>
{willBeAdded
? '➕'
: willBeRemoved
? '➖'
: isSelected
? '✅'
: '⚪'}
{country.name}
{country.has_discount && !isCurrentlyConnected && (
-{country.discount_percent}%
)}
{willBeAdded && (
+{formatPrice(country.price_kopeks)}{' '}
{t('subscription.serverManagement.forDays', {
days: countriesData.days_left,
})}
{country.has_discount && (
{formatPrice(
Math.round(
(country.base_price_kopeks *
countriesData.days_left) /
30,
),
)}
)}
)}
{!willBeAdded && !isCurrentlyConnected && (
{formatPrice(country.price_per_month_kopeks)}
{t('subscription.serverManagement.perMonth')}
{country.has_discount && (
{formatPrice(country.base_price_kopeks)}
)}
)}
{!country.is_available && !isCurrentlyConnected && (
{t('subscription.serverManagement.unavailable')}
)}
{country.country_code && (
{getFlagEmoji(country.country_code)}
)}
);
})}
{(() => {
const currentConnected = countriesData.countries
.filter((c) => c.is_connected)
.map((c) => c.uuid);
const added = selectedServersToUpdate.filter(
(u) => !currentConnected.includes(u),
);
const removed = currentConnected.filter(
(u) => !selectedServersToUpdate.includes(u),
);
const hasChanges = added.length > 0 || removed.length > 0;
// Calculate cost for added servers
const addedServers = countriesData.countries.filter((c) =>
added.includes(c.uuid),
);
const totalCost = addedServers.reduce((sum, s) => sum + s.price_kopeks, 0);
const hasEnoughBalance =
!purchaseOptions || totalCost <= purchaseOptions.balance_kopeks;
const missingAmount = purchaseOptions
? totalCost - purchaseOptions.balance_kopeks
: 0;
return hasChanges ? (
{added.length > 0 && (
{t('subscription.serverManagement.toAdd')}
{' '}
{addedServers.map((s) => s.name).join(', ')}
)}
{removed.length > 0 && (
{t('subscription.serverManagement.toDisconnect')}
{' '}
{countriesData.countries
.filter((c) => removed.includes(c.uuid))
.map((s) => s.name)
.join(', ')}
)}
{totalCost > 0 && (
{t('subscription.serverManagement.paymentProrated')}
{formatPrice(totalCost)}
)}
{totalCost > 0 && !hasEnoughBalance && missingAmount > 0 && (
)}
updateCountriesMutation.mutate(selectedServersToUpdate)
}
disabled={
updateCountriesMutation.isPending ||
selectedServersToUpdate.length === 0 ||
(totalCost > 0 && !hasEnoughBalance)
}
className="btn-primary w-full py-3"
>
{updateCountriesMutation.isPending ? (
) : (
t('subscription.serverManagement.applyChanges')
)}
) : (
{t('subscription.serverManagement.selectServersHint')}
);
})()}
{updateCountriesMutation.isError && (
{getErrorMessage(updateCountriesMutation.error)}
)}
) : (
{t('subscription.serverManagement.noServersAvailable')}
)}
)}
)}
)}
{/* My Devices Section */}
{subscription && (
{t('subscription.myDevices')}
{devicesData && devicesData.devices.length > 0 && (
{
if (confirm(t('subscription.confirmDeleteAllDevices'))) {
deleteAllDevicesMutation.mutate();
}
}}
disabled={deleteAllDevicesMutation.isPending}
className="text-[11px] font-medium transition-colors"
style={{ color: '#FF3B5C' }}
>
{t('subscription.deleteAllDevices')}
)}
{devicesLoading ? (
) : devicesData && devicesData.devices.length > 0 ? (
{devicesData.total} /{' '}
{t('subscription.devices', { count: devicesData.device_limit })}
{devicesData.devices.map((device) => (
{device.device_model || device.platform}
{device.platform}
{
if (confirm(t('subscription.confirmDeleteDevice'))) {
deleteDeviceMutation.mutate(device.hwid);
}
}}
disabled={deleteDeviceMutation.isPending}
className="p-2 transition-colors"
style={{ color: g.textFaint }}
title={t('subscription.deleteDevice')}
>
))}
) : (
{t('subscription.noDevices')}
)}
)}
{/* Tariffs Section - Combined Purchase/Extend/Switch like MiniApp */}
{isTariffsMode && tariffs.length > 0 && (
{subscription?.is_daily && !subscription?.is_trial
? t('subscription.switchTariff.title')
: subscription && !subscription.is_trial
? t('subscription.extend')
: t('subscription.getSubscription')}
{/* Trial upgrade prompt */}
{subscription?.is_trial && (
{t('subscription.trialUpgrade.title')}
{t('subscription.trialUpgrade.description')}
)}
{/* Expired subscription notice - prompt to purchase new tariff */}
{isTariffsMode &&
purchaseOptions &&
'subscription_is_expired' in purchaseOptions &&
purchaseOptions.subscription_is_expired && (
{t('subscription.expiredBanner.title')}
{t('subscription.expiredBanner.selectTariff')}
)}
{/* Legacy subscription notice - if user has subscription without tariff */}
{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')}
setSwitchTariffId(null)}
className="text-sm text-dark-400 hover:text-dark-200"
>
✕
{switchPreviewLoading ? (
) : (
switchPreview &&
(() => {
// Find the target tariff to get daily price info
const targetTariff = tariffs.find((t) => t.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}
{/* Daily tariff info */}
{isDailyTariff && (
{t('subscription.switchTariff.dailyPayment')}
{formatPrice(dailyPrice)}
{t('subscription.switchTariff.dailyChargeDescription')}
)}
{t('subscription.switchTariff.upgradeCost')}
{/* Discount badge */}
{switchPreview.discount_percent && switchPreview.discount_percent > 0 && (
-{switchPreview.discount_percent}%
)}
{/* Show original price with strikethrough if discount */}
{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.mutate(switchTariffId)}
disabled={switchTariffMutation.isPending || !switchPreview.can_switch}
className="btn-primary w-full py-2.5"
>
{switchTariffMutation.isPending ? (
) : (
t('subscription.switchTariff.switch')
)}
{/* Show error (except subscription_expired which redirects to purchase) */}
{switchTariffMutation.isError &&
(() => {
const detail =
switchTariffMutation.error instanceof AxiosError
? switchTariffMutation.error.response?.data?.detail
: null;
// Skip displaying if it's subscription_expired (handled by redirect)
if (
typeof detail === 'object' &&
detail?.error_code === 'subscription_expired'
) {
return null;
}
return (
{getErrorMessage(switchTariffMutation.error)}
);
})()}
>
);
})()
)}
)}
{!showTariffPurchase ? (
<>
{/* Promo group discount banner */}
{tariffs.some((t) => t.promo_group_name) && (
{t('subscription.promoGroup.yourGroup', {
name: tariffs.find((t) => t.promo_group_name)?.promo_group_name,
})}
{t('subscription.promoGroup.personalDiscountsApplied')}
)}
{/* Tariff List - current tariff first */}
{[...tariffs]
// Hide Trial tariffs for users who already have trial subscription
.filter((tariff) => {
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;
// Check if subscription is expired from purchaseOptions
const isSubscriptionExpired =
isTariffsMode &&
purchaseOptions &&
'subscription_is_expired' in purchaseOptions &&
purchaseOptions.subscription_is_expired === true;
// canSwitch only if subscription is active (not expired, not trial)
const canSwitch =
subscription &&
subscription.tariff_id &&
!isCurrentTariff &&
!subscription.is_trial &&
!isSubscriptionExpired &&
subscription.is_active;
// Если есть подписка БЕЗ tariff_id (классическая) - разрешить выбрать тариф
const isLegacySubscription =
subscription && !subscription.is_trial && !subscription.tariff_id;
return (
{tariff.name}
{tariff.description && (
{tariff.description}
)}
{isCurrentTariff && (
{t('subscription.currentTariff')}
)}
{/* Traffic */}
{tariff.traffic_limit_label}
{/* Devices */}
{t('subscription.devices', { count: tariff.device_limit })}
{/* Traffic Reset */}
{tariff.traffic_reset_mode &&
tariff.traffic_reset_mode !== 'NO_RESET' && (
{t(`subscription.trafficReset.${tariff.traffic_reset_mode}`)}
)}
{/* Price info - daily or period-based */}
{(() => {
// Daily tariff price (is_daily + daily_price_kopeks)
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')}
{/* Show discount badge */}
{promoDaily.percent && promoDaily.percent > 0 && (
-{promoDaily.percent}%
)}
);
}
// Period-based price
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}%
)}
);
}
// Fallback
return (
{t('subscription.tariff.flexiblePayment')}
);
})()}
{/* Action Buttons */}
{isCurrentTariff ? (
/* Current tariff - extend button (hide for daily tariffs) */
subscription?.is_daily ? (
{t('subscription.currentTariff')}
) : (
{
setSelectedTariff(tariff);
setSelectedTariffPeriod(tariff.periods[0] || null);
setShowTariffPurchase(true);
}}
className="btn-primary flex-1 py-2 text-sm"
>
{t('subscription.extend')}
)
) : isLegacySubscription ? (
/* Legacy subscription without tariff - allow selecting tariff for renewal */
{
setSelectedTariff(tariff);
setSelectedTariffPeriod(tariff.periods[0] || null);
setShowTariffPurchase(true);
}}
className="btn-primary flex-1 py-2 text-sm"
>
{t('subscription.tariff.selectForRenewal')}
) : canSwitch ? (
/* Other tariffs with existing tariff - switch button */
setSwitchTariffId(tariff.id)}
className="btn-secondary flex-1 py-2 text-sm"
>
{t('subscription.switchTariff.switch')}
) : (
/* No subscription or trial - purchase button */
{
setSelectedTariff(tariff);
setSelectedTariffPeriod(tariff.periods[0] || null);
setShowTariffPurchase(true);
}}
className="btn-primary flex-1 py-2 text-sm"
>
{t('subscription.purchase')}
)}
);
})}
>
) : (
selectedTariff && (
/* Tariff Purchase Form */
{selectedTariff.name}
{
setShowTariffPurchase(false);
setSelectedTariff(null);
setSelectedTariffPeriod(null);
}}
className="text-dark-400 hover:text-dark-200"
>
← {t('common.back')}
{/* Tariff Info */}
{t('subscription.traffic')}:
{selectedTariff.traffic_limit_label}
{t('subscription.devices')}:
{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')}
{/* Purchase button for daily tariff */}
{(() => {
const dailyPrice = selectedTariff.daily_price_kopeks || 0;
const hasEnoughBalance =
purchaseOptions && dailyPrice <= purchaseOptions.balance_kopeks;
return (
{purchaseOptions && !hasEnoughBalance && (
)}
tariffPurchaseMutation.mutate()}
disabled={tariffPurchaseMutation.isPending}
className="btn-primary w-full py-3"
>
{tariffPurchaseMutation.isPending ? (
{t('common.loading')}
) : (
t('subscription.dailyPurchase.activate', {
price: formatPrice(dailyPrice),
})
)}
{tariffPurchaseMutation.isError &&
!getInsufficientBalanceError(tariffPurchaseMutation.error) && (
{getErrorMessage(tariffPurchaseMutation.error)}
)}
{tariffPurchaseMutation.isError &&
getInsufficientBalanceError(tariffPurchaseMutation.error) && (
)}
);
})()}
) : (
<>
{/* Period Selection for non-daily tariffs */}
{t('subscription.selectPeriod')}
{/* Fixed periods */}
{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 (
{
setSelectedTariffPeriod(period);
setUseCustomDays(false);
}}
className={`relative rounded-xl border p-4 text-left transition-all ${
selectedTariffPeriod?.days === period.days && !useCustomDays
? 'border-accent-500 bg-accent-500/10'
: 'border-dark-700/50 bg-dark-800/50 hover:border-dark-600'
}`}
>
{displayDiscount && displayDiscount > 0 && (
-{displayDiscount}%
)}
{period.label}
{formatPrice(displayPrice)}
{displayOriginal && displayOriginal > displayPrice && (
{formatPrice(displayOriginal)}
)}
{formatPrice(displayPerMonth)}/{t('subscription.month')}
);
})}
)}
{/* Custom days option */}
{selectedTariff.custom_days_enabled &&
(selectedTariff.price_per_day_kopeks ?? 0) > 0 && (
{t('subscription.customDays.title')}
setUseCustomDays(!useCustomDays)}
className={`relative h-6 w-10 rounded-full transition-colors ${
useCustomDays ? 'bg-accent-500' : 'bg-dark-600'
}`}
>
{useCustomDays && (
)}
)}
{/* Custom traffic option */}
{selectedTariff.custom_traffic_enabled &&
(selectedTariff.traffic_price_per_gb_kopeks ?? 0) > 0 && (
{t('subscription.customTraffic.label')}
{t('subscription.customTraffic.selectVolume')}
setUseCustomTraffic(!useCustomTraffic)}
className={`relative h-6 w-10 rounded-full transition-colors ${
useCustomTraffic ? 'bg-accent-500' : 'bg-dark-600'
}`}
>
{!useCustomTraffic && (
{t('subscription.customTraffic.default', {
label: selectedTariff.traffic_limit_label,
})}
)}
{useCustomTraffic && (
{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) && (
{(() => {
// Calculate prices with promo discount
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 (
<>
{/* Price breakdown */}
{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)}
)}
{/* Promo discount info */}
{promoPeriod.percent && (
{t('promo.discountApplied')} -{promoPeriod.percent}%
)}
{t('subscription.total')}
{formatPrice(totalPrice)}
{originalTotal && (
{formatPrice(originalTotal)}
)}
tariffPurchaseMutation.mutate()}
disabled={tariffPurchaseMutation.isPending}
className="btn-primary w-full py-3"
>
{tariffPurchaseMutation.isPending ? (
{t('common.loading')}
) : (
t('subscription.purchase')
)}
>
);
})()}
{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 && (
setShowPurchaseForm(true)} className="btn-primary">
{subscription && !subscription.is_trial
? t('subscription.extend')
: t('subscription.getSubscription')}
)}
{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 (
{
setSelectedPeriod(period);
if (period.traffic.current !== undefined) {
setSelectedTraffic(period.traffic.current);
}
const availableServers = getAvailableServers(period);
// If only 1 server available, auto-select it (step will be skipped)
if (availableServers.length === 1) {
setSelectedServers([availableServers[0].uuid]);
} else if (period.servers.selected) {
const availUuids = new Set(availableServers.map((s) => s.uuid));
setSelectedServers(
period.servers.selected.filter((uuid) => availUuids.has(uuid)),
);
}
if (period.devices.current) {
setSelectedDevices(period.devices.current);
}
}}
className={`bento-card-hover relative p-4 text-left transition-all ${
selectedPeriod?.id === period.id
? 'bento-card-glow border-accent-500'
: ''
}`}
>
{promoPeriod.percent && promoPeriod.percent > 0 && (
-{promoPeriod.percent}%
)}
{period.label}
{formatPrice(promoPeriod.price)}
{promoPeriod.original && promoPeriod.original > promoPeriod.price && (
{formatPrice(promoPeriod.original)}
)}
);
})}
)}
{/* Step: Traffic Selection */}
{currentStep === 'traffic' && selectedPeriod?.traffic.options && (
{selectedPeriod.traffic.options.map((option) => {
const promoTraffic = applyPromoDiscount(
option.price_kopeks,
option.original_price_kopeks,
);
return (
setSelectedTraffic(option.value)}
disabled={!option.is_available}
className={`bento-card-hover relative p-4 text-center transition-all ${
selectedTraffic === option.value
? 'bento-card-glow border-accent-500'
: ''
} ${!option.is_available ? 'cursor-not-allowed opacity-50' : ''}`}
>
{promoTraffic.percent && promoTraffic.percent > 0 && (
-{promoTraffic.percent}%
)}
{option.label}
{formatPrice(promoTraffic.price)}
{promoTraffic.original && promoTraffic.original > promoTraffic.price && (
{formatPrice(promoTraffic.original)}
)}
);
})}
)}
{/* Step: Server Selection */}
{currentStep === 'servers' && selectedPeriod?.servers.options && (
{selectedPeriod.servers.options
// Hide unavailable (disabled) servers and trial servers for existing trial users
.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 (
toggleServer(server.uuid)}
disabled={!server.is_available}
className={`relative rounded-xl border p-4 text-left transition-all ${
selectedServers.includes(server.uuid)
? 'border-accent-500 bg-accent-500/10'
: server.is_available
? 'border-dark-700/50 bg-dark-800/50 hover:border-dark-600'
: 'cursor-not-allowed border-dark-800/30 bg-dark-900/30 opacity-50'
}`}
>
{promoServer.percent && promoServer.percent > 0 ? (
-{promoServer.percent}%
) : null}
{selectedServers.includes(server.uuid) && }
{server.name}
{formatPrice(promoServer.price)}
{t('subscription.perMonth')}
{promoServer.original &&
promoServer.original > promoServer.price ? (
{formatPrice(promoServer.original)}
) : null}
);
})}
)}
{/* Step: Device Selection */}
{currentStep === 'devices' && selectedPeriod && (
setSelectedDevices(
Math.max(selectedPeriod.devices.min, selectedDevices - 1),
)
}
disabled={selectedDevices <= selectedPeriod.devices.min}
className="btn-secondary flex h-14 w-14 items-center justify-center !p-0 text-2xl"
>
-
{selectedDevices}
{t('subscription.devices')}
setSelectedDevices(
Math.min(selectedPeriod.devices.max, selectedDevices + 1),
)
}
disabled={selectedDevices >= selectedPeriod.devices.max}
className="btn-secondary flex h-14 w-14 items-center justify-center !p-0 text-2xl"
>
+
{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 ? (
{/* Active promo discount banner */}
{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 && (
{t('common.back')}
)}
{isFirstStep && (
{t('common.cancel')}
)}
{!isLastStep ? (
{t('common.next')}
) : (
purchaseMutation.mutate()}
disabled={
purchaseMutation.isPending || previewLoading || !preview?.can_purchase
}
className="btn-primary flex-1"
>
{purchaseMutation.isPending ? (
{t('common.loading')}
) : (
t('subscription.purchase')
)}
)}
{purchaseMutation.isError && (
{getErrorMessage(purchaseMutation.error)}
)}
)}
)}
);
}