From f4d7a2cc8d20301108d86654ff03250206536cf3 Mon Sep 17 00:00:00 2001 From: Fringg Date: Fri, 27 Feb 2026 06:04:06 +0300 Subject: [PATCH] fix: subscription UI improvements - expired card, duplicate badges, live countdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SubscriptionCardExpired: use trialTitle for expired trials, remove traffic/devices grid, show only expiry date - Subscription page: remove duplicate trial badge from zone indicator, fix expired trial badge showing "Пробный период" instead of "Истекла" - Subscription page: replace static days counter with live HH:MM:SS countdown timer, merge "Действует до" into countdown block - Subscription page: remove duplicate traffic percentage block - Handle expired state in countdown (show "Истекла" in red instead of 00:00:00) - Use live countdown.days for urgency threshold instead of stale server value --- .../dashboard/SubscriptionCardExpired.tsx | 28 +-- src/pages/Subscription.tsx | 229 ++++++++++-------- 2 files changed, 140 insertions(+), 117 deletions(-) diff --git a/src/components/dashboard/SubscriptionCardExpired.tsx b/src/components/dashboard/SubscriptionCardExpired.tsx index e9817b9..1a18c5a 100644 --- a/src/components/dashboard/SubscriptionCardExpired.tsx +++ b/src/components/dashboard/SubscriptionCardExpired.tsx @@ -77,7 +77,9 @@ export default function SubscriptionCardExpired({ subscription }: SubscriptionCa

- {t('dashboard.expired.title')} + {subscription.is_trial + ? t('dashboard.expired.trialTitle') + : t('dashboard.expired.title')}

{subscription.is_trial @@ -117,27 +119,21 @@ export default function SubscriptionCardExpired({ subscription }: SubscriptionCa
- {/* Expired info grid */} + {/* Expired date */}
- {[ - { label: t('dashboard.expired.traffic'), value: '0 GB' }, - { label: t('dashboard.expired.devices'), value: '0' }, - { label: t('dashboard.expired.expiredDate'), value: formattedDate }, - ].map((item, i) => ( -
-
- {item.label} -
-
{item.value}
-
- ))} +
+ {t('dashboard.expired.expiredDate')} +
+
+ {formattedDate} +
{/* Action buttons */} diff --git a/src/pages/Subscription.tsx b/src/pages/Subscription.tsx index a416c3f..a8de6b8 100644 --- a/src/pages/Subscription.tsx +++ b/src/pages/Subscription.tsx @@ -187,6 +187,25 @@ export default function Subscription() { // Extract subscription from response (null if no subscription) const subscription = subscriptionResponse?.subscription ?? null; + // Live countdown timer + const [countdown, setCountdown] = useState({ days: 0, hours: 0, minutes: 0, seconds: 0 }); + useEffect(() => { + if (!subscription?.end_date) return; + const endTime = new Date(subscription.end_date).getTime(); + const tick = () => { + const diff = Math.max(0, endTime - Date.now()); + setCountdown({ + days: Math.floor(diff / 86_400_000), + hours: Math.floor((diff % 86_400_000) / 3_600_000), + minutes: Math.floor((diff % 3_600_000) / 60_000), + seconds: Math.floor((diff % 60_000) / 1_000), + }); + }; + tick(); + const id = setInterval(tick, 1000); + return () => clearInterval(id); + }, [subscription?.end_date]); + const { data: purchaseOptions, isLoading: optionsLoading } = useQuery({ queryKey: ['purchase-options'], queryFn: subscriptionApi.getPurchaseOptions, @@ -679,7 +698,6 @@ export default function Subscription() { const zone = getTrafficZone(usedPercent); const connectedDevices = devicesData?.total ?? 0; const formattedDate = new Date(subscription.end_date).toLocaleDateString(); - const daysLeft = subscription.days_left; return (
{isUnlimited ? t('dashboard.unlimited') : t(zone.labelKey)} - {subscription.is_trial && ( - - {t('subscription.trialStatus')} - - )}
{/* Plan name */} @@ -770,11 +775,11 @@ export default function Subscription() { color: subscription.is_active ? zone.mainHex : '#FF3B5C', }} > - {subscription.is_trial - ? t('subscription.trialStatus') - : subscription.is_active - ? t('subscription.active') - : t('subscription.expired')} + {subscription.is_active + ? subscription.is_trial + ? t('subscription.trialStatus') + : t('subscription.active') + : t('subscription.expired')} @@ -992,57 +997,114 @@ export default function Subscription() { {/* ─── Stats Row ─── */}
- {/* Days left */} -
-
+ {/* Countdown timer */} + {(() => { + const isUrgent = countdown.days <= 3; + const isExpired = !subscription.is_active; + return (
- +
+
+ +
+ {t('dashboard.remaining')} +
+ {isExpired ? ( +
+ {t('subscription.expired')} +
+ ) : ( +
+ {countdown.days > 0 && ( + <> + + {countdown.days} + + + {t('subscription.daysShort')} + + + )} + + {String(countdown.hours).padStart(2, '0')} + + + : + + + {String(countdown.minutes).padStart(2, '0')} + + + : + + + {String(countdown.seconds).padStart(2, '0')} + +
+ )} +
+ {t('subscription.expiresAt')}: {formattedDate} +
- {t('dashboard.remaining')} -
-
- - {daysLeft > 0 - ? daysLeft - : subscription.hours_left > 0 - ? `${subscription.hours_left}h` - : `${subscription.minutes_left}m`} - - - {daysLeft > 0 ? t('subscription.daysShort') : ''} - -
-
+ ); + })()} {/* Devices */}
-
+
- - {/* Expires */} -
-
- {t('subscription.expiresAt')} -
-
- {formattedDate} -
-
- - {/* Traffic */} -
-
- {t('subscription.traffic')} -
-
- {isUnlimited ? '∞' : `${usedPercent.toFixed(0)}%`} -
-
{/* ─── Locations ─── */}