@basedone/core 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-4GAKANLT.mjs → chunk-MVFO4WRF.mjs} +104 -0
- package/dist/ecommerce.d.mts +255 -1
- package/dist/ecommerce.d.ts +255 -1
- package/dist/ecommerce.js +104 -0
- package/dist/ecommerce.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +104 -0
- package/dist/index.mjs +1 -1
- package/lib/ecommerce/FLASH_SALES.md +340 -0
- package/lib/ecommerce/README.md +6 -0
- package/lib/ecommerce/client/customer.ts +117 -0
- package/lib/ecommerce/types/entities.ts +69 -0
- package/lib/ecommerce/types/requests.ts +55 -0
- package/lib/ecommerce/types/responses.ts +52 -0
- package/package.json +1 -1
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# Flash Sales SDK Documentation
|
|
2
|
+
|
|
3
|
+
This guide shows you how to use the `@basedone/core/ecommerce` SDK to fetch and display active flash sales.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Flash sales are time-limited deals with discounted products. The SDK provides a simple method to fetch all currently active flash sales with their products, pricing, and countdown information.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { CustomerEcommerceClient } from "@basedone/core/ecommerce";
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Basic Usage
|
|
16
|
+
|
|
17
|
+
### Initialize the Client
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
const client = new CustomerEcommerceClient({
|
|
21
|
+
baseURL: "https://your-api-domain.com",
|
|
22
|
+
authToken: "your-auth-token", // Optional for public endpoints
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Fetch Active Flash Sales
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// Get all active flash sales
|
|
30
|
+
const result = await client.getActiveFlashSales();
|
|
31
|
+
|
|
32
|
+
console.log(`Found ${result.flashSales.length} active flash sales`);
|
|
33
|
+
|
|
34
|
+
// Access the first flash sale
|
|
35
|
+
const featuredSale = result.flashSales[0];
|
|
36
|
+
console.log(`Sale: ${featuredSale.name}`);
|
|
37
|
+
console.log(`Ends at: ${featuredSale.endsAt}`);
|
|
38
|
+
|
|
39
|
+
// Access countdown timer info
|
|
40
|
+
if (result.timeRemaining) {
|
|
41
|
+
console.log(`Time remaining: ${result.timeRemaining.remainingSeconds} seconds`);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### With Parameters
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// Limit the number of flash sales returned
|
|
49
|
+
const result = await client.getActiveFlashSales({
|
|
50
|
+
limit: 5, // Maximum 50
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Filter by merchant (optional)
|
|
54
|
+
const result = await client.getActiveFlashSales({
|
|
55
|
+
limit: 10,
|
|
56
|
+
merchantId: "merchant_123",
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Response Structure
|
|
61
|
+
|
|
62
|
+
### ActiveFlashSalesResponse
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
{
|
|
66
|
+
flashSales: FlashSale[]; // Array of active flash sales
|
|
67
|
+
timeRemaining: { // Countdown info for featured sale
|
|
68
|
+
endsAt: string; // ISO timestamp
|
|
69
|
+
remainingSeconds: number; // Seconds until end
|
|
70
|
+
} | null;
|
|
71
|
+
serverTime: string; // Server timestamp for sync
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### FlashSale
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
{
|
|
79
|
+
id: string; // Flash sale ID
|
|
80
|
+
name: string; // Sale name (e.g., "Summer Flash Sale")
|
|
81
|
+
description?: string; // Optional description
|
|
82
|
+
startsAt: string; // ISO timestamp
|
|
83
|
+
endsAt: string; // ISO timestamp
|
|
84
|
+
badgeText?: string; // Badge text (e.g., "Mall")
|
|
85
|
+
badgeColor?: string; // Badge color hex (e.g., "#facc15")
|
|
86
|
+
priority: number; // Display priority (higher = first)
|
|
87
|
+
merchant?: { // Merchant info (null for admin sales)
|
|
88
|
+
id: string;
|
|
89
|
+
name: string;
|
|
90
|
+
} | null;
|
|
91
|
+
items: FlashSaleItem[]; // Products in this sale
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### FlashSaleItem
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
{
|
|
99
|
+
id: string; // Item ID
|
|
100
|
+
productId: string; // Product ID
|
|
101
|
+
salePrice: string; // Sale price in USDC (e.g., "17.90")
|
|
102
|
+
originalPrice: string; // Original price in USDC (e.g., "30.00")
|
|
103
|
+
discountPercent: number; // Discount percentage (e.g., 42)
|
|
104
|
+
maxQuantity: number | null; // Max available (null = unlimited)
|
|
105
|
+
soldQuantity: number; // Already sold count
|
|
106
|
+
remainingQuantity: number | null; // Remaining (null if unlimited)
|
|
107
|
+
limitPerCustomer: number | null; // Max per customer
|
|
108
|
+
product: { // Full product details
|
|
109
|
+
id: string;
|
|
110
|
+
title: string;
|
|
111
|
+
description?: string;
|
|
112
|
+
images: string[];
|
|
113
|
+
priceUSDC: string;
|
|
114
|
+
inventory: number | null;
|
|
115
|
+
soldCount: number;
|
|
116
|
+
averageRating: number | null;
|
|
117
|
+
reviewCount: number;
|
|
118
|
+
merchant?: {
|
|
119
|
+
id: string;
|
|
120
|
+
name: string;
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Example: Display Flash Sales
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { CustomerEcommerceClient } from "@basedone/core/ecommerce";
|
|
130
|
+
|
|
131
|
+
const client = new CustomerEcommerceClient({
|
|
132
|
+
baseURL: "https://api.example.com",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
async function displayFlashSales() {
|
|
136
|
+
try {
|
|
137
|
+
const result = await client.getActiveFlashSales({ limit: 10 });
|
|
138
|
+
|
|
139
|
+
if (result.flashSales.length === 0) {
|
|
140
|
+
console.log("No active flash sales");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Display each flash sale
|
|
145
|
+
result.flashSales.forEach((sale) => {
|
|
146
|
+
console.log(`\n⚡ ${sale.name}`);
|
|
147
|
+
console.log(` Badge: ${sale.badgeText || "Flash Deal"}`);
|
|
148
|
+
console.log(` Ends: ${new Date(sale.endsAt).toLocaleString()}`);
|
|
149
|
+
console.log(` Products: ${sale.items.length}`);
|
|
150
|
+
|
|
151
|
+
// Display products
|
|
152
|
+
sale.items.forEach((item) => {
|
|
153
|
+
console.log(` - ${item.product.title}`);
|
|
154
|
+
console.log(` Original: $${item.originalPrice} → Sale: $${item.salePrice}`);
|
|
155
|
+
console.log(` Discount: ${item.discountPercent}% off`);
|
|
156
|
+
|
|
157
|
+
if (item.remainingQuantity !== null) {
|
|
158
|
+
console.log(` Only ${item.remainingQuantity} left!`);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Display countdown
|
|
164
|
+
if (result.timeRemaining) {
|
|
165
|
+
const hours = Math.floor(result.timeRemaining.remainingSeconds / 3600);
|
|
166
|
+
const minutes = Math.floor((result.timeRemaining.remainingSeconds % 3600) / 60);
|
|
167
|
+
const seconds = result.timeRemaining.remainingSeconds % 60;
|
|
168
|
+
console.log(`\n⏰ Time remaining: ${hours}h ${minutes}m ${seconds}s`);
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error("Error fetching flash sales:", error);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
displayFlashSales();
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Example: React Component
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { useState, useEffect } from "react";
|
|
182
|
+
import { CustomerEcommerceClient, ActiveFlashSalesResponse } from "@basedone/core/ecommerce";
|
|
183
|
+
|
|
184
|
+
function FlashSalesList() {
|
|
185
|
+
const [flashSales, setFlashSales] = useState<ActiveFlashSalesResponse | null>(null);
|
|
186
|
+
const [loading, setLoading] = useState(true);
|
|
187
|
+
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
const client = new CustomerEcommerceClient({
|
|
190
|
+
baseURL: process.env.NEXT_PUBLIC_API_URL || "",
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
client
|
|
194
|
+
.getActiveFlashSales({ limit: 10 })
|
|
195
|
+
.then((data) => {
|
|
196
|
+
setFlashSales(data);
|
|
197
|
+
setLoading(false);
|
|
198
|
+
})
|
|
199
|
+
.catch((error) => {
|
|
200
|
+
console.error("Failed to load flash sales:", error);
|
|
201
|
+
setLoading(false);
|
|
202
|
+
});
|
|
203
|
+
}, []);
|
|
204
|
+
|
|
205
|
+
if (loading) return <div>Loading flash sales...</div>;
|
|
206
|
+
if (!flashSales || flashSales.flashSales.length === 0) {
|
|
207
|
+
return <div>No active flash sales</div>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<div>
|
|
212
|
+
{flashSales.flashSales.map((sale) => (
|
|
213
|
+
<div key={sale.id} className="flash-sale-card">
|
|
214
|
+
<h2>{sale.name}</h2>
|
|
215
|
+
{sale.badgeText && (
|
|
216
|
+
<span style={{ backgroundColor: sale.badgeColor || "#f97316" }}>
|
|
217
|
+
{sale.badgeText}
|
|
218
|
+
</span>
|
|
219
|
+
)}
|
|
220
|
+
<p>Ends: {new Date(sale.endsAt).toLocaleString()}</p>
|
|
221
|
+
|
|
222
|
+
<div className="products">
|
|
223
|
+
{sale.items.map((item) => (
|
|
224
|
+
<div key={item.id} className="product-card">
|
|
225
|
+
<img src={item.product.images[0]} alt={item.product.title} />
|
|
226
|
+
<h3>{item.product.title}</h3>
|
|
227
|
+
<div className="pricing">
|
|
228
|
+
<span className="original-price">${item.originalPrice}</span>
|
|
229
|
+
<span className="sale-price">${item.salePrice}</span>
|
|
230
|
+
<span className="discount">-{item.discountPercent}%</span>
|
|
231
|
+
</div>
|
|
232
|
+
{item.remainingQuantity !== null && item.remainingQuantity <= 10 && (
|
|
233
|
+
<div className="scarcity">Only {item.remainingQuantity} left!</div>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
))}
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
))}
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Example: Countdown Timer
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { useState, useEffect } from "react";
|
|
249
|
+
import { CustomerEcommerceClient } from "@basedone/core/ecommerce";
|
|
250
|
+
|
|
251
|
+
function CountdownTimer() {
|
|
252
|
+
const [timeLeft, setTimeLeft] = useState({ hours: 0, minutes: 0, seconds: 0 });
|
|
253
|
+
const [serverTime, setServerTime] = useState<Date | null>(null);
|
|
254
|
+
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
const client = new CustomerEcommerceClient({
|
|
257
|
+
baseURL: process.env.NEXT_PUBLIC_API_URL || "",
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Fetch flash sales and sync with server time
|
|
261
|
+
client.getActiveFlashSales().then((result) => {
|
|
262
|
+
if (result.timeRemaining) {
|
|
263
|
+
const serverTimeDate = new Date(result.serverTime);
|
|
264
|
+
setServerTime(serverTimeDate);
|
|
265
|
+
|
|
266
|
+
// Calculate offset for accurate countdown
|
|
267
|
+
const offset = serverTimeDate.getTime() - Date.now();
|
|
268
|
+
const endTime = new Date(result.timeRemaining.endsAt).getTime();
|
|
269
|
+
|
|
270
|
+
const updateTimer = () => {
|
|
271
|
+
const now = Date.now() + offset;
|
|
272
|
+
const remaining = Math.max(0, endTime - now);
|
|
273
|
+
|
|
274
|
+
setTimeLeft({
|
|
275
|
+
hours: Math.floor(remaining / (1000 * 60 * 60)),
|
|
276
|
+
minutes: Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60)),
|
|
277
|
+
seconds: Math.floor((remaining % (1000 * 60)) / 1000),
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
updateTimer();
|
|
282
|
+
const interval = setInterval(updateTimer, 1000);
|
|
283
|
+
return () => clearInterval(interval);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
}, []);
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<div className="countdown">
|
|
290
|
+
{timeLeft.hours.toString().padStart(2, "0")}:
|
|
291
|
+
{timeLeft.minutes.toString().padStart(2, "0")}:
|
|
292
|
+
{timeLeft.seconds.toString().padStart(2, "0")}
|
|
293
|
+
</div>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Error Handling
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
try {
|
|
302
|
+
const result = await client.getActiveFlashSales();
|
|
303
|
+
// Use result...
|
|
304
|
+
} catch (error) {
|
|
305
|
+
if (error instanceof EcommerceApiError) {
|
|
306
|
+
console.error("API Error:", error.message);
|
|
307
|
+
console.error("Status:", error.status);
|
|
308
|
+
} else {
|
|
309
|
+
console.error("Unexpected error:", error);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Notes
|
|
315
|
+
|
|
316
|
+
- Flash sales are automatically filtered to only show active sales (between `startsAt` and `endsAt`)
|
|
317
|
+
- The `timeRemaining` field provides countdown info for the featured/first flash sale
|
|
318
|
+
- Use `serverTime` to sync client-side countdown timers accurately
|
|
319
|
+
- Products in flash sales are automatically filtered to only include active products
|
|
320
|
+
- Discount percentages are pre-calculated for convenience
|
|
321
|
+
- The endpoint caches responses for 30 seconds due to time-sensitive nature
|
|
322
|
+
|
|
323
|
+
## API Endpoint
|
|
324
|
+
|
|
325
|
+
The SDK method calls:
|
|
326
|
+
```
|
|
327
|
+
GET /api/marketplace/flash-sales/active?limit={limit}&merchantId={merchantId}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Related Types
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import type {
|
|
334
|
+
ActiveFlashSalesResponse,
|
|
335
|
+
FlashSale,
|
|
336
|
+
FlashSaleItem,
|
|
337
|
+
ListActiveFlashSalesParams,
|
|
338
|
+
} from "@basedone/core/ecommerce";
|
|
339
|
+
```
|
|
340
|
+
|
package/lib/ecommerce/README.md
CHANGED
|
@@ -375,6 +375,12 @@ const client = new CustomerEcommerceClient({
|
|
|
375
375
|
});
|
|
376
376
|
```
|
|
377
377
|
|
|
378
|
+
## Additional Documentation
|
|
379
|
+
|
|
380
|
+
- **[Flash Sales Guide](./FLASH_SALES.md)** - Complete guide for fetching and displaying flash sales
|
|
381
|
+
- **[Usage Examples](./USAGE_EXAMPLES.md)** - More detailed code examples
|
|
382
|
+
- **[Quick Reference](./QUICK_REFERENCE.md)** - API method quick reference
|
|
383
|
+
|
|
378
384
|
## Support
|
|
379
385
|
|
|
380
386
|
For issues or questions, please open an issue on GitHub or contact support.
|
|
@@ -20,6 +20,8 @@ import type {
|
|
|
20
20
|
CalculateTaxRequest,
|
|
21
21
|
ListActiveBannersParams,
|
|
22
22
|
TrackBannerRequest,
|
|
23
|
+
SendMessageRequest,
|
|
24
|
+
ListActiveFlashSalesParams,
|
|
23
25
|
|
|
24
26
|
// Response types
|
|
25
27
|
ListProductsResponse,
|
|
@@ -39,6 +41,10 @@ import type {
|
|
|
39
41
|
ListBannersResponse,
|
|
40
42
|
SuccessResponse,
|
|
41
43
|
ProductDiscountsResponse,
|
|
44
|
+
CustomerMessagesResponse,
|
|
45
|
+
MessageStatsResponse,
|
|
46
|
+
MessageResponse,
|
|
47
|
+
ActiveFlashSalesResponse,
|
|
42
48
|
} from "../types";
|
|
43
49
|
|
|
44
50
|
/**
|
|
@@ -518,5 +524,116 @@ export class CustomerEcommerceClient extends BaseEcommerceClient {
|
|
|
518
524
|
async trackBanner(bannerId: string, request: TrackBannerRequest): Promise<SuccessResponse> {
|
|
519
525
|
return this.post(`/api/marketplace/banners/${bannerId}/track`, request);
|
|
520
526
|
}
|
|
527
|
+
|
|
528
|
+
// ============================================================================
|
|
529
|
+
// Messages API
|
|
530
|
+
// ============================================================================
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* List messages for customer
|
|
534
|
+
*
|
|
535
|
+
* Returns all conversations grouped by order, where each order represents
|
|
536
|
+
* a conversation with a merchant.
|
|
537
|
+
*
|
|
538
|
+
* @returns List of conversations with messages and stats
|
|
539
|
+
*
|
|
540
|
+
* @example
|
|
541
|
+
* ```typescript
|
|
542
|
+
* const result = await client.listMessages();
|
|
543
|
+
* console.log("Unread:", result.stats.unread);
|
|
544
|
+
* result.conversations.forEach(conv => {
|
|
545
|
+
* console.log(`Order ${conv.orderNumber}: ${conv.unreadCount} unread`);
|
|
546
|
+
* });
|
|
547
|
+
* ```
|
|
548
|
+
*/
|
|
549
|
+
async listMessages(): Promise<CustomerMessagesResponse> {
|
|
550
|
+
return this.get("/api/marketplace/messages");
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Get message stats (unread count)
|
|
555
|
+
*
|
|
556
|
+
* Lightweight endpoint for notification badges.
|
|
557
|
+
*
|
|
558
|
+
* @returns Unread message count
|
|
559
|
+
*
|
|
560
|
+
* @example
|
|
561
|
+
* ```typescript
|
|
562
|
+
* const stats = await client.getMessageStats();
|
|
563
|
+
* console.log("Unread messages:", stats.unread);
|
|
564
|
+
* ```
|
|
565
|
+
*/
|
|
566
|
+
async getMessageStats(): Promise<MessageStatsResponse> {
|
|
567
|
+
return this.get("/api/marketplace/messages/stats");
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Send a message to a merchant about an order
|
|
572
|
+
*
|
|
573
|
+
* @param request - Message data including orderId, recipientId (merchant user ID), and message
|
|
574
|
+
* @returns Sent message
|
|
575
|
+
*
|
|
576
|
+
* @example
|
|
577
|
+
* ```typescript
|
|
578
|
+
* await client.sendMessage({
|
|
579
|
+
* orderId: "ord_123",
|
|
580
|
+
* recipientId: "user_merchant_456",
|
|
581
|
+
* message: "When will my order ship?"
|
|
582
|
+
* });
|
|
583
|
+
* ```
|
|
584
|
+
*/
|
|
585
|
+
async sendMessage(request: SendMessageRequest): Promise<MessageResponse> {
|
|
586
|
+
return this.post("/api/marketplace/messages/send", request);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Mark a message as read
|
|
591
|
+
*
|
|
592
|
+
* @param messageId - Message ID
|
|
593
|
+
* @returns Updated message
|
|
594
|
+
*
|
|
595
|
+
* @example
|
|
596
|
+
* ```typescript
|
|
597
|
+
* await client.markMessageAsRead("msg_123");
|
|
598
|
+
* ```
|
|
599
|
+
*/
|
|
600
|
+
async markMessageAsRead(messageId: string): Promise<MessageResponse> {
|
|
601
|
+
return this.patch(`/api/marketplace/messages/${messageId}/read`, {});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// ============================================================================
|
|
605
|
+
// Flash Sales API
|
|
606
|
+
// ============================================================================
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Get active flash sales
|
|
610
|
+
*
|
|
611
|
+
* Returns currently running flash sales with their discounted products.
|
|
612
|
+
* Includes countdown timer information for UI display.
|
|
613
|
+
*
|
|
614
|
+
* @param params - Query parameters
|
|
615
|
+
* @returns List of active flash sales with time remaining
|
|
616
|
+
*
|
|
617
|
+
* @example
|
|
618
|
+
* ```typescript
|
|
619
|
+
* const result = await client.getActiveFlashSales({ limit: 5 });
|
|
620
|
+
*
|
|
621
|
+
* result.flashSales.forEach(sale => {
|
|
622
|
+
* console.log(`${sale.name} - ends at ${sale.endsAt}`);
|
|
623
|
+
* sale.items.forEach(item => {
|
|
624
|
+
* console.log(` ${item.product.title}: $${item.salePrice} (${item.discountPercent}% off)`);
|
|
625
|
+
* });
|
|
626
|
+
* });
|
|
627
|
+
*
|
|
628
|
+
* // Use timeRemaining for countdown UI
|
|
629
|
+
* if (result.timeRemaining) {
|
|
630
|
+
* console.log(`Featured sale ends in ${result.timeRemaining.remainingSeconds} seconds`);
|
|
631
|
+
* }
|
|
632
|
+
* ```
|
|
633
|
+
*/
|
|
634
|
+
async getActiveFlashSales(params?: ListActiveFlashSalesParams): Promise<ActiveFlashSalesResponse> {
|
|
635
|
+
const queryString = params ? buildQueryString(params) : "";
|
|
636
|
+
return this.get(`/api/marketplace/flash-sales/active${queryString}`);
|
|
637
|
+
}
|
|
521
638
|
}
|
|
522
639
|
|
|
@@ -720,3 +720,72 @@ export interface CustomerSummary {
|
|
|
720
720
|
firstOrderDate?: string;
|
|
721
721
|
}
|
|
722
722
|
|
|
723
|
+
/**
|
|
724
|
+
* Flash sale item entity
|
|
725
|
+
*/
|
|
726
|
+
export interface FlashSaleItem {
|
|
727
|
+
/** Item ID */
|
|
728
|
+
id: string;
|
|
729
|
+
/** Product ID */
|
|
730
|
+
productId: string;
|
|
731
|
+
/** Sale price in USDC */
|
|
732
|
+
salePrice: string;
|
|
733
|
+
/** Original price in USDC */
|
|
734
|
+
originalPrice: string;
|
|
735
|
+
/** Discount percentage */
|
|
736
|
+
discountPercent: number;
|
|
737
|
+
/** Maximum quantity available at this price */
|
|
738
|
+
maxQuantity: number | null;
|
|
739
|
+
/** Quantity sold at flash price */
|
|
740
|
+
soldQuantity: number;
|
|
741
|
+
/** Remaining quantity */
|
|
742
|
+
remainingQuantity: number | null;
|
|
743
|
+
/** Limit per customer */
|
|
744
|
+
limitPerCustomer: number | null;
|
|
745
|
+
/** Product details */
|
|
746
|
+
product: {
|
|
747
|
+
id: string;
|
|
748
|
+
title: string;
|
|
749
|
+
description?: string | null;
|
|
750
|
+
images: string[];
|
|
751
|
+
priceUSDC: string;
|
|
752
|
+
inventory: number | null;
|
|
753
|
+
soldCount: number;
|
|
754
|
+
averageRating: number | null;
|
|
755
|
+
reviewCount: number;
|
|
756
|
+
merchant?: {
|
|
757
|
+
id: string;
|
|
758
|
+
name: string;
|
|
759
|
+
};
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Flash sale entity
|
|
765
|
+
*/
|
|
766
|
+
export interface FlashSale {
|
|
767
|
+
/** Flash sale ID */
|
|
768
|
+
id: string;
|
|
769
|
+
/** Sale name */
|
|
770
|
+
name: string;
|
|
771
|
+
/** Description */
|
|
772
|
+
description?: string | null;
|
|
773
|
+
/** Start timestamp */
|
|
774
|
+
startsAt: string;
|
|
775
|
+
/** End timestamp */
|
|
776
|
+
endsAt: string;
|
|
777
|
+
/** Badge text (e.g., "Mall", "Hot Deal") */
|
|
778
|
+
badgeText?: string | null;
|
|
779
|
+
/** Badge color (hex) */
|
|
780
|
+
badgeColor?: string | null;
|
|
781
|
+
/** Priority (higher = shown first) */
|
|
782
|
+
priority: number;
|
|
783
|
+
/** Merchant information */
|
|
784
|
+
merchant?: {
|
|
785
|
+
id: string;
|
|
786
|
+
name: string;
|
|
787
|
+
} | null;
|
|
788
|
+
/** Flash sale items */
|
|
789
|
+
items: FlashSaleItem[];
|
|
790
|
+
}
|
|
791
|
+
|
|
@@ -523,3 +523,58 @@ export interface ListActiveBannersParams {
|
|
|
523
523
|
merchantId?: string;
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
+
/**
|
|
527
|
+
* List active flash sales params
|
|
528
|
+
*/
|
|
529
|
+
export interface ListActiveFlashSalesParams {
|
|
530
|
+
/** Maximum number of flash sales to return */
|
|
531
|
+
limit?: number;
|
|
532
|
+
/** Filter by merchant ID */
|
|
533
|
+
merchantId?: string;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Flash sale item input
|
|
538
|
+
*/
|
|
539
|
+
export interface FlashSaleItemInput {
|
|
540
|
+
/** Product ID */
|
|
541
|
+
productId: string;
|
|
542
|
+
/** Sale price in USDC */
|
|
543
|
+
salePrice: number;
|
|
544
|
+
/** Maximum quantity available */
|
|
545
|
+
maxQuantity?: number | null;
|
|
546
|
+
/** Limit per customer */
|
|
547
|
+
limitPerCustomer?: number;
|
|
548
|
+
/** Sort order */
|
|
549
|
+
sortOrder?: number;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Create flash sale request
|
|
554
|
+
*/
|
|
555
|
+
export interface CreateFlashSaleRequest {
|
|
556
|
+
/** Sale name */
|
|
557
|
+
name: string;
|
|
558
|
+
/** Description */
|
|
559
|
+
description?: string | null;
|
|
560
|
+
/** Start timestamp */
|
|
561
|
+
startsAt: string;
|
|
562
|
+
/** End timestamp */
|
|
563
|
+
endsAt: string;
|
|
564
|
+
/** Badge text */
|
|
565
|
+
badgeText?: string;
|
|
566
|
+
/** Badge color (hex) */
|
|
567
|
+
badgeColor?: string;
|
|
568
|
+
/** Priority (higher = shown first) */
|
|
569
|
+
priority?: number;
|
|
570
|
+
/** Is active */
|
|
571
|
+
isActive?: boolean;
|
|
572
|
+
/** Flash sale items */
|
|
573
|
+
items?: FlashSaleItemInput[];
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Update flash sale request
|
|
578
|
+
*/
|
|
579
|
+
export interface UpdateFlashSaleRequest extends Partial<CreateFlashSaleRequest> {}
|
|
580
|
+
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
CustomerSummary,
|
|
26
26
|
ProductVariant,
|
|
27
27
|
CouponUsage,
|
|
28
|
+
FlashSale,
|
|
28
29
|
} from "./entities";
|
|
29
30
|
|
|
30
31
|
/**
|
|
@@ -504,6 +505,40 @@ export interface MessageResponse {
|
|
|
504
505
|
message: Message;
|
|
505
506
|
}
|
|
506
507
|
|
|
508
|
+
/**
|
|
509
|
+
* Customer messages response (for retail users)
|
|
510
|
+
*
|
|
511
|
+
* Groups messages by order, where each order represents a conversation with a merchant.
|
|
512
|
+
*/
|
|
513
|
+
export interface CustomerMessagesResponse {
|
|
514
|
+
/** Conversations grouped by order */
|
|
515
|
+
conversations: Array<{
|
|
516
|
+
orderId: string;
|
|
517
|
+
orderNumber: string;
|
|
518
|
+
merchant: {
|
|
519
|
+
id: string;
|
|
520
|
+
name: string;
|
|
521
|
+
ownerUserId: string;
|
|
522
|
+
};
|
|
523
|
+
lastMessage: Message;
|
|
524
|
+
unreadCount: number;
|
|
525
|
+
messages: Message[];
|
|
526
|
+
}>;
|
|
527
|
+
/** Stats */
|
|
528
|
+
stats: {
|
|
529
|
+
total: number;
|
|
530
|
+
unread: number;
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Message stats response (for notification badges)
|
|
536
|
+
*/
|
|
537
|
+
export interface MessageStatsResponse {
|
|
538
|
+
/** Unread message count */
|
|
539
|
+
unread: number;
|
|
540
|
+
}
|
|
541
|
+
|
|
507
542
|
/**
|
|
508
543
|
* Analytics overview
|
|
509
544
|
*/
|
|
@@ -803,3 +838,20 @@ export interface CreateOrderEventResponse {
|
|
|
803
838
|
event: any;
|
|
804
839
|
}
|
|
805
840
|
|
|
841
|
+
/**
|
|
842
|
+
* Active flash sales response
|
|
843
|
+
*/
|
|
844
|
+
export interface ActiveFlashSalesResponse {
|
|
845
|
+
/** Flash sales */
|
|
846
|
+
flashSales: FlashSale[];
|
|
847
|
+
/** Time remaining for the featured/first sale */
|
|
848
|
+
timeRemaining: {
|
|
849
|
+
/** End timestamp */
|
|
850
|
+
endsAt: string;
|
|
851
|
+
/** Remaining seconds */
|
|
852
|
+
remainingSeconds: number;
|
|
853
|
+
} | null;
|
|
854
|
+
/** Server time (for client-side sync) */
|
|
855
|
+
serverTime: string;
|
|
856
|
+
}
|
|
857
|
+
|