feat: password update form

This commit is contained in:
Sergey Yarkov 2021-09-03 23:53:46 +03:00
parent add3b1bb24
commit fe00bb5e72
21 changed files with 347 additions and 133 deletions

View File

@ -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,
}; };

View File

@ -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'>

View File

@ -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;

View File

@ -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,17 +33,26 @@ 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'>
{isDesktop ? ( <Box as={MdSchool} color='blue.500' size='36px' />
<> </Box>
<UserNotifications /> <Box>
<UserInfo /> <Heading as='p' fontSize='2xl' lineHeight='1'>
</> {config.metaData.appName}
) : ( </Heading>
<NavMobile /> <Text as='small'>{config.metaData.appDescription}</Text>
)} </Box>
</Flex> </Flex>
{isDesktop ? (
<Flex>
<ColorModeSwitcher />
<UserNotifications />
<UserInfo />
</Flex>
) : (
<NavMobile />
)}
</Flex> </Flex>
</Box> </Box>
); );

View File

@ -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>
</> </>
); );

View File

@ -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: () => {},
});

View File

@ -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;

View File

@ -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>
</> </>

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

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

View File

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

View File

@ -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,79 +44,83 @@ const ProfilePage: React.FC<IPageProps> = ({ title }) => {
} }
return ( return (
<Box maxW='900px'> <ProfilePageViewContext.Provider value={{ statusPageView, setStatusPageView }}>
<Heading as='h1'>My account</Heading> {statusPageView === undefined && (
<Flex <Box maxW='900px'>
alignItems='center' <Heading as='h1'>My account</Heading>
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={`${userStore.me.first_name} ${userStore.me.last_name}`}
/>
</Box>
<Flex
ml='50'
flexDirection='column'
sx={{
'@media (max-width: 768px)': {
ml: '0',
mt: '5',
},
}}
>
<Heading as='h2'>{`${userStore.me.first_name} ${userStore.me.last_name}`}</Heading>
<Flex <Flex
alignItems='center' alignItems='center'
mt='20'
sx={{ sx={{
'@media (max-width: 768px)': { '@media (max-width: 768px)': {
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
mt: '10',
}, },
}} }}
> >
<Text <Box>
mr='10px' <Avatar
border={`4px solid ${colorMode === 'dark' ? '#2D3748' : '#E2E8F0'}`}
size='2xl'
name={`${userStore.me.first_name} ${userStore.me.last_name}`}
/>
</Box>
<Flex
ml='50'
flexDirection='column'
sx={{ sx={{
'@media (max-width: 768px)': { '@media (max-width: 768px)': {
mr: '0', ml: '0',
mb: '5px', mt: '5',
}, },
}} }}
> >
{userStore.me.email} <Heading as='h2'>{`${userStore.me.first_name} ${userStore.me.last_name}`}</Heading>
</Text> <Flex
<UserBadge roles={userStore.me.roles} /> 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> </Flex>
</Flex> <Box mt='10'>
</Flex> <Tabs>
<TabList>
<Box mt='10'> <Tab>Account information</Tab>
<Tabs> <Tab>My contacts</Tab>
<TabList> </TabList>
<Tab>Account information</Tab> <TabPanels>
<Tab>My contacts</Tab> <TabPanel padding='10px 0' mt='20px'>
</TabList> <UserAccountInfo user={userStore.me} />
<TabPanels> </TabPanel>
<TabPanel padding='10px 0' mt='20px'> <TabPanel padding='10px 0' mt='20px'>
<UserAccountInfo user={userStore.me} /> <UpdateUserContactsForm contacts={userStore.me.contacts} />
</TabPanel> </TabPanel>
<TabPanel padding='10px 0' mt='20px'> </TabPanels>
<UpdateUserContactsForm contacts={userStore.me.contacts} /> </Tabs>
</TabPanel> </Box>
</TabPanels> </Box>
</Tabs> )}
</Box> {statusPageView === 'update-password' && <UpdatePasswordContainer />}
</Box> </ProfilePageViewContext.Provider>
); );
}; };

View File

@ -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);

View File

@ -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;
}
} }

View File

@ -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
View 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>>;
};