fix(web): Loeschweg der Benutzerverwaltung meldet abgewiesene Server-Antworten
WINDOWS #36, Aufgabe 1/3: handleDelete verschluckte einen 403 bisher komplett (nur res.ok geprueft, Fang-Zweig ohne Wirkung). readApiMessage liest jetzt gezielt das Feld message aus dem Antwortrumpf; der Loeschdialog zeigt den Servertext, eine uebersetzte Ersatzmeldung ohne verwertbaren Rumpf oder bei Verbindungsfehler — und bleibt in allen drei Faellen offen. Neue Texte unter admin.users.errors in de.json/en.json, Umlaut-Waechter gruen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
@@ -29,6 +29,26 @@ interface UserFormData {
|
||||
role: 'SUPER_ADMIN' | 'ADMIN' | 'USER';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the backend's error message from a non-2xx JSON error body
|
||||
* (Nest's default exception filter shape: `{ statusCode, message, error }`).
|
||||
* Bewusst lokal kopiert statt aus `apps/web/src/lib/tender-radar-api.ts`
|
||||
* importiert (jene Datei gehoert zum Modul Ausschreibungs-Radar) — nur das
|
||||
* Feld `message` wird gelesen, nie der ganze Rumpf (T-A1D-01).
|
||||
*/
|
||||
async function readApiMessage(res: Response): Promise<string | null> {
|
||||
try {
|
||||
const body = (await res.json()) as { message?: unknown };
|
||||
if (typeof body.message === 'string' && body.message) return body.message;
|
||||
if (Array.isArray(body.message) && body.message.length) {
|
||||
return body.message.join(', ');
|
||||
}
|
||||
} catch {
|
||||
/* Rumpf war kein JSON — Ersatzmeldung greift beim Aufrufer */
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin users page -- User CRUD management (D-12).
|
||||
* ADMIN and SUPER_ADMIN can access. ADMIN sees only own-tenant users.
|
||||
@@ -44,6 +64,7 @@ export default function AdminUsersPage() {
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [editingUser, setEditingUser] = useState<User | null>(null);
|
||||
const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);
|
||||
const [deleteError, setDeleteError] = useState<string | null>(null);
|
||||
const [detailsUser, setDetailsUser] = useState<User | null>(null);
|
||||
const [formData, setFormData] = useState<UserFormData>({
|
||||
username: '',
|
||||
@@ -142,6 +163,7 @@ export default function AdminUsersPage() {
|
||||
};
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
setDeleteError(null);
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/users/${id}`, {
|
||||
method: 'DELETE',
|
||||
@@ -150,9 +172,14 @@ export default function AdminUsersPage() {
|
||||
if (res.ok) {
|
||||
setDeleteConfirm(null);
|
||||
fetchUsers();
|
||||
} else {
|
||||
const detail = await readApiMessage(res);
|
||||
setDeleteError(
|
||||
detail ? t('errors.serverRejected', { detail }) : t('errors.generic'),
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// silently fail
|
||||
setDeleteError(t('errors.network'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -263,7 +290,10 @@ export default function AdminUsersPage() {
|
||||
{tCommon('edit')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setDeleteConfirm(user.id)}
|
||||
onClick={() => {
|
||||
setDeleteConfirm(user.id);
|
||||
setDeleteError(null);
|
||||
}}
|
||||
disabled={user.id === currentUser?.id}
|
||||
className="rounded px-2 py-1 text-xs text-destructive hover:bg-destructive/10 transition-colors disabled:opacity-30 disabled:cursor-not-allowed disabled:pointer-events-none"
|
||||
>
|
||||
@@ -397,6 +427,14 @@ export default function AdminUsersPage() {
|
||||
<p className="text-sm text-foreground mb-4">
|
||||
{t('deleteConfirm')}
|
||||
</p>
|
||||
{deleteError && (
|
||||
<div
|
||||
role="alert"
|
||||
className="rounded-md border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive mb-4"
|
||||
>
|
||||
{deleteError}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-end gap-3">
|
||||
<button
|
||||
onClick={() => setDeleteConfirm(null)}
|
||||
|
||||
Reference in New Issue
Block a user