refactor: error handling while login

This commit is contained in:
Sergey Yarkov 2021-08-24 01:55:36 +03:00
parent 2245c4aa85
commit e7e98dd7ac
11 changed files with 84 additions and 63 deletions

View File

@ -33,10 +33,10 @@ const AuthForm: React.FC = () => {
if (error.response) {
switch (error.response.status) {
case 404:
toast({ title: 'User not found in a system.', status: 'error' });
toast({ title: 'User not found in a system.', status: 'error', duration: 2000 });
break;
case 401:
toast({ title: 'Invalid password.', status: 'error' });
toast({ title: 'Invalid password.', status: 'error', duration: 2000 });
break;
default:
handleError(error);

View File

@ -13,6 +13,7 @@ import {
Text,
Link,
useToast,
Skeleton,
} from '@chakra-ui/react';
import { MdSettings, MdExitToApp } from 'react-icons/md';
import { observer } from 'mobx-react';
@ -20,7 +21,7 @@ import { useRootStore } from '../../../hooks/useRootStore';
import { useHistory } from 'react-router-dom';
const UserInfo: React.FC = () => {
const { authStore } = useRootStore();
const { authStore, userStore } = useRootStore();
const history = useHistory();
const toast = useToast();
@ -34,6 +35,10 @@ const UserInfo: React.FC = () => {
}
};
if (userStore.me === null) {
return <Skeleton width='160px' borderRadius='md' />;
}
return (
<Flex alignItems='center'>
<Menu>
@ -41,12 +46,12 @@ const UserInfo: React.FC = () => {
<Flex alignItems='center'>
<Avatar size='sm' name='user name' marginRight={3} />
<Text as='span' mr={2}>
Username
{`${userStore.me.first_name} ${userStore.me.last_name}`}
</Text>
</Flex>
</MenuButton>
<MenuList mr='1rem'>
<MenuGroup title='User fullname'>
<MenuGroup title={`${userStore.me.first_name} ${userStore.me.last_name}`}>
<MenuDivider />
<Link to='/profile'>
<MenuItem>

View File

@ -2,6 +2,9 @@ import React from 'react';
import { Flex, Box } from '@chakra-ui/react';
import Header from '../Header/Header';
import Nav from '../Nav/Nav';
import { observer } from 'mobx-react';
import { useRootStore } from '../../hooks/useRootStore';
import { useErrorHandler } from 'react-error-boundary';
/**
*
@ -9,6 +12,19 @@ import Nav from '../Nav/Nav';
* Сomponent for rendering the current page.
*/
const Layout: React.FC = ({ children }) => {
const { authStore } = useRootStore();
const handleError = useErrorHandler();
React.useEffect(() => {
if (authStore.isLoggedIn) {
authStore.root.userStore.loadCurrentUserData().catch(error => {
if (error.response && error.response.status === 401) {
handleError(error);
}
});
}
});
return (
<>
<Header />
@ -24,4 +40,4 @@ const Layout: React.FC = ({ children }) => {
);
};
export default Layout;
export default observer(Layout);

View File

@ -12,13 +12,12 @@ import { observer } from 'mobx-react';
*/
const PrivateRoute: React.FC<IPrivateRouteProps> = ({ children, component: Component, title, ...options }) => {
const { authStore } = useRootStore();
const isLoggedIn = authStore.isLoggedIn;
return (
<Route
{...options}
render={props =>
isLoggedIn ? (
authStore.isLoggedIn ? (
<Layout>
<Helmet>
<title>{title} • App </title>

View File

@ -1,26 +1,23 @@
import { observer } from 'mobx-react';
import Helmet from 'react-helmet';
import React, { useEffect } from 'react';
import React from 'react';
import { Flex, Box } from '@chakra-ui/react';
import { MdSchool } from 'react-icons/md';
import { useHistory } from 'react-router-dom';
import { useRootStore } from '../hooks/useRootStore';
import { IPageProps } from '../interfaces';
import AuthForm from '../components/Auth/AuthForm';
import { useRootStore } from '../hooks/useRootStore';
import { Redirect } from 'react-router-dom';
/**
* Auth page
*/
const AuthPage: React.FC<IPageProps> = () => {
const { authStore } = useRootStore();
const history = useHistory();
useEffect(() => {
if (authStore.isLoggedIn) {
history.push('/');
return <Redirect to='/' />;
}
});
return (
<>

View File

@ -1,28 +1,18 @@
import React, { useEffect } from 'react';
import React from 'react';
import { Box, Heading, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Grid } from '@chakra-ui/react';
import { IPageProps } from '../interfaces';
import { useRootStore } from '../hooks/useRootStore';
import { useHistory } from 'react-router-dom';
import { observer } from 'mobx-react';
/**
* Main page
*/
const MainPage: React.FC<IPageProps> = ({ title }) => {
const { authStore } = useRootStore();
const history = useHistory();
useEffect(() => {
if (!authStore.isLoggedIn) {
history.push('/auth');
}
});
return (
<>
<Breadcrumb fontWeight='medium' fontSize='sm'>
<BreadcrumbItem isCurrentPage>
<BreadcrumbLink href='/'>{title}</BreadcrumbLink>
<BreadcrumbLink href='/'>Main page</BreadcrumbLink>
</BreadcrumbItem>
</Breadcrumb>
<Box mt='10'>
@ -37,4 +27,4 @@ const MainPage: React.FC<IPageProps> = ({ title }) => {
);
};
export default MainPage;
export default observer(MainPage);

View File

@ -1,4 +1,5 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import Cookies from 'js-cookie';
import RootStore from '../stores/RootStore';
export default class ApiService {
@ -25,10 +26,9 @@ export default class ApiService {
* Unauthorized Error 401
*/
if (error.response.status === 401) {
if (this.root.authStore.isLoggedIn) {
this.root.userStore.user = null;
this.root.userStore.me = null;
this.root.authStore.setIsLoggedIn(false);
}
Cookies.remove('logged_in');
}
}
return Promise.reject(error);

View File

@ -7,9 +7,13 @@ export default class UserService {
this.api = api;
}
/**
* Fetch authorized user data
*
* @returns User data
*/
public async fetchMe(): Promise<any> {
const result = await this.api.get('v1/me');
return result.data;
}
}

View File

@ -1,23 +1,25 @@
import { makeAutoObservable } from 'mobx';
import Cookies from 'js-cookie';
import RootStore from './RootStore';
import AuthService from '../services/AuthService';
import { AxiosInstance } from 'axios';
export default class AuthStore {
public root: RootStore;
public token: string | null = null;
public authService: AuthService;
public isLoggedIn: boolean = !!Cookies.get('logged_in');
public loading: boolean = false;
constructor(root: RootStore) {
constructor(root: RootStore, api: AxiosInstance) {
this.root = root;
this.authService = new AuthService(api);
makeAutoObservable(this);
}
public setIsLoggedIn(value: boolean) {
Cookies.remove('logged_in');
this.isLoggedIn = value;
}
@ -25,22 +27,16 @@ export default class AuthStore {
this.loading = value;
}
public setToken(token: string) {
Cookies.set('logged_in', 'true');
this.token = token;
}
public async login(login: string, password: string) {
try {
this.setLoading(true);
const result = await this.root.authService.requestLogin(login, password);
const result = await this.authService.requestLogin(login, password);
this.setIsLoggedIn(true);
this.setToken(result.data.token);
Cookies.set('logged_in', 'true');
return result;
} catch (error) {
console.error(error);
throw error;
} finally {
this.setLoading(false);
@ -50,12 +46,12 @@ export default class AuthStore {
public async logout() {
try {
this.setLoading(true);
const result = await this.root.authService.requestLogout();
const result = await this.authService.requestLogout();
this.setIsLoggedIn(false);
Cookies.remove('logged_in');
return result;
} catch (error) {
console.error(error);
throw error;
} finally {
this.setLoading(false);

View File

@ -2,7 +2,6 @@ import { API_BASE_URL } from '../constants';
import AuthStore from './AuthStore';
import ApiService from '../services/ApiService';
import AuthService from '../services/AuthService';
import UserStore from './UserStore';
import { makeAutoObservable } from 'mobx';
@ -13,24 +12,22 @@ export default class RootStore {
public apiService: ApiService;
public authService: AuthService;
constructor() {
/**
* Stores
*/
this.authStore = new AuthStore(this);
this.userStore = new UserStore(this);
/**
* Services
* Api service
*/
this.apiService = new ApiService(this, {
baseURL: API_BASE_URL,
responseType: 'json',
withCredentials: true,
});
this.authService = new AuthService(this.apiService.api);
/**
* Stores
*/
this.authStore = new AuthStore(this, this.apiService.api);
this.userStore = new UserStore(this, this.apiService.api);
makeAutoObservable(this);
}
}

View File

@ -1,4 +1,6 @@
import { makeAutoObservable } from 'mobx';
import { AxiosInstance } from 'axios';
import { autorun, makeAutoObservable } from 'mobx';
import UserService from '../services/UserService';
import RootStore from './RootStore';
export interface IUser {
@ -11,10 +13,25 @@ export interface IUser {
export default class UserStore {
public root: RootStore;
public user: IUser | null = null;
public userService: UserService;
constructor(root: RootStore) {
public me: IUser | null = null;
public users: IUser[] = [];
constructor(root: RootStore, api: AxiosInstance) {
this.root = root;
this.userService = new UserService(api);
makeAutoObservable(this);
}
public async loadCurrentUserData(): Promise<any> {
const result = await this.userService.fetchMe();
autorun(() => {
this.me = result.data;
});
return result;
}
}