feat(auth): enterprise authentication and user management

This commit is contained in:
Schubert Ferenc 2026-07-02 22:19:12 +02:00
parent 4bc8b20a21
commit 86a32a942c
36 changed files with 2239 additions and 324 deletions

View file

@ -1,63 +1,133 @@
"use client";
import { User } from "@/types/user";
import Link from "next/link";
import { Edit, Eye, Trash2 } from "lucide-react";
import DataTable, { type DataTableColumn } from "@/components/common/DataTable";
import StatusBadge from "@/components/common/StatusBadge";
import { Button, buttonVariants } from "@/components/ui/button";
import type { User } from "@/types/user";
type Props = {
users: User[];
loading: boolean;
error?: string;
sortKey: string;
sortDirection: "asc" | "desc";
onSort: (key: string) => void;
onEdit: (user: User) => void;
onDelete: (user: User) => void;
};
export default function UserTable({ users }: Props) {
function formatDate(value: string) {
return new Intl.DateTimeFormat("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
}).format(new Date(value));
}
export default function UserTable({
users,
loading,
error,
sortKey,
sortDirection,
onSort,
onEdit,
onDelete,
}: Props) {
const columns: DataTableColumn<User>[] = [
{
key: "first_name",
label: "Vorname",
sortable: true,
render: (user) => user.first_name || "-",
},
{
key: "last_name",
label: "Nachname",
sortable: true,
render: (user) => user.last_name || "-",
},
{
key: "username",
label: "Benutzername",
sortable: true,
render: (user) => <span className="font-medium text-slate-900">{user.username}</span>,
},
{
key: "email",
label: "E-Mail",
sortable: true,
render: (user) => user.email,
},
{
key: "role",
label: "Rolle",
sortable: true,
render: (user) => user.role,
},
{
key: "is_active",
label: "Status",
sortable: true,
render: (user) => <StatusBadge active={user.is_active} />,
},
{
key: "created_at",
label: "Erstellt",
sortable: true,
render: (user) => formatDate(user.created_at),
},
{
key: "actions",
label: "Aktionen",
className: "px-4 py-3 text-right",
render: (user) => (
<div className="flex justify-end gap-2">
<Link
href={`/users/${user.id}`}
className={buttonVariants({ variant: "ghost", size: "icon-sm" })}
title="Details"
>
<Eye size={16} />
</Link>
<Button
type="button"
variant="ghost"
size="icon-sm"
title="Bearbeiten"
onClick={() => onEdit(user)}
>
<Edit size={16} />
</Button>
<Button
type="button"
variant="destructive"
size="icon-sm"
title="Löschen"
onClick={() => onDelete(user)}
>
<Trash2 size={16} />
</Button>
</div>
),
},
];
return (
<div className="overflow-hidden rounded-xl border bg-white shadow">
<table className="w-full">
<thead className="bg-slate-100">
<tr>
<th className="p-4 text-left">ID</th>
<th className="p-4 text-left">Benutzername</th>
<th className="p-4 text-left">E-Mail</th>
<th className="p-4 text-center">Aktionen</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr
key={user.id}
className="border-t hover:bg-slate-50"
>
<td className="p-4">{user.id}</td>
<td className="p-4">{user.username}</td>
<td className="p-4">{user.email}</td>
<td className="p-4 text-center">
<button className="mr-3 rounded bg-blue-600 px-3 py-1 text-white hover:bg-blue-700">
Bearbeiten
</button>
<button className="rounded bg-red-600 px-3 py-1 text-white hover:bg-red-700">
Löschen
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
<DataTable
columns={columns}
rows={users}
rowKey={(user) => user.id}
sortKey={sortKey}
sortDirection={sortDirection}
loading={loading}
error={error}
emptyTitle="Keine Benutzer gefunden"
emptyDescription="Passe Suche oder Filter an oder erstelle einen neuen Benutzer."
onSort={onSort}
/>
);
}
}