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.
137 lines
6.0 KiB
Markdown
137 lines
6.0 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 `render.yaml` for [Render](https://render.com) and a
|
||
detailed `DEPLOYMENT.md` covering Render specifically. The app is a
|
||
standard Next.js 16 App Router project and runs anywhere Node 20+ can
|
||
run `next start` — Vercel, AWS Amplify Hosting, 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.
|