feat: updated lesson page layout & fethcing lessons

This commit is contained in:
Sergey Yarkov 2021-12-21 00:30:33 +03:00
parent 44cc8f47ec
commit e4eb6e84fb
4 changed files with 146 additions and 28 deletions

View File

@ -0,0 +1,21 @@
import useAsync from '@educt/hooks/useAsync';
import { ILesson } from '@educt/interfaces';
import { CourseServiceInstance } from '@educt/services';
const useFetchCourseLessons = () => {
const fetch = async (id: string) => {
try {
const { data } = await CourseServiceInstance.fetchLessonsById(id);
return data;
} catch (error) {
console.error(error);
return Promise.reject(error);
}
};
const { execute, ...state } = useAsync<Array<ILesson>, Parameters<typeof fetch>>(fetch);
return { fetchCourseLessons: execute, ...state };
};
export { useFetchCourseLessons };

View File

@ -24,6 +24,7 @@ import LoadingPage from '@educt/components/LoadingPage';
import { useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { useFetchCourse } from '@educt/hooks/queries'; import { useFetchCourse } from '@educt/hooks/queries';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { Helmet } from 'react-helmet';
const CoursePage: React.FC<IPageProps> = () => { const CoursePage: React.FC<IPageProps> = () => {
const { const {
@ -47,6 +48,9 @@ const CoursePage: React.FC<IPageProps> = () => {
return ( return (
<PageWrapper maxW='1200px'> <PageWrapper maxW='1200px'>
<Helmet>
<title>{course.title}</title>
</Helmet>
<CourseHeader> <CourseHeader>
<CourseHeading title={course.title} teacher={course.teacher} category={course.category} /> <CourseHeading title={course.title} teacher={course.teacher} category={course.category} />
<CourseInfo <CourseInfo

View File

@ -1,11 +1,27 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import moment from 'moment'; import moment from 'moment';
import { Flex, Box, Button, Divider, Heading, Text, Grid, GridItem, Stack, Icon, IconButton } from '@chakra-ui/react'; import { Link as ReactRouterLink, useHistory } from 'react-router-dom';
import {
Flex,
Box,
Button,
Divider,
Heading,
Text,
Grid,
LinkBox,
LinkOverlay,
GridItem,
Stack,
Icon,
IconButton,
useToast,
} from '@chakra-ui/react';
/** /**
* Types * Types
*/ */
import { IPageProps } from '@educt/interfaces'; import { ILesson, IPageProps } from '@educt/interfaces';
/** /**
* Hooks * Hooks
@ -14,7 +30,10 @@ import { Redirect, useParams } from 'react-router-dom';
import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements'; import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements';
import { useFetchLesson } from '@educt/hooks/queries/lesson/useFetchLesson'; import { useFetchLesson } from '@educt/hooks/queries/lesson/useFetchLesson';
import LoadingPage from '@educt/components/LoadingPage'; import LoadingPage from '@educt/components/LoadingPage';
import { MdCheckCircle, MdOutlineFilePresent, MdSkipNext, MdSkipPrevious } from 'react-icons/md'; import { MdCheckCircle, MdOutlineCircle, MdOutlineFilePresent, MdSkipNext, MdSkipPrevious } from 'react-icons/md';
import { useRootStore } from '@educt/hooks/useRootStore';
import { Helmet } from 'react-helmet';
import { useFetchCourseLessons } from '@educt/hooks/queries/course/useFetchCourseLessons';
/** /**
* Lesson Page * Lesson Page
@ -22,32 +41,87 @@ import { MdCheckCircle, MdOutlineFilePresent, MdSkipNext, MdSkipPrevious } from
const LessonPage: React.FC<IPageProps> = () => { const LessonPage: React.FC<IPageProps> = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const { error, data: lesson, isLoading } = useFetchLesson(id); const { error, data: lesson, isLoading } = useFetchLesson(id);
const { fetchCourseLessons, data: lessons } = useFetchCourseLessons();
const toast = useToast();
const history = useHistory();
const {
userStore: { me },
} = useRootStore();
const [nextLesson, setNextLesson] = useState<ILesson | null>(null);
const [prevLesson, setPrevLesson] = useState<ILesson | null>(null);
const [isPageAvailable, setIsPageAvailable] = useState<boolean>(true);
useEffect(() => {
/**
* Сheck if the user has access to the lesson
*/
const isHasAccess = me?.courses?.some(course => course.id === lesson?.course_id) ?? true;
if (!isHasAccess) {
toast({ title: 'You are not able to view this lesson.', status: 'warning' });
setIsPageAvailable(false);
}
/**
* Define next and prev lessons
*/
if (lessons) {
const currentLessonIndex = lessons.findIndex(lesson => lesson.id === id);
setNextLesson(lessons[currentLessonIndex + 1] || null);
setPrevLesson(lessons[currentLessonIndex - 1] || null);
}
}, [lessons]);
useEffect(() => {
if (lesson !== null) {
fetchCourseLessons(lesson.course_id);
}
}, [lesson]);
if (error?.response?.status === 404) { if (error?.response?.status === 404) {
return <Redirect to='/404' />; return <Redirect to='/404' />;
} }
if (!lesson || isLoading) return <LoadingPage />; if (!lesson || !lessons || isLoading) return <LoadingPage />;
if (!isPageAvailable) return <Redirect to={`/course/${lesson.course_id}`} />;
const handleNextLesson = () => nextLesson && history.push(`/lesson/${nextLesson.id}`);
const handlePrevLesson = () => prevLesson && history.push(`/lesson/${prevLesson.id}`);
return ( return (
<PageWrapper> <PageWrapper>
<Helmet>
<title>{lesson.title}</title>
</Helmet>
<PageHeading heading={lesson.title}> <PageHeading heading={lesson.title}>
<Box mt='3'> <Box mt='3'>
<Text fontSize={'sm'}>~{moment.duration(lesson.duration, 'minutes').humanize()}</Text> <Text fontSize={'sm'}>~{moment.duration(lesson.duration, 'minutes').humanize()}</Text>
</Box> </Box>
</PageHeading> </PageHeading>
<PageContent> <PageContent>
<Grid templateColumns={{ base: '1fr', xl: '3fr 1fr' }} gap={'7'}> <Grid templateColumns={{ base: '1fr', xl: '3fr 1fr' }}>
<GridItem> <GridItem>
<Box> <Box>
<Box bg={lesson.color?.hex} minH={'500px'} /> <Box bg={lesson.color?.hex} minH={'500px'} />
</Box> </Box>
<Flex justifyContent={'flex-end'} mt='3'> <Flex justifyContent={'flex-end'} mt='3'>
<IconButton aria-label='prev-lesson' icon={<MdSkipPrevious />} size='sm' mr='2' /> {prevLesson && (
<Button aria-label='next-lesson' leftIcon={<MdSkipNext />} size='sm'> <IconButton
Next lesson onClick={handlePrevLesson}
</Button> aria-label='prev-lesson'
icon={<MdSkipPrevious />}
size='sm'
mr='2'
/>
)}
{nextLesson && (
<Button onClick={handleNextLesson} aria-label='next-lesson' leftIcon={<MdSkipNext />} size='sm'>
Next lesson
</Button>
)}
</Flex> </Flex>
<Box mt='7'> <Box mt='7'>
@ -61,27 +135,35 @@ const LessonPage: React.FC<IPageProps> = () => {
</GridItem> </GridItem>
<GridItem> <GridItem>
<Box> <Box>
<Stack maxH={'445px'} overflowY={'scroll'}> <Stack maxH={'500px'} overflowY={'scroll'} spacing={'0'}>
{Array(50) {lessons.map((lesson, i) => (
.fill(1) <LinkBox
.map((n, i) => ( as='div'
<Flex key={i} alignItems={'center'} borderRadius={'xl'}> display={'flex'}
<Icon as={MdCheckCircle} color='green.500' w={7} h={7} mr='2' /> key={lesson.id}
<Box> bg={lesson.id === id ? 'gray.50' : ''}
<Text as='small' color='gray.500'> p='4'
Lesson {i + 1} alignItems={'center'}
</Text> _hover={{
bg: 'gray.50',
}}
>
<Icon as={MdOutlineCircle} color='gray.500' w={6} h={6} mr='2' />
<Box>
<Text as='small' color='gray.500'>
Lesson {i + 1}
</Text>
<LinkOverlay as={ReactRouterLink} to={`/lesson/${lesson.id}`}>
<Text fontSize='sm' fontWeight={'medium'}> <Text fontSize='sm' fontWeight={'medium'}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Vel, eaque. {lesson.title}
</Text> </Text>
</Box> </LinkOverlay>
</Flex> </Box>
))} </LinkBox>
))}
</Stack> </Stack>
<Divider mt='5' mb='5' /> <Box p='5'>
<Box>
<Text fontSize={'2xl'} fontWeight={'medium'}> <Text fontSize={'2xl'} fontWeight={'medium'}>
Materials Materials
</Text> </Text>

View File

@ -1,7 +1,7 @@
import { AxiosInstance } from 'axios'; import { AxiosInstance } from 'axios';
import { ApiServiceInstance } from '.'; import { ApiServiceInstance } from '.';
import * as helpers from '@educt/helpers'; import * as helpers from '@educt/helpers';
import { IApiRespose, ICourse, IUser } from '@educt/interfaces'; import { IApiRespose, ICourse, ILesson, IUser } from '@educt/interfaces';
import { CourseStatusEnum } from '@educt/enums'; import { CourseStatusEnum } from '@educt/enums';
import { CreateCourseParamsType, FetchCoursesParams, UpdateCourseParamsType } from '@educt/types'; import { CreateCourseParamsType, FetchCoursesParams, UpdateCourseParamsType } from '@educt/types';
@ -40,6 +40,17 @@ class CourseService {
return result.data; return result.data;
} }
/**
* Fetch lessons by course id
*
* @param id Course id
* @returns Array of lessons
*/
public async fetchLessonsById(id: string): Promise<IApiRespose<Array<ILesson>>> {
const result = await this.api.get(`/v1/courses/${id}/lessons`);
return result.data;
}
/** /**
* Update status of course by id * Update status of course by id
* *