mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
refactor: page contexts
This commit is contained in:
parent
99e22a4c69
commit
3a4296f3bd
@ -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} />
|
||||||
|
|||||||
@ -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: () => {},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 () => {
|
||||||
|
|||||||
105
src/pages/profile/containers/MainContainer/MainContainer.tsx
Normal file
105
src/pages/profile/containers/MainContainer/MainContainer.tsx
Normal 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;
|
||||||
3
src/pages/profile/containers/MainContainer/index.ts
Normal file
3
src/pages/profile/containers/MainContainer/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import MainContainer from './MainContainer';
|
||||||
|
|
||||||
|
export default MainContainer;
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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 } },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
3
src/pages/users/components/EditUserForm/index.ts
Normal file
3
src/pages/users/components/EditUserForm/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import EditUserForm from './EditUserForm';
|
||||||
|
|
||||||
|
export default EditUserForm;
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import UpdateUserForm from './UpdateUserForm';
|
|
||||||
|
|
||||||
export default UpdateUserForm;
|
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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
25
src/types/index.d.ts
vendored
@ -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>>;
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user