升级依赖版本并修复构建问题

This commit is contained in:
2025-11-20 12:10:16 +08:00
parent fa6a4e5121
commit c02ffc9983
26 changed files with 669 additions and 649 deletions

View File

@@ -0,0 +1,187 @@
'use client'
import {Input} from '@/components/ui/input'
import {Button} from '@/components/ui/button'
import {Checkbox} from '@/components/ui/checkbox'
import {Card, CardHeader, CardContent, CardTitle} from '@/components/ui/card'
import {Form, FormField} from '@/components/ui/form'
import {Label} from '@/components/ui/label'
import {Tabs, TabsList, TabsTrigger} from '@/components/ui/tabs'
import {EyeClosedIcon, EyeIcon} from 'lucide-react'
import {useEffect, useState} from 'react'
import zod from 'zod'
import {useForm} from 'react-hook-form'
import {zodResolver} from '@hookform/resolvers/zod'
import {toast} from 'sonner'
import {useRouter} from 'next/navigation'
import {login} from '@/actions/auth'
import {useProfileStore} from '@/components/stores-provider'
import Captcha from './captcha'
const smsSchema = zod.object({
username: zod.string().length(11, '请输入正确的手机号码'),
password: zod.string().length(6, '请输入正确的验证码'),
remember: zod.boolean(),
})
const pwdSchema = zod.object({
username: zod.string(),
password: zod.string().min(6, '请输入至少6位密码'),
remember: zod.boolean(),
})
export type LoginSchema = zod.infer<typeof smsSchema | typeof pwdSchema>
export default function LoginCard(props: {
defaultMode?: 'phone_code' | 'password'
redirect?: string
}) {
const router = useRouter()
const refreshProfile = useProfileStore(store => store.refreshProfile)
const [mode, setMode] = useState(props.defaultMode || 'phone_code')
const [submitting, setSubmitting] = useState(false)
const form = useForm<LoginSchema>({
resolver: zodResolver(mode === 'phone_code' ? smsSchema : pwdSchema),
defaultValues: {
username: '',
password: '',
remember: false,
},
})
const handler = form.handleSubmit(async (data) => {
setSubmitting(true)
try {
const result = await login({...data, mode})
if (!result.success) {
throw new Error(result.message || '请检查账号和密码/验证码是否正确')
}
// 登录成功
await refreshProfile()
router.push(props.redirect || '/')
toast.success('登录成功', {
description: '欢迎回来!',
})
}
catch (e) {
toast.error('登录失败', {
description: (e as Error).message,
})
}
finally {
setSubmitting(false)
}
})
const [showPwd, setShowPwd] = useState(false)
return (
<Card className="w-96 mx-4 shadow-lg relative z-20">
<CardHeader className="text-center">
<CardTitle className="text-2xl">/</CardTitle>
</CardHeader>
<CardContent className="px-8">
{/* 登录方式切换 */}
<Tabs
value={mode}
onValueChange={(val) => {
setMode(val as typeof mode)
form.reset({username: form.getValues('username'), password: '', remember: false})
}}
className="mb-6">
<TabsList className="w-full h-10 flex justify-center gap-2">
<TabsTrigger value="password" className="flex-1">
</TabsTrigger>
<TabsTrigger value="phone_code" className="flex-1">
</TabsTrigger>
</TabsList>
</Tabs>
<Form<LoginSchema> className="space-y-6" form={form} handler={handler}>
<FormField name="username" label={mode === 'phone_code' ? '手机号' : '用户名'}>
{({id, field}) => (
<Input
{...field}
id={id}
type="tel"
placeholder="请输入手机号"
autoComplete="tel-national"
/>
)}
</FormField>
<FormField name="password" label={mode === 'phone_code' ? '验证码' : '密码'}>
{({id, field}) =>
mode === 'phone_code' ? (
<div className="flex space-x-4">
<Input
{...field}
id={id}
className="h-10"
placeholder="请输入验证码"
/>
<Captcha/>
</div>
) : (
<div className="relative">
<Input
{...field}
id={id}
type={showPwd ? 'text' : 'password'}
className="h-10 pr-10"
placeholder="至少6位密码需包含字母和数字"
autoComplete="current-password"
minLength={6}
/>
<button
type="button"
tabIndex={-1}
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
onClick={() => setShowPwd(v => !v)}
aria-label={showPwd ? '隐藏密码' : '显示密码'}
>
{showPwd ? (
<EyeIcon size={20}/>
) : (
<EyeClosedIcon size={20}/>
)}
</button>
</div>
)
}
</FormField>
<FormField name="remember">
{({id, field}) => (
<div className="flex flex-row items-start space-x-2 space-y-0">
<Checkbox
id={id}
checked={field.value}
onCheckedChange={field.onChange}
/>
<div className="space-y-1 leading-none">
<Label></Label>
</div>
</div>
)}
</FormField>
<div className="flex flex-col gap-3">
<Button
className="w-full h-12 text-lg"
type="submit"
theme="gradient"
disabled={submitting}
>
{submitting ? '登录中...' : (mode === 'phone_code' ? '首次登录即注册' : '立即登录')}
</Button>
<p className="text-xs text-center text-gray-500">
<a href="#" className="text-blue-600 hover:text-blue-500"></a>
<a href="#" className="text-blue-600 hover:text-blue-500"></a>
</p>
</div>
</Form>
</CardContent>
</Card>
)
}