// Password validation for registration form // Procedural style - no OOP, passive event listeners for password manager compatibility import { Alpine } from "./alpine"; // Validation check functions function hasMinimumLength(password: string): boolean { return password.length >= 8; } function hasUppercase(password: string): boolean { return /[A-Z]/.test(password); } function hasLowercase(password: string): boolean { return /[a-z]/.test(password); } function hasNumber(password: string): boolean { return /[0-9]/.test(password); } function hasSpecialChar(password: string): boolean { return /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password); } function passwordsMatch(password: string, confirm: string): boolean { if (!password && !confirm) { return false; } return password === confirm; } function hasUsername(username: string): boolean { return username.trim().length > 0; } function hasEmail(email: string): boolean { return email.trim().length > 0; } // UI update functions function updateRequirementStatus(elementId: string, passed: boolean): void { const element = document.getElementById(elementId); if (!element) { return; } const icon = element.querySelector(".requirement-icon"); if (!icon) { return; } if (passed) { icon.textContent = "✓"; icon.className = "requirement-icon text-green-500"; element.style.color = "var(--text-primary)"; } else { icon.textContent = "○"; icon.className = "requirement-icon"; element.style.color = "var(--text-secondary)"; } } function updateSubmitButton(allPassed: boolean): void { const button = document.getElementById("register-btn") as HTMLButtonElement; if (!button) { return; } if (allPassed) { button.disabled = false; button.classList.remove("opacity-50", "cursor-not-allowed"); } else { button.disabled = true; button.classList.add("opacity-50", "cursor-not-allowed"); } } // Main validation orchestrator function validateAll(): void { const passwordField = document.getElementById("password") as HTMLInputElement; const confirmField = document.getElementById( "confirm-password", ) as HTMLInputElement; const usernameField = document.getElementById("username") as HTMLInputElement; const emailField = document.getElementById("email") as HTMLInputElement; if (!passwordField || !confirmField || !usernameField || !emailField) { return; } const password = passwordField.value; const confirm = confirmField.value; const username = usernameField.value; const email = emailField.value; // Check password requirements const hasLen = hasMinimumLength(password); const hasUpper = hasUppercase(password); const hasLower = hasLowercase(password); const hasNum = hasNumber(password); const hasSpecial = hasSpecialChar(password); const doMatch = passwordsMatch(password, confirm); const hasUser = hasUsername(username); const hasEmailAddr = hasEmail(email); // Update requirement indicators updateRequirementStatus("req-length", hasLen); updateRequirementStatus("req-upper", hasUpper); updateRequirementStatus("req-lower", hasLower); updateRequirementStatus("req-number", hasNum); updateRequirementStatus("req-special", hasSpecial); updateRequirementStatus("req-match", doMatch); // Enable/disable submit button const allPassed = hasLen && hasUpper && hasLower && hasNum && hasSpecial && doMatch && hasUser && hasEmailAddr; updateSubmitButton(allPassed); } // Debounce function to avoid excessive validation calls let debounceTimer: number | null = null; function debouncedValidation(): void { if (debounceTimer !== null) { clearTimeout(debounceTimer); } debounceTimer = window.setTimeout(() => { validateAll(); debounceTimer = null; }, 100); } // Event handlers (PASSIVE - no preventDefault, doesn't block password managers) function onPasswordInput(): void { debouncedValidation(); } function onConfirmInput(): void { debouncedValidation(); } function onUsernameInput(): void { debouncedValidation(); } function onEmailInput(): void { debouncedValidation(); } // Initialization function initPasswordValidation(): void { const passwordField = document.getElementById("password") as HTMLInputElement; const confirmField = document.getElementById( "confirm-password", ) as HTMLInputElement; const usernameField = document.getElementById("username") as HTMLInputElement; const emailField = document.getElementById("email") as HTMLInputElement; if (!passwordField || !confirmField || !usernameField || !emailField) { return; } // Add passive event listeners - don't prevent default, don't block password managers passwordField.addEventListener("input", onPasswordInput, { passive: true }); passwordField.addEventListener("paste", onPasswordInput, { passive: true }); confirmField.addEventListener("input", onConfirmInput, { passive: true }); confirmField.addEventListener("paste", onConfirmInput, { passive: true }); usernameField.addEventListener("input", onUsernameInput, { passive: true }); usernameField.addEventListener("paste", onUsernameInput, { passive: true }); emailField.addEventListener("input", onEmailInput, { passive: true }); emailField.addEventListener("paste", onEmailInput, { passive: true }); // Initial validation validateAll(); } // Export for use in template export { initPasswordValidation }; Alpine.data("validation", () => ({ initPasswordValidation, }));