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

View File

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

View File

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

View File

@ -1,10 +1,23 @@
import yup from '@educt/schema';
// import { UserRoleEnum } from 'enums';
import { UserRoleEnum } from '@educt/enums';
const UpdateUserSchema = yup.object().shape({
first_name: yup.string().required('First 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'),
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;

View File

@ -7,6 +7,7 @@ import { Button } from '@chakra-ui/button';
/**
* Components
*/
import LoadingList from '@educt/components/LoadingList';
import EditUserForm from '../EditUserForm';
import DeleteUserDialog from '../DeleteUserDialog';
@ -30,7 +31,6 @@ import { useLocation } from 'react-router';
import { useErrorHandler } from 'react-error-boundary';
import { useRootStore } from '@educt/hooks/useRootStore';
import { useDisclosure } from '@chakra-ui/hooks';
import LoadingList from '@educt/components/LoadingList';
type UserListPropsType = { render: React.FC<UserItemPropsType> };
@ -42,13 +42,13 @@ const UserList: React.FC<UserListPropsType> = ({ render: Item }) => {
const handleError = useErrorHandler();
const { onOpen: onOpenEditModal, onClose: onCloseEditModal, isOpen: isOpenEditModal } = useDisclosure();
const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure();
const { users, pagination } = userStore;
const { users, pagination, me } = userStore;
useEffect(() => {
userStore.loadUsersData().catch(error => handleError(error));
}, [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);
@ -117,7 +117,7 @@ const UserList: React.FC<UserListPropsType> = ({ render: Item }) => {
return (
<>
{editingUser && <EditUserForm onClose={onCloseEditModal} isOpen={isOpenEditModal} />}
{editingUser && <EditUserForm me={me} onClose={onCloseEditModal} isOpen={isOpenEditModal} />}
{deletingUser && <DeleteUserDialog onClose={onCloseDeleteDialog} isOpen={isOpenDeleteDialog} />}
<Box>
<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
*/
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;
}