Last active
March 18, 2025 12:41
-
-
Save rashedInt32/c9590d531f314785422d7b6da5ea240d to your computer and use it in GitHub Desktop.
Inactivity Cart Reminder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Need to ensure its desktop and chrome browser only | |
* Crate CONFIG object that holds the constant value | |
* Create timer function that trigger show modal function | |
* after 30 seconds of inactivity | |
* | |
* add Event listener for mousemove, click and keypress | |
* * Show modal function should check if the cart empty or not | |
* * if empty, do nothing. It item present trigger modal with last item | |
* create show modal function to render html | |
* Add modal close funcionlity | |
* create style function for the modal | |
* | |
* Create init funciton that check when modal should show or not | |
* attach event listener function inside init | |
* lastly call resetTimer inside init | |
* | |
** We'll get cart items details from sessionStorage `cart` key {itemCount: number, cartItems: object[]} | |
*/ | |
/** | |
* Inactivity Cart Reminder | |
*/ | |
const InactivityCartReminder = (function () { | |
function shouldInitialize() { | |
return window.innerWidth >= 1024 && /Chrome/.test(navigator.userAgent); | |
} | |
const CONFIG = { | |
inactivityTimeout: 30000, // 30 seconds | |
minScreenWidth: 1024, | |
storageKey: "cart", | |
cartPageUrl: "https://www.serenaandlily.com/shoppingbag", | |
}; | |
let inactivityTimer; | |
// Reset inactivity timer | |
function resetTimer() { | |
clearTimeout(inactivityTimer); | |
inactivityTimer = setTimeout( | |
checkCartAndShowModal, | |
CONFIG.inactivityTimeout | |
); | |
} | |
// Attach all event listeners | |
function attachEventListeners() { | |
["mousemove", "click", "keypress"].forEach((eventType) => { | |
document.addEventListener(eventType, resetTimer, { passive: true }); | |
}); | |
} | |
// Check cart and show modal if needed | |
function checkCartAndShowModal() { | |
try { | |
const cart = JSON.parse(sessionStorage.getItem(CONFIG.storageKey)) || {}; | |
const { itemCount, cartItems } = cart; | |
if (itemCount > 0) { | |
const lastItem = cartItems[cartItems.length - 1]; | |
showModal(lastItem); | |
} | |
} catch (error) { | |
console.error("Error checking cart:", error); | |
} | |
} | |
// Create and display the modal | |
function showModal(item) { | |
// Prevent multiple modals | |
if (document.querySelector(".icr-modal")) return; | |
addStyles(); | |
// Create modal elements | |
const modal = document.createElement("div"); | |
modal.className = "icr-modal"; | |
modal.innerHTML = ` | |
<div class="icr-content"> | |
<div class="icr-header"> | |
<h2 class="icr-title">Buy now before it's too late!</h2> | |
<button class="icr-close" aria-label="Close">×</button> | |
</div> | |
<div class="icr-product"> | |
<img class="icr-product-img" src="${item.imageUrl || ""}" alt="${item.name}"> | |
<div class="icr-details"> | |
<p class="icr-name">${item.name}</p> | |
${ | |
item.variations.length > 0 | |
? item.variations | |
.map( | |
(variant) => | |
`<p class="icr-desc"><span class="icr-desc-highlight">${variant.name}: </span>${variant.value}</p>` | |
) | |
.join("") | |
: "" | |
} | |
<p class="icr-desc"> | |
Qty: ${item.quantity} | |
</p> | |
<p class="icr-price">${item.displayPrice.unitPrice}</p> | |
</div> | |
</div> | |
<div class="icr-footer"> | |
<button class="icr-btn icr-btn-primary view-bag">View Bag</button> | |
<button class="icr-btn icr-btn-outline continue-shopping">Continue Shopping</button> | |
</div> | |
</div> | |
`; | |
document.body.appendChild(modal); | |
document.body.classList.add("icr-modal-open"); | |
// Add event listeners to modal buttons | |
modal.querySelector(".icr-close").addEventListener("click", closeModal); | |
modal | |
.querySelector(".continue-shopping") | |
.addEventListener("click", closeModal); | |
modal.querySelector(".view-bag").addEventListener("click", () => { | |
window.location.href = CONFIG.cartPageUrl; | |
}); | |
// Close modal when clicking overlay | |
modal.addEventListener("click", (event) => { | |
if (event.target === modal) { | |
closeModal(modal); | |
} | |
}); | |
} | |
// Close the modal | |
function closeModal() { | |
const modal = document.querySelector(".icr-modal"); | |
if (modal) { | |
modal.remove(); | |
document.body.classList.remove("icr-modal-open"); | |
} | |
} | |
// Add styles to document | |
function addStyles() { | |
if (document.getElementById("icr-styles")) return; | |
const style = document.createElement("style"); | |
style.id = "icr-styles"; | |
style.textContent = ` | |
.icr-modal { | |
position: fixed; | |
inset: 0; | |
background: rgba(0,0,0,0.5); | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
z-index: 99999; | |
font-family: BeatriceMedium, sans-serif; | |
} | |
.icr-content { | |
background: white; | |
padding: 16px; | |
min-width: 400px; | |
max-width: 500px; | |
border: 1px solid #a7b0ba; | |
box-shadow: 0 4px 20px rgba(0,0,0,0.2); | |
} | |
.icr-header { | |
position: relative; | |
display: flex; | |
padding-bottom: 10px; | |
border-bottom: 1px solid #a7b0ba; | |
} | |
.icr-title { | |
font-size: 15px; | |
text-transform: uppercase; | |
color: #243953; | |
margin: 0; | |
font-weight: normal; | |
} | |
.icr-close { | |
position: absolute; | |
top: -7px; | |
right: 0px; | |
width: 35px; | |
height: 35px; | |
border: 1px solid #a7b0ba; | |
border-radius: 30px; | |
background: none; | |
font-size: 30px; | |
cursor: pointer; | |
} | |
.icr-product { | |
display: flex; | |
align-items: flex-start; | |
padding-top: 10px; | |
} | |
.icr-product-img { | |
max-width: 100px; | |
} | |
.icr-details { | |
padding-left: 15px; | |
text-align: left; | |
} | |
.icr-name, .icr-price { | |
font-size: 14px; | |
margin: 0 0 5px; | |
} | |
.icr-desc { | |
font-size: 13px; | |
color: #6d7a86; | |
margin: 0; | |
} | |
.icr-desc-highlight { | |
color: #243953; | |
font-weight: bold; | |
} | |
.icr-footer { | |
margin-top: 20px; | |
padding-top: 15px; | |
display: flex; | |
justify-content: center; | |
gap: 15px; | |
border-top: 1px solid #a7b0ba; | |
} | |
.icr-btn { | |
border-radius: 9999px; | |
font-size: 15px; | |
letter-spacing: 1px; | |
text-transform: uppercase; | |
padding: 16px 40px 13px; | |
transition: all .3s ease; | |
cursor: pointer; | |
} | |
.icr-btn-outline { | |
border: 0; | |
background: transparent; | |
color: #243953; | |
padding: 16px 0; | |
} | |
.icr-btn-primary { | |
background-color: #243953; | |
border: 1px solid #243953; | |
color: white; | |
font-weight: 600; | |
} | |
.icr-btn-outline:hover { | |
color: rgba(36, 57, 83, 0.6); | |
} | |
.icr-btn-primary:hover { | |
background-color: #acbecf; | |
border-color: #acbecf; | |
color: #243953; | |
} | |
body.icr-modal-open { | |
overflow: hidden !important; | |
} | |
`; | |
document.head.appendChild(style); | |
} | |
// Initialize the module | |
function init() { | |
if (!shouldInitialize()) { | |
return; | |
} | |
attachEventListeners(); | |
resetTimer(); | |
} | |
return { | |
init, | |
}; | |
})(); | |
// Initialize the module when the DOM is fully loaded | |
InactivityCartReminder.init(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment