feat(reviews): order-line reviews with merchant reply and platform moderation (add-product-reviews)
This commit is contained in:
@@ -45,6 +45,10 @@ import type {
|
||||
ProductStatus,
|
||||
PublicFlashSaleSession,
|
||||
RedeemPointsBody,
|
||||
Review,
|
||||
ReviewInput,
|
||||
ReviewSummary,
|
||||
ReviewableItem,
|
||||
FreightTemplate,
|
||||
FreightTemplateInput,
|
||||
Shipment,
|
||||
@@ -252,6 +256,10 @@ export interface ApiClient {
|
||||
reopenAftersale(id: string): Promise<Aftersale>;
|
||||
submitAftersaleReturnTracking(id: string, body: AftersaleReturnTrackingBody): Promise<Aftersale>;
|
||||
addAftersaleMessage(id: string, body: AftersaleMessageBody): Promise<AftersaleMessage>;
|
||||
listProductReviews(productId: string, page?: number): Promise<Paged<Review>>;
|
||||
getProductReviewSummary(productId: string): Promise<ReviewSummary>;
|
||||
listReviewableItems(): Promise<ReviewableItem[]>;
|
||||
createReview(body: ReviewInput): Promise<Review>;
|
||||
/** Public points catalog: published products only. */
|
||||
listPointsProducts(): Promise<IntegralProduct[]>;
|
||||
/** Server-computed per-shop shipping fees for the current cart + address. */
|
||||
@@ -317,6 +325,8 @@ export interface ApiClient {
|
||||
createFreightTemplate(body: FreightTemplateInput): Promise<FreightTemplate>;
|
||||
updateFreightTemplate(id: string, body: FreightTemplateInput): Promise<FreightTemplate>;
|
||||
deleteFreightTemplate(id: string): Promise<void>;
|
||||
listReviews(page?: number): Promise<Paged<Review>>;
|
||||
replyReview(id: string, content: LocalizedText): Promise<Review>;
|
||||
};
|
||||
admin: {
|
||||
listUsers(page?: number): Promise<Paged<User>>;
|
||||
@@ -338,6 +348,9 @@ export interface ApiClient {
|
||||
listAftersales(status?: AftersaleStatus): Promise<Aftersale[]>;
|
||||
getAftersale(id: string): Promise<AftersaleDetail>;
|
||||
arbitrateAftersale(id: string, outcome: AftersaleArbitration): Promise<Aftersale>;
|
||||
listReviews(page?: number): Promise<Paged<Review>>;
|
||||
hideReview(id: string): Promise<Review>;
|
||||
deleteReview(id: string): Promise<void>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -404,6 +417,11 @@ export function createApi(opts: ApiClientOptions): ApiClient {
|
||||
submitAftersaleReturnTracking: (id, body) =>
|
||||
r("POST", `/aftersales/${id}/return-tracking`, body),
|
||||
addAftersaleMessage: (id, body) => r("POST", `/aftersales/${id}/messages`, body),
|
||||
listProductReviews: (productId, page = 1) =>
|
||||
r("GET", `/products/${productId}/reviews`, undefined, { page }),
|
||||
getProductReviewSummary: (productId) => r("GET", `/products/${productId}/review-summary`),
|
||||
listReviewableItems: () => r("GET", "/me/reviewable"),
|
||||
createReview: (body) => r("POST", "/reviews", body),
|
||||
listPointsProducts: () => r("GET", "/points/products"),
|
||||
listMyRedemptions: () => r("GET", "/points/redemptions"),
|
||||
redeemPoints: (body) => r("POST", "/points/redemptions", body),
|
||||
@@ -460,6 +478,8 @@ export function createApi(opts: ApiClientOptions): ApiClient {
|
||||
createFreightTemplate: (body) => r("POST", "/shop/freight-templates", body),
|
||||
updateFreightTemplate: (id, body) => r("PUT", `/shop/freight-templates/${id}`, body),
|
||||
deleteFreightTemplate: (id) => r("DELETE", `/shop/freight-templates/${id}`),
|
||||
listReviews: (page = 1) => r("GET", "/shop/reviews", undefined, { page }),
|
||||
replyReview: (id, content) => r("POST", `/shop/reviews/${id}/reply`, { content }),
|
||||
},
|
||||
admin: {
|
||||
listUsers: (page = 1) => r("GET", "/admin/users", undefined, { page }),
|
||||
@@ -485,6 +505,9 @@ export function createApi(opts: ApiClientOptions): ApiClient {
|
||||
getAftersale: (id) => r("GET", `/admin/aftersales/${id}`),
|
||||
arbitrateAftersale: (id, outcome) =>
|
||||
r("POST", `/admin/aftersales/${id}/arbitrate`, { outcome }),
|
||||
listReviews: (page = 1) => r("GET", "/admin/reviews", undefined, { page }),
|
||||
hideReview: (id) => r("POST", `/admin/reviews/${id}/hide`),
|
||||
deleteReview: (id) => r("DELETE", `/admin/reviews/${id}`),
|
||||
listPointsProducts: () => r("GET", "/admin/points/products"),
|
||||
createPointsProduct: (body) => r("POST", "/admin/points/products", body),
|
||||
updatePointsProduct: (id, body) => r("PUT", `/admin/points/products/${id}`, body),
|
||||
|
||||
@@ -777,3 +777,50 @@ export interface ShippingQuote {
|
||||
shops: ShippingQuoteShop[];
|
||||
total_minor: number;
|
||||
}
|
||||
|
||||
// ---- reviews ----
|
||||
|
||||
export type ReviewStatus = "visible" | "hidden";
|
||||
|
||||
export interface Review {
|
||||
id: string;
|
||||
order_item_id: string;
|
||||
order_id: string;
|
||||
product_id: string;
|
||||
shop_id: string;
|
||||
user_id: string;
|
||||
rating: number;
|
||||
content: LocalizedText;
|
||||
images: string[];
|
||||
reply: LocalizedText | null;
|
||||
reply_at: string | null;
|
||||
status: ReviewStatus;
|
||||
created_at: string;
|
||||
reviewer_name: string;
|
||||
}
|
||||
|
||||
/** SQL-aggregated over visible reviews; distribution keys are "1".."5". */
|
||||
export interface ReviewSummary {
|
||||
count: number;
|
||||
avg_rating: number;
|
||||
distribution: Record<string, number>;
|
||||
}
|
||||
|
||||
/** A completed order line of mine that has no review yet. */
|
||||
export interface ReviewableItem {
|
||||
order_item_id: string;
|
||||
order_id: string;
|
||||
order_no: string;
|
||||
product_id: string;
|
||||
product_name: LocalizedText;
|
||||
sku_code: string;
|
||||
image: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface ReviewInput {
|
||||
order_item_id: string;
|
||||
rating: number;
|
||||
content: LocalizedText;
|
||||
images?: string[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user