annator-atom / frontend /login.html
techprotrade's picture
Fix post-login redirect to ATOM hub (login.html)
66f0de1 verified
Raw
History Blame Contribute Delete
6.41 kB
<!DOCTYPE html>
<html lang="et">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ATOM · Login</title>
<style>
:root { --green:#059669; --bg:#f8fafc; --card:#fff; --text:#0f172a; --muted:#64748b; --err:#dc3545; }
* { box-sizing:border-box; }
body { margin:0; min-height:100vh; font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
background:linear-gradient(145deg,#ecfdf5 0%,#f0f9ff 50%,#f8fafc 100%); color:var(--text);
display:flex; align-items:center; justify-content:center; padding:1rem; }
.card { width:100%; max-width:420px; background:var(--card); border-radius:16px; padding:2rem;
box-shadow:0 20px 50px rgba(15,23,42,.08); border:1px solid #e2e8f0; }
h1 { margin:0 0 .25rem; font-size:1.75rem; color:var(--green); }
p.sub { margin:0 0 1.5rem; color:var(--muted); }
label { display:block; font-size:.875rem; font-weight:600; margin:.75rem 0 .35rem; }
input { width:100%; padding:.7rem .85rem; border:1px solid #cbd5e1; border-radius:10px; font-size:1rem; }
input:focus { outline:2px solid #6ee7b7; border-color:var(--green); }
button { width:100%; margin-top:1.25rem; padding:.85rem; border:0; border-radius:10px;
background:var(--green); color:#fff; font-weight:700; font-size:1rem; cursor:pointer; min-height:44px; }
button:disabled { opacity:.6; cursor:not-allowed; }
button.secondary { background:transparent; color:var(--green); border:1px solid var(--green); margin-top:.75rem; }
.err { background:#fef2f2; color:var(--err); border:1px solid #fecaca; padding:.75rem; border-radius:10px;
margin-bottom:1rem; font-size:.9rem; display:none; }
.ok { background:#ecfdf5; color:#065f46; border:1px solid #a7f3d0; padding:.75rem; border-radius:10px;
margin-bottom:1rem; font-size:.9rem; display:none; }
.row { display:grid; grid-template-columns:1fr 1fr; gap:.75rem; }
.hint { margin-top:1rem; font-size:.8rem; color:var(--muted); text-align:center; }
a { color:var(--green); }
</style>
</head>
<body>
<div class="card">
<h1>ATOM</h1>
<p class="sub" id="subtitle">Logi sisse oma kontoga</p>
<div class="err" id="err" role="alert"></div>
<div class="ok" id="ok" role="status"></div>
<form id="form" novalidate>
<div id="names" class="row" style="display:none">
<div>
<label for="first_name">Eesnimi</label>
<input id="first_name" name="first_name" autocomplete="given-name" />
</div>
<div>
<label for="last_name">Perekonnanimi</label>
<input id="last_name" name="last_name" autocomplete="family-name" />
</div>
</div>
<label for="email">Email</label>
<input id="email" name="email" type="email" required autocomplete="username" placeholder="sina@email.ee" />
<label for="password">Parool</label>
<input id="password" name="password" type="password" required autocomplete="current-password" minlength="6" />
<button type="submit" id="submit">Logi sisse</button>
<button type="button" class="secondary" id="toggle">Loo uus konto</button>
</form>
<p class="hint">API: <a href="/docs">/docs</a> · Health: <a href="/api/healthz">/api/healthz</a> · <a href="/">Avaleht</a></p>
</div>
<script>
const API = ''; // same origin on HF Space
let mode = 'login'; // or register
const err = document.getElementById('err');
const ok = document.getElementById('ok');
const names = document.getElementById('names');
const subtitle = document.getElementById('subtitle');
const submit = document.getElementById('submit');
const toggle = document.getElementById('toggle');
function showErr(msg) { err.style.display='block'; ok.style.display='none'; err.textContent=msg; }
function showOk(msg) { ok.style.display='block'; err.style.display='none'; ok.textContent=msg; }
function clearMsg() { err.style.display='none'; ok.style.display='none'; }
function formatDetail(data) {
if (!data) return 'Viga';
const d = data.detail ?? data.message ?? data.error;
if (typeof d === 'string') return d;
if (Array.isArray(d)) return d.map(x => x.msg || JSON.stringify(x)).join('; ');
return JSON.stringify(d);
}
toggle.onclick = () => {
mode = mode === 'login' ? 'register' : 'login';
clearMsg();
names.style.display = mode === 'register' ? 'grid' : 'none';
subtitle.textContent = mode === 'login' ? 'Logi sisse oma kontoga' : 'Loo uus ATOM konto';
submit.textContent = mode === 'login' ? 'Logi sisse' : 'Registreeri';
toggle.textContent = mode === 'login' ? 'Loo uus konto' : 'Tagasi sisselogimisele';
};
document.getElementById('form').onsubmit = async (e) => {
e.preventDefault();
clearMsg();
submit.disabled = true;
const email = document.getElementById('email').value.trim();
const password = document.getElementById('password').value;
try {
let res, body;
if (mode === 'login') {
res = await fetch(API + '/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: email, password })
});
} else {
body = {
email, password,
first_name: document.getElementById('first_name').value.trim() || 'User',
last_name: document.getElementById('last_name').value.trim() || 'Atom'
};
res = await fetch(API + '/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
}
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(formatDetail(data) || ('HTTP ' + res.status));
if (!data.access_token) throw new Error('Token puudub vastuses');
localStorage.setItem('token', data.access_token);
localStorage.setItem('auth_token', data.access_token);
showOk('Sisselogimine õnnestus — suunan ATOM Command Centerisse…');
setTimeout(() => { location.href = '/atom.html'; }, 600);
} catch (ex) {
showErr(ex.message || String(ex));
} finally {
submit.disabled = false;
}
};
</script>
</body>
</html>