@cshah18/sdk 4.0.0 → 4.1.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 +1185 -78
- package/dist/cobuy-sdk.esm.js.map +1 -1
- package/dist/cobuy-sdk.umd.js +1185 -78
- package/dist/cobuy-sdk.umd.js.map +1 -1
- package/dist/types/core/analytics.d.ts +6 -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 +54 -4
- 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
|
*/
|
|
@@ -1632,9 +1852,12 @@ class LobbyModal {
|
|
|
1632
1852
|
// Top section
|
|
1633
1853
|
const topSection = this.createTopSection();
|
|
1634
1854
|
mainContent.appendChild(topSection);
|
|
1635
|
-
// Activity section
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1855
|
+
// Activity section (hidden when offline redemption is present to keep layout compact)
|
|
1856
|
+
const hasOfflineRedemption = this.data.offlineRedemption && isValidOfflineRedemption(this.data.offlineRedemption);
|
|
1857
|
+
if (!hasOfflineRedemption) {
|
|
1858
|
+
const activitySection = this.createActivitySection();
|
|
1859
|
+
mainContent.appendChild(activitySection);
|
|
1860
|
+
}
|
|
1638
1861
|
mainWrapper.appendChild(mainContent);
|
|
1639
1862
|
modal.appendChild(background);
|
|
1640
1863
|
modal.appendChild(mainWrapper);
|
|
@@ -1677,9 +1900,9 @@ class LobbyModal {
|
|
|
1677
1900
|
// Title
|
|
1678
1901
|
const titleWp = this.createTitleSection();
|
|
1679
1902
|
leftWp.appendChild(titleWp);
|
|
1680
|
-
//
|
|
1681
|
-
const
|
|
1682
|
-
leftWp.appendChild(
|
|
1903
|
+
// Connected section (subtitle + link + share)
|
|
1904
|
+
const connectedSection = this.createConnectedSection();
|
|
1905
|
+
leftWp.appendChild(connectedSection);
|
|
1683
1906
|
// Offer
|
|
1684
1907
|
const offerBox = this.createOfferSection();
|
|
1685
1908
|
leftWp.appendChild(offerBox);
|
|
@@ -1691,24 +1914,35 @@ class LobbyModal {
|
|
|
1691
1914
|
createStatusSection() {
|
|
1692
1915
|
var _a;
|
|
1693
1916
|
const statusWp = document.createElement("div");
|
|
1694
|
-
statusWp.className = "lobby-status-
|
|
1917
|
+
statusWp.className = "lobby-status-section";
|
|
1695
1918
|
// Use safe defaults so UI never shows undefined values
|
|
1696
1919
|
const groupNum = (_a = this.data.groupNumber) !== null && _a !== void 0 ? _a : "1000";
|
|
1697
1920
|
const status = this.data.status === "complete" ? "complete" : "active";
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1921
|
+
// New "You are in the lobby" indicator badge
|
|
1922
|
+
const lobbyIndicator = document.createElement("div");
|
|
1923
|
+
lobbyIndicator.className = "lobby-indicator";
|
|
1924
|
+
const pulsingDot = document.createElement("div");
|
|
1925
|
+
pulsingDot.className = "pulsing-dot";
|
|
1926
|
+
const indicatorText = document.createElement("span");
|
|
1927
|
+
indicatorText.className = "indicator-text";
|
|
1928
|
+
indicatorText.textContent = "YOU ARE IN THE LOBBY";
|
|
1929
|
+
lobbyIndicator.appendChild(pulsingDot);
|
|
1930
|
+
lobbyIndicator.appendChild(indicatorText);
|
|
1931
|
+
// Status row with badge and group number
|
|
1932
|
+
const statusRow = document.createElement("div");
|
|
1933
|
+
statusRow.className = "lobby-status-wp";
|
|
1701
1934
|
// Render only the current status chip
|
|
1702
1935
|
const statusChip = document.createElement("div");
|
|
1703
1936
|
statusChip.className = `lobby-status ${status}`;
|
|
1704
1937
|
statusChip.id = "lobbyStatusChip";
|
|
1705
|
-
statusChip.textContent = status === "complete" ? "
|
|
1938
|
+
statusChip.textContent = status === "complete" ? "COMPLETED" : "ACTIVE";
|
|
1706
1939
|
const groupNumber = document.createElement("div");
|
|
1707
1940
|
groupNumber.className = "lobby-number";
|
|
1708
1941
|
groupNumber.textContent = `Group #${groupNum}`;
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
statusWp.appendChild(
|
|
1942
|
+
statusRow.appendChild(statusChip);
|
|
1943
|
+
statusRow.appendChild(groupNumber);
|
|
1944
|
+
statusWp.appendChild(lobbyIndicator);
|
|
1945
|
+
statusWp.appendChild(statusRow);
|
|
1712
1946
|
return statusWp;
|
|
1713
1947
|
}
|
|
1714
1948
|
/**
|
|
@@ -1721,24 +1955,49 @@ class LobbyModal {
|
|
|
1721
1955
|
const title = document.createElement("h2");
|
|
1722
1956
|
title.id = "lobbyTitleText";
|
|
1723
1957
|
title.textContent = titleContent.title;
|
|
1958
|
+
titleWp.appendChild(title);
|
|
1959
|
+
return titleWp;
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Create connected section (subtitle + link + share)
|
|
1963
|
+
*/
|
|
1964
|
+
createConnectedSection() {
|
|
1965
|
+
const connectedSection = document.createElement("div");
|
|
1966
|
+
connectedSection.className = "connected-section";
|
|
1967
|
+
connectedSection.id = "lobbyConnectedSection";
|
|
1968
|
+
const titleContent = this.getTitleText(this.data);
|
|
1969
|
+
// Subtitle
|
|
1724
1970
|
const subtitle = document.createElement("p");
|
|
1725
1971
|
subtitle.className = "sub-title";
|
|
1726
1972
|
subtitle.id = "lobbySubtitleText";
|
|
1727
1973
|
subtitle.textContent = titleContent.subtitle;
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1974
|
+
connectedSection.appendChild(subtitle);
|
|
1975
|
+
// Check if group is fulfilled and has offline redemption
|
|
1976
|
+
const isComplete = !this.computeIsLocked(this.data);
|
|
1977
|
+
const hasOfflineRedemption = isComplete &&
|
|
1978
|
+
this.data.offlineRedemption &&
|
|
1979
|
+
isValidOfflineRedemption(this.data.offlineRedemption);
|
|
1980
|
+
if (hasOfflineRedemption) {
|
|
1981
|
+
// Show offline redemption view with integrated actions
|
|
1982
|
+
const offlineSection = this.createOfflineRedemptionSection(this.data.offlineRedemption);
|
|
1983
|
+
connectedSection.appendChild(offlineSection);
|
|
1984
|
+
}
|
|
1985
|
+
else {
|
|
1986
|
+
// Show link and share container
|
|
1987
|
+
const linkShareContainer = document.createElement("div");
|
|
1988
|
+
linkShareContainer.className = "link-share-container";
|
|
1989
|
+
const linkWp = this.createLinkSection();
|
|
1990
|
+
linkShareContainer.appendChild(linkWp);
|
|
1991
|
+
connectedSection.appendChild(linkShareContainer);
|
|
1992
|
+
}
|
|
1993
|
+
return connectedSection;
|
|
1731
1994
|
}
|
|
1732
1995
|
/**
|
|
1733
|
-
* Create link section
|
|
1996
|
+
* Create link section (just the link box and share button, no wrapper)
|
|
1734
1997
|
*/
|
|
1735
1998
|
createLinkSection() {
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1738
|
-
linkWp.id = "lobbyLinkSection";
|
|
1739
|
-
if (!this.data.isLocked) {
|
|
1740
|
-
linkWp.style.display = "none";
|
|
1741
|
-
}
|
|
1999
|
+
const linkShareWrapper = document.createElement("div");
|
|
2000
|
+
linkShareWrapper.className = "link-share-wrapper";
|
|
1742
2001
|
// Link box
|
|
1743
2002
|
const linkBox = document.createElement("div");
|
|
1744
2003
|
linkBox.className = "lobby-link-box";
|
|
@@ -1753,23 +2012,324 @@ class LobbyModal {
|
|
|
1753
2012
|
linkUrl.textContent = this.data.groupLink || `cobuy.group/lobby/${this.data.productId}`;
|
|
1754
2013
|
linkBoxInr.appendChild(linkText);
|
|
1755
2014
|
linkBoxInr.appendChild(linkUrl);
|
|
1756
|
-
const copyBtn = document.createElement("
|
|
2015
|
+
const copyBtn = document.createElement("button");
|
|
1757
2016
|
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
|
|
2017
|
+
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
2018
|
copyBtn.addEventListener("click", () => this.copyLink());
|
|
1760
2019
|
linkBox.appendChild(linkBoxInr);
|
|
1761
2020
|
linkBox.appendChild(copyBtn);
|
|
1762
2021
|
// Share button
|
|
1763
|
-
const
|
|
1764
|
-
shareBtn.className = "share-btn-wp";
|
|
1765
|
-
const shareBtnInner = document.createElement("div");
|
|
2022
|
+
const shareBtnInner = document.createElement("button");
|
|
1766
2023
|
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
|
|
2024
|
+
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
2025
|
shareBtnInner.addEventListener("click", () => this.share());
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
2026
|
+
linkShareWrapper.appendChild(linkBox);
|
|
2027
|
+
linkShareWrapper.appendChild(shareBtnInner);
|
|
2028
|
+
return linkShareWrapper;
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Create offline redemption section
|
|
2032
|
+
*/
|
|
2033
|
+
createOfflineRedemptionSection(offlineRedemption) {
|
|
2034
|
+
const section = document.createElement("div");
|
|
2035
|
+
section.className = "offline-redemption-section";
|
|
2036
|
+
section.id = "lobbyOfflineRedemptionSection";
|
|
2037
|
+
// Top row: QR + Code side by side
|
|
2038
|
+
const topRow = document.createElement("div");
|
|
2039
|
+
topRow.className = "offline-top-row";
|
|
2040
|
+
// QR Code container
|
|
2041
|
+
const qrContainer = document.createElement("div");
|
|
2042
|
+
qrContainer.className = "offline-qr-container";
|
|
2043
|
+
const qrImage = document.createElement("img");
|
|
2044
|
+
qrImage.className = "offline-qr-image";
|
|
2045
|
+
qrImage.alt = "Redemption QR Code";
|
|
2046
|
+
qrImage.src = buildQRUrl(offlineRedemption.qr_code_data);
|
|
2047
|
+
// Handle QR image load errors
|
|
2048
|
+
qrImage.addEventListener("error", () => {
|
|
2049
|
+
qrImage.style.display = "none";
|
|
2050
|
+
const fallbackText = document.createElement("div");
|
|
2051
|
+
fallbackText.className = "offline-qr-fallback";
|
|
2052
|
+
fallbackText.textContent = "QR Code Unavailable";
|
|
2053
|
+
qrContainer.appendChild(fallbackText);
|
|
2054
|
+
});
|
|
2055
|
+
qrContainer.appendChild(qrImage);
|
|
2056
|
+
// Redemption code box
|
|
2057
|
+
const codeBox = document.createElement("div");
|
|
2058
|
+
codeBox.className = "offline-code-box";
|
|
2059
|
+
const codeLabel = document.createElement("p");
|
|
2060
|
+
codeLabel.className = "offline-code-label";
|
|
2061
|
+
codeLabel.textContent = "Redemption Code";
|
|
2062
|
+
const codeValue = document.createElement("div");
|
|
2063
|
+
codeValue.className = "offline-code-value";
|
|
2064
|
+
codeValue.textContent = offlineRedemption.redemption_code;
|
|
2065
|
+
const copyCodeBtn = document.createElement("button");
|
|
2066
|
+
copyCodeBtn.className = "offline-copy-code-btn";
|
|
2067
|
+
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>`;
|
|
2068
|
+
copyCodeBtn.addEventListener("click", () => this.copyOfflineRedemptionCode(offlineRedemption.redemption_code, copyCodeBtn));
|
|
2069
|
+
const codeRow = document.createElement("div");
|
|
2070
|
+
codeRow.className = "offline-code-row";
|
|
2071
|
+
codeRow.appendChild(codeValue);
|
|
2072
|
+
codeRow.appendChild(copyCodeBtn);
|
|
2073
|
+
codeBox.appendChild(codeLabel);
|
|
2074
|
+
codeBox.appendChild(codeRow);
|
|
2075
|
+
topRow.appendChild(qrContainer);
|
|
2076
|
+
topRow.appendChild(codeBox);
|
|
2077
|
+
// Expiry info - compact single line
|
|
2078
|
+
const expiryInfo = document.createElement("div");
|
|
2079
|
+
expiryInfo.className = "offline-expiry-info";
|
|
2080
|
+
expiryInfo.innerHTML = `<span class="offline-expiry-label">Valid Until:</span> <span class="offline-expiry-value">${formatExpiryDate(offlineRedemption.offline_expires_at)}</span>`;
|
|
2081
|
+
// Action buttons row
|
|
2082
|
+
const actionsRow = document.createElement("div");
|
|
2083
|
+
actionsRow.className = "offline-actions-row";
|
|
2084
|
+
// Download QR button
|
|
2085
|
+
const downloadQRBtn = document.createElement("button");
|
|
2086
|
+
downloadQRBtn.className = "offline-download-qr-btn";
|
|
2087
|
+
downloadQRBtn.textContent = "Download QR";
|
|
2088
|
+
downloadQRBtn.addEventListener("click", () => this.downloadOfflineQR(offlineRedemption, downloadQRBtn));
|
|
2089
|
+
// Online checkout button
|
|
2090
|
+
const onlineCheckoutBtn = document.createElement("button");
|
|
2091
|
+
onlineCheckoutBtn.className = "offline-online-checkout-btn";
|
|
2092
|
+
onlineCheckoutBtn.textContent = "Checkout Online";
|
|
2093
|
+
onlineCheckoutBtn.addEventListener("click", () => {
|
|
2094
|
+
if (this.data.groupLink) {
|
|
2095
|
+
window.open(this.data.groupLink, "_blank");
|
|
2096
|
+
}
|
|
2097
|
+
});
|
|
2098
|
+
actionsRow.appendChild(downloadQRBtn);
|
|
2099
|
+
actionsRow.appendChild(onlineCheckoutBtn);
|
|
2100
|
+
section.appendChild(topRow);
|
|
2101
|
+
section.appendChild(expiryInfo);
|
|
2102
|
+
section.appendChild(actionsRow);
|
|
2103
|
+
// Inject styles for offline redemption section
|
|
2104
|
+
this.injectOfflineRedemptionStyles();
|
|
2105
|
+
return section;
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Handle copying offline redemption code
|
|
2109
|
+
*/
|
|
2110
|
+
async copyOfflineRedemptionCode(code, button) {
|
|
2111
|
+
const originalContent = button.innerHTML;
|
|
2112
|
+
const success = await copyRedemptionCode(code);
|
|
2113
|
+
if (success) {
|
|
2114
|
+
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>`;
|
|
2115
|
+
setTimeout(() => {
|
|
2116
|
+
button.innerHTML = originalContent;
|
|
2117
|
+
}, 2000);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
/**
|
|
2121
|
+
* Handle downloading offline QR code
|
|
2122
|
+
*/
|
|
2123
|
+
async downloadOfflineQR(offlineRedemption, button) {
|
|
2124
|
+
const originalText = button.textContent;
|
|
2125
|
+
button.disabled = true;
|
|
2126
|
+
button.textContent = "Downloading...";
|
|
2127
|
+
const success = await downloadQRCode(buildQRUrl(offlineRedemption.qr_code_data), "redemption-qr.png");
|
|
2128
|
+
button.textContent = success ? "Downloaded!" : "Failed";
|
|
2129
|
+
setTimeout(() => {
|
|
2130
|
+
button.textContent = originalText;
|
|
2131
|
+
button.disabled = false;
|
|
2132
|
+
}, 2000);
|
|
2133
|
+
}
|
|
2134
|
+
/**
|
|
2135
|
+
* Inject styles for offline redemption section
|
|
2136
|
+
*/
|
|
2137
|
+
injectOfflineRedemptionStyles() {
|
|
2138
|
+
if (document.getElementById("lobby-offline-redemption-styles")) {
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
const style = document.createElement("style");
|
|
2142
|
+
style.id = "lobby-offline-redemption-styles";
|
|
2143
|
+
style.textContent = `
|
|
2144
|
+
.offline-redemption-section {
|
|
2145
|
+
display: flex;
|
|
2146
|
+
flex-direction: column;
|
|
2147
|
+
gap: 14px;
|
|
2148
|
+
padding: 16px;
|
|
2149
|
+
background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
|
|
2150
|
+
border: 1.5px solid #e5e7eb;
|
|
2151
|
+
border-radius: 12px;
|
|
2152
|
+
margin-top: 12px;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
.offline-top-row {
|
|
2156
|
+
display: flex;
|
|
2157
|
+
gap: 14px;
|
|
2158
|
+
align-items: stretch;
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
.offline-qr-container {
|
|
2162
|
+
flex: 0 0 120px;
|
|
2163
|
+
display: flex;
|
|
2164
|
+
justify-content: center;
|
|
2165
|
+
align-items: center;
|
|
2166
|
+
background: white;
|
|
2167
|
+
padding: 10px;
|
|
2168
|
+
border-radius: 8px;
|
|
2169
|
+
border: 1.5px solid #e5e7eb;
|
|
2170
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
.offline-qr-image {
|
|
2174
|
+
max-width: 100px;
|
|
2175
|
+
max-height: 100px;
|
|
2176
|
+
width: auto;
|
|
2177
|
+
height: auto;
|
|
2178
|
+
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.08));
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
.offline-qr-fallback {
|
|
2182
|
+
color: #9ca3af;
|
|
2183
|
+
font-size: 11px;
|
|
2184
|
+
text-align: center;
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
.offline-code-box {
|
|
2188
|
+
flex: 1;
|
|
2189
|
+
display: flex;
|
|
2190
|
+
flex-direction: column;
|
|
2191
|
+
gap: 8px;
|
|
2192
|
+
justify-content: center;
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
.offline-code-row {
|
|
2196
|
+
position: relative;
|
|
2197
|
+
display: flex;
|
|
2198
|
+
align-items: center;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
.offline-code-label {
|
|
2202
|
+
margin: 0;
|
|
2203
|
+
font-size: 11px;
|
|
2204
|
+
font-weight: 700;
|
|
2205
|
+
color: #1f2937;
|
|
2206
|
+
text-transform: uppercase;
|
|
2207
|
+
letter-spacing: 0.5px;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
.offline-code-value {
|
|
2211
|
+
font-family: 'Courier New', monospace;
|
|
2212
|
+
font-size: 14px;
|
|
2213
|
+
font-weight: 700;
|
|
2214
|
+
color: #111827;
|
|
2215
|
+
padding: 10px 40px 10px 12px;
|
|
2216
|
+
background: white;
|
|
2217
|
+
border: 1.5px solid #d1d5db;
|
|
2218
|
+
border-radius: 8px;
|
|
2219
|
+
text-align: center;
|
|
2220
|
+
letter-spacing: 1.5px;
|
|
2221
|
+
position: relative;
|
|
2222
|
+
display: flex;
|
|
2223
|
+
align-items: center;
|
|
2224
|
+
justify-content: center;
|
|
2225
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
|
2226
|
+
transition: all 0.2s;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
.offline-code-row .offline-code-value {
|
|
2230
|
+
width: 100%;
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
.offline-code-value:hover {
|
|
2234
|
+
border-color: #3b82f6;
|
|
2235
|
+
box-shadow: 0 2px 6px rgba(59, 130, 246, 0.1);
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
.offline-copy-code-btn {
|
|
2239
|
+
position: absolute;
|
|
2240
|
+
right: 6px;
|
|
2241
|
+
top: 50%;
|
|
2242
|
+
transform: translateY(-50%);
|
|
2243
|
+
background: none;
|
|
2244
|
+
border: none;
|
|
2245
|
+
padding: 5px 8px;
|
|
2246
|
+
cursor: pointer;
|
|
2247
|
+
color: #6b7280;
|
|
2248
|
+
display: flex;
|
|
2249
|
+
align-items: center;
|
|
2250
|
+
justify-content: center;
|
|
2251
|
+
transition: all 0.2s;
|
|
2252
|
+
border-radius: 4px;
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
.offline-copy-code-btn:hover {
|
|
2256
|
+
color: #3b82f6;
|
|
2257
|
+
background: #eff6ff;
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
.offline-expiry-info {
|
|
2261
|
+
font-size: 11px;
|
|
2262
|
+
color: #6b7280;
|
|
2263
|
+
text-align: center;
|
|
2264
|
+
padding: 6px 0;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
.offline-expiry-label {
|
|
2268
|
+
font-weight: 700;
|
|
2269
|
+
text-transform: uppercase;
|
|
2270
|
+
letter-spacing: 0.5px;
|
|
2271
|
+
color: #1f2937;
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
.offline-expiry-value {
|
|
2275
|
+
font-weight: 600;
|
|
2276
|
+
color: #374151;
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
.offline-actions-row {
|
|
2280
|
+
display: flex;
|
|
2281
|
+
gap: 10px;
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
.offline-download-qr-btn,
|
|
2285
|
+
.offline-online-checkout-btn {
|
|
2286
|
+
flex: 1;
|
|
2287
|
+
padding: 11px 16px;
|
|
2288
|
+
border: none;
|
|
2289
|
+
border-radius: 8px;
|
|
2290
|
+
font-size: 13px;
|
|
2291
|
+
font-weight: 700;
|
|
2292
|
+
cursor: pointer;
|
|
2293
|
+
transition: all 0.2s;
|
|
2294
|
+
text-transform: uppercase;
|
|
2295
|
+
letter-spacing: 0.4px;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
.offline-download-qr-btn {
|
|
2299
|
+
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
|
2300
|
+
color: white;
|
|
2301
|
+
box-shadow: 0 2px 6px rgba(59, 130, 246, 0.2);
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
.offline-download-qr-btn:hover:not(:disabled) {
|
|
2305
|
+
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
|
|
2306
|
+
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
|
|
2307
|
+
transform: translateY(-1px);
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
.offline-online-checkout-btn {
|
|
2311
|
+
background: linear-gradient(135deg, #111827 0%, #1f2937 100%);
|
|
2312
|
+
color: white;
|
|
2313
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
.offline-online-checkout-btn:hover {
|
|
2317
|
+
background: linear-gradient(135deg, #1f2937 0%, #374151 100%);
|
|
2318
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
2319
|
+
transform: translateY(-1px);
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
.offline-download-qr-btn:active:not(:disabled),
|
|
2323
|
+
.offline-online-checkout-btn:active {
|
|
2324
|
+
transform: translateY(0);
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
.offline-download-qr-btn:disabled {
|
|
2328
|
+
opacity: 0.7;
|
|
2329
|
+
cursor: not-allowed;
|
|
2330
|
+
}
|
|
2331
|
+
`;
|
|
2332
|
+
document.head.appendChild(style);
|
|
1773
2333
|
}
|
|
1774
2334
|
/**
|
|
1775
2335
|
* Create offer section
|
|
@@ -2408,24 +2968,27 @@ class LobbyModal {
|
|
|
2408
2968
|
groupNumber: this.data.groupNumber,
|
|
2409
2969
|
status: this.data.status,
|
|
2410
2970
|
});
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
this.socketManager.
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2971
|
+
// Show entrance animation, then open modal after animation completes
|
|
2972
|
+
this.showEntranceAnimation(() => {
|
|
2973
|
+
this.modalElement = this.createModalStructure();
|
|
2974
|
+
document.body.appendChild(this.modalElement);
|
|
2975
|
+
// Subscribe to realtime socket events
|
|
2976
|
+
this.subscribeToSocketEvents();
|
|
2977
|
+
// If socket is available and connected, subscribe to this group
|
|
2978
|
+
if (this.socketManager && this.socketManager.isConnected() && this.currentGroupId) {
|
|
2979
|
+
this.logger.info(`[LobbyModal] Socket connected, subscribing to group ${this.currentGroupId}`);
|
|
2980
|
+
this.socketManager.subscribeToGroup(this.currentGroupId);
|
|
2981
|
+
}
|
|
2982
|
+
else if (this.socketManager && !this.socketManager.isConnected()) {
|
|
2983
|
+
this.logger.warn("[LobbyModal] Socket manager not connected yet");
|
|
2984
|
+
}
|
|
2985
|
+
// Start timers and animations
|
|
2986
|
+
this.startTimer();
|
|
2987
|
+
this.startActivityAnimation();
|
|
2988
|
+
// Track analytics
|
|
2989
|
+
if (this.analyticsClient) ;
|
|
2990
|
+
this.logger.info(`Lobby modal opened for product: ${this.data.productId}`);
|
|
2991
|
+
});
|
|
2429
2992
|
}
|
|
2430
2993
|
/**
|
|
2431
2994
|
* Close the modal
|
|
@@ -2471,6 +3034,10 @@ class LobbyModal {
|
|
|
2471
3034
|
this.updateLockUI(!!this.data.isLocked);
|
|
2472
3035
|
this.updateTitleUI(this.data);
|
|
2473
3036
|
this.updateLinkVisibility(!!this.data.isLocked);
|
|
3037
|
+
// Update offline redemption visibility if data changed
|
|
3038
|
+
if (data.offlineRedemption !== undefined || data.status !== undefined) {
|
|
3039
|
+
this.updateOfflineRedemptionVisibility();
|
|
3040
|
+
}
|
|
2474
3041
|
// Update progress if changed
|
|
2475
3042
|
if (data.progress !== undefined) {
|
|
2476
3043
|
const progressFill = document.getElementById("progressFill");
|
|
@@ -2545,6 +3112,42 @@ class LobbyModal {
|
|
|
2545
3112
|
return;
|
|
2546
3113
|
linkSection.style.display = isLocked ? "" : "none";
|
|
2547
3114
|
}
|
|
3115
|
+
/**
|
|
3116
|
+
* Update offline redemption visibility when group is fulfilled
|
|
3117
|
+
*/
|
|
3118
|
+
updateOfflineRedemptionVisibility() {
|
|
3119
|
+
const connectedSection = document.getElementById("lobbyConnectedSection");
|
|
3120
|
+
if (!connectedSection)
|
|
3121
|
+
return;
|
|
3122
|
+
const isComplete = !this.computeIsLocked(this.data);
|
|
3123
|
+
const hasOfflineRedemption = this.data.offlineRedemption && isValidOfflineRedemption(this.data.offlineRedemption);
|
|
3124
|
+
// Get existing elements
|
|
3125
|
+
const existingOffline = connectedSection.querySelector(".offline-redemption-section");
|
|
3126
|
+
const existingLink = connectedSection.querySelector(".link-share-container");
|
|
3127
|
+
if (isComplete && hasOfflineRedemption) {
|
|
3128
|
+
// Show offline redemption, hide link/share
|
|
3129
|
+
if (existingLink) {
|
|
3130
|
+
existingLink.remove();
|
|
3131
|
+
}
|
|
3132
|
+
if (!existingOffline) {
|
|
3133
|
+
const offlineSection = this.createOfflineRedemptionSection(this.data.offlineRedemption);
|
|
3134
|
+
connectedSection.appendChild(offlineSection);
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
else {
|
|
3138
|
+
// Show link/share, hide offline redemption
|
|
3139
|
+
if (existingOffline) {
|
|
3140
|
+
existingOffline.remove();
|
|
3141
|
+
}
|
|
3142
|
+
if (!existingLink) {
|
|
3143
|
+
const linkShareContainer = document.createElement("div");
|
|
3144
|
+
linkShareContainer.className = "link-share-container";
|
|
3145
|
+
const linkWp = this.createLinkSection();
|
|
3146
|
+
linkShareContainer.appendChild(linkWp);
|
|
3147
|
+
connectedSection.appendChild(linkShareContainer);
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
2548
3151
|
/**
|
|
2549
3152
|
* Subscribe to socket events
|
|
2550
3153
|
*/
|
|
@@ -2581,7 +3184,7 @@ class LobbyModal {
|
|
|
2581
3184
|
break;
|
|
2582
3185
|
case "group:fulfilled":
|
|
2583
3186
|
emoji = "🎁";
|
|
2584
|
-
action = "unlocked the
|
|
3187
|
+
action = "unlocked the discount – group filled!";
|
|
2585
3188
|
break;
|
|
2586
3189
|
case "group:created":
|
|
2587
3190
|
emoji = "🛒";
|
|
@@ -2649,6 +3252,405 @@ class LobbyModal {
|
|
|
2649
3252
|
}
|
|
2650
3253
|
}
|
|
2651
3254
|
|
|
3255
|
+
/**
|
|
3256
|
+
* Offline Redemption Modal
|
|
3257
|
+
* Displays QR code and redemption code for fulfilled groups
|
|
3258
|
+
*/
|
|
3259
|
+
class OfflineRedemptionModal {
|
|
3260
|
+
constructor(offlineRedemption, onClose) {
|
|
3261
|
+
this.modalElement = null;
|
|
3262
|
+
this.offlineRedemption = offlineRedemption;
|
|
3263
|
+
this.onClose = onClose;
|
|
3264
|
+
}
|
|
3265
|
+
/**
|
|
3266
|
+
* Open the modal
|
|
3267
|
+
*/
|
|
3268
|
+
open() {
|
|
3269
|
+
if (this.modalElement && document.body.contains(this.modalElement)) {
|
|
3270
|
+
return; // Already open
|
|
3271
|
+
}
|
|
3272
|
+
this.modalElement = this.createModalStructure();
|
|
3273
|
+
document.body.appendChild(this.modalElement);
|
|
3274
|
+
// Trigger entrance animation
|
|
3275
|
+
requestAnimationFrame(() => {
|
|
3276
|
+
var _a;
|
|
3277
|
+
(_a = this.modalElement) === null || _a === void 0 ? void 0 : _a.classList.add("offline-redemption-modal-open");
|
|
3278
|
+
});
|
|
3279
|
+
}
|
|
3280
|
+
/**
|
|
3281
|
+
* Close the modal
|
|
3282
|
+
*/
|
|
3283
|
+
close() {
|
|
3284
|
+
if (!this.modalElement)
|
|
3285
|
+
return;
|
|
3286
|
+
this.modalElement.classList.remove("offline-redemption-modal-open");
|
|
3287
|
+
// Remove after animation completes
|
|
3288
|
+
setTimeout(() => {
|
|
3289
|
+
var _a;
|
|
3290
|
+
if (this.modalElement && document.body.contains(this.modalElement)) {
|
|
3291
|
+
document.body.removeChild(this.modalElement);
|
|
3292
|
+
this.modalElement = null;
|
|
3293
|
+
}
|
|
3294
|
+
(_a = this.onClose) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
3295
|
+
}, 300);
|
|
3296
|
+
}
|
|
3297
|
+
/**
|
|
3298
|
+
* Create the modal DOM structure
|
|
3299
|
+
*/
|
|
3300
|
+
createModalStructure() {
|
|
3301
|
+
const container = document.createElement("div");
|
|
3302
|
+
container.className = "offline-redemption-modal-overlay";
|
|
3303
|
+
// Backdrop
|
|
3304
|
+
const backdrop = document.createElement("div");
|
|
3305
|
+
backdrop.className = "offline-redemption-backdrop";
|
|
3306
|
+
backdrop.addEventListener("click", () => this.close());
|
|
3307
|
+
// Modal content
|
|
3308
|
+
const modal = document.createElement("div");
|
|
3309
|
+
modal.className = "offline-redemption-modal";
|
|
3310
|
+
// Close button
|
|
3311
|
+
const closeBtn = document.createElement("button");
|
|
3312
|
+
closeBtn.className = "offline-redemption-close-btn";
|
|
3313
|
+
closeBtn.setAttribute("aria-label", "Close modal");
|
|
3314
|
+
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>`;
|
|
3315
|
+
closeBtn.addEventListener("click", () => this.close());
|
|
3316
|
+
// Header
|
|
3317
|
+
const header = document.createElement("div");
|
|
3318
|
+
header.className = "offline-redemption-header";
|
|
3319
|
+
const title = document.createElement("h2");
|
|
3320
|
+
title.className = "offline-redemption-title";
|
|
3321
|
+
title.textContent = "Redeem In-Store";
|
|
3322
|
+
header.appendChild(title);
|
|
3323
|
+
header.appendChild(closeBtn);
|
|
3324
|
+
// Body
|
|
3325
|
+
const body = document.createElement("div");
|
|
3326
|
+
body.className = "offline-redemption-body";
|
|
3327
|
+
// QR Code section
|
|
3328
|
+
const qrSection = document.createElement("div");
|
|
3329
|
+
qrSection.className = "offline-redemption-qr-section";
|
|
3330
|
+
const qrImage = document.createElement("img");
|
|
3331
|
+
qrImage.className = "offline-redemption-qr-image";
|
|
3332
|
+
qrImage.alt = "Redemption QR Code";
|
|
3333
|
+
qrImage.src = buildQRUrl(this.offlineRedemption.qr_code_data);
|
|
3334
|
+
// QR image fallback handler
|
|
3335
|
+
qrImage.addEventListener("error", () => {
|
|
3336
|
+
qrImage.style.display = "none";
|
|
3337
|
+
const errorMsg = document.createElement("div");
|
|
3338
|
+
errorMsg.className = "offline-redemption-qr-error";
|
|
3339
|
+
errorMsg.textContent = "QR code unavailable";
|
|
3340
|
+
qrSection.appendChild(errorMsg);
|
|
3341
|
+
});
|
|
3342
|
+
qrSection.appendChild(qrImage);
|
|
3343
|
+
// Redemption code section
|
|
3344
|
+
const codeSection = document.createElement("div");
|
|
3345
|
+
codeSection.className = "offline-redemption-code-section";
|
|
3346
|
+
const codeLabel = document.createElement("p");
|
|
3347
|
+
codeLabel.className = "offline-redemption-code-label";
|
|
3348
|
+
codeLabel.textContent = "Redemption Code:";
|
|
3349
|
+
const codeValue = document.createElement("div");
|
|
3350
|
+
codeValue.className = "offline-redemption-code-value";
|
|
3351
|
+
codeValue.textContent = this.offlineRedemption.redemption_code;
|
|
3352
|
+
codeSection.appendChild(codeLabel);
|
|
3353
|
+
codeSection.appendChild(codeValue);
|
|
3354
|
+
// Expiry section
|
|
3355
|
+
const expirySection = document.createElement("div");
|
|
3356
|
+
expirySection.className = "offline-redemption-expiry-section";
|
|
3357
|
+
const expiryLabel = document.createElement("p");
|
|
3358
|
+
expiryLabel.className = "offline-redemption-expiry-label";
|
|
3359
|
+
expiryLabel.textContent = "Valid Until:";
|
|
3360
|
+
const expiryValue = document.createElement("p");
|
|
3361
|
+
expiryValue.className = "offline-redemption-expiry-value";
|
|
3362
|
+
const isValid = isRedemptionValid(this.offlineRedemption.offline_expires_at);
|
|
3363
|
+
expiryValue.textContent = formatExpiryDate(this.offlineRedemption.offline_expires_at);
|
|
3364
|
+
if (!isValid) {
|
|
3365
|
+
expiryValue.classList.add("expired");
|
|
3366
|
+
}
|
|
3367
|
+
expirySection.appendChild(expiryLabel);
|
|
3368
|
+
expirySection.appendChild(expiryValue);
|
|
3369
|
+
body.appendChild(qrSection);
|
|
3370
|
+
body.appendChild(codeSection);
|
|
3371
|
+
body.appendChild(expirySection);
|
|
3372
|
+
// Actions
|
|
3373
|
+
const actions = document.createElement("div");
|
|
3374
|
+
actions.className = "offline-redemption-actions";
|
|
3375
|
+
const downloadBtn = document.createElement("button");
|
|
3376
|
+
downloadBtn.className = "offline-redemption-action-btn download-btn";
|
|
3377
|
+
downloadBtn.textContent = "Download QR";
|
|
3378
|
+
downloadBtn.addEventListener("click", () => this.handleDownloadQR(downloadBtn));
|
|
3379
|
+
const copyBtn = document.createElement("button");
|
|
3380
|
+
copyBtn.className = "offline-redemption-action-btn copy-btn";
|
|
3381
|
+
copyBtn.textContent = "Copy Code";
|
|
3382
|
+
copyBtn.addEventListener("click", () => this.handleCopyCode(copyBtn));
|
|
3383
|
+
actions.appendChild(downloadBtn);
|
|
3384
|
+
actions.appendChild(copyBtn);
|
|
3385
|
+
// Assemble modal
|
|
3386
|
+
modal.appendChild(header);
|
|
3387
|
+
modal.appendChild(body);
|
|
3388
|
+
modal.appendChild(actions);
|
|
3389
|
+
container.appendChild(backdrop);
|
|
3390
|
+
container.appendChild(modal);
|
|
3391
|
+
// Inject styles
|
|
3392
|
+
this.injectStyles();
|
|
3393
|
+
return container;
|
|
3394
|
+
}
|
|
3395
|
+
/**
|
|
3396
|
+
* Handle download QR button click
|
|
3397
|
+
*/
|
|
3398
|
+
async handleDownloadQR(button) {
|
|
3399
|
+
const originalText = button.textContent;
|
|
3400
|
+
button.disabled = true;
|
|
3401
|
+
button.textContent = "Downloading...";
|
|
3402
|
+
const success = await downloadQRCode(buildQRUrl(this.offlineRedemption.qr_code_data), "redemption-qr.png");
|
|
3403
|
+
button.textContent = success ? "Downloaded!" : "Download Failed";
|
|
3404
|
+
setTimeout(() => {
|
|
3405
|
+
button.textContent = originalText;
|
|
3406
|
+
button.disabled = false;
|
|
3407
|
+
}, 2000);
|
|
3408
|
+
}
|
|
3409
|
+
/**
|
|
3410
|
+
* Handle copy code button click
|
|
3411
|
+
*/
|
|
3412
|
+
async handleCopyCode(button) {
|
|
3413
|
+
const originalText = button.textContent;
|
|
3414
|
+
button.disabled = true;
|
|
3415
|
+
const success = await copyRedemptionCode(this.offlineRedemption.redemption_code);
|
|
3416
|
+
button.textContent = success ? "Copied!" : "Copy Failed";
|
|
3417
|
+
setTimeout(() => {
|
|
3418
|
+
button.textContent = originalText;
|
|
3419
|
+
button.disabled = false;
|
|
3420
|
+
}, 2000);
|
|
3421
|
+
}
|
|
3422
|
+
/**
|
|
3423
|
+
* Inject modal styles
|
|
3424
|
+
*/
|
|
3425
|
+
injectStyles() {
|
|
3426
|
+
// Check if styles already injected
|
|
3427
|
+
if (document.getElementById("offline-redemption-modal-styles")) {
|
|
3428
|
+
return;
|
|
3429
|
+
}
|
|
3430
|
+
const style = document.createElement("style");
|
|
3431
|
+
style.id = "offline-redemption-modal-styles";
|
|
3432
|
+
style.textContent = `
|
|
3433
|
+
.offline-redemption-modal-overlay {
|
|
3434
|
+
position: fixed;
|
|
3435
|
+
top: 0;
|
|
3436
|
+
left: 0;
|
|
3437
|
+
right: 0;
|
|
3438
|
+
bottom: 0;
|
|
3439
|
+
display: flex;
|
|
3440
|
+
align-items: center;
|
|
3441
|
+
justify-content: center;
|
|
3442
|
+
z-index: 10000;
|
|
3443
|
+
opacity: 0;
|
|
3444
|
+
transition: opacity 0.3s ease-out;
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
.offline-redemption-modal-overlay.offline-redemption-modal-open {
|
|
3448
|
+
opacity: 1;
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
.offline-redemption-backdrop {
|
|
3452
|
+
position: absolute;
|
|
3453
|
+
top: 0;
|
|
3454
|
+
left: 0;
|
|
3455
|
+
right: 0;
|
|
3456
|
+
bottom: 0;
|
|
3457
|
+
background: rgba(0, 0, 0, 0.5);
|
|
3458
|
+
cursor: pointer;
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
.offline-redemption-modal {
|
|
3462
|
+
position: relative;
|
|
3463
|
+
z-index: 10001;
|
|
3464
|
+
background: white;
|
|
3465
|
+
border-radius: 12px;
|
|
3466
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
|
3467
|
+
max-width: 400px;
|
|
3468
|
+
width: 90%;
|
|
3469
|
+
max-height: 90vh;
|
|
3470
|
+
overflow-y: auto;
|
|
3471
|
+
animation: slideUp 0.3s ease-out;
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
@keyframes slideUp {
|
|
3475
|
+
from {
|
|
3476
|
+
opacity: 0;
|
|
3477
|
+
transform: translateY(40px);
|
|
3478
|
+
}
|
|
3479
|
+
to {
|
|
3480
|
+
opacity: 1;
|
|
3481
|
+
transform: translateY(0);
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
.offline-redemption-header {
|
|
3486
|
+
display: flex;
|
|
3487
|
+
justify-content: space-between;
|
|
3488
|
+
align-items: center;
|
|
3489
|
+
padding: 20px;
|
|
3490
|
+
border-bottom: 1px solid #f0f0f0;
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
.offline-redemption-title {
|
|
3494
|
+
margin: 0;
|
|
3495
|
+
font-size: 20px;
|
|
3496
|
+
font-weight: 600;
|
|
3497
|
+
color: #111827;
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
.offline-redemption-close-btn {
|
|
3501
|
+
background: none;
|
|
3502
|
+
border: none;
|
|
3503
|
+
padding: 0;
|
|
3504
|
+
cursor: pointer;
|
|
3505
|
+
display: flex;
|
|
3506
|
+
align-items: center;
|
|
3507
|
+
justify-content: center;
|
|
3508
|
+
color: #6b7280;
|
|
3509
|
+
transition: color 0.2s;
|
|
3510
|
+
}
|
|
3511
|
+
|
|
3512
|
+
.offline-redemption-close-btn:hover {
|
|
3513
|
+
color: #111827;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
.offline-redemption-close-btn svg {
|
|
3517
|
+
width: 24px;
|
|
3518
|
+
height: 24px;
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
.offline-redemption-body {
|
|
3522
|
+
padding: 24px 20px;
|
|
3523
|
+
display: flex;
|
|
3524
|
+
flex-direction: column;
|
|
3525
|
+
gap: 20px;
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
.offline-redemption-qr-section {
|
|
3529
|
+
display: flex;
|
|
3530
|
+
justify-content: center;
|
|
3531
|
+
background: #f9fafb;
|
|
3532
|
+
padding: 16px;
|
|
3533
|
+
border-radius: 8px;
|
|
3534
|
+
min-height: 200px;
|
|
3535
|
+
align-items: center;
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3538
|
+
.offline-redemption-qr-image {
|
|
3539
|
+
max-width: 100%;
|
|
3540
|
+
max-height: 200px;
|
|
3541
|
+
width: auto;
|
|
3542
|
+
height: auto;
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3545
|
+
.offline-redemption-qr-error {
|
|
3546
|
+
text-align: center;
|
|
3547
|
+
color: #6b7280;
|
|
3548
|
+
font-size: 14px;
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
.offline-redemption-code-section,
|
|
3552
|
+
.offline-redemption-expiry-section {
|
|
3553
|
+
display: flex;
|
|
3554
|
+
flex-direction: column;
|
|
3555
|
+
gap: 8px;
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
.offline-redemption-code-label,
|
|
3559
|
+
.offline-redemption-expiry-label {
|
|
3560
|
+
margin: 0;
|
|
3561
|
+
font-size: 12px;
|
|
3562
|
+
font-weight: 600;
|
|
3563
|
+
color: #6b7280;
|
|
3564
|
+
text-transform: uppercase;
|
|
3565
|
+
letter-spacing: 0.5px;
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
.offline-redemption-code-value {
|
|
3569
|
+
font-family: monospace;
|
|
3570
|
+
font-size: 16px;
|
|
3571
|
+
font-weight: 600;
|
|
3572
|
+
color: #111827;
|
|
3573
|
+
padding: 12px;
|
|
3574
|
+
background: #f9fafb;
|
|
3575
|
+
border: 1px solid #e5e7eb;
|
|
3576
|
+
border-radius: 6px;
|
|
3577
|
+
text-align: center;
|
|
3578
|
+
letter-spacing: 2px;
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
.offline-redemption-expiry-value {
|
|
3582
|
+
margin: 0;
|
|
3583
|
+
font-size: 14px;
|
|
3584
|
+
color: #374151;
|
|
3585
|
+
}
|
|
3586
|
+
|
|
3587
|
+
.offline-redemption-expiry-value.expired {
|
|
3588
|
+
color: #dc2626;
|
|
3589
|
+
font-weight: 600;
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
.offline-redemption-actions {
|
|
3593
|
+
display: flex;
|
|
3594
|
+
gap: 12px;
|
|
3595
|
+
padding: 20px;
|
|
3596
|
+
border-top: 1px solid #f0f0f0;
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3599
|
+
.offline-redemption-action-btn {
|
|
3600
|
+
flex: 1;
|
|
3601
|
+
padding: 12px 16px;
|
|
3602
|
+
border: none;
|
|
3603
|
+
border-radius: 6px;
|
|
3604
|
+
font-size: 14px;
|
|
3605
|
+
font-weight: 600;
|
|
3606
|
+
cursor: pointer;
|
|
3607
|
+
transition: all 0.2s;
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
.download-btn {
|
|
3611
|
+
background: #3b82f6;
|
|
3612
|
+
color: white;
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
.download-btn:hover:not(:disabled) {
|
|
3616
|
+
background: #2563eb;
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
.copy-btn {
|
|
3620
|
+
background: #f3f4f6;
|
|
3621
|
+
color: #111827;
|
|
3622
|
+
border: 1px solid #e5e7eb;
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
.copy-btn:hover:not(:disabled) {
|
|
3626
|
+
background: #e5e7eb;
|
|
3627
|
+
}
|
|
3628
|
+
|
|
3629
|
+
.offline-redemption-action-btn:disabled {
|
|
3630
|
+
opacity: 0.7;
|
|
3631
|
+
cursor: not-allowed;
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3634
|
+
/* Mobile responsiveness */
|
|
3635
|
+
@media (max-width: 480px) {
|
|
3636
|
+
.offline-redemption-modal {
|
|
3637
|
+
width: 95%;
|
|
3638
|
+
max-height: 95vh;
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3641
|
+
.offline-redemption-body {
|
|
3642
|
+
padding: 16px;
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
.offline-redemption-actions {
|
|
3646
|
+
padding: 16px;
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
`;
|
|
3650
|
+
document.head.appendChild(style);
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
|
|
2652
3654
|
/// <reference lib="dom" />
|
|
2653
3655
|
/**
|
|
2654
3656
|
* Widget state enumeration for tracking render lifecycle
|
|
@@ -2683,6 +3685,8 @@ class WidgetRoot {
|
|
|
2683
3685
|
this.lastGroupDataRefreshTime = 0;
|
|
2684
3686
|
this.GROUP_REFRESH_DEBOUNCE = 1000; // 1 second min between refreshes
|
|
2685
3687
|
this.groupExpiryRefreshTriggered = false; // Track if expiry refresh already triggered for current group
|
|
3688
|
+
this.offlineRedemption = null;
|
|
3689
|
+
this.offlineRedemptionModal = null;
|
|
2686
3690
|
/** Handle backend fulfillment notifications */
|
|
2687
3691
|
this.handleGroupFulfilledEvent = (event) => {
|
|
2688
3692
|
const detail = event.detail;
|
|
@@ -2839,11 +3843,22 @@ class WidgetRoot {
|
|
|
2839
3843
|
}
|
|
2840
3844
|
/** Persist fulfilled state, emit callback, and refresh UI */
|
|
2841
3845
|
processGroupFulfilled(eventData) {
|
|
2842
|
-
var _a, _b;
|
|
3846
|
+
var _a, _b, _c, _d;
|
|
2843
3847
|
this.groupFulfilled = true;
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
3848
|
+
// Extract reward from new event structure or fallback to legacy
|
|
3849
|
+
const reward = ((_a = eventData.frozen_reward) === null || _a === void 0 ? void 0 : _a.reward) || eventData.reward || null;
|
|
3850
|
+
this.frozenReward = reward;
|
|
3851
|
+
// Extract group ID from new event structure or fallback to legacy
|
|
3852
|
+
this.currentGroupId = ((_b = eventData.group) === null || _b === void 0 ? void 0 : _b.id) || eventData.groupId || null;
|
|
3853
|
+
// Store offline redemption data if available
|
|
3854
|
+
if (eventData.offline_redemption && isValidOfflineRedemption(eventData.offline_redemption)) {
|
|
3855
|
+
this.offlineRedemption = eventData.offline_redemption;
|
|
3856
|
+
this.logger.info("Offline redemption data stored", {
|
|
3857
|
+
member_id: this.offlineRedemption.member_id,
|
|
3858
|
+
code: this.offlineRedemption.redemption_code,
|
|
3859
|
+
});
|
|
3860
|
+
}
|
|
3861
|
+
(_d = (_c = this.events) === null || _c === void 0 ? void 0 : _c.onGroupFulfilled) === null || _d === void 0 ? void 0 : _d.call(_c, eventData);
|
|
2847
3862
|
this.renderFulfilledState();
|
|
2848
3863
|
}
|
|
2849
3864
|
/** Re-render widget and external containers to reflect fulfillment */
|
|
@@ -2958,16 +3973,42 @@ class WidgetRoot {
|
|
|
2958
3973
|
rewardCheck.textContent = "✔";
|
|
2959
3974
|
rewardCheck.style.color = "var(--cobuy-success-color, #10b981)";
|
|
2960
3975
|
const rewardLabel = document.createElement("span");
|
|
2961
|
-
rewardLabel.textContent = rewardText
|
|
3976
|
+
rewardLabel.textContent = rewardText
|
|
3977
|
+
? `Discount available: ${rewardText}`
|
|
3978
|
+
: "Discount available";
|
|
2962
3979
|
rewardLine.appendChild(rewardCheck);
|
|
2963
3980
|
rewardLine.appendChild(rewardLabel);
|
|
2964
3981
|
root.appendChild(title);
|
|
2965
3982
|
root.appendChild(rewardLine);
|
|
2966
|
-
// Create footer with
|
|
3983
|
+
// Create footer with action CTAs
|
|
2967
3984
|
const footer = document.createElement("div");
|
|
2968
3985
|
footer.style.display = "flex";
|
|
2969
3986
|
footer.style.justifyContent = "flex-end";
|
|
2970
3987
|
footer.style.alignItems = "center";
|
|
3988
|
+
footer.style.gap = "12px";
|
|
3989
|
+
// Add "Redeem In-store" CTA if offline redemption is available
|
|
3990
|
+
if (this.offlineRedemption && isValidOfflineRedemption(this.offlineRedemption)) {
|
|
3991
|
+
const redeemLink = document.createElement("button");
|
|
3992
|
+
redeemLink.className = "cobuy-redeem-instore-link";
|
|
3993
|
+
redeemLink.style.background = "none";
|
|
3994
|
+
redeemLink.style.border = "none";
|
|
3995
|
+
redeemLink.style.color = "var(--cobuy-primary-color, #3b82f6)";
|
|
3996
|
+
redeemLink.style.cursor = "pointer";
|
|
3997
|
+
redeemLink.style.fontSize = "12px";
|
|
3998
|
+
redeemLink.style.fontWeight = "600";
|
|
3999
|
+
redeemLink.style.textDecoration = "underline";
|
|
4000
|
+
redeemLink.style.padding = "0";
|
|
4001
|
+
redeemLink.style.transition = "opacity 0.2s";
|
|
4002
|
+
redeemLink.textContent = "Redeem In-store";
|
|
4003
|
+
redeemLink.addEventListener("click", () => this.openOfflineRedemptionModal());
|
|
4004
|
+
redeemLink.addEventListener("mouseover", () => {
|
|
4005
|
+
redeemLink.style.opacity = "0.7";
|
|
4006
|
+
});
|
|
4007
|
+
redeemLink.addEventListener("mouseout", () => {
|
|
4008
|
+
redeemLink.style.opacity = "1";
|
|
4009
|
+
});
|
|
4010
|
+
footer.appendChild(redeemLink);
|
|
4011
|
+
}
|
|
2971
4012
|
root.appendChild(footer);
|
|
2972
4013
|
return root;
|
|
2973
4014
|
}
|
|
@@ -3151,6 +4192,12 @@ class WidgetRoot {
|
|
|
3151
4192
|
this.currentGroupData = groupData;
|
|
3152
4193
|
this.currentGroupId = (groupData === null || groupData === void 0 ? void 0 : groupData.id) || null;
|
|
3153
4194
|
this.currentRewardData = rewardData;
|
|
4195
|
+
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));
|
|
4196
|
+
// Extract offline_redemption from API response if available
|
|
4197
|
+
if ((groupData === null || groupData === void 0 ? void 0 : groupData.offline_redemption) &&
|
|
4198
|
+
isValidOfflineRedemption(groupData.offline_redemption)) {
|
|
4199
|
+
this.offlineRedemption = groupData.offline_redemption;
|
|
4200
|
+
}
|
|
3154
4201
|
// Check if group is already fulfilled (full) on initial load
|
|
3155
4202
|
if (groupData) {
|
|
3156
4203
|
const participants = Number(groupData.participants_count || 0);
|
|
@@ -3569,16 +4616,14 @@ class WidgetRoot {
|
|
|
3569
4616
|
// rewardText ? `Reward locked in: ${rewardText}` : "Reward available for this group",
|
|
3570
4617
|
// );
|
|
3571
4618
|
// rewardLine.title = rewardLine.textContent;
|
|
3572
|
-
// }
|
|
3573
|
-
// else if (activeReward) {
|
|
4619
|
+
// } else if (activeReward) {
|
|
3574
4620
|
// const rewardText = this.formatRewardText(activeReward);
|
|
3575
4621
|
// rewardLine.textContent = rewardText
|
|
3576
4622
|
// ? `Save up to ${rewardText} with CoBuy`
|
|
3577
4623
|
// : "CoBuy reward available";
|
|
3578
4624
|
// rewardLine.setAttribute("aria-label", `Eligible for CoBuy reward: ${rewardLine.textContent}`);
|
|
3579
4625
|
// rewardLine.title = rewardLine.textContent;
|
|
3580
|
-
// }
|
|
3581
|
-
// else {
|
|
4626
|
+
// } else {
|
|
3582
4627
|
// rewardLine.textContent = "CoBuy offer loading or unavailable";
|
|
3583
4628
|
// rewardLine.setAttribute("aria-label", "CoBuy offer loading or unavailable");
|
|
3584
4629
|
// rewardLine.title = "CoBuy offer loading or unavailable";
|
|
@@ -3814,6 +4859,12 @@ class WidgetRoot {
|
|
|
3814
4859
|
}
|
|
3815
4860
|
groupJoinData = joinResponse.data;
|
|
3816
4861
|
this.logger.info("Successfully joined group", groupJoinData);
|
|
4862
|
+
this.offlineRedemption =
|
|
4863
|
+
groupJoinData &&
|
|
4864
|
+
groupJoinData.offline_redemption &&
|
|
4865
|
+
isValidOfflineRedemption(groupJoinData.offline_redemption)
|
|
4866
|
+
? groupJoinData.offline_redemption
|
|
4867
|
+
: null;
|
|
3817
4868
|
// Trigger invite tracking before opening lobby (global for product)
|
|
3818
4869
|
try {
|
|
3819
4870
|
const inviteResponse = await this.apiClient.inviteToGroup(this.currentGroupId, "copy_link");
|
|
@@ -3859,6 +4910,13 @@ class WidgetRoot {
|
|
|
3859
4910
|
const isGroupFulfilled = groupJoinData.group.participants_count >= groupJoinData.group.max_participants;
|
|
3860
4911
|
const groupLinkFromInvite = inviteData === null || inviteData === void 0 ? void 0 : inviteData.invite_url;
|
|
3861
4912
|
const shareMessageFromInvite = inviteData === null || inviteData === void 0 ? void 0 : inviteData.share_message;
|
|
4913
|
+
console.log("offlien dataaa", groupJoinData.offline_redemption, isValidOfflineRedemption(groupJoinData.offline_redemption));
|
|
4914
|
+
// Extract offline_redemption from join response if available
|
|
4915
|
+
const offlineRedemptionFromJoin = groupJoinData.offline_redemption &&
|
|
4916
|
+
isValidOfflineRedemption(groupJoinData.offline_redemption)
|
|
4917
|
+
? groupJoinData.offline_redemption
|
|
4918
|
+
: undefined;
|
|
4919
|
+
this.offlineRedemption = offlineRedemptionFromJoin || null;
|
|
3862
4920
|
// Prepare modal data with real values from join response
|
|
3863
4921
|
cobuySDK.openModal({
|
|
3864
4922
|
productId,
|
|
@@ -3877,6 +4935,7 @@ class WidgetRoot {
|
|
|
3877
4935
|
groupLink: groupLinkFromInvite || `https://cobuy.group/lobby/${groupJoinData.group.id}`,
|
|
3878
4936
|
shareMessage: shareMessageFromInvite,
|
|
3879
4937
|
isLocked: !isGroupFulfilled,
|
|
4938
|
+
offlineRedemption: offlineRedemptionFromJoin,
|
|
3880
4939
|
activities: [
|
|
3881
4940
|
{
|
|
3882
4941
|
emoji: "👤",
|
|
@@ -3926,6 +4985,20 @@ class WidgetRoot {
|
|
|
3926
4985
|
}
|
|
3927
4986
|
}
|
|
3928
4987
|
}
|
|
4988
|
+
/**
|
|
4989
|
+
* Open offline redemption modal when user clicks "Redeem In-store"
|
|
4990
|
+
*/
|
|
4991
|
+
openOfflineRedemptionModal() {
|
|
4992
|
+
if (!this.offlineRedemption || !isValidOfflineRedemption(this.offlineRedemption)) {
|
|
4993
|
+
this.logger.warn("Offline redemption data not available");
|
|
4994
|
+
return;
|
|
4995
|
+
}
|
|
4996
|
+
this.logger.info("Opening offline redemption modal");
|
|
4997
|
+
this.offlineRedemptionModal = new OfflineRedemptionModal(this.offlineRedemption, () => {
|
|
4998
|
+
this.offlineRedemptionModal = null;
|
|
4999
|
+
});
|
|
5000
|
+
this.offlineRedemptionModal.open();
|
|
5001
|
+
}
|
|
3929
5002
|
/**
|
|
3930
5003
|
* Emit checkout intent without performing navigation
|
|
3931
5004
|
*/
|
|
@@ -3984,13 +5057,13 @@ class WidgetRoot {
|
|
|
3984
5057
|
const reward = this.frozenReward || (rewardData === null || rewardData === void 0 ? void 0 : rewardData.reward);
|
|
3985
5058
|
if (this.groupFulfilled) {
|
|
3986
5059
|
const rewardText = this.formatRewardText(reward);
|
|
3987
|
-
return rewardText ? `
|
|
5060
|
+
return rewardText ? `Discount available: ${rewardText}` : "Discount available for this group";
|
|
3988
5061
|
}
|
|
3989
5062
|
if (!reward) {
|
|
3990
|
-
return "Join with CoBuy to unlock
|
|
5063
|
+
return "Join with CoBuy to unlock discount";
|
|
3991
5064
|
}
|
|
3992
5065
|
const rewardText = this.formatRewardText(reward);
|
|
3993
|
-
return rewardText ? `Save up to ${rewardText} with CoBuy` : "CoBuy
|
|
5066
|
+
return rewardText ? `Save up to ${rewardText} with CoBuy` : "CoBuy discount available";
|
|
3994
5067
|
}
|
|
3995
5068
|
/**
|
|
3996
5069
|
* Public hook to request a realtime refresh from external callers
|
|
@@ -4588,7 +5661,7 @@ class AnalyticsClient {
|
|
|
4588
5661
|
*/
|
|
4589
5662
|
async trackCtaClick(productId) {
|
|
4590
5663
|
const event = {
|
|
4591
|
-
event: "
|
|
5664
|
+
event: "CTA_CLICKED",
|
|
4592
5665
|
productId,
|
|
4593
5666
|
timestamp: new Date().toISOString(),
|
|
4594
5667
|
sessionId: this.sessionId,
|
|
@@ -4632,6 +5705,28 @@ class AnalyticsClient {
|
|
|
4632
5705
|
throw error;
|
|
4633
5706
|
}
|
|
4634
5707
|
}
|
|
5708
|
+
/**
|
|
5709
|
+
* Track page view event
|
|
5710
|
+
*/
|
|
5711
|
+
async trackPageView() {
|
|
5712
|
+
const event = {
|
|
5713
|
+
event: "PAGE_VIEW",
|
|
5714
|
+
timestamp: new Date().toISOString(),
|
|
5715
|
+
context: {
|
|
5716
|
+
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
5717
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
|
5718
|
+
sdkVersion: this.sdkVersion,
|
|
5719
|
+
},
|
|
5720
|
+
};
|
|
5721
|
+
try {
|
|
5722
|
+
await this.sendEvent(event);
|
|
5723
|
+
this.logger.info("[Analytics] Page view tracked");
|
|
5724
|
+
}
|
|
5725
|
+
catch (error) {
|
|
5726
|
+
// Log but don't throw - analytics failure should not break UX
|
|
5727
|
+
this.logger.error("[Analytics] Failed to track page view", error);
|
|
5728
|
+
}
|
|
5729
|
+
}
|
|
4635
5730
|
/**
|
|
4636
5731
|
* Track custom event (extensible for future use)
|
|
4637
5732
|
*/
|
|
@@ -8819,9 +9914,10 @@ Object.assign(lookup, {
|
|
|
8819
9914
|
* Manages a single socket.io connection for the SDK
|
|
8820
9915
|
*/
|
|
8821
9916
|
class SocketManager {
|
|
8822
|
-
constructor(config, sdkVersion) {
|
|
9917
|
+
constructor(config, sdkVersion, sessionId) {
|
|
8823
9918
|
this.config = config;
|
|
8824
9919
|
this.sdkVersion = sdkVersion;
|
|
9920
|
+
this.sessionId = sessionId;
|
|
8825
9921
|
this.socket = null;
|
|
8826
9922
|
this.logger = new Logger(config.debug);
|
|
8827
9923
|
}
|
|
@@ -8866,6 +9962,7 @@ class SocketManager {
|
|
|
8866
9962
|
auth: {
|
|
8867
9963
|
merchantKey: this.config.merchantKey,
|
|
8868
9964
|
sdkVersion: this.sdkVersion,
|
|
9965
|
+
sessionId: this.sessionId,
|
|
8869
9966
|
},
|
|
8870
9967
|
autoConnect: true,
|
|
8871
9968
|
});
|
|
@@ -9052,7 +10149,9 @@ class CoBuy {
|
|
|
9052
10149
|
const ref = window.localStorage.getItem(key);
|
|
9053
10150
|
if (ref) {
|
|
9054
10151
|
this.logger.debug(`[SDK] Retrieved checkout reference via prefix: ${key}`);
|
|
9055
|
-
const parsedGroupId = key.startsWith(`${basePrefix}_`)
|
|
10152
|
+
const parsedGroupId = key.startsWith(`${basePrefix}_`)
|
|
10153
|
+
? key.substring(basePrefix.length + 1)
|
|
10154
|
+
: null;
|
|
9056
10155
|
return { key, checkoutRef: ref, groupId: parsedGroupId };
|
|
9057
10156
|
}
|
|
9058
10157
|
}
|
|
@@ -9142,7 +10241,7 @@ class CoBuy {
|
|
|
9142
10241
|
// Initialize real-time socket connection for public mode
|
|
9143
10242
|
if (config.authMode === "public" && config.merchantKey) {
|
|
9144
10243
|
try {
|
|
9145
|
-
this.socketManager = new SocketManager(config, SDK_VERSION);
|
|
10244
|
+
this.socketManager = new SocketManager(config, SDK_VERSION, this.sessionId);
|
|
9146
10245
|
this.socketManager.connect();
|
|
9147
10246
|
this.socketManager.bindHandlers({
|
|
9148
10247
|
onGroupFulfilled: (payload) => {
|
|
@@ -9184,6 +10283,13 @@ class CoBuy {
|
|
|
9184
10283
|
this.logger.warn("[SDK] Failed to initialize sockets", e);
|
|
9185
10284
|
}
|
|
9186
10285
|
}
|
|
10286
|
+
// Track page view event after successful initialization
|
|
10287
|
+
if (this.analyticsClient) {
|
|
10288
|
+
this.analyticsClient.trackPageView().catch((error) => {
|
|
10289
|
+
// Non-blocking: Analytics failure should not affect SDK initialization
|
|
10290
|
+
this.logger.warn("[SDK] Failed to track page view", error);
|
|
10291
|
+
});
|
|
10292
|
+
}
|
|
9187
10293
|
this.logger.info("SDK initialization complete");
|
|
9188
10294
|
}
|
|
9189
10295
|
catch (error) {
|
|
@@ -9263,6 +10369,7 @@ class CoBuy {
|
|
|
9263
10369
|
groupLink: options.groupLink,
|
|
9264
10370
|
activities: options.activities,
|
|
9265
10371
|
isLocked: options.isLocked,
|
|
10372
|
+
offlineRedemption: options.offlineRedemption,
|
|
9266
10373
|
};
|
|
9267
10374
|
// Create modal instance
|
|
9268
10375
|
const modal = new LobbyModal(modalData, {
|