veza/apps/web/src/features/auth/components/RegisterForm.tsx
senke 5dedc2ce4e fix: Corriger URL Swagger et finaliser implémentation DeveloperPage
- Ajouter fallback pour Swagger UI si doc.json ne fonctionne pas
- Améliorer message d'erreur avec bouton pour ouvrir Swagger UI directement
- Les fonctionnalités API Keys et Usage Stats sont maintenant complètes et fonctionnelles
- Tous les onglets de DeveloperPage sont maintenant implémentés
2026-01-18 13:55:28 +01:00

236 lines
7 KiB
TypeScript

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useNavigate } from 'react-router-dom';
import { useAuthStore } from '../store/authStore';
import { formatErrorMessage } from '@/utils/apiErrorHandler';
import type { RegisterRequest } from '@/services/api/auth';
import type { ApiError } from '@/schemas/apiSchemas';
import { Input, Button } from '@veza/design-system';
import { logger } from '@/utils/logger';
import { useFormValidation } from '@/hooks/useFormValidation';
import { useEffect } from 'react';
import { useToast } from '@/hooks/useToast';
const registerSchema = z
.object({
email: z.string().email('Email invalide'),
username: z
.string()
.min(3, "Le nom d'utilisateur doit contenir au moins 3 caractères"),
password: z
.string()
.min(12, 'Le mot de passe doit contenir au moins 12 caractères'),
password_confirm: z.string().min(1, 'La confirmation est requise'),
})
.refine((data) => data.password === data.password_confirm, {
message: 'Les mots de passe ne correspondent pas',
path: ['password_confirm'],
});
type RegisterFormData = z.infer<typeof registerSchema>;
/**
* RegisterForm with Kōdō Design System
* MIGRATED: Now using Kōdō Input and Button components
*/
export const RegisterForm = () => {
const navigate = useNavigate();
const { register: registerStore, isLoading, error } = useAuthStore();
const { toast } = useToast();
const {
register,
handleSubmit,
formState: { errors },
watch,
} = useForm<RegisterFormData>({
resolver: zodResolver(registerSchema),
defaultValues: {
email: '',
username: '',
password: '',
password_confirm: '',
},
});
// Action 5.2.1.2: Pre-validation with backend
const formData = watch();
const { validate, errors: backendErrors } = useFormValidation({
type: 'RegisterRequest',
debounceMs: 300,
});
// Validate on form data change (debounced)
useEffect(() => {
const hasData =
formData.email || formData.username || formData.password || formData.password_confirm;
if (hasData) {
// Backend expects password_confirmation, not password_confirm
const registerData = {
email: formData.email || '',
username: formData.username || '',
password: formData.password || '',
password_confirmation: formData.password_confirm || '',
};
validate(registerData);
}
}, [formData.email, formData.username, formData.password, formData.password_confirm, validate]);
const onSubmit = async (data: RegisterFormData) => {
try {
const registerRequest: RegisterRequest = {
email: data.email,
username: data.username,
password: data.password,
password_confirm: data.password_confirm,
};
await registerStore(registerRequest);
// Redirection après succès
// Si l'utilisateur est authentifié (token reçu), aller au dashboard
// Sinon (email vérification requise), aller au login
const { isAuthenticated } = useAuthStore.getState();
if (isAuthenticated) {
navigate('/dashboard');
} else {
navigate('/login');
}
} catch (err) {
// L'erreur est déjà gérée par le store, mais afficher aussi un toast pour visibilité
const errorMessage = formatErrorMessage(err as ApiError);
logger.error('Register error', {
error: err instanceof Error ? err.message : String(err),
stack: err instanceof Error ? err.stack : undefined,
});
// UI_UX: Afficher un toast pour s'assurer que l'erreur est visible
toast({
title: "Erreur d'inscription",
description: errorMessage,
variant: 'destructive',
});
}
};
// Afficher un toast si une erreur apparaît dans le store (par exemple, erreur réseau)
useEffect(() => {
if (error) {
const errorMessage = formatErrorMessage(error);
toast({
title: "Erreur d'inscription",
description: errorMessage,
variant: 'destructive',
});
}
}, [error, toast]);
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
<h2 className="text-2xl font-heading font-bold text-center text-white">
Inscription
</h2>
{error && (
<div
className="bg-kodo-red/10 border border-kodo-red/50 text-kodo-red px-4 py-4 rounded-lg"
role="alert"
>
<span className="block sm:inline text-sm">
{formatErrorMessage(error as ApiError)}
</span>
</div>
)}
<div>
<Input
label="Email"
type="email"
{...register('email')}
placeholder="votre@email.com"
/>
{errors.email && (
<p className="text-kodo-red text-xs mt-1">{errors.email.message}</p>
)}
{backendErrors
.filter((e) => e.field === 'email')
.map((e, i) => (
<p key={i} className="text-kodo-red text-xs mt-1">
{e.message}
</p>
))}
</div>
<div>
<Input
label="Nom d'utilisateur"
type="text"
{...register('username')}
placeholder="utilisateur123"
/>
{errors.username && (
<p className="text-kodo-red text-xs mt-1">
{errors.username.message}
</p>
)}
{backendErrors
.filter((e) => e.field === 'username')
.map((e, i) => (
<p key={i} className="text-kodo-red text-xs mt-1">
{e.message}
</p>
))}
</div>
<div>
<Input
label="Mot de passe"
type="password"
{...register('password')}
placeholder="••••••••••••"
/>
{errors.password && (
<p className="text-kodo-red text-xs mt-1">
{errors.password.message}
</p>
)}
{backendErrors
.filter((e) => e.field === 'password')
.map((e, i) => (
<p key={i} className="text-kodo-red text-xs mt-1">
{e.message}
</p>
))}
</div>
<div>
<Input
label="Confirmer le mot de passe"
type="password"
{...register('password_confirm')}
placeholder="••••••••••••"
/>
{errors.password_confirm && (
<p className="text-kodo-red text-xs mt-1">
{errors.password_confirm.message}
</p>
)}
{backendErrors
.filter((e) => e.field === 'password_confirmation' || e.field === 'password_confirm')
.map((e, i) => (
<p key={i} className="text-kodo-red text-xs mt-1">
{e.message}
</p>
))}
</div>
<Button
type="submit"
disabled={isLoading}
variant="primary"
className="w-full"
>
{isLoading ? 'Inscription...' : "S'inscrire"}
</Button>
</form>
);
};