feat: persist customer product and shop favorites through the live API

Replace mall fixture favorites with customer-scoped endpoints, and send signed-out shoppers back to the page they left after sign-in.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Chengdong Zhang
2026-09-21 18:51:45 +08:00
co-authored by Cursor
parent 94a64ec712
commit 6c1357ec4d
34 changed files with 1783 additions and 119 deletions
+19
View File
@@ -13,6 +13,8 @@ import type {
CouponTemplate,
CouponTemplateInput,
Currency,
Favorite,
FavoriteKind,
FlashSaleItem,
FlashSaleItemInput,
FlashSaleSession,
@@ -126,6 +128,13 @@ export interface ConvertResult {
currency: string;
}
export interface FavoriteListQuery {
kind: FavoriteKind;
target_id?: string;
page?: number;
per_page?: number;
}
type Query = Record<string, string | number | boolean | undefined>;
async function request<T>(
@@ -212,6 +221,11 @@ export interface ApiClient {
updateAddress(id: string, address: AddressInput): Promise<AddressBookEntry>;
deleteAddress(id: string): Promise<AddressBookEntry[]>;
setDefaultAddress(id: string): Promise<AddressBookEntry>;
listFavorites(q: FavoriteListQuery): Promise<Paged<Favorite>>;
addProductFavorite(productId: string): Promise<Favorite>;
removeProductFavorite(productId: string): Promise<void>;
addShopFavorite(shopId: string): Promise<Favorite>;
removeShopFavorite(shopId: string): Promise<void>;
/** Public: coupons a shopper could claim from this shop right now. */
listShopCouponTemplates(shopId: string): Promise<CouponTemplate[]>;
listMyCoupons(): Promise<Coupon[]>;
@@ -328,6 +342,11 @@ export function createApi(opts: ApiClientOptions): ApiClient {
updateAddress: (id, address) => r("PUT", `/addresses/${id}`, address),
deleteAddress: (id) => r("DELETE", `/addresses/${id}`),
setDefaultAddress: (id) => r("POST", `/addresses/${id}/default`),
listFavorites: (q) => r("GET", "/favorites", undefined, { ...q }),
addProductFavorite: (productId) => r("PUT", `/favorites/products/${productId}`),
removeProductFavorite: (productId) => r("DELETE", `/favorites/products/${productId}`),
addShopFavorite: (shopId) => r("PUT", `/favorites/shops/${shopId}`),
removeShopFavorite: (shopId) => r("DELETE", `/favorites/shops/${shopId}`),
listShopCouponTemplates: (shopId) => r("GET", `/shops/${shopId}/coupon-templates`),
listMyCoupons: () => r("GET", "/me/coupons"),
claimCoupon: (templateId) => r("POST", "/me/coupons", { template_id: templateId }),
+31
View File
@@ -567,6 +567,37 @@ export interface ShopProfile {
score_speed: number | null;
}
/** Current public product card data nested in a product favorite. */
export interface FavoriteProductSummary {
id: string;
shop_id: string;
slug: string;
name: LocalizedText;
image: string | null;
price_minor: number | null;
currency: string | null;
}
export type FavoriteKind = "product" | "shop";
export interface ProductFavorite {
kind: "product";
id: string;
user_id: string;
created_at: string;
product: FavoriteProductSummary;
}
export interface ShopFavorite {
kind: "shop";
id: string;
user_id: string;
created_at: string;
shop: ShopProfile;
}
export type Favorite = ProductFavorite | ShopFavorite;
export interface ShopProfileInput {
logo?: string | null;
banner?: string | null;