File size: 5,201 Bytes
173e683 c036803 173e683 c036803 173e683 c036803 173e683 c036803 173e683 c036803 173e683 c036803 173e683 c036803 173e683 c036803 173e683 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | /**
* 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 = ''; }
}
|