Files
craftvia/src/components/platform-admin-forms.tsx
T
msolarczekandClaude Opus 5 c8e6f30a27
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s
Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation
und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 11:05:39 +02:00

73 lines
3.3 KiB
TypeScript

"use client";
import { useActionState } from "react";
import { createPlatformAdmin, resetPlatformAdminPassword, type AdminActionState } from "@/server/actions/platform-admins";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
const initial: AdminActionState = { status: "idle" };
const selectCls = "h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm";
function Feedback({ state }: { state: AdminActionState }) {
if (state.status === "error") return <p role="alert" className="text-[12px] text-[var(--risk)]">{state.message}</p>;
if (state.status === "done") return <p className="text-[12px] text-[var(--ok)]">{state.message}</p>;
return null;
}
/** SEC4: neuen Plattform-Admin anlegen (Voll-Admin + Step-up). */
export function CreatePlatformAdminForm({ mfaEnrolled }: { mfaEnrolled: boolean }) {
const [state, action, pending] = useActionState(createPlatformAdmin, initial);
return (
<form action={action} className="space-y-3">
<div className="grid gap-3 sm:grid-cols-2">
<div>
<Label htmlFor="pa-email">E-Mail</Label>
<Input id="pa-email" name="email" type="email" required className="mt-1" />
</div>
<div>
<Label htmlFor="pa-name">Name</Label>
<Input id="pa-name" name="name" required className="mt-1" />
</div>
<div>
<Label htmlFor="pa-role">Rolle</Label>
<select id="pa-role" name="role" defaultValue="readonly" className={`${selectCls} mt-1`}>
<option value="readonly">Read-only (nur lesen)</option>
<option value="full">Voll-Admin (darf verwalten)</option>
</select>
</div>
<div>
<Label htmlFor="pa-pw">Initial-Passwort</Label>
<Input id="pa-pw" name="password" type="password" required className="mt-1" />
</div>
</div>
{mfaEnrolled && (
<div>
<Label htmlFor="pa-token">Ihr aktueller MFA-Code (Step-up)</Label>
<Input id="pa-token" name="token" inputMode="numeric" autoComplete="one-time-code" placeholder="123456" className="mt-1 w-40" required />
</div>
)}
<div className="flex items-center gap-3">
<Button type="submit" size="sm" disabled={pending}>{pending ? "…" : "Admin anlegen"}</Button>
<Feedback state={state} />
</div>
</form>
);
}
/** SEC4: Passwort eines Admins zurücksetzen (Step-up). */
export function ResetPlatformAdminPasswordForm({ adminId, mfaEnrolled }: { adminId: string; mfaEnrolled: boolean }) {
const [state, action, pending] = useActionState(resetPlatformAdminPassword.bind(null, adminId), initial);
return (
<details>
<summary className="cursor-pointer text-[12px] text-muted-foreground">Passwort zurücksetzen</summary>
<form action={action} className="mt-2 flex flex-wrap items-end gap-2">
<Input name="password" type="password" placeholder="Neues Passwort" className="h-8 w-48" required />
{mfaEnrolled && <Input name="token" inputMode="numeric" autoComplete="one-time-code" placeholder="MFA-Code" className="h-8 w-28" required />}
<Button type="submit" size="sm" variant="outline" disabled={pending}>{pending ? "…" : "Zurücksetzen"}</Button>
<Feedback state={state} />
</form>
</details>
);
}