mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 20:51:17 +00:00
139 lines
5.3 KiB
TypeScript
139 lines
5.3 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import { Button } from "@/components/ui/button";
|
|
import { EyeIcon, EyeOffIcon, PencilIcon } from "lucide-react";
|
|
import { revealCredential } from "@/modules/shared/actions/credentials";
|
|
import { getServiceById } from "@/modules/shared/data/services-data";
|
|
import { CredentialDialog } from "./credential-dialog";
|
|
import { DeleteCredentialDialog } from "./delete-credential-dialog";
|
|
import { ServiceLogo } from "./service-logo";
|
|
|
|
type Credential = {
|
|
id: string;
|
|
service: string;
|
|
username: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
function RevealButton({ credentialId }: { credentialId: string }) {
|
|
const [password, setPassword] = useState<string | null>(null);
|
|
const [pending, setPending] = useState(false);
|
|
|
|
async function handleToggle() {
|
|
if (password !== null) {
|
|
setPassword(null);
|
|
return;
|
|
}
|
|
|
|
setPending(true);
|
|
const result = await revealCredential(credentialId);
|
|
setPending(false);
|
|
|
|
if ("password" in result) {
|
|
setPassword(result.password);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<span className="min-w-32 font-mono text-small">
|
|
{password ?? "••••••••"}
|
|
</span>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="icon-sm"
|
|
onClick={handleToggle}
|
|
disabled={pending}
|
|
aria-label={password !== null ? "Passwort verbergen" : "Passwort anzeigen"}
|
|
>
|
|
{password !== null ? <EyeOffIcon className="size-4" /> : <EyeIcon className="size-4" />}
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function CredentialsTable({
|
|
profileId,
|
|
credentials,
|
|
isAdmin,
|
|
}: {
|
|
profileId: string;
|
|
credentials: Credential[];
|
|
isAdmin: boolean;
|
|
}) {
|
|
if (credentials.length === 0) {
|
|
return (
|
|
<p className="rounded-lg border border-border bg-surface/50 p-8 text-center text-muted-foreground">
|
|
Noch keine Passwörter hinterlegt.
|
|
</p>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="overflow-hidden rounded-lg border border-border">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow className="hover:bg-transparent">
|
|
<TableHead>Dienst</TableHead>
|
|
<TableHead>Benutzername</TableHead>
|
|
<TableHead>Passwort</TableHead>
|
|
<TableHead className="w-24" />
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{credentials.map((credential) => {
|
|
const service = getServiceById(credential.service);
|
|
|
|
return (
|
|
<TableRow key={credential.id}>
|
|
<TableCell>
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex size-9 shrink-0 items-center justify-center rounded-md border border-border bg-background">
|
|
<ServiceLogo serviceId={credential.service} size={18} className="size-4.5 object-contain" />
|
|
</div>
|
|
<span className="font-medium">{service.label}</span>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell className="text-muted-foreground">{credential.username}</TableCell>
|
|
<TableCell>
|
|
<RevealButton credentialId={credential.id} />
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="flex justify-end gap-1">
|
|
<CredentialDialog
|
|
profileId={profileId}
|
|
credential={credential}
|
|
trigger={
|
|
<Button variant="ghost" size="icon-sm" aria-label="Bearbeiten">
|
|
<PencilIcon className="size-4" />
|
|
</Button>
|
|
}
|
|
/>
|
|
{isAdmin && (
|
|
<DeleteCredentialDialog
|
|
profileId={profileId}
|
|
credentialId={credential.id}
|
|
service={service.label}
|
|
/>
|
|
)}
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
);
|
|
})}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
);
|
|
}
|