Add 'Verify campaigns' as a third onboarding role

Extend OnboardingRole and StartSignupOptions to include 'verifier', add a
third RoleCard to the role picker, and wire the pick handler to branch on
the new role. For now the verifier pick routes to the public /organizations
onboarding tool; a later commit replaces this with a captive sub-flow.
This commit is contained in:
lemon
2026-06-12 16:25:27 -07:00
parent 0d334e89e7
commit f2fa16c3bb
4 changed files with 37 additions and 15 deletions
+21 -8
View File
@@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next';
import {
ArrowLeft,
ArrowRight,
BadgeCheck,
Bitcoin,
Download,
Eye,
@@ -129,18 +130,22 @@ function CaptiveOverlay() {
}
}, [step, user, cancel, goTo]);
// Role pick is the final step. Picking a role both records the choice
// (used by the role-pick CTA labels) and navigates to the matching
// surface: creator → campaign-creation form, donor → full campaign grid
// (`/campaigns`, not `/`, so they land on the browse-everything view
// rather than the curated home with its own marketing hero). No separate
// outro / celebration screen.
// Role pick is the final step for creator/donor. Picking a role both
// records the choice (used by the role-pick CTA labels) and navigates to
// the matching surface: creator → campaign-creation form, donor → full
// campaign grid (`/campaigns`, not `/`, so they land on the
// browse-everything view rather than the curated home with its own
// marketing hero). The verifier role does not navigate away — it branches
// into the captive verifier sub-flow (wired up in a later step); for now
// it routes to the public /organizations onboarding tool.
const handleRolePick = useCallback(
(next: 'creator' | 'donor') => {
(next: 'creator' | 'donor' | 'verifier') => {
setContextRole(next);
cancel();
if (next === 'creator') {
navigate('/campaigns/new');
} else if (next === 'verifier') {
navigate('/organizations');
} else {
navigate('/campaigns');
}
@@ -273,7 +278,7 @@ function CaptiveOverlay() {
interface RoleStepProps {
role: OnboardingRole;
onPick: (role: 'creator' | 'donor') => void;
onPick: (role: 'creator' | 'donor' | 'verifier') => void;
}
/**
@@ -310,6 +315,14 @@ function RoleStep({ role, onPick }: RoleStepProps) {
selected={role === 'donor'}
onClick={() => onPick('donor')}
/>
<RoleCard
icon={<BadgeCheck className="h-5 w-5 md:h-6 md:w-6 text-primary" />}
title={t('onboarding.role.verifier.title')}
description={t('onboarding.role.verifier.description')}
finderNote={t('onboarding.role.verifier.finderNote')}
selected={role === 'verifier'}
onClick={() => onPick('verifier')}
/>
</div>
</div>
+1 -1
View File
@@ -32,7 +32,7 @@ export function OnboardingProvider({ children }: { children: ReactNode }) {
// animation. We re-seed on the next startSignup().
}, []);
const setRole = useCallback((next: 'creator' | 'donor') => {
const setRole = useCallback((next: 'creator' | 'donor' | 'verifier') => {
setRoleState(next);
}, []);
+10 -6
View File
@@ -1,13 +1,17 @@
import { createContext, useContext } from 'react';
/**
* The two top-level roles a new user can pick during onboarding. Drives
* downstream copy (creator vs. donor framing) and the role-pick CTA target
* (creator → /campaigns/new, donor → /campaigns).
* The top-level roles a new user can pick during onboarding. Drives
* downstream copy (creator vs. donor vs. verifier framing) and the
* role-pick behavior:
* - `creator` → navigate to /campaigns/new
* - `donor` → navigate to /campaigns
* - `verifier`→ stay captive and branch into the verifier sub-flow
* (org identity → org bio → publish statement → how-to-verify)
*
* `null` before the user has answered the role-picker step.
*/
export type OnboardingRole = 'creator' | 'donor' | null;
export type OnboardingRole = 'creator' | 'donor' | 'verifier' | null;
/** Options to pre-seed when invoking the captive flow from a specific CTA. */
export interface StartSignupOptions {
@@ -15,7 +19,7 @@ export interface StartSignupOptions {
* Pre-fill the role picker. CTAs that semantically already imply a role
* (e.g. "Start a campaign") can skip the role step by passing this.
*/
role?: 'creator' | 'donor';
role?: 'creator' | 'donor' | 'verifier';
}
export interface OnboardingContextValue {
@@ -29,7 +33,7 @@ export interface OnboardingContextValue {
* finishes or explicitly bails out. */
cancel: () => void;
/** Update the selected role from inside the flow (role-picker step). */
setRole: (role: 'creator' | 'donor') => void;
setRole: (role: 'creator' | 'donor' | 'verifier') => void;
}
export const OnboardingContext = createContext<OnboardingContextValue | undefined>(undefined);
+5
View File
@@ -104,6 +104,11 @@
"title": "Give to campaigns",
"description": "Support causes with Bitcoin.",
"finderNote": "Your donation goes straight to the organizer's wallet."
},
"verifier": {
"title": "Verify campaigns",
"description": "Vouch for campaigns you've checked out, as an organization.",
"finderNote": "Donors see your badge on campaigns you trust."
}
},
"keygen": {