feat: Oberflaechenbausteine, Anwendungsrahmen und Konventionen
Basisbausteine im shadcn/ui-Stil, aber mit drei bewussten Abweichungen: - Bedienelemente sind 44px hoch statt 36px. Ich bediene die App oft am Handy im Heim oder auf dem Amt; kleiner ist dort nicht treffsicher. - Auswahlfelder benutzen das native <select>. Android und iOS oeffnen dafuer ihre eigene Liste - schneller als jeder Nachbau, und mit Screenreader und Tastatur funktioniert es ohne Zutun. - Eingabefelder haben mindestens 16px Schrift, sonst zoomt iOS-Safari beim Hineintippen das Formular heran. <Feld> verdrahtet Beschriftung, Erklaerung und Fehler ueber aria-describedby. Der Erklaerungstext ist kein Beiwerk: die Anwendung benutzt Begriffe aus dem Betreuungsrecht, die niemand nachschlagen soll. <Rechtshinweis> ist der stehende Vermerk, dass hier keine Rechtsberatung stattfindet. Er steht an jeder Stelle mit Paragraphenbezug. Der Anwendungsrahmen fuehrt drei Bedingungen zusammen, ohne die niemand an Fachdaten kommt: angemeldet, zweiter Faktor eingerichtet, mindestens eine betreute Person zugeordnet. Die Navigation zeigt Zaehler fuer ueberfaellige Fristen und neuen Posteingang - die Frage "wo muss ich hin?" wird schon im Menue beantwortet. Das Farbschema wird per Inline-Skript mit CSP-Nonce gesetzt, bevor die Seite gezeichnet wird - sonst blitzt beim Laden das helle Thema auf. docs/KONVENTIONEN.md haelt fest, was in allen weiteren Etappen gilt: Verzeichnisaufbau, Cent-Betraege, Umgang mit verschluesselten Feldern, Zugriffspruefung, Audit-Pflicht, Ampelfarben, Commit-Trailer. Etappe: 1 Phase: Fundament
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { ShieldCheck } from 'lucide-react'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { aktuellerBenutzer } from '@/lib/auth/sitzung'
|
||||
import { db } from '@/lib/db'
|
||||
import { AnmeldeFormular } from './formular'
|
||||
|
||||
export const metadata: Metadata = { title: 'Anmelden' }
|
||||
|
||||
/** Fachdaten dürfen nie aus einem Cache kommen. */
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export default async function AnmeldenSeite({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ weiter?: string }>
|
||||
}) {
|
||||
// Wer schon angemeldet ist, soll nicht am Anmeldeformular hängen bleiben.
|
||||
if (await aktuellerBenutzer()) redirect('/heute')
|
||||
|
||||
// Gibt es überhaupt schon einen Zugang? Beim allerersten Start nicht.
|
||||
const anzahlBenutzer = await db.user.count({ where: { geloeschtAm: null } })
|
||||
if (anzahlBenutzer === 0) redirect('/ersteinrichtung')
|
||||
|
||||
const { weiter = '/heute' } = await searchParams
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Anmelden</CardTitle>
|
||||
<CardDescription>
|
||||
E-Mail, Passwort und der Code aus deiner Authenticator-App — alle drei zusammen.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<AnmeldeFormular weiter={weiter} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="flex items-start gap-2.5 rounded-md border bg-muted/50 p-3 text-sm text-muted-foreground">
|
||||
<ShieldCheck className="mt-0.5 size-4 shrink-0" aria-hidden="true" />
|
||||
<p>
|
||||
Hier liegen Gesundheits- und Vermögensdaten einer betreuten Person. Der zweite Faktor ist
|
||||
deshalb Pflicht und lässt sich nicht abschalten.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user