Rebrand: Co-op Check-in -> Co-op Survey in user-facing copy
Updates the tool product name across the app UI (header, page title, section labels, form buttons, success/error states, report stat labels), README, deployment docs, and the CiviCRM email template guidance. Custom domain references move from check-in.fci.coop to survey.fci.coop (DNS update still required). The underlying CiviCRM "Check-in (organizing)" activity type, custom group machine names (Check_in_data__organizing_), health-check ids, and the internal org_engagement_check_in form id are unchanged -- those are CiviCRM contract surfaces, not product copy.
This commit is contained in:
+1
-1
@@ -98,7 +98,7 @@ useful to confirm env vars are wired correctly.
|
||||
|
||||
## Step 4 — Custom domain (optional)
|
||||
|
||||
Amplify can attach a custom domain (e.g. `check-in.fci.coop`) with
|
||||
Amplify can attach a custom domain (e.g. `survey.fci.coop`) with
|
||||
auto-issued TLS in a few minutes:
|
||||
|
||||
1. In the app, **Hosting** → **Custom domains** → **Add domain**.
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ Verify all of the above against your live CiviCRM by hitting `/api/health` (in d
|
||||
| `CIVI_HTTP_AUTH_USER` | only if Civi sits behind webserver-level Basic Auth | HTTP Basic Auth username. |
|
||||
| `CIVI_HTTP_AUTH_PASS` | only if CIVI_HTTP_AUTH_USER is set | HTTP Basic Auth password. |
|
||||
| `HEALTH_TOKEN` | recommended | Long random string (e.g. `openssl rand -hex 32`). Required to access `/api/health` in production. If unset, that route returns 404. |
|
||||
| `PUBLIC_ORIGIN` | optional | e.g. `https://check-in.fci.coop` — used in absolute self-links if needed later. |
|
||||
| `PUBLIC_ORIGIN` | optional | e.g. `https://survey.fci.coop` — used in absolute self-links if needed later. |
|
||||
|
||||
3. **Trigger the first deploy.** Render will run `npm ci && npm run build` then `npm run start`. The platform health check hits `/healthz` (lightweight, no Civi dependency).
|
||||
|
||||
@@ -37,7 +37,7 @@ Verify all of the above against your live CiviCRM by hitting `/api/health` (in d
|
||||
- `https://<your-render-url>/api/health?token=<HEALTH_TOKEN>` → all checks should be green
|
||||
- Visit `https://<your-render-url>/?cid=<test-individual>&cs=<their-checksum>` → form loads with prefill
|
||||
|
||||
5. **Custom domain (optional)** — Render dashboard → Custom Domain → add `check-in.fci.coop`. Update DNS to the provided CNAME. Render auto-issues a Let's Encrypt cert.
|
||||
5. **Custom domain (optional)** — Render dashboard → Custom Domain → add `survey.fci.coop`. Update DNS to the provided CNAME. Render auto-issues a Let's Encrypt cert.
|
||||
|
||||
## Security defaults
|
||||
|
||||
|
||||
+15
-15
@@ -1,10 +1,10 @@
|
||||
# Email link delivery
|
||||
|
||||
The check-in form is reached via a personalized URL that carries the
|
||||
The survey form is reached via a personalized URL that carries the
|
||||
contact's CiviCRM ID + a server-issued checksum:
|
||||
|
||||
```
|
||||
https://check-in.fci.coop/?cid=<contact_id>&cs=<checksum>
|
||||
https://survey.fci.coop/?cid=<contact_id>&cs=<checksum>
|
||||
```
|
||||
|
||||
This document covers the three pieces needed to actually get those links
|
||||
@@ -24,10 +24,10 @@ The preview endpoint (#3) is a debugging convenience.
|
||||
In CiviCRM:
|
||||
|
||||
1. Navigate to **Mailings → Message Templates → Add Message Template**.
|
||||
2. Title: `Co-op Check-in invitation`
|
||||
2. Title: `Co-op Survey invitation`
|
||||
3. Subject:
|
||||
```
|
||||
Time for your co-op check-in
|
||||
Time for your co-op survey
|
||||
```
|
||||
4. Plain-text body (substitute your real domain):
|
||||
|
||||
@@ -38,8 +38,8 @@ In CiviCRM:
|
||||
month. The form pre-fills your prior responses — you only need to update
|
||||
what's changed.
|
||||
|
||||
Open your check-in:
|
||||
https://check-in.fci.coop/?cid={contact.contact_id}&cs={contact.checksum}
|
||||
Open your survey:
|
||||
https://survey.fci.coop/?cid={contact.contact_id}&cs={contact.checksum}
|
||||
|
||||
The link is personalized to you and expires in 14 days. If you no longer
|
||||
have it, reply to this email and we'll send a fresh one.
|
||||
@@ -58,11 +58,11 @@ In CiviCRM:
|
||||
what's changed.</p>
|
||||
|
||||
<p style="margin: 24px 0;">
|
||||
<a href="https://check-in.fci.coop/?cid={contact.contact_id}&cs={contact.checksum}"
|
||||
<a href="https://survey.fci.coop/?cid={contact.contact_id}&cs={contact.checksum}"
|
||||
style="display: inline-block; background: #3a5520; color: #fafaf7;
|
||||
padding: 10px 20px; border-radius: 6px; text-decoration: none;
|
||||
font-weight: 500;">
|
||||
Open your check-in
|
||||
Open your survey
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -95,14 +95,14 @@ Best for: low volume, one-at-a-time, or you want to review each send.
|
||||
1. Open the **organization's contact record** in CiviCRM.
|
||||
2. **Relationships** tab → find the row with **"Primary Contact"** → click the related individual's name.
|
||||
3. On the individual's contact view: **Actions → Send Email**.
|
||||
4. **Use Template**: select `Co-op Check-in invitation`. The subject and body populate; tokens render in the preview.
|
||||
4. **Use Template**: select `Co-op Survey invitation`. The subject and body populate; tokens render in the preview.
|
||||
5. **Send**. CiviCRM logs the email as an Activity on the contact.
|
||||
|
||||
Repeat per contact. Slow but bulletproof.
|
||||
|
||||
### Option B — Bulk: CiviMail (campaign-style send)
|
||||
|
||||
Best for: send the check-in invitation to every active co-op at once (e.g. monthly).
|
||||
Best for: send the survey invitation to every active co-op at once (e.g. monthly).
|
||||
|
||||
1. Build a Smart Group of contacts whose Primary Contact relationships point at orgs in active stages. Example query:
|
||||
- **Contacts** → **Advanced Search**
|
||||
@@ -112,7 +112,7 @@ Best for: send the check-in invitation to every active co-op at once (e.g. month
|
||||
|
||||
2. **Mailings → New Mailing**.
|
||||
3. Recipients: the smart group above.
|
||||
4. Choose template: `Co-op Check-in invitation`.
|
||||
4. Choose template: `Co-op Survey invitation`.
|
||||
5. Schedule send.
|
||||
|
||||
Each recipient gets their own checksum embedded in the URL — CiviMail
|
||||
@@ -125,10 +125,10 @@ Best for: a button-on-the-org-record workflow.
|
||||
Setup:
|
||||
|
||||
1. **Administer → CiviRules → Manage Rules → New Rule**.
|
||||
2. Title: `Send Co-op Check-in invitation`.
|
||||
2. Title: `Send Co-op Survey invitation`.
|
||||
3. **Trigger**: pick a "manually trigger from org row" action if your CiviRules version supports it. Otherwise: trigger on org Stage change (auto-fires when staff advance an org).
|
||||
4. **Action**: `Send email to contact via message template`.
|
||||
- Template: `Co-op Check-in invitation`.
|
||||
- Template: `Co-op Survey invitation`.
|
||||
- Recipient: **Contact in relationship** → "Primary Contact" → side B (the Individual).
|
||||
5. Save.
|
||||
|
||||
@@ -153,7 +153,7 @@ contact/org context for verification.
|
||||
**Request:**
|
||||
|
||||
```bash
|
||||
curl "https://check-in.fci.coop/api/preview-link?cid=513&token=$HEALTH_TOKEN"
|
||||
curl "https://survey.fci.coop/api/preview-link?cid=513&token=$HEALTH_TOKEN"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
@@ -165,7 +165,7 @@ curl "https://check-in.fci.coop/api/preview-link?cid=513&token=$HEALTH_TOKEN"
|
||||
"orgId": 9609,
|
||||
"orgName": "A Sample Food Co-op",
|
||||
"orgStage": "Organizing",
|
||||
"url": "https://check-in.fci.coop/?cid=513&cs=8d1f...",
|
||||
"url": "https://survey.fci.coop/?cid=513&cs=8d1f...",
|
||||
"checksum": "8d1f...",
|
||||
"ttlHours": 336
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Co-op Check-in
|
||||
# Co-op Survey
|
||||
|
||||
A tokenized, mobile-friendly web form that lets external co-op contacts
|
||||
update their organization's tracking data on CiviCRM, plus a read-only
|
||||
@@ -11,8 +11,8 @@ 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
|
||||
- `https://survey.fci.coop/?cid=<contactId>&cs=<checksum>` — the form
|
||||
- `https://survey.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
|
||||
@@ -24,12 +24,12 @@ 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
|
||||
recent non-empty entry from past surveys. 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
|
||||
stage transitions by manually setting Stage on a "Check-in (organizing)" 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."
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* GET /api/preview-link?cid=<contactId>&token=<HEALTH_TOKEN>
|
||||
*
|
||||
* Returns a ready-to-send check-in URL for a given contact, plus enough
|
||||
* Returns a ready-to-send survey URL for a given contact, plus enough
|
||||
* context for staff to verify it's the right org. Used for:
|
||||
* - Generating links to paste into ad-hoc emails (or external tools)
|
||||
* - Testing a contact's setup before sending the real CiviCRM email
|
||||
|
||||
@@ -49,11 +49,11 @@ const STUB_PAYLOAD: ReportPayload = (() => {
|
||||
orgName: "Sample Co-op (stub)",
|
||||
currentStage: "Organizing",
|
||||
activities: [
|
||||
{ id: 9012, date: daysAgo(3), subject: "Co-op Check-in (form submission)" },
|
||||
{ id: 9008, date: daysAgo(34), subject: "Co-op Check-in (form submission)" },
|
||||
{ id: 9012, date: daysAgo(3), subject: "Co-op Survey (form submission)" },
|
||||
{ id: 9008, date: daysAgo(34), subject: "Co-op Survey (form submission)" },
|
||||
{ id: 9001, date: daysAgo(62), stage: "Organizing", subject: "Stage transition (staff)" },
|
||||
{ id: 8995, date: daysAgo(95), subject: "Co-op Check-in (form submission)" },
|
||||
{ id: 8980, date: daysAgo(180), stage: "Inquiry", subject: "Initial check-in (staff)" },
|
||||
{ id: 8995, date: daysAgo(95), subject: "Co-op Survey (form submission)" },
|
||||
{ id: 8980, date: daysAgo(180), stage: "Inquiry", subject: "Initial survey (staff)" },
|
||||
],
|
||||
fieldHistory: {
|
||||
Peer_Group_Participation: [
|
||||
|
||||
@@ -70,7 +70,7 @@ export async function POST(req: Request) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: appEnv().isProduction
|
||||
? "Could not save your check-in. Please try again, or contact your engagement coordinator."
|
||||
? "Could not save your survey. Please try again, or contact your engagement coordinator."
|
||||
: `Save failed: ${redact(msg)}`,
|
||||
},
|
||||
{ status: 500 },
|
||||
@@ -110,7 +110,7 @@ async function runSubmit(cid: string, cs: string, values: Record<string, unknown
|
||||
"status_id:name": "Completed",
|
||||
target_contact_id: orgId,
|
||||
source_contact_id: Number(cid),
|
||||
subject: "Co-op Check-in (form submission)",
|
||||
subject: "Co-op Survey (form submission)",
|
||||
};
|
||||
for (const [name, value] of Object.entries(values)) {
|
||||
const field = FIELD_BY_NAME.get(name);
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ const body = Open_Sans({
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Co-op Check-in · Food Co-op Initiative",
|
||||
title: "Co-op Survey · Food Co-op Initiative",
|
||||
description:
|
||||
"Update your co-op's progress through the FCI organizing Framework. A survey for food co-ops in development.",
|
||||
robots: { index: false, follow: false }, // Form pages are tokenized; not for crawlers.
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ function ReportIntro() {
|
||||
</h1>
|
||||
<p className="mt-4 max-w-prose text-[17px] leading-relaxed text-ink-soft">
|
||||
A read-only summary of every value your co-op has shared through past
|
||||
check-ins, grouped by stage. The most recent entry sits at the top of
|
||||
surveys, grouped by stage. The most recent entry sits at the top of
|
||||
each row; expand a row to see how a number or note has changed over
|
||||
time.
|
||||
</p>
|
||||
|
||||
@@ -429,7 +429,7 @@ function SubmissionContextHeader({
|
||||
}) {
|
||||
return (
|
||||
<header className="rounded-lg border border-rule bg-paper-2/40 px-6 py-5 sm:px-7 sm:py-6">
|
||||
<p className="text-[11px] uppercase tracking-[0.18em] text-ink-mute">Check-in for</p>
|
||||
<p className="text-[11px] uppercase tracking-[0.18em] text-ink-mute">Survey for</p>
|
||||
<h1 className="mt-1 font-display text-3xl font-medium leading-tight tracking-tight text-ink sm:text-[34px]">
|
||||
{orgName}
|
||||
</h1>
|
||||
@@ -551,7 +551,7 @@ function SubmitBar({
|
||||
} else if (state.kind === "submitting") {
|
||||
secondary = (
|
||||
<p className="truncate text-xs text-ink-soft" role="status">
|
||||
Saving your check-in…
|
||||
Saving your survey…
|
||||
</p>
|
||||
);
|
||||
} else if (viewedSectionLabel && viewedRank != null) {
|
||||
@@ -604,10 +604,10 @@ function SubmitBar({
|
||||
>
|
||||
{state.kind === "submitting" ? (
|
||||
<>
|
||||
<Spinner /> Saving check-in…
|
||||
<Spinner /> Saving survey…
|
||||
</>
|
||||
) : (
|
||||
<>Submit check-in</>
|
||||
<>Submit survey</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
@@ -636,7 +636,7 @@ function LoadingState() {
|
||||
aria-hidden
|
||||
className="mx-auto mb-4 h-7 w-7 animate-spin rounded-full border-[1.5px] border-rule border-t-leaf-700"
|
||||
/>
|
||||
<p className="font-display text-base text-ink-soft italic">Loading your check-in…</p>
|
||||
<p className="font-display text-base text-ink-soft italic">Loading your survey…</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -663,10 +663,10 @@ function SuccessDestination({
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="mt-5 font-display text-2xl font-medium leading-tight tracking-tight text-ink sm:text-3xl">
|
||||
Check-in saved
|
||||
Survey saved
|
||||
</h2>
|
||||
<p className="mx-auto mt-3 max-w-prose text-[15px] leading-relaxed text-ink-soft">
|
||||
Thank you. We've recorded this check-in for{" "}
|
||||
Thank you. We've recorded this survey for{" "}
|
||||
<span className="font-medium text-ink">{orgName}</span>. Your engagement coordinator will see
|
||||
it on their next review.
|
||||
</p>
|
||||
@@ -676,7 +676,7 @@ function SuccessDestination({
|
||||
onClick={onAnother}
|
||||
className="inline-flex items-center justify-center rounded-md border border-rule bg-paper px-5 py-2 text-sm font-medium text-ink-soft transition hover:bg-paper-2/60 focus:outline-none focus-visible:ring-2 focus-visible:ring-leaf-500/40"
|
||||
>
|
||||
Submit another check-in
|
||||
Submit another survey
|
||||
</button>
|
||||
<p className="text-xs text-ink-mute">It's safe to close this window.</p>
|
||||
</div>
|
||||
@@ -691,7 +691,7 @@ function ErrorState({ message }: { message: string }) {
|
||||
className="rounded-lg border-2 border-clay-200 bg-clay-100/30 px-6 py-7"
|
||||
>
|
||||
<h2 className="font-display text-xl font-medium text-clay-700">
|
||||
We couldn't open your check-in.
|
||||
We couldn't open your survey.
|
||||
</h2>
|
||||
<p className="mt-3 text-sm leading-relaxed text-ink-soft">{message}</p>
|
||||
<p className="mt-4 text-sm text-ink-soft">
|
||||
|
||||
@@ -174,7 +174,7 @@ function ReportContextHeader({
|
||||
{currentStageLabel || "—"}
|
||||
</p>
|
||||
<dl className="mt-5 grid grid-cols-3 gap-3 border-t border-rule-soft pt-4 text-sm sm:gap-6">
|
||||
<Stat label="Check-ins" value={String(totalActivities)} />
|
||||
<Stat label="Surveys" value={String(totalActivities)} />
|
||||
<Stat label="Fields tracked" value={String(totalFieldsTracked)} />
|
||||
<Stat
|
||||
label="Span"
|
||||
@@ -1414,7 +1414,7 @@ function EmptyState() {
|
||||
<div className="rounded-lg border border-dashed border-rule bg-paper-2/30 px-6 py-10 text-center">
|
||||
<p className="font-display text-lg text-ink-soft">No entries on file yet.</p>
|
||||
<p className="mt-2 text-sm text-ink-mute">
|
||||
Your co-op's first check-in will appear here once it's submitted.
|
||||
Your co-op's first survey will appear here once it's submitted.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,7 +19,7 @@ export function SiteHeader() {
|
||||
className="h-12 w-auto sm:h-14"
|
||||
/>
|
||||
<span className="hidden text-[11px] uppercase tracking-[0.18em] text-ink-mute sm:inline-block sm:border-l sm:border-rule sm:pl-4">
|
||||
Co-op Check-in
|
||||
Co-op Survey
|
||||
</span>
|
||||
</Link>
|
||||
<nav aria-label="Primary">
|
||||
|
||||
+8
-8
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Form definition for the Org Engagement Check-in form.
|
||||
* Form definition for the Org Engagement Survey form.
|
||||
*
|
||||
* Field references and types are sourced from the live CiviCRM DEV
|
||||
* (`client.crm.fci.coop`) inventory pulled 2026-05-09 via APIv4
|
||||
@@ -49,13 +49,13 @@ const G3 = "Stage_3";
|
||||
const G4 = "Stage_4";
|
||||
const G5 = "Stage_5";
|
||||
|
||||
// Stage 0 — Check-in (always visible)
|
||||
// Stage 0 — Survey (always visible)
|
||||
const stage0: StageSectionConfig = {
|
||||
rank: 0,
|
||||
id: "stage_0",
|
||||
label: "Check-in (organizing)",
|
||||
intro:
|
||||
"Core check-in fields. These are visible at every stage and capture the data we follow over time across the lifecycle of the co-op.",
|
||||
// intro:
|
||||
// "Core survey fields. These are visible at every stage and capture the data we follow over time across the lifecycle of the co-op.",
|
||||
fields: [
|
||||
// current_stage is a UI-only readonly field. Its value is the Civi
|
||||
// option *value* (e.g. "Organizing") sourced by /api/data from the
|
||||
@@ -218,7 +218,7 @@ const stage0: StageSectionConfig = {
|
||||
type: "currency",
|
||||
visibleWhen: visibleAtOrAfter(S.Stabilize),
|
||||
civiField: `${G0}.Member_equity_raised`,
|
||||
help: "Member-owner equity raised, as of the time of check-in",
|
||||
help: "Member-owner equity raised, as of the time of survey",
|
||||
},
|
||||
// {
|
||||
// name: "Member_loans_total",
|
||||
@@ -232,7 +232,7 @@ const stage0: StageSectionConfig = {
|
||||
type: "currency",
|
||||
visibleWhen: visibleAtOrAfter(S.Stabilize),
|
||||
civiField: `${G0}.Member_loans_raised`,
|
||||
help: "Total amount (USD) raised from member-owner loans, as of the time of check-in",
|
||||
help: "Total amount (USD) raised from member-owner loans, as of the time of survey",
|
||||
},
|
||||
// {
|
||||
// name: "Member_preferred_shares_total",
|
||||
@@ -498,7 +498,7 @@ const stage3: StageSectionConfig = {
|
||||
fieldGroups: [
|
||||
{ id: "site", fields: ["Site_Letter_of_Intent_Date", "Site_Letter_of_Intent_Upload"] },
|
||||
{ id: "store_design", fields: ["Store_Designer", "Store_Design_Plan_Completion"] },
|
||||
{ id: "cap_camp", fields: ["Capital_Campaign_Owner_Participation_", "Capital_Campaign_Average_Owner_Investment"]},
|
||||
// { id: "cap_camp", fields: ["Capital_Campaign_Owner_Participation_", "Capital_Campaign_Average_Owner_Investment"]},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -713,7 +713,7 @@ const stage5: StageSectionConfig = {
|
||||
|
||||
export const formConfig: FormConfig = {
|
||||
id: "org_engagement_check_in",
|
||||
title: "Co-op Check-in",
|
||||
title: "Co-op Survey",
|
||||
subtitle:
|
||||
"Update tracking data for your co-op as you progress through the organizing stages. The questions you'll see depend on where you are in the framework.",
|
||||
stageField: "current_stage",
|
||||
|
||||
+1
-1
@@ -203,7 +203,7 @@ export interface FormConfig {
|
||||
/**
|
||||
* Identifying details for the contact submitting the form, derived from the
|
||||
* cid resolved by /api/data. Surfaced as readonly fields in Stage 0 so the
|
||||
* person filling out the check-in can confirm who's logged as the submitter.
|
||||
* person filling out the survey can confirm who's logged as the submitter.
|
||||
*/
|
||||
export interface ContactIdentity {
|
||||
firstName: string;
|
||||
|
||||
Reference in New Issue
Block a user