2026-01-14 11:26:14 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* Cart Drawer Component
|
|
|
|
|
|
* Manages the cart sidebar with add/remove/update functionality
|
|
|
|
|
|
*/
|
|
|
|
|
|
(function() {
|
|
|
|
|
|
const drawer = document.getElementById('cart-drawer');
|
|
|
|
|
|
const emptyState = document.querySelector('[data-cart-empty]');
|
|
|
|
|
|
const itemsContainer = document.querySelector('[data-cart-items]');
|
|
|
|
|
|
const checkoutBtn = document.querySelector('[data-cart-checkout]');
|
|
|
|
|
|
const closeButtons = document.querySelectorAll('[data-cart-close]');
|
2026-01-14 12:02:55 +01:00
|
|
|
|
const totalDisplay = document.querySelector('[data-cart-total]');
|
2026-01-14 12:06:26 +01:00
|
|
|
|
const headerCartBtn = document.querySelector('[data-cart-open]');
|
|
|
|
|
|
const headerCartCount = document.querySelector('[data-cart-count]');
|
2026-01-14 12:02:55 +01:00
|
|
|
|
|
|
|
|
|
|
// Get translated text
|
|
|
|
|
|
const removeText = drawer.dataset.textRemove || 'Remove';
|
2026-01-14 11:26:14 +01:00
|
|
|
|
|
|
|
|
|
|
let currentCart = null;
|
|
|
|
|
|
let cartInstance = null;
|
|
|
|
|
|
|
|
|
|
|
|
// Wait for ShopifyCart to be available
|
|
|
|
|
|
function initCartDrawer() {
|
|
|
|
|
|
if (typeof ShopifyCart === 'undefined') {
|
|
|
|
|
|
setTimeout(initCartDrawer, 100);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cartInstance = new ShopifyCart({
|
|
|
|
|
|
domain: 'nv7cqv-bu.myshopify.com',
|
|
|
|
|
|
storefrontAccessToken: 'dec3d35a2554384d149c72927d1cfd1b'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize event listeners
|
|
|
|
|
|
setupEventListeners();
|
2026-01-21 11:06:28 +01:00
|
|
|
|
|
|
|
|
|
|
// Load initial cart state
|
|
|
|
|
|
loadCart();
|
2026-01-14 11:26:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function setupEventListeners() {
|
|
|
|
|
|
// Close drawer
|
|
|
|
|
|
closeButtons.forEach(btn => {
|
|
|
|
|
|
btn.addEventListener('click', closeDrawer);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-01-14 12:06:26 +01:00
|
|
|
|
// Open drawer from header button
|
|
|
|
|
|
if (headerCartBtn) {
|
|
|
|
|
|
headerCartBtn.addEventListener('click', openDrawer);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-14 11:26:14 +01:00
|
|
|
|
// Checkout button
|
|
|
|
|
|
checkoutBtn.addEventListener('click', () => {
|
|
|
|
|
|
if (currentCart?.checkoutUrl) {
|
|
|
|
|
|
window.location.href = currentCart.checkoutUrl;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Listen for custom cart update events
|
|
|
|
|
|
document.addEventListener('cart:updated', (e) => {
|
|
|
|
|
|
currentCart = e.detail.cart;
|
|
|
|
|
|
renderCart();
|
|
|
|
|
|
openDrawer();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 11:06:28 +01:00
|
|
|
|
async function loadCart() {
|
|
|
|
|
|
if (!cartInstance) return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const cart = await cartInstance.getCart();
|
|
|
|
|
|
currentCart = cart;
|
|
|
|
|
|
renderCart();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error loading cart:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-14 11:26:14 +01:00
|
|
|
|
function openDrawer() {
|
|
|
|
|
|
drawer.classList.add('is-open');
|
|
|
|
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeDrawer() {
|
|
|
|
|
|
drawer.classList.remove('is-open');
|
|
|
|
|
|
document.body.style.overflow = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-14 12:02:55 +01:00
|
|
|
|
function calculateTotal() {
|
|
|
|
|
|
if (!currentCart || !currentCart.lines) return 0;
|
|
|
|
|
|
|
|
|
|
|
|
return currentCart.lines.edges.reduce((total, edge) => {
|
|
|
|
|
|
const item = edge.node;
|
|
|
|
|
|
const price = parseFloat(item.merchandise.price.amount);
|
|
|
|
|
|
const quantity = item.quantity;
|
|
|
|
|
|
return total + (price * quantity);
|
|
|
|
|
|
}, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatPrice(amount, currency = 'EUR') {
|
|
|
|
|
|
return new Intl.NumberFormat('fr-FR', {
|
|
|
|
|
|
style: 'currency',
|
|
|
|
|
|
currency: currency
|
|
|
|
|
|
}).format(amount);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-14 12:06:26 +01:00
|
|
|
|
function updateCartCount() {
|
|
|
|
|
|
if (!currentCart || !currentCart.lines || currentCart.lines.edges.length === 0) {
|
|
|
|
|
|
if (headerCartCount) {
|
|
|
|
|
|
headerCartCount.textContent = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate total quantity
|
|
|
|
|
|
const totalQty = currentCart.lines.edges.reduce((sum, edge) => {
|
|
|
|
|
|
return sum + edge.node.quantity;
|
|
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
|
|
|
|
if (headerCartCount) {
|
|
|
|
|
|
headerCartCount.textContent = totalQty > 0 ? totalQty : '';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-14 11:26:14 +01:00
|
|
|
|
function renderCart() {
|
|
|
|
|
|
if (!currentCart || !currentCart.lines || currentCart.lines.edges.length === 0) {
|
|
|
|
|
|
emptyState.classList.remove('hidden');
|
|
|
|
|
|
itemsContainer.classList.add('hidden');
|
|
|
|
|
|
checkoutBtn.disabled = true;
|
2026-01-14 12:02:55 +01:00
|
|
|
|
if (totalDisplay) {
|
|
|
|
|
|
totalDisplay.textContent = '0,00 €';
|
|
|
|
|
|
}
|
2026-01-14 12:06:26 +01:00
|
|
|
|
updateCartCount();
|
2026-01-14 11:26:14 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
emptyState.classList.add('hidden');
|
|
|
|
|
|
itemsContainer.classList.remove('hidden');
|
|
|
|
|
|
checkoutBtn.disabled = false;
|
|
|
|
|
|
|
2026-01-14 12:02:55 +01:00
|
|
|
|
// Calculate and display total
|
|
|
|
|
|
const total = calculateTotal();
|
|
|
|
|
|
const currency = currentCart.lines.edges[0]?.node.merchandise.price.currencyCode || 'EUR';
|
|
|
|
|
|
if (totalDisplay) {
|
|
|
|
|
|
totalDisplay.textContent = formatPrice(total, currency);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-14 12:06:26 +01:00
|
|
|
|
// Update header cart count
|
|
|
|
|
|
updateCartCount();
|
|
|
|
|
|
|
2026-01-14 11:26:14 +01:00
|
|
|
|
// Render cart items
|
|
|
|
|
|
itemsContainer.innerHTML = currentCart.lines.edges.map(edge => {
|
|
|
|
|
|
const item = edge.node;
|
|
|
|
|
|
const merchandise = item.merchandise;
|
|
|
|
|
|
|
|
|
|
|
|
return `
|
|
|
|
|
|
<div class="cart-item" data-line-id="${item.id}">
|
|
|
|
|
|
<div class="cart-item__details">
|
|
|
|
|
|
<h4 class="cart-item__title">${merchandise.product.title}</h4>
|
|
|
|
|
|
${merchandise.title !== 'Default Title' ? `<p class="cart-item__variant">${merchandise.title}</p>` : ''}
|
2026-01-14 12:02:55 +01:00
|
|
|
|
<p class="cart-item__price">${formatPrice(parseFloat(merchandise.price.amount), merchandise.price.currencyCode)}</p>
|
2026-01-14 11:26:14 +01:00
|
|
|
|
|
|
|
|
|
|
<div class="cart-item__quantity">
|
|
|
|
|
|
<button class="cart-item__qty-btn" data-action="decrease" data-line-id="${item.id}">−</button>
|
|
|
|
|
|
<span class="cart-item__qty-value">${item.quantity}</span>
|
|
|
|
|
|
<button class="cart-item__qty-btn" data-action="increase" data-line-id="${item.id}">+</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<button class="cart-item__remove" data-action="remove" data-line-id="${item.id}">
|
2026-01-14 12:02:55 +01:00
|
|
|
|
${removeText}
|
2026-01-14 11:26:14 +01:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
`;
|
|
|
|
|
|
}).join('');
|
|
|
|
|
|
|
|
|
|
|
|
// Attach event listeners to quantity buttons
|
|
|
|
|
|
attachQuantityListeners();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function attachQuantityListeners() {
|
|
|
|
|
|
const buttons = itemsContainer.querySelectorAll('[data-action]');
|
|
|
|
|
|
|
|
|
|
|
|
buttons.forEach(btn => {
|
|
|
|
|
|
btn.addEventListener('click', async (e) => {
|
|
|
|
|
|
const action = e.target.dataset.action;
|
|
|
|
|
|
const lineId = e.target.dataset.lineId;
|
|
|
|
|
|
|
|
|
|
|
|
await handleQuantityChange(action, lineId);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function handleQuantityChange(action, lineId) {
|
|
|
|
|
|
if (!cartInstance || !currentCart) return;
|
|
|
|
|
|
|
|
|
|
|
|
// Find the line item
|
|
|
|
|
|
const line = currentCart.lines.edges.find(edge => edge.node.id === lineId);
|
|
|
|
|
|
if (!line) return;
|
|
|
|
|
|
|
|
|
|
|
|
const currentQty = line.node.quantity;
|
|
|
|
|
|
let newQty = currentQty;
|
|
|
|
|
|
|
|
|
|
|
|
if (action === 'increase') {
|
|
|
|
|
|
newQty = currentQty + 1;
|
|
|
|
|
|
} else if (action === 'decrease') {
|
|
|
|
|
|
newQty = Math.max(0, currentQty - 1);
|
|
|
|
|
|
} else if (action === 'remove') {
|
|
|
|
|
|
newQty = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Update cart via API
|
|
|
|
|
|
try {
|
|
|
|
|
|
itemsContainer.classList.add('is-loading');
|
|
|
|
|
|
|
|
|
|
|
|
const query = `
|
|
|
|
|
|
mutation cartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
|
|
|
|
|
|
cartLinesUpdate(cartId: $cartId, lines: $lines) {
|
|
|
|
|
|
cart {
|
|
|
|
|
|
id
|
|
|
|
|
|
checkoutUrl
|
|
|
|
|
|
lines(first: 10) {
|
|
|
|
|
|
edges {
|
|
|
|
|
|
node {
|
|
|
|
|
|
id
|
|
|
|
|
|
quantity
|
|
|
|
|
|
merchandise {
|
|
|
|
|
|
... on ProductVariant {
|
|
|
|
|
|
id
|
|
|
|
|
|
title
|
|
|
|
|
|
price {
|
|
|
|
|
|
amount
|
|
|
|
|
|
currencyCode
|
|
|
|
|
|
}
|
|
|
|
|
|
product {
|
|
|
|
|
|
title
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
userErrors {
|
|
|
|
|
|
field
|
|
|
|
|
|
message
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
const data = await cartInstance.query(query, {
|
|
|
|
|
|
cartId: currentCart.id,
|
|
|
|
|
|
lines: [{
|
|
|
|
|
|
id: lineId,
|
|
|
|
|
|
quantity: newQty
|
|
|
|
|
|
}]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (data.cartLinesUpdate.userErrors.length > 0) {
|
|
|
|
|
|
throw new Error(data.cartLinesUpdate.userErrors[0].message);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
currentCart = data.cartLinesUpdate.cart;
|
|
|
|
|
|
renderCart();
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error updating cart:', error);
|
|
|
|
|
|
alert('Erreur lors de la mise à jour du panier');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
itemsContainer.classList.remove('is-loading');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Public API
|
|
|
|
|
|
window.CartDrawer = {
|
|
|
|
|
|
open: openDrawer,
|
|
|
|
|
|
close: closeDrawer,
|
|
|
|
|
|
updateCart: (cart) => {
|
|
|
|
|
|
currentCart = cart;
|
|
|
|
|
|
renderCart();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize when DOM is ready
|
|
|
|
|
|
if (document.readyState === 'loading') {
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', initCartDrawer);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
initCartDrawer();
|
|
|
|
|
|
}
|
|
|
|
|
|
})();
|