import React, { useCallback, useEffect, useState } from 'react'; import { DismissibleAlert } from '../../../../components/ui/dismissible-alert'; import { CheckCircle, Clock, Download, FileText, XCircle } from 'lucide-react'; import { Skeleton } from '../../../../components/ui/skeleton.tsx'; import { BaaRecord, BaaHistoryEntry, useBAAService } from '../../../../services/BAAService.tsx'; import { signedRecordFor, type SignProof } from './baaSignOutcome'; import BaaSignModal from './BaaSignModal.tsx'; import BaaUploadModal from './BaaUploadModal.tsx'; import MsaAcceptForm from './MsaAcceptForm.tsx'; import { relayServerFailure } from '../../../../utils/fetchFailureMessage'; import { useAuthServiceContext } from '../../../../services/AuthService'; import { HttpError } from '../../../../services/HttpService.tsx'; import { isSaveRetired, startSaveOperation } from '../../../../utils/saveOperation'; import { writeThroughViewer } from '../../../../utils/writeThroughHold'; import { clearBaaUploadReference, loadBaaUploadReference, type BaaUploadReference } from '../../../../utils/baaUploadAttempt'; /** An earlier upload from this browser that is not settled: unknown, unproved, held, or not yet checkable. */ type OpenAttempt = | { kind: 'unknown'; reference: BaaUploadReference } | { kind: 'unchecked'; reference: BaaUploadReference } | { kind: 'held'; reference: BaaUploadReference } | { kind: 'unproved' }; const RECOVERED_NOTES: Record = { admitted: 'Your earlier submission is awaiting review.', withdrawn: 'Your earlier submission was withdrawn.', replaced: 'Your earlier submission was replaced by a newer one.', executed: 'Your earlier submission was counter-signed and is now in force.', retired: 'Your earlier submission was retired before it arrived.', 'not-admitted': 'Your earlier submission never arrived. You can send it again.', }; const codeOf = (error: unknown) => (error instanceof HttpError ? (error.body as { code?: string } | undefined)?.code : undefined); const StatusBadge: React.FC<{ status: BaaRecord['status'] }> = ({ status }) => { if (status === 'active') { return ( Active ); } if (status === 'pending') { return ( Pending Review ); } // follow-on: `revoked` is retired - a BAA is never un-signed, only superseded by a later // executed agreement. A superseded record is history and is not what this badge reports (the badge // shows the agreement in force), but the branch exists so a legacy row renders honestly rather than // as a blank. if (status === 'superseded') { return ( Superseded ); } return null; }; const InfoRow: React.FC<{ label: string; value: string }> = ({ label, value }) => (

{label}

{value}

); const BaaPage: React.FC = () => { const { getBaa, getBaaHistory, withdrawPendingBaa, recoverUpload, retireUploadAttempt } = useBAAService(); const { wpOrganization, currentUser } = useAuthServiceContext(); const [openAttempt, setOpenAttempt] = useState(null); const [attemptBusy, setAttemptBusy] = useState(false); const [note, setNote] = useState(null); const [baaRecord, setBaaRecord] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [showSignModal, setShowSignModal] = useState(false); const [showUploadModal, setShowUploadModal] = useState(false); // - replacement is reachable but never accidental: the controls stay behind an // explicit step that states the consequence first. See the block that renders them. const [showReplace, setShowReplace] = useState(false); // Its own state, and deliberately NOT folded into the load below. A history that could not be read // must not stop the agreement in force from rendering: the practice came here to see whether they // are covered, and that answer does not depend on what came before it. const [history, setHistory] = useState(null); const [historyFailed, setHistoryFailed] = useState(false); const [confirmWithdraw, setConfirmWithdraw] = useState(false); const [withdrawing, setWithdrawing] = useState(false); const [withdrawError, setWithdrawError] = useState(null); const reloadStatus = useCallback(() => { getBaa() .then(setBaaRecord) .catch((err) => setError(relayServerFailure(err, 'Failed to load BAA status.'))); }, [getBaa]); /** * An upload this browser started and did not see settle - a closed window, a reload - is asked about * once, read-only, with its original reference. Whatever it says is shown; this page never sends its * file again or withdraws anything on its behalf. */ const checkOpenAttempt = useCallback(async () => { const practiceId = wpOrganization.current?.id; if (!practiceId) return; const viewer = writeThroughViewer(); const stored = loadBaaUploadReference(practiceId, viewer, currentUser?.cognito?.sub); if (!stored) { setOpenAttempt(null); return; } if ('unproved' in stored) { setOpenAttempt({ kind: 'unproved' }); return; } const { reference } = stored; const live = () => wpOrganization.current?.id === practiceId && writeThroughViewer() === viewer; setAttemptBusy(true); try { const answer = await recoverUpload(reference, startSaveOperation(live).transport); if (!live()) return; clearBaaUploadReference(practiceId, reference.attemptId); setOpenAttempt(null); setNote(RECOVERED_NOTES[answer.outcome] ?? null); reloadStatus(); } catch (err) { if (!live() || isSaveRetired(err)) return; const code = codeOf(err); setOpenAttempt(code === 'attempt-unknown' ? { kind: 'unknown', reference } : code === 'attempt-changed' || code === 'pending-ambiguous' ? { kind: 'held', reference } : { kind: 'unchecked', reference }); } finally { setAttemptBusy(false); } }, [wpOrganization, currentUser, recoverUpload, reloadStatus]); useEffect(() => { void checkOpenAttempt(); }, [checkOpenAttempt]); /** Fences an attempt whose arrival is unknown so it can never land, before anything else is sent. */ const retireOpenAttempt = async (reference: BaaUploadReference) => { const practiceId = reference.practiceId; const live = () => wpOrganization.current?.id === practiceId && writeThroughViewer() === reference.viewer; setAttemptBusy(true); try { await retireUploadAttempt(reference, startSaveOperation(live).transport); if (!live()) return; clearBaaUploadReference(practiceId, reference.attemptId); setOpenAttempt(null); setNote('Your earlier submission was retired. You can send it again.'); reloadStatus(); } catch (err) { if (!live() || isSaveRetired(err)) return; // Admitted first: what it became is read again rather than assumed. if (codeOf(err) === 'attempt-has-standing') void checkOpenAttempt(); else setNote(relayServerFailure(err, 'That submission could not be retired. Check it again shortly.')); } finally { setAttemptBusy(false); } }; const dismissUnproved = () => { const practiceId = wpOrganization.current?.id; if (practiceId) clearBaaUploadReference(practiceId, ''); setOpenAttempt(null); }; const openUpload = () => { if (openAttempt && openAttempt.kind !== 'unproved') { setNote('Your earlier submission is not settled yet. Check it or retire it before sending another.'); return; } setShowUploadModal(true); }; const handleSettled = (message: string) => { setShowUploadModal(false); setShowReplace(false); setNote(message); reloadStatus(); loadHistory(); void checkOpenAttempt(); }; const handleWithdraw = async () => { const shown = baaRecord?.pending; if (!shown) return; const draft = { documentKey: shown.documentKey, signedAt: shown.signedAt }; const practiceId = wpOrganization.current?.id; const live = () => wpOrganization.current?.id === practiceId; setWithdrawing(true); setWithdrawError(null); try { await withdrawPendingBaa(draft, startSaveOperation(live).transport); if (!live()) return; // CLEARED LOCALLY RATHER THAN REFETCHED, and only while it is still the draft withdrawn. The // read that would confirm this is eventually consistent, so asking again immediately can // return the submission just withdrawn. setBaaRecord((prev) => (prev?.pending?.documentKey === draft.documentKey && prev.pending.signedAt === draft.signedAt ? { ...prev, pending: undefined } : prev)); setConfirmWithdraw(false); } catch (err) { if (!live() || isSaveRetired(err)) return; // A newer submission, or one no longer awaiting review, is shown as it now is. const code = codeOf(err); if (code === 'replaced' || code === 'not-pending' || code === 'already-counter-signed') reloadStatus(); // The server distinguishes "there was nothing pending" from "we counter-signed it first, // and it is now the agreement in force". Its message says which, and collapsing them would // tell a practice their withdrawal worked when the document now binds them. setWithdrawError(relayServerFailure(err, 'That submission could not be withdrawn.')); } finally { setWithdrawing(false); } }; useEffect(() => { getBaa() .then(setBaaRecord) .catch((err) => setError(relayServerFailure(err, 'Failed to load BAA status.'))) .finally(() => setIsLoading(false)); }, [getBaa]); const loadHistory = useCallback(() => { // Failure leaves this null rather than empty, and the two render differently below: an empty // history means nothing has been replaced, while a failed read means we do not know -- and on a // compliance record those must never look the same. getBaaHistory() .then((entries) => { setHistory(entries); setHistoryFailed(false); }) .catch(() => { setHistory(null); setHistoryFailed(true); }); }, [getBaaHistory]); useEffect(loadHistory, [loadHistory]); // The history route also returns the agreement in force and a submission awaiting counter-signature. // Both are shown above already, and neither was replaced, so "Previous agreements" lists only the // retired ones. const replaced = history?.filter((entry) => entry.status === 'superseded') ?? null; // A signature can retire the agreement in force, so the chooser closes and the history is read again. // Applied only for the practice the signature was started on. The receipt stays its own fact beside what // was observed now; nothing of an earlier agreement's signature or copy is carried onto it. const signedFor = (practiceId: string | undefined) => (record: BaaRecord, proof?: SignProof) => { if (wpOrganization.current?.id !== practiceId) return; setBaaRecord((prev) => signedRecordFor(prev, record, proof)); setShowSignModal(false); setShowReplace(false); loadHistory(); }; const handleUploaded = (record: BaaRecord) => { setBaaRecord(record); setShowUploadModal(false); setShowReplace(false); loadHistory(); void checkOpenAttempt(); }; const status = baaRecord?.currentUnconfirmed ? undefined : baaRecord?.status ?? null; // A FIRST SUBMISSION IS PENDING WITH NOTHING IN FORCE. The server reports the agreement in force as // `status` and a custom one awaiting counter-signature as `pending`, so a practice that has only ever // uploaded its own reads `status: null` with `pending` set. That is not "no BAA on file": it is the // same waiting state the replace path shows, without an agreement behind it. const awaitingFirst = status === null && Boolean(baaRecord?.pending); const msaFirst = baaRecord?.msa?.standing.state === 'outstanding'; return (
{note && (
setNote(null)}>{note}
)} {openAttempt && (
{openAttempt.kind === 'unknown' && ( <>

We could not confirm your earlier submission. It may still arrive.

)} {openAttempt.kind === 'unchecked' && ( <>

We could not check your earlier submission just now.

)} {openAttempt.kind === 'held' && (

Your earlier submission needs Akashic to review it before anything else changes.

)} {openAttempt.kind === 'unproved' && ( <>

An earlier upload from this browser could not be checked. If a submission seems to be missing, contact Akashic.

)}
)} {baaRecord?.pendingRead === 'failed' && (
We could not check whether a submission is awaiting review. Reload to try again.
)} {status === null && !awaitingFirst && !isLoading && !error && msaFirst && baaRecord?.msa?.preview && ( // The first BAA, standard or custom, follows the owner's MSA acceptance; an executed BAA never waits on it.

The practice owner accepts the Master Subscription Agreement before your first Business Associate Agreement.

{({ body, actions }) => (
{body} {actions}
)}
)} {status === null && !awaitingFirst && !isLoading && !error && !msaFirst && ( // Converted off @wordpress/components while here - same layout, no WP dependency.
)} {/* * 🚪 CARD 550 — THE ORGANIZATION PAGE OFFERS "Replace with a new agreement" AND THIS PAGE * HAD NO WAY TO DO IT. The sign and upload controls rendered only under `status === null`, so * the moment a BAA became active, replacement was unreachable while the button promising it * stayed on the previous screen. Supersession is a real, supported operation — `putBaaRecord` * writes an append-only history row and moves the pointer — so the capability existed on the * server with no door in the client. * * ⛔ NOT simply widened to `status !== null`. Re-signing SUPERSEDES a live compliance record, * and a control that does that must not sit one stray click away from a page people open to * download a PDF. ⭐ So it is a deliberate two-step: say what replacing does, THEN offer it. * * 🔑 And only from `active`. While a custom BAA is awaiting counter-signature there is * already a replacement in flight, and offering a second one would queue a duplicate behind * it rather than do what the admin expects. */} {status === 'active' && !baaRecord?.pending && !isLoading && !error && (
{!showReplace ? (

Need to re-sign — new wording, a corrected signatory, or a countersigned agreement of your own?

) : (
{/* ⚖️ States the consequence in the words that are actually true: the current agreement is RETIRED and kept, never deleted, and cover does not lapse. */}

Your current agreement stays on your record.{' '} Replacing it retires it rather than deleting it — it remains readable, with the date it was replaced. The agreement you have now stays in force until the new one is, so your patient portal keeps working throughout.

)}
)}

Business Associate Agreement

Required under HIPAA for patient portal access.

{baaRecord?.currentUnconfirmed ? Current status unconfirmed : status && }
{isLoading ? (
{[...Array(3)].map((_, i) => (
))}
) : error ? (

Could not load BAA status

Try again later.

{/* The bespoke Tailwind classes this carried (bg-primary / text-white / hover:bg-primary/80) are DROPPED rather than kept alongside `btn btn-blue`, because they are the same paint: `.btn-blue` is `background-color: var(--primary)` with `color: var(--primary-foreground)` — the very same tokens `bg-primary` / `text-white` resolved to. Keeping both would leave two sources of truth for one colour. Only the box moves: .btn's padding and 0.3rem radius replace `rounded p-2 px-4`. */}
) : status === null && !awaitingFirst ? (

No BAA on file

Patients cannot access the portal until a BAA is active. Sign the Akashic standard BAA or upload your own.

) : (
{baaRecord?.ownReceipt && (

{baaRecord.currentUnconfirmed ? `Your signature was recorded on ${new Date(baaRecord.ownReceipt.signedAt).toLocaleString()}. The agreement now in force could not be confirmed just now.` : `Your signature was recorded on ${new Date(baaRecord.ownReceipt.signedAt).toLocaleString()}. The agreement this practice now shows is below.`}

)} {baaRecord?.signedBy && ( <> )} {baaRecord?.signedAt && ( )} {baaRecord?.activatedAt && ( )} {/* With the rows above, so one awaiting counter-signature never splits the agreement in force. */} {baaRecord?.downloadUrl && ( )} {status === 'pending' && (
Your BAA has been submitted and is awaiting Akashic counter-signature. You will be notified when it is approved.
)} {/* Card 090 follow-on: a custom BAA awaiting counter-signature no longer displaces the one in force, so it is reported alongside it. Saying plainly which agreement still governs is the point — the org keeps full BAA-active status meanwhile. */} {baaRecord?.pending && (status === 'active' || awaitingFirst) && (
A custom BAA submitted on{' '} {new Date(baaRecord.pending.signedAt).toLocaleString()} is awaiting Akashic counter-signature.{' '} {awaitingFirst ? 'Your patient portal will be activated once the review is complete.' : 'Your current BAA remains in force until it is signed by both parties.'} {/* WHAT THEY ACTUALLY SENT. Walked 2026-09-05: "nothing lets you check which one you submitted, because the only download link on the page is the active agreement". */} {baaRecord.pending.documentUrl && (

Download submitted document

)} {withdrawError && (

{withdrawError}

)} {/* AND A WAY OUT. Before this, the only escape from a wrong PDF was for Akashic to execute it and for the practice to replace it -- two executed agreements on the record to undo one mistake. A confirming second press, because it discards something they sent us. Not worded as irreversible, because it is not: they can send another. */}

{/* Red because it discards the submission - the convention is that red means something is destroyed - and ghosted because it is not the primary action on this screen. */}

)}
)}
{/* PREVIOUS AGREEMENTS -- the promise the replace screen makes, kept. "Replacing it retires it rather than deleting it - it remains readable, with the date it was replaced." Until this section existed there was nowhere it remained readable to the practice: only Akashic staff could see it, through the internal console. Rendered BELOW the agreement in force, never mixed with it. One of these governs today and the others do not, and a list that blurred that would be worse than no list. */} {/* A FAILED READ IS NOT AN EMPTY HISTORY, and on a compliance record the two must never look the same. Rendering nothing in both cases would tell a practice whose read failed that they have never replaced an agreement -- the same erasure this product has already had to fix on a code lookup and on an order list. */} {historyFailed && (

We could not load your previous agreements just now. Nothing has changed about the agreement in force above.

)} {replaced !== null && replaced.length > 0 && (

Previous agreements

Agreements this practice has executed and since replaced. They are kept, not deleted.

    {replaced.map((entry) => (
  • {entry.type === 'custom' ? 'Custom agreement' : 'Akashic agreement'}

    {entry.signedBy?.name ? `Signed by ${entry.signedBy.name}${entry.signedBy.title ? `, ${entry.signedBy.title}` : ''}` : 'Signed'} {` on ${new Date(entry.signedAt).toLocaleDateString()}`}

    {/* The date it was replaced -- the half the promise names explicitly, and the one a list of documents alone omits. */} {entry.supersededAt && (

    {`Replaced on ${new Date(entry.supersededAt).toLocaleDateString()}`}

    )} {/* A rebuilt row says so to the practice too. They are the other party to this agreement, and a reconstructed record must not read as one written by the signature itself. */} {entry.reconstruction && (

    This agreement record was reconstructed from its document and our logs after a fault. Some details are inferred.

    )}
    {entry.documentUrl && ( Download )}
  • ))}
)} {showSignModal && ( setShowSignModal(false)} onSigned={signedFor(wpOrganization.current?.id)} /> )} {showUploadModal && ( { setShowUploadModal(false); void checkOpenAttempt(); }} onUploaded={handleUploaded} onSettled={handleSettled} shownPending={baaRecord?.pending ? { documentKey: baaRecord.pending.documentKey, signedAt: baaRecord.pending.signedAt } : null} pendingGeneration={baaRecord?.pendingGeneration} agreementInForce={status === 'active'} /> )}
); }; export default BaaPage;