From e4eb6e84fbd16201dd0575928002a37bc1dace40 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Tue, 21 Dec 2021 00:30:33 +0300 Subject: [PATCH 1/5] feat: updated lesson page layout & fethcing lessons --- .../queries/course/useFetchCourseLessons.ts | 21 +++ src/pages/course/course.tsx | 4 + src/pages/lesson/lesson.tsx | 136 ++++++++++++++---- src/services/CourseService.ts | 13 +- 4 files changed, 146 insertions(+), 28 deletions(-) create mode 100644 src/hooks/queries/course/useFetchCourseLessons.ts diff --git a/src/hooks/queries/course/useFetchCourseLessons.ts b/src/hooks/queries/course/useFetchCourseLessons.ts new file mode 100644 index 0000000..644102f --- /dev/null +++ b/src/hooks/queries/course/useFetchCourseLessons.ts @@ -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, Parameters>(fetch); + + return { fetchCourseLessons: execute, ...state }; +}; + +export { useFetchCourseLessons }; diff --git a/src/pages/course/course.tsx b/src/pages/course/course.tsx index a35d6a1..2bab4df 100644 --- a/src/pages/course/course.tsx +++ b/src/pages/course/course.tsx @@ -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 = () => { const { @@ -47,6 +48,9 @@ const CoursePage: React.FC = () => { return ( + + {course.title} + = () => { 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(null); + const [prevLesson, setPrevLesson] = useState(null); + const [isPageAvailable, setIsPageAvailable] = useState(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 ; } - if (!lesson || isLoading) return ; + if (!lesson || !lessons || isLoading) return ; + + if (!isPageAvailable) return ; + + const handleNextLesson = () => nextLesson && history.push(`/lesson/${nextLesson.id}`); + const handlePrevLesson = () => prevLesson && history.push(`/lesson/${prevLesson.id}`); return ( + + {lesson.title} + ~{moment.duration(lesson.duration, 'minutes').humanize()} - + - } size='sm' mr='2' /> - + {prevLesson && ( + } + size='sm' + mr='2' + /> + )} + {nextLesson && ( + + )} @@ -61,27 +135,35 @@ const LessonPage: React.FC = () => { - - {Array(50) - .fill(1) - .map((n, i) => ( - - - - - Lesson {i + 1} - + + {lessons.map((lesson, i) => ( + + + + + Lesson {i + 1} + + - Lorem ipsum dolor sit amet consectetur adipisicing elit. Vel, eaque. + {lesson.title} - - - ))} + + + + ))} - - - + Materials diff --git a/src/services/CourseService.ts b/src/services/CourseService.ts index 82cad95..beb7fd4 100644 --- a/src/services/CourseService.ts +++ b/src/services/CourseService.ts @@ -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>> { + const result = await this.api.get(`/v1/courses/${id}/lessons`); + return result.data; + } + /** * Update status of course by id * From 2f8b7d3f715094be6d404ed32c3eac160187da18 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Tue, 21 Dec 2021 01:01:13 +0300 Subject: [PATCH 2/5] fix: fixed scroll & fixed page access --- src/pages/lesson/lesson.tsx | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/pages/lesson/lesson.tsx b/src/pages/lesson/lesson.tsx index ef9cdd0..86d6707 100644 --- a/src/pages/lesson/lesson.tsx +++ b/src/pages/lesson/lesson.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import moment from 'moment'; import { Link as ReactRouterLink, useHistory } from 'react-router-dom'; import { @@ -16,6 +16,7 @@ import { Icon, IconButton, useToast, + useColorModeValue, } from '@chakra-ui/react'; /** @@ -30,7 +31,7 @@ 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, MdOutlineCircle, MdOutlineFilePresent, MdSkipNext, MdSkipPrevious } from 'react-icons/md'; +import { 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'; @@ -39,28 +40,36 @@ import { useFetchCourseLessons } from '@educt/hooks/queries/course/useFetchCours * Lesson Page */ const LessonPage: React.FC = () => { + const { + userStore: { me }, + } = useRootStore(); 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(null); const [prevLesson, setPrevLesson] = useState(null); const [isPageAvailable, setIsPageAvailable] = useState(true); + const currentLessonRef = useRef(null); + const bg = useColorModeValue('gray.50', 'gray.700'); useEffect(() => { /** * Сheck if the user has access to the lesson */ - const isHasAccess = me?.courses?.some(course => course.id === lesson?.course_id) ?? true; + if (me === null || lesson === null) return; + 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); } + }, []); + + useEffect(() => { + currentLessonRef.current?.scrollIntoView({ block: 'center' }); + window.scrollTo(0, 0); /** * Define next and prev lessons @@ -104,7 +113,7 @@ const LessonPage: React.FC = () => { - + @@ -133,19 +142,20 @@ const LessonPage: React.FC = () => { - + {lessons.map((lesson, i) => ( @@ -169,7 +179,7 @@ const LessonPage: React.FC = () => { {[1, 2, 3].map(n => ( - + lesson-1_sourse-code.zip
From 37c5d8c36e79cea0dd0568267629cf5b2bc70814 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Tue, 21 Dec 2021 20:51:08 +0300 Subject: [PATCH 3/5] feat: downloading materials from lesson --- src/hooks/queries/lesson/useFetchLesson.ts | 22 ++++- src/interfaces/index.ts | 22 ++++- .../edit/components/LessonList/LessonList.tsx | 6 +- src/pages/lesson/lesson.tsx | 96 ++++++++++++------- .../components/CourseList/CourseItem.tsx | 1 + src/pages/profile/components/index.tsx | 4 +- src/services/LessonService.ts | 11 +++ 7 files changed, 120 insertions(+), 42 deletions(-) diff --git a/src/hooks/queries/lesson/useFetchLesson.ts b/src/hooks/queries/lesson/useFetchLesson.ts index 7c2f27b..ddf6915 100644 --- a/src/hooks/queries/lesson/useFetchLesson.ts +++ b/src/hooks/queries/lesson/useFetchLesson.ts @@ -1,12 +1,28 @@ -import useAsync from '@educt/hooks/useAsync'; import { ILesson } from '@educt/interfaces'; import { LessonServiceInstance } from '@educt/services'; +import useAsync from '@educt/hooks/useAsync'; import { useEffect } from 'react'; +import { useToast } from '@chakra-ui/react'; +import { useErrorHandler } from 'react-error-boundary'; const useFetchLesson = (id: string) => { + const toast = useToast(); + const handleError = useErrorHandler(); + const fetch = async (id: string) => { - const { data } = await LessonServiceInstance.fetchLessonById(id); - return data; + try { + const { data } = await LessonServiceInstance.fetchLessonById(id); + return data; + } catch (error: any) { + if (error.response) { + if (error.response.status === 403) { + toast({ title: 'You are not able to view this lesson.', status: 'warning' }); + } + } else { + handleError(error); + } + return Promise.reject(error); + } }; const { execute: fetchLessonById, ...state } = useAsync>(fetch); diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 37adf39..53e43fa 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -27,7 +27,7 @@ export interface ICourse { status: CourseStatusEnum; teacher: Pick; category: ICategory; - lessons: ILesson[]; + lessons: Omit[]; students: IUser[]; color: ColorType | null; students_count: string; @@ -45,10 +45,30 @@ export interface ILesson { duration: string; color?: ColorType | undefined | null; materials_count?: string | undefined; + materials: Array; + content: ILessonContent; created_at: string; updated_at: string; } +export interface ILessonMaterial { + id: number; + lesson_id: string; + name: string; + client_name: string; + size: number; + url: string; + ext: string; + created_at: string; + updated_at: string; +} + +export interface ILessonContent { + id: number; + lesson_id: string; + video_url: string; +} + export interface ICategory { id: string; title: string; diff --git a/src/pages/courses/edit/components/LessonList/LessonList.tsx b/src/pages/courses/edit/components/LessonList/LessonList.tsx index a59c65e..e5bff33 100644 --- a/src/pages/courses/edit/components/LessonList/LessonList.tsx +++ b/src/pages/courses/edit/components/LessonList/LessonList.tsx @@ -56,13 +56,13 @@ const CreateLessonButton: React.FC<{ id: string }> = ({ id }) => { const LessonList: React.FC = ({ course }) => { const history = useHistory(); const { onOpen: onOpenDelDialog, onClose: onCloseDelDialog, isOpen: isOpenDelDialog } = useDisclosure(); - const [lessons, setLessons] = useState>(course.lessons); + const [lessons, setLessons] = useState(course.lessons); const [deleting, setDeleting] = useState | null>(null); const handleError = useErrorHandler(); const handleCreateLesson = (): void => history.push('/lessons/create'); const handleEditLesson = (id: string): void => history.push(`/lessons/edit/${id}`); - const handleDeleteLesson = (lesson: ILesson): void => { + const handleDeleteLesson = (lesson: ICourse['lessons'][number]): void => { setDeleting({ id: lesson.id, title: lesson.title }); onOpenDelDialog(); }; @@ -93,7 +93,7 @@ const LessonList: React.FC = ({ course }) => { index, }: { provided: DraggableProvided; - lesson: ILesson; + lesson: ICourse['lessons'][number]; isDragging: boolean; index: number; }) => { diff --git a/src/pages/lesson/lesson.tsx b/src/pages/lesson/lesson.tsx index 86d6707..9c3349c 100644 --- a/src/pages/lesson/lesson.tsx +++ b/src/pages/lesson/lesson.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useRef, useState } from 'react'; import moment from 'moment'; +import * as helpres from '@educt/helpers'; import { Link as ReactRouterLink, useHistory } from 'react-router-dom'; import { Flex, @@ -15,14 +16,13 @@ import { Stack, Icon, IconButton, - useToast, useColorModeValue, } from '@chakra-ui/react'; /** * Types */ -import { ILesson, IPageProps } from '@educt/interfaces'; +import { ILesson, ILessonMaterial, IPageProps } from '@educt/interfaces'; /** * Hooks @@ -32,41 +32,23 @@ import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageEle import { useFetchLesson } from '@educt/hooks/queries/lesson/useFetchLesson'; import LoadingPage from '@educt/components/LoadingPage'; import { 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'; +import { LessonServiceInstance } from '@educt/services'; /** * Lesson Page */ const LessonPage: React.FC = () => { - const { - userStore: { me }, - } = useRootStore(); const { id } = useParams<{ id: string }>(); const { error, data: lesson, isLoading } = useFetchLesson(id); const { fetchCourseLessons, data: lessons } = useFetchCourseLessons(); - const toast = useToast(); const history = useHistory(); const [nextLesson, setNextLesson] = useState(null); const [prevLesson, setPrevLesson] = useState(null); - const [isPageAvailable, setIsPageAvailable] = useState(true); const currentLessonRef = useRef(null); const bg = useColorModeValue('gray.50', 'gray.700'); - useEffect(() => { - /** - * Сheck if the user has access to the lesson - */ - if (me === null || lesson === null) return; - 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); - } - }, []); - useEffect(() => { currentLessonRef.current?.scrollIntoView({ block: 'center' }); window.scrollTo(0, 0); @@ -88,17 +70,39 @@ const LessonPage: React.FC = () => { } }, [lesson]); + /** + * Lesson not found + */ if (error?.response?.status === 404) { return ; } + /** + * Unavailable lesson for authorized user + */ + if (error?.response?.status === 403) { + return ; + } + if (!lesson || !lessons || isLoading) return ; - if (!isPageAvailable) return ; - const handleNextLesson = () => nextLesson && history.push(`/lesson/${nextLesson.id}`); + const handlePrevLesson = () => prevLesson && history.push(`/lesson/${prevLesson.id}`); + const handleDownloadMaterial = + ({ name, client_name }: ILessonMaterial) => + async () => { + const blob = await LessonServiceInstance.fetchMaterial(name); + if (blob instanceof Blob) { + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = `${client_name}`; + link.click(); + window.URL.revokeObjectURL(link.href); + } + }; + return ( @@ -177,17 +181,41 @@ const LessonPage: React.FC = () => { Materials - - {[1, 2, 3].map(n => ( - - - - lesson-1_sourse-code.zip
- 190kB -
-
- ))} -
+ {lesson.materials.length !== 0 ? ( + + {lesson.materials.map(material => ( + + + + {material.client_name} +
+ {helpres.transformBytes(material.size)} +
+
+ ))} +
+ ) : ( + + + There are no materials + + + )}
diff --git a/src/pages/profile/components/CourseList/CourseItem.tsx b/src/pages/profile/components/CourseList/CourseItem.tsx index 109bcc2..e5f4112 100644 --- a/src/pages/profile/components/CourseList/CourseItem.tsx +++ b/src/pages/profile/components/CourseList/CourseItem.tsx @@ -37,6 +37,7 @@ const CourseItem: React.FC = ({ course }) => { = props => { {email} - + + + diff --git a/src/services/LessonService.ts b/src/services/LessonService.ts index 1bc4388..7073638 100644 --- a/src/services/LessonService.ts +++ b/src/services/LessonService.ts @@ -20,6 +20,17 @@ class LessonService { return result.data; } + /** + * Download material file by file name + * + * @param fileName File name + * @returns Blob + */ + public async fetchMaterial(fileName: string): Promise { + const result = await this.api.get(`/v1/lessons/materials/${fileName}`, { responseType: 'blob' }); + return result.data; + } + /** * Delete lesson by id * From c0fda55e5dcbce5fb9628e5b355ff3508e9aab73 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Wed, 22 Dec 2021 22:09:47 +0300 Subject: [PATCH 4/5] feat: lesson progress --- .../queries/lesson/useFetchLessonProgress.ts | 22 ++++++++++ src/interfaces/index.ts | 5 ++- src/pages/lesson/lesson.tsx | 43 +++++++++++++------ src/services/LessonService.ts | 12 ++++++ src/types/index.d.ts | 6 +++ 5 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 src/hooks/queries/lesson/useFetchLessonProgress.ts diff --git a/src/hooks/queries/lesson/useFetchLessonProgress.ts b/src/hooks/queries/lesson/useFetchLessonProgress.ts new file mode 100644 index 0000000..da9facf --- /dev/null +++ b/src/hooks/queries/lesson/useFetchLessonProgress.ts @@ -0,0 +1,22 @@ +import { LessonServiceInstance } from '@educt/services'; +import type { LessonProgress } from '@educt/types'; +import useAsync from '../../useAsync'; + +const useFetchLessonProgress = () => { + const fetch = async (id: string) => { + try { + const result = await LessonServiceInstance.fetchProgress(id); + return result.data; + } catch (error) { + console.error(error); + + return Promise.reject(error); + } + }; + + const { execute, ...state } = useAsync>(fetch); + + return { fetchLessonProgress: execute, ...state }; +}; + +export { useFetchLessonProgress }; diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 53e43fa..f4a1633 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -1,5 +1,5 @@ import type { RouteComponentProps } from 'react-router-dom'; -import { AttachmentFileType, ColorType, LinkType } from '@educt/types'; +import { AttachmentFileType, ColorType, LessonProgress, LinkType } from '@educt/types'; import { CourseStatusEnum, UserRoleEnum } from '../enums'; export interface IAppConfig { @@ -27,7 +27,7 @@ export interface ICourse { status: CourseStatusEnum; teacher: Pick; category: ICategory; - lessons: Omit[]; + lessons: Omit[]; students: IUser[]; color: ColorType | null; students_count: string; @@ -46,6 +46,7 @@ export interface ILesson { color?: ColorType | undefined | null; materials_count?: string | undefined; materials: Array; + progress: LessonProgress; content: ILessonContent; created_at: string; updated_at: string; diff --git a/src/pages/lesson/lesson.tsx b/src/pages/lesson/lesson.tsx index 9c3349c..f24dcc2 100644 --- a/src/pages/lesson/lesson.tsx +++ b/src/pages/lesson/lesson.tsx @@ -1,7 +1,8 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React from 'react'; import moment from 'moment'; import * as helpres from '@educt/helpers'; -import { Link as ReactRouterLink, useHistory } from 'react-router-dom'; +import { Link as ReactRouterLink, Redirect } from 'react-router-dom'; +import { Helmet } from 'react-helmet'; import { Flex, Box, @@ -18,6 +19,9 @@ import { IconButton, useColorModeValue, } from '@chakra-ui/react'; +import { MdOutlineCircle, MdCheckCircle, MdOutlineFilePresent, MdSkipNext, MdSkipPrevious } from 'react-icons/md'; +import LoadingPage from '@educt/components/LoadingPage'; +import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements'; /** * Types @@ -27,13 +31,15 @@ import { ILesson, ILessonMaterial, IPageProps } from '@educt/interfaces'; /** * Hooks */ -import { Redirect, useParams } from 'react-router-dom'; -import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements'; +import { useEffect, useRef, useState } from 'react'; +import { useHistory, useParams } from 'react-router-dom'; import { useFetchLesson } from '@educt/hooks/queries/lesson/useFetchLesson'; -import LoadingPage from '@educt/components/LoadingPage'; -import { MdOutlineCircle, MdOutlineFilePresent, MdSkipNext, MdSkipPrevious } from 'react-icons/md'; -import { Helmet } from 'react-helmet'; import { useFetchCourseLessons } from '@educt/hooks/queries/course/useFetchCourseLessons'; +import { useFetchLessonProgress } from '@educt/hooks/queries/lesson/useFetchLessonProgress'; + +/** + * Services + */ import { LessonServiceInstance } from '@educt/services'; /** @@ -43,6 +49,7 @@ const LessonPage: React.FC = () => { const { id } = useParams<{ id: string }>(); const { error, data: lesson, isLoading } = useFetchLesson(id); const { fetchCourseLessons, data: lessons } = useFetchCourseLessons(); + const { fetchLessonProgress } = useFetchLessonProgress(); const history = useHistory(); const [nextLesson, setNextLesson] = useState(null); const [prevLesson, setPrevLesson] = useState(null); @@ -66,7 +73,10 @@ const LessonPage: React.FC = () => { useEffect(() => { if (lesson !== null) { - fetchCourseLessons(lesson.course_id); + (async () => { + await fetchCourseLessons(lesson.course_id); + await fetchLessonProgress(lesson.id); + })(); } }, [lesson]); @@ -154,15 +164,20 @@ const LessonPage: React.FC = () => { ref={lesson.id === id ? currentLessonRef : undefined} as='div' display={'flex'} + minH='100' key={lesson.id} bg={lesson.id === id ? bg : ''} p='4' alignItems={'center'} - _hover={{ - bg, - }} + _hover={{ bg }} > - + Lesson {i + 1} @@ -204,7 +219,9 @@ const LessonPage: React.FC = () => { > {material.client_name}
- {helpres.transformBytes(material.size)} + + {helpres.transformBytes(material.size)} +
))} diff --git a/src/services/LessonService.ts b/src/services/LessonService.ts index 7073638..22cb726 100644 --- a/src/services/LessonService.ts +++ b/src/services/LessonService.ts @@ -1,6 +1,7 @@ import { AxiosInstance } from 'axios'; import { IApiRespose, ILesson } from '@educt/interfaces'; import { ApiServiceInstance } from './ApiService'; +import type { LessonProgress } from '@educt/types'; class LessonService { public api: AxiosInstance; @@ -31,6 +32,17 @@ class LessonService { return result.data; } + /** + * Get progress about lesson + * + * @param id Lesson id + * @returns Progress info + */ + public async fetchProgress(id: string): Promise> { + const result = await this.api.get(`/v1/lessons/${id}/progress`); + return result.data; + } + /** * Delete lesson by id * diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 9ebd833..e626fcb 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -34,6 +34,12 @@ export type ColorType = { hex: string; }; +export type LessonProgress = { + progress: { + is_watched: boolean; + } | null; +}; + export type ConfirmEmailContainerDataType = { newEmail: string; expired_seconds: number; From b3455fd0da3c9244fe90ae588c470b214f21398d Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Sun, 2 Jan 2022 22:28:04 +0300 Subject: [PATCH 5/5] feat: showing lesson video --- constants.ts | 2 +- package-lock.json | 22 +++++ package.json | 1 + .../Forms/LessonForm/CreateLessonForm.tsx | 25 ++++++ .../Forms/LessonForm/LessonForm.tsx | 82 +++++++++++++++++++ .../Forms/LessonForm/LessonForm.validator.ts | 11 +++ src/components/Forms/LessonForm/index.ts | 3 + src/components/PageElements/index.tsx | 10 ++- src/interfaces/index.ts | 4 +- .../courses/create-lesson/create-lesson.tsx | 12 ++- src/pages/courses/create/create.tsx | 21 +++-- .../edit/components/LessonList/LessonList.tsx | 2 +- src/pages/courses/edit/edit.tsx | 13 ++- src/pages/lesson/lesson.tsx | 60 +++++++++++--- src/types/index.d.ts | 11 +++ vite.config.ts | 4 + 16 files changed, 245 insertions(+), 38 deletions(-) create mode 100644 src/components/Forms/LessonForm/CreateLessonForm.tsx create mode 100644 src/components/Forms/LessonForm/LessonForm.tsx create mode 100644 src/components/Forms/LessonForm/LessonForm.validator.ts create mode 100644 src/components/Forms/LessonForm/index.ts diff --git a/constants.ts b/constants.ts index 55cc2ac..a6d4512 100644 --- a/constants.ts +++ b/constants.ts @@ -1 +1 @@ -export const BACKEND_URL: string = 'http://localhost:3333'; +export const BACKEND_URL = 'http://localhost:3333'; diff --git a/package-lock.json b/package-lock.json index ff14158..2bcda27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1741,6 +1741,11 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -2849,6 +2854,11 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" }, + "load-script": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz", + "integrity": "sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ=" + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -3324,6 +3334,18 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-player": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-player/-/react-player-2.9.0.tgz", + "integrity": "sha512-jNUkTfMmUhwPPAktAdIqiBcVUKsFKrVGH6Ocutj6535CNfM91yrvWxHg6fvIX8Y/fjYUPoejddwh7qboNV9vGA==", + "requires": { + "deepmerge": "^4.0.0", + "load-script": "^1.0.0", + "memoize-one": "^5.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.0.1" + } + }, "react-redux": { "version": "7.2.6", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz", diff --git a/package.json b/package.json index f735c49..fad5354 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "react-helmet": "^6.1.0", "react-hook-form": "^7.12.2", "react-icons": "^4.3.1", + "react-player": "^2.9.0", "react-router-dom": "^5.2.0", "react-spinners": "^0.11.0", "react-virtuoso": "^2.2.8", diff --git a/src/components/Forms/LessonForm/CreateLessonForm.tsx b/src/components/Forms/LessonForm/CreateLessonForm.tsx new file mode 100644 index 0000000..3165be4 --- /dev/null +++ b/src/components/Forms/LessonForm/CreateLessonForm.tsx @@ -0,0 +1,25 @@ +import { yupResolver } from '@hookform/resolvers/yup'; +import React from 'react'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { LessonForm } from '.'; +import { InputFields } from './LessonForm'; +import LessonFormSchema from './LessonForm.validator'; + +const CreateLessonForm: React.FC = () => { + const form = useForm({ resolver: yupResolver(LessonFormSchema) }); + + const onSubmit: SubmitHandler = async data => { + console.log('creating...'); + }; + + return ( + + ); +}; + +export default CreateLessonForm; diff --git a/src/components/Forms/LessonForm/LessonForm.tsx b/src/components/Forms/LessonForm/LessonForm.tsx new file mode 100644 index 0000000..3f14cb1 --- /dev/null +++ b/src/components/Forms/LessonForm/LessonForm.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { + FormControl, + FormHelperText, + FormLabel, + Text, + Input, + InputGroup, + Stack, + Box, + Button, + Textarea, + Flex, +} from '@chakra-ui/react'; +import { UseFormReturn } from 'react-hook-form'; + +export type InputFields = { + title: string; + description: string; +}; + +export type LessonFormPropsType = { + onSubmit: () => Promise; + buttonLabel: string; + isLoading: boolean; + reactHookForm: UseFormReturn; +}; + +const LessonForm: React.FC = ({ onSubmit, buttonLabel, isLoading, reactHookForm }) => { + const { + control, + register, + watch, + formState: { errors, isDirty }, + } = reactHookForm; + + return ( +
+ + + Title + + + + e.g Variables in Javascript + + {errors.title?.message} + + + + + Description +