Add app.js
Quick View logic is here
This commit is contained in:
622
app.js
Normal file
622
app.js
Normal file
@@ -0,0 +1,622 @@
|
||||
class ProductQuickView {
|
||||
constructor() {
|
||||
this.product = null;
|
||||
this.size = null;
|
||||
this.idmProductId = null;
|
||||
this.basketSubmitted = false;
|
||||
this.htmlQuickView = '';
|
||||
this.htmlEl = document.querySelector('html');
|
||||
}
|
||||
|
||||
// Escapes HTML to prevent XSS in outputs
|
||||
escapeHtml(v = '') {
|
||||
return String(v)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, '');
|
||||
}
|
||||
|
||||
// Main listener to set up product quick view and handlers after DOM load
|
||||
init = () => {
|
||||
const self = this;
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
const productImgLinks = document.querySelectorAll('#search .product');
|
||||
const prodInfoContainer = document.querySelector('.prod-info__container');
|
||||
const asideProdMenu = document.querySelector('.right-aside');
|
||||
const customAdded = document.getElementById('quick_view_basket');
|
||||
productImgLinks.forEach(prod => this.initProductCard(prod, prodInfoContainer, asideProdMenu, customAdded));
|
||||
this.renderProduct(asideProdMenu, prodInfoContainer, customAdded);
|
||||
});
|
||||
}
|
||||
|
||||
// Add quick view card buttons, setup close listeners
|
||||
initProductCard(prod, prodInfoContainer, asideProdMenu, customAdded) {
|
||||
const cardPictureContainer = document.createElement('div');
|
||||
if (!prod.querySelector('.hover-btn') && !prod.querySelector('.card__picture-container')) {
|
||||
cardPictureContainer.className = 'card__picture-container';
|
||||
prod.insertAdjacentElement('afterbegin', cardPictureContainer);
|
||||
|
||||
const productContentWrapper = prod.querySelector('.product__content_wrapper');
|
||||
const closeRightAsideBtn = document.querySelector('.right-aside__close');
|
||||
const closeRightAsideBlur = document.querySelector('.right-aside__bg-blur');
|
||||
const continueShopping = document.querySelector('.added__button.--close');
|
||||
|
||||
continueShopping.addEventListener('click', () => this.closeRightAside(prodInfoContainer, asideProdMenu));
|
||||
closeRightAsideBtn.addEventListener("click", () => this.closeRightAside(prodInfoContainer, asideProdMenu));
|
||||
closeRightAsideBlur.addEventListener("click", () => this.closeRightAside(prodInfoContainer, asideProdMenu));
|
||||
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'hover-btn';
|
||||
btn.textContent = 'CHOOSE OPTION';
|
||||
prod.insertAdjacentElement('afterbegin', btn);
|
||||
|
||||
// Add button for mobile card
|
||||
if (productContentWrapper && productContentWrapper.parentElement) {
|
||||
const mobileBtn = btn.cloneNode(true);
|
||||
productContentWrapper.insertAdjacentElement('afterend', mobileBtn);
|
||||
}
|
||||
}
|
||||
// Attach anchors and buttons to quick view container
|
||||
const link = prod.querySelector('a');
|
||||
const button = prod.querySelector('button');
|
||||
if (link) cardPictureContainer.appendChild(link);
|
||||
if (button) cardPictureContainer.appendChild(button);
|
||||
}
|
||||
|
||||
// Animate/Close quick view sidebar, handle basket state
|
||||
closeRightAside(prodInfoContainer, asideProdMenu) {
|
||||
asideProdMenu.classList.remove('active');
|
||||
setTimeout(() => {
|
||||
this.htmlEl.classList.remove('activeAside');
|
||||
}, 300);
|
||||
if (this.basketSubmitted) {
|
||||
const rightAsideProdInfo = document.querySelector('.right-aside__prod-info');
|
||||
const navRightAside = document.querySelector('.right-aside__nav');
|
||||
const customAdded = document.getElementById('quick_view_basket');
|
||||
rightAsideProdInfo.style.position = 'fixed';
|
||||
navRightAside.style.position = 'static';
|
||||
this.basketSubmitted = false;
|
||||
prodInfoContainer.classList.remove("hidden");
|
||||
customAdded.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
// Set up main product quick view handler on search area
|
||||
renderProduct(asideProdMenu, prodInfoContainer, customAdded) {
|
||||
document.getElementById("search")?.addEventListener("click", async (e) => {
|
||||
const hoverBtn = e.target.closest(".hover-btn");
|
||||
if (!hoverBtn) return;
|
||||
e.stopPropagation();
|
||||
this.htmlEl.classList.add("activeAside");
|
||||
asideProdMenu.classList.add("active");
|
||||
if (prodInfoContainer.hasChildNodes()) {
|
||||
prodInfoContainer.innerHTML = "";
|
||||
}
|
||||
try {
|
||||
this.idmProductId = Number(e.target.closest(".product").dataset.product_id);
|
||||
await this.fetchQuickViewProduct(this.idmProductId, prodInfoContainer, customAdded);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
// Wait for dynamic HTML elements before binding option listeners
|
||||
const interval2 = setInterval(() => {
|
||||
const sizesContainer = document.querySelector('.idm-sizes');
|
||||
const versionsContainer = document.querySelector('.idm-versions');
|
||||
if (sizesContainer && versionsContainer) {
|
||||
clearInterval(interval2);
|
||||
this.bindOption(prodInfoContainer, sizesContainer, 'currentSize', this.product.sizes, 'currentSize', 'size-choose', 'size', true);
|
||||
this.bindOption(prodInfoContainer, versionsContainer, 'currentVersion', this.product.group.versions, 'currentVersion', 'version-choose', 'product', false);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch product data and build modal quick view HTML
|
||||
async fetchQuickViewProduct(idmProductId, prodInfoContainer, customAdded){
|
||||
const query = `
|
||||
query {
|
||||
product(productId: ${idmProductId}) {
|
||||
product{
|
||||
id
|
||||
name
|
||||
link
|
||||
icon
|
||||
enclosuresImages{ url }
|
||||
producer { id name link }
|
||||
sizes {
|
||||
id name code amount
|
||||
price {
|
||||
omnibusPrice {gross {formatted}}
|
||||
crossedPrice {gross {formatted}}
|
||||
beforeRebate {gross {formatted}}
|
||||
beforeRebateDetails {youSavePercent}
|
||||
price {gross {formatted}}
|
||||
}
|
||||
availability{description}
|
||||
}
|
||||
group{
|
||||
id
|
||||
versions{
|
||||
id name productIcon link iconSmall
|
||||
}
|
||||
}
|
||||
price {
|
||||
price { gross { formatted } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const response = await fetch("/graphql/v1/", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ query }),
|
||||
});
|
||||
const data = await response.json();
|
||||
this.product = data.data.product.product;
|
||||
this.size = this.product?.sizes?.[0];
|
||||
prodInfoContainer.innerHTML = "";
|
||||
this.htmlQuickView = `
|
||||
<div class="img__container">
|
||||
<img src="${
|
||||
this.product?.enclosuresImages?.[0]?.url
|
||||
? this.product.enclosuresImages[0].url
|
||||
: this.product.icon
|
||||
}" />
|
||||
</div>
|
||||
<div class="product-info__part">
|
||||
<div class="names-info">
|
||||
<p class="producer-name">${this.product.producer.name}</p>
|
||||
<h5 class="product-name">${this.product.name}</h5>
|
||||
</div>
|
||||
<div class="price-info">
|
||||
<div class="price__container">
|
||||
<h5 class="idm__quick-view-price">
|
||||
${this.product?.sizes?.[0]?.price?.price?.gross?.formatted}
|
||||
${
|
||||
this.product?.sizes?.[0]?.price?.crossedPrice?.gross?.formatted
|
||||
? `<span class="price-crossed">${this.product?.sizes?.[0]?.price?.crossedPrice?.gross?.formatted}</span>`
|
||||
: ""
|
||||
}
|
||||
<span class="price-note">brutto / szt</span>
|
||||
</h5>
|
||||
<span class="price-omnibus">
|
||||
${this.product?.sizes?.[0]?.price?.omnibusPrice?.gross?.formatted
|
||||
? `Najniższa cena z 30 dni przed obniżką: ${this.product?.sizes?.[0]?.price?.omnibusPrice?.gross?.formatted}`
|
||||
: ""}
|
||||
</span>
|
||||
</div>
|
||||
<div class="availability__container">
|
||||
${this.size.amount > 3 ?
|
||||
`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="10" fill="#C9EAD4"/>
|
||||
<circle cx="12" cy="12" r="8" fill="#10A443"/>
|
||||
</svg>
|
||||
`
|
||||
: this.size.amount > 0 ?
|
||||
`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="10" fill="#FFE8BF"/>
|
||||
<circle cx="12" cy="12" r="8" fill="#D28C13"/>
|
||||
</svg>
|
||||
`
|
||||
: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="10" fill="#FFBEC7"/>
|
||||
<circle cx="12" cy="12" r="8" fill="#ED001F"/>
|
||||
</svg>
|
||||
`
|
||||
}
|
||||
<p class="availability">
|
||||
${this.product?.sizes?.[0]?.availability?.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="all-sizes__container">
|
||||
<div class="idm-sizes__container">
|
||||
${
|
||||
this.product.sizes && this.product.sizes.length > 1
|
||||
? `<input type="hidden" name="currentSize" value="">
|
||||
<p>Size: <span class="size-choose">${this.product.sizes[0].name}</span></p>` +
|
||||
'<div class="idm-sizes">' +
|
||||
this.product.sizes
|
||||
.filter((s) => s.id !== "uniw")
|
||||
.map((s, i) =>
|
||||
`<button data-id="${s.id}" class="product-size${i === 0 ? ' active' : ''}">${s.name}</button>`
|
||||
)
|
||||
.join("") +
|
||||
'</div>'
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div class="idm-versions__container">
|
||||
${
|
||||
this.product.group.versions && this.product.group.versions.length > 1
|
||||
? `<input type="hidden" name="currentVersion" value="">
|
||||
<p>Type: <span class="version-choose">${this.product.group.versions.filter((s)=>s.id === this.product.id)?.[0]?.name}</span></p>` +
|
||||
'<div class="idm-versions">' +
|
||||
this.product.group.versions
|
||||
.filter((s) => s.id !== "uniw")
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((s, i) =>
|
||||
`<button data-id="${s.id}" class="product-version${this.product.id === s.id ? ' active' : ''}"><img src="${s.iconSmall}"></button>`
|
||||
)
|
||||
.join("") +
|
||||
'</div>'
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class='idm-products-banner__add-to-basket'>
|
||||
${this.idmAddToBasket(this.product, this.size)}
|
||||
</div>
|
||||
<div class="view-details__container">
|
||||
<a class="view-details" href="${this.product.link}">View Details</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
prodInfoContainer.insertAdjacentHTML("afterbegin", this.htmlQuickView);
|
||||
// Button state may depend on async HTML render - poll for element
|
||||
// const interval2 = setInterval(() => {
|
||||
// const addToBasketButton = document.querySelector(
|
||||
// ".btn.--solid.--medium.idm-products-banner__add-to-basket-button"
|
||||
// );
|
||||
// if (addToBasketButton) {
|
||||
// clearInterval(interval2);
|
||||
// if (!this.size.amount) {
|
||||
// addToBasketButton.classList.add("inactive");
|
||||
// } else {
|
||||
// addToBasketButton.classList.remove("inactive");
|
||||
// }
|
||||
// }
|
||||
// }, 100);
|
||||
if (!this.product) throw new Error("err");
|
||||
return this.product;
|
||||
}
|
||||
// Generic binding for size/version choices, handles UI and fetches updates as needed
|
||||
bindOption(prodInfoContainer, container, inputName, collection, productProp, labelClass, basketHiddenInput, updatePrice = false) {
|
||||
prodInfoContainer.addEventListener('click', async (e) => {
|
||||
const itemEl = e.target.closest('button');
|
||||
if (!itemEl || !container.contains(itemEl)) return;
|
||||
container.querySelectorAll('button.active').forEach(btn => btn.classList.remove('active'));
|
||||
itemEl.classList.add('active');
|
||||
const itemId = itemEl.dataset.id;
|
||||
let itemObj = collection.find(item => String(item.id) === String(itemId));
|
||||
this.product[productProp] = itemObj;
|
||||
const labelEl = container.closest('.product-info__part').querySelector(`.${labelClass}`);
|
||||
if (labelEl) labelEl.textContent = itemEl.textContent || itemObj.name;
|
||||
const hiddenInput = container.closest('.product-info__part')?.querySelector(`input[name="${inputName}"]`);
|
||||
if (hiddenInput) hiddenInput.value = this.escapeHtml(itemObj.id);
|
||||
const hiddenBasketInputEl = document.querySelector('.idm-products-banner__add-to-basket-form')
|
||||
?.querySelector(`input[name="${basketHiddenInput}"]`);
|
||||
if (hiddenBasketInputEl) hiddenBasketInputEl.value = this.escapeHtml(itemObj.id);
|
||||
const quickViewPriceEl = container.closest('.product-info__part').querySelector('.idm__quick-view-price');
|
||||
const quickViewOmnibusEl = container.closest('.product-info__part').querySelector('.price-omnibus');
|
||||
if (!updatePrice) {
|
||||
// When switching product version, fetch new product data and re-bind
|
||||
const idmProductId = Number(itemId);
|
||||
const newProduct = await this.fetchQuickViewProduct(idmProductId, prodInfoContainer);
|
||||
this.bindOption(
|
||||
prodInfoContainer,
|
||||
prodInfoContainer.querySelector('.idm-versions'),
|
||||
'currentVersion',
|
||||
newProduct.group.versions,
|
||||
'currentVersion',
|
||||
'version-choose',
|
||||
'product',
|
||||
false
|
||||
);
|
||||
this.bindOption(
|
||||
prodInfoContainer,
|
||||
prodInfoContainer.querySelector('.idm-sizes'),
|
||||
'currentSize',
|
||||
newProduct.sizes,
|
||||
'currentSize',
|
||||
'size-choose',
|
||||
'size',
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fast price info update just for size pick
|
||||
if (updatePrice && itemObj.price && quickViewPriceEl && quickViewOmnibusEl) {
|
||||
const crossedPriceVal = itemObj.price.crossedPrice?.gross?.formatted;
|
||||
const regularPriceVal = itemObj.price.price?.gross?.formatted;
|
||||
const omnibusPriceVal = itemObj.price.omnibusPrice?.gross?.formatted;
|
||||
const wrapper = document.querySelector('.idm-products-banner__qty');
|
||||
this.size = itemObj
|
||||
this.idmAddToBasket(this.product, this.size)
|
||||
this.amountToBasket(wrapper)
|
||||
quickViewPriceEl.innerHTML = `
|
||||
${regularPriceVal}
|
||||
${crossedPriceVal ? `<span class="price-crossed">${crossedPriceVal}</span> ` : ""}
|
||||
<span class="price-note">brutto / szt</span>
|
||||
`;
|
||||
quickViewOmnibusEl.innerHTML = omnibusPriceVal
|
||||
? `Najniższa cena z 30 dni przed obniżką: ${omnibusPriceVal}`
|
||||
: "";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handles add-to-basket GraphQL mutation and in-app UI transitions
|
||||
addToBasketQuery() {
|
||||
const addToBasketButton = document.querySelector(
|
||||
".btn.--solid.--medium.idm-products-banner__add-to-basket-button"
|
||||
);
|
||||
const customAdded = document.getElementById('quick_view_basket');
|
||||
const prodInfoContainer = document.querySelector('.prod-info__container');
|
||||
const addToBasketForm = document.querySelector(".idm-products-banner__add-to-basket-form");
|
||||
addToBasketForm.addEventListener("submit", async (el) => {
|
||||
el.preventDefault();
|
||||
const qtyElement = document.querySelector('.idm-products-banner__qty-input');
|
||||
const sellBy = qtyElement.value;
|
||||
try {
|
||||
const query = `
|
||||
mutation {
|
||||
addProductsToBasket(ProductInput: {
|
||||
id: ${Number(this.product.id)},
|
||||
size: "${this.size.id}",
|
||||
quantity: ${Number(sellBy)}
|
||||
}) {
|
||||
status
|
||||
}
|
||||
}
|
||||
`;
|
||||
const res = await fetch("/graphql/v1/", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ query }),
|
||||
});
|
||||
if (!res.ok && res.status !== 301 && res.status !== 302) {
|
||||
throw new Error(`Błąd HTTP: ${res.status}`);
|
||||
}
|
||||
const result = await res.json();
|
||||
const status = result?.data?.addProductsToBasket?.status;
|
||||
if (status && (status.toLowerCase() === 'success' || status === 'SUCCESS')) {
|
||||
this.updateBasketAfterAdd();
|
||||
} else {
|
||||
throw new Error(`Basket update failed — status = ${status}`);
|
||||
}
|
||||
await this.renderCartItemsJS();
|
||||
} catch (error) {
|
||||
if (typeof Alertek !== 'undefined') {
|
||||
Alertek.show_alert("Coś poszło nie tak");
|
||||
} else {
|
||||
alert("Coś poszło nie tak");
|
||||
}
|
||||
}
|
||||
|
||||
// Hide quickview and show confirmation after adding
|
||||
const rightAsideProdInfo = document.querySelector('.right-aside__prod-info');
|
||||
const navRightAside = document.querySelector('.right-aside__nav');
|
||||
navRightAside.style.position = 'absolute';
|
||||
prodInfoContainer.classList.add("hidden");
|
||||
customAdded.classList.remove("hidden");
|
||||
this.basketSubmitted = true;
|
||||
await this.getCartItems();
|
||||
return true;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
updateBasketAfterAdd() {
|
||||
if (typeof menu_basket_cache === 'function') {
|
||||
menu_basket_cache(false);
|
||||
} else {
|
||||
console.warn("menu_basket_cache is not defined.");
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamically render add-to-basket UI form and connect handlers
|
||||
idmAddToBasket(product, size) {
|
||||
const getElAmmountInCart = app_shop.vars.basket;
|
||||
|
||||
const getThisProduct = getElAmmountInCart.products.filter(
|
||||
(s) => s.id === String(this.product.id) && s.size === String(this.size.id)
|
||||
);
|
||||
|
||||
|
||||
const sellBy = size?.unitSellby || 1;
|
||||
const precision = size?.unitPrecision || 0;
|
||||
let max = (typeof size?.amount === 'number' && size.amount > 0) ? size.amount : '';
|
||||
const amountInCart = getThisProduct?.[0]?.count ? getThisProduct[0].count : 0;
|
||||
|
||||
let inactive = ""
|
||||
if (amountInCart && amountInCart >= max){
|
||||
inactive = "inactive";
|
||||
max = '';
|
||||
}else if(amountInCart){
|
||||
max -= amountInCart;
|
||||
}
|
||||
|
||||
console.log("getThisProduct", size.amount, "max", max)
|
||||
|
||||
const buttonsToRender = `
|
||||
<form class='idm-products-banner__add-to-basket-form' method='post' action="/basketchange.php">
|
||||
<input type='hidden' name='mode' value='1'>
|
||||
<input type='hidden' name='product' value=${product.id}>
|
||||
<input type='hidden' name='size' value=${size.id}>
|
||||
<div class='idm-products-banner__qty'
|
||||
data-sell-by='${this.escapeHtml(String(sellBy))}'
|
||||
data-precision='${this.escapeHtml(String(precision))}'
|
||||
data-max='${this.escapeHtml(String(max))}'>
|
||||
<button type='button' class='idm-products-banner__qty-decrease'>−</button>
|
||||
<input type='number'
|
||||
name='number'
|
||||
class='idm-products-banner__qty-input'
|
||||
value='${this.escapeHtml(String(sellBy))}'
|
||||
step='${this.escapeHtml(String(sellBy))}'
|
||||
min='${this.escapeHtml(String(sellBy))}'
|
||||
max='${this.escapeHtml(String(max))}'>
|
||||
<button type='button' class='idm-products-banner__qty-increase'>+</button>
|
||||
</div>
|
||||
<button type='submit' class='btn --solid --medium idm-products-banner__add-to-basket-button ${inactive}'>
|
||||
<span>Do koszyka</span>
|
||||
</button>
|
||||
</form>`;
|
||||
|
||||
// Wait for container before rendering/attaching qty logic
|
||||
const interval = setInterval(() => {
|
||||
const container = document.querySelector('.idm-products-banner__add-to-basket');
|
||||
if (container) {
|
||||
clearInterval(interval);
|
||||
container.innerHTML = buttonsToRender;
|
||||
this.addToBasketQuery();
|
||||
}
|
||||
}, 100);
|
||||
const interval2 = setInterval(() => {
|
||||
const wrapper = document.querySelector('.idm-products-banner__qty');
|
||||
if (wrapper) {
|
||||
clearInterval(interval2);
|
||||
this.amountToBasket(wrapper, max);
|
||||
}
|
||||
}, 100);
|
||||
return '';
|
||||
}
|
||||
|
||||
// Quantity +/- and validation for add to basket
|
||||
amountToBasket(wrapper, max){
|
||||
wrapper.addEventListener('click', async (e) => {
|
||||
if (!wrapper) return;
|
||||
|
||||
const input = wrapper.querySelector('.idm-products-banner__qty-input');
|
||||
const step = parseFloat(wrapper.dataset.sellBy || '1');
|
||||
const precision = parseInt(wrapper.dataset.precision || '0');
|
||||
max = parseFloat(max || '999999');
|
||||
|
||||
let current = parseFloat(input.value) || 0;
|
||||
if (e.target.classList.contains('idm-products-banner__qty-increase')) {
|
||||
current += step;
|
||||
if (current > max) current = max;
|
||||
} else if (e.target.classList.contains('idm-products-banner__qty-decrease')) {
|
||||
current -= step;
|
||||
if (current < step) current = step;
|
||||
}
|
||||
input.value = current.toFixed(precision);
|
||||
});
|
||||
document.addEventListener('blur', (e) => {
|
||||
if (!e.target.classList.contains('idm-products-banner__qty-input')) return;
|
||||
const input = e.target;
|
||||
const wrapper = input.closest('.idm-products-banner__qty');
|
||||
const step = parseFloat(wrapper.dataset.sellBy || '1');
|
||||
const precision = parseInt(wrapper.dataset.precision || '0');
|
||||
const max = parseFloat(wrapper.dataset.max || '999999');
|
||||
let val = parseFloat(input.value);
|
||||
if (isNaN(val) || val < step) {
|
||||
val = step;
|
||||
} else if (val > max) {
|
||||
val = max;
|
||||
} else {
|
||||
val = Math.round(val / step) * step;
|
||||
}
|
||||
input.value = val.toFixed(precision);
|
||||
}, true);
|
||||
}
|
||||
|
||||
// Returns after basket global variable is updated, for UI sync
|
||||
getCartItems() {
|
||||
const cartItems = app_shop.vars.basket;
|
||||
return new Promise((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
const newCartItems = app_shop.vars.basket;
|
||||
if (cartItems.productsNumber !== newCartItems.productsNumber) {
|
||||
clearInterval(interval)
|
||||
resolve(newCartItems);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
// Rebuilds basket preview after add
|
||||
async renderCartItemsJS() {
|
||||
const container = document.getElementById('cartItemsContainer');
|
||||
container.innerHTML = '';
|
||||
const products = (await this.getCartItems()).products;
|
||||
|
||||
products.forEach(product => {
|
||||
const {
|
||||
id,
|
||||
link,
|
||||
name,
|
||||
icon,
|
||||
size_name,
|
||||
count,
|
||||
price_unit,
|
||||
prices
|
||||
} = product;
|
||||
const block = document.createElement('div');
|
||||
block.className = 'added__block';
|
||||
block.innerHTML = `
|
||||
<div class="added__product product">
|
||||
<a class="added__icon product__icon m-0 d-flex justify-content-center align-items-center"
|
||||
data-product-id="${id}" href="${link}" title="${name}">
|
||||
<img class="b-lazy" src="${icon}" alt="${name}">
|
||||
</a>
|
||||
<div class="added__info">
|
||||
<h3 class="added__name_h3 d-flex">
|
||||
<a class="added__name" href="${link}" title="${name}">
|
||||
${name}
|
||||
</a>
|
||||
</h3>
|
||||
<div class="added__prices">
|
||||
<div class="added__price_single">
|
||||
${Number(prices.gros).toFixed(2).replace('.', ',')} zł
|
||||
<span class="added__price_sellby">/ ${price_unit}</span>
|
||||
</div>
|
||||
<strong class="price">
|
||||
<span class="price__unit">
|
||||
${Number(prices.worth_gros).toFixed(2).replace('.', ',')} zł
|
||||
</span>
|
||||
<span class="price_vat">brutto</span>
|
||||
</strong>
|
||||
</div>
|
||||
<ul class="added__params">
|
||||
<li class="added__params_number">Ilość: ${count}</li>
|
||||
${size_name && size_name !== 'UNIWERSALNY' ? `
|
||||
<li class="added__params_size">Rozmiar: ${size_name}</li>
|
||||
` : ''}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
this.updateShippingProgressBar()
|
||||
container.appendChild(block);
|
||||
});
|
||||
}
|
||||
|
||||
// Updates shipping progress bar UI according to basket total
|
||||
updateShippingProgressBar() {
|
||||
const basket = app_shop.vars.basket;
|
||||
if (!basket || !basket.shippingLimitFree) return;
|
||||
const totalWorth = parseFloat(basket.worth || 0);
|
||||
const shippingLimit = parseFloat(basket.shippingLimitFree || 0);
|
||||
const toShippingFree = parseFloat(basket.toShippingFree || 0);
|
||||
const toShippingFreeFormatted = basket.toShippingFree_formatted || "0,00 zł";
|
||||
const barEl = document.getElementById("shipping-bar");
|
||||
const barCompletion = document.getElementById("shipping-bar-completion");
|
||||
const amountEl = document.getElementById("shipping-left-amount");
|
||||
const freeEl = document.getElementById("shipping-bar-free");
|
||||
const notFreeText = document.getElementById("shipping-text-not-free");
|
||||
if (shippingLimit > 0 && toShippingFree > 0) {
|
||||
const percentage = Math.min(
|
||||
100,
|
||||
((shippingLimit - toShippingFree) / shippingLimit) * 100
|
||||
);
|
||||
barCompletion.style.width = percentage + "%";
|
||||
amountEl.textContent = toShippingFreeFormatted;
|
||||
barEl.style.display = "block";
|
||||
freeEl.style.display = "none";
|
||||
} else {
|
||||
const shippingfreeBar = document.getElementById('shipping-bar-free').querySelector('.added__shippingfree_bar');
|
||||
shippingfreeBar.classList.add('enough');
|
||||
barEl.style.display = "none";
|
||||
freeEl.style.display = "block";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize quick view system
|
||||
const quickView = new ProductQuickView();
|
||||
quickView.init();
|
||||
Reference in New Issue
Block a user