From f9615c18a1cc16794b6a55b76aa361332494a6ac Mon Sep 17 00:00:00 2001 From: fragosti Date: Wed, 30 May 2018 17:33:17 -0700 Subject: Move trackedTokenStateByAddress logic into portal --- packages/website/ts/components/portal/portal.tsx | 98 ++++++++++++++++++++++-- packages/website/ts/components/wallet/wallet.tsx | 85 ++------------------ 2 files changed, 100 insertions(+), 83 deletions(-) diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 589ad00ad..af90e2a09 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -37,7 +37,9 @@ import { Order, ProviderType, ScreenWidths, + Token, TokenByAddress, + TokenStateByAddress, TokenVisibility, WebsitePaths, } from 'ts/types'; @@ -77,6 +79,7 @@ interface PortalState { isDisclaimerDialogOpen: boolean; isLedgerDialogOpen: boolean; tokenManagementState: TokenManagementState; + trackedTokenStateByAddress: TokenStateByAddress; } interface AccountManagementItem { @@ -127,6 +130,9 @@ export class Portal extends React.Component { const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER); const hasAcceptedDisclaimer = !_.isUndefined(didAcceptPortalDisclaimer) && !_.isEmpty(didAcceptPortalDisclaimer); + const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress( + this._getCurrentTrackedTokens(), + ); this.state = { prevNetworkId: this.props.networkId, prevNodeVersion: this.props.nodeVersion, @@ -135,6 +141,7 @@ export class Portal extends React.Component { isDisclaimerDialogOpen: !hasAcceptedDisclaimer, tokenManagementState: TokenManagementState.None, isLedgerDialogOpen: false, + trackedTokenStateByAddress: initialTrackedTokenStateByAddress, }; } public componentDidMount(): void { @@ -143,6 +150,9 @@ export class Portal extends React.Component { } public componentWillMount(): void { this._blockchain = new Blockchain(this.props.dispatcher); + const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress); + // tslint:disable-next-line:no-floating-promises + this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses); } public componentWillUnmount(): void { this._blockchain.destroy(); @@ -178,6 +188,39 @@ export class Portal extends React.Component { prevPathname: nextProps.location.pathname, }); } + 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); + } + + const nextTrackedTokens = this._getTrackedTokens(nextProps.tokenByAddress); + const trackedTokens = this._getCurrentTrackedTokens(); + + if (!_.isEqual(nextTrackedTokens, trackedTokens)) { + const newTokens = _.difference(nextTrackedTokens, 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; + for (const tokenAddress of newTokenAddresses) { + 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(): React.ReactNode { const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen.bind( @@ -283,8 +326,6 @@ export class Portal extends React.Component { ); } private _renderWallet(): React.ReactNode { - const allTokens = _.values(this.props.tokenByAddress); - const trackedTokens = _.filter(allTokens, t => t.isTracked); return (
{ blockchainErr={this.props.blockchainErr} dispatcher={this.props.dispatcher} tokenByAddress={this.props.tokenByAddress} - trackedTokens={trackedTokens} + trackedTokens={this._getCurrentTrackedTokens()} userEtherBalanceInWei={this.props.userEtherBalanceInWei} lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} injectedProviderName={this.props.injectedProviderName} providerType={this.props.providerType} screenWidth={this.props.screenWidth} location={this.props.location} + trackedTokenStateByAddress={this.state.trackedTokenStateByAddress} onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)} onAddToken={this._onAddToken.bind(this)} onRemoveToken={this._onRemoveToken.bind(this)} + refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)} /> @@ -424,8 +467,6 @@ export class Portal extends React.Component { ); } private _renderTokenBalances(): React.ReactNode { - const allTokens = _.values(this.props.tokenByAddress); - const trackedTokens = _.filter(allTokens, t => t.isTracked); return ( { dispatcher={this.props.dispatcher} screenWidth={this.props.screenWidth} tokenByAddress={this.props.tokenByAddress} - trackedTokens={trackedTokens} + trackedTokens={this._getCurrentTrackedTokens()} userAddress={this.props.userAddress} userEtherBalanceInWei={this.props.userEtherBalanceInWei} networkId={this.props.networkId} @@ -515,6 +556,51 @@ export class Portal extends React.Component { const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; return isSmallScreen; } + + private _getCurrentTrackedTokens(): Token[] { + return this._getTrackedTokens(this.props.tokenByAddress); + } + + private _getTrackedTokens(tokenByAddress: TokenByAddress): Token[] { + const allTokens = _.values(tokenByAddress); + const trackedTokens = _.filter(allTokens, t => t.isTracked); + return trackedTokens; + } + + private _getInitialTrackedTokenStateByAddress(trackedTokens: Token[]): TokenStateByAddress { + 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[]): Promise { + 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._blockchain.getTokenBalanceAndAllowanceAsync( + userAddressIfExists, + tokenAddress, + ); + trackedTokenStateByAddress[tokenAddress] = { + balance, + allowance, + isLoaded: true, + }; + } + this.setState({ + trackedTokenStateByAddress, + }); + } + + private async _refetchTokenStateAsync(tokenAddress: string): Promise { + await this._fetchBalancesAndAllowancesAsync([tokenAddress]); + } } interface LargeLayoutProps { diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index 18dada22f..af6b216c5 100644 --- a/packages/website/ts/components/wallet/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -67,13 +67,14 @@ export interface WalletProps { providerType: ProviderType; screenWidth: ScreenWidths; location: Location; + trackedTokenStateByAddress: TokenStateByAddress; onToggleLedgerDialog: () => void; onAddToken: () => void; onRemoveToken: () => void; + refetchTokenStateAsync: (tokenAddress: string) => Promise; } interface WalletState { - trackedTokenStateByAddress: TokenStateByAddress; wrappedEtherDirection?: Side; isHoveringSidebar: boolean; } @@ -151,48 +152,12 @@ export class Wallet extends React.Component { constructor(props: WalletProps) { super(props); this._isUnmounted = false; - const trackedTokenAddresses = _.map(props.trackedTokens, token => token.address); - const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(trackedTokenAddresses); this.state = { - trackedTokenStateByAddress: initialTrackedTokenStateByAddress, wrappedEtherDirection: undefined, isHoveringSidebar: false, }; } - public componentWillMount(): void { - const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress); - // tslint:disable-next-line:no-floating-promises - this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses); - } - public componentWillUnmount(): void { - this._isUnmounted = true; - } - public componentWillReceiveProps(nextProps: WalletProps): void { - 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; - const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(newTokenAddresses); - _.assign(trackedTokenStateByAddress, initialTrackedTokenStateByAddress); - this.setState({ - trackedTokenStateByAddress, - }); - // Fetch the actual balance/allowance. - // tslint:disable-next-line:no-floating-promises - this._fetchBalancesAndAllowancesAsync(newTokenAddresses); - } - } + public render(): React.ReactNode { const isBlockchainLoaded = this.props.blockchainIsLoaded && this.props.blockchainErr === BlockchainErrs.NoError; return ( @@ -342,7 +307,7 @@ export class Wallet extends React.Component { _.isUndefined(this.props.userEtherBalanceInWei), ); const etherToken = this._getEthToken(); - const etherTokenState = this.state.trackedTokenStateByAddress[etherToken.address]; + const etherTokenState = this.props.trackedTokenStateByAddress[etherToken.address]; const etherPrice = etherTokenState.price; const secondaryText = this._renderValue( this.props.userEtherBalanceInWei || new BigNumber(0), @@ -366,7 +331,7 @@ export class Wallet extends React.Component { return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this)); } private _renderTokenRow(token: Token, index: number): React.ReactNode { - const tokenState = this.state.trackedTokenStateByAddress[token.address]; + const tokenState = this.props.trackedTokenStateByAddress[token.address]; const tokenLink = sharedUtils.getEtherScanLinkIfExists( token.address, this.props.networkId, @@ -438,7 +403,8 @@ export class Wallet extends React.Component { etherToken={etherToken} lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} onConversionSuccessful={this._closeWrappedEtherActionRow.bind(this)} - refetchEthTokenStateAsync={this._refetchTokenStateAsync.bind(this, etherToken.address)} + // tslint:disable:jsx-no-lambda + refetchEthTokenStateAsync={() => this.props.refetchTokenStateAsync(etherToken.address)} /> )}
@@ -474,7 +440,7 @@ export class Wallet extends React.Component { onErrorOccurred={_.noop} // TODO: Error handling userAddress={this.props.userAddress} isDisabled={!config.tokenState.isLoaded} - refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this, config.token.address)} + refetchTokenStateAsync={() => this.props.refetchTokenStateAsync(config.token.address)} /> ); } @@ -557,42 +523,7 @@ export class Wallet extends React.Component { }); return trackedTokenStateByAddress; } - private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]): Promise { - const balanceAndAllowanceTupleByAddress: ItemByAddress = {}; - const userAddressIfExists = _.isEmpty(this.props.userAddress) ? undefined : this.props.userAddress; - for (const tokenAddress of tokenAddresses) { - const balanceAndAllowanceTuple = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( - userAddressIfExists, - tokenAddress, - ); - balanceAndAllowanceTupleByAddress[tokenAddress] = balanceAndAllowanceTuple; - } - const priceByAddress = await this._getPriceByAddressAsync(tokenAddresses); - const trackedTokenStateByAddress = _.reduce( - tokenAddresses, - (acc, address) => { - const [balance, allowance] = balanceAndAllowanceTupleByAddress[address]; - const priceIfExists = _.get(priceByAddress, address); - acc[address] = { - balance, - allowance, - price: priceIfExists, - isLoaded: true, - }; - return acc; - }, - this.state.trackedTokenStateByAddress, - ); - if (!this._isUnmounted) { - this.setState({ - trackedTokenStateByAddress, - }); - } - } - private async _refetchTokenStateAsync(tokenAddress: string): Promise { - await this._fetchBalancesAndAllowancesAsync([tokenAddress]); - } private async _getPriceByAddressAsync(tokenAddresses: string[]): Promise> { if (_.isEmpty(tokenAddresses)) { return {}; -- cgit From f382609d01ab86da52a746ba24ddd73a1a507aa6 Mon Sep 17 00:00:00 2001 From: fragosti Date: Wed, 30 May 2018 17:51:21 -0700 Subject: Implement WETH step logic for continue --- .../ts/components/onboarding/portal_onboarding_flow.tsx | 11 ++++++++--- packages/website/ts/components/portal/portal.tsx | 2 +- packages/website/ts/containers/portal_onboarding_flow.ts | 8 +++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index 2014dd7b0..398ce0141 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { BigNumber } from '@0xproject/utils'; import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow'; -import { ProviderType, TokenByAddress } from 'ts/types'; +import { ProviderType, TokenByAddress, TokenStateByAddress } from 'ts/types'; import { utils } from 'ts/utils/utils'; export interface PortalOnboardingFlowProps { @@ -16,6 +16,7 @@ export interface PortalOnboardingFlowProps { blockchainIsLoaded: boolean; userEtherBalanceInWei?: BigNumber; tokenByAddress: TokenByAddress; + trackedTokenStateByAddress: TokenStateByAddress; updateIsRunning: (isRunning: boolean) => void; updateOnboardingStep: (stepIndex: number) => void; } @@ -89,8 +90,12 @@ export class PortalOnboardingFlow extends React.Component new BigNumber(0); } private _overrideOnboardingStateIfShould(): void { diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index af90e2a09..15654083f 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -233,7 +233,7 @@ export class Portal extends React.Component { : TokenVisibility.TRACKED; return (
- + void; } -const mapStateToProps = (state: State): ConnectedState => ({ +const mapStateToProps = (state: State, ownProps: PortalOnboardingFlowProps): ConnectedState => ({ stepIndex: state.portalOnboardingStep, isRunning: state.isPortalOnboardingShowing, userAddress: state.userAddress, -- cgit From c8421efcd32af9dbe58b433fdb97d009a6d89b84 Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 31 May 2018 12:00:31 -0700 Subject: Address feedback --- packages/website/ts/blockchain.ts | 8 +++++--- .../ts/components/onboarding/portal_onboarding_flow.tsx | 8 ++++---- packages/website/ts/components/portal/portal.tsx | 14 +++++++++----- packages/website/ts/components/wallet/wallet.tsx | 4 ++-- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts index 212e829dd..6e4d03e27 100644 --- a/packages/website/ts/blockchain.ts +++ b/packages/website/ts/blockchain.ts @@ -506,7 +506,7 @@ export class Blockchain { public async getTokenBalanceAndAllowanceAsync( ownerAddressIfExists: string, tokenAddress: string, - ): Promise { + ): Promise<[BigNumber, BigNumber]> { utils.assert(!_.isUndefined(this._contractWrappers), 'ContractWrappers must be instantiated.'); if (_.isUndefined(ownerAddressIfExists)) { @@ -516,8 +516,10 @@ export class Blockchain { let balance = new BigNumber(0); let allowance = new BigNumber(0); if (this._doesUserAddressExist()) { - balance = await this._contractWrappers.token.getBalanceAsync(tokenAddress, ownerAddressIfExists); - allowance = await this._contractWrappers.token.getProxyAllowanceAsync(tokenAddress, ownerAddressIfExists); + [balance, allowance] = await Promise.all([ + this._contractWrappers.token.getBalanceAsync(tokenAddress, ownerAddressIfExists), + this._contractWrappers.token.getProxyAllowanceAsync(tokenAddress, ownerAddressIfExists), + ]); } return [balance, allowance]; } diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx index 398ce0141..bf52684d7 100644 --- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx +++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx @@ -69,13 +69,13 @@ export class PortalOnboardingFlow extends React.Component new BigNumber(0); } - private _userHasWeth(): boolean { + private _userHasVisibleWeth(): boolean { const ethToken = utils.getEthToken(this.props.tokenByAddress); if (!ethToken) { return false; diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx index 15654083f..9aa83546a 100644 --- a/packages/website/ts/components/portal/portal.tsx +++ b/packages/website/ts/components/portal/portal.tsx @@ -582,11 +582,15 @@ export class Portal extends React.Component { private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]): Promise { 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._blockchain.getTokenBalanceAndAllowanceAsync( - userAddressIfExists, - tokenAddress, - ); + const balancesAndAllowances = await Promise.all( + tokenAddresses.map(async tokenAddress => { + return this._blockchain.getTokenBalanceAndAllowanceAsync(userAddressIfExists, tokenAddress); + }), + ); + for (let i = 0; i < tokenAddresses.length; i++) { + // Order is preserved in Promise.all + const [balance, allowance] = balancesAndAllowances[i]; + const tokenAddress = tokenAddresses[i]; trackedTokenStateByAddress[tokenAddress] = { balance, allowance, diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx index af6b216c5..7059ca350 100644 --- a/packages/website/ts/components/wallet/wallet.tsx +++ b/packages/website/ts/components/wallet/wallet.tsx @@ -404,7 +404,7 @@ export class Wallet extends React.Component { lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} onConversionSuccessful={this._closeWrappedEtherActionRow.bind(this)} // tslint:disable:jsx-no-lambda - refetchEthTokenStateAsync={() => this.props.refetchTokenStateAsync(etherToken.address)} + refetchEthTokenStateAsync={async () => this.props.refetchTokenStateAsync(etherToken.address)} /> )}
@@ -440,7 +440,7 @@ export class Wallet extends React.Component { onErrorOccurred={_.noop} // TODO: Error handling userAddress={this.props.userAddress} isDisabled={!config.tokenState.isLoaded} - refetchTokenStateAsync={() => this.props.refetchTokenStateAsync(config.token.address)} + refetchTokenStateAsync={async () => this.props.refetchTokenStateAsync(config.token.address)} /> ); } -- cgit