mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 18:29:15 +03:00
refactor: user query hooks
This commit is contained in:
parent
e329326821
commit
321149a1f2
@ -47,7 +47,7 @@ const CreateFormCourse: React.FC<CreateFormCoursePropsType> = () => {
|
||||
status: CourseStatusEnum.DRAFT,
|
||||
});
|
||||
|
||||
if (course) history.push(`/courses/edit/${course.id}`);
|
||||
history.push(`/courses/edit/${course.id}`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
@ -58,7 +58,6 @@ const EditFormCourse: React.FC<EditFormCoursePropsType> = ({ defaultValues }) =>
|
||||
try {
|
||||
const updated = await updateCourse(id, data);
|
||||
|
||||
if (updated) {
|
||||
/**
|
||||
* Update fields with new values
|
||||
*/
|
||||
@ -69,7 +68,6 @@ const EditFormCourse: React.FC<EditFormCoursePropsType> = ({ defaultValues }) =>
|
||||
category_id: updated.category.id,
|
||||
image: undefined,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
@ -84,3 +84,7 @@ export function arrayMove<T>(array: T[], from: number, to: number) {
|
||||
result.splice(to, 0, removed);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getDirtyFields<T>(dirtyFields: { [key: string]: boolean | undefined }, data: T) {
|
||||
return Object.fromEntries(Object.keys(dirtyFields).map(k => [k, data[k as keyof T]]));
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import { useErrorHandler } from 'react-error-boundary';
|
||||
import useAsync from '../../useAsync';
|
||||
import { useRootStore } from '../../useRootStore';
|
||||
|
||||
type LoginResultDataType = IToken | undefined;
|
||||
type LoginResultDataType = IToken;
|
||||
|
||||
const useLogin = () => {
|
||||
const { authStore } = useRootStore();
|
||||
|
||||
@ -6,9 +6,7 @@ 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;
|
||||
type CreatedCourseDataType = Pick<ICourse, 'id' | 'title' | 'description' | 'status' | 'created_at' | 'updated_at'>;
|
||||
|
||||
const useCreateCourse = () => {
|
||||
const toast = useToast();
|
||||
|
||||
@ -4,7 +4,7 @@ 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;
|
||||
type DeletedCourseDataType = Omit<ICourse, 'teacher' | 'students' | 'lessons'>;
|
||||
|
||||
const useDeleteCourse = () => {
|
||||
const { courseStore } = useRootStore();
|
||||
|
||||
@ -10,7 +10,7 @@ import { useEffect } from 'react';
|
||||
import useAsync from '../../useAsync';
|
||||
import { useRootStore } from '../../useRootStore';
|
||||
|
||||
type CourseResultDataType = Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'> | undefined;
|
||||
type CourseResultDataType = Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'>;
|
||||
|
||||
const useFetchCourse = (id: string) => {
|
||||
const {
|
||||
|
||||
@ -7,11 +7,8 @@ import useAsync from '../../useAsync';
|
||||
import { useRootStore } from '../../useRootStore';
|
||||
import { useToast } from '@chakra-ui/toast';
|
||||
|
||||
type SetCourseStatusResultDataType = {} | undefined;
|
||||
export type SetCourseStatusFnType = (
|
||||
id: string,
|
||||
newStatus: CourseStatusEnum
|
||||
) => Promise<SetCourseStatusResultDataType | undefined>;
|
||||
type SetCourseStatusResultDataType = {};
|
||||
export type SetCourseStatusFnType = (id: string, newStatus: CourseStatusEnum) => Promise<SetCourseStatusResultDataType>;
|
||||
|
||||
const useSetCourseStatus = () => {
|
||||
const { courseStore } = useRootStore();
|
||||
|
||||
@ -5,7 +5,7 @@ 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;
|
||||
type UpdatedCourseDataType = Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'>;
|
||||
|
||||
const useUpdateCourse = () => {
|
||||
const toast = useToast();
|
||||
|
||||
@ -20,6 +20,9 @@ import { useSetCourseStatus } from './course/useSetCourseStatus';
|
||||
/**
|
||||
* User
|
||||
*/
|
||||
import { useCreateUser } from './user/useCreateUser';
|
||||
import { useDeleteUser } from './user/useDeleteUser';
|
||||
import { useUpdateUser } from './user/useUpdateUser';
|
||||
import { useUpdateUserEmail } from './user/useUpdateUserEmail';
|
||||
|
||||
export {
|
||||
@ -30,5 +33,8 @@ export {
|
||||
useFetchCourse,
|
||||
useSetCourseStatus,
|
||||
useDeleteLesson,
|
||||
useCreateUser,
|
||||
useDeleteUser,
|
||||
useUpdateUser,
|
||||
useUpdateUserEmail,
|
||||
};
|
||||
|
||||
@ -4,7 +4,7 @@ import { useErrorHandler } from 'react-error-boundary';
|
||||
import useAsync from '../../useAsync';
|
||||
import { useRootStore } from '../../useRootStore';
|
||||
|
||||
type CreatedLessonDataType = ILesson | undefined;
|
||||
type CreatedLessonDataType = ILesson;
|
||||
|
||||
const useDeleteLesson = () => {
|
||||
const {
|
||||
|
||||
43
src/hooks/queries/user/useCreateUser.ts
Normal file
43
src/hooks/queries/user/useCreateUser.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import { CreateUserParamsType, FetchUsersParamsType } from '@educt/types';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import { useToast } from '@chakra-ui/toast';
|
||||
import { IUser } from '@educt/interfaces';
|
||||
import useAsync from '@educt/hooks/useAsync';
|
||||
|
||||
type CreatedUserDataType = IUser;
|
||||
|
||||
const useCreateUser = () => {
|
||||
const { userStore } = useRootStore();
|
||||
const toast = useToast();
|
||||
const handleError = useErrorHandler();
|
||||
|
||||
const createUser = async (data: CreateUserParamsType, paramsContext?: FetchUsersParamsType | undefined) => {
|
||||
try {
|
||||
const result = await userStore.createUser(data, paramsContext);
|
||||
toast({ title: 'User created.', status: 'info' });
|
||||
return result.data;
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
switch (error.response.status) {
|
||||
case 422:
|
||||
toast({ title: `${error.response.data.errors[0].message}`, status: 'error' });
|
||||
break;
|
||||
default:
|
||||
toast({ title: `${error.message}`, status: 'error' });
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
handleError(error);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
const { execute, ...state } = useAsync<CreatedUserDataType, Parameters<typeof createUser>>(createUser);
|
||||
|
||||
return { createUser: execute, ...state };
|
||||
};
|
||||
|
||||
export { useCreateUser };
|
||||
36
src/hooks/queries/user/useDeleteUser.ts
Normal file
36
src/hooks/queries/user/useDeleteUser.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { useToast } from '@chakra-ui/toast';
|
||||
import useAsync from '@educt/hooks/useAsync';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import { IUser } from '@educt/interfaces';
|
||||
import { FetchUsersParamsType } from '@educt/types';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
|
||||
type DeletedUserDataType = IUser;
|
||||
|
||||
const useDeleteUser = () => {
|
||||
const { userStore } = useRootStore();
|
||||
const toast = useToast();
|
||||
const handleError = useErrorHandler();
|
||||
|
||||
const deleteUser = async (id: string, paramsContext?: FetchUsersParamsType | undefined) => {
|
||||
try {
|
||||
const result = await userStore.deleteUser(id, paramsContext);
|
||||
toast({ title: 'User 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<DeletedUserDataType, Parameters<typeof deleteUser>>(deleteUser);
|
||||
|
||||
return { deleteUser: execute, ...state };
|
||||
};
|
||||
|
||||
export { useDeleteUser };
|
||||
43
src/hooks/queries/user/useUpdateUser.ts
Normal file
43
src/hooks/queries/user/useUpdateUser.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import { useToast } from '@chakra-ui/toast';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import { UpdateUserParamsType } from '@educt/types';
|
||||
import { IUser } from '@educt/interfaces';
|
||||
import useAsync from '@educt/hooks/useAsync';
|
||||
|
||||
type UpdatedUserDataType = IUser;
|
||||
|
||||
const useUpdateUser = () => {
|
||||
const { userStore } = useRootStore();
|
||||
const toast = useToast();
|
||||
const handleError = useErrorHandler();
|
||||
|
||||
const updateUser = async (id: string, params: UpdateUserParamsType) => {
|
||||
try {
|
||||
const result = await userStore.updateUser(id, params);
|
||||
toast({ title: 'User updated.', status: 'info' });
|
||||
return result.data;
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
switch (error.response.status) {
|
||||
case 422:
|
||||
toast({ title: `${error.response.data.errors[0].message}`, status: 'error' });
|
||||
break;
|
||||
default:
|
||||
toast({ title: `${error.message}`, status: 'error' });
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
handleError(error);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
const { execute, ...state } = useAsync<UpdatedUserDataType, Parameters<typeof updateUser>>(updateUser);
|
||||
|
||||
return { updateUser: execute, ...state };
|
||||
};
|
||||
|
||||
export { useUpdateUser };
|
||||
@ -3,7 +3,7 @@ import { useErrorHandler } from 'react-error-boundary';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import useAsync from '@educt/hooks/useAsync';
|
||||
|
||||
type ConfirmationResultDataType = { expired_seconds: number } | null;
|
||||
type ConfirmationResultDataType = { expired_seconds: number };
|
||||
|
||||
const useUpdateUserEmail = () => {
|
||||
const { userStore } = useRootStore();
|
||||
|
||||
@ -32,9 +32,13 @@ const DeleteCourseDialog: React.FC<DeleteCourseDialogPropsType> = ({ onClose, is
|
||||
|
||||
const handleDelete = async (isConfirmed: boolean, id: string) => {
|
||||
if (isConfirmed) {
|
||||
await deleteCourse(id)
|
||||
.catch(error => console.error(error))
|
||||
.finally(() => setDeletingCourse(undefined));
|
||||
try {
|
||||
await deleteCourse(id);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setDeletingCourse(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -93,13 +93,12 @@ const ConfirmEmailContainer: React.FC<ConfirmEmailContainerPropsType> = ({ data
|
||||
|
||||
const resendCodeHandler = async () => {
|
||||
const result = await sendConfirmationCode(data.newEmail);
|
||||
if (result !== null) {
|
||||
|
||||
/**
|
||||
* Reset timer state
|
||||
*/
|
||||
const { expired_seconds } = result;
|
||||
setExpired(v => (v = expired_seconds));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -31,13 +31,15 @@ import { SubmitHandler } from 'react-hook-form';
|
||||
/**
|
||||
* Hooks
|
||||
*/
|
||||
import { useContext, useState } from 'react';
|
||||
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
|
||||
import { useContext } from 'react';
|
||||
import { useDisclosure } from '@chakra-ui/hooks';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
|
||||
/**
|
||||
* Hooks
|
||||
*/
|
||||
import { useCreateUser } from '@educt/hooks/queries';
|
||||
|
||||
/**
|
||||
* Contexts
|
||||
@ -63,6 +65,7 @@ type CreateUserInputType = {
|
||||
const CreateUserModal: React.FC<CreateUserFormPropsType> = () => {
|
||||
const { userStore } = useRootStore();
|
||||
const { searchingRole, search } = useContext(UsersPageContext);
|
||||
const { createUser, isLoading } = useCreateUser();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const {
|
||||
register,
|
||||
@ -72,10 +75,6 @@ const CreateUserModal: React.FC<CreateUserFormPropsType> = () => {
|
||||
} = useForm<CreateUserInputType>({
|
||||
resolver: yupResolver(CreateUserSchema),
|
||||
});
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const isMountedRef = useIsMountedRef();
|
||||
const toast = useToast();
|
||||
const handleError = useErrorHandler();
|
||||
|
||||
const { me } = userStore;
|
||||
|
||||
@ -84,17 +83,14 @@ const CreateUserModal: React.FC<CreateUserFormPropsType> = () => {
|
||||
/**
|
||||
* Submit handler
|
||||
*/
|
||||
// TODO: move to hook
|
||||
const onSubmit: SubmitHandler<CreateUserInputType> = async data => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await userStore.createUser(data, {
|
||||
await createUser(data, {
|
||||
page: userStore.pagination?.current_page,
|
||||
limit: userStore.pagination?.per_page,
|
||||
role: searchingRole,
|
||||
search,
|
||||
});
|
||||
toast({ title: 'User created.', status: 'info' });
|
||||
|
||||
/**
|
||||
* Clear form state
|
||||
@ -106,19 +102,7 @@ const CreateUserModal: React.FC<CreateUserFormPropsType> = () => {
|
||||
*/
|
||||
onClose();
|
||||
} 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) {
|
||||
setLoading(false);
|
||||
}
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
@ -195,7 +179,7 @@ const CreateUserModal: React.FC<CreateUserFormPropsType> = () => {
|
||||
variant='outline'
|
||||
mr={3}
|
||||
colorScheme='blue'
|
||||
isLoading={loading}
|
||||
isLoading={isLoading}
|
||||
loadingText='Creating...'
|
||||
leftIcon={<AddIcon />}
|
||||
>
|
||||
|
||||
@ -18,10 +18,9 @@ import { UsersPageContext } from '@educt/contexts';
|
||||
/**
|
||||
* Hooks
|
||||
*/
|
||||
import { useContext, useState } from 'react';
|
||||
import { useContext } from 'react';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import { useDeleteUser } from '@educt/hooks/queries';
|
||||
|
||||
type DeleteUserDialogPropsType = {
|
||||
user: IUser;
|
||||
@ -32,9 +31,7 @@ type DeleteUserDialogPropsType = {
|
||||
const DeleteUserDialog: React.FC<DeleteUserDialogPropsType> = ({ isOpen, onClose, user }) => {
|
||||
const { userStore } = useRootStore();
|
||||
const { setDeletingUser, searchingRole, searchingPage, search } = useContext(UsersPageContext);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const handleError = useErrorHandler();
|
||||
const toast = useToast();
|
||||
const { deleteUser, isLoading } = useDeleteUser();
|
||||
|
||||
const { pagination } = userStore;
|
||||
|
||||
@ -44,26 +41,18 @@ const DeleteUserDialog: React.FC<DeleteUserDialogPropsType> = ({ isOpen, onClose
|
||||
* @param isConfirmed Is dialog confirmed
|
||||
* @param id User id
|
||||
*/
|
||||
// TODO: move to hook
|
||||
const deleteUserHandler = async (isConfirmed: boolean, id: string) => {
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await userStore.deleteUser(id, {
|
||||
await deleteUser(id, {
|
||||
page: searchingPage,
|
||||
limit: pagination?.per_page,
|
||||
role: searchingRole,
|
||||
search,
|
||||
});
|
||||
toast({ title: 'User deleted.', status: 'info' });
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
toast({ title: error.message, status: 'error' });
|
||||
} else {
|
||||
handleError(error);
|
||||
}
|
||||
console.error(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setDeletingUser(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import * as helpers from '@educt/helpers';
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
@ -25,7 +26,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
import { IMe, IUser } from '@educt/interfaces';
|
||||
import { IUser } from '@educt/interfaces';
|
||||
import { UserRoleEnum } from '@educt/enums';
|
||||
import { SubmitHandler } from 'react-hook-form';
|
||||
|
||||
@ -34,9 +35,6 @@ import { SubmitHandler } from 'react-hook-form';
|
||||
*/
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
|
||||
|
||||
/**
|
||||
* Contexts
|
||||
@ -47,6 +45,7 @@ import { UsersPageContext } from '@educt/contexts';
|
||||
* Schema
|
||||
*/
|
||||
import UpdateUserSchema from './EditUserForm.validator';
|
||||
import { useUpdateUser } from '@educt/hooks/queries';
|
||||
|
||||
type UpdateUserFormPropsType = {
|
||||
user: IUser;
|
||||
@ -66,7 +65,7 @@ type UpdateUserInputType = {
|
||||
const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ user, onClose, isOpen }) => {
|
||||
const { userStore } = useRootStore();
|
||||
const { setEditingUser } = useContext(UsersPageContext);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const { updateUser, isLoading } = useUpdateUser();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@ -83,9 +82,6 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ user, onClose, isOp
|
||||
password: '',
|
||||
},
|
||||
});
|
||||
const isMountedRef = useIsMountedRef();
|
||||
const toast = useToast();
|
||||
const handleError = useErrorHandler();
|
||||
|
||||
const { me } = userStore;
|
||||
|
||||
@ -94,28 +90,13 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ user, onClose, isOp
|
||||
/**
|
||||
* Submit handler
|
||||
*/
|
||||
// TODO: move to hook
|
||||
const onSubmit: SubmitHandler<UpdateUserInputType> = async data => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const params = Object.fromEntries(Object.keys(dirtyFields).map(k => [k, data[k as keyof UpdateUserInputType]]));
|
||||
await userStore.updateUser(user.id, params);
|
||||
toast({ title: 'User updated.', status: 'info' });
|
||||
const params = helpers.getDirtyFields<UpdateUserInputType>(dirtyFields, data);
|
||||
await updateUser(user.id, params);
|
||||
onCloseModal();
|
||||
} 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);
|
||||
}
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ import { CourseServiceInstance, LessonServiceInstance } from '@educt/services';
|
||||
export default class EditCourseStore {
|
||||
public pageStore: PageStore;
|
||||
|
||||
public course: Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'> | undefined;
|
||||
public course: Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'> | null = null;
|
||||
|
||||
public deletingLesson: ILesson | undefined;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user