refactor: page contexts

This commit is contained in:
Sergey Yarkov 2021-09-23 17:50:59 +03:00
parent 99e22a4c69
commit 3a4296f3bd
22 changed files with 247 additions and 177 deletions

View File

@ -43,7 +43,7 @@ const App = () => {
<PrivateRoute <PrivateRoute
path='/users' path='/users'
component={UsersPage} component={UsersPage}
title='Control' title='Users Management'
roles={[UserRoleEnum.ADMIN, UserRoleEnum.TEACHER]} roles={[UserRoleEnum.ADMIN, UserRoleEnum.TEACHER]}
/> />
<PublicRoute path='/auth' component={AuthPage} /> <PublicRoute path='/auth' component={AuthPage} />

View File

@ -4,9 +4,11 @@ import RootStore from '../stores/RootStore';
export const StoreContext = createContext<RootStore | undefined>(undefined); export const StoreContext = createContext<RootStore | undefined>(undefined);
export const ProfilePageViewContext = createContext<ProfilePageViewStatusContextType>({ export const ProfilePageContext = createContext<ProfilePageViewStatusContextType>({
statusPageView: 'default', statusPageView: 'default',
setStatusPageView: () => {}, setStatusPageView: () => {},
pageData: {},
setPageData: () => {},
}); });
export const UsersPageContext = createContext<UsersPageContextType>({ export const UsersPageContext = createContext<UsersPageContextType>({
@ -16,4 +18,6 @@ export const UsersPageContext = createContext<UsersPageContextType>({
setLoading: () => {}, setLoading: () => {},
search: undefined, search: undefined,
setSearch: () => {}, setSearch: () => {},
editingUser: undefined,
setEditingUser: () => {},
}); });

View File

@ -2,12 +2,12 @@ import { useState } from 'react';
import { useToast } from '@chakra-ui/react'; import { useToast } from '@chakra-ui/react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useRootStore } from './useRootStore'; import { useRootStore } from './useRootStore';
import { IDataResult } from 'interfaces'; import { IApiRespose } from 'interfaces';
import { useErrorHandler } from 'react-error-boundary'; import { useErrorHandler } from 'react-error-boundary';
import useIsMountedRef from './useIsMountedRef'; import useIsMountedRef from './useIsMountedRef';
type LogoutStateType = { type LogoutStateType = {
result: IDataResult | null; result: IApiRespose<{}> | null;
error: any; error: any;
loading: boolean; loading: boolean;
fetched: boolean; fetched: boolean;

View File

@ -1,12 +1,12 @@
import { useToast } from '@chakra-ui/react'; import { useToast } from '@chakra-ui/react';
import { IDataResult, IUserContacts } from 'interfaces'; import { IApiRespose, IUserContacts } from 'interfaces';
import { useState } from 'react'; import { useState } from 'react';
import { useErrorHandler } from 'react-error-boundary'; import { useErrorHandler } from 'react-error-boundary';
import useIsMountedRef from './useIsMountedRef'; import useIsMountedRef from './useIsMountedRef';
import { useRootStore } from './useRootStore'; import { useRootStore } from './useRootStore';
type UpdateContactsStateType = { type UpdateContactsStateType = {
result: IDataResult | null; result: IApiRespose<{}> | null;
error: any; error: any;
loading: boolean; loading: boolean;
fetched: boolean; fetched: boolean;

View File

@ -1,12 +1,12 @@
import { useToast } from '@chakra-ui/toast'; import { useToast } from '@chakra-ui/toast';
import { ISentCodeResult } from 'interfaces'; import { IApiRespose } from 'interfaces';
import { useState } from 'react'; import { useState } from 'react';
import { useErrorHandler } from 'react-error-boundary'; import { useErrorHandler } from 'react-error-boundary';
import useIsMountedRef from './useIsMountedRef'; import useIsMountedRef from './useIsMountedRef';
import { useRootStore } from './useRootStore'; import { useRootStore } from './useRootStore';
type UpdateUserEmailStateType = { type UpdateUserEmailStateType = {
result: ISentCodeResult | null; result: IApiRespose<{ expired_seconds: number }> | null;
error: any; error: any;
loading: boolean; loading: boolean;
fetched: boolean; fetched: boolean;

View File

@ -1,14 +1,14 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { Box, Heading, Stack, Flex, StackDivider, Text, Button } from '@chakra-ui/react'; import { Box, Heading, Stack, Flex, StackDivider, Text, Button } from '@chakra-ui/react';
import { IUser } from 'interfaces'; import { IUser } from 'interfaces';
import { ProfilePageViewContext } from 'contexts'; import { ProfilePageContext } from 'contexts';
type UserAccountInfoPropsType = { type UserAccountInfoPropsType = {
user: IUser; user: IUser;
}; };
const UserAccountInfo: React.FC<UserAccountInfoPropsType> = ({ user }) => { const UserAccountInfo: React.FC<UserAccountInfoPropsType> = ({ user }) => {
const { setStatusPageView } = useContext(ProfilePageViewContext); const { setStatusPageView } = useContext(ProfilePageContext);
return ( return (
<Box borderRadius='md' borderWidth='1px' padding='20px' boxShadow='sm'> <Box borderRadius='md' borderWidth='1px' padding='20px' boxShadow='sm'>
@ -45,7 +45,7 @@ const UserAccountInfo: React.FC<UserAccountInfoPropsType> = ({ user }) => {
{user.email} {user.email}
</Text> </Text>
</Box> </Box>
<Button onClick={() => setStatusPageView({ status: 'update-email' })}>Edit</Button> <Button onClick={() => setStatusPageView('update-email')}>Edit</Button>
</Flex> </Flex>
<Flex justifyContent='space-between' alignItems='center'> <Flex justifyContent='space-between' alignItems='center'>
<Box> <Box>
@ -56,7 +56,7 @@ const UserAccountInfo: React.FC<UserAccountInfoPropsType> = ({ user }) => {
********** **********
</Text> </Text>
</Box> </Box>
<Button onClick={() => setStatusPageView({ status: 'update-password' })}>Change</Button> <Button onClick={() => setStatusPageView('update-password')}>Change</Button>
</Flex> </Flex>
</Stack> </Stack>
</Box> </Box>

View File

@ -7,7 +7,7 @@ import useUpdateUserEmailQuery from 'hooks/useUpdateUserEmailQuery.ts';
import { useRootStore } from 'hooks/useRootStore'; import { useRootStore } from 'hooks/useRootStore';
import { useToast } from '@chakra-ui/toast'; import { useToast } from '@chakra-ui/toast';
import { useErrorHandler } from 'react-error-boundary'; import { useErrorHandler } from 'react-error-boundary';
import { ProfilePageViewContext } from 'contexts'; import { ProfilePageContext } from 'contexts';
import useIsMountedRef from 'hooks/useIsMountedRef'; import useIsMountedRef from 'hooks/useIsMountedRef';
type ConfirmEmailContainerPropsType = { type ConfirmEmailContainerPropsType = {
@ -16,7 +16,7 @@ type ConfirmEmailContainerPropsType = {
const ConfirmEmailContainer: React.FC<ConfirmEmailContainerPropsType> = ({ data }) => { const ConfirmEmailContainer: React.FC<ConfirmEmailContainerPropsType> = ({ data }) => {
const { userStore } = useRootStore(); const { userStore } = useRootStore();
const { setStatusPageView } = useContext(ProfilePageViewContext); const { setStatusPageView } = useContext(ProfilePageContext);
const { sendConfirmationCode, loading } = useUpdateUserEmailQuery(); const { sendConfirmationCode, loading } = useUpdateUserEmailQuery();
const [isVerifying, setIsVerifying] = useState<boolean>(false); const [isVerifying, setIsVerifying] = useState<boolean>(false);
const [code, setCode] = useState<string>(''); const [code, setCode] = useState<string>('');
@ -47,7 +47,7 @@ const ConfirmEmailContainer: React.FC<ConfirmEmailContainerPropsType> = ({ data
status: 'info', status: 'info',
}); });
if (isMountedRef.current) { if (isMountedRef.current) {
setStatusPageView({ status: 'default' }); setStatusPageView('default');
} }
} catch (error: any) { } catch (error: any) {
if (error.response) { if (error.response) {
@ -76,8 +76,10 @@ const ConfirmEmailContainer: React.FC<ConfirmEmailContainerPropsType> = ({ data
handleError(error); handleError(error);
} }
} finally { } finally {
if (isMountedRef.current) {
setIsVerifying(false); setIsVerifying(false);
} }
}
}; };
const resendCodeHandler = async () => { const resendCodeHandler = async () => {

View File

@ -0,0 +1,105 @@
import React from 'react';
import { Box, Heading, Flex, Avatar, Text, Tabs, TabList, Tab, TabPanels, TabPanel } from '@chakra-ui/react';
/**
* Components
*/
import UserBadge from 'components/UserBadge';
import UserAccountInfo from 'pages/profile/components/UserAccountInfo';
import UpdateUserContactsForm from 'pages/profile/components/UpdateUserContactsForm';
import CourseList from 'pages/profile/components/CourseList';
/**
* Hooks
*/
import { useRootStore } from 'hooks/useRootStore';
import { useColorMode } from '@chakra-ui/color-mode';
const MainContainer: React.FC = () => {
const {
userStore: { me },
} = useRootStore();
const { colorMode } = useColorMode();
if (me === null) return null;
return (
<Box maxW='900px'>
<Heading as='h1'>My account</Heading>
<Text mt='2'>Update you contacts information or password here.</Text>
<Flex
alignItems='center'
mt='20'
sx={{
'@media (max-width: 768px)': {
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
mt: '10',
},
}}
>
<Box>
<Avatar border={`4px solid ${colorMode === 'dark' ? '#2D3748' : '#E2E8F0'}`} size='2xl' name={me.fullname} />
</Box>
<Flex
ml='50'
flexDirection='column'
sx={{
'@media (max-width: 768px)': {
ml: '0',
mt: '5',
},
}}
>
<Heading as='h2'>{me.fullname}</Heading>
<Flex
alignItems='center'
sx={{
'@media (max-width: 768px)': {
flexDirection: 'column',
},
}}
>
<Text
mr='10px'
sx={{
'@media (max-width: 768px)': {
mr: '0',
mb: '5px',
},
}}
>
{me.email}
</Text>
<UserBadge roles={me.roles} />
</Flex>
</Flex>
</Flex>
<Box mt='10'>
<Tabs>
<TabList>
<Tab>Account information</Tab>
<Tab>My contacts</Tab>
{me.isStudent && <Tab>Available courses</Tab>}
</TabList>
<TabPanels>
<TabPanel>
<UserAccountInfo user={me} />
</TabPanel>
<TabPanel>
<UpdateUserContactsForm contacts={me.contacts} />
</TabPanel>
{me.isStudent && (
<TabPanel>
<CourseList courses={me.courses} />
</TabPanel>
)}
</TabPanels>
</Tabs>
</Box>
</Box>
);
};
export default MainContainer;

View File

@ -0,0 +1,3 @@
import MainContainer from './MainContainer';
export default MainContainer;

View File

@ -1,12 +1,12 @@
import { IconButton } from '@chakra-ui/button'; import { IconButton } from '@chakra-ui/button';
import { ChevronLeftIcon } from '@chakra-ui/icons'; import { ChevronLeftIcon } from '@chakra-ui/icons';
import { Box, Flex, Heading, Text } from '@chakra-ui/layout'; import { Box, Flex, Heading, Text } from '@chakra-ui/layout';
import { ProfilePageViewContext } from 'contexts'; import { ProfilePageContext } from 'contexts';
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import UpdateEmailForm from './UpdateEmailForm'; import UpdateEmailForm from './UpdateEmailForm';
const UpdateEmailContainer: React.FC = () => { const UpdateEmailContainer: React.FC = () => {
const { setStatusPageView } = useContext(ProfilePageViewContext); const { setStatusPageView } = useContext(ProfilePageContext);
return ( return (
<Box maxW='700px'> <Box maxW='700px'>
@ -15,7 +15,7 @@ const UpdateEmailContainer: React.FC = () => {
aria-label='Back' aria-label='Back'
borderRadius='full' borderRadius='full'
icon={<ChevronLeftIcon />} icon={<ChevronLeftIcon />}
onClick={() => setStatusPageView({ status: 'default' })} onClick={() => setStatusPageView('default')}
mr='5' mr='5'
/> />
<Heading as='h1'>Edit email address</Heading> <Heading as='h1'>Edit email address</Heading>

View File

@ -3,7 +3,7 @@ import { FormControl, FormHelperText, FormLabel } from '@chakra-ui/form-control'
import { Input } from '@chakra-ui/input'; import { Input } from '@chakra-ui/input';
import { Box, Stack } from '@chakra-ui/layout'; import { Box, Stack } from '@chakra-ui/layout';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { ProfilePageViewContext } from 'contexts'; import { ProfilePageContext } from 'contexts';
import { useRootStore } from 'hooks/useRootStore'; import { useRootStore } from 'hooks/useRootStore';
import useUpdateUserEmailQuery from 'hooks/useUpdateUserEmailQuery.ts'; import useUpdateUserEmailQuery from 'hooks/useUpdateUserEmailQuery.ts';
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
@ -19,7 +19,7 @@ type UpdateEmailInputType = {
*/ */
const UpdateEmailForm: React.FC = () => { const UpdateEmailForm: React.FC = () => {
const [email, setEmail] = useState<string | null>(null); const [email, setEmail] = useState<string | null>(null);
const { setStatusPageView } = useContext(ProfilePageViewContext); const { setStatusPageView, setPageData } = useContext(ProfilePageContext);
const { userStore } = useRootStore(); const { userStore } = useRootStore();
const { sendConfirmationCode, loading, result } = useUpdateUserEmailQuery(); const { sendConfirmationCode, loading, result } = useUpdateUserEmailQuery();
const { const {
@ -32,10 +32,8 @@ const UpdateEmailForm: React.FC = () => {
useEffect(() => { useEffect(() => {
if (email !== null && result !== null) { if (email !== null && result !== null) {
setStatusPageView({ setStatusPageView('confirm-email');
status: 'confirm-email', setPageData({ confirmEmailData: { newEmail: email, expired_seconds: result.data.expired_seconds } });
data: { confirmEmailData: { newEmail: email, expired_seconds: result.data.expired_seconds } },
});
} }
}); });

View File

@ -1,11 +1,11 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { Box, Heading, Flex, Text, IconButton } from '@chakra-ui/react'; import { Box, Heading, Flex, Text, IconButton } from '@chakra-ui/react';
import { ChevronLeftIcon } from '@chakra-ui/icons'; import { ChevronLeftIcon } from '@chakra-ui/icons';
import { ProfilePageViewContext } from 'contexts'; import { ProfilePageContext } from 'contexts';
import UpdatePasswordForm from './UpdatePasswordForm'; import UpdatePasswordForm from './UpdatePasswordForm';
const UpdatePasswordContainer: React.FC = () => { const UpdatePasswordContainer: React.FC = () => {
const { setStatusPageView } = useContext(ProfilePageViewContext); const { setStatusPageView } = useContext(ProfilePageContext);
return ( return (
<Box maxW='700px'> <Box maxW='700px'>
@ -14,7 +14,7 @@ const UpdatePasswordContainer: React.FC = () => {
aria-label='Back' aria-label='Back'
borderRadius='full' borderRadius='full'
icon={<ChevronLeftIcon />} icon={<ChevronLeftIcon />}
onClick={() => setStatusPageView({ status: 'default' })} onClick={() => setStatusPageView('default')}
mr='5' mr='5'
/> />
<Heading as='h1'>Update password</Heading> <Heading as='h1'>Update password</Heading>

View File

@ -9,7 +9,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
import UpdatePasswordSchema from './UpdatePasswordForm.validator'; import UpdatePasswordSchema from './UpdatePasswordForm.validator';
import { useToast } from '@chakra-ui/toast'; import { useToast } from '@chakra-ui/toast';
import { useErrorHandler } from 'react-error-boundary'; import { useErrorHandler } from 'react-error-boundary';
import { ProfilePageViewContext } from 'contexts'; import { ProfilePageContext } from 'contexts';
import useIsMountedRef from 'hooks/useIsMountedRef'; import useIsMountedRef from 'hooks/useIsMountedRef';
type UpdatePasswordInputType = { type UpdatePasswordInputType = {
@ -22,7 +22,7 @@ type UpdatePasswordInputType = {
* Update password using form * Update password using form
*/ */
const UpdatePasswordForm: React.FC = () => { const UpdatePasswordForm: React.FC = () => {
const { setStatusPageView } = useContext(ProfilePageViewContext); const { setStatusPageView } = useContext(ProfilePageContext);
const { userStore } = useRootStore(); const { userStore } = useRootStore();
const isMountedRef = useIsMountedRef(); const isMountedRef = useIsMountedRef();
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
@ -45,7 +45,7 @@ const UpdatePasswordForm: React.FC = () => {
await userStore.updateCurrentUserPassword(old_password, new_password); await userStore.updateCurrentUserPassword(old_password, new_password);
toast({ title: 'Password changed.', status: 'success' }); toast({ title: 'Password changed.', status: 'success' });
reset(); reset();
setStatusPageView({ status: 'default' }); setStatusPageView('default');
} catch (error: any) { } catch (error: any) {
if (error.response) { if (error.response) {
if (error.response.status === 401) { if (error.response.status === 401) {

View File

@ -1,35 +1,15 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import {
Box,
Heading,
Flex,
Avatar,
useColorMode,
Text,
Tabs,
TabList,
Tab,
TabPanels,
TabPanel,
} from '@chakra-ui/react';
import { userHasRoles } from 'helpers';
/** /**
* Types * Types
*/ */
import { ProfilePageStatusType } from 'types';
import { IPageProps } from 'interfaces'; import { IPageProps } from 'interfaces';
import { UserRoleEnum } from 'enums';
/** /**
* Components * Components
*/ */
import CourseList from './components/CourseList';
import UserBadge from 'components/UserBadge';
import LoadingPage from './components/LoadingPage'; import LoadingPage from './components/LoadingPage';
import UserAccountInfo from './components/UserAccountInfo';
import UpdateUserContactsForm from './components/UpdateUserContactsForm';
/** /**
* Containers * Containers
@ -47,15 +27,22 @@ import { useHistory } from 'react-router-dom';
/** /**
* Contexts * Contexts
*/ */
import { ProfilePageViewContext } from 'contexts'; import { ProfilePageContext } from 'contexts';
/**
* Providers
*/
import { ProfilePageContextProvider } from 'providers';
import MainContainer from './containers/MainContainer';
/** /**
* Profile page * Profile page
*/ */
const ProfilePage: React.FC<IPageProps> = () => { const ProfilePage: React.FC<IPageProps> = () => {
const [statusPageView, setStatusPageView] = useState<ProfilePageStatusType>({ status: 'default' }); const {
const { colorMode } = useColorMode(); userStore: { me },
const { userStore, authStore } = useRootStore(); authStore,
} = useRootStore();
const history = useHistory(); const history = useHistory();
useEffect(() => { useEffect(() => {
@ -64,101 +51,29 @@ const ProfilePage: React.FC<IPageProps> = () => {
} }
}); });
if (userStore.me === null) { if (me === null) {
return <LoadingPage />; return <LoadingPage />;
} }
const isStudent = userHasRoles(userStore.me.roles, [UserRoleEnum.STUDENT]);
return ( return (
<ProfilePageViewContext.Provider value={{ statusPageView: statusPageView.status, setStatusPageView }}> <ProfilePageContextProvider>
{statusPageView.status === 'default' && ( <ProfilePageContext.Consumer>
<Box maxW='900px'> {({ statusPageView, pageData }) => {
<Heading as='h1'>My account</Heading> switch (statusPageView) {
<Text mt='2'>Update you contacts information or password here.</Text> case 'default':
<Flex return <MainContainer />;
alignItems='center' case 'update-password':
mt='20' return <UpdatePasswordContainer />;
sx={{ case 'update-email':
'@media (max-width: 768px)': { return <UpdateEmailContainer />;
flexDirection: 'column', case 'confirm-email':
alignItems: 'center', return pageData.confirmEmailData && <ConfirmEmailContainer data={pageData.confirmEmailData} />;
textAlign: 'center', default:
mt: '10', return null;
}, }
}} }}
> </ProfilePageContext.Consumer>
<Box> </ProfilePageContextProvider>
<Avatar
border={`4px solid ${colorMode === 'dark' ? '#2D3748' : '#E2E8F0'}`}
size='2xl'
name={userStore.me.fullname}
/>
</Box>
<Flex
ml='50'
flexDirection='column'
sx={{
'@media (max-width: 768px)': {
ml: '0',
mt: '5',
},
}}
>
<Heading as='h2'>{userStore.me.fullname}</Heading>
<Flex
alignItems='center'
sx={{
'@media (max-width: 768px)': {
flexDirection: 'column',
},
}}
>
<Text
mr='10px'
sx={{
'@media (max-width: 768px)': {
mr: '0',
mb: '5px',
},
}}
>
{userStore.me.email}
</Text>
<UserBadge roles={userStore.me.roles} />
</Flex>
</Flex>
</Flex>
<Box mt='10'>
<Tabs>
<TabList>
<Tab>Account information</Tab>
<Tab>My contacts</Tab>
{isStudent && <Tab>Available courses</Tab>}
</TabList>
<TabPanels>
<TabPanel>
<UserAccountInfo user={userStore.me} />
</TabPanel>
<TabPanel>
<UpdateUserContactsForm contacts={userStore.me.contacts} />
</TabPanel>
{isStudent && (
<TabPanel>
<CourseList courses={userStore.me.courses} />
</TabPanel>
)}
</TabPanels>
</Tabs>
</Box>
</Box>
)}
{statusPageView.status === 'update-password' && <UpdatePasswordContainer />}
{statusPageView.status === 'update-email' && <UpdateEmailContainer />}
{statusPageView.status === 'confirm-email' && statusPageView.data?.confirmEmailData && (
<ConfirmEmailContainer data={statusPageView.data.confirmEmailData} />
)}
</ProfilePageViewContext.Provider>
); );
}; };

View File

@ -0,0 +1,3 @@
import EditUserForm from './EditUserForm';
export default EditUserForm;

View File

@ -1,3 +0,0 @@
import UpdateUserForm from './UpdateUserForm';
export default UpdateUserForm;

View File

@ -4,7 +4,7 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons';
import { Flex, Box, Stack, Text } from '@chakra-ui/layout'; import { Flex, Box, Stack, Text } from '@chakra-ui/layout';
import { Button } from '@chakra-ui/button'; import { Button } from '@chakra-ui/button';
import UserItem from './UserItem'; import UserItem from './UserItem';
import UpdateUserForm from '../UpdateUserForm'; import EditUserForm from '../EditUserForm';
/** /**
* Types * Types
@ -20,7 +20,7 @@ import { UsersPageContext } from 'contexts';
/** /**
* Hooks * Hooks
*/ */
import { useContext, useState } from 'react'; import { useContext } from 'react';
import { useRootStore } from 'hooks/useRootStore'; import { useRootStore } from 'hooks/useRootStore';
import { useDisclosure } from '@chakra-ui/hooks'; import { useDisclosure } from '@chakra-ui/hooks';
import { useErrorHandler } from 'react-error-boundary'; import { useErrorHandler } from 'react-error-boundary';
@ -29,8 +29,8 @@ type UserListPropsType = { users: IUser[]; pagination: IPaginationMeta };
const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => { const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => {
const { userStore } = useRootStore(); const { userStore } = useRootStore();
const [editingUser, setEditingUser] = useState<IUser | undefined>(undefined); const { searchingRole, loading, setLoading, search, editingUser, setEditingUser } =
const { searchingRole, loading, setLoading, search } = useContext<UsersPageContextType>(UsersPageContext); useContext<UsersPageContextType>(UsersPageContext);
const handleError = useErrorHandler(); const handleError = useErrorHandler();
const pagesCount = Math.ceil(pagination.total / pagination.per_page); const pagesCount = Math.ceil(pagination.total / pagination.per_page);
const { onOpen: onOpenEditModal, onClose: onCloseEditModal, isOpen: isOpenEditModal } = useDisclosure(); const { onOpen: onOpenEditModal, onClose: onCloseEditModal, isOpen: isOpenEditModal } = useDisclosure();
@ -86,7 +86,7 @@ const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => {
<> <>
{pagination.total !== 0 ? ( {pagination.total !== 0 ? (
<> <>
{editingUser && <UpdateUserForm user={editingUser} onClose={onCloseEditModal} isOpen={isOpenEditModal} />} {editingUser && <EditUserForm user={editingUser} onClose={onCloseEditModal} isOpen={isOpenEditModal} />}
<Box> <Box>
<Flex mt='7' p='0 10px' fontWeight='bold' alignItems='center' justifyContent='space-between'> <Flex mt='7' p='0 10px' fontWeight='bold' alignItems='center' justifyContent='space-between'>
<Text>Total: ({pagination.total})</Text> <Text>Total: ({pagination.total})</Text>

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect } from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Box, Flex, Button, Heading, Text } from '@chakra-ui/react'; import { Box, Flex, Button, Heading, Text } from '@chakra-ui/react';
import { FaFileExcel } from 'react-icons/fa'; import { FaFileExcel } from 'react-icons/fa';
@ -7,7 +7,6 @@ import { FaFileExcel } from 'react-icons/fa';
* Types * Types
*/ */
import { IPageProps } from 'interfaces'; import { IPageProps } from 'interfaces';
import { SearchingRoleStateType } from 'types';
/** /**
* Components * Components
@ -26,17 +25,14 @@ import { useLocation } from 'react-router';
/** /**
* Context * Context
*/ */
import { UsersPageContext } from 'contexts';
import CreateUserForm from './components/CreateUserForm'; import CreateUserForm from './components/CreateUserForm';
import { UsersPageContextProvider } from 'providers';
/** /**
* Users Page * Users Page
*/ */
const UsersPage: React.FC<IPageProps> = ({ title }) => { const UsersPage: React.FC<IPageProps> = () => {
const { userStore } = useRootStore(); const { userStore } = useRootStore();
const [searchingRole, setSearchingRole] = useState<SearchingRoleStateType>(undefined);
const [loading, setLoading] = useState<boolean>(false);
const [search, setSearch] = useState<string | undefined>(undefined);
const handleError = useErrorHandler(); const handleError = useErrorHandler();
const location = useLocation(); const location = useLocation();
@ -45,7 +41,7 @@ const UsersPage: React.FC<IPageProps> = ({ title }) => {
}, [handleError, userStore, location.search]); }, [handleError, userStore, location.search]);
return ( return (
<UsersPageContext.Provider value={{ searchingRole, setSearchingRole, loading, setLoading, search, setSearch }}> <UsersPageContextProvider>
<Box h='100%'> <Box h='100%'>
<Heading as='h1'>User management</Heading> <Heading as='h1'>User management</Heading>
<Text mt='2'>You can add or delete your users on this page.</Text> <Text mt='2'>You can add or delete your users on this page.</Text>
@ -66,7 +62,7 @@ const UsersPage: React.FC<IPageProps> = ({ title }) => {
<LoadingPage /> <LoadingPage />
)} )}
</Box> </Box>
</UsersPageContext.Provider> </UsersPageContextProvider>
); );
}; };

View File

@ -1,12 +1,46 @@
import React, { ReactNode } from 'react'; import React, { useState, ReactNode } from 'react';
import { StoreContext } from 'contexts'; import { ProfilePageContext, StoreContext, UsersPageContext } from 'contexts';
import { ProfilePageDataType, ProfilePageStatusType, SearchingRoleStateType } from 'types';
import RootStore from 'stores/RootStore'; import RootStore from 'stores/RootStore';
import { IUser } from 'interfaces';
/** /**
* Root store provider * Root store context provider
*/ */
export const RootStoreProvider = ({ children }: { children: ReactNode }) => { export const RootStoreProvider = ({ children }: { children: ReactNode }) => {
const store = new RootStore(); const store = new RootStore();
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>; return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
}; };
/**
* Profile page context provider
*/
export const ProfilePageContextProvider: React.FC = ({ children }) => {
const [statusPageView, setStatusPageView] = useState<ProfilePageStatusType>('default');
const [pageData, setPageData] = useState<ProfilePageDataType>({});
return (
<ProfilePageContext.Provider value={{ statusPageView, setStatusPageView, pageData, setPageData }}>
{children}
</ProfilePageContext.Provider>
);
};
/**
* Users page context provider
*/
export const UsersPageContextProvider: React.FC = ({ children }) => {
const [searchingRole, setSearchingRole] = useState<SearchingRoleStateType>(undefined);
const [loading, setLoading] = useState<boolean>(false);
const [search, setSearch] = useState<string | undefined>(undefined);
const [editingUser, setEditingUser] = useState<IUser | undefined>(undefined);
return (
<UsersPageContext.Provider
value={{ searchingRole, setSearchingRole, loading, setLoading, search, setSearch, editingUser, setEditingUser }}
>
{children}
</UsersPageContext.Provider>
);
};

25
src/types/index.d.ts vendored
View File

@ -1,4 +1,5 @@
import { UserRoleEnum } from 'enums'; import { UserRoleEnum } from 'enums';
import { IUser } from 'interfaces';
import React from 'react'; import React from 'react';
import { IconType } from 'react-icons/lib'; import { IconType } from 'react-icons/lib';
@ -19,19 +20,25 @@ export type ConfirmEmailContainerDataType = {
expired_seconds: number; expired_seconds: number;
}; };
export type ProfilePageStatusType = { export type ProfilePageStatusType = 'update-password' | 'update-email' | 'confirm-email' | 'default';
status: 'update-password' | 'update-email' | 'confirm-email' | 'default'; export type ProfilePageDataType = {
data?: {
/** /**
* The field that is set when trying to change the mail address through form * The field that is set when trying to change the mail address through form
*/ */
confirmEmailData?: ConfirmEmailContainerDataType; confirmEmailData?: ConfirmEmailContainerDataType;
}; };
};
export type ProfilePageViewStatusContextType = { export type ProfilePageViewStatusContextType = {
statusPageView: ProfilePageStatusType['status']; /**
* Render current container by status
*/
statusPageView: ProfilePageStatusType;
setStatusPageView: React.Dispatch<React.SetStateAction<ProfilePageStatusType>>; setStatusPageView: React.Dispatch<React.SetStateAction<ProfilePageStatusType>>;
/**
* Some data for other components
*/
pageData: ProfilePageDataType;
setPageData: React.Dispatch<React.SetStateAction<ProfilePageDataType>>;
}; };
export type SearchingRoleStateType = UserRoleEnum | 'any' | undefined; export type SearchingRoleStateType = UserRoleEnum | 'any' | undefined;
@ -53,4 +60,10 @@ export type UsersPageContextType = {
*/ */
search: string | undefined; search: string | undefined;
setSearch: React.Dispatch<React.SetStateAction<string | undefined>>; setSearch: React.Dispatch<React.SetStateAction<string | undefined>>;
/**
* Set editing user state when press on action button
*/
editingUser: IUser | undefined;
setEditingUser: React.Dispatch<React.SetStateAction<IUser | undefined>>;
}; };