mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
refactor: refactor profile page
This commit is contained in:
parent
1ceb083282
commit
27e1cdbfa5
@ -23,6 +23,8 @@ import PublicRoute from '@educt/components/PublicRoute';
|
|||||||
import AuthPage from '@educt/pages/auth/auth';
|
import AuthPage from '@educt/pages/auth/auth';
|
||||||
import NotFoundPage from '@educt/pages/404/404';
|
import NotFoundPage from '@educt/pages/404/404';
|
||||||
import CreateCoursePage from '@educt/pages/courses/create/create';
|
import CreateCoursePage from '@educt/pages/courses/create/create';
|
||||||
|
import ChangePasswordPage from '@educt/pages/profile/change-password/change-password';
|
||||||
|
import ChangeEmailPage from '@educt/pages/profile/change-email/change-email';
|
||||||
const CoursesPage = React.lazy(() => import('@educt/pages/courses/courses'));
|
const CoursesPage = React.lazy(() => import('@educt/pages/courses/courses'));
|
||||||
const MainPage = React.lazy(() => import('@educt/pages/main/main'));
|
const MainPage = React.lazy(() => import('@educt/pages/main/main'));
|
||||||
const MessagesPage = React.lazy(() => import('@educt/pages/messages/messages'));
|
const MessagesPage = React.lazy(() => import('@educt/pages/messages/messages'));
|
||||||
@ -46,7 +48,9 @@ const App = () => {
|
|||||||
roles={[UserRoleEnum.ADMIN, UserRoleEnum.TEACHER]}
|
roles={[UserRoleEnum.ADMIN, UserRoleEnum.TEACHER]}
|
||||||
/>
|
/>
|
||||||
<PrivateRoute path='/messages' component={MessagesPage} title='Messages' />
|
<PrivateRoute path='/messages' component={MessagesPage} title='Messages' />
|
||||||
<PrivateRoute path='/profile' component={ProfilePage} title='Profile' />
|
<PrivateRoute exact path='/profile' component={ProfilePage} title='Profile' />
|
||||||
|
<PrivateRoute path='/profile/change-password' component={ChangePasswordPage} title='Change Password' />
|
||||||
|
<PrivateRoute path='/profile/change-email' component={ChangeEmailPage} title='Change Email' />
|
||||||
<PrivateRoute
|
<PrivateRoute
|
||||||
path='/users'
|
path='/users'
|
||||||
component={UsersPage}
|
component={UsersPage}
|
||||||
|
|||||||
@ -1,18 +1,24 @@
|
|||||||
import config from '@educt/config';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
|
||||||
import { Flex, Box, Heading, useColorMode } from '@chakra-ui/react';
|
import { Flex, Box, Heading, useColorMode } from '@chakra-ui/react';
|
||||||
import UserInfo from './UserInfo';
|
import { MdSchool } from 'react-icons/md';
|
||||||
import UserNotifications from './UserNotifications';
|
import config from '@educt/config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Components
|
||||||
|
*/
|
||||||
import NavMobile from '@educt/components/Nav/Mobile';
|
import NavMobile from '@educt/components/Nav/Mobile';
|
||||||
import ColorModeSwitcher from '@educt/components/ColorModeSwitcher';
|
import ColorModeSwitcher from '@educt/components/ColorModeSwitcher';
|
||||||
import { MdSchool } from 'react-icons/md';
|
import UserInfo from './UserInfo';
|
||||||
|
import UserNotifications from './UserNotifications';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
import { useMediaQuery } from '@chakra-ui/media-query';
|
||||||
|
|
||||||
const Header: React.FC = () => {
|
const Header: React.FC = () => {
|
||||||
const {
|
const [isDesktop] = useMediaQuery('(min-width: 992px)');
|
||||||
uiStore: { isDesktop },
|
|
||||||
} = useRootStore();
|
|
||||||
const { colorMode } = useColorMode();
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -22,7 +28,9 @@ const Header: React.FC = () => {
|
|||||||
top={0}
|
top={0}
|
||||||
left={0}
|
left={0}
|
||||||
right={0}
|
right={0}
|
||||||
borderBottomWidth={1}
|
// borderBottomWidth={1}
|
||||||
|
// h='43px'
|
||||||
|
boxShadow='md'
|
||||||
backgroundColor={colorMode === 'dark' ? 'gray.800' : 'white'}
|
backgroundColor={colorMode === 'dark' ? 'gray.800' : 'white'}
|
||||||
zIndex={4}
|
zIndex={4}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -44,24 +44,7 @@ const Layout: React.FC<LayoutPropsType> = ({ children, roles }) => {
|
|||||||
<Header />
|
<Header />
|
||||||
<NavDesktop />
|
<NavDesktop />
|
||||||
<Flex minH='100vh' justifyContent='center'>
|
<Flex minH='100vh' justifyContent='center'>
|
||||||
<Box
|
<Box as='main' flex='1 0' maxW='85rem' ml={{ base: '0', lg: '20rem' }} mt='7rem' mb='1rem' pr='2rem' pl='2rem'>
|
||||||
as='main'
|
|
||||||
flex='1 0'
|
|
||||||
maxW='85rem'
|
|
||||||
ml='20rem'
|
|
||||||
mt='7rem'
|
|
||||||
mb='1rem'
|
|
||||||
pr='2rem'
|
|
||||||
pl='2rem'
|
|
||||||
sx={{
|
|
||||||
'@media (max-width: 1400px)': {
|
|
||||||
ml: '20rem',
|
|
||||||
},
|
|
||||||
'@media (max-width: 991px)': {
|
|
||||||
ml: '0',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Suspense fallback={<LoadingPage />}>
|
<Suspense fallback={<LoadingPage />}>
|
||||||
{(() => {
|
{(() => {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Flex, Box, useColorMode } from '@chakra-ui/react';
|
import { Flex, Box, Text, Link, IconButton } from '@chakra-ui/react';
|
||||||
import config from '@educt/config';
|
import config from '@educt/config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -11,7 +11,8 @@ import NavList from '../NavList';
|
|||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
import { useColorMode, useMediaQuery } from '@chakra-ui/react';
|
||||||
|
import { FaGithub } from 'react-icons/fa';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -19,14 +20,13 @@ import { useRootStore } from '@educt/hooks/useRootStore';
|
|||||||
* Component for navigating the application.
|
* Component for navigating the application.
|
||||||
*/
|
*/
|
||||||
const Nav: React.FC = () => {
|
const Nav: React.FC = () => {
|
||||||
const {
|
|
||||||
uiStore: { isDesktop },
|
|
||||||
} = useRootStore();
|
|
||||||
const { colorMode } = useColorMode();
|
const { colorMode } = useColorMode();
|
||||||
|
const [isDesktop] = useMediaQuery('(min-width: 992px)');
|
||||||
|
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Flex
|
||||||
|
flexDir='column'
|
||||||
borderRightWidth='1px'
|
borderRightWidth='1px'
|
||||||
w='18rem'
|
w='18rem'
|
||||||
position='fixed'
|
position='fixed'
|
||||||
@ -49,7 +49,19 @@ const Nav: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<NavList links={config.links} />
|
<NavList links={config.links} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
<Box textAlign='center' mt='auto' mb='5'>
|
||||||
|
<Text fontSize='xs' color='gray.500' lineHeight='1.5'>
|
||||||
|
Learning Management System <br /> Made by Sergey Yarkov
|
||||||
|
</Text>
|
||||||
|
<Box textAlign='center' mt='2'>
|
||||||
|
<IconButton variant='link' aria-label='GitHub repository'>
|
||||||
|
<Link isExternal href='https://github.com/sergeyyarkov/educt-client'>
|
||||||
|
<Box as={FaGithub} />
|
||||||
|
</Link>
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
import { CoursesPageContextType, ProfilePageContextType, UsersPageContextType } from '@educt/types';
|
import { ChangeEmailPageContextType, CoursesPageContextType, UsersPageContextType } from '@educt/types';
|
||||||
import RootStore from '../stores/RootStore';
|
import RootStore from '../stores/RootStore';
|
||||||
|
|
||||||
export const StoreContext = createContext<RootStore | undefined>(undefined);
|
export const StoreContext = createContext<RootStore | undefined>(undefined);
|
||||||
|
|
||||||
export const ProfilePageContext = createContext<ProfilePageContextType>({
|
export const ChangeEmailPageContext = createContext<ChangeEmailPageContextType>({
|
||||||
statusPageView: 'default',
|
isCodeSent: false,
|
||||||
setStatusPageView: () => {},
|
setIsCodeSent: () => {},
|
||||||
pageData: {},
|
confirmEmailData: undefined,
|
||||||
setPageData: () => {},
|
setConfirmEmailData: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UsersPageContext = createContext<UsersPageContextType>({
|
export const UsersPageContext = createContext<UsersPageContextType>({
|
||||||
|
|||||||
@ -88,7 +88,7 @@ const CourseList: React.FC<CourseListPropsType> = ({ courses, isLoading }) => {
|
|||||||
<>
|
<>
|
||||||
{deletingCourse && <DeleteCourseDialog onClose={onCloseDeleteDialog} isOpen={isOpenDeleteDialog} />}
|
{deletingCourse && <DeleteCourseDialog onClose={onCloseDeleteDialog} isOpen={isOpenDeleteDialog} />}
|
||||||
{courses.length !== 0 ? (
|
{courses.length !== 0 ? (
|
||||||
<Grid templateColumns='repeat(auto-fill, minmax(400px, 1fr))' gap='6'>
|
<Grid templateColumns={{ sm: 'repeat(auto-fill, minmax(400px, 1fr))' }} gap='4'>
|
||||||
{/* Render items */}
|
{/* Render items */}
|
||||||
{courses.map(course => (
|
{courses.map(course => (
|
||||||
<CourseItem key={course.id} course={course} onDelete={onDeleteCourse} onSetStatus={onSetStatus} />
|
<CourseItem key={course.id} course={course} onDelete={onDeleteCourse} onSetStatus={onSetStatus} />
|
||||||
|
|||||||
84
src/pages/profile/change-email/change-email.tsx
Normal file
84
src/pages/profile/change-email/change-email.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, Flex, Heading, Text } from '@chakra-ui/layout';
|
||||||
|
import { IconButton } from '@chakra-ui/button';
|
||||||
|
import { ChevronLeftIcon } from '@chakra-ui/icons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
import { IPageProps } from '@educt/interfaces';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Components
|
||||||
|
*/
|
||||||
|
import LoadingPage from '@educt/components/LoadingPage';
|
||||||
|
import UpdateEmailForm from './components/UpdateEmailForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Providers
|
||||||
|
*/
|
||||||
|
import { ChangeEmailPageContextProvider } from '@educt/providers';
|
||||||
|
import { ChangeEmailPageContext } from '@educt/contexts';
|
||||||
|
import ConfirmEmailContainer from './containers/ConfirmEmailContainer';
|
||||||
|
|
||||||
|
{
|
||||||
|
/* <>
|
||||||
|
{!isCodeSent ? (
|
||||||
|
<UpdateEmailForm currentEmail={me.email} />
|
||||||
|
) : (
|
||||||
|
confirmEmailData && <ConfirmEmailContainer data={confirmEmailData} />
|
||||||
|
)}
|
||||||
|
</> */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change email page
|
||||||
|
*/
|
||||||
|
const ChangeEmailPage: React.FC<IPageProps> = () => {
|
||||||
|
const {
|
||||||
|
userStore: { me },
|
||||||
|
} = useRootStore();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
if (me === null) return <LoadingPage />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChangeEmailPageContextProvider>
|
||||||
|
<ChangeEmailPageContext.Consumer>
|
||||||
|
{({ isCodeSent, confirmEmailData }) => (
|
||||||
|
<>
|
||||||
|
{!isCodeSent ? (
|
||||||
|
<Box maxW='700px'>
|
||||||
|
<Flex alignItems='center'>
|
||||||
|
<IconButton
|
||||||
|
aria-label='Back'
|
||||||
|
borderRadius='full'
|
||||||
|
icon={<ChevronLeftIcon />}
|
||||||
|
onClick={() => history.push('/profile')}
|
||||||
|
mr='5'
|
||||||
|
/>
|
||||||
|
<Heading as='h1'>Edit email address</Heading>
|
||||||
|
</Flex>
|
||||||
|
<Box mt='3'>
|
||||||
|
<Text>Type your new email address.</Text>
|
||||||
|
</Box>
|
||||||
|
<UpdateEmailForm currentEmail={me.email} />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
confirmEmailData && <ConfirmEmailContainer data={confirmEmailData} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ChangeEmailPageContext.Consumer>
|
||||||
|
</ChangeEmailPageContextProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default observer(ChangeEmailPage);
|
||||||
@ -1,26 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
import { Button } from '@chakra-ui/button';
|
import { Button } from '@chakra-ui/button';
|
||||||
import { FormControl, FormHelperText, FormLabel } from '@chakra-ui/form-control';
|
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 { ProfilePageContext } from '@educt/contexts';
|
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
import type { SubmitHandler } from 'react-hook-form';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
import { useContext, useEffect, useState } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
import useUpdateUserEmailQuery from '@educt/hooks/useUpdateUserEmailQuery.ts';
|
import useUpdateUserEmailQuery from '@educt/hooks/useUpdateUserEmailQuery.ts';
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
|
||||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
/**
|
||||||
|
* Contexts
|
||||||
|
*/
|
||||||
|
import { ChangeEmailPageContext } from '@educt/contexts';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema
|
||||||
|
*/
|
||||||
import UpdateEmailSchema from './UpdateEmailForm.validator';
|
import UpdateEmailSchema from './UpdateEmailForm.validator';
|
||||||
|
|
||||||
type UpdateEmailInputType = {
|
type UpdateEmailInputType = {
|
||||||
email: string;
|
email: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type UpdateEmailFormPropsType = {
|
||||||
|
currentEmail: string;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update email using form
|
* Send confirmation code before change email using form
|
||||||
*/
|
*/
|
||||||
const UpdateEmailForm: React.FC = () => {
|
const UpdateEmailForm: React.FC<UpdateEmailFormPropsType> = ({ currentEmail }) => {
|
||||||
const [email, setEmail] = useState<string | null>(null);
|
const [newEmail, setNewEmail] = useState<string | null>(null);
|
||||||
const { setStatusPageView, setPageData } = useContext(ProfilePageContext);
|
const { setIsCodeSent, setConfirmEmailData } = useContext(ChangeEmailPageContext);
|
||||||
const { userStore } = useRootStore();
|
|
||||||
const { sendConfirmationCode, loading, result } = useUpdateUserEmailQuery();
|
const { sendConfirmationCode, loading, result } = useUpdateUserEmailQuery();
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -30,16 +50,25 @@ const UpdateEmailForm: React.FC = () => {
|
|||||||
resolver: yupResolver(UpdateEmailSchema),
|
resolver: yupResolver(UpdateEmailSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change page view in confirmation code has been sent
|
||||||
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (email !== null && result !== null) {
|
if (newEmail !== null && result !== null) {
|
||||||
setStatusPageView('confirm-email');
|
setIsCodeSent(true);
|
||||||
setPageData({ confirmEmailData: { newEmail: email, expired_seconds: result.data.expired_seconds } });
|
setConfirmEmailData({ newEmail, expired_seconds: result.data.expired_seconds });
|
||||||
}
|
}
|
||||||
});
|
}, [result]);
|
||||||
|
|
||||||
const onSubmit: SubmitHandler<UpdateEmailInputType> = ({ email }) => {
|
/**
|
||||||
setEmail(email);
|
* Submit form handler
|
||||||
sendConfirmationCode(email);
|
*/
|
||||||
|
const onSubmit: SubmitHandler<UpdateEmailInputType> = async ({ email }) => {
|
||||||
|
/**
|
||||||
|
* Set new email state and send confirmation code
|
||||||
|
*/
|
||||||
|
setNewEmail(email);
|
||||||
|
await sendConfirmationCode(email);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -48,7 +77,7 @@ const UpdateEmailForm: React.FC = () => {
|
|||||||
<Stack spacing='15px' mt='10'>
|
<Stack spacing='15px' mt='10'>
|
||||||
<FormControl id='email'>
|
<FormControl id='email'>
|
||||||
<FormLabel>Current email</FormLabel>
|
<FormLabel>Current email</FormLabel>
|
||||||
<Input type='email' size='md' variant='filled' disabled value={userStore.me?.email} />
|
<Input type='email' size='md' variant='filled' disabled value={currentEmail} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl id='new_email' isRequired>
|
<FormControl id='new_email' isRequired>
|
||||||
<FormLabel>New email</FormLabel>
|
<FormLabel>New email</FormLabel>
|
||||||
@ -1,22 +1,30 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React from 'react';
|
||||||
import { PinInput, PinInputField } from '@chakra-ui/react';
|
import { PinInput, PinInputField } from '@chakra-ui/react';
|
||||||
import { Button } from '@chakra-ui/button';
|
import { Button } from '@chakra-ui/button';
|
||||||
import { Box, Flex, Heading, HStack, Text } from '@chakra-ui/layout';
|
import { Box, Flex, Heading, HStack, Text } from '@chakra-ui/layout';
|
||||||
import { ConfirmEmailContainerDataType } from '@educt/types';
|
|
||||||
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
import { ConfirmEmailDataType } from '@educt/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
import useUpdateUserEmailQuery from '@educt/hooks/useUpdateUserEmailQuery.ts';
|
import useUpdateUserEmailQuery from '@educt/hooks/useUpdateUserEmailQuery.ts';
|
||||||
|
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
import { useRootStore } from '@educt/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 { ProfilePageContext } from '@educt/contexts';
|
import { useHistory } from 'react-router';
|
||||||
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
|
|
||||||
|
|
||||||
type ConfirmEmailContainerPropsType = {
|
type ConfirmEmailContainerPropsType = {
|
||||||
data: ConfirmEmailContainerDataType;
|
data: ConfirmEmailDataType;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ConfirmEmailContainer: React.FC<ConfirmEmailContainerPropsType> = ({ data }) => {
|
const ConfirmEmailContainer: React.FC<ConfirmEmailContainerPropsType> = ({ data }) => {
|
||||||
const { userStore } = useRootStore();
|
const { userStore } = useRootStore();
|
||||||
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>('');
|
||||||
@ -24,6 +32,7 @@ const ConfirmEmailContainer: React.FC<ConfirmEmailContainerPropsType> = ({ data
|
|||||||
const isMountedRef = useIsMountedRef();
|
const isMountedRef = useIsMountedRef();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const handleError = useErrorHandler();
|
const handleError = useErrorHandler();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interval = window.setInterval(() => {
|
const interval = window.setInterval(() => {
|
||||||
@ -47,7 +56,7 @@ const ConfirmEmailContainer: React.FC<ConfirmEmailContainerPropsType> = ({ data
|
|||||||
status: 'info',
|
status: 'info',
|
||||||
});
|
});
|
||||||
if (isMountedRef.current) {
|
if (isMountedRef.current) {
|
||||||
setStatusPageView('default');
|
history.push('/profile');
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
54
src/pages/profile/change-password/change-password.tsx
Normal file
54
src/pages/profile/change-password/change-password.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, Heading, Flex, Text, IconButton } from '@chakra-ui/react';
|
||||||
|
import { ChevronLeftIcon } from '@chakra-ui/icons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
import { IPageProps } from '@educt/interfaces';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Components
|
||||||
|
*/
|
||||||
|
import UpdatePasswordForm from './components/UpdatePasswordForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import LoadingPage from '@educt/components/LoadingPage';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change password page
|
||||||
|
*/
|
||||||
|
const ChangePasswordPage: React.FC<IPageProps> = () => {
|
||||||
|
const {
|
||||||
|
userStore: { me },
|
||||||
|
} = useRootStore();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
if (me === null) return <LoadingPage />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box maxW='700px'>
|
||||||
|
<Flex alignItems='center'>
|
||||||
|
<IconButton
|
||||||
|
aria-label='Profile page'
|
||||||
|
borderRadius='full'
|
||||||
|
icon={<ChevronLeftIcon />}
|
||||||
|
onClick={() => history.push('/profile')}
|
||||||
|
mr='5'
|
||||||
|
/>
|
||||||
|
<Heading as='h1'>Update password</Heading>
|
||||||
|
</Flex>
|
||||||
|
<Box mt='3'>
|
||||||
|
<Text>Your new password must be different from the current one.</Text>
|
||||||
|
</Box>
|
||||||
|
<UpdatePasswordForm />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default observer(ChangePasswordPage);
|
||||||
@ -1,16 +1,30 @@
|
|||||||
import React, { useContext, useState } from 'react';
|
import React from 'react';
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
|
||||||
import { Box, Stack } from '@chakra-ui/layout';
|
import { Box, Stack } from '@chakra-ui/layout';
|
||||||
import { FormControl, FormLabel, FormHelperText } from '@chakra-ui/form-control';
|
import { FormControl, FormLabel, FormHelperText } from '@chakra-ui/form-control';
|
||||||
import { Button } from '@chakra-ui/button';
|
import { Button } from '@chakra-ui/button';
|
||||||
import { Input } from '@chakra-ui/input';
|
import { Input } from '@chakra-ui/input';
|
||||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import UpdatePasswordSchema from './UpdatePasswordForm.validator';
|
|
||||||
import { useToast } from '@chakra-ui/toast';
|
/**
|
||||||
|
* Types
|
||||||
|
*/
|
||||||
|
import type { SubmitHandler } from 'react-hook-form';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
import { useErrorHandler } from 'react-error-boundary';
|
import { useErrorHandler } from 'react-error-boundary';
|
||||||
import { ProfilePageContext } from '@educt/contexts';
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||||
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
|
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
|
||||||
|
import { useToast } from '@chakra-ui/toast';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema
|
||||||
|
*/
|
||||||
|
import UpdatePasswordSchema from './UpdatePasswordForm.validator';
|
||||||
|
|
||||||
type UpdatePasswordInputType = {
|
type UpdatePasswordInputType = {
|
||||||
old_password: string;
|
old_password: string;
|
||||||
@ -22,10 +36,9 @@ type UpdatePasswordInputType = {
|
|||||||
* Update password using form
|
* Update password using form
|
||||||
*/
|
*/
|
||||||
const UpdatePasswordForm: React.FC = () => {
|
const UpdatePasswordForm: React.FC = () => {
|
||||||
const { setStatusPageView } = useContext(ProfilePageContext);
|
|
||||||
const { userStore } = useRootStore();
|
const { userStore } = useRootStore();
|
||||||
const isMountedRef = useIsMountedRef();
|
const isMountedRef = useIsMountedRef();
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
register,
|
register,
|
||||||
@ -35,19 +48,25 @@ const UpdatePasswordForm: React.FC = () => {
|
|||||||
} = useForm<UpdatePasswordInputType>({
|
} = useForm<UpdatePasswordInputType>({
|
||||||
resolver: yupResolver(UpdatePasswordSchema),
|
resolver: yupResolver(UpdatePasswordSchema),
|
||||||
});
|
});
|
||||||
|
const history = useHistory();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const handleError = useErrorHandler();
|
const handleError = useErrorHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit form handler
|
||||||
|
*/
|
||||||
const onSubmit: SubmitHandler<UpdatePasswordInputType> = async ({ old_password, new_password, confirm_password }) => {
|
const onSubmit: SubmitHandler<UpdatePasswordInputType> = async ({ old_password, new_password, confirm_password }) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setIsLoading(true);
|
||||||
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('default');
|
history.push('/profile');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
|
/**
|
||||||
|
* Invalid current password handler
|
||||||
|
*/
|
||||||
if (error.response.status === 401) {
|
if (error.response.status === 401) {
|
||||||
toast({ title: 'Invalid current password.', status: 'error', duration: 2000 });
|
toast({ title: 'Invalid current password.', status: 'error', duration: 2000 });
|
||||||
reset({ old_password: '', new_password, confirm_password });
|
reset({ old_password: '', new_password, confirm_password });
|
||||||
@ -60,7 +79,7 @@ const UpdatePasswordForm: React.FC = () => {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (isMountedRef.current) {
|
if (isMountedRef.current) {
|
||||||
setLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -76,7 +95,7 @@ const UpdatePasswordForm: React.FC = () => {
|
|||||||
size='md'
|
size='md'
|
||||||
variant='filled'
|
variant='filled'
|
||||||
placeholder='*******'
|
placeholder='*******'
|
||||||
isInvalid={errors.old_password ? true : false}
|
isInvalid={!!errors.old_password}
|
||||||
{...register('old_password')}
|
{...register('old_password')}
|
||||||
/>
|
/>
|
||||||
<FormHelperText color='gray.500'>Type your current password.</FormHelperText>
|
<FormHelperText color='gray.500'>Type your current password.</FormHelperText>
|
||||||
@ -88,7 +107,7 @@ const UpdatePasswordForm: React.FC = () => {
|
|||||||
size='md'
|
size='md'
|
||||||
variant='filled'
|
variant='filled'
|
||||||
placeholder='*******'
|
placeholder='*******'
|
||||||
isInvalid={errors.new_password ? true : false}
|
isInvalid={!!errors.new_password}
|
||||||
{...register('new_password')}
|
{...register('new_password')}
|
||||||
/>
|
/>
|
||||||
<FormHelperText color={errors.new_password ? 'red' : 'gray.500'}>
|
<FormHelperText color={errors.new_password ? 'red' : 'gray.500'}>
|
||||||
@ -102,7 +121,7 @@ const UpdatePasswordForm: React.FC = () => {
|
|||||||
size='md'
|
size='md'
|
||||||
variant='filled'
|
variant='filled'
|
||||||
placeholder='*******'
|
placeholder='*******'
|
||||||
isInvalid={errors.confirm_password ? true : false}
|
isInvalid={!!errors.confirm_password}
|
||||||
{...register('confirm_password')}
|
{...register('confirm_password')}
|
||||||
/>
|
/>
|
||||||
<FormHelperText color={errors.confirm_password ? 'red' : 'gray.500'}>
|
<FormHelperText color={errors.confirm_password ? 'red' : 'gray.500'}>
|
||||||
@ -116,7 +135,7 @@ const UpdatePasswordForm: React.FC = () => {
|
|||||||
type='submit'
|
type='submit'
|
||||||
size='md'
|
size='md'
|
||||||
variant='outline'
|
variant='outline'
|
||||||
isLoading={loading}
|
isLoading={isLoading}
|
||||||
loadingText='Saving...'
|
loadingText='Saving...'
|
||||||
>
|
>
|
||||||
Change
|
Change
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link as ReactRouterLink } from 'react-router-dom';
|
import { Link as ReactRouterLink } from 'react-router-dom';
|
||||||
import { Tag, TagLabel } from '@chakra-ui/react';
|
import { Tag, TagLabel, LinkBox, Heading, LinkOverlay } from '@chakra-ui/react';
|
||||||
import { Text, Link, Box, Flex } from '@chakra-ui/layout';
|
import { Text, Link, Box, Flex } from '@chakra-ui/layout';
|
||||||
import { MdVideoLibrary, MdGroup, MdThumbUp } from 'react-icons/md';
|
import { MdVideoLibrary, MdGroup, MdThumbUp } from 'react-icons/md';
|
||||||
|
|
||||||
@ -15,24 +15,12 @@ type CourseItemPropsType = {
|
|||||||
|
|
||||||
const CourseItem: React.FC<CourseItemPropsType> = ({ course }) => {
|
const CourseItem: React.FC<CourseItemPropsType> = ({ course }) => {
|
||||||
return (
|
return (
|
||||||
<Flex flexDir='column' borderWidth='1px' borderRadius='lg' p='10px' boxShadow='sm' mb='3'>
|
<LinkBox as='div' p='5' borderWidth='1px' rounded='lg'>
|
||||||
<Tag colorScheme='purple' borderRadius='full' alignSelf='flex-start'>
|
<Heading size='md' my='2'>
|
||||||
<TagLabel>{course.category.title}</TagLabel>
|
<LinkOverlay href='#'>{course.title}</LinkOverlay>
|
||||||
</Tag>
|
</Heading>
|
||||||
<Flex justifyContent='space-between' alignItems='center'>
|
|
||||||
<Link
|
|
||||||
as={ReactRouterLink}
|
|
||||||
to={`course/${course.id}`}
|
|
||||||
overflowWrap='anywhere'
|
|
||||||
fontWeight='bold'
|
|
||||||
fontSize='xl'
|
|
||||||
mr='5'
|
|
||||||
>
|
|
||||||
{course.title}
|
|
||||||
</Link>
|
|
||||||
</Flex>
|
|
||||||
<Text>{course.description}</Text>
|
<Text>{course.description}</Text>
|
||||||
<Flex mt='5' alignItems='center'>
|
<Flex mt='2' alignItems='center'>
|
||||||
<MdVideoLibrary />
|
<MdVideoLibrary />
|
||||||
<Text pl='1' pr='4'>
|
<Text pl='1' pr='4'>
|
||||||
{course.lessons_count} lessons
|
{course.lessons_count} lessons
|
||||||
@ -46,7 +34,40 @@ const CourseItem: React.FC<CourseItemPropsType> = ({ course }) => {
|
|||||||
{course.likes_count} likes
|
{course.likes_count} likes
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</LinkBox>
|
||||||
|
|
||||||
|
// <Flex flexDir='column' borderWidth='1px' borderRadius='lg' p='10px' boxShadow='sm' mb='3'>
|
||||||
|
// <Tag colorScheme='purple' borderRadius='full' alignSelf='flex-start'>
|
||||||
|
// <TagLabel>{course.category.title}</TagLabel>
|
||||||
|
// </Tag>
|
||||||
|
// <Flex justifyContent='space-between' alignItems='center'>
|
||||||
|
// <Link
|
||||||
|
// as={ReactRouterLink}
|
||||||
|
// to={`course/${course.id}`}
|
||||||
|
// overflowWrap='anywhere'
|
||||||
|
// fontWeight='bold'
|
||||||
|
// fontSize='xl'
|
||||||
|
// mr='5'
|
||||||
|
// >
|
||||||
|
// {course.title}
|
||||||
|
// </Link>
|
||||||
|
// </Flex>
|
||||||
|
// <Text>{course.description}</Text>
|
||||||
|
// <Flex mt='5' alignItems='center'>
|
||||||
|
// <MdVideoLibrary />
|
||||||
|
// <Text pl='1' pr='4'>
|
||||||
|
// {course.lessons_count} lessons
|
||||||
|
// </Text>
|
||||||
|
// <MdGroup />
|
||||||
|
// <Text pl='1' pr='4'>
|
||||||
|
// {course.students_count} students
|
||||||
|
// </Text>
|
||||||
|
// <MdThumbUp />
|
||||||
|
// <Text pl='1' pr='4'>
|
||||||
|
// {course.likes_count} likes
|
||||||
|
// </Text>
|
||||||
|
// </Flex>
|
||||||
|
// </Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Text, Flex } from '@chakra-ui/layout';
|
import { Box, Text, Flex, Stack } from '@chakra-ui/layout';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
@ -16,9 +16,7 @@ type CourseListPropsType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CourseList: React.FC<CourseListPropsType> = ({ courses }) => {
|
const CourseList: React.FC<CourseListPropsType> = ({ courses }) => {
|
||||||
if (courses === undefined) {
|
if (courses === undefined) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (courses.length === 0) {
|
if (courses.length === 0) {
|
||||||
return (
|
return (
|
||||||
@ -31,7 +29,7 @@ const CourseList: React.FC<CourseListPropsType> = ({ courses }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDirection='column'>
|
<Flex flexDir='column'>
|
||||||
{courses.map(course => (
|
{courses.map(course => (
|
||||||
<CourseItem course={course} key={course.id} />
|
<CourseItem course={course} key={course.id} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -1,76 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Box, Heading, Stack, Flex, StackDivider, Text, Button } from '@chakra-ui/react';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Types
|
|
||||||
*/
|
|
||||||
import { IUser } from '@educt/interfaces';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contexts
|
|
||||||
*/
|
|
||||||
import { ProfilePageContext } from '@educt/contexts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hooks
|
|
||||||
*/
|
|
||||||
import { useContext } from 'react';
|
|
||||||
|
|
||||||
type UserAccountInfoPropsType = {
|
|
||||||
user: IUser;
|
|
||||||
};
|
|
||||||
|
|
||||||
const UserAccountInfo: React.FC<UserAccountInfoPropsType> = ({ user }) => {
|
|
||||||
const { setStatusPageView } = useContext(ProfilePageContext);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box borderRadius='lg' borderWidth='1px' padding='20px' boxShadow='sm'>
|
|
||||||
<Stack spacing='10px' divider={<StackDivider />}>
|
|
||||||
<Flex>
|
|
||||||
<Box>
|
|
||||||
<Text as='small' color='gray.500'>
|
|
||||||
First Name
|
|
||||||
</Text>
|
|
||||||
<Text fontWeight='medium' fontSize='lg'>
|
|
||||||
{user.first_name}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
<Flex>
|
|
||||||
<Box>
|
|
||||||
<Text as='small' color='gray.500'>
|
|
||||||
Last Name
|
|
||||||
</Text>
|
|
||||||
<Text fontWeight='medium' fontSize='lg'>
|
|
||||||
{user.last_name}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
<Flex justifyContent='space-between' alignItems='center'>
|
|
||||||
<Box>
|
|
||||||
<Text as='small' color='gray.500'>
|
|
||||||
Email
|
|
||||||
</Text>
|
|
||||||
<Text fontWeight='medium' fontSize='lg'>
|
|
||||||
{user.email}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Button onClick={() => setStatusPageView('update-email')}>Edit</Button>
|
|
||||||
</Flex>
|
|
||||||
<Flex justifyContent='space-between' alignItems='center'>
|
|
||||||
<Box>
|
|
||||||
<Text as='small' color='gray.500'>
|
|
||||||
Password
|
|
||||||
</Text>
|
|
||||||
<Text fontWeight='medium' fontSize='lg'>
|
|
||||||
**********
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Button onClick={() => setStatusPageView('update-password')}>Change</Button>
|
|
||||||
</Flex>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UserAccountInfo;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import UserAccountInfo from './UserAccountInfo';
|
|
||||||
|
|
||||||
export default UserAccountInfo;
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Box, Heading, Flex, Avatar, Text, Tabs, TabList, Tab, TabPanels, TabPanel, Button } from '@chakra-ui/react';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Components
|
|
||||||
*/
|
|
||||||
import UserBadge from '@educt/components/UserBadge';
|
|
||||||
import UserAccountInfo from '@educt/pages/profile/components/UserAccountInfo';
|
|
||||||
import UpdateUserContactsForm from '@educt/pages/profile/components/UpdateUserContactsForm';
|
|
||||||
import CourseList from '@educt/pages/profile/components/CourseList';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hooks
|
|
||||||
*/
|
|
||||||
import useLogoutQuery from '@educt/hooks/useLogoutQuery';
|
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
|
||||||
import { useColorMode } from '@chakra-ui/color-mode';
|
|
||||||
|
|
||||||
const MainContainer: React.FC = () => {
|
|
||||||
const {
|
|
||||||
userStore: { me },
|
|
||||||
} = useRootStore();
|
|
||||||
const { colorMode } = useColorMode();
|
|
||||||
const { logout } = useLogoutQuery();
|
|
||||||
|
|
||||||
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'
|
|
||||||
flexDir={{ base: 'column', md: 'row' }}
|
|
||||||
textAlign={{ base: 'center', md: 'left' }}
|
|
||||||
mt='20'
|
|
||||||
>
|
|
||||||
<Box>
|
|
||||||
<Avatar border={`4px solid ${colorMode === 'dark' ? '#2D3748' : '#E2E8F0'}`} size='2xl' name={me.fullname} />
|
|
||||||
</Box>
|
|
||||||
<Flex ml={{ md: '50' }} mt={{ base: '2', md: '0' }} flexDirection='column'>
|
|
||||||
<Heading as='h2'>{me.fullname}</Heading>
|
|
||||||
<Flex alignItems='center' flexDir={{ base: 'column', md: 'row' }}>
|
|
||||||
<Text mr='10px' mb={{ base: '2', md: '0' }}>
|
|
||||||
{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 mt='2'>
|
|
||||||
<TabPanel>
|
|
||||||
<UserAccountInfo user={me} />
|
|
||||||
</TabPanel>
|
|
||||||
<TabPanel>
|
|
||||||
<UpdateUserContactsForm contacts={me.contacts} />
|
|
||||||
</TabPanel>
|
|
||||||
{me.isStudent && (
|
|
||||||
<TabPanel>
|
|
||||||
<CourseList courses={me.courses} />
|
|
||||||
</TabPanel>
|
|
||||||
)}
|
|
||||||
</TabPanels>
|
|
||||||
</Tabs>
|
|
||||||
</Box>
|
|
||||||
<Box textAlign='center' mt='3'>
|
|
||||||
<Button onClick={logout} w={{ base: 'full', sm: '150px' }}>
|
|
||||||
Sign Out
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MainContainer;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import MainContainer from './MainContainer';
|
|
||||||
|
|
||||||
export default MainContainer;
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
import { IconButton } from '@chakra-ui/button';
|
|
||||||
import { ChevronLeftIcon } from '@chakra-ui/icons';
|
|
||||||
import { Box, Flex, Heading, Text } from '@chakra-ui/layout';
|
|
||||||
import { ProfilePageContext } from '@educt/contexts';
|
|
||||||
import React, { useContext } from 'react';
|
|
||||||
import UpdateEmailForm from './UpdateEmailForm';
|
|
||||||
|
|
||||||
const UpdateEmailContainer: React.FC = () => {
|
|
||||||
const { setStatusPageView } = useContext(ProfilePageContext);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box maxW='700px'>
|
|
||||||
<Flex alignItems='center'>
|
|
||||||
<IconButton
|
|
||||||
aria-label='Back'
|
|
||||||
borderRadius='full'
|
|
||||||
icon={<ChevronLeftIcon />}
|
|
||||||
onClick={() => setStatusPageView('default')}
|
|
||||||
mr='5'
|
|
||||||
/>
|
|
||||||
<Heading as='h1'>Edit email address</Heading>
|
|
||||||
</Flex>
|
|
||||||
<Box mt='3'>
|
|
||||||
<Text>Type your new email address.</Text>
|
|
||||||
</Box>
|
|
||||||
<UpdateEmailForm />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UpdateEmailContainer;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import UpdateEmailContainer from './UpdateEmailContainer';
|
|
||||||
|
|
||||||
export default UpdateEmailContainer;
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
import React, { useContext } from 'react';
|
|
||||||
import { Box, Heading, Flex, Text, IconButton } from '@chakra-ui/react';
|
|
||||||
import { ChevronLeftIcon } from '@chakra-ui/icons';
|
|
||||||
import { ProfilePageContext } from '@educt/contexts';
|
|
||||||
import UpdatePasswordForm from './UpdatePasswordForm';
|
|
||||||
|
|
||||||
const UpdatePasswordContainer: React.FC = () => {
|
|
||||||
const { setStatusPageView } = useContext(ProfilePageContext);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box maxW='700px'>
|
|
||||||
<Flex alignItems='center'>
|
|
||||||
<IconButton
|
|
||||||
aria-label='Back'
|
|
||||||
borderRadius='full'
|
|
||||||
icon={<ChevronLeftIcon />}
|
|
||||||
onClick={() => setStatusPageView('default')}
|
|
||||||
mr='5'
|
|
||||||
/>
|
|
||||||
<Heading as='h1'>Update password</Heading>
|
|
||||||
</Flex>
|
|
||||||
<Box mt='3'>
|
|
||||||
<Text>Your new password must be different from the current one.</Text>
|
|
||||||
</Box>
|
|
||||||
<UpdatePasswordForm />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UpdatePasswordContainer;
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import UpdatePasswordContainer from './UpdatePasswordContainer';
|
|
||||||
|
|
||||||
export default UpdatePasswordContainer;
|
|
||||||
@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
import { Avatar, Tabs, TabList, Tab, TabPanels, TabPanel, Button } from '@chakra-ui/react';
|
||||||
|
import { Box, Flex, Heading, Text, Stack, StackDivider } from '@chakra-ui/layout';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
@ -10,31 +12,17 @@ import { IPageProps } from '@educt/interfaces';
|
|||||||
* Components
|
* Components
|
||||||
*/
|
*/
|
||||||
import LoadingPage from './components/LoadingPage';
|
import LoadingPage from './components/LoadingPage';
|
||||||
|
import UserBadge from '@educt/components/UserBadge';
|
||||||
/**
|
import UpdateUserContactsForm from '@educt/pages/profile/components/UpdateUserContactsForm';
|
||||||
* Containers
|
import CourseList from '@educt/pages/profile/components/CourseList';
|
||||||
*/
|
|
||||||
import MainContainer from './containers/MainContainer';
|
|
||||||
import UpdatePasswordContainer from './containers/UpdatePasswordContainer';
|
|
||||||
import UpdateEmailContainer from './containers/UpdateEmailContainer';
|
|
||||||
import ConfirmEmailContainer from './containers/ConfirmEmailContainer';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
import { useColorMode } from '@chakra-ui/color-mode';
|
||||||
/**
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||||
* Contexts
|
import useLogoutQuery from '@educt/hooks/useLogoutQuery';
|
||||||
*/
|
|
||||||
import { ProfilePageContext } from '@educt/contexts';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Providers
|
|
||||||
*/
|
|
||||||
import { ProfilePageContextProvider } from '@educt/providers';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Profile page
|
* Profile page
|
||||||
@ -42,39 +30,109 @@ import { ProfilePageContextProvider } from '@educt/providers';
|
|||||||
const ProfilePage: React.FC<IPageProps> = () => {
|
const ProfilePage: React.FC<IPageProps> = () => {
|
||||||
const {
|
const {
|
||||||
userStore: { me },
|
userStore: { me },
|
||||||
authStore,
|
|
||||||
} = useRootStore();
|
} = useRootStore();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const { logout } = useLogoutQuery();
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
useEffect(() => {
|
if (me === null) return <LoadingPage />;
|
||||||
if (!authStore.isLoggedIn) {
|
|
||||||
history.push('/auth');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (me === null) {
|
|
||||||
return <LoadingPage />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProfilePageContextProvider>
|
<Box maxW='900px'>
|
||||||
<ProfilePageContext.Consumer>
|
<Heading as='h1'>My account</Heading>
|
||||||
{({ statusPageView, pageData }) => {
|
<Text mt='2'>Update you contacts information or password here.</Text>
|
||||||
switch (statusPageView) {
|
<Flex
|
||||||
case 'default':
|
alignItems='center'
|
||||||
return <MainContainer />;
|
flexDir={{ base: 'column', md: 'row' }}
|
||||||
case 'update-password':
|
textAlign={{ base: 'center', md: 'left' }}
|
||||||
return <UpdatePasswordContainer />;
|
mt='20'
|
||||||
case 'update-email':
|
>
|
||||||
return <UpdateEmailContainer />;
|
<Box>
|
||||||
case 'confirm-email':
|
<Avatar border={`4px solid ${colorMode === 'dark' ? '#2D3748' : '#E2E8F0'}`} size='2xl' name={me.fullname} />
|
||||||
return pageData.confirmEmailData && <ConfirmEmailContainer data={pageData.confirmEmailData} />;
|
</Box>
|
||||||
default:
|
<Flex ml={{ md: '50' }} mt={{ base: '2', md: '0' }} flexDirection='column'>
|
||||||
return null;
|
<Heading as='h2'>{me.fullname}</Heading>
|
||||||
}
|
<Flex alignItems='center' flexDir={{ base: 'column', md: 'row' }}>
|
||||||
}}
|
<Text mr='10px' mb={{ base: '2', md: '0' }}>
|
||||||
</ProfilePageContext.Consumer>
|
{me.email}
|
||||||
</ProfilePageContextProvider>
|
</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 mt='2'>
|
||||||
|
<TabPanel>
|
||||||
|
<Box borderRadius='lg' borderWidth='1px' padding='20px' boxShadow='sm'>
|
||||||
|
<Stack spacing='10px' divider={<StackDivider />}>
|
||||||
|
<Flex>
|
||||||
|
<Box>
|
||||||
|
<Text as='small' color='gray.500'>
|
||||||
|
First Name
|
||||||
|
</Text>
|
||||||
|
<Text fontWeight='medium' fontSize='lg'>
|
||||||
|
{me.first_name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Box>
|
||||||
|
<Text as='small' color='gray.500'>
|
||||||
|
Last Name
|
||||||
|
</Text>
|
||||||
|
<Text fontWeight='medium' fontSize='lg'>
|
||||||
|
{me.last_name}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Flex justifyContent='space-between' alignItems='center'>
|
||||||
|
<Box>
|
||||||
|
<Text as='small' color='gray.500'>
|
||||||
|
Email
|
||||||
|
</Text>
|
||||||
|
<Text fontWeight='medium' fontSize='lg'>
|
||||||
|
{me.email}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Button onClick={() => history.push('/profile/change-email')}>Edit</Button>
|
||||||
|
</Flex>
|
||||||
|
<Flex justifyContent='space-between' alignItems='center'>
|
||||||
|
<Box>
|
||||||
|
<Text as='small' color='gray.500'>
|
||||||
|
Password
|
||||||
|
</Text>
|
||||||
|
<Text fontWeight='medium' fontSize='lg'>
|
||||||
|
**********
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Button onClick={() => history.push('/profile/change-password')}>Change</Button>
|
||||||
|
</Flex>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<UpdateUserContactsForm contacts={me.contacts} />
|
||||||
|
</TabPanel>
|
||||||
|
{me.isStudent && (
|
||||||
|
<TabPanel>
|
||||||
|
<CourseList courses={me.courses} />
|
||||||
|
</TabPanel>
|
||||||
|
)}
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
<Box textAlign='center' mt='3'>
|
||||||
|
<Button onClick={logout} w={{ base: 'full', sm: '150px' }}>
|
||||||
|
Sign Out
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,6 @@
|
|||||||
import React, { useState, ReactNode } from 'react';
|
import React, { useState, ReactNode } from 'react';
|
||||||
import { CoursesPageContext, ProfilePageContext, StoreContext, UsersPageContext } from '@educt/contexts';
|
import { ChangeEmailPageContext, CoursesPageContext, StoreContext, UsersPageContext } from '@educt/contexts';
|
||||||
import {
|
import { ConfirmEmailDataType, SearchingRoleStateType } from '@educt/types';
|
||||||
CoursesPageContextType,
|
|
||||||
ProfilePageDataType,
|
|
||||||
ProfilePageStatusType,
|
|
||||||
SearchingRoleStateType,
|
|
||||||
} from '@educt/types';
|
|
||||||
import RootStore from '@educt/stores/RootStore';
|
import RootStore from '@educt/stores/RootStore';
|
||||||
import { ICategory, ICourse, IUser } from '@educt/interfaces';
|
import { ICategory, ICourse, IUser } from '@educt/interfaces';
|
||||||
import { CourseStatusEnum } from '@educt/enums';
|
import { CourseStatusEnum } from '@educt/enums';
|
||||||
@ -20,16 +15,16 @@ export const RootStoreProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Profile page context provider
|
* Change email page context provider
|
||||||
*/
|
*/
|
||||||
export const ProfilePageContextProvider: React.FC = ({ children }) => {
|
export const ChangeEmailPageContextProvider: React.FC = ({ children }) => {
|
||||||
const [statusPageView, setStatusPageView] = useState<ProfilePageStatusType>('default');
|
const [isCodeSent, setIsCodeSent] = useState<boolean>(false);
|
||||||
const [pageData, setPageData] = useState<ProfilePageDataType>({});
|
const [confirmEmailData, setConfirmEmailData] = useState<ConfirmEmailDataType | undefined>(undefined);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProfilePageContext.Provider value={{ statusPageView, setStatusPageView, pageData, setPageData }}>
|
<ChangeEmailPageContext.Provider value={{ isCodeSent, setIsCodeSent, confirmEmailData, setConfirmEmailData }}>
|
||||||
{children}
|
{children}
|
||||||
</ProfilePageContext.Provider>
|
</ChangeEmailPageContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
39
src/types/index.d.ts
vendored
39
src/types/index.d.ts
vendored
@ -1,6 +1,6 @@
|
|||||||
|
import React from 'react';
|
||||||
import { CourseStatusEnum, UserRoleEnum } from '@educt/enums';
|
import { CourseStatusEnum, UserRoleEnum } from '@educt/enums';
|
||||||
import { ICategory, ICourse, IUser } from '@educt/interfaces';
|
import { ICategory, ICourse, IUser } from '@educt/interfaces';
|
||||||
import React from 'react';
|
|
||||||
import { IconType } from 'react-icons/lib';
|
import { IconType } from 'react-icons/lib';
|
||||||
|
|
||||||
export type LinkType = {
|
export type LinkType = {
|
||||||
@ -23,35 +23,32 @@ export type AttachmentFileType = {
|
|||||||
extname: string;
|
extname: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConfirmEmailContainerDataType = {
|
|
||||||
newEmail: string;
|
|
||||||
expired_seconds: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type OptionType = {
|
export type OptionType = {
|
||||||
value: string;
|
value: string;
|
||||||
label: string;
|
label: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProfilePageStatusType = 'update-password' | 'update-email' | 'confirm-email' | 'default';
|
export type ConfirmEmailContainerDataType = {
|
||||||
export type ProfilePageDataType = {
|
newEmail: string;
|
||||||
/**
|
expired_seconds: number;
|
||||||
* The field that is set when trying to change the mail address through form
|
|
||||||
*/
|
|
||||||
confirmEmailData?: ConfirmEmailContainerDataType;
|
|
||||||
};
|
};
|
||||||
export type ProfilePageContextType = {
|
|
||||||
/**
|
/**
|
||||||
* Render current container by status
|
* Change email page context
|
||||||
*/
|
*/
|
||||||
statusPageView: ProfilePageStatusType;
|
export type ConfirmEmailDataType = {
|
||||||
setStatusPageView: React.Dispatch<React.SetStateAction<ProfilePageStatusType>>;
|
newEmail: string;
|
||||||
|
expired_seconds: number;
|
||||||
|
};
|
||||||
|
export type ChangeEmailPageContextType = {
|
||||||
|
isCodeSent: boolean;
|
||||||
|
setIsCodeSent: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some data for other components
|
* Set this data when confirmation code has been sent
|
||||||
*/
|
*/
|
||||||
pageData: ProfilePageDataType;
|
confirmEmailData?: ConfirmEmailDataType;
|
||||||
setPageData: React.Dispatch<React.SetStateAction<ProfilePageDataType>>;
|
setConfirmEmailData: React.Dispatch<React.SetStateAction<ConfirmEmailDataType | undefined>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SearchingRoleStateType = UserRoleEnum | 'any' | undefined;
|
export type SearchingRoleStateType = UserRoleEnum | 'any' | undefined;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user