/* ========================================================== YAPPY. — Shared admin settings (prices, offers, coupons, doctor profile). Read by the storefront AND the admin panel. Stored locally for the prototype only. ========================================================== */ const ADMIN_KEY = 'yappy.admin.v1'; const DEFAULT_ADMIN = { prices: { brb: null, dnd: null, offline: null }, shipping: null, stock: { brb: true, dnd: true, offline: true }, offers: [], // { id, title, pct, skus:[...], active } coupons: [], // { id, code, type:'pct'|'flat', value, min, expires, active, uses } doctors: [ { id: 'd1', email: 'doctor@yappy.demo', name: '', qual: '', reg: '', council: '', phone: '', clinic: 'Yappy Telehealth (prototype)' }, ], }; let __admin = (() => { try { return { ...DEFAULT_ADMIN, ...(JSON.parse(localStorage.getItem(ADMIN_KEY)) || {}) }; } catch (e) { return { ...DEFAULT_ADMIN }; } })(); const __adminSubs = new Set(); function getAdmin() { return __admin; } function saveAdmin(patch) { __admin = { ...__admin, ...(typeof patch === 'function' ? patch(__admin) : patch) }; localStorage.setItem(ADMIN_KEY, JSON.stringify(__admin)); syncPricing(); __adminSubs.forEach((f) => f(__admin)); } window.addEventListener('storage', (e) => { if (e.key !== ADMIN_KEY) return; try { __admin = { ...DEFAULT_ADMIN, ...JSON.parse(e.newValue || '{}') }; } catch (x) {} syncPricing(); __adminSubs.forEach((f) => f(__admin)); }); function useAdmin() { const [a, setA] = React.useState(__admin); React.useEffect(() => { __adminSubs.add(setA); return () => __adminSubs.delete(setA); }, []); return a; } function syncPricing() { PRICING.product = { ...__admin.prices }; PRICING.shipping = __admin.shipping; } syncPricing(); /* ---------- offers / pricing ---------- */ function offerFor(sku, a = __admin) { const live = a.offers.filter((o) => o.active && o.skus.includes(sku)); return live.sort((x, y) => y.pct - x.pct)[0] || null; } function priceFor(sku, a = __admin) { const base = a.prices[sku]; if (typeof base !== 'number') return { base: null, now: null, offer: null }; const o = offerFor(sku, a); return { base, now: o ? Math.round(base * (1 - o.pct / 100)) : base, offer: o }; } /* ---------- coupons ---------- */ function checkCoupon(code, subtotal, a = __admin) { if (!code) return { ok: false, msg: '' }; const c = a.coupons.find((x) => x.code.toUpperCase() === code.trim().toUpperCase()); if (!c || !c.active) return { ok: false, msg: 'This code isn’t valid.' }; if (c.expires && new Date(c.expires + 'T23:59:59') < new Date()) return { ok: false, msg: 'This code has expired.' }; if (subtotal === null) return { ok: true, coupon: c, discount: null, msg: 'Code applied. Discount shown once prices are set.' }; if (c.min && subtotal < c.min) return { ok: false, msg: `Add ${fmtPrice(c.min - subtotal)} more to use this code.` }; const discount = c.type === 'pct' ? Math.round(subtotal * c.value / 100) : Math.min(subtotal, c.value); return { ok: true, coupon: c, discount, msg: `${c.code} applied — you save ${fmtPrice(discount)}.` }; } /* ---------- cart sums (overrides data.jsx cartTotal) ---------- */ function cartSums(cart, couponCode) { let subtotal = 0, saving = 0, known = true; for (const it of cart) { const p = priceFor(it.id); if (p.now === null) { known = false; break; } subtotal += p.now * it.qty; saving += (p.base - p.now) * it.qty; } const sub = known ? subtotal : null; const cc = couponCode ? checkCoupon(couponCode, sub) : { ok: false }; const discount = cc.ok && typeof cc.discount === 'number' ? cc.discount : 0; const ship = typeof __admin.shipping === 'number' ? __admin.shipping : null; const total = sub !== null && ship !== null ? Math.max(0, sub - discount) + ship : null; return { subtotal: sub, saving: known ? saving : 0, discount, coupon: cc, shipping: ship, total }; } function cartTotal(cart) { return cartSums(cart).total; } /* ---------- price display ---------- */ function SkuPrice({ id, big }) { const a = useAdmin(); const p = priceFor(id, a); if (p.now === null) return Price TBC; return ( {fmtPrice(p.now)} {p.offer && {fmtPrice(p.base)}} {p.offer && {p.offer.pct}% off} ); } const uid = (pfx) => pfx + Math.random().toString(36).slice(2, 7).toUpperCase(); Object.assign(window, { ADMIN_KEY, getAdmin, saveAdmin, useAdmin, offerFor, priceFor, checkCoupon, cartSums, cartTotal, SkuPrice, uid });