import { useState } from "react"; import { OverlayActions } from "../../../components/ui/overlay-actions"; import { DismissibleAlert } from "../../../components/ui/dismissible-alert"; import { ClinicianSeatConfirmDialog } from "../../../components/ClinicianSeat/ClinicianSeatField"; import { usePractitionerRoleServiceContext } from "../../../services/PractitionerRoleService"; import { useBillingContext } from "../../../services/BillingContext.tsx"; import { isClinicianEnabled, setClinicianEnabled } from "../../../utils/clinicianEnabled"; import { HttpError } from "../../../services/HttpService"; import { describeClinicianSeatCharge, isSeatPriceChanged } from "../../../utils/clinicianSeatCharge"; import { describeWriteFailure } from "../../../utils/fetchFailureMessage"; import { isBillingLimitRefusal } from "../../../utils/billingLimitRefusal"; const ELLIPSIS = String.fromCharCode(8230); const DOT = String.fromCharCode(183); /** Step 5 once a practitioner is eligible: the seat is assigned here, and a paid one is priced and confirmed first. */ export const FirstClinicianSeat = ({ practitionerRoleId, name, eligibility, onAssigned, onBack, showHeading = true }: { practitionerId: string; practitionerRoleId: string; name: string; /** What made them eligible, e.g. an accepted invitation and a sign-in. */ eligibility: string; onAssigned: () => void; onBack: () => void; showHeading?: boolean; }) => { const { readPractitionerRole, updatePractitionerRole } = usePractitionerRoleServiceContext(); const { addOnPricing, clinicianCount, clinicianCountSettling, entitlementsUnavailable, billingChecked, isLoading, billingStatus, refreshBilling } = useBillingContext(); const [saving, setSaving] = useState(false); const [problem, setProblem] = useState(null); // Set once the server refuses the included seat, so a lagging refresh cannot show it as free again. const [includedTaken, setIncludedTaken] = useState(false); const [confirming, setConfirming] = useState(false); const seatCharge = describeClinicianSeatCharge({ enabledClinicians: clinicianCount, seatPriceCents: addOnPricing?.unitPriceCents?.clinicianSeats, hasSubscription: billingStatus === null ? undefined : billingStatus !== "free_basic", }); const seatState: "checking" | "unknown" | "settling" | "included" | "paid" = !billingChecked || isLoading ? "checking" : billingStatus === null || clinicianCount === undefined || entitlementsUnavailable ? "unknown" : clinicianCountSettling || (includedTaken && seatCharge.chargeCents === 0) ? "settling" : seatCharge.chargeCents === 0 ? "included" : "paid"; const assign = async (included: boolean) => { setProblem(null); setSaving(true); try { const role = await readPractitionerRole(practitionerRoleId); await updatePractitionerRole(setClinicianEnabled(role, true), included ? { expectIncludedSeat: true } : {}); void refreshBilling({ seatChanged: true }); onAssigned(); } catch (error) { if (isBillingLimitRefusal(error)) { // The app-wide billing modal reports this refusal and offers checkout. } else if (isSeatPriceChanged(error)) { setIncludedTaken(true); await Promise.resolve(refreshBilling({ seatChanged: true })).catch(() => undefined); setProblem("Your included clinician seat is already in use, so this seat is paid. Review the price below before you confirm."); } else if (!(error instanceof HttpError) && await readPractitionerRole(practitionerRoleId).then(isClinicianEnabled, () => false)) { // No answer, but the seat is on the role: it is assigned, not retried or re-priced. void refreshBilling({ seatChanged: true }); onAssigned(); } else { setProblem(describeWriteFailure(error, "The clinician seat was not assigned")); } } finally { setSaving(false); } }; return (
{showHeading &&

Assign the first clinician seat

}

{name}

{problem && setProblem(null)}>{problem}}
Ready for seat assignment {eligibility}
{seatState === "checking" &&

{`Checking your clinician seats${ELLIPSIS}`}

} {seatState === "included" && ( <>

No clinician seat is assigned yet. Your included seat is available.

{`Included clinician seat ${DOT} $0`}
)} {seatState === "paid" && (

{`Your included seat is in use, so this seat is paid. ${seatCharge.headline}`}

)} {seatState === "unknown" && (

We could not read your clinician seats, so we cannot say whether this seat is included.

)} {seatState === "settling" && (

Your clinician seat count is still updating after a recent change, so we cannot say yet whether this seat is included.

)}

Assigning a seat does not finish appointment setup. Services and availability are next.

{seatState === "included" && ( )} {seatState === "paid" && ( )} {seatState === "unknown" && ( )} {seatState === "settling" && ( )} { setConfirming(false); void assign(false); }} />
); }; export default FirstClinicianSeat;