10 Commits
Author SHA1 Message Date
Jules 8a70822731 refactor(funnel): single source of truth for funnel form options
* refactor(web): dedupe funnel form options across the 3 forms

`skills`, `chatService` and `country` were byte-identical in all three
form definitions. They move to `packages/funnel/src/form-options.ts` and
reach the pages through `AFFORM_OPTIONS`; the form files now describe
rendering only.

Lists verified ordered-identical to the originals, entry for entry.

Closes #140

* refactor(civi-crm): derive notion id/label maps from shared funnel options

`SKILLS_MAP`, `CHAT_SERVICE_MAP` and `COUNTRY_MAP` were a fourth copy of
the funnel option lists. They are now `toLabelMap()` over the shared
arrays, the way `HEAR_ABOUT_MAP` already was, so an id cannot mean one
thing in the dropdown and another in Notion.

All 275 derived entries verified identical to the previous literals, so
the Notion and n8n writes are unchanged. The one exception is country
1054, normalised to `Côte d'Ivoire` -- the spelling Notion already stored
and the one the other apostrophised country names use. The dropdown label
and the Ghost member note now match it.
2026-08-26 00:35:44 +09:00
Jules dfe8cf0451 fix(civi-crm): validate required funnel fields on afform-submit
* feat(funnel): add per-form required field list

Single source for which funnel answers are required, plus the check the
intake endpoint runs a submission against.

* refactor(web): build the funnel form schema from the shared list

Form definitions no longer carry a `required` flag, they describe
rendering only. The three pages pass REQUIRED_FIELDS_BY_FORM into
ConnectFormSection, which derives the zod schema and the required markers
from it.

* fix(civi-crm): validate required funnel fields on afform-submit

Only formName, the hear-about answer and the captcha were checked, so a
direct POST could create a record with no name, email or country. A
payload missing a required answer now gets a 400 naming the fields.

* docs(funnel): document the required-field validation
2026-08-24 23:24:13 +09:00
Jules bc30c4cede refactor(civi-crm/web): drop CiviCRM integration
* refactor(civi-crm): remove case management pages and API

The Circle Case UI, its API routes and the CiviCRM query layer they used
are gone. Keeps the app shell out entirely: no pages remain, so the
layout, styles and UI dependencies go with them.

* refactor(civi-crm): drop CiviCRM write from the intake endpoint

Submissions now go to Notion and, for the steward form, n8n. The legacy
afformCircleContactForm name was CiviCRM-only and is no longer accepted;
the fields[] payload is ignored.

* chore(web): remove CiviCRM afform generator scripts

They fetched form definitions from the CiviCRM API, which is gone. The
generated files under lib/civicrm are hand-maintained from now on.

* refactor(web): trim funnel form definitions and move to lib/funnel-forms

Fields keep only what renders the form: formKey, label, inputType,
required, repeatable. The CiviCRM entity/join/fieldName plumbing and the
fields[] POST payload are gone, along with the unused circle contact
form.

country was marked required but exempt from validation through its
CiviCRM Address join, so it is now enforced like every other required
select.

* docs: rewrite civi-crm and funnel docs for the intake-only app

Drops the CiviCRM API guide and the case management architecture, and
updates the funnel reference to the Notion + n8n flow.
2026-08-13 22:44:23 +09:00
Jules cc621dcfb1 feat(web): forward submitted form data on newsletter signup
* chore(web): add a debug level to the logger

Logs in every build but tests, for output a deployed preview needs.

* feat(web): forward submitted form data on newsletter signup

Every field a form collects is sent to the subscribe endpoint, select
values as their labels rather than CiviCRM option ids. Logs the payload.

* docs(funnel): document the forwarded subscribe payload

* fix(web): build the funnel newsletter payload from answers only

Keeps the intake captcha token and field defs out of the newsletter
service whatever a caller puts in extraPayload.
2026-08-05 19:42:15 +09:00
Jules 96901178e0 feat(web): subscribe funnel opt-ins to newsletters
* refactor(funnel): move profile map to @repo/funnel

The profile label is now needed by apps/web too. Keeps it from drifting
from the Notion Profile select.

* feat(web): subscribe funnel opt-ins to newsletters

Each ticked checkbox subscribes the submitted email to its Ghost
newsletter and appends a note to the member profile. Fired after the
intake POST resolves, sequentially, and never surfaces as a form error.

* docs(funnel): document newsletter opt-ins
2026-07-30 21:31:50 +09:00
Jules a7e5eedf35 feat: add "How did you first hear about Logos?" field to funnel forms
* feat: add @repo/funnel package for shared hear-about constants

Single source of truth for the "How did you first hear about Logos?"
question, options, and id -> label map used by apps/web and apps/civi-crm.

* feat: map hear-about answer to a Notion select on intake

Unknown option ids are dropped so tampering cannot create new select
options; field defs without a CiviCRM fieldName are skipped in
buildAfformValues so the Afform payload is unchanged.

* feat: add required hear-about field to the three funnel forms

Injected below the chat name/service row on coalition-partner,
activist-builder and activist-leader-steward. Web/Notion-only: defs
without a fieldName are filtered out of the CiviCRM fields payload.

* docs: document hear-about field in funnel reference

* refactor: extract resolveHearAboutLabel from Notion builder

Share the option-id -> label resolution so the route can reuse the
builder's exact accept/write predicate.

* fix: enforce required hear-about field server-side

Reject funnel submissions whose hear-about answer is missing or not a
known option id, instead of relying on client-side validation alone.
2026-07-08 19:39:42 +09:00
Jules dc0f7d55db refactor(civi-crm): write affiliated orgs to Mvmt Organization text field
* refactor(civi-crm): write affiliated orgs to Mvmt Organization text field

Redirect affiliatedOrgs from the Organization select to a new Mvmt
Organization rich_text property (clamped to 2000 chars, omitted when
empty). Drops the per-submit org-options lookup and the select
resolution helper.

* docs(funnel): document Mvmt Organization mapping
2026-06-30 23:28:31 +09:00
Jules 79cfba374b feat(civi-crm): split funnel websites into discrete Notion columns
* feat: write each funnel website to its own Notion column

Spread website[] across Website, Website 2..Website 5 instead of joining with ' | '. Empty entries dropped, columns fill contiguously, capped at 5.

* feat: cap funnel website field at 5 rows

Hide 'Add another' once 5 website rows exist, matching the five Notion website columns.

* docs: document per-column website mapping in funnel
2026-06-26 19:22:03 +09:00
Jules 51345d4012 feat(funnel): migrate /connect to afform-submit flow
* feat(funnel): route connect through afform-submit

Send /connect submissions to the shared afform-submit endpoint and allowlist afformCircleContactForm.

* fix(civi-crm): make connect civi-only intake

Gate Notion to movement forms, add connect case defaults, and allow missing Notion profile mappings for non-Notion intake forms.

* chore(civi-crm): remove legacy contact endpoint

Drop the old public contact submission route after connect migrated to afform-submit.

* test(civi-crm): cover connect afform-submit path

Assert connect case defaults, Note joins, and CiviCRM-required behaviour when Notion is skipped.

* docs(funnel): add connect to intake flow

Document the fourth form and its CiviCRM-only routing contract in the funnel intake guide.

* fix(web): update afform circle contact form question field

* feat(notion): add scorecard page content to funnel form submissions

heading_2, four bulleted_list_item blocks, and a paragraph for the
overall fit line using proper Notion block types.

* refactor(civi-crm): drop wildcard CORS from afform-submit

Rely on the public-cors middleware for origin allowlisting instead of
emitting Access-Control-Allow-Origin: * and a redundant OPTIONS handler.

* feat(web): rename /connect route to /contact

Updates the ROUTES constant and moves the app directory from
[locale]/connect to [locale]/contact.

* chore(web): exclude /contact from sitemap

Page currently redirects to the homepage, so it should not be indexed.

* docs(funnel): note /connect renamed to /contact and form removed
2026-06-02 16:50:28 +09:00
JulesandFelicio 77d83d2f1e feat: route all three intake forms to Notion & CiviCRM
* add forms for CRM funnels

* a

* feat(civi-crm): add notion-coalition-partner API route

Accepts coalition-partner form submissions and writes them to a
Notion DB. Resolves numeric IDs for country, skills and chat service
to human-readable labels. Same hCaptcha guard pattern as afform-submit.
Includes CORS headers for cross-origin requests from apps/web.

* feat(web): wire coalition-partner form to Notion API route

* fix(web): checkbox default value and broken checked visual state

Checkboxes were initialising to true instead of false, causing unchecked
submissions to send true to the API. peer-checked: on the Check icon also
never fired because it is not a direct sibling of the peer input -- replaced
with group-has-[input:checked]: which works via the label's group class.

* chore(civir-crm-api): suppress unused variable warnings in coalition-partner route

* fix(civi-crm): set CORS headers for public API routes via next.config

* set profile and lead source

- https://civicrm.logos.co/civicrm/admin/afform/submissions#/?name=afformActivistBuilder
- https://civicrm.logos.co/civicrm/contact/view?reset=1&cid=1000
- https://civicrm.logos.co/civicrm/contact/view?reset=1&cid=1000
- https://civicrm.logos.co/civicrm/contact/view/case?reset=1&id=388&cid=1000&action=view&context=case&selectedChild=case&key=CRMCaseFormSearch3vjNUK027Z0YMdmpnTbHyNePydwogixSnFN3Z2YluYYR4_8939

* default checkboxes to true

* remove titles

* u

* add policy

* t

* fix(civi-crm): avoid readonly env writes in CORS test

Use a keyed env helper in the public CORS test so cleanup and setup no longer rely on delete or readonly typed env properties.

* fix(web): align node programme route keys

Rename node program route/message keys to node programme and update links, metadata, sitemap entries, and contract tests.

* chore(civi-crm): lint:fix

* docs(funnel): document Notion intake CRM setup

Add funnel docs covering goals, property mapping, completed schema DDL,
Hide-when-empty limitations, manual UI checklist, and implementation roadmap.

* feat(civi-crm): add Notion lib for funnel intake submissions

Self-contained maps, property builder, and submitToNotion with
Organization option matching and Notion-Version 2026-03-11.

* docs(funnel): add implementation guide and sync status

Document phased steps, Notion lib layout, and refresh README/roadmap
for completed schema and submitToNotion work still pending wiring.

* docs(funnel): expand implementation steps and Notion lib notes

Document goals per phase, legacy route vs src/lib/notion/, and link
schema mapping to the code.

* refactor(civi-crm): extract Afform submit into submit-afform lib

Decouple CiviCRM writes from the afform-submit route for upcoming
Notion + CiviCRM orchestration.

* feat(civi-crm): add funnel intake submit disable flags

FUNNEL_INTAKE_NOTION_DISABLED and FUNNEL_INTAKE_CIVICRM_DISABLED opt out
per destination; unset means submit (default).

* feat(civi-crm): orchestrate afform-submit with Notion and CiviCRM

Verify hCaptcha once; Notion required when enabled, CiviCRM best-effort.
Point coalition partner at the shared endpoint; add CORS for public web.

* feat(civi-crm): set Notion BU to Movement on funnel intake

* chore(civi-crm): remove notion coalition partner route

* docs(civi-crm): document funnel intake endpoint and env

* docs(funnel): document orchestrator, env flags, and BU default

* docs(funnel): document cleanup completion and deploy checklist

Record removal of notion-coalition-partner, single afform-submit path,
and non-local Notion env requirements for operators.

* docs(funnel): replace per-phase docs with consolidated AGENTS.md

Merge README, implementation, and notion-database into a single
agent-targeted reference covering request flow, code layout, env vars,
and the full IFT BD CRM schema (all 32 properties, funnel relevance
annotated).

* refactor(civi-crm): rename NOTION_COALITION_PARTNER_DB_ID to NOTION_DB_ID

* docs: rename NOTION_COALITION_PARTNER_DB_ID to NOTION_DB_ID

* feat(notion): add scorecard page content to funnel form submissions

heading_2, four bulleted_list_item blocks, and a paragraph for the
overall fit line using proper Notion block types.

* refactor(civi-crm): drop wildcard CORS from afform-submit

Rely on the public-cors middleware for origin allowlisting instead of
emitting Access-Control-Allow-Origin: * and a redundant OPTIONS handler.

---------

Co-authored-by: Felicio <felicio@users.noreply.github.com>
2026-06-02 16:09:03 +09:00