feat(frontend): Add comprehensive error handling for admin API

Phase 2: User-Friendly Error Handling

 Error Handler Service:
- Created adminErrorHandler.js with handleAdminError()
- User-friendly SweetAlert2 dialogs for all error types:
  * 403 Unauthorized - Clear admin token instructions
  * 429 Rate Limit - Wait and retry message
  * 404 Not Found - Resource not found
  * 500 Server Error - Internal server error
  * Generic errors with context

 Integrated Error Handling in all Admin Components:
- ModerationGroupsPage.js (all 6 admin operations)
- ModerationGroupImagesPage.js (group loading)
- DeletionLogSection.js (log loading + statistics)
- ConsentCheckboxes.js (platform loading)

 Error Context Messages:
- "Gruppe laden"
- "Gruppe freigeben"
- "Gruppe löschen"
- "Bild löschen"
- "Consent-Export"
- "Plattformen laden"
- "Lösch-Log laden"
- "Statistiken laden"

 Benefits:
- Clear technical details for admins in error dialogs
- Context-specific error messages
- Consistent error handling across all admin features
- Better debugging with detailed 403 instructions
This commit is contained in:
Matthias Lotz 2025-11-16 18:56:21 +01:00
parent cb640576f4
commit 6effded8bf
5 changed files with 108 additions and 16 deletions

View File

@ -21,6 +21,7 @@ import InfoIcon from '@mui/icons-material/Info';
// Services
import { adminGet } from '../../services/adminApi';
import { handleAdminError } from '../../services/adminErrorHandler';
const DeletionLogSection = () => {
const [deletions, setDeletions] = useState([]);
@ -46,7 +47,7 @@ const DeletionLogSection = () => {
setDeletions(data.deletions || []);
setError(null);
} catch (error) {
console.error('Fehler beim Laden des Lösch-Logs:', error);
await handleAdminError(error, 'Lösch-Log laden');
setError('Fehler beim Laden des Lösch-Logs');
} finally {
setLoading(false);
@ -58,7 +59,7 @@ const DeletionLogSection = () => {
const data = await adminGet('/api/admin/deletion-log/stats');
setStatistics(data.statistics || null);
} catch (error) {
console.error('Fehler beim Laden der Statistiken:', error);
await handleAdminError(error, 'Statistiken laden');
}
};

View File

@ -11,6 +11,7 @@ import {
// Services
import { adminGet } from '../../../services/adminApi';
import { handleAdminError } from '../../../services/adminErrorHandler';
import InfoIcon from '@mui/icons-material/Info';
import FacebookIcon from '@mui/icons-material/Facebook';
import InstagramIcon from '@mui/icons-material/Instagram';
@ -59,7 +60,7 @@ function ConsentCheckboxes({
setPlatforms(data);
setError(null);
} catch (error) {
console.error('Error loading platforms:', error);
await handleAdminError(error, 'Plattformen laden');
setError('Plattformen konnten nicht geladen werden');
} finally {
setLoading(false);

View File

@ -4,6 +4,7 @@ import { Container, Box } from '@mui/material';
// Services
import { adminGet, adminRequest } from '../../services/adminApi';
import { handleAdminError } from '../../services/adminErrorHandler';
// Components
import Navbar from '../ComponentUtils/Headers/Navbar';
@ -51,6 +52,7 @@ const ModerationGroupImagesPage = () => {
setGroup(transformedData);
} catch (e) {
await handleAdminError(e, 'Gruppe laden');
setError('Fehler beim Laden der Gruppe');
} finally {
setLoading(false);

View File

@ -7,6 +7,7 @@ import Swal from 'sweetalert2/dist/sweetalert2.js';
// Services
import { adminGet, adminRequest, adminDownload } from '../../services/adminApi';
import { handleAdminError } from '../../services/adminErrorHandler';
// Components
import Navbar from '../ComponentUtils/Headers/Navbar';
@ -40,7 +41,7 @@ const ModerationGroupsPage = () => {
const data = await adminGet('/api/admin/social-media/platforms');
setPlatforms(data);
} catch (error) {
console.error('Fehler beim Laden der Plattformen:', error);
await handleAdminError(error, 'Plattformen laden');
}
};
@ -68,7 +69,7 @@ const ModerationGroupsPage = () => {
const data = await adminGet(url);
setGroups(data.groups);
} catch (error) {
console.error('Fehler beim Laden der Moderations-Gruppen:', error);
await handleAdminError(error, 'Moderations-Gruppen laden');
setError('Fehler beim Laden der Gruppen');
} finally {
setLoading(false);
@ -101,7 +102,7 @@ const ModerationGroupsPage = () => {
showConfirmButton: false
});
} catch (error) {
console.error('Fehler beim Freigeben der Gruppe:', error);
await handleAdminError(error, 'Gruppe freigeben');
await Swal.fire({
icon: 'error',
title: 'Fehler',
@ -139,9 +140,7 @@ const ModerationGroupsPage = () => {
));
} catch (error) {
console.error('Fehler beim Löschen des Bildes:', error);
console.error('Error details:', error.message, error.stack);
alert('Fehler beim Löschen des Bildes: ' + error.message);
await handleAdminError(error, 'Bild löschen');
}
};
@ -159,8 +158,7 @@ const ModerationGroupsPage = () => {
setShowImages(false);
}
} catch (error) {
console.error('Fehler beim Löschen der Gruppe:', error);
alert('Fehler beim Löschen der Gruppe');
await handleAdminError(error, 'Gruppe löschen');
}
};
@ -190,11 +188,7 @@ const ModerationGroupsPage = () => {
showConfirmButton: false
});
} catch (error) {
console.error('Fehler beim Export:', error);
await Swal.fire({
icon: 'error',
title: 'Fehler',
text: 'Fehler beim Export der Consent-Daten: ' + error.message
await handleAdminError(error, 'Consent-Export');
});
}
};

View File

@ -0,0 +1,94 @@
import Swal from 'sweetalert2/dist/sweetalert2.js';
/**
* Zentrale Error-Handler für Admin API Fehler
*/
/**
* Behandelt Admin API Fehler mit benutzerfreundlichen Meldungen
* @param {Error} error - Der aufgetretene Fehler
* @param {string} context - Kontext der Operation (z.B. "Gruppe laden")
* @returns {Promise<void>}
*/
export const handleAdminError = async (error, context = 'Operation') => {
console.error(`Admin API Error [${context}]:`, error);
// 403 Unauthorized - Admin Token fehlt oder ungültig
if (error.message.includes('Unauthorized') || error.message.includes('403')) {
await Swal.fire({
icon: 'error',
title: 'Authentifizierung fehlgeschlagen',
html: `
<p><strong>Admin-Token fehlt oder ist ungültig.</strong></p>
<p>Bitte kontaktieren Sie den Administrator.</p>
<hr>
<small>
<strong>Technische Details:</strong><br>
- Prüfen Sie die REACT_APP_ADMIN_API_KEY Variable<br>
- Token muss mit Backend ADMIN_API_KEY übereinstimmen<br>
- Kontext: ${context}
</small>
`,
confirmButtonText: 'OK',
confirmButtonColor: '#d33'
});
return;
}
// 429 Rate Limit
if (error.message.includes('Too many requests') || error.message.includes('429')) {
await Swal.fire({
icon: 'warning',
title: 'Zu viele Anfragen',
text: 'Bitte warten Sie einen Moment und versuchen Sie es erneut.',
timer: 3000,
showConfirmButton: false
});
return;
}
// 404 Not Found
if (error.message.includes('404')) {
await Swal.fire({
icon: 'error',
title: 'Nicht gefunden',
text: `Die angeforderte Ressource wurde nicht gefunden.`,
confirmButtonText: 'OK'
});
return;
}
// 500 Server Error
if (error.message.includes('500')) {
await Swal.fire({
icon: 'error',
title: 'Server-Fehler',
text: 'Ein interner Server-Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.',
confirmButtonText: 'OK'
});
return;
}
// Generischer Fehler
await Swal.fire({
icon: 'error',
title: `Fehler: ${context}`,
text: error.message || 'Ein unbekannter Fehler ist aufgetreten.',
confirmButtonText: 'OK'
});
};
/**
* Wrapper für async Operationen mit automatischem Error-Handling
* @param {Function} operation - Die auszuführende async Operation
* @param {string} context - Kontext der Operation
* @returns {Promise<any>} - Ergebnis der Operation oder null bei Fehler
*/
export const withErrorHandling = async (operation, context) => {
try {
return await operation();
} catch (error) {
await handleAdminError(error, context);
return null;
}
};