init
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user