import { useCallback, useEffect, useRef, useState } from "react"; import type { Appointment } from "fhir/r4"; import { Button } from "../../../../components/ui/button.tsx"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../../../../components/ui/dialog.tsx"; import { useOrgAuthorizationServiceContext, type OrgAuthorizationStatusValue, type OrgAuthorizationWithdrawResponse, } from "../../../../services/OrgAuthorizationService.tsx"; import { answerEligibilityOf } from "../../../../utils/proxyFormAuthority"; import { practiceDecisionFollowUp, PRACTICE_TEAM_PENDING_COPY } from '../../../../utils/practiceDecisionFollowUp'; import AgreementText from "../../../../components/OrgAuthorizationPrompt/AgreementText.tsx"; import { CONSENT_INSTRUMENT_DEFINITIONS, RECORDS_INSTRUMENT, agreementStateCopy, type ConsentInstrumentId, } from '../../../../config/consentInstruments.ts'; import { orgAgreementName } from '../../../../components/OrgAgreements/orgAgreements'; import type { OrgCollabTeam } from '../../../../utils/orgCollabTeams'; import { agreementStatusDeniedMessage, agreementStatusFailure } from "../../../../utils/agreementStatusFailure"; import { usePatientServiceContext } from "../../../../services/PatientService.tsx"; import { useAuthServiceContext } from "../../../../services/AuthService.tsx"; import { isAgreementAuthorityLost, useProxyMayAnswerAgreements } from "../../../../hooks/useProxyMayAnswerAgreements"; import { isSaveRetired } from "../../../../utils/saveOperation"; import { useAppointmentServiceContext } from "../../../../services/AppointmentService.tsx"; import { visitsAWithdrawalCancels, withdrawnVisitName } from "../../../../utils/withdrawalCancelledVisits.ts"; import { formatDate, formatTimeOfDay } from "../../../../utils/dateUtils.ts"; // // - THE PATIENT-FACING WITHDRAWAL. Until this existed the consent chain could only GRANT: // three capture paths, agreement versioning, a stale guard - and no way for a patient to press "no". // // IT LIVES ON THE PRACTICE'S OWN PAGE, not in a settings screen, because that is where the question // actually occurs to someone: they open the practice they are wondering about and see what it can see. // A "privacy settings" page listing organizations would be a second, competing list of the same thing. // // AND IT WORKS FOR AN AWAY PRACTICE, which is the part that needed thought. The patient's record is // org-agnostic and My Providers lists every practice involved in their care - but every org-scoped call // defaults to the WordPress install the patient is standing in. So each call here passes THIS page's // organization id explicitly. Without that, a patient viewing practice B and pressing withdraw would // have withdrawn from practice A, silently and irreversibly-looking. // // THE COPY IS LOAD-BEARING AND IS NOT A DRAFT TO BE TIGHTENED LATER. Spencer's ruling: withdrawal is // FORWARD-ONLY - the practice keeps everything it could see up to the instant, because that retention is // a legal requirement, and loses new information from then on. This card exists because the product's // patient-facing words already promised more than the code did, so wording that implies the record is // clawed back would recreate the exact defect it was raised to close. // type Phase = "loading" | "error" | "denied" | OrgAuthorizationStatusValue; type AffectedVisits = { state: "loading" } | { state: "failed" } | { state: "read"; visits: Appointment[] }; type WithdrawalOutcome = | { state: "checking" } | { state: "unconfirmed" } | { state: "read"; cancelled: Appointment[]; standing: Appointment[] }; const formatWhen = (iso?: string): string => { if (!iso) return ""; const parsed = new Date(iso); if (Number.isNaN(parsed.getTime())) return ""; return parsed.toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" }); }; const WithdrawnVisitList = ({ visits }: { visits: Appointment[] }) => ( ); export const ProviderAccessSection = ({ orgId, organizationName, instrument = RECORDS_INSTRUMENT, collabTeams, }: { orgId: string; /** This practice's people in the patient's collaboration teams, named in the withdraw confirmation. */ collabTeams?: OrgCollabTeam[] | null; organizationName?: string; /* * WHICH OF THE THREE DOCUMENTS THIS CARD IS ABOUT * * Defaults to the HIPAA Authorization, which is what this section asked about when it asked about * one, so any caller that names nothing keeps doing exactly what it did. * * The practice page now renders one per document, because a declined Consent to Treat was reachable * from nowhere at all: the outstanding-agreements notice hides once everything is answered, and a * refusal is answered. */ instrument?: ConsentInstrumentId; }) => { const { getStatus, getAgreement, agree, withdraw } = useOrgAuthorizationServiceContext(); const { patientState } = usePatientServiceContext(); const { actingAs } = useAuthServiceContext(); const { fetchAppointments, fetchAppointmentsWithPolling, holdServerChangedAppointments } = useAppointmentServiceContext(); const [phase, setPhase] = useState("loading"); // WHEN THIS AGREEMENT REACHED THE STATE IT IS IN. It used to be set only by the WITHDRAW action, // so a row could say "Withdrawn on 2 Sep" the instant a patient withdrew and say nothing at all // after a reload - the value never came back from the status read. It does now, for every state, // so the date survives the page being refreshed and an AGREED row can state its date too. const [answeredAt, setAnsweredAt] = useState(); const [agreedVersion, setAgreedVersion] = useState(); const [confirmOpen, setConfirmOpen] = useState(false); const [restoreOpen, setRestoreOpen] = useState(false); const [agreementText, setAgreementText] = useState(null); const [agreementVersion, setAgreementVersion] = useState(null); // The answer that made the section busy: only it, finishing, makes it idle again. const [busyBy, setBusyBy] = useState(null); const busy = busyBy !== null; const idleAfter = (operation: object) => setBusyBy((current) => (current === operation ? null : current)); const [actionError, setActionError] = useState(null); // Which practice, document and dialog is open: a read or answer started in one is never used in another. // Whether this viewer may answer for this practice and document is published by each status read, and read again // before every answer. const { limitation, begin, beginFollowUp, readEligibility } = useProxyMayAnswerAgreements(actingAs?.subjectName?.trim() || "them", [orgId ?? "", instrument, confirmOpen ? "withdraw" : "", restoreOpen ? "restore" : ""].join("|"), { scope: [orgId ?? "", instrument].join("|"), read: (transport) => getStatus(undefined, orgId, instrument, transport) }); const beginRead = useRef(begin); beginRead.current = begin; const [affected, setAffected] = useState({ state: "loading" }); const affectedRead = useRef(0); // What became of the visits named in the confirmation, as read back one by one after the withdrawal. const withdrawalReceipt = useRef | null>(null); const [savedWithdrawalOutcome, setWithdrawalOutcome] = useState(null); const [savedWithdrawalIncomplete, setWithdrawalIncomplete] = useState(); const withdrawalOutcome = withdrawalReceipt.current?.belongs() ? savedWithdrawalOutcome : null; const withdrawalIncomplete = withdrawalReceipt.current?.belongs() ? savedWithdrawalIncomplete : undefined; const practice = organizationName || "This practice"; // ONE TABLE, THREE DOCUMENTS. The sentences differ per document because the documents are not alike - // one governs reading the record, one governs treating the patient, and the third is an // acknowledgement that cannot be granted or refused at all. See `agreementStateCopy`. const copy = agreementStateCopy(instrument, practice, collabTeams); // GRANT OR ACKNOWLEDGEMENT, and three things read off it: the answer word, whether there is a // door at all, and whether the document's dialog has anything to press. You cannot decline // having been told something. const kind = CONSENT_INSTRUMENT_DEFINITIONS[instrument].kind; const documentName = orgAgreementName(instrument); const load = useCallback(async () => { const eligibilityRead = readEligibility(); try { // THREE DOCUMENTS NOW EXIST AND THIS SURFACE STILL ASKS ABOUT ONE: the instrument named here is the HIPAA Authorization because that is the // one whose BEHAVIOUR the old welded "Authorization to Treat" actually had - an unsigned authorization // meant the practice could not see the record, which answers access, on an object whose name said // treatment. Naming it keeps this surface doing exactly what it did. Rebuilding these surfaces around // all three documents is the consent-flow work, not this change. const read = await getStatus(undefined, orgId, instrument, eligibilityRead.transport); // Only the newest read in scope is used at all; an overtaken one changes nothing on screen. if (!eligibilityRead.publish(answerEligibilityOf(read))) return; const { status, answeredAt: when, agreedVersion: version } = read; setPhase(status); setAnsweredAt(when); setAgreedVersion(version); } catch (loadError) { // Never fall back to a concrete status. Rendering "no access" because a fetch failed would tell // a patient their record is closed when it may be wide open - a false statement about their own // data is the failure mode this whole card is about. Neither branch below names a status. // // WHAT SPLITS THEM IS THAT A REFUSAL AND A FAILURE ARE DIFFERENT EVENTS. This used to discard // the error entirely, so a 403 rendered as a loading problem with a Try again that could only // ever produce the same 403 - measured on journey D8, where all three of these calls come back // refused. The rule is that a refusal renders as a denial and never as a failure. if (!eligibilityRead.publish({ status: "unavailable" })) return; setPhase(agreementStatusFailure(loadError)); } // A different person acted for is a different answer: read again. }, [getStatus, orgId, instrument, readEligibility, actingAs?.relatedPersonId, actingAs?.subjectPatientId]); useEffect(() => { void load(); }, [load]); // What the withdrawal will cancel, read when its confirmation opens. A failed read says so; it is never // shown as none. const readAffected = useCallback(async () => { const read = ++affectedRead.current; setAffected({ state: "loading" }); try { if (!patientState) throw new Error("No patient to read visits for"); const now = new Date().toISOString(); const bundle = await fetchAppointments(patientState, now, undefined, orgId); const visits = visitsAWithdrawalCancels((bundle.entry ?? []).map((entry) => entry.resource as Appointment), orgId, now); if (read === affectedRead.current) setAffected({ state: "read", visits }); } catch (readError) { console.error("Failed to read the visits a withdrawal would cancel:", readError); if (read === affectedRead.current) setAffected({ state: "failed" }); } }, [patientState, fetchAppointments, orgId]); const openWithdraw = () => { setConfirmOpen(true); void readAffected(); }; const onWithdraw = async () => { setActionError(null); const operation = begin(); setBusyBy(operation); try { await operation.admitSend(); // The same document this section read its status from - withdrawing closes the record grant, // which is what this control has always meant. const result = await withdraw(orgId, instrument, operation.transport); if (!operation.belongs()) return; // Closing the dialog ends this answer's opening; the read-back that follows belongs to this patient and practice. const followUp = beginFollowUp(); withdrawalReceipt.current = followUp; setConfirmOpen(false); // The server is the authority on the instant, including on a repeat press where it returns the // FIRST one - the boundary never moves forward. setAnsweredAt(result.withdrawnAt); setPhase(result.withdrawn ? "withdrawn" : "none"); if (!result.withdrawn) return; setWithdrawalIncomplete(result.consequencesIncomplete); const previewed = affected.state === "read" ? affected.visits : []; setWithdrawalOutcome(previewed.length > 0 ? { state: "checking" } : null); // The server reports counts, not which visits, and a 200 can still leave some standing. Each one is // read back by id, and only a visit read back cancelled is called cancelled. Those reads are also // held in the shared list, since search goes on returning them booked for a while. holdServerChangedAppointments(previewed.map((visit) => visit.id).filter((id): id is string => Boolean(id)), followUp.transport) .then((current) => { if (!followUp.belongs()) return; const cancelledIds = new Set(current.filter((visit) => visit.status === "cancelled").map((visit) => visit.id)); if (previewed.length > 0) setWithdrawalOutcome({ state: "read", cancelled: previewed.filter((visit) => cancelledIds.has(visit.id)), standing: previewed.filter((visit) => !cancelledIds.has(visit.id)), }); }, (holdError) => { if (isSaveRetired(holdError) || !followUp.belongs()) return; console.error("Failed to read the visits a withdrawal cancelled:", holdError); if (previewed.length > 0) setWithdrawalOutcome({ state: "unconfirmed" }); }) .then(() => { if (followUp.belongs() && patientState) return fetchAppointmentsWithPolling(patientState, true); }) .catch((refreshError) => console.error("Failed to re-read the appointments after a withdrawal:", refreshError)); } catch (error) { if (isSaveRetired(error) || !operation.belongs()) return; setActionError(isAgreementAuthorityLost(error) ? "Your access could not be confirmed to answer this for them, so nothing changed. Contact the practice for help." : "We couldn't complete that just now. Your access settings have not changed."); } finally { idleAfter(operation); } }; const openRestore = () => { setActionError(null); setRestoreOpen(true); }; // The text is read afresh for each opening of the dialog, and shown or answered only while that opening stands. const subjectId = actingAs?.subjectPatientId; useEffect(() => { setAgreementText(null); setAgreementVersion(null); if (!restoreOpen) return; const reading = beginRead.current(); getAgreement(orgId, instrument, reading.transport).then((agreement) => { if (!reading.belongs()) return; setAgreementText(agreement.text); setAgreementVersion(agreement.version); }, (error) => { if (isSaveRetired(error) || !reading.belongs()) return; setActionError("We couldn't load this practice's authorization text."); }); return () => reading.retire(); }, [getAgreement, instrument, orgId, restoreOpen, subjectId]); const onRestore = async () => { if (agreementVersion === null) return; setActionError(null); const operation = begin(); setBusyBy(operation); try { await operation.admitSend(); // Re-authorizing creates a NEW authorization rather than re-opening the closed one, so the record // reads as one relationship with a gap in it. That is the server's job (ensureProposedOrgAuthorization // treats a withdrawn authorization as none); this just agrees, exactly as a first-time patient does. await agree( // The same document this section read its status and its text from. { captureMethod: "self-service", agreedVersion: agreementVersion, instrument }, orgId, operation.transport, ); if (!operation.belongs()) return; setRestoreOpen(false); // Re-agreeing mints a NEW authorization, so the old withdrawal instant is not this row's // date any more. Cleared rather than left standing: the next status read supplies the date // this agreement was given, and until it lands the row states its answer without one. setAnsweredAt(undefined); setPhase("active"); } catch (error) { if (isSaveRetired(error) || !operation.belongs()) return; setActionError(isAgreementAuthorityLost(error) ? "Your access could not be confirmed to answer this for them, so nothing changed. Contact the practice for help." : "We couldn't complete that just now. Your access settings have not changed."); } finally { idleAfter(operation); } }; if (phase === "loading") return null; /** * WHAT THE PATIENT ANSWERED, IN ONE WORD - and it is derived from the instrument's KIND rather than * written out three times. You agree to a grant and you acknowledge a notice; `hipaa-privacy` is the * one document that cannot be granted or refused at all, so it can never read "Agreed". * * THERE IS NO WORD FOR A REFUSED OR FAILED READ, and that absence is deliberate. A denial is not a * status - it is the server declining to state one - and putting any word in this cell would be the * exact failure the denial rule names: a refusal rendering as data. Those two phases render their * sentence below instead, where a whole sentence can say what happened. */ const answer = ((): string | undefined => { if (phase === "error" || phase === "denied") return undefined; if (kind === "acknowledgement") { // `withdrawn` and `declined` mean READ here too - the copy table says so, because there is // nothing about having been told something that a patient can later un-be-told. return phase === "none" || phase === "proposed" ? "Not read yet" : "Acknowledged"; } if (phase === "active") return "Agreed"; if (phase === "withdrawn") return "Withdrawn"; if (phase === "declined") return "Declined"; return "Not answered"; })(); /** * THE SENTENCE STAYS, AND IT IS WHY THIS IS A ROW WITH SOMETHING UNDER IT RATHER THAN A ROW. * * The deck compresses each document to name - answer - control, and draws every row agreed - which is * the ordinary case and is what a patient sees at almost every practice. It does not draw the states * that carry copy written against a ruling: * * declined "you declined - you can change this." Spencer's ruling names this line, and it is * the entire patient-facing purpose of the declined state * withdrawn the forward-only sentence: they keep what the record held up to that instant, * because retention is a legal requirement, and lose everything after it * denied the one denial rule - a refusal renders as a denial NAMING what is denied, never as * empty data. Measured on journey D8, where all three of these reads come back refused * * So the answer word carries the ordinary case and the sentence appears UNDER the row for every * state the deck did not draw. The compression is kept where it was designed to apply, and nothing * that exists because of a ruling can be lost to a state nobody drew. */ const sentence = ((): string | undefined => { if (phase === "active") return undefined; if (phase === "withdrawn") { return copy.withdrawn + (answeredAt ? ` Withdrawn on ${formatWhen(answeredAt)}.` : ""); } if (phase === "declined") return copy.declined; if (phase === "proposed") return copy.proposed; if (phase === "none") return copy.none; if (phase === "denied") return agreementStatusDeniedMessage(practice, documentName); // UNCHANGED, AND DELIBERATELY SO. The instinct in this sentence was always right - it refuses to // show a status it is unsure of - and it was only ever wrong about the denial case above it. return "We couldn't load this right now, so we aren't showing a status rather than showing one we are unsure of."; })(); return (
{/* NO min-w-0 HERE, AND ITS ABSENCE IS THE POINT. This row is flex-wrap, so it is meant to push a trailing item onto a second line when space runs out. min-w-0 defeated that: it let the name shrink past its own words while the answer beside it refuses to shrink, so the answer took the width and the name collapsed instead of the row wrapping. Harmless while the answer was one word; the answer now carries a date, and at 390 the name measured 29px tall enough for three lines - "Consent" / "to" / "Treat" - beside a 171px answer. Letting the name keep its min-content restores the wrap the layout already asked for. */}

{documentName}

{answer && (

{answer} {/* THE DATE RIDES THE ANSWER WORD, which is where the deck draws it - on every row, including an agreed one. It is deliberately NOT a sentence: the sentence under the row exists for states the deck did not draw, and an ordinary agreed row is the one case that has always been compressed to name, answer and control. ⛔ Not drawn for `proposed`: its date is when the question was RAISED, not answered, and printing it beside "Not answered" would read as an answer on that date. */} {answeredAt && phase !== "proposed" && ( · {formatWhen(answeredAt)} )} {agreedVersion !== undefined && phase !== "proposed" && phase !== "none" && ( · {agreedVersion < 0 ? "Akashic wording v" + (-agreedVersion) : "v" + agreedVersion} )}

)} {/* AN ACKNOWLEDGEMENT HAS NO DOOR BECAUSE IT HAS NO DECISION - but it does have a document, and the deck gives it View. Those do not conflict: reading a notice again is not a decision, and `restoreLabel` is still absent for it, which is what keeps a Withdraw or an Agree off the one document `declinableInstruments()` already excludes. */} {kind === "acknowledgement" && ( )} {/* Colour from role: this takes something away, so it is destructive. It never fires on the first click - the confirm below is where the forward-only consequence is stated. ⚠️ THE VISIBLE WORD IS THE DECK'S AND THE ACCESSIBLE ONE IS THE DOCUMENT'S. You withdraw ACCESS to a record and you withdraw CONSENT to be treated, and naming the wrong one describes the wrong thing being given up - which is why the full label is still what a screen reader announces, and what the confirmation is titled. On screen the document is named in the same row three inches to the left, so repeating it in the button spends the width the compression was for. */} {kind === "grant" && phase === "active" && copy.withdrawLabel && !limitation && ( )} {/* `declined` BELONGS IN THIS LIST, and leaving it out was the failure mode to avoid: the patient would be told they declined and given no way to change it, which turns Spencer's "you can change this" into a claim the screen does not honour. ⭐ The label says "again" only for a withdrawal - nothing was ever allowed after a decline, so "again" would be false. 🚨 THIS IS THE ONLY DOOR TO A DECLINED DOCUMENT, and the reason it lives HERE rather than in the outstanding-agreements notice: a decline is never re-prompted, and volunteering is not the same as being asked. A nag arrives; an affordance waits on a page the patient chose to open. */} {(phase === "withdrawn" || phase === "proposed" || phase === "none" || phase === "declined") && copy.restoreLabel && !limitation && ( )} {/* THE RETRY BELONGS TO THE RETRYABLE CASE ONLY, and its absence under a denial is the point rather than an oversight: offering it there hands somebody a button whose every press reproduces the refusal that put it on screen. The denied sentence says the retry is gone and why, because a control that silently vanishes reads as a bug. */} {phase === "error" && ( )}
{limitation && kind !== "acknowledgement" && phase !== "error" && (

{limitation}

)} {sentence &&

{sentence}

} {/* THE HONEST SENTENCE, and the one a bare "Withdrawn" in the answer cell gets wrong - which is the whole hazard of compressing a card into a row. Only the records grant has anything for a practice to KEEP, so only that document says so; saying it of consent to treat would describe a retention that does not exist. ⚠️ I dropped this making the row and a test caught it. It is written against Spencer's forward-only ruling: they keep what the record held up to the instant, because that retention is a legal requirement, and lose everything after it. A patient who believes withdrawing erases has been misled by the screen. */} {phase === "withdrawn" && instrument === RECORDS_INSTRUMENT && (

They can still see what your record held up to that point — they are required to keep it — but nothing added since, and they cannot message you or add you to a collaboration team.

)} {phase === "withdrawn" && withdrawalOutcome?.state === "checking" && (

Checking your visits with {practice}…

)} {phase === "withdrawn" && withdrawalOutcome?.state === "read" && withdrawalOutcome.cancelled.length > 0 && (

{withdrawalOutcome.cancelled.length === 1 ? `This visit with ${practice} was cancelled:` : `These visits with ${practice} were cancelled:`}

)} {phase === "withdrawn" && withdrawalOutcome?.state === "read" && withdrawalOutcome.standing.length > 0 && (

{withdrawalOutcome.standing.length === 1 ? `This visit with ${practice} is still booked. Contact ${practice} to cancel it:` : `These visits with ${practice} are still booked. Contact ${practice} to cancel them:`}

)} {phase === "withdrawn" && withdrawalOutcome?.state === "unconfirmed" && (

We couldn't confirm which of your visits with {practice} were cancelled. Check your appointments, or contact {practice}.

)} {phase === "withdrawn" && (withdrawalIncomplete?.appointmentSearchFailed || withdrawalIncomplete?.tasksUnsettled || (Boolean(withdrawalIncomplete?.appointmentsStillStanding) && !(withdrawalOutcome?.state === "read" && withdrawalOutcome.standing.length > 0))) && (

{practice} may still have visits booked for you. Contact {practice} to check.

)} {phase === 'withdrawn' && practiceDecisionFollowUp(withdrawalIncomplete).teams && (

Your answer is recorded. {PRACTICE_TEAM_PENDING_COPY}

)} {phase === 'withdrawn' && practiceDecisionFollowUp(withdrawalIncomplete).other && (

Your answer is recorded. Some follow-up could not yet be confirmed.

)} {actionError &&

{actionError}

} {/* A WHOLE SENTENCE FROM THE TABLE, not a label with the practice appended. "Withdraw access for X?" and "Withdraw X's access?" are not the same question in English, and this one was written and reviewed for the records grant - assembling it here would have rewritten it as a side effect of making the section serve three documents. */} {copy.withdrawTitle} {/* FROM THE TABLE, LIKE THE TITLE ABOVE IT — and it was not, which is the defect. Measured in a browser: withdrawing Consent to Treat showed the correct instrument-aware TITLE over the records-access BODY, telling a patient stopping their own care that the practice "will not see anything new in your record" and that "your appointments are not cancelled". The half that had been migrated made the half that had not look deliberate. */} {copy.withdrawPoints ? (
{copy.withdrawPoints.map((point) => (

{point.lead} {point.detail}

))}

{copy.withdrawBody}

) : ( {copy.withdrawBody} )}
{affected.state === "loading" && (

Checking your visits booked with {practice}…

)} {affected.state === "failed" && (

{`We couldn't check which visits with ${practice} this cancels. Any visit you have booked with them will still be cancelled.`}

)} {affected.state === "read" && affected.visits.length === 0 && (

{`You have no upcoming visits booked with ${practice}, so none will be cancelled.`}

)} {affected.state === "read" && affected.visits.length > 0 && ( <>

{affected.visits.length === 1 ? "This cancels 1 visit:" : `This cancels ${affected.visits.length} visits:`}

    {affected.visits.map((visit) => (
  • {withdrawnVisitName(visit)} — {formatDate(visit.start)} at {formatTimeOfDay(visit.start)}
  • ))}
)}
{/* Not offered once this helper may no longer answer, even with the confirmation already open. */} {!limitation && ( )} {limitation &&

{limitation}

}
{/* ONE DIALOG, TWO DOORS, AND ONLY ONE OF THEM AGREES TO ANYTHING. Reading the document and agreeing to it are the same text and a different act, so the footer is what differs: an acknowledgement is opened to be READ and has nothing to press. Rendering an "I agree" under a privacy notice would invent a decision the document does not carry. */} {documentName} — {practice} {kind === "acknowledgement" ? "This is the practice's notice for you to read. It does not ask for your agreement." : phase === "withdrawn" ? "This starts a new authorization. Your earlier one stays on your record, along with when it ended." : "Please read this practice's authorization before you agree."} {agreementText ? :

Loading…

} {kind === "grant" && (limitation ?

{limitation}

: ( ))}
); }; export default ProviderAccessSection;