mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
feat: add LIMITED subscription status support with traffic-exhausted UX
- Add is_limited to Subscription type - Show yellow traffic-exhausted badge on Subscription and AdminUsers pages - SubscriptionCardExpired: amber theme with warning icon and buy-traffic CTA for limited - Dashboard: route limited subscriptions to SubscriptionCardExpired - Subscription page: show additional options (buy traffic) for limited subs - Use is_limited boolean instead of status string comparison - Add trafficLimited translations for ru, en, zh, fa - AdminUserDetail StatusBadge: add limited yellow styling
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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": "دستگاهها",
|
||||||
|
|||||||
@@ -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": "Устройства",
|
||||||
|
|||||||
@@ -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": "设备",
|
||||||
|
|||||||
@@ -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',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user