feat: das Dashboard -- und die eine Regel steht im Code, nicht im Vorsatz
Schritt 4. React 18, TypeScript, Vite 5, Tailwind 4 (CSS-first), Lucide, Zustand; kein Konsta. Quelle in server/ui, Build nach server/web (gitignored), im Abbild von einer eigenen Docker-Stufe gebaut. JEDER KNOPF GIBT RUECKMELDUNG -- und zwar erzwungen, nicht vorgenommen: - api.ts haelt den EINZIGEN fetch und GENAU EINE Fehlerklasse. Der Fivemanage-Fehler war, zwei zu haben: geworfen wurde `new Error`, geprueft auf `instanceof ApiError`, und damit verschwand jeder Fehlschlag lautlos. - store.ts haelt run(): Aktion rein, Erfolgstext rein, und es meldet bei Erfolg diesen und bei Fehlschlag den Text DES DIENSTES. Wer darueber geht, kann keinen stillen Knopf bauen. - Jeder wartende Knopf ist gesperrt und zeigt einen Kreisel. Der zweite Klick eine Sekunde spaeter war der Weg zu neunzehn gleichnamigen Organisationen. - Fehlermeldungen bleiben stehen, bis jemand sie wegklickt. Ein Fehler, der von allein verschwindet, ist einer, den niemand gelesen hat. @vitejs/plugin-react ist auf ^4 festgenagelt: die 6 verlangt vite ^8. In den peerDependencies nachgesehen -- Vite 5.4.21 liegt in der Schnittmenge von plugin-react@4 und @tailwindcss/vite@4. Am laufenden Container nachgemessen: / kommt als text/html, assets/*.js als text/javascript, *.css als text/css (die MIME-Falle aus Schritt 1 traegt), und /tokens, /speicher, /konto liefern dieselbe index.html -- die SPA-Rueckfall- route tut, wofuer sie gebaut wurde. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { useState, type FormEvent } from 'react'
|
||||
import { api } from '../api'
|
||||
import { run, useStore } from '../store'
|
||||
import { Feld, Karte, Knopf } from '../components/Bausteine'
|
||||
|
||||
export function Konto() {
|
||||
const user = useStore((s) => s.user)
|
||||
const note = useStore((s) => s.note)
|
||||
const [aktuell, setAktuell] = useState('')
|
||||
const [neu, setNeu] = useState('')
|
||||
const [wieder, setWieder] = useState('')
|
||||
const [wartet, setWartet] = useState(false)
|
||||
|
||||
const zuKurz = neu.length > 0 && neu.length < 8
|
||||
const ungleich = wieder.length > 0 && neu !== wieder
|
||||
|
||||
async function wechseln(e: FormEvent) {
|
||||
e.preventDefault()
|
||||
// Die Wiederholung prüft nur diese Seite — der Dienst kennt sie nicht.
|
||||
// Deshalb hier ein eigener Satz statt einer Anfrage, die nichts
|
||||
// bewirken kann.
|
||||
if (neu !== wieder) {
|
||||
note('fehler', 'Die beiden neuen Passwörter stimmen nicht überein.')
|
||||
return
|
||||
}
|
||||
setWartet(true)
|
||||
const ok = await run(
|
||||
() => api.changePassword(aktuell, neu),
|
||||
'Passwort geändert. Alle anderen Anmeldungen wurden beendet.',
|
||||
)
|
||||
setWartet(false)
|
||||
if (ok) {
|
||||
setAktuell('')
|
||||
setNeu('')
|
||||
setWieder('')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-md">
|
||||
<Karte titel={`Angemeldet als ${user?.username ?? '—'}`}>
|
||||
<form onSubmit={wechseln} className="flex flex-col gap-3">
|
||||
<Feld
|
||||
label="Aktuelles Passwort"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
value={aktuell}
|
||||
onChange={(e) => setAktuell(e.target.value)}
|
||||
/>
|
||||
<Feld
|
||||
label="Neues Passwort"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
value={neu}
|
||||
onChange={(e) => setNeu(e.target.value)}
|
||||
hinweis={zuKurz ? 'Mindestens 8 Zeichen.' : undefined}
|
||||
/>
|
||||
<Feld
|
||||
label="Neues Passwort wiederholen"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
value={wieder}
|
||||
onChange={(e) => setWieder(e.target.value)}
|
||||
hinweis={ungleich ? 'Stimmt nicht mit dem neuen Passwort überein.' : undefined}
|
||||
/>
|
||||
|
||||
<p className="text-xs text-schrift-leise">
|
||||
Ein Wechsel beendet alle anderen Sitzungen. Diese hier bleibt bestehen.
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<Knopf
|
||||
art="haupt"
|
||||
type="submit"
|
||||
wartet={wartet}
|
||||
disabled={!aktuell || neu.length < 8 || neu !== wieder}
|
||||
kind="Passwort ändern"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</Karte>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user