refactor: user updating

This commit is contained in:
Sergey Yarkov 2021-11-05 21:56:18 +03:00
parent ff88ad75a3
commit 6d72fdb1c3
8 changed files with 166 additions and 97 deletions

View File

@ -1,5 +1,6 @@
import React, { useState, useRef } from 'react'; import React, { useState, useRef } from 'react';
import { Flex, Box, Text } from '@chakra-ui/layout'; import { Flex, Box, Text } from '@chakra-ui/layout';
import { Input } from '@chakra-ui/input';
import { Button } from '@chakra-ui/button'; import { Button } from '@chakra-ui/button';
import { MdAttachFile } from 'react-icons/md'; import { MdAttachFile } from 'react-icons/md';
import * as helpres from '@educt/helpers'; import * as helpres from '@educt/helpers';
@ -8,7 +9,6 @@ import * as helpres from '@educt/helpers';
* Hooks * Hooks
*/ */
import { useColorModeValue } from '@chakra-ui/color-mode'; import { useColorModeValue } from '@chakra-ui/color-mode';
import { Input } from '@chakra-ui/input';
type FileSelectPropsType = { type FileSelectPropsType = {
onChange: (file: File) => void; onChange: (file: File) => void;

View File

@ -0,0 +1,3 @@
import FileSelect from './FileSelect';
export default FileSelect;

View File

@ -13,10 +13,11 @@ import {
InputRightElement, InputRightElement,
Input, Input,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { MdAttachFile, MdCloudUpload } from 'react-icons/md'; import { MdCloudUpload } from 'react-icons/md';
import { SubmitHandler, useForm, useWatch, Controller } from 'react-hook-form'; import { SubmitHandler, useForm, useWatch, Controller } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import AsyncSelect from '@educt/components/AsyncSelect'; import AsyncSelect from '@educt/components/AsyncSelect';
import FileSelect from '@educt/components/FileSelect';
/** /**
* Types * Types
@ -30,14 +31,12 @@ import { CourseStatusEnum, UserRoleEnum } from '@educt/enums';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { useErrorHandler } from 'react-error-boundary'; import { useErrorHandler } from 'react-error-boundary';
import { useColorModeValue } from '@chakra-ui/color-mode';
import useIsMountedRef from '@educt/hooks/useIsMountedRef'; import useIsMountedRef from '@educt/hooks/useIsMountedRef';
/** /**
* Schema * Schema
*/ */
import CreateCourseSchema from './CreateCourseForm.validator'; import CreateCourseSchema from './CreateCourseForm.validator';
import FileSelect from '@educt/components/FileSelect/FileSelect';
type CreateCourseInputType = { type CreateCourseInputType = {
title: string; title: string;
@ -154,7 +153,7 @@ const CreateCourseForm: React.FC<CreateCourseFormProps> = () => {
<Box> <Box>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<Grid templateColumns={{ md: '2.5fr 1fr' }} gap='5'> <Grid templateColumns={{ md: '2.5fr 1fr' }} gap='5'>
<FormControl id='title' isInvalid={!!errors.title}> <FormControl isRequired id='title' isInvalid={!!errors.title}>
<FormLabel>Title</FormLabel> <FormLabel>Title</FormLabel>
<InputGroup> <InputGroup>
<Input pr='60px' size='md' placeholder='Type course title' type='text' {...register('title')} /> <Input pr='60px' size='md' placeholder='Type course title' type='text' {...register('title')} />
@ -186,7 +185,7 @@ const CreateCourseForm: React.FC<CreateCourseFormProps> = () => {
</Text> </Text>
</FormControl> </FormControl>
<FormControl id='category_id' gridColumn='1' isInvalid={!!errors.category_id}> <FormControl isRequired id='category_id' gridColumn='1' isInvalid={!!errors.category_id}>
<FormLabel>Category</FormLabel> <FormLabel>Category</FormLabel>
<Controller <Controller
control={control} control={control}
@ -206,7 +205,13 @@ const CreateCourseForm: React.FC<CreateCourseFormProps> = () => {
{errors.category_id?.message} {errors.category_id?.message}
</Text> </Text>
</FormControl> </FormControl>
<FormControl id='teacher_id' gridColumn={{ md: '2' }} gridRow={{ md: '1' }} isInvalid={!!errors.teacher_id}> <FormControl
isRequired
id='teacher_id'
gridColumn={{ md: '2' }}
gridRow={{ md: '1' }}
isInvalid={!!errors.teacher_id}
>
<FormLabel>Teacher</FormLabel> <FormLabel>Teacher</FormLabel>
<Controller <Controller
control={control} control={control}
@ -226,7 +231,7 @@ const CreateCourseForm: React.FC<CreateCourseFormProps> = () => {
{errors.teacher_id?.message} {errors.teacher_id?.message}
</Text> </Text>
</FormControl> </FormControl>
<FormControl id='description' gridColumn='1' isInvalid={!!errors.description}> <FormControl isRequired id='description' gridColumn='1' isInvalid={!!errors.description}>
<FormLabel>Description</FormLabel> <FormLabel>Description</FormLabel>
<Textarea <Textarea
resize='none' resize='none'

View File

@ -12,6 +12,7 @@ import {
FormHelperText, FormHelperText,
Input, Input,
Button, Button,
Stack,
Select, Select,
Flex, Flex,
Text, Text,
@ -38,6 +39,10 @@ import { useForm } from 'react-hook-form';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { useErrorHandler } from 'react-error-boundary'; import { useErrorHandler } from 'react-error-boundary';
import { useToast } from '@chakra-ui/react'; import { useToast } from '@chakra-ui/react';
/**
* Contexts
*/
import { UsersPageContext } from '@educt/contexts'; import { UsersPageContext } from '@educt/contexts';
type CreateUserModalPropsType = { type CreateUserModalPropsType = {
@ -128,52 +133,53 @@ const CreateUserModal: React.FC<CreateUserModalPropsType> = ({ me }) => {
<ModalCloseButton /> <ModalCloseButton />
<Divider /> <Divider />
<ModalBody pb={6}> <ModalBody pb={6}>
<FormControl> <Stack spacing='4'>
<FormLabel>First name</FormLabel> <FormControl isRequired>
<Input <FormLabel>First name</FormLabel>
placeholder='First name' <Input placeholder='First name' {...register('first_name')} isInvalid={!!errors.first_name} />
{...register('first_name')} {errors.first_name && <FormHelperText color='red.500'>{errors.first_name.message}</FormHelperText>}
isInvalid={errors.first_name ? true : false} </FormControl>
/>
{errors.first_name ? ( <FormControl isRequired>
<FormHelperText color='red.500'>{errors.first_name.message}</FormHelperText> <FormLabel>Last name</FormLabel>
) : null} <Input placeholder='Last name' {...register('last_name')} isInvalid={!!errors.last_name} />
</FormControl> {errors.last_name && <FormHelperText color='red.500'>{errors.last_name.message}</FormHelperText>}
<FormControl mt={4}> </FormControl>
<FormLabel>Last name</FormLabel>
<Input placeholder='Last name' {...register('last_name')} isInvalid={errors.last_name ? true : false} /> <FormControl isRequired>
{errors.last_name ? <FormHelperText color='red.500'>{errors.last_name.message}</FormHelperText> : null} <FormLabel>Login</FormLabel>
</FormControl> <Input placeholder='e.g student001' {...register('login')} isInvalid={!!errors.login} />
<FormControl mt={4}> {errors.login && <FormHelperText color='red.500'>{errors.login.message}</FormHelperText>}
<FormLabel>Login</FormLabel> </FormControl>
<Input placeholder='e.g student893' {...register('login')} isInvalid={errors.login ? true : false} />
{errors.login ? <FormHelperText color='red.500'>{errors.login.message}</FormHelperText> : null} <FormControl isRequired>
</FormControl> <FormLabel>Email</FormLabel>
<FormControl mt={4}> <Input placeholder='example@email.com' {...register('email')} isInvalid={!!errors.email} />
<FormLabel>Email</FormLabel> {errors.email && <FormHelperText color='red.500'>{errors.email.message}</FormHelperText>}
<Input placeholder='example@email.com' {...register('email')} isInvalid={errors.email ? true : false} /> </FormControl>
{errors.email ? <FormHelperText color='red.500'>{errors.email.message}</FormHelperText> : null}
</FormControl> <FormControl isRequired>
<FormControl mt={4}> <FormLabel>Role</FormLabel>
<FormLabel>Role</FormLabel> <Select w='full' mr='2' {...register('role')} isInvalid={!!errors.role}>
<Select w='full' mr='2' {...register('role')} isInvalid={errors.role ? true : false}> <option value={UserRoleEnum.STUDENT}>Student</option>
<option value={UserRoleEnum.STUDENT}>Student</option> {me.isAdmin && (
{me.isAdmin ? ( <>
<> <option value={UserRoleEnum.TEACHER}>Teacher</option>
<option value={UserRoleEnum.TEACHER}>Teacher</option> <option value={UserRoleEnum.ADMIN}>Administrator</option>
<option value={UserRoleEnum.ADMIN}>Administrator</option> </>
</> )}
) : null} </Select>
</Select> {errors.role && <FormHelperText color='red.500'>{errors.role.message}</FormHelperText>}
{errors.role ? <FormHelperText color='red.500'>{errors.role.message}</FormHelperText> : null} </FormControl>
</FormControl>
<FormControl mt={4}> <FormControl isRequired>
<FormLabel>Password</FormLabel> <FormLabel>Password</FormLabel>
<Input placeholder='******' {...register('password')} isInvalid={errors.password ? true : false} /> <Input placeholder='******' {...register('password')} isInvalid={!!errors.password} />
<FormHelperText color={errors.password ? 'red' : 'gray.500'}> <FormHelperText color={errors.password ? 'red' : 'gray.500'}>
{errors.password ? errors.password.message : 'Must be at least 6 characters.'} {errors.password ? errors.password.message : 'Must be at least 6 characters.'}
</FormHelperText> </FormHelperText>
</FormControl> </FormControl>
</Stack>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>

View File

@ -15,16 +15,28 @@ import {
Flex, Flex,
Text, Text,
Divider, Divider,
Select,
Stack,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { CheckIcon } from '@chakra-ui/icons'; import { CheckIcon } from '@chakra-ui/icons';
import { MdModeEdit } from 'react-icons/md'; import { MdModeEdit } from 'react-icons/md';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import UpdateUserSchema from './EditUserForm.validator';
/** /**
* Types * Types
*/ */
import { IMe } from '@educt/interfaces';
import { UserRoleEnum } from '@educt/enums'; import { UserRoleEnum } from '@educt/enums';
import { SubmitHandler } from 'react-hook-form';
/**
* 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';
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
/** /**
* Contexts * Contexts
@ -32,28 +44,26 @@ import { UserRoleEnum } from '@educt/enums';
import { UsersPageContext } from '@educt/contexts'; import { UsersPageContext } from '@educt/contexts';
/** /**
* Hooks * Schema
*/ */
import { SubmitHandler, useForm } from 'react-hook-form'; import UpdateUserSchema from './EditUserForm.validator';
import { useRootStore } from '@educt/hooks/useRootStore';
import { useErrorHandler } from 'react-error-boundary'; type UpdateUserFormPropsType = {
import { useToast } from '@chakra-ui/react'; onClose: () => void;
import useIsMountedRef from '@educt/hooks/useIsMountedRef'; isOpen: boolean;
me: IMe;
};
type UpdateUserInputType = { type UpdateUserInputType = {
first_name: string; first_name: string;
last_name: string; last_name: string;
login: string; login: string | null;
email: string; email: string;
role: UserRoleEnum; role: UserRoleEnum;
password: string; password: string | null;
};
type UpdateUserFormPropsType = {
onClose: () => void;
isOpen: boolean;
}; };
const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ onClose, isOpen }) => { const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ onClose, isOpen, me }) => {
const { userStore } = useRootStore(); const { userStore } = useRootStore();
const { editingUser, setEditingUser } = useContext(UsersPageContext); const { editingUser, setEditingUser } = useContext(UsersPageContext);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
@ -68,6 +78,9 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ onClose, isOpen })
first_name: editingUser && editingUser.first_name, first_name: editingUser && editingUser.first_name,
last_name: editingUser && editingUser.last_name, last_name: editingUser && editingUser.last_name,
email: editingUser && editingUser.email, email: editingUser && editingUser.email,
role: editingUser && editingUser.roles[0].slug,
login: null,
password: null,
}, },
}); });
const isMountedRef = useIsMountedRef(); const isMountedRef = useIsMountedRef();
@ -120,29 +133,58 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ onClose, isOpen })
<ModalCloseButton /> <ModalCloseButton />
<Divider /> <Divider />
<ModalBody pb={6}> <ModalBody pb={6}>
<FormControl isDisabled> <Stack spacing='4'>
<FormLabel>Identificator</FormLabel> <FormControl isDisabled>
<Input value={editingUser?.id} /> <FormLabel>Identificator</FormLabel>
</FormControl> <Input value={editingUser?.id} />
<FormControl mt={4}> </FormControl>
<FormLabel>First name</FormLabel>
<Input <FormControl>
placeholder='First name' <FormLabel>New Login</FormLabel>
{...register('first_name')} <Input placeholder='e.g student001' {...register('login')} isInvalid={!!errors.login} />
isInvalid={errors.first_name ? true : false} {errors.login && <FormHelperText color='red.500'>{errors.login.message}</FormHelperText>}
/> </FormControl>
{errors.first_name && <FormHelperText color='red.500'>{errors.first_name.message}</FormHelperText>}
</FormControl> <FormControl isRequired>
<FormControl mt={4}> <FormLabel>First name</FormLabel>
<FormLabel>Last name</FormLabel> <Input placeholder='First name' {...register('first_name')} isInvalid={!!errors.first_name} />
<Input placeholder='Last name' {...register('last_name')} isInvalid={errors.last_name ? true : false} /> {errors.first_name && <FormHelperText color='red.500'>{errors.first_name.message}</FormHelperText>}
{errors.last_name && <FormHelperText color='red.500'>{errors.last_name.message}</FormHelperText>} </FormControl>
</FormControl>
<FormControl mt={4}> <FormControl isRequired>
<FormLabel>Email</FormLabel> <FormLabel>Last name</FormLabel>
<Input placeholder='example@email.com' {...register('email')} isInvalid={errors.email ? true : false} /> <Input placeholder='Last name' {...register('last_name')} isInvalid={!!errors.last_name} />
{errors.email && <FormHelperText color='red.500'>{errors.email.message}</FormHelperText>} {errors.last_name && <FormHelperText color='red.500'>{errors.last_name.message}</FormHelperText>}
</FormControl> </FormControl>
<FormControl isRequired>
<FormLabel>Email</FormLabel>
<Input placeholder='example@email.com' {...register('email')} isInvalid={!!errors.email} />
{errors.email && <FormHelperText color='red.500'>{errors.email.message}</FormHelperText>}
</FormControl>
<FormControl isRequired>
<FormLabel>Role</FormLabel>
<Select w='full' mr='2' {...register('role')} isInvalid={!!errors.role}>
<option value={UserRoleEnum.STUDENT}>Student</option>
{me.isAdmin && (
<>
<option value={UserRoleEnum.TEACHER}>Teacher</option>
<option value={UserRoleEnum.ADMIN}>Administrator</option>
</>
)}
</Select>
{errors.role ? <FormHelperText color='red.500'>{errors.role.message}</FormHelperText> : null}
</FormControl>
<FormControl>
<FormLabel>New Password</FormLabel>
<Input placeholder='******' {...register('password')} isInvalid={!!errors.password} />
<FormHelperText color={errors.password ? 'red' : 'gray.500'}>
{errors.password ? errors.password.message : 'Must be at least 6 characters.'}
</FormHelperText>
</FormControl>
</Stack>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>

View File

@ -1,10 +1,23 @@
import yup from '@educt/schema'; import yup from '@educt/schema';
// import { UserRoleEnum } from 'enums'; import { UserRoleEnum } from '@educt/enums';
const UpdateUserSchema = yup.object().shape({ const UpdateUserSchema = yup.object().shape({
first_name: yup.string().required('First field name is required'), first_name: yup.string().required('First field name is required'),
last_name: yup.string().required('Last field name is required'), last_name: yup.string().required('Last field name is required'),
email: yup.string().email('Email is invalid').required('Email field is required'), email: yup.string().email('Email is invalid').required('Email field is required'),
role: yup
.mixed<UserRoleEnum>()
.required('Role field is required')
.oneOf(Object.values(UserRoleEnum), `Available roles: ${Object.keys(UserRoleEnum).join(', ')}`),
login: yup
.string()
.transform(val => (!val ? null : val))
.nullable(true),
password: yup
.string()
.min(6, 'Password must be at least 6 characters.')
.transform(val => (!val ? null : val))
.nullable(true),
}); });
export default UpdateUserSchema; export default UpdateUserSchema;

View File

@ -7,6 +7,7 @@ import { Button } from '@chakra-ui/button';
/** /**
* Components * Components
*/ */
import LoadingList from '@educt/components/LoadingList';
import EditUserForm from '../EditUserForm'; import EditUserForm from '../EditUserForm';
import DeleteUserDialog from '../DeleteUserDialog'; import DeleteUserDialog from '../DeleteUserDialog';
@ -30,7 +31,6 @@ import { useLocation } from 'react-router';
import { useErrorHandler } from 'react-error-boundary'; import { useErrorHandler } from 'react-error-boundary';
import { useRootStore } from '@educt/hooks/useRootStore'; import { useRootStore } from '@educt/hooks/useRootStore';
import { useDisclosure } from '@chakra-ui/hooks'; import { useDisclosure } from '@chakra-ui/hooks';
import LoadingList from '@educt/components/LoadingList';
type UserListPropsType = { render: React.FC<UserItemPropsType> }; type UserListPropsType = { render: React.FC<UserItemPropsType> };
@ -42,13 +42,13 @@ const UserList: React.FC<UserListPropsType> = ({ render: Item }) => {
const handleError = useErrorHandler(); const handleError = useErrorHandler();
const { onOpen: onOpenEditModal, onClose: onCloseEditModal, isOpen: isOpenEditModal } = useDisclosure(); const { onOpen: onOpenEditModal, onClose: onCloseEditModal, isOpen: isOpenEditModal } = useDisclosure();
const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure(); const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure();
const { users, pagination } = userStore; const { users, pagination, me } = userStore;
useEffect(() => { useEffect(() => {
userStore.loadUsersData().catch(error => handleError(error)); userStore.loadUsersData().catch(error => handleError(error));
}, [handleError, userStore, location.search]); }, [handleError, userStore, location.search]);
if (pagination === undefined || users === null) return <LoadingList />; if (pagination === undefined || users === null || me === null) return <LoadingList />;
const pagesCount = Math.ceil(pagination.total / pagination.per_page); const pagesCount = Math.ceil(pagination.total / pagination.per_page);
@ -117,7 +117,7 @@ const UserList: React.FC<UserListPropsType> = ({ render: Item }) => {
return ( return (
<> <>
{editingUser && <EditUserForm onClose={onCloseEditModal} isOpen={isOpenEditModal} />} {editingUser && <EditUserForm me={me} onClose={onCloseEditModal} isOpen={isOpenEditModal} />}
{deletingUser && <DeleteUserDialog onClose={onCloseDeleteDialog} isOpen={isOpenDeleteDialog} />} {deletingUser && <DeleteUserDialog onClose={onCloseDeleteDialog} isOpen={isOpenDeleteDialog} />}
<Box> <Box>
<Flex mt='7' p='0 10px' fontWeight='bold' alignItems='center' justifyContent='space-between'> <Flex mt='7' p='0 10px' fontWeight='bold' alignItems='center' justifyContent='space-between'>

View File

@ -76,7 +76,7 @@ export default class UserService {
* @returns Updated user * @returns Updated user
*/ */
public async update(id: string, data: UpdateUserParamsType): Promise<IApiRespose<IUser>> { public async update(id: string, data: UpdateUserParamsType): Promise<IApiRespose<IUser>> {
const result = await this.api.patch(`v1/users/${id}`, data); const result = await this.api.patch(`v1/users/${id}`, helpers.removeEmptyValues(data));
return result.data; return result.data;
} }