/* ========================================================== YAPPY. — Checkout · Orders · Prescription upload · Free consult All services simulated. Nothing is sent, charged or stored remotely. ========================================================== */ function Field({ id, label, err, children }) { return (
{children} {err && {err}}
); } /* ================= Checkout (single page) ================= */ function Checkout() { const { state, set } = useStore(); const [f, setF] = React.useState({ email: '', phone: '', name: '', line: '', city: '', pin: '', age: false }); const [err, setErr] = React.useState({}); const [method, setMethod] = React.useState('UPI'); const [outcome, setOutcome] = React.useState('success'); const [busy, setBusy] = React.useState(false); const [failed, setFailed] = React.useState(false); const [code, setCode] = React.useState(''); const [applied, setApplied] = React.useState(''); const lock = React.useRef(false); useAdmin(); const upd = (k) => (e) => setF({ ...f, [k]: e.target.type === 'checkbox' ? e.target.checked : e.target.value }); if (state.cart.length === 0) { return (

Your cart is empty.

Shop Yappy
); } const sums = cartSums(state.cart, applied); const total = sums.total; const fill = () => setF({ email: 'sample@example.com', phone: '9876543210', name: 'Sample Customer', line: '1 Sample Street', city: 'Mumbai', pin: '400001', age: true }); const place = (e) => { e.preventDefault(); const x = {}; if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(f.email)) x.email = 'Enter a valid email.'; if (!/^[6-9]\d{9}$/.test(f.phone.replace(/\D/g, ''))) x.phone = 'Enter a 10-digit mobile number.'; if (f.name.trim().length < 2) x.name = 'Enter your full name.'; if (f.line.trim().length < 4) x.line = 'Enter your address.'; if (f.city.trim().length < 2) x.city = 'Enter your city.'; if (!/^\d{6}$/.test(f.pin)) x.pin = 'Enter a 6-digit PIN code.'; if (!f.age) x.age = 'Please confirm you are 18 or over.'; setErr(x); if (Object.keys(x).length) { const el = document.querySelector('.input.err, .check.err'); if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 140, behavior: 'smooth' }); return; } if (lock.current) return; // prevent duplicate submission lock.current = true; setBusy(true); setFailed(false); setTimeout(() => { lock.current = false; setBusy(false); if (outcome !== 'success') { setFailed(true); return; } const ref = 'YP-' + Math.random().toString(36).slice(2, 7).toUpperCase(); const order = { ref, items: state.cart, coupon: sums.coupon.ok ? sums.coupon.coupon.code : null, discount: sums.discount, total: sums.total, date: new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' }), name: f.name, city: f.city, contact: f.email, phone: f.phone, rx: 'awaiting', ship: 'held', file: null }; if (sums.coupon.ok) saveAdmin((a) => ({ coupons: a.coupons.map((c) => c.id === sums.coupon.coupon.id ? { ...c, uses: (c.uses || 0) + 1 } : c) })); set((s) => ({ cart: [], orders: [order, ...s.orders] })); go('/order/' + ref + '?new=1'); }, 1300); }; return (

Checkout.

1 · Contact
2 · Delivery
setF({ ...f, pin: e.target.value.replace(/\D/g, '') })} />
3 · Payment
{['UPI', 'Card', 'Net banking', 'Cash on delivery'].map((m) => )}

Simulated payment — no card or bank details are collected.

Preview:
{err.age && {err.age}} {failed &&
Payment didn’t go through. Nothing was charged. Please try again.
}
); } /* ================= Prescription upload ================= */ function RxUpload({ order }) { const { updateOrder } = useStore(); const [file, setFile] = React.useState(null); const [drag, setDrag] = React.useState(false); const [err, setErr] = React.useState(''); const [busy, setBusy] = React.useState(false); const inp = React.useRef(null); const pick = (fl) => { if (!fl) return; const ok = /\.(pdf|jpe?g|png|heic)$/i.test(fl.name); if (!ok) { setErr('Please upload a PDF, JPG or PNG.'); return; } if (fl.size > 10 * 1024 * 1024) { setErr('File is larger than 10 MB.'); return; } setErr(''); setFile({ name: fl.name, size: fl.size }); }; const useSample = () => { setErr(''); setFile({ name: 'sample-prescription.pdf', size: 248000 }); }; const submit = () => { if (!file || busy) return; setBusy(true); // Simulated upload — the file is NOT read, transmitted or stored. Only the filename is kept for display. setTimeout(() => { updateOrder(order.ref, { rx: 'verifying', file: file.name }); setBusy(false); }, 1200); }; return (
{ e.preventDefault(); setDrag(true); }} onDragLeave={() => setDrag(false)} onDrop={(e) => { e.preventDefault(); setDrag(false); pick(e.dataTransfer.files[0]); }} onClick={() => inp.current && inp.current.click()} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') inp.current.click(); }}> pick(e.target.files[0])} /> {file ? (
{file.name}
{Math.round(file.size / 1024)} KB · ready to submit
) : (
Drop your prescription here
or tap to choose · PDF, JPG or PNG · up to 10 MB
)}
{err && {err}}
{file && } {!file && }

Prototype: files are not uploaded or stored. In the live service, prescriptions are seen only by our verification team.

); } /* ================= Order detail ================= */ function OrderPage({ ref_ }) { const { state, updateOrder } = useStore(); const o = state.orders.find((x) => x.ref === ref_); const isNew = getQuery().get('new') === '1'; if (!o) return ; const steps = [ ['Order placed', true], ['Prescription uploaded', o.rx !== 'awaiting'], ['Prescription verified', o.rx === 'verified'], ['Shipped', ['shipped', 'delivered'].includes(o.ship)], ['Delivered', o.ship === 'delivered'], ]; const stopped = o.rx === 'rejected'; const curIdx = steps.findIndex((s) => !s[1]); return (
{isNew && (
Order {o.ref} · simulated

Thank you.
One last step.

Upload your prescription below and we’ll ship as soon as it’s verified.

)}
{!isNew &&

Order {o.ref}.

All orders
} {/* Prescription card */}
Prescription
{RX[o.rx].label}
{o.rx === 'awaiting' && <>

Upload a valid prescription for this order. Your order is held until it’s verified.

Don’t have a prescription? Consult our doctor for free.
Book
} {o.rx === 'verifying' &&

We’ve received {o.file} and are checking it. This usually doesn’t take long (timing TBC).

} {o.rx === 'verified' &&

{o.issuedRx ? 'Our doctor issued your prescription after your consultation. A copy has been emailed to you.' : 'Your prescription has been verified. Your order is being prepared.'}

{o.issuedRx && }
} {o.rx === 'awaiting' && o.consultId &&
Free consultation booked. If our doctor prescribes, it will be added here automatically.
} {o.rx === 'rejected' &&

We couldn’t accept the prescription provided, so this order has been cancelled. Any payment will be refunded in line with our refund policy (terms TBC).

Contact support
}
{/* Tracking */}
Tracking
{steps.map(([t, done], i) => { const stop = stopped && i === 2; return (
{done && }{stop && }
{stop ? 'Prescription not accepted · order cancelled' : t}
); })}

Prototype: verification and shipping are updated by the Operations team in the admin panel.

); } /* ================= Orders list ================= */ function Orders() { const { state, reset } = useStore(); return (

My orders.

Simulated
{state.orders.length === 0 ? (
No orders yet.

Once you order, you’ll upload your prescription and track delivery here.

Shop Yappy
) : (
{state.orders.map((o) => (
{o.items.map((it) =>
)}
{o.ref}
{o.date} · {o.items.map((i) => PRODUCTS[i.id].name + '.').join(' ')}
{RX[o.rx].label}{o.ship !== 'held' && {SHIP[o.ship].label}}
{o.rx === 'awaiting' ? 'Upload' : 'View'}
))}
)}
); } /* ================= Free doctor consultation ================= */ function FreeConsult() { const { set } = useStore(); const orderRef = getQuery().get('order'); const [f, setF] = React.useState({ name: '', phone: '', email: '', agey: '', sex: '', mode: 'Video', day: 0, slot: '', age: false }); const [err, setErr] = React.useState({}); const [busy, setBusy] = React.useState(false); const [done, setDone] = React.useState(null); const days = Array.from({ length: 5 }, (_, i) => { const d = new Date(2026, 9, 26 + i); return { dow: d.toLocaleDateString('en-GB', { weekday: 'short' }), d: d.getDate(), m: d.toLocaleDateString('en-GB', { month: 'short' }) }; }); const slots = ['10:00', '11:30', '13:00', '15:00', '17:30', '19:00']; const taken = (s) => ((s.charCodeAt(1) + f.day) % 4) === 0; const book = (e) => { e.preventDefault(); const x = {}; if (f.name.trim().length < 2) x.name = 'Enter your name.'; if (!/^[6-9]\d{9}$/.test(f.phone.replace(/\D/g, ''))) x.phone = 'Enter a 10-digit mobile number.'; if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(f.email)) x.email = 'Enter your email — your prescription is sent here.'; if (!f.slot) x.slot = 'Choose a time.'; if (!f.age) x.age = 'Please confirm you are 18 or over.'; setErr(x); if (Object.keys(x).length || busy) return; setBusy(true); setTimeout(() => { setBusy(false); const when = `${days[f.day].dow} ${days[f.day].d} ${days[f.day].m}, ${f.slot}`; setDone(when); const id = uid('CN-'); set((s) => ({ consults: [{ id, when, mode: f.mode, name: f.name, phone: f.phone, email: f.email, age: f.agey, sex: f.sex, orderRef: orderRef || null, status: 'scheduled', created: Date.now() }, ...s.consults], orders: orderRef ? s.orders.map((o) => o.ref === orderRef ? { ...o, consultId: id } : o) : s.orders, })); }, 1000); }; return (
Don’t have a prescription?

Consult our
doctor. For free.

A short phone or video call with a qualified doctor. No charge, no obligation.

{[['calendar', 'Book a time', 'Pick a slot that suits you.'], ['consult', 'Talk to a doctor', 'They’ll ask a few questions about your health.'], ['decision', 'Doctor decides', 'If appropriate, they issue a prescription — attached to your order automatically.']].map(([ic, h, d], i) => (
{h}

{d}

))}

The doctor decides independently. A prescription is never guaranteed, and we won’t pressure you either way. Doctor details and credentials will be shown once verified.

{done ? (
Booked · simulated
See you {done}.

{f.mode} consultation. In the live service, you’d get a confirmation by SMS. No message has been sent.

{orderRef &&

If the doctor prescribes, it will be attached to order {orderRef}.

}
{orderRef ? Back to order : Browse Yappy}
) : (
Book your free call
Free
setF({ ...f, name: e.target.value })} /> setF({ ...f, phone: e.target.value })} />
setF({ ...f, email: e.target.value })} />
setF({ ...f, agey: e.target.value.replace(/\D/g, '') })} />
{['F', 'M', 'Other'].map((x) => )}
{['Video', 'Phone'].map((m) => )}
{days.map((d, i) => ( ))}
{slots.map((s) => { const t = taken(s); return ; })}
{err.slot && {err.slot}}
{err.age && {err.age}}

Simulated booking — no appointment is created and no message is sent.

)}
); } function DemoControl({ title, children }) { return (
{title}Prototype only
{children}
); } Object.assign(window, { Checkout, OrderPage, Orders, FreeConsult, DemoControl }); function RxViewButton({ rx }) { const [open, setOpen] = React.useState(false); return (<> {open && setOpen(false)} />} ); } window.RxViewButton = RxViewButton;