diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 74b5e053450a48a6bdb4d71aad648e7af821975c..0000000000000000000000000000000000000000 --- a/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.style.ts b/src/App.style.ts new file mode 100644 index 0000000000000000000000000000000000000000..94f03fdec5580d8c6272d090a9ce013ff3473e09 --- /dev/null +++ b/src/App.style.ts @@ -0,0 +1,29 @@ +import { createMuiTheme } from '@material-ui/core/styles'; + +export const muiTheme = createMuiTheme({ + palette: { + primary: { + main: '#5e5ce4', + }, + secondary: { + main: '#e2e45c', + }, + }, + + typography: { + fontSize: 16, + }, + + overrides: { + MuiTabs: { + indicator: { + backgroundColor: 'white', + }, + }, + MuiTab: { + wrapper: { + flexDirection: 'row', + }, + }, + }, +}); diff --git a/src/App.tsx b/src/App.tsx index 2923c4b379803a998e68b1d6a1dfff8218766ae3..e5cf11ca04b0514606273bed02a369290f0c8fd0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,76 +1,71 @@ import React, { FC, useEffect, useState } from 'react'; -import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles'; +import { ThemeProvider } from '@material-ui/core/styles'; import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; import { AuthUser } from 'components/AuthUser/AuthUser'; import { LandingPage } from 'components/NonAuthUser/LandingPage/LandingPage'; -import { PrivateRoute } from 'api/PrivateRoute/PrivateRoute'; +import { PrivateRoute } from 'components/PrivateRoute/PrivateRoute'; import { AuthRoutes, NonAuthRoutes } from 'api/routes'; import { NotFound } from 'components/NonAuthUser/NotFound/NotFound'; -import { ProfilePage } from 'components/AuthUser/ProfilePage/ProfilePage'; +import { ProfilePage } from 'components/AuthUser/Dashboard/ProfilePage/ProfilePage'; import { Roles } from 'api/userRoles'; import { Unauthorized } from 'components/NonAuthUser/Unauthorized/Unauthorized'; -import { ReservationPage } from 'components/AuthUser/ReservationPage/ReservationPage'; -import { HomePage } from 'components/AuthUser/HomePage/HomePage'; -import { SignInForm } from 'components/AuthUser/SignInForm/SignInForm'; +import { HomePage } from 'components/AuthUser/Dashboard/HomePage/HomePage'; import { AuthContext } from 'components/AuthUser/AuthContext'; +import { configDjangoCookieName } from 'api/configDjangoCookieName'; +import { Dashboard } from 'components/AuthUser/Dashboard/Dashboard'; +import { isAuthenticated } from 'api/isAuthenticated'; +import { muiTheme } from 'App.style'; +import { getRole } from 'api/getRole'; -const theme = createMuiTheme({ - palette: { - primary: { - main: '#5e5ce4', - }, - secondary: { - main: '#e2e45c', - }, - }, - - typography: { - fontSize: 16, - }, - - overrides: { - MuiTabs: { - indicator: { - backgroundColor: 'white', - }, - }, - MuiTab: { - wrapper: { - flexDirection: 'row', - }, - }, - }, -}); - +/** + * Entry point of the app. + */ export const App: FC = () => { + configDjangoCookieName(); const [role, setRole] = useState(''); - const value = { role, setRole }; + const [isAuth, setIsAuth] = useState<boolean>(false); + + const value = { role, setRole, isAuth, setIsAuth }; + + useEffect(() => { + // Initialize asking the server if this session is already logged in. + isAuthenticated().then((responseState) => setIsAuth(responseState)); + getRole().then((responseRole) => setRole(responseRole)); + }, [isAuth, role]); return ( - <ThemeProvider theme={theme}> - <Router> - <div data-testid="App"> - <Switch> - <Route exact path={NonAuthRoutes.home} component={LandingPage} /> - <AuthContext.Provider value={value}> + <div data-testid="App"> + <ThemeProvider theme={muiTheme}> + <AuthContext.Provider value={value}> + <Router> + <Switch> + <Route exact path={NonAuthRoutes.home} component={LandingPage} /> + <Route path={NonAuthRoutes.auth} component={AuthUser} /> <PrivateRoute + Component={Dashboard} path={AuthRoutes.dashboard} + requiredRoles={[Roles.admin, Roles.operator, Roles.senior]} + /> + <PrivateRoute Component={HomePage} + path={AuthRoutes.dashboard} requiredRoles={[Roles.admin, Roles.operator, Roles.senior]} /> <PrivateRoute - path={AuthRoutes.profile} Component={ProfilePage} + path={AuthRoutes.profile} requiredRoles={[Roles.admin, Roles.operator, Roles.senior]} /> - </AuthContext.Provider> - <Route path={AuthRoutes.reservation} component={ReservationPage} /> - <Route path={NonAuthRoutes.unauthorized} component={Unauthorized} /> - <Route component={NotFound} /> - </Switch> - </div> - </Router> - </ThemeProvider> + <Route + path={NonAuthRoutes.unauthorized} + component={Unauthorized} + /> + <Route component={NotFound} /> + </Switch> + </Router> + </AuthContext.Provider> + </ThemeProvider> + </div> ); }; diff --git a/src/api/EntryPoint.ts b/src/api/EntryPoint.ts deleted file mode 100644 index c3ef802d60c206998c340867a068aa11769eff33..0000000000000000000000000000000000000000 --- a/src/api/EntryPoint.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Contains server api entrypoints. - * Every server api call must be defined here and then used in the code. - */ -export enum EntryPoint { - login = '/api/web/login', - seniors = 'api/web/seniors/', -} diff --git a/src/api/configDjangoCookieName.ts b/src/api/configDjangoCookieName.ts new file mode 100644 index 0000000000000000000000000000000000000000..4213d5ad9490abdc3726d9a94db4c7059485d158 --- /dev/null +++ b/src/api/configDjangoCookieName.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; + +/** + * Config for django compatibility. + */ +export const configDjangoCookieName = (): void => { + axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN'; + axios.defaults.xsrfCookieName = 'csrftoken'; + axios.defaults.withCredentials = true; +}; diff --git a/src/api/fetchCookie.ts b/src/api/fetchCookie.ts new file mode 100644 index 0000000000000000000000000000000000000000..d531ad2c1a0ae6fd2074300c38dfcc951c8f87f1 --- /dev/null +++ b/src/api/fetchCookie.ts @@ -0,0 +1,9 @@ +import axios from 'axios'; + +/** + * Call for csrf cookie. This cookie is the user session identifier and + * must be sent during the login process. + * @returns csrf cookie + */ +export const fetchCookie = async (): Promise<string> => + axios('/api/web/csrf').then((res) => res.data.token); diff --git a/src/api/getRole.ts b/src/api/getRole.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb06049debab84ddb437b373319a1c910d611ccc --- /dev/null +++ b/src/api/getRole.ts @@ -0,0 +1,9 @@ +import axios from 'axios'; + +/** + * Asks for the current set role. + * @returns empty if the role is not set or a name between those defined + * in {@see userRoles} + */ +export const getRole = async (): Promise<string> => + axios('/api/web/login/get_role').then((res) => res.data.role); diff --git a/src/api/isAuthenticated.ts b/src/api/isAuthenticated.ts new file mode 100644 index 0000000000000000000000000000000000000000..041b4be81d010544d23c5515ef4ea15808f70aa4 --- /dev/null +++ b/src/api/isAuthenticated.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; + +/** + * Ask the server if the user is authenticated. + * @returns true or false if the user is authenticated. + */ +export const isAuthenticated = async (): Promise<boolean> => + axios('/api/web/login/is_authenticated').then( + (res) => res.data.is_authenticated, + ); diff --git a/src/api/routes.ts b/src/api/routes.ts index 23e986f648efbacf1a0596a61dacbe91bba0fb43..cd7039900d83be1a34aeb9cd4247f5ac0ad87c53 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -5,6 +5,7 @@ */ export enum AuthRoutes { dashboard = '/dashboard', + home = '/home', profile = '/profile', reservation = '/reservation', choseRole = '/choseRole', diff --git a/src/api/userRoles.ts b/src/api/userRoles.ts index c12d7c4710680068e1b855b69e2a70bbd6e266b8..1f7b30dbdd7631fbb0bd0eb1cc2642fe893b6635 100644 --- a/src/api/userRoles.ts +++ b/src/api/userRoles.ts @@ -1,5 +1,7 @@ export enum Roles { - visitor = 'visitor', + /** Website visitor, is not logged. Can access to NonAuthRoutes */ + visitor = '', + /** Can access to private and restricted routes depending on their permissions level. */ senior = 'senior', admin = 'admin', operator = 'operator', diff --git a/src/components/AuthUser/AuthContext.tsx b/src/components/AuthUser/AuthContext.tsx index b4418b9eee392463c318ed4e48fcd3c654a8da39..584aeb648406a755445a50c1a361692ee4a65180 100644 --- a/src/components/AuthUser/AuthContext.tsx +++ b/src/components/AuthUser/AuthContext.tsx @@ -3,12 +3,18 @@ import { createContext } from 'react'; export type AuthData = { role: string; + isAuth: boolean; setRole: (role: string) => void; + setIsAuth: (state: boolean) => void; }; export const AuthContext = createContext<AuthData>({ role: Roles.visitor, + isAuth: false, setRole: () => { // Do nothing }, + setIsAuth: () => { + // Do nothing + }, }); diff --git a/src/components/AuthUser/AuthUser.tsx b/src/components/AuthUser/AuthUser.tsx index dbac120b9f286d191b9d771a329786e62823407f..242bbb9c7153e73bdb14ec135cefb94bc7799986 100644 --- a/src/components/AuthUser/AuthUser.tsx +++ b/src/components/AuthUser/AuthUser.tsx @@ -1,16 +1,22 @@ import React, { FC } from 'react'; import Container from '@material-ui/core/Container'; import { Route, useRouteMatch } from 'react-router-dom'; -import { NonAuthRoutes } from 'api/routes'; +import { AuthRoutes, NonAuthRoutes } from 'api/routes'; import { SignInForm } from 'components/AuthUser/SignInForm/SignInForm'; import { SignUpForm } from 'components/AuthUser/SignUpForm/SignUpForm'; +import { ChoseRole } from 'components/AuthUser/ChoseRole/ChoseRole'; +import { RestrictedRoute } from 'components/RestrictedRoute/RestrictedRoute'; export const AuthUser: FC = () => { const { path } = useRouteMatch(); return ( <Container maxWidth="sm"> - <Route path={`${path}${NonAuthRoutes.signIn}`} component={SignInForm} /> + <RestrictedRoute + path={`${path}${NonAuthRoutes.signIn}`} + Component={SignInForm} + /> <Route path={`${path}${NonAuthRoutes.signUp}`} component={SignUpForm} /> + <Route path={`${path}${AuthRoutes.choseRole}`} component={ChoseRole} /> </Container> ); }; diff --git a/src/components/AuthUser/Dashboard/Dashboard.tsx b/src/components/AuthUser/Dashboard/Dashboard.tsx new file mode 100644 index 0000000000000000000000000000000000000000..452ed8fe75e028537f14cda215f819de93566e8e --- /dev/null +++ b/src/components/AuthUser/Dashboard/Dashboard.tsx @@ -0,0 +1,16 @@ +import React, { FC } from 'react'; +import Container from '@material-ui/core/Container'; +import { Route, useRouteMatch } from 'react-router-dom'; +import { AuthRoutes } from 'api/routes'; +import { HomePage } from 'components/AuthUser/Dashboard/HomePage/HomePage'; +import { ProfilePage } from 'components/AuthUser/Dashboard/ProfilePage/ProfilePage'; + +export const Dashboard: FC = () => { + const { path } = useRouteMatch(); + return ( + <Container maxWidth="sm"> + <Route exact path={`${path}${AuthRoutes.home}`} component={HomePage} /> + <Route path={`${path}${AuthRoutes.profile}`} component={ProfilePage} /> + </Container> + ); +}; diff --git a/src/components/AuthUser/HomePage/HomePage.tsx b/src/components/AuthUser/Dashboard/HomePage/HomePage.tsx similarity index 88% rename from src/components/AuthUser/HomePage/HomePage.tsx rename to src/components/AuthUser/Dashboard/HomePage/HomePage.tsx index 99b3448459007f960afbfdacbfefac747d0e5d9a..972aa96cb568187105ff752f659aeb72fbe89c70 100644 --- a/src/components/AuthUser/HomePage/HomePage.tsx +++ b/src/components/AuthUser/Dashboard/HomePage/HomePage.tsx @@ -1,13 +1,18 @@ import React, { FC } from 'react'; import { createStyles, Theme, makeStyles } from '@material-ui/core/styles'; -import Drawer from '@material-ui/core/Drawer'; -import AppBar from '@material-ui/core/AppBar'; -import CssBaseline from '@material-ui/core/CssBaseline'; -import Toolbar from '@material-ui/core/Toolbar'; -import List from '@material-ui/core/List'; -import Typography from '@material-ui/core/Typography'; -import ListItem from '@material-ui/core/ListItem'; -import ListItemText from '@material-ui/core/ListItemText'; + +import { AuthRoutes } from 'api/routes'; +import { + AppBar, + Toolbar, + Typography, + Button, + Drawer, + List, + ListItem, + ListItemText, +} from '@material-ui/core'; +import { Link } from 'react-router-dom'; const drawerWidth = 240; @@ -41,12 +46,14 @@ export const HomePage: FC = () => { return ( <div className={classes.root}> - <CssBaseline /> <AppBar position="fixed" className={classes.appBar}> <Toolbar> <Typography variant="h6" noWrap> Moveaid </Typography> + <Button variant="contained"> + <Link to={`/dashboard${AuthRoutes.profile}`}>Profile</Link> + </Button> </Toolbar> </AppBar> <Drawer diff --git a/src/components/AuthUser/HomePage/MapElement.tsx b/src/components/AuthUser/Dashboard/HomePage/MapElement.tsx similarity index 100% rename from src/components/AuthUser/HomePage/MapElement.tsx rename to src/components/AuthUser/Dashboard/HomePage/MapElement.tsx diff --git a/src/components/AuthUser/HomePage/NavBar.tsx b/src/components/AuthUser/Dashboard/HomePage/NavBar.tsx similarity index 96% rename from src/components/AuthUser/HomePage/NavBar.tsx rename to src/components/AuthUser/Dashboard/HomePage/NavBar.tsx index 1bc3cce61bc2b5bf11a849a6d0943de84fe6664e..3d61c755d485269fafd3f0e547f58782e71c574f 100644 --- a/src/components/AuthUser/HomePage/NavBar.tsx +++ b/src/components/AuthUser/Dashboard/HomePage/NavBar.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { makeStyles } from '@material-ui/core/styles'; -import { Tabs, Tab, AppBar } from '@material-ui/core'; +import { Tabs, Tab } from '@material-ui/core'; import HomeIcon from '@material-ui/icons/Home'; import ImportContactsIcon from '@material-ui/icons/ImportContacts'; import AccountCircleIcon from '@material-ui/icons/AccountCircle'; diff --git a/src/components/AuthUser/HomePage/NavBarLogin.tsx b/src/components/AuthUser/Dashboard/HomePage/NavBarLogin.tsx similarity index 100% rename from src/components/AuthUser/HomePage/NavBarLogin.tsx rename to src/components/AuthUser/Dashboard/HomePage/NavBarLogin.tsx diff --git a/src/components/AuthUser/HomePage/Numbers.tsx b/src/components/AuthUser/Dashboard/HomePage/Numbers.tsx similarity index 100% rename from src/components/AuthUser/HomePage/Numbers.tsx rename to src/components/AuthUser/Dashboard/HomePage/Numbers.tsx diff --git a/src/components/AuthUser/HomePage/Steps.tsx b/src/components/AuthUser/Dashboard/HomePage/Steps.tsx similarity index 100% rename from src/components/AuthUser/HomePage/Steps.tsx rename to src/components/AuthUser/Dashboard/HomePage/Steps.tsx diff --git a/src/components/AuthUser/ProfilePage/ProfilePage.md b/src/components/AuthUser/Dashboard/ProfilePage/ProfilePage.md similarity index 100% rename from src/components/AuthUser/ProfilePage/ProfilePage.md rename to src/components/AuthUser/Dashboard/ProfilePage/ProfilePage.md diff --git a/src/components/AuthUser/ProfilePage/ProfilePage.test.tsx b/src/components/AuthUser/Dashboard/ProfilePage/ProfilePage.test.tsx similarity index 100% rename from src/components/AuthUser/ProfilePage/ProfilePage.test.tsx rename to src/components/AuthUser/Dashboard/ProfilePage/ProfilePage.test.tsx diff --git a/src/components/AuthUser/ProfilePage/ProfilePage.tsx b/src/components/AuthUser/Dashboard/ProfilePage/ProfilePage.tsx similarity index 100% rename from src/components/AuthUser/ProfilePage/ProfilePage.tsx rename to src/components/AuthUser/Dashboard/ProfilePage/ProfilePage.tsx diff --git a/src/components/AuthUser/ReservationPage/Reservation/Reservation.tsx b/src/components/AuthUser/Dashboard/ReservationPage/Reservation/Reservation.tsx similarity index 100% rename from src/components/AuthUser/ReservationPage/Reservation/Reservation.tsx rename to src/components/AuthUser/Dashboard/ReservationPage/Reservation/Reservation.tsx diff --git a/src/components/AuthUser/ReservationPage/ReservationPage.test.tsx b/src/components/AuthUser/Dashboard/ReservationPage/ReservationPage.test.tsx similarity index 100% rename from src/components/AuthUser/ReservationPage/ReservationPage.test.tsx rename to src/components/AuthUser/Dashboard/ReservationPage/ReservationPage.test.tsx diff --git a/src/components/AuthUser/ReservationPage/ReservationPage.tsx b/src/components/AuthUser/Dashboard/ReservationPage/ReservationPage.tsx similarity index 98% rename from src/components/AuthUser/ReservationPage/ReservationPage.tsx rename to src/components/AuthUser/Dashboard/ReservationPage/ReservationPage.tsx index 13dc78eb5efca51fd47e55a4ec85892ef381368e..9e176c0334e8f86cf95391893e51a7d4676c8a48 100644 --- a/src/components/AuthUser/ReservationPage/ReservationPage.tsx +++ b/src/components/AuthUser/Dashboard/ReservationPage/ReservationPage.tsx @@ -8,7 +8,6 @@ import { createMuiTheme, responsiveFontSizes, MuiThemeProvider, - Container, Dialog, DialogActions, DialogContent, @@ -20,7 +19,7 @@ import { import AddIcon from '@material-ui/icons/Add'; import Fab from '@material-ui/core/Fab'; -import { Reservation } from 'components/AuthUser/ReservationPage/Reservation/Reservation'; +import { Reservation } from 'components/AuthUser/Dashboard/ReservationPage/Reservation/Reservation'; import { NavBar } from '../HomePage/NavBar'; let themeResp = createMuiTheme(); diff --git a/src/components/AuthUser/SignInForm/CredentialsType.ts b/src/components/AuthUser/SignInForm/CredentialsType.ts new file mode 100644 index 0000000000000000000000000000000000000000..e93cec7875260e44be6fcda17d5c3f1528027753 --- /dev/null +++ b/src/components/AuthUser/SignInForm/CredentialsType.ts @@ -0,0 +1,4 @@ +export type CredentialsType = { + username: string; + password: string; +}; diff --git a/src/components/AuthUser/SignInForm/SignInForm.tsx b/src/components/AuthUser/SignInForm/SignInForm.tsx index d80391a8bcd66bdb457a62f61ac680aa2e2f3af0..a640ce6dcb3b3c36b620e7402c48d401123982fb 100644 --- a/src/components/AuthUser/SignInForm/SignInForm.tsx +++ b/src/components/AuthUser/SignInForm/SignInForm.tsx @@ -6,51 +6,44 @@ import { InputField } from 'components/AuthUser/InputField/InputField'; import { useHistory } from 'react-router-dom'; import { AuthRoutes } from 'api/routes'; import { AuthContext } from 'components/AuthUser/AuthContext'; +import { fetchCookie } from 'api/fetchCookie'; import { useStyles } from './useStyles'; +import { CredentialsType } from './CredentialsType'; -const configDjangoCookieName = (): void => { - axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN'; - axios.defaults.xsrfCookieName = 'csrftoken'; - axios.defaults.withCredentials = true; +const defaultValues = { + username: '', + password: '', }; export const SignInForm: FC = () => { - const [isCookieFetched, setisCookieFetched] = useState<string>(''); - - configDjangoCookieName(); - useEffect(() => { - const fetchCookie = async (): Promise<unknown> => { - const response = await axios('/api/web/csrf'); - setisCookieFetched(response.data.token); - return null; - }; - if (!isCookieFetched) fetchCookie(); - }, [isCookieFetched]); const history = useHistory(); - const { setRole } = useContext(AuthContext); + const { setRole, setIsAuth } = useContext(AuthContext); + const [cookie, setCookie] = useState<string>(''); - interface FormData { - username: string; - password: string; - } + useEffect(() => { + let isMounted = true; + if (isMounted) + fetchCookie().then((cookieResponse) => setCookie(cookieResponse)); - const defaultValues: FormData = { - username: '', - password: '', - }; + return () => { + isMounted = false; + }; + }, [cookie]); - const { control, errors, setError, handleSubmit } = useForm<FormData>({ + const { control, errors, setError, handleSubmit } = useForm<CredentialsType>({ defaultValues, }); - const onSubmit: SubmitHandler<FormData> = (values: FormData) => { + const onSubmit: SubmitHandler<CredentialsType> = ( + values: CredentialsType, + ) => { axios .post( '/api/web/login', { username: values.username, password: values.password, - csrfmiddlewaretoken: isCookieFetched, + csrfmiddlewaretoken: cookie, }, { headers: { @@ -70,7 +63,8 @@ export const SignInForm: FC = () => { }); } else if (response.data.status === 'success') { setRole(response.data.role); - history.replace(AuthRoutes.dashboard); + setIsAuth(true); + history.replace(`${AuthRoutes.dashboard}${AuthRoutes.home}`); } }); }; diff --git a/src/components/AuthUser/SignInForm/postCredentials.ts b/src/components/AuthUser/SignInForm/postCredentials.ts new file mode 100644 index 0000000000000000000000000000000000000000..0ab2746444b53c20045f73647237af7c4df40306 --- /dev/null +++ b/src/components/AuthUser/SignInForm/postCredentials.ts @@ -0,0 +1,23 @@ +import axios from 'axios'; +import { CredentialsType } from './CredentialsType'; + +export const postCredentials = async ( + values: CredentialsType, + cookie: React.Dispatch<React.SetStateAction<string>>, +): Promise<unknown> => { + const response = await axios.post( + '/api/web/login', + { + username: values.username, + password: values.password, + csrfmiddlewaretoken: cookie, + }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + return null; +}; diff --git a/src/api/PrivateRoute/PrivateRoute.tsx b/src/components/PrivateRoute/PrivateRoute.tsx similarity index 61% rename from src/api/PrivateRoute/PrivateRoute.tsx rename to src/components/PrivateRoute/PrivateRoute.tsx index e75de44b8eb69f4ded85503d5536e2cd7e724a0c..6360e711b17e388405f7fec0a82d473278919ce0 100644 --- a/src/api/PrivateRoute/PrivateRoute.tsx +++ b/src/components/PrivateRoute/PrivateRoute.tsx @@ -1,15 +1,12 @@ -import React, { useState, useEffect, useContext } from 'react'; -import axios from 'axios'; +import React, { useContext } from 'react'; import { Route, Redirect, RouteProps } from 'react-router-dom'; import { NonAuthRoutes } from 'api/routes'; import { AuthContext } from 'components/AuthUser/AuthContext'; -import { Roles } from 'api/userRoles'; /** * A wrapper for <Route> that redirects to the login screen if you're not yet authenticated. * Every non-public route must be wrapped with this component. * */ - type Props = { Component: React.FC<RouteProps>; path: string; @@ -22,24 +19,11 @@ export const PrivateRoute = ({ path, requiredRoles, }: Props): JSX.Element => { - const [auth, setAuth] = useState<boolean>(false); - const { role } = useContext(AuthContext); + const { role, isAuth } = useContext(AuthContext); - useEffect(() => { - const fetch = async (): Promise<unknown> => { - const result = await axios('/api/web/login/is_authenticated'); - setAuth(result.data.is_authenticated); - return null; - }; - /* - Check if user is logged in. - Avoiding this condition would call is\_authenticated every time - this component state is triggered, falling in unnecessary calls to the - server. - */ - if (role !== Roles.visitor) fetch(); - }, [auth]); + // Check if the role is contained in the roles array (passed as props). const userHasRequiredRole = requiredRoles.includes(role); + const message = userHasRequiredRole ? 'Please log in to view this page' : 'Your role is not allowed'; @@ -49,12 +33,12 @@ export const PrivateRoute = ({ exact={false} path={path} render={(props: RouteProps) => - auth && userHasRequiredRole ? ( + isAuth && userHasRequiredRole ? ( <Component {...props} /> ) : ( <Redirect to={{ - pathname: userHasRequiredRole + pathname: !userHasRequiredRole ? `${NonAuthRoutes.auth}${NonAuthRoutes.signIn}` : NonAuthRoutes.unauthorized, state: { diff --git a/src/components/RestrictedRoute/RestrictedRoute.tsx b/src/components/RestrictedRoute/RestrictedRoute.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3be9157b076fd142ff3f9e7a7c4b7c02b9b4bc0e --- /dev/null +++ b/src/components/RestrictedRoute/RestrictedRoute.tsx @@ -0,0 +1,60 @@ +import React, { useState, useEffect } from 'react'; +import { Route, Redirect, RouteProps } from 'react-router-dom'; +import { AuthRoutes } from 'api/routes'; +import { isAuthenticated } from 'api/isAuthenticated'; +import { CircularProgress } from '@material-ui/core'; + +/** + * + * */ +type Props = { + Component: React.FC<RouteProps>; + path: string; +}; + +/** + * Wrapper for Route that basing on if the user is authenticated, + * redirects to: + * - Entry point of the private route (the homepage); + * - Login page. + */ + +/* eslint-disable react/jsx-props-no-spreading */ +export const RestrictedRoute = ({ Component, path }: Props): JSX.Element => { + const [isAuth, setIsAuth] = useState<boolean>(false); + const [isLoading, setLoading] = useState<boolean>(false); + + useEffect(() => { + let isMounted = true; + + isAuthenticated().then((state) => { + if (isMounted) { + setIsAuth(state); + setLoading(true); + } + }); + + return () => { + isMounted = false; + }; + }, [isLoading]); + + return !isLoading ? ( + <CircularProgress /> + ) : ( + <Route + path={path} + render={(props: RouteProps) => + !isAuth ? ( + // Redirect to component. + <Component {...props} /> + ) : ( + // Redirect to homepage. + <Redirect + to={{ pathname: `${AuthRoutes.dashboard}${AuthRoutes.home}` }} + /> + ) + } + /> + ); +};