import { useState, type CSSProperties, type ReactNode } from "react"; import { Check, ChevronDown } from "lucide-react"; import type { SetupStep, SetupStepId } from "../../../utils/practiceSetupSteps"; import { COLLAPSED_VALUE, collapsedFromStored } from "../../../components/OrgOnboarding/onboardingCollapse"; type StepCopy = { todo: string; todoDetail?: string; next: string; hint: string; done: string }; export const SETUP_STEP_COPY: Record = { owner: { todo: "Nominate your practice owner", todoDetail: "The person who holds this practice's account.", next: "nominate your practice owner", hint: "the person who holds this practice's account and its agreements.", done: "Practice owner confirmed", }, msa: { todo: "Accept the Master Subscription Agreement", todoDetail: "Your practice owner accepts it before the first Business Associate Agreement.", next: "accept the Master Subscription Agreement", hint: "your practice owner accepts it before the Business Associate Agreement.", done: "Master Subscription Agreement accepted", }, baa: { todo: "Sign your Business Associate Agreement", todoDetail: "Required before patients can use your portal.", next: "sign your Business Associate Agreement", hint: "it is required before patients can use your portal.", done: "Business Associate Agreement signed", }, location: { todo: "Add your practice's primary location", next: "add your practice's primary location", hint: "its name, address and the time zone your schedules run on.", done: "Primary location added", }, contact: { todo: "Add practice contact information", todoDetail: "General phone and email · both required.", next: "add practice contact information", hint: "enter the general phone and email patients should use, not your personal account details.", done: "Practice contact information added", }, services: { todo: "Add your services", todoDetail: "Categories of care. Each clinician's own services and hours are added when you set them up.", next: "add your services", hint: "every top-level category of care your practice offers.", done: "Practice services added", }, clinician: { todo: "Invite your first clinician", next: "invite your first clinician", hint: "they accept, then a seat, services and availability so patients can book them.", done: "First clinician ready", }, coordinator: { todo: "Say who works your incoming referrals", todoDetail: "Give somebody the Referral Coordinator role. Staff who receive referrals are free.", next: "say who works your incoming referrals", hint: "they receive incoming referrals and are the referring practice's contact.", done: "Referral coordinator assigned", }, routing: { todo: "Choose where referrals land", todoDetail: "Your front desk or the referral workspace.", next: "choose where referrals land", hint: "until you choose, a practice referring a patient to you falls back to a fax.", done: "Referral destination chosen", }, }; // The admin-v2 "Agency setup" frame: the agency is setting up somebody else's practice. export const AGENCY_STEP_COPY: Partial>> = { owner: { todo: "Name who will own this practice", todoDetail: "The practice has no owner until the person you name accepts by email. Your Platform Manager access stays the same.", next: "name who will own this practice", hint: "you name them; they say yes from the email we send them.", }, baa: { todo: "Sign the Business Associate Agreement", next: "sign the Business Associate Agreement" }, location: { todo: "Add their primary location", next: "add their primary location" }, services: { todo: "Add their services", next: "add their services" }, clinician: { todo: "Invite their first clinician", next: "invite their first clinician" }, }; export const setupStepCopy = (id: SetupStepId, agency: boolean): StepCopy => agency ? { ...SETUP_STEP_COPY[id], ...AGENCY_STEP_COPY[id] } : SETUP_STEP_COPY[id]; type PracticeSetupGuideProps = { agency: boolean; steps: SetupStep[]; doneDetail: Partial>; doneTitle?: Partial>; /** Replaces a step's to-do wording while it waits on somebody else, e.g. a pending invitee. */ todoTitle?: Partial>; todoDetail?: Partial>; unavailableTitle?: Partial>; /** Replaces the footer line when the guide has something more specific to say. */ laterNote?: string; /** What an unreadable step says when the server has said why the practice is shut. */ unavailableDetail?: string; onOpenStep: (id: SetupStepId) => void; /** The seam to the patient-facing half of setup (admin-v2 hats), under the steps and not counted in them. */ patientPagesStep?: ReactNode; optionalStep?: ReactNode; /** One person's fold at one practice, from `onboardingCollapseKey`. */ collapseKey: string; }; // admin-v2 "Finish setting up". Progress counts only steps whose data the server has confirmed; // a step that could not be read says so instead of counting either way. const PracticeSetupGuide = ({ agency, steps, doneDetail, doneTitle, todoTitle, todoDetail, unavailableTitle, laterNote, unavailableDetail, onOpenStep, patientPagesStep, optionalStep, collapseKey }: PracticeSetupGuideProps) => { const copyOf = (id: SetupStepId) => setupStepCopy(id, agency); const [expanded, setExpanded] = useState(() => { try { return !collapsedFromStored(window.localStorage.getItem(collapseKey)); } catch { return true; } }); const toggle = () => { setExpanded((open) => { try { window.localStorage.setItem(collapseKey, open ? COLLAPSED_VALUE : "false"); } catch { // Unsaved, the fold still applies here; it is only forgotten on the next visit. } return !open; }); }; const done = steps.filter((s) => s.state === "done").length; const next = steps.find((s) => s.state !== "done"); // While a practice has no owner its staff reads are refused (the owner gate), so "could not check" sent the // admin to retry when what the practice needs is an owner (B6, 2026-09-30). const ownerless = steps.some((s) => s.id === "owner" && s.state === "todo"); const detailOf = (step: SetupStep) => { if (step.state === "done") return doneDetail[step.id]; if (step.state === "checking") return "Checking…"; if (step.state === "unavailable") { if (unavailableDetail) return unavailableDetail; return ownerless ? "Waiting for an owner. This can be set up once someone owns the practice." : "Status unavailable. We could not check this just now."; } return todoDetail?.[step.id] ?? copyOf(step.id).todoDetail; }; return (
{!expanded && next && (
Next: {next.state === "unavailable" ? unavailableTitle?.[next.id] ?? copyOf(next.id).next : copyOf(next.id).next} {" — "} {next.state === "unavailable" && unavailableTitle?.[next.id] ? detailOf(next) : copyOf(next.id).hint}
)} {expanded && ( <>
    {steps.map((step, index) => { const isDone = step.state === "done"; const detail = detailOf(step); return (
  1. ); })}
{patientPagesStep} {optionalStep} {/* The deck's first visit names the count; once a step is done, the footer drops it. */}
{laterNote ?? (done === 0 ? `You can add your logo and the rest of your staff later — complete these ${steps.length === 6 ? "six" : "five"} steps to prepare for appointments.` : "You can add your logo and the rest of your staff later.")}
)}
); }; export default PracticeSetupGuide;