import { useState, useEffect } from 'react' import { useQuery, useQueryClient } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import { useAuthStore } from '../store/auth' import { balanceApi } from '../api/balance' import TopUpModal from '../components/TopUpModal' import { useCurrency } from '../hooks/useCurrency' import type { PaymentMethod, PaginatedResponse, Transaction } from '../types' export default function Balance() { const { t } = useTranslation() const { refreshUser } = useAuthStore() const queryClient = useQueryClient() const { formatAmount, currencySymbol } = useCurrency() // Fetch balance directly from API with no caching const { data: balanceData, refetch: refetchBalance } = useQuery({ queryKey: ['balance'], queryFn: balanceApi.getBalance, staleTime: 0, // Always refetch refetchOnMount: 'always', }) // Refresh user data on mount to sync balance in store useEffect(() => { refreshUser() }, []) const [selectedMethod, setSelectedMethod] = useState(null) const [promocode, setPromocode] = useState('') const [promocodeLoading, setPromocodeLoading] = useState(false) const [promocodeError, setPromocodeError] = useState(null) const [promocodeSuccess, setPromocodeSuccess] = useState<{ message: string; amount: number } | null>(null) const [transactionsPage, setTransactionsPage] = useState(1) const [isHistoryOpen, setIsHistoryOpen] = useState(false) const { data: transactions, isLoading } = useQuery>({ queryKey: ['transactions', transactionsPage], queryFn: () => balanceApi.getTransactions({ per_page: 20, page: transactionsPage }), placeholderData: (previousData) => previousData, }) const { data: paymentMethods } = useQuery({ queryKey: ['payment-methods'], queryFn: balanceApi.getPaymentMethods, }) const normalizeType = (type: string) => type?.toUpperCase?.() ?? type const getTypeBadge = (type: string) => { switch (normalizeType(type)) { case 'DEPOSIT': return 'badge-success' case 'SUBSCRIPTION_PAYMENT': return 'badge-info' case 'REFERRAL_REWARD': return 'badge-warning' case 'WITHDRAWAL': return 'badge-error' default: return 'badge-neutral' } } const getTypeLabel = (type: string) => { switch (normalizeType(type)) { case 'DEPOSIT': return t('balance.deposit') case 'SUBSCRIPTION_PAYMENT': return t('balance.subscriptionPayment') case 'REFERRAL_REWARD': return t('balance.referralReward') case 'WITHDRAWAL': return t('balance.withdrawal') default: return type } } const handlePromocodeActivate = async () => { if (!promocode.trim()) return setPromocodeLoading(true) setPromocodeError(null) setPromocodeSuccess(null) try { const result = await balanceApi.activatePromocode(promocode.trim()) if (result.success) { const bonusAmount = result.balance_after - result.balance_before setPromocodeSuccess({ message: result.bonus_description || t('balance.promocode.success'), amount: bonusAmount, }) setTransactionsPage(1) setPromocode('') // Refresh balance and transactions await refetchBalance() await refreshUser() queryClient.invalidateQueries({ queryKey: ['transactions'] }) } } catch (error: unknown) { const axiosError = error as { response?: { data?: { detail?: string } } } const errorDetail = axiosError.response?.data?.detail || 'server_error' // Map backend error messages to translation keys const errorKey = errorDetail.toLowerCase().includes('not found') ? 'not_found' : errorDetail.toLowerCase().includes('expired') ? 'expired' : errorDetail.toLowerCase().includes('fully used') ? 'used' : errorDetail.toLowerCase().includes('already used') ? 'already_used_by_user' : 'server_error' setPromocodeError(t(`balance.promocode.errors.${errorKey}`)) } finally { setPromocodeLoading(false) } } return (

{t('balance.title')}

{/* Balance Card */}
{t('balance.currentBalance')}
{formatAmount(balanceData?.balance_rubles || 0)} {currencySymbol}
{/* Promo Code Section */}

{t('balance.promocode.title')}

setPromocode(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handlePromocodeActivate()} placeholder={t('balance.promocode.placeholder')} className="input flex-1" disabled={promocodeLoading} />
{promocodeError && (
{promocodeError}
)} {promocodeSuccess && (
{promocodeSuccess.message}
{promocodeSuccess.amount > 0 && (
{t('balance.promocode.balanceAdded', { amount: promocodeSuccess.amount.toFixed(2) })}
)}
)}
{/* Payment Methods */} {paymentMethods && paymentMethods.length > 0 && (

{t('balance.topUpBalance')}

{paymentMethods.map((method) => { const methodKey = method.id.toLowerCase().replace(/-/g, '_') const translatedName = t(`balance.paymentMethods.${methodKey}.name`, { defaultValue: '' }) const translatedDesc = t(`balance.paymentMethods.${methodKey}.description`, { defaultValue: '' }) return ( ) })}
)}
{isHistoryOpen && (
{isLoading ? (
) : transactions?.items && transactions.items.length > 0 ? (
{transactions.items.map((tx) => { const isPositive = tx.amount_rubles >= 0 const displayAmount = Math.abs(tx.amount_rubles) const sign = isPositive ? '+' : '-' const colorClass = isPositive ? 'text-success-400' : 'text-error-400' return (
{getTypeLabel(tx.type)} {new Date(tx.created_at).toLocaleDateString()}
{tx.description && (
{tx.description}
)}
{sign}{formatAmount(displayAmount)} {currencySymbol}
) })}
) : (
{t('balance.noTransactions')}
)} {transactions && transactions.pages > 1 && (
{t('balance.page', { current: transactions.page, total: transactions.pages })}
)}
)}
{/* TopUp Modal */} {selectedMethod && ( setSelectedMethod(null)} /> )}
) }