diff --git a/src/api.config.ts b/src/api.config.ts
index dca9c91..2e0d588 100644
--- a/src/api.config.ts
+++ b/src/api.config.ts
@@ -1,8 +1,8 @@
import { AxiosRequestConfig } from 'axios';
-import { API_BASE_URL } from './constants';
+import * as constants from './constants';
const apiConfig: AxiosRequestConfig = {
- baseURL: API_BASE_URL,
+ baseURL: constants.API_BASE_URL,
responseType: 'json',
withCredentials: true,
};
diff --git a/src/components/ErrorFallback.tsx b/src/components/ErrorFallback.tsx
index e0a66b2..9703a43 100644
--- a/src/components/ErrorFallback.tsx
+++ b/src/components/ErrorFallback.tsx
@@ -1,8 +1,11 @@
import React from 'react';
+import Cookies from 'js-cookie';
import { useToast, Flex, Box, Heading, Text } from '@chakra-ui/react';
import { MdError } from 'react-icons/md';
+import { useRootStore } from 'hooks/useRootStore';
const ErrorFallback: React.FC<{ error: any }> = props => {
+ const { userStore, authStore } = useRootStore();
const { error } = props;
const toast = useToast();
@@ -10,6 +13,14 @@ const ErrorFallback: React.FC<{ error: any }> = props => {
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 (
diff --git a/src/components/Header/AppName.tsx b/src/components/Header/AppName.tsx
deleted file mode 100644
index 3dbd51f..0000000
--- a/src/components/Header/AppName.tsx
+++ /dev/null
@@ -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 (
-
-
-
-
-
-
- {config.metaData.appName}
-
- {config.metaData.appDescription}
-
-
- );
-};
-
-export default AppName;
diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx
index 1a944ea..8c8c237 100644
--- a/src/components/Header/Header.tsx
+++ b/src/components/Header/Header.tsx
@@ -1,11 +1,13 @@
+import config from 'config';
import React from 'react';
import { observer } from 'mobx-react';
import { useRootStore } from 'hooks/useRootStore';
-import { Flex, Box, useColorMode } from '@chakra-ui/react';
-import AppName from './AppName';
+import { Flex, Box, Heading, Text, useColorMode } from '@chakra-ui/react';
import UserInfo from './UserInfo';
import UserNotifications from './UserNotifications';
import NavMobile from 'components/Nav/Mobile';
+import ColorModeSwitcher from 'components/ColorModeSwitcher';
+import { MdSchool } from 'react-icons/md';
const Header: React.FC = () => {
const {
@@ -31,17 +33,26 @@ const Header: React.FC = () => {
alignItems='center'
padding='5px 1.5rem 5px 1.5rem'
>
-
-
- {isDesktop ? (
- <>
-
-
- >
- ) : (
-
- )}
+
+
+
+
+
+
+ {config.metaData.appName}
+
+ {config.metaData.appDescription}
+
+ {isDesktop ? (
+
+
+
+
+
+ ) : (
+
+ )}
);
diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx
index 6e9541e..b59a9bc 100644
--- a/src/components/Layout/Layout.tsx
+++ b/src/components/Layout/Layout.tsx
@@ -5,7 +5,6 @@ import { Flex, Box } from '@chakra-ui/react';
import { useRootStore } from 'hooks/useRootStore';
import Header from 'components/Header';
import NavDesktop from 'components/Nav/Desktop';
-import ColorModeSwitcher from 'components/ColorModeSwitcher';
/**
*
@@ -27,7 +26,7 @@ const Layout: React.FC = ({ children }) => {
{
>
{children}
-
>
);
diff --git a/src/constants.ts b/src/constants/index.ts
similarity index 100%
rename from src/constants.ts
rename to src/constants/index.ts
diff --git a/src/contexts/index.ts b/src/contexts/index.ts
index 633275f..7a1e9c7 100644
--- a/src/contexts/index.ts
+++ b/src/contexts/index.ts
@@ -1,4 +1,10 @@
import React from 'react';
+import { ProfilePageViewStatusContextType } from 'types';
import RootStore from '../stores/RootStore';
export const StoreContext = React.createContext(undefined);
+
+export const ProfilePageViewContext = React.createContext({
+ statusPageView: undefined,
+ setStatusPageView: () => {},
+});
diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts
index 0b10ecf..26f8f40 100644
--- a/src/interfaces/index.ts
+++ b/src/interfaces/index.ts
@@ -1,15 +1,8 @@
-import { IconType } from 'react-icons/lib';
import type { RouteProps } from 'react-router-dom';
import type { RouteComponentProps } from 'react-router-dom';
+import { LinkType } from 'types';
import { UserRoleEnum } from '../enums';
-export type LinkType = {
- location: string;
- title: string;
- public: boolean;
- icon: IconType;
-};
-
export interface IAppConfig {
metaData: {
appName: string;
diff --git a/src/pages/main/index.tsx b/src/pages/main/index.tsx
index 30107cb..012c5bd 100644
--- a/src/pages/main/index.tsx
+++ b/src/pages/main/index.tsx
@@ -18,9 +18,7 @@ const MainPage: React.FC = ({ title }) => {
Main page
-
- ...
-
+
>
diff --git a/src/pages/profile/components/UpdateUserContactsForm/UpdateUserContactsForm.tsx b/src/pages/profile/components/UpdateUserContactsForm/UpdateUserContactsForm.tsx
index 4fb374c..3891d23 100644
--- a/src/pages/profile/components/UpdateUserContactsForm/UpdateUserContactsForm.tsx
+++ b/src/pages/profile/components/UpdateUserContactsForm/UpdateUserContactsForm.tsx
@@ -1,17 +1,14 @@
import React from 'react';
+import { observer } from 'mobx-react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { MdSave } from 'react-icons/md';
import { IUserContacts } from 'interfaces';
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 { Input } from '@chakra-ui/input';
-import { Text } from '@chakra-ui/react';
-import { Button } from '@chakra-ui/button';
import { yupResolver } from '@hookform/resolvers/yup';
-
-import UpdateUserContactsSchema from 'validators/UpdateUserContactsSchema';
import useUpdateUserContactsQuery from 'hooks/useUpdateUserContactsQuery';
-import { observer } from 'mobx-react';
+import UpdateUserContactsSchema from 'validators/UpdateUserContactsSchema';
type UpdateUserContactsInputType = {
phone_number: string | null;
@@ -67,6 +64,7 @@ const UpdateUserContactsForm: React.FC = ({ contact
size='md'
variant='flushed'
placeholder='+1 (234) 555-1234'
+ isInvalid={errors.phone_number ? true : false}
{...register('phone_number')}
/>
@@ -75,21 +73,42 @@ const UpdateUserContactsForm: React.FC = ({ contact
Telegram
-
+
{errors.telegram_id?.message}
VKontakte
-
+
{errors.vk_id?.message}
diff --git a/src/pages/profile/components/UserAccountInfo/UserAccountInfo.tsx b/src/pages/profile/components/UserAccountInfo/UserAccountInfo.tsx
index 71358aa..6877836 100644
--- a/src/pages/profile/components/UserAccountInfo/UserAccountInfo.tsx
+++ b/src/pages/profile/components/UserAccountInfo/UserAccountInfo.tsx
@@ -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 { IUser } from 'interfaces';
+import { ProfilePageViewContext } from 'contexts';
type UserAccountInfoPropsType = {
user: IUser;
};
const UserAccountInfo: React.FC = ({ user }) => {
+ const { setStatusPageView } = useContext(ProfilePageViewContext);
return (
@@ -23,7 +25,6 @@ const UserAccountInfo: React.FC = ({ user }) => {
-
@@ -34,7 +35,6 @@ const UserAccountInfo: React.FC = ({ user }) => {
-
@@ -46,7 +46,6 @@ const UserAccountInfo: React.FC = ({ user }) => {
-
@@ -56,7 +55,7 @@ const UserAccountInfo: React.FC = ({ user }) => {
**********
-
+
diff --git a/src/pages/profile/containers/UpdatePasswordContainer/UpdatePasswordContainer.tsx b/src/pages/profile/containers/UpdatePasswordContainer/UpdatePasswordContainer.tsx
new file mode 100644
index 0000000..1a8fdcb
--- /dev/null
+++ b/src/pages/profile/containers/UpdatePasswordContainer/UpdatePasswordContainer.tsx
@@ -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 (
+
+
+ }
+ onClick={() => setStatusPageView(undefined)}
+ mr='5'
+ />
+ Update password
+
+
+ Your new password must be different from the current one.
+
+
+
+ );
+};
+
+export default UpdatePasswordContainer;
diff --git a/src/pages/profile/containers/UpdatePasswordContainer/UpdatePasswordForm/UpdatePasswordForm.tsx b/src/pages/profile/containers/UpdatePasswordContainer/UpdatePasswordForm/UpdatePasswordForm.tsx
new file mode 100644
index 0000000..f236111
--- /dev/null
+++ b/src/pages/profile/containers/UpdatePasswordContainer/UpdatePasswordForm/UpdatePasswordForm.tsx
@@ -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({
+ resolver: yupResolver(UpdatePasswordSchema),
+ });
+
+ const toast = useToast();
+ const handleError = useErrorHandler();
+
+ const onSubmit: SubmitHandler = 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 (
+
+
+
+ );
+};
+
+export default UpdatePasswordForm;
diff --git a/src/pages/profile/containers/UpdatePasswordContainer/UpdatePasswordForm/UpdatePasswordForm.validator.ts b/src/pages/profile/containers/UpdatePasswordContainer/UpdatePasswordForm/UpdatePasswordForm.validator.ts
new file mode 100644
index 0000000..485c887
--- /dev/null
+++ b/src/pages/profile/containers/UpdatePasswordContainer/UpdatePasswordForm/UpdatePasswordForm.validator.ts
@@ -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;
diff --git a/src/pages/profile/containers/UpdatePasswordContainer/UpdatePasswordForm/index.ts b/src/pages/profile/containers/UpdatePasswordContainer/UpdatePasswordForm/index.ts
new file mode 100644
index 0000000..46c734f
--- /dev/null
+++ b/src/pages/profile/containers/UpdatePasswordContainer/UpdatePasswordForm/index.ts
@@ -0,0 +1,3 @@
+import UpdatePasswordForm from './UpdatePasswordForm';
+
+export default UpdatePasswordForm;
diff --git a/src/pages/profile/containers/UpdatePasswordContainer/index.ts b/src/pages/profile/containers/UpdatePasswordContainer/index.ts
new file mode 100644
index 0000000..37feee6
--- /dev/null
+++ b/src/pages/profile/containers/UpdatePasswordContainer/index.ts
@@ -0,0 +1,3 @@
+import UpdatePasswordContainer from './UpdatePasswordContainer';
+
+export default UpdatePasswordContainer;
diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx
index f07a9d1..4f3a003 100644
--- a/src/pages/profile/index.tsx
+++ b/src/pages/profile/index.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
import {
Box,
Heading,
@@ -12,21 +12,23 @@ import {
TabPanels,
TabPanel,
} from '@chakra-ui/react';
-
import { IPageProps } from 'interfaces';
+import { ProfilePageStatusType } from 'types';
import { useRootStore } from 'hooks/useRootStore';
import { useHistory } from 'react-router-dom';
import { observer } from 'mobx-react';
+import { ProfilePageViewContext } from 'contexts';
import UserBadge from 'components/UserBadge';
-
import LoadingPage from './components/LoadingPage';
import UserAccountInfo from './components/UserAccountInfo';
import UpdateUserContactsForm from './components/UpdateUserContactsForm';
+import UpdatePasswordContainer from './containers/UpdatePasswordContainer';
/**
* Profile page
*/
-const ProfilePage: React.FC = ({ title }) => {
+const ProfilePage: React.FC = () => {
+ const [statusPageView, setStatusPageView] = useState(undefined);
const { colorMode } = useColorMode();
const { userStore, authStore } = useRootStore();
const history = useHistory();
@@ -42,79 +44,83 @@ const ProfilePage: React.FC = ({ title }) => {
}
return (
-
- My account
-
-
-
-
-
- {`${userStore.me.first_name} ${userStore.me.last_name}`}
+
+ {statusPageView === undefined && (
+
+ My account
-
+
+
+
- {userStore.me.email}
-
-
+ {`${userStore.me.first_name} ${userStore.me.last_name}`}
+
+
+ {userStore.me.email}
+
+
+
+
-
-
-
-
-
-
- Account information
- My contacts
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Account information
+ My contacts
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ {statusPageView === 'update-password' && }
+
);
};
diff --git a/src/services/ApiService.ts b/src/services/ApiService.ts
index aa16b1b..705a9d2 100644
--- a/src/services/ApiService.ts
+++ b/src/services/ApiService.ts
@@ -1,5 +1,4 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
-import Cookies from 'js-cookie';
import RootStore from 'stores/RootStore';
export default class ApiService {
@@ -26,9 +25,9 @@ export default class ApiService {
* Unauthorized Error 401
*/
if (error.response.status === 401) {
- this.root.userStore.me = null;
- this.root.authStore.setIsLoggedIn(false);
- Cookies.remove('logged_in');
+ // this.root.userStore.me = null;
+ // this.root.authStore.setIsLoggedIn(false);
+ // Cookies.remove('logged_in');
}
}
return Promise.reject(error);
diff --git a/src/services/UserService.ts b/src/services/UserService.ts
index c78886b..25445fd 100644
--- a/src/services/UserService.ts
+++ b/src/services/UserService.ts
@@ -1,5 +1,5 @@
import { AxiosInstance } from 'axios';
-import { IUpdatedContactsResult, IUserContacts, IUserResult } from 'interfaces';
+import { IDataResult, IUpdatedContactsResult, IUserContacts, IUserResult } from 'interfaces';
import * as helpers from 'helpers';
export default class UserService {
@@ -29,4 +29,19 @@ export default class UserService {
const result = await this.api.put('v1/me/contacts', helpers.removeEmptyValues(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 {
+ const result = await this.api.patch('v1/me/password', {
+ oldPassword,
+ newPassword,
+ });
+ return result.data;
+ }
}
diff --git a/src/stores/UserStore.ts b/src/stores/UserStore.ts
index 18fd794..2d041ed 100644
--- a/src/stores/UserStore.ts
+++ b/src/stores/UserStore.ts
@@ -1,6 +1,6 @@
import { AxiosInstance } from 'axios';
import { makeAutoObservable, runInAction } from 'mobx';
-import { IUserResult, IUser, IUserContacts, IUpdatedContactsResult } from 'interfaces';
+import { IUserResult, IUser, IUserContacts, IUpdatedContactsResult, IDataResult } from 'interfaces';
/**
* Services
@@ -57,6 +57,15 @@ export default class UserStore {
}
}
+ public async updateCurrentUserPassword(oldPassword: string, newPassword: string): Promise {
+ try {
+ const result = await this.userService.updatePassword(oldPassword, newPassword);
+ return result;
+ } catch (error) {
+ throw error;
+ }
+ }
+
public reset(): void {
runInAction(() => {
this.me = null;
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
new file mode 100644
index 0000000..f8e7946
--- /dev/null
+++ b/src/types/index.d.ts
@@ -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>;
+};