car_mms/app/lib/validation-utils.ts
2025-09-11 14:22:27 +03:00

291 lines
8.3 KiB
TypeScript

// Validation utility functions with Arabic error messages
export interface ValidationRule {
required?: boolean;
minLength?: number;
maxLength?: number;
min?: number;
max?: number;
pattern?: RegExp;
email?: boolean;
phone?: boolean;
url?: boolean;
custom?: (value: any) => string | null;
}
export interface ValidationResult {
isValid: boolean;
error?: string;
}
// Arabic error messages
export const ERROR_MESSAGES = {
required: 'هذا الحقل مطلوب',
email: 'البريد الإلكتروني غير صحيح',
phone: 'رقم الهاتف غير صحيح',
url: 'الرابط غير صحيح',
minLength: (min: number) => `يجب أن يكون على الأقل ${min} أحرف`,
maxLength: (max: number) => `يجب أن يكون أقل من ${max} حرف`,
min: (min: number) => `يجب أن يكون على الأقل ${min}`,
max: (max: number) => `يجب أن يكون أقل من ${max}`,
pattern: 'التنسيق غير صحيح',
number: 'يجب أن يكون رقم صحيح',
integer: 'يجب أن يكون رقم صحيح',
positive: 'يجب أن يكون رقم موجب',
negative: 'يجب أن يكون رقم سالب',
date: 'التاريخ غير صحيح',
time: 'الوقت غير صحيح',
datetime: 'التاريخ والوقت غير صحيح',
};
// Regular expressions for validation
export const PATTERNS = {
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
phone: /^[\+]?[0-9\s\-\(\)]+$/,
url: /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/,
arabicText: /^[\u0600-\u06FF\s\d\p{P}]+$/u,
englishText: /^[a-zA-Z\s\d\p{P}]+$/u,
alphanumeric: /^[a-zA-Z0-9]+$/,
numeric: /^\d+$/,
decimal: /^\d+(\.\d+)?$/,
plateNumber: /^[A-Z0-9\u0600-\u06FF\s\-]+$/u,
username: /^[a-zA-Z0-9_]+$/,
};
// Validate a single field
export function validateField(value: any, rules: ValidationRule): ValidationResult {
// Convert value to string for most validations
const stringValue = value != null ? String(value).trim() : '';
// Required validation
if (rules.required && (!value || stringValue === '')) {
return { isValid: false, error: ERROR_MESSAGES.required };
}
// Skip other validations if value is empty and not required
if (!rules.required && (!value || stringValue === '')) {
return { isValid: true };
}
// Email validation
if (rules.email && !PATTERNS.email.test(stringValue)) {
return { isValid: false, error: ERROR_MESSAGES.email };
}
// Phone validation
if (rules.phone && !PATTERNS.phone.test(stringValue)) {
return { isValid: false, error: ERROR_MESSAGES.phone };
}
// URL validation
if (rules.url && !PATTERNS.url.test(stringValue)) {
return { isValid: false, error: ERROR_MESSAGES.url };
}
// Pattern validation
if (rules.pattern && !rules.pattern.test(stringValue)) {
return { isValid: false, error: ERROR_MESSAGES.pattern };
}
// Length validations
if (rules.minLength && stringValue.length < rules.minLength) {
return { isValid: false, error: ERROR_MESSAGES.minLength(rules.minLength) };
}
if (rules.maxLength && stringValue.length > rules.maxLength) {
return { isValid: false, error: ERROR_MESSAGES.maxLength(rules.maxLength) };
}
// Numeric validations
if (rules.min !== undefined || rules.max !== undefined) {
const numValue = Number(value);
if (isNaN(numValue)) {
return { isValid: false, error: ERROR_MESSAGES.number };
}
if (rules.min !== undefined && numValue < rules.min) {
return { isValid: false, error: ERROR_MESSAGES.min(rules.min) };
}
if (rules.max !== undefined && numValue > rules.max) {
return { isValid: false, error: ERROR_MESSAGES.max(rules.max) };
}
}
// Custom validation
if (rules.custom) {
const customError = rules.custom(value);
if (customError) {
return { isValid: false, error: customError };
}
}
return { isValid: true };
}
// Validate multiple fields
export function validateFields(data: Record<string, any>, rules: Record<string, ValidationRule>): {
isValid: boolean;
errors: Record<string, string>;
} {
const errors: Record<string, string> = {};
Object.entries(rules).forEach(([fieldName, fieldRules]) => {
const result = validateField(data[fieldName], fieldRules);
if (!result.isValid && result.error) {
errors[fieldName] = result.error;
}
});
return {
isValid: Object.keys(errors).length === 0,
errors,
};
}
// Specific validation functions
export function validateEmail(email: string): ValidationResult {
return validateField(email, { required: true, email: true });
}
export function validatePhone(phone: string): ValidationResult {
return validateField(phone, { phone: true });
}
export function validatePassword(password: string, minLength = 8): ValidationResult {
return validateField(password, { required: true, minLength });
}
export function validateUsername(username: string): ValidationResult {
return validateField(username, {
required: true,
minLength: 3,
pattern: PATTERNS.username
});
}
export function validatePlateNumber(plateNumber: string): ValidationResult {
return validateField(plateNumber, {
required: true,
pattern: PATTERNS.plateNumber
});
}
export function validateYear(year: number, minYear = 1900, maxYear = new Date().getFullYear() + 1): ValidationResult {
return validateField(year, {
required: true,
min: minYear,
max: maxYear
});
}
export function validateCurrency(amount: number, min = 0, max = 999999999): ValidationResult {
return validateField(amount, {
required: true,
min,
max
});
}
// Form data sanitization
export function sanitizeString(value: string): string {
return value.trim().replace(/\s+/g, ' ');
}
export function sanitizeNumber(value: string | number): number | null {
const num = Number(value);
return isNaN(num) ? null : num;
}
export function sanitizeInteger(value: string | number): number | null {
const num = parseInt(String(value));
return isNaN(num) ? null : num;
}
export function sanitizeFormData(data: Record<string, any>): Record<string, any> {
const sanitized: Record<string, any> = {};
Object.entries(data).forEach(([key, value]) => {
if (typeof value === 'string') {
sanitized[key] = sanitizeString(value);
} else {
sanitized[key] = value;
}
});
return sanitized;
}
// Date validation helpers
export function isValidDate(date: any): boolean {
return date instanceof Date && !isNaN(date.getTime());
}
export function validateDateRange(startDate: Date, endDate: Date): ValidationResult {
if (!isValidDate(startDate) || !isValidDate(endDate)) {
return { isValid: false, error: ERROR_MESSAGES.date };
}
if (startDate >= endDate) {
return { isValid: false, error: 'تاريخ البداية يجب أن يكون قبل تاريخ النهاية' };
}
return { isValid: true };
}
// File validation helpers
export function validateFileSize(file: File, maxSizeInMB: number): ValidationResult {
const maxSizeInBytes = maxSizeInMB * 1024 * 1024;
if (file.size > maxSizeInBytes) {
return {
isValid: false,
error: `حجم الملف يجب أن يكون أقل من ${maxSizeInMB} ميجابايت`
};
}
return { isValid: true };
}
export function validateFileType(file: File, allowedTypes: string[]): ValidationResult {
if (!allowedTypes.includes(file.type)) {
return {
isValid: false,
error: `نوع الملف غير مدعوم. الأنواع المدعومة: ${allowedTypes.join(', ')}`
};
}
return { isValid: true };
}
// Array validation helpers
export function validateArrayLength(array: any[], min?: number, max?: number): ValidationResult {
if (min !== undefined && array.length < min) {
return {
isValid: false,
error: `يجب اختيار ${min} عنصر على الأقل`
};
}
if (max !== undefined && array.length > max) {
return {
isValid: false,
error: `يجب اختيار ${max} عنصر كحد أقصى`
};
}
return { isValid: true };
}
// Conditional validation
export function validateConditional(
value: any,
condition: boolean,
rules: ValidationRule
): ValidationResult {
if (!condition) {
return { isValid: true };
}
return validateField(value, rules);
}