refactor: remove demo timers and client-side timeout simulation

Remove lib/config/demo-timers.ts and all usages across stores
and pages. Order timeout scheduling, dispute auto-progression,
and hardcoded countdown displays are removed — timeouts are
now handled server-side by the backend.
This commit is contained in:
zetaloop
2026-05-01 04:10:03 +08:00
parent 0a1a4c877b
commit 452004b194
5 changed files with 58 additions and 213 deletions
+57 -31
View File
@@ -7,14 +7,13 @@ import { Label } from "@/components/ui/label"
import { Separator } from "@/components/ui/separator"
import { StatusBadge, type StatusBadgeProps } from "@/components/ui/status-badge"
import { Textarea } from "@/components/ui/textarea"
import { getOrderById } from "@/lib/api"
import { getOrderById, getPlayerById, uploadFile } from "@/lib/api"
import {
getDisputeByOrderId,
submitDispute,
submitDisputeAppeal,
submitDisputeResponse,
} from "@/lib/api/disputes"
import { DISPUTE_TO_RESOLVED_MS } from "@/lib/config/demo-timers"
import { notifyInfo } from "@/lib/toast"
import { useAuthStore } from "@/store/auth"
import { AlertTriangle, ArrowLeft, Clock, FileText, Upload, X } from "lucide-react"
@@ -81,12 +80,14 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
const [existingDispute, setExistingDispute] = useState<Awaited<
ReturnType<typeof getDisputeByOrderId>
> | null>(null)
const [playerUserId, setPlayerUserId] = useState<string | null>(null)
const [reason, setReason] = useState("")
const [files, setFiles] = useState<string[]>([])
const [responseReason, setResponseReason] = useState("")
const [responseFiles, setResponseFiles] = useState<string[]>([])
const [appealReason, setAppealReason] = useState("")
const [uploading, setUploading] = useState(false)
const fileInputRef = useRef<HTMLInputElement>(null)
const responseFileInputRef = useRef<HTMLInputElement>(null)
@@ -100,6 +101,7 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
setLoading(true)
setOrder(null)
setExistingDispute(null)
setPlayerUserId(null)
}
const load = async () => {
@@ -113,6 +115,13 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
if (cancelled) return
setOrder(nextOrder ?? null)
setExistingDispute(nextDispute ?? null)
if (nextOrder) {
const player = await getPlayerById(String(nextOrder.playerId))
if (cancelled) return
setPlayerUserId(player?.user.id ?? null)
}
setLoading(false)
}
@@ -133,34 +142,36 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
useEffect(
() => () => {
filesRef.current.forEach((url) => {
URL.revokeObjectURL(url)
})
responseFilesRef.current.forEach((url) => {
URL.revokeObjectURL(url)
})
filesRef.current = []
responseFilesRef.current = []
},
[],
)
const handleFileSelect = (
const handleFileSelect = async (
event: ChangeEvent<HTMLInputElement>,
setter: Dispatch<SetStateAction<string[]>>,
currentFiles: string[],
) => {
const selectedFiles = event.target.files
if (!selectedFiles?.length) return
setter((prev) => {
const remaining = 5 - prev.length
if (remaining <= 0) return prev
const nextUrls = Array.from(selectedFiles)
.slice(0, remaining)
.map((file) => URL.createObjectURL(file))
return [...prev, ...nextUrls]
})
event.target.value = ""
setUploading(true)
try {
const remaining = 5 - currentFiles.length
if (remaining <= 0) return
const nextUrls = await Promise.all(
Array.from(selectedFiles)
.slice(0, remaining)
.map((file) => uploadFile(file, "dispute")),
)
setter((prev) => [...prev, ...nextUrls])
} catch {
notifyInfo("证据上传失败")
} finally {
setUploading(false)
event.target.value = ""
}
}
const removeFile = (
@@ -168,13 +179,14 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
filesState: string[],
setter: Dispatch<SetStateAction<string[]>>,
) => {
const removed = filesState[index]
if (removed) {
URL.revokeObjectURL(removed)
}
setter((prev) => prev.filter((_, currentIndex) => currentIndex !== index))
}
const reloadDispute = async () => {
const nextDispute = await getDisputeByOrderId(id)
setExistingDispute(nextDispute ?? null)
}
const handleSubmit = () => {
if (!userId || !reason.trim()) return
@@ -212,7 +224,7 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
}
const isParticipant = Boolean(
userId && (order.consumerId === userId || order.playerId === userId),
userId && (String(order.consumerId) === userId || playerUserId === userId),
)
if (!isParticipant) {
return (
@@ -337,12 +349,15 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
accept="image/*"
multiple
className="hidden"
onChange={(event) => handleFileSelect(event, setResponseFiles)}
onChange={(event) => {
void handleFileSelect(event, setResponseFiles, responseFiles)
}}
/>
<Button
variant="outline"
className="border-border/60"
onClick={() => responseFileInputRef.current?.click()}
disabled={uploading}
>
<Upload className="mr-1 h-4 w-4" />
@@ -385,7 +400,11 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
).then((decision) => {
if (!decision.ok) {
notifyInfo(decision.error.msg)
return
}
setResponseReason("")
setResponseFiles([])
return reloadDispute()
})
}}
disabled={!responseReason.trim()}
@@ -437,7 +456,10 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
).then((decision) => {
if (!decision.ok) {
notifyInfo(decision.error.msg)
return
}
setAppealReason("")
return reloadDispute()
})
}}
disabled={!appealReason.trim()}
@@ -477,7 +499,7 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
<div className="container mx-auto max-w-2xl px-4 py-8">
<EmptyState
title="争议已提交,请等待平台处理"
description={`平台将在约 ${Math.floor(DISPUTE_TO_RESOLVED_MS / 1000)} 秒内给出模拟处理结果。`}
description="争议已提交,请等待平台处理"
icon={AlertTriangle}
action={
<Button variant="outline" className="border-border/60" asChild>
@@ -527,13 +549,15 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
accept="image/*"
multiple
className="hidden"
onChange={(event) => handleFileSelect(event, setFiles)}
onChange={(event) => {
void handleFileSelect(event, setFiles, files)
}}
/>
<button
type="button"
className="w-full rounded-md border-2 border-dashed border-border/60 bg-muted/20 p-6 text-center text-sm text-muted-foreground transition-colors hover:border-primary/60 hover:bg-muted/30 hover:text-foreground disabled:opacity-50"
onClick={() => fileInputRef.current?.click()}
disabled={files.length >= 5}
disabled={files.length >= 5 || uploading}
>
<div className="flex flex-col items-center gap-2">
<Upload className="h-5 w-5" />
@@ -566,13 +590,15 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
))}
</div>
)}
<div className="text-xs text-muted-foreground">5</div>
<div className="text-xs text-muted-foreground">
{uploading ? "证据上传中..." : "最多5张"}
</div>
</div>
<div className="space-y-1 rounded-lg border border-border/60 bg-muted/20 p-3 text-xs text-muted-foreground">
<p>· </p>
<p>· </p>
<p>· {Math.floor(DISPUTE_TO_RESOLVED_MS / 1000)} </p>
<p>· </p>
<p>· </p>
</div>