diff --git a/src/api/subscription.ts b/src/api/subscription.ts index 4371ab0..c039ed2 100644 --- a/src/api/subscription.ts +++ b/src/api/subscription.ts @@ -321,4 +321,24 @@ export const subscriptionApi = { const response = await apiClient.put('/cabinet/subscription/traffic', { gb }) return response.data }, + + // Refresh traffic usage from RemnaWave (rate limited: 1 per 60 seconds) + refreshTraffic: async (): Promise<{ + success: boolean + cached: boolean + rate_limited?: boolean + retry_after_seconds?: number + source?: string + traffic_used_bytes: number + traffic_used_gb: number + traffic_limit_bytes: number + traffic_limit_gb: number + traffic_used_percent: number + is_unlimited: boolean + lifetime_used_bytes?: number + lifetime_used_gb?: number + }> => { + const response = await apiClient.post('/cabinet/subscription/refresh-traffic') + return response.data + }, } diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 46208c8..2c20823 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -32,6 +32,12 @@ const ChevronRightIcon = () => ( ) +const RefreshIcon = ({ className = "w-4 h-4" }: { className?: string }) => ( + +) + // Check if device might be low-performance (Telegram WebApp on mobile) const isLowPerfDevice = (() => { const isTelegramWebApp = !!(window as unknown as { Telegram?: { WebApp?: unknown } }).Telegram?.WebApp @@ -123,6 +129,47 @@ export default function Dashboard() { }, }) + // Traffic refresh state and mutation + const [trafficRefreshCooldown, setTrafficRefreshCooldown] = useState(0) + const [trafficData, setTrafficData] = useState<{ + traffic_used_gb: number + traffic_used_percent: number + is_unlimited: boolean + } | null>(null) + + 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, + }) + if (data.rate_limited && data.retry_after_seconds) { + setTrafficRefreshCooldown(data.retry_after_seconds) + } else { + setTrafficRefreshCooldown(60) + } + // 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) + } + }, + }) + + // Cooldown timer + useEffect(() => { + if (trafficRefreshCooldown <= 0) return + const timer = setInterval(() => { + setTrafficRefreshCooldown((prev) => Math.max(0, prev - 1)) + }, 1000) + return () => clearInterval(timer) + }, [trafficRefreshCooldown]) + const hasNoSubscription = !subscription && !subLoading && subError // Show onboarding for new users after data loads @@ -223,9 +270,19 @@ export default function Dashboard() {