cf0fea9926
Strip store/orders.ts to a thin local cache with setOrders and updateOrder only. Remove all client-side state transition logic, actor validation, chat sync, notification generation, and wallet integration — these are now handled by the backend API. Fix components/order-actions.tsx and stores that depended on the removed order store methods (markDisputed, autoTimeout*).
266 lines
7.1 KiB
TypeScript
266 lines
7.1 KiB
TypeScript
"use client"
|
|
|
|
import { Button } from "@/components/ui/button"
|
|
import { getShopById } from "@/lib/api"
|
|
import {
|
|
acceptOrder,
|
|
cancelPreAccept,
|
|
confirmClose,
|
|
payOrder,
|
|
requestClose,
|
|
} from "@/lib/api/orders"
|
|
import type { ApiDecision } from "@/lib/errors"
|
|
import { notifyInfo, notifySuccess } from "@/lib/toast"
|
|
import type { Order, OrderStatus } from "@/lib/types"
|
|
import { useAuthStore } from "@/store/auth"
|
|
import {
|
|
AlertTriangle,
|
|
CheckCircle2,
|
|
Clock,
|
|
MessageSquare,
|
|
RefreshCw,
|
|
Star,
|
|
XCircle,
|
|
} from "lucide-react"
|
|
import Link from "next/link"
|
|
import { useCallback, useEffect, useState } from "react"
|
|
|
|
interface OrderActionsProps {
|
|
orderId: string
|
|
order?: Order
|
|
onOrderChange?: (order: Order) => void
|
|
initialStatus: OrderStatus
|
|
chatTargetId?: string
|
|
isPlayerParticipant?: boolean
|
|
serviceId: string
|
|
}
|
|
|
|
function showFeedback(message: string) {
|
|
notifySuccess(message)
|
|
}
|
|
|
|
export default function OrderActions({
|
|
orderId,
|
|
order: orderProp,
|
|
onOrderChange,
|
|
initialStatus,
|
|
chatTargetId,
|
|
isPlayerParticipant,
|
|
serviceId,
|
|
}: OrderActionsProps) {
|
|
const currentUserId = useAuthStore((state) => state.user?.id)
|
|
const order = orderProp
|
|
const [dispatchMode, setDispatchMode] = useState<"manual" | "auto" | null>(null)
|
|
const resolvedChatTargetId = chatTargetId
|
|
|
|
useEffect(() => {
|
|
if (!order?.shopId) return
|
|
|
|
let cancelled = false
|
|
|
|
Promise.resolve()
|
|
.then(() => {
|
|
if (cancelled) return undefined
|
|
setDispatchMode(null)
|
|
return getShopById(order.shopId ?? "")
|
|
})
|
|
.then((shop) => {
|
|
if (cancelled || !shop) return
|
|
setDispatchMode(shop.dispatchMode)
|
|
})
|
|
.catch(() => {
|
|
if (cancelled) return
|
|
setDispatchMode(null)
|
|
})
|
|
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [order?.shopId])
|
|
|
|
const status = order?.status ?? initialStatus
|
|
const isConsumer = String(order?.consumerId) === currentUserId
|
|
const isPlayer = isPlayerParticipant === true
|
|
const isParticipant = isConsumer || isPlayer
|
|
|
|
type ActionResult = { decision: ApiDecision; order?: Order }
|
|
|
|
const handleDecision = useCallback((okMessage: string, result: ActionResult) => {
|
|
if (result.decision.ok) {
|
|
showFeedback(okMessage)
|
|
return
|
|
}
|
|
|
|
notifyInfo(result.decision.error.msg)
|
|
}, [])
|
|
|
|
const runAction = useCallback(
|
|
(okMessage: string, actionCall: ActionResult | Promise<ActionResult>) => {
|
|
Promise.resolve(actionCall)
|
|
.then((result) => {
|
|
handleDecision(okMessage, result)
|
|
if (result.order) onOrderChange?.(result.order)
|
|
})
|
|
.catch(() => {
|
|
notifyInfo("操作失败")
|
|
})
|
|
},
|
|
[handleDecision, onOrderChange],
|
|
)
|
|
|
|
return (
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
{status === "pending_payment" && isConsumer && (
|
|
<Button
|
|
onClick={() => {
|
|
if (!currentUserId) {
|
|
notifyInfo("请先登录")
|
|
return
|
|
}
|
|
runAction("订单支付成功", payOrder(orderId))
|
|
}}
|
|
>
|
|
<CheckCircle2 className="mr-1 h-4 w-4" />
|
|
确认支付
|
|
</Button>
|
|
)}
|
|
|
|
{status === "pending_accept" && (
|
|
<>
|
|
{isConsumer && (
|
|
<Button
|
|
variant="outline"
|
|
className="border-border/60"
|
|
onClick={() => {
|
|
if (!currentUserId) {
|
|
notifyInfo("请先登录")
|
|
return
|
|
}
|
|
runAction("订单已取消", cancelPreAccept(orderId))
|
|
}}
|
|
>
|
|
<XCircle className="mr-1 h-4 w-4" />
|
|
取消订单
|
|
</Button>
|
|
)}
|
|
{isPlayer &&
|
|
(order?.shopId && dispatchMode !== "manual" ? (
|
|
<Button disabled>
|
|
<Clock className="mr-1 h-4 w-4" />
|
|
{dispatchMode === "auto" ? "系统派单中" : "读取派单规则..."}
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
onClick={() => {
|
|
if (!currentUserId) {
|
|
notifyInfo("请先登录")
|
|
return
|
|
}
|
|
runAction("已接单", acceptOrder(orderId))
|
|
}}
|
|
>
|
|
<CheckCircle2 className="mr-1 h-4 w-4" />
|
|
接单
|
|
</Button>
|
|
))}
|
|
</>
|
|
)}
|
|
|
|
{(status === "in_progress" || status === "pending_close") && resolvedChatTargetId && (
|
|
<Button variant="outline" className="border-border/60" asChild>
|
|
<Link href={`/chat/${resolvedChatTargetId}?orderId=${orderId}`}>
|
|
<MessageSquare className="mr-1 h-4 w-4" />
|
|
聊天
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
|
|
{status === "in_progress" && (
|
|
<>
|
|
{isPlayer && (
|
|
<Button
|
|
onClick={() => {
|
|
if (!currentUserId) {
|
|
notifyInfo("请先登录")
|
|
return
|
|
}
|
|
|
|
runAction("已发起结单", requestClose(orderId))
|
|
}}
|
|
>
|
|
发起结单
|
|
</Button>
|
|
)}
|
|
{isParticipant && (
|
|
<Button variant="destructive" asChild>
|
|
<Link href={`/dispute/${orderId}`}>
|
|
<AlertTriangle className="mr-1 h-4 w-4" />
|
|
发起争议
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{status === "pending_close" && (
|
|
<>
|
|
{isConsumer && (
|
|
<Button
|
|
onClick={() => {
|
|
if (!currentUserId) {
|
|
notifyInfo("请先登录")
|
|
return
|
|
}
|
|
runAction("已确认结单", confirmClose(orderId))
|
|
}}
|
|
>
|
|
确认结单
|
|
</Button>
|
|
)}
|
|
{isParticipant && (
|
|
<Button variant="destructive" asChild>
|
|
<Link href={`/dispute/${orderId}`}>
|
|
<AlertTriangle className="mr-1 h-4 w-4" />
|
|
发起争议
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{status === "pending_review" && isParticipant && (
|
|
<Button asChild>
|
|
<Link href={`/review/${orderId}`}>
|
|
<Star className="mr-1 h-4 w-4" />
|
|
评价
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
|
|
{status === "completed" && (
|
|
<Button variant="outline" className="border-border/60" asChild>
|
|
<Link href={`/order/new?serviceId=${serviceId}`}>
|
|
<RefreshCw className="mr-1 h-4 w-4" />
|
|
再来一单
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
|
|
{status === "cancelled" && (
|
|
<Button variant="outline" className="border-border/60" asChild>
|
|
<Link href={`/order/new?serviceId=${serviceId}`}>
|
|
<RefreshCw className="mr-1 h-4 w-4" />
|
|
再来一单
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
|
|
{status === "disputed" && isParticipant && (
|
|
<Button variant="outline" className="border-border/60" asChild>
|
|
<Link href={`/dispute/${orderId}`}>查看详情</Link>
|
|
</Button>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|