diff --git a/src/components/subscription/SubscriptionListCard.tsx b/src/components/subscription/SubscriptionListCard.tsx index 8cb2132..3918a2c 100644 --- a/src/components/subscription/SubscriptionListCard.tsx +++ b/src/components/subscription/SubscriptionListCard.tsx @@ -4,15 +4,10 @@ import { getGlassColors } from '../../utils/glassTheme'; import { useHaptic } from '../../platform'; import type { SubscriptionListItem } from '../../types'; -function formatTrafficDisplay(used: number, limit: number): string { - if (limit === 0) return '∞'; - return `${used.toFixed(1)} / ${limit} ГБ`; -} - -function formatDate(iso: string | null): string { +function formatDate(iso: string | null, locale?: string): string { if (!iso) return '—'; try { - return new Date(iso).toLocaleDateString('ru-RU', { + return new Date(iso).toLocaleDateString(locale ?? undefined, { day: '2-digit', month: '2-digit', year: 'numeric', @@ -22,14 +17,40 @@ function formatDate(iso: string | null): string { } } -function StatusDot({ status }: { status: string }) { - const color = - status === 'active' || status === 'trial' - ? 'bg-emerald-400' - : status === 'limited' - ? 'bg-amber-400' - : 'bg-red-400'; - return ; +function StatusBadge({ + status, + t, +}: { + status: string; + t: (key: string, fallback: string) => string; +}) { + const isActive = status === 'active' || status === 'trial'; + const isLimited = status === 'limited'; + const isExpired = status === 'expired' || status === 'disabled'; + + const color = isActive + ? 'bg-emerald-400/15 text-emerald-400 border-emerald-400/20' + : isLimited + ? 'bg-amber-400/15 text-amber-400 border-amber-400/20' + : 'bg-red-400/15 text-red-400 border-red-400/20'; + + const label = isActive + ? status === 'trial' + ? t('subscription.statusTrial', 'Тестовая') + : t('subscription.statusActive', 'Активна') + : isLimited + ? t('subscription.statusLimited', 'Ограничена') + : isExpired + ? t('subscription.statusExpired', 'Истекла') + : status; + + return ( + + {label} + + ); } export default function SubscriptionListCard({ @@ -39,7 +60,7 @@ export default function SubscriptionListCard({ subscription: SubscriptionListItem; onClick: () => void; }) { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const { isDark } = useTheme(); const g = getGlassColors(isDark); const { impact } = useHaptic(); @@ -49,54 +70,121 @@ export default function SubscriptionListCard({ onClick(); }; + const isActive = subscription.status === 'active' || subscription.status === 'trial'; + const isExpired = subscription.status === 'expired' || subscription.status === 'disabled'; + const trafficLimit = subscription.traffic_limit_gb; + const trafficUsed = subscription.traffic_used_gb; + const isUnlimited = trafficLimit === 0; + const trafficPercent = isUnlimited + ? 0 + : trafficLimit > 0 + ? Math.min(100, (trafficUsed / trafficLimit) * 100) + : 0; + const trafficColor = + trafficPercent >= 90 ? 'bg-red-400' : trafficPercent >= 70 ? 'bg-amber-400' : 'bg-emerald-400'; + return ( ); } diff --git a/src/pages/Subscription.tsx b/src/pages/Subscription.tsx index 4347dae..d177749 100644 --- a/src/pages/Subscription.tsx +++ b/src/pages/Subscription.tsx @@ -265,6 +265,7 @@ export default function Subscription() { mutationFn: () => subscriptionApi.togglePause(subscriptionId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); queryClient.invalidateQueries({ queryKey: ['balance'] }); }, }); @@ -290,6 +291,7 @@ export default function Subscription() { mutationFn: () => subscriptionApi.purchaseDevices(devicesToAdd, subscriptionId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); queryClient.invalidateQueries({ queryKey: ['devices', subscriptionId] }); queryClient.invalidateQueries({ queryKey: ['device-price'] }); setShowDeviceTopup(false); @@ -321,6 +323,7 @@ export default function Subscription() { mutationFn: () => subscriptionApi.reduceDevices(targetDeviceLimit, subscriptionId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); queryClient.invalidateQueries({ queryKey: ['devices', subscriptionId] }); queryClient.invalidateQueries({ queryKey: ['device-reduction-info', subscriptionId] }); setShowDeviceReduction(false); @@ -339,6 +342,7 @@ export default function Subscription() { mutationFn: (gb: number) => subscriptionApi.purchaseTraffic(gb, subscriptionId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); setShowTrafficTopup(false); setSelectedTrafficPackage(null); }, @@ -364,6 +368,7 @@ export default function Subscription() { mutationFn: (countries: string[]) => subscriptionApi.updateCountries(countries, subscriptionId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['subscription', subscriptionId] }); + queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] }); queryClient.invalidateQueries({ queryKey: ['countries', subscriptionId] }); setShowServerManagement(false); }, diff --git a/src/pages/Subscriptions.tsx b/src/pages/Subscriptions.tsx index 228766e..c328125 100644 --- a/src/pages/Subscriptions.tsx +++ b/src/pages/Subscriptions.tsx @@ -2,20 +2,47 @@ import { useQuery } from '@tanstack/react-query'; import { useNavigate } from 'react-router'; import { useTranslation } from 'react-i18next'; import { subscriptionApi } from '../api/subscription'; +import { useTheme } from '../hooks/useTheme'; +import { getGlassColors } from '../utils/glassTheme'; import SubscriptionListCard from '../components/subscription/SubscriptionListCard'; function EmptyState({ onBuy }: { onBuy: () => void }) { const { t } = useTranslation(); + const { isDark } = useTheme(); + const g = getGlassColors(isDark); + return ( -
-
📋
-

{t('subscriptions.empty', 'Нет подписок')}

-

+

+
+ + + +
+

+ {t('subscriptions.empty', 'Нет подписок')} +

+

{t('subscriptions.emptyDesc', 'У вас пока нет активных подписок')}

@@ -26,6 +53,8 @@ function EmptyState({ onBuy }: { onBuy: () => void }) { export default function Subscriptions() { const { t } = useTranslation(); const navigate = useNavigate(); + const { isDark } = useTheme(); + const g = getGlassColors(isDark); const { data, isLoading } = useQuery({ queryKey: ['subscriptions-list'], @@ -45,12 +74,18 @@ export default function Subscriptions() { return (
-

{t('subscriptions.title', 'Мои подписки')}

+

+ {t('subscriptions.title', 'Мои подписки')} +

{isLoading && (
{[1, 2].map((i) => ( -
+
))}
)} @@ -70,9 +105,18 @@ export default function Subscriptions() { {!isLoading && subscriptions.length > 0 && ( )}