mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 20:51:17 +00:00
167 lines
6.2 KiB
TypeScript
167 lines
6.2 KiB
TypeScript
"use client"
|
|
|
|
import { useActionState, useState } from "react";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog";
|
|
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Button } from "@/components/ui/button";
|
|
import { PlusIcon } from "lucide-react";
|
|
import { createCredential, updateCredential } from "@/modules/shared/actions/credentials";
|
|
import { SERVICES } from "@/modules/shared/data/services-data";
|
|
import { ServiceLogo } from "@/modules/shared/ui/components/service-logo";
|
|
|
|
type Credential = {
|
|
id: string;
|
|
service: string;
|
|
username: string;
|
|
} | null;
|
|
|
|
const selectClassName =
|
|
"flex h-10 w-full rounded-md border border-input bg-background px-3 text-body outline-none transition-colors placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50";
|
|
|
|
function CredentialDialogContent({
|
|
profileId,
|
|
credential,
|
|
onDone,
|
|
}: {
|
|
profileId: string;
|
|
credential: Credential;
|
|
onDone: () => void;
|
|
}) {
|
|
const isEdit = Boolean(credential);
|
|
const action = isEdit ? updateCredential : createCredential;
|
|
const [serviceId, setServiceId] = useState(
|
|
credential?.service ?? SERVICES[0].id
|
|
);
|
|
|
|
async function actionWithDoneCheck(_prevState: Awaited<ReturnType<typeof action>>, formData: FormData) {
|
|
const result = await action(_prevState, formData);
|
|
if (result && "success" in result && result.success) {
|
|
onDone();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const [state, formAction, pending] = useActionState(actionWithDoneCheck, undefined);
|
|
|
|
return (
|
|
<>
|
|
<DialogHeader className="mb-4">
|
|
<DialogTitle>{isEdit ? "Eintrag bearbeiten" : "Neuer Eintrag"}</DialogTitle>
|
|
</DialogHeader>
|
|
<form action={formAction}>
|
|
<input type="hidden" name="profileId" value={profileId} />
|
|
{isEdit && <input type="hidden" name="credentialId" value={credential!.id} />}
|
|
<FieldGroup>
|
|
<Field>
|
|
<FieldLabel htmlFor="service">
|
|
Dienst <span className="text-destructive">*</span>
|
|
</FieldLabel>
|
|
<div className="flex items-center gap-2">
|
|
<div className="flex size-10 shrink-0 items-center justify-center rounded-md border border-input bg-background">
|
|
<ServiceLogo serviceId={serviceId} size={20} className="size-5 object-contain" />
|
|
</div>
|
|
<select
|
|
id="service"
|
|
name="service"
|
|
value={serviceId}
|
|
onChange={(event) => setServiceId(event.target.value)}
|
|
required
|
|
className={selectClassName}
|
|
>
|
|
{SERVICES.map((service) => (
|
|
<option key={service.id} value={service.id}>
|
|
{service.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel htmlFor="username">
|
|
Benutzername <span className="text-destructive">*</span>
|
|
</FieldLabel>
|
|
<Input
|
|
id="username"
|
|
name="username"
|
|
defaultValue={credential?.username ?? ""}
|
|
required
|
|
className="focus-visible:ring-0"
|
|
/>
|
|
</Field>
|
|
<Field>
|
|
<FieldLabel htmlFor="password">
|
|
Passwort {!isEdit && <span className="text-destructive">*</span>}
|
|
</FieldLabel>
|
|
<Input
|
|
id="password"
|
|
name="password"
|
|
type="password"
|
|
required={!isEdit}
|
|
placeholder={isEdit ? "Leer lassen, um Passwort zu behalten" : undefined}
|
|
className="focus-visible:ring-0"
|
|
/>
|
|
</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 CredentialDialog({
|
|
profileId,
|
|
credential,
|
|
trigger,
|
|
}: {
|
|
profileId: string;
|
|
credential?: Credential;
|
|
trigger?: React.ReactElement;
|
|
}) {
|
|
const [open, setOpen] = useState(false);
|
|
const [instanceKey, setInstanceKey] = useState(0);
|
|
|
|
function handleOpenChange(nextOpen: boolean) {
|
|
setOpen(nextOpen);
|
|
if (!nextOpen) {
|
|
setInstanceKey((key) => key + 1);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
|
<DialogTrigger
|
|
render={trigger ?? <Button variant="outline" className="cursor-pointer" />}
|
|
>
|
|
{!trigger && (
|
|
<>
|
|
<PlusIcon className="size-4" />
|
|
Neuer Eintrag
|
|
</>
|
|
)}
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<CredentialDialogContent
|
|
key={instanceKey}
|
|
profileId={profileId}
|
|
credential={credential ?? null}
|
|
onDone={() => handleOpenChange(false)}
|
|
/>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|