From 9396ae4e02473b45a2ec75db52813ce9bfc82c8b Mon Sep 17 00:00:00 2001 From: antonin gallon Date: Tue, 20 Jan 2026 12:03:06 +0100 Subject: [PATCH 1/3] Fix Shopify API connection on local Windows dev server Add SSL_VERIFYPEER option to cURL to fix HTTP 0 errors when running with PHP built-in server on Windows. Co-Authored-By: Claude Opus 4.5 --- site/config/shopify.php | 1 + 1 file changed, 1 insertion(+) diff --git a/site/config/shopify.php b/site/config/shopify.php index d111b49..cbd0d2e 100644 --- a/site/config/shopify.php +++ b/site/config/shopify.php @@ -43,6 +43,7 @@ function fetchShopifyProducts(): array $ch = curl_init($endpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Nécessaire pour dev local sur Windows curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'X-Shopify-Storefront-Access-Token: ' . $token From aa873e117f83e2b9dc82541e8c2dc84a727f513e Mon Sep 17 00:00:00 2001 From: isUnknown Date: Wed, 21 Jan 2026 11:06:28 +0100 Subject: [PATCH 2/3] Fix cart initialization and ShopifyCart loading - Wait for ShopifyCart to be available before initializing structured data - Add getCart() method to retrieve existing cart from Shopify API - Load cart state on page load to display correct initial cart contents Co-Authored-By: Claude Sonnet 4.5 --- assets/js/cart-drawer.js | 15 +++++++ assets/js/shopify-cart.js | 52 +++++++++++++++++++++++ site/snippets/structured-data-product.php | 22 +++++++--- 3 files changed, 83 insertions(+), 6 deletions(-) diff --git a/assets/js/cart-drawer.js b/assets/js/cart-drawer.js index 95972c1..439c3d1 100644 --- a/assets/js/cart-drawer.js +++ b/assets/js/cart-drawer.js @@ -32,6 +32,9 @@ // Initialize event listeners setupEventListeners(); + + // Load initial cart state + loadCart(); } function setupEventListeners() { @@ -60,6 +63,18 @@ }); } + async function loadCart() { + if (!cartInstance) return; + + try { + const cart = await cartInstance.getCart(); + currentCart = cart; + renderCart(); + } catch (error) { + console.error('Error loading cart:', error); + } + } + function openDrawer() { drawer.classList.add('is-open'); document.body.style.overflow = 'hidden'; diff --git a/assets/js/shopify-cart.js b/assets/js/shopify-cart.js index 18b1338..396ae38 100644 --- a/assets/js/shopify-cart.js +++ b/assets/js/shopify-cart.js @@ -306,6 +306,58 @@ class ShopifyCart { return cart; } + /** + * Get existing cart by ID + */ + async getCart() { + if (!this.cartId) { + return null; + } + + const query = ` + query getCart($cartId: ID!) { + cart(id: $cartId) { + id + checkoutUrl + lines(first: 10) { + edges { + node { + id + quantity + merchandise { + ... on ProductVariant { + id + title + price { + amount + currencyCode + } + product { + title + } + } + } + } + } + } + } + } + `; + + try { + const data = await this.query(query, { + cartId: this.cartId + }); + + return data.cart; + } catch (error) { + // Cart might be expired or invalid + console.error('Error fetching cart:', error); + this.clearCart(); + return null; + } + } + /** * Get checkout URL to redirect user */ diff --git a/site/snippets/structured-data-product.php b/site/snippets/structured-data-product.php index b6bfefd..2390bc8 100644 --- a/site/snippets/structured-data-product.php +++ b/site/snippets/structured-data-product.php @@ -38,12 +38,18 @@ const language = container.dataset.language || 'fr'; const isEnglish = language === 'en'; - const cart = new ShopifyCart({ - domain: 'nv7cqv-bu.myshopify.com', - storefrontAccessToken: 'dec3d35a2554384d149c72927d1cfd1b' - }); + function initStructuredData() { + if (typeof ShopifyCart === 'undefined') { + setTimeout(initStructuredData, 100); + return; + } - cart.getProductByHandle(handle).then(product => { + const cart = new ShopifyCart({ + domain: 'nv7cqv-bu.myshopify.com', + storefrontAccessToken: 'dec3d35a2554384d149c72927d1cfd1b' + }); + + cart.getProductByHandle(handle).then(product => { if (!product) return; const title = isEnglish && product.titleEn?.value ? product.titleEn.value : product.title; @@ -79,6 +85,10 @@ if (schemaScript) { schemaScript.textContent = JSON.stringify(schema, null, 2); } - }); + }); + } + + // Initialize when ShopifyCart is available + initStructuredData(); })(); From 9b4bd4b731d2e0ece55f11ee1f685227c3e151a2 Mon Sep 17 00:00:00 2001 From: isUnknown Date: Wed, 21 Jan 2026 11:11:18 +0100 Subject: [PATCH 3/3] product > english > description : nl2br --- assets/js/product-loader.js | 139 +++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 57 deletions(-) diff --git a/assets/js/product-loader.js b/assets/js/product-loader.js index a8c348d..c728271 100644 --- a/assets/js/product-loader.js +++ b/assets/js/product-loader.js @@ -3,8 +3,8 @@ if (!container) return; const handle = container.dataset.shopifyHandle; - const language = container.dataset.language || 'fr'; - const isEnglish = language === 'en'; + const language = container.dataset.language || "fr"; + const isEnglish = language === "en"; const loadingState = container.querySelector(".product-loading"); const contentState = container.querySelector(".product-content"); const errorState = container.querySelector(".product-error"); @@ -63,9 +63,10 @@ function renderTitle(product, isEnglish) { const titleEl = document.querySelector("[data-product-title]"); if (titleEl) { - const title = isEnglish && product.titleEn?.value - ? product.titleEn.value - : product.title; + const title = + isEnglish && product.titleEn?.value + ? product.titleEn.value + : product.title; titleEl.textContent = title; } } @@ -81,9 +82,10 @@ function renderDetails(product, isEnglish) { const detailsEl = document.querySelector("[data-product-details]"); if (detailsEl) { - const description = isEnglish && product.descriptionEn?.value - ? product.descriptionEn.value - : product.descriptionHtml || ""; + const description = + isEnglish && product.descriptionEn?.value + ? product.descriptionEn.value.replace("\n", "

") + : product.descriptionHtml || ""; detailsEl.innerHTML = description; } } @@ -92,9 +94,10 @@ const imagesContainer = document.querySelector("[data-product-images]"); if (imagesContainer && product.images.edges.length > 0) { - const productTitle = isEnglish && product.titleEn?.value - ? product.titleEn.value - : product.title; + const productTitle = + isEnglish && product.titleEn?.value + ? product.titleEn.value + : product.title; imagesContainer.innerHTML = product.images.edges .map((edge) => { @@ -117,12 +120,16 @@ if (product.variants.edges.length <= 1) return; const firstVariant = product.variants.edges[0].node; - if (!firstVariant.selectedOptions || firstVariant.selectedOptions.length === 0) return; + if ( + !firstVariant.selectedOptions || + firstVariant.selectedOptions.length === 0 + ) + return; const mainOption = firstVariant.selectedOptions[0]; const optionValues = new Set(); - product.variants.edges.forEach(edge => { + product.variants.edges.forEach((edge) => { const variant = edge.node; if (variant.selectedOptions && variant.selectedOptions[0]) { optionValues.add(variant.selectedOptions[0].value); @@ -137,52 +144,62 @@ if (!optionsContainer || !optionsList) return; const optionName = mainOption.name; - const optionSlug = optionName.toLowerCase().replace(/\s+/g, '-'); + const optionSlug = optionName.toLowerCase().replace(/\s+/g, "-"); - optionsList.innerHTML = Array.from(optionValues).map((value) => { - const uniqueId = `${optionSlug}-${value.toLowerCase().replace(/\s+/g, '-')}`; - const variant = product.variants.edges.find(e => - e.node.selectedOptions && e.node.selectedOptions[0]?.value === value - )?.node; - const isAvailable = variant?.availableForSale || false; + optionsList.innerHTML = Array.from(optionValues) + .map((value) => { + const uniqueId = `${optionSlug}-${value + .toLowerCase() + .replace(/\s+/g, "-")}`; + const variant = product.variants.edges.find( + (e) => + e.node.selectedOptions && e.node.selectedOptions[0]?.value === value + )?.node; + const isAvailable = variant?.availableForSale || false; - return ` + return `
  • `; - }).join(''); + }) + .join(""); - optionsContainer.style.display = 'block'; + optionsContainer.style.display = "block"; const radios = optionsList.querySelectorAll('input[type="radio"]'); const addToCartBtn = document.querySelector("[data-shopify-add-to-cart]"); - const buttonText = addToCartBtn?.querySelector('[data-button-text]'); + const buttonText = addToCartBtn?.querySelector("[data-button-text]"); - radios.forEach(radio => { - radio.addEventListener('change', function() { + radios.forEach((radio) => { + radio.addEventListener("change", function () { const variantId = this.dataset.variantId; if (addToCartBtn) { addToCartBtn.dataset.variantId = variantId; - addToCartBtn.removeAttribute('disabled'); + addToCartBtn.removeAttribute("disabled"); } if (buttonText) { - buttonText.textContent = addToCartBtn.dataset.defaultText || 'Ajouter au panier'; + buttonText.textContent = + addToCartBtn.dataset.defaultText || "Ajouter au panier"; } - const allLi = optionsList.querySelectorAll('li'); - allLi.forEach(li => li.classList.remove('is-selected')); - this.closest('li').classList.add('is-selected'); + const allLi = optionsList.querySelectorAll("li"); + allLi.forEach((li) => li.classList.remove("is-selected")); + this.closest("li").classList.add("is-selected"); }); }); } @@ -196,10 +213,11 @@ const hasMultipleVariants = product.variants.edges.length > 1; const firstVariant = product.variants.edges[0]?.node; - const hasOptions = firstVariant?.selectedOptions && firstVariant.selectedOptions.length > 0; + const hasOptions = + firstVariant?.selectedOptions && firstVariant.selectedOptions.length > 0; const uniqueOptions = new Set(); - product.variants.edges.forEach(edge => { + product.variants.edges.forEach((edge) => { if (edge.node.selectedOptions && edge.node.selectedOptions[0]) { uniqueOptions.add(edge.node.selectedOptions[0].value); } @@ -207,10 +225,11 @@ const hasMultipleOptions = uniqueOptions.size > 1; if (hasMultipleVariants && hasOptions && hasMultipleOptions) { - addToCartBtn.setAttribute('disabled', 'disabled'); - const buttonText = addToCartBtn.querySelector('[data-button-text]'); + addToCartBtn.setAttribute("disabled", "disabled"); + const buttonText = addToCartBtn.querySelector("[data-button-text]"); if (buttonText) { - buttonText.textContent = addToCartBtn.dataset.textChooseOption || 'Choisissez une option'; + buttonText.textContent = + addToCartBtn.dataset.textChooseOption || "Choisissez une option"; } } else { const firstAvailableVariant = product.variants.edges.find( @@ -228,44 +247,50 @@ function updateMetaTags(product, isEnglish) { // Update title and description - const title = isEnglish && product.titleEn?.value - ? product.titleEn.value - : product.title; - const description = isEnglish && product.descriptionEn?.value - ? product.descriptionEn.value - : product.description; + const title = + isEnglish && product.titleEn?.value + ? product.titleEn.value + : product.title; + const description = + isEnglish && product.descriptionEn?.value + ? product.descriptionEn.value + : product.description; // Update Open Graph title - const ogTitle = document.getElementById('og-title'); + const ogTitle = document.getElementById("og-title"); if (ogTitle) { - ogTitle.setAttribute('content', title); + ogTitle.setAttribute("content", title); } // Update Open Graph description - const ogDescription = document.getElementById('og-description'); + const ogDescription = document.getElementById("og-description"); if (ogDescription && description) { const excerpt = description.substring(0, 160); - ogDescription.setAttribute('content', excerpt); + ogDescription.setAttribute("content", excerpt); } // Update Open Graph image - const ogImage = document.getElementById('og-image'); + const ogImage = document.getElementById("og-image"); if (ogImage && product.images.edges.length > 0) { - ogImage.setAttribute('content', product.images.edges[0].node.url); + ogImage.setAttribute("content", product.images.edges[0].node.url); } // Update product price - const ogPrice = document.getElementById('og-price'); + const ogPrice = document.getElementById("og-price"); if (ogPrice) { - const price = parseFloat(product.priceRange.minVariantPrice.amount).toFixed(2); - ogPrice.setAttribute('content', price); + const price = parseFloat( + product.priceRange.minVariantPrice.amount + ).toFixed(2); + ogPrice.setAttribute("content", price); } // Update availability - const ogAvailability = document.getElementById('og-availability'); + const ogAvailability = document.getElementById("og-availability"); if (ogAvailability) { - const availability = product.availableForSale ? 'in stock' : 'out of stock'; - ogAvailability.setAttribute('content', availability); + const availability = product.availableForSale + ? "in stock" + : "out of stock"; + ogAvailability.setAttribute("content", availability); } // Update page title @@ -275,7 +300,7 @@ let metaDescription = document.querySelector('meta[name="description"]'); if (metaDescription && description) { const excerpt = description.substring(0, 160); - metaDescription.setAttribute('content', excerpt); + metaDescription.setAttribute("content", excerpt); } } })();