Forgot password and reset password

This commit is contained in:
John Lyon-Smith
2018-03-22 14:56:39 -07:00
parent 82fbd88dab
commit 06ae76047e
15 changed files with 324 additions and 205 deletions

View File

@@ -1,12 +1,15 @@
import React from 'react'
import React, { Component, Fragment } from 'react'
import PropTypes from 'prop-types'
import { regExpPattern } from 'regexp-pattern'
import { Text, Column, BoundInput, BoundButton } from 'ui'
import { Image, Text, Column, Row, BoundInput, BoundButton, Box } from 'ui'
import { MessageModal, WaitModal } from '../Modal'
import { api } from 'src/API'
import { FormBinder } from 'react-form-binder'
import headerLogo from 'images/deighton.png'
import { sizeInfo, colorInfo } from 'ui/style'
import autobind from 'autobind-decorator'
export class ForgotPassword extends React.Component {
export class ForgotPassword extends Component {
static propTypes = {
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
}
@@ -29,74 +32,109 @@ export class ForgotPassword extends React.Component {
messageModal: null,
waitModal: null
}
this.handleSubmit = this.handleSubmit.bind(this)
this.handleMessageModalDismiss = this.handleMessageModalDismiss.bind(this)
}
handleSubmit() {
const obj = this.state.binder.getValues()
@autobind
handleSubmit(e) {
e.preventDefault()
e.stopPropagation()
const obj = this.state.binder.getModifiedFieldValues()
this.setState({ waitModal: { message: 'Requesting Reset Email' } })
api.sendResetPassword(obj.email).then((res) => {
const email = this.state.binder.getField('email').value
const cb = (res) => {
this.setState({
waitModal: null,
messageModal: {
error: false,
icon: 'thumb',
title: 'Password Reset Requested',
message: `An email will be sent to '${email}' with a reset link. Please click on it to finish resetting the password.`
message: `If everything checks out, an email will be sent to '${obj.email}' with a reset link. Please click on it to finish resetting the password.`
}
})
}).catch((error) => {
this.setState({
binder: new FormBinder({}, ForgotPassword.bindings), // Reset to avoid rapid retries
waitModal: null,
messageModal: {
error: true,
title: 'Password Reset Failed',
message: `There was a problem requesting the password reset. ${error ? error.message : ''}`
}
})
})
}
api.sendResetPassword(obj.email).then(cb, cb)
}
@autobind
handleMessageModalDismiss() {
this.props.history.replace('/')
}
render() {
const { binder, waitModal, messageModal } = this.state
return (
<div>
<form onSubmit={this.handleSubmit}>
<Column>
<Column.Item>
<Text size='large'>Forgotten Password</Text>
</Column.Item>
<Column.Item>
<BoundInput label='Email' name='email'
placeholder='example@xyz.com' binder={this.state.binder}
message='A valid email address' />
</Column.Item>
<Column.Item>
<Text>The email address of an existing user to send the password reset link to.</Text>
</Column.Item>
<Column.Item>
<BoundButton name='submit' content='Submit'
primary submit binder={this.state.binder}>Submit</BoundButton>
</Column.Item>
</Column>
</form>
<Fragment>
<Column.Item grow />
<Column.Item>
<Row>
<Row.Item grow />
<Row.Item width={sizeInfo.formRowSpacing} />
<Row.Item width={sizeInfo.modalWidth}>
<form id='forgotPasswordForm' onSubmit={this.handleSubmit}>
<Box border={{ width: sizeInfo.headerBorderWidth, color: colorInfo.headerBorder }} radius={sizeInfo.formBoxRadius}>
<Row>
<Row.Item width={sizeInfo.formRowSpacing} />
<Row.Item>
<Column>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item>
<Row>
<Row.Item grow />
<Row.Item>
<Image source={headerLogo} width={sizeInfo.loginLogoWidth} />
</Row.Item>
<Row.Item grow />
</Row>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item>
<Text size='large'>Forgotten Password</Text>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item>
<BoundInput label='Email' name='email'
placeholder='example@xyz.com' binder={this.state.binder}
message='A valid email address' />
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item>
<Text>The email address of an existing user to send the password reset link to.</Text>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item minHeight={sizeInfo.buttonHeight}>
<Row>
<Row.Item grow />
<Row.Item>
<BoundButton text='Submit' name='submit' submit='forgotPasswordForm' binder={binder} />
</Row.Item>
</Row>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
</Column>
</Row.Item>
<Row.Item width={sizeInfo.formRowSpacing} />
</Row>
</Box>
</form>
</Row.Item>
<Row.Item grow />
</Row>
</Column.Item>
<Column.Item grow>
<WaitModal active={!!waitModal}
message={waitModal ? waitModal.message : ''} />
<WaitModal active={!!this.state.waitModal}
message={this.state.waitModal ? this.state.waitModal.message : ''} />
<MessageModal
open={!!this.state.messageModal}
error={this.state.messageModal ? this.state.messageModal.error : true}
title={this.state.messageModal ? this.state.messageModal.title : ''}
message={this.state.messageModal ? this.state.messageModal.message : ''}
onDismiss={this.handleMessageModalDismiss} />
</div>
<MessageModal
open={!!messageModal}
icon={messageModal ? messageModal.icon : ''}
message={messageModal ? messageModal.message : ''}
detail={messageModal ? messageModal.detail : ''}
onDismiss={this.handleMessageModalDismiss} />
</Column.Item>
</Fragment>
)
}
}

View File

@@ -3,12 +3,12 @@ import PropTypes from 'prop-types'
import { regExpPattern } from 'regexp-pattern'
import { api } from 'src/API'
import { WaitModal, MessageModal } from '../Modal'
import { Image, Link, Text, Row, Column, BoundInput, BoundCheckbox, BoundButton } from 'ui'
import { Box, Image, Link, Text, Row, Column, BoundInput, BoundCheckbox, BoundButton } from 'ui'
import headerLogo from 'images/deighton.png'
import { versionInfo } from '../version'
import { FormBinder } from 'react-form-binder'
import { reactAutoBind } from 'auto-bind2'
import { sizeInfo } from 'ui/style'
import { sizeInfo, colorInfo } from 'ui/style'
export class Login extends Component {
static propTypes = {
@@ -103,52 +103,67 @@ export class Login extends Component {
<Row.Item grow />
<Row.Item width={sizeInfo.modalWidth}>
<form onSubmit={this.handleSubmit} id='loginForm'>
<Column minHeight='100%'>
<Column.Item>
<Row>
<Row.Item grow />
<Row.Item>
<Image source={headerLogo} width={sizeInfo.loginLogoWidth} />
</Row.Item>
<Row.Item grow />
</Row>
</Column.Item>
<Column.Item>
<BoundInput label='Email' name='email'
placeholder='example@xyz.com' binder={this.state.binder}
message='Enter the email address associated with your account.' />
</Column.Item>
<Column.Item>
<BoundInput password label='Password' name='password'
binder={this.state.binder} message='Enter your password.' />
</Column.Item>
<Column.Item>
<Row>
<Row.Item>
<Link to='/forgot-password'>Forgot your password?</Link>
</Row.Item>
<Row.Item grow />
<Row.Item>
<BoundCheckbox label='Remember Me' name='rememberMe' binder={this.state.binder} />
</Row.Item>
</Row>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item height={sizeInfo.buttonHeight}>
<Row>
<Row.Item grow />
<Row.Item>
<BoundButton name='submit' text='Login' submit='loginForm' binder={this.state.binder} />
</Row.Item>
</Row>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item>
<Text>
Please contact <Link to={`mailto:${versionInfo.supportEmail}`}>{versionInfo.supportEmail}</Link> to request login credentials.
</Text>
</Column.Item>
</Column>
<Box border={{ width: sizeInfo.headerBorderWidth, color: colorInfo.headerBorder }} radius={sizeInfo.formBoxRadius}>
<Row>
<Row.Item width={sizeInfo.formRowSpacing} />
<Row.Item>
<Column>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item>
<Row>
<Row.Item grow />
<Row.Item>
<Image source={headerLogo} width={sizeInfo.loginLogoWidth} />
</Row.Item>
<Row.Item grow />
</Row>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item>
<Text size='large'>Login</Text>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item>
<BoundInput label='Email' name='email'
placeholder='example@xyz.com' binder={this.state.binder}
message='Enter the email address associated with your account.' />
</Column.Item>
<Column.Item>
<BoundInput password label='Password' name='password'
binder={this.state.binder} message='Enter your password.' />
</Column.Item>
<Column.Item>
<Row>
<Row.Item>
<Link to='/forgot-password'>Forgot your password?</Link>
</Row.Item>
<Row.Item grow />
<Row.Item>
<BoundCheckbox label='Remember Me' name='rememberMe' binder={this.state.binder} />
</Row.Item>
</Row>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item height={sizeInfo.buttonHeight}>
<Row>
<Row.Item grow />
<Row.Item>
<BoundButton name='submit' text='Login' submit='loginForm' binder={this.state.binder} />
</Row.Item>
</Row>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item>
<Text>
Please contact <Link to={`mailto:${versionInfo.supportEmail}`}>{versionInfo.supportEmail}</Link> to request login credentials.
</Text>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
</Column>
</Row.Item>
<Row.Item width={sizeInfo.formRowSpacing} />
</Row>
</Box>
</form>
</Row.Item>
<Row.Item grow />

View File

@@ -1,11 +1,14 @@
import React from 'react'
import React, { Component, Fragment } from 'react'
import PropTypes from 'prop-types'
import { Text, Column, BoundInput, BoundButton } from 'ui'
import { Box, Text, Image, Column, Row, BoundInput, BoundButton } from 'ui'
import { MessageModal, WaitModal } from '../Modal'
import { api } from 'src/API'
import { FormBinder } from 'react-form-binder'
import { sizeInfo, colorInfo } from 'ui/style'
import autobind from 'autobind-decorator'
import headerLogo from 'images/deighton.png'
export class ResetPassword extends React.Component {
export class ResetPassword extends Component {
static propTypes = {
history: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
}
@@ -16,11 +19,11 @@ export class ResetPassword extends React.Component {
isValid: (r, v) => (v.length >= 6)
},
reenteredNewPassword: {
isValid: (r, v) => (v !== '' && v === r.getField('newPassword').value)
isValid: (r, v) => (v !== '' && v === r.getFieldValue('newPassword'))
},
submit: {
noValue: true,
isDisabled: (r) => (!r.anyModified && !r.allValid)
isDisabled: (r) => (!r.anyModified || !r.allValid)
}
}
@@ -31,73 +34,112 @@ export class ResetPassword extends React.Component {
messageModal: null,
waitModal: null
}
this.handleSubmit = this.handleSubmit.bind(this)
this.handleMessageModalDismiss = this.handleMessageModalDismiss.bind(this)
}
handleSubmit() {
const obj = this.state.binder.getValues()
@autobind
handleSubmit(e) {
e.preventDefault()
e.stopPropagation()
const obj = this.state.binder.getModifiedFieldValues()
const passwordToken = new URLSearchParams(decodeURIComponent(window.location.search)).get('password-token')
this.setState({ waitModal: { message: 'Setting Password...' } })
api.resetPassword({ newPassword: obj.newPassword, passwordToken }).then(() => {
this.setState({ waitModal: null })
this.props.history.replace('/login')
}).catch((error) => {
}).catch((err) => {
this.setState({
binder: new FormBinder({}, ResetPassword.bindings), // Reset to avoid accidental rapid retries
waitModal: null,
messageModal: {
title: 'We had a problem changing your password',
message: error.message
icon: 'hand',
title: 'There was a problem changing your password',
detail: err.message,
}
})
})
}
@autobind
handleMessageModalDismiss() {
this.setState({ messageModal: null })
}
render() {
const { messageModal, waitModal, binder } = this.state
return (
<div>
<form onSubmit={this.handleSubmit} id='resetPassword'>
<Column>
<Column.Item>
<Text size='large'>Reset Password</Text>
</Column.Item>
<Column.Item>
<BoundInput label='New Password' password name='newPassword'
message='A new password, cannot be blank or the same as your old password'
binder={this.state.binder} />
</Column.Item>
<Column.Item>
<BoundInput label='Re-entered New Password' password name='reenteredNewPassword'
message='The new password again, must match and cannot be blank'
binder={this.state.binder} />
</Column.Item>
<Column.Item>
<Text>
Passwords can contain special characters and should be unique to this application.
<br /><br />
Passwords must be at least 6 characters long.
</Text>
</Column.Item>
<Column.Item>
<BoundButton name='submit' text='Submit' submit='resetPassword' binder={this.state.binder} />
</Column.Item>
</Column>
</form>
<Fragment>
<Column.Item grow />
<Column.Item>
<Row>
<Row.Item grow />
<Row.Item width={sizeInfo.formRowSpacing} />
<Row.Item width={sizeInfo.modalWidth}>
<form onSubmit={this.handleSubmit} id='resetPasswordForm'>
<Box border={{ width: sizeInfo.headerBorderWidth, color: colorInfo.headerBorder }} radius={sizeInfo.formBoxRadius}>
<Row>
<Row.Item width={sizeInfo.formRowSpacing} />
<Row.Item>
<Column>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item>
<Row>
<Row.Item grow />
<Row.Item>
<Image source={headerLogo} width={sizeInfo.loginLogoWidth} />
</Row.Item>
<Row.Item grow />
</Row>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item>
<Text size='large'>Reset Password</Text>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item>
<BoundInput label='New Password' password name='newPassword'
message='A new password, cannot be blank or the same as your old password'
binder={binder} />
</Column.Item>
<Column.Item>
<BoundInput label='Re-enter New Password' password name='reenteredNewPassword'
message='The new password again, must match and cannot be blank'
binder={binder} />
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
<Column.Item minHeight={sizeInfo.buttonHeight}>
<Row>
<Row.Item grow />
<Row.Item>
<BoundButton text='Submit' name='submit' submit='resetPasswordForm' binder={binder} />
</Row.Item>
</Row>
</Column.Item>
<Column.Item height={sizeInfo.formColumnSpacing} />
</Column>
</Row.Item>
<Row.Item width={sizeInfo.formRowSpacing} />
</Row>
</Box>
</form>
</Row.Item>
<Row.Item grow />
</Row>
</Column.Item>
<Column.Item grow>
<MessageModal
open={!!messageModal}
icon={messageModal ? messageModal.icon : ''}
title={messageModal ? messageModal.title : ''}
message={messageModal ? messageModal.message : ''}
onDismiss={this.handleMessageModalDismiss} />
<MessageModal error open={!!this.state.messageModal}
title={this.state.messageModal ? this.state.messageModal.title : ''}
message={this.state.messageModal ? this.state.messageModal.message : ''}
onDismiss={this.handleMessageModalDismiss} />
<WaitModal active={!!this.state.waitModal}
message={this.state.waitModal ? this.state.waitModal.message : ''} />
</div>
<WaitModal active={!!waitModal}
message={waitModal ? waitModal.message : ''} />
</Column.Item>
</Fragment>
)
}
}