// SPDX-FileCopyrightText: 2026 Progmasoft // SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 "use client"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { type FormEvent, type MouseEvent, useId, useState } from "react"; import { getMessages, type Locale } from "@/lib/localization"; import { policyUrls } from "@/lib/policies"; type AuthMode = "login" | "register"; interface AuthFormProps { initialMessage?: string; locale: Locale; mode: AuthMode; } interface ProblemDetails { detail?: string; errors?: Record; title?: string; } function firstProblemMessage( problem: ProblemDetails, fallback: string, ): string { if (problem.detail) { return problem.detail; } const firstError = problem.errors ? Object.values(problem.errors).flat()[0] : undefined; return firstError ?? problem.title ?? fallback; } export function AuthForm({ initialMessage, locale, mode }: AuthFormProps) { const { auth } = getMessages(locale); const router = useRouter(); const formId = useId(); const [pending, setPending] = useState(false); const [message, setMessage] = useState(initialMessage ?? null); const [showPassword, setShowPassword] = useState(false); function continueWithGoogle(event: MouseEvent) { const form = event.currentTarget.form; const parameters = new URLSearchParams(); if (mode === "register") { const accountName = form?.elements.namedItem("accountName"); if (!(accountName instanceof HTMLInputElement)) { setMessage(auth.unreadableName); return; } if (!accountName.checkValidity()) { accountName.reportValidity(); return; } const acceptPolicies = form?.elements.namedItem("acceptPolicies"); if (!(acceptPolicies instanceof HTMLInputElement)) { setMessage(auth.consentRequired); return; } if (!acceptPolicies.checkValidity()) { acceptPolicies.reportValidity(); return; } parameters.set("accountName", accountName.value); parameters.set("acceptPolicies", "true"); } const query = parameters.size === 0 ? "" : `?${parameters.toString()}`; window.open(`/api/v1/accounts/oauth/google/start${query}`, "_self"); } async function submit(event: FormEvent) { event.preventDefault(); setMessage(null); const form = new FormData(event.currentTarget); const password = String(form.get("password") ?? ""); if ( mode === "register" && password !== String(form.get("confirmPassword") ?? "") ) { setMessage(auth.mismatch); return; } const body = mode === "register" ? { accountName: String(form.get("accountName") ?? ""), email: String(form.get("email") ?? ""), password, acceptPolicies: form.get("acceptPolicies") === "on", } : { email: String(form.get("email") ?? ""), password, }; setPending(true); try { const response = await fetch(`/api/v1/accounts/${mode}`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const result = (await response .json() .catch(() => ({}))) as ProblemDetails & { accountName?: string }; if (!response.ok) { setMessage(firstProblemMessage(result, auth.requestFailed)); return; } const accountName = result.accountName; if (!accountName) { setMessage(auth.missingName); return; } router.push(`/${encodeURIComponent(accountName)}/dashboard`); router.refresh(); } catch { setMessage(auth.unreachable); } finally { setPending(false); } } return (

{mode === "register" ? auth.createHeading : auth.loginHeading}

{mode === "register" ? auth.createDescription : auth.loginDescription}

{message && ( )} {mode === "register" && (
{auth.accountHint}
)}
{mode === "login" && ( {auth.forgotPassword} )}
{mode === "register" && {auth.passwordHint}}
{mode === "register" && (
)} {mode === "register" && (
)}

{mode === "register" ? auth.already : auth.newUser}{" "} {mode === "register" ? auth.signIn : auth.createLink}

); }