- 在游戏规则模态框中添加连续奖励表格组件 - 更新英文和印尼语的游戏规则文本内容 - 添加银行选项的图标显示功能,支持自定义logo或默认地标图标 - 实现连续奖励数据类型和接口定义 - 在游戏会话存储中添加连续奖励行数据管理 - 集成银行选择下拉菜单中的图标展示 - 更新WithdrawField组件以支持银行图标显示
433 lines
13 KiB
TypeScript
433 lines
13 KiB
TypeScript
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import {
|
|
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'
|
|
import type { DepositWithdrawConfig } from '@/type'
|
|
|
|
function formatNumber(locale: string, value: number) {
|
|
return new Intl.NumberFormat(locale).format(value)
|
|
}
|
|
|
|
function getInitialWithdrawAmount(
|
|
selectedExchangeRate: number,
|
|
maxWithdrawAmount: number,
|
|
) {
|
|
if (maxWithdrawAmount <= 0) {
|
|
return 0
|
|
}
|
|
|
|
return Math.min(
|
|
maxWithdrawAmount,
|
|
Math.max(1, Math.round(QUICK_FIAT_AMOUNTS[0] / selectedExchangeRate)),
|
|
)
|
|
}
|
|
|
|
function getCurrencyExchangeRate(
|
|
config: DepositWithdrawConfig,
|
|
currencyCode: string,
|
|
fallbackRate: number,
|
|
) {
|
|
const configuredRate = config.rates.find(
|
|
(rate) => rate.currency === currencyCode,
|
|
)?.diamondsPerFiatUnitValue
|
|
|
|
return configuredRate && configuredRate > 0 ? configuredRate : fallbackRate
|
|
}
|
|
|
|
function toOptionalNumber(value: string) {
|
|
if (value.trim().length === 0) {
|
|
return null
|
|
}
|
|
|
|
const numericValue = Number(value)
|
|
|
|
return Number.isFinite(numericValue) ? numericValue : null
|
|
}
|
|
|
|
function getChannelCurrencies(
|
|
config: DepositWithdrawConfig,
|
|
channel: DepositWithdrawConfig['withdraw']['payChannels'][number] | null,
|
|
) {
|
|
if (!channel || channel.currencyCodes.length === 0) {
|
|
return []
|
|
}
|
|
|
|
const currencyMap = new Map(
|
|
config.currencies.map((currency) => [currency.code, currency]),
|
|
)
|
|
|
|
return channel.currencyCodes
|
|
.map((currencyCode) => currencyMap.get(currencyCode))
|
|
.filter(
|
|
(currency): currency is DepositWithdrawConfig['currencies'][number] =>
|
|
Boolean(currency),
|
|
)
|
|
}
|
|
|
|
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,
|
|
payChannels: baseConfig.withdraw.payChannels,
|
|
},
|
|
}
|
|
}, [withdrawConfigQuery.data])
|
|
const locale = i18n.resolvedLanguage ?? i18n.language ?? 'en-US'
|
|
|
|
const [amount, setAmountState] = useState(0)
|
|
const [hasInitializedAmount, setHasInitializedAmount] = useState(false)
|
|
const [currencyCode, setCurrencyCode] = useState('')
|
|
const [paymentChannelCode, setPaymentChannelCode] = useState('')
|
|
const [paymentType, setPaymentType] = useState('')
|
|
const [bankCode, setBankCode] = useState('')
|
|
const [holderName, setHolderName] = useState('')
|
|
const [bankAccount, setBankAccount] = useState('')
|
|
const [receiverEmail, setReceiverEmail] = useState('')
|
|
const [receiverPhone, setReceiverPhone] = useState('')
|
|
|
|
const sortedPayChannels = useMemo(
|
|
() =>
|
|
[...config.withdraw.payChannels]
|
|
.filter((channel) => channel.status === 1)
|
|
.sort((left, right) => left.sort - right.sort),
|
|
[config.withdraw.payChannels],
|
|
)
|
|
const availableBalance = Number(currentUser?.coin ?? 0)
|
|
const maxWithdrawAmount = Math.max(0, Math.floor(availableBalance))
|
|
const selectedPaymentChannel =
|
|
sortedPayChannels.find((channel) => channel.code === paymentChannelCode) ??
|
|
null
|
|
const availablePaymentMethods = useMemo(
|
|
() => selectedPaymentChannel?.methods ?? [],
|
|
[selectedPaymentChannel],
|
|
)
|
|
const selectedPaymentMethod =
|
|
availablePaymentMethods.find((method) => method.code === paymentType) ??
|
|
null
|
|
const availableCurrencies = useMemo(
|
|
() => getChannelCurrencies(config, selectedPaymentChannel),
|
|
[config, selectedPaymentChannel],
|
|
)
|
|
const selectedCurrency =
|
|
availableCurrencies.find((item) => item.code === currencyCode) ?? null
|
|
const selectedCurrencyCode = selectedCurrency?.code ?? ''
|
|
const selectedRate = selectedCurrency?.withdrawCoinsPerFiatValue ?? 0
|
|
const selectedExchangeRate = selectedCurrency
|
|
? getCurrencyExchangeRate(config, selectedCurrency.code, selectedRate || 1)
|
|
: 0
|
|
const convertedFiatAmount =
|
|
selectedExchangeRate > 0 ? amount * selectedExchangeRate : 0
|
|
const selectedPaymentMinimumAmount = selectedPaymentMethod
|
|
? toOptionalNumber(selectedPaymentMethod.minimumAmount)
|
|
: null
|
|
const selectedPaymentMaximumAmount = selectedPaymentMethod
|
|
? toOptionalNumber(selectedPaymentMethod.maximumAmount)
|
|
: null
|
|
const amountBelowPaymentMinimum =
|
|
amount > 0 &&
|
|
selectedPaymentMinimumAmount !== null &&
|
|
convertedFiatAmount < selectedPaymentMinimumAmount
|
|
const amountAbovePaymentMaximum =
|
|
amount > 0 &&
|
|
selectedPaymentMaximumAmount !== null &&
|
|
convertedFiatAmount > selectedPaymentMaximumAmount
|
|
const sortedBanks = useMemo(
|
|
() =>
|
|
[...(selectedPaymentChannel?.banks ?? [])]
|
|
.filter(
|
|
(bank) =>
|
|
bank.status === 1 &&
|
|
selectedCurrencyCode.length > 0 &&
|
|
(!bank.currencyCode || bank.currencyCode === selectedCurrencyCode),
|
|
)
|
|
.sort((left, right) => left.sort - right.sort),
|
|
[selectedPaymentChannel, selectedCurrencyCode],
|
|
)
|
|
const setAmount = useCallback(
|
|
(nextAmount: number) => {
|
|
setAmountState(
|
|
Math.min(maxWithdrawAmount, Math.max(0, Math.floor(nextAmount))),
|
|
)
|
|
},
|
|
[maxWithdrawAmount],
|
|
)
|
|
|
|
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 (availablePaymentMethods.length === 0) {
|
|
if (paymentType) {
|
|
setPaymentType('')
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
const hasSelectedAvailablePaymentMethod = availablePaymentMethods.some(
|
|
(method) => method.code === paymentType,
|
|
)
|
|
|
|
if (!hasSelectedAvailablePaymentMethod) {
|
|
setPaymentType(availablePaymentMethods[0].code)
|
|
}
|
|
}, [availablePaymentMethods, paymentType])
|
|
|
|
useEffect(() => {
|
|
if (availableCurrencies.length === 0) {
|
|
if (currencyCode) {
|
|
setCurrencyCode('')
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
const hasSelectedAvailableCurrency = availableCurrencies.some(
|
|
(currency) => currency.code === currencyCode,
|
|
)
|
|
|
|
if (!hasSelectedAvailableCurrency) {
|
|
setCurrencyCode(availableCurrencies[0].code)
|
|
}
|
|
}, [availableCurrencies, currencyCode])
|
|
|
|
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 && selectedExchangeRate > 0) {
|
|
setAmount(
|
|
getInitialWithdrawAmount(selectedExchangeRate, maxWithdrawAmount),
|
|
)
|
|
setHasInitializedAmount(true)
|
|
}
|
|
}, [hasInitializedAmount, maxWithdrawAmount, selectedExchangeRate, setAmount])
|
|
|
|
useEffect(() => {
|
|
if (amount > maxWithdrawAmount) {
|
|
setAmount(maxWithdrawAmount)
|
|
}
|
|
}, [amount, maxWithdrawAmount, setAmount])
|
|
|
|
const quickAmounts = useMemo(() => {
|
|
if (!selectedCurrency || selectedExchangeRate <= 0) {
|
|
return []
|
|
}
|
|
|
|
return QUICK_FIAT_AMOUNTS.map((fiatAmount) => ({
|
|
diamonds: Math.min(
|
|
maxWithdrawAmount,
|
|
Math.max(1, Math.round(fiatAmount / selectedExchangeRate)),
|
|
),
|
|
id: `quick-${selectedCurrency.code}-${fiatAmount}`,
|
|
preview: `${selectedCurrency.code} ${formatNumber(locale, fiatAmount)}`,
|
|
}))
|
|
}, [locale, maxWithdrawAmount, selectedCurrency, selectedExchangeRate])
|
|
|
|
const resetForm = useCallback(() => {
|
|
const nextPaymentChannelCode = sortedPayChannels[0]?.code ?? ''
|
|
const nextPaymentChannel =
|
|
sortedPayChannels.find(
|
|
(channel) => channel.code === nextPaymentChannelCode,
|
|
) ?? null
|
|
const nextCurrency =
|
|
getChannelCurrencies(config, nextPaymentChannel)[0] ?? null
|
|
const nextExchangeRate = nextCurrency
|
|
? getCurrencyExchangeRate(
|
|
config,
|
|
nextCurrency.code,
|
|
nextCurrency.withdrawCoinsPerFiatValue || 1,
|
|
)
|
|
: 0
|
|
|
|
setAmountState(
|
|
nextExchangeRate > 0
|
|
? getInitialWithdrawAmount(nextExchangeRate, maxWithdrawAmount)
|
|
: 0,
|
|
)
|
|
setHasInitializedAmount(true)
|
|
setCurrencyCode(nextCurrency?.code ?? '')
|
|
setPaymentChannelCode(nextPaymentChannelCode)
|
|
setPaymentType(nextPaymentChannel?.methods[0]?.code ?? '')
|
|
setBankCode('')
|
|
setHolderName('')
|
|
setBankAccount('')
|
|
setReceiverEmail('')
|
|
setReceiverPhone('')
|
|
}, [config, maxWithdrawAmount, sortedPayChannels])
|
|
|
|
const selectedCurrencyPreview = useMemo(() => {
|
|
const previewCurrencyCode = selectedCurrency?.code ?? '--'
|
|
|
|
return {
|
|
currencyCode: selectedCurrency?.code ?? '',
|
|
currencyLabel: selectedCurrency?.label ?? '',
|
|
exchangeRateLabel: t('gameDesktop.withdraw.preview.exchangeRate', {
|
|
currency: previewCurrencyCode,
|
|
}),
|
|
exchangeRateValue:
|
|
selectedCurrency && selectedExchangeRate > 0
|
|
? `1:${formatNumber(locale, selectedExchangeRate)}`
|
|
: '--',
|
|
convertibleLabel: t('gameDesktop.withdraw.preview.convertible', {
|
|
currency: previewCurrencyCode,
|
|
}),
|
|
convertibleValue:
|
|
selectedCurrency && selectedExchangeRate > 0
|
|
? `${formatNumber(
|
|
locale,
|
|
amount * selectedExchangeRate,
|
|
)} ${selectedCurrency.code}`
|
|
: '--',
|
|
}
|
|
}, [amount, locale, selectedCurrency, selectedExchangeRate, t])
|
|
|
|
return {
|
|
amount,
|
|
amountAbovePaymentMaximum,
|
|
amountBelowPaymentMinimum,
|
|
amountExceedsBalance: amount > maxWithdrawAmount,
|
|
amountRequiredError: amount <= 0,
|
|
availableBalance,
|
|
availableCurrencies,
|
|
availablePaymentMethods,
|
|
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,
|
|
paymentMaximumAmountLabel:
|
|
selectedPaymentMaximumAmount !== null && selectedCurrency
|
|
? `${formatNumber(locale, selectedPaymentMaximumAmount)} ${
|
|
selectedCurrency.code
|
|
}`
|
|
: '',
|
|
paymentMinimumAmountLabel:
|
|
selectedPaymentMinimumAmount !== null && selectedCurrency
|
|
? `${formatNumber(locale, selectedPaymentMinimumAmount)} ${
|
|
selectedCurrency.code
|
|
}`
|
|
: '',
|
|
paymentType,
|
|
paymentTypeError: paymentType.trim().length === 0,
|
|
quickAmounts,
|
|
receiverEmail,
|
|
receiverEmailError: !isValidEmail(receiverEmail),
|
|
receiverPhone,
|
|
receiverPhoneError: !isValidPhone(receiverPhone),
|
|
selectedCurrency,
|
|
selectedCurrencyPreview,
|
|
selectedPaymentChannel,
|
|
selectedPaymentMethod,
|
|
selectedRate,
|
|
resetForm,
|
|
setAmount,
|
|
setBankAccount,
|
|
setBankCode,
|
|
setCurrencyCode,
|
|
setHolderName,
|
|
setPaymentChannelCode,
|
|
setPaymentType,
|
|
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,
|
|
},
|
|
}
|
|
}
|