diff options
author | Anike Arni <anikarni@gmail.com> | 2017-03-29 15:48:33 -0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-03-29 15:48:33 -0300 |
commit | 3616a6b950b0fb9d3e4842f90878d0beb4d5bc3f (patch) | |
tree | 5064ad57cb88df4efefbb8c75a65c0b6dc822c83 /web-ui/src | |
parent | 75eccfc3ea988a20919c95e892b314ccd816e7c2 (diff) | |
parent | 6daf4311d32ab64fa73292e131611ee6a38ff18a (diff) |
Merge pull request #1032 from pixelated/forgot-password-page
UI improvements for account recovery flow
Diffstat (limited to 'web-ui/src')
-rw-r--r-- | web-ui/src/account_recovery/forms/admin_recovery_code_form.js | 7 | ||||
-rw-r--r-- | web-ui/src/account_recovery/forms/forms.scss | 76 | ||||
-rw-r--r-- | web-ui/src/account_recovery/forms/new_password_form.js | 23 | ||||
-rw-r--r-- | web-ui/src/account_recovery/forms/new_password_form.spec.js | 16 | ||||
-rw-r--r-- | web-ui/src/account_recovery/forms/user_recovery_code_form.js | 20 | ||||
-rw-r--r-- | web-ui/src/account_recovery/forms/user_recovery_code_form.spec.js | 16 | ||||
-rw-r--r-- | web-ui/src/account_recovery/page.js | 8 | ||||
-rw-r--r-- | web-ui/src/account_recovery/page.scss | 22 | ||||
-rw-r--r-- | web-ui/src/account_recovery/page.spec.js | 19 | ||||
-rw-r--r-- | web-ui/src/backup_account/backup_email/backup_email.js | 16 | ||||
-rw-r--r-- | web-ui/src/backup_account/page.scss | 14 | ||||
-rw-r--r-- | web-ui/src/common/back_link/back_link.js | 35 | ||||
-rw-r--r-- | web-ui/src/common/back_link/back_link.scss | 31 | ||||
-rw-r--r-- | web-ui/src/common/back_link/back_link.spec.js | 20 |
14 files changed, 268 insertions, 55 deletions
diff --git a/web-ui/src/account_recovery/forms/admin_recovery_code_form.js b/web-ui/src/account_recovery/forms/admin_recovery_code_form.js index 75cf6ea5..3d97b191 100644 --- a/web-ui/src/account_recovery/forms/admin_recovery_code_form.js +++ b/web-ui/src/account_recovery/forms/admin_recovery_code_form.js @@ -24,13 +24,18 @@ import SubmitButton from 'src/common/submit_button/submit_button'; import './forms.scss'; export const AdminRecoveryCodeForm = ({ t, next }) => ( - <form className='admin-code-form' onSubmit={next}> + <form className='account-recovery-form admin-code' onSubmit={next}> <img className='account-recovery-progress' src='/public/images/account-recovery/step_1.svg' alt={t('account-recovery.admin-form.image-description')} /> <h1>{t('account-recovery.admin-form.title')}</h1> + <img + className='admin-codes-image' + src='/public/images/account-recovery/admins_contact.svg' + alt='' + /> <ul> <li>{t('account-recovery.admin-form.tip1')}</li> <li>{t('account-recovery.admin-form.tip2')}</li> diff --git a/web-ui/src/account_recovery/forms/forms.scss b/web-ui/src/account_recovery/forms/forms.scss index 1ef0e8d3..09d8f2ce 100644 --- a/web-ui/src/account_recovery/forms/forms.scss +++ b/web-ui/src/account_recovery/forms/forms.scss @@ -15,6 +15,78 @@ * along with Pixelated. If not, see <http://www.gnu.org/licenses/>. */ -.account-recovery-progress { - width: 100%; +.account-recovery-form { + display: flex; + flex-direction: column; + + img { + margin: 1em 0; + align-self: center; + } + + .user-code-form-content { + display: flex; + flex-direction: column; + align-items: center; + } + + .account-recovery-progress { + width: 100%; + } + + .admin-code-image { + height: 2.7em; + } + + .user-code-image { + height: 4em; + } + + .input-field-group { + margin-top: 0; + } +} + +.new-password { + .input-field-group:first-of-type { + margin-bottom: 0; + } +} + +@media only screen and (min-width : 500px) { + .account-recovery-form { + align-items: center; + + .account-recovery-progress, h1 { + width: 80%; + } + + .user-code-form-content { + flex-direction: row; + width: 80%; + + img { + margin: 1.6em; + } + } + } +} + +@media only screen and (min-width : 960px) { + .account-recovery-form { + .account-recovery-progress { + width: 80%; + margin-top: 0; + } + + h1 { + max-width: 80%; + width: auto; + } + + .input-field-group, .submit-button { + width: 60%; + align-self: center; + } + } } diff --git a/web-ui/src/account_recovery/forms/new_password_form.js b/web-ui/src/account_recovery/forms/new_password_form.js index 71239cfa..114366b3 100644 --- a/web-ui/src/account_recovery/forms/new_password_form.js +++ b/web-ui/src/account_recovery/forms/new_password_form.js @@ -20,23 +20,36 @@ import { translate } from 'react-i18next'; import InputField from 'src/common/input_field/input_field'; import SubmitButton from 'src/common/submit_button/submit_button'; +import BackLink from 'src/common/back_link/back_link'; -export const NewPasswordForm = ({ t }) => ( - <form> +export const NewPasswordForm = ({ t, previous }) => ( + <form className='account-recovery-form new-password'> <img className='account-recovery-progress' src='/public/images/account-recovery/step_3.svg' alt={t('account-recovery.new-password.image-description')} /> <h1>{t('account-recovery.new-password-form.title')}</h1> - <InputField name='new-password' label={t('account-recovery.new-password-form.input-label1')} /> - <InputField name='confirm-password' label={t('account-recovery.new-password-form.input-label2')} /> + <InputField + type='password' name='new-password' + label={t('account-recovery.new-password-form.input-label1')} + /> + <InputField + type='password' name='confirm-password' + label={t('account-recovery.new-password-form.input-label2')} + /> <SubmitButton buttonText={t('account-recovery.new-password-form.button')} /> + <BackLink + text={t('account-recovery.back')} + onClick={previous} onKeyDown={previous} + role='button' + /> </form> ); NewPasswordForm.propTypes = { - t: React.PropTypes.func.isRequired + t: React.PropTypes.func.isRequired, + previous: React.PropTypes.func.isRequired }; export default translate('', { wait: true })(NewPasswordForm); diff --git a/web-ui/src/account_recovery/forms/new_password_form.spec.js b/web-ui/src/account_recovery/forms/new_password_form.spec.js index a2986165..5ac96b40 100644 --- a/web-ui/src/account_recovery/forms/new_password_form.spec.js +++ b/web-ui/src/account_recovery/forms/new_password_form.spec.js @@ -5,11 +5,13 @@ import { NewPasswordForm } from 'src/account_recovery/forms/new_password_form'; describe('NewPasswordForm', () => { let newPasswordForm; + let mockPrevious; beforeEach(() => { const mockTranslations = key => key; + mockPrevious = expect.createSpy(); newPasswordForm = shallow( - <NewPasswordForm t={mockTranslations} /> + <NewPasswordForm t={mockTranslations} previous={mockPrevious} /> ); }); @@ -18,14 +20,26 @@ describe('NewPasswordForm', () => { }); it('renders input for new password', () => { + expect(newPasswordForm.find('InputField').at(0).props().type).toEqual('password'); expect(newPasswordForm.find('InputField').at(0).props().label).toEqual('account-recovery.new-password-form.input-label1'); }); it('renders input to confirm new password', () => { + expect(newPasswordForm.find('InputField').at(1).props().type).toEqual('password'); expect(newPasswordForm.find('InputField').at(1).props().label).toEqual('account-recovery.new-password-form.input-label2'); }); it('renders submit button', () => { expect(newPasswordForm.find('SubmitButton').props().buttonText).toEqual('account-recovery.new-password-form.button'); }); + + it('returns to previous step on link click', () => { + newPasswordForm.find('BackLink').simulate('click'); + expect(mockPrevious).toHaveBeenCalled(); + }); + + it('returns to previous step on key down', () => { + newPasswordForm.find('BackLink').simulate('keyDown'); + expect(mockPrevious).toHaveBeenCalled(); + }); }); diff --git a/web-ui/src/account_recovery/forms/user_recovery_code_form.js b/web-ui/src/account_recovery/forms/user_recovery_code_form.js index aaefd75d..30525cdf 100644 --- a/web-ui/src/account_recovery/forms/user_recovery_code_form.js +++ b/web-ui/src/account_recovery/forms/user_recovery_code_form.js @@ -20,25 +20,39 @@ import { translate } from 'react-i18next'; import InputField from 'src/common/input_field/input_field'; import SubmitButton from 'src/common/submit_button/submit_button'; +import BackLink from 'src/common/back_link/back_link'; import './forms.scss'; -export const UserRecoveryCodeForm = ({ t, next }) => ( - <form className='user-code-form' onSubmit={next}> +export const UserRecoveryCodeForm = ({ t, previous, next }) => ( + <form className='account-recovery-form user-code' onSubmit={next}> <img className='account-recovery-progress' src='/public/images/account-recovery/step_2.svg' alt={t('account-recovery.user-form.image-description')} /> <h1>{t('account-recovery.user-form.title')}</h1> - <p>{t('account-recovery.user-form.description')}</p> + <div className='user-code-form-content'> + <img + className='user-codes-image' + src='/public/images/account-recovery/codes.svg' + alt='' + /> + <p>{t('account-recovery.user-form.description')}</p> + </div> <InputField name='admin-code' label={t('account-recovery.user-form.input-label')} /> <SubmitButton buttonText={t('account-recovery.user-form.button')} /> + <BackLink + text={t('account-recovery.back')} + onClick={previous} onKeyDown={previous} + role='button' + /> </form> ); UserRecoveryCodeForm.propTypes = { t: React.PropTypes.func.isRequired, + previous: React.PropTypes.func.isRequired, next: React.PropTypes.func.isRequired }; diff --git a/web-ui/src/account_recovery/forms/user_recovery_code_form.spec.js b/web-ui/src/account_recovery/forms/user_recovery_code_form.spec.js index a20d3b7b..1aebb814 100644 --- a/web-ui/src/account_recovery/forms/user_recovery_code_form.spec.js +++ b/web-ui/src/account_recovery/forms/user_recovery_code_form.spec.js @@ -6,12 +6,16 @@ import { UserRecoveryCodeForm } from 'src/account_recovery/forms/user_recovery_c describe('UserRecoveryCodeForm', () => { let userRecoveryCodeForm; let mockNext; + let mockPrevious; beforeEach(() => { const mockTranslations = key => key; mockNext = expect.createSpy(); + mockPrevious = expect.createSpy(); userRecoveryCodeForm = shallow( - <UserRecoveryCodeForm t={mockTranslations} next={mockNext} /> + <UserRecoveryCodeForm + t={mockTranslations} next={mockNext} previous={mockPrevious} + /> ); }); @@ -35,4 +39,14 @@ describe('UserRecoveryCodeForm', () => { userRecoveryCodeForm.find('form').simulate('submit'); expect(mockNext).toHaveBeenCalled(); }); + + it('returns to previous step on link click', () => { + userRecoveryCodeForm.find('BackLink').simulate('click'); + expect(mockPrevious).toHaveBeenCalled(); + }); + + it('returns to previous step on key down', () => { + userRecoveryCodeForm.find('BackLink').simulate('keyDown'); + expect(mockPrevious).toHaveBeenCalled(); + }); }); diff --git a/web-ui/src/account_recovery/page.js b/web-ui/src/account_recovery/page.js index f867fcd5..3043a38b 100644 --- a/web-ui/src/account_recovery/page.js +++ b/web-ui/src/account_recovery/page.js @@ -40,10 +40,14 @@ export class Page extends React.Component { this.setState({ step: this.state.step + 1 }); } + previousStep = () => { + this.setState({ step: this.state.step - 1 }); + } + steps = { 0: <AdminRecoveryCodeForm next={this.nextStep} />, - 1: <UserRecoveryCodeForm next={this.nextStep} />, - 2: <NewPasswordForm /> + 1: <UserRecoveryCodeForm previous={this.previousStep} next={this.nextStep} />, + 2: <NewPasswordForm previous={this.previousStep} /> } mainContent = () => this.steps[this.state.step]; diff --git a/web-ui/src/account_recovery/page.scss b/web-ui/src/account_recovery/page.scss index a5d62e01..20604b70 100644 --- a/web-ui/src/account_recovery/page.scss +++ b/web-ui/src/account_recovery/page.scss @@ -66,22 +66,16 @@ p { margin-bottom: 0.5em; } -.link { - color: $dark_blue; - font-style: italic; - font-size: 0.8em; - - .fa { - font-size: 1.6em; - position: relative; - top: 3px; - margin-right: 0.3em; - } - -} - @media only screen and (min-width : 500px) { body { font-size: 1.3em; } } + +@media only screen and (min-width : 960px) { + .container { + width: 60%; + max-width: 700px; + padding: 3em; + } +} diff --git a/web-ui/src/account_recovery/page.spec.js b/web-ui/src/account_recovery/page.spec.js index f5e52c85..32e1477c 100644 --- a/web-ui/src/account_recovery/page.spec.js +++ b/web-ui/src/account_recovery/page.spec.js @@ -45,18 +45,29 @@ describe('Account Recovery Page', () => { it('renders user recovery code form when admin code submitted', () => { pageInstance.nextStep({ preventDefault: () => {} }); - expect(page.find(AdminRecoveryCodeForm).length).toEqual(0); expect(page.find(UserRecoveryCodeForm).length).toEqual(1); - expect(page.find(NewPasswordForm).length).toEqual(0); + }); + + it('returns to admin code form on user code form back link', () => { + pageInstance.nextStep({ preventDefault: () => {} }); + pageInstance.previousStep(); + + expect(page.find(AdminRecoveryCodeForm).length).toEqual(1); }); it('renders new password form when user code submitted', () => { pageInstance.nextStep({ preventDefault: () => {} }); pageInstance.nextStep({ preventDefault: () => {} }); - expect(page.find(AdminRecoveryCodeForm).length).toEqual(0); - expect(page.find(UserRecoveryCodeForm).length).toEqual(0); expect(page.find(NewPasswordForm).length).toEqual(1); }); + + it('returns to user code form on new password form back link', () => { + pageInstance.nextStep({ preventDefault: () => {} }); + pageInstance.nextStep({ preventDefault: () => {} }); + pageInstance.previousStep(); + + expect(page.find(UserRecoveryCodeForm).length).toEqual(1); + }); }); }); diff --git a/web-ui/src/backup_account/backup_email/backup_email.js b/web-ui/src/backup_account/backup_email/backup_email.js index 7e6cb66c..9d622d8d 100644 --- a/web-ui/src/backup_account/backup_email/backup_email.js +++ b/web-ui/src/backup_account/backup_email/backup_email.js @@ -18,11 +18,13 @@ import 'isomorphic-fetch'; import React from 'react'; import { translate } from 'react-i18next'; -import SubmitButton from 'src/common/submit_button/submit_button'; -import InputField from 'src/common/input_field/input_field'; import validator from 'validator'; import browser from 'helpers/browser'; +import SubmitButton from 'src/common/submit_button/submit_button'; +import InputField from 'src/common/input_field/input_field'; +import BackLink from 'src/common/back_link/back_link'; + import './backup_email.scss'; export class BackupEmail extends React.Component { @@ -78,12 +80,10 @@ export class BackupEmail extends React.Component { <p>{t('backup-account.backup-email.paragraph2')}</p> <InputField name='email' label={t('backup-account.backup-email.input-label')} errorText={this.state.error} onChange={this.validateEmail} /> <SubmitButton buttonText={t('backup-account.backup-email.button')} disabled={this.state.submitButtonDisabled} /> - <div className='link-content'> - <a href='/' className='link'> - <i className='fa fa-angle-left' aria-hidden='true' /> - <span>{t('back-to-inbox')}</span> - </a> - </div> + <BackLink + href='/' + text={t('back-to-inbox')} + /> </form> </div> ); diff --git a/web-ui/src/backup_account/page.scss b/web-ui/src/backup_account/page.scss index 71e3f074..d4f1f887 100644 --- a/web-ui/src/backup_account/page.scss +++ b/web-ui/src/backup_account/page.scss @@ -64,20 +64,6 @@ p { margin-bottom: 0.5em; } -.link { - color: $dark_blue; - font-style: italic; - font-size: 0.8em; - - .fa { - font-size: 1.6em; - position: relative; - top: 3px; - margin-right: 0.3em; - } - -} - @media only screen and (min-width : 500px) { body { font-size: 1.3em; diff --git a/web-ui/src/common/back_link/back_link.js b/web-ui/src/common/back_link/back_link.js new file mode 100644 index 00000000..f3bdb2b5 --- /dev/null +++ b/web-ui/src/common/back_link/back_link.js @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2017 ThoughtWorks, Inc. + * + * Pixelated is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Pixelated is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Pixelated. If not, see <http://www.gnu.org/licenses/>. + */ + +import React from 'react'; + +import './back_link.scss'; + +const BackLink = ({ text, ...other }) => ( + <div className='link-content'> + <a className='link' tabIndex='0' {...other}> + <i className='fa fa-angle-left' aria-hidden='true' /> + <span>{text}</span> + </a> + </div> +); + +BackLink.propTypes = { + text: React.PropTypes.string.isRequired +}; + +export default BackLink; diff --git a/web-ui/src/common/back_link/back_link.scss b/web-ui/src/common/back_link/back_link.scss new file mode 100644 index 00000000..a799a710 --- /dev/null +++ b/web-ui/src/common/back_link/back_link.scss @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2017 ThoughtWorks, Inc. + * + * Pixelated is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Pixelated is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Pixelated. If not, see <http://www.gnu.org/licenses/>. + */ + +@import "~scss/base/colors"; + +.link { + color: $dark_blue; + font-style: italic; + font-size: 0.8em; + + .fa { + font-size: 1.6em; + position: relative; + top: 3px; + margin-right: 0.3em; + } +} diff --git a/web-ui/src/common/back_link/back_link.spec.js b/web-ui/src/common/back_link/back_link.spec.js new file mode 100644 index 00000000..ee659267 --- /dev/null +++ b/web-ui/src/common/back_link/back_link.spec.js @@ -0,0 +1,20 @@ +import { shallow } from 'enzyme'; +import expect from 'expect'; +import React from 'react'; +import BackLink from 'src/common/back_link/back_link'; + +describe('BackLink', () => { + let backLink; + + beforeEach(() => { + backLink = shallow(<BackLink text='Back to inbox' href='/' />); + }); + + it('renders link with text', () => { + expect(backLink.find('a').text()).toEqual('Back to inbox'); + }); + + it('adds link action', () => { + expect(backLink.find('a').props().href).toEqual('/'); + }); +}); |