"use client"; import Link from "next/link"; import { useActionState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; /** * Generische Benutzer-Formulare für Popups (Anlegen/Bearbeiten). Die konkreten * Server-Actions werden als (bereits gebundene) Props übergeben — dieselben * Komponenten dienen der Plattform-Admin- und der Mandanten-Admin-Verwaltung. * Die Zustandstypen sind in beiden Action-Dateien strukturgleich. */ export type CreateResult = | { status: "idle" } | { status: "error"; message: string; values?: { name: string; email: string; roleIds: string[] } } | { status: "done"; email: string; generatedPassword: string | null; invited: boolean }; export type EditResult = { status: "idle" } | { status: "error"; message: string } | { status: "ok" }; export interface RoleOption { id: string; name: string } type CreateAction = (prev: CreateResult, formData: FormData) => Promise; type EditAction = (prev: EditResult, formData: FormData) => Promise; type PlainAction = (formData: FormData) => void | Promise; function RoleChecks({ roles, selected, idPrefix }: { roles: RoleOption[]; selected: Set; idPrefix: string }) { return (
{roles.map((r) => ( ))}
); } export function UserCreateForm({ action, roles, closeHref }: { action: CreateAction; roles: RoleOption[]; closeHref: string }) { const [state, formAction, pending] = useActionState(action, { status: "idle" } as CreateResult); if (state.status === "done") { return (

Nutzer {state.email} angelegt.

{/* Option C (WS3): Anlage nur per Einladung; neutrale Rückmeldung (kein Cross-Tenant-Leak, ob die Person schon ein Konto hatte). */}

Einladung an {state.email} gesendet. Die Person richtet ihr Konto selbst über den Link ein bzw. sieht den Mandanten künftig in ihrer Auswahl.

); } // Bei Fehler die zuvor eingegebenen Werte zurückspielen (React setzt Felder nach // einer Action zurück → ohne defaultValue wäre das Formular leer). const errVals = state.status === "error" ? state.values : undefined; return (

Die Person erhält eine Einladung und setzt ihr Passwort selbst (Option C). Ein Initialpasswort wird nicht mehr vergeben.

{state.status === "error" && (

{state.message}

)}
); } export function UserEditForm({ user, roles, updateAction, rolesAction, statusAction, }: { user: { id: string; name: string; email: string; status: string; roleIds: string[]; isSelf?: boolean }; roles: RoleOption[]; updateAction: EditAction; rolesAction: PlainAction; statusAction: PlainAction; }) { const [editState, editFormAction, editPending] = useActionState(updateAction, { status: "idle" } as EditResult); const active = user.status === "ACTIVE"; return (
{editState.status === "ok" && Gespeichert} {editState.status === "error" && {editState.message}}
{/* Option C (WS4): Passwort/MFA gehören der globalen Identity — kein Mandanten- Admin-Reset mehr. Zurücksetzen erfolgt per Self-Service (Recovery-Codes) bzw. über die Plattform-Ebene. */}

Passwort und MFA verwaltet die Person selbst (Anmeldung → „Passwort vergessen“) — ein Zurücksetzen durch die Administration ist nicht mehr vorgesehen.

); }