FCI theming refresh, contact identity fields, Amplify secrets fix

- Remap globals.css tokens to FCI brand palette (Eggplant #801d7f,
  Spring Pea #96bc33, Seed Grant #679038, Squash #c9ad2d, FCI gray
  #4b5657). Existing leaf-* / clay-* class names preserved.
- Switch body font to Open Sans (FCI's free fallback for Museo Sans).
  Headings keep Fraunces.
- Add contact identity (first name, last name, email) as readonly
  fields at the top of Stage 0. /api/data fetches via APIv4
  Contact.get with email_primary.email join; values flow through
  FormDataPayload.contact and into the form's evalState so the
  readonly renderer displays them. Draft restore re-applies them so
  a stale local draft can't override.
- amplify.yml: fetch Amplify Secrets from SSM Parameter Store when
  they don't arrive as build-shell env vars (the common failure mode
  behind "Refusing to run in production without CIVI_*"). Adds a
  length-only diagnostic echo and a hard-fail guard so a missing
  required var stops the build with a clear message instead of
  bundling empty strings and crashing the SSR Lambda at runtime.
This commit is contained in:
Joel Brock
2026-05-20 16:46:44 -07:00
parent 8caa851bcc
commit f0530ed337
7 changed files with 209 additions and 81 deletions
+30 -3
View File
@@ -39,6 +39,11 @@ const DEFAULT_STAGE = "Inquiry";
const STUB_PAYLOAD: FormDataPayload = {
orgName: "Sample Co-op (stub)",
currentStage: "Organizing",
contact: {
firstName: "Jordan",
lastName: "Sample",
email: "jordan.sample@example.coop",
},
prefill: {
Peer_Group_Participation: "Yes",
Members__current_: 87,
@@ -158,13 +163,25 @@ export async function GET(req: NextRequest) {
}
const orgId = orgs[0].contact_id_b;
// Fire org-name lookup, stage-bearing-activity lookup, prefill walk, and
// option-group fetch in parallel — they're independent.
const [orgRes, stageActivityRes, { values: prefill }, options] = await Promise.all([
// Fire org-name lookup, contact-identity lookup, stage-bearing-activity
// lookup, prefill walk, and option-group fetch in parallel — they're
// independent.
const [orgRes, contactRes, stageActivityRes, { values: prefill }, options] = await Promise.all([
civi<{ id: number; display_name: string }>("Contact", "get", {
select: ["id", "display_name"],
where: [["id", "=", orgId]],
}),
// Identifying details for the form-filler. APIv4 lets us chain through
// the primary-email join in the same call.
civi<{
id: number;
first_name: string | null;
last_name: string | null;
"email_primary.email": string | null;
}>("Contact", "get", {
select: ["id", "first_name", "last_name", "email_primary.email"],
where: [["id", "=", Number(cid)]],
}),
// Most recent Check-in (organizing) activity whose Stage custom field
// is set. Staff own this field; the form never writes it. Tiebreaker on
// equal activity_date_time is id DESC.
@@ -190,9 +207,19 @@ export async function GET(req: NextRequest) {
const stageRaw = stageActivityRes.values?.[0]?.[ACTIVITY_STAGE_FIELD];
const currentStage = typeof stageRaw === "string" && stageRaw ? stageRaw : DEFAULT_STAGE;
const contactRow = contactRes.values?.[0];
const contact = contactRow
? {
firstName: contactRow.first_name ?? "",
lastName: contactRow.last_name ?? "",
email: contactRow["email_primary.email"] ?? "",
}
: undefined;
const payload: FormDataPayload = {
orgName: org.display_name,
currentStage,
contact,
prefill,
options,
};