Init
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import Link from "next/link";
|
||||
import {
|
||||
changeUserRoleAction,
|
||||
createUserAction,
|
||||
deleteUserAction,
|
||||
} from "@/app/actions/users";
|
||||
import { Flash } from "@/components/flash";
|
||||
import { requireAdminUser } from "@/lib/auth";
|
||||
import { db, initDatabase } from "@/lib/db";
|
||||
import { formatDate, queryMessage } from "@/lib/format";
|
||||
import type { UserRole } from "@/lib/types";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
type UserRow = {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
role: UserRole;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export default async function AdminUsersPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
||||
}) {
|
||||
const currentUser = await requireAdminUser();
|
||||
initDatabase();
|
||||
const params = await searchParams;
|
||||
|
||||
const users = db
|
||||
.prepare("SELECT id, name, email, role, created_at FROM users ORDER BY name COLLATE NOCASE")
|
||||
.all() as UserRow[];
|
||||
const adminCount = users.filter((u) => u.role === "admin").length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="breadcrumbs">
|
||||
<Link href="/">Übersicht</Link>
|
||||
<span>/</span>
|
||||
Benutzer
|
||||
</div>
|
||||
<div className="page-heading">
|
||||
<div>
|
||||
<p className="eyebrow">Administration</p>
|
||||
<h1>Benutzerverwaltung</h1>
|
||||
<p className="muted">{users.length} Konten · {adminCount} Administrator(en)</p>
|
||||
</div>
|
||||
</div>
|
||||
<Flash error={queryMessage(params, "error")} success={queryMessage(params, "success")} />
|
||||
|
||||
<section className="section-block">
|
||||
<div className="section-heading">
|
||||
<div>
|
||||
<p className="eyebrow">Team</p>
|
||||
<h2>Konten</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="panel table-wrap">
|
||||
<table className="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>E-Mail</th>
|
||||
<th>Rolle</th>
|
||||
<th>Angelegt</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((row) => {
|
||||
const lastAdmin = row.role === "admin" && adminCount <= 1;
|
||||
const isSelf = row.id === currentUser.id;
|
||||
return (
|
||||
<tr key={row.id}>
|
||||
<td>{row.name}{isSelf && <span className="badge">Du</span>}</td>
|
||||
<td className="mono">{row.email}</td>
|
||||
<td>
|
||||
<form action={changeUserRoleAction} className="inline-form">
|
||||
<input type="hidden" name="id" value={row.id} />
|
||||
<select
|
||||
name="role"
|
||||
defaultValue={row.role}
|
||||
disabled={lastAdmin || isSelf}
|
||||
>
|
||||
<option value="member">Mitglied</option>
|
||||
<option value="admin">Administrator</option>
|
||||
</select>
|
||||
<button className="link-button" type="submit" disabled={lastAdmin || isSelf}>
|
||||
Speichern
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td>{formatDate(row.created_at.slice(0, 10))}</td>
|
||||
<td>
|
||||
{!isSelf && !lastAdmin && (
|
||||
<form action={deleteUserAction} className="inline-form">
|
||||
<input type="hidden" name="id" value={row.id} />
|
||||
<button className="link-button danger" type="submit">Löschen</button>
|
||||
</form>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section-block">
|
||||
<div className="section-heading">
|
||||
<div>
|
||||
<p className="eyebrow">Neu</p>
|
||||
<h2>Benutzer anlegen</h2>
|
||||
</div>
|
||||
</div>
|
||||
<form action={createUserAction} className="panel form-grid">
|
||||
<label>
|
||||
Name *
|
||||
<input name="name" required maxLength={100} />
|
||||
</label>
|
||||
<label>
|
||||
E-Mail *
|
||||
<input name="email" type="email" required maxLength={254} />
|
||||
</label>
|
||||
<label>
|
||||
Passwort * (min. 10 Zeichen)
|
||||
<input name="password" type="password" required minLength={10} maxLength={200} />
|
||||
</label>
|
||||
<label>
|
||||
Rolle *
|
||||
<select name="role" defaultValue="member" required>
|
||||
<option value="member">Mitglied</option>
|
||||
<option value="admin">Administrator</option>
|
||||
</select>
|
||||
</label>
|
||||
<div className="form-actions span-2">
|
||||
<button className="button button-primary" type="submit">Benutzer anlegen</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user