@basedone/core 0.2.0 → 0.2.2

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
+
@@ -57,6 +57,16 @@ await client.createReview("prod_123", {
57
57
  comment: "Love it!",
58
58
  });
59
59
 
60
+ // Shop Following
61
+ const followStatus = await client.isFollowingMerchant("merchant_123");
62
+ await client.followMerchant("merchant_123");
63
+ await client.unfollowMerchant("merchant_123");
64
+ const following = await client.getFollowedMerchants({ limit: 20 });
65
+
66
+ // Merchant Profile & Products (public)
67
+ const merchant = await client.getMerchantProfile("merchant_123");
68
+ const products = await client.getMerchantProducts("merchant_123", { sortBy: "popular" });
69
+
60
70
  // Calculate discounts
61
71
  const discounts = await client.calculateCartDiscounts({
62
72
  items: [{ productId: "prod_123", quantity: 2 }],
@@ -67,6 +77,17 @@ const tax = await client.calculateTax({
67
77
  items: [{ productId: "prod_123", quantity: 2 }],
68
78
  shippingAddress: { country: "US", region: "NY" },
69
79
  });
80
+
81
+ // Get merchant storefront profile
82
+ const merchant = await client.getMerchantProfile("merchant_123");
83
+ console.log(merchant.merchant.name, merchant.merchant.productCount);
84
+
85
+ // List merchant products with search/sort
86
+ const merchantProducts = await client.getMerchantProducts("merchant_123", {
87
+ search: "laptop",
88
+ sortBy: "popular",
89
+ limit: 20,
90
+ });
70
91
  ```
71
92
 
72
93
  ## Merchant Client
@@ -200,6 +221,11 @@ import type {
200
221
  PaymentMethod,
201
222
  CreateOrderRequest,
202
223
  ListProductsParams,
224
+ // Merchant storefront types
225
+ PublicMerchantProfile,
226
+ PublicMerchantProfileResponse,
227
+ MerchantProductsResponse,
228
+ ListMerchantProductsParams,
203
229
  } from "@basedone/core/ecommerce";
204
230
  ```
205
231
 
@@ -68,6 +68,17 @@ if (order.escrow) {
68
68
  // After depositing, confirm the transaction
69
69
  await client.confirmEscrowDeposit(order.orders[0].id);
70
70
  }
71
+
72
+ // Browse a merchant's storefront
73
+ const merchantProfile = await client.getMerchantProfile("merchant_123");
74
+ console.log(`${merchantProfile.merchant.name} - ${merchantProfile.merchant.productCount} products`);
75
+
76
+ // Get merchant products with search and sort
77
+ const merchantProducts = await client.getMerchantProducts("merchant_123", {
78
+ search: "laptop",
79
+ sortBy: "popular", // popular, latest, top_sales, price_asc, price_desc, rating
80
+ limit: 20,
81
+ });
71
82
  ```
72
83
 
73
84
  ### Merchant Client
@@ -142,6 +153,16 @@ await client.updateOrderStatus("ord_123", {
142
153
  - `getActiveBanners(params?)` - Get active promotional banners
143
154
  - `trackBanner(bannerId, request)` - Track banner impression/click
144
155
 
156
+ #### Merchant Storefront (Public)
157
+ - `getMerchantProfile(merchantId)` - Get public merchant profile
158
+ - `getMerchantProducts(merchantId, params?)` - Get merchant's products with filtering
159
+
160
+ #### Shop Following
161
+ - `isFollowingMerchant(merchantId)` - Check if following a merchant
162
+ - `followMerchant(merchantId)` - Follow a merchant
163
+ - `unfollowMerchant(merchantId)` - Unfollow a merchant
164
+ - `getFollowedMerchants(params?)` - Get list of followed merchants
165
+
145
166
  ### Merchant APIs
146
167
 
147
168
  #### Profile
@@ -319,6 +340,11 @@ import type {
319
340
  PaymentMethod,
320
341
  CreateOrderRequest,
321
342
  ListProductsParams,
343
+ // Merchant storefront types
344
+ PublicMerchantProfile,
345
+ PublicMerchantProfileResponse,
346
+ MerchantProductsResponse,
347
+ ListMerchantProductsParams,
322
348
  // ... and many more
323
349
  } from "@basedone/core/ecommerce";
324
350
  ```
@@ -375,6 +401,12 @@ const client = new CustomerEcommerceClient({
375
401
  });
376
402
  ```
377
403
 
404
+ ## Additional Documentation
405
+
406
+ - **[Flash Sales Guide](./FLASH_SALES.md)** - Complete guide for fetching and displaying flash sales
407
+ - **[Usage Examples](./USAGE_EXAMPLES.md)** - More detailed code examples
408
+ - **[Quick Reference](./QUICK_REFERENCE.md)** - API method quick reference
409
+
378
410
  ## Support
379
411
 
380
412
  For issues or questions, please open an issue on GitHub or contact support.
@@ -20,6 +20,10 @@ import type {
20
20
  CalculateTaxRequest,
21
21
  ListActiveBannersParams,
22
22
  TrackBannerRequest,
23
+ SendMessageRequest,
24
+ ListActiveFlashSalesParams,
25
+ ListMerchantProductsParams,
26
+ ListFollowingParams,
23
27
 
24
28
  // Response types
25
29
  ListProductsResponse,
@@ -39,6 +43,15 @@ import type {
39
43
  ListBannersResponse,
40
44
  SuccessResponse,
41
45
  ProductDiscountsResponse,
46
+ CustomerMessagesResponse,
47
+ MessageStatsResponse,
48
+ MessageResponse,
49
+ ActiveFlashSalesResponse,
50
+ PublicMerchantProfileResponse,
51
+ MerchantProductsResponse,
52
+ FollowStatusResponse,
53
+ FollowActionResponse,
54
+ ListFollowingResponse,
42
55
  } from "../types";
43
56
 
44
57
  /**
@@ -518,5 +531,241 @@ export class CustomerEcommerceClient extends BaseEcommerceClient {
518
531
  async trackBanner(bannerId: string, request: TrackBannerRequest): Promise<SuccessResponse> {
519
532
  return this.post(`/api/marketplace/banners/${bannerId}/track`, request);
520
533
  }
534
+
535
+ // ============================================================================
536
+ // Messages API
537
+ // ============================================================================
538
+
539
+ /**
540
+ * List messages for customer
541
+ *
542
+ * Returns all conversations grouped by order, where each order represents
543
+ * a conversation with a merchant.
544
+ *
545
+ * @returns List of conversations with messages and stats
546
+ *
547
+ * @example
548
+ * ```typescript
549
+ * const result = await client.listMessages();
550
+ * console.log("Unread:", result.stats.unread);
551
+ * result.conversations.forEach(conv => {
552
+ * console.log(`Order ${conv.orderNumber}: ${conv.unreadCount} unread`);
553
+ * });
554
+ * ```
555
+ */
556
+ async listMessages(): Promise<CustomerMessagesResponse> {
557
+ return this.get("/api/marketplace/messages");
558
+ }
559
+
560
+ /**
561
+ * Get message stats (unread count)
562
+ *
563
+ * Lightweight endpoint for notification badges.
564
+ *
565
+ * @returns Unread message count
566
+ *
567
+ * @example
568
+ * ```typescript
569
+ * const stats = await client.getMessageStats();
570
+ * console.log("Unread messages:", stats.unread);
571
+ * ```
572
+ */
573
+ async getMessageStats(): Promise<MessageStatsResponse> {
574
+ return this.get("/api/marketplace/messages/stats");
575
+ }
576
+
577
+ /**
578
+ * Send a message to a merchant about an order
579
+ *
580
+ * @param request - Message data including orderId, recipientId (merchant user ID), and message
581
+ * @returns Sent message
582
+ *
583
+ * @example
584
+ * ```typescript
585
+ * await client.sendMessage({
586
+ * orderId: "ord_123",
587
+ * recipientId: "user_merchant_456",
588
+ * message: "When will my order ship?"
589
+ * });
590
+ * ```
591
+ */
592
+ async sendMessage(request: SendMessageRequest): Promise<MessageResponse> {
593
+ return this.post("/api/marketplace/messages/send", request);
594
+ }
595
+
596
+ /**
597
+ * Mark a message as read
598
+ *
599
+ * @param messageId - Message ID
600
+ * @returns Updated message
601
+ *
602
+ * @example
603
+ * ```typescript
604
+ * await client.markMessageAsRead("msg_123");
605
+ * ```
606
+ */
607
+ async markMessageAsRead(messageId: string): Promise<MessageResponse> {
608
+ return this.patch(`/api/marketplace/messages/${messageId}/read`, {});
609
+ }
610
+
611
+ // ============================================================================
612
+ // Flash Sales API
613
+ // ============================================================================
614
+
615
+ /**
616
+ * Get active flash sales
617
+ *
618
+ * Returns currently running flash sales with their discounted products.
619
+ * Includes countdown timer information for UI display.
620
+ *
621
+ * @param params - Query parameters
622
+ * @returns List of active flash sales with time remaining
623
+ *
624
+ * @example
625
+ * ```typescript
626
+ * const result = await client.getActiveFlashSales({ limit: 5 });
627
+ *
628
+ * result.flashSales.forEach(sale => {
629
+ * console.log(`${sale.name} - ends at ${sale.endsAt}`);
630
+ * sale.items.forEach(item => {
631
+ * console.log(` ${item.product.title}: $${item.salePrice} (${item.discountPercent}% off)`);
632
+ * });
633
+ * });
634
+ *
635
+ * // Use timeRemaining for countdown UI
636
+ * if (result.timeRemaining) {
637
+ * console.log(`Featured sale ends in ${result.timeRemaining.remainingSeconds} seconds`);
638
+ * }
639
+ * ```
640
+ */
641
+ async getActiveFlashSales(params?: ListActiveFlashSalesParams): Promise<ActiveFlashSalesResponse> {
642
+ const queryString = params ? buildQueryString(params) : "";
643
+ return this.get(`/api/marketplace/flash-sales/active${queryString}`);
644
+ }
645
+
646
+ // ============================================================================
647
+ // Merchant Storefront API
648
+ // ============================================================================
649
+
650
+ /**
651
+ * Get public merchant profile
652
+ *
653
+ * Fetches public information about a merchant for the storefront page.
654
+ *
655
+ * @param merchantId - Merchant ID
656
+ * @returns Public merchant profile with stats
657
+ *
658
+ * @example
659
+ * ```typescript
660
+ * const result = await client.getMerchantProfile("merchant_123");
661
+ * console.log(result.merchant.name, result.merchant.productCount);
662
+ * ```
663
+ */
664
+ async getMerchantProfile(merchantId: string): Promise<PublicMerchantProfileResponse> {
665
+ return this.get(`/api/marketplace/merchants/${merchantId}`);
666
+ }
667
+
668
+ /**
669
+ * List merchant products
670
+ *
671
+ * Fetches products for a specific merchant with search, filter, and sort options.
672
+ *
673
+ * @param merchantId - Merchant ID
674
+ * @param params - Query parameters for filtering and pagination
675
+ * @returns Paginated list of products
676
+ *
677
+ * @example
678
+ * ```typescript
679
+ * const result = await client.getMerchantProducts("merchant_123", {
680
+ * search: "laptop",
681
+ * sortBy: "popular",
682
+ * limit: 20,
683
+ * });
684
+ *
685
+ * result.items.forEach(product => {
686
+ * console.log(product.title, product.priceUSDC);
687
+ * });
688
+ * ```
689
+ */
690
+ async getMerchantProducts(
691
+ merchantId: string,
692
+ params?: ListMerchantProductsParams
693
+ ): Promise<MerchantProductsResponse> {
694
+ const queryString = params ? buildQueryString(params) : "";
695
+ return this.get(`/api/marketplace/merchants/${merchantId}/products${queryString}`);
696
+ }
697
+
698
+ // ============================================================================
699
+ // Shop Following API
700
+ // ============================================================================
701
+
702
+ /**
703
+ * Check if following a merchant
704
+ *
705
+ * @param merchantId - Merchant ID
706
+ * @returns Follow status with follow date if applicable
707
+ *
708
+ * @example
709
+ * ```typescript
710
+ * const status = await client.isFollowingMerchant("merchant_123");
711
+ * if (status.isFollowing) {
712
+ * console.log(`Following since ${status.followedAt}`);
713
+ * }
714
+ * ```
715
+ */
716
+ async isFollowingMerchant(merchantId: string): Promise<FollowStatusResponse> {
717
+ return this.get(`/api/marketplace/merchants/${merchantId}/follow`);
718
+ }
719
+
720
+ /**
721
+ * Follow a merchant
722
+ *
723
+ * @param merchantId - Merchant ID to follow
724
+ * @returns Follow action result
725
+ *
726
+ * @example
727
+ * ```typescript
728
+ * const result = await client.followMerchant("merchant_123");
729
+ * console.log(result.message); // "Now following Awesome Store"
730
+ * ```
731
+ */
732
+ async followMerchant(merchantId: string): Promise<FollowActionResponse> {
733
+ return this.post(`/api/marketplace/merchants/${merchantId}/follow`);
734
+ }
735
+
736
+ /**
737
+ * Unfollow a merchant
738
+ *
739
+ * @param merchantId - Merchant ID to unfollow
740
+ * @returns Unfollow action result
741
+ *
742
+ * @example
743
+ * ```typescript
744
+ * const result = await client.unfollowMerchant("merchant_123");
745
+ * console.log(result.message); // "Unfollowed successfully"
746
+ * ```
747
+ */
748
+ async unfollowMerchant(merchantId: string): Promise<FollowActionResponse> {
749
+ return this.delete(`/api/marketplace/merchants/${merchantId}/follow`);
750
+ }
751
+
752
+ /**
753
+ * Get list of followed merchants
754
+ *
755
+ * @param params - Query parameters for pagination and sorting
756
+ * @returns Paginated list of followed merchants with their info
757
+ *
758
+ * @example
759
+ * ```typescript
760
+ * const result = await client.getFollowedMerchants({ limit: 20, sortBy: "recent" });
761
+ * result.items.forEach(item => {
762
+ * console.log(`${item.merchant.name} - followed on ${item.followedAt}`);
763
+ * });
764
+ * ```
765
+ */
766
+ async getFollowedMerchants(params?: ListFollowingParams): Promise<ListFollowingResponse> {
767
+ const queryString = params ? buildQueryString(params) : "";
768
+ return this.get(`/api/marketplace/following${queryString}`);
769
+ }
521
770
  }
522
771