refactor: remove unused dispute store and orphaned domain stores
Delete store/disputes.ts and 8 other stores that are no longer referenced by any page, component, or other store after the order and wallet rewrites. All pages now use lib/api directly. Also delete lib/domain/income.ts, order-machine.ts, and resolve-current-shop.ts which were only used by these stores.
This commit is contained in:
-104
@@ -1,104 +0,0 @@
|
|||||||
import { generateId } from "@/lib/id"
|
|
||||||
import type { ChatMessage, ChatSession, Order } from "@/lib/types"
|
|
||||||
import { create } from "zustand"
|
|
||||||
|
|
||||||
interface ChatState {
|
|
||||||
sessions: ChatSession[]
|
|
||||||
messages: ChatMessage[]
|
|
||||||
ensureOrderSession: (order: Order) => ChatSession
|
|
||||||
sendTextMessage: (sessionId: string, actorId: string, content: string) => void
|
|
||||||
sendImageMessage: (sessionId: string, actorId: string, imageUrl: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveAvatar(_userId: string) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useChatStore = create<ChatState>((set, get) => ({
|
|
||||||
sessions: [],
|
|
||||||
messages: [],
|
|
||||||
ensureOrderSession: (order) => {
|
|
||||||
const existing = get().sessions.find(
|
|
||||||
(session) => session.type === "order" && session.orderId === order.id,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (existing) {
|
|
||||||
return existing
|
|
||||||
}
|
|
||||||
|
|
||||||
const session: ChatSession = {
|
|
||||||
id: `chat-${order.id}`,
|
|
||||||
type: "order",
|
|
||||||
orderId: order.id,
|
|
||||||
participants: [
|
|
||||||
{
|
|
||||||
id: order.consumerId,
|
|
||||||
nickname: "",
|
|
||||||
avatar: resolveAvatar(order.consumerId),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: order.playerId,
|
|
||||||
nickname: "",
|
|
||||||
avatar: resolveAvatar(order.playerId),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
unreadCount: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
set((state) => ({
|
|
||||||
sessions: [session, ...state.sessions],
|
|
||||||
}))
|
|
||||||
|
|
||||||
return session
|
|
||||||
},
|
|
||||||
sendTextMessage: (sessionId, actorId, content) => {
|
|
||||||
const text = content.trim()
|
|
||||||
if (!text) return
|
|
||||||
const session = get().sessions.find((item) => item.id === sessionId)
|
|
||||||
if (!session) return
|
|
||||||
|
|
||||||
const sender = session.participants.find((participant) => participant.id === actorId)
|
|
||||||
if (!sender) return
|
|
||||||
|
|
||||||
const now = new Date().toISOString()
|
|
||||||
const message: ChatMessage = {
|
|
||||||
id: generateId("msg"),
|
|
||||||
sessionId,
|
|
||||||
senderId: actorId,
|
|
||||||
type: "text",
|
|
||||||
content: text,
|
|
||||||
createdAt: now,
|
|
||||||
}
|
|
||||||
|
|
||||||
set((state) => ({
|
|
||||||
messages: [...state.messages, message],
|
|
||||||
sessions: state.sessions.map((s) => (s.id === sessionId ? { ...s, lastMessage: text } : s)),
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
sendImageMessage: (sessionId, actorId, imageUrl) => {
|
|
||||||
const content = imageUrl.trim()
|
|
||||||
if (!content) return
|
|
||||||
const session = get().sessions.find((item) => item.id === sessionId)
|
|
||||||
if (!session) return
|
|
||||||
|
|
||||||
const sender = session.participants.find((participant) => participant.id === actorId)
|
|
||||||
if (!sender) return
|
|
||||||
|
|
||||||
const now = new Date().toISOString()
|
|
||||||
const message: ChatMessage = {
|
|
||||||
id: generateId("msg"),
|
|
||||||
sessionId,
|
|
||||||
senderId: actorId,
|
|
||||||
type: "image",
|
|
||||||
content,
|
|
||||||
createdAt: now,
|
|
||||||
}
|
|
||||||
|
|
||||||
set((state) => ({
|
|
||||||
messages: [...state.messages, message],
|
|
||||||
sessions: state.sessions.map((item) =>
|
|
||||||
item.id === sessionId ? { ...item, lastMessage: "[图片]" } : item,
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { generateId } from "@/lib/id"
|
|
||||||
import type { Comment, User } from "@/lib/types"
|
|
||||||
import { create } from "zustand"
|
|
||||||
|
|
||||||
interface CommentState {
|
|
||||||
comments: Comment[]
|
|
||||||
addComment: (postId: string, author: User, content: string) => Comment | null
|
|
||||||
toggleCommentLike: (commentId: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useCommentStore = create<CommentState>((set) => ({
|
|
||||||
comments: [],
|
|
||||||
addComment: (postId, author, content) => {
|
|
||||||
const normalizedContent = content.trim()
|
|
||||||
if (!normalizedContent) return null
|
|
||||||
|
|
||||||
const comment: Comment = {
|
|
||||||
id: generateId("comment"),
|
|
||||||
author,
|
|
||||||
content: normalizedContent,
|
|
||||||
likeCount: 0,
|
|
||||||
liked: false,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
}
|
|
||||||
|
|
||||||
set((state) => ({
|
|
||||||
comments: [...state.comments, comment],
|
|
||||||
}))
|
|
||||||
|
|
||||||
return comment
|
|
||||||
},
|
|
||||||
toggleCommentLike: (commentId) => {
|
|
||||||
set((state) => ({
|
|
||||||
comments: state.comments.map((comment) => {
|
|
||||||
if (comment.id !== commentId) return comment
|
|
||||||
const liked = !comment.liked
|
|
||||||
return {
|
|
||||||
...comment,
|
|
||||||
liked,
|
|
||||||
likeCount: liked ? comment.likeCount + 1 : Math.max(0, comment.likeCount - 1),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
import type { Actor } from "@/lib/actor"
|
|
||||||
import { allow, deny } from "@/lib/decision"
|
|
||||||
import type { ApiDecision } from "@/lib/errors"
|
|
||||||
import { generateId } from "@/lib/id"
|
|
||||||
import type { Dispute } from "@/lib/types"
|
|
||||||
import { useAuthStore } from "@/store/auth"
|
|
||||||
import { useNotificationStore } from "@/store/notifications"
|
|
||||||
import { useOrderStore } from "@/store/orders"
|
|
||||||
import { create } from "zustand"
|
|
||||||
|
|
||||||
type DisputeTimelineType = "created" | "response" | "reviewing" | "resolved" | "appealed"
|
|
||||||
|
|
||||||
interface DisputeTimelineItem {
|
|
||||||
id: string
|
|
||||||
type: DisputeTimelineType
|
|
||||||
content: string
|
|
||||||
createdAt: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DisputeRecord extends Dispute {
|
|
||||||
respondentReason?: string
|
|
||||||
respondentEvidence: string[]
|
|
||||||
appealReason?: string
|
|
||||||
appealedAt?: string
|
|
||||||
timeline: DisputeTimelineItem[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SubmitDisputeInput {
|
|
||||||
orderId: string
|
|
||||||
reason: string
|
|
||||||
evidence: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DisputeMutationResult {
|
|
||||||
decision: ApiDecision
|
|
||||||
dispute?: DisputeRecord
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DisputeState {
|
|
||||||
disputes: DisputeRecord[]
|
|
||||||
getDisputeByOrderId: (orderId: string) => DisputeRecord | undefined
|
|
||||||
submitDispute: (input: SubmitDisputeInput) => DisputeMutationResult
|
|
||||||
submitResponse: (
|
|
||||||
disputeId: string,
|
|
||||||
actorId: string,
|
|
||||||
reason: string,
|
|
||||||
evidence: string[],
|
|
||||||
) => ApiDecision
|
|
||||||
submitAppeal: (disputeId: string, actorId: string, reason: string) => ApiDecision
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveParticipantActor(orderId: string, userId: string): Actor | null {
|
|
||||||
const order = useOrderStore.getState().orders.find((item) => item.id === orderId)
|
|
||||||
if (!order) return null
|
|
||||||
|
|
||||||
if (order.consumerId === userId) {
|
|
||||||
return {
|
|
||||||
userId,
|
|
||||||
role: "consumer",
|
|
||||||
shopId: order.shopId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (order.playerId === userId) {
|
|
||||||
return {
|
|
||||||
userId,
|
|
||||||
role: "player",
|
|
||||||
shopId: order.shopId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
function notifyDispute(orderId: string, title: string, content: string) {
|
|
||||||
if (!useAuthStore.getState().notificationPrefs.order) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
useNotificationStore.getState().addNotification({
|
|
||||||
type: "order",
|
|
||||||
title,
|
|
||||||
content,
|
|
||||||
link: `/dispute/${orderId}`,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useDisputeStore = create<DisputeState>((set, get) => {
|
|
||||||
return {
|
|
||||||
disputes: [],
|
|
||||||
getDisputeByOrderId: (orderId) => get().disputes.find((dispute) => dispute.orderId === orderId),
|
|
||||||
submitDispute: (input) => {
|
|
||||||
const order = useOrderStore.getState().orders.find((item) => item.id === input.orderId)
|
|
||||||
if (!order) {
|
|
||||||
return { decision: deny(404, "订单不存在") }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (order.status !== "in_progress" && order.status !== "pending_close") {
|
|
||||||
return { decision: deny(400, "当前阶段不可发起争议") }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!input.reason.trim()) {
|
|
||||||
return { decision: deny(400, "请填写争议原因") }
|
|
||||||
}
|
|
||||||
|
|
||||||
const actor = resolveParticipantActor(input.orderId, useAuthStore.getState().user?.id ?? "")
|
|
||||||
if (!actor) {
|
|
||||||
return { decision: deny(403, "仅订单参与方可发起争议") }
|
|
||||||
}
|
|
||||||
|
|
||||||
const createdAt = new Date().toISOString()
|
|
||||||
const dispute: DisputeRecord = {
|
|
||||||
id: generateId("disp"),
|
|
||||||
orderId: input.orderId,
|
|
||||||
reason: input.reason.trim(),
|
|
||||||
evidence: input.evidence,
|
|
||||||
status: "open",
|
|
||||||
createdAt,
|
|
||||||
respondentEvidence: [],
|
|
||||||
timeline: [
|
|
||||||
{
|
|
||||||
id: generateId("timeline"),
|
|
||||||
type: "created",
|
|
||||||
content: "争议已提交",
|
|
||||||
createdAt,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
set((state) => ({ disputes: [dispute, ...state.disputes] }))
|
|
||||||
return { decision: allow(), dispute }
|
|
||||||
},
|
|
||||||
submitResponse: (disputeId, actorId, reason, evidence) => {
|
|
||||||
const dispute = get().disputes.find((item) => item.id === disputeId)
|
|
||||||
if (!dispute) {
|
|
||||||
return deny(404, "争议不存在")
|
|
||||||
}
|
|
||||||
|
|
||||||
const order = useOrderStore.getState().orders.find((item) => item.id === dispute.orderId)
|
|
||||||
if (!order) {
|
|
||||||
return deny(404, "关联订单不存在")
|
|
||||||
}
|
|
||||||
|
|
||||||
const actor = resolveParticipantActor(dispute.orderId, actorId)
|
|
||||||
if (!actor) {
|
|
||||||
return deny(403, "仅订单参与方可提交回应")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dispute.respondentReason) {
|
|
||||||
return deny(400, "回应已提交")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dispute.status !== "open" && dispute.status !== "reviewing") {
|
|
||||||
return deny(400, "当前状态不可提交回应")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!reason.trim()) {
|
|
||||||
return deny(400, "请填写回应理由")
|
|
||||||
}
|
|
||||||
|
|
||||||
set((state) => ({
|
|
||||||
disputes: state.disputes.map((item) => {
|
|
||||||
if (item.id !== disputeId) return item
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
respondentReason: reason.trim(),
|
|
||||||
respondentEvidence: evidence,
|
|
||||||
timeline: [
|
|
||||||
...item.timeline,
|
|
||||||
{
|
|
||||||
id: generateId("timeline"),
|
|
||||||
type: "response",
|
|
||||||
content: "对方已提交回应材料",
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
notifyDispute(dispute.orderId, "争议收到回应", "对方已提交争议回应材料")
|
|
||||||
|
|
||||||
return allow()
|
|
||||||
},
|
|
||||||
submitAppeal: (disputeId, actorId, reason) => {
|
|
||||||
const dispute = get().disputes.find((item) => item.id === disputeId)
|
|
||||||
if (!dispute) {
|
|
||||||
return deny(404, "争议不存在")
|
|
||||||
}
|
|
||||||
|
|
||||||
const actor = resolveParticipantActor(dispute.orderId, actorId)
|
|
||||||
if (!actor) {
|
|
||||||
return deny(403, "仅订单参与方可提交申诉")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dispute.status !== "resolved") {
|
|
||||||
return deny(400, "当前状态不可申诉")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dispute.appealedAt) {
|
|
||||||
return deny(400, "该争议已申诉过")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!reason.trim()) {
|
|
||||||
return deny(400, "请填写申诉理由")
|
|
||||||
}
|
|
||||||
|
|
||||||
set((state) => ({
|
|
||||||
disputes: state.disputes.map((item) => {
|
|
||||||
if (item.id !== disputeId) return item
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
status: "appealed",
|
|
||||||
appealReason: reason.trim(),
|
|
||||||
appealedAt: new Date().toISOString(),
|
|
||||||
timeline: [
|
|
||||||
...item.timeline,
|
|
||||||
{
|
|
||||||
id: generateId("timeline"),
|
|
||||||
type: "appealed",
|
|
||||||
content: "已提交申诉,平台将复核",
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
notifyDispute(dispute.orderId, "争议已申诉", "申诉已提交,平台将继续复核")
|
|
||||||
|
|
||||||
return allow()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import { generateId } from "@/lib/id"
|
|
||||||
import type { Favorite } from "@/lib/types"
|
|
||||||
import { create } from "zustand"
|
|
||||||
|
|
||||||
interface FavoriteState {
|
|
||||||
favorites: Favorite[]
|
|
||||||
listFavoritesByUser: (userId: string) => Favorite[]
|
|
||||||
isFavorited: (userId: string, targetType: "player" | "shop", targetId: string) => boolean
|
|
||||||
toggleFavorite: (userId: string, targetType: "player" | "shop", targetId: string) => boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useFavoriteStore = create<FavoriteState>((set, get) => ({
|
|
||||||
favorites: [],
|
|
||||||
listFavoritesByUser: (userId) => get().favorites.filter((favorite) => favorite.userId === userId),
|
|
||||||
isFavorited: (userId, targetType, targetId) =>
|
|
||||||
get().favorites.some(
|
|
||||||
(favorite) =>
|
|
||||||
favorite.userId === userId &&
|
|
||||||
favorite.targetType === targetType &&
|
|
||||||
favorite.targetId === targetId,
|
|
||||||
),
|
|
||||||
toggleFavorite: (userId, targetType, targetId) => {
|
|
||||||
const state = get()
|
|
||||||
const existing = state.favorites.find(
|
|
||||||
(favorite) =>
|
|
||||||
favorite.userId === userId &&
|
|
||||||
favorite.targetType === targetType &&
|
|
||||||
favorite.targetId === targetId,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (existing) {
|
|
||||||
set((prev) => ({
|
|
||||||
favorites: prev.favorites.filter((favorite) => favorite.id !== existing.id),
|
|
||||||
}))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const next: Favorite = {
|
|
||||||
id: generateId("fav"),
|
|
||||||
userId,
|
|
||||||
targetType,
|
|
||||||
targetId,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
}
|
|
||||||
|
|
||||||
set((prev) => ({
|
|
||||||
favorites: [next, ...prev.favorites],
|
|
||||||
}))
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import type { Player } from "@/lib/types"
|
|
||||||
import { create } from "zustand"
|
|
||||||
|
|
||||||
type PlayerStatus = Player["status"]
|
|
||||||
|
|
||||||
interface PlayerStatusState {
|
|
||||||
statuses: Record<string, PlayerStatus>
|
|
||||||
setStatus: (playerId: string, status: PlayerStatus) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const usePlayerStatusStore = create<PlayerStatusState>((set) => ({
|
|
||||||
statuses: {
|
|
||||||
u2: "available",
|
|
||||||
u4: "busy",
|
|
||||||
u5: "available",
|
|
||||||
},
|
|
||||||
setStatus: (playerId, status) =>
|
|
||||||
set((state) => ({
|
|
||||||
statuses: { ...state.statuses, [playerId]: status },
|
|
||||||
})),
|
|
||||||
}))
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import type { Player } from "@/lib/types"
|
|
||||||
import { create } from "zustand"
|
|
||||||
|
|
||||||
interface PlayerState {
|
|
||||||
players: Player[]
|
|
||||||
assignToShop: (playerId: string, shopId: string, shopName: string) => void
|
|
||||||
removeFromShop: (playerId: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const usePlayerStore = create<PlayerState>((set) => ({
|
|
||||||
players: [],
|
|
||||||
assignToShop: (playerId, shopId, shopName) =>
|
|
||||||
set((state) => ({
|
|
||||||
players: state.players.map((player) =>
|
|
||||||
player.id === playerId ? { ...player, shopId, shopName } : player,
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
removeFromShop: (playerId) =>
|
|
||||||
set((state) => ({
|
|
||||||
players: state.players.map((player) =>
|
|
||||||
player.id === playerId ? { ...player, shopId: undefined, shopName: undefined } : player,
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
}))
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import { generateId } from "@/lib/id"
|
|
||||||
import type { Post, User } from "@/lib/types"
|
|
||||||
import { create } from "zustand"
|
|
||||||
|
|
||||||
interface CreatePostInput {
|
|
||||||
author: User
|
|
||||||
title: string
|
|
||||||
content: string
|
|
||||||
images: string[]
|
|
||||||
tags: string[]
|
|
||||||
linkedOrderId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PostState {
|
|
||||||
posts: Post[]
|
|
||||||
createPost: (input: CreatePostInput) => Post
|
|
||||||
togglePostLike: (postId: string) => void
|
|
||||||
incrementCommentCount: (postId: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const usePostStore = create<PostState>((set) => ({
|
|
||||||
posts: [],
|
|
||||||
createPost: (input) => {
|
|
||||||
const post: Post = {
|
|
||||||
id: generateId("post"),
|
|
||||||
author: input.author,
|
|
||||||
title: input.title.trim(),
|
|
||||||
content: input.content.trim(),
|
|
||||||
images: input.images,
|
|
||||||
tags: input.tags,
|
|
||||||
linkedOrderId: input.linkedOrderId ? Number(input.linkedOrderId) : undefined,
|
|
||||||
pinned: false,
|
|
||||||
likeCount: 0,
|
|
||||||
commentCount: 0,
|
|
||||||
liked: false,
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
}
|
|
||||||
|
|
||||||
set((state) => ({
|
|
||||||
posts: [post, ...state.posts],
|
|
||||||
}))
|
|
||||||
|
|
||||||
return post
|
|
||||||
},
|
|
||||||
togglePostLike: (postId) =>
|
|
||||||
set((state) => ({
|
|
||||||
posts: state.posts.map((post) => {
|
|
||||||
if (post.id !== postId) return post
|
|
||||||
const liked = !post.liked
|
|
||||||
return {
|
|
||||||
...post,
|
|
||||||
liked,
|
|
||||||
likeCount: liked ? post.likeCount + 1 : Math.max(0, post.likeCount - 1),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
})),
|
|
||||||
incrementCommentCount: (postId) =>
|
|
||||||
set((state) => ({
|
|
||||||
posts: state.posts.map((post) =>
|
|
||||||
post.id === postId ? { ...post, commentCount: post.commentCount + 1 } : post,
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
}))
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { allow, deny } from "@/lib/decision"
|
|
||||||
import type { ApiDecision } from "@/lib/errors"
|
|
||||||
import { generateId } from "@/lib/id"
|
|
||||||
import type { Review } from "@/lib/types"
|
|
||||||
import { create } from "zustand"
|
|
||||||
|
|
||||||
interface SubmitReviewInput {
|
|
||||||
orderId: string
|
|
||||||
fromUserId: string
|
|
||||||
rating: number
|
|
||||||
content?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ReviewState {
|
|
||||||
reviews: Review[]
|
|
||||||
submitReview: (input: SubmitReviewInput) => ApiDecision
|
|
||||||
getReviewsByOrder: (orderId: string) => Review[]
|
|
||||||
hasUserReviewed: (orderId: string, userId: string) => boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useReviewStore = create<ReviewState>((set, get) => ({
|
|
||||||
reviews: [],
|
|
||||||
getReviewsByOrder: (orderId) => get().reviews.filter((review) => review.orderId === orderId),
|
|
||||||
hasUserReviewed: (orderId, userId) =>
|
|
||||||
get().reviews.some((review) => review.orderId === orderId && review.fromUserId === userId),
|
|
||||||
submitReview: (input) => {
|
|
||||||
if (!input.fromUserId) {
|
|
||||||
return deny(401, "请先登录")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Number.isFinite(input.rating) || input.rating < 1 || input.rating > 5) {
|
|
||||||
return deny(400, "评分范围应为 1-5")
|
|
||||||
}
|
|
||||||
|
|
||||||
const exists = get().hasUserReviewed(input.orderId, input.fromUserId)
|
|
||||||
if (exists) {
|
|
||||||
return deny(400, "该订单已提交过评价")
|
|
||||||
}
|
|
||||||
|
|
||||||
const createdAt = new Date().toISOString()
|
|
||||||
|
|
||||||
const review: Review = {
|
|
||||||
id: generateId("rev"),
|
|
||||||
orderId: input.orderId,
|
|
||||||
fromUserId: input.fromUserId,
|
|
||||||
fromUserName: "",
|
|
||||||
rating: input.rating,
|
|
||||||
content: input.content?.trim() ?? "",
|
|
||||||
sealed: true,
|
|
||||||
createdAt,
|
|
||||||
}
|
|
||||||
|
|
||||||
set((state) => ({ reviews: [...state.reviews, review] }))
|
|
||||||
|
|
||||||
const orderReviews = get().getReviewsByOrder(input.orderId)
|
|
||||||
if (orderReviews.length >= 2) {
|
|
||||||
set((state) => ({
|
|
||||||
reviews: state.reviews.map((item) =>
|
|
||||||
item.orderId === input.orderId ? { ...item, sealed: false } : item,
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return allow()
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { generateId } from "@/lib/id"
|
|
||||||
import type { PlayerService } from "@/lib/types"
|
|
||||||
import { create } from "zustand"
|
|
||||||
|
|
||||||
interface ServiceState {
|
|
||||||
services: PlayerService[]
|
|
||||||
createService: (service: Omit<PlayerService, "id">) => void
|
|
||||||
updateService: (serviceId: string, patch: Partial<Omit<PlayerService, "id">>) => void
|
|
||||||
deleteService: (serviceId: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useServiceStore = create<ServiceState>((set) => ({
|
|
||||||
services: [],
|
|
||||||
createService: (service) =>
|
|
||||||
set((state) => ({
|
|
||||||
services: [
|
|
||||||
...state.services,
|
|
||||||
{
|
|
||||||
...service,
|
|
||||||
id: generateId("svc"),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})),
|
|
||||||
updateService: (serviceId, patch) =>
|
|
||||||
set((state) => ({
|
|
||||||
services: state.services.map((service) =>
|
|
||||||
service.id === serviceId ? { ...service, ...patch } : service,
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
deleteService: (serviceId) =>
|
|
||||||
set((state) => ({
|
|
||||||
services: state.services.filter((service) => service.id !== serviceId),
|
|
||||||
})),
|
|
||||||
}))
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import type { Shop, ShopSection } from "@/lib/types"
|
|
||||||
import { create } from "zustand"
|
|
||||||
|
|
||||||
interface ShopState {
|
|
||||||
shops: Shop[]
|
|
||||||
updateShop: (shopId: string, patch: Partial<Omit<Shop, "id" | "owner">>) => void
|
|
||||||
updateTemplateSections: (shopId: string, sections: ShopSection[]) => void
|
|
||||||
updateAnnouncement: (shopId: string, index: number, announcement: string) => void
|
|
||||||
addAnnouncement: (shopId: string, announcement: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useShopStore = create<ShopState>((set) => ({
|
|
||||||
shops: [],
|
|
||||||
updateShop: (shopId, patch) =>
|
|
||||||
set((state) => ({
|
|
||||||
shops: state.shops.map((shop) => (shop.id === shopId ? { ...shop, ...patch } : shop)),
|
|
||||||
})),
|
|
||||||
updateTemplateSections: (shopId, sections) =>
|
|
||||||
set((state) => ({
|
|
||||||
shops: state.shops.map((shop) =>
|
|
||||||
shop.id === shopId
|
|
||||||
? {
|
|
||||||
...shop,
|
|
||||||
templateConfig: {
|
|
||||||
...shop.templateConfig,
|
|
||||||
sections,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: shop,
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
updateAnnouncement: (shopId, index, announcement) =>
|
|
||||||
set((state) => ({
|
|
||||||
shops: state.shops.map((shop) =>
|
|
||||||
shop.id === shopId
|
|
||||||
? {
|
|
||||||
...shop,
|
|
||||||
announcements: shop.announcements.map((item, currentIndex) =>
|
|
||||||
currentIndex === index ? announcement : item,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
: shop,
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
addAnnouncement: (shopId, announcement) =>
|
|
||||||
set((state) => ({
|
|
||||||
shops: state.shops.map((shop) =>
|
|
||||||
shop.id === shopId
|
|
||||||
? {
|
|
||||||
...shop,
|
|
||||||
announcements: [...shop.announcements, announcement],
|
|
||||||
}
|
|
||||||
: shop,
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
}))
|
|
||||||
Reference in New Issue
Block a user