diff --git a/apps/backoffice/src/app/i18n/locales/am.ts b/apps/backoffice/src/app/i18n/locales/am.ts
index 25652bbac..2815a5723 100644
--- a/apps/backoffice/src/app/i18n/locales/am.ts
+++ b/apps/backoffice/src/app/i18n/locales/am.ts
@@ -15,19 +15,6 @@ export const am: Translations = {
skipToContent: "ወደ ዋናው ይዘት ዝለል",
},
- notFound: {
- kicker: 'ስህተት 404 — ገጹ አልተገኘም',
- title: 'ከካርታው ውጭ ወጥተዋል',
- description:
- 'ይህ ገጽ የለም፣ ተዛውሯል፣ ወይም ለመለያዎ አይገኝም። አገናኙም ጊዜው ያለፈበት ሊሆን ይችላል።',
- home: 'ወደ ዳሽቦርድ ተመለስ',
- back: 'ተመለስ',
- suggestions: 'ወይም ወደ ሌላ ጠቃሚ ቦታ ይሂዱ',
- dashboardDesc: 'ወረፋዎችዎ እና የዛሬው ሥራዎ',
- analyticsDesc: 'መጠኖች፣ የማጠናቀቂያ ጊዜ እና አዝማሚያዎች',
- profileDesc: 'መለያዎ እና ምርጫዎችዎ',
- },
-
msg: {
genericError: "የሆነ ስህተት ተፈጥሯል። እባክዎ እንደገና ይሞክሩ።",
serverError: "የሰርቨር ስህተት። እባክዎ ቆየት ብለው እንደገና ይሞክሩ።",
diff --git a/apps/backoffice/src/app/i18n/locales/en.ts b/apps/backoffice/src/app/i18n/locales/en.ts
index 27366991b..927a73b03 100644
--- a/apps/backoffice/src/app/i18n/locales/en.ts
+++ b/apps/backoffice/src/app/i18n/locales/en.ts
@@ -15,21 +15,6 @@ export const en = {
skipToContent: 'Skip to main content',
},
- // The page a wrong URL lands on. Labels for its suggested destinations come
- // from `nav`, so they cannot drift from the sidebar.
- notFound: {
- kicker: 'Error 404 — page not found',
- title: "You've drifted off the chart",
- description:
- 'This page does not exist, has moved, or is not available to your account. The link may be out of date.',
- home: 'Back to dashboard',
- back: 'Go back',
- suggestions: 'Or head somewhere useful',
- dashboardDesc: "Your queues and today's workload",
- analyticsDesc: 'Volumes, turnaround and trends',
- profileDesc: 'Your account and preferences',
- },
-
msg: {
genericError: 'Something went wrong. Please try again.',
serverError: 'Server error. Please try again later.',
diff --git a/apps/backoffice/src/app/router/index.tsx b/apps/backoffice/src/app/router/index.tsx
index 2fe4f9a9f..0cc4aa8a8 100644
--- a/apps/backoffice/src/app/router/index.tsx
+++ b/apps/backoffice/src/app/router/index.tsx
@@ -1,5 +1,4 @@
import type { ReactNode } from 'react';
-import { useTranslation } from 'react-i18next';
import {
createBrowserRouter,
RouterProvider,
@@ -13,12 +12,7 @@ import {
RequirePermission,
LICENSE_PERMISSIONS as P,
} from '@ema-platform/auth';
-import { NotFoundPage, ThemeGallery } from '@ema-platform/ui';
-import {
- IconChartBar,
- IconLayoutDashboard,
- IconUserCircle,
-} from '@tabler/icons-react';
+import { ThemeGallery } from '@ema-platform/ui';
import { AuthLayout } from '../layouts/AuthLayout';
import { BackofficeLayout } from '../layouts/BackofficeLayout';
import { ProtectedRoute } from './ProtectedRoute';
@@ -73,41 +67,6 @@ const guard = (anyOf: string[], element: ReactNode) => (
{element}
);
-/**
- * Only the destinations every signed-in officer can reach — suggesting a
- * permission-gated queue would swap one dead end for another. Labels come from
- * `nav`, the same keys the sidebar uses, so the two cannot drift apart.
- */
-function NotFound() {
- const { t } = useTranslation();
-
- return (
-
- );
-}
-
const router = createBrowserRouter([
{
element: ,
@@ -204,10 +163,8 @@ const router = createBrowserRouter([
},
],
},
- // Standalone by design — a wrong URL should read as a wrong URL, not bounce
- // an officer through the layout of a page they never asked for.
- { path: '/404', element: },
- { path: '*', element: },
+ { path: '/404', element:
Page not found
},
+ { path: '*', element: },
]);
export function AppRouter() {
diff --git a/apps/portal/src/app/i18n/locales/am.ts b/apps/portal/src/app/i18n/locales/am.ts
index 31016b290..3cb4288cf 100644
--- a/apps/portal/src/app/i18n/locales/am.ts
+++ b/apps/portal/src/app/i18n/locales/am.ts
@@ -14,20 +14,6 @@ export const am: Translations = {
skipToContent: 'ወደ ዋናው ይዘት ዝለል',
},
- notFound: {
- kicker: 'ስህተት 404 — ገጹ አልተገኘም',
- title: 'ከካርታው ውጭ ወጥተዋል',
- description:
- 'ይህ ገጽ የለም፣ ተዛውሯል፣ ወይም ለመለያዎ አይገኝም። አገናኙም ጊዜው ያለፈበት ሊሆን ይችላል።',
- home: 'ወደ ዳሽቦርድ ተመለስ',
- back: 'ተመለስ',
- suggestions: 'ወይም ወደ ሌላ ጠቃሚ ቦታ ይሂዱ',
- dashboardDesc: 'ማመልከቻዎችዎ፣ ፈቃዶችዎ እና ቀጣይ እርምጃዎች',
- documentsDesc: 'የጫኗቸው ሁሉም ሰነዶች',
- notificationsDesc: 'ስላቀረቧቸው ጉዳዮች ማሻሻያዎች',
- supportDesc: 'ለእገዛ ባለሥልጣኑን ያግኙ',
- },
-
msg: {
genericError: 'የሆነ ስህተት ተፈጥሯል። እባክዎ እንደገና ይሞክሩ።',
serverError: 'የሰርቨር ስህተት። እባክዎ ቆየት ብለው እንደገና ይሞክሩ።',
diff --git a/apps/portal/src/app/i18n/locales/en.ts b/apps/portal/src/app/i18n/locales/en.ts
index 865e5dd16..e42942f78 100644
--- a/apps/portal/src/app/i18n/locales/en.ts
+++ b/apps/portal/src/app/i18n/locales/en.ts
@@ -14,22 +14,6 @@ export const en = {
skipToContent: 'Skip to main content',
},
- // The page a wrong URL lands on. Labels for its suggested destinations come
- // from `nav`, so they cannot drift from the sidebar.
- notFound: {
- kicker: 'Error 404 — page not found',
- title: "You've drifted off the chart",
- description:
- 'This page does not exist, has moved, or is not available to your account. The link may be out of date.',
- home: 'Back to dashboard',
- back: 'Go back',
- suggestions: 'Or head somewhere useful',
- dashboardDesc: 'Your applications, licences and next steps',
- documentsDesc: 'Everything you have uploaded',
- notificationsDesc: 'Updates on what you have filed',
- supportDesc: 'Reach the authority for help',
- },
-
msg: {
genericError: 'Something went wrong. Please try again.',
serverError: 'Server error. Please try again later.',
diff --git a/apps/portal/src/app/router.tsx b/apps/portal/src/app/router.tsx
index 8833fe3d0..3fadb35db 100644
--- a/apps/portal/src/app/router.tsx
+++ b/apps/portal/src/app/router.tsx
@@ -1,12 +1,5 @@
import { createBrowserRouter, Navigate } from "react-router-dom";
-import { useTranslation } from "react-i18next";
-import { NotFoundPage, ThemeGallery } from "@ema-platform/ui";
-import {
- IconBell,
- IconFolder,
- IconLayoutDashboard,
- IconLifebuoy,
-} from "@tabler/icons-react";
+import { ThemeGallery } from "@ema-platform/ui";
import { PortalLayout } from "./layouts/PortalLayout";
import { ProtectedRoute } from "./components/ProtectedRoute";
import { LandingRoute } from "./components/LandingRoute";
@@ -64,47 +57,6 @@ import { WaiverPage } from "./features/waiver/pages/WaiverPage";
import { VesselRegistrationStatusPage } from "./features/vessel-registration/pages/VesselRegistrationStatusPage";
-/**
- * Only routes with no permission gate, so a suggestion can never land the
- * applicant on a second refusal. Labels come from `nav`, the same keys the
- * sidebar uses, so the two cannot drift apart.
- */
-function NotFound() {
- const { t } = useTranslation();
-
- return (
-
- );
-}
-
export const router = createBrowserRouter([
// Public landing page — institutional overview + role-based entry points.
{ path: "/", element: },
@@ -531,11 +483,7 @@ export const router = createBrowserRouter([
element: ,
},
- // A typo'd URL used to be redirected silently to the dashboard, which read as
- // the app ignoring the link rather than as a missing page. Say so instead,
- // and offer the way back — the primary action is /dashboard, so a signed-in
- // user is one click from where the redirect used to dump them, and
- // ProtectedRoute still sends anonymous visitors on to / (landing).
- { path: "/404", element: },
- { path: "*", element: },
+ // A typo'd URL should not maroon a signed-in user on the marketing page —
+ // ProtectedRoute sends anonymous visitors on to / (landing) instead.
+ { path: "*", element: },
]);
diff --git a/libs/ui/src/index.ts b/libs/ui/src/index.ts
index 3b374bc9b..598162325 100644
--- a/libs/ui/src/index.ts
+++ b/libs/ui/src/index.ts
@@ -9,7 +9,6 @@ export * from "./lib/feedback/notify";
export * from "./lib/feedback/FeatureUnavailable";
export * from "./lib/feedback/EmptyState";
export * from "./lib/feedback/ErrorState";
-export * from "./lib/feedback/NotFoundPage";
export * from "./lib/feedback/PageLoader";
export * from "./lib/feedback/StatusBadge";
export * from "./lib/components/MaritimeLoader";
diff --git a/libs/ui/src/lib/feedback/NotFoundPage.tsx b/libs/ui/src/lib/feedback/NotFoundPage.tsx
deleted file mode 100644
index 60b84b125..000000000
--- a/libs/ui/src/lib/feedback/NotFoundPage.tsx
+++ /dev/null
@@ -1,345 +0,0 @@
-import type { ReactNode } from 'react';
-import {
- Box,
- Button,
- Center,
- Code,
- Container,
- Group,
- Paper,
- SimpleGrid,
- Stack,
- Text,
- ThemeIcon,
- Title,
-} from '@mantine/core';
-import {
- IconArrowLeft,
- IconChevronRight,
- IconLifebuoy,
- type Icon,
-} from '@tabler/icons-react';
-import { Link, useLocation, useNavigate } from 'react-router-dom';
-import { useTranslation } from 'react-i18next';
-import './not-found.css';
-
-export interface NotFoundLink {
- label: string;
- /** One line on why someone would go here instead. */
- description?: string;
- to: string;
- icon?: Icon;
-}
-
-export interface NotFoundPageProps {
- /** Where the primary action goes. Each app passes its own signed-in home. */
- homePath?: string;
- homeLabel?: string;
- /**
- * A handful of real destinations. A dead end that only says "go home" makes
- * the visitor guess; naming the pages they probably wanted does not.
- */
- links?: NotFoundLink[];
- /** Anything extra below the actions — a support link, a contact line. */
- footer?: ReactNode;
- /**
- * Fills the viewport. Turn this off when the page renders inside an app
- * layout, where the shell already owns the height.
- */
- fullHeight?: boolean;
-}
-
-/** 16 bezel ticks; every fourth is a cardinal point and drawn longer. */
-const TICKS = Array.from({ length: 16 }, (_, i) => i * 22.5);
-
-const WAVELENGTH = 1440;
-
-/**
- * Two wavelengths of swell as one continuous path.
- *
- * One path rather than two abutting copies: the band slides by exactly one
- * wavelength and loops, so it needs twice the width it shows, and two filled
- * shapes meeting at a shared edge leave an anti-aliased hairline down the
- * screen. The curve is symmetric about its midpoint, so the repeat has no kink.
- */
-const WAVE = (() => {
- const cycle = (x: number) =>
- ` C${x + 120},48 ${x + 240},48 ${x + 360},72` +
- ` C${x + 480},96 ${x + 600},96 ${x + 720},72` +
- ` C${x + 840},48 ${x + 960},48 ${x + 1080},72` +
- ` C${x + 1200},96 ${x + 1320},96 ${x + 1440},72`;
-
- return `M0,72${cycle(0)}${cycle(WAVELENGTH)} L${WAVELENGTH * 2},140 L0,140 Z`;
-})();
-
-/** The compass that cannot find the page — the illustration, not information. */
-function LostCompass() {
- return (
-
- );
-}
-
-/**
- * The page a wrong URL lands on.
- *
- * Both apps used to swallow a bad path — the portal redirected it to the
- * dashboard, the backoffice rendered a bare `
`. Neither told anyone what
- * had happened, so a typo'd or stale link looked like the app misbehaving.
- * This says plainly what was asked for, that it does not exist, and where to
- * go instead.
- */
-export function NotFoundPage({
- homePath = '/',
- homeLabel,
- links,
- footer,
- fullHeight = true,
-}: NotFoundPageProps) {
- const { t } = useTranslation();
- const navigate = useNavigate();
- const { pathname } = useLocation();
-
- return (
-
-
-
-
-
-
-
- {/* The viewBox shows one wavelength; the path carries two, and the
- band slides across the difference. */}
-
-
-
-
-
-
-
- {/* Decorative: the heading below carries the meaning for anyone
- who is not looking at it. */}
-
- 4
-
- 4
-
-
-
-
- {t('notFound.kicker', 'Error 404 — page not found')}
-
-
- {t('notFound.title', "You've drifted off the chart")}
-
-
- {t(
- 'notFound.description',
- 'This page does not exist, has moved, or is not available to your account. The link may be out of date.',
- )}
-
-
- {pathname}
-
-
-
-
- }
- >
- {homeLabel ?? t('notFound.home', 'Back to dashboard')}
-
- }
- onClick={() => navigate(-1)}
- >
- {t('notFound.back', 'Go back')}
-
-
-
- {links && links.length > 0 && (
-
-
- {t('notFound.suggestions', 'Or head somewhere useful')}
-
-
- {links.map((link) => {
- const LinkIcon = link.icon;
- return (
-
-
- {LinkIcon && (
-
-
-
- )}
-