Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesOnboardingOnboarding

Onboarding

What is this

Onboarding is the post-signup wizard that takes a brand-new owner from “I just made a Clerk account” to “my organization exists with branding, legal consents accepted, and a welcome email landed”. It runs at /{lang}/(protected)/onboarding and is built on OnboardJS (@onboardjs/react) with a PostHog plugin for analytics.

The wizard is now intentionally lean — only three steps remain: org, legal, done. The previous (FIT-93 plan) multi-step flow with delivery mode, plan picker, location, program, class type, and invite sub-steps was simplified to the bare minimum. Optional follow-up data (class type, default duration/capacity, invitations) is collected later via POST /organizations/:id/onboarding-complete if the wizard or post-onboarding UI chooses to populate it; the current wizard sends {} for that call.

FIT-93 also defines a Phase B guided tour — a post-onboarding contextual tour across the dashboard. The corresponding code lives in apps/web/src/components/guided-tour/ and the user’s tour completion is tracked via users.guided_tour_completed_at.

Who uses it

PersonaWhy
New ownerRequired gate before reaching the dashboard. No memberships → routed here.
Existing owner with another orgSkipped — the server-side guard in onboarding/page.tsx redirects if memberships exist.

The wizard is never seen by coaches or members — they arrive via invitation, which carries them straight to an active membership. Onboarding is owner-creation-only.

Persona impact

  • Server-side guard in apps/web/src/app/[lang]/(protected)/onboarding/page.tsx calls getCurrentUserServer. If memberships.length > 0, redirects to either /{lang}/dashboard/overview (staff role) or /{lang} (member). Only zero-membership users see the wizard.
  • LocalStorage persistence — the OnboardingProvider uses localStoragePersistence: { key:'fitkit:setup-wizard-v2', ttl: 7 days } so a half-finished wizard can resume after a browser restart.
  • AnalyticscreatePostHogPlugin configures the OnboardJS PostHog plugin with flowId='setup-wizard', flowVersion='2.0.0', eventPrefix='onboarding_'.

High-level capabilities

  1. Three stepsorg (name + type), legal (terms/privacy/waiver), done (summary). Each is a separate React component under apps/web/src/components/onboarding/steps/.
  2. State container — OnboardJS flowData holds orgName, orgType, legalConsentsAccepted (typed via SetupWizardContext).
  3. handleFinish — runs in this order:
    • POST /organizations with { name, type }.
    • If legalConsentsAccepted=true: POST /legal/consents with { types:['terms_of_use','privacy_policy','fitness_waiver'], context:'owner_onboarding', organizationId }.
    • POST /organizations/:id/onboarding-complete with {} (no class type, no invites).
    • Invalidate the /users/me React Query.
    • router.push('/{lang}/dashboard/overview').
  4. Form componentsOrgTypeSelector, LegalAcceptanceForm, plus utility components AddressAutocomplete, ClassTypeForm, DaySelector, DeliveryModeSelector, InviteMembersForm, OptionTile that live in apps/web/src/components/onboarding/ (some are unused by the current 3-step wizard but kept available for the post-onboarding UX that fills in additional data via onboarding-complete).

Relationship to other features

  • users-auth — sign-up immediately precedes onboarding. getCurrentUserServer powers the guard.
  • organizations — the wizard’s payoff is POST /organizations, then POST /:id/onboarding-complete.
  • membershipsPOST /organizations auto-inserts the owner membership; InviteMembersForm (if used) calls POST /:orgId/invitations.
  • Legal / consentsLegalAcceptanceForm writes legal_consents rows via POST /legal/consents with context='owner_onboarding'.
  • Platform billing / tiersplatformTier defaults to lite on create; tier upgrade happens later in /dashboard/settings/billing (TODO: verify route).

Current status

Shipped (3-step lean version). The legacy plan in docs/_archive/plans-legacy/FIT-93-guided-onboarding.md describes a fuller multi-step wizard; the live code reflects the simpler reality (onboarding-content.tsx only references OrgStep, LegalStep, DoneStep).

The guided tour (FIT-93 Phase B) is partially implemented — apps/web/src/components/guided-tour/ and users.guided_tour_completed_at exist. Tour completion is signalled via PATCH /users/me with {guidedTourCompletedAt}.

Known gaps

  • The wizard’s done step has no UI to actually populate the optional fields of onboarding-complete (classTypeName, defaultDurationMin, defaultCapacity, inviteEmails). The current wizard always POSTs {}. The richer post-onboarding setup happens through other surfaces (settings, members invite dialog).
  • LocalStorage key uses v2 suffix — earlier wizard versions could leave fitkit:setup-wizard orphan data; no cleanup observed.
  • No “skip onboarding” escape hatch for testing — the server-side guard requires zero memberships. E2E suites bypass via the standard usePersona/storageState plumbing.