Merge pull request #287 from BEDOLAGA-DEV/dev

Dev
This commit is contained in:
Egor
2026-03-13 06:18:02 +03:00
committed by GitHub
12 changed files with 316 additions and 140 deletions

View File

@@ -38,6 +38,9 @@ export default function SubscriptionCardExpired({
const formattedDate = new Date(subscription.end_date).toLocaleDateString(); const formattedDate = new Date(subscription.end_date).toLocaleDateString();
// Detect limited (traffic exhausted) state
const isLimited = subscription.is_limited;
// Detect daily subscription (disabled or expired) // Detect daily subscription (disabled or expired)
const isDaily = subscription.is_daily; const isDaily = subscription.is_daily;
const isDisabledDaily = subscription.status === 'disabled' && isDaily; const isDisabledDaily = subscription.status === 'disabled' && isDaily;
@@ -92,19 +95,38 @@ export default function SubscriptionCardExpired({
navigate(`/balance/top-up?${params.toString()}`); navigate(`/balance/top-up?${params.toString()}`);
}; };
// Color scheme: amber for limited, red for expired/disabled
const accent = isLimited
? {
r: 255,
g: 184,
b: 0,
hex: '#FFB800',
gradient: 'linear-gradient(135deg, #FFB800, #FF8C00)',
}
: {
r: 255,
g: 59,
b: 92,
hex: '#FF3B5C',
gradient: 'linear-gradient(135deg, #FF3B5C, #FF6B35)',
};
return ( return (
<div <div
className={`relative overflow-hidden rounded-3xl ${className ?? ''}`} className={`relative overflow-hidden rounded-3xl ${className ?? ''}`}
style={{ style={{
background: g.cardBg, background: g.cardBg,
border: isDark ? '1px solid rgba(255,70,70,0.12)' : '1px solid rgba(255,59,92,0.2)', border: isDark
? `1px solid rgba(${accent.r},${accent.g},${accent.b},0.12)`
: `1px solid rgba(${accent.r},${accent.g},${accent.b},0.2)`,
boxShadow: isDark boxShadow: isDark
? g.shadow ? g.shadow
: '0 2px 16px rgba(255,59,92,0.1), 0 0 0 1px rgba(255,59,92,0.06)', : `0 2px 16px rgba(${accent.r},${accent.g},${accent.b},0.1), 0 0 0 1px rgba(${accent.r},${accent.g},${accent.b},0.06)`,
padding: '28px 28px 24px', padding: '28px 28px 24px',
}} }}
> >
{/* Red glow */} {/* Glow */}
<div <div
className="pointer-events-none absolute" className="pointer-events-none absolute"
style={{ style={{
@@ -113,7 +135,7 @@ export default function SubscriptionCardExpired({
width: 200, width: 200,
height: 200, height: 200,
borderRadius: '50%', borderRadius: '50%',
background: 'radial-gradient(circle, rgba(255,59,92,0.08) 0%, transparent 70%)', background: `radial-gradient(circle, rgba(${accent.r},${accent.g},${accent.b},0.08) 0%, transparent 70%)`,
}} }}
aria-hidden="true" aria-hidden="true"
/> />
@@ -134,20 +156,36 @@ export default function SubscriptionCardExpired({
{/* Header */} {/* Header */}
<div className="mb-5 flex items-center gap-3"> <div className="mb-5 flex items-center gap-3">
{/* Clock icon */}
<div <div
className="flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-[14px]" className="flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-[14px]"
style={{ style={{
background: 'rgba(255,59,92,0.1)', background: `rgba(${accent.r},${accent.g},${accent.b},0.1)`,
border: '1px solid rgba(255,59,92,0.15)', border: `1px solid rgba(${accent.r},${accent.g},${accent.b},0.15)`,
}} }}
> >
{isLimited ? (
<svg <svg
width="22" width="22"
height="22" height="22"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="#FF3B5C" stroke={accent.hex}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" />
<line x1="12" y1="9" x2="12" y2="13" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
) : (
<svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke={accent.hex}
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
@@ -156,9 +194,12 @@ export default function SubscriptionCardExpired({
<circle cx="12" cy="12" r="10" /> <circle cx="12" cy="12" r="10" />
<path d="M12 6v6l4 2" /> <path d="M12 6v6l4 2" />
</svg> </svg>
)}
</div> </div>
<h2 className="text-lg font-bold tracking-tight text-dark-50"> <h2 className="text-lg font-bold tracking-tight text-dark-50">
{isDisabledDaily {isLimited
? t('subscription.trafficLimitedTitle')
: isDisabledDaily
? t('dashboard.suspended.title') ? t('dashboard.suspended.title')
: subscription.is_trial : subscription.is_trial
? t('dashboard.expired.trialTitle') ? t('dashboard.expired.trialTitle')
@@ -166,12 +207,19 @@ export default function SubscriptionCardExpired({
</h2> </h2>
</div> </div>
{/* Limited description */}
{isLimited && (
<p className="mb-4 text-sm text-dark-50/60">
{t('subscription.trafficLimitedDescription')}
</p>
)}
{/* Expired date + Balance row */} {/* Expired date + Balance row */}
<div <div
className="mb-5 flex items-center justify-between rounded-[14px]" className="mb-5 flex items-center justify-between rounded-[14px]"
style={{ style={{
background: 'rgba(255,59,92,0.04)', background: `rgba(${accent.r},${accent.g},${accent.b},0.04)`,
border: '1px solid rgba(255,59,92,0.08)', border: `1px solid rgba(${accent.r},${accent.g},${accent.b},0.08)`,
padding: '14px 18px', padding: '14px 18px',
}} }}
> >
@@ -207,6 +255,32 @@ export default function SubscriptionCardExpired({
{/* Action buttons */} {/* Action buttons */}
<div className="flex gap-2.5"> <div className="flex gap-2.5">
{isLimited ? (
<Link
to="/subscription"
className="flex flex-1 items-center justify-center gap-2 rounded-[14px] py-3.5 text-[15px] font-semibold tracking-tight text-white transition-all duration-300"
style={{
background: accent.gradient,
boxShadow: `0 4px 20px rgba(${accent.r},${accent.g},${accent.b},0.2)`,
}}
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M12 4.5v15m7.5-7.5h-15" />
</svg>
{t('subscription.trafficLimited')}
</Link>
) : (
<>
{/* Quick Renew or Top Up button (hidden for expired trials) */} {/* Quick Renew or Top Up button (hidden for expired trials) */}
{!subscription.is_trial && ( {!subscription.is_trial && (
<> <>
@@ -217,8 +291,8 @@ export default function SubscriptionCardExpired({
disabled={isRenewing} disabled={isRenewing}
className="flex flex-1 items-center justify-center gap-2 rounded-[14px] py-3.5 text-[15px] font-semibold tracking-tight text-white transition-all duration-300 disabled:opacity-50" className="flex flex-1 items-center justify-center gap-2 rounded-[14px] py-3.5 text-[15px] font-semibold tracking-tight text-white transition-all duration-300 disabled:opacity-50"
style={{ style={{
background: 'linear-gradient(135deg, #FF3B5C, #FF6B35)', background: accent.gradient,
boxShadow: '0 4px 20px rgba(255,59,92,0.2)', boxShadow: `0 4px 20px rgba(${accent.r},${accent.g},${accent.b},0.2)`,
}} }}
> >
{isRenewing ? ( {isRenewing ? (
@@ -253,8 +327,8 @@ export default function SubscriptionCardExpired({
onClick={handleTopUp} onClick={handleTopUp}
className="flex flex-1 items-center justify-center gap-2 rounded-[14px] py-3.5 text-[15px] font-semibold tracking-tight text-white transition-all duration-300" className="flex flex-1 items-center justify-center gap-2 rounded-[14px] py-3.5 text-[15px] font-semibold tracking-tight text-white transition-all duration-300"
style={{ style={{
background: 'linear-gradient(135deg, #FF3B5C, #FF6B35)', background: accent.gradient,
boxShadow: '0 4px 20px rgba(255,59,92,0.2)', boxShadow: `0 4px 20px rgba(${accent.r},${accent.g},${accent.b},0.2)`,
}} }}
> >
<svg <svg
@@ -285,8 +359,8 @@ export default function SubscriptionCardExpired({
style={ style={
subscription.is_trial subscription.is_trial
? { ? {
background: 'linear-gradient(135deg, #FF3B5C, #FF6B35)', background: accent.gradient,
boxShadow: '0 4px 20px rgba(255,59,92,0.2)', boxShadow: `0 4px 20px rgba(${accent.r},${accent.g},${accent.b},0.2)`,
} }
: { : {
background: g.innerBg, background: g.innerBg,
@@ -296,6 +370,8 @@ export default function SubscriptionCardExpired({
> >
{t('dashboard.expired.tariffs')} {t('dashboard.expired.tariffs')}
</Link> </Link>
</>
)}
</div> </div>
</div> </div>
); );

View File

@@ -2,25 +2,36 @@ import i18n from 'i18next';
import { initReactI18next } from 'react-i18next'; import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector'; import LanguageDetector from 'i18next-browser-languagedetector';
import ru from './locales/ru.json'; const localeLoaders: Record<string, () => Promise<{ default: Record<string, string> }>> = {
import en from './locales/en.json'; ru: () => import('./locales/ru.json'),
import zh from './locales/zh.json'; en: () => import('./locales/en.json'),
import fa from './locales/fa.json'; zh: () => import('./locales/zh.json'),
fa: () => import('./locales/fa.json'),
const resources = {
ru: { translation: ru },
en: { translation: en },
zh: { translation: zh },
fa: { translation: fa },
}; };
const SUPPORTED_LANGS = Object.keys(localeLoaders);
const FALLBACK_LNG = 'ru';
const loadedLanguages = new Set<string>();
async function loadLanguage(lng: string): Promise<void> {
if (loadedLanguages.has(lng)) return;
const loader = localeLoaders[lng];
if (!loader) return;
const mod = await loader();
i18n.addResourceBundle(lng, 'translation', mod.default, true, true);
loadedLanguages.add(lng);
}
i18n i18n
.use(LanguageDetector) .use(LanguageDetector)
.use(initReactI18next) .use(initReactI18next)
.init({ .init({
resources, fallbackLng: FALLBACK_LNG,
fallbackLng: 'ru', supportedLngs: SUPPORTED_LANGS,
supportedLngs: ['ru', 'en', 'zh', 'fa'], partialBundledLanguages: true,
detection: { detection: {
order: ['localStorage', 'navigator'], order: ['localStorage', 'navigator'],
@@ -37,4 +48,15 @@ i18n
}, },
}); });
// Load detected language + fallback on startup
const detectedLng = i18n.language?.split('-')[0] || FALLBACK_LNG;
const langsToLoad = [FALLBACK_LNG, ...(detectedLng !== FALLBACK_LNG ? [detectedLng] : [])];
Promise.all(langsToLoad.map(loadLanguage));
// Lazy-load on language change
i18n.on('languageChanged', (lng: string) => {
const code = lng.split('-')[0];
loadLanguage(code);
});
export default i18n; export default i18n;

View File

@@ -171,6 +171,7 @@
"telegramRetryFailed": "Authorization failed. Close the app and try again.", "telegramRetryFailed": "Authorization failed. Close the app and try again.",
"telegramReopenHint": "If the problem persists, close and reopen the app", "telegramReopenHint": "If the problem persists, close and reopen the app",
"telegramNotConfigured": "Telegram bot is not configured", "telegramNotConfigured": "Telegram bot is not configured",
"telegramUnavailable": "Telegram login is temporarily unavailable",
"authenticating": "Authenticating...", "authenticating": "Authenticating...",
"orOpenInApp": "Or open the bot in the app", "orOpenInApp": "Or open the bot in the app",
"loginFailed": "Login Failed", "loginFailed": "Login Failed",
@@ -290,6 +291,9 @@
"inactive": "Inactive", "inactive": "Inactive",
"trialStatus": "Trial", "trialStatus": "Trial",
"expired": "Expired", "expired": "Expired",
"trafficLimited": "Traffic Exhausted",
"trafficLimitedTitle": "Traffic Limit Reached",
"trafficLimitedDescription": "Your traffic has been exhausted. Purchase additional traffic below to continue using VPN.",
"expiresAt": "Expires at", "expiresAt": "Expires at",
"daysLeft": "Days left", "daysLeft": "Days left",
"devices": "Devices", "devices": "Devices",

View File

@@ -163,6 +163,7 @@
"telegramRetryFailed": "تایید هویت ناموفق بود. برنامه را ببندید و دوباره باز کنید.", "telegramRetryFailed": "تایید هویت ناموفق بود. برنامه را ببندید و دوباره باز کنید.",
"telegramReopenHint": "اگر مشکل ادامه دارد، برنامه را ببندید و دوباره باز کنید", "telegramReopenHint": "اگر مشکل ادامه دارد، برنامه را ببندید و دوباره باز کنید",
"telegramNotConfigured": "ربات تلگرام پیکربندی نشده", "telegramNotConfigured": "ربات تلگرام پیکربندی نشده",
"telegramUnavailable": "ورود از طریق تلگرام موقتاً در دسترس نیست",
"authenticating": "در حال تایید هویت...", "authenticating": "در حال تایید هویت...",
"orOpenInApp": "یا ربات را در برنامه باز کنید", "orOpenInApp": "یا ربات را در برنامه باز کنید",
"loginFailed": "ورود ناموفق", "loginFailed": "ورود ناموفق",
@@ -250,6 +251,9 @@
"inactive": "غیرفعال", "inactive": "غیرفعال",
"trialStatus": "دوره آزمایشی", "trialStatus": "دوره آزمایشی",
"expired": "منقضی شده", "expired": "منقضی شده",
"trafficLimited": "ترافیک تمام شده",
"trafficLimitedTitle": "محدودیت ترافیک",
"trafficLimitedDescription": "ترافیک شما تمام شده است. برای ادامه استفاده از VPN، ترافیک اضافی خریداری کنید.",
"expiresAt": "تاریخ انقضا", "expiresAt": "تاریخ انقضا",
"daysLeft": "روز باقی‌مانده", "daysLeft": "روز باقی‌مانده",
"devices": "دستگاه‌ها", "devices": "دستگاه‌ها",

View File

@@ -174,6 +174,7 @@
"telegramRetryFailed": "Авторизация не удалась. Закройте приложение и откройте заново.", "telegramRetryFailed": "Авторизация не удалась. Закройте приложение и откройте заново.",
"telegramReopenHint": "Если проблема повторяется — закройте и откройте приложение заново", "telegramReopenHint": "Если проблема повторяется — закройте и откройте приложение заново",
"telegramNotConfigured": "Telegram бот не настроен", "telegramNotConfigured": "Telegram бот не настроен",
"telegramUnavailable": "Вход через Telegram временно недоступен",
"authenticating": "Авторизация...", "authenticating": "Авторизация...",
"orOpenInApp": "Или откройте бота в приложении", "orOpenInApp": "Или откройте бота в приложении",
"loginFailed": "Ошибка входа", "loginFailed": "Ошибка входа",
@@ -302,6 +303,9 @@
"inactive": "Неактивна", "inactive": "Неактивна",
"trialStatus": "Пробный период", "trialStatus": "Пробный период",
"expired": "Истекла", "expired": "Истекла",
"trafficLimited": "Трафик исчерпан",
"trafficLimitedTitle": "Трафик закончился",
"trafficLimitedDescription": "Ваш трафик исчерпан. Докупите дополнительный трафик ниже, чтобы продолжить пользоваться VPN.",
"expiresAt": "Действует до", "expiresAt": "Действует до",
"daysLeft": "Осталось дней", "daysLeft": "Осталось дней",
"devices": "Устройства", "devices": "Устройства",

View File

@@ -163,6 +163,7 @@
"telegramRetryFailed": "授权失败。请关闭应用后重新打开。", "telegramRetryFailed": "授权失败。请关闭应用后重新打开。",
"telegramReopenHint": "如果问题持续存在,请关闭并重新打开应用", "telegramReopenHint": "如果问题持续存在,请关闭并重新打开应用",
"telegramNotConfigured": "Telegram机器人未配置", "telegramNotConfigured": "Telegram机器人未配置",
"telegramUnavailable": "Telegram登录暂时不可用",
"authenticating": "正在验证...", "authenticating": "正在验证...",
"orOpenInApp": "或在应用中打开机器人", "orOpenInApp": "或在应用中打开机器人",
"loginFailed": "登录失败", "loginFailed": "登录失败",
@@ -250,6 +251,9 @@
"inactive": "无效", "inactive": "无效",
"trialStatus": "试用期", "trialStatus": "试用期",
"expired": "已过期", "expired": "已过期",
"trafficLimited": "流量已用尽",
"trafficLimitedTitle": "流量已达上限",
"trafficLimitedDescription": "您的流量已用尽。请在下方购买额外流量以继续使用VPN。",
"expiresAt": "到期时间", "expiresAt": "到期时间",
"daysLeft": "剩余天数", "daysLeft": "剩余天数",
"devices": "设备", "devices": "设备",

View File

@@ -119,6 +119,7 @@ function StatusBadge({ status }: { status: string }) {
deleted: 'bg-dark-600 text-dark-400 border-dark-500', deleted: 'bg-dark-600 text-dark-400 border-dark-500',
trial: 'bg-accent-500/20 text-accent-400 border-accent-500/30', trial: 'bg-accent-500/20 text-accent-400 border-accent-500/30',
expired: 'bg-warning-500/20 text-warning-400 border-warning-500/30', expired: 'bg-warning-500/20 text-warning-400 border-warning-500/30',
limited: 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30',
disabled: 'bg-dark-600 text-dark-400 border-dark-500', disabled: 'bg-dark-600 text-dark-400 border-dark-500',
}; };

View File

@@ -147,6 +147,8 @@ function UserRow({ user, onClick, formatAmount }: UserRowProps) {
? 'border-success-500/30 bg-success-500/20 text-success-400' ? 'border-success-500/30 bg-success-500/20 text-success-400'
: user.subscription_status === 'trial' : user.subscription_status === 'trial'
? 'border-accent-500/30 bg-accent-500/20 text-accent-400' ? 'border-accent-500/30 bg-accent-500/20 text-accent-400'
: user.subscription_status === 'limited'
? 'border-yellow-500/30 bg-yellow-500/20 text-yellow-400'
: 'border-warning-500/30 bg-warning-500/20 text-warning-400' : 'border-warning-500/30 bg-warning-500/20 text-warning-400'
}`} }`}
> >
@@ -154,6 +156,8 @@ function UserRow({ user, onClick, formatAmount }: UserRowProps) {
? t('admin.users.status.subscription') ? t('admin.users.status.subscription')
: user.subscription_status === 'trial' : user.subscription_status === 'trial'
? t('admin.users.status.trial') ? t('admin.users.status.trial')
: user.subscription_status === 'limited'
? t('subscription.trafficLimited')
: t('admin.users.status.expired')} : t('admin.users.status.expired')}
</span> </span>
)} )}

View File

@@ -247,7 +247,9 @@ export default function Dashboard() {
<div className="skeleton h-12 w-full rounded-xl" /> <div className="skeleton h-12 w-full rounded-xl" />
</div> </div>
</div> </div>
) : subscription?.is_expired || subscription?.status === 'disabled' ? ( ) : subscription?.is_expired ||
subscription?.status === 'disabled' ||
subscription?.is_limited ? (
<SubscriptionCardExpired <SubscriptionCardExpired
subscription={subscription} subscription={subscription}
balanceKopeks={balanceData?.balance_kopeks ?? 0} balanceKopeks={balanceData?.balance_kopeks ?? 0}

View File

@@ -518,23 +518,76 @@ export default function Subscription() {
style={{ style={{
background: subscription.is_active background: subscription.is_active
? `${zone.mainHex}15` ? `${zone.mainHex}15`
: subscription.is_limited
? 'rgba(255,184,0,0.12)'
: 'rgba(255,59,92,0.12)', : 'rgba(255,59,92,0.12)',
border: subscription.is_active border: subscription.is_active
? `1px solid ${zone.mainHex}30` ? `1px solid ${zone.mainHex}30`
: subscription.is_limited
? '1px solid rgba(255,184,0,0.25)'
: '1px solid rgba(255,59,92,0.25)', : '1px solid rgba(255,59,92,0.25)',
color: subscription.is_active ? zone.mainHex : '#FF3B5C', color: subscription.is_active
? zone.mainHex
: subscription.is_limited
? '#FFB800'
: '#FF3B5C',
}} }}
> >
{subscription.is_active {subscription.is_active
? subscription.is_trial ? subscription.is_trial
? t('subscription.trialStatus') ? t('subscription.trialStatus')
: t('subscription.active') : t('subscription.active')
: subscription.is_limited
? t('subscription.trafficLimited')
: subscription.status === 'disabled' : subscription.status === 'disabled'
? t('subscription.pause.suspended') ? t('subscription.pause.suspended')
: t('subscription.expired')} : t('subscription.expired')}
</span> </span>
</div> </div>
{/* ─── Traffic Limited Banner ─── */}
{subscription.is_limited && (
<div
className="mb-6 rounded-[14px] p-4"
style={{
background:
'linear-gradient(135deg, rgba(255,184,0,0.08), rgba(255,184,0,0.03))',
border: '1px solid rgba(255,184,0,0.2)',
}}
>
<div className="flex items-start gap-3">
<div
className="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-[10px]"
style={{ background: 'rgba(255,184,0,0.12)' }}
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="#FFB800"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
<line x1="12" y1="9" x2="12" y2="13" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
</div>
<div className="min-w-0 flex-1">
<p className="text-sm font-semibold" style={{ color: '#FFB800' }}>
{t('subscription.trafficLimitedTitle')}
</p>
<p className="mt-1 text-xs text-dark-400">
{t('subscription.trafficLimitedDescription')}
</p>
</div>
</div>
</div>
)}
{/* ─── Trial Info Banner ─── */} {/* ─── Trial Info Banner ─── */}
{subscription.is_trial && subscription.is_active && ( {subscription.is_trial && subscription.is_active && (
<div <div
@@ -988,7 +1041,9 @@ export default function Subscription() {
{t('subscription.pause.title')} {t('subscription.pause.title')}
</h2> </h2>
<div className="mt-1 text-[12px] text-dark-50/35"> <div className="mt-1 text-[12px] text-dark-50/35">
{subscription.status === 'disabled' {subscription.is_limited
? t('subscription.trafficLimited')
: subscription.status === 'disabled'
? t('subscription.pause.suspended') ? t('subscription.pause.suspended')
: subscription.is_daily_paused : subscription.is_daily_paused
? t('subscription.pause.paused') ? t('subscription.pause.paused')
@@ -1140,7 +1195,7 @@ export default function Subscription() {
{/* Additional Options (Buy Devices) */} {/* Additional Options (Buy Devices) */}
{subscription && {subscription &&
subscription.is_active && (subscription.is_active || subscription.is_limited) &&
!subscription.is_trial && !subscription.is_trial &&
subscription.device_limit !== 0 && ( subscription.device_limit !== 0 && (
<div <div

View File

@@ -91,6 +91,7 @@ export interface Subscription {
hide_subscription_link: boolean; hide_subscription_link: boolean;
is_active: boolean; is_active: boolean;
is_expired: boolean; is_expired: boolean;
is_limited: boolean;
traffic_purchases?: TrafficPurchase[]; traffic_purchases?: TrafficPurchase[];
// Daily tariff fields // Daily tariff fields
is_daily?: boolean; is_daily?: boolean;

View File

@@ -36,7 +36,6 @@ export default defineConfig({
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks(id) { manualChunks(id) {
if (id.includes('/src/locales/')) return 'locales';
if (!id.includes('node_modules')) return; if (!id.includes('node_modules')) return;
if ( if (
id.includes('react-dom') || id.includes('react-dom') ||