/** * AuthUI — wires the login/register form. */ import { Auth } from '../auth/Auth.js'; export class AuthUI { constructor({ onEnter, adminOnly = false } = {}) { this.onEnter = onEnter; this.adminOnly = adminOnly; this.mode = 'login'; this._bind(); if (this.adminOnly) this._applyAdminMode(); this.setMode('login'); } /** Hide the Register tab, Guest button, and tighten the wording for /admin. */ _applyAdminMode() { const tabReg = this.$('tabRegister'); if (tabReg) tabReg.style.display = 'none'; const tabLogin = this.$('tabLogin'); if (tabLogin) tabLogin.textContent = 'Admin sign-in'; const guestRow = document.querySelector('.auth-card .guest'); if (guestRow) guestRow.style.display = 'none'; // Tighten the auth card heading. const h2 = document.querySelector('.auth-card h2'); if (h2) h2.textContent = 'ECHO · Admin'; const sub = this.$('authSub'); if (sub) sub.textContent = 'Restricted area. Admin credentials required.'; } $(id) { return document.getElementById(id); } _bind() { this.$('tabLogin').addEventListener('click', () => this.setMode('login')); this.$('tabRegister').addEventListener('click', () => this.setMode('register')); // Form submit (Enter key + button[type=submit]) this.$('authForm').addEventListener('submit', e => this._submit(e)); // Belt-and-braces: also handle direct button click in case the form's submit event is swallowed this.$('authBtn').addEventListener('click', e => { e.preventDefault(); this._submit(e); }); this.$('forgotBtn').addEventListener('click', () => this._forgot()); this.$('guestBtn').addEventListener('click', () => { Auth.logout().catch(()=>{});this.hide(); this.onEnter?.(true); }); this.$('signOutBtn').addEventListener('click', () => { Auth.logout().catch(()=>{});location.reload(); }); console.log('[Auth] UI bound successfully'); } setMode(m) { // In adminOnly mode, ignore register and force login. if (this.adminOnly) m = 'login'; this.mode = m; this.$('tabLogin').classList.toggle('active', m === 'login'); this.$('tabRegister').classList.toggle('active', m === 'register'); this.$('fNameRow').style.display = m === 'register' ? 'block' : 'none'; this.$('fPwd2Row').style.display = m === 'register' ? 'block' : 'none'; this.$('authBtn').textContent = m === 'login' ? 'Login' : 'Create account'; if (!this.adminOnly) { this.$('authSub').textContent = m === 'login' ? 'Welcome back — sign in to resume.' : 'Create an account — track stars, climb the leaderboard.'; } this.$('forgotBtn').style.display = (m === 'login' && !this.adminOnly) ? 'inline' : 'none'; this.$('fErr').textContent = ''; } async _submit(e) { if (e?.preventDefault) e.preventDefault(); const email = this.$('fEmail').value; const pwd = this.$('fPwd').value; const pwd2 = this.$('fPwd2').value; const name = this.$('fName').value; console.log(`[Auth] ${this.mode} submit: email="${email}" pwd.len=${pwd.length}`); if (!email) { this.$('fErr').style.color = 'var(--coral)'; this.$('fErr').textContent = 'Email is required.'; return; } if (!pwd) { this.$('fErr').style.color = 'var(--coral)'; this.$('fErr').textContent = 'Password is required.'; return; } try { let user; if (this.mode === 'register') { if (this.adminOnly) throw new Error('Registration is closed on /admin'); if (pwd !== pwd2) throw new Error('Passwords do not match'); user = await Auth.register(email, pwd, name); console.log('[Auth] registered:', user.email); } else { user = await Auth.login(email, pwd); console.log('[Auth] logged in:', user.email); } // /admin route: reject non-admin logins immediately. if (this.adminOnly && !user.isAdmin) { await Auth.logout().catch(() => {}); throw new Error('Admin access only.'); } this.$('fErr').style.color = 'var(--spring)'; this.$('fErr').textContent = `✓ Welcome, ${user.name}!`; setTimeout(() => { this.hide(); this.onEnter?.(false); }, 350); } catch (err) { console.warn('[Auth] error:', err.message); this.$('fErr').style.color = 'var(--coral)'; this.$('fErr').textContent = err.message; } } _forgot() { const email = this.$('fEmail').value.trim(); if (!email) { this.$('fErr').textContent = 'Type your email above, then click Forgot password.'; return; } const np = prompt(`Enter a new password (6+ chars) for ${email}:`); if (!np) return; try { Auth.resetPwd(email, np); this.$('fErr').style.color = 'var(--spring)'; this.$('fErr').textContent = 'Password reset — login with the new one.'; } catch (err) { this.$('fErr').style.color = 'var(--coral)'; this.$('fErr').textContent = err.message; } } show() { this.$('authScreen').classList.remove('hidden'); this.$('appWrap').style.display = 'none'; } hide() { this.$('authScreen').classList.add('hidden'); this.$('appWrap').style.display = ''; } }