// Login view — Mullvad-style account tokens (no username/password). // Two panels: create a new account (one-time token reveal) or log in with an // existing account number. import { api } from '../api.js'; import { el, mount, toast } from '../dom.js'; import { groupCode, normalizeCode } from '../format.js'; export function renderAuth(container, ctx) { let mode = 'create'; // 'create' | 'login' function draw() { const card = el( 'div', { class: 'auth-card card' }, el( 'div', { class: 'auth-head' }, el('div', { class: 'brand-mark brand-mark-lg' }, '🧭'), el('h1', {}, 'Trip Plan'), el('p', { class: 'muted' }, 'Plan trips together, day by day.'), ), el( 'div', { class: 'auth-tabs' }, tab('Create account', mode === 'create', () => setMode('create')), tab('Log in', mode === 'login', () => setMode('login')), ), mode === 'create' ? createPanel() : loginPanel(), ); mount(container, el('div', { class: 'auth-wrap' }, card)); } function setMode(next) { if (mode !== next) { mode = next; draw(); } } function tab(label, active, onClick) { return el('button', { class: `auth-tab${active ? ' active' : ''}`, type: 'button', onClick }, label); } // ----- Create account ----- function createPanel() { const errorEl = el('p', { class: 'form-error' }); const createBtn = el('button', { class: 'btn btn-primary btn-block', type: 'button' }, 'Create account'); async function onCreate() { errorEl.textContent = ''; createBtn.disabled = true; createBtn.textContent = 'Creating…'; try { const data = await api.auth.createAccount(); showToken(data.token, data.user); } catch (err) { errorEl.textContent = err.message; createBtn.disabled = false; createBtn.textContent = 'Create account'; } } createBtn.addEventListener('click', onCreate); return el( 'div', { class: 'auth-panel' }, el('p', { class: 'auth-lead' }, 'No email, no password. We generate a private account number — it is your only key to your trips.'), errorEl, createBtn, ); } function showToken(token, user) { const grouped = groupCode(token, 4); const copyBtn = el('button', { class: 'btn', type: 'button' }, '📋 Copy'); copyBtn.addEventListener('click', async () => { try { await navigator.clipboard.writeText(grouped); toast('Account number copied', 'success'); } catch { toast('Copy failed — select and copy it manually'); } }); const panel = el( 'div', { class: 'auth-panel token-reveal' }, el('div', { class: 'token-warning' }, el('strong', {}, 'This is your only credential.'), ' Save it now — it will never be shown again. Anyone with it can access your trips.'), el('div', { class: 'token-box' }, grouped), el('div', { class: 'token-actions' }, copyBtn), el( 'button', { class: 'btn btn-primary btn-block', type: 'button', onClick: () => { ctx.state.user = user; ctx.navigate('#/trips'); }, }, "I've saved it — continue", ), ); mount(container, el('div', { class: 'auth-wrap' }, el('div', { class: 'auth-card card' }, el('div', { class: 'auth-head' }, el('div', { class: 'brand-mark brand-mark-lg' }, '🎉'), el('h1', {}, 'Account created'), el('p', { class: 'muted' }, `You're ${user.display_name}. You can rename yourself later.`)), panel, ))); } // ----- Log in ----- function loginPanel() { const errorEl = el('p', { class: 'form-error' }); const input = el('input', { class: 'input token-input', type: 'text', autocomplete: 'off', autocapitalize: 'characters', spellcheck: 'false', placeholder: 'XXXX-XXXX-XXXX-XXXX', 'aria-label': 'Account number', }); const submitBtn = el('button', { class: 'btn btn-primary btn-block', type: 'submit' }, 'Log in'); async function onSubmit(e) { e.preventDefault(); errorEl.textContent = ''; const token = normalizeCode(input.value); if (token.length < 8) { errorEl.textContent = 'Enter your full account number.'; return; } submitBtn.disabled = true; submitBtn.textContent = 'Signing in…'; try { const data = await api.auth.login(token); ctx.state.user = data.user; ctx.navigate('#/trips'); } catch (err) { errorEl.textContent = err.message; submitBtn.disabled = false; submitBtn.textContent = 'Log in'; } } return el( 'form', { class: 'auth-panel auth-form', onSubmit }, el('label', { class: 'field' }, el('span', { class: 'field-label' }, 'Account number'), input), el('p', { class: 'hint muted' }, 'Dashes, spaces and letter case do not matter.'), errorEl, submitBtn, ); } draw(); }