2025-09-07 17:25:30 +02:00
|
|
|
/**
|
|
|
|
|
* Progress tracking service - Clean, standardized implementation
|
|
|
|
|
*/
|
|
|
|
|
|
2025-09-07 21:26:28 +02:00
|
|
|
import { useCallback, useEffect, useRef } from 'react';
|
2025-09-07 17:25:30 +02:00
|
|
|
import { onboardingService } from '../../../../api/services/onboarding';
|
|
|
|
|
import { createServiceHook } from '../utils/createServiceHook';
|
|
|
|
|
import type { ProgressTrackingState } from '../core/types';
|
|
|
|
|
import type { UserProgress } from '../../../../api/types/onboarding';
|
|
|
|
|
|
|
|
|
|
const useProgressTrackingService = createServiceHook<ProgressTrackingState>({
|
|
|
|
|
initialState: {
|
|
|
|
|
progress: null,
|
|
|
|
|
isInitialized: false,
|
|
|
|
|
isCompleted: false,
|
|
|
|
|
completionPercentage: 0,
|
|
|
|
|
currentBackendStep: null,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const useProgressTracking = () => {
|
|
|
|
|
const service = useProgressTrackingService();
|
2025-09-07 21:26:28 +02:00
|
|
|
const initializationAttempted = useRef(false);
|
2025-09-07 17:25:30 +02:00
|
|
|
|
|
|
|
|
// Load initial progress from backend
|
|
|
|
|
const loadProgress = useCallback(async (): Promise<UserProgress | null> => {
|
|
|
|
|
const result = await service.executeAsync(async () => {
|
|
|
|
|
const progress = await onboardingService.getUserProgress('');
|
|
|
|
|
|
|
|
|
|
// Update service state with additional computed values
|
|
|
|
|
const updatedData = {
|
|
|
|
|
...service.data!,
|
|
|
|
|
progress,
|
|
|
|
|
isInitialized: true,
|
|
|
|
|
isCompleted: progress?.fully_completed || false,
|
|
|
|
|
completionPercentage: progress?.completion_percentage || 0,
|
|
|
|
|
currentBackendStep: progress?.current_step || null,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
service.setSuccess(updatedData);
|
|
|
|
|
|
|
|
|
|
return progress;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result.data || null;
|
|
|
|
|
}, [service]);
|
|
|
|
|
|
|
|
|
|
// Mark a step as completed and save to backend
|
|
|
|
|
const markStepCompleted = useCallback(async (
|
|
|
|
|
stepId: string,
|
|
|
|
|
data?: Record<string, any>
|
|
|
|
|
): Promise<boolean> => {
|
|
|
|
|
const result = await service.executeAsync(async () => {
|
|
|
|
|
const updatedProgress = await onboardingService.markStepAsCompleted(stepId, data);
|
|
|
|
|
|
|
|
|
|
// Update service state
|
|
|
|
|
service.setSuccess({
|
|
|
|
|
...service.data!,
|
|
|
|
|
progress: updatedProgress,
|
|
|
|
|
isCompleted: updatedProgress?.fully_completed || false,
|
|
|
|
|
completionPercentage: updatedProgress?.completion_percentage || 0,
|
|
|
|
|
currentBackendStep: updatedProgress?.current_step || null,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log(`✅ Step "${stepId}" marked as completed in backend`);
|
|
|
|
|
return updatedProgress;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
console.error(`❌ Error marking step "${stepId}" as completed:`, result.error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result.success;
|
|
|
|
|
}, [service]);
|
|
|
|
|
|
|
|
|
|
// Get the next step the user should work on
|
|
|
|
|
const getNextStep = useCallback(async (): Promise<string> => {
|
|
|
|
|
try {
|
|
|
|
|
return await onboardingService.getNextStepId();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error getting next step:', error);
|
|
|
|
|
return 'setup';
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// Get the step and index where user should resume
|
|
|
|
|
const getResumePoint = useCallback(async (): Promise<{ stepId: string; stepIndex: number }> => {
|
|
|
|
|
try {
|
|
|
|
|
return await onboardingService.getResumeStep();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error getting resume point:', error);
|
|
|
|
|
return { stepId: 'setup', stepIndex: 0 };
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// Complete the entire onboarding process
|
|
|
|
|
const completeOnboarding = useCallback(async (): Promise<boolean> => {
|
|
|
|
|
const result = await service.executeAsync(async () => {
|
|
|
|
|
const result = await onboardingService.completeOnboarding();
|
|
|
|
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
// Reload progress to get updated status
|
|
|
|
|
await loadProgress();
|
|
|
|
|
console.log('🎉 Onboarding completed successfully!');
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw new Error('Failed to complete onboarding');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return result.success;
|
|
|
|
|
}, [service, loadProgress]);
|
|
|
|
|
|
|
|
|
|
// Check if user can access a specific step
|
|
|
|
|
const canAccessStep = useCallback(async (stepId: string): Promise<boolean> => {
|
|
|
|
|
try {
|
|
|
|
|
const result = await onboardingService.canAccessStep(stepId);
|
|
|
|
|
return result.can_access;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`Error checking access for step "${stepId}":`, error);
|
|
|
|
|
return true; // Allow access on error
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-09-07 21:26:28 +02:00
|
|
|
// Auto-load progress on hook initialization - PREVENT multiple attempts
|
2025-09-07 17:25:30 +02:00
|
|
|
useEffect(() => {
|
2025-09-07 21:26:28 +02:00
|
|
|
if (!service.data?.isInitialized && !initializationAttempted.current && !service.isLoading) {
|
|
|
|
|
initializationAttempted.current = true;
|
2025-09-07 17:25:30 +02:00
|
|
|
loadProgress();
|
|
|
|
|
}
|
2025-09-07 21:26:28 +02:00
|
|
|
}, [service.data?.isInitialized, service.isLoading]); // Remove loadProgress from deps
|
2025-09-07 17:25:30 +02:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
// State
|
|
|
|
|
isLoading: service.isLoading,
|
|
|
|
|
error: service.error,
|
|
|
|
|
progress: service.data?.progress || null,
|
|
|
|
|
isInitialized: service.data?.isInitialized || false,
|
|
|
|
|
isCompleted: service.data?.isCompleted || false,
|
|
|
|
|
completionPercentage: service.data?.completionPercentage || 0,
|
|
|
|
|
currentBackendStep: service.data?.currentBackendStep || null,
|
|
|
|
|
|
|
|
|
|
// Actions
|
|
|
|
|
loadProgress,
|
|
|
|
|
markStepCompleted,
|
|
|
|
|
getNextStep,
|
|
|
|
|
getResumePoint,
|
|
|
|
|
completeOnboarding,
|
|
|
|
|
canAccessStep,
|
|
|
|
|
clearError: service.clearError,
|
|
|
|
|
};
|
|
|
|
|
};
|