// Login and registration. import { api, keyStore } from './api.js'; import { h, clear, download, toast } from './dom.js'; export function authView({ onLogin }) { let mode = 'login'; const host = h('section', { class: 'auth' }); const brand = h('header', { class: 'brand' }, h('p', { class: 'kicker' }, 'HALCYON INSTRUMENT & CONTROL'), h('h1', { class: 'logo' }, 'FLIGHT CONTROL'), h('p', { class: 'tagline' }, 'BELT UPLINK TERMINAL // MODEL HC-33'), ); const panel = h('div', { class: 'panel auth-panel' }); host.append(brand, panel); function tabs() { const tab = (id, label) => h('button', { class: `tab ${mode === id ? 'on' : ''}`, type: 'button', role: 'tab', 'aria-selected': mode === id ? 'true' : 'false', onclick: () => { mode = id; render(); }, }, label); return h('div', { class: 'tabs auth-tabs', role: 'tablist' }, tab('login', 'LOGIN'), tab('register', 'NEW ACCOUNT')); } function render() { clear(panel).append(tabs(), mode === 'login' ? loginForm() : registerForm()); panel.querySelector('input')?.focus(); } function loginForm() { const err = h('p', { class: 'form-error', role: 'alert' }); const key = h('input', { id: 'key', type: 'password', autocomplete: 'current-password', required: true, spellcheck: 'false' }); const show = h('input', { type: 'checkbox', id: 'show', onchange: (e) => { key.type = e.target.checked ? 'text' : 'password'; } }); const btn = h('button', { class: 'btn primary wide', type: 'submit' }, 'ENGAGE LINK'); return h('form', { class: 'stack', onsubmit: async (e) => { e.preventDefault(); const k = key.value.trim(); if (!k) return; btn.disabled = true; err.textContent = ''; try { await api.me(k); keyStore.set(k); onLogin(); } catch (ex) { err.textContent = ex.status === 401 ? 'ACCESS DENIED: KEY NOT RECOGNISED' : `ERROR: ${ex.message}`; btn.disabled = false; } }, }, h('label', { for: 'key' }, 'ACCESS KEY'), key, h('label', { class: 'check', for: 'show' }, show, 'SHOW KEY'), err, btn, h('p', { class: 'hint' }, 'Your access key was shown once, when you registered. There is no password: the key is the login.'), ); } function registerForm() { const err = h('p', { class: 'form-error', role: 'alert' }); const name = h('input', { id: 'name', type: 'text', maxlength: '40', required: true, autocomplete: 'username', spellcheck: 'false' }); const btn = h('button', { class: 'btn primary wide', type: 'submit' }, 'REGISTER'); return h('form', { class: 'stack', onsubmit: async (e) => { e.preventDefault(); const callsign = name.value.trim(); if (!callsign) { err.textContent = 'ENTER A CALLSIGN'; return; } btn.disabled = true; err.textContent = ''; try { const res = await api.register(callsign); showKey(callsign, res); } catch (ex) { err.textContent = ex.status === 409 ? 'CALLSIGN ALREADY IN USE' : `ERROR: ${ex.message}`; btn.disabled = false; } }, }, h('label', { for: 'name' }, 'CALLSIGN'), name, h('p', { class: 'hint' }, '1 to 40 characters. You will be issued one command ship and a secret access key.'), err, btn, ); } function showKey(callsign, res) { const enter = h('button', { class: 'btn primary wide', type: 'button', disabled: true }, 'ENTER FLIGHT CONTROL'); const ack = h('input', { type: 'checkbox', id: 'ack', onchange: (e) => { enter.disabled = !e.target.checked; } }); enter.addEventListener('click', () => { keyStore.set(res.api_key); onLogin(); }); clear(panel).append( h('div', { class: 'panel-title' }, 'ACCOUNT CREATED'), h('div', { class: 'stack keyreveal' }, h('p', null, `WELCOME, ${callsign.toUpperCase()}. COMMAND SHIP #${res.ship_id} IS WAITING ON THE DOCK.`), h('p', { class: 'warn' }, 'THIS IS YOUR ACCESS KEY. IT IS SHOWN ONCE AND CANNOT BE RECOVERED.'), h('code', { class: 'keybox', tabindex: '0' }, res.api_key), h('div', { class: 'row' }, h('button', { class: 'btn', type: 'button', onclick: async () => { try { await navigator.clipboard.writeText(res.api_key); toast('KEY COPIED TO CLIPBOARD'); } catch { toast('COPY FAILED: SELECT THE KEY AND COPY IT BY HAND', 'err'); } } }, 'COPY'), h('button', { class: 'btn', type: 'button', onclick: () => download(`halcyon-key-${callsign}.txt`, `${res.api_key}\n`, 'text/plain') }, 'SAVE AS FILE'), ), h('label', { class: 'check', for: 'ack' }, ack, 'I HAVE STORED MY KEY SAFELY'), enter, ), ); } render(); return host; }