// Pure helpers behind CareAccess.tsx (F1 plan, Task 10) - kept dependency-free (no React/HTTP imports) so // they're testable without rendering. Mirrors the save-value derivation the component uses on every // tier/checkbox change, and the default-grant shape used when the patient has no authority Consent yet. import type { AuthorityGrant, AuthorityTier } from '../../services/relatedPersonAuthority'; // `import type` only - erased at compile time, so this stays free of a runtime dependency on // ProofingService.tsx (which pulls in React hooks/context) despite crossing a service-file boundary. import type { ProofingStatusValue } from '../../services/ProofingService.tsx'; //: the local state shape now carries the two financial flags alongside the tier + messaging. export type CareAccessState = Pick; export const DEFAULT_AUTHORITY_STATE: CareAccessState = { tier: 'assist-only', allowMessaging: false, // Financial disclosure is opt-in: no grant means the patient has disclosed nothing. allowBillingView: false, allowBillingPay: false, }; // Higher tiers always carry messaging on - the checkbox only matters (and only renders) at assist-only. export const effectiveAllowMessaging = (tier: AuthorityTier, allowMessaging: boolean): boolean => tier === 'assist-only' ? allowMessaging : true; // Assist-only messaging is off until the patient opts in. A higher tier stores messaging on (above), so a // drop to assist-only starts it off again rather than carrying that stored value down as a grant the // patient never gave. Staying at assist-only keeps the patient's own choice. export const messagingAfterTierChange = ( from: AuthorityTier, to: AuthorityTier, allowMessaging: boolean, ): boolean => (to === 'assist-only' && from !== 'assist-only' ? false : allowMessaging); // Maps a fetched grant (or its absence, when the patient hasn't granted anything yet) to the component's // local state shape. export const grantToState = (grant: AuthorityGrant | undefined): CareAccessState => grant ? { tier: grant.tier, allowMessaging: grant.allowMessaging, // `?? false` rather than a bare read: a grant written before has no billing codings, so // the server omits nothing but an OLD CACHED response could still be flag-less. Absent denied. allowBillingView: grant.allowBillingView ?? false, allowBillingPay: grant.allowBillingPay ?? false, } : DEFAULT_AUTHORITY_STATE; // Phase 2 (full-proxy identity-proofing hard-gate), client Task 8 - a full-proxy grant is recorded // in the F1 Consent the moment the patient sets the tier, but the SERVER gate (Task 1/2) keeps it // INERT until the related person completes Stripe Identity verification. This is the read-only // display gate for that fact: purely informational, never mutates the tier/grant itself. Any // non-'verified' status (including 'none'/'pending'/'failed'/'expired'/undefined-while-loading) // shows the badge - failing toward "still shows as pending" rather than silently hiding a grant // that isn't actually active yet, same fail-safe direction as the server gate's own default. // // A full proxy over a subject under 18 is effective without verification (the minor-child exemption // the server applies when it resolves the tier), so it is never shown as waiting on it. export const shouldShowPendingProofingBadge = ( tier: AuthorityTier, proofingStatus: ProofingStatusValue | undefined, subjectIsMinor = false, ): boolean => tier === 'full-proxy' && proofingStatus !== 'verified' && !subjectIsMinor; export const buildPendingProofingBadgeLabel = (name: string): string => `Full proxy — pending ${name}'s identity verification`;