import { useState, useRef, useEffect } from 'react'; import { Link, useNavigate } from 'react-router'; import { useTranslation } from 'react-i18next'; import { usePlatform } from '@/platform'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { motion, AnimatePresence } from 'framer-motion'; import { useAuthStore } from '../store/auth'; import { authApi } from '../api/auth'; import { isValidEmail } from '../utils/validation'; import { notificationsApi, NotificationSettings, NotificationSettingsUpdate, } from '../api/notifications'; import { referralApi } from '../api/referral'; import { brandingApi, type EmailAuthEnabled } from '../api/branding'; import { UI } from '../config/constants'; import { Card } from '@/components/data-display/Card'; import { Button } from '@/components/primitives/Button'; import { Switch } from '@/components/primitives/Switch'; import { staggerContainer, staggerItem } from '@/components/motion/transitions'; // Icons const CopyIcon = () => ( ); const CheckIcon = () => ( ); const ShareIcon = () => ( ); const ArrowRightIcon = () => ( ); const PencilIcon = () => ( ); export default function Profile() { const { t } = useTranslation(); const navigate = useNavigate(); const user = useAuthStore((state) => state.user); const setUser = useAuthStore((state) => state.setUser); const queryClient = useQueryClient(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const [copied, setCopied] = useState(false); // Inline email change flow const [changeEmailStep, setChangeEmailStep] = useState<'email' | 'code' | 'success' | null>(null); const [newEmail, setNewEmail] = useState(''); const [changeCode, setChangeCode] = useState(''); const [changeError, setChangeError] = useState(null); const [resendCooldown, setResendCooldown] = useState(0); const [verificationResendCooldown, setVerificationResendCooldown] = useState(0); const newEmailInputRef = useRef(null); const codeInputRef = useRef(null); // Referral data const { data: referralInfo } = useQuery({ queryKey: ['referral-info'], queryFn: referralApi.getReferralInfo, }); const { data: referralTerms } = useQuery({ queryKey: ['referral-terms'], queryFn: referralApi.getReferralTerms, }); const { data: branding } = useQuery({ queryKey: ['branding'], queryFn: brandingApi.getBranding, staleTime: 60000, }); // Check if email auth is enabled const { data: emailAuthConfig } = useQuery({ queryKey: ['email-auth-enabled'], queryFn: brandingApi.getEmailAuthEnabled, staleTime: 60000, }); const isEmailAuthEnabled = emailAuthConfig?.enabled ?? true; const isEmailVerificationEnabled = emailAuthConfig?.verification_enabled ?? true; // Build referral link for cabinet const referralLink = referralInfo?.referral_code ? `${window.location.origin}/login?ref=${referralInfo.referral_code}` : ''; const copyReferralLink = () => { if (referralLink) { navigator.clipboard.writeText(referralLink); setCopied(true); setTimeout(() => setCopied(false), 2000); } }; const shareReferralLink = () => { if (!referralLink) return; const shareText = t('referral.shareMessage', { percent: referralInfo?.commission_percent || 0, botName: branding?.name || import.meta.env.VITE_APP_NAME || 'Cabinet', }); if (navigator.share) { navigator .share({ title: t('referral.title'), text: shareText, url: referralLink, }) .catch(() => {}); return; } const telegramUrl = `https://t.me/share/url?url=${encodeURIComponent(referralLink)}&text=${encodeURIComponent(shareText)}`; window.open(telegramUrl, '_blank', 'noopener,noreferrer'); }; const registerEmailMutation = useMutation({ mutationFn: ({ email, password }: { email: string; password: string }) => authApi.registerEmail(email, password), onSuccess: async (response) => { // Backend returns merge_required when email belongs to another user if (response.merge_required && response.merge_token) { navigate(`/merge/${response.merge_token}`, { replace: true }); return; } setSuccess(t('profile.emailSent')); setError(null); setEmail(''); setPassword(''); setConfirmPassword(''); const updatedUser = await authApi.getMe(); setUser(updatedUser); queryClient.invalidateQueries({ queryKey: ['user'] }); }, onError: (err: { response?: { data?: { detail?: string } } }) => { const detail = err.response?.data?.detail; if (detail?.includes('already registered')) { setError(t('profile.emailAlreadyRegistered')); } else if (detail?.includes('already have a verified email')) { setError(t('profile.alreadyHaveEmail')); } else { setError(detail || t('common.error')); } setSuccess(null); }, }); const resendVerificationMutation = useMutation({ mutationFn: authApi.resendVerification, onSuccess: () => { setSuccess(t('profile.verificationResent')); setError(null); setVerificationResendCooldown(UI.RESEND_COOLDOWN_SEC); }, onError: (err: { response?: { data?: { detail?: string } } }) => { setError(err.response?.data?.detail || t('common.error')); setSuccess(null); }, }); // Email change mutations const requestEmailChangeMutation = useMutation({ mutationFn: (emailAddr: string) => authApi.requestEmailChange(emailAddr), onSuccess: async (data) => { setChangeError(null); if (data.expires_in_minutes === 0) { // Unverified email was replaced directly setChangeEmailStep('success'); const updatedUser = await authApi.getMe(); setUser(updatedUser); } else { setChangeEmailStep('code'); setResendCooldown(UI.RESEND_COOLDOWN_SEC); } }, onError: (err: { response?: { data?: { detail?: string } } }) => { const detail = err.response?.data?.detail; if (detail?.includes('already registered') || detail?.includes('already in use')) { setChangeError(t('profile.changeEmail.emailAlreadyUsed')); } else if (detail?.includes('same as current')) { setChangeError(t('profile.changeEmail.sameEmail')); } else if (detail?.includes('rate limit') || detail?.includes('too many')) { setChangeError(t('profile.changeEmail.tooManyRequests')); } else { setChangeError(detail || t('common.error')); } }, }); const verifyEmailChangeMutation = useMutation({ mutationFn: (verificationCode: string) => authApi.verifyEmailChange(verificationCode), onSuccess: async () => { setChangeError(null); setChangeEmailStep('success'); const updatedUser = await authApi.getMe(); setUser(updatedUser); queryClient.invalidateQueries({ queryKey: ['user'] }); }, onError: (err: { response?: { data?: { detail?: string } } }) => { const detail = err.response?.data?.detail; if (detail?.includes('invalid') || detail?.includes('wrong')) { setChangeError(t('profile.changeEmail.invalidCode')); } else if (detail?.includes('expired')) { setChangeError(t('profile.changeEmail.codeExpired')); } else { setChangeError(detail || t('common.error')); } }, }); // Resend cooldown timers useEffect(() => { if (resendCooldown <= 0) return; const timer = setInterval(() => { setResendCooldown((prev) => Math.max(0, prev - 1)); }, 1000); return () => clearInterval(timer); }, [resendCooldown]); useEffect(() => { if (verificationResendCooldown <= 0) return; const timer = setInterval(() => { setVerificationResendCooldown((prev) => Math.max(0, prev - 1)); }, 1000); return () => clearInterval(timer); }, [verificationResendCooldown]); // Auto-focus inputs on step change (skip on Telegram — keyboard hides bottom nav) const { platform: profilePlatform } = usePlatform(); useEffect(() => { if (profilePlatform === 'telegram') return; const timer = setTimeout(() => { if (changeEmailStep === 'email') newEmailInputRef.current?.focus(); else if (changeEmailStep === 'code') codeInputRef.current?.focus(); }, 100); return () => clearTimeout(timer); }, [changeEmailStep, profilePlatform]); // Auto-close success after 3s useEffect(() => { if (changeEmailStep !== 'success') return; const timer = setTimeout(() => resetChangeEmail(), 3000); return () => clearTimeout(timer); }, [changeEmailStep]); const resetChangeEmail = () => { setChangeEmailStep(null); setNewEmail(''); setChangeCode(''); setChangeError(null); setResendCooldown(0); }; const handleSendChangeCode = () => { setChangeError(null); if (!newEmail.trim()) { setChangeError(t('profile.emailRequired')); return; } if (!isValidEmail(newEmail.trim())) { setChangeError(t('profile.invalidEmail')); return; } if (user?.email && newEmail.toLowerCase().trim() === user.email.toLowerCase()) { setChangeError(t('profile.changeEmail.sameEmail')); return; } requestEmailChangeMutation.mutate(newEmail.trim()); }; const handleVerifyChangeCode = () => { setChangeError(null); if (!changeCode.trim()) { setChangeError(t('profile.changeEmail.enterCode')); return; } if (changeCode.trim().length < 4) { setChangeError(t('profile.changeEmail.invalidCode')); return; } verifyEmailChangeMutation.mutate(changeCode.trim()); }; const handleResendChangeCode = () => { if (resendCooldown > 0) return; requestEmailChangeMutation.mutate(newEmail.trim()); }; const { data: notificationSettings, isLoading: notificationsLoading } = useQuery({ queryKey: ['notification-settings'], queryFn: notificationsApi.getSettings, }); const updateNotificationsMutation = useMutation({ mutationFn: notificationsApi.updateSettings, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['notification-settings'] }); }, }); const handleNotificationToggle = (key: keyof NotificationSettings, value: boolean) => { const update: NotificationSettingsUpdate = { [key]: value }; updateNotificationsMutation.mutate(update); }; const handleNotificationValue = (key: keyof NotificationSettings, value: number) => { const update: NotificationSettingsUpdate = { [key]: value }; updateNotificationsMutation.mutate(update); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); setError(null); setSuccess(null); if (!email.trim() || !isValidEmail(email.trim())) { setError(t('profile.invalidEmail', 'Please enter a valid email address')); return; } if (!password || password.length < 8) { setError(t('profile.passwordMinLength')); return; } if (password !== confirmPassword) { setError(t('profile.passwordsMismatch')); return; } registerEmailMutation.mutate({ email, password }); }; return (

{t('profile.title')}

{/* User Info Card */}

{t('profile.accountInfo')}

{t('profile.telegramId')} {user?.telegram_id}
{user?.username && (
{t('profile.username')} @{user.username}
)}
{t('profile.name')} {user?.first_name} {user?.last_name}
{t('profile.registeredAt')} {user?.created_at ? new Date(user.created_at).toLocaleDateString() : '-'}
{/* Connected Accounts Link */} navigate('/profile/accounts')}>

{t('profile.accounts.goToAccounts')}

{t('profile.accounts.subtitle')}

{/* Referral Link Widget */} {referralTerms?.is_enabled && referralLink && (

{t('referral.yourLink')}

{t('referral.title')}

{t('referral.shareHint', { percent: referralInfo?.commission_percent || 0 })}

)} {/* Email Section - only show when email auth is enabled */} {isEmailAuthEnabled && (

{t('profile.emailAuth')}

{user?.email ? (
Email
{user.email} {user.email_verified ? ( {t('profile.verified')} ) : isEmailVerificationEnabled ? ( {t('profile.notVerified')} ) : null}
{!user.email_verified && isEmailVerificationEnabled && (

{t('profile.verificationRequired')}

)} {user.email_verified && (

{t('profile.canLoginWithEmail')}

)} {/* Inline email change flow */} {changeEmailStep === 'email' && (
setNewEmail(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleSendChangeCode(); } }} placeholder="new@email.com" className="input w-full" autoComplete="email" /> {changeError &&

{changeError}

}
)} {changeEmailStep === 'code' && (

{t('profile.changeEmail.codeSentTo', { email: newEmail })}

setChangeCode(e.target.value.replace(/\D/g, ''))} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleVerifyChangeCode(); } }} placeholder="000000" maxLength={6} className="input w-full text-center text-2xl tracking-[0.5em]" autoComplete="one-time-code" /> {changeError &&

{changeError}

}
)} {changeEmailStep === 'success' && (

{t('profile.changeEmail.success')}

{newEmail}

)}
) : (

{t('profile.linkEmailDescription')}

setEmail(e.target.value)} placeholder="your@email.com" className="input" />
setPassword(e.target.value)} placeholder={t('profile.passwordPlaceholder')} className="input" />

{t('profile.passwordHint')}

setConfirmPassword(e.target.value)} placeholder={t('profile.confirmPasswordPlaceholder')} className="input" />
{error && (
{error}
)} {success && (
{success}
)}
)} {(error || success) && user?.email && (
{error && (
{error}
)} {success && (
{success}
)}
)}
)} {/* Notification Settings */}

{t('profile.notifications.title')}

{notificationsLoading ? (
) : notificationSettings ? (
{/* Subscription Expiry */}

{t('profile.notifications.subscriptionExpiry')}

{t('profile.notifications.subscriptionExpiryDesc')}

handleNotificationToggle('subscription_expiry_enabled', checked) } />
{notificationSettings.subscription_expiry_enabled && (
{t('profile.notifications.daysBeforeExpiry')}
)}
{/* Traffic Warning */}

{t('profile.notifications.trafficWarning')}

{t('profile.notifications.trafficWarningDesc')}

handleNotificationToggle('traffic_warning_enabled', checked) } />
{notificationSettings.traffic_warning_enabled && (
{t('profile.notifications.atPercent')}
)}
{/* Balance Low */}

{t('profile.notifications.balanceLow')}

{t('profile.notifications.balanceLowDesc')}

handleNotificationToggle('balance_low_enabled', checked) } />
{notificationSettings.balance_low_enabled && (
{t('profile.notifications.threshold')} handleNotificationValue('balance_low_threshold', Number(e.target.value)) } min={0} className="input w-24 py-1" />
)}
{/* News */}

{t('profile.notifications.news')}

{t('profile.notifications.newsDesc')}

handleNotificationToggle('news_enabled', checked)} />
{/* Promo Offers */}

{t('profile.notifications.promoOffers')}

{t('profile.notifications.promoOffersDesc')}

handleNotificationToggle('promo_offers_enabled', checked) } />
) : (

{t('profile.notifications.unavailable')}

)} ); }