diff --git a/src/api/finance-api.ts b/src/api/finance-api.ts index 71585e7..c13c801 100644 --- a/src/api/finance-api.ts +++ b/src/api/finance-api.ts @@ -105,6 +105,7 @@ function normalizePayChannel(dto: FinancePayChannelDto) { currencyCodes: Array.isArray(dto.currency_codes) ? dto.currency_codes : [], depositBanks: (dto.deposit_banks ?? []).map(normalizeWithdrawBank), feeNote: dto.fee_note ?? '', + handlingFee: dto.handling_fee ?? '0.00', methods: (dto.methods ?? []).map(normalizeDepositMethod), minBank: dto.min_bank ?? '', minEwallet: dto.min_ewallet ?? '', diff --git a/src/features/game/components/desktop/desktop-withdraw.tsx b/src/features/game/components/desktop/desktop-withdraw.tsx index 00771ee..f85ca18 100644 --- a/src/features/game/components/desktop/desktop-withdraw.tsx +++ b/src/features/game/components/desktop/desktop-withdraw.tsx @@ -60,7 +60,7 @@ function WithdrawField({ }) { return (
-
+
{label} :
@@ -132,13 +132,15 @@ function AmountShell({ } function QuickAmountCard({ - amount, - preview, + amountLabel, + diamondLabel, + feeLabel, active, onClick, }: { - amount: number - preview: string + amountLabel: string + diamondLabel: string + feeLabel: string active: boolean onClick: () => void }) { @@ -147,7 +149,7 @@ function QuickAmountCard({ type="button" onClick={onClick} className={cn( - 'group relative flex h-design-76 min-w-0 w-full cursor-pointer flex-col items-start justify-center overflow-hidden rounded-[calc(var(--design-unit)*8)] border px-design-12 text-left transition-[transform,border-color,background-color,box-shadow] duration-150', + 'group relative flex h-design-92 min-w-0 w-full cursor-pointer flex-col gap-design-5 items-start justify-center overflow-hidden rounded-[calc(var(--design-unit)*8)] border px-design-12 text-left transition-[transform,border-color,background-color,box-shadow] duration-150', active ? 'border-[#D18A43] bg-[linear-gradient(180deg,rgba(88,54,28,0.96),rgba(56,33,18,0.92))] shadow-[0_0_calc(var(--design-unit)*14)_rgba(209,138,67,0.22),inset_0_0_calc(var(--design-unit)*12)_rgba(255,217,120,0.08)]' : 'border-[rgba(103,227,239,0.26)] bg-[linear-gradient(180deg,rgba(11,48,63,0.9),rgba(5,24,35,0.94))] hover:-translate-y-[1px] hover:border-[rgba(170,247,255,0.62)] hover:shadow-[0_0_calc(var(--design-unit)*12)_rgba(88,225,238,0.12)]', @@ -161,16 +163,19 @@ function QuickAmountCard({ : 'bg-[rgba(122,220,230,0.26)]', )} /> -
- {amount} +
+ {amountLabel}
- {preview} + {diamondLabel} +
+
+ {feeLabel}
) @@ -290,12 +295,12 @@ function PreviewRow({ }) { return (
-
+
{label}
@@ -500,13 +505,14 @@ function DesktopWithdraw() {
-
+
{vm.quickAmounts.map((option) => ( handleQuickAmountSelect(option.id, option.diamonds) @@ -664,7 +670,7 @@ function DesktopWithdraw() {
-
+
{t('gameDesktop.withdraw.preview.title')}
@@ -684,6 +690,10 @@ function DesktopWithdraw() { value={vm.selectedCurrencyPreview.convertibleValue} highlight={true} /> + {withdrawSubmitMutation.isPending - ? t('commonUi.action.submitting') + ? t('commonUi.actions.submitting') : `${t('gameDesktop.withdraw.confirm')}${t('gameDesktop.withdraw.withdrawal')}`}
diff --git a/src/features/game/components/mobile/mobile-withdraw.tsx b/src/features/game/components/mobile/mobile-withdraw.tsx index 60e4646..a60ebca 100644 --- a/src/features/game/components/mobile/mobile-withdraw.tsx +++ b/src/features/game/components/mobile/mobile-withdraw.tsx @@ -122,13 +122,15 @@ function AmountShell({ } function QuickAmountCard({ - amount, - preview, + amountLabel, + diamondLabel, + feeLabel, active, onClick, }: { - amount: number - preview: string + amountLabel: string + diamondLabel: string + feeLabel: string active: boolean onClick: () => void }) { @@ -137,7 +139,7 @@ function QuickAmountCard({ type="button" onClick={onClick} className={cn( - 'group relative flex h-design-42 min-w-0 w-full cursor-pointer flex-col items-start justify-center overflow-hidden rounded-[calc(var(--design-unit)*6)] border px-design-6 text-left transition-[border-color,background-color,box-shadow,filter] duration-150', + 'group relative flex h-design-58 min-w-0 w-full cursor-pointer flex-col items-start justify-center overflow-hidden rounded-[calc(var(--design-unit)*6)] border px-design-6 text-left transition-[border-color,background-color,box-shadow,filter] duration-150', active ? 'border-[#D18A43] bg-[linear-gradient(180deg,rgba(88,54,28,0.96),rgba(56,33,18,0.92))] shadow-[0_0_calc(var(--design-unit)*10)_rgba(209,138,67,0.2),inset_0_0_calc(var(--design-unit)*10)_rgba(255,217,120,0.08)]' : 'border-[rgba(103,227,239,0.26)] bg-[linear-gradient(180deg,rgba(11,48,63,0.9),rgba(5,24,35,0.94))] hover:border-[rgba(170,247,255,0.62)] hover:brightness-110', @@ -151,16 +153,19 @@ function QuickAmountCard({ : 'bg-[rgba(122,220,230,0.26)]', )} /> -
- {amount} +
+ {amountLabel}
- {preview} + {diamondLabel} +
+
+ {feeLabel}
) @@ -490,8 +495,9 @@ function MobileWithdraw() { {vm.quickAmounts.map((option) => ( handleQuickAmountSelect(option.id, option.diamonds) @@ -654,6 +660,10 @@ function MobileWithdraw() { value={vm.selectedCurrencyPreview.convertibleValue} highlight={true} /> + {withdrawSubmitMutation.isPending - ? t('commonUi.action.submitting') + ? t('commonUi.actions.submitting') : `${t('gameDesktop.withdraw.confirm')}`}
diff --git a/src/hooks/use-withdraw-vm.ts b/src/hooks/use-withdraw-vm.ts index 5a5e862..f39dfe7 100644 --- a/src/hooks/use-withdraw-vm.ts +++ b/src/hooks/use-withdraw-vm.ts @@ -20,6 +20,42 @@ function formatNumber(locale: string, value: number) { return new Intl.NumberFormat(locale).format(value) } +function formatConfiguredAmount(locale: string, value: string) { + const normalizedValue = value.trim() + const numericValue = Number(normalizedValue) + + if (!Number.isFinite(numericValue)) { + return normalizedValue || '0' + } + + const fractionDigits = Math.min( + 6, + normalizedValue.includes('.') + ? (normalizedValue.split('.')[1]?.length ?? 0) + : 0, + ) + + return new Intl.NumberFormat(locale, { + maximumFractionDigits: fractionDigits, + minimumFractionDigits: fractionDigits, + }).format(numericValue) +} + +function calculateNetWithdrawalAmount( + grossAmount: number, + handlingFee: string, +) { + const parsedHandlingFee = Number(handlingFee) + + if (!Number.isFinite(parsedHandlingFee)) { + return grossAmount + } + + const handlingFeeRate = Math.min(100, Math.max(0, parsedHandlingFee)) / 100 + + return grossAmount * (1 - handlingFeeRate) +} + function getInitialWithdrawAmount(bounds: WithdrawDiamondBounds | null) { return bounds?.minimumDiamonds ?? 0 } @@ -209,6 +245,7 @@ export function useWithdrawVm() { const selectedPaymentChannel = sortedPayChannels.find((channel) => channel.code === paymentChannelCode) ?? null + const selectedHandlingFee = selectedPaymentChannel?.handlingFee ?? '0.00' const availablePaymentMethods = useMemo( () => selectedPaymentChannel?.methods ?? [], [selectedPaymentChannel], @@ -223,6 +260,9 @@ export function useWithdrawVm() { const selectedCurrency = availableCurrencies.find((item) => item.code === currencyCode) ?? null const selectedCurrencyCode = selectedCurrency?.code ?? '' + const selectedHandlingFeeValue = selectedPaymentChannel + ? `${formatConfiguredAmount(locale, selectedHandlingFee)}%` + : '--' const selectedRate = selectedCurrency?.withdrawCoinsPerFiatValue ?? 0 const selectedExchangeRate = selectedCurrency ? getCurrencyExchangeRate(config, selectedCurrency.code, selectedRate || 1) @@ -372,12 +412,21 @@ export function useWithdrawVm() { } return getQuickWithdrawAmounts(withdrawDiamondBounds).map((diamonds) => ({ - diamonds, - id: `quick-${paymentChannelCode}-${paymentType}-${selectedCurrency.code}-${diamonds}`, - preview: `${selectedCurrency.code} ${formatNumber( + amountLabel: `${selectedCurrency.code} ${formatNumber( locale, - diamonds * selectedExchangeRate, + calculateNetWithdrawalAmount( + diamonds * selectedExchangeRate, + selectedHandlingFee, + ), )}`, + diamondLabel: t('gameDesktop.withdraw.preview.quickDiamondAmount', { + amount: formatNumber(locale, diamonds), + }), + diamonds, + feeLabel: t('gameDesktop.withdraw.preview.quickHandlingFee', { + fee: selectedHandlingFeeValue, + }), + id: `quick-${paymentChannelCode}-${paymentType}-${selectedCurrency.code}-${diamonds}`, })) }, [ locale, @@ -385,6 +434,9 @@ export function useWithdrawVm() { paymentType, selectedCurrency, selectedExchangeRate, + selectedHandlingFee, + selectedHandlingFeeValue, + t, withdrawDiamondBounds, ]) @@ -448,11 +500,24 @@ export function useWithdrawVm() { selectedCurrency && selectedExchangeRate > 0 ? `${formatNumber( locale, - amount * selectedExchangeRate, + calculateNetWithdrawalAmount( + amount * selectedExchangeRate, + selectedHandlingFee, + ), )} ${selectedCurrency.code}` : '--', + handlingFeeLabel: t('gameDesktop.withdraw.preview.handlingFee'), + handlingFeeValue: selectedHandlingFeeValue, } - }, [amount, locale, selectedCurrency, selectedExchangeRate, t]) + }, [ + amount, + locale, + selectedCurrency, + selectedExchangeRate, + selectedHandlingFee, + selectedHandlingFeeValue, + t, + ]) return { amount, diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 75968df..70910b8 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -470,6 +470,9 @@ export default { }, }, commonUi: { + actions: { + submitting: TEXT_SUBMITTING, + }, dialog: { close: 'Close alert', confirm: 'OK', @@ -839,6 +842,9 @@ export default { exchangeRate: 'Exchange Rate ({{currency}})', exchangeRateValue: '{{coins}} {{platformCoinLabel}} = 1 {{currency}}', convertible: 'Convertible {{currency}}', + handlingFee: 'Handling Fee', + quickDiamondAmount: '{{amount}} diamonds', + quickHandlingFee: 'Fee deducted: {{fee}}', fixedExchangeDiamondAmount: 'Fixed Exchange Diamond Amount', }, }, diff --git a/src/locales/id-ID.ts b/src/locales/id-ID.ts index 1ee4fa9..f099eda 100644 --- a/src/locales/id-ID.ts +++ b/src/locales/id-ID.ts @@ -469,6 +469,9 @@ export default { }, }, commonUi: { + actions: { + submitting: TEXT_SUBMITTING, + }, dialog: { close: 'Tutup notifikasi', confirm: 'OK', @@ -841,6 +844,9 @@ export default { exchangeRate: 'Rasio Penukaran ({{currency}})', exchangeRateValue: '{{coins}} {{platformCoinLabel}} = 1 {{currency}}', convertible: 'Bisa Ditukar {{currency}}', + handlingFee: 'Biaya Penanganan', + quickDiamondAmount: '{{amount}} diamond', + quickHandlingFee: 'Biaya dipotong: {{fee}}', fixedExchangeDiamondAmount: 'Jumlah Berlian Tukar Tetap', }, }, diff --git a/src/locales/ms-MY.ts b/src/locales/ms-MY.ts index 8c61ca0..fd7799a 100644 --- a/src/locales/ms-MY.ts +++ b/src/locales/ms-MY.ts @@ -472,6 +472,9 @@ export default { }, }, commonUi: { + actions: { + submitting: TEXT_SUBMITTING, + }, dialog: { close: 'Tutup notifikasi', confirm: 'OK', @@ -845,6 +848,9 @@ export default { exchangeRate: 'Kadar Pertukaran ({{currency}})', exchangeRateValue: '{{coins}} {{platformCoinLabel}} = 1 {{currency}}', convertible: 'Boleh Tukar {{currency}}', + handlingFee: 'Yuran Pengendalian', + quickDiamondAmount: '{{amount}} berlian', + quickHandlingFee: 'Yuran ditolak: {{fee}}', fixedExchangeDiamondAmount: 'Jumlah Berlian Tukaran Tetap', }, }, diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 548f45a..4b6cb63 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -453,6 +453,9 @@ export default { }, }, commonUi: { + actions: { + submitting: TEXT_SUBMITTING, + }, dialog: { close: '关闭提示', confirm: '知道了', @@ -809,6 +812,9 @@ export default { exchangeRate: '兑换比例 ({{currency}})', exchangeRateValue: '{{coins}} {{platformCoinLabel}} = 1 {{currency}}', convertible: '可兑换{{currency}}', + handlingFee: '手续费', + quickDiamondAmount: '{{amount}} 钻石', + quickHandlingFee: '扣除手续费 {{fee}}', fixedExchangeDiamondAmount: '固定兑换钻石金额', }, }, diff --git a/src/modal/desktop/desktop-withdraw-topup-modal.tsx b/src/modal/desktop/desktop-withdraw-topup-modal.tsx index 9dcc161..bc01f22 100644 --- a/src/modal/desktop/desktop-withdraw-topup-modal.tsx +++ b/src/modal/desktop/desktop-withdraw-topup-modal.tsx @@ -27,7 +27,7 @@ function DesktopWithdrawTopupModal() { } isNormalBg={true} titleAlign="left" - className={'w-design-1200 h-design-700'} + className={'h-design-700 w-design-1320'} >
{type === 'withdraw' ? : } diff --git a/src/type/api.type.ts b/src/type/api.type.ts index fae6207..df42a6e 100644 --- a/src/type/api.type.ts +++ b/src/type/api.type.ts @@ -30,6 +30,7 @@ export interface FinancePayChannelDto { currency_codes?: string[] deposit_banks?: FinanceWithdrawBankDto[] fee_note?: string + handling_fee?: string methods?: FinanceDepositMethodDto[] min_bank?: string min_ewallet?: string @@ -171,6 +172,7 @@ export interface FinancePayChannel { currencyCodes: string[] depositBanks: FinanceWithdrawBank[] feeNote: string + handlingFee: string methods: FinanceDepositMethod[] minBank: string minEwallet: string