import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '../ui/sheet'; import { useCallback, useEffect, useRef, useState } from 'react'; import { CheckboxControl } from '../ui/checkbox-control'; import { useSandboxEntry } from './useSandboxEntry'; import { CHECK_FAILED_COPY } from './sandboxLifecycle'; import RestoreSetupSheet from './RestoreSetupSheet'; import { PREPARING_LABEL } from './prepareSandbox'; import { readSandboxAgreement, type SandboxAgreementAnswer } from './sandboxAgreement'; import { AgreementBlocks } from '../FirstSignInGate/AgreementBlocks'; import { environment } from '../../environments/environment'; import { useHttpService } from '../../services'; import type { SaveTransport } from '../../utils/saveOperation'; import './SandboxEntryDialog.css'; type SandboxEntryDialogProps = { onClose: () => void; beforeEnter?: (transport: SaveTransport) => Promise; }; export const AGREEMENT_LOADING_COPY = 'Loading the Sandbox agreement...'; export const AGREEMENT_NONE_COPY = 'Sandbox Mode is not available yet: no Sandbox agreement has been published.'; export const AGREEMENT_UNREADABLE_COPY = 'The Sandbox agreement could not be loaded, so Sandbox Mode cannot open. Try again.'; // The agreement and entry flow, opened from the header's mode menu and from the setup row - every door into the // sandbox. The copy is the Sandbox agreement published on the live stack, read fresh on each opening; the box // starts unchecked each time and again whenever the copy changes, and only the copy on screen is agreed to. const SandboxEntryDialog = ({ onClose, beforeEnter }: SandboxEntryDialogProps) => { const { enter, owner, pending, refusal, notice, restore, canRestore, restoreHeld, restoreStatus, checkFailed, canResume, checkRestore, resumeRestore, } = useSandboxEntry(beforeEnter); const { get } = useHttpService(); const [confirmingRestore, setConfirmingRestore] = useState(false); // Copy and choice belong to the owner they were read and made under; another owner sees neither. const [agreement, setAgreement] = useState<{ owner: number; answer: SandboxAgreementAnswer | 'loading' }>({ owner, answer: 'loading' }); const [choice, setChoice] = useState<{ owner: number; agreed: boolean }>({ owner, agreed: false }); const readRef = useRef(null); // The latest transport, read at call time: a new `get` on a render is not a new opening and asks nothing again. const getRef = useRef(get); getRef.current = get; // Each read retires the one before it, and closing the sheet retires the last, so a late answer never shows. const read = useCallback((forOwner: number) => { readRef.current?.abort(); const controller = new AbortController(); readRef.current = controller; setAgreement({ owner: forOwner, answer: 'loading' }); setChoice({ owner: forOwner, agreed: false }); const deliver = (answer: SandboxAgreementAnswer) => { if (readRef.current === controller && !controller.signal.aborted) setAgreement({ owner: forOwner, answer }); }; readSandboxAgreement(getRef.current, environment.liveFhirFoundationApi, controller.signal) .then(deliver) .catch(() => deliver({ kind: 'unreadable' })); }, []); useEffect(() => { read(owner); return () => { readRef.current?.abort(); readRef.current = null; }; }, [read, owner]); const answer = agreement.owner === owner ? agreement.answer : 'loading'; const agreed = choice.owner === owner && choice.agreed; const setAgreed = (next: boolean) => setChoice({ owner, agreed: next }); const copy = answer !== 'loading' && answer.kind === 'ready' ? answer.copy : undefined; const agree = async () => { if (!copy) return; const askedBy = owner; if (await enter(copy, askedBy) === 'changed') read(askedBy); }; return ( <> {confirmingRestore && ( { setConfirmingRestore(false); void restore(); }} onClose={() => setConfirmingRestore(false)} /> )} !open && !pending && onClose()}> pending && event.preventDefault()} onInteractOutside={(event) => pending && event.preventDefault()} > You are entering Sandbox Mode Sandbox is a practice copy for training and trying things out. Everything in it is made-up sample data.
{answer === 'loading' &&

{AGREEMENT_LOADING_COPY}

} {answer !== 'loading' && answer.kind === 'none' &&

{AGREEMENT_NONE_COPY}

} {answer !== 'loading' && answer.kind === 'unreadable' &&

{AGREEMENT_UNREADABLE_COPY}

} {copy && ( <>
{copy.blocks ? : ( <>

{copy.title}

{copy.body.split('\n').filter((line) => line.trim()).map((line, i) =>

{line}

)} )}

Sandbox agreement, version {copy.version}

)}

Entering takes about a minute the first time, and you accept the Terms once inside.

{refusal &&

{refusal}

} {notice &&

{notice}

} {restoreStatus &&

{restoreStatus}

} {checkFailed &&

{CHECK_FAILED_COPY}

}
{restoreHeld && ( )} {canResume && ( )} {canRestore && ( )} {answer !== 'loading' && answer.kind === 'unreadable' && ( )}
); }; export default SandboxEntryDialog;