From 1471c797d965c1c640ddf0605a59a3cf4b5da577 Mon Sep 17 00:00:00 2001 From: mengstabketemaw Date: Wed, 8 Jul 2026 16:46:27 +0300 Subject: [PATCH] fix: make vessel registeration in its own routes --- .../src/app/components/ProfileGuard.tsx | 42 +++- .../profile-setup/pages/ProfileSetupPage.tsx | 8 +- .../pages/VesselOwnerLoginPage.tsx | 128 ----------- .../pages/VesselOwnerRegisterPage.tsx | 199 ------------------ apps/portal/src/app/i18n/locales/am.ts | 4 + apps/portal/src/app/i18n/locales/en.ts | 4 + apps/portal/src/app/layouts/PortalLayout.tsx | 31 ++- apps/portal/src/app/router.tsx | 18 +- libs/auth/src/lib/pages/LoginPage.tsx | 4 +- 9 files changed, 80 insertions(+), 358 deletions(-) delete mode 100644 apps/portal/src/app/features/vessel-owner/pages/VesselOwnerLoginPage.tsx delete mode 100644 apps/portal/src/app/features/vessel-owner/pages/VesselOwnerRegisterPage.tsx diff --git a/apps/portal/src/app/components/ProfileGuard.tsx b/apps/portal/src/app/components/ProfileGuard.tsx index b7f38216a..5a564b142 100644 --- a/apps/portal/src/app/components/ProfileGuard.tsx +++ b/apps/portal/src/app/components/ProfileGuard.tsx @@ -1,17 +1,53 @@ -import { Navigate } from 'react-router-dom'; +import { Navigate, useLocation, useNavigate } from 'react-router-dom'; +import { Button, Center, Stack, Text, Title } from '@mantine/core'; import type { ReactNode } from 'react'; import { authStorage } from '@ema-platform/auth'; +const ROUTE_ACCESS: Record = { + SEAFARER: ['/dashboard', '/seafarer-', '/seaman-book', '/documents', '/certificates', '/endorsements', '/notifications', '/basic-safety-training'], + VESSEL_OWNER: ['/vessel-owner', '/vessel-registration'], +}; + +const SHARED = ['/profile', '/support']; + +function ForbiddenPage({ path, type }: { path: string; type: string }) { + const navigate = useNavigate(); + const dashboard = type === 'VESSEL_OWNER' ? '/vessel-owner/dashboard' : '/dashboard'; + return ( +
+ + 403 + Access Denied + + You do not have permission to access {path}. + This section is not available for your account type. + + + +
+ ); +} + interface ProfileGuardProps { children?: ReactNode; } export function ProfileGuard({ children }: ProfileGuardProps) { - const profileId = authStorage.getProfileId(); + const profile = authStorage.getProfile<{ id: string; type: string }>(); + const location = useLocation(); - if (!profileId) { + if (!profile) { return ; } + const allowed = [...(ROUTE_ACCESS[profile.type] ?? []), ...SHARED]; + const isAllowed = allowed.some((p) => location.pathname === p || location.pathname.startsWith(p)); + + if (!isAllowed) { + return ; + } + return <>{children}; } diff --git a/apps/portal/src/app/features/profile-setup/pages/ProfileSetupPage.tsx b/apps/portal/src/app/features/profile-setup/pages/ProfileSetupPage.tsx index f89f8a878..570a9c868 100644 --- a/apps/portal/src/app/features/profile-setup/pages/ProfileSetupPage.tsx +++ b/apps/portal/src/app/features/profile-setup/pages/ProfileSetupPage.tsx @@ -24,7 +24,7 @@ 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 { authStorage, setUser, logout, type AuthUser } from '@ema-platform/auth'; +import { authStorage, setUser, setCurrentProfile, logout, type AuthUser, type CurrentProfile } from '@ema-platform/auth'; import { useAppDispatch, useAppSelector } from '../../../store/hooks'; import { ProfileFormContent, @@ -260,11 +260,9 @@ export function ProfileSetupPage() { }, }).unwrap(); - const me = await meTrigger({ url: '/auth/me', method: 'GET' }).unwrap(); - dispatch(setUser(me)); - notify.success('Profile setup complete!'); - navigate('/dashboard'); + //TODO: update the comparision + navigate('SEAFARER' === 'VESSEL_OWNER' ? '/vessel-owner/dashboard' : '/dashboard'); } catch { notify.error('Failed to save profile. Please try again.'); } finally { diff --git a/apps/portal/src/app/features/vessel-owner/pages/VesselOwnerLoginPage.tsx b/apps/portal/src/app/features/vessel-owner/pages/VesselOwnerLoginPage.tsx deleted file mode 100644 index 6d687d2d6..000000000 --- a/apps/portal/src/app/features/vessel-owner/pages/VesselOwnerLoginPage.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { - Alert, - Anchor, - Box, - Button, - Center, - Divider, - Group, - Paper, - PasswordInput, - Stack, - Text, - TextInput, - ThemeIcon, - Title, -} from '@mantine/core'; -import { - IconAnchor, - IconAlertCircle, - IconLock, - IconMail, - IconShip, -} from '@tabler/icons-react'; -import { useApiMutation } from '@ema-platform/api'; -import { notify } from '@ema-platform/ui'; - -export function VesselOwnerLoginPage() { - const navigate = useNavigate(); - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - const [loginTrigger] = useApiMutation<{ token: string; user: { id: string; name: string } }>(); - - const handleLogin = async () => { - if (!email.trim() || !password.trim()) { - setError('Please enter your email and password.'); - return; - } - setError(''); - setLoading(true); - try { - await loginTrigger({ - url: '/auth/vessel-owner/login', - method: 'POST', - body: { email, password }, - }).unwrap(); - notify.success('Login successful. Welcome!'); - navigate('/vessel-owner/dashboard'); - } catch { - setError('Invalid email or password. Please try again.'); - } finally { - setLoading(false); - } - }; - - return ( - - - {/* Brand */} - - - - - Vessel Owner Portal - - Ethiopian Maritime Affairs Authority - - - - - - - Sign In - - - {error && ( - } color="red" mb="md"> - {error} - - )} - - - } - value={email} - onChange={(e) => setEmail(e.currentTarget.value)} - onKeyDown={(e) => e.key === 'Enter' && handleLogin()} - /> - } - value={password} - onChange={(e) => setPassword(e.currentTarget.value)} - onKeyDown={(e) => e.key === 'Enter' && handleLogin()} - /> - navigate('/vessel-owner/forgot-password')}> - Forgot password? - - - - - - - - - - - This portal is exclusively for vessel owners. For seafarer services,{' '} - navigate('/login')}>sign in here. - - - - ); -} diff --git a/apps/portal/src/app/features/vessel-owner/pages/VesselOwnerRegisterPage.tsx b/apps/portal/src/app/features/vessel-owner/pages/VesselOwnerRegisterPage.tsx deleted file mode 100644 index 75d216232..000000000 --- a/apps/portal/src/app/features/vessel-owner/pages/VesselOwnerRegisterPage.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { - Alert, - Anchor, - Box, - Button, - Divider, - Group, - Paper, - PasswordInput, - Select, - SimpleGrid, - Stack, - Text, - TextInput, - ThemeIcon, - Title, -} from '@mantine/core'; -import { - IconAnchor, - IconAlertCircle, - IconCheck, - IconLock, - IconMail, - IconPhone, - IconShip, - IconUser, -} from '@tabler/icons-react'; -import { useApiMutation } from '@ema-platform/api'; -import { notify } from '@ema-platform/ui'; - -const OWNER_TYPES = [ - 'Individual (Private Owner)', - 'Private Company / PLC', - 'State Enterprise', - 'NGO / Non-Profit', - 'Government Agency', -]; - -export function VesselOwnerRegisterPage() { - const navigate = useNavigate(); - const [fullName, setFullName] = useState(''); - const [email, setEmail] = useState(''); - const [phone, setPhone] = useState(''); - const [ownerType, setOwnerType] = useState(null); - const [nationalIdOrTin, setNationalIdOrTin] = useState(''); - const [password, setPassword] = useState(''); - const [confirmPassword, setConfirmPassword] = useState(''); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - const [success, setSuccess] = useState(false); - const [registerTrigger] = useApiMutation<{ id: string }>(); - - const canSubmit = !!fullName.trim() && !!email.trim() && !!phone.trim() && !!ownerType && !!nationalIdOrTin.trim() && !!password && password === confirmPassword; - - const handleRegister = async () => { - if (!canSubmit) { - setError('Please fill in all required fields. Passwords must match.'); - return; - } - setError(''); - setLoading(true); - try { - await registerTrigger({ - url: '/auth/vessel-owner/register', - method: 'POST', - body: { fullName, email, phone, ownerType, nationalIdOrTin, password }, - }).unwrap(); - setSuccess(true); - notify.success('Account created! You can now sign in.'); - } catch { - setError('Registration failed. This email may already be registered.'); - } finally { - setLoading(false); - } - }; - - if (success) { - return ( - - - - - - - Account Created! - - Your vessel owner account has been created. You can now sign in and submit vessel registration applications. - - - - - - ); - } - - return ( - - - - - - - Create Vessel Owner Account - Ethiopian Maritime Affairs Authority - - - - - - Owner Registration - - - {error && ( - } color="red" mb="md">{error} - )} - - - - } - required - value={fullName} - onChange={(e) => setFullName(e.currentTarget.value)} - /> -