fix: fix redirect based on profile bug

This commit is contained in:
mengstabketemaw
2026-07-09 11:34:41 +03:00
parent 1471c797d9
commit db6a485374
14 changed files with 65 additions and 104 deletions

View File

@@ -1,7 +1,6 @@
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AuthConfigProvider } from '@ema-platform/auth';
import type { ReactNode } from 'react';
import { store } from '../store';
import { i18n } from '../i18n/config';
@@ -17,19 +16,9 @@ export function AppProviders({ children }: { children: ReactNode }) {
return (
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<AuthConfigProvider
value={{
appName: 'Backoffice',
storagePrefix: 'ema-backoffice',
loginRedirectPath: '/dashboard',
enableSignup: false,
enableForgotPassword: true,
}}
>
<I18nextProvider i18n={i18n}>
<MantineThemeProvider>{children}</MantineThemeProvider>
</I18nextProvider>
</AuthConfigProvider>
<I18nextProvider i18n={i18n}>
<MantineThemeProvider>{children}</MantineThemeProvider>
</I18nextProvider>
</QueryClientProvider>
</Provider>
);

View File

@@ -41,8 +41,8 @@ const router = createBrowserRouter([
{
element: <AuthLayout />,
children: [
{ path: '/login', element: <LoginPage /> },
{ path: '/forgot-password', element: <ForgotPasswordPage /> },
{ path: '/login', element: <LoginPage appName="Backoffice" enableSignup={false} /> },
{ path: '/forgot-password', element: <ForgotPasswordPage appName="Backoffice" /> },
{ path: '/otp-verify', element: <OTPVerificationPage /> },
],
},

View File

@@ -4,8 +4,8 @@ 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'],
SEAFARER: ['/','/dashboard', '/seafarer-', '/seaman-book', '/documents', '/certificates', '/endorsements', '/notifications', '/basic-safety-training'],
VESSEL_OWNER: ['/','/dashboard', '/vessel-owner', '/vessel-registration'],
};
const SHARED = ['/profile', '/support'];

View File

@@ -0,0 +1,11 @@
import { Navigate } from 'react-router-dom';
import { authStorage } from '@ema-platform/auth';
import { DashboardPage } from '../features/dashboard/pages/DashboardPage';
export function SmartDashboard() {
const profile = authStorage.getProfile<{ type: string }>();
if (profile?.type === 'VESSEL_OWNER') {
return <Navigate to="/vessel-owner/dashboard" replace />;
}
return <DashboardPage />;
}

View File

@@ -116,7 +116,7 @@ export function ProfileSetupPage() {
const [active, setActive] = useState(0);
const [completed, setCompleted] = useState<number[]>([]);
const [submitting, setSubmitting] = useState(false);
const [professions, setProfessions] = useState<Array<{ id: string; name: { en: string } }>>([]);
const [professions, setProfessions] = useState<Array<{ id: string; name: { en: string }; type?: string }>>([]);
const [professionsLoading, setProfessionsLoading] = useState(true);
const [profileTrigger] = useApiMutation<{ id: string }>();
const [addressTrigger] = useApiMutation<unknown>();
@@ -147,6 +147,14 @@ export function ProfileSetupPage() {
return map;
}, [professions]);
const professionTypeMap = useMemo(() => {
const map: Record<string, string> = {};
professions.forEach((p) => {
if (p.type) map[p.id] = p.type;
});
return map;
}, [professions]);
const nameParts = useMemo(() => (user?.name?.en || '').trim().split(/\s+/), [user]);
const profileDefaults: ProfileValues = useMemo(() => ({
professionId: '',
@@ -218,13 +226,14 @@ export function ProfileSetupPage() {
const pv = profileWatch();
const av = addressWatch();
const selectedProfessionName = professionNameMap[pv.professionId] ?? '';
const selectedType = professionTypeMap[pv.professionId] || 'SEAFARER';
const profileResult = await profileTrigger({
url: '/profiles',
method: 'POST',
body: {
userId: user?.id,
type: 'SEAFARER',
type: selectedType,
professionId: pv.professionId,
firstName: pv.firstName,
middleName: pv.middleName,
@@ -261,8 +270,7 @@ export function ProfileSetupPage() {
}).unwrap();
notify.success('Profile setup complete!');
//TODO: update the comparision
navigate('SEAFARER' === 'VESSEL_OWNER' ? '/vessel-owner/dashboard' : '/dashboard');
navigate('/dashboard');
} catch {
notify.error('Failed to save profile. Please try again.');
} finally {

View File

@@ -1,6 +1,5 @@
import { Provider } from 'react-redux';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AuthConfigProvider } from '@ema-platform/auth';
import type { ReactNode } from 'react';
import { store } from '../store';
import { MantineThemeProvider } from './MantineThemeProvider';
@@ -15,17 +14,7 @@ export function AppProviders({ children }: { children: ReactNode }) {
<ErrorBoundary>
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<AuthConfigProvider
value={{
appName: 'Portal',
storagePrefix: 'ema-portal',
loginRedirectPath: '/dashboard',
enableSignup: true,
enableForgotPassword: true,
}}
>
<MantineThemeProvider>{children}</MantineThemeProvider>
</AuthConfigProvider>
<MantineThemeProvider>{children}</MantineThemeProvider>
</QueryClientProvider>
</Provider>
</ErrorBoundary>

View File

@@ -4,6 +4,7 @@ import { i18n } from './i18n/config';
import { PortalLayout } from './layouts/PortalLayout';
import { ProtectedRoute } from './components/ProtectedRoute';
import { ProfileGuard } from './components/ProfileGuard';
import { SmartDashboard } from './components/SmartDashboard';
// Auth (standalone pages, no portal chrome)
import { LoginPage, SignupPage, OTPVerificationPage, ForgotPasswordPage } from '@ema-platform/auth';
@@ -12,7 +13,6 @@ import { LoginPage, SignupPage, OTPVerificationPage, ForgotPasswordPage } from '
import { ProfileSetupPage } from './features/profile-setup/pages/ProfileSetupPage';
// Portal feature pages
import { DashboardPage } from './features/dashboard/pages/DashboardPage';
import { ProfilePage } from './features/profile/pages/ProfilePage';
import { SupportPage } from './features/support/pages/SupportPage';
import { SeafarerRegistrationPage } from './features/seafarer/pages/SeafarerRegistrationPage';
@@ -45,11 +45,11 @@ import { VesselOwnerDashboardPage } from './features/vessel-owner/pages/VesselOw
export const router = createBrowserRouter([
// Public auth pages
{ path: '/login', element: <LoginPage /> },
{ path: '/signup', element: <SignupPage /> },
{ path: '/login', element: <LoginPage appName="Portal" /> },
{ path: '/signup', element: <SignupPage appName="Portal" /> },
// Public auth pages
{ path: '/forgot-password', element: <ForgotPasswordPage /> },
{ path: '/forgot-password', element: <ForgotPasswordPage appName="Portal" /> },
// Protected auth pages
{
@@ -73,8 +73,8 @@ export const router = createBrowserRouter([
</ProtectedRoute>
),
children: [
{ path: '/', element: <Navigate to="/dashboard" replace /> },
{ path: '/dashboard', element: <DashboardPage /> },
{ path: '/', element: <SmartDashboard /> },
{ path: '/dashboard', element: <SmartDashboard /> },
// Seafarer
{ path: '/seafarer-registration', element: <SeafarerRegistrationPage /> },

View File

@@ -1,5 +1,3 @@
export { AuthConfigProvider, useAuthConfig } from './lib/AuthConfig';
export type { AuthConfigValue } from './lib/AuthConfig';
export { AuthShell, BrandMark } from './lib/components/AuthShell';
export { ProtectedRoute } from './lib/components/ProtectedRoute';
export { LoginPage } from './lib/pages/LoginPage';

View File

@@ -1,40 +0,0 @@
import { createContext, useContext, type ReactNode } from 'react';
export interface AuthConfigValue {
appName: string;
storagePrefix: string;
loginRedirectPath: string;
enableSignup: boolean;
enableForgotPassword: boolean;
logoUrl: string;
}
const defaultConfig: AuthConfigValue = {
appName: 'Portal',
storagePrefix: 'ema-auth',
loginRedirectPath: '/dashboard',
enableSignup: true,
enableForgotPassword: true,
logoUrl: '/brand/ema-white.png',
};
const AuthConfigContext = createContext<AuthConfigValue>(defaultConfig);
export function AuthConfigProvider({
children,
value,
}: {
children: ReactNode;
value: Partial<AuthConfigValue>;
}) {
const merged = { ...defaultConfig, ...value };
return (
<AuthConfigContext.Provider value={merged}>
{children}
</AuthConfigContext.Provider>
);
}
export function useAuthConfig() {
return useContext(AuthConfigContext);
}

View File

@@ -15,14 +15,14 @@ import {
type BoxProps,
} from '@mantine/core';
import { IconCheck, IconMoon, IconSun } from '@tabler/icons-react';
import { useAuthConfig } from '../AuthConfig';
const LOGO_URL = '/brand/ema-white.png';
export function BrandMark({ size = 44, ...boxProps }: BoxProps & { size?: number }) {
const { logoUrl } = useAuthConfig();
return (
<Box
component="img"
src={logoUrl}
src={LOGO_URL}
alt="EMA"
w={size}
h={size}
@@ -78,7 +78,6 @@ export function AuthShell({
brandSubtitle = 'Apply for vessel and seafarer licenses, upload documents, and track every application in one secure portal.',
}: AuthShellProps) {
const theme = useMantineTheme();
const { logoUrl } = useAuthConfig();
const heroGradient = theme.other.heroGradient as string;
const computed = useComputedColorScheme('light');
const isDark = computed === 'dark';
@@ -167,7 +166,7 @@ export function AuthShell({
<Center>
<Box
component="img"
src={logoUrl}
src={LOGO_URL}
alt="EMA Portal"
style={{ display: 'block', maxWidth: '60%', height: 'auto' }}
/>

View File

@@ -24,7 +24,6 @@ import { Link } from 'react-router-dom';
import { useApiMutation } from '@ema-platform/api';
import { notify } from '@ema-platform/ui';
import { AuthShell } from '../components/AuthShell';
import { useAuthConfig } from '../AuthConfig';
const schema = z.object({
email: z.string().email({ message: 'Enter a valid email' }),
@@ -32,8 +31,11 @@ const schema = z.object({
type FormValues = z.infer<typeof schema>;
export function ForgotPasswordPage() {
const { appName } = useAuthConfig();
interface ForgotPasswordPageProps {
appName?: string;
}
export function ForgotPasswordPage({ appName = 'Portal' }: ForgotPasswordPageProps) {
const [forgotTrigger, { isLoading }] = useApiMutation();
const [sentTo, setSentTo] = useState<string | null>(null);
const [serverError, setServerError] = useState<string | null>(null);

View File

@@ -28,7 +28,6 @@ import { notify } from '@ema-platform/ui';
import { AuthShell } from '../components/AuthShell';
import { loginSuccess, setUser, setCurrentProfile } from '../store/auth.slice';
import type { LoginPayload, AuthUser, CurrentProfile } from '../types/auth.types';
import { useAuthConfig } from '../AuthConfig';
import { authStorage } from '../utils/auth-storage';
const schema = z.object({
@@ -38,11 +37,19 @@ const schema = z.object({
type FormValues = z.infer<typeof schema>;
export function LoginPage() {
interface LoginPageProps {
appName?: string;
enableSignup?: boolean;
enableForgotPassword?: boolean;
}
export function LoginPage({
appName = 'Portal',
enableSignup = true,
enableForgotPassword = true,
}: LoginPageProps) {
const navigate = useNavigate();
const dispatch = useDispatch();
const { appName, loginRedirectPath, enableSignup, enableForgotPassword } =
useAuthConfig();
const [isLoading, setIsLoading] = useState(false);
const [rememberMe, setRememberMe] = useState(true);
const [serverError, setServerError] = useState<string | null>(null);
@@ -75,7 +82,6 @@ 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({
@@ -84,7 +90,6 @@ 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;
@@ -109,7 +114,7 @@ export function LoginPage() {
return;
}
navigate(profileType === 'VESSEL_OWNER' ? '/vessel-owner/dashboard' : loginRedirectPath);
navigate('/dashboard');
} catch (err: unknown) {
const msg =
(err as { data?: { message?: string } })?.data?.message ??

View File

@@ -20,7 +20,6 @@ import { useNavigate, useLocation } from 'react-router-dom';
import { useApiMutation } from '@ema-platform/api';
import { notify } from '@ema-platform/ui';
import { AuthShell } from '../components/AuthShell';
import { useAuthConfig } from '../AuthConfig';
const CODE_LENGTH = 6;
const RESEND_SECONDS = 30;
@@ -36,7 +35,6 @@ type FormValues = z.infer<typeof schema>;
export function OTPVerificationPage() {
const navigate = useNavigate();
const location = useLocation();
const { loginRedirectPath } = useAuthConfig();
const state = location.state as
| { email?: string; phoneNumber?: string; needsProfile?: boolean }
| null;
@@ -73,7 +71,7 @@ export function OTPVerificationPage() {
}).unwrap();
notify.success('Phone number verified successfully');
navigate(needsProfile ? '/profile-setup' : loginRedirectPath);
navigate(needsProfile ? '/profile-setup' : '/dashboard');
} catch (err: unknown) {
const msg =
(err as { data?: { message?: string } })?.data?.message ??
@@ -182,7 +180,7 @@ export function OTPVerificationPage() {
variant="light"
fullWidth
size="md"
onClick={() => navigate(loginRedirectPath)}
onClick={() => navigate('/dashboard')}
>
Skip verification for now
</Button>

View File

@@ -30,7 +30,6 @@ import { notify } from '@ema-platform/ui';
import { AuthShell } from '../components/AuthShell';
import { loginSuccess, setUser } from '../store/auth.slice';
import type { AuthUser } from '../types/auth.types';
import { useAuthConfig } from '../AuthConfig';
const schema = z
.object({
@@ -63,10 +62,13 @@ interface SignupPayload {
confirmPassword: string;
}
export function SignupPage() {
interface SignupPageProps {
appName?: string;
}
export function SignupPage({ appName = 'Portal' }: SignupPageProps) {
const navigate = useNavigate();
const dispatch = useDispatch();
const { appName, loginRedirectPath } = useAuthConfig();
const [agreed, setAgreed] = useState(false);
const [serverError, setServerError] = useState<string | null>(null);
const [signupTrigger, { isLoading: loading }] = useApiMutation<{