diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx
index 8c8c237..7645df6 100644
--- a/src/components/Header/Header.tsx
+++ b/src/components/Header/Header.tsx
@@ -2,7 +2,7 @@ import config from 'config';
import React from 'react';
import { observer } from 'mobx-react';
import { useRootStore } from 'hooks/useRootStore';
-import { Flex, Box, Heading, Text, useColorMode } from '@chakra-ui/react';
+import { Flex, Box, Heading, useColorMode } from '@chakra-ui/react';
import UserInfo from './UserInfo';
import UserNotifications from './UserNotifications';
import NavMobile from 'components/Nav/Mobile';
@@ -41,7 +41,6 @@ const Header: React.FC = () => {
{config.metaData.appName}
- {config.metaData.appDescription}
{isDesktop ? (
diff --git a/src/contexts/index.ts b/src/contexts/index.ts
index fcde296..dd2ff8d 100644
--- a/src/contexts/index.ts
+++ b/src/contexts/index.ts
@@ -1,10 +1,10 @@
import { createContext } from 'react';
-import { ProfilePageViewStatusContextType, UsersPageContextType } from 'types';
+import { ProfilePageContextType, UsersPageContextType } from 'types';
import RootStore from '../stores/RootStore';
export const StoreContext = createContext(undefined);
-export const ProfilePageContext = createContext({
+export const ProfilePageContext = createContext({
statusPageView: 'default',
setStatusPageView: () => {},
pageData: {},
diff --git a/src/pages/users/components/CreateUserForm/CreateUserForm.tsx b/src/pages/users/components/CreateUserForm/CreateUserForm.tsx
index 3082297..c94ae88 100644
--- a/src/pages/users/components/CreateUserForm/CreateUserForm.tsx
+++ b/src/pages/users/components/CreateUserForm/CreateUserForm.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useContext, useState } from 'react';
import {
Modal,
ModalOverlay,
@@ -38,6 +38,7 @@ import { useForm } from 'react-hook-form';
import { useRootStore } from 'hooks/useRootStore';
import { useErrorHandler } from 'react-error-boundary';
import { useToast } from '@chakra-ui/react';
+import { UsersPageContext } from 'contexts';
type CreateUserModalPropsType = {
me: IMe;
@@ -54,6 +55,7 @@ type CreateUserInputType = {
const CreateUserModal: React.FC = ({ me }) => {
const { userStore } = useRootStore();
+ const { searchingRole, search } = useContext(UsersPageContext);
const { isOpen, onOpen, onClose } = useDisclosure();
const {
register,
@@ -74,7 +76,12 @@ const CreateUserModal: React.FC = ({ me }) => {
const onSubmit: SubmitHandler = async data => {
try {
setLoading(true);
- await userStore.createUser(data);
+ await userStore.createUser(data, {
+ page: userStore.pagination?.current_page,
+ limit: userStore.pagination?.per_page,
+ role: searchingRole,
+ search,
+ });
/**
* Clear form state
*/
diff --git a/src/pages/users/components/DeleteUserDialog/DeleteUserDialog.tsx b/src/pages/users/components/DeleteUserDialog/DeleteUserDialog.tsx
index d589a63..8cf2743 100644
--- a/src/pages/users/components/DeleteUserDialog/DeleteUserDialog.tsx
+++ b/src/pages/users/components/DeleteUserDialog/DeleteUserDialog.tsx
@@ -25,7 +25,7 @@ type DeleteUserDialogPropsType = {
const DeleteUserDialog: React.FC = ({ onClose, isOpen }) => {
const { userStore } = useRootStore();
- const { deletingUser, setDeletingUser } = useContext(UsersPageContext);
+ const { deletingUser, setDeletingUser, searchingRole, search } = useContext(UsersPageContext);
const isMountedRef = useIsMountedRef();
const [isLoading, setIsLoading] = useState(false);
const cancelRef = useRef(null);
@@ -38,8 +38,13 @@ const DeleteUserDialog: React.FC = ({ onClose, isOpen
const onDelete = async (id: string) => {
try {
setIsLoading(true);
- await userStore.deleteUser(id);
- toast({ title: 'User deleted', status: 'info' });
+ await userStore.deleteUser(id, {
+ page: userStore.pagination?.current_page,
+ limit: userStore.pagination?.per_page,
+ role: searchingRole,
+ search,
+ });
+ toast({ title: 'User deleted.', status: 'info' });
onClose();
setDeletingUser(undefined);
} catch (error: any) {
diff --git a/src/pages/users/components/EditUserForm/EditUserForm.tsx b/src/pages/users/components/EditUserForm/EditUserForm.tsx
index 0a3dd77..91ebc36 100644
--- a/src/pages/users/components/EditUserForm/EditUserForm.tsx
+++ b/src/pages/users/components/EditUserForm/EditUserForm.tsx
@@ -1,4 +1,4 @@
-import React, { useContext } from 'react';
+import React, { useContext, useState } from 'react';
import {
Modal,
ModalOverlay,
@@ -12,26 +12,33 @@ import {
FormHelperText,
Input,
Button,
- Select,
Flex,
Text,
Divider,
} from '@chakra-ui/react';
-import { AddIcon } from '@chakra-ui/icons';
+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 { UserRoleEnum } from 'enums';
+/**
+ * Contexts
+ */
+import { UsersPageContext } from 'contexts';
+
/**
* Hooks
*/
import { SubmitHandler, useForm } from 'react-hook-form';
-import UpdateUserSchema from './EditUserForm.validator';
-import { UsersPageContext } from 'contexts';
+import { useRootStore } from 'hooks/useRootStore';
+import { useErrorHandler } from 'react-error-boundary';
+import { useToast } from '@chakra-ui/react';
+import useIsMountedRef from 'hooks/useIsMountedRef';
type UpdateUserInputType = {
first_name: string;
@@ -47,12 +54,14 @@ type UpdateUserFormPropsType = {
};
const UpdateUserForm: React.FC = ({ onClose, isOpen }) => {
+ const { userStore } = useRootStore();
const { editingUser, setEditingUser } = useContext(UsersPageContext);
+ const [isLoading, setIsLoading] = useState(false);
const {
register,
handleSubmit,
reset,
- formState: { errors },
+ formState: { errors, isDirty, dirtyFields },
} = useForm({
resolver: yupResolver(UpdateUserSchema),
defaultValues: {
@@ -61,13 +70,44 @@ const UpdateUserForm: React.FC = ({ onClose, isOpen })
email: editingUser && editingUser.email,
},
});
+ const isMountedRef = useIsMountedRef();
+ const toast = useToast();
+ const handleError = useErrorHandler();
- const onSubmit: SubmitHandler = data => {
- console.log(data);
+ if (editingUser === undefined) return null;
+
+ const onSubmit: SubmitHandler = async data => {
+ try {
+ setIsLoading(true);
+ const params = Object.fromEntries(Object.keys(dirtyFields).map(k => [k, data[k as keyof UpdateUserInputType]]));
+ await userStore.updateUser(editingUser.id, params);
+ toast({ title: 'User updated.', status: 'info' });
+ onCloseModal();
+ } catch (error: any) {
+ if (error.response) {
+ 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 {
+ if (isMountedRef.current) {
+ setIsLoading(false);
+ }
+ }
+ };
+
+ const onCloseModal = () => {
+ reset({});
+ setEditingUser(undefined);
+ onClose();
};
return (
-
+
diff --git a/src/pages/users/components/EditUserForm/EditUserForm.validator.ts b/src/pages/users/components/EditUserForm/EditUserForm.validator.ts
index b3013f8..db41324 100644
--- a/src/pages/users/components/EditUserForm/EditUserForm.validator.ts
+++ b/src/pages/users/components/EditUserForm/EditUserForm.validator.ts
@@ -1,16 +1,10 @@
import yup from 'schema';
-import { UserRoleEnum } from 'enums';
+// 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/UserList/UserItem.tsx b/src/pages/users/components/UserList/UserItem.tsx
index 4b0a2dc..305aa02 100644
--- a/src/pages/users/components/UserList/UserItem.tsx
+++ b/src/pages/users/components/UserList/UserItem.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import * as helpres from 'helpers';
-import { Box, Flex, Avatar, Text, IconButton } from '@chakra-ui/react';
+import { Box, Flex, Avatar, Text, IconButton, Link } from '@chakra-ui/react';
+import { Link as ReactRouterLink } from 'react-router-dom';
import UserBadge from 'components/UserBadge';
/**
@@ -48,9 +49,9 @@ const UserItem: React.FC = ({ user, onEdit, onDelete }) => {
-
+
{user.first_name} {user.last_name}
-
+
{user.email}
diff --git a/src/services/UserService.ts b/src/services/UserService.ts
index 5f18d8c..70acd5b 100644
--- a/src/services/UserService.ts
+++ b/src/services/UserService.ts
@@ -12,6 +12,7 @@ export type CreateUserParamsType = {
role: UserRoleEnum;
password: string;
};
+export type UpdateUserParamsType = { first_name?: string; last_name?: string; email?: string };
export default class UserService {
public api: AxiosInstance;
@@ -20,11 +21,22 @@ export default class UserService {
this.api = api;
}
+ /**
+ * Fetch authorized user
+ *
+ * @returns Authorized user data
+ */
public async fetchMe(): Promise> {
const result = await this.api.get('v1/me');
return result.data;
}
+ /**
+ * Fetch all users
+ *
+ * @param params Fetch users params
+ * @returns Array of users
+ */
public async fetchAll(params?: FetchUsersParamsType): Promise> {
const { page = 1, limit = 6, role, search } = params || {};
const result = await this.api.get('/v1/users', {
@@ -38,6 +50,12 @@ export default class UserService {
return result.data;
}
+ /**
+ * Create new user
+ *
+ * @param data Data for creating user
+ * @returns Created user
+ */
public async create(data: CreateUserParamsType): Promise> {
const result = await this.api.post('v1/users', {
first_name: data.first_name,
@@ -50,17 +68,48 @@ export default class UserService {
return result.data;
}
+ /**
+ * Delete user by id
+ *
+ * @param id User id
+ * @returns Deleted user
+ */
public async delete(id: string): Promise> {
const result = await this.api.delete(`v1/users/${id}`);
return result.data;
}
+ /**
+ * Update user by id
+ *
+ * @param id User id
+ * @param data Data to update
+ * @returns Updated user
+ */
+ public async update(id: string, data: UpdateUserParamsType): Promise> {
+ const result = await this.api.patch(`v1/users/${id}`, data);
+ return result.data;
+ }
+
+ /**
+ * Update contacts of authorized user
+ *
+ * @param data New values for contacts
+ * @returns Updated contacts
+ */
public async updateContacts(data: IUserContacts): Promise> {
const result = await this.api.put('v1/me/contacts', helpers.removeEmptyValues(data));
return result.data;
}
- public async updatePassword(oldPassword: string, newPassword: string): Promise> {
+ /**
+ * Update password of authorized user
+ *
+ * @param oldPassword Old password
+ * @param newPassword New password
+ * @returns Empty object
+ */
+ public async updatePassword(oldPassword: string, newPassword: string): Promise> {
const result = await this.api.patch('v1/me/password', {
oldPassword,
newPassword,
@@ -68,11 +117,24 @@ export default class UserService {
return result.data;
}
+ /**
+ * Request on update email of authorized user
+ *
+ * @param email New email
+ * @returns Expiration in seconds for confirmation code
+ */
public async updateEmail(email: string): Promise> {
const result = await this.api.patch('v1/me/email', { email });
return result.data;
}
+ /**
+ * Confirm updating email of authorized user
+ *
+ * @param email New email
+ * @param confirmationCode Code for confirmation
+ * @returns Updated email
+ */
public async updateEmailConfirm(
email: string,
confirmationCode: string
diff --git a/src/stores/UserStore.ts b/src/stores/UserStore.ts
index 7828fd1..322e20f 100644
--- a/src/stores/UserStore.ts
+++ b/src/stores/UserStore.ts
@@ -6,7 +6,7 @@ import * as helpers from 'helpers';
/**
* Services
*/
-import UserService, { CreateUserParamsType, FetchUsersParamsType } from 'services/UserService';
+import UserService, { CreateUserParamsType, UpdateUserParamsType, FetchUsersParamsType } from 'services/UserService';
/**
* Stores
@@ -78,15 +78,37 @@ export default class UserStore {
}
}
- public async createUser(data: CreateUserParamsType) {
+ public async createUser(data: CreateUserParamsType, paramsContext?: FetchUsersParamsType) {
try {
const result = await this.userService.create(data);
console.log(`[${this.constructor.name}]: ${result.message}`, result);
+ if (this.users !== null) {
+ /**
+ * Fetch updated users data with new pagination data
+ */
+ await this.loadUsersData(paramsContext);
+ }
+
+ return result;
+ } catch (error: any) {
+ throw error;
+ }
+ }
+
+ public async updateUser(id: string, params: UpdateUserParamsType) {
+ try {
+ const result = await this.userService.update(id, params);
+
+ console.log(`[${this.constructor.name}]: ${result.message}`, result);
+
runInAction(() => {
if (this.users !== null) {
- this.users.unshift(result.data);
+ const userIndex = this.users.findIndex(user => user.id === id);
+ if (userIndex !== -1) {
+ this.users[userIndex] = result.data;
+ }
}
});
@@ -96,17 +118,24 @@ export default class UserStore {
}
}
- public async deleteUser(id: string) {
+ /**
+ *
+ * @param id User id
+ * @param paramsContext Params context to refresh users store data
+ * @returns
+ */
+ public async deleteUser(id: string, paramsContext?: FetchUsersParamsType) {
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);
- }
- });
+ if (this.users !== null) {
+ /**
+ * Fetch updated users data with new pagination data
+ */
+ await this.loadUsersData(paramsContext);
+ }
return result;
} catch (error: any) {
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
index 7ad1ea0..60cc5d5 100644
--- a/src/types/index.d.ts
+++ b/src/types/index.d.ts
@@ -27,7 +27,7 @@ export type ProfilePageDataType = {
*/
confirmEmailData?: ConfirmEmailContainerDataType;
};
-export type ProfilePageViewStatusContextType = {
+export type ProfilePageContextType = {
/**
* Render current container by status
*/