Add a cart button in the header (right of language switcher) that displays the number of items in parentheses when cart is not empty. Button opens the cart drawer on click and count updates dynamically when items are added/removed. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
275 lines
7.8 KiB
JavaScript
275 lines
7.8 KiB
JavaScript
/**
|
||
* 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]');
|
||
const totalDisplay = document.querySelector('[data-cart-total]');
|
||
const headerCartBtn = document.querySelector('[data-cart-open]');
|
||
const headerCartCount = document.querySelector('[data-cart-count]');
|
||
|
||
// Get translated text
|
||
const removeText = drawer.dataset.textRemove || 'Remove';
|
||
|
||
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();
|
||
}
|
||
|
||
function setupEventListeners() {
|
||
// Close drawer
|
||
closeButtons.forEach(btn => {
|
||
btn.addEventListener('click', closeDrawer);
|
||
});
|
||
|
||
// Open drawer from header button
|
||
if (headerCartBtn) {
|
||
headerCartBtn.addEventListener('click', openDrawer);
|
||
}
|
||
|
||
// 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();
|
||
});
|
||
}
|
||
|
||
function openDrawer() {
|
||
drawer.classList.add('is-open');
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
|
||
function closeDrawer() {
|
||
drawer.classList.remove('is-open');
|
||
document.body.style.overflow = '';
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
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 : '';
|
||
}
|
||
}
|
||
|
||
function renderCart() {
|
||
if (!currentCart || !currentCart.lines || currentCart.lines.edges.length === 0) {
|
||
emptyState.classList.remove('hidden');
|
||
itemsContainer.classList.add('hidden');
|
||
checkoutBtn.disabled = true;
|
||
if (totalDisplay) {
|
||
totalDisplay.textContent = '0,00 €';
|
||
}
|
||
updateCartCount();
|
||
return;
|
||
}
|
||
|
||
emptyState.classList.add('hidden');
|
||
itemsContainer.classList.remove('hidden');
|
||
checkoutBtn.disabled = false;
|
||
|
||
// 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);
|
||
}
|
||
|
||
// Update header cart count
|
||
updateCartCount();
|
||
|
||
// 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>` : ''}
|
||
<p class="cart-item__price">${formatPrice(parseFloat(merchandise.price.amount), merchandise.price.currencyCode)}</p>
|
||
|
||
<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}">
|
||
${removeText}
|
||
</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();
|
||
}
|
||
})();
|