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();