-
+
-
- {[...Array(4)].map((_, i) => (
-
- ))}
-
-
-
+ ) : subscription?.is_expired ? (
+
) : subscription ? (
-
-
-
-
{t('subscription.status')}
- {subscription.tariff_name && (
-
{subscription.tariff_name}
- )}
-
-
- {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.trialBanner.title')}
-
-
- {t('subscription.trialBanner.description', { days: subscription.days_left })}
-
-
- {t('subscription.trialBanner.upgrade')}
-
-
-
-
-
- )}
-
-
-
-
{t('subscription.expiresAt')}
-
- {new Date(subscription.end_date).toLocaleDateString()}
-
-
-
-
- {t('subscription.traffic')}
-
-
-
- {(trafficData?.traffic_used_gb ?? subscription.traffic_used_gb).toFixed(1)} /{' '}
- {subscription.traffic_limit_gb || '∞'} GB
-
-
-
-
{t('subscription.devices')}
-
{subscription.device_limit}
-
-
-
{t('subscription.timeLeft')}
-
- {subscription.days_left > 0
- ? t('subscription.days', { count: subscription.days_left })
- : `${t('subscription.hours', { count: subscription.hours_left })} ${t('subscription.minutes', { count: subscription.minutes_left })}`}
-
-
-
-
- {/* Traffic Progress */}
- {subscription.traffic_limit_gb > 0 && (
-
-
- {t('subscription.trafficUsed')}
-
- {(trafficData?.traffic_used_percent ?? subscription.traffic_used_percent).toFixed(
- 1,
- )}
- %
-
-
-
-
- )}
-
-
-
- {t('dashboard.viewSubscription')}
-
- {subscription.subscription_url && (
- navigate('/connection')}
- containerClassName="w-full"
- className="flex w-full items-center justify-center py-2.5 text-sm"
- data-onboarding="connect-devices"
- >
- {t('subscription.getConfig')}
-
- )}
-
-
+
) : null}
- {/* Promo Offers - показываем сразу после карточки подписки */}
-
-
- {/* Stats Grid */}
-
- {/* Balance */}
-
-
-
{t('balance.currentBalance')}
-
-
-
-
-
- {formatAmount(balanceData?.balance_rubles || 0)}
- {currencySymbol}
-
-
-
- {/* Subscription */}
-
-
-
{t('subscription.title')}
-
-
-
-
- {subLoading ? (
-
- ) : subscription ? (
-
- {subscription.days_left > 0 ? (
- <>
- {subscription.days_left}
- {t('subscription.days')}
- >
- ) : subscription.hours_left > 0 ? (
- <>
- {subscription.hours_left}
- {t('subscription.hours')}
- >
- ) : subscription.minutes_left > 0 ? (
- <>
- {subscription.minutes_left}
- {t('subscription.minutes')}
- >
- ) : (
- {t('subscription.expired')}
- )}
-
- ) : (
-
{t('subscription.inactive')}
- )}
-
-
- {/* Referrals */}
-
-
-
{t('referral.stats.totalReferrals')}
-
-
-
-
- {refLoading ? (
-
- ) : (
-
{referralInfo?.total_referrals || 0}
- )}
-
-
- {/* Earnings */}
-
-
-
{t('referral.stats.totalEarnings')}
-
-
-
-
- {refLoading ? (
-
- ) : (
-
- {formatPositive(referralInfo?.total_earnings_rubles || 0)}
-
- )}
-
-
-
{/* Trial Activation */}
{hasNoSubscription && !trialLoading && trialInfo?.is_available && (
-
-
-
-
-
-
-
- {trialInfo.requires_payment
- ? t('subscription.trial.titlePaid', 'Trial Subscription')
- : t('subscription.trial.title', 'Free Trial')}
-
-
- {t('subscription.trial.description', 'Try our VPN service for free!')}
-
-
-
-
-
- {trialInfo.duration_days}
-
-
{t('subscription.trial.days')}
-
-
-
- {trialInfo.traffic_limit_gb || '∞'}
-
-
GB
-
-
-
{trialInfo.device_limit}
-
{t('subscription.trial.devices')}
-
-
-
- {trialInfo.requires_payment && trialInfo.price_rubles > 0 && (
-
-
-
- {t('subscription.trial.price', 'Activation price')}:
-
-
- {trialInfo.price_rubles.toFixed(2)} {currencySymbol}
-
-
-
-
- {t('balance.currentBalance', 'Your balance')}:
-
- = trialInfo.price_kopeks ? 'text-success-400' : 'text-warning-400'}`}
- >
- {formatAmount(balanceData?.balance_rubles || 0)} {currencySymbol}
-
-
- {(balanceData?.balance_kopeks || 0) < trialInfo.price_kopeks && (
-
- {t(
- 'subscription.trial.insufficientBalance',
- 'Top up your balance to activate',
- )}
-
- )}
-
- )}
-
- {trialError && (
-
- {trialError}
-
- )}
-
- {trialInfo.requires_payment && trialInfo.price_kopeks > 0 ? (
- (balanceData?.balance_kopeks || 0) >= trialInfo.price_kopeks ? (
-
activateTrialMutation.mutate()}
- containerClassName={`w-full ${activateTrialMutation.isPending ? 'pointer-events-none opacity-50' : ''}`}
- className="flex w-full items-center justify-center"
- >
- {activateTrialMutation.isPending
- ? t('common.loading', 'Loading...')
- : t('subscription.trial.payAndActivate', 'Pay from Balance & Activate')}
-
- ) : (
-
- {t('subscription.trial.topUpToActivate', 'Top Up Balance')}
-
- )
- ) : (
-
activateTrialMutation.mutate()}
- containerClassName={`w-full ${activateTrialMutation.isPending ? 'pointer-events-none opacity-50' : ''}`}
- className="flex w-full items-center justify-center"
- >
- {activateTrialMutation.isPending
- ? t('common.loading', 'Loading...')
- : t('subscription.trial.activate', 'Activate Free Trial')}
-
- )}
-
-
-
+
)}
+ {/* Promo Offers */}
+
+
+ {/* Stats Grid */}
+
+
{/* Fortune Wheel Banner */}
{wheelConfig?.is_enabled && (
- {/* Emoji */}
🎰
{t('wheel.banner.title')}
diff --git a/src/pages/Subscription.tsx b/src/pages/Subscription.tsx
index 4395da6..9356176 100644
--- a/src/pages/Subscription.tsx
+++ b/src/pages/Subscription.tsx
@@ -6,6 +6,7 @@ import { AxiosError } from 'axios';
import { subscriptionApi } from '../api/subscription';
import { promoApi } from '../api/promo';
import { HoverBorderGradient } from '../components/ui/hover-border-gradient';
+import TrafficProgressBar from '../components/dashboard/TrafficProgressBar';
import type {
PurchaseSelection,
PeriodOption,
@@ -605,12 +606,6 @@ export default function Subscription() {
}
};
- const getTrafficColor = (percent: number) => {
- if (percent > 90) return 'bg-error-500';
- if (percent > 70) return 'bg-warning-500';
- return 'bg-success-500';
- };
-
const toggleServer = (uuid: string) => {
if (selectedServers.includes(uuid)) {
if (selectedServers.length > 1) {
@@ -756,11 +751,11 @@ export default function Subscription() {
{t('subscription.connectionInfo')}
- {/* Get Config Button */}
+ {/* Get Config Button with device indicator */}
navigate('/connection')}
containerClassName="mb-3 w-full"
- className="flex w-full items-center justify-center gap-2 py-3"
+ className="flex w-full items-center justify-center gap-3 py-3"
>
- {t('subscription.getConfig')}
+ {t('subscription.getConfig')}
+
+ {Array.from({ length: Math.min(subscription.device_limit, 5) }).map((_, i) => (
+
+ ))}
+
+ {/* Compact traffic progress bar */}
+
+
+
+
{/* Subscription URL - hidden when hide_subscription_link is true */}
{!subscription.hide_subscription_link && (
@@ -885,21 +898,20 @@ export default function Subscription() {
{t('subscription.trafficUsed')}
-
+
{(trafficData?.traffic_used_percent ?? subscription.traffic_used_percent).toFixed(
1,
)}
%
-
+
)}