* 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.
* 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
* 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.
* 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
* 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.