mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
feat: password update form
This commit is contained in:
parent
add3b1bb24
commit
fe00bb5e72
@ -1,8 +1,8 @@
|
|||||||
import { AxiosRequestConfig } from 'axios';
|
import { AxiosRequestConfig } from 'axios';
|
||||||
import { API_BASE_URL } from './constants';
|
import * as constants from './constants';
|
||||||
|
|
||||||
const apiConfig: AxiosRequestConfig = {
|
const apiConfig: AxiosRequestConfig = {
|
||||||
baseURL: API_BASE_URL,
|
baseURL: constants.API_BASE_URL,
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
import { useToast, Flex, Box, Heading, Text } from '@chakra-ui/react';
|
import { useToast, Flex, Box, Heading, Text } from '@chakra-ui/react';
|
||||||
import { MdError } from 'react-icons/md';
|
import { MdError } from 'react-icons/md';
|
||||||
|
import { useRootStore } from 'hooks/useRootStore';
|
||||||
|
|
||||||
const ErrorFallback: React.FC<{ error: any }> = props => {
|
const ErrorFallback: React.FC<{ error: any }> = props => {
|
||||||
|
const { userStore, authStore } = useRootStore();
|
||||||
const { error } = props;
|
const { error } = props;
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
@ -10,6 +13,14 @@ const ErrorFallback: React.FC<{ error: any }> = props => {
|
|||||||
toast({ title: `${error.message}`, duration: 10000, isClosable: true, status: 'error' });
|
toast({ title: `${error.message}`, duration: 10000, isClosable: true, status: 'error' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (error.response) {
|
||||||
|
if (error.response.status === 401) {
|
||||||
|
userStore.me = null;
|
||||||
|
authStore.setIsLoggedIn(false);
|
||||||
|
Cookies.remove('logged_in');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box textAlign='center' mt={40}>
|
<Box textAlign='center' mt={40}>
|
||||||
<Flex justifyContent='center'>
|
<Flex justifyContent='center'>
|
||||||
|
|||||||
@ -1,22 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Flex, Box, Text, Heading } from '@chakra-ui/react';
|
|
||||||
import { MdSchool } from 'react-icons/md';
|
|
||||||
import config from 'config';
|
|
||||||
|
|
||||||
const AppName: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<Flex alignItems='center'>
|
|
||||||
<Box mr='4'>
|
|
||||||
<Box as={MdSchool} color='blue.500' size='36px' />
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Heading as='p' fontSize='2xl' lineHeight='1'>
|
|
||||||
{config.metaData.appName}
|
|
||||||
</Heading>
|
|
||||||
<Text as='small'>{config.metaData.appDescription}</Text>
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AppName;
|
|
||||||
@ -1,11 +1,13 @@
|
|||||||
|
import config from 'config';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { useRootStore } from 'hooks/useRootStore';
|
import { useRootStore } from 'hooks/useRootStore';
|
||||||
import { Flex, Box, useColorMode } from '@chakra-ui/react';
|
import { Flex, Box, Heading, Text, useColorMode } from '@chakra-ui/react';
|
||||||
import AppName from './AppName';
|
|
||||||
import UserInfo from './UserInfo';
|
import UserInfo from './UserInfo';
|
||||||
import UserNotifications from './UserNotifications';
|
import UserNotifications from './UserNotifications';
|
||||||
import NavMobile from 'components/Nav/Mobile';
|
import NavMobile from 'components/Nav/Mobile';
|
||||||
|
import ColorModeSwitcher from 'components/ColorModeSwitcher';
|
||||||
|
import { MdSchool } from 'react-icons/md';
|
||||||
|
|
||||||
const Header: React.FC = () => {
|
const Header: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
@ -31,18 +33,27 @@ const Header: React.FC = () => {
|
|||||||
alignItems='center'
|
alignItems='center'
|
||||||
padding='5px 1.5rem 5px 1.5rem'
|
padding='5px 1.5rem 5px 1.5rem'
|
||||||
>
|
>
|
||||||
<AppName />
|
<Flex alignItems='center'>
|
||||||
<Flex>
|
<Box mr='4'>
|
||||||
|
<Box as={MdSchool} color='blue.500' size='36px' />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Heading as='p' fontSize='2xl' lineHeight='1'>
|
||||||
|
{config.metaData.appName}
|
||||||
|
</Heading>
|
||||||
|
<Text as='small'>{config.metaData.appDescription}</Text>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
{isDesktop ? (
|
{isDesktop ? (
|
||||||
<>
|
<Flex>
|
||||||
|
<ColorModeSwitcher />
|
||||||
<UserNotifications />
|
<UserNotifications />
|
||||||
<UserInfo />
|
<UserInfo />
|
||||||
</>
|
</Flex>
|
||||||
) : (
|
) : (
|
||||||
<NavMobile />
|
<NavMobile />
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { Flex, Box } from '@chakra-ui/react';
|
|||||||
import { useRootStore } from 'hooks/useRootStore';
|
import { useRootStore } from 'hooks/useRootStore';
|
||||||
import Header from 'components/Header';
|
import Header from 'components/Header';
|
||||||
import NavDesktop from 'components/Nav/Desktop';
|
import NavDesktop from 'components/Nav/Desktop';
|
||||||
import ColorModeSwitcher from 'components/ColorModeSwitcher';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -27,7 +26,7 @@ const Layout: React.FC = ({ children }) => {
|
|||||||
<NavDesktop />
|
<NavDesktop />
|
||||||
<Box
|
<Box
|
||||||
as='main'
|
as='main'
|
||||||
flex='1 0 auto'
|
flex='1 0'
|
||||||
maxW='85rem'
|
maxW='85rem'
|
||||||
ml='25rem'
|
ml='25rem'
|
||||||
mt='7rem'
|
mt='7rem'
|
||||||
@ -45,7 +44,6 @@ const Layout: React.FC = ({ children }) => {
|
|||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
<ColorModeSwitcher position='absolute' right={0} bottom={0} margin={5} />
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { ProfilePageViewStatusContextType } from 'types';
|
||||||
import RootStore from '../stores/RootStore';
|
import RootStore from '../stores/RootStore';
|
||||||
|
|
||||||
export const StoreContext = React.createContext<RootStore | undefined>(undefined);
|
export const StoreContext = React.createContext<RootStore | undefined>(undefined);
|
||||||
|
|
||||||
|
export const ProfilePageViewContext = React.createContext<ProfilePageViewStatusContextType>({
|
||||||
|
statusPageView: undefined,
|
||||||
|
setStatusPageView: () => {},
|
||||||
|
});
|
||||||
|
|||||||
@ -1,15 +1,8 @@
|
|||||||
import { IconType } from 'react-icons/lib';
|
|
||||||
import type { RouteProps } from 'react-router-dom';
|
import type { RouteProps } from 'react-router-dom';
|
||||||
import type { RouteComponentProps } from 'react-router-dom';
|
import type { RouteComponentProps } from 'react-router-dom';
|
||||||
|
import { LinkType } from 'types';
|
||||||
import { UserRoleEnum } from '../enums';
|
import { UserRoleEnum } from '../enums';
|
||||||
|
|
||||||
export type LinkType = {
|
|
||||||
location: string;
|
|
||||||
title: string;
|
|
||||||
public: boolean;
|
|
||||||
icon: IconType;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface IAppConfig {
|
export interface IAppConfig {
|
||||||
metaData: {
|
metaData: {
|
||||||
appName: string;
|
appName: string;
|
||||||
|
|||||||
@ -18,9 +18,7 @@ const MainPage: React.FC<IPageProps> = ({ title }) => {
|
|||||||
<Box mt='10'>
|
<Box mt='10'>
|
||||||
<Box>
|
<Box>
|
||||||
<Heading as='h1'>Main page</Heading>
|
<Heading as='h1'>Main page</Heading>
|
||||||
<Grid templateColumns='3fr 1fr' mt='25px'>
|
<Grid templateColumns='3fr 1fr' mt='25px'></Grid>
|
||||||
...
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,17 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
import { SubmitHandler, useForm } from 'react-hook-form';
|
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||||
import { MdSave } from 'react-icons/md';
|
import { MdSave } from 'react-icons/md';
|
||||||
import { IUserContacts } from 'interfaces';
|
import { IUserContacts } from 'interfaces';
|
||||||
import { Box, Heading, Stack } from '@chakra-ui/layout';
|
import { Box, Heading, Stack } from '@chakra-ui/layout';
|
||||||
|
import { Text, Input, Button } from '@chakra-ui/react';
|
||||||
import { FormControl, FormHelperText } from '@chakra-ui/form-control';
|
import { FormControl, FormHelperText } from '@chakra-ui/form-control';
|
||||||
import { Input } from '@chakra-ui/input';
|
|
||||||
import { Text } from '@chakra-ui/react';
|
|
||||||
import { Button } from '@chakra-ui/button';
|
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
|
||||||
import UpdateUserContactsSchema from 'validators/UpdateUserContactsSchema';
|
|
||||||
import useUpdateUserContactsQuery from 'hooks/useUpdateUserContactsQuery';
|
import useUpdateUserContactsQuery from 'hooks/useUpdateUserContactsQuery';
|
||||||
import { observer } from 'mobx-react';
|
import UpdateUserContactsSchema from 'validators/UpdateUserContactsSchema';
|
||||||
|
|
||||||
type UpdateUserContactsInputType = {
|
type UpdateUserContactsInputType = {
|
||||||
phone_number: string | null;
|
phone_number: string | null;
|
||||||
@ -67,6 +64,7 @@ const UpdateUserContactsForm: React.FC<UpdateUserContactsFormProps> = ({ contact
|
|||||||
size='md'
|
size='md'
|
||||||
variant='flushed'
|
variant='flushed'
|
||||||
placeholder='+1 (234) 555-1234'
|
placeholder='+1 (234) 555-1234'
|
||||||
|
isInvalid={errors.phone_number ? true : false}
|
||||||
{...register('phone_number')}
|
{...register('phone_number')}
|
||||||
/>
|
/>
|
||||||
<Text as='small' color='red.500'>
|
<Text as='small' color='red.500'>
|
||||||
@ -75,21 +73,42 @@ const UpdateUserContactsForm: React.FC<UpdateUserContactsFormProps> = ({ contact
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl id='twitter_id'>
|
<FormControl id='twitter_id'>
|
||||||
<FormHelperText color='gray.500'>Twitter</FormHelperText>
|
<FormHelperText color='gray.500'>Twitter</FormHelperText>
|
||||||
<Input type='text' size='md' variant='flushed' placeholder='@tw_username' {...register('twitter_id')} />
|
<Input
|
||||||
|
type='text'
|
||||||
|
size='md'
|
||||||
|
variant='flushed'
|
||||||
|
placeholder='@tw_username'
|
||||||
|
isInvalid={errors.twitter_id ? true : false}
|
||||||
|
{...register('twitter_id')}
|
||||||
|
/>
|
||||||
<Text as='small' color='red.500'>
|
<Text as='small' color='red.500'>
|
||||||
{errors.twitter_id?.message}
|
{errors.twitter_id?.message}
|
||||||
</Text>
|
</Text>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl id='telegram'>
|
<FormControl id='telegram'>
|
||||||
<FormHelperText color='gray.500'>Telegram</FormHelperText>
|
<FormHelperText color='gray.500'>Telegram</FormHelperText>
|
||||||
<Input type='text' size='md' variant='flushed' placeholder='@tg_username' {...register('telegram_id')} />
|
<Input
|
||||||
|
type='text'
|
||||||
|
size='md'
|
||||||
|
variant='flushed'
|
||||||
|
placeholder='@tg_username'
|
||||||
|
isInvalid={errors.telegram_id ? true : false}
|
||||||
|
{...register('telegram_id')}
|
||||||
|
/>
|
||||||
<Text as='small' color='red.500'>
|
<Text as='small' color='red.500'>
|
||||||
{errors.telegram_id?.message}
|
{errors.telegram_id?.message}
|
||||||
</Text>
|
</Text>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl id='vk'>
|
<FormControl id='vk'>
|
||||||
<FormHelperText color='gray.500'>VKontakte</FormHelperText>
|
<FormHelperText color='gray.500'>VKontakte</FormHelperText>
|
||||||
<Input type='text' size='md' variant='flushed' placeholder='VKontakte id' {...register('vk_id')} />
|
<Input
|
||||||
|
type='text'
|
||||||
|
size='md'
|
||||||
|
variant='flushed'
|
||||||
|
placeholder='VKontakte id'
|
||||||
|
isInvalid={errors.vk_id ? true : false}
|
||||||
|
{...register('vk_id')}
|
||||||
|
/>
|
||||||
<Text as='small' color='red.500'>
|
<Text as='small' color='red.500'>
|
||||||
{errors.vk_id?.message}
|
{errors.vk_id?.message}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import React 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';
|
||||||
|
|
||||||
type UserAccountInfoPropsType = {
|
type UserAccountInfoPropsType = {
|
||||||
user: IUser;
|
user: IUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserAccountInfo: React.FC<UserAccountInfoPropsType> = ({ user }) => {
|
const UserAccountInfo: React.FC<UserAccountInfoPropsType> = ({ user }) => {
|
||||||
|
const { setStatusPageView } = useContext(ProfilePageViewContext);
|
||||||
return (
|
return (
|
||||||
<Box borderRadius='md' borderWidth='1px' padding='20px'>
|
<Box borderRadius='md' borderWidth='1px' padding='20px'>
|
||||||
<Heading as='h3' size='lg'>
|
<Heading as='h3' size='lg'>
|
||||||
@ -23,7 +25,6 @@ const UserAccountInfo: React.FC<UserAccountInfoPropsType> = ({ user }) => {
|
|||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex>
|
<Flex>
|
||||||
<Box>
|
<Box>
|
||||||
<Text as='small' color='gray.500'>
|
<Text as='small' color='gray.500'>
|
||||||
@ -34,7 +35,6 @@ const UserAccountInfo: React.FC<UserAccountInfoPropsType> = ({ user }) => {
|
|||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex justifyContent='space-between' alignItems='center'>
|
<Flex justifyContent='space-between' alignItems='center'>
|
||||||
<Box>
|
<Box>
|
||||||
<Text as='small' color='gray.500'>
|
<Text as='small' color='gray.500'>
|
||||||
@ -46,7 +46,6 @@ const UserAccountInfo: React.FC<UserAccountInfoPropsType> = ({ user }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
<Button>Edit</Button>
|
<Button>Edit</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex justifyContent='space-between' alignItems='center'>
|
<Flex justifyContent='space-between' alignItems='center'>
|
||||||
<Box>
|
<Box>
|
||||||
<Text as='small' color='gray.500'>
|
<Text as='small' color='gray.500'>
|
||||||
@ -56,7 +55,7 @@ const UserAccountInfo: React.FC<UserAccountInfoPropsType> = ({ user }) => {
|
|||||||
**********
|
**********
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Button>Change</Button>
|
<Button onClick={() => setStatusPageView('update-password')}>Change</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { Box, Heading, Flex, Text, IconButton } from '@chakra-ui/react';
|
||||||
|
import { ChevronLeftIcon } from '@chakra-ui/icons';
|
||||||
|
import { ProfilePageViewContext } from 'contexts';
|
||||||
|
import UpdatePasswordForm from './UpdatePasswordForm';
|
||||||
|
|
||||||
|
const UpdatePasswordContainer: React.FC = () => {
|
||||||
|
const { setStatusPageView } = useContext(ProfilePageViewContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box maxW='700px'>
|
||||||
|
<Flex alignItems='center'>
|
||||||
|
<IconButton
|
||||||
|
aria-label='Back'
|
||||||
|
borderRadius='full'
|
||||||
|
icon={<ChevronLeftIcon />}
|
||||||
|
onClick={() => setStatusPageView(undefined)}
|
||||||
|
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;
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { Box, Stack } from '@chakra-ui/layout';
|
||||||
|
import { FormControl, FormLabel, FormHelperText } from '@chakra-ui/form-control';
|
||||||
|
import { Button } from '@chakra-ui/button';
|
||||||
|
import { Input } from '@chakra-ui/input';
|
||||||
|
import { SubmitHandler, useForm } from 'react-hook-form';
|
||||||
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import UpdatePasswordSchema from './UpdatePasswordForm.validator';
|
||||||
|
import { useRootStore } from 'hooks/useRootStore';
|
||||||
|
import { useToast } from '@chakra-ui/toast';
|
||||||
|
import { useErrorHandler } from 'react-error-boundary';
|
||||||
|
import { ProfilePageViewContext } from 'contexts';
|
||||||
|
|
||||||
|
type UpdatePasswordInputType = {
|
||||||
|
old_password: string;
|
||||||
|
new_password: string;
|
||||||
|
confirm_password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update password using form
|
||||||
|
*/
|
||||||
|
const UpdatePasswordForm: React.FC = () => {
|
||||||
|
const { setStatusPageView } = useContext(ProfilePageViewContext);
|
||||||
|
const { userStore } = useRootStore();
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
register,
|
||||||
|
reset,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<UpdatePasswordInputType>({
|
||||||
|
resolver: yupResolver(UpdatePasswordSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
const handleError = useErrorHandler();
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<UpdatePasswordInputType> = async data => {
|
||||||
|
try {
|
||||||
|
await userStore.updateCurrentUserPassword(data.old_password, data.new_password);
|
||||||
|
toast({ title: 'Password changed.', status: 'success' });
|
||||||
|
reset();
|
||||||
|
setStatusPageView(undefined);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.response) {
|
||||||
|
if (error.response.status === 401) {
|
||||||
|
toast({ title: 'Invalid current password.', status: 'error', duration: 2000 });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box maxW='700px'>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Stack spacing='15px' mt='10'>
|
||||||
|
<FormControl id='old_password' isRequired>
|
||||||
|
<FormLabel>Old password</FormLabel>
|
||||||
|
<Input
|
||||||
|
type='text'
|
||||||
|
size='md'
|
||||||
|
variant='filled'
|
||||||
|
placeholder='*******'
|
||||||
|
isInvalid={errors.old_password ? true : false}
|
||||||
|
{...register('old_password')}
|
||||||
|
/>
|
||||||
|
<FormHelperText color='gray.500'>Type your current password.</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl id='new_password' isRequired>
|
||||||
|
<FormLabel>New password</FormLabel>
|
||||||
|
<Input
|
||||||
|
type='text'
|
||||||
|
size='md'
|
||||||
|
variant='filled'
|
||||||
|
placeholder='*******'
|
||||||
|
isInvalid={errors.new_password ? true : false}
|
||||||
|
{...register('new_password')}
|
||||||
|
/>
|
||||||
|
<FormHelperText color={errors.new_password ? 'red' : 'gray.500'}>
|
||||||
|
Must be at least 6 characters.
|
||||||
|
</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl id='confirm_password' isRequired>
|
||||||
|
<FormLabel>Confirm new password</FormLabel>
|
||||||
|
<Input
|
||||||
|
type='text'
|
||||||
|
size='md'
|
||||||
|
variant='filled'
|
||||||
|
placeholder='*******'
|
||||||
|
isInvalid={errors.confirm_password ? true : false}
|
||||||
|
{...register('confirm_password')}
|
||||||
|
/>
|
||||||
|
<FormHelperText color={errors.confirm_password ? 'red' : 'gray.500'}>
|
||||||
|
Both passwords must match.
|
||||||
|
</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
</Stack>
|
||||||
|
<Button colorScheme='blue' mt='4' type='submit' size='md' variant='outline' loadingText='Saving...'>
|
||||||
|
Change
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpdatePasswordForm;
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import yup from 'validators';
|
||||||
|
|
||||||
|
const UpdatePasswordSchema = yup.object().shape({
|
||||||
|
old_password: yup.string().required('Old password is required'),
|
||||||
|
new_password: yup.string().required('New password is required').min(6, 'Password bust be at least 6 characters.'),
|
||||||
|
confirm_password: yup
|
||||||
|
.string()
|
||||||
|
.required('Confirm password is required')
|
||||||
|
.test('passwords-match', 'Make sure your passwords match.', function (value) {
|
||||||
|
return this.parent.new_password === value;
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default UpdatePasswordSchema;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import UpdatePasswordForm from './UpdatePasswordForm';
|
||||||
|
|
||||||
|
export default UpdatePasswordForm;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
import UpdatePasswordContainer from './UpdatePasswordContainer';
|
||||||
|
|
||||||
|
export default UpdatePasswordContainer;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Heading,
|
Heading,
|
||||||
@ -12,21 +12,23 @@ import {
|
|||||||
TabPanels,
|
TabPanels,
|
||||||
TabPanel,
|
TabPanel,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
import { IPageProps } from 'interfaces';
|
import { IPageProps } from 'interfaces';
|
||||||
|
import { ProfilePageStatusType } from 'types';
|
||||||
import { useRootStore } from 'hooks/useRootStore';
|
import { useRootStore } from 'hooks/useRootStore';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
import { ProfilePageViewContext } from 'contexts';
|
||||||
import UserBadge from 'components/UserBadge';
|
import UserBadge from 'components/UserBadge';
|
||||||
|
|
||||||
import LoadingPage from './components/LoadingPage';
|
import LoadingPage from './components/LoadingPage';
|
||||||
import UserAccountInfo from './components/UserAccountInfo';
|
import UserAccountInfo from './components/UserAccountInfo';
|
||||||
import UpdateUserContactsForm from './components/UpdateUserContactsForm';
|
import UpdateUserContactsForm from './components/UpdateUserContactsForm';
|
||||||
|
import UpdatePasswordContainer from './containers/UpdatePasswordContainer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Profile page
|
* Profile page
|
||||||
*/
|
*/
|
||||||
const ProfilePage: React.FC<IPageProps> = ({ title }) => {
|
const ProfilePage: React.FC<IPageProps> = () => {
|
||||||
|
const [statusPageView, setStatusPageView] = useState<ProfilePageStatusType>(undefined);
|
||||||
const { colorMode } = useColorMode();
|
const { colorMode } = useColorMode();
|
||||||
const { userStore, authStore } = useRootStore();
|
const { userStore, authStore } = useRootStore();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@ -42,6 +44,8 @@ const ProfilePage: React.FC<IPageProps> = ({ title }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<ProfilePageViewContext.Provider value={{ statusPageView, setStatusPageView }}>
|
||||||
|
{statusPageView === undefined && (
|
||||||
<Box maxW='900px'>
|
<Box maxW='900px'>
|
||||||
<Heading as='h1'>My account</Heading>
|
<Heading as='h1'>My account</Heading>
|
||||||
<Flex
|
<Flex
|
||||||
@ -97,7 +101,6 @@ const ProfilePage: React.FC<IPageProps> = ({ title }) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Box mt='10'>
|
<Box mt='10'>
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<TabList>
|
<TabList>
|
||||||
@ -115,6 +118,9 @@ const ProfilePage: React.FC<IPageProps> = ({ title }) => {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
)}
|
||||||
|
{statusPageView === 'update-password' && <UpdatePasswordContainer />}
|
||||||
|
</ProfilePageViewContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||||
import Cookies from 'js-cookie';
|
|
||||||
import RootStore from 'stores/RootStore';
|
import RootStore from 'stores/RootStore';
|
||||||
|
|
||||||
export default class ApiService {
|
export default class ApiService {
|
||||||
@ -26,9 +25,9 @@ export default class ApiService {
|
|||||||
* Unauthorized Error 401
|
* Unauthorized Error 401
|
||||||
*/
|
*/
|
||||||
if (error.response.status === 401) {
|
if (error.response.status === 401) {
|
||||||
this.root.userStore.me = null;
|
// this.root.userStore.me = null;
|
||||||
this.root.authStore.setIsLoggedIn(false);
|
// this.root.authStore.setIsLoggedIn(false);
|
||||||
Cookies.remove('logged_in');
|
// Cookies.remove('logged_in');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { AxiosInstance } from 'axios';
|
import { AxiosInstance } from 'axios';
|
||||||
import { IUpdatedContactsResult, IUserContacts, IUserResult } from 'interfaces';
|
import { IDataResult, IUpdatedContactsResult, IUserContacts, IUserResult } from 'interfaces';
|
||||||
import * as helpers from 'helpers';
|
import * as helpers from 'helpers';
|
||||||
|
|
||||||
export default class UserService {
|
export default class UserService {
|
||||||
@ -29,4 +29,19 @@ export default class UserService {
|
|||||||
const result = await this.api.put('v1/me/contacts', helpers.removeEmptyValues(data));
|
const result = await this.api.put('v1/me/contacts', helpers.removeEmptyValues(data));
|
||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update password of authorized user
|
||||||
|
*
|
||||||
|
* @param oldPassword string
|
||||||
|
* @param newPassword string
|
||||||
|
* @returns Result data
|
||||||
|
*/
|
||||||
|
public async updatePassword(oldPassword: string, newPassword: string): Promise<IDataResult> {
|
||||||
|
const result = await this.api.patch('v1/me/password', {
|
||||||
|
oldPassword,
|
||||||
|
newPassword,
|
||||||
|
});
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { AxiosInstance } from 'axios';
|
import { AxiosInstance } from 'axios';
|
||||||
import { makeAutoObservable, runInAction } from 'mobx';
|
import { makeAutoObservable, runInAction } from 'mobx';
|
||||||
import { IUserResult, IUser, IUserContacts, IUpdatedContactsResult } from 'interfaces';
|
import { IUserResult, IUser, IUserContacts, IUpdatedContactsResult, IDataResult } from 'interfaces';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Services
|
* Services
|
||||||
@ -57,6 +57,15 @@ export default class UserStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async updateCurrentUserPassword(oldPassword: string, newPassword: string): Promise<IDataResult> {
|
||||||
|
try {
|
||||||
|
const result = await this.userService.updatePassword(oldPassword, newPassword);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public reset(): void {
|
public reset(): void {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.me = null;
|
this.me = null;
|
||||||
|
|||||||
14
src/types/index.d.ts
vendored
Normal file
14
src/types/index.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { IconType } from 'react-icons/lib';
|
||||||
|
|
||||||
|
export type LinkType = {
|
||||||
|
location: string;
|
||||||
|
title: string;
|
||||||
|
public: boolean;
|
||||||
|
icon: IconType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProfilePageStatusType = 'update-password' | 'update-email' | undefined;
|
||||||
|
export type ProfilePageViewStatusContextType = {
|
||||||
|
statusPageView: ProfilePageStatusType;
|
||||||
|
setStatusPageView: React.Dispatch<React.SetStateAction<ProfilePageStatusType>>;
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user