Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/Components/Navbar/AdminNavbar.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react';
import { useUser } from '../context/UserContext';
import { useAuth } from '../context/AuthContext';

export default function UserNavBar(props) {
const { user } = useUser();
const { setAuthenticated } = useAuth();
const getLinkClassName = (path) => {
const weAreAtGivenPath = path === window.location.pathname;
let className = 'flex items-center p-2 text-gray-900 rounded-lg dark:text-white';
Expand All @@ -15,7 +17,7 @@ export default function UserNavBar(props) {
};

function handleLogout() {
props.setAuthenticated(false);
setAuthenticated(false);
window.localStorage.removeItem('jwtToken');
window.location.reload();
}
Expand Down
4 changes: 3 additions & 1 deletion src/Components/Navbar/NavBarWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ import React from 'react';
import UserNavbar from './UserNavbar';
import AdminNavbar from './AdminNavbar';
import { useUser } from '../context/UserContext';
import { useAuth } from '../context/AuthContext';

function NavBarWrapper({
enableAdminNavbar = false,
component: Component,
...appProps
}) {
const { user, setUser } = useUser();
const { setAuthenticated } = useAuth();

function handleLogout() {
appProps.setAuthenticated(false);
setAuthenticated(false);
setUser(null);
window.localStorage.removeItem('jwtToken');
window.location.reload();
Expand Down
4 changes: 3 additions & 1 deletion src/Components/Navbar/UserNavbar.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React from 'react';
import { membershipState } from '../../Enums';
import { useUser } from '../context/UserContext';
import { useAuth } from '../context/AuthContext';

export default function UserNavbar(props) {
const { user } = useUser();
const { authenticated } = useAuth();
let initials = '';
if (user && user.firstName && user.lastName) {
initials = user.firstName[0] + user.lastName[0];
Expand Down Expand Up @@ -76,7 +78,7 @@ export default function UserNavbar(props) {
</div>

<div className="navbar-end">
{props.authenticated && user ? (
{authenticated && user ? (
<>
<div className="dropdown dropdown-end sm:hidden">
<div tabIndex={0} role="button" className="btn btn-ghost rounded-btn">Services</div>
Expand Down
8 changes: 5 additions & 3 deletions src/Components/Routing/PrivateRoute.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ import { Route, Redirect } from 'react-router-dom';
import { membershipState } from '../../Enums';
import { allowedIf } from '../../Routes';
import { useUser } from '../../Components/context/UserContext';
import { useAuth } from '../../Components/context/AuthContext';

export default function PrivateRoute({
component: Component,
appProps,
...params
}) {
const { user } = useUser();
const { authenticated } = useAuth();

// Check if the user's access level matches with route's access grant
const PERMISSION_LOOKUP_TABLE = {
[allowedIf.MEMBER]: user?.accessLevel >= membershipState.MEMBER,
[allowedIf.OFFICER_OR_ADMIN]: user?.accessLevel >= membershipState.OFFICER,
[allowedIf.AUTHENTICATED]: !!appProps.authenticated,
[allowedIf.UNAUTHENTICATED]: !appProps.authenticated,
[allowedIf.AUTHENTICATED]: !!authenticated,
[allowedIf.UNAUTHENTICATED]: !authenticated,
};

const isAllowed = PERMISSION_LOOKUP_TABLE[appProps.allowed] ?? false;
Expand All @@ -27,7 +29,7 @@ export default function PrivateRoute({
render={(props) => {
if (isAllowed) {
return <Component {...appProps} {...props} />;
} else if (appProps.authenticated) {
} else if (authenticated) {
return (
<Redirect
to={{
Expand Down
8 changes: 5 additions & 3 deletions src/Components/ShortcutKeyModal/SearchModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import './SearchModal.css';
import { officerOrAdminRoutes, signedOutRoutes, memberRoutes, notAuthenticatedRoutes } from '../../Routes';
import { membershipState } from '../../Enums';
import { useUser } from '../context/UserContext';
import { useAuth } from '../context/AuthContext';

export default function SearchModal({ appProps }) {
const [open, setOpen] = useState(false);
Expand All @@ -13,10 +14,11 @@ export default function SearchModal({ appProps }) {
const [selectItem, setSelectItem] = useState(0);
const { user } = useUser();
const [errorMsg, setErrorMsg] = useState('');
const { authenticated } = useAuth();

/**
* Returns the appropriate routes array based on the user's access level.
* @dependencies user.accessLevel, appProps.authenticated
* @dependencies user.accessLevel, authenticated
*/
const routes = useMemo(() => {
if (user.accessLevel === membershipState.MEMBER)
Expand All @@ -29,13 +31,13 @@ export default function SearchModal({ appProps }) {
...officerOrAdminRoutes.filter(r => r.pageName !== 'Edit User Info'),
...signedOutRoutes
];
if (!appProps.authenticated)
if (!authenticated)
return [
...notAuthenticatedRoutes,
...signedOutRoutes
];
return [...signedOutRoutes];
}, [user.accessLevel, appProps.authenticated]);
}, [user.accessLevel, authenticated]);

/**
* Helper function updates the keyword when the user types
Expand Down
10 changes: 10 additions & 0 deletions src/Components/context/AuthContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React, { createContext, useContext } from 'react';

export const AuthContext = createContext({
authenticated: false,
setAuthenticated: () => {}
});

export function useAuth() {
return useContext(AuthContext);
}
6 changes: 4 additions & 2 deletions src/Pages/Login/Login.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React, { useState } from 'react';
import { loginUser } from '../../APIFunctions/Auth';
import Background from '../../Components/Background/background';
import { useAuth } from '../../Components/context/AuthContext';

export default function Login(props) {
export default function Login() {
const { setAuthenticated } = useAuth();
const queryParams = new URLSearchParams(window.location.search);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
Expand All @@ -24,7 +26,7 @@ export default function Login(props) {
e.preventDefault();
const loginStatus = await loginUser(email, password);
if (!loginStatus.error) {
props.setAuthenticated(true);
setAuthenticated(true);
window.localStorage.setItem('jwtToken', loginStatus.token);
if (queryParams.get('redirect')) {
window.location.href = queryParams.get('redirect');
Expand Down
4 changes: 2 additions & 2 deletions src/Routing.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import NavBarWrapper from './Components/Navbar/NavBarWrapper';
import NotFoundPage from './Pages/NotFoundPage/NotFoundPage';

import { useUser } from './Components/context/UserContext';
import { useAuth } from './Components/context/AuthContext';

import { officerOrAdminRoutes, notAuthenticatedRoutes, signedOutRoutes } from './Routes.js';

export default function Routing({ appProps }) {
const { user, setUser } = useUser();
const userIsAuthenticated = appProps.authenticated;
const { authenticated } = useAuth();

const signedInRoutes = [...officerOrAdminRoutes, ...notAuthenticatedRoutes];

Expand Down Expand Up @@ -46,7 +47,6 @@ export default function Routing({ appProps }) {
appProps={{
allowed: allowedIf,
redirect,
authenticated:userIsAuthenticated,
...appProps
}}
component={props => getCorrectComponent(props)}
Expand Down
11 changes: 7 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Routing from './Routing';
import { checkIfUserIsSignedIn } from './APIFunctions/Auth';
import { UserContext } from './Components/context/UserContext';
import SearchModal from './Components/ShortcutKeyModal/SearchModal';
import { AuthContext } from './Components/context/AuthContext';

function App(props) {
const [authenticated, setAuthenticated] = useState(false);
Expand All @@ -29,10 +30,12 @@ function App(props) {
return (
!isAuthenticating && (
<UserContext.Provider value={{ user, setUser }}>
<BrowserRouter>
<SearchModal appProps={{ authenticated }} />
<Routing appProps={{ authenticated, setAuthenticated }} />
</BrowserRouter>
<AuthContext.Provider value={{ authenticated, setAuthenticated }}>
<BrowserRouter>
<SearchModal/>
<Routing/>
</BrowserRouter>
</AuthContext.Provider>
</UserContext.Provider>
)
);
Expand Down
15 changes: 11 additions & 4 deletions test/frontend/Routing.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Enzyme, { mount } from 'enzyme';
import { expect } from 'chai';
import Adapter from '@cfaester/enzyme-adapter-react-18';
import { UserContext } from '../../src/Components/context/UserContext';
import { AuthContext } from '../../src/Components/context/AuthContext';

import Routing from '../../src/Routing';
import Home from '../../src/Pages/Home/Home';
Expand All @@ -29,7 +30,6 @@ Object.defineProperty(window, 'localStorage', {
});

const adminAppProps = {
authenticated: true
};

const mockUser = { accessLevel: membershipState.ADMIN };
Expand All @@ -46,11 +46,18 @@ function getComponentFromRoute(route, props = adminAppProps, user = mockUser) {
setUser: () => {}
};

const mockAuthContext = {
authenticated: true,
setAuthenticated: () => {}
};

return mount(
<UserContext.Provider value={mockUserContext}>
<MemoryRouter initialEntries={[route]}>
<Routing appProps={props} />
</MemoryRouter>
<AuthContext.Provider value={mockAuthContext}>
<MemoryRouter initialEntries={[route]}>
<Routing appProps={props} />
</MemoryRouter>
</AuthContext.Provider>
</UserContext.Provider>
);
}
Expand Down