Konto und Einstellungen standen auf max-w-2xl, alle anderen Seiten auf der Containerbreite -- beim Wechseln sprang der Inhalt sichtbar. Ein Fehler beim Bauen, kein Entwurf. Die Breite steht jetzt an EINER Stelle (BREITE in App.tsx) und gilt fuer Kopfzeile und Inhalt. Vorher stand sie zweimal da, und zwei Seiten setzten zusaetzlich ihre eigene: drei Orte fuer eine Zahl, die immer gleich sein soll. Formulare werden stattdessen INNERHALB ihrer Karte begrenzt. Das war der eigentliche Grund fuer die Ausnahme -- ein Passwortfeld ueber 1280 Pixel sieht albern aus. Nur gehoert diese Begrenzung ans Formular und nicht an die Seite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
110 lines
4.4 KiB
TypeScript
110 lines
4.4 KiB
TypeScript
import { useState, type FormEvent } from 'react'
|
|
import { api } from '../api'
|
|
import { run, useStore } from '../store'
|
|
import { Feld, Karte, Knopf } from '../components/Bausteine'
|
|
import { Avatar } from '../components/Avatar'
|
|
|
|
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('')
|
|
}
|
|
}
|
|
|
|
// Der Discord-Name steht nur daneben, wenn er vom Anzeigenamen abweicht —
|
|
// sonst stuende dort zweimal dasselbe.
|
|
const herkunft =
|
|
user?.discordName && user.discordName !== user.username
|
|
? `über Discord angemeldet — dort „${user.discordName}"`
|
|
: 'über Discord angemeldet'
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<Karte titel="Konto">
|
|
<div className="mb-4 flex items-center gap-3 border-b border-rand pb-4">
|
|
<Avatar name={user?.username ?? '?'} url={user?.avatar} groesse={44} />
|
|
<div className="min-w-0">
|
|
<p className="truncate font-medium">{user?.username ?? '—'}</p>
|
|
<p className="text-xs text-schrift-leise">
|
|
{user?.discord ? herkunft : 'mit Passwort angemeldet'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{user?.discord && (
|
|
<p className="mb-4 rounded-md border border-rand bg-grund px-3 py-2 text-xs text-schrift-leise">
|
|
Dieses Konto hat kein Passwort — es kommt ausschließlich über Discord
|
|
herein. Das Formular unten wirkt nur für Konten mit Passwort.
|
|
</p>
|
|
)}
|
|
|
|
<form onSubmit={wechseln} className="flex max-w-md 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>
|
|
)
|
|
}
|