fix(web): Formularweg und Listenladen der Benutzerverwaltung melden Fehler sichtbar

WINDOWS #36, Aufgabe 2/3: handleSubmit und fetchUsers verschluckten
abgewiesene Antworten und Verbindungsfehler ebenso wie der Loeschweg aus
Aufgabe 1. formError zeigt jetzt den Servertext oder eine Ersatzmeldung
im offenen Formulardialog; loadError verhindert die irrefuehrende
Meldung "Keine Benutzer gefunden", wenn das Laden selbst gescheitert
ist. Beide Zustaende werden beim Oeffnen eines neuen Dialogs
zurueckgesetzt, damit eine alte Meldung nicht in den naechsten Aufruf
hinueberwandert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
2026-09-21 07:31:47 +02:00
parent 38d2586466
commit 51bff7564f
2 changed files with 220 additions and 4 deletions
+34 -4
View File
@@ -65,6 +65,8 @@ export default function AdminUsersPage() {
const [editingUser, setEditingUser] = useState<User | null>(null);
const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);
const [deleteError, setDeleteError] = useState<string | null>(null);
const [formError, setFormError] = useState<string | null>(null);
const [loadError, setLoadError] = useState<string | null>(null);
const [detailsUser, setDetailsUser] = useState<User | null>(null);
const [formData, setFormData] = useState<UserFormData>({
username: '',
@@ -79,19 +81,22 @@ export default function AdminUsersPage() {
currentUser?.role === 'ADMIN' || currentUser?.role === 'SUPER_ADMIN';
const fetchUsers = useCallback(async () => {
setLoadError(null);
try {
const res = await fetch(`${API_URL}/users`, {
credentials: 'include',
});
if (res.ok) {
setUsers(await res.json());
} else {
setLoadError(t('errors.loadFailed'));
}
} catch {
// silently fail
setLoadError(t('errors.loadFailed'));
} finally {
setLoading(false);
}
}, []);
}, [t]);
useEffect(() => {
if (hasAccess) {
@@ -110,6 +115,7 @@ export default function AdminUsersPage() {
displayName: '',
role: 'USER',
});
setFormError(null);
setShowForm(true);
};
@@ -122,11 +128,13 @@ export default function AdminUsersPage() {
displayName: user.displayName ?? '',
role: user.role,
});
setFormError(null);
setShowForm(true);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setFormError(null);
const url = editingUser
? `${API_URL}/users/${editingUser.id}`
@@ -156,9 +164,14 @@ export default function AdminUsersPage() {
if (res.ok) {
setShowForm(false);
fetchUsers();
} else {
const detail = await readApiMessage(res);
setFormError(
detail ? t('errors.serverRejected', { detail }) : t('errors.generic'),
);
}
} catch {
// silently fail
setFormError(t('errors.network'));
}
};
@@ -215,10 +228,19 @@ export default function AdminUsersPage() {
</button>
</div>
{loadError && (
<div
role="alert"
className="rounded-md border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive"
>
{loadError}
</div>
)}
{/* Users table */}
{loading ? (
<p className="text-muted-foreground">{tCommon('loading')}</p>
) : users.length === 0 ? (
) : loadError ? null : users.length === 0 ? (
<p className="text-muted-foreground">{t('noUsers')}</p>
) : (
<div className="overflow-x-auto rounded-md border border-border">
@@ -400,6 +422,14 @@ export default function AdminUsersPage() {
)}
</select>
</div>
{formError && (
<div
role="alert"
className="rounded-md border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive"
>
{formError}
</div>
)}
<div className="flex justify-end gap-3 pt-2">
<button
type="button"