refactored

This commit is contained in:
Sean Macfarlane
2020-03-11 17:55:42 -04:00
parent 63b392d73d
commit b619cfe8b0
31 changed files with 836 additions and 261 deletions

View File

@@ -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"]
}
}
}

View 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;
}

View File

@@ -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>
);

View File

@@ -1,3 +1,5 @@
@import 'styles/variables';
.Sider {
height: 100vh;
left: 0;
@@ -12,7 +14,7 @@
}
.TopArea {
height: 64px;
height: $header-height;
}
.LogoContainer {

View File

@@ -1 +1,2 @@
export const NAME = 'KodaCloud';
export const NAME = 'ConnectUs';
export const API_DOMAIN = 'connectus.ai';

View File

@@ -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;

View File

@@ -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;

View 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,
};

View File

@@ -0,0 +1,5 @@
import React from 'react';
const Dashboard = () => <h1>Dashboard</h1>;
export default Dashboard;

View File

@@ -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;
}

View File

@@ -0,0 +1,8 @@
import { SET_MENU } from './constants';
export function setMenu(menu) {
return {
type: SET_MENU,
menu,
};
}

View File

@@ -0,0 +1,2 @@
export const STATE_KEY = 'layout';
export const SET_MENU = 'layout/SET_MENU';

View File

@@ -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)),
};
}

View 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;

View 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 };

View File

@@ -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>

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -0,0 +1,4 @@
$sidebar-width: 234px;
$sidebar-collapsed-width: 80;
$header-height: 64px;

9
app/utils/environment.js Normal file
View 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;
}

View File

@@ -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);
};

View File

@@ -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
View 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;
}

View File

@@ -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
View 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;
};

View File

@@ -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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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',
}),