Absenderanschrift in jedem Schreiben
Jeder erzeugte Brief ging bisher ohne Ruecksendeadresse hinaus: Im Absenderblock stand nur der Name, in der Datumszeile fehlte der Ort. Bei einer Versicherung oder Behoerde wirkt das unserioes und macht es der Gegenstelle unnoetig schwer zu antworten. Neu unter "Mein Konto" ein Feld fuer Strasse, PLZ und Ort. Ab da stehen sie im Absenderblock jedes Schreibens, und die Datumszeile lautet "Bayreuth, den 23.08.2026". Die Anschrift haengt am Benutzer, nicht an der betreuten Person: Sie gehoert zu mir und gilt fuer alle Akten. Nicht die Logik fehlte, sondern die Datenquelle - absenderBlock() und datumsZeile() konnten die Felder schon, briefkopfBauen() hat sie mit einem Kommentar auf null gesetzt, weil es sie im Datenmodell nicht gab. Aufbau: - Drei Felder am Benutzer, Migration ueber den Wrapper aus #24. Das war sein erster echter Einsatz: erzeugte Datei ohne Handarbeit brauchbar, DROP-INDEX-Zeilen bereits heraus. - Ein Absender-Typ statt eines blossen Namens, durchgereicht bis in den Serienlauf - der hat bisher {name} gebaut und haette die Anschrift sonst still verloren. - Zwei Platzhalter fuer eigene Vorlagen: betreuer_anschrift, betreuer_ort. Was fehlt, bleibt weg: Jede der drei Angaben ist einzeln freiwillig. Nur ein Ort ergibt eine Datumszeile mit Ort und einen Absenderblock, der weiterhin nur aus dem Namen besteht. Eine halb ausgefuellte Adresse sieht nach Fehler aus, ein blosser Name nach Absicht. Geprueft: sieben Einheitstests fuer den Absenderblock inklusive jeder Teilbefuellung und der Gegenprobe, dass nie die Heimadresse der betreuten Person im Absender landet; vier End-to-End-Tests vom leeren Zustand ueber das Speichern bis zum fertigen Brief. Refs #21
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
'use client'
|
||||
|
||||
import { useActionState } from 'react'
|
||||
import { useFormStatus } from 'react-dom'
|
||||
import { AlertCircle, CheckCircle2, Loader2 } from 'lucide-react'
|
||||
import { anschriftSpeichern, type AnschriftZustand } from '@/server/actions/konto'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Feld } from '@/components/feld'
|
||||
|
||||
function Absenden() {
|
||||
const { pending } = useFormStatus()
|
||||
return (
|
||||
<Button type="submit" disabled={pending}>
|
||||
{pending && <Loader2 className="animate-spin" aria-hidden="true" />}
|
||||
{pending ? 'Wird gespeichert …' : 'Anschrift speichern'}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export function AnschriftFormular({
|
||||
strasse,
|
||||
plz,
|
||||
ort,
|
||||
}: {
|
||||
strasse: string | null
|
||||
plz: string | null
|
||||
ort: string | null
|
||||
}) {
|
||||
const [zustand, aktion] = useActionState<AnschriftZustand, FormData>(anschriftSpeichern, {})
|
||||
|
||||
return (
|
||||
<form action={aktion} className="space-y-4" noValidate>
|
||||
{zustand.fehler && (
|
||||
<Alert variant="fehler">
|
||||
<AlertCircle aria-hidden="true" />
|
||||
<AlertDescription>{zustand.fehler}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
{zustand.erfolg && (
|
||||
<Alert variant="erfolg">
|
||||
<CheckCircle2 aria-hidden="true" />
|
||||
<AlertDescription>{zustand.erfolg}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Feld id="strasse" beschriftung="Straße und Hausnummer" fehler={zustand.felder?.strasse}>
|
||||
<Input
|
||||
id="strasse"
|
||||
name="strasse"
|
||||
defaultValue={strasse ?? ''}
|
||||
maxLength={150}
|
||||
autoComplete="street-address"
|
||||
/>
|
||||
</Feld>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-[8rem_1fr]">
|
||||
<Feld id="plz" beschriftung="PLZ" fehler={zustand.felder?.plz}>
|
||||
<Input
|
||||
id="plz"
|
||||
name="plz"
|
||||
defaultValue={plz ?? ''}
|
||||
maxLength={10}
|
||||
autoComplete="postal-code"
|
||||
inputMode="numeric"
|
||||
/>
|
||||
</Feld>
|
||||
|
||||
<Feld
|
||||
id="ort"
|
||||
beschriftung="Ort"
|
||||
erklaerung="Steht außerdem in der Datumszeile: „Bayreuth, den 23.08.2026“."
|
||||
fehler={zustand.felder?.ort}
|
||||
>
|
||||
<Input
|
||||
id="ort"
|
||||
name="ort"
|
||||
defaultValue={ort ?? ''}
|
||||
maxLength={100}
|
||||
autoComplete="address-level2"
|
||||
/>
|
||||
</Feld>
|
||||
</div>
|
||||
|
||||
<Absenden />
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Metadata } from 'next'
|
||||
import Link from 'next/link'
|
||||
import { Fingerprint, KeyRound, ShieldCheck } from 'lucide-react'
|
||||
import { Fingerprint, KeyRound, MapPin, ShieldCheck } from 'lucide-react'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
@@ -11,6 +11,7 @@ import { PasskeyVerwaltung } from '@/components/konto/passkey-verwaltung'
|
||||
import { ROLLEN_BESCHRIFTUNG, ROLLEN_ERKLAERUNG } from '@/lib/auth/rollen'
|
||||
import { formatDatumZeit } from '@/lib/utils'
|
||||
import { PasswortFormular } from './passwort-formular'
|
||||
import { AnschriftFormular } from './anschrift-formular'
|
||||
|
||||
export const metadata: Metadata = { title: 'Mein Konto' }
|
||||
export const dynamic = 'force-dynamic'
|
||||
@@ -22,7 +23,10 @@ export default async function KontoSeite() {
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl space-y-6">
|
||||
<SeitenKopf titel="Mein Konto" beschreibung="Zugangsdaten, zweiter Faktor und Passkeys." />
|
||||
<SeitenKopf
|
||||
titel="Mein Konto"
|
||||
beschreibung="Zugangsdaten, eigene Anschrift, zweiter Faktor und Passkeys."
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
@@ -55,6 +59,27 @@ export default async function KontoSeite() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<MapPin className="size-5" aria-hidden="true" />
|
||||
Meine Anschrift
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Steht im Absenderblock jedes Schreibens, das hier entsteht, und liefert den Ort für
|
||||
die Datumszeile. Ohne sie geht ein förmlicher Brief ohne Rücksendeadresse hinaus.
|
||||
Jede Angabe ist einzeln freiwillig — was fehlt, bleibt im Brief einfach weg.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<AnschriftFormular
|
||||
strasse={benutzer.strasse}
|
||||
plz={benutzer.plz}
|
||||
ort={benutzer.ort}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
|
||||
@@ -50,7 +50,7 @@ export default async function SchreibenSeite({
|
||||
try {
|
||||
entwurf = await entwurfBauen({
|
||||
betreuteId: aktive.id,
|
||||
benutzerName: benutzer.name,
|
||||
benutzer,
|
||||
art,
|
||||
vertragId: text('vertragId') || null,
|
||||
vorgangId: text('vorgangId') || null,
|
||||
|
||||
Reference in New Issue
Block a user