Commit Graph
87 Commits
Author SHA1 Message Date
Joel Brock 8caa851bcc Comment out three Stage 0 fields (Peer Group, Internal Startup Assessment x2) 2026-05-20 16:04:21 -07:00
Joel Brock fbf668800b Amplify: write env vars + secrets to .env.production at build time
Amplify Gen 2 exposes Environment Variables and Secrets in the build shell
but does not inject them into the SSR Lambda runtime. Writing them to
.env.production during preBuild lets Next.js bundle them into the server
output so process.env reads work at request time.
2026-05-20 16:03:33 -07:00
Joel Brock 97222afe58 AMPLIFY_DEPLOY: document Gen 2 Environment variables vs Secrets split
Amplify Gen 2's console has two separate pages for runtime config:
Environment variables (plaintext) and Secrets (SSM Parameter Store
SecureString). The earlier 'mark as Secret with eye icon' wording was
Gen 1; in Gen 2 you choose by which page you add the value on.

Step 2 rewritten:
- Brief explanation of both pages and how they're injected (both end
  up as plain env vars in the app, same name).
- Combined variable table with a Page column showing where each value
  lives.
- Rule-of-thumb: anything that would let someone impersonate the app
  to CiviCRM or bypass a gate is a Secret; hostnames/usernames are
  fine in Environment variables.
- Callout reminding not to duplicate names across both pages
  (precedence undefined in Gen 2).
2026-05-19 17:28:24 -07:00
Joel Brock e90d007513 Amplify: install devDependencies during build (Tailwind/PostCSS need them)
@tailwindcss/postcss lives in devDependencies (along with the rest of
the PostCSS toolchain). When NODE_ENV=production is set in the Amplify
build environment, npm ci skips devDependencies — and next build then
fails resolving @tailwindcss/postcss while compiling globals.css.

amplify.yml now passes --include=dev to npm ci so the build always
installs everything regardless of NODE_ENV. AMPLIFY_DEPLOY.md updated
to warn against setting NODE_ENV=production in the Amplify env vars
panel — it's redundant (Next sets it correctly) and an easy footgun.
2026-05-19 17:13:13 -07:00
Joel Brock 6e7df382c9 AWS Amplify Hosting deploy prep
- amplify.yml: build spec (preBuild npm ci with offline cache, build
  next build, artifacts at .next/**, cache node_modules/.next-cache/.npm).
- .nvmrc: pin Node 20 so Amplify uses the same runtime as local.
- AMPLIFY_DEPLOY.md: first-time walkthrough covering AWS-side setup
  (create app, connect GitHub via OAuth/App, branch/auto-detect),
  environment variable table with secret-flag guidance, smoke-test via
  /healthz and /api/health, optional custom domain + per-PR previews,
  cost estimate, and operational notes (cold starts, no static
  egress IPs, CloudWatch logs, secret rotation).
- README deploy section: now points at both AMPLIFY_DEPLOY.md and the
  existing DEPLOYMENT.md (Render).
2026-05-19 16:48:38 -07:00
Joel Brock d88f1229ae Report: stair-step actual line, drop sparklines, move chart into Stage 0
- Actual member line now uses the same carry-forward step pattern as
  the goal line — between measurements the chart holds the prior value
  instead of interpolating diagonally, and the final value extends flat
  to the right edge. Eliminates the apparent dips that arose when
  diagonal interpolation crossed missing periods or low intermediate
  values.
- Per-field Sparkline (and its isNumericField / formatScalarText
  helpers) removed entirely. Expanding 'earlier entries' now just shows
  the chronological list. Curated multi-metric charts (like the
  Membership chart) are the path forward for trend visualization.
- Membership chart relocated from the top-of-report band into the
  Stage 0 ('Check-in (organizing)') section, rendered inline after
  whichever of Members__current_ / Member_Goal_for_current_Stage
  appears last in the section's fields-with-history list. Naturally
  scopes the chart to wherever those questions live (no double-render
  if config later moves them). Chart props refactored to take field
  + history pairs directly instead of walking the full sections array.
2026-05-19 16:45:53 -07:00
Joel Brock ae4e47025c Report: dedicated Membership chart (goal vs actual over time)
New MembershipChart card sits between the DateTimeline and the section
accordions, rendering whenever Members__current_ or
Member_Goal_for_current_Stage has any historical data.

- Actual member count: smooth leaf-700 polyline with a faint leaf-500
  area fill underneath, dots at every measurement, an emphasized dot
  on the most recent point with the value labeled inline.
- Goal: dashed clay-600 step line — each goal value is treated as a
  target that holds until the next update, then extends flat to the
  right edge of the chart. Dots at each update; the most-recent goal
  value labeled at the right.
- Y-axis: niceYTicks picks 3–5 round-number ticks (snapped to
  1/2/2.5/5/10 × 10^N) spanning [min(0, dataMin), dataMax]; faint
  gridlines + tabular-num labels on the left. Anchoring at 0 keeps
  growth-from-small-base readable.
- X-axis: reuses generateAxisTicks for adaptive month/year stepping,
  matching the timeline above. Today gets a dashed clay vertical
  guide when in range.
- Header: title + subtitle + an inline 'NNN of MMM target · X to go'
  callout in tabular-nums, color-coded (clay-700 if behind goal,
  leaf-700 if above).
- Legend at the bottom with line+dot chips for both series.
2026-05-19 16:37:01 -07:00
Joel Brock 00e10a992f Timeline: hover/focus tooltips, stage 0 lane, adaptive date axis
Three improvements to the report's DateTimeline:

1. Tooltips on every dot. Each dot is now a focusable span (tabIndex,
   role=img, full aria-label). A small ink-tinted card appears above
   the dot on mouse hover or keyboard focus, showing field label,
   formatted date, stage rank, and an 'Opened' marker for Date_Opened.
   Anchor flips to left/center/right based on the dot's position so
   tooltips don't overflow the row at the edges.

2. Plot every date field, including stage 0. The previous version
   skipped Stage 0 dates (Internal_Startup_Assessment_Date,
   Date_Closed_Folded). Now there are six swim lanes (0-5) instead
   of five. Stage 0 gets bg-leaf-200 so the gradient extends one
   step lighter.

3. Adaptive month/year x-axis under the lanes. generateAxisTicks
   picks a 'nice' interval based on the visible span: 1mo / 2mo /
   3mo / 6mo / 1yr / 2yr. January-bordered ticks include the year
   so the reader has anchors. Today gets its own labeled clay tick
   when it falls in range.
2026-05-19 16:32:50 -07:00
Joel Brock 814b560363 Report: sparkline charts on numeric history + stage-grouped date timeline
Two visual additions to the read-only activity report.

Sparkline: when the user expands earlier-entries on a numeric field
(number/currency/percent) with two or more numeric points, the
expansion now leads with a 240x56 inline SVG trend chart — chronological
polyline, faint area fill, small dots at every measurement, a slightly
larger emphasized dot on the most recent point. Min and max captions
sit beneath in tabular-nums, formatted in the field's native style
(currency uses Intl, percent appends %, etc.). Non-numeric fields are
unchanged.

DateTimeline: a new card between the context header and the section
accordions. Walks every date-type field in stage sections 1-5 (Stage 0
omitted as it isn't a stage in the journey sense), pulls each field's
most-recent entered date, and lays the events out in five horizontal
swim lanes — one per stage rank, labeled at the left. Time axis
spans from the earliest event to max(latest event, Date_Opened).
Stage 5's Date_Opened is rendered as a larger clay-700 dot with a
heavier ring so it reads as the journey's anchor at the right end.
A faint clay-300 dashed vertical line marks 'today' if it falls
within the range. Color scale across stages is leaf-300 / leaf-500
/ leaf-600 / leaf-700 / clay-700 — a sprout-to-fruit gradient that
matches the existing palette. Empty stage rows still draw their lane
line at half opacity so the structure stays readable. SR-only event
list provides screen-reader access to all plotted dates with their
labels.

Stub payload enriched with four cross-stage date entries so the
timeline has content in dev preview.
2026-05-19 16:16:57 -07:00
Joel Brock 9209d6dc02 Prefill file fields with their file_name joined from CiviCRM
CiviCRM APIv4 file custom fields return a bare file id by default; an
extra '.file_name' join is required to get the human-readable filename.
Both the form prefill walk (lib/prefill.ts) and the report walk
(app/api/report/route.ts) now request '<civiField>.file_name' for every
file-type field alongside the primary value, and wrap the prefill into
a { id, file_name } object so downstream UI has both. Falls back to
file_name undefined when the join returns null (eg orphaned id).

The form's FilePriorIndicator already accepts the object shape, so it
now shows the filename inline. The report's FormattedValue gets a
matching case: renders the file_name string if present, falls back to
'Attachment #<id>' when only the id came through.
2026-05-19 15:51:35 -07:00
Joel Brock a8ed6073a9 File fields: surface a prior-attachment indicator from RHF prefill
The earlier 'Currently on file' indicator hung off readonlyValue, which
is sourced from evalState (only carries current_stage). For file fields
the prefill lives in RHF state, not in evalState, so the indicator
never fired.

Replaced with a FilePriorIndicator subcomponent that subscribes to the
field's RHF value via useWatch and renders a small leaf-50 banner with
a paperclip glyph when a previous attachment is present. Falls silent
the moment the user picks a new file (RHF value becomes a FileList).
Filename is derived from whatever shape Civi returned — bare string,
object with file_name/name/label/filename, or numeric file id (generic
message in that case).
2026-05-19 15:48:10 -07:00
Joel Brock dbd607b829 Prune scaffold cruft: unused Next.js template SVGs and axios dep
Removed five default Next.js scaffold SVGs from public/ that were
created by create-next-app and never referenced (file.svg, globe.svg,
next.svg, vercel.svg, window.svg). The actual brand mark
public/fci-logo.png is the only image the app uses.

Removed axios from dependencies — the app uses native fetch
everywhere, and axios hasn't been imported since the initial
scaffolding pass. Lockfile regenerated; build verified clean.
2026-05-13 13:36:21 -07:00
Joel Brock 899dad2323 README: refresh for current architecture, drop stale Framework Stage / mock-field notes
Trimmed the in-the-weeds tech tour (file tree, lib names, custom-field
inventory tutorial) and replaced with a brief functional overview, the
visual/UX highlights that survived the polish passes, the accessibility
features, an env-var table, and the minimum to run/deploy. Outdated
material removed: org-side Framework Stage as authority, 'Org Engagement
Submission' activity type, stage_at_submission write, placeholder
custom_<name> civiField refs, Inter+SourceSerif typography, leaf+stone
palette. Documents stage-from-activity, /report, locked future stages,
draft auto-save, success destination, HTTP-Basic-Auth proxy env vars,
HEALTH_TOKEN, and PREVIEW_ADMIN_TOKEN.
2026-05-13 13:19:11 -07:00
Joel Brock ba88eb0165 Add /report — read-only activity history view
Mirrors the form's IA and Field Almanac aesthetic; same auth (cid/cs
checksum) so the org owner who can fill the form can also view its
history.

New routes:
- GET /api/report — verifies checksum, resolves the org via the
  Primary Contact relationship, fires Contact.get + Activity.get +
  OptionValue.get in parallel. For every form field with a civiField,
  walks all the org's Check-in (organizing) activities and collects
  every non-empty value into a sorted-DESC history list. Returns
  ReportPayload (orgName, currentStage, activities, fieldHistory,
  options). Has stub-mode payload for env-less local dev.
- /report — page entry; same layout shell (SiteHeader + SiteFooter,
  3xl page width). Eyebrow "Activity report - Co-op organizing".

ReportView component:
- ReportContextHeader: large org name, progress dots + uppercase
  "Current stage" eyebrow + the Civi option *label* on its own line
  at display-font xl/2xl leaf-800 (matches the form's header). Below
  it a 3-up stat band: total check-ins, fields tracked, date span.
- One accordion card per stage section, in stage-rank order. Only
  sections that have at least one field-with-entries render — past,
  current, or "future-with-data" all welcome; truly empty stages stay
  hidden so the page is calm.
- Same journey rail (md+) and mobile stem (md-) with past =
  check-filled-leaf, current = filled-leaf-with-ring, future = dashed
  hollow ring; solid leaf line vs dashed muted between markers.
- Within each card: divide-y rows. Field label and help on the left,
  most-recent value on the right in display-font lg leaf-800, dated
  beneath with an "{N} earlier entries" disclosure that expands a
  small vertical timeline (date on left, value on right).
- FormattedValue handles currency (Intl), percent, number (tabular
  nums), date (long, timezone-safe for YYYY-MM-DD), boolean (Yes/No),
  select/readonly (resolved via option group), multiselect (handles
  array or delimited string), file (filename), text-like (as-is).
- Loading / empty / error states match the form's treatments.

types/form.ts: new FieldHistoryEntry, ActivitySummary, ReportPayload.
The fieldHistory map keys by FieldConfig.name and only includes fields
that have at least one non-empty entry.
2026-05-13 12:33:26 -07:00
Joel Brock 04e69ca04c Header: show Civi stage label prominently; restore current_stage readonly in Stage 0
- New STAGE_OPTION_GROUP_ID constant (=75) added to config and consumed
  by both the in-card readonly and the header. Because the field carries
  optionGroupId, /api/data auto-includes the Stage option group in its
  parallel fetch (via the existing optionGroupIds derivation).
- SubmissionContextHeader: dropped the small inline 'Stage Organizing'
  line. Replaced with an uppercase 'Current stage' eyebrow next to the
  progress dots, followed by a display-font 20/24px leaf-toned label
  underneath. Resolves the stored option value ('Organizing') to its
  Civi option label ('Stage 1 — Convene & Prepare') via the new
  resolveStageLabel helper; falls back to the raw value if the option
  group hasn't loaded.
- Stage 0 'Check-in (organizing)' section: re-added a current_stage
  readonly field at the top. With optionGroupId set, FieldRenderer's
  readonly branch resolves the value to the Civi label for display.
2026-05-13 12:09:24 -07:00
Joel Brock 74a85d38fe Drop in-form Framework Stage field; align section labels with Civi
The current_stage readonly field is removed from Stage 0 — the activity-
derived stage value already displays in the SubmissionContextHeader, so
showing it again in-card was redundant. current_stage remains in form
state (RHF defaults from /api/data) because every Stage 1-5 visibleWhen
rule and the journey rail still key off it; it just isn't rendered.

Section labels for Stages 1-5 updated to match the CiviCRM Stage option
group labels exactly (ampersand instead of 'and'; Stage 2 corrected from
'Feasibility' to 'Grow & Plan'). Stage 0 keeps its 'Check-in (organizing)'
label since it represents the always-visible core fields, not the Inquiry
stage rank.

Also folded in: in-progress visibleWhen additions on several Stage 0
fields and commented-out field stubs from your working tree.
2026-05-13 12:04:22 -07:00
Joel Brock b4e80517a7 Derive current stage from most-recent stage-bearing activity
Stage authority moves from Organization.Food_Co_op_Organizing.Stage to the
most recent Check-in (organizing) activity whose Stage custom field is set.
Staff create these activities manually to mark transitions; org-owner form
submissions no longer write the Stage field at all, so they cannot override
a staff-set transition.

- /api/data: removed the Contact.get for org-side Stage; added an
  Activity.get filtered to ACTIVITY_TYPE_NAME + ACTIVITY_STAGE_FIELD IS
  NOT EMPTY, ordered by activity_date_time DESC, id DESC, limit 1.
  Fallback when no such activity exists: Inquiry (rank 0). Org-name
  lookup, stage activity, prefill, and option-group fetch all run in
  parallel via Promise.all.
- /api/submit: removed the stageAtSubmission read + the
  [ACTIVITY_STAGE_FIELD] write on the activity record. The form's
  activities are stage-null by design now.
- config/form.ts: dropped the stage_at_submission readonly field (no
  longer being set or displayed). Kept ACTIVITY_STAGE_FIELD export — it's
  now used by /api/data to find stage-bearing activities. Updated the
  current_stage field comment to reflect the new source.
- components/EngagementForm.tsx: dropped stage_at_submission from
  evalState (no longer referenced by any visibility rule or readonly
  display).

Org.Food_Co_op_Organizing.Stage remains in CiviCRM for staff list views;
the middleware no longer reads or writes it. No backfill required —
orgs without a stage-bearing activity simply read as Inquiry.
2026-05-13 11:41:25 -07:00
Joel Brock 18b7a67fa1 Swap inline 'Sown' SVG for the real FCI brand logo
Drops the stylized SVG mark in favor of public/fci-logo.png (the official
Food Co-op Initiative logo, including its wordmark). Because the logo now
contains the 'Food Co-op Initiative' text, the duplicate display-font
wordmark is removed; the 'Co-op Check-in' eyebrow stays as a quiet hairline
divider to the right (sm+ only, hidden on mobile to keep the header tight).
2026-05-11 13:42:06 -07:00
Joel Brock 762605f04b Compact the journey rail to reclaim form width
Gutter shrinks from md:pl-20 (80px) to md:pl-12 (48px) — a 32px (40%) gain
back to the form column. Markers, pulse halo, and 'Now' pill scale down to
match so the rail still reads at a glance:

- Marker container: -left-14 w-12 -> -left-9 w-7
- Past/future markers: h-6 -> h-5
- Current marker: h-7 ring-4 -> h-6 ring-2; rank label 11px -> 10px
- 'Now' pill: 9px -> 8px; tracking and offset re-balanced for the shorter
  drop from the smaller marker bottom
- Rail-pulse keyframe shadow radius: 8px -> 6px to suit the smaller disc
2026-05-11 13:37:28 -07:00
Joel Brock 9103ccaf9d Locked future-stage cards + journey rail
Future stages now render as preview-only "look ahead" cards instead of being
hidden. A user at stage 2 can see headers and contents for stages 3, 4, 5,
but those sections are visibly locked and uneditable.

Locked-card treatment:
- Dashed-rule border, paper-2 fill, no shadow — visually quieter than
  active cards
- "Upcoming" pill in the header with a small lock glyph
- Muted stage rank mark (dashed badge, low-opacity icon)
- Panel content wrapped in fieldset[disabled] so every form control inside
  is natively non-interactive, with an opacity tweak for affordance
- "A look ahead" banner explaining that fields will become editable when
  the co-op reaches this stage

Section visibleWhen is still consulted on submit, so locked-stage values
never get written back to CiviCRM even if data is prefilled.

Journey rail:
- Vertical rail (md+) in a new left gutter; each card carries an aligned
  marker. Past stages = filled leaf circle with check; current = filled
  leaf disc with rank number, leaf-100 halo ring, and a subtle rail-pulse
  box-shadow animation (motion-safe). A "Now" pill sits beneath the
  current marker. Future stages = dashed hollow ring with lock glyph.
- Connector segments between markers are solid leaf when the next stage
  is past-or-current, dashed muted when future — so the transition from
  "traveled" to "ahead" reads at the right place in the journey.
- Mobile fallback: a small vertical stem in the gap between adjacent
  cards, styled the same way (solid vs dashed) so the progression cue
  still reads on narrow viewports.
2026-05-11 13:05:34 -07:00
Joel Brock a804650f65 Audit short-term: currency preview, date bounds, success destination, safe-area
- H3: Live currency preview below currency inputs shows the value formatted
  with thousands separators (en-US, USD) using Intl.NumberFormat. Skipped
  inside matrix cells to keep the Y1 monthly table compact.
- M1: Date inputs now apply min/max bounds. Default window is 1900-01-01 to
  2100-12-31; per-field override via FieldConfig.min/max as ISO strings.
- H6: On successful submit, replace the form with a SuccessDestination card
  (large checkmark, org name, "Submit another" + "safe to close" affordance).
  Prevents accidental duplicate submits from back-button / autofill replay.
- M6: Sticky submit bar respects iOS safe-area-inset-bottom.

FieldRenderer now takes a control prop so the currency preview can subscribe
to its single field via useWatch without re-rendering the whole form.
2026-05-11 09:48:03 -07:00
Joel Brock 0d84b9654b Polish pass: removed lastTouched dead code, selective useWatch perf, token sweep, required-field messages, scroll-to-first-error, file prefill display, matrix sticky shadow 2026-05-09 22:49:25 -07:00
Joel Brock e452fbb15f Email delivery: /api/preview-link admin endpoint + EMAIL_DELIVERY.md guide 2026-05-09 22:31:51 -07:00
Joel Brock 442740939e Allow CSP unsafe-eval in dev only (Next.js HMR); production stays strict 2026-05-09 21:51:28 -07:00
Joel Brock 656bf7fd0a Visual identity: Field Almanac — Fraunces+DM Sans, OKLCH cream/leaf/clay palette, paper texture, hand-drawn stage icons, draft auto-save, stage progress dots 2026-05-09 21:48:45 -07:00
Joel Brock dcdf315244 Production hardening: CSP, rate limit, env validation, health gating, Render blueprint, DEPLOYMENT.md 2026-05-09 21:43:43 -07:00
Joel Brock da3e48a874 Add matrix-group display for monthly/quarterly time-series fields in Stage 5 2026-05-09 21:29:21 -07:00
Joel Brock 082238d884 Fix Activity.create payload: pass values as object not array 2026-05-09 21:15:56 -07:00
Joel Brock 1655c485d9 Fix APIv4 relationship_type_id join: use dot syntax not colon 2026-05-09 21:10:56 -07:00
Joel Brock da830af533 Health probe: dump all relationships for a given contact, flag direction issues 2026-05-09 21:08:30 -07:00
Joel Brock 2e9d3855ba Use existing 'Primary Contact' relationship type via FORM_CONTACT_RELATIONSHIP constant 2026-05-09 20:57:29 -07:00
Joel Brock e132789a3e Sharper health probes: resolve stage option group via CustomField metadata; list near-match relationship types 2026-05-09 20:52:06 -07:00
Joel Brock 82d7849a30 Support webserver-level HTTP Basic Auth in front of CiviCRM 2026-05-09 20:49:37 -07:00
Joel Brock 234bec5766 Add /api/health diagnostic route; remove stale .js route duplicates 2026-05-09 20:45:14 -07:00
Joel Brock c58a49be6d Wire real CiviCRM DEV custom fields and dynamic option fetching 2026-05-09 20:42:35 -07:00
Joel Brock 54555c74d2 Build standalone CiviCRM check-in middleware 2026-05-09 20:08:15 -07:00
google-labs-jules[bot]andjoelbrock 0899e6ae9a Implement CiviCRM middleware form with stage-based visibility
- Initialize Next.js project with Tailwind CSS
- Create CiviCRM APIv4 integration layer
- Implement stage-based form visibility (stages 0-5)
- Add field mapping configuration for CRM-to-form linking
- Create API routes for data retrieval and submission
- Record form submissions as CiviCRM activities
- Support dynamic contactId and orgId via URL parameters
- Ensure robust form state management with react-hook-form

Co-authored-by: joelbrock <52835+joelbrock@users.noreply.github.com>
2026-05-09 08:12:39 +00:00