diff --git a/src/components/DeviceConnect/index.js b/src/components/DeviceConnect/index.js new file mode 100644 index 00000000..54a3994c --- /dev/null +++ b/src/components/DeviceConnect/index.js @@ -0,0 +1,321 @@ +// @flow + +import React, { PureComponent } from 'react' +import styled from 'styled-components' +import { Trans, translate } from 'react-i18next' +import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' + +import type { T, Device } from 'types/common' + +import noop from 'lodash/noop' + +import Box from 'components/base/Box' +import Spinner from 'components/base/Spinner' +import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon' +import TranslatedError from 'components/TranslatedError' + +import IconCheck from 'icons/Check' +import IconExclamationCircle from 'icons/ExclamationCircle' +import IconInfoCircle from 'icons/InfoCircle' +import IconUsb from 'icons/Usb' +import IconHome from 'icons/Home' + +import * as IconDevice from 'icons/device' + +// TODO: CHECK IF COMPONENT CAN BE REMOVED + +const Step = styled(Box).attrs({ + borderRadius: 1, + justifyContent: 'center', + fontSize: 4, +})` + border: 1px solid + ${p => + p.validated + ? p.theme.colors.wallet + : p.hasErrors + ? 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, + alignItems: 'center', +})` + height: 60px; + line-height: 1.2; + + strong { + font-weight: 600; + } +` + +const ListDevices = styled(Box).attrs({ + p: 3, + pt: 1, + flow: 2, +})`` + +const DeviceItem = styled(Box).attrs({ + bg: 'lightGrey', + borderRadius: 1, + alignItems: 'center', + color: 'dark', + ff: 'Open Sans|SemiBold', + fontSize: 4, + horizontal: true, + pr: 3, + pl: 0, +})` + cursor: pointer; + height: 54px; +` +const DeviceIcon = styled(Box).attrs({ + alignItems: 'center', + justifyContent: 'center', + color: 'graphite', +})` + width: 55px; +` +const DeviceSelected = styled(Box).attrs({ + alignItems: 'center', + bg: p => (p.selected ? 'wallet' : 'white'), + color: 'white', + justifyContent: 'center', +})` + border-radius: 50%; + border: 1px solid ${p => (p.selected ? p.theme.colors.wallet : p.theme.colors.fog)}; + height: 18px; + width: 18px; +` + +const WrapperIconCurrency = styled(Box).attrs({ + alignItems: 'center', + justifyContent: 'center', +})` + border: 1px solid ${p => p.theme.colors[p.color]}; + border-radius: 8px; + height: 24px; + width: 24px; +` + +const Info = styled(Box).attrs({ + alignItems: 'center', + color: p => (p.hasErrors ? 'alertRed' : 'grey'), + flow: 2, + fontSize: 3, + horizontal: true, + ml: 1, +})` + strong { + font-weight: 600; + } +` + +const StepCheck = ({ checked, hasErrors }: { checked: boolean, hasErrors?: boolean }) => ( + + {checked ? ( + + + + ) : hasErrors ? ( + + + + ) : ( + + )} + +) + +StepCheck.defaultProps = { + hasErrors: false, +} + +type Props = { + appOpened: null | 'success' | 'fail', + genuineCheckStatus: null | 'success' | 'fail', + withGenuineCheck: boolean, + currency: CryptoCurrency, + devices: Device[], + deviceSelected: ?Device, + onChangeDevice: Device => void, + t: T, + error: ?Error, +} + +const emitChangeDevice = props => { + const { onChangeDevice, deviceSelected, devices } = props + + if (deviceSelected === null && devices.length > 0) { + onChangeDevice(devices[0]) + } +} + +class DeviceConnect extends PureComponent { + static defaultProps = { + accountName: null, + appOpened: null, + devices: [], + deviceSelected: null, + onChangeDevice: noop, + withGenuineCheck: false, + } + + componentDidMount() { + emitChangeDevice(this.props) + } + + componentWillReceiveProps(nextProps) { + emitChangeDevice(nextProps) + } + + getStepState = stepStatus => ({ + success: stepStatus === 'success', + fail: stepStatus === 'fail', + }) + + render() { + const { + deviceSelected, + genuineCheckStatus, + withGenuineCheck, + appOpened, + error, + currency, + t, + onChangeDevice, + devices, + } = this.props + + const appState = this.getStepState(appOpened) + const genuineCheckState = this.getStepState(genuineCheckStatus) + + const hasDevice = devices.length > 0 + const hasMultipleDevices = devices.length > 1 + // TODO: place custom wording in trans tags into yml file + /* eslint-disable react/jsx-no-literals */ + return ( + + + + + + + + + Connect and unlock your Ledger device + + + + + {hasMultipleDevices && ( + + + {t('app:deviceConnect.step1.choose', { count: devices.length })} + + + {devices.map(d => { + const Icon = IconDevice[d.product.replace(/\s/g, '')] + return ( + onChangeDevice(d)}> + + + + + {`${d.manufacturer} ${d.product}`} + + + + + + + + ) + })} + + + )} + + + + {currency ? ( + + + + + + + + + {'Open the '} + {currency.name} + {' app on your device'} + + + + + ) : ( + + + + + + + + + {'Navigate to the '} + {'dashboard'} + {' on your device'} + + + + + )} + + + {/* GENUINE CHECK */} + {/* ------------- */} + + {withGenuineCheck && ( + + + + + + + + + + {'Allow '} + {'Ledger Manager'} + {' on your device'} + + + + + + )} + + {error ? ( + + + + + + + ) : null} + + ) + } +} + +export default translate()(DeviceConnect) diff --git a/static/i18n/en/onboarding.yml b/static/i18n/en/onboarding.yml index f9bdde1c..eecb4534 100644 --- a/static/i18n/en/onboarding.yml +++ b/static/i18n/en/onboarding.yml @@ -66,8 +66,6 @@ writeSeed: initialize: title: Save your recovery phrase desc: Your device will generate a 24-word recovery phrase to back up your private keys - #Save the 24-word recovery phrase on your device to back up your private keys - # Your device will generate a 24-word recovery phrase to back up your private keys nano: step1: 'Copy the word displayed below <1><0>Word #1 in position 1 on a blank Recovery sheet.' step2: 'Press the right button to display <1><0>Word #2 and repeat the process until all 24 words are copied on the Recovery sheet.' #Recovery sheet