Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
52ccc1c
Prototype V2 two-axis onboarding intent step for design review
RisingOrange Aug 1, 2026
852ed75
Drop the forced newsletter opt-in for Volunteers/Leads
RisingOrange Aug 1, 2026
02a9616
Use the #1010 critical-alert notice copy verbatim for consistency
RisingOrange Aug 1, 2026
a9f0fd8
Add a list-unsubscribe reassurance to the critical-alert notice
RisingOrange Aug 1, 2026
9a51ea2
Prettier-format the critical-alert notice block
RisingOrange Aug 1, 2026
e8f25a6
Use LinkWithoutIcon for the privacy link in the notice (eslint)
RisingOrange Aug 1, 2026
3ffd7a0
Polish from review: clearer bell copy, axis section label, fix stale …
RisingOrange Aug 1, 2026
81552fb
Reframe the privacy-policy checkbox as notice-acknowledgement, not ag…
RisingOrange Aug 1, 2026
989eca3
Rename the opt-ins section label to "Want to hear from us?"
RisingOrange Aug 2, 2026
526b416
style(onboarding): tighten Step 2 vertical spacing so the footer fits
RisingOrange Aug 3, 2026
a94a5f4
copy(onboarding): distinguish Substack from the keep-informed box by …
RisingOrange Aug 4, 2026
cde2bbe
copy(onboarding): note chapter updates on the Volunteer and Lead options
RisingOrange Aug 5, 2026
9237a1f
style(onboarding): prettier-wrap the long intent sub strings
RisingOrange Aug 5, 2026
ac18561
copy(onboarding): split the chapter-updates note into its own sentence
RisingOrange Aug 5, 2026
c238cea
copy(onboarding): propagate the reworked keep-informed copy to the br…
RisingOrange Aug 7, 2026
8be5dfe
merge: main, bringing in Turnstile and the preview fix
RisingOrange Aug 16, 2026
fe1d57c
merge main + implement the step-2 rework: opt-ins split from intent, …
RisingOrange Aug 20, 2026
e2d8fe5
lint: eslint-disable for the notice's {@html}, matching the gdpr line
RisingOrange Aug 20, 2026
e3b73ee
review: scope the nothing-required contract to /join; continuation fa…
RisingOrange Aug 20, 2026
7e64af3
review: group semantics + translated radiogroup label, fr fidelity, d…
RisingOrange Aug 20, 2026
5704dd2
trim comments: drop narration, keep the constraints
RisingOrange Aug 20, 2026
d0e127b
merge main: reconcile the flow doc with #1049 (continuation gate word…
RisingOrange Aug 20, 2026
c782c6a
i18n: align chapter vocabulary with the chapters' own sites (de Grupp…
RisingOrange Aug 21, 2026
f70d82d
copy: the bell promises chapter news and ways to help, not news alone
RisingOrange Aug 21, 2026
678256c
copy: drop the redundant PauseAI from the bell's chapter mention
RisingOrange Aug 21, 2026
066fd3f
copy: the volunteer and lead disclosures say the chapter makes contac…
RisingOrange Aug 21, 2026
2d542ac
copy: unbracket the chapter-contact disclosure, it is not an aside
RisingOrange Aug 21, 2026
ae68bc6
Merge remote-tracking branch 'origin/main' into prototype/onboarding-…
RisingOrange Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions docs/join-form-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ stateDiagram-v2
Step1 --> Browse: "I just want to take action now" (mode=browse)
Browse: Browse mode<br/>(act-now, no signup)<br/>includes ActionCards

Step2: Step 2 — Intent<br/>(keep informed / newsletter / act-now / volunteer / lead)
Step2: Step 2 — Opt-ins + intent<br/>(opt-ins: keep informed / newsletter;<br/>intent: act-now / volunteer / lead)
Step2 --> Step1: Back
Step2 --> Submit2: Submit (POST /embed/onboarding-form?/submit)
Submit2 --> Step3Confirm: intent = null OR act-now (contact)
Expand Down Expand Up @@ -235,6 +235,12 @@ stateDiagram-v2
Browse --> [*]
```

Step 2 shows the two email opt-ins above the intent cards, with a
critical-alert disclosure under the opt-ins (`aria-describedby` on both, so
screen readers reach it). On `/join`, neither the opt-ins nor an intent is
required: a submission carrying only the GDPR consent is valid and lands on
the critical-alerts tier only.

### Continuation mode

The diagram above describes a fresh `/join` visit. When `OnboardingFlow` is
Expand All @@ -245,9 +251,9 @@ question, "what do you want to do", rather than a signup:

- The keep-informed and Substack opt-in cards are hidden, as is the GDPR
consent checkbox, because both were answered on the subscribe form.
- Picking an intent becomes **required** to submit. On a fresh visit an opt-in
alone is enough, so this is a different submit gate, not just a different
layout.
- Picking an intent becomes **required** to submit. On a fresh visit nothing
beyond the GDPR consent is required, so this is a different submit gate, not
just a different layout.
- The two hidden inputs described under "Chapter sharing" are added.

So the `Step2 --> Step1: Back` edge and the opt-in-only submit path in the
Expand Down Expand Up @@ -346,10 +352,13 @@ an update is in "Create versus update" above.
- Required: `full_name`, `email`, `country`, `city`.
- `email` must match `^\S+@\S+\.\S+$`.
- `country` must be in `COUNTRIES`, checked only when one is supplied.
- `intent` must be one of `INTENTS` (`Act now` | `Volunteer` | `Lead` | `Keep informed`).
- GDPR consent (`agree_gdpr`). `/subscribe` posts it as a hidden field, since
signing up on that form is itself the privacy-policy consent, which its
microcopy links.
- `intent` must be one of `INTENTS` (`None` | `Keep informed` | `Act now` |
`Volunteer` | `Lead`). Step 2 submits `None` when no intent is picked; the
browse signup hardcodes `Act now`; `/subscribe` hardcodes `Keep informed`.
- GDPR consent (`agree_gdpr`) required **only on the create path** — step-3
volunteer updates are exempt because consent was captured at step 2.
`/subscribe` posts it as a hidden field, since signing up on that form is
itself the privacy-policy consent, which its microcopy links.
- Volunteer path additionally requires: ≥1 language, a valid `hours` value, and
both `agree_volunteer` and `agree_conduct` checkboxes.

Expand Down
35 changes: 23 additions & 12 deletions src/lib/components/onboarding/OnboardingFlow.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@
// back so the server updates the record instead of creating another. When the
// /subscribe flow hands off, it's seeded with the record already created there.
let recordId = $state(initialRecordId)
// Never pre-checked on /join: marketing consent must be freely given, for
// Volunteers/Leads too (operational volunteer comms ride legitimate interest).
let keepInformed = $state(initialKeepInformed)
let submitting = $state(false)
let browseSignedUp = $state(false)
Expand Down Expand Up @@ -203,9 +205,9 @@
const isContinuation = $derived(startStep === 2 && !!initialRecordId)

// GDPR data-processing consent gating every record-creating submission
// (step 2 + browse). Chapter-sharing consent is not bundled here: it lives
// in the optional "Keep me informed" opt-in, since we only share details
// with a local chapter when the person asks to be connected to one.
// (step 2 + browse). On /join this checkbox also carries chapter sharing:
// the server writes share permission on every /join create. /subscribe asks
// for chapter sharing as its own checkbox instead.
// Stays false so it never pre-checks a visible consent box; the continuation
// already consented on the subscribe form and is exempted at the submit gate.
let gdprConsent = $state(false)
Expand Down Expand Up @@ -555,7 +557,7 @@
<input
type="hidden"
name="intent"
value={intent ? INTENT_VALUES[intent] : 'Keep informed'}
value={intent ? INTENT_VALUES[intent] : isContinuation ? 'Keep informed' : 'None'}
/>
<!-- The server writes Email subscription from this post every time, so a
post without this input clears the flag. Both forms that can update
Expand All @@ -573,13 +575,15 @@
{/if}
<h2>{msgs.onboarding_step2_heading}</h2>
{#if !isContinuation}
<div class="intent-grid">
<p class="section-label" id="optins-heading">{msgs.onboarding_optins_heading}</p>
<div class="intent-grid" role="group" aria-labelledby="optins-heading">
<button
type="button"
class="intent-option"
class:selected={keepInformed}
role="checkbox"
aria-checked={keepInformed}
aria-describedby="critical-alert-notice"
onclick={() => (keepInformed = !keepInformed)}
>
<span class="intent-icon">
Expand All @@ -599,6 +603,7 @@
class:selected={basics.newsletter}
role="checkbox"
aria-checked={basics.newsletter}
aria-describedby="critical-alert-notice"
onclick={() => (basics.newsletter = !basics.newsletter)}
>
<span class="intent-icon">
Expand All @@ -613,9 +618,17 @@
</span>
</button>
</div>
<p class="helper" id="critical-alert-notice">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html msgs.onboarding_email_critical_notice}
</p>
<p class="section-label">{msgs.onboarding_intent_more_optional}</p>
{/if}
<div class="intent-stack" role="radiogroup" aria-label="Want to do more?">
<div
class="intent-stack"
role="radiogroup"
aria-label={msgs.onboarding_intent_more_optional}
>
{#each intentOptions as option (option.key)}
<button
type="button"
Expand Down Expand Up @@ -647,9 +660,7 @@
<button
type="submit"
class="primary"
disabled={(isContinuation ? !intent : !intent && !keepInformed && !basics.newsletter) ||
(!isContinuation && !gdprConsent) ||
!canSubmit}
disabled={(isContinuation ? !intent : !gdprConsent) || !canSubmit}
>
{submitting
? msgs.onboarding_btn_submitting
Expand Down Expand Up @@ -1077,7 +1088,7 @@

h2 {
font-family: var(--font-heading);
margin-top: 0;
margin: 0;
}

.browse-banner {
Expand All @@ -1102,7 +1113,7 @@
display: flex;
flex-direction: column;
align-items: stretch;
gap: 1rem;
gap: 0.75rem;
width: 100%;
max-width: none;
}
Expand Down Expand Up @@ -1520,7 +1531,7 @@
text-transform: uppercase;
letter-spacing: 0.05em;
opacity: 0.7;
margin: 1.5rem 0 0.5rem 0;
margin: 0.75rem 0 0.5rem 0;
}

.confirmation-footer :global(.discord-button) {
Expand Down
Loading
Loading