Browse Source

Merge branch 'master' into i18n-eslint

master
Thibaut Boustany 7 years ago
parent
commit
053f270bfc
No known key found for this signature in database GPG Key ID: 32475B11A2B13EEC
  1. 76
      src/commands/libcoreSignAndBroadcast.js
  2. 8
      src/components/AccountPage/index.js
  3. 8
      src/components/Breadcrumb/Step.js
  4. 9
      src/components/ManagerPage/AppSearchBar.js
  5. 26
      src/components/ManagerPage/AppsList.js
  6. 4
      src/components/ManagerPage/Dashboard.js
  7. 2
      src/components/ManagerPage/FirmwareUpdate.js
  8. 12
      src/components/ManagerPage/ManagerApp.js
  9. 26
      src/components/ManagerPage/Workflow.js
  10. 28
      src/components/ManagerPage/WorkflowDefault.js
  11. 12
      src/components/ManagerPage/index.js
  12. 17
      src/components/Onboarding/OnboardingFooter.js
  13. 11
      src/components/Onboarding/helperComponents.js
  14. 2
      src/components/Onboarding/index.js
  15. 25
      src/components/Onboarding/steps/Analytics.js
  16. 4
      src/components/Onboarding/steps/Finish.js
  17. 112
      src/components/Onboarding/steps/GenuineCheck.js
  18. 24
      src/components/Onboarding/steps/Init.js
  19. 71
      src/components/Onboarding/steps/NoDevice.js
  20. 15
      src/components/Onboarding/steps/SelectDevice.js
  21. 8
      src/components/Onboarding/steps/SelectPIN/index.js
  22. 34
      src/components/Onboarding/steps/SetPassword.js
  23. 8
      src/components/Onboarding/steps/WriteSeed/index.js
  24. 3
      src/components/base/InputPassword/index.js
  25. 38
      src/helpers/libcore.js
  26. 16
      src/icons/Cart.js
  27. 16
      src/icons/EyeOff.js
  28. 16
      src/icons/Truck.js
  29. 9
      src/reducers/onboarding.js
  30. 16
      static/i18n/en/onboarding.yml

76
src/commands/libcoreSignAndBroadcast.js

@ -10,7 +10,7 @@ import { isSegwitAccount } from 'helpers/bip32'
import withLibcore from 'helpers/withLibcore' import withLibcore from 'helpers/withLibcore'
import { createCommand, Command } from 'helpers/ipc' import { createCommand, Command } from 'helpers/ipc'
import { withDevice } from 'helpers/deviceAccess' import { withDevice } from 'helpers/deviceAccess'
import { getWalletIdentifier } from 'helpers/libcore' import * as accountIdHelper from 'helpers/accountId'
type BitcoinLikeTransaction = { type BitcoinLikeTransaction = {
amount: number, amount: number,
@ -152,56 +152,46 @@ export async function doSignAndBroadcast({
onSigned: () => void, onSigned: () => void,
onOperationBroadcasted: (optimisticOp: $Exact<OperationRaw>) => void, onOperationBroadcasted: (optimisticOp: $Exact<OperationRaw>) => void,
}): Promise<void> { }): Promise<void> {
let njsAccount const { walletName } = accountIdHelper.decode(account.id)
const njsWallet = await core.getWallet(walletName)
if (isCancelled()) return
const njsAccount = await njsWallet.getAccount(account.index)
if (isCancelled()) return
const bitcoinLikeAccount = njsAccount.asBitcoinLikeAccount()
const njsWalletCurrency = njsWallet.getCurrency()
const amount = core.createAmount(njsWalletCurrency, transaction.amount)
const fees = core.createAmount(njsWalletCurrency, transaction.feePerByte)
const transactionBuilder = bitcoinLikeAccount.buildTransaction()
// TODO: check if is valid address. if not, it will fail silently on invalid
transactionBuilder.sendToAddress(amount, transaction.recipient)
// TODO: don't use hardcoded value for sequence (and first also maybe)
transactionBuilder.pickInputs(0, 0xffffff)
transactionBuilder.setFeesPerByte(fees)
const builded = await transactionBuilder.build()
if (isCancelled()) return
const sigHashType = Buffer.from(njsWalletCurrency.bitcoinLikeNetworkParameters.SigHash).toString(
'hex',
)
const signedTransaction = await withDevice(deviceId)(async transport => { const hasTimestamp = !!njsWalletCurrency.bitcoinLikeNetworkParameters.UsesTimestampedTransaction
const hwApp = new Btc(transport) // TODO: const timestampDelay = njsWalletCurrency.bitcoinLikeNetworkParameters.TimestampDelay
const WALLET_IDENTIFIER = await getWalletIdentifier({ const currency = getCryptoCurrencyById(account.currencyId)
hwApp,
isSegwit: isSegwitAccount(account), const signedTransaction = await withDevice(deviceId)(async transport =>
currencyId: account.currencyId, signTransaction({
devicePath: deviceId, hwApp: new Btc(transport),
})
const njsWallet = await core.getWallet(WALLET_IDENTIFIER)
if (isCancelled()) return null
njsAccount = await njsWallet.getAccount(account.index)
if (isCancelled()) return null
const bitcoinLikeAccount = njsAccount.asBitcoinLikeAccount()
const njsWalletCurrency = njsWallet.getCurrency()
const amount = core.createAmount(njsWalletCurrency, transaction.amount)
const fees = core.createAmount(njsWalletCurrency, transaction.feePerByte)
const transactionBuilder = bitcoinLikeAccount.buildTransaction()
// TODO: check if is valid address. if not, it will fail silently on invalid
transactionBuilder.sendToAddress(amount, transaction.recipient)
// TODO: don't use hardcoded value for sequence (and first also maybe)
transactionBuilder.pickInputs(0, 0xffffff)
transactionBuilder.setFeesPerByte(fees)
const builded = await transactionBuilder.build()
if (isCancelled()) return null
const sigHashType = Buffer.from(
njsWalletCurrency.bitcoinLikeNetworkParameters.SigHash,
).toString('hex')
const hasTimestamp = !!njsWalletCurrency.bitcoinLikeNetworkParameters.UsesTimestampedTransaction
// TODO: const timestampDelay = njsWalletCurrency.bitcoinLikeNetworkParameters.TimestampDelay
const currency = getCryptoCurrencyById(account.currencyId)
return signTransaction({
hwApp,
currencyId: account.currencyId, currencyId: account.currencyId,
transaction: builded, transaction: builded,
sigHashType: parseInt(sigHashType, 16), sigHashType: parseInt(sigHashType, 16),
supportsSegwit: !!currency.supportsSegwit, supportsSegwit: !!currency.supportsSegwit,
isSegwit: isSegwitAccount(account), isSegwit: isSegwitAccount(account),
hasTimestamp, hasTimestamp,
}) }),
}) )
if (!signedTransaction || isCancelled() || !njsAccount) return if (!signedTransaction || isCancelled() || !njsAccount) return
onSigned() onSigned()

8
src/components/AccountPage/index.js

@ -130,9 +130,11 @@ class AccountPage extends PureComponent<Props, State> {
)} )}
<Tooltip render={() => t('app:account.settings.title')}> <Tooltip render={() => t('app:account.settings.title')}>
<ButtonSettings onClick={() => openModal(MODAL_SETTINGS_ACCOUNT, { account })}> <ButtonSettings onClick={() => openModal(MODAL_SETTINGS_ACCOUNT, { account })}>
<Box align="center"> <Button small outlineGrey>
<IconAccountSettings size={16} /> <Box justifyContent="center">
</Box> <IconAccountSettings size={16} />
</Box>
</Button>
</ButtonSettings> </ButtonSettings>
</Tooltip> </Tooltip>
</Box> </Box>

8
src/components/Breadcrumb/Step.js

@ -35,7 +35,13 @@ const StepNumber = styled(Box).attrs({
ff: 'Rubik|Regular', ff: 'Rubik|Regular',
})` })`
border-radius: 50%; border-radius: 50%;
border: 1px solid ${p => (['active', 'valid'].includes(p.status) ? colors.wallet : colors.fog)}; border: 1px solid
${p =>
['active', 'valid'].includes(p.status)
? colors.wallet
: p.status === 'error'
? colors.alertRed
: colors.fog};
font-size: 10px; font-size: 10px;
height: ${RADIUS}px; height: ${RADIUS}px;
line-height: 10px; line-height: 10px;

9
src/components/ManagerPage/AppSearchBar.js

@ -42,6 +42,13 @@ const SearchBarWrapper = styled(Box).attrs({
margin: 0 0 20px 0; margin: 0 0 20px 0;
background-color: white; background-color: white;
padding: 0 13px; padding: 0 13px;
${p =>
p.focused
? `
border: 1px solid #6490f1;
`
: 'border: 1px solid white;'};
` `
const Input = styled.input.attrs({ const Input = styled.input.attrs({
@ -91,7 +98,7 @@ class AppSearchBar extends PureComponent<Props, State> {
return ( return (
<Box> <Box>
<SearchBarWrapper align="center"> <SearchBarWrapper align="center" focused={focused}>
<SearchIcon size={16} style={{ color }} /> <SearchIcon size={16} style={{ color }} />
<Input <Input
innerRef={c => (this.input = c)} innerRef={c => (this.input = c)}

26
src/components/ManagerPage/AppsList.js

@ -15,6 +15,8 @@ import Modal, { ModalBody } from 'components/base/Modal'
import Tooltip from 'components/base/Tooltip' import Tooltip from 'components/base/Tooltip'
import Text from 'components/base/Text' import Text from 'components/base/Text'
import Progress from 'components/base/Progress' import Progress from 'components/base/Progress'
import Spinner from 'components/base/Spinner'
import Button from 'components/base/Button'
import ExclamationCircle from 'icons/ExclamationCircle' import ExclamationCircle from 'icons/ExclamationCircle'
import Update from 'icons/Update' import Update from 'icons/Update'
@ -38,7 +40,7 @@ const ICONS_FALLBACK = {
} }
type Status = 'loading' | 'idle' | 'busy' | 'success' | 'error' type Status = 'loading' | 'idle' | 'busy' | 'success' | 'error'
type Mode = '' | 'installing' | 'uninstalling' type Mode = 'home' | 'installing' | 'uninstalling'
type LedgerApp = { type LedgerApp = {
name: string, name: string,
@ -71,7 +73,7 @@ class AppsList extends PureComponent<Props, State> {
error: null, error: null,
appsList: [], appsList: [],
app: '', app: '',
mode: '', mode: 'home',
} }
componentDidMount() { componentDidMount() {
@ -108,7 +110,7 @@ class AppsList extends PureComponent<Props, State> {
await installApp.send(data).toPromise() await installApp.send(data).toPromise()
this.setState({ status: 'success', app: '' }) this.setState({ status: 'success', app: '' })
} catch (err) { } catch (err) {
this.setState({ status: 'error', error: err.message, app: '', mode: '' }) this.setState({ status: 'error', error: err.message, app: '', mode: 'home' })
} }
} }
@ -123,11 +125,11 @@ class AppsList extends PureComponent<Props, State> {
await uninstallApp.send(data).toPromise() await uninstallApp.send(data).toPromise()
this.setState({ status: 'success', app: '' }) this.setState({ status: 'success', app: '' })
} catch (err) { } catch (err) {
this.setState({ status: 'error', error: err.message, app: '', mode: '' }) this.setState({ status: 'error', error: err.message, app: '', mode: 'home' })
} }
} }
handleCloseModal = () => this.setState({ status: 'idle', mode: '' }) handleCloseModal = () => this.setState({ status: 'idle', mode: 'home' })
renderModal = () => { renderModal = () => {
const { t } = this.props const { t } = this.props
@ -152,7 +154,9 @@ class AppsList extends PureComponent<Props, State> {
<Box align="center" justify="center" flow={3}> <Box align="center" justify="center" flow={3}>
<div>{'error happened'}</div> <div>{'error happened'}</div>
{error} {error}
<button onClick={this.handleCloseModal}>close</button> <Button primary onClick={this.handleCloseModal}>
close
</Button>
</Box> </Box>
) : status === 'success' ? ( ) : status === 'success' ? (
<Box align="center" justify="center" flow={3}> <Box align="center" justify="center" flow={3}>
@ -167,7 +171,9 @@ class AppsList extends PureComponent<Props, State> {
{ app }, { app },
)} )}
</Text> </Text>
<button onClick={this.handleCloseModal}>close</button> <Button primary onClick={this.handleCloseModal}>
close
</Button>
</Box> </Box>
) : null} ) : null}
</ModalBody> </ModalBody>
@ -178,7 +184,7 @@ class AppsList extends PureComponent<Props, State> {
renderList() { renderList() {
const { appsList } = this.state const { appsList } = this.state
return ( return appsList.length > 0 ? (
<Box> <Box>
<AppSearchBar list={appsList}> <AppSearchBar list={appsList}>
{items => ( {items => (
@ -198,6 +204,10 @@ class AppsList extends PureComponent<Props, State> {
</AppSearchBar> </AppSearchBar>
{this.renderModal()} {this.renderModal()}
</Box> </Box>
) : (
<Box align="center" justify="center">
<Spinner size={50} />
</Box>
) )
} }

4
src/components/ManagerPage/Dashboard.js

@ -33,7 +33,7 @@ const Dashboard = ({ device, deviceInfo, t }: Props) => (
{t('app:manager.subtitle')} {t('app:manager.subtitle')}
</Text> </Text>
</Box> </Box>
<Box mt={7}> <Box mt={5}>
<FirmwareUpdate <FirmwareUpdate
infos={{ infos={{
targetId: deviceInfo.targetId, targetId: deviceInfo.targetId,
@ -42,7 +42,7 @@ const Dashboard = ({ device, deviceInfo, t }: Props) => (
device={device} device={device}
/> />
</Box> </Box>
<Box> <Box mt={5}>
<AppsList device={device} targetId={deviceInfo.targetId} /> <AppsList device={device} targetId={deviceInfo.targetId} />
</Box> </Box>
</Box> </Box>

2
src/components/ManagerPage/FirmwareUpdate.js

@ -102,7 +102,7 @@ class FirmwareUpdate extends PureComponent<Props, State> {
const { latestFirmware } = this.state const { latestFirmware } = this.state
return ( return (
<Card px={4} py={25}> <Card p={4}>
<Box horizontal align="center" flow={2}> <Box horizontal align="center" flow={2}>
<Box color="dark"> <Box color="dark">
<NanoS size={30} /> <NanoS size={30} />

12
src/components/ManagerPage/ManagerApp.js

@ -14,13 +14,14 @@ import Button from 'components/base/Button'
const Container = styled(Box).attrs({ const Container = styled(Box).attrs({
horizontal: true, horizontal: true,
m: 3, my: 2,
mx: 3,
p: 4, p: 4,
boxShadow: 0, boxShadow: 0,
borderRadius: 4, borderRadius: 4,
flow: 3, flow: 2,
})` })`
width: 342px; width: calc(50% - 30px);
background: white; background: white;
line-height: normal; line-height: normal;
` `
@ -37,7 +38,6 @@ const AppName = styled(Box).attrs({
color: 'dark', color: 'dark',
})` })`
display: block; display: block;
width: 115px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
@ -57,8 +57,8 @@ function ManagerApp({ name, version, icon, onInstall, onUninstall, t }: Props) {
return ( return (
<Container> <Container>
<AppIcon src={iconUrl} /> <AppIcon src={iconUrl} />
<Box flex="1"> <Box flex="1" ml={3}>
<AppName>{name}</AppName> <AppName flex={1}>{name}</AppName>
<Text ff="Open Sans|Regular" fontSize={3} color="grey"> <Text ff="Open Sans|Regular" fontSize={3} color="grey">
{version} {version}
</Text> </Text>

26
src/components/ManagerPage/Workflow.js

@ -25,13 +25,16 @@ type Props = {
renderDefault: ( renderDefault: (
device: ?Device, device: ?Device,
deviceInfo: ?DeviceInfo, deviceInfo: ?DeviceInfo,
dashboardError: ?Error,
isGenuine: ?boolean, isGenuine: ?boolean,
error: {
dashboardError: ?Error,
genuineError: ?Error,
},
) => Node, ) => Node,
renderMcuUpdate: (deviceInfo: DeviceInfo) => Node, renderMcuUpdate: (deviceInfo: DeviceInfo) => Node,
renderFinalUpdate: (deviceInfo: DeviceInfo) => Node, renderFinalUpdate: (deviceInfo: DeviceInfo) => Node,
renderDashboard: (device: Device, deviceInfo: DeviceInfo) => Node, renderDashboard: (device: Device, deviceInfo: DeviceInfo) => Node,
renderError: (dashboardError: ?Error, genuineError: ?Error) => Node, renderError?: (dashboardError: ?Error, genuineError: ?Error) => Node,
} }
type State = {} type State = {}
@ -52,14 +55,12 @@ class Workflow extends PureComponent<Props, State> {
<EnsureGenuine device={device}> <EnsureGenuine device={device}>
{(isGenuine: ?boolean, genuineError: ?Error) => { {(isGenuine: ?boolean, genuineError: ?Error) => {
if (dashboardError || genuineError) { if (dashboardError || genuineError) {
return renderError ? ( return renderError
renderError(dashboardError, genuineError) ? renderError(dashboardError, genuineError)
) : ( : renderDefault(device, deviceInfo, isGenuine, {
<div> genuineError,
{dashboardError && <span>{dashboardError.message}</span>} dashboardError,
{genuineError && <span>{genuineError.message}</span>} })
</div>
)
} }
if (deviceInfo && deviceInfo.mcu) { if (deviceInfo && deviceInfo.mcu) {
@ -74,7 +75,10 @@ class Workflow extends PureComponent<Props, State> {
return renderDashboard(device, deviceInfo) return renderDashboard(device, deviceInfo)
} }
return renderDefault(device, deviceInfo, dashboardError, isGenuine) return renderDefault(device, deviceInfo, isGenuine, {
genuineError,
dashboardError,
})
}} }}
</EnsureGenuine> </EnsureGenuine>
)} )}

28
src/components/ManagerPage/WorkflowDefault.js

@ -8,7 +8,6 @@ import type { Device, T } from 'types/common'
import { i } from 'helpers/staticPath' import { i } from 'helpers/staticPath'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Space from 'components/base/Space'
import Text from 'components/base/Text' import Text from 'components/base/Text'
import Spinner from 'components/base/Spinner' import Spinner from 'components/base/Spinner'
@ -97,13 +96,15 @@ type Props = {
t: T, t: T,
device: ?Device, device: ?Device,
deviceInfo: ?DeviceInfo, deviceInfo: ?DeviceInfo,
dashboardError: ?Error, errors: {
dashboardError: ?Error,
genuineError: ?Error,
},
isGenuine: boolean, isGenuine: boolean,
} }
const WorkflowDefault = ({ device, deviceInfo, dashboardError, isGenuine, t }: Props) => ( const WorkflowDefault = ({ device, deviceInfo, errors, isGenuine, t }: Props) => (
<Box align="center"> <Box align="center" justify="center" sticky>
<Space of={152} />
<Box align="center" style={{ maxWidth: 460, padding: '0 10px' }}> <Box align="center" style={{ maxWidth: 460, padding: '0 10px' }}>
<img <img
src={i('logos/connectDevice.png')} src={i('logos/connectDevice.png')}
@ -117,7 +118,7 @@ const WorkflowDefault = ({ device, deviceInfo, dashboardError, isGenuine, t }: P
{t('app:manager.device.desc')} {t('app:manager.device.desc')}
</Text> </Text>
</Box> </Box>
<Box flow={4} style={{ maxWidth: 460, padding: '60px 10px 0' }}> <Box flow={4} style={{ maxWidth: 460, padding: '60px 10px 0' }} ff="Open Sans|Regular">
{/* DEVICE CHECK */} {/* DEVICE CHECK */}
<Step validated={!!device}> <Step validated={!!device}>
<StepContent> <StepContent>
@ -138,7 +139,7 @@ const WorkflowDefault = ({ device, deviceInfo, dashboardError, isGenuine, t }: P
</Step> </Step>
{/* DASHBOARD CHECK */} {/* DASHBOARD CHECK */}
<Step validated={!!device && !!deviceInfo} hasErrors={!!device && !!dashboardError}> <Step validated={!!device && !!deviceInfo} hasErrors={!!device && !!errors.dashboardError}>
<StepContent> <StepContent>
<StepIcon> <StepIcon>
<WrapperIconCurrency> <WrapperIconCurrency>
@ -152,14 +153,21 @@ const WorkflowDefault = ({ device, deviceInfo, dashboardError, isGenuine, t }: P
{' on your device'} {' on your device'}
</Trans> </Trans>
</Box> </Box>
<StepCheck checked={!!device && !!deviceInfo} hasErrors={!!device && !!dashboardError} /> <StepCheck
checked={!!device && !!deviceInfo}
hasErrors={!!device && !!errors.dashboardError}
/>
</StepContent> </StepContent>
</Step> </Step>
{/* GENUINE CHECK */} {/* GENUINE CHECK */}
<Step <Step
validated={(!!device && !isNull(isGenuine) && isGenuine) || undefined} validated={
hasErrors={(!!device && !isNull(isGenuine) && !isGenuine) || undefined} (!!device && !isNull(isGenuine) && isGenuine && !errors.genuineError) || undefined
}
hasErrors={
(!!device && !isNull(isGenuine) && !isGenuine) || errors.genuineError || undefined
}
> >
<StepContent> <StepContent>
<StepIcon> <StepIcon>

12
src/components/ManagerPage/index.js

@ -24,11 +24,6 @@ type Error = {
function ManagerPage(): Node { function ManagerPage(): Node {
return ( return (
<Workflow <Workflow
renderError={(dashboardError: ?Error, genuineError: ?Error) => {
if (dashboardError) return <span>Dashboard Error: {dashboardError.message}</span>
if (genuineError) return <span>Genuine Error: {genuineError.message}</span>
return <span>Error</span>
}}
renderFinalUpdate={(deviceInfo: DeviceInfo) => ( renderFinalUpdate={(deviceInfo: DeviceInfo) => (
<p>UPDATE FINAL FIRMARE (TEMPLATE + ACTION WIP) {deviceInfo.final}</p> <p>UPDATE FINAL FIRMARE (TEMPLATE + ACTION WIP) {deviceInfo.final}</p>
)} )}
@ -41,13 +36,16 @@ function ManagerPage(): Node {
renderDefault={( renderDefault={(
device: ?Device, device: ?Device,
deviceInfo: ?DeviceInfo, deviceInfo: ?DeviceInfo,
dashboardError: ?Error,
isGenuine: ?boolean, isGenuine: ?boolean,
errors: {
dashboardError: ?Error,
genuineError: ?Error,
},
) => ( ) => (
<WorkflowDefault <WorkflowDefault
device={device} device={device}
deviceInfo={deviceInfo} deviceInfo={deviceInfo}
dashboardError={dashboardError} errors={errors}
isGenuine={isGenuine} isGenuine={isGenuine}
/> />
)} )}

17
src/components/Onboarding/OnboardingFooter.js

@ -1,22 +1,11 @@
// @flow // @flow
import React from 'react' import React from 'react'
import styled from 'styled-components'
import { radii } from 'styles/theme'
import type { T } from 'types/common' import type { T } from 'types/common'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import Box from 'components/base/Box' import { OnboardingFooterWrapper } from './helperComponents'
const Wrapper = styled(Box).attrs({
px: 5,
py: 3,
})`
border-top: 1px solid ${p => p.theme.colors.lightFog};
border-bottom-left-radius: ${radii[1]}px;
border-bottom-right-radius: ${radii[1]}px;
`
type Props = { type Props = {
t: T, t: T,
@ -33,13 +22,13 @@ const OnboardingFooter = ({
...props ...props
}: Props) => ( }: Props) => (
<Wrapper {...props}> <OnboardingFooterWrapper {...props}>
<Button padded outlineGrey onClick={() => prevStep()}> <Button padded outlineGrey onClick={() => prevStep()}>
{t('app:common.back')} {t('app:common.back')}
</Button> </Button>
<Button padded disabled={isContinueDisabled} primary onClick={() => nextStep()} ml="auto"> <Button padded disabled={isContinueDisabled} primary onClick={() => nextStep()} ml="auto">
{t('app:common.continue')} {t('app:common.continue')}
</Button> </Button>
</Wrapper> </OnboardingFooterWrapper>
) )
export default OnboardingFooter export default OnboardingFooter

11
src/components/Onboarding/helperComponents.js

@ -32,12 +32,19 @@ export const Inner = styled(Box).attrs({
flow: 4, flow: 4,
})`` })``
export const FixedTopContainer = styled(Box).attrs({
sticky: true,
mt: 170,
backgroundColor: 'red',
})``
// FOOTER // FOOTER
export const OnboardingFooter = styled(Box).attrs({
export const OnboardingFooterWrapper = styled(Box).attrs({
px: 5, px: 5,
py: 3, py: 3,
horizontal: true,
})` })`
border-top: 2px solid ${p => p.theme.colors.lightGrey}; border-top: 2px solid ${p => p.theme.colors.lightFog};
border-bottom-left-radius: ${radii[1]}px; border-bottom-left-radius: ${radii[1]}px;
border-bottom-right-radius: ${radii[1]}px; border-bottom-right-radius: ${radii[1]}px;
` `

2
src/components/Onboarding/index.js

@ -27,6 +27,7 @@ import Box from 'components/base/Box'
import Start from './steps/Start' import Start from './steps/Start'
import InitStep from './steps/Init' import InitStep from './steps/Init'
import NoDeviceStep from './steps/NoDevice'
import OnboardingBreadcrumb from './OnboardingBreadcrumb' import OnboardingBreadcrumb from './OnboardingBreadcrumb'
import SelectDevice from './steps/SelectDevice' import SelectDevice from './steps/SelectDevice'
import SelectPIN from './steps/SelectPIN/index' import SelectPIN from './steps/SelectPIN/index'
@ -46,6 +47,7 @@ const STEPS = {
analytics: Analytics, analytics: Analytics,
finish: Finish, finish: Finish,
start: Start, start: Start,
noDevice: NoDeviceStep,
} }
const mapStateToProps = state => ({ const mapStateToProps = state => ({

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

@ -6,7 +6,7 @@ import { connect } from 'react-redux'
import { saveSettings } from 'actions/settings' import { saveSettings } from 'actions/settings'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import CheckBox from 'components/base/CheckBox' import CheckBox from 'components/base/CheckBox'
import { Title, Description } from '../helperComponents' import { Title, Description, FixedTopContainer } from '../helperComponents'
import OnboardingFooter from '../OnboardingFooter' import OnboardingFooter from '../OnboardingFooter'
import type { StepProps } from '..' import type { StepProps } from '..'
@ -15,13 +15,11 @@ const mapDispatchToProps = { saveSettings }
type State = { type State = {
analyticsToggle: boolean, analyticsToggle: boolean,
termsConditionsToggle: boolean,
sentryLogsToggle: boolean, sentryLogsToggle: boolean,
} }
const INITIAL_STATE = { const INITIAL_STATE = {
analyticsToggle: true, analyticsToggle: true,
termsConditionsToggle: false,
sentryLogsToggle: true, sentryLogsToggle: true,
} }
@ -40,9 +38,6 @@ class Analytics extends PureComponent<StepProps, State> {
shareAnalytics: isChecked, shareAnalytics: isChecked,
}) })
} }
handleTermsToggle = () => {
this.setState({ termsConditionsToggle: !this.state.termsConditionsToggle })
}
handleNavBack = () => { handleNavBack = () => {
const { savePassword, prevStep } = this.props const { savePassword, prevStep } = this.props
@ -52,11 +47,11 @@ class Analytics extends PureComponent<StepProps, State> {
render() { render() {
const { nextStep, t } = this.props const { nextStep, t } = this.props
const { analyticsToggle, termsConditionsToggle, sentryLogsToggle } = this.state const { analyticsToggle, sentryLogsToggle } = this.state
return ( return (
<Box sticky pt={50}> <FixedTopContainer>
<Box grow alignItems="center" justifyContent="center"> <Box grow alignItems="center">
<Title>{t('onboarding:analytics.title')}</Title> <Title>{t('onboarding:analytics.title')}</Title>
<Description>{t('onboarding:analytics.desc')}</Description> <Description>{t('onboarding:analytics.desc')}</Description>
<Box mt={5}> <Box mt={5}>
@ -78,15 +73,6 @@ class Analytics extends PureComponent<StepProps, State> {
<CheckBox isChecked={analyticsToggle} onChange={this.handleAnalyticsToggle} /> <CheckBox isChecked={analyticsToggle} onChange={this.handleAnalyticsToggle} />
</Box> </Box>
</Container> </Container>
<Container>
<Box>
<AnalyticsTitle>{t('onboarding:analytics.termsConditions.title')}</AnalyticsTitle>
<AnalyticsText>{t('onboarding:analytics.termsConditions.desc')}</AnalyticsText>
</Box>
<Box justifyContent="center">
<CheckBox isChecked={termsConditionsToggle} onChange={this.handleTermsToggle} />
</Box>
</Container>
</Box> </Box>
</Box> </Box>
<OnboardingFooter <OnboardingFooter
@ -96,9 +82,8 @@ class Analytics extends PureComponent<StepProps, State> {
t={t} t={t}
nextStep={nextStep} nextStep={nextStep}
prevStep={this.handleNavBack} prevStep={this.handleNavBack}
isContinueDisabled={!termsConditionsToggle}
/> />
</Box> </FixedTopContainer>
) )
} }
} }

4
src/components/Onboarding/steps/Finish.js

@ -39,8 +39,8 @@ const socialMedia = [
export default (props: StepProps) => { export default (props: StepProps) => {
const { finish, t } = props const { finish, t } = props
return ( return (
<Box sticky pt={200}> <Box sticky justifyContent="center">
<Box grow alignItems="center"> <Box alignItems="center">
<Box color="positiveGreen"> <Box color="positiveGreen">
<IconCheckCircle size={44} /> <IconCheckCircle size={44} />
</Box> </Box>

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

@ -4,13 +4,13 @@ import React, { PureComponent, Fragment } from 'react'
import { shell } from 'electron' import { shell } from 'electron'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import styled from 'styled-components' import styled from 'styled-components'
import { radii, colors } from 'styles/theme' import { colors } from 'styles/theme'
import type { T } from 'types/common' import type { T } from 'types/common'
import { updateGenuineCheck } from 'reducers/onboarding' import { updateGenuineCheck } from 'reducers/onboarding'
import Box, { Card } from 'components/base/Box' import Box from 'components/base/Box'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import RadioGroup from 'components/base/RadioGroup' import RadioGroup from 'components/base/RadioGroup'
import GenuineCheckModal from 'components/GenuineCheckModal' import GenuineCheckModal from 'components/GenuineCheckModal'
@ -19,7 +19,13 @@ import IconLedgerNanoError from 'icons/illustrations/LedgerNanoError'
import IconLedgerBlueError from 'icons/illustrations/LedgerBlueError' import IconLedgerBlueError from 'icons/illustrations/LedgerBlueError'
import IconCheck from 'icons/Check' import IconCheck from 'icons/Check'
import { Title, Description, IconOptionRow } from '../helperComponents' import {
Title,
Description,
IconOptionRow,
FixedTopContainer,
OnboardingFooterWrapper,
} from '../helperComponents'
import type { StepProps } from '..' import type { StepProps } from '..'
import OnboardingFooter from '../OnboardingFooter' import OnboardingFooter from '../OnboardingFooter'
@ -120,8 +126,8 @@ class GenuineCheck extends PureComponent<StepProps, State> {
} }
return ( return (
<Box sticky pt={50}> <FixedTopContainer>
<Box grow alignItems="center" justifyContent="center"> <Box grow alignItems="center">
<Title>{t('onboarding:genuineCheck.title')}</Title> <Title>{t('onboarding:genuineCheck.title')}</Title>
<Description>{t('onboarding:genuineCheck.desc')}</Description> <Description>{t('onboarding:genuineCheck.desc')}</Description>
<Box mt={5}> <Box mt={5}>
@ -145,16 +151,18 @@ class GenuineCheck extends PureComponent<StepProps, State> {
<CardWrapper isDisabled={!genuine.pinStepPass}> <CardWrapper isDisabled={!genuine.pinStepPass}>
<Box justify="center"> <Box justify="center">
<Box horizontal> <Box horizontal>
<IconOptionRow>{'2.'}</IconOptionRow> <IconOptionRow color={!genuine.pinStepPass ? 'grey' : 'wallet'}>{'2.'}</IconOptionRow>
<CardTitle>{t('onboarding:genuineCheck.steps.step2.title')}</CardTitle> <CardTitle>{t('onboarding:genuineCheck.steps.step2.title')}</CardTitle>
</Box> </Box>
</Box> </Box>
<Box justify="center"> <Box justify="center">
<RadioGroup {genuine.pinStepPass && (
items={this.getButtonLabel()} <RadioGroup
activeKey={cachedRecoveryStepButton} items={this.getButtonLabel()}
onChange={item => this.handleButtonPass(item, 'recoveryStepPass')} activeKey={cachedRecoveryStepButton}
/> onChange={item => this.handleButtonPass(item, 'recoveryStepPass')}
/>
)}
</Box> </Box>
</CardWrapper> </CardWrapper>
</Box> </Box>
@ -162,28 +170,32 @@ class GenuineCheck extends PureComponent<StepProps, State> {
<CardWrapper isDisabled={!genuine.recoveryStepPass}> <CardWrapper isDisabled={!genuine.recoveryStepPass}>
<Box justify="center"> <Box justify="center">
<Box horizontal> <Box horizontal>
<IconOptionRow>{'3.'}</IconOptionRow> <IconOptionRow color={!genuine.recoveryStepPass ? 'grey' : 'wallet'}>
{'3.'}
</IconOptionRow>
<CardTitle>{t('onboarding:genuineCheck.steps.step3.title')}</CardTitle> <CardTitle>{t('onboarding:genuineCheck.steps.step3.title')}</CardTitle>
</Box> </Box>
</Box> </Box>
<Box justify="center"> {genuine.recoveryStepPass && (
{genuine.isDeviceGenuine ? ( <Box justify="center">
<Box horizontal align="center" flow={1} color={colors.wallet}> {genuine.isDeviceGenuine ? (
<IconCheck size={16} /> <Box horizontal align="center" flow={1} color={colors.wallet}>
<GenuineSuccessText> <IconCheck size={16} />
{t('onboarding:genuineCheck.isGenuinePassed')} <GenuineSuccessText>
</GenuineSuccessText> {t('onboarding:genuineCheck.isGenuinePassed')}
</Box> </GenuineSuccessText>
) : ( </Box>
<Button ) : (
primary <Button
disabled={!genuine.recoveryStepPass} primary
onClick={this.handleOpenGenuineCheckModal} disabled={!genuine.recoveryStepPass}
> onClick={this.handleOpenGenuineCheckModal}
{t('onboarding:genuineCheck.buttons.genuineCheck')} >
</Button> {t('onboarding:genuineCheck.buttons.genuineCheck')}
)} </Button>
</Box> )}
</Box>
)}
</CardWrapper> </CardWrapper>
</Box> </Box>
</Box> </Box>
@ -201,7 +213,7 @@ class GenuineCheck extends PureComponent<StepProps, State> {
onClose={this.handleCloseGenuineCheckModal} onClose={this.handleCloseGenuineCheckModal}
onGenuineCheck={this.handleGenuineCheck} onGenuineCheck={this.handleGenuineCheck}
/> />
</Box> </FixedTopContainer>
) )
} }
} }
@ -246,27 +258,14 @@ export function GenuineCheckFail({
</Fragment> </Fragment>
)} )}
</Box> </Box>
<Wrapper horizontal> <OnboardingFooterWrapper>
<Button <Button padded outlineGrey onClick={() => redoGenuineCheck()}>
padded
outline
onClick={() => {
redoGenuineCheck()
}}
>
{t('app:common.back')} {t('app:common.back')}
</Button> </Button>
<Button <Button padded danger onClick={() => contactSupport()} ml="auto">
padded
danger
onClick={() => {
contactSupport()
}}
ml="auto"
>
{t('onboarding:genuineCheck.buttons.contactSupport')} {t('onboarding:genuineCheck.buttons.contactSupport')}
</Button> </Button>
</Wrapper> </OnboardingFooterWrapper>
</Box> </Box>
) )
} }
@ -281,23 +280,22 @@ export const CardTitle = styled(Box).attrs({
pl: 2, pl: 2,
})`` })``
const Wrapper = styled(Box).attrs({ const CardWrapper = styled(Box).attrs({
px: 5,
py: 3,
})`
border-top: 2px solid ${p => p.theme.colors.lightGrey};
border-bottom-left-radius: ${radii[1]}px;
border-bottom-right-radius: ${radii[1]}px;
`
const CardWrapper = styled(Card).attrs({
horizontal: true, horizontal: true,
p: 5, p: 5,
borderRadius: '4px',
justify: 'space-between', justify: 'space-between',
})` })`
width: 580px; width: 580px;
height: 74px; height: 74px;
transition: all ease-in-out 0.2s;
color: ${p => (p.isDisabled ? p.theme.colors.grey : p.theme.colors.black)};
border: ${p => `1px ${p.isDisabled ? 'dashed' : 'solid'} ${p.theme.colors.fog}`}; border: ${p => `1px ${p.isDisabled ? 'dashed' : 'solid'} ${p.theme.colors.fog}`};
pointer-events: ${p => (p.isDisabled ? 'none' : 'auto')}; pointer-events: ${p => (p.isDisabled ? 'none' : 'auto')};
background-color: ${p => (p.isDisabled ? p.theme.colors.lightGrey : p.theme.colors.white)}; background-color: ${p => (p.isDisabled ? p.theme.colors.lightGrey : p.theme.colors.white)};
opacity: ${p => (p.isDisabled ? 0.7 : 1)}; opacity: ${p => (p.isDisabled ? 0.7 : 1)};
&:hover {
cursor: pointer;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.05);
}
` `

24
src/components/Onboarding/steps/Init.js

@ -1,7 +1,6 @@
// @flow // @flow
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { shell } from 'electron'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { colors } from 'styles/theme' import { colors } from 'styles/theme'
@ -22,7 +21,7 @@ const mapDispatchToProps = { flowType }
class Init extends PureComponent<StepProps, *> { class Init extends PureComponent<StepProps, *> {
render() { render() {
const { nextStep, t } = this.props const { t, flowType, jumpStep } = this.props
const optionCards = [ const optionCards = [
{ {
@ -30,8 +29,8 @@ class Init extends PureComponent<StepProps, *> {
icon: <IconPlus size={20} />, icon: <IconPlus size={20} />,
title: t('onboarding:init.newDevice.title'), title: t('onboarding:init.newDevice.title'),
onClick: () => { onClick: () => {
nextStep() jumpStep('selectDevice')
this.props.flowType('newDevice') flowType('newDevice')
}, },
}, },
{ {
@ -39,8 +38,8 @@ class Init extends PureComponent<StepProps, *> {
icon: <IconRecover size={20} />, icon: <IconRecover size={20} />,
title: t('onboarding:init.restoreDevice.title'), title: t('onboarding:init.restoreDevice.title'),
onClick: () => { onClick: () => {
nextStep() jumpStep('selectDevice')
this.props.flowType('restoreDevice') flowType('restoreDevice')
}, },
}, },
{ {
@ -48,8 +47,8 @@ class Init extends PureComponent<StepProps, *> {
icon: <IconCheck size={20} />, icon: <IconCheck size={20} />,
title: t('onboarding:init.initializedDevice.title'), title: t('onboarding:init.initializedDevice.title'),
onClick: () => { onClick: () => {
nextStep() jumpStep('selectDevice')
this.props.flowType('initializedDevice') flowType('initializedDevice')
}, },
}, },
{ {
@ -57,15 +56,15 @@ class Init extends PureComponent<StepProps, *> {
icon: <IconExternalLink size={20} />, icon: <IconExternalLink size={20} />,
title: t('onboarding:init.noDevice.title'), title: t('onboarding:init.noDevice.title'),
onClick: () => { onClick: () => {
shell.openExternal('https://www.ledger.fr/') jumpStep('noDevice')
this.props.flowType('noDevice') flowType('noDevice')
}, },
}, },
] ]
return ( return (
<Box sticky pt={130}> <Box sticky justifyContent="center">
<Box align="center" justifyContent="center"> <Box align="center">
<Box color="wallet"> <Box color="wallet">
<IconUser size={36} /> <IconUser size={36} />
</Box> </Box>
@ -117,6 +116,7 @@ const InitCardContainer = styled(Box).attrs({
border: 1px solid ${p => p.theme.colors.fog}; border: 1px solid ${p => p.theme.colors.fog};
width: 530px; width: 530px;
height: 70px; height: 70px;
transition: all ease-in-out 0.2s;
&:hover { &:hover {
cursor: pointer; cursor: pointer;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.05); box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.05);

71
src/components/Onboarding/steps/NoDevice.js

@ -0,0 +1,71 @@
// @flow
import React, { PureComponent } from 'react'
import { shell } from 'electron'
import Box from 'components/base/Box'
import IconUser from 'icons/User'
import IconCart from 'icons/Cart'
import IconTruck from 'icons/Truck'
import IconInfoCircle from 'icons/InfoCircle'
import Button from '../../base/Button/index'
import { Title, OnboardingFooterWrapper } from '../helperComponents'
import { OptionFlowCard } from './Init'
import type { StepProps } from '..'
class NoDevice extends PureComponent<StepProps, *> {
render() {
const { t, prevStep } = this.props
const optionCards = [
{
key: 'buyNew',
icon: <IconCart size={20} />,
title: t('onboarding:noDevice.buyNew.title'),
onClick: () => {
shell.openExternal('https://www.ledgerwallet.com/')
},
},
{
key: 'trackOrder',
icon: <IconTruck size={20} />,
title: t('onboarding:noDevice.trackOrder.title'),
onClick: () => {
shell.openExternal('http://order.ledgerwallet.com/')
},
},
{
key: 'learnMore',
icon: <IconInfoCircle size={20} />,
title: t('onboarding:noDevice.learnMore.title'),
onClick: () => {
shell.openExternal('https://www.ledgerwallet.com/')
},
},
]
return (
<Box sticky pt={130}>
<Box grow alignItems="center">
<Box color="wallet">
<IconUser size={36} />
</Box>
<Box m={5} style={{ maxWidth: 480 }}>
<Title>{t('onboarding:noDevice.title')}</Title>
</Box>
<Box pt={4} flow={4}>
{optionCards.map(card => <OptionFlowCard key={card.key} card={card} />)}
</Box>
</Box>
<OnboardingFooterWrapper>
<Button padded outlineGrey onClick={() => prevStep()} mr="auto">
{t('app:common.back')}
</Button>
</OnboardingFooterWrapper>
</Box>
)
}
}
export default NoDevice

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

@ -11,7 +11,7 @@ import Box from 'components/base/Box'
import IconCheckCirle from 'icons/Check' import IconCheckCirle from 'icons/Check'
import IconLedgerNano from 'icons/illustrations/LedgerNano' import IconLedgerNano from 'icons/illustrations/LedgerNano'
import IconLedgerBlue from 'icons/illustrations/LedgerBlue' import IconLedgerBlue from 'icons/illustrations/LedgerBlue'
import { Title, Inner } from '../helperComponents' import { Title, Inner, FixedTopContainer } from '../helperComponents'
import OnboardingFooter from '../OnboardingFooter' import OnboardingFooter from '../OnboardingFooter'
import type { StepProps } from '..' import type { StepProps } from '..'
@ -32,11 +32,11 @@ class SelectDevice extends PureComponent<StepProps, {}> {
} }
} }
render() { render() {
const { t, onboarding, prevStep } = this.props const { t, onboarding, jumpStep } = this.props
return ( return (
<Box sticky> <FixedTopContainer>
<Box grow alignItems="center" justifyContent="center"> <Box grow alignItems="center">
<Box m={5}> <Box mb={5}>
<Title>{t('onboarding:selectDevice.title')}</Title> <Title>{t('onboarding:selectDevice.title')}</Title>
</Box> </Box>
<Box pt={4}> <Box pt={4}>
@ -66,13 +66,12 @@ class SelectDevice extends PureComponent<StepProps, {}> {
</Box> </Box>
<OnboardingFooter <OnboardingFooter
horizontal horizontal
flow={2}
t={t} t={t}
nextStep={this.handleContinue} nextStep={this.handleContinue}
prevStep={prevStep} prevStep={() => jumpStep('init')}
isContinueDisabled={onboarding.isLedgerNano === null} isContinueDisabled={onboarding.isLedgerNano === null}
/> />
</Box> </FixedTopContainer>
) )
} }
} }

8
src/components/Onboarding/steps/SelectPIN/index.js

@ -4,7 +4,7 @@ import React from 'react'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import { Title } from '../../helperComponents' import { Title, FixedTopContainer } from '../../helperComponents'
import OnboardingFooter from '../../OnboardingFooter' import OnboardingFooter from '../../OnboardingFooter'
import SelectPINnano from './SelectPINnano' import SelectPINnano from './SelectPINnano'
import SelectPINblue from './SelectPINblue' import SelectPINblue from './SelectPINblue'
@ -15,14 +15,14 @@ export default (props: StepProps) => {
const { nextStep, prevStep, t, onboarding } = props const { nextStep, prevStep, t, onboarding } = props
return ( return (
<Box sticky pt={50}> <FixedTopContainer>
<Box grow alignItems="center" justifyContent="center"> <Box grow alignItems="center">
<Title>{t('onboarding:selectPIN.title')}</Title> <Title>{t('onboarding:selectPIN.title')}</Title>
<Box align="center" mt={7}> <Box align="center" mt={7}>
{onboarding.isLedgerNano ? <SelectPINnano /> : <SelectPINblue />} {onboarding.isLedgerNano ? <SelectPINnano /> : <SelectPINblue />}
</Box> </Box>
</Box> </Box>
<OnboardingFooter horizontal flow={2} t={t} nextStep={nextStep} prevStep={prevStep} /> <OnboardingFooter horizontal flow={2} t={t} nextStep={nextStep} prevStep={prevStep} />
</Box> </FixedTopContainer>
) )
} }

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

@ -2,8 +2,7 @@
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import bcrypt from 'bcryptjs' import bcrypt from 'bcryptjs'
import { colors, radii } from 'styles/theme' import { colors } from 'styles/theme'
import styled from 'styled-components'
import { setEncryptionKey } from 'helpers/db' import { setEncryptionKey } from 'helpers/db'
@ -15,7 +14,13 @@ import IconChevronRight from 'icons/ChevronRight'
import PasswordForm from '../../SettingsPage/PasswordForm' import PasswordForm from '../../SettingsPage/PasswordForm'
import type { StepProps } from '..' import type { StepProps } from '..'
import { Title, Description, DisclaimerBox } from '../helperComponents' import {
Title,
Description,
DisclaimerBox,
FixedTopContainer,
OnboardingFooterWrapper,
} from '../helperComponents'
type State = { type State = {
currentPassword: string, currentPassword: string,
@ -90,10 +95,10 @@ class SetPassword extends PureComponent<StepProps, State> {
] ]
return ( return (
<Box sticky pt={50}> <FixedTopContainer>
<Box grow alignItems="center" justify="center"> <Box grow alignItems="center">
<Fragment> <Fragment>
<Box mb={3} alignItems="center"> <Box alignItems="center">
<Title>{t('onboarding:setPassword.title')}</Title> <Title>{t('onboarding:setPassword.title')}</Title>
<Description style={{ maxWidth: 620 }}> <Description style={{ maxWidth: 620 }}>
{t('onboarding:setPassword.desc')} {t('onboarding:setPassword.desc')}
@ -117,7 +122,7 @@ class SetPassword extends PureComponent<StepProps, State> {
</Fragment> </Fragment>
</Box> </Box>
<CustomFooter> <OnboardingFooterWrapper>
<Button padded outlineGrey onClick={() => prevStep()}> <Button padded outlineGrey onClick={() => prevStep()}>
{t('app:common.back')} {t('app:common.back')}
</Button> </Button>
@ -134,21 +139,10 @@ class SetPassword extends PureComponent<StepProps, State> {
{t('app:common.continue')} {t('app:common.continue')}
</Button> </Button>
</Box> </Box>
</CustomFooter> </OnboardingFooterWrapper>
</Box> </FixedTopContainer>
) )
} }
} }
export default SetPassword 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;
`

8
src/components/Onboarding/steps/WriteSeed/index.js

@ -9,15 +9,15 @@ import OnboardingFooter from '../../OnboardingFooter'
import WriteSeedNano from './WriteSeedNano' import WriteSeedNano from './WriteSeedNano'
import WriteSeedBlue from './WriteSeedBlue' import WriteSeedBlue from './WriteSeedBlue'
import WriteSeedRestore from './WriteSeedRestore' import WriteSeedRestore from './WriteSeedRestore'
import { FixedTopContainer } from '../../helperComponents'
import type { StepProps } from '../..' import type { StepProps } from '../..'
export default (props: StepProps) => { export default (props: StepProps) => {
const { nextStep, prevStep, t, onboarding } = props const { nextStep, prevStep, t, onboarding } = props
return ( return (
<Box sticky pt={50}> <FixedTopContainer>
<Box grow alignItems="center" justifyContent="center"> <Box grow alignItems="center">
{onboarding.flowType === 'restoreDevice' ? ( {onboarding.flowType === 'restoreDevice' ? (
<WriteSeedRestore /> <WriteSeedRestore />
) : onboarding.isLedgerNano ? ( ) : onboarding.isLedgerNano ? (
@ -34,6 +34,6 @@ export default (props: StepProps) => {
nextStep={nextStep} nextStep={nextStep}
prevStep={prevStep} prevStep={prevStep}
/> />
</Box> </FixedTopContainer>
) )
} }

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

@ -14,6 +14,7 @@ import Box from 'components/base/Box'
import Input from 'components/base/Input' import Input from 'components/base/Input'
import IconEye from 'icons/Eye' import IconEye from 'icons/Eye'
import IconEyeOff from 'icons/EyeOff'
const InputRight = styled(Box).attrs({ const InputRight = styled(Box).attrs({
color: 'grey', color: 'grey',
@ -99,7 +100,7 @@ class InputPassword extends PureComponent<Props, State> {
onChange={this.handleChange} onChange={this.handleChange}
renderRight={ renderRight={
<InputRight onClick={this.toggleInputType}> <InputRight onClick={this.toggleInputType}>
<IconEye size={16} /> {inputType === 'password' ? <IconEye size={16} /> : <IconEyeOff size={16} />}
</InputRight> </InputRight>
} }
/> />

38
src/helpers/libcore.js

@ -34,10 +34,9 @@ export function scanAccountsOnDevice(props: Props): Promise<AccountRaw[]> {
const commonParams = { const commonParams = {
core, core,
hwApp,
currencyId, currencyId,
onAccountScanned, onAccountScanned,
devicePath, hwApp,
} }
let allAccounts = [] let allAccounts = []
@ -62,22 +61,16 @@ export function scanAccountsOnDevice(props: Props): Promise<AccountRaw[]> {
}) })
} }
export async function getWalletIdentifier({ function encodeWalletName({
hwApp, publicKey,
isSegwit,
currencyId, currencyId,
devicePath, isSegwit,
}: { }: {
hwApp: Object, publicKey: string,
isSegwit: boolean,
currencyId: string, currencyId: string,
devicePath: string, isSegwit: boolean,
}): Promise<string> { }) {
const isVerify = false return `${publicKey}__${currencyId}${isSegwit ? '_segwit' : ''}`
const deviceIdentifiers = await hwApp.getWalletPublicKey(devicePath, isVerify, isSegwit)
const { publicKey } = deviceIdentifiers
const WALLET_IDENTIFIER = `${publicKey}__${currencyId}${isSegwit ? '_segwit' : ''}`
return WALLET_IDENTIFIER
} }
async function scanAccountsOnDeviceBySegwit({ async function scanAccountsOnDeviceBySegwit({
@ -85,7 +78,6 @@ async function scanAccountsOnDeviceBySegwit({
hwApp, hwApp,
currencyId, currencyId,
onAccountScanned, onAccountScanned,
devicePath,
isSegwit, isSegwit,
showNewAccount, showNewAccount,
}: { }: {
@ -93,15 +85,19 @@ async function scanAccountsOnDeviceBySegwit({
hwApp: Object, hwApp: Object,
currencyId: string, currencyId: string,
onAccountScanned: AccountRaw => void, onAccountScanned: AccountRaw => void,
devicePath: string, isSegwit: boolean, // FIXME all segwit to change to 'purpose'
isSegwit: boolean,
showNewAccount: boolean, showNewAccount: boolean,
}): Promise<AccountRaw[]> { }): Promise<AccountRaw[]> {
// compute wallet identifier const { coinType } = getCryptoCurrencyById(currencyId)
const WALLET_IDENTIFIER = await getWalletIdentifier({ hwApp, isSegwit, currencyId, devicePath }) const { publicKey } = await hwApp.getWalletPublicKey(
`${isSegwit ? '49' : '44'}'/${coinType}'`,
false,
isSegwit,
)
const walletName = encodeWalletName({ publicKey, currencyId, isSegwit })
// retrieve or create the wallet // retrieve or create the wallet
const wallet = await getOrCreateWallet(core, WALLET_IDENTIFIER, currencyId, isSegwit) const wallet = await getOrCreateWallet(core, walletName, currencyId, isSegwit)
const accountsCount = await wallet.getAccountCount() const accountsCount = await wallet.getAccountCount()
// recursively scan all accounts on device on the given app // recursively scan all accounts on device on the given app

16
src/icons/Cart.js

@ -0,0 +1,16 @@
// @flow
import React from 'react'
const path = (
<path
fill="currentColor"
d="M15.333 2.375H4.008l-.243-1.307A.677.677 0 0 0 3.111.5H.333A.343.343 0 0 0 0 .852v.703c0 .194.15.351.333.351h2.23l1.932 10.421A1.923 1.923 0 0 0 4 13.625c0 1.036.796 1.875 1.778 1.875s1.778-.84 1.778-1.875c0-.33-.082-.653-.239-.938h4.033a1.945 1.945 0 0 0-.239.938c0 1.036.796 1.875 1.778 1.875s1.778-.84 1.778-1.875c0-.531-.21-1.01-.547-1.352l.029-.14c.09-.437-.226-.852-.652-.852H5.66L5.4 9.875h8.677c.314 0 .585-.23.652-.554l1.256-6.094c.09-.438-.227-.852-.652-.852zM5.778 14.328c-.368 0-.667-.315-.667-.703 0-.388.3-.703.667-.703.367 0 .666.315.666.703 0 .388-.299.703-.666.703zm7.11 0c-.367 0-.666-.315-.666-.703 0-.388.3-.703.667-.703.368 0 .667.315.667.703 0 .388-.3.703-.667.703zm.652-5.86H5.138l-.87-4.687h10.238L13.54 8.47z"
/>
)
export default ({ size, ...p }: { size: number }) => (
<svg viewBox="0 0 16 16" height={size} width={size} {...p}>
{path}
</svg>
)

16
src/icons/EyeOff.js

@ -0,0 +1,16 @@
// @flow
import React from 'react'
const path = (
<path
fill="currentColor"
d="M2.502 8.393c.335.494.731.99 1.184 1.45 1.267 1.286 2.713 2.046 4.304 2.046a5.462 5.462 0 0 0 3.121-1.03c.257-.187.512-.072.708.172.195.244.256.525 0 .71A6.674 6.674 0 0 1 8 13c-1.972 0-3.698-.907-5.165-2.398a11.477 11.477 0 0 1-1.313-1.606 8.35 8.35 0 0 1-.46-.748.532.532 0 0 1 .007-.51 10.965 10.965 0 0 1 3.112-3.48.603.603 0 0 1 .818.105.538.538 0 0 1-.11.779 9.859 9.859 0 0 0-2.64 2.86c.074.12.158.251.253.391zm10.996-.786c-.335-.494-.731-.99-1.184-1.45-1.267-1.286-2.713-2.046-4.315-2.046-.368 0-.734.04-1.091.119a.585.585 0 0 1-.701-.414.555.555 0 0 1 .435-.668c.446-.1.902-.149 1.358-.148 1.972 0 3.698.907 5.165 2.398.504.512.942 1.059 1.313 1.606.225.33.378.591.46.748a.532.532 0 0 1-.007.51 10.821 10.821 0 0 1-1.328 1.868.604.604 0 0 1-.822.067.538.538 0 0 1-.07-.782c.39-.442.738-.916 1.04-1.416a9.37 9.37 0 0 0-.253-.392zm-4.426 2.29a2.225 2.225 0 0 1-3.38-1.306 2.221 2.221 0 0 1 .3-1.775L1.17 1.996a.583.583 0 1 1 .825-.825l12.833 12.833a.583.583 0 1 1-.825.825L9.072 9.896zm-2.26-2.26a1.11 1.11 0 0 0 1.44 1.44l-1.44-1.44z"
/>
)
export default ({ size, ...p }: { size: number }) => (
<svg viewBox="0 0 16 16" height={size} width={size} {...p}>
{path}
</svg>
)

16
src/icons/Truck.js

@ -0,0 +1,16 @@
// @flow
import React from 'react'
const path = (
<path
fill="currentColor"
d="M14.8 1.5h-8c-.663 0-1.2.546-1.2 1.219v1.219H4.497a1.19 1.19 0 0 0-.848.356L1.15 6.831a1.228 1.228 0 0 0-.351.861v3.152H.5c-.166 0-.3.136-.3.304v.61c0 .168.134.305.3.305h1.1C1.6 13.409 2.675 14.5 4 14.5s2.4-1.091 2.4-2.438h3.2c0 1.347 1.075 2.438 2.4 2.438s2.4-1.091 2.4-2.438h.4c.663 0 1.2-.545 1.2-1.218V2.719A1.21 1.21 0 0 0 14.8 1.5zM4 13.281a1.21 1.21 0 0 1-1.2-1.219A1.21 1.21 0 0 1 4 10.845c.662 0 1.2.546 1.2 1.219A1.21 1.21 0 0 1 4 13.28zm1.6-3.035A2.37 2.37 0 0 0 4 9.625c-.835 0-1.57.433-2 1.09V7.692l2.497-2.536H5.6v5.09zM12 13.28a1.21 1.21 0 0 1-1.2-1.219 1.21 1.21 0 0 1 1.2-1.218c.662 0 1.2.546 1.2 1.219A1.21 1.21 0 0 1 12 13.28zm2.8-2.437h-.721A2.393 2.393 0 0 0 12 9.625c-.888 0-1.664.49-2.079 1.219H6.8V2.719h8v8.125zM2.8 8l2-2.031V8h-2z"
/>
)
export default ({ size, ...p }: { size: number }) => (
<svg viewBox="0 0 16 16" height={size} width={size} {...p}>
{path}
</svg>
)

9
src/reducers/onboarding.js

@ -57,6 +57,15 @@ const state: OnboardingState = {
showBreadcrumb: false, showBreadcrumb: false,
}, },
}, },
{
name: 'noDevice',
external: true,
options: {
showFooter: false,
showBackground: true,
showBreadcrumb: false,
},
},
{ {
name: 'selectDevice', name: 'selectDevice',
label: 'Select Device', label: 'Select Device',

16
static/i18n/en/onboarding.yml

@ -15,6 +15,14 @@ init:
noDevice: noDevice:
title: Do not have a Ledger device yet? title: Do not have a Ledger device yet?
desc: Please replace it with the final wording once it’s done. desc: Please replace it with the final wording once it’s done.
noDevice:
title: Do not have a Ledger device yet?
buyNew:
title: Buy a Ledger device
trackOrder:
title: Track your order
learnMore:
title: Learn about Ledger Live
selectDevice: selectDevice:
title: To get started, select your device title: To get started, select your device
ledgerNanoCard: ledgerNanoCard:
@ -44,14 +52,14 @@ writeSeed:
title: Save your recovery phrase title: Save your recovery phrase
desc: Your recovery phrase is formed by 24 words. They will be displayed only once. desc: Your recovery phrase is formed by 24 words. They will be displayed only once.
step1: Press the right button to select the length of your recovery phrase. Press both buttons to confirm. step1: Press the right button to select the length of your recovery phrase. Press both buttons to confirm.
step2: Select the first letters of Word \#1 by pressing the right or left button. Press both buttons to confirm each letter. step2: 'Select the first letters of Word #1 by pressing the right or left button. Press both buttons to confirm each letter.'
step3: Select Word \#1 from the suggested words. Press both buttons to continue. step3: 'Select Word #1 from the suggested words. Press both buttons to continue.'
step4: Repeat the process until the last word. step4: Repeat the process until the last word.
nano: nano:
title: Save your recovery phrase title: Save your recovery phrase
desc: Your recovery phrase is formed by 24 words. They will be displayed only once. desc: Your recovery phrase is formed by 24 words. They will be displayed only once.
step1: Copy the first word (Word \#1) in position 1 on the blank Recovery sheet. step1: 'Copy the first word (Word #1) in position 1 on the blank Recovery sheet.'
step2: Press the right button to display Word \#2 and repeat the process until all 24 words are copied on the Recovery sheet. step2: 'Press the right button to display Word #2 and repeat the process until all 24 words are copied on the Recovery sheet.'
step3: Confirm your recovery phrase press both buttons to validate each word displayed on the screen. step3: Confirm your recovery phrase press both buttons to validate each word displayed on the screen.
blue: blue:
title: Save your recovery phrase title: Save your recovery phrase

Loading…
Cancel
Save