/** * Form Validation Utilities for GridPilot * * Provides reusable validation functions and schemas for common form fields */ export interface ValidationResult { isValid: boolean; errors: string[]; } export interface ValidationRule { validate: (value: T) => boolean; message: string; } /** * Email validation */ export const emailValidation = (email: string): ValidationResult => { const errors: string[] = []; if (!email || !email.trim()) { errors.push('Email is required'); } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { errors.push('Invalid email format'); } return { isValid: errors.length === 0, errors, }; }; /** * Password validation */ export const passwordValidation = (password: string): ValidationResult => { const errors: string[] = []; if (!password) { errors.push('Password is required'); } else { if (password.length < 8) { errors.push('Password must be at least 8 characters'); } if (!/[a-z]/.test(password) || !/[A-Z]/.test(password)) { errors.push('Password must contain uppercase and lowercase letters'); } if (!/\d/.test(password)) { errors.push('Password must contain at least one number'); } } return { isValid: errors.length === 0, errors, }; }; /** * Name validation (for display names, first names, last names) */ export const nameValidation = (name: string, field: string = 'Name'): ValidationResult => { const errors: string[] = []; const trimmed = name ? name.trim() : ''; if (!trimmed) { errors.push(`${field} is required`); } else if (trimmed.length < 2) { errors.push(`${field} must be at least 2 characters`); } else if (trimmed.length > 25) { errors.push(`${field} must be no more than 25 characters`); } else if (!/^[A-Za-z\-']+$/.test(trimmed)) { errors.push(`${field} can only contain letters, hyphens, and apostrophes`); } else if (/^(user|test|demo|guest|player)/i.test(trimmed)) { errors.push(`Please use your real ${field.toLowerCase()}, not a nickname`); } return { isValid: errors.length === 0, errors, }; }; /** * Confirm password validation */ export const confirmPasswordValidation = ( password: string, confirmPassword: string ): ValidationResult => { const errors: string[] = []; if (!confirmPassword) { errors.push('Please confirm your password'); } else if (password !== confirmPassword) { errors.push('Passwords do not match'); } return { isValid: errors.length === 0, errors, }; }; /** * Login form validation */ export interface LoginFormValues { email: string; password: string; rememberMe?: boolean; } export const validateLoginForm = (values: LoginFormValues): Record => { const errors: Record = {}; const emailResult = emailValidation(values.email || ''); if (!emailResult.isValid) { errors.email = emailResult.errors[0]; } const passwordResult = passwordValidation(values.password || ''); if (!passwordResult.isValid) { errors.password = passwordResult.errors[0]; } return errors; }; /** * Signup form validation */ export interface SignupFormValues { firstName: string; lastName: string; email: string; password: string; confirmPassword: string; } export const validateSignupForm = (values: SignupFormValues): Record => { const errors: Record = {}; const firstNameResult = nameValidation(values.firstName || '', 'First name'); if (!firstNameResult.isValid) { errors.firstName = firstNameResult.errors[0]; } const lastNameResult = nameValidation(values.lastName || '', 'Last name'); if (!lastNameResult.isValid) { errors.lastName = lastNameResult.errors[0]; } const emailResult = emailValidation(values.email || ''); if (!emailResult.isValid) { errors.email = emailResult.errors[0]; } const passwordResult = passwordValidation(values.password || ''); if (!passwordResult.isValid) { errors.password = passwordResult.errors[0]; } const confirmPasswordResult = confirmPasswordValidation(values.password || '', values.confirmPassword || ''); if (!confirmPasswordResult.isValid) { errors.confirmPassword = confirmPasswordResult.errors[0]; } return errors; }; /** * Password strength checker */ export interface PasswordStrength { score: number; // 0-5 label: string; color: string; requirements: Array<{ met: boolean; label: string }>; } export function checkPasswordStrength(password: string): PasswordStrength { let score = 0; const requirements = [ { met: password.length >= 8, label: 'At least 8 characters' }, { met: password.length >= 12, label: 'At least 12 characters' }, { met: /[a-z]/.test(password) && /[A-Z]/.test(password), label: 'Upper and lowercase letters' }, { met: /\d/.test(password), label: 'At least one number' }, { met: /[^a-zA-Z\d]/.test(password), label: 'At least one special character' }, ]; requirements.forEach(req => { if (req.met) score++; }); let label = 'Weak'; let color = 'bg-red-500'; if (score <= 1) { label = 'Weak'; color = 'bg-red-500'; } else if (score <= 2) { label = 'Fair'; color = 'bg-warning-amber'; } else if (score <= 3) { label = 'Good'; color = 'bg-primary-blue'; } else { label = 'Strong'; color = 'bg-performance-green'; } return { score, label, color, requirements, }; } /** * Field validation helper for real-time validation */ export function createFieldValidator(rules: Array>) { return (value: T): ValidationResult => { const errors: string[] = []; for (const rule of rules) { if (!rule.validate(value)) { errors.push(rule.message); break; // Stop at first failure } } return { isValid: errors.length === 0, errors, }; }; } /** * Common validation rules */ export const requiredRule: ValidationRule = { validate: (value) => value.trim().length > 0, message: 'This field is required', }; export const minLengthRule = (min: number): ValidationRule => ({ validate: (value) => value.length >= min, message: `Must be at least ${min} characters`, }); export const maxLengthRule = (max: number): ValidationRule => ({ validate: (value) => value.length <= max, message: `Must be no more than ${max} characters`, }); export const emailRule: ValidationRule = { validate: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), message: 'Invalid email format', }; export const noSpacesRule: ValidationRule = { validate: (value) => !/\s/.test(value), message: 'No spaces allowed', };