@cshah18/sdk 4.14.0 → 4.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cobuy-sdk.esm.js +1136 -60
- package/dist/cobuy-sdk.esm.js.map +1 -1
- package/dist/cobuy-sdk.umd.js +1136 -60
- package/dist/cobuy-sdk.umd.js.map +1 -1
- package/dist/types/core/api-client.d.ts +23 -1
- 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 +39 -0
- package/dist/types/ui/lobby/lobby-modal.d.ts +54 -2
- package/dist/types/ui/widget/widget-root.d.ts +6 -0
- package/package.json +1 -1
package/dist/cobuy-sdk.esm.js
CHANGED
|
@@ -837,6 +837,7 @@ class GroupListModal {
|
|
|
837
837
|
this.onGroupMemberJoined = (event) => {
|
|
838
838
|
const detail = event.detail || {};
|
|
839
839
|
console.log("eventttttt", detail);
|
|
840
|
+
const eventType = event.type;
|
|
840
841
|
const productId = detail.product_id;
|
|
841
842
|
const groupData = detail.group;
|
|
842
843
|
// Only process if this is for the current product
|
|
@@ -858,6 +859,16 @@ class GroupListModal {
|
|
|
858
859
|
if (typeof participantsCount === "number") {
|
|
859
860
|
this.groups[groupIndex].joined = participantsCount;
|
|
860
861
|
}
|
|
862
|
+
// If this session left the currently joined group, clear stale local membership state.
|
|
863
|
+
const leavingSessionId = detail.session_id;
|
|
864
|
+
if (eventType === "group:member:left" &&
|
|
865
|
+
this.currentSessionId &&
|
|
866
|
+
leavingSessionId === this.currentSessionId &&
|
|
867
|
+
this.currentJoinedGroupId === groupId) {
|
|
868
|
+
this.currentJoinedGroupId = null;
|
|
869
|
+
this.groups[groupIndex].isMember = false;
|
|
870
|
+
this.updateStartButtonState(false, this.isLoading);
|
|
871
|
+
}
|
|
861
872
|
this.logger.info(`[GroupListModal] Updated group ${groupId} - participants: ${participantsCount}`);
|
|
862
873
|
// Re-render the specific group card
|
|
863
874
|
this.updateGroupCard(groupId);
|
|
@@ -899,6 +910,7 @@ class GroupListModal {
|
|
|
899
910
|
return;
|
|
900
911
|
}
|
|
901
912
|
window.addEventListener("group:member:joined", this.handleGroupMemberJoinedEvent);
|
|
913
|
+
window.addEventListener("group:member:left", this.handleGroupMemberJoinedEvent);
|
|
902
914
|
this.socketListenerRegistered = true;
|
|
903
915
|
this.logger.debug("[GroupListModal] Socket event listeners registered");
|
|
904
916
|
}
|
|
@@ -908,6 +920,7 @@ class GroupListModal {
|
|
|
908
920
|
return;
|
|
909
921
|
}
|
|
910
922
|
window.removeEventListener("group:member:joined", this.handleGroupMemberJoinedEvent);
|
|
923
|
+
window.removeEventListener("group:member:left", this.handleGroupMemberJoinedEvent);
|
|
911
924
|
this.socketListenerRegistered = false;
|
|
912
925
|
this.logger.debug("[GroupListModal] Socket event listeners unregistered");
|
|
913
926
|
}
|
|
@@ -1039,13 +1052,11 @@ class GroupListModal {
|
|
|
1039
1052
|
console.log("groupsss", groups);
|
|
1040
1053
|
// If API reports membership, prefer it over cached state
|
|
1041
1054
|
const memberGroup = groups.find((g) => g.isMember);
|
|
1042
|
-
|
|
1043
|
-
this.currentJoinedGroupId = memberGroup.groupId;
|
|
1044
|
-
}
|
|
1055
|
+
this.currentJoinedGroupId = memberGroup ? memberGroup.groupId : null;
|
|
1045
1056
|
this.groups = groups;
|
|
1046
1057
|
this.liveCount = groups.length;
|
|
1047
1058
|
this.logger.info(`Fetched ${groups.length} active groups`);
|
|
1048
|
-
const hasMembership = Boolean(
|
|
1059
|
+
const hasMembership = Boolean(memberGroup);
|
|
1049
1060
|
this.updateStartButtonState(hasMembership, false);
|
|
1050
1061
|
// Update and show live count display
|
|
1051
1062
|
const liveCountText = document.getElementById("cobuy-live-count-text");
|
|
@@ -1403,7 +1414,7 @@ class GroupListModal {
|
|
|
1403
1414
|
createGroupCard(group) {
|
|
1404
1415
|
const card = document.createElement("div");
|
|
1405
1416
|
card.className = "cobuy-group-card";
|
|
1406
|
-
card.dataset.groupId = group.
|
|
1417
|
+
card.dataset.groupId = group.groupId;
|
|
1407
1418
|
const header = document.createElement("div");
|
|
1408
1419
|
header.className = "cobuy-group-card-header";
|
|
1409
1420
|
const groupId = document.createElement("div");
|
|
@@ -2156,15 +2167,24 @@ function styleInject(css, ref) {
|
|
|
2156
2167
|
}
|
|
2157
2168
|
}
|
|
2158
2169
|
|
|
2159
|
-
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}";
|
|
2170
|
+
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}";
|
|
2160
2171
|
styleInject(css_248z);
|
|
2161
2172
|
|
|
2162
2173
|
/// <reference lib="dom" />
|
|
2174
|
+
const CONTACT_PROMPT_TITLE = "Protect your group spot";
|
|
2175
|
+
const CONTACT_PROMPT_SUBTITLE = "Add your email or phone so we can help you recover your group membership if you leave accidentally.";
|
|
2176
|
+
const CONTACT_PROMPT_PRIMARY_CTA = "Protect my spot";
|
|
2177
|
+
const CONTACT_PROMPT_REMIND_CTA = "Remind me later";
|
|
2178
|
+
const CONTACT_PROMPT_INVALID_INPUT = "Please enter a valid email or phone number.";
|
|
2179
|
+
const CONTACT_PROMPT_SAVE_ERROR = "Could not save contact right now. Please try again.";
|
|
2180
|
+
const CONTACT_PROTECTED_TOAST = "Spot protected. We can help recover your group membership.";
|
|
2181
|
+
const LEAVE_WARNING_TITLE = "Before you leave";
|
|
2182
|
+
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.";
|
|
2163
2183
|
/**
|
|
2164
2184
|
* LobbyModal - Renders and manages the group buying lobby modal
|
|
2165
2185
|
*/
|
|
2166
2186
|
class LobbyModal {
|
|
2167
|
-
constructor(data, callbacks, apiClient, socketManager = null, debug = false) {
|
|
2187
|
+
constructor(data, callbacks, apiClient, socketManager = null, analyticsClient = null, debug = false) {
|
|
2168
2188
|
this.modalElement = null;
|
|
2169
2189
|
this.timerInterval = null;
|
|
2170
2190
|
this.activityInterval = null;
|
|
@@ -2173,6 +2193,17 @@ class LobbyModal {
|
|
|
2173
2193
|
this.currentGroupId = null;
|
|
2174
2194
|
this.shareOverlay = null;
|
|
2175
2195
|
this.keyboardHandler = null;
|
|
2196
|
+
this.leaveFlowInProgress = false;
|
|
2197
|
+
this.leaveSignalSent = false;
|
|
2198
|
+
this.hasContactProtection = false;
|
|
2199
|
+
this.initialContactPromptTimer = null;
|
|
2200
|
+
this.contactPromptOverlay = null;
|
|
2201
|
+
this.leaveLoaderOverlay = null;
|
|
2202
|
+
this.beforeUnloadHandler = null;
|
|
2203
|
+
this.pageHideHandler = null;
|
|
2204
|
+
this.visibilityChangeHandler = null;
|
|
2205
|
+
this.CONTACT_PROTECTION_PREFIX = "cobuy_contact_protection";
|
|
2206
|
+
this.LAST_LEFT_GROUP_PREFIX = "cobuy_last_left_group";
|
|
2176
2207
|
/**
|
|
2177
2208
|
* Handle socket group update events
|
|
2178
2209
|
*/
|
|
@@ -2260,10 +2291,16 @@ class LobbyModal {
|
|
|
2260
2291
|
this.updateData(updatePayload);
|
|
2261
2292
|
// Also update team card to reflect new member count
|
|
2262
2293
|
this.updateTeamCard(participants, max);
|
|
2294
|
+
// If group just became complete and we need offline codes for THIS user, fetch them
|
|
2295
|
+
if (isComplete && !this.data.offlineRedemption) {
|
|
2296
|
+
this.logger.info("[Socket] Group fulfilled - fetching current user's offline codes");
|
|
2297
|
+
this.fetchCurrentUserOfflineCodesIfNeeded();
|
|
2298
|
+
}
|
|
2263
2299
|
};
|
|
2264
2300
|
this.logger = new Logger(debug);
|
|
2265
2301
|
this.apiClient = apiClient;
|
|
2266
2302
|
this.socketManager = socketManager;
|
|
2303
|
+
this.analyticsClient = analyticsClient;
|
|
2267
2304
|
// Log the group data being passed into the modal
|
|
2268
2305
|
this.logger.info("LobbyModal initialized with group data", {
|
|
2269
2306
|
groupId: data.groupId,
|
|
@@ -2275,14 +2312,385 @@ class LobbyModal {
|
|
|
2275
2312
|
timeLeft: data.timeLeft,
|
|
2276
2313
|
offlineRedemption: data.offlineRedemption,
|
|
2277
2314
|
});
|
|
2278
|
-
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);
|
|
2315
|
+
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);
|
|
2279
2316
|
// Normalize optional flags so undefined values don't override defaults
|
|
2280
2317
|
this.data.isLocked = this.computeIsLocked(this.data);
|
|
2281
2318
|
if (!this.data.activities || !Array.isArray(this.data.activities)) {
|
|
2282
2319
|
this.data.activities = this.getDefaultActivities();
|
|
2283
2320
|
}
|
|
2321
|
+
this.hasContactProtection = this.readContactProtectionState();
|
|
2284
2322
|
this.callbacks = callbacks;
|
|
2285
2323
|
}
|
|
2324
|
+
getSessionId() {
|
|
2325
|
+
var _a;
|
|
2326
|
+
return ((_a = this.apiClient) === null || _a === void 0 ? void 0 : _a.getSessionId()) || "anonymous";
|
|
2327
|
+
}
|
|
2328
|
+
getContactProtectionStorageKey() {
|
|
2329
|
+
return `${this.CONTACT_PROTECTION_PREFIX}:${this.getSessionId()}:${this.data.productId}`;
|
|
2330
|
+
}
|
|
2331
|
+
getLastLeftGroupStorageKey() {
|
|
2332
|
+
return `${this.LAST_LEFT_GROUP_PREFIX}:${this.getSessionId()}:${this.data.productId}`;
|
|
2333
|
+
}
|
|
2334
|
+
readContactProtectionState() {
|
|
2335
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
2336
|
+
return false;
|
|
2337
|
+
}
|
|
2338
|
+
try {
|
|
2339
|
+
return window.localStorage.getItem(this.getContactProtectionStorageKey()) === "1";
|
|
2340
|
+
}
|
|
2341
|
+
catch (_a) {
|
|
2342
|
+
return false;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
persistContactProtectionState(value) {
|
|
2346
|
+
this.hasContactProtection = value;
|
|
2347
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
try {
|
|
2351
|
+
const key = this.getContactProtectionStorageKey();
|
|
2352
|
+
if (value) {
|
|
2353
|
+
window.localStorage.setItem(key, "1");
|
|
2354
|
+
}
|
|
2355
|
+
else {
|
|
2356
|
+
window.localStorage.removeItem(key);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
catch (_a) {
|
|
2360
|
+
// Ignore storage errors (private mode, quota exceeded, etc.)
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
persistLastLeftGroup(groupId) {
|
|
2364
|
+
if (typeof window === "undefined" || !window.localStorage || !groupId) {
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
try {
|
|
2368
|
+
window.localStorage.setItem(this.getLastLeftGroupStorageKey(), JSON.stringify({
|
|
2369
|
+
groupId,
|
|
2370
|
+
productId: this.data.productId,
|
|
2371
|
+
leftAt: Date.now(),
|
|
2372
|
+
}));
|
|
2373
|
+
}
|
|
2374
|
+
catch (_a) {
|
|
2375
|
+
// Ignore storage errors
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
clearInitialContactPromptTimer() {
|
|
2379
|
+
if (this.initialContactPromptTimer !== null) {
|
|
2380
|
+
window.clearTimeout(this.initialContactPromptTimer);
|
|
2381
|
+
this.initialContactPromptTimer = null;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
clearContactPromptOverlay() {
|
|
2385
|
+
if (this.contactPromptOverlay) {
|
|
2386
|
+
this.contactPromptOverlay.remove();
|
|
2387
|
+
this.contactPromptOverlay = null;
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
showBlockingLoader(message) {
|
|
2391
|
+
if (!this.modalElement) {
|
|
2392
|
+
return;
|
|
2393
|
+
}
|
|
2394
|
+
this.hideBlockingLoader();
|
|
2395
|
+
const overlay = document.createElement("div");
|
|
2396
|
+
overlay.className = "lobby-blocking-loader-overlay";
|
|
2397
|
+
const card = document.createElement("div");
|
|
2398
|
+
card.className = "lobby-blocking-loader-card";
|
|
2399
|
+
const spinner = document.createElement("div");
|
|
2400
|
+
spinner.className = "lobby-blocking-loader-spinner";
|
|
2401
|
+
const text = document.createElement("p");
|
|
2402
|
+
text.className = "lobby-blocking-loader-text";
|
|
2403
|
+
text.textContent = message;
|
|
2404
|
+
card.appendChild(spinner);
|
|
2405
|
+
card.appendChild(text);
|
|
2406
|
+
overlay.appendChild(card);
|
|
2407
|
+
this.modalElement.appendChild(overlay);
|
|
2408
|
+
this.leaveLoaderOverlay = overlay;
|
|
2409
|
+
}
|
|
2410
|
+
hideBlockingLoader() {
|
|
2411
|
+
if (this.leaveLoaderOverlay) {
|
|
2412
|
+
this.leaveLoaderOverlay.remove();
|
|
2413
|
+
this.leaveLoaderOverlay = null;
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
updateContactProtectionUI() {
|
|
2417
|
+
const banner = document.getElementById("lobbyContactProtectionBanner");
|
|
2418
|
+
const explicitLeaveContainer = document.getElementById("lobbyProtectedLeaveContainer");
|
|
2419
|
+
if (!banner)
|
|
2420
|
+
return;
|
|
2421
|
+
const canShowContactActions = this.data.status !== "complete" && Boolean(this.apiClient);
|
|
2422
|
+
if (!canShowContactActions) {
|
|
2423
|
+
banner.style.display = "none";
|
|
2424
|
+
if (explicitLeaveContainer) {
|
|
2425
|
+
explicitLeaveContainer.style.display = "none";
|
|
2426
|
+
}
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
if (this.hasContactProtection) {
|
|
2430
|
+
banner.style.display = "none";
|
|
2431
|
+
if (explicitLeaveContainer) {
|
|
2432
|
+
explicitLeaveContainer.style.display = "flex";
|
|
2433
|
+
}
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
banner.style.display = "flex";
|
|
2437
|
+
if (explicitLeaveContainer) {
|
|
2438
|
+
explicitLeaveContainer.style.display = "none";
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
showToastMessage(message) {
|
|
2442
|
+
if (!this.modalElement) {
|
|
2443
|
+
return;
|
|
2444
|
+
}
|
|
2445
|
+
const existing = this.modalElement.querySelector(".lobby-contact-toast");
|
|
2446
|
+
if (existing) {
|
|
2447
|
+
existing.remove();
|
|
2448
|
+
}
|
|
2449
|
+
const toast = document.createElement("div");
|
|
2450
|
+
toast.className = "lobby-contact-toast";
|
|
2451
|
+
toast.textContent = message;
|
|
2452
|
+
this.modalElement.appendChild(toast);
|
|
2453
|
+
window.setTimeout(() => {
|
|
2454
|
+
toast.remove();
|
|
2455
|
+
}, 2400);
|
|
2456
|
+
}
|
|
2457
|
+
trackAnalyticsEvent(eventName, metadata) {
|
|
2458
|
+
if (!this.analyticsClient) {
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
2461
|
+
this.analyticsClient
|
|
2462
|
+
.trackEvent(eventName, this.data.productId, Object.assign({ groupId: this.currentGroupId || this.data.groupId }, metadata))
|
|
2463
|
+
.catch((error) => {
|
|
2464
|
+
this.logger.warn(`[Analytics] Failed to track ${eventName}`, error);
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2467
|
+
async saveContactValue(contactValue, promptSource) {
|
|
2468
|
+
const trimmed = contactValue.trim();
|
|
2469
|
+
if (!trimmed || !this.apiClient) {
|
|
2470
|
+
return false;
|
|
2471
|
+
}
|
|
2472
|
+
const contactType = this.inferContactType(trimmed);
|
|
2473
|
+
if (!contactType) {
|
|
2474
|
+
window.alert(CONTACT_PROMPT_INVALID_INPUT);
|
|
2475
|
+
return false;
|
|
2476
|
+
}
|
|
2477
|
+
const payload = contactType === "phone"
|
|
2478
|
+
? { type: "phone", value: trimmed.replace(/\D/g, "") }
|
|
2479
|
+
: { type: "email", value: trimmed };
|
|
2480
|
+
const result = await this.apiClient.setContact(payload);
|
|
2481
|
+
if (!result.success) {
|
|
2482
|
+
this.logger.warn("Failed to save lobby contact", result.error);
|
|
2483
|
+
window.alert(CONTACT_PROMPT_SAVE_ERROR);
|
|
2484
|
+
return false;
|
|
2485
|
+
}
|
|
2486
|
+
this.persistContactProtectionState(true);
|
|
2487
|
+
this.trackAnalyticsEvent("CONTACT_INFO_SAVED", {
|
|
2488
|
+
promptSource,
|
|
2489
|
+
contactType,
|
|
2490
|
+
protectionState: "protected",
|
|
2491
|
+
});
|
|
2492
|
+
this.updateContactProtectionUI();
|
|
2493
|
+
this.showToastMessage(CONTACT_PROTECTED_TOAST);
|
|
2494
|
+
return true;
|
|
2495
|
+
}
|
|
2496
|
+
openContactProtectionPrompt(source) {
|
|
2497
|
+
if (typeof document === "undefined" || !this.modalElement) {
|
|
2498
|
+
return Promise.resolve("cancel");
|
|
2499
|
+
}
|
|
2500
|
+
this.clearContactPromptOverlay();
|
|
2501
|
+
this.trackAnalyticsEvent("CONTACT_PROMPT_SHOWN", {
|
|
2502
|
+
promptSource: source,
|
|
2503
|
+
protectionState: "unprotected",
|
|
2504
|
+
});
|
|
2505
|
+
return new Promise((resolve) => {
|
|
2506
|
+
var _a;
|
|
2507
|
+
const overlay = document.createElement("div");
|
|
2508
|
+
overlay.className = "lobby-contact-overlay";
|
|
2509
|
+
const card = document.createElement("div");
|
|
2510
|
+
card.className = "lobby-contact-card";
|
|
2511
|
+
const title = document.createElement("h3");
|
|
2512
|
+
title.className = "lobby-contact-title";
|
|
2513
|
+
title.textContent = CONTACT_PROMPT_TITLE;
|
|
2514
|
+
const subtitle = document.createElement("p");
|
|
2515
|
+
subtitle.className = "lobby-contact-subtitle";
|
|
2516
|
+
subtitle.textContent = CONTACT_PROMPT_SUBTITLE;
|
|
2517
|
+
const input = document.createElement("input");
|
|
2518
|
+
input.type = "text";
|
|
2519
|
+
input.className = "lobby-contact-input";
|
|
2520
|
+
input.placeholder = "Email or phone";
|
|
2521
|
+
input.autocomplete = "email";
|
|
2522
|
+
const actions = document.createElement("div");
|
|
2523
|
+
actions.className = "lobby-contact-actions";
|
|
2524
|
+
const primary = document.createElement("button");
|
|
2525
|
+
primary.type = "button";
|
|
2526
|
+
primary.className = "lobby-contact-primary";
|
|
2527
|
+
primary.textContent = CONTACT_PROMPT_PRIMARY_CTA;
|
|
2528
|
+
const secondary = document.createElement("button");
|
|
2529
|
+
secondary.type = "button";
|
|
2530
|
+
secondary.className = "lobby-contact-secondary";
|
|
2531
|
+
secondary.textContent = source === "leave-warning" ? "Cancel" : CONTACT_PROMPT_REMIND_CTA;
|
|
2532
|
+
const cleanupAndResolve = (result) => {
|
|
2533
|
+
this.clearContactPromptOverlay();
|
|
2534
|
+
resolve(result);
|
|
2535
|
+
};
|
|
2536
|
+
primary.addEventListener("click", async () => {
|
|
2537
|
+
const originalLabel = primary.textContent;
|
|
2538
|
+
primary.disabled = true;
|
|
2539
|
+
secondary.disabled = true;
|
|
2540
|
+
input.disabled = true;
|
|
2541
|
+
primary.classList.add("is-loading");
|
|
2542
|
+
primary.textContent = "Saving...";
|
|
2543
|
+
try {
|
|
2544
|
+
const saved = await this.saveContactValue(input.value, source);
|
|
2545
|
+
if (saved) {
|
|
2546
|
+
cleanupAndResolve("saved");
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
finally {
|
|
2550
|
+
if (this.contactPromptOverlay) {
|
|
2551
|
+
primary.disabled = false;
|
|
2552
|
+
secondary.disabled = false;
|
|
2553
|
+
input.disabled = false;
|
|
2554
|
+
primary.classList.remove("is-loading");
|
|
2555
|
+
primary.textContent = originalLabel || CONTACT_PROMPT_PRIMARY_CTA;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
});
|
|
2559
|
+
secondary.addEventListener("click", () => {
|
|
2560
|
+
if (source !== "leave-warning") {
|
|
2561
|
+
this.trackAnalyticsEvent("CONTACT_PROMPT_DEFERRED", {
|
|
2562
|
+
promptSource: source,
|
|
2563
|
+
protectionState: "unprotected",
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2566
|
+
cleanupAndResolve(source === "leave-warning" ? "cancel" : "later");
|
|
2567
|
+
});
|
|
2568
|
+
overlay.addEventListener("click", (event) => {
|
|
2569
|
+
if (event.target === overlay) {
|
|
2570
|
+
if (source !== "leave-warning") {
|
|
2571
|
+
this.trackAnalyticsEvent("CONTACT_PROMPT_DEFERRED", {
|
|
2572
|
+
promptSource: source,
|
|
2573
|
+
protectionState: "unprotected",
|
|
2574
|
+
});
|
|
2575
|
+
}
|
|
2576
|
+
cleanupAndResolve(source === "leave-warning" ? "cancel" : "later");
|
|
2577
|
+
}
|
|
2578
|
+
});
|
|
2579
|
+
actions.appendChild(primary);
|
|
2580
|
+
actions.appendChild(secondary);
|
|
2581
|
+
card.appendChild(title);
|
|
2582
|
+
card.appendChild(subtitle);
|
|
2583
|
+
card.appendChild(input);
|
|
2584
|
+
card.appendChild(actions);
|
|
2585
|
+
overlay.appendChild(card);
|
|
2586
|
+
(_a = this.modalElement) === null || _a === void 0 ? void 0 : _a.appendChild(overlay);
|
|
2587
|
+
this.contactPromptOverlay = overlay;
|
|
2588
|
+
window.setTimeout(() => input.focus(), 0);
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
openUnprotectedLeaveWarning() {
|
|
2592
|
+
if (typeof document === "undefined" || !this.modalElement) {
|
|
2593
|
+
return Promise.resolve("cancel");
|
|
2594
|
+
}
|
|
2595
|
+
this.clearContactPromptOverlay();
|
|
2596
|
+
return new Promise((resolve) => {
|
|
2597
|
+
var _a;
|
|
2598
|
+
const overlay = document.createElement("div");
|
|
2599
|
+
overlay.className = "lobby-contact-overlay";
|
|
2600
|
+
const card = document.createElement("div");
|
|
2601
|
+
card.className = "lobby-contact-card";
|
|
2602
|
+
const title = document.createElement("h3");
|
|
2603
|
+
title.className = "lobby-contact-title";
|
|
2604
|
+
title.textContent = LEAVE_WARNING_TITLE;
|
|
2605
|
+
const subtitle = document.createElement("p");
|
|
2606
|
+
subtitle.className = "lobby-contact-subtitle";
|
|
2607
|
+
subtitle.textContent = LEAVE_WARNING_SUBTITLE;
|
|
2608
|
+
const actions = document.createElement("div");
|
|
2609
|
+
actions.className = "lobby-contact-actions";
|
|
2610
|
+
const addContactBtn = document.createElement("button");
|
|
2611
|
+
addContactBtn.type = "button";
|
|
2612
|
+
addContactBtn.className = "lobby-contact-primary";
|
|
2613
|
+
addContactBtn.textContent = "Add contact first";
|
|
2614
|
+
const leaveBtn = document.createElement("button");
|
|
2615
|
+
leaveBtn.type = "button";
|
|
2616
|
+
leaveBtn.className = "lobby-contact-danger";
|
|
2617
|
+
leaveBtn.textContent = "Leave anyway";
|
|
2618
|
+
const cancelBtn = document.createElement("button");
|
|
2619
|
+
cancelBtn.type = "button";
|
|
2620
|
+
cancelBtn.className = "lobby-contact-secondary";
|
|
2621
|
+
cancelBtn.textContent = "Cancel";
|
|
2622
|
+
const cleanup = (result) => {
|
|
2623
|
+
this.clearContactPromptOverlay();
|
|
2624
|
+
resolve(result);
|
|
2625
|
+
};
|
|
2626
|
+
addContactBtn.addEventListener("click", () => cleanup("add_contact"));
|
|
2627
|
+
leaveBtn.addEventListener("click", () => cleanup("leave"));
|
|
2628
|
+
cancelBtn.addEventListener("click", () => cleanup("cancel"));
|
|
2629
|
+
overlay.addEventListener("click", (event) => {
|
|
2630
|
+
if (event.target === overlay) {
|
|
2631
|
+
cleanup("cancel");
|
|
2632
|
+
}
|
|
2633
|
+
});
|
|
2634
|
+
actions.appendChild(addContactBtn);
|
|
2635
|
+
actions.appendChild(leaveBtn);
|
|
2636
|
+
actions.appendChild(cancelBtn);
|
|
2637
|
+
card.appendChild(title);
|
|
2638
|
+
card.appendChild(subtitle);
|
|
2639
|
+
card.appendChild(actions);
|
|
2640
|
+
overlay.appendChild(card);
|
|
2641
|
+
(_a = this.modalElement) === null || _a === void 0 ? void 0 : _a.appendChild(overlay);
|
|
2642
|
+
this.contactPromptOverlay = overlay;
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2645
|
+
scheduleInitialContactPrompt() {
|
|
2646
|
+
if (!this.modalElement ||
|
|
2647
|
+
!this.shouldRunLeaveFlow() ||
|
|
2648
|
+
this.hasContactProtection ||
|
|
2649
|
+
!this.apiClient) {
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
this.clearInitialContactPromptTimer();
|
|
2653
|
+
this.initialContactPromptTimer = window.setTimeout(async () => {
|
|
2654
|
+
this.initialContactPromptTimer = null;
|
|
2655
|
+
if (!this.modalElement || this.hasContactProtection) {
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
await this.openContactProtectionPrompt("initial");
|
|
2659
|
+
this.updateContactProtectionUI();
|
|
2660
|
+
}, 2000);
|
|
2661
|
+
}
|
|
2662
|
+
async leaveGroupExplicitly() {
|
|
2663
|
+
var _a, _b;
|
|
2664
|
+
if (!this.apiClient || !this.currentGroupId || this.leaveFlowInProgress) {
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
2667
|
+
this.leaveFlowInProgress = true;
|
|
2668
|
+
try {
|
|
2669
|
+
this.showBlockingLoader("Leaving group...");
|
|
2670
|
+
const leaveResult = await this.apiClient.leaveGroup(this.currentGroupId, {
|
|
2671
|
+
leave_source: "close_icon",
|
|
2672
|
+
leave_reason: "voluntary_with_contact",
|
|
2673
|
+
});
|
|
2674
|
+
if (!leaveResult.success) {
|
|
2675
|
+
this.logger.warn("Explicit leave group failed", leaveResult.error);
|
|
2676
|
+
this.hideBlockingLoader();
|
|
2677
|
+
window.alert("Could not leave the group right now. Please try again.");
|
|
2678
|
+
return;
|
|
2679
|
+
}
|
|
2680
|
+
this.leaveSignalSent = true;
|
|
2681
|
+
this.persistLastLeftGroup(((_b = (_a = leaveResult.data) === null || _a === void 0 ? void 0 : _a.group) === null || _b === void 0 ? void 0 : _b.id) || this.currentGroupId);
|
|
2682
|
+
this.trackAnalyticsEvent("PROTECTED_USER_EXPLICIT_LEAVE", {
|
|
2683
|
+
leaveSource: "close_icon",
|
|
2684
|
+
leaveReason: "voluntary_with_contact",
|
|
2685
|
+
protectionState: "protected",
|
|
2686
|
+
});
|
|
2687
|
+
this.close({ skipLeaveFlow: true });
|
|
2688
|
+
}
|
|
2689
|
+
finally {
|
|
2690
|
+
this.hideBlockingLoader();
|
|
2691
|
+
this.leaveFlowInProgress = false;
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2286
2694
|
/**
|
|
2287
2695
|
* Derive lock state from data so UI reflects completion
|
|
2288
2696
|
*/
|
|
@@ -2599,6 +3007,7 @@ class LobbyModal {
|
|
|
2599
3007
|
* Create connected section (subtitle + link + share)
|
|
2600
3008
|
*/
|
|
2601
3009
|
createConnectedSection() {
|
|
3010
|
+
var _a, _b;
|
|
2602
3011
|
const connectedSection = document.createElement("div");
|
|
2603
3012
|
connectedSection.className = "connected-section";
|
|
2604
3013
|
connectedSection.id = "lobbyConnectedSection";
|
|
@@ -2609,18 +3018,59 @@ class LobbyModal {
|
|
|
2609
3018
|
subtitle.id = "lobbySubtitleText";
|
|
2610
3019
|
subtitle.textContent = titleContent.subtitle;
|
|
2611
3020
|
connectedSection.appendChild(subtitle);
|
|
3021
|
+
const contactBanner = document.createElement("div");
|
|
3022
|
+
contactBanner.className = "lobby-contact-protection-banner";
|
|
3023
|
+
contactBanner.id = "lobbyContactProtectionBanner";
|
|
3024
|
+
const contactCopy = document.createElement("p");
|
|
3025
|
+
contactCopy.className = "lobby-contact-protection-copy";
|
|
3026
|
+
contactCopy.textContent =
|
|
3027
|
+
"Spot not protected. Add contact so we can help recover your group membership.";
|
|
3028
|
+
const contactAction = document.createElement("button");
|
|
3029
|
+
contactAction.type = "button";
|
|
3030
|
+
contactAction.className = "lobby-contact-protection-btn";
|
|
3031
|
+
contactAction.textContent = "Add contact";
|
|
3032
|
+
contactAction.addEventListener("click", async () => {
|
|
3033
|
+
await this.openContactProtectionPrompt("banner");
|
|
3034
|
+
this.updateContactProtectionUI();
|
|
3035
|
+
});
|
|
3036
|
+
contactBanner.appendChild(contactCopy);
|
|
3037
|
+
contactBanner.appendChild(contactAction);
|
|
3038
|
+
connectedSection.appendChild(contactBanner);
|
|
3039
|
+
const protectedLeaveContainer = document.createElement("div");
|
|
3040
|
+
protectedLeaveContainer.className = "lobby-protected-leave-container";
|
|
3041
|
+
protectedLeaveContainer.id = "lobbyProtectedLeaveContainer";
|
|
3042
|
+
const protectedLeaveText = document.createElement("p");
|
|
3043
|
+
protectedLeaveText.className = "lobby-protected-leave-copy";
|
|
3044
|
+
protectedLeaveText.textContent = "You are protected and still part of this group.";
|
|
3045
|
+
const protectedLeaveAction = document.createElement("button");
|
|
3046
|
+
protectedLeaveAction.type = "button";
|
|
3047
|
+
protectedLeaveAction.className = "lobby-protected-leave-btn";
|
|
3048
|
+
protectedLeaveAction.textContent = "Leave group";
|
|
3049
|
+
protectedLeaveAction.addEventListener("click", () => {
|
|
3050
|
+
void this.leaveGroupExplicitly();
|
|
3051
|
+
});
|
|
3052
|
+
protectedLeaveContainer.appendChild(protectedLeaveText);
|
|
3053
|
+
protectedLeaveContainer.appendChild(protectedLeaveAction);
|
|
3054
|
+
connectedSection.appendChild(protectedLeaveContainer);
|
|
2612
3055
|
// Check if group is fulfilled and has offline redemption
|
|
2613
3056
|
const isComplete = !this.computeIsLocked(this.data);
|
|
2614
3057
|
const hasOfflineRedemption = isComplete &&
|
|
2615
3058
|
this.data.offlineRedemption &&
|
|
2616
|
-
isValidOfflineRedemption(this.data.offlineRedemption)
|
|
3059
|
+
isValidOfflineRedemption(this.data.offlineRedemption) &&
|
|
3060
|
+
((_a = this.data.redemptionMethod) !== null && _a !== void 0 ? _a : "online") !== "online";
|
|
2617
3061
|
if (hasOfflineRedemption) {
|
|
2618
|
-
//
|
|
3062
|
+
// Complete + offline/both: show offline redemption section
|
|
2619
3063
|
const offlineSection = this.createOfflineRedemptionSection(this.data.offlineRedemption);
|
|
2620
3064
|
connectedSection.appendChild(offlineSection);
|
|
2621
3065
|
}
|
|
3066
|
+
else if (isComplete && ((_b = this.data.redemptionMethod) !== null && _b !== void 0 ? _b : "online") !== "offline") {
|
|
3067
|
+
// Complete + online/both (no offline redemption data): show only checkout CTA
|
|
3068
|
+
const checkoutBtn = this.createOnlineCheckoutButton();
|
|
3069
|
+
connectedSection.appendChild(checkoutBtn);
|
|
3070
|
+
this.injectOfflineRedemptionStyles();
|
|
3071
|
+
}
|
|
2622
3072
|
else {
|
|
2623
|
-
//
|
|
3073
|
+
// Group not yet complete: show link and share
|
|
2624
3074
|
const linkShareContainer = document.createElement("div");
|
|
2625
3075
|
linkShareContainer.className = "link-share-container";
|
|
2626
3076
|
const linkWp = this.createLinkSection();
|
|
@@ -2664,6 +3114,23 @@ class LobbyModal {
|
|
|
2664
3114
|
linkShareWrapper.appendChild(shareBtnInner);
|
|
2665
3115
|
return linkShareWrapper;
|
|
2666
3116
|
}
|
|
3117
|
+
/**
|
|
3118
|
+
* Create a standalone "Checkout Online" button for the link/share section
|
|
3119
|
+
*/
|
|
3120
|
+
createOnlineCheckoutButton() {
|
|
3121
|
+
const btn = document.createElement("button");
|
|
3122
|
+
btn.className = "offline-online-checkout-btn lobby-online-checkout-btn";
|
|
3123
|
+
btn.textContent = "Checkout Online";
|
|
3124
|
+
btn.style.marginTop = "10px";
|
|
3125
|
+
btn.style.width = "100%";
|
|
3126
|
+
btn.addEventListener("click", () => {
|
|
3127
|
+
this.close();
|
|
3128
|
+
window.setTimeout(() => {
|
|
3129
|
+
this.triggerContinueToCheckout();
|
|
3130
|
+
}, 0);
|
|
3131
|
+
});
|
|
3132
|
+
return btn;
|
|
3133
|
+
}
|
|
2667
3134
|
/**
|
|
2668
3135
|
* Create offline redemption section
|
|
2669
3136
|
*/
|
|
@@ -2728,14 +3195,16 @@ class LobbyModal {
|
|
|
2728
3195
|
onlineCheckoutBtn.className = "offline-online-checkout-btn";
|
|
2729
3196
|
onlineCheckoutBtn.textContent = "Checkout Online";
|
|
2730
3197
|
onlineCheckoutBtn.addEventListener("click", () => {
|
|
2731
|
-
this.close();
|
|
3198
|
+
this.close({ skipLeaveFlow: true });
|
|
2732
3199
|
// Ensure modal closes before triggering checkout intent
|
|
2733
3200
|
window.setTimeout(() => {
|
|
2734
3201
|
this.triggerContinueToCheckout();
|
|
2735
3202
|
}, 0);
|
|
2736
3203
|
});
|
|
2737
3204
|
actionsRow.appendChild(downloadQRBtn);
|
|
2738
|
-
|
|
3205
|
+
if (this.data.redemptionMethod !== "offline") {
|
|
3206
|
+
actionsRow.appendChild(onlineCheckoutBtn);
|
|
3207
|
+
}
|
|
2739
3208
|
section.appendChild(topRow);
|
|
2740
3209
|
section.appendChild(expiryInfo);
|
|
2741
3210
|
section.appendChild(actionsRow);
|
|
@@ -3641,6 +4110,10 @@ class LobbyModal {
|
|
|
3641
4110
|
this.logger.info(`Lobby modal opened for product: ${this.data.productId}`);
|
|
3642
4111
|
// Set up keyboard accessibility (focus trap, ESC to close)
|
|
3643
4112
|
this.setupKeyboardAccessibility();
|
|
4113
|
+
// Register lifecycle-based dropoff handling for browser/tab close.
|
|
4114
|
+
this.registerLifecycleLeaveHandlers();
|
|
4115
|
+
this.updateContactProtectionUI();
|
|
4116
|
+
this.scheduleInitialContactPrompt();
|
|
3644
4117
|
});
|
|
3645
4118
|
}
|
|
3646
4119
|
/**
|
|
@@ -3706,19 +4179,166 @@ class LobbyModal {
|
|
|
3706
4179
|
this.keyboardHandler = null;
|
|
3707
4180
|
}
|
|
3708
4181
|
}
|
|
4182
|
+
registerLifecycleLeaveHandlers() {
|
|
4183
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
4184
|
+
return;
|
|
4185
|
+
}
|
|
4186
|
+
if (!this.shouldRunLeaveFlow()) {
|
|
4187
|
+
return;
|
|
4188
|
+
}
|
|
4189
|
+
if (!this.beforeUnloadHandler) {
|
|
4190
|
+
this.beforeUnloadHandler = () => {
|
|
4191
|
+
this.triggerBestEffortLeave("browser_close");
|
|
4192
|
+
};
|
|
4193
|
+
window.addEventListener("beforeunload", this.beforeUnloadHandler);
|
|
4194
|
+
}
|
|
4195
|
+
if (!this.pageHideHandler) {
|
|
4196
|
+
this.pageHideHandler = () => {
|
|
4197
|
+
this.triggerBestEffortLeave("browser_close");
|
|
4198
|
+
};
|
|
4199
|
+
window.addEventListener("pagehide", this.pageHideHandler);
|
|
4200
|
+
}
|
|
4201
|
+
if (!this.visibilityChangeHandler) {
|
|
4202
|
+
this.visibilityChangeHandler = () => {
|
|
4203
|
+
if (document.visibilityState === "hidden") {
|
|
4204
|
+
this.triggerBestEffortLeave("browser_close");
|
|
4205
|
+
}
|
|
4206
|
+
};
|
|
4207
|
+
document.addEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
unregisterLifecycleLeaveHandlers() {
|
|
4211
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
4212
|
+
return;
|
|
4213
|
+
}
|
|
4214
|
+
if (this.beforeUnloadHandler) {
|
|
4215
|
+
window.removeEventListener("beforeunload", this.beforeUnloadHandler);
|
|
4216
|
+
this.beforeUnloadHandler = null;
|
|
4217
|
+
}
|
|
4218
|
+
if (this.pageHideHandler) {
|
|
4219
|
+
window.removeEventListener("pagehide", this.pageHideHandler);
|
|
4220
|
+
this.pageHideHandler = null;
|
|
4221
|
+
}
|
|
4222
|
+
if (this.visibilityChangeHandler) {
|
|
4223
|
+
document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
4224
|
+
this.visibilityChangeHandler = null;
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
triggerBestEffortLeave(source) {
|
|
4228
|
+
if (this.leaveSignalSent || this.leaveFlowInProgress) {
|
|
4229
|
+
return;
|
|
4230
|
+
}
|
|
4231
|
+
if (this.hasContactProtection) {
|
|
4232
|
+
return;
|
|
4233
|
+
}
|
|
4234
|
+
if (!this.apiClient || !this.currentGroupId || !this.shouldRunLeaveFlow()) {
|
|
4235
|
+
return;
|
|
4236
|
+
}
|
|
4237
|
+
this.leaveSignalSent = true;
|
|
4238
|
+
this.apiClient.leaveGroupBestEffort(this.currentGroupId, {
|
|
4239
|
+
leave_source: source,
|
|
4240
|
+
leave_reason: "voluntary_no_contact",
|
|
4241
|
+
});
|
|
4242
|
+
}
|
|
4243
|
+
shouldRunLeaveFlow() {
|
|
4244
|
+
if (!this.apiClient || !this.currentGroupId) {
|
|
4245
|
+
return false;
|
|
4246
|
+
}
|
|
4247
|
+
// Dropoff handling applies only while group is still active/forming.
|
|
4248
|
+
return this.data.status !== "complete";
|
|
4249
|
+
}
|
|
4250
|
+
inferContactType(value) {
|
|
4251
|
+
const trimmed = value.trim();
|
|
4252
|
+
if (!trimmed)
|
|
4253
|
+
return null;
|
|
4254
|
+
if (trimmed.includes("@")) {
|
|
4255
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
4256
|
+
return emailRegex.test(trimmed) ? "email" : null;
|
|
4257
|
+
}
|
|
4258
|
+
const digitsOnly = trimmed.replace(/\D/g, "");
|
|
4259
|
+
return digitsOnly.length >= 10 && digitsOnly.length <= 15 ? "phone" : null;
|
|
4260
|
+
}
|
|
4261
|
+
async runLeaveFlowAndClose() {
|
|
4262
|
+
var _a, _b;
|
|
4263
|
+
if (this.leaveFlowInProgress)
|
|
4264
|
+
return;
|
|
4265
|
+
this.leaveFlowInProgress = true;
|
|
4266
|
+
try {
|
|
4267
|
+
if (this.hasContactProtection) {
|
|
4268
|
+
this.trackAnalyticsEvent("LOBBY_CLOSED_PROTECTED", {
|
|
4269
|
+
leaveSource: "close_icon",
|
|
4270
|
+
leaveReason: "stay_in_group",
|
|
4271
|
+
protectionState: "protected",
|
|
4272
|
+
});
|
|
4273
|
+
this.close({ skipLeaveFlow: true });
|
|
4274
|
+
return;
|
|
4275
|
+
}
|
|
4276
|
+
let leaveReason = "voluntary_no_contact";
|
|
4277
|
+
const leaveDecision = await this.openUnprotectedLeaveWarning();
|
|
4278
|
+
if (leaveDecision === "cancel") {
|
|
4279
|
+
return;
|
|
4280
|
+
}
|
|
4281
|
+
if (leaveDecision === "add_contact") {
|
|
4282
|
+
const promptResult = await this.openContactProtectionPrompt("leave-warning");
|
|
4283
|
+
if (promptResult === "saved") {
|
|
4284
|
+
this.updateContactProtectionUI();
|
|
4285
|
+
return;
|
|
4286
|
+
}
|
|
4287
|
+
else {
|
|
4288
|
+
return;
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
if (this.apiClient && this.currentGroupId) {
|
|
4292
|
+
this.showBlockingLoader("Leaving group...");
|
|
4293
|
+
const leaveResult = await this.apiClient.leaveGroup(this.currentGroupId, {
|
|
4294
|
+
leave_source: "close_icon",
|
|
4295
|
+
leave_reason: leaveReason,
|
|
4296
|
+
});
|
|
4297
|
+
if (!leaveResult.success) {
|
|
4298
|
+
this.logger.warn("Leave group failed", leaveResult.error);
|
|
4299
|
+
this.hideBlockingLoader();
|
|
4300
|
+
window.alert("Could not leave the group right now. Please try again.");
|
|
4301
|
+
return;
|
|
4302
|
+
}
|
|
4303
|
+
this.leaveSignalSent = true;
|
|
4304
|
+
this.persistLastLeftGroup(((_b = (_a = leaveResult.data) === null || _a === void 0 ? void 0 : _a.group) === null || _b === void 0 ? void 0 : _b.id) || this.currentGroupId);
|
|
4305
|
+
this.trackAnalyticsEvent("LOBBY_LEFT_UNPROTECTED", {
|
|
4306
|
+
leaveSource: "close_icon",
|
|
4307
|
+
leaveReason,
|
|
4308
|
+
protectionState: "unprotected",
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
this.close({ skipLeaveFlow: true });
|
|
4312
|
+
}
|
|
4313
|
+
finally {
|
|
4314
|
+
this.hideBlockingLoader();
|
|
4315
|
+
this.leaveFlowInProgress = false;
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
3709
4318
|
/**
|
|
3710
4319
|
* Close the modal
|
|
3711
4320
|
*/
|
|
3712
|
-
close() {
|
|
4321
|
+
close(options = {}) {
|
|
3713
4322
|
if (!this.modalElement) {
|
|
3714
4323
|
this.logger.warn("Modal not open");
|
|
3715
4324
|
return;
|
|
3716
4325
|
}
|
|
4326
|
+
if (!options.skipLeaveFlow && this.shouldRunLeaveFlow()) {
|
|
4327
|
+
void this.runLeaveFlowAndClose();
|
|
4328
|
+
return;
|
|
4329
|
+
}
|
|
3717
4330
|
// Stop timers and animations
|
|
3718
4331
|
this.stopTimer();
|
|
3719
4332
|
this.stopActivityAnimation();
|
|
3720
4333
|
// Remove keyboard event listeners
|
|
3721
4334
|
this.removeKeyboardAccessibility();
|
|
4335
|
+
// Remove lifecycle handlers for dropoff detection
|
|
4336
|
+
this.unregisterLifecycleLeaveHandlers();
|
|
4337
|
+
this.clearInitialContactPromptTimer();
|
|
4338
|
+
this.clearContactPromptOverlay();
|
|
4339
|
+
this.hideBlockingLoader();
|
|
4340
|
+
// Remove socket listeners to avoid duplicate handlers on reopen
|
|
4341
|
+
this.unsubscribeFromSocketEvents();
|
|
3722
4342
|
// Remove modal from DOM
|
|
3723
4343
|
document.body.removeChild(this.modalElement);
|
|
3724
4344
|
this.modalElement = null;
|
|
@@ -3831,29 +4451,47 @@ class LobbyModal {
|
|
|
3831
4451
|
* Update offline redemption visibility when group is fulfilled
|
|
3832
4452
|
*/
|
|
3833
4453
|
updateOfflineRedemptionVisibility() {
|
|
4454
|
+
var _a, _b;
|
|
3834
4455
|
const connectedSection = document.getElementById("lobbyConnectedSection");
|
|
3835
4456
|
if (!connectedSection)
|
|
3836
4457
|
return;
|
|
3837
4458
|
const isComplete = !this.computeIsLocked(this.data);
|
|
3838
|
-
const hasOfflineRedemption = this.data.offlineRedemption &&
|
|
4459
|
+
const hasOfflineRedemption = this.data.offlineRedemption &&
|
|
4460
|
+
isValidOfflineRedemption(this.data.offlineRedemption) &&
|
|
4461
|
+
((_a = this.data.redemptionMethod) !== null && _a !== void 0 ? _a : "online") !== "online";
|
|
3839
4462
|
// Get existing elements
|
|
3840
4463
|
const existingOffline = connectedSection.querySelector(".offline-redemption-section");
|
|
3841
4464
|
const existingLink = connectedSection.querySelector(".link-share-container");
|
|
4465
|
+
const existingCheckoutBtn = connectedSection.querySelector(".lobby-online-checkout-btn");
|
|
3842
4466
|
if (isComplete && hasOfflineRedemption) {
|
|
3843
|
-
//
|
|
3844
|
-
if (existingLink)
|
|
4467
|
+
// Complete + offline/both: show offline section, remove link/share and standalone checkout btn
|
|
4468
|
+
if (existingLink)
|
|
3845
4469
|
existingLink.remove();
|
|
3846
|
-
|
|
4470
|
+
if (existingCheckoutBtn)
|
|
4471
|
+
existingCheckoutBtn.remove();
|
|
3847
4472
|
if (!existingOffline) {
|
|
3848
4473
|
const offlineSection = this.createOfflineRedemptionSection(this.data.offlineRedemption);
|
|
3849
4474
|
connectedSection.appendChild(offlineSection);
|
|
3850
4475
|
}
|
|
3851
4476
|
}
|
|
3852
|
-
else {
|
|
3853
|
-
//
|
|
3854
|
-
if (
|
|
4477
|
+
else if (isComplete && ((_b = this.data.redemptionMethod) !== null && _b !== void 0 ? _b : "online") !== "offline") {
|
|
4478
|
+
// Complete + online/both (no offline data): show only checkout CTA, remove link/share
|
|
4479
|
+
if (existingLink)
|
|
4480
|
+
existingLink.remove();
|
|
4481
|
+
if (existingOffline)
|
|
3855
4482
|
existingOffline.remove();
|
|
4483
|
+
if (!existingCheckoutBtn) {
|
|
4484
|
+
const checkoutBtn = this.createOnlineCheckoutButton();
|
|
4485
|
+
connectedSection.appendChild(checkoutBtn);
|
|
4486
|
+
this.injectOfflineRedemptionStyles();
|
|
3856
4487
|
}
|
|
4488
|
+
}
|
|
4489
|
+
else {
|
|
4490
|
+
// Group not yet complete: show link/share, remove offline and checkout btn
|
|
4491
|
+
if (existingOffline)
|
|
4492
|
+
existingOffline.remove();
|
|
4493
|
+
if (existingCheckoutBtn)
|
|
4494
|
+
existingCheckoutBtn.remove();
|
|
3857
4495
|
if (!existingLink) {
|
|
3858
4496
|
const linkShareContainer = document.createElement("div");
|
|
3859
4497
|
linkShareContainer.className = "link-share-container";
|
|
@@ -3887,9 +4525,47 @@ class LobbyModal {
|
|
|
3887
4525
|
}
|
|
3888
4526
|
window.addEventListener("group:fulfilled", this.handleSocketGroupUpdate);
|
|
3889
4527
|
window.addEventListener("group:member:joined", this.handleSocketGroupUpdate);
|
|
4528
|
+
window.addEventListener("group:member:left", this.handleSocketGroupUpdate);
|
|
3890
4529
|
window.addEventListener("group:created", this.handleSocketGroupUpdate);
|
|
3891
4530
|
this.socketListenerRegistered = true;
|
|
3892
4531
|
}
|
|
4532
|
+
unsubscribeFromSocketEvents() {
|
|
4533
|
+
if (typeof window === "undefined" || !this.socketListenerRegistered) {
|
|
4534
|
+
return;
|
|
4535
|
+
}
|
|
4536
|
+
window.removeEventListener("group:fulfilled", this.handleSocketGroupUpdate);
|
|
4537
|
+
window.removeEventListener("group:member:joined", this.handleSocketGroupUpdate);
|
|
4538
|
+
window.removeEventListener("group:member:left", this.handleSocketGroupUpdate);
|
|
4539
|
+
window.removeEventListener("group:created", this.handleSocketGroupUpdate);
|
|
4540
|
+
this.socketListenerRegistered = false;
|
|
4541
|
+
}
|
|
4542
|
+
/**
|
|
4543
|
+
* Fetch current user's offline redemption codes when group becomes fulfilled
|
|
4544
|
+
* This ensures we show the current user's codes, not someone else's
|
|
4545
|
+
*/
|
|
4546
|
+
async fetchCurrentUserOfflineCodesIfNeeded() {
|
|
4547
|
+
var _a;
|
|
4548
|
+
if (!this.apiClient || !this.currentGroupId) {
|
|
4549
|
+
return;
|
|
4550
|
+
}
|
|
4551
|
+
try {
|
|
4552
|
+
this.logger.info("[Offline] Fetching current user's offline codes");
|
|
4553
|
+
const response = await this.apiClient.getGroupOfflineRedemption(this.currentGroupId);
|
|
4554
|
+
if (response.success && ((_a = response.data) === null || _a === void 0 ? void 0 : _a.offline_redemption)) {
|
|
4555
|
+
const offlineRedemption = response.data.offline_redemption;
|
|
4556
|
+
if (isValidOfflineRedemption(offlineRedemption)) {
|
|
4557
|
+
this.logger.info("[Offline] Successfully fetched current user's offline codes");
|
|
4558
|
+
this.updateData({ offlineRedemption });
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
else {
|
|
4562
|
+
this.logger.warn("[Offline] Failed to fetch offline codes", response.error);
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
4565
|
+
catch (error) {
|
|
4566
|
+
this.logger.error("[Offline] Error fetching offline codes", error);
|
|
4567
|
+
}
|
|
4568
|
+
}
|
|
3893
4569
|
/**
|
|
3894
4570
|
* Create activity item from socket event data
|
|
3895
4571
|
*/
|
|
@@ -3920,6 +4596,10 @@ class LobbyModal {
|
|
|
3920
4596
|
emoji = "🛒";
|
|
3921
4597
|
action = "started a new group";
|
|
3922
4598
|
break;
|
|
4599
|
+
case "group:member:left":
|
|
4600
|
+
emoji = "🚪";
|
|
4601
|
+
action = "left the group";
|
|
4602
|
+
break;
|
|
3923
4603
|
default:
|
|
3924
4604
|
emoji = "⚡";
|
|
3925
4605
|
action = "activity in group";
|
|
@@ -4392,6 +5072,9 @@ var WidgetState;
|
|
|
4392
5072
|
WidgetState["LOADED"] = "loaded";
|
|
4393
5073
|
WidgetState["ERROR"] = "error";
|
|
4394
5074
|
})(WidgetState || (WidgetState = {}));
|
|
5075
|
+
const RECOVERY_TOAST_REJOINED = "Rejoined your previous group.";
|
|
5076
|
+
const RECOVERY_TOAST_NEXT_AVAILABLE = "Your previous group was full, so we joined you to the next available group.";
|
|
5077
|
+
const RECOVERY_TOAST_CREATED_NEW = "Your previous group was no longer available, so we created a fresh group for you.";
|
|
4395
5078
|
/**
|
|
4396
5079
|
* WidgetRoot handles rendering of the CoBuy widget into a DOM container
|
|
4397
5080
|
*/
|
|
@@ -4423,6 +5106,7 @@ class WidgetRoot {
|
|
|
4423
5106
|
this.groupExpiryRefreshTriggered = false; // Track if expiry refresh already triggered for current group
|
|
4424
5107
|
this.offlineRedemption = null;
|
|
4425
5108
|
this.offlineRedemptionModal = null;
|
|
5109
|
+
this.campaignRedemptionMethod = "online";
|
|
4426
5110
|
this.isRendering = false;
|
|
4427
5111
|
this.renderPromise = null;
|
|
4428
5112
|
this.liveRegionAnnouncer = null;
|
|
@@ -4432,6 +5116,7 @@ class WidgetRoot {
|
|
|
4432
5116
|
this.renderDebounceTimer = null;
|
|
4433
5117
|
this.pendingRenderOptions = null;
|
|
4434
5118
|
this.RENDER_DEBOUNCE_MS = 300; // 300ms debounce for rapid re-renders
|
|
5119
|
+
this.LAST_LEFT_GROUP_PREFIX = "cobuy_last_left_group";
|
|
4435
5120
|
/** Handle backend fulfillment notifications */
|
|
4436
5121
|
this.handleGroupFulfilledEvent = (event) => {
|
|
4437
5122
|
const detail = event.detail;
|
|
@@ -4475,6 +5160,80 @@ class WidgetRoot {
|
|
|
4475
5160
|
this.handleCTAClick(productId);
|
|
4476
5161
|
}, 300); // 300ms debounce to prevent double-clicks
|
|
4477
5162
|
}
|
|
5163
|
+
getRecoveryStorageKey(productId) {
|
|
5164
|
+
var _a;
|
|
5165
|
+
const sessionId = (_a = this.apiClient) === null || _a === void 0 ? void 0 : _a.getSessionId();
|
|
5166
|
+
if (!sessionId || !productId) {
|
|
5167
|
+
return null;
|
|
5168
|
+
}
|
|
5169
|
+
return `${this.LAST_LEFT_GROUP_PREFIX}:${sessionId}:${productId}`;
|
|
5170
|
+
}
|
|
5171
|
+
getStoredRecoveryGroupId(productId) {
|
|
5172
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
5173
|
+
return null;
|
|
5174
|
+
}
|
|
5175
|
+
const key = this.getRecoveryStorageKey(productId);
|
|
5176
|
+
if (!key) {
|
|
5177
|
+
return null;
|
|
5178
|
+
}
|
|
5179
|
+
try {
|
|
5180
|
+
const raw = window.localStorage.getItem(key);
|
|
5181
|
+
if (!raw) {
|
|
5182
|
+
return null;
|
|
5183
|
+
}
|
|
5184
|
+
const parsed = JSON.parse(raw);
|
|
5185
|
+
if (!(parsed === null || parsed === void 0 ? void 0 : parsed.groupId) || parsed.productId !== productId) {
|
|
5186
|
+
return null;
|
|
5187
|
+
}
|
|
5188
|
+
return parsed.groupId;
|
|
5189
|
+
}
|
|
5190
|
+
catch (_a) {
|
|
5191
|
+
return null;
|
|
5192
|
+
}
|
|
5193
|
+
}
|
|
5194
|
+
clearStoredRecoveryGroupId(productId) {
|
|
5195
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
5196
|
+
return;
|
|
5197
|
+
}
|
|
5198
|
+
const key = this.getRecoveryStorageKey(productId);
|
|
5199
|
+
if (!key) {
|
|
5200
|
+
return;
|
|
5201
|
+
}
|
|
5202
|
+
try {
|
|
5203
|
+
window.localStorage.removeItem(key);
|
|
5204
|
+
}
|
|
5205
|
+
catch (_a) {
|
|
5206
|
+
// Ignore storage issues
|
|
5207
|
+
}
|
|
5208
|
+
}
|
|
5209
|
+
showRecoveryToast(message) {
|
|
5210
|
+
if (typeof document === "undefined") {
|
|
5211
|
+
return;
|
|
5212
|
+
}
|
|
5213
|
+
const existing = document.getElementById("cobuy-recovery-toast");
|
|
5214
|
+
if (existing) {
|
|
5215
|
+
existing.remove();
|
|
5216
|
+
}
|
|
5217
|
+
const toast = document.createElement("div");
|
|
5218
|
+
toast.id = "cobuy-recovery-toast";
|
|
5219
|
+
toast.style.position = "fixed";
|
|
5220
|
+
toast.style.left = "50%";
|
|
5221
|
+
toast.style.bottom = "24px";
|
|
5222
|
+
toast.style.transform = "translateX(-50%)";
|
|
5223
|
+
toast.style.background = "#0f172a";
|
|
5224
|
+
toast.style.color = "#fff";
|
|
5225
|
+
toast.style.padding = "10px 14px";
|
|
5226
|
+
toast.style.borderRadius = "10px";
|
|
5227
|
+
toast.style.fontSize = "13px";
|
|
5228
|
+
toast.style.fontWeight = "600";
|
|
5229
|
+
toast.style.zIndex = "10040";
|
|
5230
|
+
toast.style.boxShadow = "0 8px 20px rgba(0,0,0,0.24)";
|
|
5231
|
+
toast.textContent = message;
|
|
5232
|
+
document.body.appendChild(toast);
|
|
5233
|
+
window.setTimeout(() => {
|
|
5234
|
+
toast.remove();
|
|
5235
|
+
}, 2800);
|
|
5236
|
+
}
|
|
4478
5237
|
/** Subscribe once to backend socket events routed through the host page */
|
|
4479
5238
|
subscribeToSocketEvents() {
|
|
4480
5239
|
if (typeof window === "undefined" || this.socketListenerRegistered) {
|
|
@@ -4482,11 +5241,13 @@ class WidgetRoot {
|
|
|
4482
5241
|
}
|
|
4483
5242
|
window.addEventListener("group:fulfilled", this.handleGroupFulfilledEvent);
|
|
4484
5243
|
window.addEventListener("group:member:joined", this.handleGroupUpdatedEvent);
|
|
5244
|
+
window.addEventListener("group:member:left", this.handleGroupUpdatedEvent);
|
|
4485
5245
|
window.addEventListener("group:created", this.handleGroupUpdatedEvent);
|
|
4486
5246
|
this.socketListenerRegistered = true;
|
|
4487
5247
|
}
|
|
4488
5248
|
/** Fetch latest group data and re-render containers */
|
|
4489
5249
|
async refreshGroupDataFromRealtime() {
|
|
5250
|
+
var _a;
|
|
4490
5251
|
// Debounce rapid refreshes to prevent loops and reduce API load
|
|
4491
5252
|
const now = Date.now();
|
|
4492
5253
|
if (now - this.lastGroupDataRefreshTime < this.GROUP_REFRESH_DEBOUNCE) {
|
|
@@ -4503,21 +5264,29 @@ class WidgetRoot {
|
|
|
4503
5264
|
const groupData = await this.fetchPrimaryGroup(this.currentProductId);
|
|
4504
5265
|
this.currentGroupData = groupData;
|
|
4505
5266
|
this.currentGroupId = (groupData === null || groupData === void 0 ? void 0 : groupData.id) || this.currentGroupId;
|
|
5267
|
+
// If backend returned member-scoped offline redemption, this session is a member.
|
|
5268
|
+
const hasMemberOfflineRedemption = !!(groupData === null || groupData === void 0 ? void 0 : groupData.offline_redemption) && isValidOfflineRedemption(groupData.offline_redemption);
|
|
5269
|
+
if (hasMemberOfflineRedemption && !this.currentSessionId) {
|
|
5270
|
+
this.currentSessionId = ((_a = this.apiClient) === null || _a === void 0 ? void 0 : _a.getSessionId()) || null;
|
|
5271
|
+
}
|
|
4506
5272
|
// If backend signals completion via counts, reflect it — but only for actual members.
|
|
4507
5273
|
// Observers may see a full group returned by fetchPrimaryGroup; they should NOT enter
|
|
4508
5274
|
// the checkout flow. currentSessionId is the membership signal (non-null = joined).
|
|
4509
5275
|
if (groupData) {
|
|
4510
5276
|
const participants = Number(groupData.participants_count || 0);
|
|
4511
5277
|
const max = Number(groupData.max_participants || 0);
|
|
4512
|
-
|
|
5278
|
+
const isMember = !!this.currentSessionId || hasMemberOfflineRedemption;
|
|
5279
|
+
if (max > 0 && participants >= max && isMember) {
|
|
4513
5280
|
this.groupFulfilled = true;
|
|
4514
5281
|
// The primary group API returns offline_redemption for members of fulfilled groups.
|
|
4515
5282
|
// Extract it here so renderFulfilledSummary shows the "Redeem In-store" link
|
|
4516
5283
|
// (matching the UI users see on a full page reload).
|
|
4517
|
-
if (
|
|
4518
|
-
isValidOfflineRedemption(groupData.offline_redemption)) {
|
|
5284
|
+
if (hasMemberOfflineRedemption) {
|
|
4519
5285
|
this.offlineRedemption = groupData.offline_redemption;
|
|
4520
5286
|
}
|
|
5287
|
+
if (groupData.campaign_redemption_method) {
|
|
5288
|
+
this.campaignRedemptionMethod = groupData.campaign_redemption_method;
|
|
5289
|
+
}
|
|
4521
5290
|
}
|
|
4522
5291
|
}
|
|
4523
5292
|
else {
|
|
@@ -4613,7 +5382,7 @@ class WidgetRoot {
|
|
|
4613
5382
|
}
|
|
4614
5383
|
/** Persist fulfilled state, emit callback, and refresh UI */
|
|
4615
5384
|
processGroupFulfilled(eventData) {
|
|
4616
|
-
var _a, _b, _c, _d;
|
|
5385
|
+
var _a, _b, _c, _d, _e;
|
|
4617
5386
|
this.groupFulfilled = true;
|
|
4618
5387
|
// Extract reward from new event structure or fallback to legacy
|
|
4619
5388
|
const reward = ((_a = eventData.frozen_reward) === null || _a === void 0 ? void 0 : _a.reward) || eventData.reward || null;
|
|
@@ -4628,7 +5397,10 @@ class WidgetRoot {
|
|
|
4628
5397
|
code: this.offlineRedemption.redemption_code,
|
|
4629
5398
|
});
|
|
4630
5399
|
}
|
|
4631
|
-
|
|
5400
|
+
if ((_c = eventData.group) === null || _c === void 0 ? void 0 : _c.campaign_redemption_method) {
|
|
5401
|
+
this.campaignRedemptionMethod = eventData.group.campaign_redemption_method;
|
|
5402
|
+
}
|
|
5403
|
+
(_e = (_d = this.events) === null || _d === void 0 ? void 0 : _d.onGroupFulfilled) === null || _e === void 0 ? void 0 : _e.call(_d, eventData);
|
|
4632
5404
|
this.renderFulfilledState();
|
|
4633
5405
|
}
|
|
4634
5406
|
/** Re-render widget and external containers to reflect fulfillment */
|
|
@@ -4768,8 +5540,10 @@ class WidgetRoot {
|
|
|
4768
5540
|
footer.style.justifyContent = "flex-end";
|
|
4769
5541
|
footer.style.alignItems = "center";
|
|
4770
5542
|
footer.style.gap = "12px";
|
|
4771
|
-
// Add "Redeem In-store" CTA if offline redemption is available
|
|
4772
|
-
if (this.offlineRedemption &&
|
|
5543
|
+
// Add "Redeem In-store" CTA if offline redemption is available and campaign allows it
|
|
5544
|
+
if (this.offlineRedemption &&
|
|
5545
|
+
isValidOfflineRedemption(this.offlineRedemption) &&
|
|
5546
|
+
this.campaignRedemptionMethod !== "online") {
|
|
4773
5547
|
const redeemLink = document.createElement("button");
|
|
4774
5548
|
redeemLink.className = "cobuy-redeem-instore-link";
|
|
4775
5549
|
redeemLink.style.background = "none";
|
|
@@ -4867,7 +5641,7 @@ class WidgetRoot {
|
|
|
4867
5641
|
}
|
|
4868
5642
|
// Set callback to open lobby when a group is successfully joined
|
|
4869
5643
|
this.groupListModal.setOnGroupJoined((joinData) => {
|
|
4870
|
-
var _a;
|
|
5644
|
+
var _a, _b;
|
|
4871
5645
|
// Update current group ID and session ID so the modal knows which group the user has joined
|
|
4872
5646
|
this.currentGroupId = joinData.group.id;
|
|
4873
5647
|
this.currentSessionId = ((_a = this.apiClient) === null || _a === void 0 ? void 0 : _a.getSessionId()) || null;
|
|
@@ -4881,8 +5655,9 @@ class WidgetRoot {
|
|
|
4881
5655
|
currentMembers: joinData.group.participants_count,
|
|
4882
5656
|
totalMembers: joinData.group.max_participants,
|
|
4883
5657
|
timeLeft: joinData.group.timeLeftSeconds,
|
|
5658
|
+
redemptionMethod: (_b = joinData.group.campaign_redemption_method) !== null && _b !== void 0 ? _b : this.campaignRedemptionMethod,
|
|
4884
5659
|
};
|
|
4885
|
-
const lobbyModal = new LobbyModal(lobbyData, {},
|
|
5660
|
+
const lobbyModal = new LobbyModal(lobbyData, {}, this.apiClient, null, this.analyticsClient, this.config.debug);
|
|
4886
5661
|
lobbyModal.open(joinData.group.id);
|
|
4887
5662
|
});
|
|
4888
5663
|
// Set callback for viewing progress on already joined group
|
|
@@ -4898,8 +5673,9 @@ class WidgetRoot {
|
|
|
4898
5673
|
progress: Math.round((groupData.joined / groupData.total) * 100),
|
|
4899
5674
|
currentMembers: groupData.joined,
|
|
4900
5675
|
totalMembers: groupData.total,
|
|
5676
|
+
redemptionMethod: this.campaignRedemptionMethod,
|
|
4901
5677
|
};
|
|
4902
|
-
const lobbyModal = new LobbyModal(lobbyData, {},
|
|
5678
|
+
const lobbyModal = new LobbyModal(lobbyData, {}, this.apiClient, null, this.analyticsClient, this.config.debug);
|
|
4903
5679
|
lobbyModal.open(groupId);
|
|
4904
5680
|
});
|
|
4905
5681
|
}
|
|
@@ -5083,6 +5859,9 @@ class WidgetRoot {
|
|
|
5083
5859
|
isValidOfflineRedemption(groupData.offline_redemption)) {
|
|
5084
5860
|
this.offlineRedemption = groupData.offline_redemption;
|
|
5085
5861
|
}
|
|
5862
|
+
if (groupData === null || groupData === void 0 ? void 0 : groupData.campaign_redemption_method) {
|
|
5863
|
+
this.campaignRedemptionMethod = groupData.campaign_redemption_method;
|
|
5864
|
+
}
|
|
5086
5865
|
// Check if group is already fulfilled (full) on initial load
|
|
5087
5866
|
if (groupData) {
|
|
5088
5867
|
const participants = Number(groupData.participants_count || 0);
|
|
@@ -5804,10 +6583,99 @@ class WidgetRoot {
|
|
|
5804
6583
|
to { transform: rotate(360deg); }
|
|
5805
6584
|
}
|
|
5806
6585
|
|
|
6586
|
+
.cobuy-prejoin-contact-overlay {
|
|
6587
|
+
position: fixed;
|
|
6588
|
+
inset: 0;
|
|
6589
|
+
z-index: 10045;
|
|
6590
|
+
background: rgba(2, 6, 23, 0.5);
|
|
6591
|
+
display: flex;
|
|
6592
|
+
align-items: center;
|
|
6593
|
+
justify-content: center;
|
|
6594
|
+
padding: 16px;
|
|
6595
|
+
}
|
|
6596
|
+
|
|
6597
|
+
.cobuy-prejoin-contact-card {
|
|
6598
|
+
width: min(440px, 100%);
|
|
6599
|
+
background: #fff;
|
|
6600
|
+
border-radius: 14px;
|
|
6601
|
+
padding: 18px;
|
|
6602
|
+
box-shadow: 0 14px 36px rgba(15, 23, 42, 0.22);
|
|
6603
|
+
display: flex;
|
|
6604
|
+
flex-direction: column;
|
|
6605
|
+
gap: 10px;
|
|
6606
|
+
}
|
|
6607
|
+
|
|
6608
|
+
.cobuy-prejoin-contact-title {
|
|
6609
|
+
font-size: 20px;
|
|
6610
|
+
font-weight: 800;
|
|
6611
|
+
color: #0f172a;
|
|
6612
|
+
margin: 0;
|
|
6613
|
+
}
|
|
6614
|
+
|
|
6615
|
+
.cobuy-prejoin-contact-subtitle {
|
|
6616
|
+
font-size: 14px;
|
|
6617
|
+
line-height: 1.5;
|
|
6618
|
+
color: #334155;
|
|
6619
|
+
margin: 0;
|
|
6620
|
+
}
|
|
6621
|
+
|
|
6622
|
+
.cobuy-prejoin-contact-input {
|
|
6623
|
+
width: 100%;
|
|
6624
|
+
border: 1px solid #cbd5e1;
|
|
6625
|
+
border-radius: 10px;
|
|
6626
|
+
padding: 11px 12px;
|
|
6627
|
+
font-size: 14px;
|
|
6628
|
+
outline: none;
|
|
6629
|
+
}
|
|
6630
|
+
|
|
6631
|
+
.cobuy-prejoin-contact-input:focus {
|
|
6632
|
+
border-color: #1d4ed8;
|
|
6633
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
|
6634
|
+
}
|
|
6635
|
+
|
|
6636
|
+
.cobuy-prejoin-contact-actions {
|
|
6637
|
+
display: flex;
|
|
6638
|
+
gap: 8px;
|
|
6639
|
+
flex-wrap: wrap;
|
|
6640
|
+
}
|
|
6641
|
+
|
|
6642
|
+
.cobuy-prejoin-contact-primary,
|
|
6643
|
+
.cobuy-prejoin-contact-secondary {
|
|
6644
|
+
border: none;
|
|
6645
|
+
border-radius: 10px;
|
|
6646
|
+
padding: 10px 12px;
|
|
6647
|
+
font-weight: 700;
|
|
6648
|
+
cursor: pointer;
|
|
6649
|
+
font-size: 13px;
|
|
6650
|
+
}
|
|
6651
|
+
|
|
6652
|
+
.cobuy-prejoin-contact-primary {
|
|
6653
|
+
background: #1d4ed8;
|
|
6654
|
+
color: #fff;
|
|
6655
|
+
}
|
|
6656
|
+
|
|
6657
|
+
.cobuy-prejoin-contact-primary:hover {
|
|
6658
|
+
background: #1e40af;
|
|
6659
|
+
}
|
|
6660
|
+
|
|
6661
|
+
.cobuy-prejoin-contact-secondary {
|
|
6662
|
+
background: #e2e8f0;
|
|
6663
|
+
color: #1e293b;
|
|
6664
|
+
}
|
|
6665
|
+
|
|
6666
|
+
.cobuy-prejoin-contact-secondary:hover {
|
|
6667
|
+
background: #cbd5e1;
|
|
6668
|
+
}
|
|
6669
|
+
|
|
5807
6670
|
@media (max-width: 640px) {
|
|
5808
6671
|
.cobuy-widget {
|
|
5809
6672
|
grid-template-columns: 1fr;
|
|
5810
6673
|
}
|
|
6674
|
+
|
|
6675
|
+
.cobuy-prejoin-contact-primary,
|
|
6676
|
+
.cobuy-prejoin-contact-secondary {
|
|
6677
|
+
width: 100%;
|
|
6678
|
+
}
|
|
5811
6679
|
}
|
|
5812
6680
|
`;
|
|
5813
6681
|
document.head.appendChild(style);
|
|
@@ -5816,7 +6684,7 @@ class WidgetRoot {
|
|
|
5816
6684
|
* Handle CTA button click with analytics and modal opening
|
|
5817
6685
|
*/
|
|
5818
6686
|
async handleCTAClick(productId) {
|
|
5819
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
6687
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
5820
6688
|
this.logger.info(`CTA clicked for product: ${productId}`);
|
|
5821
6689
|
// Track analytics event asynchronously (fire-and-forget)
|
|
5822
6690
|
if (this.analyticsClient) {
|
|
@@ -5831,29 +6699,113 @@ class WidgetRoot {
|
|
|
5831
6699
|
// Join group before opening modal
|
|
5832
6700
|
let groupJoinData = null;
|
|
5833
6701
|
let inviteData = null;
|
|
6702
|
+
let recoveryGroupId = null;
|
|
5834
6703
|
if (this.apiClient && this.currentGroupId) {
|
|
5835
6704
|
try {
|
|
5836
|
-
|
|
5837
|
-
if (this.analyticsClient) {
|
|
6705
|
+
recoveryGroupId = this.getStoredRecoveryGroupId(productId);
|
|
6706
|
+
if (this.analyticsClient && recoveryGroupId) {
|
|
5838
6707
|
this.analyticsClient
|
|
5839
|
-
.
|
|
6708
|
+
.trackEvent("RECOVERY_ATTEMPT", productId, {
|
|
6709
|
+
previousGroupId: recoveryGroupId,
|
|
6710
|
+
protectionState: "protected",
|
|
6711
|
+
})
|
|
5840
6712
|
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
5841
6713
|
}
|
|
5842
|
-
const
|
|
5843
|
-
if (
|
|
5844
|
-
|
|
6714
|
+
const recoverResponse = await this.apiClient.recoverOrJoinGroup(productId);
|
|
6715
|
+
if (recoverResponse.success && recoverResponse.data) {
|
|
6716
|
+
groupJoinData = recoverResponse.data;
|
|
6717
|
+
this.currentGroupId = groupJoinData.group.id;
|
|
6718
|
+
this.clearStoredRecoveryGroupId(productId);
|
|
6719
|
+
if (this.analyticsClient && groupJoinData.recovery.recovered) {
|
|
6720
|
+
this.analyticsClient
|
|
6721
|
+
.trackEvent("RECOVERY_SUCCESS", productId, {
|
|
6722
|
+
previousGroupId: groupJoinData.recovery.previous_group_id || recoveryGroupId,
|
|
6723
|
+
joinedGroupId: groupJoinData.recovery.joined_group_id,
|
|
6724
|
+
recoveryOutcome: groupJoinData.recovery.outcome,
|
|
6725
|
+
protectionState: groupJoinData.recovery.matched_by === "none" ? "unprotected" : "protected",
|
|
6726
|
+
})
|
|
6727
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6728
|
+
}
|
|
6729
|
+
else if (this.analyticsClient && recoveryGroupId) {
|
|
6730
|
+
this.analyticsClient
|
|
6731
|
+
.trackEvent("RECOVERY_FAILED", productId, {
|
|
6732
|
+
previousGroupId: recoveryGroupId,
|
|
6733
|
+
errorCode: "NOT_RECOVERED",
|
|
6734
|
+
errorMessage: "Recovery did not restore the previous membership.",
|
|
6735
|
+
protectionState: "protected",
|
|
6736
|
+
})
|
|
6737
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6738
|
+
}
|
|
6739
|
+
switch (groupJoinData.recovery.outcome) {
|
|
6740
|
+
case "rejoined_previous_group":
|
|
6741
|
+
this.showRecoveryToast(RECOVERY_TOAST_REJOINED);
|
|
6742
|
+
break;
|
|
6743
|
+
case "joined_next_available_group":
|
|
6744
|
+
if (groupJoinData.recovery.recovered) {
|
|
6745
|
+
this.showRecoveryToast(RECOVERY_TOAST_NEXT_AVAILABLE);
|
|
6746
|
+
}
|
|
6747
|
+
break;
|
|
6748
|
+
case "created_new_group":
|
|
6749
|
+
if (groupJoinData.recovery.recovered) {
|
|
6750
|
+
this.showRecoveryToast(RECOVERY_TOAST_CREATED_NEW);
|
|
6751
|
+
}
|
|
6752
|
+
break;
|
|
6753
|
+
}
|
|
6754
|
+
}
|
|
6755
|
+
else {
|
|
6756
|
+
if (this.analyticsClient && recoveryGroupId) {
|
|
6757
|
+
const errCode = (_c = recoverResponse.error) === null || _c === void 0 ? void 0 : _c.code;
|
|
6758
|
+
const errMsg = (_d = recoverResponse.error) === null || _d === void 0 ? void 0 : _d.message;
|
|
6759
|
+
this.analyticsClient
|
|
6760
|
+
.trackEvent("RECOVERY_FAILED", productId, {
|
|
6761
|
+
previousGroupId: recoveryGroupId,
|
|
6762
|
+
errorCode: errCode || "RECOVERY_UNAVAILABLE",
|
|
6763
|
+
errorMessage: errMsg || "Recovery response was not successful.",
|
|
6764
|
+
protectionState: "protected",
|
|
6765
|
+
})
|
|
6766
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6767
|
+
}
|
|
6768
|
+
const joinTargetGroupId = recoveryGroupId || this.currentGroupId;
|
|
6769
|
+
this.logger.info(`Joining group: ${joinTargetGroupId}`);
|
|
5845
6770
|
if (this.analyticsClient) {
|
|
5846
|
-
const errCode = (_c = joinResponse.error) === null || _c === void 0 ? void 0 : _c.code;
|
|
5847
|
-
const errMsg = (_d = joinResponse.error) === null || _d === void 0 ? void 0 : _d.message;
|
|
5848
6771
|
this.analyticsClient
|
|
5849
|
-
.
|
|
6772
|
+
.trackJoinAttempt(productId, joinTargetGroupId)
|
|
5850
6773
|
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
5851
6774
|
}
|
|
5852
|
-
this.
|
|
5853
|
-
|
|
6775
|
+
let joinResponse = await this.apiClient.joinGroup(joinTargetGroupId);
|
|
6776
|
+
if (!joinResponse.success && recoveryGroupId && this.currentGroupId !== recoveryGroupId) {
|
|
6777
|
+
this.clearStoredRecoveryGroupId(productId);
|
|
6778
|
+
joinResponse = await this.apiClient.joinGroup(this.currentGroupId);
|
|
6779
|
+
}
|
|
6780
|
+
if (!joinResponse.success) {
|
|
6781
|
+
this.logger.error("Failed to join group, modal will not open", joinResponse.error);
|
|
6782
|
+
if (this.analyticsClient) {
|
|
6783
|
+
const errCode = (_e = joinResponse.error) === null || _e === void 0 ? void 0 : _e.code;
|
|
6784
|
+
const errMsg = (_f = joinResponse.error) === null || _f === void 0 ? void 0 : _f.message;
|
|
6785
|
+
this.analyticsClient
|
|
6786
|
+
.trackJoinFailure(productId, joinTargetGroupId, errCode, errMsg)
|
|
6787
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6788
|
+
}
|
|
6789
|
+
this.setButtonLoadingState(false);
|
|
6790
|
+
return; // Don't open modal on error
|
|
6791
|
+
}
|
|
6792
|
+
groupJoinData = joinResponse.data;
|
|
6793
|
+
if ((_g = groupJoinData === null || groupJoinData === void 0 ? void 0 : groupJoinData.group) === null || _g === void 0 ? void 0 : _g.id) {
|
|
6794
|
+
this.currentGroupId = groupJoinData.group.id;
|
|
6795
|
+
}
|
|
6796
|
+
if (recoveryGroupId && ((_h = groupJoinData === null || groupJoinData === void 0 ? void 0 : groupJoinData.group) === null || _h === void 0 ? void 0 : _h.id)) {
|
|
6797
|
+
this.clearStoredRecoveryGroupId(productId);
|
|
6798
|
+
if (groupJoinData.group.id !== recoveryGroupId) {
|
|
6799
|
+
this.showRecoveryToast(RECOVERY_TOAST_NEXT_AVAILABLE);
|
|
6800
|
+
}
|
|
6801
|
+
else {
|
|
6802
|
+
this.showRecoveryToast(RECOVERY_TOAST_REJOINED);
|
|
6803
|
+
}
|
|
6804
|
+
}
|
|
5854
6805
|
}
|
|
5855
|
-
groupJoinData = joinResponse.data;
|
|
5856
6806
|
this.logger.info("Successfully joined group", groupJoinData);
|
|
6807
|
+
// Mark this widget session as an active member for subsequent socket fulfillment handling.
|
|
6808
|
+
this.currentSessionId = ((_j = this.apiClient) === null || _j === void 0 ? void 0 : _j.getSessionId()) || this.currentSessionId;
|
|
5857
6809
|
if (this.analyticsClient && groupJoinData) {
|
|
5858
6810
|
this.analyticsClient
|
|
5859
6811
|
.trackJoinSuccess(productId, groupJoinData.group.id)
|
|
@@ -5865,26 +6817,39 @@ class WidgetRoot {
|
|
|
5865
6817
|
isValidOfflineRedemption(groupJoinData.offline_redemption)
|
|
5866
6818
|
? groupJoinData.offline_redemption
|
|
5867
6819
|
: null;
|
|
6820
|
+
const joinedGroupId = (_k = groupJoinData === null || groupJoinData === void 0 ? void 0 : groupJoinData.group) === null || _k === void 0 ? void 0 : _k.id;
|
|
5868
6821
|
// Trigger invite tracking before opening lobby (global for product)
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
6822
|
+
if (joinedGroupId) {
|
|
6823
|
+
try {
|
|
6824
|
+
const inviteResponse = await this.apiClient.inviteToGroup(joinedGroupId, "copy_link");
|
|
6825
|
+
if (inviteResponse.success && inviteResponse.data) {
|
|
6826
|
+
inviteData = inviteResponse.data;
|
|
6827
|
+
this.logger.info("Invite link generated", inviteData);
|
|
6828
|
+
}
|
|
6829
|
+
else {
|
|
6830
|
+
this.logger.warn("Invite link generation failed", inviteResponse.error);
|
|
6831
|
+
}
|
|
5874
6832
|
}
|
|
5875
|
-
|
|
5876
|
-
this.logger.warn("Invite
|
|
6833
|
+
catch (inviteError) {
|
|
6834
|
+
this.logger.warn("Invite request failed", inviteError);
|
|
5877
6835
|
}
|
|
5878
6836
|
}
|
|
5879
|
-
catch (inviteError) {
|
|
5880
|
-
this.logger.warn("Invite request failed", inviteError);
|
|
5881
|
-
}
|
|
5882
6837
|
}
|
|
5883
6838
|
catch (error) {
|
|
5884
6839
|
this.logger.error("Group join failed, modal will not open", error);
|
|
6840
|
+
if (this.analyticsClient && recoveryGroupId) {
|
|
6841
|
+
this.analyticsClient
|
|
6842
|
+
.trackEvent("RECOVERY_FAILED", productId, {
|
|
6843
|
+
previousGroupId: recoveryGroupId,
|
|
6844
|
+
errorCode: "EXCEPTION",
|
|
6845
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
6846
|
+
protectionState: "protected",
|
|
6847
|
+
})
|
|
6848
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6849
|
+
}
|
|
5885
6850
|
if (this.analyticsClient) {
|
|
5886
6851
|
this.analyticsClient
|
|
5887
|
-
.trackJoinFailure(productId, (
|
|
6852
|
+
.trackJoinFailure(productId, (_l = this.currentGroupId) !== null && _l !== void 0 ? _l : undefined, "EXCEPTION", error instanceof Error ? error.message : String(error))
|
|
5888
6853
|
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
5889
6854
|
}
|
|
5890
6855
|
this.setButtonLoadingState(false);
|
|
@@ -5901,7 +6866,7 @@ class WidgetRoot {
|
|
|
5901
6866
|
const progress = Math.round((groupJoinData.group.participants_count / groupJoinData.group.max_participants) * 100);
|
|
5902
6867
|
// Format discount based on reward type
|
|
5903
6868
|
let discountText = "";
|
|
5904
|
-
if ((
|
|
6869
|
+
if ((_o = (_m = this.currentRewardData) === null || _m === void 0 ? void 0 : _m.reward) === null || _o === void 0 ? void 0 : _o.value) {
|
|
5905
6870
|
const rewardType = this.currentRewardData.reward.type;
|
|
5906
6871
|
const rewardValue = this.currentRewardData.reward.value;
|
|
5907
6872
|
if (rewardType === "percentage" || rewardType === "cashback") {
|
|
@@ -5941,6 +6906,7 @@ class WidgetRoot {
|
|
|
5941
6906
|
shareMessage: shareMessageFromInvite,
|
|
5942
6907
|
isLocked: !isGroupFulfilled,
|
|
5943
6908
|
offlineRedemption: offlineRedemptionFromJoin,
|
|
6909
|
+
redemptionMethod: (_p = groupJoinData.group.campaign_redemption_method) !== null && _p !== void 0 ? _p : this.campaignRedemptionMethod,
|
|
5944
6910
|
onShare: this.analyticsClient
|
|
5945
6911
|
? () => {
|
|
5946
6912
|
this.analyticsClient.trackShareClick(productId, groupJoinData.group.id, "other").catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
@@ -6111,11 +7077,14 @@ const API_ENDPOINTS = {
|
|
|
6111
7077
|
PRODUCT_CONTEXT: "/v1/sdk/products/:productId/context",
|
|
6112
7078
|
PRODUCT_REWARD: "/v1/sdk/products/:productId/reward",
|
|
6113
7079
|
PRODUCT_PRIMARY_GROUP: "/v1/sdk/products/:productId/group/primary",
|
|
7080
|
+
PRODUCT_RECOVER_OR_JOIN_GROUP: "/v1/sdk/products/:productId/group/recover-or-join",
|
|
6114
7081
|
// Group endpoints
|
|
6115
7082
|
GROUP_JOIN: "/v1/sdk/groups/:groupId/join",
|
|
7083
|
+
GROUP_LEAVE: "/v1/sdk/groups/:groupId/leave",
|
|
6116
7084
|
GROUP_CREATE_AND_JOIN: "/v1/sdk/groups/new/join",
|
|
6117
7085
|
PRODUCT_ACTIVE_GROUPS: "/v1/sdk/products/:productId/groups/active",
|
|
6118
7086
|
GROUP_INVITE: "/v1/sdk/groups/:groupId/invite",
|
|
7087
|
+
GROUP_OFFLINE_REDEMPTION: "/v1/sdk/groups/:groupId/offline-redemption",
|
|
6119
7088
|
GROUP_CHECKOUT_PREPARE: "/v1/sdk/groups/:groupId/checkout/prepare",
|
|
6120
7089
|
GROUP_CHECKOUT_VALIDATE: "/v1/sdk/groups/:groupId/checkout/validate",
|
|
6121
7090
|
GROUP_CHECKOUT_CONFIRM: "/v1/sdk/groups/:groupId/checkout/confirm",
|
|
@@ -7203,6 +8172,98 @@ class ApiClient {
|
|
|
7203
8172
|
},
|
|
7204
8173
|
};
|
|
7205
8174
|
}
|
|
8175
|
+
/**
|
|
8176
|
+
* Leave an active group for the current SDK session.
|
|
8177
|
+
*/
|
|
8178
|
+
async leaveGroup(groupId, params) {
|
|
8179
|
+
const endpoint = buildApiUrl("", API_ENDPOINTS.GROUP_LEAVE, { groupId });
|
|
8180
|
+
this.logger.info(`Leaving group: ${groupId}`);
|
|
8181
|
+
const response = await this.post(endpoint, {
|
|
8182
|
+
leave_source: (params === null || params === void 0 ? void 0 : params.leave_source) || "unknown",
|
|
8183
|
+
leave_reason: (params === null || params === void 0 ? void 0 : params.leave_reason) || "unknown",
|
|
8184
|
+
});
|
|
8185
|
+
if (response.success) {
|
|
8186
|
+
const payload = response.data;
|
|
8187
|
+
return {
|
|
8188
|
+
success: true,
|
|
8189
|
+
data: ((payload === null || payload === void 0 ? void 0 : payload.data) || response.data),
|
|
8190
|
+
};
|
|
8191
|
+
}
|
|
8192
|
+
return {
|
|
8193
|
+
success: false,
|
|
8194
|
+
error: response.error || {
|
|
8195
|
+
message: "Failed to leave group",
|
|
8196
|
+
code: "LEAVE_GROUP_ERROR",
|
|
8197
|
+
},
|
|
8198
|
+
};
|
|
8199
|
+
}
|
|
8200
|
+
/**
|
|
8201
|
+
* Fetch current session member's offline redemption codes for a group.
|
|
8202
|
+
*/
|
|
8203
|
+
async getGroupOfflineRedemption(groupId) {
|
|
8204
|
+
const endpoint = buildApiUrl("", API_ENDPOINTS.GROUP_OFFLINE_REDEMPTION, {
|
|
8205
|
+
groupId,
|
|
8206
|
+
});
|
|
8207
|
+
const response = await this.get(endpoint);
|
|
8208
|
+
if (response.success) {
|
|
8209
|
+
const payload = response.data;
|
|
8210
|
+
return {
|
|
8211
|
+
success: true,
|
|
8212
|
+
data: (payload === null || payload === void 0 ? void 0 : payload.data) || {},
|
|
8213
|
+
};
|
|
8214
|
+
}
|
|
8215
|
+
return {
|
|
8216
|
+
success: false,
|
|
8217
|
+
error: response.error || {
|
|
8218
|
+
message: "Failed to fetch offline redemption",
|
|
8219
|
+
code: "OFFLINE_REDEMPTION_FETCH_ERROR",
|
|
8220
|
+
},
|
|
8221
|
+
};
|
|
8222
|
+
}
|
|
8223
|
+
async recoverOrJoinGroup(productId, contact) {
|
|
8224
|
+
var _a;
|
|
8225
|
+
const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_RECOVER_OR_JOIN_GROUP, {
|
|
8226
|
+
productId,
|
|
8227
|
+
});
|
|
8228
|
+
this.logger.info(`Recovering or joining group for product: ${productId}`);
|
|
8229
|
+
const response = await this.post(endpoint, contact ? { contact } : {});
|
|
8230
|
+
if (response.success && ((_a = response.data) === null || _a === void 0 ? void 0 : _a.data)) {
|
|
8231
|
+
return {
|
|
8232
|
+
success: true,
|
|
8233
|
+
data: response.data.data,
|
|
8234
|
+
};
|
|
8235
|
+
}
|
|
8236
|
+
return {
|
|
8237
|
+
success: false,
|
|
8238
|
+
error: response.error || {
|
|
8239
|
+
message: "Failed to recover or join group",
|
|
8240
|
+
code: "RECOVER_OR_JOIN_GROUP_ERROR",
|
|
8241
|
+
},
|
|
8242
|
+
};
|
|
8243
|
+
}
|
|
8244
|
+
/**
|
|
8245
|
+
* Best-effort leave signal for unload/pagehide scenarios.
|
|
8246
|
+
* Uses fetch keepalive so it can run while page is closing.
|
|
8247
|
+
*/
|
|
8248
|
+
leaveGroupBestEffort(groupId, params) {
|
|
8249
|
+
if (!groupId)
|
|
8250
|
+
return;
|
|
8251
|
+
const endpoint = buildApiUrl("", API_ENDPOINTS.GROUP_LEAVE, { groupId });
|
|
8252
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
8253
|
+
const payload = JSON.stringify({
|
|
8254
|
+
leave_source: (params === null || params === void 0 ? void 0 : params.leave_source) || "browser_close",
|
|
8255
|
+
leave_reason: (params === null || params === void 0 ? void 0 : params.leave_reason) || "voluntary_no_contact",
|
|
8256
|
+
});
|
|
8257
|
+
const authHeaders = this.authStrategy.getHeaders();
|
|
8258
|
+
void fetch(url, {
|
|
8259
|
+
method: "POST",
|
|
8260
|
+
headers: Object.assign({ "Content-Type": "application/json", "X-CoBuy-SDK-Version": "1.0.0", "X-CoBuy-Session": this.sessionId }, authHeaders),
|
|
8261
|
+
body: payload,
|
|
8262
|
+
keepalive: true,
|
|
8263
|
+
}).catch((error) => {
|
|
8264
|
+
this.logger.debug("Best-effort leave request failed", error);
|
|
8265
|
+
});
|
|
8266
|
+
}
|
|
7206
8267
|
/**
|
|
7207
8268
|
* Prepare checkout for a group
|
|
7208
8269
|
*
|
|
@@ -12032,15 +13093,16 @@ class SocketManager {
|
|
|
12032
13093
|
return;
|
|
12033
13094
|
const bind = (eventName, handler) => {
|
|
12034
13095
|
var _a;
|
|
12035
|
-
if (!handler)
|
|
12036
|
-
return;
|
|
12037
13096
|
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.on(eventName, (payload) => {
|
|
12038
13097
|
this.logger.info(`[Socket] ${eventName} payload: ${this.formatPayload(payload)}`);
|
|
12039
13098
|
this.dispatchWindowEvent(eventName, payload);
|
|
12040
|
-
handler
|
|
13099
|
+
if (handler) {
|
|
13100
|
+
handler(payload);
|
|
13101
|
+
}
|
|
12041
13102
|
});
|
|
12042
13103
|
};
|
|
12043
13104
|
bind("group:member:joined", handlers.onGroupMemberJoined);
|
|
13105
|
+
bind("group:member:left", handlers.onGroupMemberLeft);
|
|
12044
13106
|
bind("group:created", handlers.onGroupCreated);
|
|
12045
13107
|
bind("group:fulfilled", handlers.onGroupFulfilled);
|
|
12046
13108
|
}
|
|
@@ -12051,6 +13113,7 @@ class SocketManager {
|
|
|
12051
13113
|
if (!this.socket)
|
|
12052
13114
|
return;
|
|
12053
13115
|
this.socket.off("group:member:joined");
|
|
13116
|
+
this.socket.off("group:member:left");
|
|
12054
13117
|
this.socket.off("group:created");
|
|
12055
13118
|
this.socket.off("group:fulfilled");
|
|
12056
13119
|
}
|
|
@@ -13424,6 +14487,7 @@ class CoBuy {
|
|
|
13424
14487
|
activities: options.activities,
|
|
13425
14488
|
isLocked: options.isLocked,
|
|
13426
14489
|
offlineRedemption: options.offlineRedemption,
|
|
14490
|
+
redemptionMethod: options.redemptionMethod,
|
|
13427
14491
|
};
|
|
13428
14492
|
// Create modal instance
|
|
13429
14493
|
const modal = new LobbyModal(modalData, {
|
|
@@ -13435,10 +14499,22 @@ class CoBuy {
|
|
|
13435
14499
|
if ((_a = config.events) === null || _a === void 0 ? void 0 : _a.onModalClose) {
|
|
13436
14500
|
config.events.onModalClose(options.productId);
|
|
13437
14501
|
}
|
|
14502
|
+
// Sync widget state after lobby closes so fulfilled CTAs/group view
|
|
14503
|
+
// reflect socket-driven changes without requiring a page reload.
|
|
14504
|
+
const refreshPromises = [];
|
|
14505
|
+
this.widgets.forEach((w) => {
|
|
14506
|
+
const pid = typeof w.getProductId === "function"
|
|
14507
|
+
? w.getProductId()
|
|
14508
|
+
: null;
|
|
14509
|
+
if (pid === options.productId && typeof w.requestRefresh === "function") {
|
|
14510
|
+
refreshPromises.push(w.requestRefresh());
|
|
14511
|
+
}
|
|
14512
|
+
});
|
|
14513
|
+
void Promise.all(refreshPromises.map((p) => p.then(() => undefined).catch(() => undefined)));
|
|
13438
14514
|
},
|
|
13439
14515
|
onCopyLink: options.onCopyLink,
|
|
13440
14516
|
onShare: options.onShare,
|
|
13441
|
-
}, this.apiClient, this.socketManager, config.debug);
|
|
14517
|
+
}, this.apiClient, this.socketManager, this.analyticsClient, config.debug);
|
|
13442
14518
|
// Store in map for persistence
|
|
13443
14519
|
this.modals.set(modalKey, modal);
|
|
13444
14520
|
// Maintain backward compatibility
|