- dashboard.templ: Replaced individual script tags with single main.js import - admin_users.templ, custom_section.templ: Minor formatting/cleanup - docs.templ: Updated script imports (excluded from Alpine conversion per user request) - api.ts, docs.ts, password_validation.ts: Minor updates for compatibility
195 lines
5.4 KiB
TypeScript
195 lines
5.4 KiB
TypeScript
// 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.global("validation", {
|
|
initPasswordValidation,
|
|
});
|