// src/account.jsx — Account + Address-picker screens. Both real, both interactive.
// AccountScreen: identity + standing + saved addresses + payment + preferences + recent orders + neighborhood credits.
// AddressPicker: full-screen overlay; saved addresses, recent searches, "current location" tile, map preview, etc.
// ─── AccountScreen ─────────────────────────────────────────────────────────
function AccountScreen({ onNav }) {
const isMobile = useMedia("(max-width: 720px)");
// Local state for toggles & defaults — feels real on click.
const [defaultAddrId, setDefaultAddrId] = React.useState("home");
const [defaultPay, setDefaultPay] = React.useState("amex");
const [prefs, setPrefs] = React.useState({
plasticReturn: true,
sundayDigest: true,
notifyOpenLate: false,
roundUp: true,
leaveAtDoor: true,
});
const togglePref = (k) => setPrefs(p => ({ ...p, [k]: !p[k] }));
const orders = [
{ id: "4821", status: "Out for delivery", store: "Kalymnos Deli", items: 3, total: 28.40, eta: "9 min", when: "Today, 6:42 PM" },
{ id: "4799", status: "Pickup ready", store: "Fig & Thistle", items: 2, total: 41.00, eta: "Held til 8:30 PM", when: "Today, 5:10 PM" },
{ id: "4754", status: "Delivered", store: "Verde Produce", items: 7, total: 32.18, eta: "", when: "Tue, 7:18 PM" },
{ id: "4691", status: "Delivered", store: "Liu's 24hr", items: 4, total: 18.62, eta: "", when: "Sun, 11:24 PM" },
];
const addresses = [
{ id: "home", label: "Home", line1: "98 Orchard St, Apt 4B", line2: "New York, NY 10002", note: "Buzz 4B — door is unmarked", icon: "Pin" },
{ id: "work", label: "Studio", line1: "315 W 36th St, Floor 9", line2: "New York, NY 10018", note: "Reception accepts after 6pm", icon: "Store" },
{ id: "moms", label: "Mom's", line1: "1142 Carroll St", line2: "Brooklyn, NY 11225", note: "Stairs on the left", icon: "Heart" },
];
const payments = [
{ id: "amex", brand: "Amex", last4: "1004", exp: "06/27" },
{ id: "visa", brand: "Visa", last4: "8821", exp: "11/26" },
{ id: "ebt", brand: "EBT", last4: "—", exp: "" },
];
return (
{/* Header band */}
{/* Decorative arcs */}
NG
Nikhil Gupta
Member since Mar 2024 · 84 orders · 6 stores supported
}>Edit profile
Sign out
{/* Standing card — neighborhood credits */}
Block standing
$14.20
in store credits
{[
{ k: "212", l: "bags saved" },
{ k: "$32", l: "given pantry" },
{ k: "9", l: "shared trips" },
].map(s => (
))}
{/* Two-column layout: orders left, settings right (stack on mobile) */}
{/* LEFT — orders + addresses */}
{/* Recent orders */}
}>All orders} />
{orders.map(o => {
const live = o.status === "Out for delivery" || o.status === "Pickup ready";
return (
{o.store}
· {o.items} items · {money(o.total)}
{live && ● {o.status}{o.eta && ` · ${o.eta}`} }
{!live && {o.status} }
· {o.when}
· #{o.id}
{live ? (
Track
) : (
}>Reorder
)}
);
})}
{/* Saved addresses */}
}>Add} />
{addresses.map(a => {
const isDefault = defaultAddrId === a.id;
const Icon = Icons[a.icon] || Icons.Pin;
return (
setDefaultAddrId(a.id)}
style={{
textAlign: "left",
padding: "14px 16px", borderRadius: 14,
background: "var(--surface)",
border: `1.5px solid ${isDefault ? "var(--navy)" : "var(--line)"}`,
display: "flex", flexDirection: "column", gap: 8,
transition: "border-color 0.15s",
}}>
{a.line1}
{a.note && (
Note: {a.note}
)}
);
})}
{/* RIGHT — payment, preferences, support */}
{/* Payment methods */}
}>Add} />
{payments.map(p => {
const isDefault = defaultPay === p.id;
return (
setDefaultPay(p.id)}
style={{
display: "flex", alignItems: "center", gap: 12,
padding: "12px 14px", borderRadius: 12,
background: "var(--surface)",
border: `1.5px solid ${isDefault ? "var(--navy)" : "var(--line)"}`,
textAlign: "left",
}}>
{p.brand}
{p.brand} {p.last4 !== "—" && <>···· {p.last4} >}
{p.exp &&
Expires {p.exp}
}
{!p.exp &&
For SNAP-eligible items
}
{isDefault && Default }
);
})}
{/* Preferences */}
{[
{ k: "leaveAtDoor", label: "Leave at door", hint: "Default for all deliveries" },
{ k: "plasticReturn", label: "Return bags & containers", hint: "Driver picks up empty totes" },
{ k: "roundUp", label: "Round up for the LES pantry", hint: "$0.18 average per order" },
{ k: "sundayDigest", label: "Sunday digest from your block", hint: "What's new at your shops" },
{ k: "notifyOpenLate", label: "Tell me when shops stay open late", hint: "Push only, no email" },
].map((row, i, arr) => (
togglePref(row.k)}
style={{
display: "flex", alignItems: "center", gap: 12,
padding: "12px 14px",
borderBottom: i < arr.length - 1 ? "1px solid var(--line)" : "none",
textAlign: "left",
}}>
))}
{/* Support / legal */}
{[
{ label: "Help center", icon: "Sparkle" },
{ label: "Contact your last driver", icon: "Mic" },
{ label: "Privacy & data", icon: "User" },
{ label: "Terms", icon: "Receipt" },
].map((row, i, arr) => {
const Icon = Icons[row.icon] || Icons.Chevron;
return (
{row.label}
);
})}
Opa v2.0 · build 4821 · made on Orchard St
);
}
// ─── Switch ────────────────────────────────────────────────────────────────
function Switch({ on }) {
return (
);
}
// ─── AddressPicker ─────────────────────────────────────────────────────────
// Full-screen overlay. Triggered from the topbar address pill.
function AddressPicker({ open, current, onClose, onPick }) {
const isMobile = useMedia("(max-width: 720px)");
const [q, setQ] = React.useState("");
const inputRef = React.useRef(null);
React.useEffect(() => {
if (open) setTimeout(() => inputRef.current?.focus(), 100);
if (!open) setQ("");
}, [open]);
// Lock body scroll
React.useEffect(() => {
if (!open) return;
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden";
return () => { document.body.style.overflow = prev; };
}, [open]);
// ESC closes
React.useEffect(() => {
if (!open) return;
const fn = (e) => { if (e.key === "Escape") onClose(); };
window.addEventListener("keydown", fn);
return () => window.removeEventListener("keydown", fn);
}, [open, onClose]);
if (!open) return null;
const saved = [
{ id: "home", label: "Home", line: "98 Orchard St, Apt 4B · NY 10002", eta: "Default · 28 stores within 0.5 mi", icon: "Pin", tone: "navy" },
{ id: "work", label: "Studio", line: "315 W 36th St, Floor 9 · NY 10018", eta: "12 stores within 0.5 mi", icon: "Store", tone: "navy" },
{ id: "moms", label: "Mom's", line: "1142 Carroll St · Brooklyn, NY 11225", eta: "9 stores within 0.5 mi", icon: "Heart", tone: "navy" },
];
const recent = [
{ line: "55 Hudson Yards · NY 10001", last: "Tue" },
{ line: "Brooklyn Bridge Park, Pier 2", last: "Sat — picnic order" },
{ line: "230 5th Ave Rooftop · NY 10001", last: "Last month" },
];
// crude filter
const matches = q.trim()
? [...saved, ...recent.map(r => ({ id: r.line, label: r.line.split(" · ")[0], line: r.line }))]
.filter(s => (s.label + " " + s.line).toLowerCase().includes(q.toLowerCase()))
: null;
return (
{ if (e.currentTarget === e.target) onClose(); }}>
{/* Header / search */}
setQ(e.target.value)}
placeholder="Address, intersection, or landmark"
style={{ flex: 1, height: 28, border: 0, background: "transparent", outline: 0,
color: "var(--ink)", fontSize: 14.5, fontFamily: "inherit" }} />
{q && (
setQ("")}
style={{ color: "var(--ink-4)", fontSize: 11 }}>Clear
)}
{/* Body — scrolls */}
{/* Use current location */}
{ onPick({ id: "current", label: "Current location" }); onClose(); }}
style={{
display: "flex", alignItems: "center", gap: 14,
padding: "14px 16px", borderRadius: 14,
background: "var(--navy-tint)", border: "1px solid transparent",
textAlign: "left", color: "var(--ink)",
}}>
Use current location
~Lower East Side · accuracy ±15m
{!matches && (
<>
{/* Saved */}
Saved
{saved.map(s => {
const Icon = Icons[s.icon] || Icons.Pin;
const isCurrent = current === s.id;
return (
{ onPick(s); onClose(); }}
style={{
display: "flex", alignItems: "center", gap: 12,
padding: "12px 14px", borderRadius: 12,
background: "var(--surface)",
border: `1.5px solid ${isCurrent ? "var(--navy)" : "var(--line)"}`,
textAlign: "left",
}}>
{s.label}
{isCurrent &&
Current }
{s.line}
{s.eta}
);
})}
{/* Recent */}
Recent
{recent.map((r) => (
{ onPick({ id: r.line, label: r.line.split(" · ")[0], line: r.line }); onClose(); }}
style={{
display: "flex", alignItems: "center", gap: 12,
padding: "10px 12px", borderRadius: 10,
background: "transparent", textAlign: "left",
}}>
))}
{/* Schedule for later */}
Need it later? Tonight · Tomorrow · Pick a time
>
)}
{matches && (
{matches.length} matches
{matches.map(m => (
{ onPick(m); onClose(); }}
style={{
display: "flex", alignItems: "center", gap: 12,
padding: "10px 12px", borderRadius: 10,
background: "var(--surface)", border: "1px solid var(--line)",
textAlign: "left",
}}>
))}
{matches.length === 0 && (
No saved or recent address matches "{q}". Add it .
)}
)}
);
}
Object.assign(window, { AccountScreen, AddressPicker, Switch });