import { useCallback, useEffect, useRef, useState } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import { ChevronRight, CreditCard, FileText, Scale, Share2, ShieldCheck, TriangleAlert, UsersRound, Wallet } from "lucide-react"; import { useAuthServiceContext } from "../../../services/AuthService"; import { useStaffPermissionServiceContext } from "../../../services/StaffPermissionService"; import { useBillingContext } from "../../../services/BillingContext"; import { useFhirPractitioners } from "../../../hooks"; import { useBAAService, type BaaRecord } from "../../../services/BAAService"; import { useOrganizationOwnerService, type OwnerState } from "../../../services/OrganizationOwnerService"; import { useOrganizationContractorsService, type OrganizationContractorRow } from "../../../services/OrganizationContractorsService"; import OrganizationOwnerNotice from "../../../components/OrganizationOwnerNotice/OrganizationOwnerNotice"; import { ownerCandidatesOf } from "../../../components/OrganizationOwnerNotice/ownerCandidates"; import { referencesPractitioner } from "../../../components/OrganizationOwnerNotice/ownerReference"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "../../../components/ui/sheet"; import { formatFhirHumanName } from "../../../utils/fhirHumanName"; import { practiceTimeZoneOf } from "../../../utils/practiceTimezone"; import { chargeDay } from "../../SubscriptionPage/chargeDate"; import { formatDayMonthYear } from "../../../utils/dateUtils"; import ContractorAccessSection from "./ContractorAccessSection"; import AgencyAutopaySection, { autopayStatus } from "./AgencyAutopaySection"; import Part2DeclarationSection from "./Part2DeclarationSection"; import { useContractorAutopayService, type AutopayPage } from "../../../services/ContractorAutopayService"; import { isSaveRetired, useSaveScope } from "../../../utils/saveOperation"; import { isAutopayOperationId, autopayHolder, readAutopayHold, writeAutopayHold, newAutopayOperationId } from "../../../utils/autopaySetup"; import PracticeClosureStatus from "./PracticeClosure/PracticeClosureStatus"; import { PRACTICE_CLOSURE_PATH } from "./PracticeClosure/closureText"; import { isUnderClosure, usePracticeClosure } from "../../../services/PracticeClosureService"; import { readOperatingScope } from "../../../utils/operatingScope"; import "./PracticeAccountPage.css"; type Panel = "agency" | "owner" | "autopay" | "part2"; const formatMoney = (cents: number) => new Intl.NumberFormat(undefined, { style: "currency", currency: "USD" }).format(cents / 100); // The card on file, or nothing when the server sent no usable detail. const cardLabel = (method: { brand: string; last4: string; label: string } | null) => method?.label?.trim() || (method?.last4 ? `${method.brand ? `${method.brand} ` : "Card "}ending ${method.last4}` : undefined); // admin-v2 .acctrow: one account destination on the shared list card, its icon at the left and the // chevron at the right. A row that opens nothing (a status that could not be read) draws no chevron. const AccountRow = ({ icon: Icon, title, details, onOpen }: { icon: typeof FileText; title: string; details: (string | undefined)[]; onOpen?: () => void; }) => { const body = ( <> {title} {details.filter(Boolean).map((detail) => {detail})} {onOpen && } ); const layout = "ak-row min-h-16 w-full items-start gap-[14px] text-left"; return onOpen ? :
{body}
; }; // The owner banner is static; ownership that needs action keeps the owner notice at the top. const PracticeAccountPage = () => { const navigate = useNavigate(); const { currentOrganization, currentUser, USER_ROLES, actingAs, actingAsSessionRef } = useAuthServiceContext(); const receivingOnly = readOperatingScope(currentOrganization) === "receiving"; const { activeWorkspace, canManage } = useStaffPermissionServiceContext(); const billing = useBillingContext(); const { practitionersList } = useFhirPractitioners(); const { getBaa } = useBAAService(); const { fetchOwner } = useOrganizationOwnerService(); const { listContractors } = useOrganizationContractorsService(); const { listArrangements } = useContractorAutopayService(); const canManageOrganization = canManage("organization"); const isAdminWorkspace = activeWorkspace === USER_ROLES.ADMIN; const practitionerId = currentUser?.fhir?.resourceType === "Practitioner" ? currentUser.fhir.id : undefined; const isViewer = (reference: string | undefined) => referencesPractitioner(reference, practitionerId); const [owner, setOwner] = useState("checking"); const [baaRecord, setBaaRecord] = useState("checking"); const [contractors, setContractors] = useState("checking"); const [panel, setPanel] = useState(null); // A billing warning may link straight to contractor access; it opens once, and only where that row is offered. const [searchParams] = useSearchParams(); const askedForAgency = searchParams.get("panel") === "agency"; const openedFromLink = useRef(false); useEffect(() => { if (openedFromLink.current || !askedForAgency || !isAdminWorkspace) return; openedFromLink.current = true; setPanel("agency"); }, [askedForAgency, isAdminWorkspace]); const autopayViewer = `${currentUser?.fhir?.resourceType}/${currentUser?.fhir?.id}|${actingAs?.relatedPersonId ?? ""}|${actingAsSessionRef?.current ?? ""}`; const autopayOwner = JSON.stringify([currentOrganization?.id, autopayViewer, isAdminWorkspace, canManageOrganization]); const autopayScope = useSaveScope(autopayOwner); const [autopayRead, setAutopayRead] = useState<{ owner: string; page?: AutopayPage; checking: boolean }>({ owner: autopayOwner, checking: true }); const autopayPage = autopayRead.owner === autopayOwner ? autopayRead.page : undefined; const arrangements = autopayRead.owner !== autopayOwner || autopayRead.checking ? "checking" : autopayPage?.arrangements; const [autopayRefresh, setAutopayRefresh] = useState(0); const [autopayReadingMore, setAutopayReadingMore] = useState(false); const autopayCursors = useRef(new Set()); const { view: closure, reload: reloadClosure } = usePracticeClosure(currentOrganization?.id); const [closureNotice, setClosureNotice] = useState<{ organizationId?: string; text: string }>(); const closureRead = typeof closure === "string" ? undefined : closure; const underClosure = closureRead ? isUnderClosure(closureRead) : false; const noticeFor = currentOrganization?.id; const onReopened = (notice: string) => { setClosureNotice({ organizationId: noticeFor, text: notice }); reloadClosure(); void billing.refreshBilling(); }; const ownerState = owner === "checking" ? undefined : owner; const viewerIsOwner = isViewer(ownerState?.ownerPractitionerId); const canReadBaa = canManageOrganization || viewerIsOwner === true; useEffect(() => { if (!currentOrganization?.id) return; let active = true; // A re-read keeps the last answer on screen until the new one arrives. fetchOwner(currentOrganization.id) .then((state) => active && setOwner(state)) .catch(() => active && setOwner(undefined)); return () => { active = false; }; }, [currentOrganization?.id, fetchOwner]); // The notice reports every ownership change it makes, including a handover. const onOwnerStateChange = useCallback((state: OwnerState | undefined) => { if (state) setOwner(state); }, []); useEffect(() => { if (!canReadBaa) return; let active = true; setBaaRecord("checking"); getBaa() .then((record) => active && setBaaRecord(record)) .catch(() => active && setBaaRecord(undefined)); return () => { active = false; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [canReadBaa, currentOrganization?.id]); useEffect(() => { if (!isAdminWorkspace) return; let active = true; setContractors("checking"); listContractors() .then(({ contractors: rows }) => active && setContractors(rows)) .catch(() => active && setContractors(undefined)); return () => { active = false; }; }, [isAdminWorkspace, listContractors, currentOrganization?.id, panel]); useEffect(() => { const organizationId = currentOrganization?.id; if (!isAdminWorkspace || !canManageOrganization || !organizationId) return; const operation = autopayScope.start(); setAutopayRead({ owner: autopayOwner, checking: true }); setAutopayReadingMore(false); autopayCursors.current.clear(); listArrangements({ ...operation.transport, organizationId }) .then((page) => { operation.admit(); if (page.nextCursor) autopayCursors.current.add(page.nextCursor); setAutopayRead({ owner: autopayOwner, page, checking: false }); }) .catch((failure) => { if (operation.belongs() && !isSaveRetired(failure)) setAutopayRead({ owner: autopayOwner, checking: false }); }); return () => operation.retire(); }, [autopayOwner, isAdminWorkspace, listArrangements, currentOrganization?.id, autopayRefresh, autopayScope]); const readMoreAutopay = async () => { const organizationId = currentOrganization?.id; if (!isAdminWorkspace || !canManageOrganization || !organizationId || !autopayPage?.nextCursor || autopayReadingMore) return; const operation = autopayScope.start(); const original = autopayPage; setAutopayReadingMore(true); try { const next = await operation.step(() => listArrangements({ ...operation.transport, organizationId }, original.nextCursor)); operation.admit(); if ((next.nextCursor && autopayCursors.current.has(next.nextCursor)) || next.arrangements.some((row) => original.arrangements.some((prior) => prior.contractorId === row.contractorId && JSON.stringify(prior) !== JSON.stringify(row)))) throw new Error("The auto-pay list could not be completed."); if (next.nextCursor) autopayCursors.current.add(next.nextCursor); setAutopayRead({ owner: autopayOwner, checking: false, page: { ...next, arrangements: [...original.arrangements, ...next.arrangements.filter((row) => !original.arrangements.some((prior) => prior.contractorId === row.contractorId))] } }); } catch { if (operation.belongs()) setAutopayRead({ owner: autopayOwner, checking: false, page: { ...original, complete: false } }); } finally { if (operation.belongs()) setAutopayReadingMore(false); operation.retire(); } }; useEffect(() => { if ((!isAdminWorkspace || !canManageOrganization) && panel === "autopay") setPanel(null); }, [isAdminWorkspace, canManageOrganization, panel]); useEffect(() => { const returnedOperation = searchParams.get("autopayOperation"); const contractorId = searchParams.get("autopayContractor"); if (!isAdminWorkspace || !currentOrganization?.id || !isAutopayOperationId(returnedOperation) || !contractorId) return; const row = autopayPage?.arrangements.find((entry) => entry.contractorId === contractorId && entry.setup?.operationId === returnedOperation); if (!row) return; const holder = autopayHolder(autopayViewer, currentOrganization.id, contractorId); if (!readAutopayHold(holder)) writeAutopayHold(holder, { operationId: returnedOperation, opening: newAutopayOperationId(), attempt: newAutopayOperationId(), sent: true }); setPanel("autopay"); }, [searchParams, autopayViewer, currentOrganization?.id, isAdminWorkspace, autopayPage]); const ownerNeedsAttention = Boolean( ownerState && (ownerState.needsOwner || isViewer(ownerState.pendingTransferToPractitionerId)), ); const nameOf = (reference: string | undefined) => { const id = reference?.split("/").pop(); const found = practitionersList.find((entry) => entry.practitioner.id === id)?.practitioner.name?.[0]; return found ? formatFhirHumanName(found) : undefined; }; // A custom agreement awaiting counter-signature arrives in `pending`, beside whatever is in force. const baaAwaitingCountersign = baaRecord !== "checking" && baaRecord !== undefined && (Boolean(baaRecord.pending) || baaRecord.status === "pending"); const baaSummary = baaRecord === "checking" ? "Checking…" : baaRecord === undefined ? "BAA status unavailable." : baaRecord.currentUnconfirmed ? "Your signature is recorded; the agreement now in force could not be confirmed." : baaRecord.status === "active" ? `Signed${baaRecord.signedAt ? ` ${formatDayMonthYear(baaRecord.signedAt)}` : ""}${baaRecord.signedBy?.name ? ` by ${baaRecord.signedBy.name}${baaRecord.signedBy.title ? `, ${baaRecord.signedBy.title}` : ""}` : ""}.${baaAwaitingCountersign ? " A replacement is waiting for our counter-signature." : ""}` : baaAwaitingCountersign ? "Waiting for our counter-signature." : "Not yet signed."; const subscription = billing.subscription; // Billing is the practice's, so its dates are the practice's calendar days, as on the Subscription page. const practiceZone = practiceTimeZoneOf(currentOrganization) ?? undefined; const subscriptionFacts = subscription ? [ `${formatMoney(subscription.monthlyTotalCents)}/month`, subscription.cancelAtPeriodEnd ? subscription.paidThrough ? `paid through ${formatDayMonthYear(chargeDay(subscription.paidThrough, subscription.paidThroughInstant, practiceZone))}, will not renew` : "will not renew" : subscription.nextChargeAt ? `next charge ${formatDayMonthYear(chargeDay(subscription.nextChargeAt, subscription.nextChargeInstant, practiceZone))}` : undefined, cardLabel(subscription.paymentMethod), ].filter(Boolean).join(" · ") + "." : undefined; const activeAgencies = Array.isArray(contractors) ? contractors.filter((c) => !c.revokedAt) : []; const agencySummary = contractors === "checking" ? "Checking…" : contractors === undefined ? "Contractor access could not be checked." : activeAgencies.length === 0 ? "No contractor currently has access. Review access history or authorize a contractor." : `${activeAgencies.map((c) => c.name || "A contractor").join(", ")} ${activeAgencies.length === 1 ? "has" : "have"} platform manager access.`; const ownerName = nameOf(ownerState?.ownerPractitionerId); const ownerSince = ownerState?.acceptedAt ? formatDayMonthYear(ownerState.acceptedAt) : undefined; const ownerSummary = owner === "checking" ? "Checking…" : owner === undefined ? "Ownership could not be checked." : ownerState?.needsOwner ? "This practice has no owner yet." : [ ownerName && ownerSince ? `${ownerName} · owner since ${ownerSince}.` : ownerName ? `${ownerName} owns this practice.` : viewerIsOwner ? "You own this practice." : "This practice has an owner.", ownerState?.pendingTransferToPractitionerId ? `The owner stays the same until ${nameOf(ownerState.pendingTransferToPractitionerId) ?? "the nominated person"} accepts.` : undefined, ].filter(Boolean).join(" "); return (

Practice account

Manage your agreement, subscription, ownership and contractor access.

{closureNotice && closureNotice.organizationId === currentOrganization?.id && (
{closureNotice.text}
)} {underClosure && closureRead ? ( ) : ownerNeedsAttention ? ( ) : viewerIsOwner ? (

You own this practice

Only the practice owner can transfer ownership or close this practice.

) : null} {!underClosure && (
navigate("../baa", { relative: "path" })} /> {!receivingOnly && ( navigate("/dashboard/subscription")} /> )} {isAdminWorkspace && setPanel("agency")} />} {isAdminWorkspace && Array.isArray(arrangements) && arrangements.length > 0 && ( (row.contractorName || "Your contractor") + ": " + autopayStatus(row) + ".")} onOpen={() => setPanel("autopay")} /> )} {isAdminWorkspace && (arrangements === undefined || autopayPage?.complete === false) && ( setPanel("autopay")} /> )} setPanel("owner")} /> {/* Admins only, as the route admits: the practice's own declaration, opened and read fresh each time. */} {isAdminWorkspace && currentOrganization?.id && ( setPanel("part2")} /> )} {closureRead?.isOwner ? ( navigate(PRACTICE_CLOSURE_PATH)} /> ) : closure === "denied" || closure === "failed" ? ( ) : null}
)}
!open && setPanel(null)}> {panel === "agency" ? "Contractor access" : panel === "autopay" ? "Contractor auto-pay" : panel === "part2" ? "Part 2 program status" : "Practice owner"} {panel === "agency" ? "Who from an outside contractor can manage this practice." : panel === "autopay" ? "What your contractor may charge for its services, and the card it uses." : panel === "part2" ? "Whether this practice's programs are covered by 42 CFR Part 2." : "The one person nobody else can remove from this practice's account."}
{panel === "agency" && ( )} {panel === "autopay" && isAdminWorkspace && currentOrganization?.id && (
{Array.isArray(arrangements) && } {arrangements === "checking" &&

Checking contractor auto-pay…

} {(arrangements === undefined || autopayPage?.complete === false) &&

Contractor auto-pay could not be checked completely.

} {autopayPage?.nextCursor && }
)} {panel === "part2" && isAdminWorkspace && currentOrganization?.id && ( )} {panel === "owner" && ( )}
); }; export default PracticeAccountPage;