import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { TERMS_VERSION } from "~/lib/legal"; export default function RegisterPage() { const { t } = useTranslation("journal"); const [email, setEmail] = useState(""); const [username, setUsername] = useState(""); const [termsAccepted, setTermsAccepted] = useState(false); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const [supportsPasskey, setSupportsPasskey] = useState(null); const [magicLinkSent, setMagicLinkSent] = useState(false); const [hostname, setHostname] = useState("…"); useEffect(() => { setHostname(window.location.hostname); import("@simplewebauthn/browser").then(({ browserSupportsWebAuthn }) => { setSupportsPasskey(browserSupportsWebAuthn()); }); }, []); const handleRegisterPasskey = async (e: React.FormEvent) => { e.preventDefault(); if (!termsAccepted) { setError(t("auth.termsRequired")); return; } setError(null); setLoading(true); try { const startResp = await fetch("/api/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ step: "start", email, username, termsAccepted, termsVersion: TERMS_VERSION, }), }); const startData = await startResp.json(); if (startData.error) { setError(startData.error); setLoading(false); return; } const { startRegistration: startWebAuthn } = await import("@simplewebauthn/browser"); const webAuthnResp = await startWebAuthn(startData.options); const finishResp = await fetch("/api/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ step: "finish", email, username, termsAccepted, termsVersion: TERMS_VERSION, response: webAuthnResp, challenge: startData.options.challenge, userId: startData.userId, }), }); const finishData = await finishResp.json(); if (finishData.error) { setError(finishData.error); } else if (finishData.step === "done") { window.location.href = "/"; } } catch (err) { setError((err as Error).message); } finally { setLoading(false); } }; const handleRegisterMagicLink = async (e: React.FormEvent) => { e.preventDefault(); if (!termsAccepted) { setError(t("auth.termsRequired")); return; } setError(null); setLoading(true); try { const resp = await fetch("/api/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ step: "register-magic-link", email, username, termsAccepted, termsVersion: TERMS_VERSION, }), }); const result = await resp.json(); if (result.error) { setError(result.error); } else if (result.devLink) { window.location.href = result.devLink; } else { setMagicLinkSent(true); } } catch (err) { setError((err as Error).message); } finally { setLoading(false); } }; if (magicLinkSent) { return (

{t("auth.register")}

{t("auth.checkEmail")} {email}.

{t("auth.linkExpires")}

); } return (

{t("auth.register")}

{t("auth.registerDescription")}

setEmail(e.target.value)} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
setUsername(e.target.value.toLowerCase().replace(/[^a-z0-9_-]/g, ""))} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" placeholder="alice" />

{t("auth.handleWillBe", { handle: `@${username || "…"}@${hostname}`, })}

{error && (

{error}

)} {supportsPasskey ? ( ) : ( )} {supportsPasskey === false && (

{t("auth.passkeyNotSupportedRegister")}

)}

{t("auth.alreadyHaveAccount")}{" "} {t("auth.login")}

); }