cart.js
5.7 kB · javascript · 178 lines
1const KEY = 'cm-cart';2const SHOP = '{{SHOP}}';34/* STORE */56export function load() {7 try {8 const raw = localStorage.getItem(KEY);9 const items = raw ? JSON.parse(raw) : [];10 return Array.isArray(items) ? items : [];11 } catch {12 return [];13 }14}1516export function save(items) {17 try {18 if (items.length) localStorage.setItem(KEY, JSON.stringify(items));19 else localStorage.removeItem(KEY);20 } catch {}21 window.dispatchEvent(new CustomEvent('cart', { detail: items }));22 return items;23}2425export function add(line) {26 const items = load();27 const found = items.find((item) => item.id === line.id);28 if (found) found.qty += 1;29 else items.push({ ...line, qty: 1 });30 return save(items);31}3233export const remove = (id) => save(load().filter((item) => item.id !== id));3435export function setQty(id, qty) {36 if (qty < 1) return remove(id);37 const items = load();38 const found = items.find((item) => item.id === id);39 if (found) found.qty = qty;40 return save(items);41}4243export const clear = () => save([]);4445export const count = (items) => items.reduce((sum, item) => sum + item.qty, 0);4647export const total = (items) => items.reduce((sum, item) => sum + Number(item.price) * item.qty, 0).toFixed(2);4849export const checkoutUrl = (items) =>50 items.length ? `https://${SHOP}/cart/${items.map((item) => `${item.id}:${item.qty}`).join(',')}` : '';5152const money = (amount) => `$${Number(amount).toFixed(2)}`;5354/* PRODUCT */5556function stock() {57 const button = document.querySelector('[data-add]');58 if (!button) return;59 const buy = document.querySelector('[data-buy]');60 const price = document.querySelector('[data-price]');61 const picked = document.querySelector('.sizes button[aria-pressed="true"]');62 if (picked) {63 button.dataset.variant = picked.dataset.variant;64 button.dataset.price = picked.dataset.price;65 button.dataset.size = picked.dataset.size;66 }67 if (price) price.textContent = money(button.dataset.price);68 if (buy) buy.href = SHOP ? `https://${SHOP}/cart/${button.dataset.variant}:1` : '/cart/';69}7071function pick(button) {72 for (const other of document.querySelectorAll('.sizes button')) other.setAttribute('aria-pressed', String(other === button));73 stock();74}7576function shot() {77 const image = document.querySelector('[data-mockups] img');78 return image ? image.currentSrc || image.src : '';79}8081function drop(button) {82 if (button.disabled) return;83 add({84 id: button.dataset.variant,85 key: button.dataset.key,86 title: button.dataset.title,87 size: button.dataset.size,88 price: button.dataset.price,89 image: shot(),90 });91 button.classList.add('added');92 button.textContent = 'Added!';93 setTimeout(() => {94 button.classList.remove('added');95 button.textContent = 'Add to cart';96 }, 1500);97}9899/* CART PAGE */100101function lines() {102 const host = document.querySelector('[data-cart-lines]');103 const sum = document.querySelector('[data-cart-sum]');104 if (!host || !sum) return;105 const items = load();106 host.replaceChildren();107 sum.replaceChildren();108 if (!items.length) {109 const lead = document.createElement('p');110 lead.className = 'lead';111 lead.textContent = 'Your cart is empty.';112 const back = document.createElement('a');113 back.href = '/';114 back.textContent = 'Go to the shop';115 sum.append(lead, back);116 return;117 }118 const template = document.getElementById('line');119 for (const item of items) {120 const node = template.content.cloneNode(true);121 const link = node.querySelector('[data-href]');122 link.href = `/products/${item.key}/`;123 const image = node.querySelector('img');124 image.src = item.image;125 image.alt = item.key;126 const title = node.querySelector('[data-title]');127 title.href = `/products/${item.key}/`;128 title.textContent = `${item.title} (${item.key})`;129 node.querySelector('[data-size]').textContent = `${item.size} · ${money(item.price)}`;130 node.querySelector('[data-qty]').textContent = String(item.qty);131 node.querySelector('[data-total]').textContent = money(Number(item.price) * item.qty);132 node.querySelector('[data-dec]').dataset.id = item.id;133 node.querySelector('[data-inc]').dataset.id = item.id;134 node.querySelector('[data-remove]').dataset.id = item.id;135 node.querySelector('[data-dec]').dataset.qty = String(item.qty - 1);136 node.querySelector('[data-inc]').dataset.qty = String(item.qty + 1);137 host.append(node);138 }139 const price = document.createElement('p');140 price.className = 'num';141 price.textContent = `${count(items)} item${count(items) === 1 ? '' : 's'} · ${money(total(items))}`;142 const go = document.createElement('a');143 go.className = 'buy primary';144 go.href = checkoutUrl(items);145 go.dataset.checkout = '';146 go.textContent = 'Checkout';147 const wipe = document.createElement('button');148 wipe.type = 'button';149 wipe.dataset.clear = '';150 wipe.textContent = 'Clear cart';151 const back = document.createElement('a');152 back.href = '/';153 back.textContent = 'Continue shopping';154 sum.append(price, go, wipe, back);155}156157/* WIRE */158159document.addEventListener('click', (event) => {160 const target = event.target instanceof Element ? event.target : null;161 if (!target) return;162 const size = target.closest('.sizes button');163 if (size) return pick(size);164 const drops = target.closest('[data-add]');165 if (drops) return drop(drops);166 const dec = target.closest('[data-dec], [data-inc]');167 if (dec) return void setQty(dec.dataset.id, Number(dec.dataset.qty));168 const gone = target.closest('[data-remove]');169 if (gone) return void remove(gone.dataset.id);170 const wipe = target.closest('[data-clear]');171 if (wipe) return void clear();172});173174window.addEventListener('cart', lines);175window.addEventListener('flip', stock);176window.addEventListener('storage', lines);177stock();178lines();