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 = ''; }
}