import { useState, useEffect, useMemo } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { Link } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { DotLottieReact } from '@lottiefiles/dotlottie-react' import { useAuthStore } from '../store/auth' import { subscriptionApi } from '../api/subscription' import { referralApi } from '../api/referral' import { balanceApi } from '../api/balance' import { wheelApi } from '../api/wheel' import ConnectionModal from '../components/ConnectionModal' import Onboarding, { useOnboarding } from '../components/Onboarding' import PromoOffersSection from '../components/PromoOffersSection' import { useCurrency } from '../hooks/useCurrency' // Icons const ArrowRightIcon = () => ( ) const SparklesIcon = () => ( ) const ChevronRightIcon = () => ( ) const SupportLottieIcon = () => (
) export default function Dashboard() { const { t } = useTranslation() const { user, refreshUser } = useAuthStore() const queryClient = useQueryClient() const { formatAmount, currencySymbol, formatPositive } = useCurrency() const [trialError, setTrialError] = useState(null) const [showConnectionModal, setShowConnectionModal] = useState(false) const { isCompleted: isOnboardingCompleted, complete: completeOnboarding } = useOnboarding() const [showOnboarding, setShowOnboarding] = useState(false) // Refresh user data on mount useEffect(() => { refreshUser() }, []) // Fetch balance from API with no caching const { data: balanceData } = useQuery({ queryKey: ['balance'], queryFn: balanceApi.getBalance, staleTime: 0, refetchOnMount: 'always', }) const { data: subscription, isLoading: subLoading, error: subError } = useQuery({ queryKey: ['subscription'], queryFn: subscriptionApi.getSubscription, retry: false, staleTime: 0, refetchOnMount: 'always', }) const { data: trialInfo, isLoading: trialLoading } = useQuery({ queryKey: ['trial-info'], queryFn: subscriptionApi.getTrialInfo, enabled: !subscription && !subLoading, }) const { data: referralInfo, isLoading: refLoading } = useQuery({ queryKey: ['referral-info'], queryFn: referralApi.getReferralInfo, }) // Fetch wheel config to show banner if enabled const { data: wheelConfig } = useQuery({ queryKey: ['wheel-config'], queryFn: wheelApi.getConfig, staleTime: 60000, // 1 minute retry: false, }) const activateTrialMutation = useMutation({ mutationFn: subscriptionApi.activateTrial, onSuccess: () => { setTrialError(null) queryClient.invalidateQueries({ queryKey: ['subscription'] }) queryClient.invalidateQueries({ queryKey: ['trial-info'] }) refreshUser() }, onError: (error: { response?: { data?: { detail?: string } } }) => { setTrialError(error.response?.data?.detail || t('common.error')) }, }) const hasNoSubscription = !subscription && !subLoading && subError // Show onboarding for new users after data loads useEffect(() => { if (!isOnboardingCompleted && !subLoading && !refLoading) { // Small delay to ensure DOM is ready const timer = setTimeout(() => setShowOnboarding(true), 500) return () => clearTimeout(timer) } }, [isOnboardingCompleted, subLoading, refLoading]) // Define onboarding steps based on available data const onboardingSteps = useMemo(() => { type Placement = 'top' | 'bottom' | 'left' | 'right' const steps: Array<{ target: string title: string description: string placement: Placement }> = [ { target: 'welcome', title: t('onboarding.steps.welcome.title'), description: t('onboarding.steps.welcome.description'), placement: 'bottom', }, { target: 'balance', title: t('onboarding.steps.balance.title'), description: t('onboarding.steps.balance.description'), placement: 'bottom', }, { target: 'subscription-status', title: t('onboarding.steps.subscription.title'), description: t('onboarding.steps.subscription.description'), placement: 'bottom', }, ] // Add connect devices step only if subscription exists if (subscription?.subscription_url) { steps.splice(1, 0, { target: 'connect-devices', title: t('onboarding.steps.connectDevices.title'), description: t('onboarding.steps.connectDevices.description'), placement: 'bottom', }) } steps.push({ target: 'quick-actions', title: t('onboarding.steps.quickActions.title'), description: t('onboarding.steps.quickActions.description'), placement: 'top', }) return steps }, [t, subscription]) const handleOnboardingComplete = () => { setShowOnboarding(false) completeOnboarding() } // Calculate traffic percentage color const getTrafficColor = (percent: number) => { if (percent > 90) return 'bg-error-500' if (percent > 70) return 'bg-warning-500' return 'bg-success-500' } return (
{/* Header */}

{t('dashboard.welcome', { name: user?.first_name || user?.username || '' })}

{t('dashboard.yourSubscription')}

{/* Subscription Status - Main Card */} {subscription && (

{t('subscription.status')}

{subscription.is_active ? t('subscription.active') : t('subscription.expired')}
{t('subscription.expiresAt')}
{new Date(subscription.end_date).toLocaleDateString()}
{t('subscription.traffic')}
{subscription.traffic_used_gb.toFixed(1)} / {subscription.traffic_limit_gb || '∞'} GB
{t('subscription.devices')}
{subscription.device_limit}
{t('subscription.timeLeft')}
{subscription.days_left > 0 ? `${subscription.days_left} ${t('subscription.days')}` : `${subscription.hours_left}${t('subscription.hours')} ${subscription.minutes_left}${t('subscription.minutes')}` }
{/* Traffic Progress */} {subscription.traffic_limit_gb > 0 && (
{t('subscription.trafficUsed')} {subscription.traffic_used_percent.toFixed(1)}%
)}
{t('dashboard.viewSubscription')} {subscription.subscription_url && ( )}
)} {/* Stats Grid */}
{/* Balance */}
{t('balance.currentBalance')}
{formatAmount(balanceData?.balance_rubles || 0)} {currencySymbol}
{/* Subscription */}
{t('subscription.title')}
{subLoading ? (
) : subscription ? (
{subscription.days_left > 0 ? ( <> {subscription.days_left} {t('subscription.days')} ) : subscription.hours_left > 0 ? ( <> {subscription.hours_left} {t('subscription.hours')} ) : subscription.minutes_left > 0 ? ( <> {subscription.minutes_left} {t('subscription.minutes')} ) : ( {t('subscription.expired')} )}
) : (
{t('subscription.inactive')}
)} {/* Referrals */}
{t('referral.stats.totalReferrals')}
{refLoading ? (
) : (
{referralInfo?.total_referrals || 0}
)} {/* Earnings */}
{t('referral.stats.totalEarnings')}
{refLoading ? (
) : (
{formatPositive(referralInfo?.total_earnings_rubles || 0)}
)}
{/* Trial Activation */} {hasNoSubscription && !trialLoading && trialInfo?.is_available && (

{t('subscription.trial.title', 'Free Trial')}

{t('subscription.trial.description', 'Try our VPN service for free!')}

{trialInfo.duration_days}
{t('subscription.trial.days', 'days')}
{trialInfo.traffic_limit_gb}
GB
{trialInfo.device_limit}
{t('subscription.trial.devices', 'devices')}
{trialInfo.requires_payment && trialInfo.price_rubles > 0 && (

{t('subscription.trial.price', 'Activation price')}: {trialInfo.price_rubles.toFixed(2)} ₽

)} {trialError && (
{trialError}
)}
)} {/* Promo Offers */} {/* Fortune Wheel Banner */} {wheelConfig?.is_enabled && (
{/* Emoji */} 🎰

{t('wheel.banner.title')}

{t('wheel.banner.description')}

)} {/* Quick Actions */}

{t('dashboard.quickActions')}

{t('dashboard.topUpBalance')} {t('subscription.renew')} {t('dashboard.inviteFriends')} {t('dashboard.getSupport')}
{/* Connection Modal */} {showConnectionModal && ( setShowConnectionModal(false)} /> )} {/* Onboarding Tutorial */} {showOnboarding && ( )}
) }