feat: wire order and chat state flow

This commit is contained in:
zetaloop
2026-02-22 06:40:40 +08:00
parent 4ce7303258
commit 02269dd9c3
10 changed files with 372 additions and 57 deletions
+19 -3
View File
@@ -8,14 +8,15 @@ import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { ScrollArea } from "@/components/ui/scroll-area"
import { mockChatMessages, mockChatSessions } from "@/lib/mock"
import { cn } from "@/lib/utils"
import { useAuthStore } from "@/store/auth"
import { useChatStore } from "@/store/chat"
export default function ChatDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = use(params)
const session = mockChatSessions.find((s) => s.id === id)
const messages = mockChatMessages.filter((m) => m.sessionId === id)
const session = useChatStore((state) => state.sessions.find((item) => item.id === id))
const messages = useChatStore((state) => state.messages.filter((item) => item.sessionId === id))
const sendTextMessage = useChatStore((state) => state.sendTextMessage)
const [input, setInput] = useState("")
const { user } = useAuthStore()
@@ -103,6 +104,21 @@ export default function ChatDetailPage({ params }: { params: Promise<{ id: strin
className="flex gap-2 max-w-2xl mx-auto"
onSubmit={(e) => {
e.preventDefault()
const text = input.trim()
if (!text) return
const sender = session.participants.find(
(participant) => participant.id === currentUserId,
)
sendTextMessage(
session.id,
{
id: currentUserId,
name: sender?.name ?? user?.nickname ?? "",
avatar: sender?.avatar ?? user?.avatar ?? "",
},
text,
)
setInput("")
}}
>
+12 -4
View File
@@ -1,18 +1,26 @@
"use client"
import { Lock, MessageSquare } from "lucide-react"
import Link from "next/link"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Card, CardContent } from "@/components/ui/card"
import { mockChatSessions } from "@/lib/mock"
import { useAuthStore } from "@/store/auth"
import { useChatStore } from "@/store/chat"
export default function ChatListPage() {
const sessions = useChatStore((state) => state.sessions)
const userId = useAuthStore((state) => state.user?.id)
return (
<div className="container mx-auto py-8 px-4 max-w-2xl">
<h1 className="text-2xl font-bold mb-6"></h1>
<div className="space-y-2">
{mockChatSessions.map((session) => {
const other = session.participants[1]
{sessions.map((session) => {
const other =
session.participants.find((participant) => participant.id !== userId) ??
session.participants[0]
return (
<Link key={session.id} href={`/chat/${session.id}`}>
<Card className="hover:bg-accent/30 transition-colors">
@@ -49,7 +57,7 @@ export default function ChatListPage() {
)
})}
{mockChatSessions.length === 0 && (
{sessions.length === 0 && (
<div className="text-center py-12 text-muted-foreground">
<MessageSquare className="h-12 w-12 mx-auto mb-2 opacity-50" />
+5 -2
View File
@@ -11,7 +11,8 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Label } from "@/components/ui/label"
import { Separator } from "@/components/ui/separator"
import { Textarea } from "@/components/ui/textarea"
import { mockDisputes, mockOrders } from "@/lib/mock"
import { mockDisputes } from "@/lib/mock"
import { useOrderStore } from "@/store/orders"
const disputeStatusLabels: Record<string, string> = {
open: "已提交",
@@ -24,7 +25,8 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
const { id } = use(params)
const router = useRouter()
const searchParams = useSearchParams()
const order = mockOrders.find((o) => o.id === id)
const order = useOrderStore((state) => state.orders.find((item) => item.id === id))
const updateOrderStatus = useOrderStore((state) => state.updateOrderStatus)
const existingDispute = mockDisputes.find((d) => d.orderId === id)
const [reason, setReason] = useState("")
const [submitted, setSubmitted] = useState(false)
@@ -70,6 +72,7 @@ export default function DisputePage({ params }: { params: Promise<{ id: string }
}
const handleSubmit = () => {
updateOrderStatus(id, "disputed")
setSubmitted(true)
router.replace(`/dispute/${id}?submitted=1`)
}
+26 -7
View File
@@ -1,13 +1,17 @@
"use client"
import { ArrowLeft, CheckCircle, Clock, Star } from "lucide-react"
import Link from "next/link"
import { notFound } from "next/navigation"
import { use, useEffect } from "react"
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 { statusLabels } from "@/lib/constants"
import { mockChatSessions, mockOrders, mockReviews } from "@/lib/mock"
import { mockReviews } from "@/lib/mock"
import type { OrderStatus } from "@/lib/types"
import { useChatStore } from "@/store/chat"
import { useOrderStore } from "@/store/orders"
const normalStatusSteps: OrderStatus[] = [
"pending_payment",
@@ -28,13 +32,28 @@ const disputedStatusSteps: OrderStatus[] = [
const cancelledStatusSteps: OrderStatus[] = ["pending_payment", "pending_accept", "cancelled"]
export default async function OrderDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const order = mockOrders.find((o) => o.id === id)
if (!order) notFound()
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 ensureOrderSession = useChatStore((state) => state.ensureOrderSession)
useEffect(() => {
if (!order) return
if (order.status === "pending_payment" || order.status === "cancelled") return
ensureOrderSession(order)
}, [order, ensureOrderSession])
if (!order) {
return (
<div className="container mx-auto py-8 px-4 text-center text-muted-foreground">
</div>
)
}
const reviews = mockReviews.filter((r) => r.orderId === id)
const chatSession = mockChatSessions.find((s) => s.orderId === id)
const chatSession = sessions.find((session) => session.type === "order" && session.orderId === id)
const statusSteps =
order.status === "disputed"
? disputedStatusSteps
+26 -10
View File
@@ -11,8 +11,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 { mockOrders, mockPlayers, mockServices, walletBalance } from "@/lib/mock"
import { mockPlayers, mockServices, walletBalance } from "@/lib/mock"
import { useRequireAuth } from "@/lib/use-require-auth"
import { useAuthStore } from "@/store/auth"
import { useChatStore } from "@/store/chat"
import { useOrderStore } from "@/store/orders"
function showFeedback(message: string) {
if (typeof window === "undefined") return
@@ -23,6 +26,8 @@ export default function NewOrderPage() {
const router = useRouter()
const searchParams = useSearchParams()
const { requireAuth } = useRequireAuth()
const createOrder = useOrderStore((state) => state.createOrder)
const ensureOrderSession = useChatStore((state) => state.ensureOrderSession)
const serviceId = searchParams.get("serviceId")
const service = mockServices.find((s) => s.id === serviceId)
@@ -41,8 +46,6 @@ export default function NewOrderPage() {
}
const totalPrice = service.price * quantity
const redirectOrderId =
mockOrders.find((order) => order.service.id === service.id)?.id ?? mockOrders[0]?.id
if (submitted) {
return (
@@ -186,15 +189,28 @@ export default function NewOrderPage() {
onClick={() =>
requireAuth(async () => {
await new Promise((resolve) => setTimeout(resolve, 500))
const currentUser = useAuthStore.getState().user
if (!currentUser) return
const order = createOrder({
consumerId: currentUser.id,
consumerName: currentUser.nickname,
playerId: player.id,
playerName: player.user.nickname,
shopId: player.shopId,
shopName: player.shopName,
service,
totalPrice,
note,
status: "pending_accept",
})
ensureOrderSession(order)
setSubmitted(true)
showFeedback("下单成功")
if (redirectOrderId) {
setTimeout(() => {
router.push(`/order/${redirectOrderId}`)
}, 800)
return
}
router.push("/orders")
setTimeout(() => {
router.push(`/order/${order.id}`)
}, 800)
})
}
>
+38 -18
View File
@@ -8,10 +8,11 @@ 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 { statusLabels } from "@/lib/constants"
import { mockChatSessions, mockOrders } from "@/lib/mock"
import type { OrderStatus } from "@/lib/types"
import { cn } from "@/lib/utils"
import { useAuthStore } from "@/store/auth"
import { useChatStore } from "@/store/chat"
import { useOrderStore } from "@/store/orders"
const statusColors: Record<OrderStatus, string> = {
pending_payment: "bg-yellow-100 text-yellow-800",
@@ -51,6 +52,9 @@ const ownerTabs = [
export default function OrderListPage() {
const [tab, setTab] = useState<TabFilter | "pending">("all")
const { currentRole, user } = useAuthStore()
const orders = useOrderStore((state) => state.orders)
const sessions = useChatStore((state) => state.sessions)
const ensureOrderSession = useChatStore((state) => state.ensureOrderSession)
const currentUserId = user?.id ?? "u1"
const ownerShopId = "shop1"
@@ -62,7 +66,14 @@ export default function OrderListPage() {
const tabs =
currentRole === "consumer" ? consumerTabs : currentRole === "player" ? playerTabs : ownerTabs
const roleFiltered = mockOrders.filter((order) => {
useEffect(() => {
orders.forEach((order) => {
if (order.status === "pending_payment" || order.status === "cancelled") return
ensureOrderSession(order)
})
}, [orders, ensureOrderSession])
const roleFiltered = orders.filter((order) => {
if (currentRole === "consumer") return order.consumerId === currentUserId
if (currentRole === "player") return order.playerId === currentUserId
return order.shopId === ownerShopId
@@ -70,8 +81,15 @@ export default function OrderListPage() {
const filtered = roleFiltered.filter((order) => {
if (tab === "pending") return order.status === "pending_accept"
if (tab === "active")
return ["in_progress", "pending_close", "pending_review"].includes(order.status)
if (tab === "active") {
return [
"pending_payment",
"pending_accept",
"in_progress",
"pending_close",
"pending_review",
].includes(order.status)
}
if (tab === "completed") return order.status === "completed" || order.status === "cancelled"
if (tab === "disputed") return order.status === "disputed"
return true
@@ -130,20 +148,22 @@ export default function OrderListPage() {
</span>
</div>
<div className="flex gap-2">
{order.status === "in_progress" &&
(() => {
const session = mockChatSessions.find((s) => s.orderId === order.id)
return (
session && (
<Button variant="outline" size="sm" asChild>
<Link href={`/chat/${session.id}`}>
<MessageSquare className="mr-1 h-3.5 w-3.5" />
</Link>
</Button>
)
)
})()}
{(() => {
if (order.status !== "in_progress" && order.status !== "pending_close")
return null
const session = sessions.find(
(item) => item.type === "order" && item.orderId === order.id,
)
if (!session) return null
return (
<Button variant="outline" size="sm" asChild>
<Link href={`/chat/${session.id}`}>
<MessageSquare className="mr-1 h-3.5 w-3.5" />
</Link>
</Button>
)
})()}
{order.status === "completed" && (
<Button variant="outline" size="sm" asChild>
<Link href={`/order/new?serviceId=${order.service.id}`}>
+11 -3
View File
@@ -7,11 +7,12 @@ import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { mockOrders } from "@/lib/mock"
import { useOrderStore } from "@/store/orders"
export default function ReviewPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = use(params)
const order = mockOrders.find((o) => o.id === id)
const order = useOrderStore((state) => state.orders.find((item) => item.id === id))
const updateOrderStatus = useOrderStore((state) => state.updateOrderStatus)
const [rating, setRating] = useState(0)
const [hoverRating, setHoverRating] = useState(0)
const [content, setContent] = useState("")
@@ -97,7 +98,14 @@ export default function ReviewPage({ params }: { params: Promise<{ id: string }>
<span></span>
</div>
<Button className="w-full" disabled={rating === 0} onClick={() => setSubmitted(true)}>
<Button
className="w-full"
disabled={rating === 0}
onClick={() => {
updateOrderStatus(id, "completed")
setSubmitted(true)
}}
>
</Button>
</CardContent>