From ee0bdb0e4a53f18ab06b6e78d90d7c779da35b77 Mon Sep 17 00:00:00 2001 From: Sergey Yarkov Date: Thu, 23 Sep 2021 22:03:44 +0300 Subject: [PATCH] feat: deleting user from dialog --- src/contexts/index.ts | 2 + .../components/CourseList/CourseItem.tsx | 13 +-- .../DeleteUserDialog/DeleteUserDialog.tsx | 98 +++++++++++++++++++ .../components/DeleteUserDialog/index.ts | 3 + .../components/EditUserForm/EditUserForm.tsx | 84 +++++++++++++--- .../users/components/UserList/UserItem.tsx | 18 +++- .../users/components/UserList/UserList.tsx | 24 ++++- src/pages/users/index.tsx | 2 +- src/providers/index.tsx | 14 ++- src/services/UserService.ts | 5 + src/stores/UserStore.ts | 18 ++++ src/types/index.d.ts | 8 +- 12 files changed, 258 insertions(+), 31 deletions(-) create mode 100644 src/pages/users/components/DeleteUserDialog/DeleteUserDialog.tsx create mode 100644 src/pages/users/components/DeleteUserDialog/index.ts diff --git a/src/contexts/index.ts b/src/contexts/index.ts index 8fcbee7..fcde296 100644 --- a/src/contexts/index.ts +++ b/src/contexts/index.ts @@ -20,4 +20,6 @@ export const UsersPageContext = createContext({ setSearch: () => {}, editingUser: undefined, setEditingUser: () => {}, + deletingUser: undefined, + setDeletingUser: () => {}, }); diff --git a/src/pages/profile/components/CourseList/CourseItem.tsx b/src/pages/profile/components/CourseList/CourseItem.tsx index 2d0c9a9..5b97452 100644 --- a/src/pages/profile/components/CourseList/CourseItem.tsx +++ b/src/pages/profile/components/CourseList/CourseItem.tsx @@ -1,8 +1,9 @@ import React from 'react'; -import { Text, Badge, Link, Box, Flex } from '@chakra-ui/layout'; +import { Text, Link, Box, Flex } from '@chakra-ui/layout'; import { Link as ReactRouterLink } from 'react-router-dom'; import { MdVideoLibrary, MdGroup, MdThumbUp } from 'react-icons/md'; import { UserCourseType } from 'interfaces'; +import { Tag, TagLabel } from '@chakra-ui/react'; type CourseItemPropsType = { course: UserCourseType; @@ -15,11 +16,11 @@ const CourseItem: React.FC = ({ course }) => { {course.title} - - - {course.category.title} - - + + + {course.category.title} + + {course.description} diff --git a/src/pages/users/components/DeleteUserDialog/DeleteUserDialog.tsx b/src/pages/users/components/DeleteUserDialog/DeleteUserDialog.tsx new file mode 100644 index 0000000..d589a63 --- /dev/null +++ b/src/pages/users/components/DeleteUserDialog/DeleteUserDialog.tsx @@ -0,0 +1,98 @@ +import React, { useContext, useRef, useState } from 'react'; +import { + AlertDialog, + AlertDialogBody, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogContent, + AlertDialogOverlay, + Button, +} from '@chakra-ui/react'; +import { UsersPageContext } from 'contexts'; + +/** + * Hooks + */ +import { useRootStore } from 'hooks/useRootStore'; +import useIsMountedRef from 'hooks/useIsMountedRef'; +import { useToast } from '@chakra-ui/react'; +import { useErrorHandler } from 'react-error-boundary'; + +type DeleteUserDialogPropsType = { + onClose: () => void; + isOpen: boolean; +}; + +const DeleteUserDialog: React.FC = ({ onClose, isOpen }) => { + const { userStore } = useRootStore(); + const { deletingUser, setDeletingUser } = useContext(UsersPageContext); + const isMountedRef = useIsMountedRef(); + const [isLoading, setIsLoading] = useState(false); + const cancelRef = useRef(null); + const handleError = useErrorHandler(); + const toast = useToast(); + + /** + * Delete user handler + */ + const onDelete = async (id: string) => { + try { + setIsLoading(true); + await userStore.deleteUser(id); + toast({ title: 'User deleted', status: 'info' }); + onClose(); + setDeletingUser(undefined); + } catch (error: any) { + if (error.response) { + toast({ title: error.message, status: 'error' }); + } else { + handleError(error); + } + } finally { + if (isMountedRef.current) { + setIsLoading(false); + } + } + }; + + if (deletingUser === undefined) return null; + + return ( + <> + + + + + Confirm deleting + + + The user named "{deletingUser.first_name} {deletingUser.last_name}" will be removed from the system. + + + + + + + + + + ); +}; + +export default DeleteUserDialog; diff --git a/src/pages/users/components/DeleteUserDialog/index.ts b/src/pages/users/components/DeleteUserDialog/index.ts new file mode 100644 index 0000000..b3f8372 --- /dev/null +++ b/src/pages/users/components/DeleteUserDialog/index.ts @@ -0,0 +1,3 @@ +import DeleteUserDialog from './DeleteUserDialog'; + +export default DeleteUserDialog; diff --git a/src/pages/users/components/EditUserForm/EditUserForm.tsx b/src/pages/users/components/EditUserForm/EditUserForm.tsx index bc1600a..0a3dd77 100644 --- a/src/pages/users/components/EditUserForm/EditUserForm.tsx +++ b/src/pages/users/components/EditUserForm/EditUserForm.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { Modal, ModalOverlay, @@ -9,6 +9,7 @@ import { ModalFooter, FormControl, FormLabel, + FormHelperText, Input, Button, Select, @@ -18,25 +19,58 @@ import { } from '@chakra-ui/react'; import { AddIcon } from '@chakra-ui/icons'; import { MdModeEdit } from 'react-icons/md'; +import { yupResolver } from '@hookform/resolvers/yup'; /** * Types */ -import { IUser } from 'interfaces'; import { UserRoleEnum } from 'enums'; +/** + * Hooks + */ +import { SubmitHandler, useForm } from 'react-hook-form'; +import UpdateUserSchema from './EditUserForm.validator'; +import { UsersPageContext } from 'contexts'; + +type UpdateUserInputType = { + first_name: string; + last_name: string; + login: string; + email: string; + role: UserRoleEnum; + password: string; +}; type UpdateUserFormPropsType = { - user: IUser; onClose: () => void; isOpen: boolean; }; -const UpdateUserForm: React.FC = ({ user, onClose, isOpen }) => { +const UpdateUserForm: React.FC = ({ onClose, isOpen }) => { + const { editingUser, setEditingUser } = useContext(UsersPageContext); + const { + register, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + resolver: yupResolver(UpdateUserSchema), + defaultValues: { + first_name: editingUser && editingUser.first_name, + last_name: editingUser && editingUser.last_name, + email: editingUser && editingUser.email, + }, + }); + + const onSubmit: SubmitHandler = data => { + console.log(data); + }; + return ( -
+ @@ -48,31 +82,43 @@ const UpdateUserForm: React.FC = ({ user, onClose, isOp First name - + + {errors.first_name ? {errors.first_name.message} : null} Last name - + + {errors.last_name ? {errors.last_name.message} : null} Login - + + {errors.login ? {errors.login.message} : null} Email - + + {errors.email ? {errors.email.message} : null} Role - + {errors.role ? {errors.role.message} : null} Password - + + + {errors.password ? errors.password.message : 'Must be at least 6 characters.'} + @@ -80,14 +126,22 @@ const UpdateUserForm: React.FC = ({ user, onClose, isOp + -
diff --git a/src/pages/users/components/UserList/UserItem.tsx b/src/pages/users/components/UserList/UserItem.tsx index 0a71658..4b0a2dc 100644 --- a/src/pages/users/components/UserList/UserItem.tsx +++ b/src/pages/users/components/UserList/UserItem.tsx @@ -1,7 +1,6 @@ import React from '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'; /** @@ -14,13 +13,15 @@ import { UserRoleEnum } from 'enums'; * Hooks */ import { useRootStore } from 'hooks/useRootStore'; +import { DeleteIcon, EditIcon } from '@chakra-ui/icons'; type UserItemPropsType = { user: IUser; onEdit: (user: IUser) => void; + onDelete: (user: IUser) => void; }; -const UserItem: React.FC = ({ user, onEdit }) => { +const UserItem: React.FC = ({ user, onEdit, onDelete }) => { const { userStore: { me }, } = useRootStore(); @@ -60,9 +61,18 @@ const UserItem: React.FC = ({ user, onEdit }) => { onEdit(user)} - aria-label='Actions' + aria-label='Edit' variant='ghost' - icon={} + mr={1} + icon={} + /> + onDelete(user)} + colorScheme='red' + aria-label='Delete' + variant='ghost' + icon={} />
diff --git a/src/pages/users/components/UserList/UserList.tsx b/src/pages/users/components/UserList/UserList.tsx index b25ae40..87102a1 100644 --- a/src/pages/users/components/UserList/UserList.tsx +++ b/src/pages/users/components/UserList/UserList.tsx @@ -3,6 +3,10 @@ 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'; + +/** + * Components + */ import UserItem from './UserItem'; import EditUserForm from '../EditUserForm'; @@ -24,16 +28,18 @@ import { useContext } from 'react'; import { useRootStore } from 'hooks/useRootStore'; import { useDisclosure } from '@chakra-ui/hooks'; import { useErrorHandler } from 'react-error-boundary'; +import DeleteUserDialog from '../DeleteUserDialog'; type UserListPropsType = { users: IUser[]; pagination: IPaginationMeta }; const UserList: React.FC = ({ users, pagination }) => { const { userStore } = useRootStore(); - const { searchingRole, loading, setLoading, search, editingUser, setEditingUser } = + const { searchingRole, loading, setLoading, search, editingUser, setEditingUser, deletingUser, setDeletingUser } = useContext(UsersPageContext); const handleError = useErrorHandler(); const pagesCount = Math.ceil(pagination.total / pagination.per_page); const { onOpen: onOpenEditModal, onClose: onCloseEditModal, isOpen: isOpenEditModal } = useDisclosure(); + const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure(); /** * Handle next page @@ -75,6 +81,7 @@ const UserList: React.FC = ({ users, pagination }) => { /** * Set editing state when click on edit button + * and open editing user form modal */ const onEditUser = (user: IUser) => { setEditingUser(user); @@ -82,11 +89,22 @@ const UserList: React.FC = ({ users, pagination }) => { console.log(`[LOG]: Editing user "${user.id}"`); }; + /** + * Set deleting state when click on delete button + * and open delete user dialog + */ + const onDeleteUser = (user: IUser) => { + setDeletingUser(user); + onOpenDeleteDialog(); + console.log(`[LOG]: Alert delete "${user.id}"`); + }; + return ( <> {pagination.total !== 0 ? ( <> - {editingUser && } + {editingUser && } + {deletingUser && } Total: ({pagination.total}) @@ -99,7 +117,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 aeadb6b..c0193d1 100644 --- a/src/pages/users/index.tsx +++ b/src/pages/users/index.tsx @@ -44,7 +44,7 @@ const UsersPage: React.FC = () => { User management - You can add or delete your users on this page. + You can add or delete users on this page. {userStore.users !== null && userStore.me !== null && userStore.pagination !== undefined ? ( diff --git a/src/providers/index.tsx b/src/providers/index.tsx index 9b9f34f..bbaa867 100644 --- a/src/providers/index.tsx +++ b/src/providers/index.tsx @@ -35,10 +35,22 @@ export const UsersPageContextProvider: React.FC = ({ children }) => { const [loading, setLoading] = useState(false); const [search, setSearch] = useState(undefined); const [editingUser, setEditingUser] = useState(undefined); + const [deletingUser, setDeletingUser] = useState(undefined); return ( {children} diff --git a/src/services/UserService.ts b/src/services/UserService.ts index 88982cc..5f18d8c 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -50,6 +50,11 @@ export default class UserService { return result.data; } + public async delete(id: string): Promise> { + const result = await this.api.delete(`v1/users/${id}`); + return result.data; + } + public async updateContacts(data: IUserContacts): Promise> { const result = await this.api.put('v1/me/contacts', helpers.removeEmptyValues(data)); return result.data; diff --git a/src/stores/UserStore.ts b/src/stores/UserStore.ts index ed17f75..7828fd1 100644 --- a/src/stores/UserStore.ts +++ b/src/stores/UserStore.ts @@ -96,6 +96,24 @@ export default class UserStore { } } + public async deleteUser(id: string) { + try { + const result = await this.userService.delete(id); + + console.log(`[${this.constructor.name}]: ${result.message}`, result); + + runInAction(() => { + if (this.users !== null) { + this.users = this.users.filter(user => user.id !== id); + } + }); + + return result; + } catch (error: any) { + throw error; + } + } + /** * Update contacts of authorized user * diff --git a/src/types/index.d.ts b/src/types/index.d.ts index f9aad17..7ad1ea0 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -62,8 +62,14 @@ export type UsersPageContextType = { setSearch: React.Dispatch>; /** - * Set editing user state when press on action button + * Set editing user state when press on edit button */ editingUser: IUser | undefined; setEditingUser: React.Dispatch>; + + /** + * Set deliting user state when press on delete button + */ + deletingUser: IUser | undefined; + setDeletingUser: React.Dispatch>; };