Files
2026-04-26 01:53:15 +08:00

217 lines
6.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { EmptyState } from "@/components/ui/empty-state"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { getOrderById, listReviewsByOrder } from "@/lib/api"
import { submitReview } from "@/lib/api/reviews"
import { notifyInfo, notifySuccess } from "@/lib/toast"
import { useAuthStore } from "@/store/auth"
import { ArrowLeft, Lock, Star } from "lucide-react"
import Link from "next/link"
import { use, useEffect, useState } from "react"
export default function ReviewPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = use(params)
const userId = useAuthStore((state) => state.user?.id)
const [order, setOrder] = useState<Awaited<ReturnType<typeof getOrderById>>>()
const [reviews, setReviews] = useState<Awaited<ReturnType<typeof listReviewsByOrder>>>([])
const [loading, setLoading] = useState(true)
const [rating, setRating] = useState(0)
const [hoverRating, setHoverRating] = useState(0)
const [content, setContent] = useState("")
useEffect(() => {
let cancelled = false
queueMicrotask(() => {
if (cancelled) return
setLoading(true)
})
void Promise.all([getOrderById(id), Promise.resolve(listReviewsByOrder(id))])
.then(([nextOrder, nextReviews]) => {
if (cancelled) return
setOrder(nextOrder)
setReviews(nextReviews)
})
.catch(() => {
if (cancelled) return
setOrder(undefined)
setReviews([])
})
.finally(() => {
if (cancelled) return
setLoading(false)
})
return () => {
cancelled = true
}
}, [id])
if (loading) {
return (
<div className="container mx-auto max-w-2xl px-4 py-8">
<EmptyState title="加载中" description="正在读取评价信息..." icon={Star} />
</div>
)
}
if (!order) {
return (
<div className="container mx-auto max-w-2xl px-4 py-8">
<EmptyState title="订单不存在" description="该订单可能已被删除或暂不可访问。" />
</div>
)
}
const hasSubmitted = Boolean(userId && reviews.some((review) => review.fromUserId === userId))
const isRevealed = reviews.length >= 2 && reviews.every((review) => !review.sealed)
if (order.status !== "pending_review") {
return (
<div className="container mx-auto max-w-2xl px-4 py-8">
<EmptyState
title="当前阶段不可评价"
description="仅待评价状态的订单可以提交评价。"
icon={Star}
action={
<Button variant="outline" asChild>
<Link href={`/order/${id}`}></Link>
</Button>
}
/>
</div>
)
}
if (hasSubmitted && !isRevealed) {
return (
<div className="container mx-auto max-w-2xl px-4 py-8">
<EmptyState
title="评价已提交"
description="等待对方提交评价,双方都提交后将同时揭晓。"
icon={Lock}
action={
<Button variant="outline" asChild>
<Link href={`/order/${id}`}></Link>
</Button>
}
/>
</div>
)
}
if (hasSubmitted && isRevealed) {
return (
<div className="container mx-auto max-w-2xl px-4 py-8">
<EmptyState
title="评价已揭晓"
description="双方评价已同步公开,可在订单详情查看。"
icon={Star}
action={
<Button variant="outline" asChild>
<Link href={`/order/${id}`}></Link>
</Button>
}
/>
</div>
)
}
return (
<div className="container mx-auto max-w-2xl px-4 py-8 space-y-4">
<Link
href={`/order/${id}`}
className="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground"
>
<ArrowLeft className="h-4 w-4" />
</Link>
<Card className="border-border/80 shadow-sm">
<CardHeader>
<CardTitle></CardTitle>
<p className="text-sm text-muted-foreground">{order.service.title}</p>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<Label></Label>
<div className="flex gap-1">
{[1, 2, 3, 4, 5].map((star) => (
<button
key={star}
type="button"
onClick={() => setRating(star)}
onMouseEnter={() => setHoverRating(star)}
onMouseLeave={() => setHoverRating(0)}
className="p-0.5"
>
<Star
className={`h-8 w-8 transition-colors ${
star <= (hoverRating || rating)
? "fill-warning text-warning"
: "text-muted stroke-muted-foreground"
}`}
/>
</button>
))}
</div>
</div>
<div className="space-y-2">
<Label htmlFor="review-content"></Label>
<Textarea
id="review-content"
placeholder="分享你的体验..."
value={content}
onChange={(e) => setContent(e.target.value)}
rows={4}
/>
</div>
<div className="rounded-lg border border-border/60 bg-muted/30 p-3 text-xs text-muted-foreground flex items-start gap-2">
<Lock className="h-4 w-4 shrink-0 mt-0.5" />
<span></span>
</div>
<Button
className="w-full"
disabled={rating === 0 || !userId}
onClick={() => {
if (!userId) {
notifyInfo("请先登录")
return
}
void Promise.resolve(
submitReview({
orderId: id,
rating,
content,
}),
).then((decision) => {
if (decision.ok) {
notifySuccess("评价已提交")
void Promise.resolve(listReviewsByOrder(id)).then((nextReviews) => {
setReviews(nextReviews)
})
return
}
notifyInfo(decision.error.msg)
})
}}
>
</Button>
</CardContent>
</Card>
</div>
)
}