di usage in website

This commit is contained in:
2026-01-06 19:36:03 +01:00
parent 589b55a87e
commit e589c30bf8
191 changed files with 6367 additions and 4253 deletions

View File

@@ -22,7 +22,10 @@ import Button from '@/components/ui/Button';
import Input from '@/components/ui/Input';
import Heading from '@/components/ui/Heading';
import CountrySelect from '@/components/ui/CountrySelect';
import { useServices } from '@/lib/services/ServiceProvider';
import { useAuth } from '@/lib/auth/AuthContext';
import { useCompleteOnboarding } from '@/hooks/onboarding/useCompleteOnboarding';
import { useGenerateAvatars } from '@/hooks/onboarding/useGenerateAvatars';
import { useValidateFacePhoto } from '@/hooks/onboarding/useValidateFacePhoto';
// ============================================================================
// TYPES
@@ -163,9 +166,8 @@ function StepIndicator({ currentStep }: { currentStep: number }) {
export default function OnboardingWizard() {
const router = useRouter();
const fileInputRef = useRef<HTMLInputElement>(null);
const { onboardingService, sessionService } = useServices();
const { session } = useAuth();
const [step, setStep] = useState<OnboardingStep>(1);
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState<FormErrors>({});
// Form state
@@ -270,6 +272,19 @@ export default function OnboardingWizard() {
reader.readAsDataURL(file);
};
const validateFacePhotoMutation = useValidateFacePhoto({
onSuccess: () => {
setAvatarInfo(prev => ({ ...prev, isValidating: false }));
},
onError: (error) => {
setErrors(prev => ({
...prev,
facePhoto: error.message || 'Face validation failed'
}));
setAvatarInfo(prev => ({ ...prev, facePhoto: null, isValidating: false }));
},
});
const validateFacePhoto = async (photoData: string) => {
setAvatarInfo(prev => ({ ...prev, isValidating: true }));
setErrors(prev => {
@@ -278,7 +293,7 @@ export default function OnboardingWizard() {
});
try {
const result = await onboardingService.validateFacePhoto(photoData);
const result = await validateFacePhotoMutation.mutateAsync(photoData);
if (!result.isValid) {
setErrors(prev => ({
@@ -286,8 +301,6 @@ export default function OnboardingWizard() {
facePhoto: result.errorMessage || 'Face validation failed'
}));
setAvatarInfo(prev => ({ ...prev, facePhoto: null, isValidating: false }));
} else {
setAvatarInfo(prev => ({ ...prev, isValidating: false }));
}
} catch (error) {
// For now, just accept the photo if validation fails
@@ -295,31 +308,8 @@ export default function OnboardingWizard() {
}
};
const generateAvatars = async () => {
if (!avatarInfo.facePhoto) {
setErrors({ ...errors, facePhoto: 'Please upload a photo first' });
return;
}
setAvatarInfo(prev => ({ ...prev, isGenerating: true, generatedAvatars: [], selectedAvatarIndex: null }));
setErrors(prev => {
const { avatar, ...rest } = prev;
return rest;
});
try {
// Get current user ID from session
const session = await sessionService.getSession();
if (!session?.user?.userId) {
throw new Error('User not authenticated');
}
const result = await onboardingService.generateAvatars(
session.user.userId,
avatarInfo.facePhoto,
avatarInfo.suitColor
);
const generateAvatarsMutation = useGenerateAvatars({
onSuccess: (result) => {
if (result.success && result.avatarUrls) {
setAvatarInfo(prev => ({
...prev,
@@ -330,15 +320,56 @@ export default function OnboardingWizard() {
setErrors(prev => ({ ...prev, avatar: result.errorMessage || 'Failed to generate avatars' }));
setAvatarInfo(prev => ({ ...prev, isGenerating: false }));
}
} catch (error) {
},
onError: () => {
setErrors(prev => ({ ...prev, avatar: 'Failed to generate avatars. Please try again.' }));
setAvatarInfo(prev => ({ ...prev, isGenerating: false }));
},
});
const generateAvatars = async () => {
if (!avatarInfo.facePhoto) {
setErrors({ ...errors, facePhoto: 'Please upload a photo first' });
return;
}
if (!session?.user?.userId) {
setErrors({ ...errors, submit: 'User not authenticated' });
return;
}
setAvatarInfo(prev => ({ ...prev, isGenerating: true, generatedAvatars: [], selectedAvatarIndex: null }));
setErrors(prev => {
const { avatar, ...rest } = prev;
return rest;
});
try {
await generateAvatarsMutation.mutateAsync({
userId: session.user.userId,
facePhotoData: avatarInfo.facePhoto,
suitColor: avatarInfo.suitColor,
});
} catch (error) {
// Error handling is done in the mutation's onError callback
}
};
const completeOnboardingMutation = useCompleteOnboarding({
onSuccess: () => {
// TODO: Handle avatar assignment separately if needed
router.push('/dashboard');
router.refresh();
},
onError: (error) => {
setErrors({
submit: error.message || 'Failed to create profile',
});
},
});
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
if (loading) return;
// Validate step 2 - must have selected an avatar
if (!validateStep(2)) {
@@ -350,35 +381,26 @@ export default function OnboardingWizard() {
return;
}
setLoading(true);
setErrors({});
try {
// Note: The current API doesn't support avatarUrl in onboarding
// This would need to be handled separately or the API would need to be updated
const result = await onboardingService.completeOnboarding({
await completeOnboardingMutation.mutateAsync({
firstName: personalInfo.firstName.trim(),
lastName: personalInfo.lastName.trim(),
displayName: personalInfo.displayName.trim(),
country: personalInfo.country,
timezone: personalInfo.timezone || undefined,
});
if (result.success) {
// TODO: Handle avatar assignment separately if needed
router.push('/dashboard');
router.refresh();
} else {
throw new Error(result.errorMessage || 'Failed to create profile');
}
} catch (error) {
setErrors({
submit: error instanceof Error ? error.message : 'Failed to create profile',
});
setLoading(false);
// Error handling is done in the mutation's onError callback
}
};
// Loading state comes from the mutations
const loading = completeOnboardingMutation.isPending ||
generateAvatarsMutation.isPending ||
validateFacePhotoMutation.isPending;
const getCountryFlag = (countryCode: string): string => {
const code = countryCode.toUpperCase();
if (code.length === 2) {