Browse Source

wip password refactor and password page onboarding

master
Anastasia Poupeney 7 years ago
parent
commit
7784c80d9f
  1. 45
      src/components/Onboarding/index.js
  2. 262
      src/components/Onboarding/steps/SetPassword.js
  3. 92
      src/components/SettingsPage/PasswordForm.js
  4. 94
      src/components/SettingsPage/PasswordModal.js
  5. 18
      src/reducers/onboarding.js
  6. 8
      static/i18n/en/onboarding.yml
  7. 12
      static/i18n/en/password.yml

45
src/components/Onboarding/index.js

@ -8,6 +8,7 @@ import styled from 'styled-components'
import type { T } from 'types/common' import type { T } from 'types/common'
import type { OnboardingState } from 'reducers/onboarding' import type { OnboardingState } from 'reducers/onboarding'
import type { SettingsState } from 'reducers/settings'
import { saveSettings } from 'actions/settings' import { saveSettings } from 'actions/settings'
import { import {
@ -20,7 +21,7 @@ import {
} from 'reducers/onboarding' } from 'reducers/onboarding'
import { getCurrentDevice } from 'reducers/devices' import { getCurrentDevice } from 'reducers/devices'
// import { unlock } from 'reducers/application' import { unlock } from 'reducers/application'
import Box from 'components/base/Box' import Box from 'components/base/Box'
@ -32,7 +33,7 @@ import SelectPIN from './steps/SelectPIN/index'
import WriteSeed from './steps/WriteSeed/index' import WriteSeed from './steps/WriteSeed/index'
import GenuineCheck from './steps/GenuineCheck' import GenuineCheck from './steps/GenuineCheck'
// UNTIL IS NEEDED SET PASSWORD IS COMMENTED OUT // UNTIL IS NEEDED SET PASSWORD IS COMMENTED OUT
// import SetPassword from './steps/SetPassword' import SetPassword from './steps/SetPassword'
import Analytics from './steps/Analytics' import Analytics from './steps/Analytics'
import Finish from './steps/Finish' import Finish from './steps/Finish'
@ -42,7 +43,7 @@ const STEPS = {
selectPIN: SelectPIN, selectPIN: SelectPIN,
writeSeed: WriteSeed, writeSeed: WriteSeed,
genuineCheck: GenuineCheck, genuineCheck: GenuineCheck,
// setPassword: SetPassword, setPassword: SetPassword,
analytics: Analytics, analytics: Analytics,
finish: Finish, finish: Finish,
start: Start, start: Start,
@ -51,6 +52,7 @@ const STEPS = {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
hasCompletedOnboarding: state.settings.hasCompletedOnboarding, hasCompletedOnboarding: state.settings.hasCompletedOnboarding,
onboarding: state.onboarding, onboarding: state.onboarding,
settings: state.settings,
getCurrentDevice: getCurrentDevice(state), getCurrentDevice: getCurrentDevice(state),
}) })
@ -59,7 +61,7 @@ const mapDispatchToProps = {
nextStep, nextStep,
prevStep, prevStep,
jumpStep, jumpStep,
// unlock, unlock,
} }
type Props = { type Props = {
@ -67,6 +69,7 @@ type Props = {
hasCompletedOnboarding: boolean, hasCompletedOnboarding: boolean,
saveSettings: Function, saveSettings: Function,
onboarding: OnboardingState, onboarding: OnboardingState,
settings: SettingsState,
prevStep: Function, prevStep: Function,
nextStep: Function, nextStep: Function,
jumpStep: Function, jumpStep: Function,
@ -77,12 +80,13 @@ type Props = {
export type StepProps = { export type StepProps = {
t: T, t: T,
onboarding: OnboardingState, onboarding: OnboardingState,
settings: SettingsState,
prevStep: Function, prevStep: Function,
nextStep: Function, nextStep: Function,
jumpStep: Function, jumpStep: Function,
finish: Function, finish: Function,
saveSettings: Function, saveSettings: Function,
// savePassword: Function, savePassword: Function,
getDeviceInfo: Function, getDeviceInfo: Function,
updateGenuineCheck: Function, updateGenuineCheck: Function,
isLedgerNano: Function, isLedgerNano: Function,
@ -92,18 +96,26 @@ export type StepProps = {
class Onboarding extends PureComponent<Props> { class Onboarding extends PureComponent<Props> {
getDeviceInfo = () => this.props.getCurrentDevice getDeviceInfo = () => this.props.getCurrentDevice
finish = () => this.props.saveSettings({ hasCompletedOnboarding: true }) finish = () => this.props.saveSettings({ hasCompletedOnboarding: true })
// savePassword = hash => { savePassword = hash => {
// this.props.saveSettings({ this.props.saveSettings({
// password: { password: {
// isEnabled: hash !== undefined, isEnabled: hash !== undefined,
// value: hash, value: hash,
// }, },
// }) })
// this.props.unlock() this.props.unlock()
// } }
render() { render() {
const { hasCompletedOnboarding, onboarding, prevStep, nextStep, jumpStep, t } = this.props const {
hasCompletedOnboarding,
onboarding,
prevStep,
nextStep,
jumpStep,
settings,
t,
} = this.props
if (hasCompletedOnboarding) { if (hasCompletedOnboarding) {
return null return null
} }
@ -119,6 +131,7 @@ class Onboarding extends PureComponent<Props> {
const stepProps: StepProps = { const stepProps: StepProps = {
t, t,
onboarding, onboarding,
settings,
updateGenuineCheck, updateGenuineCheck,
isLedgerNano, isLedgerNano,
flowType, flowType,
@ -126,7 +139,7 @@ class Onboarding extends PureComponent<Props> {
nextStep, nextStep,
jumpStep, jumpStep,
finish: this.finish, finish: this.finish,
// savePassword: this.savePassword, savePassword: this.savePassword,
getDeviceInfo: this.getDeviceInfo, getDeviceInfo: this.getDeviceInfo,
saveSettings, saveSettings,
} }

262
src/components/Onboarding/steps/SetPassword.js

@ -1,93 +1,169 @@
// UNTIL IS NEEDED SET PASSWORD STEP IS COMMENTED OUT // @flow
// // @flow import React, { PureComponent, Fragment } from 'react'
// import bcrypt from 'bcryptjs'
// import React, { PureComponent } from 'react' import { colors } from 'styles/theme'
// import bcrypt from 'bcryptjs' import styled from 'styled-components'
// import { radii } from 'styles/theme'
// import { setEncryptionKey } from 'helpers/db'
// import { setEncryptionKey } from 'helpers/db'
// import Box from 'components/base/Box'
// import Button from 'components/base/Button' 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 PasswordModal from 'components/SettingsPage/PasswordModal'
// import OnboardingFooter from '../OnboardingFooter' import OnboardingFooter from '../OnboardingFooter'
// import IconChevronRight from 'icons/ChevronRight'
// import type { StepProps } from '..' import { ErrorMessageInput } from 'components/base/Input'
//
// import { Title, Description } from '../helperComponents' import PasswordForm from '../../SettingsPage/PasswordForm'
// import type { StepProps } from '..'
// type State = {
// isPasswordModalOpened: boolean, import { Title, Description, DisclaimerBox, Inner } from '../helperComponents'
// isPasswordEnabled: boolean,
// } type State = {
// currentPassword: string,
// class SetPassword extends PureComponent<StepProps, State> { newPassword: string,
// state = { confirmPassword: string,
// isPasswordModalOpened: false, incorrectPassword: boolean,
// isPasswordEnabled: false, submitError: boolean,
// } }
//
// handleOpenPasswordModal = () => { const INITIAL_STATE = {
// this.setState({ isPasswordModalOpened: true }) currentPassword: '',
// } newPassword: '',
// handleClosePasswordModal = () => { confirmPassword: '',
// this.setState({ isPasswordModalOpened: false }) incorrectPassword: false,
// } submitError: false,
// handleChangePassword = (password: string) => { }
// window.requestIdleCallback(() => {
// setEncryptionKey('accounts', password) class SetPassword extends PureComponent<StepProps, State> {
// const hash = password ? bcrypt.hashSync(password, 8) : undefined state = INITIAL_STATE
// this.props.savePassword(hash)
// }) handleSave = (e: SyntheticEvent<HTMLFormElement>) => {
// } if (e) {
// e.preventDefault()
// handleInputChange = (key: string) => (value: string) => { }
// this.setState({ [key]: value }) if (!this.isValid()) {
// } this.setState({ submitError: true })
// return
// render() { }
// const { nextStep, prevStep, t } = this.props const { newPassword } = this.state
// const { isPasswordModalOpened, isPasswordEnabled } = this.state const { nextStep } = this.props
// return (
// <Box sticky pt={150}> setEncryptionKey('accounts', newPassword)
// <Box grow alignItems="center"> const hash = newPassword ? bcrypt.hashSync(newPassword, 8) : undefined
// <Title>{t('onboarding:setPassword.title')}</Title> this.props.savePassword(hash)
// <Description>{t('onboarding:setPassword.desc')}</Description> nextStep()
// <IconSetPassword /> }
// <Box style={{ paddingTop: 35 }}>
// <Button small primary onClick={() => this.handleOpenPasswordModal()}> handleInputChange = (key: string) => (value: string) => {
// Set Password if (this.state.incorrectPassword) {
// </Button> this.setState({ incorrectPassword: false })
// </Box> }
// {/* we might not be able to re-use what we have currently without modifications this.setState({ [key]: value })
// the title and descriptions are not dynamic, we might need deffirent size as well */} }
// {isPasswordModalOpened && (
// <PasswordModal handleReset = () => this.setState(INITIAL_STATE)
// t={t}
// isOpened={isPasswordModalOpened} isValid = () => {
// onClose={this.handleClosePasswordModal} const { newPassword, confirmPassword } = this.state
// onChangePassword={this.handleChangePassword} return newPassword === confirmPassword ? true : false
// isPasswordEnabled={isPasswordEnabled} }
// currentPasswordHash=""
// /> render() {
// )} const { nextStep, prevStep, t, savePassword, settings } = this.props
// <Box onClick={() => nextStep()} style={{ padding: 15 }}> const {
// <Button>Skip this step</Button> newPassword,
// </Box> currentPassword,
// </Box> incorrectPassword,
// <OnboardingFooter confirmPassword,
// horizontal submitError,
// align="center" } = this.state
// flow={2}
// t={t} const isPasswordEnabled = settings.password.isEnabled === true
// nextStep={nextStep}
// prevStep={prevStep} const disclaimerNotes = [
// /> {
// </Box> key: 'note1',
// ) icon: <IconChevronRight size={12} style={{ color: colors.smoke }} />,
// } desc: t('onboarding:writeSeed.disclaimer.note1'),
// } },
// {
// export default SetPassword key: 'note2',
icon: <IconChevronRight size={12} style={{ color: colors.smoke }} />,
desc: t('onboarding:writeSeed.disclaimer.note2'),
},
{
key: 'note3',
icon: <IconChevronRight size={12} style={{ color: colors.smoke }} />,
desc: t('onboarding:writeSeed.disclaimer.note3'),
},
]
return (
<Box sticky pt={50}>
<Box grow alignItems="center" justify="center">
<Fragment>
<Box mb={3} alignItems="center">
<Title>{t('onboarding:setPassword.title')}</Title>
<Description style={{ maxWidth: 620 }}>
{t('onboarding:setPassword.desc')}
</Description>
</Box>
<Box align="center" mt={2}>
<PasswordForm
onSubmit={this.handleSave}
isPasswordEnabled={isPasswordEnabled}
newPassword={newPassword}
currentPassword={currentPassword}
confirmPassword={confirmPassword}
incorrectPassword={incorrectPassword}
onChange={this.handleInputChange}
t={t}
/>
{!this.isValid() && (
<ErrorMessageInput style={{ width: 300 }}>
{t('password:errorMessageNotMatchingPassword')}
</ErrorMessageInput>
)}
<DisclaimerBox mt={6} disclaimerNotes={disclaimerNotes} />
</Box>
</Fragment>
</Box>
<CustomFooter>
<Button padded outlineGrey onClick={() => prevStep()}>
{t('common:back')}
</Button>
<Box horizontal ml="auto">
<Button padded disabled={false} onClick={() => nextStep()} mx={2}>
Skip This Step
</Button>
<Button
padded
onClick={this.handleSave}
disabled={!this.isValid() || !newPassword.length || !confirmPassword.length}
primary
>
{t('common:continue')}
</Button>
</Box>
</CustomFooter>
</Box>
)
}
}
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;
`

92
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<Props> {
render() {
const {
t,
isPasswordEnabled,
currentPassword,
newPassword,
incorrectPassword,
confirmPassword,
onChange,
onSubmit,
} = this.props
return (
<form onSubmit={onSubmit}>
<Box px={7} mt={4} flow={3}>
{isPasswordEnabled && (
<Box flow={1}>
<Label htmlFor="currentPassword">{t('password:currentPassword.label')}</Label>
<InputPassword
autoFocus
type="password"
placeholder={t('password:currentPassword.placeholder')}
id="currentPassword"
onChange={onChange('currentPassword')}
value={currentPassword}
/>
{incorrectPassword && (
<ErrorMessageInput>{t('password:errorMessageIncorrectPassword')}</ErrorMessageInput>
)}
</Box>
)}
<Box flow={1}>
{isPasswordEnabled && (
<Label htmlFor="newPassword">{t('password:newPassword.label')}</Label>
)}
<InputPassword
style={{ mt: 4, width: 240 }}
autoFocus
placeholder={t('password:newPassword.placeholder')}
id="newPassword"
onChange={onChange('newPassword')}
value={newPassword}
// withStrength
/>
</Box>
<Box flow={1}>
{isPasswordEnabled && (
<Label htmlFor="confirmPassword">{t('password:confirmPassword.label')}</Label>
)}
<InputPassword
style={{ width: 240 }}
placeholder={t('password:confirmPassword.placeholder')}
id="confirmPassword"
onChange={onChange('confirmPassword')}
value={confirmPassword}
// withStrength
/>
</Box>
</Box>
</form>
)
}
}
export default PasswordForm

94
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 { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from 'components/base/Modal'
import { ErrorMessageInput } from 'components/base/Input' import { ErrorMessageInput } from 'components/base/Input'
import PasswordForm from './PasswordForm'
import type { T } from 'types/common' import type { T } from 'types/common'
const mapDispatchToProps = { const mapDispatchToProps = {
@ -31,12 +32,14 @@ type Props = {
type State = { type State = {
currentPassword: string, currentPassword: string,
newPassword: string, newPassword: string,
confirmPassword: string,
incorrectPassword: boolean, incorrectPassword: boolean,
} }
const INITIAL_STATE = { const INITIAL_STATE = {
currentPassword: '', currentPassword: '',
newPassword: '', newPassword: '',
confirmPassword: '',
incorrectPassword: false, incorrectPassword: false,
} }
@ -50,7 +53,7 @@ class PasswordModal extends PureComponent<Props, State> {
if (!this.isValid()) { if (!this.isValid()) {
return return
} }
const { currentPassword, newPassword } = this.state const { currentPassword, newPassword, confirmPassword } = this.state
const { isPasswordEnabled, currentPasswordHash, onChangePassword } = this.props const { isPasswordEnabled, currentPasswordHash, onChangePassword } = this.props
if (isPasswordEnabled) { if (isPasswordEnabled) {
if (!bcrypt.compareSync(currentPassword, currentPasswordHash)) { if (!bcrypt.compareSync(currentPassword, currentPasswordHash)) {
@ -79,7 +82,7 @@ class PasswordModal extends PureComponent<Props, State> {
render() { render() {
const { t, isPasswordEnabled, onClose, ...props } = this.props const { t, isPasswordEnabled, onClose, ...props } = this.props
const { currentPassword, newPassword, incorrectPassword } = this.state const { currentPassword, newPassword, incorrectPassword, confirmPassword } = this.state
const isValid = this.isValid() const isValid = this.isValid()
return ( return (
<Modal <Modal
@ -87,64 +90,35 @@ class PasswordModal extends PureComponent<Props, State> {
onHide={this.handleReset} onHide={this.handleReset}
onClose={onClose} onClose={onClose}
render={({ onClose }) => ( render={({ onClose }) => (
<form onSubmit={this.handleSave}> <ModalBody onClose={onClose}>
<ModalBody onClose={onClose}> <ModalTitle>{t('settings:profile.passwordModalTitle')}</ModalTitle>
<ModalTitle>{t('settings:profile.passwordModalTitle')}</ModalTitle> <ModalContent>
<ModalContent> <Box ff="Museo Sans|Regular" color="dark" textAlign="center" mb={2} mt={3}>
<Box ff="Museo Sans|Regular" color="dark" textAlign="center" mb={2} mt={3}> {t('settings:profile.passwordModalSubtitle')}
{t('settings:profile.passwordModalSubtitle')} </Box>
</Box> <Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" px={4}>
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" px={4}> {t('settings:profile.passwordModalDesc')}
{t('settings:profile.passwordModalDesc')} </Box>
<Box px={7} mt={4} flow={3}> <PasswordForm
{isPasswordEnabled && ( onSubmit={this.handleSave}
<Box flow={1}> isPasswordEnabled={isPasswordEnabled}
<Label htmlFor="password"> newPassword={newPassword}
{t('settings:profile.passwordModalPasswordInput')} currentPassword={currentPassword}
</Label> confirmPassword={confirmPassword}
<InputPassword incorrectPassword={incorrectPassword}
autoFocus onChange={this.handleInputChange}
type="password" t={t}
placeholder={t('settings:profile.passwordModalPasswordInput')} />
id="password" </ModalContent>
onChange={this.handleInputChange('currentPassword')} <ModalFooter horizontal align="center" justify="flex-end" flow={2}>
value={currentPassword} <Button type="button" onClick={onClose}>
/> {t('common:cancel')}
{incorrectPassword && ( </Button>
<ErrorMessageInput> <Button primary onClick={this.handleSave} disabled={!isValid}>
{t('password:errorMessageIncorrectPassword')} {t('settings:profile.passwordModalSave')}
</ErrorMessageInput> </Button>
)} </ModalFooter>
</Box> </ModalBody>
)}
<Box flow={1}>
{isPasswordEnabled && (
<Label htmlFor="newPassword">
{t('settings:profile.passwordModalNewPasswordInput')}
</Label>
)}
<InputPassword
autoFocus={!isPasswordEnabled}
placeholder={t('settings:profile.passwordModalNewPasswordInput')}
id="newPassword"
onChange={this.handleInputChange('newPassword')}
value={newPassword}
withStrength
/>
</Box>
</Box>
</Box>
</ModalContent>
<ModalFooter horizontal align="center" justify="flex-end" flow={2}>
<Button type="button" onClick={onClose}>
{t('common:cancel')}
</Button>
<Button primary onClick={this.handleSave} disabled={!isValid}>
{t('settings:profile.passwordModalSave')}
</Button>
</ModalFooter>
</ModalBody>
</form>
)} )}
/> />
) )

18
src/reducers/onboarding.js

@ -94,15 +94,15 @@ const state: OnboardingState = {
}, },
}, },
// UNTIL IS NEEDED SET PASSWORD IS COMMENTED OUT // UNTIL IS NEEDED SET PASSWORD IS COMMENTED OUT
// { {
// name: 'setPassword', name: 'setPassword',
// label: 'Set Password', label: 'Set Password',
// options: { options: {
// showFooter: false, showFooter: false,
// showBackground: true, showBackground: true,
// showBreadcrumb: true, showBreadcrumb: true,
// }, },
// }, },
{ {
name: 'analytics', name: 'analytics',
label: 'Analytics & Bug report', label: 'Analytics & Bug report',

8
static/i18n/en/onboarding.yml

@ -88,8 +88,12 @@ genuineCheck:
title: Something is wrong with your Ledger Blue 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. desc: A problem occurred with your Ledger Blue. Contact Ledger Support to get assistance or go back to the security check.
setPassword: setPassword:
title: Choose a password to securely access Ledger Live title: Protect your privacy (optional)
desc: Use uppercase, lowercase, special characters (#, @, !, ?) and numbers for a stronger password. 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: analytics:
title: Help Ledger to improve its products and services 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 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

12
static/i18n/en/password.yml

@ -3,4 +3,14 @@ warning_1: Warning 1
warning_2: Warning 2 warning_2: Warning 2
warning_3: Warning 3 warning_3: Warning 3
warning_4: Warning 4 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

Loading…
Cancel
Save