diff --git a/src/pages/users/components/CreateUserModal/CreateUserModal.tsx b/src/pages/users/components/CreateUserForm/CreateUserForm.tsx similarity index 93% rename from src/pages/users/components/CreateUserModal/CreateUserModal.tsx rename to src/pages/users/components/CreateUserForm/CreateUserForm.tsx index 147a702..3082297 100644 --- a/src/pages/users/components/CreateUserModal/CreateUserModal.tsx +++ b/src/pages/users/components/CreateUserForm/CreateUserForm.tsx @@ -32,6 +32,7 @@ import { UserRoleEnum } from 'enums'; /** * Hooks */ +import useIsMountedRef from 'hooks/useIsMountedRef'; import { useDisclosure } from '@chakra-ui/hooks'; import { useForm } from 'react-hook-form'; import { useRootStore } from 'hooks/useRootStore'; @@ -63,6 +64,7 @@ const CreateUserModal: React.FC = ({ me }) => { resolver: yupResolver(CreateUserSchema), }); const [loading, setLoading] = useState(false); + const isMountedRef = useIsMountedRef(); const toast = useToast(); const handleError = useErrorHandler(); @@ -84,12 +86,18 @@ const CreateUserModal: React.FC = ({ me }) => { onClose(); } catch (error: any) { if (error.response) { - toast({ title: `${error.message}`, status: 'error' }); + 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 { - setLoading(false); + if (isMountedRef.current) { + setLoading(false); + } } }; diff --git a/src/pages/users/components/CreateUserModal/CreateUserModal.validator.ts b/src/pages/users/components/CreateUserForm/CreateUserModal.validator.ts similarity index 100% rename from src/pages/users/components/CreateUserModal/CreateUserModal.validator.ts rename to src/pages/users/components/CreateUserForm/CreateUserModal.validator.ts diff --git a/src/pages/users/components/CreateUserForm/index.ts b/src/pages/users/components/CreateUserForm/index.ts new file mode 100644 index 0000000..f1da973 --- /dev/null +++ b/src/pages/users/components/CreateUserForm/index.ts @@ -0,0 +1,3 @@ +import CreateUserForm from './CreateUserForm'; + +export default CreateUserForm; diff --git a/src/pages/users/components/CreateUserModal/index.ts b/src/pages/users/components/CreateUserModal/index.ts deleted file mode 100644 index acf6db5..0000000 --- a/src/pages/users/components/CreateUserModal/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CreateUserModal from './CreateUserModal'; - -export default CreateUserModal; diff --git a/src/pages/users/components/UpdateUserForm/UpdateUserForm.tsx b/src/pages/users/components/UpdateUserForm/UpdateUserForm.tsx new file mode 100644 index 0000000..bc1600a --- /dev/null +++ b/src/pages/users/components/UpdateUserForm/UpdateUserForm.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalCloseButton, + ModalBody, + ModalFooter, + FormControl, + FormLabel, + Input, + Button, + Select, + Flex, + Text, + Divider, +} from '@chakra-ui/react'; +import { AddIcon } from '@chakra-ui/icons'; +import { MdModeEdit } from 'react-icons/md'; + +/** + * Types + */ +import { IUser } from 'interfaces'; +import { UserRoleEnum } from 'enums'; + +type UpdateUserFormPropsType = { + user: IUser; + onClose: () => void; + isOpen: boolean; +}; + +const UpdateUserForm: React.FC = ({ user, onClose, isOpen }) => { + return ( + + + +
+ + + + Edit user + + + + + + + First name + + + + Last name + + + + Login + + + + Email + + + + Role + + + + Password + + + + + + + + + +
+
+ ); +}; + +export default UpdateUserForm; diff --git a/src/pages/users/components/UpdateUserForm/UpdateUserForm.validator.ts b/src/pages/users/components/UpdateUserForm/UpdateUserForm.validator.ts new file mode 100644 index 0000000..b3013f8 --- /dev/null +++ b/src/pages/users/components/UpdateUserForm/UpdateUserForm.validator.ts @@ -0,0 +1,16 @@ +import yup from 'schema'; +import { UserRoleEnum } from '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'), + login: yup.string().required('Login field is required'), + email: yup.string().email('Email is invalid').required('Email field is required'), + role: yup + .mixed() + .required('Role field is required') + .oneOf(Object.values(UserRoleEnum), `Available roles: ${Object.keys(UserRoleEnum).join(', ')}`), + password: yup.string().required('Password field is required').min(6, 'Password must be at least 6 characters.'), +}); + +export default UpdateUserSchema; diff --git a/src/pages/users/components/UpdateUserForm/index.ts b/src/pages/users/components/UpdateUserForm/index.ts new file mode 100644 index 0000000..2d56e2f --- /dev/null +++ b/src/pages/users/components/UpdateUserForm/index.ts @@ -0,0 +1,3 @@ +import UpdateUserForm from './UpdateUserForm'; + +export default UpdateUserForm; diff --git a/src/pages/users/components/UserList/UserItem.tsx b/src/pages/users/components/UserList/UserItem.tsx index 75832c3..0a71658 100644 --- a/src/pages/users/components/UserList/UserItem.tsx +++ b/src/pages/users/components/UserList/UserItem.tsx @@ -1,14 +1,46 @@ import React from 'react'; -import { Box, Flex, Avatar, Text, Tooltip, IconButton } from '@chakra-ui/react'; +import * as helpres from 'helpers'; +import { Box, Flex, Avatar, Text, IconButton } from '@chakra-ui/react'; import { MdMoreHoriz } from 'react-icons/md'; import UserBadge from 'components/UserBadge'; + +/** + * Types + */ import { IUser } from 'interfaces'; +import { UserRoleEnum } from 'enums'; + +/** + * Hooks + */ +import { useRootStore } from 'hooks/useRootStore'; type UserItemPropsType = { user: IUser; + onEdit: (user: IUser) => void; }; -const UserItem: React.FC = ({ user }) => { +const UserItem: React.FC = ({ user, onEdit }) => { + const { + userStore: { me }, + } = useRootStore(); + + /** + * The user with role TEACHER can edit users with only STUDENT role + */ + const isDisabledActions = () => { + if (helpres.userContainRoles(user.roles, [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER]) && me !== null) { + /** + * Disable editing + */ + if (me.isTeacher || me.isStudent) { + return true; + } else return false; + } else { + return false; + } + }; + return ( @@ -25,9 +57,13 @@ const UserItem: React.FC = ({ user }) => { - - } /> - + onEdit(user)} + aria-label='Actions' + variant='ghost' + icon={} + /> diff --git a/src/pages/users/components/UserList/UserList.tsx b/src/pages/users/components/UserList/UserList.tsx index 26c48d7..71b217c 100644 --- a/src/pages/users/components/UserList/UserList.tsx +++ b/src/pages/users/components/UserList/UserList.tsx @@ -1,26 +1,43 @@ +import React from 'react'; +import { observer } from 'mobx-react'; import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons'; import { Flex, Box, Stack, Text } from '@chakra-ui/layout'; import { Button } from '@chakra-ui/button'; -import { IPaginationMeta, IUser } from 'interfaces'; -import React, { useContext } from 'react'; import UserItem from './UserItem'; -import { useRootStore } from 'hooks/useRootStore'; -import { useErrorHandler } from 'react-error-boundary'; -import { UsersPageContextType } from 'types'; -import { UsersPageContext } from 'contexts'; -import { observer } from 'mobx-react'; +import UpdateUserForm from '../UpdateUserForm'; -type UserListPropsType = { - users: IUser[]; - pagination: IPaginationMeta; -}; +/** + * Types + */ +import { IPaginationMeta, IUser } from 'interfaces'; +import { UsersPageContextType } from 'types'; + +/** + * Contexts + */ +import { UsersPageContext } from 'contexts'; + +/** + * Hooks + */ +import { useContext, useState } from 'react'; +import { useRootStore } from 'hooks/useRootStore'; +import { useDisclosure } from '@chakra-ui/hooks'; +import { useErrorHandler } from 'react-error-boundary'; + +type UserListPropsType = { users: IUser[]; pagination: IPaginationMeta }; const UserList: React.FC = ({ users, pagination }) => { const { userStore } = useRootStore(); + const [editingUser, setEditingUser] = useState(undefined); const { searchingRole, loading, setLoading, search } = useContext(UsersPageContext); const handleError = useErrorHandler(); const pagesCount = Math.ceil(pagination.total / pagination.per_page); + const { onOpen: onOpenEditModal, onClose: onCloseEditModal, isOpen: isOpenEditModal } = useDisclosure(); + /** + * Handle next page + */ const nextPage = async () => { try { setLoading(true); @@ -37,6 +54,9 @@ const UserList: React.FC = ({ users, pagination }) => { } }; + /** + * Handle prev page + */ const prevPage = async () => { try { setLoading(true); @@ -53,10 +73,20 @@ const UserList: React.FC = ({ users, pagination }) => { } }; + /** + * Set editing state when click on edit button + */ + const onEditUser = (user: IUser) => { + setEditingUser(user); + onOpenEditModal(); + console.log(`[LOG]: Editing user "${user.id}"`); + }; + return ( <> {pagination.total !== 0 ? ( <> + {editingUser && } Total: ({pagination.total}) @@ -69,7 +99,7 @@ const UserList: React.FC = ({ users, pagination }) => { ) : ( {users.map(user => ( - + ))} )} diff --git a/src/pages/users/index.tsx b/src/pages/users/index.tsx index 84fdcda..7e450e3 100644 --- a/src/pages/users/index.tsx +++ b/src/pages/users/index.tsx @@ -27,7 +27,7 @@ import { useLocation } from 'react-router'; * Context */ import { UsersPageContext } from 'contexts'; -import CreateUserModal from './components/CreateUserModal'; +import CreateUserForm from './components/CreateUserForm'; /** * Users Page @@ -54,8 +54,8 @@ const UsersPage: React.FC = ({ title }) => { - -