refactor: refactored other hooks query

This commit is contained in:
Sergey Yarkov 2021-12-02 21:53:25 +03:00
parent dd86228cab
commit e329326821
23 changed files with 156 additions and 193 deletions

View File

@ -22,11 +22,11 @@ import UserBadge from '@educt/components/UserBadge';
/**
* Hooks
*/
import useLogoutQuery from '@educt/hooks/useLogoutQuery';
import { useLogout } from '@educt/hooks/queries';
const UserInfo: React.FC = () => {
const { userStore } = useRootStore();
const { logout } = useLogoutQuery();
const { logout } = useLogout();
if (userStore.me === null) {
return <Skeleton width='260px' height='40px' borderRadius='md' />;

View File

@ -17,7 +17,7 @@ import NavList from '../NavList';
/**
* Hooks
*/
import useLogoutQuery from '@educt/hooks/useLogoutQuery';
import { useLogout } from '@educt/hooks/queries';
import { useRootStore } from '@educt/hooks/useRootStore';
import { useColorMode, useDisclosure } from '@chakra-ui/react';
@ -27,7 +27,7 @@ const NavMobile: React.FC = () => {
} = useRootStore();
const { colorMode } = useColorMode();
const { isOpen, onOpen, onClose } = useDisclosure();
const { logout } = useLogoutQuery();
const { logout } = useLogout();
return (
<>

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { Button } from '@chakra-ui/react';
import { AiOutlineEye, AiOutlineEyeInvisible } from 'react-icons/ai';
import { CourseStatusEnum } from '@educt/enums';
import useSetCourseStatusQuery from '@educt/hooks/useSetCourseStatusQuery';
import { useSetCourseStatus } from '@educt/hooks/queries';
type SetCourseStatusButtonPropsType = {
courseId: string;
@ -16,7 +16,7 @@ const SetCourseStatusButton: React.FC<SetCourseStatusButtonPropsType> = ({
loadingText = 'Wait a second...',
}) => {
const [status, setStatus] = useState<keyof typeof CourseStatusEnum>(currentStatus);
const { setCourseStatus, isLoading } = useSetCourseStatusQuery();
const { setCourseStatus, isLoading } = useSetCourseStatus();
const handleChangeStatus = async (newStatus: CourseStatusEnum) => {
await setCourseStatus(courseId, newStatus);

View File

@ -1,10 +1,12 @@
import { useToast } from '@chakra-ui/react';
import { IToken } 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';
const useLoginQuery = () => {
type LoginResultDataType = IToken | undefined;
const useLogin = () => {
const { authStore } = useRootStore();
const toast = useToast();
const handleError = useErrorHandler();
@ -21,16 +23,13 @@ const useLoginQuery = () => {
return result.data;
} catch (error: any) {
if (error.response) {
/**
* Handle response error
*/
switch (error.response.status) {
case 404:
toast({ title: 'User not found in a system.', status: 'error', duration: 2000 });
throw error;
break;
case 401:
toast({ title: 'Invalid password.', status: 'error', duration: 2000 });
throw error;
break;
default:
handleError(error);
break;
@ -38,12 +37,14 @@ const useLoginQuery = () => {
} else {
handleError(error);
}
return Promise.reject(error);
}
};
const { execute, ...state } = useAsync<IToken | undefined, Parameters<typeof login>>(login);
const { execute, ...state } = useAsync<LoginResultDataType, Parameters<typeof login>>(login);
return { login: execute, ...state };
};
export default useLoginQuery;
export { useLogin };

View File

@ -1,10 +1,12 @@
import { useToast } from '@chakra-ui/react';
import { useHistory } from 'react-router-dom';
import { useRootStore } from './useRootStore';
import { useRootStore } from '../../useRootStore';
import { useErrorHandler } from 'react-error-boundary';
import useAsync from './useAsync';
import useAsync from '../../useAsync';
const useLogoutQuery = () => {
type LogoutResultDataType = {};
const useLogout = () => {
const { authStore } = useRootStore();
const history = useHistory();
const toast = useToast();
@ -12,17 +14,19 @@ const useLogoutQuery = () => {
const logout = async () => {
try {
await authStore.logout();
const result = await authStore.logout();
history.push('/auth');
toast({ title: 'You are logged out.', isClosable: true, status: 'info' });
} catch (error) {
return result.data;
} catch (error: any) {
handleError(error);
return Promise.reject(error);
}
};
const { execute, ...state } = useAsync<void, Parameters<typeof logout>>(logout);
const { execute, ...state } = useAsync<LogoutResultDataType, Parameters<typeof logout>>(logout);
return { logout: execute, ...state };
};
export default useLogoutQuery;
export { useLogout };

View File

@ -28,10 +28,13 @@ const useCreateCourse = () => {
return result.data;
} catch (error: any) {
if (error.response) {
if (error.response.status === 422) {
switch (error.response.status) {
case 422:
toast({ title: `${error.response.data.errors[0].message}`, status: 'error' });
} else {
break;
default:
toast({ title: `${error.message}`, status: 'error' });
break;
}
} else {
handleError(error);

View File

@ -7,12 +7,12 @@ import { ICourse } from '@educt/interfaces';
* Hooks
*/
import { useEffect } from 'react';
import useAsync from './useAsync';
import { useRootStore } from './useRootStore';
import useAsync from '../../useAsync';
import { useRootStore } from '../../useRootStore';
type QueryResponseDataType = Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'> | undefined;
type CourseResultDataType = Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'> | undefined;
const useFetchCourseQuery = (id: string) => {
const useFetchCourse = (id: string) => {
const {
pageStore: { editCourseStore },
} = useRootStore();
@ -22,7 +22,7 @@ const useFetchCourseQuery = (id: string) => {
return result.data;
};
const { execute: fetchCourseById, ...state } = useAsync<QueryResponseDataType, Parameters<typeof fetch>>(fetch);
const { execute: fetchCourseById, ...state } = useAsync<CourseResultDataType, Parameters<typeof fetch>>(fetch);
useEffect(() => {
fetchCourseById(id);
@ -31,4 +31,4 @@ const useFetchCourseQuery = (id: string) => {
return { ...state };
};
export default useFetchCourseQuery;
export { useFetchCourse };

View File

@ -3,17 +3,17 @@
*/
import { CourseStatusEnum } from '@educt/enums';
import { useErrorHandler } from 'react-error-boundary';
import useAsync from './useAsync';
import { useRootStore } from './useRootStore';
import useAsync from '../../useAsync';
import { useRootStore } from '../../useRootStore';
import { useToast } from '@chakra-ui/toast';
type QueryResponseDataType = {} | undefined;
type SetCourseStatusResultDataType = {} | undefined;
export type SetCourseStatusFnType = (
id: string,
newStatus: CourseStatusEnum
) => Promise<QueryResponseDataType | undefined>;
) => Promise<SetCourseStatusResultDataType | undefined>;
const useSetCourseStatusQuery = () => {
const useSetCourseStatus = () => {
const { courseStore } = useRootStore();
const toast = useToast();
const handleError = useErrorHandler();
@ -38,12 +38,16 @@ const useSetCourseStatusQuery = () => {
} else {
handleError(error);
}
return Promise.reject(error);
}
};
const { execute, ...state } = useAsync<QueryResponseDataType, Parameters<SetCourseStatusFnType>>(setCourseStatus);
const { execute, ...state } = useAsync<SetCourseStatusResultDataType, Parameters<typeof setCourseStatus>>(
setCourseStatus
);
return { setCourseStatus: execute, ...state };
};
export default useSetCourseStatusQuery;
export { useSetCourseStatus };

View File

@ -1,6 +1,34 @@
/**
* Auth
*/
import { useLogin } from './auth/useLogin';
import { useLogout } from './auth/useLogout';
/**
* Lesson
*/
import { useDeleteLesson } from './lesson/useDeleteLesson';
/**
* Course
*/
import { useCreateCourse } from './course/useCreateCourse';
import { useUpdateCourse } from './course/useUpdateCourse';
import { useFetchCourse } from './course/useFetchCourse';
import { useSetCourseStatus } from './course/useSetCourseStatus';
export { useCreateCourse, useUpdateCourse, useDeleteLesson };
/**
* User
*/
import { useUpdateUserEmail } from './user/useUpdateUserEmail';
export {
useLogin,
useLogout,
useCreateCourse,
useUpdateCourse,
useFetchCourse,
useSetCourseStatus,
useDeleteLesson,
useUpdateUserEmail,
};

View File

@ -0,0 +1,49 @@
import { useToast } from '@chakra-ui/toast';
import { useErrorHandler } from 'react-error-boundary';
import { useRootStore } from '@educt/hooks/useRootStore';
import useAsync from '@educt/hooks/useAsync';
type ConfirmationResultDataType = { expired_seconds: number } | null;
const useUpdateUserEmail = () => {
const { userStore } = useRootStore();
const toast = useToast();
const handleError = useErrorHandler();
const sendConfirmationCode = async (email: string) => {
try {
const result = await userStore.updateCurrentUserEmail(email);
toast({ title: 'Confirmation code has been sent.', status: 'info' });
return result.data;
} catch (error: any) {
if (error.response) {
if (error.response.status === 503) {
switch (error.response.data.code) {
case 'E_CONFIRM_CODE_EXIST':
toast({ title: 'Please, try send code later.', status: 'info' });
break;
default:
toast({ title: `Unable to send confirmation code, try again later.`, status: 'error' });
break;
}
} else if (error.response.status === 422) {
toast({ title: error.response.data.errors[0].message, status: 'error' });
} else {
handleError(error);
}
} else {
handleError(error);
}
return Promise.reject(error);
}
};
const { execute, ...state } = useAsync<ConfirmationResultDataType, Parameters<typeof sendConfirmationCode>>(
sendConfirmationCode
);
return { sendConfirmationCode: execute, ...state };
};
export { useUpdateUserEmail };

View File

@ -1,56 +0,0 @@
// /**
// * Types
// */
// import { ILesson } from '@educt/interfaces';
// /**
// * Hooks
// */
// import { useEffect, useState } from 'react';
// import { useErrorHandler } from 'react-error-boundary';
// import useIsMountedRef from './useIsMountedRef';
// type LessonStateType = {
// data: ILesson | null;
// error: any;
// loading: boolean;
// fetched: boolean;
// };
// const useFetchLessonQuery = (id: string) => {
// const [state, setState] = useState<LessonStateType>({ data: null, error: null, loading: false, fetched: false });
// const isMountedRef = useIsMountedRef();
// const handleError = useErrorHandler();
// /**
// * Fetch course handler
// */
// useEffect(() => {
// setState(s => ({ ...s, loading: true }));
// lessonService
// .fetchById(id)
// .then(data => {
// if (isMountedRef.current) {
// setState(s => ({ ...s, data: data.data }));
// }
// })
// .catch(error => {
// if (error.response) {
// if (isMountedRef.current) {
// setState(s => ({ ...s, error }));
// }
// } else {
// handleError(error);
// }
// })
// .finally(() => {
// if (isMountedRef.current) {
// setState(s => ({ ...s, loading: false, fetched: true }));
// }
// });
// }, [id]);
// return { ...state };
// };
// export default useFetchLessonQuery;

View File

@ -1,72 +0,0 @@
import { useToast } from '@chakra-ui/toast';
import { IApiRespose } from '@educt/interfaces';
import { useState } from 'react';
import { useErrorHandler } from 'react-error-boundary';
import useIsMountedRef from './useIsMountedRef';
import { useRootStore } from './useRootStore';
type UpdateUserEmailStateType = {
result: IApiRespose<{ expired_seconds: number }> | null;
error: any;
loading: boolean;
fetched: boolean;
};
const useUpdateUserEmailQuery = () => {
const isMountedRef = useIsMountedRef();
const { userStore } = useRootStore();
const [state, setState] = useState<UpdateUserEmailStateType>({
result: null,
error: null,
loading: false,
fetched: false,
});
const toast = useToast();
const handleError = useErrorHandler();
const sendConfirmationCode = async (email: string) => {
try {
setState(s => ({ ...s, loading: true }));
const result = await userStore.updateCurrentUserEmail(email);
toast({ title: 'Confirmation code has been sent.', status: 'info' });
if (isMountedRef.current) {
setState(s => ({ ...s, result }));
}
return result;
} catch (error: any) {
if (isMountedRef.current) {
setState(s => ({ ...s, error }));
}
if (error.response) {
const status = error.response.status;
if (status === 503) {
switch (error.response.data.code) {
case 'E_CONFIRM_CODE_EXIST':
toast({ title: 'Please, try send code later.', status: 'info' });
break;
default:
toast({ title: `Unable to send confirmation code, try again later.`, status: 'error' });
break;
}
} else if (status === 422) {
toast({ title: error.response.data.errors[0].message, status: 'error' });
} else {
handleError(error);
}
} else {
handleError(error);
}
} finally {
if (isMountedRef.current) {
setState(s => ({ ...s, loading: false, fetched: true }));
}
}
};
return { ...state, sendConfirmationCode };
};
export default useUpdateUserEmailQuery;

View File

@ -23,7 +23,7 @@ import type { SubmitHandler } from 'react-hook-form';
* Hooks
*/
import { useForm } from 'react-hook-form';
import useLoginQuery from '@educt/hooks/useLoginQuery';
import { useLogin } from '@educt/hooks/queries';
/**
* Schema
@ -44,7 +44,7 @@ const AuthForm: React.FC = () => {
} = useForm<AuthInputType>({
resolver: yupResolver(LoginSchema),
});
const { login, isLoading } = useLoginQuery();
const { login, isLoading } = useLogin();
/**
* Login handler

View File

@ -12,8 +12,8 @@ import { DeleteIcon, EditIcon } from '@chakra-ui/icons';
* Types
*/
import { ICourse } from '@educt/interfaces';
import type { SetCourseStatusFnType } from '@educt/hooks/queries/course/useSetCourseStatus';
import { CourseStatusEnum } from '@educt/enums';
import { SetCourseStatusFnType } from '@educt/hooks/useSetCourseStatusQuery';
/**
* Components

View File

@ -18,6 +18,7 @@ import LoadingList from '@educt/components/LoadingList';
* Hooks
*/
import { useContext, useEffect } from 'react';
import { useSetCourseStatus } from '@educt/hooks/queries';
import { useRootStore } from '@educt/hooks/useRootStore';
import { useDisclosure } from '@chakra-ui/hooks';
@ -25,7 +26,6 @@ import { useDisclosure } from '@chakra-ui/hooks';
* Contexts
*/
import { CoursesPageContext } from '@educt/contexts';
import useSetCourseStatusQuery from '@educt/hooks/useSetCourseStatusQuery';
type CourseListPropsType = {
render: React.FC<CourseItemPropsType>;
@ -38,7 +38,7 @@ const CourseList: React.FC<CourseListPropsType> = ({ render: Item }) => {
} = useRootStore();
const { selectedCategory, courseStatus, deletingCourse, setDeletingCourse } = useContext(CoursesPageContext);
const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure();
const { setCourseStatus } = useSetCourseStatusQuery();
const { setCourseStatus } = useSetCourseStatus();
const { courses, isLoading } = courseStore;
/**

View File

@ -22,7 +22,7 @@ import { StudentList, StudentItem } from './components/StudentList';
*/
import { useParams } from 'react-router-dom';
import { useRootStore } from '@educt/hooks/useRootStore';
import useFetchCourseQuery from '@educt/hooks/useFetchCourseQuery';
import { useFetchCourse } from '@educt/hooks/queries';
/**
* Course editor
@ -34,7 +34,7 @@ const EditCoursePage: React.FC<IPageProps> = () => {
editCourseStore: { course },
},
} = useRootStore();
const { error, isLoading } = useFetchCourseQuery(params.id);
const { error, isLoading } = useFetchCourse(params.id);
/**
* Not Found

View File

@ -15,7 +15,7 @@ import type { SubmitHandler } from 'react-hook-form';
*/
import { useContext, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import useUpdateUserEmailQuery from '@educt/hooks/useUpdateUserEmailQuery.ts';
import { useUpdateUserEmail } from '@educt/hooks/queries';
/**
* Contexts
@ -41,7 +41,7 @@ type UpdateEmailFormPropsType = {
const UpdateEmailForm: React.FC<UpdateEmailFormPropsType> = ({ currentEmail }) => {
const [newEmail, setNewEmail] = useState<string | null>(null);
const { setIsCodeSent, setConfirmEmailData } = useContext(ChangeEmailPageContext);
const { sendConfirmationCode, loading, result } = useUpdateUserEmailQuery();
const { sendConfirmationCode, isLoading, data } = useUpdateUserEmail();
const {
handleSubmit,
register,
@ -54,11 +54,11 @@ const UpdateEmailForm: React.FC<UpdateEmailFormPropsType> = ({ currentEmail }) =
* Change page view in confirmation code has been sent
*/
useEffect(() => {
if (newEmail !== null && result !== null) {
if (newEmail !== null && data !== null) {
setIsCodeSent(true);
setConfirmEmailData({ newEmail, expired_seconds: result.data.expired_seconds });
setConfirmEmailData({ newEmail, expired_seconds: data.expired_seconds });
}
}, [result]);
}, [data]);
/**
* Submit form handler
@ -93,7 +93,7 @@ const UpdateEmailForm: React.FC<UpdateEmailFormPropsType> = ({ currentEmail }) =
type='submit'
size='md'
variant='outline'
isLoading={loading}
isLoading={isLoading}
loadingText='Sending...'
>
Send code

View File

@ -12,7 +12,7 @@ import { ConfirmEmailDataType } from '@educt/types';
* Hooks
*/
import { useEffect, useState } from 'react';
import useUpdateUserEmailQuery from '@educt/hooks/useUpdateUserEmailQuery.ts';
import { useUpdateUserEmail } from '@educt/hooks/queries';
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
import { useRootStore } from '@educt/hooks/useRootStore';
import { useToast } from '@chakra-ui/toast';
@ -25,7 +25,7 @@ type ConfirmEmailContainerPropsType = {
const ConfirmEmailContainer: React.FC<ConfirmEmailContainerPropsType> = ({ data }) => {
const { userStore } = useRootStore();
const { sendConfirmationCode, loading } = useUpdateUserEmailQuery();
const { sendConfirmationCode, isLoading } = useUpdateUserEmail();
const [isVerifying, setIsVerifying] = useState<boolean>(false);
const [code, setCode] = useState<string>('');
const [expired, setExpired] = useState(data.expired_seconds);
@ -93,11 +93,11 @@ const ConfirmEmailContainer: React.FC<ConfirmEmailContainerPropsType> = ({ data
const resendCodeHandler = async () => {
const result = await sendConfirmationCode(data.newEmail);
if (result !== undefined) {
if (result !== null) {
/**
* Reset timer state
*/
const { expired_seconds } = result.data;
const { expired_seconds } = result;
setExpired(v => (v = expired_seconds));
}
};
@ -158,7 +158,7 @@ const ConfirmEmailContainer: React.FC<ConfirmEmailContainerPropsType> = ({ data
mt='3'
textDecoration='underline'
loadingText='Wait a second.'
isLoading={loading}
isLoading={isLoading}
>
Resend code
</Button>

View File

@ -1,5 +1,4 @@
import React from 'react';
import { observer } from 'mobx-react';
import {
Avatar,
Tabs,
@ -35,7 +34,7 @@ import CourseList from '@educt/pages/profile/components/CourseList';
import { useHistory } from 'react-router-dom';
import { useColorMode } from '@chakra-ui/color-mode';
import { useRootStore } from '@educt/hooks/useRootStore';
import useLogoutQuery from '@educt/hooks/useLogoutQuery';
import { useLogout } from '@educt/hooks/queries';
/**
* Profile page
@ -45,7 +44,7 @@ const ProfilePage: React.FC<IPageProps> = () => {
userStore: { me },
} = useRootStore();
const history = useHistory();
const { logout } = useLogoutQuery();
const { logout } = useLogout();
const { colorMode } = useColorMode();
if (me === null) return <LoadingPage />;
@ -139,7 +138,7 @@ const ProfilePage: React.FC<IPageProps> = () => {
</Tabs>
</Box>
<Box textAlign='center' mt='3'>
<Button onClick={logout} w={{ base: 'full', sm: '150px' }}>
<Button onClick={logout} size='sm' w={{ base: 'full', sm: '150px' }}>
Sign Out
</Button>
</Box>

View File

@ -84,6 +84,7 @@ const CreateUserModal: React.FC<CreateUserFormPropsType> = () => {
/**
* Submit handler
*/
// TODO: move to hook
const onSubmit: SubmitHandler<CreateUserInputType> = async data => {
try {
setLoading(true);

View File

@ -44,6 +44,7 @@ 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 {

View File

@ -94,6 +94,7 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ user, onClose, isOp
/**
* Submit handler
*/
// TODO: move to hook
const onSubmit: SubmitHandler<UpdateUserInputType> = async data => {
try {
setIsLoading(true);

View File

@ -28,7 +28,7 @@ class AuthService implements AuthServiceImpl {
* @param password User password
* @returns Token result data
*/
public async requestLogin(login: string, password: string) {
public async requestLogin(login: string, password: string): Promise<IApiRespose<IToken>> {
const result = await this.api.post('v1/auth/login', {
login,
password,
@ -41,7 +41,7 @@ class AuthService implements AuthServiceImpl {
*
* @returns Empty obj
*/
public async requestLogout() {
public async requestLogout(): Promise<IApiRespose<{}>> {
const result = await this.api.post('v1/auth/logout');
return result.data;
}