Files
juwan-frontend/components/order-actions.tsx
T
zetaloop cf0fea9926 refactor(orders): replace local state machine with minimal cache
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*).
2026-05-01 17:32:06 +08:00

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>
)
}