-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* Clean out dead code * Refactor Redirect component to separate file * Refactor ProtectedRoute component into its own file * First pass, rewriting route implementation- no auth/redirect handling yet * Tweak sidebar a bit * Tweak sidebar more * New redirect/auth protection implementation * Remove old code * Restore loader while performing initial auth check
- Loading branch information
Showing
11 changed files
with
258 additions
and
210 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,187 +1,62 @@ | ||
import React, { useContext, useEffect } from 'react'; | ||
import { Outlet, Routes, Route, useLocation, useNavigate } from 'react-router'; | ||
import { Loader } from '@mantine/core'; | ||
import { useContext, useEffect, ReactElement } from 'react'; | ||
import { useLocation, useNavigate, Outlet } from 'react-router'; | ||
import { useQuery } from '@tanstack/react-query'; | ||
import PropTypes from 'prop-types'; | ||
|
||
import { Layout } from './stories/Layout/Layout'; | ||
import Home from './pages/home'; | ||
import Login from './pages/auth/login/login'; | ||
import Register from './pages/auth/register/register'; | ||
import Dashboard from './pages/dashboard/Dashboard'; | ||
import AdminPatientsGenerate from './pages/admin/patients/AdminPatientsGenerate'; | ||
import NotFound from './pages/notFound/NotFound'; | ||
import { AdminUsers } from './pages/admin/users/AdminUsers'; | ||
import { Center, Loader } from '@mantine/core'; | ||
import { Notifications } from '@mantine/notifications'; | ||
|
||
import Context from './Context'; | ||
import AdminPendingUsers from './pages/admin/pending-users/AdminPendingUsers'; | ||
import PasswordForgot from './pages/auth/password-forgot/passwordForgot'; | ||
import PasswordReset from './pages/auth/password-reset/passwordReset'; | ||
import AuthLayout from './stories/AuthLayout/AuthLayout'; | ||
import Verify from './pages/verify/verify'; | ||
import PatientRegistration from './pages/patients/register/PatientRegistration'; | ||
import PatientDetails from './pages/patients/patient-details/PatientDetails'; | ||
import Patients from './pages/patients/Patients'; | ||
|
||
const RedirectProps = { | ||
isLoading: PropTypes.bool.isRequired, | ||
isLoggedIn: PropTypes.bool.isRequired, | ||
isLoggedInRequired: PropTypes.bool, | ||
}; | ||
import { useAuthorization } from './hooks/useAuthorization'; | ||
|
||
/** | ||
* Redirects browser based on props | ||
* @param {PropTypes.InferProps<typeof RedirectProps>} props | ||
* @returns {React.ReactElement} | ||
* Top-level application component. * | ||
* @param {PropTypes.func} handleRedirects | ||
* @returns {ReactElement} | ||
*/ | ||
function Redirect({ isLoading, isLoggedIn, isLoggedInRequired }) { | ||
function App({ handleRedirects }) { | ||
const location = useLocation(); | ||
const navigate = useNavigate(); | ||
useEffect(() => { | ||
if (!isLoading) { | ||
if (isLoggedInRequired && !isLoggedIn) { | ||
let redirectTo = `${location.pathname}`; | ||
if (location.search) { | ||
redirectTo = `${redirectTo}?${location.search}`; | ||
} | ||
navigate('/login', { state: { redirectTo } }); | ||
} else if (!isLoggedInRequired && isLoggedIn) { | ||
navigate('/dashboard'); | ||
} | ||
} | ||
}, [isLoading, isLoggedIn, isLoggedInRequired, location, navigate]); | ||
if (isLoading) { | ||
return <Loader />; | ||
} | ||
return <Outlet />; | ||
} | ||
|
||
Redirect.propTypes = RedirectProps; | ||
|
||
const ProtectedRouteProps = { | ||
role: PropTypes.string.isRequired, | ||
restrictedRoles: PropTypes.arrayOf(PropTypes.string).isRequired, | ||
destination: PropTypes.string, | ||
message: PropTypes.string, | ||
children: PropTypes.element.isRequired, | ||
}; | ||
|
||
/** | ||
* Protect route elements that don't allow for FIRST_RESPONDER role | ||
* @param {PropTypes.InferProps<typeof ProtectedRouteProps>} props | ||
* @returns {React.ReactElement} | ||
*/ | ||
function ProtectedRoute({ | ||
restrictedRoles, | ||
role, | ||
destination = 'notFound', | ||
message, | ||
children, | ||
}) { | ||
const navigate = useNavigate(); | ||
useEffect(() => { | ||
if (restrictedRoles.includes(role)) { | ||
if (destination === 'forbidden') { | ||
navigate('/forbidden', { | ||
replace: true, | ||
}); | ||
} else { | ||
navigate('/not-found', { | ||
replace: true, | ||
state: { message }, | ||
}); | ||
} | ||
} | ||
}, [restrictedRoles, role, navigate, destination, message]); | ||
|
||
return restrictedRoles.includes(role) ? <Loader /> : children; | ||
} | ||
|
||
ProtectedRoute.propTypes = ProtectedRouteProps; | ||
|
||
/** | ||
* Top-level application component. * | ||
* @returns {React.ReactElement} | ||
*/ | ||
function App() { | ||
const { user, setUser } = useContext(Context); | ||
const { handleLogout } = useAuthorization(); | ||
|
||
const { isLoading } = useQuery({ | ||
queryKey: ['user'], | ||
queryFn: () => { | ||
return fetch('/api/v1/users/me', { credentials: 'include' }) | ||
.then((response) => response.json()) | ||
.then((response) => (response.ok ? response.json() : null)) | ||
.then((newUser) => { | ||
setUser(newUser); | ||
return newUser; | ||
}); | ||
}, | ||
}); | ||
const isLoggedIn = !isLoading && !!user?.id; | ||
|
||
return ( | ||
<> | ||
<Routes> | ||
<Route | ||
element={ | ||
<Redirect | ||
isLoading={isLoading} | ||
isLoggedIn={isLoggedIn} | ||
isLoggedInRequired={true} | ||
/> | ||
} | ||
> | ||
<Route | ||
path="/admin/patients/generate" | ||
element={<AdminPatientsGenerate />} | ||
/> | ||
<Route element={<Layout />}> | ||
<Route path="/patients" element={<Patients />} /> | ||
<Route path="/patients/:patientId" element={<PatientDetails />} /> | ||
<Route | ||
path="/patients/register/:patientId" | ||
element={ | ||
user ? ( | ||
<ProtectedRoute | ||
role={user?.role} | ||
restrictedRoles={['FIRST_RESPONDER']} | ||
message={'Patient does not exist.'} | ||
> | ||
<PatientRegistration /> | ||
</ProtectedRoute> | ||
) : ( | ||
<Loader /> | ||
) | ||
} | ||
/> | ||
useEffect(() => { | ||
try { | ||
handleRedirects(user, location, (to, options) => | ||
navigate(to, { ...options, replace: true }), | ||
); | ||
} catch { | ||
handleLogout(); | ||
} | ||
}, [handleRedirects, handleLogout, user, location, navigate]); | ||
|
||
<Route path="/admin/users" element={<AdminUsers />} /> | ||
<Route | ||
path="/admin/pending-users" | ||
element={<AdminPendingUsers />} | ||
/> | ||
<Route path="/dashboard" element={<Dashboard />} /> | ||
<Route path="*" element={<NotFound />} /> | ||
</Route> | ||
</Route> | ||
<Route | ||
element={<Redirect isLoading={isLoading} isLoggedIn={isLoggedIn} />} | ||
> | ||
<Route path="/" element={<Home />} /> | ||
<Route element={<AuthLayout />}> | ||
<Route path="/register" element={<Register />} /> | ||
<Route path="/register/:inviteId" element={<Register />} /> | ||
<Route path="/login" element={<Login />} /> | ||
<Route path="/password/forgot" element={<PasswordForgot />} /> | ||
<Route | ||
path="/password/:passwordResetToken" | ||
element={<PasswordReset />} | ||
/> | ||
<Route path="verify/:emailVerificationToken" element={<Verify />} /> | ||
</Route> | ||
</Route> | ||
</Routes> | ||
return isLoading ? ( | ||
<Center w="100vw" h="100vh"> | ||
<Loader /> | ||
</Center> | ||
) : ( | ||
<> | ||
<Outlet /> | ||
<Notifications position="bottom-right" /> | ||
</> | ||
); | ||
} | ||
|
||
App.propTypes = { | ||
handleRedirects: PropTypes.func.isRequired, | ||
}; | ||
|
||
export default App; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.