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:
isUnknown 2026-01-16 12:03:20 +01:00
parent 957cf79e45
commit ad699f0365
22 changed files with 649 additions and 579 deletions

View file

@ -1,109 +1,46 @@
/**
* Product Add to Cart functionality
* Handles the add to cart button interaction with Shopify
*/
(function() {
// Initialize Shopify Cart
const cart = new ShopifyCart({
domain: 'nv7cqv-bu.myshopify.com',
storefrontAccessToken: 'dec3d35a2554384d149c72927d1cfd1b'
});
// Get product ID from data attribute
const addToCartBtn = document.querySelector('[data-shopify-add-to-cart]');
if (!addToCartBtn) {
console.warn('Add to cart button not found');
return;
}
const productId = addToCartBtn.dataset.productId;
const variantId = addToCartBtn.dataset.variantId;
const stockDisplay = document.querySelector('[data-product-stock]');
// Get translated texts
const texts = {
add: addToCartBtn.dataset.textAdd || 'Add to cart',
adding: addToCartBtn.dataset.textAdding || 'Adding...',
added: addToCartBtn.dataset.textAdded || 'Added! ✓',
error: addToCartBtn.dataset.textError || 'Error - Try again',
outOfStock: addToCartBtn.dataset.textOutOfStock || 'Out of stock',
inStock: addToCartBtn.dataset.textInStock || 'In stock'
error: addToCartBtn.dataset.textError || 'Error - Try again'
};
// Load product data to check availability
async function loadProductData() {
try {
const product = await cart.getProduct(productId);
if (!product) {
console.error('Product not found');
return;
}
// Find the specific variant or use the first one
let variant;
if (variantId) {
variant = product.variants.edges.find(
edge => edge.node.id === `gid://shopify/ProductVariant/${variantId}`
)?.node;
} else {
variant = product.variants.edges[0]?.node;
}
if (!variant) {
console.error('Variant not found');
return;
}
// Update button based on availability
if (!variant.availableForSale) {
addToCartBtn.disabled = true;
addToCartBtn.textContent = texts.outOfStock;
addToCartBtn.classList.add('out-of-stock');
if (stockDisplay) {
stockDisplay.textContent = texts.outOfStock;
stockDisplay.classList.add('out-of-stock');
}
} else {
// Show in stock
if (stockDisplay) {
stockDisplay.textContent = texts.inStock;
stockDisplay.classList.add('in-stock');
}
}
// Store variant ID for later use
addToCartBtn.dataset.variantId = variant.id.replace('gid://shopify/ProductVariant/', '');
} catch (error) {
console.error('Error loading product:', error);
}
}
// Handle add to cart click
addToCartBtn.addEventListener('click', async function(e) {
e.preventDefault();
// Disable button during request
const variantId = this.dataset.variantId;
if (!variantId) {
console.error('No variant ID found');
return;
}
addToCartBtn.disabled = true;
const originalText = addToCartBtn.textContent;
addToCartBtn.textContent = texts.adding;
try {
const variantId = this.dataset.variantId;
const cartResult = await cart.addToCart(variantId, 1);
// Show success feedback
addToCartBtn.textContent = texts.added;
addToCartBtn.classList.add('success');
// Dispatch event to open cart drawer
document.dispatchEvent(new CustomEvent('cart:updated', {
detail: { cart: cartResult }
}));
// Reset button after short delay
setTimeout(() => {
addToCartBtn.disabled = false;
addToCartBtn.textContent = originalText;
@ -113,11 +50,9 @@
} catch (error) {
console.error('Error adding to cart:', error);
// Show error feedback
addToCartBtn.textContent = texts.error;
addToCartBtn.classList.add('error');
// Re-enable button after delay
setTimeout(() => {
addToCartBtn.disabled = false;
addToCartBtn.textContent = originalText;
@ -125,7 +60,4 @@
}, 2000);
}
});
// Load product data on page load
loadProductData();
})();