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() 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 37808c3..ce46c5e 100644 --- a/static/package.json +++ b/static/package.json @@ -29,92 +29,93 @@ "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-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-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", + "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", - "extract-text-webpack-plugin": "^1.0.1", - "file-loader": "^0.9.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", + "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": "^4.10.1", + "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": {} + "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/auth.js b/static/src/actions/auth.js index a992dc9..b858817 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, @@ -8,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); @@ -49,20 +58,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 +79,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 +129,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,7 +137,7 @@ export function registerUser(email, password) { .then(response => { try { dispatch(registerUserSuccess(response.token)); - browserHistory.push('/main'); + history.push('/main'); } catch (e) { dispatch(registerUserFailure({ response: { diff --git a/static/src/actions/data.js b/static/src/actions/data.js index 0c4f7e1..b3ef7b9 100644 --- a/static/src/actions/data.js +++ b/static/src/actions/data.js @@ -18,18 +18,16 @@ 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) { + dispatch(fetchProtectedDataRequest()); + data_about_user(token) + .then(parseJSON) + .then(response => { + dispatch(receiveProtectedData(response.result)); + }) + .catch(error => { + if (error.status === 401) { + dispatch(logoutAndRedirect(error)); + } + }); } 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..b8157bb 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, useSelector } 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) => { + + const ret = (props) => { + const isAuthenticated = useSelector(state => state.auth.isAuthenticated); + const loadIfNeeded = useSelector(state => state.option.loadIfNeeded); + const dispatch = useDispatch(); + const history = useHistory(); - checkAuth(props = this.props) { - if (!props.isAuthenticated) { + useComponentDidMount(() => { + if (!isAuthenticated) { const token = localStorage.getItem('token'); if (!token) { - browserHistory.push('/home'); + history.push('/home'); } else { fetch('/api/is_token_valid', { method: 'post', @@ -45,44 +28,38 @@ 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) { + dispatch(authActions.loginUserSuccess(token)); + dispatch(optionActions.setLoadIfNeeded(true)); + } else { + history.push('/home'); + } + }); + } } else { - this.setState({ - loaded_if_needed: true, - }); + dispatch(optionActions.setLoadIfNeeded(true)); } - } + }) - render() { - return ( -
- {this.props.isAuthenticated && this.state.loaded_if_needed - ? - : null - } -
- ); - } + 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..ae6f796 100644 --- a/static/src/components/DetermineAuth.js +++ b/static/src/components/DetermineAuth.js @@ -1,38 +1,18 @@ 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) { + const ret = (props) => { + const dispatch = useDispatch(); + const loadIfNeeded = useSelector(state => state.option.loadIfNeeded) + const isAuthenticated = useSelector(state => state.auth.isAuthenticated); - 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) { + useComponentDidMount(() => { + if (!isAuthenticated) { const token = localStorage.getItem('token'); if (token) { fetch('/api/is_token_valid', { @@ -44,41 +24,36 @@ 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 - } -
- ); - } + 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..e8d5fc8 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,69 @@ 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, - }; - - } +export const Header = () => { + const history = useHistory(); + const dispatch = useDispatch(); + const sideBarOpen = useSelector((state) => (state.option.sideBarOpen)); + const isAuthenticated = useSelector((state) => (state.auth.isAuthenticated)); - dispatchNewRoute(route) { - browserHistory.push(route); - this.setState({ - open: false, - }); + 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(history)); + 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 - - - this.logout(e)}> - Logout - -
- } -
- this.openNav()} - iconElementRight={ - this.dispatchNewRoute('/')} /> - } - /> -
+ return ( +
+ + { + !isAuthenticated ? +
+ dispatchNewRoute('/login')}> + Login + + dispatchNewRoute('/register')}> + Register + +
+ : +
+ dispatchNewRoute('/analytics')}> + Analytics + + + + logout(e)}> + Logout + +
+ } +
+ openNav()} + iconElementRight={ + 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..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,139 +19,133 @@ 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)} + /> + +
+ + + +
+ ); - } } -LoginView.propTypes = { - loginUser: React.PropTypes.func, - statusText: React.PropTypes.string, -}; +export default LoginView; + +// 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..79beb5c 100755 --- a/static/src/components/ProtectedView.js +++ b/static/src/components/ProtectedView.js @@ -1,55 +1,41 @@ 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, - }; -} - - -function mapDispatchToProps(dispatch) { - return bindActionCreators(actionCreators, dispatch); +import { useDispatch, useSelector } from 'react-redux'; +import { useComponentDidMount, useComponentDidUpdate } from '../utils/lifecycle_hook'; +import * as dataActions from '../actions/data'; + + +const ProtectedView = (props) => { + const data = useSelector(state => state.data); + const token = useSelector(state => state.auth.token); + const loaded = data.loaded; + const email = data.data ? data.data.email : ""; + const dispatch = useDispatch(); + + useComponentDidMount(() => { + dispatch(() => dataActions.fetchProtectedData(token, dispatch)); + }); + + return ( +
+ {!loaded + ?

Loading data...

+ : +
+

Welcome back, + {props.userName}!

+

{email}

+
+ } +
+ ); } -@connect(mapStateToProps, mapDispatchToProps) -export default class ProtectedView extends React.Component { - componentDidMount() { - this.fetchData(); - } - - - 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}

-
- } -
- ); - } -} +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, -}; +// 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..443e30f 100644 --- a/static/src/components/RegisterView.js +++ b/static/src/components/RegisterView.js @@ -1,26 +1,16 @@ /* eslint camelcase: 0, no-underscore-dangle: 0 */ import React from 'react'; -import { bindActionCreators } from 'redux'; -import { connect } 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'; -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); -} +import { useHistory } from 'react-router'; const style = { marginTop: 50, @@ -31,137 +21,131 @@ 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) => { + console.log("register view"); + + const state = useSelector(state => state.auth); + const dispatch = useDispatch(); + const history = useHistory(); - isDisabled() { + 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 register = (e) => { + e.preventDefault(); + dispatch(authActions.registerUser(state.email, state.password, history)); } - _handleKeyPress(e) { + const _handleKeyPress = (e) => { if (e.key === 'Enter') { - if (!this.state.disabled) { - this.login(e); + if (!state.disabled) { + register(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!

+ { + state.registerStatusText && +
+ {state.registerStatusText} +
+ } + +
+ changeValue(e, 'email')} + /> +
+
+ changeValue(e, 'password')} /> -
- -
- ); + register(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..22b4b9a 100644 --- a/static/src/components/notAuthenticatedComponent.js +++ b/static/src/components/notAuthenticatedComponent.js @@ -1,46 +1,24 @@ 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); - } + 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) { - browserHistory.push('/main'); - - } else { + useComponentDidMount(() => { + if(isAuthenticated) { + useHistory().push('/main'); + } + else { const token = localStorage.getItem('token'); if (token) { fetch('/api/is_token_valid', { @@ -52,43 +30,39 @@ 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 - } -
- ); - - } + + }); + + 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..c8454fc 100644 --- a/static/src/constants/index.js +++ b/static/src/constants/index.js @@ -9,3 +9,16 @@ 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_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' +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/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/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..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 } from 'react-router'; +import { Route, Switch, Redirect } from 'react-router'; /* containers */ import { App } from './containers/App'; @@ -17,12 +17,13 @@ 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/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) { 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(), ], };