feat(auth): 集成认证授权功能并优化API客户端

- 实现了完整的登录注册认证流程,包括密码验证和用户资料获取
- 集成了JWT令牌管理和自动刷新机制,支持设备ID生成和管理
- 添加了WebSocket连接配置和API基础URL环境变量设置
- 实现了API客户端的请求拦截器,包括令牌验证和错误处理逻辑
- 集成了MD5加密和认证令牌缓存机制,提升安全性
- 添加了多语言国际化支持,包括英语、中文、马来语和印尼语
- 实现了认证状态管理和本地存储持久化功能
- 添加了表单验证schema和错误处理机制,增强用户体验
This commit is contained in:
JiaJun
2026-05-16 09:03:55 +08:00
parent 6aaf90a6ac
commit 5dd4e31db4
81 changed files with 6086 additions and 627 deletions

View File

@@ -1,5 +1,6 @@
import { type ReactNode, useEffect } from 'react'
import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next'
import modalBg from '@/assets/system/modal-bg.webp'
import modalClose from '@/assets/system/modal-close.webp'
import modalNormalBg from '@/assets/system/modal-normal-bg.png'
@@ -29,6 +30,7 @@ export function CenterModal({
children,
className,
}: CenterModalProps) {
const { t } = useTranslation()
const handleClose = () => {
onClose?.()
}
@@ -63,7 +65,11 @@ export function CenterModal({
<SmartBackground
role="dialog"
aria-modal="true"
aria-label={typeof title === 'string' ? title : 'Modal'}
aria-label={
typeof title === 'string'
? title
: t('commonUi.modal.defaultAriaLabel')
}
className={cn(
'relative flex h-design-640 w-design-720 flex-col overflow-hidden rounded-[calc(var(--design-unit)*28)] px-design-20 text-white',
className,
@@ -93,7 +99,7 @@ export function CenterModal({
{isShowClose && onClose ? (
<button
type="button"
aria-label="Close modal"
aria-label={t('commonUi.modal.close')}
onClick={handleClose}
className={cn(
'absolute top-1/2 inline-flex h-design-60 w-design-60 -translate-y-1/2 items-center justify-center rounded-full transition hover:scale-105 active:scale-95',

View File

@@ -1,4 +1,8 @@
import type { AppLanguage } from '@/i18n'
import { type AppLanguage, supportedLanguages } from '@/i18n'
const languagePrefixPattern = new RegExp(
`^/(${supportedLanguages.join('|')})(?=/|$)`,
)
interface LanguageLinkProps {
currentPathname: string
@@ -14,7 +18,7 @@ export function LanguageLink({
language,
}: LanguageLinkProps) {
const nextPathname = currentPathname.replace(
/^\/(zh-CN|en-US)(?=\/|$)/,
languagePrefixPattern,
`/${language}`,
)

View File

@@ -0,0 +1,71 @@
import {
CheckCircle2,
Info,
LoaderCircle,
TriangleAlert,
X,
XCircle,
} from 'lucide-react'
import { notify, useNotificationStore } from '@/lib/notify'
import { cn } from '@/lib/utils'
const TOAST_ICON_BY_TYPE = {
error: <XCircle className="h-4 w-4 shrink-0 text-[#FF8A9E]" />,
info: <Info className="h-4 w-4 shrink-0 text-[#7CE8FF]" />,
loading: (
<LoaderCircle className="h-4 w-4 shrink-0 animate-spin text-[#7CE8FF]" />
),
success: <CheckCircle2 className="h-4 w-4 shrink-0 text-[#7CF0B8]" />,
warning: <TriangleAlert className="h-4 w-4 shrink-0 text-[#FFD66E]" />,
} as const
const TOAST_TONE_CLASS_BY_TYPE = {
error: 'game-toast-error',
info: 'game-toast-info',
loading: 'game-toast-loading',
success: 'game-toast-success',
warning: 'game-toast-warning',
} as const
export function AppToaster() {
const toasts = useNotificationStore((state) => state.toasts)
return (
<div
aria-atomic="true"
aria-live="polite"
className="game-toaster pointer-events-none fixed top-[calc(var(--design-unit)*88)] left-1/2 z-[9999] flex w-full -translate-x-1/2 flex-col items-center gap-3 px-4 md:top-[calc(var(--design-unit)*88)]"
>
{toasts.map((toast) => (
<div
key={toast.id}
role="status"
className={cn(
'game-toast pointer-events-auto',
TOAST_TONE_CLASS_BY_TYPE[toast.type],
)}
>
<span aria-hidden="true" className="game-toast-icon">
{TOAST_ICON_BY_TYPE[toast.type]}
</span>
<div className="game-toast-content">
<div className="game-toast-title">{toast.message}</div>
{toast.description ? (
<div className="game-toast-description">{toast.description}</div>
) : null}
</div>
<button
type="button"
aria-label="Close notification"
onClick={() => notify.dismiss(toast.id)}
className="game-toast-close"
>
<X className="h-3.5 w-3.5 text-[#D5FBFF]" />
</button>
</div>
))}
</div>
)
}