diff --git a/src/pages/courses/components/CourseList/CourseItem.tsx b/src/pages/courses/components/CourseList/CourseItem.tsx index e9f4570..db9abfb 100644 --- a/src/pages/courses/components/CourseList/CourseItem.tsx +++ b/src/pages/courses/components/CourseList/CourseItem.tsx @@ -16,7 +16,7 @@ import { import { Link as ReactRouterLink } from 'react-router-dom'; import { Icon } from '@chakra-ui/react'; import { MdNote, MdMoreHoriz, MdGroup, MdThumbUp, MdVideoLibrary } from 'react-icons/md'; -import { DeleteIcon, EditIcon } from '@chakra-ui/icons'; +import { CheckIcon, DeleteIcon, EditIcon } from '@chakra-ui/icons'; /** * Types @@ -29,13 +29,16 @@ import { ICourse } from '@educt/interfaces'; import { useColorMode } from '@chakra-ui/color-mode'; import { useRootStore } from '@educt/hooks/useRootStore'; import CourseStatusBadge from '@educt/components/CourseStatusBadge'; +import { CourseStatusEnum } from '@educt/enums'; +import { observer } from 'mobx-react'; type CourseItemPropsType = { course: Omit; onDelete: (course: Pick) => void; + onSetStatus: (id: string, status: CourseStatusEnum) => Promise; }; -const CourseItem: React.FC = ({ course, onDelete }) => { +const CourseItem: React.FC = ({ course, onDelete, onSetStatus }) => { const { userStore: { me }, } = useRootStore(); @@ -49,25 +52,38 @@ const CourseItem: React.FC = ({ course, onDelete }) => { - } - _hover={{ backgroundColor: colorMode === 'dark' ? 'gray.600' : 'gray.200' }} - _active={{ backgroundColor: colorMode === 'dark' ? 'gray.600' : 'gray.200' }} - /> - - }>Edit course - }>Mark as Draft - onDelete({ id: course.id, title: course.title })} - icon={} - color='red.500' - > - Delete - - + {({ isOpen, onClose }) => ( + <> + } + _hover={{ backgroundColor: colorMode === 'dark' ? 'gray.600' : 'gray.200' }} + _active={{ backgroundColor: colorMode === 'dark' ? 'gray.600' : 'gray.200' }} + /> + + }>Edit course + {course.status === CourseStatusEnum.DRAFT && ( + onSetStatus(course.id, CourseStatusEnum.PUBLISHED)} icon={}> + Publish + + )} + {course.status === CourseStatusEnum.PUBLISHED && ( + onSetStatus(course.id, CourseStatusEnum.DRAFT)} icon={}> + Mark as Draft + + )} + onDelete({ id: course.id, title: course.title })} + icon={} + color='red.500' + > + Delete + + + + )} @@ -131,4 +147,4 @@ const CourseItem: React.FC = ({ course, onDelete }) => { ); }; -export default CourseItem; +export default observer(CourseItem); diff --git a/src/pages/courses/components/CourseList/CourseList.tsx b/src/pages/courses/components/CourseList/CourseList.tsx index 297eabe..7682f85 100644 --- a/src/pages/courses/components/CourseList/CourseList.tsx +++ b/src/pages/courses/components/CourseList/CourseList.tsx @@ -18,6 +18,8 @@ import DeleteCourseDialog from '../DeleteCourseDialog'; */ import { useRootStore } from '@educt/hooks/useRootStore'; import { useDisclosure } from '@chakra-ui/hooks'; +import { useToast } from '@chakra-ui/toast'; +import { useErrorHandler } from 'react-error-boundary'; /** * Contexts @@ -37,6 +39,8 @@ const CourseList: React.FC = ({ courses, isLoading }) => { } = useRootStore(); const { selectedCategory, courseStatus, deletingCourse, setDeletingCourse } = useContext(CoursesPageContext); const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure(); + const handleError = useErrorHandler(); + const toast = useToast(); useEffect(() => { if (me !== null) { @@ -47,11 +51,31 @@ const CourseList: React.FC = ({ courses, isLoading }) => { } }, [courseStore, selectedCategory, courseStatus, me]); + /** + * Handle delete course + * Set deleting course to context and open dialog + */ const onDeleteCourse = (course: Pick) => { setDeletingCourse(course); onOpenDeleteDialog(); }; + /** + * Handle update course status + */ + const onSetStatus = async (id: string, status: CourseStatusEnum) => { + try { + await courseStore.setCourseStatus(id, status); + toast({ title: 'Status updated.', status: 'info' }); + } catch (error: any) { + if (error.response) { + toast({ title: error.message, status: 'error' }); + } else { + handleError(error); + } + } + }; + if (isLoading || courses === null) { return ; } @@ -73,7 +97,7 @@ const CourseList: React.FC = ({ courses, isLoading }) => { }} > {courses.map(course => ( - + ))} ) : ( diff --git a/src/pages/courses/components/CourseStatusTabs/CourseStatusTabs.tsx b/src/pages/courses/components/CourseStatusTabs/CourseStatusTabs.tsx index b4a96a0..3b53bc0 100644 --- a/src/pages/courses/components/CourseStatusTabs/CourseStatusTabs.tsx +++ b/src/pages/courses/components/CourseStatusTabs/CourseStatusTabs.tsx @@ -1,8 +1,16 @@ import React, { useContext } from 'react'; import { Tabs, TabList, Tab } from '@chakra-ui/react'; -import { CoursesPageContext } from '@educt/contexts'; + +/** + * Types + */ import { CourseStatusEnum } from '@educt/enums'; +/** + * Contexts + */ +import { CoursesPageContext } from '@educt/contexts'; + type CourseStatusTabsProps = {}; const CourseStatusTabs: React.FC = () => { @@ -13,16 +21,11 @@ const CourseStatusTabs: React.FC = () => { handleChangeStatus(undefined)}>All - {Object.values(CourseStatusEnum).map(status => { - return ( - handleChangeStatus(status)}>{`${status.charAt(0).toUpperCase()}${status - .substr(1) - .toLowerCase()}`} - ); - })} + handleChangeStatus(CourseStatusEnum.PUBLISHED)}>Published + handleChangeStatus(CourseStatusEnum.DRAFT)}>Unpublished Drafts ); }; -export default React.memo(CourseStatusTabs); +export default CourseStatusTabs; diff --git a/src/pages/courses/components/DeleteCourseDialog/DeleteCourseDialog.tsx b/src/pages/courses/components/DeleteCourseDialog/DeleteCourseDialog.tsx index dbacf0a..74bd58c 100644 --- a/src/pages/courses/components/DeleteCourseDialog/DeleteCourseDialog.tsx +++ b/src/pages/courses/components/DeleteCourseDialog/DeleteCourseDialog.tsx @@ -36,6 +36,11 @@ const DeleteCourseDialog: React.FC = ({ onClose, isOpen const handleError = useErrorHandler(); const toast = useToast(); + /** + * Delete course handler + * + * @param id Course id + */ const onDelete = async (id: string) => { try { setIsLoading(true); diff --git a/src/pages/courses/courses.tsx b/src/pages/courses/courses.tsx index 6d30479..33b5f93 100644 --- a/src/pages/courses/courses.tsx +++ b/src/pages/courses/courses.tsx @@ -1,15 +1,19 @@ -import React, { useEffect } from 'react'; -import { observer } from 'mobx-react-lite'; +import React from 'react'; +import { observer } from 'mobx-react'; import { Flex, Box, Heading, Text, Button } from '@chakra-ui/react'; - -import { IPageProps } from '@educt/interfaces'; import { AddIcon } from '@chakra-ui/icons'; +/** + * Types + */ +import { IPageProps } from '@educt/interfaces'; + /** * Components */ import CourseList from './components/CourseList'; import CategoryList from './components/CategoryList'; +import CourseStatusTabs from './components/CourseStatusTabs'; import LoadingPage from '@educt/components/LoadingPage'; /** @@ -17,7 +21,6 @@ import LoadingPage from '@educt/components/LoadingPage'; */ import { useRootStore } from '@educt/hooks/useRootStore'; import { CoursesPageContextProvider } from '@educt/providers'; -import CourseStatusTabs from './components/CourseStatusTabs'; /** * Courses page @@ -29,10 +32,6 @@ const CoursesPage: React.FC = ({ title }) => { courseStore, } = useRootStore(); - useEffect(() => { - console.log('upadate'); - }); - if (me === null) return ; return ( diff --git a/src/services/CourseService.ts b/src/services/CourseService.ts index f6053e0..415851e 100644 --- a/src/services/CourseService.ts +++ b/src/services/CourseService.ts @@ -1,6 +1,7 @@ import { AxiosInstance } from 'axios'; import { IApiRespose, ICourse } from '@educt/interfaces'; import { FetchCoursesParams } from '@educt/types'; +import { CourseStatusEnum } from '@educt/enums'; export class CourseService { public api: AxiosInstance; @@ -26,6 +27,20 @@ export class CourseService { return result.data; } + /** + * Update status of course by id + * + * @param id Course id + * @param status Course status + * @returns Empty data + */ + public async setStatus(id: string, status: CourseStatusEnum): Promise> { + const result = await this.api.post(`/v1/courses/${id}/set-status`, { + status, + }); + return result.data; + } + /** * Delete course by id * diff --git a/src/stores/CourseStore.ts b/src/stores/CourseStore.ts index 7887c76..e0b5295 100644 --- a/src/stores/CourseStore.ts +++ b/src/stores/CourseStore.ts @@ -16,6 +16,7 @@ import { CourseService } from '@educt/services/CourseService'; * Stores */ import RootStore from './RootStore'; +import { CourseStatusEnum } from '@educt/enums'; export default class CourseStore { public root: RootStore; @@ -41,6 +42,9 @@ export default class CourseStore { this.setLoading(true); const result = await this.courseService.fetchAll(params); + /** + * Load courses in store + */ runInAction(() => { this.courses = result.data; }); @@ -56,8 +60,10 @@ export default class CourseStore { public async deleteCourse(id: string) { try { const result = await this.courseService.delete(id); - console.log(`[${this.constructor.name}]: ${result.message}`, result); + /** + * Remove deleted course form store + */ runInAction(() => { if (this.courses !== null) { this.courses = this.courses.filter(course => course.id !== id); @@ -69,4 +75,27 @@ export default class CourseStore { throw error; } } + + public async setCourseStatus(id: string, status: CourseStatusEnum) { + try { + const result = await this.courseService.setStatus(id, status); + + /** + * Update status in store + */ + runInAction(() => { + if (this.courses !== null) { + const courseIndex = this.courses.findIndex(course => course.id === id); + + if (courseIndex !== -1) { + this.courses[courseIndex].status = status; + } + } + }); + + return result; + } catch (error: any) { + throw error; + } + } } diff --git a/vite.config.ts b/vite.config.ts index b5f8321..a407df2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,7 +5,7 @@ import { visualizer } from 'rollup-plugin-visualizer'; import { dependencies } from './package.json'; function renderChunks(deps: Record) { - let chunks = {}; + const chunks = {}; Object.keys(deps).forEach(key => { if (['@chakra-ui/react', '@chakra-ui/icons', 'framer-motion'].includes(key)) chunks[key] = [key]; return;