feat: add landing page copy and styles for English and Amharic versions

- Introduced `landing-copy.ts` containing text for the public landing page, supporting both English and Amharic languages.
- Added `landing.css` for styling the landing page, ensuring scoped styles to prevent conflicts with other app styles.
- Implemented smooth scrolling and animations for a better user experience.
This commit is contained in:
estifanos
2026-08-14 07:20:42 +00:00
parent d8b01a2003
commit d6b813b697
16 changed files with 1077 additions and 23 deletions

View File

@@ -1,6 +1,9 @@
import { landingAm } from "@ema-platform/ui";
import type { Translations } from "./en";
export const am: Translations = {
landing: landingAm,
app: {
name: "ኢማ አስተዳደር",
shortName: "ኢማ",

View File

@@ -1,4 +1,8 @@
import { landingEn } from '@ema-platform/ui';
export const en = {
landing: landingEn,
app: {
name: 'EMA Admin',
shortName: 'EMA',

View File

@@ -0,0 +1,13 @@
import Cookies from 'js-cookie';
import { LandingPage } from '@ema-platform/ui';
import { authStorage } from '@ema-platform/auth';
/**
* Public `/` — mounts the shared landing page. Backoffice has no /signup
* (enableSignup: false) and no /verify route, so those props are omitted.
*/
export function LandingRoute() {
const token = authStorage.getToken() ?? Cookies.get('auth-token');
return <LandingPage primaryHref={token ? '/dashboard' : '/login'} />;
}

View File

@@ -15,6 +15,7 @@ import {
import { AuthLayout } from '../layouts/AuthLayout';
import { BackofficeLayout } from '../layouts/BackofficeLayout';
import { ProtectedRoute } from './ProtectedRoute';
import { LandingRoute } from './LandingRoute';
import { DashboardPage } from '../features/dashboard/pages/DashboardPage';
import UserManagementPage from '../features/user-management/UserManagementPage';
import { ProfilePage } from '../features/profile/pages/ProfilePage';
@@ -61,7 +62,7 @@ const router = createBrowserRouter([
],
},
{ path: '/um/*', element: <UserManagementPage /> },
{ path: '/', element: <Navigate to="/dashboard" replace /> },
{ path: '/', element: <LandingRoute /> },
{ path: '/profile-setup', element: <Navigate to="/dashboard" replace /> },
{
element: <ProtectedRoute />,
@@ -69,7 +70,6 @@ const router = createBrowserRouter([
{
element: <BackofficeLayout />,
children: [
{ index: true, element: <Navigate to="/dashboard" replace /> },
{ path: 'dashboard', element: <DashboardPage /> },
{ path: 'vessel-registration-head-dashboard', element: guard([P.VIEW_VESSEL_REGISTRY], <VesselRegistrationHeadDashboardPage />) },
{ path: 'logistics-head-dashboard', element: guard(APPLICATION_QUEUE, <LogisticsHeadDashboardPage />) },

View File

@@ -39,7 +39,7 @@ window.__USER_MANAGEMENT_BRANDING__ = {
organizationName: 'Ethiopian Maritime Authority',
logoSrc: '/assets/emaLogo.jpg',
logoAlt: 'EMA Logo',
homePath: '/',
homePath: '/dashboard',
moduleBasePath: '/user-management',
backToAppPath: '/dashboard',
backToAppLabel: 'Back to dashboard',

View File

@@ -0,0 +1,15 @@
import Cookies from 'js-cookie';
import { LandingPage } from '@ema-platform/ui';
import { authStorage } from '@ema-platform/auth';
/**
* Public `/` — mounts the shared landing page with portal-specific routes.
* Auth state is read the same way ProtectedRoute does (token cookie or
* storage fallback) so the header can show "Go to dashboard" instead of
* Login/Sign Up without gating the route itself.
*/
export function LandingRoute() {
const token = authStorage.getToken() ?? Cookies.get('auth-token');
return <LandingPage primaryHref={token ? '/dashboard' : '/login'} signupHref="/signup" verifyHref="/verify" />;
}

View File

@@ -1,6 +1,9 @@
import { landingAm } from '@ema-platform/ui';
import type { Translations } from './en';
export const am: Translations = {
landing: landingAm,
app: {
name: 'ኢባባ ፖርታል',
authority: 'የኢትዮጵያ ባሕር ጉዳዮች ባለሥልጣን',

View File

@@ -1,4 +1,8 @@
import { landingEn } from '@ema-platform/ui';
export const en = {
landing: landingEn,
app: {
name: 'EMA Portal',
authority: 'Ethiopian Maritime Authority',

View File

@@ -1,8 +1,10 @@
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AuthBootstrap, AuthConfigProvider } from '@ema-platform/auth';
import type { ReactNode } from 'react';
import { store } from '../store';
import { i18n } from '../i18n/config';
import { MantineThemeProvider } from './MantineThemeProvider';
import { ErrorBoundary } from '../components/ErrorBoundary';
@@ -26,9 +28,11 @@ export function AppProviders({ children }: { children: ReactNode }) {
enableForgotPassword: true,
}}
>
<MantineThemeProvider>
<AuthBootstrap>{children}</AuthBootstrap>
</MantineThemeProvider>
<I18nextProvider i18n={i18n}>
<MantineThemeProvider>
<AuthBootstrap>{children}</AuthBootstrap>
</MantineThemeProvider>
</I18nextProvider>
</AuthConfigProvider>
</QueryClientProvider>
</Provider>

View File

@@ -1,8 +1,7 @@
import { createBrowserRouter, Navigate } from "react-router-dom";
import { I18nextProvider } from "react-i18next";
import { i18n } from "./i18n/config";
import { PortalLayout } from "./layouts/PortalLayout";
import { ProtectedRoute } from "./components/ProtectedRoute";
import { LandingRoute } from "./components/LandingRoute";
// Auth (standalone pages, no portal chrome)
import {
@@ -58,6 +57,9 @@ import { WaiverPage } from "./features/waiver/pages/WaiverPage";
import { VesselRegistrationStatusPage } from "./features/vessel-registration/pages/VesselRegistrationStatusPage";
export const router = createBrowserRouter([
// Public landing page — institutional overview + role-based entry points.
{ path: "/", element: <LandingRoute /> },
// Public auth pages
{ path: "/login", element: <LoginPage /> },
{ path: "/signup", element: <SignupPage /> },
@@ -100,17 +102,14 @@ export const router = createBrowserRouter([
{
element: (
<ProtectedRoute>
<I18nextProvider i18n={i18n}>
{/* Asks what the applicant operates as before the rest of the
portal, which is filtered by that answer. */}
<RequireOperations>
<PortalLayout />
</RequireOperations>
</I18nextProvider>
{/* Asks what the applicant operates as before the rest of the
portal, which is filtered by that answer. */}
<RequireOperations>
<PortalLayout />
</RequireOperations>
</ProtectedRoute>
),
children: [
{ path: "/", element: <Navigate to="/dashboard" replace /> },
{ path: "/dashboard", element: <DashboardPage /> },
{ path: "/onboarding/operations", element: <OperationsOnboardingPage /> },
@@ -409,5 +408,7 @@ export const router = createBrowserRouter([
element: <Navigate to="/vessel-ownership-transfer" replace />,
},
{ path: "*", element: <Navigate to="/" replace /> },
// A typo'd URL should not maroon a signed-in user on the marketing page —
// ProtectedRoute sends anonymous visitors on to /login exactly as before.
{ path: "*", element: <Navigate to="/dashboard" replace /> },
]);