From 2906bf92c308e6bf7651527af2bbf35c8ae21022 Mon Sep 17 00:00:00 2001 From: wrk Date: Sun, 11 Jul 2021 21:24:05 +0800 Subject: [PATCH 1/6] fix a small problem with bcrypt --- application/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/models.py b/application/models.py index 9aff69c..9a9138b 100644 --- a/application/models.py +++ b/application/models.py @@ -13,8 +13,8 @@ def __init__(self, email, password): @staticmethod def hashed_password(password): - return bcrypt.generate_password_hash(password).decode("utf-8") - + return bcrypt.generate_password_hash(password) + @staticmethod def get_user_with_email_and_password(email, password): user = User.query.filter_by(email=email).first() From ce39660ef57e6e70c4e4bf7ede0ca9895ea94393 Mon Sep 17 00:00:00 2001 From: wrk Date: Sun, 11 Jul 2021 21:24:48 +0800 Subject: [PATCH 2/6] modify the http header config --- static/src/utils/http_functions.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/static/src/utils/http_functions.js b/static/src/utils/http_functions.js index aaa7eb2..058932a 100644 --- a/static/src/utils/http_functions.js +++ b/static/src/utils/http_functions.js @@ -2,12 +2,17 @@ import axios from 'axios'; +const DEFAULT_HEADER = { + 'Content-Type': 'application/json' +}; + const tokenConfig = (token) => ({ - headers: { + headers: Object.assign({}, DEFAULT_HEADER, { 'Authorization': token, // eslint-disable-line quote-props - }, + }), }); + export function validate_token(token) { return axios.post('/api/is_token_valid', { token, @@ -25,14 +30,14 @@ export function create_user(email, password) { return axios.post('/api/create_user', { email, password, - }); + }, DEFAULT_HEADER); } export function get_token(email, password) { return axios.post('/api/get_token', { email, password, - }); + }, DEFAULT_HEADER); } export function has_github_token(token) { From be5380d9f13ec615e7e8c5b43002f89253862540 Mon Sep 17 00:00:00 2001 From: wrk Date: Sun, 11 Jul 2021 21:26:24 +0800 Subject: [PATCH 3/6] update npm dependencies --- static/package.json | 158 ++++++++++++++++++++++---------------------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/static/package.json b/static/package.json index 37808c3..f197288 100644 --- a/static/package.json +++ b/static/package.json @@ -29,92 +29,92 @@ "author": "https://github.com/anorudes, https://github.com/keske", "license": "MIT", "devDependencies": { - "autoprefixer": "6.5.3", - "axios": "^0.15.3", - "babel-core": "^6.4.5", + "autoprefixer": "10.3.0", + "axios": "^0.21.1", + "babel-core": "^6.26.3", "babel-eslint": "^7.1.1", - "babel-loader": "^6.2.1", - "babel-plugin-react-transform": "^2.0.0", - "babel-plugin-transform-decorators-legacy": "^1.3.4", - "babel-polyfill": "^6.3.14", + "babel-loader": "^8.2.2", + "babel-plugin-react-transform": "^3.0.0", + "babel-plugin-transform-decorators-legacy": "^1.3.5", + "babel-polyfill": "^6.26.0", "babel-preset-es2015": "^6.3.13", - "babel-preset-react": "^6.3.13", - "babel-preset-react-hmre": "^1.0.1", - "babel-preset-stage-0": "^6.3.13", - "bootstrap": "^3.3.5", - "bootstrap-loader": "^1.2.0-beta.1", - "bootstrap-sass": "^3.3.6", - "bootstrap-webpack": "0.0.5", - "classnames": "^2.2.3", - "css-loader": "^0.26.1", + "babel-preset-react": "^6.24.1", + "babel-preset-react-hmre": "^1.1.1", + "babel-preset-stage-0": "^6.24.1", + "bootstrap": "^5.0.2", + "bootstrap-loader": "^3.0.4", + "bootstrap-sass": "^3.4.1", + "bootstrap-webpack": "0.0.6", + "classnames": "^2.3.1", + "css-loader": "^5.2.6", "csswring": "^5.1.0", - "deep-equal": "^1.0.1", - "eslint": "^3.4.0", - "eslint-config-airbnb": "13.0.0", - "eslint-plugin-import": "^2.2.0", - "eslint-plugin-jsx-a11y": "^3.0.1", - "eslint-plugin-react": "^6.1.2", - "expect": "^1.13.4", - "exports-loader": "^0.6.2", - "expose-loader": "^0.7.1", - "express": "^4.13.4", - "express-open-in-editor": "^1.1.0", + "deep-equal": "^2.0.5", + "eslint": "^7.30.0", + "eslint-config-airbnb": "18.2.1", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-react": "^7.24.0", + "expect": "^27.0.6", + "exports-loader": "^3.0.0", + "expose-loader": "^3.0.0", + "express": "^4.17.1", + "express-open-in-editor": "^3.1.1", "extract-text-webpack-plugin": "^1.0.1", - "file-loader": "^0.9.0", + "file-loader": "^6.2.0", "gapi": "0.0.3", - "history": "^4.4.1", - "http-proxy": "^1.12.0", - "imports-loader": "^0.6.5", - "jasmine-core": "^2.4.1", - "jquery": "^3.1.0", - "jwt-decode": "^2.1.0", - "karma": "^1.2.0", - "karma-chrome-launcher": "^2.0.0", - "karma-mocha": "^1.1.1", - "karma-webpack": "^1.7.0", - "less": "^2.5.3", - "less-loader": "^2.2.2", - "lodash": "^4.5.1", - "material-ui": "^0.16.4", - "mocha": "^3.0.2", - "morgan": "^1.6.1", - "node-sass": "^3.4.2", - "postcss-import": "^9.0.0", - "postcss-loader": "^1.1.1", - "q": "^1.4.1", - "qs": "^6.1.0", - "rc-datepicker": "^4.0.1", - "react": "^15.3.1", - "react-addons-css-transition-group": "^15.3.1", - "react-calendar-component": "^1.0.0", - "react-date-picker": "^5.3.28", - "react-datepicker": "^0.37.0", - "react-document-meta": "^2.0.0-rc2", - "react-dom": "^15.1.0", + "history": "^5.0.0", + "http-proxy": "^1.18.1", + "imports-loader": "^3.0.0", + "jasmine-core": "^3.8.0", + "jquery": "^3.6.0", + "jwt-decode": "^3.1.2", + "karma": "^6.3.4", + "karma-chrome-launcher": "^3.1.0", + "karma-mocha": "^2.0.1", + "karma-webpack": "^5.0.0", + "less": "^4.1.1", + "less-loader": "^10.0.1", + "lodash": "^4.17.21", + "material-ui": "^0.20.0", + "mocha": "^9.0.2", + "morgan": "^1.10.0", + "node-sass": "^6.0.1", + "postcss-import": "^14.0.2", + "postcss-loader": "^6.1.1", + "q": "^1.5.1", + "qs": "^6.10.1", + "rc-datepicker": "^5.0.16", + "react": "^17.0.2", + "react-addons-css-transition-group": "^15.6.2", + "react-calendar-component": "^3.0.0", + "react-date-picker": "^8.2.0", + "react-datepicker": "^4.1.1", + "react-document-meta": "^3.0.0-beta.2", + "react-dom": "^17.0.2", "react-forms": "^2.0.0-beta33", - "react-hot-loader": "^1.3.0", + "react-hot-loader": "^4.13.0", "react-loading-order-with-animation": "^1.0.0", - "react-onclickoutside": "^5.3.3", - "react-redux": "^4.3.0", - "react-router": "3.0.0", - "react-router-redux": "^4.0.0", - "react-tap-event-plugin": "^2.0.1", - "react-transform-hmr": "^1.0.1", - "redux": "^3.2.1", - "redux-form": "^6.0.1", - "redux-logger": "2.7.4", - "redux-thunk": "^2.1.0", - "resolve-url-loader": "^1.4.3", - "rimraf": "^2.5.0", - "sass-loader": "^4.0.0", - "style-loader": "^0.13.0", - "url-loader": "^0.5.7", - "webpack": "^1.12.11", - "webpack-dev-middleware": "^1.5.0", - "webpack-dev-server": "^1.14.1", - "webpack-hot-middleware": "^2.6.0", - "webpack-merge": "^1.0.2", - "yargs": "^6.5.0" + "react-onclickoutside": "^6.11.2", + "react-redux": "^7.2.4", + "react-router": "5.2.0", + "react-router-redux": "^4.0.8", + "react-tap-event-plugin": "^3.0.3", + "react-transform-hmr": "^1.0.4", + "redux": "^4.1.0", + "redux-form": "^8.3.7", + "redux-logger": "3.0.6", + "redux-thunk": "^2.3.0", + "resolve-url-loader": "^4.0.0", + "rimraf": "^3.0.2", + "sass-loader": "^12.1.0", + "style-loader": "^3.0.0", + "url-loader": "^4.1.1", + "webpack": "^5.44.0", + "webpack-dev-middleware": "^5.0.0", + "webpack-dev-server": "^3.11.2", + "webpack-hot-middleware": "^2.25.0", + "webpack-merge": "^5.8.0", + "yargs": "^17.0.1" }, "dependencies": {} } From ce7c522869464c2f3d06ed0004932fb705ae523b Mon Sep 17 00:00:00 2001 From: wrk Date: Mon, 12 Jul 2021 22:39:39 +0800 Subject: [PATCH 4/6] webpack v5, dependencies solved --- static/.babelrc | 11 +- static/bin/server.js | 2 +- static/package.json | 15 +- static/server.js | 2 +- static/src/actions/option.js | 21 ++ static/src/actions/register.js | 12 ++ .../src/components/AuthenticatedComponent.js | 109 +++++----- static/src/components/DetermineAuth.js | 97 ++++----- static/src/components/Header/index.js | 142 ++++++------- static/src/components/LoginView.js | 8 +- static/src/components/NotFound.js | 30 +-- static/src/components/ProtectedView.js | 79 +++----- static/src/components/RegisterView.js | 189 ++++++++---------- .../components/notAuthenticatedComponent.js | 117 +++++------ static/src/constants/index.js | 11 + static/src/containers/App/index.js | 41 ++-- static/src/index.js | 16 +- static/src/reducers/index.js | 4 + static/src/reducers/option.js | 19 ++ static/src/reducers/register.js | 16 ++ static/src/routes.js | 18 +- static/src/store/configureStore.js | 2 +- static/src/utils/lifecycle_hook.js | 25 +++ static/webpack/common.config.js | 111 +++++++--- static/webpack/dev.config.js | 42 +++- static/webpack/prod.config.js | 18 +- 26 files changed, 598 insertions(+), 559 deletions(-) create mode 100644 static/src/actions/option.js create mode 100644 static/src/actions/register.js create mode 100644 static/src/reducers/option.js create mode 100644 static/src/reducers/register.js create mode 100644 static/src/utils/lifecycle_hook.js diff --git a/static/.babelrc b/static/.babelrc index 535d3c8..e88f7ae 100755 --- a/static/.babelrc +++ b/static/.babelrc @@ -1,11 +1,6 @@ { - "presets": ["react", "es2015" , "stage-0"], + "presets": ["@babel/react"], "plugins": [ - ["transform-decorators-legacy"] - ], - "env": { - "start": { - "presets": ["react-hmre"] - } - } + ["@babel/plugin-proposal-decorators", { "legacy": true }] + ] } diff --git a/static/bin/server.js b/static/bin/server.js index ceb6ad4..6f91b02 100755 --- a/static/bin/server.js +++ b/static/bin/server.js @@ -10,5 +10,5 @@ try { console.error(err); } -require('babel-core/register')(config); +require("@babel/register")(config); require('../server'); diff --git a/static/package.json b/static/package.json index f197288..7eeefeb 100644 --- a/static/package.json +++ b/static/package.json @@ -31,16 +31,11 @@ "devDependencies": { "autoprefixer": "10.3.0", "axios": "^0.21.1", - "babel-core": "^6.26.3", "babel-eslint": "^7.1.1", "babel-loader": "^8.2.2", "babel-plugin-react-transform": "^3.0.0", - "babel-plugin-transform-decorators-legacy": "^1.3.5", "babel-polyfill": "^6.26.0", "babel-preset-es2015": "^6.3.13", - "babel-preset-react": "^6.24.1", - "babel-preset-react-hmre": "^1.1.1", - "babel-preset-stage-0": "^6.24.1", "bootstrap": "^5.0.2", "bootstrap-loader": "^3.0.4", "bootstrap-sass": "^3.4.1", @@ -59,7 +54,6 @@ "expose-loader": "^3.0.0", "express": "^4.17.1", "express-open-in-editor": "^3.1.1", - "extract-text-webpack-plugin": "^1.0.1", "file-loader": "^6.2.0", "gapi": "0.0.3", "history": "^5.0.0", @@ -116,5 +110,12 @@ "webpack-merge": "^5.8.0", "yargs": "^17.0.1" }, - "dependencies": {} + "dependencies": { + "@babel/core": "^7.14.6", + "@babel/plugin-proposal-decorators": "^7.14.5", + "@babel/register": "^7.14.5", + "duplicate-package-checker-webpack-plugin": "^3.0.0", + "mini-css-extract-plugin": "^2.1.0", + "uglifyjs-webpack-plugin": "^2.2.0" + } } diff --git a/static/server.js b/static/server.js index 8a2c2d1..cb23d7c 100755 --- a/static/server.js +++ b/static/server.js @@ -16,7 +16,7 @@ app.use(require('morgan')('short')); const compiler = webpack(webpackConfig); app.use(require('webpack-dev-middleware')(compiler, { - noInfo: true, publicPath: webpackConfig.output.publicPath, + publicPath: webpackConfig.output.publicPath, })); app.use(require('webpack-hot-middleware')(compiler, { diff --git a/static/src/actions/option.js b/static/src/actions/option.js new file mode 100644 index 0000000..0b8ec6e --- /dev/null +++ b/static/src/actions/option.js @@ -0,0 +1,21 @@ +import { SET_LOAD_IF_NEEDED, SET_SIDEBAR_OPEN } from "../constants"; + + +export function setLoadIfNeeded(value) { + return { + type: SET_LOAD_IF_NEEDED, + payload: { + data: value, + }, + }; +} + + +export function setSideBarOpen(value) { + return { + type: SET_SIDEBAR_OPEN, + payload: { + data: value, + }, + }; +} \ No newline at end of file diff --git a/static/src/actions/register.js b/static/src/actions/register.js new file mode 100644 index 0000000..bc0bcfd --- /dev/null +++ b/static/src/actions/register.js @@ -0,0 +1,12 @@ +import { SET_REGISTER } from "../constants"; + + +export function setRegister(data) { + return { + type: SET_REGISTER, + payload: { + data: data, + }, + }; +} + diff --git a/static/src/components/AuthenticatedComponent.js b/static/src/components/AuthenticatedComponent.js index 66cd2d4..0c68af2 100755 --- a/static/src/components/AuthenticatedComponent.js +++ b/static/src/components/AuthenticatedComponent.js @@ -1,40 +1,23 @@ import React from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import { browserHistory } from 'react-router'; -import * as actionCreators from '../actions/auth'; +import { useDispatch } from 'react-redux'; +import { useHistory } from 'react-router'; +import { useComponentDidMount } from '../utils/lifecycle_hook'; +import * as authActions from '../actions/auth'; +import * as optionActions from '../actions/option'; -function mapStateToProps(state) { - return { - token: state.auth.token, - userName: state.auth.userName, - isAuthenticated: state.auth.isAuthenticated, - }; -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators(actionCreators, dispatch); -} - - -export function requireAuthentication(Component) { - class AuthenticatedComponent extends React.Component { - componentWillMount() { - this.checkAuth(); - this.state = { - loaded_if_needed: false, - }; - } - - componentWillReceiveProps(nextProps) { - this.checkAuth(nextProps); - } +export const requireAuthentication = (Component) => { + console.log("require"); + + const ret = (props) => { + const isAuthenticated = useSelector(state => state.auth.isAuthenticated); + const loadIfNeeded = useSelector(state => state.option.loadIfNeeded); + const dispatch = useDispatch(); - checkAuth(props = this.props) { - if (!props.isAuthenticated) { + useComponentDidMount(() => { + if (!isAuthenticated) { const token = localStorage.getItem('token'); if (!token) { - browserHistory.push('/home'); + useHistory().push('/home'); } else { fetch('/api/is_token_valid', { method: 'post', @@ -45,44 +28,42 @@ export function requireAuthentication(Component) { }, body: JSON.stringify({ token }), }) - .then(res => { - if (res.status === 200) { - this.props.loginUserSuccess(token); - this.setState({ - loaded_if_needed: true, - }); - - } else { - browserHistory.push('/home'); - - } - }); - + .then(res => { + if (res.status === 200) { + console.log('200'); + dispatch(authActions.loginUserSuccess(token)); + dispatch(optionActions.setLoadIfNeeded(true)); + } else { + console.log('invalid'); + useHistory().push('/home'); + } + }); + } } else { - this.setState({ - loaded_if_needed: true, - }); + dispatch(optionActions.setLoadIfNeeded(true)); } - } + }) - render() { - return ( -
- {this.props.isAuthenticated && this.state.loaded_if_needed - ? - : null - } -
- ); + console.log("isAuthenticated: ", isAuthenticated); + console.log("loadIfNeeded: ", loadIfNeeded); + + return ( +
+ {isAuthenticated && loadIfNeeded + ? + : null + } +
+ ); - } } - AuthenticatedComponent.propTypes = { - loginUserSuccess: React.PropTypes.func, - isAuthenticated: React.PropTypes.bool, - }; + return ret; + + // AuthenticatedComponent.propTypes = { + // loginUserSuccess: React.PropTypes.func, + // isAuthenticated: React.PropTypes.bool, + // }; - return connect(mapStateToProps, mapDispatchToProps)(AuthenticatedComponent); } diff --git a/static/src/components/DetermineAuth.js b/static/src/components/DetermineAuth.js index 27398bf..49e8206 100644 --- a/static/src/components/DetermineAuth.js +++ b/static/src/components/DetermineAuth.js @@ -1,38 +1,19 @@ import React from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import * as actionCreators from '../actions/auth'; - -function mapStateToProps(state) { - return { - token: state.auth.token, - userName: state.auth.userName, - isAuthenticated: state.auth.isAuthenticated, - }; -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators(actionCreators, dispatch); -} +import { useDispatch, useSelector } from 'react-redux'; +import * as authActions from '../actions/auth'; +import * as optionActions from '../actions/option'; +import { useComponentDidMount } from '../utils/lifecycle_hook'; export function DetermineAuth(Component) { - - class AuthenticatedComponent extends React.Component { - - componentWillMount() { - this.checkAuth(); - this.state = { - loaded_if_needed: false, - }; - } - - componentWillReceiveProps(nextProps) { - this.checkAuth(nextProps); - } - - checkAuth(props = this.props) { - if (!props.isAuthenticated) { + console.log("determine"); + const ret = (props) => { + const dispatch = useDispatch(); + const loadIfNeeded = useSelector(state => state.option.loadIfNeeded) + const isAuthenticated = useSelector(state => state.auth.isAuthenticated); + + useComponentDidMount(() => { + if (!isAuthenticated) { const token = localStorage.getItem('token'); if (token) { fetch('/api/is_token_valid', { @@ -44,41 +25,37 @@ export function DetermineAuth(Component) { }, body: JSON.stringify({ token }), }) - .then(res => { - if (res.status === 200) { - this.props.loginUserSuccess(token); - this.setState({ - loaded_if_needed: true, - }); - - } - }); + .then(res => { + if (res.status === 200) { + dispatch(authActions.loginUserSuccess(token)); + dispatch(optionActions.setLoadIfNeeded(true)); + } + }); } - - } else { - this.setState({ - loaded_if_needed: true, - }); } - } + else { + dispatch(optionActions.setLoadIfNeeded(true)); + } + }); - render() { - return ( -
- {this.state.loaded_if_needed - ? - : null - } -
- ); + console.log(loadIfNeeded); + + return ( +
+ {loadIfNeeded + ? + : null + } +
+ ); - } } - AuthenticatedComponent.propTypes = { - loginUserSuccess: React.PropTypes.func, - }; + return ret; + + // AuthenticatedComponent.propTypes = { + // loginUserSuccess: React.PropTypes.func, + // }; - return connect(mapStateToProps, mapDispatchToProps)(AuthenticatedComponent); } diff --git a/static/src/components/Header/index.js b/static/src/components/Header/index.js index ca03bd5..2a31231 100644 --- a/static/src/components/Header/index.js +++ b/static/src/components/Header/index.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; -import { browserHistory } from 'react-router'; -import { connect } from 'react-redux'; +import { useHistory } from 'react-router'; +import { useDispatch, useSelector } from 'react-redux'; import { bindActionCreators } from 'redux'; import AppBar from 'material-ui/AppBar'; import LeftNav from 'material-ui/Drawer'; @@ -8,7 +8,8 @@ import MenuItem from 'material-ui/MenuItem'; import FlatButton from 'material-ui/FlatButton'; import Divider from 'material-ui/Divider'; -import * as actionCreators from '../../actions/auth'; +import * as authActions from '../../actions/auth'; +import * as optionActions from '../../actions/option'; function mapStateToProps(state) { return { @@ -18,91 +19,70 @@ function mapStateToProps(state) { }; } -function mapDispatchToProps(dispatch) { - return bindActionCreators(actionCreators, dispatch); -} - -@connect(mapStateToProps, mapDispatchToProps) -export class Header extends Component { - constructor(props) { - super(props); - this.state = { - open: false, - }; - - } - - dispatchNewRoute(route) { - browserHistory.push(route); - this.setState({ - open: false, - }); - - } +export const Header = () => { + const history = useHistory(); + const dispatch = useDispatch(); + const sideBarOpen = useSelector((state) => (state.option.sideBarOpen)); + const isAuthenticated = useSelector((state) => (state.auth.isAuthenticated)); + const dispatchNewRoute = (route) => { + dispatch(optionActions.setSideBarOpen(false)); + history.push(route); - handleClickOutside() { - this.setState({ - open: false, - }); - } + }; + const handleClickOutside = () => { + dispatch(optionActions.setSideBarOpen(false)); + }; - logout(e) { + const logout = (e) => { e.preventDefault(); - this.props.logoutAndRedirect(); - this.setState({ - open: false, - }); - } + dispatch(authActions.logoutAndRedirect()); + dispatch(optionActions.setSideBarOpen(false)); + }; - openNav() { - this.setState({ - open: true, - }); - } + const openNav = () => { + dispatch(optionActions.setSideBarOpen(true)); + }; - render() { - return ( -
- - { - !this.props.isAuthenticated ? -
- this.dispatchNewRoute('/login')}> - Login - - this.dispatchNewRoute('/register')}> - Register - -
- : -
- this.dispatchNewRoute('/analytics')}> - Analytics - - + console.log("sideBarOpen", sideBarOpen); + + return ( +
+ + { + !isAuthenticated ? +
+ dispatchNewRoute('/login')}> + Login + + dispatchNewRoute('/register')}> + Register + +
+ : +
+ dispatchNewRoute('/analytics')}> + Analytics + + + + logout(e)}> + Logout + +
+ } +
+ openNav()} + iconElementRight={ + dispatchNewRoute('/')} /> + } + /> +
+ + ); - this.logout(e)}> - Logout - -
- } -
- this.openNav()} - iconElementRight={ - this.dispatchNewRoute('/')} /> - } - /> -
- ); - } } - -Header.propTypes = { - logoutAndRedirect: React.PropTypes.func, - isAuthenticated: React.PropTypes.bool, -}; diff --git a/static/src/components/LoginView.js b/static/src/components/LoginView.js index d7a5921..9aa352b 100755 --- a/static/src/components/LoginView.js +++ b/static/src/components/LoginView.js @@ -162,7 +162,7 @@ export default class LoginView extends React.Component { } } -LoginView.propTypes = { - loginUser: React.PropTypes.func, - statusText: React.PropTypes.string, -}; +// LoginView.propTypes = { +// loginUser: React.PropTypes.func, +// statusText: React.PropTypes.string, +// }; diff --git a/static/src/components/NotFound.js b/static/src/components/NotFound.js index 25ac18e..63baa4d 100644 --- a/static/src/components/NotFound.js +++ b/static/src/components/NotFound.js @@ -1,30 +1,12 @@ import React from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import * as actionCreators from '../actions/auth'; -function mapStateToProps(state) { - return { - token: state.auth.token, - userName: state.auth.userName, - isAuthenticated: state.auth.isAuthenticated, - }; -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators(actionCreators, dispatch); -} - -@connect(mapStateToProps, mapDispatchToProps) -class NotFound extends React.Component { // eslint-disable-line react/prefer-stateless-function - render() { - return ( -
-

Not Found

-
- ); - } +const NotFound = () => { // eslint-disable-line react/prefer-stateless-function + return ( +
+

Not Found

+
+ ); } export default NotFound; diff --git a/static/src/components/ProtectedView.js b/static/src/components/ProtectedView.js index 271e8a0..2c40417 100755 --- a/static/src/components/ProtectedView.js +++ b/static/src/components/ProtectedView.js @@ -1,55 +1,38 @@ import React from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import * as actionCreators from '../actions/data'; - -function mapStateToProps(state) { - return { - data: state.data, - token: state.auth.token, - loaded: state.data.loaded, - isFetching: state.data.isFetching, - }; -} +import { useSelector } from 'react-redux'; +import { useComponentDidMount } from '../utils/lifecycle_hook'; -function mapDispatchToProps(dispatch) { - return bindActionCreators(actionCreators, dispatch); -} - -@connect(mapStateToProps, mapDispatchToProps) -export default class ProtectedView extends React.Component { - componentDidMount() { - this.fetchData(); +const ProtectedView = (props) => { + console.log('protectedView'); + const fetchData = () => { + const token = useSelector(state => state.data.token); + props.fetchProtectedData(token); } - - fetchData() { - const token = this.props.token; - this.props.fetchProtectedData(token); - } - - render() { - return ( -
- {!this.props.loaded - ?

Loading data...

- : -
-

Welcome back, - {this.props.userName}!

-

{this.props.data.data.email}

-
- } -
- ); - } + useComponentDidMount(() => {fetchData()}); + + return ( +
+ {!useSelector(state => state.data.loaded) + ?

Loading data...

+ : +
+

Welcome back, + {props.userName}!

+

{useSelector(state => state.data.data.email)}

+
+ } +
+ ); } -ProtectedView.propTypes = { - fetchProtectedData: React.PropTypes.func, - loaded: React.PropTypes.bool, - userName: React.PropTypes.string, - data: React.PropTypes.any, - token: React.PropTypes.string, -}; +export default ProtectedView; + +// ProtectedView.propTypes = { +// fetchProtectedData: React.PropTypes.func, +// loaded: React.PropTypes.bool, +// userName: React.PropTypes.string, +// data: React.PropTypes.any, +// token: React.PropTypes.string, +// }; diff --git a/static/src/components/RegisterView.js b/static/src/components/RegisterView.js index 4d59536..35ae5bf 100644 --- a/static/src/components/RegisterView.js +++ b/static/src/components/RegisterView.js @@ -2,26 +2,16 @@ import React from 'react'; import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; +import { connect, useDispatch, useSelector } from 'react-redux'; import TextField from 'material-ui/TextField'; import RaisedButton from 'material-ui/RaisedButton'; import Paper from 'material-ui/Paper'; -import * as actionCreators from '../actions/auth'; +import * as authActions from '../actions/auth'; +import * as registerActions from '../actions/register'; import { validateEmail } from '../utils/misc'; -function mapStateToProps(state) { - return { - isRegistering: state.auth.isRegistering, - registerStatusText: state.auth.registerStatusText, - }; -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators(actionCreators, dispatch); -} - const style = { marginTop: 50, paddingBottom: 50, @@ -31,137 +21,124 @@ const style = { display: 'inline-block', }; -@connect(mapStateToProps, mapDispatchToProps) -export default class RegisterView extends React.Component { - - constructor(props) { - super(props); - const redirectRoute = '/login'; - this.state = { - email: '', - password: '', - email_error_text: null, - password_error_text: null, - redirectTo: redirectRoute, - disabled: true, - }; - } +const RegisterView = (props) => { + + const state = useSelector(state => state.register); + const dispatch = useDispatch; + const registerStatusText = useSelector(state => state.auth.registerStatusText); + const isRegistering = useSelector(state => state.auth.isRegistering); - isDisabled() { + const isDisabled = () => { let email_is_valid = false; let password_is_valid = false; - if (this.state.email === '') { - this.setState({ + if (state.email === '') { + dispatch(registerActions.setRegister({ email_error_text: null, - }); - } else if (validateEmail(this.state.email)) { + })); + } else if (validateEmail(state.email)) { email_is_valid = true; - this.setState({ + dispatch(registerActions.setRegister({ email_error_text: null, - }); - + })); } else { - this.setState({ + dispatch(registerActions.setRegister({ email_error_text: 'Sorry, this is not a valid email', - }); + })); } - if (this.state.password === '' || !this.state.password) { - this.setState({ + if (state.password === '' || !state.password) { + dispatch(registerActions.setRegister({ password_error_text: null, - }); - } else if (this.state.password.length >= 6) { + })); + } else if (state.password.length >= 6) { password_is_valid = true; - this.setState({ + dispatch(registerActions.setRegister({ password_error_text: null, - }); + })); } else { - this.setState({ + dispatch(registerActions.setRegister({ password_error_text: 'Your password must be at least 6 characters', - }); - + })); } if (email_is_valid && password_is_valid) { - this.setState({ + dispatch(registerActions.setRegister({ disabled: false, - }); + })); } } - changeValue(e, type) { + const changeValue = (e, type) => { const value = e.target.value; const next_state = {}; next_state[type] = value; - this.setState(next_state, () => { - this.isDisabled(); - }); + dispatch(registerActions.setRegister(next_state)); + } + + const login = (e) => { + e.preventDefault(); + dispatch(authActions.registerUser(state.email, state.password, state.redirectTo)); } - _handleKeyPress(e) { + const _handleKeyPress = (e) => { if (e.key === 'Enter') { - if (!this.state.disabled) { + if (!state.disabled) { this.login(e); } } } - login(e) { - e.preventDefault(); - this.props.registerUser(this.state.email, this.state.password, this.state.redirectTo); - } - - render() { - return ( -
this._handleKeyPress(e)}> - -
-

Register to view protected content!

- { - this.props.registerStatusText && -
- {this.props.registerStatusText} -
- } - -
- this.changeValue(e, 'email')} - /> -
-
- this.changeValue(e, 'password')} - /> -
- - this.login(e)} + isDisabled(); + return ( +
_handleKeyPress(e)}> + +
+

Register to view protected content!

+ { + registerStatusText && +
+ {registerStatusText} +
+ } + +
+ changeValue(e, 'email')} + /> +
+
+ changeValue(e, 'password')} /> -
- -
- ); + login(e)} + /> + +
+ + +
+ ); - } } -RegisterView.propTypes = { - registerUser: React.PropTypes.func, - registerStatusText: React.PropTypes.string, -}; +export default RegisterView; +// RegisterView.propTypes = { +// registerUser: React.PropTypes.func, +// registerStatusText: React.PropTypes.string, +// }; diff --git a/static/src/components/notAuthenticatedComponent.js b/static/src/components/notAuthenticatedComponent.js index 1f37471..4c6729f 100644 --- a/static/src/components/notAuthenticatedComponent.js +++ b/static/src/components/notAuthenticatedComponent.js @@ -1,46 +1,26 @@ import React from 'react'; -import { connect } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { bindActionCreators } from 'redux'; -import { browserHistory } from 'react-router'; -import * as actionCreators from '../actions/auth'; - -function mapStateToProps(state) { - return { - token: state.auth.token, - userName: state.auth.userName, - isAuthenticated: state.auth.isAuthenticated, - }; -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators(actionCreators, dispatch); -} +import { useHistory } from 'react-router'; +import * as authActions from '../actions/auth'; +import * as optionActions from '../actions/option'; +import { useComponentDidMount } from '../utils/lifecycle_hook'; export function requireNoAuthentication(Component) { - - class notAuthenticatedComponent extends React.Component { - - constructor(props) { - super(props); - this.state = { - loaded: false, - }; - } - - componentWillMount() { - this.checkAuth(); - } - - componentWillReceiveProps(nextProps) { - this.checkAuth(nextProps); - } - - checkAuth(props = this.props) { - if (props.isAuthenticated) { - browserHistory.push('/main'); - - } else { + console.log('require no'); + + const ret = (props) => { + const isAuthenticated = useSelector(state => state.auth.isAuthenticated); + const loadIfNeeded = useSelector(state => state.option.loadIfNeeded); + const dispatch = useDispatch(); + + + useComponentDidMount(() => { + if(isAuthenticated) { + useHistory().push('/main'); + } + else { const token = localStorage.getItem('token'); if (token) { fetch('/api/is_token_valid', { @@ -52,43 +32,40 @@ export function requireNoAuthentication(Component) { }, body: JSON.stringify({ token }), }) - .then(res => { - if (res.status === 200) { - this.props.loginUserSuccess(token); - browserHistory.push('/main'); - - } else { - this.setState({ - loaded: true, - }); - } - }); - } else { - this.setState({ - loaded: true, + .then(res => { + if (res.status === 200) { + dispatch(authActions.loginUserSuccess(token)); + useHistory().push('/main'); + } else { + dispatch(optionActions.setLoadIfNeeded(true)); + } }); + } + else { + dispatch(optionActions.setLoadIfNeeded(true)); } } - } - - render() { - return ( -
- {!this.props.isAuthenticated && this.state.loaded - ? - : null - } -
- ); - - } + + }); + + console.log(isAuthenticated, loadIfNeeded); + return ( +
+ {!isAuthenticated && loadIfNeeded + ? + : null + } +
+ ); + } - notAuthenticatedComponent.propTypes = { - loginUserSuccess: React.PropTypes.func, - isAuthenticated: React.PropTypes.bool, - }; + return ret; + + // notAuthenticatedComponent.propTypes = { + // loginUserSuccess: React.PropTypes.func, + // isAuthenticated: React.PropTypes.bool, + // }; - return connect(mapStateToProps, mapDispatchToProps)(notAuthenticatedComponent); } diff --git a/static/src/constants/index.js b/static/src/constants/index.js index a77ca3d..5f9b6f3 100644 --- a/static/src/constants/index.js +++ b/static/src/constants/index.js @@ -9,3 +9,14 @@ export const REGISTER_USER_REQUEST = 'REGISTER_USER_REQUEST'; export const FETCH_PROTECTED_DATA_REQUEST = 'FETCH_PROTECTED_DATA_REQUEST'; export const RECEIVE_PROTECTED_DATA = 'RECEIVE_PROTECTED_DATA'; + +export const SET_SIDEBAR_OPEN = 'SET_SIDEBAR_OPEN'; +export const SET_LOAD_IF_NEEDED = 'SET_LOAD_IF_NEEDED'; + +export const SET_REGISTER = 'SET_REGISTER' +export const SET_REGISTER_EMAIL = 'SET_REGISTER_EMAIL' +export const SET_REGISTER_PASSWORD = 'SET_REGISTER_PASSWORD' +export const SET_REGISTER_EMAIL_ERROR_TEXT = 'SET_REGISTER_EMAIL_ERROR_TEXT' +export const SET_REGISTER_PASSWORD_EMAIL_TEXT = 'SET_REGISTER_PASSWORD_EMAIL_TEXT' +export const SET_REGISTER_REDIRECT_TO = 'SET_REGISTER_REDIRECT_TO' +export const SET_REGISTER_DISABLED = 'SET_REGISTER_DISABLED' diff --git a/static/src/containers/App/index.js b/static/src/containers/App/index.js index 92e9e53..9db0e5b 100644 --- a/static/src/containers/App/index.js +++ b/static/src/containers/App/index.js @@ -10,29 +10,22 @@ import { Footer } from '../../components/Footer'; /* global styles for app */ import './styles/app.scss'; -class App extends React.Component { // eslint-disable-line react/prefer-stateless-function - static propTypes = { - children: React.PropTypes.node, - }; - - render() { - return ( - -
-
-
- {this.props.children} -
-
-
-
-
-
- ); - } +export const App = (props) => { + return ( + +
+
+
+ {props.children} +
+
+
+
+
+
+ ); } -export { App }; diff --git a/static/src/index.js b/static/src/index.js index fc22bee..bf42030 100644 --- a/static/src/index.js +++ b/static/src/index.js @@ -1,26 +1,28 @@ +import { createBrowserHistory } from 'history'; import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; -import { Router, Redirect, browserHistory } from 'react-router'; -import injectTapEventPlugin from 'react-tap-event-plugin'; +import { BrowserRouter, Router, Redirect } from 'react-router'; import { syncHistoryWithStore } from 'react-router-redux'; import configureStore from './store/configureStore'; import routes from './routes'; import './style.scss'; +import { App } from './containers/App'; -require('expose?$!expose?jQuery!jquery'); -require('bootstrap-webpack'); +// require('expose?$!expose?jQuery!jquery'); +// require('bootstrap-webpack'); -injectTapEventPlugin(); const store = configureStore(); -const history = syncHistoryWithStore(browserHistory, store); +const bh = createBrowserHistory(); +const history = syncHistoryWithStore(bh, store); ReactDOM.render( - + {routes} + , document.getElementById('root') diff --git a/static/src/reducers/index.js b/static/src/reducers/index.js index 04de555..d59c04f 100644 --- a/static/src/reducers/index.js +++ b/static/src/reducers/index.js @@ -2,12 +2,16 @@ import { combineReducers } from 'redux'; import { routerReducer } from 'react-router-redux'; import auth from './auth'; import data from './data'; +import option from './option'; +import register from './register'; const rootReducer = combineReducers({ routing: routerReducer, /* your reducers */ auth, data, + option, + register }); export default rootReducer; diff --git a/static/src/reducers/option.js b/static/src/reducers/option.js new file mode 100644 index 0000000..58a10e6 --- /dev/null +++ b/static/src/reducers/option.js @@ -0,0 +1,19 @@ +import { SET_LOAD_IF_NEEDED, SET_SIDEBAR_OPEN } from '../constants'; +import { createReducer } from '../utils/misc'; + +const initialState = { + sideBarOpen: false, + loadIfNeeded: false +}; + +export default createReducer(initialState, { + [SET_SIDEBAR_OPEN]: (state, payload) => + Object.assign({}, state, { + sideBarOpen: payload.data, + }), + [SET_LOAD_IF_NEEDED]: (state, payload) => + Object.assign({}, state, { + test: true, + loadIfNeeded: payload.data, + }), +}); diff --git a/static/src/reducers/register.js b/static/src/reducers/register.js new file mode 100644 index 0000000..8dec5c5 --- /dev/null +++ b/static/src/reducers/register.js @@ -0,0 +1,16 @@ +import { SET_REGISTER } from '../constants'; +import { createReducer } from '../utils/misc'; + +const initialState = { + email: '', + password: '', + emailErrorText: null, + passwordErrorText: null, + redirectTo: '/login', + disabled: true, +}; + +export default createReducer(initialState, { + [SET_REGISTER]: (state, payload) => + Object.assign({}, state, payload.data), +}); diff --git a/static/src/routes.js b/static/src/routes.js index 356a291..e1cd51e 100644 --- a/static/src/routes.js +++ b/static/src/routes.js @@ -1,7 +1,7 @@ /* eslint new-cap: 0 */ import React from 'react'; -import { Route } from 'react-router'; +import { Route, Switch } from 'react-router'; /* containers */ import { App } from './containers/App'; @@ -17,12 +17,12 @@ import { requireAuthentication } from './components/AuthenticatedComponent'; import { requireNoAuthentication } from './components/notAuthenticatedComponent'; export default ( - - - - - - - - + + + + + + + {/* */} + ); diff --git a/static/src/store/configureStore.js b/static/src/store/configureStore.js index 61fbd2a..2fce97a 100644 --- a/static/src/store/configureStore.js +++ b/static/src/store/configureStore.js @@ -4,7 +4,7 @@ import rootReducer from '../reducers'; const debugware = []; if (process.env.NODE_ENV !== 'production') { - const createLogger = require('redux-logger'); + const {createLogger} = require('redux-logger'); debugware.push(createLogger({ collapsed: true, diff --git a/static/src/utils/lifecycle_hook.js b/static/src/utils/lifecycle_hook.js new file mode 100644 index 0000000..f95f321 --- /dev/null +++ b/static/src/utils/lifecycle_hook.js @@ -0,0 +1,25 @@ +import { useEffect, useRef } from "react"; + +export const useComponentDidMount = handler => { + return useEffect(() => { + return handler(); + }, []); +}; + +export const useComponentDidUpdate = (handler, deps) => { + const isInitialMount = useRef(true); + + useEffect(() => { + if (isInitialMount.current) { + isInitialMount.current = false; + + return; + } + + return handler(); + }, deps); +}; + +export const useComponentWillUnmount = handler => { + return useEffect(() => handler, []); +}; \ No newline at end of file diff --git a/static/webpack/common.config.js b/static/webpack/common.config.js index 65870d4..83e35cf 100755 --- a/static/webpack/common.config.js +++ b/static/webpack/common.config.js @@ -1,7 +1,8 @@ const path = require('path'); +const webpack = require('webpack'); const autoprefixer = require('autoprefixer'); const postcssImport = require('postcss-import'); -const merge = require('webpack-merge'); +const {merge} = require('webpack-merge'); const development = require('./dev.config'); const production = require('./prod.config'); @@ -29,54 +30,114 @@ const common = { resolve: { extensions: ['', '.jsx', '.js', '.json', '.scss'], - modulesDirectories: ['node_modules', PATHS.app], + modules: ['node_modules', PATHS.app], + }, + + infrastructureLogging: { + level: 'verbose', }, module: { - loaders: [{ - test: /bootstrap-sass\/assets\/javascripts\//, - loader: 'imports?jQuery=jquery', - }, { + rules: [ + // { + // test: /bootstrap-sass\/assets\/javascripts\//, + // use: [ + // { + // loader: 'imports-loader', + // } + // ] + // }, + { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url?limit=10000&mimetype=application/font-woff', + use: [ + { + loader: 'url-loader', + options: {limit: 10000, mimetype: 'application/font-woff'} + } + ] }, { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url?limit=10000&mimetype=application/font-woff2', + use: [ + { + loader: 'url-loader', + options: {limit: 10000, mimetype: 'application/font-woff2'} + } + ] }, { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url?limit=10000&mimetype=application/octet-stream', + use: [ + { + loader: 'url-loader', + options: {limit: 10000, mimetype: 'application/font-woff'} + } + ] }, { test: /\.otf(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url?limit=10000&mimetype=application/font-otf', + use: [ + { + loader: 'url-loader', + options: {limit: 10000, mimetype: 'application/font-otf'} + } + ] }, { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - loader: 'file', + use: [ + { + loader: 'file-loader', + } + ] }, { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - loader: 'url?limit=10000&mimetype=image/svg+xml', + use: [ + { + loader: 'url-loader', + options: {limit: 10000, mimetype: 'application/svg+xml'} + } + ] }, { test: /\.js$/, - loaders: ['babel-loader'], exclude: /node_modules/, + use: [ + { + loader: 'babel-loader' + } + ] }, { test: /\.png$/, - loader: 'file?name=[name].[ext]', + use: [ + { + loader: 'file-loader', + options: {name: '[name].[ext]'} + } + ] }, { test: /\.jpg$/, - loader: 'file?name=[name].[ext]', + use: [ + { + loader: 'file-loader', + options: {name: '[name].[ext]'} + } + ] }], }, - postcss: (webpack) => ( - [ - autoprefixer({ - browsers: ['last 2 versions'], - }), - postcssImport({ - addDependencyTo: webpack, - }), - ] - ), + plugins: [ + new webpack.LoaderOptionsPlugin({ + options: { + postcss: (webpack) => ( + [ + autoprefixer({ + browsers: ['last 2 versions'], + }), + postcssImport({ + addDependencyTo: webpack, + }), + ] + ), + } + }) + ] + }; if (TARGET === 'start' || !TARGET) { diff --git a/static/webpack/dev.config.js b/static/webpack/dev.config.js index 45fc206..3e5b87a 100755 --- a/static/webpack/dev.config.js +++ b/static/webpack/dev.config.js @@ -1,10 +1,11 @@ const webpack = require('webpack'); -const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { - devtool: 'cheap-module-eval-source-map', + mode: "development", + devtool: 'eval-cheap-module-source-map', entry: [ - 'bootstrap-loader', + // 'bootstrap-loader', 'webpack-hot-middleware/client', './src/index', ], @@ -13,9 +14,33 @@ module.exports = { }, module: { - loaders: [{ - test: /\.scss$/, - loader: 'style!css?localIdentName=[path][name]--[local]!postcss-loader!sass', + rules: [{ + test: /\.(scss)$/, + use: [{ + // inject CSS to page + loader: 'style-loader' + }, { + // translates CSS into CommonJS modules + loader: 'css-loader' + }, { + // Run postcss actions + loader: 'postcss-loader', + options: { + // `postcssOptions` is needed for postcss 8.x; + // if you use postcss 7.x skip the key + postcssOptions: { + // postcss plugins, can be exported to postcss.config.js + plugins: function () { + return [ + require('autoprefixer') + ]; + } + } + } + }, { + // compiles Sass to CSS + loader: 'sass-loader' + }] }], }, @@ -26,10 +51,9 @@ module.exports = { }, __DEVELOPMENT__: true, }), - new ExtractTextPlugin('bundle.css'), - new webpack.optimize.OccurenceOrderPlugin(), + new MiniCssExtractPlugin(), new webpack.HotModuleReplacementPlugin(), - new webpack.NoErrorsPlugin(), + new webpack.NoEmitOnErrorsPlugin(), new webpack.ProvidePlugin({ jQuery: 'jquery', }), diff --git a/static/webpack/prod.config.js b/static/webpack/prod.config.js index 6f76490..1697fd4 100755 --- a/static/webpack/prod.config.js +++ b/static/webpack/prod.config.js @@ -1,7 +1,10 @@ const webpack = require('webpack'); -const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const DuplicatePackageCheckerPlugin = require("duplicate-package-checker-webpack-plugin"); +const UglifyJsPlugin = require('uglifyjs-webpack-plugin') module.exports = { + mode: "production", devtool: 'source-map', entry: ['bootstrap-loader/extractStyles'], @@ -11,7 +14,7 @@ module.exports = { }, module: { - loaders: [{ + rules: [{ test: /\.scss$/, loader: 'style!css!postcss-loader!sass', }], @@ -24,13 +27,8 @@ module.exports = { }, __DEVELOPMENT__: false, }), - new ExtractTextPlugin('bundle.css'), - new webpack.optimize.DedupePlugin(), - new webpack.optimize.OccurenceOrderPlugin(), - new webpack.optimize.UglifyJsPlugin({ - compress: { - warnings: false, - }, - }), + new MiniCssExtractPlugin(), + new DuplicatePackageCheckerPlugin(), + new UglifyJsPlugin(), ], }; From 72ad435b8ff4dd312ba668166b93f6a83c7f9e89 Mon Sep 17 00:00:00 2001 From: wrk Date: Mon, 12 Jul 2021 23:22:56 +0800 Subject: [PATCH 5/6] register and main finished --- static/src/actions/auth.js | 19 +++---- static/src/actions/data.js | 28 +++++----- .../src/components/AuthenticatedComponent.js | 7 ++- static/src/components/ProtectedView.js | 24 +++++--- static/src/components/RegisterView.js | 55 +++++++++++-------- .../components/notAuthenticatedComponent.js | 1 - 6 files changed, 74 insertions(+), 60 deletions(-) diff --git a/static/src/actions/auth.js b/static/src/actions/auth.js index a992dc9..45f2798 100644 --- a/static/src/actions/auth.js +++ b/static/src/actions/auth.js @@ -1,5 +1,3 @@ -import { browserHistory } from 'react-router'; - import { LOGIN_USER_SUCCESS, LOGIN_USER_FAILURE, @@ -49,20 +47,20 @@ export function logout() { }; } -export function logoutAndRedirect() { +export function logoutAndRedirect(history) { return (dispatch) => { dispatch(logout()); - browserHistory.push('/'); + history.push('/'); }; } -export function redirectToRoute(route) { +export function redirectToRoute(route, history) { return () => { - browserHistory.push(route); + history.push(route); }; } -export function loginUser(email, password) { +export function loginUser(email, password, history) { return function (dispatch) { dispatch(loginUserRequest()); return get_token(email, password) @@ -70,7 +68,7 @@ export function loginUser(email, password) { .then(response => { try { dispatch(loginUserSuccess(response.token)); - browserHistory.push('/main'); + history.push('/main'); } catch (e) { alert(e); dispatch(loginUserFailure({ @@ -120,7 +118,7 @@ export function registerUserFailure(error) { }; } -export function registerUser(email, password) { +export function registerUser(email, password, history) { return function (dispatch) { dispatch(registerUserRequest()); return create_user(email, password) @@ -128,8 +126,9 @@ export function registerUser(email, password) { .then(response => { try { dispatch(registerUserSuccess(response.token)); - browserHistory.push('/main'); + history.push('/main'); } catch (e) { + console.log(e); dispatch(registerUserFailure({ response: { status: 403, diff --git a/static/src/actions/data.js b/static/src/actions/data.js index 0c4f7e1..d263239 100644 --- a/static/src/actions/data.js +++ b/static/src/actions/data.js @@ -18,18 +18,18 @@ export function fetchProtectedDataRequest() { }; } -export function fetchProtectedData(token) { - return (dispatch) => { - dispatch(fetchProtectedDataRequest()); - data_about_user(token) - .then(parseJSON) - .then(response => { - dispatch(receiveProtectedData(response.result)); - }) - .catch(error => { - if (error.status === 401) { - dispatch(logoutAndRedirect(error)); - } - }); - }; +export function fetchProtectedData(token, dispatch) { + console.log('fetchprotecteddata'); + dispatch(fetchProtectedDataRequest()); + data_about_user(token) + .then(parseJSON) + .then(response => { + dispatch(receiveProtectedData(response.result)); + }) + .catch(error => { + console.log(error) + if (error.status === 401) { + dispatch(logoutAndRedirect(error)); + } + }); } diff --git a/static/src/components/AuthenticatedComponent.js b/static/src/components/AuthenticatedComponent.js index 0c68af2..5bf5876 100755 --- a/static/src/components/AuthenticatedComponent.js +++ b/static/src/components/AuthenticatedComponent.js @@ -1,5 +1,5 @@ import React from 'react'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router'; import { useComponentDidMount } from '../utils/lifecycle_hook'; import * as authActions from '../actions/auth'; @@ -12,12 +12,13 @@ export const requireAuthentication = (Component) => { const isAuthenticated = useSelector(state => state.auth.isAuthenticated); const loadIfNeeded = useSelector(state => state.option.loadIfNeeded); const dispatch = useDispatch(); + const history = useHistory(); useComponentDidMount(() => { if (!isAuthenticated) { const token = localStorage.getItem('token'); if (!token) { - useHistory().push('/home'); + history.push('/home'); } else { fetch('/api/is_token_valid', { method: 'post', @@ -35,7 +36,7 @@ export const requireAuthentication = (Component) => { dispatch(optionActions.setLoadIfNeeded(true)); } else { console.log('invalid'); - useHistory().push('/home'); + history.push('/home'); } }); diff --git a/static/src/components/ProtectedView.js b/static/src/components/ProtectedView.js index 2c40417..b232af2 100755 --- a/static/src/components/ProtectedView.js +++ b/static/src/components/ProtectedView.js @@ -1,26 +1,32 @@ import React from 'react'; -import { useSelector } from 'react-redux'; -import { useComponentDidMount } from '../utils/lifecycle_hook'; +import { useDispatch, useSelector } from 'react-redux'; +import { useComponentDidMount, useComponentDidUpdate } from '../utils/lifecycle_hook'; +import * as dataActions from '../actions/data'; const ProtectedView = (props) => { console.log('protectedView'); - const fetchData = () => { - const token = useSelector(state => state.data.token); - props.fetchProtectedData(token); - } + const data = useSelector(state => state.data); + console.log(data); + const token = useSelector(state => state.auth.token); + const loaded = data.loaded; + const email = data.data ? data.data.email : ""; + const dispatch = useDispatch(); - useComponentDidMount(() => {fetchData()}); + useComponentDidMount(() => { + console.log("did mount"); + dispatch(() => dataActions.fetchProtectedData(token, dispatch)); + }); return (
- {!useSelector(state => state.data.loaded) + {!loaded ?

Loading data...

:

Welcome back, {props.userName}!

-

{useSelector(state => state.data.data.email)}

+

{email}

}
diff --git a/static/src/components/RegisterView.js b/static/src/components/RegisterView.js index 35ae5bf..c83d2c1 100644 --- a/static/src/components/RegisterView.js +++ b/static/src/components/RegisterView.js @@ -1,8 +1,7 @@ /* eslint camelcase: 0, no-underscore-dangle: 0 */ import React from 'react'; -import { bindActionCreators } from 'redux'; -import { connect, useDispatch, useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import TextField from 'material-ui/TextField'; import RaisedButton from 'material-ui/RaisedButton'; import Paper from 'material-ui/Paper'; @@ -11,6 +10,7 @@ import * as authActions from '../actions/auth'; import * as registerActions from '../actions/register'; import { validateEmail } from '../utils/misc'; +import { useHistory } from 'react-router'; const style = { marginTop: 50, @@ -22,48 +22,57 @@ const style = { }; const RegisterView = (props) => { + console.log("register view"); const state = useSelector(state => state.register); - const dispatch = useDispatch; + const dispatch = useDispatch(); const registerStatusText = useSelector(state => state.auth.registerStatusText); const isRegistering = useSelector(state => state.auth.isRegistering); + const history = useHistory(); const isDisabled = () => { let email_is_valid = false; let password_is_valid = false; if (state.email === '') { - dispatch(registerActions.setRegister({ - email_error_text: null, - })); + if(state.email_error_text!=null) + dispatch(registerActions.setRegister({ + email_error_text: null, + })); } else if (validateEmail(state.email)) { email_is_valid = true; - dispatch(registerActions.setRegister({ - email_error_text: null, - })); + if(state.email_error_text!=null) + dispatch(registerActions.setRegister({ + email_error_text: null, + })); } else { - dispatch(registerActions.setRegister({ - email_error_text: 'Sorry, this is not a valid email', - })); + if(state.email_error_text==null) + dispatch(registerActions.setRegister({ + email_error_text: 'Sorry, this is not a valid email', + })); } if (state.password === '' || !state.password) { - dispatch(registerActions.setRegister({ - password_error_text: null, - })); + if(state.password_error_text!=null) + dispatch(registerActions.setRegister({ + password_error_text: null, + })); } else if (state.password.length >= 6) { password_is_valid = true; - dispatch(registerActions.setRegister({ - password_error_text: null, - })); + if(state.password_error_text!=null) + dispatch(registerActions.setRegister({ + password_error_text: null, + })); } else { - dispatch(registerActions.setRegister({ - password_error_text: 'Your password must be at least 6 characters', - })); + if(state.password_error_text==null) + dispatch(registerActions.setRegister({ + password_error_text: 'Your password must be at least 6 characters', + })); } if (email_is_valid && password_is_valid) { - dispatch(registerActions.setRegister({ + if(state.disabled) + dispatch(registerActions.setRegister({ disabled: false, })); } @@ -79,7 +88,7 @@ const RegisterView = (props) => { const login = (e) => { e.preventDefault(); - dispatch(authActions.registerUser(state.email, state.password, state.redirectTo)); + dispatch(authActions.registerUser(state.email, state.password, history)); } const _handleKeyPress = (e) => { diff --git a/static/src/components/notAuthenticatedComponent.js b/static/src/components/notAuthenticatedComponent.js index 4c6729f..7a3fb80 100644 --- a/static/src/components/notAuthenticatedComponent.js +++ b/static/src/components/notAuthenticatedComponent.js @@ -15,7 +15,6 @@ export function requireNoAuthentication(Component) { const loadIfNeeded = useSelector(state => state.option.loadIfNeeded); const dispatch = useDispatch(); - useComponentDidMount(() => { if(isAuthenticated) { useHistory().push('/main'); From d4ea4e8d1f94e13d14d1e645c43ed3007899afa7 Mon Sep 17 00:00:00 2001 From: wrk Date: Tue, 13 Jul 2021 10:14:52 +0800 Subject: [PATCH 6/6] finished --- static/package.json | 2 +- static/src/actions/auth.js | 12 +- static/src/actions/data.js | 2 - .../src/components/AuthenticatedComponent.js | 5 - static/src/components/DetermineAuth.js | 2 - static/src/components/Header/index.js | 3 +- static/src/components/LoginView.js | 207 ++++++++---------- static/src/components/ProtectedView.js | 3 - static/src/components/RegisterView.js | 30 ++- .../components/notAuthenticatedComponent.js | 2 - static/src/constants/index.js | 2 + static/src/reducers/auth.js | 10 + static/src/routes.js | 3 +- 13 files changed, 136 insertions(+), 147 deletions(-) diff --git a/static/package.json b/static/package.json index 7eeefeb..ce46c5e 100644 --- a/static/package.json +++ b/static/package.json @@ -56,7 +56,7 @@ "express-open-in-editor": "^3.1.1", "file-loader": "^6.2.0", "gapi": "0.0.3", - "history": "^5.0.0", + "history": "^4.10.1", "http-proxy": "^1.18.1", "imports-loader": "^3.0.0", "jasmine-core": "^3.8.0", diff --git a/static/src/actions/auth.js b/static/src/actions/auth.js index 45f2798..b858817 100644 --- a/static/src/actions/auth.js +++ b/static/src/actions/auth.js @@ -6,11 +6,22 @@ import { REGISTER_USER_FAILURE, REGISTER_USER_REQUEST, REGISTER_USER_SUCCESS, + SET_AUTH, } from '../constants/index'; import { parseJSON } from '../utils/misc'; import { get_token, create_user } from '../utils/http_functions'; +export function setAuth(data) { + return { + type: SET_AUTH, + payload: { + data: data, + }, + }; +} + + export function loginUserSuccess(token) { localStorage.setItem('token', token); @@ -128,7 +139,6 @@ export function registerUser(email, password, history) { dispatch(registerUserSuccess(response.token)); history.push('/main'); } catch (e) { - console.log(e); dispatch(registerUserFailure({ response: { status: 403, diff --git a/static/src/actions/data.js b/static/src/actions/data.js index d263239..b3ef7b9 100644 --- a/static/src/actions/data.js +++ b/static/src/actions/data.js @@ -19,7 +19,6 @@ export function fetchProtectedDataRequest() { } export function fetchProtectedData(token, dispatch) { - console.log('fetchprotecteddata'); dispatch(fetchProtectedDataRequest()); data_about_user(token) .then(parseJSON) @@ -27,7 +26,6 @@ export function fetchProtectedData(token, dispatch) { dispatch(receiveProtectedData(response.result)); }) .catch(error => { - console.log(error) if (error.status === 401) { dispatch(logoutAndRedirect(error)); } diff --git a/static/src/components/AuthenticatedComponent.js b/static/src/components/AuthenticatedComponent.js index 5bf5876..b8157bb 100755 --- a/static/src/components/AuthenticatedComponent.js +++ b/static/src/components/AuthenticatedComponent.js @@ -6,7 +6,6 @@ import * as authActions from '../actions/auth'; import * as optionActions from '../actions/option'; export const requireAuthentication = (Component) => { - console.log("require"); const ret = (props) => { const isAuthenticated = useSelector(state => state.auth.isAuthenticated); @@ -31,11 +30,9 @@ export const requireAuthentication = (Component) => { }) .then(res => { if (res.status === 200) { - console.log('200'); dispatch(authActions.loginUserSuccess(token)); dispatch(optionActions.setLoadIfNeeded(true)); } else { - console.log('invalid'); history.push('/home'); } }); @@ -46,8 +43,6 @@ export const requireAuthentication = (Component) => { } }) - console.log("isAuthenticated: ", isAuthenticated); - console.log("loadIfNeeded: ", loadIfNeeded); return (
diff --git a/static/src/components/DetermineAuth.js b/static/src/components/DetermineAuth.js index 49e8206..ae6f796 100644 --- a/static/src/components/DetermineAuth.js +++ b/static/src/components/DetermineAuth.js @@ -6,7 +6,6 @@ import { useComponentDidMount } from '../utils/lifecycle_hook'; export function DetermineAuth(Component) { - console.log("determine"); const ret = (props) => { const dispatch = useDispatch(); const loadIfNeeded = useSelector(state => state.option.loadIfNeeded) @@ -38,7 +37,6 @@ export function DetermineAuth(Component) { } }); - console.log(loadIfNeeded); return (
diff --git a/static/src/components/Header/index.js b/static/src/components/Header/index.js index 2a31231..e8d5fc8 100644 --- a/static/src/components/Header/index.js +++ b/static/src/components/Header/index.js @@ -37,7 +37,7 @@ export const Header = () => { const logout = (e) => { e.preventDefault(); - dispatch(authActions.logoutAndRedirect()); + dispatch(authActions.logoutAndRedirect(history)); dispatch(optionActions.setSideBarOpen(false)); }; @@ -45,7 +45,6 @@ export const Header = () => { dispatch(optionActions.setSideBarOpen(true)); }; - console.log("sideBarOpen", sideBarOpen); return (
diff --git a/static/src/components/LoginView.js b/static/src/components/LoginView.js index 9aa352b..b0b4ac4 100755 --- a/static/src/components/LoginView.js +++ b/static/src/components/LoginView.js @@ -2,24 +2,13 @@ import React from 'react'; import { bindActionCreators } from 'redux'; -import { connect } from 'react-redux'; +import { connect, useDispatch, useSelector } from 'react-redux'; import TextField from 'material-ui/TextField'; import RaisedButton from 'material-ui/RaisedButton'; import Paper from 'material-ui/Paper'; -import * as actionCreators from '../actions/auth'; +import * as authActions from '../actions/auth'; import { validateEmail } from '../utils/misc'; - -function mapStateToProps(state) { - return { - isAuthenticating: state.auth.isAuthenticating, - statusText: state.auth.statusText, - }; -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators(actionCreators, dispatch); -} - +import { useHistory } from 'react-router'; const style = { marginTop: 50, @@ -30,138 +19,132 @@ const style = { display: 'inline-block', }; -@connect(mapStateToProps, mapDispatchToProps) -export default class LoginView extends React.Component { - - constructor(props) { - super(props); - const redirectRoute = '/login'; - this.state = { - email: '', - password: '', - email_error_text: null, - password_error_text: null, - redirectTo: redirectRoute, - disabled: true, - }; - } +const LoginView = (props) => { - isDisabled() { + const state = useSelector(state => state.auth); + const dispatch = useDispatch(); + const history = useHistory(); + + const isDisabled = () => { let email_is_valid = false; let password_is_valid = false; - if (this.state.email === '') { - this.setState({ - email_error_text: null, - }); - } else if (validateEmail(this.state.email)) { + if (state.email === '') { + if(state.email_error_text!=null) + dispatch(authActions.setAuth({ + email_error_text: null, + })); + } else if (validateEmail(state.email)) { email_is_valid = true; - this.setState({ - email_error_text: null, - }); - + if(state.email_error_text!=null) + dispatch(authActions.setAuth({ + email_error_text: null, + })); } else { - this.setState({ - email_error_text: 'Sorry, this is not a valid email', - }); + if(state.email_error_text==null) + dispatch(authActions.setAuth({ + email_error_text: 'Sorry, this is not a valid email', + })); } - if (this.state.password === '' || !this.state.password) { - this.setState({ - password_error_text: null, - }); - } else if (this.state.password.length >= 6) { + if (state.password === '' || !state.password) { + if(state.password_error_text!=null) + dispatch(authActions.setAuth({ + password_error_text: null, + })); + } else if (state.password.length >= 6) { password_is_valid = true; - this.setState({ - password_error_text: null, - }); + if(state.password_error_text!=null) + dispatch(authActions.setAuth({ + password_error_text: null, + })); } else { - this.setState({ - password_error_text: 'Your password must be at least 6 characters', - }); - + if(state.password_error_text==null) + dispatch(authActions.setAuth({ + password_error_text: 'Your password must be at least 6 characters', + })); } if (email_is_valid && password_is_valid) { - this.setState({ + if(state.disabled) + dispatch(authActions.setAuth({ disabled: false, - }); + })); } } - changeValue(e, type) { + const changeValue = (e, type) => { const value = e.target.value; const next_state = {}; next_state[type] = value; - this.setState(next_state, () => { - this.isDisabled(); - }); + dispatch(authActions.setAuth(next_state)); + } + + const login = (e) => { + e.preventDefault(); + dispatch(authActions.loginUser(state.email, state.password, history)); } - _handleKeyPress(e) { + const _handleKeyPress = (e) => { if (e.key === 'Enter') { - if (!this.state.disabled) { - this.login(e); + if (!state.disabled) { + login(e); } } } - login(e) { - e.preventDefault(); - this.props.loginUser(this.state.email, this.state.password, this.state.redirectTo); - } - - render() { - return ( -
this._handleKeyPress(e)}> - -
-
-

Login to view protected content!

- { - this.props.statusText && -
- {this.props.statusText} -
- } - -
- this.changeValue(e, 'email')} - /> -
-
- this.changeValue(e, 'password')} - /> -
- - this.login(e)} + isDisabled(); + return ( +
_handleKeyPress(e)}> + + +
+

Login to view protected content!

+ { + state.statusText && +
+ {state.statusText} +
+ } + +
+ changeValue(e, 'email')} + /> +
+
+ changeValue(e, 'password')} /> -
- - -
- ); + login(e)} + /> + +
+ + + +
+ ); - } } +export default LoginView; + // LoginView.propTypes = { // loginUser: React.PropTypes.func, // statusText: React.PropTypes.string, diff --git a/static/src/components/ProtectedView.js b/static/src/components/ProtectedView.js index b232af2..79beb5c 100755 --- a/static/src/components/ProtectedView.js +++ b/static/src/components/ProtectedView.js @@ -5,16 +5,13 @@ import * as dataActions from '../actions/data'; const ProtectedView = (props) => { - console.log('protectedView'); const data = useSelector(state => state.data); - console.log(data); const token = useSelector(state => state.auth.token); const loaded = data.loaded; const email = data.data ? data.data.email : ""; const dispatch = useDispatch(); useComponentDidMount(() => { - console.log("did mount"); dispatch(() => dataActions.fetchProtectedData(token, dispatch)); }); diff --git a/static/src/components/RegisterView.js b/static/src/components/RegisterView.js index c83d2c1..443e30f 100644 --- a/static/src/components/RegisterView.js +++ b/static/src/components/RegisterView.js @@ -24,10 +24,8 @@ const style = { const RegisterView = (props) => { console.log("register view"); - const state = useSelector(state => state.register); + const state = useSelector(state => state.auth); const dispatch = useDispatch(); - const registerStatusText = useSelector(state => state.auth.registerStatusText); - const isRegistering = useSelector(state => state.auth.isRegistering); const history = useHistory(); const isDisabled = () => { @@ -36,43 +34,43 @@ const RegisterView = (props) => { if (state.email === '') { if(state.email_error_text!=null) - dispatch(registerActions.setRegister({ + dispatch(authActions.setAuth({ email_error_text: null, })); } else if (validateEmail(state.email)) { email_is_valid = true; if(state.email_error_text!=null) - dispatch(registerActions.setRegister({ + dispatch(authActions.setAuth({ email_error_text: null, })); } else { if(state.email_error_text==null) - dispatch(registerActions.setRegister({ + dispatch(authActions.setAuth({ email_error_text: 'Sorry, this is not a valid email', })); } if (state.password === '' || !state.password) { if(state.password_error_text!=null) - dispatch(registerActions.setRegister({ + dispatch(authActions.setAuth({ password_error_text: null, })); } else if (state.password.length >= 6) { password_is_valid = true; if(state.password_error_text!=null) - dispatch(registerActions.setRegister({ + dispatch(authActions.setAuth({ password_error_text: null, })); } else { if(state.password_error_text==null) - dispatch(registerActions.setRegister({ + dispatch(authActions.setAuth({ password_error_text: 'Your password must be at least 6 characters', })); } if (email_is_valid && password_is_valid) { if(state.disabled) - dispatch(registerActions.setRegister({ + dispatch(authActions.setAuth({ disabled: false, })); } @@ -83,10 +81,10 @@ const RegisterView = (props) => { const value = e.target.value; const next_state = {}; next_state[type] = value; - dispatch(registerActions.setRegister(next_state)); + dispatch(authActions.setAuth(next_state)); } - const login = (e) => { + const register = (e) => { e.preventDefault(); dispatch(authActions.registerUser(state.email, state.password, history)); } @@ -94,7 +92,7 @@ const RegisterView = (props) => { const _handleKeyPress = (e) => { if (e.key === 'Enter') { if (!state.disabled) { - this.login(e); + register(e); } } } @@ -106,9 +104,9 @@ const RegisterView = (props) => {

Register to view protected content!

{ - registerStatusText && + state.registerStatusText &&
- {registerStatusText} + {state.registerStatusText}
} @@ -135,7 +133,7 @@ const RegisterView = (props) => { disabled={state.disabled} style={{ marginTop: 50 }} label="Submit" - onClick={(e) => login(e)} + onClick={(e) => register(e)} />
diff --git a/static/src/components/notAuthenticatedComponent.js b/static/src/components/notAuthenticatedComponent.js index 7a3fb80..22b4b9a 100644 --- a/static/src/components/notAuthenticatedComponent.js +++ b/static/src/components/notAuthenticatedComponent.js @@ -8,7 +8,6 @@ import { useComponentDidMount } from '../utils/lifecycle_hook'; export function requireNoAuthentication(Component) { - console.log('require no'); const ret = (props) => { const isAuthenticated = useSelector(state => state.auth.isAuthenticated); @@ -47,7 +46,6 @@ export function requireNoAuthentication(Component) { }); - console.log(isAuthenticated, loadIfNeeded); return (
{!isAuthenticated && loadIfNeeded diff --git a/static/src/constants/index.js b/static/src/constants/index.js index 5f9b6f3..c8454fc 100644 --- a/static/src/constants/index.js +++ b/static/src/constants/index.js @@ -13,6 +13,8 @@ export const RECEIVE_PROTECTED_DATA = 'RECEIVE_PROTECTED_DATA'; export const SET_SIDEBAR_OPEN = 'SET_SIDEBAR_OPEN'; export const SET_LOAD_IF_NEEDED = 'SET_LOAD_IF_NEEDED'; +export const SET_AUTH = 'SET_AUTH'; + export const SET_REGISTER = 'SET_REGISTER' export const SET_REGISTER_EMAIL = 'SET_REGISTER_EMAIL' export const SET_REGISTER_PASSWORD = 'SET_REGISTER_PASSWORD' diff --git a/static/src/reducers/auth.js b/static/src/reducers/auth.js index 545d769..1826c15 100644 --- a/static/src/reducers/auth.js +++ b/static/src/reducers/auth.js @@ -9,6 +9,7 @@ import { REGISTER_USER_FAILURE, REGISTER_USER_REQUEST, REGISTER_USER_SUCCESS, + SET_AUTH, } from '../constants/index'; const initialState = { @@ -20,6 +21,13 @@ const initialState = { isRegistering: false, isRegistered: false, registerStatusText: null, + + email: '', + password: '', + emailErrorText: null, + passwordErrorText: null, + redirectTo: '/login', + disabled: true, }; export default createReducer(initialState, { @@ -71,4 +79,6 @@ export default createReducer(initialState, { userName: null, registerStatusText: `Register Error: ${payload.status} ${payload.statusText}`, }), + [SET_AUTH]: (state, payload) => + Object.assign({}, state, payload.data), }); diff --git a/static/src/routes.js b/static/src/routes.js index e1cd51e..9b8c801 100644 --- a/static/src/routes.js +++ b/static/src/routes.js @@ -1,7 +1,7 @@ /* eslint new-cap: 0 */ import React from 'react'; -import { Route, Switch } from 'react-router'; +import { Route, Switch, Redirect } from 'react-router'; /* containers */ import { App } from './containers/App'; @@ -23,6 +23,7 @@ export default ( + {/* */} );