Connect app with redux and saga. Add basic login page
This commit is contained in:
parent
cd19824b62
commit
6e00397e60
@ -10,6 +10,9 @@
|
|||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
"@zxing/library": "^0.18.3",
|
"@zxing/library": "^0.18.3",
|
||||||
"add": "^2.0.6",
|
"add": "^2.0.6",
|
||||||
|
"connected-react-router": "^6.8.0",
|
||||||
|
"history": "4.10.1",
|
||||||
|
"immer": "^8.0.0",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
@ -21,6 +24,7 @@
|
|||||||
"react-webcam": "^5.2.2",
|
"react-webcam": "^5.2.2",
|
||||||
"recharts": "^1.8.5",
|
"recharts": "^1.8.5",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
|
"redux-injectors": "^1.3.0",
|
||||||
"redux-saga": "^1.1.3",
|
"redux-saga": "^1.1.3",
|
||||||
"reselect": "^4.0.0",
|
"reselect": "^4.0.0",
|
||||||
"web-vitals": "^0.2.4",
|
"web-vitals": "^0.2.4",
|
||||||
|
0
src/containers/App/actions.js
Normal file
0
src/containers/App/actions.js
Normal file
4
src/containers/App/constants.js
Normal file
4
src/containers/App/constants.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const LOGOUT_REQUEST = 'app/App/LOGOUT_REQUEST';
|
||||||
|
export const LOGOUT_ERROR = 'app/App/LOGOUT_ERROR';
|
||||||
|
export const LOGOUT_SUCCESS = 'app/App/LOGOUT_SUCCESS';
|
||||||
|
export const LOGIN_SUCCESS = 'app/App/LOGIN_SUCCESS';
|
@ -1,28 +1,30 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Switch } from 'react-router-dom';
|
import {Route, Switch} from 'react-router-dom';
|
||||||
import RouteWithSubRoutes from 'containers/RouteWithSubRoutes'
|
|
||||||
import { ThemeProvider } from '@material-ui/core/styles'
|
import { ThemeProvider } from '@material-ui/core/styles'
|
||||||
|
import {createStructuredSelector} from "reselect";
|
||||||
import CssBaseline from '@material-ui/core/CssBaseline'
|
import CssBaseline from '@material-ui/core/CssBaseline'
|
||||||
import { ROUTES } from 'utils/routes';
|
import { theme, routes } from 'utils'
|
||||||
import { theme } from 'utils/theme'
|
import {makeSelectIsLogged} from "./selectors";
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import Navbar from 'components/Navbar'
|
import Navbar from 'components/Navbar'
|
||||||
|
|
||||||
|
import HomePage from 'pages/Home'
|
||||||
|
import LoginPage from 'pages/Login'
|
||||||
|
|
||||||
|
const stateSelector = createStructuredSelector({
|
||||||
|
isLogged: makeSelectIsLogged(),
|
||||||
|
});
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
const { isLogged } = useSelector(stateSelector)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Navbar />
|
{isLogged && <Navbar /> }
|
||||||
<Switch>
|
<Switch>
|
||||||
{ROUTES.map(({ key, component, path, exact, routes }) => (
|
<Route exact path={routes.dashboard.path} component={HomePage} />
|
||||||
<RouteWithSubRoutes
|
<Route exact path={routes.login.path} component={LoginPage} />
|
||||||
key={key}
|
|
||||||
component={component}
|
|
||||||
path={path}
|
|
||||||
exact={exact}
|
|
||||||
routes={routes}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Switch>
|
</Switch>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
30
src/containers/App/reducer.js
Normal file
30
src/containers/App/reducer.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import produce from 'immer';
|
||||||
|
import {
|
||||||
|
LOGOUT_ERROR,
|
||||||
|
LOGOUT_SUCCESS,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
import { LOGIN_SUCCESS } from 'pages/Login/constants'
|
||||||
|
|
||||||
|
export const initialState = {
|
||||||
|
isLogged: false,
|
||||||
|
notifications: [],
|
||||||
|
tokens: {},
|
||||||
|
user: {},
|
||||||
|
};
|
||||||
|
const appReducer = produce((draft, action) => {
|
||||||
|
switch(action.type) {
|
||||||
|
case LOGIN_SUCCESS:
|
||||||
|
draft.isLogged = true;
|
||||||
|
draft.tokens = action.tokens;
|
||||||
|
draft.user = action.user;
|
||||||
|
break;
|
||||||
|
case LOGOUT_ERROR:
|
||||||
|
case LOGOUT_SUCCESS:
|
||||||
|
return initialState;
|
||||||
|
default:
|
||||||
|
return initialState;
|
||||||
|
}
|
||||||
|
}, initialState);
|
||||||
|
|
||||||
|
export default appReducer;
|
23
src/containers/App/selectors.js
Normal file
23
src/containers/App/selectors.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { initialState } from './reducer';
|
||||||
|
|
||||||
|
const selectGlobalDomain = (state) => state.global || initialState;
|
||||||
|
|
||||||
|
const makeSelectIsLogged = () =>
|
||||||
|
createSelector(selectGlobalDomain, (substate) => substate.isLogged);
|
||||||
|
|
||||||
|
const makeSelectNotifications = () =>
|
||||||
|
createSelector(selectGlobalDomain, (substate) => substate.notifications);
|
||||||
|
|
||||||
|
const makeSelectTokens = () =>
|
||||||
|
createSelector(selectGlobalDomain, (substate) => substate.tokens);
|
||||||
|
|
||||||
|
const makeSelectUser = () =>
|
||||||
|
createSelector(selectGlobalDomain, (substate) => substate.user);
|
||||||
|
|
||||||
|
export {
|
||||||
|
makeSelectIsLogged,
|
||||||
|
makeSelectNotifications,
|
||||||
|
makeSelectTokens,
|
||||||
|
makeSelectUser,
|
||||||
|
};
|
@ -1,20 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Route } from 'react-router-dom'
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
const RouteWithSubRoutes = ({ component: Component, path, exact, routes = [] }) => (
|
|
||||||
<Route
|
|
||||||
path={path}
|
|
||||||
exact={exact}
|
|
||||||
render={props => <Component routes={routes} {...props} />}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
RouteWithSubRoutes.propTypes = {
|
|
||||||
routes: PropTypes.array,
|
|
||||||
component: PropTypes.elementType.isRequired,
|
|
||||||
path: PropTypes.string.isRequired,
|
|
||||||
exact: PropTypes.bool.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RouteWithSubRoutes;
|
|
16
src/index.js
16
src/index.js
@ -1,14 +1,22 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import { Provider } from 'react-redux';
|
||||||
|
import { ConnectedRouter } from 'connected-react-router';
|
||||||
import App from 'containers/App';
|
import App from 'containers/App';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
import configureStore from 'utils/configureStore'
|
||||||
|
import {history} from 'utils';
|
||||||
|
|
||||||
|
const initialState = {};
|
||||||
|
const store = configureStore(initialState, history);
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<BrowserRouter>
|
<Provider store={store}>
|
||||||
<App />
|
<ConnectedRouter history={history}>
|
||||||
</BrowserRouter>
|
<App />
|
||||||
|
</ConnectedRouter>
|
||||||
|
</Provider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
);
|
);
|
||||||
|
22
src/pages/Login/actions.js
Normal file
22
src/pages/Login/actions.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { LOGIN_INPUT_CHANGE, LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_ERROR } from './constants';
|
||||||
|
|
||||||
|
export const loginInputChange = (name, value) => ({
|
||||||
|
type: LOGIN_INPUT_CHANGE,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const loginAction = () => ({
|
||||||
|
type: LOGIN_REQUEST,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const loginSuccessAction = (user, tokens) => ({
|
||||||
|
type: LOGIN_SUCCESS,
|
||||||
|
user,
|
||||||
|
tokens,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const loginErrorAction = (error) => ({
|
||||||
|
type: LOGIN_ERROR,
|
||||||
|
error,
|
||||||
|
})
|
5
src/pages/Login/constants.js
Normal file
5
src/pages/Login/constants.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const LOGIN_REQUEST = 'app/LoginPage/LOGIN_REQUEST';
|
||||||
|
export const LOGIN_SUCCESS = 'app/LoginPage/LOGIN_SUCCESS';
|
||||||
|
export const LOGIN_ERROR = 'app/LoginPage/LOGIN_ERROR';
|
||||||
|
|
||||||
|
export const LOGIN_INPUT_CHANGE = 'app/LoginPage/LOGIN_INPUT_CHANGE';
|
68
src/pages/Login/index.js
Normal file
68
src/pages/Login/index.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useInjectReducer, useInjectSaga } from 'redux-injectors';
|
||||||
|
import { createStructuredSelector } from 'reselect';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { Container, Grid, Box, Paper, Button, Typography, TextField } from '@material-ui/core'
|
||||||
|
import reducer from './reducer';
|
||||||
|
import saga from './saga';
|
||||||
|
import { makeSelectEmail, makeSelectPassword, makeSelectLoading } from './selectors'
|
||||||
|
import { loginInputChange, loginAction } from './actions'
|
||||||
|
|
||||||
|
const stateSelector = createStructuredSelector({
|
||||||
|
email: makeSelectEmail(),
|
||||||
|
password: makeSelectPassword(),
|
||||||
|
loading: makeSelectLoading(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = 'loginPage'
|
||||||
|
const Login = () => {
|
||||||
|
useInjectReducer({ key, reducer });
|
||||||
|
useInjectSaga({ key, saga });
|
||||||
|
const { email, password, loading } = useSelector(stateSelector)
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const onChangeInput = ({target: { name, value }}) => {
|
||||||
|
dispatch(loginInputChange(name, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
dispatch(loginAction())
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Box marginTop={2}>
|
||||||
|
<Paper component="form" noValidate autoComplete="off" onSubmit={handleSubmit}>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
direction="column"
|
||||||
|
alignItems="center"
|
||||||
|
justify="center"
|
||||||
|
>
|
||||||
|
<Typography>Login</Typography>
|
||||||
|
<TextField
|
||||||
|
type="text"
|
||||||
|
label="E-mail"
|
||||||
|
name="email"
|
||||||
|
variant="outlined"
|
||||||
|
value={email}
|
||||||
|
onChange={onChangeInput}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
type="password"
|
||||||
|
label="password"
|
||||||
|
name="password"
|
||||||
|
variant="outlined"
|
||||||
|
value={password}
|
||||||
|
onChange={onChangeInput}
|
||||||
|
/>
|
||||||
|
<Button type="submit" disabled={loading} color="primary">Sign in</Button>
|
||||||
|
</Grid>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Login;
|
31
src/pages/Login/reducer.js
Normal file
31
src/pages/Login/reducer.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import produce from 'immer';
|
||||||
|
import {LOGIN_INPUT_CHANGE, LOGIN_ERROR, LOGIN_REQUEST, LOGIN_SUCCESS} from './constants';
|
||||||
|
|
||||||
|
export const initialState = {
|
||||||
|
loading: false,
|
||||||
|
error: {},
|
||||||
|
email: 'admin@admin.com',
|
||||||
|
password: 'Kox32113@#$',
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginPageReducer = produce((draft, action) => {
|
||||||
|
switch(action.type) {
|
||||||
|
case LOGIN_SUCCESS:
|
||||||
|
draft.loading = false;
|
||||||
|
break;
|
||||||
|
case LOGIN_REQUEST:
|
||||||
|
draft.loading = true;
|
||||||
|
break;
|
||||||
|
case LOGIN_ERROR:
|
||||||
|
draft.loading = false;
|
||||||
|
draft.error = action.error;
|
||||||
|
break;
|
||||||
|
case LOGIN_INPUT_CHANGE:
|
||||||
|
draft[action.name] = action.value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return initialState;
|
||||||
|
}
|
||||||
|
}, initialState);
|
||||||
|
|
||||||
|
export default loginPageReducer;
|
30
src/pages/Login/saga.js
Normal file
30
src/pages/Login/saga.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { takeLatest, call, put, select } from 'redux-saga/effects';
|
||||||
|
import { push } from 'connected-react-router';
|
||||||
|
import { api, request, routes } from 'utils';
|
||||||
|
import { LOGIN_REQUEST } from './constants';
|
||||||
|
import { makeSelectPassword, makeSelectEmail } from './selectors';
|
||||||
|
import { loginSuccessAction, loginErrorAction } from './actions';
|
||||||
|
|
||||||
|
export function* login() {
|
||||||
|
const email = yield select(makeSelectEmail());
|
||||||
|
const password = yield select(makeSelectPassword());
|
||||||
|
|
||||||
|
const requestURL = api.auth.login;
|
||||||
|
const requestParameters = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ email, password }),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { tokens, user } = yield call(request, requestURL, requestParameters);
|
||||||
|
yield put(loginSuccessAction(user, tokens));
|
||||||
|
yield put(push(routes.dashboard.path));
|
||||||
|
} catch (error) {
|
||||||
|
yield put(loginErrorAction(error.message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function* loginPageSaga() {
|
||||||
|
yield takeLatest(LOGIN_REQUEST, login);
|
||||||
|
}
|
19
src/pages/Login/selectors.js
Normal file
19
src/pages/Login/selectors.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { initialState } from './reducer';
|
||||||
|
|
||||||
|
const selectLoginPageDomain = (state) => state.loginPage || initialState;
|
||||||
|
|
||||||
|
const makeSelectEmail = () =>
|
||||||
|
createSelector(selectLoginPageDomain, (substate) => substate.email);
|
||||||
|
|
||||||
|
const makeSelectPassword = () =>
|
||||||
|
createSelector(selectLoginPageDomain, (substate) => substate.password);
|
||||||
|
const makeSelectLoading = () =>
|
||||||
|
createSelector(selectLoginPageDomain, (substate) => substate.loading);
|
||||||
|
|
||||||
|
export {
|
||||||
|
selectLoginPageDomain,
|
||||||
|
makeSelectEmail,
|
||||||
|
makeSelectPassword,
|
||||||
|
makeSelectLoading,
|
||||||
|
};
|
11
src/utils/api.js
Normal file
11
src/utils/api.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
const API_BASE_URL = 'http://localhost:3001/v1'
|
||||||
|
const AUTH = 'auth';
|
||||||
|
|
||||||
|
const urls = {
|
||||||
|
auth: {
|
||||||
|
login: `${API_BASE_URL}/${AUTH}/login`,
|
||||||
|
register: `${API_BASE_URL}/${AUTH}/register`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default urls
|
41
src/utils/configureStore.js
Normal file
41
src/utils/configureStore.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
|
import { routerMiddleware } from 'connected-react-router';
|
||||||
|
import { createInjectorsEnhancer } from 'redux-injectors';
|
||||||
|
import createSagaMiddleware from 'redux-saga';
|
||||||
|
import createReducer from './reducers';
|
||||||
|
|
||||||
|
export default function configureStore(initialState = {}, history) {
|
||||||
|
let composeEnhancers = compose;
|
||||||
|
const reduxSagaMonitorOptions = {};
|
||||||
|
|
||||||
|
if (typeof window === 'object') {
|
||||||
|
/* eslint-disable no-underscore-dangle */
|
||||||
|
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__)
|
||||||
|
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
|
||||||
|
const { run: runSaga } = sagaMiddleware;
|
||||||
|
|
||||||
|
// Create the store with two middlewares
|
||||||
|
// 1. sagaMiddleware: Makes redux-sagas work
|
||||||
|
// 2. routerMiddleware: Syncs the location/URL path to the state
|
||||||
|
const middlewares = [sagaMiddleware, routerMiddleware(history)];
|
||||||
|
|
||||||
|
const enhancers = [
|
||||||
|
applyMiddleware(...middlewares),
|
||||||
|
createInjectorsEnhancer({
|
||||||
|
createReducer,
|
||||||
|
runSaga,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const store = createStore(
|
||||||
|
createReducer(),
|
||||||
|
initialState,
|
||||||
|
composeEnhancers(...enhancers),
|
||||||
|
);
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
3
src/utils/history.js
Normal file
3
src/utils/history.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { createBrowserHistory } from 'history';
|
||||||
|
const history = createBrowserHistory();
|
||||||
|
export default history;
|
5
src/utils/index.js
Normal file
5
src/utils/index.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export { default as api } from './api';
|
||||||
|
export { default as request } from './request';
|
||||||
|
export { default as routes } from './routes';
|
||||||
|
export { default as history } from './history';
|
||||||
|
export { default as theme } from './theme';
|
15
src/utils/reducers.js
Normal file
15
src/utils/reducers.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { combineReducers } from 'redux';
|
||||||
|
import { connectRouter } from 'connected-react-router';
|
||||||
|
|
||||||
|
import {history} from 'utils';
|
||||||
|
import globalReducer from 'containers/App/reducer';
|
||||||
|
|
||||||
|
export default function createReducer(injectedReducers = {}) {
|
||||||
|
const rootReducer = combineReducers({
|
||||||
|
global: globalReducer,
|
||||||
|
router: connectRouter(history),
|
||||||
|
...injectedReducers,
|
||||||
|
});
|
||||||
|
|
||||||
|
return rootReducer;
|
||||||
|
}
|
24
src/utils/request.js
Normal file
24
src/utils/request.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
function parseJSON(response) {
|
||||||
|
if (response.status === 204 || response.status === 205) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkStatus(response) {
|
||||||
|
if (response.status >= 200 && response.status < 300) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = new Error(response.statusText);
|
||||||
|
error.statusCode = response.status;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = async (url, options) => {
|
||||||
|
const fetchResponse = await fetch(url, options);
|
||||||
|
const response = await checkStatus(fetchResponse);
|
||||||
|
return parseJSON(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default request
|
@ -1,10 +1,13 @@
|
|||||||
import HomePage from 'pages/Home'
|
|
||||||
|
|
||||||
export const ROUTES = [
|
const routes = {
|
||||||
{
|
dashboard: {
|
||||||
path: '/',
|
path: '/',
|
||||||
key: 'HOME',
|
|
||||||
exact: true,
|
exact: true,
|
||||||
component: HomePage
|
|
||||||
},
|
},
|
||||||
]
|
login: {
|
||||||
|
path: '/login',
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default routes
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { createMuiTheme } from '@material-ui/core/styles';
|
import { createMuiTheme } from '@material-ui/core/styles';
|
||||||
|
|
||||||
export const theme = createMuiTheme({
|
const theme = createMuiTheme({
|
||||||
palette: {
|
palette: {
|
||||||
type: 'dark',
|
type: 'dark',
|
||||||
primary: {
|
primary: {
|
||||||
@ -11,3 +11,4 @@ export const theme = createMuiTheme({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
export default theme
|
||||||
|
33
yarn.lock
33
yarn.lock
@ -3466,6 +3466,13 @@ connect-history-api-fallback@^1.6.0:
|
|||||||
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
|
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
|
||||||
integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
|
integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
|
||||||
|
|
||||||
|
connected-react-router@^6.8.0:
|
||||||
|
version "6.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/connected-react-router/-/connected-react-router-6.8.0.tgz#ddc687b31d498322445d235d660798489fa56cae"
|
||||||
|
integrity sha512-E64/6krdJM3Ag3MMmh2nKPtMbH15s3JQDuaYJvOVXzu6MbHbDyIvuwLOyhQIuP4Om9zqEfZYiVyflROibSsONg==
|
||||||
|
dependencies:
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
console-browserify@^1.1.0:
|
console-browserify@^1.1.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
|
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
|
||||||
@ -5606,7 +5613,7 @@ hex-color-regex@^1.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
||||||
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
||||||
|
|
||||||
history@^4.9.0:
|
history@4.10.1, history@^4.9.0:
|
||||||
version "4.10.1"
|
version "4.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
|
||||||
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
|
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
|
||||||
@ -5857,6 +5864,11 @@ immer@7.0.9:
|
|||||||
resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.9.tgz#28e7552c21d39dd76feccd2b800b7bc86ee4a62e"
|
resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.9.tgz#28e7552c21d39dd76feccd2b800b7bc86ee4a62e"
|
||||||
integrity sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A==
|
integrity sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A==
|
||||||
|
|
||||||
|
immer@^8.0.0:
|
||||||
|
version "8.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.0.tgz#08763549ba9dd7d5e2eb4bec504a8315bd9440c2"
|
||||||
|
integrity sha512-jm87NNBAIG4fHwouilCHIecFXp5rMGkiFrAuhVO685UnMAlOneEAnOyzPt8OnP47TC11q/E7vpzZe0WvwepFTg==
|
||||||
|
|
||||||
import-cwd@^2.0.0:
|
import-cwd@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
|
resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9"
|
||||||
@ -5994,6 +6006,13 @@ internal-slot@^1.0.2:
|
|||||||
has "^1.0.3"
|
has "^1.0.3"
|
||||||
side-channel "^1.0.2"
|
side-channel "^1.0.2"
|
||||||
|
|
||||||
|
invariant@^2.2.4:
|
||||||
|
version "2.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||||
|
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.0.0"
|
||||||
|
|
||||||
ip-regex@^2.1.0:
|
ip-regex@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
|
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
|
||||||
@ -7301,7 +7320,7 @@ loglevel@^1.6.8:
|
|||||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0"
|
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0"
|
||||||
integrity sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==
|
integrity sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==
|
||||||
|
|
||||||
loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
|
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||||
@ -9719,6 +9738,16 @@ reduce-function-call@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
balanced-match "^1.0.0"
|
balanced-match "^1.0.0"
|
||||||
|
|
||||||
|
redux-injectors@^1.3.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/redux-injectors/-/redux-injectors-1.3.0.tgz#7d283633428ccbe22e165903b60f432e63da6947"
|
||||||
|
integrity sha512-ZoyKf8Y0bRqpmJImaVO3jnDKuMXTzMDp3j+b0bqtSuPAgWcHD/2P9gRr4mI1EjgCiheIyQ/JJI8yLG29ijqRaw==
|
||||||
|
dependencies:
|
||||||
|
hoist-non-react-statics "^3.3.2"
|
||||||
|
invariant "^2.2.4"
|
||||||
|
lodash "^4.17.15"
|
||||||
|
redux "^4.0.5"
|
||||||
|
|
||||||
redux-saga@^1.1.3:
|
redux-saga@^1.1.3:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-1.1.3.tgz#9f3e6aebd3c994bbc0f6901a625f9a42b51d1112"
|
resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-1.1.3.tgz#9f3e6aebd3c994bbc0f6901a625f9a42b51d1112"
|
||||||
|
Loading…
Reference in New Issue
Block a user