Form Validation: - Create comprehensive Zod validation schemas for all forms - Login form: email, password validation - Register form: first name, last name, email, password strength requirements - Profile update form: all user fields with optional phone/address - Password change form: current password, new password confirmation - Vote submission form: election ID and candidate selection Password Strength: - Minimum 8 characters - At least one uppercase letter - At least one digit - At least one special character (!@#$%^&*) React Hook Form Integration: - Update login page with useForm and field-level error display - Update register page with form validation and error messages - Show validation errors inline with red borders - Disable form submission while loading or submitting - Better user feedback with detailed error messages Type Safety: - Zod schemas with TypeScript inference - Type-safe form data types - Proper error handling and validation Build Status: - All pages compile successfully - Zero TypeScript errors - Bundle size includes Zod (~40 kB) and React Hook Form - Login/Register pages: 145 kB First Load JS (includes new validation libraries) - Shared bundle remains ~102 kB Setup: - npm install zod react-hook-form @hookform/resolvers - Ready for production with form validation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
147 lines
4.0 KiB
TypeScript
147 lines
4.0 KiB
TypeScript
/**
|
|
* Form Validation Schemas
|
|
* Using Zod for type-safe validation
|
|
*/
|
|
|
|
import { z } from "zod"
|
|
|
|
/**
|
|
* Login form validation schema
|
|
*/
|
|
export const loginSchema = z.object({
|
|
email: z
|
|
.string()
|
|
.email("Adresse email invalide")
|
|
.min(1, "Email requis"),
|
|
password: z
|
|
.string()
|
|
.min(1, "Mot de passe requis")
|
|
.min(6, "Le mot de passe doit contenir au moins 6 caractères"),
|
|
})
|
|
|
|
export type LoginFormData = z.infer<typeof loginSchema>
|
|
|
|
/**
|
|
* Registration form validation schema
|
|
*/
|
|
export const registerSchema = z.object({
|
|
firstName: z
|
|
.string()
|
|
.min(1, "Prénom requis")
|
|
.min(2, "Le prénom doit contenir au moins 2 caractères")
|
|
.max(50, "Le prénom ne doit pas dépasser 50 caractères"),
|
|
lastName: z
|
|
.string()
|
|
.min(1, "Nom requis")
|
|
.min(2, "Le nom doit contenir au moins 2 caractères")
|
|
.max(50, "Le nom ne doit pas dépasser 50 caractères"),
|
|
email: z
|
|
.string()
|
|
.email("Adresse email invalide")
|
|
.min(1, "Email requis"),
|
|
password: z
|
|
.string()
|
|
.min(8, "Le mot de passe doit contenir au moins 8 caractères")
|
|
.regex(/[A-Z]/, "Le mot de passe doit contenir au moins une majuscule")
|
|
.regex(/[0-9]/, "Le mot de passe doit contenir au moins un chiffre")
|
|
.regex(/[!@#$%^&*]/, "Le mot de passe doit contenir au moins un caractère spécial (!@#$%^&*)"),
|
|
passwordConfirm: z
|
|
.string()
|
|
.min(1, "Confirmation du mot de passe requise"),
|
|
}).refine(
|
|
(data) => data.password === data.passwordConfirm,
|
|
{
|
|
message: "Les mots de passe ne correspondent pas",
|
|
path: ["passwordConfirm"],
|
|
}
|
|
)
|
|
|
|
export type RegisterFormData = z.infer<typeof registerSchema>
|
|
|
|
/**
|
|
* Profile update validation schema
|
|
*/
|
|
export const profileUpdateSchema = z.object({
|
|
firstName: z
|
|
.string()
|
|
.min(1, "Prénom requis")
|
|
.min(2, "Le prénom doit contenir au moins 2 caractères")
|
|
.max(50, "Le prénom ne doit pas dépasser 50 caractères"),
|
|
lastName: z
|
|
.string()
|
|
.min(1, "Nom requis")
|
|
.min(2, "Le nom doit contenir au least 2 caractères")
|
|
.max(50, "Le nom ne doit pas dépasser 50 caractères"),
|
|
email: z
|
|
.string()
|
|
.email("Adresse email invalide")
|
|
.min(1, "Email requis"),
|
|
phone: z
|
|
.string()
|
|
.regex(/^[+\d\s\-()]*$/, "Numéro de téléphone invalide")
|
|
.optional()
|
|
.or(z.literal("")),
|
|
address: z
|
|
.string()
|
|
.max(100, "L'adresse ne doit pas dépasser 100 caractères")
|
|
.optional()
|
|
.or(z.literal("")),
|
|
city: z
|
|
.string()
|
|
.max(50, "La ville ne doit pas dépasser 50 caractères")
|
|
.optional()
|
|
.or(z.literal("")),
|
|
postalCode: z
|
|
.string()
|
|
.regex(/^\d{5}$/, "Code postal invalide (doit être 5 chiffres)")
|
|
.optional()
|
|
.or(z.literal("")),
|
|
country: z
|
|
.string()
|
|
.max(50, "Le pays ne doit pas dépasser 50 caractères")
|
|
.optional()
|
|
.or(z.literal("")),
|
|
})
|
|
|
|
export type ProfileUpdateFormData = z.infer<typeof profileUpdateSchema>
|
|
|
|
/**
|
|
* Password change validation schema
|
|
*/
|
|
export const passwordChangeSchema = z.object({
|
|
currentPassword: z
|
|
.string()
|
|
.min(1, "Mot de passe actuel requis"),
|
|
newPassword: z
|
|
.string()
|
|
.min(8, "Le nouveau mot de passe doit contenir au moins 8 caractères")
|
|
.regex(/[A-Z]/, "Le mot de passe doit contenir au moins une majuscule")
|
|
.regex(/[0-9]/, "Le mot de passe doit contenir au moins un chiffre")
|
|
.regex(/[!@#$%^&*]/, "Le mot de passe doit contenir au moins un caractère spécial"),
|
|
confirmPassword: z
|
|
.string()
|
|
.min(1, "Confirmation du mot de passe requise"),
|
|
}).refine(
|
|
(data) => data.newPassword === data.confirmPassword,
|
|
{
|
|
message: "Les nouveaux mots de passe ne correspondent pas",
|
|
path: ["confirmPassword"],
|
|
}
|
|
)
|
|
|
|
export type PasswordChangeFormData = z.infer<typeof passwordChangeSchema>
|
|
|
|
/**
|
|
* Vote submission validation schema
|
|
*/
|
|
export const voteSubmissionSchema = z.object({
|
|
electionId: z
|
|
.number()
|
|
.min(1, "Élection requise"),
|
|
choix: z
|
|
.string()
|
|
.min(1, "Candidat requis"),
|
|
})
|
|
|
|
export type VoteSubmissionFormData = z.infer<typeof voteSubmissionSchema>
|