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/getOnboardingDetailsresponse, 3-minute TTL (ONBOARDING_DETAILS_CACHE_TTL = 3 * 60 * 1000). This is intentionally shorter than the 5-min default inapi.jsbecause onboarding state changes more often.
Two actions:
onboarding/fetchOnboardingFlow({ obId, accountId })¶
Fetches the flow for a given onboarding type:
- If
obIdis missing butaccountIdis present, calls/auth/getaccountdetailsto look up the type. - Falls back to
obId = 6if both are missing. - POSTs
/getonboardingtypewith the resolvedobId. - Parses
onboarding_flow(which may arrive as a JSON string or already-parsed object). - Commits to the store and writes cookies
onboardingFlowandonboardingtypeso 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.
Cookie sync¶
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.
fetchOnboardingFlowwrites 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.
The accountdetails cookie¶
A subset of the onboarding-relevant fields is also stored in the accountdetails cookie:
This is set in two places:
middleware/redirect.js → getuser(after the/auth/getaccountdetailsfetch).- 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:
- Reads
landing_urlfromgetaccountdetails. - If the user is not already at
/<accountId><landing_url>, redirects there. - If
landing_urlis 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¶
- 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. - Add a new page under
pages/_accid/<step-name>/with the navigation mixin imported: - Use the layout
layout: 'onBoard'for visual consistency with other onboarding pages (theonBoard.vuelayout strips the main app chrome). - Test with the actual
Onboarding_type_idthe new step belongs to — don't rely ondefaultFlow. Setting theonboardingFlowcookie manually in devtools is a useful way to test specific flow shapes. - Watch for substring collisions in URL names (see "navigation mixin" section above).
Sharp edges¶
- The
defaultFlowin 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 fallbackobId = 6is hardcoded. Verify this is still the right default if you see unexpected steps in development.onboarding_flowmay arrive as JSON string or object. The action handles both. Don't change one branch without the other.accountdetailscookie 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-refreshapi/fetchAccountDetailsif your feature depends on the latest values.- The
onboardingVuex module'sclearOnboardingDetailsCachemutation is the only invalidation hook. There's noclearAlllike inapi.js— if you need to wipe both the flow and the details, do it explicitly.