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 { 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<Props> {
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<Props> {
const stepProps: StepProps = {
t,
onboarding,
settings,
updateGenuineCheck,
isLedgerNano,
flowType,
@ -126,7 +139,7 @@ class Onboarding extends PureComponent<Props> {
nextStep,
jumpStep,
finish: this.finish,
// savePassword: this.savePassword,
savePassword: this.savePassword,
getDeviceInfo: this.getDeviceInfo,
saveSettings,
}

262
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<StepProps, State> {
// 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 (
// <Box sticky pt={150}>
// <Box grow alignItems="center">
// <Title>{t('onboarding:setPassword.title')}</Title>
// <Description>{t('onboarding:setPassword.desc')}</Description>
// <IconSetPassword />
// <Box style={{ paddingTop: 35 }}>
// <Button small primary onClick={() => this.handleOpenPasswordModal()}>
// Set Password
// </Button>
// </Box>
// {/* 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 && (
// <PasswordModal
// t={t}
// isOpened={isPasswordModalOpened}
// onClose={this.handleClosePasswordModal}
// onChangePassword={this.handleChangePassword}
// isPasswordEnabled={isPasswordEnabled}
// currentPasswordHash=""
// />
// )}
// <Box onClick={() => nextStep()} style={{ padding: 15 }}>
// <Button>Skip this step</Button>
// </Box>
// </Box>
// <OnboardingFooter
// horizontal
// align="center"
// flow={2}
// t={t}
// nextStep={nextStep}
// prevStep={prevStep}
// />
// </Box>
// )
// }
// }
//
// 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<StepProps, State> {
state = INITIAL_STATE
handleSave = (e: SyntheticEvent<HTMLFormElement>) => {
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: <IconChevronRight size={12} style={{ color: colors.smoke }} />,
desc: t('onboarding:writeSeed.disclaimer.note1'),
},
{
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 { 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<Props, State> {
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<Props, State> {
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 (
<Modal
@ -87,64 +90,35 @@ class PasswordModal extends PureComponent<Props, State> {
onHide={this.handleReset}
onClose={onClose}
render={({ onClose }) => (
<form onSubmit={this.handleSave}>
<ModalBody onClose={onClose}>
<ModalTitle>{t('settings:profile.passwordModalTitle')}</ModalTitle>
<ModalContent>
<Box ff="Museo Sans|Regular" color="dark" textAlign="center" mb={2} mt={3}>
{t('settings:profile.passwordModalSubtitle')}
</Box>
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" px={4}>
{t('settings:profile.passwordModalDesc')}
<Box px={7} mt={4} flow={3}>
{isPasswordEnabled && (
<Box flow={1}>
<Label htmlFor="password">
{t('settings:profile.passwordModalPasswordInput')}
</Label>
<InputPassword
autoFocus
type="password"
placeholder={t('settings:profile.passwordModalPasswordInput')}
id="password"
onChange={this.handleInputChange('currentPassword')}
value={currentPassword}
/>
{incorrectPassword && (
<ErrorMessageInput>
{t('password:errorMessageIncorrectPassword')}
</ErrorMessageInput>
)}
</Box>
)}
<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>
<ModalBody onClose={onClose}>
<ModalTitle>{t('settings:profile.passwordModalTitle')}</ModalTitle>
<ModalContent>
<Box ff="Museo Sans|Regular" color="dark" textAlign="center" mb={2} mt={3}>
{t('settings:profile.passwordModalSubtitle')}
</Box>
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" px={4}>
{t('settings:profile.passwordModalDesc')}
</Box>
<PasswordForm
onSubmit={this.handleSave}
isPasswordEnabled={isPasswordEnabled}
newPassword={newPassword}
currentPassword={currentPassword}
confirmPassword={confirmPassword}
incorrectPassword={incorrectPassword}
onChange={this.handleInputChange}
t={t}
/>
</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>
)}
/>
)

18
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',

8
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

12
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

Loading…
Cancel
Save