From 5709ab4ff168539f3541f3f676844fc28fc82fda Mon Sep 17 00:00:00 2001 From: Egor Date: Wed, 21 Jan 2026 09:08:08 +0300 Subject: [PATCH] Add files via upload --- src/pages/Dashboard.tsx | 34 +++++++++++++-- src/pages/Subscription.tsx | 89 +++++++++++++++++++++++++++++++++++--- 2 files changed, 114 insertions(+), 9 deletions(-) diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 99880ae..2dfde26 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useMemo } from 'react' +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' @@ -145,18 +145,19 @@ export default function Dashboard() { traffic_used_percent: data.traffic_used_percent, is_unlimited: data.is_unlimited, }) + // Save last refresh timestamp to localStorage + localStorage.setItem('traffic_refresh_ts', Date.now().toString()) if (data.rate_limited && data.retry_after_seconds) { setTrafficRefreshCooldown(data.retry_after_seconds) } else { - setTrafficRefreshCooldown(60) + setTrafficRefreshCooldown(30) } - // Also update subscription query cache queryClient.invalidateQueries({ queryKey: ['subscription'] }) }, onError: (error: { response?: { status?: number; headers?: { get?: (key: string) => string } } }) => { if (error.response?.status === 429) { const retryAfter = error.response.headers?.get?.('Retry-After') - setTrafficRefreshCooldown(retryAfter ? parseInt(retryAfter, 10) : 60) + setTrafficRefreshCooldown(retryAfter ? parseInt(retryAfter, 10) : 30) } }, }) @@ -170,6 +171,31 @@ export default function Dashboard() { return () => clearInterval(timer) }, [trafficRefreshCooldown]) + // Track if we've already triggered auto-refresh this session + const hasAutoRefreshed = useRef(false) + + // Auto-refresh traffic on mount (with 30s caching) + useEffect(() => { + if (!subscription) return + if (hasAutoRefreshed.current) return + hasAutoRefreshed.current = true + + const lastRefresh = localStorage.getItem('traffic_refresh_ts') + const now = Date.now() + const cacheMs = 30 * 1000 + + if (lastRefresh && now - parseInt(lastRefresh, 10) < cacheMs) { + const elapsed = now - parseInt(lastRefresh, 10) + const remaining = Math.ceil((cacheMs - elapsed) / 1000) + if (remaining > 0) { + setTrafficRefreshCooldown(remaining) + } + return + } + + refreshTrafficMutation.mutate() + }, [subscription]) + const hasNoSubscription = !subscription && !subLoading && subError // Show onboarding for new users after data loads diff --git a/src/pages/Subscription.tsx b/src/pages/Subscription.tsx index 2634932..e09fb50 100644 --- a/src/pages/Subscription.tsx +++ b/src/pages/Subscription.tsx @@ -100,6 +100,14 @@ export default function Subscription() { const [showServerManagement, setShowServerManagement] = useState(false) const [selectedServersToUpdate, setSelectedServersToUpdate] = useState([]) + // Traffic refresh state + const [trafficRefreshCooldown, setTrafficRefreshCooldown] = useState(0) + const [trafficData, setTrafficData] = useState<{ + traffic_used_gb: number + traffic_used_percent: number + is_unlimited: boolean + } | null>(null) + const { data: subscription, isLoading } = useQuery({ queryKey: ['subscription'], queryFn: subscriptionApi.getSubscription, @@ -326,6 +334,65 @@ export default function Subscription() { }, }) + // Traffic refresh mutation + const refreshTrafficMutation = useMutation({ + mutationFn: subscriptionApi.refreshTraffic, + onSuccess: (data) => { + setTrafficData({ + traffic_used_gb: data.traffic_used_gb, + traffic_used_percent: data.traffic_used_percent, + is_unlimited: data.is_unlimited, + }) + localStorage.setItem('traffic_refresh_ts', Date.now().toString()) + if (data.rate_limited && data.retry_after_seconds) { + setTrafficRefreshCooldown(data.retry_after_seconds) + } else { + setTrafficRefreshCooldown(30) + } + queryClient.invalidateQueries({ queryKey: ['subscription'] }) + }, + onError: (error: { response?: { status?: number; headers?: { get?: (key: string) => string } } }) => { + if (error.response?.status === 429) { + const retryAfter = error.response.headers?.get?.('Retry-After') + setTrafficRefreshCooldown(retryAfter ? parseInt(retryAfter, 10) : 30) + } + }, + }) + + // Track if we've already triggered auto-refresh this session + const hasAutoRefreshed = useRef(false) + + // Cooldown timer for traffic refresh + useEffect(() => { + if (trafficRefreshCooldown <= 0) return + const timer = setInterval(() => { + setTrafficRefreshCooldown((prev) => Math.max(0, prev - 1)) + }, 1000) + return () => clearInterval(timer) + }, [trafficRefreshCooldown]) + + // Auto-refresh traffic on mount (with 30s caching) + useEffect(() => { + if (!subscription) return + if (hasAutoRefreshed.current) return + hasAutoRefreshed.current = true + + const lastRefresh = localStorage.getItem('traffic_refresh_ts') + const now = Date.now() + const cacheMs = 30 * 1000 + + if (lastRefresh && now - parseInt(lastRefresh, 10) < cacheMs) { + const elapsed = now - parseInt(lastRefresh, 10) + const remaining = Math.ceil((cacheMs - elapsed) / 1000) + if (remaining > 0) { + setTrafficRefreshCooldown(remaining) + } + return + } + + refreshTrafficMutation.mutate() + }, [subscription]) + // Auto-scroll to switch tariff modal when it appears useEffect(() => { if (switchTariffId && switchModalRef.current) { @@ -495,9 +562,21 @@ export default function Subscription() {
-
{t('subscription.traffic')}
+
+ {t('subscription.traffic')} + +
- {subscription.traffic_used_gb.toFixed(1)} / {subscription.traffic_limit_gb || '∞'} GB + {(trafficData?.traffic_used_gb ?? subscription.traffic_used_gb).toFixed(1)} / {subscription.traffic_limit_gb || '∞'} GB
@@ -531,12 +610,12 @@ export default function Subscription() {
{t('subscription.trafficUsed')} - {subscription.traffic_used_percent.toFixed(1)}% + {(trafficData?.traffic_used_percent ?? subscription.traffic_used_percent).toFixed(1)}%