- 将注册表单从用户名改为手机号输入 - 集成短信验证码功能,添加验证码输入字段 - 实现发送短信验证码的API调用和倒计时逻辑 - 更新国际化配置以支持验证码相关文案 - 添加桌面端客服聊天弹窗功能 - 调整注册表单UI布局和样式优化 - 修改认证相关常量和类型定义以适配新流程
216 lines
7.3 KiB
TypeScript
216 lines
7.3 KiB
TypeScript
import { startTransition, useEffect, useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
import { getGameLobbyInit } from '@/features/game'
|
|
import { EntryNoticeGateModal } from '@/features/game/components'
|
|
import { MobileEntry } from '@/features/game/entry/mobile-entry.tsx'
|
|
import { PcEntry } from '@/features/game/entry/pc-entry.tsx'
|
|
import { useGameRealtimeSync } from '@/features/game/hooks/use-game-realtime-sync.ts'
|
|
import DesktopAutoSettingModal from '@/features/game/modal/desktop/desktop-auto-setting-modal.tsx'
|
|
import DesktopLanguageModal from '@/features/game/modal/desktop/desktop-language-modal.tsx'
|
|
import DesktopLoginModal from '@/features/game/modal/desktop/desktop-login-modal.tsx'
|
|
import DesktopNoticeModal from '@/features/game/modal/desktop/desktop-notice-modal.tsx'
|
|
import { DesktopPeriodHistoryDrawer } from '@/features/game/modal/desktop/desktop-period-history-drawer.tsx'
|
|
import DesktopProceduresModal from '@/features/game/modal/desktop/desktop-procedures-modal.tsx'
|
|
import DesktopRegisterModal from '@/features/game/modal/desktop/desktop-register-modal.tsx'
|
|
import DesktopRulesModal from '@/features/game/modal/desktop/desktop-rules-modal.tsx'
|
|
import DesktopSupportModal from '@/features/game/modal/desktop/desktop-support-modal.tsx'
|
|
import DesktopUserInfoModal from '@/features/game/modal/desktop/desktop-userInfo-modal.tsx'
|
|
import DesktopWithdrawTopupModal from '@/features/game/modal/desktop/desktop-withdraw-topup-modal.tsx'
|
|
import { useDocumentMetadata } from '@/lib/head/document-metadata'
|
|
import { notify } from '@/lib/notify'
|
|
import { useAuthStore } from '@/store/auth'
|
|
import { useGameRoundStore, useGameSessionStore } from '@/store/game'
|
|
|
|
function EntryModalHost() {
|
|
return (
|
|
<>
|
|
{/* 桌面端登录弹窗:用于未登录用户进入登录流程 */}
|
|
<DesktopLoginModal />
|
|
{/* 桌面端注册弹窗:用于新用户注册账号 */}
|
|
<DesktopRegisterModal />
|
|
{/* 桌面端语言切换弹窗:用于选择当前站点展示语言 */}
|
|
<DesktopLanguageModal />
|
|
{/* 桌面端规则弹窗:展示当前游戏玩法、下注与结算规则 */}
|
|
<DesktopRulesModal />
|
|
{/* 桌面端用户信息弹窗:展示个人资料与站内消息 */}
|
|
<DesktopUserInfoModal />
|
|
{/* 桌面端公告弹窗:展示活动公告或运营通知内容 */}
|
|
<DesktopNoticeModal />
|
|
{/* 桌面端自动托管弹窗:配置自动托管相关条件 */}
|
|
<DesktopAutoSettingModal />
|
|
{/* 桌面端充值/提现前置选择弹窗:先选择进入充值还是提现 */}
|
|
<DesktopProceduresModal />
|
|
{/* 桌面端充值/提现业务弹窗:承载具体的充值或提现内容 */}
|
|
<DesktopWithdrawTopupModal />
|
|
{/* 桌面端客服弹窗:承载在线客服 iframe */}
|
|
<DesktopSupportModal />
|
|
{/* 强制弹窗 */}
|
|
<EntryNoticeGateModal />
|
|
{/* 历史开奖信息弹窗 */}
|
|
<DesktopPeriodHistoryDrawer />
|
|
</>
|
|
)
|
|
}
|
|
|
|
export function EntryPage() {
|
|
const { t } = useTranslation()
|
|
useGameRealtimeSync()
|
|
const hydrateRound = useGameRoundStore((state) => state.hydrateRound)
|
|
const selectChip = useGameRoundStore((state) => state.selectChip)
|
|
const hydrateSession = useGameSessionStore((state) => state.hydrateSession)
|
|
const syncConnection = useGameSessionStore((state) => state.syncConnection)
|
|
const setCurrentUser = useAuthStore((state) => state.setCurrentUser)
|
|
const authStatus = useAuthStore((state) => state.status)
|
|
const authIsHydrated = useAuthStore((state) => state.isHydrated)
|
|
const accessToken = useAuthStore((state) => state.accessToken)
|
|
const lastUnauthorizedAt = useAuthStore((state) => state.lastUnauthorizedAt)
|
|
const isReloginRequired =
|
|
authStatus === 'anonymous' && Boolean(lastUnauthorizedAt)
|
|
|
|
const [isHydrating, setIsHydrating] = useState(true)
|
|
const [isMobile, setIsMobile] = useState(() => {
|
|
if (typeof window === 'undefined') {
|
|
return false
|
|
}
|
|
|
|
return window.matchMedia('(max-width: 768px)').matches
|
|
})
|
|
|
|
useDocumentMetadata({
|
|
title: t('game.metaTitle'),
|
|
description: t('game.metaDescription'),
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (!authIsHydrated) {
|
|
setIsHydrating(true)
|
|
|
|
return
|
|
}
|
|
|
|
if (isReloginRequired || authStatus !== 'authenticated' || !accessToken) {
|
|
setIsHydrating(false)
|
|
|
|
return
|
|
}
|
|
|
|
let cancelled = false
|
|
|
|
void getGameLobbyInit()
|
|
.then((result) => {
|
|
if (cancelled) {
|
|
return
|
|
}
|
|
|
|
startTransition(() => {
|
|
const snapshot = result.snapshot
|
|
|
|
hydrateRound({
|
|
cells: snapshot.cells,
|
|
chips: snapshot.chips,
|
|
history: snapshot.history,
|
|
maxSelectionCount: snapshot.maxSelectionCount,
|
|
round: snapshot.round,
|
|
selections: snapshot.selections,
|
|
trends: snapshot.trends,
|
|
})
|
|
const defaultChipId =
|
|
snapshot.chips.find((chip) => chip.isDefault)?.id ?? null
|
|
|
|
if (defaultChipId) {
|
|
selectChip(defaultChipId)
|
|
}
|
|
hydrateSession({
|
|
announcements: snapshot.announcements,
|
|
connection: snapshot.connection,
|
|
dashboard: snapshot.dashboard,
|
|
})
|
|
|
|
const currentUser = useAuthStore.getState().currentUser
|
|
|
|
if (currentUser) {
|
|
setCurrentUser({
|
|
...currentUser,
|
|
coin: result.userSnapshot.coin,
|
|
currentStreak: result.userSnapshot.current_streak,
|
|
isJackpot: result.userSnapshot.is_jackpot,
|
|
oddsFactor: result.userSnapshot.odds_factor,
|
|
streakLevel: result.userSnapshot.streak_level,
|
|
})
|
|
}
|
|
|
|
setIsHydrating(false)
|
|
})
|
|
})
|
|
.catch((error) => {
|
|
console.error('Failed to load game lobby init', error)
|
|
|
|
if (!cancelled) {
|
|
if (authStatus === 'authenticated') {
|
|
notify.error(t('commonUi.toast.lobbyInitFailed'), {
|
|
description: error instanceof Error ? error.message : undefined,
|
|
})
|
|
}
|
|
|
|
syncConnection({
|
|
connectedAt: null,
|
|
lastError:
|
|
error instanceof Error
|
|
? error.message
|
|
: 'Failed to load game lobby init',
|
|
lastMessageAt: null,
|
|
latencyMs: null,
|
|
status: 'disconnected',
|
|
transport: 'offline',
|
|
})
|
|
setIsHydrating(false)
|
|
}
|
|
})
|
|
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [
|
|
accessToken,
|
|
authIsHydrated,
|
|
authStatus,
|
|
hydrateRound,
|
|
hydrateSession,
|
|
isReloginRequired,
|
|
selectChip,
|
|
setCurrentUser,
|
|
syncConnection,
|
|
t,
|
|
])
|
|
|
|
useEffect(() => {
|
|
if (typeof window === 'undefined') {
|
|
return
|
|
}
|
|
|
|
const mediaQuery = window.matchMedia('(max-width: 768px)')
|
|
const syncLayout = (event?: MediaQueryListEvent) => {
|
|
setIsMobile(event?.matches ?? mediaQuery.matches)
|
|
}
|
|
|
|
syncLayout()
|
|
mediaQuery.addEventListener('change', syncLayout)
|
|
|
|
return () => {
|
|
mediaQuery.removeEventListener('change', syncLayout)
|
|
}
|
|
}, [])
|
|
|
|
return (
|
|
<section
|
|
aria-busy={isHydrating}
|
|
aria-label={t('game.lobbyTitle')}
|
|
className="flex min-h-0 flex-1 flex-col"
|
|
>
|
|
{isMobile ? <MobileEntry /> : <PcEntry />}
|
|
<EntryModalHost />
|
|
</section>
|
|
)
|
|
}
|