diff --git a/src/app/[locale]/register/page.tsx b/src/app/[locale]/register/page.tsx index 6c1024c..997f2e7 100644 --- a/src/app/[locale]/register/page.tsx +++ b/src/app/[locale]/register/page.tsx @@ -6,37 +6,59 @@ import { useRouter } from "next/navigation"; import { Card } from "@/components/ui/card"; type FormState = "idle" | "submitting" | "success" | "error"; +type AccountType = "personal" | "company"; /** - * Slice 4 + Bug 9: a "Register as individual" toggle distinguishes - * personal accounts from company registrations. When the toggle is on: - * - the company name field is hidden (and not sent) - * - the server skips the duplicate-domain check - * - the ZITADEL org is named `personal-{8hex}` (opaque, collision-free) - * - the user's display name lives only on the user record; the GUI - * shows it instead of the opaque org name everywhere + * Registration entry — Bug 1 redesign. + * + * Previously a hidden checkbox ("Register as an individual") sat on top + * of the company-flavoured form, which buried personal accounts under a + * single click that most users miss. The new layout puts a primary + * account-type chooser at the top: two large cards, one for Personal, + * one for Company. Selection is required before the form below + * appears, so the rest of the layout adapts cleanly without a + * collapsing-checkbox feel. + * + * Bug 12: per-field validation runs on submit. The native HTML required + * attribute already blocks empty submits at the browser level; the + * server-side Zod schema in `/api/register` is the authoritative + * second line of defence. + * + * Behaviour: + * - "Personal account": company-name field is hidden; on submit, the + * server generates an opaque `personal-{8hex}` org name (Bug 9). + * - "Company account": company-name field is required; the server + * additionally runs the duplicate-domain check. + * - Returning users (those who arrive here by accident) can switch + * types after picking — the choice cards stay clickable above the + * form. Field state is preserved across switches so they don't + * have to re-type their name. */ export default function RegisterPage() { const t = useTranslations("register"); const tCommon = useTranslations("common"); const router = useRouter(); + const [accountType, setAccountType] = useState(null); + const [form, setForm] = useState({ companyName: "", givenName: "", familyName: "", email: "", }); - const [isPersonal, setIsPersonal] = useState(false); const [state, setState] = useState("idle"); const [error, setError] = useState(""); + const isPersonal = accountType === "personal"; + const handleChange = (e: React.ChangeEvent) => { setForm((prev) => ({ ...prev, [e.target.name]: e.target.value })); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + if (!accountType) return; // Should be impossible — submit button is gated setError(""); setState("submitting"); @@ -62,9 +84,6 @@ export default function RegisterPage() { if (!res.ok) { const data = await res.json(); - // Localize known structured codes; fall back to server-supplied - // English message for everything else (validation, ZITADEL errors, - // generic 500s). if (data.code === "duplicate_domain" && data.domain) { throw new Error(t("duplicateDomain", { domain: data.domain })); } @@ -120,120 +139,212 @@ export default function RegisterPage() {

{t("subtitle")}

- -
- {/* Personal-account toggle */} -