import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import type { DepositWithdrawConfig } from '@/api' import { DEFAULT_CURRENCY_CODE, DEFAULT_WITHDRAW_CONFIG, QUICK_FIAT_AMOUNTS, WITHDRAW_EMAIL_PATTERN, WITHDRAW_PHONE_PATTERN, } from '@/constants' import { useDepositWithdrawConfig } from '@/hooks/use-deposit-withdraw-config' import { useAuthStore } from '@/store' function formatNumber(locale: string, value: number) { return new Intl.NumberFormat(locale).format(value) } function getInitialWithdrawAmount( selectedRate: number, maxWithdrawAmount: number, ) { if (maxWithdrawAmount <= 0) { return 0 } return Math.min( maxWithdrawAmount, Math.max(1, Math.round(selectedRate * QUICK_FIAT_AMOUNTS[0])), ) } function getActiveCurrencyCode( currencies: DepositWithdrawConfig['currencies'], selectedCurrencyCode: string, ) { return ( currencies.find((item) => item.code === selectedCurrencyCode) ?? currencies[0] ?? DEFAULT_WITHDRAW_CONFIG.currencies[0] ) } function getNormalizedConfig( config: DepositWithdrawConfig | undefined, fallback: DepositWithdrawConfig, ) { return config ?? fallback } function isValidEmail(value: string) { if (value.trim().length === 0) { return false } return WITHDRAW_EMAIL_PATTERN.test(value.trim()) } function isValidPhone(value: string) { const normalized = value.replace(/[^\d+]/g, '') if (normalized.length === 0) { return false } return WITHDRAW_PHONE_PATTERN.test(normalized) } export function useWithdrawVm() { const { i18n, t } = useTranslation() const currentUser = useAuthStore((state) => state.currentUser) const withdrawConfigQuery = useDepositWithdrawConfig() const config = useMemo(() => { const baseConfig = getNormalizedConfig( withdrawConfigQuery.data, DEFAULT_WITHDRAW_CONFIG, ) return { ...baseConfig, currencies: baseConfig.currencies.length > 0 ? baseConfig.currencies : DEFAULT_WITHDRAW_CONFIG.currencies, payChannels: baseConfig.payChannels, withdraw: { ...baseConfig.withdraw, banks: baseConfig.withdraw.banks, }, } }, [withdrawConfigQuery.data]) const locale = i18n.resolvedLanguage ?? i18n.language ?? 'en-US' const [amount, setAmountState] = useState(0) const [hasInitializedAmount, setHasInitializedAmount] = useState(false) const [currencyCode, setCurrencyCode] = useState( config.currencies[0]?.code ?? DEFAULT_CURRENCY_CODE, ) const [paymentChannelCode, setPaymentChannelCode] = useState('') const [bankCode, setBankCode] = useState('') const [holderName, setHolderName] = useState('') const [bankAccount, setBankAccount] = useState('') const [receiverEmail, setReceiverEmail] = useState('') const [receiverPhone, setReceiverPhone] = useState('') const selectedCurrency = getActiveCurrencyCode( config.currencies, currencyCode, ) const selectedRate = selectedCurrency.withdrawCoinsPerFiatValue || 1 const sortedPayChannels = useMemo( () => [...config.payChannels] .filter((channel) => channel.status === 1) .sort((left, right) => left.sort - right.sort), [config.payChannels], ) const sortedBanks = useMemo( () => [...config.withdraw.banks] .filter((bank) => bank.status === 1) .sort((left, right) => left.sort - right.sort), [config.withdraw.banks], ) const availableBalance = Number(currentUser?.coin ?? 0) const maxWithdrawAmount = Math.max(0, Math.floor(availableBalance)) const selectedPaymentChannel = sortedPayChannels.find((channel) => channel.code === paymentChannelCode) ?? null const setAmount = useCallback( (nextAmount: number) => { setAmountState( Math.min(maxWithdrawAmount, Math.max(0, Math.floor(nextAmount))), ) }, [maxWithdrawAmount], ) useEffect(() => { if ( selectedCurrency && selectedCurrency.code !== currencyCode && config.currencies.some((item) => item.code === currencyCode) ) { return } if (selectedCurrency && selectedCurrency.code !== currencyCode) { setCurrencyCode(selectedCurrency.code) } }, [config.currencies, currencyCode, selectedCurrency]) useEffect(() => { const firstAvailablePayChannel = sortedPayChannels[0] if (!firstAvailablePayChannel) { if (paymentChannelCode) { setPaymentChannelCode('') } return } const hasSelectedAvailablePayChannel = sortedPayChannels.some( (channel) => channel.code === paymentChannelCode, ) if (!hasSelectedAvailablePayChannel) { setPaymentChannelCode(firstAvailablePayChannel.code) } }, [paymentChannelCode, sortedPayChannels]) useEffect(() => { if (sortedBanks.length === 0) { if (bankCode) { setBankCode('') } return } const hasSelectedAvailableBank = sortedBanks.some( (bank) => bank.code === bankCode, ) if (!hasSelectedAvailableBank) { setBankCode('') } }, [bankCode, sortedBanks]) useEffect(() => { if (!hasInitializedAmount && selectedRate > 0) { setAmount(getInitialWithdrawAmount(selectedRate, maxWithdrawAmount)) setHasInitializedAmount(true) } }, [hasInitializedAmount, maxWithdrawAmount, selectedRate, setAmount]) useEffect(() => { if (amount > maxWithdrawAmount) { setAmount(maxWithdrawAmount) } }, [amount, maxWithdrawAmount, setAmount]) const quickAmounts = useMemo(() => { return QUICK_FIAT_AMOUNTS.map((fiatAmount) => ({ diamonds: Math.min( maxWithdrawAmount, Math.max(1, Math.round(selectedRate * fiatAmount)), ), id: `quick-${selectedCurrency.code}-${fiatAmount}`, preview: `${selectedCurrency.code} ${formatNumber(locale, fiatAmount)}`, })) }, [locale, maxWithdrawAmount, selectedCurrency.code, selectedRate]) const resetForm = useCallback(() => { const nextCurrencyCode = config.currencies[0]?.code ?? DEFAULT_CURRENCY_CODE const nextCurrency = getActiveCurrencyCode( config.currencies, nextCurrencyCode, ) const nextRate = nextCurrency.withdrawCoinsPerFiatValue || 1 setAmountState(getInitialWithdrawAmount(nextRate, maxWithdrawAmount)) setHasInitializedAmount(true) setCurrencyCode(nextCurrencyCode) setPaymentChannelCode(sortedPayChannels[0]?.code ?? '') setBankCode('') setHolderName('') setBankAccount('') setReceiverEmail('') setReceiverPhone('') }, [config.currencies, maxWithdrawAmount, sortedPayChannels]) const selectedCurrencyPreview = useMemo( () => ({ currencyCode: selectedCurrency.code, currencyLabel: selectedCurrency.label, exchangeRateLabel: t('gameDesktop.withdraw.preview.exchangeRate', { currency: selectedCurrency.code, }), exchangeRateValue: t('gameDesktop.withdraw.preview.exchangeRateValue', { coins: formatNumber(locale, selectedRate), currency: selectedCurrency.code, platformCoinLabel: config.platformCoinLabel, }), convertibleLabel: t('gameDesktop.withdraw.preview.convertible', { currency: selectedCurrency.code, }), convertibleValue: `${formatNumber( locale, selectedRate > 0 ? amount / selectedRate : 0, )} ${selectedCurrency.code}`, }), [ amount, config.platformCoinLabel, locale, selectedCurrency.code, selectedCurrency.label, selectedRate, t, ], ) return { amount, amountExceedsBalance: amount > maxWithdrawAmount, amountRequiredError: amount <= 0, availableBalance, bankAccount, bankAccountError: bankAccount.trim().length === 0, bankCode, bankCodeError: bankCode.trim().length === 0, config, currencyCode, holderName, holderNameError: holderName.trim().length === 0, isLoading: withdrawConfigQuery.isLoading, isRefetching: withdrawConfigQuery.isFetching, maxWithdrawAmount, paymentChannelCode, paymentChannelCodeError: paymentChannelCode.trim().length === 0, quickAmounts, receiverEmail, receiverEmailError: !isValidEmail(receiverEmail), receiverPhone, receiverPhoneError: !isValidPhone(receiverPhone), selectedCurrency, selectedCurrencyPreview, selectedPaymentChannel, selectedRate, resetForm, setAmount, setBankAccount, setBankCode, setCurrencyCode, setHolderName, setPaymentChannelCode, setReceiverEmail, setReceiverPhone, sortedBanks, sortedPayChannels, withdrawCopy: { bankLabel: t('gameDesktop.withdraw.bank'), eWalletLabel: t('gameDesktop.withdraw.eWallet'), feeNote: config.withdraw.feeNote, noticeLabel: t('gameDesktop.withdraw.notice'), processingLabel: t('gameDesktop.withdraw.processingTime'), processingValue: config.withdraw.processingNote, rateHint: config.withdraw.rateHint, }, } }