@cshah18/sdk 3.0.5 → 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 +1303 -95
- package/dist/cobuy-sdk.esm.js.map +1 -1
- package/dist/cobuy-sdk.umd.js +1303 -95
- 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 -5
- package/dist/types/ui/group-list/group-list-modal.d.ts +11 -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
|
@@ -304,11 +304,44 @@ class GroupListModal {
|
|
|
304
304
|
this.currentSessionId = null;
|
|
305
305
|
this.onGroupJoined = null;
|
|
306
306
|
this.onViewProgress = null;
|
|
307
|
+
this.socketListenerRegistered = false;
|
|
307
308
|
this.escapeHandler = (event) => {
|
|
308
309
|
if (event.key === "Escape") {
|
|
309
310
|
this.close();
|
|
310
311
|
}
|
|
311
312
|
};
|
|
313
|
+
this.handleGroupMemberJoinedEvent = (event) => {
|
|
314
|
+
this.onGroupMemberJoined(event);
|
|
315
|
+
};
|
|
316
|
+
/** Handle group member joined socket event and update groups list */
|
|
317
|
+
this.onGroupMemberJoined = (event) => {
|
|
318
|
+
const detail = event.detail || {};
|
|
319
|
+
const productId = detail.product_id;
|
|
320
|
+
const groupData = detail.group;
|
|
321
|
+
// Only process if this is for the current product
|
|
322
|
+
if (!productId || !this.currentProductId || productId !== this.currentProductId) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (!groupData) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const groupId = groupData.id;
|
|
329
|
+
const participantsCount = groupData.participants_count;
|
|
330
|
+
if (!groupId) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
// Find and update the group in the list
|
|
334
|
+
const groupIndex = this.groups.findIndex((g) => g.groupId === groupId);
|
|
335
|
+
if (groupIndex !== -1) {
|
|
336
|
+
// Update the joined count
|
|
337
|
+
if (typeof participantsCount === "number") {
|
|
338
|
+
this.groups[groupIndex].joined = participantsCount;
|
|
339
|
+
}
|
|
340
|
+
this.logger.info(`[GroupListModal] Updated group ${groupId} - participants: ${participantsCount}`);
|
|
341
|
+
// Re-render the specific group card
|
|
342
|
+
this.updateGroupCard(groupId);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
312
345
|
this.logger = new Logger(debug);
|
|
313
346
|
this.groups = groups;
|
|
314
347
|
this.liveCount = liveCount;
|
|
@@ -335,6 +368,65 @@ class GroupListModal {
|
|
|
335
368
|
hasGroup(groupId) {
|
|
336
369
|
return this.groups.some((group) => group.groupId === groupId);
|
|
337
370
|
}
|
|
371
|
+
/** Subscribe to socket events for real-time group updates */
|
|
372
|
+
subscribeToSocketEvents() {
|
|
373
|
+
if (typeof window === "undefined" || this.socketListenerRegistered) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
window.addEventListener("group:member:joined", this.handleGroupMemberJoinedEvent);
|
|
377
|
+
this.socketListenerRegistered = true;
|
|
378
|
+
this.logger.debug("[GroupListModal] Socket event listeners registered");
|
|
379
|
+
}
|
|
380
|
+
/** Unsubscribe from socket events */
|
|
381
|
+
unsubscribeFromSocketEvents() {
|
|
382
|
+
if (typeof window === "undefined" || !this.socketListenerRegistered) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
window.removeEventListener("group:member:joined", this.handleGroupMemberJoinedEvent);
|
|
386
|
+
this.socketListenerRegistered = false;
|
|
387
|
+
this.logger.debug("[GroupListModal] Socket event listeners unregistered");
|
|
388
|
+
}
|
|
389
|
+
/** Update the rendered group card with new data */
|
|
390
|
+
updateGroupCard(groupId) {
|
|
391
|
+
if (!this.overlayEl) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const groupCard = this.overlayEl.querySelector(`[data-group-id="${groupId}"]`);
|
|
395
|
+
if (!groupCard) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const group = this.groups.find((g) => g.groupId === groupId);
|
|
399
|
+
if (!group) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
// Update the progress info
|
|
403
|
+
const remaining = Math.max(group.total - group.joined, 0);
|
|
404
|
+
const joinedCountEl = groupCard.querySelector(".cobuy-joined-count");
|
|
405
|
+
const spotsLeftEl = groupCard.querySelector(".cobuy-spots-left");
|
|
406
|
+
if (joinedCountEl) {
|
|
407
|
+
joinedCountEl.textContent = `${group.joined} Joined`;
|
|
408
|
+
}
|
|
409
|
+
if (spotsLeftEl) {
|
|
410
|
+
spotsLeftEl.textContent = `${remaining} ${remaining === 1 ? "spot" : "spots"} left`;
|
|
411
|
+
}
|
|
412
|
+
// Update the progress bar
|
|
413
|
+
const progressFill = groupCard.querySelector(".cobuy-gl-progress-fill");
|
|
414
|
+
if (progressFill) {
|
|
415
|
+
const pct = Math.max(0, Math.min(100, Math.round((group.joined / group.total) * 100)));
|
|
416
|
+
progressFill.style.width = `${pct}%`;
|
|
417
|
+
}
|
|
418
|
+
// Update member avatars
|
|
419
|
+
const membersContainer = groupCard.querySelector(".cobuy-group-members");
|
|
420
|
+
if (membersContainer) {
|
|
421
|
+
// Clear existing avatars
|
|
422
|
+
membersContainer.innerHTML = "";
|
|
423
|
+
// Recreate avatars with updated joined count
|
|
424
|
+
for (let i = 0; i < group.total; i++) {
|
|
425
|
+
const avatar = this.createMemberAvatar(i >= group.joined);
|
|
426
|
+
membersContainer.appendChild(avatar);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
338
430
|
async open(productId, sessionId, joinedGroupId) {
|
|
339
431
|
if (this.overlayEl) {
|
|
340
432
|
this.logger.debug("Group list modal already open");
|
|
@@ -380,6 +472,8 @@ class GroupListModal {
|
|
|
380
472
|
this.overlayEl = overlay;
|
|
381
473
|
document.body.appendChild(overlay);
|
|
382
474
|
document.addEventListener("keydown", this.escapeHandler);
|
|
475
|
+
// Subscribe to socket events for real-time group updates
|
|
476
|
+
this.subscribeToSocketEvents();
|
|
383
477
|
requestAnimationFrame(() => {
|
|
384
478
|
overlay.classList.add("cobuy-gl-open");
|
|
385
479
|
});
|
|
@@ -410,12 +504,14 @@ class GroupListModal {
|
|
|
410
504
|
// Convert API groups to display format
|
|
411
505
|
const groups = response.data.groups.map((apiGroup) => ({
|
|
412
506
|
groupId: apiGroup.id,
|
|
507
|
+
name: apiGroup.group_name,
|
|
413
508
|
timeLabel: this.formatTimeRemaining(apiGroup.timeLeftSeconds),
|
|
414
509
|
timeLeftSeconds: apiGroup.timeLeftSeconds,
|
|
415
510
|
joined: apiGroup.participants_count,
|
|
416
511
|
total: apiGroup.max_participants,
|
|
417
512
|
isMember: Boolean(apiGroup.is_member),
|
|
418
513
|
}));
|
|
514
|
+
console.log("groupsss", groups);
|
|
419
515
|
// If API reports membership, prefer it over cached state
|
|
420
516
|
const memberGroup = groups.find((g) => g.isMember);
|
|
421
517
|
if (memberGroup) {
|
|
@@ -477,6 +573,8 @@ class GroupListModal {
|
|
|
477
573
|
if (!this.overlayEl) {
|
|
478
574
|
return;
|
|
479
575
|
}
|
|
576
|
+
// Unsubscribe from socket events
|
|
577
|
+
this.unsubscribeFromSocketEvents();
|
|
480
578
|
// Clear any running countdown intervals to avoid leaks
|
|
481
579
|
this.countdownIntervals.forEach((id) => window.clearInterval(id));
|
|
482
580
|
this.countdownIntervals = [];
|
|
@@ -720,11 +818,12 @@ class GroupListModal {
|
|
|
720
818
|
createGroupCard(group) {
|
|
721
819
|
const card = document.createElement("div");
|
|
722
820
|
card.className = "cobuy-group-card";
|
|
821
|
+
card.setAttribute("data-group-id", group.name || group.groupId);
|
|
723
822
|
const header = document.createElement("div");
|
|
724
823
|
header.className = "cobuy-group-card-header";
|
|
725
824
|
const groupId = document.createElement("div");
|
|
726
825
|
groupId.className = "cobuy-group-id";
|
|
727
|
-
groupId.textContent = `Group #${group.groupId}`;
|
|
826
|
+
groupId.textContent = `Group #${group.name || group.groupId}`;
|
|
728
827
|
const timer = document.createElement("div");
|
|
729
828
|
timer.className = "cobuy-group-timer";
|
|
730
829
|
timer.innerHTML = `
|
|
@@ -1263,6 +1362,158 @@ class GroupListModal {
|
|
|
1263
1362
|
}
|
|
1264
1363
|
}
|
|
1265
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
|
+
|
|
1266
1517
|
function styleInject(css, ref) {
|
|
1267
1518
|
if ( ref === void 0 ) ref = {};
|
|
1268
1519
|
var insertAt = ref.insertAt;
|
|
@@ -1290,7 +1541,7 @@ function styleInject(css, ref) {
|
|
|
1290
1541
|
}
|
|
1291
1542
|
}
|
|
1292
1543
|
|
|
1293
|
-
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}";
|
|
1294
1545
|
styleInject(css_248z,{"insertAt":"bottom"});
|
|
1295
1546
|
|
|
1296
1547
|
/**
|
|
@@ -1326,6 +1577,7 @@ class LobbyModal {
|
|
|
1326
1577
|
this.handleSocketGroupUpdate = (event) => {
|
|
1327
1578
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
1328
1579
|
const detail = event.detail || {};
|
|
1580
|
+
console.log("group completed", event, detail);
|
|
1329
1581
|
const normalizeId = (id) => {
|
|
1330
1582
|
if (id === undefined || id === null)
|
|
1331
1583
|
return null;
|
|
@@ -1395,6 +1647,14 @@ class LobbyModal {
|
|
|
1395
1647
|
status: isComplete ? "complete" : "active",
|
|
1396
1648
|
isLocked: !isComplete,
|
|
1397
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
|
+
}
|
|
1398
1658
|
this.updateData(updatePayload);
|
|
1399
1659
|
// Also update team card to reflect new member count
|
|
1400
1660
|
this.updateTeamCard(participants, max);
|
|
@@ -1412,6 +1672,7 @@ class LobbyModal {
|
|
|
1412
1672
|
currentMembers: data.currentMembers,
|
|
1413
1673
|
totalMembers: data.totalMembers,
|
|
1414
1674
|
timeLeft: data.timeLeft,
|
|
1675
|
+
offlineRedemption: data.offlineRedemption,
|
|
1415
1676
|
});
|
|
1416
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);
|
|
1417
1678
|
// Normalize optional flags so undefined values don't override defaults
|
|
@@ -1442,7 +1703,7 @@ class LobbyModal {
|
|
|
1442
1703
|
return true;
|
|
1443
1704
|
}
|
|
1444
1705
|
getRewardText(isLocked) {
|
|
1445
|
-
return isLocked ? "🔓 Complete group to unlock
|
|
1706
|
+
return isLocked ? "🔓 Complete group to unlock discount." : "🎉 Discount unlocked!";
|
|
1446
1707
|
}
|
|
1447
1708
|
getTitleText(data) {
|
|
1448
1709
|
var _a, _b;
|
|
@@ -1451,19 +1712,19 @@ class LobbyModal {
|
|
|
1451
1712
|
const unlocked = !this.computeIsLocked(data);
|
|
1452
1713
|
if (unlocked) {
|
|
1453
1714
|
return {
|
|
1454
|
-
title: "
|
|
1455
|
-
subtitle: "Group filled — enjoy your
|
|
1715
|
+
title: "Discount unlocked!",
|
|
1716
|
+
subtitle: "Group filled — enjoy your discount.",
|
|
1456
1717
|
};
|
|
1457
1718
|
}
|
|
1458
1719
|
if (remaining <= 1) {
|
|
1459
1720
|
return {
|
|
1460
1721
|
title: "Just one more!",
|
|
1461
|
-
subtitle: "Invite a friend to unlock your
|
|
1722
|
+
subtitle: "Invite a friend to unlock your discount instantly.",
|
|
1462
1723
|
};
|
|
1463
1724
|
}
|
|
1464
1725
|
return {
|
|
1465
1726
|
title: "Almost there!",
|
|
1466
|
-
subtitle: `Just ${remaining} more ${remaining === 1 ? "member" : "members"} — your
|
|
1727
|
+
subtitle: `Just ${remaining} more ${remaining === 1 ? "member" : "members"} — your discount unlocks instantly.`,
|
|
1467
1728
|
};
|
|
1468
1729
|
}
|
|
1469
1730
|
/**
|
|
@@ -1513,6 +1774,62 @@ class LobbyModal {
|
|
|
1513
1774
|
},
|
|
1514
1775
|
];
|
|
1515
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
|
+
}
|
|
1516
1833
|
/**
|
|
1517
1834
|
* Create the modal DOM structure
|
|
1518
1835
|
*/
|
|
@@ -1535,9 +1852,12 @@ class LobbyModal {
|
|
|
1535
1852
|
// Top section
|
|
1536
1853
|
const topSection = this.createTopSection();
|
|
1537
1854
|
mainContent.appendChild(topSection);
|
|
1538
|
-
// Activity section
|
|
1539
|
-
const
|
|
1540
|
-
|
|
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
|
+
}
|
|
1541
1861
|
mainWrapper.appendChild(mainContent);
|
|
1542
1862
|
modal.appendChild(background);
|
|
1543
1863
|
modal.appendChild(mainWrapper);
|
|
@@ -1580,9 +1900,9 @@ class LobbyModal {
|
|
|
1580
1900
|
// Title
|
|
1581
1901
|
const titleWp = this.createTitleSection();
|
|
1582
1902
|
leftWp.appendChild(titleWp);
|
|
1583
|
-
//
|
|
1584
|
-
const
|
|
1585
|
-
leftWp.appendChild(
|
|
1903
|
+
// Connected section (subtitle + link + share)
|
|
1904
|
+
const connectedSection = this.createConnectedSection();
|
|
1905
|
+
leftWp.appendChild(connectedSection);
|
|
1586
1906
|
// Offer
|
|
1587
1907
|
const offerBox = this.createOfferSection();
|
|
1588
1908
|
leftWp.appendChild(offerBox);
|
|
@@ -1594,24 +1914,35 @@ class LobbyModal {
|
|
|
1594
1914
|
createStatusSection() {
|
|
1595
1915
|
var _a;
|
|
1596
1916
|
const statusWp = document.createElement("div");
|
|
1597
|
-
statusWp.className = "lobby-status-
|
|
1917
|
+
statusWp.className = "lobby-status-section";
|
|
1598
1918
|
// Use safe defaults so UI never shows undefined values
|
|
1599
1919
|
const groupNum = (_a = this.data.groupNumber) !== null && _a !== void 0 ? _a : "1000";
|
|
1600
1920
|
const status = this.data.status === "complete" ? "complete" : "active";
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
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";
|
|
1604
1934
|
// Render only the current status chip
|
|
1605
1935
|
const statusChip = document.createElement("div");
|
|
1606
1936
|
statusChip.className = `lobby-status ${status}`;
|
|
1607
1937
|
statusChip.id = "lobbyStatusChip";
|
|
1608
|
-
statusChip.textContent = status === "complete" ? "
|
|
1938
|
+
statusChip.textContent = status === "complete" ? "COMPLETED" : "ACTIVE";
|
|
1609
1939
|
const groupNumber = document.createElement("div");
|
|
1610
1940
|
groupNumber.className = "lobby-number";
|
|
1611
1941
|
groupNumber.textContent = `Group #${groupNum}`;
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
statusWp.appendChild(
|
|
1942
|
+
statusRow.appendChild(statusChip);
|
|
1943
|
+
statusRow.appendChild(groupNumber);
|
|
1944
|
+
statusWp.appendChild(lobbyIndicator);
|
|
1945
|
+
statusWp.appendChild(statusRow);
|
|
1615
1946
|
return statusWp;
|
|
1616
1947
|
}
|
|
1617
1948
|
/**
|
|
@@ -1624,24 +1955,49 @@ class LobbyModal {
|
|
|
1624
1955
|
const title = document.createElement("h2");
|
|
1625
1956
|
title.id = "lobbyTitleText";
|
|
1626
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
|
|
1627
1970
|
const subtitle = document.createElement("p");
|
|
1628
1971
|
subtitle.className = "sub-title";
|
|
1629
1972
|
subtitle.id = "lobbySubtitleText";
|
|
1630
1973
|
subtitle.textContent = titleContent.subtitle;
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
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;
|
|
1634
1994
|
}
|
|
1635
1995
|
/**
|
|
1636
|
-
* Create link section
|
|
1996
|
+
* Create link section (just the link box and share button, no wrapper)
|
|
1637
1997
|
*/
|
|
1638
1998
|
createLinkSection() {
|
|
1639
|
-
const
|
|
1640
|
-
|
|
1641
|
-
linkWp.id = "lobbyLinkSection";
|
|
1642
|
-
if (!this.data.isLocked) {
|
|
1643
|
-
linkWp.style.display = "none";
|
|
1644
|
-
}
|
|
1999
|
+
const linkShareWrapper = document.createElement("div");
|
|
2000
|
+
linkShareWrapper.className = "link-share-wrapper";
|
|
1645
2001
|
// Link box
|
|
1646
2002
|
const linkBox = document.createElement("div");
|
|
1647
2003
|
linkBox.className = "lobby-link-box";
|
|
@@ -1656,23 +2012,324 @@ class LobbyModal {
|
|
|
1656
2012
|
linkUrl.textContent = this.data.groupLink || `cobuy.group/lobby/${this.data.productId}`;
|
|
1657
2013
|
linkBoxInr.appendChild(linkText);
|
|
1658
2014
|
linkBoxInr.appendChild(linkUrl);
|
|
1659
|
-
const copyBtn = document.createElement("
|
|
2015
|
+
const copyBtn = document.createElement("button");
|
|
1660
2016
|
copyBtn.className = "copy-link-btn";
|
|
1661
|
-
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>`;
|
|
1662
2018
|
copyBtn.addEventListener("click", () => this.copyLink());
|
|
1663
2019
|
linkBox.appendChild(linkBoxInr);
|
|
1664
2020
|
linkBox.appendChild(copyBtn);
|
|
1665
2021
|
// Share button
|
|
1666
|
-
const
|
|
1667
|
-
shareBtn.className = "share-btn-wp";
|
|
1668
|
-
const shareBtnInner = document.createElement("div");
|
|
2022
|
+
const shareBtnInner = document.createElement("button");
|
|
1669
2023
|
shareBtnInner.className = "share-btn";
|
|
1670
|
-
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>`;
|
|
1671
2025
|
shareBtnInner.addEventListener("click", () => this.share());
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
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);
|
|
1676
2333
|
}
|
|
1677
2334
|
/**
|
|
1678
2335
|
* Create offer section
|
|
@@ -2311,24 +2968,27 @@ class LobbyModal {
|
|
|
2311
2968
|
groupNumber: this.data.groupNumber,
|
|
2312
2969
|
status: this.data.status,
|
|
2313
2970
|
});
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
this.socketManager.
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
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
|
+
});
|
|
2332
2992
|
}
|
|
2333
2993
|
/**
|
|
2334
2994
|
* Close the modal
|
|
@@ -2374,6 +3034,10 @@ class LobbyModal {
|
|
|
2374
3034
|
this.updateLockUI(!!this.data.isLocked);
|
|
2375
3035
|
this.updateTitleUI(this.data);
|
|
2376
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
|
+
}
|
|
2377
3041
|
// Update progress if changed
|
|
2378
3042
|
if (data.progress !== undefined) {
|
|
2379
3043
|
const progressFill = document.getElementById("progressFill");
|
|
@@ -2448,6 +3112,42 @@ class LobbyModal {
|
|
|
2448
3112
|
return;
|
|
2449
3113
|
linkSection.style.display = isLocked ? "" : "none";
|
|
2450
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
|
+
}
|
|
2451
3151
|
/**
|
|
2452
3152
|
* Subscribe to socket events
|
|
2453
3153
|
*/
|
|
@@ -2484,7 +3184,7 @@ class LobbyModal {
|
|
|
2484
3184
|
break;
|
|
2485
3185
|
case "group:fulfilled":
|
|
2486
3186
|
emoji = "🎁";
|
|
2487
|
-
action = "unlocked the
|
|
3187
|
+
action = "unlocked the discount – group filled!";
|
|
2488
3188
|
break;
|
|
2489
3189
|
case "group:created":
|
|
2490
3190
|
emoji = "🛒";
|
|
@@ -2552,6 +3252,405 @@ class LobbyModal {
|
|
|
2552
3252
|
}
|
|
2553
3253
|
}
|
|
2554
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
|
+
|
|
2555
3654
|
/// <reference lib="dom" />
|
|
2556
3655
|
/**
|
|
2557
3656
|
* Widget state enumeration for tracking render lifecycle
|
|
@@ -2586,6 +3685,8 @@ class WidgetRoot {
|
|
|
2586
3685
|
this.lastGroupDataRefreshTime = 0;
|
|
2587
3686
|
this.GROUP_REFRESH_DEBOUNCE = 1000; // 1 second min between refreshes
|
|
2588
3687
|
this.groupExpiryRefreshTriggered = false; // Track if expiry refresh already triggered for current group
|
|
3688
|
+
this.offlineRedemption = null;
|
|
3689
|
+
this.offlineRedemptionModal = null;
|
|
2589
3690
|
/** Handle backend fulfillment notifications */
|
|
2590
3691
|
this.handleGroupFulfilledEvent = (event) => {
|
|
2591
3692
|
const detail = event.detail;
|
|
@@ -2742,11 +3843,22 @@ class WidgetRoot {
|
|
|
2742
3843
|
}
|
|
2743
3844
|
/** Persist fulfilled state, emit callback, and refresh UI */
|
|
2744
3845
|
processGroupFulfilled(eventData) {
|
|
2745
|
-
var _a, _b;
|
|
3846
|
+
var _a, _b, _c, _d;
|
|
2746
3847
|
this.groupFulfilled = true;
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
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);
|
|
2750
3862
|
this.renderFulfilledState();
|
|
2751
3863
|
}
|
|
2752
3864
|
/** Re-render widget and external containers to reflect fulfillment */
|
|
@@ -2861,16 +3973,42 @@ class WidgetRoot {
|
|
|
2861
3973
|
rewardCheck.textContent = "✔";
|
|
2862
3974
|
rewardCheck.style.color = "var(--cobuy-success-color, #10b981)";
|
|
2863
3975
|
const rewardLabel = document.createElement("span");
|
|
2864
|
-
rewardLabel.textContent = rewardText
|
|
3976
|
+
rewardLabel.textContent = rewardText
|
|
3977
|
+
? `Discount available: ${rewardText}`
|
|
3978
|
+
: "Discount available";
|
|
2865
3979
|
rewardLine.appendChild(rewardCheck);
|
|
2866
3980
|
rewardLine.appendChild(rewardLabel);
|
|
2867
3981
|
root.appendChild(title);
|
|
2868
3982
|
root.appendChild(rewardLine);
|
|
2869
|
-
// Create footer with
|
|
3983
|
+
// Create footer with action CTAs
|
|
2870
3984
|
const footer = document.createElement("div");
|
|
2871
3985
|
footer.style.display = "flex";
|
|
2872
3986
|
footer.style.justifyContent = "flex-end";
|
|
2873
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
|
+
}
|
|
2874
4012
|
root.appendChild(footer);
|
|
2875
4013
|
return root;
|
|
2876
4014
|
}
|
|
@@ -3054,6 +4192,12 @@ class WidgetRoot {
|
|
|
3054
4192
|
this.currentGroupData = groupData;
|
|
3055
4193
|
this.currentGroupId = (groupData === null || groupData === void 0 ? void 0 : groupData.id) || null;
|
|
3056
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
|
+
}
|
|
3057
4201
|
// Check if group is already fulfilled (full) on initial load
|
|
3058
4202
|
if (groupData) {
|
|
3059
4203
|
const participants = Number(groupData.participants_count || 0);
|
|
@@ -3422,6 +4566,7 @@ class WidgetRoot {
|
|
|
3422
4566
|
createWidget(rewardData, container, options) {
|
|
3423
4567
|
const isFulfilled = this.groupFulfilled;
|
|
3424
4568
|
const activeReward = this.frozenReward || (rewardData === null || rewardData === void 0 ? void 0 : rewardData.reward) || null;
|
|
4569
|
+
this.logger.info(`activeReward: ${JSON.stringify(activeReward)}`);
|
|
3425
4570
|
const wrapper = document.createElement("div");
|
|
3426
4571
|
wrapper.className = "cobuy-widget";
|
|
3427
4572
|
wrapper.style.display = "grid";
|
|
@@ -3461,28 +4606,29 @@ class WidgetRoot {
|
|
|
3461
4606
|
rewardLine.style.opacity = "0";
|
|
3462
4607
|
rewardLine.style.animation =
|
|
3463
4608
|
"cobuy-fadeIn var(--cobuy-animation-duration, 300ms) var(--cobuy-animation-easing, ease-out) forwards";
|
|
3464
|
-
if (isFulfilled) {
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
}
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
}
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
4609
|
+
// if (isFulfilled) {
|
|
4610
|
+
// const rewardText = this.formatRewardText(activeReward);
|
|
4611
|
+
// rewardLine.textContent = rewardText
|
|
4612
|
+
// ? `Reward available: ${rewardText}`
|
|
4613
|
+
// : "Reward available for this group";
|
|
4614
|
+
// rewardLine.setAttribute(
|
|
4615
|
+
// "aria-label",
|
|
4616
|
+
// rewardText ? `Reward locked in: ${rewardText}` : "Reward available for this group",
|
|
4617
|
+
// );
|
|
4618
|
+
// rewardLine.title = rewardLine.textContent;
|
|
4619
|
+
// } else if (activeReward) {
|
|
4620
|
+
// const rewardText = this.formatRewardText(activeReward);
|
|
4621
|
+
// rewardLine.textContent = rewardText
|
|
4622
|
+
// ? `Save up to ${rewardText} with CoBuy`
|
|
4623
|
+
// : "CoBuy reward available";
|
|
4624
|
+
// rewardLine.setAttribute("aria-label", `Eligible for CoBuy reward: ${rewardLine.textContent}`);
|
|
4625
|
+
// rewardLine.title = rewardLine.textContent;
|
|
4626
|
+
// } else {
|
|
4627
|
+
// rewardLine.textContent = "CoBuy offer loading or unavailable";
|
|
4628
|
+
// rewardLine.setAttribute("aria-label", "CoBuy offer loading or unavailable");
|
|
4629
|
+
// rewardLine.title = "CoBuy offer loading or unavailable";
|
|
4630
|
+
// rewardLine.style.color = "#6b7280";
|
|
4631
|
+
// }
|
|
3486
4632
|
sections.reward = rewardLine;
|
|
3487
4633
|
// Button - semantic button element with accessibility
|
|
3488
4634
|
const button = document.createElement("button");
|
|
@@ -3713,6 +4859,12 @@ class WidgetRoot {
|
|
|
3713
4859
|
}
|
|
3714
4860
|
groupJoinData = joinResponse.data;
|
|
3715
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;
|
|
3716
4868
|
// Trigger invite tracking before opening lobby (global for product)
|
|
3717
4869
|
try {
|
|
3718
4870
|
const inviteResponse = await this.apiClient.inviteToGroup(this.currentGroupId, "copy_link");
|
|
@@ -3758,6 +4910,13 @@ class WidgetRoot {
|
|
|
3758
4910
|
const isGroupFulfilled = groupJoinData.group.participants_count >= groupJoinData.group.max_participants;
|
|
3759
4911
|
const groupLinkFromInvite = inviteData === null || inviteData === void 0 ? void 0 : inviteData.invite_url;
|
|
3760
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;
|
|
3761
4920
|
// Prepare modal data with real values from join response
|
|
3762
4921
|
cobuySDK.openModal({
|
|
3763
4922
|
productId,
|
|
@@ -3776,6 +4935,7 @@ class WidgetRoot {
|
|
|
3776
4935
|
groupLink: groupLinkFromInvite || `https://cobuy.group/lobby/${groupJoinData.group.id}`,
|
|
3777
4936
|
shareMessage: shareMessageFromInvite,
|
|
3778
4937
|
isLocked: !isGroupFulfilled,
|
|
4938
|
+
offlineRedemption: offlineRedemptionFromJoin,
|
|
3779
4939
|
activities: [
|
|
3780
4940
|
{
|
|
3781
4941
|
emoji: "👤",
|
|
@@ -3825,6 +4985,20 @@ class WidgetRoot {
|
|
|
3825
4985
|
}
|
|
3826
4986
|
}
|
|
3827
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
|
+
}
|
|
3828
5002
|
/**
|
|
3829
5003
|
* Emit checkout intent without performing navigation
|
|
3830
5004
|
*/
|
|
@@ -3883,13 +5057,13 @@ class WidgetRoot {
|
|
|
3883
5057
|
const reward = this.frozenReward || (rewardData === null || rewardData === void 0 ? void 0 : rewardData.reward);
|
|
3884
5058
|
if (this.groupFulfilled) {
|
|
3885
5059
|
const rewardText = this.formatRewardText(reward);
|
|
3886
|
-
return rewardText ? `
|
|
5060
|
+
return rewardText ? `Discount available: ${rewardText}` : "Discount available for this group";
|
|
3887
5061
|
}
|
|
3888
5062
|
if (!reward) {
|
|
3889
|
-
return "Join with CoBuy to unlock
|
|
5063
|
+
return "Join with CoBuy to unlock discount";
|
|
3890
5064
|
}
|
|
3891
5065
|
const rewardText = this.formatRewardText(reward);
|
|
3892
|
-
return rewardText ? `Save up to ${rewardText} with CoBuy` : "CoBuy
|
|
5066
|
+
return rewardText ? `Save up to ${rewardText} with CoBuy` : "CoBuy discount available";
|
|
3893
5067
|
}
|
|
3894
5068
|
/**
|
|
3895
5069
|
* Public hook to request a realtime refresh from external callers
|
|
@@ -4487,7 +5661,7 @@ class AnalyticsClient {
|
|
|
4487
5661
|
*/
|
|
4488
5662
|
async trackCtaClick(productId) {
|
|
4489
5663
|
const event = {
|
|
4490
|
-
event: "
|
|
5664
|
+
event: "CTA_CLICKED",
|
|
4491
5665
|
productId,
|
|
4492
5666
|
timestamp: new Date().toISOString(),
|
|
4493
5667
|
sessionId: this.sessionId,
|
|
@@ -4531,6 +5705,28 @@ class AnalyticsClient {
|
|
|
4531
5705
|
throw error;
|
|
4532
5706
|
}
|
|
4533
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
|
+
}
|
|
4534
5730
|
/**
|
|
4535
5731
|
* Track custom event (extensible for future use)
|
|
4536
5732
|
*/
|
|
@@ -8718,9 +9914,10 @@ Object.assign(lookup, {
|
|
|
8718
9914
|
* Manages a single socket.io connection for the SDK
|
|
8719
9915
|
*/
|
|
8720
9916
|
class SocketManager {
|
|
8721
|
-
constructor(config, sdkVersion) {
|
|
9917
|
+
constructor(config, sdkVersion, sessionId) {
|
|
8722
9918
|
this.config = config;
|
|
8723
9919
|
this.sdkVersion = sdkVersion;
|
|
9920
|
+
this.sessionId = sessionId;
|
|
8724
9921
|
this.socket = null;
|
|
8725
9922
|
this.logger = new Logger(config.debug);
|
|
8726
9923
|
}
|
|
@@ -8765,6 +9962,7 @@ class SocketManager {
|
|
|
8765
9962
|
auth: {
|
|
8766
9963
|
merchantKey: this.config.merchantKey,
|
|
8767
9964
|
sdkVersion: this.sdkVersion,
|
|
9965
|
+
sessionId: this.sessionId,
|
|
8768
9966
|
},
|
|
8769
9967
|
autoConnect: true,
|
|
8770
9968
|
});
|
|
@@ -8951,7 +10149,9 @@ class CoBuy {
|
|
|
8951
10149
|
const ref = window.localStorage.getItem(key);
|
|
8952
10150
|
if (ref) {
|
|
8953
10151
|
this.logger.debug(`[SDK] Retrieved checkout reference via prefix: ${key}`);
|
|
8954
|
-
const parsedGroupId = key.startsWith(`${basePrefix}_`)
|
|
10152
|
+
const parsedGroupId = key.startsWith(`${basePrefix}_`)
|
|
10153
|
+
? key.substring(basePrefix.length + 1)
|
|
10154
|
+
: null;
|
|
8955
10155
|
return { key, checkoutRef: ref, groupId: parsedGroupId };
|
|
8956
10156
|
}
|
|
8957
10157
|
}
|
|
@@ -9041,7 +10241,7 @@ class CoBuy {
|
|
|
9041
10241
|
// Initialize real-time socket connection for public mode
|
|
9042
10242
|
if (config.authMode === "public" && config.merchantKey) {
|
|
9043
10243
|
try {
|
|
9044
|
-
this.socketManager = new SocketManager(config, SDK_VERSION);
|
|
10244
|
+
this.socketManager = new SocketManager(config, SDK_VERSION, this.sessionId);
|
|
9045
10245
|
this.socketManager.connect();
|
|
9046
10246
|
this.socketManager.bindHandlers({
|
|
9047
10247
|
onGroupFulfilled: (payload) => {
|
|
@@ -9083,6 +10283,13 @@ class CoBuy {
|
|
|
9083
10283
|
this.logger.warn("[SDK] Failed to initialize sockets", e);
|
|
9084
10284
|
}
|
|
9085
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
|
+
}
|
|
9086
10293
|
this.logger.info("SDK initialization complete");
|
|
9087
10294
|
}
|
|
9088
10295
|
catch (error) {
|
|
@@ -9162,6 +10369,7 @@ class CoBuy {
|
|
|
9162
10369
|
groupLink: options.groupLink,
|
|
9163
10370
|
activities: options.activities,
|
|
9164
10371
|
isLocked: options.isLocked,
|
|
10372
|
+
offlineRedemption: options.offlineRedemption,
|
|
9165
10373
|
};
|
|
9166
10374
|
// Create modal instance
|
|
9167
10375
|
const modal = new LobbyModal(modalData, {
|