1
0
mirror of https://github.com/trezor/trezor-wallet synced 2024-11-24 01:08:27 +00:00

Refactoring: SelectedAccountReducer synchronized with other reducers

thru WalletService + implementation new data model in components
This commit is contained in:
Szymon Lesisz 2018-05-25 11:26:36 +02:00
parent d74c2ceaba
commit 283af40356
22 changed files with 792 additions and 889 deletions

View File

@ -1,66 +1,98 @@
/* @flow */ /* @flow */
'use strict'; 'use strict';
import { LOCATION_CHANGE } from 'react-router-redux';
import * as ACCOUNT from './constants/account'; import * as ACCOUNT from './constants/account';
import * as SEND from './constants/send';
import * as SendFormActions from './SendFormActions';
import * as SessionStorageActions from './SessionStorageActions';
import * as stateUtils from '../reducers/utils';
import { initialState } from '../reducers/SelectedAccountReducer'; import { initialState } from '../reducers/SelectedAccountReducer';
import type { AsyncAction, ThunkAction, Action, GetState, Dispatch, TrezorDevice } from '~/flowtype'; import type {
import type { State } from '../reducers/SelectedAccountReducer'; Coin,
import type { Coin } from '../reducers/LocalStorageReducer'; TrezorDevice,
AsyncAction,
ThunkAction,
Action,
GetState,
Dispatch,
State,
} from '~/flowtype';
export type SelectedAccountAction = { export type SelectedAccountAction = {
type: typeof ACCOUNT.INIT,
state: State
} | {
type: typeof ACCOUNT.DISPOSE, type: typeof ACCOUNT.DISPOSE,
} | {
type: typeof ACCOUNT.UPDATE_SELECTED_ACCOUNT,
payload: $ElementType<State, 'selectedAccount'>
}; };
export const init = (): ThunkAction => { export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => {
return (dispatch: Dispatch, getState: GetState): void => { return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const { location } = getState().router; const locationChange: boolean = action.type === LOCATION_CHANGE;
const urlParams = location.state; const state: State = getState();
const location = state.router.location;
const selected: ?TrezorDevice = getState().wallet.selectedDevice;; let needUpdate: boolean = false;
if (!selected) return;
if (!selected.state || !selected.features) return;
const { config } = getState().localStorage; // reset form to default
const coin: ?Coin = config.coins.find(c => c.network === urlParams.network); if (action.type === SEND.TX_COMPLETE) {
if (!coin) return; dispatch( SendFormActions.init() );
// linear action
SessionStorageActions.clear(location.pathname);
}
const state: State = { // handle devices state change (from trezor-connect events or location change)
index: parseInt(urlParams.account), if (locationChange
deviceState: selected.state || '0', || prevState.accounts !== state.accounts
deviceId: selected.features ? selected.features.device_id : '0', || prevState.discovery !== state.discovery
deviceInstance: selected.instance, || prevState.tokens !== state.tokens
network: urlParams.network, || prevState.web3 !== state.web3) {
coin,
location: location.pathname, const account = stateUtils.getSelectedAccount(state);
}; const network = stateUtils.getSelectedNetwork(state);
const discovery = stateUtils.getDiscoveryProcess(state);
const tokens = stateUtils.getTokens(state, account);
const web3 = stateUtils.getWeb3(state);
dispatch({ const payload: $ElementType<State, 'selectedAccount'> = {
type: ACCOUNT.INIT, // location: location.pathname,
state: state account,
}); network,
discovery,
tokens,
web3
}
} let needUpdate: boolean = false;
} Object.keys(payload).forEach((key) => {
if (payload[key] !== state.selectedAccount[key]) {
needUpdate = true;
}
})
export const update = (initAccountAction: () => ThunkAction): ThunkAction => { if (needUpdate) {
return (dispatch: Dispatch, getState: GetState): void => { dispatch({
const { type: ACCOUNT.UPDATE_SELECTED_ACCOUNT,
selectedAccount, payload,
router })
} = getState(); }
const shouldReload: boolean = (!selectedAccount || router.location.pathname !== selectedAccount.location); if (locationChange) {
if (shouldReload) {
dispatch( dispose() ); if (prevState.router.location && prevState.router.location.state.send) {
dispatch( init() ); SessionStorageActions.save(prevState.router.location.pathname, state.sendForm);
if (selectedAccount !== null) }
initAccountAction();
dispatch( dispose() );
if (location.state.send) {
dispatch( SendFormActions.init( SessionStorageActions.load(location.pathname) ) );
}
}
} }
} }
} }
@ -69,10 +101,4 @@ export const dispose = (): Action => {
return { return {
type: ACCOUNT.DISPOSE type: ACCOUNT.DISPOSE
} }
}
export default {
init,
update,
dispose
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,27 @@
/* @flow */ /* @flow */
'use strict'; 'use strict';
import { LOCATION_CHANGE } from 'react-router-redux';
import * as WALLET from './constants/wallet'; import * as WALLET from './constants/wallet';
import * as stateUtils from '../reducers/utils';
import type { TrezorDevice, RouterLocationState, ThunkAction, Dispatch, GetState } from '~/flowtype'; import type
{
Account,
Coin,
Discovery,
Token,
Web3Instance,
TrezorDevice,
RouterLocationState,
ThunkAction,
AsyncAction,
Action,
Dispatch,
GetState,
State
} from '~/flowtype';
export type WalletAction = { export type WalletAction = {
type: typeof WALLET.SET_INITIAL_URL, type: typeof WALLET.SET_INITIAL_URL,
@ -51,3 +69,30 @@ export const toggleDeviceDropdown = (opened: boolean): WalletAction => {
opened opened
} }
} }
export const updateSelectedValues = (prevState: State, action: Action): AsyncAction => {
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const locationChange: boolean = action.type === LOCATION_CHANGE;
const state: State = getState();
// handle devices state change (from trezor-connect events or location change)
if (locationChange || prevState.devices !== state.devices) {
const device = stateUtils.getSelectedDevice(state);
if (state.wallet.selectedDevice !== device) {
if (device && stateUtils.isSelectedDevice(state.wallet.selectedDevice, device)) {
dispatch({
type: WALLET.UPDATE_SELECTED_DEVICE,
device
});
} else {
dispatch({
type: WALLET.SET_SELECTED_DEVICE,
device
});
}
}
}
}
}

View File

@ -8,4 +8,6 @@ export const CREATE: 'account__create' = 'account__create';
export const REMOVE: 'account__remove' = 'account__remove'; export const REMOVE: 'account__remove' = 'account__remove';
export const SET_BALANCE: 'account__set_balance' = 'account__set_balance'; export const SET_BALANCE: 'account__set_balance' = 'account__set_balance';
export const SET_NONCE: 'account__set_nonce' = 'account__set_nonce'; export const SET_NONCE: 'account__set_nonce' = 'account__set_nonce';
export const FROM_STORAGE: 'account__from_storage' = 'account__from_storage'; export const FROM_STORAGE: 'account__from_storage' = 'account__from_storage';
export const UPDATE_SELECTED_ACCOUNT: 'account__update_selected_account' = 'account__update_selected_account';

View File

@ -4,6 +4,7 @@
export const INIT: 'send__init' = 'send__init'; export const INIT: 'send__init' = 'send__init';
export const DISPOSE: 'send__dispose' = 'send__dispose'; export const DISPOSE: 'send__dispose' = 'send__dispose';
export const VALIDATION: 'send__validation' = 'send__validation'; export const VALIDATION: 'send__validation' = 'send__validation';
export const ADDRESS_VALIDATION: 'send__address_validation' = 'send__address_validation';
export const ADDRESS_CHANGE: 'send__address_change' = 'send__address_change'; export const ADDRESS_CHANGE: 'send__address_change' = 'send__address_change';
export const AMOUNT_CHANGE: 'send__amount_change' = 'send__amount_change'; export const AMOUNT_CHANGE: 'send__amount_change' = 'send__amount_change';
export const SET_MAX: 'send__set_max' = 'send__set_max'; export const SET_MAX: 'send__set_max' = 'send__set_max';

View File

@ -8,10 +8,11 @@ import type { Props } from './index';
const ConfirmAddress = (props: Props) => { const ConfirmAddress = (props: Props) => {
const { accounts, selectedAccount } = props; const {
if (!selectedAccount) return null; account,
const account = findAccount(accounts, selectedAccount.index, selectedAccount.deviceState, selectedAccount.network); network
if (!account) return null; } = props.selectedAccount;
if (!account || !network) return null;
return ( return (
<div className="confirm-address"> <div className="confirm-address">
@ -21,7 +22,7 @@ const ConfirmAddress = (props: Props) => {
</div> </div>
<div className="content"> <div className="content">
<p>{ account.address }</p> <p>{ account.address }</p>
<label>{ selectedAccount.coin.symbol } account #{ (account.index + 1) }</label> <label>{ network.symbol } account #{ (account.index + 1) }</label>
</div> </div>
</div> </div>
); );
@ -50,14 +51,9 @@ export class ConfirmUnverifiedAddress extends Component<Props> {
verifyAddress() { verifyAddress() {
if (!this.props.modal.opened) return; if (!this.props.modal.opened) return;
const {
const { account
accounts, } = this.props.selectedAccount;
selectedAccount
} = this.props;
if(!selectedAccount) return null;
const account = findAccount(accounts, selectedAccount.index, selectedAccount.deviceState, selectedAccount.network);
if (!account) return null; if (!account) return null;
this.props.modalActions.onCancel(); this.props.modalActions.onCancel();

View File

@ -12,9 +12,7 @@ const Confirmation = (props: Props) => {
const { const {
amount, amount,
address, address,
network, currency,
coinSymbol,
selectedCurrency,
total, total,
selectedFeeLevel selectedFeeLevel
} = props.sendForm; } = props.sendForm;
@ -27,7 +25,7 @@ const Confirmation = (props: Props) => {
</div> </div>
<div className="content"> <div className="content">
<label>Send </label> <label>Send </label>
<p>{ `${amount} ${ selectedCurrency }` }</p> <p>{ `${amount} ${ currency }` }</p>
<label>To</label> <label>To</label>
<p>{ address }</p> <p>{ address }</p>
<label>Fee</label> <label>Fee</label>

View File

@ -1,171 +1,117 @@
/* @flow */ /* @flow */
'use strict'; 'use strict';
import React, { Component } from 'react'; import * as React from 'react';
import { Notification } from '~/js/components/common/Notification'; import { Notification } from '~/js/components/common/Notification';
import { findDevice } from '~/js/reducers/DevicesReducer';
// import * as SelectedAccountActions from '~/js/actions/SelectedAccountActions';
import { default as SelectedAccountActions } from '~/js/actions/SelectedAccountActions';
import type { State, TrezorDevice, Action, ThunkAction } from '~/flowtype'; import type { State, TrezorDevice, Action, ThunkAction } from '~/flowtype';
import type { Account } from '~/js/reducers/AccountsReducer'; import type { Account } from '~/js/reducers/AccountsReducer';
import type { Discovery } from '~/js/reducers/DiscoveryReducer'; import type { Discovery } from '~/js/reducers/DiscoveryReducer';
export type StateProps = { export type StateProps = {
className: string;
selectedAccount: $ElementType<State, 'selectedAccount'>, selectedAccount: $ElementType<State, 'selectedAccount'>,
devices: $ElementType<State, 'devices'>, wallet: $ElementType<State, 'wallet'>,
discovery: $ElementType<State, 'discovery'>, children?: React.Node
accounts: $ElementType<State, 'accounts'>,
} }
export type DispatchProps = { export type DispatchProps = {
selectedAccountActions: typeof SelectedAccountActions,
initAccount: () => ThunkAction,
disposeAccount: () => Action,
} }
export type Props = StateProps & DispatchProps; export type Props = StateProps & DispatchProps;
export type ComponentState = { const SelectedAccount = (props: Props) => {
device: ?TrezorDevice; const device = props.wallet.selectedDevice;
account: ?Account; if (!device || !device.state) {
discovery: ?Discovery; return (<section><Notification className="info" title="Loading device..." /></section>);
deviceStatusNotification: ?React$Element<typeof Notification>;
}
export default class SelectedAccount<P> extends Component<Props & P, ComponentState> {
state: ComponentState = {
device: null,
account: null,
discovery: null,
deviceStatusNotification: null
};
componentDidMount() {
this.props.selectedAccountActions.init();
this.props.initAccount();
} }
componentWillReceiveProps(props: Props & P) { const accountState = props.selectedAccount;
this.props.selectedAccountActions.update( this.props.initAccount );
const accountState = props.selectedAccount; const {
if (!accountState) return; account,
discovery
} = accountState;
const device = findDevice(props.devices, accountState.deviceId, accountState.deviceState, accountState.deviceInstance); // account not found (yet). checking why...
if (!device) return; if (!account) {
const discovery = props.discovery.find(d => d.deviceState === device.state && d.network === accountState.network); if (!discovery || discovery.waitingForDevice) {
// if (!discovery) return; if (device.connected) {
const account = props.accounts.find(a => a.deviceState === accountState.deviceState && a.index === accountState.index && a.network === accountState.network); // case 1: device is connected but discovery not started yet (probably waiting for auth)
if (device.available) {
let deviceStatusNotification: ?React$Element<typeof Notification> = null; return (
if (account) { <section>
if (!device.connected) { <Notification className="info" title="Loading accounts..." />
deviceStatusNotification = <Notification className="info" title={ `Device ${ device.instanceLabel } is disconnected` } />; </section>
} else if (!device.available) { );
deviceStatusNotification = <Notification className="info" title={ `Device ${ device.instanceLabel } is unavailable` } message="Change passphrase settings to use this device" />;
}
}
if (discovery && !discovery.completed && !deviceStatusNotification) {
deviceStatusNotification = <Notification className="info" title="Loading accounts..." />;
}
this.setState({
device,
discovery,
account,
deviceStatusNotification
})
}
componentWillUnmount() {
this.props.selectedAccountActions.dispose();
this.props.disposeAccount();
}
render(): ?React$Element<string> {
const props = this.props;
const accountState = props.selectedAccount;
if (!accountState) {
return (<section><Notification className="info" title="Loading device..." /></section>);
}
const {
device,
account,
discovery
} = this.state;
if (!device) {
return (<section><Notification className="warning" title={ `Device with state ${accountState.deviceState} not found` } /></section>);
}
// account not found. checking why...
if (!account) {
if (!discovery || discovery.waitingForDevice) {
if (device.connected) {
// case 1: device is connected but discovery not started yet (probably waiting for auth)
if (device.available) {
return (
<section>
<Notification className="info" title="Loading accounts..." />
</section>
);
} else {
// case 2: device is unavailable (created with different passphrase settings) account cannot be accessed
return (
<section>
<Notification
className="info"
title={ `Device ${ device.instanceLabel } is unavailable` }
message="Change passphrase settings to use this device"
/>
</section>
);
}
} else { } else {
// case 3: device is disconnected // case 2: device is unavailable (created with different passphrase settings) account cannot be accessed
return ( return (
<section> <section>
<Notification <Notification
className="info" className="info"
title={ `Device ${ device.instanceLabel } is disconnected` } title={ `Device ${ device.instanceLabel } is unavailable` }
message="Connect device to load accounts" message="Change passphrase settings to use this device"
/> />
</section> </section>
); );
} }
} else if (discovery.waitingForBackend) {
// case 4: backend is not working
return (
<section>
<Notification className="warning" title="Backend not working" />
</section>
);
} else if (discovery.completed) {
// case 5: account not found and discovery is completed
return (
<section>
<Notification className="warning" title="Account does not exist" />
</section>
);
} else { } else {
// case 6: discovery is not completed yet // case 3: device is disconnected
return ( return (
<section> <section>
<Notification className="info" title="Account is loading..." /> <Notification
className="info"
title={ `Device ${ device.instanceLabel } is disconnected` }
message="Connect device to load accounts"
/>
</section> </section>
); );
} }
} else if (discovery.waitingForBackend) {
// case 4: backend is not working
return (
<section>
<Notification className="warning" title="Backend not working" />
</section>
);
} else if (discovery.completed) {
// case 5: account not found and discovery is completed
return (
<section>
<Notification className="warning" title="Account does not exist" />
</section>
);
} else {
// case 6: discovery is not completed yet
return (
<section>
<Notification className="info" title="Loading accounts..." />
</section>
);
}
} else {
let notification: ?React$Element<typeof Notification> = null;
if (!device.connected) {
notification = <Notification className="info" title={ `Device ${ device.instanceLabel } is disconnected` } />;
} else if (!device.available) {
notification = <Notification className="info" title={ `Device ${ device.instanceLabel } is unavailable` } message="Change passphrase settings to use this device" />;
} }
return null; if (discovery && !discovery.completed && !notification) {
notification = <Notification className="info" title="Loading accounts..." />;
}
return (
<section className={ props.className }>
{ notification }
{ props.children }
</section>
)
} }
}
}
export default SelectedAccount;

View File

@ -9,31 +9,26 @@ import { QRCode } from 'react-qr-svg';
import SelectedAccount from '../SelectedAccount'; import SelectedAccount from '../SelectedAccount';
import { Notification } from '~/js/components/common/Notification'; import { Notification } from '~/js/components/common/Notification';
import type { ComponentState } from '../SelectedAccount';
import type { Props } from './index'; import type { Props } from './index';
export default class Receive extends SelectedAccount<Props> {
render() {
return super.render() || _render(this.props, this.state);
}
}
const _render = (props: Props, state: ComponentState): React$Element<string> => {
const Receive = (props: Props) => {
const device = props.wallet.selectedDevice;
const { const {
device,
account, account,
network,
discovery, discovery,
deviceStatusNotification } = props.selectedAccount;
} = state;
if (!device || !account || !discovery) return null;
const { const {
addressVerified, addressVerified,
addressUnverified, addressUnverified,
} = props.receive; } = props.receive;
if (!device || !account || !discovery) return <section></section>;
let qrCode = null; let qrCode = null;
let address = `${account.address.substring(0, 20)}...`; let address = `${account.address.substring(0, 20)}...`;
let className = 'address hidden'; let className = 'address hidden';
@ -83,8 +78,7 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
} }
return ( return (
<section className="receive"> <div>
{ deviceStatusNotification }
<h2>Receive Ethereum or tokens</h2> <h2>Receive Ethereum or tokens</h2>
<div className={ className }> <div className={ className }>
@ -95,6 +89,14 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
{ button } { button }
</div> </div>
{ qrCode } { qrCode }
</section> </div>
);
}
export default (props: Props) => {
return (
<SelectedAccount { ...props }>
<Receive { ...props} />
</SelectedAccount>
); );
} }

View File

@ -6,7 +6,6 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { default as ReceiveActions } from '~/js/actions/ReceiveActions'; import { default as ReceiveActions } from '~/js/actions/ReceiveActions';
import { default as SelectedAccountActions } from '~/js/actions/SelectedAccountActions';
import * as TokenActions from '~/js/actions/TokenActions'; import * as TokenActions from '~/js/actions/TokenActions';
import Receive from './Receive'; import Receive from './Receive';
@ -28,14 +27,14 @@ type DispatchProps = BaseDispatchProps & {
showAddress: typeof ReceiveActions.showAddress showAddress: typeof ReceiveActions.showAddress
}; };
export type Props = StateProps & DispatchProps; export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => { const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => {
return { return {
className: "receive",
selectedAccount: state.selectedAccount, selectedAccount: state.selectedAccount,
devices: state.devices, wallet: state.wallet,
accounts: state.accounts,
discovery: state.discovery,
receive: state.receive, receive: state.receive,
modal: state.modal, modal: state.modal,
}; };
@ -43,10 +42,6 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => { const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => {
return { return {
selectedAccountActions: bindActionCreators(SelectedAccountActions, dispatch),
initAccount: bindActionCreators(ReceiveActions.init, dispatch),
disposeAccount: bindActionCreators(ReceiveActions.dispose, dispatch),
showAddress: bindActionCreators(ReceiveActions.showAddress, dispatch), showAddress: bindActionCreators(ReceiveActions.showAddress, dispatch),
}; };
} }

View File

@ -14,15 +14,17 @@ type Props = {
const AdvancedForm = (props: Props) => { const AdvancedForm = (props: Props) => {
const selectedAccount = props.selectedAccount;
if (!selectedAccount) return null;
const { network } = selectedAccount;
const { const {
coinSymbol, account,
selectedCurrency, network
} = props.selectedAccount;
const {
networkSymbol,
currency,
gasPrice, gasPrice,
gasLimit, gasLimit,
calculatingGasLimit,
nonce, nonce,
data, data,
errors, errors,
@ -120,10 +122,11 @@ const AdvancedForm = (props: Props) => {
autoCapitalize="off" autoCapitalize="off"
spellCheck="false" spellCheck="false"
value={ gasLimit } value={ gasLimit }
disabled={ coinSymbol === selectedCurrency && data.length > 0 } disabled={ networkSymbol === currency && data.length > 0 }
onChange={ event => onGasLimitChange(event.target.value) } /> onChange={ event => onGasLimitChange(event.target.value) } />
{ errors.gasLimit ? (<span className="error">{ errors.gasLimit }</span>) : null } { errors.gasLimit ? (<span className="error">{ errors.gasLimit }</span>) : null }
{ warnings.gasLimit ? (<span className="warning">{ warnings.gasLimit }</span>) : null } { warnings.gasLimit ? (<span className="warning">{ warnings.gasLimit }</span>) : null }
{ calculatingGasLimit ? (<span className="info">Calculating...</span>) : null }
</div> </div>
<div className="column"> <div className="column">
<label> <label>
@ -157,7 +160,7 @@ const AdvancedForm = (props: Props) => {
<span className="what-is-it"></span> <span className="what-is-it"></span>
</Tooltip> </Tooltip>
</label> </label>
<textarea disabled={ coinSymbol !== selectedCurrency } value={ coinSymbol !== selectedCurrency ? '' : data } onChange={ event => onDataChange(event.target.value) }></textarea> <textarea disabled={ networkSymbol !== currency } value={ networkSymbol !== currency ? '' : data } onChange={ event => onDataChange(event.target.value) }></textarea>
{ errors.data ? (<span className="error">{ errors.data }</span>) : null } { errors.data ? (<span className="error">{ errors.data }</span>) : null }
</div> </div>

View File

@ -6,40 +6,52 @@ import Select from 'react-select';
import AdvancedForm from './AdvancedForm'; import AdvancedForm from './AdvancedForm';
import PendingTransactions from './PendingTransactions'; import PendingTransactions from './PendingTransactions';
import { FeeSelectValue, FeeSelectOption } from './FeeSelect'; import { FeeSelectValue, FeeSelectOption } from './FeeSelect';
import { Notification } from '~/js/components/common/Notification';
import SelectedAccount from '../SelectedAccount'; import SelectedAccount from '../SelectedAccount';
import { findAccountTokens } from '~/js/reducers/TokensReducer'; import { findAccountTokens } from '~/js/reducers/TokensReducer';
import { calculate, validation } from '~/js/actions/SendFormActions';
import { findToken } from '~/js/reducers/TokensReducer';
import type { Props } from './index'; import type { Props } from './index';
import type { ComponentState } from '../SelectedAccount'; import type { Token } from '~/flowtype';
export default class SendContainer extends Component<Props> {
componentWillReceiveProps(newProps: Props) {
calculate(this.props, newProps);
validation(newProps);
}
export default class Send extends SelectedAccount<Props> {
render() { render() {
return super.render() || _render(this.props, this.state); return (
<SelectedAccount { ...this.props }>
<Send { ...this.props} />
</SelectedAccount>
);
} }
} }
const _render = (props: Props, state: ComponentState): React$Element<string> => {
const Send = (props: Props) => {
const device = props.wallet.selectedDevice;
const { const {
device,
account, account,
network,
discovery, discovery,
deviceStatusNotification tokens
} = state; } = props.selectedAccount;
const selectedAccount = props.selectedAccount;
if (!device || !account || !discovery || !selectedAccount) return <section></section>; if (!device || !account || !discovery || !network) return null;
const tokens = findAccountTokens(props.tokens, account);
const { network } = selectedAccount;
const { const {
address, address,
amount, amount,
setMax, setMax,
coinSymbol, networkSymbol,
selectedCurrency, currency,
feeLevels, feeLevels,
selectedFeeLevel, selectedFeeLevel,
gasPriceNeedsUpdate, gasPriceNeedsUpdate,
@ -48,6 +60,7 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
warnings, warnings,
infos, infos,
advanced, advanced,
data,
sending, sending,
} = props.sendForm; } = props.sendForm;
@ -61,13 +74,12 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
onSend, onSend,
} = props.sendFormActions; } = props.sendFormActions;
const selectedCoin = selectedAccount.coin;
const fiatRate = props.fiat.find(f => f.network === network); const fiatRate = props.fiat.find(f => f.network === network);
const tokensSelectData = tokens.map(t => { const tokensSelectData = tokens.map(t => {
return { value: t.symbol, label: t.symbol }; return { value: t.symbol, label: t.symbol };
}); });
tokensSelectData.unshift({ value: selectedCoin.symbol, label: selectedCoin.symbol }); tokensSelectData.unshift({ value: network.symbol, label: network.symbol });
const setMaxClassName: string = setMax ? 'set-max enabled' : 'set-max'; const setMaxClassName: string = setMax ? 'set-max enabled' : 'set-max';
@ -89,10 +101,10 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
let buttonDisabled: boolean = Object.keys(errors).length > 0 || total === '0' || amount.length === 0 || address.length === 0 || sending; let buttonDisabled: boolean = Object.keys(errors).length > 0 || total === '0' || amount.length === 0 || address.length === 0 || sending;
let buttonLabel: string = 'Send'; let buttonLabel: string = 'Send';
if (coinSymbol !== selectedCurrency && amount.length > 0 && !errors.amount) { if (networkSymbol !== currency && amount.length > 0 && !errors.amount) {
buttonLabel += ` ${amount} ${ selectedCurrency.toUpperCase() }` buttonLabel += ` ${amount} ${ currency.toUpperCase() }`
} else if (coinSymbol === selectedCurrency && total !== '0') { } else if (networkSymbol === currency && total !== '0') {
buttonLabel += ` ${total} ${ selectedCoin.symbol }`; buttonLabel += ` ${total} ${ network.symbol }`;
} }
if (!device.connected){ if (!device.connected){
@ -105,14 +117,9 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
buttonLabel = 'Loading accounts'; buttonLabel = 'Loading accounts';
buttonDisabled = true; buttonDisabled = true;
} }
let notification = null;
return ( return (
<section className="send-form"> <section className="send-form">
{ deviceStatusNotification }
<h2>Send Ethereum or tokens</h2> <h2>Send Ethereum or tokens</h2>
<div className="row address-input"> <div className="row address-input">
<label>Address</label> <label>Address</label>
@ -152,7 +159,7 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
searchable={ false } searchable={ false }
clearable= { false } clearable= { false }
multi={ false } multi={ false }
value={ selectedCurrency } value={ currency }
disabled={ tokensSelectData.length < 2 } disabled={ tokensSelectData.length < 2 }
onChange={ onCurrencyChange } onChange={ onCurrencyChange }
options={ tokensSelectData } /> options={ tokensSelectData } />
@ -172,6 +179,7 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
onChange={ onFeeLevelChange } onChange={ onFeeLevelChange }
valueComponent={ FeeSelectValue } valueComponent={ FeeSelectValue }
optionComponent={ FeeSelectOption } optionComponent={ FeeSelectOption }
disabled={ networkSymbol === currency && data.length > 0 }
optionClassName="fee-option" optionClassName="fee-option"
options={ feeLevels } /> options={ feeLevels } />
</div> </div>
@ -187,7 +195,7 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
pending={ props.pending } pending={ props.pending }
tokens={ props.tokens } tokens={ props.tokens }
account={ account } account={ account }
selectedCoin={ selectedCoin } /> selectedCoin={ network } />
</section> </section>
); );

View File

@ -6,7 +6,6 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { default as SendFormActions } from '~/js/actions/SendFormActions'; import { default as SendFormActions } from '~/js/actions/SendFormActions';
import { default as SelectedAccountActions } from '~/js/actions/SelectedAccountActions';
import SendForm from './SendForm'; import SendForm from './SendForm';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux'; import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
@ -28,14 +27,14 @@ export type DispatchProps = BaseDispatchProps & {
sendFormActions: typeof SendFormActions sendFormActions: typeof SendFormActions
} }
export type Props = StateProps & DispatchProps; export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => { const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => {
return { return {
className: "send-from",
selectedAccount: state.selectedAccount, selectedAccount: state.selectedAccount,
devices: state.devices, wallet: state.wallet,
accounts: state.accounts,
discovery: state.discovery,
tokens: state.tokens, tokens: state.tokens,
pending: state.pending, pending: state.pending,
sendForm: state.sendForm, sendForm: state.sendForm,
@ -46,10 +45,7 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => { const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => {
return { return {
selectedAccountActions: bindActionCreators(SelectedAccountActions, dispatch),
sendFormActions: bindActionCreators(SendFormActions, dispatch), sendFormActions: bindActionCreators(SendFormActions, dispatch),
initAccount: bindActionCreators(SendFormActions.init, dispatch),
disposeAccount: bindActionCreators(SendFormActions.dispose, dispatch),
}; };
} }

View File

@ -3,7 +3,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { Async } from 'react-select'; import { Async as AsyncSelect } from 'react-select';
import Tooltip from 'rc-tooltip'; import Tooltip from 'rc-tooltip';
import { resolveAfter } from '~/js/utils/promiseUtils'; import { resolveAfter } from '~/js/utils/promiseUtils';
@ -13,56 +13,38 @@ import SummaryDetails from './SummaryDetails.js';
import SummaryTokens from './SummaryTokens.js'; import SummaryTokens from './SummaryTokens.js';
import type { Props } from './index'; import type { Props } from './index';
import type { ComponentState } from '../SelectedAccount';
import type { TrezorDevice } from '~/flowtype';
import type { NetworkToken } from '~/js/reducers/LocalStorageReducer'; import type { NetworkToken } from '~/js/reducers/LocalStorageReducer';
import type { Account } from '~/js/reducers/AccountsReducer';
import type { Discovery } from '~/js/reducers/DiscoveryReducer';
import { findAccountTokens } from '~/js/reducers/TokensReducer';
export default class Summary extends SelectedAccount<Props> {
render() {
return super.render() || _render(this.props, this.state);
}
}
const _render = (props: Props, state: ComponentState): React$Element<string> => {
const Summary = (props: Props) => {
const device = props.wallet.selectedDevice;
const { const {
device,
account, account,
deviceStatusNotification network,
} = state; tokens,
const selectedAccount = props.selectedAccount; } = props.selectedAccount;
if (!device || !account || !selectedAccount) return <section></section>; // flow
if (!device || !account || !network) return null;
const tokens = findAccountTokens(props.tokens, account);
const explorerLink: string = `${selectedAccount.coin.explorer.address}${account.address}`;
const tokensTooltip = ( const tokensTooltip = (
<div className="tooltip-wrapper"> <div className="tooltip-wrapper">
Insert token name, symbol or address to be able to send it. Insert token name, symbol or address to be able to send it.
</div> </div>
); );
const explorerLink: string = `${network.explorer.address}${account.address}`;
return ( return (
<div>
<section className="summary"> <h2 className={ `summary-header ${account.network}` }>
{ deviceStatusNotification } Account #{ parseInt(account.index) + 1 }
<h2 className={ `summary-header ${selectedAccount.network}` }>
Account #{ parseInt(selectedAccount.index) + 1 }
<a href={ explorerLink } className="gray" target="_blank" rel="noreferrer noopener">See full transaction history</a> <a href={ explorerLink } className="gray" target="_blank" rel="noreferrer noopener">See full transaction history</a>
</h2> </h2>
<SummaryDetails <SummaryDetails
coin={ selectedAccount.coin } coin={ network }
summary={ props.summary } summary={ props.summary }
balance={ account.balance } balance={ account.balance }
network={ selectedAccount.network } network={ network.network }
fiat={ props.fiat } fiat={ props.fiat }
localStorage={ props.localStorage } localStorage={ props.localStorage }
onToggle={ props.onDetailsToggle } /> onToggle={ props.onDetailsToggle } />
@ -79,7 +61,7 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
{/* 0x58cda554935e4a1f2acbe15f8757400af275e084 Lahod */} {/* 0x58cda554935e4a1f2acbe15f8757400af275e084 Lahod */}
{/* 0x58cda554935e4a1f2acbe15f8757400af275e084 T01 */} {/* 0x58cda554935e4a1f2acbe15f8757400af275e084 T01 */}
<div className="filter"> <div className="filter">
<Async <AsyncSelect
className="token-select" className="token-select"
multi={ false } multi={ false }
autoload={ false } autoload={ false }
@ -102,25 +84,27 @@ const _render = (props: Props, state: ComponentState): React$Element<string> =>
return o; return o;
} }
}); });
// return options.filter(o => {
// return !tokens.find(t => t.symbol === o.symbol);
// });
} }
} }
valueKey="symbol" valueKey="symbol"
labelKey="name" labelKey="name"
placeholder="Search for token" placeholder="Search for token"
searchPromptText="Type token name or address" searchPromptText="Type token name or address"
noResultsText="Token not found" noResultsText="Token not found" />
/>
</div> </div>
<SummaryTokens tokens={ tokens } removeToken={ props.removeToken } /> <SummaryTokens tokens={ tokens } removeToken={ props.removeToken } />
</section> </div>
)
}
export default (props: Props) => {
return (
<SelectedAccount { ...props }>
<Summary { ...props} />
</SelectedAccount>
); );
} }

View File

@ -6,7 +6,6 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Summary from './Summary'; import Summary from './Summary';
import { default as SelectedAccountActions } from '~/js/actions/SelectedAccountActions';
import * as SummaryActions from '~/js/actions/SummaryActions'; import * as SummaryActions from '~/js/actions/SummaryActions';
import * as TokenActions from '~/js/actions/TokenActions'; import * as TokenActions from '~/js/actions/TokenActions';
@ -21,7 +20,7 @@ type StateProps = BaseStateProps & {
summary: $ElementType<State, 'summary'>, summary: $ElementType<State, 'summary'>,
fiat: $ElementType<State, 'fiat'>, fiat: $ElementType<State, 'fiat'>,
localStorage: $ElementType<State, 'localStorage'>, localStorage: $ElementType<State, 'localStorage'>,
} };
type DispatchProps = BaseDispatchProps & { type DispatchProps = BaseDispatchProps & {
onDetailsToggle: typeof SummaryActions.onDetailsToggle, onDetailsToggle: typeof SummaryActions.onDetailsToggle,
@ -30,14 +29,13 @@ type DispatchProps = BaseDispatchProps & {
removeToken: typeof TokenActions.remove, removeToken: typeof TokenActions.remove,
} }
export type Props = StateProps & DispatchProps; export type Props = StateProps & BaseStateProps & DispatchProps & BaseDispatchProps;
const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => { const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: State, own: OwnProps): StateProps => {
return { return {
className: "summary",
selectedAccount: state.selectedAccount, selectedAccount: state.selectedAccount,
devices: state.devices, wallet: state.wallet,
accounts: state.accounts,
discovery: state.discovery,
tokens: state.tokens, tokens: state.tokens,
summary: state.summary, summary: state.summary,
@ -48,11 +46,6 @@ const mapStateToProps: MapStateToProps<State, OwnProps, StateProps> = (state: St
const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => { const mapDispatchToProps: MapDispatchToProps<Dispatch, OwnProps, DispatchProps> = (dispatch: Dispatch): DispatchProps => {
return { return {
selectedAccountActions: bindActionCreators(SelectedAccountActions, dispatch),
initAccount: bindActionCreators(SummaryActions.init, dispatch),
disposeAccount: bindActionCreators(SummaryActions.dispose, dispatch),
onDetailsToggle: bindActionCreators(SummaryActions.onDetailsToggle, dispatch), onDetailsToggle: bindActionCreators(SummaryActions.onDetailsToggle, dispatch),
addToken: bindActionCreators(TokenActions.add, dispatch), addToken: bindActionCreators(TokenActions.add, dispatch),
loadTokens: bindActionCreators(TokenActions.load, dispatch), loadTokens: bindActionCreators(TokenActions.load, dispatch),

View File

@ -297,14 +297,4 @@ export const getNewInstance = (devices: State, device: Device | TrezorDevice): n
}, 0); }, 0);
return instance; return instance;
}
export const findDevice = (devices: State, deviceId: string, deviceState: string, instance: ?number): ?TrezorDevice => {
return devices.find(d => {
// TODO: && (instance && d.instance === instance)
if (d.features && d.features.device_id === deviceId && d.state === deviceState) {
return true;
}
return false;
});
} }

View File

@ -3,6 +3,7 @@
import { UI } from 'trezor-connect'; import { UI } from 'trezor-connect';
import * as RECEIVE from '../actions/constants/receive'; import * as RECEIVE from '../actions/constants/receive';
import * as ACCOUNT from '../actions/constants/account';
import type { Action } from '~/flowtype'; import type { Action } from '~/flowtype';
@ -23,7 +24,7 @@ export default (state: State = initialState, action: Action): State => {
case RECEIVE.INIT : case RECEIVE.INIT :
return action.state; return action.state;
case RECEIVE.DISPOSE : case ACCOUNT.DISPOSE :
return initialState; return initialState;
case RECEIVE.SHOW_ADDRESS : case RECEIVE.SHOW_ADDRESS :

View File

@ -2,32 +2,41 @@
'use strict'; 'use strict';
import * as ACCOUNT from '../actions/constants/account'; import * as ACCOUNT from '../actions/constants/account';
import * as CONNECT from '../actions/constants/TrezorConnect';
import type { Action } from '~/flowtype'; import type {
import type { Coin } from './LocalStorageReducer'; Action,
Account,
Coin,
Token,
Discovery,
Web3Instance
} from '~/flowtype';
export type State = { export type State = {
+index: number; location?: string;
+deviceState: string;
+deviceId: string; account: ?Account;
+deviceInstance: ?number; network: ?Coin;
+network: string; tokens: Array<Token>,
+coin: Coin; web3: ?Web3Instance,
+location: string; discovery: ?Discovery
}; };
export const initialState: ?State = null; export const initialState: State = {
location: '/',
account: null,
network: null,
tokens: [],
web3: null,
discovery: null
};
export default (state: ?State = initialState, action: Action): ?State => { export default (state: State = initialState, action: Action): State => {
switch (action.type) { switch (action.type) {
case ACCOUNT.INIT : case ACCOUNT.UPDATE_SELECTED_ACCOUNT :
return action.state; return action.payload;
case ACCOUNT.DISPOSE :
return initialState;
default: default:
return state; return state;

View File

@ -4,6 +4,8 @@
import * as SEND from '../actions/constants/send'; import * as SEND from '../actions/constants/send';
import * as WEB3 from '../actions/constants/web3'; import * as WEB3 from '../actions/constants/web3';
import * as ACCOUNT from '../actions/constants/account'; import * as ACCOUNT from '../actions/constants/account';
import * as WALLET from '../actions/constants/wallet';
import EthereumjsUnits from 'ethereumjs-units'; import EthereumjsUnits from 'ethereumjs-units';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { getFeeLevels } from '../actions/SendFormActions'; import { getFeeLevels } from '../actions/SendFormActions';
@ -14,10 +16,9 @@ import type {
} from '../actions/Web3Actions'; } from '../actions/Web3Actions';
export type State = { export type State = {
+network: string; +networkName: string;
+coinSymbol: string; +networkSymbol: string;
selectedCurrency: string; currency: string;
balanceNeedUpdate: boolean;
// form fields // form fields
advanced: boolean; advanced: boolean;
@ -31,14 +32,17 @@ export type State = {
recommendedGasPrice: string; recommendedGasPrice: string;
gasPriceNeedsUpdate: boolean; gasPriceNeedsUpdate: boolean;
gasLimit: string; gasLimit: string;
calculatingGasLimit: boolean;
gasPrice: string; gasPrice: string;
data: string; data: string;
nonce: string; nonce: string;
total: string; total: string;
sending: boolean;
errors: {[k: string]: string}; errors: {[k: string]: string};
warnings: {[k: string]: string}; warnings: {[k: string]: string};
infos: {[k: string]: string}; infos: {[k: string]: string};
sending: boolean;
} }
export type FeeLevel = { export type FeeLevel = {
@ -49,14 +53,13 @@ export type FeeLevel = {
export const initialState: State = { export const initialState: State = {
network: '', networkName: '',
coinSymbol: '', networkSymbol: '',
selectedCurrency: '', currency: '',
advanced: false, advanced: false,
untouched: true, untouched: true,
touched: {}, touched: {},
balanceNeedUpdate: false,
address: '', address: '',
//address: '0x574BbB36871bA6b78E27f4B4dCFb76eA0091880B', //address: '0x574BbB36871bA6b78E27f4B4dCFb76eA0091880B',
amount: '', amount: '',
@ -70,6 +73,7 @@ export const initialState: State = {
recommendedGasPrice: '0', recommendedGasPrice: '0',
gasPriceNeedsUpdate: false, gasPriceNeedsUpdate: false,
gasLimit: '0', gasLimit: '0',
calculatingGasLimit: false,
gasPrice: '0', gasPrice: '0',
data: '', data: '',
nonce: '0', nonce: '0',
@ -88,13 +92,13 @@ const onGasPriceUpdated = (state: State, action: Web3UpdateGasPriceAction): Stat
// } // }
// const newPrice = getRandomInt(10, 50).toString(); // const newPrice = getRandomInt(10, 50).toString();
const newPrice: string = EthereumjsUnits.convert(action.gasPrice, 'wei', 'gwei'); const newPrice: string = EthereumjsUnits.convert(action.gasPrice, 'wei', 'gwei');
if (action.network === state.network && newPrice !== state.recommendedGasPrice) { if (action.network === state.networkName && newPrice !== state.recommendedGasPrice) {
const newState: State = { ...state }; const newState: State = { ...state };
if (!state.untouched) { if (!state.untouched) {
newState.gasPriceNeedsUpdate = true; newState.gasPriceNeedsUpdate = true;
newState.recommendedGasPrice = newPrice; newState.recommendedGasPrice = newPrice;
} else { } else {
const newFeeLevels = getFeeLevels(state.coinSymbol, newPrice, state.gasLimit); const newFeeLevels = getFeeLevels(state.networkSymbol, newPrice, state.gasLimit);
const selectedFeeLevel: ?FeeLevel = newFeeLevels.find(f => f.value === 'Normal'); const selectedFeeLevel: ?FeeLevel = newFeeLevels.find(f => f.value === 'Normal');
if (!selectedFeeLevel) return state; if (!selectedFeeLevel) return state;
newState.recommendedGasPrice = newPrice; newState.recommendedGasPrice = newPrice;
@ -107,19 +111,6 @@ const onGasPriceUpdated = (state: State, action: Web3UpdateGasPriceAction): Stat
return state; return state;
} }
const onBalanceUpdated = (state: State, action: any): State => {
// balanceNeedUpdate
// if (state.senderAddress === action.address) {
// return {
// ...state,
// balance: '1'
// }
// }
// TODO: handle balance update during send form life cycle
return state;
}
export default (state: State = initialState, action: Action): State => { export default (state: State = initialState, action: Action): State => {
@ -128,7 +119,7 @@ export default (state: State = initialState, action: Action): State => {
case SEND.INIT : case SEND.INIT :
return action.state; return action.state;
case SEND.DISPOSE : case ACCOUNT.DISPOSE :
return initialState; return initialState;
// this will be called right after Web3 instance initialization before any view is shown // this will be called right after Web3 instance initialization before any view is shown
@ -136,8 +127,6 @@ export default (state: State = initialState, action: Action): State => {
case WEB3.GAS_PRICE_UPDATED : case WEB3.GAS_PRICE_UPDATED :
return onGasPriceUpdated(state, action); return onGasPriceUpdated(state, action);
case ACCOUNT.SET_BALANCE :
return onBalanceUpdated(state, action);
case SEND.TOGGLE_ADVANCED : case SEND.TOGGLE_ADVANCED :
return { return {
@ -148,6 +137,7 @@ export default (state: State = initialState, action: Action): State => {
// user actions // user actions
case SEND.ADDRESS_CHANGE : case SEND.ADDRESS_CHANGE :
case SEND.ADDRESS_VALIDATION :
case SEND.AMOUNT_CHANGE : case SEND.AMOUNT_CHANGE :
case SEND.SET_MAX : case SEND.SET_MAX :
case SEND.CURRENCY_CHANGE : case SEND.CURRENCY_CHANGE :
@ -165,33 +155,12 @@ export default (state: State = initialState, action: Action): State => {
sending: true, sending: true,
} }
case SEND.TX_COMPLETE :
return {
...state,
sending: false,
untouched: true,
touched: {},
address: '',
amount: '',
setMax: false,
gasPriceNeedsUpdate: false,
gasLimit: state.gasLimit,
gasPrice: state.recommendedGasPrice,
data: '',
nonce: new BigNumber(state.nonce).plus(1).toString(),
total: '0',
errors: {},
warnings: {},
infos: {},
}
case SEND.TX_ERROR : case SEND.TX_ERROR :
return { return {
...state, ...state,
sending: false, sending: false,
} }
case SEND.VALIDATION : case SEND.VALIDATION :
return { return {
...state, ...state,

View File

@ -1,6 +1,7 @@
/* @flow */ /* @flow */
'use strict'; 'use strict';
import * as ACCOUNT from '../actions/constants/account';
import * as SUMMARY from '../actions/constants/summary'; import * as SUMMARY from '../actions/constants/summary';
import type { Action } from '~/flowtype'; import type { Action } from '~/flowtype';
import type { NetworkToken } from './LocalStorageReducer'; import type { NetworkToken } from './LocalStorageReducer';
@ -13,13 +14,15 @@ export type State = {
export const initialState: State = { export const initialState: State = {
details: true, details: true,
selectedToken: null selectedToken: null
}; }
export default (state: State = initialState, action: Action): State => { export default (state: State = initialState, action: Action): State => {
switch (action.type) { switch (action.type) {
case ACCOUNT.DISPOSE :
return initialState;
case SUMMARY.INIT : case SUMMARY.INIT :
return action.state; return action.state;

View File

@ -40,8 +40,20 @@ export const getSelectedDevice = (state: State): ?TrezorDevice => {
}); });
} }
//
export const isSelectedDevice = (current: ?TrezorDevice, device: ?TrezorDevice): boolean => { export const isSelectedDevice = (current: ?TrezorDevice, device: ?TrezorDevice): boolean => {
return (current && device && (current.path === device.path || current.instance === device.instance)) ? true : false; return (current && device && (current.path === device.path && current.instance === device.instance)) ? true : false;
}
// find device by id and state
export const findDevice = (devices: Array<TrezorDevice>, deviceId: string, deviceState: string, instance: ?number): ?TrezorDevice => {
return devices.find(d => {
// TODO: && (instance && d.instance === instance)
if (d.features && d.features.device_id === deviceId && d.state === deviceState) {
return true;
}
return false;
});
} }
export const getSelectedAccount = (state: State): ?Account => { export const getSelectedAccount = (state: State): ?Account => {
@ -71,10 +83,10 @@ export const getDiscoveryProcess = (state: State): ?Discovery => {
return state.discovery.find(d => d.deviceState === device.state && d.network === locationState.network); return state.discovery.find(d => d.deviceState === device.state && d.network === locationState.network);
} }
export const getTokens = (state: State): Array<Token> => { export const getTokens = (state: State, account: ?Account): Array<Token> => {
const account = state.selectedAccount.account; const a = account;
if (!account) return state.selectedAccount.tokens; if (!a) return state.selectedAccount.tokens;
return state.tokens.filter(t => t.ethAddress === account.address && t.network === account.network && t.deviceState === account.deviceState); return state.tokens.filter(t => t.ethAddress === a.address && t.network === a.network && t.deviceState === a.deviceState);
} }
export const getWeb3 = (state: State): ?Web3Instance => { export const getWeb3 = (state: State): ?Web3Instance => {

View File

@ -3,10 +3,12 @@
import { LOCATION_CHANGE } from 'react-router-redux'; import { LOCATION_CHANGE } from 'react-router-redux';
import * as WALLET from '../actions/constants/wallet'; import * as WALLET from '../actions/constants/wallet';
import * as SEND from '../actions/constants/wallet';
import * as WalletActions from '../actions/WalletActions'; import * as WalletActions from '../actions/WalletActions';
import * as LocalStorageActions from '../actions/LocalStorageActions'; import * as LocalStorageActions from '../actions/LocalStorageActions';
import * as TrezorConnectActions from '../actions/TrezorConnectActions'; import * as TrezorConnectActions from '../actions/TrezorConnectActions';
import * as SelectedAccountActions from '../actions/SelectedAccountActions';
import type { import type {
Middleware, Middleware,
@ -19,24 +21,6 @@ import type {
TrezorDevice, TrezorDevice,
} from '~/flowtype'; } from '~/flowtype';
const getSelectedDevice = (state: State): ?TrezorDevice => {
const locationState = state.router.location.state;
if (!locationState.device) return undefined;
const instance: ?number = locationState.deviceInstance ? parseInt(locationState.deviceInstance) : undefined;
return state.devices.find(d => {
if (d.unacquired && d.path === locationState.device) {
return true;
} else if (d.features && d.features.bootloader_mode && d.path === locationState.device) {
return true;
} else if (d.features && d.features.device_id === locationState.device && d.instance === instance) {
return true;
}
return false;
});
}
/** /**
* Middleware * Middleware
*/ */
@ -58,38 +42,22 @@ const WalletService: Middleware = (api: MiddlewareAPI) => (next: MiddlewareDispa
// pass action // pass action
next(action); next(action);
const state = api.getState(); // update common values in WallerReducer
api.dispatch( WalletActions.updateSelectedValues(prevState, action) );
// handle devices state change // update common values in SelectedAccountReducer
if (locationChange || prevState.devices !== state.devices) { api.dispatch( SelectedAccountActions.updateSelectedValues(prevState, action) );
const device = getSelectedDevice(state);
const currentDevice = state.wallet.selectedDevice;
if (state.wallet.selectedDevice !== device) { // selected device changed
if (!locationChange && currentDevice && device && (currentDevice.path === device.path || currentDevice.instance === device.instance)) { if (action.type === WALLET.SET_SELECTED_DEVICE) {
api.dispatch({ if (action.device) {
type: WALLET.UPDATE_SELECTED_DEVICE, api.dispatch( TrezorConnectActions.getSelectedDeviceState() );
device } else {
}) api.dispatch( TrezorConnectActions.switchToFirstAvailableDevice() );
} else {
api.dispatch({
type: WALLET.SET_SELECTED_DEVICE,
device
});
if (device) {
api.dispatch( TrezorConnectActions.getSelectedDeviceState() );
} else {
api.dispatch( TrezorConnectActions.switchToFirstAvailableDevice() );
}
}
} }
} }
return action; return action;
}; };
export default WalletService; export default WalletService;