import { useEffect, useRef, useState } from 'react'; import { AlertCircle, CheckCircle2, Loader2 } from 'lucide-react'; import type { Address, Organization } from 'fhir/r4'; import { useAuthServiceContext } from '../../../services/AuthService.tsx'; import { useOrganizationServiceContext } from '../../../services/OrganizationService.tsx'; import { useStaffPermissionServiceContext } from '../../../services/StaffPermissionService'; import { formatCampaignPostalAddress, resolveCampaignMailingAddress } from '../../../config/campaignMailingAddress.ts'; import { Button } from '../../../components/ui/button.tsx'; import { Checkbox } from '../../../components/ui/checkbox.tsx'; import { Input } from '../../../components/ui/input.tsx'; import { Label } from '../../../components/ui/label.tsx'; import { Alert, AlertDescription } from '../../../components/ui/alert.tsx'; import { relayServerFailure } from '../../../utils/fetchFailureMessage'; import SandboxDisabled from '../../../components/SandboxMode/SandboxDisabled'; import { environment } from '../../../environments/environment'; import { isSaveRetired, isSaveTimedOut, useSaveScope } from '../../../utils/saveOperation'; import { holdSettingsWrite, settingsWriteHolder, unresolvedSettingsWrite, useUnresolvedSettingsWrite, type UnresolvedSettingsWrite } from '../../../utils/unresolvedSettingsWrite'; const EMPTY: Address = { line: [''], city: '', state: '', postalCode: '', country: 'US' }; const UNKNOWN = 'The mailing-address save could not be confirmed. Check the saved mailing address before saving again.'; const normalized = (address: Address): Address => ({ line: (address.line ?? []).map(line => line.trim()).filter(Boolean), city: address.city?.trim(), state: address.state?.trim(), postalCode: address.postalCode?.trim(), ...(address.country?.trim() ? { country: address.country.trim() } : {}), }); const readableOrganization = (value: Organization | undefined, id: string): value is Organization => value?.resourceType === 'Organization' && value.id === id && typeof value.meta?.versionId === 'string' && Boolean(value.meta.versionId); const readableSetting = (value: { source?: unknown; effectiveAddress?: Address | null } | undefined): boolean => { if (value?.source === 'none') return value.effectiveAddress === null; if (value?.source !== 'primary' && value?.source !== 'override') return false; const address = value.effectiveAddress; return Boolean(address && Array.isArray(address.line) && address.line.every(line => typeof line === 'string') && address.line.some(line => typeof line === 'string' && line.trim()) && [address.city, address.state, address.postalCode].every(part => typeof part === 'string' && part.trim())); }; const CampaignMailingAddressSection = () => { const auth = useAuthServiceContext(); const { setWpOrganization } = auth; const currentOrganization = auth.currentOrganization ?? undefined; const { updateCampaignMailingAddress, fetchOrganizationAsCaller } = useOrganizationServiceContext(); const { permissionsSettled, canManage } = useStaffPermissionServiceContext(); const mayManage = permissionsSettled && canManage('organization'); const current = useRef({ organization: currentOrganization, mayManage }); current.current = { organization: currentOrganization, mayManage }; const [baseline, setBaseline] = useState(currentOrganization); const [resolved, setResolved] = useState(() => resolveCampaignMailingAddress(currentOrganization)); const [useOverride, setUseOverride] = useState(false); const [draft, setDraft] = useState
(EMPTY); const [saving, setSaving] = useState(false); const [checking, setChecking] = useState(false); const [needsRead, setNeedsRead] = useState(false); const [checked, setChecked] = useState(false); const [error, setError] = useState(); const [saved, setSaved] = useState(false); const generation = useRef(0); const working = useRef(false); const holder = settingsWriteHolder(`campaign-mailing-address:${currentOrganization?.id ?? ''}`); const held = useUnresolvedSettingsWrite(holder); const seed = (organization: Organization | undefined) => { const setting = resolveCampaignMailingAddress(organization); setUseOverride(setting.source === 'override'); setDraft(setting.source === 'override' && setting.address ? setting.address : EMPTY); }; const scope = useSaveScope(mayManage ? currentOrganization?.id : undefined, () => { working.current = false; setSaving(false); setChecking(false); setError(undefined); setSaved(false); setChecked(false); setNeedsRead(false); setBaseline(current.current.organization); setResolved(resolveCampaignMailingAddress(current.current.organization)); seed(current.current.organization); generation.current += 1; }); useEffect(() => { setBaseline(currentOrganization); setResolved(resolveCampaignMailingAddress(currentOrganization)); seed(currentOrganization); setError(undefined); setSaved(false); setChecked(false); setNeedsRead(false); generation.current += 1; }, [currentOrganization?.id]); const patch = (field: keyof Address, value: string) => { generation.current += 1; setDraft(prev => field === 'line' ? { ...prev, line: [value] } : { ...prev, [field]: value }); setSaved(false); }; const handleSave = async () => { const organizationId = currentOrganization?.id; const versionId = baseline?.meta?.versionId; if (environment.inSandboxMode || !current.current.mayManage || !organizationId || baseline?.id !== organizationId || !versionId || needsRead || working.current || unresolvedSettingsWrite(holder)) return; const operation = scope.start(); const admittedOrganization = current.current.organization; const draftGeneration = generation.current; const address = useOverride ? normalized(draft) : null; let sent = false; let ownHold: UnresolvedSettingsWrite | undefined; const settle = () => { if (ownHold && unresolvedSettingsWrite(holder) === ownHold) holdSettingsWrite(holder, undefined); }; const transport = { ...operation.transport, organizationId, versionId, admit: () => { operation.admit(); if (!current.current.mayManage || current.current.organization?.id !== organizationId) throw new Error('Your current practice cannot save this setting.'); if (unresolvedSettingsWrite(holder) && unresolvedSettingsWrite(holder) !== ownHold) throw new Error(UNKNOWN); }, onSend: () => { operation.admit(); sent = true; ownHold = { kind: 'campaign-mailing-address', state: 'unconfirmed', detail: versionId }; holdSettingsWrite(holder, ownHold); }, }; working.current = true; setSaving(true); setError(undefined); setSaved(false); setChecked(false); try { const answer = await operation.step(() => updateCampaignMailingAddress({ address }, transport).catch(error => { const status = (error as { status?: unknown } | null)?.status; if (typeof status === 'number' && status >= 400 && status < 500) settle(); throw error; }), true); operation.admit(); if (!readableOrganization(answer?.organization, organizationId) || !readableSetting(answer?.settings)) throw new Error(UNKNOWN); let confirmed = answer.organization; let effective = { address: answer.settings.effectiveAddress, source: answer.settings.source }; if (current.current.organization !== admittedOrganization) { const beforeRead = current.current.organization; confirmed = await operation.step(() => fetchOrganizationAsCaller(organizationId, undefined, organizationId, operation.transport)); if (!readableOrganization(confirmed, organizationId)) throw new Error(UNKNOWN); effective = resolveCampaignMailingAddress(confirmed); operation.admit(); if (current.current.organization !== beforeRead) throw new Error(UNKNOWN); setWpOrganization(confirmed); } else setWpOrganization(confirmed); operation.admit(); setBaseline(confirmed); setResolved(effective); settle(); if (generation.current === draftGeneration) { seed(confirmed); setSaved(true); } else setError('The earlier mailing address was saved. Your newer changes have not been saved.'); } catch (failure) { if (isSaveRetired(failure) || !operation.belongs()) return; const status = (failure as { status?: unknown } | null)?.status; if (sent && unresolvedSettingsWrite(holder) === ownHold) setError(UNKNOWN); else { setError(isSaveTimedOut(failure) ? 'The save timed out before it was sent. Try again.' : relayServerFailure(failure, 'Failed to save this setting. Please try again.')); if (status === 412 || status === 428) setNeedsRead(true); } } finally { if (operation.belongs()) { working.current = false; setSaving(false); } operation.retire(); } }; const checkCurrent = async () => { const organizationId = currentOrganization?.id; if (working.current || !organizationId || !current.current.mayManage || environment.inSandboxMode) return; const operation = scope.start(); const draftGeneration = generation.current; const beforeRead = current.current.organization; const heldAtStart = unresolvedSettingsWrite(holder); working.current = true; setChecking(true); setError(undefined); try { const read = await operation.step(() => fetchOrganizationAsCaller(organizationId, undefined, organizationId, operation.transport)); if (!readableOrganization(read, organizationId)) throw new Error('The current saved mailing address could not be read. Try the check again.'); operation.admit(); if (current.current.organization !== beforeRead) throw new Error('The practice changed during the check. Check again.'); setWpOrganization(read); setBaseline(read); setResolved(resolveCampaignMailingAddress(read)); if (generation.current === draftGeneration) seed(read); if (unresolvedSettingsWrite(holder) === heldAtStart) holdSettingsWrite(holder, undefined); setNeedsRead(false); setChecked(true); setSaved(false); } catch (failure) { if (isSaveRetired(failure) || !operation.belongs()) return; setError('The current saved mailing address could not be read. Save remains unavailable until it is checked.'); } finally { if (operation.belongs()) { working.current = false; setChecking(false); } operation.retire(); } }; return (

Marketing Email Mailing Address

The law requires a physical mailing address in every marketing email. By default we use your practice's primary address.

{(error || held) && ( {error ?? UNKNOWN} )} {saved && ( Mailing address saved. )} {resolved.source === 'none' && !useOverride && ( Your practice has no primary address on file, so marketing emails cannot be sent yet. Add one under Locations, or set a mailing address below. )} {resolved.source !== 'none' && (

{resolved.source === 'override' ? 'Marketing emails currently show: ' : 'Marketing emails currently show your primary address: '} {formatCampaignPostalAddress(resolved.address as Address)}

)}
{ generation.current += 1; setUseOverride(checked === true); setSaved(false); }} />

Only if your mail goes somewhere other than your practice address — a PO box, a billing office, or a management company.

{useOverride && (
patch('line', event.target.value)} />
patch('city', event.target.value)} />
patch('state', event.target.value)} />
patch('postalCode', event.target.value)} />
)} {(held || needsRead || !baseline?.meta?.versionId) && } {checked &&

Current saved mailing address checked. This does not confirm the earlier request.

} {!mayManage &&

{permissionsSettled ? 'Ask a practice administrator to change the marketing mailing address.' : 'Checking your permissions.'}

}
); }; export default CampaignMailingAddressSection;