feat(mall): live flash-sale page and activity price snapshots
The flash-sale page loads active sessions and their items through the shared client with live sale price, remaining activity stock, sell-through, and a countdown, and adds the SKU to the existing cart so checkout resolves the authoritative price. Payment and order detail tag and render the snapshotted activity unit price. The flash-sale fixtures leave the page; the fixed-data adapter still serves the domain as the rollback path.
This commit is contained in:
@@ -13,6 +13,10 @@ import type {
|
||||
CouponTemplate,
|
||||
CouponTemplateInput,
|
||||
Currency,
|
||||
FlashSaleItem,
|
||||
FlashSaleItemInput,
|
||||
FlashSaleSession,
|
||||
FlashSaleSessionInput,
|
||||
HomeContent,
|
||||
IntegralOrder,
|
||||
IntegralProduct,
|
||||
@@ -25,9 +29,11 @@ import type {
|
||||
Paged,
|
||||
Product,
|
||||
ProductStatus,
|
||||
PublicFlashSaleSession,
|
||||
RedeemPointsBody,
|
||||
Shipment,
|
||||
Shop,
|
||||
ShopFlashSaleSession,
|
||||
ShopProfile,
|
||||
ShopProfileInput,
|
||||
Sku,
|
||||
@@ -208,6 +214,8 @@ export interface ApiClient {
|
||||
listPointsProducts(): Promise<IntegralProduct[]>;
|
||||
listMyRedemptions(): Promise<IntegralOrder[]>;
|
||||
redeemPoints(body: RedeemPointsBody): Promise<IntegralOrder>;
|
||||
/** Public: active flash-sale sessions with their purchasable items. */
|
||||
listFlashSales(): Promise<PublicFlashSaleSession[]>;
|
||||
shop: {
|
||||
getMyShop(): Promise<Shop>;
|
||||
listMyProducts(q?: ShopProductQuery): Promise<Paged<Product>>;
|
||||
@@ -233,6 +241,13 @@ export interface ApiClient {
|
||||
createCouponTemplate(body: CouponTemplateInput): Promise<CouponTemplate>;
|
||||
updateCouponTemplate(id: string, body: CouponTemplateInput): Promise<CouponTemplate>;
|
||||
deleteCouponTemplate(id: string): Promise<void>;
|
||||
listFlashSales(): Promise<ShopFlashSaleSession[]>;
|
||||
createFlashSale(body: FlashSaleSessionInput): Promise<FlashSaleSession>;
|
||||
updateFlashSale(id: string, body: FlashSaleSessionInput): Promise<FlashSaleSession>;
|
||||
deleteFlashSale(id: string): Promise<void>;
|
||||
addFlashSaleItem(sessionId: string, body: FlashSaleItemInput): Promise<FlashSaleItem>;
|
||||
updateFlashSaleItem(id: string, body: FlashSaleItemInput): Promise<FlashSaleItem>;
|
||||
deleteFlashSaleItem(id: string): Promise<void>;
|
||||
};
|
||||
admin: {
|
||||
listUsers(page?: number): Promise<Paged<User>>;
|
||||
@@ -303,6 +318,7 @@ export function createApi(opts: ApiClientOptions): ApiClient {
|
||||
listPointsProducts: () => r("GET", "/points/products"),
|
||||
listMyRedemptions: () => r("GET", "/points/redemptions"),
|
||||
redeemPoints: (body) => r("POST", "/points/redemptions", body),
|
||||
listFlashSales: () => r("GET", "/flash-sales"),
|
||||
shop: {
|
||||
getMyShop: () => r("GET", "/shop/profile"),
|
||||
listMyProducts: (q = {}) => r("GET", "/shop/products", undefined, { ...q }),
|
||||
@@ -328,6 +344,14 @@ export function createApi(opts: ApiClientOptions): ApiClient {
|
||||
createCouponTemplate: (body) => r("POST", "/shop/coupon-templates", body),
|
||||
updateCouponTemplate: (id, body) => r("PUT", `/shop/coupon-templates/${id}`, body),
|
||||
deleteCouponTemplate: (id) => r("DELETE", `/shop/coupon-templates/${id}`),
|
||||
listFlashSales: () => r("GET", "/shop/flash-sales"),
|
||||
createFlashSale: (body) => r("POST", "/shop/flash-sales", body),
|
||||
updateFlashSale: (id, body) => r("PUT", `/shop/flash-sales/${id}`, body),
|
||||
deleteFlashSale: (id) => r("DELETE", `/shop/flash-sales/${id}`),
|
||||
addFlashSaleItem: (sessionId, body) =>
|
||||
r("POST", `/shop/flash-sales/${sessionId}/items`, body),
|
||||
updateFlashSaleItem: (id, body) => r("PUT", `/shop/flash-sale-items/${id}`, body),
|
||||
deleteFlashSaleItem: (id) => r("DELETE", `/shop/flash-sale-items/${id}`),
|
||||
},
|
||||
admin: {
|
||||
listUsers: (page = 1) => r("GET", "/admin/users", undefined, { page }),
|
||||
|
||||
@@ -145,6 +145,8 @@ export interface OrderItem {
|
||||
unit_price_minor: number;
|
||||
qty: number;
|
||||
image: string | null;
|
||||
/** Set when this line received flash-sale pricing. */
|
||||
flash_sale_item_id: string | null;
|
||||
}
|
||||
|
||||
export interface Order {
|
||||
@@ -310,6 +312,92 @@ export interface RedeemPointsBody {
|
||||
shipping_address: Address;
|
||||
}
|
||||
|
||||
// ---- flash sales ----
|
||||
|
||||
/** Shop-owned timed session. */
|
||||
export interface FlashSaleSession {
|
||||
id: string;
|
||||
shop_id: string;
|
||||
label: LocalizedText;
|
||||
starts_at: string;
|
||||
ends_at: string;
|
||||
enabled: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface FlashSaleSessionInput {
|
||||
label: LocalizedText;
|
||||
starts_at: string;
|
||||
ends_at: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface FlashSaleItemInput {
|
||||
sku_id: string;
|
||||
sale_price_minor: number;
|
||||
currency: string;
|
||||
reserved_stock: number;
|
||||
per_customer_limit: number;
|
||||
}
|
||||
|
||||
export interface FlashSaleItem {
|
||||
id: string;
|
||||
session_id: string;
|
||||
sku_id: string;
|
||||
sale_price_minor: number;
|
||||
currency: string;
|
||||
/** Remaining activity inventory. */
|
||||
reserved_stock: number;
|
||||
sold_count: number;
|
||||
per_customer_limit: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
/** Shop-side item joined with its SKU and catalog price. */
|
||||
export interface ShopFlashSaleItem {
|
||||
id: string;
|
||||
session_id: string;
|
||||
sku_id: string;
|
||||
sku_code: string;
|
||||
product_name: LocalizedText;
|
||||
sale_price_minor: number;
|
||||
currency: string;
|
||||
original_price_minor: number;
|
||||
original_currency: string;
|
||||
reserved_stock: number;
|
||||
sold_count: number;
|
||||
per_customer_limit: number;
|
||||
}
|
||||
|
||||
export interface ShopFlashSaleSession extends FlashSaleSession {
|
||||
items: ShopFlashSaleItem[];
|
||||
}
|
||||
|
||||
/** Public discovery item carrying everything needed to add it to the cart. */
|
||||
export interface PublicFlashSaleItem {
|
||||
id: string;
|
||||
session_id: string;
|
||||
sku_id: string;
|
||||
product_id: string;
|
||||
product_slug: string;
|
||||
product_name: LocalizedText;
|
||||
image: string | null;
|
||||
sku_code: string;
|
||||
sale_price_minor: number;
|
||||
currency: string;
|
||||
original_price_minor: number;
|
||||
original_currency: string;
|
||||
reserved_stock: number;
|
||||
sold_count: number;
|
||||
per_customer_limit: number;
|
||||
}
|
||||
|
||||
export interface PublicFlashSaleSession extends FlashSaleSession {
|
||||
items: PublicFlashSaleItem[];
|
||||
}
|
||||
|
||||
export type ShipmentStatus = "pending" | "shipped" | "delivered";export interface Shipment {
|
||||
id: string;
|
||||
shipment_no: string;
|
||||
|
||||
Reference in New Issue
Block a user