meriadec
7 years ago
13 changed files with 505 additions and 636 deletions
@ -1,102 +0,0 @@ |
|||||
// @flow
|
|
||||
import React, { Fragment } from 'react' |
|
||||
import type { Account } from '@ledgerhq/live-common/lib/types' |
|
||||
import type { T } from 'types/common' |
|
||||
import type { WalletBridge } from 'bridge/types' |
|
||||
import TrackPage from 'analytics/TrackPage' |
|
||||
import Box from 'components/base/Box' |
|
||||
import AccountField from './AccountField' |
|
||||
import RecipientField from './RecipientField' |
|
||||
import AmountField from './AmountField' |
|
||||
|
|
||||
type PropsStepAmount<Transaction> = { |
|
||||
t: T, |
|
||||
account: ?Account, |
|
||||
bridge: ?WalletBridge<Transaction>, |
|
||||
transaction: ?Transaction, |
|
||||
onChangeAccount: Account => void, |
|
||||
onChangeTransaction: Transaction => void, |
|
||||
} |
|
||||
|
|
||||
function StepAmount({ |
|
||||
t, |
|
||||
account, |
|
||||
bridge, |
|
||||
transaction, |
|
||||
onChangeAccount, |
|
||||
onChangeTransaction, |
|
||||
}: PropsStepAmount<*>) { |
|
||||
// TODO need to split each field into a component
|
|
||||
const FeesField = bridge && bridge.EditFees |
|
||||
const AdvancedOptionsField = bridge && bridge.EditAdvancedOptions |
|
||||
|
|
||||
return ( |
|
||||
<Box flow={4}> |
|
||||
<TrackPage category="Send" name="Step1" /> |
|
||||
|
|
||||
<AccountField t={t} onChange={onChangeAccount} value={account} /> |
|
||||
|
|
||||
{/* HACK HACK HACK WTF */} |
|
||||
<div hidden> |
|
||||
{account && |
|
||||
bridge && |
|
||||
transaction && ( |
|
||||
<RecipientField |
|
||||
account={account} |
|
||||
bridge={bridge} |
|
||||
transaction={transaction} |
|
||||
onChangeTransaction={onChangeTransaction} |
|
||||
t={t} |
|
||||
/> |
|
||||
)} |
|
||||
{account && |
|
||||
bridge && |
|
||||
transaction && ( |
|
||||
<AmountField |
|
||||
account={account} |
|
||||
bridge={bridge} |
|
||||
transaction={transaction} |
|
||||
onChangeTransaction={onChangeTransaction} |
|
||||
t={t} |
|
||||
/> |
|
||||
)} |
|
||||
</div> |
|
||||
{/* HACK HACK HACK WTF */} |
|
||||
|
|
||||
{account && bridge && transaction ? ( |
|
||||
<Fragment key={account.id}> |
|
||||
<RecipientField |
|
||||
autoFocus |
|
||||
account={account} |
|
||||
bridge={bridge} |
|
||||
transaction={transaction} |
|
||||
onChangeTransaction={onChangeTransaction} |
|
||||
t={t} |
|
||||
/> |
|
||||
|
|
||||
<AmountField |
|
||||
account={account} |
|
||||
bridge={bridge} |
|
||||
transaction={transaction} |
|
||||
onChangeTransaction={onChangeTransaction} |
|
||||
t={t} |
|
||||
/> |
|
||||
|
|
||||
{FeesField && ( |
|
||||
<FeesField account={account} value={transaction} onChange={onChangeTransaction} /> |
|
||||
)} |
|
||||
|
|
||||
{AdvancedOptionsField && ( |
|
||||
<AdvancedOptionsField |
|
||||
account={account} |
|
||||
value={transaction} |
|
||||
onChange={onChangeTransaction} |
|
||||
/> |
|
||||
)} |
|
||||
</Fragment> |
|
||||
) : null} |
|
||||
</Box> |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
export default StepAmount |
|
@ -1,16 +0,0 @@ |
|||||
import React, { Component, Fragment } from 'react' |
|
||||
import TrackPage from 'analytics/TrackPage' |
|
||||
import StepConnectDevice from '../StepConnectDevice' |
|
||||
|
|
||||
class SendStepConnectDevice extends Component<*> { |
|
||||
render() { |
|
||||
return ( |
|
||||
<Fragment> |
|
||||
<TrackPage category="Send" name="Step2" /> |
|
||||
<StepConnectDevice {...this.props} /> |
|
||||
</Fragment> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default SendStepConnectDevice |
|
@ -1,41 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import React from 'react' |
|
||||
import styled from 'styled-components' |
|
||||
|
|
||||
import TrackPage from 'analytics/TrackPage' |
|
||||
import Box from 'components/base/Box' |
|
||||
import WarnBox from 'components/WarnBox' |
|
||||
import { multiline } from 'styles/helpers' |
|
||||
import DeviceConfirm from 'components/DeviceConfirm' |
|
||||
|
|
||||
import type { T } from 'types/common' |
|
||||
|
|
||||
const Container = styled(Box).attrs({ |
|
||||
alignItems: 'center', |
|
||||
fontSize: 4, |
|
||||
pb: 4, |
|
||||
})`` |
|
||||
|
|
||||
const Info = styled(Box).attrs({ |
|
||||
ff: 'Open Sans|SemiBold', |
|
||||
color: 'dark', |
|
||||
mt: 6, |
|
||||
mb: 4, |
|
||||
px: 5, |
|
||||
})` |
|
||||
text-align: center; |
|
||||
` |
|
||||
|
|
||||
type Props = { |
|
||||
t: T, |
|
||||
} |
|
||||
|
|
||||
export default ({ t }: Props) => ( |
|
||||
<Container> |
|
||||
<TrackPage category="Send" name="Step3" /> |
|
||||
<WarnBox>{multiline(t('app:send.steps.verification.warning'))}</WarnBox> |
|
||||
<Info>{t('app:send.steps.verification.body')}</Info> |
|
||||
<DeviceConfirm /> |
|
||||
</Container> |
|
||||
) |
|
@ -1,14 +0,0 @@ |
|||||
// @flow
|
|
||||
import React from 'react' |
|
||||
import Box from 'components/base/Box' |
|
||||
import Label from 'components/base/Label' |
|
||||
import SelectAccount from 'components/SelectAccount' |
|
||||
|
|
||||
const AccountField = ({ onChange, value, t }: *) => ( |
|
||||
<Box flow={1}> |
|
||||
<Label>{t('app:send.steps.amount.selectAccountDebit')}</Label> |
|
||||
<SelectAccount onChange={onChange} value={value} /> |
|
||||
</Box> |
|
||||
) |
|
||||
|
|
||||
export default AccountField |
|
@ -1,50 +0,0 @@ |
|||||
// @flow
|
|
||||
import React from 'react' |
|
||||
import type { Operation, Account } from '@ledgerhq/live-common/lib/types' |
|
||||
import { shell } from 'electron' |
|
||||
import Button from 'components/base/Button' |
|
||||
import { ModalFooter } from 'components/base/Modal' |
|
||||
import { getAccountOperationExplorer } from '@ledgerhq/live-common/lib/explorers' |
|
||||
import type { T } from 'types/common' |
|
||||
|
|
||||
export default ({ |
|
||||
t, |
|
||||
error, |
|
||||
account, |
|
||||
optimisticOperation, |
|
||||
onClose, |
|
||||
onGoToFirstStep, |
|
||||
}: { |
|
||||
t: T, |
|
||||
error: ?Error, |
|
||||
account: ?Account, |
|
||||
optimisticOperation: ?Operation, |
|
||||
onClose: () => void, |
|
||||
onGoToFirstStep: () => void, |
|
||||
}) => { |
|
||||
const url = |
|
||||
optimisticOperation && account && getAccountOperationExplorer(account, optimisticOperation) |
|
||||
return ( |
|
||||
<ModalFooter horizontal alignItems="center" justifyContent="flex-end" flow={2}> |
|
||||
<Button onClick={onClose}>{t('app:common.close')}</Button> |
|
||||
{optimisticOperation ? ( |
|
||||
// TODO: actually go to operations details
|
|
||||
url ? ( |
|
||||
<Button |
|
||||
onClick={() => { |
|
||||
shell.openExternal(url) |
|
||||
onClose() |
|
||||
}} |
|
||||
primary |
|
||||
> |
|
||||
{t('app:send.steps.confirmation.success.cta')} |
|
||||
</Button> |
|
||||
) : null |
|
||||
) : error ? ( |
|
||||
<Button onClick={onGoToFirstStep} primary> |
|
||||
{t('app:send.steps.confirmation.error.cta')} |
|
||||
</Button> |
|
||||
) : null} |
|
||||
</ModalFooter> |
|
||||
) |
|
||||
} |
|
@ -1,106 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import React, { PureComponent } from 'react' |
|
||||
import type { Account } from '@ledgerhq/live-common/lib/types' |
|
||||
|
|
||||
import type { T } from 'types/common' |
|
||||
|
|
||||
import { ModalFooter } from 'components/base/Modal' |
|
||||
import Box from 'components/base/Box' |
|
||||
import Button from 'components/base/Button' |
|
||||
import CounterValue from 'components/CounterValue' |
|
||||
import FormattedVal from 'components/base/FormattedVal' |
|
||||
import Label from 'components/base/Label' |
|
||||
import Text from 'components/base/Text' |
|
||||
import type { WalletBridge } from 'bridge/types' |
|
||||
|
|
||||
type Props = { |
|
||||
t: T, |
|
||||
account: Account, |
|
||||
bridge: WalletBridge<*>, |
|
||||
transaction: *, |
|
||||
onNext: () => void, |
|
||||
canNext: ?boolean, |
|
||||
showTotal: boolean, |
|
||||
} |
|
||||
|
|
||||
class Footer extends PureComponent< |
|
||||
Props, |
|
||||
{ |
|
||||
totalSpent: number, |
|
||||
canBeSpent: boolean, |
|
||||
}, |
|
||||
> { |
|
||||
state = { |
|
||||
totalSpent: 0, |
|
||||
canBeSpent: true, |
|
||||
} |
|
||||
componentDidMount() { |
|
||||
this.resync() |
|
||||
} |
|
||||
componentDidUpdate(nextProps: Props) { |
|
||||
if ( |
|
||||
nextProps.account !== this.props.account || |
|
||||
nextProps.transaction !== this.props.transaction |
|
||||
) { |
|
||||
this.resync() |
|
||||
} |
|
||||
} |
|
||||
componentWillUnmount() { |
|
||||
this.unmount = true |
|
||||
} |
|
||||
unmount = false |
|
||||
async resync() { |
|
||||
const { account, bridge, transaction } = this.props |
|
||||
const totalSpent = await bridge.getTotalSpent(account, transaction) |
|
||||
const canBeSpent = await bridge.canBeSpent(account, transaction) |
|
||||
if (this.unmount) return |
|
||||
this.setState({ totalSpent, canBeSpent }) |
|
||||
} |
|
||||
render() { |
|
||||
const { account, t, onNext, canNext, showTotal } = this.props |
|
||||
const { totalSpent, canBeSpent } = this.state |
|
||||
return ( |
|
||||
<ModalFooter> |
|
||||
<Box horizontal alignItems="center" justifyContent="flex-end" flow={2}> |
|
||||
{showTotal && ( |
|
||||
<Box grow> |
|
||||
<Label>{t('app:send.totalSpent')}</Label> |
|
||||
<Box horizontal flow={2} align="center"> |
|
||||
<FormattedVal |
|
||||
disableRounding |
|
||||
color="dark" |
|
||||
val={totalSpent} |
|
||||
unit={account.unit} |
|
||||
showCode |
|
||||
/> |
|
||||
<Box horizontal align="center"> |
|
||||
<Text ff="Rubik" fontSize={3}> |
|
||||
{'(' /* eslint-disable-line react/jsx-no-literals */} |
|
||||
</Text> |
|
||||
<CounterValue |
|
||||
currency={account.currency} |
|
||||
value={totalSpent} |
|
||||
disableRounding |
|
||||
color="grey" |
|
||||
fontSize={3} |
|
||||
showCode |
|
||||
alwaysShowSign={false} |
|
||||
/> |
|
||||
<Text ff="Rubik" fontSize={3}> |
|
||||
{')' /* eslint-disable-line react/jsx-no-literals */} |
|
||||
</Text> |
|
||||
</Box> |
|
||||
</Box> |
|
||||
</Box> |
|
||||
)} |
|
||||
<Button primary onClick={onNext} disabled={!canNext || !canBeSpent}> |
|
||||
{t('app:common.next')} |
|
||||
</Button> |
|
||||
</Box> |
|
||||
</ModalFooter> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default Footer |
|
@ -0,0 +1,187 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent, Fragment } from 'react' |
||||
|
|
||||
|
import Box from 'components/base/Box' |
||||
|
import Button from 'components/base/Button' |
||||
|
import Label from 'components/base/Label' |
||||
|
import SelectAccount from 'components/SelectAccount' |
||||
|
import FormattedVal from 'components/base/FormattedVal' |
||||
|
import Text from 'components/base/Text' |
||||
|
import CounterValue from 'components/CounterValue' |
||||
|
import Spinner from 'components/base/Spinner' |
||||
|
|
||||
|
import RecipientField from '../fields/RecipientField' |
||||
|
import AmountField from '../fields/AmountField' |
||||
|
|
||||
|
import type { StepProps } from '../index' |
||||
|
|
||||
|
export default ({ |
||||
|
t, |
||||
|
account, |
||||
|
bridge, |
||||
|
transaction, |
||||
|
onChangeAccount, |
||||
|
onChangeTransaction, |
||||
|
}: StepProps<*>) => { |
||||
|
const FeesField = bridge && bridge.EditFees |
||||
|
const AdvancedOptionsField = bridge && bridge.EditAdvancedOptions |
||||
|
|
||||
|
// TODO: figure out why flow can't understand when we put conditions in variables
|
||||
|
// e.g:
|
||||
|
// const hasTransaction = !!account && !!bridge && !!transaction
|
||||
|
|
||||
|
return ( |
||||
|
<Box flow={4}> |
||||
|
<Box flow={1}> |
||||
|
<Label>{t('app:send.steps.amount.selectAccountDebit')}</Label> |
||||
|
<SelectAccount onChange={onChangeAccount} value={account} /> |
||||
|
</Box> |
||||
|
|
||||
|
{account && |
||||
|
bridge && |
||||
|
transaction && ( |
||||
|
<RecipientField |
||||
|
autoFocus |
||||
|
account={account} |
||||
|
bridge={bridge} |
||||
|
transaction={transaction} |
||||
|
onChangeTransaction={onChangeTransaction} |
||||
|
t={t} |
||||
|
/> |
||||
|
)} |
||||
|
|
||||
|
{account && |
||||
|
bridge && |
||||
|
transaction && ( |
||||
|
<AmountField |
||||
|
account={account} |
||||
|
bridge={bridge} |
||||
|
transaction={transaction} |
||||
|
onChangeTransaction={onChangeTransaction} |
||||
|
t={t} |
||||
|
/> |
||||
|
)} |
||||
|
|
||||
|
{account && |
||||
|
bridge && |
||||
|
transaction && |
||||
|
FeesField && ( |
||||
|
<FeesField account={account} value={transaction} onChange={onChangeTransaction} /> |
||||
|
)} |
||||
|
|
||||
|
{account && |
||||
|
bridge && |
||||
|
transaction && |
||||
|
AdvancedOptionsField && ( |
||||
|
<AdvancedOptionsField |
||||
|
account={account} |
||||
|
value={transaction} |
||||
|
onChange={onChangeTransaction} |
||||
|
/> |
||||
|
)} |
||||
|
</Box> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export class StepAmountFooter extends PureComponent< |
||||
|
StepProps<*>, |
||||
|
{ |
||||
|
totalSpent: number, |
||||
|
canBeSpent: boolean, |
||||
|
isSyncing: boolean, |
||||
|
}, |
||||
|
> { |
||||
|
state = { |
||||
|
isSyncing: false, |
||||
|
totalSpent: 0, |
||||
|
canBeSpent: true, |
||||
|
} |
||||
|
|
||||
|
componentDidMount() { |
||||
|
this.resync() |
||||
|
} |
||||
|
|
||||
|
componentDidUpdate(nextProps: StepProps<*>) { |
||||
|
if ( |
||||
|
nextProps.account !== this.props.account || |
||||
|
nextProps.transaction !== this.props.transaction |
||||
|
) { |
||||
|
this.resync() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
componentWillUnmount() { |
||||
|
this._isUnmounted = true |
||||
|
} |
||||
|
|
||||
|
_isUnmounted = false |
||||
|
|
||||
|
async resync() { |
||||
|
const { account, bridge, transaction } = this.props |
||||
|
|
||||
|
if (!account || !transaction || !bridge) { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
this.setState({ isSyncing: true }) |
||||
|
|
||||
|
try { |
||||
|
const totalSpent = await bridge.getTotalSpent(account, transaction) |
||||
|
if (this._isUnmounted) return |
||||
|
const canBeSpent = await bridge.canBeSpent(account, transaction) |
||||
|
if (this._isUnmounted) return |
||||
|
this.setState({ totalSpent, canBeSpent, isSyncing: false }) |
||||
|
} catch (err) { |
||||
|
this.setState({ isSyncing: false }) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { t, transitionTo, account, transaction, bridge } = this.props |
||||
|
const { totalSpent, canBeSpent, isSyncing } = this.state |
||||
|
const canNext = |
||||
|
account && transaction && bridge && bridge.isValidTransaction(account, transaction) |
||||
|
return ( |
||||
|
<Fragment> |
||||
|
<Box grow> |
||||
|
<Label>{t('app:send.totalSpent')}</Label> |
||||
|
<Box horizontal flow={2} align="center"> |
||||
|
{account && ( |
||||
|
<FormattedVal |
||||
|
disableRounding |
||||
|
color="dark" |
||||
|
val={totalSpent} |
||||
|
unit={account.unit} |
||||
|
showCode |
||||
|
/> |
||||
|
)} |
||||
|
<Box horizontal align="center"> |
||||
|
<Text ff="Rubik" fontSize={3}> |
||||
|
{'(' /* eslint-disable-line react/jsx-no-literals */} |
||||
|
</Text> |
||||
|
{account && ( |
||||
|
<CounterValue |
||||
|
currency={account.currency} |
||||
|
value={totalSpent} |
||||
|
disableRounding |
||||
|
color="grey" |
||||
|
fontSize={3} |
||||
|
showCode |
||||
|
alwaysShowSign={false} |
||||
|
/> |
||||
|
)} |
||||
|
<Text ff="Rubik" fontSize={3}> |
||||
|
{')' /* eslint-disable-line react/jsx-no-literals */} |
||||
|
</Text> |
||||
|
</Box> |
||||
|
{isSyncing && <Spinner size={10} />} |
||||
|
</Box> |
||||
|
</Box> |
||||
|
<Button disabled={!canBeSpent || !canNext} primary onClick={() => transitionTo('device')}> |
||||
|
{t('app:common.next')} |
||||
|
</Button> |
||||
|
</Fragment> |
||||
|
) |
||||
|
} |
||||
|
} |
@ -0,0 +1,30 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Fragment } from 'react' |
||||
|
|
||||
|
import TrackPage from 'analytics/TrackPage' |
||||
|
import Button from 'components/base/Button' |
||||
|
import EnsureDeviceApp from 'components/EnsureDeviceApp' |
||||
|
|
||||
|
import type { StepProps } from '../index' |
||||
|
|
||||
|
export default function StepConnectDevice({ account, onChangeAppOpened }: StepProps<*>) { |
||||
|
return ( |
||||
|
<Fragment> |
||||
|
<TrackPage category="Send" name="Step2" /> |
||||
|
<EnsureDeviceApp |
||||
|
account={account} |
||||
|
waitBeforeSuccess={200} |
||||
|
onSuccess={() => onChangeAppOpened(true)} |
||||
|
/> |
||||
|
</Fragment> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export function StepConnectDeviceFooter({ t, transitionTo, isAppOpened }: StepProps<*>) { |
||||
|
return ( |
||||
|
<Button disabled={!isAppOpened} primary onClick={() => transitionTo('verification')}> |
||||
|
{t('app:common.next')} |
||||
|
</Button> |
||||
|
) |
||||
|
} |
@ -0,0 +1,86 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import invariant from 'invariant' |
||||
|
import React, { PureComponent } from 'react' |
||||
|
import styled from 'styled-components' |
||||
|
|
||||
|
import { multiline } from 'styles/helpers' |
||||
|
import { createCustomErrorClass } from 'helpers/errors' |
||||
|
|
||||
|
import TrackPage from 'analytics/TrackPage' |
||||
|
import Box from 'components/base/Box' |
||||
|
import WarnBox from 'components/WarnBox' |
||||
|
import DeviceConfirm from 'components/DeviceConfirm' |
||||
|
|
||||
|
import type { StepProps } from '../index' |
||||
|
|
||||
|
export const UserRefusedOnDevice = createCustomErrorClass('UserRefusedOnDevice') |
||||
|
|
||||
|
const Container = styled(Box).attrs({ alignItems: 'center', fontSize: 4, pb: 4 })`` |
||||
|
const Info = styled(Box).attrs({ ff: 'Open Sans|SemiBold', color: 'dark', mt: 6, mb: 4, px: 5 })` |
||||
|
text-align: center; |
||||
|
` |
||||
|
|
||||
|
export default class StepVerification extends PureComponent<StepProps<*>> { |
||||
|
componentDidMount() { |
||||
|
this.signTransaction() |
||||
|
} |
||||
|
|
||||
|
componentWillUnmount() { |
||||
|
this._isUnmounted = true |
||||
|
if (this._signTransactionSub) { |
||||
|
this._signTransactionSub.unsubscribe() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
_isUnmounted = false |
||||
|
_signTransactionSub = null |
||||
|
|
||||
|
signTransaction = async () => { |
||||
|
const { |
||||
|
device, |
||||
|
account, |
||||
|
transaction, |
||||
|
bridge, |
||||
|
onTransactionError, |
||||
|
transitionTo, |
||||
|
onOperationBroadcasted, |
||||
|
} = this.props |
||||
|
invariant(device && account && transaction && bridge, 'signTransaction invalid conditions') |
||||
|
this._signTransactionSub = bridge |
||||
|
.signAndBroadcast(account, transaction, device.path) |
||||
|
.subscribe({ |
||||
|
next: e => { |
||||
|
switch (e.type) { |
||||
|
case 'signed': { |
||||
|
if (this._isUnmounted) return |
||||
|
transitionTo('confirmation') |
||||
|
break |
||||
|
} |
||||
|
case 'broadcasted': { |
||||
|
onOperationBroadcasted(e.operation) |
||||
|
transitionTo('confirmation') |
||||
|
break |
||||
|
} |
||||
|
default: |
||||
|
} |
||||
|
}, |
||||
|
error: err => { |
||||
|
const error = err.statusCode === 0x6985 ? new UserRefusedOnDevice() : err |
||||
|
onTransactionError(error) |
||||
|
transitionTo('confirmation') |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
render() { |
||||
|
const { t } = this.props |
||||
|
return ( |
||||
|
<Container> |
||||
|
<TrackPage category="Send" name="Step3" /> |
||||
|
<WarnBox>{multiline(t('app:send.steps.verification.warning'))}</WarnBox> |
||||
|
<Info>{t('app:send.steps.verification.body')}</Info> |
||||
|
<DeviceConfirm /> |
||||
|
</Container> |
||||
|
) |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue