Refactor imports and improve LoginPage validation logic

This commit is contained in:
Estifo77
2026-08-08 09:26:47 +03:00
parent a92580c7be
commit 23675005fc
5 changed files with 311 additions and 249 deletions

View File

@@ -15,6 +15,7 @@ import {
Select,
NumberInput,
Textarea,
Table,
} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { useTranslation } from 'react-i18next';
@@ -26,7 +27,7 @@ import {
IconSend,
IconGavel,
} from '@tabler/icons-react';
import { notify } from '@ema-platform/ui';
import { AdvancedColumn, ModalFooter, notify } from '@ema-platform/ui';
import { extractErrorMessage } from '@ema-platform/api';
import { useGetCertificationsQuery } from '../../certification/api/certification-api';
import {

View File

@@ -1,79 +1,91 @@
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 { 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";
// Auth (standalone pages, no portal chrome)
import { LoginPage, SignupPage, OTPVerificationPage, ForgotPasswordPage, SetPasswordPage } from '@ema-platform/auth';
import {
LoginPage,
SignupPage,
OTPVerificationPage,
ForgotPasswordPage,
SetPasswordPage,
} from "@ema-platform/auth";
// Portal feature pages
import { DashboardPage } from './features/dashboard/pages/DashboardPage';
import { RequireOperations } from './features/onboarding/components/RequireOperations';
import { OperationsOnboardingPage } from './features/onboarding/pages/OperationsOnboardingPage';
import { ProfilePage } from './features/profile/pages/ProfilePage';
import { SupportPage } from './features/support/pages/SupportPage';
import { SeafarerRegistrationPage } from './features/seafarer/pages/SeafarerRegistrationPage';
import { MySeaRecordsPage } from './features/seafarer/pages/MySeaRecordsPage';
import { VerifyCertificatePage } from './features/verify/pages/VerifyCertificatePage';
import { ExamsPage } from './features/exams/pages/ExamsPage';
import { DashboardPage } from "./features/dashboard/pages/DashboardPage";
import { RequireOperations } from "./features/onboarding/components/RequireOperations";
import { OperationsOnboardingPage } from "./features/onboarding/pages/OperationsOnboardingPage";
import { ProfilePage } from "./features/profile/pages/ProfilePage";
import { SupportPage } from "./features/support/pages/SupportPage";
import { SeafarerRegistrationPage } from "./features/seafarer/pages/SeafarerRegistrationPage";
import { MySeaRecordsPage } from "./features/seafarer/pages/MySeaRecordsPage";
import { VerifyCertificatePage } from "./features/verify/pages/VerifyCertificatePage";
import { ExamsPage } from "./features/exams/pages/ExamsPage";
// Phase 1 pages
import { DocumentVaultPage } from './features/documents/pages/DocumentVaultPage';
import { SeamanBookPage } from './features/seaman-book/pages/SeamanBookPage';
import { SeamanBookApplicationPage } from './features/seaman-book/pages/SeamanBookApplicationPage';
import { NotificationsPage } from './features/notifications/pages/NotificationsPage';
import { DocumentVaultPage } from "./features/documents/pages/DocumentVaultPage";
import { SeamanBookPage } from "./features/seaman-book/pages/SeamanBookPage";
import { SeamanBookApplicationPage } from "./features/seaman-book/pages/SeamanBookApplicationPage";
import { NotificationsPage } from "./features/notifications/pages/NotificationsPage";
// Phase 2 — CoC / CoP
import { CertificatesPage } from './features/certificates/pages/CertificatesPage';
import { CoCApplicationPage } from './features/certificates/pages/CoCApplicationPage';
import { CertificatesPage } from "./features/certificates/pages/CertificatesPage";
import { CoCApplicationPage } from "./features/certificates/pages/CoCApplicationPage";
// Phase 3 — Endorsement
import { EndorsementPage } from './features/endorsement/pages/EndorsementPage';
import { VesselRegistrationPage } from './features/vessel-registration/pages/VesselRegistrationPage';
import { OwnershipTransferPage } from './features/vessel-registration/pages/OwnershipTransferPage';
import { MyApplicationsPage } from './features/licensing/pages/MyApplicationsPage';
import { PaymentCheckPage } from './features/payments/pages/PaymentCheckPage';
import { PaymentSuccessPage } from './features/payments/pages/PaymentSuccessPage';
import { PaymentFailurePage } from './features/payments/pages/PaymentFailurePage';
import { LicenseApplicationPage } from './features/licensing/pages/LicenseApplicationPage';
import { ApplicationRedirectPage } from './features/licensing/pages/ApplicationRedirectPage';
import { WaiverPage } from './features/waiver/pages/WaiverPage';
import { EndorsementPage } from "./features/endorsement/pages/EndorsementPage";
import { VesselRegistrationPage } from "./features/vessel-registration/pages/VesselRegistrationPage";
import { OwnershipTransferPage } from "./features/vessel-registration/pages/OwnershipTransferPage";
import { MyApplicationsPage } from "./features/licensing/pages/MyApplicationsPage";
import { PaymentCheckPage } from "./features/payments/pages/PaymentCheckPage";
import { PaymentSuccessPage } from "./features/payments/pages/PaymentSuccessPage";
import { PaymentFailurePage } from "./features/payments/pages/PaymentFailurePage";
import { LicenseApplicationPage } from "./features/licensing/pages/LicenseApplicationPage";
import { ApplicationRedirectPage } from "./features/licensing/pages/ApplicationRedirectPage";
import { WaiverPage } from "./features/waiver/pages/WaiverPage";
import { VesselRegistrationStatusPage } from "./features/vessel-registration/pages/VesselRegistrationStatusPage";
export const router = createBrowserRouter([
// Public auth pages
{ path: '/login', element: <LoginPage /> },
{ path: '/signup', element: <SignupPage /> },
{ path: "/login", element: <LoginPage /> },
{ path: "/signup", element: <SignupPage /> },
// Public certificate verification — the target of every printed QR code.
// No auth: a verifier scanning a certificate has no portal account.
{ path: '/verify', element: <VerifyCertificatePage /> },
{ path: '/verify/:code', element: <VerifyCertificatePage /> },
{ path: "/verify", element: <VerifyCertificatePage /> },
{ path: "/verify/:code", element: <VerifyCertificatePage /> },
// Completes the forgot-password flow; the reset message links here. The
// IAM package generates `/reset-password` links, `/set-password` is the
// first-time-credential variant — one page serves both.
{ path: '/set-password', element: <SetPasswordPage /> },
{ path: '/reset-password', element: <SetPasswordPage /> },
{ path: "/set-password", element: <SetPasswordPage /> },
{ path: "/reset-password", element: <SetPasswordPage /> },
// Protected auth pages
{
element: <ProtectedRoute><OTPVerificationPage /></ProtectedRoute>,
path: '/otp-verify',
element: (
<ProtectedRoute>
<OTPVerificationPage />
</ProtectedRoute>
),
path: "/otp-verify",
},
{
element: <ProtectedRoute><ForgotPasswordPage /></ProtectedRoute>,
path: '/forgot-password',
element: (
<ProtectedRoute>
<ForgotPasswordPage />
</ProtectedRoute>
),
path: "/forgot-password",
},
// The two-step setup wizard is gone. Signing up lands on the dashboard, and
// profile details are collected where they are actually needed: on /profile,
// via the dashboard nudge, or inline in an application flow. The path stays
// as a redirect so existing bookmarks and emailed links do not 404.
{ path: '/profile-setup', element: <Navigate to="/profile" replace /> },
{ path: "/profile-setup", element: <Navigate to="/profile" replace /> },
// Portal — protected.
{
@@ -89,83 +101,159 @@ export const router = createBrowserRouter([
</ProtectedRoute>
),
children: [
{ path: '/', element: <Navigate to="/dashboard" replace /> },
{ path: '/dashboard', element: <DashboardPage /> },
{ path: '/onboarding/operations', element: <OperationsOnboardingPage /> },
{ path: "/", element: <Navigate to="/dashboard" replace /> },
{ path: "/dashboard", element: <DashboardPage /> },
{ path: "/onboarding/operations", element: <OperationsOnboardingPage /> },
// Config-driven licensing: one set of pages serves every licence type.
{ path: '/licensing/applications', element: <MyApplicationsPage /> },
{ path: "/licensing/applications", element: <MyApplicationsPage /> },
// Telebirr returns the applicant to these.
{ path: '/payments/check', element: <PaymentCheckPage /> },
{ path: '/payments/success', element: <PaymentSuccessPage /> },
{ path: '/payments/failure', element: <PaymentFailurePage /> },
{ path: '/licensing/:typeCode/apply', element: <LicenseApplicationPage /> },
{ path: "/payments/check", element: <PaymentCheckPage /> },
{ path: "/payments/success", element: <PaymentSuccessPage /> },
{ path: "/payments/failure", element: <PaymentFailurePage /> },
{
path: '/licensing/:typeCode/applications/:applicationId',
path: "/licensing/:typeCode/apply",
element: <LicenseApplicationPage />,
},
{
path: "/licensing/:typeCode/applications/:applicationId",
element: <LicenseApplicationPage />,
},
// Notification / email deep links arrive as /applications/<id>; resolve the
// licence type and forward to the canonical route.
{ path: '/applications/:applicationId', element: <ApplicationRedirectPage /> },
{
path: "/applications/:applicationId",
element: <ApplicationRedirectPage />,
},
// Seafarer
{ path: '/seafarer-registration', element: <SeafarerRegistrationPage /> },
{ path: '/seafarer/records', element: <MySeaRecordsPage /> },
{ path: '/exams', element: <ExamsPage /> },
{ path: "/seafarer-registration", element: <SeafarerRegistrationPage /> },
{ path: "/seafarer/records", element: <MySeaRecordsPage /> },
{ path: "/exams", element: <ExamsPage /> },
// The public-facing registry was a hardcoded mock and does not belong in
// the applicant portal; officers browse seafarers in the backoffice.
{ path: '/seafarer-registry', element: <Navigate to="/seafarer-registration" replace /> },
{ path: '/seafarer-registry/:id', element: <Navigate to="/seafarer-registration" replace /> },
{
path: "/seafarer-registry",
element: <Navigate to="/seafarer-registration" replace />,
},
{
path: "/seafarer-registry/:id",
element: <Navigate to="/seafarer-registration" replace />,
},
// Phase 1
{ path: '/documents', element: <DocumentVaultPage /> },
{ path: '/seaman-book', element: <SeamanBookPage /> },
{ path: '/seaman-book/apply', element: <SeamanBookApplicationPage /> },
{ path: '/notifications', element: <NotificationsPage /> },
{ path: "/documents", element: <DocumentVaultPage /> },
{ path: "/seaman-book", element: <SeamanBookPage /> },
{ path: "/seaman-book/apply", element: <SeamanBookApplicationPage /> },
{ path: "/notifications", element: <NotificationsPage /> },
// Phase 2 — CoC / CoP
{ path: '/certificates', element: <CertificatesPage /> },
{ path: '/certificates/apply', element: <CoCApplicationPage /> },
{ path: "/certificates", element: <CertificatesPage /> },
{ path: "/certificates/apply", element: <CoCApplicationPage /> },
// Phase 3 — Endorsement
{ path: '/endorsements', element: <EndorsementPage /> },
{ path: '/vessel-registration', element: <VesselRegistrationPage /> },
{ path: "/endorsements", element: <EndorsementPage /> },
{ path: "/vessel-registration", element: <VesselRegistrationPage /> },
// The registration wizard is the config-driven licensing flow; the old
// standalone wizard posted to endpoints that never existed.
{ path: '/vessel-registration/apply', element: <Navigate to="/licensing/VESSEL_REGISTRATION/apply" replace /> },
{ path: '/vessel-registration-dashboard', element: <Navigate to="/vessel-registration" replace /> },
{ path: '/vessel-registration/transfer', element: <OwnershipTransferPage /> },
{
path: "/vessel-registration/apply",
element: <Navigate to="/licensing/VESSEL_REGISTRATION/apply" replace />,
},
{
path: "/vessel-registration-dashboard",
element: <Navigate to="/vessel-registration" replace />,
},
{
path: "/vessel-registration/transfer",
element: <OwnershipTransferPage />,
},
// Legacy per-licence-type URLs. Each once had its own hand-written page
// that posted to a `/logistics-licenses/*` endpoint the API never had,
// and dropped every uploaded document on the floor. They are kept as
// redirects so old bookmarks land somewhere real; the config-driven
// wizard below serves every licence type from one place.
{ path: '/logistics-dashboard', element: <Navigate to="/dashboard" replace /> },
{ path: '/freight-forwarder-license', element: <Navigate to="/licensing/applications" replace /> },
{ path: '/freight-forwarder-license/apply', element: <Navigate to="/licensing/FREIGHT_FORWARDER/apply" replace /> },
{ path: '/freight-forwarder-license/:id/renew', element: <Navigate to="/licensing/applications" replace /> },
{ path: '/shipping-agent-license', element: <Navigate to="/licensing/applications" replace /> },
{ path: '/shipping-agent-license/apply', element: <Navigate to="/licensing/SHIPPING_AGENT/apply" replace /> },
{ path: '/shipping-agent-license/:id/renew', element: <Navigate to="/licensing/applications" replace /> },
{ path: '/combined-license', element: <Navigate to="/licensing/applications" replace /> },
{ path: '/combined-license/apply', element: <Navigate to="/licensing/COMBINED_SA_FF/apply" replace /> },
{ path: '/combined-license/:id/renew', element: <Navigate to="/licensing/applications" replace /> },
{ path: '/joint-investment-license', element: <Navigate to="/licensing/applications" replace /> },
{ path: '/joint-investment-license/apply', element: <Navigate to="/licensing/JOINT_INVESTOR/apply" replace /> },
{ path: '/joint-investment-license/:id/renew', element: <Navigate to="/licensing/applications" replace /> },
{ path: '/mto-license', element: <Navigate to="/licensing/applications" replace /> },
{ path: '/mto-license/apply', element: <Navigate to="/licensing/MULTIMODAL_TRANSPORT_OPERATOR/apply" replace /> },
{ path: '/mto-license/:id/renew', element: <Navigate to="/licensing/applications" replace /> },
{
path: "/logistics-dashboard",
element: <Navigate to="/dashboard" replace />,
},
{
path: "/freight-forwarder-license",
element: <Navigate to="/licensing/applications" replace />,
},
{
path: "/freight-forwarder-license/apply",
element: <Navigate to="/licensing/FREIGHT_FORWARDER/apply" replace />,
},
{
path: "/freight-forwarder-license/:id/renew",
element: <Navigate to="/licensing/applications" replace />,
},
{
path: "/shipping-agent-license",
element: <Navigate to="/licensing/applications" replace />,
},
{
path: "/shipping-agent-license/apply",
element: <Navigate to="/licensing/SHIPPING_AGENT/apply" replace />,
},
{
path: "/shipping-agent-license/:id/renew",
element: <Navigate to="/licensing/applications" replace />,
},
{
path: "/combined-license",
element: <Navigate to="/licensing/applications" replace />,
},
{
path: "/combined-license/apply",
element: <Navigate to="/licensing/COMBINED_SA_FF/apply" replace />,
},
{
path: "/combined-license/:id/renew",
element: <Navigate to="/licensing/applications" replace />,
},
{
path: "/joint-investment-license",
element: <Navigate to="/licensing/applications" replace />,
},
{
path: "/joint-investment-license/apply",
element: <Navigate to="/licensing/JOINT_INVESTOR/apply" replace />,
},
{
path: "/joint-investment-license/:id/renew",
element: <Navigate to="/licensing/applications" replace />,
},
{
path: "/mto-license",
element: <Navigate to="/licensing/applications" replace />,
},
{
path: "/mto-license/apply",
element: (
<Navigate
to="/licensing/MULTIMODAL_TRANSPORT_OPERATOR/apply"
replace
/>
),
},
{
path: "/mto-license/:id/renew",
element: <Navigate to="/licensing/applications" replace />,
},
// Waiver has no backend yet, so it says so rather than pretending.
{ path: '/waiver', element: <WaiverPage /> },
{ path: '/waiver/apply', element: <Navigate to="/waiver" replace /> },
{ path: "/waiver", element: <WaiverPage /> },
{ path: "/waiver/apply", element: <Navigate to="/waiver" replace /> },
// Vessel Registration
{ path: '/vessel-registrations', element: <VesselRegistrationPage /> },
{ path: '/vessel-registrations/apply', element: <VesselRegistrationApplicationPage /> },
{ path: '/vessel-registrations/:id', element: <VesselRegistrationStatusPage /> },
{ path: "/vessel-registrations", element: <VesselRegistrationPage /> },
// { path: '/vessel-registrations/apply', element: <VesselRegistrationPage /> },
{
path: "/vessel-registrations/:id",
element: <VesselRegistrationStatusPage />,
},
// General
{ path: "/profile", element: <ProfilePage /> },
@@ -175,12 +263,27 @@ export const router = createBrowserRouter([
// The separate vessel-owner login/portal was mock-only and called auth
// endpoints that never existed; vessel owners are ordinary portal users.
{ path: '/vessel-owner/login', element: <Navigate to="/login" replace /> },
{ path: '/vessel-owner/register', element: <Navigate to="/signup" replace /> },
{ path: '/vessel-owner/dashboard', element: <Navigate to="/vessel-registration" replace /> },
{ path: '/vessel-owner/registration', element: <Navigate to="/vessel-registration" replace /> },
{ path: '/vessel-owner/registration/apply', element: <Navigate to="/licensing/VESSEL_REGISTRATION/apply" replace /> },
{ path: '/vessel-owner/registration/transfer', element: <Navigate to="/vessel-registration/transfer" replace /> },
{ path: "/vessel-owner/login", element: <Navigate to="/login" replace /> },
{
path: "/vessel-owner/register",
element: <Navigate to="/signup" replace />,
},
{
path: "/vessel-owner/dashboard",
element: <Navigate to="/vessel-registration" replace />,
},
{
path: "/vessel-owner/registration",
element: <Navigate to="/vessel-registration" replace />,
},
{
path: "/vessel-owner/registration/apply",
element: <Navigate to="/licensing/VESSEL_REGISTRATION/apply" replace />,
},
{
path: "/vessel-owner/registration/transfer",
element: <Navigate to="/vessel-registration/transfer" replace />,
},
{ path: '*', element: <Navigate to="/" replace /> },
{ path: "*", element: <Navigate to="/" replace /> },
]);

View File

@@ -1,6 +1,5 @@
import { configureStore } from "@reduxjs/toolkit";
import { baseApi, configureTokenRefresh } from "@ema-platform/api";
import { setToken } from "@ema-platform/auth";
import {
authReducer,
signupReducer,
@@ -8,6 +7,7 @@ import {
authStorage,
refreshAccessToken,
logout,
setToken,
} from "@ema-platform/auth";
import type { AuthUser, CurrentProfile } from "@ema-platform/auth";