Files
python-editor/src/static/register.html
Jimmy 9f28eabd2d Rename UI to LED Editor and improve mobile editor layout
Use "LED Editor" in page titles and the home heading. On narrow
viewports, make the file tree an off-canvas drawer with backdrop,
hamburger toggle, Escape to close, and auto-close after opening a
file. Add safe-area and tap-target tweaks, cache-bust static assets.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 03:16:35 +12:00

146 lines
5.1 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register — LED Editor</title>
<link rel="icon" href="data:,">
<style>
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(135deg, #0f172a, #1e293b);
color: #e2e8f0;
}
.card {
width: min(400px, 92vw);
background: rgba(15, 23, 42, 0.85);
border: 1px solid rgba(148, 163, 184, 0.25);
border-radius: 14px;
padding: 2rem;
}
h1 { margin: 0 0 1rem 0; font-size: 1.4rem; }
label { display: block; font-size: 0.85rem; color: #94a3b8; margin-bottom: 0.35rem; }
input {
width: 100%;
padding: 0.55rem 0.65rem;
border-radius: 8px;
border: 1px solid #64748b;
background: #0f172a;
color: #e2e8f0;
margin-bottom: 1rem;
}
button {
width: 100%;
padding: 0.65rem;
border: none;
border-radius: 8px;
background: #3b82f6;
color: #fff;
font-weight: 600;
cursor: pointer;
margin-bottom: 0.75rem;
}
button:disabled { opacity: 0.6; cursor: not-allowed; }
.err { color: #fca5a5; font-size: 0.9rem; margin-bottom: 0.75rem; min-height: 1.2em; }
.hint { font-size: 0.8rem; color: #94a3b8; margin-bottom: 1rem; }
a { color: #93c5fd; }
</style>
</head>
<body>
<main class="card">
<h1>Create account</h1>
<p id="invite-banner" class="hint hidden"></p>
<p class="hint">Username: letters, numbers, underscore (364). Password: at least 8 characters.</p>
<div id="err" class="err"></div>
<form id="form">
<label for="username">Username</label>
<input id="username" name="username" autocomplete="username" required />
<label for="password">Password</label>
<input id="password" type="password" name="password" autocomplete="new-password" required />
<input id="invite-token" type="hidden" name="invite_token" />
<button type="submit" id="submit">Register</button>
</form>
<p><a href="/login">Sign in</a> · <a href="/">Home</a></p>
</main>
<script>
const inviteTokenRaw = new URLSearchParams(window.location.search).get("invite") || "";
const inviteToken = inviteTokenRaw.trim();
const inviteInput = document.getElementById("invite-token");
if (inviteInput) inviteInput.value = inviteToken;
if (inviteToken) {
const banner = document.getElementById("invite-banner");
if (banner) {
banner.classList.remove("hidden");
banner.textContent =
"You're using an invite link. Choose a username and password — the link works for one signup only.";
}
}
function formatApiDetail(body) {
if (!body || body.detail === undefined || body.detail === null) return "";
const d = body.detail;
if (typeof d === "string") return d;
if (Array.isArray(d))
return d
.map((item) =>
typeof item === "object" && item && item.msg ? String(item.msg) : JSON.stringify(item)
)
.join(" ");
return String(d);
}
(async function checkStatus() {
try {
const r = await fetch("/api/auth/status");
const s = await r.json();
if (!s.auth_enabled) {
document.getElementById("err").textContent = "Registration is disabled (AUTH_ENABLED is not set).";
document.getElementById("form").style.display = "none";
} else if (!s.register_open && !inviteToken) {
document.getElementById("err").textContent = "Public registration is closed. Ask an administrator.";
document.getElementById("form").style.display = "none";
} else if (s.invite_required && !inviteToken) {
document.getElementById("err").textContent = "This server requires an invite link to register.";
document.getElementById("form").style.display = "none";
}
} catch (_e) {}
})();
document.getElementById("form").addEventListener("submit", async (e) => {
e.preventDefault();
const err = document.getElementById("err");
const btn = document.getElementById("submit");
err.textContent = "";
btn.disabled = true;
try {
const body = {
username: document.getElementById("username").value.trim(),
password: document.getElementById("password").value,
invite_token: inviteToken ? inviteToken : null,
};
const res = await fetch("/api/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!res.ok) {
const j = await res.json().catch(() => ({}));
err.textContent = formatApiDetail(j) || res.statusText;
return;
}
window.location.href = "/login?next=/editor";
} catch (ex) {
err.textContent = String(ex.message || ex);
} finally {
btn.disabled = false;
}
});
</script>
</body>
</html>