diff --git a/apps/backoffice/src/app/features/dashboard/pages/DashboardPage/index.tsx b/apps/backoffice/src/app/features/dashboard/pages/DashboardPage/index.tsx
index 443b66eae..927ca47a3 100644
--- a/apps/backoffice/src/app/features/dashboard/pages/DashboardPage/index.tsx
+++ b/apps/backoffice/src/app/features/dashboard/pages/DashboardPage/index.tsx
@@ -11,7 +11,7 @@ import {
} from '@mantine/core';
import { IconChevronRight } from '@tabler/icons-react';
import { useGetAssignedToMeQuery, useGetQueueQuery } from '@ema-platform/api';
-import { AdvancedTable, useServerTable } from '@ema-platform/ui';
+import { AdvancedTable, PageLoader, useServerTable } from '@ema-platform/ui';
import { dashboardQueueColumns } from './columns';
/**
@@ -28,11 +28,7 @@ export function DashboardPage() {
const table = useServerTable();
if (queue.isLoading || mine.isLoading) {
- return (
-
-
-
- );
+ return ;
}
const unclaimed = queue.data?.items ?? [];
diff --git a/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx b/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx
index e49efe907..6c09dd93b 100644
--- a/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx
+++ b/apps/backoffice/src/app/features/exam/pages/ExamDetailPage.tsx
@@ -54,6 +54,7 @@ import { QuestionAssigner } from '../components/QuestionAssigner';
import { RecordResultModal } from '../../result/components/RecordResultModal';
import { ExamCandidatesPanel } from '../components/ExamCandidatesPanel';
import { ExamIncidentsPanel } from '../components/ExamIncidentsPanel';
+import { PageLoader } from '@ema-platform/ui';
import type { ExamStatus, QuestionBrief } from '../types/exam';
const STATUS_COLOR: Record = {
@@ -129,11 +130,7 @@ export function ExamDetailPage() {
}, [allQuestions, exam?.certificationId, exam?.form]);
if (isLoading)
- return (
-
-
-
- );
+ return ;
if (isError || !exam) {
return (
diff --git a/apps/portal/src/app/features/certificates/pages/CertificatesPage/index.tsx b/apps/portal/src/app/features/certificates/pages/CertificatesPage/index.tsx
index 42ab94351..bdba090ea 100644
--- a/apps/portal/src/app/features/certificates/pages/CertificatesPage/index.tsx
+++ b/apps/portal/src/app/features/certificates/pages/CertificatesPage/index.tsx
@@ -31,7 +31,12 @@ import {
useGetMySeaTimeQuery,
} from '@ema-platform/api';
import { useCurrentProfile, usePermissions } from '@ema-platform/auth';
-import { AdvancedTable, notify, useServerTable } from '@ema-platform/ui';
+import {
+ AdvancedTable,
+ PageLoader,
+ notify,
+ useServerTable,
+} from '@ema-platform/ui';
import { useDateDisplayer } from '@ema-platform/shared';
import { certificateColumns } from './columns';
@@ -107,11 +112,7 @@ export function CertificatesPage() {
}
if (loadingProfile || loadingApplications) {
- return (
-
-
-
- );
+ return ;
}
const pagedIssued = issuedTable.paginate(issued);
diff --git a/apps/portal/src/app/features/dashboard/pages/DashboardPage/index.tsx b/apps/portal/src/app/features/dashboard/pages/DashboardPage/index.tsx
index f7ed4a652..17f44e7c4 100644
--- a/apps/portal/src/app/features/dashboard/pages/DashboardPage/index.tsx
+++ b/apps/portal/src/app/features/dashboard/pages/DashboardPage/index.tsx
@@ -36,7 +36,7 @@ import {
useGetMyApplicationsQuery,
useGetMyLicensesQuery,
} from '@ema-platform/api';
-import { AdvancedTable, useServerTable } from '@ema-platform/ui';
+import { AdvancedTable, PageLoader, useServerTable } from '@ema-platform/ui';
import type { IssuedLicense, LicenseApplication } from '@ema-platform/api';
import { LicenseCatalogue } from '../../../licensing/components/LicenseCatalogue';
import { LicenseCard, useRenewLicense } from '../../../licensing/components/LicenseCard';
@@ -108,11 +108,7 @@ export function DashboardPage() {
}
if (isLoading) {
- return (
-
-
-
- );
+ return ;
}
return (
diff --git a/apps/portal/src/app/features/endorsement/pages/EndorsementPage.tsx b/apps/portal/src/app/features/endorsement/pages/EndorsementPage.tsx
index ac7ae28d1..fa33be9d1 100644
--- a/apps/portal/src/app/features/endorsement/pages/EndorsementPage.tsx
+++ b/apps/portal/src/app/features/endorsement/pages/EndorsementPage.tsx
@@ -31,7 +31,7 @@ import {
useGetMyLicensesQuery,
} from '@ema-platform/api';
import { useCurrentProfile } from '@ema-platform/auth';
-import { notify } from '@ema-platform/ui';
+import { PageLoader, notify } from '@ema-platform/ui';
import { useDateDisplayer } from '@ema-platform/shared';
const ENDORSEMENT_TYPE_KEYS = ['ENDORSEMENT_COC', 'ENDORSEMENT_GOC'];
@@ -94,11 +94,7 @@ export function EndorsementPage() {
}
if (loadingProfile || loadingApplications) {
- return (
-
-
-
- );
+ return ;
}
return (
diff --git a/apps/portal/src/app/features/exams/pages/ExamsPage/index.tsx b/apps/portal/src/app/features/exams/pages/ExamsPage/index.tsx
index b4cb0e0cf..17b42f336 100644
--- a/apps/portal/src/app/features/exams/pages/ExamsPage/index.tsx
+++ b/apps/portal/src/app/features/exams/pages/ExamsPage/index.tsx
@@ -12,7 +12,7 @@ import {
Title,
} from '@mantine/core';
import { IconClipboardList } from '@tabler/icons-react';
-import { AdvancedTable, notify, useServerTable } from '@ema-platform/ui';
+import { AdvancedTable, PageLoader, notify, useServerTable } from '@ema-platform/ui';
import { useDateDisplayer } from '@ema-platform/shared';
import {
useApiQuery,
@@ -178,11 +178,7 @@ export function ExamsPage() {
};
if (loadingOpen || loadingMine || loadingResults) {
- return (
-
-
-
- );
+ return ;
}
const pagedRegistrations = registrationTable.paginate(mine ?? []);
diff --git a/apps/portal/src/app/features/licensing/pages/LicenseApplicationPage.tsx b/apps/portal/src/app/features/licensing/pages/LicenseApplicationPage.tsx
index 3c95453c2..33c2cdb48 100644
--- a/apps/portal/src/app/features/licensing/pages/LicenseApplicationPage.tsx
+++ b/apps/portal/src/app/features/licensing/pages/LicenseApplicationPage.tsx
@@ -53,7 +53,7 @@ import {
type ValidationIssue,
type Vessel,
} from '@ema-platform/api';
-import { getCountryCode, getCountryName, ModalFooter } from '@ema-platform/ui';
+import { getCountryCode, getCountryName, ModalFooter, PageLoader } from '@ema-platform/ui';
import {
LICENSE_PERMISSIONS,
PORTAL_PERMISSIONS,
@@ -212,11 +212,7 @@ export function LicenseApplicationPage() {
);
if (loadingConfig || !config || !appId || !application) {
- return (
-
-
-
- );
+ return ;
}
const readOnly = !['DRAFT', 'RESUBMIT_REQUIRED'].includes(application.status);
diff --git a/apps/portal/src/app/features/onboarding/components/RequireOperations.tsx b/apps/portal/src/app/features/onboarding/components/RequireOperations.tsx
index 3e0cde509..e4f0f0c8e 100644
--- a/apps/portal/src/app/features/onboarding/components/RequireOperations.tsx
+++ b/apps/portal/src/app/features/onboarding/components/RequireOperations.tsx
@@ -1,6 +1,6 @@
import { Navigate, useLocation } from 'react-router-dom';
-import { Center, Loader } from '@mantine/core';
import { useGetMyOperatorTypesQuery } from '@ema-platform/api';
+import { PageLoader } from '@ema-platform/ui';
/**
* Sends an applicant who has not said what they operate as to the step that
@@ -75,11 +75,7 @@ export function RequireOperations({ children }: { children: React.ReactNode }) {
}
if (isLoading) {
- return (
-
-
-
- );
+ return ;
}
// A failed lookup must not lock anyone out of the portal — the server still
@@ -93,11 +89,7 @@ export function RequireOperations({ children }: { children: React.ReactNode }) {
// the same tick; deciding on the pre-save cache bounced the applicant
// straight back to the screen they had just completed.
if (isFetching) {
- return (
-
-
-
- );
+ return ;
}
return ;
}
diff --git a/apps/portal/src/app/features/profile/components/RequireSeafarerProfile.tsx b/apps/portal/src/app/features/profile/components/RequireSeafarerProfile.tsx
index 350b0b014..b86057a1e 100644
--- a/apps/portal/src/app/features/profile/components/RequireSeafarerProfile.tsx
+++ b/apps/portal/src/app/features/profile/components/RequireSeafarerProfile.tsx
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
import { Center, Loader } from '@mantine/core';
import { Navigate, useLocation, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
-import { notify } from '@ema-platform/ui';
+import { PageLoader, notify } from '@ema-platform/ui';
import {
PROFILE_FIELD_SECTION,
useCurrentProfile,
@@ -75,11 +75,7 @@ export function RequireSeafarerProfile({ children }: { children: React.ReactNode
if (!gated) return <>{children}>;
if (isLoading) {
- return (
-
-
-
- );
+ return ;
}
// A failed lookup must not lock anyone out — the server still refuses the
@@ -93,11 +89,7 @@ export function RequireSeafarerProfile({ children }: { children: React.ReactNode
// headed back here in the same tick — deciding on the pre-save cache would
// bounce them off the screen they just finished.
if (isFetching) {
- return (
-
-
-
- );
+ return ;
}
const target = PROFILE_FIELD_SECTION[gaps[0]];
diff --git a/libs/auth/src/lib/components/AuthBootstrap.tsx b/libs/auth/src/lib/components/AuthBootstrap.tsx
index de5395ddb..9a821d4b0 100644
--- a/libs/auth/src/lib/components/AuthBootstrap.tsx
+++ b/libs/auth/src/lib/components/AuthBootstrap.tsx
@@ -71,7 +71,7 @@ export function AuthBootstrap({ children }: { children: ReactNode }) {
// Rendering the router before the session resolves would let the guards
// redirect based on a state that is about to change.
- if (!ready) return ;
+ if (!ready) return ;
return <>{children}>;
}
diff --git a/libs/ui/src/lib/theme/maritime-loader-theme.ts b/libs/ui/src/lib/theme/maritime-loader-theme.ts
index f030b4b19..f38ef3c68 100644
--- a/libs/ui/src/lib/theme/maritime-loader-theme.ts
+++ b/libs/ui/src/lib/theme/maritime-loader-theme.ts
@@ -1,31 +1,47 @@
-import { Button, createTheme, Loader, type MantineLoaderComponent } from '@mantine/core';
+import {
+ ActionIcon,
+ Button,
+ createTheme,
+ Input,
+ Loader,
+ Select,
+ TextInput,
+ type MantineLoaderComponent,
+} from '@mantine/core';
import { MaritimeLoader } from '../components/MaritimeLoader';
/**
- * Registers the branded ship loader as the default Mantine `Loader` type, and
- * opts `Button`'s inline loading state back out to the stock oval — a 1.88:1
- * ship does not fit a button's loader slot.
- *
- * Merge into an app theme with `mergeThemeOverrides(appTheme, maritimeLoaderTheme)`
- * rather than importing this in `libs/shared`: `libs/ui` already depends on
- * `libs/shared`, so the reverse import would be circular.
+ * Registers the branded Ethiopian Maritime Authority loader as the default Mantine `Loader` type,
+ * sets prominent 80px size for page/section loaders, and opts inline control loaders
+ * (buttons, inputs, select fields) back out to the compact oval spinner.
*/
export const maritimeLoaderTheme = createTheme({
components: {
Loader: Loader.extend({
defaultProps: {
- // Mantine types custom loaders as ForwardRefExoticComponent; a plain
- // function component satisfies the runtime contract (it forwards a
- // ref as a normal prop under React 19) but not the structural type.
loaders: {
...Loader.defaultLoaders,
maritime: MaritimeLoader as unknown as MantineLoaderComponent,
},
type: 'maritime',
+ size: 80,
},
}),
Button: Button.extend({
defaultProps: { loaderProps: { type: 'oval' } },
}),
+ ActionIcon: ActionIcon.extend({
+ defaultProps: { loaderProps: { type: 'oval' } },
+ }),
+ Input: Input.extend({
+ defaultProps: { loaderProps: { type: 'oval' } },
+ }),
+ TextInput: TextInput.extend({
+ defaultProps: { loaderProps: { type: 'oval' } },
+ }),
+ Select: Select.extend({
+ defaultProps: { loaderProps: { type: 'oval' } },
+ }),
},
});
+