@cshah18/sdk 4.13.0 → 4.15.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.
- package/dist/cobuy-sdk.esm.js +1657 -75
- package/dist/cobuy-sdk.esm.js.map +1 -1
- package/dist/cobuy-sdk.umd.js +1657 -75
- package/dist/cobuy-sdk.umd.js.map +1 -1
- package/dist/types/core/analytics.d.ts +55 -0
- package/dist/types/core/api-client.d.ts +33 -2
- package/dist/types/core/endpoints.d.ts +3 -0
- package/dist/types/core/socket.d.ts +3 -2
- package/dist/types/core/types.d.ts +118 -3
- package/dist/types/ui/group-list/group-list-modal.d.ts +4 -0
- package/dist/types/ui/lobby/lobby-modal.d.ts +49 -2
- package/dist/types/ui/widget/widget-root.d.ts +12 -0
- package/package.json +1 -1
package/dist/cobuy-sdk.umd.js
CHANGED
|
@@ -455,6 +455,8 @@ var CoBuySDK = (function (exports) {
|
|
|
455
455
|
debug,
|
|
456
456
|
events: options.events,
|
|
457
457
|
performance,
|
|
458
|
+
geo: options.geo,
|
|
459
|
+
device: options.device,
|
|
458
460
|
};
|
|
459
461
|
return this.config;
|
|
460
462
|
}
|
|
@@ -822,6 +824,7 @@ var CoBuySDK = (function (exports) {
|
|
|
822
824
|
this.currentJoinedGroupId = null;
|
|
823
825
|
this.currentProductId = null;
|
|
824
826
|
this.currentSessionId = null;
|
|
827
|
+
this.analyticsClient = null;
|
|
825
828
|
this.onGroupJoined = null;
|
|
826
829
|
this.onViewProgress = null;
|
|
827
830
|
this.socketListenerRegistered = false;
|
|
@@ -836,6 +839,8 @@ var CoBuySDK = (function (exports) {
|
|
|
836
839
|
/** Handle group member joined socket event and update groups list */
|
|
837
840
|
this.onGroupMemberJoined = (event) => {
|
|
838
841
|
const detail = event.detail || {};
|
|
842
|
+
console.log("eventttttt", detail);
|
|
843
|
+
const eventType = event.type;
|
|
839
844
|
const productId = detail.product_id;
|
|
840
845
|
const groupData = detail.group;
|
|
841
846
|
// Only process if this is for the current product
|
|
@@ -857,6 +862,16 @@ var CoBuySDK = (function (exports) {
|
|
|
857
862
|
if (typeof participantsCount === "number") {
|
|
858
863
|
this.groups[groupIndex].joined = participantsCount;
|
|
859
864
|
}
|
|
865
|
+
// If this session left the currently joined group, clear stale local membership state.
|
|
866
|
+
const leavingSessionId = detail.session_id;
|
|
867
|
+
if (eventType === "group:member:left" &&
|
|
868
|
+
this.currentSessionId &&
|
|
869
|
+
leavingSessionId === this.currentSessionId &&
|
|
870
|
+
this.currentJoinedGroupId === groupId) {
|
|
871
|
+
this.currentJoinedGroupId = null;
|
|
872
|
+
this.groups[groupIndex].isMember = false;
|
|
873
|
+
this.updateStartButtonState(false, this.isLoading);
|
|
874
|
+
}
|
|
860
875
|
this.logger.info(`[GroupListModal] Updated group ${groupId} - participants: ${participantsCount}`);
|
|
861
876
|
// Re-render the specific group card
|
|
862
877
|
this.updateGroupCard(groupId);
|
|
@@ -868,6 +883,10 @@ var CoBuySDK = (function (exports) {
|
|
|
868
883
|
this.apiClient = apiClient;
|
|
869
884
|
this.injectStyles();
|
|
870
885
|
}
|
|
886
|
+
/** Set the analytics client for event tracking */
|
|
887
|
+
setAnalyticsClient(client) {
|
|
888
|
+
this.analyticsClient = client;
|
|
889
|
+
}
|
|
871
890
|
/** Set callback for when a group is joined successfully */
|
|
872
891
|
setOnGroupJoined(callback) {
|
|
873
892
|
this.onGroupJoined = callback;
|
|
@@ -894,6 +913,7 @@ var CoBuySDK = (function (exports) {
|
|
|
894
913
|
return;
|
|
895
914
|
}
|
|
896
915
|
window.addEventListener("group:member:joined", this.handleGroupMemberJoinedEvent);
|
|
916
|
+
window.addEventListener("group:member:left", this.handleGroupMemberJoinedEvent);
|
|
897
917
|
this.socketListenerRegistered = true;
|
|
898
918
|
this.logger.debug("[GroupListModal] Socket event listeners registered");
|
|
899
919
|
}
|
|
@@ -903,6 +923,7 @@ var CoBuySDK = (function (exports) {
|
|
|
903
923
|
return;
|
|
904
924
|
}
|
|
905
925
|
window.removeEventListener("group:member:joined", this.handleGroupMemberJoinedEvent);
|
|
926
|
+
window.removeEventListener("group:member:left", this.handleGroupMemberJoinedEvent);
|
|
906
927
|
this.socketListenerRegistered = false;
|
|
907
928
|
this.logger.debug("[GroupListModal] Socket event listeners unregistered");
|
|
908
929
|
}
|
|
@@ -1034,13 +1055,11 @@ var CoBuySDK = (function (exports) {
|
|
|
1034
1055
|
console.log("groupsss", groups);
|
|
1035
1056
|
// If API reports membership, prefer it over cached state
|
|
1036
1057
|
const memberGroup = groups.find((g) => g.isMember);
|
|
1037
|
-
|
|
1038
|
-
this.currentJoinedGroupId = memberGroup.groupId;
|
|
1039
|
-
}
|
|
1058
|
+
this.currentJoinedGroupId = memberGroup ? memberGroup.groupId : null;
|
|
1040
1059
|
this.groups = groups;
|
|
1041
1060
|
this.liveCount = groups.length;
|
|
1042
1061
|
this.logger.info(`Fetched ${groups.length} active groups`);
|
|
1043
|
-
const hasMembership = Boolean(
|
|
1062
|
+
const hasMembership = Boolean(memberGroup);
|
|
1044
1063
|
this.updateStartButtonState(hasMembership, false);
|
|
1045
1064
|
// Update and show live count display
|
|
1046
1065
|
const liveCountText = document.getElementById("cobuy-live-count-text");
|
|
@@ -1125,10 +1144,21 @@ var CoBuySDK = (function (exports) {
|
|
|
1125
1144
|
buttonElement.disabled = true;
|
|
1126
1145
|
const originalText = button.textContent;
|
|
1127
1146
|
button.textContent = "Joining...";
|
|
1147
|
+
const productId = this.currentProductId || "";
|
|
1148
|
+
if (this.analyticsClient) {
|
|
1149
|
+
this.analyticsClient
|
|
1150
|
+
.trackJoinAttempt(productId, groupId)
|
|
1151
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
1152
|
+
}
|
|
1128
1153
|
try {
|
|
1129
1154
|
const response = await this.apiClient.joinGroup(groupId);
|
|
1130
1155
|
if (response.success && response.data) {
|
|
1131
1156
|
this.logger.info(`[handleJoinGroup] Successfully joined group: ${groupId}`, response.data);
|
|
1157
|
+
if (this.analyticsClient) {
|
|
1158
|
+
this.analyticsClient
|
|
1159
|
+
.trackJoinSuccess(productId, groupId)
|
|
1160
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
1161
|
+
}
|
|
1132
1162
|
// Track the joined group
|
|
1133
1163
|
this.currentJoinedGroupId = groupId;
|
|
1134
1164
|
// Close the modal and trigger callback with the full join response data
|
|
@@ -1139,12 +1169,22 @@ var CoBuySDK = (function (exports) {
|
|
|
1139
1169
|
}
|
|
1140
1170
|
else {
|
|
1141
1171
|
this.logger.error("Failed to join group: API response unsuccessful");
|
|
1172
|
+
if (this.analyticsClient) {
|
|
1173
|
+
this.analyticsClient
|
|
1174
|
+
.trackJoinFailure(productId, groupId)
|
|
1175
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
1176
|
+
}
|
|
1142
1177
|
button.textContent = originalText;
|
|
1143
1178
|
buttonElement.disabled = false;
|
|
1144
1179
|
}
|
|
1145
1180
|
}
|
|
1146
1181
|
catch (error) {
|
|
1147
1182
|
this.logger.error("Error joining group", error);
|
|
1183
|
+
if (this.analyticsClient) {
|
|
1184
|
+
this.analyticsClient
|
|
1185
|
+
.trackJoinFailure(productId, groupId, "EXCEPTION", error instanceof Error ? error.message : String(error))
|
|
1186
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
1187
|
+
}
|
|
1148
1188
|
button.textContent = originalText;
|
|
1149
1189
|
buttonElement.disabled = false;
|
|
1150
1190
|
}
|
|
@@ -1165,10 +1205,21 @@ var CoBuySDK = (function (exports) {
|
|
|
1165
1205
|
this.setJoinButtonsDisabled(true);
|
|
1166
1206
|
const originalText = button.textContent;
|
|
1167
1207
|
button.textContent = "Creating...";
|
|
1208
|
+
const productId = this.currentProductId;
|
|
1209
|
+
if (this.analyticsClient) {
|
|
1210
|
+
this.analyticsClient
|
|
1211
|
+
.trackGroupCreateAttempt(productId)
|
|
1212
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
1213
|
+
}
|
|
1168
1214
|
try {
|
|
1169
1215
|
const response = await this.apiClient.createAndJoinGroup(this.currentProductId);
|
|
1170
1216
|
if (response.success && response.data) {
|
|
1171
1217
|
this.logger.info("Successfully created and joined new group", response.data);
|
|
1218
|
+
if (this.analyticsClient) {
|
|
1219
|
+
this.analyticsClient
|
|
1220
|
+
.trackGroupCreateSuccess(productId, response.data.group.id)
|
|
1221
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
1222
|
+
}
|
|
1172
1223
|
this.currentJoinedGroupId = response.data.group.id;
|
|
1173
1224
|
this.close();
|
|
1174
1225
|
if (this.onGroupJoined) {
|
|
@@ -1177,6 +1228,11 @@ var CoBuySDK = (function (exports) {
|
|
|
1177
1228
|
}
|
|
1178
1229
|
else {
|
|
1179
1230
|
this.logger.error("Failed to create group: API response unsuccessful");
|
|
1231
|
+
if (this.analyticsClient) {
|
|
1232
|
+
this.analyticsClient
|
|
1233
|
+
.trackGroupCreateFailure(productId)
|
|
1234
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
1235
|
+
}
|
|
1180
1236
|
button.textContent = originalText;
|
|
1181
1237
|
buttonElement.disabled = false;
|
|
1182
1238
|
this.setJoinButtonsDisabled(false);
|
|
@@ -1184,6 +1240,11 @@ var CoBuySDK = (function (exports) {
|
|
|
1184
1240
|
}
|
|
1185
1241
|
catch (error) {
|
|
1186
1242
|
this.logger.error("Error creating group", error);
|
|
1243
|
+
if (this.analyticsClient) {
|
|
1244
|
+
this.analyticsClient
|
|
1245
|
+
.trackGroupCreateFailure(productId, "EXCEPTION", error instanceof Error ? error.message : String(error))
|
|
1246
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
1247
|
+
}
|
|
1187
1248
|
button.textContent = originalText;
|
|
1188
1249
|
buttonElement.disabled = false;
|
|
1189
1250
|
this.setJoinButtonsDisabled(false);
|
|
@@ -1356,7 +1417,7 @@ var CoBuySDK = (function (exports) {
|
|
|
1356
1417
|
createGroupCard(group) {
|
|
1357
1418
|
const card = document.createElement("div");
|
|
1358
1419
|
card.className = "cobuy-group-card";
|
|
1359
|
-
card.dataset.groupId = group.
|
|
1420
|
+
card.dataset.groupId = group.groupId;
|
|
1360
1421
|
const header = document.createElement("div");
|
|
1361
1422
|
header.className = "cobuy-group-card-header";
|
|
1362
1423
|
const groupId = document.createElement("div");
|
|
@@ -2109,15 +2170,24 @@ var CoBuySDK = (function (exports) {
|
|
|
2109
2170
|
}
|
|
2110
2171
|
}
|
|
2111
2172
|
|
|
2112
|
-
var css_248z = ".sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.cb-lobby-modal-container{height:100%;left:0;overflow-y:auto!important;position:fixed;top:0;width:100%;z-index:10000}.cb-lobby-modal-container *{box-sizing:border-box;margin:0;padding:0}.cb-lobby-modal-container body,.cb-lobby-modal-container html{font-family:Inter,sans-serif;height:100%;overflow-x:hidden;width:100%}.cb-lobby-main{backdrop-filter:blur(4px);background-color:color-mix(in oklab,#fff 5%,transparent);border-radius:50px;box-shadow:0 25px 50px -12px #00000040;padding:80px;position:relative;width:100%;z-index:1}.cb-lobby-bg{background-image:url(https://cobuy-dev.s3.af-south-1.amazonaws.com/public/sdk/cb-back-image.png);background-position:50%;background-repeat:no-repeat;background-size:cover;height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:-1}.cb-lobby-main-wp{align-items:center;display:flex;justify-content:center;padding:40px 80px}.lobby-indicator{align-items:center;backdrop-filter:blur(8px);background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.3);border-radius:24px;display:flex;gap:12px;margin-bottom:16px;padding:6px 12px;width:fit-content}.lobby-indicator-logo{display:block;flex-shrink:0;height:34px;width:auto}.pulsing-dot{align-items:center;display:flex;height:8px;justify-content:center;position:relative;width:8px}.pulsing-dot:after{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.pulsing-dot:after,.pulsing-dot:before{background:#10b981;border-radius:50%;content:\"\";height:100%;position:absolute;width:100%}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.indicator-text{color:#047857;font-size:11px;font-weight:800;letter-spacing:.5px;line-height:1;text-transform:uppercase}.lobby-status-section{display:flex;flex-direction:column;margin-bottom:24px}.lobby-status{background:#fff;border-radius:4px;color:#000;font-size:10px;font-weight:600;line-height:1.2;padding:6px 10px;text-transform:uppercase}.lobby-status.active{background:#155dfc;color:#fff}.lobby-status.complete{background:#10b981;color:#fff}.lobby-status-wp{align-items:center;display:flex;flex-wrap:wrap;gap:10px}.lobby-number{backdrop-filter:blur(3px);background:hsla(0,0%,100%,.2);border-radius:4px;box-shadow:0 25px 50px -12px #00000040;font-size:12px;font-weight:700;letter-spacing:1px;line-height:1.2;padding:5px 8px;text-transform:uppercase}.title-wp{margin-top:25px}.title-wp h2{font-size:60px;font-weight:900;line-height:1;margin-bottom:15px}.sub-title{-webkit-text-fill-color:transparent;animation:gradient-flow 4s linear infinite;background-clip:text;-webkit-background-clip:text;background-image:linear-gradient(90deg,#1e293b,#2563eb 25%,#1e293b 50%);background-size:200% auto;color:#1e293b;font-size:16px;font-weight:700;line-height:1.5;margin-bottom:0}@keyframes gradient-flow{0%{background-position:200% 0}to{background-position:-200% 0}}.sub-title.completed{-webkit-text-fill-color:unset;background-clip:unset;-webkit-background-clip:unset;background-image:none;color:#1e293b}.connected-section{backdrop-filter:blur(4px);background:hsla(0,0%,100%,.05);border:1px solid hsla(0,0%,100%,.1);border-radius:24px;box-shadow:0 1px 2px 0 rgba(0,0,0,.05);display:flex;flex-direction:column;gap:20px;margin-top:24px;padding:24px;transition:all .3s ease}.connected-section:hover{background:hsla(0,0%,100%,.1);box-shadow:0 2px 4px 0 rgba(0,0,0,.08)}.link-share-container{display:flex;flex-direction:column}.link-share-wrapper{align-items:center;display:flex;gap:12px;width:100%}.lobby-link-box{align-items:center;backdrop-filter:blur(8px);background-color:hsla(0,0%,100%,.4);border:1px solid hsla(0,0%,100%,.3);border-radius:14px;box-shadow:0 1px 2px 0 rgba(0,0,0,.05);display:flex;flex:1;gap:16px;justify-content:space-between;min-height:50px;padding:16px 20px;transition:all .2s ease}.lobby-link-box:hover{background-color:hsla(0,0%,100%,.5);box-shadow:0 2px 4px 0 rgba(0,0,0,.08)}.lobby-link-text{color:#71717a;font-size:10px;font-weight:700;letter-spacing:.6px;line-height:1.2;margin-bottom:6px;text-transform:uppercase}.copy-link-btn{align-items:center;background:transparent;border:none;border-radius:6px;color:#64748b;cursor:pointer;display:flex;flex-shrink:0;font-size:13px;font-weight:600;gap:6px;justify-content:center;padding:0;transition:all .2s ease;white-space:nowrap}.copy-link-btn:hover{color:#1e293b}.copy-link-btn .copy-text{display:none}.copy-link-btn svg{flex-shrink:0;height:18px;width:18px}@media (min-width:640px){.copy-link-btn .copy-text{display:inline}}.lobby-link-url{color:#1e293b;font-size:15px;font-weight:700;line-height:1.4;word-break:break-all}.link-box-container{flex:1;min-width:0}.share-btn{align-items:center;background:#1e293b;border:none;border-radius:14px;box-shadow:0 2px 4px 0 rgba(0,0,0,.1);color:#fff;cursor:pointer;display:flex;flex-shrink:0;font-size:15px;font-weight:600;gap:8px;height:50px;justify-content:center;min-width:auto;padding:35px 24px;transition:all .2s ease}.share-btn .share-text{color:#fff;display:inline;font-size:18px;margin:0!important}.share-btn svg{color:#fff;flex-shrink:0;height:18px;width:18px}@media (max-width:640px){.share-btn{font-size:14px;height:50px;padding:0 18px}.share-btn .share-text{color:#fff;display:inline}}.share-btn:hover{background:#0f172a;box-shadow:0 4px 8px 0 rgba(0,0,0,.15);transform:translateY(-1px)}.share-btn:active{transform:scale(.95)}.lobby-offer-box{align-items:center;backdrop-filter:blur(8px);background-color:rgba(59,130,246,.063);border:1px solid rgba(59,130,246,.125);border-radius:1rem;display:flex;gap:30px;margin-top:30px;padding:15px 20px}.offer-box-icon{align-items:center;background:linear-gradient(135deg,#3b82f6,rgba(59,130,246,.867));border-radius:10px;box-shadow:0 10px 15px -3px rgba(59,130,246,.314);color:#fff;cursor:pointer;display:flex;height:56px;justify-content:center;padding:5px;width:56px}.offer-lock-status{align-items:center;display:flex;gap:6px;margin-bottom:2px}.offer-lock-status span{font-size:14px;font-weight:700;line-height:1}.offer-box-content h3{font-size:30px;font-weight:900;line-height:1.2}.cb-lobby-top{display:grid;gap:100px;grid-template-columns:7fr 5fr}.group-info{backdrop-filter:blur(8px);background-color:color-mix(in oklab,#fff 5%,transparent);border-radius:20px;box-shadow:0 10px 15px -3px #0000001a;padding:30px}.progress-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:14px}.progress-header .title{align-items:center;color:#000;display:flex;font-size:14px;font-weight:900;justify-content:center;letter-spacing:.7px;position:relative}.progress-badge{background:#3b82f6;border-radius:999px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;color:#fff;font-size:13px;font-weight:700;padding:6px 12px}.cb-lobby-modal-container .progress-bar{background:#fff;border-radius:999px;height:14px;overflow:hidden;width:100%}.cb-lobby-modal-container .progress-fill{animation:shimmer 2.7s linear infinite;background:#3b82f6;background-image:linear-gradient(120deg,hsla(0,0%,100%,.15) 25%,hsla(0,0%,100%,.45) 37%,hsla(0,0%,100%,.15) 63%);background-size:200% 100%;border-radius:999px;height:100%;position:relative;transition:width .6s ease;width:0}@keyframes shimmer{0%{background-position:-200% 0}to{background-position:200% 0}}.progress-labels{color:#474d56;display:flex;font-size:12px;justify-content:space-between;margin-top:8px}.team-card{margin-top:40px}.team-card .icon-box svg{width:16px}.team-card .team-header{align-items:center;display:flex;justify-content:space-between}.team-card .team-title{align-items:center;display:flex;font-size:14px;font-weight:600;gap:12px;letter-spacing:1px}.team-card .icon-box{align-items:center;background-color:#3b82f6;border-radius:.5rem;box-shadow:0 10px 15px -3px #3b82f64d;color:#fff;display:flex;font-size:18px;height:2rem;justify-content:center;width:2rem}.team-card .team-title span{color:#000;font-size:14px;font-weight:900;letter-spacing:.7px}.team-card .team-count{background-color:#3b82f6;border-radius:9999px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;color:#fff;font-size:13px;font-weight:700;letter-spacing:.7px;padding:6px 12px}.team-card .team-members{align-items:center;display:flex;gap:14px;justify-content:center;margin-top:20px}.team-card .member{border:3px solid #fff;border-radius:50%;box-shadow:0 4px 12px #0000001a;font-size:22px;height:56px;position:relative;width:56px}.team-card .member,.team-card .member:after{align-items:center;color:#fff;display:flex;justify-content:center}.team-card .member:after{background:#3b82f6;background-image:url(https://cobuy-dev.s3.af-south-1.amazonaws.com/public/sdk/tick-mark-icon.svg);background-position:50%;background-repeat:no-repeat;background-size:75%;border:2px solid #fff;border-radius:50%;bottom:-4px;content:\"\";height:12px;padding:2px;position:absolute;right:-4px;width:12px}.team-card .member.blue{background:#2563eb}.team-card .member.purple{background:#9333ea}.team-card .member.pink{background:#ec4899}.team-card .member.orange{background:#f97316}.team-card .member.empty{background:#f8fafc;color:#9ca3af}.team-card .member.empty:after{display:none}.time-card{align-items:center;backdrop-filter:blur(8px);background:hsla(0,0%,100%,.2);border:1px solid hsla(0,0%,100%,.2);border-radius:20px;display:flex;gap:14px;margin-top:30px;padding:15px}.time-card .time-icon{align-items:center;background:#3b82f6;border-radius:14px;color:#fff;display:flex;flex-shrink:0;font-size:22px;height:48px;justify-content:center;width:48px}.time-card .time-content{display:flex;flex-direction:column}.time-card .time-label{color:#4b5563;font-size:12px;font-weight:600;letter-spacing:.6px}.time-card .time-value{color:#111827;font-size:22px;font-weight:900}.group-info-box{backdrop-filter:blur(8px);background:color-mix(in oklab,#fff 5%,transparent);border:1px solid color-mix(in oklab,#fff 20%,transparent);border-radius:1.5rem;box-shadow:0 10px 15px -3px #0000001a;padding:30px}.offer-lock-status svg{height:16px;width:16px}.cb-lobby-modal-container .offer-box-content .reward-text{background:unset;border:none;color:#000;font-size:14px;line-height:1;margin-top:4px}.progress-header .title:before{background:#3b82f6;border-radius:50%;content:\"\";display:inline-block;height:8px;margin-right:8px;position:relative;width:8px}.live-activity-wrapper{backdrop-filter:blur(8px);background-color:color-mix(in oklab,#fff 80%,transparent);border-radius:18px;box-shadow:0 30px 80px rgba(0,0,0,.15);padding:16px}.live-activity-header{display:flex;gap:10px;justify-content:space-between;margin-bottom:12px}.live-activity-header .title{align-items:center;color:#000;display:flex;font-size:14px;font-weight:900;gap:8px;letter-spacing:.7px}.live-activity-header .dot{background:#3b82f6;border-radius:50%;height:8px;position:relative;width:8px}.live-activity-header .dot:after{animation:livePulse 1.6s ease-out infinite;background:rgba(59,130,246,.6);border-radius:50%;content:\"\";inset:0;position:absolute}@keyframes livePulse{0%{opacity:.8;transform:scale(1)}70%{opacity:0;transform:scale(2.4)}to{opacity:0}}.activity-stats{align-items:center;display:flex;gap:8px}.activity-stats-badge{background-color:rgba(59,130,246,.082);border-radius:999px;color:#3b82f6;font-size:12px;font-weight:500;line-height:1;padding:4px 10px}.activity-stats-badge.light{background-color:#f9f3f4;color:#45556c}.activity-list{height:104px;overflow:hidden;position:relative}.activity-card{align-items:center;background:linear-gradient(90deg,#fff,#f2f6ff);border:1px solid #dbeafe;border-radius:14px;display:flex;gap:10px;height:58px;inset:0;padding:12px 10px;position:absolute;transition:transform .6s cubic-bezier(.22,.61,.36,1),opacity .6s}.activity-card .text{font-size:14px}.activity-card .avatar{align-items:center;border-radius:50%;color:#fff;display:flex;flex:0 0 30px;height:30px;justify-content:center;width:30px}.activity-card .pink{background:linear-gradient(135deg,#ec4899,#f472b6)}.activity-card .purple{background:linear-gradient(135deg,#8b5cf6,#a78bfa)}.activity-card .blue{background:linear-gradient(135deg,#3b82f6,#60a5fa)}.activity-card .green{background:linear-gradient(135deg,#10b981,#34d399)}.activity-card .orange{background:linear-gradient(135deg,#f97316,#fb923c)}.activity-card .text p{color:#6b7280;font-size:12px;margin:2px 0 0}.activity-card .time{color:#94a3b8;font-size:10px;margin-left:auto}.lobby-activity-wp{backdrop-filter:blur(12px);background-color:hsla(0,0%,100%,.4);border:1px solid hsla(0,0%,100%,.2);border-radius:25px;box-shadow:0 1px 3px 0 #0000001a;margin-top:50px;padding:8px}.lobby-close-icon{align-items:center;background-color:color-mix(in oklab,#000 80%,transparent);border-radius:50%;color:#fff;cursor:pointer;display:flex;height:34px;justify-content:center;position:fixed;right:30px;top:30px;width:34px;z-index:99}.lobby-close-icon svg{height:20px;width:20px}@media screen and (max-width:1600px){.cb-lobby-main{border-radius:30px;padding:50px}.title-wp h2{font-size:48px;margin-bottom:12px}.sub-title{font-size:16px}.title-wp{margin-top:20px}.lobby-link-section{margin-top:30px}.offer-box-content h3{font-size:26px}.lobby-offer-box{gap:24px;padding:15px}.cb-lobby-top{gap:80px}.group-info-box{border-radius:20px;padding:22px}.team-card{margin-top:30px}.team-card .team-members{margin-top:12px}.lobby-activity-wp{margin-top:40px}.lobby-close-icon{height:30px;top:20px;width:30px}}@media screen and (max-width:1280px){.cb-lobby-main,.cb-lobby-main-wp{padding:40px}.title-wp h2{font-size:42px}.cb-lobby-top{gap:60px}}@media screen and (max-width:1120px){.title-wp h2{font-size:38px}}@media screen and (max-width:991px){.cb-lobby-top{gap:30px;grid-template-columns:1fr}.cb-lobby-main{border-radius:15px;padding:30px}.cb-lobby-main-wp{padding:30px}.lobby-close-icon{right:20px}.lobby-link-box{border-radius:10px}.share-btn{border-radius:8px}.offer-box-content h3{font-size:24px}.lobby-activity-wp,.lobby-offer-box,.time-card{border-radius:15px;margin-top:20px}.live-activity-wrapper{border-radius:10px}.group-info-box{border-radius:15px}}@media screen and (max-width:767px){.link-share-wrapper{flex-direction:column!important}}@media screen and (max-width:575px){.cb-lobby-main,.cb-lobby-main-wp{padding:20px}.title-wp h2{font-size:30px}.lobby-link-text{font-size:11px;margin-bottom:4px}.lobby-link-url{font-size:14px}.lobby-link-box{min-height:48px;padding:12px 16px}.link-share-wrapper{gap:10px}.share-btn{border-radius:8px;font-size:13px;height:48px;min-width:auto;padding:0 14px}.lobby-offer-box{padding:10px}.offer-box-content h3{font-size:20px}.offer-box-content .reward-text{font-size:13px}.group-info-box{padding:13px}.progress-header .title,.team-card .team-title span{font-size:13px}.team-card .member{height:40px;width:40px}.team-card .member:after{height:8px;width:8px}.team-card .team-members{gap:10px;margin-top:12px}.time-card{padding:13px}.team-card{margin-top:22px}.activity-card .text{font-size:12px}.activity-card .text p{font-size:11px}.live-activity-header .title{font-size:12px}.time-card .time-value{font-size:20px}}@media screen and (max-width:480px){.cb-lobby-main-wp{padding:20px 12px}.cb-lobby-main{padding:15px 12px}.lobby-status{font-size:9px}.lobby-number{font-size:10px}.title-wp h2{font-size:28px;margin-bottom:7px}.sub-title{font-size:14px}.lobby-link-section{gap:10px;margin-top:20px}.lobby-offer-box{gap:12px}.offer-box-icon{height:45px;width:45px}.offer-box-content .reward-text,.offer-lock-status span{font-size:12px}.cb-lobby-top{gap:20px}.lobby-close-icon{right:10px;top:10px}.live-activity-header{flex-direction:column;gap:5px}.activity-card{border-radius:9px;padding:6px}}.share-overlay{align-items:center;background:rgba(0,0,0,.45);display:flex;inset:0;justify-content:center;opacity:0;position:fixed;transition:.3s ease;visibility:hidden;z-index:999}.share-overlay.active{opacity:1;visibility:visible}.share-popup{background:#fff;border-radius:18px;max-height:90%;overflow:auto;padding:24px;position:relative;transform:translateY(30px);transition:.35s ease;width:420px}.share-overlay.active .share-popup{transform:translateY(0)}.share-popup .close-btn{align-items:center;background:#f3f4f6;border:none;border-radius:50%;cursor:pointer;display:flex;height:32px;justify-content:center;position:absolute;right:14px;top:14px;transition:.3s;width:32px}.share-popup .close-btn:hover{background:#eeecec}.share-popup h2{font-size:22px;font-weight:600;line-height:1;margin-bottom:10px}.share-popup .subtitle{color:#4a5565;font-size:14px;margin:6px 0 30px}.share-popup .share-grid{display:grid;gap:14px;grid-template-columns:repeat(2,1fr)}.share-popup .share-card{align-items:center;background:#f2f6f9;border-radius:14px;cursor:pointer;display:flex;flex-direction:column;padding:16px;position:relative;text-align:center;transition:.25s ease}.share-popup .share-card:hover{transform:translateY(-3px)}.share-popup .share-card .icon{align-items:center;background:#1877f2;border-radius:50%;color:#fff;display:flex;font-size:30px;height:50px;justify-content:center;margin-bottom:8px;text-align:center;width:50px}.share-popup .share-card span{font-size:13px;font-weight:500}.share-popup .share-card.whatsapp{background:#ecfdf5;border:2px solid #22c55e;color:#22c55e}.share-popup .share-card.whatsapp .icon{background:#22c55e}.share-popup .share-card .badge{background:#2563eb;border-radius:12px;color:#fff;font-size:11px;padding:4px 10px;position:absolute;right:10px;top:-8px}.share-popup .link-box{background:#fbf9fa;border:1px solid #ebe6e7;border-radius:14px;margin-top:18px;padding:14px}.share-popup .link-box label{color:#64748b;font-size:13px}.share-popup .link-row{display:flex;gap:8px;margin-top:6px}.share-popup .link-row input{background:transparent;border:none;color:#334155;flex:1;font-size:13px;letter-spacing:.8px;line-height:1.2;outline:none}.share-popup .link-row button{align-items:center;background:#fff;border:1px solid #ebe6e7;border-radius:10px;cursor:pointer;display:flex;height:35px;justify-content:center;padding:6px 8px;width:35px}.share-popup .success{color:#2563eb;display:block;font-size:12px;margin-top:6px;opacity:0;transition:opacity .3s}.share-popup .success.show{opacity:1}.share-popup .footer-text{color:#64748b;font-size:12px;margin-top:14px;text-align:center}.share-popup .share-card.twitter .icon{background:#000}.share-popup .share-card.facebook .icon{background:#1877f2}.share-popup .share-card.sms .icon{background:#059669}.share-popup .share-card.copied .icon{background:#2563eb}@keyframes entrance-fade-in{0%{opacity:0}to{opacity:1}}@keyframes entrance-scale-in{0%{opacity:0;transform:scale(.9) translateY(20px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes entrance-text-fade-in{0%{opacity:0}to{opacity:1}}@keyframes exit-blur-scale{0%{filter:blur(0);opacity:1;transform:scale(1)}to{filter:blur(10px);opacity:0;transform:scale(1.1)}}@keyframes pulse-dot{0%,to{opacity:1}50%{opacity:.5}}.entrance-animation-overlay{align-items:center;animation:entrance-fade-in .8s ease-in-out forwards;background-color:#0f172a;color:#fff;display:flex;flex-direction:column;inset:0;justify-content:center;position:fixed;z-index:9999}.entrance-animation-overlay.exit{animation:exit-blur-scale .8s ease-in-out forwards}.entrance-content{align-items:center;animation:entrance-scale-in .5s ease-out .2s both;display:flex;flex-direction:column}.entrance-icon-box{align-items:center;background:#2563eb;border-radius:16px;box-shadow:0 10px 25px rgba(37,99,235,.2);display:flex;height:64px;justify-content:center;margin-bottom:24px;width:64px}.entrance-icon-box svg{color:#fff;height:32px;width:32px}.entrance-title{font-size:32px;font-weight:700;letter-spacing:-.5px;margin-bottom:12px}@media (min-width:768px){.entrance-title{font-size:48px}}.entrance-message{align-items:center;animation:entrance-text-fade-in .5s ease-out .5s both;color:#60a5fa;display:flex;font-size:18px;font-weight:500;gap:8px}.entrance-pulse-dot{animation:pulse-dot 1.5s ease-in-out infinite;background-color:#60a5fa;border-radius:50%;height:8px;width:8px}";
|
|
2173
|
+
var css_248z = ".sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.cb-lobby-modal-container{height:100%;left:0;overflow-y:auto!important;position:fixed;top:0;width:100%;z-index:10000}.cb-lobby-modal-container *{box-sizing:border-box;margin:0;padding:0}.cb-lobby-modal-container body,.cb-lobby-modal-container html{font-family:Inter,sans-serif;height:100%;overflow-x:hidden;width:100%}.cb-lobby-main{backdrop-filter:blur(4px);background-color:color-mix(in oklab,#fff 5%,transparent);border-radius:50px;box-shadow:0 25px 50px -12px #00000040;padding:80px;position:relative;width:100%;z-index:1}.cb-lobby-bg{background-image:url(https://cobuy-dev.s3.af-south-1.amazonaws.com/public/sdk/cb-back-image.png);background-position:50%;background-repeat:no-repeat;background-size:cover;height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:-1}.cb-lobby-main-wp{align-items:center;display:flex;justify-content:center;padding:40px 80px}.lobby-indicator{align-items:center;backdrop-filter:blur(8px);background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.3);border-radius:24px;display:flex;gap:12px;margin-bottom:16px;padding:6px 12px;width:fit-content}.lobby-indicator-logo{display:block;flex-shrink:0;height:34px;width:auto}.pulsing-dot{align-items:center;display:flex;height:8px;justify-content:center;position:relative;width:8px}.pulsing-dot:after{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.pulsing-dot:after,.pulsing-dot:before{background:#10b981;border-radius:50%;content:\"\";height:100%;position:absolute;width:100%}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.indicator-text{color:#047857;font-size:11px;font-weight:800;letter-spacing:.5px;line-height:1;text-transform:uppercase}.lobby-status-section{display:flex;flex-direction:column;margin-bottom:24px}.lobby-status{background:#fff;border-radius:4px;color:#000;font-size:10px;font-weight:600;line-height:1.2;padding:6px 10px;text-transform:uppercase}.lobby-status.active{background:#155dfc;color:#fff}.lobby-status.complete{background:#10b981;color:#fff}.lobby-status-wp{align-items:center;display:flex;flex-wrap:wrap;gap:10px}.lobby-number{backdrop-filter:blur(3px);background:hsla(0,0%,100%,.2);border-radius:4px;box-shadow:0 25px 50px -12px #00000040;font-size:12px;font-weight:700;letter-spacing:1px;line-height:1.2;padding:5px 8px;text-transform:uppercase}.title-wp{margin-top:25px}.title-wp h2{font-size:60px;font-weight:900;line-height:1;margin-bottom:15px}.sub-title{-webkit-text-fill-color:transparent;animation:gradient-flow 4s linear infinite;background-clip:text;-webkit-background-clip:text;background-image:linear-gradient(90deg,#1e293b,#2563eb 25%,#1e293b 50%);background-size:200% auto;color:#1e293b;font-size:16px;font-weight:700;line-height:1.5;margin-bottom:0}@keyframes gradient-flow{0%{background-position:200% 0}to{background-position:-200% 0}}.sub-title.completed{-webkit-text-fill-color:unset;background-clip:unset;-webkit-background-clip:unset;background-image:none;color:#1e293b}.connected-section{backdrop-filter:blur(4px);background:hsla(0,0%,100%,.05);border:1px solid hsla(0,0%,100%,.1);border-radius:24px;box-shadow:0 1px 2px 0 rgba(0,0,0,.05);display:flex;flex-direction:column;gap:20px;margin-top:24px;padding:24px;transition:all .3s ease}.connected-section:hover{background:hsla(0,0%,100%,.1);box-shadow:0 2px 4px 0 rgba(0,0,0,.08)}.lobby-contact-protection-banner{align-items:center;background:#fffbeb;border:1px solid #f59e0b;border-radius:12px;display:flex;gap:12px;justify-content:space-between;padding:12px 14px}.lobby-contact-protection-copy{color:#7c2d12;font-size:13px;font-weight:600;line-height:1.4}.lobby-contact-protection-btn{background:#1e293b;border:none;border-radius:8px;color:#fff;cursor:pointer;font-size:12px;font-weight:700;padding:8px 12px;white-space:nowrap}.lobby-contact-protection-btn:hover{background:#0f172a}.lobby-protected-leave-container{align-items:center;background:#f0fdf4;border:1px solid #86efac;border-radius:12px;display:none;gap:12px;justify-content:space-between;padding:12px 14px}.lobby-protected-leave-copy{color:#166534;font-size:13px;font-weight:600;line-height:1.4}.lobby-protected-leave-btn{background:#b91c1c;border:none;border-radius:8px;color:#fff;cursor:pointer;font-size:12px;font-weight:700;padding:8px 12px;white-space:nowrap}.lobby-protected-leave-btn:hover{background:#991b1b}.lobby-contact-overlay{align-items:center;background:rgba(2,6,23,.5);display:flex;inset:0;justify-content:center;padding:20px;position:fixed;z-index:10020}.lobby-contact-card{background:#fff;border-radius:16px;box-shadow:0 12px 32px rgba(15,23,42,.25);display:flex;flex-direction:column;gap:12px;padding:20px;width:min(460px,100%)}.lobby-contact-title{color:#0f172a;font-size:22px;font-weight:800;line-height:1.2}.lobby-contact-subtitle{color:#334155;font-size:14px;line-height:1.5}.lobby-contact-input{border:1px solid #cbd5e1;border-radius:10px;font-size:14px;outline:none;padding:11px 12px;width:100%}.lobby-contact-input:focus{border-color:#1d4ed8;box-shadow:0 0 0 3px rgba(59,130,246,.2)}.lobby-contact-actions{align-items:center;display:flex;flex-wrap:wrap;gap:10px}.lobby-contact-danger,.lobby-contact-primary,.lobby-contact-secondary{border:none;border-radius:10px;cursor:pointer;font-size:13px;font-weight:700;padding:10px 14px}.lobby-contact-danger:disabled,.lobby-contact-primary:disabled,.lobby-contact-secondary:disabled{cursor:not-allowed;opacity:.7}.lobby-contact-primary.is-loading{padding-right:34px;position:relative}.lobby-contact-primary.is-loading:after{animation:lobby-spin .8s linear infinite;border:2px solid hsla(0,0%,100%,.45);border-radius:50%;border-top-color:#fff;content:\"\";height:14px;margin-top:-7px;position:absolute;right:12px;top:50%;width:14px}@keyframes lobby-spin{to{transform:rotate(1turn)}}.lobby-contact-primary{background:#1d4ed8;color:#fff}.lobby-contact-primary:hover{background:#1e40af}.lobby-contact-secondary{background:#e2e8f0;color:#1e293b}.lobby-contact-secondary:hover{background:#cbd5e1}.lobby-contact-danger{background:#fee2e2;color:#991b1b}.lobby-contact-danger:hover{background:#fecaca}.lobby-contact-toast{background:#0f172a;border-radius:10px;bottom:30px;box-shadow:0 8px 20px rgba(0,0,0,.25);color:#fff;font-size:13px;font-weight:600;left:50%;padding:10px 14px;position:fixed;transform:translateX(-50%);z-index:10030}.lobby-blocking-loader-overlay{align-items:center;background:rgba(2,6,23,.45);display:flex;inset:0;justify-content:center;padding:20px;position:fixed;z-index:10035}.lobby-blocking-loader-card{align-items:center;background:#fff;border-radius:12px;box-shadow:0 12px 30px rgba(15,23,42,.24);display:flex;gap:10px;padding:16px 18px}.lobby-blocking-loader-spinner{animation:lobby-spin .8s linear infinite;border:2px solid #cbd5e1;border-radius:50%;border-top-color:#1d4ed8;height:16px;width:16px}.lobby-blocking-loader-text{color:#0f172a;font-size:14px;font-weight:600;margin:0}.link-share-container{display:flex;flex-direction:column}.link-share-wrapper{align-items:center;display:flex;gap:12px;width:100%}.lobby-link-box{align-items:center;backdrop-filter:blur(8px);background-color:hsla(0,0%,100%,.4);border:1px solid hsla(0,0%,100%,.3);border-radius:14px;box-shadow:0 1px 2px 0 rgba(0,0,0,.05);display:flex;flex:1;gap:16px;justify-content:space-between;min-height:50px;padding:16px 20px;transition:all .2s ease}.lobby-link-box:hover{background-color:hsla(0,0%,100%,.5);box-shadow:0 2px 4px 0 rgba(0,0,0,.08)}.lobby-link-text{color:#71717a;font-size:10px;font-weight:700;letter-spacing:.6px;line-height:1.2;margin-bottom:6px;text-transform:uppercase}.copy-link-btn{align-items:center;background:transparent;border:none;border-radius:6px;color:#64748b;cursor:pointer;display:flex;flex-shrink:0;font-size:13px;font-weight:600;gap:6px;justify-content:center;padding:0;transition:all .2s ease;white-space:nowrap}.copy-link-btn:hover{color:#1e293b}.copy-link-btn .copy-text{display:none}.copy-link-btn svg{flex-shrink:0;height:18px;width:18px}@media (min-width:640px){.copy-link-btn .copy-text{display:inline}}@media (max-width:640px){.lobby-contact-protection-banner{align-items:flex-start;flex-direction:column}.lobby-contact-protection-btn{width:100%}.lobby-protected-leave-container{align-items:flex-start;flex-direction:column}.lobby-protected-leave-btn{width:100%}}.lobby-link-url{color:#1e293b;font-size:15px;font-weight:700;line-height:1.4;word-break:break-all}.link-box-container{flex:1;min-width:0}.share-btn{align-items:center;background:#1e293b;border:none;border-radius:14px;box-shadow:0 2px 4px 0 rgba(0,0,0,.1);color:#fff;cursor:pointer;display:flex;flex-shrink:0;font-size:15px;font-weight:600;gap:8px;height:50px;justify-content:center;min-width:auto;padding:35px 24px;transition:all .2s ease}.share-btn .share-text{color:#fff;display:inline;font-size:18px;margin:0!important}.share-btn svg{color:#fff;flex-shrink:0;height:18px;width:18px}@media (max-width:640px){.share-btn{font-size:14px;height:50px;padding:0 18px}.share-btn .share-text{color:#fff;display:inline}}.share-btn:hover{background:#0f172a;box-shadow:0 4px 8px 0 rgba(0,0,0,.15);transform:translateY(-1px)}.share-btn:active{transform:scale(.95)}.lobby-offer-box{align-items:center;backdrop-filter:blur(8px);background-color:rgba(59,130,246,.063);border:1px solid rgba(59,130,246,.125);border-radius:1rem;display:flex;gap:30px;margin-top:30px;padding:15px 20px}.offer-box-icon{align-items:center;background:linear-gradient(135deg,#3b82f6,rgba(59,130,246,.867));border-radius:10px;box-shadow:0 10px 15px -3px rgba(59,130,246,.314);color:#fff;cursor:pointer;display:flex;height:56px;justify-content:center;padding:5px;width:56px}.offer-lock-status{align-items:center;display:flex;gap:6px;margin-bottom:2px}.offer-lock-status span{font-size:14px;font-weight:700;line-height:1}.offer-box-content h3{font-size:30px;font-weight:900;line-height:1.2}.cb-lobby-top{display:grid;gap:100px;grid-template-columns:7fr 5fr}.group-info{backdrop-filter:blur(8px);background-color:color-mix(in oklab,#fff 5%,transparent);border-radius:20px;box-shadow:0 10px 15px -3px #0000001a;padding:30px}.progress-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:14px}.progress-header .title{align-items:center;color:#000;display:flex;font-size:14px;font-weight:900;justify-content:center;letter-spacing:.7px;position:relative}.progress-badge{background:#3b82f6;border-radius:999px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;color:#fff;font-size:13px;font-weight:700;padding:6px 12px}.cb-lobby-modal-container .progress-bar{background:#fff;border-radius:999px;height:14px;overflow:hidden;width:100%}.cb-lobby-modal-container .progress-fill{animation:shimmer 2.7s linear infinite;background:#3b82f6;background-image:linear-gradient(120deg,hsla(0,0%,100%,.15) 25%,hsla(0,0%,100%,.45) 37%,hsla(0,0%,100%,.15) 63%);background-size:200% 100%;border-radius:999px;height:100%;position:relative;transition:width .6s ease;width:0}@keyframes shimmer{0%{background-position:-200% 0}to{background-position:200% 0}}.progress-labels{color:#474d56;display:flex;font-size:12px;justify-content:space-between;margin-top:8px}.team-card{margin-top:40px}.team-card .icon-box svg{width:16px}.team-card .team-header{align-items:center;display:flex;justify-content:space-between}.team-card .team-title{align-items:center;display:flex;font-size:14px;font-weight:600;gap:12px;letter-spacing:1px}.team-card .icon-box{align-items:center;background-color:#3b82f6;border-radius:.5rem;box-shadow:0 10px 15px -3px #3b82f64d;color:#fff;display:flex;font-size:18px;height:2rem;justify-content:center;width:2rem}.team-card .team-title span{color:#000;font-size:14px;font-weight:900;letter-spacing:.7px}.team-card .team-count{background-color:#3b82f6;border-radius:9999px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;color:#fff;font-size:13px;font-weight:700;letter-spacing:.7px;padding:6px 12px}.team-card .team-members{align-items:center;display:flex;gap:14px;justify-content:center;margin-top:20px}.team-card .member{border:3px solid #fff;border-radius:50%;box-shadow:0 4px 12px #0000001a;font-size:22px;height:56px;position:relative;width:56px}.team-card .member,.team-card .member:after{align-items:center;color:#fff;display:flex;justify-content:center}.team-card .member:after{background:#3b82f6;background-image:url(https://cobuy-dev.s3.af-south-1.amazonaws.com/public/sdk/tick-mark-icon.svg);background-position:50%;background-repeat:no-repeat;background-size:75%;border:2px solid #fff;border-radius:50%;bottom:-4px;content:\"\";height:12px;padding:2px;position:absolute;right:-4px;width:12px}.team-card .member.blue{background:#2563eb}.team-card .member.purple{background:#9333ea}.team-card .member.pink{background:#ec4899}.team-card .member.orange{background:#f97316}.team-card .member.empty{background:#f8fafc;color:#9ca3af}.team-card .member.empty:after{display:none}.time-card{align-items:center;backdrop-filter:blur(8px);background:hsla(0,0%,100%,.2);border:1px solid hsla(0,0%,100%,.2);border-radius:20px;display:flex;gap:14px;margin-top:30px;padding:15px}.time-card .time-icon{align-items:center;background:#3b82f6;border-radius:14px;color:#fff;display:flex;flex-shrink:0;font-size:22px;height:48px;justify-content:center;width:48px}.time-card .time-content{display:flex;flex-direction:column}.time-card .time-label{color:#4b5563;font-size:12px;font-weight:600;letter-spacing:.6px}.time-card .time-value{color:#111827;font-size:22px;font-weight:900}.group-info-box{backdrop-filter:blur(8px);background:color-mix(in oklab,#fff 5%,transparent);border:1px solid color-mix(in oklab,#fff 20%,transparent);border-radius:1.5rem;box-shadow:0 10px 15px -3px #0000001a;padding:30px}.offer-lock-status svg{height:16px;width:16px}.cb-lobby-modal-container .offer-box-content .reward-text{background:unset;border:none;color:#000;font-size:14px;line-height:1;margin-top:4px}.progress-header .title:before{background:#3b82f6;border-radius:50%;content:\"\";display:inline-block;height:8px;margin-right:8px;position:relative;width:8px}.live-activity-wrapper{backdrop-filter:blur(8px);background-color:color-mix(in oklab,#fff 80%,transparent);border-radius:18px;box-shadow:0 30px 80px rgba(0,0,0,.15);padding:16px}.live-activity-header{display:flex;gap:10px;justify-content:space-between;margin-bottom:12px}.live-activity-header .title{align-items:center;color:#000;display:flex;font-size:14px;font-weight:900;gap:8px;letter-spacing:.7px}.live-activity-header .dot{background:#3b82f6;border-radius:50%;height:8px;position:relative;width:8px}.live-activity-header .dot:after{animation:livePulse 1.6s ease-out infinite;background:rgba(59,130,246,.6);border-radius:50%;content:\"\";inset:0;position:absolute}@keyframes livePulse{0%{opacity:.8;transform:scale(1)}70%{opacity:0;transform:scale(2.4)}to{opacity:0}}.activity-stats{align-items:center;display:flex;gap:8px}.activity-stats-badge{background-color:rgba(59,130,246,.082);border-radius:999px;color:#3b82f6;font-size:12px;font-weight:500;line-height:1;padding:4px 10px}.activity-stats-badge.light{background-color:#f9f3f4;color:#45556c}.activity-list{height:104px;overflow:hidden;position:relative}.activity-card{align-items:center;background:linear-gradient(90deg,#fff,#f2f6ff);border:1px solid #dbeafe;border-radius:14px;display:flex;gap:10px;height:58px;inset:0;padding:12px 10px;position:absolute;transition:transform .6s cubic-bezier(.22,.61,.36,1),opacity .6s}.activity-card .text{font-size:14px}.activity-card .avatar{align-items:center;border-radius:50%;color:#fff;display:flex;flex:0 0 30px;height:30px;justify-content:center;width:30px}.activity-card .pink{background:linear-gradient(135deg,#ec4899,#f472b6)}.activity-card .purple{background:linear-gradient(135deg,#8b5cf6,#a78bfa)}.activity-card .blue{background:linear-gradient(135deg,#3b82f6,#60a5fa)}.activity-card .green{background:linear-gradient(135deg,#10b981,#34d399)}.activity-card .orange{background:linear-gradient(135deg,#f97316,#fb923c)}.activity-card .text p{color:#6b7280;font-size:12px;margin:2px 0 0}.activity-card .time{color:#94a3b8;font-size:10px;margin-left:auto}.lobby-activity-wp{backdrop-filter:blur(12px);background-color:hsla(0,0%,100%,.4);border:1px solid hsla(0,0%,100%,.2);border-radius:25px;box-shadow:0 1px 3px 0 #0000001a;margin-top:50px;padding:8px}.lobby-close-icon{align-items:center;background-color:color-mix(in oklab,#000 80%,transparent);border-radius:50%;color:#fff;cursor:pointer;display:flex;height:34px;justify-content:center;position:fixed;right:30px;top:30px;width:34px;z-index:99}.lobby-close-icon svg{height:20px;width:20px}@media screen and (max-width:1600px){.cb-lobby-main{border-radius:30px;padding:50px}.title-wp h2{font-size:48px;margin-bottom:12px}.sub-title{font-size:16px}.title-wp{margin-top:20px}.lobby-link-section{margin-top:30px}.offer-box-content h3{font-size:26px}.lobby-offer-box{gap:24px;padding:15px}.cb-lobby-top{gap:80px}.group-info-box{border-radius:20px;padding:22px}.team-card{margin-top:30px}.team-card .team-members{margin-top:12px}.lobby-activity-wp{margin-top:40px}.lobby-close-icon{height:30px;top:20px;width:30px}}@media screen and (max-width:1280px){.cb-lobby-main,.cb-lobby-main-wp{padding:40px}.title-wp h2{font-size:42px}.cb-lobby-top{gap:60px}}@media screen and (max-width:1120px){.title-wp h2{font-size:38px}}@media screen and (max-width:991px){.cb-lobby-top{gap:30px;grid-template-columns:1fr}.cb-lobby-main{border-radius:15px;padding:30px}.cb-lobby-main-wp{padding:30px}.lobby-close-icon{right:20px}.lobby-link-box{border-radius:10px}.share-btn{border-radius:8px}.offer-box-content h3{font-size:24px}.lobby-activity-wp,.lobby-offer-box,.time-card{border-radius:15px;margin-top:20px}.live-activity-wrapper{border-radius:10px}.group-info-box{border-radius:15px}}@media screen and (max-width:767px){.link-share-wrapper{flex-direction:column!important}}@media screen and (max-width:575px){.cb-lobby-main,.cb-lobby-main-wp{padding:20px}.title-wp h2{font-size:30px}.lobby-link-text{font-size:11px;margin-bottom:4px}.lobby-link-url{font-size:14px}.lobby-link-box{min-height:48px;padding:12px 16px}.link-share-wrapper{gap:10px}.share-btn{border-radius:8px;font-size:13px;height:48px;min-width:auto;padding:0 14px}.lobby-offer-box{padding:10px}.offer-box-content h3{font-size:20px}.offer-box-content .reward-text{font-size:13px}.group-info-box{padding:13px}.progress-header .title,.team-card .team-title span{font-size:13px}.team-card .member{height:40px;width:40px}.team-card .member:after{height:8px;width:8px}.team-card .team-members{gap:10px;margin-top:12px}.time-card{padding:13px}.team-card{margin-top:22px}.activity-card .text{font-size:12px}.activity-card .text p{font-size:11px}.live-activity-header .title{font-size:12px}.time-card .time-value{font-size:20px}}@media screen and (max-width:480px){.cb-lobby-main-wp{padding:20px 12px}.cb-lobby-main{padding:15px 12px}.lobby-status{font-size:9px}.lobby-number{font-size:10px}.title-wp h2{font-size:28px;margin-bottom:7px}.sub-title{font-size:14px}.lobby-link-section{gap:10px;margin-top:20px}.lobby-offer-box{gap:12px}.offer-box-icon{height:45px;width:45px}.offer-box-content .reward-text,.offer-lock-status span{font-size:12px}.cb-lobby-top{gap:20px}.lobby-close-icon{right:10px;top:10px}.live-activity-header{flex-direction:column;gap:5px}.activity-card{border-radius:9px;padding:6px}}.share-overlay{align-items:center;background:rgba(0,0,0,.45);display:flex;inset:0;justify-content:center;opacity:0;position:fixed;transition:.3s ease;visibility:hidden;z-index:999}.share-overlay.active{opacity:1;visibility:visible}.share-popup{background:#fff;border-radius:18px;max-height:90%;overflow:auto;padding:24px;position:relative;transform:translateY(30px);transition:.35s ease;width:420px}.share-overlay.active .share-popup{transform:translateY(0)}.share-popup .close-btn{align-items:center;background:#f3f4f6;border:none;border-radius:50%;cursor:pointer;display:flex;height:32px;justify-content:center;position:absolute;right:14px;top:14px;transition:.3s;width:32px}.share-popup .close-btn:hover{background:#eeecec}.share-popup h2{font-size:22px;font-weight:600;line-height:1;margin-bottom:10px}.share-popup .subtitle{color:#4a5565;font-size:14px;margin:6px 0 30px}.share-popup .share-grid{display:grid;gap:14px;grid-template-columns:repeat(2,1fr)}.share-popup .share-card{align-items:center;background:#f2f6f9;border-radius:14px;cursor:pointer;display:flex;flex-direction:column;padding:16px;position:relative;text-align:center;transition:.25s ease}.share-popup .share-card:hover{transform:translateY(-3px)}.share-popup .share-card .icon{align-items:center;background:#1877f2;border-radius:50%;color:#fff;display:flex;font-size:30px;height:50px;justify-content:center;margin-bottom:8px;text-align:center;width:50px}.share-popup .share-card span{font-size:13px;font-weight:500}.share-popup .share-card.whatsapp{background:#ecfdf5;border:2px solid #22c55e;color:#22c55e}.share-popup .share-card.whatsapp .icon{background:#22c55e}.share-popup .share-card .badge{background:#2563eb;border-radius:12px;color:#fff;font-size:11px;padding:4px 10px;position:absolute;right:10px;top:-8px}.share-popup .link-box{background:#fbf9fa;border:1px solid #ebe6e7;border-radius:14px;margin-top:18px;padding:14px}.share-popup .link-box label{color:#64748b;font-size:13px}.share-popup .link-row{display:flex;gap:8px;margin-top:6px}.share-popup .link-row input{background:transparent;border:none;color:#334155;flex:1;font-size:13px;letter-spacing:.8px;line-height:1.2;outline:none}.share-popup .link-row button{align-items:center;background:#fff;border:1px solid #ebe6e7;border-radius:10px;cursor:pointer;display:flex;height:35px;justify-content:center;padding:6px 8px;width:35px}.share-popup .success{color:#2563eb;display:block;font-size:12px;margin-top:6px;opacity:0;transition:opacity .3s}.share-popup .success.show{opacity:1}.share-popup .footer-text{color:#64748b;font-size:12px;margin-top:14px;text-align:center}.share-popup .share-card.twitter .icon{background:#000}.share-popup .share-card.facebook .icon{background:#1877f2}.share-popup .share-card.sms .icon{background:#059669}.share-popup .share-card.copied .icon{background:#2563eb}@keyframes entrance-fade-in{0%{opacity:0}to{opacity:1}}@keyframes entrance-scale-in{0%{opacity:0;transform:scale(.9) translateY(20px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes entrance-text-fade-in{0%{opacity:0}to{opacity:1}}@keyframes exit-blur-scale{0%{filter:blur(0);opacity:1;transform:scale(1)}to{filter:blur(10px);opacity:0;transform:scale(1.1)}}@keyframes pulse-dot{0%,to{opacity:1}50%{opacity:.5}}.entrance-animation-overlay{align-items:center;animation:entrance-fade-in .8s ease-in-out forwards;background-color:#0f172a;color:#fff;display:flex;flex-direction:column;inset:0;justify-content:center;position:fixed;z-index:9999}.entrance-animation-overlay.exit{animation:exit-blur-scale .8s ease-in-out forwards}.entrance-content{align-items:center;animation:entrance-scale-in .5s ease-out .2s both;display:flex;flex-direction:column}.entrance-icon-box{align-items:center;background:#2563eb;border-radius:16px;box-shadow:0 10px 25px rgba(37,99,235,.2);display:flex;height:64px;justify-content:center;margin-bottom:24px;width:64px}.entrance-icon-box svg{color:#fff;height:32px;width:32px}.entrance-title{font-size:32px;font-weight:700;letter-spacing:-.5px;margin-bottom:12px}@media (min-width:768px){.entrance-title{font-size:48px}}.entrance-message{align-items:center;animation:entrance-text-fade-in .5s ease-out .5s both;color:#60a5fa;display:flex;font-size:18px;font-weight:500;gap:8px}.entrance-pulse-dot{animation:pulse-dot 1.5s ease-in-out infinite;background-color:#60a5fa;border-radius:50%;height:8px;width:8px}";
|
|
2113
2174
|
styleInject(css_248z);
|
|
2114
2175
|
|
|
2115
2176
|
/// <reference lib="dom" />
|
|
2177
|
+
const CONTACT_PROMPT_TITLE = "Protect your group spot";
|
|
2178
|
+
const CONTACT_PROMPT_SUBTITLE = "Add your email or phone so we can help you recover your group membership if you leave accidentally.";
|
|
2179
|
+
const CONTACT_PROMPT_PRIMARY_CTA = "Protect my spot";
|
|
2180
|
+
const CONTACT_PROMPT_REMIND_CTA = "Remind me later";
|
|
2181
|
+
const CONTACT_PROMPT_INVALID_INPUT = "Please enter a valid email or phone number.";
|
|
2182
|
+
const CONTACT_PROMPT_SAVE_ERROR = "Could not save contact right now. Please try again.";
|
|
2183
|
+
const CONTACT_PROTECTED_TOAST = "Spot protected. We can help recover your group membership.";
|
|
2184
|
+
const LEAVE_WARNING_TITLE = "Before you leave";
|
|
2185
|
+
const LEAVE_WARNING_SUBTITLE = "If you leave now without adding contact, you may lose this group spot and may not be able to rejoin the same group.";
|
|
2116
2186
|
/**
|
|
2117
2187
|
* LobbyModal - Renders and manages the group buying lobby modal
|
|
2118
2188
|
*/
|
|
2119
2189
|
class LobbyModal {
|
|
2120
|
-
constructor(data, callbacks, apiClient, socketManager = null, debug = false) {
|
|
2190
|
+
constructor(data, callbacks, apiClient, socketManager = null, analyticsClient = null, debug = false) {
|
|
2121
2191
|
this.modalElement = null;
|
|
2122
2192
|
this.timerInterval = null;
|
|
2123
2193
|
this.activityInterval = null;
|
|
@@ -2126,6 +2196,17 @@ var CoBuySDK = (function (exports) {
|
|
|
2126
2196
|
this.currentGroupId = null;
|
|
2127
2197
|
this.shareOverlay = null;
|
|
2128
2198
|
this.keyboardHandler = null;
|
|
2199
|
+
this.leaveFlowInProgress = false;
|
|
2200
|
+
this.leaveSignalSent = false;
|
|
2201
|
+
this.hasContactProtection = false;
|
|
2202
|
+
this.initialContactPromptTimer = null;
|
|
2203
|
+
this.contactPromptOverlay = null;
|
|
2204
|
+
this.leaveLoaderOverlay = null;
|
|
2205
|
+
this.beforeUnloadHandler = null;
|
|
2206
|
+
this.pageHideHandler = null;
|
|
2207
|
+
this.visibilityChangeHandler = null;
|
|
2208
|
+
this.CONTACT_PROTECTION_PREFIX = "cobuy_contact_protection";
|
|
2209
|
+
this.LAST_LEFT_GROUP_PREFIX = "cobuy_last_left_group";
|
|
2129
2210
|
/**
|
|
2130
2211
|
* Handle socket group update events
|
|
2131
2212
|
*/
|
|
@@ -2217,6 +2298,7 @@ var CoBuySDK = (function (exports) {
|
|
|
2217
2298
|
this.logger = new Logger(debug);
|
|
2218
2299
|
this.apiClient = apiClient;
|
|
2219
2300
|
this.socketManager = socketManager;
|
|
2301
|
+
this.analyticsClient = analyticsClient;
|
|
2220
2302
|
// Log the group data being passed into the modal
|
|
2221
2303
|
this.logger.info("LobbyModal initialized with group data", {
|
|
2222
2304
|
groupId: data.groupId,
|
|
@@ -2228,14 +2310,385 @@ var CoBuySDK = (function (exports) {
|
|
|
2228
2310
|
timeLeft: data.timeLeft,
|
|
2229
2311
|
offlineRedemption: data.offlineRedemption,
|
|
2230
2312
|
});
|
|
2231
|
-
this.data = Object.assign({ groupNumber: "1000", status: "active", progress: 80, currentMembers: 4, totalMembers: 5, timeLeft: 1390, discount: "20% OFF", isLocked: true, activities: this.getDefaultActivities() }, data);
|
|
2313
|
+
this.data = Object.assign({ groupNumber: "1000", status: "active", progress: 80, currentMembers: 4, totalMembers: 5, timeLeft: 1390, discount: "20% OFF", isLocked: true, activities: this.getDefaultActivities(), redemptionMethod: "online" }, data);
|
|
2232
2314
|
// Normalize optional flags so undefined values don't override defaults
|
|
2233
2315
|
this.data.isLocked = this.computeIsLocked(this.data);
|
|
2234
2316
|
if (!this.data.activities || !Array.isArray(this.data.activities)) {
|
|
2235
2317
|
this.data.activities = this.getDefaultActivities();
|
|
2236
2318
|
}
|
|
2319
|
+
this.hasContactProtection = this.readContactProtectionState();
|
|
2237
2320
|
this.callbacks = callbacks;
|
|
2238
2321
|
}
|
|
2322
|
+
getSessionId() {
|
|
2323
|
+
var _a;
|
|
2324
|
+
return ((_a = this.apiClient) === null || _a === void 0 ? void 0 : _a.getSessionId()) || "anonymous";
|
|
2325
|
+
}
|
|
2326
|
+
getContactProtectionStorageKey() {
|
|
2327
|
+
return `${this.CONTACT_PROTECTION_PREFIX}:${this.getSessionId()}:${this.data.productId}`;
|
|
2328
|
+
}
|
|
2329
|
+
getLastLeftGroupStorageKey() {
|
|
2330
|
+
return `${this.LAST_LEFT_GROUP_PREFIX}:${this.getSessionId()}:${this.data.productId}`;
|
|
2331
|
+
}
|
|
2332
|
+
readContactProtectionState() {
|
|
2333
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
2334
|
+
return false;
|
|
2335
|
+
}
|
|
2336
|
+
try {
|
|
2337
|
+
return window.localStorage.getItem(this.getContactProtectionStorageKey()) === "1";
|
|
2338
|
+
}
|
|
2339
|
+
catch (_a) {
|
|
2340
|
+
return false;
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
persistContactProtectionState(value) {
|
|
2344
|
+
this.hasContactProtection = value;
|
|
2345
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
try {
|
|
2349
|
+
const key = this.getContactProtectionStorageKey();
|
|
2350
|
+
if (value) {
|
|
2351
|
+
window.localStorage.setItem(key, "1");
|
|
2352
|
+
}
|
|
2353
|
+
else {
|
|
2354
|
+
window.localStorage.removeItem(key);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
catch (_a) {
|
|
2358
|
+
// Ignore storage errors (private mode, quota exceeded, etc.)
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
persistLastLeftGroup(groupId) {
|
|
2362
|
+
if (typeof window === "undefined" || !window.localStorage || !groupId) {
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
try {
|
|
2366
|
+
window.localStorage.setItem(this.getLastLeftGroupStorageKey(), JSON.stringify({
|
|
2367
|
+
groupId,
|
|
2368
|
+
productId: this.data.productId,
|
|
2369
|
+
leftAt: Date.now(),
|
|
2370
|
+
}));
|
|
2371
|
+
}
|
|
2372
|
+
catch (_a) {
|
|
2373
|
+
// Ignore storage errors
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
clearInitialContactPromptTimer() {
|
|
2377
|
+
if (this.initialContactPromptTimer !== null) {
|
|
2378
|
+
window.clearTimeout(this.initialContactPromptTimer);
|
|
2379
|
+
this.initialContactPromptTimer = null;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
clearContactPromptOverlay() {
|
|
2383
|
+
if (this.contactPromptOverlay) {
|
|
2384
|
+
this.contactPromptOverlay.remove();
|
|
2385
|
+
this.contactPromptOverlay = null;
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
showBlockingLoader(message) {
|
|
2389
|
+
if (!this.modalElement) {
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
this.hideBlockingLoader();
|
|
2393
|
+
const overlay = document.createElement("div");
|
|
2394
|
+
overlay.className = "lobby-blocking-loader-overlay";
|
|
2395
|
+
const card = document.createElement("div");
|
|
2396
|
+
card.className = "lobby-blocking-loader-card";
|
|
2397
|
+
const spinner = document.createElement("div");
|
|
2398
|
+
spinner.className = "lobby-blocking-loader-spinner";
|
|
2399
|
+
const text = document.createElement("p");
|
|
2400
|
+
text.className = "lobby-blocking-loader-text";
|
|
2401
|
+
text.textContent = message;
|
|
2402
|
+
card.appendChild(spinner);
|
|
2403
|
+
card.appendChild(text);
|
|
2404
|
+
overlay.appendChild(card);
|
|
2405
|
+
this.modalElement.appendChild(overlay);
|
|
2406
|
+
this.leaveLoaderOverlay = overlay;
|
|
2407
|
+
}
|
|
2408
|
+
hideBlockingLoader() {
|
|
2409
|
+
if (this.leaveLoaderOverlay) {
|
|
2410
|
+
this.leaveLoaderOverlay.remove();
|
|
2411
|
+
this.leaveLoaderOverlay = null;
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
updateContactProtectionUI() {
|
|
2415
|
+
const banner = document.getElementById("lobbyContactProtectionBanner");
|
|
2416
|
+
const explicitLeaveContainer = document.getElementById("lobbyProtectedLeaveContainer");
|
|
2417
|
+
if (!banner)
|
|
2418
|
+
return;
|
|
2419
|
+
const canShowContactActions = this.data.status !== "complete" && Boolean(this.apiClient);
|
|
2420
|
+
if (!canShowContactActions) {
|
|
2421
|
+
banner.style.display = "none";
|
|
2422
|
+
if (explicitLeaveContainer) {
|
|
2423
|
+
explicitLeaveContainer.style.display = "none";
|
|
2424
|
+
}
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
if (this.hasContactProtection) {
|
|
2428
|
+
banner.style.display = "none";
|
|
2429
|
+
if (explicitLeaveContainer) {
|
|
2430
|
+
explicitLeaveContainer.style.display = "flex";
|
|
2431
|
+
}
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
banner.style.display = "flex";
|
|
2435
|
+
if (explicitLeaveContainer) {
|
|
2436
|
+
explicitLeaveContainer.style.display = "none";
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
showToastMessage(message) {
|
|
2440
|
+
if (!this.modalElement) {
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
const existing = this.modalElement.querySelector(".lobby-contact-toast");
|
|
2444
|
+
if (existing) {
|
|
2445
|
+
existing.remove();
|
|
2446
|
+
}
|
|
2447
|
+
const toast = document.createElement("div");
|
|
2448
|
+
toast.className = "lobby-contact-toast";
|
|
2449
|
+
toast.textContent = message;
|
|
2450
|
+
this.modalElement.appendChild(toast);
|
|
2451
|
+
window.setTimeout(() => {
|
|
2452
|
+
toast.remove();
|
|
2453
|
+
}, 2400);
|
|
2454
|
+
}
|
|
2455
|
+
trackAnalyticsEvent(eventName, metadata) {
|
|
2456
|
+
if (!this.analyticsClient) {
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2459
|
+
this.analyticsClient
|
|
2460
|
+
.trackEvent(eventName, this.data.productId, Object.assign({ groupId: this.currentGroupId || this.data.groupId }, metadata))
|
|
2461
|
+
.catch((error) => {
|
|
2462
|
+
this.logger.warn(`[Analytics] Failed to track ${eventName}`, error);
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
async saveContactValue(contactValue, promptSource) {
|
|
2466
|
+
const trimmed = contactValue.trim();
|
|
2467
|
+
if (!trimmed || !this.apiClient) {
|
|
2468
|
+
return false;
|
|
2469
|
+
}
|
|
2470
|
+
const contactType = this.inferContactType(trimmed);
|
|
2471
|
+
if (!contactType) {
|
|
2472
|
+
window.alert(CONTACT_PROMPT_INVALID_INPUT);
|
|
2473
|
+
return false;
|
|
2474
|
+
}
|
|
2475
|
+
const payload = contactType === "phone"
|
|
2476
|
+
? { type: "phone", value: trimmed.replace(/\D/g, "") }
|
|
2477
|
+
: { type: "email", value: trimmed };
|
|
2478
|
+
const result = await this.apiClient.setContact(payload);
|
|
2479
|
+
if (!result.success) {
|
|
2480
|
+
this.logger.warn("Failed to save lobby contact", result.error);
|
|
2481
|
+
window.alert(CONTACT_PROMPT_SAVE_ERROR);
|
|
2482
|
+
return false;
|
|
2483
|
+
}
|
|
2484
|
+
this.persistContactProtectionState(true);
|
|
2485
|
+
this.trackAnalyticsEvent("CONTACT_INFO_SAVED", {
|
|
2486
|
+
promptSource,
|
|
2487
|
+
contactType,
|
|
2488
|
+
protectionState: "protected",
|
|
2489
|
+
});
|
|
2490
|
+
this.updateContactProtectionUI();
|
|
2491
|
+
this.showToastMessage(CONTACT_PROTECTED_TOAST);
|
|
2492
|
+
return true;
|
|
2493
|
+
}
|
|
2494
|
+
openContactProtectionPrompt(source) {
|
|
2495
|
+
if (typeof document === "undefined" || !this.modalElement) {
|
|
2496
|
+
return Promise.resolve("cancel");
|
|
2497
|
+
}
|
|
2498
|
+
this.clearContactPromptOverlay();
|
|
2499
|
+
this.trackAnalyticsEvent("CONTACT_PROMPT_SHOWN", {
|
|
2500
|
+
promptSource: source,
|
|
2501
|
+
protectionState: "unprotected",
|
|
2502
|
+
});
|
|
2503
|
+
return new Promise((resolve) => {
|
|
2504
|
+
var _a;
|
|
2505
|
+
const overlay = document.createElement("div");
|
|
2506
|
+
overlay.className = "lobby-contact-overlay";
|
|
2507
|
+
const card = document.createElement("div");
|
|
2508
|
+
card.className = "lobby-contact-card";
|
|
2509
|
+
const title = document.createElement("h3");
|
|
2510
|
+
title.className = "lobby-contact-title";
|
|
2511
|
+
title.textContent = CONTACT_PROMPT_TITLE;
|
|
2512
|
+
const subtitle = document.createElement("p");
|
|
2513
|
+
subtitle.className = "lobby-contact-subtitle";
|
|
2514
|
+
subtitle.textContent = CONTACT_PROMPT_SUBTITLE;
|
|
2515
|
+
const input = document.createElement("input");
|
|
2516
|
+
input.type = "text";
|
|
2517
|
+
input.className = "lobby-contact-input";
|
|
2518
|
+
input.placeholder = "Email or phone";
|
|
2519
|
+
input.autocomplete = "email";
|
|
2520
|
+
const actions = document.createElement("div");
|
|
2521
|
+
actions.className = "lobby-contact-actions";
|
|
2522
|
+
const primary = document.createElement("button");
|
|
2523
|
+
primary.type = "button";
|
|
2524
|
+
primary.className = "lobby-contact-primary";
|
|
2525
|
+
primary.textContent = CONTACT_PROMPT_PRIMARY_CTA;
|
|
2526
|
+
const secondary = document.createElement("button");
|
|
2527
|
+
secondary.type = "button";
|
|
2528
|
+
secondary.className = "lobby-contact-secondary";
|
|
2529
|
+
secondary.textContent = source === "leave-warning" ? "Cancel" : CONTACT_PROMPT_REMIND_CTA;
|
|
2530
|
+
const cleanupAndResolve = (result) => {
|
|
2531
|
+
this.clearContactPromptOverlay();
|
|
2532
|
+
resolve(result);
|
|
2533
|
+
};
|
|
2534
|
+
primary.addEventListener("click", async () => {
|
|
2535
|
+
const originalLabel = primary.textContent;
|
|
2536
|
+
primary.disabled = true;
|
|
2537
|
+
secondary.disabled = true;
|
|
2538
|
+
input.disabled = true;
|
|
2539
|
+
primary.classList.add("is-loading");
|
|
2540
|
+
primary.textContent = "Saving...";
|
|
2541
|
+
try {
|
|
2542
|
+
const saved = await this.saveContactValue(input.value, source);
|
|
2543
|
+
if (saved) {
|
|
2544
|
+
cleanupAndResolve("saved");
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
finally {
|
|
2548
|
+
if (this.contactPromptOverlay) {
|
|
2549
|
+
primary.disabled = false;
|
|
2550
|
+
secondary.disabled = false;
|
|
2551
|
+
input.disabled = false;
|
|
2552
|
+
primary.classList.remove("is-loading");
|
|
2553
|
+
primary.textContent = originalLabel || CONTACT_PROMPT_PRIMARY_CTA;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2557
|
+
secondary.addEventListener("click", () => {
|
|
2558
|
+
if (source !== "leave-warning") {
|
|
2559
|
+
this.trackAnalyticsEvent("CONTACT_PROMPT_DEFERRED", {
|
|
2560
|
+
promptSource: source,
|
|
2561
|
+
protectionState: "unprotected",
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
2564
|
+
cleanupAndResolve(source === "leave-warning" ? "cancel" : "later");
|
|
2565
|
+
});
|
|
2566
|
+
overlay.addEventListener("click", (event) => {
|
|
2567
|
+
if (event.target === overlay) {
|
|
2568
|
+
if (source !== "leave-warning") {
|
|
2569
|
+
this.trackAnalyticsEvent("CONTACT_PROMPT_DEFERRED", {
|
|
2570
|
+
promptSource: source,
|
|
2571
|
+
protectionState: "unprotected",
|
|
2572
|
+
});
|
|
2573
|
+
}
|
|
2574
|
+
cleanupAndResolve(source === "leave-warning" ? "cancel" : "later");
|
|
2575
|
+
}
|
|
2576
|
+
});
|
|
2577
|
+
actions.appendChild(primary);
|
|
2578
|
+
actions.appendChild(secondary);
|
|
2579
|
+
card.appendChild(title);
|
|
2580
|
+
card.appendChild(subtitle);
|
|
2581
|
+
card.appendChild(input);
|
|
2582
|
+
card.appendChild(actions);
|
|
2583
|
+
overlay.appendChild(card);
|
|
2584
|
+
(_a = this.modalElement) === null || _a === void 0 ? void 0 : _a.appendChild(overlay);
|
|
2585
|
+
this.contactPromptOverlay = overlay;
|
|
2586
|
+
window.setTimeout(() => input.focus(), 0);
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
openUnprotectedLeaveWarning() {
|
|
2590
|
+
if (typeof document === "undefined" || !this.modalElement) {
|
|
2591
|
+
return Promise.resolve("cancel");
|
|
2592
|
+
}
|
|
2593
|
+
this.clearContactPromptOverlay();
|
|
2594
|
+
return new Promise((resolve) => {
|
|
2595
|
+
var _a;
|
|
2596
|
+
const overlay = document.createElement("div");
|
|
2597
|
+
overlay.className = "lobby-contact-overlay";
|
|
2598
|
+
const card = document.createElement("div");
|
|
2599
|
+
card.className = "lobby-contact-card";
|
|
2600
|
+
const title = document.createElement("h3");
|
|
2601
|
+
title.className = "lobby-contact-title";
|
|
2602
|
+
title.textContent = LEAVE_WARNING_TITLE;
|
|
2603
|
+
const subtitle = document.createElement("p");
|
|
2604
|
+
subtitle.className = "lobby-contact-subtitle";
|
|
2605
|
+
subtitle.textContent = LEAVE_WARNING_SUBTITLE;
|
|
2606
|
+
const actions = document.createElement("div");
|
|
2607
|
+
actions.className = "lobby-contact-actions";
|
|
2608
|
+
const addContactBtn = document.createElement("button");
|
|
2609
|
+
addContactBtn.type = "button";
|
|
2610
|
+
addContactBtn.className = "lobby-contact-primary";
|
|
2611
|
+
addContactBtn.textContent = "Add contact first";
|
|
2612
|
+
const leaveBtn = document.createElement("button");
|
|
2613
|
+
leaveBtn.type = "button";
|
|
2614
|
+
leaveBtn.className = "lobby-contact-danger";
|
|
2615
|
+
leaveBtn.textContent = "Leave anyway";
|
|
2616
|
+
const cancelBtn = document.createElement("button");
|
|
2617
|
+
cancelBtn.type = "button";
|
|
2618
|
+
cancelBtn.className = "lobby-contact-secondary";
|
|
2619
|
+
cancelBtn.textContent = "Cancel";
|
|
2620
|
+
const cleanup = (result) => {
|
|
2621
|
+
this.clearContactPromptOverlay();
|
|
2622
|
+
resolve(result);
|
|
2623
|
+
};
|
|
2624
|
+
addContactBtn.addEventListener("click", () => cleanup("add_contact"));
|
|
2625
|
+
leaveBtn.addEventListener("click", () => cleanup("leave"));
|
|
2626
|
+
cancelBtn.addEventListener("click", () => cleanup("cancel"));
|
|
2627
|
+
overlay.addEventListener("click", (event) => {
|
|
2628
|
+
if (event.target === overlay) {
|
|
2629
|
+
cleanup("cancel");
|
|
2630
|
+
}
|
|
2631
|
+
});
|
|
2632
|
+
actions.appendChild(addContactBtn);
|
|
2633
|
+
actions.appendChild(leaveBtn);
|
|
2634
|
+
actions.appendChild(cancelBtn);
|
|
2635
|
+
card.appendChild(title);
|
|
2636
|
+
card.appendChild(subtitle);
|
|
2637
|
+
card.appendChild(actions);
|
|
2638
|
+
overlay.appendChild(card);
|
|
2639
|
+
(_a = this.modalElement) === null || _a === void 0 ? void 0 : _a.appendChild(overlay);
|
|
2640
|
+
this.contactPromptOverlay = overlay;
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2643
|
+
scheduleInitialContactPrompt() {
|
|
2644
|
+
if (!this.modalElement ||
|
|
2645
|
+
!this.shouldRunLeaveFlow() ||
|
|
2646
|
+
this.hasContactProtection ||
|
|
2647
|
+
!this.apiClient) {
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2650
|
+
this.clearInitialContactPromptTimer();
|
|
2651
|
+
this.initialContactPromptTimer = window.setTimeout(async () => {
|
|
2652
|
+
this.initialContactPromptTimer = null;
|
|
2653
|
+
if (!this.modalElement || this.hasContactProtection) {
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
await this.openContactProtectionPrompt("initial");
|
|
2657
|
+
this.updateContactProtectionUI();
|
|
2658
|
+
}, 2000);
|
|
2659
|
+
}
|
|
2660
|
+
async leaveGroupExplicitly() {
|
|
2661
|
+
var _a, _b;
|
|
2662
|
+
if (!this.apiClient || !this.currentGroupId || this.leaveFlowInProgress) {
|
|
2663
|
+
return;
|
|
2664
|
+
}
|
|
2665
|
+
this.leaveFlowInProgress = true;
|
|
2666
|
+
try {
|
|
2667
|
+
this.showBlockingLoader("Leaving group...");
|
|
2668
|
+
const leaveResult = await this.apiClient.leaveGroup(this.currentGroupId, {
|
|
2669
|
+
leave_source: "close_icon",
|
|
2670
|
+
leave_reason: "voluntary_with_contact",
|
|
2671
|
+
});
|
|
2672
|
+
if (!leaveResult.success) {
|
|
2673
|
+
this.logger.warn("Explicit leave group failed", leaveResult.error);
|
|
2674
|
+
this.hideBlockingLoader();
|
|
2675
|
+
window.alert("Could not leave the group right now. Please try again.");
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
this.leaveSignalSent = true;
|
|
2679
|
+
this.persistLastLeftGroup(((_b = (_a = leaveResult.data) === null || _a === void 0 ? void 0 : _a.group) === null || _b === void 0 ? void 0 : _b.id) || this.currentGroupId);
|
|
2680
|
+
this.trackAnalyticsEvent("PROTECTED_USER_EXPLICIT_LEAVE", {
|
|
2681
|
+
leaveSource: "close_icon",
|
|
2682
|
+
leaveReason: "voluntary_with_contact",
|
|
2683
|
+
protectionState: "protected",
|
|
2684
|
+
});
|
|
2685
|
+
this.close({ skipLeaveFlow: true });
|
|
2686
|
+
}
|
|
2687
|
+
finally {
|
|
2688
|
+
this.hideBlockingLoader();
|
|
2689
|
+
this.leaveFlowInProgress = false;
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2239
2692
|
/**
|
|
2240
2693
|
* Derive lock state from data so UI reflects completion
|
|
2241
2694
|
*/
|
|
@@ -2552,6 +3005,7 @@ var CoBuySDK = (function (exports) {
|
|
|
2552
3005
|
* Create connected section (subtitle + link + share)
|
|
2553
3006
|
*/
|
|
2554
3007
|
createConnectedSection() {
|
|
3008
|
+
var _a, _b;
|
|
2555
3009
|
const connectedSection = document.createElement("div");
|
|
2556
3010
|
connectedSection.className = "connected-section";
|
|
2557
3011
|
connectedSection.id = "lobbyConnectedSection";
|
|
@@ -2562,18 +3016,59 @@ var CoBuySDK = (function (exports) {
|
|
|
2562
3016
|
subtitle.id = "lobbySubtitleText";
|
|
2563
3017
|
subtitle.textContent = titleContent.subtitle;
|
|
2564
3018
|
connectedSection.appendChild(subtitle);
|
|
3019
|
+
const contactBanner = document.createElement("div");
|
|
3020
|
+
contactBanner.className = "lobby-contact-protection-banner";
|
|
3021
|
+
contactBanner.id = "lobbyContactProtectionBanner";
|
|
3022
|
+
const contactCopy = document.createElement("p");
|
|
3023
|
+
contactCopy.className = "lobby-contact-protection-copy";
|
|
3024
|
+
contactCopy.textContent =
|
|
3025
|
+
"Spot not protected. Add contact so we can help recover your group membership.";
|
|
3026
|
+
const contactAction = document.createElement("button");
|
|
3027
|
+
contactAction.type = "button";
|
|
3028
|
+
contactAction.className = "lobby-contact-protection-btn";
|
|
3029
|
+
contactAction.textContent = "Add contact";
|
|
3030
|
+
contactAction.addEventListener("click", async () => {
|
|
3031
|
+
await this.openContactProtectionPrompt("banner");
|
|
3032
|
+
this.updateContactProtectionUI();
|
|
3033
|
+
});
|
|
3034
|
+
contactBanner.appendChild(contactCopy);
|
|
3035
|
+
contactBanner.appendChild(contactAction);
|
|
3036
|
+
connectedSection.appendChild(contactBanner);
|
|
3037
|
+
const protectedLeaveContainer = document.createElement("div");
|
|
3038
|
+
protectedLeaveContainer.className = "lobby-protected-leave-container";
|
|
3039
|
+
protectedLeaveContainer.id = "lobbyProtectedLeaveContainer";
|
|
3040
|
+
const protectedLeaveText = document.createElement("p");
|
|
3041
|
+
protectedLeaveText.className = "lobby-protected-leave-copy";
|
|
3042
|
+
protectedLeaveText.textContent = "You are protected and still part of this group.";
|
|
3043
|
+
const protectedLeaveAction = document.createElement("button");
|
|
3044
|
+
protectedLeaveAction.type = "button";
|
|
3045
|
+
protectedLeaveAction.className = "lobby-protected-leave-btn";
|
|
3046
|
+
protectedLeaveAction.textContent = "Leave group";
|
|
3047
|
+
protectedLeaveAction.addEventListener("click", () => {
|
|
3048
|
+
void this.leaveGroupExplicitly();
|
|
3049
|
+
});
|
|
3050
|
+
protectedLeaveContainer.appendChild(protectedLeaveText);
|
|
3051
|
+
protectedLeaveContainer.appendChild(protectedLeaveAction);
|
|
3052
|
+
connectedSection.appendChild(protectedLeaveContainer);
|
|
2565
3053
|
// Check if group is fulfilled and has offline redemption
|
|
2566
3054
|
const isComplete = !this.computeIsLocked(this.data);
|
|
2567
3055
|
const hasOfflineRedemption = isComplete &&
|
|
2568
3056
|
this.data.offlineRedemption &&
|
|
2569
|
-
isValidOfflineRedemption(this.data.offlineRedemption)
|
|
3057
|
+
isValidOfflineRedemption(this.data.offlineRedemption) &&
|
|
3058
|
+
((_a = this.data.redemptionMethod) !== null && _a !== void 0 ? _a : "online") !== "online";
|
|
2570
3059
|
if (hasOfflineRedemption) {
|
|
2571
|
-
//
|
|
3060
|
+
// Complete + offline/both: show offline redemption section
|
|
2572
3061
|
const offlineSection = this.createOfflineRedemptionSection(this.data.offlineRedemption);
|
|
2573
3062
|
connectedSection.appendChild(offlineSection);
|
|
2574
3063
|
}
|
|
3064
|
+
else if (isComplete && ((_b = this.data.redemptionMethod) !== null && _b !== void 0 ? _b : "online") !== "offline") {
|
|
3065
|
+
// Complete + online/both (no offline redemption data): show only checkout CTA
|
|
3066
|
+
const checkoutBtn = this.createOnlineCheckoutButton();
|
|
3067
|
+
connectedSection.appendChild(checkoutBtn);
|
|
3068
|
+
this.injectOfflineRedemptionStyles();
|
|
3069
|
+
}
|
|
2575
3070
|
else {
|
|
2576
|
-
//
|
|
3071
|
+
// Group not yet complete: show link and share
|
|
2577
3072
|
const linkShareContainer = document.createElement("div");
|
|
2578
3073
|
linkShareContainer.className = "link-share-container";
|
|
2579
3074
|
const linkWp = this.createLinkSection();
|
|
@@ -2617,6 +3112,23 @@ var CoBuySDK = (function (exports) {
|
|
|
2617
3112
|
linkShareWrapper.appendChild(shareBtnInner);
|
|
2618
3113
|
return linkShareWrapper;
|
|
2619
3114
|
}
|
|
3115
|
+
/**
|
|
3116
|
+
* Create a standalone "Checkout Online" button for the link/share section
|
|
3117
|
+
*/
|
|
3118
|
+
createOnlineCheckoutButton() {
|
|
3119
|
+
const btn = document.createElement("button");
|
|
3120
|
+
btn.className = "offline-online-checkout-btn lobby-online-checkout-btn";
|
|
3121
|
+
btn.textContent = "Checkout Online";
|
|
3122
|
+
btn.style.marginTop = "10px";
|
|
3123
|
+
btn.style.width = "100%";
|
|
3124
|
+
btn.addEventListener("click", () => {
|
|
3125
|
+
this.close();
|
|
3126
|
+
window.setTimeout(() => {
|
|
3127
|
+
this.triggerContinueToCheckout();
|
|
3128
|
+
}, 0);
|
|
3129
|
+
});
|
|
3130
|
+
return btn;
|
|
3131
|
+
}
|
|
2620
3132
|
/**
|
|
2621
3133
|
* Create offline redemption section
|
|
2622
3134
|
*/
|
|
@@ -2681,14 +3193,16 @@ var CoBuySDK = (function (exports) {
|
|
|
2681
3193
|
onlineCheckoutBtn.className = "offline-online-checkout-btn";
|
|
2682
3194
|
onlineCheckoutBtn.textContent = "Checkout Online";
|
|
2683
3195
|
onlineCheckoutBtn.addEventListener("click", () => {
|
|
2684
|
-
this.close();
|
|
3196
|
+
this.close({ skipLeaveFlow: true });
|
|
2685
3197
|
// Ensure modal closes before triggering checkout intent
|
|
2686
3198
|
window.setTimeout(() => {
|
|
2687
3199
|
this.triggerContinueToCheckout();
|
|
2688
3200
|
}, 0);
|
|
2689
3201
|
});
|
|
2690
3202
|
actionsRow.appendChild(downloadQRBtn);
|
|
2691
|
-
|
|
3203
|
+
if (this.data.redemptionMethod !== "offline") {
|
|
3204
|
+
actionsRow.appendChild(onlineCheckoutBtn);
|
|
3205
|
+
}
|
|
2692
3206
|
section.appendChild(topRow);
|
|
2693
3207
|
section.appendChild(expiryInfo);
|
|
2694
3208
|
section.appendChild(actionsRow);
|
|
@@ -3581,20 +4095,23 @@ var CoBuySDK = (function (exports) {
|
|
|
3581
4095
|
document.body.appendChild(this.modalElement);
|
|
3582
4096
|
// Subscribe to realtime socket events
|
|
3583
4097
|
this.subscribeToSocketEvents();
|
|
3584
|
-
//
|
|
3585
|
-
|
|
3586
|
-
|
|
4098
|
+
// Subscribe to the group room for targeted events.
|
|
4099
|
+
// Socket.IO client buffers emits until connected, so this is safe even
|
|
4100
|
+
// if the handshake hasn't completed yet.
|
|
4101
|
+
if (this.socketManager && this.currentGroupId) {
|
|
4102
|
+
this.logger.info(`[LobbyModal] Subscribing to group room ${this.currentGroupId}`);
|
|
3587
4103
|
this.socketManager.subscribeToGroup(this.currentGroupId);
|
|
3588
4104
|
}
|
|
3589
|
-
else if (this.socketManager && !this.socketManager.isConnected()) {
|
|
3590
|
-
this.logger.warn("[LobbyModal] Socket manager not connected yet");
|
|
3591
|
-
}
|
|
3592
4105
|
// Start timers and animations
|
|
3593
4106
|
this.startTimer();
|
|
3594
4107
|
this.startActivityAnimation();
|
|
3595
4108
|
this.logger.info(`Lobby modal opened for product: ${this.data.productId}`);
|
|
3596
4109
|
// Set up keyboard accessibility (focus trap, ESC to close)
|
|
3597
4110
|
this.setupKeyboardAccessibility();
|
|
4111
|
+
// Register lifecycle-based dropoff handling for browser/tab close.
|
|
4112
|
+
this.registerLifecycleLeaveHandlers();
|
|
4113
|
+
this.updateContactProtectionUI();
|
|
4114
|
+
this.scheduleInitialContactPrompt();
|
|
3598
4115
|
});
|
|
3599
4116
|
}
|
|
3600
4117
|
/**
|
|
@@ -3660,19 +4177,166 @@ var CoBuySDK = (function (exports) {
|
|
|
3660
4177
|
this.keyboardHandler = null;
|
|
3661
4178
|
}
|
|
3662
4179
|
}
|
|
4180
|
+
registerLifecycleLeaveHandlers() {
|
|
4181
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
4182
|
+
return;
|
|
4183
|
+
}
|
|
4184
|
+
if (!this.shouldRunLeaveFlow()) {
|
|
4185
|
+
return;
|
|
4186
|
+
}
|
|
4187
|
+
if (!this.beforeUnloadHandler) {
|
|
4188
|
+
this.beforeUnloadHandler = () => {
|
|
4189
|
+
this.triggerBestEffortLeave("browser_close");
|
|
4190
|
+
};
|
|
4191
|
+
window.addEventListener("beforeunload", this.beforeUnloadHandler);
|
|
4192
|
+
}
|
|
4193
|
+
if (!this.pageHideHandler) {
|
|
4194
|
+
this.pageHideHandler = () => {
|
|
4195
|
+
this.triggerBestEffortLeave("browser_close");
|
|
4196
|
+
};
|
|
4197
|
+
window.addEventListener("pagehide", this.pageHideHandler);
|
|
4198
|
+
}
|
|
4199
|
+
if (!this.visibilityChangeHandler) {
|
|
4200
|
+
this.visibilityChangeHandler = () => {
|
|
4201
|
+
if (document.visibilityState === "hidden") {
|
|
4202
|
+
this.triggerBestEffortLeave("browser_close");
|
|
4203
|
+
}
|
|
4204
|
+
};
|
|
4205
|
+
document.addEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4208
|
+
unregisterLifecycleLeaveHandlers() {
|
|
4209
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
4210
|
+
return;
|
|
4211
|
+
}
|
|
4212
|
+
if (this.beforeUnloadHandler) {
|
|
4213
|
+
window.removeEventListener("beforeunload", this.beforeUnloadHandler);
|
|
4214
|
+
this.beforeUnloadHandler = null;
|
|
4215
|
+
}
|
|
4216
|
+
if (this.pageHideHandler) {
|
|
4217
|
+
window.removeEventListener("pagehide", this.pageHideHandler);
|
|
4218
|
+
this.pageHideHandler = null;
|
|
4219
|
+
}
|
|
4220
|
+
if (this.visibilityChangeHandler) {
|
|
4221
|
+
document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
4222
|
+
this.visibilityChangeHandler = null;
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
triggerBestEffortLeave(source) {
|
|
4226
|
+
if (this.leaveSignalSent || this.leaveFlowInProgress) {
|
|
4227
|
+
return;
|
|
4228
|
+
}
|
|
4229
|
+
if (this.hasContactProtection) {
|
|
4230
|
+
return;
|
|
4231
|
+
}
|
|
4232
|
+
if (!this.apiClient || !this.currentGroupId || !this.shouldRunLeaveFlow()) {
|
|
4233
|
+
return;
|
|
4234
|
+
}
|
|
4235
|
+
this.leaveSignalSent = true;
|
|
4236
|
+
this.apiClient.leaveGroupBestEffort(this.currentGroupId, {
|
|
4237
|
+
leave_source: source,
|
|
4238
|
+
leave_reason: "voluntary_no_contact",
|
|
4239
|
+
});
|
|
4240
|
+
}
|
|
4241
|
+
shouldRunLeaveFlow() {
|
|
4242
|
+
if (!this.apiClient || !this.currentGroupId) {
|
|
4243
|
+
return false;
|
|
4244
|
+
}
|
|
4245
|
+
// Dropoff handling applies only while group is still active/forming.
|
|
4246
|
+
return this.data.status !== "complete";
|
|
4247
|
+
}
|
|
4248
|
+
inferContactType(value) {
|
|
4249
|
+
const trimmed = value.trim();
|
|
4250
|
+
if (!trimmed)
|
|
4251
|
+
return null;
|
|
4252
|
+
if (trimmed.includes("@")) {
|
|
4253
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
4254
|
+
return emailRegex.test(trimmed) ? "email" : null;
|
|
4255
|
+
}
|
|
4256
|
+
const digitsOnly = trimmed.replace(/\D/g, "");
|
|
4257
|
+
return digitsOnly.length >= 10 && digitsOnly.length <= 15 ? "phone" : null;
|
|
4258
|
+
}
|
|
4259
|
+
async runLeaveFlowAndClose() {
|
|
4260
|
+
var _a, _b;
|
|
4261
|
+
if (this.leaveFlowInProgress)
|
|
4262
|
+
return;
|
|
4263
|
+
this.leaveFlowInProgress = true;
|
|
4264
|
+
try {
|
|
4265
|
+
if (this.hasContactProtection) {
|
|
4266
|
+
this.trackAnalyticsEvent("LOBBY_CLOSED_PROTECTED", {
|
|
4267
|
+
leaveSource: "close_icon",
|
|
4268
|
+
leaveReason: "stay_in_group",
|
|
4269
|
+
protectionState: "protected",
|
|
4270
|
+
});
|
|
4271
|
+
this.close({ skipLeaveFlow: true });
|
|
4272
|
+
return;
|
|
4273
|
+
}
|
|
4274
|
+
let leaveReason = "voluntary_no_contact";
|
|
4275
|
+
const leaveDecision = await this.openUnprotectedLeaveWarning();
|
|
4276
|
+
if (leaveDecision === "cancel") {
|
|
4277
|
+
return;
|
|
4278
|
+
}
|
|
4279
|
+
if (leaveDecision === "add_contact") {
|
|
4280
|
+
const promptResult = await this.openContactProtectionPrompt("leave-warning");
|
|
4281
|
+
if (promptResult === "saved") {
|
|
4282
|
+
this.updateContactProtectionUI();
|
|
4283
|
+
return;
|
|
4284
|
+
}
|
|
4285
|
+
else {
|
|
4286
|
+
return;
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
if (this.apiClient && this.currentGroupId) {
|
|
4290
|
+
this.showBlockingLoader("Leaving group...");
|
|
4291
|
+
const leaveResult = await this.apiClient.leaveGroup(this.currentGroupId, {
|
|
4292
|
+
leave_source: "close_icon",
|
|
4293
|
+
leave_reason: leaveReason,
|
|
4294
|
+
});
|
|
4295
|
+
if (!leaveResult.success) {
|
|
4296
|
+
this.logger.warn("Leave group failed", leaveResult.error);
|
|
4297
|
+
this.hideBlockingLoader();
|
|
4298
|
+
window.alert("Could not leave the group right now. Please try again.");
|
|
4299
|
+
return;
|
|
4300
|
+
}
|
|
4301
|
+
this.leaveSignalSent = true;
|
|
4302
|
+
this.persistLastLeftGroup(((_b = (_a = leaveResult.data) === null || _a === void 0 ? void 0 : _a.group) === null || _b === void 0 ? void 0 : _b.id) || this.currentGroupId);
|
|
4303
|
+
this.trackAnalyticsEvent("LOBBY_LEFT_UNPROTECTED", {
|
|
4304
|
+
leaveSource: "close_icon",
|
|
4305
|
+
leaveReason,
|
|
4306
|
+
protectionState: "unprotected",
|
|
4307
|
+
});
|
|
4308
|
+
}
|
|
4309
|
+
this.close({ skipLeaveFlow: true });
|
|
4310
|
+
}
|
|
4311
|
+
finally {
|
|
4312
|
+
this.hideBlockingLoader();
|
|
4313
|
+
this.leaveFlowInProgress = false;
|
|
4314
|
+
}
|
|
4315
|
+
}
|
|
3663
4316
|
/**
|
|
3664
4317
|
* Close the modal
|
|
3665
4318
|
*/
|
|
3666
|
-
close() {
|
|
4319
|
+
close(options = {}) {
|
|
3667
4320
|
if (!this.modalElement) {
|
|
3668
4321
|
this.logger.warn("Modal not open");
|
|
3669
4322
|
return;
|
|
3670
4323
|
}
|
|
4324
|
+
if (!options.skipLeaveFlow && this.shouldRunLeaveFlow()) {
|
|
4325
|
+
void this.runLeaveFlowAndClose();
|
|
4326
|
+
return;
|
|
4327
|
+
}
|
|
3671
4328
|
// Stop timers and animations
|
|
3672
4329
|
this.stopTimer();
|
|
3673
4330
|
this.stopActivityAnimation();
|
|
3674
4331
|
// Remove keyboard event listeners
|
|
3675
4332
|
this.removeKeyboardAccessibility();
|
|
4333
|
+
// Remove lifecycle handlers for dropoff detection
|
|
4334
|
+
this.unregisterLifecycleLeaveHandlers();
|
|
4335
|
+
this.clearInitialContactPromptTimer();
|
|
4336
|
+
this.clearContactPromptOverlay();
|
|
4337
|
+
this.hideBlockingLoader();
|
|
4338
|
+
// Remove socket listeners to avoid duplicate handlers on reopen
|
|
4339
|
+
this.unsubscribeFromSocketEvents();
|
|
3676
4340
|
// Remove modal from DOM
|
|
3677
4341
|
document.body.removeChild(this.modalElement);
|
|
3678
4342
|
this.modalElement = null;
|
|
@@ -3785,29 +4449,47 @@ var CoBuySDK = (function (exports) {
|
|
|
3785
4449
|
* Update offline redemption visibility when group is fulfilled
|
|
3786
4450
|
*/
|
|
3787
4451
|
updateOfflineRedemptionVisibility() {
|
|
4452
|
+
var _a, _b;
|
|
3788
4453
|
const connectedSection = document.getElementById("lobbyConnectedSection");
|
|
3789
4454
|
if (!connectedSection)
|
|
3790
4455
|
return;
|
|
3791
4456
|
const isComplete = !this.computeIsLocked(this.data);
|
|
3792
|
-
const hasOfflineRedemption = this.data.offlineRedemption &&
|
|
4457
|
+
const hasOfflineRedemption = this.data.offlineRedemption &&
|
|
4458
|
+
isValidOfflineRedemption(this.data.offlineRedemption) &&
|
|
4459
|
+
((_a = this.data.redemptionMethod) !== null && _a !== void 0 ? _a : "online") !== "online";
|
|
3793
4460
|
// Get existing elements
|
|
3794
4461
|
const existingOffline = connectedSection.querySelector(".offline-redemption-section");
|
|
3795
4462
|
const existingLink = connectedSection.querySelector(".link-share-container");
|
|
4463
|
+
const existingCheckoutBtn = connectedSection.querySelector(".lobby-online-checkout-btn");
|
|
3796
4464
|
if (isComplete && hasOfflineRedemption) {
|
|
3797
|
-
//
|
|
3798
|
-
if (existingLink)
|
|
4465
|
+
// Complete + offline/both: show offline section, remove link/share and standalone checkout btn
|
|
4466
|
+
if (existingLink)
|
|
3799
4467
|
existingLink.remove();
|
|
3800
|
-
|
|
4468
|
+
if (existingCheckoutBtn)
|
|
4469
|
+
existingCheckoutBtn.remove();
|
|
3801
4470
|
if (!existingOffline) {
|
|
3802
4471
|
const offlineSection = this.createOfflineRedemptionSection(this.data.offlineRedemption);
|
|
3803
4472
|
connectedSection.appendChild(offlineSection);
|
|
3804
4473
|
}
|
|
3805
4474
|
}
|
|
3806
|
-
else {
|
|
3807
|
-
//
|
|
3808
|
-
if (
|
|
4475
|
+
else if (isComplete && ((_b = this.data.redemptionMethod) !== null && _b !== void 0 ? _b : "online") !== "offline") {
|
|
4476
|
+
// Complete + online/both (no offline data): show only checkout CTA, remove link/share
|
|
4477
|
+
if (existingLink)
|
|
4478
|
+
existingLink.remove();
|
|
4479
|
+
if (existingOffline)
|
|
3809
4480
|
existingOffline.remove();
|
|
4481
|
+
if (!existingCheckoutBtn) {
|
|
4482
|
+
const checkoutBtn = this.createOnlineCheckoutButton();
|
|
4483
|
+
connectedSection.appendChild(checkoutBtn);
|
|
4484
|
+
this.injectOfflineRedemptionStyles();
|
|
3810
4485
|
}
|
|
4486
|
+
}
|
|
4487
|
+
else {
|
|
4488
|
+
// Group not yet complete: show link/share, remove offline and checkout btn
|
|
4489
|
+
if (existingOffline)
|
|
4490
|
+
existingOffline.remove();
|
|
4491
|
+
if (existingCheckoutBtn)
|
|
4492
|
+
existingCheckoutBtn.remove();
|
|
3811
4493
|
if (!existingLink) {
|
|
3812
4494
|
const linkShareContainer = document.createElement("div");
|
|
3813
4495
|
linkShareContainer.className = "link-share-container";
|
|
@@ -3841,9 +4523,20 @@ var CoBuySDK = (function (exports) {
|
|
|
3841
4523
|
}
|
|
3842
4524
|
window.addEventListener("group:fulfilled", this.handleSocketGroupUpdate);
|
|
3843
4525
|
window.addEventListener("group:member:joined", this.handleSocketGroupUpdate);
|
|
4526
|
+
window.addEventListener("group:member:left", this.handleSocketGroupUpdate);
|
|
3844
4527
|
window.addEventListener("group:created", this.handleSocketGroupUpdate);
|
|
3845
4528
|
this.socketListenerRegistered = true;
|
|
3846
4529
|
}
|
|
4530
|
+
unsubscribeFromSocketEvents() {
|
|
4531
|
+
if (typeof window === "undefined" || !this.socketListenerRegistered) {
|
|
4532
|
+
return;
|
|
4533
|
+
}
|
|
4534
|
+
window.removeEventListener("group:fulfilled", this.handleSocketGroupUpdate);
|
|
4535
|
+
window.removeEventListener("group:member:joined", this.handleSocketGroupUpdate);
|
|
4536
|
+
window.removeEventListener("group:member:left", this.handleSocketGroupUpdate);
|
|
4537
|
+
window.removeEventListener("group:created", this.handleSocketGroupUpdate);
|
|
4538
|
+
this.socketListenerRegistered = false;
|
|
4539
|
+
}
|
|
3847
4540
|
/**
|
|
3848
4541
|
* Create activity item from socket event data
|
|
3849
4542
|
*/
|
|
@@ -3874,6 +4567,10 @@ var CoBuySDK = (function (exports) {
|
|
|
3874
4567
|
emoji = "🛒";
|
|
3875
4568
|
action = "started a new group";
|
|
3876
4569
|
break;
|
|
4570
|
+
case "group:member:left":
|
|
4571
|
+
emoji = "🚪";
|
|
4572
|
+
action = "left the group";
|
|
4573
|
+
break;
|
|
3877
4574
|
default:
|
|
3878
4575
|
emoji = "⚡";
|
|
3879
4576
|
action = "activity in group";
|
|
@@ -4346,6 +5043,9 @@ var CoBuySDK = (function (exports) {
|
|
|
4346
5043
|
WidgetState["LOADED"] = "loaded";
|
|
4347
5044
|
WidgetState["ERROR"] = "error";
|
|
4348
5045
|
})(WidgetState || (WidgetState = {}));
|
|
5046
|
+
const RECOVERY_TOAST_REJOINED = "Rejoined your previous group.";
|
|
5047
|
+
const RECOVERY_TOAST_NEXT_AVAILABLE = "Your previous group was full, so we joined you to the next available group.";
|
|
5048
|
+
const RECOVERY_TOAST_CREATED_NEW = "Your previous group was no longer available, so we created a fresh group for you.";
|
|
4349
5049
|
/**
|
|
4350
5050
|
* WidgetRoot handles rendering of the CoBuy widget into a DOM container
|
|
4351
5051
|
*/
|
|
@@ -4377,6 +5077,7 @@ var CoBuySDK = (function (exports) {
|
|
|
4377
5077
|
this.groupExpiryRefreshTriggered = false; // Track if expiry refresh already triggered for current group
|
|
4378
5078
|
this.offlineRedemption = null;
|
|
4379
5079
|
this.offlineRedemptionModal = null;
|
|
5080
|
+
this.campaignRedemptionMethod = "online";
|
|
4380
5081
|
this.isRendering = false;
|
|
4381
5082
|
this.renderPromise = null;
|
|
4382
5083
|
this.liveRegionAnnouncer = null;
|
|
@@ -4386,13 +5087,22 @@ var CoBuySDK = (function (exports) {
|
|
|
4386
5087
|
this.renderDebounceTimer = null;
|
|
4387
5088
|
this.pendingRenderOptions = null;
|
|
4388
5089
|
this.RENDER_DEBOUNCE_MS = 300; // 300ms debounce for rapid re-renders
|
|
5090
|
+
this.LAST_LEFT_GROUP_PREFIX = "cobuy_last_left_group";
|
|
4389
5091
|
/** Handle backend fulfillment notifications */
|
|
4390
5092
|
this.handleGroupFulfilledEvent = (event) => {
|
|
4391
5093
|
const detail = event.detail;
|
|
4392
|
-
if (!detail || !detail.
|
|
5094
|
+
if (!detail || !detail.product_id) {
|
|
4393
5095
|
return;
|
|
4394
5096
|
}
|
|
4395
|
-
if (this.currentProductId && detail.
|
|
5097
|
+
if (this.currentProductId && detail.product_id !== this.currentProductId) {
|
|
5098
|
+
return;
|
|
5099
|
+
}
|
|
5100
|
+
// Only enter the fulfilled/checkout flow for users who have actively joined a group this
|
|
5101
|
+
// session. currentSessionId is set exclusively in the setOnGroupJoined callback (when the
|
|
5102
|
+
// user completes a join API call). Observers who never joined should re-fetch the primary
|
|
5103
|
+
// group API to display the updated group state instead.
|
|
5104
|
+
if (!this.currentSessionId) {
|
|
5105
|
+
void this.refreshGroupDataFromRealtime();
|
|
4396
5106
|
return;
|
|
4397
5107
|
}
|
|
4398
5108
|
this.processGroupFulfilled(detail);
|
|
@@ -4421,6 +5131,80 @@ var CoBuySDK = (function (exports) {
|
|
|
4421
5131
|
this.handleCTAClick(productId);
|
|
4422
5132
|
}, 300); // 300ms debounce to prevent double-clicks
|
|
4423
5133
|
}
|
|
5134
|
+
getRecoveryStorageKey(productId) {
|
|
5135
|
+
var _a;
|
|
5136
|
+
const sessionId = (_a = this.apiClient) === null || _a === void 0 ? void 0 : _a.getSessionId();
|
|
5137
|
+
if (!sessionId || !productId) {
|
|
5138
|
+
return null;
|
|
5139
|
+
}
|
|
5140
|
+
return `${this.LAST_LEFT_GROUP_PREFIX}:${sessionId}:${productId}`;
|
|
5141
|
+
}
|
|
5142
|
+
getStoredRecoveryGroupId(productId) {
|
|
5143
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
5144
|
+
return null;
|
|
5145
|
+
}
|
|
5146
|
+
const key = this.getRecoveryStorageKey(productId);
|
|
5147
|
+
if (!key) {
|
|
5148
|
+
return null;
|
|
5149
|
+
}
|
|
5150
|
+
try {
|
|
5151
|
+
const raw = window.localStorage.getItem(key);
|
|
5152
|
+
if (!raw) {
|
|
5153
|
+
return null;
|
|
5154
|
+
}
|
|
5155
|
+
const parsed = JSON.parse(raw);
|
|
5156
|
+
if (!(parsed === null || parsed === void 0 ? void 0 : parsed.groupId) || parsed.productId !== productId) {
|
|
5157
|
+
return null;
|
|
5158
|
+
}
|
|
5159
|
+
return parsed.groupId;
|
|
5160
|
+
}
|
|
5161
|
+
catch (_a) {
|
|
5162
|
+
return null;
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
clearStoredRecoveryGroupId(productId) {
|
|
5166
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
5167
|
+
return;
|
|
5168
|
+
}
|
|
5169
|
+
const key = this.getRecoveryStorageKey(productId);
|
|
5170
|
+
if (!key) {
|
|
5171
|
+
return;
|
|
5172
|
+
}
|
|
5173
|
+
try {
|
|
5174
|
+
window.localStorage.removeItem(key);
|
|
5175
|
+
}
|
|
5176
|
+
catch (_a) {
|
|
5177
|
+
// Ignore storage issues
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
showRecoveryToast(message) {
|
|
5181
|
+
if (typeof document === "undefined") {
|
|
5182
|
+
return;
|
|
5183
|
+
}
|
|
5184
|
+
const existing = document.getElementById("cobuy-recovery-toast");
|
|
5185
|
+
if (existing) {
|
|
5186
|
+
existing.remove();
|
|
5187
|
+
}
|
|
5188
|
+
const toast = document.createElement("div");
|
|
5189
|
+
toast.id = "cobuy-recovery-toast";
|
|
5190
|
+
toast.style.position = "fixed";
|
|
5191
|
+
toast.style.left = "50%";
|
|
5192
|
+
toast.style.bottom = "24px";
|
|
5193
|
+
toast.style.transform = "translateX(-50%)";
|
|
5194
|
+
toast.style.background = "#0f172a";
|
|
5195
|
+
toast.style.color = "#fff";
|
|
5196
|
+
toast.style.padding = "10px 14px";
|
|
5197
|
+
toast.style.borderRadius = "10px";
|
|
5198
|
+
toast.style.fontSize = "13px";
|
|
5199
|
+
toast.style.fontWeight = "600";
|
|
5200
|
+
toast.style.zIndex = "10040";
|
|
5201
|
+
toast.style.boxShadow = "0 8px 20px rgba(0,0,0,0.24)";
|
|
5202
|
+
toast.textContent = message;
|
|
5203
|
+
document.body.appendChild(toast);
|
|
5204
|
+
window.setTimeout(() => {
|
|
5205
|
+
toast.remove();
|
|
5206
|
+
}, 2800);
|
|
5207
|
+
}
|
|
4424
5208
|
/** Subscribe once to backend socket events routed through the host page */
|
|
4425
5209
|
subscribeToSocketEvents() {
|
|
4426
5210
|
if (typeof window === "undefined" || this.socketListenerRegistered) {
|
|
@@ -4428,6 +5212,7 @@ var CoBuySDK = (function (exports) {
|
|
|
4428
5212
|
}
|
|
4429
5213
|
window.addEventListener("group:fulfilled", this.handleGroupFulfilledEvent);
|
|
4430
5214
|
window.addEventListener("group:member:joined", this.handleGroupUpdatedEvent);
|
|
5215
|
+
window.addEventListener("group:member:left", this.handleGroupUpdatedEvent);
|
|
4431
5216
|
window.addEventListener("group:created", this.handleGroupUpdatedEvent);
|
|
4432
5217
|
this.socketListenerRegistered = true;
|
|
4433
5218
|
}
|
|
@@ -4449,12 +5234,24 @@ var CoBuySDK = (function (exports) {
|
|
|
4449
5234
|
const groupData = await this.fetchPrimaryGroup(this.currentProductId);
|
|
4450
5235
|
this.currentGroupData = groupData;
|
|
4451
5236
|
this.currentGroupId = (groupData === null || groupData === void 0 ? void 0 : groupData.id) || this.currentGroupId;
|
|
4452
|
-
// If backend signals completion via counts, reflect it
|
|
5237
|
+
// If backend signals completion via counts, reflect it — but only for actual members.
|
|
5238
|
+
// Observers may see a full group returned by fetchPrimaryGroup; they should NOT enter
|
|
5239
|
+
// the checkout flow. currentSessionId is the membership signal (non-null = joined).
|
|
4453
5240
|
if (groupData) {
|
|
4454
5241
|
const participants = Number(groupData.participants_count || 0);
|
|
4455
5242
|
const max = Number(groupData.max_participants || 0);
|
|
4456
|
-
if (max > 0 && participants >= max) {
|
|
5243
|
+
if (max > 0 && participants >= max && this.currentSessionId) {
|
|
4457
5244
|
this.groupFulfilled = true;
|
|
5245
|
+
// The primary group API returns offline_redemption for members of fulfilled groups.
|
|
5246
|
+
// Extract it here so renderFulfilledSummary shows the "Redeem In-store" link
|
|
5247
|
+
// (matching the UI users see on a full page reload).
|
|
5248
|
+
if (groupData.offline_redemption &&
|
|
5249
|
+
isValidOfflineRedemption(groupData.offline_redemption)) {
|
|
5250
|
+
this.offlineRedemption = groupData.offline_redemption;
|
|
5251
|
+
}
|
|
5252
|
+
if (groupData.campaign_redemption_method) {
|
|
5253
|
+
this.campaignRedemptionMethod = groupData.campaign_redemption_method;
|
|
5254
|
+
}
|
|
4458
5255
|
}
|
|
4459
5256
|
}
|
|
4460
5257
|
else {
|
|
@@ -4550,7 +5347,7 @@ var CoBuySDK = (function (exports) {
|
|
|
4550
5347
|
}
|
|
4551
5348
|
/** Persist fulfilled state, emit callback, and refresh UI */
|
|
4552
5349
|
processGroupFulfilled(eventData) {
|
|
4553
|
-
var _a, _b, _c, _d;
|
|
5350
|
+
var _a, _b, _c, _d, _e;
|
|
4554
5351
|
this.groupFulfilled = true;
|
|
4555
5352
|
// Extract reward from new event structure or fallback to legacy
|
|
4556
5353
|
const reward = ((_a = eventData.frozen_reward) === null || _a === void 0 ? void 0 : _a.reward) || eventData.reward || null;
|
|
@@ -4565,7 +5362,10 @@ var CoBuySDK = (function (exports) {
|
|
|
4565
5362
|
code: this.offlineRedemption.redemption_code,
|
|
4566
5363
|
});
|
|
4567
5364
|
}
|
|
4568
|
-
|
|
5365
|
+
if ((_c = eventData.group) === null || _c === void 0 ? void 0 : _c.campaign_redemption_method) {
|
|
5366
|
+
this.campaignRedemptionMethod = eventData.group.campaign_redemption_method;
|
|
5367
|
+
}
|
|
5368
|
+
(_e = (_d = this.events) === null || _d === void 0 ? void 0 : _d.onGroupFulfilled) === null || _e === void 0 ? void 0 : _e.call(_d, eventData);
|
|
4569
5369
|
this.renderFulfilledState();
|
|
4570
5370
|
}
|
|
4571
5371
|
/** Re-render widget and external containers to reflect fulfillment */
|
|
@@ -4705,8 +5505,10 @@ var CoBuySDK = (function (exports) {
|
|
|
4705
5505
|
footer.style.justifyContent = "flex-end";
|
|
4706
5506
|
footer.style.alignItems = "center";
|
|
4707
5507
|
footer.style.gap = "12px";
|
|
4708
|
-
// Add "Redeem In-store" CTA if offline redemption is available
|
|
4709
|
-
if (this.offlineRedemption &&
|
|
5508
|
+
// Add "Redeem In-store" CTA if offline redemption is available and campaign allows it
|
|
5509
|
+
if (this.offlineRedemption &&
|
|
5510
|
+
isValidOfflineRedemption(this.offlineRedemption) &&
|
|
5511
|
+
this.campaignRedemptionMethod !== "online") {
|
|
4710
5512
|
const redeemLink = document.createElement("button");
|
|
4711
5513
|
redeemLink.className = "cobuy-redeem-instore-link";
|
|
4712
5514
|
redeemLink.style.background = "none";
|
|
@@ -4799,9 +5601,12 @@ var CoBuySDK = (function (exports) {
|
|
|
4799
5601
|
getGroupListModal() {
|
|
4800
5602
|
if (!this.groupListModal) {
|
|
4801
5603
|
this.groupListModal = new GroupListModal(undefined, 8, this.config.debug, this.apiClient);
|
|
5604
|
+
if (this.analyticsClient) {
|
|
5605
|
+
this.groupListModal.setAnalyticsClient(this.analyticsClient);
|
|
5606
|
+
}
|
|
4802
5607
|
// Set callback to open lobby when a group is successfully joined
|
|
4803
5608
|
this.groupListModal.setOnGroupJoined((joinData) => {
|
|
4804
|
-
var _a;
|
|
5609
|
+
var _a, _b;
|
|
4805
5610
|
// Update current group ID and session ID so the modal knows which group the user has joined
|
|
4806
5611
|
this.currentGroupId = joinData.group.id;
|
|
4807
5612
|
this.currentSessionId = ((_a = this.apiClient) === null || _a === void 0 ? void 0 : _a.getSessionId()) || null;
|
|
@@ -4815,8 +5620,9 @@ var CoBuySDK = (function (exports) {
|
|
|
4815
5620
|
currentMembers: joinData.group.participants_count,
|
|
4816
5621
|
totalMembers: joinData.group.max_participants,
|
|
4817
5622
|
timeLeft: joinData.group.timeLeftSeconds,
|
|
5623
|
+
redemptionMethod: (_b = joinData.group.campaign_redemption_method) !== null && _b !== void 0 ? _b : this.campaignRedemptionMethod,
|
|
4818
5624
|
};
|
|
4819
|
-
const lobbyModal = new LobbyModal(lobbyData, {},
|
|
5625
|
+
const lobbyModal = new LobbyModal(lobbyData, {}, this.apiClient, null, this.analyticsClient, this.config.debug);
|
|
4820
5626
|
lobbyModal.open(joinData.group.id);
|
|
4821
5627
|
});
|
|
4822
5628
|
// Set callback for viewing progress on already joined group
|
|
@@ -4832,8 +5638,9 @@ var CoBuySDK = (function (exports) {
|
|
|
4832
5638
|
progress: Math.round((groupData.joined / groupData.total) * 100),
|
|
4833
5639
|
currentMembers: groupData.joined,
|
|
4834
5640
|
totalMembers: groupData.total,
|
|
5641
|
+
redemptionMethod: this.campaignRedemptionMethod,
|
|
4835
5642
|
};
|
|
4836
|
-
const lobbyModal = new LobbyModal(lobbyData, {},
|
|
5643
|
+
const lobbyModal = new LobbyModal(lobbyData, {}, this.apiClient, null, this.analyticsClient, this.config.debug);
|
|
4837
5644
|
lobbyModal.open(groupId);
|
|
4838
5645
|
});
|
|
4839
5646
|
}
|
|
@@ -5017,6 +5824,9 @@ var CoBuySDK = (function (exports) {
|
|
|
5017
5824
|
isValidOfflineRedemption(groupData.offline_redemption)) {
|
|
5018
5825
|
this.offlineRedemption = groupData.offline_redemption;
|
|
5019
5826
|
}
|
|
5827
|
+
if (groupData === null || groupData === void 0 ? void 0 : groupData.campaign_redemption_method) {
|
|
5828
|
+
this.campaignRedemptionMethod = groupData.campaign_redemption_method;
|
|
5829
|
+
}
|
|
5020
5830
|
// Check if group is already fulfilled (full) on initial load
|
|
5021
5831
|
if (groupData) {
|
|
5022
5832
|
const participants = Number(groupData.participants_count || 0);
|
|
@@ -5024,6 +5834,11 @@ var CoBuySDK = (function (exports) {
|
|
|
5024
5834
|
if (max > 0 && participants >= max) {
|
|
5025
5835
|
this.groupFulfilled = true;
|
|
5026
5836
|
this.logger.info("Group is already fulfilled on initial load", { participants, max });
|
|
5837
|
+
if (this.analyticsClient) {
|
|
5838
|
+
this.analyticsClient
|
|
5839
|
+
.trackGroupFullView(options.productId, groupData.id, max)
|
|
5840
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
5841
|
+
}
|
|
5027
5842
|
}
|
|
5028
5843
|
}
|
|
5029
5844
|
}
|
|
@@ -5089,6 +5904,11 @@ var CoBuySDK = (function (exports) {
|
|
|
5089
5904
|
// LOADED state - render widget only if we have group data
|
|
5090
5905
|
this.createWidget(rewardData, container, options);
|
|
5091
5906
|
this.logger.info(`Widget rendered for product: ${options.productId}`);
|
|
5907
|
+
if (this.analyticsClient) {
|
|
5908
|
+
this.analyticsClient
|
|
5909
|
+
.trackCreativeView(options.productId)
|
|
5910
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
5911
|
+
}
|
|
5092
5912
|
}
|
|
5093
5913
|
else {
|
|
5094
5914
|
// No group data available - hide widget
|
|
@@ -5728,10 +6548,99 @@ var CoBuySDK = (function (exports) {
|
|
|
5728
6548
|
to { transform: rotate(360deg); }
|
|
5729
6549
|
}
|
|
5730
6550
|
|
|
6551
|
+
.cobuy-prejoin-contact-overlay {
|
|
6552
|
+
position: fixed;
|
|
6553
|
+
inset: 0;
|
|
6554
|
+
z-index: 10045;
|
|
6555
|
+
background: rgba(2, 6, 23, 0.5);
|
|
6556
|
+
display: flex;
|
|
6557
|
+
align-items: center;
|
|
6558
|
+
justify-content: center;
|
|
6559
|
+
padding: 16px;
|
|
6560
|
+
}
|
|
6561
|
+
|
|
6562
|
+
.cobuy-prejoin-contact-card {
|
|
6563
|
+
width: min(440px, 100%);
|
|
6564
|
+
background: #fff;
|
|
6565
|
+
border-radius: 14px;
|
|
6566
|
+
padding: 18px;
|
|
6567
|
+
box-shadow: 0 14px 36px rgba(15, 23, 42, 0.22);
|
|
6568
|
+
display: flex;
|
|
6569
|
+
flex-direction: column;
|
|
6570
|
+
gap: 10px;
|
|
6571
|
+
}
|
|
6572
|
+
|
|
6573
|
+
.cobuy-prejoin-contact-title {
|
|
6574
|
+
font-size: 20px;
|
|
6575
|
+
font-weight: 800;
|
|
6576
|
+
color: #0f172a;
|
|
6577
|
+
margin: 0;
|
|
6578
|
+
}
|
|
6579
|
+
|
|
6580
|
+
.cobuy-prejoin-contact-subtitle {
|
|
6581
|
+
font-size: 14px;
|
|
6582
|
+
line-height: 1.5;
|
|
6583
|
+
color: #334155;
|
|
6584
|
+
margin: 0;
|
|
6585
|
+
}
|
|
6586
|
+
|
|
6587
|
+
.cobuy-prejoin-contact-input {
|
|
6588
|
+
width: 100%;
|
|
6589
|
+
border: 1px solid #cbd5e1;
|
|
6590
|
+
border-radius: 10px;
|
|
6591
|
+
padding: 11px 12px;
|
|
6592
|
+
font-size: 14px;
|
|
6593
|
+
outline: none;
|
|
6594
|
+
}
|
|
6595
|
+
|
|
6596
|
+
.cobuy-prejoin-contact-input:focus {
|
|
6597
|
+
border-color: #1d4ed8;
|
|
6598
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
|
6599
|
+
}
|
|
6600
|
+
|
|
6601
|
+
.cobuy-prejoin-contact-actions {
|
|
6602
|
+
display: flex;
|
|
6603
|
+
gap: 8px;
|
|
6604
|
+
flex-wrap: wrap;
|
|
6605
|
+
}
|
|
6606
|
+
|
|
6607
|
+
.cobuy-prejoin-contact-primary,
|
|
6608
|
+
.cobuy-prejoin-contact-secondary {
|
|
6609
|
+
border: none;
|
|
6610
|
+
border-radius: 10px;
|
|
6611
|
+
padding: 10px 12px;
|
|
6612
|
+
font-weight: 700;
|
|
6613
|
+
cursor: pointer;
|
|
6614
|
+
font-size: 13px;
|
|
6615
|
+
}
|
|
6616
|
+
|
|
6617
|
+
.cobuy-prejoin-contact-primary {
|
|
6618
|
+
background: #1d4ed8;
|
|
6619
|
+
color: #fff;
|
|
6620
|
+
}
|
|
6621
|
+
|
|
6622
|
+
.cobuy-prejoin-contact-primary:hover {
|
|
6623
|
+
background: #1e40af;
|
|
6624
|
+
}
|
|
6625
|
+
|
|
6626
|
+
.cobuy-prejoin-contact-secondary {
|
|
6627
|
+
background: #e2e8f0;
|
|
6628
|
+
color: #1e293b;
|
|
6629
|
+
}
|
|
6630
|
+
|
|
6631
|
+
.cobuy-prejoin-contact-secondary:hover {
|
|
6632
|
+
background: #cbd5e1;
|
|
6633
|
+
}
|
|
6634
|
+
|
|
5731
6635
|
@media (max-width: 640px) {
|
|
5732
6636
|
.cobuy-widget {
|
|
5733
6637
|
grid-template-columns: 1fr;
|
|
5734
6638
|
}
|
|
6639
|
+
|
|
6640
|
+
.cobuy-prejoin-contact-primary,
|
|
6641
|
+
.cobuy-prejoin-contact-secondary {
|
|
6642
|
+
width: 100%;
|
|
6643
|
+
}
|
|
5735
6644
|
}
|
|
5736
6645
|
`;
|
|
5737
6646
|
document.head.appendChild(style);
|
|
@@ -5740,7 +6649,7 @@ var CoBuySDK = (function (exports) {
|
|
|
5740
6649
|
* Handle CTA button click with analytics and modal opening
|
|
5741
6650
|
*/
|
|
5742
6651
|
async handleCTAClick(productId) {
|
|
5743
|
-
var _a, _b, _c, _d;
|
|
6652
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
5744
6653
|
this.logger.info(`CTA clicked for product: ${productId}`);
|
|
5745
6654
|
// Track analytics event asynchronously (fire-and-forget)
|
|
5746
6655
|
if (this.analyticsClient) {
|
|
@@ -5755,40 +6664,157 @@ var CoBuySDK = (function (exports) {
|
|
|
5755
6664
|
// Join group before opening modal
|
|
5756
6665
|
let groupJoinData = null;
|
|
5757
6666
|
let inviteData = null;
|
|
6667
|
+
let recoveryGroupId = null;
|
|
5758
6668
|
if (this.apiClient && this.currentGroupId) {
|
|
5759
6669
|
try {
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
6670
|
+
recoveryGroupId = this.getStoredRecoveryGroupId(productId);
|
|
6671
|
+
if (this.analyticsClient && recoveryGroupId) {
|
|
6672
|
+
this.analyticsClient
|
|
6673
|
+
.trackEvent("RECOVERY_ATTEMPT", productId, {
|
|
6674
|
+
previousGroupId: recoveryGroupId,
|
|
6675
|
+
protectionState: "protected",
|
|
6676
|
+
})
|
|
6677
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6678
|
+
}
|
|
6679
|
+
const recoverResponse = await this.apiClient.recoverOrJoinGroup(productId);
|
|
6680
|
+
if (recoverResponse.success && recoverResponse.data) {
|
|
6681
|
+
groupJoinData = recoverResponse.data;
|
|
6682
|
+
this.currentGroupId = groupJoinData.group.id;
|
|
6683
|
+
this.clearStoredRecoveryGroupId(productId);
|
|
6684
|
+
if (this.analyticsClient && groupJoinData.recovery.recovered) {
|
|
6685
|
+
this.analyticsClient
|
|
6686
|
+
.trackEvent("RECOVERY_SUCCESS", productId, {
|
|
6687
|
+
previousGroupId: groupJoinData.recovery.previous_group_id || recoveryGroupId,
|
|
6688
|
+
joinedGroupId: groupJoinData.recovery.joined_group_id,
|
|
6689
|
+
recoveryOutcome: groupJoinData.recovery.outcome,
|
|
6690
|
+
protectionState: groupJoinData.recovery.matched_by === "none" ? "unprotected" : "protected",
|
|
6691
|
+
})
|
|
6692
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6693
|
+
}
|
|
6694
|
+
else if (this.analyticsClient && recoveryGroupId) {
|
|
6695
|
+
this.analyticsClient
|
|
6696
|
+
.trackEvent("RECOVERY_FAILED", productId, {
|
|
6697
|
+
previousGroupId: recoveryGroupId,
|
|
6698
|
+
errorCode: "NOT_RECOVERED",
|
|
6699
|
+
errorMessage: "Recovery did not restore the previous membership.",
|
|
6700
|
+
protectionState: "protected",
|
|
6701
|
+
})
|
|
6702
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6703
|
+
}
|
|
6704
|
+
switch (groupJoinData.recovery.outcome) {
|
|
6705
|
+
case "rejoined_previous_group":
|
|
6706
|
+
this.showRecoveryToast(RECOVERY_TOAST_REJOINED);
|
|
6707
|
+
break;
|
|
6708
|
+
case "joined_next_available_group":
|
|
6709
|
+
if (groupJoinData.recovery.recovered) {
|
|
6710
|
+
this.showRecoveryToast(RECOVERY_TOAST_NEXT_AVAILABLE);
|
|
6711
|
+
}
|
|
6712
|
+
break;
|
|
6713
|
+
case "created_new_group":
|
|
6714
|
+
if (groupJoinData.recovery.recovered) {
|
|
6715
|
+
this.showRecoveryToast(RECOVERY_TOAST_CREATED_NEW);
|
|
6716
|
+
}
|
|
6717
|
+
break;
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
else {
|
|
6721
|
+
if (this.analyticsClient && recoveryGroupId) {
|
|
6722
|
+
const errCode = (_c = recoverResponse.error) === null || _c === void 0 ? void 0 : _c.code;
|
|
6723
|
+
const errMsg = (_d = recoverResponse.error) === null || _d === void 0 ? void 0 : _d.message;
|
|
6724
|
+
this.analyticsClient
|
|
6725
|
+
.trackEvent("RECOVERY_FAILED", productId, {
|
|
6726
|
+
previousGroupId: recoveryGroupId,
|
|
6727
|
+
errorCode: errCode || "RECOVERY_UNAVAILABLE",
|
|
6728
|
+
errorMessage: errMsg || "Recovery response was not successful.",
|
|
6729
|
+
protectionState: "protected",
|
|
6730
|
+
})
|
|
6731
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6732
|
+
}
|
|
6733
|
+
const joinTargetGroupId = recoveryGroupId || this.currentGroupId;
|
|
6734
|
+
this.logger.info(`Joining group: ${joinTargetGroupId}`);
|
|
6735
|
+
if (this.analyticsClient) {
|
|
6736
|
+
this.analyticsClient
|
|
6737
|
+
.trackJoinAttempt(productId, joinTargetGroupId)
|
|
6738
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6739
|
+
}
|
|
6740
|
+
let joinResponse = await this.apiClient.joinGroup(joinTargetGroupId);
|
|
6741
|
+
if (!joinResponse.success && recoveryGroupId && this.currentGroupId !== recoveryGroupId) {
|
|
6742
|
+
this.clearStoredRecoveryGroupId(productId);
|
|
6743
|
+
joinResponse = await this.apiClient.joinGroup(this.currentGroupId);
|
|
6744
|
+
}
|
|
6745
|
+
if (!joinResponse.success) {
|
|
6746
|
+
this.logger.error("Failed to join group, modal will not open", joinResponse.error);
|
|
6747
|
+
if (this.analyticsClient) {
|
|
6748
|
+
const errCode = (_e = joinResponse.error) === null || _e === void 0 ? void 0 : _e.code;
|
|
6749
|
+
const errMsg = (_f = joinResponse.error) === null || _f === void 0 ? void 0 : _f.message;
|
|
6750
|
+
this.analyticsClient
|
|
6751
|
+
.trackJoinFailure(productId, joinTargetGroupId, errCode, errMsg)
|
|
6752
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6753
|
+
}
|
|
6754
|
+
this.setButtonLoadingState(false);
|
|
6755
|
+
return; // Don't open modal on error
|
|
6756
|
+
}
|
|
6757
|
+
groupJoinData = joinResponse.data;
|
|
6758
|
+
if ((_g = groupJoinData === null || groupJoinData === void 0 ? void 0 : groupJoinData.group) === null || _g === void 0 ? void 0 : _g.id) {
|
|
6759
|
+
this.currentGroupId = groupJoinData.group.id;
|
|
6760
|
+
}
|
|
6761
|
+
if (recoveryGroupId && ((_h = groupJoinData === null || groupJoinData === void 0 ? void 0 : groupJoinData.group) === null || _h === void 0 ? void 0 : _h.id)) {
|
|
6762
|
+
this.clearStoredRecoveryGroupId(productId);
|
|
6763
|
+
if (groupJoinData.group.id !== recoveryGroupId) {
|
|
6764
|
+
this.showRecoveryToast(RECOVERY_TOAST_NEXT_AVAILABLE);
|
|
6765
|
+
}
|
|
6766
|
+
else {
|
|
6767
|
+
this.showRecoveryToast(RECOVERY_TOAST_REJOINED);
|
|
6768
|
+
}
|
|
6769
|
+
}
|
|
5766
6770
|
}
|
|
5767
|
-
groupJoinData = joinResponse.data;
|
|
5768
6771
|
this.logger.info("Successfully joined group", groupJoinData);
|
|
6772
|
+
if (this.analyticsClient && groupJoinData) {
|
|
6773
|
+
this.analyticsClient
|
|
6774
|
+
.trackJoinSuccess(productId, groupJoinData.group.id)
|
|
6775
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6776
|
+
}
|
|
5769
6777
|
this.offlineRedemption =
|
|
5770
6778
|
groupJoinData &&
|
|
5771
6779
|
groupJoinData.offline_redemption &&
|
|
5772
6780
|
isValidOfflineRedemption(groupJoinData.offline_redemption)
|
|
5773
6781
|
? groupJoinData.offline_redemption
|
|
5774
6782
|
: null;
|
|
6783
|
+
const joinedGroupId = (_j = groupJoinData === null || groupJoinData === void 0 ? void 0 : groupJoinData.group) === null || _j === void 0 ? void 0 : _j.id;
|
|
5775
6784
|
// Trigger invite tracking before opening lobby (global for product)
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
6785
|
+
if (joinedGroupId) {
|
|
6786
|
+
try {
|
|
6787
|
+
const inviteResponse = await this.apiClient.inviteToGroup(joinedGroupId, "copy_link");
|
|
6788
|
+
if (inviteResponse.success && inviteResponse.data) {
|
|
6789
|
+
inviteData = inviteResponse.data;
|
|
6790
|
+
this.logger.info("Invite link generated", inviteData);
|
|
6791
|
+
}
|
|
6792
|
+
else {
|
|
6793
|
+
this.logger.warn("Invite link generation failed", inviteResponse.error);
|
|
6794
|
+
}
|
|
5781
6795
|
}
|
|
5782
|
-
|
|
5783
|
-
this.logger.warn("Invite
|
|
6796
|
+
catch (inviteError) {
|
|
6797
|
+
this.logger.warn("Invite request failed", inviteError);
|
|
5784
6798
|
}
|
|
5785
6799
|
}
|
|
5786
|
-
catch (inviteError) {
|
|
5787
|
-
this.logger.warn("Invite request failed", inviteError);
|
|
5788
|
-
}
|
|
5789
6800
|
}
|
|
5790
6801
|
catch (error) {
|
|
5791
6802
|
this.logger.error("Group join failed, modal will not open", error);
|
|
6803
|
+
if (this.analyticsClient && recoveryGroupId) {
|
|
6804
|
+
this.analyticsClient
|
|
6805
|
+
.trackEvent("RECOVERY_FAILED", productId, {
|
|
6806
|
+
previousGroupId: recoveryGroupId,
|
|
6807
|
+
errorCode: "EXCEPTION",
|
|
6808
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
6809
|
+
protectionState: "protected",
|
|
6810
|
+
})
|
|
6811
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6812
|
+
}
|
|
6813
|
+
if (this.analyticsClient) {
|
|
6814
|
+
this.analyticsClient
|
|
6815
|
+
.trackJoinFailure(productId, (_k = this.currentGroupId) !== null && _k !== void 0 ? _k : undefined, "EXCEPTION", error instanceof Error ? error.message : String(error))
|
|
6816
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6817
|
+
}
|
|
5792
6818
|
this.setButtonLoadingState(false);
|
|
5793
6819
|
return; // Don't open modal on error
|
|
5794
6820
|
}
|
|
@@ -5803,7 +6829,7 @@ var CoBuySDK = (function (exports) {
|
|
|
5803
6829
|
const progress = Math.round((groupJoinData.group.participants_count / groupJoinData.group.max_participants) * 100);
|
|
5804
6830
|
// Format discount based on reward type
|
|
5805
6831
|
let discountText = "";
|
|
5806
|
-
if ((
|
|
6832
|
+
if ((_m = (_l = this.currentRewardData) === null || _l === void 0 ? void 0 : _l.reward) === null || _m === void 0 ? void 0 : _m.value) {
|
|
5807
6833
|
const rewardType = this.currentRewardData.reward.type;
|
|
5808
6834
|
const rewardValue = this.currentRewardData.reward.value;
|
|
5809
6835
|
if (rewardType === "percentage" || rewardType === "cashback") {
|
|
@@ -5843,6 +6869,12 @@ var CoBuySDK = (function (exports) {
|
|
|
5843
6869
|
shareMessage: shareMessageFromInvite,
|
|
5844
6870
|
isLocked: !isGroupFulfilled,
|
|
5845
6871
|
offlineRedemption: offlineRedemptionFromJoin,
|
|
6872
|
+
redemptionMethod: (_o = groupJoinData.group.campaign_redemption_method) !== null && _o !== void 0 ? _o : this.campaignRedemptionMethod,
|
|
6873
|
+
onShare: this.analyticsClient
|
|
6874
|
+
? () => {
|
|
6875
|
+
this.analyticsClient.trackShareClick(productId, groupJoinData.group.id, "other").catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6876
|
+
}
|
|
6877
|
+
: undefined,
|
|
5846
6878
|
activities: [
|
|
5847
6879
|
{
|
|
5848
6880
|
emoji: "👤",
|
|
@@ -5878,6 +6910,12 @@ var CoBuySDK = (function (exports) {
|
|
|
5878
6910
|
},
|
|
5879
6911
|
], // Will be populated from real-time data later
|
|
5880
6912
|
});
|
|
6913
|
+
// Track popup open after modal is launched
|
|
6914
|
+
if (this.analyticsClient) {
|
|
6915
|
+
this.analyticsClient
|
|
6916
|
+
.trackPopupOpen(productId, groupJoinData.group.id)
|
|
6917
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6918
|
+
}
|
|
5881
6919
|
// Remove loading state after modal opens
|
|
5882
6920
|
this.setButtonLoadingState(false);
|
|
5883
6921
|
}
|
|
@@ -5984,6 +7022,14 @@ var CoBuySDK = (function (exports) {
|
|
|
5984
7022
|
getProductId() {
|
|
5985
7023
|
return this.currentProductId;
|
|
5986
7024
|
}
|
|
7025
|
+
/**
|
|
7026
|
+
* Returns the group ID that the current user has joined via this widget, or null if the user
|
|
7027
|
+
* is only an observer (has not actively joined a group this session).
|
|
7028
|
+
* Used by the CoBuy host class to check membership before preparing checkout.
|
|
7029
|
+
*/
|
|
7030
|
+
getJoinedGroupId() {
|
|
7031
|
+
return this.currentSessionId ? this.currentGroupId : null;
|
|
7032
|
+
}
|
|
5987
7033
|
}
|
|
5988
7034
|
|
|
5989
7035
|
/**
|
|
@@ -5991,10 +7037,13 @@ var CoBuySDK = (function (exports) {
|
|
|
5991
7037
|
*/
|
|
5992
7038
|
const API_ENDPOINTS = {
|
|
5993
7039
|
// Product endpoints
|
|
7040
|
+
PRODUCT_CONTEXT: "/v1/sdk/products/:productId/context",
|
|
5994
7041
|
PRODUCT_REWARD: "/v1/sdk/products/:productId/reward",
|
|
5995
7042
|
PRODUCT_PRIMARY_GROUP: "/v1/sdk/products/:productId/group/primary",
|
|
7043
|
+
PRODUCT_RECOVER_OR_JOIN_GROUP: "/v1/sdk/products/:productId/group/recover-or-join",
|
|
5996
7044
|
// Group endpoints
|
|
5997
7045
|
GROUP_JOIN: "/v1/sdk/groups/:groupId/join",
|
|
7046
|
+
GROUP_LEAVE: "/v1/sdk/groups/:groupId/leave",
|
|
5998
7047
|
GROUP_CREATE_AND_JOIN: "/v1/sdk/groups/new/join",
|
|
5999
7048
|
PRODUCT_ACTIVE_GROUPS: "/v1/sdk/products/:productId/groups/active",
|
|
6000
7049
|
GROUP_INVITE: "/v1/sdk/groups/:groupId/invite",
|
|
@@ -6041,8 +7090,11 @@ var CoBuySDK = (function (exports) {
|
|
|
6041
7090
|
var _a;
|
|
6042
7091
|
this.traceId = null;
|
|
6043
7092
|
this.rewardCache = new Map();
|
|
7093
|
+
this.productContextCache = new Map();
|
|
6044
7094
|
this.REWARD_CACHE_TTL = 60000; // 1 minute
|
|
6045
|
-
this.
|
|
7095
|
+
this.PRODUCT_CONTEXT_CACHE_TTL = 30000; // 30 seconds
|
|
7096
|
+
this.pendingRewardRequests = new Map();
|
|
7097
|
+
this.pendingContextRequests = new Map();
|
|
6046
7098
|
this.baseUrl = config.baseUrl;
|
|
6047
7099
|
this.authStrategy = config.authStrategy;
|
|
6048
7100
|
this.sessionId = config.sessionId;
|
|
@@ -6438,6 +7490,106 @@ var CoBuySDK = (function (exports) {
|
|
|
6438
7490
|
getTraceId() {
|
|
6439
7491
|
return this.traceId;
|
|
6440
7492
|
}
|
|
7493
|
+
getCachedProductContext(productId) {
|
|
7494
|
+
const cached = this.productContextCache.get(productId);
|
|
7495
|
+
if (cached && cached.expires > Date.now()) {
|
|
7496
|
+
return cached.data;
|
|
7497
|
+
}
|
|
7498
|
+
if (cached) {
|
|
7499
|
+
this.productContextCache.delete(productId);
|
|
7500
|
+
}
|
|
7501
|
+
return null;
|
|
7502
|
+
}
|
|
7503
|
+
normalizePrimaryGroup(groupData) {
|
|
7504
|
+
if (!groupData) {
|
|
7505
|
+
return null;
|
|
7506
|
+
}
|
|
7507
|
+
const nestedGroup = groupData.group;
|
|
7508
|
+
if (nestedGroup && typeof nestedGroup === "object" && "id" in nestedGroup) {
|
|
7509
|
+
return nestedGroup;
|
|
7510
|
+
}
|
|
7511
|
+
return groupData;
|
|
7512
|
+
}
|
|
7513
|
+
buildRewardDataFromContext(productId, context) {
|
|
7514
|
+
var _a, _b, _c, _d, _e;
|
|
7515
|
+
const primaryGroup = this.normalizePrimaryGroup(context.primary_group);
|
|
7516
|
+
return {
|
|
7517
|
+
productId,
|
|
7518
|
+
reward: (_a = context.reward) !== null && _a !== void 0 ? _a : null,
|
|
7519
|
+
campaign_mode: (_c = (_b = context.campaign) === null || _b === void 0 ? void 0 : _b.campaign_mode) !== null && _c !== void 0 ? _c : "group",
|
|
7520
|
+
campaign_creative_id: (_d = primaryGroup === null || primaryGroup === void 0 ? void 0 : primaryGroup.campaign_creative_id) !== null && _d !== void 0 ? _d : null,
|
|
7521
|
+
eligibility: (_e = context.eligibility) !== null && _e !== void 0 ? _e : { isEligible: false },
|
|
7522
|
+
};
|
|
7523
|
+
}
|
|
7524
|
+
setProductContextCache(productId, context) {
|
|
7525
|
+
this.productContextCache.set(productId, {
|
|
7526
|
+
data: context,
|
|
7527
|
+
expires: Date.now() + this.PRODUCT_CONTEXT_CACHE_TTL,
|
|
7528
|
+
});
|
|
7529
|
+
this.rewardCache.set(productId, {
|
|
7530
|
+
data: this.buildRewardDataFromContext(productId, context),
|
|
7531
|
+
expires: Date.now() + this.REWARD_CACHE_TTL,
|
|
7532
|
+
});
|
|
7533
|
+
}
|
|
7534
|
+
/**
|
|
7535
|
+
* Get product context information
|
|
7536
|
+
*
|
|
7537
|
+
* Uses the consolidated bootstrap endpoint so the SDK can resolve campaign,
|
|
7538
|
+
* reward, primary group, and active groups in a single request.
|
|
7539
|
+
*/
|
|
7540
|
+
async getProductContext(productId) {
|
|
7541
|
+
if (!validateProductId(productId)) {
|
|
7542
|
+
return {
|
|
7543
|
+
success: false,
|
|
7544
|
+
error: {
|
|
7545
|
+
message: "Invalid productId format. Must be a non-empty string (max 200 chars).",
|
|
7546
|
+
code: "INVALID_PRODUCT_ID",
|
|
7547
|
+
},
|
|
7548
|
+
};
|
|
7549
|
+
}
|
|
7550
|
+
// const cached = this.getCachedProductContext(productId);
|
|
7551
|
+
// if (cached) {
|
|
7552
|
+
// this.logger.info(`Using cached product context for product: ${productId}`);
|
|
7553
|
+
// return {
|
|
7554
|
+
// success: true,
|
|
7555
|
+
// data: cached,
|
|
7556
|
+
// };
|
|
7557
|
+
// }
|
|
7558
|
+
const cacheKey = `context:${productId}`;
|
|
7559
|
+
const pending = this.pendingContextRequests.get(cacheKey);
|
|
7560
|
+
if (pending) {
|
|
7561
|
+
this.logger.info(`Deduplicating product context request for: ${productId}`);
|
|
7562
|
+
return pending;
|
|
7563
|
+
}
|
|
7564
|
+
const promise = this.fetchProductContext(productId).then((response) => {
|
|
7565
|
+
if (response.success && response.data) {
|
|
7566
|
+
this.setProductContextCache(productId, response.data);
|
|
7567
|
+
}
|
|
7568
|
+
return response;
|
|
7569
|
+
});
|
|
7570
|
+
this.pendingContextRequests.set(cacheKey, promise);
|
|
7571
|
+
promise.then(() => this.pendingContextRequests.delete(cacheKey), () => this.pendingContextRequests.delete(cacheKey));
|
|
7572
|
+
return promise;
|
|
7573
|
+
}
|
|
7574
|
+
async fetchProductContext(productId) {
|
|
7575
|
+
var _a;
|
|
7576
|
+
const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_CONTEXT, { productId });
|
|
7577
|
+
this.logger.info(`Fetching product context for product: ${productId}`);
|
|
7578
|
+
const response = await this.get(endpoint);
|
|
7579
|
+
if (response.success && ((_a = response.data) === null || _a === void 0 ? void 0 : _a.data)) {
|
|
7580
|
+
return {
|
|
7581
|
+
success: true,
|
|
7582
|
+
data: response.data.data,
|
|
7583
|
+
};
|
|
7584
|
+
}
|
|
7585
|
+
return {
|
|
7586
|
+
success: false,
|
|
7587
|
+
error: response.error || {
|
|
7588
|
+
message: "Failed to fetch product context data",
|
|
7589
|
+
code: "PRODUCT_CONTEXT_FETCH_ERROR",
|
|
7590
|
+
},
|
|
7591
|
+
};
|
|
7592
|
+
}
|
|
6441
7593
|
/**
|
|
6442
7594
|
* Get product reward information
|
|
6443
7595
|
*
|
|
@@ -6482,13 +7634,31 @@ var CoBuySDK = (function (exports) {
|
|
|
6482
7634
|
data: cached.data,
|
|
6483
7635
|
};
|
|
6484
7636
|
}
|
|
7637
|
+
const cachedContext = this.getCachedProductContext(productId);
|
|
7638
|
+
if (cachedContext) {
|
|
7639
|
+
this.logger.info(`Using cached product context reward for product: ${productId}`);
|
|
7640
|
+
return {
|
|
7641
|
+
success: true,
|
|
7642
|
+
data: this.buildRewardDataFromContext(productId, cachedContext),
|
|
7643
|
+
};
|
|
7644
|
+
}
|
|
6485
7645
|
const cacheKey = `reward:${productId}`;
|
|
6486
|
-
const pending = this.
|
|
7646
|
+
const pending = this.pendingRewardRequests.get(cacheKey);
|
|
6487
7647
|
if (pending) {
|
|
6488
7648
|
this.logger.info(`Deduplicating request for product: ${productId}`);
|
|
6489
7649
|
return pending;
|
|
6490
7650
|
}
|
|
6491
|
-
const promise =
|
|
7651
|
+
const promise = (async () => {
|
|
7652
|
+
const contextResponse = await this.getProductContext(productId);
|
|
7653
|
+
if (contextResponse.success && contextResponse.data) {
|
|
7654
|
+
return {
|
|
7655
|
+
success: true,
|
|
7656
|
+
data: this.buildRewardDataFromContext(productId, contextResponse.data),
|
|
7657
|
+
};
|
|
7658
|
+
}
|
|
7659
|
+
this.logger.info(`Falling back to legacy reward endpoint for product: ${productId}`);
|
|
7660
|
+
return this.fetchProductReward(productId);
|
|
7661
|
+
})().then((response) => {
|
|
6492
7662
|
if (response.success && response.data) {
|
|
6493
7663
|
this.rewardCache.set(productId, {
|
|
6494
7664
|
data: response.data,
|
|
@@ -6497,8 +7667,8 @@ var CoBuySDK = (function (exports) {
|
|
|
6497
7667
|
}
|
|
6498
7668
|
return response;
|
|
6499
7669
|
});
|
|
6500
|
-
this.
|
|
6501
|
-
promise.then(() => this.
|
|
7670
|
+
this.pendingRewardRequests.set(cacheKey, promise);
|
|
7671
|
+
promise.then(() => this.pendingRewardRequests.delete(cacheKey), () => this.pendingRewardRequests.delete(cacheKey));
|
|
6502
7672
|
return promise;
|
|
6503
7673
|
}
|
|
6504
7674
|
/**
|
|
@@ -6514,6 +7684,7 @@ var CoBuySDK = (function (exports) {
|
|
|
6514
7684
|
*/
|
|
6515
7685
|
clearRewardCache() {
|
|
6516
7686
|
this.rewardCache.clear();
|
|
7687
|
+
this.productContextCache.clear();
|
|
6517
7688
|
}
|
|
6518
7689
|
async fetchProductReward(productId) {
|
|
6519
7690
|
var _a;
|
|
@@ -6561,7 +7732,7 @@ var CoBuySDK = (function (exports) {
|
|
|
6561
7732
|
* ```
|
|
6562
7733
|
*/
|
|
6563
7734
|
async getProductPrimaryGroup(productId) {
|
|
6564
|
-
var _a;
|
|
7735
|
+
var _a, _b;
|
|
6565
7736
|
if (!validateProductId(productId)) {
|
|
6566
7737
|
return {
|
|
6567
7738
|
success: false,
|
|
@@ -6571,15 +7742,38 @@ var CoBuySDK = (function (exports) {
|
|
|
6571
7742
|
},
|
|
6572
7743
|
};
|
|
6573
7744
|
}
|
|
6574
|
-
const
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
7745
|
+
// const cachedContext = this.getCachedProductContext(productId);
|
|
7746
|
+
// const cachedGroup = this.normalizePrimaryGroup(cachedContext?.primary_group);
|
|
7747
|
+
// if (cachedGroup) {
|
|
7748
|
+
// this.logger.info(`Using cached primary group from product context for product: ${productId}`);
|
|
7749
|
+
// return {
|
|
7750
|
+
// success: true,
|
|
7751
|
+
// data: {
|
|
7752
|
+
// ...cachedGroup,
|
|
7753
|
+
// group: cachedGroup,
|
|
7754
|
+
// },
|
|
7755
|
+
// };
|
|
7756
|
+
// }
|
|
7757
|
+
const contextResponse = await this.getProductContext(productId);
|
|
7758
|
+
const contextGroup = this.normalizePrimaryGroup((_a = contextResponse.data) === null || _a === void 0 ? void 0 : _a.primary_group);
|
|
7759
|
+
if (contextResponse.success && contextGroup) {
|
|
6578
7760
|
return {
|
|
6579
7761
|
success: true,
|
|
6580
|
-
data:
|
|
7762
|
+
data: Object.assign(Object.assign({}, contextGroup), { group: contextGroup }),
|
|
6581
7763
|
};
|
|
6582
7764
|
}
|
|
7765
|
+
const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_PRIMARY_GROUP, { productId }, { allowAutoCreate: true });
|
|
7766
|
+
this.logger.info(`Fetching primary group for product: ${productId}`);
|
|
7767
|
+
const response = await this.get(endpoint);
|
|
7768
|
+
if (response.success && ((_b = response.data) === null || _b === void 0 ? void 0 : _b.data)) {
|
|
7769
|
+
const normalizedGroup = this.normalizePrimaryGroup(response.data.data);
|
|
7770
|
+
if (normalizedGroup) {
|
|
7771
|
+
return {
|
|
7772
|
+
success: true,
|
|
7773
|
+
data: Object.assign(Object.assign({}, normalizedGroup), { group: normalizedGroup }),
|
|
7774
|
+
};
|
|
7775
|
+
}
|
|
7776
|
+
}
|
|
6583
7777
|
return {
|
|
6584
7778
|
success: false,
|
|
6585
7779
|
error: response.error || {
|
|
@@ -6627,6 +7821,25 @@ var CoBuySDK = (function (exports) {
|
|
|
6627
7821
|
},
|
|
6628
7822
|
};
|
|
6629
7823
|
}
|
|
7824
|
+
const cachedContext = this.getCachedProductContext(productId);
|
|
7825
|
+
if (cachedContext) {
|
|
7826
|
+
this.logger.info(`Using cached active groups from product context for product: ${productId}`);
|
|
7827
|
+
return {
|
|
7828
|
+
success: true,
|
|
7829
|
+
data: {
|
|
7830
|
+
groups: (cachedContext.active_groups || []),
|
|
7831
|
+
},
|
|
7832
|
+
};
|
|
7833
|
+
}
|
|
7834
|
+
const contextResponse = await this.getProductContext(productId);
|
|
7835
|
+
if (contextResponse.success && contextResponse.data) {
|
|
7836
|
+
return {
|
|
7837
|
+
success: true,
|
|
7838
|
+
data: {
|
|
7839
|
+
groups: (contextResponse.data.active_groups || []),
|
|
7840
|
+
},
|
|
7841
|
+
};
|
|
7842
|
+
}
|
|
6630
7843
|
const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_ACTIVE_GROUPS, { productId });
|
|
6631
7844
|
this.logger.info(`Fetching active groups for product: ${productId}`);
|
|
6632
7845
|
const response = await this.get(endpoint);
|
|
@@ -6921,6 +8134,75 @@ var CoBuySDK = (function (exports) {
|
|
|
6921
8134
|
},
|
|
6922
8135
|
};
|
|
6923
8136
|
}
|
|
8137
|
+
/**
|
|
8138
|
+
* Leave an active group for the current SDK session.
|
|
8139
|
+
*/
|
|
8140
|
+
async leaveGroup(groupId, params) {
|
|
8141
|
+
const endpoint = buildApiUrl("", API_ENDPOINTS.GROUP_LEAVE, { groupId });
|
|
8142
|
+
this.logger.info(`Leaving group: ${groupId}`);
|
|
8143
|
+
const response = await this.post(endpoint, {
|
|
8144
|
+
leave_source: (params === null || params === void 0 ? void 0 : params.leave_source) || "unknown",
|
|
8145
|
+
leave_reason: (params === null || params === void 0 ? void 0 : params.leave_reason) || "unknown",
|
|
8146
|
+
});
|
|
8147
|
+
if (response.success) {
|
|
8148
|
+
const payload = response.data;
|
|
8149
|
+
return {
|
|
8150
|
+
success: true,
|
|
8151
|
+
data: ((payload === null || payload === void 0 ? void 0 : payload.data) || response.data),
|
|
8152
|
+
};
|
|
8153
|
+
}
|
|
8154
|
+
return {
|
|
8155
|
+
success: false,
|
|
8156
|
+
error: response.error || {
|
|
8157
|
+
message: "Failed to leave group",
|
|
8158
|
+
code: "LEAVE_GROUP_ERROR",
|
|
8159
|
+
},
|
|
8160
|
+
};
|
|
8161
|
+
}
|
|
8162
|
+
async recoverOrJoinGroup(productId, contact) {
|
|
8163
|
+
var _a;
|
|
8164
|
+
const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_RECOVER_OR_JOIN_GROUP, {
|
|
8165
|
+
productId,
|
|
8166
|
+
});
|
|
8167
|
+
this.logger.info(`Recovering or joining group for product: ${productId}`);
|
|
8168
|
+
const response = await this.post(endpoint, contact ? { contact } : {});
|
|
8169
|
+
if (response.success && ((_a = response.data) === null || _a === void 0 ? void 0 : _a.data)) {
|
|
8170
|
+
return {
|
|
8171
|
+
success: true,
|
|
8172
|
+
data: response.data.data,
|
|
8173
|
+
};
|
|
8174
|
+
}
|
|
8175
|
+
return {
|
|
8176
|
+
success: false,
|
|
8177
|
+
error: response.error || {
|
|
8178
|
+
message: "Failed to recover or join group",
|
|
8179
|
+
code: "RECOVER_OR_JOIN_GROUP_ERROR",
|
|
8180
|
+
},
|
|
8181
|
+
};
|
|
8182
|
+
}
|
|
8183
|
+
/**
|
|
8184
|
+
* Best-effort leave signal for unload/pagehide scenarios.
|
|
8185
|
+
* Uses fetch keepalive so it can run while page is closing.
|
|
8186
|
+
*/
|
|
8187
|
+
leaveGroupBestEffort(groupId, params) {
|
|
8188
|
+
if (!groupId)
|
|
8189
|
+
return;
|
|
8190
|
+
const endpoint = buildApiUrl("", API_ENDPOINTS.GROUP_LEAVE, { groupId });
|
|
8191
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
8192
|
+
const payload = JSON.stringify({
|
|
8193
|
+
leave_source: (params === null || params === void 0 ? void 0 : params.leave_source) || "browser_close",
|
|
8194
|
+
leave_reason: (params === null || params === void 0 ? void 0 : params.leave_reason) || "voluntary_no_contact",
|
|
8195
|
+
});
|
|
8196
|
+
const authHeaders = this.authStrategy.getHeaders();
|
|
8197
|
+
void fetch(url, {
|
|
8198
|
+
method: "POST",
|
|
8199
|
+
headers: Object.assign({ "Content-Type": "application/json", "X-CoBuy-SDK-Version": "1.0.0", "X-CoBuy-Session": this.sessionId }, authHeaders),
|
|
8200
|
+
body: payload,
|
|
8201
|
+
keepalive: true,
|
|
8202
|
+
}).catch((error) => {
|
|
8203
|
+
this.logger.debug("Best-effort leave request failed", error);
|
|
8204
|
+
});
|
|
8205
|
+
}
|
|
6924
8206
|
/**
|
|
6925
8207
|
* Prepare checkout for a group
|
|
6926
8208
|
*
|
|
@@ -7131,6 +8413,7 @@ var CoBuySDK = (function (exports) {
|
|
|
7131
8413
|
const event = {
|
|
7132
8414
|
event: "CTA_CLICKED",
|
|
7133
8415
|
productId,
|
|
8416
|
+
sessionId: this.sessionId,
|
|
7134
8417
|
timestamp: new Date().toISOString(),
|
|
7135
8418
|
context: {
|
|
7136
8419
|
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
@@ -7183,12 +8466,32 @@ var CoBuySDK = (function (exports) {
|
|
|
7183
8466
|
throw new CoBuyApiError(error instanceof Error ? error.message : "Unknown analytics error", "ANALYTICS_ERROR", { originalError: error });
|
|
7184
8467
|
}
|
|
7185
8468
|
}
|
|
8469
|
+
/**
|
|
8470
|
+
* Track session init event fired when the SDK initializes
|
|
8471
|
+
*/
|
|
8472
|
+
async trackSessionInit(geo, device) {
|
|
8473
|
+
const event = {
|
|
8474
|
+
event: "SESSION_INIT",
|
|
8475
|
+
sessionId: this.sessionId,
|
|
8476
|
+
timestamp: new Date().toISOString(),
|
|
8477
|
+
context: Object.assign(Object.assign({ pageUrl: typeof window !== "undefined" ? window.location.href : undefined, userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined, sdkVersion: this.sdkVersion }, (geo ? { geo } : {})), (device ? { device } : {})),
|
|
8478
|
+
};
|
|
8479
|
+
try {
|
|
8480
|
+
await this.sendEvent(event);
|
|
8481
|
+
this.logger.info("[Analytics] Session init tracked");
|
|
8482
|
+
}
|
|
8483
|
+
catch (error) {
|
|
8484
|
+
// Non-blocking
|
|
8485
|
+
this.logger.error("[Analytics] Failed to track session init", error);
|
|
8486
|
+
}
|
|
8487
|
+
}
|
|
7186
8488
|
/**
|
|
7187
8489
|
* Track page view event
|
|
7188
8490
|
*/
|
|
7189
8491
|
async trackPageView() {
|
|
7190
8492
|
const event = {
|
|
7191
8493
|
event: "PAGE_VIEW",
|
|
8494
|
+
sessionId: this.sessionId,
|
|
7192
8495
|
timestamp: new Date().toISOString(),
|
|
7193
8496
|
context: {
|
|
7194
8497
|
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
@@ -7205,6 +8508,273 @@ var CoBuySDK = (function (exports) {
|
|
|
7205
8508
|
this.logger.error("[Analytics] Failed to track page view", error);
|
|
7206
8509
|
}
|
|
7207
8510
|
}
|
|
8511
|
+
/**
|
|
8512
|
+
* Track creative view event (widget rendered and visible)
|
|
8513
|
+
*/
|
|
8514
|
+
async trackCreativeView(productId) {
|
|
8515
|
+
const event = {
|
|
8516
|
+
event: "CREATIVE_VIEW",
|
|
8517
|
+
productId,
|
|
8518
|
+
sessionId: this.sessionId,
|
|
8519
|
+
timestamp: new Date().toISOString(),
|
|
8520
|
+
context: {
|
|
8521
|
+
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
8522
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
|
8523
|
+
sdkVersion: this.sdkVersion,
|
|
8524
|
+
},
|
|
8525
|
+
};
|
|
8526
|
+
try {
|
|
8527
|
+
await this.sendEvent(event);
|
|
8528
|
+
this.logger.info(`[Analytics] Creative view tracked for product: ${productId}`);
|
|
8529
|
+
}
|
|
8530
|
+
catch (error) {
|
|
8531
|
+
this.logger.error("[Analytics] Failed to track creative view", error);
|
|
8532
|
+
}
|
|
8533
|
+
}
|
|
8534
|
+
/**
|
|
8535
|
+
* Track popup/lobby modal open event
|
|
8536
|
+
*/
|
|
8537
|
+
async trackPopupOpen(productId, groupId) {
|
|
8538
|
+
const event = {
|
|
8539
|
+
event: "POPUP_OPEN",
|
|
8540
|
+
productId,
|
|
8541
|
+
sessionId: this.sessionId,
|
|
8542
|
+
timestamp: new Date().toISOString(),
|
|
8543
|
+
context: {
|
|
8544
|
+
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
8545
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
|
8546
|
+
sdkVersion: this.sdkVersion,
|
|
8547
|
+
groupId,
|
|
8548
|
+
},
|
|
8549
|
+
};
|
|
8550
|
+
try {
|
|
8551
|
+
await this.sendEvent(event);
|
|
8552
|
+
this.logger.info(`[Analytics] Popup open tracked for product: ${productId}`);
|
|
8553
|
+
}
|
|
8554
|
+
catch (error) {
|
|
8555
|
+
this.logger.error("[Analytics] Failed to track popup open", error);
|
|
8556
|
+
}
|
|
8557
|
+
}
|
|
8558
|
+
/**
|
|
8559
|
+
* Track join attempt event (before API call)
|
|
8560
|
+
*/
|
|
8561
|
+
async trackJoinAttempt(productId, groupId) {
|
|
8562
|
+
const event = {
|
|
8563
|
+
event: "JOIN_ATTEMPT",
|
|
8564
|
+
productId,
|
|
8565
|
+
sessionId: this.sessionId,
|
|
8566
|
+
timestamp: new Date().toISOString(),
|
|
8567
|
+
context: {
|
|
8568
|
+
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
8569
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
|
8570
|
+
sdkVersion: this.sdkVersion,
|
|
8571
|
+
groupId,
|
|
8572
|
+
},
|
|
8573
|
+
};
|
|
8574
|
+
try {
|
|
8575
|
+
await this.sendEvent(event);
|
|
8576
|
+
this.logger.info(`[Analytics] Join attempt tracked for product: ${productId}`);
|
|
8577
|
+
}
|
|
8578
|
+
catch (error) {
|
|
8579
|
+
this.logger.error("[Analytics] Failed to track join attempt", error);
|
|
8580
|
+
}
|
|
8581
|
+
}
|
|
8582
|
+
/**
|
|
8583
|
+
* Track successful group join event
|
|
8584
|
+
*/
|
|
8585
|
+
async trackJoinSuccess(productId, groupId) {
|
|
8586
|
+
const event = {
|
|
8587
|
+
event: "JOIN_SUCCESS",
|
|
8588
|
+
productId,
|
|
8589
|
+
sessionId: this.sessionId,
|
|
8590
|
+
timestamp: new Date().toISOString(),
|
|
8591
|
+
context: {
|
|
8592
|
+
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
8593
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
|
8594
|
+
sdkVersion: this.sdkVersion,
|
|
8595
|
+
groupId,
|
|
8596
|
+
},
|
|
8597
|
+
};
|
|
8598
|
+
try {
|
|
8599
|
+
await this.sendEvent(event);
|
|
8600
|
+
this.logger.info(`[Analytics] Join success tracked for product: ${productId}`);
|
|
8601
|
+
}
|
|
8602
|
+
catch (error) {
|
|
8603
|
+
this.logger.error("[Analytics] Failed to track join success", error);
|
|
8604
|
+
}
|
|
8605
|
+
}
|
|
8606
|
+
/**
|
|
8607
|
+
* Track join failure event
|
|
8608
|
+
*/
|
|
8609
|
+
async trackJoinFailure(productId, groupId, errorCode, message) {
|
|
8610
|
+
const event = {
|
|
8611
|
+
event: "JOIN_FAILURE",
|
|
8612
|
+
productId,
|
|
8613
|
+
sessionId: this.sessionId,
|
|
8614
|
+
timestamp: new Date().toISOString(),
|
|
8615
|
+
context: {
|
|
8616
|
+
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
8617
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
|
8618
|
+
sdkVersion: this.sdkVersion,
|
|
8619
|
+
groupId,
|
|
8620
|
+
errorCode,
|
|
8621
|
+
errorMessage: message,
|
|
8622
|
+
},
|
|
8623
|
+
};
|
|
8624
|
+
try {
|
|
8625
|
+
await this.sendEvent(event);
|
|
8626
|
+
this.logger.info(`[Analytics] Join failure tracked for product: ${productId}`);
|
|
8627
|
+
}
|
|
8628
|
+
catch (error) {
|
|
8629
|
+
this.logger.error("[Analytics] Failed to track join failure", error);
|
|
8630
|
+
}
|
|
8631
|
+
}
|
|
8632
|
+
/**
|
|
8633
|
+
* Track already joined event (user attempts to join a group they're already in)
|
|
8634
|
+
*/
|
|
8635
|
+
async trackAlreadyJoined(productId, groupId) {
|
|
8636
|
+
const event = {
|
|
8637
|
+
event: "ALREADY_JOINED",
|
|
8638
|
+
productId,
|
|
8639
|
+
sessionId: this.sessionId,
|
|
8640
|
+
timestamp: new Date().toISOString(),
|
|
8641
|
+
context: {
|
|
8642
|
+
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
8643
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
|
8644
|
+
sdkVersion: this.sdkVersion,
|
|
8645
|
+
groupId,
|
|
8646
|
+
},
|
|
8647
|
+
};
|
|
8648
|
+
try {
|
|
8649
|
+
await this.sendEvent(event);
|
|
8650
|
+
this.logger.info(`[Analytics] Already joined tracked for product: ${productId}`);
|
|
8651
|
+
}
|
|
8652
|
+
catch (error) {
|
|
8653
|
+
this.logger.error("[Analytics] Failed to track already joined", error);
|
|
8654
|
+
}
|
|
8655
|
+
}
|
|
8656
|
+
/**
|
|
8657
|
+
* Track group full view event (user sees a fulfilled/full group)
|
|
8658
|
+
*/
|
|
8659
|
+
async trackGroupFullView(productId, groupId, totalMembers) {
|
|
8660
|
+
const event = {
|
|
8661
|
+
event: "GROUP_FULL_VIEW",
|
|
8662
|
+
productId,
|
|
8663
|
+
sessionId: this.sessionId,
|
|
8664
|
+
timestamp: new Date().toISOString(),
|
|
8665
|
+
context: {
|
|
8666
|
+
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
8667
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
|
8668
|
+
sdkVersion: this.sdkVersion,
|
|
8669
|
+
groupId,
|
|
8670
|
+
totalMembers,
|
|
8671
|
+
},
|
|
8672
|
+
};
|
|
8673
|
+
try {
|
|
8674
|
+
await this.sendEvent(event);
|
|
8675
|
+
this.logger.info(`[Analytics] Group full view tracked for product: ${productId}`);
|
|
8676
|
+
}
|
|
8677
|
+
catch (error) {
|
|
8678
|
+
this.logger.error("[Analytics] Failed to track group full view", error);
|
|
8679
|
+
}
|
|
8680
|
+
}
|
|
8681
|
+
/**
|
|
8682
|
+
* Track share click event
|
|
8683
|
+
*/
|
|
8684
|
+
async trackShareClick(productId, groupId, channel) {
|
|
8685
|
+
const event = {
|
|
8686
|
+
event: "SHARE_CLICK",
|
|
8687
|
+
productId,
|
|
8688
|
+
sessionId: this.sessionId,
|
|
8689
|
+
timestamp: new Date().toISOString(),
|
|
8690
|
+
context: {
|
|
8691
|
+
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
8692
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
|
8693
|
+
sdkVersion: this.sdkVersion,
|
|
8694
|
+
groupId,
|
|
8695
|
+
channel,
|
|
8696
|
+
},
|
|
8697
|
+
};
|
|
8698
|
+
try {
|
|
8699
|
+
await this.sendEvent(event);
|
|
8700
|
+
this.logger.info(`[Analytics] Share click tracked for product: ${productId}`);
|
|
8701
|
+
}
|
|
8702
|
+
catch (error) {
|
|
8703
|
+
this.logger.error("[Analytics] Failed to track share click", error);
|
|
8704
|
+
}
|
|
8705
|
+
}
|
|
8706
|
+
/**
|
|
8707
|
+
* Track group creation attempt event
|
|
8708
|
+
*/
|
|
8709
|
+
async trackGroupCreateAttempt(productId) {
|
|
8710
|
+
const event = {
|
|
8711
|
+
event: "GROUP_CREATE_ATTEMPT",
|
|
8712
|
+
productId,
|
|
8713
|
+
sessionId: this.sessionId,
|
|
8714
|
+
timestamp: new Date().toISOString(),
|
|
8715
|
+
context: {
|
|
8716
|
+
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
8717
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
|
8718
|
+
sdkVersion: this.sdkVersion,
|
|
8719
|
+
},
|
|
8720
|
+
};
|
|
8721
|
+
try {
|
|
8722
|
+
await this.sendEvent(event);
|
|
8723
|
+
this.logger.info(`[Analytics] Group create attempt tracked for product: ${productId}`);
|
|
8724
|
+
}
|
|
8725
|
+
catch (error) {
|
|
8726
|
+
this.logger.error("[Analytics] Failed to track group create attempt", error);
|
|
8727
|
+
}
|
|
8728
|
+
}
|
|
8729
|
+
/**
|
|
8730
|
+
* Track successful group creation event
|
|
8731
|
+
*/
|
|
8732
|
+
async trackGroupCreateSuccess(productId, groupId) {
|
|
8733
|
+
const event = {
|
|
8734
|
+
event: "GROUP_CREATE_SUCCESS",
|
|
8735
|
+
productId,
|
|
8736
|
+
sessionId: this.sessionId,
|
|
8737
|
+
timestamp: new Date().toISOString(),
|
|
8738
|
+
context: {
|
|
8739
|
+
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
8740
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
|
8741
|
+
sdkVersion: this.sdkVersion,
|
|
8742
|
+
groupId,
|
|
8743
|
+
},
|
|
8744
|
+
};
|
|
8745
|
+
try {
|
|
8746
|
+
await this.sendEvent(event);
|
|
8747
|
+
this.logger.info(`[Analytics] Group create success tracked for product: ${productId}`);
|
|
8748
|
+
}
|
|
8749
|
+
catch (error) {
|
|
8750
|
+
this.logger.error("[Analytics] Failed to track group create success", error);
|
|
8751
|
+
}
|
|
8752
|
+
}
|
|
8753
|
+
/**
|
|
8754
|
+
* Track group creation failure event
|
|
8755
|
+
*/
|
|
8756
|
+
async trackGroupCreateFailure(productId, errorCode, message) {
|
|
8757
|
+
const event = {
|
|
8758
|
+
event: "GROUP_CREATE_FAILURE",
|
|
8759
|
+
productId,
|
|
8760
|
+
sessionId: this.sessionId,
|
|
8761
|
+
timestamp: new Date().toISOString(),
|
|
8762
|
+
context: {
|
|
8763
|
+
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
8764
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
|
8765
|
+
sdkVersion: this.sdkVersion,
|
|
8766
|
+
errorCode,
|
|
8767
|
+
errorMessage: message,
|
|
8768
|
+
},
|
|
8769
|
+
};
|
|
8770
|
+
try {
|
|
8771
|
+
await this.sendEvent(event);
|
|
8772
|
+
this.logger.info(`[Analytics] Group create failure tracked for product: ${productId}`);
|
|
8773
|
+
}
|
|
8774
|
+
catch (error) {
|
|
8775
|
+
this.logger.error("[Analytics] Failed to track group create failure", error);
|
|
8776
|
+
}
|
|
8777
|
+
}
|
|
7208
8778
|
/**
|
|
7209
8779
|
* Track custom event (extensible for future use)
|
|
7210
8780
|
*/
|
|
@@ -11462,15 +13032,16 @@ var CoBuySDK = (function (exports) {
|
|
|
11462
13032
|
return;
|
|
11463
13033
|
const bind = (eventName, handler) => {
|
|
11464
13034
|
var _a;
|
|
11465
|
-
if (!handler)
|
|
11466
|
-
return;
|
|
11467
13035
|
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.on(eventName, (payload) => {
|
|
11468
13036
|
this.logger.info(`[Socket] ${eventName} payload: ${this.formatPayload(payload)}`);
|
|
11469
13037
|
this.dispatchWindowEvent(eventName, payload);
|
|
11470
|
-
handler
|
|
13038
|
+
if (handler) {
|
|
13039
|
+
handler(payload);
|
|
13040
|
+
}
|
|
11471
13041
|
});
|
|
11472
13042
|
};
|
|
11473
13043
|
bind("group:member:joined", handlers.onGroupMemberJoined);
|
|
13044
|
+
bind("group:member:left", handlers.onGroupMemberLeft);
|
|
11474
13045
|
bind("group:created", handlers.onGroupCreated);
|
|
11475
13046
|
bind("group:fulfilled", handlers.onGroupFulfilled);
|
|
11476
13047
|
}
|
|
@@ -11481,6 +13052,7 @@ var CoBuySDK = (function (exports) {
|
|
|
11481
13052
|
if (!this.socket)
|
|
11482
13053
|
return;
|
|
11483
13054
|
this.socket.off("group:member:joined");
|
|
13055
|
+
this.socket.off("group:member:left");
|
|
11484
13056
|
this.socket.off("group:created");
|
|
11485
13057
|
this.socket.off("group:fulfilled");
|
|
11486
13058
|
}
|
|
@@ -12650,8 +14222,14 @@ var CoBuySDK = (function (exports) {
|
|
|
12650
14222
|
participantsCount !== undefined &&
|
|
12651
14223
|
participantsCount >= maxParticipants);
|
|
12652
14224
|
if (isFulfilled && productId && groupId) {
|
|
12653
|
-
//
|
|
12654
|
-
this.
|
|
14225
|
+
// Only prepare checkout if the current user is actually a member of this group.
|
|
14226
|
+
// this.widgets tracks all WidgetRoot instances; getJoinedGroupId() returns the
|
|
14227
|
+
// joined group ID only when the user has completed a join this session (not for
|
|
14228
|
+
// observers). Calling prepareCheckout for non-members would generate spurious 403s.
|
|
14229
|
+
const userIsInGroup = [...this.widgets].some((w) => w.getJoinedGroupId() === groupId);
|
|
14230
|
+
if (userIsInGroup) {
|
|
14231
|
+
this.prepareCheckoutIfNotDone(productId, groupId);
|
|
14232
|
+
}
|
|
12655
14233
|
}
|
|
12656
14234
|
// Emit user-defined callback if provided
|
|
12657
14235
|
if ((_a = config.events) === null || _a === void 0 ? void 0 : _a.onGroupMemberJoined) {
|
|
@@ -12664,8 +14242,11 @@ var CoBuySDK = (function (exports) {
|
|
|
12664
14242
|
this.logger.warn("[SDK] Failed to initialize sockets", e);
|
|
12665
14243
|
}
|
|
12666
14244
|
}
|
|
12667
|
-
// Track page view
|
|
14245
|
+
// Track session init + page view events after successful initialization
|
|
12668
14246
|
if (this.analyticsClient) {
|
|
14247
|
+
this.analyticsClient.trackSessionInit(config.geo, config.device).catch((error) => {
|
|
14248
|
+
this.logger.warn("[SDK] Failed to track session init", error);
|
|
14249
|
+
});
|
|
12669
14250
|
this.analyticsClient.trackPageView().catch((error) => {
|
|
12670
14251
|
// Non-blocking: Analytics failure should not affect SDK initialization
|
|
12671
14252
|
this.logger.warn("[SDK] Failed to track page view", error);
|
|
@@ -12845,6 +14426,7 @@ var CoBuySDK = (function (exports) {
|
|
|
12845
14426
|
activities: options.activities,
|
|
12846
14427
|
isLocked: options.isLocked,
|
|
12847
14428
|
offlineRedemption: options.offlineRedemption,
|
|
14429
|
+
redemptionMethod: options.redemptionMethod,
|
|
12848
14430
|
};
|
|
12849
14431
|
// Create modal instance
|
|
12850
14432
|
const modal = new LobbyModal(modalData, {
|
|
@@ -12859,7 +14441,7 @@ var CoBuySDK = (function (exports) {
|
|
|
12859
14441
|
},
|
|
12860
14442
|
onCopyLink: options.onCopyLink,
|
|
12861
14443
|
onShare: options.onShare,
|
|
12862
|
-
}, this.apiClient, this.socketManager, config.debug);
|
|
14444
|
+
}, this.apiClient, this.socketManager, this.analyticsClient, config.debug);
|
|
12863
14445
|
// Store in map for persistence
|
|
12864
14446
|
this.modals.set(modalKey, modal);
|
|
12865
14447
|
// Maintain backward compatibility
|