feat(disputes): migrate disputes and reviews to backend API
This commit is contained in:
+85
-25
@@ -1,29 +1,89 @@
|
||||
import { deny } from "@/lib/decision"
|
||||
import { useAuthStore } from "@/store/auth"
|
||||
import { useReviewStore } from "@/store/reviews"
|
||||
import { allow, deny } from "@/lib/decision"
|
||||
import { toApiError, type ApiDecision } from "@/lib/errors"
|
||||
import type { Review } from "@/lib/types"
|
||||
|
||||
export function listReviews() {
|
||||
return useReviewStore.getState().reviews
|
||||
}
|
||||
import { httpJson } from "./http"
|
||||
|
||||
export function listReviewsByOrder(orderId: string) {
|
||||
return useReviewStore.getState().reviews.filter((review) => review.orderId === orderId)
|
||||
}
|
||||
|
||||
export function listReviewsByTargetUser(userId: string) {
|
||||
return useReviewStore.getState().reviews.filter((review) => review.toUserId === userId)
|
||||
}
|
||||
|
||||
export function submitReview(input: { orderId: string; rating: number; content?: string }) {
|
||||
const userId = useAuthStore.getState().user?.id
|
||||
if (!userId) {
|
||||
return deny(401, "请先登录")
|
||||
type Paginated<T> = {
|
||||
items: T[]
|
||||
meta: {
|
||||
total: number
|
||||
offset: number
|
||||
limit: number
|
||||
}
|
||||
}
|
||||
|
||||
export type ListReviewsOptions = {
|
||||
offset?: number
|
||||
limit?: number
|
||||
}
|
||||
|
||||
function withOffsetLimit(path: string, options?: ListReviewsOptions): string {
|
||||
const offset = options?.offset ?? 0
|
||||
const limit = options?.limit ?? 1000
|
||||
|
||||
const searchParams = new URLSearchParams({
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
})
|
||||
return `${path}?${searchParams.toString()}`
|
||||
}
|
||||
|
||||
function unwrapItems<T>(value: unknown): T[] {
|
||||
if (Array.isArray(value)) return value as T[]
|
||||
if (typeof value !== "object" || value === null) {
|
||||
throw new Error("Invalid response")
|
||||
}
|
||||
if ("items" in value) {
|
||||
const envelope = value as { items?: unknown }
|
||||
if (Array.isArray(envelope.items)) return envelope.items as T[]
|
||||
}
|
||||
throw new Error("Invalid response")
|
||||
}
|
||||
|
||||
export async function listReviews(options?: ListReviewsOptions): Promise<Review[]> {
|
||||
const res = await httpJson<Paginated<Review> | Review[]>(
|
||||
withOffsetLimit("/api/v1/reviews", options),
|
||||
{
|
||||
cache: "no-store",
|
||||
},
|
||||
)
|
||||
return unwrapItems<Review>(res)
|
||||
}
|
||||
|
||||
export async function listReviewsByOrder(orderId: string): Promise<Review[]> {
|
||||
const res = await httpJson<Paginated<Review> | Review[]>(
|
||||
`/api/v1/orders/${encodeURIComponent(orderId)}/reviews`,
|
||||
{ cache: "no-store" },
|
||||
)
|
||||
return unwrapItems<Review>(res)
|
||||
}
|
||||
|
||||
export async function listReviewsByTargetUser(userId: string): Promise<Review[]> {
|
||||
const res = await httpJson<Paginated<Review> | Review[]>(
|
||||
`/api/v1/users/${encodeURIComponent(userId)}/reviews`,
|
||||
{ cache: "no-store" },
|
||||
)
|
||||
return unwrapItems<Review>(res)
|
||||
}
|
||||
|
||||
export async function submitReview(input: {
|
||||
orderId: string
|
||||
rating: number
|
||||
content?: string
|
||||
}): Promise<ApiDecision> {
|
||||
try {
|
||||
await httpJson<unknown>(`/api/v1/orders/${encodeURIComponent(input.orderId)}/review`, {
|
||||
method: "POST",
|
||||
cache: "no-store",
|
||||
json: { rating: input.rating, content: input.content },
|
||||
})
|
||||
return allow()
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === "UNAUTHORIZED") {
|
||||
return deny(401, "请先登录")
|
||||
}
|
||||
const apiError = toApiError(error)
|
||||
return deny(apiError.code, apiError.msg)
|
||||
}
|
||||
|
||||
return useReviewStore.getState().submitReview({
|
||||
orderId: input.orderId,
|
||||
fromUserId: userId,
|
||||
rating: input.rating,
|
||||
content: input.content,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user