feat(order): add sealed review reveal, timeout rules, and dispatch behavior
This commit is contained in:
@@ -2,6 +2,7 @@ import { create } from "zustand"
|
||||
import { generateId } from "@/lib/id"
|
||||
import { mockOrders } from "@/lib/mock"
|
||||
import type { Order, OrderStatus, PlayerService } from "@/lib/types"
|
||||
import { useWalletStore } from "@/store/wallet"
|
||||
|
||||
interface CreateOrderInput {
|
||||
consumerId: string
|
||||
@@ -22,6 +23,44 @@ interface OrderState {
|
||||
updateOrderStatus: (orderId: string, status: OrderStatus) => void
|
||||
}
|
||||
|
||||
const orderTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
|
||||
function clearOrderTimeout(orderId: string) {
|
||||
const timer = orderTimeouts.get(orderId)
|
||||
if (!timer) return
|
||||
clearTimeout(timer)
|
||||
orderTimeouts.delete(orderId)
|
||||
}
|
||||
|
||||
function scheduleOrderTimeout(orderId: string, status: OrderStatus) {
|
||||
clearOrderTimeout(orderId)
|
||||
|
||||
if (status !== "pending_accept" && status !== "pending_close") {
|
||||
return
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
const state = useOrderStore.getState()
|
||||
const order = state.orders.find((item) => item.id === orderId)
|
||||
if (!order || order.status !== status) {
|
||||
orderTimeouts.delete(orderId)
|
||||
return
|
||||
}
|
||||
|
||||
if (status === "pending_accept") {
|
||||
state.updateOrderStatus(orderId, "cancelled")
|
||||
}
|
||||
|
||||
if (status === "pending_close") {
|
||||
state.updateOrderStatus(orderId, "pending_review")
|
||||
}
|
||||
|
||||
orderTimeouts.delete(orderId)
|
||||
}, 30000)
|
||||
|
||||
orderTimeouts.set(orderId, timer)
|
||||
}
|
||||
|
||||
export const useOrderStore = create<OrderState>((set) => ({
|
||||
orders: mockOrders,
|
||||
createOrder: (input) => {
|
||||
@@ -44,6 +83,8 @@ export const useOrderStore = create<OrderState>((set) => ({
|
||||
orders: [order, ...state.orders],
|
||||
}))
|
||||
|
||||
scheduleOrderTimeout(order.id, order.status)
|
||||
|
||||
return order
|
||||
},
|
||||
updateOrderStatus: (orderId, status) =>
|
||||
@@ -60,6 +101,12 @@ export const useOrderStore = create<OrderState>((set) => ({
|
||||
status,
|
||||
acceptedAt: order.acceptedAt ?? now,
|
||||
}
|
||||
case "pending_close":
|
||||
return {
|
||||
...order,
|
||||
status,
|
||||
closedAt: order.closedAt ?? now,
|
||||
}
|
||||
case "pending_review":
|
||||
return {
|
||||
...order,
|
||||
@@ -67,6 +114,9 @@ export const useOrderStore = create<OrderState>((set) => ({
|
||||
closedAt: order.closedAt ?? now,
|
||||
}
|
||||
case "completed":
|
||||
if (order.status !== "completed") {
|
||||
useWalletStore.getState().addIncome(order.id, order.totalPrice)
|
||||
}
|
||||
return {
|
||||
...order,
|
||||
status,
|
||||
@@ -82,3 +132,18 @@ export const useOrderStore = create<OrderState>((set) => ({
|
||||
}),
|
||||
})),
|
||||
}))
|
||||
|
||||
useOrderStore.subscribe((state, prevState) => {
|
||||
state.orders.forEach((order) => {
|
||||
const prevOrder = prevState.orders.find((item) => item.id === order.id)
|
||||
if (!prevOrder || prevOrder.status !== order.status) {
|
||||
scheduleOrderTimeout(order.id, order.status)
|
||||
}
|
||||
})
|
||||
|
||||
prevState.orders.forEach((order) => {
|
||||
if (!state.orders.some((item) => item.id === order.id)) {
|
||||
clearOrderTimeout(order.id)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import { create } from "zustand"
|
||||
import { generateId } from "@/lib/id"
|
||||
import { mockReviews, mockUsers } from "@/lib/mock"
|
||||
import type { Review } from "@/lib/types"
|
||||
import { useOrderStore } from "@/store/orders"
|
||||
|
||||
interface SubmitReviewInput {
|
||||
orderId: string
|
||||
fromUserId: string
|
||||
rating: number
|
||||
content?: string
|
||||
}
|
||||
|
||||
interface ReviewState {
|
||||
reviews: Review[]
|
||||
submitReview: (input: SubmitReviewInput) => void
|
||||
getReviewsByOrder: (orderId: string) => Review[]
|
||||
hasUserReviewed: (orderId: string, userId: string) => boolean
|
||||
}
|
||||
|
||||
const autoReplyTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
|
||||
function resolveUser(userId: string) {
|
||||
return mockUsers.find((user) => user.id === userId)
|
||||
}
|
||||
|
||||
function resolveOrderUser(orderId: string, userId: string) {
|
||||
const order = useOrderStore.getState().orders.find((item) => item.id === orderId)
|
||||
if (!order) return null
|
||||
|
||||
if (order.consumerId === userId) {
|
||||
return {
|
||||
fromUserName: order.consumerName,
|
||||
toUserId: order.playerId,
|
||||
toUserName: order.playerName,
|
||||
}
|
||||
}
|
||||
|
||||
if (order.playerId === userId) {
|
||||
return {
|
||||
fromUserName: order.playerName,
|
||||
toUserId: order.consumerId,
|
||||
toUserName: order.consumerName,
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export const useReviewStore = create<ReviewState>((set, get) => ({
|
||||
reviews: mockReviews,
|
||||
getReviewsByOrder: (orderId) => get().reviews.filter((review) => review.orderId === orderId),
|
||||
hasUserReviewed: (orderId, userId) =>
|
||||
get().reviews.some((review) => review.orderId === orderId && review.fromUserId === userId),
|
||||
submitReview: (input) => {
|
||||
const relation = resolveOrderUser(input.orderId, input.fromUserId)
|
||||
if (!relation) return
|
||||
|
||||
const exists = get().hasUserReviewed(input.orderId, input.fromUserId)
|
||||
if (exists) return
|
||||
|
||||
const fromUser = resolveUser(input.fromUserId)
|
||||
const createdAt = new Date().toISOString()
|
||||
|
||||
const review: Review = {
|
||||
id: generateId("rev"),
|
||||
orderId: input.orderId,
|
||||
fromUserId: input.fromUserId,
|
||||
fromUserName: relation.fromUserName,
|
||||
fromUserAvatar: fromUser?.avatar ?? "",
|
||||
toUserId: relation.toUserId,
|
||||
rating: input.rating,
|
||||
content: input.content?.trim() || undefined,
|
||||
sealed: true,
|
||||
createdAt,
|
||||
}
|
||||
|
||||
set((state) => ({ reviews: [...state.reviews, review] }))
|
||||
|
||||
const orderReviews = get().getReviewsByOrder(input.orderId)
|
||||
if (orderReviews.length >= 2) {
|
||||
const timer = autoReplyTimers.get(input.orderId)
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
autoReplyTimers.delete(input.orderId)
|
||||
}
|
||||
|
||||
set((state) => ({
|
||||
reviews: state.reviews.map((item) =>
|
||||
item.orderId === input.orderId ? { ...item, sealed: false } : item,
|
||||
),
|
||||
}))
|
||||
useOrderStore.getState().updateOrderStatus(input.orderId, "completed")
|
||||
return
|
||||
}
|
||||
|
||||
if (autoReplyTimers.has(input.orderId)) return
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
autoReplyTimers.delete(input.orderId)
|
||||
if (get().hasUserReviewed(input.orderId, relation.toUserId)) {
|
||||
return
|
||||
}
|
||||
|
||||
get().submitReview({
|
||||
orderId: input.orderId,
|
||||
fromUserId: relation.toUserId,
|
||||
rating: 5,
|
||||
content: `收到 ${relation.toUserName} 的评价,感谢本次对局。`,
|
||||
})
|
||||
}, 3000)
|
||||
|
||||
autoReplyTimers.set(input.orderId, timer)
|
||||
},
|
||||
}))
|
||||
Reference in New Issue
Block a user