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

@@ -9,6 +9,16 @@ export { useData } from './useData';
export { useTraining } from './useTraining';
export { useForecast } from './useForecast';
export { useNotification } from './useNotification';
export { useOnboarding, useOnboardingStep } from './useOnboarding';
// Import hooks for combined usage
import { useAuth } from './useAuth';
import { useTenant } from './useTenant';
import { useData } from './useData';
import { useTraining } from './useTraining';
import { useForecast } from './useForecast';
import { useNotification } from './useNotification';
import { useOnboarding } from './useOnboarding';
// Combined hook for common operations
export const useApiHooks = () => {
@@ -18,6 +28,7 @@ export const useApiHooks = () => {
const training = useTraining();
const forecast = useForecast();
const notification = useNotification();
const onboarding = useOnboarding();
return {
auth,
@@ -26,5 +37,6 @@ export const useApiHooks = () => {
training,
forecast,
notification,
onboarding,
};
};

View File

@@ -0,0 +1,194 @@
// frontend/src/api/hooks/useOnboarding.ts
/**
* Onboarding Hook
* React hook for managing user onboarding flow and progress
*/
import { useState, useEffect } from 'react';
import { onboardingService } from '../services/onboarding.service';
import type { UserProgress, UpdateStepRequest } from '../services/onboarding.service';
export interface UseOnboardingReturn {
progress: UserProgress | null;
isLoading: boolean;
error: string | null;
currentStep: string | null;
nextStep: string | null;
completionPercentage: number;
isFullyComplete: boolean;
// Actions
updateStep: (data: UpdateStepRequest) => Promise<void>;
completeStep: (stepName: string, data?: Record<string, any>) => Promise<void>;
resetStep: (stepName: string) => Promise<void>;
getNextStep: () => Promise<string>;
completeOnboarding: () => Promise<void>;
canAccessStep: (stepName: string) => Promise<boolean>;
refreshProgress: () => Promise<void>;
}
export const useOnboarding = (): UseOnboardingReturn => {
const [progress, setProgress] = useState<UserProgress | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Derived state
const currentStep = progress?.current_step || null;
const nextStep = progress?.next_step || null;
const completionPercentage = progress?.completion_percentage || 0;
const isFullyComplete = progress?.fully_completed || false;
// Load initial progress
const loadProgress = async () => {
setIsLoading(true);
setError(null);
try {
const userProgress = await onboardingService.getUserProgress();
setProgress(userProgress);
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to load onboarding progress';
setError(message);
console.error('Onboarding progress load error:', err);
} finally {
setIsLoading(false);
}
};
// Update step
const updateStep = async (data: UpdateStepRequest) => {
setIsLoading(true);
setError(null);
try {
const updatedProgress = await onboardingService.updateStep(data);
setProgress(updatedProgress);
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to update step';
setError(message);
throw err; // Re-throw so calling component can handle it
} finally {
setIsLoading(false);
}
};
// Complete step with data
const completeStep = async (stepName: string, data?: Record<string, any>) => {
await updateStep({
step_name: stepName,
completed: true,
data
});
};
// Reset step
const resetStep = async (stepName: string) => {
await updateStep({
step_name: stepName,
completed: false
});
};
// Get next step
const getNextStep = async (): Promise<string> => {
try {
const result = await onboardingService.getNextStep();
return result.step;
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to get next step';
setError(message);
throw err;
}
};
// Complete entire onboarding
const completeOnboarding = async () => {
setIsLoading(true);
setError(null);
try {
await onboardingService.completeOnboarding();
await loadProgress(); // Refresh progress after completion
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to complete onboarding';
setError(message);
throw err;
} finally {
setIsLoading(false);
}
};
// Check if user can access step
const canAccessStep = async (stepName: string): Promise<boolean> => {
try {
const result = await onboardingService.canAccessStep(stepName);
return result.can_access;
} catch (err) {
console.error('Can access step check failed:', err);
return false;
}
};
// Refresh progress
const refreshProgress = async () => {
await loadProgress();
};
// Load progress on mount
useEffect(() => {
loadProgress();
}, []);
return {
progress,
isLoading,
error,
currentStep,
nextStep,
completionPercentage,
isFullyComplete,
updateStep,
completeStep,
resetStep,
getNextStep,
completeOnboarding,
canAccessStep,
refreshProgress,
};
};
// Helper hook for specific steps
export const useOnboardingStep = (stepName: string) => {
const onboarding = useOnboarding();
const stepStatus = onboarding.progress?.steps.find(
step => step.step_name === stepName
);
const isCompleted = stepStatus?.completed || false;
const stepData = stepStatus?.data || {};
const completedAt = stepStatus?.completed_at;
const completeThisStep = async (data?: Record<string, any>) => {
await onboarding.completeStep(stepName, data);
};
const resetThisStep = async () => {
await onboarding.resetStep(stepName);
};
const canAccessThisStep = async (): Promise<boolean> => {
return await onboarding.canAccessStep(stepName);
};
return {
...onboarding,
stepName,
isCompleted,
stepData,
completedAt,
completeThisStep,
resetThisStep,
canAccessThisStep,
};
};

View File

@@ -28,6 +28,7 @@ export {
useForecast,
useNotification,
useApiHooks,
useOnboarding,
} from './hooks';
// Export WebSocket functionality

View File

@@ -11,6 +11,7 @@ import { DataService } from './data.service';
import { TrainingService } from './training.service';
import { ForecastingService } from './forecasting.service';
import { NotificationService } from './notification.service';
import { OnboardingService } from './onboarding.service';
// Create service instances
export const authService = new AuthService();
@@ -19,9 +20,10 @@ export const dataService = new DataService();
export const trainingService = new TrainingService();
export const forecastingService = new ForecastingService();
export const notificationService = new NotificationService();
export const onboardingService = new OnboardingService();
// Export the classes as well
export { AuthService, TenantService, DataService, TrainingService, ForecastingService, NotificationService };
export { AuthService, TenantService, DataService, TrainingService, ForecastingService, NotificationService, OnboardingService };
// Import base client
export { apiClient } from '../client';
@@ -37,6 +39,7 @@ export const api = {
training: trainingService,
forecasting: forecastingService,
notification: notificationService,
onboarding: onboardingService,
} as const;
// Service status checking

View File

@@ -0,0 +1,92 @@
// frontend/src/api/services/onboarding.service.ts
/**
* Onboarding Service
* Handles user progress tracking and onboarding flow management
*/
import { apiClient } from '../client';
export interface OnboardingStepStatus {
step_name: string;
completed: boolean;
completed_at?: string;
data?: Record<string, any>;
}
export interface UserProgress {
user_id: string;
steps: OnboardingStepStatus[];
current_step: string;
next_step?: string;
completion_percentage: number;
fully_completed: boolean;
last_updated: string;
}
export interface UpdateStepRequest {
step_name: string;
completed: boolean;
data?: Record<string, any>;
}
export class OnboardingService {
private baseEndpoint = '/users/me/onboarding';
/**
* Get user's current onboarding progress
*/
async getUserProgress(): Promise<UserProgress> {
return apiClient.get(`${this.baseEndpoint}/progress`);
}
/**
* Update a specific onboarding step
*/
async updateStep(data: UpdateStepRequest): Promise<UserProgress> {
return apiClient.put(`${this.baseEndpoint}/step`, data);
}
/**
* Mark step as completed with optional data
*/
async completeStep(stepName: string, data?: Record<string, any>): Promise<UserProgress> {
return this.updateStep({
step_name: stepName,
completed: true,
data
});
}
/**
* Reset a step (mark as incomplete)
*/
async resetStep(stepName: string): Promise<UserProgress> {
return this.updateStep({
step_name: stepName,
completed: false
});
}
/**
* Get next required step for user
*/
async getNextStep(): Promise<{ step: string; data?: Record<string, any> }> {
return apiClient.get(`${this.baseEndpoint}/next-step`);
}
/**
* Complete entire onboarding process
*/
async completeOnboarding(): Promise<{ success: boolean; message: string }> {
return apiClient.post(`${this.baseEndpoint}/complete`);
}
/**
* Check if user can access a specific step
*/
async canAccessStep(stepName: string): Promise<{ can_access: boolean; reason?: string }> {
return apiClient.get(`${this.baseEndpoint}/can-access/${stepName}`);
}
}
export const onboardingService = new OnboardingService();