Browse Source

Merge branch 'develop' into feature/getDeviceInfo

master
amougel 7 years ago
parent
commit
4957ab6a6d
  1. 3
      .npmrc
  2. 1
      .nvmrc
  3. 4
      electron-builder.yml
  4. 3
      package.json
  5. 12
      scripts/compile.sh
  6. 3
      scripts/dist-dir.sh
  7. 2
      scripts/start.sh
  8. 2
      src/api/Ethereum.js
  9. 2
      src/api/Fees.js
  10. 2
      src/api/network.js
  11. 2
      src/commands/libcoreGetFees.js
  12. 2
      src/commands/libcoreHardReset.js
  13. 2
      src/components/AccountPage/index.js
  14. 2
      src/components/AppError.js
  15. 6
      src/components/BalanceSummary/BalanceInfos.js
  16. 4
      src/components/EnsureDeviceApp.js
  17. 23
      src/components/ExportLogsBtn.js
  18. 2
      src/components/ManagerPage/AppsList.js
  19. 2
      src/components/Onboarding/helperComponents.js
  20. 9
      src/components/Onboarding/steps/GenuineCheck.js
  21. 9
      src/components/Onboarding/steps/SelectDevice.js
  22. 4
      src/components/Onboarding/steps/SelectPIN/SelectPINblue.js
  23. 4
      src/components/Onboarding/steps/SelectPIN/SelectPINnano.js
  24. 4
      src/components/Onboarding/steps/SelectPIN/SelectPINrestoreBlue.js
  25. 4
      src/components/Onboarding/steps/SelectPIN/SelectPINrestoreNano.js
  26. 4
      src/components/Onboarding/steps/Start.js
  27. 4
      src/components/Onboarding/steps/WriteSeed/WriteSeedBlue.js
  28. 4
      src/components/Onboarding/steps/WriteSeed/WriteSeedNano.js
  29. 4
      src/components/Onboarding/steps/WriteSeed/WriteSeedRestore.js
  30. 41
      src/components/RenderError.js
  31. 2
      src/components/RequestAmount/index.js
  32. 8
      src/components/SelectAccount/index.js
  33. 2
      src/components/SettingsPage/sections/Currencies.js
  34. 2
      src/components/base/Box/Box.js
  35. 2
      src/components/base/SideBar/SideBarListItem.js
  36. 81
      src/components/base/Stepper/index.js
  37. 50
      src/components/base/Stepper/stories.js
  38. 208
      src/components/modals/AddAccounts/index.js
  39. 15
      src/components/modals/AddAccounts/steps/01-step-choose-currency.js
  40. 6
      src/components/modals/AddAccounts/steps/02-step-connect-device.js
  41. 81
      src/components/modals/AddAccounts/steps/03-step-import.js
  42. 9
      src/components/modals/OperationDetails.js
  43. 2
      src/components/modals/Send/index.js
  44. 5
      src/config/constants.js
  45. 2
      src/helpers/apps/installApp.js
  46. 2
      src/helpers/apps/uninstallApp.js
  47. 14
      src/helpers/createCustomErrorClass.js
  48. 2
      src/helpers/devices/getNextMCU.js
  49. 79
      src/helpers/errors.js
  50. 2
      src/helpers/getAddressForCurrency/btc.js
  51. 7
      src/helpers/ipc.js
  52. 2
      src/helpers/libcore.js
  53. 4
      src/helpers/promise.js
  54. 4
      src/helpers/socket.js
  55. 5
      src/icons/Home.js
  56. 0
      src/icons/SensitiveOperationShield.js
  57. 79
      src/icons/illustrations/GetStartedLogo.js
  58. 32
      src/icons/illustrations/LedgerBlue.js
  59. 47
      src/icons/illustrations/LedgerBlueError.js
  60. 47
      src/icons/illustrations/LedgerBlueSelectPIN.js
  61. 44
      src/icons/illustrations/LedgerNano.js
  62. 78
      src/icons/illustrations/LedgerNanoError.js
  63. 60
      src/icons/illustrations/LedgerNanoSelectPIN.js
  64. 63
      src/icons/illustrations/SetPassword.js
  65. 40
      src/icons/illustrations/WriteSeed.js
  66. 7
      src/internals/index.js
  67. 16
      src/logger.js
  68. 41
      src/main/app.js
  69. 2
      src/main/bridge.js
  70. 32
      src/main/terminator.js
  71. 2
      src/stories/icons.stories.js
  72. 8
      static/i18n/en/app.yml
  73. 8
      static/i18n/fr/app.yml
  74. 9
      static/i18n/fr/errors.yml
  75. 19
      static/images/blue-error-onb.svg
  76. 13
      static/images/get-started-onb.svg
  77. 7
      static/images/ledger-blue-onb.svg
  78. 10
      static/images/ledger-nano-onb.svg
  79. 34
      static/images/nano-error-onb.svg
  80. 19
      static/images/select-pin-blue-onb.svg
  81. 25
      static/images/select-pin-nano-onb.svg
  82. 9
      static/images/write-seed-onb.svg
  83. 23
      webpack/renderer.config.js
  84. 17
      yarn.lock

3
.npmrc

@ -0,0 +1,3 @@
npm_config_target=1.8.7
npm_config_disturl=https://atom.io/download/electron
npm_config_runtime=electron

1
.nvmrc

@ -0,0 +1 @@
lts/*

4
electron-builder.yml

@ -28,6 +28,10 @@ linux:
win: win:
artifactName: ${name}-${version}-${os}-${arch}.${ext} artifactName: ${name}-${version}-${os}-${arch}.${ext}
certificateSubjectName: Ledger SAS
certificateSha1: 7dd9acb2ef0402883c65901ebbafd06e5293d391
signingHashAlgorithms:
- sha256
target: target:
- target: nsis - target: nsis
arch: arch:

3
package.json

@ -8,7 +8,7 @@
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"compile": "bash ./scripts/compile.sh", "compile": "bash ./scripts/compile.sh",
"dist:dir": "yarn dist --dir -c.compression=store -c.mac.identity=null", "dist:dir": "bash ./scripts/dist-dir.sh",
"dist": "bash ./scripts/dist.sh", "dist": "bash ./scripts/dist.sh",
"test": "jest", "test": "jest",
"flow": "flow", "flow": "flow",
@ -156,6 +156,7 @@
"prettier": "^1.13.5", "prettier": "^1.13.5",
"react-hot-loader": "^4.3.2", "react-hot-loader": "^4.3.2",
"react-test-renderer": "^16.4.1", "react-test-renderer": "^16.4.1",
"uglifyjs-webpack-plugin": "^1.2.6",
"webpack": "^4.6.0", "webpack": "^4.6.0",
"webpack-bundle-analyzer": "^2.11.1", "webpack-bundle-analyzer": "^2.11.1",
"webpack-cli": "^2.0.14", "webpack-cli": "^2.0.14",

12
scripts/compile.sh

@ -2,11 +2,11 @@
set -e set -e
export GIT_REVISION=`git rev-parse HEAD` GIT_REVISION=`git rev-parse HEAD`
export SENTRY_URL=https://db8f5b9b021048d4a401f045371701cb@sentry.io/274561 SENTRY_URL=https://db8f5b9b021048d4a401f045371701cb@sentry.io/274561
NODE_ENV=production
rm -rf ./node_modules/.cache dist rm -rf ./node_modules/.cache dist
yarn JOBS=max yarn
rm -rf dist && yarn run webpack-cli --mode production --config webpack/internals.config.js
NODE_ENV=production yarn run webpack-cli --mode production --config webpack/internals.config.js && yarn run electron-webpack
NODE_ENV=production yarn run electron-webpack

3
scripts/dist-dir.sh

@ -0,0 +1,3 @@
#/bin/bash
yarn compile && DEBUG=electron-builder electron-builder --dir -c.compression=store -c.mac.identity=null

2
scripts/start.sh

@ -1,5 +1,5 @@
#/bin/bash #/bin/bash
concurrently --raw \ concurrently --raw --kill-others \
"cross-env NODE_ENV=development webpack-cli --mode development --watch --config webpack/internals.config.js" \ "cross-env NODE_ENV=development webpack-cli --mode development --watch --config webpack/internals.config.js" \
"cross-env NODE_ENV=development electron-webpack dev" "cross-env NODE_ENV=development electron-webpack dev"

2
src/api/Ethereum.js

@ -1,6 +1,6 @@
// @flow // @flow
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import createCustomErrorClass from 'helpers/createCustomErrorClass' import { createCustomErrorClass } from 'helpers/errors'
import network from './network' import network from './network'
import { blockchainBaseURL } from './Ledger' import { blockchainBaseURL } from './Ledger'

2
src/api/Fees.js

@ -2,7 +2,7 @@
import invariant from 'invariant' import invariant from 'invariant'
import LRU from 'lru-cache' import LRU from 'lru-cache'
import type { Currency } from '@ledgerhq/live-common/lib/types' import type { Currency } from '@ledgerhq/live-common/lib/types'
import createCustomErrorClass from 'helpers/createCustomErrorClass' import { createCustomErrorClass } from 'helpers/errors'
import { blockchainBaseURL } from './Ledger' import { blockchainBaseURL } from './Ledger'
import network from './network' import network from './network'

2
src/api/network.js

@ -3,7 +3,7 @@ import axios from 'axios'
import { GET_CALLS_RETRY, GET_CALLS_TIMEOUT } from 'config/constants' import { GET_CALLS_RETRY, GET_CALLS_TIMEOUT } from 'config/constants'
import { retry } from 'helpers/promise' import { retry } from 'helpers/promise'
import logger from 'logger' import logger from 'logger'
import createCustomErrorClass from 'helpers/createCustomErrorClass' import { createCustomErrorClass } from 'helpers/errors'
export const LedgerAPIErrorWithMessage = createCustomErrorClass('LedgerAPIErrorWithMessage') export const LedgerAPIErrorWithMessage = createCustomErrorClass('LedgerAPIErrorWithMessage')
export const LedgerAPIError = createCustomErrorClass('LedgerAPIError') export const LedgerAPIError = createCustomErrorClass('LedgerAPIError')

2
src/commands/libcoreGetFees.js

@ -5,7 +5,7 @@ import withLibcore from 'helpers/withLibcore'
import { createCommand, Command } from 'helpers/ipc' import { createCommand, Command } from 'helpers/ipc'
import * as accountIdHelper from 'helpers/accountId' import * as accountIdHelper from 'helpers/accountId'
import { isValidAddress } from 'helpers/libcore' import { isValidAddress } from 'helpers/libcore'
import createCustomErrorClass from 'helpers/createCustomErrorClass' import { createCustomErrorClass } from 'helpers/errors'
const InvalidAddress = createCustomErrorClass('InvalidAddress') const InvalidAddress = createCustomErrorClass('InvalidAddress')

2
src/commands/libcoreHardReset.js

@ -3,7 +3,7 @@
import { createCommand } from 'helpers/ipc' import { createCommand } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise' import { fromPromise } from 'rxjs/observable/fromPromise'
import withLibcore from 'helpers/withLibcore' import withLibcore from 'helpers/withLibcore'
import createCustomErrorClass from 'helpers/createCustomErrorClass' import { createCustomErrorClass } from 'helpers/errors'
const HardResetFail = createCustomErrorClass('HardResetFail') const HardResetFail = createCustomErrorClass('HardResetFail')

2
src/components/AccountPage/index.js

@ -150,12 +150,14 @@ class AccountPage extends PureComponent<Props> {
<Box flow={4} mb={2}> <Box flow={4} mb={2}>
<Box horizontal> <Box horizontal>
<BalanceTotal <BalanceTotal
showCryptoEvenIfNotAvailable
isAvailable={isAvailable} isAvailable={isAvailable}
totalBalance={account.balance} totalBalance={account.balance}
unit={account.unit} unit={account.unit}
> >
<FormattedVal <FormattedVal
animateTicker animateTicker
disableRounding
alwaysShowSign={false} alwaysShowSign={false}
color="warmGrey" color="warmGrey"
unit={counterValue.units[0]} unit={counterValue.units[0]}

2
src/components/AppError.js

@ -13,7 +13,7 @@ import RenderError from './RenderError'
const App = ({ language, error }: { error: Error, language: string }) => ( const App = ({ language, error }: { error: Error, language: string }) => (
<I18nextProvider i18n={i18n} initialLanguage={language}> <I18nextProvider i18n={i18n} initialLanguage={language}>
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<RenderError disableExport error={error}> <RenderError withoutAppData error={error}>
<TriggerAppReady /> <TriggerAppReady />
</RenderError> </RenderError>
</ThemeProvider> </ThemeProvider>

6
src/components/BalanceSummary/BalanceInfos.js

@ -32,6 +32,7 @@ type BalanceTotalProps = {
unit: Unit, unit: Unit,
isAvailable: boolean, isAvailable: boolean,
totalBalance: number, totalBalance: number,
showCryptoEvenIfNotAvailable?: boolean,
} }
type Props = { type Props = {
@ -90,10 +91,10 @@ export function BalanceSinceDiff(props: Props) {
} }
export function BalanceTotal(props: BalanceTotalProps) { export function BalanceTotal(props: BalanceTotalProps) {
const { unit, totalBalance, isAvailable, children } = props const { unit, totalBalance, isAvailable, showCryptoEvenIfNotAvailable, children } = props
return ( return (
<Box grow {...props}> <Box grow {...props}>
{!isAvailable ? ( {!isAvailable && !showCryptoEvenIfNotAvailable ? (
<PlaceholderLine width={150} /> <PlaceholderLine width={150} />
) : ( ) : (
<FormattedVal <FormattedVal
@ -101,6 +102,7 @@ export function BalanceTotal(props: BalanceTotalProps) {
color="dark" color="dark"
unit={unit} unit={unit}
fontSize={8} fontSize={8}
disableRounding
showCode showCode
val={totalBalance} val={totalBalance}
/> />

4
src/components/EnsureDeviceApp/index.js → src/components/EnsureDeviceApp.js

@ -13,7 +13,7 @@ import type { State as StoreState } from 'reducers/index'
import getAddress from 'commands/getAddress' import getAddress from 'commands/getAddress'
import { standardDerivation } from 'helpers/derivations' import { standardDerivation } from 'helpers/derivations'
import isDashboardOpen from 'commands/isDashboardOpen' import isDashboardOpen from 'commands/isDashboardOpen'
import createCustomErrorClass from 'helpers/createCustomErrorClass' import { createCustomErrorClass } from 'helpers/errors'
import { CHECK_APP_INTERVAL_WHEN_VALID, CHECK_APP_INTERVAL_WHEN_INVALID } from 'config/constants' import { CHECK_APP_INTERVAL_WHEN_VALID, CHECK_APP_INTERVAL_WHEN_INVALID } from 'config/constants'
@ -36,7 +36,7 @@ type OwnProps = {
deviceSelected: ?Device, deviceSelected: ?Device,
deviceStatus: DeviceStatus, deviceStatus: DeviceStatus,
error: ?Error, error: ?Error,
}) => React$Element<*>, }) => React$Node,
} }
type Props = OwnProps & { type Props = OwnProps & {

23
src/components/ExportLogsBtn.js

@ -19,15 +19,24 @@ const mapStateToProps = createStructuredSelector({
class ExportLogsBtn extends Component<{ class ExportLogsBtn extends Component<{
t: *, t: *,
settings: *, settings: ?*,
accounts: *, accounts: ?*,
hookToShortcut?: boolean, hookToShortcut?: boolean,
}> { }> {
handleExportLogs = () => { handleExportLogs = () => {
const { accounts, settings } = this.props const { accounts, settings } = this.props
const logs = logger.exportLogs() const logs = logger.exportLogs()
const resourceUsage = webFrame.getResourceUsage() const resourceUsage = webFrame.getResourceUsage()
const report = { resourceUsage, logs, accounts, settings, date: new Date() } const report = {
resourceUsage,
logs,
accounts,
settings,
date: new Date(),
release: __APP_VERSION__,
git_commit: __GIT_REVISION__,
environment: __DEV__ ? 'development' : 'production',
}
console.log(report) // eslint-disable-line no-console console.log(report) // eslint-disable-line no-console
const reportJSON = JSON.stringify(report) const reportJSON = JSON.stringify(report)
const path = remote.dialog.showSaveDialog({ const path = remote.dialog.showSaveDialog({
@ -69,4 +78,10 @@ class ExportLogsBtn extends Component<{
} }
} }
export default translate()(connect(mapStateToProps)(ExportLogsBtn)) const WithAppData = connect(mapStateToProps)(ExportLogsBtn)
const WithoutAppData = ExportLogsBtn
const ExportLogsBtnDispatcher = ({ withAppData, ...rest }: *) =>
withAppData ? <WithAppData {...rest} /> : <WithoutAppData {...rest} />
export default translate()(ExportLogsBtnDispatcher)

2
src/components/ManagerPage/AppsList.js

@ -117,7 +117,7 @@ class AppsList extends PureComponent<Props, State> {
} = this.props } = this.props
const data = { app, devicePath, targetId } const data = { app, devicePath, targetId }
await uninstallApp.send(data).toPromise() await uninstallApp.send(data).toPromise()
this.setState({ status: 'success', app: '' }) this.setState({ status: 'success' })
} catch (err) { } catch (err) {
this.setState({ status: 'error', error: err, app: '', mode: 'home' }) this.setState({ status: 'error', error: err, app: '', mode: 'home' })
} }

2
src/components/Onboarding/helperComponents.js

@ -5,7 +5,7 @@ import { radii } from 'styles/theme'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import GrowScroll from 'components/base/GrowScroll' import GrowScroll from 'components/base/GrowScroll'
import IconSensitiveOperationShield from 'icons/illustrations/SensitiveOperationShield' import IconSensitiveOperationShield from 'icons/SensitiveOperationShield'
// GENERAL // GENERAL
export const Title = styled(Box).attrs({ export const Title = styled(Box).attrs({

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

@ -5,6 +5,7 @@ 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 { colors } from 'styles/theme' import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import type { T } from 'types/common' import type { T } from 'types/common'
@ -17,8 +18,6 @@ import RadioGroup from 'components/base/RadioGroup'
import GenuineCheckModal from 'components/GenuineCheckModal' import GenuineCheckModal from 'components/GenuineCheckModal'
import TranslatedError from 'components/TranslatedError' import TranslatedError from 'components/TranslatedError'
import IconLedgerNanoError from 'icons/illustrations/LedgerNanoError'
import IconLedgerBlueError from 'icons/illustrations/LedgerBlueError'
import IconCheck from 'icons/Check' import IconCheck from 'icons/Check'
import IconCross from 'icons/Cross' import IconCross from 'icons/Cross'
@ -325,8 +324,8 @@ export function GenuineCheckFail({
<Fragment> <Fragment>
<Title>{t('onboarding:genuineCheck.errorPage.ledgerNano.title')}</Title> <Title>{t('onboarding:genuineCheck.errorPage.ledgerNano.title')}</Title>
<Description>{t('onboarding:genuineCheck.errorPage.ledgerNano.desc')}</Description> <Description>{t('onboarding:genuineCheck.errorPage.ledgerNano.desc')}</Description>
<Box style={{ width: 550 }} mt={5} ml={100}> <Box mt={5} mr={7}>
<IconLedgerNanoError /> <img alt="" src={i('nano-error-onb.svg')} />
</Box> </Box>
</Fragment> </Fragment>
) : ( ) : (
@ -336,7 +335,7 @@ export function GenuineCheckFail({
{t('onboarding:genuineCheck.errorPage.ledgerBlue.desc')} {t('onboarding:genuineCheck.errorPage.ledgerBlue.desc')}
</Description> </Description>
<Box alignItems="center"> <Box alignItems="center">
<IconLedgerBlueError /> <img alt="" src={i('blue-error-onb.svg')} />
</Box> </Box>
</Fragment> </Fragment>
)} )}

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

@ -3,14 +3,15 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { i } from 'helpers/staticPath'
import { rgba } from 'styles/helpers' import { rgba } from 'styles/helpers'
import { isLedgerNano } from 'reducers/onboarding' import { isLedgerNano } from 'reducers/onboarding'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import IconCheckCirle from 'icons/Check' import IconCheckCirle from 'icons/Check'
import IconLedgerNano from 'icons/illustrations/LedgerNano'
import IconLedgerBlue from 'icons/illustrations/LedgerBlue'
import { Title, Inner, FixedTopContainer, StepContainerInner } from '../helperComponents' import { Title, Inner, FixedTopContainer, StepContainerInner } from '../helperComponents'
import OnboardingFooter from '../OnboardingFooter' import OnboardingFooter from '../OnboardingFooter'
@ -47,7 +48,7 @@ class SelectDevice extends PureComponent<StepProps, {}> {
> >
{onboarding.isLedgerNano && <DeviceSelected />} {onboarding.isLedgerNano && <DeviceSelected />}
<DeviceIcon> <DeviceIcon>
<IconLedgerNano /> <img alt="" src={i('ledger-nano-onb.svg')} />
</DeviceIcon> </DeviceIcon>
<BlockTitle>{t('onboarding:selectDevice.ledgerNanoCard.title')}</BlockTitle> <BlockTitle>{t('onboarding:selectDevice.ledgerNanoCard.title')}</BlockTitle>
</DeviceContainer> </DeviceContainer>
@ -57,7 +58,7 @@ class SelectDevice extends PureComponent<StepProps, {}> {
> >
{!onboarding.isLedgerNano && onboarding.isLedgerNano !== null && <DeviceSelected />} {!onboarding.isLedgerNano && onboarding.isLedgerNano !== null && <DeviceSelected />}
<DeviceIcon> <DeviceIcon>
<IconLedgerBlue /> <img alt="" src={i('ledger-blue-onb.svg')} />
</DeviceIcon> </DeviceIcon>
<BlockTitle>{t('onboarding:selectDevice.ledgerBlueCard.title')}</BlockTitle> <BlockTitle>{t('onboarding:selectDevice.ledgerBlueCard.title')}</BlockTitle>
</DeviceContainer> </DeviceContainer>

4
src/components/Onboarding/steps/SelectPIN/SelectPINblue.js

@ -2,11 +2,11 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { colors } from 'styles/theme' import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import type { T } from 'types/common' import type { T } from 'types/common'
import IconLedgerBlueSelectPIN from 'icons/illustrations/LedgerBlueSelectPIN'
import IconChevronRight from 'icons/ChevronRight' import IconChevronRight from 'icons/ChevronRight'
@ -60,7 +60,7 @@ class SelectPIN extends PureComponent<Props, *> {
<Box align="center"> <Box align="center">
<Inner style={{ width: 550 }}> <Inner style={{ width: 550 }}>
<Box style={{ width: 180, justifyContent: 'center', alignItems: 'center' }}> <Box style={{ width: 180, justifyContent: 'center', alignItems: 'center' }}>
<IconLedgerBlueSelectPIN /> <img alt="" src={i('select-pin-blue-onb.svg')} />
</Box> </Box>
<Box> <Box>
<Box shrink grow flow={4}> <Box shrink grow flow={4}>

4
src/components/Onboarding/steps/SelectPIN/SelectPINnano.js

@ -2,11 +2,11 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { colors } from 'styles/theme' import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import type { T } from 'types/common' import type { T } from 'types/common'
import IconLedgerNanoSelectPIN from 'icons/illustrations/LedgerNanoSelectPIN'
import IconChevronRight from 'icons/ChevronRight' import IconChevronRight from 'icons/ChevronRight'
@ -63,7 +63,7 @@ class SelectPINnano extends PureComponent<Props, *> {
return ( return (
<Box align="center" mt={3}> <Box align="center" mt={3}>
<Inner style={{ width: 700 }}> <Inner style={{ width: 700 }}>
<IconLedgerNanoSelectPIN /> <img alt="" src={i('select-pin-nano-onb.svg')} />
<Box shrink grow flow={4} style={{ marginLeft: 40 }}> <Box shrink grow flow={4} style={{ marginLeft: 40 }}>
{stepsLedgerNano.map(step => <OptionRow key={step.key} step={step} />)} {stepsLedgerNano.map(step => <OptionRow key={step.key} step={step} />)}
</Box> </Box>

4
src/components/Onboarding/steps/SelectPIN/SelectPINrestoreBlue.js

@ -2,11 +2,11 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { colors } from 'styles/theme' import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import type { T } from 'types/common' import type { T } from 'types/common'
import IconLedgerBlueSelectPIN from 'icons/illustrations/LedgerBlueSelectPIN'
import IconChevronRight from 'icons/ChevronRight' import IconChevronRight from 'icons/ChevronRight'
@ -60,7 +60,7 @@ class SelectPINrestoreBlue extends PureComponent<Props, *> {
<Box align="center"> <Box align="center">
<Inner style={{ width: 550 }}> <Inner style={{ width: 550 }}>
<Box style={{ width: 180, justifyContent: 'center', alignItems: 'center' }}> <Box style={{ width: 180, justifyContent: 'center', alignItems: 'center' }}>
<IconLedgerBlueSelectPIN /> <img alt="" src={i('select-pin-blue-onb.svg')} />
</Box> </Box>
<Box> <Box>
<Box shrink grow flow={4}> <Box shrink grow flow={4}>

4
src/components/Onboarding/steps/SelectPIN/SelectPINrestoreNano.js

@ -2,11 +2,11 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { colors } from 'styles/theme' import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import type { T } from 'types/common' import type { T } from 'types/common'
import IconLedgerNanoSelectPIN from 'icons/illustrations/LedgerNanoSelectPIN'
import IconChevronRight from 'icons/ChevronRight' import IconChevronRight from 'icons/ChevronRight'
@ -63,7 +63,7 @@ class SelectPINrestoreNano extends PureComponent<Props, *> {
return ( return (
<Box align="center" mt={3}> <Box align="center" mt={3}>
<Inner style={{ width: 700 }}> <Inner style={{ width: 700 }}>
<IconLedgerNanoSelectPIN /> <img alt="" src={i('select-pin-nano-onb.svg')} />
<Box shrink grow flow={4} style={{ marginLeft: 40 }}> <Box shrink grow flow={4} style={{ marginLeft: 40 }}>
{stepsLedgerNano.map(step => <OptionRow key={step.key} step={step} />)} {stepsLedgerNano.map(step => <OptionRow key={step.key} step={step} />)}
</Box> </Box>

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

@ -1,11 +1,11 @@
// @flow // @flow
import React from 'react' import React from 'react'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import IconGetStarted from 'icons/illustrations/GetStartedLogo'
import type { StepProps } from '..' import type { StepProps } from '..'
import { Title } from '../helperComponents' import { Title } from '../helperComponents'
@ -14,7 +14,7 @@ export default (props: StepProps) => {
return ( return (
<Box sticky justifyContent="center"> <Box sticky justifyContent="center">
<Box alignItems="center"> <Box alignItems="center">
<IconGetStarted /> <img alt="" src={i('get-started-onb.svg')} />
<Box my={4}> <Box my={4}>
<Title>{t('onboarding:start.title')}</Title> <Title>{t('onboarding:start.title')}</Title>
</Box> </Box>

4
src/components/Onboarding/steps/WriteSeed/WriteSeedBlue.js

@ -2,11 +2,11 @@
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { colors } from 'styles/theme' import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import type { T } from 'types/common' import type { T } from 'types/common'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import IconWriteSeed from 'icons/illustrations/WriteSeed'
import IconChevronRight from 'icons/ChevronRight' import IconChevronRight from 'icons/ChevronRight'
import { import {
@ -75,7 +75,7 @@ class WriteSeedBlue extends PureComponent<Props, *> {
<Box align="center"> <Box align="center">
<Inner style={{ width: 760 }}> <Inner style={{ width: 760 }}>
<Box style={{ width: 260, justifyContent: 'center', alignItems: 'center' }}> <Box style={{ width: 260, justifyContent: 'center', alignItems: 'center' }}>
<IconWriteSeed /> <img alt="" src={i('write-seed-onb.svg')} />
</Box> </Box>
<Box shrink flow={2} m={0}> <Box shrink flow={2} m={0}>
{steps.map(step => <OptionRow key={step.key} step={step} />)} {steps.map(step => <OptionRow key={step.key} step={step} />)}

4
src/components/Onboarding/steps/WriteSeed/WriteSeedNano.js

@ -2,11 +2,11 @@
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { colors } from 'styles/theme' import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import type { T } from 'types/common' import type { T } from 'types/common'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import IconWriteSeed from 'icons/illustrations/WriteSeed'
import IconChevronRight from 'icons/ChevronRight' import IconChevronRight from 'icons/ChevronRight'
import { import {
@ -75,7 +75,7 @@ class WriteSeedNano extends PureComponent<Props, *> {
<Box align="center" mt={3}> <Box align="center" mt={3}>
<Inner style={{ width: 700 }}> <Inner style={{ width: 700 }}>
<Box style={{ width: 300 }} justifyContent="center" alignItems="center"> <Box style={{ width: 300 }} justifyContent="center" alignItems="center">
<IconWriteSeed /> <img alt="" src={i('write-seed-onb.svg')} />
</Box> </Box>
<Box shrink grow flow={4}> <Box shrink grow flow={4}>

4
src/components/Onboarding/steps/WriteSeed/WriteSeedRestore.js

@ -2,11 +2,11 @@
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { colors } from 'styles/theme' import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import type { T } from 'types/common' import type { T } from 'types/common'
import IconWriteSeed from 'icons/illustrations/WriteSeed'
import type { OnboardingState } from 'reducers/onboarding' import type { OnboardingState } from 'reducers/onboarding'
import IconChevronRight from 'icons/ChevronRight' import IconChevronRight from 'icons/ChevronRight'
@ -100,7 +100,7 @@ class WriteSeedRestore extends PureComponent<Props, *> {
<Box align="center"> <Box align="center">
<Inner style={{ width: 760 }}> <Inner style={{ width: 760 }}>
<Box style={{ width: 260, justifyContent: 'center', alignItems: 'center' }}> <Box style={{ width: 260, justifyContent: 'center', alignItems: 'center' }}>
<IconWriteSeed /> <img alt="" src={i('write-seed-onb.svg')} />
</Box> </Box>
{onboarding.isLedgerNano ? ( {onboarding.isLedgerNano ? (
<Box shrink flow={2} m={0}> <Box shrink flow={2} m={0}>

41
src/components/RenderError.js

@ -1,6 +1,7 @@
// @flow // @flow
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { shell, remote } from 'electron' import { shell, remote } from 'electron'
import qs from 'querystring' import qs from 'querystring'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
@ -14,14 +15,14 @@ import ExportLogsBtn from 'components/ExportLogsBtn'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Space from 'components/base/Space' import Space from 'components/base/Space'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import ConfirmModal from 'components/base/Modal/ConfirmModal'
import IconTriangleWarning from 'icons/TriangleWarning' import IconTriangleWarning from 'icons/TriangleWarning'
import ConfirmModal from './base/Modal/ConfirmModal'
import { IconWrapperCircle } from './SettingsPage/sections/Profile' import { IconWrapperCircle } from './SettingsPage/sections/Profile'
type Props = { type Props = {
error: Error, error: Error,
t: T, t: T,
disableExport?: boolean, withoutAppData?: boolean,
children?: *, children?: *,
} }
@ -75,7 +76,7 @@ ${error.stack}
} }
render() { render() {
const { error, t, disableExport, children } = this.props const { error, t, withoutAppData, children } = this.props
const { isHardResetting, isHardResetModalOpened } = this.state const { isHardResetting, isHardResetModalOpened } = this.state
return ( return (
<Box align="center" grow> <Box align="center" grow>
@ -100,7 +101,7 @@ ${error.stack}
<Button primary onClick={this.handleRestart}> <Button primary onClick={this.handleRestart}>
{t('app:crash.restart')} {t('app:crash.restart')}
</Button> </Button>
{!disableExport ? <ExportLogsBtn /> : null} <ExportLogsBtn withoutAppData={withoutAppData} />
<Button primary onClick={this.handleCreateIssue}> <Button primary onClick={this.handleCreateIssue}>
{t('app:crash.createTicket')} {t('app:crash.createTicket')}
</Button> </Button>
@ -120,10 +121,8 @@ ${error.stack}
renderIcon={this.hardResetIconRender} renderIcon={this.hardResetIconRender}
/> />
<Box my={6}> <Box my={6}>
<ErrContainer> <ErrContainer>{`${String(error)}
<strong>{String(error)}</strong> ${error.stack || 'no stacktrace'}`}</ErrContainer>
<div>{error.stack || 'no stacktrace'}</div>
</ErrContainer>
</Box> </Box>
<pre <pre
style={{ style={{
@ -142,21 +141,15 @@ ${error.stack}
} }
} }
const ErrContainer = ({ children }: { children: any }) => ( const ErrContainer = styled.pre`
<pre margin: auto;
style={{ max-width: 80vw;
margin: 'auto', overflow: auto;
maxWidth: '80vw', font-size: 10px;
overflow: 'auto', font-family: monospace;
fontSize: 10, cursor: text;
fontFamily: 'monospace', user-select: text;
cursor: 'text', opacity: 0.3;
userSelect: 'text', `
opacity: 0.3,
}}
>
{children}
</pre>
)
export default translate()(RenderError) export default translate()(RenderError)

2
src/components/RequestAmount/index.js

@ -21,7 +21,7 @@ import InputCurrency from 'components/base/InputCurrency'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import type { State } from 'reducers' import type { State } from 'reducers'
import createCustomErrorClass from 'helpers/createCustomErrorClass' import { createCustomErrorClass } from 'helpers/errors'
const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance') const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance')

8
src/components/SelectAccount/index.js

@ -43,7 +43,13 @@ const renderOption = a => {
</Text> </Text>
</Box> </Box>
<Box> <Box>
<FormattedVal color="grey" val={account.balance} unit={account.unit} showCode /> <FormattedVal
color="grey"
val={account.balance}
unit={account.unit}
showCode
disableRounding
/>
</Box> </Box>
</Box> </Box>
) )

2
src/components/SettingsPage/sections/Currencies.js

@ -112,7 +112,7 @@ class TabCurrencies extends PureComponent<Props, State> {
<Body> <Body>
{currency !== intermediaryCurrency ? ( {currency !== intermediaryCurrency ? (
<Row <Row
title={t('app:settings.display.exchange', { title={t('app:settings.currencies.exchange', {
ticker: `${currency.ticker}${intermediaryCurrency.ticker}`, ticker: `${currency.ticker}${intermediaryCurrency.ticker}`,
})} })}
desc={t('app:settings.currencies.exchangeDesc')} desc={t('app:settings.currencies.exchangeDesc')}

2
src/components/base/Box/Box.js

@ -17,6 +17,7 @@ import {
import fontFamily from 'styles/styled/fontFamily' import fontFamily from 'styles/styled/fontFamily'
export const styledTextAlign = style({ prop: 'textAlign', cssProperty: 'textAlign' }) export const styledTextAlign = style({ prop: 'textAlign', cssProperty: 'textAlign' })
export const styledOverflow = style({ prop: 'overflow', cssProperty: 'overflow' })
export const styledCursor = style({ prop: 'cursor', cssProperty: 'cursor' }) export const styledCursor = style({ prop: 'cursor', cssProperty: 'cursor' })
export const styledTextTransform = style({ prop: 'textTransform', cssProperty: 'textTransform' }) export const styledTextTransform = style({ prop: 'textTransform', cssProperty: 'textTransform' })
@ -34,6 +35,7 @@ export default styled.div`
${styledTextAlign}; ${styledTextAlign};
${styledCursor}; ${styledCursor};
${styledTextTransform}; ${styledTextTransform};
${styledOverflow};
display: flex; display: flex;
flex-shrink: ${p => (p.noShrink === true ? '0' : p.shrink === true ? '1' : '')}; flex-shrink: ${p => (p.noShrink === true ? '0' : p.shrink === true ? '1' : '')};

2
src/components/base/SideBar/SideBarListItem.js

@ -6,7 +6,7 @@ import styled from 'styled-components'
import Box, { Tabbable } from 'components/base/Box' import Box, { Tabbable } from 'components/base/Box'
export type Props = { export type Props = {
label: string | (Props => React$Element<any>), label: string | (Props => React$Node),
desc?: Props => any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯ desc?: Props => any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯
icon?: any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯ icon?: any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯
disabled?: boolean, disabled?: boolean,

81
src/components/base/Stepper/index.js

@ -0,0 +1,81 @@
// @flow
import React, { PureComponent } from 'react'
import invariant from 'invariant'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
import { ModalContent, ModalTitle, ModalFooter, ModalBody } from 'components/base/Modal'
import Breadcrumb from 'components/Breadcrumb'
type Props = {
t: T,
title: string,
initialStepId: string,
onClose: void => void,
steps: Step[],
children: any,
}
export type Step = {
id: string,
label: string,
component: StepProps => React$Node,
footer: StepProps => React$Node,
preventClose?: boolean,
onBack?: StepProps => void,
}
type State = {
stepId: string,
}
export type StepProps = {
t: T,
transitionTo: string => void,
}
class Stepper extends PureComponent<Props, State> {
state = {
stepId: this.props.initialStepId,
}
transitionTo = stepId => this.setState({ stepId })
render() {
const { t, steps, title, onClose, children, ...props } = this.props
const { stepId } = this.state
const stepIndex = steps.findIndex(s => s.id === stepId)
const step = steps[stepIndex]
invariant(step, `Stepper: step ${stepId} doesn't exists`)
const { component: StepComponent, footer: StepFooter, onBack, preventClose } = step
const stepProps: StepProps = {
t,
transitionTo: this.transitionTo,
...props,
}
return (
<ModalBody onClose={preventClose ? undefined : onClose}>
<ModalTitle onBack={onBack ? () => onBack(stepProps) : undefined}>{title}</ModalTitle>
<ModalContent>
<Breadcrumb mb={6} currentStep={stepIndex} items={steps} />
<StepComponent {...stepProps} />
{children}
</ModalContent>
{StepFooter && (
<ModalFooter horizontal align="center" justify="flex-end">
<StepFooter {...stepProps} />
</ModalFooter>
)}
</ModalBody>
)
}
}
export default translate()(Stepper)

50
src/components/base/Stepper/stories.js

@ -0,0 +1,50 @@
// @flow
import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import Stepper from 'components/base/Stepper'
import Button from 'components/base/Button'
import type { StepProps, Step } from 'components/base/Stepper'
const stories = storiesOf('Components/base', module)
const steps: Step[] = [
{
id: 'first',
label: 'first step',
component: () => <div>first step</div>,
footer: ({ transitionTo }: StepProps) => (
<div>
<Button primary onClick={() => transitionTo('second')}>
Click to go next
</Button>
</div>
),
},
{
id: 'second',
label: 'second step',
preventClose: true,
onBack: ({ transitionTo }: StepProps) => transitionTo('first'),
component: () => <div>second step (you cant close on this one)</div>,
footer: ({ transitionTo }: StepProps) => (
<div>
<Button primary onClick={() => transitionTo('first')}>
Click to go prev
</Button>
</div>
),
},
]
stories.add('Stepper', () => (
<Stepper
onClose={action('onClose')}
title="Stepper component"
steps={steps}
initialStepId="first"
/>
))

208
src/components/modals/AddAccounts/index.js

@ -1,6 +1,5 @@
// @flow // @flow
import invariant from 'invariant'
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { compose } from 'redux' import { compose } from 'redux'
import { connect } from 'react-redux' import { connect } from 'react-redux'
@ -13,59 +12,66 @@ import type { Currency, Account } from '@ledgerhq/live-common/lib/types'
import { MODAL_ADD_ACCOUNTS } from 'config/constants' import { MODAL_ADD_ACCOUNTS } from 'config/constants'
import type { T, Device } from 'types/common' import type { T, Device } from 'types/common'
import type { StepProps as DefaultStepProps, Step } from 'components/base/Stepper'
import { idleCallback } from 'helpers/promise'
import { getCurrentDevice } from 'reducers/devices' import { getCurrentDevice } from 'reducers/devices'
import { accountsSelector } from 'reducers/accounts' import { accountsSelector } from 'reducers/accounts'
import { addAccount } from 'actions/accounts' import { addAccount } from 'actions/accounts'
import { closeModal } from 'reducers/modals' import { closeModal } from 'reducers/modals'
import Modal, { ModalContent, ModalTitle, ModalFooter, ModalBody } from 'components/base/Modal' import Modal from 'components/base/Modal'
import Box from 'components/base/Box' import Stepper from 'components/base/Stepper'
import Breadcrumb from 'components/Breadcrumb'
import StepChooseCurrency, { StepChooseCurrencyFooter } from './steps/01-step-choose-currency' import StepChooseCurrency, { StepChooseCurrencyFooter } from './steps/01-step-choose-currency'
import StepConnectDevice, { StepConnectDeviceFooter } from './steps/02-step-connect-device' import StepConnectDevice, { StepConnectDeviceFooter } from './steps/02-step-connect-device'
import StepImport, { StepImportFooter } from './steps/03-step-import' import StepImport, { StepImportFooter } from './steps/03-step-import'
import StepFinish from './steps/04-step-finish' import StepFinish from './steps/04-step-finish'
const createSteps = ({ t }: { t: T }) => [ const createSteps = ({ t }: { t: T }) => {
{ const onBack = ({ transitionTo, resetScanState }: StepProps) => {
id: 'chooseCurrency', resetScanState()
label: t('app:addAccounts.breadcrumb.informations'), transitionTo('chooseCurrency')
component: StepChooseCurrency, }
footer: StepChooseCurrencyFooter, return [
onBack: null, {
hideFooter: false, id: 'chooseCurrency',
}, label: t('app:addAccounts.breadcrumb.informations'),
{ component: StepChooseCurrency,
id: 'connectDevice', footer: StepChooseCurrencyFooter,
label: t('app:addAccounts.breadcrumb.connectDevice'), onBack: null,
component: StepConnectDevice, hideFooter: false,
footer: StepConnectDeviceFooter, },
onBack: ({ transitionTo }: StepProps) => transitionTo('chooseCurrency'), {
hideFooter: false, id: 'connectDevice',
}, label: t('app:addAccounts.breadcrumb.connectDevice'),
{ component: StepConnectDevice,
id: 'import', footer: StepConnectDeviceFooter,
label: t('app:addAccounts.breadcrumb.import'), onBack,
component: StepImport, hideFooter: false,
footer: StepImportFooter, },
onBack: ({ transitionTo }: StepProps) => transitionTo('chooseCurrency'), {
hideFooter: false, id: 'import',
}, label: t('app:addAccounts.breadcrumb.import'),
{ component: StepImport,
id: 'finish', footer: StepImportFooter,
label: t('app:addAccounts.breadcrumb.finish'), onBack,
component: StepFinish, hideFooter: false,
footer: null, },
onBack: null, {
hideFooter: true, id: 'finish',
}, label: t('app:addAccounts.breadcrumb.finish'),
] component: StepFinish,
footer: null,
onBack: null,
hideFooter: true,
},
]
}
type Props = { type Props = {
t: T, t: T,
currentDevice: ?Device, device: ?Device,
existingAccounts: Account[], existingAccounts: Account[],
closeModal: string => void, closeModal: string => void,
addAccount: Account => void, addAccount: Account => void,
@ -75,37 +81,39 @@ type StepId = 'chooseCurrency' | 'connectDevice' | 'import' | 'finish'
type ScanStatus = 'idle' | 'scanning' | 'error' | 'finished' type ScanStatus = 'idle' | 'scanning' | 'error' | 'finished'
type State = { type State = {
stepId: StepId, // TODO: I'm sure there will be always StepId and ScanStatus given,
// but I struggle making flow understand it. So I put string as fallback
stepId: StepId | string,
scanStatus: ScanStatus | string,
isAppOpened: boolean, isAppOpened: boolean,
currency: ?Currency, currency: ?Currency,
// scan process
scannedAccounts: Account[], scannedAccounts: Account[],
checkedAccountsIds: string[], checkedAccountsIds: string[],
scanStatus: ScanStatus,
err: ?Error, err: ?Error,
} }
export type StepProps = { export type StepProps = DefaultStepProps & {
t: T, t: T,
currency: ?Currency, currency: ?Currency,
currentDevice: ?Device, device: ?Device,
isAppOpened: boolean, isAppOpened: boolean,
transitionTo: StepId => void,
setState: any => void,
onClickAdd: void => Promise<void>,
onCloseModal: void => void,
// scan process
scannedAccounts: Account[], scannedAccounts: Account[],
existingAccounts: Account[], existingAccounts: Account[],
checkedAccountsIds: string[], checkedAccountsIds: string[],
scanStatus: ScanStatus, scanStatus: ScanStatus,
err: ?Error, err: ?Error,
onClickAdd: void => Promise<void>,
onCloseModal: void => void,
resetScanState: void => void,
setCurrency: (?Currency) => void,
setAppOpened: boolean => void,
setScanStatus: (ScanStatus, ?Error) => string,
setScannedAccounts: ({ scannedAccounts?: Account[], checkedAccountsIds?: string[] }) => void,
} }
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentDevice: getCurrentDevice, device: getCurrentDevice,
existingAccounts: accountsSelector, existingAccounts: accountsSelector,
}) })
@ -126,18 +134,7 @@ const INITIAL_STATE = {
class AddAccounts extends PureComponent<Props, State> { class AddAccounts extends PureComponent<Props, State> {
state = INITIAL_STATE state = INITIAL_STATE
STEPS = createSteps({ STEPS = createSteps({ t: this.props.t })
t: this.props.t,
})
transitionTo = stepId => {
const { currency } = this.state
let nextState = { stepId }
if (stepId === 'chooseCurrency') {
nextState = { ...INITIAL_STATE, currency }
}
this.setState(nextState)
}
handleClickAdd = async () => { handleClickAdd = async () => {
const { addAccount } = this.props const { addAccount } = this.props
@ -151,16 +148,43 @@ class AddAccounts extends PureComponent<Props, State> {
await idleCallback() await idleCallback()
addAccount(accountsToAdd[i]) addAccount(accountsToAdd[i])
} }
this.transitionTo('finish')
} }
handleCloseModal = () => { handleCloseModal = () => this.props.closeModal(MODAL_ADD_ACCOUNTS)
const { closeModal } = this.props handleStepChange = (step: Step) => this.setState({ stepId: step.id })
closeModal(MODAL_ADD_ACCOUNTS)
handleSetCurrency = (currency: ?Currency) => this.setState({ currency })
handleSetScanStatus = (scanStatus: string, err: ?Error = null) => {
this.setState({ scanStatus, err })
}
handleSetScannedAccounts = ({
checkedAccountsIds,
scannedAccounts,
}: {
checkedAccountsIds: string[],
scannedAccounts: Account[],
}) => {
this.setState({
...(checkedAccountsIds ? { checkedAccountsIds } : {}),
...(scannedAccounts ? { scannedAccounts } : {}),
})
}
handleResetScanState = () => {
this.setState({
scanStatus: 'idle',
err: null,
scannedAccounts: [],
checkedAccountsIds: [],
})
} }
handleSetAppOpened = (isAppOpened: boolean) => this.setState({ isAppOpened })
render() { render() {
const { t, currentDevice, existingAccounts } = this.props const { t, device, existingAccounts } = this.props
const { const {
stepId, stepId,
currency, currency,
@ -171,17 +195,9 @@ class AddAccounts extends PureComponent<Props, State> {
err, err,
} = this.state } = this.state
const stepIndex = this.STEPS.findIndex(s => s.id === stepId) const addtionnalProps = {
const step = this.STEPS[stepIndex]
invariant(step, `AddAccountsModal: step ${stepId} doesn't exists`)
const { component: StepComponent, footer: StepFooter, hideFooter, onBack } = step
const stepProps: StepProps = {
t,
currency, currency,
currentDevice, device,
existingAccounts, existingAccounts,
scannedAccounts, scannedAccounts,
checkedAccountsIds, checkedAccountsIds,
@ -190,8 +206,11 @@ class AddAccounts extends PureComponent<Props, State> {
isAppOpened, isAppOpened,
onClickAdd: this.handleClickAdd, onClickAdd: this.handleClickAdd,
onCloseModal: this.handleCloseModal, onCloseModal: this.handleCloseModal,
transitionTo: this.transitionTo, setScanStatus: this.handleSetScanStatus,
setState: (...args) => this.setState(...args), setCurrency: this.handleSetCurrency,
setScannedAccounts: this.handleSetScannedAccounts,
resetScanState: this.handleResetScanState,
setAppOpened: this.handleSetAppOpened,
} }
return ( return (
@ -200,21 +219,16 @@ class AddAccounts extends PureComponent<Props, State> {
refocusWhenChange={stepId} refocusWhenChange={stepId}
onHide={() => this.setState({ ...INITIAL_STATE })} onHide={() => this.setState({ ...INITIAL_STATE })}
render={({ onClose }) => ( render={({ onClose }) => (
<ModalBody onClose={onClose}> <Stepper
title={t('app:addAccounts.title')}
initialStepId="chooseCurrency"
onStepChange={this.handleStepChange}
onClose={onClose}
steps={this.STEPS}
{...addtionnalProps}
>
<SyncSkipUnderPriority priority={100} /> <SyncSkipUnderPriority priority={100} />
<ModalTitle onBack={onBack ? () => onBack(stepProps) : void 0}> </Stepper>
{t('app:addAccounts.title')}
</ModalTitle>
<ModalContent>
<Breadcrumb mb={6} currentStep={stepIndex} items={this.STEPS} />
<StepComponent {...stepProps} />
</ModalContent>
{!hideFooter && (
<ModalFooter horizontal align="center" justify="flex-end" style={{ height: 80 }}>
{StepFooter ? <StepFooter {...stepProps} /> : <Box />}
</ModalFooter>
)}
</ModalBody>
)} )}
/> />
) )
@ -228,7 +242,3 @@ export default compose(
), ),
translate(), translate(),
)(AddAccounts) )(AddAccounts)
function idleCallback() {
return new Promise(resolve => window.requestIdleCallback(resolve))
}

15
src/components/modals/AddAccounts/steps/01-step-choose-currency.js

@ -1,7 +1,6 @@
// @flow // @flow
import React, { Fragment } from 'react' import React, { Fragment } from 'react'
import isArray from 'lodash/isArray'
import SelectCurrency from 'components/SelectCurrency' import SelectCurrency from 'components/SelectCurrency'
import Button from 'components/base/Button' import Button from 'components/base/Button'
@ -9,18 +8,8 @@ import CurrencyBadge from 'components/base/CurrencyBadge'
import type { StepProps } from '../index' import type { StepProps } from '../index'
function StepChooseCurrency({ currency, setState }: StepProps) { function StepChooseCurrency({ currency, setCurrency }: StepProps) {
return ( return <SelectCurrency autoFocus onChange={setCurrency} value={currency} />
<SelectCurrency
autoFocus
onChange={currency => {
setState({
currency: isArray(currency) && currency.length === 0 ? null : currency,
})
}}
value={currency}
/>
)
} }
export function StepChooseCurrencyFooter({ transitionTo, currency, t }: StepProps) { export function StepChooseCurrencyFooter({ transitionTo, currency, t }: StepProps) {

6
src/components/modals/AddAccounts/steps/02-step-connect-device.js

@ -11,7 +11,7 @@ import { CurrencyCircleIcon } from 'components/base/CurrencyBadge'
import type { StepProps } from '../index' import type { StepProps } from '../index'
function StepConnectDevice({ t, currency, currentDevice, setState }: StepProps) { function StepConnectDevice({ t, currency, device, setAppOpened }: StepProps) {
invariant(currency, 'No currency given') invariant(currency, 'No currency given')
return ( return (
@ -30,11 +30,11 @@ function StepConnectDevice({ t, currency, currentDevice, setState }: StepProps)
</Box> </Box>
<ConnectDevice <ConnectDevice
t={t} t={t}
deviceSelected={currentDevice} deviceSelected={device}
currency={currency} currency={currency}
onStatusChange={(deviceStatus, appStatus) => { onStatusChange={(deviceStatus, appStatus) => {
if (appStatus === 'success') { if (appStatus === 'success') {
setState({ isAppOpened: true }) setAppOpened(true)
} }
}} }}
/> />

81
src/components/modals/AddAccounts/steps/03-step-import.js

@ -18,19 +18,23 @@ import type { StepProps } from '../index'
class StepImport extends PureComponent<StepProps> { class StepImport extends PureComponent<StepProps> {
componentDidMount() { componentDidMount() {
this.props.setState({ scanStatus: 'scanning' }) this.props.setScanStatus('scanning')
} }
componentDidUpdate(prevProps: StepProps) { componentDidUpdate(prevProps: StepProps) {
// handle case when we click on stop sync const didStartScan = prevProps.scanStatus !== 'scanning' && this.props.scanStatus === 'scanning'
if (prevProps.scanStatus !== 'finished' && this.props.scanStatus === 'finished') { const didFinishScan =
this.unsub() prevProps.scanStatus !== 'finished' && this.props.scanStatus === 'finished'
}
// handle case when we click on retry sync // handle case when we click on retry sync
if (prevProps.scanStatus !== 'scanning' && this.props.scanStatus === 'scanning') { if (didStartScan) {
this.startScanAccountsDevice() this.startScanAccountsDevice()
} }
// handle case when we click on stop sync
if (didFinishScan) {
this.unsub()
}
} }
componentWillUnmount() { componentWillUnmount() {
@ -63,15 +67,15 @@ class StepImport extends PureComponent<StepProps> {
startScanAccountsDevice() { startScanAccountsDevice() {
this.unsub() this.unsub()
const { currency, currentDevice, setState } = this.props const { currency, device, setScanStatus, setScannedAccounts } = this.props
try { try {
invariant(currency, 'No currency to scan') invariant(currency, 'No currency to scan')
invariant(currentDevice, 'No device') invariant(device, 'No device')
const bridge = getBridgeForCurrency(currency) const bridge = getBridgeForCurrency(currency)
// TODO: use the real device // TODO: use the real device
const devicePath = currentDevice.path const devicePath = device.path
this.scanSubscription = bridge.scanAccountsOnDevice(currency, devicePath).subscribe({ this.scanSubscription = bridge.scanAccountsOnDevice(currency, devicePath).subscribe({
next: account => { next: account => {
@ -80,7 +84,7 @@ class StepImport extends PureComponent<StepProps> {
const hasAlreadyBeenImported = !!existingAccounts.find(a => account.id === a.id) const hasAlreadyBeenImported = !!existingAccounts.find(a => account.id === a.id)
const isNewAccount = account.operations.length === 0 const isNewAccount = account.operations.length === 0
if (!hasAlreadyBeenScanned) { if (!hasAlreadyBeenScanned) {
setState({ setScannedAccounts({
scannedAccounts: [...scannedAccounts, this.translateName(account)], scannedAccounts: [...scannedAccounts, this.translateName(account)],
checkedAccountsIds: checkedAccountsIds:
!hasAlreadyBeenImported && !isNewAccount !hasAlreadyBeenImported && !isNewAccount
@ -89,43 +93,33 @@ class StepImport extends PureComponent<StepProps> {
}) })
} }
}, },
complete: () => setState({ scanStatus: 'finished' }), complete: () => setScanStatus('finished'),
error: err => setState({ scanStatus: 'error', err }), error: err => setScanStatus('error', err),
}) })
} catch (err) { } catch (err) {
setState({ scanStatus: 'error', err }) setScanStatus('error', err)
} }
} }
handleRetry = () => { handleRetry = () => {
this.unsub() this.unsub()
this.handleResetState() this.props.resetScanState()
this.startScanAccountsDevice() this.startScanAccountsDevice()
} }
handleResetState = () => {
const { setState } = this.props
setState({
scanStatus: 'idle',
err: null,
scannedAccounts: [],
checkedAccountsIds: [],
})
}
handleToggleAccount = (account: Account) => { handleToggleAccount = (account: Account) => {
const { checkedAccountsIds, setState } = this.props const { checkedAccountsIds, setScannedAccounts } = this.props
const isChecked = checkedAccountsIds.find(id => id === account.id) !== undefined const isChecked = checkedAccountsIds.find(id => id === account.id) !== undefined
if (isChecked) { if (isChecked) {
setState({ checkedAccountsIds: checkedAccountsIds.filter(id => id !== account.id) }) setScannedAccounts({ checkedAccountsIds: checkedAccountsIds.filter(id => id !== account.id) })
} else { } else {
setState({ checkedAccountsIds: [...checkedAccountsIds, account.id] }) setScannedAccounts({ checkedAccountsIds: [...checkedAccountsIds, account.id] })
} }
} }
handleUpdateAccount = (updatedAccount: Account) => { handleUpdateAccount = (updatedAccount: Account) => {
const { scannedAccounts, setState } = this.props const { scannedAccounts, setScannedAccounts } = this.props
setState({ setScannedAccounts({
scannedAccounts: scannedAccounts.map(account => { scannedAccounts: scannedAccounts.map(account => {
if (account.id !== updatedAccount.id) { if (account.id !== updatedAccount.id) {
return account return account
@ -136,19 +130,26 @@ class StepImport extends PureComponent<StepProps> {
} }
handleSelectAll = () => { handleSelectAll = () => {
const { scannedAccounts, setState } = this.props const { scannedAccounts, setScannedAccounts } = this.props
setState({ setScannedAccounts({
checkedAccountsIds: scannedAccounts.filter(a => a.operations.length > 0).map(a => a.id), checkedAccountsIds: scannedAccounts.filter(a => a.operations.length > 0).map(a => a.id),
}) })
} }
handleUnselectAll = () => this.props.setState({ checkedAccountsIds: [] }) handleUnselectAll = () => this.props.setScannedAccounts({ checkedAccountsIds: [] })
renderError() { renderError() {
const { err, t } = this.props const { err, t } = this.props
invariant(err, 'Trying to render inexisting error') invariant(err, 'Trying to render inexisting error')
return ( return (
<Box style={{ height: 200 }} align="center" justify="center" color="alertRed"> <Box
style={{ height: 200 }}
px={5}
textAlign="center"
align="center"
justify="center"
color="alertRed"
>
<IconExclamationCircleThin size={43} /> <IconExclamationCircleThin size={43} />
<Box mt={4}>{t('app:addAccounts.somethingWentWrong')}</Box> <Box mt={4}>{t('app:addAccounts.somethingWentWrong')}</Box>
<Box mt={4}> <Box mt={4}>
@ -236,7 +237,8 @@ class StepImport extends PureComponent<StepProps> {
export default StepImport export default StepImport
export const StepImportFooter = ({ export const StepImportFooter = ({
setState, transitionTo,
setScanStatus,
scanStatus, scanStatus,
onClickAdd, onClickAdd,
onCloseModal, onCloseModal,
@ -274,18 +276,23 @@ export const StepImportFooter = ({
: t('app:common.close') : t('app:common.close')
const willClose = !willCreateAccount && !willAddAccounts const willClose = !willCreateAccount && !willAddAccounts
const onClick = willClose ? onCloseModal : onClickAdd const onClick = willClose
? onCloseModal
: async () => {
await onClickAdd()
transitionTo('finish')
}
return ( return (
<Fragment> <Fragment>
{currency && <CurrencyBadge mr="auto" currency={currency} />} {currency && <CurrencyBadge mr="auto" currency={currency} />}
{scanStatus === 'error' && ( {scanStatus === 'error' && (
<Button mr={2} onClick={() => setState({ scanStatus: 'scanning', err: null })}> <Button mr={2} onClick={() => setScanStatus('scanning')}>
{t('app:common.retry')} {t('app:common.retry')}
</Button> </Button>
)} )}
{scanStatus === 'scanning' && ( {scanStatus === 'scanning' && (
<Button mr={2} onClick={() => setState({ scanStatus: 'finished' })}> <Button mr={2} onClick={() => setScanStatus('finished')}>
{t('app:common.stop')} {t('app:common.stop')}
</Button> </Button>
)} )}

9
src/components/modals/OperationDetails.js

@ -126,7 +126,14 @@ const OperationDetails = connect(mapStateToProps)((props: Props) => {
/> />
<Box my={4} alignItems="center"> <Box my={4} alignItems="center">
<Box> <Box>
<FormattedVal unit={unit} alwaysShowSign showCode val={amount} fontSize={7} /> <FormattedVal
unit={unit}
alwaysShowSign
showCode
val={amount}
fontSize={7}
disableRounding
/>
</Box> </Box>
<Box mt={1}> <Box mt={1}>
<CounterValue <CounterValue

2
src/components/modals/Send/index.js

@ -15,7 +15,7 @@ import { getBridgeForCurrency } from 'bridge'
import { accountsSelector } from 'reducers/accounts' import { accountsSelector } from 'reducers/accounts'
import { updateAccountWithUpdater } from 'actions/accounts' import { updateAccountWithUpdater } from 'actions/accounts'
import createCustomErrorClass from 'helpers/createCustomErrorClass' import { createCustomErrorClass } from 'helpers/errors'
import { MODAL_SEND } from 'config/constants' import { MODAL_SEND } from 'config/constants'
import Modal, { ModalBody, ModalContent, ModalTitle } from 'components/base/Modal' import Modal, { ModalBody, ModalContent, ModalTitle } from 'components/base/Modal'

5
src/config/constants.js

@ -12,6 +12,11 @@ const boolFromEnv = (key: string): boolean => {
const stringFromEnv = (key: string, def: string): string => process.env[key] || def const stringFromEnv = (key: string, def: string): string => process.env[key] || def
// Size
export const MIN_HEIGHT = intFromEnv('LEDGER_MIN_HEIGHT', 768)
export const MIN_WIDTH = intFromEnv('LEDGER_MIN_WIDTH', 1024)
// time and delays... // time and delays...
export const GET_CALLS_TIMEOUT = intFromEnv('GET_CALLS_TIMEOUT', 30 * 1000) export const GET_CALLS_TIMEOUT = intFromEnv('GET_CALLS_TIMEOUT', 30 * 1000)

2
src/helpers/apps/installApp.js

@ -7,7 +7,7 @@ import { createDeviceSocket } from 'helpers/socket'
import type { LedgerScriptParams } from 'helpers/common' import type { LedgerScriptParams } from 'helpers/common'
import createCustomErrorClass from '../createCustomErrorClass' import { createCustomErrorClass } from '../errors'
const ManagerUnexpectedError = createCustomErrorClass('ManagerUnexpected') const ManagerUnexpectedError = createCustomErrorClass('ManagerUnexpected')
const ManagerNotEnoughSpaceError = createCustomErrorClass('ManagerNotEnoughSpace') const ManagerNotEnoughSpaceError = createCustomErrorClass('ManagerNotEnoughSpace')

2
src/helpers/apps/uninstallApp.js

@ -6,7 +6,7 @@ import { BASE_SOCKET_URL_SECURE } from 'config/constants'
import { createDeviceSocket } from 'helpers/socket' import { createDeviceSocket } from 'helpers/socket'
import type { LedgerScriptParams } from 'helpers/common' import type { LedgerScriptParams } from 'helpers/common'
import createCustomErrorClass from '../createCustomErrorClass' import { createCustomErrorClass } from '../errors'
const ManagerUnexpectedError = createCustomErrorClass('ManagerUnexpectedError') const ManagerUnexpectedError = createCustomErrorClass('ManagerUnexpectedError')
const ManagerDeviceLockedError = createCustomErrorClass('ManagerDeviceLocked') const ManagerDeviceLockedError = createCustomErrorClass('ManagerDeviceLocked')

14
src/helpers/createCustomErrorClass.js

@ -1,14 +0,0 @@
// @flow
export default (name: string): Class<any> => {
const C = function CustomError(message?: string, fields?: Object) {
this.name = name
this.message = message || name
this.stack = new Error().stack
Object.assign(this, fields)
}
// $FlowFixMe
C.prototype = new Error()
// $FlowFixMe we can't easily type a subset of Error for now...
return C
}

2
src/helpers/devices/getNextMCU.js

@ -2,7 +2,7 @@
import network from 'api/network' import network from 'api/network'
import { GET_NEXT_MCU } from 'helpers/urls' import { GET_NEXT_MCU } from 'helpers/urls'
import createCustomErrorClass from 'helpers/createCustomErrorClass' import { createCustomErrorClass } from 'helpers/errors'
const LatestMCUInstalledError = createCustomErrorClass('LatestMCUInstalledError') const LatestMCUInstalledError = createCustomErrorClass('LatestMCUInstalledError')

79
src/helpers/errors.js

@ -1,3 +1,80 @@
// @flow // @flow
/* eslint-disable no-continue */
export const formatError = (e: Error) => e.message const errorClasses = {}
export const createCustomErrorClass = (name: string): Class<any> => {
const C = function CustomError(message?: string, fields?: Object) {
this.name = name
this.message = message || name
this.stack = new Error().stack
Object.assign(this, fields)
}
// $FlowFixMe
C.prototype = new Error()
errorClasses[name] = C
// $FlowFixMe we can't easily type a subset of Error for now...
return C
}
// inspired from https://github.com/programble/errio/blob/master/index.js
export const deserializeError = (object: mixed): Error => {
if (typeof object === 'object' && object) {
const constructor = (typeof object.name === 'string' && errorClasses[object.name]) || Error
const error = Object.create(constructor.prototype)
for (const prop in object) {
if (object.hasOwnProperty(prop)) {
error[prop] = object[prop]
}
}
if (!error.stack && Error.captureStackTrace) {
Error.captureStackTrace(error, deserializeError)
}
return error
}
return new Error(String(object))
}
// inspired from https://github.com/sindresorhus/serialize-error/blob/master/index.js
export const serializeError = (value: mixed) => {
if (!value) return value
if (typeof value === 'object') {
return destroyCircular(value, [])
}
if (typeof value === 'function') {
return `[Function: ${value.name || 'anonymous'}]`
}
return value
}
// https://www.npmjs.com/package/destroy-circular
function destroyCircular(from: Object, seen) {
const to = {}
seen.push(from)
for (const key of Object.keys(from)) {
const value = from[key]
if (typeof value === 'function') {
continue
}
if (!value || typeof value !== 'object') {
to[key] = value
continue
}
if (seen.indexOf(from[key]) === -1) {
to[key] = destroyCircular(from[key], seen.slice(0))
continue
}
to[key] = '[Circular]'
}
if (typeof from.name === 'string') {
to.name = from.name
}
if (typeof from.message === 'string') {
to.message = from.message
}
if (typeof from.stack === 'string') {
to.stack = from.stack
}
return to
}

2
src/helpers/getAddressForCurrency/btc.js

@ -4,7 +4,7 @@ import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import Btc from '@ledgerhq/hw-app-btc' import Btc from '@ledgerhq/hw-app-btc'
import type Transport from '@ledgerhq/hw-transport' import type Transport from '@ledgerhq/hw-transport'
import getBitcoinLikeInfo from '../devices/getBitcoinLikeInfo' import getBitcoinLikeInfo from '../devices/getBitcoinLikeInfo'
import createCustomErrorClass from '../createCustomErrorClass' import { createCustomErrorClass } from '../errors'
const BtcUnmatchedApp = createCustomErrorClass('BtcUnmatchedApp') const BtcUnmatchedApp = createCustomErrorClass('BtcUnmatchedApp')

7
src/helpers/ipc.js

@ -2,6 +2,7 @@
import logger from 'logger' import logger from 'logger'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import uuidv4 from 'uuid/v4' import uuidv4 from 'uuid/v4'
import { deserializeError } from './errors'
export function createCommand<In, A>(id: string, impl: In => Observable<A>): Command<In, A> { export function createCommand<In, A>(id: string, impl: In => Observable<A>): Command<In, A> {
return new Command(id, impl) return new Command(id, impl)
@ -60,10 +61,12 @@ function ipcRendererSendCommand<In, A>(id: string, data: In): Observable<A> {
ipcRenderer.removeListener('command-event', handleCommandEvent) ipcRenderer.removeListener('command-event', handleCommandEvent)
break break
case 'cmd.ERROR': case 'cmd.ERROR': {
o.error(msg.data) const error = deserializeError(msg.data)
o.error(error)
ipcRenderer.removeListener('command-event', handleCommandEvent) ipcRenderer.removeListener('command-event', handleCommandEvent)
break break
}
default: default:
} }

2
src/helpers/libcore.js

@ -11,7 +11,7 @@ import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgerc
import { isSegwitAccount } from 'helpers/bip32' import { isSegwitAccount } from 'helpers/bip32'
import * as accountIdHelper from 'helpers/accountId' import * as accountIdHelper from 'helpers/accountId'
import createCustomErrorClass from './createCustomErrorClass' import { createCustomErrorClass } from './errors'
import { getAccountPlaceholderName, getNewAccountPlaceholderName } from './accountName' import { getAccountPlaceholderName, getNewAccountPlaceholderName } from './accountName'

4
src/helpers/promise.js

@ -27,3 +27,7 @@ export function retry<A>(f: () => Promise<A>, options?: $Shape<typeof defaults>)
}) })
} }
} }
export function idleCallback() {
return new Promise(resolve => window.requestIdleCallback(resolve))
}

4
src/helpers/socket.js

@ -5,7 +5,7 @@ import logger from 'logger'
import Websocket from 'ws' import Websocket from 'ws'
import type Transport from '@ledgerhq/hw-transport' import type Transport from '@ledgerhq/hw-transport'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import createCustomErrorClass from './createCustomErrorClass' import { createCustomErrorClass } from './errors'
const WebsocketConnectionError = createCustomErrorClass('WebsocketConnectionError') const WebsocketConnectionError = createCustomErrorClass('WebsocketConnectionError')
const WebsocketConnectionFailed = createCustomErrorClass('WebsocketConnectionFailed') const WebsocketConnectionFailed = createCustomErrorClass('WebsocketConnectionFailed')
@ -74,7 +74,9 @@ export const createDeviceSocket = (transport: Transport<*>, url: string) =>
for (const apdu of data) { for (const apdu of data) {
const r: Buffer = await transport.exchange(Buffer.from(apdu, 'hex')) const r: Buffer = await transport.exchange(Buffer.from(apdu, 'hex'))
lastStatus = r.slice(r.length - 2) lastStatus = r.slice(r.length - 2)
if (lastStatus.toString('hex') !== '9000') break
} }
if (!lastStatus) { if (!lastStatus) {
throw new DeviceSocketNoBulkStatus() throw new DeviceSocketNoBulkStatus()
} }

5
src/icons/Home.js

@ -3,7 +3,10 @@
import React from 'react' import React from 'react'
const path = ( 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" /> <path
fill="currentColor"
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 }) => ( export default ({ size, ...p }: { size: number }) => (

0
src/icons/illustrations/SensitiveOperationShield.js → src/icons/SensitiveOperationShield.js

79
src/icons/illustrations/GetStartedLogo.js

@ -1,79 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg width="113" height="109">
<g fill="none" fillRule="evenodd">
<rect
width="1.44"
height="5.6"
y="16.6"
fill="#1D2028"
rx=".72"
transform="matrix(-1 0 0 1 1.44 0)"
/>
<rect
width="1.44"
height="5.6"
y="34.8"
fill="#1D2028"
rx=".72"
transform="matrix(-1 0 0 1 1.44 0)"
/>
<path
fill="#6490F1"
fillOpacity=".1"
stroke="#1D2028"
strokeWidth="2"
d="M16.592 12c.225 0 .408.183.408.408v95.184a.408.408 0 0 1-.408.408H2.128a.408.408 0 0 1-.408-.408V12.408c0-.225.183-.408.408-.408h14.464z"
/>
<rect
width="7.64"
height="27"
x="5.513"
y="18.522"
fill="#FFF"
stroke="#6490F1"
rx=".704"
transform="matrix(-1 0 0 1 18.665 0)"
/>
<path
fill="#FFF"
stroke="#1D2028"
strokeWidth="2"
d="M9.36 54A7.64 7.64 0 0 1 17 61.64v45.952a.408.408 0 0 1-.408.408H2.128a.408.408 0 0 1-.408-.408V61.64A7.64 7.64 0 0 1 9.36 54z"
/>
<ellipse
cx="9.36"
cy="61.4"
fill="#FFF"
stroke="#6490F1"
rx="3.82"
ry="3.7"
transform="matrix(-1 0 0 1 18.72 0)"
/>
<rect width="3.137" height="9.306" x="109.863" y="13.959" fill="#1D2028" rx="1.569" />
<rect
width="76.431"
height="106.571"
x="34"
y="1"
fill="#6490F1"
fillOpacity=".1"
stroke="#1D2027"
strokeWidth="2"
rx="5.44"
/>
<rect
width="52.333"
height="79.653"
x="46.043"
y="15.235"
fill="#FFF"
stroke="#6490F1"
rx="4.08"
/>
</g>
</svg>
)

32
src/icons/illustrations/LedgerBlue.js

@ -1,32 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg width="52" height="72">
<g fill="none" fillRule="evenodd">
<rect width="2.039" height="6.171" x="49.961" y="9.257" fill="#1D2028" rx="1.02" />
<rect
width="49.48"
height="70.5"
x=".75"
y=".75"
fill="#6490F1"
fillOpacity=".1"
stroke="#1D2027"
strokeWidth="1.5"
rx="3.2"
/>
<rect
width="34.167"
height="52.986"
x="8.403"
y="10.021"
fill="#FFF"
stroke="#6490F1"
strokeWidth=".5"
rx="2.4"
/>
</g>
</svg>
)

47
src/icons/illustrations/LedgerBlueError.js

@ -1,47 +0,0 @@
// @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/illustrations/LedgerBlueSelectPIN.js

@ -1,47 +0,0 @@
// @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>
)

44
src/icons/illustrations/LedgerNano.js

@ -1,44 +0,0 @@
// @flow
import React from 'react'
export default () => (
<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.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="5.74"
height="20.071"
x="3.39"
y="5.409"
fill="#FFF"
stroke="#6490F1"
strokeWidth=".5"
rx=".8"
/>
<path
fill="#FFF"
stroke="#1D2028"
strokeWidth="1.5"
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"
/>
</g>
</svg>
)

78
src/icons/illustrations/LedgerNanoError.js

@ -1,78 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg>
<defs>
<linearGradient id="a" x1="50%" x2="50%" y1="0%" y2="100%">
<stop offset="0%" />
<stop offset="100%" stopColor="#FFF" />
</linearGradient>
<rect id="b" width="41.711" height="238.384" rx="4" />
<path
id="c"
d="M5.773 5l2.541-2.541a.235.235 0 0 0 0-.332l-.441-.441a.235.235 0 0 0-.332 0L5 4.226l-2.541-2.54a.235.235 0 0 0-.332 0l-.441.441a.235.235 0 0 0 0 .332L4.226 5l-2.54 2.541a.235.235 0 0 0 0 .332l.441.441c.092.092.24.092.332 0L5 5.774l2.541 2.54c.092.092.24.092.332 0l.441-.441a.235.235 0 0 0 0-.332L5.774 5z"
/>
</defs>
<g fill="none" fillRule="evenodd">
<path
stroke="#1D2027"
strokeWidth="2"
d="M127.356 30a1 1 0 0 1-1 1H100.13a5 5 0 0 1-5-5v-8.486a5 5 0 0 1 5-5h26.225a1 1 0 0 1 1 1V30z"
/>
<path stroke="#142533" strokeWidth="2" d="M94.747 24.792H83.818v-6.436h10.93v6.436z" />
<path
stroke="#1D2027"
strokeWidth="2"
d="M127.423 27.287V16.032l6.977.082a1 1 0 0 1 .988 1v9.267a1 1 0 0 1-1.012.988l-6.953-.082z"
/>
<path
fill="url(#a)"
d="M6.836 53.925h1.616v82.65H6.836v-82.65zm5.657 0h1.616v82.65h-1.616v-82.65z"
transform="matrix(0 -1 -1 0 137 32)"
/>
<g transform="rotate(-90 85 -42)">
<rect width="4.492" height="17.12" x="38.336" y="15.505" fill="#142533" rx="2" />
<rect width="4.492" height="17.12" x="38.336" y="70.094" fill="#142533" rx="2" />
<use fill="#FFF" />
<rect
width="39.711"
height="236.384"
x="1"
y="1"
fill="#FCE0E4"
stroke="#142533"
strokeLinejoin="square"
strokeWidth="2"
rx="4"
/>
<rect
width="20.176"
height="61.019"
x="10.767"
y="21.173"
fill="#FFF"
stroke="#EA2E49"
rx="1.6"
/>
<path
fill="#FFF"
stroke="#142533"
strokeWidth="2"
d="M20.856 95.966C9.89 95.966 1 104.856 1 115.822v118.562a3 3 0 0 0 3 3h33.711a3 3 0 0 0 3-3V115.822c0-10.966-8.89-19.856-19.855-19.856z"
/>
<ellipse cx="21.016" cy="116.123" stroke="#EA2E49" rx="10.57" ry="10.644" />
<g transform="translate(16.066 26.884)">
<mask id="d" fill="#fff">
<use xlinkHref="#c" />
</mask>
<use fill="#000" fillRule="nonzero" xlinkHref="#c" />
<g fill="#EA2E49" mask="url(#d)">
<path d="M0 0h10v10H0z" />
</g>
</g>
</g>
</g>
</svg>
)

60
src/icons/illustrations/LedgerNanoSelectPIN.js

@ -1,60 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg width="260" height="129">
<defs>
<linearGradient id="a" x1="50%" x2="50%" y1="0%" y2="100%">
<stop offset="0%" />
<stop offset="100%" stopColor="#FFF" />
</linearGradient>
<path
id="b"
d="M91 0h34a4 4 0 0 1 4 4v108.144c0 11.519-9.337 20.856-20.856 20.856h-.288C96.337 133 87 123.663 87 112.144V4a4 4 0 0 1 4-4z"
/>
</defs>
<g fill="none" fillRule="evenodd">
<path
stroke="#1D2027"
strokeWidth="2"
d="M127.856 31.44a1 1 0 0 1-1 1H100.63a5 5 0 0 1-5-5v-8.486a5 5 0 0 1 5-5h26.225a1 1 0 0 1 1 1v16.485z"
/>
<path stroke="#142533" strokeWidth="2" d="M95.247 26.231H84.318v-6.435h10.93v6.435z" />
<path
stroke="#1D2027"
strokeWidth="2"
d="M127.923 28.726V17.471l6.977.083a1 1 0 0 1 .988 1v9.266a1 1 0 0 1-1.012.988l-6.953-.083z"
/>
<path
fill="url(#a)"
d="M6.836 53.925h1.616v82.65H6.836v-82.65zm5.657 0h1.616v82.65h-1.616v-82.65z"
transform="matrix(0 -1 -1 0 137.5 33.44)"
/>
<g transform="rotate(-90 128.59 1.975)">
<rect width="4.492" height="17.12" x="125.336" y="15.505" fill="#142533" rx="2" />
<rect width="4.492" height="17.12" x="125.336" y="70.094" fill="#142533" rx="2" />
<use fill="#FFF" xlinkHref="#b" />
<path
fill="#6490F1"
fillOpacity=".15"
stroke="#142533"
strokeLinejoin="square"
strokeWidth="2"
d="M91 1a3 3 0 0 0-3 3v108.144C88 123.11 96.89 132 107.856 132h.288C119.11 132 128 123.11 128 112.144V4a3 3 0 0 0-3-3H91z"
/>
<rect width="21" height="62" x="97.5" y="21.5" fill="#FFF" stroke="#6490F1" rx="1.6" />
<path
fill="#6490F1"
d="M105.5 35h5a.5.5 0 0 1 .5.5v34a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-34a.5.5 0 0 1 .5-.5zm1.238 3.042l.774.512v.013l-.774.505.341.466.722-.577h.013l.243.899.551-.177-.328-.88.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.472zm0 3.908l.774.512v.013l-.774.505.341.466.722-.578h.013l.243.9.551-.178-.328-.88.932.053v-.597l-.932.046.328-.872-.551-.17-.243.891h-.013l-.722-.584-.34.473zm0 3.907l.774.512v.013l-.774.505.341.466.722-.577h.013l.243.899.551-.178-.328-.879.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.472zm0 3.908l.774.511v.014l-.774.505.341.466.722-.578h.013l.243.899.551-.177-.328-.88.932.053v-.597l-.932.046.328-.872-.551-.171-.243.892h-.013l-.722-.584-.34.473zm0 3.907l.774.512v.013l-.774.505.341.466.722-.577h.013l.243.898.551-.177-.328-.879.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.472zm0 3.908l.774.511v.013l-.774.506.341.465.722-.577h.013l.243.899.551-.177-.328-.88.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.473zm0 3.907l.774.512v.013l-.774.505.341.466.722-.578h.013l.243.9.551-.178-.328-.879.932.052v-.597l-.932.046.328-.872-.551-.17-.243.891h-.013l-.722-.583-.34.472zm0 3.907l.774.512v.013l-.774.505.341.466.722-.577h.013l.243.899.551-.177-.328-.88.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.472z"
/>
<path
fill="#FFF"
stroke="#142533"
strokeWidth="2"
d="M123.166 125.105c7.049-8.4 5.953-20.925-2.447-27.974l-90.824-76.21a3 3 0 0 0-4.227.37L4 47.115a3 3 0 0 0 .37 4.227l90.824 76.21c8.4 7.049 20.924 5.953 27.973-2.447z"
/>
<ellipse cx="108.016" cy="111.123" stroke="#6490F1" rx="10.57" ry="10.644" />
</g>
</g>
</svg>
)

63
src/icons/illustrations/SetPassword.js

@ -1,63 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg width="136" height="52">
<g fill="none" fillRule="evenodd">
<g fillRule="nonzero">
<g transform="translate(36 1)">
<ellipse cx="37.488" cy="45.422" fill="#000" rx="1.037" ry="1.036" />
<ellipse cx="33.5" cy="45.422" fill="#000" rx="1.037" ry="1.036" />
<ellipse cx="29.512" cy="45.422" fill="#000" rx="1.037" ry="1.036" />
<path
fill="#142533"
d="M65.405 40.242h-2.792V9.562c0-7.251-5.982-9.164-9.173-9.164H13.56c-3.191 0-9.173 1.913-9.173 9.165v30.68H1.595C.957 40.242.4 40.8.4 41.436c0 3.188 1.914 9.165 9.172 9.165H57.43c7.258 0 9.172-5.977 9.172-9.164 0-.638-.558-1.196-1.196-1.196zM6.78 9.562c0-6.534 6.062-6.773 6.78-6.773h39.88c.718 0 6.78.24 6.78 6.774v30.68H6.78V9.562zm50.649 38.649H9.57c-5.025 0-6.3-3.586-6.7-5.578h61.18c-.32 1.912-1.595 5.578-6.62 5.578z"
/>
</g>
<g fill="#6490F1">
<path d="M74.189 20.125c0-.16.08-.319.08-.478V16.38c0-1.833-1.524-3.427-3.448-3.427h-2.005c-1.844 0-3.448 1.514-3.448 3.427v3.267c0 .16 0 .319.08.478-1.763.16-3.127 1.594-3.127 3.347v6.773c0 1.833 1.524 3.427 3.448 3.427h8.26c1.844 0 3.447-1.514 3.447-3.427v-6.773c-.08-1.753-1.523-3.188-3.287-3.347zM67.854 16.3c0-.558.481-1.036 1.043-1.036H70.9c.561 0 1.043.478 1.043 1.036v3.267c0 .16-.08.319-.16.478h-3.69c-.08-.16-.16-.318-.16-.478l-.08-3.267zm7.136 14.025c0 .558-.48 1.036-1.042 1.036H65.77a1.058 1.058 0 0 1-1.042-1.036v-6.773c0-.558.481-1.036 1.042-1.036h8.26c.56 0 1.042.478 1.042 1.036l-.08 6.773z" />
<path d="M69.899 24.11c-.638 0-1.197.548-1.197 1.175v2.43c0 .627.559 1.176 1.197 1.176s1.196-.549 1.196-1.176v-2.43c0-.705-.558-1.176-1.196-1.176z" />
</g>
</g>
<g transform="translate(109 4)">
<path
fill="#CCC"
fillRule="nonzero"
d="M6.325 38.352l.614-.636c.254-.263.712-.233 1.023.067.31.3.356.757.102 1.02l-.614.636.643.62c.267.258.242.715-.055 1.022-.296.307-.753.348-1.019.09l-.643-.62-.614.635c-.254.264-.712.234-1.023-.066-.31-.3-.356-.757-.102-1.02l.614-.636-.643-.62c-.266-.258-.242-.715.055-1.022.296-.308.753-.348 1.02-.09l.642.62z"
/>
<ellipse cx="1.224" cy="24.349" stroke="#E2E2E2" strokeWidth=".8" rx="1.146" ry="1.133" />
<ellipse cx="21.21" cy="23.216" stroke="#E2E2E2" strokeWidth=".8" rx="1.564" ry="1.546" />
<path
fill="#EEE"
fillRule="nonzero"
d="M11.558 12.189l.917-.95c.281-.291.677-.366.883-.167.206.2.145.597-.136.888l-.917.95.96.928c.295.284.375.68.178.884-.197.204-.596.139-.89-.146l-.961-.927-.917.95c-.281.29-.677.365-.883.166-.206-.2-.145-.597.136-.888l.917-.95-.96-.928c-.295-.284-.375-.68-.178-.884.197-.204.596-.139.89.146l.961.928z"
/>
<path
fill="#CCC"
fillRule="nonzero"
d="M25.03 3.01l.71-.736c.251-.26.657-.277.905-.037.249.24.246.646-.005.907l-.71.734.744.718c.263.255.285.66.047.906-.237.246-.643.239-.907-.016l-.743-.718-.71.735c-.251.26-.657.277-.906.037-.248-.24-.246-.646.006-.906l.71-.735-.744-.718c-.263-.255-.285-.66-.047-.906.237-.246.643-.238.907.016l.743.718z"
/>
</g>
<g transform="translate(1 -1)">
<path
fill="#CCC"
fillRule="nonzero"
d="M20.486 48.016l.621-.643c.257-.267.72-.238 1.033.065.314.303.36.764.102 1.03l-.621.644.648.626c.269.26.243.722-.057 1.033-.3.31-.761.352-1.03.093l-.648-.627-.622.644c-.257.266-.72.237-1.033-.066-.313-.302-.359-.764-.102-1.03l.622-.643-.649-.627c-.268-.26-.243-.721.057-1.032.3-.311.762-.353 1.03-.093l.649.626z"
/>
<ellipse cx="3.155" cy="37.389" stroke="#CCC" strokeWidth=".8" rx="3.155" ry="3.13" />
<ellipse cx="18.932" cy="29.564" stroke="#E2E2E2" strokeWidth=".8" rx="1.578" ry="1.565" />
<path
fill="#EEE"
fillRule="nonzero"
d="M9.447 16.156l1.118-1.158c.343-.356.825-.447 1.075-.205.251.242.176.726-.167 1.081l-1.118 1.158 1.167 1.128c.358.346.454.828.214 1.076-.24.249-.725.17-1.084-.176l-1.167-1.127-1.118 1.158c-.343.355-.825.447-1.075.205-.251-.242-.176-.727.167-1.082l1.118-1.158L7.41 15.93c-.358-.346-.454-.828-.214-1.077.24-.248.725-.17 1.084.176l1.167 1.128z"
/>
<path
fill="#CCC"
fillRule="nonzero"
d="M22.47 2.013l.699-.724c.214-.222.515-.28.672-.128.156.151.11.454-.105.676l-.699.724.73.704c.224.216.283.518.133.673-.15.156-.453.106-.677-.11l-.73-.704-.698.723c-.215.222-.515.28-.672.128-.157-.15-.11-.454.104-.676l.7-.723-.73-.705c-.224-.216-.284-.517-.134-.673.15-.155.453-.106.677.11l.73.705z"
/>
</g>
</g>
</svg>
)

40
src/icons/illustrations/WriteSeed.js

@ -1,40 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg width="145" height="109">
<g fill="none" fillRule="evenodd" transform="translate(-1)">
<path
fill="#6490F1"
fillOpacity=".1"
stroke="#142533"
strokeWidth="1.8"
d="M29.105 104.014l19.87-84.34L4.123 35.999a2.7 2.7 0 0 0-1.573 3.566l26.556 64.449zm89.265 1.336L97.73 34.29l44.752 16.288a2.7 2.7 0 0 1 1.49 3.748L118.37 105.35z"
/>
<rect
width="88.2"
height="107.2"
x="29.9"
y=".9"
fill="#FFF"
stroke="#142533"
strokeWidth="1.8"
rx="3.6"
/>
<rect width="50" height="4" x="49" y="14" fill="#6490F1" rx="2" />
<path
fill="#6490F1"
d="M83.18 91.378h24.01a1.81 1.81 0 0 1 0 3.622H83.18a1.81 1.81 0 1 1 0-3.622zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.622H40.81a1.81 1.81 0 1 1 0-3.622zm42.37-12.675h24.01a1.81 1.81 0 0 1 0 3.621H83.18a1.81 1.81 0 1 1 0-3.621zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.621H40.81a1.81 1.81 0 1 1 0-3.621zm42.37-12.676h24.01a1.81 1.81 0 0 1 0 3.622H83.18a1.81 1.81 0 1 1 0-3.622zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.622H40.81a1.81 1.81 0 1 1 0-3.622zm42.37-12.676h24.01a1.81 1.81 0 0 1 0 3.622H83.18a1.81 1.81 0 1 1 0-3.622zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.622H40.81a1.81 1.81 0 1 1 0-3.622zm42.37-12.675h24.01a1.81 1.81 0 0 1 0 3.621H83.18a1.81 1.81 0 1 1 0-3.621zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.621H40.81a1.81 1.81 0 1 1 0-3.621zM83.18 28h24.01a1.81 1.81 0 0 1 0 3.622H83.18a1.81 1.81 0 1 1 0-3.622zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.622H40.81a1.81 1.81 0 1 1 0-3.622z"
opacity=".5"
/>
<path
fill="#FFF"
fillRule="nonzero"
stroke="#6490F1"
strokeWidth="2"
d="M85.623 56.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 46.278 67.447 41 73.908 41c6.462 0 11.715 5.278 11.715 11.766v3.28zM73.809 73.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"
/>
</g>
</svg>
)

7
src/internals/index.js

@ -5,6 +5,7 @@ import uuid from 'uuid/v4'
import { setImplementation } from 'api/network' import { setImplementation } from 'api/network'
import sentry from 'sentry/node' import sentry from 'sentry/node'
import { DEBUG_NETWORK } from 'config/constants' import { DEBUG_NETWORK } from 'config/constants'
import { serializeError } from 'helpers/errors'
require('../env') require('../env')
@ -75,11 +76,7 @@ process.on('message', m => {
process.send({ process.send({
type: 'cmd.ERROR', type: 'cmd.ERROR',
requestId, requestId,
data: { data: serializeError(error),
...error,
name: error && error.name,
message: error && error.message,
},
}) })
}, },
}) })

16
src/logger.js

@ -148,14 +148,16 @@ export default {
critical: (error: Error) => { critical: (error: Error) => {
addLog('critical', error) addLog('critical', error)
console.error(error) console.error(error)
try { if (!process.env.STORYBOOK_ENV) {
if (typeof window !== 'undefined') { try {
require('sentry/browser').captureException(error) if (typeof window !== 'undefined') {
} else { require('sentry/browser').captureException(error)
require('sentry/node').captureException(error) } else {
require('sentry/node').captureException(error)
}
} catch (e) {
console.warn("Can't send to sentry", error, e)
} }
} catch (e) {
console.warn("Can't send to sentry", error, e)
} }
}, },

41
src/main/app.js

@ -2,16 +2,22 @@
import { app, BrowserWindow, Menu, screen } from 'electron' import { app, BrowserWindow, Menu, screen } from 'electron'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'
import { MIN_HEIGHT, MIN_WIDTH } from 'config/constants'
import menu from 'main/menu' import menu from 'main/menu'
import db from 'helpers/db' import db from 'helpers/db'
import { setMainProcessPID, terminateAllTheThings } from './terminator'
setMainProcessPID(process.pid)
// necessary to prevent win from being garbage collected // necessary to prevent win from being garbage collected
let mainWindow = null let mainWindow = null
export const getMainWindow = () => mainWindow export const getMainWindow = () => mainWindow
let forceClose = false // TODO put back OSX close behavior
// let forceClose = false
const { UPGRADE_EXTENSIONS, ELECTRON_WEBPACK_WDS_PORT, DEV_TOOLS, DEV_TOOLS_MODE } = process.env const { UPGRADE_EXTENSIONS, ELECTRON_WEBPACK_WDS_PORT, DEV_TOOLS, DEV_TOOLS_MODE } = process.env
@ -26,15 +32,16 @@ const getWindowPosition = (height, width, display = screen.getPrimaryDisplay())
} }
} }
const handleCloseWindow = w => e => { // TODO put back OSX close behavior
if (!forceClose) { // const handleCloseWindow = w => e => {
e.preventDefault() // if (!forceClose) {
w.webContents.send('lock') // e.preventDefault()
if (w !== null) { // w.webContents.send('lock')
w.hide() // if (w !== null) {
} // w.hide()
} // }
} // }
// }
const getDefaultUrl = () => const getDefaultUrl = () =>
__DEV__ ? `http://localhost:${ELECTRON_WEBPACK_WDS_PORT || ''}` : `file://${__dirname}/index.html` __DEV__ ? `http://localhost:${ELECTRON_WEBPACK_WDS_PORT || ''}` : `file://${__dirname}/index.html`
@ -67,9 +74,6 @@ const defaultWindowOptions = {
} }
function createMainWindow() { function createMainWindow() {
const MIN_HEIGHT = 768
const MIN_WIDTH = 1024
const savedDimensions = db.getIn('settings', 'window.MainWindow.dimensions', {}) const savedDimensions = db.getIn('settings', 'window.MainWindow.dimensions', {})
const savedPositions = db.getIn('settings', 'window.MainWindow.positions', null) const savedPositions = db.getIn('settings', 'window.MainWindow.positions', null)
@ -109,7 +113,9 @@ function createMainWindow() {
window.loadURL(url) window.loadURL(url)
window.on('close', handleCloseWindow(window)) // TODO put back OSX close behavior
// window.on('close', handleCloseWindow(window))
window.on('close', terminateAllTheThings)
window.on('ready-to-show', () => { window.on('ready-to-show', () => {
window.show() window.show()
@ -128,9 +134,10 @@ function createMainWindow() {
return window return window
} }
app.on('before-quit', () => { // TODO put back OSX close behavior
forceClose = true // app.on('before-quit', () => {
}) // forceClose = true
// })
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
// On macOS it is common for applications to stay open // On macOS it is common for applications to stay open

2
src/main/bridge.js

@ -11,6 +11,7 @@ import sentry from 'sentry/node'
import user from 'helpers/user' import user from 'helpers/user'
import setupAutoUpdater, { quitAndInstall } from './autoUpdate' import setupAutoUpdater, { quitAndInstall } from './autoUpdate'
import { setInternalProcessPID } from './terminator'
import { getMainWindow } from './app' import { getMainWindow } from './app'
@ -49,6 +50,7 @@ const bootInternalProcess = () => {
SENTRY_USER_ID: userId, SENTRY_USER_ID: userId,
}, },
}) })
setInternalProcessPID(internalProcess.pid)
internalProcess.on('message', handleGlobalInternalMessage) internalProcess.on('message', handleGlobalInternalMessage)
internalProcess.on('exit', handleExit) internalProcess.on('exit', handleExit)
} }

32
src/main/terminator.js

@ -0,0 +1,32 @@
// @flow
// <((((((\\\
// / . }\
// ;--..--._|}
// (\ '--/\--' )
// DISCLAIMER \\ | '-' :'|
// This is a dirty hack \\ . -==- .-|
// \\ \.__.' \--._
// [\\ __.--| // _/'--.
// \ \\ .'-._ ('-----'/ __/ \
// \ \\ / __>| | '--. |
// \ \\ | \ | / / /
// \ '\ / \ | | _/ /
// \ \ \ | | / /
// \ \ \ /
let MAIN_PROCESS_PID: ?number = null
let INTERNAL_PROCESS_PID: ?number = null
function kill(processType, pid) {
console.log(`-> Killing ${processType} process ${pid}`) // eslint-disable-line no-console
process.kill(pid, 'SIGTERM')
}
exports.setMainProcessPID = (pid: number) => (MAIN_PROCESS_PID = pid)
exports.setInternalProcessPID = (pid: number) => (INTERNAL_PROCESS_PID = pid)
exports.terminateAllTheThings = () => {
if (INTERNAL_PROCESS_PID) kill('internal', INTERNAL_PROCESS_PID)
if (MAIN_PROCESS_PID) kill('main', MAIN_PROCESS_PID)
}

2
src/stories/icons.stories.js

@ -6,7 +6,7 @@ import Box from 'components/base/Box'
const stories = storiesOf('Common', module) const stories = storiesOf('Common', module)
const req = require.context('../icons', true, /^((?!illustrations).)*\.js$/) const req = require.context('../icons', true, /.js$/)
const icons = req const icons = req
.keys() .keys()
.map(file => { .map(file => {

8
static/i18n/en/app.yml

@ -286,9 +286,7 @@ settings:
language: Language language: Language
languageDesc: Choose the language to display. languageDesc: Choose the language to display.
counterValue: Base currency counterValue: Base currency
counterValueDesc: Choose the currency to display next to your balance and operations. counterValueDesc: Choose the currency to display next to your balance and operations. An exchange is used (via BTC pair).
exchange: Rate provider ({{ticker}})
exchangeDesc: Choose the provider of the base currency exchange rates.
region: Region region: Region
regionDesc: Choose the region in which you’re located to set the application’s time zone. regionDesc: Choose the region in which you’re located to set the application’s time zone.
stock: Regional market indicator stock: Regional market indicator
@ -296,7 +294,7 @@ settings:
currencies: currencies:
desc: Select a cryptocurrency to edit its settings. desc: Select a cryptocurrency to edit its settings.
exchange: Rate provider ({{ticker}}) exchange: Rate provider ({{ticker}})
exchangeDesc: Choose the provider of the base currency exchange rates. exchangeDesc: Choose the provider of the base currency exchange rates (via BTC).
confirmationsToSpend: Number of confirmations required to spend confirmationsToSpend: Number of confirmations required to spend
confirmationsToSpendDesc: Set the number of confirmations required for your funds to be spendable. # A higher number of confirmations decreases the probability that a transaction is rejected. confirmationsToSpendDesc: Set the number of confirmations required for your funds to be spendable. # A higher number of confirmations decreases the probability that a transaction is rejected.
confirmationsNb: Number of confirmations confirmationsNb: Number of confirmations
@ -321,7 +319,7 @@ settings:
hardResetDesc: Erase all Ledger Live data stored on your computer, including your profile, accounts, transaction history and settings. The private keys that manage your crypto assets remain secure on your Ledger device. hardResetDesc: Erase all Ledger Live data stored on your computer, including your profile, accounts, transaction history and settings. The private keys that manage your crypto assets remain secure on your Ledger device.
hardReset: Reset hardReset: Reset
developerMode: Developer mode developerMode: Developer mode
developerModeDesc: Show developer apps in the Manager. developerModeDesc: Show developer apps in the Manager and enable testnet currencies.
analytics: Analytics analytics: Analytics
analyticsDesc: Enable analytics of anonymous data to help Ledger improve the user experience. This includes the operating system, language, firmware versions and the number of added accounts. analyticsDesc: Enable analytics of anonymous data to help Ledger improve the user experience. This includes the operating system, language, firmware versions and the number of added accounts.
reportErrors: Usage and diagnostics reportErrors: Usage and diagnostics

8
static/i18n/fr/app.yml

@ -280,9 +280,7 @@ settings:
language: Language language: Language
languageDesc: Choose the language to display. languageDesc: Choose the language to display.
counterValue: Base currency counterValue: Base currency
counterValueDesc: Choose the currency to display next to your balance and operations. counterValueDesc: Choose the currency to display next to your balance and operations. An exchange is used (via BTC pair).
exchange: Rate provider ({{ticker}})
exchangeDesc: Choose the provider of the base currency exchange rates.
region: Region region: Region
regionDesc: Choose the region in which you’re located to set the application’s time zone. regionDesc: Choose the region in which you’re located to set the application’s time zone.
stock: Regional market indicator stock: Regional market indicator
@ -290,7 +288,7 @@ settings:
currencies: currencies:
desc: Select a cryptocurrency to edit its settings. desc: Select a cryptocurrency to edit its settings.
exchange: Rate provider ({{ticker}}) exchange: Rate provider ({{ticker}})
exchangeDesc: Choose the provider of the base currency exchange rates. exchangeDesc: Choose the provider of the base currency exchange rates (via BTC).
confirmationsToSpend: Number of confirmations required to spend confirmationsToSpend: Number of confirmations required to spend
confirmationsToSpendDesc: Set the number of confirmations required for your funds to be spendable. confirmationsToSpendDesc: Set the number of confirmations required for your funds to be spendable.
confirmationsNb: Number of confirmations confirmationsNb: Number of confirmations
@ -315,7 +313,7 @@ settings:
hardResetDesc: Erase all Ledger Live data stored on your computer, including your profile, accounts, transaction history and settings. The private keys that manage your crypto assets remain secure on your Ledger device. hardResetDesc: Erase all Ledger Live data stored on your computer, including your profile, accounts, transaction history and settings. The private keys that manage your crypto assets remain secure on your Ledger device.
hardReset: Reset hardReset: Reset
developerMode: Developer mode developerMode: Developer mode
developerModeDesc: Show developer apps in the Manager. developerModeDesc: Show developer apps in the Manager and enable testnet currencies.
analytics: Analytics analytics: Analytics
analyticsDesc: Enable analytics of anonymous data to help Ledger improve the user experience. This includes the operating system, language, firmware versions and the number of added accounts. analyticsDesc: Enable analytics of anonymous data to help Ledger improve the user experience. This includes the operating system, language, firmware versions and the number of added accounts.
reportErrors: Usage and diagnostics reportErrors: Usage and diagnostics

9
static/i18n/fr/errors.yml

@ -22,5 +22,10 @@ DeviceSocketNoBulkStatus: Oops, device connection failed. Please try again [bulk
DeviceSocketNoHandler: Oops, device connection failed (handler {{query}}). Please try again. DeviceSocketNoHandler: Oops, device connection failed (handler {{query}}). Please try again.
LatestMCUInstalledError: MCU on device already up to date. LatestMCUInstalledError: MCU on device already up to date.
HardResetFail: Reset failed. Please try again. HardResetFail: Reset failed. Please try again.
CannotUninstall: Cannot uninstall app. ManagerAPIsFail: Our services are currently unavailable. Please try again later.
CannotInstall: Not enough room left on your device. Please uninstall some apps and try again. ManagerUnexpected: Unexpected error occurred ({{msg}}). Please try again later.
ManagerNotEnoughSpace: Not enough room left on your device. Please uninstall some apps and try again.
ManagerDeviceLocked: Device is locked
ManagerAppAlreadyInstalled: App is already installed
ManagerAppRelyOnBTC: You must install Bitcoin application first
ManagerUninstallBTCDep: You must uninstall other altcoins first

19
static/images/blue-error-onb.svg

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="188" viewBox="0 0 92 188">
<defs>
<linearGradient id="a" x1="50%" x2="50%" y1="0%" y2="100%">
<stop offset="0%"/>
<stop offset="100%" stop-color="#FFF"/>
</linearGradient>
</defs>
<g fill="none" fill-rule="evenodd">
<path stroke="#1D2027" stroke-width="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" stroke-width="2" d="M42.208 153.253v10.929h6.436v-10.93h-6.436z"/>
<path stroke="#1D2027" stroke-width="2" d="M39.713 120.577h11.255l-.082-6.977a1 1 0 0 0-1-.988H40.619a1 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)"/>
<g>
<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" stroke-width="2" rx="5.44"/>
<rect width="59" height="88.615" x="15.5" y="16.5" fill="#FFF" stroke="#EA2E49" rx="4.08"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

13
static/images/get-started-onb.svg

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="113" height="109" viewBox="0 0 113 109">
<g fill="none" fill-rule="evenodd">
<rect width="1.44" height="5.6" y="16.6" fill="#1D2028" rx=".72" transform="matrix(-1 0 0 1 1.44 0)"/>
<rect width="1.44" height="5.6" y="34.8" fill="#1D2028" rx=".72" transform="matrix(-1 0 0 1 1.44 0)"/>
<path fill="#6490F1" fill-opacity=".1" stroke="#1D2028" stroke-width="2" d="M16.592 12c.225 0 .408.183.408.408v95.184a.408.408 0 0 1-.408.408H2.128a.408.408 0 0 1-.408-.408V12.408c0-.225.183-.408.408-.408h14.464z"/>
<rect width="7.64" height="27" x="5.513" y="18.522" fill="#FFF" stroke="#6490F1" rx=".704" transform="matrix(-1 0 0 1 18.665 0)"/>
<path fill="#FFF" stroke="#1D2028" stroke-width="2" d="M9.36 54A7.64 7.64 0 0 1 17 61.64v45.952a.408.408 0 0 1-.408.408H2.128a.408.408 0 0 1-.408-.408V61.64A7.64 7.64 0 0 1 9.36 54z"/>
<ellipse cx="9.36" cy="61.4" fill="#FFF" stroke="#6490F1" rx="3.82" ry="3.7" transform="matrix(-1 0 0 1 18.72 0)"/>
<rect width="3.137" height="9.306" x="109.863" y="13.959" fill="#1D2028" rx="1.569"/>
<rect width="76.431" height="106.571" x="34" y="1" fill="#6490F1" fill-opacity=".1" stroke="#1D2027" stroke-width="2" rx="5.44"/>
<rect width="52.333" height="79.653" x="46.043" y="15.235" fill="#FFF" stroke="#6490F1" rx="4.08"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

7
static/images/ledger-blue-onb.svg

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="72" viewBox="0 0 52 72">
<g fill="none" fill-rule="evenodd">
<rect width="2.039" height="6.171" x="49.961" y="9.257" fill="#1D2028" rx="1.02"/>
<rect width="49.48" height="70.5" x=".75" y=".75" fill="#6490F1" fill-opacity=".1" stroke="#1D2027" stroke-width="1.5" rx="3.2"/>
<rect width="34.167" height="52.986" x="8.403" y="10.021" fill="#FFF" stroke="#6490F1" stroke-width=".5" rx="2.4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 493 B

10
static/images/ledger-nano-onb.svg

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="72" viewBox="0 0 13 72">
<g fill="none" fill-rule="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" fill-opacity=".1" stroke="#1D2028" stroke-width="1.5" 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="5.74" height="20.071" x="3.39" y="5.409" fill="#FFF" stroke="#6490F1" stroke-width=".5" rx=".8"/>
<path fill="#FFF" stroke="#1D2028" stroke-width="1.5" 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" stroke-width=".5" rx="2.87" ry="2.836"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 931 B

34
static/images/nano-error-onb.svg

@ -0,0 +1,34 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="366" height="43" viewBox="0 0 366 43">
<defs>
<linearGradient id="a" x1="50%" x2="50%" y1="0%" y2="100%">
<stop offset="0%"/>
<stop offset="100%" stop-color="#FFF"/>
</linearGradient>
<rect id="b" width="41.711" height="238.384" rx="4"/>
<path id="c" d="M5.773 5l2.541-2.541a.235.235 0 0 0 0-.332l-.441-.441a.235.235 0 0 0-.332 0L5 4.226l-2.541-2.54a.235.235 0 0 0-.332 0l-.441.441a.235.235 0 0 0 0 .332L4.226 5l-2.54 2.541a.235.235 0 0 0 0 .332l.441.441c.092.092.24.092.332 0L5 5.774l2.541 2.54c.092.092.24.092.332 0l.441-.441a.235.235 0 0 0 0-.332L5.774 5z"/>
</defs>
<g fill="none" fill-rule="evenodd">
<path stroke="#1D2027" stroke-width="2" d="M127.356 30a1 1 0 0 1-1 1H100.13a5 5 0 0 1-5-5v-8.486a5 5 0 0 1 5-5h26.225a1 1 0 0 1 1 1V30z"/>
<path stroke="#142533" stroke-width="2" d="M94.747 24.792H83.818v-6.436h10.93v6.436z"/>
<path stroke="#1D2027" stroke-width="2" d="M127.423 27.287V16.032l6.977.082a1 1 0 0 1 .988 1V26.381a1 1 0 0 1-1.012.988l-6.953-.082z"/>
<path fill="url(#a)" d="M6.836 53.925h1.616v82.65H6.836v-82.65zm5.657 0h1.616v82.65h-1.616v-82.65z" transform="matrix(0 -1 -1 0 137 32)"/>
<g transform="rotate(-90 85 -42)">
<rect width="4.492" height="17.12" x="38.336" y="15.505" fill="#142533" rx="2"/>
<rect width="4.492" height="17.12" x="38.336" y="70.094" fill="#142533" rx="2"/>
<use fill="#FFF" xlink:href="#b"/>
<rect width="39.711" height="236.384" x="1" y="1" fill="#FCE0E4" stroke="#142533" stroke-linejoin="square" stroke-width="2" rx="4"/>
<rect width="20.176" height="61.019" x="10.767" y="21.173" fill="#FFF" stroke="#EA2E49" rx="1.6"/>
<path fill="#FFF" stroke="#142533" stroke-width="2" d="M20.856 95.966C9.89 95.966 1 104.856 1 115.822v118.562a3 3 0 0 0 3 3h33.711a3 3 0 0 0 3-3V115.822c0-10.966-8.89-19.856-19.855-19.856z"/>
<ellipse cx="21.016" cy="116.123" stroke="#EA2E49" rx="10.57" ry="10.644"/>
<g transform="translate(16.066 26.884)">
<mask id="d" fill="#fff">
<use xlink:href="#c"/>
</mask>
<use fill="#000" fill-rule="nonzero" xlink:href="#c"/>
<g fill="#EA2E49" mask="url(#d)">
<path d="M0 0h10v10H0z"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

19
static/images/select-pin-blue-onb.svg

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="188" viewBox="0 0 92 188">
<defs>
<linearGradient id="a" x1="50%" x2="50%" y1="0%" y2="100%">
<stop offset="0%"/>
<stop offset="100%" stop-color="#FFF"/>
</linearGradient>
</defs>
<g fill="none" fill-rule="evenodd">
<path stroke="#1D2027" stroke-width="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" stroke-width="2" d="M42.208 153.253v10.929h6.436v-10.93h-6.436z"/>
<path stroke="#1D2027" stroke-width="2" d="M39.713 120.577h11.255l-.082-6.977a1 1 0 0 0-1-.988H40.619a1 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)"/>
<g>
<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" stroke-width="2" rx="5.44"/>
<rect width="59" height="88.615" x="15.5" y="16.5" fill="#FFF" stroke="#6490F1" rx="4.08"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

25
static/images/select-pin-nano-onb.svg

@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="260" height="129" viewBox="0 0 260 129">
<defs>
<linearGradient id="a" x1="50%" x2="50%" y1="0%" y2="100%">
<stop offset="0%"/>
<stop offset="100%" stop-color="#FFF"/>
</linearGradient>
<path id="b" d="M91 0h34a4 4 0 0 1 4 4v108.144c0 11.519-9.337 20.856-20.856 20.856h-.288C96.337 133 87 123.663 87 112.144V4a4 4 0 0 1 4-4z"/>
</defs>
<g fill="none" fill-rule="evenodd">
<path stroke="#1D2027" stroke-width="2" d="M127.856 31.44a1 1 0 0 1-1 1H100.63a5 5 0 0 1-5-5v-8.486a5 5 0 0 1 5-5h26.225a1 1 0 0 1 1 1v16.485z"/>
<path stroke="#142533" stroke-width="2" d="M95.247 26.231H84.318v-6.435h10.93v6.435z"/>
<path stroke="#1D2027" stroke-width="2" d="M127.923 28.726V17.471l6.977.083a1 1 0 0 1 .988 1V27.82a1 1 0 0 1-1.012.988l-6.953-.083z"/>
<path fill="url(#a)" d="M6.836 53.925h1.616v82.65H6.836v-82.65zm5.657 0h1.616v82.65h-1.616v-82.65z" transform="matrix(0 -1 -1 0 137.5 33.44)"/>
<g transform="rotate(-90 128.59 1.975)">
<rect width="4.492" height="17.12" x="125.336" y="15.505" fill="#142533" rx="2"/>
<rect width="4.492" height="17.12" x="125.336" y="70.094" fill="#142533" rx="2"/>
<use fill="#FFF" xlink:href="#b"/>
<path fill="#6490F1" fill-opacity=".15" stroke="#142533" stroke-linejoin="square" stroke-width="2" d="M91 1a3 3 0 0 0-3 3v108.144C88 123.11 96.89 132 107.856 132h.288C119.11 132 128 123.11 128 112.144V4a3 3 0 0 0-3-3H91z"/>
<rect width="21" height="62" x="97.5" y="21.5" fill="#FFF" stroke="#6490F1" rx="1.6"/>
<path fill="#6490F1" d="M105.5 35h5a.5.5 0 0 1 .5.5v34a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-34a.5.5 0 0 1 .5-.5zm1.238 3.042l.774.512v.013l-.774.505.341.466.722-.577h.013l.243.899.551-.177-.328-.88.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.472zm0 3.908l.774.512v.013l-.774.505.341.466.722-.578h.013l.243.9.551-.178-.328-.88.932.053v-.597l-.932.046.328-.872-.551-.17-.243.891h-.013l-.722-.584-.34.473zm0 3.907l.774.512v.013l-.774.505.341.466.722-.577h.013l.243.899.551-.178-.328-.879.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.472zm0 3.908l.774.511v.014l-.774.505.341.466.722-.578h.013l.243.899.551-.177-.328-.88.932.053v-.597l-.932.046.328-.872-.551-.171-.243.892h-.013l-.722-.584-.34.473zm0 3.907l.774.512v.013l-.774.505.341.466.722-.577h.013l.243.898.551-.177-.328-.879.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.472zm0 3.908l.774.511v.013l-.774.506.341.465.722-.577h.013l.243.899.551-.177-.328-.88.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.473zm0 3.907l.774.512v.013l-.774.505.341.466.722-.578h.013l.243.9.551-.178-.328-.879.932.052v-.597l-.932.046.328-.872-.551-.17-.243.891h-.013l-.722-.583-.34.472zm0 3.907l.774.512v.013l-.774.505.341.466.722-.577h.013l.243.899.551-.177-.328-.88.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.472z"/>
<path fill="#FFF" stroke="#142533" stroke-width="2" d="M123.166 125.105c7.049-8.4 5.953-20.925-2.447-27.974l-90.824-76.21a3 3 0 0 0-4.227.37L4 47.115a3 3 0 0 0 .37 4.227l90.824 76.21c8.4 7.049 20.924 5.953 27.973-2.447z"/>
<ellipse cx="108.016" cy="111.123" stroke="#6490F1" rx="10.57" ry="10.644"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

9
static/images/write-seed-onb.svg

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="145" height="109" viewBox="0 0 145 109">
<g fill="none" fill-rule="evenodd" transform="translate(-1)">
<path fill="#6490F1" fill-opacity=".1" stroke="#142533" stroke-width="1.8" d="M29.105 104.014l19.87-84.34L4.123 35.999a2.7 2.7 0 0 0-1.573 3.566l26.556 64.449zM118.37 105.35L97.73 34.29l44.752 16.288a2.7 2.7 0 0 1 1.49 3.748L118.37 105.35z"/>
<rect width="88.2" height="107.2" x="29.9" y=".9" fill="#FFF" stroke="#142533" stroke-width="1.8" rx="3.6"/>
<rect width="50" height="4" x="49" y="14" fill="#6490F1" rx="2"/>
<path fill="#6490F1" d="M83.18 91.378h24.01a1.81 1.81 0 0 1 0 3.622H83.18a1.81 1.81 0 1 1 0-3.622zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.622H40.81a1.81 1.81 0 1 1 0-3.622zm42.37-12.675h24.01a1.81 1.81 0 0 1 0 3.621H83.18a1.81 1.81 0 1 1 0-3.621zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.621H40.81a1.81 1.81 0 1 1 0-3.621zm42.37-12.676h24.01a1.81 1.81 0 0 1 0 3.622H83.18a1.81 1.81 0 1 1 0-3.622zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.622H40.81a1.81 1.81 0 1 1 0-3.622zm42.37-12.676h24.01a1.81 1.81 0 0 1 0 3.622H83.18a1.81 1.81 0 1 1 0-3.622zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.622H40.81a1.81 1.81 0 1 1 0-3.622zm42.37-12.675h24.01a1.81 1.81 0 0 1 0 3.621H83.18a1.81 1.81 0 1 1 0-3.621zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.621H40.81a1.81 1.81 0 1 1 0-3.621zM83.18 28h24.01a1.81 1.81 0 0 1 0 3.622H83.18a1.81 1.81 0 1 1 0-3.622zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.622H40.81a1.81 1.81 0 1 1 0-3.622z" opacity=".5"/>
<path fill="#FFF" fill-rule="nonzero" stroke="#6490F1" stroke-width="2" d="M85.623 56.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 46.278 67.447 41 73.908 41c6.462 0 11.715 5.278 11.715 11.766v3.28zM73.809 73.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-.003-.001zm0 .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"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

23
webpack/renderer.config.js

@ -1,9 +1,29 @@
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin') const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const plugins = require('./plugins') const plugins = require('./plugins')
const resolve = require('./resolve') const resolve = require('./resolve')
const rules = require('./rules') const rules = require('./rules')
const getOptimization = env => {
const optimization = {
minimizer: [
// Default Config without mangling
new UglifyJsPlugin({
parallel: true,
sourceMap: true,
uglifyOptions: {
mangle: false,
compress: {
ecma: 7,
},
},
}),
],
}
return env === 'production' ? optimization : undefined
}
const config = { const config = {
mode: __ENV__, mode: __ENV__,
plugins: [...plugins('renderer'), new HardSourceWebpackPlugin()], plugins: [...plugins('renderer'), new HardSourceWebpackPlugin()],
@ -14,6 +34,9 @@ const config = {
devServer: { devServer: {
historyApiFallback: true, historyApiFallback: true,
}, },
optimization: {
...getOptimization(__ENV__),
},
} }
if (__DEV__) { if (__DEV__) {

17
yarn.lock

@ -1477,8 +1477,8 @@
bip32-path "0.4.2" bip32-path "0.4.2"
"@ledgerhq/hw-transport-node-hid@^4.13.0": "@ledgerhq/hw-transport-node-hid@^4.13.0":
version "4.15.0" version "4.16.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.15.0.tgz#d25b1839230509235782884a5be3d56e791dad26" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.16.0.tgz#de8d7ffd37e01c77aed566d8c534fe3937c3c35c"
dependencies: dependencies:
"@ledgerhq/hw-transport" "^4.15.0" "@ledgerhq/hw-transport" "^4.15.0"
node-hid "^0.7.2" node-hid "^0.7.2"
@ -13797,6 +13797,19 @@ uglifyjs-webpack-plugin@^1.2.4, uglifyjs-webpack-plugin@^1.2.5:
webpack-sources "^1.1.0" webpack-sources "^1.1.0"
worker-farm "^1.5.2" worker-farm "^1.5.2"
uglifyjs-webpack-plugin@^1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.6.tgz#f4bb44f02431e82b301d8d4624330a6a35729381"
dependencies:
cacache "^10.0.4"
find-cache-dir "^1.0.0"
schema-utils "^0.4.5"
serialize-javascript "^1.4.0"
source-map "^0.6.1"
uglify-es "^3.3.4"
webpack-sources "^1.1.0"
worker-farm "^1.5.2"
uid-number@0.0.6: uid-number@0.0.6:
version "0.0.6" version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"

Loading…
Cancel
Save