1/** 2 * Shared Auth Page JavaScript 3 * Used by: reset-password.html, accept-invite.html 4 */ 5 6(function() { 7 'use strict'; 8 9 /** 10 * Password validation rules 11 */ 12 const PASSWORD_RULES = { 13 length: { test: (p) => p.length >= 8, label: '8+ characters' }, 14 upper: { test: (p) => /[A-Z]/.test(p), label: 'Uppercase' }, 15 lower: { test: (p) => /[a-z]/.test(p), label: 'Lowercase' }, 16 number: { test: (p) => /[0-9]/.test(p), label: 'Number' } 17 }; 18 19 /** 20 * Update a single requirement indicator 21 */ 22 function updateRequirement(req, met) { 23 const el = document.querySelector(`[data-req="${req}"]`); 24 if (!el) return; 25 26 const icon = el.querySelector('i'); 27 if (met) { 28 el.classList.add('met'); 29 if (icon) { 30 icon.classList.remove('fa-circle'); 31 icon.classList.add('fa-check'); 32 } 33 } else { 34 el.classList.remove('met'); 35 if (icon) { 36 icon.classList.remove('fa-check'); 37 icon.classList.add('fa-circle'); 38 } 39 } 40 } 41 42 /** 43 * Validate password against all rules 44 */ 45 function validatePassword(password) { 46 const results = {}; 47 for (const [key, rule] of Object.entries(PASSWORD_RULES)) { 48 results[key] = rule.test(password); 49 updateRequirement(key, results[key]); 50 } 51 return results; 52 } 53 54 /** 55 * Check if password meets all requirements 56 */ 57 function isPasswordValid(password) { 58 return Object.values(PASSWORD_RULES).every(rule => rule.test(password)); 59 } 60 61 /** 62 * Setup password toggle functionality 63 */ 64 function setupPasswordToggle() { 65 document.querySelectorAll('.toggle-password').forEach(button => { 66 // Prevent adding duplicate listeners 67 if (button.dataset.toggleSetup) return; 68 button.dataset.toggleSetup = 'true'; 69 70 button.addEventListener('click', function() { 71 const input = this.parentElement.querySelector('input'); 72 const icon = this.querySelector('i'); 73 74 if (input.type === 'password') { 75 input.type = 'text'; 76 icon.classList.remove('fa-eye'); 77 icon.classList.add('fa-eye-slash'); 78 } else { 79 input.type = 'password'; 80 icon.classList.remove('fa-eye-slash'); 81 icon.classList.add('fa-eye'); 82 } 83 }); 84 }); 85 } 86 87 /** 88 * Setup password validation on input 89 */ 90 function setupPasswordValidation(passwordId, confirmId, errorId) { 91 const passwordInput = document.getElementById(passwordId); 92 const confirmInput = document.getElementById(confirmId); 93 const errorEl = errorId ? document.getElementById(errorId) : null; 94 95 if (passwordInput) { 96 passwordInput.addEventListener('input', function() { 97 validatePassword(this.value); 98 }); 99 } 100 101 if (confirmInput && passwordInput) { 102 confirmInput.addEventListener('input', function() { 103 const password = passwordInput.value; 104 const confirm = this.value; 105 106 if (confirm && password !== confirm) { 107 if (errorEl) errorEl.classList.remove('hidden'); 108 confirmInput.classList.add('error'); 109 } else { 110 if (errorEl) errorEl.classList.add('hidden'); 111 confirmInput.classList.remove('error'); 112 } 113 }); 114 } 115 116 // Setup toggle buttons 117 setupPasswordToggle(); 118 } 119 120 /** 121 * Set button loading state 122 */ 123 function setButtonLoading(button, loading, text, iconClass) { 124 if (loading) { 125 button.disabled = true; 126 button.innerHTML = `<div class="loading-spinner"></div><span>${text || 'Loading...'}</span>`; 127 } else { 128 button.disabled = false; 129 const icon = iconClass ? `<i class="fas ${iconClass}"></i>` : ''; 130 button.innerHTML = `${icon}<span>${text || 'Submit'}</span>`;
131 } 132 } 133 134 /** 135 * Show error message 136 */ 137 function showError(container, message) { 138 const errorEl = container.querySelector('.auth-error') || createMessageEl(container, 'error'); 139 errorEl.querySelector('span').textContent = message; 140 errorEl.style.display = 'flex'; 141 142 const successEl = container.querySelector('.auth-success'); 143 if (successEl) successEl.style.display = 'none'; 144 } 145 146 /** 147 * Show success message 148 */ 149 function showSuccess(container, message) { 150 const successEl = container.querySelector('.auth-success') || createMessageEl(container, 'success'); 151 successEl.querySelector('span').textContent = message; 152 successEl.style.display = 'flex'; 153 154 const errorEl = container.querySelector('.auth-error'); 155 if (errorEl) errorEl.style.display = 'none'; 156 } 157 158 /** 159 * Create message element if it doesn't exist 160 */ 161 function createMessageEl(container, type) { 162 const el = document.createElement('div'); 163 el.className = `auth-${type}`; 164 el.innerHTML = ` 165 <i class="fas fa-${type === 'success' ? 'check-circle' : 'exclamation-circle'}"></i> 166 <span></span> 167 `; 168 el.style.display = 'none'; 169 170 const form = container.querySelector('form'); 171 if (form) { 172 form.parentNode.insertBefore(el, form); 173 } else { 174 container.appendChild(el); 175 } 176 177 return el; 178 } 179 180 // Export to window 181 window.AuthShared = { 182 PASSWORD_RULES, 183 validatePassword, 184 isPasswordValid, 185 setupPasswordValidation, 186 setupPasswordToggle, 187 setButtonLoading, 188 showError, 189 showSuccess 190 }; 191 192 // Auto-setup on DOMContentLoaded 193 document.addEventListener('DOMContentLoaded', function() { 194 setupPasswordToggle(); 195 }); 196})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.