feat: Grundgerüst mit Anmeldung, Layout und Design-Tokens

Next.js 16, Prisma 7, Tailwind 4 und Vitest/Playwright, Konfiguration aus
Laufzettel übernommen, weil dort derselbe Stack erprobt ist.

Die Farben der Branding-Vorgabe stehen als Tokens in globals.css, das
helle Theme setzt dieselben Namen neu. Amber und Rot sind dort
abgedunkelt, sonst reicht der Kontrast nicht – ein Test prüft beide
Themes gegen 4,5:1.

Anmeldung mit lokalem Benutzer, Rolle je Anfrage aus der Datenbank,
Audit-Log per Trigger nur anhängbar. Geprüft im gebauten Stand auf
1440 px und 390 px.

Closes #001

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 09:23:41 +02:00
co-authored by Claude Opus 5.5
parent c3211d0fe2
commit 871bb3a77c
75 changed files with 10155 additions and 15 deletions
+36
View File
@@ -0,0 +1,36 @@
import type { Metadata } from "next";
import { redirect } from "next/navigation";
import { Symbol, Wortmarke } from "@/components/wortmarke";
import { aktuellerBenutzer } from "@/server/auth/zugang";
import { Anmeldeformular } from "./anmeldeformular";
export const metadata: Metadata = { title: "Anmelden" };
/** Nur relative Ziele innerhalb der Anwendung – sonst wäre `?ziel=` eine offene Weiterleitung. */
function sicheresZiel(ziel: string | string[] | undefined): string {
return typeof ziel === "string" && ziel.startsWith("/") && !ziel.startsWith("//") ? ziel : "/";
}
export default async function Anmelden({
searchParams,
}: {
searchParams: Promise<Record<string, string | string[]>>;
}) {
const ziel = sicheresZiel((await searchParams)["ziel"]);
if (await aktuellerBenutzer()) redirect(ziel);
return (
<main className="flex min-h-dvh items-center justify-center px-4 py-10">
<div className="w-full max-w-sm">
<div className="mb-8 flex flex-col items-center gap-3">
<Symbol className="h-14 w-14" />
<h1 className="text-2xl">
<Wortmarke blinkt />
</h1>
<p className="text-muted text-center text-sm">VMs und LXC-Container auf Proxmox VE ausrollen</p>
</div>
<Anmeldeformular ziel={ziel} />
</div>
</main>
);
}