@cshah18/sdk 4.0.0 → 4.2.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 +1135 -80
- package/dist/cobuy-sdk.esm.js.map +1 -1
- package/dist/cobuy-sdk.umd.js +1135 -80
- package/dist/cobuy-sdk.umd.js.map +1 -1
- package/dist/types/core/analytics.d.ts +6 -2
- package/dist/types/core/api-client.d.ts +2 -2
- package/dist/types/core/cobuy.d.ts +19 -2
- package/dist/types/core/group-channel-manager.d.ts +74 -0
- package/dist/types/core/offline-redemption.d.ts +41 -0
- package/dist/types/core/socket.d.ts +2 -1
- package/dist/types/core/types.d.ts +76 -5
- package/dist/types/ui/group-list/group-list-modal.d.ts +1 -0
- package/dist/types/ui/lobby/lobby-modal.d.ts +39 -1
- package/dist/types/ui/offline-redemption/offline-redemption-modal.d.ts +35 -0
- package/dist/types/ui/widget/widget-root.d.ts +6 -0
- package/package.json +1 -1
- package/dist/types/core/socket-client.d.ts +0 -126
package/dist/cobuy-sdk.esm.js
CHANGED
|
@@ -504,12 +504,14 @@ class GroupListModal {
|
|
|
504
504
|
// Convert API groups to display format
|
|
505
505
|
const groups = response.data.groups.map((apiGroup) => ({
|
|
506
506
|
groupId: apiGroup.id,
|
|
507
|
+
name: apiGroup.group_name,
|
|
507
508
|
timeLabel: this.formatTimeRemaining(apiGroup.timeLeftSeconds),
|
|
508
509
|
timeLeftSeconds: apiGroup.timeLeftSeconds,
|
|
509
510
|
joined: apiGroup.participants_count,
|
|
510
511
|
total: apiGroup.max_participants,
|
|
511
512
|
isMember: Boolean(apiGroup.is_member),
|
|
512
513
|
}));
|
|
514
|
+
console.log("groupsss", groups);
|
|
513
515
|
// If API reports membership, prefer it over cached state
|
|
514
516
|
const memberGroup = groups.find((g) => g.isMember);
|
|
515
517
|
if (memberGroup) {
|
|
@@ -816,12 +818,12 @@ class GroupListModal {
|
|
|
816
818
|
createGroupCard(group) {
|
|
817
819
|
const card = document.createElement("div");
|
|
818
820
|
card.className = "cobuy-group-card";
|
|
819
|
-
card.setAttribute("data-group-id", group.groupId);
|
|
821
|
+
card.setAttribute("data-group-id", group.name || group.groupId);
|
|
820
822
|
const header = document.createElement("div");
|
|
821
823
|
header.className = "cobuy-group-card-header";
|
|
822
824
|
const groupId = document.createElement("div");
|
|
823
825
|
groupId.className = "cobuy-group-id";
|
|
824
|
-
groupId.textContent = `Group #${group.groupId}`;
|
|
826
|
+
groupId.textContent = `Group #${group.name || group.groupId}`;
|
|
825
827
|
const timer = document.createElement("div");
|
|
826
828
|
timer.className = "cobuy-group-timer";
|
|
827
829
|
timer.innerHTML = `
|
|
@@ -1360,6 +1362,158 @@ class GroupListModal {
|
|
|
1360
1362
|
}
|
|
1361
1363
|
}
|
|
1362
1364
|
|
|
1365
|
+
/**
|
|
1366
|
+
* Offline Redemption Utilities
|
|
1367
|
+
* Helpers for constructing QR URLs, copying codes, and downloading QR images
|
|
1368
|
+
*/
|
|
1369
|
+
const S3_BUCKET = "cobuy-dev";
|
|
1370
|
+
const S3_REGION = "af-south-1";
|
|
1371
|
+
/**
|
|
1372
|
+
* Build full S3 URL for QR code image
|
|
1373
|
+
* @param qrCodeData - S3 key from offline_redemption.qr_code_data
|
|
1374
|
+
* @returns Full HTTPS URL to the QR code image
|
|
1375
|
+
*/
|
|
1376
|
+
function buildQRUrl(qrCodeData) {
|
|
1377
|
+
if (!qrCodeData) {
|
|
1378
|
+
return "";
|
|
1379
|
+
}
|
|
1380
|
+
const prefix = `https://${S3_BUCKET}.s3.${S3_REGION}.amazonaws.com/`;
|
|
1381
|
+
return `${prefix}${qrCodeData}`;
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Copy redemption code to clipboard
|
|
1385
|
+
* @param code - Redemption code to copy
|
|
1386
|
+
* @returns Promise that resolves when copy is complete
|
|
1387
|
+
*/
|
|
1388
|
+
async function copyRedemptionCode(code) {
|
|
1389
|
+
const logger = new Logger(false);
|
|
1390
|
+
if (!code) {
|
|
1391
|
+
logger.warn("Cannot copy empty redemption code");
|
|
1392
|
+
return false;
|
|
1393
|
+
}
|
|
1394
|
+
try {
|
|
1395
|
+
// Use modern clipboard API if available
|
|
1396
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
1397
|
+
await navigator.clipboard.writeText(code);
|
|
1398
|
+
logger.info("Redemption code copied to clipboard");
|
|
1399
|
+
return true;
|
|
1400
|
+
}
|
|
1401
|
+
// Fallback for older browsers
|
|
1402
|
+
const textarea = document.createElement("textarea");
|
|
1403
|
+
textarea.value = code;
|
|
1404
|
+
textarea.style.position = "fixed";
|
|
1405
|
+
textarea.style.opacity = "0";
|
|
1406
|
+
document.body.appendChild(textarea);
|
|
1407
|
+
textarea.select();
|
|
1408
|
+
textarea.setSelectionRange(0, 99999); // For mobile devices
|
|
1409
|
+
const successful = document.execCommand("copy");
|
|
1410
|
+
document.body.removeChild(textarea);
|
|
1411
|
+
if (successful) {
|
|
1412
|
+
logger.info("Redemption code copied to clipboard (fallback method)");
|
|
1413
|
+
return true;
|
|
1414
|
+
}
|
|
1415
|
+
return false;
|
|
1416
|
+
}
|
|
1417
|
+
catch (error) {
|
|
1418
|
+
logger.error("Failed to copy redemption code", error);
|
|
1419
|
+
return false;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Download QR code image
|
|
1424
|
+
* @param qrUrl - Full URL to the QR code image
|
|
1425
|
+
* @param filename - Name for the downloaded file
|
|
1426
|
+
*/
|
|
1427
|
+
async function downloadQRCode(qrUrl, filename = "redemption-qr.png") {
|
|
1428
|
+
const logger = new Logger(false);
|
|
1429
|
+
if (!qrUrl) {
|
|
1430
|
+
logger.warn("Cannot download QR code - empty URL");
|
|
1431
|
+
return false;
|
|
1432
|
+
}
|
|
1433
|
+
try {
|
|
1434
|
+
const response = await fetch(qrUrl);
|
|
1435
|
+
if (!response.ok) {
|
|
1436
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1437
|
+
}
|
|
1438
|
+
const blob = await response.blob();
|
|
1439
|
+
const blobUrl = window.URL.createObjectURL(blob);
|
|
1440
|
+
const link = document.createElement("a");
|
|
1441
|
+
link.href = blobUrl;
|
|
1442
|
+
link.download = filename;
|
|
1443
|
+
link.style.display = "none";
|
|
1444
|
+
document.body.appendChild(link);
|
|
1445
|
+
link.click();
|
|
1446
|
+
document.body.removeChild(link);
|
|
1447
|
+
window.URL.revokeObjectURL(blobUrl);
|
|
1448
|
+
logger.info("QR code downloaded successfully");
|
|
1449
|
+
return true;
|
|
1450
|
+
}
|
|
1451
|
+
catch (error) {
|
|
1452
|
+
logger.error("Failed to download QR code", error);
|
|
1453
|
+
return false;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Validate offline redemption data
|
|
1458
|
+
* @param redemption - Offline redemption object
|
|
1459
|
+
* @returns true if data is valid and complete
|
|
1460
|
+
*/
|
|
1461
|
+
function isValidOfflineRedemption(redemption) {
|
|
1462
|
+
if (!redemption || typeof redemption !== "object") {
|
|
1463
|
+
return false;
|
|
1464
|
+
}
|
|
1465
|
+
const obj = redemption;
|
|
1466
|
+
return (typeof obj.member_id === "string" &&
|
|
1467
|
+
obj.member_id.length > 0 &&
|
|
1468
|
+
typeof obj.qr_code_value === "string" &&
|
|
1469
|
+
obj.qr_code_value.length > 0 &&
|
|
1470
|
+
typeof obj.redemption_code === "string" &&
|
|
1471
|
+
obj.redemption_code.length > 0 &&
|
|
1472
|
+
typeof obj.qr_code_data === "string" &&
|
|
1473
|
+
obj.qr_code_data.length > 0 &&
|
|
1474
|
+
typeof obj.offline_expires_at === "string" &&
|
|
1475
|
+
obj.offline_expires_at.length > 0);
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Format expiry date for display
|
|
1479
|
+
* @param isoDate - ISO 8601 date string
|
|
1480
|
+
* @returns Formatted date string (e.g., "Feb 20, 2026 at 10:21 AM")
|
|
1481
|
+
*/
|
|
1482
|
+
function formatExpiryDate(isoDate) {
|
|
1483
|
+
try {
|
|
1484
|
+
const date = new Date(isoDate);
|
|
1485
|
+
// Check if date is valid
|
|
1486
|
+
if (isNaN(date.getTime())) {
|
|
1487
|
+
return "Invalid date";
|
|
1488
|
+
}
|
|
1489
|
+
return date.toLocaleDateString("en-US", {
|
|
1490
|
+
year: "numeric",
|
|
1491
|
+
month: "short",
|
|
1492
|
+
day: "numeric",
|
|
1493
|
+
hour: "2-digit",
|
|
1494
|
+
minute: "2-digit",
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
catch (_a) {
|
|
1498
|
+
return "Invalid date";
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
/**
|
|
1502
|
+
* Check if offline redemption is still valid
|
|
1503
|
+
* @param expiryDate - ISO 8601 expiry date string
|
|
1504
|
+
* @returns true if redemption has not expired
|
|
1505
|
+
*/
|
|
1506
|
+
function isRedemptionValid(expiryDate) {
|
|
1507
|
+
try {
|
|
1508
|
+
const expiry = new Date(expiryDate);
|
|
1509
|
+
const now = new Date();
|
|
1510
|
+
return now < expiry;
|
|
1511
|
+
}
|
|
1512
|
+
catch (_a) {
|
|
1513
|
+
return false;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1363
1517
|
function styleInject(css, ref) {
|
|
1364
1518
|
if ( ref === void 0 ) ref = {};
|
|
1365
1519
|
var insertAt = ref.insertAt;
|
|
@@ -1387,7 +1541,7 @@ function styleInject(css, ref) {
|
|
|
1387
1541
|
}
|
|
1388
1542
|
}
|
|
1389
1543
|
|
|
1390
|
-
var css_248z = ".cb-lobby-modal-container{height:100%;left:0;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-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:green;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{font-size:20px;line-height:1;margin-bottom:10px}.lobby-link-box{align-items:center;backdrop-filter:blur(12px);background-color:hsla(0,0%,100%,.4);border:1px solid hsla(0,0%,100%,.2);border-radius:1rem;box-shadow:0 1px 3px 0 #0000001a;display:flex;flex:1;gap:20px;justify-content:space-between;padding:13px 20px}.lobby-link-text{font-size:12px;font-weight:600;line-height:1;margin-bottom:6px;text-transform:uppercase}.copy-link-btn{align-items:center;color:#1d293d;cursor:pointer;display:flex;justify-content:center;padding:5px}.lobby-link-url{font-size:16px;font-weight:700;line-height:1}.lobby-link-wp{align-items:center;display:flex;gap:20px;justify-content:space-between;margin-top:40px}.share-btn-wp .share-btn{align-items:center;background:#000;border-radius:15px;color:#fff;cursor:pointer;display:flex;height:68px;justify-content:center;padding:5px;width:68px}.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:600;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-wp{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}.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: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{padding:10px}.lobby-link-wp{gap:12px}.share-btn-wp .share-btn{border-radius:8px;height:52px;width:52px}.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-wp{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}";
|
|
1544
|
+
var css_248z = ".cb-lobby-modal-container{height:100%;left:0;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{backdrop-filter:blur(8px);background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.3);border-radius:24px;gap:8px;margin-bottom:16px;padding:8px 16px;width:fit-content}.lobby-indicator,.pulsing-dot{align-items:center;display:flex}.pulsing-dot{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:15px}.share-btn svg{color:#fff}@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)}.share-btn svg{flex-shrink:0;height:18px;width:18px}.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:600;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: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}";
|
|
1391
1545
|
styleInject(css_248z,{"insertAt":"bottom"});
|
|
1392
1546
|
|
|
1393
1547
|
/**
|
|
@@ -1423,6 +1577,7 @@ class LobbyModal {
|
|
|
1423
1577
|
this.handleSocketGroupUpdate = (event) => {
|
|
1424
1578
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
1425
1579
|
const detail = event.detail || {};
|
|
1580
|
+
console.log("group completed", event, detail);
|
|
1426
1581
|
const normalizeId = (id) => {
|
|
1427
1582
|
if (id === undefined || id === null)
|
|
1428
1583
|
return null;
|
|
@@ -1492,6 +1647,14 @@ class LobbyModal {
|
|
|
1492
1647
|
status: isComplete ? "complete" : "active",
|
|
1493
1648
|
isLocked: !isComplete,
|
|
1494
1649
|
};
|
|
1650
|
+
// Add offline redemption data if available and group is complete
|
|
1651
|
+
if (isComplete) {
|
|
1652
|
+
const offlineRedemption = detail.offline_redemption;
|
|
1653
|
+
if (offlineRedemption && isValidOfflineRedemption(offlineRedemption)) {
|
|
1654
|
+
updatePayload.offlineRedemption = offlineRedemption;
|
|
1655
|
+
this.logger.info("Offline redemption data received in socket event");
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1495
1658
|
this.updateData(updatePayload);
|
|
1496
1659
|
// Also update team card to reflect new member count
|
|
1497
1660
|
this.updateTeamCard(participants, max);
|
|
@@ -1509,6 +1672,7 @@ class LobbyModal {
|
|
|
1509
1672
|
currentMembers: data.currentMembers,
|
|
1510
1673
|
totalMembers: data.totalMembers,
|
|
1511
1674
|
timeLeft: data.timeLeft,
|
|
1675
|
+
offlineRedemption: data.offlineRedemption,
|
|
1512
1676
|
});
|
|
1513
1677
|
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);
|
|
1514
1678
|
// Normalize optional flags so undefined values don't override defaults
|
|
@@ -1539,7 +1703,7 @@ class LobbyModal {
|
|
|
1539
1703
|
return true;
|
|
1540
1704
|
}
|
|
1541
1705
|
getRewardText(isLocked) {
|
|
1542
|
-
return isLocked ? "🔓 Complete group to unlock
|
|
1706
|
+
return isLocked ? "🔓 Complete group to unlock discount." : "🎉 Discount unlocked!";
|
|
1543
1707
|
}
|
|
1544
1708
|
getTitleText(data) {
|
|
1545
1709
|
var _a, _b;
|
|
@@ -1548,19 +1712,19 @@ class LobbyModal {
|
|
|
1548
1712
|
const unlocked = !this.computeIsLocked(data);
|
|
1549
1713
|
if (unlocked) {
|
|
1550
1714
|
return {
|
|
1551
|
-
title: "
|
|
1552
|
-
subtitle: "Group filled — enjoy your
|
|
1715
|
+
title: "Discount unlocked!",
|
|
1716
|
+
subtitle: "Group filled — enjoy your discount.",
|
|
1553
1717
|
};
|
|
1554
1718
|
}
|
|
1555
1719
|
if (remaining <= 1) {
|
|
1556
1720
|
return {
|
|
1557
1721
|
title: "Just one more!",
|
|
1558
|
-
subtitle: "Invite a friend to unlock your
|
|
1722
|
+
subtitle: "Invite a friend to unlock your discount instantly.",
|
|
1559
1723
|
};
|
|
1560
1724
|
}
|
|
1561
1725
|
return {
|
|
1562
1726
|
title: "Almost there!",
|
|
1563
|
-
subtitle: `Just ${remaining} more ${remaining === 1 ? "member" : "members"} — your
|
|
1727
|
+
subtitle: `Just ${remaining} more ${remaining === 1 ? "member" : "members"} — your discount unlocks instantly.`,
|
|
1564
1728
|
};
|
|
1565
1729
|
}
|
|
1566
1730
|
/**
|
|
@@ -1610,6 +1774,62 @@ class LobbyModal {
|
|
|
1610
1774
|
},
|
|
1611
1775
|
];
|
|
1612
1776
|
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Create entrance animation overlay
|
|
1779
|
+
*/
|
|
1780
|
+
createEntranceAnimation() {
|
|
1781
|
+
const overlay = document.createElement("div");
|
|
1782
|
+
overlay.className = "entrance-animation-overlay";
|
|
1783
|
+
overlay.id = "entranceAnimationOverlay";
|
|
1784
|
+
const content = document.createElement("div");
|
|
1785
|
+
content.className = "entrance-content";
|
|
1786
|
+
// Icon box
|
|
1787
|
+
const iconBox = document.createElement("div");
|
|
1788
|
+
iconBox.className = "entrance-icon-box";
|
|
1789
|
+
iconBox.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-users"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>`;
|
|
1790
|
+
// Title
|
|
1791
|
+
const title = document.createElement("h1");
|
|
1792
|
+
title.className = "entrance-title";
|
|
1793
|
+
title.textContent = "Group Lobby";
|
|
1794
|
+
// Message with pulsing dot
|
|
1795
|
+
const message = document.createElement("p");
|
|
1796
|
+
message.className = "entrance-message";
|
|
1797
|
+
const pulseDot = document.createElement("span");
|
|
1798
|
+
pulseDot.className = "entrance-pulse-dot";
|
|
1799
|
+
const messageText = document.createElement("span");
|
|
1800
|
+
messageText.textContent = "Entering secure session...";
|
|
1801
|
+
message.appendChild(pulseDot);
|
|
1802
|
+
message.appendChild(messageText);
|
|
1803
|
+
content.appendChild(iconBox);
|
|
1804
|
+
content.appendChild(title);
|
|
1805
|
+
content.appendChild(message);
|
|
1806
|
+
overlay.appendChild(content);
|
|
1807
|
+
return overlay;
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Show entrance animation and auto-hide after duration
|
|
1811
|
+
*/
|
|
1812
|
+
showEntranceAnimation(onComplete) {
|
|
1813
|
+
const overlay = this.createEntranceAnimation();
|
|
1814
|
+
document.body.appendChild(overlay);
|
|
1815
|
+
// After 2.5 seconds, trigger exit animation and remove
|
|
1816
|
+
setTimeout(() => {
|
|
1817
|
+
this.hideEntranceAnimation(overlay, onComplete);
|
|
1818
|
+
}, 2500);
|
|
1819
|
+
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Hide entrance animation with exit effect
|
|
1822
|
+
*/
|
|
1823
|
+
hideEntranceAnimation(overlay, onComplete) {
|
|
1824
|
+
overlay.classList.add("exit");
|
|
1825
|
+
// Remove after animation completes (800ms) and call callback
|
|
1826
|
+
setTimeout(() => {
|
|
1827
|
+
overlay.remove();
|
|
1828
|
+
if (onComplete) {
|
|
1829
|
+
onComplete();
|
|
1830
|
+
}
|
|
1831
|
+
}, 800);
|
|
1832
|
+
}
|
|
1613
1833
|
/**
|
|
1614
1834
|
* Create the modal DOM structure
|
|
1615
1835
|
*/
|
|
@@ -1677,9 +1897,9 @@ class LobbyModal {
|
|
|
1677
1897
|
// Title
|
|
1678
1898
|
const titleWp = this.createTitleSection();
|
|
1679
1899
|
leftWp.appendChild(titleWp);
|
|
1680
|
-
//
|
|
1681
|
-
const
|
|
1682
|
-
leftWp.appendChild(
|
|
1900
|
+
// Connected section (subtitle + link + share)
|
|
1901
|
+
const connectedSection = this.createConnectedSection();
|
|
1902
|
+
leftWp.appendChild(connectedSection);
|
|
1683
1903
|
// Offer
|
|
1684
1904
|
const offerBox = this.createOfferSection();
|
|
1685
1905
|
leftWp.appendChild(offerBox);
|
|
@@ -1691,24 +1911,35 @@ class LobbyModal {
|
|
|
1691
1911
|
createStatusSection() {
|
|
1692
1912
|
var _a;
|
|
1693
1913
|
const statusWp = document.createElement("div");
|
|
1694
|
-
statusWp.className = "lobby-status-
|
|
1914
|
+
statusWp.className = "lobby-status-section";
|
|
1695
1915
|
// Use safe defaults so UI never shows undefined values
|
|
1696
1916
|
const groupNum = (_a = this.data.groupNumber) !== null && _a !== void 0 ? _a : "1000";
|
|
1697
1917
|
const status = this.data.status === "complete" ? "complete" : "active";
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1918
|
+
// New "You are in the lobby" indicator badge
|
|
1919
|
+
const lobbyIndicator = document.createElement("div");
|
|
1920
|
+
lobbyIndicator.className = "lobby-indicator";
|
|
1921
|
+
const pulsingDot = document.createElement("div");
|
|
1922
|
+
pulsingDot.className = "pulsing-dot";
|
|
1923
|
+
const indicatorText = document.createElement("span");
|
|
1924
|
+
indicatorText.className = "indicator-text";
|
|
1925
|
+
indicatorText.textContent = "YOU ARE IN THE LOBBY";
|
|
1926
|
+
lobbyIndicator.appendChild(pulsingDot);
|
|
1927
|
+
lobbyIndicator.appendChild(indicatorText);
|
|
1928
|
+
// Status row with badge and group number
|
|
1929
|
+
const statusRow = document.createElement("div");
|
|
1930
|
+
statusRow.className = "lobby-status-wp";
|
|
1701
1931
|
// Render only the current status chip
|
|
1702
1932
|
const statusChip = document.createElement("div");
|
|
1703
1933
|
statusChip.className = `lobby-status ${status}`;
|
|
1704
1934
|
statusChip.id = "lobbyStatusChip";
|
|
1705
|
-
statusChip.textContent = status === "complete" ? "
|
|
1935
|
+
statusChip.textContent = status === "complete" ? "COMPLETED" : "ACTIVE";
|
|
1706
1936
|
const groupNumber = document.createElement("div");
|
|
1707
1937
|
groupNumber.className = "lobby-number";
|
|
1708
1938
|
groupNumber.textContent = `Group #${groupNum}`;
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
statusWp.appendChild(
|
|
1939
|
+
statusRow.appendChild(statusChip);
|
|
1940
|
+
statusRow.appendChild(groupNumber);
|
|
1941
|
+
statusWp.appendChild(lobbyIndicator);
|
|
1942
|
+
statusWp.appendChild(statusRow);
|
|
1712
1943
|
return statusWp;
|
|
1713
1944
|
}
|
|
1714
1945
|
/**
|
|
@@ -1721,24 +1952,48 @@ class LobbyModal {
|
|
|
1721
1952
|
const title = document.createElement("h2");
|
|
1722
1953
|
title.id = "lobbyTitleText";
|
|
1723
1954
|
title.textContent = titleContent.title;
|
|
1955
|
+
titleWp.appendChild(title);
|
|
1956
|
+
return titleWp;
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Create connected section (subtitle + link + share)
|
|
1960
|
+
*/
|
|
1961
|
+
createConnectedSection() {
|
|
1962
|
+
const connectedSection = document.createElement("div");
|
|
1963
|
+
connectedSection.className = "connected-section";
|
|
1964
|
+
connectedSection.id = "lobbyConnectedSection";
|
|
1965
|
+
const titleContent = this.getTitleText(this.data);
|
|
1966
|
+
// Subtitle
|
|
1724
1967
|
const subtitle = document.createElement("p");
|
|
1725
1968
|
subtitle.className = "sub-title";
|
|
1726
1969
|
subtitle.id = "lobbySubtitleText";
|
|
1727
1970
|
subtitle.textContent = titleContent.subtitle;
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1971
|
+
connectedSection.appendChild(subtitle);
|
|
1972
|
+
// Check if group is fulfilled and has offline redemption
|
|
1973
|
+
const isComplete = !this.computeIsLocked(this.data);
|
|
1974
|
+
if (isComplete &&
|
|
1975
|
+
this.data.offlineRedemption &&
|
|
1976
|
+
isValidOfflineRedemption(this.data.offlineRedemption)) {
|
|
1977
|
+
// Show offline redemption view instead of link/share
|
|
1978
|
+
const offlineSection = this.createOfflineRedemptionSection(this.data.offlineRedemption);
|
|
1979
|
+
connectedSection.appendChild(offlineSection);
|
|
1980
|
+
}
|
|
1981
|
+
else {
|
|
1982
|
+
// Show link and share container
|
|
1983
|
+
const linkShareContainer = document.createElement("div");
|
|
1984
|
+
linkShareContainer.className = "link-share-container";
|
|
1985
|
+
const linkWp = this.createLinkSection();
|
|
1986
|
+
linkShareContainer.appendChild(linkWp);
|
|
1987
|
+
connectedSection.appendChild(linkShareContainer);
|
|
1988
|
+
}
|
|
1989
|
+
return connectedSection;
|
|
1731
1990
|
}
|
|
1732
1991
|
/**
|
|
1733
|
-
* Create link section
|
|
1992
|
+
* Create link section (just the link box and share button, no wrapper)
|
|
1734
1993
|
*/
|
|
1735
1994
|
createLinkSection() {
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1738
|
-
linkWp.id = "lobbyLinkSection";
|
|
1739
|
-
if (!this.data.isLocked) {
|
|
1740
|
-
linkWp.style.display = "none";
|
|
1741
|
-
}
|
|
1995
|
+
const linkShareWrapper = document.createElement("div");
|
|
1996
|
+
linkShareWrapper.className = "link-share-wrapper";
|
|
1742
1997
|
// Link box
|
|
1743
1998
|
const linkBox = document.createElement("div");
|
|
1744
1999
|
linkBox.className = "lobby-link-box";
|
|
@@ -1753,23 +2008,243 @@ class LobbyModal {
|
|
|
1753
2008
|
linkUrl.textContent = this.data.groupLink || `cobuy.group/lobby/${this.data.productId}`;
|
|
1754
2009
|
linkBoxInr.appendChild(linkText);
|
|
1755
2010
|
linkBoxInr.appendChild(linkUrl);
|
|
1756
|
-
const copyBtn = document.createElement("
|
|
2011
|
+
const copyBtn = document.createElement("button");
|
|
1757
2012
|
copyBtn.className = "copy-link-btn";
|
|
1758
|
-
copyBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy
|
|
2013
|
+
copyBtn.innerHTML = `<span class="copy-text">Copy</span><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg>`;
|
|
1759
2014
|
copyBtn.addEventListener("click", () => this.copyLink());
|
|
1760
2015
|
linkBox.appendChild(linkBoxInr);
|
|
1761
2016
|
linkBox.appendChild(copyBtn);
|
|
1762
2017
|
// Share button
|
|
1763
|
-
const
|
|
1764
|
-
shareBtn.className = "share-btn-wp";
|
|
1765
|
-
const shareBtnInner = document.createElement("div");
|
|
2018
|
+
const shareBtnInner = document.createElement("button");
|
|
1766
2019
|
shareBtnInner.className = "share-btn";
|
|
1767
|
-
shareBtnInner.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-share
|
|
2020
|
+
shareBtnInner.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-share"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path><polyline points="16 6 12 2 8 6"></polyline><line x1="12" x2="12" y1="2" y2="15"></line></svg><span class="share-text">Share</span>`;
|
|
1768
2021
|
shareBtnInner.addEventListener("click", () => this.share());
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
2022
|
+
linkShareWrapper.appendChild(linkBox);
|
|
2023
|
+
linkShareWrapper.appendChild(shareBtnInner);
|
|
2024
|
+
return linkShareWrapper;
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Create offline redemption section
|
|
2028
|
+
*/
|
|
2029
|
+
createOfflineRedemptionSection(offlineRedemption) {
|
|
2030
|
+
const section = document.createElement("div");
|
|
2031
|
+
section.className = "offline-redemption-section";
|
|
2032
|
+
section.id = "lobbyOfflineRedemptionSection";
|
|
2033
|
+
// QR Code container
|
|
2034
|
+
const qrContainer = document.createElement("div");
|
|
2035
|
+
qrContainer.className = "offline-qr-container";
|
|
2036
|
+
const qrImage = document.createElement("img");
|
|
2037
|
+
qrImage.className = "offline-qr-image";
|
|
2038
|
+
qrImage.alt = "Redemption QR Code";
|
|
2039
|
+
qrImage.src = buildQRUrl(offlineRedemption.qr_code_data);
|
|
2040
|
+
// Handle QR image load errors
|
|
2041
|
+
qrImage.addEventListener("error", () => {
|
|
2042
|
+
qrImage.style.display = "none";
|
|
2043
|
+
const fallbackText = document.createElement("div");
|
|
2044
|
+
fallbackText.className = "offline-qr-fallback";
|
|
2045
|
+
fallbackText.textContent = "QR Code Unavailable";
|
|
2046
|
+
qrContainer.appendChild(fallbackText);
|
|
2047
|
+
});
|
|
2048
|
+
qrContainer.appendChild(qrImage);
|
|
2049
|
+
// Redemption code box
|
|
2050
|
+
const codeBox = document.createElement("div");
|
|
2051
|
+
codeBox.className = "offline-code-box";
|
|
2052
|
+
const codeLabel = document.createElement("p");
|
|
2053
|
+
codeLabel.className = "offline-code-label";
|
|
2054
|
+
codeLabel.textContent = "Redemption Code";
|
|
2055
|
+
const codeValue = document.createElement("div");
|
|
2056
|
+
codeValue.className = "offline-code-value";
|
|
2057
|
+
codeValue.textContent = offlineRedemption.redemption_code;
|
|
2058
|
+
const copyCodeBtn = document.createElement("button");
|
|
2059
|
+
copyCodeBtn.className = "offline-copy-code-btn";
|
|
2060
|
+
copyCodeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg>`;
|
|
2061
|
+
copyCodeBtn.addEventListener("click", () => this.copyOfflineRedemptionCode(offlineRedemption.redemption_code, copyCodeBtn));
|
|
2062
|
+
codeBox.appendChild(codeLabel);
|
|
2063
|
+
codeBox.appendChild(codeValue);
|
|
2064
|
+
codeBox.appendChild(copyCodeBtn);
|
|
2065
|
+
// Expiry info
|
|
2066
|
+
const expiryInfo = document.createElement("div");
|
|
2067
|
+
expiryInfo.className = "offline-expiry-info";
|
|
2068
|
+
const expiryLabel = document.createElement("p");
|
|
2069
|
+
expiryLabel.className = "offline-expiry-label";
|
|
2070
|
+
expiryLabel.textContent = "Valid Until";
|
|
2071
|
+
const expiryValue = document.createElement("p");
|
|
2072
|
+
expiryValue.className = "offline-expiry-value";
|
|
2073
|
+
expiryValue.textContent = formatExpiryDate(offlineRedemption.offline_expires_at);
|
|
2074
|
+
expiryInfo.appendChild(expiryLabel);
|
|
2075
|
+
expiryInfo.appendChild(expiryValue);
|
|
2076
|
+
// Download QR button
|
|
2077
|
+
const downloadQRBtn = document.createElement("button");
|
|
2078
|
+
downloadQRBtn.className = "offline-download-qr-btn";
|
|
2079
|
+
downloadQRBtn.textContent = "Download QR";
|
|
2080
|
+
downloadQRBtn.addEventListener("click", () => this.downloadOfflineQR(offlineRedemption, downloadQRBtn));
|
|
2081
|
+
section.appendChild(qrContainer);
|
|
2082
|
+
section.appendChild(codeBox);
|
|
2083
|
+
section.appendChild(expiryInfo);
|
|
2084
|
+
section.appendChild(downloadQRBtn);
|
|
2085
|
+
// Inject styles for offline redemption section
|
|
2086
|
+
this.injectOfflineRedemptionStyles();
|
|
2087
|
+
return section;
|
|
2088
|
+
}
|
|
2089
|
+
/**
|
|
2090
|
+
* Handle copying offline redemption code
|
|
2091
|
+
*/
|
|
2092
|
+
async copyOfflineRedemptionCode(code, button) {
|
|
2093
|
+
const originalContent = button.innerHTML;
|
|
2094
|
+
const success = await copyRedemptionCode(code);
|
|
2095
|
+
if (success) {
|
|
2096
|
+
button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
|
|
2097
|
+
setTimeout(() => {
|
|
2098
|
+
button.innerHTML = originalContent;
|
|
2099
|
+
}, 2000);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Handle downloading offline QR code
|
|
2104
|
+
*/
|
|
2105
|
+
async downloadOfflineQR(offlineRedemption, button) {
|
|
2106
|
+
const originalText = button.textContent;
|
|
2107
|
+
button.disabled = true;
|
|
2108
|
+
button.textContent = "Downloading...";
|
|
2109
|
+
const success = await downloadQRCode(buildQRUrl(offlineRedemption.qr_code_data), "redemption-qr.png");
|
|
2110
|
+
button.textContent = success ? "Downloaded!" : "Failed";
|
|
2111
|
+
setTimeout(() => {
|
|
2112
|
+
button.textContent = originalText;
|
|
2113
|
+
button.disabled = false;
|
|
2114
|
+
}, 2000);
|
|
2115
|
+
}
|
|
2116
|
+
/**
|
|
2117
|
+
* Inject styles for offline redemption section
|
|
2118
|
+
*/
|
|
2119
|
+
injectOfflineRedemptionStyles() {
|
|
2120
|
+
if (document.getElementById("lobby-offline-redemption-styles")) {
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
const style = document.createElement("style");
|
|
2124
|
+
style.id = "lobby-offline-redemption-styles";
|
|
2125
|
+
style.textContent = `
|
|
2126
|
+
.offline-redemption-section {
|
|
2127
|
+
display: flex;
|
|
2128
|
+
flex-direction: column;
|
|
2129
|
+
gap: 16px;
|
|
2130
|
+
padding: 16px 0;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
.offline-qr-container {
|
|
2134
|
+
display: flex;
|
|
2135
|
+
justify-content: center;
|
|
2136
|
+
background: #f9fafb;
|
|
2137
|
+
padding: 12px;
|
|
2138
|
+
border-radius: 8px;
|
|
2139
|
+
min-height: 160px;
|
|
2140
|
+
align-items: center;
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
.offline-qr-image {
|
|
2144
|
+
max-width: 140px;
|
|
2145
|
+
max-height: 140px;
|
|
2146
|
+
width: auto;
|
|
2147
|
+
height: auto;
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
.offline-qr-fallback {
|
|
2151
|
+
color: #9ca3af;
|
|
2152
|
+
font-size: 12px;
|
|
2153
|
+
text-align: center;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
.offline-code-box {
|
|
2157
|
+
display: flex;
|
|
2158
|
+
flex-direction: column;
|
|
2159
|
+
gap: 6px;
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
.offline-code-label {
|
|
2163
|
+
margin: 0;
|
|
2164
|
+
font-size: 11px;
|
|
2165
|
+
font-weight: 600;
|
|
2166
|
+
color: #6b7280;
|
|
2167
|
+
text-transform: uppercase;
|
|
2168
|
+
letter-spacing: 0.5px;
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
.offline-code-value {
|
|
2172
|
+
font-family: monospace;
|
|
2173
|
+
font-size: 14px;
|
|
2174
|
+
font-weight: 600;
|
|
2175
|
+
color: #111827;
|
|
2176
|
+
padding: 10px 12px;
|
|
2177
|
+
background: #f9fafb;
|
|
2178
|
+
border: 1px solid #e5e7eb;
|
|
2179
|
+
border-radius: 6px;
|
|
2180
|
+
text-align: center;
|
|
2181
|
+
letter-spacing: 1px;
|
|
2182
|
+
position: relative;
|
|
2183
|
+
display: flex;
|
|
2184
|
+
align-items: center;
|
|
2185
|
+
justify-content: center;
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
.offline-copy-code-btn {
|
|
2189
|
+
position: absolute;
|
|
2190
|
+
right: 8px;
|
|
2191
|
+
background: none;
|
|
2192
|
+
border: none;
|
|
2193
|
+
padding: 4px 8px;
|
|
2194
|
+
cursor: pointer;
|
|
2195
|
+
color: #6b7280;
|
|
2196
|
+
display: flex;
|
|
2197
|
+
align-items: center;
|
|
2198
|
+
transition: color 0.2s;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
.offline-copy-code-btn:hover {
|
|
2202
|
+
color: #111827;
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
.offline-expiry-info {
|
|
2206
|
+
display: flex;
|
|
2207
|
+
flex-direction: column;
|
|
2208
|
+
gap: 4px;
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
.offline-expiry-label {
|
|
2212
|
+
margin: 0;
|
|
2213
|
+
font-size: 11px;
|
|
2214
|
+
font-weight: 600;
|
|
2215
|
+
color: #6b7280;
|
|
2216
|
+
text-transform: uppercase;
|
|
2217
|
+
letter-spacing: 0.5px;
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
.offline-expiry-value {
|
|
2221
|
+
margin: 0;
|
|
2222
|
+
font-size: 13px;
|
|
2223
|
+
color: #374151;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
.offline-download-qr-btn {
|
|
2227
|
+
padding: 10px 16px;
|
|
2228
|
+
background: #3b82f6;
|
|
2229
|
+
color: white;
|
|
2230
|
+
border: none;
|
|
2231
|
+
border-radius: 6px;
|
|
2232
|
+
font-size: 13px;
|
|
2233
|
+
font-weight: 600;
|
|
2234
|
+
cursor: pointer;
|
|
2235
|
+
transition: background 0.2s;
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
.offline-download-qr-btn:hover:not(:disabled) {
|
|
2239
|
+
background: #2563eb;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
.offline-download-qr-btn:disabled {
|
|
2243
|
+
opacity: 0.7;
|
|
2244
|
+
cursor: not-allowed;
|
|
2245
|
+
}
|
|
2246
|
+
`;
|
|
2247
|
+
document.head.appendChild(style);
|
|
1773
2248
|
}
|
|
1774
2249
|
/**
|
|
1775
2250
|
* Create offer section
|
|
@@ -2408,24 +2883,27 @@ class LobbyModal {
|
|
|
2408
2883
|
groupNumber: this.data.groupNumber,
|
|
2409
2884
|
status: this.data.status,
|
|
2410
2885
|
});
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
this.socketManager.
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2886
|
+
// Show entrance animation, then open modal after animation completes
|
|
2887
|
+
this.showEntranceAnimation(() => {
|
|
2888
|
+
this.modalElement = this.createModalStructure();
|
|
2889
|
+
document.body.appendChild(this.modalElement);
|
|
2890
|
+
// Subscribe to realtime socket events
|
|
2891
|
+
this.subscribeToSocketEvents();
|
|
2892
|
+
// If socket is available and connected, subscribe to this group
|
|
2893
|
+
if (this.socketManager && this.socketManager.isConnected() && this.currentGroupId) {
|
|
2894
|
+
this.logger.info(`[LobbyModal] Socket connected, subscribing to group ${this.currentGroupId}`);
|
|
2895
|
+
this.socketManager.subscribeToGroup(this.currentGroupId);
|
|
2896
|
+
}
|
|
2897
|
+
else if (this.socketManager && !this.socketManager.isConnected()) {
|
|
2898
|
+
this.logger.warn("[LobbyModal] Socket manager not connected yet");
|
|
2899
|
+
}
|
|
2900
|
+
// Start timers and animations
|
|
2901
|
+
this.startTimer();
|
|
2902
|
+
this.startActivityAnimation();
|
|
2903
|
+
// Track analytics
|
|
2904
|
+
if (this.analyticsClient) ;
|
|
2905
|
+
this.logger.info(`Lobby modal opened for product: ${this.data.productId}`);
|
|
2906
|
+
});
|
|
2429
2907
|
}
|
|
2430
2908
|
/**
|
|
2431
2909
|
* Close the modal
|
|
@@ -2471,6 +2949,10 @@ class LobbyModal {
|
|
|
2471
2949
|
this.updateLockUI(!!this.data.isLocked);
|
|
2472
2950
|
this.updateTitleUI(this.data);
|
|
2473
2951
|
this.updateLinkVisibility(!!this.data.isLocked);
|
|
2952
|
+
// Update offline redemption visibility if data changed
|
|
2953
|
+
if (data.offlineRedemption !== undefined || data.status !== undefined) {
|
|
2954
|
+
this.updateOfflineRedemptionVisibility();
|
|
2955
|
+
}
|
|
2474
2956
|
// Update progress if changed
|
|
2475
2957
|
if (data.progress !== undefined) {
|
|
2476
2958
|
const progressFill = document.getElementById("progressFill");
|
|
@@ -2545,6 +3027,42 @@ class LobbyModal {
|
|
|
2545
3027
|
return;
|
|
2546
3028
|
linkSection.style.display = isLocked ? "" : "none";
|
|
2547
3029
|
}
|
|
3030
|
+
/**
|
|
3031
|
+
* Update offline redemption visibility when group is fulfilled
|
|
3032
|
+
*/
|
|
3033
|
+
updateOfflineRedemptionVisibility() {
|
|
3034
|
+
const connectedSection = document.getElementById("lobbyConnectedSection");
|
|
3035
|
+
if (!connectedSection)
|
|
3036
|
+
return;
|
|
3037
|
+
const isComplete = !this.computeIsLocked(this.data);
|
|
3038
|
+
const hasOfflineRedemption = this.data.offlineRedemption && isValidOfflineRedemption(this.data.offlineRedemption);
|
|
3039
|
+
// Get existing elements
|
|
3040
|
+
const existingOffline = connectedSection.querySelector(".offline-redemption-section");
|
|
3041
|
+
const existingLink = connectedSection.querySelector(".link-share-container");
|
|
3042
|
+
if (isComplete && hasOfflineRedemption) {
|
|
3043
|
+
// Show offline redemption, hide link/share
|
|
3044
|
+
if (existingLink) {
|
|
3045
|
+
existingLink.remove();
|
|
3046
|
+
}
|
|
3047
|
+
if (!existingOffline) {
|
|
3048
|
+
const offlineSection = this.createOfflineRedemptionSection(this.data.offlineRedemption);
|
|
3049
|
+
connectedSection.appendChild(offlineSection);
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
else {
|
|
3053
|
+
// Show link/share, hide offline redemption
|
|
3054
|
+
if (existingOffline) {
|
|
3055
|
+
existingOffline.remove();
|
|
3056
|
+
}
|
|
3057
|
+
if (!existingLink) {
|
|
3058
|
+
const linkShareContainer = document.createElement("div");
|
|
3059
|
+
linkShareContainer.className = "link-share-container";
|
|
3060
|
+
const linkWp = this.createLinkSection();
|
|
3061
|
+
linkShareContainer.appendChild(linkWp);
|
|
3062
|
+
connectedSection.appendChild(linkShareContainer);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
2548
3066
|
/**
|
|
2549
3067
|
* Subscribe to socket events
|
|
2550
3068
|
*/
|
|
@@ -2581,7 +3099,7 @@ class LobbyModal {
|
|
|
2581
3099
|
break;
|
|
2582
3100
|
case "group:fulfilled":
|
|
2583
3101
|
emoji = "🎁";
|
|
2584
|
-
action = "unlocked the
|
|
3102
|
+
action = "unlocked the discount – group filled!";
|
|
2585
3103
|
break;
|
|
2586
3104
|
case "group:created":
|
|
2587
3105
|
emoji = "🛒";
|
|
@@ -2649,6 +3167,405 @@ class LobbyModal {
|
|
|
2649
3167
|
}
|
|
2650
3168
|
}
|
|
2651
3169
|
|
|
3170
|
+
/**
|
|
3171
|
+
* Offline Redemption Modal
|
|
3172
|
+
* Displays QR code and redemption code for fulfilled groups
|
|
3173
|
+
*/
|
|
3174
|
+
class OfflineRedemptionModal {
|
|
3175
|
+
constructor(offlineRedemption, onClose) {
|
|
3176
|
+
this.modalElement = null;
|
|
3177
|
+
this.offlineRedemption = offlineRedemption;
|
|
3178
|
+
this.onClose = onClose;
|
|
3179
|
+
}
|
|
3180
|
+
/**
|
|
3181
|
+
* Open the modal
|
|
3182
|
+
*/
|
|
3183
|
+
open() {
|
|
3184
|
+
if (this.modalElement && document.body.contains(this.modalElement)) {
|
|
3185
|
+
return; // Already open
|
|
3186
|
+
}
|
|
3187
|
+
this.modalElement = this.createModalStructure();
|
|
3188
|
+
document.body.appendChild(this.modalElement);
|
|
3189
|
+
// Trigger entrance animation
|
|
3190
|
+
requestAnimationFrame(() => {
|
|
3191
|
+
var _a;
|
|
3192
|
+
(_a = this.modalElement) === null || _a === void 0 ? void 0 : _a.classList.add("offline-redemption-modal-open");
|
|
3193
|
+
});
|
|
3194
|
+
}
|
|
3195
|
+
/**
|
|
3196
|
+
* Close the modal
|
|
3197
|
+
*/
|
|
3198
|
+
close() {
|
|
3199
|
+
if (!this.modalElement)
|
|
3200
|
+
return;
|
|
3201
|
+
this.modalElement.classList.remove("offline-redemption-modal-open");
|
|
3202
|
+
// Remove after animation completes
|
|
3203
|
+
setTimeout(() => {
|
|
3204
|
+
var _a;
|
|
3205
|
+
if (this.modalElement && document.body.contains(this.modalElement)) {
|
|
3206
|
+
document.body.removeChild(this.modalElement);
|
|
3207
|
+
this.modalElement = null;
|
|
3208
|
+
}
|
|
3209
|
+
(_a = this.onClose) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
3210
|
+
}, 300);
|
|
3211
|
+
}
|
|
3212
|
+
/**
|
|
3213
|
+
* Create the modal DOM structure
|
|
3214
|
+
*/
|
|
3215
|
+
createModalStructure() {
|
|
3216
|
+
const container = document.createElement("div");
|
|
3217
|
+
container.className = "offline-redemption-modal-overlay";
|
|
3218
|
+
// Backdrop
|
|
3219
|
+
const backdrop = document.createElement("div");
|
|
3220
|
+
backdrop.className = "offline-redemption-backdrop";
|
|
3221
|
+
backdrop.addEventListener("click", () => this.close());
|
|
3222
|
+
// Modal content
|
|
3223
|
+
const modal = document.createElement("div");
|
|
3224
|
+
modal.className = "offline-redemption-modal";
|
|
3225
|
+
// Close button
|
|
3226
|
+
const closeBtn = document.createElement("button");
|
|
3227
|
+
closeBtn.className = "offline-redemption-close-btn";
|
|
3228
|
+
closeBtn.setAttribute("aria-label", "Close modal");
|
|
3229
|
+
closeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>`;
|
|
3230
|
+
closeBtn.addEventListener("click", () => this.close());
|
|
3231
|
+
// Header
|
|
3232
|
+
const header = document.createElement("div");
|
|
3233
|
+
header.className = "offline-redemption-header";
|
|
3234
|
+
const title = document.createElement("h2");
|
|
3235
|
+
title.className = "offline-redemption-title";
|
|
3236
|
+
title.textContent = "Redeem In-Store";
|
|
3237
|
+
header.appendChild(title);
|
|
3238
|
+
header.appendChild(closeBtn);
|
|
3239
|
+
// Body
|
|
3240
|
+
const body = document.createElement("div");
|
|
3241
|
+
body.className = "offline-redemption-body";
|
|
3242
|
+
// QR Code section
|
|
3243
|
+
const qrSection = document.createElement("div");
|
|
3244
|
+
qrSection.className = "offline-redemption-qr-section";
|
|
3245
|
+
const qrImage = document.createElement("img");
|
|
3246
|
+
qrImage.className = "offline-redemption-qr-image";
|
|
3247
|
+
qrImage.alt = "Redemption QR Code";
|
|
3248
|
+
qrImage.src = buildQRUrl(this.offlineRedemption.qr_code_data);
|
|
3249
|
+
// QR image fallback handler
|
|
3250
|
+
qrImage.addEventListener("error", () => {
|
|
3251
|
+
qrImage.style.display = "none";
|
|
3252
|
+
const errorMsg = document.createElement("div");
|
|
3253
|
+
errorMsg.className = "offline-redemption-qr-error";
|
|
3254
|
+
errorMsg.textContent = "QR code unavailable";
|
|
3255
|
+
qrSection.appendChild(errorMsg);
|
|
3256
|
+
});
|
|
3257
|
+
qrSection.appendChild(qrImage);
|
|
3258
|
+
// Redemption code section
|
|
3259
|
+
const codeSection = document.createElement("div");
|
|
3260
|
+
codeSection.className = "offline-redemption-code-section";
|
|
3261
|
+
const codeLabel = document.createElement("p");
|
|
3262
|
+
codeLabel.className = "offline-redemption-code-label";
|
|
3263
|
+
codeLabel.textContent = "Redemption Code:";
|
|
3264
|
+
const codeValue = document.createElement("div");
|
|
3265
|
+
codeValue.className = "offline-redemption-code-value";
|
|
3266
|
+
codeValue.textContent = this.offlineRedemption.redemption_code;
|
|
3267
|
+
codeSection.appendChild(codeLabel);
|
|
3268
|
+
codeSection.appendChild(codeValue);
|
|
3269
|
+
// Expiry section
|
|
3270
|
+
const expirySection = document.createElement("div");
|
|
3271
|
+
expirySection.className = "offline-redemption-expiry-section";
|
|
3272
|
+
const expiryLabel = document.createElement("p");
|
|
3273
|
+
expiryLabel.className = "offline-redemption-expiry-label";
|
|
3274
|
+
expiryLabel.textContent = "Valid Until:";
|
|
3275
|
+
const expiryValue = document.createElement("p");
|
|
3276
|
+
expiryValue.className = "offline-redemption-expiry-value";
|
|
3277
|
+
const isValid = isRedemptionValid(this.offlineRedemption.offline_expires_at);
|
|
3278
|
+
expiryValue.textContent = formatExpiryDate(this.offlineRedemption.offline_expires_at);
|
|
3279
|
+
if (!isValid) {
|
|
3280
|
+
expiryValue.classList.add("expired");
|
|
3281
|
+
}
|
|
3282
|
+
expirySection.appendChild(expiryLabel);
|
|
3283
|
+
expirySection.appendChild(expiryValue);
|
|
3284
|
+
body.appendChild(qrSection);
|
|
3285
|
+
body.appendChild(codeSection);
|
|
3286
|
+
body.appendChild(expirySection);
|
|
3287
|
+
// Actions
|
|
3288
|
+
const actions = document.createElement("div");
|
|
3289
|
+
actions.className = "offline-redemption-actions";
|
|
3290
|
+
const downloadBtn = document.createElement("button");
|
|
3291
|
+
downloadBtn.className = "offline-redemption-action-btn download-btn";
|
|
3292
|
+
downloadBtn.textContent = "Download QR";
|
|
3293
|
+
downloadBtn.addEventListener("click", () => this.handleDownloadQR(downloadBtn));
|
|
3294
|
+
const copyBtn = document.createElement("button");
|
|
3295
|
+
copyBtn.className = "offline-redemption-action-btn copy-btn";
|
|
3296
|
+
copyBtn.textContent = "Copy Code";
|
|
3297
|
+
copyBtn.addEventListener("click", () => this.handleCopyCode(copyBtn));
|
|
3298
|
+
actions.appendChild(downloadBtn);
|
|
3299
|
+
actions.appendChild(copyBtn);
|
|
3300
|
+
// Assemble modal
|
|
3301
|
+
modal.appendChild(header);
|
|
3302
|
+
modal.appendChild(body);
|
|
3303
|
+
modal.appendChild(actions);
|
|
3304
|
+
container.appendChild(backdrop);
|
|
3305
|
+
container.appendChild(modal);
|
|
3306
|
+
// Inject styles
|
|
3307
|
+
this.injectStyles();
|
|
3308
|
+
return container;
|
|
3309
|
+
}
|
|
3310
|
+
/**
|
|
3311
|
+
* Handle download QR button click
|
|
3312
|
+
*/
|
|
3313
|
+
async handleDownloadQR(button) {
|
|
3314
|
+
const originalText = button.textContent;
|
|
3315
|
+
button.disabled = true;
|
|
3316
|
+
button.textContent = "Downloading...";
|
|
3317
|
+
const success = await downloadQRCode(buildQRUrl(this.offlineRedemption.qr_code_data), "redemption-qr.png");
|
|
3318
|
+
button.textContent = success ? "Downloaded!" : "Download Failed";
|
|
3319
|
+
setTimeout(() => {
|
|
3320
|
+
button.textContent = originalText;
|
|
3321
|
+
button.disabled = false;
|
|
3322
|
+
}, 2000);
|
|
3323
|
+
}
|
|
3324
|
+
/**
|
|
3325
|
+
* Handle copy code button click
|
|
3326
|
+
*/
|
|
3327
|
+
async handleCopyCode(button) {
|
|
3328
|
+
const originalText = button.textContent;
|
|
3329
|
+
button.disabled = true;
|
|
3330
|
+
const success = await copyRedemptionCode(this.offlineRedemption.redemption_code);
|
|
3331
|
+
button.textContent = success ? "Copied!" : "Copy Failed";
|
|
3332
|
+
setTimeout(() => {
|
|
3333
|
+
button.textContent = originalText;
|
|
3334
|
+
button.disabled = false;
|
|
3335
|
+
}, 2000);
|
|
3336
|
+
}
|
|
3337
|
+
/**
|
|
3338
|
+
* Inject modal styles
|
|
3339
|
+
*/
|
|
3340
|
+
injectStyles() {
|
|
3341
|
+
// Check if styles already injected
|
|
3342
|
+
if (document.getElementById("offline-redemption-modal-styles")) {
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3345
|
+
const style = document.createElement("style");
|
|
3346
|
+
style.id = "offline-redemption-modal-styles";
|
|
3347
|
+
style.textContent = `
|
|
3348
|
+
.offline-redemption-modal-overlay {
|
|
3349
|
+
position: fixed;
|
|
3350
|
+
top: 0;
|
|
3351
|
+
left: 0;
|
|
3352
|
+
right: 0;
|
|
3353
|
+
bottom: 0;
|
|
3354
|
+
display: flex;
|
|
3355
|
+
align-items: center;
|
|
3356
|
+
justify-content: center;
|
|
3357
|
+
z-index: 10000;
|
|
3358
|
+
opacity: 0;
|
|
3359
|
+
transition: opacity 0.3s ease-out;
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
.offline-redemption-modal-overlay.offline-redemption-modal-open {
|
|
3363
|
+
opacity: 1;
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
.offline-redemption-backdrop {
|
|
3367
|
+
position: absolute;
|
|
3368
|
+
top: 0;
|
|
3369
|
+
left: 0;
|
|
3370
|
+
right: 0;
|
|
3371
|
+
bottom: 0;
|
|
3372
|
+
background: rgba(0, 0, 0, 0.5);
|
|
3373
|
+
cursor: pointer;
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
.offline-redemption-modal {
|
|
3377
|
+
position: relative;
|
|
3378
|
+
z-index: 10001;
|
|
3379
|
+
background: white;
|
|
3380
|
+
border-radius: 12px;
|
|
3381
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
|
3382
|
+
max-width: 400px;
|
|
3383
|
+
width: 90%;
|
|
3384
|
+
max-height: 90vh;
|
|
3385
|
+
overflow-y: auto;
|
|
3386
|
+
animation: slideUp 0.3s ease-out;
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
@keyframes slideUp {
|
|
3390
|
+
from {
|
|
3391
|
+
opacity: 0;
|
|
3392
|
+
transform: translateY(40px);
|
|
3393
|
+
}
|
|
3394
|
+
to {
|
|
3395
|
+
opacity: 1;
|
|
3396
|
+
transform: translateY(0);
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
.offline-redemption-header {
|
|
3401
|
+
display: flex;
|
|
3402
|
+
justify-content: space-between;
|
|
3403
|
+
align-items: center;
|
|
3404
|
+
padding: 20px;
|
|
3405
|
+
border-bottom: 1px solid #f0f0f0;
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
.offline-redemption-title {
|
|
3409
|
+
margin: 0;
|
|
3410
|
+
font-size: 20px;
|
|
3411
|
+
font-weight: 600;
|
|
3412
|
+
color: #111827;
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
.offline-redemption-close-btn {
|
|
3416
|
+
background: none;
|
|
3417
|
+
border: none;
|
|
3418
|
+
padding: 0;
|
|
3419
|
+
cursor: pointer;
|
|
3420
|
+
display: flex;
|
|
3421
|
+
align-items: center;
|
|
3422
|
+
justify-content: center;
|
|
3423
|
+
color: #6b7280;
|
|
3424
|
+
transition: color 0.2s;
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
.offline-redemption-close-btn:hover {
|
|
3428
|
+
color: #111827;
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
.offline-redemption-close-btn svg {
|
|
3432
|
+
width: 24px;
|
|
3433
|
+
height: 24px;
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
.offline-redemption-body {
|
|
3437
|
+
padding: 24px 20px;
|
|
3438
|
+
display: flex;
|
|
3439
|
+
flex-direction: column;
|
|
3440
|
+
gap: 20px;
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
.offline-redemption-qr-section {
|
|
3444
|
+
display: flex;
|
|
3445
|
+
justify-content: center;
|
|
3446
|
+
background: #f9fafb;
|
|
3447
|
+
padding: 16px;
|
|
3448
|
+
border-radius: 8px;
|
|
3449
|
+
min-height: 200px;
|
|
3450
|
+
align-items: center;
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
.offline-redemption-qr-image {
|
|
3454
|
+
max-width: 100%;
|
|
3455
|
+
max-height: 200px;
|
|
3456
|
+
width: auto;
|
|
3457
|
+
height: auto;
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
.offline-redemption-qr-error {
|
|
3461
|
+
text-align: center;
|
|
3462
|
+
color: #6b7280;
|
|
3463
|
+
font-size: 14px;
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
.offline-redemption-code-section,
|
|
3467
|
+
.offline-redemption-expiry-section {
|
|
3468
|
+
display: flex;
|
|
3469
|
+
flex-direction: column;
|
|
3470
|
+
gap: 8px;
|
|
3471
|
+
}
|
|
3472
|
+
|
|
3473
|
+
.offline-redemption-code-label,
|
|
3474
|
+
.offline-redemption-expiry-label {
|
|
3475
|
+
margin: 0;
|
|
3476
|
+
font-size: 12px;
|
|
3477
|
+
font-weight: 600;
|
|
3478
|
+
color: #6b7280;
|
|
3479
|
+
text-transform: uppercase;
|
|
3480
|
+
letter-spacing: 0.5px;
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
.offline-redemption-code-value {
|
|
3484
|
+
font-family: monospace;
|
|
3485
|
+
font-size: 16px;
|
|
3486
|
+
font-weight: 600;
|
|
3487
|
+
color: #111827;
|
|
3488
|
+
padding: 12px;
|
|
3489
|
+
background: #f9fafb;
|
|
3490
|
+
border: 1px solid #e5e7eb;
|
|
3491
|
+
border-radius: 6px;
|
|
3492
|
+
text-align: center;
|
|
3493
|
+
letter-spacing: 2px;
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3496
|
+
.offline-redemption-expiry-value {
|
|
3497
|
+
margin: 0;
|
|
3498
|
+
font-size: 14px;
|
|
3499
|
+
color: #374151;
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
.offline-redemption-expiry-value.expired {
|
|
3503
|
+
color: #dc2626;
|
|
3504
|
+
font-weight: 600;
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
.offline-redemption-actions {
|
|
3508
|
+
display: flex;
|
|
3509
|
+
gap: 12px;
|
|
3510
|
+
padding: 20px;
|
|
3511
|
+
border-top: 1px solid #f0f0f0;
|
|
3512
|
+
}
|
|
3513
|
+
|
|
3514
|
+
.offline-redemption-action-btn {
|
|
3515
|
+
flex: 1;
|
|
3516
|
+
padding: 12px 16px;
|
|
3517
|
+
border: none;
|
|
3518
|
+
border-radius: 6px;
|
|
3519
|
+
font-size: 14px;
|
|
3520
|
+
font-weight: 600;
|
|
3521
|
+
cursor: pointer;
|
|
3522
|
+
transition: all 0.2s;
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
.download-btn {
|
|
3526
|
+
background: #3b82f6;
|
|
3527
|
+
color: white;
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3530
|
+
.download-btn:hover:not(:disabled) {
|
|
3531
|
+
background: #2563eb;
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
.copy-btn {
|
|
3535
|
+
background: #f3f4f6;
|
|
3536
|
+
color: #111827;
|
|
3537
|
+
border: 1px solid #e5e7eb;
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
.copy-btn:hover:not(:disabled) {
|
|
3541
|
+
background: #e5e7eb;
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
.offline-redemption-action-btn:disabled {
|
|
3545
|
+
opacity: 0.7;
|
|
3546
|
+
cursor: not-allowed;
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3549
|
+
/* Mobile responsiveness */
|
|
3550
|
+
@media (max-width: 480px) {
|
|
3551
|
+
.offline-redemption-modal {
|
|
3552
|
+
width: 95%;
|
|
3553
|
+
max-height: 95vh;
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
.offline-redemption-body {
|
|
3557
|
+
padding: 16px;
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
.offline-redemption-actions {
|
|
3561
|
+
padding: 16px;
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
`;
|
|
3565
|
+
document.head.appendChild(style);
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
|
|
2652
3569
|
/// <reference lib="dom" />
|
|
2653
3570
|
/**
|
|
2654
3571
|
* Widget state enumeration for tracking render lifecycle
|
|
@@ -2683,6 +3600,8 @@ class WidgetRoot {
|
|
|
2683
3600
|
this.lastGroupDataRefreshTime = 0;
|
|
2684
3601
|
this.GROUP_REFRESH_DEBOUNCE = 1000; // 1 second min between refreshes
|
|
2685
3602
|
this.groupExpiryRefreshTriggered = false; // Track if expiry refresh already triggered for current group
|
|
3603
|
+
this.offlineRedemption = null;
|
|
3604
|
+
this.offlineRedemptionModal = null;
|
|
2686
3605
|
/** Handle backend fulfillment notifications */
|
|
2687
3606
|
this.handleGroupFulfilledEvent = (event) => {
|
|
2688
3607
|
const detail = event.detail;
|
|
@@ -2839,11 +3758,22 @@ class WidgetRoot {
|
|
|
2839
3758
|
}
|
|
2840
3759
|
/** Persist fulfilled state, emit callback, and refresh UI */
|
|
2841
3760
|
processGroupFulfilled(eventData) {
|
|
2842
|
-
var _a, _b;
|
|
3761
|
+
var _a, _b, _c, _d;
|
|
2843
3762
|
this.groupFulfilled = true;
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
3763
|
+
// Extract reward from new event structure or fallback to legacy
|
|
3764
|
+
const reward = ((_a = eventData.frozen_reward) === null || _a === void 0 ? void 0 : _a.reward) || eventData.reward || null;
|
|
3765
|
+
this.frozenReward = reward;
|
|
3766
|
+
// Extract group ID from new event structure or fallback to legacy
|
|
3767
|
+
this.currentGroupId = ((_b = eventData.group) === null || _b === void 0 ? void 0 : _b.id) || eventData.groupId || null;
|
|
3768
|
+
// Store offline redemption data if available
|
|
3769
|
+
if (eventData.offline_redemption && isValidOfflineRedemption(eventData.offline_redemption)) {
|
|
3770
|
+
this.offlineRedemption = eventData.offline_redemption;
|
|
3771
|
+
this.logger.info("Offline redemption data stored", {
|
|
3772
|
+
member_id: this.offlineRedemption.member_id,
|
|
3773
|
+
code: this.offlineRedemption.redemption_code,
|
|
3774
|
+
});
|
|
3775
|
+
}
|
|
3776
|
+
(_d = (_c = this.events) === null || _c === void 0 ? void 0 : _c.onGroupFulfilled) === null || _d === void 0 ? void 0 : _d.call(_c, eventData);
|
|
2847
3777
|
this.renderFulfilledState();
|
|
2848
3778
|
}
|
|
2849
3779
|
/** Re-render widget and external containers to reflect fulfillment */
|
|
@@ -2958,16 +3888,42 @@ class WidgetRoot {
|
|
|
2958
3888
|
rewardCheck.textContent = "✔";
|
|
2959
3889
|
rewardCheck.style.color = "var(--cobuy-success-color, #10b981)";
|
|
2960
3890
|
const rewardLabel = document.createElement("span");
|
|
2961
|
-
rewardLabel.textContent = rewardText
|
|
3891
|
+
rewardLabel.textContent = rewardText
|
|
3892
|
+
? `Discount available: ${rewardText}`
|
|
3893
|
+
: "Discount available";
|
|
2962
3894
|
rewardLine.appendChild(rewardCheck);
|
|
2963
3895
|
rewardLine.appendChild(rewardLabel);
|
|
2964
3896
|
root.appendChild(title);
|
|
2965
3897
|
root.appendChild(rewardLine);
|
|
2966
|
-
// Create footer with
|
|
3898
|
+
// Create footer with action CTAs
|
|
2967
3899
|
const footer = document.createElement("div");
|
|
2968
3900
|
footer.style.display = "flex";
|
|
2969
3901
|
footer.style.justifyContent = "flex-end";
|
|
2970
3902
|
footer.style.alignItems = "center";
|
|
3903
|
+
footer.style.gap = "12px";
|
|
3904
|
+
// Add "Redeem In-store" CTA if offline redemption is available
|
|
3905
|
+
if (this.offlineRedemption && isValidOfflineRedemption(this.offlineRedemption)) {
|
|
3906
|
+
const redeemLink = document.createElement("button");
|
|
3907
|
+
redeemLink.className = "cobuy-redeem-instore-link";
|
|
3908
|
+
redeemLink.style.background = "none";
|
|
3909
|
+
redeemLink.style.border = "none";
|
|
3910
|
+
redeemLink.style.color = "var(--cobuy-primary-color, #3b82f6)";
|
|
3911
|
+
redeemLink.style.cursor = "pointer";
|
|
3912
|
+
redeemLink.style.fontSize = "12px";
|
|
3913
|
+
redeemLink.style.fontWeight = "600";
|
|
3914
|
+
redeemLink.style.textDecoration = "underline";
|
|
3915
|
+
redeemLink.style.padding = "0";
|
|
3916
|
+
redeemLink.style.transition = "opacity 0.2s";
|
|
3917
|
+
redeemLink.textContent = "Redeem In-store";
|
|
3918
|
+
redeemLink.addEventListener("click", () => this.openOfflineRedemptionModal());
|
|
3919
|
+
redeemLink.addEventListener("mouseover", () => {
|
|
3920
|
+
redeemLink.style.opacity = "0.7";
|
|
3921
|
+
});
|
|
3922
|
+
redeemLink.addEventListener("mouseout", () => {
|
|
3923
|
+
redeemLink.style.opacity = "1";
|
|
3924
|
+
});
|
|
3925
|
+
footer.appendChild(redeemLink);
|
|
3926
|
+
}
|
|
2971
3927
|
root.appendChild(footer);
|
|
2972
3928
|
return root;
|
|
2973
3929
|
}
|
|
@@ -3151,6 +4107,12 @@ class WidgetRoot {
|
|
|
3151
4107
|
this.currentGroupData = groupData;
|
|
3152
4108
|
this.currentGroupId = (groupData === null || groupData === void 0 ? void 0 : groupData.id) || null;
|
|
3153
4109
|
this.currentRewardData = rewardData;
|
|
4110
|
+
console.log("group data for product ", options.productId, groupData === null || groupData === void 0 ? void 0 : groupData.offline_redemption, isValidOfflineRedemption(groupData === null || groupData === void 0 ? void 0 : groupData.offline_redemption));
|
|
4111
|
+
// Extract offline_redemption from API response if available
|
|
4112
|
+
if ((groupData === null || groupData === void 0 ? void 0 : groupData.offline_redemption) &&
|
|
4113
|
+
isValidOfflineRedemption(groupData.offline_redemption)) {
|
|
4114
|
+
this.offlineRedemption = groupData.offline_redemption;
|
|
4115
|
+
}
|
|
3154
4116
|
// Check if group is already fulfilled (full) on initial load
|
|
3155
4117
|
if (groupData) {
|
|
3156
4118
|
const participants = Number(groupData.participants_count || 0);
|
|
@@ -3569,16 +4531,14 @@ class WidgetRoot {
|
|
|
3569
4531
|
// rewardText ? `Reward locked in: ${rewardText}` : "Reward available for this group",
|
|
3570
4532
|
// );
|
|
3571
4533
|
// rewardLine.title = rewardLine.textContent;
|
|
3572
|
-
// }
|
|
3573
|
-
// else if (activeReward) {
|
|
4534
|
+
// } else if (activeReward) {
|
|
3574
4535
|
// const rewardText = this.formatRewardText(activeReward);
|
|
3575
4536
|
// rewardLine.textContent = rewardText
|
|
3576
4537
|
// ? `Save up to ${rewardText} with CoBuy`
|
|
3577
4538
|
// : "CoBuy reward available";
|
|
3578
4539
|
// rewardLine.setAttribute("aria-label", `Eligible for CoBuy reward: ${rewardLine.textContent}`);
|
|
3579
4540
|
// rewardLine.title = rewardLine.textContent;
|
|
3580
|
-
// }
|
|
3581
|
-
// else {
|
|
4541
|
+
// } else {
|
|
3582
4542
|
// rewardLine.textContent = "CoBuy offer loading or unavailable";
|
|
3583
4543
|
// rewardLine.setAttribute("aria-label", "CoBuy offer loading or unavailable");
|
|
3584
4544
|
// rewardLine.title = "CoBuy offer loading or unavailable";
|
|
@@ -3814,6 +4774,12 @@ class WidgetRoot {
|
|
|
3814
4774
|
}
|
|
3815
4775
|
groupJoinData = joinResponse.data;
|
|
3816
4776
|
this.logger.info("Successfully joined group", groupJoinData);
|
|
4777
|
+
this.offlineRedemption =
|
|
4778
|
+
groupJoinData &&
|
|
4779
|
+
groupJoinData.offline_redemption &&
|
|
4780
|
+
isValidOfflineRedemption(groupJoinData.offline_redemption)
|
|
4781
|
+
? groupJoinData.offline_redemption
|
|
4782
|
+
: null;
|
|
3817
4783
|
// Trigger invite tracking before opening lobby (global for product)
|
|
3818
4784
|
try {
|
|
3819
4785
|
const inviteResponse = await this.apiClient.inviteToGroup(this.currentGroupId, "copy_link");
|
|
@@ -3859,6 +4825,13 @@ class WidgetRoot {
|
|
|
3859
4825
|
const isGroupFulfilled = groupJoinData.group.participants_count >= groupJoinData.group.max_participants;
|
|
3860
4826
|
const groupLinkFromInvite = inviteData === null || inviteData === void 0 ? void 0 : inviteData.invite_url;
|
|
3861
4827
|
const shareMessageFromInvite = inviteData === null || inviteData === void 0 ? void 0 : inviteData.share_message;
|
|
4828
|
+
console.log("offlien dataaa", groupJoinData.offline_redemption, isValidOfflineRedemption(groupJoinData.offline_redemption));
|
|
4829
|
+
// Extract offline_redemption from join response if available
|
|
4830
|
+
const offlineRedemptionFromJoin = groupJoinData.offline_redemption &&
|
|
4831
|
+
isValidOfflineRedemption(groupJoinData.offline_redemption)
|
|
4832
|
+
? groupJoinData.offline_redemption
|
|
4833
|
+
: undefined;
|
|
4834
|
+
this.offlineRedemption = offlineRedemptionFromJoin || null;
|
|
3862
4835
|
// Prepare modal data with real values from join response
|
|
3863
4836
|
cobuySDK.openModal({
|
|
3864
4837
|
productId,
|
|
@@ -3877,6 +4850,7 @@ class WidgetRoot {
|
|
|
3877
4850
|
groupLink: groupLinkFromInvite || `https://cobuy.group/lobby/${groupJoinData.group.id}`,
|
|
3878
4851
|
shareMessage: shareMessageFromInvite,
|
|
3879
4852
|
isLocked: !isGroupFulfilled,
|
|
4853
|
+
offlineRedemption: offlineRedemptionFromJoin,
|
|
3880
4854
|
activities: [
|
|
3881
4855
|
{
|
|
3882
4856
|
emoji: "👤",
|
|
@@ -3926,6 +4900,20 @@ class WidgetRoot {
|
|
|
3926
4900
|
}
|
|
3927
4901
|
}
|
|
3928
4902
|
}
|
|
4903
|
+
/**
|
|
4904
|
+
* Open offline redemption modal when user clicks "Redeem In-store"
|
|
4905
|
+
*/
|
|
4906
|
+
openOfflineRedemptionModal() {
|
|
4907
|
+
if (!this.offlineRedemption || !isValidOfflineRedemption(this.offlineRedemption)) {
|
|
4908
|
+
this.logger.warn("Offline redemption data not available");
|
|
4909
|
+
return;
|
|
4910
|
+
}
|
|
4911
|
+
this.logger.info("Opening offline redemption modal");
|
|
4912
|
+
this.offlineRedemptionModal = new OfflineRedemptionModal(this.offlineRedemption, () => {
|
|
4913
|
+
this.offlineRedemptionModal = null;
|
|
4914
|
+
});
|
|
4915
|
+
this.offlineRedemptionModal.open();
|
|
4916
|
+
}
|
|
3929
4917
|
/**
|
|
3930
4918
|
* Emit checkout intent without performing navigation
|
|
3931
4919
|
*/
|
|
@@ -3984,13 +4972,13 @@ class WidgetRoot {
|
|
|
3984
4972
|
const reward = this.frozenReward || (rewardData === null || rewardData === void 0 ? void 0 : rewardData.reward);
|
|
3985
4973
|
if (this.groupFulfilled) {
|
|
3986
4974
|
const rewardText = this.formatRewardText(reward);
|
|
3987
|
-
return rewardText ? `
|
|
4975
|
+
return rewardText ? `Discount available: ${rewardText}` : "Discount available for this group";
|
|
3988
4976
|
}
|
|
3989
4977
|
if (!reward) {
|
|
3990
|
-
return "Join with CoBuy to unlock
|
|
4978
|
+
return "Join with CoBuy to unlock discount";
|
|
3991
4979
|
}
|
|
3992
4980
|
const rewardText = this.formatRewardText(reward);
|
|
3993
|
-
return rewardText ? `Save up to ${rewardText} with CoBuy` : "CoBuy
|
|
4981
|
+
return rewardText ? `Save up to ${rewardText} with CoBuy` : "CoBuy discount available";
|
|
3994
4982
|
}
|
|
3995
4983
|
/**
|
|
3996
4984
|
* Public hook to request a realtime refresh from external callers
|
|
@@ -4549,12 +5537,28 @@ class ApiClient {
|
|
|
4549
5537
|
* @param checkoutRef - The checkout reference ID from the prepare step
|
|
4550
5538
|
* @returns Promise with success status
|
|
4551
5539
|
*/
|
|
4552
|
-
async confirmCheckout(groupId, checkoutRef) {
|
|
5540
|
+
async confirmCheckout(groupId, checkoutRef, data) {
|
|
4553
5541
|
const endpoint = buildApiUrl("", API_ENDPOINTS.GROUP_CHECKOUT_CONFIRM, { groupId });
|
|
4554
5542
|
this.logger.info(`Confirming checkout for group: ${groupId}`);
|
|
4555
|
-
const
|
|
5543
|
+
const payload = {
|
|
4556
5544
|
checkout_ref: checkoutRef,
|
|
4557
|
-
}
|
|
5545
|
+
};
|
|
5546
|
+
// Add optional fields if provided
|
|
5547
|
+
if (data) {
|
|
5548
|
+
if (data.order_id)
|
|
5549
|
+
payload.order_id = data.order_id;
|
|
5550
|
+
if (data.order_total !== undefined)
|
|
5551
|
+
payload.order_total = data.order_total;
|
|
5552
|
+
if (data.discount_applied !== undefined)
|
|
5553
|
+
payload.discount_applied = data.discount_applied;
|
|
5554
|
+
if (data.currency)
|
|
5555
|
+
payload.currency = data.currency;
|
|
5556
|
+
if (data.payment_method)
|
|
5557
|
+
payload.payment_method = data.payment_method;
|
|
5558
|
+
if (data.metadata)
|
|
5559
|
+
payload.metadata = data.metadata;
|
|
5560
|
+
}
|
|
5561
|
+
const response = await this.post(endpoint, payload);
|
|
4558
5562
|
if (response.success) {
|
|
4559
5563
|
this.logger.info("Checkout confirmed successfully");
|
|
4560
5564
|
return {
|
|
@@ -4588,7 +5592,7 @@ class AnalyticsClient {
|
|
|
4588
5592
|
*/
|
|
4589
5593
|
async trackCtaClick(productId) {
|
|
4590
5594
|
const event = {
|
|
4591
|
-
event: "
|
|
5595
|
+
event: "CTA_CLICKED",
|
|
4592
5596
|
productId,
|
|
4593
5597
|
timestamp: new Date().toISOString(),
|
|
4594
5598
|
sessionId: this.sessionId,
|
|
@@ -4632,6 +5636,28 @@ class AnalyticsClient {
|
|
|
4632
5636
|
throw error;
|
|
4633
5637
|
}
|
|
4634
5638
|
}
|
|
5639
|
+
/**
|
|
5640
|
+
* Track page view event
|
|
5641
|
+
*/
|
|
5642
|
+
async trackPageView() {
|
|
5643
|
+
const event = {
|
|
5644
|
+
event: "PAGE_VIEW",
|
|
5645
|
+
timestamp: new Date().toISOString(),
|
|
5646
|
+
context: {
|
|
5647
|
+
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
5648
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
|
5649
|
+
sdkVersion: this.sdkVersion,
|
|
5650
|
+
},
|
|
5651
|
+
};
|
|
5652
|
+
try {
|
|
5653
|
+
await this.sendEvent(event);
|
|
5654
|
+
this.logger.info("[Analytics] Page view tracked");
|
|
5655
|
+
}
|
|
5656
|
+
catch (error) {
|
|
5657
|
+
// Log but don't throw - analytics failure should not break UX
|
|
5658
|
+
this.logger.error("[Analytics] Failed to track page view", error);
|
|
5659
|
+
}
|
|
5660
|
+
}
|
|
4635
5661
|
/**
|
|
4636
5662
|
* Track custom event (extensible for future use)
|
|
4637
5663
|
*/
|
|
@@ -8819,9 +9845,10 @@ Object.assign(lookup, {
|
|
|
8819
9845
|
* Manages a single socket.io connection for the SDK
|
|
8820
9846
|
*/
|
|
8821
9847
|
class SocketManager {
|
|
8822
|
-
constructor(config, sdkVersion) {
|
|
9848
|
+
constructor(config, sdkVersion, sessionId) {
|
|
8823
9849
|
this.config = config;
|
|
8824
9850
|
this.sdkVersion = sdkVersion;
|
|
9851
|
+
this.sessionId = sessionId;
|
|
8825
9852
|
this.socket = null;
|
|
8826
9853
|
this.logger = new Logger(config.debug);
|
|
8827
9854
|
}
|
|
@@ -8866,6 +9893,7 @@ class SocketManager {
|
|
|
8866
9893
|
auth: {
|
|
8867
9894
|
merchantKey: this.config.merchantKey,
|
|
8868
9895
|
sdkVersion: this.sdkVersion,
|
|
9896
|
+
sessionId: this.sessionId,
|
|
8869
9897
|
},
|
|
8870
9898
|
autoConnect: true,
|
|
8871
9899
|
});
|
|
@@ -9052,7 +10080,9 @@ class CoBuy {
|
|
|
9052
10080
|
const ref = window.localStorage.getItem(key);
|
|
9053
10081
|
if (ref) {
|
|
9054
10082
|
this.logger.debug(`[SDK] Retrieved checkout reference via prefix: ${key}`);
|
|
9055
|
-
const parsedGroupId = key.startsWith(`${basePrefix}_`)
|
|
10083
|
+
const parsedGroupId = key.startsWith(`${basePrefix}_`)
|
|
10084
|
+
? key.substring(basePrefix.length + 1)
|
|
10085
|
+
: null;
|
|
9056
10086
|
return { key, checkoutRef: ref, groupId: parsedGroupId };
|
|
9057
10087
|
}
|
|
9058
10088
|
}
|
|
@@ -9142,7 +10172,7 @@ class CoBuy {
|
|
|
9142
10172
|
// Initialize real-time socket connection for public mode
|
|
9143
10173
|
if (config.authMode === "public" && config.merchantKey) {
|
|
9144
10174
|
try {
|
|
9145
|
-
this.socketManager = new SocketManager(config, SDK_VERSION);
|
|
10175
|
+
this.socketManager = new SocketManager(config, SDK_VERSION, this.sessionId);
|
|
9146
10176
|
this.socketManager.connect();
|
|
9147
10177
|
this.socketManager.bindHandlers({
|
|
9148
10178
|
onGroupFulfilled: (payload) => {
|
|
@@ -9184,6 +10214,13 @@ class CoBuy {
|
|
|
9184
10214
|
this.logger.warn("[SDK] Failed to initialize sockets", e);
|
|
9185
10215
|
}
|
|
9186
10216
|
}
|
|
10217
|
+
// Track page view event after successful initialization
|
|
10218
|
+
if (this.analyticsClient) {
|
|
10219
|
+
this.analyticsClient.trackPageView().catch((error) => {
|
|
10220
|
+
// Non-blocking: Analytics failure should not affect SDK initialization
|
|
10221
|
+
this.logger.warn("[SDK] Failed to track page view", error);
|
|
10222
|
+
});
|
|
10223
|
+
}
|
|
9187
10224
|
this.logger.info("SDK initialization complete");
|
|
9188
10225
|
}
|
|
9189
10226
|
catch (error) {
|
|
@@ -9263,6 +10300,7 @@ class CoBuy {
|
|
|
9263
10300
|
groupLink: options.groupLink,
|
|
9264
10301
|
activities: options.activities,
|
|
9265
10302
|
isLocked: options.isLocked,
|
|
10303
|
+
offlineRedemption: options.offlineRedemption,
|
|
9266
10304
|
};
|
|
9267
10305
|
// Create modal instance
|
|
9268
10306
|
const modal = new LobbyModal(modalData, {
|
|
@@ -9424,14 +10462,31 @@ class CoBuy {
|
|
|
9424
10462
|
*
|
|
9425
10463
|
* @param groupId - The group ID to confirm checkout for
|
|
9426
10464
|
* @param checkoutRef - The checkout reference ID from the prepare step
|
|
10465
|
+
/**
|
|
10466
|
+
* Confirm checkout for a group
|
|
10467
|
+
* Should be called after validateCheckout to complete the checkout process.
|
|
10468
|
+
*
|
|
10469
|
+
* @param groupId The group ID to confirm checkout for
|
|
10470
|
+
* @param checkoutRef The checkout reference from the prepare step
|
|
10471
|
+
* @param data Optional order details (order_id, order_total, discount_applied, currency, payment_method, metadata)
|
|
9427
10472
|
*
|
|
9428
10473
|
* @example
|
|
9429
10474
|
* ```typescript
|
|
9430
10475
|
* // Confirm checkout for a group
|
|
9431
10476
|
* await CoBuy.confirmCheckout('fae238ae-7468-47e9-9eec-b6d52fe3b012', 'chk_9a6d8750-ed60-4795-a207-2abe955e8509');
|
|
10477
|
+
*
|
|
10478
|
+
* // Confirm checkout with order details
|
|
10479
|
+
* await CoBuy.confirmCheckout('fae238ae-7468-47e9-9eec-b6d52fe3b012', 'chk_9a6d8750-ed60-4795-a207-2abe955e8509', {
|
|
10480
|
+
* order_id: 'ORDER-1001',
|
|
10481
|
+
* order_total: 100.00,
|
|
10482
|
+
* discount_applied: 20.00,
|
|
10483
|
+
* currency: 'USD',
|
|
10484
|
+
* payment_method: 'card',
|
|
10485
|
+
* metadata: { coupon_code: 'WELCOME20', customer_id: 'cust_123' }
|
|
10486
|
+
* });
|
|
9432
10487
|
* ```
|
|
9433
10488
|
*/
|
|
9434
|
-
async confirmCheckout(groupId, checkoutRef) {
|
|
10489
|
+
async confirmCheckout(groupId, checkoutRef, data) {
|
|
9435
10490
|
if (!this.configManager.isInitialized()) {
|
|
9436
10491
|
this.logger.warn("SDK not initialized, cannot confirm checkout");
|
|
9437
10492
|
return;
|
|
@@ -9441,7 +10496,7 @@ class CoBuy {
|
|
|
9441
10496
|
return;
|
|
9442
10497
|
}
|
|
9443
10498
|
try {
|
|
9444
|
-
const response = await this.apiClient.confirmCheckout(groupId, checkoutRef);
|
|
10499
|
+
const response = await this.apiClient.confirmCheckout(groupId, checkoutRef, data);
|
|
9445
10500
|
if (response.success) {
|
|
9446
10501
|
this.logger.info(`Checkout confirmed successfully for group: ${groupId}`);
|
|
9447
10502
|
// Directly refresh widgets. If we can infer productId, refresh only matching widgets.
|