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) {
|
if (error.response) {
|
||||||
switch (error.response.status) {
|
switch (error.response.status) {
|
||||||
case 404:
|
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;
|
break;
|
||||||
case 401:
|
case 401:
|
||||||
toast({ title: 'Invalid password.', status: 'error' });
|
toast({ title: 'Invalid password.', status: 'error', duration: 2000 });
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
handleError(error);
|
handleError(error);
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Link,
|
Link,
|
||||||
useToast,
|
useToast,
|
||||||
|
Skeleton,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { MdSettings, MdExitToApp } from 'react-icons/md';
|
import { MdSettings, MdExitToApp } from 'react-icons/md';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
@ -20,7 +21,7 @@ import { useRootStore } from '../../../hooks/useRootStore';
|
|||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
const UserInfo: React.FC = () => {
|
const UserInfo: React.FC = () => {
|
||||||
const { authStore } = useRootStore();
|
const { authStore, userStore } = useRootStore();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
@ -34,6 +35,10 @@ const UserInfo: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (userStore.me === null) {
|
||||||
|
return <Skeleton width='160px' borderRadius='md' />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex alignItems='center'>
|
<Flex alignItems='center'>
|
||||||
<Menu>
|
<Menu>
|
||||||
@ -41,12 +46,12 @@ const UserInfo: React.FC = () => {
|
|||||||
<Flex alignItems='center'>
|
<Flex alignItems='center'>
|
||||||
<Avatar size='sm' name='user name' marginRight={3} />
|
<Avatar size='sm' name='user name' marginRight={3} />
|
||||||
<Text as='span' mr={2}>
|
<Text as='span' mr={2}>
|
||||||
Username
|
{`${userStore.me.first_name} ${userStore.me.last_name}`}
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList mr='1rem'>
|
<MenuList mr='1rem'>
|
||||||
<MenuGroup title='User fullname'>
|
<MenuGroup title={`${userStore.me.first_name} ${userStore.me.last_name}`}>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<Link to='/profile'>
|
<Link to='/profile'>
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
|
|||||||
@ -2,6 +2,9 @@ import React from 'react';
|
|||||||
import { Flex, Box } from '@chakra-ui/react';
|
import { Flex, Box } from '@chakra-ui/react';
|
||||||
import Header from '../Header/Header';
|
import Header from '../Header/Header';
|
||||||
import Nav from '../Nav/Nav';
|
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.
|
* Сomponent for rendering the current page.
|
||||||
*/
|
*/
|
||||||
const Layout: React.FC = ({ children }) => {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header />
|
<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 PrivateRoute: React.FC<IPrivateRouteProps> = ({ children, component: Component, title, ...options }) => {
|
||||||
const { authStore } = useRootStore();
|
const { authStore } = useRootStore();
|
||||||
const isLoggedIn = authStore.isLoggedIn;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Route
|
<Route
|
||||||
{...options}
|
{...options}
|
||||||
render={props =>
|
render={props =>
|
||||||
isLoggedIn ? (
|
authStore.isLoggedIn ? (
|
||||||
<Layout>
|
<Layout>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{title} • App </title>
|
<title>{title} • App </title>
|
||||||
|
|||||||
@ -1,26 +1,23 @@
|
|||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
import React, { useEffect } from 'react';
|
import React from 'react';
|
||||||
import { Flex, Box } from '@chakra-ui/react';
|
import { Flex, Box } from '@chakra-ui/react';
|
||||||
import { MdSchool } from 'react-icons/md';
|
import { MdSchool } from 'react-icons/md';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import { useRootStore } from '../hooks/useRootStore';
|
|
||||||
import { IPageProps } from '../interfaces';
|
import { IPageProps } from '../interfaces';
|
||||||
|
|
||||||
import AuthForm from '../components/Auth/AuthForm';
|
import AuthForm from '../components/Auth/AuthForm';
|
||||||
|
import { useRootStore } from '../hooks/useRootStore';
|
||||||
|
import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auth page
|
* Auth page
|
||||||
*/
|
*/
|
||||||
const AuthPage: React.FC<IPageProps> = () => {
|
const AuthPage: React.FC<IPageProps> = () => {
|
||||||
const { authStore } = useRootStore();
|
const { authStore } = useRootStore();
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (authStore.isLoggedIn) {
|
if (authStore.isLoggedIn) {
|
||||||
history.push('/');
|
return <Redirect to='/' />;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
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 { Box, Heading, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Grid } from '@chakra-ui/react';
|
||||||
|
|
||||||
import { IPageProps } from '../interfaces';
|
import { IPageProps } from '../interfaces';
|
||||||
import { useRootStore } from '../hooks/useRootStore';
|
import { observer } from 'mobx-react';
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main page
|
* Main page
|
||||||
*/
|
*/
|
||||||
const MainPage: React.FC<IPageProps> = ({ title }) => {
|
const MainPage: React.FC<IPageProps> = ({ title }) => {
|
||||||
const { authStore } = useRootStore();
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!authStore.isLoggedIn) {
|
|
||||||
history.push('/auth');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Breadcrumb fontWeight='medium' fontSize='sm'>
|
<Breadcrumb fontWeight='medium' fontSize='sm'>
|
||||||
<BreadcrumbItem isCurrentPage>
|
<BreadcrumbItem isCurrentPage>
|
||||||
<BreadcrumbLink href='/'>{title}</BreadcrumbLink>
|
<BreadcrumbLink href='/'>Main page</BreadcrumbLink>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<Box mt='10'>
|
<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 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 {
|
||||||
@ -25,10 +26,9 @@ export default class ApiService {
|
|||||||
* Unauthorized Error 401
|
* Unauthorized Error 401
|
||||||
*/
|
*/
|
||||||
if (error.response.status === 401) {
|
if (error.response.status === 401) {
|
||||||
if (this.root.authStore.isLoggedIn) {
|
this.root.userStore.me = null;
|
||||||
this.root.userStore.user = null;
|
|
||||||
this.root.authStore.setIsLoggedIn(false);
|
this.root.authStore.setIsLoggedIn(false);
|
||||||
}
|
Cookies.remove('logged_in');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|||||||
@ -7,9 +7,13 @@ export default class UserService {
|
|||||||
this.api = api;
|
this.api = api;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch authorized user data
|
||||||
|
*
|
||||||
|
* @returns User data
|
||||||
|
*/
|
||||||
public async fetchMe(): Promise<any> {
|
public async fetchMe(): Promise<any> {
|
||||||
const result = await this.api.get('v1/me');
|
const result = await this.api.get('v1/me');
|
||||||
|
|
||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,25 @@
|
|||||||
import { makeAutoObservable } from 'mobx';
|
import { makeAutoObservable } from 'mobx';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
import RootStore from './RootStore';
|
import RootStore from './RootStore';
|
||||||
|
import AuthService from '../services/AuthService';
|
||||||
|
import { AxiosInstance } from 'axios';
|
||||||
|
|
||||||
export default class AuthStore {
|
export default class AuthStore {
|
||||||
public root: RootStore;
|
public root: RootStore;
|
||||||
|
|
||||||
public token: string | null = null;
|
public authService: AuthService;
|
||||||
|
|
||||||
public isLoggedIn: boolean = !!Cookies.get('logged_in');
|
public isLoggedIn: boolean = !!Cookies.get('logged_in');
|
||||||
|
|
||||||
public loading: boolean = false;
|
public loading: boolean = false;
|
||||||
|
|
||||||
constructor(root: RootStore) {
|
constructor(root: RootStore, api: AxiosInstance) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
|
this.authService = new AuthService(api);
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setIsLoggedIn(value: boolean) {
|
public setIsLoggedIn(value: boolean) {
|
||||||
Cookies.remove('logged_in');
|
|
||||||
this.isLoggedIn = value;
|
this.isLoggedIn = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,22 +27,16 @@ export default class AuthStore {
|
|||||||
this.loading = value;
|
this.loading = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setToken(token: string) {
|
|
||||||
Cookies.set('logged_in', 'true');
|
|
||||||
this.token = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async login(login: string, password: string) {
|
public async login(login: string, password: string) {
|
||||||
try {
|
try {
|
||||||
this.setLoading(true);
|
this.setLoading(true);
|
||||||
const result = await this.root.authService.requestLogin(login, password);
|
const result = await this.authService.requestLogin(login, password);
|
||||||
|
|
||||||
this.setIsLoggedIn(true);
|
this.setIsLoggedIn(true);
|
||||||
this.setToken(result.data.token);
|
Cookies.set('logged_in', 'true');
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
this.setLoading(false);
|
this.setLoading(false);
|
||||||
@ -50,12 +46,12 @@ export default class AuthStore {
|
|||||||
public async logout() {
|
public async logout() {
|
||||||
try {
|
try {
|
||||||
this.setLoading(true);
|
this.setLoading(true);
|
||||||
const result = await this.root.authService.requestLogout();
|
const result = await this.authService.requestLogout();
|
||||||
|
|
||||||
this.setIsLoggedIn(false);
|
this.setIsLoggedIn(false);
|
||||||
|
Cookies.remove('logged_in');
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
this.setLoading(false);
|
this.setLoading(false);
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { API_BASE_URL } from '../constants';
|
|||||||
import AuthStore from './AuthStore';
|
import AuthStore from './AuthStore';
|
||||||
|
|
||||||
import ApiService from '../services/ApiService';
|
import ApiService from '../services/ApiService';
|
||||||
import AuthService from '../services/AuthService';
|
|
||||||
import UserStore from './UserStore';
|
import UserStore from './UserStore';
|
||||||
import { makeAutoObservable } from 'mobx';
|
import { makeAutoObservable } from 'mobx';
|
||||||
|
|
||||||
@ -13,24 +12,22 @@ export default class RootStore {
|
|||||||
|
|
||||||
public apiService: ApiService;
|
public apiService: ApiService;
|
||||||
|
|
||||||
public authService: AuthService;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
/**
|
/**
|
||||||
* Stores
|
* Api service
|
||||||
*/
|
|
||||||
this.authStore = new AuthStore(this);
|
|
||||||
this.userStore = new UserStore(this);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Services
|
|
||||||
*/
|
*/
|
||||||
this.apiService = new ApiService(this, {
|
this.apiService = new ApiService(this, {
|
||||||
baseURL: API_BASE_URL,
|
baseURL: API_BASE_URL,
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
withCredentials: true,
|
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);
|
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';
|
import RootStore from './RootStore';
|
||||||
|
|
||||||
export interface IUser {
|
export interface IUser {
|
||||||
@ -11,10 +13,25 @@ export interface IUser {
|
|||||||
export default class UserStore {
|
export default class UserStore {
|
||||||
public root: RootStore;
|
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.root = root;
|
||||||
|
this.userService = new UserService(api);
|
||||||
makeAutoObservable(this);
|
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