From e6a29e525399324d70c06f9c9ca82f4eb0b28df5 Mon Sep 17 00:00:00 2001 From: Mateusz Nestorowicz Date: Sun, 14 Mar 2021 22:51:53 +0100 Subject: [PATCH] feat(client): add ConditionRoute --- .../client/src/interfaces/lazyLoading.d.ts | 4 +- .../api/AuthProvider/AuthProvider.context.ts | 2 +- .../api/AuthProvider/AuthProvider.types.d.ts | 2 +- .../src/providers/api/AuthProvider/index.tsx | 14 ++---- packages/client/src/providers/index.tsx | 16 +++---- .../ConditionRoute/Condition.types.d.ts | 20 ++++++++ .../src/routes/ConditionRoute/index.tsx | 47 +++++++++++++++++++ packages/client/src/routes/PrivateRoute.tsx | 18 ------- packages/client/src/routes/PublicRoute.tsx | 18 ------- packages/client/src/routes/conditions.ts | 43 +++++++++++++++++ packages/client/src/routes/index.tsx | 23 ++++----- packages/client/src/routes/routesConfig.ts | 36 ++++++++++---- 12 files changed, 163 insertions(+), 80 deletions(-) create mode 100644 packages/client/src/routes/ConditionRoute/Condition.types.d.ts create mode 100644 packages/client/src/routes/ConditionRoute/index.tsx delete mode 100644 packages/client/src/routes/PrivateRoute.tsx delete mode 100644 packages/client/src/routes/PublicRoute.tsx create mode 100644 packages/client/src/routes/conditions.ts diff --git a/packages/client/src/interfaces/lazyLoading.d.ts b/packages/client/src/interfaces/lazyLoading.d.ts index a002f870..79e62ca9 100644 --- a/packages/client/src/interfaces/lazyLoading.d.ts +++ b/packages/client/src/interfaces/lazyLoading.d.ts @@ -1,5 +1,5 @@ +export type LazyLoadingState = 'loading' | 'error' | 'data'; export interface LazyLoading { - loading: boolean; + state: LazyLoadingState; data: T; - error: boolean; } diff --git a/packages/client/src/providers/api/AuthProvider/AuthProvider.context.ts b/packages/client/src/providers/api/AuthProvider/AuthProvider.context.ts index 76b0f37e..82b93721 100644 --- a/packages/client/src/providers/api/AuthProvider/AuthProvider.context.ts +++ b/packages/client/src/providers/api/AuthProvider/AuthProvider.context.ts @@ -3,7 +3,7 @@ import { createContext } from 'react'; import { AuthProps } from './AuthProvider.types'; const defaultValues: AuthProps = { - checkAuth: () => Promise.resolve(false), + checkAuth: () => Promise.resolve(undefined), logout: () => {}, login: () => Promise.resolve(false), register: () => Promise.resolve(false), diff --git a/packages/client/src/providers/api/AuthProvider/AuthProvider.types.d.ts b/packages/client/src/providers/api/AuthProvider/AuthProvider.types.d.ts index bef833d4..8c7380eb 100644 --- a/packages/client/src/providers/api/AuthProvider/AuthProvider.types.d.ts +++ b/packages/client/src/providers/api/AuthProvider/AuthProvider.types.d.ts @@ -27,6 +27,6 @@ interface AuthProviderProps { export interface AuthProps { login: (user: LoginData) => Promise; register: (user: RegistrationData) => Promise; - checkAuth: () => Promise; + checkAuth: () => Promise; logout: () => void; } diff --git a/packages/client/src/providers/api/AuthProvider/index.tsx b/packages/client/src/providers/api/AuthProvider/index.tsx index b15926b7..216d604d 100644 --- a/packages/client/src/providers/api/AuthProvider/index.tsx +++ b/packages/client/src/providers/api/AuthProvider/index.tsx @@ -1,5 +1,5 @@ import { AxiosResponse } from 'axios'; -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback } from 'react'; import { localStorageKeys } from '../../../constants'; import { useCurrentUser } from '../../../hooks'; @@ -21,28 +21,24 @@ const AuthProvider = ({ children }: AuthProviderProps) => { const checkAuth = useCallback(async () => { if (currentUser) { - return true; + return currentUser; } if (expiration) { return axios .get>('/auth/me') .then(({ data: { user, expirationDate } }) => { setCurrentUser(user, expirationDate); - return true; + return user; }) .catch(() => { setCurrentUser(undefined); - return false; + return undefined; }); } setCurrentUser(undefined); - return false; + return undefined; }, [axios, expiration, currentUser, setCurrentUser]); - useEffect(() => { - checkAuth(); - }, [checkAuth]); - const login = useCallback( async (userData: LoginData) => { const { diff --git a/packages/client/src/providers/index.tsx b/packages/client/src/providers/index.tsx index c592a50e..a5ece2d1 100644 --- a/packages/client/src/providers/index.tsx +++ b/packages/client/src/providers/index.tsx @@ -12,17 +12,17 @@ interface ProvidersProps { } const Providers = ({ children }: ProvidersProps) => ( - - - - + + + + {children} - - - - + + + + ); export default Providers; diff --git a/packages/client/src/routes/ConditionRoute/Condition.types.d.ts b/packages/client/src/routes/ConditionRoute/Condition.types.d.ts new file mode 100644 index 00000000..d42d6037 --- /dev/null +++ b/packages/client/src/routes/ConditionRoute/Condition.types.d.ts @@ -0,0 +1,20 @@ +import { RouteProps } from 'react-router'; + +import { User } from '../../providers/api/CurrentUserProvider/CurrentUserProvider.types'; + +export interface ConditionFuncProps { + currentUser: User | undefined; +} + +export type ConditionFunc = (props: ConditionFuncProps) => boolean; + +export interface BasicConditionProps { + condition: ConditionFunc; + redirectTo: string; +} + +export interface BasicRouteProps extends RouteProps { + path: string; +} + +export interface ConditionRouteProps extends BasicConditionProps, BasicRouteProps {} diff --git a/packages/client/src/routes/ConditionRoute/index.tsx b/packages/client/src/routes/ConditionRoute/index.tsx new file mode 100644 index 00000000..70df2ef6 --- /dev/null +++ b/packages/client/src/routes/ConditionRoute/index.tsx @@ -0,0 +1,47 @@ +import React, { useEffect, useState } from 'react'; +import { Redirect, Route } from 'react-router-dom'; + +import { PageLoader } from '../../elements'; +import { useAuth } from '../../hooks'; +import { LazyLoading } from '../../interfaces/lazyLoading'; +import { ConditionRouteProps } from './Condition.types'; + +const ConditionRoute = ({ redirectTo, condition, ...props }: ConditionRouteProps) => { + const { checkAuth } = useAuth(); + const [{ data, state }, setLazyLoading] = useState>({ + data: false, + state: 'loading', + }); + + useEffect(() => { + let isActive = true; + + checkAuth() + .then((currentUser) => { + if (isActive) { + const routeCondition = condition({ currentUser }); + setLazyLoading(() => ({ state: 'data', data: routeCondition })); + } + }) + .catch(() => { + if (isActive) { + setLazyLoading((prev) => ({ ...prev, state: 'error' })); + } + }); + + return () => { + isActive = false; + }; + }, [checkAuth, condition]); + + switch (true) { + case state === 'loading': + return ; + case state === 'data' && data: + return ; + default: + return ; + } +}; + +export default ConditionRoute; diff --git a/packages/client/src/routes/PrivateRoute.tsx b/packages/client/src/routes/PrivateRoute.tsx deleted file mode 100644 index ec592857..00000000 --- a/packages/client/src/routes/PrivateRoute.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { RouteProps } from 'react-router'; -import { Redirect, Route } from 'react-router-dom'; - -import { routes } from '../constants'; -import { useCurrentUser } from '../hooks'; - -const RouteGuard = (props: RouteProps) => { - const [currentUser] = useCurrentUser(); - - if (currentUser) { - return ; - } - - return ; -}; - -export default RouteGuard; diff --git a/packages/client/src/routes/PublicRoute.tsx b/packages/client/src/routes/PublicRoute.tsx deleted file mode 100644 index a93fe127..00000000 --- a/packages/client/src/routes/PublicRoute.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { RouteProps } from 'react-router'; -import { Redirect, Route } from 'react-router-dom'; - -import { routes } from '../constants'; -import { useCurrentUser } from '../hooks'; - -const PublicGuard = (props: RouteProps) => { - const [currentUser] = useCurrentUser(); - - if (currentUser) { - return ; - } - - return ; -}; - -export default PublicGuard; diff --git a/packages/client/src/routes/conditions.ts b/packages/client/src/routes/conditions.ts new file mode 100644 index 00000000..6a8ffbad --- /dev/null +++ b/packages/client/src/routes/conditions.ts @@ -0,0 +1,43 @@ +import { routes } from '../constants'; +import { Role } from '../enums/role.enum'; +import { BasicConditionProps, ConditionFunc } from './ConditionRoute/Condition.types'; + +type onlyAuthenticatedConditionType = (requiredRoles?: [Role]) => ConditionFunc; + +export const onlyAuthenticatedCondition: onlyAuthenticatedConditionType = ( + requiredRoles?: [Role], +) => { + return ({ currentUser }) => { + if (!currentUser) { + return false; + } + if (!requiredRoles || !requiredRoles.length) { + return true; + } + + return ( + currentUser && + currentUser.roles && + requiredRoles.some((role) => currentUser.roles.includes(role)) + ); + }; +}; + +const onlyUnauthenticatedCondition: ConditionFunc = ({ currentUser }) => !currentUser; + +const allowAllCondition: ConditionFunc = () => true; + +export const onlyAuthenticated: BasicConditionProps = { + condition: onlyAuthenticatedCondition(), + redirectTo: routes.LOGIN, +}; + +export const onlyUnauthenticated: BasicConditionProps = { + condition: onlyUnauthenticatedCondition, + redirectTo: routes.HOME, +}; + +export const allowAll: BasicConditionProps = { + condition: allowAllCondition, + redirectTo: routes.HOME, +}; diff --git a/packages/client/src/routes/index.tsx b/packages/client/src/routes/index.tsx index bf42e70d..445187d1 100644 --- a/packages/client/src/routes/index.tsx +++ b/packages/client/src/routes/index.tsx @@ -3,10 +3,8 @@ import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { GlobalLayout } from '../elements'; import { useCurrentUser } from '../hooks'; -import { AuthProvider } from '../providers/api'; -import PrivateRoute from './PrivateRoute'; -import PublicRoute from './PublicRoute'; -import { authorizedRoutes, unauthorizedRoutes } from './routesConfig'; +import ConditionRoute from './ConditionRoute'; +import { routesArray } from './routesConfig'; const NotFoundPage = () => { const [currentUser] = useCurrentUser(); @@ -16,17 +14,12 @@ const NotFoundPage = () => { const Routes = () => ( - - - {unauthorizedRoutes.map((routeProps) => ( - - ))} - {authorizedRoutes.map((routeProps) => ( - - ))} - - - + + {routesArray.map((props) => ( + + ))} + + ); diff --git a/packages/client/src/routes/routesConfig.ts b/packages/client/src/routes/routesConfig.ts index 0e2c9df5..2faa8a7e 100644 --- a/packages/client/src/routes/routesConfig.ts +++ b/packages/client/src/routes/routesConfig.ts @@ -1,21 +1,30 @@ -import { RouteProps as RouterRouteProps } from 'react-router'; - import { routes } from '../constants'; import { Dashboard, Login, Registration } from '../containers'; +import { Role } from '../enums/role.enum'; +import { BasicRouteProps, ConditionRouteProps } from './ConditionRoute/Condition.types'; +import { + allowAll, + onlyAuthenticated, + onlyAuthenticatedCondition, + onlyUnauthenticated, +} from './conditions'; -interface RouteProps extends RouterRouteProps { - path: string; +interface OnlyAuthenticatedRouteProps extends BasicRouteProps { + roles?: [Role]; } - -export const authorizedRoutes = [ +const onlyAuthenticatedRoutes: Array = (>[ { path: routes.HOME, component: Dashboard, exact: true, }, -]; +]).map(({ roles, ...props }) => ({ + ...props, + ...onlyAuthenticated, + condition: onlyAuthenticatedCondition(roles), +})); -export const unauthorizedRoutes: Array = [ +const onlyUnauthenticatedRoutes: Array = (>[ { path: routes.LOGIN, component: Login, @@ -24,4 +33,15 @@ export const unauthorizedRoutes: Array = [ path: routes.REGISTRATION, component: Registration, }, +]).map((props) => ({ ...props, ...onlyUnauthenticated })); + +const allowAllRoutes: Array = (>[{}]).map((props) => ({ + ...props, + ...allowAll, +})); + +export const routesArray: Array = [ + ...onlyAuthenticatedRoutes, + ...onlyUnauthenticatedRoutes, + ...allowAllRoutes, ];