@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.
@@ -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
+
@@ -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
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basedone/core",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Core utilities for Based One",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",