diff --git a/package-lock.json b/package-lock.json index f41f558..d1c3347 100644 --- a/package-lock.json +++ b/package-lock.json @@ -984,17 +984,36 @@ "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" }, "@emotion/react": { - "version": "11.4.1", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.4.1.tgz", - "integrity": "sha512-pRegcsuGYj4FCdZN6j5vqCALkNytdrKw3TZMekTzNXixRg4wkLsU5QEaBG5LC6l01Vppxlp7FE3aTHpIG5phLg==", + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.5.0.tgz", + "integrity": "sha512-MYq/bzp3rYbee4EMBORCn4duPQfgpiEB5XzrZEBnUZAL80Qdfr7CEv/T80jwaTl/dnZmt9SnTa8NkTrwFNpLlw==", "requires": { "@babel/runtime": "^7.13.10", - "@emotion/cache": "^11.4.0", + "@emotion/cache": "^11.5.0", "@emotion/serialize": "^1.0.2", - "@emotion/sheet": "^1.0.2", + "@emotion/sheet": "^1.0.3", "@emotion/utils": "^1.0.0", "@emotion/weak-memoize": "^0.2.5", "hoist-non-react-statics": "^3.3.1" + }, + "dependencies": { + "@emotion/cache": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.5.0.tgz", + "integrity": "sha512-mAZ5QRpLriBtaj/k2qyrXwck6yeoz1V5lMt/jfj6igWU35yYlNKs2LziXVgvH81gnJZ+9QQNGelSsnuoAy6uIw==", + "requires": { + "@emotion/memoize": "^0.7.4", + "@emotion/sheet": "^1.0.3", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "stylis": "^4.0.10" + } + }, + "@emotion/sheet": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.0.3.tgz", + "integrity": "sha512-YoX5GyQ4db7LpbmXHMuc8kebtBGP6nZfRC5Z13OKJMixBEwdZrJ914D6yJv/P+ZH/YY3F5s89NYX2hlZAf3SRQ==" + } } }, "@emotion/serialize": { diff --git a/package.json b/package.json index 8e7bbe6..55cb9ad 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,10 @@ "lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"" }, "dependencies": { - "@chakra-ui/icons": "^1.0.15", - "@chakra-ui/react": "^1.0.0", - "@emotion/react": "^11.0.0", - "@emotion/styled": "^11.0.0", + "@chakra-ui/icons": "^1.0.16", + "@chakra-ui/react": "^1.6.10", + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", "@hookform/resolvers": "^2.8.0", "axios": "^0.21.1", "framer-motion": "^4.0.0", diff --git a/src/App.tsx b/src/App.tsx index 237cb83..ec5e270 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,6 @@ import React from 'react'; import { Router, Switch } from 'react-router-dom'; - -/** - * Types - */ -import { UserRoleEnum } from '@educt/enums'; +import { routes } from './routes.config'; /** * Hooks @@ -17,20 +13,6 @@ import { useRootStore } from '@educt/hooks/useRootStore'; import PrivateRoute from '@educt/components/PrivateRoute'; import PublicRoute from '@educt/components/PublicRoute'; -/** - * Pages - */ -import AuthPage from '@educt/pages/auth/auth'; -import NotFoundPage from '@educt/pages/404/404'; -import CreateCoursePage from '@educt/pages/courses/create/create'; -import ChangePasswordPage from '@educt/pages/profile/change-password/change-password'; -import ChangeEmailPage from '@educt/pages/profile/change-email/change-email'; -const CoursesPage = React.lazy(() => import('@educt/pages/courses/courses')); -const MainPage = React.lazy(() => import('@educt/pages/main/main')); -const MessagesPage = React.lazy(() => import('@educt/pages/messages/messages')); -const ProfilePage = React.lazy(() => import('@educt/pages/profile/profile')); -const UsersPage = React.lazy(() => import('@educt/pages/users/users')); - const App = () => { const { uiStore: { history }, @@ -39,26 +21,10 @@ const App = () => { return ( - - - - - - - - - - + {routes.map((routeProps, i) => { + const { isPrivate, ...props } = routeProps; + return isPrivate ? : ; + })} ); diff --git a/src/components/Nav/Desktop/index.tsx b/src/components/Nav/Desktop/index.tsx index 02a9e5c..ec33bb5 100644 --- a/src/components/Nav/Desktop/index.tsx +++ b/src/components/Nav/Desktop/index.tsx @@ -50,8 +50,8 @@ const Nav: React.FC = () => { - - Learning Management System
Made by Sergey Yarkov + + Learning Management System
Made by Sergey Yarkov
v{config.metaData.appVersion}
diff --git a/src/components/PrivateRoute/PrivateRoute.tsx b/src/components/PrivateRoute/PrivateRoute.tsx index b65482e..10b4e67 100644 --- a/src/components/PrivateRoute/PrivateRoute.tsx +++ b/src/components/PrivateRoute/PrivateRoute.tsx @@ -3,16 +3,13 @@ import React from 'react'; import Helmet from 'react-helmet'; import { ErrorBoundary } from 'react-error-boundary'; import { Route, Redirect } from 'react-router-dom'; -import { RouteProps } from 'react-router'; -import { IPageProps } from '@educt/interfaces'; import { UserRoleEnum } from '@educt/enums'; import { useRootStore } from '@educt/hooks/useRootStore'; import Layout from '@educt/components/Layout'; import ErrorFallback from '@educt/components/ErrorFallback'; +import { IPublicRouteProps } from '../PublicRoute/PublicRoute'; -export interface IPrivateRouteProps extends RouteProps { - component: React.FC; - title?: string; +export interface IPrivateRouteProps extends IPublicRouteProps { /** * This field means who can access the page */ diff --git a/src/components/PublicRoute/PublicRoute.tsx b/src/components/PublicRoute/PublicRoute.tsx index fd2b719..d107f61 100644 --- a/src/components/PublicRoute/PublicRoute.tsx +++ b/src/components/PublicRoute/PublicRoute.tsx @@ -7,7 +7,7 @@ import ErrorFallback from '@educt/components/ErrorFallback'; export interface IPublicRouteProps extends RouteProps { component: React.FC; - title?: string; + title: string; } /** diff --git a/src/config.ts b/src/config.ts index e64023c..a0a6f0f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,7 @@ import { UserRoleEnum } from '@educt/enums'; import { MdAccountCircle, MdCollectionsBookmark, MdGroup, MdHome, MdMessage } from 'react-icons/md'; import { IAppConfig } from './interfaces'; +import packageJson from '../package.json'; const config: IAppConfig = { /** @@ -8,7 +9,8 @@ const config: IAppConfig = { */ metaData: { appName: 'Educt', - appDescription: 'Learn management system', + appDescription: 'Learning management system', + appVersion: packageJson.version, }, /** * Links that are rendered in the navigation diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 4757bd3..f431b2f 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -6,6 +6,7 @@ export interface IAppConfig { metaData: { appName: string; appDescription: string; + appVersion: string; }; links: LinkType[]; } @@ -127,10 +128,5 @@ export interface IApiRespose { } export interface IPageProps extends RouteComponentProps { - title?: string; -} - -export interface IWindowDimensions { - width: number; - height: number; + title: string; } diff --git a/src/pages/404/404.tsx b/src/pages/404/404.tsx index 18d5174..32b1a4c 100644 --- a/src/pages/404/404.tsx +++ b/src/pages/404/404.tsx @@ -1,10 +1,15 @@ import React from 'react'; import Helmet from 'react-helmet'; -import { MdError } from 'react-icons/md'; -import { Flex, Box, Heading, Text, useToast, Link } from '@chakra-ui/react'; import { Link as ReactRouterLink } from 'react-router-dom'; +import { Flex, Box, Heading, Text, useToast, Link } from '@chakra-ui/react'; +import { MdError } from 'react-icons/md'; -const NotFoundPage: React.FC = () => { +/** + * Types + */ +import { IPageProps } from '@educt/interfaces'; + +const NotFoundPage: React.FC = ({ title }) => { const toast = useToast(); React.useEffect(() => { @@ -14,7 +19,7 @@ const NotFoundPage: React.FC = () => { return ( <> - Error 404 + {title} diff --git a/src/pages/auth/auth.tsx b/src/pages/auth/auth.tsx index e402a3d..1eb1977 100644 --- a/src/pages/auth/auth.tsx +++ b/src/pages/auth/auth.tsx @@ -11,7 +11,7 @@ import AuthForm from './components/AuthForm'; /** * Auth page */ -const AuthPage: React.FC = () => { +const AuthPage: React.FC = ({ title }) => { const { authStore } = useRootStore(); /** @@ -24,7 +24,7 @@ const AuthPage: React.FC = () => { return ( <> - Auth + {title} diff --git a/src/pages/courses/components/CourseList/CourseList.tsx b/src/pages/courses/components/CourseList/CourseList.tsx index 5fad702..6dd15d3 100644 --- a/src/pages/courses/components/CourseList/CourseList.tsx +++ b/src/pages/courses/components/CourseList/CourseList.tsx @@ -44,7 +44,7 @@ const CourseList: React.FC = ({ courses, isLoading }) => { const toast = useToast(); /** - * Fetch handler + * Fetch courses handler */ useEffect(() => { if (me !== null) { @@ -53,7 +53,7 @@ const CourseList: React.FC = ({ courses, isLoading }) => { status: me.isAdmin || me.isTeacher ? courseStatus : CourseStatusEnum.PUBLISHED, }); } - }, [courseStore, selectedCategory, courseStatus, me]); + }, [courseStore, selectedCategory, courseStatus]); /** * Handle delete course diff --git a/src/pages/courses/courses.tsx b/src/pages/courses/courses.tsx index 8a0066a..24e8c74 100644 --- a/src/pages/courses/courses.tsx +++ b/src/pages/courses/courses.tsx @@ -30,7 +30,7 @@ import { useHistory } from 'react-router'; /** * Courses page */ -const CoursesPage: React.FC = ({ title }) => { +const CoursesPage: React.FC = () => { const { userStore: { me }, categoryStore, @@ -46,7 +46,7 @@ const CoursesPage: React.FC = ({ title }) => { Courses - List of all available courses. + List of all courses. {(me.isAdmin || me.isTeacher) && ( - - - - - Password - - - ********** - - - - - - + + + + + First name + + + + + Last name + + + + + Email + + + + + + + + + + Password + + + + + + + + + + diff --git a/src/routes.config.ts b/src/routes.config.ts new file mode 100644 index 0000000..35ff0bd --- /dev/null +++ b/src/routes.config.ts @@ -0,0 +1,87 @@ +import { lazy } from 'react'; +import { IPrivateRouteProps } from './components/PrivateRoute/PrivateRoute'; + +/** + * Pages + */ +import AuthPage from '@educt/pages/auth/auth'; +import NotFoundPage from '@educt/pages/404/404'; +import CreateCoursePage from '@educt/pages/courses/create/create'; +import ChangePasswordPage from '@educt/pages/profile/change-password/change-password'; +import ChangeEmailPage from '@educt/pages/profile/change-email/change-email'; +import { UserRoleEnum } from './enums'; +const CoursesPage = lazy(() => import('@educt/pages/courses/courses')); +const MainPage = lazy(() => import('@educt/pages/main/main')); +const MessagesPage = lazy(() => import('@educt/pages/messages/messages')); +const ProfilePage = lazy(() => import('@educt/pages/profile/profile')); +const UsersPage = lazy(() => import('@educt/pages/users/users')); + +interface IRouteOption extends IPrivateRouteProps { + isPrivate: boolean; +} + +export const routes: IRouteOption[] = [ + { + path: '/', + title: 'Main', + component: MainPage, + exact: true, + isPrivate: true, + }, + { + path: '/courses', + title: 'Courses', + component: CoursesPage, + exact: true, + isPrivate: true, + }, + { + path: '/courses/create', + title: 'Create new course', + component: CreateCoursePage, + isPrivate: true, + }, + { + path: '/messages', + title: 'Messages', + component: MessagesPage, + isPrivate: true, + }, + { + path: '/profile', + title: 'Profile', + component: ProfilePage, + isPrivate: true, + exact: true, + }, + { + path: '/profile/change-password', + title: 'Change password', + component: ChangePasswordPage, + isPrivate: true, + }, + { + path: '/profile/change-email', + title: 'Change email', + component: ChangeEmailPage, + isPrivate: true, + }, + { + path: '/users', + title: 'Users management', + component: UsersPage, + isPrivate: true, + roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER], + }, + { + path: '/auth', + title: 'Auth', + component: AuthPage, + isPrivate: false, + }, + { + title: 'Page not found', + component: NotFoundPage, + isPrivate: false, + }, +]; diff --git a/src/stores/UIStore.ts b/src/stores/UIStore.ts index 154fb68..9605cdf 100644 --- a/src/stores/UIStore.ts +++ b/src/stores/UIStore.ts @@ -1,11 +1,6 @@ -import { makeAutoObservable, observable, runInAction } from 'mobx'; +import { makeAutoObservable } from 'mobx'; import { createBrowserHistory } from 'history'; -/** - * Types - */ -import { IWindowDimensions } from '@educt/interfaces'; - /** * Stores */ @@ -18,15 +13,11 @@ export default class UIStore { public history: ReturnType; - public windowDimensions: IWindowDimensions = { width: window.innerWidth, height: window.innerHeight }; - constructor(root: RootStore) { this.root = root; this.history = createBrowserHistory(); - makeAutoObservable(this, { - windowDimensions: observable.struct, - }); + makeAutoObservable(this); this.setupListeners(); } @@ -41,31 +32,10 @@ export default class UIStore { this.history.listen(({ pathname }) => { this.setLocation(pathname); }); - - /** - * Window dimensions updated - */ - window.onresize = () => this.updateWindowDimensions(); } public setLocation(location: string): void { console.log(`[history]: location changed to "${location}"`); this.location = location; } - - private updateWindowDimensions(): void { - this.windowDimensions = { width: window.innerWidth, height: window.innerHeight }; - } - - get isDesktop() { - return this.windowDimensions.width > 991; - } - - get isTablet() { - return this.windowDimensions.width < 991; - } - - get isMobile() { - return this.windowDimensions.width < 568; - } }