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:
isUnknown 2026-01-14 11:26:14 +01:00
parent c08662caf8
commit 28501fec7c
13 changed files with 1158 additions and 81 deletions

View file

@ -0,0 +1,233 @@
/* Cart Drawer Styles */
.cart-drawer {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s ease;
}
.cart-drawer.is-open {
pointer-events: auto;
opacity: 1;
}
.cart-drawer__overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
cursor: pointer;
}
.cart-drawer__panel {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 100%;
max-width: 420px;
background-color: #ffffff;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.cart-drawer.is-open .cart-drawer__panel {
transform: translateX(0);
}
.cart-drawer__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.5rem;
border-bottom: 1px solid #e0e0e0;
}
.cart-drawer__header h3 {
margin: 0;
font-size: 1.5rem;
font-weight: bold;
}
.cart-drawer__close {
background: none;
border: none;
cursor: pointer;
padding: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.2s;
}
.cart-drawer__close:hover {
opacity: 0.7;
}
.cart-drawer__content {
flex: 1;
overflow-y: auto;
padding: 1.5rem;
}
.cart-drawer__empty {
text-align: center;
padding: 3rem 1rem;
color: #666;
}
.cart-drawer__empty.hidden {
display: none;
}
.cart-drawer__items {
display: flex;
flex-direction: column;
gap: 1rem;
}
.cart-drawer__items.hidden {
display: none;
}
.cart-item {
display: flex;
gap: 1rem;
padding: 1rem;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.cart-item__image {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 4px;
flex-shrink: 0;
}
.cart-item__details {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.cart-item__title {
font-weight: 600;
margin: 0;
font-size: 1rem;
}
.cart-item__variant {
font-size: 0.875rem;
color: #666;
margin: 0;
}
.cart-item__price {
font-weight: bold;
color: #000;
}
.cart-item__quantity {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: auto;
}
.cart-item__qty-btn {
width: 28px;
height: 28px;
border: 1px solid #ddd;
background: #fff;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
transition: background-color 0.2s;
}
.cart-item__qty-btn:hover:not(:disabled) {
background-color: #f5f5f5;
}
.cart-item__qty-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.cart-item__qty-value {
min-width: 30px;
text-align: center;
font-weight: 600;
}
.cart-item__remove {
background: none;
border: none;
color: #ff3333;
cursor: pointer;
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
text-decoration: underline;
align-self: flex-start;
}
.cart-item__remove:hover {
color: #cc0000;
}
.cart-drawer__footer {
border-top: 1px solid #e0e0e0;
padding: 1.5rem;
}
.cart-drawer__checkout-btn {
width: 100%;
font-family: "Open Sans", sans-serif;
font-weight: bold;
font-size: 1rem;
color: #000000;
background-color: #00ff00;
border: none;
border-radius: 40px;
padding: 14px 34px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.cart-drawer__checkout-btn:hover:not(:disabled) {
background-color: #00e600;
}
.cart-drawer__checkout-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Mobile responsiveness */
@media (max-width: 768px) {
.cart-drawer__panel {
max-width: 100%;
}
}
/* Loading state */
.cart-drawer__content.is-loading {
opacity: 0.5;
pointer-events: none;
}

View file

@ -1258,8 +1258,238 @@ body.is-fullscreen {
margin-top: calc(var(--spacing) * 4);
}
.snipcart-modal__container {
z-index: 1000;
/* Cart Drawer Styles */
.cart-drawer {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s ease;
}
.cart-drawer.is-open {
pointer-events: auto;
opacity: 1;
}
.cart-drawer__overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
cursor: pointer;
}
.cart-drawer__panel {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 100%;
max-width: 420px;
background-color: #ffffff;
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.cart-drawer.is-open .cart-drawer__panel {
transform: translateX(0);
}
.cart-drawer__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.5rem;
border-bottom: 1px solid #e0e0e0;
}
.cart-drawer__header h3 {
margin: 0;
font-size: 1.5rem;
font-weight: bold;
}
.cart-drawer__close {
background: none;
border: none;
cursor: pointer;
padding: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.2s;
}
.cart-drawer__close:hover {
opacity: 0.7;
}
.cart-drawer__content {
flex: 1;
overflow-y: auto;
padding: 1.5rem;
}
.cart-drawer__empty {
text-align: center;
padding: 3rem 1rem;
color: #666;
}
.cart-drawer__empty.hidden {
display: none;
}
.cart-drawer__items {
display: flex;
flex-direction: column;
gap: 1rem;
}
.cart-drawer__items.hidden {
display: none;
}
.cart-item {
display: flex;
gap: 1rem;
padding: 1rem;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.cart-item__image {
width: 80px;
height: 80px;
-o-object-fit: cover;
object-fit: cover;
border-radius: 4px;
flex-shrink: 0;
}
.cart-item__details {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.cart-item__title {
font-weight: 600;
margin: 0;
font-size: 1rem;
}
.cart-item__variant {
font-size: 0.875rem;
color: #666;
margin: 0;
}
.cart-item__price {
font-weight: bold;
color: #000;
}
.cart-item__quantity {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: auto;
}
.cart-item__qty-btn {
width: 28px;
height: 28px;
border: 1px solid #ddd;
background: #fff;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
transition: background-color 0.2s;
}
.cart-item__qty-btn:hover:not(:disabled) {
background-color: #f5f5f5;
}
.cart-item__qty-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.cart-item__qty-value {
min-width: 30px;
text-align: center;
font-weight: 600;
}
.cart-item__remove {
background: none;
border: none;
color: #ff3333;
cursor: pointer;
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
text-decoration: underline;
align-self: flex-start;
}
.cart-item__remove:hover {
color: #cc0000;
}
.cart-drawer__footer {
border-top: 1px solid #e0e0e0;
padding: 1.5rem;
}
.cart-drawer__checkout-btn {
width: 100%;
font-family: "Open Sans", sans-serif;
font-weight: bold;
font-size: 1rem;
color: #000000;
background-color: #00ff00;
border: none;
border-radius: 40px;
padding: 14px 34px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.cart-drawer__checkout-btn:hover:not(:disabled) {
background-color: #00e600;
}
.cart-drawer__checkout-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Mobile responsiveness */
@media (max-width: 768px) {
.cart-drawer__panel {
max-width: 100%;
}
}
/* Loading state */
.cart-drawer__content.is-loading {
opacity: 0.5;
pointer-events: none;
}
[data-template=subscription-newsletter] main {

File diff suppressed because one or more lines are too long

View file

@ -22,6 +22,7 @@
@import "template/shop/layout";
@import "template/shop/section--product";
@import "template/shop/thanks";
// @import "template/shop/snipcart"; // Snipcart désactivé - voir assets/snipcart-archive/README.md
@import "components/shopify-buy-button.scss";
@import "components/shopify-cart-drawer.scss";
@import "template/subscription-newsletter/layout";

View file

@ -1,3 +0,0 @@
.snipcart-modal__container {
z-index: 1000;
}

214
assets/js/cart-drawer.js Normal file
View 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();
}
})();

View file

@ -0,0 +1,121 @@
/**
* 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]');
// 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 = 'Rupture de stock';
addToCartBtn.classList.add('out-of-stock');
if (stockDisplay) {
stockDisplay.textContent = 'Rupture de stock';
stockDisplay.classList.add('out-of-stock');
}
} else {
// Show in stock
if (stockDisplay) {
stockDisplay.textContent = 'En stock';
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
addToCartBtn.disabled = true;
const originalText = addToCartBtn.textContent;
addToCartBtn.textContent = 'Ajout en cours...';
try {
const variantId = this.dataset.variantId;
const cartResult = await cart.addToCart(variantId, 1);
// Show success feedback
addToCartBtn.textContent = 'Ajouté ! ✓';
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;
addToCartBtn.classList.remove('success');
}, 1500);
} catch (error) {
console.error('Error adding to cart:', error);
// Show error feedback
addToCartBtn.textContent = 'Erreur - Réessayer';
addToCartBtn.classList.add('error');
// Re-enable button after delay
setTimeout(() => {
addToCartBtn.disabled = false;
addToCartBtn.textContent = originalText;
addToCartBtn.classList.remove('error');
}, 2000);
}
});
// Load product data on page load
loadProductData();
})();

232
assets/js/shopify-cart.js Normal file
View file

@ -0,0 +1,232 @@
/**
* Shopify Storefront API Client
* Custom implementation using Cart API (2026-01)
*/
class ShopifyCart {
constructor(config) {
this.domain = config.domain;
this.storefrontAccessToken = config.storefrontAccessToken;
this.apiVersion = '2026-01';
this.endpoint = `https://${this.domain}/api/${this.apiVersion}/graphql.json`;
this.cartId = null;
this.cartItems = [];
// Load existing cart from localStorage
this.loadCart();
}
/**
* Make GraphQL request to Shopify Storefront API
*/
async query(query, variables = {}) {
const response = await fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Storefront-Access-Token': this.storefrontAccessToken,
},
body: JSON.stringify({ query, variables }),
});
const result = await response.json();
if (result.errors) {
console.error('Shopify API Error:', result.errors);
throw new Error(result.errors[0].message);
}
return result.data;
}
/**
* Get product information by ID
*/
async getProduct(productId) {
const query = `
query getProduct($id: ID!) {
product(id: $id) {
id
title
description
availableForSale
variants(first: 10) {
edges {
node {
id
title
price {
amount
currencyCode
}
availableForSale
}
}
}
}
}
`;
const data = await this.query(query, {
id: `gid://shopify/Product/${productId}`
});
return data.product;
}
/**
* Create a new cart
*/
async createCart(lines = []) {
const query = `
mutation cartCreate($input: CartInput!) {
cartCreate(input: $input) {
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 this.query(query, {
input: { lines }
});
if (data.cartCreate.userErrors.length > 0) {
throw new Error(data.cartCreate.userErrors[0].message);
}
this.cartId = data.cartCreate.cart.id;
this.saveCart();
return data.cartCreate.cart;
}
/**
* Add item to cart
*/
async addToCart(variantId, quantity = 1) {
const lines = [{
merchandiseId: `gid://shopify/ProductVariant/${variantId}`,
quantity: quantity
}];
let cart;
if (this.cartId) {
// Add to existing cart
const query = `
mutation cartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
cartLinesAdd(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 this.query(query, {
cartId: this.cartId,
lines
});
if (data.cartLinesAdd.userErrors.length > 0) {
throw new Error(data.cartLinesAdd.userErrors[0].message);
}
cart = data.cartLinesAdd.cart;
} else {
// Create new cart
cart = await this.createCart(lines);
}
this.cartItems = cart.lines.edges;
return cart;
}
/**
* Get checkout URL to redirect user
*/
getCheckoutUrl(cart) {
return cart?.checkoutUrl || null;
}
/**
* Save cart ID to localStorage
*/
saveCart() {
if (this.cartId) {
localStorage.setItem('shopify_cart_id', this.cartId);
}
}
/**
* Load cart ID from localStorage
*/
loadCart() {
this.cartId = localStorage.getItem('shopify_cart_id');
}
/**
* Clear cart
*/
clearCart() {
this.cartId = null;
this.cartItems = [];
localStorage.removeItem('shopify_cart_id');
}
}
// Export for use in other scripts
window.ShopifyCart = ShopifyCart;

View file

@ -0,0 +1,85 @@
<div class="product-purchase">
<div class="product-stock-info">
<p data-product-stock class="stock-status"></p>
</div>
<button
class="btn-add-to-cart"
data-shopify-add-to-cart
data-product-id="15689076179317"
data-variant-id=""
>
Ajouter au panier
</button>
</div>
<style>
.product-purchase {
margin-top: 2rem;
}
.product-stock-info {
margin-bottom: 1rem;
}
.stock-status {
font-size: 0.9rem;
font-weight: 600;
margin: 0;
}
.stock-status.in-stock {
color: #00cc00;
}
.stock-status.low-stock {
color: #ff9900;
}
.stock-status.out-of-stock {
color: #ff3333;
}
.btn-add-to-cart {
font-family: "Open Sans", sans-serif;
font-weight: bold;
font-size: 1rem;
color: #000000;
background-color: #00ff00;
border: none;
border-radius: 40px;
padding: 12px 34px;
cursor: pointer;
transition: background-color 0.3s ease;
width: 100%;
max-width: 300px;
}
.btn-add-to-cart:hover:not(:disabled) {
background-color: #00e600;
}
.btn-add-to-cart:focus {
outline: 2px solid #00e600;
outline-offset: 2px;
}
.btn-add-to-cart:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-add-to-cart.success {
background-color: #00cc00;
}
.btn-add-to-cart.error {
background-color: #ff3333;
color: #ffffff;
}
.btn-add-to-cart.out-of-stock {
background-color: #cccccc;
color: #666666;
}
</style>

View file

@ -1,3 +1,33 @@
<!-- Cart Drawer -->
<div id="cart-drawer" class="cart-drawer">
<div class="cart-drawer__overlay" data-cart-close></div>
<div class="cart-drawer__panel">
<div class="cart-drawer__header">
<h3>Panier</h3>
<button class="cart-drawer__close" data-cart-close aria-label="Fermer le panier">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="cart-drawer__content">
<div class="cart-drawer__empty" data-cart-empty>
<p>Votre panier est vide</p>
</div>
<div class="cart-drawer__items" data-cart-items></div>
</div>
<div class="cart-drawer__footer">
<button class="cart-drawer__checkout-btn" data-cart-checkout>
Passer commande
</button>
</div>
</div>
</div>
<footer id="site-footer">
<div class="site-footer__container">
<div class="footer__mentions">
@ -10,6 +40,8 @@
</footer>
<script src="<?= url('assets/js/onload.js') ?>"></script>
<script src="<?= url('assets/js/shopify-cart.js') ?>"></script>
<script src="<?= url('assets/js/cart-drawer.js') ?>"></script>
<?php if(isset($scripts) && is_array($scripts)): ?>
<?php foreach($scripts as $script): ?>
<script src="<?= url($script) ?>"></script>

View file

@ -12,6 +12,7 @@
<link rel="stylesheet" type="text/css" href="<?= url('assets/fonts/stylesheet.css') ?>?version-cache-prevent<?= rand(0, 1000)?>" />
<link rel="stylesheet" type="text/css" href="<?= url('assets/css/style.css') ?>" />
<link rel="stylesheet" type="text/css" href="<?= url('assets/css/cart-drawer.css') ?>" />
</head>
<body data-template="<?= $page->template() ?>">
<header id="site-header">

View file

@ -18,75 +18,7 @@
<?php endif ?>
</div>
<?php if($page->hasOptions()->toBool() && $page->optionValues()->isNotEmpty()): ?>
<div class="product-options">
<ul class="product-options__list">
<?php
$values = $page->optionValues()->split(',');
$optionSlug = $page->optionLabel()->slug();
foreach($values as $index => $value):
$value = trim($value);
$uniqueId = $optionSlug . '-' . Str::slug(strtolower($value));
?>
<li>
<input type="radio" id="<?= $uniqueId ?>" name="<?= $optionSlug ?>" value="<?= $value ?>" />
<label for="<?= $uniqueId ?>"><?= $value ?></label>
</li>
<?php endforeach ?>
</ul>
</div>
<?php endif ?>
<div class="add-to-cart">
<!-- Snipcart désactivé - voir assets/snipcart-archive/README.md pour restauration -->
<button
class="btn__default"
disabled
<?php
/*
// SNIPCART - Décommenter pour réactiver
class="btn__default snipcart-add-item"
data-item-id="<?= $page->slug() ?>"
data-item-price="<?= $page->price() ?>"
data-item-description="<?= $page->description()->excerpt(100) ?>"
data-item-image="<?= $page->images()->first() ? $page->images()->first()->url() : '' ?>"
data-item-name="<?= $page->title()->html() ?>"
data-item-shippable="true"
data-item-weight="<?= $page->weight()->or(0) ?>"
data-item-length="<?= $page->length()->or(0) ?>"
data-item-width="<?= $page->width()->or(0) ?>"
data-item-height="<?= $page->height()->or(0) ?>"
*/
?>
<?php
/*
// SNIPCART OPTIONS - Décommenter pour réactiver
if($page->hasOptions()->toBool() && $page->optionValues()->isNotEmpty()):
$values = $page->optionValues()->split(',');
$trimmedValues = array_map('trim', $values);
$snipcartOptions = implode('|', $trimmedValues);
?>
data-item-custom1-name="<?= $page->optionLabel()->html() ?>"
data-item-custom1-options="<?= $snipcartOptions ?>"
data-item-custom1-required="true"
disabled
<?php endif; */
?>
>
<span class="icon">
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="m14.523 18.787s4.501-4.505 6.255-6.26c.146-.146.219-.338.219-.53s-.073-.383-.219-.530c-1.753-1.754-6.255-6.258-6.255-6.258-.144-.145-.334-.217-.524-.217-.193 0-.385.074-.532.221-.293.292-.295.766-.004 1.056l4.978 4.978h-14.692c-.414 0-.75.336-.75.75s.336.75.75.75h14.692l-4.979 4.979c-.289.289-.286.762.006 1.054.148.148.341.222.533.222.19 0 .378-.072.522-.215z" fill-rule="nonzero" />
</svg>
</span>
<div class="txt" data-default-text="<?= t('addToCart', 'Ajouter au panier') ?>">
<?php if($page->hasOptions()->toBool() && $page->optionValues()->isNotEmpty()): ?>
<?= t('chooseOption', 'Choisissez une option') ?>
<?php else: ?>
<?= t('addToCart', 'Ajouter au panier') ?>
<?php endif ?>
</div>
</button>
</div>
<?php snippet('buy-button--t-shirt') ?>
</div>
<div class="product-gallery swiper">
@ -122,8 +54,7 @@
</section>
</main>
<?php
// Snipcart désactivé - voir assets/snipcart-archive/README.md
// Pour réactiver: ajouter 'assets/js/product-size.js', 'assets/js/snipcart.js' dans le tableau
snippet('footer', ['scripts' => ['assets/js/product-gallery.js']])
?>
<?php snippet('footer', ['scripts' => [
'assets/js/product-add-to-cart.js',
'assets/js/product-gallery.js'
]]) ?>