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

View File

@ -1,11 +1,27 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
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
*/
import { IPageProps } from '@educt/interfaces';
import { ILesson, IPageProps } from '@educt/interfaces';
/**
* Hooks
@ -14,7 +30,10 @@ import { Redirect, useParams } from 'react-router-dom';
import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements';
import { useFetchLesson } from '@educt/hooks/queries/lesson/useFetchLesson';
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
@ -22,32 +41,87 @@ import { MdCheckCircle, MdOutlineFilePresent, MdSkipNext, MdSkipPrevious } from
const LessonPage: React.FC<IPageProps> = () => {
const { id } = useParams<{ id: string }>();
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) {
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 (
<PageWrapper>
<Helmet>
<title>{lesson.title}</title>
</Helmet>
<PageHeading heading={lesson.title}>
<Box mt='3'>
<Text fontSize={'sm'}>~{moment.duration(lesson.duration, 'minutes').humanize()}</Text>
</Box>
</PageHeading>
<PageContent>
<Grid templateColumns={{ base: '1fr', xl: '3fr 1fr' }} gap={'7'}>
<Grid templateColumns={{ base: '1fr', xl: '3fr 1fr' }}>
<GridItem>
<Box>
<Box bg={lesson.color?.hex} minH={'500px'} />
</Box>
<Flex justifyContent={'flex-end'} mt='3'>
<IconButton aria-label='prev-lesson' icon={<MdSkipPrevious />} size='sm' mr='2' />
<Button aria-label='next-lesson' leftIcon={<MdSkipNext />} size='sm'>
{prevLesson && (
<IconButton
onClick={handlePrevLesson}
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>
<Box mt='7'>
@ -61,27 +135,35 @@ const LessonPage: React.FC<IPageProps> = () => {
</GridItem>
<GridItem>
<Box>
<Stack maxH={'445px'} overflowY={'scroll'}>
{Array(50)
.fill(1)
.map((n, i) => (
<Flex key={i} alignItems={'center'} borderRadius={'xl'}>
<Icon as={MdCheckCircle} color='green.500' w={7} h={7} mr='2' />
<Stack maxH={'500px'} overflowY={'scroll'} spacing={'0'}>
{lessons.map((lesson, i) => (
<LinkBox
as='div'
display={'flex'}
key={lesson.id}
bg={lesson.id === id ? 'gray.50' : ''}
p='4'
alignItems={'center'}
_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'}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Vel, eaque.
{lesson.title}
</Text>
</LinkOverlay>
</Box>
</Flex>
</LinkBox>
))}
</Stack>
<Divider mt='5' mb='5' />
<Box>
<Box p='5'>
<Text fontSize={'2xl'} fontWeight={'medium'}>
Materials
</Text>

View File

@ -1,7 +1,7 @@
import { AxiosInstance } from 'axios';
import { ApiServiceInstance } from '.';
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 { CreateCourseParamsType, FetchCoursesParams, UpdateCourseParamsType } from '@educt/types';
@ -40,6 +40,17 @@ class CourseService {
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
*