270 lines
6.6 KiB
TypeScript
270 lines
6.6 KiB
TypeScript
/**
|
|
* 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<T> {
|
|
validate: (value: T) => boolean;
|
|
message: string;
|
|
}
|
|
|
|
/**
|
|
* Email validation
|
|
*/
|
|
export const emailValidation = (email: string): ValidationResult => {
|
|
const errors: string[] = [];
|
|
|
|
if (!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.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<string, string> => {
|
|
const errors: Record<string, string> = {};
|
|
|
|
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<string, string> => {
|
|
const errors: Record<string, string> = {};
|
|
|
|
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<T>(rules: Array<ValidationRule<T>>) {
|
|
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<string> = {
|
|
validate: (value) => value.trim().length > 0,
|
|
message: 'This field is required',
|
|
};
|
|
|
|
export const minLengthRule = (min: number): ValidationRule<string> => ({
|
|
validate: (value) => value.length >= min,
|
|
message: `Must be at least ${min} characters`,
|
|
});
|
|
|
|
export const maxLengthRule = (max: number): ValidationRule<string> => ({
|
|
validate: (value) => value.length <= max,
|
|
message: `Must be no more than ${max} characters`,
|
|
});
|
|
|
|
export const emailRule: ValidationRule<string> = {
|
|
validate: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
|
|
message: 'Invalid email format',
|
|
};
|
|
|
|
export const noSpacesRule: ValidationRule<string> = {
|
|
validate: (value) => !/\s/.test(value),
|
|
message: 'No spaces allowed',
|
|
}; |