mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-27 01:22:49 +00:00
fix: make vessel registeration in its own routes
This commit is contained in:
@@ -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<string, string[]> = {
|
||||
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 (
|
||||
<Center mih="100vh">
|
||||
<Stack align="center" gap="md">
|
||||
<Title order={1} c="red">403</Title>
|
||||
<Title order={3}>Access Denied</Title>
|
||||
<Text ta="center" maw={480}>
|
||||
You do not have permission to access <Text span fw={700} component="code">{path}</Text>.
|
||||
This section is not available for your account type.
|
||||
</Text>
|
||||
<Button onClick={() => navigate(dashboard)}>
|
||||
Go to Dashboard
|
||||
</Button>
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
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 <Navigate to="/profile-setup" replace />;
|
||||
}
|
||||
|
||||
const allowed = [...(ROUTE_ACCESS[profile.type] ?? []), ...SHARED];
|
||||
const isAllowed = allowed.some((p) => location.pathname === p || location.pathname.startsWith(p));
|
||||
|
||||
if (!isAllowed) {
|
||||
return <ForbiddenPage path={location.pathname} type={profile.type} />;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 (
|
||||
<Box style={{ minHeight: '100vh', background: 'var(--mantine-color-gray-0)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Stack align="center" gap="xl" w="100%" maw={440} px="md">
|
||||
{/* Brand */}
|
||||
<Stack align="center" gap="xs">
|
||||
<ThemeIcon size={64} radius="xl" color="blue" variant="light">
|
||||
<IconShip size={36} />
|
||||
</ThemeIcon>
|
||||
<Title order={2} ta="center">Vessel Owner Portal</Title>
|
||||
<Text fz="sm" c="dimmed" ta="center">
|
||||
Ethiopian Maritime Affairs Authority
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<Paper withBorder radius="lg" p="xl" w="100%" shadow="sm">
|
||||
<Group gap="xs" mb="lg">
|
||||
<IconAnchor size={20} color="var(--mantine-color-blue-6)" />
|
||||
<Text fw={700} fz="lg">Sign In</Text>
|
||||
</Group>
|
||||
|
||||
{error && (
|
||||
<Alert icon={<IconAlertCircle size={16} />} color="red" mb="md">
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Email Address"
|
||||
placeholder="owner@example.com"
|
||||
leftSection={<IconMail size={16} />}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.currentTarget.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleLogin()}
|
||||
/>
|
||||
<PasswordInput
|
||||
label="Password"
|
||||
placeholder="Enter your password"
|
||||
leftSection={<IconLock size={16} />}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.currentTarget.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleLogin()}
|
||||
/>
|
||||
<Anchor fz="sm" ta="right" onClick={() => navigate('/vessel-owner/forgot-password')}>
|
||||
Forgot password?
|
||||
</Anchor>
|
||||
<Button fullWidth size="md" loading={loading} onClick={handleLogin} leftSection={<IconAnchor size={16} />}>
|
||||
Sign In
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<Divider my="md" label="Don't have an account?" labelPosition="center" />
|
||||
|
||||
<Button
|
||||
fullWidth
|
||||
variant="light"
|
||||
onClick={() => navigate('/vessel-owner/register')}
|
||||
>
|
||||
Create Vessel Owner Account
|
||||
</Button>
|
||||
</Paper>
|
||||
|
||||
<Text fz="xs" c="dimmed" ta="center">
|
||||
This portal is exclusively for vessel owners. For seafarer services,{' '}
|
||||
<Anchor fz="xs" onClick={() => navigate('/login')}>sign in here</Anchor>.
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -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<string | null>(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 (
|
||||
<Box style={{ minHeight: '100vh', background: 'var(--mantine-color-gray-0)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Paper withBorder radius="lg" p="xl" w="100%" maw={440} shadow="sm" mx="md">
|
||||
<Stack align="center" gap="md">
|
||||
<ThemeIcon size={56} radius="xl" color="teal" variant="light">
|
||||
<IconCheck size={30} />
|
||||
</ThemeIcon>
|
||||
<Title order={3} ta="center">Account Created!</Title>
|
||||
<Text fz="sm" c="dimmed" ta="center">
|
||||
Your vessel owner account has been created. You can now sign in and submit vessel registration applications.
|
||||
</Text>
|
||||
<Button fullWidth onClick={() => navigate('/vessel-owner/login')}>
|
||||
Sign In Now
|
||||
</Button>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box style={{ minHeight: '100vh', background: 'var(--mantine-color-gray-0)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Stack align="center" gap="xl" w="100%" maw={540} px="md">
|
||||
<Stack align="center" gap="xs">
|
||||
<ThemeIcon size={64} radius="xl" color="blue" variant="light">
|
||||
<IconShip size={36} />
|
||||
</ThemeIcon>
|
||||
<Title order={2} ta="center">Create Vessel Owner Account</Title>
|
||||
<Text fz="sm" c="dimmed" ta="center">Ethiopian Maritime Affairs Authority</Text>
|
||||
</Stack>
|
||||
|
||||
<Paper withBorder radius="lg" p="xl" w="100%" shadow="sm">
|
||||
<Group gap="xs" mb="lg">
|
||||
<IconAnchor size={20} color="var(--mantine-color-blue-6)" />
|
||||
<Text fw={700} fz="lg">Owner Registration</Text>
|
||||
</Group>
|
||||
|
||||
{error && (
|
||||
<Alert icon={<IconAlertCircle size={16} />} color="red" mb="md">{error}</Alert>
|
||||
)}
|
||||
|
||||
<Stack gap="md">
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
|
||||
<TextInput
|
||||
label="Full Name / Company Name"
|
||||
placeholder="e.g. Abebe Girma"
|
||||
leftSection={<IconUser size={16} />}
|
||||
required
|
||||
value={fullName}
|
||||
onChange={(e) => setFullName(e.currentTarget.value)}
|
||||
/>
|
||||
<Select
|
||||
label="Owner Type"
|
||||
placeholder="Select type"
|
||||
required
|
||||
data={OWNER_TYPES}
|
||||
value={ownerType}
|
||||
onChange={setOwnerType}
|
||||
/>
|
||||
<TextInput
|
||||
label="Email Address"
|
||||
placeholder="owner@example.com"
|
||||
leftSection={<IconMail size={16} />}
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="Phone Number"
|
||||
placeholder="+251 9XX XXX XXX"
|
||||
leftSection={<IconPhone size={16} />}
|
||||
required
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.currentTarget.value)}
|
||||
/>
|
||||
<TextInput
|
||||
label="National ID / TIN"
|
||||
placeholder="ET-0000000 or TIN"
|
||||
required
|
||||
value={nationalIdOrTin}
|
||||
onChange={(e) => setNationalIdOrTin(e.currentTarget.value)}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
|
||||
<Divider label="Set Password" labelPosition="center" />
|
||||
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
|
||||
<PasswordInput
|
||||
label="Password"
|
||||
placeholder="Min. 8 characters"
|
||||
leftSection={<IconLock size={16} />}
|
||||
required
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.currentTarget.value)}
|
||||
/>
|
||||
<PasswordInput
|
||||
label="Confirm Password"
|
||||
placeholder="Repeat password"
|
||||
leftSection={<IconLock size={16} />}
|
||||
required
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.currentTarget.value)}
|
||||
error={confirmPassword && password !== confirmPassword ? 'Passwords do not match' : undefined}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
|
||||
<Button fullWidth size="md" loading={loading} disabled={!canSubmit} onClick={handleRegister}>
|
||||
Create Account
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
<Divider my="md" label="Already have an account?" labelPosition="center" />
|
||||
<Button fullWidth variant="light" onClick={() => navigate('/vessel-owner/login')}>
|
||||
Sign In
|
||||
</Button>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -25,6 +25,10 @@ export const am: Translations = {
|
||||
support: 'እገዛና ድጋፍ',
|
||||
collapseSidebar: 'ሰብስብ',
|
||||
expandSidebar: 'ዘርጋ',
|
||||
myVessels: 'መርከቦቼ',
|
||||
vesselRegistration: 'የመርከብ ምዝገባ',
|
||||
ownershipTransfer: 'የባለቤትነት ዝውውር',
|
||||
basicSafetyTraining: 'መሰረታዊ የደህንነት ስልጠና',
|
||||
},
|
||||
|
||||
common: {
|
||||
|
||||
@@ -23,6 +23,10 @@ export const en = {
|
||||
support: 'Help & Support',
|
||||
collapseSidebar: 'Collapse',
|
||||
expandSidebar: 'Expand sidebar',
|
||||
myVessels: 'My Vessels',
|
||||
vesselRegistration: 'Vessel Registration',
|
||||
ownershipTransfer: 'Ownership Transfer',
|
||||
basicSafetyTraining: 'Basic Safety Training',
|
||||
},
|
||||
|
||||
common: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AppShell } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import {
|
||||
IconAnchor,
|
||||
IconBell,
|
||||
IconFolderOpen,
|
||||
IconHeadset,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
IconRubberStamp,
|
||||
IconSend,
|
||||
IconShieldCheck,
|
||||
IconShip,
|
||||
IconUserCircle,
|
||||
} from '@tabler/icons-react';
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||
@@ -20,16 +22,20 @@ import { logout } from '@ema-platform/auth';
|
||||
import { SUPPORTED_LANGUAGES } from '../i18n/config';
|
||||
import { useAppSelector } from '../store/hooks';
|
||||
|
||||
const NAV_ITEMS: (NavItem & { i18nKey: string })[] = [
|
||||
{ to: '/dashboard', label: 'Dashboard', i18nKey: 'nav.dashboard', icon: IconHome2 },
|
||||
{ to: '/seafarer-registry', label: 'Seafarer Registry', i18nKey: 'nav.seafarerRegistry', icon: IconList },
|
||||
{ to: '/seaman-book', label: 'My Application', i18nKey: 'nav.myApplication', icon: IconSend },
|
||||
{ to: '/certificates', label: 'Certificates', i18nKey: 'nav.certificates', icon: IconShieldCheck },
|
||||
{ to: '/endorsements', label: 'Endorsements', i18nKey: 'nav.endorsements', icon: IconRubberStamp },
|
||||
{ to: '/documents', label: 'My Documents', i18nKey: 'nav.documents', icon: IconFolderOpen },
|
||||
{ to: '/notifications',label: 'Notifications', i18nKey: 'nav.notifications', icon: IconBell },
|
||||
{ to: '/profile', label: 'Profile', i18nKey: 'nav.profile', icon: IconUserCircle },
|
||||
{ to: '/support', label: 'Help & Support', i18nKey: 'nav.support', icon: IconHeadset },
|
||||
const NAV_ITEMS: (NavItem & { i18nKey: string; allowedTypes: string[] })[] = [
|
||||
{ to: '/dashboard', label: 'Dashboard', i18nKey: 'nav.dashboard', icon: IconHome2, allowedTypes: ['SEAFARER'] },
|
||||
{ to: '/seafarer-registry', label: 'Seafarer Registry', i18nKey: 'nav.seafarerRegistry', icon: IconList, allowedTypes: ['SEAFARER'] },
|
||||
{ to: '/seaman-book', label: 'My Application', i18nKey: 'nav.myApplication', icon: IconSend, allowedTypes: ['SEAFARER'] },
|
||||
{ to: '/certificates', label: 'Certificates', i18nKey: 'nav.certificates', icon: IconShieldCheck, allowedTypes: ['SEAFARER'] },
|
||||
{ to: '/endorsements', label: 'Endorsements', i18nKey: 'nav.endorsements', icon: IconRubberStamp, allowedTypes: ['SEAFARER'] },
|
||||
{ to: '/documents', label: 'My Documents', i18nKey: 'nav.documents', icon: IconFolderOpen, allowedTypes: ['SEAFARER'] },
|
||||
{ to: '/notifications', label: 'Notifications', i18nKey: 'nav.notifications', icon: IconBell, allowedTypes: ['SEAFARER'] },
|
||||
{ to: '/basic-safety-training', label: 'Basic Safety Training', i18nKey: 'nav.basicSafetyTraining', icon: IconShieldCheck, allowedTypes: ['SEAFARER'] },
|
||||
{ to: '/vessel-owner/dashboard', label: 'My Vessels', i18nKey: 'nav.myVessels', icon: IconShip, allowedTypes: ['VESSEL_OWNER'] },
|
||||
{ to: '/vessel-registration', label: 'Vessel Registration', i18nKey: 'nav.vesselRegistration', icon: IconAnchor, allowedTypes: ['VESSEL_OWNER'] },
|
||||
{ to: '/vessel-registration/transfer', label: 'Ownership Transfer', i18nKey: 'nav.ownershipTransfer', icon: IconAnchor, allowedTypes: ['VESSEL_OWNER'] },
|
||||
{ to: '/profile', label: 'Profile', i18nKey: 'nav.profile', icon: IconUserCircle, allowedTypes: ['SEAFARER', 'VESSEL_OWNER'] },
|
||||
{ to: '/support', label: 'Help & Support', i18nKey: 'nav.support', icon: IconHeadset, allowedTypes: ['SEAFARER', 'VESSEL_OWNER'] },
|
||||
];
|
||||
|
||||
const PAGE_META: Record<string, { i18nKey: string }> = {
|
||||
@@ -79,6 +85,9 @@ export function PortalLayout() {
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
const profile = useAppSelector((s) => s.auth.currentProfile);
|
||||
const filteredNavItems = NAV_ITEMS.filter((item) => profile && item.allowedTypes.includes(profile.type));
|
||||
|
||||
const displayName = user?.name?.en || user?.username || '';
|
||||
const initials = displayName
|
||||
? displayName.split(/\s+/).map((s) => s[0]).join('').toUpperCase().slice(0, 2)
|
||||
@@ -123,7 +132,7 @@ export function PortalLayout() {
|
||||
}}
|
||||
>
|
||||
<AppSidebar
|
||||
navItems={NAV_ITEMS.map(({ i18nKey, ...rest }) => ({ ...rest, label: t(i18nKey) }))}
|
||||
navItems={filteredNavItems.map(({ i18nKey, allowedTypes: _, ...rest }) => ({ ...rest, label: t(i18nKey) }))}
|
||||
collapsed={sidebarCollapsed}
|
||||
activePath={location.pathname}
|
||||
onToggleCollapse={toggleSidebar}
|
||||
|
||||
@@ -39,10 +39,7 @@ import { VesselRegistrationPage } from './features/vessel-registration/pages/Ves
|
||||
import { VesselRegistrationApplicationPage } from './features/vessel-registration/pages/VesselRegistrationApplicationPage';
|
||||
import { OwnershipTransferPage } from './features/vessel-registration/pages/OwnershipTransferPage';
|
||||
|
||||
// Vessel Owner Portal (restricted)
|
||||
import { VesselOwnerLayout } from './layouts/VesselOwnerLayout';
|
||||
import { VesselOwnerLoginPage } from './features/vessel-owner/pages/VesselOwnerLoginPage';
|
||||
import { VesselOwnerRegisterPage } from './features/vessel-owner/pages/VesselOwnerRegisterPage';
|
||||
// Vessel Owner
|
||||
import { VesselOwnerDashboardPage } from './features/vessel-owner/pages/VesselOwnerDashboardPage';
|
||||
|
||||
|
||||
@@ -51,15 +48,14 @@ export const router = createBrowserRouter([
|
||||
{ path: '/login', element: <LoginPage /> },
|
||||
{ path: '/signup', element: <SignupPage /> },
|
||||
|
||||
// Public auth pages
|
||||
{ path: '/forgot-password', element: <ForgotPasswordPage /> },
|
||||
|
||||
// Protected auth pages
|
||||
{
|
||||
element: <ProtectedRoute><OTPVerificationPage /></ProtectedRoute>,
|
||||
path: '/otp-verify',
|
||||
},
|
||||
{
|
||||
element: <ProtectedRoute><ForgotPasswordPage /></ProtectedRoute>,
|
||||
path: '/forgot-password',
|
||||
},
|
||||
{
|
||||
element: <ProtectedRoute><ProfileSetupPage /></ProtectedRoute>,
|
||||
path: '/profile-setup',
|
||||
@@ -106,9 +102,9 @@ export const router = createBrowserRouter([
|
||||
{ path: '/vessel-registration/apply', element: <VesselRegistrationApplicationPage /> },
|
||||
{ path: '/vessel-registration/transfer', element: <OwnershipTransferPage /> },
|
||||
|
||||
// Vessel Owner Portal — public login/register
|
||||
{ path: '/vessel-owner/login', element: <VesselOwnerLoginPage /> },
|
||||
{ path: '/vessel-owner/register', element: <VesselOwnerRegisterPage /> },
|
||||
// Vessel Owner
|
||||
{ path: '/vessel-owner/dashboard', element: <VesselOwnerDashboardPage /> },
|
||||
{ path: '/vessel-owner', element: <Navigate to="/vessel-owner/dashboard" replace /> },
|
||||
// General
|
||||
{ path: '/profile', element: <ProfilePage /> },
|
||||
{ path: '/support', element: <SupportPage /> },
|
||||
|
||||
@@ -75,6 +75,7 @@ export function LoginPage() {
|
||||
dispatch(setUser(me));
|
||||
|
||||
let hasProfile = false;
|
||||
let profileType: string | undefined;
|
||||
try {
|
||||
const q = `w=user_id:=:${me.id}&i=user,address,profession`;
|
||||
const result = await profileCheckTrigger({
|
||||
@@ -83,6 +84,7 @@ export function LoginPage() {
|
||||
}).unwrap();
|
||||
if (result.total > 0 && result.items.length > 0) {
|
||||
const profile = result.items[0];
|
||||
profileType = 'VESSEL_OWNER';
|
||||
authStorage.setProfileId(profile.id);
|
||||
dispatch(setCurrentProfile(profile));
|
||||
hasProfile = true;
|
||||
@@ -107,7 +109,7 @@ export function LoginPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(loginRedirectPath);
|
||||
navigate(profileType === 'VESSEL_OWNER' ? '/vessel-owner/dashboard' : loginRedirectPath);
|
||||
} catch (err: unknown) {
|
||||
const msg =
|
||||
(err as { data?: { message?: string } })?.data?.message ??
|
||||
|
||||
Reference in New Issue
Block a user