mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
feat: pages for creating lesson
This commit is contained in:
parent
44e8fa3158
commit
320fe09249
5
package-lock.json
generated
5
package-lock.json
generated
@ -1869,6 +1869,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.2.1.tgz",
|
||||||
"integrity": "sha512-hwURgfmP2apX3HQrB55V9DN47kuN3C6KlQvI5UIfJRibXma72C/JudcNt2r9dWjAdFMrcZoz1ivvtXMCkJ2aQA=="
|
"integrity": "sha512-hwURgfmP2apX3HQrB55V9DN47kuN3C6KlQvI5UIfJRibXma72C/JudcNt2r9dWjAdFMrcZoz1ivvtXMCkJ2aQA=="
|
||||||
},
|
},
|
||||||
|
"moment": {
|
||||||
|
"version": "2.29.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||||
|
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
|
||||||
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
"js-cookie": "^3.0.0",
|
"js-cookie": "^3.0.0",
|
||||||
"mobx": "^6.3.2",
|
"mobx": "^6.3.2",
|
||||||
"mobx-react": "^7.2.0",
|
"mobx-react": "^7.2.0",
|
||||||
|
"moment": "^2.29.1",
|
||||||
"qs": "^6.10.1",
|
"qs": "^6.10.1",
|
||||||
"react": "^17.0.0",
|
"react": "^17.0.0",
|
||||||
"react-dom": "^17.0.0",
|
"react-dom": "^17.0.0",
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { Controller, UseFormReturn } from 'react-hook-form';
|
|||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
*/
|
*/
|
||||||
import { OptionType } from '@educt/types';
|
import type { OptionType } from '@educt/types';
|
||||||
import { UserRoleEnum } from '@educt/enums';
|
import { UserRoleEnum } from '@educt/enums';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,6 +93,7 @@ const CourseForm: React.FC<CourseFormPropsType> = ({ onSubmit, buttonLabel, isLo
|
|||||||
};
|
};
|
||||||
|
|
||||||
const watchTitle = watch('title');
|
const watchTitle = watch('title');
|
||||||
|
const watchDescription = watch('description');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
@ -119,9 +120,14 @@ const CourseForm: React.FC<CourseFormPropsType> = ({ onSubmit, buttonLabel, isLo
|
|||||||
<FormControl isRequired id='description' isInvalid={!!errors.description}>
|
<FormControl isRequired id='description' isInvalid={!!errors.description}>
|
||||||
<FormLabel>Description</FormLabel>
|
<FormLabel>Description</FormLabel>
|
||||||
<Textarea resize='none' minH='150px' placeholder='Course description...' {...register('description')} />
|
<Textarea resize='none' minH='150px' placeholder='Course description...' {...register('description')} />
|
||||||
|
<Flex justifyContent='space-between'>
|
||||||
<Text as='small' color='red.500'>
|
<Text as='small' color='red.500'>
|
||||||
{errors.description?.message}
|
{errors.description?.message}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text as='small' color={!!errors.description || watchDescription?.length > 250 ? 'red.500' : 'gray.500'}>
|
||||||
|
{watchDescription?.length || 0}/250
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<Flex justifyContent='space-between' flexDirection={{ base: 'column', lg: 'row' }}>
|
<Flex justifyContent='space-between' flexDirection={{ base: 'column', lg: 'row' }}>
|
||||||
|
|||||||
@ -5,7 +5,10 @@ const SUPPORTED_FORMATS = ['image/jpg', 'image/jpeg', 'image/png', 'image/webp']
|
|||||||
|
|
||||||
const CreateCourseSchema = yup.object().shape({
|
const CreateCourseSchema = yup.object().shape({
|
||||||
title: yup.string().required('Title field name is required').max(90, 'Title must be at most 90 characters'),
|
title: yup.string().required('Title field name is required').max(90, 'Title must be at most 90 characters'),
|
||||||
description: yup.string().required('Description field name is required'),
|
description: yup
|
||||||
|
.string()
|
||||||
|
.required('Description field name is required')
|
||||||
|
.max(250, 'Description must be at most 250 characters'),
|
||||||
image: yup
|
image: yup
|
||||||
.mixed()
|
.mixed()
|
||||||
.test('fileSize', 'File size is too large', (value: File) => {
|
.test('fileSize', 'File size is too large', (value: File) => {
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
import { ICourse } from '@educt/interfaces';
|
import { ICourse } from '@educt/interfaces';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
56
src/hooks/useFetchLessonQuery.ts
Normal file
56
src/hooks/useFetchLessonQuery.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// /**
|
||||||
|
// * Types
|
||||||
|
// */
|
||||||
|
// import { ILesson } from '@educt/interfaces';
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Hooks
|
||||||
|
// */
|
||||||
|
// import { useEffect, useState } from 'react';
|
||||||
|
// import { useErrorHandler } from 'react-error-boundary';
|
||||||
|
// import useIsMountedRef from './useIsMountedRef';
|
||||||
|
|
||||||
|
// type LessonStateType = {
|
||||||
|
// data: ILesson | null;
|
||||||
|
// error: any;
|
||||||
|
// loading: boolean;
|
||||||
|
// fetched: boolean;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const useFetchLessonQuery = (id: string) => {
|
||||||
|
// const [state, setState] = useState<LessonStateType>({ data: null, error: null, loading: false, fetched: false });
|
||||||
|
// const isMountedRef = useIsMountedRef();
|
||||||
|
// const handleError = useErrorHandler();
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Fetch course handler
|
||||||
|
// */
|
||||||
|
// useEffect(() => {
|
||||||
|
// setState(s => ({ ...s, loading: true }));
|
||||||
|
// lessonService
|
||||||
|
// .fetchById(id)
|
||||||
|
// .then(data => {
|
||||||
|
// if (isMountedRef.current) {
|
||||||
|
// setState(s => ({ ...s, data: data.data }));
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .catch(error => {
|
||||||
|
// if (error.response) {
|
||||||
|
// if (isMountedRef.current) {
|
||||||
|
// setState(s => ({ ...s, error }));
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// handleError(error);
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .finally(() => {
|
||||||
|
// if (isMountedRef.current) {
|
||||||
|
// setState(s => ({ ...s, loading: false, fetched: true }));
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }, [id]);
|
||||||
|
|
||||||
|
// return { ...state };
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export default useFetchLessonQuery;
|
||||||
@ -23,10 +23,10 @@ export interface ICourse {
|
|||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
status: CourseStatusEnum;
|
status: CourseStatusEnum;
|
||||||
teacher: Pick<IUser, 'id' | 'first_name' | 'last_name' | 'email'>;
|
teacher: Pick<IUser, 'id' | 'first_name' | 'fullname' | 'last_name' | 'email'>;
|
||||||
category: ICategory;
|
category: ICategory;
|
||||||
lessons: ILesson[];
|
lessons: ILesson[];
|
||||||
students: Pick<IUser, 'id' | 'first_name' | 'last_name' | 'email'>;
|
students: Array<Pick<IUser, 'id' | 'first_name' | 'fullname' | 'last_name' | 'email'>>;
|
||||||
students_count: string;
|
students_count: string;
|
||||||
likes_count: string;
|
likes_count: string;
|
||||||
lessons_count: string;
|
lessons_count: string;
|
||||||
@ -39,6 +39,8 @@ export interface ILesson {
|
|||||||
course_id: string;
|
course_id: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICategory {
|
export interface ICategory {
|
||||||
|
|||||||
@ -133,7 +133,7 @@ const CourseItem: React.FC<CourseItemPropsType> = ({ course, onDelete, onSetStat
|
|||||||
<Heading as='h2' fontSize='xl' mt='5px'>
|
<Heading as='h2' fontSize='xl' mt='5px'>
|
||||||
{course.title}
|
{course.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text>{course.description}</Text>
|
<Text mt='2'>{course.description}</Text>
|
||||||
<Flex mt='20px' flexWrap='wrap'>
|
<Flex mt='20px' flexWrap='wrap'>
|
||||||
<Flex alignItems='center' mr='1'>
|
<Flex alignItems='center' mr='1'>
|
||||||
<Icon as={MdVideoLibrary} mr='2' />
|
<Icon as={MdVideoLibrary} mr='2' />
|
||||||
|
|||||||
17
src/pages/courses/create-lesson/create-lesson.tsx
Normal file
17
src/pages/courses/create-lesson/create-lesson.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, Heading } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import { IPageProps } from '@educt/interfaces';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Lesson page
|
||||||
|
*/
|
||||||
|
const CreateLessonPage: React.FC<IPageProps> = () => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Heading as='h1'>Create Lesson page</Heading>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateLessonPage;
|
||||||
84
src/pages/courses/edit/components/LessonList/LessonItem.tsx
Normal file
84
src/pages/courses/edit/components/LessonList/LessonItem.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Flex, Box, Text, Icon, Button } from '@chakra-ui/react';
|
||||||
|
import { EditIcon, TimeIcon } from '@chakra-ui/icons';
|
||||||
|
import { MdLibraryBooks, MdPlayCircleOutline } from 'react-icons/md';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
import type { ILesson } from '@educt/interfaces';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import { useColorModeValue } from '@chakra-ui/color-mode';
|
||||||
|
|
||||||
|
export type LessonItemPropsType = {
|
||||||
|
lesson: ILesson;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LessonItem: React.FC<LessonItemPropsType> = ({ lesson }) => {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={lesson.title}
|
||||||
|
p='3'
|
||||||
|
borderBottomWidth='1px'
|
||||||
|
borderRadius='lg'
|
||||||
|
bg={useColorModeValue('gray.50', 'gray.700')}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Flex justifyContent='space-between'>
|
||||||
|
<Text lineHeight='1.4' fontSize='lg' fontWeight='medium'>
|
||||||
|
<Text as='small' fontSize='sm' color='gray.500' fontWeight='normal'>
|
||||||
|
{/* TODO: need to make lesson order */}
|
||||||
|
Lesson {lesson.id}
|
||||||
|
</Text>
|
||||||
|
<br />
|
||||||
|
{lesson.title}
|
||||||
|
</Text>
|
||||||
|
<Box>
|
||||||
|
<Text>Created {moment(lesson.created_at).format('ll')}</Text>{' '}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Box>
|
||||||
|
<Text>{lesson.description}</Text>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Flex justifyContent='space-between' alignItems='center' mt='2'>
|
||||||
|
<Flex alignItems='center'>
|
||||||
|
<Icon as={TimeIcon} mr='2' />
|
||||||
|
<Text mr='4' color={useColorModeValue('gray.600', 'gray.400')}>
|
||||||
|
1:30:00
|
||||||
|
</Text>
|
||||||
|
<Icon as={MdLibraryBooks} mr='2' />
|
||||||
|
<Text color={useColorModeValue('gray.600', 'gray.400')}>4 materials</Text>
|
||||||
|
</Flex>
|
||||||
|
<Box>
|
||||||
|
<Button
|
||||||
|
onClick={() => history.push(`/lesson/${lesson.id}`)}
|
||||||
|
size='sm'
|
||||||
|
colorScheme='blue'
|
||||||
|
variant='outline'
|
||||||
|
leftIcon={<Icon as={MdPlayCircleOutline} w='4' h='4' />}
|
||||||
|
mr='2'
|
||||||
|
>
|
||||||
|
Visit page
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => history.push(`/lessons/edit/${lesson.id}`)}
|
||||||
|
size='sm'
|
||||||
|
leftIcon={<Icon as={EditIcon} />}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LessonItem;
|
||||||
49
src/pages/courses/edit/components/LessonList/LessonList.tsx
Normal file
49
src/pages/courses/edit/components/LessonList/LessonList.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, Stack, Text, Button } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
import type { LessonItemPropsType } from './LessonItem';
|
||||||
|
import type { ICourse } from '@educt/interfaces';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
|
||||||
|
type LessonListPropsType = {
|
||||||
|
render: React.FC<LessonItemPropsType>;
|
||||||
|
course: Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LessonList: React.FC<LessonListPropsType> = ({ render: Item, course }) => {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{course.lessons.length !== 0 ? (
|
||||||
|
<Stack mt='4'>
|
||||||
|
{course.lessons.map(lesson => (
|
||||||
|
<Item key={lesson.id} lesson={lesson} />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<Box textAlign='center' mt='10'>
|
||||||
|
<Text>No lessons have been added to this course yet</Text>
|
||||||
|
<Button
|
||||||
|
onClick={() => history.push(`${course.id}/create-lesson`)}
|
||||||
|
mt='3'
|
||||||
|
size='sm'
|
||||||
|
colorScheme='blue'
|
||||||
|
variant='outline'
|
||||||
|
>
|
||||||
|
Create new lesson
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LessonList;
|
||||||
4
src/pages/courses/edit/components/LessonList/index.ts
Normal file
4
src/pages/courses/edit/components/LessonList/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import LessonList from './LessonList';
|
||||||
|
import LessonItem from './LessonItem';
|
||||||
|
|
||||||
|
export { LessonList, LessonItem };
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
import { IUser } from '@educt/interfaces';
|
||||||
|
|
||||||
|
export type StudentItemPropsType = {
|
||||||
|
student: Pick<IUser, 'id' | 'first_name' | 'fullname' | 'last_name' | 'email'>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StudentItem: React.FC<StudentItemPropsType> = ({ student }) => {
|
||||||
|
return <div>{student.fullname}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StudentItem;
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Stack, Box, Text, Button } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
import { StudentItemPropsType } from './StudentItem';
|
||||||
|
import { ICourse } from '@educt/interfaces';
|
||||||
|
|
||||||
|
type StudentListPropsType = {
|
||||||
|
render: React.FC<StudentItemPropsType>;
|
||||||
|
students: ICourse['students'];
|
||||||
|
};
|
||||||
|
|
||||||
|
const StudentList: React.FC<StudentListPropsType> = ({ render: Item, students }) => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{students.length !== 0 ? (
|
||||||
|
<Stack>
|
||||||
|
{students.map(student => (
|
||||||
|
<Item key={student.id} student={student} />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<Box textAlign='center' mt='10'>
|
||||||
|
<Text>There are no students in the course yet</Text>
|
||||||
|
<Button mt='3' size='sm' colorScheme='blue' variant='outline'>
|
||||||
|
Add student
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StudentList;
|
||||||
4
src/pages/courses/edit/components/StudentList/index.ts
Normal file
4
src/pages/courses/edit/components/StudentList/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import StudentList from './StudentList';
|
||||||
|
import StudentItem from './StudentItem';
|
||||||
|
|
||||||
|
export { StudentList, StudentItem };
|
||||||
@ -1,24 +1,26 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from 'react-router-dom';
|
||||||
import { Box, Heading, Text, Tab, TabList, Tabs, TabPanels, TabPanel, Button } from '@chakra-ui/react';
|
import { Box, Heading, Text, Tab, TabList, Flex, Tabs, TabPanels, TabPanel } from '@chakra-ui/react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
*/
|
*/
|
||||||
import { IPageProps } from '@educt/interfaces';
|
import type { IPageProps } from '@educt/interfaces';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Components
|
* Components
|
||||||
*/
|
*/
|
||||||
import LoadingPage from '@educt/components/LoadingPage';
|
import LoadingPage from '@educt/components/LoadingPage';
|
||||||
import { EditCourseForm } from '@educt/components/Forms/CourseForm';
|
import { EditCourseForm } from '@educt/components/Forms/CourseForm';
|
||||||
|
import { LessonList, LessonItem } from './components/LessonList';
|
||||||
|
import { StudentList, StudentItem } from './components/StudentList';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
import useFetchCourseQuery from '@educt/hooks/useFetchCourseQuery';
|
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import useFetchCourseQuery from '@educt/hooks/useFetchCourseQuery';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Course editor
|
* Course editor
|
||||||
@ -42,10 +44,13 @@ const EditCoursePage: React.FC<IPageProps> = () => {
|
|||||||
<Helmet>
|
<Helmet>
|
||||||
<title>Editing {course.title}</title>
|
<title>Editing {course.title}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
<Flex>
|
||||||
<Box>
|
<Box>
|
||||||
<Heading as='h1'>Course editor</Heading>
|
<Heading as='h1'>Course editor</Heading>
|
||||||
<Text mt='2'>{course.title}</Text>
|
<Text mt='2'>{course.title}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box></Box>
|
||||||
|
</Flex>
|
||||||
<Box>
|
<Box>
|
||||||
<Tabs isLazy mt='8'>
|
<Tabs isLazy mt='8'>
|
||||||
<TabList>
|
<TabList>
|
||||||
@ -56,6 +61,7 @@ const EditCoursePage: React.FC<IPageProps> = () => {
|
|||||||
|
|
||||||
<TabPanels>
|
<TabPanels>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
|
<Box>
|
||||||
<EditCourseForm
|
<EditCourseForm
|
||||||
defaultValues={{
|
defaultValues={{
|
||||||
title: course.title,
|
title: course.title,
|
||||||
@ -65,18 +71,14 @@ const EditCoursePage: React.FC<IPageProps> = () => {
|
|||||||
image: undefined,
|
image: undefined,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
{course.lessons.length === 0 ? (
|
<LessonList render={LessonItem} course={course} />
|
||||||
<Box textAlign='center' mt='10'>
|
</TabPanel>
|
||||||
<Text>No lessons have been added to this course yet</Text>
|
<TabPanel>
|
||||||
<Button mt='3' size='sm' colorScheme='blue' variant='outline'>
|
<StudentList render={StudentItem} students={course.students} />
|
||||||
Create new lesson
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
) : null}
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel></TabPanel>
|
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
27
src/pages/lesson/lesson.tsx
Normal file
27
src/pages/lesson/lesson.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, Heading } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
import { IPageProps } from '@educt/interfaces';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lesson Page
|
||||||
|
*/
|
||||||
|
const LessonPage: React.FC<IPageProps> = () => {
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Heading as='h1'>Lesson {id}</Heading>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LessonPage;
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
import { Link as ReactRouterLink } from 'react-router-dom';
|
import { Link as ReactRouterLink } from 'react-router-dom';
|
||||||
import { Tag, TagLabel, LinkBox, Heading, LinkOverlay, Icon, Link } from '@chakra-ui/react';
|
import { Tag, TagLabel, LinkBox, Text, LinkOverlay, Icon, Link } from '@chakra-ui/react';
|
||||||
import { Box, Flex } from '@chakra-ui/layout';
|
import { Box, Flex } from '@chakra-ui/layout';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -12,81 +13,54 @@ import { TimeIcon } from '@chakra-ui/icons';
|
|||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
import { useColorMode } from '@chakra-ui/react';
|
import { useColorModeValue } from '@chakra-ui/color-mode';
|
||||||
|
|
||||||
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='3' borderBottomWidth='1px' borderRadius='lg' bg={useColorModeValue('gray.50', 'gray.700')}>
|
||||||
<LinkBox as='div' p='5' borderWidth='1px' rounded='lg'>
|
<Flex justifyContent='space-between'>
|
||||||
<Flex mb='4' justifyContent='space-between'>
|
|
||||||
<Box>
|
<Box>
|
||||||
<Tag variant='outline' borderRadius='full' alignSelf='flex-start'>
|
<Tag variant='outline' borderRadius='full' alignSelf='flex-start'>
|
||||||
<TagLabel>{course.lessons_count} lessons</TagLabel>
|
<TagLabel>{course.lessons_count} lessons</TagLabel>
|
||||||
</Tag>
|
</Tag>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<Tag variant='subtle' colorScheme='green' borderRadius='full' alignSelf='flex-start'>
|
<Tag variant='subtle' colorScheme='green' borderRadius='full' alignSelf='flex-start'>
|
||||||
<TagLabel>{course.category.title}</TagLabel>
|
<TagLabel>{course.category.title}</TagLabel>
|
||||||
</Tag>
|
</Tag>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Heading size='md' my='2'>
|
<Flex justifyContent='space-between' mt='2'>
|
||||||
<LinkOverlay as={ReactRouterLink} to={`/course/${course.id}`} overflowWrap='anywhere'>
|
<Text>
|
||||||
|
<LinkOverlay
|
||||||
|
as={ReactRouterLink}
|
||||||
|
fontSize='xl'
|
||||||
|
fontWeight='medium'
|
||||||
|
to={`/course/${course.id}`}
|
||||||
|
overflowWrap='anywhere'
|
||||||
|
>
|
||||||
{course.title}
|
{course.title}
|
||||||
</LinkOverlay>
|
</LinkOverlay>
|
||||||
</Heading>
|
</Text>
|
||||||
<Flex mt='6' alignItems='center'>
|
</Flex>
|
||||||
|
|
||||||
|
<Flex mt='2' alignItems='center' sx={{ gap: '9px' }}>
|
||||||
<Icon as={TimeIcon} w='14px' h='14px' />
|
<Icon as={TimeIcon} w='14px' h='14px' />
|
||||||
<Box
|
<Box
|
||||||
as='time'
|
as='time'
|
||||||
color={colorMode === 'dark' ? 'gray.400' : 'gray.600'}
|
|
||||||
ml='2'
|
|
||||||
verticalAlign='middle'
|
verticalAlign='middle'
|
||||||
dateTime={course.created_at}
|
dateTime={course.created_at}
|
||||||
|
color={useColorModeValue('gray.600', 'gray.400')}
|
||||||
>
|
>
|
||||||
Last update {new Date(course.created_at).toLocaleDateString()}
|
Last update {moment(course.created_at).format('ll')}
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
</LinkBox>
|
</LinkBox>
|
||||||
</Box>
|
|
||||||
|
|
||||||
// <Flex flexDir='column' borderWidth='1px' borderRadius='lg' p='10px' boxShadow='sm' mb='3'>
|
|
||||||
// <Tag colorScheme='purple' borderRadius='full' alignSelf='flex-start'>
|
|
||||||
// <TagLabel>{course.category.title}</TagLabel>
|
|
||||||
// </Tag>
|
|
||||||
// <Flex justifyContent='space-between' alignItems='center'>
|
|
||||||
// <Link
|
|
||||||
// as={ReactRouterLink}
|
|
||||||
// to={`course/${course.id}`}
|
|
||||||
// overflowWrap='anywhere'
|
|
||||||
// fontWeight='bold'
|
|
||||||
// fontSize='xl'
|
|
||||||
// mr='5'
|
|
||||||
// >
|
|
||||||
// {course.title}
|
|
||||||
// </Link>
|
|
||||||
// </Flex>
|
|
||||||
// <Text>{course.description}</Text>
|
|
||||||
// <Flex mt='5' alignItems='center'>
|
|
||||||
// <MdVideoLibrary />
|
|
||||||
// <Text pl='1' pr='4'>
|
|
||||||
// {course.lessons_count} lessons
|
|
||||||
// </Text>
|
|
||||||
// <MdGroup />
|
|
||||||
// <Text pl='1' pr='4'>
|
|
||||||
// {course.students_count} students
|
|
||||||
// </Text>
|
|
||||||
// <MdThumbUp />
|
|
||||||
// <Text pl='1' pr='4'>
|
|
||||||
// {course.likes_count} likes
|
|
||||||
// </Text>
|
|
||||||
// </Flex>
|
|
||||||
// </Flex>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -69,6 +69,7 @@ const UserItem: React.FC<UserItemPropsType> = ({ user, onEdit, onDelete }) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
<Box>
|
<Box>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
size='sm'
|
||||||
disabled={isDisabledActions}
|
disabled={isDisabledActions}
|
||||||
onClick={() => onEdit(user)}
|
onClick={() => onEdit(user)}
|
||||||
aria-label='Edit'
|
aria-label='Edit'
|
||||||
@ -77,6 +78,7 @@ const UserItem: React.FC<UserItemPropsType> = ({ user, onEdit, onDelete }) => {
|
|||||||
icon={<EditIcon />}
|
icon={<EditIcon />}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
size='sm'
|
||||||
disabled={isDisabledActions}
|
disabled={isDisabledActions}
|
||||||
onClick={() => onDelete(user)}
|
onClick={() => onDelete(user)}
|
||||||
colorScheme='red'
|
colorScheme='red'
|
||||||
|
|||||||
@ -7,12 +7,14 @@ import { IPrivateRouteProps } from './components/PrivateRoute/PrivateRoute';
|
|||||||
import AuthPage from '@educt/pages/auth/auth';
|
import AuthPage from '@educt/pages/auth/auth';
|
||||||
import NotFoundPage from '@educt/pages/404/404';
|
import NotFoundPage from '@educt/pages/404/404';
|
||||||
import CreateCoursePage from '@educt/pages/courses/create/create';
|
import CreateCoursePage from '@educt/pages/courses/create/create';
|
||||||
|
import CreateLessonPage from '@educt/pages/courses/create-lesson/create-lesson';
|
||||||
import EditCoursePage from '@educt/pages/courses/edit/edit';
|
import EditCoursePage from '@educt/pages/courses/edit/edit';
|
||||||
import ChangePasswordPage from '@educt/pages/profile/change-password/change-password';
|
import ChangePasswordPage from '@educt/pages/profile/change-password/change-password';
|
||||||
import ChangeEmailPage from '@educt/pages/profile/change-email/change-email';
|
import ChangeEmailPage from '@educt/pages/profile/change-email/change-email';
|
||||||
import { UserRoleEnum } from './enums';
|
import { UserRoleEnum } from './enums';
|
||||||
const CoursesPage = lazy(() => import('@educt/pages/courses/courses'));
|
const CoursesPage = lazy(() => import('@educt/pages/courses/courses'));
|
||||||
const LessonsPage = lazy(() => import('@educt/pages/lessons/lessons'));
|
const LessonsPage = lazy(() => import('@educt/pages/lessons/lessons'));
|
||||||
|
const LessonPage = lazy(() => import('@educt/pages/lesson/lesson'));
|
||||||
const MainPage = lazy(() => import('@educt/pages/main/main'));
|
const MainPage = lazy(() => import('@educt/pages/main/main'));
|
||||||
const MessagesPage = lazy(() => import('@educt/pages/messages/messages'));
|
const MessagesPage = lazy(() => import('@educt/pages/messages/messages'));
|
||||||
const ProfilePage = lazy(() => import('@educt/pages/profile/profile'));
|
const ProfilePage = lazy(() => import('@educt/pages/profile/profile'));
|
||||||
@ -52,6 +54,14 @@ export const routes: IRouteOption[] = [
|
|||||||
exact: true,
|
exact: true,
|
||||||
roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER],
|
roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/courses/edit/:id/create-lesson',
|
||||||
|
title: 'Create Lesson',
|
||||||
|
component: CreateLessonPage,
|
||||||
|
isPrivate: true,
|
||||||
|
exact: true,
|
||||||
|
roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/lessons',
|
path: '/lessons',
|
||||||
title: 'Lessons',
|
title: 'Lessons',
|
||||||
@ -60,6 +70,13 @@ export const routes: IRouteOption[] = [
|
|||||||
exact: true,
|
exact: true,
|
||||||
roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER],
|
roles: [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/lesson/:id',
|
||||||
|
title: 'Lesson',
|
||||||
|
component: LessonPage,
|
||||||
|
isPrivate: true,
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/messages',
|
path: '/messages',
|
||||||
title: 'Messages',
|
title: 'Messages',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user