Browse Source

Skeleton for genuine check in onboarding

master
meriadec 7 years ago
parent
commit
13f8e69034
No known key found for this signature in database GPG Key ID: 1D2FC2305E2CB399
  1. 96
      src/components/DeviceConnect/index.js
  2. 60
      src/components/EnsureDeviceApp/index.js
  3. 71
      src/components/GenuineCheckModal/index.js
  4. 48
      src/components/Onboarding/steps/GenuineCheck.js
  5. 13
      src/icons/Home.js
  6. 2
      static/i18n/en/genuinecheck.yml
  7. 1
      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>

60
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'
@ -14,8 +13,10 @@ import getAddress from 'commands/getAddress'
type OwnProps = {
currency: ?CryptoCurrency,
deviceSelected: ?Device,
withGenuineCheck: boolean,
account: ?Account,
onStatusChange?: (DeviceStatus, AppStatus, ?string) => void,
onGenuineCheck: (isGenuine: boolean) => void,
// TODO prefer children function
render?: ({
appStatus: AppStatus,
@ -87,19 +88,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.path,
@ -107,21 +110,31 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
segwit: account.path.startsWith("49'"), // TODO: store segwit info in account
}
} 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.address !== address) {
throw new Error('Account address is different than device address')
if (appOptions) {
const { address } = await getAddress.send(appOptions).toPromise()
if (account && account.address !== 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 +143,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(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 +188,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)

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

@ -12,8 +12,11 @@ import { setGenuineCheckFail } from 'reducers/onboarding'
import Box, { Card } from 'components/base/Box'
import Button from 'components/base/Button'
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'
@ -27,6 +30,8 @@ type State = {
phraseStepPass: boolean | null,
cachedPinStepButton: string,
cachedPhraseStepButton: string,
isGenuineCheckModalOpened: boolean,
isDeviceGenuine: boolean,
}
class GenuineCheck extends PureComponent<StepProps, State> {
@ -35,6 +40,8 @@ class GenuineCheck extends PureComponent<StepProps, State> {
phraseStepPass: null,
cachedPinStepButton: '',
cachedPhraseStepButton: '',
isGenuineCheckModalOpened: false,
isDeviceGenuine: false,
}
getButtonLabel() {
@ -66,12 +73,23 @@ class GenuineCheck extends PureComponent<StepProps, State> {
}
}
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}
@ -83,7 +101,14 @@ class GenuineCheck extends PureComponent<StepProps, State> {
render() {
const { nextStep, prevStep, t, onboarding } = this.props
const { pinStepPass, phraseStepPass, cachedPinStepButton, cachedPhraseStepButton } = this.state
const {
pinStepPass,
phraseStepPass,
cachedPinStepButton,
cachedPhraseStepButton,
isGenuineCheckModalOpened,
isDeviceGenuine,
} = this.state
if (onboarding.isGenuineFail) {
return this.renderGenuineFail()
@ -138,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>
@ -153,6 +190,11 @@ class GenuineCheck extends PureComponent<StepProps, State> {
nextStep={nextStep}
prevStep={prevStep}
/>
<GenuineCheckModal
isOpened={isGenuineCheckModalOpened}
onClose={this.handleCloseGenuineCheckModal}
onGenuineCheck={this.handleGenuineCheck}
/>
</Box>
)
}

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>
)

2
static/i18n/en/genuinecheck.yml

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

1
static/i18n/en/onboarding.yml

@ -67,6 +67,7 @@ genuineCheck:
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:

Loading…
Cancel
Save