Merge branch 'development' into addExtraDocs
* development: Change blockchain prop to not optional Other style changes Updated padding and colors Refactor TokenState type Fix a bug causing fillOrdersUpTo validation to fail because of some extra orders being passed Implement initial wallet interface # Conflicts: # packages/react-shared/CHANGELOG.md # packages/website/ts/types.ts
This commit is contained in:
		@@ -3,6 +3,7 @@
 | 
				
			|||||||
## v0.34.0 - _TBD_
 | 
					## v0.34.0 - _TBD_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    * Update Kovan EtherToken artifact address to match TokenRegistry.
 | 
					    * Update Kovan EtherToken artifact address to match TokenRegistry.
 | 
				
			||||||
 | 
					    * Fix the bug causing `zeroEx.exchange.fillOrdersUpToAsync` validation to fail if there were some extra orders passed (#470)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## v0.33.2 - _March 18, 2018_
 | 
					## v0.33.2 - _March 18, 2018_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -281,6 +281,9 @@ export class ExchangeWrapper extends ContractWrapper {
 | 
				
			|||||||
                    zrxTokenAddress,
 | 
					                    zrxTokenAddress,
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
                filledTakerTokenAmount = filledTakerTokenAmount.plus(singleFilledTakerTokenAmount);
 | 
					                filledTakerTokenAmount = filledTakerTokenAmount.plus(singleFilledTakerTokenAmount);
 | 
				
			||||||
 | 
					                if (filledTakerTokenAmount.eq(fillTakerTokenAmount)) {
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -596,6 +596,19 @@ describe('ExchangeWrapper', () => {
 | 
				
			|||||||
                    const remainingFillAmount = fillableAmount.minus(1);
 | 
					                    const remainingFillAmount = fillableAmount.minus(1);
 | 
				
			||||||
                    expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount);
 | 
					                    expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount);
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					                it('should successfully fill up to specified amount and leave the rest of the orders untouched', async () => {
 | 
				
			||||||
 | 
					                    const txHash = await zeroEx.exchange.fillOrdersUpToAsync(
 | 
				
			||||||
 | 
					                        signedOrders,
 | 
				
			||||||
 | 
					                        fillableAmount,
 | 
				
			||||||
 | 
					                        shouldThrowOnInsufficientBalanceOrAllowance,
 | 
				
			||||||
 | 
					                        takerAddress,
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    await zeroEx.awaitTransactionMinedAsync(txHash);
 | 
				
			||||||
 | 
					                    const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex);
 | 
				
			||||||
 | 
					                    const zeroAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex);
 | 
				
			||||||
 | 
					                    expect(filledAmount).to.be.bignumber.equal(fillableAmount);
 | 
				
			||||||
 | 
					                    expect(zeroAmount).to.be.bignumber.equal(0);
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
                it('should successfully fill up to specified amount even if filling all orders would fail', async () => {
 | 
					                it('should successfully fill up to specified amount even if filling all orders would fail', async () => {
 | 
				
			||||||
                    const missingBalance = new BigNumber(1); // User will still have enough balance to fill up to 9,
 | 
					                    const missingBalance = new BigNumber(1); // User will still have enough balance to fill up to 9,
 | 
				
			||||||
                    // but won't have 10 to fully fill all orders in a batch.
 | 
					                    // but won't have 10 to fully fill all orders in a batch.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
# CHANGELOG
 | 
					# CHANGELOG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## v0.0.2 - _TBD_
 | 
					## v0.1.0 - _TBD, 2018_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    * Added new colors (#468)
 | 
				
			||||||
    * Fix section and menuItem text display to replace dashes with spaces.
 | 
					    * Fix section and menuItem text display to replace dashes with spaces.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,4 +45,7 @@ export const colors = {
 | 
				
			|||||||
    orange: '#E69D00',
 | 
					    orange: '#E69D00',
 | 
				
			||||||
    amber800: '#FF8F00',
 | 
					    amber800: '#FF8F00',
 | 
				
			||||||
    darkYellow: '#caca03',
 | 
					    darkYellow: '#caca03',
 | 
				
			||||||
 | 
					    walletBoxShadow: 'rgba(56, 59, 137, 0.2)',
 | 
				
			||||||
 | 
					    walletBorder: '#f5f5f6',
 | 
				
			||||||
 | 
					    walletDefaultItemBackground: '#fbfbfc',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,14 @@ import ReactTooltip = require('react-tooltip');
 | 
				
			|||||||
import { Blockchain } from 'ts/blockchain';
 | 
					import { Blockchain } from 'ts/blockchain';
 | 
				
			||||||
import { EthWethConversionButton } from 'ts/components/eth_weth_conversion_button';
 | 
					import { EthWethConversionButton } from 'ts/components/eth_weth_conversion_button';
 | 
				
			||||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
					import { Dispatcher } from 'ts/redux/dispatcher';
 | 
				
			||||||
import { OutdatedWrappedEtherByNetworkId, Side, Token, TokenByAddress, TokenState } from 'ts/types';
 | 
					import {
 | 
				
			||||||
 | 
					    OutdatedWrappedEtherByNetworkId,
 | 
				
			||||||
 | 
					    Side,
 | 
				
			||||||
 | 
					    Token,
 | 
				
			||||||
 | 
					    TokenByAddress,
 | 
				
			||||||
 | 
					    TokenState,
 | 
				
			||||||
 | 
					    TokenStateByAddress,
 | 
				
			||||||
 | 
					} from 'ts/types';
 | 
				
			||||||
import { configs } from 'ts/utils/configs';
 | 
					import { configs } from 'ts/utils/configs';
 | 
				
			||||||
import { constants } from 'ts/utils/constants';
 | 
					import { constants } from 'ts/utils/constants';
 | 
				
			||||||
import { utils } from 'ts/utils/utils';
 | 
					import { utils } from 'ts/utils/utils';
 | 
				
			||||||
@@ -20,13 +27,6 @@ const ICON_DIMENSION = 40;
 | 
				
			|||||||
const ETHER_ICON_PATH = '/images/ether.png';
 | 
					const ETHER_ICON_PATH = '/images/ether.png';
 | 
				
			||||||
const OUTDATED_WETH_ICON_PATH = '/images/wrapped_eth_gray.png';
 | 
					const OUTDATED_WETH_ICON_PATH = '/images/wrapped_eth_gray.png';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface OutdatedWETHAddressToIsStateLoaded {
 | 
					 | 
				
			||||||
    [address: string]: boolean;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
interface OutdatedWETHStateByAddress {
 | 
					 | 
				
			||||||
    [address: string]: TokenState;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface EthWrappersProps {
 | 
					interface EthWrappersProps {
 | 
				
			||||||
    networkId: number;
 | 
					    networkId: number;
 | 
				
			||||||
    blockchain: Blockchain;
 | 
					    blockchain: Blockchain;
 | 
				
			||||||
@@ -39,9 +39,7 @@ interface EthWrappersProps {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
interface EthWrappersState {
 | 
					interface EthWrappersState {
 | 
				
			||||||
    ethTokenState: TokenState;
 | 
					    ethTokenState: TokenState;
 | 
				
			||||||
    isWethStateLoaded: boolean;
 | 
					    outdatedWETHStateByAddress: TokenStateByAddress;
 | 
				
			||||||
    outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded;
 | 
					 | 
				
			||||||
    outdatedWETHStateByAddress: OutdatedWETHStateByAddress;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersState> {
 | 
					export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersState> {
 | 
				
			||||||
@@ -50,22 +48,20 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
 | 
				
			|||||||
        super(props);
 | 
					        super(props);
 | 
				
			||||||
        this._isUnmounted = false;
 | 
					        this._isUnmounted = false;
 | 
				
			||||||
        const outdatedWETHAddresses = this._getOutdatedWETHAddresses();
 | 
					        const outdatedWETHAddresses = this._getOutdatedWETHAddresses();
 | 
				
			||||||
        const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {};
 | 
					        const outdatedWETHStateByAddress: TokenStateByAddress = {};
 | 
				
			||||||
        const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {};
 | 
					 | 
				
			||||||
        _.each(outdatedWETHAddresses, outdatedWETHAddress => {
 | 
					        _.each(outdatedWETHAddresses, outdatedWETHAddress => {
 | 
				
			||||||
            outdatedWETHAddressToIsStateLoaded[outdatedWETHAddress] = false;
 | 
					 | 
				
			||||||
            outdatedWETHStateByAddress[outdatedWETHAddress] = {
 | 
					            outdatedWETHStateByAddress[outdatedWETHAddress] = {
 | 
				
			||||||
                balance: new BigNumber(0),
 | 
					                balance: new BigNumber(0),
 | 
				
			||||||
                allowance: new BigNumber(0),
 | 
					                allowance: new BigNumber(0),
 | 
				
			||||||
 | 
					                isLoaded: false,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        this.state = {
 | 
					        this.state = {
 | 
				
			||||||
            outdatedWETHAddressToIsStateLoaded,
 | 
					 | 
				
			||||||
            outdatedWETHStateByAddress,
 | 
					            outdatedWETHStateByAddress,
 | 
				
			||||||
            isWethStateLoaded: false,
 | 
					 | 
				
			||||||
            ethTokenState: {
 | 
					            ethTokenState: {
 | 
				
			||||||
                balance: new BigNumber(0),
 | 
					                balance: new BigNumber(0),
 | 
				
			||||||
                allowance: new BigNumber(0),
 | 
					                allowance: new BigNumber(0),
 | 
				
			||||||
 | 
					                isLoaded: false,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -169,7 +165,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
 | 
				
			|||||||
                                        {this._renderTokenLink(tokenLabel, etherscanUrl)}
 | 
					                                        {this._renderTokenLink(tokenLabel, etherscanUrl)}
 | 
				
			||||||
                                    </TableRowColumn>
 | 
					                                    </TableRowColumn>
 | 
				
			||||||
                                    <TableRowColumn>
 | 
					                                    <TableRowColumn>
 | 
				
			||||||
                                        {this.state.isWethStateLoaded ? (
 | 
					                                        {this.state.ethTokenState.isLoaded ? (
 | 
				
			||||||
                                            `${wethBalance.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} WETH`
 | 
					                                            `${wethBalance.toFixed(configs.AMOUNT_DISPLAY_PRECSION)} WETH`
 | 
				
			||||||
                                        ) : (
 | 
					                                        ) : (
 | 
				
			||||||
                                            <i className="zmdi zmdi-spinner zmdi-hc-spin" />
 | 
					                                            <i className="zmdi zmdi-spinner zmdi-hc-spin" />
 | 
				
			||||||
@@ -183,7 +179,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
 | 
				
			|||||||
                                            networkId={this.props.networkId}
 | 
					                                            networkId={this.props.networkId}
 | 
				
			||||||
                                            isOutdatedWrappedEther={false}
 | 
					                                            isOutdatedWrappedEther={false}
 | 
				
			||||||
                                            direction={Side.Receive}
 | 
					                                            direction={Side.Receive}
 | 
				
			||||||
                                            isDisabled={!this.state.isWethStateLoaded}
 | 
					                                            isDisabled={!this.state.ethTokenState.isLoaded}
 | 
				
			||||||
                                            ethToken={etherToken}
 | 
					                                            ethToken={etherToken}
 | 
				
			||||||
                                            dispatcher={this.props.dispatcher}
 | 
					                                            dispatcher={this.props.dispatcher}
 | 
				
			||||||
                                            blockchain={this.props.blockchain}
 | 
					                                            blockchain={this.props.blockchain}
 | 
				
			||||||
@@ -266,8 +262,8 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
 | 
				
			|||||||
                    ...etherToken,
 | 
					                    ...etherToken,
 | 
				
			||||||
                    address: outdatedWETHIfExists.address,
 | 
					                    address: outdatedWETHIfExists.address,
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
                const isStateLoaded = this.state.outdatedWETHAddressToIsStateLoaded[outdatedWETHIfExists.address];
 | 
					 | 
				
			||||||
                const outdatedEtherTokenState = this.state.outdatedWETHStateByAddress[outdatedWETHIfExists.address];
 | 
					                const outdatedEtherTokenState = this.state.outdatedWETHStateByAddress[outdatedWETHIfExists.address];
 | 
				
			||||||
 | 
					                const isStateLoaded = outdatedEtherTokenState.isLoaded;
 | 
				
			||||||
                const balanceInEthIfExists = isStateLoaded
 | 
					                const balanceInEthIfExists = isStateLoaded
 | 
				
			||||||
                    ? ZeroEx.toUnitAmount(outdatedEtherTokenState.balance, constants.DECIMAL_PLACES_ETH).toFixed(
 | 
					                    ? ZeroEx.toUnitAmount(outdatedEtherTokenState.balance, constants.DECIMAL_PLACES_ETH).toFixed(
 | 
				
			||||||
                          configs.AMOUNT_DISPLAY_PRECSION,
 | 
					                          configs.AMOUNT_DISPLAY_PRECSION,
 | 
				
			||||||
@@ -345,10 +341,15 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    private async _onOutdatedConversionSuccessfulAsync(outdatedWETHAddress: string) {
 | 
					    private async _onOutdatedConversionSuccessfulAsync(outdatedWETHAddress: string) {
 | 
				
			||||||
 | 
					        const currentOutdatedWETHState = this.state.outdatedWETHStateByAddress[outdatedWETHAddress];
 | 
				
			||||||
        this.setState({
 | 
					        this.setState({
 | 
				
			||||||
            outdatedWETHAddressToIsStateLoaded: {
 | 
					            outdatedWETHStateByAddress: {
 | 
				
			||||||
                ...this.state.outdatedWETHAddressToIsStateLoaded,
 | 
					                ...this.state.outdatedWETHStateByAddress,
 | 
				
			||||||
                [outdatedWETHAddress]: false,
 | 
					                [outdatedWETHAddress]: {
 | 
				
			||||||
 | 
					                    balance: currentOutdatedWETHState.balance,
 | 
				
			||||||
 | 
					                    allowance: currentOutdatedWETHState.allowance,
 | 
				
			||||||
 | 
					                    isLoaded: false,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
 | 
					        const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
 | 
				
			||||||
@@ -357,15 +358,12 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
 | 
				
			|||||||
            outdatedWETHAddress,
 | 
					            outdatedWETHAddress,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        this.setState({
 | 
					        this.setState({
 | 
				
			||||||
            outdatedWETHAddressToIsStateLoaded: {
 | 
					 | 
				
			||||||
                ...this.state.outdatedWETHAddressToIsStateLoaded,
 | 
					 | 
				
			||||||
                [outdatedWETHAddress]: true,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            outdatedWETHStateByAddress: {
 | 
					            outdatedWETHStateByAddress: {
 | 
				
			||||||
                ...this.state.outdatedWETHStateByAddress,
 | 
					                ...this.state.outdatedWETHStateByAddress,
 | 
				
			||||||
                [outdatedWETHAddress]: {
 | 
					                [outdatedWETHAddress]: {
 | 
				
			||||||
                    balance,
 | 
					                    balance,
 | 
				
			||||||
                    allowance,
 | 
					                    allowance,
 | 
				
			||||||
 | 
					                    isLoaded: true,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -380,8 +378,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const outdatedWETHAddresses = this._getOutdatedWETHAddresses();
 | 
					        const outdatedWETHAddresses = this._getOutdatedWETHAddresses();
 | 
				
			||||||
        const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {};
 | 
					        const outdatedWETHStateByAddress: TokenStateByAddress = {};
 | 
				
			||||||
        const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {};
 | 
					 | 
				
			||||||
        for (const address of outdatedWETHAddresses) {
 | 
					        for (const address of outdatedWETHAddresses) {
 | 
				
			||||||
            const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
 | 
					            const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
 | 
				
			||||||
                userAddressIfExists,
 | 
					                userAddressIfExists,
 | 
				
			||||||
@@ -390,18 +387,17 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
 | 
				
			|||||||
            outdatedWETHStateByAddress[address] = {
 | 
					            outdatedWETHStateByAddress[address] = {
 | 
				
			||||||
                balance,
 | 
					                balance,
 | 
				
			||||||
                allowance,
 | 
					                allowance,
 | 
				
			||||||
 | 
					                isLoaded: true,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            outdatedWETHAddressToIsStateLoaded[address] = true;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (!this._isUnmounted) {
 | 
					        if (!this._isUnmounted) {
 | 
				
			||||||
            this.setState({
 | 
					            this.setState({
 | 
				
			||||||
                outdatedWETHStateByAddress,
 | 
					                outdatedWETHStateByAddress,
 | 
				
			||||||
                outdatedWETHAddressToIsStateLoaded,
 | 
					 | 
				
			||||||
                ethTokenState: {
 | 
					                ethTokenState: {
 | 
				
			||||||
                    balance: wethBalance,
 | 
					                    balance: wethBalance,
 | 
				
			||||||
                    allowance: wethAllowance,
 | 
					                    allowance: wethAllowance,
 | 
				
			||||||
 | 
					                    isLoaded: true,
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                isWethStateLoaded: true,
 | 
					 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -434,6 +430,7 @@ export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersSt
 | 
				
			|||||||
            ethTokenState: {
 | 
					            ethTokenState: {
 | 
				
			||||||
                balance,
 | 
					                balance,
 | 
				
			||||||
                allowance,
 | 
					                allowance,
 | 
				
			||||||
 | 
					                isLoaded: true,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,12 +19,22 @@ import { TokenBalances } from 'ts/components/token_balances';
 | 
				
			|||||||
import { TopBar } from 'ts/components/top_bar/top_bar';
 | 
					import { TopBar } from 'ts/components/top_bar/top_bar';
 | 
				
			||||||
import { TradeHistory } from 'ts/components/trade_history/trade_history';
 | 
					import { TradeHistory } from 'ts/components/trade_history/trade_history';
 | 
				
			||||||
import { FlashMessage } from 'ts/components/ui/flash_message';
 | 
					import { FlashMessage } from 'ts/components/ui/flash_message';
 | 
				
			||||||
 | 
					import { Wallet } from 'ts/components/wallet';
 | 
				
			||||||
import { GenerateOrderForm } from 'ts/containers/generate_order_form';
 | 
					import { GenerateOrderForm } from 'ts/containers/generate_order_form';
 | 
				
			||||||
import { localStorage } from 'ts/local_storage/local_storage';
 | 
					import { localStorage } from 'ts/local_storage/local_storage';
 | 
				
			||||||
import { Dispatcher } from 'ts/redux/dispatcher';
 | 
					import { Dispatcher } from 'ts/redux/dispatcher';
 | 
				
			||||||
import { portalOrderSchema } from 'ts/schemas/portal_order_schema';
 | 
					import { portalOrderSchema } from 'ts/schemas/portal_order_schema';
 | 
				
			||||||
import { validator } from 'ts/schemas/validator';
 | 
					import { validator } from 'ts/schemas/validator';
 | 
				
			||||||
import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAddress, WebsitePaths } from 'ts/types';
 | 
					import {
 | 
				
			||||||
 | 
					    BlockchainErrs,
 | 
				
			||||||
 | 
					    Environments,
 | 
				
			||||||
 | 
					    HashData,
 | 
				
			||||||
 | 
					    Order,
 | 
				
			||||||
 | 
					    ProviderType,
 | 
				
			||||||
 | 
					    ScreenWidths,
 | 
				
			||||||
 | 
					    TokenByAddress,
 | 
				
			||||||
 | 
					    WebsitePaths,
 | 
				
			||||||
 | 
					} from 'ts/types';
 | 
				
			||||||
import { configs } from 'ts/utils/configs';
 | 
					import { configs } from 'ts/utils/configs';
 | 
				
			||||||
import { constants } from 'ts/utils/constants';
 | 
					import { constants } from 'ts/utils/constants';
 | 
				
			||||||
import { Translate } from 'ts/utils/translate';
 | 
					import { Translate } from 'ts/utils/translate';
 | 
				
			||||||
@@ -194,6 +204,12 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
 | 
				
			|||||||
                                    <div className="py2" style={{ backgroundColor: colors.grey50 }}>
 | 
					                                    <div className="py2" style={{ backgroundColor: colors.grey50 }}>
 | 
				
			||||||
                                        {this.props.blockchainIsLoaded ? (
 | 
					                                        {this.props.blockchainIsLoaded ? (
 | 
				
			||||||
                                            <Switch>
 | 
					                                            <Switch>
 | 
				
			||||||
 | 
					                                                {configs.ENVIRONMENT === Environments.DEVELOPMENT && (
 | 
				
			||||||
 | 
					                                                    <Route
 | 
				
			||||||
 | 
					                                                        path={`${WebsitePaths.Portal}/wallet`}
 | 
				
			||||||
 | 
					                                                        render={this._renderWallet.bind(this)}
 | 
				
			||||||
 | 
					                                                    />
 | 
				
			||||||
 | 
					                                                )}
 | 
				
			||||||
                                                <Route
 | 
					                                                <Route
 | 
				
			||||||
                                                    path={`${WebsitePaths.Portal}/weth`}
 | 
					                                                    path={`${WebsitePaths.Portal}/weth`}
 | 
				
			||||||
                                                    render={this._renderEthWrapper.bind(this)}
 | 
					                                                    render={this._renderEthWrapper.bind(this)}
 | 
				
			||||||
@@ -272,6 +288,28 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> {
 | 
				
			|||||||
            isLedgerDialogOpen: !this.state.isLedgerDialogOpen,
 | 
					            isLedgerDialogOpen: !this.state.isLedgerDialogOpen,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    private _renderWallet() {
 | 
				
			||||||
 | 
					        const allTokens = _.values(this.props.tokenByAddress);
 | 
				
			||||||
 | 
					        const trackedTokens = _.filter(allTokens, t => t.isTracked);
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <div className="flex flex-center">
 | 
				
			||||||
 | 
					                <div className="mx-auto">
 | 
				
			||||||
 | 
					                    <Wallet
 | 
				
			||||||
 | 
					                        userAddress={this.props.userAddress}
 | 
				
			||||||
 | 
					                        networkId={this.props.networkId}
 | 
				
			||||||
 | 
					                        blockchain={this._blockchain}
 | 
				
			||||||
 | 
					                        blockchainIsLoaded={this.props.blockchainIsLoaded}
 | 
				
			||||||
 | 
					                        blockchainErr={this.props.blockchainErr}
 | 
				
			||||||
 | 
					                        dispatcher={this.props.dispatcher}
 | 
				
			||||||
 | 
					                        tokenByAddress={this.props.tokenByAddress}
 | 
				
			||||||
 | 
					                        trackedTokens={trackedTokens}
 | 
				
			||||||
 | 
					                        userEtherBalanceInWei={this.props.userEtherBalanceInWei}
 | 
				
			||||||
 | 
					                        lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    private _renderEthWrapper() {
 | 
					    private _renderEthWrapper() {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <EthWrappers
 | 
					            <EthWrappers
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
import * as _ from 'lodash';
 | 
					import * as _ from 'lodash';
 | 
				
			||||||
import * as React from 'react';
 | 
					import * as React from 'react';
 | 
				
			||||||
import { MenuItem } from 'ts/components/ui/menu_item';
 | 
					import { MenuItem } from 'ts/components/ui/menu_item';
 | 
				
			||||||
import { WebsitePaths } from 'ts/types';
 | 
					import { Environments, WebsitePaths } from 'ts/types';
 | 
				
			||||||
 | 
					import { configs } from 'ts/utils/configs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface PortalMenuProps {
 | 
					export interface PortalMenuProps {
 | 
				
			||||||
    menuItemStyle: React.CSSProperties;
 | 
					    menuItemStyle: React.CSSProperties;
 | 
				
			||||||
@@ -57,6 +58,16 @@ export class PortalMenu extends React.Component<PortalMenuProps, PortalMenuState
 | 
				
			|||||||
                >
 | 
					                >
 | 
				
			||||||
                    {this._renderMenuItemWithIcon('Wrap ETH', 'zmdi-circle-o')}
 | 
					                    {this._renderMenuItemWithIcon('Wrap ETH', 'zmdi-circle-o')}
 | 
				
			||||||
                </MenuItem>
 | 
					                </MenuItem>
 | 
				
			||||||
 | 
					                {configs.ENVIRONMENT === Environments.DEVELOPMENT && (
 | 
				
			||||||
 | 
					                    <MenuItem
 | 
				
			||||||
 | 
					                        style={this.props.menuItemStyle}
 | 
				
			||||||
 | 
					                        className="py2"
 | 
				
			||||||
 | 
					                        to={`${WebsitePaths.Portal}/wallet`}
 | 
				
			||||||
 | 
					                        onClick={this.props.onClick.bind(this)}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        {this._renderMenuItemWithIcon('Wallet', 'zmdi-balance-wallet')}
 | 
				
			||||||
 | 
					                    </MenuItem>
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,6 +37,7 @@ import {
 | 
				
			|||||||
    ScreenWidths,
 | 
					    ScreenWidths,
 | 
				
			||||||
    Token,
 | 
					    Token,
 | 
				
			||||||
    TokenByAddress,
 | 
					    TokenByAddress,
 | 
				
			||||||
 | 
					    TokenStateByAddress,
 | 
				
			||||||
    TokenVisibility,
 | 
					    TokenVisibility,
 | 
				
			||||||
} from 'ts/types';
 | 
					} from 'ts/types';
 | 
				
			||||||
import { configs } from 'ts/utils/configs';
 | 
					import { configs } from 'ts/utils/configs';
 | 
				
			||||||
@@ -61,14 +62,6 @@ const styles: Styles = {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface TokenStateByAddress {
 | 
					 | 
				
			||||||
    [address: string]: {
 | 
					 | 
				
			||||||
        balance: BigNumber;
 | 
					 | 
				
			||||||
        allowance: BigNumber;
 | 
					 | 
				
			||||||
        isLoaded: boolean;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface TokenBalancesProps {
 | 
					interface TokenBalancesProps {
 | 
				
			||||||
    blockchain: Blockchain;
 | 
					    blockchain: Blockchain;
 | 
				
			||||||
    blockchainErr: BlockchainErrs;
 | 
					    blockchainErr: BlockchainErrs;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										373
									
								
								packages/website/ts/components/wallet.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										373
									
								
								packages/website/ts/components/wallet.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,373 @@
 | 
				
			|||||||
 | 
					import { ZeroEx } from '0x.js';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    colors,
 | 
				
			||||||
 | 
					    constants as sharedConstants,
 | 
				
			||||||
 | 
					    EtherscanLinkSuffixes,
 | 
				
			||||||
 | 
					    Styles,
 | 
				
			||||||
 | 
					    utils as sharedUtils,
 | 
				
			||||||
 | 
					} from '@0xproject/react-shared';
 | 
				
			||||||
 | 
					import { BigNumber } from '@0xproject/utils';
 | 
				
			||||||
 | 
					import * as _ from 'lodash';
 | 
				
			||||||
 | 
					import FlatButton from 'material-ui/FlatButton';
 | 
				
			||||||
 | 
					import { List, ListItem } from 'material-ui/List';
 | 
				
			||||||
 | 
					import NavigationArrowDownward from 'material-ui/svg-icons/navigation/arrow-downward';
 | 
				
			||||||
 | 
					import NavigationArrowUpward from 'material-ui/svg-icons/navigation/arrow-upward';
 | 
				
			||||||
 | 
					import * as React from 'react';
 | 
				
			||||||
 | 
					import ReactTooltip = require('react-tooltip');
 | 
				
			||||||
 | 
					import firstBy = require('thenby');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Blockchain } from 'ts/blockchain';
 | 
				
			||||||
 | 
					import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle';
 | 
				
			||||||
 | 
					import { Identicon } from 'ts/components/ui/identicon';
 | 
				
			||||||
 | 
					import { TokenIcon } from 'ts/components/ui/token_icon';
 | 
				
			||||||
 | 
					import { Dispatcher } from 'ts/redux/dispatcher';
 | 
				
			||||||
 | 
					import { BalanceErrs, BlockchainErrs, Token, TokenByAddress, TokenState, TokenStateByAddress } from 'ts/types';
 | 
				
			||||||
 | 
					import { constants } from 'ts/utils/constants';
 | 
				
			||||||
 | 
					import { utils } from 'ts/utils/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface WalletProps {
 | 
				
			||||||
 | 
					    userAddress?: string;
 | 
				
			||||||
 | 
					    networkId?: number;
 | 
				
			||||||
 | 
					    blockchain: Blockchain;
 | 
				
			||||||
 | 
					    blockchainIsLoaded: boolean;
 | 
				
			||||||
 | 
					    blockchainErr: BlockchainErrs;
 | 
				
			||||||
 | 
					    dispatcher: Dispatcher;
 | 
				
			||||||
 | 
					    tokenByAddress: TokenByAddress;
 | 
				
			||||||
 | 
					    trackedTokens: Token[];
 | 
				
			||||||
 | 
					    userEtherBalanceInWei: BigNumber;
 | 
				
			||||||
 | 
					    lastForceTokenStateRefetch: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface WalletState {
 | 
				
			||||||
 | 
					    trackedTokenStateByAddress: TokenStateByAddress;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum WrappedEtherAction {
 | 
				
			||||||
 | 
					    Wrap,
 | 
				
			||||||
 | 
					    Unwrap,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface AllowanceToggleConfig {
 | 
				
			||||||
 | 
					    token: Token;
 | 
				
			||||||
 | 
					    tokenState: TokenState;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface AccessoryItemConfig {
 | 
				
			||||||
 | 
					    wrappedEtherAction?: WrappedEtherAction;
 | 
				
			||||||
 | 
					    allowanceToggleConfig?: AllowanceToggleConfig;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const styles: Styles = {
 | 
				
			||||||
 | 
					    wallet: {
 | 
				
			||||||
 | 
					        width: 346,
 | 
				
			||||||
 | 
					        backgroundColor: colors.white,
 | 
				
			||||||
 | 
					        borderBottomRightRadius: 10,
 | 
				
			||||||
 | 
					        borderBottomLeftRadius: 10,
 | 
				
			||||||
 | 
					        borderTopRightRadius: 10,
 | 
				
			||||||
 | 
					        borderTopLeftRadius: 10,
 | 
				
			||||||
 | 
					        boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`,
 | 
				
			||||||
 | 
					        overflow: 'hidden',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    list: {
 | 
				
			||||||
 | 
					        padding: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    tokenItemInnerDiv: {
 | 
				
			||||||
 | 
					        paddingLeft: 60,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    headerItemInnerDiv: {
 | 
				
			||||||
 | 
					        paddingLeft: 65,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    footerItemInnerDiv: {
 | 
				
			||||||
 | 
					        paddingLeft: 24,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    borderedItem: {
 | 
				
			||||||
 | 
					        borderBottomColor: colors.walletBorder,
 | 
				
			||||||
 | 
					        borderBottomStyle: 'solid',
 | 
				
			||||||
 | 
					        borderWidth: 1,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    tokenItem: {
 | 
				
			||||||
 | 
					        backgroundColor: colors.walletDefaultItemBackground,
 | 
				
			||||||
 | 
					        paddingTop: 8,
 | 
				
			||||||
 | 
					        paddingBottom: 8,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    headerItem: {
 | 
				
			||||||
 | 
					        paddingTop: 8,
 | 
				
			||||||
 | 
					        paddingBottom: 8,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    wrappedEtherButtonLabel: {
 | 
				
			||||||
 | 
					        fontSize: 12,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    amountLabel: {
 | 
				
			||||||
 | 
					        fontWeight: 'bold',
 | 
				
			||||||
 | 
					        color: colors.black,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ETHER_ICON_PATH = '/images/ether.png';
 | 
				
			||||||
 | 
					const ETHER_TOKEN_SYMBOL = 'WETH';
 | 
				
			||||||
 | 
					const ZRX_TOKEN_SYMBOL = 'ZRX';
 | 
				
			||||||
 | 
					const ETHER_SYMBOL = 'ETH';
 | 
				
			||||||
 | 
					const ICON_DIMENSION = 24;
 | 
				
			||||||
 | 
					const TOKEN_AMOUNT_DISPLAY_PRECISION = 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class Wallet extends React.Component<WalletProps, WalletState> {
 | 
				
			||||||
 | 
					    private _isUnmounted: boolean;
 | 
				
			||||||
 | 
					    constructor(props: WalletProps) {
 | 
				
			||||||
 | 
					        super(props);
 | 
				
			||||||
 | 
					        this._isUnmounted = false;
 | 
				
			||||||
 | 
					        const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(props.trackedTokens);
 | 
				
			||||||
 | 
					        this.state = {
 | 
				
			||||||
 | 
					            trackedTokenStateByAddress: initialTrackedTokenStateByAddress,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public componentWillMount() {
 | 
				
			||||||
 | 
					        const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress);
 | 
				
			||||||
 | 
					        // tslint:disable-next-line:no-floating-promises
 | 
				
			||||||
 | 
					        this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public componentWillUnmount() {
 | 
				
			||||||
 | 
					        this._isUnmounted = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public componentWillReceiveProps(nextProps: WalletProps) {
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            nextProps.userAddress !== this.props.userAddress ||
 | 
				
			||||||
 | 
					            nextProps.networkId !== this.props.networkId ||
 | 
				
			||||||
 | 
					            nextProps.lastForceTokenStateRefetch !== this.props.lastForceTokenStateRefetch
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress);
 | 
				
			||||||
 | 
					            // tslint:disable-next-line:no-floating-promises
 | 
				
			||||||
 | 
					            this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!_.isEqual(nextProps.trackedTokens, this.props.trackedTokens)) {
 | 
				
			||||||
 | 
					            const newTokens = _.difference(nextProps.trackedTokens, this.props.trackedTokens);
 | 
				
			||||||
 | 
					            const newTokenAddresses = _.map(newTokens, token => token.address);
 | 
				
			||||||
 | 
					            // Add placeholder entry for this token to the state, since fetching the
 | 
				
			||||||
 | 
					            // balance/allowance is asynchronous
 | 
				
			||||||
 | 
					            const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress;
 | 
				
			||||||
 | 
					            _.each(newTokenAddresses, (tokenAddress: string) => {
 | 
				
			||||||
 | 
					                trackedTokenStateByAddress[tokenAddress] = {
 | 
				
			||||||
 | 
					                    balance: new BigNumber(0),
 | 
				
			||||||
 | 
					                    allowance: new BigNumber(0),
 | 
				
			||||||
 | 
					                    isLoaded: false,
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            this.setState({
 | 
				
			||||||
 | 
					                trackedTokenStateByAddress,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            // Fetch the actual balance/allowance.
 | 
				
			||||||
 | 
					            // tslint:disable-next-line:no-floating-promises
 | 
				
			||||||
 | 
					            this._fetchBalancesAndAllowancesAsync(newTokenAddresses);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public render() {
 | 
				
			||||||
 | 
					        const isReadyToRender = this.props.blockchainIsLoaded && this.props.blockchainErr === BlockchainErrs.NoError;
 | 
				
			||||||
 | 
					        return <div style={styles.wallet}>{isReadyToRender && this._renderRows()}</div>;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private _renderRows() {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <List style={styles.list}>
 | 
				
			||||||
 | 
					                {_.concat(
 | 
				
			||||||
 | 
					                    this._renderHeaderRows(),
 | 
				
			||||||
 | 
					                    this._renderEthRows(),
 | 
				
			||||||
 | 
					                    this._renderTokenRows(),
 | 
				
			||||||
 | 
					                    this._renderFooterRows(),
 | 
				
			||||||
 | 
					                )}
 | 
				
			||||||
 | 
					            </List>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private _renderHeaderRows() {
 | 
				
			||||||
 | 
					        const userAddress = this.props.userAddress;
 | 
				
			||||||
 | 
					        const primaryText = utils.getAddressBeginAndEnd(userAddress);
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <ListItem
 | 
				
			||||||
 | 
					                primaryText={primaryText}
 | 
				
			||||||
 | 
					                leftIcon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />}
 | 
				
			||||||
 | 
					                style={{ ...styles.headerItem, ...styles.borderedItem }}
 | 
				
			||||||
 | 
					                innerDivStyle={styles.headerItemInnerDiv}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private _renderFooterRows() {
 | 
				
			||||||
 | 
					        const primaryText = '+ other tokens';
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <ListItem primaryText={primaryText} style={styles.borderedItem} innerDivStyle={styles.footerItemInnerDiv} />
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private _renderEthRows() {
 | 
				
			||||||
 | 
					        const primaryText = this._renderAmount(
 | 
				
			||||||
 | 
					            this.props.userEtherBalanceInWei,
 | 
				
			||||||
 | 
					            constants.DECIMAL_PLACES_ETH,
 | 
				
			||||||
 | 
					            ETHER_SYMBOL,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        const accessoryItemConfig = {
 | 
				
			||||||
 | 
					            wrappedEtherAction: WrappedEtherAction.Wrap,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <ListItem
 | 
				
			||||||
 | 
					                primaryText={primaryText}
 | 
				
			||||||
 | 
					                leftIcon={<img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} />}
 | 
				
			||||||
 | 
					                rightAvatar={this._renderAccessoryItems(accessoryItemConfig)}
 | 
				
			||||||
 | 
					                style={{ ...styles.tokenItem, ...styles.borderedItem }}
 | 
				
			||||||
 | 
					                innerDivStyle={styles.tokenItemInnerDiv}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private _renderTokenRows() {
 | 
				
			||||||
 | 
					        const trackedTokens = this.props.trackedTokens;
 | 
				
			||||||
 | 
					        const trackedTokensStartingWithEtherToken = trackedTokens.sort(
 | 
				
			||||||
 | 
					            firstBy((t: Token) => t.symbol !== ETHER_TOKEN_SYMBOL)
 | 
				
			||||||
 | 
					                .thenBy((t: Token) => t.symbol !== ZRX_TOKEN_SYMBOL)
 | 
				
			||||||
 | 
					                .thenBy('address'),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private _renderTokenRow(token: Token) {
 | 
				
			||||||
 | 
					        const tokenState = this.state.trackedTokenStateByAddress[token.address];
 | 
				
			||||||
 | 
					        const tokenLink = sharedUtils.getEtherScanLinkIfExists(
 | 
				
			||||||
 | 
					            token.address,
 | 
				
			||||||
 | 
					            this.props.networkId,
 | 
				
			||||||
 | 
					            EtherscanLinkSuffixes.Address,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        const amount = this._renderAmount(tokenState.balance, token.decimals, token.symbol);
 | 
				
			||||||
 | 
					        const wrappedEtherAction = token.symbol === ETHER_TOKEN_SYMBOL ? WrappedEtherAction.Unwrap : undefined;
 | 
				
			||||||
 | 
					        const accessoryItemConfig: AccessoryItemConfig = {
 | 
				
			||||||
 | 
					            wrappedEtherAction,
 | 
				
			||||||
 | 
					            allowanceToggleConfig: {
 | 
				
			||||||
 | 
					                token,
 | 
				
			||||||
 | 
					                tokenState,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <ListItem
 | 
				
			||||||
 | 
					                primaryText={amount}
 | 
				
			||||||
 | 
					                leftIcon={this._renderTokenIcon(token, tokenLink)}
 | 
				
			||||||
 | 
					                rightAvatar={this._renderAccessoryItems(accessoryItemConfig)}
 | 
				
			||||||
 | 
					                style={{ ...styles.tokenItem, ...styles.borderedItem }}
 | 
				
			||||||
 | 
					                innerDivStyle={styles.tokenItemInnerDiv}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private _renderAccessoryItems(config: AccessoryItemConfig) {
 | 
				
			||||||
 | 
					        const shouldShowWrappedEtherAction = !_.isUndefined(config.wrappedEtherAction);
 | 
				
			||||||
 | 
					        const shouldShowToggle = !_.isUndefined(config.allowanceToggleConfig);
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <div style={{ width: 160 }}>
 | 
				
			||||||
 | 
					                <div className="flex">
 | 
				
			||||||
 | 
					                    <div className="flex-auto">
 | 
				
			||||||
 | 
					                        {shouldShowWrappedEtherAction && this._renderWrappedEtherButton(config.wrappedEtherAction)}
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div className="flex-last py1">
 | 
				
			||||||
 | 
					                        {shouldShowToggle && this._renderAllowanceToggle(config.allowanceToggleConfig)}
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private _renderAllowanceToggle(config: AllowanceToggleConfig) {
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <AllowanceToggle
 | 
				
			||||||
 | 
					                networkId={this.props.networkId}
 | 
				
			||||||
 | 
					                blockchain={this.props.blockchain}
 | 
				
			||||||
 | 
					                dispatcher={this.props.dispatcher}
 | 
				
			||||||
 | 
					                token={config.token}
 | 
				
			||||||
 | 
					                tokenState={config.tokenState}
 | 
				
			||||||
 | 
					                onErrorOccurred={_.noop} // TODO: Error handling
 | 
				
			||||||
 | 
					                userAddress={this.props.userAddress}
 | 
				
			||||||
 | 
					                isDisabled={!config.tokenState.isLoaded}
 | 
				
			||||||
 | 
					                refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, config.token.address)}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private _renderAmount(amount: BigNumber, decimals: number, symbol: string) {
 | 
				
			||||||
 | 
					        const unitAmount = ZeroEx.toUnitAmount(amount, decimals);
 | 
				
			||||||
 | 
					        const formattedAmount = unitAmount.toPrecision(TOKEN_AMOUNT_DISPLAY_PRECISION);
 | 
				
			||||||
 | 
					        const result = `${formattedAmount} ${symbol}`;
 | 
				
			||||||
 | 
					        return <div style={styles.amountLabel}>{result}</div>;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private _renderTokenIcon(token: Token, tokenLink?: string) {
 | 
				
			||||||
 | 
					        const tooltipId = `tooltip-${token.address}`;
 | 
				
			||||||
 | 
					        const tokenIcon = <TokenIcon token={token} diameter={ICON_DIMENSION} />;
 | 
				
			||||||
 | 
					        if (_.isUndefined(tokenLink)) {
 | 
				
			||||||
 | 
					            return tokenIcon;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return (
 | 
				
			||||||
 | 
					                <a href={tokenLink} target="_blank" style={{ textDecoration: 'none' }}>
 | 
				
			||||||
 | 
					                    {tokenIcon}
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private _renderWrappedEtherButton(action: WrappedEtherAction) {
 | 
				
			||||||
 | 
					        let buttonLabel;
 | 
				
			||||||
 | 
					        let buttonIcon;
 | 
				
			||||||
 | 
					        switch (action) {
 | 
				
			||||||
 | 
					            case WrappedEtherAction.Wrap:
 | 
				
			||||||
 | 
					                buttonLabel = 'wrap';
 | 
				
			||||||
 | 
					                buttonIcon = <NavigationArrowDownward />;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case WrappedEtherAction.Unwrap:
 | 
				
			||||||
 | 
					                buttonLabel = 'unwrap';
 | 
				
			||||||
 | 
					                buttonIcon = <NavigationArrowUpward />;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                throw utils.spawnSwitchErr('wrappedEtherAction', action);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            <FlatButton
 | 
				
			||||||
 | 
					                label={buttonLabel}
 | 
				
			||||||
 | 
					                labelPosition="after"
 | 
				
			||||||
 | 
					                primary={true}
 | 
				
			||||||
 | 
					                icon={buttonIcon}
 | 
				
			||||||
 | 
					                labelStyle={styles.wrappedEtherButtonLabel}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private _getInitialTrackedTokenStateByAddress(trackedTokens: Token[]) {
 | 
				
			||||||
 | 
					        const trackedTokenStateByAddress: TokenStateByAddress = {};
 | 
				
			||||||
 | 
					        _.each(trackedTokens, token => {
 | 
				
			||||||
 | 
					            trackedTokenStateByAddress[token.address] = {
 | 
				
			||||||
 | 
					                balance: new BigNumber(0),
 | 
				
			||||||
 | 
					                allowance: new BigNumber(0),
 | 
				
			||||||
 | 
					                isLoaded: false,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return trackedTokenStateByAddress;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]) {
 | 
				
			||||||
 | 
					        const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress;
 | 
				
			||||||
 | 
					        const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
 | 
				
			||||||
 | 
					        for (const tokenAddress of tokenAddresses) {
 | 
				
			||||||
 | 
					            const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
 | 
				
			||||||
 | 
					                userAddressIfExists,
 | 
				
			||||||
 | 
					                tokenAddress,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            trackedTokenStateByAddress[tokenAddress] = {
 | 
				
			||||||
 | 
					                balance,
 | 
				
			||||||
 | 
					                allowance,
 | 
				
			||||||
 | 
					                isLoaded: true,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!this._isUnmounted) {
 | 
				
			||||||
 | 
					            this.setState({
 | 
				
			||||||
 | 
					                trackedTokenStateByAddress,
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    private async _refetchTokenStateAsync(tokenAddress: string) {
 | 
				
			||||||
 | 
					        const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress;
 | 
				
			||||||
 | 
					        const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync(
 | 
				
			||||||
 | 
					            userAddressIfExists,
 | 
				
			||||||
 | 
					            tokenAddress,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        this.setState({
 | 
				
			||||||
 | 
					            trackedTokenStateByAddress: {
 | 
				
			||||||
 | 
					                ...this.state.trackedTokenStateByAddress,
 | 
				
			||||||
 | 
					                [tokenAddress]: {
 | 
				
			||||||
 | 
					                    balance,
 | 
				
			||||||
 | 
					                    allowance,
 | 
				
			||||||
 | 
					                    isLoaded: true,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -21,11 +21,6 @@ export interface TokenByAddress {
 | 
				
			|||||||
    [address: string]: Token;
 | 
					    [address: string]: Token;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface TokenState {
 | 
					 | 
				
			||||||
    allowance: BigNumber;
 | 
					 | 
				
			||||||
    balance: BigNumber;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface AssetToken {
 | 
					export interface AssetToken {
 | 
				
			||||||
    address?: string;
 | 
					    address?: string;
 | 
				
			||||||
    amount?: BigNumber;
 | 
					    amount?: BigNumber;
 | 
				
			||||||
@@ -484,4 +479,14 @@ export interface OutdatedWrappedEtherByNetworkId {
 | 
				
			|||||||
        timestampMsRange: TimestampMsRange;
 | 
					        timestampMsRange: TimestampMsRange;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface TokenStateByAddress {
 | 
				
			||||||
 | 
					    [address: string]: TokenState;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface TokenState {
 | 
				
			||||||
 | 
					    balance: BigNumber;
 | 
				
			||||||
 | 
					    allowance: BigNumber;
 | 
				
			||||||
 | 
					    isLoaded: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
// tslint:disable:max-file-line-count
 | 
					// tslint:disable:max-file-line-count
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user