|
|
@ -1,192 +1,111 @@ |
|
|
|
// @flow
|
|
|
|
|
|
|
|
import React, { Fragment, PureComponent } from 'react' |
|
|
|
import React, { PureComponent } from 'react' |
|
|
|
import { compose } from 'redux' |
|
|
|
import { connect } from 'react-redux' |
|
|
|
import { translate } from 'react-i18next' |
|
|
|
import { createStructuredSelector } from 'reselect' |
|
|
|
|
|
|
|
import { accountsSelector } from 'reducers/accounts' |
|
|
|
import SyncSkipUnderPriority from 'components/SyncSkipUnderPriority' |
|
|
|
|
|
|
|
import type { Account } from '@ledgerhq/live-common/lib/types' |
|
|
|
import type { T, Device } from 'types/common' |
|
|
|
|
|
|
|
import { MODAL_RECEIVE } from 'config/constants' |
|
|
|
import { isSegwitAccount } from 'helpers/bip32' |
|
|
|
import type { T, Device } from 'types/common' |
|
|
|
import type { StepProps as DefaultStepProps } from 'components/base/Stepper' |
|
|
|
|
|
|
|
import getAddress from 'commands/getAddress' |
|
|
|
import SyncSkipUnderPriority from 'components/SyncSkipUnderPriority' |
|
|
|
import SyncOneAccountOnMount from 'components/SyncOneAccountOnMount' |
|
|
|
import { getCurrentDevice } from 'reducers/devices' |
|
|
|
import { accountsSelector } from 'reducers/accounts' |
|
|
|
import { closeModal } from 'reducers/modals' |
|
|
|
|
|
|
|
import Box from 'components/base/Box' |
|
|
|
import Breadcrumb from 'components/Breadcrumb' |
|
|
|
import Button from 'components/base/Button' |
|
|
|
import Modal, { ModalBody, ModalTitle, ModalContent, ModalFooter } from 'components/base/Modal' |
|
|
|
import StepConnectDevice from 'components/modals/StepConnectDevice' |
|
|
|
import { WrongDeviceForAccount } from 'components/EnsureDeviceApp' |
|
|
|
import Modal from 'components/base/Modal' |
|
|
|
import Stepper from 'components/base/Stepper' |
|
|
|
|
|
|
|
import StepAccount from './01-step-account' |
|
|
|
import StepConfirmAddress from './03-step-confirm-address' |
|
|
|
import StepReceiveFunds from './04-step-receive-funds' |
|
|
|
import StepAccount, { StepAccountFooter } from './steps/01-step-account' |
|
|
|
import StepConnectDevice, { StepConnectDeviceFooter } from './steps/02-step-connect-device' |
|
|
|
import StepConfirmAddress, { StepConfirmAddressFooter } from './steps/03-step-confirm-address' |
|
|
|
import StepReceiveFunds, { StepReceiveFundsFooter } from './steps/04-step-receive-funds' |
|
|
|
|
|
|
|
type Props = { |
|
|
|
t: T, |
|
|
|
device: ?Device, |
|
|
|
accounts: Account[], |
|
|
|
closeModal: string => void, |
|
|
|
} |
|
|
|
|
|
|
|
type State = { |
|
|
|
account: Account | null, |
|
|
|
addressVerified: null | boolean, |
|
|
|
amount: string | number, |
|
|
|
appStatus: null | string, |
|
|
|
deviceSelected: Device | null, |
|
|
|
stepIndex: number, |
|
|
|
stepsDisabled: Array<number>, |
|
|
|
stepsErrors: Array<number>, |
|
|
|
stepId: string, |
|
|
|
account: ?Account, |
|
|
|
isAppOpened: boolean, |
|
|
|
isAddressVerified: ?boolean, |
|
|
|
disabledSteps: number[], |
|
|
|
errorSteps: number[], |
|
|
|
} |
|
|
|
|
|
|
|
const GET_STEPS = t => [ |
|
|
|
{ label: t('app:receive.steps.chooseAccount.title'), Comp: StepAccount }, |
|
|
|
{ label: t('app:receive.steps.connectDevice.title'), Comp: StepConnectDevice }, |
|
|
|
{ label: t('app:receive.steps.confirmAddress.title'), Comp: StepConfirmAddress }, |
|
|
|
{ label: t('app:receive.steps.receiveFunds.title'), Comp: StepReceiveFunds }, |
|
|
|
] |
|
|
|
|
|
|
|
const INITIAL_STATE = { |
|
|
|
account: null, |
|
|
|
addressVerified: null, |
|
|
|
amount: '', |
|
|
|
appStatus: null, |
|
|
|
deviceSelected: null, |
|
|
|
stepIndex: 0, |
|
|
|
stepsDisabled: [], |
|
|
|
stepsErrors: [], |
|
|
|
// FIXME the two above can be derivated from other info (if we keep error etc)
|
|
|
|
// we can get rid of it after a big refactoring (see how done in Send)
|
|
|
|
export type StepProps = DefaultStepProps & { |
|
|
|
device: ?Device, |
|
|
|
account: ?Account, |
|
|
|
closeModal: void => void, |
|
|
|
isAppOpened: boolean, |
|
|
|
isAddressVerified: ?boolean, |
|
|
|
onSkipConfirm: void => void, |
|
|
|
onResetSkip: void => void, |
|
|
|
onChangeAccount: (?Account) => void, |
|
|
|
onChangeAppOpened: boolean => void, |
|
|
|
onChangeAddressVerified: boolean => void, |
|
|
|
} |
|
|
|
|
|
|
|
const createSteps = ({ t }: { t: T }) => [ |
|
|
|
{ |
|
|
|
id: 'account', |
|
|
|
label: t('app:receive.steps.chooseAccount.title'), |
|
|
|
component: StepAccount, |
|
|
|
footer: StepAccountFooter, |
|
|
|
}, |
|
|
|
{ |
|
|
|
id: 'device', |
|
|
|
label: t('app:receive.steps.connectDevice.title'), |
|
|
|
component: StepConnectDevice, |
|
|
|
footer: StepConnectDeviceFooter, |
|
|
|
onBack: ({ transitionTo }: StepProps) => transitionTo('account'), |
|
|
|
}, |
|
|
|
{ |
|
|
|
id: 'confirm', |
|
|
|
label: t('app:receive.steps.confirmAddress.title'), |
|
|
|
component: StepConfirmAddress, |
|
|
|
footer: StepConfirmAddressFooter, |
|
|
|
shouldRenderFooter: ({ isAddressVerified }: StepProps) => isAddressVerified === false, |
|
|
|
shouldPreventClose: ({ isAddressVerified }: StepProps) => isAddressVerified === null, |
|
|
|
}, |
|
|
|
{ |
|
|
|
id: 'receive', |
|
|
|
label: t('app:receive.steps.receiveFunds.title'), |
|
|
|
component: StepReceiveFunds, |
|
|
|
footer: StepReceiveFundsFooter, |
|
|
|
}, |
|
|
|
] |
|
|
|
|
|
|
|
const mapStateToProps = createStructuredSelector({ |
|
|
|
device: getCurrentDevice, |
|
|
|
accounts: accountsSelector, |
|
|
|
}) |
|
|
|
|
|
|
|
class ReceiveModal extends PureComponent<Props, State> { |
|
|
|
state = INITIAL_STATE |
|
|
|
|
|
|
|
_steps = GET_STEPS(this.props.t) |
|
|
|
|
|
|
|
canNext = () => { |
|
|
|
const { account, stepIndex } = this.state |
|
|
|
|
|
|
|
if (stepIndex === 0) { |
|
|
|
return account !== null |
|
|
|
} |
|
|
|
|
|
|
|
if (stepIndex === 1) { |
|
|
|
const { deviceSelected, appStatus } = this.state |
|
|
|
return deviceSelected !== null && appStatus === 'success' |
|
|
|
} |
|
|
|
|
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
canClose = () => { |
|
|
|
const { stepIndex, addressVerified } = this.state |
|
|
|
|
|
|
|
if (stepIndex === 2) { |
|
|
|
return addressVerified === false |
|
|
|
} |
|
|
|
|
|
|
|
return true |
|
|
|
} |
|
|
|
|
|
|
|
canPrev = () => { |
|
|
|
const { addressVerified, stepIndex } = this.state |
|
|
|
|
|
|
|
if (stepIndex === 1) { |
|
|
|
return true |
|
|
|
} |
|
|
|
|
|
|
|
if (stepIndex === 2) { |
|
|
|
return addressVerified === false |
|
|
|
} |
|
|
|
|
|
|
|
if (stepIndex === 3) { |
|
|
|
return true |
|
|
|
} |
|
|
|
|
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
handleReset = () => this.setState(INITIAL_STATE) |
|
|
|
|
|
|
|
handleNextStep = () => { |
|
|
|
const { stepIndex } = this.state |
|
|
|
if (stepIndex >= this._steps.length - 1) { |
|
|
|
return |
|
|
|
} |
|
|
|
this.setState({ stepIndex: stepIndex + 1 }) |
|
|
|
|
|
|
|
// TODO: do that better
|
|
|
|
if (stepIndex === 1) { |
|
|
|
this.verifyAddress() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
handlePrevStep = () => { |
|
|
|
const { stepIndex } = this.state |
|
|
|
|
|
|
|
let newStepIndex |
|
|
|
|
|
|
|
switch (stepIndex) { |
|
|
|
default: |
|
|
|
case 1: |
|
|
|
newStepIndex = 0 |
|
|
|
break |
|
|
|
|
|
|
|
case 2: |
|
|
|
case 3: |
|
|
|
newStepIndex = 1 |
|
|
|
break |
|
|
|
} |
|
|
|
|
|
|
|
this.setState({ |
|
|
|
addressVerified: null, |
|
|
|
appStatus: null, |
|
|
|
deviceSelected: null, |
|
|
|
stepIndex: newStepIndex, |
|
|
|
stepsDisabled: [], |
|
|
|
stepsErrors: [], |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
handleChangeDevice = d => this.setState({ deviceSelected: d }) |
|
|
|
|
|
|
|
handleChangeAccount = account => this.setState({ account }) |
|
|
|
|
|
|
|
handleChangeStatus = (deviceStatus, appStatus) => this.setState({ appStatus }) |
|
|
|
|
|
|
|
handleCheckAddress = isVerified => { |
|
|
|
this.setState({ |
|
|
|
addressVerified: isVerified, |
|
|
|
stepsErrors: isVerified === false ? [2] : [], |
|
|
|
}) |
|
|
|
|
|
|
|
if (isVerified === true) { |
|
|
|
this.handleNextStep() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
handleRetryCheckAddress = () => { |
|
|
|
this.setState({ |
|
|
|
addressVerified: null, |
|
|
|
stepsErrors: [], |
|
|
|
}) |
|
|
|
const mapDispatchToProps = { |
|
|
|
closeModal, |
|
|
|
} |
|
|
|
|
|
|
|
// TODO: do that better
|
|
|
|
this.verifyAddress() |
|
|
|
} |
|
|
|
const INITIAL_STATE = { |
|
|
|
stepId: 'account', |
|
|
|
account: null, |
|
|
|
isAppOpened: false, |
|
|
|
isAddressVerified: null, |
|
|
|
disabledSteps: [], |
|
|
|
errorSteps: [], |
|
|
|
} |
|
|
|
|
|
|
|
handleChangeAmount = amount => this.setState({ amount }) |
|
|
|
class ReceiveModal extends PureComponent<Props, State> { |
|
|
|
state = INITIAL_STATE |
|
|
|
STEPS = createSteps({ t: this.props.t }) |
|
|
|
|
|
|
|
handleBeforeOpenModal = ({ data }) => { |
|
|
|
const { account } = this.state |
|
|
@ -194,174 +113,87 @@ class ReceiveModal extends PureComponent<Props, State> { |
|
|
|
|
|
|
|
if (!account) { |
|
|
|
if (data && data.account) { |
|
|
|
this.setState({ |
|
|
|
account: data.account, |
|
|
|
stepIndex: 1, |
|
|
|
}) |
|
|
|
this.setState({ account: data.account }) |
|
|
|
} else { |
|
|
|
this.setState({ |
|
|
|
account: accounts[0], |
|
|
|
}) |
|
|
|
this.setState({ account: accounts[0] }) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
handleSkipStep = () => |
|
|
|
this.setState({ |
|
|
|
addressVerified: false, |
|
|
|
stepsErrors: [], |
|
|
|
stepsDisabled: [1, 2], |
|
|
|
stepIndex: this._steps.length - 1, // last step
|
|
|
|
}) |
|
|
|
|
|
|
|
verifyAddress = async () => { |
|
|
|
const { account, deviceSelected: device } = this.state |
|
|
|
try { |
|
|
|
if (account && device) { |
|
|
|
const { address } = await getAddress |
|
|
|
.send({ |
|
|
|
currencyId: account.currency.id, |
|
|
|
devicePath: device.path, |
|
|
|
path: account.freshAddressPath, |
|
|
|
segwit: isSegwitAccount(account), |
|
|
|
verify: true, |
|
|
|
}) |
|
|
|
.toPromise() |
|
|
|
|
|
|
|
if (address !== account.freshAddress) { |
|
|
|
throw new WrongDeviceForAccount(`WrongDeviceForAccount ${account.name}`, { |
|
|
|
accountName: account.name, |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
this.handleCheckAddress(true) |
|
|
|
} else { |
|
|
|
this.handleCheckAddress(false) |
|
|
|
handleReset = () => this.setState({ ...INITIAL_STATE }) |
|
|
|
handleCloseModal = () => this.props.closeModal(MODAL_RECEIVE) |
|
|
|
handleStepChange = step => this.setState({ stepId: step.id }) |
|
|
|
handleChangeAccount = (account: ?Account) => this.setState({ account }) |
|
|
|
handleChangeAppOpened = (isAppOpened: boolean) => this.setState({ isAppOpened }) |
|
|
|
handleChangeAddressVerified = (isAddressVerified: boolean) => { |
|
|
|
if (isAddressVerified) { |
|
|
|
this.setState({ isAddressVerified }) |
|
|
|
} else { |
|
|
|
const confirmStepIndex = this.STEPS.findIndex(step => step.id === 'confirm') |
|
|
|
if (confirmStepIndex > -1) { |
|
|
|
this.setState({ |
|
|
|
isAddressVerified, |
|
|
|
errorSteps: [confirmStepIndex], |
|
|
|
}) |
|
|
|
} |
|
|
|
} catch (err) { |
|
|
|
this.handleCheckAddress(false) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
renderStep = () => { |
|
|
|
const { account, amount, addressVerified, deviceSelected, stepIndex } = this.state |
|
|
|
const { t } = this.props |
|
|
|
const step = this._steps[stepIndex] |
|
|
|
if (!step) { |
|
|
|
return null |
|
|
|
} |
|
|
|
const { Comp } = step |
|
|
|
|
|
|
|
const props = (predicate, props) => (predicate ? props : {}) |
|
|
|
|
|
|
|
const stepProps = { |
|
|
|
t, |
|
|
|
account, |
|
|
|
...props(stepIndex === 0, { |
|
|
|
onChangeAccount: this.handleChangeAccount, |
|
|
|
}), |
|
|
|
...props(stepIndex === 1, { |
|
|
|
accountName: account ? account.name : undefined, |
|
|
|
deviceSelected, |
|
|
|
onChangeDevice: this.handleChangeDevice, |
|
|
|
onStatusChange: this.handleChangeStatus, |
|
|
|
}), |
|
|
|
...props(stepIndex === 2, { |
|
|
|
addressVerified, |
|
|
|
onCheck: this.handleCheckAddress, |
|
|
|
device: deviceSelected, |
|
|
|
}), |
|
|
|
...props(stepIndex === 3, { |
|
|
|
addressVerified, |
|
|
|
amount, |
|
|
|
onChangeAmount: this.handleChangeAmount, |
|
|
|
onVerify: this.handlePrevStep, |
|
|
|
}), |
|
|
|
handleResetSkip = () => this.setState({ disabledSteps: [] }) |
|
|
|
handleSkipConfirm = () => { |
|
|
|
const connectStepIndex = this.STEPS.findIndex(step => step.id === 'device') |
|
|
|
const confirmStepIndex = this.STEPS.findIndex(step => step.id === 'confirm') |
|
|
|
if (confirmStepIndex > -1 && connectStepIndex > -1) { |
|
|
|
this.setState({ disabledSteps: [connectStepIndex, confirmStepIndex] }) |
|
|
|
} |
|
|
|
|
|
|
|
return <Comp {...stepProps} /> |
|
|
|
} |
|
|
|
|
|
|
|
renderButton = () => { |
|
|
|
const { t } = this.props |
|
|
|
const { stepIndex, addressVerified } = this.state |
|
|
|
|
|
|
|
let onClick |
|
|
|
let props |
|
|
|
|
|
|
|
switch (stepIndex) { |
|
|
|
case 2: |
|
|
|
props = { |
|
|
|
primary: true, |
|
|
|
onClick: this.handleRetryCheckAddress, |
|
|
|
children: t('app:common.retry'), |
|
|
|
} |
|
|
|
break |
|
|
|
default: |
|
|
|
onClick = this.handleNextStep |
|
|
|
props = { |
|
|
|
primary: true, |
|
|
|
disabled: !this.canNext(), |
|
|
|
onClick, |
|
|
|
children: t('app:common.next'), |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return ( |
|
|
|
<Fragment> |
|
|
|
{stepIndex === 1 && ( |
|
|
|
<Button onClick={this.handleSkipStep} fontSize={4}> |
|
|
|
{t('app:receive.steps.connectDevice.withoutDevice')} |
|
|
|
</Button> |
|
|
|
)} |
|
|
|
{stepIndex === 2 && |
|
|
|
addressVerified === false && ( |
|
|
|
<Button fontSize={4}>{t('app:receive.steps.confirmAddress.support')}</Button> |
|
|
|
)} |
|
|
|
<Button {...props} /> |
|
|
|
</Fragment> |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
render() { |
|
|
|
const { t } = this.props |
|
|
|
const { stepsErrors, stepsDisabled, stepIndex, account } = this.state |
|
|
|
const { t, device } = this.props |
|
|
|
const { |
|
|
|
stepId, |
|
|
|
account, |
|
|
|
isAppOpened, |
|
|
|
isAddressVerified, |
|
|
|
disabledSteps, |
|
|
|
errorSteps, |
|
|
|
} = this.state |
|
|
|
|
|
|
|
const addtionnalProps = { |
|
|
|
device, |
|
|
|
account, |
|
|
|
isAppOpened, |
|
|
|
isAddressVerified, |
|
|
|
closeModal: this.handleCloseModal, |
|
|
|
onSkipConfirm: this.handleSkipConfirm, |
|
|
|
onResetSkip: this.handleResetSkip, |
|
|
|
onChangeAccount: this.handleChangeAccount, |
|
|
|
onChangeAppOpened: this.handleChangeAppOpened, |
|
|
|
onChangeAddressVerified: this.handleChangeAddressVerified, |
|
|
|
} |
|
|
|
|
|
|
|
const canClose = this.canClose() |
|
|
|
const canPrev = this.canPrev() |
|
|
|
const isModalLocked = stepId === 'confirm' && isAddressVerified === null |
|
|
|
|
|
|
|
return ( |
|
|
|
<Modal |
|
|
|
name={MODAL_RECEIVE} |
|
|
|
onBeforeOpen={this.handleBeforeOpenModal} |
|
|
|
refocusWhenChange={stepId} |
|
|
|
onHide={this.handleReset} |
|
|
|
preventBackdropClick={!canClose} |
|
|
|
preventBackdropClick={isModalLocked} |
|
|
|
onBeforeOpen={this.handleBeforeOpenModal} |
|
|
|
render={({ onClose }) => ( |
|
|
|
<ModalBody onClose={canClose ? onClose : undefined}> |
|
|
|
<SyncSkipUnderPriority priority={9} /> |
|
|
|
{account && <SyncOneAccountOnMount priority={10} accountId={account.id} />} |
|
|
|
<ModalTitle onBack={canPrev ? this.handlePrevStep : undefined}> |
|
|
|
{t('app:receive.title')} |
|
|
|
</ModalTitle> |
|
|
|
<ModalContent> |
|
|
|
<Breadcrumb |
|
|
|
mb={6} |
|
|
|
currentStep={stepIndex} |
|
|
|
stepsErrors={stepsErrors} |
|
|
|
stepsDisabled={stepsDisabled} |
|
|
|
items={this._steps} |
|
|
|
/> |
|
|
|
{this.renderStep()} |
|
|
|
</ModalContent> |
|
|
|
{stepIndex !== 3 && |
|
|
|
canClose && ( |
|
|
|
<ModalFooter> |
|
|
|
<Box horizontal alignItems="center" justifyContent="flex-end" flow={2}> |
|
|
|
{this.renderButton()} |
|
|
|
</Box> |
|
|
|
</ModalFooter> |
|
|
|
)} |
|
|
|
</ModalBody> |
|
|
|
<Stepper |
|
|
|
title={t('app:receive.title')} |
|
|
|
initialStepId={stepId} |
|
|
|
onStepChange={this.handleStepChange} |
|
|
|
onClose={onClose} |
|
|
|
steps={this.STEPS} |
|
|
|
disabledSteps={disabledSteps} |
|
|
|
errorSteps={errorSteps} |
|
|
|
{...addtionnalProps} |
|
|
|
> |
|
|
|
<SyncSkipUnderPriority priority={100} /> |
|
|
|
</Stepper> |
|
|
|
)} |
|
|
|
/> |
|
|
|
) |
|
|
@ -369,6 +201,9 @@ class ReceiveModal extends PureComponent<Props, State> { |
|
|
|
} |
|
|
|
|
|
|
|
export default compose( |
|
|
|
connect(mapStateToProps), |
|
|
|
connect( |
|
|
|
mapStateToProps, |
|
|
|
mapDispatchToProps, |
|
|
|
), |
|
|
|
translate(), |
|
|
|
)(ReceiveModal) |
|
|
|