2025-04-16 09:43:12 +08:00
|
|
|
|
'use client'
|
2025-04-08 11:21:58 +08:00
|
|
|
|
import {Button} from '@/components/ui/button'
|
|
|
|
|
|
import Image from 'next/image'
|
2025-04-11 17:34:42 +08:00
|
|
|
|
import Page from '@/components/page'
|
2025-04-16 09:43:12 +08:00
|
|
|
|
import {Dialog, DialogContent, DialogFooter, DialogTitle, DialogTrigger} from '@/components/ui/dialog'
|
|
|
|
|
|
import {Form, FormField} from '@/components/ui/form'
|
|
|
|
|
|
import {useForm} from 'react-hook-form'
|
|
|
|
|
|
import zod from 'zod'
|
|
|
|
|
|
import {zodResolver} from '@hookform/resolvers/zod'
|
2025-04-26 14:18:08 +08:00
|
|
|
|
import {Identify} from '@/actions/user'
|
2025-04-16 09:43:12 +08:00
|
|
|
|
import {toast} from 'sonner'
|
2025-12-11 14:10:52 +08:00
|
|
|
|
import {ReactNode, Suspense, use, useEffect, useRef, useState} from 'react'
|
2025-04-16 09:43:12 +08:00
|
|
|
|
import * as qrcode from 'qrcode'
|
2025-12-11 14:10:52 +08:00
|
|
|
|
import {useProfileStore} from '@/components/stores/profile'
|
2025-04-28 18:57:06 +08:00
|
|
|
|
import {merge} from '@/lib/utils'
|
|
|
|
|
|
import banner from './_assets/banner.webp'
|
|
|
|
|
|
import personal from './_assets/personal.webp'
|
|
|
|
|
|
import step1 from './_assets/step1.webp'
|
|
|
|
|
|
import step2 from './_assets/step2.webp'
|
|
|
|
|
|
import step3 from './_assets/step3.webp'
|
2025-11-21 14:16:39 +08:00
|
|
|
|
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card'
|
|
|
|
|
|
import {CheckCircleIcon, WorkflowIcon} from 'lucide-react'
|
|
|
|
|
|
|
|
|
|
|
|
const schema = zod.object({
|
|
|
|
|
|
type: zod.enum([`personal`, `enterprise`], {errorMap: () => ({message: `请选择认证类型`})}).default('personal'),
|
|
|
|
|
|
name: zod.string().min(2, {message: `姓名至少2个字符`}),
|
|
|
|
|
|
iden_no: zod.string().length(18, {message: `身份证号码必须为18位`}),
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
type Schema = zod.infer<typeof schema>
|
2025-04-08 11:21:58 +08:00
|
|
|
|
|
|
|
|
|
|
export type IdentifyPageProps = {}
|
|
|
|
|
|
|
2025-04-16 09:43:12 +08:00
|
|
|
|
export default function IdentifyPage(props: IdentifyPageProps) {
|
|
|
|
|
|
// ======================
|
|
|
|
|
|
// 填写信息
|
|
|
|
|
|
// ======================
|
|
|
|
|
|
|
|
|
|
|
|
const form = useForm<Schema>({
|
|
|
|
|
|
resolver: zodResolver(schema),
|
|
|
|
|
|
defaultValues: {
|
|
|
|
|
|
type: `personal`,
|
|
|
|
|
|
name: ``,
|
|
|
|
|
|
iden_no: ``,
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const handler = form.handleSubmit(
|
|
|
|
|
|
async (value) => {
|
|
|
|
|
|
const resp = await Identify({
|
|
|
|
|
|
type: value.type === `personal` ? 1 : 2,
|
|
|
|
|
|
name: value.name,
|
|
|
|
|
|
iden_no: value.iden_no,
|
|
|
|
|
|
})
|
|
|
|
|
|
if (resp.success) {
|
|
|
|
|
|
form.reset()
|
|
|
|
|
|
if (!resp.data?.identified) {
|
|
|
|
|
|
setStep('scan')
|
|
|
|
|
|
setTarget(resp.data.target)
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
toast.success('认证已完成')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
2025-06-30 16:08:08 +08:00
|
|
|
|
toast.error(resp.message || `认证失败:请稍后重试`)
|
2025-04-16 09:43:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// ======================
|
|
|
|
|
|
// 扫码认证
|
|
|
|
|
|
// ======================
|
|
|
|
|
|
|
|
|
|
|
|
const [step, setStep] = useState<'form' | 'scan'>('form')
|
|
|
|
|
|
const [target, setTarget] = useState('')
|
|
|
|
|
|
const [openDialog, setOpenDialog] = useState(false)
|
|
|
|
|
|
const canvas = useRef<HTMLCanvasElement>(null)
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (canvas.current && target) {
|
|
|
|
|
|
qrcode.toCanvas(canvas.current, target, {
|
|
|
|
|
|
width: 256,
|
|
|
|
|
|
margin: 0,
|
|
|
|
|
|
}, (error) => {
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
console.error(error)
|
|
|
|
|
|
toast.error(`二维码生成失败:请稍后重试`)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [target])
|
|
|
|
|
|
|
|
|
|
|
|
// ======================
|
|
|
|
|
|
// 用户数据
|
|
|
|
|
|
// ======================
|
|
|
|
|
|
|
2025-04-23 19:00:53 +08:00
|
|
|
|
const profile = useProfileStore(store => store.profile)
|
|
|
|
|
|
const refreshProfile = useProfileStore(store => store.refreshProfile)
|
2025-04-16 09:43:12 +08:00
|
|
|
|
|
2026-03-13 18:26:23 +08:00
|
|
|
|
// 重置认证流程
|
|
|
|
|
|
const handleDialogOpenChange = async (open: boolean) => {
|
|
|
|
|
|
setOpenDialog(open)
|
|
|
|
|
|
if (!open) {
|
|
|
|
|
|
setStep('form')
|
|
|
|
|
|
setTarget('')
|
|
|
|
|
|
await refreshProfile()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-16 09:43:12 +08:00
|
|
|
|
// ======================
|
|
|
|
|
|
// render
|
|
|
|
|
|
// ======================
|
|
|
|
|
|
|
2025-04-08 11:21:58 +08:00
|
|
|
|
return (
|
2025-06-10 19:09:19 +08:00
|
|
|
|
<Page className="flex-row max-md:flex-col">
|
2025-11-18 19:16:24 +08:00
|
|
|
|
<div className="flex-3/4 flex flex-col bg-white rounded-lg gap-16 max-md:gap-0">
|
2025-04-08 11:21:58 +08:00
|
|
|
|
|
|
|
|
|
|
{/* banner */}
|
2025-06-10 19:09:19 +08:00
|
|
|
|
<section className="flex-none basis-40 relative flex flex-col gap-4 pl-8 pr-4 justify-center">
|
|
|
|
|
|
<Image src={banner} alt="背景图" className="absolute inset-0 w-full h-full object-cover"/>
|
2025-06-07 11:49:57 +08:00
|
|
|
|
<h3 className="text-lg font-bold z-10 relative">蓝狐HTTP邀请您参与【先测后买】服务</h3>
|
|
|
|
|
|
<p className="text-sm text-gray-600 z-10 relative">为了保障您的账户安全,请先完成实名认证,即可获取福利套餐测试资格</p>
|
2025-04-08 11:21:58 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
|
2025-06-11 10:10:32 +08:00
|
|
|
|
<div className="flex-auto flex justify-center items-start max-md:m-4">
|
2025-04-08 11:21:58 +08:00
|
|
|
|
{/* 个人认证 */}
|
2025-06-07 11:49:57 +08:00
|
|
|
|
<section className="w-96 bg-gray-50 p-8 rounded-md flex flex-col gap-4 items-center">
|
|
|
|
|
|
<Image src={personal} alt="个人认证"/>
|
2025-04-08 11:21:58 +08:00
|
|
|
|
<div>
|
2025-06-07 11:49:57 +08:00
|
|
|
|
<h3 className="text-center text-lg font-bold">个人认证</h3>
|
|
|
|
|
|
<p className="text-sm text-gray-600">
|
2025-04-30 10:42:47 +08:00
|
|
|
|
平台不会收集您的个人信息,您的信息仅用于账户安全认证
|
|
|
|
|
|
</p>
|
2025-04-08 11:21:58 +08:00
|
|
|
|
</div>
|
2025-12-11 14:10:52 +08:00
|
|
|
|
<Suspense>
|
|
|
|
|
|
<IfNotIdentofy>
|
2026-03-13 18:26:23 +08:00
|
|
|
|
<Dialog open={openDialog} onOpenChange={handleDialogOpenChange}>
|
2025-12-11 14:10:52 +08:00
|
|
|
|
<DialogTrigger asChild>
|
|
|
|
|
|
<Button className="w-full">去认证</Button>
|
|
|
|
|
|
</DialogTrigger>
|
|
|
|
|
|
<DialogContent>
|
|
|
|
|
|
<DialogTitle>
|
|
|
|
|
|
{step === 'form' ? `实名认证` : `扫码完成认证`}
|
|
|
|
|
|
</DialogTitle>
|
|
|
|
|
|
{step === 'form' && (
|
|
|
|
|
|
<Form form={form} handler={handler} className="flex flex-col gap-4">
|
|
|
|
|
|
<FormField<Schema> name="name" label="姓名">
|
|
|
|
|
|
{({id, field}) => (
|
|
|
|
|
|
<input
|
|
|
|
|
|
{...field}
|
|
|
|
|
|
id={id}
|
|
|
|
|
|
placeholder="请输入姓名"
|
|
|
|
|
|
className="border rounded p-2 w-full"
|
|
|
|
|
|
autoComplete="name"
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
<FormField<Schema> name="iden_no" label="身份证号">
|
|
|
|
|
|
{({id, field}) => (
|
|
|
|
|
|
<input
|
|
|
|
|
|
{...field}
|
|
|
|
|
|
id={id}
|
|
|
|
|
|
placeholder="请输入身份证号"
|
|
|
|
|
|
className="border rounded p-2 w-full"
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
<DialogFooter>
|
|
|
|
|
|
<Button type="submit" className="w-full mt-4">提交</Button>
|
|
|
|
|
|
</DialogFooter>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{step === 'scan' && (
|
|
|
|
|
|
<div className="flex flex-col gap-4 items-center">
|
|
|
|
|
|
<canvas ref={canvas} width={256} height={256}/>
|
|
|
|
|
|
<p className="text-sm text-gray-600">请扫码完成认证</p>
|
2026-03-13 18:26:23 +08:00
|
|
|
|
<Button onClick={() => handleDialogOpenChange(false)}>
|
2026-03-14 18:00:27 +08:00
|
|
|
|
已完成扫脸认证
|
2025-12-11 14:10:52 +08:00
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</DialogContent>
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
</IfNotIdentofy>
|
|
|
|
|
|
</Suspense>
|
2025-04-08 11:21:58 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-06-07 11:49:57 +08:00
|
|
|
|
<Card className="flex-none basis-80">
|
2025-04-29 18:47:36 +08:00
|
|
|
|
<CardHeader>
|
|
|
|
|
|
<CardTitle>
|
2025-06-07 11:49:57 +08:00
|
|
|
|
<WorkflowIcon size={18}/>
|
|
|
|
|
|
操作引导
|
2025-04-29 18:47:36 +08:00
|
|
|
|
</CardTitle>
|
|
|
|
|
|
</CardHeader>
|
2025-06-07 11:49:57 +08:00
|
|
|
|
<CardContent className="flex flex-col px-4">
|
|
|
|
|
|
<p className="text-sm text-weak mb-4">
|
2025-04-29 18:47:36 +08:00
|
|
|
|
为响应国家相关规定,使用HTTP代理需完成实名认证。认证服务由支付宝提供,您的个人信息将受到严格保护,仅用于账户安全认证
|
|
|
|
|
|
</p>
|
2025-06-07 11:49:57 +08:00
|
|
|
|
<p className="flex gap-2 items-center justify-between w-56 self-center">
|
|
|
|
|
|
<span className="flex gap-2">
|
|
|
|
|
|
<span className="bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center">01</span>
|
|
|
|
|
|
<span>注册账号</span>
|
|
|
|
|
|
</span>
|
2025-06-10 19:09:19 +08:00
|
|
|
|
<Image alt="步骤配图" src={step1}/>
|
2025-04-28 18:57:06 +08:00
|
|
|
|
</p>
|
2025-06-07 11:49:57 +08:00
|
|
|
|
<div className="h-16 w-56 px-4 flex self-center">
|
2025-04-28 18:57:06 +08:00
|
|
|
|
<div className={merge(
|
|
|
|
|
|
`w-0 h-full border-x border-primary border-dashed relative`,
|
2025-11-21 15:35:26 +08:00
|
|
|
|
`after:absolute after:-left-[3px] after:bottom-0 after:w-1.5 after:h-1.5 after:rounded-full after:bg-primary`,
|
2025-06-07 11:49:57 +08:00
|
|
|
|
)}>
|
|
|
|
|
|
</div>
|
2025-04-28 18:57:06 +08:00
|
|
|
|
</div>
|
2025-06-07 11:49:57 +08:00
|
|
|
|
<p className="flex gap-2 items-center justify-between w-56 self-center">
|
|
|
|
|
|
<span className="flex gap-2">
|
|
|
|
|
|
<span className="bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center">02</span>
|
|
|
|
|
|
<span>实名认证</span>
|
|
|
|
|
|
</span>
|
2025-06-10 19:09:19 +08:00
|
|
|
|
<Image alt="步骤配图" src={step2}/>
|
2025-04-28 18:57:06 +08:00
|
|
|
|
</p>
|
2025-06-07 11:49:57 +08:00
|
|
|
|
<div className="h-16 w-56 px-4 flex self-center">
|
2025-04-28 18:57:06 +08:00
|
|
|
|
<div className={merge(
|
|
|
|
|
|
`w-0 h-full border-x border-primary border-dashed relative`,
|
2025-11-21 15:35:26 +08:00
|
|
|
|
`after:absolute after:-left-[3px] after:bottom-0 after:w-1.5 after:h-1.5 after:rounded-full after:bg-primary`,
|
2025-06-07 11:49:57 +08:00
|
|
|
|
)}>
|
|
|
|
|
|
</div>
|
2025-04-28 18:57:06 +08:00
|
|
|
|
</div>
|
2025-06-07 11:49:57 +08:00
|
|
|
|
<p className="flex gap-2 items-center justify-between w-56 self-center">
|
|
|
|
|
|
<span className="flex gap-2">
|
|
|
|
|
|
<span className="bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center">03</span>
|
2026-03-13 18:26:23 +08:00
|
|
|
|
<span>支付订单</span>
|
2025-06-07 11:49:57 +08:00
|
|
|
|
</span>
|
2025-06-10 19:09:19 +08:00
|
|
|
|
<Image alt="步骤配图" src={step3}/>
|
2025-04-28 18:57:06 +08:00
|
|
|
|
</p>
|
2025-04-29 18:47:36 +08:00
|
|
|
|
</CardContent>
|
|
|
|
|
|
</Card>
|
2025-04-11 17:34:42 +08:00
|
|
|
|
</Page>
|
2025-04-08 11:21:58 +08:00
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-12-11 14:10:52 +08:00
|
|
|
|
|
|
|
|
|
|
function IfNotIdentofy(props: {children: ReactNode}) {
|
|
|
|
|
|
const profile = use(useProfileStore(store => store.profile))
|
|
|
|
|
|
return !profile?.id_token
|
|
|
|
|
|
? props.children
|
|
|
|
|
|
: (
|
|
|
|
|
|
<p className="flex gap-2 items-center">
|
|
|
|
|
|
<CheckCircleIcon className="text-done"/>
|
|
|
|
|
|
<span>已完成实名认证</span>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|