165 lines
4.9 KiB
TypeScript
165 lines
4.9 KiB
TypeScript
import { Layout } from "./Layout.tsx";
|
|
|
|
export const RegisterPage = (
|
|
{ initialCode = "" }: { initialCode?: string },
|
|
) => {
|
|
return (
|
|
<Layout title="Register">
|
|
<div style={{ textAlign: "center" }}>
|
|
<h1 style={{ marginBottom: "0.5rem" }}>Create Account</h1>
|
|
<p
|
|
style={{ color: "#666", marginBottom: "1.5rem", fontSize: "0.95rem" }}
|
|
>
|
|
Register a secure hardware token or passkey using your invite code.
|
|
</p>
|
|
|
|
<div style={{ textAlign: "left", marginBottom: "1rem" }}>
|
|
<label
|
|
for="username"
|
|
style={{
|
|
display: "block",
|
|
marginBottom: "0.25rem",
|
|
fontWeight: "bold",
|
|
}}
|
|
>
|
|
Username
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="username"
|
|
placeholder="e.g. pilot_alice"
|
|
required
|
|
autofocus={!initialCode}
|
|
/>
|
|
</div>
|
|
|
|
<div style={{ textAlign: "left", marginBottom: "1.5rem" }}>
|
|
<label
|
|
for="inviteCode"
|
|
style={{
|
|
display: "block",
|
|
marginBottom: "0.25rem",
|
|
fontWeight: "bold",
|
|
}}
|
|
>
|
|
Invite Code
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="inviteCode"
|
|
placeholder="Invite Code (e.g. 00000000-... or custom code)"
|
|
value={initialCode}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
id="instructionBox"
|
|
style={{
|
|
background: "#e8f4fd",
|
|
padding: "0.75rem",
|
|
borderRadius: "6px",
|
|
marginBottom: "1.5rem",
|
|
fontSize: "0.85rem",
|
|
color: "#0c5460",
|
|
border: "1px solid #bee5eb",
|
|
textAlign: "left",
|
|
}}
|
|
>
|
|
<strong>Tip:</strong>{" "}
|
|
You can use your phone (via QR code / Bluetooth), biometric sensor
|
|
(Touch ID, Windows Hello), password manager (1Password, Bitwarden,
|
|
Chrome), or USB security key (YubiKey).
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
id="registerBtn"
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
gap: "0.5rem",
|
|
fontWeight: "bold",
|
|
background: "#28a745",
|
|
}}
|
|
>
|
|
<svg
|
|
width="20"
|
|
height="20"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
>
|
|
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
|
|
</svg>
|
|
Register Passkey
|
|
</button>
|
|
|
|
<div
|
|
id="loadingIndicator"
|
|
style={{
|
|
display: "none",
|
|
marginTop: "1rem",
|
|
color: "#28a745",
|
|
fontSize: "0.9rem",
|
|
}}
|
|
>
|
|
Setting up passkey... Follow the prompt on your device.
|
|
</div>
|
|
|
|
<div id="statusMessage" class="error" style={{ marginTop: "1rem" }}>
|
|
</div>
|
|
|
|
<div class="links" style={{ marginTop: "2rem" }}>
|
|
Already have an account? <a href="/login">Login here</a>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/public/auth-client.js?v=2"></script>
|
|
<script
|
|
dangerouslySetInnerHTML={{
|
|
__html: `
|
|
// Auto-populate invite code from URL if present
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const codeParam = urlParams.get('code');
|
|
if (codeParam) {
|
|
const input = document.getElementById('inviteCode');
|
|
if (input) {
|
|
input.value = codeParam;
|
|
document.getElementById('username').focus();
|
|
}
|
|
}
|
|
|
|
document.getElementById('registerBtn').addEventListener('click', async () => {
|
|
const username = document.getElementById('username').value.trim();
|
|
const inviteCode = document.getElementById('inviteCode').value.trim();
|
|
|
|
if (!username || !inviteCode) {
|
|
document.getElementById('statusMessage').textContent = "Username and Invite Code are required.";
|
|
document.getElementById('statusMessage').className = "error";
|
|
return;
|
|
}
|
|
|
|
document.getElementById('loadingIndicator').style.display = 'block';
|
|
document.getElementById('registerBtn').disabled = true;
|
|
document.getElementById('statusMessage').textContent = '';
|
|
|
|
try {
|
|
await startWebAuthnRegistration(username, inviteCode);
|
|
} finally {
|
|
document.getElementById('loadingIndicator').style.display = 'none';
|
|
document.getElementById('registerBtn').disabled = false;
|
|
}
|
|
});
|
|
`,
|
|
}}
|
|
>
|
|
</script>
|
|
</Layout>
|
|
);
|
|
};
|