mirror of
https://github.com/Telecominfraproject/wlan-cloud-ui.git
synced 2025-11-01 19:27:51 +00:00
refactored
This commit is contained in:
25
.babelrc
25
.babelrc
@@ -1,3 +1,26 @@
|
||||
{
|
||||
"presets": ["@babel/preset-env", "@babel/preset-react"]
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"modules": false
|
||||
}
|
||||
],
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": ["@babel/plugin-proposal-class-properties"],
|
||||
"env": {
|
||||
"production": {
|
||||
"only": ["app"],
|
||||
"plugins": [
|
||||
"lodash",
|
||||
"transform-react-remove-prop-types",
|
||||
"@babel/plugin-transform-react-inline-elements",
|
||||
"@babel/plugin-transform-react-constant-elements"
|
||||
]
|
||||
},
|
||||
"test": {
|
||||
"plugins": ["@babel/plugin-transform-modules-commonjs", "dynamic-import-node"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
41
app/components/GlobalHeader/GlobalHeader.module.scss
Normal file
41
app/components/GlobalHeader/GlobalHeader.module.scss
Normal file
@@ -0,0 +1,41 @@
|
||||
@import 'styles/variables';
|
||||
|
||||
.GlobalHeader {
|
||||
height: $header-height;
|
||||
left: $sidebar-width;
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
right: 0;
|
||||
z-index: 2000;
|
||||
|
||||
&.mobile {
|
||||
left: 0;
|
||||
}
|
||||
&.collapsed {
|
||||
left: $sidebar-collapsed-width;
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.LogoContainer {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 0 0 24px;
|
||||
}
|
||||
|
||||
.MenuIcon {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
line-height: 64px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import { Layout, Icon, Popover, Row } from 'antd';
|
||||
|
||||
import logoMobile from 'images/logoxmobile.jpg';
|
||||
|
||||
import styles from './GlobalHeader.module.scss';
|
||||
|
||||
const { Header } = Layout;
|
||||
|
||||
const GlobalHeader = ({ collapsed, onMenuButtonClick, isMobile }) => {
|
||||
@@ -44,13 +46,23 @@ const GlobalHeader = ({ collapsed, onMenuButtonClick, isMobile }) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Header collapsed={collapsed} isMobile={isMobile}>
|
||||
<Header
|
||||
className={`${styles.GlobalHeader} ${collapsed ? styles.collapsed : ''} ${
|
||||
isMobile ? styles.mobile : ''
|
||||
}`}
|
||||
collapsed={collapsed}
|
||||
isMobile={isMobile}
|
||||
>
|
||||
{isMobile && [
|
||||
<Link to="/" key="mobileLogo">
|
||||
<Link className={styles.LogoContainer} to="/" key="mobileLogo">
|
||||
<img src={logoMobile} alt="logo" width="32" />
|
||||
</Link>,
|
||||
]}
|
||||
<Icon type={collapsed ? 'menu-unfold' : 'menu-fold'} onClick={onMenuButtonClick} />
|
||||
<Icon
|
||||
className={styles.MenuIcon}
|
||||
type={collapsed ? 'menu-unfold' : 'menu-fold'}
|
||||
onClick={onMenuButtonClick}
|
||||
/>
|
||||
<Popover
|
||||
content={userOptions}
|
||||
trigger="click"
|
||||
@@ -58,7 +70,7 @@ const GlobalHeader = ({ collapsed, onMenuButtonClick, isMobile }) => {
|
||||
visible={popoverVisible}
|
||||
onVisibleChange={handleVisibleChange}
|
||||
>
|
||||
<Icon type="setting" />
|
||||
<Icon className={styles.MenuIcon} type="setting" />
|
||||
</Popover>
|
||||
</Header>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import 'styles/variables';
|
||||
|
||||
.Sider {
|
||||
height: 100vh;
|
||||
left: 0;
|
||||
@@ -12,7 +14,7 @@
|
||||
}
|
||||
|
||||
.TopArea {
|
||||
height: 64px;
|
||||
height: $header-height;
|
||||
}
|
||||
|
||||
.LogoContainer {
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export const NAME = 'KodaCloud';
|
||||
export const NAME = 'ConnectUs';
|
||||
export const API_DOMAIN = 'connectus.ai';
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import React from 'react';
|
||||
import { compose } from 'redux';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Switch } from 'react-router-dom';
|
||||
|
||||
import 'styles/antd.less';
|
||||
import 'styles/index.scss';
|
||||
|
||||
import injectReducer from 'utils/injectReducer';
|
||||
import injectSaga from 'utils/injectSaga';
|
||||
|
||||
import reducer from './reducer';
|
||||
import saga from './saga';
|
||||
import Dashboard from 'containers/Dashboard';
|
||||
|
||||
import RouteWithLayout from './components/RouteWithLayout';
|
||||
|
||||
@@ -21,12 +16,9 @@ const App = () => (
|
||||
</Helmet>
|
||||
|
||||
<Switch>
|
||||
<RouteWithLayout exact path="/" component={Home} />
|
||||
<RouteWithLayout exact path="/" component={Dashboard} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
|
||||
const withReducer = injectReducer({ key: 'global', reducer });
|
||||
const withSaga = injectSaga({ key: 'global', saga });
|
||||
|
||||
export default compose(withReducer, withSaga)(App);
|
||||
export default App;
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
import { fromJS } from 'immutable';
|
||||
/*
|
||||
* AppReducer
|
||||
*
|
||||
* The reducer takes care of our data. Using actions, we can
|
||||
* update our application state. To add a new action,
|
||||
* add it to the switch statement in the reducer function
|
||||
*
|
||||
*/
|
||||
|
||||
import produce from 'immer';
|
||||
|
||||
// The initial state of the App
|
||||
const initialState = fromJS({});
|
||||
export const initialState = {
|
||||
loading: false,
|
||||
error: false,
|
||||
currentUser: false,
|
||||
};
|
||||
|
||||
// const mergedState = initialState.merge(getItem(FILTERS_LS_KEY));
|
||||
|
||||
function appReducer(currentState = initialState, action) {
|
||||
const state = currentState.setIn(['error'], false);
|
||||
|
||||
switch (action.type) {
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
/* eslint-disable default-case, no-param-reassign */
|
||||
const appReducer = (state = initialState, action) =>
|
||||
produce(state, () => {
|
||||
switch (action.type) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
export default appReducer;
|
||||
|
||||
42
app/containers/App/selectors.js
Normal file
42
app/containers/App/selectors.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* The global state selectors
|
||||
*/
|
||||
|
||||
import { createSelector } from 'reselect';
|
||||
import { initialState } from './reducer';
|
||||
|
||||
const selectGlobal = state => state.global || initialState;
|
||||
|
||||
const selectRouter = state => state.router;
|
||||
|
||||
const makeSelectCurrentUser = () =>
|
||||
createSelector(
|
||||
selectGlobal,
|
||||
globalState => globalState.currentUser,
|
||||
);
|
||||
|
||||
const makeSelectLoading = () =>
|
||||
createSelector(
|
||||
selectGlobal,
|
||||
globalState => globalState.loading,
|
||||
);
|
||||
|
||||
const makeSelectError = () =>
|
||||
createSelector(
|
||||
selectGlobal,
|
||||
globalState => globalState.error,
|
||||
);
|
||||
|
||||
const makeSelectLocation = () =>
|
||||
createSelector(
|
||||
selectRouter,
|
||||
routerState => routerState.location,
|
||||
);
|
||||
|
||||
export {
|
||||
selectGlobal,
|
||||
makeSelectCurrentUser,
|
||||
makeSelectLoading,
|
||||
makeSelectError,
|
||||
makeSelectLocation,
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
const Dashboard = () => <h1>Dashboard</h1>;
|
||||
|
||||
export default Dashboard;
|
||||
@@ -1,15 +1,22 @@
|
||||
@import 'styles/variables';
|
||||
|
||||
.MainLayout {
|
||||
height: 100vh;
|
||||
margin-left: 234px;
|
||||
margin-left: $sidebar-width;
|
||||
overflow: auto;
|
||||
|
||||
&.mobile {
|
||||
margin-left: 0;
|
||||
}
|
||||
&.collapsed {
|
||||
margin-left: 80px;
|
||||
margin-left: $sidebar-collapsed-width;
|
||||
}
|
||||
}
|
||||
|
||||
.Content {
|
||||
margin-top: $header-height;
|
||||
}
|
||||
|
||||
.Footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
8
app/containers/MasterLayout/actions.js
Normal file
8
app/containers/MasterLayout/actions.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { SET_MENU } from './constants';
|
||||
|
||||
export function setMenu(menu) {
|
||||
return {
|
||||
type: SET_MENU,
|
||||
menu,
|
||||
};
|
||||
}
|
||||
2
app/containers/MasterLayout/constants.js
Normal file
2
app/containers/MasterLayout/constants.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export const STATE_KEY = 'layout';
|
||||
export const SET_MENU = 'layout/SET_MENU';
|
||||
@@ -1,25 +1,87 @@
|
||||
import React from 'react';
|
||||
import T from 'prop-types';
|
||||
import IT from 'react-immutable-proptypes';
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Layout as AntdLayout } from 'antd';
|
||||
import { connect } from 'react-redux';
|
||||
import { compose } from 'redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
|
||||
import { useInjectReducer } from 'utils/injectReducer';
|
||||
|
||||
import GlobalHeader from 'components/GlobalHeader';
|
||||
import SideMenu from 'components/SideMenu';
|
||||
|
||||
import { makeSelectLocation, makeSelectMenu, makeSelectError } from 'containers/App/selectors';
|
||||
import { makeSelectLocation, makeSelectError } from 'containers/App/selectors';
|
||||
|
||||
import { setMenu } from './actions';
|
||||
import { STATE_KEY } from './constants';
|
||||
import { makeSelectMenu } from './selectors';
|
||||
import reducer from './reducer';
|
||||
import styles from './MasterLayout.module.scss';
|
||||
|
||||
const { Content, Footer } = AntdLayout;
|
||||
|
||||
const MasterLayout = ({ children, locationState, menu }) => {
|
||||
const collapsed = menu.get('collapsed');
|
||||
const isMobile = menu.get('isMobile');
|
||||
const MasterLayout = ({ children, locationState, menu, onSetMenu }) => {
|
||||
useInjectReducer({ key: STATE_KEY, reducer });
|
||||
|
||||
const { collapsed, isMobile, screen } = menu;
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const handleResize = () => {
|
||||
const width = window.innerWidth;
|
||||
|
||||
if (width < 768 && screen !== 'sm') {
|
||||
onSetMenu({
|
||||
collapsed: true,
|
||||
isMobile: true,
|
||||
screen: 'sm',
|
||||
});
|
||||
} else if (width >= 768 && width < 992 && screen !== 'md') {
|
||||
onSetMenu({
|
||||
collapsed: true,
|
||||
isMobile: false,
|
||||
screen: 'md',
|
||||
});
|
||||
} else if (width >= 992 && screen !== 'lg') {
|
||||
onSetMenu({
|
||||
collapsed: false,
|
||||
isMobile: false,
|
||||
screen: 'lg',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleMenuToggle = () => {
|
||||
onSetMenu({
|
||||
...menu,
|
||||
collapsed: !collapsed,
|
||||
});
|
||||
|
||||
// Highcharts will only resize with a Timeout
|
||||
window.setTimeout(() => {
|
||||
// window.dispatchEvent(new Event('resize'));
|
||||
}, 10);
|
||||
};
|
||||
|
||||
const handleMenuItemClick = () => {
|
||||
if (isMobile === true) {
|
||||
onSetMenu({
|
||||
...menu,
|
||||
collapsed: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
// logoutUser();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleResize();
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AntdLayout>
|
||||
<SideMenu
|
||||
@@ -30,7 +92,13 @@ const MasterLayout = ({ children, locationState, menu }) => {
|
||||
onMenuItemClick={handleMenuItemClick}
|
||||
onLogout={handleLogout}
|
||||
/>
|
||||
<AntdLayout collapsed={collapsed} isMobile={isMobile}>
|
||||
<AntdLayout
|
||||
className={`${styles.MainLayout} ${collapsed ? styles.collapsed : ''} ${
|
||||
isMobile ? styles.mobile : ''
|
||||
}`}
|
||||
collapsed={collapsed}
|
||||
isMobile={isMobile}
|
||||
>
|
||||
<GlobalHeader
|
||||
collapsed={collapsed}
|
||||
onMenuButtonClick={handleMenuToggle}
|
||||
@@ -46,26 +114,21 @@ const MasterLayout = ({ children, locationState, menu }) => {
|
||||
};
|
||||
|
||||
MasterLayout.propTypes = {
|
||||
children: T.node.isRequired,
|
||||
locationState: T.instanceOf(Object).isRequired,
|
||||
menu: IT.map.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
locationState: PropTypes.instanceOf(Object).isRequired,
|
||||
menu: PropTypes.instanceOf(Object).isRequired,
|
||||
onSetMenu: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export const mapStateToProps = createStructuredSelector({
|
||||
locationState: makeSelectLocation(),
|
||||
menu: makeSelectMenu(),
|
||||
globalError: makeSelectError(),
|
||||
menu: makeSelectMenu(),
|
||||
});
|
||||
|
||||
export function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
logoutUser: evt => {
|
||||
if (evt !== undefined && evt.preventDefault) evt.preventDefault();
|
||||
dispatch(logoutUserAction());
|
||||
},
|
||||
setMenu: (...params) => {
|
||||
dispatch(portalActions.setMenu(...params));
|
||||
},
|
||||
onSetMenu: (...params) => dispatch(setMenu(...params)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
23
app/containers/MasterLayout/reducer.js
Normal file
23
app/containers/MasterLayout/reducer.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import produce from 'immer';
|
||||
import { SET_MENU } from './constants';
|
||||
|
||||
// The initial state of the App
|
||||
export const initialState = {
|
||||
menu: {
|
||||
collapsed: false,
|
||||
isMobile: false,
|
||||
screen: 'lg',
|
||||
},
|
||||
};
|
||||
|
||||
/* eslint-disable default-case, no-param-reassign */
|
||||
const layoutReducer = (state = initialState, action) =>
|
||||
produce(state, draft => {
|
||||
switch (action.type) {
|
||||
case SET_MENU:
|
||||
draft.menu = action.menu;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
export default layoutReducer;
|
||||
10
app/containers/MasterLayout/selectors.js
Normal file
10
app/containers/MasterLayout/selectors.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { STATE_KEY } from './constants';
|
||||
import { initialState } from './reducer';
|
||||
|
||||
const selectLayout = state => state[STATE_KEY] || initialState;
|
||||
|
||||
const makeSelectMenu = () => createSelector(selectLayout, state => state.menu);
|
||||
|
||||
export { selectLayout, makeSelectMenu };
|
||||
@@ -7,8 +7,8 @@
|
||||
<!-- Make the page mobile compatible -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<!-- Open Sans Font -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet" />
|
||||
<!-- Allow installing the app to the homescreen -->
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -19,6 +19,9 @@
|
||||
<strong>enable JS</strong> to make this app work.</noscript
|
||||
>
|
||||
|
||||
<!-- Open Sans Font -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet" />
|
||||
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
22
app/index.js
22
app/index.js
@@ -1,24 +1,30 @@
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ConnectedRouter } from 'react-router-redux';
|
||||
import { ConnectedRouter } from 'connected-react-router';
|
||||
|
||||
import App from 'containers/App';
|
||||
import store from 'store';
|
||||
import configureStore from 'store';
|
||||
import history from 'utils/history';
|
||||
|
||||
const renderApp = () =>
|
||||
render(
|
||||
// Create redux store with history
|
||||
const initialState = {};
|
||||
const store = configureStore(initialState, history);
|
||||
const MOUNT_NODE = document.getElementById('root');
|
||||
|
||||
const render = () => {
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<App />
|
||||
</ConnectedRouter>
|
||||
</Provider>,
|
||||
document.getElementById('root')
|
||||
MOUNT_NODE
|
||||
);
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production' && module.hot) {
|
||||
module.hot.accept('containers/App', renderApp);
|
||||
module.hot.accept('containers/App', () => ReactDOM.unmountComponentAtNode(MOUNT_NODE));
|
||||
}
|
||||
|
||||
renderApp();
|
||||
render();
|
||||
|
||||
@@ -2,44 +2,22 @@
|
||||
* Combine all reducers in this file and export the combined reducers.
|
||||
*/
|
||||
|
||||
import { combineReducers } from 'redux-immutable';
|
||||
import { fromJS } from 'immutable';
|
||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||
import { combineReducers } from 'redux';
|
||||
import { connectRouter } from 'connected-react-router';
|
||||
|
||||
/*
|
||||
* routeReducer
|
||||
*
|
||||
* The reducer merges route location changes into our immutable state.
|
||||
* The change is necessitated by moving to react-router-redux@4
|
||||
*
|
||||
*/
|
||||
|
||||
// Initial routing state
|
||||
const routeInitialState = fromJS({
|
||||
location: null,
|
||||
});
|
||||
import globalReducer from 'containers/App/reducer';
|
||||
import history from 'utils/history';
|
||||
|
||||
/**
|
||||
* Merge route into the global application state
|
||||
* Merges the main reducer with the router state and dynamically injected reducers
|
||||
*/
|
||||
function routeReducer(state = routeInitialState, action) {
|
||||
switch (action.type) {
|
||||
/* istanbul ignore next */
|
||||
case LOCATION_CHANGE:
|
||||
return state.merge({
|
||||
location: action.payload,
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the main reducer with the dynamically injected ones
|
||||
*/
|
||||
export default function createReducer(injectedReducers) {
|
||||
return combineReducers({
|
||||
route: routeReducer,
|
||||
export default function createReducer(injectedReducers = {}) {
|
||||
const rootReducer = combineReducers({
|
||||
global: globalReducer,
|
||||
router: connectRouter(history),
|
||||
...injectedReducers,
|
||||
});
|
||||
}
|
||||
|
||||
return rootReducer;
|
||||
}
|
||||
91
app/store.js
91
app/store.js
@@ -3,62 +3,53 @@
|
||||
*/
|
||||
|
||||
import { createStore, applyMiddleware, compose } from 'redux';
|
||||
import { fromJS } from 'immutable';
|
||||
import { routerMiddleware } from 'react-router-redux';
|
||||
import { routerMiddleware } from 'connected-react-router';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
import createReducer from './reducers';
|
||||
|
||||
import history from 'utils/history';
|
||||
import createReducer from 'reducers';
|
||||
export default function configureStore(initialState = {}, history) {
|
||||
let composeEnhancers = compose;
|
||||
const reduxSagaMonitorOptions = {};
|
||||
|
||||
const initialState = {};
|
||||
// If Redux Dev Tools and Saga Dev Tools Extensions are installed, enable them
|
||||
/* istanbul ignore next */
|
||||
if (process.env.NODE_ENV !== 'production' && typeof window === 'object') {
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__)
|
||||
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({});
|
||||
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
// NOTE: Uncomment the code below to restore support for Redux Saga
|
||||
// Dev Tools once it supports redux-saga version 1.x.x
|
||||
// if (window.__SAGA_MONITOR_EXTENSION__)
|
||||
// reduxSagaMonitorOptions = {
|
||||
// sagaMonitor: window.__SAGA_MONITOR_EXTENSION__,
|
||||
// };
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
// 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 sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
|
||||
|
||||
const isReduxLogger = false;
|
||||
// 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)];
|
||||
|
||||
if (process.env.NODE_ENV === 'development' && isReduxLogger) {
|
||||
// eslint-disable-next-line global-require
|
||||
const { createLogger } = require('redux-logger');
|
||||
const enhancers = [applyMiddleware(...middlewares)];
|
||||
|
||||
middlewares.push(
|
||||
createLogger({
|
||||
predicate: (getState, action) => getState && action,
|
||||
})
|
||||
);
|
||||
const store = createStore(createReducer(), initialState, composeEnhancers(...enhancers));
|
||||
|
||||
// Extensions
|
||||
store.runSaga = sagaMiddleware.run;
|
||||
store.injectedReducers = {}; // Reducer registry
|
||||
store.injectedSagas = {}; // Saga registry
|
||||
|
||||
// Make reducers hot reloadable, see http://mxs.is/googmo
|
||||
/* istanbul ignore next */
|
||||
if (module.hot) {
|
||||
module.hot.accept('./reducers', () => {
|
||||
store.replaceReducer(createReducer(store.injectedReducers));
|
||||
});
|
||||
}
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
const enhancers = [applyMiddleware(...middlewares)];
|
||||
|
||||
// If Redux DevTools Extension is installed use it, otherwise use Redux compose
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
const composeEnhancers =
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
typeof window === 'object' &&
|
||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
||||
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
|
||||
// TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
|
||||
// Prevent recomputing reducers for `replaceReducer`
|
||||
shouldHotReload: false,
|
||||
})
|
||||
: compose;
|
||||
/* eslint-enable */
|
||||
|
||||
const store = createStore(createReducer(), fromJS(initialState), composeEnhancers(...enhancers));
|
||||
|
||||
// Extensions
|
||||
store.runSaga = sagaMiddleware.run;
|
||||
store.injectedReducers = {}; // Reducer registry
|
||||
store.injectedSagas = {}; // Saga registry
|
||||
|
||||
// Make reducers hot reloadable
|
||||
if (process.env.NODE_ENV !== 'production' && module.hot) {
|
||||
module.hot.accept('./reducers', () => {
|
||||
store.replaceReducer(createReducer(store.injectedReducers));
|
||||
});
|
||||
}
|
||||
export default store;
|
||||
|
||||
4
app/styles/variables.scss
Normal file
4
app/styles/variables.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
$sidebar-width: 234px;
|
||||
$sidebar-collapsed-width: 80;
|
||||
|
||||
$header-height: 64px;
|
||||
9
app/utils/environment.js
Normal file
9
app/utils/environment.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/* global location */
|
||||
/* eslint no-restricted-globals: ["error", "event"] */
|
||||
|
||||
export function isLocalhost() {
|
||||
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -13,25 +13,24 @@ import getInjectors from './reducerInjectors';
|
||||
*/
|
||||
export default ({ key, reducer }) => WrappedComponent => {
|
||||
class ReducerInjector extends React.Component {
|
||||
static WrappedComponent = WrappedComponent;
|
||||
|
||||
static contextType = ReactReduxContext;
|
||||
|
||||
static displayName = `withReducer(${WrappedComponent.displayName ||
|
||||
WrappedComponent.name ||
|
||||
'Component'})`;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
getInjectors(context.store).injectReducer(key, reducer);
|
||||
}
|
||||
|
||||
static WrappedComponent = WrappedComponent;
|
||||
|
||||
render() {
|
||||
return <WrappedComponent {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
ReducerInjector.displayName = `withReducer(${WrappedComponent.displayName ||
|
||||
WrappedComponent.name ||
|
||||
'Component'})`;
|
||||
ReducerInjector.contextType = ReactReduxContext;
|
||||
|
||||
return hoistNonReactStatics(ReducerInjector, WrappedComponent);
|
||||
};
|
||||
|
||||
|
||||
@@ -18,14 +18,6 @@ import getInjectors from './sagaInjectors';
|
||||
*/
|
||||
export default ({ key, saga, mode }) => WrappedComponent => {
|
||||
class InjectSaga extends React.Component {
|
||||
static WrappedComponent = WrappedComponent;
|
||||
|
||||
static contextType = ReactReduxContext;
|
||||
|
||||
static displayName = `withSaga(${WrappedComponent.displayName ||
|
||||
WrappedComponent.name ||
|
||||
'Component'})`;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
@@ -38,11 +30,19 @@ export default ({ key, saga, mode }) => WrappedComponent => {
|
||||
this.injectors.ejectSaga(key);
|
||||
}
|
||||
|
||||
static WrappedComponent = WrappedComponent;
|
||||
|
||||
render() {
|
||||
return <WrappedComponent {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
InjectSaga.contextType = ReactReduxContext;
|
||||
|
||||
InjectSaga.displayName = `withSaga(${WrappedComponent.displayName ||
|
||||
WrappedComponent.name ||
|
||||
'Component'})`;
|
||||
|
||||
return hoistNonReactStatics(InjectSaga, WrappedComponent);
|
||||
};
|
||||
|
||||
|
||||
13
app/utils/jwt.js
Normal file
13
app/utils/jwt.js
Normal file
@@ -0,0 +1,13 @@
|
||||
export function parseJwt(token) {
|
||||
const base64Url = token.split('.')[1];
|
||||
const base64 = base64Url.replace('-', '+').replace('_', '/');
|
||||
return JSON.parse(window.atob(base64));
|
||||
}
|
||||
|
||||
export function isTokenExpired(token) {
|
||||
const t = parseJwt(token);
|
||||
if (t.exp && Date.now() < t.exp * 1000) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
|
||||
const loadable = (importFunc, { fallback = null } = { fallback: null }) => {
|
||||
const LazyComponent = lazy(importFunc);
|
||||
|
||||
return props => (
|
||||
<Suspense fallback={fallback}>
|
||||
<LazyComponent {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export default loadable;
|
||||
39
app/utils/localStorage.js
Normal file
39
app/utils/localStorage.js
Normal file
@@ -0,0 +1,39 @@
|
||||
export const isItemExpired = value => {
|
||||
if (value.expiration && Date.now() < value.expiration) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const getItemExpiration = () => {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() + 3);
|
||||
return d.getTime();
|
||||
};
|
||||
|
||||
export const setItem = (key, data, expiration) => {
|
||||
const localStorageState = data;
|
||||
localStorageState.expiration = expiration || getItemExpiration();
|
||||
window.localStorage.setItem(key, JSON.stringify(localStorageState));
|
||||
};
|
||||
|
||||
export const removeItem = key => {
|
||||
window.localStorage.removeItem(key);
|
||||
};
|
||||
|
||||
export const getItem = key => {
|
||||
let value;
|
||||
try {
|
||||
value = JSON.parse(window.localStorage.getItem(key) || '{}');
|
||||
} catch (err) {
|
||||
removeItem();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
if (isItemExpired(value)) {
|
||||
return removeItem();
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Parses the JSON returned by a network request
|
||||
*
|
||||
* @param {object} response A response from a network request
|
||||
*
|
||||
* @return {object} The parsed JSON from the request
|
||||
*/
|
||||
function parseJSON(response) {
|
||||
if (response.status === 204 || response.status === 205) {
|
||||
return null;
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a network request came back fine, and throws an error if not
|
||||
*
|
||||
* @param {object} response A response from a network request
|
||||
*
|
||||
* @return {object|undefined} Returns either the response, or throws an error
|
||||
*/
|
||||
function checkStatus(response) {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const error = new Error(response.statusText);
|
||||
error.response = response;
|
||||
throw error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests a URL, returning a promise
|
||||
*
|
||||
* @param {string} url The URL we want to request
|
||||
* @param {object} [options] The options we want to pass to "fetch"
|
||||
*
|
||||
* @return {object} The response data
|
||||
*/
|
||||
export default function request(url, options) {
|
||||
return fetch(url, options)
|
||||
.then(checkStatus)
|
||||
.then(parseJSON);
|
||||
}
|
||||
396
package-lock.json
generated
396
package-lock.json
generated
@@ -174,6 +174,20 @@
|
||||
"semver": "5.7.1"
|
||||
}
|
||||
},
|
||||
"@babel/helper-create-class-features-plugin": {
|
||||
"version": "7.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz",
|
||||
"integrity": "sha512-klTBDdsr+VFFqaDHm5rR69OpEQtO2Qv8ECxHS1mNhJJvaHArR6a1xTf5K/eZW7eZpJbhCx3NW1Yt/sKsLXLblg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-function-name": "7.8.3",
|
||||
"@babel/helper-member-expression-to-functions": "7.8.3",
|
||||
"@babel/helper-optimise-call-expression": "7.8.3",
|
||||
"@babel/helper-plugin-utils": "7.8.3",
|
||||
"@babel/helper-replace-supers": "7.8.6",
|
||||
"@babel/helper-split-export-declaration": "7.8.3"
|
||||
}
|
||||
},
|
||||
"@babel/helper-create-regexp-features-plugin": {
|
||||
"version": "7.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.6.tgz",
|
||||
@@ -387,6 +401,16 @@
|
||||
"@babel/plugin-syntax-async-generators": "7.8.4"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-class-properties": {
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz",
|
||||
"integrity": "sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-create-class-features-plugin": "7.8.6",
|
||||
"@babel/helper-plugin-utils": "7.8.3"
|
||||
}
|
||||
},
|
||||
"@babel/plugin-proposal-dynamic-import": {
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz",
|
||||
@@ -1768,12 +1792,138 @@
|
||||
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
|
||||
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
|
||||
"requires": {
|
||||
"follow-redirects": "1.5.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
|
||||
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
|
||||
"requires": {
|
||||
"debug": "3.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"axobject-query": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.2.tgz",
|
||||
"integrity": "sha512-ICt34ZmrVt8UQnvPl6TVyDTkmhXmAyAT4Jh5ugfGUX4MOrZ+U/ZY6/sdylRw3qGNr9Ub5AJsaHeDMzNLehRdOQ==",
|
||||
"dev": true
|
||||
},
|
||||
"babel-code-frame": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
|
||||
"integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "1.1.3",
|
||||
"esutils": "2.0.3",
|
||||
"js-tokens": "3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
|
||||
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "2.2.1",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"has-ansi": "2.0.0",
|
||||
"strip-ansi": "3.0.1",
|
||||
"supports-color": "2.0.0"
|
||||
}
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
|
||||
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
|
||||
"dev": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "2.1.1"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
|
||||
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-core": {
|
||||
"version": "6.26.3",
|
||||
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz",
|
||||
"integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-code-frame": "6.26.0",
|
||||
"babel-generator": "6.26.1",
|
||||
"babel-helpers": "6.24.1",
|
||||
"babel-messages": "6.23.0",
|
||||
"babel-register": "6.26.0",
|
||||
"babel-runtime": "6.26.0",
|
||||
"babel-template": "6.26.0",
|
||||
"babel-traverse": "6.26.0",
|
||||
"babel-types": "6.26.0",
|
||||
"babylon": "6.18.0",
|
||||
"convert-source-map": "1.7.0",
|
||||
"debug": "2.6.9",
|
||||
"json5": "0.5.1",
|
||||
"lodash": "4.17.15",
|
||||
"minimatch": "3.0.4",
|
||||
"path-is-absolute": "1.0.1",
|
||||
"private": "0.1.8",
|
||||
"slash": "1.0.0",
|
||||
"source-map": "0.5.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
|
||||
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
|
||||
"dev": true
|
||||
},
|
||||
"slash": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
|
||||
"integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-eslint": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
|
||||
@@ -1788,6 +1938,40 @@
|
||||
"resolve": "1.15.1"
|
||||
}
|
||||
},
|
||||
"babel-generator": {
|
||||
"version": "6.26.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz",
|
||||
"integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-messages": "6.23.0",
|
||||
"babel-runtime": "6.26.0",
|
||||
"babel-types": "6.26.0",
|
||||
"detect-indent": "4.0.0",
|
||||
"jsesc": "1.3.0",
|
||||
"lodash": "4.17.15",
|
||||
"source-map": "0.5.7",
|
||||
"trim-right": "1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsesc": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
|
||||
"integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-helpers": {
|
||||
"version": "6.24.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz",
|
||||
"integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-runtime": "6.26.0",
|
||||
"babel-template": "6.26.0"
|
||||
}
|
||||
},
|
||||
"babel-loader": {
|
||||
"version": "8.0.6",
|
||||
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz",
|
||||
@@ -1800,6 +1984,15 @@
|
||||
"pify": "4.0.1"
|
||||
}
|
||||
},
|
||||
"babel-messages": {
|
||||
"version": "6.23.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
|
||||
"integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-runtime": "6.26.0"
|
||||
}
|
||||
},
|
||||
"babel-plugin-dynamic-import-node": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz",
|
||||
@@ -1818,6 +2011,51 @@
|
||||
"slash": "3.0.0"
|
||||
}
|
||||
},
|
||||
"babel-polyfill": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
|
||||
"integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-runtime": "6.26.0",
|
||||
"core-js": "2.6.11",
|
||||
"regenerator-runtime": "0.10.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"regenerator-runtime": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
|
||||
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-register": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz",
|
||||
"integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-core": "6.26.3",
|
||||
"babel-runtime": "6.26.0",
|
||||
"core-js": "2.6.11",
|
||||
"home-or-tmp": "2.0.0",
|
||||
"lodash": "4.17.15",
|
||||
"mkdirp": "0.5.1",
|
||||
"source-map-support": "0.4.18"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map-support": {
|
||||
"version": "0.4.18",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
|
||||
"integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"source-map": "0.5.7"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-runtime": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
|
||||
@@ -1834,6 +2072,70 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-template": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz",
|
||||
"integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-runtime": "6.26.0",
|
||||
"babel-traverse": "6.26.0",
|
||||
"babel-types": "6.26.0",
|
||||
"babylon": "6.18.0",
|
||||
"lodash": "4.17.15"
|
||||
}
|
||||
},
|
||||
"babel-traverse": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz",
|
||||
"integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-code-frame": "6.26.0",
|
||||
"babel-messages": "6.23.0",
|
||||
"babel-runtime": "6.26.0",
|
||||
"babel-types": "6.26.0",
|
||||
"babylon": "6.18.0",
|
||||
"debug": "2.6.9",
|
||||
"globals": "9.18.0",
|
||||
"invariant": "2.2.4",
|
||||
"lodash": "4.17.15"
|
||||
},
|
||||
"dependencies": {
|
||||
"globals": {
|
||||
"version": "9.18.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
|
||||
"integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"babel-types": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
|
||||
"integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-runtime": "6.26.0",
|
||||
"esutils": "2.0.3",
|
||||
"lodash": "4.17.15",
|
||||
"to-fast-properties": "1.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"to-fast-properties": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
|
||||
"integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"babylon": {
|
||||
"version": "6.18.0",
|
||||
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
|
||||
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
|
||||
"dev": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
@@ -2598,6 +2900,14 @@
|
||||
"integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==",
|
||||
"dev": true
|
||||
},
|
||||
"connected-react-router": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/connected-react-router/-/connected-react-router-6.7.0.tgz",
|
||||
"integrity": "sha512-RDmcmiwSfUWQ3U7J7RVkc9cwNtek26fUn0DWpA8pS7JylC97VNeosrsIxjJ/3CGDrzZPqnc0Hr/kZxjh75JGlw==",
|
||||
"requires": {
|
||||
"prop-types": "15.7.2"
|
||||
}
|
||||
},
|
||||
"console-browserify": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
|
||||
@@ -2951,11 +3261,6 @@
|
||||
"integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=",
|
||||
"dev": true
|
||||
},
|
||||
"deep-diff": {
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz",
|
||||
"integrity": "sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ="
|
||||
},
|
||||
"deep-equal": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
|
||||
@@ -3091,6 +3396,15 @@
|
||||
"integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
|
||||
"dev": true
|
||||
},
|
||||
"detect-indent": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
|
||||
"integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"repeating": "2.0.1"
|
||||
}
|
||||
},
|
||||
"detect-node": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
|
||||
@@ -4097,6 +4411,28 @@
|
||||
"flat-cache": "2.0.1"
|
||||
}
|
||||
},
|
||||
"file-loader": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-5.1.0.tgz",
|
||||
"integrity": "sha512-u/VkLGskw3Ue59nyOwUwXI/6nuBCo7KBkniB/l7ICwr/7cPNGsL1WCXUp3GB0qgOOKU1TiP49bv4DZF/LJqprg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "1.4.0",
|
||||
"schema-utils": "2.6.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"schema-utils": {
|
||||
"version": "2.6.5",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz",
|
||||
"integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "6.12.0",
|
||||
"ajv-keywords": "3.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
@@ -5283,6 +5619,16 @@
|
||||
"react-is": "16.13.0"
|
||||
}
|
||||
},
|
||||
"home-or-tmp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
|
||||
"integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"os-homedir": "1.0.2",
|
||||
"os-tmpdir": "1.0.2"
|
||||
}
|
||||
},
|
||||
"homedir-polyfill": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
|
||||
@@ -5678,10 +6024,10 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"immutable": {
|
||||
"version": "4.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-0M2XxkZLx/mi3t8NVwIm1g8nHoEmM9p9UBl/G9k4+hm0kBgOVdMV/B3CY5dQ8qG8qc80NN4gDV4HQv6FTJ5q7A=="
|
||||
"immer": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-6.0.1.tgz",
|
||||
"integrity": "sha512-oXwigCKgznQywsXi1VgrqgWbQEU3wievNCVc4Fcwky6mwXU6YHj6JuYp0WEM/B1EphkqsLr0x18lm5OiuemPcA=="
|
||||
},
|
||||
"import-fresh": {
|
||||
"version": "3.2.1",
|
||||
@@ -7468,8 +7814,7 @@
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"multicast-dns": {
|
||||
"version": "6.2.3",
|
||||
@@ -9249,11 +9594,6 @@
|
||||
"react-side-effect": "1.2.0"
|
||||
}
|
||||
},
|
||||
"react-immutable-proptypes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz",
|
||||
"integrity": "sha1-Aj1vObsVyXwHHp5g0A0TbqxfoLQ="
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz",
|
||||
@@ -9340,11 +9680,6 @@
|
||||
"tiny-warning": "1.0.3"
|
||||
}
|
||||
},
|
||||
"react-router-redux": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/react-router-redux/-/react-router-redux-4.0.8.tgz",
|
||||
"integrity": "sha1-InQDWWtRUeGCN32rg1tdRfD4BU4="
|
||||
},
|
||||
"react-side-effect": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-1.2.0.tgz",
|
||||
@@ -9475,19 +9810,6 @@
|
||||
"symbol-observable": "1.2.0"
|
||||
}
|
||||
},
|
||||
"redux-immutable": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz",
|
||||
"integrity": "sha1-Ohoy32Y2ZGK2NpHw4dw15HK7yfM="
|
||||
},
|
||||
"redux-logger": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz",
|
||||
"integrity": "sha1-91VZZvMJjzyIYExEnPC69XeCdL8=",
|
||||
"requires": {
|
||||
"deep-diff": "0.3.8"
|
||||
}
|
||||
},
|
||||
"redux-saga": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.1.3.tgz",
|
||||
@@ -11194,6 +11516,12 @@
|
||||
"integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
|
||||
"dev": true
|
||||
},
|
||||
"trim-right": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
|
||||
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
|
||||
"dev": true
|
||||
},
|
||||
"true-case-path": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz",
|
||||
|
||||
17
package.json
17
package.json
@@ -17,32 +17,33 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"antd": "^4.0.2",
|
||||
"axios": "^0.19.2",
|
||||
"connected-react-router": "^6.7.0",
|
||||
"history": "^4.10.1",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"immutable": "^4.0.0-rc.12",
|
||||
"immer": "^6.0.1",
|
||||
"invariant": "^2.2.4",
|
||||
"lodash": "^4.17.15",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.13.0",
|
||||
"react-dom": "^16.13.0",
|
||||
"react-helmet": "^5.2.1",
|
||||
"react-immutable-proptypes": "^2.1.0",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-router-redux": "^4.0.8",
|
||||
"redux": "^4.0.5",
|
||||
"redux-immutable": "^4.0.0",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-saga": "^1.1.3",
|
||||
"reselect": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.7",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.7",
|
||||
"@babel/preset-react": "^7.8.3",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-root-import": "^6.4.1",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"css-loader": "^3.4.2",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb": "^18.0.1",
|
||||
@@ -54,6 +55,7 @@
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"eslint-plugin-react": "^7.19.0",
|
||||
"eslint-plugin-react-hooks": "^2.5.0",
|
||||
"file-loader": "^5.1.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "^4.2.3",
|
||||
"less": "^3.11.1",
|
||||
@@ -70,6 +72,11 @@
|
||||
"webpack-dev-server": "^3.10.3"
|
||||
},
|
||||
"precommit": "NODE_ENV=production lint-staged",
|
||||
"browserslist": [
|
||||
"last 2 versions",
|
||||
"> 1%",
|
||||
"IE 10"
|
||||
],
|
||||
"lint-staged": {
|
||||
"*.{js,jsx}": [
|
||||
"pretty-quick --staged",
|
||||
|
||||
@@ -4,7 +4,7 @@ const HtmlWebPackPlugin = require('html-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
mode: process.env.NODE_ENV,
|
||||
entry: './app/index.js',
|
||||
entry: ['babel-polyfill', './app/index.js'],
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
publicPath: '/',
|
||||
@@ -15,13 +15,28 @@ module.exports = {
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(png|jpe?g|gif)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: ['babel-loader', 'eslint-loader'],
|
||||
},
|
||||
{
|
||||
// Preprocess 3rd party .css files located in node_modules
|
||||
test: /\.css$/,
|
||||
include: /node_modules/,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'style-loader',
|
||||
@@ -61,9 +76,6 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -80,6 +92,7 @@ module.exports = {
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebPackPlugin({
|
||||
inject: true,
|
||||
template: './app/index.html',
|
||||
filename: './index.html',
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user