Implement custom Shopify cart with drawer UI
Replace Shopify Buy Button iframe with custom implementation using Storefront API 2026-01. Create interactive cart drawer with full product management capabilities (add, remove, update quantities) and seamless checkout flow. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c08662caf8
commit
28501fec7c
13 changed files with 1158 additions and 81 deletions
214
assets/js/cart-drawer.js
Normal file
214
assets/js/cart-drawer.js
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
/**
|
||||
* 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]');
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
// 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 renderCart() {
|
||||
if (!currentCart || !currentCart.lines || currentCart.lines.edges.length === 0) {
|
||||
emptyState.classList.remove('hidden');
|
||||
itemsContainer.classList.add('hidden');
|
||||
checkoutBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
emptyState.classList.add('hidden');
|
||||
itemsContainer.classList.remove('hidden');
|
||||
checkoutBtn.disabled = false;
|
||||
|
||||
// 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">${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}">
|
||||
Retirer
|
||||
</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();
|
||||
}
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue