feat(auth): complete verification state machine with resubmit flow

This commit is contained in:
zetaloop
2026-02-22 08:16:14 +08:00
parent 76df8a6f56
commit 33b7e4d0b9
2 changed files with 140 additions and 24 deletions
+92 -23
View File
@@ -1,7 +1,7 @@
"use client"
import { CheckCircle, Clock, ShieldCheck, Upload } from "lucide-react"
import { useState } from "react"
import { useEffect, useRef, useState } from "react"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
@@ -22,29 +22,39 @@ import { useAuthStore } from "@/store/auth"
export default function VerifyPage() {
const [verifyRole, setVerifyRole] = useState<UserRole | "">("")
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 submitted = Object.values(verificationStatus).includes("pending")
const approveVerification = useAuthStore((state) => state.approveVerification)
const timersRef = useRef<Map<UserRole, ReturnType<typeof setTimeout>>>(new Map())
if (submitted) {
return (
<div className="max-w-2xl space-y-6">
<h1 className="text-2xl font-bold"></h1>
<Card>
<CardContent className="py-12 text-center space-y-4">
<Clock className="h-12 w-12 mx-auto text-muted-foreground" />
<h2 className="text-xl font-bold"></h2>
<p className="text-sm text-muted-foreground">
1-3
</p>
<Badge variant="outline" className="text-sm">
<Clock className="mr-1 h-3.5 w-3.5" />
</Badge>
</CardContent>
</Card>
</div>
useEffect(
() => () => {
timersRef.current.forEach((timer) => {
clearTimeout(timer)
})
timersRef.current.clear()
},
[],
)
const submitWithMockApproval = (role: UserRole) => {
submitVerification(role)
const oldTimer = timersRef.current.get(role)
if (oldTimer) {
clearTimeout(oldTimer)
}
const timer = setTimeout(() => {
approveVerification(role)
timersRef.current.delete(role)
}, 3000)
timersRef.current.set(role, timer)
}
const roleMeta: { role: UserRole; label: string }[] = [
{ role: "player", label: "打手认证" },
{ role: "owner", label: "店主认证" },
]
return (
<div className="max-w-2xl space-y-6">
@@ -66,6 +76,59 @@ export default function VerifyPage() {
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base"></CardTitle>
</CardHeader>
<CardContent className="space-y-3">
{roleMeta.map((item) => {
const status = verificationStatus[item.role]
const reason = verificationReasons[item.role]
return (
<div key={item.role} className="rounded-md border p-3 space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">{item.label}</span>
{status === "approved" || verifiedRoles.includes(item.role) ? (
<Badge
variant="outline"
className="text-green-700 border-green-200 bg-green-50"
>
</Badge>
) : status === "pending" ? (
<Badge variant="outline">
<Clock className="mr-1 h-3.5 w-3.5" />
</Badge>
) : status === "rejected" ? (
<Badge variant="outline" className="text-red-700 border-red-200 bg-red-50">
</Badge>
) : (
<Badge variant="secondary"></Badge>
)}
</div>
{status === "rejected" && (
<p className="text-xs text-muted-foreground">{reason}</p>
)}
{status === "rejected" && (
<Button
variant="outline"
size="sm"
onClick={() => submitWithMockApproval(item.role)}
>
</Button>
)}
</div>
)
})}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base"></CardTitle>
@@ -122,13 +185,19 @@ export default function VerifyPage() {
<Button
className="w-full"
disabled={!verifyRole}
disabled={
!verifyRole ||
verificationStatus[verifyRole] === "pending" ||
verificationStatus[verifyRole] === "approved"
}
onClick={() => {
if (!verifyRole) return
submitVerification(verifyRole)
submitWithMockApproval(verifyRole)
}}
>
{verifyRole && verificationStatus[verifyRole] === "rejected"
? "重新提交认证申请"
: "提交认证申请"}
</Button>
</CardContent>
</Card>
+47
View File
@@ -6,9 +6,12 @@ interface AuthState {
currentRole: UserRole
verifiedRoles: UserRole[]
verificationStatus: Partial<Record<UserRole, VerificationStatus>>
verificationReasons: Partial<Record<UserRole, string>>
user: User | null
switchRole: (role: UserRole) => void
submitVerification: (role: UserRole) => void
approveVerification: (role: UserRole) => void
rejectVerification: (role: UserRole, reason: string) => void
login: (user: User, verifiedRoles?: UserRole[]) => void
logout: () => void
}
@@ -18,6 +21,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
currentRole: "consumer",
verifiedRoles: ["consumer"],
verificationStatus: { consumer: "approved" },
verificationReasons: {},
user: null,
switchRole: (role) => {
const { verifiedRoles } = get()
@@ -31,11 +35,52 @@ export const useAuthStore = create<AuthState>((set, get) => ({
return state
}
const nextReasons = { ...state.verificationReasons }
delete nextReasons[role]
return {
verificationStatus: {
...state.verificationStatus,
[role]: "pending",
},
verificationReasons: nextReasons,
}
}),
approveVerification: (role) =>
set((state) => {
if (state.verifiedRoles.includes(role) && state.verificationStatus[role] === "approved") {
return state
}
const nextReasons = { ...state.verificationReasons }
delete nextReasons[role]
return {
verifiedRoles: state.verifiedRoles.includes(role)
? state.verifiedRoles
: [...state.verifiedRoles, role],
verificationStatus: {
...state.verificationStatus,
[role]: "approved",
},
verificationReasons: nextReasons,
}
}),
rejectVerification: (role, reason) =>
set((state) => {
if (state.verifiedRoles.includes(role)) {
return state
}
return {
verificationStatus: {
...state.verificationStatus,
[role]: "rejected",
},
verificationReasons: {
...state.verificationReasons,
[role]: reason.trim() || "认证资料不完整,请补充后重试",
},
}
}),
login: (user, verifiedRoles = ["consumer"]) =>
@@ -51,6 +96,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
},
{},
),
verificationReasons: {},
}),
logout: () =>
set({
@@ -58,6 +104,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
currentRole: "consumer",
verifiedRoles: ["consumer"],
verificationStatus: { consumer: "approved" },
verificationReasons: {},
user: null,
}),
}))