Skip to content

Commit

Permalink
feat(client): add ConditionRoute
Browse files Browse the repository at this point in the history
  • Loading branch information
Mefjus committed Mar 14, 2021
1 parent c5c763c commit e6a29e5
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 80 deletions.
4 changes: 2 additions & 2 deletions packages/client/src/interfaces/lazyLoading.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export type LazyLoadingState = 'loading' | 'error' | 'data';
export interface LazyLoading<T> {
loading: boolean;
state: LazyLoadingState;
data: T;
error: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ interface AuthProviderProps {
export interface AuthProps {
login: (user: LoginData) => Promise<string | boolean>;
register: (user: RegistrationData) => Promise<string | boolean>;
checkAuth: () => Promise<boolean>;
checkAuth: () => Promise<User | undefined>;
logout: () => void;
}
14 changes: 5 additions & 9 deletions packages/client/src/providers/api/AuthProvider/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -21,28 +21,24 @@ const AuthProvider = ({ children }: AuthProviderProps) => {

const checkAuth = useCallback(async () => {
if (currentUser) {
return true;
return currentUser;
}
if (expiration) {
return axios
.get<never, AxiosResponse<LoginUserInfo>>('/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 {
Expand Down
16 changes: 8 additions & 8 deletions packages/client/src/providers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ interface ProvidersProps {
}

const Providers = ({ children }: ProvidersProps) => (
<StylesProvider>
<UserProvider>
<AxiosProvider>
<AuthProvider>
<UserProvider>
<AxiosProvider>
<AuthProvider>
<StylesProvider>
<I18nextProvider i18n={i18n}>
<SnackbarProvider>{children}</SnackbarProvider>
</I18nextProvider>
</AuthProvider>
</AxiosProvider>
</UserProvider>
</StylesProvider>
</StylesProvider>
</AuthProvider>
</AxiosProvider>
</UserProvider>
);

export default Providers;
20 changes: 20 additions & 0 deletions packages/client/src/routes/ConditionRoute/Condition.types.d.ts
Original file line number Diff line number Diff line change
@@ -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 {}
47 changes: 47 additions & 0 deletions packages/client/src/routes/ConditionRoute/index.tsx
Original file line number Diff line number Diff line change
@@ -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<LazyLoading<boolean>>({
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 <PageLoader />;
case state === 'data' && data:
return <Route {...props} />;
default:
return <Redirect to={{ pathname: redirectTo }} />;
}
};

export default ConditionRoute;
18 changes: 0 additions & 18 deletions packages/client/src/routes/PrivateRoute.tsx

This file was deleted.

18 changes: 0 additions & 18 deletions packages/client/src/routes/PublicRoute.tsx

This file was deleted.

43 changes: 43 additions & 0 deletions packages/client/src/routes/conditions.ts
Original file line number Diff line number Diff line change
@@ -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,
};
23 changes: 8 additions & 15 deletions packages/client/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -16,17 +14,12 @@ const NotFoundPage = () => {
const Routes = () => (
<BrowserRouter>
<GlobalLayout>
<AuthProvider>
<Switch>
{unauthorizedRoutes.map((routeProps) => (
<PublicRoute key={routeProps.path} {...routeProps} />
))}
{authorizedRoutes.map((routeProps) => (
<PrivateRoute key={routeProps.path} {...routeProps} />
))}
<Route component={NotFoundPage} />
</Switch>
</AuthProvider>
<Switch>
{routesArray.map((props) => (
<ConditionRoute key={props.path} {...props} />
))}
<Route component={NotFoundPage} />
</Switch>
</GlobalLayout>
</BrowserRouter>
);
Expand Down
36 changes: 28 additions & 8 deletions packages/client/src/routes/routesConfig.ts
Original file line number Diff line number Diff line change
@@ -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<ConditionRouteProps> = (<Array<OnlyAuthenticatedRouteProps>>[
{
path: routes.HOME,
component: Dashboard,
exact: true,
},
];
]).map(({ roles, ...props }) => ({
...props,
...onlyAuthenticated,
condition: onlyAuthenticatedCondition(roles),
}));

export const unauthorizedRoutes: Array<RouteProps> = [
const onlyUnauthenticatedRoutes: Array<ConditionRouteProps> = (<Array<BasicRouteProps>>[
{
path: routes.LOGIN,
component: Login,
Expand All @@ -24,4 +33,15 @@ export const unauthorizedRoutes: Array<RouteProps> = [
path: routes.REGISTRATION,
component: Registration,
},
]).map((props) => ({ ...props, ...onlyUnauthenticated }));

const allowAllRoutes: Array<ConditionRouteProps> = (<Array<BasicRouteProps>>[{}]).map((props) => ({
...props,
...allowAll,
}));

export const routesArray: Array<ConditionRouteProps> = [
...onlyAuthenticatedRoutes,
...onlyUnauthenticatedRoutes,
...allowAllRoutes,
];

0 comments on commit e6a29e5

Please sign in to comment.