@cshah18/sdk 4.14.0 → 4.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cobuy-sdk.esm.js +1057 -57
- package/dist/cobuy-sdk.esm.js.map +1 -1
- package/dist/cobuy-sdk.umd.js +1057 -57
- package/dist/cobuy-sdk.umd.js.map +1 -1
- package/dist/types/core/api-client.d.ts +17 -1
- package/dist/types/core/endpoints.d.ts +2 -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 +49 -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
|
*/
|
|
@@ -2264,6 +2295,7 @@ class LobbyModal {
|
|
|
2264
2295
|
this.logger = new Logger(debug);
|
|
2265
2296
|
this.apiClient = apiClient;
|
|
2266
2297
|
this.socketManager = socketManager;
|
|
2298
|
+
this.analyticsClient = analyticsClient;
|
|
2267
2299
|
// Log the group data being passed into the modal
|
|
2268
2300
|
this.logger.info("LobbyModal initialized with group data", {
|
|
2269
2301
|
groupId: data.groupId,
|
|
@@ -2275,14 +2307,385 @@ class LobbyModal {
|
|
|
2275
2307
|
timeLeft: data.timeLeft,
|
|
2276
2308
|
offlineRedemption: data.offlineRedemption,
|
|
2277
2309
|
});
|
|
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);
|
|
2310
|
+
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
2311
|
// Normalize optional flags so undefined values don't override defaults
|
|
2280
2312
|
this.data.isLocked = this.computeIsLocked(this.data);
|
|
2281
2313
|
if (!this.data.activities || !Array.isArray(this.data.activities)) {
|
|
2282
2314
|
this.data.activities = this.getDefaultActivities();
|
|
2283
2315
|
}
|
|
2316
|
+
this.hasContactProtection = this.readContactProtectionState();
|
|
2284
2317
|
this.callbacks = callbacks;
|
|
2285
2318
|
}
|
|
2319
|
+
getSessionId() {
|
|
2320
|
+
var _a;
|
|
2321
|
+
return ((_a = this.apiClient) === null || _a === void 0 ? void 0 : _a.getSessionId()) || "anonymous";
|
|
2322
|
+
}
|
|
2323
|
+
getContactProtectionStorageKey() {
|
|
2324
|
+
return `${this.CONTACT_PROTECTION_PREFIX}:${this.getSessionId()}:${this.data.productId}`;
|
|
2325
|
+
}
|
|
2326
|
+
getLastLeftGroupStorageKey() {
|
|
2327
|
+
return `${this.LAST_LEFT_GROUP_PREFIX}:${this.getSessionId()}:${this.data.productId}`;
|
|
2328
|
+
}
|
|
2329
|
+
readContactProtectionState() {
|
|
2330
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
2331
|
+
return false;
|
|
2332
|
+
}
|
|
2333
|
+
try {
|
|
2334
|
+
return window.localStorage.getItem(this.getContactProtectionStorageKey()) === "1";
|
|
2335
|
+
}
|
|
2336
|
+
catch (_a) {
|
|
2337
|
+
return false;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
persistContactProtectionState(value) {
|
|
2341
|
+
this.hasContactProtection = value;
|
|
2342
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
try {
|
|
2346
|
+
const key = this.getContactProtectionStorageKey();
|
|
2347
|
+
if (value) {
|
|
2348
|
+
window.localStorage.setItem(key, "1");
|
|
2349
|
+
}
|
|
2350
|
+
else {
|
|
2351
|
+
window.localStorage.removeItem(key);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
catch (_a) {
|
|
2355
|
+
// Ignore storage errors (private mode, quota exceeded, etc.)
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
persistLastLeftGroup(groupId) {
|
|
2359
|
+
if (typeof window === "undefined" || !window.localStorage || !groupId) {
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
try {
|
|
2363
|
+
window.localStorage.setItem(this.getLastLeftGroupStorageKey(), JSON.stringify({
|
|
2364
|
+
groupId,
|
|
2365
|
+
productId: this.data.productId,
|
|
2366
|
+
leftAt: Date.now(),
|
|
2367
|
+
}));
|
|
2368
|
+
}
|
|
2369
|
+
catch (_a) {
|
|
2370
|
+
// Ignore storage errors
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
clearInitialContactPromptTimer() {
|
|
2374
|
+
if (this.initialContactPromptTimer !== null) {
|
|
2375
|
+
window.clearTimeout(this.initialContactPromptTimer);
|
|
2376
|
+
this.initialContactPromptTimer = null;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
clearContactPromptOverlay() {
|
|
2380
|
+
if (this.contactPromptOverlay) {
|
|
2381
|
+
this.contactPromptOverlay.remove();
|
|
2382
|
+
this.contactPromptOverlay = null;
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
showBlockingLoader(message) {
|
|
2386
|
+
if (!this.modalElement) {
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
this.hideBlockingLoader();
|
|
2390
|
+
const overlay = document.createElement("div");
|
|
2391
|
+
overlay.className = "lobby-blocking-loader-overlay";
|
|
2392
|
+
const card = document.createElement("div");
|
|
2393
|
+
card.className = "lobby-blocking-loader-card";
|
|
2394
|
+
const spinner = document.createElement("div");
|
|
2395
|
+
spinner.className = "lobby-blocking-loader-spinner";
|
|
2396
|
+
const text = document.createElement("p");
|
|
2397
|
+
text.className = "lobby-blocking-loader-text";
|
|
2398
|
+
text.textContent = message;
|
|
2399
|
+
card.appendChild(spinner);
|
|
2400
|
+
card.appendChild(text);
|
|
2401
|
+
overlay.appendChild(card);
|
|
2402
|
+
this.modalElement.appendChild(overlay);
|
|
2403
|
+
this.leaveLoaderOverlay = overlay;
|
|
2404
|
+
}
|
|
2405
|
+
hideBlockingLoader() {
|
|
2406
|
+
if (this.leaveLoaderOverlay) {
|
|
2407
|
+
this.leaveLoaderOverlay.remove();
|
|
2408
|
+
this.leaveLoaderOverlay = null;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
updateContactProtectionUI() {
|
|
2412
|
+
const banner = document.getElementById("lobbyContactProtectionBanner");
|
|
2413
|
+
const explicitLeaveContainer = document.getElementById("lobbyProtectedLeaveContainer");
|
|
2414
|
+
if (!banner)
|
|
2415
|
+
return;
|
|
2416
|
+
const canShowContactActions = this.data.status !== "complete" && Boolean(this.apiClient);
|
|
2417
|
+
if (!canShowContactActions) {
|
|
2418
|
+
banner.style.display = "none";
|
|
2419
|
+
if (explicitLeaveContainer) {
|
|
2420
|
+
explicitLeaveContainer.style.display = "none";
|
|
2421
|
+
}
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
if (this.hasContactProtection) {
|
|
2425
|
+
banner.style.display = "none";
|
|
2426
|
+
if (explicitLeaveContainer) {
|
|
2427
|
+
explicitLeaveContainer.style.display = "flex";
|
|
2428
|
+
}
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
banner.style.display = "flex";
|
|
2432
|
+
if (explicitLeaveContainer) {
|
|
2433
|
+
explicitLeaveContainer.style.display = "none";
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
showToastMessage(message) {
|
|
2437
|
+
if (!this.modalElement) {
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
const existing = this.modalElement.querySelector(".lobby-contact-toast");
|
|
2441
|
+
if (existing) {
|
|
2442
|
+
existing.remove();
|
|
2443
|
+
}
|
|
2444
|
+
const toast = document.createElement("div");
|
|
2445
|
+
toast.className = "lobby-contact-toast";
|
|
2446
|
+
toast.textContent = message;
|
|
2447
|
+
this.modalElement.appendChild(toast);
|
|
2448
|
+
window.setTimeout(() => {
|
|
2449
|
+
toast.remove();
|
|
2450
|
+
}, 2400);
|
|
2451
|
+
}
|
|
2452
|
+
trackAnalyticsEvent(eventName, metadata) {
|
|
2453
|
+
if (!this.analyticsClient) {
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
this.analyticsClient
|
|
2457
|
+
.trackEvent(eventName, this.data.productId, Object.assign({ groupId: this.currentGroupId || this.data.groupId }, metadata))
|
|
2458
|
+
.catch((error) => {
|
|
2459
|
+
this.logger.warn(`[Analytics] Failed to track ${eventName}`, error);
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
async saveContactValue(contactValue, promptSource) {
|
|
2463
|
+
const trimmed = contactValue.trim();
|
|
2464
|
+
if (!trimmed || !this.apiClient) {
|
|
2465
|
+
return false;
|
|
2466
|
+
}
|
|
2467
|
+
const contactType = this.inferContactType(trimmed);
|
|
2468
|
+
if (!contactType) {
|
|
2469
|
+
window.alert(CONTACT_PROMPT_INVALID_INPUT);
|
|
2470
|
+
return false;
|
|
2471
|
+
}
|
|
2472
|
+
const payload = contactType === "phone"
|
|
2473
|
+
? { type: "phone", value: trimmed.replace(/\D/g, "") }
|
|
2474
|
+
: { type: "email", value: trimmed };
|
|
2475
|
+
const result = await this.apiClient.setContact(payload);
|
|
2476
|
+
if (!result.success) {
|
|
2477
|
+
this.logger.warn("Failed to save lobby contact", result.error);
|
|
2478
|
+
window.alert(CONTACT_PROMPT_SAVE_ERROR);
|
|
2479
|
+
return false;
|
|
2480
|
+
}
|
|
2481
|
+
this.persistContactProtectionState(true);
|
|
2482
|
+
this.trackAnalyticsEvent("CONTACT_INFO_SAVED", {
|
|
2483
|
+
promptSource,
|
|
2484
|
+
contactType,
|
|
2485
|
+
protectionState: "protected",
|
|
2486
|
+
});
|
|
2487
|
+
this.updateContactProtectionUI();
|
|
2488
|
+
this.showToastMessage(CONTACT_PROTECTED_TOAST);
|
|
2489
|
+
return true;
|
|
2490
|
+
}
|
|
2491
|
+
openContactProtectionPrompt(source) {
|
|
2492
|
+
if (typeof document === "undefined" || !this.modalElement) {
|
|
2493
|
+
return Promise.resolve("cancel");
|
|
2494
|
+
}
|
|
2495
|
+
this.clearContactPromptOverlay();
|
|
2496
|
+
this.trackAnalyticsEvent("CONTACT_PROMPT_SHOWN", {
|
|
2497
|
+
promptSource: source,
|
|
2498
|
+
protectionState: "unprotected",
|
|
2499
|
+
});
|
|
2500
|
+
return new Promise((resolve) => {
|
|
2501
|
+
var _a;
|
|
2502
|
+
const overlay = document.createElement("div");
|
|
2503
|
+
overlay.className = "lobby-contact-overlay";
|
|
2504
|
+
const card = document.createElement("div");
|
|
2505
|
+
card.className = "lobby-contact-card";
|
|
2506
|
+
const title = document.createElement("h3");
|
|
2507
|
+
title.className = "lobby-contact-title";
|
|
2508
|
+
title.textContent = CONTACT_PROMPT_TITLE;
|
|
2509
|
+
const subtitle = document.createElement("p");
|
|
2510
|
+
subtitle.className = "lobby-contact-subtitle";
|
|
2511
|
+
subtitle.textContent = CONTACT_PROMPT_SUBTITLE;
|
|
2512
|
+
const input = document.createElement("input");
|
|
2513
|
+
input.type = "text";
|
|
2514
|
+
input.className = "lobby-contact-input";
|
|
2515
|
+
input.placeholder = "Email or phone";
|
|
2516
|
+
input.autocomplete = "email";
|
|
2517
|
+
const actions = document.createElement("div");
|
|
2518
|
+
actions.className = "lobby-contact-actions";
|
|
2519
|
+
const primary = document.createElement("button");
|
|
2520
|
+
primary.type = "button";
|
|
2521
|
+
primary.className = "lobby-contact-primary";
|
|
2522
|
+
primary.textContent = CONTACT_PROMPT_PRIMARY_CTA;
|
|
2523
|
+
const secondary = document.createElement("button");
|
|
2524
|
+
secondary.type = "button";
|
|
2525
|
+
secondary.className = "lobby-contact-secondary";
|
|
2526
|
+
secondary.textContent = source === "leave-warning" ? "Cancel" : CONTACT_PROMPT_REMIND_CTA;
|
|
2527
|
+
const cleanupAndResolve = (result) => {
|
|
2528
|
+
this.clearContactPromptOverlay();
|
|
2529
|
+
resolve(result);
|
|
2530
|
+
};
|
|
2531
|
+
primary.addEventListener("click", async () => {
|
|
2532
|
+
const originalLabel = primary.textContent;
|
|
2533
|
+
primary.disabled = true;
|
|
2534
|
+
secondary.disabled = true;
|
|
2535
|
+
input.disabled = true;
|
|
2536
|
+
primary.classList.add("is-loading");
|
|
2537
|
+
primary.textContent = "Saving...";
|
|
2538
|
+
try {
|
|
2539
|
+
const saved = await this.saveContactValue(input.value, source);
|
|
2540
|
+
if (saved) {
|
|
2541
|
+
cleanupAndResolve("saved");
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
finally {
|
|
2545
|
+
if (this.contactPromptOverlay) {
|
|
2546
|
+
primary.disabled = false;
|
|
2547
|
+
secondary.disabled = false;
|
|
2548
|
+
input.disabled = false;
|
|
2549
|
+
primary.classList.remove("is-loading");
|
|
2550
|
+
primary.textContent = originalLabel || CONTACT_PROMPT_PRIMARY_CTA;
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
});
|
|
2554
|
+
secondary.addEventListener("click", () => {
|
|
2555
|
+
if (source !== "leave-warning") {
|
|
2556
|
+
this.trackAnalyticsEvent("CONTACT_PROMPT_DEFERRED", {
|
|
2557
|
+
promptSource: source,
|
|
2558
|
+
protectionState: "unprotected",
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
cleanupAndResolve(source === "leave-warning" ? "cancel" : "later");
|
|
2562
|
+
});
|
|
2563
|
+
overlay.addEventListener("click", (event) => {
|
|
2564
|
+
if (event.target === overlay) {
|
|
2565
|
+
if (source !== "leave-warning") {
|
|
2566
|
+
this.trackAnalyticsEvent("CONTACT_PROMPT_DEFERRED", {
|
|
2567
|
+
promptSource: source,
|
|
2568
|
+
protectionState: "unprotected",
|
|
2569
|
+
});
|
|
2570
|
+
}
|
|
2571
|
+
cleanupAndResolve(source === "leave-warning" ? "cancel" : "later");
|
|
2572
|
+
}
|
|
2573
|
+
});
|
|
2574
|
+
actions.appendChild(primary);
|
|
2575
|
+
actions.appendChild(secondary);
|
|
2576
|
+
card.appendChild(title);
|
|
2577
|
+
card.appendChild(subtitle);
|
|
2578
|
+
card.appendChild(input);
|
|
2579
|
+
card.appendChild(actions);
|
|
2580
|
+
overlay.appendChild(card);
|
|
2581
|
+
(_a = this.modalElement) === null || _a === void 0 ? void 0 : _a.appendChild(overlay);
|
|
2582
|
+
this.contactPromptOverlay = overlay;
|
|
2583
|
+
window.setTimeout(() => input.focus(), 0);
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2586
|
+
openUnprotectedLeaveWarning() {
|
|
2587
|
+
if (typeof document === "undefined" || !this.modalElement) {
|
|
2588
|
+
return Promise.resolve("cancel");
|
|
2589
|
+
}
|
|
2590
|
+
this.clearContactPromptOverlay();
|
|
2591
|
+
return new Promise((resolve) => {
|
|
2592
|
+
var _a;
|
|
2593
|
+
const overlay = document.createElement("div");
|
|
2594
|
+
overlay.className = "lobby-contact-overlay";
|
|
2595
|
+
const card = document.createElement("div");
|
|
2596
|
+
card.className = "lobby-contact-card";
|
|
2597
|
+
const title = document.createElement("h3");
|
|
2598
|
+
title.className = "lobby-contact-title";
|
|
2599
|
+
title.textContent = LEAVE_WARNING_TITLE;
|
|
2600
|
+
const subtitle = document.createElement("p");
|
|
2601
|
+
subtitle.className = "lobby-contact-subtitle";
|
|
2602
|
+
subtitle.textContent = LEAVE_WARNING_SUBTITLE;
|
|
2603
|
+
const actions = document.createElement("div");
|
|
2604
|
+
actions.className = "lobby-contact-actions";
|
|
2605
|
+
const addContactBtn = document.createElement("button");
|
|
2606
|
+
addContactBtn.type = "button";
|
|
2607
|
+
addContactBtn.className = "lobby-contact-primary";
|
|
2608
|
+
addContactBtn.textContent = "Add contact first";
|
|
2609
|
+
const leaveBtn = document.createElement("button");
|
|
2610
|
+
leaveBtn.type = "button";
|
|
2611
|
+
leaveBtn.className = "lobby-contact-danger";
|
|
2612
|
+
leaveBtn.textContent = "Leave anyway";
|
|
2613
|
+
const cancelBtn = document.createElement("button");
|
|
2614
|
+
cancelBtn.type = "button";
|
|
2615
|
+
cancelBtn.className = "lobby-contact-secondary";
|
|
2616
|
+
cancelBtn.textContent = "Cancel";
|
|
2617
|
+
const cleanup = (result) => {
|
|
2618
|
+
this.clearContactPromptOverlay();
|
|
2619
|
+
resolve(result);
|
|
2620
|
+
};
|
|
2621
|
+
addContactBtn.addEventListener("click", () => cleanup("add_contact"));
|
|
2622
|
+
leaveBtn.addEventListener("click", () => cleanup("leave"));
|
|
2623
|
+
cancelBtn.addEventListener("click", () => cleanup("cancel"));
|
|
2624
|
+
overlay.addEventListener("click", (event) => {
|
|
2625
|
+
if (event.target === overlay) {
|
|
2626
|
+
cleanup("cancel");
|
|
2627
|
+
}
|
|
2628
|
+
});
|
|
2629
|
+
actions.appendChild(addContactBtn);
|
|
2630
|
+
actions.appendChild(leaveBtn);
|
|
2631
|
+
actions.appendChild(cancelBtn);
|
|
2632
|
+
card.appendChild(title);
|
|
2633
|
+
card.appendChild(subtitle);
|
|
2634
|
+
card.appendChild(actions);
|
|
2635
|
+
overlay.appendChild(card);
|
|
2636
|
+
(_a = this.modalElement) === null || _a === void 0 ? void 0 : _a.appendChild(overlay);
|
|
2637
|
+
this.contactPromptOverlay = overlay;
|
|
2638
|
+
});
|
|
2639
|
+
}
|
|
2640
|
+
scheduleInitialContactPrompt() {
|
|
2641
|
+
if (!this.modalElement ||
|
|
2642
|
+
!this.shouldRunLeaveFlow() ||
|
|
2643
|
+
this.hasContactProtection ||
|
|
2644
|
+
!this.apiClient) {
|
|
2645
|
+
return;
|
|
2646
|
+
}
|
|
2647
|
+
this.clearInitialContactPromptTimer();
|
|
2648
|
+
this.initialContactPromptTimer = window.setTimeout(async () => {
|
|
2649
|
+
this.initialContactPromptTimer = null;
|
|
2650
|
+
if (!this.modalElement || this.hasContactProtection) {
|
|
2651
|
+
return;
|
|
2652
|
+
}
|
|
2653
|
+
await this.openContactProtectionPrompt("initial");
|
|
2654
|
+
this.updateContactProtectionUI();
|
|
2655
|
+
}, 2000);
|
|
2656
|
+
}
|
|
2657
|
+
async leaveGroupExplicitly() {
|
|
2658
|
+
var _a, _b;
|
|
2659
|
+
if (!this.apiClient || !this.currentGroupId || this.leaveFlowInProgress) {
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
this.leaveFlowInProgress = true;
|
|
2663
|
+
try {
|
|
2664
|
+
this.showBlockingLoader("Leaving group...");
|
|
2665
|
+
const leaveResult = await this.apiClient.leaveGroup(this.currentGroupId, {
|
|
2666
|
+
leave_source: "close_icon",
|
|
2667
|
+
leave_reason: "voluntary_with_contact",
|
|
2668
|
+
});
|
|
2669
|
+
if (!leaveResult.success) {
|
|
2670
|
+
this.logger.warn("Explicit leave group failed", leaveResult.error);
|
|
2671
|
+
this.hideBlockingLoader();
|
|
2672
|
+
window.alert("Could not leave the group right now. Please try again.");
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
this.leaveSignalSent = true;
|
|
2676
|
+
this.persistLastLeftGroup(((_b = (_a = leaveResult.data) === null || _a === void 0 ? void 0 : _a.group) === null || _b === void 0 ? void 0 : _b.id) || this.currentGroupId);
|
|
2677
|
+
this.trackAnalyticsEvent("PROTECTED_USER_EXPLICIT_LEAVE", {
|
|
2678
|
+
leaveSource: "close_icon",
|
|
2679
|
+
leaveReason: "voluntary_with_contact",
|
|
2680
|
+
protectionState: "protected",
|
|
2681
|
+
});
|
|
2682
|
+
this.close({ skipLeaveFlow: true });
|
|
2683
|
+
}
|
|
2684
|
+
finally {
|
|
2685
|
+
this.hideBlockingLoader();
|
|
2686
|
+
this.leaveFlowInProgress = false;
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2286
2689
|
/**
|
|
2287
2690
|
* Derive lock state from data so UI reflects completion
|
|
2288
2691
|
*/
|
|
@@ -2599,6 +3002,7 @@ class LobbyModal {
|
|
|
2599
3002
|
* Create connected section (subtitle + link + share)
|
|
2600
3003
|
*/
|
|
2601
3004
|
createConnectedSection() {
|
|
3005
|
+
var _a, _b;
|
|
2602
3006
|
const connectedSection = document.createElement("div");
|
|
2603
3007
|
connectedSection.className = "connected-section";
|
|
2604
3008
|
connectedSection.id = "lobbyConnectedSection";
|
|
@@ -2609,18 +3013,59 @@ class LobbyModal {
|
|
|
2609
3013
|
subtitle.id = "lobbySubtitleText";
|
|
2610
3014
|
subtitle.textContent = titleContent.subtitle;
|
|
2611
3015
|
connectedSection.appendChild(subtitle);
|
|
3016
|
+
const contactBanner = document.createElement("div");
|
|
3017
|
+
contactBanner.className = "lobby-contact-protection-banner";
|
|
3018
|
+
contactBanner.id = "lobbyContactProtectionBanner";
|
|
3019
|
+
const contactCopy = document.createElement("p");
|
|
3020
|
+
contactCopy.className = "lobby-contact-protection-copy";
|
|
3021
|
+
contactCopy.textContent =
|
|
3022
|
+
"Spot not protected. Add contact so we can help recover your group membership.";
|
|
3023
|
+
const contactAction = document.createElement("button");
|
|
3024
|
+
contactAction.type = "button";
|
|
3025
|
+
contactAction.className = "lobby-contact-protection-btn";
|
|
3026
|
+
contactAction.textContent = "Add contact";
|
|
3027
|
+
contactAction.addEventListener("click", async () => {
|
|
3028
|
+
await this.openContactProtectionPrompt("banner");
|
|
3029
|
+
this.updateContactProtectionUI();
|
|
3030
|
+
});
|
|
3031
|
+
contactBanner.appendChild(contactCopy);
|
|
3032
|
+
contactBanner.appendChild(contactAction);
|
|
3033
|
+
connectedSection.appendChild(contactBanner);
|
|
3034
|
+
const protectedLeaveContainer = document.createElement("div");
|
|
3035
|
+
protectedLeaveContainer.className = "lobby-protected-leave-container";
|
|
3036
|
+
protectedLeaveContainer.id = "lobbyProtectedLeaveContainer";
|
|
3037
|
+
const protectedLeaveText = document.createElement("p");
|
|
3038
|
+
protectedLeaveText.className = "lobby-protected-leave-copy";
|
|
3039
|
+
protectedLeaveText.textContent = "You are protected and still part of this group.";
|
|
3040
|
+
const protectedLeaveAction = document.createElement("button");
|
|
3041
|
+
protectedLeaveAction.type = "button";
|
|
3042
|
+
protectedLeaveAction.className = "lobby-protected-leave-btn";
|
|
3043
|
+
protectedLeaveAction.textContent = "Leave group";
|
|
3044
|
+
protectedLeaveAction.addEventListener("click", () => {
|
|
3045
|
+
void this.leaveGroupExplicitly();
|
|
3046
|
+
});
|
|
3047
|
+
protectedLeaveContainer.appendChild(protectedLeaveText);
|
|
3048
|
+
protectedLeaveContainer.appendChild(protectedLeaveAction);
|
|
3049
|
+
connectedSection.appendChild(protectedLeaveContainer);
|
|
2612
3050
|
// Check if group is fulfilled and has offline redemption
|
|
2613
3051
|
const isComplete = !this.computeIsLocked(this.data);
|
|
2614
3052
|
const hasOfflineRedemption = isComplete &&
|
|
2615
3053
|
this.data.offlineRedemption &&
|
|
2616
|
-
isValidOfflineRedemption(this.data.offlineRedemption)
|
|
3054
|
+
isValidOfflineRedemption(this.data.offlineRedemption) &&
|
|
3055
|
+
((_a = this.data.redemptionMethod) !== null && _a !== void 0 ? _a : "online") !== "online";
|
|
2617
3056
|
if (hasOfflineRedemption) {
|
|
2618
|
-
//
|
|
3057
|
+
// Complete + offline/both: show offline redemption section
|
|
2619
3058
|
const offlineSection = this.createOfflineRedemptionSection(this.data.offlineRedemption);
|
|
2620
3059
|
connectedSection.appendChild(offlineSection);
|
|
2621
3060
|
}
|
|
3061
|
+
else if (isComplete && ((_b = this.data.redemptionMethod) !== null && _b !== void 0 ? _b : "online") !== "offline") {
|
|
3062
|
+
// Complete + online/both (no offline redemption data): show only checkout CTA
|
|
3063
|
+
const checkoutBtn = this.createOnlineCheckoutButton();
|
|
3064
|
+
connectedSection.appendChild(checkoutBtn);
|
|
3065
|
+
this.injectOfflineRedemptionStyles();
|
|
3066
|
+
}
|
|
2622
3067
|
else {
|
|
2623
|
-
//
|
|
3068
|
+
// Group not yet complete: show link and share
|
|
2624
3069
|
const linkShareContainer = document.createElement("div");
|
|
2625
3070
|
linkShareContainer.className = "link-share-container";
|
|
2626
3071
|
const linkWp = this.createLinkSection();
|
|
@@ -2664,6 +3109,23 @@ class LobbyModal {
|
|
|
2664
3109
|
linkShareWrapper.appendChild(shareBtnInner);
|
|
2665
3110
|
return linkShareWrapper;
|
|
2666
3111
|
}
|
|
3112
|
+
/**
|
|
3113
|
+
* Create a standalone "Checkout Online" button for the link/share section
|
|
3114
|
+
*/
|
|
3115
|
+
createOnlineCheckoutButton() {
|
|
3116
|
+
const btn = document.createElement("button");
|
|
3117
|
+
btn.className = "offline-online-checkout-btn lobby-online-checkout-btn";
|
|
3118
|
+
btn.textContent = "Checkout Online";
|
|
3119
|
+
btn.style.marginTop = "10px";
|
|
3120
|
+
btn.style.width = "100%";
|
|
3121
|
+
btn.addEventListener("click", () => {
|
|
3122
|
+
this.close();
|
|
3123
|
+
window.setTimeout(() => {
|
|
3124
|
+
this.triggerContinueToCheckout();
|
|
3125
|
+
}, 0);
|
|
3126
|
+
});
|
|
3127
|
+
return btn;
|
|
3128
|
+
}
|
|
2667
3129
|
/**
|
|
2668
3130
|
* Create offline redemption section
|
|
2669
3131
|
*/
|
|
@@ -2728,14 +3190,16 @@ class LobbyModal {
|
|
|
2728
3190
|
onlineCheckoutBtn.className = "offline-online-checkout-btn";
|
|
2729
3191
|
onlineCheckoutBtn.textContent = "Checkout Online";
|
|
2730
3192
|
onlineCheckoutBtn.addEventListener("click", () => {
|
|
2731
|
-
this.close();
|
|
3193
|
+
this.close({ skipLeaveFlow: true });
|
|
2732
3194
|
// Ensure modal closes before triggering checkout intent
|
|
2733
3195
|
window.setTimeout(() => {
|
|
2734
3196
|
this.triggerContinueToCheckout();
|
|
2735
3197
|
}, 0);
|
|
2736
3198
|
});
|
|
2737
3199
|
actionsRow.appendChild(downloadQRBtn);
|
|
2738
|
-
|
|
3200
|
+
if (this.data.redemptionMethod !== "offline") {
|
|
3201
|
+
actionsRow.appendChild(onlineCheckoutBtn);
|
|
3202
|
+
}
|
|
2739
3203
|
section.appendChild(topRow);
|
|
2740
3204
|
section.appendChild(expiryInfo);
|
|
2741
3205
|
section.appendChild(actionsRow);
|
|
@@ -3641,6 +4105,10 @@ class LobbyModal {
|
|
|
3641
4105
|
this.logger.info(`Lobby modal opened for product: ${this.data.productId}`);
|
|
3642
4106
|
// Set up keyboard accessibility (focus trap, ESC to close)
|
|
3643
4107
|
this.setupKeyboardAccessibility();
|
|
4108
|
+
// Register lifecycle-based dropoff handling for browser/tab close.
|
|
4109
|
+
this.registerLifecycleLeaveHandlers();
|
|
4110
|
+
this.updateContactProtectionUI();
|
|
4111
|
+
this.scheduleInitialContactPrompt();
|
|
3644
4112
|
});
|
|
3645
4113
|
}
|
|
3646
4114
|
/**
|
|
@@ -3706,19 +4174,166 @@ class LobbyModal {
|
|
|
3706
4174
|
this.keyboardHandler = null;
|
|
3707
4175
|
}
|
|
3708
4176
|
}
|
|
4177
|
+
registerLifecycleLeaveHandlers() {
|
|
4178
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
4179
|
+
return;
|
|
4180
|
+
}
|
|
4181
|
+
if (!this.shouldRunLeaveFlow()) {
|
|
4182
|
+
return;
|
|
4183
|
+
}
|
|
4184
|
+
if (!this.beforeUnloadHandler) {
|
|
4185
|
+
this.beforeUnloadHandler = () => {
|
|
4186
|
+
this.triggerBestEffortLeave("browser_close");
|
|
4187
|
+
};
|
|
4188
|
+
window.addEventListener("beforeunload", this.beforeUnloadHandler);
|
|
4189
|
+
}
|
|
4190
|
+
if (!this.pageHideHandler) {
|
|
4191
|
+
this.pageHideHandler = () => {
|
|
4192
|
+
this.triggerBestEffortLeave("browser_close");
|
|
4193
|
+
};
|
|
4194
|
+
window.addEventListener("pagehide", this.pageHideHandler);
|
|
4195
|
+
}
|
|
4196
|
+
if (!this.visibilityChangeHandler) {
|
|
4197
|
+
this.visibilityChangeHandler = () => {
|
|
4198
|
+
if (document.visibilityState === "hidden") {
|
|
4199
|
+
this.triggerBestEffortLeave("browser_close");
|
|
4200
|
+
}
|
|
4201
|
+
};
|
|
4202
|
+
document.addEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
unregisterLifecycleLeaveHandlers() {
|
|
4206
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
4207
|
+
return;
|
|
4208
|
+
}
|
|
4209
|
+
if (this.beforeUnloadHandler) {
|
|
4210
|
+
window.removeEventListener("beforeunload", this.beforeUnloadHandler);
|
|
4211
|
+
this.beforeUnloadHandler = null;
|
|
4212
|
+
}
|
|
4213
|
+
if (this.pageHideHandler) {
|
|
4214
|
+
window.removeEventListener("pagehide", this.pageHideHandler);
|
|
4215
|
+
this.pageHideHandler = null;
|
|
4216
|
+
}
|
|
4217
|
+
if (this.visibilityChangeHandler) {
|
|
4218
|
+
document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
4219
|
+
this.visibilityChangeHandler = null;
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
triggerBestEffortLeave(source) {
|
|
4223
|
+
if (this.leaveSignalSent || this.leaveFlowInProgress) {
|
|
4224
|
+
return;
|
|
4225
|
+
}
|
|
4226
|
+
if (this.hasContactProtection) {
|
|
4227
|
+
return;
|
|
4228
|
+
}
|
|
4229
|
+
if (!this.apiClient || !this.currentGroupId || !this.shouldRunLeaveFlow()) {
|
|
4230
|
+
return;
|
|
4231
|
+
}
|
|
4232
|
+
this.leaveSignalSent = true;
|
|
4233
|
+
this.apiClient.leaveGroupBestEffort(this.currentGroupId, {
|
|
4234
|
+
leave_source: source,
|
|
4235
|
+
leave_reason: "voluntary_no_contact",
|
|
4236
|
+
});
|
|
4237
|
+
}
|
|
4238
|
+
shouldRunLeaveFlow() {
|
|
4239
|
+
if (!this.apiClient || !this.currentGroupId) {
|
|
4240
|
+
return false;
|
|
4241
|
+
}
|
|
4242
|
+
// Dropoff handling applies only while group is still active/forming.
|
|
4243
|
+
return this.data.status !== "complete";
|
|
4244
|
+
}
|
|
4245
|
+
inferContactType(value) {
|
|
4246
|
+
const trimmed = value.trim();
|
|
4247
|
+
if (!trimmed)
|
|
4248
|
+
return null;
|
|
4249
|
+
if (trimmed.includes("@")) {
|
|
4250
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
4251
|
+
return emailRegex.test(trimmed) ? "email" : null;
|
|
4252
|
+
}
|
|
4253
|
+
const digitsOnly = trimmed.replace(/\D/g, "");
|
|
4254
|
+
return digitsOnly.length >= 10 && digitsOnly.length <= 15 ? "phone" : null;
|
|
4255
|
+
}
|
|
4256
|
+
async runLeaveFlowAndClose() {
|
|
4257
|
+
var _a, _b;
|
|
4258
|
+
if (this.leaveFlowInProgress)
|
|
4259
|
+
return;
|
|
4260
|
+
this.leaveFlowInProgress = true;
|
|
4261
|
+
try {
|
|
4262
|
+
if (this.hasContactProtection) {
|
|
4263
|
+
this.trackAnalyticsEvent("LOBBY_CLOSED_PROTECTED", {
|
|
4264
|
+
leaveSource: "close_icon",
|
|
4265
|
+
leaveReason: "stay_in_group",
|
|
4266
|
+
protectionState: "protected",
|
|
4267
|
+
});
|
|
4268
|
+
this.close({ skipLeaveFlow: true });
|
|
4269
|
+
return;
|
|
4270
|
+
}
|
|
4271
|
+
let leaveReason = "voluntary_no_contact";
|
|
4272
|
+
const leaveDecision = await this.openUnprotectedLeaveWarning();
|
|
4273
|
+
if (leaveDecision === "cancel") {
|
|
4274
|
+
return;
|
|
4275
|
+
}
|
|
4276
|
+
if (leaveDecision === "add_contact") {
|
|
4277
|
+
const promptResult = await this.openContactProtectionPrompt("leave-warning");
|
|
4278
|
+
if (promptResult === "saved") {
|
|
4279
|
+
this.updateContactProtectionUI();
|
|
4280
|
+
return;
|
|
4281
|
+
}
|
|
4282
|
+
else {
|
|
4283
|
+
return;
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
if (this.apiClient && this.currentGroupId) {
|
|
4287
|
+
this.showBlockingLoader("Leaving group...");
|
|
4288
|
+
const leaveResult = await this.apiClient.leaveGroup(this.currentGroupId, {
|
|
4289
|
+
leave_source: "close_icon",
|
|
4290
|
+
leave_reason: leaveReason,
|
|
4291
|
+
});
|
|
4292
|
+
if (!leaveResult.success) {
|
|
4293
|
+
this.logger.warn("Leave group failed", leaveResult.error);
|
|
4294
|
+
this.hideBlockingLoader();
|
|
4295
|
+
window.alert("Could not leave the group right now. Please try again.");
|
|
4296
|
+
return;
|
|
4297
|
+
}
|
|
4298
|
+
this.leaveSignalSent = true;
|
|
4299
|
+
this.persistLastLeftGroup(((_b = (_a = leaveResult.data) === null || _a === void 0 ? void 0 : _a.group) === null || _b === void 0 ? void 0 : _b.id) || this.currentGroupId);
|
|
4300
|
+
this.trackAnalyticsEvent("LOBBY_LEFT_UNPROTECTED", {
|
|
4301
|
+
leaveSource: "close_icon",
|
|
4302
|
+
leaveReason,
|
|
4303
|
+
protectionState: "unprotected",
|
|
4304
|
+
});
|
|
4305
|
+
}
|
|
4306
|
+
this.close({ skipLeaveFlow: true });
|
|
4307
|
+
}
|
|
4308
|
+
finally {
|
|
4309
|
+
this.hideBlockingLoader();
|
|
4310
|
+
this.leaveFlowInProgress = false;
|
|
4311
|
+
}
|
|
4312
|
+
}
|
|
3709
4313
|
/**
|
|
3710
4314
|
* Close the modal
|
|
3711
4315
|
*/
|
|
3712
|
-
close() {
|
|
4316
|
+
close(options = {}) {
|
|
3713
4317
|
if (!this.modalElement) {
|
|
3714
4318
|
this.logger.warn("Modal not open");
|
|
3715
4319
|
return;
|
|
3716
4320
|
}
|
|
4321
|
+
if (!options.skipLeaveFlow && this.shouldRunLeaveFlow()) {
|
|
4322
|
+
void this.runLeaveFlowAndClose();
|
|
4323
|
+
return;
|
|
4324
|
+
}
|
|
3717
4325
|
// Stop timers and animations
|
|
3718
4326
|
this.stopTimer();
|
|
3719
4327
|
this.stopActivityAnimation();
|
|
3720
4328
|
// Remove keyboard event listeners
|
|
3721
4329
|
this.removeKeyboardAccessibility();
|
|
4330
|
+
// Remove lifecycle handlers for dropoff detection
|
|
4331
|
+
this.unregisterLifecycleLeaveHandlers();
|
|
4332
|
+
this.clearInitialContactPromptTimer();
|
|
4333
|
+
this.clearContactPromptOverlay();
|
|
4334
|
+
this.hideBlockingLoader();
|
|
4335
|
+
// Remove socket listeners to avoid duplicate handlers on reopen
|
|
4336
|
+
this.unsubscribeFromSocketEvents();
|
|
3722
4337
|
// Remove modal from DOM
|
|
3723
4338
|
document.body.removeChild(this.modalElement);
|
|
3724
4339
|
this.modalElement = null;
|
|
@@ -3831,29 +4446,47 @@ class LobbyModal {
|
|
|
3831
4446
|
* Update offline redemption visibility when group is fulfilled
|
|
3832
4447
|
*/
|
|
3833
4448
|
updateOfflineRedemptionVisibility() {
|
|
4449
|
+
var _a, _b;
|
|
3834
4450
|
const connectedSection = document.getElementById("lobbyConnectedSection");
|
|
3835
4451
|
if (!connectedSection)
|
|
3836
4452
|
return;
|
|
3837
4453
|
const isComplete = !this.computeIsLocked(this.data);
|
|
3838
|
-
const hasOfflineRedemption = this.data.offlineRedemption &&
|
|
4454
|
+
const hasOfflineRedemption = this.data.offlineRedemption &&
|
|
4455
|
+
isValidOfflineRedemption(this.data.offlineRedemption) &&
|
|
4456
|
+
((_a = this.data.redemptionMethod) !== null && _a !== void 0 ? _a : "online") !== "online";
|
|
3839
4457
|
// Get existing elements
|
|
3840
4458
|
const existingOffline = connectedSection.querySelector(".offline-redemption-section");
|
|
3841
4459
|
const existingLink = connectedSection.querySelector(".link-share-container");
|
|
4460
|
+
const existingCheckoutBtn = connectedSection.querySelector(".lobby-online-checkout-btn");
|
|
3842
4461
|
if (isComplete && hasOfflineRedemption) {
|
|
3843
|
-
//
|
|
3844
|
-
if (existingLink)
|
|
4462
|
+
// Complete + offline/both: show offline section, remove link/share and standalone checkout btn
|
|
4463
|
+
if (existingLink)
|
|
3845
4464
|
existingLink.remove();
|
|
3846
|
-
|
|
4465
|
+
if (existingCheckoutBtn)
|
|
4466
|
+
existingCheckoutBtn.remove();
|
|
3847
4467
|
if (!existingOffline) {
|
|
3848
4468
|
const offlineSection = this.createOfflineRedemptionSection(this.data.offlineRedemption);
|
|
3849
4469
|
connectedSection.appendChild(offlineSection);
|
|
3850
4470
|
}
|
|
3851
4471
|
}
|
|
3852
|
-
else {
|
|
3853
|
-
//
|
|
3854
|
-
if (
|
|
4472
|
+
else if (isComplete && ((_b = this.data.redemptionMethod) !== null && _b !== void 0 ? _b : "online") !== "offline") {
|
|
4473
|
+
// Complete + online/both (no offline data): show only checkout CTA, remove link/share
|
|
4474
|
+
if (existingLink)
|
|
4475
|
+
existingLink.remove();
|
|
4476
|
+
if (existingOffline)
|
|
3855
4477
|
existingOffline.remove();
|
|
4478
|
+
if (!existingCheckoutBtn) {
|
|
4479
|
+
const checkoutBtn = this.createOnlineCheckoutButton();
|
|
4480
|
+
connectedSection.appendChild(checkoutBtn);
|
|
4481
|
+
this.injectOfflineRedemptionStyles();
|
|
3856
4482
|
}
|
|
4483
|
+
}
|
|
4484
|
+
else {
|
|
4485
|
+
// Group not yet complete: show link/share, remove offline and checkout btn
|
|
4486
|
+
if (existingOffline)
|
|
4487
|
+
existingOffline.remove();
|
|
4488
|
+
if (existingCheckoutBtn)
|
|
4489
|
+
existingCheckoutBtn.remove();
|
|
3857
4490
|
if (!existingLink) {
|
|
3858
4491
|
const linkShareContainer = document.createElement("div");
|
|
3859
4492
|
linkShareContainer.className = "link-share-container";
|
|
@@ -3887,9 +4520,20 @@ class LobbyModal {
|
|
|
3887
4520
|
}
|
|
3888
4521
|
window.addEventListener("group:fulfilled", this.handleSocketGroupUpdate);
|
|
3889
4522
|
window.addEventListener("group:member:joined", this.handleSocketGroupUpdate);
|
|
4523
|
+
window.addEventListener("group:member:left", this.handleSocketGroupUpdate);
|
|
3890
4524
|
window.addEventListener("group:created", this.handleSocketGroupUpdate);
|
|
3891
4525
|
this.socketListenerRegistered = true;
|
|
3892
4526
|
}
|
|
4527
|
+
unsubscribeFromSocketEvents() {
|
|
4528
|
+
if (typeof window === "undefined" || !this.socketListenerRegistered) {
|
|
4529
|
+
return;
|
|
4530
|
+
}
|
|
4531
|
+
window.removeEventListener("group:fulfilled", this.handleSocketGroupUpdate);
|
|
4532
|
+
window.removeEventListener("group:member:joined", this.handleSocketGroupUpdate);
|
|
4533
|
+
window.removeEventListener("group:member:left", this.handleSocketGroupUpdate);
|
|
4534
|
+
window.removeEventListener("group:created", this.handleSocketGroupUpdate);
|
|
4535
|
+
this.socketListenerRegistered = false;
|
|
4536
|
+
}
|
|
3893
4537
|
/**
|
|
3894
4538
|
* Create activity item from socket event data
|
|
3895
4539
|
*/
|
|
@@ -3920,6 +4564,10 @@ class LobbyModal {
|
|
|
3920
4564
|
emoji = "🛒";
|
|
3921
4565
|
action = "started a new group";
|
|
3922
4566
|
break;
|
|
4567
|
+
case "group:member:left":
|
|
4568
|
+
emoji = "🚪";
|
|
4569
|
+
action = "left the group";
|
|
4570
|
+
break;
|
|
3923
4571
|
default:
|
|
3924
4572
|
emoji = "⚡";
|
|
3925
4573
|
action = "activity in group";
|
|
@@ -4392,6 +5040,9 @@ var WidgetState;
|
|
|
4392
5040
|
WidgetState["LOADED"] = "loaded";
|
|
4393
5041
|
WidgetState["ERROR"] = "error";
|
|
4394
5042
|
})(WidgetState || (WidgetState = {}));
|
|
5043
|
+
const RECOVERY_TOAST_REJOINED = "Rejoined your previous group.";
|
|
5044
|
+
const RECOVERY_TOAST_NEXT_AVAILABLE = "Your previous group was full, so we joined you to the next available group.";
|
|
5045
|
+
const RECOVERY_TOAST_CREATED_NEW = "Your previous group was no longer available, so we created a fresh group for you.";
|
|
4395
5046
|
/**
|
|
4396
5047
|
* WidgetRoot handles rendering of the CoBuy widget into a DOM container
|
|
4397
5048
|
*/
|
|
@@ -4423,6 +5074,7 @@ class WidgetRoot {
|
|
|
4423
5074
|
this.groupExpiryRefreshTriggered = false; // Track if expiry refresh already triggered for current group
|
|
4424
5075
|
this.offlineRedemption = null;
|
|
4425
5076
|
this.offlineRedemptionModal = null;
|
|
5077
|
+
this.campaignRedemptionMethod = "online";
|
|
4426
5078
|
this.isRendering = false;
|
|
4427
5079
|
this.renderPromise = null;
|
|
4428
5080
|
this.liveRegionAnnouncer = null;
|
|
@@ -4432,6 +5084,7 @@ class WidgetRoot {
|
|
|
4432
5084
|
this.renderDebounceTimer = null;
|
|
4433
5085
|
this.pendingRenderOptions = null;
|
|
4434
5086
|
this.RENDER_DEBOUNCE_MS = 300; // 300ms debounce for rapid re-renders
|
|
5087
|
+
this.LAST_LEFT_GROUP_PREFIX = "cobuy_last_left_group";
|
|
4435
5088
|
/** Handle backend fulfillment notifications */
|
|
4436
5089
|
this.handleGroupFulfilledEvent = (event) => {
|
|
4437
5090
|
const detail = event.detail;
|
|
@@ -4475,6 +5128,80 @@ class WidgetRoot {
|
|
|
4475
5128
|
this.handleCTAClick(productId);
|
|
4476
5129
|
}, 300); // 300ms debounce to prevent double-clicks
|
|
4477
5130
|
}
|
|
5131
|
+
getRecoveryStorageKey(productId) {
|
|
5132
|
+
var _a;
|
|
5133
|
+
const sessionId = (_a = this.apiClient) === null || _a === void 0 ? void 0 : _a.getSessionId();
|
|
5134
|
+
if (!sessionId || !productId) {
|
|
5135
|
+
return null;
|
|
5136
|
+
}
|
|
5137
|
+
return `${this.LAST_LEFT_GROUP_PREFIX}:${sessionId}:${productId}`;
|
|
5138
|
+
}
|
|
5139
|
+
getStoredRecoveryGroupId(productId) {
|
|
5140
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
5141
|
+
return null;
|
|
5142
|
+
}
|
|
5143
|
+
const key = this.getRecoveryStorageKey(productId);
|
|
5144
|
+
if (!key) {
|
|
5145
|
+
return null;
|
|
5146
|
+
}
|
|
5147
|
+
try {
|
|
5148
|
+
const raw = window.localStorage.getItem(key);
|
|
5149
|
+
if (!raw) {
|
|
5150
|
+
return null;
|
|
5151
|
+
}
|
|
5152
|
+
const parsed = JSON.parse(raw);
|
|
5153
|
+
if (!(parsed === null || parsed === void 0 ? void 0 : parsed.groupId) || parsed.productId !== productId) {
|
|
5154
|
+
return null;
|
|
5155
|
+
}
|
|
5156
|
+
return parsed.groupId;
|
|
5157
|
+
}
|
|
5158
|
+
catch (_a) {
|
|
5159
|
+
return null;
|
|
5160
|
+
}
|
|
5161
|
+
}
|
|
5162
|
+
clearStoredRecoveryGroupId(productId) {
|
|
5163
|
+
if (typeof window === "undefined" || !window.localStorage) {
|
|
5164
|
+
return;
|
|
5165
|
+
}
|
|
5166
|
+
const key = this.getRecoveryStorageKey(productId);
|
|
5167
|
+
if (!key) {
|
|
5168
|
+
return;
|
|
5169
|
+
}
|
|
5170
|
+
try {
|
|
5171
|
+
window.localStorage.removeItem(key);
|
|
5172
|
+
}
|
|
5173
|
+
catch (_a) {
|
|
5174
|
+
// Ignore storage issues
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
5177
|
+
showRecoveryToast(message) {
|
|
5178
|
+
if (typeof document === "undefined") {
|
|
5179
|
+
return;
|
|
5180
|
+
}
|
|
5181
|
+
const existing = document.getElementById("cobuy-recovery-toast");
|
|
5182
|
+
if (existing) {
|
|
5183
|
+
existing.remove();
|
|
5184
|
+
}
|
|
5185
|
+
const toast = document.createElement("div");
|
|
5186
|
+
toast.id = "cobuy-recovery-toast";
|
|
5187
|
+
toast.style.position = "fixed";
|
|
5188
|
+
toast.style.left = "50%";
|
|
5189
|
+
toast.style.bottom = "24px";
|
|
5190
|
+
toast.style.transform = "translateX(-50%)";
|
|
5191
|
+
toast.style.background = "#0f172a";
|
|
5192
|
+
toast.style.color = "#fff";
|
|
5193
|
+
toast.style.padding = "10px 14px";
|
|
5194
|
+
toast.style.borderRadius = "10px";
|
|
5195
|
+
toast.style.fontSize = "13px";
|
|
5196
|
+
toast.style.fontWeight = "600";
|
|
5197
|
+
toast.style.zIndex = "10040";
|
|
5198
|
+
toast.style.boxShadow = "0 8px 20px rgba(0,0,0,0.24)";
|
|
5199
|
+
toast.textContent = message;
|
|
5200
|
+
document.body.appendChild(toast);
|
|
5201
|
+
window.setTimeout(() => {
|
|
5202
|
+
toast.remove();
|
|
5203
|
+
}, 2800);
|
|
5204
|
+
}
|
|
4478
5205
|
/** Subscribe once to backend socket events routed through the host page */
|
|
4479
5206
|
subscribeToSocketEvents() {
|
|
4480
5207
|
if (typeof window === "undefined" || this.socketListenerRegistered) {
|
|
@@ -4482,6 +5209,7 @@ class WidgetRoot {
|
|
|
4482
5209
|
}
|
|
4483
5210
|
window.addEventListener("group:fulfilled", this.handleGroupFulfilledEvent);
|
|
4484
5211
|
window.addEventListener("group:member:joined", this.handleGroupUpdatedEvent);
|
|
5212
|
+
window.addEventListener("group:member:left", this.handleGroupUpdatedEvent);
|
|
4485
5213
|
window.addEventListener("group:created", this.handleGroupUpdatedEvent);
|
|
4486
5214
|
this.socketListenerRegistered = true;
|
|
4487
5215
|
}
|
|
@@ -4518,6 +5246,9 @@ class WidgetRoot {
|
|
|
4518
5246
|
isValidOfflineRedemption(groupData.offline_redemption)) {
|
|
4519
5247
|
this.offlineRedemption = groupData.offline_redemption;
|
|
4520
5248
|
}
|
|
5249
|
+
if (groupData.campaign_redemption_method) {
|
|
5250
|
+
this.campaignRedemptionMethod = groupData.campaign_redemption_method;
|
|
5251
|
+
}
|
|
4521
5252
|
}
|
|
4522
5253
|
}
|
|
4523
5254
|
else {
|
|
@@ -4613,7 +5344,7 @@ class WidgetRoot {
|
|
|
4613
5344
|
}
|
|
4614
5345
|
/** Persist fulfilled state, emit callback, and refresh UI */
|
|
4615
5346
|
processGroupFulfilled(eventData) {
|
|
4616
|
-
var _a, _b, _c, _d;
|
|
5347
|
+
var _a, _b, _c, _d, _e;
|
|
4617
5348
|
this.groupFulfilled = true;
|
|
4618
5349
|
// Extract reward from new event structure or fallback to legacy
|
|
4619
5350
|
const reward = ((_a = eventData.frozen_reward) === null || _a === void 0 ? void 0 : _a.reward) || eventData.reward || null;
|
|
@@ -4628,7 +5359,10 @@ class WidgetRoot {
|
|
|
4628
5359
|
code: this.offlineRedemption.redemption_code,
|
|
4629
5360
|
});
|
|
4630
5361
|
}
|
|
4631
|
-
|
|
5362
|
+
if ((_c = eventData.group) === null || _c === void 0 ? void 0 : _c.campaign_redemption_method) {
|
|
5363
|
+
this.campaignRedemptionMethod = eventData.group.campaign_redemption_method;
|
|
5364
|
+
}
|
|
5365
|
+
(_e = (_d = this.events) === null || _d === void 0 ? void 0 : _d.onGroupFulfilled) === null || _e === void 0 ? void 0 : _e.call(_d, eventData);
|
|
4632
5366
|
this.renderFulfilledState();
|
|
4633
5367
|
}
|
|
4634
5368
|
/** Re-render widget and external containers to reflect fulfillment */
|
|
@@ -4768,8 +5502,10 @@ class WidgetRoot {
|
|
|
4768
5502
|
footer.style.justifyContent = "flex-end";
|
|
4769
5503
|
footer.style.alignItems = "center";
|
|
4770
5504
|
footer.style.gap = "12px";
|
|
4771
|
-
// Add "Redeem In-store" CTA if offline redemption is available
|
|
4772
|
-
if (this.offlineRedemption &&
|
|
5505
|
+
// Add "Redeem In-store" CTA if offline redemption is available and campaign allows it
|
|
5506
|
+
if (this.offlineRedemption &&
|
|
5507
|
+
isValidOfflineRedemption(this.offlineRedemption) &&
|
|
5508
|
+
this.campaignRedemptionMethod !== "online") {
|
|
4773
5509
|
const redeemLink = document.createElement("button");
|
|
4774
5510
|
redeemLink.className = "cobuy-redeem-instore-link";
|
|
4775
5511
|
redeemLink.style.background = "none";
|
|
@@ -4867,7 +5603,7 @@ class WidgetRoot {
|
|
|
4867
5603
|
}
|
|
4868
5604
|
// Set callback to open lobby when a group is successfully joined
|
|
4869
5605
|
this.groupListModal.setOnGroupJoined((joinData) => {
|
|
4870
|
-
var _a;
|
|
5606
|
+
var _a, _b;
|
|
4871
5607
|
// Update current group ID and session ID so the modal knows which group the user has joined
|
|
4872
5608
|
this.currentGroupId = joinData.group.id;
|
|
4873
5609
|
this.currentSessionId = ((_a = this.apiClient) === null || _a === void 0 ? void 0 : _a.getSessionId()) || null;
|
|
@@ -4881,8 +5617,9 @@ class WidgetRoot {
|
|
|
4881
5617
|
currentMembers: joinData.group.participants_count,
|
|
4882
5618
|
totalMembers: joinData.group.max_participants,
|
|
4883
5619
|
timeLeft: joinData.group.timeLeftSeconds,
|
|
5620
|
+
redemptionMethod: (_b = joinData.group.campaign_redemption_method) !== null && _b !== void 0 ? _b : this.campaignRedemptionMethod,
|
|
4884
5621
|
};
|
|
4885
|
-
const lobbyModal = new LobbyModal(lobbyData, {},
|
|
5622
|
+
const lobbyModal = new LobbyModal(lobbyData, {}, this.apiClient, null, this.analyticsClient, this.config.debug);
|
|
4886
5623
|
lobbyModal.open(joinData.group.id);
|
|
4887
5624
|
});
|
|
4888
5625
|
// Set callback for viewing progress on already joined group
|
|
@@ -4898,8 +5635,9 @@ class WidgetRoot {
|
|
|
4898
5635
|
progress: Math.round((groupData.joined / groupData.total) * 100),
|
|
4899
5636
|
currentMembers: groupData.joined,
|
|
4900
5637
|
totalMembers: groupData.total,
|
|
5638
|
+
redemptionMethod: this.campaignRedemptionMethod,
|
|
4901
5639
|
};
|
|
4902
|
-
const lobbyModal = new LobbyModal(lobbyData, {},
|
|
5640
|
+
const lobbyModal = new LobbyModal(lobbyData, {}, this.apiClient, null, this.analyticsClient, this.config.debug);
|
|
4903
5641
|
lobbyModal.open(groupId);
|
|
4904
5642
|
});
|
|
4905
5643
|
}
|
|
@@ -5083,6 +5821,9 @@ class WidgetRoot {
|
|
|
5083
5821
|
isValidOfflineRedemption(groupData.offline_redemption)) {
|
|
5084
5822
|
this.offlineRedemption = groupData.offline_redemption;
|
|
5085
5823
|
}
|
|
5824
|
+
if (groupData === null || groupData === void 0 ? void 0 : groupData.campaign_redemption_method) {
|
|
5825
|
+
this.campaignRedemptionMethod = groupData.campaign_redemption_method;
|
|
5826
|
+
}
|
|
5086
5827
|
// Check if group is already fulfilled (full) on initial load
|
|
5087
5828
|
if (groupData) {
|
|
5088
5829
|
const participants = Number(groupData.participants_count || 0);
|
|
@@ -5804,10 +6545,99 @@ class WidgetRoot {
|
|
|
5804
6545
|
to { transform: rotate(360deg); }
|
|
5805
6546
|
}
|
|
5806
6547
|
|
|
6548
|
+
.cobuy-prejoin-contact-overlay {
|
|
6549
|
+
position: fixed;
|
|
6550
|
+
inset: 0;
|
|
6551
|
+
z-index: 10045;
|
|
6552
|
+
background: rgba(2, 6, 23, 0.5);
|
|
6553
|
+
display: flex;
|
|
6554
|
+
align-items: center;
|
|
6555
|
+
justify-content: center;
|
|
6556
|
+
padding: 16px;
|
|
6557
|
+
}
|
|
6558
|
+
|
|
6559
|
+
.cobuy-prejoin-contact-card {
|
|
6560
|
+
width: min(440px, 100%);
|
|
6561
|
+
background: #fff;
|
|
6562
|
+
border-radius: 14px;
|
|
6563
|
+
padding: 18px;
|
|
6564
|
+
box-shadow: 0 14px 36px rgba(15, 23, 42, 0.22);
|
|
6565
|
+
display: flex;
|
|
6566
|
+
flex-direction: column;
|
|
6567
|
+
gap: 10px;
|
|
6568
|
+
}
|
|
6569
|
+
|
|
6570
|
+
.cobuy-prejoin-contact-title {
|
|
6571
|
+
font-size: 20px;
|
|
6572
|
+
font-weight: 800;
|
|
6573
|
+
color: #0f172a;
|
|
6574
|
+
margin: 0;
|
|
6575
|
+
}
|
|
6576
|
+
|
|
6577
|
+
.cobuy-prejoin-contact-subtitle {
|
|
6578
|
+
font-size: 14px;
|
|
6579
|
+
line-height: 1.5;
|
|
6580
|
+
color: #334155;
|
|
6581
|
+
margin: 0;
|
|
6582
|
+
}
|
|
6583
|
+
|
|
6584
|
+
.cobuy-prejoin-contact-input {
|
|
6585
|
+
width: 100%;
|
|
6586
|
+
border: 1px solid #cbd5e1;
|
|
6587
|
+
border-radius: 10px;
|
|
6588
|
+
padding: 11px 12px;
|
|
6589
|
+
font-size: 14px;
|
|
6590
|
+
outline: none;
|
|
6591
|
+
}
|
|
6592
|
+
|
|
6593
|
+
.cobuy-prejoin-contact-input:focus {
|
|
6594
|
+
border-color: #1d4ed8;
|
|
6595
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
|
6596
|
+
}
|
|
6597
|
+
|
|
6598
|
+
.cobuy-prejoin-contact-actions {
|
|
6599
|
+
display: flex;
|
|
6600
|
+
gap: 8px;
|
|
6601
|
+
flex-wrap: wrap;
|
|
6602
|
+
}
|
|
6603
|
+
|
|
6604
|
+
.cobuy-prejoin-contact-primary,
|
|
6605
|
+
.cobuy-prejoin-contact-secondary {
|
|
6606
|
+
border: none;
|
|
6607
|
+
border-radius: 10px;
|
|
6608
|
+
padding: 10px 12px;
|
|
6609
|
+
font-weight: 700;
|
|
6610
|
+
cursor: pointer;
|
|
6611
|
+
font-size: 13px;
|
|
6612
|
+
}
|
|
6613
|
+
|
|
6614
|
+
.cobuy-prejoin-contact-primary {
|
|
6615
|
+
background: #1d4ed8;
|
|
6616
|
+
color: #fff;
|
|
6617
|
+
}
|
|
6618
|
+
|
|
6619
|
+
.cobuy-prejoin-contact-primary:hover {
|
|
6620
|
+
background: #1e40af;
|
|
6621
|
+
}
|
|
6622
|
+
|
|
6623
|
+
.cobuy-prejoin-contact-secondary {
|
|
6624
|
+
background: #e2e8f0;
|
|
6625
|
+
color: #1e293b;
|
|
6626
|
+
}
|
|
6627
|
+
|
|
6628
|
+
.cobuy-prejoin-contact-secondary:hover {
|
|
6629
|
+
background: #cbd5e1;
|
|
6630
|
+
}
|
|
6631
|
+
|
|
5807
6632
|
@media (max-width: 640px) {
|
|
5808
6633
|
.cobuy-widget {
|
|
5809
6634
|
grid-template-columns: 1fr;
|
|
5810
6635
|
}
|
|
6636
|
+
|
|
6637
|
+
.cobuy-prejoin-contact-primary,
|
|
6638
|
+
.cobuy-prejoin-contact-secondary {
|
|
6639
|
+
width: 100%;
|
|
6640
|
+
}
|
|
5811
6641
|
}
|
|
5812
6642
|
`;
|
|
5813
6643
|
document.head.appendChild(style);
|
|
@@ -5816,7 +6646,7 @@ class WidgetRoot {
|
|
|
5816
6646
|
* Handle CTA button click with analytics and modal opening
|
|
5817
6647
|
*/
|
|
5818
6648
|
async handleCTAClick(productId) {
|
|
5819
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
6649
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
5820
6650
|
this.logger.info(`CTA clicked for product: ${productId}`);
|
|
5821
6651
|
// Track analytics event asynchronously (fire-and-forget)
|
|
5822
6652
|
if (this.analyticsClient) {
|
|
@@ -5831,28 +6661,110 @@ class WidgetRoot {
|
|
|
5831
6661
|
// Join group before opening modal
|
|
5832
6662
|
let groupJoinData = null;
|
|
5833
6663
|
let inviteData = null;
|
|
6664
|
+
let recoveryGroupId = null;
|
|
5834
6665
|
if (this.apiClient && this.currentGroupId) {
|
|
5835
6666
|
try {
|
|
5836
|
-
|
|
5837
|
-
if (this.analyticsClient) {
|
|
6667
|
+
recoveryGroupId = this.getStoredRecoveryGroupId(productId);
|
|
6668
|
+
if (this.analyticsClient && recoveryGroupId) {
|
|
5838
6669
|
this.analyticsClient
|
|
5839
|
-
.
|
|
6670
|
+
.trackEvent("RECOVERY_ATTEMPT", productId, {
|
|
6671
|
+
previousGroupId: recoveryGroupId,
|
|
6672
|
+
protectionState: "protected",
|
|
6673
|
+
})
|
|
5840
6674
|
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
5841
6675
|
}
|
|
5842
|
-
const
|
|
5843
|
-
if (
|
|
5844
|
-
|
|
6676
|
+
const recoverResponse = await this.apiClient.recoverOrJoinGroup(productId);
|
|
6677
|
+
if (recoverResponse.success && recoverResponse.data) {
|
|
6678
|
+
groupJoinData = recoverResponse.data;
|
|
6679
|
+
this.currentGroupId = groupJoinData.group.id;
|
|
6680
|
+
this.clearStoredRecoveryGroupId(productId);
|
|
6681
|
+
if (this.analyticsClient && groupJoinData.recovery.recovered) {
|
|
6682
|
+
this.analyticsClient
|
|
6683
|
+
.trackEvent("RECOVERY_SUCCESS", productId, {
|
|
6684
|
+
previousGroupId: groupJoinData.recovery.previous_group_id || recoveryGroupId,
|
|
6685
|
+
joinedGroupId: groupJoinData.recovery.joined_group_id,
|
|
6686
|
+
recoveryOutcome: groupJoinData.recovery.outcome,
|
|
6687
|
+
protectionState: groupJoinData.recovery.matched_by === "none" ? "unprotected" : "protected",
|
|
6688
|
+
})
|
|
6689
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6690
|
+
}
|
|
6691
|
+
else if (this.analyticsClient && recoveryGroupId) {
|
|
6692
|
+
this.analyticsClient
|
|
6693
|
+
.trackEvent("RECOVERY_FAILED", productId, {
|
|
6694
|
+
previousGroupId: recoveryGroupId,
|
|
6695
|
+
errorCode: "NOT_RECOVERED",
|
|
6696
|
+
errorMessage: "Recovery did not restore the previous membership.",
|
|
6697
|
+
protectionState: "protected",
|
|
6698
|
+
})
|
|
6699
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6700
|
+
}
|
|
6701
|
+
switch (groupJoinData.recovery.outcome) {
|
|
6702
|
+
case "rejoined_previous_group":
|
|
6703
|
+
this.showRecoveryToast(RECOVERY_TOAST_REJOINED);
|
|
6704
|
+
break;
|
|
6705
|
+
case "joined_next_available_group":
|
|
6706
|
+
if (groupJoinData.recovery.recovered) {
|
|
6707
|
+
this.showRecoveryToast(RECOVERY_TOAST_NEXT_AVAILABLE);
|
|
6708
|
+
}
|
|
6709
|
+
break;
|
|
6710
|
+
case "created_new_group":
|
|
6711
|
+
if (groupJoinData.recovery.recovered) {
|
|
6712
|
+
this.showRecoveryToast(RECOVERY_TOAST_CREATED_NEW);
|
|
6713
|
+
}
|
|
6714
|
+
break;
|
|
6715
|
+
}
|
|
6716
|
+
}
|
|
6717
|
+
else {
|
|
6718
|
+
if (this.analyticsClient && recoveryGroupId) {
|
|
6719
|
+
const errCode = (_c = recoverResponse.error) === null || _c === void 0 ? void 0 : _c.code;
|
|
6720
|
+
const errMsg = (_d = recoverResponse.error) === null || _d === void 0 ? void 0 : _d.message;
|
|
6721
|
+
this.analyticsClient
|
|
6722
|
+
.trackEvent("RECOVERY_FAILED", productId, {
|
|
6723
|
+
previousGroupId: recoveryGroupId,
|
|
6724
|
+
errorCode: errCode || "RECOVERY_UNAVAILABLE",
|
|
6725
|
+
errorMessage: errMsg || "Recovery response was not successful.",
|
|
6726
|
+
protectionState: "protected",
|
|
6727
|
+
})
|
|
6728
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6729
|
+
}
|
|
6730
|
+
const joinTargetGroupId = recoveryGroupId || this.currentGroupId;
|
|
6731
|
+
this.logger.info(`Joining group: ${joinTargetGroupId}`);
|
|
5845
6732
|
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
6733
|
this.analyticsClient
|
|
5849
|
-
.
|
|
6734
|
+
.trackJoinAttempt(productId, joinTargetGroupId)
|
|
5850
6735
|
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
5851
6736
|
}
|
|
5852
|
-
this.
|
|
5853
|
-
|
|
6737
|
+
let joinResponse = await this.apiClient.joinGroup(joinTargetGroupId);
|
|
6738
|
+
if (!joinResponse.success && recoveryGroupId && this.currentGroupId !== recoveryGroupId) {
|
|
6739
|
+
this.clearStoredRecoveryGroupId(productId);
|
|
6740
|
+
joinResponse = await this.apiClient.joinGroup(this.currentGroupId);
|
|
6741
|
+
}
|
|
6742
|
+
if (!joinResponse.success) {
|
|
6743
|
+
this.logger.error("Failed to join group, modal will not open", joinResponse.error);
|
|
6744
|
+
if (this.analyticsClient) {
|
|
6745
|
+
const errCode = (_e = joinResponse.error) === null || _e === void 0 ? void 0 : _e.code;
|
|
6746
|
+
const errMsg = (_f = joinResponse.error) === null || _f === void 0 ? void 0 : _f.message;
|
|
6747
|
+
this.analyticsClient
|
|
6748
|
+
.trackJoinFailure(productId, joinTargetGroupId, errCode, errMsg)
|
|
6749
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6750
|
+
}
|
|
6751
|
+
this.setButtonLoadingState(false);
|
|
6752
|
+
return; // Don't open modal on error
|
|
6753
|
+
}
|
|
6754
|
+
groupJoinData = joinResponse.data;
|
|
6755
|
+
if ((_g = groupJoinData === null || groupJoinData === void 0 ? void 0 : groupJoinData.group) === null || _g === void 0 ? void 0 : _g.id) {
|
|
6756
|
+
this.currentGroupId = groupJoinData.group.id;
|
|
6757
|
+
}
|
|
6758
|
+
if (recoveryGroupId && ((_h = groupJoinData === null || groupJoinData === void 0 ? void 0 : groupJoinData.group) === null || _h === void 0 ? void 0 : _h.id)) {
|
|
6759
|
+
this.clearStoredRecoveryGroupId(productId);
|
|
6760
|
+
if (groupJoinData.group.id !== recoveryGroupId) {
|
|
6761
|
+
this.showRecoveryToast(RECOVERY_TOAST_NEXT_AVAILABLE);
|
|
6762
|
+
}
|
|
6763
|
+
else {
|
|
6764
|
+
this.showRecoveryToast(RECOVERY_TOAST_REJOINED);
|
|
6765
|
+
}
|
|
6766
|
+
}
|
|
5854
6767
|
}
|
|
5855
|
-
groupJoinData = joinResponse.data;
|
|
5856
6768
|
this.logger.info("Successfully joined group", groupJoinData);
|
|
5857
6769
|
if (this.analyticsClient && groupJoinData) {
|
|
5858
6770
|
this.analyticsClient
|
|
@@ -5865,26 +6777,39 @@ class WidgetRoot {
|
|
|
5865
6777
|
isValidOfflineRedemption(groupJoinData.offline_redemption)
|
|
5866
6778
|
? groupJoinData.offline_redemption
|
|
5867
6779
|
: null;
|
|
6780
|
+
const joinedGroupId = (_j = groupJoinData === null || groupJoinData === void 0 ? void 0 : groupJoinData.group) === null || _j === void 0 ? void 0 : _j.id;
|
|
5868
6781
|
// Trigger invite tracking before opening lobby (global for product)
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
6782
|
+
if (joinedGroupId) {
|
|
6783
|
+
try {
|
|
6784
|
+
const inviteResponse = await this.apiClient.inviteToGroup(joinedGroupId, "copy_link");
|
|
6785
|
+
if (inviteResponse.success && inviteResponse.data) {
|
|
6786
|
+
inviteData = inviteResponse.data;
|
|
6787
|
+
this.logger.info("Invite link generated", inviteData);
|
|
6788
|
+
}
|
|
6789
|
+
else {
|
|
6790
|
+
this.logger.warn("Invite link generation failed", inviteResponse.error);
|
|
6791
|
+
}
|
|
5874
6792
|
}
|
|
5875
|
-
|
|
5876
|
-
this.logger.warn("Invite
|
|
6793
|
+
catch (inviteError) {
|
|
6794
|
+
this.logger.warn("Invite request failed", inviteError);
|
|
5877
6795
|
}
|
|
5878
6796
|
}
|
|
5879
|
-
catch (inviteError) {
|
|
5880
|
-
this.logger.warn("Invite request failed", inviteError);
|
|
5881
|
-
}
|
|
5882
6797
|
}
|
|
5883
6798
|
catch (error) {
|
|
5884
6799
|
this.logger.error("Group join failed, modal will not open", error);
|
|
6800
|
+
if (this.analyticsClient && recoveryGroupId) {
|
|
6801
|
+
this.analyticsClient
|
|
6802
|
+
.trackEvent("RECOVERY_FAILED", productId, {
|
|
6803
|
+
previousGroupId: recoveryGroupId,
|
|
6804
|
+
errorCode: "EXCEPTION",
|
|
6805
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
6806
|
+
protectionState: "protected",
|
|
6807
|
+
})
|
|
6808
|
+
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
6809
|
+
}
|
|
5885
6810
|
if (this.analyticsClient) {
|
|
5886
6811
|
this.analyticsClient
|
|
5887
|
-
.trackJoinFailure(productId, (
|
|
6812
|
+
.trackJoinFailure(productId, (_k = this.currentGroupId) !== null && _k !== void 0 ? _k : undefined, "EXCEPTION", error instanceof Error ? error.message : String(error))
|
|
5888
6813
|
.catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
5889
6814
|
}
|
|
5890
6815
|
this.setButtonLoadingState(false);
|
|
@@ -5901,7 +6826,7 @@ class WidgetRoot {
|
|
|
5901
6826
|
const progress = Math.round((groupJoinData.group.participants_count / groupJoinData.group.max_participants) * 100);
|
|
5902
6827
|
// Format discount based on reward type
|
|
5903
6828
|
let discountText = "";
|
|
5904
|
-
if ((
|
|
6829
|
+
if ((_m = (_l = this.currentRewardData) === null || _l === void 0 ? void 0 : _l.reward) === null || _m === void 0 ? void 0 : _m.value) {
|
|
5905
6830
|
const rewardType = this.currentRewardData.reward.type;
|
|
5906
6831
|
const rewardValue = this.currentRewardData.reward.value;
|
|
5907
6832
|
if (rewardType === "percentage" || rewardType === "cashback") {
|
|
@@ -5941,6 +6866,7 @@ class WidgetRoot {
|
|
|
5941
6866
|
shareMessage: shareMessageFromInvite,
|
|
5942
6867
|
isLocked: !isGroupFulfilled,
|
|
5943
6868
|
offlineRedemption: offlineRedemptionFromJoin,
|
|
6869
|
+
redemptionMethod: (_o = groupJoinData.group.campaign_redemption_method) !== null && _o !== void 0 ? _o : this.campaignRedemptionMethod,
|
|
5944
6870
|
onShare: this.analyticsClient
|
|
5945
6871
|
? () => {
|
|
5946
6872
|
this.analyticsClient.trackShareClick(productId, groupJoinData.group.id, "other").catch((e) => this.logger.warn("Analytics tracking failed", e));
|
|
@@ -6111,8 +7037,10 @@ const API_ENDPOINTS = {
|
|
|
6111
7037
|
PRODUCT_CONTEXT: "/v1/sdk/products/:productId/context",
|
|
6112
7038
|
PRODUCT_REWARD: "/v1/sdk/products/:productId/reward",
|
|
6113
7039
|
PRODUCT_PRIMARY_GROUP: "/v1/sdk/products/:productId/group/primary",
|
|
7040
|
+
PRODUCT_RECOVER_OR_JOIN_GROUP: "/v1/sdk/products/:productId/group/recover-or-join",
|
|
6114
7041
|
// Group endpoints
|
|
6115
7042
|
GROUP_JOIN: "/v1/sdk/groups/:groupId/join",
|
|
7043
|
+
GROUP_LEAVE: "/v1/sdk/groups/:groupId/leave",
|
|
6116
7044
|
GROUP_CREATE_AND_JOIN: "/v1/sdk/groups/new/join",
|
|
6117
7045
|
PRODUCT_ACTIVE_GROUPS: "/v1/sdk/products/:productId/groups/active",
|
|
6118
7046
|
GROUP_INVITE: "/v1/sdk/groups/:groupId/invite",
|
|
@@ -7203,6 +8131,75 @@ class ApiClient {
|
|
|
7203
8131
|
},
|
|
7204
8132
|
};
|
|
7205
8133
|
}
|
|
8134
|
+
/**
|
|
8135
|
+
* Leave an active group for the current SDK session.
|
|
8136
|
+
*/
|
|
8137
|
+
async leaveGroup(groupId, params) {
|
|
8138
|
+
const endpoint = buildApiUrl("", API_ENDPOINTS.GROUP_LEAVE, { groupId });
|
|
8139
|
+
this.logger.info(`Leaving group: ${groupId}`);
|
|
8140
|
+
const response = await this.post(endpoint, {
|
|
8141
|
+
leave_source: (params === null || params === void 0 ? void 0 : params.leave_source) || "unknown",
|
|
8142
|
+
leave_reason: (params === null || params === void 0 ? void 0 : params.leave_reason) || "unknown",
|
|
8143
|
+
});
|
|
8144
|
+
if (response.success) {
|
|
8145
|
+
const payload = response.data;
|
|
8146
|
+
return {
|
|
8147
|
+
success: true,
|
|
8148
|
+
data: ((payload === null || payload === void 0 ? void 0 : payload.data) || response.data),
|
|
8149
|
+
};
|
|
8150
|
+
}
|
|
8151
|
+
return {
|
|
8152
|
+
success: false,
|
|
8153
|
+
error: response.error || {
|
|
8154
|
+
message: "Failed to leave group",
|
|
8155
|
+
code: "LEAVE_GROUP_ERROR",
|
|
8156
|
+
},
|
|
8157
|
+
};
|
|
8158
|
+
}
|
|
8159
|
+
async recoverOrJoinGroup(productId, contact) {
|
|
8160
|
+
var _a;
|
|
8161
|
+
const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_RECOVER_OR_JOIN_GROUP, {
|
|
8162
|
+
productId,
|
|
8163
|
+
});
|
|
8164
|
+
this.logger.info(`Recovering or joining group for product: ${productId}`);
|
|
8165
|
+
const response = await this.post(endpoint, contact ? { contact } : {});
|
|
8166
|
+
if (response.success && ((_a = response.data) === null || _a === void 0 ? void 0 : _a.data)) {
|
|
8167
|
+
return {
|
|
8168
|
+
success: true,
|
|
8169
|
+
data: response.data.data,
|
|
8170
|
+
};
|
|
8171
|
+
}
|
|
8172
|
+
return {
|
|
8173
|
+
success: false,
|
|
8174
|
+
error: response.error || {
|
|
8175
|
+
message: "Failed to recover or join group",
|
|
8176
|
+
code: "RECOVER_OR_JOIN_GROUP_ERROR",
|
|
8177
|
+
},
|
|
8178
|
+
};
|
|
8179
|
+
}
|
|
8180
|
+
/**
|
|
8181
|
+
* Best-effort leave signal for unload/pagehide scenarios.
|
|
8182
|
+
* Uses fetch keepalive so it can run while page is closing.
|
|
8183
|
+
*/
|
|
8184
|
+
leaveGroupBestEffort(groupId, params) {
|
|
8185
|
+
if (!groupId)
|
|
8186
|
+
return;
|
|
8187
|
+
const endpoint = buildApiUrl("", API_ENDPOINTS.GROUP_LEAVE, { groupId });
|
|
8188
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
8189
|
+
const payload = JSON.stringify({
|
|
8190
|
+
leave_source: (params === null || params === void 0 ? void 0 : params.leave_source) || "browser_close",
|
|
8191
|
+
leave_reason: (params === null || params === void 0 ? void 0 : params.leave_reason) || "voluntary_no_contact",
|
|
8192
|
+
});
|
|
8193
|
+
const authHeaders = this.authStrategy.getHeaders();
|
|
8194
|
+
void fetch(url, {
|
|
8195
|
+
method: "POST",
|
|
8196
|
+
headers: Object.assign({ "Content-Type": "application/json", "X-CoBuy-SDK-Version": "1.0.0", "X-CoBuy-Session": this.sessionId }, authHeaders),
|
|
8197
|
+
body: payload,
|
|
8198
|
+
keepalive: true,
|
|
8199
|
+
}).catch((error) => {
|
|
8200
|
+
this.logger.debug("Best-effort leave request failed", error);
|
|
8201
|
+
});
|
|
8202
|
+
}
|
|
7206
8203
|
/**
|
|
7207
8204
|
* Prepare checkout for a group
|
|
7208
8205
|
*
|
|
@@ -12032,15 +13029,16 @@ class SocketManager {
|
|
|
12032
13029
|
return;
|
|
12033
13030
|
const bind = (eventName, handler) => {
|
|
12034
13031
|
var _a;
|
|
12035
|
-
if (!handler)
|
|
12036
|
-
return;
|
|
12037
13032
|
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.on(eventName, (payload) => {
|
|
12038
13033
|
this.logger.info(`[Socket] ${eventName} payload: ${this.formatPayload(payload)}`);
|
|
12039
13034
|
this.dispatchWindowEvent(eventName, payload);
|
|
12040
|
-
handler
|
|
13035
|
+
if (handler) {
|
|
13036
|
+
handler(payload);
|
|
13037
|
+
}
|
|
12041
13038
|
});
|
|
12042
13039
|
};
|
|
12043
13040
|
bind("group:member:joined", handlers.onGroupMemberJoined);
|
|
13041
|
+
bind("group:member:left", handlers.onGroupMemberLeft);
|
|
12044
13042
|
bind("group:created", handlers.onGroupCreated);
|
|
12045
13043
|
bind("group:fulfilled", handlers.onGroupFulfilled);
|
|
12046
13044
|
}
|
|
@@ -12051,6 +13049,7 @@ class SocketManager {
|
|
|
12051
13049
|
if (!this.socket)
|
|
12052
13050
|
return;
|
|
12053
13051
|
this.socket.off("group:member:joined");
|
|
13052
|
+
this.socket.off("group:member:left");
|
|
12054
13053
|
this.socket.off("group:created");
|
|
12055
13054
|
this.socket.off("group:fulfilled");
|
|
12056
13055
|
}
|
|
@@ -13424,6 +14423,7 @@ class CoBuy {
|
|
|
13424
14423
|
activities: options.activities,
|
|
13425
14424
|
isLocked: options.isLocked,
|
|
13426
14425
|
offlineRedemption: options.offlineRedemption,
|
|
14426
|
+
redemptionMethod: options.redemptionMethod,
|
|
13427
14427
|
};
|
|
13428
14428
|
// Create modal instance
|
|
13429
14429
|
const modal = new LobbyModal(modalData, {
|
|
@@ -13438,7 +14438,7 @@ class CoBuy {
|
|
|
13438
14438
|
},
|
|
13439
14439
|
onCopyLink: options.onCopyLink,
|
|
13440
14440
|
onShare: options.onShare,
|
|
13441
|
-
}, this.apiClient, this.socketManager, config.debug);
|
|
14441
|
+
}, this.apiClient, this.socketManager, this.analyticsClient, config.debug);
|
|
13442
14442
|
// Store in map for persistence
|
|
13443
14443
|
this.modals.set(modalKey, modal);
|
|
13444
14444
|
// Maintain backward compatibility
|