feat: integrate error handling across various components

- Added `useErrorHandler` hook to centralize error handling logic.
- Updated components in the backoffice and portal applications to utilize the new error handling mechanism, replacing direct notify calls with `handleError` for improved error messaging.
- Enhanced localization files to include generic error messages for better user feedback.
- Refactored error handling in forms and API interactions to ensure consistent user experience across the application.
This commit is contained in:
estifanos
2026-07-24 09:08:41 +00:00
parent 4ccf27c044
commit 8f7c163b9e
28 changed files with 229 additions and 121 deletions

View File

@@ -2,6 +2,7 @@ export * from './lib/input/BilingualInput';
export * from './lib/feedback/ConfirmModal';
export * from './lib/feedback/ApiErrorAlert';
export * from './lib/feedback/notify';
export * from './lib/feedback/use-error-handler';
export * from './lib/layout/AppHeader';
export * from './lib/layout/AppSidebar';
export * from './lib/layout/BrandAvatar';

View File

@@ -0,0 +1,72 @@
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { notify } from './notify';
// Recursive: first non-empty string in a string | array | { message | error | detail }. Never throws.
function extractMessage(value: unknown): string | null {
if (value == null) return null;
if (typeof value === 'string') return value.trim() || null;
if (Array.isArray(value)) {
const parts = value.map(extractMessage).filter(Boolean) as string[];
return parts.length ? parts.join(', ') : null;
}
if (typeof value === 'object') {
const o = value as Record<string, unknown>;
return extractMessage(o.message) ?? extractMessage(o.error) ?? extractMessage(o.detail) ?? null;
}
return null;
}
// HTTP status OR RTK string status (FETCH_ERROR/TIMEOUT_ERROR/PARSING_ERROR/CUSTOM_ERROR) → i18n key.
function statusKeyFor(status: number | string | undefined): string {
if (status === 400 || status === 422) return 'msg.validationError';
if (status === 401) return 'msg.authError';
if (status === 403) return 'msg.permissionError';
if (status === 404) return 'msg.notFoundError';
if (status === 413) return 'msg.fileTooLarge';
if (typeof status === 'number' && status >= 500) return 'msg.serverError';
if (typeof status === 'string') return 'msg.networkError';
return 'msg.genericError';
}
function logError(err: unknown): void {
if (err && typeof err === 'object' && 'status' in err) {
console.error('[API ERROR]', (err as { status?: unknown }).status, (err as { data?: unknown }).data);
return;
}
console.error('Error caught:', err);
}
export function useErrorHandler() {
const { t } = useTranslation();
// Priority: backend message (FetchBaseQueryError.data) → Error.message → status/network fallback key.
const getErrorMessage = useCallback(
(err: unknown): string => {
let status: number | string | undefined;
if (err && typeof err === 'object' && ('status' in err || 'data' in err)) {
status = (err as { status?: number | string }).status;
const fromData = extractMessage((err as { data?: unknown }).data);
if (fromData) return fromData;
}
if (err instanceof Error) {
const fromError = extractMessage(err.message);
if (fromError) return fromError;
}
return t(statusKeyFor(status));
},
[t],
);
const handleError = useCallback(
(err: unknown): string => {
logError(err);
const message = getErrorMessage(err);
notify.error(message);
return message;
},
[getErrorMessage],
);
return { getErrorMessage, handleError };
}