/* ==========================================================
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 });