Initialize i18n via initI18n() in both root.tsx files and replace all hardcoded user-facing strings with useTranslation() calls. Adds missing translation keys for planner (profiles, connection states, save flow) and journal (auth pages, route management, passkey prompts). Both English and German translations are complete. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
123 lines
4 KiB
TypeScript
123 lines
4 KiB
TypeScript
import { useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
export default function RegisterPage() {
|
|
const { t } = useTranslation("journal");
|
|
const [email, setEmail] = useState("");
|
|
const [username, setUsername] = useState("");
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const handleRegister = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setError(null);
|
|
setLoading(true);
|
|
|
|
try {
|
|
// Step 1: Get registration options from server
|
|
const startResp = await fetch("/api/auth/register", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ step: "start", email, username }),
|
|
});
|
|
const startData = await startResp.json();
|
|
|
|
if (startData.error) {
|
|
setError(startData.error);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
// Step 2: Create passkey via browser
|
|
const { startRegistration: startWebAuthn } = await import("@simplewebauthn/browser");
|
|
const webAuthnResp = await startWebAuthn(startData.options);
|
|
|
|
// Step 3: Send response to server
|
|
const finishResp = await fetch("/api/auth/register", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
step: "finish",
|
|
email,
|
|
username,
|
|
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);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="mx-auto max-w-md px-4 py-16">
|
|
<h1 className="text-2xl font-bold text-gray-900">{t("auth.register")}</h1>
|
|
<p className="mt-2 text-sm text-gray-600">
|
|
{t("auth.registerDescription")}
|
|
</p>
|
|
|
|
<form onSubmit={handleRegister} className="mt-8 space-y-4">
|
|
<div>
|
|
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
|
|
{t("auth.email")}
|
|
</label>
|
|
<input
|
|
id="email"
|
|
type="email"
|
|
required
|
|
value={email}
|
|
onChange={(e) => 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"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label htmlFor="username" className="block text-sm font-medium text-gray-700">
|
|
{t("auth.username")}
|
|
</label>
|
|
<input
|
|
id="username"
|
|
type="text"
|
|
required
|
|
value={username}
|
|
onChange={(e) => 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"
|
|
/>
|
|
<p className="mt-1 text-xs text-gray-500">
|
|
Your handle will be @{username || "..."}@{typeof window !== "undefined" ? window.location.hostname : "trails.cool"}
|
|
</p>
|
|
</div>
|
|
|
|
{error && (
|
|
<p className="text-sm text-red-600">{error}</p>
|
|
)}
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={loading}
|
|
className="w-full rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
|
|
>
|
|
{loading ? t("auth.creatingPasskey") : t("auth.registerWithPasskey")}
|
|
</button>
|
|
</form>
|
|
|
|
<p className="mt-6 text-center text-sm text-gray-500">
|
|
Already have an account?{" "}
|
|
<a href="/auth/login" className="text-blue-600 hover:underline">
|
|
{t("auth.login")}
|
|
</a>
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|