diff --git a/src/locales/en.json b/src/locales/en.json index f3b2684..76f47be 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -306,6 +306,21 @@ "insufficientBalance": "Top up your balance to activate", "unavailable": "Trial is not available" }, + "trialBanner": { + "title": "Trial period active", + "description": "You have {{days}} days left in your trial. Subscribe to continue using the service.", + "upgrade": "Choose a plan" + }, + "trialInfo": { + "title": "Trial Period", + "description": "You are using a trial version. Choose a plan below for full access.", + "remaining": "remaining", + "upgradeNow": "Choose a plan" + }, + "trialUpgrade": { + "title": "Choose a plan to continue", + "description": "Your trial period is ending soon. Select a plan to continue using VPN without restrictions." + }, "connection": { "title": "Connect VPN", "selectDevice": "Select your device", diff --git a/src/locales/ru.json b/src/locales/ru.json index 0e2a33b..a358eaf 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -328,6 +328,21 @@ "insufficientBalance": "Пополните баланс для активации", "unavailable": "Пробный период недоступен" }, + "trialBanner": { + "title": "Пробный период активен", + "description": "У вас осталось {{days}} дней пробного периода. Оформите подписку, чтобы продолжить пользоваться сервисом.", + "upgrade": "Выбрать тариф" + }, + "trialInfo": { + "title": "Пробный период", + "description": "Вы используете пробную версию сервиса. Выберите тариф ниже для полного доступа.", + "remaining": "осталось", + "upgradeNow": "Выбрать тариф" + }, + "trialUpgrade": { + "title": "Выберите тариф для продолжения", + "description": "Ваш пробный период скоро закончится. Выберите подходящий тариф, чтобы продолжить пользоваться VPN без ограничений." + }, "connection": { "title": "Подключить VPN", "selectDevice": "Выберите устройство", diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 72ac6de..534a7cd 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -2,8 +2,6 @@ import { useState, useEffect, useMemo, useRef } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { motion } from 'framer-motion'; - import { useAuthStore } from '../store/auth'; import { subscriptionApi } from '../api/subscription'; import { referralApi } from '../api/referral'; @@ -14,25 +12,15 @@ import Onboarding, { useOnboarding } from '../components/Onboarding'; import PromoOffersSection from '../components/PromoOffersSection'; import { useCurrency } from '../hooks/useCurrency'; -import { Card } from '@/components/data-display/Card'; -import { Button } from '@/components/primitives/Button'; -import { staggerContainer, staggerItem } from '@/components/motion/transitions'; - // Icons -const ArrowRightIcon = ({ className = 'h-4 w-4' }: { className?: string }) => ( - +const ArrowRightIcon = () => ( + ); -const SparklesIcon = ({ className = 'h-5 w-5' }: { className?: string }) => ( - +const SparklesIcon = () => ( + ( ); -const ChevronRightIcon = ({ className = 'h-5 w-5' }: { className?: string }) => ( - +const ChevronRightIcon = () => ( + ); @@ -57,6 +45,16 @@ const RefreshIcon = ({ className = 'w-4 h-4' }: { className?: string }) => ( ); +const SupportLottieIcon = () => ( + + + +); + export default function Dashboard() { const { t } = useTranslation(); const { user, refreshUser } = useAuthStore(); @@ -246,6 +244,13 @@ export default function Dashboard() { }); } + steps.push({ + target: 'quick-actions', + title: t('onboarding.steps.quickActions.title'), + description: t('onboarding.steps.quickActions.description'), + placement: 'top', + }); + return steps; }, [t, subscription]); @@ -262,373 +267,418 @@ export default function Dashboard() { }; return ( - +
{/* Header */} - +

{t('dashboard.welcome', { name: user?.first_name || user?.username || '' })}

{t('dashboard.yourSubscription')}

- +
{/* Subscription Status - Main Card */} {subLoading ? ( - - -
-
-
-
-
- {[...Array(4)].map((_, i) => ( -
-
-
-
- ))} -
-
-
-
-
-
-
-
- - +
+
+
+
+
+
+ {[...Array(4)].map((_, i) => ( +
+
+
+
+ ))} +
+
+
+
+
+
+
+
+
) : subscription ? ( - - -
+
+
+

{t('subscription.status')}

- - {subscription.is_active ? t('subscription.active') : t('subscription.expired')} - -
- -
-
-
{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)} - % - -
-
-
-
-
- )} - -
- - {subscription.subscription_url && ( - + {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 && ( + + )} +
+
) : null} {/* Stats Grid */} - +
{/* Balance */} - - -
- {t('balance.currentBalance')} - -
-
- {formatAmount(balanceData?.balance_rubles || 0)} - {currencySymbol} -
- -
+ +
+ {t('balance.currentBalance')} + + + +
+
+ {formatAmount(balanceData?.balance_rubles || 0)} + {currencySymbol} +
+ {/* Subscription */} - - -
- {t('subscription.title')} - + +
+ {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')} + )}
- {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')}
- )} - - + ) : ( +
{t('subscription.inactive')}
+ )} + {/* Referrals */} - - -
- {t('referral.stats.totalReferrals')} - -
- {refLoading ? ( -
- ) : ( -
{referralInfo?.total_referrals || 0}
- )} - - + +
+ {t('referral.stats.totalReferrals')} + + + +
+ {refLoading ? ( +
+ ) : ( +
{referralInfo?.total_referrals || 0}
+ )} + {/* Earnings */} - - -
- {t('referral.stats.totalEarnings')} - + +
+ {t('referral.stats.totalEarnings')} + + + +
+ {refLoading ? ( +
+ ) : ( +
+ {formatPositive(referralInfo?.total_earnings_rubles || 0)}
- {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 ? ( - - ) : ( - - ) - ) : ( - - )} -
+
+
+
+
- - +
+

+ {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 ? ( + + ) : ( + + {t('subscription.trial.topUpToActivate', 'Top Up Balance')} + + ) + ) : ( + + )} +
+
+
)} {/* Promo Offers */} - - - + {/* Fortune Wheel Banner */} {wheelConfig?.is_enabled && ( - - - -
- 🎰 -
-

- {t('wheel.banner.title')} -

-

{t('wheel.banner.description')}

-
-
- - -
-
+ +
+ {/* Emoji */} + 🎰 +
+

{t('wheel.banner.title')}

+

{t('wheel.banner.description')}

+
+
+
+ +
+ )} + {/* Quick Actions */} +
+

{t('dashboard.quickActions')}

+
+ + {t('dashboard.topUpBalance')} + + + {t('subscription.renew')} + + + {t('dashboard.inviteFriends')} + + + + {t('dashboard.getSupport')} + +
+
+ {/* Connection Modal */} {showConnectionModal && setShowConnectionModal(false)} />} @@ -640,6 +690,6 @@ export default function Dashboard() { onSkip={handleOnboardingComplete} /> )} - +
); } diff --git a/src/pages/Subscription.tsx b/src/pages/Subscription.tsx index 237021a..402de39 100644 --- a/src/pages/Subscription.tsx +++ b/src/pages/Subscription.tsx @@ -3,7 +3,6 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom'; import { AxiosError } from 'axios'; -import { motion } from 'framer-motion'; import { subscriptionApi } from '../api/subscription'; import { promoApi } from '../api/promo'; import type { @@ -18,8 +17,6 @@ import InsufficientBalancePrompt from '../components/InsufficientBalancePrompt'; import { useCurrency } from '../hooks/useCurrency'; import { useCloseOnSuccessNotification } from '../store/successNotification'; import i18n from '../i18n'; -import { Card } from '@/components/data-display/Card'; -import { staggerContainer, staggerItem } from '@/components/motion/transitions'; // Helper to extract error message from axios/api errors const getErrorMessage = (error: unknown): string => { @@ -599,60 +596,47 @@ export default function Subscription() { } return ( - - -

{t('subscription.title')}

-
+
+

{t('subscription.title')}

{/* Current Subscription */} {subscription ? ( - - -
-
-

- {t('subscription.currentPlan')} -

- {subscription.tariff_name && ( -
{subscription.tariff_name}
- )} -
- - {subscription.is_trial - ? t('subscription.trialStatus') - : subscription.is_active - ? t('subscription.active') - : t('subscription.expired')} - +
+
+
+

+ {t('subscription.currentPlan')} +

+ {subscription.tariff_name && ( +
{subscription.tariff_name}
+ )}
+ + {subscription.is_trial + ? t('subscription.trialStatus') + : subscription.is_active + ? t('subscription.active') + : t('subscription.expired')} + +
- {/* Connection Data - Top Priority */} - {subscription.subscription_url && ( -
-
- {t('subscription.connectionInfo')} -
- - {/* Get Config Button */} - - - {/* Subscription URL - hidden when hide_subscription_link is true */} - {!subscription.hide_subscription_link && ( -
- - {subscription.subscription_url} - - +
+
+
+ {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 || '∞'} GB + + {t('subscription.traffic')} +
+
+ {subscription.device_limit} + {t('subscription.devices')} +
- )} -
- )} - -
-
-
{t('subscription.daysLeft')}
-
- {subscription.days_left > 0 ? ( - t('subscription.days', { count: subscription.days_left }) - ) : subscription.hours_left > 0 ? ( - `${t('subscription.hours', { count: subscription.hours_left })} ${t('subscription.minutes', { count: subscription.minutes_left })}` - ) : subscription.minutes_left > 0 ? ( - t('subscription.minutes', { count: subscription.minutes_left }) - ) : ( - {t('subscription.expired')} - )} -
-
-
-
{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}
+ )} - {/* Servers */} - {subscription.servers && subscription.servers.length > 0 && ( -
-
{t('subscription.serversLabel')}
-
- {subscription.servers.map((server) => ( - - {server.country_code && ( - {getFlagEmoji(server.country_code)} - )} - {server.name} - - ))} -
+ {/* Connection Data - Top Priority */} + {subscription.subscription_url && ( +
+
+ {t('subscription.connectionInfo')}
- )} - {/* Traffic Usage */} - {subscription.traffic_limit_gb > 0 && ( -
-
- {t('subscription.trafficUsed')} - - {( - trafficData?.traffic_used_percent ?? subscription.traffic_used_percent - ).toFixed(1)} - % - -
-
-
setShowConnectionModal(true)} + className="btn-primary mb-3 flex w-full items-center justify-center gap-2 py-3" + > + + -
-
- )} + + {t('subscription.getConfig')} + - {/* Purchased Traffic Packages */} - {subscription.traffic_purchases && subscription.traffic_purchases.length > 0 && ( -
-
- {t('subscription.purchasedTraffic')} + {/* Subscription URL - hidden when hide_subscription_link is true */} + {!subscription.hide_subscription_link && ( +
+ + {subscription.subscription_url} + +
-
- {subscription.traffic_purchases.map((purchase) => ( -
-
-
+ )} +
+ )} + +
+
+
{t('subscription.daysLeft')}
+
+ {subscription.days_left > 0 ? ( + t('subscription.days', { count: subscription.days_left }) + ) : subscription.hours_left > 0 ? ( + `${t('subscription.hours', { count: subscription.hours_left })} ${t('subscription.minutes', { count: subscription.minutes_left })}` + ) : subscription.minutes_left > 0 ? ( + t('subscription.minutes', { count: subscription.minutes_left }) + ) : ( + {t('subscription.expired')} + )} +
+
+
+
{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}
+
+
+ + {/* Servers */} + {subscription.servers && subscription.servers.length > 0 && ( +
+
{t('subscription.serversLabel')}
+
+ {subscription.servers.map((server) => ( + + {server.country_code && ( + {getFlagEmoji(server.country_code)} + )} + {server.name} + + ))} +
+
+ )} + + {/* Traffic Usage */} + {subscription.traffic_limit_gb > 0 && ( +
+
+ {t('subscription.trafficUsed')} + + {(trafficData?.traffic_used_percent ?? subscription.traffic_used_percent).toFixed( + 1, + )} + % + +
+
+
+
+
+ )} + + {/* 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 })} + + )} +
+
- - {purchase.traffic_gb} {t('common.units.gb')} + + {t('subscription.trafficResetAt')}:{' '} + {new Date(purchase.expires_at).toLocaleDateString(undefined, { + day: '2-digit', + month: '2-digit', + year: 'numeric', + })}
-
-
- {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 - hide for daily tariffs */} - {!subscription.is_trial && !subscription.is_daily && ( -
-
-
{t('subscription.autoRenewal')}
-
- {t('subscription.daysBeforeExpiry', { - count: subscription.autopay_days_before, - })} +
+
+
+
+ {new Date(purchase.created_at).toLocaleDateString()} + {new Date(purchase.expires_at).toLocaleDateString()} +
-
- + ))}
- )} - - - ) : ( - - -
- - -
-
{t('subscription.noSubscription')}
-
-
+ )} + + {/* Autopay Toggle - hide for daily tariffs */} + {!subscription.is_trial && !subscription.is_daily && ( +
+
+
{t('subscription.autoRenewal')}
+
+ {t('subscription.daysBeforeExpiry', { count: subscription.autopay_days_before })} +
+
+ +
+ )} +
+ ) : ( +
+
+ + + +
+
{t('subscription.noSubscription')}
+
)} {/* Daily Subscription Pause */} {subscription && subscription.is_daily && !subscription.is_trial && ( - +

@@ -1043,12 +1074,12 @@ export default function Subscription() { ); })() )} - +

)} {/* Additional Options (Buy Devices) */} {subscription && subscription.is_active && !subscription.is_trial && ( - +

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

@@ -1253,10 +1284,8 @@ export default function Subscription() { {deviceReductionInfo?.available === false ? (
- {deviceReductionInfo.reason?.includes('minimum device limit') - ? t('subscription.additionalOptions.alreadyAtMinDeviceLimit') - : deviceReductionInfo.reason || - t('subscription.additionalOptions.reduceUnavailable')} + {deviceReductionInfo.reason || + t('subscription.additionalOptions.reduceUnavailable')}
) : deviceReductionInfo ? (
@@ -1788,12 +1817,12 @@ export default function Subscription() { )}
)} - +
)} {/* My Devices Section */} {subscription && ( - +

{t('subscription.myDevices')}

{devicesData && devicesData.devices.length > 0 && ( @@ -1879,12 +1908,12 @@ export default function Subscription() { ) : (
{t('subscription.noDevices')}
)} - +
)} {/* Tariffs Section - Combined Purchase/Extend/Switch like MiniApp */} {isTariffsMode && tariffs.length > 0 && ( - +

{subscription?.is_daily && !subscription?.is_trial @@ -1895,17 +1924,48 @@ export default function Subscription() {

+ {/* Trial upgrade prompt */} + {subscription?.is_trial && ( +
+
+
+ + + +
+
+
+ {t('subscription.trialUpgrade.title')} +
+
+ {t('subscription.trialUpgrade.description')} +
+
+
+
+ )} + {/* Legacy subscription notice - if user has subscription without tariff */} {subscription && !subscription.is_trial && !subscription.tariff_id && (
- 📦 {t('subscription.legacy.selectTariffTitle')} + {t('subscription.legacy.selectTariffTitle')}
{t('subscription.legacy.selectTariffDescription')}
- ⚠️ {t('subscription.legacy.currentSubContinues')} + {t('subscription.legacy.currentSubContinues')}
)} @@ -2094,20 +2154,64 @@ export default function Subscription() { )}
-
- - {tariff.traffic_limit_label} - - - +
+ {/* Traffic */} +
+ + + + + {tariff.traffic_limit_label} + +
+ {/* Devices */} +
+ + + + {t('subscription.devices', { count: tariff.device_limit })} - - - +
+ {/* Servers */} +
+ + + + {t('subscription.servers', { count: tariff.servers_count })} - +
{/* Price info - daily or period-based */}
@@ -2847,12 +2951,12 @@ export default function Subscription() {
) )} - +
)} {/* Purchase/Extend Section - Classic Mode */} {classicOptions && classicOptions.periods.length > 0 && ( - +

{subscription && !subscription.is_trial @@ -3255,11 +3359,11 @@ export default function Subscription() { )}

)} - +
)} {/* Connection Modal */} {showConnectionModal && setShowConnectionModal(false)} />} - +
); }