mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
refactor: error handling while login
This commit is contained in:
parent
2245c4aa85
commit
e7e98dd7ac
@ -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);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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('/');
|
||||
}
|
||||
});
|
||||
if (authStore.isLoggedIn) {
|
||||
return <Redirect to='/' />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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.authStore.setIsLoggedIn(false);
|
||||
}
|
||||
this.root.userStore.me = null;
|
||||
this.root.authStore.setIsLoggedIn(false);
|
||||
Cookies.remove('logged_in');
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user