Files
bricktrack/src/app/(app)/konto/page.tsx
T
unknownandClaude Opus 5 4c78c5672b feat(auth): better-auth mit passwort, passkeys und app-shell
Registrierung des ersten Nutzers macht ihn zum Administrator, danach
ist die Registrierung geschlossen (Einladungen folgen in Phase 2).
Login per Passwort oder Passkey inklusive Conditional UI, Verwaltung
der Passkeys unter "Konto" mit Umbenennen und Loeschen.

Rate-Limiting liegt in der Datenbank und ueberlebt Neustarts;
Session-Cookies sind httpOnly, sameSite=lax und im Produktivbetrieb
secure. Die Client-IP wird hinter dem Reverse Proxy aus
x-forwarded-for gelesen.

Dazu die mobile-first Grundstruktur: Bottom-Navigation, Header mit
Konto-Menue, Healthcheck unter /api/health und ein Dashboard mit den
vier Einstiegspunkten. Bedienelemente sind auf 44px Touch-Groesse
angehoben.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 22:40:54 +02:00

63 lines
2.3 KiB
TypeScript

import type { Metadata } from 'next';
import { headers } from 'next/headers';
import { PasskeyManager, type PasskeyRow } from '@/components/auth/passkey-manager';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { auth } from '@/lib/auth';
import { requireUser } from '@/lib/session';
export const metadata: Metadata = { title: 'Konto' };
export default async function AccountPage() {
const user = await requireUser();
const passkeys = await auth.api.listPasskeys({ headers: await headers() });
const rows: PasskeyRow[] = passkeys.map((passkey) => ({
id: passkey.id,
name: passkey.name ?? null,
createdAt: passkey.createdAt ? new Date(passkey.createdAt) : null,
}));
return (
<div className="space-y-6">
<h1 className="text-2xl font-semibold tracking-tight">Konto</h1>
<Card>
<CardHeader>
<CardTitle>Profil</CardTitle>
<CardDescription>Diese Angaben stammen aus deiner Registrierung.</CardDescription>
</CardHeader>
<CardContent className="space-y-3 text-sm">
<div className="flex items-center justify-between gap-4">
<span className="text-muted-foreground">Name</span>
<span className="font-medium">{user.name}</span>
</div>
<div className="flex items-center justify-between gap-4">
<span className="text-muted-foreground">E-Mail</span>
<span className="truncate font-medium">{user.email}</span>
</div>
<div className="flex items-center justify-between gap-4">
<span className="text-muted-foreground">Rolle</span>
<Badge variant={user.role === 'admin' ? 'default' : 'secondary'}>
{user.role === 'admin' ? 'Administrator' : 'Mitglied'}
</Badge>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Passkeys</CardTitle>
<CardDescription>
Passkeys sind an diese Domain gebunden und funktionieren nur über HTTPS. Du kannst
beliebig viele Geräte hinterlegen.
</CardDescription>
</CardHeader>
<CardContent>
<PasskeyManager passkeys={rows} />
</CardContent>
</Card>
</div>
);
}