diff --git a/src/App.tsx b/src/App.tsx
index 0171cc4..7590a33 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,6 +1,14 @@
import React from 'react';
import { Router, Switch } from 'react-router-dom';
+
+/**
+ * Types
+ */
import { UserRoleEnum } from 'enums';
+
+/**
+ * Hooks
+ */
import { useRootStore } from 'hooks/useRootStore';
/**
@@ -32,7 +40,12 @@ const App = () => {
-
+
diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx
index b59a9bc..2f4a90b 100644
--- a/src/components/Layout/Layout.tsx
+++ b/src/components/Layout/Layout.tsx
@@ -1,21 +1,25 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import { observer } from 'mobx-react';
import { useErrorHandler } from 'react-error-boundary';
import { Flex, Box } from '@chakra-ui/react';
import { useRootStore } from 'hooks/useRootStore';
import Header from 'components/Header';
import NavDesktop from 'components/Nav/Desktop';
+import { UserRoleEnum } from 'enums';
+import { userContainRoles } from 'helpers';
+import { Redirect } from 'react-router';
+
+type LayoutPropsType = { roles: UserRoleEnum[] | undefined };
/**
- *
* Layout component
* Сomponent for rendering the current page.
*/
-const Layout: React.FC = ({ children }) => {
+const Layout: React.FC = ({ children, roles }) => {
const { userStore } = useRootStore();
const handleError = useErrorHandler();
- React.useEffect(() => {
+ useEffect(() => {
userStore.loadCurrentUserData().catch(error => handleError(error));
}, [handleError, userStore]);
@@ -42,7 +46,28 @@ const Layout: React.FC = ({ children }) => {
},
}}
>
- {children}
+ {(() => {
+ /**
+ * Check user roles if defined "roles" prop in
+ * PrivateRoute component and return page
+ */
+ if (roles) {
+ const { me } = userStore;
+ if (me === null) return null;
+
+ /**
+ * The user does not have the required roles
+ */
+ if (!userContainRoles(me.roles, roles)) return ;
+
+ return children;
+ } else {
+ /**
+ * Return children if "roles" prop is not defined
+ */
+ return children;
+ }
+ })()}
>
diff --git a/src/components/Nav/Mobile/index.tsx b/src/components/Nav/Mobile/index.tsx
index 8842e7e..74c657d 100644
--- a/src/components/Nav/Mobile/index.tsx
+++ b/src/components/Nav/Mobile/index.tsx
@@ -3,7 +3,6 @@ import { observer } from 'mobx-react';
import { MdExitToApp } from 'react-icons/md';
import { Link } from 'react-router-dom';
import { useRootStore } from 'hooks/useRootStore';
-
import { Avatar } from '@chakra-ui/avatar';
import { Flex, Box, Text } from '@chakra-ui/layout';
import { Drawer, DrawerOverlay, DrawerContent, DrawerBody } from '@chakra-ui/modal';
@@ -31,31 +30,37 @@ const NavMobile: React.FC = () => {
{userStore.me ? (
-
-
-
-
-
- {`${userStore.me.first_name} ${userStore.me.last_name}`}
-
-
- View profile
-
+
+
+
+
+
+
+ {`${userStore.me.first_name} ${userStore.me.last_name}`}
+
+
+ View profile
+
+
+ }
+ />
- }
- />
-
+
) : (
= ({ link, onClickLink }) => {
- const { uiStore } = useRootStore();
+ const { uiStore, userStore } = useRootStore();
const { colorMode } = useColorMode();
-
const onClick = () => onClickLink(link);
+ if (!link.public && link.roles !== undefined) {
+ const { me } = userStore;
+ if (me === null) return null;
+ if (!userContainRoles(me.roles, link.roles)) return null;
+ }
+
return (
= ({ links, onCloseDrawer }) => {
- const { userStore } = useRootStore();
const history = useHistory();
const onClickLink = useCallback(
(link: LinkType) => {
@@ -30,16 +26,9 @@ const NavList: React.FC = ({ links, onCloseDrawer }) => {
return (
<>
- {links.map(link => {
- if (link.public) {
- return ;
- } else {
- if (userStore.me && userHasRoles(userStore.me.roles, [UserRoleEnum.ADMIN])) {
- return ;
- }
- }
- return null;
- })}
+ {links.map(link => (
+
+ ))}
>
);
};
diff --git a/src/components/PrivateRoute/PrivateRoute.tsx b/src/components/PrivateRoute/PrivateRoute.tsx
index fc69a52..644f604 100644
--- a/src/components/PrivateRoute/PrivateRoute.tsx
+++ b/src/components/PrivateRoute/PrivateRoute.tsx
@@ -21,13 +21,13 @@ const PrivateRoute: React.FC = ({ component: Component, titl
render={props =>
authStore.isLoggedIn ? (
-
+
{title} • {config.metaData.appName}{' '}
-
+
) : (
diff --git a/src/config.ts b/src/config.ts
index 5793c58..412d1c1 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1,3 +1,4 @@
+import { UserRoleEnum } from 'enums';
import { MdAccountCircle, MdBuild, MdCollectionsBookmark, MdHome, MdMessage } from 'react-icons/md';
import { IAppConfig } from './interfaces';
@@ -7,11 +8,17 @@ const config: IAppConfig = {
appDescription: 'Learn management system',
},
links: [
- { location: '/', title: 'Home', public: true, icon: MdHome },
+ { location: '/', title: 'Main', public: true, icon: MdHome },
{ location: '/courses', title: 'Courses', public: true, icon: MdCollectionsBookmark },
{ location: '/messages', title: 'Messages', public: true, icon: MdMessage },
{ location: '/profile', title: 'Profile', public: true, icon: MdAccountCircle },
- { location: '/control', title: 'Control', public: false, icon: MdBuild },
+ {
+ location: '/control',
+ title: 'Control',
+ public: false,
+ roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER],
+ icon: MdBuild,
+ },
],
};
diff --git a/src/helpers/index.ts b/src/helpers/index.ts
index 80415cc..4bf4419 100644
--- a/src/helpers/index.ts
+++ b/src/helpers/index.ts
@@ -13,6 +13,17 @@ export function userHasRoles(userRoles: IUserRole[], roles: UserRoleEnum[]): boo
return true;
}
+/**
+ * Return true if user roles contain any one
+ * of roles in second param
+ *
+ * @param userRoles Current user roles
+ * @param roles Roles to check
+ */
+export function userContainRoles(userRoles: IUserRole[], roles: UserRoleEnum[]): boolean {
+ return userRoles.map(r => r.slug).some(r => roles.includes(r));
+}
+
/**
* Clean object values
*
diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts
index 2824c18..b7cb630 100644
--- a/src/interfaces/index.ts
+++ b/src/interfaces/index.ts
@@ -119,12 +119,14 @@ export interface IUserResult extends IDataResult {
export interface IPageProps extends RouteComponentProps {
title?: string;
- roles?: UserRoleEnum[];
}
export interface IPrivateRouteProps extends RouteProps {
component: React.FC;
title?: string;
+ /**
+ * This field means who can access the page
+ */
roles?: UserRoleEnum[];
}
diff --git a/src/pages/404/index.tsx b/src/pages/404/index.tsx
index 0f7f037..18d5174 100644
--- a/src/pages/404/index.tsx
+++ b/src/pages/404/index.tsx
@@ -1,7 +1,8 @@
import React from 'react';
import Helmet from 'react-helmet';
import { MdError } from 'react-icons/md';
-import { Flex, Box, Heading, Text, useToast } from '@chakra-ui/react';
+import { Flex, Box, Heading, Text, useToast, Link } from '@chakra-ui/react';
+import { Link as ReactRouterLink } from 'react-router-dom';
const NotFoundPage: React.FC = () => {
const toast = useToast();
@@ -23,6 +24,9 @@ const NotFoundPage: React.FC = () => {
Page not found
Sorry the page you are looking for could not be found.
+
+ Return to main page
+
>
);
diff --git a/src/pages/control/index.tsx b/src/pages/control/index.tsx
index 1a4177b..a83446a 100644
--- a/src/pages/control/index.tsx
+++ b/src/pages/control/index.tsx
@@ -3,20 +3,11 @@ import { Box, Heading, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Grid } from '
import { IPageProps } from 'interfaces';
import { observer } from 'mobx-react';
-import { useRootStore } from 'hooks/useRootStore';
-import { userHasRoles } from 'helpers';
-import { Redirect } from 'react-router-dom';
/**
* Control page
*/
-const ControlPage: React.FC = ({ title, roles }) => {
- const { userStore } = useRootStore();
-
- if (userStore.me && roles && !userHasRoles(userStore.me.roles, roles)) {
- return ;
- }
-
+const ControlPage: React.FC = ({ title }) => {
return (
<>
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
index a326da0..996fd27 100644
--- a/src/types/index.d.ts
+++ b/src/types/index.d.ts
@@ -1,9 +1,15 @@
+import { UserRoleEnum } from 'enums';
import { IconType } from 'react-icons/lib';
export type LinkType = {
location: string;
title: string;
public: boolean;
+ /**
+ * Render link if user roles includes roles
+ * in config
+ */
+ roles?: UserRoleEnum[];
icon: IconType;
};