- Add product loaders (product-loader.js, products-list-loader.js) to fetch data from Shopify - Extend Shopify API client with getProductByHandle() and getAllProducts() methods - Integrate Shopify metafields for multilingual support (custom.title_en, custom.description_en) - Refactor product.php and home.php templates to load content dynamically - Simplify product blueprint to minimal routing configuration - Create generic buy-button.php snippet with variant selection - Update footer.php with conditional script loading - Refactor _section--product.scss for better Sass structure - Add translations for loading states and product errors - Clean up old Kirby product content files Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
188 lines
5.6 KiB
JavaScript
188 lines
5.6 KiB
JavaScript
(async function () {
|
|
const container = document.querySelector("[data-product-loader]");
|
|
if (!container) return;
|
|
|
|
const handle = container.dataset.shopifyHandle;
|
|
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");
|
|
|
|
try {
|
|
const cart = new ShopifyCart({
|
|
domain: "nv7cqv-bu.myshopify.com",
|
|
storefrontAccessToken: "dec3d35a2554384d149c72927d1cfd1b",
|
|
});
|
|
|
|
const product = await cart.getProductByHandle(handle);
|
|
|
|
if (!product) {
|
|
throw new Error("Product not found");
|
|
}
|
|
|
|
renderProduct(product, isEnglish);
|
|
|
|
loadingState.style.display = "none";
|
|
contentState.removeAttribute("style");
|
|
|
|
setTimeout(() => {
|
|
if (typeof Swiper !== "undefined" && product.images.edges.length > 0) {
|
|
new Swiper(".product-gallery", {
|
|
loop: product.images.edges.length > 1,
|
|
navigation: {
|
|
nextEl: ".swiper-button-next",
|
|
prevEl: ".swiper-button-prev",
|
|
},
|
|
pagination: {
|
|
el: ".swiper-pagination",
|
|
clickable: true,
|
|
},
|
|
keyboard: {
|
|
enabled: true,
|
|
},
|
|
});
|
|
}
|
|
}, 100);
|
|
} catch (error) {
|
|
console.error("Error loading product:", error);
|
|
loadingState.style.display = "none";
|
|
errorState.style.display = "block";
|
|
}
|
|
|
|
function renderProduct(product, isEnglish) {
|
|
renderTitle(product, isEnglish);
|
|
renderPrice(product);
|
|
renderDetails(product, isEnglish);
|
|
renderImages(product, isEnglish);
|
|
renderVariants(product);
|
|
setupAddToCart(product);
|
|
renderStock(product);
|
|
}
|
|
|
|
function renderTitle(product, isEnglish) {
|
|
const titleEl = document.querySelector("[data-product-title]");
|
|
if (titleEl) {
|
|
const title = isEnglish && product.titleEn?.value
|
|
? product.titleEn.value
|
|
: product.title;
|
|
titleEl.textContent = title;
|
|
}
|
|
}
|
|
|
|
function renderPrice(product) {
|
|
const priceEl = document.querySelector("[data-product-price]");
|
|
if (priceEl) {
|
|
const price = parseFloat(product.priceRange.minVariantPrice.amount);
|
|
priceEl.textContent = price.toFixed(2) + "€";
|
|
}
|
|
}
|
|
|
|
function renderDetails(product, isEnglish) {
|
|
const detailsEl = document.querySelector("[data-product-details]");
|
|
if (detailsEl) {
|
|
const description = isEnglish && product.descriptionEn?.value
|
|
? product.descriptionEn.value
|
|
: product.descriptionHtml || "";
|
|
detailsEl.innerHTML = description;
|
|
}
|
|
}
|
|
|
|
function renderImages(product, isEnglish) {
|
|
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;
|
|
|
|
imagesContainer.innerHTML = product.images.edges
|
|
.map((edge) => {
|
|
const img = edge.node;
|
|
return `
|
|
<div class="swiper-slide">
|
|
<figure>
|
|
<img src="${img.url}"
|
|
alt="${img.altText || productTitle}"
|
|
loading="lazy" />
|
|
</figure>
|
|
</div>
|
|
`;
|
|
})
|
|
.join("");
|
|
}
|
|
}
|
|
|
|
function renderVariants(product) {
|
|
if (product.variants.edges.length <= 1) return;
|
|
|
|
const variantsContainer = document.querySelector("[data-product-variants]");
|
|
const variantSelector = document.querySelector("[data-variant-selector]");
|
|
|
|
if (!variantsContainer || !variantSelector) return;
|
|
|
|
variantsContainer.style.display = "block";
|
|
|
|
variantSelector.innerHTML = product.variants.edges
|
|
.map((edge) => {
|
|
const variant = edge.node;
|
|
const variantId = variant.id.replace(
|
|
"gid://shopify/ProductVariant/",
|
|
""
|
|
);
|
|
const price = parseFloat(variant.price.amount).toFixed(2) + "€";
|
|
const availability = variant.availableForSale
|
|
? ""
|
|
: " (Rupture de stock)";
|
|
|
|
return `<option value="${variantId}" ${
|
|
!variant.availableForSale ? "disabled" : ""
|
|
}>
|
|
${variant.title} - ${price}${availability}
|
|
</option>`;
|
|
})
|
|
.join("");
|
|
|
|
variantSelector.addEventListener("change", (e) => {
|
|
const addToCartBtn = document.querySelector("[data-shopify-add-to-cart]");
|
|
if (addToCartBtn) {
|
|
addToCartBtn.dataset.variantId = e.target.value;
|
|
}
|
|
});
|
|
}
|
|
|
|
function setupAddToCart(product) {
|
|
const addToCartBtn = document.querySelector("[data-shopify-add-to-cart]");
|
|
if (!addToCartBtn) return;
|
|
|
|
const productId = product.id.replace("gid://shopify/Product/", "");
|
|
addToCartBtn.dataset.productId = productId;
|
|
|
|
const firstAvailableVariant = product.variants.edges.find(
|
|
(e) => e.node.availableForSale
|
|
);
|
|
if (firstAvailableVariant) {
|
|
const variantId = firstAvailableVariant.node.id.replace(
|
|
"gid://shopify/ProductVariant/",
|
|
""
|
|
);
|
|
addToCartBtn.dataset.variantId = variantId;
|
|
}
|
|
}
|
|
|
|
function renderStock(product) {
|
|
const stockEl = document.querySelector("[data-product-stock]");
|
|
if (!stockEl) return;
|
|
|
|
const addToCartBtn = document.querySelector("[data-shopify-add-to-cart]");
|
|
|
|
if (product.availableForSale) {
|
|
stockEl.textContent = addToCartBtn?.dataset.textInStock || "En stock";
|
|
stockEl.classList.add("in-stock");
|
|
} else {
|
|
stockEl.textContent =
|
|
addToCartBtn?.dataset.textOutOfStock || "Rupture de stock";
|
|
stockEl.classList.add("out-of-stock");
|
|
}
|
|
}
|
|
})();
|