feat: wire order and chat state flow
This commit is contained in:
@@ -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("")
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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" />
|
||||
暂无消息
|
||||
|
||||
@@ -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`)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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}`}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user