feat(auth): 更新用户认证类型定义并优化提款功能

- 添加 AuthCoinAmount 类型和 AuthWithdrawAccount 接口
- 在 AuthUser 和相关 DTO 中增加余额和提款账户字段
- 将获取用户资料的请求方法从 POST 改为 GET
- 实现提款账户信息的默认值设置和状态管理
- 添加 JackpotPoolTicker 组件并在状态栏中显示
- 更新多语言文件中的游戏规则说明
- 实现提款表单的预填充和状态重置功能
This commit is contained in:
JiaJun
2026-07-15 18:00:33 +08:00
parent fa40c23720
commit 9ade40c29b
10 changed files with 154 additions and 31 deletions

View File

@@ -67,7 +67,7 @@ function logAuthSessionExpiry(action: string, session: AuthSessionInput) {
}
async function getCurrentUserProfileByToken(userToken: string) {
const response = await api.post<AuthUserProfileDto>(AUTH_ENDPOINTS.profile, {
const response = await api.get<AuthUserProfileDto>(AUTH_ENDPOINTS.profile, {
headers: {
Authorization: `Bearer ${userToken}`,
'user-token': userToken,
@@ -195,7 +195,7 @@ export async function sendSmsCode(
}
export async function getCurrentUserProfile() {
const response = await api.post<AuthUserProfileDto>(AUTH_ENDPOINTS.profile)
const response = await api.get<AuthUserProfileDto>(AUTH_ENDPOINTS.profile)
return normalizeAuthUserProfile(
unwrapEnvelope(

View File

@@ -21,9 +21,13 @@ function formatWinAmount(value: string) {
type MessageBroadcastProps = {
className?: string
showJackpotPool?: boolean
}
export function MessageBroadcast({ className }: MessageBroadcastProps) {
export function MessageBroadcast({
className,
showJackpotPool = true,
}: MessageBroadcastProps) {
const { t } = useTranslation()
const prefersReducedMotion = useReducedMotion()
const jackpotBroadcasts = useGameSessionStore(
@@ -165,10 +169,12 @@ export function MessageBroadcast({ className }: MessageBroadcastProps) {
</AnimatePresence>
</div>
</div>
{showJackpotPool ? (
<JackpotPoolTicker
variant="inline"
className="!w-fit max-w-[54%] shrink-0 md:max-w-[calc(var(--design-unit)*320)]"
/>
) : null}
</section>
)
}

View File

@@ -8,6 +8,7 @@ import fire from '@/assets/system/fire.webp'
import lock from '@/assets/system/lock.webp'
import statusCenter from '@/assets/system/status-center.webp'
import statusLine from '@/assets/system/status-line.webp'
import { JackpotPoolTicker } from '@/components/jackpot-pool-ticker.tsx'
import { LottiePlayer } from '@/components/lottie-player.tsx'
import { MessageBroadcast } from '@/components/message-broadcast.tsx'
import { SmartBackground } from '@/components/smart-background.tsx'
@@ -43,7 +44,7 @@ export function DesktopStatusLine() {
return (
<div className={'relative w-full flex flex-col text-design-22'}>
<div className={'w-full px-design-16 mb-design-10'}>
<MessageBroadcast />
<MessageBroadcast showJackpotPool={false} />
</div>
<SmartBackground
src={statusLine}
@@ -157,8 +158,8 @@ export function DesktopStatusLine() {
className={countdownClassName}
/>
</div>
<div className="flex flex-1 items-center justify-center gap-design-88">
<div className="flex items-center gap-design-9">
<div className="grid min-w-0 flex-1 grid-cols-[repeat(3,minmax(0,1fr))] items-center justify-items-stretch pl-design-6 pr-design-16">
<div className="flex min-w-0 items-center justify-center gap-design-6">
<span className="relative flex h-design-24 w-design-24 items-center justify-center">
<motion.span
aria-hidden="true"
@@ -207,14 +208,26 @@ export function DesktopStatusLine() {
</div>
</div>
<div className="flex items-baseline gap-design-7 font-bold leading-none">
<div className="flex min-w-0 items-baseline justify-center gap-design-5 font-bold leading-none">
<span className="text-design-20 tracking-[0.06em] text-[#9FBAC0]">
{t('gameDesktop.status.roundId')}:
</span>
<span className="text-design-24 font-black tracking-[0.06em] text-[#E7FFFF] [text-shadow:0_0_calc(var(--design-unit)*9)_rgba(75,255,254,0.38)]">
<span className="min-w-0 truncate text-design-24 font-black tracking-[0.06em] text-[#E7FFFF] [text-shadow:0_0_calc(var(--design-unit)*9)_rgba(75,255,254,0.38)]">
{roundId}
</span>
</div>
<div className="flex min-w-0 items-center justify-center">
<JackpotPoolTicker
variant="inline"
className={cn(
'!h-design-52 !w-full max-w-full justify-center !px-design-5 md:!px-design-6',
'[&>div:first-child]:md:!mr-design-5 [&>div:first-child]:md:!h-design-20 [&>div:first-child]:md:!w-design-20',
'[&>span:first-of-type]:md:!mr-[20px] [&>span:first-of-type]:md:!text-design-10',
'[&>span:last-child]:!shrink-0 [&>span:last-child]:!text-design-9 md:[&>span:last-child]:!text-design-20',
)}
/>
</div>
</div>
</SmartBackground>
</div>

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
DEFAULT_WITHDRAW_CONFIG,
@@ -7,7 +7,7 @@ import {
} from '@/constants'
import { useDepositWithdrawConfig } from '@/hooks/use-deposit-withdraw-config'
import { useAuthStore } from '@/store'
import type { DepositWithdrawConfig } from '@/type'
import type { AuthWithdrawAccount, DepositWithdrawConfig } from '@/type'
const QUICK_WITHDRAW_OPTION_COUNT = 6
@@ -16,6 +16,24 @@ interface WithdrawDiamondBounds {
minimumDiamonds: number
}
interface WithdrawAccountDefaults {
bankAccount: string
holderName: string
receiverEmail: string
receiverPhone: string
}
function getWithdrawAccountDefaults(
withdraw: AuthWithdrawAccount | null | undefined,
): WithdrawAccountDefaults {
return {
bankAccount: withdraw?.receiveAccount ?? '',
holderName: withdraw?.receiverName ?? '',
receiverEmail: withdraw?.receiverEmail ?? '',
receiverPhone: withdraw?.receiverMobile ?? '',
}
}
function formatNumber(locale: string, value: number) {
return new Intl.NumberFormat(locale).format(value)
}
@@ -200,6 +218,11 @@ export function useWithdrawVm() {
const { i18n, t } = useTranslation()
const currentUser = useAuthStore((state) => state.currentUser)
const withdrawConfigQuery = useDepositWithdrawConfig()
const withdrawAccountDefaults = useMemo(
() => getWithdrawAccountDefaults(currentUser?.withdraw),
[currentUser?.withdraw],
)
const hasEditedWithdrawAccountRef = useRef(false)
const config = useMemo(() => {
const baseConfig = getNormalizedConfig(
withdrawConfigQuery.data,
@@ -228,10 +251,18 @@ export function useWithdrawVm() {
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 [holderName, setHolderNameState] = useState(
() => withdrawAccountDefaults.holderName,
)
const [bankAccount, setBankAccountState] = useState(
() => withdrawAccountDefaults.bankAccount,
)
const [receiverEmail, setReceiverEmailState] = useState(
() => withdrawAccountDefaults.receiverEmail,
)
const [receiverPhone, setReceiverPhoneState] = useState(
() => withdrawAccountDefaults.receiverPhone,
)
const sortedPayChannels = useMemo(
() =>
@@ -318,6 +349,28 @@ export function useWithdrawVm() {
},
[maxWithdrawAmount],
)
const applyWithdrawAccountDefaults = useCallback(() => {
setHolderNameState(withdrawAccountDefaults.holderName)
setBankAccountState(withdrawAccountDefaults.bankAccount)
setReceiverEmailState(withdrawAccountDefaults.receiverEmail)
setReceiverPhoneState(withdrawAccountDefaults.receiverPhone)
}, [withdrawAccountDefaults])
const setHolderName = useCallback((nextHolderName: string) => {
hasEditedWithdrawAccountRef.current = true
setHolderNameState(nextHolderName)
}, [])
const setBankAccount = useCallback((nextBankAccount: string) => {
hasEditedWithdrawAccountRef.current = true
setBankAccountState(nextBankAccount)
}, [])
const setReceiverEmail = useCallback((nextReceiverEmail: string) => {
hasEditedWithdrawAccountRef.current = true
setReceiverEmailState(nextReceiverEmail)
}, [])
const setReceiverPhone = useCallback((nextReceiverPhone: string) => {
hasEditedWithdrawAccountRef.current = true
setReceiverPhoneState(nextReceiverPhone)
}, [])
useEffect(() => {
const firstAvailablePayChannel = sortedPayChannels[0]
@@ -406,6 +459,14 @@ export function useWithdrawVm() {
}
}, [amount, maxWithdrawAmount, setAmount])
useEffect(() => {
if (hasEditedWithdrawAccountRef.current) {
return
}
applyWithdrawAccountDefaults()
}, [applyWithdrawAccountDefaults])
const quickAmounts = useMemo(() => {
if (!selectedCurrency || !withdrawDiamondBounds) {
return []
@@ -474,11 +535,14 @@ export function useWithdrawVm() {
setPaymentChannelCode(nextPaymentChannelCode)
setPaymentType(nextPaymentMethod?.code ?? '')
setBankCode('')
setHolderName('')
setBankAccount('')
setReceiverEmail('')
setReceiverPhone('')
}, [config, maxWithdrawAmount, sortedPayChannels])
hasEditedWithdrawAccountRef.current = false
applyWithdrawAccountDefaults()
}, [
applyWithdrawAccountDefaults,
config,
maxWithdrawAmount,
sortedPayChannels,
])
const selectedCurrencyPreview = useMemo(() => {
const previewCurrencyCode = selectedCurrency?.code ?? '--'

View File

@@ -24,9 +24,11 @@ export function normalizeAuthUserProfile(dto: AuthUserProfileDto): AuthUser {
return {
channelId: dto.channel_id,
coin: dto.coin,
coinBalance: dto.coin_balance,
createTime: dto.create_time,
currentStreak: dto.current_streak,
email: dto.email,
frozenBalance: dto.frozen_balance,
headImage: dto.head_image,
id: dto.uuid,
lastBetPeriodNo: dto.last_bet_period_no,
@@ -34,8 +36,18 @@ export function normalizeAuthUserProfile(dto: AuthUserProfileDto): AuthUser {
phone: dto.phone,
registerInviteCode: dto.register_invite_code,
riskFlags: dto.risk_flags,
totalDepositCoin: dto.total_deposit_coin,
totalWithdrawCoin: dto.total_withdraw_coin,
username: dto.username,
uuid: dto.uuid,
withdraw: dto.withdraw
? {
receiveAccount: dto.withdraw.receive_account,
receiverEmail: dto.withdraw.receiver_email,
receiverMobile: dto.withdraw.receiver_mobile,
receiverName: dto.withdraw.receiver_name,
}
: undefined,
}
}

View File

@@ -251,7 +251,8 @@ export default {
'',
'1. Only **1 winning number** is drawn in each round.',
'2. If the players selected numbers **include the winning number**, the bet is considered a win.',
'3. If a player selects multiple numbers and the bet amount is the same for each number, then:',
'3. Each round, a player may bet on **15 numbers only**, and must not select more than **5**.',
'4. If a player selects multiple numbers and the bet amount is the same for each number, then:',
'',
'```text',
'Winning Number Stake = Total Bet Amount ÷ Number of Selected Numbers',

View File

@@ -250,7 +250,8 @@ export default {
'',
'1. Hanya **1 nomor pemenang** yang akan dikeluarkan di setiap putaran.',
'2. Jika nomor yang dipilih pemain **mencakup nomor pemenang**, maka taruhan tersebut dianggap menang.',
'3. Jika pemain memilih beberapa nomor dan jumlah taruhan untuk setiap nomor sama, maka:',
'3. Dalam setiap putaran, pemain hanya boleh bertaruh pada **15 nomor**, dan tidak boleh melebihi **5 nomor**.',
'4. Jika pemain memilih beberapa nomor dan jumlah taruhan untuk setiap nomor sama, maka:',
'',
'```text',
'Nilai Taruhan Nomor Menang = Total Taruhan ÷ Jumlah Nomor yang Dipilih',

View File

@@ -253,7 +253,8 @@ export default {
'',
'1. Hanya **1 nombor kemenangan** akan dikeluarkan dalam setiap pusingan.',
'2. Jika nombor yang dipilih oleh pemain **mengandungi nombor kemenangan**, pertaruhan tersebut dianggap menang.',
'3. Jika pemain memilih beberapa nombor dan jumlah pertaruhan bagi setiap nombor adalah sama, maka:',
'3. Dalam setiap pusingan, pemain hanya boleh bertaruh pada **15 nombor**, dan tidak boleh melebihi **5 nombor**.',
'4. Jika pemain memilih beberapa nombor dan jumlah pertaruhan bagi setiap nombor adalah sama, maka:',
'',
'```text',
'Nilai Pertaruhan Nombor Menang = Jumlah Pertaruhan ÷ Bilangan Nombor yang Dipilih',

View File

@@ -243,10 +243,11 @@ export default {
'',
'1. 每期只开出 **1 个中奖号码**。',
'2. 如果玩家押注的号码中 **包含开奖号码**,则视为中奖。',
'3. 如果玩家选择了多个号码,且每个号码押注金额相同,则:',
'3. 每场玩家最多只能下注 **15 个号码**,不能超过 **5 个**。',
'4. 如果玩家选择了多个号码,且每个号码押注金额相同,则:',
'',
'```text',
'压中号码面额 = 本笔注总额 ÷ 所选号码个数',
'压中号码面额 = 本笔注总额 ÷ 所选号码个数',
'```',
'',
'**示例:**',

View File

@@ -4,12 +4,23 @@ export type AuthStatus = 'anonymous' | 'authenticated' | 'restoring'
export type AuthSubmitContext = 'login' | 'register'
export type AuthCoinAmount = number | string
export interface AuthWithdrawAccount {
receiveAccount?: string
receiverEmail?: string
receiverMobile?: string
receiverName?: string
}
export interface AuthUser {
createTime?: number
channelId?: number
coin?: string
coin?: AuthCoinAmount
coinBalance?: number
currentStreak?: number
email?: string
frozenBalance?: number
headImage?: string
id: string
isJackpot?: boolean
@@ -21,8 +32,11 @@ export interface AuthUser {
riskFlags?: number
roles?: string[]
streakLevel?: number
totalDepositCoin?: number
totalWithdrawCoin?: number
username?: string
uuid?: string
withdraw?: AuthWithdrawAccount
}
export interface AuthSessionInput {
@@ -47,7 +61,7 @@ export interface AuthTokenDto {
export interface AuthUserDto {
channel_id: number
coin: string
coin: AuthCoinAmount
phone?: string
risk_flags: number
username: string
@@ -69,17 +83,27 @@ export interface RefreshTokenDto {
export interface AuthUserProfileDto {
channel_id: number
coin: string
coin: number
coin_balance: number
create_time: number
current_streak: number
email: string
frozen_balance: number
head_image: string
last_bet_period_no: string
phone: string
register_invite_code: string
risk_flags: number
total_deposit_coin: number
total_withdraw_coin: number
username: string
uuid: string
withdraw?: {
receive_account: string
receiver_email: string
receiver_mobile: string
receiver_name: string
} | null
}
export interface LoginRequestDto {