From 5b9974040ff942be11e3dacae5260eff2d060cba Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Wed, 13 Oct 2021 23:30:11 +0300 Subject: [PATCH] feat: deleting course dialog --- src/contexts/index.ts | 2 + .../components/CourseList/CourseItem.tsx | 9 +- .../components/CourseList/CourseList.tsx | 13 ++- .../DeleteCourseDialog/DeleteCourseDialog.tsx | 96 +++++++++++++++++++ .../components/DeleteCourseDialog/index.ts | 3 + src/providers/index.tsx | 14 ++- src/services/CourseService.ts | 11 +++ src/stores/CourseStore.ts | 17 ++++ src/types/index.d.ts | 10 +- 9 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 src/pages/courses/components/DeleteCourseDialog/DeleteCourseDialog.tsx create mode 100644 src/pages/courses/components/DeleteCourseDialog/index.ts diff --git a/src/contexts/index.ts b/src/contexts/index.ts index 60945d9..4fd3e58 100644 --- a/src/contexts/index.ts +++ b/src/contexts/index.ts @@ -29,4 +29,6 @@ export const CoursesPageContext = createContext({ setSelectedCategory: () => {}, courseStatus: undefined, setCourseStatus: () => {}, + deletingCourse: undefined, + setDeletingCourse: () => {}, }); diff --git a/src/pages/courses/components/CourseList/CourseItem.tsx b/src/pages/courses/components/CourseList/CourseItem.tsx index dab4ad1..e2301b5 100644 --- a/src/pages/courses/components/CourseList/CourseItem.tsx +++ b/src/pages/courses/components/CourseList/CourseItem.tsx @@ -32,9 +32,10 @@ import CourseStatusBadge from 'components/CourseStatusBadge'; type CourseItemPropsType = { course: Omit; + onDelete: (course: Pick) => void; }; -const CourseItem: React.FC = ({ course }) => { +const CourseItem: React.FC = ({ course, onDelete }) => { const { userStore: { me }, } = useRootStore(); @@ -59,7 +60,11 @@ const CourseItem: React.FC = ({ course }) => { }>Edit course }>Mark as Draft - } color='red.500'> + onDelete({ id: course.id, title: course.title })} + icon={} + color='red.500' + > Delete diff --git a/src/pages/courses/components/CourseList/CourseList.tsx b/src/pages/courses/components/CourseList/CourseList.tsx index 05010c1..cfb66f8 100644 --- a/src/pages/courses/components/CourseList/CourseList.tsx +++ b/src/pages/courses/components/CourseList/CourseList.tsx @@ -11,11 +11,13 @@ import { ICourse } from 'interfaces'; */ import CourseItem from './CourseItem'; import CourseListLoading from './CourseListLoading'; +import DeleteCourseDialog from '../DeleteCourseDialog'; /** * Hooks */ import { useRootStore } from 'hooks/useRootStore'; +import { useDisclosure } from '@chakra-ui/hooks'; /** * Contexts @@ -33,7 +35,8 @@ const CourseList: React.FC = ({ courses, isLoading }) => { userStore: { me }, courseStore, } = useRootStore(); - const { selectedCategory, courseStatus } = useContext(CoursesPageContext); + const { selectedCategory, courseStatus, deletingCourse, setDeletingCourse } = useContext(CoursesPageContext); + const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure(); useEffect(() => { if (me !== null) { @@ -44,12 +47,18 @@ const CourseList: React.FC = ({ courses, isLoading }) => { } }, [courseStore, selectedCategory, courseStatus, me]); + const onDeleteCourse = (course: Pick) => { + setDeletingCourse(course); + onOpenDeleteDialog(); + }; + if (isLoading || courses === null) { return ; } return ( <> + {deletingCourse && } {courses.length !== 0 ? ( = ({ courses, isLoading }) => { }} > {courses.map(course => ( - + ))} ) : ( diff --git a/src/pages/courses/components/DeleteCourseDialog/DeleteCourseDialog.tsx b/src/pages/courses/components/DeleteCourseDialog/DeleteCourseDialog.tsx new file mode 100644 index 0000000..9fa181c --- /dev/null +++ b/src/pages/courses/components/DeleteCourseDialog/DeleteCourseDialog.tsx @@ -0,0 +1,96 @@ +import React, { useContext, useRef, useState } from 'react'; +import { + AlertDialog, + AlertDialogBody, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogContent, + AlertDialogOverlay, + Button, + useToast, +} from '@chakra-ui/react'; + +/** + * Hooks + */ +import useIsMountedRef from 'hooks/useIsMountedRef'; +import { useErrorHandler } from 'react-error-boundary'; +import { CoursesPageContext } from 'contexts'; +import { useRootStore } from 'hooks/useRootStore'; + +type DeleteCourseDialogProps = { + onClose: () => void; + isOpen: boolean; +}; + +const DeleteCourseDialog: React.FC = ({ onClose, isOpen }) => { + const { courseStore } = useRootStore(); + const { deletingCourse, setDeletingCourse } = useContext(CoursesPageContext); + const isMountedRef = useIsMountedRef(); + const [isLoading, setIsLoading] = useState(false); + const cancelRef = useRef(null); + const handleError = useErrorHandler(); + const toast = useToast(); + + const onDelete = async (id: string) => { + try { + setIsLoading(true); + await courseStore.deleteCourse(id); + toast({ title: 'Course deleted.', status: 'info' }); + onClose(); + setDeletingCourse(undefined); + } catch (error: any) { + if (error.response) { + toast({ title: error.message, status: 'error' }); + } else { + handleError(error); + } + } finally { + if (isMountedRef.current) { + setIsLoading(false); + } + } + }; + + if (deletingCourse === undefined) return null; + + return ( + <> + + + + + Confirm deleting + + + The course with title "{deletingCourse.title}" will be removed from the system. After deleting a course, + the lessons in it will also be deleted. + + + + + + + + + + ); +}; + +export default DeleteCourseDialog; diff --git a/src/pages/courses/components/DeleteCourseDialog/index.ts b/src/pages/courses/components/DeleteCourseDialog/index.ts new file mode 100644 index 0000000..331d590 --- /dev/null +++ b/src/pages/courses/components/DeleteCourseDialog/index.ts @@ -0,0 +1,3 @@ +import DeleteCourseDialog from './DeleteCourseDialog'; + +export default DeleteCourseDialog; diff --git a/src/providers/index.tsx b/src/providers/index.tsx index 5fa7b4d..df767be 100644 --- a/src/providers/index.tsx +++ b/src/providers/index.tsx @@ -2,7 +2,7 @@ import React, { useState, ReactNode } from 'react'; import { CoursesPageContext, ProfilePageContext, StoreContext, UsersPageContext } from 'contexts'; import { ProfilePageDataType, ProfilePageStatusType, SearchingRoleStateType } from 'types'; import RootStore from 'stores/RootStore'; -import { ICategory, IUser } from 'interfaces'; +import { ICategory, ICourse, IUser } from 'interfaces'; import { CourseStatusEnum } from 'enums'; /** @@ -64,9 +64,19 @@ export const UsersPageContextProvider: React.FC = ({ children }) => { export const CoursesPageContextProvider: React.FC = ({ children }) => { const [selectedCategory, setSelectedCategory] = useState(undefined); const [courseStatus, setCourseStatus] = useState(undefined); + const [deletingCourse, setDeletingCourse] = useState | undefined>(undefined); return ( - + {children} ); diff --git a/src/services/CourseService.ts b/src/services/CourseService.ts index 47093e7..42e8148 100644 --- a/src/services/CourseService.ts +++ b/src/services/CourseService.ts @@ -25,4 +25,15 @@ export class CourseService { }); return result.data; } + + /** + * Delete course by id + * + * @param id Course id + * @returns Deleted course + */ + public async delete(id: string): Promise>> { + const result = await this.api.delete(`/v1/courses/${id}`); + return result.data; + } } diff --git a/src/stores/CourseStore.ts b/src/stores/CourseStore.ts index 6183e7e..be04cf7 100644 --- a/src/stores/CourseStore.ts +++ b/src/stores/CourseStore.ts @@ -53,4 +53,21 @@ export default class CourseStore { this.setLoading(false); } } + + public async deleteCourse(id: string) { + try { + const result = await this.courseService.delete(id); + console.log(`[${this.constructor.name}]: ${result.message}`, result); + + runInAction(() => { + if (this.courses !== null) { + this.courses = this.courses.filter(course => course.id !== id); + } + }); + + return result; + } catch (error: any) { + throw error; + } + } } diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 91e80cd..59edaca 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,5 +1,5 @@ import { CourseStatusEnum, UserRoleEnum } from 'enums'; -import { ICategory, IUser } from 'interfaces'; +import { ICategory, ICourse, IUser } from 'interfaces'; import React from 'react'; import { IconType } from 'react-icons/lib'; @@ -68,7 +68,7 @@ export type UsersPageContextType = { setEditingUser: React.Dispatch>; /** - * Set deliting user state when press on delete button + * Set deleting user state when press on delete button */ deletingUser: IUser | undefined; setDeletingUser: React.Dispatch>; @@ -86,6 +86,12 @@ export type CoursesPageContextType = { */ courseStatus: CourseStatusEnum | undefined; setCourseStatus: React.Dispatch>; + + /** + * Set deleting course state when press on delete button + */ + deletingCourse: Pick | undefined; + setDeletingCourse: React.Dispatch | undefined>>; }; export type FetchCoursesParams = { /**