feat: centralized error notification service (issue #15)

- Create NotificationService with context provider for centralized error/success messaging
- Add NotificationContainer component to render notification stack
- Integrate NotificationProvider into App root
- Refactor BanUnbanForm to use notification service instead of local error state
- Update fetchError utility to optionally use notification callbacks
- Add comprehensive error handling guidelines to Web-Development.md
- Prevent duplicate notifications with deduplication logic
- Support auto-dismiss with configurable TTL per notification type

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-28 08:41:33 +02:00
parent da6433b2cf
commit ae34d98859
8 changed files with 557 additions and 152 deletions

View File

@@ -2,19 +2,40 @@ import { isAuthError } from "../api/client";
/**
* Normalize fetch error handling across hooks.
*
* Handles three error cases:
* 1. Request was aborted — silently ignored (expected cleanup)
* 2. Auth error (401/403) — silently handled by AuthProvider (do not display)
* 3. Other error — stored in component state or notified via callback
*
* @param err - The caught error
* @param setError - State setter to store error message (used when no notification callback)
* @param fallback - Default error message if err is not an Error instance
* @param onError - Optional callback to notify of errors instead of using setError
*/
export function handleFetchError(
err: unknown,
setError: (value: string | null) => void,
fallback: string = "Unknown error",
onError?: (message: string) => void,
): void {
// Abort errors are expected during cleanup — ignore silently
if (err instanceof DOMException && err.name === "AbortError") {
return;
}
// Auth errors are handled globally by AuthProvider — do not display locally
if (isAuthError(err)) {
return;
}
setError(err instanceof Error ? err.message : fallback);
const message = err instanceof Error ? err.message : fallback;
// Use notification callback if provided; otherwise use local state setter
if (onError) {
onError(message);
} else {
setError(message);
}
}