- 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).
137 lines
6.1 KiB
Markdown
137 lines
6.1 KiB
Markdown
# Co-op Check-in
|
||
|
||
A tokenized, mobile-friendly web form that lets external co-op contacts
|
||
update their organization's tracking data on CiviCRM, plus a read-only
|
||
activity report showing every value the co-op has shared over time. Built
|
||
to wrap CiviCRM's existing custom-field schema without modifying it.
|
||
|
||
## How it works
|
||
|
||
Each co-op has a designated **Primary Contact** (the individual) linked
|
||
to the **Organization** record in CiviCRM. Staff send that contact a
|
||
personalized link generated against the contact's CiviCRM checksum:
|
||
|
||
- `https://check-in.fci.coop/?cid=<contactId>&cs=<checksum>` — the form
|
||
- `https://check-in.fci.coop/report?cid=<contactId>&cs=<checksum>` — the report
|
||
|
||
When the link is opened, the app verifies the checksum against CiviCRM,
|
||
resolves the organization through the Primary Contact relationship, and
|
||
loads the right view.
|
||
|
||
**Form.** Sections are organized by the co-op development stages
|
||
(Inquiry → Convene & Prepare → Grow & Plan → Connect & Gather → Excite &
|
||
Build → Fulfill & Stabilize). The org's current stage controls which
|
||
sections are editable; past and current stages are open for editing,
|
||
future stages render as previews with their fields locked so the co-op
|
||
can see the framework ahead. Fields prefill with each value's most
|
||
recent non-empty entry from past check-ins. On submit, a new
|
||
"Check-in (organizing)" activity is created; nothing is overwritten.
|
||
|
||
**Stage authority.** The current stage is derived from the most recent
|
||
"Check-in (organizing)" activity whose Stage field is set. Staff own
|
||
stage transitions by manually setting Stage on a check-in activity they
|
||
create; the form itself never writes Stage, so org self-submissions
|
||
can't override a staff transition. Orgs with no stage-bearing activity
|
||
default to "Inquiry."
|
||
|
||
**Report.** A read-only view of every field that has ever held a value,
|
||
grouped by stage. Each row shows the most recent value prominently; an
|
||
"N earlier entries" disclosure expands a chronological list of prior
|
||
values with their dates. Empty stages and untouched fields are hidden so
|
||
the page stays calm.
|
||
|
||
## Visual & UX details
|
||
|
||
- **Field Almanac** aesthetic: warm cream paper, deep botanical green
|
||
ink, a sparing terracotta accent. Fraunces (display) and DM Sans
|
||
(body) at variable weights. Subtle paper-grain texture via layered
|
||
CSS gradients.
|
||
- **Journey rail** runs down the left side on desktop, with a marker
|
||
per stage and a "Now" pill at the current stage. Past stages are
|
||
filled with a checkmark, future stages are dashed and locked. A small
|
||
inter-card stem stands in for the rail on mobile.
|
||
- **Draft auto-save** to `localStorage` (30-day TTL) so partial answers
|
||
survive page refreshes; a "Draft restored" banner appears on return.
|
||
- **Successful submit** lands on a destination screen instead of a
|
||
reloaded form, preventing accidental duplicate submits from
|
||
back-button or autofill replay.
|
||
- **Currency live preview**, date min/max bounds (1900–2100), and
|
||
thousands-separator formatting on numeric fields.
|
||
- **Hand-drawn stage icons** for each of the six stages; FCI brand logo
|
||
in the header.
|
||
|
||
## Accessibility
|
||
|
||
- Real `<label htmlFor>` on every control; help text and inline errors
|
||
linked via `aria-describedby`.
|
||
- Required fields use `aria-required`, a visible `*` with
|
||
`aria-label="required"`, and typed error messages.
|
||
- Failed-validation submit auto-expands the section containing the
|
||
first error, scrolls it into view, and focuses the field.
|
||
- Accordion sections follow the disclosure pattern: `<button>` headers
|
||
with `aria-expanded` + `aria-controls`, `role="region"` panels.
|
||
- All animations honour `prefers-reduced-motion`.
|
||
- Skip-to-content link present for keyboard users.
|
||
- iOS safe-area inset respected on the sticky submit bar.
|
||
|
||
## Environment variables
|
||
|
||
The app runs in **stub mode** when any of the CiviCRM variables below
|
||
are missing — the UI is fully exercisable against synthesized data with
|
||
no live CRM calls. Production startup will refuse to boot in stub mode.
|
||
|
||
| Variable | Required? | Purpose |
|
||
|---|---|---|
|
||
| `CIVI_BASE_URL` | yes | CiviCRM root, e.g. `https://crm.fci.coop` |
|
||
| `CIVI_API_KEY` | yes | API key of a Civi user with permission to read contacts/activities and create activities |
|
||
| `CIVI_SITE_KEY` | yes | The `site_key` from `civicrm.settings.php` |
|
||
| `CIVI_HTTP_AUTH_USER` | optional | Username, if CiviCRM sits behind HTTP Basic Auth at the webserver layer |
|
||
| `CIVI_HTTP_AUTH_PASS` | optional | Password for the above |
|
||
| `HEALTH_TOKEN` | optional | If set, `/api/health` requires `?token=…` in production |
|
||
| `PREVIEW_ADMIN_TOKEN` | optional | If set, `/api/preview-link` requires a matching `Authorization: Bearer …` header (admin convenience endpoint for minting form links) |
|
||
| `NODE_ENV` | — | Set to `production` for the production build |
|
||
|
||
Copy `.env.example` to `.env.local` for local development.
|
||
|
||
## Run locally
|
||
|
||
```
|
||
npm install
|
||
npm run dev
|
||
```
|
||
|
||
Open `http://localhost:3000/?cid=1&cs=anything` — any non-empty `cs`
|
||
works while in stub mode. The report is at `/report?cid=1&cs=anything`.
|
||
|
||
## Deploy
|
||
|
||
The repo ships with `amplify.yml` for [AWS Amplify Hosting](https://aws.amazon.com/amplify/)
|
||
(see `AMPLIFY_DEPLOY.md` for a first-time walkthrough) and `render.yaml`
|
||
for [Render](https://render.com) (see `DEPLOYMENT.md`). The app is a
|
||
standard Next.js 16 App Router project and runs anywhere Node 20+ can
|
||
run `next start` — Vercel, App Runner, Fly, self-hosted, etc.
|
||
|
||
Build + start:
|
||
|
||
```
|
||
npm run build
|
||
npm start
|
||
```
|
||
|
||
Set all three required CiviCRM variables in the host's secret store;
|
||
the app auto-leaves stub mode the moment they're all present.
|
||
|
||
A `/healthz` route returns a lightweight 200 for platform health checks;
|
||
`/api/health` returns a richer diagnostic payload (gated by
|
||
`HEALTH_TOKEN` in production).
|
||
|
||
## Editing the form
|
||
|
||
The form definition lives in `config/form.ts` — one TypeScript file
|
||
declaring sections, fields, types, visibility rules, and matrix groups
|
||
(used for compact M1–M12 / Q1–Q4 layouts in Stage 5). Each field
|
||
references its CiviCRM custom field via APIv4's `<group_name>.<field_name>`
|
||
syntax. Edit the file, restart `npm run dev`, you're done.
|
||
|
||
A browser-based form builder is out of scope for this version.
|