// The confirmation a patient sees before a decline lands, and it is the safety of this whole feature. // // A refusal is about the RELATIONSHIP rather than a date, so every appointment with that practice goes, // not just the next one. Cancelling three visits from one tap is a surprise the patient cannot undo, so // this names them: the count AND each appointment, never a bare number in prose. import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '../ui/alert-dialog'; import { ConsentInstrumentId } from '../../config/consentInstruments'; import { orgAgreementName, type VisitsRead } from './orgAgreements'; import type { AgreementSubject } from './agreementSubject'; export interface CancellableAppointment { id: string; /** What the patient calls it - "the annual physical". */ description: string; /** Formatted for display by the caller - "Wed Aug 26". */ whenLabel: string; } interface DeclineAgreementConfirmationProps { open: boolean; organizationName: string; instrument: ConsentInstrumentId; /** EVERY appointment with this practice, not only the next one. */ appointments: readonly CancellableAppointment[]; /** Whether that list was read; an empty list not read is never presented as no appointments. */ visitsRead?: VisitsRead; onConfirm: () => void; onGoBack: () => void; subject?: AgreementSubject; } // "the appointment" / "both appointments" / "all 3 appointments" - the drawing says "both" for two, // which is how somebody actually speaks. The list below carries the detail; this only sets expectation. // // SINGULAR IS "the", NOT "your", and it is the sentence around it that decides that: it already ends // "you have with them", so a possessive here produced "cancel your appointment you have with them" - // seen on screen in a walk. The possession is stated once, at the end, for every count. const countPhrase = (total: number): string => { if (total === 1) { return 'the appointment'; } if (total === 2) { return 'both appointments'; } return `all ${total} appointments`; }; const DeclineAgreementConfirmation = ({ open, organizationName, instrument, appointments, visitsRead = 'read', onConfirm, onGoBack, subject, }: DeclineAgreementConfirmationProps) => ( { if (!next) { onGoBack(); } }}> If you do not agree {organizationName} cannot treat {subject ? subject.subjectName : 'you'} without {orgAgreementName(instrument)}. {appointments.length > 0 && (

Declining will cancel {countPhrase(appointments.length)} {subject ? subject.subjectName + ' has' : 'you have'} with them:

{/* * NAMED, not counted. The card's rule is "count and dates, not a number in prose" - * a patient tapping Decline on a legal document must not discover afterwards that * they cancelled three visits. */}
    {appointments.map((appointment) => (
  • {appointment.description} on {appointment.whenLabel}
  • ))}
)} {visitsRead !== 'read' && (appointments.length === 0 || visitsRead === 'partial') && (

{visitsRead === 'loading' ? <>Checking the appointments {subject ? subject.subjectName + ' has' : 'you have'} with them… : visitsRead === 'partial' ? <>This may not be every appointment {subject ? subject.subjectName + ' has' : 'you have'} with {organizationName}. Declining cancels every appointment with this practice. : <>We could not check which appointments {subject ? subject.subjectName + ' has' : 'you have'} with {organizationName}. Declining cancels every appointment with this practice.}

)} {/* * ⚠️ This sentence is drawn, and it sits against the card's rule that a decline is final and * never re-prompted. Both hold only if "never re-prompted" means the notice stops asking * while the patient may still volunteer - and the route back is not drawn anywhere. Raised * in `_CLAUDE/2026-09-02-consent-three-agreements-pair-log.md`; the copy is kept as drawn * rather than quietly rewritten to match the card. */}

You can agree later if you change your mind, but the appointments will need booking again.

{/* * 🔑 DESTRUCTIVE ON THE LEFT, THE WAY OUT ON THE RIGHT, and red against blue rather than red * against nothing. Both come from the drawing, and the drawing is consistent with the * settled pattern on the family screens - the safe answer sits where the thumb rests and * carries the primary colour, so the destructive one is never the one pressed by momentum. * ⛔ `AlertDialogFooter` reverses its children on phones, so the order here is the DESKTOP * order; on a phone the safe answer ends up on top, which is the same principle. */} Decline and cancel Go back
); export default DeclineAgreementConfirmation;