This commit updates the web frontend TypeScript modules: Core modules: - admin.ts: Admin panel functionality and user management - analytics.ts: Analytics dashboard and data visualization - api-explorer.ts: Interactive API documentation explorer - api.ts: Core API client with request/response handling - collections.ts: Book collection management UI - conflicts.ts: Sync conflict resolution interface - custom-section-builder.ts: Dynamic section builder for UI - docs.ts: Documentation viewer and navigation - dom.ts: DOM manipulation utilities and helpers - header.ts: Application header with navigation - library.ts: Library view and book grid management - linking.ts: Device-book linking interface - password_validation.ts: Client-side password strength validation - queue.ts: Device sync queue management UI - search.ts: Full-text search with Lunr integration - storage.ts: Local storage and cache management - theme.ts: Theme management and CSS variable updates - themeDropdown.ts: Theme selector dropdown component - toast.ts: Toast notification system - woodPaneling.ts: Visual theme effects - woodPanelingInit.ts: Visual effects initialization Type definitions: - api.d.ts: Updated TypeScript definitions for API responses These updates enhance the frontend with improved functionality for book management, device synchronization, and user experience.
189 lines
5.3 KiB
TypeScript
189 lines
5.3 KiB
TypeScript
// Password validation for registration form
|
|
// Procedural style - no OOP, passive event listeners for password manager compatibility
|
|
|
|
// 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
|
|
(window as any).initPasswordValidation = initPasswordValidation;
|