refactor(constants): 提取常量并优化国际化配置

- 创建API相关常量文件,包括响应码、HTTP状态码、请求头等
- 将认证相关常量从auth模块提取到独立的常量文件
- 在API客户端中使用新定义的常量替换硬编码值
- 更新认证API和服务中对常量的引用
- 在国际化配置中创建统一的文案常量以减少重复
- 将认证表单验证规则改为使用常量配置
This commit is contained in:
JiaJun
2026-06-02 12:03:29 +08:00
parent 68cf8c0be2
commit 901ad1c30b
30 changed files with 707 additions and 273 deletions

View File

@@ -1,4 +1,9 @@
import { AUTH_ENDPOINTS, AUTH_SKIP_REFRESH_CONTEXT_KEY } from '@/constants'
import {
API_SUCCESS_CODE,
AUTH_ENDPOINTS,
AUTH_SKIP_REFRESH_CONTEXT_KEY,
SMS_SEND_EVENT_REGISTER,
} from '@/constants'
import { api } from '@/lib/api/api-client'
import { ApiError } from '@/lib/api/api-error'
import type { AuthSessionInput } from '@/store/auth'
@@ -34,7 +39,7 @@ function unwrapEnvelope<T>(
response: ApiResponse<T>,
fallbackErrorKey = 'auth.errors.requestFailed',
) {
if (response.code === 1) {
if (response.code === API_SUCCESS_CODE) {
return response.data
}
@@ -172,7 +177,7 @@ export async function sendSmsCode(
AUTH_ENDPOINTS.sendSmsCode,
{
json: {
event: 'user_register',
event: SMS_SEND_EVENT_REGISTER,
mobile: payload.mobile,
},
},

View File

@@ -1,3 +1,4 @@
import type { SMS_SEND_EVENT_REGISTER } from '@/constants'
import type { AuthSessionInput, AuthUser } from '@/store/auth'
export interface AuthApiEnvelope<T> {
@@ -70,7 +71,7 @@ export interface RegisterRequestDto {
}
export interface SendSmsCodeRequestDto {
event: 'user_register'
event: typeof SMS_SEND_EVENT_REGISTER
mobile: string
}

View File

@@ -1,5 +1,9 @@
import { useMutation } from '@tanstack/react-query'
import { useForm } from 'react-hook-form'
import {
DEFAULT_REGISTER_INVITE_CODE,
REGISTER_INVITE_CODE_QUERY_PARAM,
} from '@/constants'
import i18n from '@/i18n'
import { notify } from '@/lib/notify'
import { useAuthStore } from '@/store/auth'
@@ -15,9 +19,6 @@ interface UseRegisterFormOptions {
onSuccess?: () => void
}
const REGISTER_INVITE_CODE_QUERY_PARAM = 'registerInviteCode'
const DEFAULT_REGISTER_INVITE_CODE = 'D97DBC16'
function getInitialRegisterInviteCode() {
if (typeof window === 'undefined') {
return DEFAULT_REGISTER_INVITE_CODE

View File

@@ -1,12 +1,11 @@
import { useMutation } from '@tanstack/react-query'
import { useEffect, useState } from 'react'
import { SMS_CODE_COOLDOWN_FALLBACK_SECONDS } from '@/constants'
import i18n from '@/i18n'
import { notify } from '@/lib/notify'
import { sendSmsCode } from '../api/auth-api'
import { toAuthSubmitErrorKey } from './auth-error-key'
const FALLBACK_SMS_CODE_COOLDOWN_SECONDS = 60
export function useSendSmsCode() {
const [remainingSeconds, setRemainingSeconds] = useState(0)
const mutation = useMutation({
@@ -24,7 +23,7 @@ export function useSendSmsCode() {
setRemainingSeconds(
result.expiresIn > 0
? result.expiresIn
: FALLBACK_SMS_CODE_COOLDOWN_SECONDS,
: SMS_CODE_COOLDOWN_FALLBACK_SECONDS,
)
notify.success(i18n.t('auth.register.sms.success'))
},

View File

@@ -1,5 +1,11 @@
import { z } from 'zod'
import {
INVITE_CODE_MAX_LENGTH,
PASSWORD_MAX_LENGTH,
PASSWORD_MIN_LENGTH,
} from '@/constants'
const usernameSchema = z
.string()
.trim()
@@ -12,8 +18,8 @@ const captchaSchema = z
const passwordSchema = z
.string()
.min(6, 'auth.validation.password.min')
.max(32, 'auth.validation.password.max')
.min(PASSWORD_MIN_LENGTH, 'auth.validation.password.min')
.max(PASSWORD_MAX_LENGTH, 'auth.validation.password.max')
export const loginFormSchema = z.object({
password: passwordSchema,
@@ -28,7 +34,7 @@ export const registerFormSchema = z
.string()
.trim()
.min(1, 'auth.validation.inviteCode.required')
.max(32, 'auth.validation.inviteCode.max'),
.max(INVITE_CODE_MAX_LENGTH, 'auth.validation.inviteCode.max'),
password: passwordSchema,
mobile: usernameSchema,
})

View File

@@ -1,4 +1,8 @@
import { FINANCE_API_ENDPOINTS } from '@/constants'
import {
API_SUCCESS_CODE,
DEFAULT_LIST_PAGE_SIZE,
FINANCE_API_ENDPOINTS,
} from '@/constants'
import { api } from '@/lib/api/api-client'
import { ApiError } from '@/lib/api/api-error'
import type { ApiResponse } from '@/type'
@@ -30,7 +34,7 @@ function unwrapFinanceEnvelope<T>(
response: ApiResponse<T>,
fallbackMessage = 'Finance request failed',
) {
if (response.code === 1) {
if (response.code === API_SUCCESS_CODE) {
return response.data
}
@@ -194,7 +198,7 @@ function normalizeFinanceOrderList(dto: FinanceOrderListDto): FinanceOrderList {
list: (dto.list ?? []).map(normalizeFinanceOrderItem),
pagination: {
page: dto.pagination?.page ?? 1,
page_size: dto.pagination?.page_size ?? 20,
page_size: dto.pagination?.page_size ?? DEFAULT_LIST_PAGE_SIZE,
total: dto.pagination?.total ?? 0,
},
}
@@ -237,7 +241,8 @@ function normalizeWalletRecordList(dto: WalletRecordListDto): WalletRecordList {
list: (dto.list ?? []).map(normalizeWalletRecordItem),
pagination: {
page: dto.pagination?.page ?? dto.page ?? 1,
page_size: dto.pagination?.page_size ?? dto.page_size ?? 20,
page_size:
dto.pagination?.page_size ?? dto.page_size ?? DEFAULT_LIST_PAGE_SIZE,
total: dto.pagination?.total ?? dto.total ?? 0,
},
}
@@ -301,7 +306,7 @@ export async function getDepositOrderList(params?: {
{
searchParams: {
page: String(params?.page ?? 1),
page_size: String(params?.pageSize ?? 20),
page_size: String(params?.pageSize ?? DEFAULT_LIST_PAGE_SIZE),
},
},
)
@@ -322,7 +327,7 @@ export async function getWithdrawOrderList(params?: {
{
searchParams: {
page: String(params?.page ?? 1),
page_size: String(params?.pageSize ?? 20),
page_size: String(params?.pageSize ?? DEFAULT_LIST_PAGE_SIZE),
},
},
)
@@ -344,7 +349,7 @@ export async function getWalletRecordList(params?: {
{
searchParams: {
page: String(params?.page ?? 1),
page_size: String(params?.pageSize ?? 20),
page_size: String(params?.pageSize ?? DEFAULT_LIST_PAGE_SIZE),
type: params?.type ?? 'payout',
},
},

View File

@@ -1,4 +1,8 @@
import { GAME_API_ENDPOINTS } from '@/constants'
import {
API_SUCCESS_CODE,
DEFAULT_LIST_PAGE_SIZE,
GAME_API_ENDPOINTS,
} from '@/constants'
import { api } from '@/lib/api/api-client'
import { ApiError } from '@/lib/api/api-error'
import type { ApiResponse } from '@/type'
@@ -51,7 +55,7 @@ function unwrapGameEnvelope<T>(
response: ApiResponse<T>,
fallbackMessage = 'Game request failed',
) {
if (response.code === 1) {
if (response.code === API_SUCCESS_CODE) {
return response.data
}
@@ -424,7 +428,7 @@ export async function getNoticeList(params?: {
const response = await api.get<NoticeListDto>(GAME_API_ENDPOINTS.noticeList, {
searchParams: {
page: String(params?.page ?? 1),
page_size: String(params?.pageSize ?? 20),
page_size: String(params?.pageSize ?? DEFAULT_LIST_PAGE_SIZE),
},
})
const dto = unwrapGameEnvelope(
@@ -478,7 +482,7 @@ export async function getGameBetMyOrders(params: {
{
json: {
page: params.page ?? 1,
page_size: params.pageSize ?? 20,
page_size: params.pageSize ?? DEFAULT_LIST_PAGE_SIZE,
},
},
)

View File

@@ -1,4 +1,4 @@
import { GAME_API_ENDPOINTS } from '@/constants'
import { API_SUCCESS_CODE, GAME_API_ENDPOINTS } from '@/constants'
import { api } from '@/lib/api/api-client'
import { ApiError } from '@/lib/api/api-error'
import type { ApiResponse } from '@/type'
@@ -16,7 +16,7 @@ interface GamePeriodHistoryDto {
function unwrapPeriodHistoryEnvelope(
response: ApiResponse<GamePeriodHistoryDto>,
) {
if (response.code === 1) {
if (response.code === API_SUCCESS_CODE) {
return response.data
}

View File

@@ -1,6 +1,7 @@
import { startTransition, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { MOBILE_LAYOUT_BREAKPOINT_PX } from '@/constants'
import { getGameLobbyInit } from '@/features/game'
import { EntryNoticeGateModal } from '@/features/game/components'
import { MobileEntry } from '@/features/game/entry/mobile-entry.tsx'
@@ -74,7 +75,8 @@ export function EntryPage() {
return false
}
return window.matchMedia('(max-width: 768px)').matches
return window.matchMedia(`(max-width: ${MOBILE_LAYOUT_BREAKPOINT_PX}px)`)
.matches
})
useDocumentMetadata({
@@ -189,7 +191,9 @@ export function EntryPage() {
return
}
const mediaQuery = window.matchMedia('(max-width: 768px)')
const mediaQuery = window.matchMedia(
`(max-width: ${MOBILE_LAYOUT_BREAKPOINT_PX}px)`,
)
const syncLayout = (event?: MediaQueryListEvent) => {
setIsMobile(event?.matches ?? mediaQuery.matches)
}

View File

@@ -1,7 +1,10 @@
import { useQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { DEFAULT_APP_LANGUAGE } from '@/constants'
import {
DEFAULT_APP_LANGUAGE,
FINANCE_CONFIG_QUERY_STALE_TIME_MS,
} from '@/constants'
import { getDepositTierList } from '@/features/game/api'
export function useDepositTierList() {
@@ -12,6 +15,6 @@ export function useDepositTierList() {
return useQuery({
queryKey: ['finance', 'deposit-tier-list', language],
queryFn: () => getDepositTierList(),
staleTime: 5 * 60 * 1000,
staleTime: FINANCE_CONFIG_QUERY_STALE_TIME_MS,
})
}

View File

@@ -1,7 +1,10 @@
import { useQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { DEFAULT_APP_LANGUAGE } from '@/constants'
import {
DEFAULT_APP_LANGUAGE,
FINANCE_CONFIG_QUERY_STALE_TIME_MS,
} from '@/constants'
import { getDepositWithdrawConfig } from '@/features/game/api'
export function useDepositWithdrawConfig() {
@@ -12,6 +15,6 @@ export function useDepositWithdrawConfig() {
return useQuery({
queryKey: ['finance', 'deposit-withdraw-config', language],
queryFn: () => getDepositWithdrawConfig(),
staleTime: 5 * 60 * 1000,
staleTime: FINANCE_CONFIG_QUERY_STALE_TIME_MS,
})
}

View File

@@ -2,12 +2,11 @@ import { useInfiniteQuery } from '@tanstack/react-query'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { DEFAULT_LIST_PAGE_SIZE } from '@/constants'
import { getDepositOrderList, getWithdrawOrderList } from '@/features/game/api'
export type FinanceRecordType = 'deposit' | 'withdraw'
const FINANCE_RECORD_PAGE_SIZE = 20
const FINANCE_RECORD_TYPE_OPTIONS: Array<{
key: FinanceRecordType
labelKey: string
@@ -46,11 +45,11 @@ export function useFinanceRecordsVm({ enabled }: { enabled: boolean }) {
recordType === 'deposit'
? getDepositOrderList({
page: pageParam,
pageSize: FINANCE_RECORD_PAGE_SIZE,
pageSize: DEFAULT_LIST_PAGE_SIZE,
})
: getWithdrawOrderList({
page: pageParam,
pageSize: FINANCE_RECORD_PAGE_SIZE,
pageSize: DEFAULT_LIST_PAGE_SIZE,
}),
enabled,
getNextPageParam: (lastPage) => {

View File

@@ -1,4 +1,9 @@
import { useEffect, useMemo, useState } from 'react'
import {
CONNECTION_LATENCY_FAIR_MS,
CONNECTION_LATENCY_GOOD_MS,
CONNECTION_LATENCY_POOR_MS,
} from '@/constants'
import { useAppLanguage } from '@/features/game/hooks/use-app-language'
import {
isDesktopFullscreen,
@@ -74,7 +79,7 @@ function resolveSignalPresentation(input: {
} satisfies SignalPresentation
}
if (input.latencyMs <= 80) {
if (input.latencyMs <= CONNECTION_LATENCY_GOOD_MS) {
return {
activeBars: 4,
latencyLabel: String(input.latencyMs),
@@ -82,7 +87,7 @@ function resolveSignalPresentation(input: {
} satisfies SignalPresentation
}
if (input.latencyMs <= 150) {
if (input.latencyMs <= CONNECTION_LATENCY_FAIR_MS) {
return {
activeBars: 3,
latencyLabel: String(input.latencyMs),
@@ -90,7 +95,7 @@ function resolveSignalPresentation(input: {
} satisfies SignalPresentation
}
if (input.latencyMs <= 300) {
if (input.latencyMs <= CONNECTION_LATENCY_POOR_MS) {
return {
activeBars: 2,
latencyLabel: String(input.latencyMs),

View File

@@ -3,9 +3,9 @@ import dayjs from 'dayjs'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { DEFAULT_LIST_PAGE_SIZE } from '@/constants'
import { getWalletRecordList } from '@/features/game/api'
const WALLET_RECORD_PAGE_SIZE = 20
const WALLET_RECORD_TYPE = 'payout'
function formatWalletAmount(value: string, locale: string) {
@@ -47,7 +47,7 @@ export function useWalletRecordsVm({ enabled }: { enabled: boolean }) {
queryFn: ({ pageParam }) =>
getWalletRecordList({
page: pageParam,
pageSize: WALLET_RECORD_PAGE_SIZE,
pageSize: DEFAULT_LIST_PAGE_SIZE,
type: WALLET_RECORD_TYPE,
}),
enabled,

View File

@@ -1,7 +1,13 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { DEFAULT_WITHDRAW_CONFIG, QUICK_FIAT_AMOUNTS } from '@/constants'
import {
DEFAULT_CURRENCY_CODE,
DEFAULT_WITHDRAW_CONFIG,
QUICK_FIAT_AMOUNTS,
WITHDRAW_EMAIL_PATTERN,
WITHDRAW_PHONE_PATTERN,
} from '@/constants'
import type { DepositWithdrawConfig } from '@/features/game/api'
import { useDepositWithdrawConfig } from '@/features/game/hooks/use-deposit-withdraw-config'
import { useAuthStore } from '@/store'
@@ -47,7 +53,7 @@ function isValidEmail(value: string) {
return false
}
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim())
return WITHDRAW_EMAIL_PATTERN.test(value.trim())
}
function isValidPhone(value: string) {
@@ -57,7 +63,7 @@ function isValidPhone(value: string) {
return false
}
return /^\+?\d{6,20}$/.test(normalized)
return WITHDRAW_PHONE_PATTERN.test(normalized)
}
export function useWithdrawVm() {
@@ -88,7 +94,7 @@ export function useWithdrawVm() {
const [amount, setAmountState] = useState(0)
const [hasInitializedAmount, setHasInitializedAmount] = useState(false)
const [currencyCode, setCurrencyCode] = useState(
config.currencies[0]?.code ?? 'MYR',
config.currencies[0]?.code ?? DEFAULT_CURRENCY_CODE,
)
const [paymentChannelCode, setPaymentChannelCode] = useState('')
const [bankCode, setBankCode] = useState('')
@@ -207,7 +213,7 @@ export function useWithdrawVm() {
}, [locale, maxWithdrawAmount, selectedCurrency.code, selectedRate])
const resetForm = useCallback(() => {
const nextCurrencyCode = config.currencies[0]?.code ?? 'MYR'
const nextCurrencyCode = config.currencies[0]?.code ?? DEFAULT_CURRENCY_CODE
const nextCurrency = getActiveCurrencyCode(
config.currencies,
nextCurrencyCode,

View File

@@ -5,6 +5,7 @@ import { CenterModal } from '@/components/center-modal.tsx'
import { SmartBackground } from '@/components/smart-background.tsx'
import { Input } from '@/components/ui/input.tsx'
import { Switch } from '@/components/ui/switch.tsx'
import { AUTO_HOSTING_DEFAULT_SINGLE_WIN_THRESHOLD } from '@/constants'
import { notify } from '@/lib/notify'
import { useModalStore } from '@/store'
import { useAuthStore } from '@/store/auth'
@@ -51,7 +52,9 @@ function DesktopAutoSettingModal() {
const [balanceLimitEnabled, setBalanceLimitEnabled] = useState(false)
const [balanceLimitValue, setBalanceLimitValue] = useState('0')
const [singleWinLimitEnabled, setSingleWinLimitEnabled] = useState(false)
const [singleWinLimitValue, setSingleWinLimitValue] = useState('50000')
const [singleWinLimitValue, setSingleWinLimitValue] = useState(
String(AUTO_HOSTING_DEFAULT_SINGLE_WIN_THRESHOLD),
)
const [jackpotStopEnabled, setJackpotStopEnabled] = useState(false)
function handleClose() {

View File

@@ -15,6 +15,7 @@ import userInfoBg from '@/assets/system/userInfo-bg.webp'
import { CenterModal } from '@/components/center-modal.tsx'
import { SmartBackground } from '@/components/smart-background.tsx'
import { SmartImage } from '@/components/smart-image.tsx'
import { REGISTER_INVITE_CODE_QUERY_PARAM } from '@/constants'
import { logoutWithPassword } from '@/features/auth/api/auth-api'
import DesktopFinanceRecordsTab from '@/features/game/modal/desktop/desktop-finance-records-tab'
import DesktopWalletRecordsTab from '@/features/game/modal/desktop/desktop-wallet-records-tab'
@@ -47,8 +48,6 @@ const USER_INFO_TABS: Array<{
},
]
const REGISTER_INVITE_CODE_QUERY_PARAM = 'registerInviteCode'
function createRegisterInviteUrl(inviteCode: string) {
const url = new URL(window.location.href)