1(function() { 2 // Initialize state 3 if (typeof window.voyadoStarted === 'undefined') { 4 window.voyadoStarted = false; 5 } 6 7 /** 8 * Sets the voyado-analytics consent cookie 9 * Cookie expires in 1 year (31536000 seconds) 10 */ 11 function setVoyadoConsentCookie() { 12 try { 13 // Get current domain for cookie 14 var domain = window.location.hostname; 15 document.cookie = "voyado-analytics=1; path=/; max-age=31536000; SameSite=Lax"; 16 17 // Verify cookie was actually set 18 var cookieSet = document.cookie.indexOf('voyado-analytics=1') !== -1; 19 20 if (!cookieSet) { 21 console.warn("Voyado: Cookie could not be set. Check browser cookie settings."); 22 } 23 } catch (e) { 24 console.error("Voyado: Failed to set cookie:", e); 25 } 26 } 27 28 /** 29 * Clears the voyado-analytics consent cookie 30 */ 31 function clearVoyadoConsentCookie() { 32 document.cookie = "voyado-analytics=; path=/; max-age=0; SameSite=Lax"; 33 } 34 35 /** 36 * Gets cookie value (early version for use before DOM ready) 37 */ 38 function getCookieValueEarly(cookieName) { 39 var name = cookieName + "="; 40 var decodedCookie = decodeURIComponent(document.cookie); 41 var cookieArray = decodedCookie.split(';'); 42 for (var i = 0; i < cookieArray.length; i++) { 43 var cookie = cookieArray[i].trim(); 44 if (cookie.indexOf(name) === 0) { 45 return cookie.substring(name.length, cookie.length); 46 } 47 } 48 return null; 49 } 50 51 /** 52 * Initializes Voyado tracking (only once) 53 * Dispatches 'voyadoReady' event when tracking is started 54 */ 55 function startVoyado() { 56 if (window.voyadoStarted) { 57 return; 58 } 59 window.voyadoStarted = true; 60 61 // Dispatch ready event for any listeners 62 window.dispatchEvent(new CustomEvent('voyadoReady')); 63 64 // Also call initVoyadoTracking directly if DOM is already loaded 65 if (document.readyState === 'complete' || document.readyState === 'interactive') { 66 if (typeof initVoyadoTracking === 'function') { 67 setTimeout(initVoyadoTracking, 0); 68 } 69 } 70 } 71 72 /** 73 * Stops Voyado tracking and clears consent 74 * Dispatches 'voyadoStopped' event when tracking is stopped 75 */ 76 function stopVoyado() { 77 clearVoyadoConsentCookie(); 78 window.voyadoStarted = false; 79 window.voyadoTrackingInitialized = false; 80 window.dispatchEvent(new CustomEvent('voyadoStopped')); 81 } 82 83 // Listen for consent granted event from CMP or custom integration 84 // Usage: window.dispatchEvent(new CustomEvent('voyadoConsentGranted')); 85 window.addEventListener('voyadoConsentGranted', function() { 86 console.log("Voyado: voyadoConsentGranted event received"); 87 setVoyadoConsentCookie(); 88 startVoyado(); 89 }); 90 91 // Listen for consent revoked event from CMP or custom integration 92 // Usage: window.dispatchEvent(new CustomEvent('voyadoConsentRevoked')); 93 window.addEventListener('voyadoConsentRevoked', function() { 94 stopVoyado(); 95 }); 96 97 // Check for existing consent at script load 98 var existingCookie = getCookieValueEarly('voyado-analytics'); 99 100 // Auto-start if consent cookie already exists (returning users or Shopware cookie banner) 101 if (existingCookie === '1') { 102 startVoyado(); 103 } 104 105 // Handle late consent: check if flag was set before script loaded 106 // Useful for CMPs that grant consent before Voyado script is loaded 107 if (window.voyadoConsentAlreadyGranted === true) { 108 setVoyadoConsentCookie(); 109 startVoyado(); 110 } 111 // Expose helper for manual testing 112 window.voyadoGrantConsent = function() { 113 window.dispatchEvent(new CustomEvent('voyadoConsentGranted')); 114 }; 115 window.voyadoRevokeConsent = function() { 116 window.dispatchEvent(new CustomEvent('voyadoConsentRevoked')); 117 }; 118})(); 119 120/** 121 * Helper function to check if tracking consent is currently active 122 * This dynamically checks the cookie value each time it's called 123 */ 124function hasVoyadoConsent() { 125 // Check voyadoStarted flag first (set by consent mechanism) 126 if (window.voyadoStarted === true) { 127 return true; 128 } 129 // Also check cookie as fallback 130 return getCookieValue('voyado-analytics') === '1'; 131} 132 133// Set up voyadoReady listener BEFORE DOMContentLoaded to catch early events 134window.addEventListener('voyadoReady', function() { 135 initVoyadoTracking(); 136}); 137document.addEventListener("DOMContentLoaded", function() { 138 initVoyadoTracking(); 139}); 140 141function initVoyadoTracking() { 142 // Skip if already initialized 143 if (window.voyadoTrackingInitialized) { 144 return; 145 } 146 147 // Skip if no consent yet (will be called again when voyadoReady fires) 148 if (!hasVoyadoConsent()) { 149 return; 150 }
151 let currentUrl = window.location.href; 152 let originUrl = window.location.origin; 153 const urlParams = new URLSearchParams(window.location.search); 154 const eclub = urlParams.get("eclub"); 155 if (currentUrl.includes('?') && eclub === null && currentUrl.includes('orderId=')) { 156 // Remove everything after and including the '?' 157 currentUrl = currentUrl.split('?')[0]; 158 } 159 const voyadoAnalytics = hasVoyadoConsent() ? '1' : getCookieValue('voyado-analytics'); 160 const apiUrl = "https://accelerator.voyado.com"; 161 const voyadoCid = localStorage.getItem('voyado-cid'); 162 const voyadoTrackingElement = document.getElementById("voyado-tracking-data"); 163 // Script to remove session ID and email from storage 164 const loginPageElement = document.getElementById("login-page-url"); 165 if (loginPageElement) { 166 const loginPageUrl = loginPageElement.getAttribute("data-login-page-url"); 167 if (typeof loginPageUrl !== 'undefined') { 168 if(currentUrl === loginPageUrl) 169 { 170 if (typeof sessionStorage !== 'undefined') { 171 // Check if voyado-session-id exists in sessionStorage before removing it 172 if (sessionStorage.getItem('voyado-session-id')) { 173 sessionStorage.removeItem('voyado-session-id'); 174 } 175 } 176 } 177 } 178 } 179 180 // Script to Store Voyado ContactId 181 if(voyadoAnalytics === '1') 182 { 183 const customerDataElement = document.getElementById("voyado-customer-data"); 184 if (customerDataElement) { 185 const accountPageUrl = customerDataElement.getAttribute("data-account-page-url"); 186 if (typeof accountPageUrl !== 'undefined') { 187 if(currentUrl === accountPageUrl) 188 { 189 const customerId = customerDataElement.getAttribute("data-customer-id"); 190 const customerEmail = customerDataElement.getAttribute("data-customer-email"); 191 const customerData = { 192 customerId: customerDataElement.getAttribute("data-customer-id"), 193 customerEmail: customerDataElement.getAttribute("data-customer-email"), 194 shopId: customerDataElement.getAttribute("data-shop-id"), 195 saleschannelId: customerDataElement.getAttribute("data-saleschannel-id") 196 }; 197 if (customerId) { 198 fetch(apiUrl + "/api/v1/Shopware/store-contactid", { 199 method: 'POST', 200 headers: { 201 'Content-Type': 'application/json' 202 }, 203 body: JSON.stringify(customerData) 204 }) 205 .then(response => { 206 return response.text(); // Get the response as text first 207 }) 208 .then(text => { 209 try { 210 const voyadoContactID = text; 211 localStorage.setItem('voyado-cid', voyadoContactID); 212 } catch (error) { 213 // Handle JSON parsing error 214 console.error('Error parsing JSON:', error); 215 } 216 }) 217 .catch(error => { 218 console.log('Error sending data to webhook:', error); 219 }); 220 221 } else { 222 // If customerId doesn't exist, remove voyadoContactID from localStorage & sessionStorage 223 localStorage.removeItem('voyado-cid'); 224 } 225 } 226 } 227 } 228 } 229 // Script to handle the Product Tracking Functionality and Back in Stock Functionality 230 productPageElement = document.getElementById("voyado-product-tracking-buy-widget-form"); 231 if (productPageElement) { 232 const productPageUrl = productPageElement.getAttribute("data-product-page-url"); 233 if (typeof productPageUrl !== 'undefined') { 234 const productAvailableStock = productPageElement.getAttribute("data-product-available-stock"); 235 if(productAvailableStock != 1) 236 { 237 // Script to handle the PO Editor Data 238 const shopPrivacyPolicyLink = { 239 shopId: productPageElement.getAttribute("data-app-shop-id"), 240 saleschannelId: productPageElement.getAttribute("data-saleschannel-id"), 241 languageId: productPageElement.getAttribute("data-language-id"), 242 shopUrl: originUrl 243 }; 244 fetch(apiUrl + "/api/v2/shopware/get-privacy-policy", { 245 method: 'POST', 246 headers: { 247 'Content-Type': 'application/json' 248 }, 249 body: JSON.stringify(shopPrivacyPolicyLink) // Send the JSON data 250 }) 251 .then(response => { 252 if (!response.ok) { 253 throw new Error('Network response was not ok'); 254 } 255 return response.json(); 256 }) 257 .then(data => { 258 if (data) { 259 if(data.privacyPolicy) 260 { 261 let privacyPolicyLink = document.querySelector('.voyado-privacy-policy-link'); // Select the <a> tag 262 privacyPolicyLink.href = data.privacyPolicy; // Set the href with the received URL 263 privacyPolicyLink.target = "_blank"; // Open link in a new tab 264 privacyPolicyLink.rel = "noopener noreferrer"; 265 } 266 if(data.resourceData) 267 { 268 let notifyTitle = document.querySelector("#voyadoNotifyCustomerForm h4"); 269 if (notifyTitle) { 270 notifyTitle.textContent = data.resourceData.BackInStockWidgetTitle || "Notify me when available"; 271 } 272 273 // Update Email Label 274 let emailLabel = document.querySelector("#voyadoNotifyCustomerForm label[for='email']"); 275 if (emailLabel) { 276 emailLabel.textContent = (data.resourceData.BackInStockWidgetEmail || "Email") + ":"; 277 } 278 279 // Update Checkbox Label 280 let emailSubscriptionLabel = document.querySelector("#voyadoNotifyCustomerForm label[for='agree_terms']"); 281 if (emailSubscriptionLabel) { 282 emailSubscriptionLabel.textContent = data.resourceData.BackInStockWidgetAcceptsEmail || "Email me with news and offer"; 283 } 284 285 // Update Submit Button Text 286 let submitButton = document.querySelector("#voyadoNotifyCustomerForm button[type='submit']"); 287 if (submitButton) { 288 submitButton.textContent = data.resourceData.BackInStockWidgetButton || "Notify Me"; 289 } 290 291 let privacyPolicy = document.querySelector("#voyadoNotifyCustomerForm a"); 292 if (privacyPolicy) { 293 privacyPolicy.textContent = data.resourceData.BackInStockWidgetPrivacyLink || "Privacy Policy"; 294 } 295 296 let voyadoResponseMessage = document.querySelector("#voyadoResponseMessage"); 297 if (voyadoResponseMessage) { 298 voyadoResponseMessage.textContent = data.resourceData.BackInStockWidgetResponse || "Thank you! We will notify you when the product is available."; 299 } 300 } 301 } 302 }) 303 .catch(() => {}); 304 const voyadoNotifyCustomerForm = document.getElementById("voyadoNotifyCustomerForm"); 305 const voyadoResponseMessage = document.getElementById("voyadoResponseMessage"); 306 voyadoNotifyCustomerForm.addEventListener("submit", function(event) { 307 event.preventDefault(); // Prevent default form submission 308 const formData = new FormData(voyadoNotifyCustomerForm); // Create a FormData object from the form 309 const formObject = {}; // Initialize an empty object 310 // Convert FormData to a plain JavaScript object 311 formData.forEach(function(value, key) {
312 formObject[key] = value; 313 }); 314 // Add origin URL 315 formObject.shopUrl = originUrl; 316 // Convert the plain object to a JSON string 317 const jsonData = JSON.stringify(formObject); 318 // Send the form data via AJAX 319 fetch(apiUrl + "/api/v2/shopware/back-in-stock/subscribe", { 320 method: 'POST', 321 headers: { 322 'Content-Type': 'application/json' 323 }, 324 body: jsonData // Send the JSON data 325 }) 326 .then(response => { 327 if (!response.ok) { 328 throw new Error('Network response was not ok'); 329 } 330 return response.text(); 331 }) 332 .then(data => { 333 voyadoResponseMessage.innerText = data; 334 document.querySelector('.voyadoResponseMessage').style.display = 'block'; 335 document.querySelector('.notify-customer').style.display = 'none'; 336 }) 337 .catch(error => { 338 console.error('Error submitting form:', error); 339 }); 340 }); 341 } 342 if(voyadoAnalytics === '1' && voyadoCid) 343 { 344 let voyadoSessionId = sessionStorage.getItem('voyado-session-id'); 345 let newSession; 346 347 if (!voyadoSessionId) { 348 // Generate a new session ID if none exists 349 voyadoSessionId = generateSessionID(); 350 sessionStorage.setItem('voyado-session-id', voyadoSessionId); 351 newSession = true; // This is a new session 352 } else { 353 // Session ID exists, no need to generate a new one 354 newSession = false; 355 } 356 const productData = { 357 customerId: voyadoCid, 358 id: productPageElement.getAttribute("data-product-id"), 359 productNumber: productPageElement.getAttribute("data-product-number"), 360 url: productPageElement.getAttribute("data-product-page-url"), 361 time: getCurrentDateTime(), 362 language_id: productPageElement.getAttribute("data-language-id"), 363 saleschannelId: productPageElement.getAttribute("data-saleschannel-id"), 364 shopId : productPageElement.getAttribute("data-app-shop-id"), 365 sessionId: voyadoSessionId, 366 newSession: newSession, 367 shopUrl: originUrl 368 }; 369 // Send product data to webhook endpoint via fetch API 370 fetch(apiUrl + "/api/v2/shopware/product-tracking", { 371 method: 'POST', 372 headers: { 373 'Content-Type': 'application/json' 374 }, 375 body: JSON.stringify(productData) 376 }) 377 .then(response => { 378 if (!response.ok) { 379 throw new Error('Network response was not ok'); 380 } 381 return response.text(); 382 }) 383 .catch(error => { 384 console.log('Error sending data to webhook:'); 385 }); 386 } 387 } 388 } 389 if(voyadoAnalytics === '1' && voyadoCid) 390 { 391 // Script to handle the Cart Tracking Functionality When click on add to cart button and display promotions 392 const buyButtons = document.querySelectorAll('.btn-buy'); 393 // Add click event listener to each button 394 if(buyButtons) 395 { 396 buyButtons.forEach(function (button) { 397 button.addEventListener('click', function () { 398 // Delay execution by 10 seconds 399 setTimeout(function() { 400 const cartElement = document.getElementById('voyad
400o-cart-tracking'); 401 let productDataArray; 402 if (cartElement) { 403 const cartData = JSON.parse(cartElement.getAttribute('data-cart')); 404 productDataArray = extractCartData(cartData); 405 } 406 else 407 { 408 let cartTrackingData; 409 const productPageUrl = productPageElement.getAttribute("data-product-page-url"); 410 if (typeof productPageUrl !== 'undefined') { 411 if(currentUrl === productPageUrl) 412 { 413 const quantityInput = document.querySelector('.quantity-selector-group-input'); 414 415 if (quantityInput) { 416 cartTrackingData = { 417 productId: productPageElement.getAttribute("data-product-id"), 418 itemNumber: productPageElement.getAttribute("data-product-number"), 419 quantity: quantityInput.value 420 }; 421 } 422 } 423 } 424 else 425 { 426 const cartTrackingElement = document.getElementById("voyado-product-data"); 427 428 if (cartTrackingElement) { 429 cartTrackingData = { 430 productId: cartTrackingElement.getAttribute("data-product-id"), 431 itemNumber: cartTrackingElement.getAttribute("data-item-number"), 432 quantity: cartTrackingElement.getAttribute("data-quantity") 433 }; 434 } 435 } 436 productDataArray = cartTrackingData; 437 } 438 const previousCartData = JSON.parse(localStorage.getItem('previousCartData')) || []; 439 if (!productDataArray || productDataArray.length === 0) { 440 // Cart is empty, remove previous cart data from local storage 441 localStorage.removeItem('previousCartData'); 442 return; 443 } 444 445 // Check if the cart has changed 446 if (hasCartChanged(previousCartData, productDataArray)) { 447 const cartId = generateCartId(32); 448 const cartNewData = { 449 customerId: voyadoCid, 450 cartId: generateCartId(32), // Using the random cart ID 451 productdata: productDataArray, 452 url: currentUrl, 453 time: getCurrentDateTime(), 454 language: voyadoTrackingElement.getAttribute("data-language"), 455 shopId: voyadoTrackingElement.getAttribute("data-app-shop-id"), 456 saleschannelId: voyadoTrackingElement.getAttribute("data-saleschannel-id"), 457 shopUrl: originUrl 458 }; 459 // First fetch request (cart tracking) 460 fetch(apiUrl + "/api/v2/shopware/cart-tracking", { 461 method: 'POST', 462 headers: { 'Content-Type': 'application/json' }, 463 body: JSON.stringify(cartNewData) 464 }).then(response => { 465 if (!response.ok) { 466 throw new Error('Network response was not ok'); 467 } 468 return response.text(); 469 }).catch(error => {
470 console.error('Error sending cart data to webhook:', error); 471 }); 472 473 // Update previous cart data in local storage 474 localStorage.setItem('previousCartData', JSON.stringify(productDataArray)); 475 localStorage.setItem('voyado-cartid', cartId); 476 } 477 }, 2000); // 10-second delay 478 // First check if promotions should be shown 479 const promotionCheckData = { 480 shopId: voyadoTrackingElement.getAttribute("data-app-shop-id"), 481 saleschannelId: voyadoTrackingElement.getAttribute("data-saleschannel-id"), 482 shopUrl: originUrl 483 }; 484 fetch(apiUrl + "/api/v1/shopware/check-promotion-widget", { 485 method: 'POST', 486 headers: { 487 'Content-Type': 'application/json' 488 }, 489 body: JSON.stringify(promotionCheckData) 490 }).then(response => { 491 if (!response.ok) { 492 throw new Error('Network response was not ok'); 493 } 494 return response.json(); 495 }).then(data => { 496 const promotionContainer = document.querySelector('.voyado-promotion'); 497 if (data.showPromotionToggle && promotionContainer) { 498 // Script to Display Promotions in minicart 499 const promotionData = { 500 voyadoCid: voyadoCid, 501 shopId: voyadoTrackingElement.getAttribute("data-app-shop-id"), 502 saleschannelId: voyadoTrackingElement.getAttribute("data-saleschannel-id"), 503 shopUrl: originUrl 504 }; 505 // Fetch and display promotions 506 fetch(apiUrl + "/api/v2/shopware/get-promotions", { 507 method: 'POST', 508 headers: { 509 'Content-Type': 'application/json' 510 }, 511 body: JSON.stringify(promotionData) 512 }).then(response => { 513 if (!response.ok) { 514 throw new Error('Network response was not ok'); 515 } 516 return response.text(); 517 }).then(text => { 518 const promotionresponse = JSON.parse(text); 519 // Scenario 1: Widget enabled + active offers -> show widget 520 if (promotionresponse && promotionresponse.length > 0) { 521 promotionContainer.style.display = 'block'; 522 displayPromotionData(promotionresponse); 523 } else { 524 // Scenario 3: Widget enabled but no offers -> remove completely 525 promotionContainer.remove(); 526 } 527 }).catch(error => { 528 console.log('Error fetching promotions:', error); 529 promotionContainer.remove(); 530 }); 531 } else if (promotionContainer) { 532 // Scenario 2: Widget disabled -> remove completely 533 promotionContainer.remove(); 534 } 535 }).catch(error => { 536 console.log('Error checking promotions enabled:', error); 537 const promotionContainer = document.querySelector('.voyado-promotion'); 538 if (promotionContainer) promotionContainer.remove(); 539 }); 540 }); 541 }); 542 } 543 // Script to handle the Cart Tracking Functionality on CheckoutConfirmPage 544 const checkoutconfirmPageElement = document.getElementById("voyado-checkout-confirm-page-data"); 545 if (checkoutconfirmPageElement) { 546 const checkoutConfirmPageUrl = checkoutconfirmPageElement.getAttribute("data-checkout-confirm-page-url"); 547 if (typeof checkoutConfirmPageUrl !== 'undefined') { 548 if(currentUrl === checkoutConfirmPageUrl) 549 { 550 const previousCartData = JSON.parse(localStorage.getItem('previousCartData')) || []; 551 const productDataRaw = checkoutconfirmPageElement.getAttribute("data-product-data"); 552 const productDataArray = productDataRaw ? JSON.parse(productDataRaw) : []; 553 if (!productDataArray || productDataArray.length === 0) { 554 // Cart is empty, remove previous cart data from local storage 555 localStorage.removeItem('previousCartData'); 556 return; 557 } 558 if (hasCartChanged(previousCartData, productDataArray)) { 559 const cartId = localStorage.getItem('voyado-cartid'); 560 if (!cartId) { 561 const cartId = generateCartId(32); 562 localStorage.setItem('voyado-cartid', cartId); 563 } 564 const cartTrackingData = { 565 customerId: voyadoCid, 566 cartId: cartId, 567 productdata: JSON.parse(checkoutconfirmPageElement.getAttribute("data-product-data")), 568 url: checkoutconfirmPageElement.getAttribute("data-checkout-confirm-page-url"), 569 time: checkoutconfirmPageElement.getAttribute("data-time"), 570 language: checkoutconfirmPageElement.getAttribute("data-language"), 571 saleschannelId: checkoutconfirmPageElement.getAttribute("data-saleschannel-id"), 572 shopId: checkoutconfirmPageElement.getAttribute("data-shop-id"), 573 shopUrl: originUrl 574 }; 575 fetch(apiUrl + "/api/v2/shopware/cart-tracking", { 576 method: 'POST', 577 headers: { 'Content-Type': 'application/json' }, 578 body: JSON.stringify(cartTrackingData) 579 }).then(response => { 580 if (!response.ok) { 581 throw new Error('Network response was not ok'); 582 } 583 return response.text(); 584 }).catch(error => {
585 console.error('Error sending cart data to webhook:', error); 586 }); 587 588 // Update previous cart data in local storage 589 localStorage.setItem('previousCartData', JSON.stringify(productDataArray)); 590 } 591 } 592 } 593 } 594 // Script to handle the Cart Tracking Functionality on CheckoutFinishPage 595 const checkoutfinishPageElement = document.getElementById("voyado-checkout-finish-page-data"); 596 if (checkoutfinishPageElement) { 597 const checkoutFinishPageUrl = checkoutfinishPageElement.getAttribute("data-checkout-finish-page-url"); 598 if (typeof checkoutFinishPageUrl !== 'undefined') { 599 if(currentUrl === checkoutFinishPageUrl) 600 { 601 const previousCartData = JSON.parse(localStorage.getItem('previousCartData')) || []; 602 const productDataArray = checkoutfinishPageElement.getAttribute("data-product-data"); 603 if (productDataArray.length > 0 && previousCartData.length > 0) { 604 if (hasCartChangedFinishPage(previousCartData, productDataArray)) { 605 const cartTrackingData = { 606 customerId: voyadoCid, 607 cartId: localStorage.getItem('voyado-cartid'), 608 productdata: [], 609 url: checkoutfinishPageElement.getAttribute("data-checkout-finish-page-url"), 610 time: checkoutfinishPageElement.getAttribute("data-time"), 611 language: checkoutfinishPageElement.getAttribute("data-language"), 612 saleschannelId: checkoutfinishPageElement.getAttribute("data-saleschannel-id"), 613 shopId: checkoutfinishPageElement.getAttribute("data-shop-id"), 614 shopUrl: originUrl 615 }; 616 // First fetch request (cart tracking) 617 fetch(apiUrl + "/api/v2/shopware/cart-tracking", { 618 method: 'POST', 619 headers: { 'Content-Type': 'application/json' }, 620 body: JSON.stringify(cartTrackingData) 621 }).then(response => { 622 if (!response.ok) { 623 throw new Error('Network response was not ok'); 624 } 625 return response.text(); 626 }).catch(error => { 627 console.error('Error sending cart data to webhook:', error); 628 }); 629 630 localStorage.removeItem('previousCartData'); 631 localStorage.removeItem('voyado-cartid'); 632 633 } 634 } 635 } 636 } 637 } 638 } 639 // Script to handle the Cart Tracking Functionality on CheckoutCartPage and Display Promotions 640 const checkoutcartPageElement = document.getElementById("voyado-cart-page-data"); 641 if (checkoutcartPageElement) { 642 const checkoutCartPageUrl = checkoutcartPageElement.getAttribute("data-checkout-cart-page-url"); 643 if (typeof checkoutCartPageUrl !== 'undefined') { 644 if(currentUrl === checkoutCartPageUrl) 645 { 646 if(voyadoCid) 647 { 648 const promotionItems = JSON.parse(checkoutcartPageElement.getAttribute("data-promotion-items")); 649 if (promotionItems.length > 0) { 650 const tagContainer = document.querySelector('.voyado-promotion-tags'); 651 652 // Clear existing tags 653 tagContainer.innerHTML = ''; 654 655 // Loop through promotions and display them 656 promotionItems.forEach((promotion) => { 657 const promotionCode = promotion.referencedId; 658 659 // Create a tag element 660 const tag = document.createElement('div'); 661 tag.className = 'voyado-promotion-tag'; 662 tag.innerHTML = `
663 <span>${promotionCode}</span> 664 <button class="voyado-remove-tag" data-id="${promotion.id}" title="voyado-remove-tag">X</button> 665 `; 666 tagContainer.appendChild(tag); 667 }); 668 document.querySelectorAll('.voyado-remove-tag').forEach(button => { 669 button.addEventListener('click', function() { 670 const lineItemId = this.getAttribute('data-id'); 671 // Generate the delete URL dynamically 672 const deleteUrl = "{{ path('frontend.checkout.line-item.delete', {id: '__LINE_ITEM_ID__'}) }}".replace('__LINE_ITEM_ID__', lineItemId); 673 // Dynamically find the form using the generated delete path 674 const formSelector = `form[action^="${deleteUrl.split('?')[0]}"]`; 675 const form = document.querySelector(formSelector); 676 677 if (!form) { 678 console.error('Remove promotion form not found.'); 679 return; 680 } 681 682 // Update the form's action URL to include the correct promotion ID 683 form.setAttribute('action', deleteUrl); 684 685 // Submit the form programmatically 686 form.submit(); 687 }); 688 }); 689 } 690 // First check if promotions widget is enabled 691 const promotionCheckDataCart = { 692 shopId: checkoutcartPageElement.getAttribute("data-shop-id"), 693 saleschannelId: checkoutcartPageElement.getAttribute("data-saleschannel-id"), 694 shopUrl: originUrl 695 }; 696 const promotionContainerCart = document.querySelector('.voyado-promotion'); 697 fetch(apiUrl + "/api/v1/shopware/check-promotion-widget", { 698 method: 'POST', 699 headers: { 700 'Content-Type': 'application/json' 701 }, 702 body: JSON.stringify(promotionCheckDataCart) 703 }).then(response => { 704 if (!response.ok) { 705 throw new Error('Network response was not ok'); 706 } 707 return response.json(); 708 }).then(data => { 709 if (data.showPromotionToggle) {
710 // Widget enabled - fetch promotions 711 const promotionData = { 712 voyadoCid: voyadoCid, 713 shopId: checkoutcartPageElement.getAttribute("data-shop-id"), 714 saleschannelId: checkoutcartPageElement.getAttribute("data-saleschannel-id"), 715 shopUrl: originUrl 716 }; 717 fetch(apiUrl + "/api/v2/shopware/get-promotions", { 718 method: 'POST', 719 headers: { 720 'Content-Type': 'application/json' 721 }, 722 body: JSON.stringify(promotionData) 723 }).then(response => { 724 if (!response.ok) { 725 throw new Error('Network response was not ok'); 726 } 727 return response.text(); 728 }).then(text => { 729 const promotionresponse = JSON.parse(text); 730 // Scenario 1: Widget enabled + active offers -> show widget 731 if (promotionresponse && promotionresponse.length > 0) { 732 if (promotionContainerCart) { 733 promotionContainerCart.style.display = 'block'; 734 } 735 displayPromotionData(promotionresponse, promotionContainerCart); 736 const checkoutAsideAction = document.querySelector('.voyado-checkout-aside-action'); 737 const promotionTags = document.querySelector('.voyado-promotion-tags'); 738 const defaultCheckoutAside = document.querySelector('.checkout-aside-action'); 739 if (checkoutAsideAction) checkoutAsideAction.style.display = 'flex'; 740 if (promotionTags) promotionTags.style.display = 'block'; 741 if (defaultCheckoutAside) defaultCheckoutAside.style.display = 'none'; 742 } else { 743 // Scenario 3: Widget enabled but no offers -> remove completely 744 if (promotionContainerCart) { 745 promotionContainerCart.remove(); 746 } 747 document.querySelector('.voyado-checkout-aside-action')?.remove(); 748 document.querySelector('.voyado-promotion-tags')?.remove(); 749 const checkoutAsideActionHide = document.querySelector('.checkout-aside-action'); 750 if (checkoutAsideActionHide) checkoutAsideActionHide.style.display = 'block'; 751 } 752 }).catch(error => { 753 if (promotionContainerCart) { 754 promotionContainerCart.remove(); 755 } 756 document.querySelector('.voyado-checkout-aside-action')?.remove(); 757 document.querySelector('.voyado-promotion-tags')?.remove(); 758 const checkoutAsideActionErr1 = document.querySelector('.checkout-aside-action'); 759 if (checkoutAsideActionErr1) checkoutAsideActionErr1.style.display = 'block'; 760 }); 761 } else { 762 // Scenario 2: Widget disabled -> remove completely 763 if (promotionContainerCart) { 764 promotionContainerCart.remove(); 765 } 766 document.querySelector('.voyado-checkout-aside-action')?.remove(); 767 document.querySelector('.voyado-promotion-tags')?.remove(); 768 const checkoutAsideActionDisabled = document.querySelector('.checkout-aside-action'); 769 if (checkoutAsideActionDisabled) checkoutAsideActionDisabled.style.display = 'block'; 770 } 771 }).catch(error => { 772 // Error checking widget status -> remove completely 773 if (promotionContainerCart) { 774 promotionContainerCart.remove(); 775 } 776 document.querySelector('.voyado-checkout-aside-action')?.remove(); 777 document.querySelector('.voyado-promotion-tags')?.remove(); 778 const checkoutAsideActionErr2 = document.querySelector('.checkout-aside-action'); 779 if (checkoutAsideActionErr2) checkoutAsideActionErr2.style.display = 'block'; 780 }); 781 if(voyadoAnalytics === '1') 782 { 783 const previousCartData = JSON.parse(localStorage.getItem('previousCartData')) || []; 784 const productDataRaw = checkoutcartPageElement.getAttribute("data-product-data"); 785 const productDataArray = productDataRaw ? JSON.parse(productDataRaw) : []; 786 if (!productDataArray || productDataArray.length === 0) { 787 // Cart is empty, remove previous cart data from local storage 788 localStorage.removeItem('previousCartData'); 789 return; 790 } 791 //Check for changes and update cart ID if necessary 792 if (hasCartChanged(previousCartData, productDataArray)) { 793 const cartId = localStorage.getItem('voyado-cartid'); 794 if (!cartId) { 795 const cartId = generateCartId(32); 796 localStorage.setItem('voyado-cartid', cartId); 797 } 798 const cartTrackingData = { 799 customerId: voyadoCid, 800 cartId: cartId, 801 productdata: JSON.parse(checkoutcartPageElement.getAttribute("data-product-data")), 802 url: checkoutcartPageElement.getAttribute("data-checkout-cart-page-url"), 803 time: checkoutcartPageElement.getAttribute("data-time"), 804 language: checkoutcartPageElement.getAttribute("data-language"), 805 saleschannelId: checkoutcartPageElement.getAttribute("data-saleschannel-id"), 806 shopId: checkoutcartPageElement.getAttribute("data-shop-id"), 807 shopUrl: originUrl 808 }; 809 // First fetch request (cart tracking) 810 fetch(apiUrl + "/api/v2/shopware/cart-tracking", { 811 method: 'POST', 812 headers: { 813 'Content-Type': 'application/json' 814 }, 815 body: JSON.stringify(cartTrackingData) 816 }).then(response => { 817 if (!response.ok) { 818 throw new Error('Network response was not ok'); 819 } 820 return response.text(); 821 }).catch(error => {
822 console.error('Error sending cart data to webhook:', error); 823 }); 824 // Update previous cart data in local storage 825 localStorage.setItem('previousCartData', JSON.stringify(productDataArray)); 826 } 827 } 828 } 829 830 } 831 } 832 } 833 // Script to display promotions in minicart page 834 const cartButton = document.querySelector(".header-cart-btn"); 835 if (cartButton) { 836 cartButton.addEventListener("click", function () { 837 if(voyadoCid) 838 { 839 // First check if promotions widget is enabled 840 const promotionCheckDataMinicart = { 841 shopId: voyadoTrackingElement.getAttribute("data-app-shop-id"), 842 saleschannelId: voyadoTrackingElement.getAttribute("data-saleschannel-id"), 843 shopUrl: originUrl 844 }; 845 const promotionContainerMinicart = document.querySelector('.voyado-promotion'); 846 fetch(apiUrl + "/api/v1/shopware/check-promotion-widget", { 847 method: 'POST', 848 headers: { 849 'Content-Type': 'application/json' 850 }, 851 body: JSON.stringify(promotionCheckDataMinicart) 852 }).then(response => { 853 if (!response.ok) { 854 throw new Error('Network response was not ok'); 855 } 856 return response.json(); 857 }).then(data => { 858 if (data.showPromotionToggle) {
859 // Widget enabled - fetch promotions 860 const promotionData = { 861 voyadoCid: voyadoCid, 862 shopId: voyadoTrackingElement.getAttribute("data-app-shop-id"), 863 saleschannelId: voyadoTrackingElement.getAttribute("data-saleschannel-id"), 864 shopUrl: originUrl 865 }; 866 fetch(apiUrl + "/api/v2/shopware/get-promotions", { 867 method: 'POST', 868 headers: { 869 'Content-Type': 'application/json' 870 }, 871 body: JSON.stringify(promotionData) 872 }).then(response => { 873 if (!response.ok) { 874 throw new Error('Network response was not ok'); 875 } 876 return response.text(); 877 }).then(text => { 878 const promotionresponse = JSON.parse(text); 879 // Scenario 1: Widget enabled + active offers -> show widget 880 if (promotionresponse && promotionresponse.length > 0) { 881 if (promotionContainerMinicart) { 882 promotionContainerMinicart.style.display = 'block'; 883 } 884 displayPromotionData(promotionresponse); 885 } else { 886 // Scenario 3: Widget enabled but no offers -> remove completely 887 if (promotionContainerMinicart) { 888 promotionContainerMinicart.remove(); 889 } 890 } 891 }).catch(error => { 892 console.log('Error fetching promotions:', error); 893 if (promotionContainerMinicart) { 894 promotionContainerMinicart.remove(); 895 } 896 }); 897 } else { 898 // Scenario 2: Widget disabled -> remove completely 899 if (promotionContainerMinicart) { 900 promotionContainerMinicart.remove(); 901 } 902 } 903 }).catch(error => { 904 console.log('Error checking promotions enabled:', error); 905 if (promotionContainerMinicart) { 906 promotionContainerMinicart.remove(); 907 } 908 }); 909 } 910 }); 911 } 912 // Script to assign promotions through soft identification 913 if (eclub) { 914 try { 915 const decryptionData = { 916 shopId: voyadoTrackingElement.getAttribute("data-app-shop-id"), 917 saleschannelId: voyadoTrackingElement.getAttribute("data-saleschannel-id"), 918 eclub: eclub 919 }; 920 // Step 1: Get decryption key from API 921 fetch(apiUrl + "/api/v2/shopware/getSoftDecryptionKey", { 922 method: "POST", 923 headers: { "Content-Type": "application/json" }, 924 body: JSON.stringify(decryptionData), 925 }) 926 .then(response => { 927 if (!response.ok) { 928 throw new Error("Network response was not ok"); 929 } 930 return response.json(); // parse JSON response 931 }) 932 .then(result => { 933 // Step 1: Parse the stringified JSON 934 const parsedData = JSON.parse(result.decryptionkey); 935 // Step 2: Store contactId 936 localStorage.setItem("voyado-cid", parsedData.contactId); 937 const voyadoCid = localStorage.getItem("voyado-cid"); 938 939 // Step 3: Send promotion data 940 const promotionData = { 941 voyadoCid: voyadoCid, 942 email: parsedData.email, 943 shopId: voyadoTrackingElement.getAttribute("data-app-shop-id"), 944 saleschannelId: voyadoTrackingElement.getAttribute("data-saleschannel-id"), 945 shopUrl: originUrl 946 }; 947 return fetch(apiUrl + "/api/v2/shopware/assignPromotionBySoftid", { 948 method: "POST", 949 headers: { "Content-Type": "application/json" }, 950 body: JSON.stringify(promotionData), 951 }); 952 }) 953 .then(response => { 954 if (response && !response.ok) { 955 throw new Error("Promotion API failed"); 956 } 957 if (response) return response.text(); 958 }) 959 .then(result => { 960 if (result) console.log("Promotion assigned:", result); 961 }) 962 .catch(error => { 963 console.error("Error:", error); 964 }); 965 966 } catch (err) { 967 console.error("Decryption failed:", err); 968 } 969 } 970 // Mark tracking as initialized to prevent duplicate initialization 971 window.voyadoTrackingInitialized = true; 972} 973// Get Cookie Value 974function getCookieValue(cookieName) { 975 const name = cookieName + "="; 976 const decodedCookie = decodeURIComponent(document.cookie); 977 const cookieArray = decodedCookie.split(';'); 978 979 for (let i = 0; i < cookieArray.length; i++) { 980 let cookie = cookieArray[i].trim(); 981 if (cookie.indexOf(name) === 0) { 982 return cookie.substring(name.length, cookie.length); 983 } 984 } 985 return null; // return null if the cookie is not found 986} 987 988// Generate a random cart ID 989function generateCartId(length) { 990 const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
991 let cartId = ''; 992 for (let i = 0; i < length; i++) { 993 const randomIndex = Math.floor(Math.random() * charset.length); 994 cartId += charset[randomIndex]; 995 } 996 return cartId; 997} 998 999// Generate a random integer within a given range [min, max] 1000function randomIndexFromInterval(min, max) { 1001 return Math.floor(Math.random() * (max - min + 1) + min); 1002} 1003 1004//Function to check if the cart has changed 1005function hasCartChanged(previousData, currentData) { 1006 if (!Array.isArray(previousData)) { 1007 previousData = [previousData]; 1008 } 1009 if (!Array.isArray(currentData)) { 1010 currentData = [currentData]; 1011 } 1012 if (previousData.length !== currentData.length) { 1013 return true; // Different number of items 1014 } 1015 for (let i = 0; i < currentData.length; i++) { 1016 const currentItem = currentData[i]; 1017 const previousItem = previousData.find(item => item.productId === currentItem.productId); 1018 if (!previousItem || previousItem.quantity !== currentItem.quantity) { 1019 return true; // Item quantity has changed or item is new 1020 } 1021 } 1022 return false; // No changes detected 1023} 1024 1025//Function to check cart information in checkout finishpage 1026function hasCartChangedFinishPage(previousData, currentData) { 1027 // Parse data if it's a JSON string 1028 if (typeof previousData === "string") { 1029 try { 1030 previousData = JSON.parse(previousData); 1031 } catch (e) { 1032 console.error("Error parsing previousData:", e); 1033 previousData = []; 1034 } 1035 } 1036 if (typeof currentData === "string") { 1037 try { 1038 currentData = JSON.parse(currentData); 1039 } catch (e) { 1040 console.error("Error parsing currentData:", e); 1041 currentData = []; 1042 } 1043 } 1044 previousData = Array.isArray(previousData) ? previousData : [previousData]; 1045 currentData = Array.isArray(currentData) ? currentData : [currentData]; 1046 // Sort both arrays by productId for consistent comparison 1047 previousData = [...previousData].sort((a, b) => a.productId.localeCompare(b.productId)); 1048 currentData = [...currentData].sort((a, b) => a.productId.localeCompare(b.productId)); 1049 // Compare each item in both sorted arrays 1050 return previousData.every((prevItem, index) => { 1051 const currItem = currentData[index]; 1052 return currItem && prevItem.productId === currItem.productId && prevItem.quantity === currItem.quantity; 1053 }); 1054} 1055//Function to display promotions 1056function displayPromotionData(promotionResponse, targetContainer) { 1057 const container = targetContainer || document.querySelector('.voyado-promotion'); 1058 if (!container) { 1059 return; 1060 } 1061 container.innerHTML = ''; // Clear previous content 1062 1063 // Add header 1064 const header = document.createElement('div'); 1065 header.className = 'voyado-promotion-header'; 1066 header.innerHTML = `<span> <svg width="18" height="18" viewBox="0 0 19 17" fill="none" xmlns="http://www.w3.org/2000/svg"> 1067 <path d="M16.7191 4.44906C17.6214 4.44906 18.385 5.21262 18.385 6.115V9.44687C18.385 9.75924 18.1073 10.0022 17.8297 10.0022H17.2744V15C17.2744 15.9371 16.5108 16.6659 15.6084 16.6659H3.39156C2.45447 16.6659 1.72562 15.9371 1.72562 15V10.0022H1.17031C0.857949 10.0022 0.615 9.75924 0.615 9.44687V6.115C0.615 5.21262 1.34385 4.44906 2.28094 4.44906H3.63451C3.28744 3.99787 3.11391 3.44256 3.11391 2.78312C3.11391 1.25602 4.25924 0.00656211 5.68223 0.00656211C7.13992 0.00656211 8.0076 0.908945 9.5 3.33844C10.9577 0.908945 11.8254 0.00656211 13.2831 0.00656211C14.7061 0.00656211 15.8861 1.25602 15.8861 2.78312C15.8861 3.44256 15.6779 3.99787 15.3308 4.44906H16.7191ZM10.1941 4.34494L10.1247 4.44906H13.2831C14.0813 4.44906 14.7755 3.72021 14.7755 2.78312C14.7755 1.88074 14.0813 1.11719 13.2831 1.11719C12.4848 1.11719 11.8948 1.46426 10.1941 4.34494ZM5.68223 1.11719C4.88396 1.11719 4.22453 1.88074 4.18982 2.78312C4.18982 3.72021 4.88396 4.44906 5.68223 4.44906H8.84057L8.77115 4.34494C7.07051 1.46426 6.48049 1.11719 5.68223 1.11719ZM7.27875 15.5553V10.0022H2.83625V15C2.83625 15.3124 3.0792 15.5553 3.39156 15.5553H7.27875ZM7.27875 8.89156V5.55969H2.28094C1.96857 5.55969 1.72562 5.83734 1.72562 6.115V8.89156H7.27875ZM10.6106 15.5553V5.55969H8.38937V15.5553H10.6106ZM16.1638 15V10.0022H11.7213V15.5553H15.6084C15.8861 15.5553 16.1638 15.3124 16.1638 15ZM17.2744 8.89156V6.115C17.2744 5.83734 16.9967 5.55969 16.7191 5.55969H11.7213V8.89156H17.2744Z" fill="#21211F"/> 1068 </svg>Your Offers</span>
1069 <button id="voyadodropdownIcon" class="voyado-dropdown-icon" style="margin-bottom:5px" title="offer-button"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"> 1070 <path d="M7.247 11.14l-4.796-5.481c-.566-.648.208-1.659 1.034-1.127l4.763 4.57 4.763-4.57c.825-.532 1.6.48 1.034 1.127L8.753 11.14a1 1 0 0 1-1.506 0z"></path> 1071 </svg></button>`; 1072 container.appendChild(header); 1073 // Add promotion list 1074 const promotionList = document.createElement('ul'); 1075 promotionList.className = 'voyado-promotion-list'; 1076 promotionList.style.display = 'none'; 1077 promotionResponse.forEach((promotion) => { 1078 const card = document.createElement('li'); 1079 card.className = 'voyado-promotion-card'; 1080 1081 // Promotion info 1082 const info = document.createElement('div'); 1083 info.className = 'voyado-promotion-info'; 1084 1085 const title = document.createElement('span'); 1086 title.className = 'voyado-promotion-title'; 1087 title.innerText = promotion.name; 1088 1089 const description = document.createElement('p'); 1090 description.className = 'voyado-promotion-description'; 1091 description.innerText = promotion.value; 1092 1093 const expiry = document.createElement('p'); 1094 expiry.className = 'voyado-promotion-expiry'; 1095 expiry.innerHTML = ` 1096 <svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg"> 1097 <path fill-rule="evenodd" clip-rule="evenodd" d="M8.35611 0.878906C11.7501 0.878906 14.5 3.62879 14.5 7.02279C14.5 9.93145 12.4804 12.367 9.76649 13.0041L10.2741 13.884C10.6003 14.4335 10.1883 15.1203 9.57013 15.1203H1.32844C0.693144 15.1203 0.298229 14.4335 0.607293 13.884L2.91623 9.88185C2.46665 9.02793 2.21222 8.05516 2.21222 7.02279C2.21222 3.62879 4.96211 0.878906 8.35611 0.878906ZM3.39858 9.04578L4.72814 6.74121C5.0372 6.19177 5.8442 6.19177 6.15326 6.74121L9.34945 12.2813C11.8255 11.8136 13.7072 9.63159 13.7072 7.02279C13.7072 4.07472 11.2794 1.67167 8.35611 1.67167C5.38326 1.67167 3.00498 4.09949 3.00498 7.02279C3.00498 7.74009 3.14488 8.42277 3.39858 9.04578ZM10.016 9.22766L7.98451 7.76601C7.91018 7.69169 7.88541 7.61736 7.88541 7.51827V3.55447C7.88541 3.40583 8.00928 3.25719 8.1827 3.25719H8.52953C8.67817 3.25719 8.82681 3.40583 8.82681 3.55447V7.19621L10.561 8.45967C10.6848 8.55877 10.7344 8.73218 10.6353 8.88083L10.4123 9.15334C10.338 9.2772 10.1398 9.32675 10.016 9.22766ZM5.8442 9.19655C5.8442 9.12787 5.80986 9.07636 5.74118 9.07636H5.14022C5.07154 9.07636 5.0372 9.12787 5.0372 9.19655L5.15739 12.5619C5.15739 12.6134 5.2089 12.6478 5.26041 12.6478H5.62099C5.6725 12.6478 5.72401 12.6134 5.72401 12.5619L5.8442 9.19655ZM5.44929 12.9912C5.70684 12.9912 5.93005 13.2144 5.93005 13.4719C5.93005 13.7467 5.70684 13.9527 5.44929 13.9527C5.17456 13.9527 4.96852 13.7467 4.96852 13.4719C4.96852 13.2144 5.17456 12.9912 5.44929 12.9912Z" fill="black"/> 1098 </svg> 1099 ${promotion.expiresOn}.`; 1100 1101 info.appendChild(title); 1102 info.appendChild(description); 1103 info.appendChild(expiry); 1104 const applyButton = document.createElement('button'); 1105 applyButton.className = 'btn btn-primary voyado-promotion-apply-button'; 1106 applyButton.innerText = 'Apply'; 1107 applyButton.title = "voyado-promotion-apply-button"; 1108 applyButton.onclick = () => { 1109 let inputGroup = document.querySelector('.js-offcanvas-cart-promotion'); 1110 if (!inputGroup) { 1111 inputGroup = document.querySelector('.voyado-checkout-aside-action'); 1112 } 1113 const inputField = inputGroup.querySelector('input[type="text"]'); 1114 // Set the promotion code 1115 inputField.value = promotion.value; 1116 inputField.dispatchEvent(new Event('input')); 1117 }; 1118 1119 card.appendChild(info); 1120 card.appendChild(applyButton); 1121 promotionList.appendChild(card); 1122 }); 1123 1124 container.appendChild(promotionList); 1125 1126 const dropdownButton = header.querySelector("#voyadodropdownIcon"); 1127 dropdownButton.addEventListener("click", () => { 1128 const isOpen = promotionList.style.display === "block"; 1129 promotionList.style.display = isOpen ? "none" : "block"; 1130 promotionList.style.border = isOpen ? "none" : "block"; 1131 dropdownButton.innerHTML = isOpen 1132 ? `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"> 1133 <path d="M7.247 11.14l-4.796-5.481c-.566-.648.208-1.659 1.034-1.127l4.763 4.57 4.763-4.57c.825-.532 1.6.48 1.034 1.127L8.753 11.14a1 1 0 0 1-1.506 0z"></path> 1134 </svg>` 1135 : `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"> 1136 <path d="M7.247 4.86l-4.796 5.481c-.566.648.208 1.659 1.034 1.127l4.763-4.57 4.763 4.57c.825.532 1.6-.48 1.034-1.127L8.753 4.86a1 1 0 0 0-1.506 0z"></path> 1137 </svg>`; 1138 }); 1139} 1140 1141// Generate a random session ID using alphanumeric characters 1142function generateSessionID() { 1143 return Math.random().toString(36).substr(2, 12); // Generate a random string and take a substring of length 12 1144} 1145// Function to format the current date and time 1146function getCurrentDateTime() { 1147 const now = new Date(); 1148 const year = now.getFullYear();
1149 const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are 0-based 1150 const day = String(now.getDate()).padStart(2, '0'); 1151 const hours = String(now.getHours()).padStart(2, '0'); 1152 const minutes = String(now.getMinutes()).padStart(2, '0'); 1153 const seconds = String(now.getSeconds()).padStart(2, '0'); 1154 return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; 1155} 1156 1157// Function to extract desired data from cart line items 1158function extractCartData(cartItems) { 1159 return cartItems.map(item => { 1160 return { 1161 productId: item.id, 1162 quantity: item.quantity, 1163 itemNumber: item.payload.productNumber 1164 }; 1165 }); 1166}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.