// W2Go screens — Checkout, Tracking, Order Done, Orders list, Account
const { useState: useStateC, useEffect: useEffectC, useRef: useRefC } = React;
// ───────────────────────────────────────────────────────────────
// 9. CHECKOUT
// ───────────────────────────────────────────────────────────────
function CheckoutScreen({ go, cart, address, placeOrder }) {
const [dropoff, setDropoff] = useStateC('door'); // door | hand | leave
const [pay, setPay] = useStateC('applepay');
const [tip, setTip] = useStateC(10);
const [notes, setNotes] = useStateC('');
const sub = cart.reduce((a, c) => a + c.unitPrice * c.qty, 0);
const delivery = 8, fee = 4;
const total = sub + delivery + fee + tip;
const tips = [0, 5, 10, 15, 20];
return (
go('cart')} title="Checkout"/>
{/* Address */}
go('location')} style={btnLink}>Change}>
{address?.label || 'Home'}
{address?.line || 'Tower 2, The Pearl'} · {address?.city || 'Doha, Qatar'}
{/* Drop-off */}
{[
{ id: 'door', label: 'Hand it to me at the door', icon: Ic.Door },
{ id: 'leave', label: 'Leave at the door', icon: Ic.Box },
{ id: 'hand', label: 'Meet outside', icon: Ic.User },
].map(o => {
const I = o.icon; const on = dropoff === o.id;
return (
setDropoff(o.id)} style={{
display: 'flex', alignItems: 'center', gap: 12, padding: '12px 14px', borderRadius: 14,
border: on ? '2px solid var(--w-green)' : '1.5px solid rgba(23,25,25,0.08)',
background: on ? 'rgba(0,76,75,0.04)' : '#fff', cursor: 'pointer',
fontFamily: 'inherit', textAlign: 'left'
}}>
{o.label}
);
})}
setNotes(e.target.value)}
placeholder="Note to rider (apt buzzer, gate code…)"
style={{
marginTop: 4, padding: '12px 14px', borderRadius: 12, fontSize: 13,
border: '1.5px solid rgba(23,25,25,0.08)', background: '#fff', outline: 'none',
fontFamily: 'inherit'
}}/>
{/* Payment */}
{[
{ id: 'applepay', label: 'Apple Pay', sub: 'Touch ID', icon: Ic.Apple, bg: '#000', fg: '#fff' },
{ id: 'card', label: 'Visa ••4821', sub: 'Default', icon: Ic.Card, bg: 'var(--w-green)', fg: 'var(--w-cream)' },
{ id: 'cash', label: 'Cash on delivery', sub: 'Exact change preferred', icon: Ic.Wallet, bg: 'var(--w-onyx)', fg: 'var(--w-cream)' },
].map(p => {
const I = p.icon; const on = pay === p.id;
return (
setPay(p.id)} style={{
display: 'flex', alignItems: 'center', gap: 12, padding: '10px 14px', borderRadius: 14,
border: on ? '2px solid var(--w-green)' : '1.5px solid rgba(23,25,25,0.08)',
background: on ? 'rgba(0,76,75,0.04)' : '#fff', cursor: 'pointer',
fontFamily: 'inherit', textAlign: 'left'
}}>
);
})}
{/* Tip */}
100% TO RIDER}>
{tips.map(t => (
setTip(t)} style={{
padding: '12px 4px', borderRadius: 12,
border: tip === t ? '2px solid var(--w-green)' : '1.5px solid rgba(23,25,25,0.1)',
background: tip === t ? 'rgba(0,76,75,0.06)' : '#fff',
fontFamily: 'inherit', cursor: 'pointer',
}}>
{t === 0 ? '—' : W2_money(t)}
{t === 0 ? 'None' : t < 8 ? 'Tap' : t < 12 ? 'Solid' : t < 18 ? 'Big up' : 'Hero'}
))}
{/* Summary */}
{cart.map((l, i) => (
{l.qty}× {l.item.name}
{W2_money(l.unitPrice * l.qty)}
))}
placeOrder({ dropoff, pay, tip, notes, total })}
iconRight={}>Place order · {W2_money(total)}
Tap to confirm — you can cancel for free for the first 2 minutes.
);
}
// ───────────────────────────────────────────────────────────────
// 9.5 PAYMENT — Apple Pay-style sheet → success → tracking
// ───────────────────────────────────────────────────────────────
function PaymentScreen({ go, order, paymentComplete }) {
const [stage, setStage] = useStateC('ready'); // ready | auth | processing | success
const total = order?.total || 0;
const pay = order?.pay || 'applepay';
const tip = order?.tip || 0;
const items = order?.items || [];
useEffectC(() => {
if (stage === 'auth') {
const t = setTimeout(() => setStage('processing'), 1400);
return () => clearTimeout(t);
}
if (stage === 'processing') {
const t = setTimeout(() => setStage('success'), 1600);
return () => clearTimeout(t);
}
if (stage === 'success') {
const t = setTimeout(() => paymentComplete(), 1200);
return () => clearTimeout(t);
}
}, [stage]);
const payMeta = {
applepay: { label: 'Apple Pay', sub: 'Face ID', bg: '#000', fg: '#fff', icon: Ic.Apple },
card: { label: 'Visa •• 4821', sub: 'Card', bg: 'var(--w-green)', fg: 'var(--w-cream)', icon: Ic.Card },
cash: { label: 'Cash on delivery', sub: 'Pay rider', bg: 'var(--w-onyx)', fg: 'var(--w-cream)', icon: Ic.Wallet },
}[pay] || {};
// Cash: skip processing — just confirm
const isCash = pay === 'cash';
return (
{/* dim photographic bg */}
{/* close */}
go('checkout')} style={{
width: 40, height: 40, borderRadius: 999, background: 'rgba(255,255,255,0.08)', border: 'none',
display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', color: 'var(--w-cream)'
}} disabled={stage !== 'ready'}>
{/* Brand */}
{stage === 'success' ? 'PAYMENT CONFIRMED' : 'PAY WITH'}
{/* Payment card */}
{payMeta.label}
{payMeta.sub}
{W2_money(total)}
{/* Bill stub */}
ORDER · W2-{Math.floor(Math.random()*9000+1000)}
{items.length} {items.length === 1 ? 'item' : 'items'}
{items.slice(0, 3).map((l, i) => (
{l.qty}× {l.item.name}
{W2_money(l.unitPrice * l.qty)}
))}
{items.length > 3 && (
+ {items.length - 3} more
)}
Delivery · service {W2_money(12)}
Rider tip {W2_money(tip)}
{/* Center action: face-id / spinner / check */}
{stage === 'ready' && (
<>
{pay === 'applepay' ? : (pay === 'card' ? : )}
{pay === 'applepay' && 'Double-click side button'}
{pay === 'card' && 'Confirm with card'}
{pay === 'cash' && 'Confirm cash order'}
{pay === 'applepay' && 'Then Face ID will authorize the payment.'}
{pay === 'card' && 'Your card will be charged once the rider picks up.'}
{pay === 'cash' && 'Hand the rider exact change if possible.'}
>
)}
{stage === 'auth' && (
<>
Authorizing…
Hold still, just a moment.
>
)}
{stage === 'processing' && (
<>
Sending to kitchen…
{isCash ? 'Order locked in.' : 'Payment cleared.'} Cooking starts now.
>
)}
{stage === 'success' && (
<>
Cravings locked.
Your order is in the W Doha kitchen. We'll text you when the rider's rolling.
>
)}
{stage === 'ready' && (
setStage(isCash ? 'processing' : 'auth')}
iconRight={}>
{pay === 'applepay' && 'Pay ' + W2_money(total)}
{pay === 'card' && 'Confirm ' + W2_money(total)}
{pay === 'cash' && 'Place order'}
)}
{(stage === 'auth' || stage === 'processing') && (
Please wait…
)}
{stage === 'success' && (
}>
Track your order
)}
Secured by W2GO · 256-bit TLS · Doha, Qatar
);
}
// Face-ID style icon
function FaceID({ color = 'currentColor' }) {
return (
);
}
// Spinner
function Spinner({ size = 56, color = 'var(--w-cream)' }) {
return (
);
}
const btnLink = {
background: 'none', border: 'none', color: 'var(--w-green)', fontWeight: 700,
fontSize: 12, cursor: 'pointer', fontFamily: 'inherit'
};
function Block({ title, action, children }) {
return (
);
}
// ───────────────────────────────────────────────────────────────
// 10. TRACKING
// ───────────────────────────────────────────────────────────────
const TRACK_STAGES = [
{ id: 'placed', label: 'Order placed', sub: 'We got it.', t: 0 },
{ id: 'cook', label: 'Cooking', sub: 'Pans on, heat up.', t: 4 },
{ id: 'pack', label: 'Packed', sub: 'Sealed & rider-ready', t: 9 },
{ id: 'rider', label: 'Rider on the way', sub: 'Heading to you', t: 14 },
{ id: 'delivered', label: 'Delivered', sub: 'Eat the vibe.', t: 23 },
];
function TrackingScreen({ go, order, finishOrder }) {
const [t, setT] = useStateC(0); // 0..23
useEffectC(() => {
const iv = setInterval(() => setT(x => Math.min(x + 1, 23)), 1400);
return () => clearInterval(iv);
}, []);
const stageIdx = TRACK_STAGES.findIndex((s, i) => i === TRACK_STAGES.length - 1 || t < TRACK_STAGES[i + 1].t);
const stage = TRACK_STAGES[stageIdx];
const minsLeft = Math.max(0, 23 - t);
const rider = W2_RIDERS[0];
return (
{/* Map */}
{/* roads */}
{/* origin */}
W2GO Kitchen
{/* destination */}
You · The Pearl
{/* rider position — interp along path */}
{(() => {
const p = Math.min(1, t / 23);
const x = 40 + (378 - 40) * p;
const y = 358 - (358 - 82) * (p ** 0.7);
return (
);
})()}
{/* back */}
go('home')} style={{
position: 'absolute', top: 14, left: 14, width: 44, height: 44, borderRadius: 999,
background: 'rgba(249,242,232,0.92)', backdropFilter: 'blur(10px)', border: 'none',
display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', color: 'var(--w-onyx)'
}}>
{/* Sheet */}
ARRIVING IN
{minsLeft} min
{stage.label} · {stage.sub}
Skip ahead →
{/* progress bar */}
{TRACK_STAGES.map((s, i) => (
))}
{/* timeline */}
{TRACK_STAGES.map((s, i) => {
const done = i < stageIdx;
const live = i === stageIdx;
return (
{done ? : null}
{live && }
{s.label}
{i === stageIdx ? 'now' : (done ? '✓' : `${TRACK_STAGES[i].t} min`)}
);
})}
{/* Rider card */}
{stageIdx >= 3 && (
{rider.name.slice(0, 1)}
{rider.name}
{rider.rating} · {rider.vehicle}
)}
{stageIdx === TRACK_STAGES.length - 1 && (
Rate your delivery
)}
);
}
// ───────────────────────────────────────────────────────────────
// 11. RATE / ORDER DONE
// ───────────────────────────────────────────────────────────────
function RateScreen({ go, doneRating }) {
const [stars, setStars] = useStateC(0);
const [tags, setTags] = useStateC([]);
const toggle = (t) => setTags(tags.includes(t) ? tags.filter(x => x !== t) : [...tags, t]);
return (
go('home')}/>
DELIVERED · ENJOY
How did that hit?
{[1,2,3,4,5].map(n => (
setStars(n)} style={{
background: 'none', border: 'none', cursor: 'pointer', padding: 4,
color: n <= stars ? '#C24A2C' : 'rgba(23,25,25,0.15)',
transform: n <= stars ? 'scale(1.05)' : 'scale(1)',
transition: 'transform .15s'
}}>
))}
{stars === 0 ? 'Tap to rate.' : stars < 3 ? 'We can do better.' : stars < 5 ? 'Pretty solid.' : 'Crave Worthy.'}
What made it good?
{['Tasted fresh', 'Hot on arrival', 'Sealed & tidy', 'Quick rider', 'Portion on point', 'Worth the price'].map(t => (
toggle(t)} kind={tags.includes(t) ? 'green' : 'default'}>{t}
))}
Skip
Send rating
);
}
// ───────────────────────────────────────────────────────────────
// 12. ORDERS LIST
// ───────────────────────────────────────────────────────────────
const MOCK_ORDERS = [
{ id: 'W2-08412', date: 'Today · 12:42', items: 'Omakase 12, Dragon Roll, Iced Yuzu Lemonade', total: 245, status: 'Delivered' },
{ id: 'W2-08193', date: 'Yesterday · 19:08', items: 'Yuzu Salmon Poké, Matcha Oat Latte', total: 80, status: 'Delivered' },
{ id: 'W2-08055', date: 'Tue 14 · 13:30', items: 'Rainbow Roll, Tempura Prawns, Edamame', total: 160, status: 'Delivered' },
{ id: 'W2-07982', date: 'Mon 13 · 20:15', items: 'Spicy Salmon Maki, Beef Gyoza, Mochi Trio', total: 132, status: 'Cancelled' },
];
function OrdersScreen({ go }) {
return (
Past orders
Reorder in two taps.
{MOCK_ORDERS.map((o, i) => (
{o.id}
{o.status.toUpperCase()}
{o.date}
{o.items}
{W2_money(o.total)}
View
go('home')}>Reorder
))}
);
}
// ───────────────────────────────────────────────────────────────
// 13. ACCOUNT
// ───────────────────────────────────────────────────────────────
function AccountScreen({ go, user, signOut }) {
const M = window.W2_MEMBER || {};
const name = user?.name || M.name || 'Friend';
const perks = M.perks || [];
return (
{/* Profile hero */}
{name.slice(0, 1).toUpperCase()}
{name}
{M.tier || 'Gold'} member
{user?.phone || M.phone}
Edit
{[
{ l: 'Orders', v: String(M.orders || 24), sub: 'all time' },
{ l: 'Saved', v: String(M.saved || 8), sub: 'favorites' },
{ l: 'Vibe Points', v: (M.points || 2480).toLocaleString(), sub: `${M.pointsToNext || 520} to ${M.nextTier || 'Platinum'}` },
].map(s => (
{s.l.toUpperCase()}
{s.v}
{s.sub}
))}
{/* Membership card — perks (Soho House exclusivity) */}
Your {M.tier || 'Gold'} perks
Member since {M.memberSince || '2024'}
{M.pointsToNext || 520} points to {M.nextTier || 'Platinum'}
{perks.map((p, i) => {
const I = Ic[p.icon] || Ic.Spark;
return (
);
})}
{/* Menu rows */}
{[
{ l: 'Addresses', i: Ic.Pin, cb: () => go('location') },
{ l: 'Payment methods', i: Ic.Card, cb: () => null },
{ l: 'Order history', i: Ic.Receipt, cb: () => go('orders') },
{ l: 'Favorites', i: Ic.Heart, cb: () => null },
{ l: 'Notifications', i: Ic.Bell, cb: () => null },
{ l: 'Help & Support', i: Ic.Spark, cb: () => null },
].map((r, i, arr) => {
const I = r.i;
return (
{r.l}
);
})}
Sign out
W2GO · CRAFTED BY W DOHA · v1.0
);
}
Object.assign(window, { CheckoutScreen, PaymentScreen, TrackingScreen, RateScreen, OrdersScreen, AccountScreen });