2025-07-08 13:52:13 +08:00
|
|
|
|
'use client'
|
2025-12-11 14:10:52 +08:00
|
|
|
|
import {useState} from 'react'
|
2025-07-08 13:52:13 +08:00
|
|
|
|
import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger} from '@/components/ui/dialog'
|
|
|
|
|
|
import {Button} from '@/components/ui/button'
|
|
|
|
|
|
import {Form, FormField} from '@/components/ui/form'
|
|
|
|
|
|
import {Input} from '@/components/ui/input'
|
2025-12-11 14:10:52 +08:00
|
|
|
|
import {useForm, useFormContext} from 'react-hook-form'
|
2025-07-08 13:52:13 +08:00
|
|
|
|
import {zodResolver} from '@hookform/resolvers/zod'
|
|
|
|
|
|
import * as z from 'zod'
|
|
|
|
|
|
import {toast} from 'sonner'
|
|
|
|
|
|
import {useRouter} from 'next/navigation'
|
|
|
|
|
|
import {updatePassword} from '@/actions/user'
|
2025-12-11 14:10:52 +08:00
|
|
|
|
import SendMsg from '@/components/send-msg'
|
|
|
|
|
|
|
|
|
|
|
|
// 表单验证规则
|
|
|
|
|
|
const schema = z.object({
|
|
|
|
|
|
phone: z.string().regex(/^1\d{10}$/, `请输入正确的手机号`),
|
|
|
|
|
|
captcha: z.string().nonempty('请输入验证码'),
|
|
|
|
|
|
code: z.string().regex(/^\d{6}$/, `请输入正确的验证码`),
|
|
|
|
|
|
password: z.string().min(6, `密码至少6位`),
|
|
|
|
|
|
confirm_password: z.string(),
|
|
|
|
|
|
}).refine(data => data.password === data.confirm_password, {
|
|
|
|
|
|
message: '两次输入的密码不一致',
|
|
|
|
|
|
path: ['confirm_password'],
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
type Schema = z.infer<typeof schema>
|
2025-07-08 13:52:13 +08:00
|
|
|
|
|
|
|
|
|
|
interface ChangePasswordDialogProps {
|
|
|
|
|
|
triggerClassName?: string
|
|
|
|
|
|
open?: boolean
|
2025-11-20 12:10:16 +08:00
|
|
|
|
defaultOpen?: boolean
|
2025-07-08 13:52:13 +08:00
|
|
|
|
onOpenChange?: (open: boolean) => void
|
|
|
|
|
|
onSuccess?: () => void
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function ChangePasswordDialog({
|
|
|
|
|
|
triggerClassName,
|
|
|
|
|
|
open,
|
2025-11-20 12:10:16 +08:00
|
|
|
|
defaultOpen,
|
2025-07-08 13:52:13 +08:00
|
|
|
|
onOpenChange,
|
|
|
|
|
|
onSuccess,
|
|
|
|
|
|
}: ChangePasswordDialogProps) {
|
2025-11-20 12:10:16 +08:00
|
|
|
|
const [internalOpen, setInternalOpen] = useState(defaultOpen || false)
|
2025-07-08 13:52:13 +08:00
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
|
|
|
|
|
|
const actualOpen = open !== undefined ? open : internalOpen
|
|
|
|
|
|
const actualOnOpenChange = onOpenChange || setInternalOpen
|
|
|
|
|
|
|
|
|
|
|
|
// 表单初始化
|
|
|
|
|
|
const form = useForm<Schema>({
|
|
|
|
|
|
resolver: zodResolver(
|
|
|
|
|
|
schema.refine(
|
2025-07-17 14:30:51 +08:00
|
|
|
|
data => /^(?=.*[a-zA-Z])(?=.*\d).{6,}$/.test(data.password),
|
2025-07-08 13:52:13 +08:00
|
|
|
|
{
|
2025-07-17 14:30:51 +08:00
|
|
|
|
message: '密码需包含字母和数字,且不少于6位',
|
2025-07-08 13:52:13 +08:00
|
|
|
|
path: ['password'],
|
|
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
defaultValues: {
|
|
|
|
|
|
phone: '',
|
|
|
|
|
|
captcha: '',
|
|
|
|
|
|
code: '',
|
|
|
|
|
|
password: '',
|
|
|
|
|
|
confirm_password: '',
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 提交处理
|
|
|
|
|
|
const handler = form.handleSubmit(async (value) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const resp = await updatePassword({
|
|
|
|
|
|
phone: value.phone,
|
|
|
|
|
|
code: value.code,
|
|
|
|
|
|
password: value.password,
|
|
|
|
|
|
})
|
|
|
|
|
|
if (!resp.success) {
|
|
|
|
|
|
throw new Error(resp.message)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
toast.success(`保存成功,请重新登录`)
|
|
|
|
|
|
onSuccess?.()
|
|
|
|
|
|
actualOnOpenChange(false)
|
|
|
|
|
|
router.replace('/login')
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (e) {
|
|
|
|
|
|
console.error(e)
|
|
|
|
|
|
toast.error(`保存失败`, {
|
|
|
|
|
|
description: e instanceof Error ? e.message : String(e),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Dialog open={actualOpen} onOpenChange={actualOnOpenChange}>
|
|
|
|
|
|
<DialogTrigger asChild>
|
|
|
|
|
|
<Button theme="outline" className={triggerClassName || 'w-24 h-9'}>修改密码</Button>
|
|
|
|
|
|
</DialogTrigger>
|
|
|
|
|
|
<DialogContent>
|
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
|
<DialogTitle>修改密码</DialogTitle>
|
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
|
<Form form={form} handler={handler} className="flex flex-col gap-4 mt-4">
|
|
|
|
|
|
{/* 手机号输入 */}
|
|
|
|
|
|
<FormField<Schema> name="phone" label="手机号" className="flex-auto">
|
|
|
|
|
|
{({field}) => (
|
|
|
|
|
|
<Input {...field} placeholder="请输入手机号" autoComplete="tel-national"/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 短信验证码 */}
|
2025-12-11 14:10:52 +08:00
|
|
|
|
<div className="flex gap-4 items-end">
|
|
|
|
|
|
<FormField<Schema> name="code" label="验证码" className="flex-auto">
|
|
|
|
|
|
{({field}) => (
|
2025-07-08 13:52:13 +08:00
|
|
|
|
<Input {...field} placeholder="请输入验证码" autoComplete="one-time-code"/>
|
2025-12-11 14:10:52 +08:00
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
<SendMsgByPhone/>
|
|
|
|
|
|
</div>
|
2025-07-08 13:52:13 +08:00
|
|
|
|
|
|
|
|
|
|
{/* 新密码 */}
|
|
|
|
|
|
<FormField<Schema> name="password" label="新密码" className="flex-auto">
|
|
|
|
|
|
{({field}) => (
|
|
|
|
|
|
<Input {...field} placeholder="请输入新密码" type="password" autoComplete="new-password"/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 确认密码 */}
|
|
|
|
|
|
<FormField<Schema> name="confirm_password" label="确认密码" className="flex-auto">
|
|
|
|
|
|
{({field}) => (
|
|
|
|
|
|
<Input {...field} placeholder="请再次输入新密码" type="password" autoComplete="new-password"/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
|
|
|
|
|
|
<DialogFooter>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
theme="outline"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
actualOnOpenChange(false)
|
|
|
|
|
|
form.reset()
|
|
|
|
|
|
}}>
|
|
|
|
|
|
关闭
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button onClick={handler}>
|
|
|
|
|
|
保存
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</DialogFooter>
|
|
|
|
|
|
</DialogContent>
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-12-11 14:10:52 +08:00
|
|
|
|
|
|
|
|
|
|
function SendMsgByPhone() {
|
|
|
|
|
|
const form = useFormContext<Schema>()
|
|
|
|
|
|
const phone = form.watch('phone')
|
|
|
|
|
|
return <SendMsg phone={phone}/>
|
|
|
|
|
|
}
|