Files
juip-web/src/components/composites/dialogs/realNameDialog.tsx
2026-09-11 17:06:29 +08:00

233 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { zodResolver } from "@hookform/resolvers/zod"
import { useState } from "react"
import { Controller, useForm } from "react-hook-form"
import { toast } from "sonner"
import { z } from "zod"
import { submitRealName } from "@/api/agreement"
import { QrCodeImage } from "@/components/qrCodeImage"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { FieldError } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
const realNameSchema = z.object({
name: z.string().trim().min(1, "请输入真实姓名"),
idCode: z
.string()
.trim()
.regex(/^\d{15}$|^\d{18}$|^\d{17}(\d|X|x)$/, "身份证号码不合法"),
})
type RealNameValues = z.infer<typeof realNameSchema>
export default function RealNameDialog({
open,
onOpenChange,
onVerified,
mandatory = false,
}: {
open: boolean
onOpenChange: (open: boolean) => void
onVerified: () => void
/** 强制模式:不可关闭,直到完成实名认证(由外层 Gate 在认证成功后卸载) */
mandatory?: boolean
}) {
const [stage, setStage] = useState<"form" | "scan">("form")
const [qrUrl, setQrUrl] = useState("")
const [submitting, setSubmitting] = useState(false)
const form = useForm<RealNameValues>({
resolver: zodResolver(realNameSchema),
defaultValues: { name: "", idCode: "" },
})
const handleOpenChange = (next: boolean) => {
if (!next && mandatory) return
onOpenChange(next)
if (!next) {
form.reset({ name: "", idCode: "" })
setStage("form")
setQrUrl("")
}
}
const handleFinish = () => {
onVerified()
if (!mandatory) onOpenChange(false)
}
const onSubmit = async (data: RealNameValues) => {
setSubmitting(true)
const res = await submitRealName({
idCode: data.idCode,
name: data.name,
})
setSubmitting(false)
if (!res.success) {
toast.error(res.message)
return
}
const { code, url } = res.data
if (code === 20000) {
toast.success("已经认证成功")
handleFinish()
return
}
if (code === 30000) {
toast.error("该身份已经进行认证,无法再次进行认证,请联系在线客服处理")
return
}
if (code === -10000) {
toast.error("接口问题,请联系客服")
return
}
if (code === 10000 && url) {
setQrUrl(url)
setStage("scan")
return
}
toast.error("请正确填写姓名和身份证号码")
}
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent
className="max-h-[85dvh] overflow-y-auto sm:max-w-lg"
showCloseButton={!mandatory}
onEscapeKeyDown={mandatory ? e => e.preventDefault() : undefined}
onPointerDownOutside={mandatory ? e => e.preventDefault() : undefined}
>
{stage === "form" ? (
<>
<DialogHeader>
<DialogTitle className="text-xl font-semibold text-blue-600">
尊敬的用户,您还未进行实名认证!
</DialogTitle>
</DialogHeader>
<div className="max-h-56 overflow-y-auto space-y-2 rounded-lg bg-gray-50 p-4 text-xs leading-5 text-gray-600">
<p>
《中华人民共和国网络安全法》第二十四条规定:
<br />
网络运营者为用户办理网络接入、域名注册服务,办理固定电话、移动电话等入网手续,或者为用户提供信息发布、即时通讯等服务,在与用户签订协议或者确认提供服务时,应当要求用户提供真实身份信息。用户不提供真实身份信息的,网络运营者不得为其提供相关服务。
</p>
<p className="text-red-500">
1、为了你的帐号安全请勿使用他人手机号或身份信息
</p>
<p>
2、聚IP承诺:用户的个人隐私数据仅与国家公安一所信息库比对,本公司不调用及储存用户的实人认证数据,仅接收公安一所方面的实人认证结果!
</p>
<p>3、若您扫脸认证不成功,请联系客服</p>
</div>
<form
className="space-y-4"
onSubmit={form.handleSubmit(onSubmit)}
noValidate
>
<div className="space-y-1.5">
<Label className="text-sm text-gray-600">真实姓名</Label>
<Controller
name="name"
control={form.control}
render={({ field, fieldState }) => (
<>
<Input
{...field}
placeholder="请输入真实姓名"
className="h-11 bg-gray-50 border-gray-200"
/>
<FieldError>{fieldState.error?.message}</FieldError>
</>
)}
/>
</div>
<div className="space-y-1.5">
<Label className="text-sm text-gray-600">身份证号</Label>
<Controller
name="idCode"
control={form.control}
render={({ field, fieldState }) => (
<>
<Input
{...field}
placeholder="请输入身份证号码"
className="h-11 bg-gray-50 border-gray-200"
/>
<FieldError>{fieldState.error?.message}</FieldError>
</>
)}
/>
</div>
<DialogFooter className="gap-3">
{!mandatory && (
<Button
type="button"
variant="outline"
onClick={() => handleOpenChange(false)}
>
取消
</Button>
)}
<Button
type="submit"
disabled={submitting}
className="bg-linear-to-r from-blue-500 to-cyan-400 text-white hover:opacity-90"
>
{submitting ? "认证中..." : "确认"}
</Button>
</DialogFooter>
</form>
</>
) : (
<>
<DialogHeader>
<DialogTitle className="text-xl font-semibold text-blue-600">
请打开微信扫码进行认证
</DialogTitle>
</DialogHeader>
<p className="text-center text-sm text-gray-600">
若扫脸后仍未认证成功,请联系客服
</p>
<div className="flex justify-center py-2">
<div className="rounded-lg border border-gray-200 p-2">
<QrCodeImage text={qrUrl} size={240} />
</div>
</div>
<DialogFooter className="gap-3">
{!mandatory && (
<Button
variant="outline"
onClick={() => handleOpenChange(false)}
>
取消
</Button>
)}
<Button
onClick={handleFinish}
className="bg-linear-to-r from-blue-500 to-cyan-400 text-white hover:opacity-90"
>
实名扫脸结束后请点击
</Button>
</DialogFooter>
</>
)}
</DialogContent>
</Dialog>
)
}