Phase 7: Add user onboarding tour and tenant setup wizard

Feature 1 - How-To Intro Tour (react-joyride):
- 8-step guided walkthrough highlighting Dashboard, Accounts, Assessments,
  Transactions, Budgets, Reports, and AI Investment Planning
- Runs automatically on first login, tracked via has_seen_intro flag on user
- Centralized step config in config/tourSteps.ts for easy text editing
- data-tour attributes on Sidebar nav items and Dashboard for targeting

Feature 2 - Tenant Onboarding Wizard:
- 3-step modal wizard: create operating account, assessment group + units,
  import budget CSV
- Runs after tour completes, tracked via onboardingComplete in org settings JSONB
- Reuses existing API endpoints (POST /accounts, /assessment-groups, /units,
  /budgets/:year/import)

Backend changes:
- Add has_seen_intro column to shared.users + migration
- Add PATCH /auth/intro-seen endpoint to mark tour complete
- Add PATCH /organizations/settings endpoint for org settings updates
- Include hasSeenIntro in login response, settings in switch-org response

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 09:47:45 -05:00
parent d1c40c633f
commit f1e66966f3
15 changed files with 4111 additions and 10 deletions

View File

@@ -7,6 +7,7 @@ interface Organization {
role: string;
schemaName?: string;
status?: string;
settings?: Record<string, any>;
}
interface User {
@@ -16,6 +17,7 @@ interface User {
lastName: string;
isSuperadmin?: boolean;
isPlatformOwner?: boolean;
hasSeenIntro?: boolean;
}
interface ImpersonationOriginal {
@@ -33,6 +35,8 @@ interface AuthState {
impersonationOriginal: ImpersonationOriginal | null;
setAuth: (token: string, user: User, organizations: Organization[]) => void;
setCurrentOrg: (org: Organization, token?: string) => void;
setUserIntroSeen: () => void;
setOrgSettings: (settings: Record<string, any>) => void;
startImpersonation: (token: string, user: User, organizations: Organization[]) => void;
stopImpersonation: () => void;
logout: () => void;
@@ -59,6 +63,16 @@ export const useAuthStore = create<AuthState>()(
currentOrg: org,
token: token || state.token,
})),
setUserIntroSeen: () =>
set((state) => ({
user: state.user ? { ...state.user, hasSeenIntro: true } : null,
})),
setOrgSettings: (settings) =>
set((state) => ({
currentOrg: state.currentOrg
? { ...state.currentOrg, settings: { ...(state.currentOrg.settings || {}), ...settings } }
: null,
})),
startImpersonation: (token, user, organizations) => {
const state = get();
set({
@@ -97,7 +111,7 @@ export const useAuthStore = create<AuthState>()(
}),
{
name: 'ledgeriq-auth',
version: 4,
version: 5,
migrate: () => ({
token: null,
user: null,