@cshah18/sdk 4.15.0 → 4.16.0

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.
@@ -2291,6 +2291,11 @@ class LobbyModal {
2291
2291
  this.updateData(updatePayload);
2292
2292
  // Also update team card to reflect new member count
2293
2293
  this.updateTeamCard(participants, max);
2294
+ // If group just became complete and we need offline codes for THIS user, fetch them
2295
+ if (isComplete && !this.data.offlineRedemption) {
2296
+ this.logger.info("[Socket] Group fulfilled - fetching current user's offline codes");
2297
+ this.fetchCurrentUserOfflineCodesIfNeeded();
2298
+ }
2294
2299
  };
2295
2300
  this.logger = new Logger(debug);
2296
2301
  this.apiClient = apiClient;
@@ -4534,6 +4539,33 @@ class LobbyModal {
4534
4539
  window.removeEventListener("group:created", this.handleSocketGroupUpdate);
4535
4540
  this.socketListenerRegistered = false;
4536
4541
  }
4542
+ /**
4543
+ * Fetch current user's offline redemption codes when group becomes fulfilled
4544
+ * This ensures we show the current user's codes, not someone else's
4545
+ */
4546
+ async fetchCurrentUserOfflineCodesIfNeeded() {
4547
+ var _a;
4548
+ if (!this.apiClient || !this.currentGroupId) {
4549
+ return;
4550
+ }
4551
+ try {
4552
+ this.logger.info("[Offline] Fetching current user's offline codes");
4553
+ const response = await this.apiClient.getGroupOfflineRedemption(this.currentGroupId);
4554
+ if (response.success && ((_a = response.data) === null || _a === void 0 ? void 0 : _a.offline_redemption)) {
4555
+ const offlineRedemption = response.data.offline_redemption;
4556
+ if (isValidOfflineRedemption(offlineRedemption)) {
4557
+ this.logger.info("[Offline] Successfully fetched current user's offline codes");
4558
+ this.updateData({ offlineRedemption });
4559
+ }
4560
+ }
4561
+ else {
4562
+ this.logger.warn("[Offline] Failed to fetch offline codes", response.error);
4563
+ }
4564
+ }
4565
+ catch (error) {
4566
+ this.logger.error("[Offline] Error fetching offline codes", error);
4567
+ }
4568
+ }
4537
4569
  /**
4538
4570
  * Create activity item from socket event data
4539
4571
  */
@@ -5215,6 +5247,7 @@ class WidgetRoot {
5215
5247
  }
5216
5248
  /** Fetch latest group data and re-render containers */
5217
5249
  async refreshGroupDataFromRealtime() {
5250
+ var _a;
5218
5251
  // Debounce rapid refreshes to prevent loops and reduce API load
5219
5252
  const now = Date.now();
5220
5253
  if (now - this.lastGroupDataRefreshTime < this.GROUP_REFRESH_DEBOUNCE) {
@@ -5231,19 +5264,24 @@ class WidgetRoot {
5231
5264
  const groupData = await this.fetchPrimaryGroup(this.currentProductId);
5232
5265
  this.currentGroupData = groupData;
5233
5266
  this.currentGroupId = (groupData === null || groupData === void 0 ? void 0 : groupData.id) || this.currentGroupId;
5267
+ // If backend returned member-scoped offline redemption, this session is a member.
5268
+ const hasMemberOfflineRedemption = !!(groupData === null || groupData === void 0 ? void 0 : groupData.offline_redemption) && isValidOfflineRedemption(groupData.offline_redemption);
5269
+ if (hasMemberOfflineRedemption && !this.currentSessionId) {
5270
+ this.currentSessionId = ((_a = this.apiClient) === null || _a === void 0 ? void 0 : _a.getSessionId()) || null;
5271
+ }
5234
5272
  // If backend signals completion via counts, reflect it — but only for actual members.
5235
5273
  // Observers may see a full group returned by fetchPrimaryGroup; they should NOT enter
5236
5274
  // the checkout flow. currentSessionId is the membership signal (non-null = joined).
5237
5275
  if (groupData) {
5238
5276
  const participants = Number(groupData.participants_count || 0);
5239
5277
  const max = Number(groupData.max_participants || 0);
5240
- if (max > 0 && participants >= max && this.currentSessionId) {
5278
+ const isMember = !!this.currentSessionId || hasMemberOfflineRedemption;
5279
+ if (max > 0 && participants >= max && isMember) {
5241
5280
  this.groupFulfilled = true;
5242
5281
  // The primary group API returns offline_redemption for members of fulfilled groups.
5243
5282
  // Extract it here so renderFulfilledSummary shows the "Redeem In-store" link
5244
5283
  // (matching the UI users see on a full page reload).
5245
- if (groupData.offline_redemption &&
5246
- isValidOfflineRedemption(groupData.offline_redemption)) {
5284
+ if (hasMemberOfflineRedemption) {
5247
5285
  this.offlineRedemption = groupData.offline_redemption;
5248
5286
  }
5249
5287
  if (groupData.campaign_redemption_method) {
@@ -6646,7 +6684,7 @@ class WidgetRoot {
6646
6684
  * Handle CTA button click with analytics and modal opening
6647
6685
  */
6648
6686
  async handleCTAClick(productId) {
6649
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
6687
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
6650
6688
  this.logger.info(`CTA clicked for product: ${productId}`);
6651
6689
  // Track analytics event asynchronously (fire-and-forget)
6652
6690
  if (this.analyticsClient) {
@@ -6766,6 +6804,8 @@ class WidgetRoot {
6766
6804
  }
6767
6805
  }
6768
6806
  this.logger.info("Successfully joined group", groupJoinData);
6807
+ // Mark this widget session as an active member for subsequent socket fulfillment handling.
6808
+ this.currentSessionId = ((_j = this.apiClient) === null || _j === void 0 ? void 0 : _j.getSessionId()) || this.currentSessionId;
6769
6809
  if (this.analyticsClient && groupJoinData) {
6770
6810
  this.analyticsClient
6771
6811
  .trackJoinSuccess(productId, groupJoinData.group.id)
@@ -6777,7 +6817,7 @@ class WidgetRoot {
6777
6817
  isValidOfflineRedemption(groupJoinData.offline_redemption)
6778
6818
  ? groupJoinData.offline_redemption
6779
6819
  : null;
6780
- const joinedGroupId = (_j = groupJoinData === null || groupJoinData === void 0 ? void 0 : groupJoinData.group) === null || _j === void 0 ? void 0 : _j.id;
6820
+ const joinedGroupId = (_k = groupJoinData === null || groupJoinData === void 0 ? void 0 : groupJoinData.group) === null || _k === void 0 ? void 0 : _k.id;
6781
6821
  // Trigger invite tracking before opening lobby (global for product)
6782
6822
  if (joinedGroupId) {
6783
6823
  try {
@@ -6809,7 +6849,7 @@ class WidgetRoot {
6809
6849
  }
6810
6850
  if (this.analyticsClient) {
6811
6851
  this.analyticsClient
6812
- .trackJoinFailure(productId, (_k = this.currentGroupId) !== null && _k !== void 0 ? _k : undefined, "EXCEPTION", error instanceof Error ? error.message : String(error))
6852
+ .trackJoinFailure(productId, (_l = this.currentGroupId) !== null && _l !== void 0 ? _l : undefined, "EXCEPTION", error instanceof Error ? error.message : String(error))
6813
6853
  .catch((e) => this.logger.warn("Analytics tracking failed", e));
6814
6854
  }
6815
6855
  this.setButtonLoadingState(false);
@@ -6826,7 +6866,7 @@ class WidgetRoot {
6826
6866
  const progress = Math.round((groupJoinData.group.participants_count / groupJoinData.group.max_participants) * 100);
6827
6867
  // Format discount based on reward type
6828
6868
  let discountText = "";
6829
- if ((_m = (_l = this.currentRewardData) === null || _l === void 0 ? void 0 : _l.reward) === null || _m === void 0 ? void 0 : _m.value) {
6869
+ if ((_o = (_m = this.currentRewardData) === null || _m === void 0 ? void 0 : _m.reward) === null || _o === void 0 ? void 0 : _o.value) {
6830
6870
  const rewardType = this.currentRewardData.reward.type;
6831
6871
  const rewardValue = this.currentRewardData.reward.value;
6832
6872
  if (rewardType === "percentage" || rewardType === "cashback") {
@@ -6866,7 +6906,7 @@ class WidgetRoot {
6866
6906
  shareMessage: shareMessageFromInvite,
6867
6907
  isLocked: !isGroupFulfilled,
6868
6908
  offlineRedemption: offlineRedemptionFromJoin,
6869
- redemptionMethod: (_o = groupJoinData.group.campaign_redemption_method) !== null && _o !== void 0 ? _o : this.campaignRedemptionMethod,
6909
+ redemptionMethod: (_p = groupJoinData.group.campaign_redemption_method) !== null && _p !== void 0 ? _p : this.campaignRedemptionMethod,
6870
6910
  onShare: this.analyticsClient
6871
6911
  ? () => {
6872
6912
  this.analyticsClient.trackShareClick(productId, groupJoinData.group.id, "other").catch((e) => this.logger.warn("Analytics tracking failed", e));
@@ -7044,6 +7084,7 @@ const API_ENDPOINTS = {
7044
7084
  GROUP_CREATE_AND_JOIN: "/v1/sdk/groups/new/join",
7045
7085
  PRODUCT_ACTIVE_GROUPS: "/v1/sdk/products/:productId/groups/active",
7046
7086
  GROUP_INVITE: "/v1/sdk/groups/:groupId/invite",
7087
+ GROUP_OFFLINE_REDEMPTION: "/v1/sdk/groups/:groupId/offline-redemption",
7047
7088
  GROUP_CHECKOUT_PREPARE: "/v1/sdk/groups/:groupId/checkout/prepare",
7048
7089
  GROUP_CHECKOUT_VALIDATE: "/v1/sdk/groups/:groupId/checkout/validate",
7049
7090
  GROUP_CHECKOUT_CONFIRM: "/v1/sdk/groups/:groupId/checkout/confirm",
@@ -8156,6 +8197,29 @@ class ApiClient {
8156
8197
  },
8157
8198
  };
8158
8199
  }
8200
+ /**
8201
+ * Fetch current session member's offline redemption codes for a group.
8202
+ */
8203
+ async getGroupOfflineRedemption(groupId) {
8204
+ const endpoint = buildApiUrl("", API_ENDPOINTS.GROUP_OFFLINE_REDEMPTION, {
8205
+ groupId,
8206
+ });
8207
+ const response = await this.get(endpoint);
8208
+ if (response.success) {
8209
+ const payload = response.data;
8210
+ return {
8211
+ success: true,
8212
+ data: (payload === null || payload === void 0 ? void 0 : payload.data) || {},
8213
+ };
8214
+ }
8215
+ return {
8216
+ success: false,
8217
+ error: response.error || {
8218
+ message: "Failed to fetch offline redemption",
8219
+ code: "OFFLINE_REDEMPTION_FETCH_ERROR",
8220
+ },
8221
+ };
8222
+ }
8159
8223
  async recoverOrJoinGroup(productId, contact) {
8160
8224
  var _a;
8161
8225
  const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_RECOVER_OR_JOIN_GROUP, {
@@ -14435,6 +14499,18 @@ class CoBuy {
14435
14499
  if ((_a = config.events) === null || _a === void 0 ? void 0 : _a.onModalClose) {
14436
14500
  config.events.onModalClose(options.productId);
14437
14501
  }
14502
+ // Sync widget state after lobby closes so fulfilled CTAs/group view
14503
+ // reflect socket-driven changes without requiring a page reload.
14504
+ const refreshPromises = [];
14505
+ this.widgets.forEach((w) => {
14506
+ const pid = typeof w.getProductId === "function"
14507
+ ? w.getProductId()
14508
+ : null;
14509
+ if (pid === options.productId && typeof w.requestRefresh === "function") {
14510
+ refreshPromises.push(w.requestRefresh());
14511
+ }
14512
+ });
14513
+ void Promise.all(refreshPromises.map((p) => p.then(() => undefined).catch(() => undefined)));
14438
14514
  },
14439
14515
  onCopyLink: options.onCopyLink,
14440
14516
  onShare: options.onShare,