Add onboardin steps improvements
This commit is contained in:
39
frontend/src/utils/__tests__/onboardingRouter.test.md
Normal file
39
frontend/src/utils/__tests__/onboardingRouter.test.md
Normal 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
|
||||
215
frontend/src/utils/onboardingRouter.ts
Normal file
215
frontend/src/utils/onboardingRouter.ts
Normal 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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user