From 628c4dfd9b4f0d0dd6dda124812b6d58d9f2f785 Mon Sep 17 00:00:00 2001 From: Yining Shi Date: Sat, 11 Mar 2017 00:48:25 -0500 Subject: [PATCH 1/8] added account page showing username and email --- client/components/Nav.jsx | 5 ++ .../modules/User/components/AccountForm.jsx | 84 +++++++++++++++++++ client/modules/User/pages/AccountView.jsx | 81 ++++++++++++++++++ client/routes.jsx | 2 + 4 files changed, 172 insertions(+) create mode 100644 client/modules/User/components/AccountForm.jsx create mode 100644 client/modules/User/pages/AccountView.jsx diff --git a/client/components/Nav.jsx b/client/components/Nav.jsx index c0f9778a72..82b73274d9 100644 --- a/client/components/Nav.jsx +++ b/client/components/Nav.jsx @@ -136,6 +136,11 @@ class Nav extends React.PureComponent { My sketches +
  • + + My account + +
  • + + +
    +

    My Account

    + + {/*

    Or

    + */} +
    + + ); + } +} + +function mapStateToProps(state) { + return { + user: state.user, + previousPath: state.ide.previousPath + }; +} + +function mapDispatchToProps() { + return { + validateAndLoginUser + }; +} + +function validate(formProps) { + const errors = {}; + if (!formProps.email) { + errors.email = 'Please enter an email'; + } + if (!formProps.password) { + errors.password = 'Please enter a password'; + } + return errors; +} + +AccountView.propTypes = { + previousPath: PropTypes.string.isRequired +}; + +export default reduxForm({ + form: 'login', + fields: ['currentPassword', 'newPassword'], + validate +}, mapStateToProps, mapDispatchToProps)(AccountView); diff --git a/client/routes.jsx b/client/routes.jsx index 5015917511..3ceaa71397 100644 --- a/client/routes.jsx +++ b/client/routes.jsx @@ -7,6 +7,7 @@ import LoginView from './modules/User/pages/LoginView'; import SignupView from './modules/User/pages/SignupView'; import ResetPasswordView from './modules/User/pages/ResetPasswordView'; import NewPasswordView from './modules/User/pages/NewPasswordView'; +import AccountView from './modules/User/pages/AccountView'; // import SketchListView from './modules/Sketch/pages/SketchListView'; import { getUser } from './modules/User/actions'; @@ -27,6 +28,7 @@ const routes = store => + ); From b0363810c6787c26897f219e063686b76b61e31a Mon Sep 17 00:00:00 2001 From: Yining Shi Date: Wed, 15 Mar 2017 02:13:33 -0400 Subject: [PATCH 2/8] change username and email --- client/modules/User/actions.js | 18 +++++ .../modules/User/components/AccountForm.jsx | 30 ++++---- client/modules/User/pages/AccountView.jsx | 70 +++++++++++++++---- server/controllers/user.controller.js | 25 +++++++ server/routes/user.routes.js | 2 + 5 files changed, 116 insertions(+), 29 deletions(-) diff --git a/client/modules/User/actions.js b/client/modules/User/actions.js index 89818ee85e..948bed20b5 100644 --- a/client/modules/User/actions.js +++ b/client/modules/User/actions.js @@ -160,3 +160,21 @@ export function updatePassword(token, formValues) { })); }; } + +export function saveSettingsSuccess(user) { + return { + type: ActionTypes.AUTH_USER, + user + }; +} + +export function updateSettings(formValues) { + return (dispatch) => { + axios.put(`${ROOT_URL}/account`, formValues, { withCredentials: true }) + .then((response) => { + dispatch(saveSettingsSuccess(response.data)); + browserHistory.push('/'); + }) + .catch(response => dispatch(authError(response.data.error))); + }; +} diff --git a/client/modules/User/components/AccountForm.jsx b/client/modules/User/components/AccountForm.jsx index 2015a34cc7..0c7fd61ddd 100644 --- a/client/modules/User/components/AccountForm.jsx +++ b/client/modules/User/components/AccountForm.jsx @@ -3,14 +3,14 @@ import { domOnlyProps } from '../../../utils/reduxFormUtils'; function AccountForm(props) { const { - fields: { currentPassword, newPassword }, - user: { email, username }, + fields: { username, email, currentPassword, newPassword }, handleSubmit, submitting, + invalid, pristine } = props; return ( -
    +

    {email.touched && email.error && {email.error}}

    @@ -30,49 +30,49 @@ function AccountForm(props) { type="text" id="username" defaultValue={username} + {...username} /> + {username.touched && username.error && {username.error}}

    - + {currentPassword.touched && currentPassword.error && {currentPassword.error}}

    - + {newPassword.touched && newPassword.error && {newPassword.error}}

    - +
    ); } AccountForm.propTypes = { fields: PropTypes.shape({ + username: PropTypes.object.isRequired, + email: PropTypes.object.isRequired, currentPassword: PropTypes.object.isRequired, newPassword: PropTypes.object.isRequired }).isRequired, - user: PropTypes.shape({ - email: PropTypes.string.isRequired, - username: PropTypes.string.isRequired, - }).isRequired, handleSubmit: PropTypes.func.isRequired, - validateAndLoginUser: PropTypes.func.isRequired, + updateSettings: PropTypes.func.isRequired, submitting: PropTypes.bool, + invalid: PropTypes.bool, pristine: PropTypes.bool, - previousPath: PropTypes.string.isRequired }; AccountForm.defaultProps = { diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx index f7c2e9bdfa..4cb6b6ed53 100644 --- a/client/modules/User/pages/AccountView.jsx +++ b/client/modules/User/pages/AccountView.jsx @@ -1,10 +1,12 @@ import React, { PropTypes } from 'react'; import { reduxForm } from 'redux-form'; -import { Link, browserHistory } from 'react-router'; +import { bindActionCreators } from 'redux'; +import { browserHistory } from 'react-router'; import InlineSVG from 'react-inlinesvg'; -import { validateAndLoginUser } from '../actions'; +import axios from 'axios'; +import { updateSettings } from '../actions'; import AccountForm from '../components/AccountForm'; -// import GithubButton from '../components/GithubButton'; + const exitUrl = require('../../../images/exit.svg'); const logoUrl = require('../../../images/p5js-logo.svg'); @@ -48,34 +50,74 @@ class AccountView extends React.Component { function mapStateToProps(state) { return { + initialValues: state.user, // <- initialValues for reduxForm user: state.user, previousPath: state.ide.previousPath }; } -function mapDispatchToProps() { - return { - validateAndLoginUser - }; +function mapDispatchToProps(dispatch) { + return bindActionCreators({ updateSettings }, dispatch); +} + +function asyncValidate(formProps, dispatch, props) { + const fieldToValidate = props.form._active; + if (fieldToValidate) { + const queryParams = {}; + queryParams[fieldToValidate] = formProps[fieldToValidate]; + queryParams.check_type = fieldToValidate; + return axios.get('/api/signup/duplicate_check', { params: queryParams }) + .then((response) => { + if (response.data.exists) { + const error = {}; + error[fieldToValidate] = response.data.message; + throw error; + } + }); + } + return Promise.resolve(true).then(() => {}); } function validate(formProps) { const errors = {}; - if (!formProps.email) { - errors.email = 'Please enter an email'; + + if (!formProps.username) { + errors.username = 'Please enter a username.'; + } else if (!formProps.username.match(/^.{1,20}$/)) { + errors.username = 'Username must be less than 20 characters.'; + } else if (!formProps.username.match(/^[a-zA-Z0-9._-]{1,20}$/)) { + errors.username = 'Username must only consist of numbers, letters, periods, dashes, and underscores.'; } - if (!formProps.password) { - errors.password = 'Please enter a password'; + + if (!formProps.email) { + errors.email = 'Please enter an email.'; + } else if (!formProps.email.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i)) { + errors.email = 'Please enter a valid email address.'; } + + // if (!formProps.currentPassword) { + // errors.currentPassword = 'Please enter current password'; + // } + // if (!formProps.newPassword) { + // errors.newPassword = 'Please enter a new password'; + // } + return errors; } +function onSubmitFail(errors) { + console.log(errors); +} + AccountView.propTypes = { previousPath: PropTypes.string.isRequired }; export default reduxForm({ - form: 'login', - fields: ['currentPassword', 'newPassword'], - validate + form: 'updateAllSettings', + fields: ['username', 'email', 'currentPassword', 'newPassword'], + onSubmitFail, + validate, + asyncValidate, + asyncBlurFields: ['username', 'email'] }, mapStateToProps, mapDispatchToProps)(AccountView); diff --git a/server/controllers/user.controller.js b/server/controllers/user.controller.js index 306f7ee5b2..27aa7f663c 100644 --- a/server/controllers/user.controller.js +++ b/server/controllers/user.controller.js @@ -182,3 +182,28 @@ export function userExists(username, callback) { user ? callback(true) : callback(false) )); } + +export function updateSettings(req, res) { + User.findById(req.user.id, (err, user) => { + if (err) { + res.status(500).json({ error: err }); + return; + } + if (!user) { + res.status(404).json({ error: 'Document not found' }); + return; + } + + user.email = req.body.email; + user.username = req.body.username; + + user.save((saveErr) => { + if (saveErr) { + res.status(500).json({ error: saveErr }); + return; + } + + res.json(user); + }); + }); +} diff --git a/server/routes/user.routes.js b/server/routes/user.routes.js index a295e96c45..b3468cd87d 100644 --- a/server/routes/user.routes.js +++ b/server/routes/user.routes.js @@ -15,4 +15,6 @@ router.route('/reset-password/:token').get(UserController.validateResetPasswordT router.route('/reset-password/:token').post(UserController.updatePassword); +router.route('/account').put(UserController.updateSettings); + export default router; From 28b2d5625014a0c5445fc608fe66903437aed47d Mon Sep 17 00:00:00 2001 From: Yining Shi Date: Wed, 15 Mar 2017 06:54:03 -0400 Subject: [PATCH 3/8] validate current password and add new password --- client/constants.js | 1 + client/modules/User/actions.js | 6 +++++- client/modules/User/pages/AccountView.jsx | 7 ------- server/controllers/user.controller.js | 19 +++++++++++++++++++ 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/client/constants.js b/client/constants.js index b6444700c4..9c5121feb2 100644 --- a/client/constants.js +++ b/client/constants.js @@ -96,6 +96,7 @@ export const RESET_INFINITE_LOOPS = 'RESET_INFINITE_LOOPS'; export const RESET_PASSWORD_INITIATE = 'RESET_PASSWORD_INITIATE'; export const RESET_PASSWORD_RESET = 'RESET_PASSWORD_RESET'; export const INVALID_RESET_PASSWORD_TOKEN = 'INVALID_RESET_PASSWORD_TOKEN'; +export const INVALID_CURRENT_PASSWORD = 'INVALID_CURRENT_PASSWORD'; // eventually, handle errors more specifically and better export const ERROR = 'ERROR'; diff --git a/client/modules/User/actions.js b/client/modules/User/actions.js index 948bed20b5..99326a6388 100644 --- a/client/modules/User/actions.js +++ b/client/modules/User/actions.js @@ -170,11 +170,15 @@ export function saveSettingsSuccess(user) { export function updateSettings(formValues) { return (dispatch) => { + console.log(formValues); axios.put(`${ROOT_URL}/account`, formValues, { withCredentials: true }) .then((response) => { dispatch(saveSettingsSuccess(response.data)); browserHistory.push('/'); }) - .catch(response => dispatch(authError(response.data.error))); + .catch(response => dispatch({ + type: ActionTypes.INVALID_CURRENT_PASSWORD, + message: response.data.error + })); }; } diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx index 4cb6b6ed53..971946e320 100644 --- a/client/modules/User/pages/AccountView.jsx +++ b/client/modules/User/pages/AccountView.jsx @@ -95,13 +95,6 @@ function validate(formProps) { errors.email = 'Please enter a valid email address.'; } - // if (!formProps.currentPassword) { - // errors.currentPassword = 'Please enter current password'; - // } - // if (!formProps.newPassword) { - // errors.newPassword = 'Please enter a new password'; - // } - return errors; } diff --git a/server/controllers/user.controller.js b/server/controllers/user.controller.js index 27aa7f663c..ac3418037f 100644 --- a/server/controllers/user.controller.js +++ b/server/controllers/user.controller.js @@ -197,6 +197,25 @@ export function updateSettings(req, res) { user.email = req.body.email; user.username = req.body.username; + let validPassword = true; + if (req.body.currentPassword) { + validPassword = false; + user.comparePassword(req.body.currentPassword, (err, isMatch) => { + if (err) throw err; + if (!isMatch) { + validPassword = false; + } else { + validPassword = true; + user.password = req.body.newPassword; + } + }); + } + + if (!validPassword) { + res.status(401).json({ error: 'Invalid current password.' }); + return; + } + user.save((saveErr) => { if (saveErr) { res.status(500).json({ error: saveErr }); From e082d1a4d6144865c69ed8a6ac8c7a6ac95e5276 Mon Sep 17 00:00:00 2001 From: Yining Shi Date: Wed, 15 Mar 2017 22:08:29 -0400 Subject: [PATCH 4/8] reject promise with error for reduxForm submit-validation for current password --- client/constants.js | 2 +- client/modules/User/actions.js | 21 +++---------------- .../modules/User/components/AccountForm.jsx | 4 ++-- client/modules/User/pages/AccountView.jsx | 7 +------ client/modules/User/reducers.js | 1 + server/controllers/user.controller.js | 2 +- 6 files changed, 9 insertions(+), 28 deletions(-) diff --git a/client/constants.js b/client/constants.js index 9c5121feb2..e6c71eed40 100644 --- a/client/constants.js +++ b/client/constants.js @@ -96,7 +96,7 @@ export const RESET_INFINITE_LOOPS = 'RESET_INFINITE_LOOPS'; export const RESET_PASSWORD_INITIATE = 'RESET_PASSWORD_INITIATE'; export const RESET_PASSWORD_RESET = 'RESET_PASSWORD_RESET'; export const INVALID_RESET_PASSWORD_TOKEN = 'INVALID_RESET_PASSWORD_TOKEN'; -export const INVALID_CURRENT_PASSWORD = 'INVALID_CURRENT_PASSWORD'; + // eventually, handle errors more specifically and better export const ERROR = 'ERROR'; diff --git a/client/modules/User/actions.js b/client/modules/User/actions.js index 99326a6388..cf9512efc6 100644 --- a/client/modules/User/actions.js +++ b/client/modules/User/actions.js @@ -161,24 +161,9 @@ export function updatePassword(token, formValues) { }; } -export function saveSettingsSuccess(user) { - return { - type: ActionTypes.AUTH_USER, - user - }; -} - export function updateSettings(formValues) { - return (dispatch) => { - console.log(formValues); + return dispatch => axios.put(`${ROOT_URL}/account`, formValues, { withCredentials: true }) - .then((response) => { - dispatch(saveSettingsSuccess(response.data)); - browserHistory.push('/'); - }) - .catch(response => dispatch({ - type: ActionTypes.INVALID_CURRENT_PASSWORD, - message: response.data.error - })); - }; + .then(response => browserHistory.push('/')) + .catch(response => Promise.reject({ currentPassword: response.data.error })); } diff --git a/client/modules/User/components/AccountForm.jsx b/client/modules/User/components/AccountForm.jsx index 0c7fd61ddd..b8980d9f4d 100644 --- a/client/modules/User/components/AccountForm.jsx +++ b/client/modules/User/components/AccountForm.jsx @@ -18,7 +18,7 @@ function AccountForm(props) { aria-label="email" type="text" id="email" - {...email} + {...domOnlyProps(email)} /> {email.touched && email.error && {email.error}}

    @@ -30,7 +30,7 @@ function AccountForm(props) { type="text" id="username" defaultValue={username} - {...username} + {...domOnlyProps(username)} /> {username.touched && username.error && {username.error}}

    diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx index 971946e320..4960e08398 100644 --- a/client/modules/User/pages/AccountView.jsx +++ b/client/modules/User/pages/AccountView.jsx @@ -98,10 +98,6 @@ function validate(formProps) { return errors; } -function onSubmitFail(errors) { - console.log(errors); -} - AccountView.propTypes = { previousPath: PropTypes.string.isRequired }; @@ -109,8 +105,7 @@ AccountView.propTypes = { export default reduxForm({ form: 'updateAllSettings', fields: ['username', 'email', 'currentPassword', 'newPassword'], - onSubmitFail, validate, asyncValidate, - asyncBlurFields: ['username', 'email'] + asyncBlurFields: ['username', 'email', 'currentPassword'] }, mapStateToProps, mapDispatchToProps)(AccountView); diff --git a/client/modules/User/reducers.js b/client/modules/User/reducers.js index 04220f584b..8debbf677d 100644 --- a/client/modules/User/reducers.js +++ b/client/modules/User/reducers.js @@ -19,6 +19,7 @@ const user = (state = { authenticated: false }, action) => { return Object.assign({}, state, { resetPasswordInitiate: false }); case ActionTypes.INVALID_RESET_PASSWORD_TOKEN: return Object.assign({}, state, { resetPasswordInvalid: true }); + default: return state; } diff --git a/server/controllers/user.controller.js b/server/controllers/user.controller.js index ac3418037f..0a79e9af87 100644 --- a/server/controllers/user.controller.js +++ b/server/controllers/user.controller.js @@ -212,7 +212,7 @@ export function updateSettings(req, res) { } if (!validPassword) { - res.status(401).json({ error: 'Invalid current password.' }); + res.status(401).json({ error: 'Current password is invalid.' }); return; } From 8d4b71e92b52ac2ab4b40f9227f58bfb6982c289 Mon Sep 17 00:00:00 2001 From: Yining Shi Date: Wed, 15 Mar 2017 22:46:18 -0400 Subject: [PATCH 5/8] updated user reducer to handle setting sucess and server side async --- client/constants.js | 2 ++ client/modules/User/actions.js | 12 +++++++++++- client/modules/User/reducers.js | 3 ++- server/controllers/user.controller.js | 26 ++++++++++++-------------- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/client/constants.js b/client/constants.js index e6c71eed40..3cc8f104a7 100644 --- a/client/constants.js +++ b/client/constants.js @@ -23,6 +23,8 @@ export const AUTH_USER = 'AUTH_USER'; export const UNAUTH_USER = 'UNAUTH_USER'; export const AUTH_ERROR = 'AUTH_ERROR'; +export const SETTINGS_UPDATED = 'SETTINGS_UPDATED'; + export const SET_PROJECT_NAME = 'SET_PROJECT_NAME'; export const PROJECT_SAVE_SUCCESS = 'PROJECT_SAVE_SUCCESS'; diff --git a/client/modules/User/actions.js b/client/modules/User/actions.js index cf9512efc6..e8a3953cf9 100644 --- a/client/modules/User/actions.js +++ b/client/modules/User/actions.js @@ -161,9 +161,19 @@ export function updatePassword(token, formValues) { }; } +export function updateSettingsSuccess(user) { + return { + type: ActionTypes.SETTINGS_UPDATED, + user + }; +} + export function updateSettings(formValues) { return dispatch => axios.put(`${ROOT_URL}/account`, formValues, { withCredentials: true }) - .then(response => browserHistory.push('/')) + .then((response) => { + dispatch(updateSettingsSuccess(response.data)); + browserHistory.push('/'); + }) .catch(response => Promise.reject({ currentPassword: response.data.error })); } diff --git a/client/modules/User/reducers.js b/client/modules/User/reducers.js index 8debbf677d..5554a11f44 100644 --- a/client/modules/User/reducers.js +++ b/client/modules/User/reducers.js @@ -19,7 +19,8 @@ const user = (state = { authenticated: false }, action) => { return Object.assign({}, state, { resetPasswordInitiate: false }); case ActionTypes.INVALID_RESET_PASSWORD_TOKEN: return Object.assign({}, state, { resetPasswordInvalid: true }); - + case ActionTypes.SETTINGS_UPDATED: + return { ...state, ...action.user }; default: return state; } diff --git a/server/controllers/user.controller.js b/server/controllers/user.controller.js index 0a79e9af87..ffe6876ed7 100644 --- a/server/controllers/user.controller.js +++ b/server/controllers/user.controller.js @@ -197,32 +197,30 @@ export function updateSettings(req, res) { user.email = req.body.email; user.username = req.body.username; - let validPassword = true; if (req.body.currentPassword) { - validPassword = false; user.comparePassword(req.body.currentPassword, (err, isMatch) => { if (err) throw err; if (!isMatch) { - validPassword = false; + res.status(401).json({ error: 'Current password is invalid.' }); + return; } else { - validPassword = true; user.password = req.body.newPassword; + saveUser(res, user); } }); + } else { + saveUser(res, user); } + }); +} - if (!validPassword) { - res.status(401).json({ error: 'Current password is invalid.' }); +export function saveUser(res, user) { + user.save((saveErr) => { + if (saveErr) { + res.status(500).json({ error: saveErr }); return; } - user.save((saveErr) => { - if (saveErr) { - res.status(500).json({ error: saveErr }); - return; - } - - res.json(user); - }); + res.json(user); }); } From fca28077171789e93cd3b66607f1766b74883b09 Mon Sep 17 00:00:00 2001 From: Yining Shi Date: Wed, 15 Mar 2017 23:09:19 -0400 Subject: [PATCH 6/8] warning if there is current password but no new password --- client/modules/User/pages/AccountView.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx index 4960e08398..2736b7e3d8 100644 --- a/client/modules/User/pages/AccountView.jsx +++ b/client/modules/User/pages/AccountView.jsx @@ -95,6 +95,10 @@ function validate(formProps) { errors.email = 'Please enter a valid email address.'; } + if (formProps.currentPassword && !formProps.newPassword) { + errors.newPassword = 'Please enter a new password or leave the current password empty.'; + } + return errors; } From 13966078ca12d399ea7874d0b54fd4ffde0da871 Mon Sep 17 00:00:00 2001 From: Yining Shi Date: Thu, 16 Mar 2017 02:18:02 -0400 Subject: [PATCH 7/8] fixes logout button --- client/styles/components/_nav.scss | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/styles/components/_nav.scss b/client/styles/components/_nav.scss index 84f91fdd09..2eaaccf47d 100644 --- a/client/styles/components/_nav.scss +++ b/client/styles/components/_nav.scss @@ -56,7 +56,7 @@ .nav__item-spacer { @include themify() { color: map-get($theme-map, 'inactive-text-color'); - } + } padding: 0 #{15 / $base-font-size}rem; } @@ -65,12 +65,16 @@ width: 100%; } -.nav__dropdown a { +.nav__dropdown a, button { @include themify() { color: getThemifyVariable('secondary-text-color'); } } +.nav__dropdown button { + padding: 0; +} + .nav__dropdown a:hover { @include themify() { color: getThemifyVariable('primary-text-color'); From b33b8093be997453e0c407333a31112648f45fc1 Mon Sep 17 00:00:00 2001 From: Yining Shi Date: Thu, 16 Mar 2017 17:30:38 -0400 Subject: [PATCH 8/8] import validate function, fixes logout style --- client/modules/User/pages/AccountView.jsx | 27 +---------- client/modules/User/pages/LoginView.jsx | 14 +----- client/modules/User/pages/SignupView.jsx | 34 +------------ client/utils/reduxFormUtils.js | 58 +++++++++++++++++++++++ 4 files changed, 64 insertions(+), 69 deletions(-) diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx index 2736b7e3d8..00397c0acd 100644 --- a/client/modules/User/pages/AccountView.jsx +++ b/client/modules/User/pages/AccountView.jsx @@ -6,6 +6,7 @@ import InlineSVG from 'react-inlinesvg'; import axios from 'axios'; import { updateSettings } from '../actions'; import AccountForm from '../components/AccountForm'; +import { validateSettings } from '../../../utils/reduxFormUtils'; const exitUrl = require('../../../images/exit.svg'); const logoUrl = require('../../../images/p5js-logo.svg'); @@ -78,30 +79,6 @@ function asyncValidate(formProps, dispatch, props) { return Promise.resolve(true).then(() => {}); } -function validate(formProps) { - const errors = {}; - - if (!formProps.username) { - errors.username = 'Please enter a username.'; - } else if (!formProps.username.match(/^.{1,20}$/)) { - errors.username = 'Username must be less than 20 characters.'; - } else if (!formProps.username.match(/^[a-zA-Z0-9._-]{1,20}$/)) { - errors.username = 'Username must only consist of numbers, letters, periods, dashes, and underscores.'; - } - - if (!formProps.email) { - errors.email = 'Please enter an email.'; - } else if (!formProps.email.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i)) { - errors.email = 'Please enter a valid email address.'; - } - - if (formProps.currentPassword && !formProps.newPassword) { - errors.newPassword = 'Please enter a new password or leave the current password empty.'; - } - - return errors; -} - AccountView.propTypes = { previousPath: PropTypes.string.isRequired }; @@ -109,7 +86,7 @@ AccountView.propTypes = { export default reduxForm({ form: 'updateAllSettings', fields: ['username', 'email', 'currentPassword', 'newPassword'], - validate, + validate: validateSettings, asyncValidate, asyncBlurFields: ['username', 'email', 'currentPassword'] }, mapStateToProps, mapDispatchToProps)(AccountView); diff --git a/client/modules/User/pages/LoginView.jsx b/client/modules/User/pages/LoginView.jsx index 255d640826..b25e50535b 100644 --- a/client/modules/User/pages/LoginView.jsx +++ b/client/modules/User/pages/LoginView.jsx @@ -4,6 +4,7 @@ import { Link, browserHistory } from 'react-router'; import InlineSVG from 'react-inlinesvg'; import { validateAndLoginUser } from '../actions'; import LoginForm from '../components/LoginForm'; +import { validateLogin } from '../../../utils/reduxFormUtils'; // import GithubButton from '../components/GithubButton'; const exitUrl = require('../../../images/exit.svg'); const logoUrl = require('../../../images/p5js-logo.svg'); @@ -67,17 +68,6 @@ function mapDispatchToProps() { }; } -function validate(formProps) { - const errors = {}; - if (!formProps.email) { - errors.email = 'Please enter an email'; - } - if (!formProps.password) { - errors.password = 'Please enter a password'; - } - return errors; -} - LoginView.propTypes = { previousPath: PropTypes.string.isRequired }; @@ -85,5 +75,5 @@ LoginView.propTypes = { export default reduxForm({ form: 'login', fields: ['email', 'password'], - validate + validate: validateLogin }, mapStateToProps, mapDispatchToProps)(LoginView); diff --git a/client/modules/User/pages/SignupView.jsx b/client/modules/User/pages/SignupView.jsx index d575995703..47278271a7 100644 --- a/client/modules/User/pages/SignupView.jsx +++ b/client/modules/User/pages/SignupView.jsx @@ -6,6 +6,7 @@ import InlineSVG from 'react-inlinesvg'; import { reduxForm } from 'redux-form'; import * as UserActions from '../actions'; import SignupForm from '../components/SignupForm'; +import { validateSignup } from '../../../utils/reduxFormUtils'; const exitUrl = require('../../../images/exit.svg'); const logoUrl = require('../../../images/p5js-logo.svg'); @@ -78,37 +79,6 @@ function asyncValidate(formProps, dispatch, props) { return Promise.resolve(true).then(() => {}); } -function validate(formProps) { - const errors = {}; - - if (!formProps.username) { - errors.username = 'Please enter a username.'; - } else if (!formProps.username.match(/^.{1,20}$/)) { - errors.username = 'Username must be less than 20 characters.'; - } else if (!formProps.username.match(/^[a-zA-Z0-9._-]{1,20}$/)) { - errors.username = 'Username must only consist of numbers, letters, periods, dashes, and underscores.'; - } - - if (!formProps.email) { - errors.email = 'Please enter an email.'; - } else if (!formProps.email.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i)) { - errors.email = 'Please enter a valid email address.'; - } - - if (!formProps.password) { - errors.password = 'Please enter a password'; - } - if (!formProps.confirmPassword) { - errors.confirmPassword = 'Please enter a password confirmation'; - } - - if (formProps.password !== formProps.confirmPassword) { - errors.password = 'Passwords must match'; - } - - return errors; -} - function onSubmitFail(errors) { console.log(errors); } @@ -121,7 +91,7 @@ export default reduxForm({ form: 'signup', fields: ['username', 'email', 'password', 'confirmPassword'], onSubmitFail, - validate, + validate: validateSignup, asyncValidate, asyncBlurFields: ['username', 'email'] }, mapStateToProps, mapDispatchToProps)(SignupView); diff --git a/client/utils/reduxFormUtils.js b/client/utils/reduxFormUtils.js index 8b4b120545..c2c68fa0c5 100644 --- a/client/utils/reduxFormUtils.js +++ b/client/utils/reduxFormUtils.js @@ -14,3 +14,61 @@ export const domOnlyProps = ({ error, ...domProps }) => domProps; /* eslint-enable */ + +function validateNameEmail(formProps, errors) { + if (!formProps.username) { + errors.username = 'Please enter a username.'; + } else if (!formProps.username.match(/^.{1,20}$/)) { + errors.username = 'Username must be less than 20 characters.'; + } else if (!formProps.username.match(/^[a-zA-Z0-9._-]{1,20}$/)) { + errors.username = 'Username must only consist of numbers, letters, periods, dashes, and underscores.'; + } + + if (!formProps.email) { + errors.email = 'Please enter an email.'; + } else if (!formProps.email.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i)) { + errors.email = 'Please enter a valid email address.'; + } +} + +export function validateSettings(formProps) { + const errors = {}; + + validateNameEmail(formProps, errors); + + if (formProps.currentPassword && !formProps.newPassword) { + errors.newPassword = 'Please enter a new password or leave the current password empty.'; + } + + return errors; +} + +export function validateLogin(formProps) { + const errors = {}; + if (!formProps.email) { + errors.email = 'Please enter an email'; + } + if (!formProps.password) { + errors.password = 'Please enter a password'; + } + return errors; +} + +export function validateSignup(formProps) { + const errors = {}; + + validateNameEmail(formProps, errors); + + if (!formProps.password) { + errors.password = 'Please enter a password'; + } + if (!formProps.confirmPassword) { + errors.confirmPassword = 'Please enter a password confirmation'; + } + + if (formProps.password !== formProps.confirmPassword) { + errors.password = 'Passwords must match'; + } + + return errors; +}