mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-09-08 05:25:43 +00:00
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:
@@ -19,6 +19,7 @@ import {
|
||||
rem,
|
||||
} from '@mantine/core';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { useErrorHandler } from '@ema-platform/ui';
|
||||
import {
|
||||
IconArrowRight,
|
||||
IconBook2,
|
||||
@@ -98,6 +99,7 @@ export function CertificatesPage() {
|
||||
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
||||
const [previewTitle, setPreviewTitle] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { handleError } = useErrorHandler();
|
||||
|
||||
const openPreview = async (profileId: string, title: string) => {
|
||||
setLoading(true);
|
||||
@@ -107,11 +109,7 @@ export function CertificatesPage() {
|
||||
setPreviewTitle(title);
|
||||
setPreviewUrl(url);
|
||||
} catch (err) {
|
||||
notifications.show({
|
||||
color: 'red',
|
||||
title: 'Error',
|
||||
message: err instanceof Error ? err.message : 'Could not generate certificate',
|
||||
});
|
||||
handleError(err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -127,11 +125,7 @@ export function CertificatesPage() {
|
||||
message: 'Certificate PDF downloaded successfully',
|
||||
});
|
||||
} catch (err) {
|
||||
notifications.show({
|
||||
color: 'red',
|
||||
title: 'Error',
|
||||
message: err instanceof Error ? err.message : 'Could not download certificate',
|
||||
});
|
||||
handleError(err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useApiMutation } from '@ema-platform/api';
|
||||
import { notify } from '@ema-platform/ui';
|
||||
import { notify, useErrorHandler } from '@ema-platform/ui';
|
||||
import { authStorage, setUser, logout, type AuthUser } from '@ema-platform/auth';
|
||||
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
|
||||
import {
|
||||
@@ -122,6 +122,7 @@ export function ProfileSetupPage() {
|
||||
const [addressTrigger] = useApiMutation<unknown>();
|
||||
const [meTrigger] = useApiMutation<AuthUser>();
|
||||
const [fetchProfessions] = useApiMutation<{ count: number; items: Array<{ id: string; name: { en: string } }> }>();
|
||||
const { handleError } = useErrorHandler();
|
||||
const fetched = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -265,8 +266,8 @@ export function ProfileSetupPage() {
|
||||
|
||||
notify.success('Profile setup complete!');
|
||||
navigate('/dashboard');
|
||||
} catch {
|
||||
notify.error('Failed to save profile. Please try again.');
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { notify, PageHeader } from '@ema-platform/ui';
|
||||
import { notify, PageHeader, useErrorHandler } from '@ema-platform/ui';
|
||||
import { useApiMutation } from '@ema-platform/api';
|
||||
import { authStorage, setUser, setCurrentProfile } from '@ema-platform/auth';
|
||||
import type { CurrentProfile } from '@ema-platform/auth';
|
||||
@@ -86,6 +86,7 @@ export function ProfilePage() {
|
||||
const user = useAppSelector((state) => state.auth.user);
|
||||
const currentProfile = useAppSelector((state) => state.auth.currentProfile);
|
||||
const { colorScheme, setColorScheme } = useMantineColorScheme();
|
||||
const { handleError } = useErrorHandler();
|
||||
|
||||
const [updateTrigger] = useApiMutation<AuthUser>();
|
||||
const [meTrigger] = useApiMutation<AuthUser>();
|
||||
@@ -255,8 +256,8 @@ export function ProfilePage() {
|
||||
dispatch(setUser(me));
|
||||
|
||||
notify.success(t('profile.profileUpdated'));
|
||||
} catch {
|
||||
notify.error(t('profile.updateFailed'));
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
} finally {
|
||||
setIsSavingProfile(false);
|
||||
}
|
||||
@@ -286,8 +287,8 @@ export function ProfilePage() {
|
||||
}).unwrap();
|
||||
|
||||
notify.success('Profile updated');
|
||||
} catch {
|
||||
notify.error('Failed to update profile');
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
} finally {
|
||||
setIsSavingMaritime(false);
|
||||
}
|
||||
@@ -320,8 +321,8 @@ export function ProfilePage() {
|
||||
}).unwrap();
|
||||
|
||||
notify.success('Address updated');
|
||||
} catch {
|
||||
notify.error('Failed to update address');
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
} finally {
|
||||
setIsSavingAddress(false);
|
||||
}
|
||||
@@ -366,8 +367,8 @@ export function ProfilePage() {
|
||||
|
||||
notify.success(t('profile.passwordChanged'));
|
||||
resetPassword();
|
||||
} catch {
|
||||
notify.error(t('profile.passwordFailed'));
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
} finally {
|
||||
setIsSavingPassword(false);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ import {
|
||||
IconX,
|
||||
} from '@tabler/icons-react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { notify } from '@ema-platform/ui';
|
||||
import { notify, useErrorHandler } from '@ema-platform/ui';
|
||||
import type { Seafarer } from './SeafarerRegistryPage';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -569,12 +569,13 @@ export function SeafarerProfilePage() {
|
||||
const [medicalModal, medicalModalHandlers] = useDisclosure(false);
|
||||
const [seaServiceModal, seaServiceModalHandlers] = useDisclosure(false);
|
||||
const [certModal, certModalHandlers] = useDisclosure(false);
|
||||
const { handleError } = useErrorHandler();
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
fetchSeafarerProfile(id)
|
||||
.then(setProfile)
|
||||
.catch(() => notify.error('Failed to load seafarer profile.'))
|
||||
.catch((e) => handleError(e))
|
||||
.finally(() => setLoading(false));
|
||||
}, [id]);
|
||||
|
||||
@@ -584,8 +585,8 @@ export function SeafarerProfilePage() {
|
||||
await updateSeafarerStatus(profile.id, newStatus);
|
||||
setProfile((p) => p ? { ...p, status: newStatus } : p);
|
||||
notify.success(`Status updated to ${newStatus}.`);
|
||||
} catch {
|
||||
notify.error('Failed to update status.');
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
IconUser,
|
||||
} from '@tabler/icons-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { notify, BilingualInput } from '@ema-platform/ui';
|
||||
import { notify, BilingualInput, useErrorHandler } from '@ema-platform/ui';
|
||||
import type { BilingualValue } from '@ema-platform/ui';
|
||||
import { AmharicDatePicker, toEthiopicDateLabel } from '../../../components/AmharicDatePicker';
|
||||
import { LocationPicker } from '../../location/components/LocationPicker';
|
||||
@@ -257,6 +257,7 @@ export function SeafarerRegistrationPage() {
|
||||
const [active, setActive] = useState(0);
|
||||
const [completed, setCompleted] = useState<number[]>([]);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const { handleError } = useErrorHandler();
|
||||
|
||||
// Step 1 — Personal Information
|
||||
const [firstName, setFirstName] = useState<BilingualValue>({ en: '', am: '' });
|
||||
@@ -312,8 +313,8 @@ export function SeafarerRegistrationPage() {
|
||||
});
|
||||
notify.success(`Registration submitted! Reference: ${result.referenceId}`);
|
||||
navigate('/applications');
|
||||
} catch {
|
||||
notify.error('Submission failed. Please try again.');
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
IconX,
|
||||
} from '@tabler/icons-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { notify } from '@ema-platform/ui';
|
||||
import { notify, useErrorHandler } from '@ema-platform/ui';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
@@ -194,11 +194,12 @@ export function SeafarerRegistryPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [search, setSearch] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState<string | null>(null);
|
||||
const { handleError } = useErrorHandler();
|
||||
|
||||
useEffect(() => {
|
||||
fetchSeafarers()
|
||||
.then(setSeafarers)
|
||||
.catch(() => notify.error('Failed to load seafarers.'))
|
||||
.catch((e) => handleError(e))
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
IconUpload,
|
||||
} from '@tabler/icons-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { notify } from '@ema-platform/ui';
|
||||
import { notify, useErrorHandler } from '@ema-platform/ui';
|
||||
import { TelebirrPayment } from '../../payment/components/TelebirrPayment';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -212,6 +212,7 @@ export function SeamanBookApplicationPage() {
|
||||
const [active, setActive] = useState(0);
|
||||
const [completed, setCompleted] = useState<number[]>([]);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const { handleError } = useErrorHandler();
|
||||
|
||||
// BST
|
||||
const [bstData, setBstData] = useState<Record<string, { certNumber: string; issuer: string; issueDate: string; expiryDate: string; file: File | null }>>(() =>
|
||||
@@ -268,8 +269,8 @@ export function SeamanBookApplicationPage() {
|
||||
await new Promise((r) => setTimeout(r, 1400));
|
||||
notify.success('Application submitted! Reference: SB-BTC-2025-001');
|
||||
navigate('/seaman-book');
|
||||
} catch {
|
||||
notify.error('Submission failed. Please try again.');
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,17 @@ export const am: Translations = {
|
||||
tagline: 'የባሕር ፍቃድና የምስክር ወረቀት አገልግሎቶች',
|
||||
},
|
||||
|
||||
msg: {
|
||||
genericError: 'የሆነ ስህተት ተፈጥሯል። እባክዎ እንደገና ይሞክሩ።',
|
||||
serverError: 'የሰርቨር ስህተት። እባክዎ ቆየት ብለው እንደገና ይሞክሩ።',
|
||||
validationError: 'እባክዎ ያስገቡትን መረጃ ያረጋግጡና እንደገና ይሞክሩ።',
|
||||
authError: 'ክፍለ ጊዜዎ አልቋል። እባክዎ እንደገና ይግቡ።',
|
||||
permissionError: 'ይህን ድርጊት ለመፈጸም ፈቃድ የለዎትም።',
|
||||
notFoundError: 'የተጠየቀው ንጥል አልተገኘም።',
|
||||
fileTooLarge: 'ፋይሉ ለመስቀል በጣም ትልቅ ነው።',
|
||||
networkError: 'የአውታረ መረብ ስህተት። ግንኙነትዎን አረጋግጠው እንደገና ይሞክሩ።',
|
||||
},
|
||||
|
||||
language: {
|
||||
label: 'ቋንቋ',
|
||||
en: 'English',
|
||||
|
||||
@@ -5,6 +5,17 @@ export const en = {
|
||||
tagline: 'Maritime licensing & certification services',
|
||||
},
|
||||
|
||||
msg: {
|
||||
genericError: 'Something went wrong. Please try again.',
|
||||
serverError: 'Server error. Please try again later.',
|
||||
validationError: 'Please check your input and try again.',
|
||||
authError: 'Your session has expired. Please sign in again.',
|
||||
permissionError: "You don't have permission to perform this action.",
|
||||
notFoundError: 'The requested item was not found.',
|
||||
fileTooLarge: 'The file is too large to upload.',
|
||||
networkError: 'Network error. Check your connection and try again.',
|
||||
},
|
||||
|
||||
language: {
|
||||
label: 'Language',
|
||||
en: 'English',
|
||||
|
||||
Reference in New Issue
Block a user