fix(ui): extract meaningful error messages from API responses
All checks were successful
CI / build (push) Successful in 2m9s
CI / docker (push) Successful in 1m28s

Introduces ApiError class in client.ts that parses Spring Boot error
bodies to extract human-readable messages (message, error, detail fields).
Adds errorMessage() helper used by all toast descriptions instead of
raw String(err) which dumped JSON blobs to the user.

Affected: all 10 page components that display error toasts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-26 21:10:28 +02:00
parent 73e41e5607
commit 088bc34e67
11 changed files with 95 additions and 40 deletions

View File

@@ -1,5 +1,6 @@
import { useState } from 'react';
import { QRCodeSVG } from 'qrcode.react';
import { errorMessage } from '../../api/client';
import {
Alert,
Badge,
@@ -49,7 +50,7 @@ function MfaSection() {
setSetupData(data);
setVerifyCode('');
} catch (err) {
toast({ title: 'Failed to start MFA setup', description: String(err), variant: 'error' });
toast({ title: 'Failed to start MFA setup', description: errorMessage(err), variant: 'error' });
}
}
@@ -69,7 +70,7 @@ function MfaSection() {
toast({ title: 'Invalid code. Please try again.', variant: 'error' });
}
} catch (err) {
toast({ title: 'Verification failed', description: String(err), variant: 'error' });
toast({ title: 'Verification failed', description: errorMessage(err), variant: 'error' });
}
}
@@ -80,7 +81,7 @@ function MfaSection() {
setCodesSaved(false);
toast({ title: 'Backup codes regenerated', variant: 'success' });
} catch (err) {
toast({ title: 'Failed to regenerate backup codes', description: String(err), variant: 'error' });
toast({ title: 'Failed to regenerate backup codes', description: errorMessage(err), variant: 'error' });
}
}
@@ -92,7 +93,7 @@ function MfaSection() {
setSetupData(null);
toast({ title: 'MFA removed', variant: 'success' });
} catch (err) {
toast({ title: 'Failed to remove MFA', description: String(err), variant: 'error' });
toast({ title: 'Failed to remove MFA', description: errorMessage(err), variant: 'error' });
}
}
@@ -287,7 +288,7 @@ function MfaEnforcementToggle() {
await updateSettings.mutateAsync({ mfaRequired: false });
toast({ title: 'MFA requirement disabled for all members', variant: 'success' });
} catch (err) {
toast({ title: 'Failed to update MFA setting', description: String(err), variant: 'error' });
toast({ title: 'Failed to update MFA setting', description: errorMessage(err), variant: 'error' });
}
}
@@ -297,7 +298,7 @@ function MfaEnforcementToggle() {
setConfirmEnable(false);
toast({ title: 'MFA is now required for all members', variant: 'success' });
} catch (err) {
toast({ title: 'Failed to update MFA setting', description: String(err), variant: 'error' });
toast({ title: 'Failed to update MFA setting', description: errorMessage(err), variant: 'error' });
}
}
@@ -365,7 +366,7 @@ export function SettingsPage() {
setNewPassword('');
setConfirmPassword('');
} catch (err) {
toast({ title: 'Failed to change password', description: String(err), variant: 'error' });
toast({ title: 'Failed to change password', description: errorMessage(err), variant: 'error' });
}
}
@@ -475,7 +476,7 @@ export function SettingsPage() {
toast({ title: 'Server admin password reset successfully', variant: 'success' });
setServerAdminPw('');
} catch (err) {
toast({ title: 'Failed to reset server admin password', description: String(err), variant: 'error' });
toast({ title: 'Failed to reset server admin password', description: errorMessage(err), variant: 'error' });
}
}}
style={{ display: 'flex', flexDirection: 'column', gap: 16, marginTop: 12 }}