/* Consult booking — mock flow.
* Date → time → contact form → confirmation. Reads stack context from
* sessionStorage if user came from BioReveal, otherwise renders generic.
* No real backend; submission stays client-side and shows a confirmation.
*/
const TIMES = [
{ t: "9:00 AM", taken: false },
{ t: "10:30 AM", taken: false },
{ t: "12:00 PM", taken: true },
{ t: "1:30 PM", taken: false },
{ t: "3:00 PM", taken: false },
{ t: "4:30 PM", taken: true },
{ t: "6:00 PM", taken: false },
];
const CLINICIANS = [
{ name: "Dr. Maya Brennan, MD", cred: "Internal Medicine · Functional Med fellow", years: 11, photo: null, color: "#10b981" },
{ name: "Dr. Jonas Reid, DO", cred: "Sports Medicine · Endocrine focus", years: 9, photo: null, color: "#6366f1" },
{ name: "Dr. Tara Olamide, MD", cred: "Anti-aging · Hormone optimization", years: 14, photo: null, color: "#f59e0b" },
];
function getNextDays(n) {
const days = [];
const now = new Date();
for (let i = 1; i <= n; i++) {
const d = new Date(now);
d.setDate(now.getDate() + i);
days.push(d);
}
return days;
}
function fmtDay(d) {
return d.toLocaleDateString("en-US", { weekday: "short" }).toUpperCase();
}
function fmtNum(d) {
return d.getDate();
}
function fmtMonth(d) {
return d.toLocaleDateString("en-US", { month: "short" }).toUpperCase();
}
function fmtFull(d) {
return d.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric" });
}
const ConsultPage = () => {
const Header = window.Header;
const Footer = window.Footer;
const days = React.useMemo(() => getNextDays(10), []);
const [step, setStep] = React.useState("schedule"); // schedule | form | done
const [selectedDay, setSelectedDay] = React.useState(days[0]);
const [selectedTime, setSelectedTime] = React.useState(null);
const [clinician, setClinician] = React.useState(CLINICIANS[0]);
const [form, setForm] = React.useState({ name: "", email: "", phone: "", note: "" });
const [errors, setErrors] = React.useState({});
// Pull BioReveal context if it exists
const stackCtx = React.useMemo(() => {
try {
const raw = sessionStorage.getItem("br_protocol_summary");
return raw ? JSON.parse(raw) : null;
} catch { return null; }
}, []);
function pickTime(t) {
setSelectedTime(t);
setStep("form");
window.scrollTo({ top: 0, behavior: "smooth" });
}
function submit(e) {
e.preventDefault();
const errs = {};
if (!form.name.trim()) errs.name = "Required";
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(form.email)) errs.email = "Valid email required";
if (!form.phone.trim() || form.phone.replace(/\D/g, "").length < 10) errs.phone = "10-digit phone required";
setErrors(errs);
if (Object.keys(errs).length) return;
setStep("done");
window.scrollTo({ top: 0, behavior: "smooth" });
}
return (
{/* HERO */}
★ THE NEXT STEP
{step === "done" ? "You're booked." : "Book your $0 MD consult."}
{step === "done"
? `We sent a confirmation to ${form.email}. ${clinician.name.split(",")[0]} will join you ${fmtFull(selectedDay)} at ${selectedTime}.`
: "20 minutes with a licensed U.S. clinician. They review your protocol, ask about your history, and either approve, adjust, or pause before anything ships. Free — no card required."}
{/* STACK SUMMARY (if from BioReveal) */}
{stackCtx && step !== "done" && (
Reviewing on this consult
{stackCtx.stackName || "Your BioReveal stack"}
{stackCtx.peptides && stackCtx.peptides.length > 0 && (
{stackCtx.peptides.join(" · ")}
)}
{stackCtx.total && (
EST. STACK
${stackCtx.total}/mo
)}
)}
{/* STEP: SCHEDULE */}
{step === "schedule" && (
<>
{/* Clinician picker */}
★ CHOOSE A CLINICIAN
{CLINICIANS.map(c => (
setClinician(c)}
style={{
textAlign: "left", padding: "16px 18px", borderRadius: 12, cursor: "pointer",
background: clinician.name === c.name ? "var(--ink)" : "var(--paper-2)",
color: clinician.name === c.name ? "var(--paper)" : "var(--ink)",
border: clinician.name === c.name ? "1px solid var(--ink)" : "1px solid var(--paper-3)",
transition: "all 0.15s",
}}>
{c.name.split(" ")[1][0]}{c.name.split(" ")[2] ? c.name.split(" ")[2][0] : ""}
{c.name}
{c.cred}
{c.years} yrs practicing
))}
{/* Date picker */}
★ PICK A DAY
{days.map((d, i) => {
const isSel = d.toDateString() === selectedDay.toDateString();
return (
setSelectedDay(d)}
style={{
flex: "0 0 auto", padding: "14px 18px", borderRadius: 10, cursor: "pointer",
background: isSel ? "var(--ink)" : "var(--paper-2)",
color: isSel ? "var(--paper)" : "var(--ink)",
border: isSel ? "1px solid var(--ink)" : "1px solid var(--paper-3)",
minWidth: 86, textAlign: "center", transition: "all 0.15s",
}}>
{fmtDay(d)}
{fmtNum(d)}
{fmtMonth(d)}
);
})}
{/* Time slots */}
★ AVAILABLE TIMES — {fmtFull(selectedDay).toUpperCase()}
{TIMES.map(s => (
pickTime(s.t)}
style={{
padding: "16px 14px", borderRadius: 10,
background: s.taken ? "var(--paper-2)" : "var(--paper)",
color: s.taken ? "var(--muted)" : "var(--ink)",
border: s.taken ? "1px solid var(--paper-3)" : "1.5px solid var(--ink)",
cursor: s.taken ? "not-allowed" : "pointer",
fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 15,
textDecoration: s.taken ? "line-through" : "none",
opacity: s.taken ? 0.5 : 1,
transition: "all 0.12s",
}}
onMouseEnter={(e) => { if (!s.taken) { e.currentTarget.style.background = "var(--ink)"; e.currentTarget.style.color = "var(--paper)"; } }}
onMouseLeave={(e) => { if (!s.taken) { e.currentTarget.style.background = "var(--paper)"; e.currentTarget.style.color = "var(--ink)"; } }}>
{s.t}
{s.taken && BOOKED
}
))}
>
)}
{/* STEP: FORM */}
{step === "form" && (
Confirming
{clinician.name.split(",")[0]} · {fmtFull(selectedDay)} · {selectedTime}
setStep("schedule")} style={{ background: "none", border: "none", color: "var(--ink)", fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.18em", textTransform: "uppercase", cursor: "pointer", textDecoration: "underline", textUnderlineOffset: 4 }}>← Change
★ YOUR DETAILS
)}
{/* STEP: DONE */}
{step === "done" && (
★ CONFIRMATION
WHEN
{fmtFull(selectedDay)} {selectedTime} EST
WITH
{clinician.name}{clinician.cred}
Confirmation sent to {form.email}. We'll text {form.phone} a reminder 1 hour before. The link to join (Zoom) is in your email.
Before the call: nothing required. The clinician already has your BioReveal intake. They'll spend the time on questions, not setup.
)}
);
};
const Field = ({ label, error, children }) => (
{label} {error && · {error} }
{React.cloneElement(children, {
style: {
width: "100%", padding: "14px 16px", borderRadius: 10,
border: error ? "1.5px solid #ef4444" : "1.5px solid var(--paper-3)",
background: "var(--paper)", fontFamily: "var(--font-display)", fontSize: 15,
color: "var(--ink)", outline: "none", transition: "border-color 0.15s",
...(children.props.style || {}),
},
onFocus: (e) => { if (!error) e.target.style.borderColor = "var(--ink)"; },
onBlur: (e) => { if (!error) e.target.style.borderColor = "var(--paper-3)"; },
})}
);
ReactDOM.createRoot(document.getElementById("root")).render( );