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, ''); } // 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 = `

${this.product.producer.name}

${this.product.name}
${this.product?.sizes?.[0]?.price?.price?.gross?.formatted} ${ this.product?.sizes?.[0]?.price?.crossedPrice?.gross?.formatted ? `${this.product?.sizes?.[0]?.price?.crossedPrice?.gross?.formatted}` : "" } brutto / szt
${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}` : ""}
${this.size.amount > 3 ? ` ` : this.size.amount > 0 ? ` ` : ` ` }

${this.product?.sizes?.[0]?.availability?.description}

${ this.product.sizes && this.product.sizes.length > 1 ? `

Size: ${this.product.sizes[0].name}

` + '
' + this.product.sizes .filter((s) => s.id !== "uniw") .map((s, i) => `` ) .join("") + '
' : "" }
${ this.product.group.versions && this.product.group.versions.length > 1 ? `

Type: ${this.product.group.versions.filter((s)=>s.id === this.product.id)?.[0]?.name}

` + '
' + this.product.group.versions .filter((s) => s.id !== "uniw") .sort((a, b) => a.name.localeCompare(b.name)) .map((s, i) => `` ) .join("") + '
' : "" }
${this.idmAddToBasket(this.product, this.size)}
View Details
`; 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 ? `${crossedPriceVal} ` : ""} brutto / szt `; 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 = `
`; // 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 = `
${name}

${name}

${Number(prices.gros).toFixed(2).replace('.', ',')} zł / ${price_unit}
${Number(prices.worth_gros).toFixed(2).replace('.', ',')} zł brutto
`; 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();