diff --git a/apps/portal/src/app/app.tsx b/apps/portal/src/app/app.tsx index 58451087d..7d770b793 100644 --- a/apps/portal/src/app/app.tsx +++ b/apps/portal/src/app/app.tsx @@ -1,10 +1,10 @@ -import { configureIam } from "@tria-plc/iamui-common"; +// import { configureIam } from "@tria-plc/iamui-common"; import { BrowserRouter } from 'react-router-dom'; -import "@tria-plc/iamui-common/styles.css"; +// import "@tria-plc/iamui-common/styles.css"; import { AppProviders } from './providers/AppProviders'; import { AppRouter } from './router'; -configureIam({ apiUrl: 'http://localhost:3001/api' }); +// configureIam({ apiUrl: 'http://localhost:3001/api' }); export function App() { return ( diff --git a/apps/portal/src/app/components/ErrorBoundary.tsx b/apps/portal/src/app/components/ErrorBoundary.tsx new file mode 100644 index 000000000..d8b892333 --- /dev/null +++ b/apps/portal/src/app/components/ErrorBoundary.tsx @@ -0,0 +1,50 @@ +import { Component } from 'react'; +import type { ReactNode, ErrorInfo } from 'react'; +import { Center, Paper, Title, Text, Button } from '@mantine/core'; + +interface Props { + children: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +export class ErrorBoundary extends Component { + state: State = { hasError: false, error: null }; + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, info: ErrorInfo) { + console.error('ErrorBoundary caught:', error, info); + } + + render() { + if (this.state.hasError) { + return ( +
+ + Something went wrong + + {this.state.error?.message || 'An unexpected error occurred.'} + + + +
+ ); + } + + return this.props.children; + } +} diff --git a/apps/portal/src/app/features/auth/pages/LoginPage.tsx b/apps/portal/src/app/features/auth/pages/LoginPage.tsx index a9f26f342..5419d9d1e 100644 --- a/apps/portal/src/app/features/auth/pages/LoginPage.tsx +++ b/apps/portal/src/app/features/auth/pages/LoginPage.tsx @@ -49,13 +49,13 @@ export function LoginPage() { url: '/auth/login', method: 'POST', body: values, - }).unwrap() as LoginPayload; + }).unwrap(); dispatch(loginSuccess(data)); const me = await meTrigger({ url: '/auth/me', method: 'GET', - }).unwrap() as AuthUser; + }).unwrap(); dispatch(setUser(me)); if (me.status === 'accepted') { diff --git a/apps/portal/src/app/features/auth/pages/SignupPage.tsx b/apps/portal/src/app/features/auth/pages/SignupPage.tsx index 16bc78945..5edd9cbac 100644 --- a/apps/portal/src/app/features/auth/pages/SignupPage.tsx +++ b/apps/portal/src/app/features/auth/pages/SignupPage.tsx @@ -83,7 +83,7 @@ export function SignupPage() { url: '/auth/signup-with-pwd', method: 'POST', body: payload, - }).unwrap() as { token: string; refreshToken: string; isPhoneNumberVerified: boolean }; + }).unwrap(); dispatch( loginSuccess({ diff --git a/apps/portal/src/app/providers/AppProviders.tsx b/apps/portal/src/app/providers/AppProviders.tsx index 0a2dfd5ee..5fbc567da 100644 --- a/apps/portal/src/app/providers/AppProviders.tsx +++ b/apps/portal/src/app/providers/AppProviders.tsx @@ -4,6 +4,7 @@ import { AuthProvider } from '@tria-plc/iamui-common'; import type { ReactNode } from 'react'; import { store } from '../store'; import { MantineThemeProvider } from './MantineThemeProvider'; +import { ErrorBoundary } from '../components/ErrorBoundary'; const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 1, staleTime: 1000 * 60 * 5 } }, @@ -11,12 +12,14 @@ const queryClient = new QueryClient({ export function AppProviders({ children }: { children: ReactNode }) { return ( - - - - {children} - - - + + + + + {children} + + + + ); } diff --git a/apps/portal/src/app/router.tsx b/apps/portal/src/app/router.tsx index 098111c44..9cf530108 100644 --- a/apps/portal/src/app/router.tsx +++ b/apps/portal/src/app/router.tsx @@ -5,13 +5,11 @@ import { LoginPage } from './features/auth/pages/LoginPage'; import { SignupPage } from './features/auth/pages/SignupPage'; import { OTPVerificationPage } from './features/auth/pages/OTPVerificationPage'; import { DashboardPage } from './features/dashboard/pages/DashboardPage'; - -function getToken(): string | null { - return localStorage.getItem('ema-portal-auth-token'); -} +import { useAppSelector } from './store/hooks'; function ProtectedRoute({ children }: { children: ReactNode }) { - if (!getToken()) return ; + const isAuthenticated = useAppSelector((s) => s.auth.isAuthenticated); + if (!isAuthenticated) return ; return <>{children}; } diff --git a/apps/portal/src/app/store/index.ts b/apps/portal/src/app/store/index.ts index bc34aa245..59fd5d024 100644 --- a/apps/portal/src/app/store/index.ts +++ b/apps/portal/src/app/store/index.ts @@ -1,12 +1,23 @@ import { configureStore } from '@reduxjs/toolkit'; import { baseApi } from '@ema-platform/api'; import { authReducer } from '../features/auth/store/auth.slice'; +import { authStorage } from '../features/auth/utils/auth-storage'; + +const preloadedAuth = (() => { + const token = authStorage.getToken(); + const user = authStorage.getUser(); + if (token && user) { + return { token, user, isAuthenticated: true }; + } + return undefined; +})(); export const store = configureStore({ reducer: { auth: authReducer, [baseApi.reducerPath]: baseApi.reducer, }, + preloadedState: preloadedAuth ? { auth: preloadedAuth } : undefined, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(baseApi.middleware), });