Merge pull request #257 from mredigonda/admin-recover-password-issue-161

Admin recover password issue 161
This commit is contained in:
Ivan Diaz 2018-07-31 01:46:37 +02:00 committed by GitHub
commit 51b086d63e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 305 additions and 51 deletions

View File

@ -0,0 +1,72 @@
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,
renderLogo: React.PropTypes.bool
};
static defaultProps = {
renderLogo: false
};
render() {
return (
<Widget {...this.props} className="password-recovery__content">
{this.renderLogo()}
<Form {...this.props.formProps}>
<div className="password-recovery__inputs">
<FormField placeholder={i18n('EMAIL_LOWERCASE')} name="email" className="password-recovery__input" validation="EMAIL" required/>
</div>
<div className="password-recovery__submit-button">
<SubmitButton type="primary">{i18n('RECOVER_PASSWORD')}</SubmitButton>
</div>
</Form>
<Button className="password-recovery__forgot-password" type="link" onClick={this.props.onBackToLoginClick} onMouseDown={(event) => {event.preventDefault()}}>
{i18n('BACK_LOGIN_FORM')}
</Button>
{this.renderRecoverStatus()}
</Widget>
);
}
renderLogo() {
let logo = null;
if (this.props.renderLogo) {
logo = (<div className="password-recovery__image"><img width="100%" src={API.getURL() + '/images/logo.png'} alt="OpenSupports Login Panel"/></div>);
}
return logo;
}
renderRecoverStatus() {
let status = null;
if (this.props.recoverSent) {
status = (
<Message className="password-recovery__message" type="info" leftAligned>
{i18n('RECOVER_SENT')}
</Message>
);
}
return status;
}
}
export default PasswordRecovery;

View File

@ -0,0 +1,26 @@
.password-recovery {
&__inputs {
display: inline-block;
margin: 0 auto 20px;
text-align: left;
}
&__content {
margin: 0 auto;
padding: 40px;
}
&__forgot-password {
margin-top: 20px;
}
&__message {
margin-top: 18px;
}
&__image {
width: 365px;
margin-bottom: 30px;
}
}

View File

@ -1,55 +1,203 @@
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';
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',
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 (
return (
<div className="admin-login-page">
<WidgetTransition sideToShow={this.state.sideToShow} className={classNames('admin-login-page__container', this.props.className)}>
{this.renderLogin()}
{this.renderPasswordRecovery()}
</WidgetTransition>
</div>
);
}
renderLogin() {
return (
<div>
<Widget className="admin-login-page__content">
<div className="admin-login-page__image"><img width="100%" src={API.getURL() + '/images/logo.png'} alt="OpenSupports Admin Panel"/></div>
<div className="admin-login-page__login-form">
<Form onSubmit={this.onSubmit.bind(this)} loading={this.props.session.pending}>
<Form onSubmit={this.onLoginFormSubmit.bind(this)} loading={this.props.session.pending}>
<FormField name="email" label={i18n('EMAIL')} field="input" validation="EMAIL" fieldProps={{size:'large'}} required />
<FormField name="password" label={i18n('PASSWORD')} field="input" fieldProps={{password:true, size:'large'}} />
<SubmitButton>{i18n('LOG_IN')}</SubmitButton>
</Form>
</div>
{this.renderMessage()}
{this.renderRecoverStatus()}
{this.renderErrorStatus()}
<Button className="login-widget__forgot-password" type="link" onClick={this.onForgotPasswordClick.bind(this)} onMouseDown={(event) => {event.preventDefault()}}>
{i18n('FORGOT_PASSWORD')}
</Button>
</Widget>
</div>
);
}
renderPasswordRecovery() {
return (
<div>
<PasswordRecovery recoverSent={this.state.recoverSent} formProps={this.getRecoverFormProps()} onBackToLoginClick={this.onBackToLoginClick.bind(this)} renderLogo={true}/>
</div>
);
}
renderMessage() {
let message = null;
renderRecoverStatus() {
let status = null;
if(this.props.session.failed) {
message = (
if (this.state.recoverSent) {
status = (
<Message className="admin-login-page__message" type="info" leftAligned>
{i18n('RECOVER_SENT')}
</Message>
);
}
return status;
}
renderErrorStatus() {
let status = null;
if (this.props.session.failed) {
status = (
<Message className="admin-login-page__error" type="error">
{i18n('EMAIL_OR_PASSWORD')}
</Message>
);
}
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) => {

View File

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

View File

@ -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,20 +66,7 @@ class MainHomePageLoginWidget extends React.Component {
renderPasswordRecovery() {
return (
<Widget className="main-home-page__widget login-widget_password" title={i18n('RECOVER_PASSWORD')} ref="recoverWidget">
<Form {...this.getRecoverFormProps()}>
<div className="login-widget__inputs">
<FormField placeholder={i18n('EMAIL_LOWERCASE')} name="email" className="login-widget__input" validation="EMAIL" required/>
</div>
<div className="login-widget__submit-button">
<SubmitButton type="primary">{i18n('RECOVER_PASSWORD')}</SubmitButton>
</div>
</Form>
<Button className="login-widget__forgot-password" type="link" onClick={this.onBackToLoginClick.bind(this)} onMouseDown={(event) => {event.preventDefault()}}>
{i18n('BACK_LOGIN_FORM')}
</Button>
{this.renderRecoverStatus()}
</Widget>
<PasswordRecovery recoverSent={this.state.recoverSent} formProps={this.getRecoverFormProps()} onBackToLoginClick={this.onBackToLoginClick.bind(this)}/>
);
}
@ -163,14 +151,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 +178,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);
}
}
}

View File

@ -17,4 +17,4 @@
&__message {
margin-top: 18px;
}
}
}

View File

@ -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;
export default MainRecoverPasswordPage;

View File

@ -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,12 @@ class WidgetTransition extends React.Component {
};
}
componentDidUpdate(prevProps) {
if (prevProps.sideToShow != this.props.sideToShow && this.primaryWidget && this.secondaryWidget) {
this.moveFocusToCurrentSide();
}
}
render() {
return (
<Motion defaultStyle={this.getDefaultAnimation()} style={this.getAnimation()}>
@ -30,7 +38,7 @@ class WidgetTransition extends React.Component {
renderChildren(animation) {
return (
<div className={this.getClass()}>
{React.Children.map(this.props.children, function (child, index) {
{React.Children.map(this.props.children, (child, index) => {
let modifiedChild;
if (index === 0) {
@ -38,14 +46,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 +79,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;

View File

@ -77,7 +77,9 @@ module.exports = [
if (data.password.length > 6) {
return {
status: 'success',
data: {}
data: {
staff: true
}
};
} else {
return {