// src/tracking.jsx — Order-placed tracker. Distinct visual shape from cart/product // screens: full-bleed hero with a hand-drawn map SVG, a live-ticking countdown, // a step timeline, and a minimal order summary. No card grid. function Tracking({ onNav, order }) { const isMobile = useMedia("(max-width: 720px)"); // Fallback when user deep-links without an order (e.g., tweaks jump). const safeOrder = order || { storeId: "kalymnos", number: "4821", itemsCount: 3, items: [ { id: "deli-eggcheese", name: "Bacon-Egg-Cheese on a roll", qty: 1, unit: "6\" hero", price: 8.50 }, { id: "drk-gatorade", name: "Gatorade Lemon-Lime", qty: 1, unit: "32oz btl", price: 2.99 }, { id: "drk-topo", name: "Topo Chico", qty: 2, unit: "355ml btl", price: 2.50 }, ], total: 24.50, placedAt: Date.now(), etaSeconds: 18 * 60, }; const store = STORES.find(s => s.id === safeOrder.storeId) || STORES[0]; const [remaining, setRemaining] = React.useState(safeOrder.etaSeconds); React.useEffect(() => { const id = setInterval(() => setRemaining(r => Math.max(0, r - 1)), 1000); return () => clearInterval(id); }, []); const elapsed = safeOrder.etaSeconds - remaining; const progress = Math.min(1, elapsed / safeOrder.etaSeconds); // Four-step timeline — "current" step advances with elapsed time. const steps = [ { id: "placed", label: "Order placed", sub: "We texted the store", at: 0.0 }, { id: "packed", label: "Packed", sub: `${store.name} bagged it`, at: 0.25 }, { id: "out", label: "Out for delivery", sub: "On a bike, not a truck", at: 0.55 }, { id: "home", label: "At your door", sub: "Leave it with the doorman", at: 1.0 }, ]; const currentIdx = steps.reduce((acc, s, i) => progress >= s.at ? i : acc, 0); const mm = String(Math.floor(remaining / 60)).padStart(2, "0"); const ss = String(remaining % 60).padStart(2, "0"); return (
{/* Hero — map + countdown, not a card stack */}
Order #{safeOrder.number} · Live

On the way.
From {store.name.split(" ")[0]} to {isMobile ? "you" : "your door"}.

{mm}:{ss}
{remaining > 0 ? "estimated to door" : "Should be knocking now."}
{/* Tiny map — inline SVG, not an image. Biker moves along a curve. */} {!isMobile && (
)}
{/* Timeline — distinct shape: vertical ticks with an active ring */}
Progress
    {steps.map((s, i) => { const done = i < currentIdx; const active = i === currentIdx; return (
  1. {done ? : active ? : null}
    {s.label}
    {s.sub}
  2. ); })}
{/* Summary — minimal, not another card grid */}
); } function Row2({ l, r }) { return (
{l} {r}
); } // TrackingMap — minimal inline SVG. Store pin at top-right, home pin at bottom-left, // a curved route with a moving bike marker driven by `progress` [0..1]. function TrackingMap({ progress, storeName }) { // Control points for a gentle quadratic curve from store to home. const from = { x: 254, y: 46 }; // store corner const to = { x: 46, y: 254 }; // home corner const cp = { x: 60, y: 60 }; // curve control // Quadratic Bezier at time t const t = progress; const mt = 1 - t; const bx = mt * mt * from.x + 2 * mt * t * cp.x + t * t * to.x; const by = mt * mt * from.y + 2 * mt * t * cp.y + t * t * to.y; return ( {/* Grid — suggests a map without being literal */} {/* Route (dashed — traveled portion draws first) */} {/* Filled portion */} {/* Store pin */} {storeName.split(" ")[0].toUpperCase()} {/* Home pin */} YOU {/* Bike marker */} 🚴 ); } window.Tracking = Tracking; // ─── Orders (list) ───────────────────────────────────────────────────────── // Entry point for the Orders tab. Shows 2 active orders as compact tracker // cards up top, then a list of recent orders below. Tapping a card opens the // full Tracking screen for that order. function Orders({ onNav, activeOrder, setActiveOrder }) { const isMobile = useMedia("(max-width: 720px)"); // If there's a live order from checkout, put it at the front. Otherwise two // mocked in-progress orders so the page has meaningful content in the demo. // Each fallback carries a realistic item snapshot so the Tracking sidebar // has something to render when the user taps "Track order". const now = Date.now(); const fallbacks = [ { number: "4821", storeId: "kalymnos", itemsCount: 3, total: 24.50, items: [ { id: "deli-eggcheese", name: "Bacon-Egg-Cheese on a roll", qty: 1, unit: "6\" hero", price: 8.50 }, { id: "drk-gatorade", name: "Gatorade Lemon-Lime", qty: 1, unit: "32oz btl", price: 2.99 }, { id: "drk-topo", name: "Topo Chico", qty: 2, unit: "355ml btl", price: 2.50 }, ], placedAt: now - 4 * 60 * 1000, etaSeconds: 14 * 60 }, { number: "4833", storeId: "verde", itemsCount: 6, total: 38.75, items: [ { id: "prd-tomato", name: "Vine tomatoes", qty: 1, unit: "lb", price: 3.49 }, { id: "prd-apple", name: "Honeycrisp apples", qty: 2, unit: "lb", price: 3.99 }, { id: "prd-lemon", name: "Lemons", qty: 3, unit: "each", price: 0.79 }, { id: "prd-avocado",name: "Hass avocado", qty: 2, unit: "each", price: 2.49 }, ], placedAt: now - 11 * 60 * 1000, etaSeconds: 7 * 60 }, ]; const inProgress = activeOrder ? [activeOrder, fallbacks[1]] : fallbacks; const openTracking = (order) => { setActiveOrder?.(order); onNav({ screen: "tracking" }); }; const recent = [ { number: "4802", storeId: "kalymnos", itemsCount: 3, total: 24.50, when: "Mar 28 · 6:14 PM", status: "Delivered", items: ["Bacon-Egg-Cheese", "Gatorade Lemon-Lime", "Topo Chico"] }, { number: "4791", storeId: "fig", itemsCount: 2, total: 47.00, when: "Mar 25 · 8:02 PM", status: "Delivered", items: ["Meiomi Pinot Noir", "Whispering Angel"] }, { number: "4785", storeId: "liu", itemsCount: 5, total: 18.20, when: "Mar 22 · 11:47 PM", status: "Delivered", items: ["Advil 50ct", "Red Bull 12oz", "Liquid Death ×2", "Gum"] }, { number: "4770", storeId: "arbor", itemsCount: 4, total: 41.85, when: "Mar 18 · 10:15 AM", status: "Delivered", items: ["Hammer", "Picture hooks", "Drywall anchors", "Level"] }, { number: "4758", storeId: "kalymnos", itemsCount: 7, total: 52.40, when: "Mar 14 · 5:30 PM", status: "Delivered", items: ["Modelo 12pk", "Flamin' Hot Cheetos", "Oreo", "Topo Chico ×2", "Peanuts", "Doritos"] }, ]; return (
Your orders

In progress & recent.

{/* In-progress section */}
In progress · {inProgress.length}
Live ETA from each store
{inProgress.map(o => ( openTracking(o)} /> ))}
{/* Recent section */}
Recent
Last 30 days · {recent.length}
{recent.map(o => ( onNav({ screen: "store", storeId: o.storeId })} /> ))}
); } // Compact active-order card — store thumb, live countdown, progress bar, Track. function ActiveOrderCard({ order, onOpen }) { const store = STORES.find(s => s.id === order.storeId) || STORES[0]; const [remaining, setRemaining] = React.useState(order.etaSeconds); React.useEffect(() => { const id = setInterval(() => setRemaining(r => Math.max(0, r - 1)), 1000); return () => clearInterval(id); }, []); const progress = Math.min(1, (order.etaSeconds - remaining) / order.etaSeconds); const mm = String(Math.floor(remaining / 60)).padStart(2, "0"); const ss = String(remaining % 60).padStart(2, "0"); const stage = progress < 0.25 ? "Packing" : progress < 0.55 ? "Packed" : progress < 1 ? "On the way" : "At your door"; return (
{store.name} Live
#{order.number} · {order.itemsCount} items · {money(order.total)}
{stage}
{mm}:{ss}
to door
~{store.distance}
}> Track order alert("We'll text the rider.")}> Message rider
); } // Dense past-order row — store, order number, when, items count, total. function RecentOrderRow({ order, onOpen, isMobile }) { const store = STORES.find(s => s.id === order.storeId) || STORES[0]; return ( ); } window.Orders = Orders;