Browse Source

Merge pull request #366 from gre/onboarding

Onboarding
master
Gaëtan Renaudeau 7 years ago
committed by GitHub
parent
commit
97447ba995
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 96
      src/components/DeviceConnect/index.js
  2. 71
      src/components/EnsureDeviceApp/index.js
  3. 71
      src/components/GenuineCheckModal/index.js
  4. 3
      src/components/Onboarding/helperComponents.js
  5. 42
      src/components/Onboarding/index.js
  6. 67
      src/components/Onboarding/steps/Analytics.js
  7. 200
      src/components/Onboarding/steps/GenuineCheck.js
  8. 98
      src/components/Onboarding/steps/SelectDevice.js
  9. 80
      src/components/Onboarding/steps/SelectPIN.js
  10. 182
      src/components/Onboarding/steps/SetPassword.js
  11. 2
      src/components/Onboarding/steps/WriteSeed.js
  12. 2
      src/components/base/InputPassword/index.js
  13. 13
      src/icons/Home.js
  14. 47
      src/icons/onboarding/LedgerBlueError.js
  15. 47
      src/icons/onboarding/LedgerBlueSelectPIN.js
  16. 30
      src/icons/onboarding/LedgerNano.js
  17. 0
      src/icons/onboarding/LedgerNanoSelectPIN.js
  18. 206
      src/icons/onboarding/WriteSeed.js
  19. 26
      src/reducers/onboarding.js
  20. 2
      static/i18n/en/genuinecheck.yml
  21. 42
      static/i18n/en/onboarding.yml

96
src/components/DeviceConnect/index.js

@ -17,6 +17,7 @@ import IconExclamationCircle from 'icons/ExclamationCircle'
import IconInfoCircle from 'icons/InfoCircle'
import IconLoader from 'icons/Loader'
import IconUsb from 'icons/Usb'
import IconHome from 'icons/Home'
import * as IconDevice from 'icons/device'
@ -33,12 +34,14 @@ const Step = styled(Box).attrs({
? p.theme.colors.alertRed
: p.theme.colors.fog};
`
const StepIcon = styled(Box).attrs({
alignItems: 'center',
justifyContent: 'center',
})`
width: 64px;
`
const StepContent = styled(Box).attrs({
color: 'dark',
horizontal: true,
@ -138,6 +141,8 @@ StepCheck.defaultProps = {
type Props = {
accountName: null | string,
appOpened: null | 'success' | 'fail',
genuineCheckStatus: null | 'success' | 'fail',
withGenuineCheck: boolean,
currency: CryptoCurrency,
devices: Device[],
deviceSelected: ?Device,
@ -161,6 +166,7 @@ class DeviceConnect extends PureComponent<Props> {
devices: [],
deviceSelected: null,
onChangeDevice: noop,
withGenuineCheck: false,
}
componentDidMount() {
@ -171,18 +177,17 @@ class DeviceConnect extends PureComponent<Props> {
emitChangeDevice(nextProps)
}
getAppState() {
const { appOpened } = this.props
return {
success: appOpened === 'success',
fail: appOpened === 'fail',
}
}
getStepState = stepStatus => ({
success: stepStatus === 'success',
fail: stepStatus === 'fail',
})
render() {
const {
deviceSelected,
genuineCheckStatus,
withGenuineCheck,
appOpened,
errorMessage,
accountName,
currency,
@ -191,7 +196,8 @@ class DeviceConnect extends PureComponent<Props> {
devices,
} = this.props
const appState = this.getAppState()
const appState = this.getStepState(appOpened)
const genuineCheckState = this.getStepState(genuineCheckStatus)
const hasDevice = devices.length > 0
const hasMultipleDevices = devices.length > 1
@ -242,24 +248,66 @@ class DeviceConnect extends PureComponent<Props> {
</ListDevices>
)}
</Step>
<Step validated={appState.success} hasErrors={appState.fail}>
<StepContent>
<StepIcon>
<WrapperIconCurrency>
<CryptoCurrencyIcon currency={currency} size={12} />
</WrapperIconCurrency>
</StepIcon>
<Box grow shrink>
<Trans i18nKey="deviceConnect:step2.open" parent="div">
{'Open '}
<strong>{currency.name}</strong>
{' App on your device'}
</Trans>
</Box>
<StepCheck checked={appState.success} hasErrors={appState.fail} />
</StepContent>
{currency ? (
<StepContent>
<StepIcon>
<WrapperIconCurrency>
<CryptoCurrencyIcon currency={currency} size={12} />
</WrapperIconCurrency>
</StepIcon>
<Box grow shrink>
<Trans i18nKey="deviceConnect:step2.open" parent="div">
{'Open '}
<strong>{currency.name}</strong>
{' App on your device'}
</Trans>
</Box>
<StepCheck checked={appState.success} hasErrors={appState.fail} />
</StepContent>
) : (
<StepContent>
<StepIcon>
<WrapperIconCurrency>
<IconHome size={12} />
</WrapperIconCurrency>
</StepIcon>
<Box grow shrink>
<Trans i18nKey="deviceConnect:dashboard.open" parent="div">
{'Go to the '}
<strong>{'dashboard'}</strong>
{' on your device'}
</Trans>
</Box>
<StepCheck checked={appState.success} hasErrors={appState.fail} />
</StepContent>
)}
</Step>
{/* GENUINE CHECK */}
{/* ------------- */}
{withGenuineCheck && (
<Step validated={genuineCheckState.success} hasErrors={genuineCheckState.fail}>
<StepContent>
<StepIcon>
<WrapperIconCurrency>
<IconCheck size={12} />
</WrapperIconCurrency>
</StepIcon>
<Box grow shrink>
<Trans i18nKey="deviceConnect:stepGenuine.open" parent="div">
{'Confirm '}
<strong>{'authentication'}</strong>
{' on your device'}
</Trans>
</Box>
<StepCheck checked={genuineCheckState.success} hasErrors={genuineCheckState.fail} />
</StepContent>
</Step>
)}
{appState.fail ? (
<Info hasErrors>
<Box>

71
src/components/EnsureDeviceApp/index.js

@ -1,5 +1,4 @@
// @flow
import invariant from 'invariant'
import { PureComponent } from 'react'
import { connect } from 'react-redux'
import { standardDerivation } from 'helpers/derivations'
@ -12,14 +11,17 @@ import type { State as StoreState } from 'reducers/index'
import getAddress from 'commands/getAddress'
type OwnProps = {
currency: ?CryptoCurrency,
currency?: ?CryptoCurrency,
deviceSelected: ?Device,
account: ?Account,
withGenuineCheck?: boolean,
account?: ?Account,
onStatusChange?: (DeviceStatus, AppStatus, ?string) => void,
onGenuineCheck?: (isGenuine: boolean) => void,
// TODO prefer children function
render?: ({
appStatus: AppStatus,
currency: CryptoCurrency,
genuineCheckStatus: GenuineCheckStatus,
currency: ?CryptoCurrency,
devices: Device[],
deviceSelected: ?Device,
deviceStatus: DeviceStatus,
@ -35,10 +37,13 @@ type DeviceStatus = 'unconnected' | 'connected'
type AppStatus = 'success' | 'fail' | 'progress'
type GenuineCheckStatus = 'success' | 'fail' | 'progress'
type State = {
deviceStatus: DeviceStatus,
appStatus: AppStatus,
errorMessage: ?string,
genuineCheckStatus: GenuineCheckStatus,
}
const mapStateToProps = (state: StoreState) => ({
@ -50,6 +55,7 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
appStatus: 'progress',
deviceStatus: this.props.deviceSelected ? 'connected' : 'unconnected',
errorMessage: null,
genuineCheckStatus: 'progress',
}
componentDidMount() {
@ -87,19 +93,21 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
componentWillUnmount() {
clearTimeout(this._timeout)
this._unmounted = true
}
checkAppOpened = async () => {
const { deviceSelected, account, currency } = this.props
const { deviceSelected, account, currency, withGenuineCheck } = this.props
const { appStatus } = this.state
if (!deviceSelected) {
return
}
let options
let appOptions
if (account) {
options = {
appOptions = {
devicePath: deviceSelected.path,
currencyId: account.currency.id,
path: account.freshAddressPath,
@ -107,21 +115,31 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
segwit: !!account.isSegwit,
}
} else if (currency) {
options = {
appOptions = {
devicePath: deviceSelected.path,
currencyId: currency.id,
path: standardDerivation({ currency, x: 0, segwit: false }),
}
} else {
throw new Error('either currency or account is required')
}
try {
const { address } = await getAddress.send(options).toPromise()
if (account && account.freshAddress !== address) {
throw new Error('Account address is different than device address')
if (appOptions) {
const { address } = await getAddress.send(appOptions).toPromise()
if (account && account.freshAddress !== address) {
throw new Error('Account address is different than device address')
}
} else {
// TODO: real check if user is on the device dashboard
if (!deviceSelected) {
throw new Error('No device')
}
await sleep(1)
}
this.handleStatusChange(this.state.deviceStatus, 'success')
if (withGenuineCheck && appStatus !== 'success') {
this.handleGenuineCheck()
}
} catch (e) {
this.handleStatusChange(this.state.deviceStatus, 'fail', e.message)
}
@ -130,27 +148,42 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
}
_timeout: *
_unmounted = false
handleStatusChange = (deviceStatus, appStatus, errorMessage = null) => {
const { onStatusChange } = this.props
clearTimeout(this._timeout)
this.setState({ deviceStatus, appStatus, errorMessage })
onStatusChange && onStatusChange(deviceStatus, appStatus, errorMessage)
if (!this._unmounted) {
this.setState({ deviceStatus, appStatus, errorMessage })
onStatusChange && onStatusChange(deviceStatus, appStatus, errorMessage)
}
}
handleGenuineCheck = async () => {
// TODO: do a *real* genuine check
await sleep(1)
if (!this._unmounted) {
this.setState({ genuineCheckStatus: 'success' })
this.props.onGenuineCheck && this.props.onGenuineCheck(true)
}
}
render() {
const { currency, account, devices, deviceSelected, render } = this.props
const { appStatus, deviceStatus, errorMessage } = this.state
const { appStatus, deviceStatus, genuineCheckStatus, errorMessage } = this.state
if (render) {
// if cur is not provided, we assume we want to check if user is on
// the dashboard
const cur = account ? account.currency : currency
invariant(cur, 'currency is either provided or taken from account')
return render({
appStatus,
currency: cur,
devices,
deviceSelected: deviceStatus === 'connected' ? deviceSelected : null,
deviceStatus,
genuineCheckStatus,
errorMessage,
})
}
@ -160,3 +193,7 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
}
export default connect(mapStateToProps)(EnsureDeviceApp)
async function sleep(s) {
return new Promise(resolve => setTimeout(resolve, s * 1e3))
}

71
src/components/GenuineCheckModal/index.js

@ -0,0 +1,71 @@
// @flow
import React, { PureComponent } from 'react'
import { compose } from 'redux'
import { connect } from 'react-redux'
import { translate } from 'react-i18next'
import type { T, Device } from 'types/common'
import { getCurrentDevice } from 'reducers/devices'
import DeviceConnect from 'components/DeviceConnect'
import EnsureDeviceApp from 'components/EnsureDeviceApp'
import Modal, { ModalBody, ModalTitle, ModalContent } from 'components/base/Modal'
const mapStateToProps = state => ({
currentDevice: getCurrentDevice(state),
})
type Props = {
t: T,
currentDevice: ?Device,
onGenuineCheck: (isGenuine: boolean) => void,
}
type State = {}
class GenuineCheck extends PureComponent<Props, State> {
renderBody = ({ onClose }) => {
const { t, currentDevice, onGenuineCheck } = this.props
// TODO: use the real devices list. for now we force choosing only
// the current device because we don't handle multi device in MVP
const reducedDevicesList = currentDevice ? [currentDevice] : []
return (
<ModalBody onClose={onClose}>
<ModalTitle>{t('genuinecheck:modal.title')}</ModalTitle>
<ModalContent>
<EnsureDeviceApp
deviceSelected={currentDevice}
withGenuineCheck
onGenuineCheck={onGenuineCheck}
onStatusChange={status => {
console.log(`status changed to ${status}`)
}}
render={({ appStatus, genuineCheckStatus, deviceSelected, errorMessage }) => (
<DeviceConnect
appOpened={
appStatus === 'success' ? 'success' : appStatus === 'fail' ? 'fail' : null
}
withGenuineCheck
genuineCheckStatus={genuineCheckStatus}
devices={reducedDevicesList}
deviceSelected={deviceSelected}
errorMessage={errorMessage}
/>
)}
/>
</ModalContent>
</ModalBody>
)
}
render() {
const { ...props } = this.props
return <Modal {...props} render={({ onClose }) => this.renderBody({ onClose })} />
}
}
export default compose(connect(mapStateToProps), translate())(GenuineCheck)

3
src/components/Onboarding/helperComponents.js

@ -16,8 +16,6 @@ export const Title = styled(Box).attrs({
})``
export const Description = styled(Box).attrs({
width: 714,
height: 48,
ff: 'Museo Sans|Light',
fontSize: 5,
lineHeight: 1.5,
@ -25,6 +23,7 @@ export const Description = styled(Box).attrs({
color: 'grey',
})`
margin: 10px auto 25px;
max-width: 550px;
`
export const Inner = styled(Box).attrs({
horizontal: true,

42
src/components/Onboarding/index.js

@ -10,11 +10,16 @@ import type { T } from 'types/common'
import type { OnboardingState } from 'reducers/onboarding'
import { saveSettings } from 'actions/settings'
import { nextStep, prevStep, jumpStep, setGenuineCheckFail } from 'reducers/onboarding'
import {
nextStep,
prevStep,
jumpStep,
setGenuineCheckFail,
isLedgerNano,
} from 'reducers/onboarding'
import { getCurrentDevice } from 'reducers/devices'
// TODO: re-write it without auto lock, fixed width of the password modal, not dynamic titles
import { unlock } from 'reducers/application'
// import { unlock } from 'reducers/application'
import Box from 'components/base/Box'
@ -25,7 +30,8 @@ import SelectDevice from './steps/SelectDevice'
import SelectPIN from './steps/SelectPIN'
import WriteSeed from './steps/WriteSeed'
import GenuineCheck from './steps/GenuineCheck'
import SetPassword from './steps/SetPassword'
// UNTIL IS NEEDED SET PASSWORD IS COMMENTED OUT
// import SetPassword from './steps/SetPassword'
import Analytics from './steps/Analytics'
import Finish from './steps/Finish'
@ -35,7 +41,7 @@ const STEPS = {
selectPIN: SelectPIN,
writeSeed: WriteSeed,
genuineCheck: GenuineCheck,
setPassword: SetPassword,
// setPassword: SetPassword,
analytics: Analytics,
finish: Finish,
start: Start,
@ -52,7 +58,7 @@ const mapDispatchToProps = {
nextStep,
prevStep,
jumpStep,
unlock,
// unlock,
}
type Props = {
@ -74,23 +80,24 @@ export type StepProps = {
nextStep: Function,
jumpStep: Function,
finish: Function,
savePassword: Function,
// savePassword: Function,
getDeviceInfo: Function,
setGenuineCheckFail: Function,
isLedgerNano: Function,
}
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
@ -110,11 +117,12 @@ class Onboarding extends PureComponent<Props> {
t,
onboarding,
setGenuineCheckFail,
isLedgerNano,
prevStep,
nextStep,
jumpStep,
finish: this.finish,
savePassword: this.savePassword,
// savePassword: this.savePassword,
getDeviceInfo: this.getDeviceInfo,
}

67
src/components/Onboarding/steps/Analytics.js

@ -4,7 +4,6 @@ import React from 'react'
import styled from 'styled-components'
import Box from 'components/base/Box'
import IconAnalytics from 'icons/onboarding/Analytics'
import CheckBox from 'components/base/CheckBox'
import { Title, Description } from '../helperComponents'
import OnboardingFooter from '../OnboardingFooter'
@ -17,25 +16,31 @@ export default (props: StepProps) => {
<Box sticky pt={150}>
<Box grow alignItems="center">
<Title>{t('onboarding:analytics.title')}</Title>
<Description style={{ maxWidth: 714 }}>{t('onboarding:analytics.desc')}</Description>
<DeviceIcon style={{ padding: 15 }}>
<IconAnalytics />
</DeviceIcon>
<Box horizontal flow={2} align="center">
<CheckBox isChecked={false} />
<AnalyticsText>
This is a long text, please replace it with the final wording once its done.
<br />
Lorem ipsum dolor amet ledger lorem dolor ipsum amet
</AnalyticsText>
</Box>
<Box horizontal flow={2} align="center">
<CheckBox isChecked={false} />
<AnalyticsText>
This is a long text, please replace it with the final wording once its done.
<br />
Lorem ipsum dolor amet ledger lorem dolor ipsum amet
</AnalyticsText>
<Description>{t('onboarding:analytics.desc')}</Description>
<Box mt={5}>
<Container>
<Box justify="center">
<Box horizontal>
<AnalyticsTitle>{t('onboarding:analytics.shareDiagnostics.title')}</AnalyticsTitle>
</Box>
<AnalyticsText>{t('onboarding:analytics.shareDiagnostics.desc')}</AnalyticsText>
</Box>
<Box alignItems="center" horizontal mx={5}>
<CheckBox isChecked={false} />
</Box>
</Container>
<Container>
<Box justify="center">
<Box horizontal>
<AnalyticsTitle>{t('onboarding:analytics.shareDiagnostics.title')}</AnalyticsTitle>
</Box>
<AnalyticsText>{t('onboarding:analytics.shareDiagnostics.desc')}</AnalyticsText>
</Box>
<Box alignItems="center" horizontal mx={5}>
<CheckBox isChecked={false} />
</Box>
</Container>
</Box>
</Box>
<OnboardingFooter
@ -52,17 +57,23 @@ export default (props: StepProps) => {
export const AnalyticsText = styled(Box).attrs({
ff: 'Open Sans|Regular',
fontSize: 4,
fontSize: 3,
textAlign: 'left',
color: 'smoke',
})`
margin: 10px auto 25px;
padding-left: 10px;
max-width: 450px;
`
export const AnalyticsTitle = styled(Box).attrs({
ff: 'Open Sans|SemiBold',
fontSize: 4,
textAlign: 'left',
})`
margin-bottom: 5px;
`
const DeviceIcon = styled(Box).attrs({
alignItems: 'center',
justifyContent: 'center',
color: 'graphite',
const Container = styled(Box).attrs({
horizontal: true,
p: 5,
})`
width: 55px;
max-height: 90px;
width: 620px;
`

200
src/components/Onboarding/steps/GenuineCheck.js

@ -1,9 +1,9 @@
// @flow
import React, { PureComponent } from 'react'
import React, { PureComponent, Fragment } from 'react'
import { connect } from 'react-redux'
import styled from 'styled-components'
import { radii, colors } from 'styles/theme'
import { radii } from 'styles/theme'
import type { T } from 'types/common'
@ -11,8 +11,12 @@ import { setGenuineCheckFail } from 'reducers/onboarding'
import Box, { Card } from 'components/base/Box'
import Button from 'components/base/Button'
import IconCheck from 'icons/Check'
import RadioGroup from 'components/base/RadioGroup'
import GenuineCheckModal from 'components/GenuineCheckModal'
import IconLedgerNanoError from 'icons/onboarding/LedgerNanoError'
import IconLedgerBlueError from 'icons/onboarding/LedgerBlueError'
import IconCheck from 'icons/Check'
import { Title, Description, IconOptionRow } from '../helperComponents'
@ -24,39 +28,87 @@ const mapDispatchToProps = { setGenuineCheckFail }
type State = {
pinStepPass: boolean | null,
phraseStepPass: boolean | null,
cachedPinStepButton: string,
cachedPhraseStepButton: string,
isGenuineCheckModalOpened: boolean,
isDeviceGenuine: boolean,
}
class GenuineCheck extends PureComponent<StepProps, State> {
state = {
pinStepPass: null,
phraseStepPass: null,
cachedPinStepButton: '',
cachedPhraseStepButton: '',
isGenuineCheckModalOpened: false,
isDeviceGenuine: false,
}
handleStepPass = (step: string, pass: boolean | null) => {
this.setState({ [`${step}`]: pass })
getButtonLabel() {
const { t } = this.props
return [
{
label: t('common:yes'),
key: 'yes',
pass: true,
},
{
label: t('common:no'),
key: 'no',
pass: false,
},
]
}
handleButtonPass = (item: Object, step: string) => {
this.setState({ [`${step}`]: item.pass })
if (step === 'pinStepPass') {
this.setState({ cachedPinStepButton: item.key })
} else {
this.setState({ cachedPhraseStepButton: item.key })
}
if (typeof pass === 'boolean' && !pass) {
if (!item.pass) {
this.props.setGenuineCheckFail(true)
}
}
handleOpenGenuineCheckModal = () => this.setState({ isGenuineCheckModalOpened: true })
handleCloseGenuineCheckModal = () => this.setState({ isGenuineCheckModalOpened: false })
handleGenuineCheck = async isGenuine => {
await new Promise(r => setTimeout(r, 1e3)) // let's wait a bit before closing modal
this.handleCloseGenuineCheckModal()
this.setState({ isDeviceGenuine: isGenuine })
}
redoGenuineCheck = () => {
this.props.setGenuineCheckFail(false)
}
contactSupport = () => {
console.log('contact support coming later')
}
renderGenuineFail = () => (
<GenuineCheckFail
redoGenuineCheck={this.redoGenuineCheck}
contactSupport={this.contactSupport}
t={this.props.t}
isLedgerNano={this.props.onboarding.isLedgerNano}
/>
)
render() {
const { nextStep, prevStep, t, onboarding } = this.props
const { pinStepPass, phraseStepPass } = this.state
const {
pinStepPass,
phraseStepPass,
cachedPinStepButton,
cachedPhraseStepButton,
isGenuineCheckModalOpened,
isDeviceGenuine,
} = this.state
if (onboarding.isGenuineFail) {
return this.renderGenuineFail()
@ -76,22 +128,16 @@ class GenuineCheck extends PureComponent<StepProps, State> {
</Box>
<CardDescription>{t('onboarding:genuineCheck.steps.step2.desc')}</CardDescription>
</Box>
{!pinStepPass ? (
<ButtonCombo
handleStepPass={this.handleStepPass}
step="pinStepPass"
disabled={false}
t={this.props.t}
/>
) : (
<Box justify="center" color={colors.wallet} ml={8}>
<IconCheck size={16} />
</Box>
)}
<RadioGroup
style={{ margin: '0 30px' }}
items={this.getButtonLabel()}
activeKey={cachedPinStepButton}
onChange={item => this.handleButtonPass(item, 'pinStepPass')}
/>
</CardWrapper>
</Box>
<Box mt={5}>
<CardWrapper disabled={!pinStepPass}>
<CardWrapper isDisabled={!pinStepPass}>
<Box justify="center">
<Box horizontal>
<IconOptionRow>2.</IconOptionRow>
@ -99,22 +145,16 @@ class GenuineCheck extends PureComponent<StepProps, State> {
</Box>
<CardDescription>{t('onboarding:genuineCheck.steps.step2.desc')}</CardDescription>
</Box>
{!phraseStepPass ? (
<ButtonCombo
handleStepPass={this.handleStepPass}
step="phraseStepPass"
disabled={!pinStepPass}
t={this.props.t}
/>
) : (
<Box justify="center" color={colors.wallet} ml={8}>
<IconCheck size={16} />
</Box>
)}
<RadioGroup
style={{ margin: '0 30px' }}
items={this.getButtonLabel()}
activeKey={cachedPhraseStepButton}
onChange={item => this.handleButtonPass(item, 'phraseStepPass')}
/>
</CardWrapper>
</Box>
<Box mt={5}>
<CardWrapper disabled={!phraseStepPass}>
<CardWrapper isDisabled={!phraseStepPass}>
<Box justify="center">
<Box horizontal>
<IconOptionRow>3.</IconOptionRow>
@ -123,8 +163,20 @@ class GenuineCheck extends PureComponent<StepProps, State> {
<CardDescription>{t('onboarding:genuineCheck.steps.step3.desc')}</CardDescription>
</Box>
<Box justify="center" horizontal mx={5}>
<Button big primary disabled={!phraseStepPass}>
{t('onboarding:genuineCheck.buttons.genuineCheck')}
<Button
big
primary
disabled={!phraseStepPass}
onClick={this.handleOpenGenuineCheckModal}
>
{isDeviceGenuine ? (
<Box horizontal align="center" flow={1}>
<IconCheck size={16} />
<span>{t('onboarding:genuineCheck.buttons.tryAgain')}</span>
</Box>
) : (
t('onboarding:genuineCheck.buttons.genuineCheck')
)}
</Button>
</Box>
</CardWrapper>
@ -138,6 +190,11 @@ class GenuineCheck extends PureComponent<StepProps, State> {
nextStep={nextStep}
prevStep={prevStep}
/>
<GenuineCheckModal
isOpened={isGenuineCheckModalOpened}
onClose={this.handleCloseGenuineCheckModal}
onGenuineCheck={this.handleGenuineCheck}
/>
</Box>
)
}
@ -145,58 +202,42 @@ class GenuineCheck extends PureComponent<StepProps, State> {
export default connect(null, mapDispatchToProps)(GenuineCheck)
export function ButtonCombo({
handleStepPass,
step,
disabled,
t,
}: {
handleStepPass: any,
step: string,
disabled: boolean,
t: T,
}) {
return (
<Box justify="center" horizontal style={{ margin: '0 20px' }}>
<Button
disabled={disabled}
style={{ padding: '0 20px' }}
outline
onClick={() => handleStepPass(step, true)}
>
{t('common:yes')}
</Button>
<Button
disabled={disabled}
style={{ padding: '0 20px' }}
outline
onClick={() => handleStepPass(step, false)}
>
{t('common:no')}
</Button>
</Box>
)
}
// TODO extract to a separate file
export function GenuineCheckFail({
redoGenuineCheck,
contactSupport,
isLedgerNano,
t,
}: {
redoGenuineCheck: () => void,
contactSupport: () => void,
isLedgerNano: boolean,
t: T,
}) {
return (
<Box sticky pt={150}>
<Box sticky pt={250}>
<Box grow alignItems="center">
<Title>{t('onboarding:genuineCheck.errorPage.ledgerNano.title')}</Title>
<Description style={{ maxWidth: 527 }}>
{t('onboarding:genuineCheck.errorPage.ledgerNano.desc')}
</Description>
<Box style={{ minWidth: 527 }}>
<IconLedgerNanoError />
</Box>
{isLedgerNano ? (
<Fragment>
<Title>{t('onboarding:genuineCheck.errorPage.ledgerNano.title')}</Title>
<Description style={{ maxWidth: 527 }}>
{t('onboarding:genuineCheck.errorPage.ledgerNano.desc')}
</Description>
<Box style={{ minWidth: 527 }}>
<IconLedgerNanoError />
</Box>
</Fragment>
) : (
<Fragment>
<Title>{t('onboarding:genuineCheck.errorPage.ledgerBlue.title')}</Title>
<Description style={{ maxWidth: 527 }}>
{t('onboarding:genuineCheck.errorPage.ledgerBlue.desc')}
</Description>
<Box style={{ minWidth: 527, alignItems: 'center' }}>
<IconLedgerBlueError />
</Box>
</Fragment>
)}
</Box>
<Wrapper horizontal>
<Button
@ -249,9 +290,10 @@ const CardWrapper = styled(Card).attrs({
horizontal: true,
p: 5,
})`
border: ${props => (props.disabled ? '1px dashed #d8d8d8' : '1px solid #d8d8d8')};
max-height: 97px;
min-width: 620px;
background-color: ${props => (props.disabled ? colors.lightGrey : colors.white)};
opacity: ${props => (props.disabled ? 0.7 : 1)};
width: 620px;
border: ${p => `1px ${p.isDisabled ? 'dashed' : 'solid'} ${p.theme.colors.fog}`};
pointer-events: ${p => (p.isDisabled ? 'none' : 'auto')};
background-color: ${p => (p.isDisabled ? p.theme.colors.lightGrey : p.theme.colors.white)};
opacity: ${p => (p.isDisabled ? 0.7 : 1)};
`

98
src/components/Onboarding/steps/SelectDevice.js

@ -1,57 +1,78 @@
// @flow
import React from 'react'
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { connect } from 'react-redux'
import { isLedgerNano } from 'reducers/onboarding'
import Box from 'components/base/Box'
import IconCheckCirle from 'icons/CheckCircle'
import IconLedgerNano from 'icons/onboarding/LedgerNano'
import IconLedgerBlue from 'icons/onboarding/LedgerBlue'
import { Title, Description, Inner } from '../helperComponents'
import type { StepProps } from '..'
export default (props: StepProps) => {
const { nextStep, t } = props
const mapDispatchToProps = { isLedgerNano }
return (
<Box sticky pt={150}>
<Box grow alignItems="center">
<Title>{t('onboarding:selectDevice.title')}</Title>
<Description style={{ maxWidth: 714 }}>{t('onboarding:selectDevice.desc')}</Description>
<Box>
<Inner>
<DeviceContainer onClick={() => nextStep()}>
<DeviceIcon>
<IconLedgerNano />
</DeviceIcon>
<BlockTitle pb={3}>{t('onboarding:selectDevice.ledgerNanoCard.title')}</BlockTitle>
<BlockDescription>
{t('onboarding:selectDevice.ledgerNanoCard.desc')}
</BlockDescription>
</DeviceContainer>
<DeviceContainer>
<DeviceIcon>
<IconLedgerBlue />
</DeviceIcon>
<BlockTitle pb={3}>{t('onboarding:selectDevice.ledgerBlueCard.title')}</BlockTitle>
<BlockDescription>
{t('onboarding:selectDevice.ledgerBlueCard.desc')}
</BlockDescription>
</DeviceContainer>
</Inner>
class SelectDevice extends PureComponent<StepProps, {}> {
handleIsLedgerNano = (isLedgerNano: boolean) => {
this.props.isLedgerNano(isLedgerNano)
this.props.nextStep()
}
render() {
const { t, onboarding } = this.props
return (
<Box sticky pt={150}>
<Box grow alignItems="center">
<Title>{t('onboarding:selectDevice.title')}</Title>
<Description>{t('onboarding:selectDevice.desc')}</Description>
<Box>
<Inner>
<DeviceContainer
onClick={() => this.handleIsLedgerNano(true)}
style={{
position: 'relative',
}}
>
{onboarding.isLedgerNano && <DeviceSelected />}
<DeviceIcon>
<IconLedgerNano />
</DeviceIcon>
<BlockTitle pb={3}>{t('onboarding:selectDevice.ledgerNanoCard.title')}</BlockTitle>
</DeviceContainer>
<DeviceContainer
onClick={() => this.handleIsLedgerNano(false)}
style={{
position: 'relative',
}}
>
{!onboarding.isLedgerNano && <DeviceSelected />}
<DeviceIcon>
<IconLedgerBlue />
</DeviceIcon>
<BlockTitle pb={3}>{t('onboarding:selectDevice.ledgerBlueCard.title')}</BlockTitle>
</DeviceContainer>
</Inner>
</Box>
</Box>
</Box>
</Box>
)
)
}
}
export default connect(null, mapDispatchToProps)(SelectDevice)
const DeviceContainer = styled(Box).attrs({
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
})`
width: 218px;
height: 204px;
border: 1px solid #d8d8d8;
border: ${props => `1px solid ${props.theme.colors.fog}`};
&:hover,
&:focus {
opacity: 0.5;
@ -77,3 +98,16 @@ export const BlockTitle = styled(Box).attrs({
fontSize: 4,
textAlign: 'center',
})``
export function DeviceSelected() {
return (
<Box
style={{
position: 'absolute',
top: '10px',
right: '10px',
}}
>
<IconCheckCirle size={12} />
</Box>
)
}

80
src/components/Onboarding/steps/SelectPIN.js

@ -1,47 +1,59 @@
// @flow
import React from 'react'
import React, { Fragment } from 'react'
import Box from 'components/base/Box'
import { colors } from 'styles/theme'
import IconSelectPIN from 'icons/onboarding/SelectPIN'
import IconLedgerNanoSelectPIN from 'icons/onboarding/LedgerNanoSelectPIN'
import IconLedgerBlueSelectPIN from 'icons/onboarding/LedgerBlueSelectPIN'
import IconChevronRight from 'icons/ChevronRight'
import {
Title,
Description,
Inner,
OptionRow,
IconOptionRow,
DisclaimerBox,
} from '../helperComponents'
import { Title, Inner, OptionRow, IconOptionRow, DisclaimerBox } from '../helperComponents'
import OnboardingFooter from '../OnboardingFooter'
import type { StepProps } from '..'
export default (props: StepProps) => {
const { nextStep, prevStep, t } = props
const steps = [
const { nextStep, prevStep, t, onboarding } = props
const stepsLedgerNano = [
{
key: 'step1',
icon: <IconOptionRow>1.</IconOptionRow>,
desc: t('onboarding:selectPIN.instructions.step1'),
desc: t('onboarding:selectPIN.instructions.ledgerNano.step1'),
},
{
key: 'step2',
icon: <IconOptionRow>2.</IconOptionRow>,
desc: t('onboarding:selectPIN.instructions.step2'),
desc: t('onboarding:selectPIN.instructions.ledgerNano.step2'),
},
{
key: 'step3',
icon: <IconOptionRow>3.</IconOptionRow>,
desc: t('onboarding:selectPIN.instructions.step3'),
desc: t('onboarding:selectPIN.instructions.ledgerNano.step3'),
},
{
key: 'step4',
icon: <IconOptionRow>4.</IconOptionRow>,
desc: t('onboarding:selectPIN.instructions.step4'),
desc: t('onboarding:selectPIN.instructions.ledgerNano.step4'),
},
]
const stepsLedgerBlue = [
{
key: 'step1',
icon: <IconOptionRow>1.</IconOptionRow>,
desc: t('onboarding:selectPIN.instructions.ledgerBlue.step1'),
},
{
key: 'step2',
icon: <IconOptionRow>2.</IconOptionRow>,
desc: t('onboarding:selectPIN.instructions.ledgerBlue.step2'),
},
{
key: 'step3',
icon: <IconOptionRow>3.</IconOptionRow>,
desc: t('onboarding:selectPIN.instructions.ledgerBlue.step3'),
},
]
const disclaimerNotes = [
@ -66,18 +78,34 @@ export default (props: StepProps) => {
<Box grow alignItems="center">
<Box align="center" mb={5}>
<Title>{t('onboarding:selectPIN.title')}</Title>
<Description style={{ maxWidth: 527 }}>{t('onboarding:selectPIN.desc')}</Description>
</Box>
<Box align="center">
<Inner style={{ width: 760 }}>
<Box style={{ width: 260 }} mt={5}>
<IconSelectPIN />
</Box>
<Box align="center" mt={5}>
{onboarding.isLedgerNano ? (
<Fragment>
<Inner style={{ width: 680 }}>
<Box style={{ width: 260 }} mt={5}>
<IconLedgerNanoSelectPIN />
</Box>
<Box shrink grow flow={4}>
{stepsLedgerNano.map(step => <OptionRow key={step.key} step={step} />)}
</Box>
</Inner>
</Fragment>
) : (
<Fragment>
<Inner style={{ width: 680 }}>
<Box style={{ width: 260, alignItems: 'center' }}>
<IconLedgerBlueSelectPIN />
</Box>
<Box shrink grow flow={4}>
{stepsLedgerBlue.map(step => <OptionRow key={step.key} step={step} />)}
</Box>
</Inner>
</Fragment>
)}
<Box shrink grow flow={4}>
{steps.map(step => <OptionRow key={step.key} step={step} />)}
</Box>
</Inner>
<DisclaimerBox mt={6} disclaimerNotes={disclaimerNotes} />
</Box>
</Box>

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

@ -1,91 +1,93 @@
// @flow
// UNTIL IS NEEDED SET PASSWORD STEP IS COMMENTED OUT
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 style={{ maxWidth: 714 }}>{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 } 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

2
src/components/Onboarding/steps/WriteSeed.js

@ -60,7 +60,7 @@ export default (props: StepProps) => {
<Box grow alignItems="center">
<Box align="center" mb={5}>
<Title>{t('onboarding:writeSeed.title')}</Title>
<Description style={{ maxWidth: 714 }}>{t('onboarding:writeSeed.desc')}</Description>
<Description>{t('onboarding:writeSeed.desc')}</Description>
</Box>
<Box align="center">
<Inner style={{ width: 760 }}>

2
src/components/base/InputPassword/index.js

@ -106,7 +106,7 @@ class InputPassword extends PureComponent<Props, State> {
{withStrength && (
<Fragment>
<Box flow={1} horizontal>
{[0, 1, 2, 3, 4].map(v => (
{[0, 1, 2].map(v => (
<Strength
key={v}
warning={passwordStrength <= 1}

13
src/icons/Home.js

@ -0,0 +1,13 @@
// @flow
import React from 'react'
const path = (
<path d="m8.261 2.252a1.11 1.11 0 0 0-1.408 0l-6.772 5.545a0.224 0.224 0 0 0-0.03 0.313l0.563 0.69a0.224 0.224 0 0 0 0.314 0.03l0.408-0.333v5.502c0 0.245 0.2 0.445 0.445 0.445h4.667c0.122 0 0.222-0.1 0.222-0.222v-3.556h1.778v3.556c0 0.122 0.1 0.222 0.222 0.222h4.666c0.245 0 0.445-0.2 0.445-0.445v-5.505l0.408 0.333a0.224 0.224 0 0 0 0.314-0.03l0.564-0.69a0.227 0.227 0 0 0-0.036-0.31l-6.771-5.545zm4.184 10.858h-2.667v-3.555c0-0.122-0.1-0.222-0.222-0.222h-4c-0.122 0-0.222 0.1-0.222 0.222v3.555h-2.667v-5.708l4.747-3.889c0.08-0.066 0.2-0.066 0.28 0l4.748 3.89v5.707z"/>
)
export default ({ size, ...p }: { size: number }) => (
<svg viewBox="0 0 15.111 12.444" height={size} width={size} {...p}>
{path}
</svg>
)

47
src/icons/onboarding/LedgerBlueError.js

@ -0,0 +1,47 @@
// @flow
import React from 'react'
export default () => (
<svg width="92" height="188">
<defs>
<linearGradient id="a" x1="50%" x2="50%" y1="0%" y2="100%">
<stop offset="0%" />
<stop offset="100%" stopColor="#FFF" />
</linearGradient>
</defs>
<g fill="none" fillRule="evenodd">
<path
stroke="#1D2027"
strokeWidth="2"
d="M37 120.644a1 1 0 0 0-1 1v26.225a5 5 0 0 0 5 5h8.486a5 5 0 0 0 5-5v-26.225a1 1 0 0 0-1-1H37z"
/>
<path stroke="#142533" strokeWidth="2" d="M42.208 153.253v10.929h6.436v-10.93h-6.436z" />
<path
stroke="#1D2027"
strokeWidth="2"
d="M39.713 120.577h11.255l-.082-6.977a1 1 0 0 0-1-.988h-9.267a1 1 0 0 0-.988 1.012l.082 6.953z"
/>
<path
fill="url(#a)"
d="M6.836 53.925h1.616v22.65H6.836v-22.65zm5.657 0h1.616v22.65h-1.616v-22.65z"
transform="translate(35 111)"
/>
<path
fill="#1D2028"
d="M88.556 17.556c0-1.105.671-2 1.5-2 .828 0 1.5.895 1.5 2v6c0 1.104-.672 2-1.5 2-.829 0-1.5-.896-1.5-2"
/>
<rect
width="88"
height="118.635"
x="1"
y="1"
fill="#FCEAEC"
stroke="#1D2027"
strokeWidth="2"
rx="5.44"
/>
<rect width="59" height="88.615" x="15.5" y="16.5" fill="#FFF" stroke="#EA2E49" rx="4.08" />
</g>
</svg>
)

47
src/icons/onboarding/LedgerBlueSelectPIN.js

@ -0,0 +1,47 @@
// @flow
import React from 'react'
export default () => (
<svg width="92" height="188">
<defs>
<linearGradient id="a" x1="50%" x2="50%" y1="0%" y2="100%">
<stop offset="0%" />
<stop offset="100%" stopColor="#FFF" />
</linearGradient>
</defs>
<g fill="none" fillRule="evenodd">
<path
stroke="#1D2027"
strokeWidth="2"
d="M37 120.644a1 1 0 0 0-1 1v26.225a5 5 0 0 0 5 5h8.486a5 5 0 0 0 5-5v-26.225a1 1 0 0 0-1-1H37z"
/>
<path stroke="#142533" strokeWidth="2" d="M42.208 153.253v10.929h6.436v-10.93h-6.436z" />
<path
stroke="#1D2027"
strokeWidth="2"
d="M39.713 120.577h11.255l-.082-6.977a1 1 0 0 0-1-.988h-9.267a1 1 0 0 0-.988 1.012l.082 6.953z"
/>
<path
fill="url(#a)"
d="M6.836 53.925h1.616v22.65H6.836v-22.65zm5.657 0h1.616v22.65h-1.616v-22.65z"
transform="translate(35 111)"
/>
<path
fill="#1D2028"
d="M88.556 17.556c0-1.105.671-2 1.5-2 .828 0 1.5.895 1.5 2v6c0 1.104-.672 2-1.5 2-.829 0-1.5-.896-1.5-2"
/>
<rect
width="88"
height="118.635"
x="1"
y="1"
fill="#EFF3FD"
stroke="#1D2027"
strokeWidth="2"
rx="5.44"
/>
<rect width="59" height="88.615" x="15.5" y="16.5" fill="#FFF" stroke="#6490F1" rx="4.08" />
</g>
</svg>
)

30
src/icons/onboarding/LedgerNano.js

@ -3,22 +3,22 @@
import React from 'react'
export default () => (
<svg width="112" height="20">
<g fill="none" fillRule="evenodd" transform="rotate(-90 10 10)">
<rect width="1.6" height="6.4" x="18.4" y="6.4" fill="#1D2028" rx=".8" />
<rect width="1.6" height="6.4" x="18.4" y="27.2" fill="#1D2028" rx=".8" />
<svg width="13" height="72">
<g fill="none" fillRule="evenodd">
<rect width="1.04" height="4.114" x="11.96" y="4.114" fill="#1D2028" rx=".52" />
<rect width="1.04" height="4.114" x="11.96" y="17.486" fill="#1D2028" rx=".52" />
<path
fill="#6490F1"
fillOpacity=".1"
stroke="#1D2028"
strokeWidth="1.5"
d="M1.6.75a.85.85 0 0 0-.85.85v108.8c0 .47.38.85.85.85h16c.47 0 .85-.38.85-.85V1.6a.85.85 0 0 0-.85-.85h-16z"
d="M1.6.75a.85.85 0 0 0-.85.85v68.8c0 .47.38.85.85.85h9.28c.47 0 .85-.38.85-.85V1.6a.85.85 0 0 0-.85-.85H1.6z"
/>
<rect
width="9.1"
height="31.5"
x="5.081"
y="8.275"
width="5.74"
height="20.071"
x="3.39"
y="5.409"
fill="#FFF"
stroke="#6490F1"
strokeWidth=".5"
@ -28,9 +28,17 @@ export default () => (
fill="#FFF"
stroke="#1D2028"
strokeWidth="1.5"
d="M9.6 48.75A8.85 8.85 0 0 0 .75 57.6v52.8c0 .47.38.85.85.85h16c.47 0 .85-.38.85-.85V57.6a8.85 8.85 0 0 0-8.85-8.85z"
d="M6.24 31.607a5.49 5.49 0 0 0-5.49 5.49V70.4c0 .47.38.85.85.85h9.28c.47 0 .85-.38.85-.85V37.097a5.49 5.49 0 0 0-5.49-5.49z"
/>
<ellipse
cx="6.24"
cy="37.029"
fill="#FFF"
stroke="#6490F1"
strokeWidth=".5"
rx="2.87"
ry="2.836"
/>
<circle cx="9.6" cy="57.6" r="4.55" fill="#FFF" stroke="#6490F1" strokeWidth=".5" />
</g>
</svg>
)

0
src/icons/onboarding/SelectPIN.js → src/icons/onboarding/LedgerNanoSelectPIN.js

206
src/icons/onboarding/WriteSeed.js

@ -3,195 +3,43 @@
import React from 'react'
export default () => (
<svg width="157" height="144">
<defs>
<rect id="b" width="42" height="10" y="45" rx="2" />
<filter
id="a"
width="123.8%"
height="200%"
x="-11.9%"
y="-40%"
filterUnits="objectBoundingBox"
>
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="1.5" />
<feColorMatrix
in="shadowBlurOuter1"
values="0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 1 0"
/>
</filter>
<rect id="d" width="42" height="10" x="67" y="35" rx="2" />
<filter
id="c"
width="123.8%"
height="200%"
x="-11.9%"
y="-40%"
filterUnits="objectBoundingBox"
>
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="1.5" />
<feColorMatrix
in="shadowBlurOuter1"
values="0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 1 0"
/>
</filter>
<rect id="f" width="42" height="10" x="31" y="11" rx="2" />
<filter
id="e"
width="123.8%"
height="200%"
x="-11.9%"
y="-40%"
filterUnits="objectBoundingBox"
>
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="1.5" />
<feColorMatrix
in="shadowBlurOuter1"
values="0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 1 0"
/>
</filter>
<rect id="h" width="42" height="10" x="103" rx="2" />
<filter
id="g"
width="123.8%"
height="200%"
x="-11.9%"
y="-40%"
filterUnits="objectBoundingBox"
>
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="1.5" />
<feColorMatrix
in="shadowBlurOuter1"
values="0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 1 0"
/>
</filter>
<rect id="j" width="42" height="10" x="104" y="55" rx="2" />
<filter
id="i"
width="123.8%"
height="200%"
x="-11.9%"
y="-40%"
filterUnits="objectBoundingBox"
>
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="1.5" />
<feColorMatrix
in="shadowBlurOuter1"
values="0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 1 0"
/>
</filter>
<rect id="l" width="42" height="10" x="31" y="67" rx="2" />
<filter
id="k"
width="123.8%"
height="200%"
x="-11.9%"
y="-40%"
filterUnits="objectBoundingBox"
>
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="1.5" />
<feColorMatrix
in="shadowBlurOuter1"
values="0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 1 0"
/>
</filter>
<rect id="n" width="42" height="10" x="11" y="96" rx="2" />
<filter
id="m"
width="123.8%"
height="200%"
x="-11.9%"
y="-40%"
filterUnits="objectBoundingBox"
>
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="1.5" />
<feColorMatrix
in="shadowBlurOuter1"
values="0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 1 0"
/>
</filter>
<rect id="p" width="42" height="10" x="109" y="103" rx="2" />
<filter
id="o"
width="123.8%"
height="200%"
x="-11.9%"
y="-40%"
filterUnits="objectBoundingBox"
>
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="1.5" />
<feColorMatrix
in="shadowBlurOuter1"
values="0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 1 0"
/>
</filter>
<rect id="r" width="42" height="10" x="78" y="83" rx="2" />
<filter
id="q"
width="123.8%"
height="200%"
x="-11.9%"
y="-40%"
filterUnits="objectBoundingBox"
>
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
<feGaussianBlur in="shadowOffsetOuter1" result="shadowBlurOuter1" stdDeviation="1.5" />
<feColorMatrix
in="shadowBlurOuter1"
values="0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 0 0.847058824 0 0 0 1 0"
/>
</filter>
</defs>
<g fill="none" fillRule="evenodd" transform="translate(3 2)">
<circle
cx="79"
cy="72"
r="57"
<svg width="145" height="126">
<g fill="none" fillRule="evenodd" transform="translate(-1)">
<path
fill="#6490F1"
fillOpacity=".1"
stroke="#142533"
strokeWidth="2"
strokeWidth="1.8"
d="M29.105 103.014l19.87-84.34L4.123 34.999a2.7 2.7 0 0 0-1.573 3.566l26.556 64.449zm89.265 1.336L97.73 33.29l44.752 16.288a2.7 2.7 0 0 1 1.49 3.748L118.37 104.35z"
/>
<rect
width="88.2"
height="106.2"
x="29.9"
y=".9"
fill="#FFF"
stroke="#142533"
strokeWidth="1.8"
rx="3.6"
/>
<path
fill="#6490F1"
fillRule="nonzero"
stroke="#142533"
d="M88.63 118.285h-1.07v-6.596c0-4.791-3.846-8.689-8.574-8.689s-8.574 3.898-8.574 8.69v6.595H69.34c-1.844 0-3.339 1.515-3.339 3.384v15.695c0 1.869 1.495 3.384 3.339 3.384h19.292c1.843 0 3.339-1.515 3.339-3.384V121.67c0-1.87-1.495-3.384-3.34-3.384zm-15.22-6.596c0-3.115 2.502-5.649 5.576-5.649 3.072 0 5.573 2.534 5.573 5.65v6.595H73.41v-6.596z"
d="M82.969 92.378h23.22a1.81 1.81 0 0 1 0 3.622H82.97a1.81 1.81 0 0 1 0-3.622zm-41.158 0h23.22a1.81 1.81 0 0 1 0 3.622h-23.22a1.81 1.81 0 1 1 0-3.622zm41.158-12.675h23.22a1.81 1.81 0 0 1 0 3.621H82.97a1.81 1.81 0 0 1 0-3.621zm-41.158 0h23.22a1.81 1.81 0 0 1 0 3.621h-23.22a1.81 1.81 0 1 1 0-3.621zm41.158-12.676h23.22a1.81 1.81 0 0 1 0 3.622H82.97a1.81 1.81 0 0 1 0-3.622zm-41.158 0h23.22a1.81 1.81 0 0 1 0 3.622h-23.22a1.81 1.81 0 1 1 0-3.622zm41.158-12.676h23.22a1.81 1.81 0 0 1 0 3.622H82.97a1.81 1.81 0 0 1 0-3.622zm-41.158 0h23.22a1.81 1.81 0 0 1 0 3.622h-23.22a1.81 1.81 0 1 1 0-3.622zm41.158-12.675h23.22a1.81 1.81 0 0 1 0 3.621H82.97a1.81 1.81 0 0 1 0-3.621zm-41.158 0h23.22a1.81 1.81 0 0 1 0 3.621h-23.22a1.81 1.81 0 1 1 0-3.621zM82.969 29h23.22a1.81 1.81 0 0 1 0 3.622H82.97a1.81 1.81 0 0 1 0-3.622zM41.81 29h23.22a1.81 1.81 0 0 1 0 3.622H41.81a1.81 1.81 0 1 1 0-3.622z"
opacity=".5"
/>
<path
fill="#FFF"
d="M80.694 129.345v4.225a1.72 1.72 0 0 1-1.709 1.73 1.72 1.72 0 0 1-1.708-1.73v-4.225a3.08 3.08 0 0 1-1.325-2.54c0-1.698 1.358-3.074 3.033-3.074 1.675 0 3.033 1.377 3.033 3.075a3.083 3.083 0 0 1-1.325 2.54z"
fillRule="nonzero"
stroke="#6490F1"
strokeWidth="2"
d="M85.623 57.047a17.987 17.987 0 0 1 6.194 13.596c0 9.92-8.032 17.99-17.909 17.99-9.876 0-17.908-8.07-17.908-17.99a17.986 17.986 0 0 1 6.194-13.596v-3.281C62.194 47.278 67.447 42 73.908 42c6.462 0 11.715 5.278 11.715 11.766v3.28zM73.809 74.193zm.003-.018v4.55a.1.1 0 0 0 .096.102.1.1 0 0 0 .097-.102v-4.55a3.149 3.149 0 0 1-.006-.054l-.088-.899.885-.179a4.505 4.505 0 0 0 3.596-4.42c0-2.493-2.008-4.51-4.484-4.51-2.475 0-4.483 2.018-4.483 4.51a4.505 4.505 0 0 0 3.596 4.42l.885.18-.088.898a3.149 3.149 0 0 1-.006.054zm.196.018a.1.1 0 0 0 0 .004v-.004zm0 .004v-.002zm-7.62-19.876a17.719 17.719 0 0 1 7.52-1.668c2.633 0 5.186.577 7.521 1.668v-.555c0-4.17-3.375-7.562-7.52-7.562-4.146 0-7.521 3.392-7.521 7.562v.555z"
/>
<use fill="#000" filter="url(#a)" xlinkHref="#b" />
<use fill="#FFF" xlinkHref="#b" />
<use fill="#000" filter="url(#c)" xlinkHref="#d" />
<use fill="#FFF" xlinkHref="#d" />
<use fill="#000" filter="url(#e)" xlinkHref="#f" />
<use fill="#FFF" xlinkHref="#f" />
<use fill="#000" filter="url(#g)" xlinkHref="#h" />
<use fill="#FFF" xlinkHref="#h" />
<use fill="#000" filter="url(#i)" xlinkHref="#j" />
<use fill="#FFF" xlinkHref="#j" />
<use fill="#000" filter="url(#k)" xlinkHref="#l" />
<use fill="#FFF" xlinkHref="#l" />
<use fill="#000" filter="url(#m)" xlinkHref="#n" />
<use fill="#FFF" xlinkHref="#n" />
<g>
<use fill="#000" filter="url(#o)" xlinkHref="#p" />
<use fill="#FFF" xlinkHref="#p" />
</g>
<g>
<use fill="#000" filter="url(#q)" xlinkHref="#r" />
<use fill="#FFF" xlinkHref="#r" />
</g>
{/* <text fill="#142533" fontFamily="MuseoSans-700, Museo Sans" fontSize="5" fontWeight="500">
<tspan x="41" y="17">
RECOVERY PHRASE
</tspan>
</text> */}
<ellipse cx="75" cy="122.5" fill="#EEE" rx="48" ry="3.5" />
</g>
</svg>
)

26
src/reducers/onboarding.js

@ -18,12 +18,14 @@ export type OnboardingState = {
stepName: string, // TODO: specify that the string comes from Steps type
steps: Step[],
isGenuineFail: boolean,
isLedgerNano: boolean,
}
const state: OnboardingState = {
stepIndex: 0,
stepName: 'start',
isGenuineFail: false,
isLedgerNano: true,
steps: [
{
name: 'start',
@ -79,15 +81,16 @@ const state: OnboardingState = {
showBreadcrumb: true,
},
},
{
name: 'setPassword',
label: 'Set Password',
options: {
showFooter: false,
showBackground: true,
showBreadcrumb: true,
},
},
// UNTIL IS NEEDED SET PASSWORD IS COMMENTED OUT
// {
// name: 'setPassword',
// label: 'Set Password',
// options: {
// showFooter: false,
// showBackground: true,
// showBreadcrumb: true,
// },
// },
{
name: 'analytics',
label: 'Analytics & Bug report',
@ -144,6 +147,10 @@ const handlers = {
...state,
isGenuineFail,
}),
ONBOARDING_SET_DEVICE_TYPE: (state, { payload: isLedgerNano }) => ({
...state,
isLedgerNano,
}),
}
export default handleActions(handlers, state)
@ -152,3 +159,4 @@ export const nextStep = createAction('ONBOARDING_NEXT_STEP')
export const prevStep = createAction('ONBOARDING_PREV_STEP')
export const jumpStep = createAction('ONBOARDING_JUMP_STEP')
export const setGenuineCheckFail = createAction('ONBOARDING_SET_GENUINE_CHECK_FAIL')
export const isLedgerNano = createAction('ONBOARDING_SET_DEVICE_TYPE')

2
static/i18n/en/genuinecheck.yml

@ -0,0 +1,2 @@
modal:
title: Genuine check, bro

42
static/i18n/en/onboarding.yml

@ -25,13 +25,17 @@ selectDevice:
title: Ledger Blue
desc: Please replace it with the final wording once it’s done.
selectPIN:
title: Choose your PIN code
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
title: Start initialization & choose your PIN code
instructions:
step1: Connect the Ledger Nano S to your computer.
step2: Press both buttons simultaneously as instructed on the screen.
step3: Press the right button to select Configure as new device.
step4: Choose a PIN code between 4 and 8 digits long.
ledgerNano:
step1: Connect the Ledger Nano S to your computer.
step2: Press both buttons simultaneously as instructed on the screen.
step3: Press the right button to select Configure as new device.
step4: Choose a PIN code between 4 and 8 digits long.
ledgerBlue:
step1: Connect the Ledger Blue to your computer.
step2: Tap on Configure as new device.
step3: Choose a PIN code between 4 and 8 digits long.
disclaimer:
note1: Choose your own PIN code. This code unlocks your device.
note2: An 8-digit PIN code offers an optimum level of security.
@ -49,31 +53,41 @@ writeSeed:
note3: Make sure you are the sole holder of the 24-word recovery phrase.
note4: Never use a device supplied with a recovery phrase and/or a PIN code.
genuineCheck:
title: Check PIN / Seed / Authenticity
title: Final security check
desc: Your Ledger Nano S should now display Your device is now ready. Before getting started, please confirm that
steps:
step1:
title: You alone have chosen your PIN code
title: Did you choose your PIN code by yourself?
desc: This is a long text, please replace it with the final wording once it’s done. Lorem ipsum dolor amet ledger lorem dolor
step2:
title: You alone have initialized your recovery phrase
title: Did you save your recovery phrase by yourself?
desc: This is a long text, please replace it with the final wording once it’s done. Lorem ipsum dolor amet ledger lorem dolor
step3:
title: Your device is a genuine Ledger device
title: Check if your Ledger device is genuine
desc: This is a long text, please replace it with the final wording once it’s done. Lorem ipsum dolor amet ledger lorem dolor
buttons:
genuineCheck: Genuine check
tryAgain: Check again
contactSupport: Contact Support
errorPage:
ledgerNano:
title: Something is wrong with your Ledger Nano S
desc: Your Ledger Nano S should now display Your device is now ready. Before getting started, please confirm that
desc: A problem occurred with your Ledger Nano S. Contact Ledger Support to get assistance or go back to the security check.
ledgerBlue:
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: This is the title of the screen. 1 line is the maximum
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
title: Choose a password to securely access Ledger Live
desc: Use uppercase, lowercase, special characters (#, @, !, ?) and numbers for a stronger password.
analytics:
title: This is the title of the screen. 1 line is the maximum
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
shareDiagnostics:
title: Share analytics
desc: Help Ledger improve its products and services by automatically sending diagnostics and usage data.
shareData:
title: Share analytics
desc: Help Ledger improve its products and services by automatically sending diagnostics and usage data.
finish:
title: This is the title of the screen. 1 line is the maximum
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

Loading…
Cancel
Save