mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 20:51:17 +00:00
169 lines
7.9 KiB
TypeScript
169 lines
7.9 KiB
TypeScript
"use client";
|
|
|
|
import { useActionState, useState } from "react";
|
|
import { KeyRoundIcon, MoreHorizontalIcon, PencilIcon } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
|
import { Field, FieldDescription, FieldGroup, FieldLabel } from "@/components/ui/field";
|
|
import { Input } from "@/components/ui/input";
|
|
import { resetClientPassword, updateClientAccount } from "@/modules/admin/actions/manage-client";
|
|
import { MIN_PASSWORD_LENGTH } from "@/modules/admin/lib/constants";
|
|
import { CopyField } from "./copy-field";
|
|
|
|
type Client = { id: string; name: string; email: string };
|
|
|
|
function ResetPasswordContent({ client, onDone }: { client: Client; onDone: () => void }) {
|
|
const [mode, setMode] = useState<"generate" | "manual">("generate");
|
|
const [state, formAction, pending] = useActionState(resetClientPassword, undefined);
|
|
|
|
if (state && "success" in state) {
|
|
return (
|
|
<>
|
|
<DialogHeader className="mb-4">
|
|
<DialogTitle>Passwort geändert</DialogTitle>
|
|
<DialogDescription>
|
|
Gib das neue Passwort an {client.name} weiter. Es wird nur jetzt angezeigt.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<FieldGroup>
|
|
<CopyField label="Email" value={client.email} />
|
|
<CopyField label="Neues Passwort" value={state.password} />
|
|
</FieldGroup>
|
|
<DialogFooter className="mt-6">
|
|
<Button type="button" onClick={onDone}>Fertig</Button>
|
|
</DialogFooter>
|
|
</>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<DialogHeader className="mb-4">
|
|
<DialogTitle>Passwort zurücksetzen</DialogTitle>
|
|
<DialogDescription>
|
|
Setzt das Passwort von <span className="font-medium text-foreground">{client.name}</span> neu.
|
|
Das alte Passwort funktioniert danach nicht mehr.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<form action={formAction}>
|
|
<input type="hidden" name="profileId" value={client.id} />
|
|
<input type="hidden" name="mode" value={mode} />
|
|
<FieldGroup>
|
|
<div role="radiogroup" aria-label="Art des neuen Passworts" className="grid grid-cols-2 gap-2">
|
|
{(["generate", "manual"] as const).map((value) => (
|
|
<label
|
|
key={value}
|
|
className="flex cursor-pointer items-center gap-2 rounded-md border border-border px-3 py-2 text-sm has-checked:border-primary has-checked:bg-surface"
|
|
>
|
|
<input
|
|
type="radio"
|
|
name="mode-choice"
|
|
value={value}
|
|
checked={mode === value}
|
|
onChange={() => setMode(value)}
|
|
className="accent-primary"
|
|
/>
|
|
{value === "generate" ? "Automatisch generieren" : "Selbst festlegen"}
|
|
</label>
|
|
))}
|
|
</div>
|
|
{mode === "manual" && (
|
|
<Field>
|
|
<FieldLabel htmlFor="new-password">Neues Passwort</FieldLabel>
|
|
<Input
|
|
id="new-password"
|
|
name="password"
|
|
type="text"
|
|
autoComplete="new-password"
|
|
minLength={MIN_PASSWORD_LENGTH}
|
|
required
|
|
className="font-mono focus-visible:ring-0"
|
|
/>
|
|
<FieldDescription>Mindestens {MIN_PASSWORD_LENGTH} Zeichen.</FieldDescription>
|
|
</Field>
|
|
)}
|
|
{state && "error" in state && <p className="text-sm text-destructive">{state.error}</p>}
|
|
</FieldGroup>
|
|
<DialogFooter className="mt-6">
|
|
<Button type="submit" disabled={pending}>
|
|
{pending ? "Wird geändert..." : "Passwort zurücksetzen"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function EditClientContent({ client, onDone }: { client: Client; onDone: () => void }) {
|
|
async function run(prev: Parameters<typeof updateClientAccount>[0], formData: FormData) {
|
|
const result = await updateClientAccount(prev, formData);
|
|
if (result && "success" in result) onDone();
|
|
return result;
|
|
}
|
|
|
|
const [state, formAction, pending] = useActionState(run, undefined);
|
|
|
|
return (
|
|
<>
|
|
<DialogHeader className="mb-4">
|
|
<DialogTitle>Client bearbeiten</DialogTitle>
|
|
</DialogHeader>
|
|
<form action={formAction}>
|
|
<input type="hidden" name="profileId" value={client.id} />
|
|
<FieldGroup>
|
|
<Field>
|
|
<FieldLabel htmlFor="edit-client-name">Name</FieldLabel>
|
|
<Input id="edit-client-name" name="name" defaultValue={client.name} required className="focus-visible:ring-0" />
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel htmlFor="edit-client-email">Email</FieldLabel>
|
|
<Input id="edit-client-email" name="email" type="email" defaultValue={client.email} required className="focus-visible:ring-0" />
|
|
<FieldDescription>Das ist auch die Login-Email des Clients.</FieldDescription>
|
|
</Field>
|
|
{state && "error" in state && <p className="text-sm text-destructive">{state.error}</p>}
|
|
</FieldGroup>
|
|
<DialogFooter className="mt-6">
|
|
<Button type="submit" disabled={pending}>
|
|
{pending ? "Wird gespeichert..." : "Speichern"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export function ClientActionsMenu({ client }: { client: Client }) {
|
|
const [dialog, setDialog] = useState<"password" | "edit" | null>(null);
|
|
const [instanceKey, setInstanceKey] = useState(0);
|
|
|
|
function close() {
|
|
setDialog(null);
|
|
setInstanceKey((key) => key + 1);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger render={<Button variant="ghost" size="icon-sm" aria-label={`Aktionen für ${client.name}`} />}>
|
|
<MoreHorizontalIcon />
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" className="w-52">
|
|
<DropdownMenuItem onClick={() => setDialog("edit")}>
|
|
<PencilIcon /> Bearbeiten
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => setDialog("password")}>
|
|
<KeyRoundIcon /> Passwort zurücksetzen
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
|
|
<Dialog open={dialog !== null} onOpenChange={(open) => !open && close()}>
|
|
<DialogContent>
|
|
{dialog === "password" && <ResetPasswordContent key={instanceKey} client={client} onDone={close} />}
|
|
{dialog === "edit" && <EditClientContent key={instanceKey} client={client} onDone={close} />}
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
);
|
|
}
|