fix(pages): adapt all pages to backend-aligned types

Replace removed fields with available data sources throughout UI:
- order pages: use service.title instead of consumer/player names
- chat: look up sender from session.participants, remove readonly
- community: simplify post cards, keep pinned icon
- post detail: keep pinned/linkedOrderId display
- shop rules: use string commissionValue
- dashboard: parse string amounts for income display
- dispute/review: remove initiator/avatar references
This commit is contained in:
zetaloop
2026-04-23 21:15:28 +08:00
parent 12284290cc
commit 4d8877f588
17 changed files with 153 additions and 238 deletions
+2 -2
View File
@@ -226,7 +226,7 @@ export default function WalletPage() {
) : filteredTransactions.length > 0 ? ( ) : filteredTransactions.length > 0 ? (
filteredTransactions.map((tx) => { filteredTransactions.map((tx) => {
const Icon = typeIcons[tx.type] const Icon = typeIcons[tx.type]
const isIncome = tx.amount > 0 const isIncome = Number(tx.amount) > 0
return ( return (
<div key={tx.id} className="flex items-center justify-between"> <div key={tx.id} className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
@@ -242,7 +242,7 @@ export default function WalletPage() {
</div> </div>
<div className="text-right"> <div className="text-right">
<p className={`text-sm font-medium ${isIncome ? "text-green-600" : ""}`}> <p className={`text-sm font-medium ${isIncome ? "text-green-600" : ""}`}>
{isIncome ? "+" : ""}¥{Math.abs(tx.amount).toFixed(2)} {isIncome ? "+" : ""}¥{Math.abs(Number(tx.amount)).toFixed(2)}
</p> </p>
<Badge variant="outline" className="text-[10px]"> <Badge variant="outline" className="text-[10px]">
{typeLabels[tx.type]} {typeLabels[tx.type]}
+1 -3
View File
@@ -141,9 +141,7 @@ export default function DashboardPage() {
<div className="flex items-center justify-between rounded-md border p-3 hover:bg-muted/50 transition-colors"> <div className="flex items-center justify-between rounded-md border p-3 hover:bg-muted/50 transition-colors">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<p className="text-sm font-medium truncate">{order.service.title}</p> <p className="text-sm font-medium truncate">{order.service.title}</p>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">{order.service.title}</p>
{order.consumerName} {order.playerName}
</p>
</div> </div>
<div className="flex items-center gap-3 shrink-0"> <div className="flex items-center gap-3 shrink-0">
<span className="text-sm font-medium">¥{order.totalPrice}</span> <span className="text-sm font-medium">¥{order.totalPrice}</span>
@@ -158,12 +158,12 @@ export default function ShopIncomePage() {
<TableRow key={transaction.id}> <TableRow key={transaction.id}>
<TableCell> <TableCell>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{transaction.amount > 0 ? ( {Number(transaction.amount) > 0 ? (
<ArrowDownLeft className="h-4 w-4 text-green-500" /> <ArrowDownLeft className="h-4 w-4 text-green-500" />
) : ( ) : (
<ArrowUpRight className="h-4 w-4 text-red-500" /> <ArrowUpRight className="h-4 w-4 text-red-500" />
)} )}
<Badge variant={transaction.amount > 0 ? "default" : "secondary"}> <Badge variant={Number(transaction.amount) > 0 ? "default" : "secondary"}>
{transaction.type === "topup" {transaction.type === "topup"
? "充值" ? "充值"
: transaction.type === "payment" : transaction.type === "payment"
@@ -178,9 +178,9 @@ export default function ShopIncomePage() {
</TableCell> </TableCell>
<TableCell>{transaction.description}</TableCell> <TableCell>{transaction.description}</TableCell>
<TableCell <TableCell
className={transaction.amount > 0 ? "text-green-600" : "text-red-600"} className={Number(transaction.amount) > 0 ? "text-green-600" : "text-red-600"}
> >
{transaction.amount > 0 ? "+" : ""} {Number(transaction.amount) > 0 ? "+" : ""}
{transaction.amount} {transaction.amount}
</TableCell> </TableCell>
<TableCell>{new Date(transaction.createdAt).toLocaleString()}</TableCell> <TableCell>{new Date(transaction.createdAt).toLocaleString()}</TableCell>
@@ -103,8 +103,8 @@ export default function ShopOrdersPage() {
{shopOrders.map((order) => ( {shopOrders.map((order) => (
<TableRow key={order.id}> <TableRow key={order.id}>
<TableCell className="font-medium">{order.service.title}</TableCell> <TableCell className="font-medium">{order.service.title}</TableCell>
<TableCell>{order.consumerName}</TableCell> <TableCell>{order.consumerId}</TableCell>
<TableCell>{order.playerName}</TableCell> <TableCell>{order.playerId}</TableCell>
<TableCell> <TableCell>
<Badge variant="outline">{statusLabels[order.status]}</Badge> <Badge variant="outline">{statusLabels[order.status]}</Badge>
</TableCell> </TableCell>
@@ -39,19 +39,19 @@ function ShopRulesForm({
shop: Shop shop: Shop
updateShop: (shopId: string, patch: Partial<Omit<Shop, "id" | "owner">>) => void updateShop: (shopId: string, patch: Partial<Omit<Shop, "id" | "owner">>) => void
}) { }) {
const [commissionType, setCommissionType] = useState<Shop["commissionType"]>(shop.commissionType)
const [commissionValue, setCommissionValue] = useState(shop.commissionValue)
const [allowMultiShop, setAllowMultiShop] = useState(shop.allowMultiShop) const [allowMultiShop, setAllowMultiShop] = useState(shop.allowMultiShop)
const [allowIndependentOrders, setAllowIndependentOrders] = useState(shop.allowIndependentOrders) const [allowIndependentOrders, setAllowIndependentOrders] = useState(shop.allowIndependentOrders)
const [dispatchMode, setDispatchMode] = useState<Shop["dispatchMode"]>(shop.dispatchMode) const [dispatchMode, setDispatchMode] = useState<Shop["dispatchMode"]>(shop.dispatchMode)
const [commissionType, setCommissionType] = useState<Shop["commissionType"]>(shop.commissionType)
const [commissionValue, setCommissionValue] = useState(shop.commissionValue.toString())
const handleSave = () => { const handleSave = () => {
updateShop(shop.id, { updateShop(shop.id, {
commissionType,
commissionValue,
allowMultiShop, allowMultiShop,
allowIndependentOrders, allowIndependentOrders,
dispatchMode, dispatchMode,
commissionType,
commissionValue: Number(commissionValue),
}) })
} }
+9 -47
View File
@@ -4,17 +4,15 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card" import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"
import { listGames, listOrders, listPlayers, listPosts } from "@/lib/api" import { listGames, listPosts } from "@/lib/api"
import { roleLabels } from "@/lib/constants" import { roleLabels } from "@/lib/constants"
import type { Game, Player, Post } from "@/lib/types" import type { Game, Post } from "@/lib/types"
import { ClipboardList, Heart, MessageCircle, PenSquare, Pin } from "lucide-react" import { Heart, MessageCircle, PenSquare, Pin } from "lucide-react"
import Link from "next/link" import Link from "next/link"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
export default function CommunityPage() { export default function CommunityPage() {
const [games, setGames] = useState<Game[]>([]) const [games, setGames] = useState<Game[]>([])
const [players, setPlayers] = useState<Player[]>([])
const [orders, setOrders] = useState<Awaited<ReturnType<typeof listOrders>>>([])
const [posts, setPosts] = useState<Post[]>([]) const [posts, setPosts] = useState<Post[]>([])
const [postsLoading, setPostsLoading] = useState(true) const [postsLoading, setPostsLoading] = useState(true)
@@ -24,20 +22,16 @@ export default function CommunityPage() {
useEffect(() => { useEffect(() => {
let cancelled = false let cancelled = false
Promise.all([listGames(), listPlayers(), Promise.resolve(listOrders()), listPosts()]) Promise.all([listGames(), listPosts()])
.then(([gamesItems, playersItems, ordersItems, postsItems]) => { .then(([gamesItems, postsItems]) => {
if (cancelled) return if (cancelled) return
setGames(gamesItems) setGames(gamesItems)
setPlayers(playersItems)
setOrders(ordersItems)
setPosts(postsItems) setPosts(postsItems)
setPostsLoading(false) setPostsLoading(false)
}) })
.catch(() => { .catch(() => {
if (cancelled) return if (cancelled) return
setGames([]) setGames([])
setPlayers([])
setOrders([])
setPosts([]) setPosts([])
setPostsLoading(false) setPostsLoading(false)
}) })
@@ -110,16 +104,7 @@ export default function CommunityPage() {
) : filteredPosts.length === 0 ? ( ) : filteredPosts.length === 0 ? (
<div className="text-center py-12 text-muted-foreground"></div> <div className="text-center py-12 text-muted-foreground"></div>
) : ( ) : (
filteredPosts.map((post) => filteredPosts.map((post) => (
(() => {
const linkedOrder = post.linkedOrderId
? orders.find((order) => order.id === post.linkedOrderId)
: null
const linkedPlayer = linkedOrder
? players.find((player) => player.id === linkedOrder.playerId)
: null
return (
<Link key={post.id} href={`/post/${post.id}`} className="block"> <Link key={post.id} href={`/post/${post.id}`} className="block">
<Card className="hover:shadow-md transition-shadow gap-4"> <Card className="hover:shadow-md transition-shadow gap-4">
<CardHeader> <CardHeader>
@@ -132,7 +117,7 @@ export default function CommunityPage() {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm font-medium">{post.author.nickname}</span> <span className="text-sm font-medium">{post.author.nickname}</span>
<Badge variant="outline" className="text-[10px] px-1.5 py-0"> <Badge variant="outline" className="text-[10px] px-1.5 py-0">
{roleLabels[post.authorRole]} {roleLabels[post.author.role]}
</Badge> </Badge>
{post.pinned && <Pin className="h-3 w-3 text-muted-foreground" />} {post.pinned && <Pin className="h-3 w-3 text-muted-foreground" />}
</div> </div>
@@ -154,31 +139,10 @@ export default function CommunityPage() {
))} ))}
</div> </div>
)} )}
{post.linkedOrderId && (
<div className="mt-2 rounded-lg border bg-muted/30 px-3 py-2 text-xs text-muted-foreground space-y-1.5">
<div className="flex items-center gap-1.5">
<ClipboardList className="h-3.5 w-3.5" />
</div>
{linkedOrder && (
<div className="pl-5">
<p>
{linkedOrder.service.gameName} · {linkedOrder.service.title}
</p>
<p>
{linkedOrder.playerName}
{linkedPlayer ? ` · ${linkedPlayer.rating}` : ""}
</p>
</div>
)}
</div>
)}
</CardContent> </CardContent>
<CardFooter className="text-sm text-muted-foreground gap-4"> <CardFooter className="text-sm text-muted-foreground gap-4">
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<Heart <Heart className={`h-4 w-4 ${post.liked ? "fill-red-500 text-red-500" : ""}`} />
className={`h-4 w-4 ${post.liked ? "fill-red-500 text-red-500" : ""}`}
/>
{post.likeCount} {post.likeCount}
</span> </span>
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
@@ -188,9 +152,7 @@ export default function CommunityPage() {
</CardFooter> </CardFooter>
</Card> </Card>
</Link> </Link>
) ))
})(),
)
)} )}
</div> </div>
</div> </div>
+1 -1
View File
@@ -155,7 +155,7 @@ export default async function PlayerDetailPage({ params }: { params: Promise<{ i
<CardContent className="p-6"> <CardContent className="p-6">
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<Avatar> <Avatar>
<AvatarImage src={review.fromUserAvatar} alt={review.fromUserName} /> <AvatarImage src="" alt={review.fromUserName} />
<AvatarFallback>{review.fromUserName[0]}</AvatarFallback> <AvatarFallback>{review.fromUserName[0]}</AvatarFallback>
</Avatar> </Avatar>
<div className="flex-grow space-y-2"> <div className="flex-grow space-y-2">
+13 -13
View File
@@ -38,7 +38,7 @@ export default async function PostDetailPage({ params }: { params: Promise<{ id:
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="font-medium">{post.author.nickname}</span> <span className="font-medium">{post.author.nickname}</span>
<Badge variant="outline" className="text-[10px] px-1.5 py-0"> <Badge variant="outline" className="text-[10px] px-1.5 py-0">
{roleLabels[post.authorRole]} {roleLabels[post.author.role]}
</Badge> </Badge>
{post.pinned && <Pin className="h-3 w-3 text-muted-foreground" />} {post.pinned && <Pin className="h-3 w-3 text-muted-foreground" />}
</div> </div>
@@ -62,18 +62,6 @@ export default async function PostDetailPage({ params }: { params: Promise<{ id:
</div> </div>
)} )}
{post.linkedOrderId && (
<Link href={`/order/${post.linkedOrderId}`}>
<div className="rounded-lg border bg-muted/30 p-3 text-sm hover:bg-muted/50 transition-colors">
<div className="flex items-center gap-2 mb-1">
<Star className="h-3.5 w-3.5 text-yellow-500" />
<span className="font-medium"></span>
</div>
<p className="text-muted-foreground text-xs"></p>
</div>
</Link>
)}
{post.tags.length > 0 && ( {post.tags.length > 0 && (
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{post.tags.map((tag) => ( {post.tags.map((tag) => (
@@ -84,6 +72,18 @@ export default async function PostDetailPage({ params }: { params: Promise<{ id:
</div> </div>
)} )}
{post.linkedOrderId ? (
<Link href={`/order/${post.linkedOrderId}`}>
<div className="rounded-lg border bg-muted/30 p-3 text-sm hover:bg-muted/50 transition-colors">
<div className="flex items-center gap-2 mb-1">
<Star className="h-3.5 w-3.5 text-yellow-500" />
<span className="font-medium"></span>
</div>
<p className="text-muted-foreground text-xs"></p>
</div>
</Link>
) : null}
<div className="flex items-center gap-4 text-sm text-muted-foreground pt-2"> <div className="flex items-center gap-4 text-sm text-muted-foreground pt-2">
<PostLikeButton <PostLikeButton
postId={post.id} postId={post.id}
+1 -3
View File
@@ -78,13 +78,11 @@ export default function NewPostPage() {
createPost({ createPost({
author: user, author: user,
authorRole: currentRole,
title: data.title, title: data.title,
content: data.content, content: data.content,
images: Array.from({ length: imageCount }).map(() => "/posts/p1-1.jpg"), images: Array.from({ length: imageCount }).map(() => "/posts/p1-1.jpg"),
tags: selectedTags, tags: selectedTags,
linkedOrderId: effectivePostType === "show_order" ? selectedOrderId : undefined, linkedOrderId: effectivePostType === "show_order" ? selectedOrderId : undefined,
quotedPostId: effectivePostType === "quote" ? selectedQuotePostId : undefined,
}) })
router.push("/community") router.push("/community")
@@ -131,7 +129,7 @@ export default function NewPostPage() {
<SelectContent> <SelectContent>
{availableOrders.map((order) => ( {availableOrders.map((order) => (
<SelectItem key={order.id} value={order.id}> <SelectItem key={order.id} value={order.id}>
{order.service.title} · {order.playerName} {order.service.title}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>
+1 -1
View File
@@ -218,7 +218,7 @@ function ShopCard({ item }: { item: ShopSearchItem }) {
<div className="flex items-center gap-4 mb-3 text-sm"> <div className="flex items-center gap-4 mb-3 text-sm">
<div className="flex items-center gap-1 text-yellow-500 font-medium"> <div className="flex items-center gap-1 text-yellow-500 font-medium">
<Star className="w-4 h-4 fill-current" /> <Star className="w-4 h-4 fill-current" />
{item.shop.rating.toFixed(1)} {item.shop.rating}
</div> </div>
<div className="text-muted-foreground"> {item.shop.totalOrders}</div> <div className="text-muted-foreground"> {item.shop.totalOrders}</div>
</div> </div>
+2 -2
View File
@@ -25,7 +25,7 @@ export default async function ShopPage({ params }: PageProps) {
const [shopPlayers, allServices] = await Promise.all([listPlayersByShop(shop.id), listServices()]) const [shopPlayers, allServices] = await Promise.all([listPlayersByShop(shop.id), listServices()])
const playerIds = shopPlayers.map((p) => p.id) const playerIds = shopPlayers.map((p) => p.id)
const shopServices = allServices.filter((s) => playerIds.includes(s.playerId)) const shopServices = allServices.filter((s) => playerIds.includes(s.playerId))
const shopReviews = (await listReviews()).filter((r) => playerIds.includes(r.toUserId)) const shopReviews = await listReviews()
const sortedSections = [...shop.templateConfig.sections] const sortedSections = [...shop.templateConfig.sections]
.filter((s) => s.enabled) .filter((s) => s.enabled)
.sort((a, b) => a.order - b.order) .sort((a, b) => a.order - b.order)
@@ -234,7 +234,7 @@ export default async function ShopPage({ params }: PageProps) {
<CardContent className="pt-6"> <CardContent className="pt-6">
<div className="flex gap-4"> <div className="flex gap-4">
<Avatar> <Avatar>
<AvatarImage src={review.fromUserAvatar} /> <AvatarImage src="" />
<AvatarFallback>{review.fromUserName[0]}</AvatarFallback> <AvatarFallback>{review.fromUserName[0]}</AvatarFallback>
</Avatar> </Avatar>
<div className="flex-1 space-y-1"> <div className="flex-1 space-y-1">
+8 -18
View File
@@ -11,7 +11,7 @@ import { sendImageMessage, sendTextMessage } from "@/lib/api/chat"
import { notifyInfo } from "@/lib/toast" import { notifyInfo } from "@/lib/toast"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { useAuthStore } from "@/store/auth" import { useAuthStore } from "@/store/auth"
import { ArrowLeft, ImagePlus, Lock, Send } from "lucide-react" import { ArrowLeft, ImagePlus, Send } from "lucide-react"
import Image from "next/image" import Image from "next/image"
import Link from "next/link" import Link from "next/link"
import { use, useEffect, useRef, useState } from "react" import { use, useEffect, useRef, useState } from "react"
@@ -96,20 +96,14 @@ export default function ChatDetailPage({ params }: { params: Promise<{ id: strin
</Link> </Link>
<Avatar className="h-8 w-8"> <Avatar className="h-8 w-8">
<AvatarImage src={other.avatar} /> <AvatarImage src={other.avatar} />
<AvatarFallback>{other.name[0]}</AvatarFallback> <AvatarFallback>{other.nickname[0]}</AvatarFallback>
</Avatar> </Avatar>
<div> <div>
<span className="text-sm font-medium">{other.name}</span> <span className="text-sm font-medium">{other.nickname}</span>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Badge variant="outline" className="text-[10px] px-1.5 py-0"> <Badge variant="outline" className="text-[10px] px-1.5 py-0">
{session.type === "order" ? "订单会话" : "咨询会话"} {session.type === "order" ? "订单会话" : "咨询会话"}
</Badge> </Badge>
{session.readonly && (
<span className="text-[10px] text-muted-foreground flex items-center gap-0.5">
<Lock className="h-3 w-3" />
</span>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -127,11 +121,14 @@ export default function ChatDetailPage({ params }: { params: Promise<{ id: strin
) )
} }
const isMine = msg.senderId === userId const isMine = msg.senderId === userId
const sender = session.participants.find(
(participant) => participant.id === msg.senderId,
)
return ( return (
<div key={msg.id} className={cn("flex gap-2", isMine && "flex-row-reverse")}> <div key={msg.id} className={cn("flex gap-2", isMine && "flex-row-reverse")}>
<Avatar className="h-8 w-8 shrink-0"> <Avatar className="h-8 w-8 shrink-0">
<AvatarImage src={msg.senderAvatar} /> <AvatarImage src={sender?.avatar} />
<AvatarFallback>{msg.senderName[0]}</AvatarFallback> <AvatarFallback>{(sender?.nickname ?? "?")[0]}</AvatarFallback>
</Avatar> </Avatar>
<div className={cn("max-w-[70%]", isMine && "text-right")}> <div className={cn("max-w-[70%]", isMine && "text-right")}>
{msg.type === "image" ? ( {msg.type === "image" ? (
@@ -166,7 +163,6 @@ export default function ChatDetailPage({ params }: { params: Promise<{ id: strin
</div> </div>
</ScrollArea> </ScrollArea>
{!session.readonly ? (
<div className="border-t p-4 bg-muted/30"> <div className="border-t p-4 bg-muted/30">
<input <input
ref={imageInputRef} ref={imageInputRef}
@@ -228,12 +224,6 @@ export default function ChatDetailPage({ params }: { params: Promise<{ id: strin
</Button> </Button>
</form> </form>
</div> </div>
) : (
<div className="border-t p-4 text-center text-sm text-muted-foreground bg-muted/30">
<Lock className="h-4 w-4 inline mr-1" />
</div>
)}
</Card> </Card>
</div> </div>
) )
+3 -9
View File
@@ -5,7 +5,7 @@ import { Badge } from "@/components/ui/badge"
import { Card, CardContent } from "@/components/ui/card" import { Card, CardContent } from "@/components/ui/card"
import { listChatSessions } from "@/lib/api" import { listChatSessions } from "@/lib/api"
import { useAuthStore } from "@/store/auth" import { useAuthStore } from "@/store/auth"
import { Lock, MessageSquare } from "lucide-react" import { MessageSquare } from "lucide-react"
import Link from "next/link" import Link from "next/link"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
@@ -45,24 +45,18 @@ export default function ChatListPage() {
<CardContent className="flex items-center gap-3 p-4"> <CardContent className="flex items-center gap-3 p-4">
<Avatar className="h-10 w-10"> <Avatar className="h-10 w-10">
<AvatarImage src={other.avatar} /> <AvatarImage src={other.avatar} />
<AvatarFallback>{other.name[0]}</AvatarFallback> <AvatarFallback>{other.nickname[0]}</AvatarFallback>
</Avatar> </Avatar>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm font-medium">{other.name}</span> <span className="text-sm font-medium">{other.nickname}</span>
<Badge variant="outline" className="text-[10px] px-1.5 py-0"> <Badge variant="outline" className="text-[10px] px-1.5 py-0">
{session.type === "order" ? "订单" : "咨询"} {session.type === "order" ? "订单" : "咨询"}
</Badge> </Badge>
{session.readonly && <Lock className="h-3 w-3 text-muted-foreground" />}
</div> </div>
<p className="text-xs text-muted-foreground truncate">{session.lastMessage}</p> <p className="text-xs text-muted-foreground truncate">{session.lastMessage}</p>
</div> </div>
<div className="flex flex-col items-end gap-1 shrink-0"> <div className="flex flex-col items-end gap-1 shrink-0">
{session.lastMessageAt && (
<span className="text-[10px] text-muted-foreground">
{new Date(session.lastMessageAt).toLocaleDateString("zh-CN")}
</span>
)}
{session.unreadCount > 0 && ( {session.unreadCount > 0 && (
<Badge className="h-4 min-w-4 px-1 flex items-center justify-center text-[10px]"> <Badge className="h-4 min-w-4 px-1 flex items-center justify-center text-[10px]">
{session.unreadCount} {session.unreadCount}
+2 -6
View File
@@ -213,10 +213,8 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
} }
if (existingDispute) { if (existingDispute) {
const isInitiator = userId === existingDispute.initiatorId
const canRespond = const canRespond =
isParticipant && isParticipant &&
!isInitiator &&
!existingDispute.respondentReason && !existingDispute.respondentReason &&
(existingDispute.status === "open" || existingDispute.status === "reviewing") (existingDispute.status === "open" || existingDispute.status === "reviewing")
const canAppeal = const canAppeal =
@@ -246,7 +244,7 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
<div className="flex items-center gap-2 text-sm"> <div className="flex items-center gap-2 text-sm">
<FileText className="h-4 w-4 text-muted-foreground" /> <FileText className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground">:</span> <span className="text-muted-foreground">:</span>
{existingDispute.initiatorName}
</div> </div>
<div className="flex items-center gap-2 text-sm"> <div className="flex items-center gap-2 text-sm">
<Clock className="h-4 w-4 text-muted-foreground" /> <Clock className="h-4 w-4 text-muted-foreground" />
@@ -480,9 +478,7 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
<AlertTriangle className="h-5 w-5 text-yellow-500" /> <AlertTriangle className="h-5 w-5 text-yellow-500" />
</CardTitle> </CardTitle>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">{order.service.title}</p>
{order.service.title} · {order.playerName}
</p>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
+2 -17
View File
@@ -140,7 +140,7 @@ export default function OrderDetailPage({ params }: { params: Promise<{ id: stri
const base = const base =
order.status === "pending_accept" order.status === "pending_accept"
? new Date(order.createdAt).getTime() ? new Date(order.createdAt).getTime()
: new Date(order.closedAt ?? order.createdAt).getTime() : new Date(order.createdAt).getTime()
const timeoutMs = const timeoutMs =
order.status === "pending_accept" ? ORDER_ACCEPT_TIMEOUT_MS : ORDER_CLOSE_TIMEOUT_MS order.status === "pending_accept" ? ORDER_ACCEPT_TIMEOUT_MS : ORDER_CLOSE_TIMEOUT_MS
const remainSeconds = Math.max(0, Math.ceil((timeoutMs - (nowTs - base)) / 1000)) const remainSeconds = Math.max(0, Math.ceil((timeoutMs - (nowTs - base)) / 1000))
@@ -223,17 +223,9 @@ export default function OrderDetailPage({ params }: { params: Promise<{ id: stri
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span className="text-muted-foreground"></span> <span className="text-muted-foreground"></span>
<Link href={`/player/${order.playerId}`} className="text-primary hover:underline"> <Link href={`/player/${order.playerId}`} className="text-primary hover:underline">
{order.playerName} {order.service.title}
</Link> </Link>
</div> </div>
{order.shopName && (
<div className="flex justify-between text-sm">
<span className="text-muted-foreground"></span>
<Link href={`/shop/${order.shopId}`} className="text-primary hover:underline">
{order.shopName}
</Link>
</div>
)}
<Separator /> <Separator />
<div className="flex justify-between text-sm font-medium"> <div className="flex justify-between text-sm font-medium">
<span></span> <span></span>
@@ -265,13 +257,6 @@ export default function OrderDetailPage({ params }: { params: Promise<{ id: stri
{new Date(order.acceptedAt).toLocaleString("zh-CN")} {new Date(order.acceptedAt).toLocaleString("zh-CN")}
</div> </div>
)} )}
{order.closedAt && (
<div className="flex items-center gap-2">
<CheckCircle className="h-3.5 w-3.5 text-green-500" />
<span className="text-muted-foreground">:</span>
{new Date(order.closedAt).toLocaleString("zh-CN")}
</div>
)}
{order.completedAt && ( {order.completedAt && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<CheckCircle className="h-3.5 w-3.5 text-green-500" /> <CheckCircle className="h-3.5 w-3.5 text-green-500" />
+1 -7
View File
@@ -178,13 +178,7 @@ function OrderListContent({
{statusLabels[order.status]} {statusLabels[order.status]}
</Badge> </Badge>
</div> </div>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">{order.service.title}</p>
{currentRole === "consumer"
? `打手: ${order.playerName}`
: currentRole === "player"
? `客户: ${order.consumerName}`
: `客户: ${order.consumerName} · 打手: ${order.playerName}`}
</p>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
+1 -3
View File
@@ -120,9 +120,7 @@ export default function ReviewPage({ params }: { params: Promise<{ id: string }>
<Card className="hover:shadow-card-hover"> <Card className="hover:shadow-card-hover">
<CardHeader> <CardHeader>
<CardTitle></CardTitle> <CardTitle></CardTitle>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">{order.service.title}</p>
{order.service.title} · {order.playerName}
</p>
</CardHeader> </CardHeader>
<CardContent className="space-y-6"> <CardContent className="space-y-6">
<div className="space-y-2"> <div className="space-y-2">