From cfb9f874180c95f396ffc5a40fc508584c344802 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 5 Dec 2017 22:07:25 -0600 Subject: Fix Party element so that an identicon's height is that which was passed in --- packages/website/ts/components/ui/party.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/ui/party.tsx b/packages/website/ts/components/ui/party.tsx index 2927d9d3d..5bafa6071 100644 --- a/packages/website/ts/components/ui/party.tsx +++ b/packages/website/ts/components/ui/party.tsx @@ -73,7 +73,7 @@ export class Party extends React.Component { /> :
Date: Tue, 5 Dec 2017 22:15:31 -0600 Subject: Add missing await --- packages/website/ts/components/ui/lifecycle_raised_button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/ui/lifecycle_raised_button.tsx b/packages/website/ts/components/ui/lifecycle_raised_button.tsx index 630f71545..338a3bf76 100644 --- a/packages/website/ts/components/ui/lifecycle_raised_button.tsx +++ b/packages/website/ts/components/ui/lifecycle_raised_button.tsx @@ -83,7 +83,7 @@ export class LifeCycleRaisedButton extends this.setState({ buttonState: ButtonState.LOADING, }); - const didSucceed = this.props.onClickAsyncFn(); + const didSucceed = await this.props.onClickAsyncFn(); if (this.didUnmount) { return; // noop since unmount called before async callback returned. } -- cgit v1.2.3 From b4faa4851a08fd526a6ffc546eed575b796a5b3d Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 28 Jan 2018 10:28:17 +0100 Subject: Properly detect user signing cancellation on Metamask, Parity signer and Ledger --- packages/website/ts/components/eth_weth_conversion_button.tsx | 2 +- packages/website/ts/components/fill_order.tsx | 4 ++-- packages/website/ts/components/inputs/allowance_toggle.tsx | 2 +- packages/website/ts/components/send_button.tsx | 2 +- packages/website/ts/components/token_balances.tsx | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx index 300e71f1f..cc5e623ea 100644 --- a/packages/website/ts/components/eth_weth_conversion_button.tsx +++ b/packages/website/ts/components/eth_weth_conversion_button.tsx @@ -108,7 +108,7 @@ export class EthWethConversionButton extends React.Component< const errMsg = `${err}`; if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - } else if (!_.includes(errMsg, 'User denied transaction')) { + } else if (!utils.didUserDenyWeb3Request(errMsg)) { utils.consoleLog(`Unexpected error encountered: ${err}`); utils.consoleLog(err.stack); const errorMsg = diff --git a/packages/website/ts/components/fill_order.tsx b/packages/website/ts/components/fill_order.tsx index 1a150e9ee..28488f399 100644 --- a/packages/website/ts/components/fill_order.tsx +++ b/packages/website/ts/components/fill_order.tsx @@ -573,7 +573,7 @@ export class FillOrder extends React.Component { isFilling: false, }); const errMsg = `${err}`; - if (_.includes(errMsg, 'User denied transaction signature')) { + if (utils.didUserDenyWeb3Request(errMsg)) { return; } globalErrMsg = 'Failed to fill order, please refresh and try again'; @@ -653,7 +653,7 @@ export class FillOrder extends React.Component { isCancelling: false, }); const errMsg = `${err}`; - if (_.includes(errMsg, 'User denied transaction signature')) { + if (utils.didUserDenyWeb3Request(errMsg)) { return; } globalErrMsg = 'Failed to cancel order, please refresh and try again'; diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx index da46db4f4..d8852c11f 100644 --- a/packages/website/ts/components/inputs/allowance_toggle.tsx +++ b/packages/website/ts/components/inputs/allowance_toggle.tsx @@ -78,7 +78,7 @@ export class AllowanceToggle extends React.Component Date: Sun, 28 Jan 2018 10:29:15 +0100 Subject: Initial Ledger support implementation --- .../ts/components/dialogs/ledger_config_dialog.tsx | 46 ++- .../ts/components/dropdowns/network_drop_down.tsx | 40 +++ packages/website/ts/components/portal.tsx | 35 +- packages/website/ts/components/top_bar.tsx | 347 ------------------- .../ts/components/top_bar/provider_display.tsx | 149 ++++++++ .../ts/components/top_bar/provider_picker.tsx | 88 +++++ packages/website/ts/components/top_bar/top_bar.tsx | 377 +++++++++++++++++++++ .../ts/components/top_bar/top_bar_menu_item.tsx | 52 +++ .../website/ts/components/top_bar_menu_item.tsx | 52 --- packages/website/ts/components/ui/drop_down.tsx | 110 ++++++ .../ts/components/ui/drop_down_menu_item.tsx | 104 ------ 11 files changed, 887 insertions(+), 513 deletions(-) create mode 100644 packages/website/ts/components/dropdowns/network_drop_down.tsx delete mode 100644 packages/website/ts/components/top_bar.tsx create mode 100644 packages/website/ts/components/top_bar/provider_display.tsx create mode 100644 packages/website/ts/components/top_bar/provider_picker.tsx create mode 100644 packages/website/ts/components/top_bar/top_bar.tsx create mode 100644 packages/website/ts/components/top_bar/top_bar_menu_item.tsx delete mode 100644 packages/website/ts/components/top_bar_menu_item.tsx create mode 100644 packages/website/ts/components/ui/drop_down.tsx delete mode 100644 packages/website/ts/components/ui/drop_down_menu_item.tsx (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index 60db93c52..aff3f67b1 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -7,8 +7,10 @@ import TextField from 'material-ui/TextField'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); import { Blockchain } from 'ts/blockchain'; +import { NetworkDropDown } from 'ts/components/dropdowns/network_drop_down'; import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; import { Dispatcher } from 'ts/redux/dispatcher'; +import { ProviderType } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; @@ -27,27 +29,30 @@ interface LedgerConfigDialogProps { dispatcher: Dispatcher; blockchain: Blockchain; networkId: number; + providerType: ProviderType; } interface LedgerConfigDialogState { - didConnectFail: boolean; + connectionErrMsg: string; stepIndex: LedgerSteps; userAddresses: string[]; addressBalances: BigNumber[]; derivationPath: string; derivationErrMsg: string; + preferredNetworkId: number; } export class LedgerConfigDialog extends React.Component { constructor(props: LedgerConfigDialogProps) { super(props); this.state = { - didConnectFail: false, + connectionErrMsg: '', stepIndex: LedgerSteps.CONNECT, userAddresses: [], addressBalances: [], derivationPath: configs.DEFAULT_DERIVATION_PATH, derivationErrMsg: '', + preferredNetworkId: props.networkId, }; } public render() { @@ -77,7 +82,7 @@ export class LedgerConfigDialog extends React.Component
Follow these instructions before proceeding:
-
    +
    1. Connect your Ledger Nano S & Open the Ethereum application
    2. Verify that Browser Support is enabled in Settings
    3. @@ -86,7 +91,15 @@ export class LedgerConfigDialog extends React.Component1.2
    4. +
    5. Choose your desired network:
    +
    + +
    - {this.state.didConnectFail && ( + {!_.isEmpty(this.state.connectionErrMsg) && (
    - Failed to connect. Follow the instructions and try again. + {this.state.connectionErrMsg}
    )}
    @@ -172,7 +185,7 @@ export class LedgerConfigDialog extends React.Component void; + selectedNetworkId: number; + avialableNetworkIds: number[]; +} + +interface NetworkDropDownState {} + +export class NetworkDropDown extends React.Component { + public render() { + return ( +
    + + {this._renderDropDownItems()} + +
    + ); + } + private _renderDropDownItems() { + const items = _.map(this.props.avialableNetworkIds, networkId => { + const networkName = constants.NETWORK_NAME_BY_ID[networkId]; + const primaryText = ( +
    +
    + +
    +
    {networkName}
    +
    + ); + return ; + }); + return items; + } +} diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx index e2e28e8b6..5975569c4 100644 --- a/packages/website/ts/components/portal.tsx +++ b/packages/website/ts/components/portal.tsx @@ -6,6 +6,7 @@ import * as DocumentTitle from 'react-document-title'; import { Route, Switch } from 'react-router-dom'; import { Blockchain } from 'ts/blockchain'; import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog'; +import { LedgerConfigDialog } from 'ts/components/dialogs/ledger_config_dialog'; import { PortalDisclaimerDialog } from 'ts/components/dialogs/portal_disclaimer_dialog'; import { WrappedEthSectionNoticeDialog } from 'ts/components/dialogs/wrapped_eth_section_notice_dialog'; import { EthWrappers } from 'ts/components/eth_wrappers'; @@ -13,19 +14,21 @@ import { FillOrder } from 'ts/components/fill_order'; import { Footer } from 'ts/components/footer'; import { PortalMenu } from 'ts/components/portal_menu'; import { TokenBalances } from 'ts/components/token_balances'; -import { TopBar } from 'ts/components/top_bar'; +import { TopBar } from 'ts/components/top_bar/top_bar'; import { TradeHistory } from 'ts/components/trade_history/trade_history'; import { FlashMessage } from 'ts/components/ui/flash_message'; import { Loading } from 'ts/components/ui/loading'; import { GenerateOrderForm } from 'ts/containers/generate_order_form'; import { localStorage } from 'ts/local_storage/local_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; import { orderSchema } from 'ts/schemas/order_schema'; import { SchemaValidator } from 'ts/schemas/validator'; import { BlockchainErrs, HashData, Order, + ProviderType, ScreenWidths, Token, TokenByAddress, @@ -46,9 +49,11 @@ export interface PortalAllProps { blockchainIsLoaded: boolean; dispatcher: Dispatcher; hashData: HashData; + injectedProviderName: string; networkId: number; nodeVersion: string; orderFillAmount: BigNumber; + providerType: ProviderType; screenWidth: ScreenWidths; tokenByAddress: TokenByAddress; tokenStateByAddress: TokenStateByAddress; @@ -67,6 +72,7 @@ interface PortalAllState { prevPathname: string; isDisclaimerDialogOpen: boolean; isWethNoticeDialogOpen: boolean; + isLedgerDialogOpen: boolean; } export class Portal extends React.Component { @@ -96,6 +102,7 @@ export class Portal extends React.Component { prevPathname: this.props.location.pathname, isDisclaimerDialogOpen: !hasAcceptedDisclaimer, isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, + isLedgerDialogOpen: false, }; } public componentDidMount() { @@ -127,8 +134,9 @@ export class Portal extends React.Component { this._blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress); if (!_.isEmpty(nextProps.userAddress) && nextProps.blockchainIsLoaded) { const tokens = _.values(nextProps.tokenByAddress); + const trackedTokens = _.filter(tokens, t => t.isTracked); // tslint:disable-next-line:no-floating-promises - this._updateBalanceAndAllowanceWithLoadingScreenAsync(tokens); + this._updateBalanceAndAllowanceWithLoadingScreenAsync(trackedTokens); } this.setState({ prevUserAddress: nextProps.userAddress, @@ -167,8 +175,14 @@ export class Portal extends React.Component {
    @@ -239,11 +253,26 @@ export class Portal extends React.Component { onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)} /> + {this.props.blockchainIsLoaded && ( + + )}
    -
    +
    ;
); } + public onToggleLedgerDialog() { + this.setState({ + isLedgerDialogOpen: !this.state.isLedgerDialogOpen, + }); + } private _renderEthWrapper() { return ( { - public static defaultProps: Partial = { - shouldFullWidth: false, - style: {}, - isNightVersion: false, - }; - constructor(props: TopBarProps) { - super(props); - this.state = { - isDrawerOpen: false, - }; - } - public render() { - const isNightVersion = this.props.isNightVersion; - const isFullWidthPage = this.props.shouldFullWidth; - const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`; - const developerSectionMenuItems = [ - - - , - - - , - - - , - - - , - - - , - - - , - ]; - const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {}; - const fullWidthClasses = isFullWidthPage ? 'pr4' : ''; - const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png'; - const menuClasses = `col col-${isFullWidthPage ? '4' : '5'} ${fullWidthClasses} lg-pr0 md-pr2 sm-hide xs-hide`; - const menuIconStyle = { - fontSize: 25, - color: isNightVersion ? 'white' : 'black', - cursor: 'pointer', - paddingTop: 16, - }; - return ( -
-
-
- - - -
-
-
- {!this._isViewingPortal() && ( -
-
- - - - -
-
- )} - {this.props.blockchainIsLoaded && - !_.isEmpty(this.props.userAddress) && ( -
{this._renderUser()}
- )} -
-
- -
-
-
- {this._renderDrawer()} -
- ); - } - private _renderDrawer() { - return ( - - {this._renderPortalMenu()} - {this._renderDocsMenu()} - {this._renderWiki()} -
- Website -
- - Home - - - Wiki - - {!this._isViewing0xjsDocs() && ( - - 0x.js Docs - - )} - {!this._isViewingConnectDocs() && ( - - 0x Connect Docs - - )} - {!this._isViewingSmartContractsDocs() && ( - - Smart Contract Docs - - )} - {!this._isViewingPortal() && ( - - Portal DApp - - )} - - Whitepaper - - - About - - - Blog - - - - FAQ - - -
- ); - } - private _renderDocsMenu(): React.ReactNode { - if ( - (!this._isViewing0xjsDocs() && !this._isViewingSmartContractsDocs() && !this._isViewingConnectDocs()) || - _.isUndefined(this.props.menu) - ) { - return undefined; - } - - const sectionTitle = `${this.props.docsInfo.displayName} Docs`; - return ( -
-
- {sectionTitle} -
- -
- ); - } - private _renderWiki(): React.ReactNode { - if (!this._isViewingWiki()) { - return undefined; - } - - return ( -
-
- 0x Protocol Wiki -
- -
- ); - } - private _renderPortalMenu(): React.ReactNode { - if (!this._isViewingPortal()) { - return undefined; - } - - return ( -
-
- Portal DApp -
- -
- ); - } - private _renderUser() { - const userAddress = this.props.userAddress; - const identiconDiameter = 26; - return ( -
-
- {!_.isEmpty(userAddress) ? userAddress : ''} -
- {userAddress} -
- -
-
- ); - } - private _onMenuButtonClick() { - this.setState({ - isDrawerOpen: !this.state.isDrawerOpen, - }); - } - private _isViewingPortal() { - return _.includes(this.props.location.pathname, WebsitePaths.Portal); - } - private _isViewingFAQ() { - return _.includes(this.props.location.pathname, WebsitePaths.FAQ); - } - private _isViewing0xjsDocs() { - return _.includes(this.props.location.pathname, WebsitePaths.ZeroExJs); - } - private _isViewingConnectDocs() { - return _.includes(this.props.location.pathname, WebsitePaths.Connect); - } - private _isViewingSmartContractsDocs() { - return _.includes(this.props.location.pathname, WebsitePaths.SmartContracts); - } - private _isViewingWiki() { - return _.includes(this.props.location.pathname, WebsitePaths.Wiki); - } - private _shouldDisplayBottomBar() { - return ( - this._isViewingWiki() || - this._isViewing0xjsDocs() || - this._isViewingFAQ() || - this._isViewingSmartContractsDocs() || - this._isViewingConnectDocs() - ); - } -} diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx new file mode 100644 index 000000000..c7b6a4743 --- /dev/null +++ b/packages/website/ts/components/top_bar/provider_display.tsx @@ -0,0 +1,149 @@ +import * as _ from 'lodash'; +import Menu from 'material-ui/Menu'; +import MenuItem from 'material-ui/MenuItem'; +import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; +import RaisedButton from 'material-ui/RaisedButton'; +import * as React from 'react'; +import { Link } from 'react-router-dom'; +import { Blockchain } from 'ts/blockchain'; +import { ProviderPicker } from 'ts/components/top_bar/provider_picker'; +import { DropDown } from 'ts/components/ui/drop_down'; +import { Identicon } from 'ts/components/ui/identicon'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { ProviderType } from 'ts/types'; +import { colors } from 'ts/utils/colors'; +import { constants } from 'ts/utils/constants'; +import { utils } from 'ts/utils/utils'; + +const IDENTICON_DIAMETER = 32; + +interface ProviderDisplayProps { + dispatcher: Dispatcher; + userAddress: string; + networkId: number; + injectedProviderName: string; + providerType: ProviderType; + onToggleLedgerDialog: () => void; + blockchain: Blockchain; +} + +interface ProviderDisplayState {} + +export class ProviderDisplay extends React.Component { + public render() { + const isAddressAvailable = !_.isEmpty(this.props.userAddress); + const isExternallyInjectedProvider = ProviderType.Injected && this.props.injectedProviderName !== '0x Public'; + const displayAddress = isAddressAvailable + ? utils.getAddressBeginAndEnd(this.props.userAddress) + : isExternallyInjectedProvider ? 'Account locked' : '0x0000...0000'; + // If the "injected" provider is our fallback public node, then we want to + // show the "connect a wallet" message instead of the providerName + const injectedProviderName = isExternallyInjectedProvider + ? this.props.injectedProviderName + : 'Connect a wallet'; + const providerTitle = + this.props.providerType === ProviderType.Injected ? injectedProviderName : 'Ledger Nano S'; + const hoverActiveNode = ( +
+
+ +
+
+
{providerTitle}
+
{displayAddress}
+
+
+ +
+
+ ); + const hasInjectedProvider = + this.props.injectedProviderName !== '0x Public' && this.props.providerType === ProviderType.Injected; + const hasLedgerProvider = this.props.providerType === ProviderType.Ledger; + const horizontalPosition = hasInjectedProvider || hasLedgerProvider ? 'left' : 'middle'; + return ( +
+ +
+ ); + } + public renderPopoverContent(hasInjectedProvider: boolean, hasLedgerProvider: boolean) { + if (hasInjectedProvider || hasLedgerProvider) { + return ( + + ); + } else { + // Nothing to connect to, show install/info popover + return ( +
+
+ Choose a wallet: +
+
+
+
Install a browser wallet
+
+ +
+
+ Use{' '} + + Metamask + {' '} + or{' '} + + Parity Signer + +
+
+
+
+
or
+
+
+
+
Connect to a ledger hardware wallet
+
+ +
+
+ +
+
+
+
+ ); + } + } +} diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx new file mode 100644 index 000000000..ca98d8d05 --- /dev/null +++ b/packages/website/ts/components/top_bar/provider_picker.tsx @@ -0,0 +1,88 @@ +import * as _ from 'lodash'; +import Menu from 'material-ui/Menu'; +import MenuItem from 'material-ui/MenuItem'; +import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; +import * as React from 'react'; +import { Link } from 'react-router-dom'; +import { Blockchain } from 'ts/blockchain'; +import { DropDown } from 'ts/components/ui/drop_down'; +import { Identicon } from 'ts/components/ui/identicon'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { ProviderType } from 'ts/types'; +import { constants } from 'ts/utils/constants'; +import { utils } from 'ts/utils/utils'; + +const IDENTICON_DIAMETER = 32; +const SELECTED_BG_COLOR = '#F7F7F7'; + +interface ProviderPickerProps { + networkId: number; + injectedProviderName: string; + providerType: ProviderType; + onToggleLedgerDialog: () => void; + dispatcher: Dispatcher; + blockchain: Blockchain; +} + +interface ProviderPickerState {} + +export class ProviderPicker extends React.Component { + public render() { + const isLedgerSelected = this.props.providerType === ProviderType.Ledger; + const menuStyle = { + padding: 10, + paddingTop: 15, + paddingBottom: 15, + }; + const injectedLabel = ( +
+
{this.props.injectedProviderName}
+ {this._renderNetwork()} +
+ ); + // Show dropdown with two options + return ( +
+ + + + +
+ ); + } + private _renderNetwork() { + const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId]; + return ( +
+
+ +
+
{networkName}
+
+ ); + } + private _onProviderRadioChanged(e: any, value: string) { + if (value === ProviderType.Ledger) { + this.props.onToggleLedgerDialog(); + } else { + // Fire and forget + this.props.blockchain.updateProviderToInjectedAsync(); + } + } +} diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx new file mode 100644 index 000000000..652da5435 --- /dev/null +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -0,0 +1,377 @@ +import * as _ from 'lodash'; +import Drawer from 'material-ui/Drawer'; +import Menu from 'material-ui/Menu'; +import MenuItem from 'material-ui/MenuItem'; +import * as React from 'react'; +import { Link } from 'react-router-dom'; +import ReactTooltip = require('react-tooltip'); +import { Blockchain } from 'ts/blockchain'; +import { PortalMenu } from 'ts/components/portal_menu'; +import { ProviderDisplay } from 'ts/components/top_bar/provider_display'; +import { TopBarMenuItem } from 'ts/components/top_bar/top_bar_menu_item'; +import { DropDown } from 'ts/components/ui/drop_down'; +import { Identicon } from 'ts/components/ui/identicon'; +import { DocsInfo } from 'ts/pages/documentation/docs_info'; +import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { DocsMenu, MenuSubsectionsBySection, ProviderType, Styles, TypeDocNode, WebsitePaths } from 'ts/types'; +import { colors } from 'ts/utils/colors'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; + +interface TopBarProps { + userAddress?: string; + networkId?: number; + injectedProviderName?: string; + providerType?: ProviderType; + onToggleLedgerDialog?: () => void; + blockchain?: Blockchain; + dispatcher?: Dispatcher; + blockchainIsLoaded: boolean; + location: Location; + docsVersion?: string; + availableDocVersions?: string[]; + menu?: DocsMenu; + menuSubsectionsBySection?: MenuSubsectionsBySection; + shouldFullWidth?: boolean; + docsInfo?: DocsInfo; + style?: React.CSSProperties; + isNightVersion?: boolean; +} + +interface TopBarState { + isDrawerOpen: boolean; +} + +const styles: Styles = { + address: { + marginRight: 12, + overflow: 'hidden', + paddingTop: 4, + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + width: 70, + }, + topBar: { + backgroundcolor: colors.white, + height: 59, + width: '100%', + position: 'relative', + top: 0, + zIndex: 1100, + paddingBottom: 1, + }, + bottomBar: { + boxShadow: 'rgba(0, 0, 0, 0.187647) 0px 1px 3px', + }, + menuItem: { + fontSize: 14, + color: colors.darkestGrey, + paddingTop: 6, + paddingBottom: 6, + marginTop: 17, + cursor: 'pointer', + fontWeight: 400, + }, +}; + +export class TopBar extends React.Component { + public static defaultProps: Partial = { + shouldFullWidth: false, + style: {}, + isNightVersion: false, + }; + constructor(props: TopBarProps) { + super(props); + this.state = { + isDrawerOpen: false, + }; + } + public render() { + const isNightVersion = this.props.isNightVersion; + const isFullWidthPage = this.props.shouldFullWidth; + const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`; + const developerSectionMenuItems = [ + + + , + + + , + + + , + + + , + + + , + + + , + ]; + const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {}; + const fullWidthClasses = isFullWidthPage ? 'pr4' : ''; + const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png'; + const menuClasses = `col col-${isFullWidthPage ? '4' : '5'} ${fullWidthClasses} lg-pr0 md-pr2 sm-hide xs-hide`; + const menuIconStyle = { + fontSize: 25, + color: isNightVersion ? 'white' : 'black', + cursor: 'pointer', + paddingTop: 16, + }; + const hoverActiveNode = ( +
+
Developers
+
+ +
+
+ ); + const popoverContent = {developerSectionMenuItems}; + return ( +
+
+
+ + + +
+
+
+ {!this._isViewingPortal() && ( +
+
+ + + + +
+
+ )} + {this.props.blockchainIsLoaded && ( +
+ +
+ )} +
+
+ +
+
+
+ {this._renderDrawer()} +
+ ); + } + private _renderDrawer() { + return ( + + {this._renderPortalMenu()} + {this._renderDocsMenu()} + {this._renderWiki()} +
+ Website +
+ + Home + + + Wiki + + {!this._isViewing0xjsDocs() && ( + + 0x.js Docs + + )} + {!this._isViewingConnectDocs() && ( + + 0x Connect Docs + + )} + {!this._isViewingSmartContractsDocs() && ( + + Smart Contract Docs + + )} + {!this._isViewingPortal() && ( + + Portal DApp + + )} + + Whitepaper + + + About + + + Blog + + + + FAQ + + +
+ ); + } + private _renderDocsMenu(): React.ReactNode { + if ( + (!this._isViewing0xjsDocs() && !this._isViewingSmartContractsDocs() && !this._isViewingConnectDocs()) || + _.isUndefined(this.props.menu) + ) { + return undefined; + } + + const sectionTitle = `${this.props.docsInfo.displayName} Docs`; + return ( +
+
+ {sectionTitle} +
+ +
+ ); + } + private _renderWiki(): React.ReactNode { + if (!this._isViewingWiki()) { + return undefined; + } + + return ( +
+
+ 0x Protocol Wiki +
+ +
+ ); + } + private _renderPortalMenu(): React.ReactNode { + if (!this._isViewingPortal()) { + return undefined; + } + + return ( +
+
+ Portal DApp +
+ +
+ ); + } + private _renderUser() { + const userAddress = this.props.userAddress; + const identiconDiameter = 26; + return ( +
+
+ {!_.isEmpty(userAddress) ? userAddress : ''} +
+ {userAddress} +
+ +
+
+ ); + } + private _onMenuButtonClick() { + this.setState({ + isDrawerOpen: !this.state.isDrawerOpen, + }); + } + private _isViewingPortal() { + return _.includes(this.props.location.pathname, WebsitePaths.Portal); + } + private _isViewingFAQ() { + return _.includes(this.props.location.pathname, WebsitePaths.FAQ); + } + private _isViewing0xjsDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.ZeroExJs); + } + private _isViewingConnectDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.Connect); + } + private _isViewingSmartContractsDocs() { + return _.includes(this.props.location.pathname, WebsitePaths.SmartContracts); + } + private _isViewingWiki() { + return _.includes(this.props.location.pathname, WebsitePaths.Wiki); + } + private _shouldDisplayBottomBar() { + return ( + this._isViewingWiki() || + this._isViewing0xjsDocs() || + this._isViewingFAQ() || + this._isViewingSmartContractsDocs() || + this._isViewingConnectDocs() + ); + } +} diff --git a/packages/website/ts/components/top_bar/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx new file mode 100644 index 000000000..96ee86142 --- /dev/null +++ b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx @@ -0,0 +1,52 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { Link } from 'react-router-dom'; +import { colors } from 'ts/utils/colors'; + +const DEFAULT_STYLE = { + color: colors.darkestGrey, +}; + +interface TopBarMenuItemProps { + title: string; + path?: string; + isPrimary?: boolean; + style?: React.CSSProperties; + className?: string; + isNightVersion?: boolean; +} + +interface TopBarMenuItemState {} + +export class TopBarMenuItem extends React.Component { + public static defaultProps: Partial = { + isPrimary: false, + style: DEFAULT_STYLE, + className: '', + isNightVersion: false, + }; + public render() { + const primaryStyles = this.props.isPrimary + ? { + borderRadius: 4, + border: `1px solid ${this.props.isNightVersion ? colors.grey : colors.greyishPink}`, + marginTop: 15, + paddingLeft: 9, + paddingRight: 9, + width: 77, + } + : {}; + const menuItemColor = this.props.isNightVersion ? 'white' : this.props.style.color; + const linkColor = _.isUndefined(menuItemColor) ? colors.darkestGrey : menuItemColor; + return ( +
+ + {this.props.title} + +
+ ); + } +} diff --git a/packages/website/ts/components/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar_menu_item.tsx deleted file mode 100644 index 96ee86142..000000000 --- a/packages/website/ts/components/top_bar_menu_item.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import * as _ from 'lodash'; -import * as React from 'react'; -import { Link } from 'react-router-dom'; -import { colors } from 'ts/utils/colors'; - -const DEFAULT_STYLE = { - color: colors.darkestGrey, -}; - -interface TopBarMenuItemProps { - title: string; - path?: string; - isPrimary?: boolean; - style?: React.CSSProperties; - className?: string; - isNightVersion?: boolean; -} - -interface TopBarMenuItemState {} - -export class TopBarMenuItem extends React.Component { - public static defaultProps: Partial = { - isPrimary: false, - style: DEFAULT_STYLE, - className: '', - isNightVersion: false, - }; - public render() { - const primaryStyles = this.props.isPrimary - ? { - borderRadius: 4, - border: `1px solid ${this.props.isNightVersion ? colors.grey : colors.greyishPink}`, - marginTop: 15, - paddingLeft: 9, - paddingRight: 9, - width: 77, - } - : {}; - const menuItemColor = this.props.isNightVersion ? 'white' : this.props.style.color; - const linkColor = _.isUndefined(menuItemColor) ? colors.darkestGrey : menuItemColor; - return ( -
- - {this.props.title} - -
- ); - } -} diff --git a/packages/website/ts/components/ui/drop_down.tsx b/packages/website/ts/components/ui/drop_down.tsx new file mode 100644 index 000000000..31a67f0d7 --- /dev/null +++ b/packages/website/ts/components/ui/drop_down.tsx @@ -0,0 +1,110 @@ +import * as _ from 'lodash'; +import Menu from 'material-ui/Menu'; +import Popover, { PopoverAnimationVertical } from 'material-ui/Popover'; +import * as React from 'react'; +import { MaterialUIPosition, Styles, WebsitePaths } from 'ts/types'; +import { colors } from 'ts/utils/colors'; + +const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300; +const DEFAULT_STYLE = { + fontSize: 14, +}; + +interface DropDownProps { + hoverActiveNode: React.ReactNode; + popoverContent: React.ReactNode; + anchorOrigin: MaterialUIPosition; + targetOrigin: MaterialUIPosition; + style?: React.CSSProperties; + zDepth?: number; +} + +interface DropDownState { + isDropDownOpen: boolean; + anchorEl?: HTMLInputElement; +} + +export class DropDown extends React.Component { + public static defaultProps: Partial = { + style: DEFAULT_STYLE, + zDepth: 1, + }; + private _isHovering: boolean; + private _popoverCloseCheckIntervalId: number; + constructor(props: DropDownProps) { + super(props); + this.state = { + isDropDownOpen: false, + }; + } + public componentDidMount() { + this._popoverCloseCheckIntervalId = window.setInterval(() => { + this._checkIfShouldClosePopover(); + }, CHECK_CLOSE_POPOVER_INTERVAL_MS); + } + public componentWillUnmount() { + window.clearInterval(this._popoverCloseCheckIntervalId); + } + public componentWillReceiveProps(nextProps: DropDownProps) { + // HACK: If the popoverContent is updated to a different dimension and the users + // mouse is no longer above it, the dropdown can enter an inconsistent state where + // it believes the user is still hovering over it. In order to remedy this, we + // call hoverOff whenever the dropdown receives updated props. This is a hack + // because it will effectively close the dropdown on any prop update, barring + // dropdowns from having dynamic content. + this._onHoverOff(); + } + public render() { + return ( +
+ {this.props.hoverActiveNode} + +
+ {this.props.popoverContent} +
+
+
+ ); + } + private _onHover(event: React.FormEvent) { + this._isHovering = true; + this._checkIfShouldOpenPopover(event); + } + private _checkIfShouldOpenPopover(event: React.FormEvent) { + if (this.state.isDropDownOpen) { + return; // noop + } + + this.setState({ + isDropDownOpen: true, + anchorEl: event.currentTarget, + }); + } + private _onHoverOff() { + this._isHovering = false; + } + private _checkIfShouldClosePopover() { + if (!this.state.isDropDownOpen || this._isHovering) { + return; // noop + } + this._closePopover(); + } + private _closePopover() { + this.setState({ + isDropDownOpen: false, + }); + } +} diff --git a/packages/website/ts/components/ui/drop_down_menu_item.tsx b/packages/website/ts/components/ui/drop_down_menu_item.tsx deleted file mode 100644 index a578fb4f9..000000000 --- a/packages/website/ts/components/ui/drop_down_menu_item.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import * as _ from 'lodash'; -import Menu from 'material-ui/Menu'; -import Popover from 'material-ui/Popover'; -import * as React from 'react'; -import { colors } from 'ts/utils/colors'; - -const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300; -const DEFAULT_STYLE = { - fontSize: 14, -}; - -interface DropDownMenuItemProps { - title: string; - subMenuItems: React.ReactNode[]; - style?: React.CSSProperties; - menuItemStyle?: React.CSSProperties; - isNightVersion?: boolean; -} - -interface DropDownMenuItemState { - isDropDownOpen: boolean; - anchorEl?: HTMLInputElement; -} - -export class DropDownMenuItem extends React.Component { - public static defaultProps: Partial = { - style: DEFAULT_STYLE, - menuItemStyle: DEFAULT_STYLE, - isNightVersion: false, - }; - private _isHovering: boolean; - private _popoverCloseCheckIntervalId: number; - constructor(props: DropDownMenuItemProps) { - super(props); - this.state = { - isDropDownOpen: false, - }; - } - public componentDidMount() { - this._popoverCloseCheckIntervalId = window.setInterval(() => { - this._checkIfShouldClosePopover(); - }, CHECK_CLOSE_POPOVER_INTERVAL_MS); - } - public componentWillUnmount() { - window.clearInterval(this._popoverCloseCheckIntervalId); - } - public render() { - const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color; - return ( -
-
-
{this.props.title}
-
- -
-
- -
- {this.props.subMenuItems} -
-
-
- ); - } - private _onHover(event: React.FormEvent) { - this._isHovering = true; - this._checkIfShouldOpenPopover(event); - } - private _checkIfShouldOpenPopover(event: React.FormEvent) { - if (this.state.isDropDownOpen) { - return; // noop - } - - this.setState({ - isDropDownOpen: true, - anchorEl: event.currentTarget, - }); - } - private _onHoverOff(event: React.FormEvent) { - this._isHovering = false; - } - private _checkIfShouldClosePopover() { - if (!this.state.isDropDownOpen || this._isHovering) { - return; // noop - } - this._closePopover(); - } - private _closePopover() { - this.setState({ - isDropDownOpen: false, - }); - } -} -- cgit v1.2.3 From 6206ebc994a2cf76b90ac426218d6ed18b74a072 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 28 Jan 2018 16:19:55 +0100 Subject: Implement just-in-time loading of token balances & allowances --- .../dialogs/eth_weth_conversion_dialog.tsx | 60 ++++++--- .../ts/components/dialogs/ledger_config_dialog.tsx | 3 +- .../website/ts/components/dialogs/send_dialog.tsx | 13 +- .../dialogs/track_token_confirmation_dialog.tsx | 10 -- .../ts/components/eth_weth_conversion_button.tsx | 14 +- packages/website/ts/components/eth_wrappers.tsx | 86 ++++++++++-- packages/website/ts/components/fill_order.tsx | 17 ++- .../ts/components/generate_order/asset_picker.tsx | 14 +- .../generate_order/generate_order_form.tsx | 20 ++- .../components/generate_order/new_token_form.tsx | 14 +- .../ts/components/inputs/allowance_toggle.tsx | 5 +- .../ts/components/inputs/balance_bounded_input.tsx | 3 + .../ts/components/inputs/token_amount_input.tsx | 54 +++++++- packages/website/ts/components/portal.tsx | 22 +--- packages/website/ts/components/send_button.tsx | 13 +- packages/website/ts/components/token_balances.tsx | 146 ++++++++++++++++----- .../ts/components/top_bar/provider_picker.tsx | 2 +- 17 files changed, 353 insertions(+), 143 deletions(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx index 661cc1d8c..a3a39a1b9 100644 --- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -2,25 +2,31 @@ import { BigNumber } from '@0xproject/utils'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import * as React from 'react'; +import { Blockchain } from 'ts/blockchain'; import { EthAmountInput } from 'ts/components/inputs/eth_amount_input'; import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; -import { Side, Token, TokenState } from 'ts/types'; +import { Side, Token } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface EthWethConversionDialogProps { + blockchain: Blockchain; + userAddress: string; + networkId: number; direction: Side; onComplete: (direction: Side, value: BigNumber) => void; onCancelled: () => void; isOpen: boolean; token: Token; - tokenState: TokenState; etherBalance: BigNumber; + lastForceTokenStateRefetch: number; } interface EthWethConversionDialogState { value?: BigNumber; shouldShowIncompleteErrs: boolean; hasErrors: boolean; + isEthTokenBalanceLoaded: boolean; + ethTokenBalance: BigNumber; } export class EthWethConversionDialog extends React.Component< @@ -32,8 +38,14 @@ export class EthWethConversionDialog extends React.Component< this.state = { shouldShowIncompleteErrs: false, hasErrors: false, + isEthTokenBalanceLoaded: false, + ethTokenBalance: new BigNumber(0), }; } + public componentWillMount() { + // tslint:disable-next-line:no-floating-promises + this._fetchEthTokenBalanceAsync(); + } public render() { const convertDialogActions = [ , @@ -72,8 +84,11 @@ export class EthWethConversionDialog extends React.Component<
{this.props.direction === Side.Receive ? (
1 ETH = 1 WETH
- {this.props.direction === Side.Receive && ( -
- Max -
- )} + {this.props.direction === Side.Receive && + this.state.isEthTokenBalanceLoaded && ( +
+ Max +
+ )}
@@ -132,7 +148,7 @@ export class EthWethConversionDialog extends React.Component< } private _onMaxClick() { this.setState({ - value: this.props.tokenState.balance, + value: this.state.ethTokenBalance, }); } private _onValueChange(isValid: boolean, amount?: BigNumber) { @@ -160,4 +176,14 @@ export class EthWethConversionDialog extends React.Component< }); this.props.onCancelled(); } + private async _fetchEthTokenBalanceAsync() { + const [balance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + this.props.token.address, + ); + this.setState({ + isEthTokenBalanceLoaded: true, + ethTokenBalance: balance, + }); + } } diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index aff3f67b1..a17a51622 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -197,7 +197,8 @@ export class LedgerConfigDialog extends React.Component void; onCancelled: () => void; isOpen: boolean; token: Token; - tokenState: TokenState; + lastForceTokenStateRefetch: number; } interface SendDialogState { @@ -66,15 +70,18 @@ export class SendDialog extends React.Component
); diff --git a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx index 3f29d46f8..bb7e3ed1a 100644 --- a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx +++ b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx @@ -82,16 +82,6 @@ export class TrackTokenConfirmationDialog extends React.Component< newTokenEntry.isTracked = true; trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); this.props.dispatcher.updateTokenByAddress([newTokenEntry]); - - const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( - token.address, - ); - this.props.dispatcher.updateTokenStateByAddress({ - [token.address]: { - balance, - allowance, - }, - }); } this.setState({ diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx index cc5e623ea..52240fd0f 100644 --- a/packages/website/ts/components/eth_weth_conversion_button.tsx +++ b/packages/website/ts/components/eth_weth_conversion_button.tsx @@ -12,6 +12,8 @@ import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; interface EthWethConversionButtonProps { + userAddress: string; + networkId: number; direction: Side; ethToken: Token; ethTokenState: TokenState; @@ -21,6 +23,8 @@ interface EthWethConversionButtonProps { isOutdatedWrappedEther: boolean; onConversionSuccessful?: () => void; isDisabled?: boolean; + lastForceTokenStateRefetch: number; + refetchEthTokenStateAsync: () => Promise; } interface EthWethConversionButtonState { @@ -64,13 +68,16 @@ export class EthWethConversionButton extends React.Component< onClick={this._toggleConversionDialog.bind(this)} />
); @@ -87,21 +94,18 @@ export class EthWethConversionButton extends React.Component< this._toggleConversionDialog(); const token = this.props.ethToken; const tokenState = this.props.ethTokenState; - let balance = tokenState.balance; try { if (direction === Side.Deposit) { await this.props.blockchain.convertEthToWrappedEthTokensAsync(token.address, value); const ethAmount = ZeroEx.toUnitAmount(value, constants.DECIMAL_PLACES_ETH); this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`); - balance = balance.plus(value); } else { await this.props.blockchain.convertWrappedEthTokensToEthAsync(token.address, value); const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals); this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`); - balance = balance.minus(value); } if (!this.props.isOutdatedWrappedEther) { - this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + this.props.refetchEthTokenStateAsync(); } this.props.onConversionSuccessful(); } catch (err) { diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index d074ec787..460a6cae3 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -41,12 +41,14 @@ interface EthWrappersProps { blockchain: Blockchain; dispatcher: Dispatcher; tokenByAddress: TokenByAddress; - tokenStateByAddress: TokenStateByAddress; userAddress: string; userEtherBalance: BigNumber; + lastForceTokenStateRefetch: number; } interface EthWrappersState { + ethTokenState: TokenState; + isWethStateLoaded: boolean; outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded; outdatedWETHStateByAddress: OutdatedWETHStateByAddress; } @@ -67,18 +69,31 @@ export class EthWrappers extends React.Component {this._renderTokenLink(tokenLabel, etherscanUrl)} - {wethBalance.toFixed(PRECISION)} WETH + + {this.state.isWethStateLoaded ? ( + `${wethBalance.toFixed(PRECISION)} WETH` + ) : ( + + )} + - {this._renderOutdatedWeths(etherToken, etherTokenState)} + {this._renderOutdatedWeths(etherToken, this.state.ethTokenState)}
@@ -269,6 +299,10 @@ export class EthWrappers extends React.Component token.symbol === 'WETH'); + const [wethBalance, wethAllowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + wethToken.address, + ); + const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; @@ -356,6 +397,11 @@ export class EthWrappers extends React.Component { symbol: takerToken.symbol, }; const fillToken = this.props.tokenByAddress[takerToken.address]; - const fillTokenState = this.props.tokenStateByAddress[takerToken.address]; const makerTokenAddress = this.state.parsedOrder.maker.token.address; const makerToken = this.props.tokenByAddress[makerTokenAddress]; const makerAssetToken = { @@ -249,14 +248,17 @@ export class FillOrder extends React.Component { {!isUserMaker && (
{ signedOrder, this.props.orderFillAmount, ); - // After fill completes, let's update the token balances - const makerToken = this.props.tokenByAddress[parsedOrder.maker.token.address]; - const takerToken = this.props.tokenByAddress[parsedOrder.taker.token.address]; - const tokens = [makerToken, takerToken]; - await this.props.blockchain.updateTokenBalancesAndAllowancesAsync(tokens); + // After fill completes, let's force fetch the token balances + this.props.dispatcher.forceTokenStateRefetch(); this.setState({ isFilling: false, didFillOrderSucceed: true, diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx index df7d87cfd..8d4eaab40 100644 --- a/packages/website/ts/components/generate_order/asset_picker.tsx +++ b/packages/website/ts/components/generate_order/asset_picker.tsx @@ -223,10 +223,7 @@ export class AssetPicker extends React.Component void; + onNewTokenSubmitted: (token: Token) => void; } interface NewTokenFormState { @@ -110,13 +110,9 @@ export class NewTokenForm extends React.Component Promise; } interface AllowanceToggleState { @@ -45,7 +47,7 @@ export class AllowanceToggle extends React.Component
@@ -73,6 +75,7 @@ export class AllowanceToggle extends React.Component InputErrMsg; onVisitBalancesPageClick?: () => void; shouldHideVisitBalancesLink?: boolean; + isDisabled?: boolean; } interface BalanceBoundedInputState { @@ -29,6 +30,7 @@ export class BalanceBoundedInput extends React.Component = { shouldShowIncompleteErrs: false, shouldHideVisitBalancesLink: false, + isDisabled: false, }; constructor(props: BalanceBoundedInputProps) { super(props); @@ -88,6 +90,7 @@ export class BalanceBoundedInput extends React.Componentamount} onChange={this._onValueChange.bind(this)} underlineStyle={{ width: 'calc(100% + 50px)' }} + disabled={this.props.isDisabled} /> ); } diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx index 63966d759..f41d42d02 100644 --- a/packages/website/ts/components/inputs/token_amount_input.tsx +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -3,13 +3,16 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import * as React from 'react'; import { Link } from 'react-router-dom'; +import { Blockchain } from 'ts/blockchain'; import { BalanceBoundedInput } from 'ts/components/inputs/balance_bounded_input'; import { InputErrMsg, Token, TokenState, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface TokenAmountInputProps { + userAddress: string; + networkId: number; + blockchain: Blockchain; token: Token; - tokenState: TokenState; label?: string; amount?: BigNumber; shouldShowIncompleteErrs: boolean; @@ -17,11 +20,39 @@ interface TokenAmountInputProps { shouldCheckAllowance: boolean; onChange: ValidatedBigNumberCallback; onVisitBalancesPageClick?: () => void; + lastForceTokenStateRefetch: number; } -interface TokenAmountInputState {} +interface TokenAmountInputState { + balance: BigNumber; + allowance: BigNumber; + isBalanceAndAllowanceLoaded: boolean; +} export class TokenAmountInput extends React.Component { + constructor(props: TokenAmountInputProps) { + super(props); + const defaultAmount = new BigNumber(0); + this.state = { + balance: defaultAmount, + allowance: defaultAmount, + isBalanceAndAllowanceLoaded: false, + }; + } + public componentWillMount() { + // tslint:disable-next-line:no-floating-promises + this._fetchBalanceAndAllowanceAsync(this.props.token.address); + } + public componentWillReceiveProps(nextProps: TokenAmountInputProps) { + if ( + nextProps.userAddress !== this.props.userAddress || + nextProps.networkId !== this.props.networkId || + nextProps.token.address !== this.props.token.address || + nextProps.lastForceTokenStateRefetch !== this.props.lastForceTokenStateRefetch + ) { + this._fetchBalanceAndAllowanceAsync(nextProps.token.address); + } + } public render() { const amount = this.props.amount ? ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals) @@ -32,12 +63,13 @@ export class TokenAmountInput extends React.Component
{this.props.token.symbol}
@@ -51,7 +83,7 @@ export class TokenAmountInput extends React.Component Insufficient allowance.{' '} @@ -67,4 +99,18 @@ export class TokenAmountInput extends React.Component { if (nextProps.userAddress !== this.state.prevUserAddress) { // tslint:disable-next-line:no-floating-promises this._blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress); - if (!_.isEmpty(nextProps.userAddress) && nextProps.blockchainIsLoaded) { - const tokens = _.values(nextProps.tokenByAddress); - const trackedTokens = _.filter(tokens, t => t.isTracked); - // tslint:disable-next-line:no-floating-promises - this._updateBalanceAndAllowanceWithLoadingScreenAsync(trackedTokens); - } this.setState({ prevUserAddress: nextProps.userAddress, }); @@ -280,9 +274,9 @@ export class Portal extends React.Component { blockchain={this._blockchain} dispatcher={this.props.dispatcher} tokenByAddress={this.props.tokenByAddress} - tokenStateByAddress={this.props.tokenStateByAddress} userAddress={this.props.userAddress} userEtherBalance={this.props.userEtherBalance} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> ); } @@ -296,6 +290,8 @@ export class Portal extends React.Component { ); } private _renderTokenBalances() { + 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} - tokenStateByAddress={this.props.tokenStateByAddress} + trackedTokens={trackedTokens} userAddress={this.props.userAddress} userEtherBalance={this.props.userEtherBalance} networkId={this.props.networkId} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> ); } @@ -325,8 +322,8 @@ export class Portal extends React.Component { networkId={this.props.networkId} userAddress={this.props.userAddress} tokenByAddress={this.props.tokenByAddress} - tokenStateByAddress={this.props.tokenStateByAddress} dispatcher={this.props.dispatcher} + lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch} /> ); } @@ -382,9 +379,4 @@ export class Portal extends React.Component { const newScreenWidth = utils.getScreenWidth(); this.props.dispatcher.updateScreenWidth(newScreenWidth); } - private async _updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) { - this.props.dispatcher.updateBlockchainIsLoaded(false); - await this._blockchain.updateTokenBalancesAndAllowancesAsync(tokens); - this.props.dispatcher.updateBlockchainIsLoaded(true); - } } diff --git a/packages/website/ts/components/send_button.tsx b/packages/website/ts/components/send_button.tsx index 7c0c7c083..f13e8ecce 100644 --- a/packages/website/ts/components/send_button.tsx +++ b/packages/website/ts/components/send_button.tsx @@ -10,11 +10,15 @@ import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; interface SendButtonProps { + userAddress: string; + networkId: number; token: Token; tokenState: TokenState; dispatcher: Dispatcher; blockchain: Blockchain; onError: () => void; + lastForceTokenStateRefetch: number; + refetchTokenStateAsync: (tokenAddress: string) => Promise; } interface SendButtonState { @@ -42,11 +46,14 @@ export class SendButton extends React.Component
); @@ -63,11 +70,9 @@ export class SendButton extends React.Component { public constructor(props: TokenBalancesProps) { super(props); + const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(props.trackedTokens); this.state = { errorType: undefined, isBalanceSpinnerVisible: false, @@ -91,8 +100,14 @@ export class TokenBalances extends React.Component t.symbol === ZRX_TOKEN_SYMBOL); - const nextZrxTokenBalance = nextProps.tokenStateByAddress[nextZrxToken.address].balance; - if (!_.isUndefined(this.state.currentZrxBalance) && !nextZrxTokenBalance.eq(this.state.currentZrxBalance)) { - if (this.state.isZRXSpinnerVisible) { - const receivedAmount = nextZrxTokenBalance.minus(this.state.currentZrxBalance); - const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, constants.DECIMAL_PLACES_ZRX); - this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`); - } - this.setState({ - isZRXSpinnerVisible: false, - currentZrxBalance: undefined, - }); + + 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); + this._fetchBalancesAndAllowancesAsync(newTokenAddresses); } } public componentDidMount() { @@ -303,8 +321,7 @@ export class TokenBalances extends React.Component t.isTracked); + 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) @@ -317,7 +334,7 @@ export class TokenBalances extends React.Component - {this._renderAmount(tokenState.balance, token.decimals)} {token.symbol} - {this.state.isZRXSpinnerVisible && - token.symbol === ZRX_TOKEN_SYMBOL && ( - - - - )} + {tokenState.isLoaded ? ( + + {this._renderAmount(tokenState.balance, token.decimals)} {token.symbol} + {this.state.isZRXSpinnerVisible && + token.symbol === ZRX_TOKEN_SYMBOL && ( + + + + )} + + ) : ( + + )} @@ -383,11 +408,15 @@ export class TokenBalances extends React.Component )} @@ -414,7 +443,6 @@ export class TokenBalances extends React.Component { try { await this.props.blockchain.mintTestTokensAsync(token); + await this._refetchTokenStateAsync(token.address); const amount = ZeroEx.toUnitAmount(constants.MINT_AMOUNT, token.decimals); this.props.dispatcher.showFlashMessage(`Successfully minted ${amount.toString(10)} ${token.symbol}`); return true; @@ -569,15 +598,11 @@ export class TokenBalances extends React.Component t.symbol === ZRX_TOKEN_SYMBOL); - const zrxTokenState = this.props.tokenStateByAddress[zrxToken.address]; this.setState({ isZRXSpinnerVisible: true, - currentZrxBalance: zrxTokenState.balance, }); // tslint:disable-next-line:no-floating-promises - this.props.blockchain.pollTokenBalanceAsync(zrxToken); + this._startPollingZrxBalanceAsync(); } return true; } @@ -603,4 +628,63 @@ export class TokenBalances extends React.Component t.symbol === ZRX_TOKEN_SYMBOL); + + // tslint:disable-next-line:no-floating-promises + const balance = await this.props.blockchain.pollTokenBalanceAsync(zrxToken); + const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress; + trackedTokenStateByAddress[zrxToken.address] = { + ...trackedTokenStateByAddress[zrxToken.address], + balance, + }; + this.setState({ + isZRXSpinnerVisible: false, + }); + } + private async _fetchBalancesAndAllowancesAsync(tokenAddresses: string[]) { + const trackedTokenStateByAddress = this.state.trackedTokenStateByAddress; + for (const tokenAddress of tokenAddresses) { + const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + tokenAddress, + ); + trackedTokenStateByAddress[tokenAddress] = { + balance, + allowance, + isLoaded: true, + }; + } + this.setState({ + trackedTokenStateByAddress, + }); + } + 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 _refetchTokenStateAsync(tokenAddress: string) { + const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + tokenAddress, + ); + this.setState({ + trackedTokenStateByAddress: { + ...this.state.trackedTokenStateByAddress, + [tokenAddress]: { + balance, + allowance, + isLoaded: true, + }, + }, + }); + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx index ca98d8d05..418f8696b 100644 --- a/packages/website/ts/components/top_bar/provider_picker.tsx +++ b/packages/website/ts/components/top_bar/provider_picker.tsx @@ -81,7 +81,7 @@ export class ProviderPicker extends React.Component Date: Sun, 28 Jan 2018 17:45:20 +0100 Subject: Fix bug where could not switch to Ledger and back --- packages/website/ts/components/inputs/token_amount_input.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx index f41d42d02..44f3fc4a8 100644 --- a/packages/website/ts/components/inputs/token_amount_input.tsx +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -41,7 +41,7 @@ export class TokenAmountInput extends React.Component Date: Mon, 29 Jan 2018 10:44:30 +0100 Subject: Add network name to the select provider --- .../ts/components/top_bar/provider_picker.tsx | 27 ++++++++++++---------- 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx index 418f8696b..4dc8ff0bd 100644 --- a/packages/website/ts/components/top_bar/provider_picker.tsx +++ b/packages/website/ts/components/top_bar/provider_picker.tsx @@ -34,12 +34,6 @@ export class ProviderPicker extends React.Component -
{this.props.injectedProviderName}
- {this._renderNetwork()} -
- ); // Show dropdown with two options return (
@@ -51,29 +45,38 @@ export class ProviderPicker extends React.Component
); } + private _renderLabel(title: string, shouldShowNetwork: boolean) { + const label = ( +
+
{title}
+ {shouldShowNetwork && this._renderNetwork()} +
+ ); + return label; + } private _renderNetwork() { const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId]; return ( -
-
+
+
-
{networkName}
+
{networkName}
); } -- cgit v1.2.3 From d18554e0e88de06fd2196d00cf1469eca1358eaf Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 29 Jan 2018 10:46:15 +0100 Subject: Replace heavy loading animation with simple circular loader --- packages/website/ts/components/portal.tsx | 16 +++++++++-- packages/website/ts/components/ui/loading.tsx | 39 --------------------------- 2 files changed, 14 insertions(+), 41 deletions(-) delete mode 100644 packages/website/ts/components/ui/loading.tsx (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx index e8190e2f5..0182a91f7 100644 --- a/packages/website/ts/components/portal.tsx +++ b/packages/website/ts/components/portal.tsx @@ -1,5 +1,6 @@ import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; +import CircularProgress from 'material-ui/CircularProgress'; import Paper from 'material-ui/Paper'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; @@ -17,7 +18,6 @@ import { TokenBalances } from 'ts/components/token_balances'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { TradeHistory } from 'ts/components/trade_history/trade_history'; import { FlashMessage } from 'ts/components/ui/flash_message'; -import { Loading } from 'ts/components/ui/loading'; import { GenerateOrderForm } from 'ts/containers/generate_order_form'; import { localStorage } from 'ts/local_storage/local_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; @@ -223,7 +223,19 @@ export class Portal extends React.Component { /> ) : ( - +
+
+
+ +
+
+ Loading Portal... +
+
+
)}
diff --git a/packages/website/ts/components/ui/loading.tsx b/packages/website/ts/components/ui/loading.tsx deleted file mode 100644 index aa319e9e9..000000000 --- a/packages/website/ts/components/ui/loading.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as _ from 'lodash'; -import Paper from 'material-ui/Paper'; -import * as React from 'react'; -import { DefaultPlayer as Video } from 'react-html5video'; -import 'react-html5video/dist/styles.css'; -import { utils } from 'ts/utils/utils'; - -interface LoadingProps {} - -interface LoadingState {} - -export class Loading extends React.Component { - public render() { - return ( -
- - {utils.isUserOnMobile() ? ( - - ) : ( -
- -
- )} -
- Connecting to the blockchain... -
-
-
- ); - } -} -- cgit v1.2.3 From 45fdfc2d3d2bcfcb37d16f29df2e30524e6d7717 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 29 Jan 2018 10:55:53 +0100 Subject: Use colors module and remove in-lined colors --- packages/website/ts/components/top_bar/provider_display.tsx | 9 ++++++--- packages/website/ts/components/top_bar/provider_picker.tsx | 8 ++++---- 2 files changed, 10 insertions(+), 7 deletions(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx index c7b6a4743..e132f9cee 100644 --- a/packages/website/ts/components/top_bar/provider_display.tsx +++ b/packages/website/ts/components/top_bar/provider_display.tsx @@ -49,11 +49,14 @@ export class ProviderDisplay extends React.Component
-
{providerTitle}
+
{providerTitle}
{displayAddress}
-
- +
+
); diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx index 4dc8ff0bd..75261ab94 100644 --- a/packages/website/ts/components/top_bar/provider_picker.tsx +++ b/packages/website/ts/components/top_bar/provider_picker.tsx @@ -9,11 +9,11 @@ import { DropDown } from 'ts/components/ui/drop_down'; import { Identicon } from 'ts/components/ui/identicon'; import { Dispatcher } from 'ts/redux/dispatcher'; import { ProviderType } from 'ts/types'; +import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; import { utils } from 'ts/utils/utils'; const IDENTICON_DIAMETER = 32; -const SELECTED_BG_COLOR = '#F7F7F7'; interface ProviderPickerProps { networkId: number; @@ -43,12 +43,12 @@ export class ProviderPicker extends React.Component @@ -76,7 +76,7 @@ export class ProviderPicker extends React.Component -
{networkName}
+
{networkName}
); } -- cgit v1.2.3 From af08177f79decd8dd3194d300a1fa43d43872229 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 29 Jan 2018 12:10:49 +0100 Subject: Make it such that users can switch between Ledger accounts without first switching back to an injected provider --- .../website/ts/components/dialogs/ledger_config_dialog.tsx | 5 ++++- packages/website/ts/components/top_bar/provider_picker.tsx | 10 ++++------ 2 files changed, 8 insertions(+), 7 deletions(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index a17a51622..66b04f198 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -265,7 +265,10 @@ export class LedgerConfigDialog extends React.Component - + ); } - private _onProviderRadioChanged(e: any, value: string) { + private _onProviderRadioChanged(value: string) { if (value === ProviderType.Ledger) { this.props.onToggleLedgerDialog(); } else { -- cgit v1.2.3 From 63ffa80c5cde2b289043ad58aec6bc2fe4d72993 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 29 Jan 2018 12:46:07 +0100 Subject: Re-set Ledger config dialog to connect step if dialog is closed --- packages/website/ts/components/dialogs/ledger_config_dialog.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index 66b04f198..7dc1e2620 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -186,6 +186,7 @@ export class LedgerConfigDialog extends React.Component Date: Mon, 29 Jan 2018 13:16:40 +0100 Subject: Add missing entries for Ropsten and Rinkeby testnets, added Ropsten to Ledger network dropdown --- .../components/dialogs/blockchain_err_dialog.tsx | 4 +-- .../ts/components/dialogs/ledger_config_dialog.tsx | 8 ++++-- packages/website/ts/components/token_balances.tsx | 33 +++++++++++----------- 3 files changed, 25 insertions(+), 20 deletions(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx index f555ca6b1..3908d987e 100644 --- a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx +++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx @@ -3,7 +3,7 @@ import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; -import { BlockchainErrs } from 'ts/types'; +import { BlockchainErrs, Networks } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; @@ -129,7 +129,7 @@ export class BlockchainErrDialog extends React.Component The 0x smart contracts are not deployed on the Ethereum network you are currently connected to (network Id: {this.props.networkId}). In order to use the 0x portal dApp, please connect to the{' '} - {constants.TESTNET_NAME} testnet (network Id: {constants.NETWORK_ID_TESTNET}) + {Networks.kovan} testnet (network Id: {constants.NETWORK_ID_KOVAN}) {configs.IS_MAINNET_ENABLED ? ` or ${constants.MAINNET_NAME} (network Id: ${constants.NETWORK_ID_MAINNET}).` : `.`} diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index 7dc1e2620..510025774 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -10,7 +10,7 @@ import { Blockchain } from 'ts/blockchain'; import { NetworkDropDown } from 'ts/components/dropdowns/network_drop_down'; import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { ProviderType } from 'ts/types'; +import { ProviderType, Networks } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; @@ -97,7 +97,11 @@ export class LedgerConfigDialog extends React.Component
diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index 5b450c772..ecafe5432 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -27,6 +27,7 @@ import { BlockchainCallErrs, BlockchainErrs, EtherscanLinkSuffixes, + Networks, ScreenWidths, Styles, Token, @@ -155,13 +156,13 @@ export class TokenBalances extends React.Component, ]; - const isTestNetwork = this.props.networkId === constants.NETWORK_ID_TESTNET; + const isKovanTestNetwork = this.props.networkId === constants.NETWORK_ID_KOVAN; const dharmaButtonColumnStyle = { paddingLeft: 3, - display: isTestNetwork ? 'table-cell' : 'none', + display: isKovanTestNetwork ? 'table-cell' : 'none', }; const stubColumnStyle = { - display: isTestNetwork ? 'none' : 'table-cell', + display: isKovanTestNetwork ? 'none' : 'table-cell', }; const allTokenRowHeight = _.size(this.props.tokenByAddress) * TOKEN_TABLE_ROW_HEIGHT; const tokenTableHeight = @@ -180,10 +181,10 @@ export class TokenBalances extends React.Component -

{isTestNetwork ? 'Test ether' : 'Ether'}

+

{isKovanTestNetwork ? 'Test ether' : 'Ether'}

- {isTestNetwork + {isKovanTestNetwork ? 'In order to try out the 0x Portal Dapp, request some test ether to pay for \ gas costs. It might take a bit of time for the test ether to show up.' : 'Ether must be converted to Ether Tokens in order to be tradable via 0x. \ @@ -195,12 +196,12 @@ export class TokenBalances extends React.ComponentCurrency Balance - {isTestNetwork && ( + {isKovanTestNetwork && ( {isSmallScreen ? 'Faucet' : 'Request from faucet'} )} - {isTestNetwork && ( + {isKovanTestNetwork && ( {isSmallScreen ? 'Loan' : 'Request Dharma loan'} @@ -222,7 +223,7 @@ export class TokenBalances extends React.Component - {isTestNetwork && ( + {isKovanTestNetwork && ( )} - {isTestNetwork && ( + {isKovanTestNetwork && (
-

{isTestNetwork ? 'Test tokens' : 'Tokens'}

+

{isKovanTestNetwork ? 'Test tokens' : 'Tokens'}

@@ -261,7 +262,7 @@ export class TokenBalances extends React.Component
- {isTestNetwork + {isKovanTestNetwork ? "Mint some test tokens you'd like to use to generate or fill an order using 0x." : "Set trading permissions for a token you'd like to start trading."}
@@ -391,7 +392,7 @@ export class TokenBalances extends React.Component )} {token.symbol === ZRX_TOKEN_SYMBOL && - this.props.networkId === constants.NETWORK_ID_TESTNET && ( + this.props.networkId === constants.NETWORK_ID_KOVAN && ( - Our faucet can only send test Ether to addresses on the {constants.TESTNET_NAME} testnet - (networkId {constants.NETWORK_ID_TESTNET}). Please make sure you are connected to the{' '} - {constants.TESTNET_NAME} testnet and try requesting ether again. + Our faucet can only send test Ether to addresses on the {Networks.kovan} testnet (networkId{' '} + {constants.NETWORK_ID_KOVAN}). Please make sure you are connected to the {Networks.kovan}{' '} + testnet and try requesting ether again.
); @@ -568,7 +569,7 @@ export class TokenBalances extends React.Component Date: Mon, 29 Jan 2018 13:25:51 +0100 Subject: Fix bug related to balance/allowance fetching being async --- packages/website/ts/components/token_balances.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index ecafe5432..776ccb16e 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -133,6 +133,20 @@ export class TokenBalances extends React.Component 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. this._fetchBalancesAndAllowancesAsync(newTokenAddresses); } } -- cgit v1.2.3 From bb0cedd2de44a0b6533df37133ae6f576ee84b2a Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Mon, 29 Jan 2018 18:35:06 +0100 Subject: Disallow negative amounts --- packages/website/ts/components/inputs/balance_bounded_input.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx index 67d5b781e..3bbc7a5f6 100644 --- a/packages/website/ts/components/inputs/balance_bounded_input.tsx +++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx @@ -103,7 +103,7 @@ export class BalanceBoundedInput extends React.Component { const isValid = _.isUndefined(errMsg); - if (utils.isNumeric(amountString)) { + if (utils.isNumeric(amountString) && !_.includes(amountString, '-')) { this.props.onChange(isValid, new BigNumber(amountString)); } else { this.props.onChange(isValid); -- cgit v1.2.3 From 89e98240b485bfdfc62d27dc2982dcfab9169ea9 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 30 Jan 2018 15:05:55 +0100 Subject: Add Rinkeby support --- packages/website/ts/components/dialogs/ledger_config_dialog.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index 510025774..d5510606b 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -10,7 +10,7 @@ import { Blockchain } from 'ts/blockchain'; import { NetworkDropDown } from 'ts/components/dropdowns/network_drop_down'; import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { ProviderType, Networks } from 'ts/types'; +import { Networks, ProviderType } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; @@ -79,6 +79,7 @@ export class LedgerConfigDialog extends React.Component
Follow these instructions before proceeding:
@@ -97,11 +98,7 @@ export class LedgerConfigDialog extends React.Component
-- cgit v1.2.3 From da1071526f1faae9e80b992bcbd3dfd3c9a98e1a Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 30 Jan 2018 19:03:10 +0100 Subject: Add browser data to dialog info --- packages/website/ts/components/dialogs/ledger_config_dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index d5510606b..d9b4d1504 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -85,7 +85,7 @@ export class LedgerConfigDialog extends React.ComponentFollow these instructions before proceeding:
  1. Connect your Ledger Nano S & Open the Ethereum application
  2. -
  3. Verify that Browser Support is enabled in Settings
  4. +
  5. Verify that "Browser Support" AND "Contract Data" are enabled in Settings
  6. If no Browser Support is found in settings, verify that you have{' '} -- cgit v1.2.3 From 5019c5194044f9ab2631090365e09083044a730d Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 30 Jan 2018 20:10:51 +0100 Subject: Default the derivation path to that found in the Ledger subprovider --- packages/website/ts/components/dialogs/ledger_config_dialog.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index d9b4d1504..5b7e20671 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -45,12 +45,15 @@ interface LedgerConfigDialogState { export class LedgerConfigDialog extends React.Component { constructor(props: LedgerConfigDialogProps) { super(props); + const derivationPathIfExists = props.blockchain.getLedgerDerivationPathIfExists(); this.state = { connectionErrMsg: '', stepIndex: LedgerSteps.CONNECT, userAddresses: [], addressBalances: [], - derivationPath: configs.DEFAULT_DERIVATION_PATH, + derivationPath: _.isUndefined(derivationPathIfExists) + ? configs.DEFAULT_DERIVATION_PATH + : derivationPathIfExists, derivationErrMsg: '', preferredNetworkId: props.networkId, }; -- cgit v1.2.3 From e219772b2a25712f41fb819be36917d3b889201f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 30 Jan 2018 20:12:32 +0100 Subject: Fix all setState calls after unmounted errors. Decided to create our own flag rather then using a cancellablePromise since there was little to be gained from this alternative --- .../dialogs/eth_weth_conversion_dialog.tsx | 15 +++++++++---- packages/website/ts/components/eth_wrappers.tsx | 25 ++++++++++++++-------- packages/website/ts/components/fill_order.tsx | 19 ++++++++++------ .../ts/components/inputs/token_amount_input.tsx | 17 ++++++++++----- packages/website/ts/components/token_balances.tsx | 13 ++++++++--- 5 files changed, 62 insertions(+), 27 deletions(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx index a3a39a1b9..acd4a7110 100644 --- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -33,8 +33,10 @@ export class EthWethConversionDialog extends React.Component< EthWethConversionDialogProps, EthWethConversionDialogState > { + private _isUnmounted: boolean; constructor() { super(); + this._isUnmounted = false; this.state = { shouldShowIncompleteErrs: false, hasErrors: false, @@ -46,6 +48,9 @@ export class EthWethConversionDialog extends React.Component< // tslint:disable-next-line:no-floating-promises this._fetchEthTokenBalanceAsync(); } + public componentWillUnmount() { + this._isUnmounted = true; + } public render() { const convertDialogActions = [ , @@ -181,9 +186,11 @@ export class EthWethConversionDialog extends React.Component< this.props.userAddress, this.props.token.address, ); - this.setState({ - isEthTokenBalanceLoaded: true, - ethTokenBalance: balance, - }); + if (!this._isUnmounted) { + this.setState({ + isEthTokenBalanceLoaded: true, + ethTokenBalance: balance, + }); + } } } diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index 460a6cae3..90d2c0514 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -54,8 +54,10 @@ interface EthWrappersState { } export class EthWrappers extends React.Component { + private _isUnmounted: boolean; constructor(props: EthWrappersProps) { super(props); + this._isUnmounted = false; const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; @@ -91,6 +93,9 @@ export class EthWrappers extends React.Component { private _validator: SchemaValidator; + private _isUnmounted: boolean; constructor(props: FillOrderProps) { super(props); + this._isUnmounted = false; this.state = { globalErrMsg: '', didOrderValidationRun: false, @@ -90,6 +92,9 @@ export class FillOrder extends React.Component { public componentDidMount() { window.scrollTo(0, 0); } + public componentWillUnmount() { + this._isUnmounted = true; + } public render() { return (
    @@ -456,12 +461,14 @@ export class FillOrder extends React.Component { if (!_.isEmpty(orderJSON)) { orderJSONErrMsg = 'Submitted order JSON is not valid JSON'; } - this.setState({ - didOrderValidationRun: true, - orderJSON, - orderJSONErrMsg, - parsedOrder, - }); + if (!this._isUnmounted) { + this.setState({ + didOrderValidationRun: true, + orderJSON, + orderJSONErrMsg, + parsedOrder, + }); + } return; } diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx index 44f3fc4a8..9078f7fe1 100644 --- a/packages/website/ts/components/inputs/token_amount_input.tsx +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -30,8 +30,10 @@ interface TokenAmountInputState { } export class TokenAmountInput extends React.Component { + private _isUnmounted: boolean; constructor(props: TokenAmountInputProps) { super(props); + this._isUnmounted = false; const defaultAmount = new BigNumber(0); this.state = { balance: defaultAmount, @@ -43,6 +45,9 @@ export class TokenAmountInput extends React.Component { + private _isUnmounted: boolean; public constructor(props: TokenBalancesProps) { super(props); + this._isUnmounted = false; const initialTrackedTokenStateByAddress = this._getInitialTrackedTokenStateByAddress(props.trackedTokens); this.state = { errorType: undefined, @@ -109,6 +111,9 @@ export class TokenBalances extends React.Component Date: Tue, 30 Jan 2018 20:45:09 +0100 Subject: Fix linter errors --- packages/website/ts/components/dialogs/ledger_config_dialog.tsx | 2 +- packages/website/ts/components/eth_weth_conversion_button.tsx | 6 ++---- packages/website/ts/components/eth_wrappers.tsx | 3 --- packages/website/ts/components/generate_order/asset_picker.tsx | 2 +- .../website/ts/components/generate_order/generate_order_form.tsx | 1 - packages/website/ts/components/generate_order/new_token_form.tsx | 3 +-- packages/website/ts/components/inputs/token_amount_input.tsx | 3 ++- packages/website/ts/components/portal.tsx | 3 --- packages/website/ts/components/send_button.tsx | 6 ++---- packages/website/ts/components/token_balances.tsx | 4 ++-- packages/website/ts/components/top_bar/provider_display.tsx | 4 ---- packages/website/ts/components/top_bar/provider_picker.tsx | 8 -------- packages/website/ts/components/top_bar/top_bar.tsx | 5 ++--- packages/website/ts/components/ui/drop_down.tsx | 4 +--- 14 files changed, 14 insertions(+), 40 deletions(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index 5b7e20671..bc5f05241 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -10,7 +10,7 @@ import { Blockchain } from 'ts/blockchain'; import { NetworkDropDown } from 'ts/components/dropdowns/network_drop_down'; import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { Networks, ProviderType } from 'ts/types'; +import { ProviderType } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx index 52240fd0f..62bafdba7 100644 --- a/packages/website/ts/components/eth_weth_conversion_button.tsx +++ b/packages/website/ts/components/eth_weth_conversion_button.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; import { EthWethConversionDialog } from 'ts/components/dialogs/eth_weth_conversion_dialog'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { BlockchainCallErrs, Side, Token, TokenState } from 'ts/types'; +import { BlockchainCallErrs, Side, Token } from 'ts/types'; import { constants } from 'ts/utils/constants'; import { errorReporter } from 'ts/utils/error_reporter'; import { utils } from 'ts/utils/utils'; @@ -16,7 +16,6 @@ interface EthWethConversionButtonProps { networkId: number; direction: Side; ethToken: Token; - ethTokenState: TokenState; dispatcher: Dispatcher; blockchain: Blockchain; userEtherBalance: BigNumber; @@ -93,7 +92,6 @@ export class EthWethConversionButton extends React.Component< }); this._toggleConversionDialog(); const token = this.props.ethToken; - const tokenState = this.props.ethTokenState; try { if (direction === Side.Deposit) { await this.props.blockchain.convertEthToWrappedEthTokensAsync(token.address, value); @@ -105,7 +103,7 @@ export class EthWethConversionButton extends React.Component< this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`); } if (!this.props.isOutdatedWrappedEther) { - this.props.refetchEthTokenStateAsync(); + await this.props.refetchEthTokenStateAsync(); } this.props.onConversionSuccessful(); } catch (err) { diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index 90d2c0514..db1ceecb5 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -16,7 +16,6 @@ import { Token, TokenByAddress, TokenState, - TokenStateByAddress, } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; @@ -163,7 +162,6 @@ export class EthWrappers extends React.Component void; @@ -69,10 +68,9 @@ export class SendButton extends React.Component {
    )} {this.props.blockchainIsLoaded && ( -
    +
    Date: Tue, 30 Jan 2018 21:01:16 +0100 Subject: Uppercase Networks enum values --- packages/website/ts/components/dialogs/blockchain_err_dialog.tsx | 2 +- packages/website/ts/components/token_balances.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx index 3908d987e..278e2bbf5 100644 --- a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx +++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx @@ -129,7 +129,7 @@ export class BlockchainErrDialog extends React.Component The 0x smart contracts are not deployed on the Ethereum network you are currently connected to (network Id: {this.props.networkId}). In order to use the 0x portal dApp, please connect to the{' '} - {Networks.kovan} testnet (network Id: {constants.NETWORK_ID_KOVAN}) + {Networks.Kovan} testnet (network Id: {constants.NETWORK_ID_KOVAN}) {configs.IS_MAINNET_ENABLED ? ` or ${constants.MAINNET_NAME} (network Id: ${constants.NETWORK_ID_MAINNET}).` : `.`} diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index 9b9304e23..c6a9a46be 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -497,8 +497,8 @@ export class TokenBalances extends React.Component - Our faucet can only send test Ether to addresses on the {Networks.kovan} testnet (networkId{' '} - {constants.NETWORK_ID_KOVAN}). Please make sure you are connected to the {Networks.kovan}{' '} + Our faucet can only send test Ether to addresses on the {Networks.Kovan} testnet (networkId{' '} + {constants.NETWORK_ID_KOVAN}). Please make sure you are connected to the {Networks.Kovan}{' '} testnet and try requesting ether again.
    ); -- cgit v1.2.3 From d3e42e4b3e2f893b616246d35f46dba92f251e83 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 30 Jan 2018 21:16:47 +0100 Subject: Fix prettier --- packages/website/ts/components/portal.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx index 1c4937654..92589f75c 100644 --- a/packages/website/ts/components/portal.tsx +++ b/packages/website/ts/components/portal.tsx @@ -23,15 +23,7 @@ import { localStorage } from 'ts/local_storage/local_storage'; import { Dispatcher } from 'ts/redux/dispatcher'; import { orderSchema } from 'ts/schemas/order_schema'; import { SchemaValidator } from 'ts/schemas/validator'; -import { - BlockchainErrs, - HashData, - Order, - ProviderType, - ScreenWidths, - TokenByAddress, - WebsitePaths, -} from 'ts/types'; +import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAddress, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; -- cgit v1.2.3 From 8aac6e46d4f23fe8eee6116aeb9570945dc9df23 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 30 Jan 2018 21:34:13 +0100 Subject: Remove unused prop --- packages/website/ts/components/eth_wrappers.tsx | 1 - 1 file changed, 1 deletion(-) (limited to 'packages/website/ts/components') diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx index db1ceecb5..c2cdf6751 100644 --- a/packages/website/ts/components/eth_wrappers.tsx +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -309,7 +309,6 @@ export class EthWrappers extends React.Component