refactor(forms): enable approved Form A placements by default

This commit is contained in:
DeCentN2Madness committed 2026-10-30 15:08:02 -07:00
1 parent 55adb79705
commit 95c5f229f5
41 files changed
+862 -523

No files matched your search

+1 -1
View File
@@ -14,7 +14,7 @@ interface Props {
const { lang = 'en', logicalForm = 'general' } = Astro.props;
const text = formMessages(lang);
const title = Astro.props.title ?? text.contactHeading;
const config = resolveFormMode(import.meta.env.PUBLIC_AIA_FORMS_MODE, import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED);
const config = resolveFormMode(import.meta.env.PUBLIC_AIA_FORMS_MODE);
const mode = formModeForRuntime(config);
const mockResult = resolveMockFormResult(import.meta.env.PUBLIC_AIA_FORMS_MOCK_RESULT);
---
+1 -1
View File
@@ -10,7 +10,7 @@ interface Props {
const { lang = 'en', variant = 'full', submitText } = Astro.props;
const text = formMessages(lang).formAPreview;
const config = resolveFormMode(import.meta.env.PUBLIC_AIA_FORMS_MODE, import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED);
const config = resolveFormMode(import.meta.env.PUBLIC_AIA_FORMS_MODE);
const mode = formModeForRuntime(config);
const enabled = formAPreviewEnabled(import.meta.env.PUBLIC_AIA_FORM_A_PREVIEW, import.meta.env.PUBLIC_ALLOW_INDEXING, mode);
const prefix = `form-a-${variant}`;
+1 -7
View File
@@ -8,18 +8,12 @@ const { lang = 'en', showBackground = false, route = '' } = Astro.props;
const text = formMessages(lang);
const title = Astro.props.title ?? text.heading;
const logicalForm: LogicalForm = Astro.props.logicalForm ?? (Astro.props.title === 'Physician & Insurer Referrals' ? 'referral' : 'service');
const config = resolveFormMode(import.meta.env.PUBLIC_AIA_FORMS_MODE, import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED);
const config = resolveFormMode(import.meta.env.PUBLIC_AIA_FORMS_MODE);
const mode = formModeForRuntime(config);
const mockResult = resolveMockFormResult(import.meta.env.PUBLIC_AIA_FORMS_MOCK_RESULT);
const formAIframe = logicalForm === 'service'
? resolveFormAIframe({
rawMode: import.meta.env.PUBLIC_AIA_FORMS_MODE,
liveApproved: import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED,
isolatedTestApproved: import.meta.env.PUBLIC_AIA_FORMS_ISOLATED_TEST_APPROVED,
consultationTestApproved: import.meta.env.PUBLIC_AIA_FORMS_CONSULTATION_TEST_APPROVED,
productionApproved: import.meta.env.PUBLIC_AIA_FORM_A_PRODUCTION_APPROVED,
environmentRole: import.meta.env.PUBLIC_AIA_FORMS_ENVIRONMENT,
allowIndexing: import.meta.env.PUBLIC_ALLOW_INDEXING,
route,
locale: lang,
})
-1
View File
@@ -27,7 +27,6 @@ const headingId = `${idBase}-heading`;
aria-label={hideHeading ? config.title : undefined}
data-jotform-form-a
data-form-mode={config.mode}
data-form-environment={config.environmentRole}
data-logical-form={config.context === 'consultation' ? 'service' : 'general'}
data-form-context={config.context}
data-jotform-source="approved-form-a"
+8 -14
View File
@@ -122,24 +122,18 @@ const serviceCopy = serviceInquiryCopy[service][lang];
const drawerId = `service-inquiry-${lang}-${service}`;
const titleId = `${drawerId}-title`;
const noteId = `${drawerId}-note`;
const config = resolveFormMode(import.meta.env.PUBLIC_AIA_FORMS_MODE, import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED);
const config = resolveFormMode(import.meta.env.PUBLIC_AIA_FORMS_MODE);
const mode = formModeForRuntime(config);
const mockResult = resolveMockFormResult(import.meta.env.PUBLIC_AIA_FORMS_MOCK_RESULT);
const formAIframe = resolveFormAIframe({
rawMode: import.meta.env.PUBLIC_AIA_FORMS_MODE,
liveApproved: import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED,
isolatedTestApproved: import.meta.env.PUBLIC_AIA_FORMS_ISOLATED_TEST_APPROVED,
consultationTestApproved: import.meta.env.PUBLIC_AIA_FORMS_CONSULTATION_TEST_APPROVED,
productionApproved: import.meta.env.PUBLIC_AIA_FORM_A_PRODUCTION_APPROVED,
environmentRole: import.meta.env.PUBLIC_AIA_FORMS_ENVIRONMENT,
allowIndexing: import.meta.env.PUBLIC_ALLOW_INDEXING,
route,
locale: lang,
});
const consultationTestEnabled = formAIframe.enabled && formAIframe.context === 'consultation';
const consultationEnabled = formAIframe.enabled && formAIframe.context === 'consultation';
---
{consultationTestEnabled && formAIframe.url && <>
{consultationEnabled && formAIframe.url && <>
<noscript>
<p class="service-inquiry-drawer__noscript">
{copy.noScript} <a href={formAIframe.url} target="_blank" rel="noopener noreferrer">{copy.hostedLink}</a>
@@ -151,7 +145,7 @@ const consultationTestEnabled = formAIframe.enabled && formAIframe.context === '
id={drawerId}
class="service-inquiry-drawer"
data-service-inquiry-drawer
data-consultation-test={consultationTestEnabled ? 'true' : undefined}
data-consultation-enabled={consultationEnabled ? 'true' : undefined}
data-service={service}
aria-labelledby={titleId}
>
@@ -162,12 +156,12 @@ const consultationTestEnabled = formAIframe.enabled && formAIframe.context === '
</button>
<header class="service-inquiry-drawer__intro">
<h2 id={titleId} data-service-inquiry-title tabindex="-1">{consultationTestEnabled ? serviceCopy.heading : serviceName}</h2>
<p>{consultationTestEnabled ? serviceCopy.introduction : copy.legacyStatus}</p>
{consultationTestEnabled && <p class="service-inquiry-drawer__privacy">{copy.privacy}</p>}
<h2 id={titleId} data-service-inquiry-title tabindex="-1">{consultationEnabled ? serviceCopy.heading : serviceName}</h2>
<p>{consultationEnabled ? serviceCopy.introduction : copy.legacyStatus}</p>
{consultationEnabled && <p class="service-inquiry-drawer__privacy">{copy.privacy}</p>}
</header>
{consultationTestEnabled
{consultationEnabled
? <div class="service-inquiry-drawer__iframe">
<p data-service-inquiry-completed hidden role="status" aria-live="polite" aria-atomic="true">
{lang === 'es' ? '¡Gracias! Hemos recibido su mensaje.' : 'Thank you! Your message has been received.'}
@@ -12,7 +12,7 @@ interface Props {
const { sourcePage, title, intro, buttonText } = Astro.props;
const phone = import.meta.env.PUBLIC_AIA_PHONE || '480-687-7099';
const tel = phone.replace(/[^0-9+]/g, '');
const formConfig = resolveFormMode(import.meta.env.PUBLIC_AIA_FORMS_MODE, import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED);
const formConfig = resolveFormMode(import.meta.env.PUBLIC_AIA_FORMS_MODE);
const formPreview = formAPreviewEnabled(import.meta.env.PUBLIC_AIA_FORM_A_PREVIEW, import.meta.env.PUBLIC_ALLOW_INDEXING, formModeForRuntime(formConfig));
const campaignRoute = sourcePage === 'general'
? '/lp/aba-therapy'
@@ -24,12 +24,6 @@ const campaignRoute = sourcePage === 'general'
const iframeDescription = 'Use this form to request a conversation. An AIA team member can explain the next steps for your family.';
const formAIframe = resolveFormAIframe({
rawMode: import.meta.env.PUBLIC_AIA_FORMS_MODE,
liveApproved: import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED,
isolatedTestApproved: import.meta.env.PUBLIC_AIA_FORMS_ISOLATED_TEST_APPROVED,
consultationTestApproved: import.meta.env.PUBLIC_AIA_FORMS_CONSULTATION_TEST_APPROVED,
productionApproved: import.meta.env.PUBLIC_AIA_FORM_A_PRODUCTION_APPROVED,
environmentRole: import.meta.env.PUBLIC_AIA_FORMS_ENVIRONMENT,
allowIndexing: import.meta.env.PUBLIC_ALLOW_INDEXING,
route: campaignRoute,
locale: 'en',
});
@@ -4,12 +4,6 @@ import { resolveFormAIframe } from '../../utils/form-integration';
const formAIframe = resolveFormAIframe({
rawMode: import.meta.env.PUBLIC_AIA_FORMS_MODE,
liveApproved: import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED,
isolatedTestApproved: import.meta.env.PUBLIC_AIA_FORMS_ISOLATED_TEST_APPROVED,
consultationTestApproved: import.meta.env.PUBLIC_AIA_FORMS_CONSULTATION_TEST_APPROVED,
productionApproved: import.meta.env.PUBLIC_AIA_FORM_A_PRODUCTION_APPROVED,
environmentRole: import.meta.env.PUBLIC_AIA_FORMS_ENVIRONMENT,
allowIndexing: import.meta.env.PUBLIC_ALLOW_INDEXING,
route: '/lp/early-intervention',
locale: 'en',
});
+1 -7
View File
@@ -12,17 +12,11 @@ interface Props {
const { title, lang } = Astro.props;
const text = formMessages(lang);
const formConfig = resolveFormMode(import.meta.env.PUBLIC_AIA_FORMS_MODE, import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED);
const formConfig = resolveFormMode(import.meta.env.PUBLIC_AIA_FORMS_MODE);
const formPreview = formAPreviewEnabled(import.meta.env.PUBLIC_AIA_FORM_A_PREVIEW, import.meta.env.PUBLIC_ALLOW_INDEXING, formModeForRuntime(formConfig));
const route = lang === 'es' ? '/es/contact' : '/contact';
const formAIframe = resolveFormAIframe({
rawMode: import.meta.env.PUBLIC_AIA_FORMS_MODE,
liveApproved: import.meta.env.PUBLIC_AIA_FORMS_LIVE_APPROVED,
isolatedTestApproved: import.meta.env.PUBLIC_AIA_FORMS_ISOLATED_TEST_APPROVED,
consultationTestApproved: import.meta.env.PUBLIC_AIA_FORMS_CONSULTATION_TEST_APPROVED,
productionApproved: import.meta.env.PUBLIC_AIA_FORM_A_PRODUCTION_APPROVED,
environmentRole: import.meta.env.PUBLIC_AIA_FORMS_ENVIRONMENT,
allowIndexing: import.meta.env.PUBLIC_ALLOW_INDEXING,
route,
locale: lang,
});
@@ -5,7 +5,6 @@ import Button from '../Button.astro';
const payload = readPortalInput({
approved: import.meta.env.AIA_PORTAL_LOCAL_PREVIEW_APPROVED,
indexing: import.meta.env.PUBLIC_ALLOW_INDEXING,
environment: import.meta.env.PUBLIC_AIA_FORMS_ENVIRONMENT,
file: import.meta.env.AIA_PORTAL_CIPHERTEXT_FILE,
});
const serialized = payload ? JSON.stringify(payload).replaceAll('<', '\\u003c') : 'null';
+3 -3
View File
@@ -18,7 +18,7 @@ export function initializeServiceInquiryDrawers() {
document.querySelectorAll<HTMLDialogElement>('[data-service-inquiry-drawer]').forEach((dialog) => {
const controller = new AbortController();
const { signal } = controller;
const authorized = dialog.dataset.consultationTest === 'true';
const authorized = dialog.dataset.consultationEnabled === 'true';
const closeButton = dialog.querySelector<HTMLButtonElement>('[data-service-inquiry-close]');
const triggers = [...document.querySelectorAll<HTMLAnchorElement>('[data-service-inquiry-trigger]')]
.filter((anchor) => anchor.dataset.serviceInquiryTrigger === dialog.dataset.service);
@@ -121,8 +121,8 @@ export function initializeServiceInquiryDrawers() {
observer?.observe(document.documentElement);
checkScroll();
}
} else if (!suppressed()) {
// Retain the existing inactive/mock demonstration timer outside test mode.
} else if (dialog.querySelector('[data-form-mode="mock"]') && !suppressed()) {
// Retain the existing synthetic demonstration timer only in mock mode.
timer = window.setTimeout(() => open(), 10000);
}
addEventListener('storage', (event) => { if (event.key === suppressionKey && suppressed()) stopAuto(); }, { signal });
+18 -91
View File
@@ -1,47 +1,32 @@
export type FormMode = 'off' | 'mock' | 'isolated-test' | 'live';
export type FormMode = 'live' | 'off' | 'mock';
export type LogicalForm = 'general' | 'service' | 'referral';
export type MockFormResult = 'success' | 'failed' | 'unavailable';
export interface FormModeConfig {
mode: FormMode;
reason: 'default-off' | 'mock' | 'isolated-test' | 'live-approved' | 'invalid-mode' | 'live-not-approved';
reason: 'default-live' | 'live' | 'off' | 'mock';
}
export function resolveFormMode(rawMode: string | undefined, liveApproved: string | undefined): FormModeConfig {
export function resolveFormMode(rawMode: string | undefined): FormModeConfig {
const mode = rawMode?.trim().toLowerCase();
if (!mode) return { mode: 'off', reason: 'default-off' };
if (mode === 'mock') return { mode: 'mock', reason: 'mock' };
if (mode === 'isolated-test') return { mode: 'isolated-test', reason: 'isolated-test' };
if (mode !== 'live') return { mode: 'off', reason: 'invalid-mode' };
if (liveApproved?.trim().toLowerCase() !== 'true') return { mode: 'off', reason: 'live-not-approved' };
return { mode: 'live', reason: 'live-approved' };
if (!mode) return { mode: 'live', reason: 'default-live' };
if (mode === 'live' || mode === 'off' || mode === 'mock') return { mode, reason: mode };
throw new Error('Invalid PUBLIC_AIA_FORMS_MODE. Remove the obsolete setting (including isolated-test) or choose live, off, or mock.');
}
/** Native fixtures never become live; only the bounded Form A adapter can. */
export function formModeForRuntime(config: FormModeConfig): 'off' | 'mock' {
// The isolated iframe adapter has its own fixed Form A route allowlist.
// Keep all other lead-form surfaces and every live setting fail-closed.
return config.mode === 'mock' ? 'mock' : 'off';
}
export type FormAIframeReason =
| 'enabled'
| 'mode-not-authorized'
| 'isolated-test-not-approved'
| 'consultation-test-not-approved'
| 'live-approval-not-disabled'
| 'production-approval-not-granted'
| 'invalid-environment-role'
| 'indexing-enabled'
| 'route-not-authorized'
| 'locale-not-authorized';
export type FormAIframeReason = 'enabled' | 'mode-not-authorized' | 'route-not-authorized' | 'locale-not-authorized';
export interface FormAIframeConfig {
enabled: boolean;
reason: FormAIframeReason;
route: FormAIframeRoute | string;
locale: 'en' | 'es' | string;
mode?: 'isolated-test' | 'live';
environmentRole?: 'isolated-test' | 'production';
mode?: 'live';
context?: FormAIframeContext;
url?: string;
title?: string;
@@ -51,25 +36,13 @@ export interface FormAIframeConfig {
export interface ResolveFormAIframeOptions {
rawMode: string | undefined;
liveApproved: string | undefined;
isolatedTestApproved: string | undefined;
consultationTestApproved: string | undefined;
productionApproved: string | undefined;
environmentRole: string | undefined;
allowIndexing: string | undefined;
route: string;
locale: string;
}
/**
* The owner-approved Form A identifier is deliberately only made into an
* iframe URL after every explicit isolated-test or production gate has
* passed. It is never enabled by an API URL, hostname, or publication flag.
*/
/** Fixed owner-approved identity. Never accept visitor-supplied destinations. */
export const formAIframeOrigin = 'https://form.jotform.com' as const;
export const formAIframeId = '262946078314059' as const;
const isolatedTestEnvironmentRole = 'isolated-test';
const productionEnvironmentRole = 'production';
export type FormAIframeContext = 'contact' | 'landing' | 'consultation';
export type FormAIframeRoute =
@@ -96,13 +69,7 @@ export interface FormAIframePlacement {
context: FormAIframeContext;
}
/**
* This is the complete fixed allowlist for the Form A adapter in both
* explicitly approved modes.
* Campaign placements are an owner-approved override of their earlier
* logical Form B mapping. The shared compact landing presentation uses the
* same fixed `landing` context; no route may supply its own context or URL.
*/
/** Complete owner-approved registry, independent of indexing or hostname. */
export const formAIframePlacements: ReadonlyArray<FormAIframePlacement> = [
{ route: '/contact', locale: 'en', context: 'contact' },
{ route: '/es/contact', locale: 'es', context: 'contact' },
@@ -111,15 +78,6 @@ export const formAIframePlacements: ReadonlyArray<FormAIframePlacement> = [
{ route: '/lp/aba-ahcccs', locale: 'en', context: 'landing' },
{ route: '/lp/aba-private-pay', locale: 'en', context: 'landing' },
{ route: '/lp/early-intervention', locale: 'en', context: 'landing' },
];
/**
* Consultation placements are a separate, non-production allowlist. They
* cannot be activated by the production approval gate, even when every
* production value is true. Service drawers defer assigning their iframe src
* until the authorized drawer opens.
*/
export const formAConsultationTestPlacements: ReadonlyArray<FormAIframePlacement> = [
{ route: '/client-consultation', locale: 'en', context: 'consultation' },
{ route: '/es/client-consultation', locale: 'es', context: 'consultation' },
{ route: '/schedule-consultation', locale: 'en', context: 'consultation' },
@@ -131,8 +89,6 @@ export const formAConsultationTestPlacements: ReadonlyArray<FormAIframePlacement
{ route: '/es/learner-social-club', locale: 'es', context: 'consultation' },
];
const isTrue = (value: string | undefined) => value?.trim().toLowerCase() === 'true';
function formAIframeCopy(locale: 'en' | 'es', context: FormAIframeContext) {
const consultation = context === 'consultation';
return locale === 'es'
@@ -150,17 +106,16 @@ function formAIframeCopy(locale: 'en' | 'es', context: FormAIframeContext) {
/** Builds a fixed, allowlisted URL; it accepts neither visitor data nor URL parameters. */
export function buildFormAIframeUrl(locale: 'en' | 'es', context: FormAIframeContext): string {
if (!['en', 'es'].includes(locale) || !['contact', 'landing', 'consultation'].includes(context)) {
throw new Error('Unsupported Form A locale or context');
}
const url = new URL(`/${formAIframeId}`, formAIframeOrigin);
url.searchParams.set('formContext', context);
url.searchParams.set('language', locale);
return url.toString();
}
/**
* Enables the real Form A iframe only through one of two explicit gates:
* a non-indexing isolated test, or a separately approved production build.
* No API URL, hostname, indexing setting, or form ID can activate it alone.
*/
/** Enables Form A only for a fixed route/locale pairing in live mode. */
export function resolveFormAIframe(options: ResolveFormAIframeOptions): FormAIframeConfig {
const disabled = (reason: Exclude<FormAIframeReason, 'enabled'>): FormAIframeConfig => ({
enabled: false,
@@ -168,45 +123,17 @@ export function resolveFormAIframe(options: ResolveFormAIframeOptions): FormAIfr
route: options.route,
locale: options.locale,
});
const config = resolveFormMode(options.rawMode, options.liveApproved);
const environmentRole = options.environmentRole?.trim().toLowerCase();
let mode: 'isolated-test' | 'live';
let approvedEnvironment: 'isolated-test' | 'production';
if (config.mode === 'isolated-test') {
if (options.liveApproved?.trim().toLowerCase() !== 'false') return disabled('live-approval-not-disabled');
if (!isTrue(options.isolatedTestApproved)) return disabled('isolated-test-not-approved');
if (environmentRole !== isolatedTestEnvironmentRole) return disabled('invalid-environment-role');
if (isTrue(options.allowIndexing)) return disabled('indexing-enabled');
mode = 'isolated-test';
approvedEnvironment = 'isolated-test';
} else if (config.mode === 'live') {
if (!isTrue(options.productionApproved)) return disabled('production-approval-not-granted');
if (environmentRole !== productionEnvironmentRole) return disabled('invalid-environment-role');
mode = 'live';
approvedEnvironment = 'production';
} else {
return disabled('mode-not-authorized');
}
const standardPlacement = formAIframePlacements.find((candidate) => candidate.route === options.route);
const consultationPlacement = formAConsultationTestPlacements.find((candidate) => candidate.route === options.route);
const placement = mode === 'isolated-test'
? standardPlacement ?? consultationPlacement
: standardPlacement;
if (resolveFormMode(options.rawMode).mode !== 'live') return disabled('mode-not-authorized');
const placement = formAIframePlacements.find((candidate) => candidate.route === options.route);
if (!placement) return disabled('route-not-authorized');
if (options.locale !== placement.locale) return disabled('locale-not-authorized');
if (placement.context === 'consultation' && (options.allowIndexing !== 'false' || !isTrue(options.consultationTestApproved))) {
return disabled('consultation-test-not-approved');
}
return {
enabled: true,
reason: 'enabled',
route: options.route,
locale: placement.locale,
mode,
environmentRole: approvedEnvironment,
mode: 'live',
context: placement.context,
url: buildFormAIframeUrl(placement.locale, placement.context),
...formAIframeCopy(placement.locale, placement.context),
+2 -2
View File
@@ -1,8 +1,8 @@
// Build-only reader; never imported by a browser script.
import fs from 'node:fs';
import { validatePortalPayload } from './portal-crypto.mjs';
export function readPortalInput({ approved, indexing, environment, file }) {
if (approved !== 'true' || indexing !== 'false' || environment !== 'isolated-test' || !file) return undefined;
export function readPortalInput({ approved, indexing, file }) {
if (approved !== 'true' || indexing !== 'false' || !file) return undefined;
try {
if (fs.statSync(file).size > 12_000_000) return undefined;
return validatePortalPayload(JSON.parse(fs.readFileSync(file, 'utf8')));