Migrate product data from Kirby to Shopify Storefront API
- 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>
This commit is contained in:
parent
957cf79e45
commit
ad699f0365
22 changed files with 649 additions and 579 deletions
188
assets/js/product-loader.js
Normal file
188
assets/js/product-loader.js
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
(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");
|
||||
}
|
||||
}
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue