feat(orders): migrate orders to backend API
This commit is contained in:
@@ -19,7 +19,8 @@ export default function DashboardPage() {
|
||||
const [player, setPlayer] = useState<Player | null>(null)
|
||||
const [shop, setShop] = useState<Shop | null>(null)
|
||||
const [services, setServices] = useState<PlayerService[]>([])
|
||||
const recentOrders = listOrders().slice(0, 3)
|
||||
const [orders, setOrders] = useState<Awaited<ReturnType<typeof listOrders>>>([])
|
||||
const recentOrders = orders.slice(0, 3)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
@@ -43,6 +44,25 @@ export default function DashboardPage() {
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
|
||||
;(async () => {
|
||||
try {
|
||||
const orders = await Promise.resolve(listOrders())
|
||||
if (cancelled) return
|
||||
setOrders(orders)
|
||||
} catch {
|
||||
if (cancelled) return
|
||||
setOrders([])
|
||||
}
|
||||
})()
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [])
|
||||
|
||||
const totalOrders = isOwner ? (shop?.totalOrders ?? 0) : (player?.totalOrders ?? 0)
|
||||
const rating = isOwner ? (shop?.rating ?? 0) : (player?.rating ?? 0)
|
||||
const playerCount = shop?.playerCount ?? 0
|
||||
|
||||
@@ -14,9 +14,9 @@ import { useEffect, useState } from "react"
|
||||
export default function CommunityPage() {
|
||||
const [games, setGames] = useState<Game[]>([])
|
||||
const [players, setPlayers] = useState<Player[]>([])
|
||||
const [orders, setOrders] = useState<Awaited<ReturnType<typeof listOrders>>>([])
|
||||
const [posts, setPosts] = useState<Post[]>([])
|
||||
const [postsLoading, setPostsLoading] = useState(true)
|
||||
const orders = listOrders()
|
||||
|
||||
const [sortMode, setSortMode] = useState<"latest" | "hot">("latest")
|
||||
const [selectedGame, setSelectedGame] = useState<string | null>(null)
|
||||
@@ -26,11 +26,12 @@ export default function CommunityPage() {
|
||||
|
||||
setPostsLoading(true)
|
||||
|
||||
Promise.all([listGames(), listPlayers(), listPosts()])
|
||||
.then(([gamesItems, playersItems, postsItems]) => {
|
||||
Promise.all([listGames(), listPlayers(), Promise.resolve(listOrders()), listPosts()])
|
||||
.then(([gamesItems, playersItems, ordersItems, postsItems]) => {
|
||||
if (cancelled) return
|
||||
setGames(gamesItems)
|
||||
setPlayers(playersItems)
|
||||
setOrders(ordersItems)
|
||||
setPosts(postsItems)
|
||||
setPostsLoading(false)
|
||||
})
|
||||
@@ -38,6 +39,7 @@ export default function CommunityPage() {
|
||||
if (cancelled) return
|
||||
setGames([])
|
||||
setPlayers([])
|
||||
setOrders([])
|
||||
setPosts([])
|
||||
setPostsLoading(false)
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { getOrderById, getPlayerById, getPostById } from "@/lib/api"
|
||||
import { getPostById } from "@/lib/api"
|
||||
import { roleLabels } from "@/lib/constants"
|
||||
import { ArrowLeft, Pin, Star } from "lucide-react"
|
||||
import Image from "next/image"
|
||||
@@ -17,9 +17,6 @@ export default async function PostDetailPage({ params }: { params: Promise<{ id:
|
||||
const post = await getPostById(id)
|
||||
if (!post) notFound()
|
||||
|
||||
const linkedOrder = post.linkedOrderId ? getOrderById(post.linkedOrderId) : null
|
||||
const linkedPlayer = linkedOrder ? await getPlayerById(linkedOrder.playerId) : null
|
||||
|
||||
return (
|
||||
<div className="container mx-auto max-w-2xl px-4 py-8 space-y-6">
|
||||
<Link
|
||||
@@ -65,17 +62,14 @@ export default async function PostDetailPage({ params }: { params: Promise<{ id:
|
||||
</div>
|
||||
)}
|
||||
|
||||
{linkedOrder && (
|
||||
<Link href={`/order/${linkedOrder.id}`}>
|
||||
{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">
|
||||
{linkedOrder.service.gameName} · {linkedOrder.service.title} · 评分{" "}
|
||||
{linkedPlayer?.rating ?? "--"}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-xs">点击查看订单详情</p>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -4,11 +4,11 @@ import OrderActions from "@/components/order-actions"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { getOrderById } from "@/lib/api"
|
||||
import { ORDER_ACCEPT_TIMEOUT_MS, ORDER_CLOSE_TIMEOUT_MS } from "@/lib/config/demo-timers"
|
||||
import { statusLabels } from "@/lib/constants"
|
||||
import type { OrderStatus } from "@/lib/types"
|
||||
import { useChatStore } from "@/store/chat"
|
||||
import { useOrderStore } from "@/store/orders"
|
||||
import { useReviewStore } from "@/store/reviews"
|
||||
import { ArrowLeft, CheckCircle, Clock, Star } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
@@ -35,7 +35,6 @@ const cancelledStatusSteps: OrderStatus[] = ["pending_payment", "pending_accept"
|
||||
|
||||
export default function OrderDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = use(params)
|
||||
const order = useOrderStore((state) => state.orders.find((item) => item.id === id))
|
||||
const sessions = useChatStore((state) => state.sessions)
|
||||
const allReviews = useReviewStore((state) => state.reviews)
|
||||
// Filtering is deferred to useMemo after reading the raw store array.
|
||||
@@ -43,8 +42,34 @@ export default function OrderDetailPage({ params }: { params: Promise<{ id: stri
|
||||
// Returning a fresh filtered array from the selector can re-trigger updates
|
||||
// and loop under useSyncExternalStore (pmndrs/zustand#1936, #3155).
|
||||
const reviews = useMemo(() => allReviews.filter((item) => item.orderId === id), [allReviews, id])
|
||||
const [order, setOrder] = useState<Awaited<ReturnType<typeof getOrderById>> | undefined>(
|
||||
undefined,
|
||||
)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [nowTs, setNowTs] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
|
||||
setLoading(true)
|
||||
;(async () => {
|
||||
try {
|
||||
const order = await Promise.resolve(getOrderById(id))
|
||||
if (cancelled) return
|
||||
setOrder(order)
|
||||
setLoading(false)
|
||||
} catch {
|
||||
if (cancelled) return
|
||||
setOrder(undefined)
|
||||
setLoading(false)
|
||||
}
|
||||
})()
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [id])
|
||||
|
||||
useEffect(() => {
|
||||
if (!order) return
|
||||
if (order.status !== "pending_accept" && order.status !== "pending_close") return
|
||||
@@ -56,6 +81,12 @@ export default function OrderDetailPage({ params }: { params: Promise<{ id: stri
|
||||
return () => clearInterval(timer)
|
||||
}, [order])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="container mx-auto py-8 px-4 text-center text-muted-foreground">加载中...</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!order) {
|
||||
return (
|
||||
<div className="container mx-auto py-8 px-4 text-center text-muted-foreground">
|
||||
@@ -257,6 +288,8 @@ export default function OrderDetailPage({ params }: { params: Promise<{ id: stri
|
||||
|
||||
<OrderActions
|
||||
orderId={order.id}
|
||||
order={order}
|
||||
onOrderChange={(next) => setOrder(next)}
|
||||
initialStatus={order.status}
|
||||
chatSessionId={chatSession?.id}
|
||||
serviceId={order.service.id}
|
||||
|
||||
@@ -7,13 +7,11 @@ import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import type { Actor } from "@/lib/actor"
|
||||
import { getPlayerById, getServiceById } from "@/lib/api"
|
||||
import { createPaidOrder } from "@/lib/api/orders"
|
||||
import { notifySuccess } from "@/lib/toast"
|
||||
import type { Player, PlayerService } from "@/lib/types"
|
||||
import { useRequireAuth } from "@/lib/use-require-auth"
|
||||
import { useAuthStore } from "@/store/auth"
|
||||
import { useOrderStore } from "@/store/orders"
|
||||
import { useWalletStore } from "@/store/wallet"
|
||||
import { ArrowLeft, CheckCircle, CreditCard, ShieldCheck } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
@@ -24,7 +22,6 @@ export default function NewOrderPage() {
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const { requireAuth } = useRequireAuth()
|
||||
const createPaidOrder = useOrderStore((state) => state.createPaidOrder)
|
||||
const balance = useWalletStore((state) => state.balance)
|
||||
const serviceId = searchParams.get("serviceId")
|
||||
|
||||
@@ -234,34 +231,26 @@ export default function NewOrderPage() {
|
||||
disabled={balance < totalPrice}
|
||||
onClick={() =>
|
||||
requireAuth(() => {
|
||||
const authUser = useAuthStore.getState().user
|
||||
if (!authUser) return
|
||||
|
||||
const actor: Actor = {
|
||||
userId: authUser.id,
|
||||
role: "consumer",
|
||||
}
|
||||
|
||||
const result = createPaidOrder(
|
||||
{
|
||||
Promise.resolve(
|
||||
createPaidOrder({
|
||||
playerId: player.id,
|
||||
serviceId: service.id,
|
||||
shopId: player.shopId,
|
||||
quantity,
|
||||
note,
|
||||
},
|
||||
actor,
|
||||
)
|
||||
if (!result.decision.ok || !result.order) {
|
||||
return
|
||||
}
|
||||
const nextOrder = result.order
|
||||
}),
|
||||
).then((result) => {
|
||||
if (!result.decision.ok || !result.order) {
|
||||
return
|
||||
}
|
||||
const nextOrder = result.order
|
||||
|
||||
setSubmitted(true)
|
||||
notifySuccess("下单成功")
|
||||
setTimeout(() => {
|
||||
router.push(`/order/${nextOrder.id}`)
|
||||
}, 800)
|
||||
setSubmitted(true)
|
||||
notifySuccess("下单成功")
|
||||
setTimeout(() => {
|
||||
router.push(`/order/${nextOrder.id}`)
|
||||
}, 800)
|
||||
})
|
||||
})
|
||||
}
|
||||
>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { listOrders } from "@/lib/api"
|
||||
import { statusLabels } from "@/lib/constants"
|
||||
import {
|
||||
isActiveOrder,
|
||||
@@ -16,11 +17,10 @@ import type { OrderStatus, UserRole } from "@/lib/types"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useAuthStore } from "@/store/auth"
|
||||
import { useChatStore } from "@/store/chat"
|
||||
import { useOrderStore } from "@/store/orders"
|
||||
import { useShopStore } from "@/store/shops"
|
||||
import { Clock, MessageSquare, RefreshCw } from "lucide-react"
|
||||
import Link from "next/link"
|
||||
import { useState } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
const statusColors: Record<OrderStatus, string> = {
|
||||
pending_payment: "bg-yellow-100 text-yellow-800",
|
||||
@@ -82,9 +82,28 @@ function OrderListContent({
|
||||
ownerShopId?: string
|
||||
}) {
|
||||
const [tab, setTab] = useState<TabFilter | "pending">("all")
|
||||
const orders = useOrderStore((state) => state.orders)
|
||||
const [orders, setOrders] = useState<Awaited<ReturnType<typeof listOrders>>>([])
|
||||
const sessions = useChatStore((state) => state.sessions)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
|
||||
;(async () => {
|
||||
try {
|
||||
const items = await Promise.resolve(listOrders())
|
||||
if (cancelled) return
|
||||
setOrders(items)
|
||||
} catch {
|
||||
if (cancelled) return
|
||||
setOrders([])
|
||||
}
|
||||
})()
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [])
|
||||
|
||||
const tabs =
|
||||
currentRole === "consumer" ? consumerTabs : currentRole === "player" ? playerTabs : ownerTabs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user