mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 18:29:15 +03:00
refactor: refactored course & lesson query hooks
This commit is contained in:
parent
2fa555b8be
commit
dd86228cab
6
package-lock.json
generated
6
package-lock.json
generated
@ -2552,9 +2552,9 @@
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
|
||||
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz",
|
||||
"integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==",
|
||||
"dev": true
|
||||
},
|
||||
"use-callback-ref": {
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
"@types/validator": "^13.6.3",
|
||||
"@vitejs/plugin-react": "^1.0.0",
|
||||
"rollup-plugin-visualizer": "^5.5.2",
|
||||
"typescript": "^4.3.2",
|
||||
"typescript": "^4.5.2",
|
||||
"vite": "^2.6.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,17 +17,9 @@ import CourseForm from './CourseForm';
|
||||
/**
|
||||
* Hooks
|
||||
*/
|
||||
import { useState } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useToast } from '@chakra-ui/toast';
|
||||
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
|
||||
/**
|
||||
* Services
|
||||
*/
|
||||
import { CourseServiceInstance } from '@educt/services';
|
||||
import { useCreateCourse } from '@educt/hooks/queries';
|
||||
|
||||
/**
|
||||
* Schema
|
||||
@ -37,21 +29,16 @@ import CourseFormSchema from './CourseForm.validator';
|
||||
type CreateFormCoursePropsType = {};
|
||||
|
||||
const CreateFormCourse: React.FC<CreateFormCoursePropsType> = () => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const form = useForm<InputFields>({ resolver: yupResolver(CourseFormSchema) });
|
||||
const isMountedRef = useIsMountedRef();
|
||||
const { createCourse, isLoading } = useCreateCourse();
|
||||
const history = useHistory();
|
||||
const toast = useToast();
|
||||
const handleError = useErrorHandler();
|
||||
|
||||
/**
|
||||
* Submit handler
|
||||
*/
|
||||
// TODO: move request to useCreateCourse hook
|
||||
const onSubmit: SubmitHandler<InputFields> = async data => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const course = await CourseServiceInstance.create({
|
||||
const course = await createCourse({
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
teacher_id: data.teacher_id,
|
||||
@ -59,22 +46,10 @@ const CreateFormCourse: React.FC<CreateFormCoursePropsType> = () => {
|
||||
image: data.image,
|
||||
status: CourseStatusEnum.DRAFT,
|
||||
});
|
||||
toast({ title: `Course successfully created.`, status: 'success' });
|
||||
history.push(`/courses/edit/${course.data.id}`);
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
if (error.response.status === 422) {
|
||||
toast({ title: `${error.response.data.errors[0].message}`, status: 'error' });
|
||||
} else {
|
||||
toast({ title: `${error.message}`, status: 'error' });
|
||||
}
|
||||
} else {
|
||||
handleError(error);
|
||||
}
|
||||
} finally {
|
||||
if (isMountedRef.current) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
if (course) history.push(`/courses/edit/${course.id}`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -16,21 +16,14 @@ import CourseForm from './CourseForm';
|
||||
/**
|
||||
* Hooks
|
||||
*/
|
||||
import { useState } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import { useToast } from '@chakra-ui/toast';
|
||||
|
||||
/**
|
||||
* Services
|
||||
*/
|
||||
import { CourseServiceInstance } from '@educt/services';
|
||||
|
||||
/**
|
||||
* Schema
|
||||
*/
|
||||
import CourseFormSchema from './CourseForm.validator';
|
||||
import { useUpdateCourse } from '@educt/hooks/queries';
|
||||
|
||||
type EditFormCoursePropsType = {
|
||||
defaultValues?:
|
||||
@ -45,7 +38,6 @@ type EditFormCoursePropsType = {
|
||||
};
|
||||
|
||||
const EditFormCourse: React.FC<EditFormCoursePropsType> = ({ defaultValues }) => {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const form = useForm<InputFields>({
|
||||
resolver: yupResolver(CourseFormSchema),
|
||||
defaultValues: {
|
||||
@ -56,44 +48,30 @@ const EditFormCourse: React.FC<EditFormCoursePropsType> = ({ defaultValues }) =>
|
||||
image: undefined,
|
||||
},
|
||||
});
|
||||
const toast = useToast();
|
||||
const handleError = useErrorHandler();
|
||||
const { updateCourse, isLoading } = useUpdateCourse();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
/**
|
||||
* Submit handler
|
||||
*/
|
||||
// TODO move request to useUpdateCourse hook
|
||||
const onSubmit: SubmitHandler<InputFields> = async data => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const course = await CourseServiceInstance.update(id, {
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
teacher_id: data.teacher_id,
|
||||
category_id: data.category_id,
|
||||
image: data.image,
|
||||
});
|
||||
toast({ title: `Course updated.`, status: 'info' });
|
||||
const updated = await updateCourse(id, data);
|
||||
|
||||
/**
|
||||
* Update fields with new values
|
||||
*/
|
||||
form.reset({
|
||||
title: course.data.title,
|
||||
description: course.data.description,
|
||||
teacher_id: course.data.teacher.id,
|
||||
category_id: course.data.category.id,
|
||||
image: undefined,
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
toast({ title: `${error.message}`, status: 'error' });
|
||||
} else {
|
||||
handleError(error);
|
||||
if (updated) {
|
||||
/**
|
||||
* Update fields with new values
|
||||
*/
|
||||
form.reset({
|
||||
title: updated.title,
|
||||
description: updated.description,
|
||||
teacher_id: updated.teacher.id,
|
||||
category_id: updated.category.id,
|
||||
image: undefined,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
49
src/hooks/queries/course/useCreateCourse.ts
Normal file
49
src/hooks/queries/course/useCreateCourse.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { useToast } from '@chakra-ui/toast';
|
||||
import { CourseStatusEnum } from '@educt/enums';
|
||||
import { ICourse } from '@educt/interfaces';
|
||||
import { CourseServiceInstance } from '@educt/services';
|
||||
import { CreateCourseParamsType } from '@educt/types';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import useAsync from '../../useAsync';
|
||||
|
||||
type CreatedCourseDataType =
|
||||
| Pick<ICourse, 'id' | 'title' | 'description' | 'status' | 'created_at' | 'updated_at'>
|
||||
| undefined;
|
||||
|
||||
const useCreateCourse = () => {
|
||||
const toast = useToast();
|
||||
const handleError = useErrorHandler();
|
||||
|
||||
const createCourse = async (data: CreateCourseParamsType) => {
|
||||
try {
|
||||
const result = await CourseServiceInstance.create({
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
teacher_id: data.teacher_id,
|
||||
category_id: data.category_id,
|
||||
image: data.image,
|
||||
status: CourseStatusEnum.DRAFT,
|
||||
});
|
||||
toast({ title: `Course successfully created.`, status: 'success' });
|
||||
return result.data;
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
if (error.response.status === 422) {
|
||||
toast({ title: `${error.response.data.errors[0].message}`, status: 'error' });
|
||||
} else {
|
||||
toast({ title: `${error.message}`, status: 'error' });
|
||||
}
|
||||
} else {
|
||||
handleError(error);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
const { execute, ...state } = useAsync<CreatedCourseDataType, Parameters<typeof createCourse>>(createCourse);
|
||||
|
||||
return { createCourse: execute, ...state };
|
||||
};
|
||||
|
||||
export { useCreateCourse };
|
||||
35
src/hooks/queries/course/useDeleteCourse.ts
Normal file
35
src/hooks/queries/course/useDeleteCourse.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { useToast } from '@chakra-ui/toast';
|
||||
import { ICourse } from '@educt/interfaces';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import useAsync from '@educt/hooks/useAsync';
|
||||
|
||||
type DeletedCourseDataType = Omit<ICourse, 'teacher' | 'students' | 'lessons'> | undefined;
|
||||
|
||||
const useDeleteCourse = () => {
|
||||
const { courseStore } = useRootStore();
|
||||
const toast = useToast();
|
||||
const handleError = useErrorHandler();
|
||||
|
||||
const deleteCourse = async (id: string) => {
|
||||
try {
|
||||
const result = await courseStore.deleteCourse(id);
|
||||
toast({ title: 'Course deleted.', status: 'info' });
|
||||
return result.data;
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
toast({ title: error.message, status: 'error' });
|
||||
} else {
|
||||
handleError(error);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
const { execute, ...state } = useAsync<DeletedCourseDataType, Parameters<typeof deleteCourse>>(deleteCourse);
|
||||
|
||||
return { deleteCourse: execute, ...state };
|
||||
};
|
||||
|
||||
export { useDeleteCourse };
|
||||
41
src/hooks/queries/course/useUpdateCourse.ts
Normal file
41
src/hooks/queries/course/useUpdateCourse.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { useToast } from '@chakra-ui/toast';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import { CourseServiceInstance } from '@educt/services';
|
||||
import { UpdateCourseParamsType } from '@educt/types';
|
||||
import useAsync from '../../useAsync';
|
||||
import { ICourse } from '@educt/interfaces';
|
||||
|
||||
type UpdatedCourseDataType = Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'> | undefined;
|
||||
|
||||
const useUpdateCourse = () => {
|
||||
const toast = useToast();
|
||||
const handleError = useErrorHandler();
|
||||
|
||||
const updateCourse = async (id: string, data: UpdateCourseParamsType) => {
|
||||
try {
|
||||
const result = await CourseServiceInstance.update(id, {
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
teacher_id: data.teacher_id,
|
||||
category_id: data.category_id,
|
||||
image: data.image,
|
||||
});
|
||||
toast({ title: `Course updated.`, status: 'info' });
|
||||
return result.data;
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
toast({ title: `${error.message}`, status: 'error' });
|
||||
} else {
|
||||
handleError(error);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
const { execute, ...state } = useAsync<UpdatedCourseDataType, Parameters<typeof updateCourse>>(updateCourse);
|
||||
|
||||
return { updateCourse: execute, ...state };
|
||||
};
|
||||
|
||||
export { useUpdateCourse };
|
||||
@ -1,3 +1,6 @@
|
||||
import { useDeleteLesson } from './useDeleteLesson';
|
||||
import { useDeleteLesson } from './lesson/useDeleteLesson';
|
||||
|
||||
export { useDeleteLesson };
|
||||
import { useCreateCourse } from './course/useCreateCourse';
|
||||
import { useUpdateCourse } from './course/useUpdateCourse';
|
||||
|
||||
export { useCreateCourse, useUpdateCourse, useDeleteLesson };
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { useToast } from '@chakra-ui/toast';
|
||||
import { ILesson } from '@educt/interfaces';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import useAsync from '../useAsync';
|
||||
import { useRootStore } from '../useRootStore';
|
||||
import useAsync from '../../useAsync';
|
||||
import { useRootStore } from '../../useRootStore';
|
||||
|
||||
type CreatedLessonDataType = ILesson | undefined;
|
||||
|
||||
const useDeleteLesson = () => {
|
||||
const {
|
||||
@ -22,10 +24,12 @@ const useDeleteLesson = () => {
|
||||
} else {
|
||||
handleError(error);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
const { execute, ...state } = useAsync<ILesson | undefined, Parameters<typeof deleteLesson>>(deleteLesson);
|
||||
const { execute, ...state } = useAsync<CreatedLessonDataType, Parameters<typeof deleteLesson>>(deleteLesson);
|
||||
|
||||
return { deleteLesson: execute, ...state };
|
||||
};
|
||||
@ -1,14 +1,9 @@
|
||||
import React, { useContext, useRef, useState } from 'react';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogBody,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogContent,
|
||||
AlertDialogOverlay,
|
||||
Button,
|
||||
useToast,
|
||||
} from '@chakra-ui/react';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
import { ICourse } from '@educt/interfaces';
|
||||
|
||||
/**
|
||||
* Components
|
||||
@ -23,9 +18,7 @@ import { CoursesPageContext } from '@educt/contexts';
|
||||
/**
|
||||
* Hooks
|
||||
*/
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import { ICourse } from '@educt/interfaces';
|
||||
import { useDeleteCourse } from '@educt/hooks/queries/course/useDeleteCourse';
|
||||
|
||||
type DeleteCourseDialogPropsType = {
|
||||
course: Pick<ICourse, 'id' | 'title'>;
|
||||
@ -34,34 +27,14 @@ type DeleteCourseDialogPropsType = {
|
||||
};
|
||||
|
||||
const DeleteCourseDialog: React.FC<DeleteCourseDialogPropsType> = ({ onClose, isOpen, course }) => {
|
||||
const { courseStore } = useRootStore();
|
||||
const { setDeletingCourse } = useContext(CoursesPageContext);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const handleError = useErrorHandler();
|
||||
const toast = useToast();
|
||||
const { deleteCourse, isLoading } = useDeleteCourse();
|
||||
|
||||
/**
|
||||
* Delete course handler
|
||||
*
|
||||
* @param isConfirmed Is dialog confirmed
|
||||
* @param id Course id
|
||||
*/
|
||||
const deleteCourseHandler = async (isConfirmed: boolean, id: string) => {
|
||||
const handleDelete = async (isConfirmed: boolean, id: string) => {
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await courseStore.deleteCourse(id);
|
||||
toast({ title: 'Course deleted.', status: 'info' });
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
toast({ title: error.message, status: 'error' });
|
||||
} else {
|
||||
handleError(error);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setDeletingCourse(undefined);
|
||||
}
|
||||
await deleteCourse(id)
|
||||
.catch(error => console.error(error))
|
||||
.finally(() => setDeletingCourse(undefined));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,7 +47,7 @@ const DeleteCourseDialog: React.FC<DeleteCourseDialogPropsType> = ({ onClose, is
|
||||
<ConfirmDialog
|
||||
isOpen={isOpen}
|
||||
isLoading={isLoading}
|
||||
onClose={isConfirmed => deleteCourseHandler(isConfirmed, course.id)}
|
||||
onClose={isConfirmed => handleDelete(isConfirmed, course.id)}
|
||||
title='Confirm deleting'
|
||||
message={`The course with title "${course.title}" will be removed from the system. After deleting a course, the lessons in it will also be deleted.`}
|
||||
confirmMessage='Delete'
|
||||
|
||||
@ -38,7 +38,7 @@ type UpdateUserContactsFormPropsType = {
|
||||
const UpdateUserContactsForm: React.FC<UpdateUserContactsFormPropsType> = ({ contacts }) => {
|
||||
const {
|
||||
register,
|
||||
formState: { errors, isDirty },
|
||||
formState: { errors, dirtyFields, isDirty },
|
||||
handleSubmit,
|
||||
reset,
|
||||
} = useForm<UpdateUserContactsInputType>({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user