From e204b1877c2501e131c46633279a8b260db58b67 Mon Sep 17 00:00:00 2001 From: Maxi Redigonda Date: Fri, 20 Jul 2018 17:35:58 -0300 Subject: [PATCH 1/7] Fixes redirection upon password recovery --- .../main-recover-password/main-recover-password-page.js | 7 ++++--- client/src/data/fixtures/user-fixtures.js | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/client/src/app/main/main-recover-password/main-recover-password-page.js b/client/src/app/main/main-recover-password/main-recover-password-page.js index 3c791bc2..e06ec60d 100644 --- a/client/src/app/main/main-recover-password/main-recover-password-page.js +++ b/client/src/app/main/main-recover-password/main-recover-password-page.js @@ -3,6 +3,7 @@ import _ from 'lodash'; import i18n from 'lib-app/i18n'; import API from 'lib-app/api-call'; +import history from 'lib-app/history'; import Widget from 'core-components/widget'; import Form from 'core-components/form'; @@ -73,8 +74,8 @@ class MainRecoverPasswordPage extends React.Component { }).then(this.onPasswordRecovered.bind(this)).catch(this.onPasswordRecoverFail.bind(this)); } - onPasswordRecovered() { - setTimeout(() => {this.props.history.push('/')}, 2000); + onPasswordRecovered(response) { + setTimeout(() => {history.push(response.data.staff ? '/admin' : '/')}, 2000); this.setState({ recoverStatus: 'valid', loading: false @@ -89,4 +90,4 @@ class MainRecoverPasswordPage extends React.Component { } } -export default MainRecoverPasswordPage; \ No newline at end of file +export default MainRecoverPasswordPage; diff --git a/client/src/data/fixtures/user-fixtures.js b/client/src/data/fixtures/user-fixtures.js index 65bb8fa2..5771d7ec 100644 --- a/client/src/data/fixtures/user-fixtures.js +++ b/client/src/data/fixtures/user-fixtures.js @@ -77,7 +77,9 @@ module.exports = [ if (data.password.length > 6) { return { status: 'success', - data: {} + data: { + staff: true + } }; } else { return { From edb8ba1de0a05115f2695e7feccc7b7b5972ad65 Mon Sep 17 00:00:00 2001 From: Maxi Redigonda Date: Fri, 20 Jul 2018 18:56:19 -0300 Subject: [PATCH 2/7] Makes widget-transition set focus when changing side to show --- .../src/core-components/widget-transition.js | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/client/src/core-components/widget-transition.js b/client/src/core-components/widget-transition.js index 70309ac3..3ecfd4a3 100644 --- a/client/src/core-components/widget-transition.js +++ b/client/src/core-components/widget-transition.js @@ -1,7 +1,9 @@ import React from 'react'; +import ReactDOM from 'react-dom'; import classNames from 'classnames'; import _ from 'lodash'; import {Motion, spring} from 'react-motion'; +import focus from 'lib-core/focus'; class WidgetTransition extends React.Component { @@ -19,6 +21,13 @@ class WidgetTransition extends React.Component { }; } + componentDidUpdate(prevProps) { + if (prevProps.sideToShow != this.props.sideToShow && this.primaryWidget && this.secondaryWidget) { + console.log("The component was updated!"); + this.moveFocusToCurrentSide(); + } + } + render() { return ( @@ -30,7 +39,7 @@ class WidgetTransition extends React.Component { renderChildren(animation) { return (
- {React.Children.map(this.props.children, function (child, index) { + {React.Children.map(this.props.children, (child, index) => { let modifiedChild; if (index === 0) { @@ -38,14 +47,16 @@ class WidgetTransition extends React.Component { className: child.props.className + ' widget-transition--widget', style: _.extend ({}, child.props.style, { transform: `rotateY(${(animation.rotateY) ? animation.rotateY: 0}deg)` - }) + }), + ref: (node) => this.primaryWidget = node }); } else { modifiedChild = React.cloneElement(child, { className: child.props.className + ' widget-transition--widget', style: _.extend ({}, child.props.style, { transform: `rotateY(${-180 + animation.rotateY}deg)` - }) + }), + ref: (node) => this.secondaryWidget = node }); } @@ -69,6 +80,24 @@ class WidgetTransition extends React.Component { rotateY: (this.props.sideToShow === 'front') ? spring(0, [100, 20]) : spring(180, [100, 20]) }; } + + moveFocusToCurrentSide() { + let currentWidget; + let previousWidget; + + if (this.props.sideToShow === 'front') { + currentWidget = this.primaryWidget; + previousWidget = this.secondaryWidget; + } else { + currentWidget = this.secondaryWidget; + previousWidget = this.primaryWidget; + } + + currentWidget = ReactDOM.findDOMNode(currentWidget); + previousWidget = ReactDOM.findDOMNode(previousWidget); + + focus.focusFirstInput(currentWidget); + } } export default WidgetTransition; From c0e0ba0d775c76f83e9c2d1bc94b5363ceb2d99c Mon Sep 17 00:00:00 2001 From: Maxi Redigonda Date: Fri, 20 Jul 2018 19:08:33 -0300 Subject: [PATCH 3/7] Removes focus-on-changing-side machinery from home-page-login-widget, since widget-transition now does that job --- .../main-home/main-home-page-login-widget.js | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/client/src/app/main/main-home/main-home-page-login-widget.js b/client/src/app/main/main-home/main-home-page-login-widget.js index 47e34583..94c061c1 100644 --- a/client/src/app/main/main-home/main-home-page-login-widget.js +++ b/client/src/app/main/main-home/main-home-page-login-widget.js @@ -163,14 +163,14 @@ class MainHomePageLoginWidget extends React.Component { onForgotPasswordClick() { this.setState({ sideToShow: 'back' - }, this.moveFocusToCurrentSide); + }); } onBackToLoginClick() { this.setState({ sideToShow: 'front', recoverSent: false - }, this.moveFocusToCurrentSide); + }); } onRecoverPasswordSent() { @@ -190,23 +190,6 @@ class MainHomePageLoginWidget extends React.Component { this.refs.recoverForm.refs.email.focus(); }.bind(this)); } - - moveFocusToCurrentSide() { - let currentWidget; - let previousWidget; - - if (this.state.sideToShow === 'front') { - currentWidget = ReactDOM.findDOMNode(this.refs.loginWidget); - previousWidget = ReactDOM.findDOMNode(this.refs.recoverWidget); - } else { - currentWidget = ReactDOM.findDOMNode(this.refs.recoverWidget); - previousWidget = ReactDOM.findDOMNode(this.refs.loginWidget); - } - - if (focus.isActiveElementInsideDOMTree(previousWidget)) { - focus.focusFirstInput(currentWidget); - } - } } From e60105cccc4f9ee3a131129ff7439e79660fdace Mon Sep 17 00:00:00 2001 From: Maxi Redigonda Date: Fri, 20 Jul 2018 20:23:15 -0300 Subject: [PATCH 4/7] WIP - Creating PasswordRecovery component --- .../src/app-components/password-recovery.js | 54 +++++++++++++++++++ .../src/app-components/password-recovery.scss | 16 ++++++ client/src/app/admin/admin-login-page.js | 41 ++++++++++++++ .../main-home/main-home-page-login-widget.js | 6 ++- .../main-home-page-login-widget.scss | 2 +- 5 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 client/src/app-components/password-recovery.js create mode 100644 client/src/app-components/password-recovery.scss diff --git a/client/src/app-components/password-recovery.js b/client/src/app-components/password-recovery.js new file mode 100644 index 00000000..0ad6b79f --- /dev/null +++ b/client/src/app-components/password-recovery.js @@ -0,0 +1,54 @@ +import React from 'react'; +import classNames from 'classnames'; + +import i18n from 'lib-app/i18n'; + +import Form from 'core-components/form'; +import FormField from 'core-components/form-field'; +import Widget from 'core-components/widget'; +import Button from 'core-components/button'; +import SubmitButton from 'core-components/submit-button'; + +class PasswordRecovery extends React.Component { + + static propTypes = { + recoverSent: React.PropTypes.bool, + formProps: React.PropTypes.object, + onBackToLoginClick: React.PropTypes.func + }; + + render() { + return ( + +
+
+ +
+
+ {i18n('RECOVER_PASSWORD')} +
+
+ + {this.renderRecoverStatus()} +
+ ); + } + + renderRecoverStatus() { + let status = null; + + if (this.props.recoverSent) { + status = ( + + {i18n('RECOVER_SENT')} + + ); + } + + return status; + } +} + +export default PasswordRecovery; diff --git a/client/src/app-components/password-recovery.scss b/client/src/app-components/password-recovery.scss new file mode 100644 index 00000000..cf33ab9f --- /dev/null +++ b/client/src/app-components/password-recovery.scss @@ -0,0 +1,16 @@ +.password-recovery { + + &__inputs { + display: inline-block; + margin: 0 auto 20px; + text-align: left; + } + + &__forgot-password { + margin-top: 20px; + } + + &__message { + margin-top: 18px; + } +} diff --git a/client/src/app/admin/admin-login-page.js b/client/src/app/admin/admin-login-page.js index 54d38d5d..358ba8f6 100644 --- a/client/src/app/admin/admin-login-page.js +++ b/client/src/app/admin/admin-login-page.js @@ -6,14 +6,51 @@ import i18n from 'lib-app/i18n'; import API from 'lib-app/api-call'; import SessionActions from 'actions/session-actions'; +import Button from 'core-components/button'; import Form from 'core-components/form'; import FormField from 'core-components/form-field'; import SubmitButton from 'core-components/submit-button'; import Message from 'core-components/message'; import Widget from 'core-components/widget'; +import WidgetTransition from 'core-components/widget-transition'; class AdminLoginPage extends React.Component { + + state = { + sideToShow: 'front' + }; + render() { + return ( + + {this.renderLogin()} + {this.renderPasswordRecovery()} + + ) + } + + renderLogin() { + return ( +
+ +
OpenSupports Admin Panel
+
+
+ + + {i18n('LOG_IN')} + +
+ {this.renderMessage()} + +
+
+ ); + } + + renderPasswordRecovery() { return (
@@ -30,6 +67,10 @@ class AdminLoginPage extends React.Component {
); } + + onForgotPasswordClick() { + this.setState({sideToShow: 'back'}); + } renderMessage() { let message = null; diff --git a/client/src/app/main/main-home/main-home-page-login-widget.js b/client/src/app/main/main-home/main-home-page-login-widget.js index 94c061c1..656170ac 100644 --- a/client/src/app/main/main-home/main-home-page-login-widget.js +++ b/client/src/app/main/main-home/main-home-page-login-widget.js @@ -9,6 +9,7 @@ import API from 'lib-app/api-call'; import focus from 'lib-core/focus'; import i18n from 'lib-app/i18n'; +import PasswordRecovery from 'app-components/password-recovery.js'; import SubmitButton from 'core-components/submit-button'; import Button from 'core-components/button'; import Form from 'core-components/form'; @@ -65,7 +66,8 @@ class MainHomePageLoginWidget extends React.Component { renderPasswordRecovery() { return ( - + + /*
@@ -78,7 +80,7 @@ class MainHomePageLoginWidget extends React.Component { {i18n('BACK_LOGIN_FORM')} {this.renderRecoverStatus()} - + */ ); } diff --git a/client/src/app/main/main-home/main-home-page-login-widget.scss b/client/src/app/main/main-home/main-home-page-login-widget.scss index f9de5447..c1b42380 100644 --- a/client/src/app/main/main-home/main-home-page-login-widget.scss +++ b/client/src/app/main/main-home/main-home-page-login-widget.scss @@ -17,4 +17,4 @@ &__message { margin-top: 18px; } -} \ No newline at end of file +} From da3cf30192998825cc15efbc76d0f871f8a364d1 Mon Sep 17 00:00:00 2001 From: Maxi Redigonda Date: Thu, 26 Jul 2018 15:51:21 -0300 Subject: [PATCH 5/7] Makes password-recovery act as a widget by injecting props --- client/src/app-components/password-recovery.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/app-components/password-recovery.js b/client/src/app-components/password-recovery.js index 0ad6b79f..2a15f66d 100644 --- a/client/src/app-components/password-recovery.js +++ b/client/src/app-components/password-recovery.js @@ -19,7 +19,7 @@ class PasswordRecovery extends React.Component { render() { return ( - +
From 949334fa715b572bb1c1adee0cdf9ce37e2bc350 Mon Sep 17 00:00:00 2001 From: Maxi Redigonda Date: Thu, 26 Jul 2018 16:03:25 -0300 Subject: [PATCH 6/7] Removes old commented code --- .../main/main-home/main-home-page-login-widget.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/client/src/app/main/main-home/main-home-page-login-widget.js b/client/src/app/main/main-home/main-home-page-login-widget.js index 656170ac..2a56aba0 100644 --- a/client/src/app/main/main-home/main-home-page-login-widget.js +++ b/client/src/app/main/main-home/main-home-page-login-widget.js @@ -67,20 +67,6 @@ class MainHomePageLoginWidget extends React.Component { renderPasswordRecovery() { return ( - /* - -
- -
-
- {i18n('RECOVER_PASSWORD')} -
- - - {this.renderRecoverStatus()} -
*/ ); } From 876fd8d52fcd2c51b51dbeeb4f68091dc14643a2 Mon Sep 17 00:00:00 2001 From: Maxi Redigonda Date: Thu, 26 Jul 2018 17:46:26 -0300 Subject: [PATCH 7/7] Enhances password-recovery to show a logo if required, and adds password recovery to admin panel --- .../src/app-components/password-recovery.js | 22 ++- .../src/app-components/password-recovery.scss | 10 + client/src/app/admin/admin-login-page.js | 171 ++++++++++++++---- client/src/app/admin/admin-login-page.scss | 8 +- .../src/core-components/widget-transition.js | 1 - 5 files changed, 176 insertions(+), 36 deletions(-) diff --git a/client/src/app-components/password-recovery.js b/client/src/app-components/password-recovery.js index 2a15f66d..4541a5fc 100644 --- a/client/src/app-components/password-recovery.js +++ b/client/src/app-components/password-recovery.js @@ -2,24 +2,32 @@ import React from 'react'; import classNames from 'classnames'; import i18n from 'lib-app/i18n'; +import API from 'lib-app/api-call'; import Form from 'core-components/form'; import FormField from 'core-components/form-field'; import Widget from 'core-components/widget'; import Button from 'core-components/button'; import SubmitButton from 'core-components/submit-button'; +import Message from 'core-components/message'; class PasswordRecovery extends React.Component { static propTypes = { recoverSent: React.PropTypes.bool, formProps: React.PropTypes.object, - onBackToLoginClick: React.PropTypes.func + onBackToLoginClick: React.PropTypes.func, + renderLogo: React.PropTypes.bool + }; + + static defaultProps = { + renderLogo: false }; render() { return ( - + + {this.renderLogo()}
@@ -36,6 +44,16 @@ class PasswordRecovery extends React.Component { ); } + renderLogo() { + let logo = null; + + if (this.props.renderLogo) { + logo = (
OpenSupports Login Panel
); + } + + return logo; + } + renderRecoverStatus() { let status = null; diff --git a/client/src/app-components/password-recovery.scss b/client/src/app-components/password-recovery.scss index cf33ab9f..85bf3d33 100644 --- a/client/src/app-components/password-recovery.scss +++ b/client/src/app-components/password-recovery.scss @@ -6,6 +6,11 @@ text-align: left; } + &__content { + margin: 0 auto; + padding: 40px; + } + &__forgot-password { margin-top: 20px; } @@ -13,4 +18,9 @@ &__message { margin-top: 18px; } + + &__image { + width: 365px; + margin-bottom: 30px; + } } diff --git a/client/src/app/admin/admin-login-page.js b/client/src/app/admin/admin-login-page.js index 358ba8f6..72e5daba 100644 --- a/client/src/app/admin/admin-login-page.js +++ b/client/src/app/admin/admin-login-page.js @@ -1,11 +1,13 @@ import React from 'react'; import _ from 'lodash'; +import classNames from 'classnames'; import {connect} from 'react-redux'; import i18n from 'lib-app/i18n'; import API from 'lib-app/api-call'; import SessionActions from 'actions/session-actions'; +import PasswordRecovery from 'app-components/password-recovery.js'; import Button from 'core-components/button'; import Form from 'core-components/form'; import FormField from 'core-components/form-field'; @@ -17,31 +19,45 @@ import WidgetTransition from 'core-components/widget-transition'; class AdminLoginPage extends React.Component { state = { - sideToShow: 'front' + sideToShow: 'front', + loginFormErrors: {}, + recoverFormErrors: {}, + recoverSent: false, + loadingLogin: false, + loadingRecover: false }; + + componentDidUpdate(prevProps) { + if (!prevProps.session.failed && this.props.session.failed) { + this.refs.loginForm.refs.password.focus(); + } + } render() { - return ( - - {this.renderLogin()} - {this.renderPasswordRecovery()} - - ) + return ( +
+ + {this.renderLogin()} + {this.renderPasswordRecovery()} + +
+ ); } renderLogin() { return ( -
+
OpenSupports Admin Panel
- + {i18n('LOG_IN')}
- {this.renderMessage()} + {this.renderRecoverStatus()} + {this.renderErrorStatus()} @@ -49,48 +65,139 @@ class AdminLoginPage extends React.Component {
); } - + renderPasswordRecovery() { return ( -
- -
OpenSupports Admin Panel
-
-
- - - {i18n('LOG_IN')} - -
- {this.renderMessage()} -
+
+
); } - - onForgotPasswordClick() { - this.setState({sideToShow: 'back'}); + + renderRecoverStatus() { + let status = null; + + if (this.state.recoverSent) { + status = ( + + {i18n('RECOVER_SENT')} + + ); + } + + return status; } - renderMessage() { - let message = null; + renderErrorStatus() { + let status = null; - if(this.props.session.failed) { - message = ( + if (this.props.session.failed) { + status = ( {i18n('EMAIL_OR_PASSWORD')} ); } - return message; + return status; } - onSubmit(formState) { + getLoginFormProps() { + return { + loading: this.props.session.pending, + className: 'admin-login-page__form', + ref: 'loginForm', + onSubmit: this.onLoginFormSubmit.bind(this), + errors: this.getLoginFormErrors(), + onValidateErrors: this.onLoginFormErrorsValidation.bind(this) + }; + } + + getRecoverFormProps() { + return { + loading: this.state.loadingRecover, + className: 'admin-login-page__form', + ref: 'recoverForm', + onSubmit: this.onForgotPasswordSubmit.bind(this), + errors: this.state.recoverFormErrors, + onValidateErrors: this.onRecoverFormErrorsValidation.bind(this) + }; + } + + getLoginFormErrors() { + let errors = _.extend({}, this.state.loginFormErrors); + + if (this.props.session.failed) { + if (this.props.session.failMessage === 'INVALID_CREDENTIALS') { + errors.password = i18n('ERROR_PASSWORD'); + } else if (this.props.session.failMessage === 'UNVERIFIED_USER') { + errors.email = i18n('UNVERIFIED_EMAIL'); + } + } + + return errors; + } + + onLoginFormSubmit(formState) { this.props.dispatch(SessionActions.login(_.extend({}, formState, { staff: true }))); } + + onForgotPasswordSubmit(formState) { + this.setState({ + loadingRecover: true, + recoverSent: false + }); + + API.call({ + path: '/user/send-recover-password', + data: _.extend({}, formState, {staff: true}) + }).then(this.onRecoverPasswordSent.bind(this)).catch(this.onRecoverPasswordFail.bind(this)); + } + + onLoginFormErrorsValidation(errors) { + this.setState({ + loginFormErrors: errors + }); + } + + onRecoverFormErrorsValidation(errors) { + this.setState({ + recoverFormErrors: errors + }); + } + + onForgotPasswordClick() { + this.setState({ + sideToShow: 'back' + }); + } + + onBackToLoginClick() { + this.setState({ + sideToShow: 'front', + recoverSent: false + }); + } + + onRecoverPasswordSent() { + this.setState({ + loadingRecover: false, + recoverSent: true + }); + } + + onRecoverPasswordFail() { + this.setState({ + loadingRecover: false, + recoverFormErrors: { + email: i18n('EMAIL_NOT_EXIST') + } + }, function () { + this.refs.recoverForm.refs.email.focus(); + }.bind(this)); + } } export default connect((store) => { diff --git a/client/src/app/admin/admin-login-page.scss b/client/src/app/admin/admin-login-page.scss index 57b07631..4f9f6b14 100644 --- a/client/src/app/admin/admin-login-page.scss +++ b/client/src/app/admin/admin-login-page.scss @@ -2,6 +2,12 @@ .admin-login-page { display: flex; + margin: 0 auto; + width: 446px; + + &__container { + height: 361px; + } &__content { margin: 0 auto; @@ -21,4 +27,4 @@ &__error { margin-top: 30px; } -} \ No newline at end of file +} diff --git a/client/src/core-components/widget-transition.js b/client/src/core-components/widget-transition.js index 3ecfd4a3..218f392c 100644 --- a/client/src/core-components/widget-transition.js +++ b/client/src/core-components/widget-transition.js @@ -23,7 +23,6 @@ class WidgetTransition extends React.Component { componentDidUpdate(prevProps) { if (prevProps.sideToShow != this.props.sideToShow && this.primaryWidget && this.secondaryWidget) { - console.log("The component was updated!"); this.moveFocusToCurrentSide(); } }