mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 18:29:15 +03:00
refactor: added routes config & refactor layout
This commit is contained in:
parent
27e1cdbfa5
commit
c92163accb
29
package-lock.json
generated
29
package-lock.json
generated
@ -984,17 +984,36 @@
|
|||||||
"integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
|
"integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
|
||||||
},
|
},
|
||||||
"@emotion/react": {
|
"@emotion/react": {
|
||||||
"version": "11.4.1",
|
"version": "11.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.5.0.tgz",
|
||||||
"integrity": "sha512-pRegcsuGYj4FCdZN6j5vqCALkNytdrKw3TZMekTzNXixRg4wkLsU5QEaBG5LC6l01Vppxlp7FE3aTHpIG5phLg==",
|
"integrity": "sha512-MYq/bzp3rYbee4EMBORCn4duPQfgpiEB5XzrZEBnUZAL80Qdfr7CEv/T80jwaTl/dnZmt9SnTa8NkTrwFNpLlw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.13.10",
|
"@babel/runtime": "^7.13.10",
|
||||||
"@emotion/cache": "^11.4.0",
|
"@emotion/cache": "^11.5.0",
|
||||||
"@emotion/serialize": "^1.0.2",
|
"@emotion/serialize": "^1.0.2",
|
||||||
"@emotion/sheet": "^1.0.2",
|
"@emotion/sheet": "^1.0.3",
|
||||||
"@emotion/utils": "^1.0.0",
|
"@emotion/utils": "^1.0.0",
|
||||||
"@emotion/weak-memoize": "^0.2.5",
|
"@emotion/weak-memoize": "^0.2.5",
|
||||||
"hoist-non-react-statics": "^3.3.1"
|
"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": {
|
"@emotion/serialize": {
|
||||||
|
|||||||
@ -9,10 +9,10 @@
|
|||||||
"lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\""
|
"lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/icons": "^1.0.15",
|
"@chakra-ui/icons": "^1.0.16",
|
||||||
"@chakra-ui/react": "^1.0.0",
|
"@chakra-ui/react": "^1.6.10",
|
||||||
"@emotion/react": "^11.0.0",
|
"@emotion/react": "^11.5.0",
|
||||||
"@emotion/styled": "^11.0.0",
|
"@emotion/styled": "^11.3.0",
|
||||||
"@hookform/resolvers": "^2.8.0",
|
"@hookform/resolvers": "^2.8.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"framer-motion": "^4.0.0",
|
"framer-motion": "^4.0.0",
|
||||||
|
|||||||
44
src/App.tsx
44
src/App.tsx
@ -1,10 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Router, Switch } from 'react-router-dom';
|
import { Router, Switch } from 'react-router-dom';
|
||||||
|
import { routes } from './routes.config';
|
||||||
/**
|
|
||||||
* Types
|
|
||||||
*/
|
|
||||||
import { UserRoleEnum } from '@educt/enums';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
@ -17,20 +13,6 @@ import { useRootStore } from '@educt/hooks/useRootStore';
|
|||||||
import PrivateRoute from '@educt/components/PrivateRoute';
|
import PrivateRoute from '@educt/components/PrivateRoute';
|
||||||
import PublicRoute from '@educt/components/PublicRoute';
|
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 App = () => {
|
||||||
const {
|
const {
|
||||||
uiStore: { history },
|
uiStore: { history },
|
||||||
@ -39,26 +21,10 @@ const App = () => {
|
|||||||
return (
|
return (
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<PrivateRoute exact path='/' component={MainPage} title='Home' />
|
{routes.map((routeProps, i) => {
|
||||||
<PrivateRoute exact path='/courses' component={CoursesPage} title='Courses' />
|
const { isPrivate, ...props } = routeProps;
|
||||||
<PrivateRoute
|
return isPrivate ? <PrivateRoute key={i} {...props} /> : <PublicRoute key={i} {...props} />;
|
||||||
path='/courses/create'
|
})}
|
||||||
component={CreateCoursePage}
|
|
||||||
title='Create new course'
|
|
||||||
roles={[UserRoleEnum.ADMIN, UserRoleEnum.TEACHER]}
|
|
||||||
/>
|
|
||||||
<PrivateRoute path='/messages' component={MessagesPage} title='Messages' />
|
|
||||||
<PrivateRoute exact path='/profile' component={ProfilePage} title='Profile' />
|
|
||||||
<PrivateRoute path='/profile/change-password' component={ChangePasswordPage} title='Change Password' />
|
|
||||||
<PrivateRoute path='/profile/change-email' component={ChangeEmailPage} title='Change Email' />
|
|
||||||
<PrivateRoute
|
|
||||||
path='/users'
|
|
||||||
component={UsersPage}
|
|
||||||
title='Users Management'
|
|
||||||
roles={[UserRoleEnum.ADMIN, UserRoleEnum.TEACHER]}
|
|
||||||
/>
|
|
||||||
<PublicRoute path='/auth' component={AuthPage} />
|
|
||||||
<PublicRoute component={NotFoundPage} />
|
|
||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -50,8 +50,8 @@ const Nav: React.FC = () => {
|
|||||||
<NavList links={config.links} />
|
<NavList links={config.links} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box textAlign='center' mt='auto' mb='5'>
|
<Box textAlign='center' mt='auto' mb='5'>
|
||||||
<Text fontSize='xs' color='gray.500' lineHeight='1.5'>
|
<Text fontSize='xs' color='gray.500' lineHeight='1.6'>
|
||||||
Learning Management System <br /> Made by Sergey Yarkov
|
Learning Management System <br /> Made by Sergey Yarkov <br />v{config.metaData.appVersion}
|
||||||
</Text>
|
</Text>
|
||||||
<Box textAlign='center' mt='2'>
|
<Box textAlign='center' mt='2'>
|
||||||
<IconButton variant='link' aria-label='GitHub repository'>
|
<IconButton variant='link' aria-label='GitHub repository'>
|
||||||
|
|||||||
@ -3,16 +3,13 @@ import React from 'react';
|
|||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { Route, Redirect } from 'react-router-dom';
|
import { Route, Redirect } from 'react-router-dom';
|
||||||
import { RouteProps } from 'react-router';
|
|
||||||
import { IPageProps } from '@educt/interfaces';
|
|
||||||
import { UserRoleEnum } from '@educt/enums';
|
import { UserRoleEnum } from '@educt/enums';
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||||
import Layout from '@educt/components/Layout';
|
import Layout from '@educt/components/Layout';
|
||||||
import ErrorFallback from '@educt/components/ErrorFallback';
|
import ErrorFallback from '@educt/components/ErrorFallback';
|
||||||
|
import { IPublicRouteProps } from '../PublicRoute/PublicRoute';
|
||||||
|
|
||||||
export interface IPrivateRouteProps extends RouteProps {
|
export interface IPrivateRouteProps extends IPublicRouteProps {
|
||||||
component: React.FC<IPageProps>;
|
|
||||||
title?: string;
|
|
||||||
/**
|
/**
|
||||||
* This field means who can access the page
|
* This field means who can access the page
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import ErrorFallback from '@educt/components/ErrorFallback';
|
|||||||
|
|
||||||
export interface IPublicRouteProps extends RouteProps {
|
export interface IPublicRouteProps extends RouteProps {
|
||||||
component: React.FC<IPageProps>;
|
component: React.FC<IPageProps>;
|
||||||
title?: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { UserRoleEnum } from '@educt/enums';
|
import { UserRoleEnum } from '@educt/enums';
|
||||||
import { MdAccountCircle, MdCollectionsBookmark, MdGroup, MdHome, MdMessage } from 'react-icons/md';
|
import { MdAccountCircle, MdCollectionsBookmark, MdGroup, MdHome, MdMessage } from 'react-icons/md';
|
||||||
import { IAppConfig } from './interfaces';
|
import { IAppConfig } from './interfaces';
|
||||||
|
import packageJson from '../package.json';
|
||||||
|
|
||||||
const config: IAppConfig = {
|
const config: IAppConfig = {
|
||||||
/**
|
/**
|
||||||
@ -8,7 +9,8 @@ const config: IAppConfig = {
|
|||||||
*/
|
*/
|
||||||
metaData: {
|
metaData: {
|
||||||
appName: 'Educt',
|
appName: 'Educt',
|
||||||
appDescription: 'Learn management system',
|
appDescription: 'Learning management system',
|
||||||
|
appVersion: packageJson.version,
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Links that are rendered in the navigation
|
* Links that are rendered in the navigation
|
||||||
|
|||||||
@ -6,6 +6,7 @@ export interface IAppConfig {
|
|||||||
metaData: {
|
metaData: {
|
||||||
appName: string;
|
appName: string;
|
||||||
appDescription: string;
|
appDescription: string;
|
||||||
|
appVersion: string;
|
||||||
};
|
};
|
||||||
links: LinkType[];
|
links: LinkType[];
|
||||||
}
|
}
|
||||||
@ -127,10 +128,5 @@ export interface IApiRespose<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IPageProps extends RouteComponentProps {
|
export interface IPageProps extends RouteComponentProps {
|
||||||
title?: string;
|
title: string;
|
||||||
}
|
|
||||||
|
|
||||||
export interface IWindowDimensions {
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Helmet from 'react-helmet';
|
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 { 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<IPageProps> = ({ title }) => {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -14,7 +19,7 @@ const NotFoundPage: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>Error 404</title>
|
<title>{title}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<Box textAlign='center' mt={40}>
|
<Box textAlign='center' mt={40}>
|
||||||
<Flex justifyContent='center'>
|
<Flex justifyContent='center'>
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import AuthForm from './components/AuthForm';
|
|||||||
/**
|
/**
|
||||||
* Auth page
|
* Auth page
|
||||||
*/
|
*/
|
||||||
const AuthPage: React.FC<IPageProps> = () => {
|
const AuthPage: React.FC<IPageProps> = ({ title }) => {
|
||||||
const { authStore } = useRootStore();
|
const { authStore } = useRootStore();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,7 +24,7 @@ const AuthPage: React.FC<IPageProps> = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>Auth</title>
|
<title>{title}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<Flex minHeight='100vh' align='center' justifyContent='center' padding='5'>
|
<Flex minHeight='100vh' align='center' justifyContent='center' padding='5'>
|
||||||
<AuthForm />
|
<AuthForm />
|
||||||
|
|||||||
@ -44,7 +44,7 @@ const CourseList: React.FC<CourseListPropsType> = ({ courses, isLoading }) => {
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch handler
|
* Fetch courses handler
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (me !== null) {
|
if (me !== null) {
|
||||||
@ -53,7 +53,7 @@ const CourseList: React.FC<CourseListPropsType> = ({ courses, isLoading }) => {
|
|||||||
status: me.isAdmin || me.isTeacher ? courseStatus : CourseStatusEnum.PUBLISHED,
|
status: me.isAdmin || me.isTeacher ? courseStatus : CourseStatusEnum.PUBLISHED,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [courseStore, selectedCategory, courseStatus, me]);
|
}, [courseStore, selectedCategory, courseStatus]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle delete course
|
* Handle delete course
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import { useHistory } from 'react-router';
|
|||||||
/**
|
/**
|
||||||
* Courses page
|
* Courses page
|
||||||
*/
|
*/
|
||||||
const CoursesPage: React.FC<IPageProps> = ({ title }) => {
|
const CoursesPage: React.FC<IPageProps> = () => {
|
||||||
const {
|
const {
|
||||||
userStore: { me },
|
userStore: { me },
|
||||||
categoryStore,
|
categoryStore,
|
||||||
@ -46,7 +46,7 @@ const CoursesPage: React.FC<IPageProps> = ({ title }) => {
|
|||||||
<Flex alignItems='center' justifyContent='space-between'>
|
<Flex alignItems='center' justifyContent='space-between'>
|
||||||
<Box>
|
<Box>
|
||||||
<Heading as='h1'>Courses</Heading>
|
<Heading as='h1'>Courses</Heading>
|
||||||
<Text mt='2'>List of all available courses.</Text>
|
<Text mt='2'>List of all courses.</Text>
|
||||||
</Box>
|
</Box>
|
||||||
{(me.isAdmin || me.isTeacher) && (
|
{(me.isAdmin || me.isTeacher) && (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -14,6 +14,11 @@ import { IPageProps } from '@educt/interfaces';
|
|||||||
import LoadingPage from '@educt/components/LoadingPage';
|
import LoadingPage from '@educt/components/LoadingPage';
|
||||||
import UpdateEmailForm from './components/UpdateEmailForm';
|
import UpdateEmailForm from './components/UpdateEmailForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Containers
|
||||||
|
*/
|
||||||
|
import ConfirmEmailContainer from './containers/ConfirmEmailContainer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
@ -21,22 +26,15 @@ import { useHistory } from 'react-router';
|
|||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contexts
|
||||||
|
*/
|
||||||
|
import { ChangeEmailPageContext } from '@educt/contexts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Providers
|
* Providers
|
||||||
*/
|
*/
|
||||||
import { ChangeEmailPageContextProvider } from '@educt/providers';
|
import { ChangeEmailPageContextProvider } from '@educt/providers';
|
||||||
import { ChangeEmailPageContext } from '@educt/contexts';
|
|
||||||
import ConfirmEmailContainer from './containers/ConfirmEmailContainer';
|
|
||||||
|
|
||||||
{
|
|
||||||
/* <>
|
|
||||||
{!isCodeSent ? (
|
|
||||||
<UpdateEmailForm currentEmail={me.email} />
|
|
||||||
) : (
|
|
||||||
confirmEmailData && <ConfirmEmailContainer data={confirmEmailData} />
|
|
||||||
)}
|
|
||||||
</> */
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change email page
|
* Change email page
|
||||||
|
|||||||
@ -1,40 +1,59 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link as ReactRouterLink } from 'react-router-dom';
|
import { Link as ReactRouterLink } from 'react-router-dom';
|
||||||
import { Tag, TagLabel, LinkBox, Heading, LinkOverlay } from '@chakra-ui/react';
|
import { Tag, TagLabel, LinkBox, Heading, LinkOverlay, Icon, Link } from '@chakra-ui/react';
|
||||||
import { Text, Link, Box, Flex } from '@chakra-ui/layout';
|
import { Box, Flex } from '@chakra-ui/layout';
|
||||||
import { MdVideoLibrary, MdGroup, MdThumbUp } from 'react-icons/md';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
*/
|
*/
|
||||||
import { UserCourseType } from '@educt/interfaces';
|
import { UserCourseType } from '@educt/interfaces';
|
||||||
|
import { TimeIcon } from '@chakra-ui/icons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
import { useColorMode } from '@chakra-ui/react';
|
||||||
|
|
||||||
type CourseItemPropsType = {
|
type CourseItemPropsType = {
|
||||||
course: UserCourseType;
|
course: UserCourseType;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CourseItem: React.FC<CourseItemPropsType> = ({ course }) => {
|
const CourseItem: React.FC<CourseItemPropsType> = ({ course }) => {
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
return (
|
return (
|
||||||
|
<Box>
|
||||||
<LinkBox as='div' p='5' borderWidth='1px' rounded='lg'>
|
<LinkBox as='div' p='5' borderWidth='1px' rounded='lg'>
|
||||||
|
<Flex mb='4' justifyContent='space-between'>
|
||||||
|
<Box>
|
||||||
|
<Tag variant='outline' borderRadius='full' alignSelf='flex-start'>
|
||||||
|
<TagLabel>{course.lessons_count} lessons</TagLabel>
|
||||||
|
</Tag>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Tag variant='subtle' colorScheme='green' borderRadius='full' alignSelf='flex-start'>
|
||||||
|
<TagLabel>{course.category.title}</TagLabel>
|
||||||
|
</Tag>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
<Heading size='md' my='2'>
|
<Heading size='md' my='2'>
|
||||||
<LinkOverlay href='#'>{course.title}</LinkOverlay>
|
<LinkOverlay as={ReactRouterLink} to={`/course/${course.id}`} overflowWrap='anywhere'>
|
||||||
|
{course.title}
|
||||||
|
</LinkOverlay>
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text>{course.description}</Text>
|
<Flex mt='6' alignItems='center'>
|
||||||
<Flex mt='2' alignItems='center'>
|
<Icon as={TimeIcon} w='14px' h='14px' />
|
||||||
<MdVideoLibrary />
|
<Box
|
||||||
<Text pl='1' pr='4'>
|
as='time'
|
||||||
{course.lessons_count} lessons
|
color={colorMode === 'dark' ? 'gray.400' : 'gray.600'}
|
||||||
</Text>
|
ml='2'
|
||||||
<MdGroup />
|
verticalAlign='middle'
|
||||||
<Text pl='1' pr='4'>
|
dateTime={course.created_at}
|
||||||
{course.students_count} students
|
>
|
||||||
</Text>
|
Last update {new Date(course.created_at).toLocaleDateString()}
|
||||||
<MdThumbUp />
|
</Box>
|
||||||
<Text pl='1' pr='4'>
|
|
||||||
{course.likes_count} likes
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</LinkBox>
|
</LinkBox>
|
||||||
|
</Box>
|
||||||
|
|
||||||
// <Flex flexDir='column' borderWidth='1px' borderRadius='lg' p='10px' boxShadow='sm' mb='3'>
|
// <Flex flexDir='column' borderWidth='1px' borderRadius='lg' p='10px' boxShadow='sm' mb='3'>
|
||||||
// <Tag colorScheme='purple' borderRadius='full' alignSelf='flex-start'>
|
// <Tag colorScheme='purple' borderRadius='full' alignSelf='flex-start'>
|
||||||
|
|||||||
@ -29,11 +29,11 @@ const CourseList: React.FC<CourseListPropsType> = ({ courses }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir='column'>
|
<Stack>
|
||||||
{courses.map(course => (
|
{courses.map(course => (
|
||||||
<CourseItem course={course} key={course.id} />
|
<CourseItem course={course} key={course.id} />
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -90,7 +90,7 @@ const UpdateUserContactsForm: React.FC<UpdateUserContactsFormPropsType> = ({ con
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Box boxShadow='sm' borderRadius='lg' borderWidth='1px' padding='20px'>
|
<Box boxShadow='sm' borderRadius='lg' borderWidth='1px' padding='5'>
|
||||||
<Stack spacing='5px'>
|
<Stack spacing='5px'>
|
||||||
<FormControl id='phone_number'>
|
<FormControl id='phone_number'>
|
||||||
<FormHelperText color='gray.500'>Phone number with country code</FormHelperText>
|
<FormHelperText color='gray.500'>Phone number with country code</FormHelperText>
|
||||||
@ -106,6 +106,7 @@ const UpdateUserContactsForm: React.FC<UpdateUserContactsFormPropsType> = ({ con
|
|||||||
{errors.phone_number?.message}
|
{errors.phone_number?.message}
|
||||||
</Text>
|
</Text>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl id='twitter_id'>
|
<FormControl id='twitter_id'>
|
||||||
<FormHelperText color='gray.500'>Twitter</FormHelperText>
|
<FormHelperText color='gray.500'>Twitter</FormHelperText>
|
||||||
<Input
|
<Input
|
||||||
@ -120,6 +121,7 @@ const UpdateUserContactsForm: React.FC<UpdateUserContactsFormPropsType> = ({ con
|
|||||||
{errors.twitter_id?.message}
|
{errors.twitter_id?.message}
|
||||||
</Text>
|
</Text>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl id='telegram'>
|
<FormControl id='telegram'>
|
||||||
<FormHelperText color='gray.500'>Telegram</FormHelperText>
|
<FormHelperText color='gray.500'>Telegram</FormHelperText>
|
||||||
<Input
|
<Input
|
||||||
@ -134,6 +136,7 @@ const UpdateUserContactsForm: React.FC<UpdateUserContactsFormPropsType> = ({ con
|
|||||||
{errors.telegram_id?.message}
|
{errors.telegram_id?.message}
|
||||||
</Text>
|
</Text>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl id='vk'>
|
<FormControl id='vk'>
|
||||||
<FormHelperText color='gray.500'>VKontakte</FormHelperText>
|
<FormHelperText color='gray.500'>VKontakte</FormHelperText>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@ -1,7 +1,20 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Avatar, Tabs, TabList, Tab, TabPanels, TabPanel, Button } from '@chakra-ui/react';
|
import {
|
||||||
import { Box, Flex, Heading, Text, Stack, StackDivider } from '@chakra-ui/layout';
|
Avatar,
|
||||||
|
Tabs,
|
||||||
|
TabList,
|
||||||
|
Tab,
|
||||||
|
TabPanels,
|
||||||
|
TabPanel,
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
FormHelperText,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
InputRightElement,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { Box, Flex, Heading, Text, Stack } from '@chakra-ui/layout';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
@ -69,52 +82,50 @@ const ProfilePage: React.FC<IPageProps> = () => {
|
|||||||
</TabList>
|
</TabList>
|
||||||
<TabPanels mt='2'>
|
<TabPanels mt='2'>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<Box borderRadius='lg' borderWidth='1px' padding='20px' boxShadow='sm'>
|
<form>
|
||||||
<Stack spacing='10px' divider={<StackDivider />}>
|
<Box boxShadow='sm' borderRadius='lg' borderWidth='1px' padding='5'>
|
||||||
<Flex>
|
<Stack spacing='5px'>
|
||||||
<Box>
|
<FormControl isDisabled>
|
||||||
<Text as='small' color='gray.500'>
|
<FormHelperText color='gray.500'>First name</FormHelperText>
|
||||||
First Name
|
<Input type='text' value={me.first_name} size='md' variant='flushed' />
|
||||||
</Text>
|
</FormControl>
|
||||||
<Text fontWeight='medium' fontSize='lg'>
|
|
||||||
{me.first_name}
|
<FormControl isDisabled>
|
||||||
</Text>
|
<FormHelperText color='gray.500'>Last name</FormHelperText>
|
||||||
</Box>
|
<Input type='text' value={me.last_name} size='md' variant='flushed' />
|
||||||
</Flex>
|
</FormControl>
|
||||||
<Flex>
|
|
||||||
<Box>
|
<FormControl isDisabled>
|
||||||
<Text as='small' color='gray.500'>
|
<FormHelperText color='gray.500'>Email</FormHelperText>
|
||||||
Last Name
|
<InputGroup>
|
||||||
</Text>
|
<Input type='text' value={me.email} size='md' variant='flushed' />
|
||||||
<Text fontWeight='medium' fontSize='lg'>
|
<InputRightElement width='4rem'>
|
||||||
{me.last_name}
|
<Button size='sm' h='1.90rem' w='full' onClick={() => history.push('/profile/change-email')}>
|
||||||
</Text>
|
Edit
|
||||||
</Box>
|
</Button>
|
||||||
</Flex>
|
</InputRightElement>
|
||||||
<Flex justifyContent='space-between' alignItems='center'>
|
</InputGroup>
|
||||||
<Box>
|
</FormControl>
|
||||||
<Text as='small' color='gray.500'>
|
|
||||||
Email
|
<FormControl isDisabled>
|
||||||
</Text>
|
<FormHelperText color='gray.500'>Password</FormHelperText>
|
||||||
<Text fontWeight='medium' fontSize='lg'>
|
<InputGroup>
|
||||||
{me.email}
|
<Input type='text' value='**********' size='md' variant='flushed' />
|
||||||
</Text>
|
<InputRightElement width='5rem'>
|
||||||
</Box>
|
<Button
|
||||||
<Button onClick={() => history.push('/profile/change-email')}>Edit</Button>
|
size='sm'
|
||||||
</Flex>
|
h='1.90rem'
|
||||||
<Flex justifyContent='space-between' alignItems='center'>
|
w='full'
|
||||||
<Box>
|
onClick={() => history.push('/profile/change-password')}
|
||||||
<Text as='small' color='gray.500'>
|
>
|
||||||
Password
|
Change
|
||||||
</Text>
|
</Button>
|
||||||
<Text fontWeight='medium' fontSize='lg'>
|
</InputRightElement>
|
||||||
**********
|
</InputGroup>
|
||||||
</Text>
|
</FormControl>
|
||||||
</Box>
|
|
||||||
<Button onClick={() => history.push('/profile/change-password')}>Change</Button>
|
|
||||||
</Flex>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
</form>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<UpdateUserContactsForm contacts={me.contacts} />
|
<UpdateUserContactsForm contacts={me.contacts} />
|
||||||
|
|||||||
87
src/routes.config.ts
Normal file
87
src/routes.config.ts
Normal file
@ -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,
|
||||||
|
},
|
||||||
|
];
|
||||||
@ -1,11 +1,6 @@
|
|||||||
import { makeAutoObservable, observable, runInAction } from 'mobx';
|
import { makeAutoObservable } from 'mobx';
|
||||||
import { createBrowserHistory } from 'history';
|
import { createBrowserHistory } from 'history';
|
||||||
|
|
||||||
/**
|
|
||||||
* Types
|
|
||||||
*/
|
|
||||||
import { IWindowDimensions } from '@educt/interfaces';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores
|
* Stores
|
||||||
*/
|
*/
|
||||||
@ -18,15 +13,11 @@ export default class UIStore {
|
|||||||
|
|
||||||
public history: ReturnType<typeof createBrowserHistory>;
|
public history: ReturnType<typeof createBrowserHistory>;
|
||||||
|
|
||||||
public windowDimensions: IWindowDimensions = { width: window.innerWidth, height: window.innerHeight };
|
|
||||||
|
|
||||||
constructor(root: RootStore) {
|
constructor(root: RootStore) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.history = createBrowserHistory();
|
this.history = createBrowserHistory();
|
||||||
|
|
||||||
makeAutoObservable(this, {
|
makeAutoObservable(this);
|
||||||
windowDimensions: observable.struct,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setupListeners();
|
this.setupListeners();
|
||||||
}
|
}
|
||||||
@ -41,31 +32,10 @@ export default class UIStore {
|
|||||||
this.history.listen(({ pathname }) => {
|
this.history.listen(({ pathname }) => {
|
||||||
this.setLocation(pathname);
|
this.setLocation(pathname);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Window dimensions updated
|
|
||||||
*/
|
|
||||||
window.onresize = () => this.updateWindowDimensions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setLocation(location: string): void {
|
public setLocation(location: string): void {
|
||||||
console.log(`[history]: location changed to "${location}"`);
|
console.log(`[history]: location changed to "${location}"`);
|
||||||
this.location = 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user