mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 18:29:15 +03:00
feat: setup auth
This commit is contained in:
parent
05b79a0875
commit
8ef0d4f8f2
32
src/App.tsx
32
src/App.tsx
@ -1,21 +1,43 @@
|
|||||||
import * as React from 'react';
|
import React from '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 { ChakraProvider, theme } from '@chakra-ui/react';
|
import { useRootStore } from './hooks/useRootStore';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pages
|
* Pages
|
||||||
*/
|
*/
|
||||||
import MainPage from './pages/main';
|
import MainPage from './pages/main';
|
||||||
import AuthPage from './pages/auth';
|
import AuthPage from './pages/auth';
|
||||||
|
import NotFoundPage from './pages/404';
|
||||||
|
import { useToast } from '@chakra-ui/react';
|
||||||
|
import { autorun } from 'mobx';
|
||||||
|
|
||||||
export const App = () => (
|
const App = () => {
|
||||||
<ChakraProvider theme={theme}>
|
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 (
|
||||||
<Router>
|
<Router>
|
||||||
<Switch>
|
<Switch>
|
||||||
<PrivateRoute exact path='/' component={MainPage} title='Main' />
|
<PrivateRoute exact path='/' component={MainPage} title='Main' />
|
||||||
<Route path='/auth' component={AuthPage} />
|
<Route path='/auth' component={AuthPage} />
|
||||||
|
<Route component={NotFoundPage} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
</ChakraProvider>
|
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default observer(App);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { MdAccountCircle, MdVpnKey } from 'react-icons/md';
|
import { MdAccountCircle, MdVpnKey } from 'react-icons/md';
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -12,44 +12,39 @@ import {
|
|||||||
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';
|
||||||
|
|
||||||
type AuthFormState = {
|
type AuthInputs = {
|
||||||
login: string;
|
login: string;
|
||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AuthForm: React.FC = () => {
|
const AuthForm: React.FC = () => {
|
||||||
const [authFormState, setAuthFormState] = useState<AuthFormState>({
|
const { register, reset, handleSubmit } = useForm<AuthInputs>();
|
||||||
login: '',
|
|
||||||
password: '',
|
|
||||||
});
|
|
||||||
const { authStore } = useRootStore();
|
const { authStore } = useRootStore();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const handleLoginForm = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const { login, password } = authFormState;
|
const onSubmit: SubmitHandler<AuthInputs> = async (data) => {
|
||||||
await authStore.login(login, password);
|
try {
|
||||||
|
await authStore.login(data.login, data.password);
|
||||||
history.push('/');
|
history.push('/');
|
||||||
|
} catch (error) {
|
||||||
|
console.log('error while log in ');
|
||||||
|
} finally {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleLoginForm}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<FormControl isRequired={true}>
|
<FormControl isRequired={true}>
|
||||||
<FormLabel>Login</FormLabel>
|
<FormLabel>Login</FormLabel>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputLeftElement children={<Icon as={MdAccountCircle} />} />
|
<InputLeftElement children={<Icon as={MdAccountCircle} />} />
|
||||||
<Input
|
<Input
|
||||||
type='text'
|
type='text'
|
||||||
name='login'
|
|
||||||
placeholder='email@mail.com'
|
placeholder='email@mail.com'
|
||||||
value={authFormState.login}
|
{...register('login', { required: true })}
|
||||||
onChange={(e) =>
|
|
||||||
setAuthFormState({
|
|
||||||
...authFormState,
|
|
||||||
login: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -59,20 +54,14 @@ const AuthForm: React.FC = () => {
|
|||||||
<InputLeftElement children={<Icon as={MdVpnKey} />} />
|
<InputLeftElement children={<Icon as={MdVpnKey} />} />
|
||||||
<Input
|
<Input
|
||||||
type='password'
|
type='password'
|
||||||
name='password'
|
|
||||||
placeholder='********'
|
placeholder='********'
|
||||||
value={authFormState.password}
|
{...register('password', { required: true })}
|
||||||
onChange={(e) =>
|
|
||||||
setAuthFormState({
|
|
||||||
...authFormState,
|
|
||||||
password: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
loadingText='Logging in...'
|
loadingText='Logging in...'
|
||||||
|
isLoading={authStore.loading}
|
||||||
type='submit'
|
type='submit'
|
||||||
colorScheme='blue'
|
colorScheme='blue'
|
||||||
variant='outline'
|
variant='outline'
|
||||||
@ -81,7 +70,7 @@ const AuthForm: React.FC = () => {
|
|||||||
Log in
|
Log in
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default observer(AuthForm)
|
export default observer(AuthForm);
|
||||||
|
|||||||
21
src/components/Header/AppName.tsx
Normal file
21
src/components/Header/AppName.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Flex, Box, Text, Heading } from '@chakra-ui/react';
|
||||||
|
import { MdSchool } from 'react-icons/md';
|
||||||
|
|
||||||
|
const AppName: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Flex alignItems='center'>
|
||||||
|
<Box mr='4'>
|
||||||
|
<MdSchool fill='blue' size='36px' />
|
||||||
|
</Box>
|
||||||
|
<Box lineHeight='1'>
|
||||||
|
<Heading as='p' fontSize='2xl' color='gray.700'>
|
||||||
|
App name
|
||||||
|
</Heading>
|
||||||
|
<Text as='small'>App description</Text>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppName;
|
||||||
37
src/components/Header/Header.tsx
Normal file
37
src/components/Header/Header.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Flex, Box } from '@chakra-ui/react';
|
||||||
|
import AppName from './AppName';
|
||||||
|
import UserInfo from './User/UserInfo';
|
||||||
|
import UserNotifications from './User/UserNotifications';
|
||||||
|
|
||||||
|
const Header: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as='header'
|
||||||
|
background='#fff'
|
||||||
|
position='fixed'
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
right={0}
|
||||||
|
borderBottomWidth={1}
|
||||||
|
width='100%'
|
||||||
|
height='4rem'
|
||||||
|
zIndex={4}>
|
||||||
|
<Flex
|
||||||
|
justifyContent='space-between'
|
||||||
|
width='100%'
|
||||||
|
height='100%'
|
||||||
|
alignItems='center'
|
||||||
|
paddingLeft='1.5rem'
|
||||||
|
paddingRight='1.5rem'>
|
||||||
|
<AppName />
|
||||||
|
<Flex>
|
||||||
|
<UserNotifications />
|
||||||
|
<UserInfo />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
||||||
67
src/components/Header/User/UserInfo.tsx
Normal file
67
src/components/Header/User/UserInfo.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Menu,
|
||||||
|
MenuGroup,
|
||||||
|
MenuList,
|
||||||
|
MenuDivider,
|
||||||
|
MenuButton,
|
||||||
|
MenuItem,
|
||||||
|
Button,
|
||||||
|
Flex,
|
||||||
|
Box,
|
||||||
|
Avatar,
|
||||||
|
Text,
|
||||||
|
Link,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { MdSettings, MdExitToApp } from 'react-icons/md';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import { useRootStore } from '../../../hooks/useRootStore';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
const UserInfo: React.FC = () => {
|
||||||
|
const { authStore } = useRootStore();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const onLogoutHandler = async (
|
||||||
|
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
await authStore.logout();
|
||||||
|
history.push('/auth');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex alignItems='center'>
|
||||||
|
<Menu>
|
||||||
|
<MenuButton as={Button} pr={6}>
|
||||||
|
<Flex alignItems='center'>
|
||||||
|
<Avatar size='sm' name='user name' marginRight={3} />
|
||||||
|
<Text as='span' mr={2}>
|
||||||
|
Username
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList mr='1rem'>
|
||||||
|
<MenuGroup title='User fullname'>
|
||||||
|
<MenuDivider />
|
||||||
|
<Link to='/profile'>
|
||||||
|
<MenuItem>
|
||||||
|
<Box as={MdSettings} mr={2} />
|
||||||
|
My profile
|
||||||
|
</MenuItem>
|
||||||
|
</Link>
|
||||||
|
<MenuItem onClick={onLogoutHandler}>
|
||||||
|
<Box as={MdExitToApp} mr={2} />
|
||||||
|
Logout
|
||||||
|
</MenuItem>
|
||||||
|
</MenuGroup>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default observer(UserInfo);
|
||||||
31
src/components/Header/User/UserNotifications.tsx
Normal file
31
src/components/Header/User/UserNotifications.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box } from '@chakra-ui/layout';
|
||||||
|
import { Button } from '@chakra-ui/button';
|
||||||
|
import { MdNotifications } from 'react-icons/md';
|
||||||
|
|
||||||
|
const UserNotifications: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
marginRight={3}
|
||||||
|
variant='solid'
|
||||||
|
outline='none'
|
||||||
|
border='none'
|
||||||
|
_after={{
|
||||||
|
content: '"2"',
|
||||||
|
display: 'block',
|
||||||
|
position: 'absolute',
|
||||||
|
top: '8px',
|
||||||
|
right: '10px',
|
||||||
|
background: '#E53E3E',
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: '11px',
|
||||||
|
width: '15px',
|
||||||
|
height: '15px',
|
||||||
|
borderRadius: '100%',
|
||||||
|
}}>
|
||||||
|
<Box as={MdNotifications} boxSize='21px' />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserNotifications;
|
||||||
@ -1,5 +1,6 @@
|
|||||||
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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -8,6 +9,8 @@ import { Flex, Box } from '@chakra-ui/react';
|
|||||||
*/
|
*/
|
||||||
const Layout: React.FC = ({ children }) => {
|
const Layout: React.FC = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<Header />
|
||||||
<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
|
||||||
@ -23,6 +26,7 @@ const Layout: React.FC = ({ children }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { ColorModeScript } from '@chakra-ui/react';
|
import { ChakraProvider, ColorModeScript, theme } from '@chakra-ui/react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { App } from './App';
|
import App from './App';
|
||||||
import { RootStoreProvider } from './providers';
|
import { RootStoreProvider } from './providers';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
@ -9,7 +9,9 @@ ReactDOM.render(
|
|||||||
<>
|
<>
|
||||||
<ColorModeScript />
|
<ColorModeScript />
|
||||||
<RootStoreProvider>
|
<RootStoreProvider>
|
||||||
|
<ChakraProvider theme={theme}>
|
||||||
<App />
|
<App />
|
||||||
|
</ChakraProvider>
|
||||||
</RootStoreProvider>
|
</RootStoreProvider>
|
||||||
</>,
|
</>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
|
|||||||
26
src/pages/404.tsx
Normal file
26
src/pages/404.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Helmet from 'react-helmet';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { Box, Heading, Button } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const NotFoundPage: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>Error 404</title>
|
||||||
|
</Helmet>
|
||||||
|
<Box textAlign='center' mt={40}>
|
||||||
|
<Heading display='block' as='h1' fontSize='6xl'>
|
||||||
|
Error 404
|
||||||
|
</Heading>
|
||||||
|
<Link to='/'>
|
||||||
|
<Button variant='link' colorScheme='blue'>
|
||||||
|
Return to main page
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotFoundPage;
|
||||||
@ -1,11 +1,8 @@
|
|||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
Flex,
|
|
||||||
Box,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { MdSchool } from "react-icons/md";
|
|
||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { Flex, Box } from '@chakra-ui/react';
|
||||||
|
import { MdSchool } from 'react-icons/md';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { useRootStore } from '../hooks/useRootStore';
|
import { useRootStore } from '../hooks/useRootStore';
|
||||||
import { IPageProps } from '../interfaces';
|
import { IPageProps } from '../interfaces';
|
||||||
@ -19,19 +16,24 @@ const AuthPage: React.FC<IPageProps> = () => {
|
|||||||
const { authStore } = useRootStore();
|
const { authStore } = useRootStore();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
if (authStore.isLoggedIn) {
|
if (authStore.isLoggedIn) {
|
||||||
history.push('/');
|
history.push('/');
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>Auth</title>
|
<title>Auth</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<Flex minHeight='100vh' align='center' justifyContent='center'>
|
<Flex
|
||||||
|
minHeight='100vh'
|
||||||
|
align='center'
|
||||||
|
justifyContent='center'
|
||||||
|
padding='5'>
|
||||||
<Box
|
<Box
|
||||||
p={8}
|
p={8}
|
||||||
my={40}
|
|
||||||
width='full'
|
width='full'
|
||||||
maxWidth='450px'
|
maxWidth='450px'
|
||||||
borderWidth={1}
|
borderWidth={1}
|
||||||
@ -41,7 +43,7 @@ const AuthPage: React.FC<IPageProps> = () => {
|
|||||||
<MdSchool fill='blue' size='64px' />
|
<MdSchool fill='blue' size='64px' />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<Box my='30px'>
|
<Box>
|
||||||
<AuthForm />
|
<AuthForm />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Heading,
|
Heading,
|
||||||
@ -9,11 +9,22 @@ import {
|
|||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
import { IPageProps } from '../interfaces';
|
import { IPageProps } from '../interfaces';
|
||||||
|
import { useRootStore } from '../hooks/useRootStore';
|
||||||
|
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'>
|
||||||
|
|||||||
@ -2,12 +2,13 @@ import React, { ReactNode } from 'react';
|
|||||||
import { StoreContext } from '../contexts';
|
import { StoreContext } from '../contexts';
|
||||||
import RootStore from '../stores/RootStore';
|
import RootStore from '../stores/RootStore';
|
||||||
|
|
||||||
let store: RootStore;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Root store provider
|
* Root store provider
|
||||||
*/
|
*/
|
||||||
export const RootStoreProvider = ({ children }: { children: ReactNode }) => {
|
export const RootStoreProvider = ({ children }: { children: ReactNode }) => {
|
||||||
const root = store ?? new RootStore();
|
const store = new RootStore();
|
||||||
return <StoreContext.Provider value={root}>{children}</StoreContext.Provider>;
|
|
||||||
|
return (
|
||||||
|
<StoreContext.Provider value={store}>{children}</StoreContext.Provider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -20,15 +20,29 @@ export default class ApiService {
|
|||||||
* Response
|
* Response
|
||||||
*/
|
*/
|
||||||
this.api.interceptors.response.use(undefined, (error) => {
|
this.api.interceptors.response.use(undefined, (error) => {
|
||||||
|
if (error.response) {
|
||||||
/**
|
/**
|
||||||
* Unauthorized
|
* Internal Server Error 500
|
||||||
*/
|
*/
|
||||||
if (
|
if (error.response.status === 500) {
|
||||||
error.response.status === 401 ||
|
}
|
||||||
error.response.data.message === '401 Unauthorized'
|
|
||||||
) {
|
/**
|
||||||
|
* Unauthorized Error 401
|
||||||
|
*/
|
||||||
|
if (error.response.status === 401) {
|
||||||
|
if (this.root.authStore.isLoggedIn) {
|
||||||
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import ApiService from './ApiService';
|
|
||||||
import { IDataResult } from '../interfaces';
|
import { IDataResult } from '../interfaces';
|
||||||
|
import { AxiosInstance } from 'axios';
|
||||||
|
|
||||||
interface ILoginResult extends IDataResult {
|
export interface ILoginResult extends IDataResult {
|
||||||
data: {
|
data: {
|
||||||
token: string;
|
token: string;
|
||||||
type: string;
|
type: string;
|
||||||
@ -10,21 +10,39 @@ interface ILoginResult extends IDataResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class AuthService {
|
export default class AuthService {
|
||||||
public apiService: ApiService;
|
public api: AxiosInstance;
|
||||||
|
|
||||||
constructor(apiService: ApiService) {
|
constructor(api: AxiosInstance) {
|
||||||
this.apiService = apiService;
|
this.api = api;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new token
|
||||||
|
*
|
||||||
|
* @param login User login
|
||||||
|
* @param password User password
|
||||||
|
* @returns Login result
|
||||||
|
*/
|
||||||
public async requestLogin(
|
public async requestLogin(
|
||||||
login: string,
|
login: string,
|
||||||
password: string
|
password: string
|
||||||
): Promise<ILoginResult> {
|
): Promise<ILoginResult> {
|
||||||
const result = await this.apiService.api.post('v1/auth/login', {
|
const result = await this.api.post('v1/auth/login', {
|
||||||
login,
|
login,
|
||||||
password,
|
password,
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revoke token
|
||||||
|
*
|
||||||
|
* @returns Logout result
|
||||||
|
*/
|
||||||
|
public async requestLogout(): Promise<any> {
|
||||||
|
const result = await this.api.post('v1/auth/logout');
|
||||||
|
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/services/UserService.ts
Normal file
15
src/services/UserService.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { AxiosInstance } from 'axios';
|
||||||
|
|
||||||
|
export default class UserService {
|
||||||
|
public api: AxiosInstance;
|
||||||
|
|
||||||
|
constructor(api: AxiosInstance) {
|
||||||
|
this.api = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fetchMe(): Promise<any> {
|
||||||
|
const result = await this.api.get('v1/me');
|
||||||
|
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,8 @@ export default class AuthStore {
|
|||||||
|
|
||||||
public isLoggedIn: boolean = !!Cookies.get('logged_in');
|
public isLoggedIn: boolean = !!Cookies.get('logged_in');
|
||||||
|
|
||||||
|
public loading: boolean = false;
|
||||||
|
|
||||||
constructor(root: RootStore) {
|
constructor(root: RootStore) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
@ -19,17 +21,44 @@ export default class AuthStore {
|
|||||||
this.isLoggedIn = value;
|
this.isLoggedIn = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setLoading(value: boolean) {
|
||||||
|
this.loading = value;
|
||||||
|
}
|
||||||
|
|
||||||
public setToken(token: string) {
|
public setToken(token: string) {
|
||||||
Cookies.set('logged_in', 'true');
|
Cookies.set('logged_in', 'true');
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async login(login: string, password: string) {
|
public async login(login: string, password: string) {
|
||||||
|
try {
|
||||||
|
this.setLoading(true);
|
||||||
const result = await this.root.authService.requestLogin(login, password);
|
const result = await this.root.authService.requestLogin(login, password);
|
||||||
|
|
||||||
this.setIsLoggedIn(true);
|
this.setIsLoggedIn(true);
|
||||||
this.setToken(result.data.token);
|
this.setToken(result.data.token);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
this.setLoading(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public logout() {}
|
public async logout() {
|
||||||
|
try {
|
||||||
|
this.setLoading(true);
|
||||||
|
const result = await this.root.authService.requestLogout();
|
||||||
|
|
||||||
|
this.setIsLoggedIn(false);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
this.setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,10 +3,16 @@ import AuthStore from './AuthStore';
|
|||||||
|
|
||||||
import ApiService from '../services/ApiService';
|
import ApiService from '../services/ApiService';
|
||||||
import AuthService from '../services/AuthService';
|
import AuthService from '../services/AuthService';
|
||||||
|
import UserStore from './UserStore';
|
||||||
|
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 apiService: ApiService;
|
public apiService: ApiService;
|
||||||
|
|
||||||
public authService: AuthService;
|
public authService: AuthService;
|
||||||
@ -16,6 +22,7 @@ export default class RootStore {
|
|||||||
* Stores
|
* Stores
|
||||||
*/
|
*/
|
||||||
this.authStore = new AuthStore(this);
|
this.authStore = new AuthStore(this);
|
||||||
|
this.userStore = new UserStore(this);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Services
|
* Services
|
||||||
@ -23,7 +30,13 @@ export default class RootStore {
|
|||||||
this.apiService = new ApiService(this, {
|
this.apiService = new ApiService(this, {
|
||||||
baseURL: API_BASE_URL,
|
baseURL: API_BASE_URL,
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
this.authService = new AuthService(this.apiService);
|
this.authService = new AuthService(this.apiService.api);
|
||||||
|
makeAutoObservable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setNetworkApiError(error: any) {
|
||||||
|
this.networkApiError = error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/stores/UserStore.ts
Normal file
20
src/stores/UserStore.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { makeAutoObservable } from 'mobx';
|
||||||
|
import RootStore from './RootStore';
|
||||||
|
|
||||||
|
export interface IUser {
|
||||||
|
id: string;
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class UserStore {
|
||||||
|
public root: RootStore;
|
||||||
|
|
||||||
|
public user: IUser | null = null;
|
||||||
|
|
||||||
|
constructor(root: RootStore) {
|
||||||
|
this.root = root;
|
||||||
|
makeAutoObservable(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user