${popup?.design?.text?.html_string || ""}
`;
};
const PopupLayoutThree = ({ popup, id, data }) => {
const now = new Date().getTime(); // current time in milliseconds
const then = data.updated_at ? data.updated_at * 1000 : data.created_at * 1000; // Unix timestamp in milliseconds
const diff = (now - then) / 1000; // difference in seconds
const hide_close_button = popup.display?.hide_close_button;
let timeElapsesd;
if (diff < 60) {
timeElapsesd = `${Math.floor(diff)} seconds ago`;
} else if (diff < 3600) {
timeElapsesd = `${Math.floor(diff / 60)} minutes ago`;
} else if (diff < 3600 * 24) {
timeElapsesd = `${Math.floor(diff / 3600)} hours ago`;
} else {
timeElapsesd = `${Math.floor(diff / (3600 * 24))} days ago`;
}
return `
`;
};
// widget layouts
const Text_Above = ({ widget }) => {
return `
${widget?.design?.text?.html_string || ""}
`;
};
const Text_Above_Above = ({ widget }) => {
return `
${widget?.design?.text?.html_string || ""}
`;
};
const Text_Above_Below = ({ widget }) => {
return `
${widget?.design?.text?.html_string || ""}
`;
};
const Text_Below = ({ widget }) => {
return `
${widget?.design?.text?.html_string || ""}
`;
};
const Text_Below_Above = ({ widget }) => {
return `
${widget?.design?.text?.html_string || ""}
`;
};
const Text_Below_Below = ({ widget }) => {
return `
${widget?.design?.text?.html_string || ""}
`;
};
// function to display popup
const displayPopup = async (popups) => {
let orderData,
cartData,
ordersLastKey,
cartLastKey,
validateCartTimeLimit = true,
validateOrdersTimeLimit = true; // turn to false if there are any cart items that are older than the time limit
// function to fetch recent orders
const fetchOrderData = async ({ key, session_limit }) => {
const ordersResponse = await fetch(`/apps/stocksheep/shop-requests/getOrdersData`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ key, session_limit }),
});
const data = await ordersResponse.json();
ordersLastKey = data.LastEvaluatedKey;
if (data.Items?.length > 0) {
return { items: [...data.Items] };
}
return;
};
// function to fetch recent add to cart activity
const fetchCartData = async ({ key, session_limit }) => {
const cartResponse = await fetch(`/apps/stocksheep/shop-requests/getCartData`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ key, session_limit }),
});
const data = await cartResponse.json();
cartLastKey = data.LastEvaluatedKey;
if (data.Items?.length > 0) {
return { items: [...data.Items] };
}
return;
};
const addPopupHtml = ({ popup, data, type }) => {
const stop_cycle = popup.display?.stop_cycle_items;
const session_limit = popup.display?.items_per_session || 10;
data.forEach((item, index) => {
// append popup after set interval
setTimeout(() => {
const randomId = Math.random().toString(36).substring(2, 15);
["mobile", "desktop"].forEach((device) => {
const querySelector = device === "mobile" ? `stocksheep_popup_mobile_${popup.position.mobile.position}` : `stocksheep_popup_desktop_${popup.position.desktop.position}_${popup.position.desktop.alignment}`;
let popupElement = document.querySelector(`.${querySelector}`);
// justify content center for mobile popups
if (device === "mobile") {
const popupElementStyle = document.createElement("style");
popupElementStyle.innerHTML = `
.stocksheep_popup_mobile_${popup.position.mobile.position} .${randomId} {
display: flex;
justify-content: center;
}
`;
document.head.appendChild(popupElementStyle);
}
/// if popup element not found create new one
if (!popupElement) {
popupElement = document.createElement("div");
popupElement.classList.add(querySelector);
document.body.appendChild(popupElement);
const popupElementStyle = document.createElement("style");
popupElementStyle.innerHTML =
device === "mobile"
? `
.stocksheep_popup_mobile_${popup.position.mobile.position} {
position: fixed;
z-index: 999999999999;
}
@media (max-width: 600px) {
.stocksheep_popup_mobile_${popup.position.mobile.position} {
max-width: 100%;
transform-origin: ${popup.position.mobile.position} left;
transform: translateX(-50%);
top: ${popup.position.mobile.position === "top" ? "1%" : "auto"};
bottom: ${popup.position.mobile.position === "bottom" ? "1%" : "auto"};
left: 50%;
}
}
@media (min-width: 600px) {
.stocksheep_popup_mobile_${popup.position.mobile.position} {
display: none;
}
}
`
: `
.stocksheep_popup_desktop_${popup.position.desktop.position}_${popup.position.desktop.alignment} {
position: fixed;
z-index: 999999999999;
}
@media (max-width: 600px) {
.stocksheep_popup_desktop_${popup.position.desktop.position}_${popup.position.desktop.alignment} {
display: none;
}
}
@media (min-width: 600px) {
.stocksheep_popup_desktop_${popup.position.desktop.position}_${popup.position.desktop.alignment} {
max-width: 100%;
transform-origin: ${popup.position.desktop.position} ${popup.position.desktop.alignment === "right" ? "right" : "left"};
${popup.position.desktop.alignment === "center" ? "transform: translateX(-50%);" : ""}
top: ${popup.position.desktop.position === "top" ? "4%" : "auto"};
bottom: ${popup.position.desktop.position === "bottom" ? "0" : "auto"};
left: ${popup.position.desktop.alignment === "left" ? "0" : popup.position.desktop.alignment === "center" ? "50%" : "auto"};
right: ${popup.position.desktop.alignment === "right" ? "0" : "auto"};
}
}
`;
document.head.appendChild(popupElementStyle);
}
// append font if font url is provided
if (popup.design.text.font_url) {
appendFont(popup.design.text.font_url);
}
// append popup html based on layout
switch (popup.design.layout) {
case "layout-1":
popupElement.insertAdjacentHTML("beforeend", PopupLayoutOne({ popup, id: randomId, data: item }));
break;
case "layout-2":
popupElement.insertAdjacentHTML("beforeend", PopupLayoutTwo({ popup, id: randomId, data: item }));
break;
case "layout-3":
popupElement.insertAdjacentHTML("beforeend", PopupLayoutThree({ popup, id: randomId, data: item }));
break;
}
// remove popup after display time
setTimeout(() => {
[...document.getElementsByClassName(randomId)].forEach((popup) => {
popup.remove();
});
}, 1000 * parseInt(popup.display.display_time));
});
//if this is last item for which popup is displayed then fetch another cart data and display popup
if (stop_cycle !== true) {
if (index === data.length - 1) {
// if type is cart then fetch cart data
if (type === "cart") {
setTimeout(() => {
// if LastEvaluatedKey for cart data is not null then fetch previous cart data
if (cartLastKey && validateCartTimeLimit) {
fetchCartData({ session_limit, key: cartLastKey }).then((data) => {
const items = data?.items;
if (items && items.length > 0) {
let filteredData = items.filter((cartItem) => {
if (popup.display?.display_time_limit) {
const now = new Date().getTime(); // current time in milliseconds
const then = cartItem.updated_at ? cartItem.updated_at * 1000 : cartItem.created_at * 1000; // Unix timestamp in milliseconds
const diff = (now - then) / 1000; // difference in seconds
const limit = popup.display?.display_time_limit_unit == "all" ? now / 1000 - 1592200722 : popup.display?.display_time_limit_unit == "days" ? popup.display?.display_time_limit * 24 * 60 * 60 : popup.display?.display_time_limit * 60 * 60;
if (diff < limit) {
return true;
} else {
validateCartTimeLimit = false;
}
}
return false;
});
filteredData =
popup.display.type == "global"
? [...filteredData]
: filteredData
.filter((cart) => {
return popup.display?.display_products?.some((product) => {
return product.variants.some((variant) => {
return variant.id == cart.product.variant_id;
});
});
})
.sort((a, b) => a.updated_at - b.updated_at);
if (filteredData && filteredData.length > 0) {
addPopupHtml({ popup, data: filteredData, type: "cart" });
}
}
});
} else {
// if LastEvaluatedKey for cart data is null then fetch new cart data
fetchCartData({ session_limit }).then((data) => {
if (data.items && data.items.length > 0) {
const newItems = data.items.filter((item) => !cartData.find((cart) => cart.id == item.id));
if (newItems && newItems.length > 0) {
const filteredData =
popup.display.type == "global"
? [...newItems]
: newItems
.filter((cart) => {
return popup.display?.display_products?.some((product) => {
return product.variants.some((variant) => {
return variant.id == cart.product.variant_id;
});
});
})
.sort((a, b) => b.updated_at - a.updated_at);
if (filteredData && filteredData.length > 0) {
addPopupHtml({ popup, data: filteredData, type: "cart" });
}
}
}
});
}
}, 1000 * parseInt(popup.display.session_delay));
}
// if type is order then fetch order data
else if (type === "orders") {
setTimeout(() => {
// if LastEvaluatedKey for orders data is not null then fetch previous orders data
if (ordersLastKey && validateOrdersTimeLimit) {
fetchOrderData({ key: ordersLastKey, session_limit }).then((data) => {
const items = data?.items;
if (items && items.length > 0) {
let filteredData = items.filter((orderItem) => {
if (popup.display?.display_time_limit) {
const now = new Date().getTime(); // current time in milliseconds
const then = orderItem.updated_at ? orderItem.updated_at * 1000 : orderItem.created_at * 1000; // Unix timestamp in milliseconds
const diff = (now - then) / 1000; // difference in seconds
const limit = popup.display?.display_time_limit_unit == "all" ? now / 1000 - 1592200722 : popup.display?.display_time_limit_unit == "days" ? popup.display?.display_time_limit * 24 * 60 * 60 : popup.display?.display_time_limit * 60 * 60;
if (diff < limit) {
return true;
} else {
validateOrdersTimeLimit = false;
}
}
return false;
});
filteredData =
popup.display.type == "global"
? [...filteredData]
: filteredData.filter((order) => {
return popup.display?.display_products?.some((product) => {
return product.variants.some((variant) => {
return variant.id == order.product.variant_id;
});
});
});
if (filteredData && filteredData.length > 0) {
addPopupHtml({ popup, data: filteredData, type: "orders" });
}
}
});
} else {
// if LastEvaluatedKey for orders data is null then fetch new orders data
fetchOrderData({ session_limit }).then((data) => {
if (data.items && data.items.length > 0) {
const newItems = data.items.filter((item) => !orderData.find((e) => e.id == item.id));
if (newItems && newItems.length > 0) {
const filteredData =
popup.display.type == "global"
? [...newItems]
: newItems.filter((order) => {
return popup.display?.display_products?.some((product) => {
return product.variants.some((variant) => {
return variant.id == order.product.variant_id;
});
});
});
if (filteredData && filteredData.length > 0) {
addPopupHtml({ popup, data: filteredData, type: "orders" });
}
}
}
});
}
}, 1000 * parseInt(popup.display.session_delay));
}
}
}
// remove popup after close button click
[...document.getElementsByClassName(`stocksheep_popup_close_${randomId}`)].forEach((element) => {
element.addEventListener("click", () => {
[...document.getElementsByClassName(randomId)].forEach((popup) => {
popup.remove();
});
});
});
}, index * (parseInt(popup.display.popup_delay) + parseInt(popup.display.display_time)) * 1000);
});
};
// function to append popups html
const appendSalesPopup = async (popup) => {
const session_limit = popup.display?.items_per_session || 10;
//add session delay for popup
const orderItems = await fetchOrderData({ session_limit });
orderData = [...orderItems.items];
if (orderData && orderData.length > 0) {
setTimeout(() => {
// filter order data based on popup time settings
let filteredData = orderData.filter((orderItem) => {
if (popup.display?.display_time_limit_unit !== "all") {
return true;
}
if (popup.display?.display_time_limit) {
const now = new Date().getTime(); // current time in milliseconds
const then = orderItem.updated_at ? orderItem.updated_at * 1000 : orderItem.created_at * 1000; // Unix timestamp in milliseconds
const diff = (now - then) / 1000; // difference in seconds
const limit = popup.display?.display_time_limit_unit == "all" ? now / 1000 - 1592200722 : popup.display?.display_time_limit_unit == "days" ? popup.display?.display_time_limit * 24 * 60 * 60 : popup.display?.display_time_limit * 60 * 60;
if (diff < limit) {
return true;
} else {
validateOrdersTimeLimit = false;
}
}
return false;
});
// filter order data based on popup display settings
filteredData =
popup.display.type == "global"
? [...filteredData]
: filteredData.filter((order) => {
return popup.display?.display_products?.some((product) => {
return product.variants.some((variant) => {
return variant.id == order.product.variant_id;
});
});
});
if (filteredData && filteredData.length > 0) {
addPopupHtml({ popup, data: filteredData, type: "orders" });
}
}, parseInt(popup.display.session_delay) * 1000);
}
};
const appendCartPopup = async (popup) => {
const session_limit = popup.display?.items_per_session || 10;
const cartItems = await fetchCartData({ session_limit });
cartData = [...cartItems.items];
//add session delay for popup
setTimeout(() => {
// filter data based on popup display time settings
let filteredData = cartData.filter((cartItem) => {
if (popup.display?.display_time_limit_unit == "all") {
return true;
}
if (popup.display?.display_time_limit) {
const now = new Date().getTime(); // current time in milliseconds
const then = cartItem.updated_at ? cartItem.updated_at * 1000 : cartItem.created_at * 1000; // Unix timestamp in milliseconds
const diff = (now - then) / 1000; // difference in seconds
const limit = popup.display?.display_time_limit_unit == "days" ? popup.display?.display_time_limit * 24 * 60 * 60 : popup.display?.display_time_limit * 60 * 60;
if (diff < limit) {
return true;
} else {
validateCartTimeLimit = false;
}
}
return false;
});
// filter cart data based on popup display products settings
filteredData =
popup.display.type == "global"
? [...filteredData]
: filteredData.filter((cartItem) => {
return popup.display?.display_products?.some((product) => {
return product.variants.some((variant) => {
return variant.id == cartItem.product.variant_id;
});
});
});
if (filteredData && filteredData.length > 0) {
addPopupHtml({ popup, data: filteredData, type: "cart" });
}
}, parseInt(popup.display.session_delay) * 1000);
};
popups.forEach(async (popup) => {
let displayCondition = true;
let geoLocationCondition = true;
// check for the display conditions
if (popup.additional_settings) {
if (popup.additional_settings.exclude_from_pages == "home" && window.location.pathname == "/") {
displayCondition = false;
}
if (popup.additional_settings.exclude_from_pages == "url" && popup.additional_settings.exclude_for_url && window.location.href.split(/[?#]/)[0] == popup.additional_settings.exclude_for_url) {
displayCondition = false;
}
if (popup.additional_settings.exclude_from_pages == "keyword" && popup.additional_settings.exclude_for_keyword?.length > 0) {
popup.additional_settings.exclude_for_keyword.forEach((keyword) => {
if (window.location.href.includes(keyword)) {
displayCondition = false;
}
});
}
if ((!popup.position.desktop.display && window.innerWidth > 768) || (!popup.position.mobile.display && window.innerWidth < 768)) {
displayCondition = false;
}
}
// check if the user satisfies the geo location condition
if (popup.additional_settings?.geo_targeting?.include?.length > 0 || popup.additional_settings?.geo_targeting?.exclude?.length > 0) {
const locationResponse = await fetch("https://ipapi.co/json/");
const { country_code } = await locationResponse.json();
if (popup.additional_settings.geo_targeting.include.length > 0) {
if (!popup.additional_settings.geo_targeting.include.find((country) => country.code == country_code)) {
geoLocationCondition = false;
}
}
if (popup.additional_settings.geo_targeting.exclude.length > 0) {
if (popup.additional_settings.geo_targeting.exclude.find((country) => country.code == country_code)) {
geoLocationCondition = false;
}
}
}
if (displayCondition && geoLocationCondition) {
if (popup.type == "cart") {
appendCartPopup(popup);
} else if (popup.type == "sales") {
appendSalesPopup(popup);
}
}
});
};
// function to display widgets
const displayWidgets = (widgets) => {
if (widgets && widgets.length > 0) {
widgets.forEach(async (widget) => {
let displayCondition = true;
let geoLocationCondition = true;
if (widget?.inventory && parseInt(widget?.inventory.available_inventory) < 1) {
widget.inventory.available_inventory = 0;
}
if (widget?.inventory && parseInt(widget?.inventory.sold_inventory) < 1) {
widget.inventory.sold_inventory = 0;
}
if (widget.additional_settings.display_lower_limit && widget.inventory.available_inventory > widget.additional_settings.display_lower_limit) {
// check if the widgets satisfies minimum availaible quantity limit
displayCondition = false;
}
// check if the user satisfies the geo location condition
if (widget.additional_settings?.geo_targeting?.include?.length > 0 || widget.additional_settings?.geo_targeting?.exclude?.length > 0) {
const locationResponse = await fetch("https://ipapi.co/json/");
const { country_code } = await locationResponse.json();
if (widget.additional_settings.geo_targeting.include.length > 0) {
if (!widget.additional_settings.geo_targeting.include.find((country) => country.code == country_code)) {
geoLocationCondition = false;
}
}
if (widget.additional_settings.geo_targeting.exclude.length > 0) {
if (widget.additional_settings.geo_targeting.exclude.find((country) => country.code == country_code)) {
geoLocationCondition = false;
}
}
}
if (displayCondition && geoLocationCondition) {
if (widget.design.text.stock_text.font_url) {
appendFont(widget.design.text.stock_text.font_url);
}
if (widget.design.text.sold_text.font_url && widget.design.text.sold_text.font_url !== widget.design.text.stock_text.font_url) {
appendFont(widget.design.text.sold_text.font_url);
}
const widgetHTML = widget.design.progress_bar.layout === "text_above" ? Text_Above({ widget }) : widget.design.progress_bar.layout === "text_below" ? Text_Below({ widget }) : widget.design.progress_bar.layout === "text_below_above" ? Text_Below_Above({ widget }) : widget.design.progress_bar.layout === "text_above_below" ? Text_Above_Below({ widget }) : widget.design.progress_bar.layout === "text_above_above" ? Text_Above_Above({ widget }) : widget.design.progress_bar.layout === "text_below_below" ? Text_Below_Below({ widget }) : null;
const selector = document.querySelector(widget.position.selector.replace(/\s+/g, ""));
const WidgetWrapper = document.querySelector(".sold_mainbox") || document.createElement("div");
if (!WidgetWrapper.classList.contains("sold_mainbox")) {
WidgetWrapper.classList.add(["sold_mainbox"]);
}
WidgetWrapper.innerHTML = widgetHTML;
if (selector && widget.position.position === "above") {
selector.insertAdjacentElement("beforebegin", WidgetWrapper);
}
if (selector && widget.position.position === "below") {
selector.insertAdjacentElement("afterend", WidgetWrapper);
}
if (selector && widget.position.position === "inside-top") {
selector.insertAdjacentElement("afterbegin", WidgetWrapper);
}
if (selector && widget.position.position === "inside-bottom") {
selector.insertAdjacentElement("beforeend", WidgetWrapper);
}
}
});
}
};
// Fetch popups from the app //
const fetchItems = async () => {
// fetch popups
const popups = [];
const getPopups = async () => {
try {
const response = await fetch(`/apps/stocksheep/shop-requests/getPopups`, {
method: "GET",
});
const data = await response.json();
if (data && data.length > 0) {
data.forEach((popup) => {
popups.push(popup);
});
}
} catch (err) {}
};
// fetch widgets
const widgets = [];
let pageId;
let product_handle;
if (window.location.href.includes("products") && window.location.href.split("/").findIndex((e) => e.includes("products")) <= window.location.href.split("/").length - 1) {
if (window.location.href.split("variant=")[1]) {
pageId = `VARIANT#${
window.location.href
.split("?")[1]
?.split("&")
?.find((e) => e.includes("variant"))
?.split("variant=")[1]
}`;
product_handle = window.location.pathname.split("/")[window.location.pathname.split("/").length - 1];
} else {
try {
const response = await fetch(window.location.pathname + ".js", {
method: "GET",
headers: {
"Content-type": "application/json",
},
});
const data = await response.json();
product_handle = data.handle;
if (data?.variants?.length >= 1) {
pageId = `VARIANT#${data.variants.find((variant) => variant.available) ? data.variants.find((variant) => variant.available).id : data.variants[0].id}`;
}
} catch (error) {}
}
}
const getWidgets = async (pageId, product_handle) => {
if (pageId && product_handle) {
try {
const response = await fetch(`/apps/stocksheep/shop-requests/getWidgets`, {
method: "POST",
headers: {
"Content-type": "application/json",
},
body: JSON.stringify({
pageId,
product_handle,
}),
});
const data = await response.json();
if (data) {
data.forEach((widget) => {
widgets.push(widget);
});
}
} catch (err) {}
}
};
let commands = [getPopups()];
if (pageId) {
commands.push(getWidgets(pageId, product_handle));
}
await Promise.all(commands);
if (popups && popups.length > 0) {
displayPopup(popups);
}
if (widgets && widgets.length > 0) {
displayWidgets(widgets);
}
};
fetchItems();
// fetch widgets on variant change
const refreshWidget = async () => {
const widgets = [];
if (window.location.href.includes("products") && window.location.href.split("/").findIndex((e) => e.includes("products")) <= window.location.href.split("/").length - 1) {
let pageId;
let product_handle;
if (window.location.href.split("variant=")[1]) {
pageId = `VARIANT#${
window.location.href
.split("?")[1]
?.split("&")
?.find((e) => e.includes("variant"))
?.split("variant=")[1]
}`;
product_handle = window.location.pathname.split("/")[window.location.pathname.split("/").length - 1];
} else {
try {
const response = await fetch(window.location.pathname + ".js", {
method: "GET",
headers: {
"Content-type": "application/json",
},
});
const data = await response.json();
product_handle = data.handle;
if (data?.variants?.length >= 1) {
pageId = `VARIANT#${data.variants.find((variant) => variant.available) ? data.variants.find((variant) => variant.available).id : data.variants[0].id}`;
}
} catch (error) {}
}
try {
const response = await fetch(`/apps/stocksheep/shop-requests/getWidgets`, {
method: "POST",
headers: {
"Content-type": "application/json",
},
body: JSON.stringify({
pageId,
product_handle,
}),
});
const data = await response.json();
if (data) {
data.forEach((widget) => {
widgets.push(widget);
});
}
if (widgets && widgets.length > 0) {
displayWidgets(widgets);
}
} catch (err) {}
}
};
const variantSwitchSelector = [".single-option-selector.product-form__input", "stocksheep_variant_switcher"];
let variantSwitcher;
for (const selector of variantSwitchSelector) {
if (document.querySelector(selector)) {
variantSwitcher = document.querySelector(selector);
break;
}
}
if (variantSwitcher) {
variantSwitcher.addEventListener("change", () => {
refreshWidget();
});
} else {
let previousUrl = "";
const observer = new MutationObserver(function (mutations) {
if (window.location.href !== previousUrl) {
previousUrl = window.location.href;
refreshWidget();
}
});
const config = { subtree: true, childList: true };
// start listening to changes
observer.observe(document, config);
}