diff --git a/src/components/LoadingPage/LoadingPage.tsx b/src/components/LoadingPage/LoadingPage.tsx index 2434480..e631171 100644 --- a/src/components/LoadingPage/LoadingPage.tsx +++ b/src/components/LoadingPage/LoadingPage.tsx @@ -3,7 +3,7 @@ import React from 'react'; const LoadingPage: React.FC = () => { return ( - + diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index b7cb630..281edab 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -57,12 +57,18 @@ export type UserCourseType = Pick< export interface IUser { id: string; - fullname: string; first_name: string; last_name: string; email: string; roles: IUserRole[]; contacts: IUserContacts | null; +} + +/** + * Current authenticated user data + */ +export interface IMe extends IUser { + fullname: string; /** * The field can be undefined if the user * does not have the STUDENT role @@ -89,6 +95,12 @@ export interface IDataResult { status: number; } +export interface IApiRespose { + data: T; + message: string; + status: number; +} + export interface ILoginResult extends IDataResult { data: { token: string; diff --git a/src/pages/profile/components/CourseList/CourseItem.tsx b/src/pages/profile/components/CourseList/CourseItem.tsx index 2e19976..2d0c9a9 100644 --- a/src/pages/profile/components/CourseList/CourseItem.tsx +++ b/src/pages/profile/components/CourseList/CourseItem.tsx @@ -10,7 +10,7 @@ type CourseItemPropsType = { const CourseItem: React.FC = ({ course }) => { return ( - + {course.title} diff --git a/src/pages/profile/components/CourseList/CourseList.tsx b/src/pages/profile/components/CourseList/CourseList.tsx index 25b7e22..1371a4b 100644 --- a/src/pages/profile/components/CourseList/CourseList.tsx +++ b/src/pages/profile/components/CourseList/CourseList.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { IUser } from 'interfaces'; +import { IMe } from 'interfaces'; import { Box, Text, Stack, StackDivider } from '@chakra-ui/layout'; import CourseItem from './CourseItem'; type CourseListPropsType = { - courses: IUser['courses']; + courses: IMe['courses']; }; /** diff --git a/src/pages/profile/components/LoadingPage/LoadingPage.tsx b/src/pages/profile/components/LoadingPage/LoadingPage.tsx index c8d01ae..08112a1 100644 --- a/src/pages/profile/components/LoadingPage/LoadingPage.tsx +++ b/src/pages/profile/components/LoadingPage/LoadingPage.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Box, Skeleton, SkeletonCircle, Flex, Spinner } from '@chakra-ui/react'; -const LoadingPage = () => { +const LoadingPage: React.FC = () => { return ( diff --git a/src/pages/users/components/LoadingPage.tsx b/src/pages/users/components/LoadingPage.tsx new file mode 100644 index 0000000..dd33779 --- /dev/null +++ b/src/pages/users/components/LoadingPage.tsx @@ -0,0 +1,11 @@ +import { Flex } from '@chakra-ui/layout'; +import React from 'react'; + +//TODO add some skeleton styling when loading this page +const LoadingPage: React.FC = () => { + console.log('123'); + + return Loading users...; +}; + +export default LoadingPage; diff --git a/src/pages/users/components/UserList/UserItem.tsx b/src/pages/users/components/UserList/UserItem.tsx new file mode 100644 index 0000000..75832c3 --- /dev/null +++ b/src/pages/users/components/UserList/UserItem.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Box, Flex, Avatar, Text, Tooltip, IconButton } from '@chakra-ui/react'; +import { MdMoreHoriz } from 'react-icons/md'; +import UserBadge from 'components/UserBadge'; +import { IUser } from 'interfaces'; + +type UserItemPropsType = { + user: IUser; +}; + +const UserItem: React.FC = ({ user }) => { + return ( + + + + + + + {user.first_name} {user.last_name} + + {user.email} + + + + + + + + } /> + + + + + ); +}; + +export default UserItem; diff --git a/src/pages/users/components/UserList/UserList.tsx b/src/pages/users/components/UserList/UserList.tsx new file mode 100644 index 0000000..b545206 --- /dev/null +++ b/src/pages/users/components/UserList/UserList.tsx @@ -0,0 +1,28 @@ +import { Box, Stack, Text } from '@chakra-ui/layout'; +import { IUser } from 'interfaces'; +import React from 'react'; +import UserItem from './UserItem'; + +type UserListPropsType = { + users: IUser[]; +}; + +const UserList: React.FC = ({ users }) => { + if (users.length === 0) { + return ( + + There are no users in the system + + ); + } + + return ( + + {users.map(user => ( + + ))} + + ); +}; + +export default UserList; diff --git a/src/pages/users/components/UserList/index.ts b/src/pages/users/components/UserList/index.ts new file mode 100644 index 0000000..a3c8e07 --- /dev/null +++ b/src/pages/users/components/UserList/index.ts @@ -0,0 +1,3 @@ +import UserList from './UserList'; + +export default UserList; diff --git a/src/pages/users/index.tsx b/src/pages/users/index.tsx index 8372daa..0b2167c 100644 --- a/src/pages/users/index.tsx +++ b/src/pages/users/index.tsx @@ -1,24 +1,46 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { observer } from 'mobx-react'; import { Select } from '@chakra-ui/react'; -import { Box, Flex, Text, Button, Heading, Badge, Avatar, Stack, Tooltip } from '@chakra-ui/react'; -import { Input, InputGroup, InputLeftElement, IconButton } from '@chakra-ui/react'; -import { IPageProps } from 'interfaces'; +import { Box, Flex, Text, Button, Heading } from '@chakra-ui/react'; +import { Input, InputGroup, InputLeftElement } from '@chakra-ui/react'; import { AddIcon, ChevronLeftIcon, ChevronRightIcon, SearchIcon } from '@chakra-ui/icons'; -import { MdMoreHoriz } from 'react-icons/md'; import { FaFileExcel } from 'react-icons/fa'; +/** + * Types + */ +import { IPageProps } from 'interfaces'; + +/** + * Components + */ +import UserList from './components/UserList'; +import LoadingPage from './components/LoadingPage'; + +/** + *Hooks + */ +import { useRootStore } from 'hooks/useRootStore'; +import { useErrorHandler } from 'react-error-boundary'; + /** * Users Page */ const UsersPage: React.FC = ({ title }) => { + const { userStore } = useRootStore(); + const handleError = useErrorHandler(); + + useEffect(() => { + userStore.loadUsersData().catch(error => handleError(error)); + }, [handleError, userStore]); + return ( - + User management - - - - + {userStore.users !== null ? ( + + + + + + + + + + + Total users: ({userStore.users.length}) + Actions + + - - - + + + Page 1 of 23 + + + + + - - - Total users: (24) - Actions - - - {[1, 2, 3, 4, 5, 6, 7].map(e => ( - - - - - - - Sergey Yarkov - - - serzh.yarkov@gmail.com - - - - - - Administrator - - - - - } /> - - - - - ))} - - - - - Page 1 of 23 - - - - - - - + ) : ( + + )} ); }; diff --git a/src/services/UserService.ts b/src/services/UserService.ts index 04a411f..d56f8f8 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -1,11 +1,13 @@ import { AxiosInstance } from 'axios'; import { + IApiRespose, IDataResult, + IMe, ISentCodeResult, IUpdatedContactsResult, IUpdatedUserEmail, + IUser, IUserContacts, - IUserResult, } from 'interfaces'; import * as helpers from 'helpers'; @@ -16,11 +18,16 @@ export default class UserService { this.api = api; } - public async fetchMe(): Promise { + public async fetchMe(): Promise> { const result = await this.api.get('v1/me'); return result.data; } + public async fetchAll(): Promise> { + const result = await this.api.get('/v1/users'); + 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 e67067b..cb3ecb1 100644 --- a/src/stores/UserStore.ts +++ b/src/stores/UserStore.ts @@ -1,13 +1,13 @@ import { AxiosInstance } from 'axios'; import { makeAutoObservable, runInAction } from 'mobx'; import { - IUserResult, IUser, IUserContacts, IUpdatedContactsResult, IDataResult, ISentCodeResult, IUpdatedUserEmail, + IMe, } from 'interfaces'; /** @@ -25,9 +25,9 @@ export default class UserStore { public userService: UserService; - public me: IUser | null = null; + public me: IMe | null = null; - public users: IUser[] = []; + public users: IUser[] | null = null; constructor(root: RootStore, api: AxiosInstance) { this.root = root; @@ -35,19 +35,29 @@ export default class UserStore { makeAutoObservable(this); } - /** - * Initialize authorized user in store - * - * @returns User result - */ - public async loadCurrentUserData(): Promise { + public async loadUsersData() { + try { + const result = await this.userService.fetchAll(); + console.log(`[${this.constructor.name}]: ${result.message}`, result); + + runInAction(() => { + this.users = result.data; + }); + + return result; + } catch (error: any) { + throw error; + } + } + + public async loadCurrentUserData() { try { const result = await this.userService.fetchMe(); const { data: { id, first_name, last_name, email, roles, contacts, courses }, } = result; - console.log(`[UserStore]: ${result.message}`, result); + console.log(`[${this.constructor.name}]: ${result.message}`, result); runInAction(() => { this.me = {