Landingpages als eigene Gattung, erster Typ: Mehrere Links
Die sechs noch offenen Typen haben kein fremdes Ziel, auf das man weiterleiten koennte — sie brauchen eine Seite. Dafuer bekommt der Typ-Vertrag `hasLandingPage`: /r/[slug] schickt solche Codes auf /s/[slug], statt `redirectTarget` zu befragen. Die oeffentlichen Seiten duerfen die Navigation der Anwendung nicht zeigen — wer einen Code scannt, hat mit „Verwaltung" nichts zu tun. Deshalb liegt das App-Chrome jetzt in der Routengruppe (app); das Wurzel-Layout traegt nur noch html und body. Die URLs aendern sich dadurch nicht.
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
|
||||
import { ChangePasswordForm, SignOutCard } from "@/components/account";
|
||||
import { Badge, Card, CardTitle, Notice } from "@/components/ui";
|
||||
import { isAuthEnabled } from "@/lib/auth/actor";
|
||||
import { requirePageActor } from "@/lib/auth/guard";
|
||||
import { MIN_PASSWORD_LENGTH } from "@/lib/auth/password";
|
||||
import { ROLE_DESCRIPTIONS, ROLE_LABELS } from "@/lib/auth/policy";
|
||||
|
||||
export const metadata: Metadata = { title: "Konto" };
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AccountPage() {
|
||||
const actor = await requirePageActor();
|
||||
|
||||
// Bei ausgeschalteter Anmeldung gibt es kein Konto — dann erklärt die Seite,
|
||||
// wie sich das umstellen lässt, statt einfach leer zu sein.
|
||||
if (actor.kind !== "user") {
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl px-4 py-10 sm:px-6">
|
||||
<h1 className="text-2xl font-bold tracking-tight text-slate-900">
|
||||
Konto
|
||||
</h1>
|
||||
<div className="mt-5">
|
||||
<Notice tone="info">
|
||||
<span className="font-medium">Die Anmeldung ist ausgeschaltet.</span>{" "}
|
||||
Es gibt deshalb kein Konto und niemanden zum Abmelden — wer die
|
||||
Anwendung erreicht, darf alles. Zum Einschalten{" "}
|
||||
<code className="rounded bg-white/70 px-1.5 py-0.5 font-mono text-xs">
|
||||
AUTH_ENABLED=true
|
||||
</code>{" "}
|
||||
in der <code className="font-mono text-xs">.env</code> setzen und{" "}
|
||||
<code className="rounded bg-white/70 px-1.5 py-0.5 font-mono text-xs">
|
||||
docker compose up -d
|
||||
</code>{" "}
|
||||
ausführen. Beim nächsten Aufruf legst du den ersten Administrator
|
||||
an.
|
||||
</Notice>
|
||||
</div>
|
||||
<Link
|
||||
href="/"
|
||||
className="mt-6 inline-flex items-center gap-2 rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-indigo-700"
|
||||
>
|
||||
Zur Typ-Auswahl
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const { user } = actor;
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl px-4 py-8 sm:px-6 sm:py-10">
|
||||
<div className="mb-7">
|
||||
<h1 className="text-2xl font-bold tracking-tight text-slate-900 sm:text-3xl">
|
||||
Konto
|
||||
</h1>
|
||||
<p className="mt-2 text-slate-600">
|
||||
Deine Anmeldedaten und die Gruppen, in denen du mitarbeitest.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<Card>
|
||||
<CardTitle>Angemeldet als</CardTitle>
|
||||
<dl className="space-y-2 text-sm">
|
||||
<div className="flex flex-wrap gap-x-2">
|
||||
<dt className="text-slate-500">Name:</dt>
|
||||
<dd className="text-slate-900">{user.name?.trim() || "—"}</dd>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-x-2">
|
||||
<dt className="text-slate-500">E-Mail:</dt>
|
||||
<dd className="text-slate-900">{user.email}</dd>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-x-2">
|
||||
<dt className="text-slate-500">Rolle:</dt>
|
||||
<dd>
|
||||
{user.isInstanceAdmin ? (
|
||||
<Badge tone="popular">Administrator der Installation</Badge>
|
||||
) : (
|
||||
<span className="text-slate-900">Benutzer</span>
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardTitle
|
||||
hint={
|
||||
user.isInstanceAdmin
|
||||
? "Als Administrator der Installation siehst du ohnehin alle Gruppen."
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
Meine Gruppen
|
||||
</CardTitle>
|
||||
{user.memberships.length === 0 ? (
|
||||
<p className="text-sm text-slate-600">
|
||||
Du bist in keiner Gruppe. Bitte einen Administrator, dich einer
|
||||
zuzuordnen — sonst kannst du keine Codes anlegen.
|
||||
</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{user.memberships.map((membership) => (
|
||||
<li
|
||||
key={membership.groupId}
|
||||
className="flex flex-wrap items-center gap-2 rounded-lg border border-slate-200 p-3"
|
||||
>
|
||||
<span className="font-medium text-slate-900">
|
||||
{membership.groupName}
|
||||
</span>
|
||||
<Badge tone="neutral">{ROLE_LABELS[membership.role]}</Badge>
|
||||
<span className="w-full text-sm text-slate-500">
|
||||
{ROLE_DESCRIPTIONS[membership.role]}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{isAuthEnabled() ? (
|
||||
<>
|
||||
<ChangePasswordForm minLength={MIN_PASSWORD_LENGTH} />
|
||||
<SignOutCard />
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user