feat: added some pages & error handling on auth

This commit is contained in:
Sergey Yarkov 2021-08-23 15:16:15 +03:00
parent 8ef0d4f8f2
commit 2245c4aa85
29 changed files with 396 additions and 145 deletions

10
.prettierrc.json Normal file
View File

@ -0,0 +1,10 @@
{
"singleQuote": true,
"useTabs": false,
"tabWidth": 2,
"semi": true,
"jsxSingleQuote": true,
"arrowParens": "avoid",
"endOfLine": "auto",
"printWidth": 120
}

8
package-lock.json generated
View File

@ -12527,6 +12527,14 @@
"scheduler": "^0.20.2" "scheduler": "^0.20.2"
} }
}, },
"react-error-boundary": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.3.tgz",
"integrity": "sha512-A+F9HHy9fvt9t8SNDlonq01prnU8AmkjvGKV4kk8seB9kU3xMEO8J/PQlLVmoOIDODl5U2kufSBs4vrWIqhsAA==",
"requires": {
"@babel/runtime": "^7.12.5"
}
},
"react-error-overlay": { "react-error-overlay": {
"version": "6.0.9", "version": "6.0.9",
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz",

View File

@ -19,6 +19,7 @@
"mobx-react": "^7.2.0", "mobx-react": "^7.2.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-error-boundary": "^3.1.3",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-hook-form": "^7.12.2", "react-hook-form": "^7.12.2",
"react-icons": "^3.11.0", "react-icons": "^3.11.0",

View File

@ -1,42 +1,34 @@
import React from 'react'; import React from 'react';
import { observer } from 'mobx-react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import PrivateRoute from './components/PrivateRoute/PrivateRoute'; import PrivateRoute from './components/PrivateRoute/PrivateRoute';
import { useRootStore } from './hooks/useRootStore'; import ErrorFallback from './components/ErrorFallback';
import { observer } from 'mobx-react';
/** /**
* Pages * Pages
*/ */
import MainPage from './pages/main'; import MainPage from './pages/main';
import CoursesPage from './pages/courses';
import MessagesPage from './pages/messages';
import ProfilePage from './pages/profile';
import AuthPage from './pages/auth'; import AuthPage from './pages/auth';
import NotFoundPage from './pages/404'; import NotFoundPage from './pages/404';
import { useToast } from '@chakra-ui/react'; import { ErrorBoundary } from 'react-error-boundary';
import { autorun } from 'mobx';
const App = () => { const App = () => {
const rootStore = useRootStore();
const toast = useToast();
React.useEffect(() => {
autorun(() => {
if (rootStore.networkApiError !== undefined) {
toast({
title: `Network error while connecting to the server.`,
description: 'Try your request later.',
status: 'error',
});
}
});
});
return ( return (
<Router> <ErrorBoundary FallbackComponent={ErrorFallback}>
<Switch> <Router>
<PrivateRoute exact path='/' component={MainPage} title='Main' /> <Switch>
<Route path='/auth' component={AuthPage} /> <PrivateRoute exact path='/' component={MainPage} title='Main' />
<Route component={NotFoundPage} /> <PrivateRoute path='/courses' component={CoursesPage} title='Courses' />
</Switch> <PrivateRoute path='/messages' component={MessagesPage} title='Messages' />
</Router> <PrivateRoute path='/profile' component={ProfilePage} title='Profile' />
<Route path='/auth' component={AuthPage} />
<Route component={NotFoundPage} />
</Switch>
</Router>
</ErrorBoundary>
); );
}; };

View File

@ -1,18 +1,11 @@
import React from 'react'; import React from 'react';
import { MdAccountCircle, MdVpnKey } from 'react-icons/md'; import { MdAccountCircle, MdVpnKey } from 'react-icons/md';
import { import { FormControl, FormLabel, InputGroup, Input, InputLeftElement, Button, Icon, useToast } from '@chakra-ui/react';
FormControl,
FormLabel,
InputGroup,
Input,
InputLeftElement,
Button,
Icon,
} from '@chakra-ui/react';
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 { SubmitHandler, useForm } from 'react-hook-form'; import { SubmitHandler, useForm } from 'react-hook-form';
import { useErrorHandler } from 'react-error-boundary';
type AuthInputs = { type AuthInputs = {
login: string; login: string;
@ -23,13 +16,35 @@ const AuthForm: React.FC = () => {
const { register, reset, handleSubmit } = useForm<AuthInputs>(); const { register, reset, handleSubmit } = useForm<AuthInputs>();
const { authStore } = useRootStore(); const { authStore } = useRootStore();
const history = useHistory(); const history = useHistory();
const handleError = useErrorHandler();
const toast = useToast();
const onSubmit: SubmitHandler<AuthInputs> = async (data) => { const onSubmit: SubmitHandler<AuthInputs> = async data => {
try { try {
await authStore.login(data.login, data.password); await authStore.login(data.login, data.password);
history.push('/'); history.push('/');
toast({
title: `👋 Welcome back!`,
description: 'You are successfully logged in.',
isClosable: true,
status: 'success',
});
} catch (error) { } catch (error) {
console.log('error while log in '); if (error.response) {
switch (error.response.status) {
case 404:
toast({ title: 'User not found in a system.', status: 'error' });
break;
case 401:
toast({ title: 'Invalid password.', status: 'error' });
break;
default:
handleError(error);
break;
}
} else {
handleError(error);
}
} finally { } finally {
reset(); reset();
} }
@ -41,22 +56,14 @@ const AuthForm: React.FC = () => {
<FormLabel>Login</FormLabel> <FormLabel>Login</FormLabel>
<InputGroup> <InputGroup>
<InputLeftElement children={<Icon as={MdAccountCircle} />} /> <InputLeftElement children={<Icon as={MdAccountCircle} />} />
<Input <Input type='text' placeholder='Type your login' {...register('login', { required: true })} />
type='text'
placeholder='email@mail.com'
{...register('login', { required: true })}
/>
</InputGroup> </InputGroup>
</FormControl> </FormControl>
<FormControl mt={5} isRequired={true}> <FormControl mt={5} isRequired={true}>
<FormLabel>Password</FormLabel> <FormLabel>Password</FormLabel>
<InputGroup> <InputGroup>
<InputLeftElement children={<Icon as={MdVpnKey} />} /> <InputLeftElement children={<Icon as={MdVpnKey} />} />
<Input <Input type='password' placeholder='********' {...register('password', { required: true })} />
type='password'
placeholder='********'
{...register('password', { required: true })}
/>
</InputGroup> </InputGroup>
</FormControl> </FormControl>
<Button <Button
@ -66,7 +73,8 @@ const AuthForm: React.FC = () => {
colorScheme='blue' colorScheme='blue'
variant='outline' variant='outline'
width='full' width='full'
mt={4}> mt={4}
>
Log in Log in
</Button> </Button>
</form> </form>

View File

@ -1,26 +1,21 @@
import * as React from "react"; import * as React from 'react';
import { import { useColorMode, useColorModeValue, IconButton, IconButtonProps } from '@chakra-ui/react';
useColorMode, import { FaMoon, FaSun } from 'react-icons/fa';
useColorModeValue,
IconButton,
IconButtonProps,
} from "@chakra-ui/react";
import { FaMoon, FaSun } from "react-icons/fa";
type ColorModeSwitcherProps = Omit<IconButtonProps, "aria-label">; type ColorModeSwitcherProps = Omit<IconButtonProps, 'aria-label'>;
export const ColorModeSwitcher: React.FC<ColorModeSwitcherProps> = (props) => { export const ColorModeSwitcher: React.FC<ColorModeSwitcherProps> = props => {
const { toggleColorMode } = useColorMode(); const { toggleColorMode } = useColorMode();
const text = useColorModeValue("dark", "light"); const text = useColorModeValue('dark', 'light');
const SwitchIcon = useColorModeValue(FaMoon, FaSun); const SwitchIcon = useColorModeValue(FaMoon, FaSun);
return ( return (
<IconButton <IconButton
size="md" size='md'
fontSize="lg" fontSize='lg'
variant="ghost" variant='ghost'
color="current" color='current'
marginLeft="2" marginLeft='2'
onClick={toggleColorMode} onClick={toggleColorMode}
icon={<SwitchIcon />} icon={<SwitchIcon />}
aria-label={`Switch to ${text} mode`} aria-label={`Switch to ${text} mode`}

View File

@ -0,0 +1,26 @@
import React from 'react';
import { useToast, Flex, Box, Heading, Text } from '@chakra-ui/react';
import { MdError } from 'react-icons/md';
const ErrorFallback: React.FC<{ error: any }> = props => {
const { error } = props;
const toast = useToast();
React.useEffect(() => {
toast({ title: `${error.message}`, duration: 10000, isClosable: true, status: 'error' });
});
return (
<Box textAlign='center' mt={40}>
<Flex justifyContent='center'>
<MdError size='64px' />
</Flex>
<Heading display='block' as='h1' fontSize='6xl'>
Something went wrong
</Heading>
<Text mt='5'>Sorry, something went wrong. Reload page and try your request again later.</Text>
</Box>
);
};
export default ErrorFallback;

View File

@ -16,14 +16,16 @@ const Header: React.FC = () => {
borderBottomWidth={1} borderBottomWidth={1}
width='100%' width='100%'
height='4rem' height='4rem'
zIndex={4}> zIndex={4}
>
<Flex <Flex
justifyContent='space-between' justifyContent='space-between'
width='100%' width='100%'
height='100%' height='100%'
alignItems='center' alignItems='center'
paddingLeft='1.5rem' paddingLeft='1.5rem'
paddingRight='1.5rem'> paddingRight='1.5rem'
>
<AppName /> <AppName />
<Flex> <Flex>
<UserNotifications /> <UserNotifications />

View File

@ -12,6 +12,7 @@ import {
Avatar, Avatar,
Text, Text,
Link, Link,
useToast,
} 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';
@ -21,13 +22,13 @@ import { useHistory } from 'react-router-dom';
const UserInfo: React.FC = () => { const UserInfo: React.FC = () => {
const { authStore } = useRootStore(); const { authStore } = useRootStore();
const history = useHistory(); const history = useHistory();
const toast = useToast();
const onLogoutHandler = async ( const onLogoutHandler = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
try { try {
await authStore.logout(); await authStore.logout();
history.push('/auth'); history.push('/auth');
toast({ title: 'You are logged out.', isClosable: true, status: 'info' });
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }

View File

@ -22,7 +22,8 @@ const UserNotifications: React.FC = () => {
width: '15px', width: '15px',
height: '15px', height: '15px',
borderRadius: '100%', borderRadius: '100%',
}}> }}
>
<Box as={MdNotifications} boxSize='21px' /> <Box as={MdNotifications} boxSize='21px' />
</Button> </Button>
); );

View File

@ -1,6 +1,7 @@
import React from 'react'; 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';
/** /**
* *
@ -11,17 +12,10 @@ const Layout: React.FC = ({ children }) => {
return ( return (
<> <>
<Header /> <Header />
<Nav />
<Flex minH='100vh' flexDirection='column'> <Flex minH='100vh' flexDirection='column'>
<Box pl='18rem' mt='4rem' mb='4rem' flex='1 0 auto'> <Box pl='18rem' mt='4rem' mb='4rem' flex='1 0 auto'>
<Box <Box as='main' mr='auto' ml='auto' maxW='85rem' pl={5} pr={5} pt='2rem' pb='2rem'>
as='main'
mr='auto'
ml='auto'
maxW='85rem'
pl={5}
pr={5}
pt='2rem'
pb='2rem'>
{children} {children}
</Box> </Box>
</Box> </Box>

View File

@ -0,0 +1,52 @@
import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';
import NavLink from './NavLink';
import config from '../../config';
import { Flex, Box } from '@chakra-ui/react';
/**
*
* Nav component
* Component for navigating the application.
*/
export type ActiveLinkState = {
title: string | null;
};
const Nav: React.FC = () => {
const [activeLink, setActiveLink] = useState<ActiveLinkState>({ title: null });
const history = useHistory();
const handleRoute = (location: string): void => history.push(location);
const handleOnHoverLink = (title: string): void => setActiveLink({ title });
const handleOnLeaveLink = (): void => setActiveLink({ title: null });
return (
<Box display='block' position='fixed' left={0} right={0} top={0} width='100%' height='100%' maxWidth='18rem'>
<Box top='4rem' position='relative' overflowY='auto' borderRightWidth={1}>
<Flex
as='nav'
flexDirection='column'
alignItems='flex-start'
textDecoration='none'
height='calc(100vh - 4rem)'
padding='1.5rem 1.2rem'
>
{config.links.map((link, i) => (
<NavLink
handleOnHoverLink={handleOnHoverLink}
handleOnLeaveLink={handleOnLeaveLink}
handleRoute={handleRoute}
activeLink={activeLink}
history={history}
link={link}
key={i}
/>
))}
</Flex>
</Box>
</Box>
);
};
export default Nav;

View File

@ -0,0 +1,65 @@
import React from 'react';
import { Box } from '@chakra-ui/react';
import type { History } from 'history';
import type { LinkType } from '../../interfaces';
import type { ActiveLinkState } from './Nav';
/**
*
* NavLink component
* Returns the link component for navigation.
*
*/
interface NavLinkProps {
handleOnHoverLink: (title: string) => void;
handleOnLeaveLink: () => void;
handleRoute: (location: string) => void;
history: History;
activeLink: ActiveLinkState;
link: LinkType;
}
const NavLink: React.FC<NavLinkProps> = ({
handleOnHoverLink,
handleOnLeaveLink,
handleRoute,
history,
activeLink,
link,
}) => {
const onMouseEnter = () => handleOnHoverLink(link.title);
const onClick = () => handleRoute(link.location);
return (
<Box
as='a'
onMouseEnter={onMouseEnter}
onMouseLeave={handleOnLeaveLink}
onClick={onClick}
display='flex'
margin='3px 0'
width='100%'
border='none'
aria-label={link.title}
role='link'
cursor='pointer'
>
<Box
color={history.location.pathname === link.location || activeLink.title === link.title ? 'blue.500' : ''}
backgroundColor={activeLink.title === link.title ? '#EBF8FF' : ''}
alignItems='center'
borderRadius='9999px'
display='flex'
padding='10px 15px'
fontWeight='500'
transition='all .1s'
>
<Box as={link.icon} boxSize='26px' mr={3} />
{link.title}
</Box>
</Box>
);
};
export default NavLink;

View File

@ -10,19 +10,14 @@ import { observer } from 'mobx-react';
* Checks if the user is loggedIn by reactive variable and if not, * Checks if the user is loggedIn by reactive variable and if not,
* then a redirect to "/auth" route. * then a redirect to "/auth" route.
*/ */
const PrivateRoute: React.FC<IPrivateRouteProps> = ({ const PrivateRoute: React.FC<IPrivateRouteProps> = ({ children, component: Component, title, ...options }) => {
children,
component: Component,
title,
...options
}) => {
const { authStore } = useRootStore(); const { authStore } = useRootStore();
const isLoggedIn = authStore.isLoggedIn; const isLoggedIn = authStore.isLoggedIn;
return ( return (
<Route <Route
{...options} {...options}
render={(props) => render={props =>
isLoggedIn ? ( isLoggedIn ? (
<Layout> <Layout>
<Helmet> <Helmet>
@ -31,9 +26,7 @@ const PrivateRoute: React.FC<IPrivateRouteProps> = ({
<Component {...props} title={title} /> <Component {...props} title={title} />
</Layout> </Layout>
) : ( ) : (
<Redirect <Redirect to={{ pathname: '/auth', state: { from: props.location } }} />
to={{ pathname: '/auth', state: { from: props.location } }}
/>
) )
} }
/> />

13
src/config.ts Normal file
View File

@ -0,0 +1,13 @@
import { MdAccountCircle, MdCollectionsBookmark, MdHome, MdMessage } from 'react-icons/md';
import { IAppConfig } from './interfaces';
const config: IAppConfig = {
links: [
{ location: '/', title: 'Home', icon: MdHome },
{ location: '/courses', title: 'Courses', icon: MdCollectionsBookmark },
{ location: '/messages', title: 'Messages', icon: MdMessage },
{ location: '/profile', title: 'Profile', icon: MdAccountCircle },
],
};
export default config;

View File

@ -1,6 +1,4 @@
import React from 'react'; import React from 'react';
import RootStore from '../stores/RootStore'; import RootStore from '../stores/RootStore';
export const StoreContext = React.createContext<RootStore | undefined>( export const StoreContext = React.createContext<RootStore | undefined>(undefined);
undefined
);

View File

@ -1,6 +1,17 @@
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';
export type LinkType = {
location: string;
title: string;
icon: IconType;
};
export interface IAppConfig {
links: LinkType[];
}
export interface IDataResult { export interface IDataResult {
data: any; data: any;
message: string; message: string;

View File

@ -27,17 +27,8 @@ const AuthPage: React.FC<IPageProps> = () => {
<Helmet> <Helmet>
<title>Auth</title> <title>Auth</title>
</Helmet> </Helmet>
<Flex <Flex minHeight='100vh' align='center' justifyContent='center' padding='5'>
minHeight='100vh' <Box p={8} width='full' maxWidth='450px' borderWidth={1} borderRadius={8}>
align='center'
justifyContent='center'
padding='5'>
<Box
p={8}
width='full'
maxWidth='450px'
borderWidth={1}
borderRadius={8}>
<Box textAlign='center'> <Box textAlign='center'>
<Flex justifyContent='center'> <Flex justifyContent='center'>
<MdSchool fill='blue' size='64px' /> <MdSchool fill='blue' size='64px' />

40
src/pages/courses.tsx Normal file
View File

@ -0,0 +1,40 @@
import React, { useEffect } 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';
/**
* Courses page
*/
const CoursesPage: 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>
</BreadcrumbItem>
</Breadcrumb>
<Box mt='10'>
<Box>
<Heading as='h1'>Courses page</Heading>
<Grid templateColumns='3fr 1fr' mt='25px'>
...
</Grid>
</Box>
</Box>
</>
);
};
export default CoursesPage;

View File

@ -1,12 +1,5 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { import { Box, Heading, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Grid } from '@chakra-ui/react';
Box,
Heading,
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
Grid,
} from '@chakra-ui/react';
import { IPageProps } from '../interfaces'; import { IPageProps } from '../interfaces';
import { useRootStore } from '../hooks/useRootStore'; import { useRootStore } from '../hooks/useRootStore';
@ -32,7 +25,7 @@ const MainPage: React.FC<IPageProps> = ({ title }) => {
<BreadcrumbLink href='/'>{title}</BreadcrumbLink> <BreadcrumbLink href='/'>{title}</BreadcrumbLink>
</BreadcrumbItem> </BreadcrumbItem>
</Breadcrumb> </Breadcrumb>
<Box marginTop={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'>

40
src/pages/messages.tsx Normal file
View File

@ -0,0 +1,40 @@
import React, { useEffect } 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';
/**
* Messages page
*/
const MessagesPage: 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>
</BreadcrumbItem>
</Breadcrumb>
<Box mt='10'>
<Box>
<Heading as='h1'>Messages page</Heading>
<Grid templateColumns='3fr 1fr' mt='25px'>
...
</Grid>
</Box>
</Box>
</>
);
};
export default MessagesPage;

40
src/pages/profile.tsx Normal file
View File

@ -0,0 +1,40 @@
import React, { useEffect } 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';
/**
* Profile page
*/
const ProfilePage: 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>
</BreadcrumbItem>
</Breadcrumb>
<Box mt='10'>
<Box>
<Heading as='h1'>Profile page</Heading>
<Grid templateColumns='3fr 1fr' mt='25px'>
...
</Grid>
</Box>
</Box>
</>
);
};
export default ProfilePage;

View File

@ -8,7 +8,5 @@ import RootStore from '../stores/RootStore';
export const RootStoreProvider = ({ children }: { children: ReactNode }) => { export const RootStoreProvider = ({ children }: { children: ReactNode }) => {
const store = new RootStore(); const store = new RootStore();
return ( return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
<StoreContext.Provider value={store}>{children}</StoreContext.Provider>
);
}; };

View File

@ -1,8 +1,8 @@
import { ReportHandler } from "web-vitals"; import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => { const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) { if (onPerfEntry && onPerfEntry instanceof Function) {
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry); getCLS(onPerfEntry);
getFID(onPerfEntry); getFID(onPerfEntry);
getFCP(onPerfEntry); getFCP(onPerfEntry);

View File

@ -19,29 +19,17 @@ export default class ApiService {
/** /**
* Response * Response
*/ */
this.api.interceptors.response.use(undefined, (error) => { this.api.interceptors.response.use(undefined, error => {
if (error.response) { if (error.response) {
/**
* Internal Server Error 500
*/
if (error.response.status === 500) {
}
/** /**
* Unauthorized Error 401 * Unauthorized Error 401
*/ */
if (error.response.status === 401) { if (error.response.status === 401) {
if (this.root.authStore.isLoggedIn) { if (this.root.authStore.isLoggedIn) {
this.root.userStore.user = null;
this.root.authStore.setIsLoggedIn(false); this.root.authStore.setIsLoggedIn(false);
} }
} }
/* Clear error state from root */
if (this.root.networkApiError !== undefined) {
this.root.setNetworkApiError(undefined);
}
} else {
this.root.setNetworkApiError(error);
} }
return Promise.reject(error); return Promise.reject(error);
}); });

View File

@ -23,10 +23,7 @@ export default class AuthService {
* @param password User password * @param password User password
* @returns Login result * @returns Login result
*/ */
public async requestLogin( public async requestLogin(login: string, password: string): Promise<ILoginResult> {
login: string,
password: string
): Promise<ILoginResult> {
const result = await this.api.post('v1/auth/login', { const result = await this.api.post('v1/auth/login', {
login, login,
password, password,

View File

@ -2,4 +2,4 @@
// allows you to do things like: // allows you to do things like:
// expect(element).toHaveTextContent(/react/i) // expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom // learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom"; import '@testing-library/jest-dom';

View File

@ -7,8 +7,6 @@ import UserStore from './UserStore';
import { makeAutoObservable } from 'mobx'; import { makeAutoObservable } from 'mobx';
export default class RootStore { export default class RootStore {
public networkApiError: any;
public authStore: AuthStore; public authStore: AuthStore;
public userStore: UserStore; public userStore: UserStore;
@ -35,8 +33,4 @@ export default class RootStore {
this.authService = new AuthService(this.apiService.api); this.authService = new AuthService(this.apiService.api);
makeAutoObservable(this); makeAutoObservable(this);
} }
public setNetworkApiError(error: any) {
this.networkApiError = error;
}
} }

View File

@ -1,6 +1,6 @@
import * as React from "react"; import * as React from 'react';
import { render, RenderOptions } from "@testing-library/react"; import { render, RenderOptions } from '@testing-library/react';
import { ChakraProvider, theme } from "@chakra-ui/react"; import { ChakraProvider, theme } from '@chakra-ui/react';
const AllProviders = ({ children }: { children?: React.ReactNode }) => ( const AllProviders = ({ children }: { children?: React.ReactNode }) => (
<ChakraProvider theme={theme}>{children}</ChakraProvider> <ChakraProvider theme={theme}>{children}</ChakraProvider>