Skip to content

15 — Onboarding flow

The onboarding flow is the multi-step sequence a new account completes between signup and reaching the content planner. It's server-driven (the backend chooses which steps to show) but navigated client-side (the frontend handles step-to-step transitions).

This doc explains how the pieces fit together. Read it before changing anything in pages/_accid/Organisationprofile, subjects_services, goals_objectives, chaptername, brandkit, or payment — and before touching store/onboarding.js, mixins/onboardingNavigation.js, or the landing_url logic in middleware/redirect.js.

High-level shape

┌─────────────────────────────────────────────────────────────────────────────┐
│  1. User signs up / logs in                                                 │
│                                                                             │
│  2. middleware/redirect.js → getuser()                                      │
│     └─ POST /auth/getaccountdetails                                         │
│         returns { landing_url, Onboarding_type_id, isInvited, ... }         │
│                                                                             │
│  3. If landing_url is set and we are not already on it:                     │
│     ├─ store/onboarding/fetchOnboardingFlow({ obId: typeId })  (prefetch)   │
│     ├─ Cookie 'accountdetails' = { isInvited, noId, roleType, accountId,    │
│     │                              onboardingTypeId }                       │
│     └─ redirect /<accountId><landing_url>                                   │
│                                                                             │
│  4. Each onboarding page reads the flow from cookies and uses                │
│     mixins/onboardingNavigation.js to compute next/previous step,           │
│     current step number, total steps.                                       │
│                                                                             │
│  5. When the user completes the last step, navigation falls through to     │
│     /contentplanner (the mixin's hardcoded fallback).                       │
└─────────────────────────────────────────────────────────────────────────────┘

The Onboarding_type_id

The backend tags each account with an Onboarding_type_id integer. This field is what selects which onboarding flow the user gets — different cohorts (BNI, regular signup, partners, etc.) get different flows. The default is 6 (used as a fallback in store/onboarding.js when no obId is provided and getaccountdetails doesn't return one).

The flow itself is stored on the backend as a JSON array. The frontend does not own the canonical sequence of steps for any flow — that's a server-side configuration. Verify with the team which Onboarding_type_id values currently exist and what they correspond to.

The Vuex onboarding module

store/onboarding.js is the source of truth for in-flight onboarding state. It holds:

  • onboardingFlow — the array of steps [{ id, url, name }, ...].
  • onboardingTypeId — the type-id the flow was loaded for.
  • onboardingDetailsCache + lastOnboardingDetailsFetch — cached /auth/getOnboardingDetails response, 3-minute TTL (ONBOARDING_DETAILS_CACHE_TTL = 3 * 60 * 1000). This is intentionally shorter than the 5-min default in api.js because onboarding state changes more often.

Two actions:

onboarding/fetchOnboardingFlow({ obId, accountId })

Fetches the flow for a given onboarding type:

  1. If obId is missing but accountId is present, calls /auth/getaccountdetails to look up the type.
  2. Falls back to obId = 6 if both are missing.
  3. POSTs /getonboardingtype with the resolved obId.
  4. Parses onboarding_flow (which may arrive as a JSON string or already-parsed object).
  5. Commits to the store and writes cookies onboardingFlow and onboardingtype so legacy code reading from cookies stays in sync.

middleware/redirect.js → getuser calls this action immediately before redirecting to the landing_url, so by the time the first onboarding page mounts, the flow is already in the store and cookies. The page does not need to fetch.

onboarding/fetchOnboardingDetails({ force })

Fetches /auth/getOnboardingDetails, cached at 3-min TTL. Use this when an onboarding step needs to read the user's current onboarding progress (e.g. which steps are already completed).

The navigation mixin

mixins/onboardingNavigation.js is mixed into onboarding pages. It exposes:

data() { return { defaultFlow: [...] } }
computed: {
  onboardingFlow,        // current flow (from cookie, or defaultFlow)
  currentFlowIndex,      // index of the current page in the flow
  totalSteps,            // flow.length
  currentStep,           // currentFlowIndex + 1 (for "Step X of Y" UI)
  nextPage,              // URL of the next step or '/contentplanner' if last
  previousPage,          // URL of the previous step or null if first
}
methods: {
  handleNext(),          // router.push to nextPage
  handlePrevious(),      // router.push to previousPage if any
}

The mixin reads the flow from the onboardingFlow cookie, falling back to a hardcoded defaultFlow:

defaultFlow: [
  { id: 1, url: '/subjects_services',     name: 'subjects_services' },
  { id: 2, url: '/Organisationprofile',   name: 'Organisationprofile' },
  { id: 3, url: '/brandkit',              name: 'brandkit' },
  { id: 4, url: '/payment',               name: 'payment' }
]

This default is what runs if no onboardingFlow cookie is set — for example, in a development scenario where the prefetch didn't happen. It is not the actual flow most users will see. Real flows are server-defined and may be longer or differently ordered.

The mixin matches the current page to a flow step by route.path.includes(item.url.replace('/', '')). So a page at /<accountId>/Organisationprofile/something still matches the Organisationprofile step. Be careful with naming collisions — adding a new step whose URL is a substring of another step's URL will break the index calculation.

Steps in the codebase

The current set of onboarding-step pages (under pages/_accid/):

Step Page Backend endpoints
Subjects & services subjects_services/ /auth/getRecommendedTopics, /auth/chooseRecomSubject, /auth/getUserChosenSubjects
Organisation profile Organisationprofile/ /auth/getCompanyData, /auth/AICompanyData, /process-organization-profile
Goals & objectives goals_objectives/ /getGoalsObjects, /auth/getAiObjective, /auth/approveObjective
Chapter (BNI) chaptername/ /generate-chapter-profile, /update-chapter-profile, /auth/approveChapterData
Brand kit brandkit/ /auth/getLogo, /auth/uploadLogo, /auth/savecolorset, /auth/saveFont, /auth/brandTemplateSet
Payment payment/ /auth/createpaymentintent, Paddle/Stripe checkout

A flow may include any subset of these in any order. The chapter step is BNI-specific; flows without BNI users skip it.

The flow is duplicated to cookies for legacy reasons: the navigation mixin reads from the onboardingFlow cookie, not from the Vuex store. This is intentional:

  • The Vuex store is the modern source of truth.
  • The cookie keeps older code working without refactor.
  • fetchOnboardingFlow writes both, so they stay in sync as long as you go through the action.

If you bypass fetchOnboardingFlow and write directly to either, the two will desync and bugs will surface in pages that read from one but not the other. Always use the action.

A subset of the onboarding-relevant fields is also stored in the accountdetails cookie:

{
  isInvited:        0 | 1,
  noId:             0 | 1,
  roleType:         int,
  accountId:        int,
  onboardingTypeId: int,
}

This is set in two places:

  • middleware/redirect.js → getuser (after the /auth/getaccountdetails fetch).
  • The impersonation handler in the same file (sets the same fields).

Pages can read this cookie directly when they need quick access to identity-shaped fields without going through $auth.user or the Vuex api/getAccountDetails. But: the cookie is not refreshed if the underlying account fields change mid-session — re-dispatch api/fetchAccountDetails if you suspect drift.

How the landing_url integrates

landing_url is a string returned by /auth/getaccountdetails per account. It is the page the user should see immediately after login. Possible values include onboarding step paths (/Organisationprofile, /subjects_services, …) or post-onboarding destinations (/contentplanner).

The redirect middleware:

  1. Reads landing_url from getaccountdetails.
  2. If the user is not already at /<accountId><landing_url>, redirects there.
  3. If landing_url is empty/null, falls back to /<accountId>/contentplanner.

So onboarding "completion" is server-side: when the backend decides the account has done enough, it stops returning step URLs as landing_url and starts returning /contentplanner (or empty). The frontend doesn't decide when onboarding is done.

Adding or modifying an onboarding step

  1. The flow is server-side. Changing the order or set of steps requires a backend change to whatever drives /getonboardingtype. The frontend page just needs to exist at the URL the backend specifies.
  2. Add a new page under pages/_accid/<step-name>/ with the navigation mixin imported:
    import onboardingNavigation from '@/mixins/onboardingNavigation'
    export default {
      mixins: [onboardingNavigation],
      // …
      methods: {
        async onSubmit() {
          await this.$axios.post('/auth/<your-step-endpoint>', payload)
          this.handleNext()    // navigates to the next step
        }
      }
    }
    
  3. Use the layout layout: 'onBoard' for visual consistency with other onboarding pages (the onBoard.vue layout strips the main app chrome).
  4. Test with the actual Onboarding_type_id the new step belongs to — don't rely on defaultFlow. Setting the onboardingFlow cookie manually in devtools is a useful way to test specific flow shapes.
  5. Watch for substring collisions in URL names (see "navigation mixin" section above).

Sharp edges

  • The defaultFlow in the mixin is a safety net, not the real flow. New devs sometimes assume it's the canonical sequence and write code based on its order. It isn't.
  • fetchOnboardingFlow's fallback obId = 6 is hardcoded. Verify this is still the right default if you see unexpected steps in development.
  • onboarding_flow may arrive as JSON string or object. The action handles both. Don't change one branch without the other.
  • accountdetails cookie staleness. If the user's role or onboarding-type changes mid-session (e.g. an admin changes their role), the cookie won't reflect it until the next login. Force-refresh api/fetchAccountDetails if your feature depends on the latest values.
  • The onboarding Vuex module's clearOnboardingDetailsCache mutation is the only invalidation hook. There's no clearAll like in api.js — if you need to wipe both the flow and the details, do it explicitly.