diff --git a/app/(account)/verify/page.tsx b/app/(account)/verify/page.tsx index 469b013..4935592 100644 --- a/app/(account)/verify/page.tsx +++ b/app/(account)/verify/page.tsx @@ -3,7 +3,6 @@ import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, @@ -12,59 +11,132 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select" -import { Separator } from "@/components/ui/separator" -import { Textarea } from "@/components/ui/textarea" +import { + applyCurrentUserVerification, + getCurrentUserForLogin, + listCurrentUserVerifications, + uploadFile, +} from "@/lib/api" +import { toApiError } from "@/lib/errors" +import { notifyInfo, notifySuccess } from "@/lib/toast" import type { UserRole } from "@/lib/types" import { useAuthStore } from "@/store/auth" import { CheckCircle, Clock, ShieldCheck, Upload } from "lucide-react" -import { useEffect, useRef, useState } from "react" +import { useEffect, useState } from "react" + +type MaterialKey = "idCardFront" | "idCardBack" | "gameScreenshot" + +function MaterialUpload({ + label, + value, + uploading, + onSelect, +}: { + label: string + value: string + uploading: boolean + onSelect: (file: File) => Promise +}) { + return ( + + ) +} export default function VerifyPage() { const [verifyRole, setVerifyRole] = useState("") - const [realName, setRealName] = useState("") - const [idNumber, setIdNumber] = useState("") - const [gameProfile, setGameProfile] = useState("") + const [idCardFront, setIdCardFront] = useState("") + const [idCardBack, setIdCardBack] = useState("") + const [gameScreenshot, setGameScreenshot] = useState("") + const [uploading, setUploading] = useState(null) + const [submitting, setSubmitting] = useState(false) + const [verificationRecords, setVerificationRecords] = useState< + Awaited> + >([]) const verificationStatus = useAuthStore((state) => state.verificationStatus) const verificationReasons = useAuthStore((state) => state.verificationReasons) const verifiedRoles = useAuthStore((state) => state.verifiedRoles) - const submitVerification = useAuthStore((state) => state.submitVerification) - const approveVerification = useAuthStore((state) => state.approveVerification) - const timersRef = useRef>>(new Map()) + const login = useAuthStore((state) => state.login) - useEffect( - () => () => { - timersRef.current.forEach((timer) => { - clearTimeout(timer) + useEffect(() => { + let cancelled = false + + void listCurrentUserVerifications() + .then((records) => { + if (!cancelled) setVerificationRecords(records) + }) + .catch((error) => { + if (!cancelled) notifyInfo(toApiError(error).msg) }) - timersRef.current.clear() - }, - [], - ) - const buildMaterials = () => { - const materials = { - realName, - idNumber, - gameProfile, - idCardFront: "mock://idCardFront", - idCardBack: "mock://idCardBack", - gameScreenshot: "mock://gameScreenshot", - } satisfies Record + return () => { + cancelled = true + } + }, []) - return materials + const uploadMaterial = async (key: MaterialKey, file: File) => { + setUploading(key) + try { + const url = await uploadFile(file, "verification") + if (key === "idCardFront") setIdCardFront(url) + if (key === "idCardBack") setIdCardBack(url) + if (key === "gameScreenshot") setGameScreenshot(url) + notifySuccess("材料已上传") + } catch (error) { + notifyInfo(toApiError(error).msg) + } finally { + setUploading(null) + } } - const submitWithMockApproval = (role: UserRole) => { - submitVerification(role, buildMaterials()) - const oldTimer = timersRef.current.get(role) - if (oldTimer) { - clearTimeout(oldTimer) + const submitVerification = async (role: UserRole) => { + if (!idCardFront || !idCardBack) { + notifyInfo("请先上传身份证正反面") + return + } + + setSubmitting(true) + try { + await applyCurrentUserVerification({ + role, + materials: { + idCardFront, + idCardBack, + gameScreenshots: gameScreenshot ? [gameScreenshot] : undefined, + }, + }) + const [records, updated] = await Promise.all([ + listCurrentUserVerifications(), + getCurrentUserForLogin(), + ]) + setVerificationRecords(records) + login(updated, updated.verifiedRoles ?? [updated.role]) + notifySuccess("认证申请已提交") + } catch (error) { + notifyInfo(toApiError(error).msg) + } finally { + setSubmitting(false) } - const timer = setTimeout(() => { - approveVerification(role) - timersRef.current.delete(role) - }, 3000) - timersRef.current.set(role, timer) } const roleMeta = [ @@ -72,6 +144,16 @@ export default function VerifyPage() { { role: "owner", label: "店主认证" }, ] as const + const statusFor = (role: UserRole) => { + const record = verificationRecords.find((item) => item.role === role) + return record?.status ?? verificationStatus[role] + } + + const reasonFor = (role: UserRole) => { + const record = verificationRecords.find((item) => item.role === role) + return record?.rejectReason ?? verificationReasons[role] + } + return (

身份认证

@@ -98,8 +180,8 @@ export default function VerifyPage() { {roleMeta.map((item) => { - const status = verificationStatus[item.role] - const reason = verificationReasons[item.role] + const status = statusFor(item.role) + const reason = reasonFor(item.role) return ( @@ -131,12 +213,8 @@ export default function VerifyPage() { )} {status === "rejected" && ( - )} @@ -163,72 +241,53 @@ export default function VerifyPage() {
-
- - setRealName(event.target.value)} - /> -
- -
- - setIdNumber(event.target.value)} - /> -
- - - -
- -