升级依赖版本并修复构建问题
This commit is contained in:
187
src/app/(auth)/login/login-card.tsx
Normal file
187
src/app/(auth)/login/login-card.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user