233 lines
7.6 KiB
TypeScript
233 lines
7.6 KiB
TypeScript
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>
|
||
)
|
||
}
|