/* ========================================================================== XENEX GLASS OS — Login Screen Behavior assets/JS/login.js This file handles UI behavior only: password visibility and client-side form validation. It does NOT authenticate anyone. There is no backend yet, so submitting the form cannot and does not log a user in — it only reports that sign-in isn't wired up yet. See the comment above handleSubmit() for where the real API call goes once it exists. ========================================================================== */ document.addEventListener('DOMContentLoaded', () => { initPasswordToggle(); initFormValidation(); }); /* ---- Password visibility toggle ---- */ function initPasswordToggle() { const toggleButton = document.querySelector('[data-password-toggle]'); const passwordInput = document.getElementById('password'); if (!toggleButton || !passwordInput) return; toggleButton.addEventListener('click', () => { const isHidden = passwordInput.type === 'password'; passwordInput.type = isHidden ? 'text' : 'password'; toggleButton.setAttribute('aria-pressed', String(isHidden)); toggleButton.setAttribute('aria-label', isHidden ? 'Hide password' : 'Show password'); const toggleText = toggleButton.querySelector('.login-field__toggle-text'); if (toggleText) { toggleText.textContent = isHidden ? 'Hide' : 'Show'; } }); } /* ---- Form validation + submit ---- */ function initFormValidation() { const form = document.querySelector('.login-form'); const emailInput = document.getElementById('email'); const passwordInput = document.getElementById('password'); if (!form || !emailInput || !passwordInput) return; const statusRegion = createStatusRegion(form); form.addEventListener('submit', (event) => { event.preventDefault(); clearFieldErrors(form); setStatus(statusRegion, '', null); const errors = validateFields(emailInput, passwordInput); if (errors.length > 0) { errors.forEach(({ input, message }) => showFieldError(input, message)); errors[0].input.focus(); return; } handleSubmit({ email: emailInput.value.trim() }); }); // Clear a field's error state as soon as the person edits it. [emailInput, passwordInput].forEach((input) => { input.addEventListener('input', () => clearFieldError(input)); }); } function validateFields(emailInput, passwordInput) { const errors = []; const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (emailInput.value.trim() === '') { errors.push({ input: emailInput, message: 'Enter your email address.' }); } else if (!emailPattern.test(emailInput.value.trim())) { errors.push({ input: emailInput, message: 'Enter a valid email address.' }); } if (passwordInput.value === '') { errors.push({ input: passwordInput, message: 'Enter your password.' }); } return errors; } /* * There is no backend yet. When one exists, this is where the real * request goes, e.g.: * * const response = await fetch('/auth/login', { * method: 'POST', * headers: { 'Content-Type': 'application/json' }, * body: JSON.stringify({ email, password }), * credentials: 'include' // secure HttpOnly session cookie, not a token in JS * }); * * Until that endpoint exists, do not simulate a response. Tell the person * plainly that sign-in isn't connected yet. */ function handleSubmit({ email }) { const form = document.querySelector('.login-form'); const statusRegion = form.querySelector('[data-login-status]'); const submitButton = document.querySelector('[data-login-submit]'); submitButton.disabled = true; setStatus( statusRegion, "Sign-in isn't connected to a server yet. This form will work once the Xenex backend is live.", 'info' ); console.info('[Xenex] Login form submitted with no backend configured.', { email }); submitButton.disabled = false; } /* ---- Field error helpers ---- */ function showFieldError(input, message) { const field = input.closest('.login-field'); if (!field) return; field.classList.add('login-field--error'); input.setAttribute('aria-invalid', 'true'); let errorEl = field.querySelector('.login-field__error'); if (!errorEl) { errorEl = document.createElement('p'); errorEl.className = 'login-field__error'; errorEl.id = `${input.id}-error`; field.appendChild(errorEl); } errorEl.textContent = message; input.setAttribute('aria-describedby', errorEl.id); } function clearFieldError(input) { const field = input.closest('.login-field'); if (!field) return; field.classList.remove('login-field--error'); input.removeAttribute('aria-invalid'); input.removeAttribute('aria-describedby'); const errorEl = field.querySelector('.login-field__error'); if (errorEl) errorEl.remove(); } function clearFieldErrors(form) { form.querySelectorAll('.login-field__input').forEach(clearFieldError); } /* ---- Status region (created once, announced to screen readers) ---- */ function createStatusRegion(form) { let region = form.querySelector('[data-login-status]'); if (region) return region; region = document.createElement('p'); region.setAttribute('data-login-status', ''); region.className = 'login-status'; region.setAttribute('role', 'status'); region.setAttribute('aria-live', 'polite'); form.appendChild(region); return region; } function setStatus(region, message, kind) { if (!region) return; region.textContent = message; region.classList.toggle('login-status--info', kind === 'info'); region.classList.toggle('login-status--error', kind === 'error'); }