import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } /** * Validates an email address format * @param email - The email address to validate * @returns true if the email is valid, false otherwise */ export function isValidEmail(email: string): boolean { if (!email || typeof email !== 'string') { return false; } // Trim whitespace const trimmedEmail = email.trim(); // Basic email regex pattern // Matches: local-part@domain // - Local part: alphanumeric, dots, hyphens, underscores, plus signs // - Domain: alphanumeric, dots, hyphens // - Must have @ symbol // - Domain must have at least one dot const emailRegex = /^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; // Check length constraints (RFC 5321) if (trimmedEmail.length > 254) { return false; } // Check for valid format if (!emailRegex.test(trimmedEmail)) { return false; } // Additional checks // - Cannot start or end with dot // - Cannot have consecutive dots // - Must have valid domain part const parts = trimmedEmail.split('@'); if (parts.length !== 2) { return false; } const [localPart, domain] = parts; // Validate local part if (localPart.length === 0 || localPart.length > 64) { return false; } if (localPart.startsWith('.') || localPart.endsWith('.')) { return false; } if (localPart.includes('..')) { return false; } // Validate domain part if (domain.length === 0 || domain.length > 253) { return false; } if (domain.startsWith('.') || domain.endsWith('.')) { return false; } if (domain.includes('..')) { return false; } if (!domain.includes('.')) { return false; } // Domain must have at least one TLD (top-level domain) const domainParts = domain.split('.'); if (domainParts.length < 2) { return false; } const tld = domainParts[domainParts.length - 1]; if (tld.length < 2) { return false; } return true; }