Skip to content
/*
* ============================================================
* SHOPIFY CUSTOM CART DRAWER
* Features:
* 1. Free shipping progress bar (threshold: 250 AED)
* 2. Stock validation with error messages
* 3. Frequently Bought Together (random upsell, excludes cart items)
* ============================================================
*
* INSTALLATION:
* 1. Go to Shopify Admin > Online Store > Themes > Edit Code
* 2. In layout/theme.liquid, paste the HTML (below) just before
* 3. In assets/, create cart-drawer.js and paste this JS code
* 4. In layout/theme.liquid, add this before
:
*
* 5. In assets/cart-drawer.css (create new), paste the CSS from bottom
* 6. In layout/theme.liquid
, add:
*
*
* ============================================================
*/
/* ---- CONFIG ---- */
const FREE_SHIPPING_THRESHOLD = 250; // AED
/* ============================================================
CART DRAWER HTML
Paste this in layout/theme.liquid just before
============================================================
Your Cart
👉
Frequently Bought Together
============================================================ */
class CartDrawer {
constructor() {
this.threshold = FREE_SHIPPING_THRESHOLD;
this.errorTimers = {};
this.upsellIds = [];
// All products in store for upsell (fetched from Shopify)
this.allProducts = [];
this.init();
}
async init() {
await this.fetchAllProducts();
this.bindEvents();
await this.renderCart();
this.pickUpsell();
}
/* ---- SHOPIFY API ---- */
async getCart() {
const res = await fetch('/cart.js');
return res.json();
}
async fetchAllProducts() {
// Fetch all products for upsell — adjust limit as needed
try {
const res = await fetch('/products.json?limit=20');
const data = await res.json();
this.allProducts = data.products.map(p => ({
id: p.variants[0].id,
productId: p.id,
name: p.title,
price: parseFloat(p.variants[0].price),
stock: p.variants[0].inventory_quantity,
image: p.images[0]?.src || '',
handle: p.handle,
}));
} catch (e) {
console.error('Could not fetch products:', e);
}
}
async addToCartAPI(variantId, qty = 1) {
const res = await fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: variantId, quantity: qty }),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.description || 'Could not add item');
}
return res.json();
}
async changeQtyAPI(variantId, qty) {
const res = await fetch('/cart/change.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: variantId, quantity: qty }),
});
return res.json();
}
async removeFromCartAPI(variantId) {
return this.changeQtyAPI(variantId, 0);
}
/* ---- OPEN / CLOSE ---- */
bindEvents() {
document.getElementById('cd-close')?.addEventListener('click', () => this.close());
document.getElementById('cart-overlay')?.addEventListener('click', () => this.close());
// Open drawer when any add-to-cart button on the page is clicked
document.querySelectorAll('[data-open-cart], .cart-icon, #cart-icon, .header__cart').forEach(el => {
el.addEventListener('click', e => { e.preventDefault(); this.open(); });
});
}
open() {
document.getElementById('cart-drawer')?.classList.add('cd-open');
document.getElementById('cart-overlay')?.classList.add('cd-open');
document.getElementById('cart-drawer')?.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden';
this.renderCart();
}
close() {
document.getElementById('cart-drawer')?.classList.remove('cd-open');
document.getElementById('cart-overlay')?.classList.remove('cd-open');
document.getElementById('cart-drawer')?.setAttribute('aria-hidden', 'true');
document.body.style.overflow = '';
}
/* ---- RENDER CART ---- */
async renderCart() {
const cart = await this.getCart();
const itemsEl = document.getElementById('cd-items');
if (!itemsEl) return;
if (cart.items.length === 0) {
itemsEl.innerHTML = `