Browse Source

Merge branch 'develop' into LL-931

develop
meriadec 6 years ago
parent
commit
4abf811706
No known key found for this signature in database GPG Key ID: 1D2FC2305E2CB399
  1. 15
      package.json
  2. 15
      scripts/check-no-dups.sh
  3. 5
      src/commands/autoUpdate.js
  4. 3
      src/components/CurrentAddress/index.js
  5. 13
      src/components/GenuineCheckModal.js
  6. 4
      src/components/IsUnlocked.js
  7. 93
      src/components/ManagerPage/AppsList.js
  8. 5
      src/components/Onboarding/steps/GenuineCheck/GenuineCheckUnavailable.js
  9. 3
      src/components/SettingsPage/CleanButton.js
  10. 29
      src/components/SettingsPage/DisablePasswordModal.js
  11. 8
      src/components/SettingsPage/PasswordAutoLockSelect.js
  12. 1
      src/components/SettingsPage/PasswordForm.js
  13. 39
      src/components/SettingsPage/PasswordModal.js
  14. 14
      src/components/SettingsPage/RepairDeviceButton.js
  15. 3
      src/components/SettingsPage/ResetButton.js
  16. 2
      src/components/SettingsPage/ResetFallbackModal.js
  17. 4
      src/components/SettingsPage/sections/Display.js
  18. 35
      src/components/SettingsPage/sections/Export.js
  19. 51
      src/components/base/Modal/ConfirmModal.js
  20. 137
      src/components/base/Modal/ModalBody.js
  21. 59
      src/components/base/Modal/ModalContent.js
  22. 18
      src/components/base/Modal/ModalFooter.js
  23. 93
      src/components/base/Modal/ModalHeader.js
  24. 75
      src/components/base/Modal/ModalTitle.js
  25. 47
      src/components/base/Modal/RepairModal.js
  26. 337
      src/components/base/Modal/index.js
  27. 71
      src/components/base/Modal/stories.js
  28. 26
      src/components/base/Stepper/index.js
  29. 82
      src/components/modals/AccountSettingRenderBody.js
  30. 2
      src/components/modals/AddAccounts/index.js
  31. 26
      src/components/modals/Debug.js
  32. 43
      src/components/modals/Disclaimer.js
  33. 31
      src/components/modals/OperationDetails.js
  34. 5
      src/components/modals/Receive/index.js
  35. 26
      src/components/modals/ReleaseNotes/ReleaseNotesBody.js
  36. 1
      src/components/modals/ReleaseNotes/index.js
  37. 1
      src/components/modals/Send/index.js
  38. 5
      src/components/modals/SettingsAccount.js
  39. 54
      src/components/modals/ShareAnalytics.js
  40. 51
      src/components/modals/TechnicalData.js
  41. 38
      src/components/modals/UpdateFirmware/Disclaimer.js
  42. 1
      src/components/modals/UpdateFirmware/index.js
  43. 1
      src/index.ejs
  44. 1
      src/main/app.js
  45. 1
      src/reducers/modals.js
  46. 8
      static/i18n/en/app.json
  47. 82
      yarn.lock

15
package.json

@ -20,7 +20,7 @@
"test-e2e": "jest test-e2e", "test-e2e": "jest test-e2e",
"test-sync": "bash test-e2e/sync/launch.sh", "test-sync": "bash test-e2e/sync/launch.sh",
"prettier": "prettier --write \"{src,webpack,.storybook,test-e2e}/**/*.{js,json}\"", "prettier": "prettier --write \"{src,webpack,.storybook,test-e2e}/**/*.{js,json}\"",
"ci": "yarn lint && yarn flow && yarn prettier && yarn test", "ci": "yarn check --integrity && ./scripts/check-no-dups.sh && yarn lint && yarn flow && yarn prettier && yarn test",
"storybook": "NODE_ENV=development STORYBOOK_ENV=1 start-storybook -s ./static -p 4444", "storybook": "NODE_ENV=development STORYBOOK_ENV=1 start-storybook -s ./static -p 4444",
"publish-storybook": "bash ./scripts/legacy/publish-storybook.sh", "publish-storybook": "bash ./scripts/legacy/publish-storybook.sh",
"reset-files": "bash ./scripts/legacy/reset-files.sh" "reset-files": "bash ./scripts/legacy/reset-files.sh"
@ -36,11 +36,11 @@
}, },
"dependencies": { "dependencies": {
"@ledgerhq/errors": "^4.35.1", "@ledgerhq/errors": "^4.35.1",
"@ledgerhq/hw-app-btc": "^4.34.0", "@ledgerhq/hw-app-btc": "^4.35.0",
"@ledgerhq/hw-app-eth": "^4.32.0", "@ledgerhq/hw-app-eth": "^4.35.0",
"@ledgerhq/hw-app-xrp": "^4.32.0", "@ledgerhq/hw-app-xrp": "^4.35.0",
"@ledgerhq/hw-transport": "^4.32.0", "@ledgerhq/hw-transport": "^4.35.0",
"@ledgerhq/hw-transport-node-hid": "^4.32.0", "@ledgerhq/hw-transport-node-hid": "^4.35.0",
"@ledgerhq/ledger-core": "2.0.0-rc.16", "@ledgerhq/ledger-core": "2.0.0-rc.16",
"@ledgerhq/live-common": "4.15.0-beta.0", "@ledgerhq/live-common": "4.15.0-beta.0",
"animated": "^0.2.2", "animated": "^0.2.2",
@ -185,7 +185,8 @@
"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",
"yaml-loader": "^0.5.0" "yaml-loader": "^0.5.0",
"yarn-deduplicate": "^1.1.1"
}, },
"engines": { "engines": {
"node": ">=8.9.0 <=8.15.0", "node": ">=8.9.0 <=8.15.0",

15
scripts/check-no-dups.sh

@ -0,0 +1,15 @@
#!/bin/bash
yarn-deduplicate -l | grep \@ledgerhq
if [ $? -eq 0 ]; then
echo "Found duplicates in @ledgerhq/* – fix it with yarn-deduplicate"
exit 1
fi
yarn-deduplicate -l | grep \"react
if [ $? -eq 0 ]; then
echo "Found duplicates in some react packages – fix it with yarn-deduplicate"
exit 1
fi

5
src/commands/autoUpdate.js

@ -3,8 +3,9 @@
import { createCommand, Command } from 'helpers/ipc' import { createCommand, Command } from 'helpers/ipc'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { UPDATE_CHECK_IGNORE, UPDATE_CHECK_FEED } from 'config/constants' // import { UPDATE_CHECK_IGNORE, UPDATE_CHECK_FEED } from 'config/constants'
import createElectronAppUpdater from 'main/updater/createElectronAppUpdater' import { UPDATE_CHECK_IGNORE } from 'config/constants'
// import createElectronAppUpdater from 'main/updater/createElectronAppUpdater'
import type { UpdateStatus } from 'components/Updater/UpdaterContext' import type { UpdateStatus } from 'components/Updater/UpdaterContext'
type Input = {} type Input = {}

3
src/components/CurrentAddress/index.js

@ -145,8 +145,11 @@ class CurrentAddress extends PureComponent<Props, { copyFeedback: boolean }> {
componentWillUnmount() { componentWillUnmount() {
if (this._timeout) clearTimeout(this._timeout) if (this._timeout) clearTimeout(this._timeout)
this._isUnmounted = true
} }
_isUnmounted = false
renderCopy = copy => { renderCopy = copy => {
const { t } = this.props const { t } = this.props
return ( return (

13
src/components/GenuineCheckModal.js

@ -5,7 +5,7 @@ import { translate } from 'react-i18next'
import type { T } from 'types/common' import type { T } from 'types/common'
import Modal, { ModalBody, ModalTitle, ModalContent } from 'components/base/Modal' import Modal, { ModalBody } from 'components/base/Modal'
import GenuineCheck from 'components/GenuineCheck' import GenuineCheck from 'components/GenuineCheck'
type Props = { type Props = {
@ -19,12 +19,13 @@ class GenuineCheckModal extends PureComponent<Props> {
renderBody = ({ onClose }) => { renderBody = ({ onClose }) => {
const { t, onSuccess, onFail, onUnavailable } = this.props const { t, onSuccess, onFail, onUnavailable } = this.props
return ( return (
<ModalBody onClose={onClose}> <ModalBody
<ModalTitle>{t('genuinecheck.modal.title')}</ModalTitle> onClose={onClose}
<ModalContent> title={t('genuinecheck.modal.title')}
render={() => (
<GenuineCheck onSuccess={onSuccess} onFail={onFail} onUnavailable={onUnavailable} /> <GenuineCheck onSuccess={onSuccess} onFail={onFail} onUnavailable={onUnavailable} />
</ModalContent> )}
</ModalBody> />
) )
} }

4
src/components/IsUnlocked.js

@ -23,8 +23,8 @@ import Box from 'components/base/Box'
import InputPassword from 'components/base/InputPassword' import InputPassword from 'components/base/InputPassword'
import LedgerLiveLogo from 'components/base/LedgerLiveLogo' import LedgerLiveLogo from 'components/base/LedgerLiveLogo'
import IconArrowRight from 'icons/ArrowRight' import IconArrowRight from 'icons/ArrowRight'
import Button from './base/Button/index' import Button from 'components/base/Button/index'
import ConfirmModal from './base/Modal/ConfirmModal' import ConfirmModal from 'components/base/Modal/ConfirmModal'
type InputValue = { type InputValue = {
password: string, password: string,

93
src/components/ManagerPage/AppsList.js

@ -1,7 +1,7 @@
// @flow // @flow
/* eslint-disable react/jsx-no-literals */ // FIXME /* eslint-disable react/jsx-no-literals */ // FIXME
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { connect } from 'react-redux' import { connect } from 'react-redux'
@ -15,7 +15,7 @@ import { developerModeSelector } from 'reducers/settings'
import installApp from 'commands/installApp' import installApp from 'commands/installApp'
import uninstallApp from 'commands/uninstallApp' import uninstallApp from 'commands/uninstallApp'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Modal, { ModalBody, ModalFooter, ModalTitle, ModalContent } from 'components/base/Modal' import Modal from 'components/base/Modal'
import Tooltip from 'components/base/Tooltip' import Tooltip from 'components/base/Tooltip'
import Text from 'components/base/Text' import Text from 'components/base/Text'
import ProgressBar from 'components/ProgressBar' import ProgressBar from 'components/ProgressBar'
@ -31,6 +31,7 @@ import CheckCircle from 'icons/CheckCircle'
import { FreezeDeviceChangeEvents } from './HookDeviceChange' import { FreezeDeviceChangeEvents } from './HookDeviceChange'
import ManagerApp, { Container as FakeManagerAppContainer } from './ManagerApp' import ManagerApp, { Container as FakeManagerAppContainer } from './ManagerApp'
import AppSearchBar from './AppSearchBar' import AppSearchBar from './AppSearchBar'
import ModalBody from '../base/Modal/ModalBody'
const mapStateToProps = state => ({ const mapStateToProps = state => ({
isDevMode: developerModeSelector(state), isDevMode: developerModeSelector(state),
@ -183,46 +184,32 @@ class AppsList extends PureComponent<Props, State> {
handleCloseModal = () => this.setState({ status: 'idle', mode: 'home' }) handleCloseModal = () => this.setState({ status: 'idle', mode: 'home' })
renderModal = () => { renderBody = () => {
const { t } = this.props const { t } = this.props
const { app, status, error, mode, progress } = this.state const { app, status, error, mode, progress } = this.state
return (
<Modal return ['busy', 'idle'].includes(status) ? (
isOpened={status !== 'idle' && status !== 'loading'} <Box grow align="center" justify="center">
render={() => (
<ModalBody align="center" justify="center" style={{ height: 300 }}>
<FreezeDeviceChangeEvents />
{status === 'busy' || status === 'idle' ? (
<Fragment>
<ModalTitle>
{mode === 'installing' ? ( {mode === 'installing' ? (
<Box color="grey"> <Box color="grey" grow align="center" mb={5}>
<Update size={30} /> <Update size={30} />
</Box> </Box>
) : ( ) : (
<Box color="grey"> <Box color="grey" grow align="center" mb={5}>
<Trash size={30} /> <Trash size={30} />
</Box> </Box>
)} )}
</ModalTitle>
<ModalContent>
<Text ff="Museo Sans|Regular" fontSize={6} color="dark"> <Text ff="Museo Sans|Regular" fontSize={6} color="dark">
{t(`manager.apps.${mode}`, { app })} {t(`manager.apps.${mode}`, { app })}
</Text> </Text>
<Box mt={6}> <Box mt={6}>
<ProgressBar width={150} progress={progress} /> <ProgressBar width={150} progress={progress} />
</Box> </Box>
</ModalContent> </Box>
</Fragment>
) : status === 'error' ? ( ) : status === 'error' ? (
<Fragment> <Box>
<TrackPage <TrackPage category="Manager" name="Error Modal" error={error && error.name} app={app} />
category="Manager" <Box grow align="center" justify="center" mt={5}>
name="Error Modal"
error={error && error.name}
app={app}
/>
<ModalContent grow align="center" justify="center" mt={5}>
<Box color="alertRed"> <Box color="alertRed">
<ExclamationCircleThin size={44} /> <ExclamationCircleThin size={44} />
</Box> </Box>
@ -246,16 +233,10 @@ class AppsList extends PureComponent<Props, State> {
> >
<TranslatedError error={error} field="description" /> <TranslatedError error={error} field="description" />
</Box> </Box>
</ModalContent> </Box>
<ModalFooter horizontal justifyContent="flex-end" style={{ width: '100%' }}> </Box>
<Button primary onClick={this.handleCloseModal}>
{t('common.close')}
</Button>
</ModalFooter>
</Fragment>
) : status === 'success' ? ( ) : status === 'success' ? (
<Fragment> <Box grow align="center" justify="center" mt={5}>
<ModalContent grow align="center" justify="center" mt={5}>
<Box color="positiveGreen"> <Box color="positiveGreen">
<CheckCircle size={44} /> <CheckCircle size={44} />
</Box> </Box>
@ -267,24 +248,39 @@ class AppsList extends PureComponent<Props, State> {
textAlign="center" textAlign="center"
style={{ maxWidth: 350 }} style={{ maxWidth: 350 }}
> >
{t( {t(`manager.apps.${mode === 'installing' ? 'installSuccess' : 'uninstallSuccess'}`, {
`manager.apps.${ app,
mode === 'installing' ? 'installSuccess' : 'uninstallSuccess' })}
}`, </Box>
{ app },
)}
</Box> </Box>
</ModalContent> ) : null
<ModalFooter horizontal justifyContent="flex-end" style={{ width: '100%' }}> }
renderFooter = () => {
const { t } = this.props
return (
<Box horizontal justifyContent="flex-end" style={{ width: '100%' }}>
<Button primary onClick={this.handleCloseModal}> <Button primary onClick={this.handleCloseModal}>
{t('common.close')} {t('common.close')}
</Button> </Button>
</ModalFooter> </Box>
</Fragment> )
) : null} }
renderModal = () => {
const { status } = this.state
return (
<Modal isOpened={status !== 'idle' && status !== 'loading'} centered>
<ModalBody
align="center"
justify="center"
title={''}
render={this.renderBody}
renderFooter={['error', 'success'].includes(status) ? this.renderFooter : undefined}
>
<FreezeDeviceChangeEvents />
</ModalBody> </ModalBody>
)} </Modal>
/>
) )
} }
@ -325,6 +321,7 @@ class AppsList extends PureComponent<Props, State> {
render() { render() {
const { t } = this.props const { t } = this.props
return ( return (
<Box> <Box>
<Box mb={4} color="dark" ff="Museo Sans" fontSize={5} flow={2} horizontal align="center"> <Box mb={4} color="dark" ff="Museo Sans" fontSize={5} flow={2} horizontal align="center">

5
src/components/Onboarding/steps/GenuineCheck/GenuineCheckUnavailable.js

@ -31,6 +31,8 @@ export function GenuineCheckUnavailableFooter({
</Button> </Button>
<Box horizontal ml="auto"> <Box horizontal ml="auto">
<Button <Button
outline
outlineColor="alertRed"
disabled={false} disabled={false}
event="Onboarding Skip Genuine Check" event="Onboarding Skip Genuine Check"
onClick={() => nextStep()} onClick={() => nextStep()}
@ -38,9 +40,6 @@ export function GenuineCheckUnavailableFooter({
> >
{t('common.skipThisStep')} {t('common.skipThisStep')}
</Button> </Button>
<Button onClick={nextStep} disabled primary>
{t('common.continue')}
</Button>
</Box> </Box>
</OnboardingFooterWrapper> </OnboardingFooterWrapper>
) )

3
src/components/SettingsPage/CleanButton.js

@ -7,7 +7,7 @@ import logger from 'logger'
import type { T } from 'types/common' import type { T } from 'types/common'
import { cleanAccountsCache } from 'actions/accounts' import { cleanAccountsCache } from 'actions/accounts'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import { ConfirmModal } from 'components/base/Modal' import ConfirmModal from 'components/base/Modal/ConfirmModal'
import { softReset } from 'helpers/reset' import { softReset } from 'helpers/reset'
import ResetFallbackModal from './ResetFallbackModal' import ResetFallbackModal from './ResetFallbackModal'
@ -60,6 +60,7 @@ class CleanButton extends PureComponent<Props, State> {
<ConfirmModal <ConfirmModal
analyticsName="CleanCache" analyticsName="CleanCache"
centered
isOpened={opened} isOpened={opened}
onClose={this.close} onClose={this.close}
onReject={this.close} onReject={this.close}

29
src/components/SettingsPage/DisablePasswordModal.js

@ -8,7 +8,8 @@ import Box from 'components/base/Box'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import InputPassword from 'components/base/InputPassword' import InputPassword from 'components/base/InputPassword'
import Label from 'components/base/Label' import Label from 'components/base/Label'
import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from 'components/base/Modal' import Modal from 'components/base/Modal'
import ModalBody from 'components/base/Modal/ModalBody'
import type { T } from 'types/common' import type { T } from 'types/common'
@ -61,17 +62,12 @@ class DisablePasswordModal extends PureComponent<Props, State> {
const { t, onClose, ...props } = this.props const { t, onClose, ...props } = this.props
const { currentPassword, incorrectPassword } = this.state const { currentPassword, incorrectPassword } = this.state
return ( return (
<Modal <Modal {...props} centered onHide={this.handleReset} onClose={onClose}>
{...props}
onHide={this.handleReset}
onClose={onClose}
render={({ onClose }) => (
<form onSubmit={this.disablePassword}> <form onSubmit={this.disablePassword}>
<ModalBody onClose={onClose}> <ModalBody
<ModalTitle data-e2e="disablePassword_modalTitle"> onClose={onClose}
{t('password.disablePassword.title')} title={t('password.disablePassword.title')}
</ModalTitle> render={() => (
<ModalContent>
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" px={4}> <Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" px={4}>
{t('password.disablePassword.desc')} {t('password.disablePassword.desc')}
<Box px={7} mt={4} flow={3}> <Box px={7} mt={4} flow={3}>
@ -90,8 +86,9 @@ class DisablePasswordModal extends PureComponent<Props, State> {
</Box> </Box>
</Box> </Box>
</Box> </Box>
</ModalContent> )}
<ModalFooter horizontal align="center" justify="flex-end" flow={2}> renderFooter={() => (
<Box horizontal align="center" justify="flex-end" flow={2}>
<Button small type="button" onClick={onClose}> <Button small type="button" onClick={onClose}>
{t('common.cancel')} {t('common.cancel')}
</Button> </Button>
@ -103,11 +100,11 @@ class DisablePasswordModal extends PureComponent<Props, State> {
> >
{t('common.save')} {t('common.save')}
</Button> </Button>
</ModalFooter> </Box>
</ModalBody>
</form>
)} )}
/> />
</form>
</Modal>
) )
} }
} }

8
src/components/SettingsPage/PasswordAutoLockSelect.js

@ -30,10 +30,10 @@ class PasswordAutoLockSelect extends PureComponent<Props> {
} }
timeouts = [ timeouts = [
{ value: 1, label: `1 ${this.props.t('app:time.minute')}` }, { value: 1, label: `1 ${this.props.t('time.minute')}` },
{ value: 10, label: `10 ${this.props.t('app:time.minute')}s` }, { value: 10, label: `10 ${this.props.t('time.minute')}s` },
{ value: 30, label: `30 ${this.props.t('app:time.minute')}s` }, { value: 30, label: `30 ${this.props.t('time.minute')}s` },
{ value: 60, label: `1 ${this.props.t('app:time.hour')}` }, { value: 60, label: `1 ${this.props.t('time.hour')}` },
{ value: -1, label: this.props.t(`app:common.never`) }, { value: -1, label: this.props.t(`app:common.never`) },
] ]

1
src/components/SettingsPage/PasswordForm.js

@ -70,6 +70,7 @@ class PasswordForm extends PureComponent<Props> {
</Label> </Label>
<InputPassword <InputPassword
style={{ width: 240 }} style={{ width: 240 }}
onEnter={onSubmit}
id="confirmPassword" id="confirmPassword"
onChange={onChange('confirmPassword')} onChange={onChange('confirmPassword')}
value={confirmPassword} value={confirmPassword}

39
src/components/SettingsPage/PasswordModal.js

@ -1,6 +1,6 @@
// @flow // @flow
import React, { PureComponent } from 'react' import React, { Fragment, PureComponent } from 'react'
import type { T } from 'types/common' import type { T } from 'types/common'
@ -8,7 +8,7 @@ import db from 'helpers/db'
import { PasswordIncorrectError } from '@ledgerhq/errors' import { PasswordIncorrectError } from '@ledgerhq/errors'
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 { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from 'components/base/Modal' import Modal, { ModalBody } from 'components/base/Modal'
import PasswordForm from './PasswordForm' import PasswordForm from './PasswordForm'
@ -17,6 +17,7 @@ type Props = {
onClose: () => void, onClose: () => void,
onChangePassword: (?string) => void, onChangePassword: (?string) => void,
hasPassword: boolean, hasPassword: boolean,
isOpened: boolean,
} }
type State = { type State = {
@ -36,6 +37,12 @@ const INITIAL_STATE = {
class PasswordModal extends PureComponent<Props, State> { class PasswordModal extends PureComponent<Props, State> {
state = INITIAL_STATE state = INITIAL_STATE
componentWillReceiveProps(nextProps: Props) {
if (!nextProps.isOpened) {
this.setState(INITIAL_STATE)
}
}
handleSave = (e: SyntheticEvent<HTMLFormElement>) => { handleSave = (e: SyntheticEvent<HTMLFormElement>) => {
const { currentPassword, newPassword } = this.state const { currentPassword, newPassword } = this.state
@ -73,23 +80,17 @@ class PasswordModal extends PureComponent<Props, State> {
} }
render() { render() {
const { t, hasPassword, onClose, ...props } = this.props const { t, hasPassword, onClose, isOpened, ...props } = this.props
const { currentPassword, newPassword, incorrectPassword, confirmPassword } = this.state const { currentPassword, newPassword, incorrectPassword, confirmPassword } = this.state
return ( return (
<Modal <Modal isOpened={isOpened} centered>
<ModalBody
{...props} {...props}
title={hasPassword ? t('password.changePassword.title') : t('password.setPassword.title')}
onHide={this.handleReset} onHide={this.handleReset}
onClose={onClose} onClose={onClose}
render={({ onClose }) => ( render={() => (
<ModalBody onClose={onClose}> <Fragment>
{hasPassword ? (
<ModalTitle>{t('password.changePassword.title')}</ModalTitle>
) : (
<ModalTitle data-e2e="enablePassword_modal">
{t('password.setPassword.title')}
</ModalTitle>
)}
<ModalContent>
<Box <Box
ff="Museo Sans|Regular" ff="Museo Sans|Regular"
color="dark" color="dark"
@ -116,8 +117,10 @@ class PasswordModal extends PureComponent<Props, State> {
onChange={this.handleInputChange} onChange={this.handleInputChange}
t={t} t={t}
/> />
</ModalContent> </Fragment>
<ModalFooter horizontal align="center" justify="flex-end" flow={2}> )}
renderFooter={() => (
<Box horizontal align="center" justify="flex-end" flow={2}>
<Button <Button
small small
type="button" type="button"
@ -134,10 +137,10 @@ class PasswordModal extends PureComponent<Props, State> {
> >
{t('common.save')} {t('common.save')}
</Button> </Button>
</ModalFooter> </Box>
</ModalBody>
)} )}
/> />
</Modal>
) )
} }
} }

14
src/components/SettingsPage/RepairDeviceButton.js

@ -11,7 +11,7 @@ import logger from 'logger'
import type { T } from 'types/common' import type { T } from 'types/common'
import firmwareRepair from 'commands/firmwareRepair' import firmwareRepair from 'commands/firmwareRepair'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import { RepairModal } from 'components/base/Modal' import RepairModal from 'components/base/Modal/RepairModal'
type Props = { type Props = {
t: T, t: T,
@ -33,28 +33,38 @@ class RepairDeviceButton extends PureComponent<Props, State> {
progress: 0, progress: 0,
} }
componentWillUnmount() {
if (this.timeout) {
clearTimeout(this.timeout)
}
}
open = () => this.setState({ opened: true, error: null }) open = () => this.setState({ opened: true, error: null })
sub: * sub: *
timeout: *
close = () => { close = () => {
if (this.sub) this.sub.unsubscribe() if (this.sub) this.sub.unsubscribe()
if (this.timeout) clearTimeout(this.timeout)
this.setState({ opened: false, isLoading: false, error: null, progress: 0 }) this.setState({ opened: false, isLoading: false, error: null, progress: 0 })
} }
repair = (version = null) => { repair = (version = null) => {
if (this.state.isLoading) return if (this.state.isLoading) return
const { push } = this.props const { push } = this.props
this.setState({ isLoading: true }) this.timeout = setTimeout(() => this.setState({ isLoading: true }), 500)
this.sub = firmwareRepair.send({ version }).subscribe({ this.sub = firmwareRepair.send({ version }).subscribe({
next: patch => { next: patch => {
this.setState(patch) this.setState(patch)
}, },
error: error => { error: error => {
logger.critical(error) logger.critical(error)
if (this.timeout) clearTimeout(this.timeout)
this.setState({ error, isLoading: false, progress: 0 }) this.setState({ error, isLoading: false, progress: 0 })
}, },
complete: () => { complete: () => {
if (this.timeout) clearTimeout(this.timeout)
this.setState({ opened: false, isLoading: false, progress: 0 }, () => { this.setState({ opened: false, isLoading: false, progress: 0 }, () => {
push('/manager') push('/manager')
}) })

3
src/components/SettingsPage/ResetButton.js

@ -9,7 +9,7 @@ import type { T } from 'types/common'
import { hardReset } from 'helpers/reset' import { hardReset } from 'helpers/reset'
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 { ConfirmModal } from 'components/base/Modal' import ConfirmModal from 'components/base/Modal/ConfirmModal'
import IconTriangleWarning from 'icons/TriangleWarning' import IconTriangleWarning from 'icons/TriangleWarning'
import ResetFallbackModal from './ResetFallbackModal' import ResetFallbackModal from './ResetFallbackModal'
@ -58,6 +58,7 @@ class ResetButton extends PureComponent<Props, State> {
<ConfirmModal <ConfirmModal
analyticsName="HardReset" analyticsName="HardReset"
isDanger isDanger
centered
isLoading={pending} isLoading={pending}
isOpened={opened} isOpened={opened}
onClose={this.close} onClose={this.close}

2
src/components/SettingsPage/ResetFallbackModal.js

@ -3,7 +3,7 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { ConfirmModal } from 'components/base/Modal' import ConfirmModal from 'components/base/Modal/ConfirmModal'
import { openUserDataFolderAndQuit } from 'helpers/reset' import { openUserDataFolderAndQuit } from 'helpers/reset'
type Props = { type Props = {

4
src/components/SettingsPage/sections/Display.js

@ -91,8 +91,8 @@ class TabGeneral extends PureComponent<Props> {
</Row> </Row>
{hasPassword ? ( {hasPassword ? (
<Row <Row
title={t('app:settings.profile.passwordAutoLock')} title={t('settings.profile.passwordAutoLock')}
desc={t('app:settings.profile.passwordAutoLockDesc')} desc={t('settings.profile.passwordAutoLockDesc')}
> >
<PasswordAutoLockSelect /> <PasswordAutoLockSelect />
</Row> </Row>

35
src/components/SettingsPage/sections/Export.js

@ -11,7 +11,8 @@ import { SettingsSection as Section, SettingsSectionHeader as Header } from '../
import { EXPERIMENTAL_WS_EXPORT } from '../../../config/constants' import { EXPERIMENTAL_WS_EXPORT } from '../../../config/constants'
import IconShare from '../../../icons/Share' import IconShare from '../../../icons/Share'
import Button from '../../base/Button' import Button from '../../base/Button'
import Modal, { ModalBody, ModalContent, ModalFooter, ModalTitle } from '../../base/Modal' import Modal from '../../base/Modal'
import ModalBody from '../../base/Modal/ModalBody'
import Box from '../../base/Box' import Box from '../../base/Box'
import QRCodeExporter from '../../QRCodeExporter' import QRCodeExporter from '../../QRCodeExporter'
import { BulletRow } from '../../Onboarding/helperComponents' import { BulletRow } from '../../Onboarding/helperComponents'
@ -68,7 +69,10 @@ class SectionExport extends PureComponent<Props, State> {
<Text ff="Open Sans|SemiBold" color="dark"> <Text ff="Open Sans|SemiBold" color="dark">
{'+'} {'+'}
</Text> </Text>
{'button in Accounts'} {'button in'}
<Text ff="Open Sans|SemiBold" color="dark">
{'Accounts'}
</Text>
</Trans> </Trans>
</Box> </Box>
), ),
@ -92,16 +96,24 @@ class SectionExport extends PureComponent<Props, State> {
icon: <BulletRowIcon>{'3'}</BulletRowIcon>, icon: <BulletRowIcon>{'3'}</BulletRowIcon>,
desc: ( desc: (
<Box style={{ display: 'block' }}> <Box style={{ display: 'block' }}>
<Trans i18nKey="settings.export.modal.step3" /> <Trans i18nKey="settings.export.modal.step3">
{'Scan the'}
<Text ff="Open Sans|SemiBold" color="dark">
{'LiveQR Code'}
</Text>
{'until the loader hits 100%'}
</Trans>
</Box> </Box>
), ),
}, },
] ]
return ( return (
<ModalBody onClose={onClose}> <ModalBody
<ModalTitle>{t('settings.export.modal.title')}</ModalTitle> onClose={onClose}
<ModalContent flow={2} justify="center" align="center"> title={t('settings.export.modal.title')}
render={() => (
<Box justify="center" align="center">
<Box flow={2}> <Box flow={2}>
<QRCodeExporter size={330} /> <QRCodeExporter size={330} />
</Box> </Box>
@ -113,13 +125,16 @@ class SectionExport extends PureComponent<Props, State> {
<Box style={{ width: 330 }}> <Box style={{ width: 330 }}>
{stepsImportMobile.map(step => <BulletRow key={step.key} step={step} />)} {stepsImportMobile.map(step => <BulletRow key={step.key} step={step} />)}
</Box> </Box>
</ModalContent> </Box>
<ModalFooter horizontal align="center" justify="flex-end" flow={2}> )}
renderFooter={() => (
<Box>
<Button small onClick={onClose} primary> <Button small onClick={onClose} primary>
{t('settings.export.modal.button')} {t('settings.export.modal.button')}
</Button> </Button>
</ModalFooter> </Box>
</ModalBody> )}
/>
) )
} }

51
src/components/base/Modal/ConfirmModal.js

@ -9,7 +9,8 @@ import TrackPage from 'analytics/TrackPage'
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 { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from './index' import Modal from './index'
import ModalBody from './ModalBody'
type Props = { type Props = {
isOpened: boolean, isOpened: boolean,
@ -21,11 +22,13 @@ type Props = {
confirmText?: string, confirmText?: string,
cancelText?: string, cancelText?: string,
onReject: Function, onReject: Function,
onClose?: Function,
onConfirm: Function, onConfirm: Function,
t: T, t: T,
isLoading?: boolean, isLoading?: boolean,
analyticsName: string, analyticsName: string,
cancellable?: boolean, cancellable?: boolean,
centered?: boolean,
} }
class ConfirmModal extends PureComponent<Props> { class ConfirmModal extends PureComponent<Props> {
@ -43,23 +46,38 @@ class ConfirmModal extends PureComponent<Props> {
onConfirm, onConfirm,
isLoading, isLoading,
renderIcon, renderIcon,
onClose,
t, t,
analyticsName, analyticsName,
centered,
...props ...props
} = this.props } = this.props
const realConfirmText = confirmText || t('common.confirm') const realConfirmText = confirmText || t('common.confirm')
const realCancelText = cancelText || t('common.cancel') const realCancelText = cancelText || t('common.cancel')
return ( return (
<Modal <Modal isOpened={isOpened} centered={centered}>
isOpened={isOpened} <ModalBody
preventBackdropClick={isLoading} preventBackdropClick={isLoading}
{...props} {...props}
render={({ onClose }) => ( onClose={!cancellable && isLoading ? undefined : onClose}
<ModalBody onClose={!cancellable && isLoading ? undefined : onClose}> title={title}
<TrackPage category="Modal" name={analyticsName} /> renderFooter={() => (
<ModalTitle>{title}</ModalTitle> <Box horizontal align="center" justify="flex-end" flow={2}>
<ModalContent> {!isLoading && <Button onClick={onReject}>{realCancelText}</Button>}
<Button
onClick={onConfirm}
primary={!isDanger}
danger={isDanger}
isLoading={isLoading}
disabled={isLoading}
>
{realConfirmText}
</Button>
</Box>
)}
render={() => (
<Box>
{subTitle && ( {subTitle && (
<Box ff="Museo Sans|Regular" color="dark" textAlign="center" mb={2} mt={3}> <Box ff="Museo Sans|Regular" color="dark" textAlign="center" mb={2} mt={3}>
{subTitle} {subTitle}
@ -73,22 +91,11 @@ class ConfirmModal extends PureComponent<Props> {
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center"> <Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center">
{desc} {desc}
</Box> </Box>
</ModalContent> </Box>
<ModalFooter horizontal align="center" justify="flex-end" flow={2}>
{!isLoading && <Button onClick={onReject}>{realCancelText}</Button>}
<Button
onClick={onConfirm}
primary={!isDanger}
danger={isDanger}
isLoading={isLoading}
disabled={isLoading}
>
{realConfirmText}
</Button>
</ModalFooter>
</ModalBody>
)} )}
/> />
<TrackPage category="Modal" name={analyticsName} />
</Modal>
) )
} }
} }

137
src/components/base/Modal/ModalBody.js

@ -1,95 +1,102 @@
// @flow // @flow
import React, { PureComponent } from 'react' import React, { PureComponent, Fragment } from 'react'
import styled, { keyframes } from 'styled-components' import Animated from 'animated/lib/targets/react-dom'
import { findDOMNode } from 'react-dom'
import Box from 'components/base/Box' import ModalContent from './ModalContent'
import IconCross from 'icons/Cross' import ModalHeader from './ModalHeader'
import ModalFooter from './ModalFooter'
export const Container = styled(Box).attrs({ import type { RenderProps } from './index'
px: 5,
pb: 5,
})``
type Props = { type Props = {
deferHeight?: number, title: string,
onClose?: Function, onBack?: void => void,
children: any, onClose?: void => void,
render?: (?RenderProps) => any,
renderFooter?: (?RenderProps) => any,
renderProps?: RenderProps,
noScroll?: boolean,
refocusWhenChange?: any,
} }
type State = { type State = {
isHidden: boolean, animGradient: Animated.Value,
} }
class ModalBody extends PureComponent<Props, State> { class ModalBody extends PureComponent<Props, State> {
static defaultProps = { state = {
onClose: undefined, animGradient: new Animated.Value(0),
} }
state = { componentDidUpdate(prevProps: Props) {
isHidden: true, const shouldFocus = prevProps.refocusWhenChange !== this.props.refocusWhenChange
if (shouldFocus) {
if (this._content) {
const node = findDOMNode(this._content) // eslint-disable-line react/no-find-dom-node
if (node) {
// $FlowFixMe
node.focus()
}
}
} }
}
_content = null
componentDidMount() { animateGradient = (isScrollable: boolean) => {
setTimeout(() => { const anim = {
window.requestAnimationFrame(() => { duration: 150,
this.setState({ isHidden: false }) toValue: isScrollable ? 1 : 0,
}) }
}, 150) Animated.timing(this.state.animGradient, anim).start()
} }
render() { render() {
const { children, onClose, deferHeight, ...props } = this.props const { onBack, onClose, title, render, renderFooter, renderProps, noScroll } = this.props
const { isHidden } = this.state const { animGradient } = this.state
const gradientStyle = {
...GRADIENT_STYLE,
opacity: animGradient,
}
return ( return (
<Body <Fragment>
style={{ height: isHidden && deferHeight ? deferHeight : undefined }} <ModalHeader onBack={onBack} onClose={onClose}>
data-e2e="modalBody" {title}
</ModalHeader>
<ModalContent
tabIndex={0}
ref={n => (this._content = n)}
onIsScrollableChange={this.animateGradient}
noScroll={noScroll}
> >
{onClose && ( {render && render(renderProps)}
<CloseContainer onClick={onClose}> </ModalContent>
<IconCross size={16} /> <div style={GRADIENT_WRAPPER_STYLE}>
</CloseContainer> <Animated.div style={gradientStyle} />
)} </div>
{(!isHidden || !deferHeight) && <Inner {...props}>{children}</Inner>} {renderFooter && <ModalFooter>{renderFooter(renderProps)}</ModalFooter>}
</Body> </Fragment>
) )
} }
} }
const CloseContainer = styled(Box).attrs({ const GRADIENT_STYLE = {
p: 4, background: 'linear-gradient(rgba(255, 255, 255, 0), #ffffff)',
color: 'fog', height: 40,
})` position: 'absolute',
position: absolute; bottom: 0,
top: 0; left: 0,
right: 0; right: 20,
z-index: 1;
&:hover {
color: ${p => p.theme.colors.grey};
} }
&:active { const GRADIENT_WRAPPER_STYLE = {
color: ${p => p.theme.colors.dark}; height: 0,
position: 'relative',
pointerEvents: 'none',
} }
`
const Body = styled(Box).attrs({
bg: p => p.theme.colors.white,
relative: true,
borderRadius: 1,
})`
box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.2);
`
const appear = keyframes`
from { opacity: 0; }
to { opacity: 1; }
`
const Inner = styled(Box)`
animation: ${appear} 80ms linear;
`
export default ModalBody export default ModalBody

59
src/components/base/Modal/ModalContent.js

@ -0,0 +1,59 @@
// @flow
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
import React, { PureComponent } from 'react'
class ModalContent extends PureComponent<{
children: any,
onIsScrollableChange: boolean => void,
noScroll?: boolean,
}> {
componentDidMount() {
window.requestAnimationFrame(() => {
if (this._isUnmounted) return
this.showHideGradient()
if (this._outer) {
const ro = new ResizeObserver(this.showHideGradient)
ro.observe(this._outer)
}
})
}
componentWillUnmount() {
this._isUnmounted = true
}
_outer = null
_isUnmounted = false
showHideGradient = () => {
if (!this._outer) return
const { onIsScrollableChange } = this.props
const isScrollable = this._outer.scrollHeight > this._outer.clientHeight
onIsScrollableChange(isScrollable)
}
render() {
const { children, noScroll } = this.props
const contentStyle = {
...CONTENT_STYLE,
overflow: noScroll ? 'visible' : 'auto',
}
return (
<div style={contentStyle} ref={n => (this._outer = n)} tabIndex={0}>
{children}
</div>
)
}
}
const CONTENT_STYLE = {
flexShrink: 1,
padding: 20,
paddingBottom: 40,
}
export default ModalContent

18
src/components/base/Modal/ModalFooter.js

@ -0,0 +1,18 @@
// @flow
import React from 'react'
import { colors } from 'styles/theme'
const MODAL_FOOTER_STYLE = {
display: 'flex',
justifyContent: 'flex-end',
borderTop: `2px solid ${colors.lightGrey}`,
padding: 20,
}
const ModalFooter = ({ children }: { children: any }) => (
<div style={MODAL_FOOTER_STYLE}>{children}</div>
)
export default ModalFooter

93
src/components/base/Modal/ModalHeader.js

@ -0,0 +1,93 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
import Box from 'components/base/Box'
import IconAngleLeft from 'icons/AngleLeft'
import IconCross from 'icons/Cross'
const MODAL_HEADER_STYLE = {
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: 20,
}
const ModalTitle = styled(Box).attrs({
color: 'dark',
ff: 'Museo Sans|Regular',
fontSize: 6,
grow: true,
shrink: true,
})`
text-align: center;
line-height: 1;
`
const iconAngleLeft = <IconAngleLeft size={16} />
const iconCross = <IconCross size={16} />
const ModalHeaderAction = styled(Box).attrs({
horizontal: true,
align: 'center',
fontSize: 3,
p: 4,
color: 'grey',
})`
position: absolute;
top: 0;
left: ${p => (p.right ? 'auto' : 0)};
right: ${p => (p.right ? 0 : 'auto')};
line-height: 0;
cursor: pointer;
&:hover {
color: ${p => p.theme.colors.graphite};
}
&:active {
color: ${p => p.theme.colors.dark};
}
span {
border-bottom: 1px dashed transparent;
}
&:focus span {
border-bottom-color: inherit;
}
`
const ModalHeader = ({
children,
onBack,
onClose,
t,
}: {
children: any,
onBack: void => void,
onClose: void => void,
t: T,
}) => (
<div style={MODAL_HEADER_STYLE}>
{onBack && (
<ModalHeaderAction onClick={onBack}>
{iconAngleLeft}
<span>{t('common.back')}</span>
</ModalHeaderAction>
)}
<ModalTitle>{children}</ModalTitle>
{onClose && (
<ModalHeaderAction right color="fog" onClick={onClose}>
{iconCross}
</ModalHeaderAction>
)}
</div>
)
export default translate()(ModalHeader)

75
src/components/base/Modal/ModalTitle.js

@ -1,75 +0,0 @@
// @flow
import React from 'react'
import styled from 'styled-components'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
import Box from 'components/base/Box'
import IconAngleLeft from 'icons/AngleLeft'
const Container = styled(Box).attrs({
alignItems: 'center',
color: 'dark',
ff: 'Museo Sans|Regular',
fontSize: 6,
justifyContent: 'center',
p: 5,
relative: true,
})``
const Back = styled(Box).attrs({
unstyled: true,
horizontal: true,
align: 'center',
color: 'grey',
ff: 'Open Sans',
fontSize: 3,
p: 4,
})`
position: absolute;
line-height: 1;
top: 0;
left: 0;
&:hover {
color: ${p => p.theme.colors.graphite};
}
&:active {
color: ${p => p.theme.colors.dark};
}
span {
border-bottom: 1px dashed transparent;
}
&:focus span {
border-bottom-color: inherit;
}
`
function ModalTitle({
t,
onBack,
children,
...props
}: {
t: T,
onBack: any => void,
children: any,
}) {
return (
<Container {...props} data-e2e="modal_title">
{onBack && (
<Back onClick={onBack}>
<IconAngleLeft size={16} />
<span>{t('common.back')}</span>
</Back>
)}
{children}
</Container>
)
}
export default translate()(ModalTitle)

47
src/components/base/Modal/RepairModal.js

@ -17,7 +17,8 @@ import ProgressCircle from 'components/ProgressCircle'
import TranslatedError from 'components/TranslatedError' import TranslatedError from 'components/TranslatedError'
import ExclamationCircleThin from 'icons/ExclamationCircleThin' import ExclamationCircleThin from 'icons/ExclamationCircleThin'
import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from './index' import Modal from './index'
import ModalBody from './ModalBody'
const Container = styled(Box).attrs({ const Container = styled(Box).attrs({
alignItems: 'center', alignItems: 'center',
@ -39,18 +40,18 @@ const Separator = styled(Box).attrs({
` `
const DisclaimerStep = ({ desc }: { desc?: string }) => ( const DisclaimerStep = ({ desc }: { desc?: string }) => (
<ModalContent> <Box>
{desc ? ( {desc ? (
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" mb={2}> <Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" mb={2}>
{desc} {desc}
</Box> </Box>
) : null} ) : null}
</ModalContent> </Box>
) )
const FlashStep = ({ progress, t }: { progress: number, t: * }) => const FlashStep = ({ progress, t }: { progress: number, t: * }) =>
progress === 0 ? ( progress === 0 ? (
<ModalContent> <Box>
<Box mx={7}> <Box mx={7}>
<Text ff="Open Sans|Regular" align="center" color="smoke"> <Text ff="Open Sans|Regular" align="center" color="smoke">
<Bullet>{'1.'}</Bullet> <Bullet>{'1.'}</Bullet>
@ -74,9 +75,9 @@ const FlashStep = ({ progress, t }: { progress: number, t: * }) =>
alt={t('manager.modal.mcuFirst')} alt={t('manager.modal.mcuFirst')}
/> />
</Box> </Box>
</ModalContent> </Box>
) : ( ) : (
<ModalContent> <Box>
<Box mx={7} align="center"> <Box mx={7} align="center">
<ProgressCircle size={64} progress={progress} /> <ProgressCircle size={64} progress={progress} />
</Box> </Box>
@ -88,11 +89,11 @@ const FlashStep = ({ progress, t }: { progress: number, t: * }) =>
{t('manager.modal.mcuPin')} {t('manager.modal.mcuPin')}
</Text> </Text>
</Box> </Box>
</ModalContent> </Box>
) )
const ErrorStep = ({ error }: { error: Error }) => ( const ErrorStep = ({ error }: { error: Error }) => (
<ModalContent> <Box>
<Container> <Container>
<Box color="alertRed"> <Box color="alertRed">
<ExclamationCircleThin size={44} /> <ExclamationCircleThin size={44} />
@ -118,7 +119,7 @@ const ErrorStep = ({ error }: { error: Error }) => (
<TranslatedError error={error} field="description" /> <TranslatedError error={error} field="description" />
</Box> </Box>
</Container> </Container>
</ModalContent> </Box>
) )
type Props = { type Props = {
@ -177,12 +178,16 @@ class RepairModal extends PureComponent<Props, *> {
return ( return (
<Modal <Modal
isOpened={isOpened} isOpened={isOpened}
centered
preventBackdropClick={isLoading} preventBackdropClick={isLoading}
onClose={!cancellable && isLoading ? undefined : onReject}
{...props} {...props}
render={({ onClose }) => ( >
<ModalBody onClose={!cancellable && isLoading ? undefined : onClose}>
<TrackPage category="Modal" name={analyticsName} /> <TrackPage category="Modal" name={analyticsName} />
<ModalTitle>{title}</ModalTitle> <ModalBody
title={title}
render={() => (
<Box>
{error ? ( {error ? (
<ErrorStep error={error} /> <ErrorStep error={error} />
) : isLoading ? ( ) : isLoading ? (
@ -205,10 +210,12 @@ class RepairModal extends PureComponent<Props, *> {
/> />
</Box> </Box>
) : null} ) : null}
</Box>
{!isLoading ? ( )}
<ModalFooter horizontal align="center" justify="flex-end" flow={2}> renderFooter={() =>
{error ? <Button onClick={onReject}>{t(`common.close`)}</Button> : null} !isLoading ? (
<Box horizontal align="center" justify="flex-end" flow={2}>
<Button onClick={onReject}>{t(`common.${error ? 'close' : 'cancel'}`)}</Button>
{error ? null : ( {error ? null : (
<> <>
<Button <Button
@ -222,11 +229,11 @@ class RepairModal extends PureComponent<Props, *> {
</Button> </Button>
</> </>
)} )}
</ModalFooter> </Box>
) : null} ) : null
</ModalBody> }
)}
/> />
</Modal>
) )
} }
} }

337
src/components/base/Modal/index.js

@ -1,54 +1,28 @@
// @flow // @flow
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable react/no-multi-comp */ /* eslint-disable jsx-a11y/click-events-have-key-events */
import React, { Component } from 'react' import React, { PureComponent, Fragment } from 'react'
import { findDOMNode } from 'react-dom' import { createPortal } from 'react-dom'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import Mortal from 'react-mortal'
import styled from 'styled-components'
import noop from 'lodash/noop' import noop from 'lodash/noop'
import { EXPERIMENTAL_CENTER_MODAL } from 'config/constants' import Animated from 'animated/lib/targets/react-dom'
import Easing from 'animated/lib/Easing'
import { rgba } from 'styles/helpers'
import { radii } from 'styles/theme'
import { closeModal, isModalOpened, getModalData } from 'reducers/modals' import { closeModal, isModalOpened, getModalData } from 'reducers/modals'
import { colors } from 'styles/theme'
import Box from 'components/base/Box'
import GrowScroll from 'components/base/GrowScroll'
export { default as ModalBody } from './ModalBody' export { default as ModalBody } from './ModalBody'
export { default as ConfirmModal } from './ConfirmModal'
export { default as RepairModal } from './RepairModal'
export { default as ModalTitle } from './ModalTitle'
const springConfig = { const animShowHide = {
stiffness: 320, duration: 200,
} easing: Easing.bezier(0.3, 1.0, 0.5, 0.8),
type OwnProps = {
name?: string, // eslint-disable-line
isOpened?: boolean,
onBeforeOpen?: ({ data: * }) => *, // eslint-disable-line
onClose?: () => void,
onHide?: () => void,
preventBackdropClick?: boolean,
render: Function,
refocusWhenChange?: string,
width?: string,
} }
type Props = OwnProps & { const domNode = process.env.STORYBOOK_ENV ? document.body : document.getElementById('modals')
isOpened?: boolean,
data?: any,
} & {
onClose?: () => void,
}
const mapStateToProps = (state, { name, isOpened, onBeforeOpen }: OwnProps): * => { const mapStateToProps = (state, { name, isOpened, onBeforeOpen }: Props): * => {
const data = getModalData(state, name || '') const data = getModalData(state, name || '')
const modalOpened = isOpened || (name && isModalOpened(state, name)) const modalOpened = isOpened || (name && isModalOpened(state, name))
@ -62,7 +36,7 @@ const mapStateToProps = (state, { name, isOpened, onBeforeOpen }: OwnProps): * =
} }
} }
const mapDispatchToProps = (dispatch: *, { name, onClose = noop }: OwnProps): * => ({ const mapDispatchToProps = (dispatch: *, { name, onClose = noop }: Props): * => ({
onClose: name onClose: name
? () => { ? () => {
dispatch(closeModal(name)) dispatch(closeModal(name))
@ -71,178 +45,187 @@ const mapDispatchToProps = (dispatch: *, { name, onClose = noop }: OwnProps): *
: onClose, : onClose,
}) })
const Container = styled(Box).attrs({ export type RenderProps = {
color: 'grey', onClose?: void => void,
sticky: true, data: any,
style: p => ({
pointerEvents: p.isVisible ? 'auto' : 'none',
}),
})`
position: fixed;
z-index: 30;
`
const Backdrop = styled(Box).attrs({
bg: p => rgba(p.theme.colors.black, 0.4),
sticky: true,
style: p => ({
opacity: p.op,
}),
})`
position: fixed;
`
const NonClickableHeadArea = styled.div`
position: fixed;
height: 48px;
width: 100%;
top: 0;
left: 0;
z-index: 1;
`
const Wrapper = styled(Box).attrs({
bg: 'transparent',
flow: 4,
style: p => ({
opacity: p.op,
transform: `scale3d(${p.scale}, ${p.scale}, ${p.scale})`,
}),
})`
outline: none;
width: ${p => (p.width ? p.width : '500px')};
z-index: 2;
`
class Pure extends Component<any> {
shouldComponentUpdate(nextProps) {
if (nextProps.isAnimated) {
return false
}
return true
} }
render() { type Props = {
const { data, onClose, render } = this.props isOpened?: boolean,
children?: any,
centered?: boolean,
onClose?: void => void,
onHide?: void => void,
render?: RenderProps => any,
data?: any,
preventBackdropClick?: boolean,
return render({ data, onClose }) name?: string, // eslint-disable-line
onBeforeOpen?: ({ data: * }) => *, // eslint-disable-line
} }
type State = {
animShowHide: Animated.Value,
isInDOM: boolean,
} }
function stopPropagation(e) { class Modal extends PureComponent<Props, State> {
e.stopPropagation() state = {
animShowHide: new Animated.Value(0),
isInDOM: this.props.isOpened === true,
} }
const wrap = EXPERIMENTAL_CENTER_MODAL static getDerivedStateFromProps(nextProps: Props) {
? children => ( const patch = {}
<Box alignItems="center" justifyContent="center" grow> if (nextProps.isOpened) {
{children} patch.isInDOM = true
</Box> }
) return patch
: children => ( }
<GrowScroll alignItems="center" full pt={8}>
{children}
</GrowScroll>
)
export class Modal extends Component<Props> { componentDidMount() {
static defaultProps = { if (this.props.isOpened) {
isOpened: false, this.animateEnter()
onHide: noop,
preventBackdropClick: false,
} }
shouldComponentUpdate(nextProps: Props) { this.state.animShowHide.addListener(({ value }) => {
if (this.props.isOpened || nextProps.isOpened) { if (value === 0) {
return true const { onHide } = this.props
this.setState({ isInDOM: false })
if (onHide) {
onHide()
}
} }
if (value === 1) this.setState({ isInDOM: true })
})
return false document.addEventListener('keyup', this.handleKeyup)
} }
componentDidUpdate(prevProps: Props) { componentDidUpdate(prevProps: Props) {
const didOpened = this.props.isOpened && !prevProps.isOpened const didOpened = !prevProps.isOpened && this.props.isOpened
const didClose = !this.props.isOpened && prevProps.isOpened const didClosed = prevProps.isOpened && !this.props.isOpened
const shouldFocus = didOpened || this.props.refocusWhenChange !== prevProps.refocusWhenChange
if (didOpened) { if (didOpened) {
// Store a reference to the last active element, to restore it after this.animateEnter()
// modal close
this._lastFocusedElement = document.activeElement
}
if (shouldFocus) {
this.focusWrapper()
} }
if (didClose) { if (didClosed) {
if (this._lastFocusedElement) { this.animateLeave()
this._lastFocusedElement.focus()
} }
} }
componentWillUnmount() {
document.removeEventListener('keyup', this.handleKeyup)
} }
_wrapper = null handleKeyup = (e: KeyboardEvent) => {
_lastFocusedElement = null const { onClose, preventBackdropClick } = this.props
if (e.which === 27 && onClose && !preventBackdropClick) {
onClose()
}
}
focusWrapper = () => { handleClickOnBackdrop = () => {
// Forced to use findDOMNode here, because innerRef is giving a proxied component const { preventBackdropClick, onClose } = this.props
const domWrapper = findDOMNode(this._wrapper) // eslint-disable-line react/no-find-dom-node if (!preventBackdropClick && onClose) {
if (domWrapper instanceof HTMLDivElement && !domWrapper.contains(this._lastFocusedElement)) { onClose()
domWrapper.focus() }
} }
swallowClick = e => {
e.preventDefault()
e.stopPropagation()
} }
animateEnter = () =>
Animated.timing(this.state.animShowHide, { ...animShowHide, toValue: 1 }).start()
animateLeave = () =>
Animated.timing(this.state.animShowHide, { ...animShowHide, toValue: 0 }).start()
render() { render() {
const { preventBackdropClick, isOpened, onHide, render, data, onClose, width } = this.props const { animShowHide, isInDOM } = this.state
const { children, render, centered, onClose, data, isOpened } = this.props
return (
<Mortal if (!isInDOM) {
isOpened={isOpened} return null
onClose={onClose} }
onHide={onHide}
closeOnEsc={!preventBackdropClick} const backdropStyle = {
motionStyle={(spring, isVisible) => ({ ...BACKDROP_STYLE,
opacity: spring(isVisible ? 1 : 0, springConfig), opacity: animShowHide,
scale: spring(isVisible ? 1 : 0.95, springConfig), }
})}
> const containerStyle = {
{(m, isVisible, isAnimated) => ( ...CONTAINER_STYLE,
<Container isVisible={isVisible} onClick={preventBackdropClick ? undefined : onClose}> justifyContent: centered ? 'center' : 'flex-start',
<Backdrop op={m.opacity} /> pointerEvents: isOpened ? 'auto' : 'none',
<NonClickableHeadArea onClick={stopPropagation} /> }
{wrap(
<Wrapper const scale = animShowHide.interpolate({
tabIndex={-1} inputRange: [0, 1],
op={m.opacity} outputRange: [1.1, 1],
scale={m.scale} clamp: true,
innerRef={n => (this._wrapper = n)} })
onClick={stopPropagation}
width={width} const bodyWrapperStyle = {
> ...BODY_WRAPPER_STYLE,
<Pure isAnimated={isAnimated} render={render} data={data} onClose={onClose} /> opacity: animShowHide,
</Wrapper>, transform: [{ scale }],
)} }
</Container>
)} const renderProps = {
</Mortal> onClose,
data,
}
const modal = (
<Fragment>
<Animated.div style={backdropStyle} />
<div style={containerStyle} onClick={this.handleClickOnBackdrop}>
<Animated.div style={bodyWrapperStyle} onClick={this.swallowClick}>
{render && render(renderProps)}
{children}
</Animated.div>
</div>
</Fragment>
) )
return domNode ? createPortal(modal, domNode) : null
} }
} }
export const ModalFooter = styled(Box).attrs({ const BACKDROP_STYLE = {
px: 5, pointerEvents: 'none',
py: 3, position: 'fixed',
})` top: 0,
border-top: 2px solid ${p => p.theme.colors.lightGrey}; left: 0,
border-bottom-left-radius: ${radii[1]}px; right: 0,
border-bottom-right-radius: ${radii[1]}px; bottom: 0,
` background: 'rgba(0, 0, 0, 0.4)',
zIndex: 100,
}
export const ModalContent = styled(Box).attrs({ const CONTAINER_STYLE = {
px: 5, ...BACKDROP_STYLE,
pb: 5, background: 'transparent',
selectable: true, padding: '60px 0 60px 0',
})`` display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}
const BODY_WRAPPER_STYLE = {
background: 'white',
width: 500,
borderRadius: 3,
boxShadow: 'box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.2)',
color: colors.smoke,
flexShrink: 1,
display: 'flex',
flexDirection: 'column',
}
export default connect( export default connect(
mapStateToProps, mapStateToProps,

71
src/components/base/Modal/stories.js

@ -2,50 +2,57 @@
import React from 'react' import React from 'react'
import { storiesOf } from '@storybook/react' import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import { boolean, text } from '@storybook/addon-knobs' import { boolean, text } from '@storybook/addon-knobs'
import { action } from '@storybook/addon-actions'
import { import Modal from 'components/base/Modal'
Modal, import ModalBody from 'components/base/Modal/ModalBody'
ModalBody, import Input from 'components/base/Input'
ModalTitle, import Label from 'components/base/Label'
ModalContent,
ModalFooter,
ConfirmModal,
} from 'components/base/Modal'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Button from 'components/base/Button'
const stories = storiesOf('Components/base', module) const stories = storiesOf('Components/base', module)
stories.add('Modal', () => ( stories.add('Modal', () => (
<Modal <Modal
isOpened={boolean('isOpened', true)} isOpened={boolean('isOpened', true)}
centered={boolean('centered', true)}
onClose={action('onClose')}
render={({ onClose }) => ( render={({ onClose }) => (
<ModalBody onClose={onClose}> <ModalBody
<ModalTitle>{'modal title'}</ModalTitle> onClose={onClose}
<ModalContent>{'this is the modal content'}</ModalContent> onBack={action('onBack')}
<ModalFooter horizontal align="center"> title={text('title', 'Send funds')}
<Box grow>{'modal footer'}</Box> render={() => (
<Button primary>{'Next'}</Button> <Box flow={4}>
</ModalFooter> <Box flow={2}>
</ModalBody> <Label>{'first field'}</Label>
<Input autoFocus />
</Box>
<Box horizontal flow={4}>
<Box flow={2} flex={1}>
<Label>{'second field'}</Label>
<Input />
</Box>
<Box flow={2} flex={1}>
<Label>{'third field'}</Label>
<Input />
</Box>
</Box>
<Box horizontal flow={4}>
<Box flow={2} flex={1}>
<Label>{'second field'}</Label>
<Input />
</Box>
<Box flow={2} flex={1}>
<Label>{'third field'}</Label>
<Input />
</Box>
</Box>
</Box>
)} )}
renderFooter={() => 'footer'}
/> />
))
stories.add('ConfirmModal', () => (
<ConfirmModal
categoryName=""
isOpened
isDanger={boolean('isDanger', false)}
title={text('title', 'Hard reset')}
subTitle={text('subTitle', 'Are you sure houston?')}
desc={text(
'desc',
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer non nibh diam. In eget ipsum arcu donec finibus',
)} )}
onConfirm={action('onConfirm')}
onReject={action('onReject')}
/> />
)) ))

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

@ -1,12 +1,12 @@
// @flow // @flow
import React, { PureComponent } from 'react' import React, { PureComponent, Fragment } from 'react'
import invariant from 'invariant' import invariant from 'invariant'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import type { T } from 'types/common' import type { T } from 'types/common'
import { ModalContent, ModalTitle, ModalFooter, ModalBody } from 'components/base/Modal' import { ModalBody } from 'components/base/Modal'
import Breadcrumb from 'components/Breadcrumb' import Breadcrumb from 'components/Breadcrumb'
type Props = { type Props = {
@ -29,6 +29,7 @@ export type Step = {
shouldRenderFooter?: StepProps => boolean, shouldRenderFooter?: StepProps => boolean,
shouldPreventClose?: boolean | (StepProps => boolean), shouldPreventClose?: boolean | (StepProps => boolean),
onBack?: StepProps => void, onBack?: StepProps => void,
noScroll?: boolean,
} }
type State = { type State = {
@ -72,6 +73,7 @@ class Stepper extends PureComponent<Props, State> {
onBack, onBack,
shouldPreventClose, shouldPreventClose,
shouldRenderFooter, shouldRenderFooter,
noScroll,
} = step } = step
const stepProps: StepProps = { const stepProps: StepProps = {
@ -89,9 +91,14 @@ class Stepper extends PureComponent<Props, State> {
: !!shouldPreventClose : !!shouldPreventClose
return ( return (
<ModalBody onClose={preventClose ? undefined : onClose}> <ModalBody
<ModalTitle onBack={onBack ? () => onBack(stepProps) : undefined}>{title}</ModalTitle> refocusWhenChange={stepId}
<ModalContent> onClose={preventClose ? undefined : onClose}
onBack={onBack ? () => onBack(stepProps) : undefined}
title={title}
noScroll={noScroll}
render={() => (
<Fragment>
<Breadcrumb <Breadcrumb
mb={6} mb={6}
currentStep={stepIndex} currentStep={stepIndex}
@ -101,13 +108,10 @@ class Stepper extends PureComponent<Props, State> {
/> />
<StepComponent {...stepProps} /> <StepComponent {...stepProps} />
{children} {children}
</ModalContent> </Fragment>
{renderFooter && (
<ModalFooter horizontal align="center" justify="flex-end">
<StepFooter {...stepProps} />
</ModalFooter>
)} )}
</ModalBody> renderFooter={renderFooter ? () => <StepFooter {...stepProps} /> : undefined}
/>
) )
} }
} }

82
src/components/modals/AccountSettingRenderBody.js

@ -1,6 +1,6 @@
// @flow // @flow
import React, { PureComponent } from 'react' import React, { PureComponent, Fragment } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { compose } from 'redux' import { compose } from 'redux'
@ -23,18 +23,14 @@ import TrackPage from 'analytics/TrackPage'
import Spoiler from 'components/base/Spoiler' import Spoiler from 'components/base/Spoiler'
import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon' import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Space from 'components/base/Space'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import Input from 'components/base/Input' import Input from 'components/base/Input'
import Select from 'components/base/Select' import Select from 'components/base/Select'
import SyncAgo from 'components/SyncAgo' import SyncAgo from 'components/SyncAgo'
import { import ConfirmModal from 'components/base/Modal/ConfirmModal'
ModalBody, import ModalBody from 'components/base/Modal/ModalBody'
ModalTitle,
ModalFooter,
ModalContent,
ConfirmModal,
} from 'components/base/Modal'
type State = { type State = {
accountName: ?string, accountName: ?string,
@ -74,7 +70,7 @@ const defaultState = {
isRemoveAccountModalOpen: false, isRemoveAccountModalOpen: false,
} }
class HelperComp extends PureComponent<Props, State> { class AccountSettingRenderBody extends PureComponent<Props, State> {
state = { state = {
...defaultState, ...defaultState,
} }
@ -84,7 +80,6 @@ class HelperComp extends PureComponent<Props, State> {
} }
getAccount(data: Object): Account { getAccount(data: Object): Account {
// FIXME this should be a selector
const { accountName } = this.state const { accountName } = this.state
const account = get(data, 'account', {}) const account = get(data, 'account', {})
@ -129,10 +124,9 @@ class HelperComp extends PureComponent<Props, State> {
}) })
handleSubmit = (account: Account, onClose: () => void) => ( handleSubmit = (account: Account, onClose: () => void) => (
e: SyntheticEvent<HTMLFormElement>, e: SyntheticEvent<HTMLFormElement | HTMLInputElement>,
) => { ) => {
e.preventDefault() e.preventDefault()
const { updateAccount, setDataModal } = this.props const { updateAccount, setDataModal } = this.props
const { accountName, accountUnit, endpointConfig, endpointConfigError } = this.state const { accountName, accountUnit, endpointConfig, endpointConfigError } = this.state
@ -194,10 +188,10 @@ class HelperComp extends PureComponent<Props, State> {
endpointConfigError, endpointConfigError,
} = this.state } = this.state
const { t, onClose, data } = this.props const { t, onClose, data } = this.props
if (!data) return null
const account = this.getAccount(data) const account = this.getAccount(data)
const bridge = getBridgeForCurrency(account.currency) const bridge = getBridgeForCurrency(account.currency)
const usefulData = { const usefulData = {
xpub: account.xpub || undefined, xpub: account.xpub || undefined,
index: account.index, index: account.index,
@ -206,12 +200,15 @@ class HelperComp extends PureComponent<Props, State> {
blockHeight: account.blockHeight, blockHeight: account.blockHeight,
} }
const onSubmit = this.handleSubmit(account, onClose)
return ( return (
<ModalBody onClose={onClose}> <ModalBody
<form onSubmit={this.handleSubmit(account, onClose)}> onClose={onClose}
title={t('account.settings.title')}
render={() => (
<Fragment>
<TrackPage category="Modal" name="AccountSettings" /> <TrackPage category="Modal" name="AccountSettings" />
<ModalTitle>{t('account.settings.title')}</ModalTitle>
<ModalContent mb={3}>
<Container> <Container>
<Box> <Box>
<OptionRowTitle>{t('account.settings.accountName.title')}</OptionRowTitle> <OptionRowTitle>{t('account.settings.accountName.title')}</OptionRowTitle>
@ -224,6 +221,7 @@ class HelperComp extends PureComponent<Props, State> {
value={account.name} value={account.name}
maxLength={MAX_ACCOUNT_NAME_SIZE} maxLength={MAX_ACCOUNT_NAME_SIZE}
onChange={this.handleChangeName} onChange={this.handleChangeName}
onEnter={onSubmit}
onFocus={e => this.handleFocus(e, 'accountName')} onFocus={e => this.handleFocus(e, 'accountName')}
error={accountNameError} error={accountNameError}
/> />
@ -268,8 +266,7 @@ class HelperComp extends PureComponent<Props, State> {
) : null} ) : null}
<Spoiler textTransform title={t('account.settings.advancedLogs')}> <Spoiler textTransform title={t('account.settings.advancedLogs')}>
<SyncAgo date={account.lastSyncDate} /> <SyncAgo date={account.lastSyncDate} />
<textarea <div
readOnly
style={{ style={{
userSelect: 'text', userSelect: 'text',
border: '1px dashed #f9f9f9', border: '1px dashed #f9f9f9',
@ -277,28 +274,17 @@ class HelperComp extends PureComponent<Props, State> {
color: '#000', color: '#000',
fontFamily: 'monospace', fontFamily: 'monospace',
fontSize: '10px', fontSize: '10px',
height: 200,
outline: 'none', outline: 'none',
padding: '20px', padding: '20px',
width: '100%',
whiteSpace: 'pre-wrap',
wordWrap: 'break-word',
overflow: 'auto',
}} }}
value={JSON.stringify(usefulData, null, 2)}
/>
</Spoiler>
</ModalContent>
<ModalFooter horizontal>
<Button
event="OpenAccountDelete"
danger
type="button"
onClick={this.handleOpenRemoveAccountModal}
> >
{t('common.delete')} {JSON.stringify(usefulData, null, 2)}
</Button> </div>
<Button event="DoneEditingAccount" ml="auto" type="submit" primary> </Spoiler>
{t('common.apply')}
</Button>
</ModalFooter>
</form>
<ConfirmModal <ConfirmModal
analyticsName="RemoveAccount" analyticsName="RemoveAccount"
isDanger isDanger
@ -310,7 +296,25 @@ class HelperComp extends PureComponent<Props, State> {
subTitle={t('common.areYouSure')} subTitle={t('common.areYouSure')}
desc={t('settings.removeAccountModal.desc')} desc={t('settings.removeAccountModal.desc')}
/> />
</ModalBody> <Space of={20} />
</Fragment>
)}
renderFooter={() => (
<Fragment>
<Button
event="OpenAccountDelete"
danger
type="button"
onClick={this.handleOpenRemoveAccountModal}
>
{t('common.delete')}
</Button>
<Button event="DoneEditingAccount" ml="auto" onClick={onSubmit} primary>
{t('common.apply')}
</Button>
</Fragment>
)}
/>
) )
} }
} }
@ -321,7 +325,7 @@ export default compose(
mapDispatchToProps, mapDispatchToProps,
), ),
translate(), translate(),
)(HelperComp) )(AccountSettingRenderBody)
export function InputLeft({ currency }: { currency: Currency }) { export function InputLeft({ currency }: { currency: Currency }) {
return ( return (

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

@ -44,6 +44,7 @@ const createSteps = () => {
footer: StepChooseCurrencyFooter, footer: StepChooseCurrencyFooter,
onBack: null, onBack: null,
hideFooter: false, hideFooter: false,
noScroll: true,
}, },
{ {
id: 'connectDevice', id: 'connectDevice',
@ -245,6 +246,7 @@ class AddAccounts extends PureComponent<Props, State> {
return ( return (
<Modal <Modal
centered
name={MODAL_ADD_ACCOUNTS} name={MODAL_ADD_ACCOUNTS}
refocusWhenChange={stepId} refocusWhenChange={stepId}
onHide={() => this.setState({ ...INITIAL_STATE })} onHide={() => this.setState({ ...INITIAL_STATE })}

26
src/components/modals/Debug.js

@ -1,11 +1,12 @@
// @flow // @flow
/* eslint-disable react/jsx-no-literals */ /* eslint-disable react/jsx-no-literals */
import React, { Component } from 'react' import React, { Component, Fragment } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { createStructuredSelector } from 'reselect' import { createStructuredSelector } from 'reselect'
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/currencies' import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/currencies'
import { getDerivationScheme, runDerivationScheme } from '@ledgerhq/live-common/lib/derivation' import { getDerivationScheme, runDerivationScheme } from '@ledgerhq/live-common/lib/derivation'
import Modal, { ModalBody, ModalTitle, ModalContent } from 'components/base/Modal' import Modal from 'components/base/Modal'
import ModalBody from 'components/base/Modal/ModalBody'
import { getCurrentDevice } from 'reducers/devices' import { getCurrentDevice } from 'reducers/devices'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import Box from 'components/base/Box' import Box from 'components/base/Box'
@ -127,14 +128,12 @@ class Debug extends Component<*, *> {
const { device } = this.props const { device } = this.props
const { logs } = this.state const { logs } = this.state
return ( return (
<Modal <Modal name="MODAL_DEBUG" centered onHide={this.onHide}>
name="MODAL_DEBUG" <ModalBody
onHide={this.onHide} title="developer internal tools"
render={({ onClose }: *) => ( render={() => (
<ModalBody onClose={onClose}> <Box>
<SyncSkipUnderPriority priority={99999999} /> <SyncSkipUnderPriority priority={99999999} />
<ModalTitle>developer internal tools</ModalTitle>
<ModalContent>
<Box style={{ height: 60, overflow: 'auto' }}> <Box style={{ height: 60, overflow: 'auto' }}>
{device && ( {device && (
<Box horizontal style={{ padding: 10 }}> <Box horizontal style={{ padding: 10 }}>
@ -191,6 +190,7 @@ class Debug extends Component<*, *> {
> >
{logs.map(log => ( {logs.map(log => (
<Box <Box
key={log.txt}
style={{ style={{
userSelect: 'all', userSelect: 'all',
color: log.type === 'error' ? '#c22' : '#888', color: log.type === 'error' ? '#c22' : '#888',
@ -200,6 +200,10 @@ class Debug extends Component<*, *> {
</Box> </Box>
))} ))}
</Box> </Box>
</Box>
)}
renderFooter={() => (
<Fragment>
<Button <Button
style={{ position: 'absolute', right: 30, bottom: 28 }} style={{ position: 'absolute', right: 30, bottom: 28 }}
onClick={() => { onClick={() => {
@ -208,10 +212,10 @@ class Debug extends Component<*, *> {
> >
Clear Clear
</Button> </Button>
</ModalContent> </Fragment>
</ModalBody>
)} )}
/> />
</Modal>
) )
} }
} }

43
src/components/modals/Disclaimer.js

@ -7,42 +7,59 @@ import type { T } from 'types/common'
import { MODAL_DISCLAIMER } from 'config/constants' import { MODAL_DISCLAIMER } from 'config/constants'
import Modal, { ModalBody, ModalTitle, ModalContent, ModalFooter } from 'components/base/Modal' import Modal from 'components/base/Modal'
import ModalBody from 'components/base/Modal/ModalBody'
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 { HandShield } from 'components/WarnBox' import { HandShield } from 'components/WarnBox'
import { compose } from 'redux'
import connect from 'react-redux/es/connect/connect'
import { closeModal } from '../../reducers/modals'
type Props = { type Props = {
t: T, t: T,
closeModal: string => void,
}
const mapDispatchToProps = {
closeModal,
} }
class DisclaimerModal extends PureComponent<Props> { class DisclaimerModal extends PureComponent<Props> {
onClose = () => this.props.closeModal(MODAL_DISCLAIMER)
render() { render() {
const { t } = this.props const { t } = this.props
return ( return (
<Modal <Modal name={MODAL_DISCLAIMER} centered>
name={MODAL_DISCLAIMER} <ModalBody
render={({ onClose }) => ( onClose={this.onClose}
<ModalBody onClose={onClose}> title={t('disclaimerModal.title')}
<ModalTitle>{t('disclaimerModal.title')}</ModalTitle> render={() => (
<ModalContent flow={4} ff="Open Sans|Regular" fontSize={4} color="smoke"> <Box flow={4} ff="Open Sans|Regular" fontSize={4} color="smoke">
<Box align="center" mt={4} pb={4}> <Box align="center" mt={4} pb={4}>
<HandShield size={55} /> <HandShield size={55} />
</Box> </Box>
<p>{t('disclaimerModal.desc_1')}</p> <p>{t('disclaimerModal.desc_1')}</p>
<p>{t('disclaimerModal.desc_2')}</p> <p>{t('disclaimerModal.desc_2')}</p>
</ModalContent> </Box>
<ModalFooter horizontal justifyContent="flex-end"> )}
<Button data-e2e="continue_button" onClick={onClose} primary> renderFooter={() => (
<Box horizontal justifyContent="flex-end">
<Button data-e2e="continue_button" onClick={this.onClose} primary>
{t('disclaimerModal.cta')} {t('disclaimerModal.cta')}
</Button> </Button>
</ModalFooter> </Box>
</ModalBody>
)} )}
/> />
</Modal>
) )
} }
} }
export default translate()(DisclaimerModal) export default compose(
connect(
null,
mapDispatchToProps,
),
translate(),
)(DisclaimerModal)

31
src/components/modals/OperationDetails.js

@ -18,12 +18,10 @@ import { MODAL_OPERATION_DETAILS } from 'config/constants'
import { getMarketColor } from 'styles/helpers' import { getMarketColor } from 'styles/helpers'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import GradientBox from 'components/GradientBox'
import GrowScroll from 'components/base/GrowScroll'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import Bar from 'components/base/Bar' import Bar from 'components/base/Bar'
import FormattedVal from 'components/base/FormattedVal' import FormattedVal from 'components/base/FormattedVal'
import Modal, { ModalBody, ModalTitle, ModalFooter, ModalContent } from 'components/base/Modal' import Modal, { ModalBody } from 'components/base/Modal'
import Text from 'components/base/Text' import Text from 'components/base/Text'
import CopyWithFeedback from 'components/base/CopyWithFeedback' import CopyWithFeedback from 'components/base/CopyWithFeedback'
@ -130,11 +128,10 @@ const OperationDetails = connect(mapStateToProps)((props: Props) => {
const uniqueSenders = uniq(senders) const uniqueSenders = uniq(senders)
return ( return (
<ModalBody onClose={onClose}> <ModalBody
<TrackPage category="Modal" name="OperationDetails" /> title={t('operationDetails.title')}
<ModalTitle>{t('operationDetails.title')}</ModalTitle> onClose={onClose}
<ModalContent relative style={{ height: 500 }} px={0} pb={0}> render={() => (
<GrowScroll px={5} pt={1} pb={8}>
<Box flow={3}> <Box flow={3}>
<Box alignItems="center" mt={1}> <Box alignItems="center" mt={1}>
<ConfirmationCheck <ConfirmationCheck
@ -235,17 +232,16 @@ const OperationDetails = connect(mapStateToProps)((props: Props) => {
</Box> </Box>
))} ))}
</Box> </Box>
</GrowScroll> )}
<GradientBox /> renderFooter={() =>
</ModalContent> url && (
{url && (
<ModalFooter horizontal justify="flex-end" flow={2}>
<Button primary onClick={() => openURL(url)}> <Button primary onClick={() => openURL(url)}>
{t('operationDetails.viewOperation')} {t('operationDetails.viewOperation')}
</Button> </Button>
</ModalFooter> )
)} }
>
<TrackPage category="Modal" name="OperationDetails" />
</ModalBody> </ModalBody>
) )
}) })
@ -255,12 +251,13 @@ type ModalRenderProps = {
account: string, account: string,
operation: string, operation: string,
}, },
onClose: Function, onClose?: Function,
} }
const OperationDetailsWrapper = ({ t }: { t: T }) => ( const OperationDetailsWrapper = ({ t }: { t: T }) => (
<Modal <Modal
name={MODAL_OPERATION_DETAILS} name={MODAL_OPERATION_DETAILS}
centered
render={(props: ModalRenderProps) => { render={(props: ModalRenderProps) => {
const { data, onClose } = props const { data, onClose } = props
return <OperationDetails t={t} {...data} onClose={onClose} /> return <OperationDetails t={t} {...data} onClose={onClose} />

5
src/components/modals/Receive/index.js

@ -197,16 +197,17 @@ class ReceiveModal extends PureComponent<Props, State> {
return ( return (
<Modal <Modal
name={MODAL_RECEIVE} name={MODAL_RECEIVE}
centered
refocusWhenChange={stepId} refocusWhenChange={stepId}
onHide={this.handleReset} onHide={this.handleReset}
preventBackdropClick={isModalLocked} preventBackdropClick={isModalLocked}
onBeforeOpen={this.handleBeforeOpenModal} onBeforeOpen={this.handleBeforeOpenModal}
render={({ onClose }) => ( render={() => (
<Stepper <Stepper
title={t('receive.title')} title={t('receive.title')}
initialStepId={stepId} initialStepId={stepId}
onStepChange={this.handleStepChange} onStepChange={this.handleStepChange}
onClose={onClose} onClose={addtionnalProps.closeModal}
steps={this.STEPS} steps={this.STEPS}
disabledSteps={disabledSteps} disabledSteps={disabledSteps}
errorSteps={errorSteps} errorSteps={errorSteps}

26
src/components/modals/ReleaseNotes/ReleaseNotesBody.js

@ -7,14 +7,13 @@ import network from 'api/network'
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 GrowScroll from 'components/base/GrowScroll'
import Text from 'components/base/Text' import Text from 'components/base/Text'
import Spinner from 'components/base/Spinner' import Spinner from 'components/base/Spinner'
import GradientBox from 'components/GradientBox' import GradientBox from 'components/GradientBox'
import TranslatedError from 'components/TranslatedError' import TranslatedError from 'components/TranslatedError'
import TrackPage from 'analytics/TrackPage' import TrackPage from 'analytics/TrackPage'
import Markdown, { Notes } from 'components/base/Markdown' import Markdown, { Notes } from 'components/base/Markdown'
import { ModalBody, ModalTitle, ModalContent, ModalFooter } from 'components/base/Modal' import ModalBody from 'components/base/Modal/ModalBody'
import type { T } from 'types/common' import type { T } from 'types/common'
@ -115,21 +114,26 @@ class ReleaseNotesBody extends PureComponent<Props, State> {
const { onClose, t } = this.props const { onClose, t } = this.props
return ( return (
<ModalBody onClose={onClose}> <ModalBody
onClose={onClose}
title={t('releaseNotes.title')}
render={() => (
<Box relative style={{ height: 500 }} px={0} pb={0}>
<TrackPage category="Modal" name="ReleaseNotes" /> <TrackPage category="Modal" name="ReleaseNotes" />
<ModalTitle>{t('releaseNotes.title')}</ModalTitle> <Box px={5} pb={8}>
<ModalContent relative style={{ height: 500 }} px={0} pb={0}>
<GrowScroll px={5} pb={8}>
{this.renderContent()} {this.renderContent()}
</GrowScroll> </Box>
<GradientBox /> <GradientBox />
</ModalContent> </Box>
<ModalFooter horizontal justifyContent="flex-end"> )}
renderFooter={() => (
<Box horizontal justifyContent="flex-end">
<Button onClick={onClose} primary> <Button onClick={onClose} primary>
{t('common.continue')} {t('common.continue')}
</Button> </Button>
</ModalFooter> </Box>
</ModalBody> )}
/>
) )
} }
} }

1
src/components/modals/ReleaseNotes/index.js

@ -43,6 +43,7 @@ class ReleaseNotesModal extends PureComponent<Props> {
return ( return (
<Modal <Modal
name={MODAL_RELEASES_NOTES} name={MODAL_RELEASES_NOTES}
centered
render={({ data, onClose }) => <ReleaseNotesBody version={data} onClose={onClose} />} render={({ data, onClose }) => <ReleaseNotesBody version={data} onClose={onClose} />}
/> />
) )

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

@ -277,6 +277,7 @@ class SendModal extends PureComponent<Props, State<*>> {
return ( return (
<Modal <Modal
name={MODAL_SEND} name={MODAL_SEND}
centered
refocusWhenChange={stepId} refocusWhenChange={stepId}
onHide={this.handleReset} onHide={this.handleReset}
preventBackdropClick={isModalLocked} preventBackdropClick={isModalLocked}

5
src/components/modals/SettingsAccount.js

@ -11,7 +11,10 @@ export default class SettingsAccount extends PureComponent<*, *> {
return ( return (
<Modal <Modal
name={MODAL_SETTINGS_ACCOUNT} name={MODAL_SETTINGS_ACCOUNT}
render={({ data, onClose }) => <AccountSettingRenderBody data={data} onClose={onClose} />} centered
render={({ data, onClose }) => (
<AccountSettingRenderBody {...this.props} data={data} onClose={onClose} />
)}
/> />
) )
} }

54
src/components/modals/ShareAnalytics.js

@ -1,20 +1,30 @@
// @flow // @flow
import React, { PureComponent } from 'react' import React, { Fragment, PureComponent } from 'react'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import { MODAL_SHARE_ANALYTICS } from 'config/constants' import { MODAL_SHARE_ANALYTICS } from 'config/constants'
import Modal, { ModalBody, ModalTitle, ModalContent, ModalFooter } from 'components/base/Modal' import Modal from 'components/base/Modal'
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 { connect } from 'react-redux'
import { compose } from 'redux'
import type { T } from 'types/common' import type { T } from 'types/common'
import { closeModal } from '../../reducers/modals'
import ModalBody from '../base/Modal/ModalBody'
type Props = { type Props = {
t: T, t: T,
closeModal: string => void,
}
const mapDispatchToProps = {
closeModal,
} }
class ShareAnalytics extends PureComponent<Props, *> { class ShareAnalytics extends PureComponent<Props, *> {
onClose = () => this.props.closeModal(MODAL_SHARE_ANALYTICS)
render() { render() {
const { t } = this.props const { t } = this.props
const items = [ const items = [
@ -56,30 +66,38 @@ class ShareAnalytics extends PureComponent<Props, *> {
}, },
] ]
return ( return (
<Modal <Modal name={MODAL_SHARE_ANALYTICS} centered>
name={MODAL_SHARE_ANALYTICS} <ModalBody
render={({ onClose }) => ( onClose={this.onClose}
<ModalBody onClose={onClose}> title={t('onboarding.analytics.shareAnalytics.title')}
<ModalTitle data-e2e="modal_title_shareAnalytics"> render={() => (
{t('onboarding.analytics.shareAnalytics.title')} <Fragment>
</ModalTitle>
<InlineDesc>{t('onboarding.analytics.shareAnalytics.desc')}</InlineDesc> <InlineDesc>{t('onboarding.analytics.shareAnalytics.desc')}</InlineDesc>
<ModalContent mx={5}> <Box mx={5}>
<Ul>{items.map(item => <li key={item.key}>{item.desc}</li>)}</Ul> <Ul>{items.map(item => <li key={item.key}>{item.desc}</li>)}</Ul>
</ModalContent> </Box>
<ModalFooter horizontal justifyContent="flex-end"> </Fragment>
<Button onClick={onClose} primary data-e2e="modal_buttonClose_shareAnalytics"> )}
renderFooter={() => (
<Fragment>
<Button onClick={this.onClose} primary data-e2e="modal_buttonClose_shareAnalytics">
{t('common.close')} {t('common.close')}
</Button> </Button>
</ModalFooter> </Fragment>
</ModalBody>
)} )}
/> />
</Modal>
) )
} }
} }
export default translate()(ShareAnalytics) export default compose(
connect(
null,
mapDispatchToProps,
),
translate(),
)(ShareAnalytics)
export const Ul = styled.ul.attrs({ export const Ul = styled.ul.attrs({
ff: 'Open Sans|Regular', ff: 'Open Sans|Regular',
@ -93,5 +111,5 @@ export const InlineDesc = styled(Box).attrs({
ff: 'Open Sans|SemiBold', ff: 'Open Sans|SemiBold',
fontSize: 4, fontSize: 4,
color: 'dark', color: 'dark',
mx: '45px', mx: '15px',
})`` })``

51
src/components/modals/TechnicalData.js

@ -1,19 +1,30 @@
// @flow // @flow
import React, { PureComponent } from 'react' import React, { Fragment, PureComponent } from 'react'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { MODAL_TECHNICAL_DATA } from 'config/constants' import { MODAL_TECHNICAL_DATA } from 'config/constants'
import Modal, { ModalBody, ModalTitle, ModalContent, ModalFooter } from 'components/base/Modal' import Modal, { ModalBody } from 'components/base/Modal'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import type { T } from 'types/common' import type { T } from 'types/common'
import { connect } from 'react-redux'
import { compose } from 'redux'
import Box from 'components/base/Box'
import { Ul, InlineDesc } from './ShareAnalytics' import { Ul, InlineDesc } from './ShareAnalytics'
import { closeModal } from '../../reducers/modals'
type Props = { type Props = {
t: T, t: T,
closeModal: string => void,
}
const mapDispatchToProps = {
closeModal,
} }
class TechnicalData extends PureComponent<Props, *> { class TechnicalData extends PureComponent<Props, *> {
onClose = () => this.props.closeModal(MODAL_TECHNICAL_DATA)
render() { render() {
const { t } = this.props const { t } = this.props
@ -33,27 +44,35 @@ class TechnicalData extends PureComponent<Props, *> {
] ]
return ( return (
<Modal <Modal name={MODAL_TECHNICAL_DATA} centered>
name={MODAL_TECHNICAL_DATA} <ModalBody
render={({ onClose }) => ( onClose={this.onClose}
<ModalBody onClose={onClose}> title={t('onboarding.analytics.technicalData.mandatoryContextual.title')}
<ModalTitle data-e2e="modal_title_TechData"> render={() => (
{t('onboarding.analytics.technicalData.mandatoryContextual.title')} <Fragment>
</ModalTitle>
<InlineDesc>{t('onboarding.analytics.technicalData.desc')}</InlineDesc> <InlineDesc>{t('onboarding.analytics.technicalData.desc')}</InlineDesc>
<ModalContent mx={5}> <Box mx={5}>
<Ul>{items.map(item => <li key={item.key}>{item.desc}</li>)}</Ul> <Ul>{items.map(item => <li key={item.key}>{item.desc}</li>)}</Ul>
</ModalContent> </Box>
<ModalFooter horizontal justifyContent="flex-end"> </Fragment>
<Button onClick={onClose} primary data-e2e="modal_buttonClose_techData"> )}
renderFooter={() => (
<Fragment>
<Button onClick={this.onClose} primary data-e2e="modal_buttonClose_techData">
{t('common.close')} {t('common.close')}
</Button> </Button>
</ModalFooter> </Fragment>
</ModalBody>
)} )}
/> />
</Modal>
) )
} }
} }
export default translate()(TechnicalData) export default compose(
connect(
null,
mapDispatchToProps,
),
translate(),
)(TechnicalData)

38
src/components/modals/UpdateFirmware/Disclaimer.js

@ -1,12 +1,13 @@
// @flow // @flow
/* eslint react/jsx-no-literals: 0 */ /* eslint react/jsx-no-literals: 0 */
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent } from 'react'
import { translate, Trans } from 'react-i18next' import { translate, Trans } from 'react-i18next'
import type { OsuFirmware, FinalFirmware } from '@ledgerhq/live-common/lib/types/manager' import type { OsuFirmware, FinalFirmware } from '@ledgerhq/live-common/lib/types/manager'
import type { T } from 'types/common' import type { T } from 'types/common'
import Modal, { ModalBody, ModalFooter, ModalTitle, ModalContent } from 'components/base/Modal' import Modal from 'components/base/Modal'
import ModalBody from 'components/base/Modal/ModalBody'
import Text from 'components/base/Text' import Text from 'components/base/Text'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import GrowScroll from 'components/base/GrowScroll' import GrowScroll from 'components/base/GrowScroll'
@ -17,6 +18,7 @@ import TrackPage from 'analytics/TrackPage'
import type { ModalStatus } from 'components/ManagerPage/FirmwareUpdate' import type { ModalStatus } from 'components/ManagerPage/FirmwareUpdate'
import { getCleanVersion } from 'components/ManagerPage/FirmwareUpdate' import { getCleanVersion } from 'components/ManagerPage/FirmwareUpdate'
import Box from '../../base/Box/Box'
type Props = { type Props = {
t: T, t: T,
@ -35,15 +37,16 @@ class DisclaimerModal extends PureComponent<Props, State> {
render(): React$Node { render(): React$Node {
const { status, firmware, onClose, t, goToNextStep } = this.props const { status, firmware, onClose, t, goToNextStep } = this.props
return ( return (
<Modal <Modal isOpened={status === 'disclaimer'} onClose={onClose}>
isOpened={status === 'disclaimer'}
onClose={onClose}
render={({ onClose }) => (
<ModalBody onClose={onClose} grow align="center" justify="center" mt={3}>
<TrackPage category="Manager" name="DisclaimerModal" /> <TrackPage category="Manager" name="DisclaimerModal" />
<Fragment> <ModalBody
<ModalTitle>{t('manager.firmware.update')}</ModalTitle> grow
<ModalContent> align="center"
justify="center"
mt={3}
title={t('manager.firmware.update')}
render={() => (
<Box>
<Text ff="Open Sans|Regular" fontSize={4} color="graphite" align="center"> <Text ff="Open Sans|Regular" fontSize={4} color="graphite" align="center">
<Trans i18nKey="manager.firmware.disclaimerTitle"> <Trans i18nKey="manager.firmware.disclaimerTitle">
You are about to install You are about to install
@ -58,26 +61,27 @@ class DisclaimerModal extends PureComponent<Props, State> {
{t('manager.firmware.disclaimerAppDelete')} {t('manager.firmware.disclaimerAppDelete')}
{t('manager.firmware.disclaimerAppReinstall')} {t('manager.firmware.disclaimerAppReinstall')}
</Text> </Text>
</ModalContent>
{firmware && firmware.osu ? ( {firmware && firmware.osu ? (
<ModalContent relative pb={0} style={{ height: 250, width: '100%' }}> <Box relative pb={0} style={{ height: 250, width: '100%' }}>
<GrowScroll pb={5}> <GrowScroll pb={5}>
<Notes> <Notes>
<Markdown>{firmware.osu.notes}</Markdown> <Markdown>{firmware.osu.notes}</Markdown>
</Notes> </Notes>
</GrowScroll> </GrowScroll>
<GradientBox /> <GradientBox />
</ModalContent> </Box>
) : null} ) : null}
<ModalFooter horizontal justifyContent="flex-end" style={{ width: '100%' }}> </Box>
)}
renderFooter={() => (
<Box horizontal justifyContent="flex-end">
<Button primary onClick={goToNextStep}> <Button primary onClick={goToNextStep}>
{t('common.continue')} {t('common.continue')}
</Button> </Button>
</ModalFooter> </Box>
</Fragment>
</ModalBody>
)} )}
/> />
</Modal>
) )
} }
} }

1
src/components/modals/UpdateFirmware/index.js

@ -107,6 +107,7 @@ class UpdateModal extends PureComponent<Props, State> {
return ( return (
<Modal <Modal
onClose={onClose} onClose={onClose}
centered
onHide={this.handleReset} onHide={this.handleReset}
isOpened={status === 'install'} isOpened={status === 'install'}
refocusWhenChange={stepId} refocusWhenChange={stepId}

1
src/index.ejs

@ -75,6 +75,7 @@
<img class="logo" src="<%= __DEV__ ? '.' : '../static' %>/images/ledgerlive-logo.svg" alt="" /> <img class="logo" src="<%= __DEV__ ? '.' : '../static' %>/images/ledgerlive-logo.svg" alt="" />
</div> </div>
<div id="app"></div> <div id="app"></div>
<div id="modals"></div>
<script> <script>
const { remote } = require('electron') const { remote } = require('electron')
const { name } = remote.getCurrentWindow() const { name } = remote.getCurrentWindow()

1
src/main/app.js

@ -84,6 +84,7 @@ const defaultWindowOptions = {
backgroundColor: '#fff', backgroundColor: '#fff',
webPreferences: { webPreferences: {
blinkFeatures: 'OverlayScrollbars',
devTools, devTools,
// Enable, among other things, the ResizeObserver // Enable, among other things, the ResizeObserver
experimentalFeatures: true, experimentalFeatures: true,

1
src/reducers/modals.js

@ -44,7 +44,6 @@ const handlers = {
...state, ...state,
[name]: { [name]: {
isOpened: false, isOpened: false,
data: undefined,
}, },
} }
}, },

8
static/i18n/en/app.json

@ -369,10 +369,10 @@
"modal": { "modal": {
"button": "Done", "button": "Done",
"title": "Scan to export to mobile", "title": "Scan to export to mobile",
"listTitle": "To import accounts on your Ledger Live Mobile app:", "listTitle": "On the Ledger Live mobile app:",
"step1": "Tap the <1><0>+</0></1> button in Accounts", "step1": "Tap the <1><0>+</0></1> button in <3><0>Accounts</0></3>",
"step2": "Tap <1><0>Import desktop accounts</0></1>", "step2": "Tap <1><0>Import desktop accounts</0></1>",
"step3": "Scan until the loader hits 100%" "step3": "Scan the <1><0>LiveQR code</0></1> until the loader hits 100%"
} }
}, },
"display": { "display": {
@ -791,7 +791,7 @@
"description": "Please retry. Interacting with Ledger's API server went wrong." "description": "Please retry. Interacting with Ledger's API server went wrong."
}, },
"LedgerAPIErrorWithMessage": { "LedgerAPIErrorWithMessage": {
"title": "Oops, {{message}}", "title": "{{message}}",
"description": "Please retry or contact Ledger Support" "description": "Please retry or contact Ledger Support"
}, },
"LedgerAPINotAvailable": { "LedgerAPINotAvailable": {

82
yarn.lock

@ -1682,51 +1682,43 @@
resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-4.35.1.tgz#3f162dc05480e444083b6381bd098df187751633" resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-4.35.1.tgz#3f162dc05480e444083b6381bd098df187751633"
integrity sha512-2Bo3/NRKyz3ddR07TvZ87VpDJc8fz4+ONLJnhzC0mwIwu+Pxal6SgCBiGtv503oGxkgDuG5PtODZBaehWkGRnQ== integrity sha512-2Bo3/NRKyz3ddR07TvZ87VpDJc8fz4+ONLJnhzC0mwIwu+Pxal6SgCBiGtv503oGxkgDuG5PtODZBaehWkGRnQ==
"@ledgerhq/hw-app-btc@^4.32.0": "@ledgerhq/hw-app-btc@^4.32.0", "@ledgerhq/hw-app-btc@^4.35.0":
version "4.32.0" version "4.35.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-btc/-/hw-app-btc-4.32.0.tgz#e883dcaa3ebb4aca1e2cb27acfc47b8db4e85f3f" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-btc/-/hw-app-btc-4.35.0.tgz#aafd655c988da39f774b0a4706e7f8897222f414"
integrity sha512-N/RxtkPVjTDwU+lDPQQE7+4YQMXaXStDrpufQbDn0NXoaJ8KgY+QGkOH6bkuwV+LQvc7rEaM7E3p7/t58KJpMg== integrity sha512-oX9YcQAuU+rOJm/lE7YF5+JXNppHcUv23ZltGz5CbWHnhm7Tqo4MOR8N5oSnHKlHW+IawfWCPN5PqdF7RGyQ5w==
dependencies: dependencies:
"@ledgerhq/hw-transport" "^4.32.0" "@ledgerhq/hw-transport" "^4.35.0"
create-hash "^1.1.3" create-hash "^1.1.3"
"@ledgerhq/hw-app-btc@^4.34.0": "@ledgerhq/hw-app-eth@^4.32.0", "@ledgerhq/hw-app-eth@^4.35.0":
version "4.34.0" version "4.35.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-btc/-/hw-app-btc-4.34.0.tgz#0bbc46afd29de04ac6a73582fbf9a09fcf5ed117" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-eth/-/hw-app-eth-4.35.0.tgz#3a8c1b0db87224a24ff5441a0e819122e5585f2d"
integrity sha512-xR4rH8o8YRvyhnTvb8g89NAJQQqXJkApiFtCvduBamu5V+rDvhHYlFu2B+CU6g8lzLFACMDIqJqXbmwT80AGjw== integrity sha512-MSDr8+CaoXhtm64ELuI/8wpcfmrMUjzGJgASY6bnjc82vAW+6sHNZlTU0zWRTZxqQUuZ8WpuJP159cf92MWq3g==
dependencies: dependencies:
"@ledgerhq/hw-transport" "^4.32.0" "@ledgerhq/hw-transport" "^4.35.0"
create-hash "^1.1.3"
"@ledgerhq/hw-app-eth@^4.32.0": "@ledgerhq/hw-app-xrp@^4.32.0", "@ledgerhq/hw-app-xrp@^4.35.0":
version "4.32.0" version "4.35.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-eth/-/hw-app-eth-4.32.0.tgz#7d43ca2c7952f1fb726e02c3b4485be10af481a2" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-xrp/-/hw-app-xrp-4.35.0.tgz#f6aec06ae53f8732d90f745963a2de96c2ffa432"
integrity sha512-d22WinjcsqJNoZSI+6UpTWZ7hl+UhL2dFeVeliCwtBWSj40z6F25MpoviGxPsv0WC7IUjayw+a9jIRcOJ5kkIw== integrity sha512-kQLdr9xrYvkFR9+QVyTNtmSGFDfrQ63ac0QhWKEoILiiQ0dxfZ7qCCp/qPJk/sx9H8dMX37X6y+xAnSU1frbfg==
dependencies: dependencies:
"@ledgerhq/hw-transport" "^4.32.0" "@ledgerhq/hw-transport" "^4.35.0"
"@ledgerhq/hw-app-xrp@^4.32.0":
version "4.32.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-xrp/-/hw-app-xrp-4.32.0.tgz#260daafa9de1073598ea91ddfeb168dc437edd50"
integrity sha512-MNmLAGUp7Bnj/mjg1Lo5bK1v+q/QPYw7RJAbI4Vl1A4Fsqj6oiZspnSK+BTHGp+CRJavCwumjKuf5y3X5Dp8cA==
dependencies:
"@ledgerhq/hw-transport" "^4.32.0"
bip32-path "0.4.2" bip32-path "0.4.2"
"@ledgerhq/hw-transport-node-hid@^4.32.0": "@ledgerhq/hw-transport-node-hid@^4.35.0":
version "4.33.3" version "4.35.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.33.3.tgz#5e96dca2be0a23d80814303f262398087b208a6a" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.35.0.tgz#0eba08e5edd14a8c779ebaf73ec21976ee5f112e"
integrity sha512-hmNAm7k385RJXY38hVUpzYgGgyk9QjScD3erNlFCTO8FnnxmEJCFUmVhWkv4sTwufuUJSpXL3ZXXNZ44qLMJpg== integrity sha512-Otnymk9B7qCEfjych/SvTvJsMM+DqyoB0saEwL80ukjuGFqMunecrG5w8nC4aCc169IVz70Spkg2uU90TBUCuw==
dependencies: dependencies:
"@ledgerhq/hw-transport" "^4.32.0" "@ledgerhq/hw-transport" "^4.35.0"
lodash "^4.17.11" lodash "^4.17.11"
node-hid "^0.7.2" node-hid "^0.7.2"
usb "^1.3.3" usb "^1.3.3"
"@ledgerhq/hw-transport@^4.21.0", "@ledgerhq/hw-transport@^4.32.0": "@ledgerhq/hw-transport@^4.21.0", "@ledgerhq/hw-transport@^4.32.0", "@ledgerhq/hw-transport@^4.35.0":
version "4.32.0" version "4.35.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-4.32.0.tgz#592b9dc51459cb1cd31ce9444cf943f627bc4beb" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-4.35.0.tgz#aa7b851111ed759cd7489fa07a7b34c1773e8314"
integrity sha512-Wgsk9UHC4RShqYoDeIEeKgHZOvNCtB0WWIG0xqlVPzS+IcKDkIxtXQw7hTA7GQSuDuGeauVtlbTQ5yat6+2/BA== integrity sha512-o8ekdoCkHMvOByIKDmAMNDjm8Q5cu+sbqmebPtGrHAPbgIZBUbNA5UupY/Om+xypdxXYnuBw+MF8FyIVOjnIsg==
dependencies: dependencies:
events "^3.0.0" events "^3.0.0"
@ -2270,6 +2262,11 @@
text-table "^0.2.0" text-table "^0.2.0"
webpack-log "^1.1.2" webpack-log "^1.1.2"
"@yarnpkg/lockfile@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
abab@^1.0.4: abab@^1.0.4:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
@ -5265,6 +5262,11 @@ commander@2.6.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d" resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d"
integrity sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0= integrity sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=
commander@^2.10.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==
commander@~2.13.0: commander@~2.13.0:
version "2.13.0" version "2.13.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
@ -13302,12 +13304,7 @@ react-inspector@^2.2.2:
babel-runtime "^6.26.0" babel-runtime "^6.26.0"
is-dom "^1.0.9" is-dom "^1.0.9"
react-is@^16.3.1, react-is@^16.4.1: react-is@^16.3.1, react-is@^16.3.2, react-is@^16.4.1:
version "16.4.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.1.tgz#d624c4650d2c65dbd52c72622bbf389435d9776e"
integrity sha512-xpb0PpALlFWNw/q13A+1aHeyJyLYCg0/cCHPUA43zYluZuIPHaHL3k8OBsTgQtxqW0FhyDEMvi8fZ/+7+r4OSQ==
react-is@^16.3.2:
version "16.5.2" version "16.5.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3"
integrity sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ== integrity sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==
@ -17181,6 +17178,15 @@ yargs@~3.10.0:
decamelize "^1.0.0" decamelize "^1.0.0"
window-size "0.1.0" window-size "0.1.0"
yarn-deduplicate@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-1.1.1.tgz#19b4a87654b66f55bf3a4bd6b153b4e4ab1b6e6d"
integrity sha512-2FDJ1dFmtvqhRmfja89ohYzpaheCYg7BFBSyaUq+kxK0y61C9oHv1XaQovCWGJtP2WU8PksQOgzMVV7oQOobzw==
dependencies:
"@yarnpkg/lockfile" "^1.1.0"
commander "^2.10.0"
semver "^5.3.0"
yauzl@2.4.1: yauzl@2.4.1:
version "2.4.1" version "2.4.1"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005"

Loading…
Cancel
Save