Add onboardin steps improvements

This commit is contained in:
Urtzi Alfaro
2025-08-11 07:01:08 +02:00
parent c721575cd3
commit c4d4aeb449
16 changed files with 2015 additions and 103 deletions

View File

@@ -0,0 +1,39 @@
# Onboarding Router Test Cases
## Dashboard Access for Completed Users
### Test Case 1: Training Completed
**Given**: User has completed training (`current_step: "training_completed"`)
**When**: User logs in
**Expected**: `nextAction: "dashboard"`
**Message**: "Great! Your AI model is ready. Welcome to your dashboard!"
### Test Case 2: Dashboard Accessible
**Given**: User has reached dashboard accessible step (`current_step: "dashboard_accessible"`)
**When**: User logs in
**Expected**: `nextAction: "dashboard"`
**Message**: "Welcome back! You're ready to use your AI-powered dashboard."
### Test Case 3: High Completion Percentage
**Given**: User has `completion_percentage >= 80` but step may not be final
**When**: User logs in
**Expected**: `nextAction: "dashboard"`
### Test Case 4: Fully Completed
**Given**: User has `fully_completed: true`
**When**: User logs in
**Expected**: `nextAction: "dashboard"`
### Test Case 5: In Progress User
**Given**: User has `current_step: "sales_data_uploaded"`
**When**: User logs in
**Expected**: `nextAction: "onboarding_data"`
**Should**: Continue onboarding flow
## Manual Testing
1. Complete onboarding flow for a user
2. Log out
3. Log back in
4. Verify user goes directly to dashboard (not onboarding)
5. Check welcome message shows completion status

View File

@@ -0,0 +1,215 @@
// frontend/src/utils/onboardingRouter.ts
/**
* Onboarding Router Utility
* Determines user's next step based on their current progress
*/
import { onboardingService } from '../api/services/onboarding.service';
import type { UserProgress } from '../api/services/onboarding.service';
export type OnboardingStep =
| 'user_registered'
| 'bakery_registered'
| 'sales_data_uploaded'
| 'training_completed'
| 'dashboard_accessible';
export type NextAction =
| 'register'
| 'login'
| 'onboarding_bakery'
| 'onboarding_data'
| 'onboarding_training'
| 'dashboard'
| 'landing';
export interface RoutingDecision {
nextAction: NextAction;
currentStep: OnboardingStep | null;
message?: string;
canSkip?: boolean;
completionPercentage: number;
}
export class OnboardingRouter {
/**
* Determine next action for authenticated user
*/
static async getNextActionForUser(userProgress?: UserProgress): Promise<RoutingDecision> {
try {
const progress = userProgress || await onboardingService.getUserProgress();
// Check if user has fully completed onboarding or has high completion percentage
const isFullyCompleted = progress.fully_completed || progress.completion_percentage >= 80;
const currentStep = progress.current_step as OnboardingStep;
let nextAction: NextAction;
if (isFullyCompleted || currentStep === 'training_completed' || currentStep === 'dashboard_accessible') {
// User can access dashboard
nextAction = 'dashboard';
} else {
// Use step-based routing
nextAction = this.mapStepToAction(currentStep);
}
return {
nextAction,
currentStep,
completionPercentage: progress.completion_percentage,
message: this.getStepMessage(currentStep),
canSkip: this.canSkipStep(currentStep)
};
} catch (error) {
console.error('Error getting user progress:', error);
// Fallback logic when API fails
return {
nextAction: 'onboarding_bakery',
currentStep: 'bakery_registered',
completionPercentage: 0,
message: 'Let\'s set up your bakery information'
};
}
}
/**
* Determine next action for unauthenticated user
*/
static getNextActionForGuest(): RoutingDecision {
return {
nextAction: 'landing',
currentStep: null,
completionPercentage: 0,
message: 'Welcome to PanIA - AI for your bakery'
};
}
/**
* Check if user can access dashboard with current progress
*/
static async canAccessDashboard(): Promise<boolean> {
try {
const progress = await onboardingService.getUserProgress();
return progress.fully_completed || progress.completion_percentage >= 80;
} catch (error) {
console.error('Error checking dashboard access:', error);
return false;
}
}
/**
* Get dynamic onboarding step within the flow
*/
static async getOnboardingStepFromProgress(): Promise<{
step: number;
totalSteps: number;
canProceed: boolean;
}> {
try {
const progress = await onboardingService.getUserProgress();
const currentStep = progress.current_step as OnboardingStep;
const stepMap: Record<OnboardingStep, number> = {
'user_registered': 1,
'bakery_registered': 2,
'sales_data_uploaded': 3,
'training_completed': 4,
'dashboard_accessible': 5
};
const currentStepNumber = stepMap[currentStep] || 1;
return {
step: Math.max(currentStepNumber - 1, 1), // Convert to 1-based onboarding steps
totalSteps: 4, // We have 4 onboarding steps (excluding user registration)
canProceed: progress.completion_percentage > 0
};
} catch (error) {
console.error('Error getting onboarding step:', error);
return {
step: 1,
totalSteps: 4,
canProceed: true
};
}
}
/**
* Update progress when user completes an action
*/
static async completeStep(
step: OnboardingStep,
data?: Record<string, any>
): Promise<UserProgress> {
return await onboardingService.completeStep(step, data);
}
/**
* Check if user has completed a specific step
*/
static async hasCompletedStep(step: OnboardingStep): Promise<boolean> {
try {
const progress = await onboardingService.getUserProgress();
const stepStatus = progress.steps.find((s: any) => s.step_name === step);
return stepStatus?.completed || false;
} catch (error) {
console.error(`Error checking step completion for ${step}:`, error);
return false;
}
}
/**
* Get user-friendly message for current step
*/
private static getStepMessage(step: OnboardingStep): string {
const messages: Record<OnboardingStep, string> = {
'user_registered': 'Welcome! Your account has been created successfully.',
'bakery_registered': 'Let\'s set up your bakery information to get started.',
'sales_data_uploaded': 'Upload your historical sales data for better predictions.',
'training_completed': 'Great! Your AI model is ready. Welcome to your dashboard!',
'dashboard_accessible': 'Welcome back! You\'re ready to use your AI-powered dashboard.'
};
return messages[step] || 'Continue setting up your account';
}
/**
* Map step to corresponding frontend action
*/
private static mapStepToAction(step: OnboardingStep): NextAction {
const actionMap: Record<OnboardingStep, NextAction> = {
'user_registered': 'onboarding_bakery',
'bakery_registered': 'onboarding_bakery',
'sales_data_uploaded': 'onboarding_data',
'training_completed': 'dashboard', // ✅ Users can access dashboard when training is completed
'dashboard_accessible': 'dashboard'
};
return actionMap[step] || 'onboarding_bakery';
}
/**
* Check if user can skip a specific step
*/
private static canSkipStep(step: OnboardingStep): boolean {
// Define which steps can be skipped
const skippableSteps: OnboardingStep[] = [
'training_completed' // Users can access dashboard even if training is still in progress
];
return skippableSteps.includes(step);
}
}
// Helper functions for components
export const useOnboardingRouter = () => {
return {
getNextActionForUser: OnboardingRouter.getNextActionForUser,
getNextActionForGuest: OnboardingRouter.getNextActionForGuest,
canAccessDashboard: OnboardingRouter.canAccessDashboard,
getOnboardingStepFromProgress: OnboardingRouter.getOnboardingStepFromProgress,
completeStep: OnboardingRouter.completeStep,
hasCompletedStep: OnboardingRouter.hasCompletedStep,
};
};