From 7784c80d9ff2ca09dc3f6c796db1c33d89075c09 Mon Sep 17 00:00:00 2001 From: Anastasia Poupeney Date: Mon, 11 Jun 2018 13:38:35 +0200 Subject: [PATCH] wip password refactor and password page onboarding --- src/components/Onboarding/index.js | 45 +-- .../Onboarding/steps/SetPassword.js | 262 +++++++++++------- src/components/SettingsPage/PasswordForm.js | 92 ++++++ src/components/SettingsPage/PasswordModal.js | 94 +++---- src/reducers/onboarding.js | 18 +- static/i18n/en/onboarding.yml | 8 +- static/i18n/en/password.yml | 12 +- 7 files changed, 350 insertions(+), 181 deletions(-) create mode 100644 src/components/SettingsPage/PasswordForm.js diff --git a/src/components/Onboarding/index.js b/src/components/Onboarding/index.js index 495ca599..92c6dd0e 100644 --- a/src/components/Onboarding/index.js +++ b/src/components/Onboarding/index.js @@ -8,6 +8,7 @@ import styled from 'styled-components' import type { T } from 'types/common' import type { OnboardingState } from 'reducers/onboarding' +import type { SettingsState } from 'reducers/settings' import { saveSettings } from 'actions/settings' import { @@ -20,7 +21,7 @@ import { } from 'reducers/onboarding' import { getCurrentDevice } from 'reducers/devices' -// import { unlock } from 'reducers/application' +import { unlock } from 'reducers/application' import Box from 'components/base/Box' @@ -32,7 +33,7 @@ import SelectPIN from './steps/SelectPIN/index' import WriteSeed from './steps/WriteSeed/index' import GenuineCheck from './steps/GenuineCheck' // UNTIL IS NEEDED SET PASSWORD IS COMMENTED OUT -// import SetPassword from './steps/SetPassword' +import SetPassword from './steps/SetPassword' import Analytics from './steps/Analytics' import Finish from './steps/Finish' @@ -42,7 +43,7 @@ const STEPS = { selectPIN: SelectPIN, writeSeed: WriteSeed, genuineCheck: GenuineCheck, - // setPassword: SetPassword, + setPassword: SetPassword, analytics: Analytics, finish: Finish, start: Start, @@ -51,6 +52,7 @@ const STEPS = { const mapStateToProps = state => ({ hasCompletedOnboarding: state.settings.hasCompletedOnboarding, onboarding: state.onboarding, + settings: state.settings, getCurrentDevice: getCurrentDevice(state), }) @@ -59,7 +61,7 @@ const mapDispatchToProps = { nextStep, prevStep, jumpStep, - // unlock, + unlock, } type Props = { @@ -67,6 +69,7 @@ type Props = { hasCompletedOnboarding: boolean, saveSettings: Function, onboarding: OnboardingState, + settings: SettingsState, prevStep: Function, nextStep: Function, jumpStep: Function, @@ -77,12 +80,13 @@ type Props = { export type StepProps = { t: T, onboarding: OnboardingState, + settings: SettingsState, prevStep: Function, nextStep: Function, jumpStep: Function, finish: Function, saveSettings: Function, - // savePassword: Function, + savePassword: Function, getDeviceInfo: Function, updateGenuineCheck: Function, isLedgerNano: Function, @@ -92,18 +96,26 @@ export type StepProps = { class Onboarding extends PureComponent { getDeviceInfo = () => this.props.getCurrentDevice finish = () => this.props.saveSettings({ hasCompletedOnboarding: true }) - // savePassword = hash => { - // this.props.saveSettings({ - // password: { - // isEnabled: hash !== undefined, - // value: hash, - // }, - // }) - // this.props.unlock() - // } + savePassword = hash => { + this.props.saveSettings({ + password: { + isEnabled: hash !== undefined, + value: hash, + }, + }) + this.props.unlock() + } render() { - const { hasCompletedOnboarding, onboarding, prevStep, nextStep, jumpStep, t } = this.props + const { + hasCompletedOnboarding, + onboarding, + prevStep, + nextStep, + jumpStep, + settings, + t, + } = this.props if (hasCompletedOnboarding) { return null } @@ -119,6 +131,7 @@ class Onboarding extends PureComponent { const stepProps: StepProps = { t, onboarding, + settings, updateGenuineCheck, isLedgerNano, flowType, @@ -126,7 +139,7 @@ class Onboarding extends PureComponent { nextStep, jumpStep, finish: this.finish, - // savePassword: this.savePassword, + savePassword: this.savePassword, getDeviceInfo: this.getDeviceInfo, saveSettings, } diff --git a/src/components/Onboarding/steps/SetPassword.js b/src/components/Onboarding/steps/SetPassword.js index b844a4fb..8a50a77d 100644 --- a/src/components/Onboarding/steps/SetPassword.js +++ b/src/components/Onboarding/steps/SetPassword.js @@ -1,93 +1,169 @@ -// UNTIL IS NEEDED SET PASSWORD STEP IS COMMENTED OUT - -// // @flow -// -// import React, { PureComponent } from 'react' -// import bcrypt from 'bcryptjs' -// -// import { setEncryptionKey } from 'helpers/db' -// -// import Box from 'components/base/Box' -// import Button from 'components/base/Button' -// -// import IconSetPassword from 'icons/onboarding/SetPassword' -// import PasswordModal from 'components/SettingsPage/PasswordModal' -// import OnboardingFooter from '../OnboardingFooter' -// -// import type { StepProps } from '..' -// -// import { Title, Description } from '../helperComponents' -// -// type State = { -// isPasswordModalOpened: boolean, -// isPasswordEnabled: boolean, -// } -// -// class SetPassword extends PureComponent { -// state = { -// isPasswordModalOpened: false, -// isPasswordEnabled: false, -// } -// -// handleOpenPasswordModal = () => { -// this.setState({ isPasswordModalOpened: true }) -// } -// handleClosePasswordModal = () => { -// this.setState({ isPasswordModalOpened: false }) -// } -// handleChangePassword = (password: string) => { -// window.requestIdleCallback(() => { -// setEncryptionKey('accounts', password) -// const hash = password ? bcrypt.hashSync(password, 8) : undefined -// this.props.savePassword(hash) -// }) -// } -// -// handleInputChange = (key: string) => (value: string) => { -// this.setState({ [key]: value }) -// } -// -// render() { -// const { nextStep, prevStep, t } = this.props -// const { isPasswordModalOpened, isPasswordEnabled } = this.state -// return ( -// -// -// {t('onboarding:setPassword.title')} -// {t('onboarding:setPassword.desc')} -// -// -// -// -// {/* we might not be able to re-use what we have currently without modifications -// the title and descriptions are not dynamic, we might need deffirent size as well */} -// {isPasswordModalOpened && ( -// -// )} -// nextStep()} style={{ padding: 15 }}> -// -// -// -// -// -// ) -// } -// } -// -// export default SetPassword +// @flow + +import React, { PureComponent, Fragment } from 'react' +import bcrypt from 'bcryptjs' +import { colors } from 'styles/theme' +import styled from 'styled-components' +import { radii } from 'styles/theme' + +import { setEncryptionKey } from 'helpers/db' + +import Box from 'components/base/Box' +import Button from 'components/base/Button' + +import PasswordModal from 'components/SettingsPage/PasswordModal' +import OnboardingFooter from '../OnboardingFooter' +import IconChevronRight from 'icons/ChevronRight' +import { ErrorMessageInput } from 'components/base/Input' + +import PasswordForm from '../../SettingsPage/PasswordForm' +import type { StepProps } from '..' + +import { Title, Description, DisclaimerBox, Inner } from '../helperComponents' + +type State = { + currentPassword: string, + newPassword: string, + confirmPassword: string, + incorrectPassword: boolean, + submitError: boolean, +} + +const INITIAL_STATE = { + currentPassword: '', + newPassword: '', + confirmPassword: '', + incorrectPassword: false, + submitError: false, +} + +class SetPassword extends PureComponent { + state = INITIAL_STATE + + handleSave = (e: SyntheticEvent) => { + if (e) { + e.preventDefault() + } + if (!this.isValid()) { + this.setState({ submitError: true }) + return + } + const { newPassword } = this.state + const { nextStep } = this.props + + setEncryptionKey('accounts', newPassword) + const hash = newPassword ? bcrypt.hashSync(newPassword, 8) : undefined + this.props.savePassword(hash) + nextStep() + } + + handleInputChange = (key: string) => (value: string) => { + if (this.state.incorrectPassword) { + this.setState({ incorrectPassword: false }) + } + this.setState({ [key]: value }) + } + + handleReset = () => this.setState(INITIAL_STATE) + + isValid = () => { + const { newPassword, confirmPassword } = this.state + return newPassword === confirmPassword ? true : false + } + + render() { + const { nextStep, prevStep, t, savePassword, settings } = this.props + const { + newPassword, + currentPassword, + incorrectPassword, + confirmPassword, + submitError, + } = this.state + + const isPasswordEnabled = settings.password.isEnabled === true + + const disclaimerNotes = [ + { + key: 'note1', + icon: , + desc: t('onboarding:writeSeed.disclaimer.note1'), + }, + { + key: 'note2', + icon: , + desc: t('onboarding:writeSeed.disclaimer.note2'), + }, + { + key: 'note3', + icon: , + desc: t('onboarding:writeSeed.disclaimer.note3'), + }, + ] + + return ( + + + + + {t('onboarding:setPassword.title')} + + {t('onboarding:setPassword.desc')} + + + + + {!this.isValid() && ( + + {t('password:errorMessageNotMatchingPassword')} + + )} + + + + + + + + + + + + + + ) + } +} + +export default SetPassword + +const CustomFooter = styled(Box).attrs({ + px: 5, + py: 3, + horizontal: true, + align: 'center', +})` + border-top: 1px solid ${p => p.theme.colors.lightFog}; + border-bottom-left-radius: ${radii[1]}px; + border-bottom-right-radius: ${radii[1]}px; +` diff --git a/src/components/SettingsPage/PasswordForm.js b/src/components/SettingsPage/PasswordForm.js new file mode 100644 index 00000000..c64217b9 --- /dev/null +++ b/src/components/SettingsPage/PasswordForm.js @@ -0,0 +1,92 @@ +// @flow + +import React, { PureComponent, Fragment } from 'react' +import { connect } from 'react-redux' +import bcrypt from 'bcryptjs' + +import Box from 'components/base/Box' +import Button from 'components/base/Button' +import InputPassword from 'components/base/InputPassword' +import Label from 'components/base/Label' +import { ErrorMessageInput } from 'components/base/Input' + +import type { T } from 'types/common' + +type Props = { + t: T, + isPasswordEnabled: boolean, + + currentPassword: string, + newPassword: string, + confirmPassword: string, + incorrectPassword: boolean, + onSubmit: Function, + onChange: Function, +} + +class PasswordForm extends PureComponent { + render() { + const { + t, + isPasswordEnabled, + currentPassword, + newPassword, + incorrectPassword, + confirmPassword, + onChange, + onSubmit, + } = this.props + + return ( +
+ + {isPasswordEnabled && ( + + + + {incorrectPassword && ( + {t('password:errorMessageIncorrectPassword')} + )} + + )} + + {isPasswordEnabled && ( + + )} + + + + {isPasswordEnabled && ( + + )} + + + +
+ ) + } +} + +export default PasswordForm diff --git a/src/components/SettingsPage/PasswordModal.js b/src/components/SettingsPage/PasswordModal.js index 9d58b47f..84f6f0a0 100644 --- a/src/components/SettingsPage/PasswordModal.js +++ b/src/components/SettingsPage/PasswordModal.js @@ -13,6 +13,7 @@ import Label from 'components/base/Label' import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from 'components/base/Modal' import { ErrorMessageInput } from 'components/base/Input' +import PasswordForm from './PasswordForm' import type { T } from 'types/common' const mapDispatchToProps = { @@ -31,12 +32,14 @@ type Props = { type State = { currentPassword: string, newPassword: string, + confirmPassword: string, incorrectPassword: boolean, } const INITIAL_STATE = { currentPassword: '', newPassword: '', + confirmPassword: '', incorrectPassword: false, } @@ -50,7 +53,7 @@ class PasswordModal extends PureComponent { if (!this.isValid()) { return } - const { currentPassword, newPassword } = this.state + const { currentPassword, newPassword, confirmPassword } = this.state const { isPasswordEnabled, currentPasswordHash, onChangePassword } = this.props if (isPasswordEnabled) { if (!bcrypt.compareSync(currentPassword, currentPasswordHash)) { @@ -79,7 +82,7 @@ class PasswordModal extends PureComponent { render() { const { t, isPasswordEnabled, onClose, ...props } = this.props - const { currentPassword, newPassword, incorrectPassword } = this.state + const { currentPassword, newPassword, incorrectPassword, confirmPassword } = this.state const isValid = this.isValid() return ( { onHide={this.handleReset} onClose={onClose} render={({ onClose }) => ( -
- - {t('settings:profile.passwordModalTitle')} - - - {t('settings:profile.passwordModalSubtitle')} - - - {t('settings:profile.passwordModalDesc')} - - {isPasswordEnabled && ( - - - - {incorrectPassword && ( - - {t('password:errorMessageIncorrectPassword')} - - )} - - )} - - {isPasswordEnabled && ( - - )} - - - - - - - - - - -
+ + {t('settings:profile.passwordModalTitle')} + + + {t('settings:profile.passwordModalSubtitle')} + + + {t('settings:profile.passwordModalDesc')} + + + + + + + + )} /> ) diff --git a/src/reducers/onboarding.js b/src/reducers/onboarding.js index 3c57ba63..b9b967a3 100644 --- a/src/reducers/onboarding.js +++ b/src/reducers/onboarding.js @@ -94,15 +94,15 @@ const state: OnboardingState = { }, }, // UNTIL IS NEEDED SET PASSWORD IS COMMENTED OUT - // { - // name: 'setPassword', - // label: 'Set Password', - // options: { - // showFooter: false, - // showBackground: true, - // showBreadcrumb: true, - // }, - // }, + { + name: 'setPassword', + label: 'Set Password', + options: { + showFooter: false, + showBackground: true, + showBreadcrumb: true, + }, + }, { name: 'analytics', label: 'Analytics & Bug report', diff --git a/static/i18n/en/onboarding.yml b/static/i18n/en/onboarding.yml index e0ed0394..e25c6e4e 100644 --- a/static/i18n/en/onboarding.yml +++ b/static/i18n/en/onboarding.yml @@ -88,8 +88,12 @@ genuineCheck: title: Something is wrong with your Ledger Blue desc: A problem occurred with your Ledger Blue. Contact Ledger Support to get assistance or go back to the security check. setPassword: - title: Choose a password to securely access Ledger Live - desc: Use uppercase, lowercase, special characters (#, @, !, ?) and numbers for a stronger password. + title: Protect your privacy (optional) + desc: Set a password to prevent unauthorized access to Ledger Live data stored on your computer, including account names, balances, transactions and public addresses. + disclaimer: + note1: Make sure to remember your password and do not share it. + note2: Losing your password requires resetting Ledger Live and re-adding accounts. + note3: Loss of password doesn’t affect your crypto-assets. analytics: title: Help Ledger to improve its products and services desc: This is a long text, please replace it with the final wording once it’s done.
Lorem ipsum dolor amet ledger lorem dolor ipsum amet diff --git a/static/i18n/en/password.yml b/static/i18n/en/password.yml index 469b5f45..a5464044 100644 --- a/static/i18n/en/password.yml +++ b/static/i18n/en/password.yml @@ -3,4 +3,14 @@ warning_1: Warning 1 warning_2: Warning 2 warning_3: Warning 3 warning_4: Warning 4 -errorMessageIncorrectPassword: The password you entered is incorrect. +errorMessageIncorrectPassword: The password you entered is incorrect. +errorMessageNotMatchingPassword: Passwords don't match. +newPassword: + label: New Password + placeholder: New Password +confirmPassword: + label: Confirm Password + placeholder: Confirm Password +currentPassword: + label: Current Password + placeholder: Current Password