diff options
Diffstat (limited to 'packages/website/ts/components')
59 files changed, 2216 insertions, 2175 deletions
diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx index 963bd4388..f555ca6b1 100644 --- a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx +++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx @@ -1,12 +1,12 @@ import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {Blockchain} from 'ts/blockchain'; -import {BlockchainErrs} from 'ts/types'; -import {configs} from 'ts/utils/configs'; -import {constants} from 'ts/utils/constants'; +import { Blockchain } from 'ts/blockchain'; +import { BlockchainErrs } from 'ts/types'; +import { colors } from 'ts/utils/colors'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; interface BlockchainErrDialogProps { blockchain: Blockchain; @@ -31,127 +31,126 @@ export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProp const hasWalletAddress = this.props.userAddress !== ''; return ( <Dialog - title={this.getTitle(hasWalletAddress)} - titleStyle={{fontWeight: 100}} + title={this._getTitle(hasWalletAddress)} + titleStyle={{ fontWeight: 100 }} actions={dialogActions} open={this.props.isOpen} - contentStyle={{width: 400}} + contentStyle={{ width: 400 }} onRequestClose={this.props.toggleDialogFn.bind(this.props.toggleDialogFn, false)} autoScrollBodyContent={true} > - <div className="pt2" style={{color: colors.grey700}}> - {this.renderExplanation(hasWalletAddress)} + <div className="pt2" style={{ color: colors.grey700 }}> + {this._renderExplanation(hasWalletAddress)} </div> </Dialog> ); } - private getTitle(hasWalletAddress: boolean) { - if (this.props.blockchainErr === BlockchainErrs.A_CONTRACT_NOT_DEPLOYED_ON_NETWORK) { + private _getTitle(hasWalletAddress: boolean) { + if (this.props.blockchainErr === BlockchainErrs.AContractNotDeployedOnNetwork) { return '0x smart contracts not found'; } else if (!hasWalletAddress) { return 'Enable wallet communication'; - } else if (this.props.blockchainErr === BlockchainErrs.DISCONNECTED_FROM_ETHEREUM_NODE) { + } else if (this.props.blockchainErr === BlockchainErrs.DisconnectedFromEthereumNode) { return 'Disconnected from Ethereum network'; } else { return 'Unexpected error'; } } - private renderExplanation(hasWalletAddress: boolean) { - if (this.props.blockchainErr === BlockchainErrs.A_CONTRACT_NOT_DEPLOYED_ON_NETWORK) { - return this.renderContractsNotDeployedExplanation(); + private _renderExplanation(hasWalletAddress: boolean) { + if (this.props.blockchainErr === BlockchainErrs.AContractNotDeployedOnNetwork) { + return this._renderContractsNotDeployedExplanation(); } else if (!hasWalletAddress) { - return this.renderNoWalletFoundExplanation(); - } else if (this.props.blockchainErr === BlockchainErrs.DISCONNECTED_FROM_ETHEREUM_NODE) { - return this.renderDisconnectedFromNode(); + return this._renderNoWalletFoundExplanation(); + } else if (this.props.blockchainErr === BlockchainErrs.DisconnectedFromEthereumNode) { + return this._renderDisconnectedFromNode(); } else { - return this.renderUnexpectedErrorExplanation(); + return this._renderUnexpectedErrorExplanation(); } } - private renderDisconnectedFromNode() { + private _renderDisconnectedFromNode() { return ( <div> - You were disconnected from the backing Ethereum node. - {' '}If using <a href={constants.METAMASK_CHROME_STORE_URL} target="_blank"> + You were disconnected from the backing Ethereum node. If using{' '} + <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> Metamask - </a> or <a href={constants.MIST_DOWNLOAD_URL} target="_blank">Mist</a> try refreshing - {' '}the page. If using a locally hosted Ethereum node, make sure it's still running. + </a>{' '} + or{' '} + <a href={constants.URL_MIST_DOWNLOAD} target="_blank"> + Mist + </a>{' '} + try refreshing the page. If using a locally hosted Ethereum node, make sure it's still running. </div> ); } - private renderUnexpectedErrorExplanation() { - return ( - <div> - We encountered an unexpected error. Please try refreshing the page. - </div> - ); + private _renderUnexpectedErrorExplanation() { + return <div>We encountered an unexpected error. Please try refreshing the page.</div>; } - private renderNoWalletFoundExplanation() { + private _renderNoWalletFoundExplanation() { return ( <div> <div> - We were unable to access an Ethereum wallet you control. In order to interact - {' '}with the 0x portal dApp, - we need a way to interact with one of your Ethereum wallets. - {' '}There are two easy ways you can enable us to do that: + We were unable to access an Ethereum wallet you control. In order to interact with the 0x portal + dApp, we need a way to interact with one of your Ethereum wallets. There are two easy ways you can + enable us to do that: </div> <h4>1. Metamask chrome extension</h4> <div> You can install the{' '} - <a href={constants.METAMASK_CHROME_STORE_URL} target="_blank"> + <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> Metamask - </a> Chrome extension Ethereum wallet. Once installed and set up, refresh this page. + </a>{' '} + Chrome extension Ethereum wallet. Once installed and set up, refresh this page. <div className="pt1"> - <span className="bold">Note:</span> - {' '}If you already have Metamask installed, make sure it is unlocked. + <span className="bold">Note:</span> If you already have Metamask installed, make sure it is + unlocked. </div> </div> <h4>Parity Signer</h4> <div> - The <a href={constants.PARITY_CHROME_STORE_URL} target="_blank">Parity Signer - Chrome extension</a>{' '}lets you connect to a locally running Parity node. - Make sure you have started your local Parity node with{' '} - {configs.isMainnetEnabled && '`parity ui` or'} `parity --chain kovan ui`{' '} - in order to connect to {configs.isMainnetEnabled ? 'mainnet or Kovan respectively.' : 'Kovan.'} + The{' '} + <a href={constants.URL_PARITY_CHROME_STORE} target="_blank"> + Parity Signer Chrome extension + </a>{' '} + lets you connect to a locally running Parity node. Make sure you have started your local Parity node + with {configs.IS_MAINNET_ENABLED && '`parity ui` or'} `parity --chain kovan ui` in order to connect + to {configs.IS_MAINNET_ENABLED ? 'mainnet or Kovan respectively.' : 'Kovan.'} </div> <div className="pt2"> - <span className="bold">Note:</span> - {' '}If you have done one of the above steps and are still seeing this message, - {' '}we might still be unable to retrieve an Ethereum address by calling `web3.eth.accounts`. - {' '}Make sure you have created at least one Ethereum address. + <span className="bold">Note:</span> If you have done one of the above steps and are still seeing + this message, we might still be unable to retrieve an Ethereum address by calling + `web3.eth.accounts`. Make sure you have created at least one Ethereum address. </div> </div> ); } - private renderContractsNotDeployedExplanation() { + private _renderContractsNotDeployedExplanation() { return ( <div> <div> - 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.TESTNET_NETWORK_ID}) - {configs.isMainnetEnabled ? - ` or ${constants.MAINNET_NAME} (network Id: ${constants.MAINNET_NETWORK_ID}).` : - `.` - } + 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}) + {configs.IS_MAINNET_ENABLED + ? ` or ${constants.MAINNET_NAME} (network Id: ${constants.NETWORK_ID_MAINNET}).` + : `.`} </div> <h4>Metamask</h4> <div> If you are using{' '} - <a href={constants.METAMASK_CHROME_STORE_URL} target="_blank"> + <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank"> Metamask </a>, you can switch networks in the top left corner of the extension popover. </div> <h4>Parity Signer</h4> <div> - If using the <a href={constants.PARITY_CHROME_STORE_URL} target="_blank">Parity Signer - Chrome extension</a>, make sure to start your local Parity node with{' '} - {configs.isMainnetEnabled ? - '`parity ui` or `parity --chain Kovan ui` in order to connect to mainnet \ - or Kovan respectively.' : - '`parity --chain kovan ui` in order to connect to Kovan.' - } + If using the{' '} + <a href={constants.URL_PARITY_CHROME_STORE} target="_blank"> + Parity Signer Chrome extension + </a>, make sure to start your local Parity node with{' '} + {configs.IS_MAINNET_ENABLED + ? '`parity ui` or `parity --chain Kovan ui` in order to connect to mainnet \ + or Kovan respectively.' + : '`parity --chain kovan ui` in order to connect to Kovan.'} </div> </div> ); 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 aba5b9faf..661cc1d8c 100644 --- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx +++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx @@ -1,14 +1,14 @@ -import BigNumber from 'bignumber.js'; +import { BigNumber } from '@0xproject/utils'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; -import RadioButton from 'material-ui/RadioButton'; -import RadioButtonGroup from 'material-ui/RadioButton/RadioButtonGroup'; import * as React from 'react'; -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 { 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 { colors } from 'ts/utils/colors'; interface EthWethConversionDialogProps { + direction: Side; onComplete: (direction: Side, value: BigNumber) => void; onCancelled: () => void; isOpen: boolean; @@ -19,105 +19,129 @@ interface EthWethConversionDialogProps { interface EthWethConversionDialogState { value?: BigNumber; - direction: Side; shouldShowIncompleteErrs: boolean; hasErrors: boolean; } -export class EthWethConversionDialog extends - React.Component<EthWethConversionDialogProps, EthWethConversionDialogState> { +export class EthWethConversionDialog extends React.Component< + EthWethConversionDialogProps, + EthWethConversionDialogState +> { constructor() { super(); this.state = { - direction: Side.deposit, shouldShowIncompleteErrs: false, - hasErrors: true, + hasErrors: false, }; } public render() { const convertDialogActions = [ - <FlatButton - key="cancel" - label="Cancel" - onTouchTap={this.onCancel.bind(this)} - />, - <FlatButton - key="convert" - label="Convert" - primary={true} - onTouchTap={this.onConvertClick.bind(this)} - />, + <FlatButton key="cancel" label="Cancel" onTouchTap={this._onCancel.bind(this)} />, + <FlatButton key="convert" label="Convert" primary={true} onTouchTap={this._onConvertClick.bind(this)} />, ]; + const title = this.props.direction === Side.Deposit ? 'Wrap ETH' : 'Unwrap WETH'; return ( <Dialog - title="I want to convert" - titleStyle={{fontWeight: 100}} + title={title} + titleStyle={{ fontWeight: 100 }} actions={convertDialogActions} + contentStyle={{ width: 448 }} open={this.props.isOpen} > - {this.renderConversionDialogBody()} + {this._renderConversionDialogBody()} </Dialog> ); } - private renderConversionDialogBody() { + private _renderConversionDialogBody() { + const explanation = + this.props.direction === Side.Deposit + ? 'Convert your Ether into a tokenized, tradable form.' + : "Convert your Wrapped Ether back into it's native form."; + const isWrappedVersion = this.props.direction === Side.Receive; + return ( + <div> + <div className="pb2">{explanation}</div> + <div className="mx-auto" style={{ maxWidth: 312 }}> + <div className="flex"> + {this._renderCurrency(isWrappedVersion)} + <div style={{ paddingTop: 68 }}> + <i style={{ fontSize: 28, color: colors.darkBlue }} className="zmdi zmdi-arrow-right" /> + </div> + {this._renderCurrency(!isWrappedVersion)} + </div> + <div className="pt2 mx-auto" style={{ width: 245 }}> + {this.props.direction === Side.Receive ? ( + <TokenAmountInput + token={this.props.token} + tokenState={this.props.tokenState} + shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} + shouldCheckBalance={true} + shouldCheckAllowance={false} + onChange={this._onValueChange.bind(this)} + amount={this.state.value} + onVisitBalancesPageClick={this.props.onCancelled} + /> + ) : ( + <EthAmountInput + balance={this.props.etherBalance} + amount={this.state.value} + onChange={this._onValueChange.bind(this)} + shouldCheckBalance={true} + shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} + onVisitBalancesPageClick={this.props.onCancelled} + /> + )} + <div className="pt1" style={{ fontSize: 12 }}> + <div className="left">1 ETH = 1 WETH</div> + {this.props.direction === Side.Receive && ( + <div + className="right" + onClick={this._onMaxClick.bind(this)} + style={{ + color: colors.darkBlue, + textDecoration: 'underline', + cursor: 'pointer', + }} + > + Max + </div> + )} + </div> + </div> + </div> + </div> + ); + } + private _renderCurrency(isWrappedVersion: boolean) { + const name = isWrappedVersion ? 'Wrapped Ether' : 'Ether'; + const iconUrl = isWrappedVersion ? '/images/token_icons/ether_erc20.png' : '/images/ether.png'; + const symbol = isWrappedVersion ? 'WETH' : 'ETH'; return ( - <div className="mx-auto" style={{maxWidth: 300}}> - <RadioButtonGroup - className="pb1" - defaultSelected={this.state.direction} - name="conversionDirection" - onChange={this.onConversionDirectionChange.bind(this)} - > - <RadioButton - className="pb1" - value={Side.deposit} - label="Ether -> Ether Tokens" - /> - <RadioButton - value={Side.receive} - label="Ether Tokens -> Ether" - /> - </RadioButtonGroup> - {this.state.direction === Side.receive ? - <TokenAmountInput - label="Amount to convert" - token={this.props.token} - tokenState={this.props.tokenState} - shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} - shouldCheckBalance={true} - shouldCheckAllowance={false} - onChange={this.onValueChange.bind(this)} - amount={this.state.value} - onVisitBalancesPageClick={this.props.onCancelled} - /> : - <EthAmountInput - label="Amount to convert" - balance={this.props.etherBalance} - amount={this.state.value} - onChange={this.onValueChange.bind(this)} - shouldCheckBalance={true} - shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} - onVisitBalancesPageClick={this.props.onCancelled} - /> - } + <div className="mx-auto pt2"> + <div className="center" style={{ color: colors.darkBlue }}> + {name} + </div> + <div className="center py2"> + <img src={iconUrl} style={{ width: 60 }} /> + </div> + <div className="center" style={{ fontSize: 12 }}> + ({symbol}) + </div> </div> ); } - private onConversionDirectionChange(e: any, direction: Side) { + private _onMaxClick() { this.setState({ - value: undefined, - shouldShowIncompleteErrs: false, - direction, - hasErrors: true, + value: this.props.tokenState.balance, }); } - private onValueChange(isValid: boolean, amount?: BigNumber) { + private _onValueChange(isValid: boolean, amount?: BigNumber) { this.setState({ value: amount, hasErrors: !isValid, }); } - private onConvertClick() { + private _onConvertClick() { if (this.state.hasErrors) { this.setState({ shouldShowIncompleteErrs: true, @@ -127,10 +151,10 @@ export class EthWethConversionDialog extends this.setState({ value: undefined, }); - this.props.onComplete(this.state.direction, value); + this.props.onComplete(this.props.direction, value); } } - private onCancel() { + private _onCancel() { this.setState({ value: undefined, }); diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx index d3c95a011..60db93c52 100644 --- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx +++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx @@ -1,24 +1,18 @@ -import BigNumber from 'bignumber.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; -import {colors} from 'material-ui/styles'; -import { - Table, - TableBody, - TableHeader, - TableHeaderColumn, - TableRow, - TableRowColumn, -} from 'material-ui/Table'; +import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table'; import TextField from 'material-ui/TextField'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); -import {Blockchain} from 'ts/blockchain'; -import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button'; -import {Dispatcher} from 'ts/redux/dispatcher'; -import {constants} from 'ts/utils/constants'; -import {utils} from 'ts/utils/utils'; +import { Blockchain } from 'ts/blockchain'; +import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { colors } from 'ts/utils/colors'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; +import { utils } from 'ts/utils/utils'; const VALID_ETHEREUM_DERIVATION_PATH_PREFIX = `44'/60'`; @@ -52,58 +46,45 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, stepIndex: LedgerSteps.CONNECT, userAddresses: [], addressBalances: [], - derivationPath: constants.DEFAULT_DERIVATION_PATH, + derivationPath: configs.DEFAULT_DERIVATION_PATH, derivationErrMsg: '', }; } public render() { const dialogActions = [ - <FlatButton - key="ledgerConnectCancel" - label="Cancel" - onTouchTap={this.onClose.bind(this)} - />, + <FlatButton key="ledgerConnectCancel" label="Cancel" onTouchTap={this._onClose.bind(this)} />, ]; - const dialogTitle = this.state.stepIndex === LedgerSteps.CONNECT ? - 'Connect to your Ledger' : - 'Select desired address'; + const dialogTitle = + this.state.stepIndex === LedgerSteps.CONNECT ? 'Connect to your Ledger' : 'Select desired address'; return ( <Dialog title={dialogTitle} - titleStyle={{fontWeight: 100}} + titleStyle={{ fontWeight: 100 }} actions={dialogActions} open={this.props.isOpen} - onRequestClose={this.onClose.bind(this)} + onRequestClose={this._onClose.bind(this)} autoScrollBodyContent={true} - bodyStyle={{paddingBottom: 0}} + bodyStyle={{ paddingBottom: 0 }} > - <div style={{color: colors.grey700, paddingTop: 1}}> - {this.state.stepIndex === LedgerSteps.CONNECT && - this.renderConnectStep() - } - {this.state.stepIndex === LedgerSteps.SELECT_ADDRESS && - this.renderSelectAddressStep() - } + <div style={{ color: colors.grey700, paddingTop: 1 }}> + {this.state.stepIndex === LedgerSteps.CONNECT && this._renderConnectStep()} + {this.state.stepIndex === LedgerSteps.SELECT_ADDRESS && this._renderSelectAddressStep()} </div> </Dialog> ); } - private renderConnectStep() { + private _renderConnectStep() { return ( <div> - <div className="h4 pt3"> - Follow these instructions before proceeding: - </div> + <div className="h4 pt3">Follow these instructions before proceeding:</div> <ol> - <li className="pb1"> - Connect your Ledger Nano S & Open the Ethereum application - </li> - <li className="pb1"> - Verify that Browser Support is enabled in Settings - </li> + <li className="pb1">Connect your Ledger Nano S & Open the Ethereum application</li> + <li className="pb1">Verify that Browser Support is enabled in Settings</li> <li className="pb1"> If no Browser Support is found in settings, verify that you have{' '} - <a href="https://www.ledgerwallet.com/apps/manager" target="_blank">Firmware >1.2</a> + <a href="https://www.ledgerwallet.com/apps/manager" target="_blank"> + Firmware >1.2 + </a> </li> </ol> <div className="center pb3"> @@ -112,85 +93,74 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, labelReady="Connect to Ledger" labelLoading="Connecting..." labelComplete="Connected!" - onClickAsyncFn={this.onConnectLedgerClickAsync.bind(this, true)} + onClickAsyncFn={this._onConnectLedgerClickAsync.bind(this, true)} /> - {this.state.didConnectFail && - <div className="pt2 left-align" style={{color: colors.red200}}> + {this.state.didConnectFail && ( + <div className="pt2 left-align" style={{ color: colors.red200 }}> Failed to connect. Follow the instructions and try again. </div> - } + )} </div> </div> ); } - private renderSelectAddressStep() { + private _renderSelectAddressStep() { return ( <div> <div> - <Table - bodyStyle={{height: 300}} - onRowSelection={this.onAddressSelected.bind(this)} - > + <Table bodyStyle={{ height: 300 }} onRowSelection={this._onAddressSelected.bind(this)}> <TableHeader displaySelectAll={false}> <TableRow> <TableHeaderColumn colSpan={2}>Address</TableHeaderColumn> <TableHeaderColumn>Balance</TableHeaderColumn> </TableRow> </TableHeader> - <TableBody> - {this.renderAddressTableRows()} - </TableBody> + <TableBody>{this._renderAddressTableRows()}</TableBody> </Table> </div> - <div className="flex pt2" style={{height: 100}}> - <div className="overflow-hidden" style={{width: 180}}> + <div className="flex pt2" style={{ height: 100 }}> + <div className="overflow-hidden" style={{ width: 180 }}> <TextField floatingLabelFixed={true} - floatingLabelStyle={{color: colors.grey500}} + floatingLabelStyle={{ color: colors.grey }} floatingLabelText="Update path derivation (advanced)" value={this.state.derivationPath} errorText={this.state.derivationErrMsg} - onChange={this.onDerivationPathChanged.bind(this)} + onChange={this._onDerivationPathChanged.bind(this)} /> </div> - <div className="pl2" style={{paddingTop: 28}}> + <div className="pl2" style={{ paddingTop: 28 }}> <LifeCycleRaisedButton labelReady="Update" labelLoading="Updating..." labelComplete="Updated!" - onClickAsyncFn={this.onFetchAddressesForDerivationPathAsync.bind(this, true)} + onClickAsyncFn={this._onFetchAddressesForDerivationPathAsync.bind(this)} /> </div> </div> </div> ); } - private renderAddressTableRows() { + private _renderAddressTableRows() { const rows = _.map(this.state.userAddresses, (userAddress: string, i: number) => { const balance = this.state.addressBalances[i]; const addressTooltipId = `address-${userAddress}`; const balanceTooltipId = `balance-${userAddress}`; - const networkName = constants.networkNameById[this.props.networkId]; + const networkName = constants.NETWORK_NAME_BY_ID[this.props.networkId]; // We specifically prefix kovan ETH. // TODO: We should probably add prefixes for all networks const isKovanNetwork = networkName === 'Kovan'; const balanceString = `${balance.toString()} ${isKovanNetwork ? 'Kovan ' : ''}ETH`; return ( - <TableRow key={userAddress} style={{height: 40}}> + <TableRow key={userAddress} style={{ height: 40 }}> <TableRowColumn colSpan={2}> - <div - data-tip={true} - data-for={addressTooltipId} - > + <div data-tip={true} data-for={addressTooltipId}> {userAddress} </div> <ReactTooltip id={addressTooltipId}>{userAddress}</ReactTooltip> </TableRowColumn> <TableRowColumn> - <div - data-tip={true} - data-for={balanceTooltipId} - > + <div data-tip={true} data-for={balanceTooltipId}> {balanceString} </div> <ReactTooltip id={balanceTooltipId}>{balanceString}</ReactTooltip> @@ -200,14 +170,14 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, }); return rows; } - private onClose() { + private _onClose() { this.setState({ didConnectFail: false, }); const isOpen = false; this.props.toggleDialogFn(isOpen); } - private onAddressSelected(selectedRowIndexes: number[]) { + private _onAddressSelected(selectedRowIndexes: number[]) { const selectedRowIndex = selectedRowIndexes[0]; this.props.blockchain.updateLedgerDerivationIndex(selectedRowIndex); const selectedAddress = this.state.userAddresses[selectedRowIndex]; @@ -221,13 +191,15 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, const isOpen = false; this.props.toggleDialogFn(isOpen); } - private async onFetchAddressesForDerivationPathAsync() { + private async _onFetchAddressesForDerivationPathAsync(): Promise<boolean> { const currentlySetPath = this.props.blockchain.getLedgerDerivationPathIfExists(); + let didSucceed; if (currentlySetPath === this.state.derivationPath) { - return; + didSucceed = true; + return didSucceed; } this.props.blockchain.updateLedgerDerivationPathIfExists(this.state.derivationPath); - const didSucceed = await this.fetchAddressesAndBalancesAsync(); + didSucceed = await this._fetchAddressesAndBalancesAsync(); if (!didSucceed) { this.setState({ derivationErrMsg: 'Failed to connect to Ledger.', @@ -235,11 +207,11 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, } return didSucceed; } - private async fetchAddressesAndBalancesAsync() { + private async _fetchAddressesAndBalancesAsync() { let userAddresses: string[]; const addressBalances: BigNumber[] = []; try { - userAddresses = await this.getUserAddressesAsync(); + userAddresses = await this._getUserAddressesAsync(); for (const address of userAddresses) { const balance = await this.props.blockchain.getBalanceInEthAsync(address); addressBalances.push(balance); @@ -257,7 +229,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, }); return true; } - private onDerivationPathChanged(e: any, derivationPath: string) { + private _onDerivationPathChanged(e: any, derivationPath: string) { let derivationErrMsg = ''; if (!_.startsWith(derivationPath, VALID_ETHEREUM_DERIVATION_PATH_PREFIX)) { derivationErrMsg = 'Must be valid Ethereum path.'; @@ -268,8 +240,8 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, derivationErrMsg, }); } - private async onConnectLedgerClickAsync() { - const didSucceed = await this.fetchAddressesAndBalancesAsync(); + private async _onConnectLedgerClickAsync() { + const didSucceed = await this._fetchAddressesAndBalancesAsync(); if (didSucceed) { this.setState({ stepIndex: LedgerSteps.SELECT_ADDRESS, @@ -277,7 +249,7 @@ export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, } return didSucceed; } - private async getUserAddressesAsync(): Promise<string[]> { + private async _getUserAddressesAsync(): Promise<string[]> { let userAddresses: string[]; userAddresses = await this.props.blockchain.getUserAccountsAsync(); diff --git a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx index 1d90624ee..3ecc454a0 100644 --- a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx +++ b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx @@ -1,8 +1,7 @@ import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {constants} from 'ts/utils/constants'; +import { colors } from 'ts/utils/colors'; interface PortalDisclaimerDialogProps { isOpen: boolean; @@ -13,31 +12,23 @@ export function PortalDisclaimerDialog(props: PortalDisclaimerDialogProps) { return ( <Dialog title="0x Portal Disclaimer" - titleStyle={{fontWeight: 100}} - actions={[ - <FlatButton - key="portalAgree" - label="I Agree" - onTouchTap={props.onToggleDialog.bind(this)} - />, - ]} + titleStyle={{ fontWeight: 100 }} + actions={[<FlatButton key="portalAgree" label="I Agree" onTouchTap={props.onToggleDialog} />]} open={props.isOpen} - onRequestClose={props.onToggleDialog.bind(this)} + onRequestClose={props.onToggleDialog} autoScrollBodyContent={true} modal={true} > - <div className="pt2" style={{color: colors.grey700}}> + <div className="pt2" style={{ color: colors.grey700 }}> <div> - 0x Portal is a free software-based tool intended to help users to - buy and sell ERC20-compatible blockchain tokens through the 0x protocol - on a purely peer-to-peer basis. 0x portal is not a regulated marketplace, - exchange or intermediary of any kind, and therefore, you should only use - 0x portal to exchange tokens that are not securities, commodity interests, - or any other form of regulated instrument. 0x has not attempted to screen - or otherwise limit the tokens that you may enter in 0x Portal. By clicking - “I Agree” below, you understand that you are solely responsible for using 0x - Portal and buying and selling tokens using 0x Portal in compliance with all - applicable laws and regulations. + 0x Portal is a free software-based tool intended to help users to buy and sell ERC20-compatible + blockchain tokens through the 0x protocol on a purely peer-to-peer basis. 0x portal is not a + regulated marketplace, exchange or intermediary of any kind, and therefore, you should only use 0x + portal to exchange tokens that are not securities, commodity interests, or any other form of + regulated instrument. 0x has not attempted to screen or otherwise limit the tokens that you may + enter in 0x Portal. By clicking “I Agree” below, you understand that you are solely responsible for + using 0x Portal and buying and selling tokens using 0x Portal in compliance with all applicable laws + and regulations. </div> </div> </Dialog> diff --git a/packages/website/ts/components/dialogs/send_dialog.tsx b/packages/website/ts/components/dialogs/send_dialog.tsx index 31afc3386..b3dbce598 100644 --- a/packages/website/ts/components/dialogs/send_dialog.tsx +++ b/packages/website/ts/components/dialogs/send_dialog.tsx @@ -1,14 +1,11 @@ -import BigNumber from 'bignumber.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; -import RadioButton from 'material-ui/RadioButton'; -import RadioButtonGroup from 'material-ui/RadioButton/RadioButtonGroup'; import * as React from 'react'; -import {AddressInput} from 'ts/components/inputs/address_input'; -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 { AddressInput } from 'ts/components/inputs/address_input'; +import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; +import { Token, TokenState } from 'ts/types'; interface SendDialogProps { onComplete: (recipient: string, value: BigNumber) => void; @@ -36,37 +33,33 @@ export class SendDialog extends React.Component<SendDialogProps, SendDialogState } public render() { const transferDialogActions = [ - <FlatButton - key="cancelTransfer" - label="Cancel" - onTouchTap={this.onCancel.bind(this)} - />, + <FlatButton key="cancelTransfer" label="Cancel" onTouchTap={this._onCancel.bind(this)} />, <FlatButton key="sendTransfer" - disabled={this.hasErrors()} + disabled={this._hasErrors()} label="Send" primary={true} - onTouchTap={this.onSendClick.bind(this)} + onTouchTap={this._onSendClick.bind(this)} />, ]; return ( <Dialog title="I want to send" - titleStyle={{fontWeight: 100}} + titleStyle={{ fontWeight: 100 }} actions={transferDialogActions} open={this.props.isOpen} > - {this.renderSendDialogBody()} + {this._renderSendDialogBody()} </Dialog> ); } - private renderSendDialogBody() { + private _renderSendDialogBody() { return ( - <div className="mx-auto" style={{maxWidth: 300}}> - <div style={{height: 80}}> + <div className="mx-auto" style={{ maxWidth: 300 }}> + <div style={{ height: 80 }}> <AddressInput initialAddress={this.state.recipient} - updateAddress={this.onRecipientChange.bind(this)} + updateAddress={this._onRecipientChange.bind(this)} isRequired={true} label={'Recipient address'} hintText={'Address'} @@ -79,27 +72,27 @@ export class SendDialog extends React.Component<SendDialogProps, SendDialogState shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} shouldCheckBalance={true} shouldCheckAllowance={false} - onChange={this.onValueChange.bind(this)} + onChange={this._onValueChange.bind(this)} amount={this.state.value} onVisitBalancesPageClick={this.props.onCancelled} /> </div> ); } - private onRecipientChange(recipient?: string) { + private _onRecipientChange(recipient?: string) { this.setState({ shouldShowIncompleteErrs: false, recipient, }); } - private onValueChange(isValid: boolean, amount?: BigNumber) { + private _onValueChange(isValid: boolean, amount?: BigNumber) { this.setState({ isAmountValid: isValid, value: amount, }); } - private onSendClick() { - if (this.hasErrors()) { + private _onSendClick() { + if (this._hasErrors()) { this.setState({ shouldShowIncompleteErrs: true, }); @@ -112,15 +105,13 @@ export class SendDialog extends React.Component<SendDialogProps, SendDialogState this.props.onComplete(this.state.recipient, value); } } - private onCancel() { + private _onCancel() { this.setState({ value: undefined, }); this.props.onCancelled(); } - private hasErrors() { - return _.isUndefined(this.state.recipient) || - _.isUndefined(this.state.value) || - !this.state.isAmountValid; + private _hasErrors() { + return _.isUndefined(this.state.recipient) || _.isUndefined(this.state.value) || !this.state.isAmountValid; } } 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 70c7d1ab6..3f29d46f8 100644 --- a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx +++ b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx @@ -1,14 +1,12 @@ import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {Blockchain} from 'ts/blockchain'; -import {TrackTokenConfirmation} from 'ts/components/track_token_confirmation'; -import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; -import {Dispatcher} from 'ts/redux/dispatcher'; -import {Token, TokenByAddress} from 'ts/types'; -import {constants} from 'ts/utils/constants'; +import { Blockchain } from 'ts/blockchain'; +import { TrackTokenConfirmation } from 'ts/components/track_token_confirmation'; +import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { Token, TokenByAddress } from 'ts/types'; interface TrackTokenConfirmationDialogProps { tokens: Token[]; @@ -25,8 +23,10 @@ interface TrackTokenConfirmationDialogState { isAddingTokenToTracked: boolean; } -export class TrackTokenConfirmationDialog extends - React.Component<TrackTokenConfirmationDialogProps, TrackTokenConfirmationDialogState> { +export class TrackTokenConfirmationDialog extends React.Component< + TrackTokenConfirmationDialogProps, + TrackTokenConfirmationDialogState +> { constructor(props: TrackTokenConfirmationDialogProps) { super(props); this.state = { @@ -38,17 +38,17 @@ export class TrackTokenConfirmationDialog extends return ( <Dialog title="Tracking confirmation" - titleStyle={{fontWeight: 100}} + titleStyle={{ fontWeight: 100 }} actions={[ <FlatButton key="trackNo" label="No" - onTouchTap={this.onTrackConfirmationRespondedAsync.bind(this, false)} + onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)} />, <FlatButton key="trackYes" label="Yes" - onTouchTap={this.onTrackConfirmationRespondedAsync.bind(this, true)} + onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)} />, ]} open={this.props.isOpen} @@ -66,7 +66,7 @@ export class TrackTokenConfirmationDialog extends </Dialog> ); } - private async onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { + private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { if (!didUserAcceptTracking) { this.props.onToggleDialog(didUserAcceptTracking); return; @@ -75,16 +75,17 @@ export class TrackTokenConfirmationDialog extends isAddingTokenToTracked: true, }); for (const token of this.props.tokens) { - const newTokenEntry = _.assign({}, token); + const newTokenEntry = { + ...token, + }; 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); + const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( + token.address, + ); this.props.dispatcher.updateTokenStateByAddress({ [token.address]: { balance, diff --git a/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx index 09c32c997..098e3e26d 100644 --- a/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx +++ b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx @@ -1,8 +1,8 @@ import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {constants} from 'ts/utils/constants'; +import { colors } from 'ts/utils/colors'; +import { constants } from 'ts/utils/constants'; interface U2fNotSupportedDialogProps { isOpen: boolean; @@ -13,24 +13,16 @@ export function U2fNotSupportedDialog(props: U2fNotSupportedDialogProps) { return ( <Dialog title="U2F Not Supported" - titleStyle={{fontWeight: 100}} - actions={[ - <FlatButton - key="u2fNo" - label="Ok" - onTouchTap={props.onToggleDialog.bind(this)} - />, - ]} + titleStyle={{ fontWeight: 100 }} + actions={[<FlatButton key="u2fNo" label="Ok" onTouchTap={props.onToggleDialog.bind(this)} />]} open={props.isOpen} onRequestClose={props.onToggleDialog.bind(this)} autoScrollBodyContent={true} > - <div className="pt2" style={{color: colors.grey700}}> + <div className="pt2" style={{ color: colors.grey700 }}> <div> - It looks like your browser does not support U2F connections - required for us to communicate with your hardware wallet. - Please use a browser that supports U2F connections and try - again. + It looks like your browser does not support U2F connections required for us to communicate with your + hardware wallet. Please use a browser that supports U2F connections and try again. </div> <div> <ul> @@ -39,9 +31,9 @@ export function U2fNotSupportedDialog(props: U2fNotSupportedDialogProps) { <li> Firefox with{' '} <a - href={constants.FIREFOX_U2F_ADDON} + href={constants.URL_FIREFOX_U2F_ADDON} target="_blank" - style={{textDecoration: 'underline'}} + style={{ textDecoration: 'underline' }} > this extension </a>. diff --git a/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx b/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx new file mode 100644 index 000000000..9e91ff12d --- /dev/null +++ b/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx @@ -0,0 +1,33 @@ +import Dialog from 'material-ui/Dialog'; +import FlatButton from 'material-ui/FlatButton'; +import { colors } from 'material-ui/styles'; +import * as React from 'react'; + +interface WrappedEthSectionNoticeDialogProps { + isOpen: boolean; + onToggleDialog: () => void; +} + +export function WrappedEthSectionNoticeDialog(props: WrappedEthSectionNoticeDialogProps) { + return ( + <Dialog + title="Dedicated Wrapped Ether Section" + titleStyle={{ fontWeight: 100 }} + actions={[ + <FlatButton key="acknowledgeWrapEthSection" label="Sounds good" onTouchTap={props.onToggleDialog} />, + ]} + open={props.isOpen} + onRequestClose={props.onToggleDialog} + autoScrollBodyContent={true} + modal={true} + > + <div className="pt2" style={{ color: colors.grey700 }}> + <div> + We have recently updated the Wrapped Ether token (WETH) used by 0x Portal. Don't worry, unwrapping + Ether tied to the old Wrapped Ether token can be done at any time by clicking on the "Wrap ETH" + section in the menu to the left. + </div> + </div> + </Dialog> + ); +} diff --git a/packages/website/ts/components/eth_weth_conversion_button.tsx b/packages/website/ts/components/eth_weth_conversion_button.tsx index a83b1543f..300e71f1f 100644 --- a/packages/website/ts/components/eth_weth_conversion_button.tsx +++ b/packages/website/ts/components/eth_weth_conversion_button.tsx @@ -1,23 +1,26 @@ -import {ZeroEx} from '0x.js'; -import BigNumber from 'bignumber.js'; +import { ZeroEx } from '0x.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import RaisedButton from 'material-ui/RaisedButton'; 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 {constants} from 'ts/utils/constants'; -import {errorReporter} from 'ts/utils/error_reporter'; -import {utils} from 'ts/utils/utils'; +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 { constants } from 'ts/utils/constants'; +import { errorReporter } from 'ts/utils/error_reporter'; +import { utils } from 'ts/utils/utils'; interface EthWethConversionButtonProps { + direction: Side; ethToken: Token; ethTokenState: TokenState; dispatcher: Dispatcher; blockchain: Blockchain; userEtherBalance: BigNumber; - onError: () => void; + isOutdatedWrappedEther: boolean; + onConversionSuccessful?: () => void; + isDisabled?: boolean; } interface EthWethConversionButtonState { @@ -25,8 +28,14 @@ interface EthWethConversionButtonState { isEthConversionHappening: boolean; } -export class EthWethConversionButton extends - React.Component<EthWethConversionButtonProps, EthWethConversionButtonState> { +export class EthWethConversionButton extends React.Component< + EthWethConversionButtonProps, + EthWethConversionButtonState +> { + public static defaultProps: Partial<EthWethConversionButtonProps> = { + isDisabled: false, + onConversionSuccessful: _.noop, + }; public constructor(props: EthWethConversionButtonProps) { super(props); this.state = { @@ -35,20 +44,30 @@ export class EthWethConversionButton extends }; } public render() { - const labelStyle = this.state.isEthConversionHappening ? {fontSize: 10} : {}; + const labelStyle = this.state.isEthConversionHappening ? { fontSize: 10 } : {}; + let callToActionLabel; + let inProgressLabel; + if (this.props.direction === Side.Deposit) { + callToActionLabel = 'Wrap'; + inProgressLabel = 'Wrapping...'; + } else { + callToActionLabel = 'Unwrap'; + inProgressLabel = 'Unwrapping...'; + } return ( <div> <RaisedButton - style={{width: '100%'}} + style={{ width: '100%' }} labelStyle={labelStyle} - disabled={this.state.isEthConversionHappening} - label={this.state.isEthConversionHappening ? 'Converting...' : 'Convert'} - onClick={this.toggleConversionDialog.bind(this)} + disabled={this.props.isDisabled || this.state.isEthConversionHappening} + label={this.state.isEthConversionHappening ? inProgressLabel : callToActionLabel} + onClick={this._toggleConversionDialog.bind(this)} /> <EthWethConversionDialog + direction={this.props.direction} isOpen={this.state.isEthConversionDialogVisible} - onComplete={this.onConversionAmountSelectedAsync.bind(this)} - onCancelled={this.toggleConversionDialog.bind(this)} + onComplete={this._onConversionAmountSelectedAsync.bind(this)} + onCancelled={this._toggleConversionDialog.bind(this)} etherBalance={this.props.userEtherBalance} token={this.props.ethToken} tokenState={this.props.ethTokenState} @@ -56,41 +75,48 @@ export class EthWethConversionButton extends </div> ); } - private toggleConversionDialog() { + private _toggleConversionDialog() { this.setState({ isEthConversionDialogVisible: !this.state.isEthConversionDialogVisible, }); } - private async onConversionAmountSelectedAsync(direction: Side, value: BigNumber) { + private async _onConversionAmountSelectedAsync(direction: Side, value: BigNumber) { this.setState({ isEthConversionHappening: true, }); - this.toggleConversionDialog(); + 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(value); - const ethAmount = ZeroEx.toUnitAmount(value, constants.ETH_DECIMAL_PLACES); - this.props.dispatcher.showFlashMessage(`Successfully converted ${ethAmount.toString()} ETH to WETH`); + 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(value); + await this.props.blockchain.convertWrappedEthTokensToEthAsync(token.address, value); const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals); - this.props.dispatcher.showFlashMessage(`Successfully converted ${tokenAmount.toString()} WETH to ETH`); + this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`); balance = balance.minus(value); } - this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + if (!this.props.isOutdatedWrappedEther) { + this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); + } + this.props.onConversionSuccessful(); } catch (err) { - const errMsg = '' + err; - if (_.includes(errMsg, BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES)) { + const errMsg = `${err}`; + if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); } else if (!_.includes(errMsg, 'User denied transaction')) { utils.consoleLog(`Unexpected error encountered: ${err}`); utils.consoleLog(err.stack); + const errorMsg = + direction === Side.Deposit + ? 'Failed to wrap your ETH. Please try again.' + : 'Failed to unwrap your WETH. Please try again.'; + this.props.dispatcher.showFlashMessage(errorMsg); await errorReporter.reportAsync(err); - this.props.onError(); } } this.setState({ diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx new file mode 100644 index 000000000..d074ec787 --- /dev/null +++ b/packages/website/ts/components/eth_wrappers.tsx @@ -0,0 +1,374 @@ +import { ZeroEx } from '0x.js'; +import { BigNumber } from '@0xproject/utils'; +import * as _ from 'lodash'; +import Divider from 'material-ui/Divider'; +import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table'; +import * as moment from 'moment'; +import * as React from 'react'; +import ReactTooltip = require('react-tooltip'); +import { Blockchain } from 'ts/blockchain'; +import { EthWethConversionButton } from 'ts/components/eth_weth_conversion_button'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { + EtherscanLinkSuffixes, + OutdatedWrappedEtherByNetworkId, + Side, + Token, + TokenByAddress, + TokenState, + TokenStateByAddress, +} from 'ts/types'; +import { colors } from 'ts/utils/colors'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; +import { utils } from 'ts/utils/utils'; + +const PRECISION = 5; +const DATE_FORMAT = 'D/M/YY'; +const ICON_DIMENSION = 40; +const ETHER_ICON_PATH = '/images/ether.png'; +const OUTDATED_WETH_ICON_PATH = '/images/wrapped_eth_gray.png'; + +interface OutdatedWETHAddressToIsStateLoaded { + [address: string]: boolean; +} +interface OutdatedWETHStateByAddress { + [address: string]: TokenState; +} + +interface EthWrappersProps { + networkId: number; + blockchain: Blockchain; + dispatcher: Dispatcher; + tokenByAddress: TokenByAddress; + tokenStateByAddress: TokenStateByAddress; + userAddress: string; + userEtherBalance: BigNumber; +} + +interface EthWrappersState { + outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded; + outdatedWETHStateByAddress: OutdatedWETHStateByAddress; +} + +export class EthWrappers extends React.Component<EthWrappersProps, EthWrappersState> { + constructor(props: EthWrappersProps) { + super(props); + const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); + const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; + const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; + _.each(outdatedWETHAddresses, outdatedWETHAddress => { + outdatedWETHAddressToIsStateLoaded[outdatedWETHAddress] = false; + outdatedWETHStateByAddress[outdatedWETHAddress] = { + balance: new BigNumber(0), + allowance: new BigNumber(0), + }; + }); + this.state = { + outdatedWETHAddressToIsStateLoaded, + outdatedWETHStateByAddress, + }; + } + public componentDidMount() { + window.scrollTo(0, 0); + // tslint:disable-next-line:no-floating-promises + this._fetchOutdatedWETHStateAsync(); + } + public render() { + const tokens = _.values(this.props.tokenByAddress); + const etherToken = _.find(tokens, { symbol: 'WETH' }); + const etherTokenState = this.props.tokenStateByAddress[etherToken.address]; + const wethBalance = ZeroEx.toUnitAmount(etherTokenState.balance, constants.DECIMAL_PLACES_ETH); + const isBidirectional = true; + const etherscanUrl = utils.getEtherScanLinkIfExists( + etherToken.address, + this.props.networkId, + EtherscanLinkSuffixes.Address, + ); + const tokenLabel = this._renderToken('Wrapped Ether', etherToken.address, configs.ICON_URL_BY_SYMBOL.WETH); + return ( + <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> + <div className="relative"> + <h3>ETH Wrapper</h3> + <div className="absolute" style={{ top: 0, right: 0 }}> + <a target="_blank" href={constants.URL_WETH_IO} style={{ color: colors.grey }}> + <div className="flex"> + <div>About Wrapped ETH</div> + <div className="pl1"> + <i className="zmdi zmdi-open-in-new" /> + </div> + </div> + </a> + </div> + </div> + <Divider /> + <div> + <div className="py2">Wrap ETH into an ERC20-compliant Ether token. 1 ETH = 1 WETH.</div> + <div> + <Table selectable={false} style={{ backgroundColor: colors.grey50 }}> + <TableHeader displaySelectAll={false} adjustForCheckbox={false}> + <TableRow> + <TableHeaderColumn>ETH Token</TableHeaderColumn> + <TableHeaderColumn>Balance</TableHeaderColumn> + <TableHeaderColumn className="center"> + {this._renderActionColumnTitle(isBidirectional)} + </TableHeaderColumn> + </TableRow> + </TableHeader> + <TableBody displayRowCheckbox={false}> + <TableRow key="ETH"> + <TableRowColumn className="py1"> + <div className="flex"> + <img + style={{ + width: ICON_DIMENSION, + height: ICON_DIMENSION, + }} + src={ETHER_ICON_PATH} + /> + <div className="ml2 sm-hide xs-hide" style={{ marginTop: 12 }}> + ETH + </div> + </div> + </TableRowColumn> + <TableRowColumn> + {this.props.userEtherBalance.toFixed(PRECISION)} ETH + </TableRowColumn> + <TableRowColumn> + <EthWethConversionButton + isOutdatedWrappedEther={false} + direction={Side.Deposit} + ethToken={etherToken} + ethTokenState={etherTokenState} + dispatcher={this.props.dispatcher} + blockchain={this.props.blockchain} + userEtherBalance={this.props.userEtherBalance} + /> + </TableRowColumn> + </TableRow> + <TableRow key="WETH"> + <TableRowColumn className="py1"> + {this._renderTokenLink(tokenLabel, etherscanUrl)} + </TableRowColumn> + <TableRowColumn>{wethBalance.toFixed(PRECISION)} WETH</TableRowColumn> + <TableRowColumn> + <EthWethConversionButton + isOutdatedWrappedEther={false} + direction={Side.Receive} + ethToken={etherToken} + ethTokenState={etherTokenState} + dispatcher={this.props.dispatcher} + blockchain={this.props.blockchain} + userEtherBalance={this.props.userEtherBalance} + /> + </TableRowColumn> + </TableRow> + </TableBody> + </Table> + </div> + </div> + <div> + <h4>Outdated WETH</h4> + <Divider /> + <div className="pt2" style={{ lineHeight: 1.5 }}> + The{' '} + <a href="https://blog.0xproject.com/canonical-weth-a9aa7d0279dd" target="_blank"> + canonical WETH + </a>{' '} + contract is updated when necessary. Unwrap outdated WETH in order to
retrieve your ETH and move + it to the updated WETH token. + </div> + <div> + <Table selectable={false} style={{ backgroundColor: colors.grey50 }}> + <TableHeader displaySelectAll={false} adjustForCheckbox={false}> + <TableRow> + <TableHeaderColumn>WETH Version</TableHeaderColumn> + <TableHeaderColumn>Balance</TableHeaderColumn> + <TableHeaderColumn className="center"> + {this._renderActionColumnTitle(!isBidirectional)} + </TableHeaderColumn> + </TableRow> + </TableHeader> + <TableBody displayRowCheckbox={false}> + {this._renderOutdatedWeths(etherToken, etherTokenState)} + </TableBody> + </Table> + </div> + </div> + </div> + ); + } + private _renderActionColumnTitle(isBidirectional: boolean) { + let iconClass = 'zmdi-long-arrow-right'; + let leftSymbol = 'WETH'; + let rightSymbol = 'ETH'; + if (isBidirectional) { + iconClass = 'zmdi-swap'; + leftSymbol = 'ETH'; + rightSymbol = 'WETH'; + } + return ( + <div className="flex mx-auto" style={{ width: 85 }}> + <div style={{ paddingTop: 3 }}>{leftSymbol}</div> + <div className="px1"> + <i style={{ fontSize: 18 }} className={`zmdi ${iconClass}`} /> + </div> + <div style={{ paddingTop: 3 }}>{rightSymbol}</div> + </div> + ); + } + private _renderOutdatedWeths(etherToken: Token, etherTokenState: TokenState) { + const rows = _.map( + configs.OUTDATED_WRAPPED_ETHERS, + (outdatedWETHByNetworkId: OutdatedWrappedEtherByNetworkId) => { + const outdatedWETHIfExists = outdatedWETHByNetworkId[this.props.networkId]; + if (_.isUndefined(outdatedWETHIfExists)) { + return null; // noop + } + const timestampMsRange = outdatedWETHIfExists.timestampMsRange; + let dateRange: string; + if (!_.isUndefined(timestampMsRange)) { + const startMoment = moment(timestampMsRange.startTimestampMs); + const endMoment = moment(timestampMsRange.endTimestampMs); + dateRange = `${startMoment.format(DATE_FORMAT)}-${endMoment.format(DATE_FORMAT)}`; + } else { + dateRange = '-'; + } + const outdatedEtherToken = { + ...etherToken, + address: outdatedWETHIfExists.address, + }; + const isStateLoaded = this.state.outdatedWETHAddressToIsStateLoaded[outdatedWETHIfExists.address]; + const outdatedEtherTokenState = this.state.outdatedWETHStateByAddress[outdatedWETHIfExists.address]; + const balanceInEthIfExists = isStateLoaded + ? ZeroEx.toUnitAmount(outdatedEtherTokenState.balance, constants.DECIMAL_PLACES_ETH).toFixed( + PRECISION, + ) + : undefined; + const onConversionSuccessful = this._onOutdatedConversionSuccessfulAsync.bind( + this, + outdatedWETHIfExists.address, + ); + const etherscanUrl = utils.getEtherScanLinkIfExists( + outdatedWETHIfExists.address, + this.props.networkId, + EtherscanLinkSuffixes.Address, + ); + const tokenLabel = this._renderToken(dateRange, outdatedEtherToken.address, OUTDATED_WETH_ICON_PATH); + return ( + <TableRow key={`weth-${outdatedWETHIfExists.address}`}> + <TableRowColumn className="py1"> + {this._renderTokenLink(tokenLabel, etherscanUrl)} + </TableRowColumn> + <TableRowColumn> + {isStateLoaded ? ( + `${balanceInEthIfExists} WETH` + ) : ( + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + )} + </TableRowColumn> + <TableRowColumn> + <EthWethConversionButton + isDisabled={!isStateLoaded} + isOutdatedWrappedEther={true} + direction={Side.Receive} + ethToken={outdatedEtherToken} + ethTokenState={outdatedEtherTokenState} + dispatcher={this.props.dispatcher} + blockchain={this.props.blockchain} + userEtherBalance={this.props.userEtherBalance} + onConversionSuccessful={onConversionSuccessful} + /> + </TableRowColumn> + </TableRow> + ); + }, + ); + return rows; + } + private _renderTokenLink(tokenLabel: React.ReactNode, etherscanUrl: string) { + return ( + <span> + {_.isUndefined(etherscanUrl) ? ( + tokenLabel + ) : ( + <a href={etherscanUrl} target="_blank" style={{ textDecoration: 'none' }}> + {tokenLabel} + </a> + )} + </span> + ); + } + private _renderToken(name: string, address: string, imgPath: string) { + const tooltipId = `tooltip-${address}`; + return ( + <div className="flex"> + <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={imgPath} /> + <div className="ml2 sm-hide xs-hide" style={{ marginTop: 12 }}> + <span data-tip={true} data-for={tooltipId}> + {name} + </span> + <ReactTooltip id={tooltipId}>{address}</ReactTooltip> + </div> + </div> + ); + } + private async _onOutdatedConversionSuccessfulAsync(outdatedWETHAddress: string) { + this.setState({ + outdatedWETHAddressToIsStateLoaded: { + ...this.state.outdatedWETHAddressToIsStateLoaded, + [outdatedWETHAddress]: false, + }, + }); + const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + outdatedWETHAddress, + ); + this.setState({ + outdatedWETHAddressToIsStateLoaded: { + ...this.state.outdatedWETHAddressToIsStateLoaded, + [outdatedWETHAddress]: true, + }, + outdatedWETHStateByAddress: { + ...this.state.outdatedWETHStateByAddress, + [outdatedWETHAddress]: { + balance, + allowance, + }, + }, + }); + } + private async _fetchOutdatedWETHStateAsync() { + const outdatedWETHAddresses = this._getOutdatedWETHAddresses(); + const outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded = {}; + const outdatedWETHStateByAddress: OutdatedWETHStateByAddress = {}; + for (const address of outdatedWETHAddresses) { + const [balance, allowance] = await this.props.blockchain.getTokenBalanceAndAllowanceAsync( + this.props.userAddress, + address, + ); + outdatedWETHStateByAddress[address] = { + balance, + allowance, + }; + outdatedWETHAddressToIsStateLoaded[address] = true; + } + this.setState({ + outdatedWETHStateByAddress, + outdatedWETHAddressToIsStateLoaded, + }); + } + private _getOutdatedWETHAddresses(): string[] { + const outdatedWETHAddresses = _.compact( + _.map(configs.OUTDATED_WRAPPED_ETHERS, outdatedWrappedEtherByNetwork => { + const outdatedWrappedEtherIfExists = outdatedWrappedEtherByNetwork[this.props.networkId]; + if (_.isUndefined(outdatedWrappedEtherIfExists)) { + return undefined; + } + const address = outdatedWrappedEtherIfExists.address; + return address; + }), + ); + return outdatedWETHAddresses; + } +} // tslint:disable:max-file-line-count diff --git a/packages/website/ts/components/fill_order.tsx b/packages/website/ts/components/fill_order.tsx index 388c72d8e..1a150e9ee 100644 --- a/packages/website/ts/components/fill_order.tsx +++ b/packages/website/ts/components/fill_order.tsx @@ -1,46 +1,29 @@ -import {Order as ZeroExOrder, ZeroEx} from '0x.js'; +import { Order as ZeroExOrder, ZeroEx } from '0x.js'; +import { BigNumber } from '@0xproject/utils'; import * as accounting from 'accounting'; -import BigNumber from 'bignumber.js'; import * as _ from 'lodash'; -import {Card, CardHeader, CardText} from 'material-ui/Card'; +import { Card, CardHeader, CardText } from 'material-ui/Card'; import Divider from 'material-ui/Divider'; -import Paper from 'material-ui/Paper'; import RaisedButton from 'material-ui/RaisedButton'; -import TextField from 'material-ui/TextField'; -import * as moment from 'moment'; import * as React from 'react'; -import {Link} from 'react-router-dom'; -import {Blockchain} from 'ts/blockchain'; -import {TrackTokenConfirmationDialog} from 'ts/components/dialogs/track_token_confirmation_dialog'; -import {FillOrderJSON} from 'ts/components/fill_order_json'; -import {FillWarningDialog} from 'ts/components/fill_warning_dialog'; -import {TokenAmountInput} from 'ts/components/inputs/token_amount_input'; -import {Alert} from 'ts/components/ui/alert'; -import {EthereumAddress} from 'ts/components/ui/ethereum_address'; -import {Identicon} from 'ts/components/ui/identicon'; -import {VisualOrder} from 'ts/components/visual_order'; -import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; -import {Dispatcher} from 'ts/redux/dispatcher'; -import {orderSchema} from 'ts/schemas/order_schema'; -import {SchemaValidator} from 'ts/schemas/validator'; -import { - AlertTypes, - BlockchainErrs, - ContractResponse, - ExchangeContractErrs, - Order, - OrderToken, - Side, - Token, - TokenByAddress, - TokenStateByAddress, - WebsitePaths, -} from 'ts/types'; -import {constants} from 'ts/utils/constants'; -import {errorReporter} from 'ts/utils/error_reporter'; -import {utils} from 'ts/utils/utils'; - -const CUSTOM_LIGHT_GRAY = '#BBBBBB'; +import { Link } from 'react-router-dom'; +import { Blockchain } from 'ts/blockchain'; +import { TrackTokenConfirmationDialog } from 'ts/components/dialogs/track_token_confirmation_dialog'; +import { FillOrderJSON } from 'ts/components/fill_order_json'; +import { FillWarningDialog } from 'ts/components/fill_warning_dialog'; +import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; +import { Alert } from 'ts/components/ui/alert'; +import { EthereumAddress } from 'ts/components/ui/ethereum_address'; +import { Identicon } from 'ts/components/ui/identicon'; +import { VisualOrder } from 'ts/components/visual_order'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { orderSchema } from 'ts/schemas/order_schema'; +import { SchemaValidator } from 'ts/schemas/validator'; +import { AlertTypes, BlockchainErrs, Order, Token, TokenByAddress, TokenStateByAddress, WebsitePaths } from 'ts/types'; +import { colors } from 'ts/utils/colors'; +import { constants } from 'ts/utils/constants'; +import { errorReporter } from 'ts/utils/error_reporter'; +import { utils } from 'ts/utils/utils'; interface FillOrderProps { blockchain: Blockchain; @@ -75,7 +58,7 @@ interface FillOrderState { } export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { - private validator: SchemaValidator; + private _validator: SchemaValidator; constructor(props: FillOrderProps) { super(props); this.state = { @@ -96,12 +79,12 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { isConfirmingTokenTracking: false, tokensToTrack: [], }; - this.validator = new SchemaValidator(); + this._validator = new SchemaValidator(); } public componentWillMount() { if (!_.isEmpty(this.state.orderJSON)) { // tslint:disable-next-line:no-floating-promises - this.validateFillOrderFireAndForgetAsync(this.state.orderJSON); + this._validateFillOrderFireAndForgetAsync(this.state.orderJSON); } } public componentDidMount() { @@ -109,57 +92,57 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { } public render() { return ( - <div className="clearfix lg-px4 md-px4 sm-px2" style={{minHeight: 600}}> + <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}> <h3>Fill an order</h3> <Divider /> <div> - {!this.props.isOrderInUrl && + {!this.props.isOrderInUrl && ( <div> - <div className="pt2 pb2"> - Paste an order JSON snippet below to begin - </div> + <div className="pt2 pb2">Paste an order JSON snippet below to begin</div> <div className="pb2">Order JSON</div> <FillOrderJSON blockchain={this.props.blockchain} tokenByAddress={this.props.tokenByAddress} networkId={this.props.networkId} orderJSON={this.state.orderJSON} - onFillOrderJSONChanged={this.onFillOrderJSONChanged.bind(this)} + onFillOrderJSONChanged={this._onFillOrderJSONChanged.bind(this)} /> - {this.renderOrderJsonNotices()} + {this._renderOrderJsonNotices()} </div> - } + )} <div> - {!_.isUndefined(this.state.parsedOrder) && this.state.didOrderValidationRun - && this.state.areAllInvolvedTokensTracked && - this.renderVisualOrder() - } + {!_.isUndefined(this.state.parsedOrder) && + this.state.didOrderValidationRun && + this.state.areAllInvolvedTokensTracked && + this._renderVisualOrder()} </div> - {this.props.isOrderInUrl && + {this.props.isOrderInUrl && ( <div className="pt2"> - <Card style={{boxShadow: 'none', backgroundColor: 'none', border: '1px solid #eceaea'}}> - <CardHeader - title="Order JSON" - actAsExpander={true} - showExpandableButton={true} - /> + <Card + style={{ + boxShadow: 'none', + backgroundColor: 'none', + border: '1px solid #eceaea', + }} + > + <CardHeader title="Order JSON" actAsExpander={true} showExpandableButton={true} /> <CardText expandable={true}> <FillOrderJSON blockchain={this.props.blockchain} tokenByAddress={this.props.tokenByAddress} networkId={this.props.networkId} orderJSON={this.state.orderJSON} - onFillOrderJSONChanged={this.onFillOrderJSONChanged.bind(this)} + onFillOrderJSONChanged={this._onFillOrderJSONChanged.bind(this)} /> </CardText> </Card> - {this.renderOrderJsonNotices()} + {this._renderOrderJsonNotices()} </div> - } + )} </div> <FillWarningDialog isOpen={this.state.isFillWarningDialogOpen} - onToggleDialog={this.onFillWarningClosed.bind(this)} + onToggleDialog={this._onFillWarningClosed.bind(this)} /> <TrackTokenConfirmationDialog userAddress={this.props.userAddress} @@ -169,29 +152,30 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { dispatcher={this.props.dispatcher} tokens={this.state.tokensToTrack} isOpen={this.state.isConfirmingTokenTracking} - onToggleDialog={this.onToggleTrackConfirmDialog.bind(this)} + onToggleDialog={this._onToggleTrackConfirmDialog.bind(this)} /> </div> ); } - private renderOrderJsonNotices() { + private _renderOrderJsonNotices() { return ( <div> - {!_.isUndefined(this.props.initialOrder) && !this.state.didOrderValidationRun && - <div className="pt2"> - <span className="pr1"> - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - </span> - <span>Validating order...</span> - </div> - } - {!_.isEmpty(this.state.orderJSONErrMsg) && + {!_.isUndefined(this.props.initialOrder) && + !this.state.didOrderValidationRun && ( + <div className="pt2"> + <span className="pr1"> + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + </span> + <span>Validating order...</span> + </div> + )} + {!_.isEmpty(this.state.orderJSONErrMsg) && ( <Alert type={AlertTypes.ERROR} message={this.state.orderJSONErrMsg} /> - } + )} </div> ); } - private renderVisualOrder() { + private _renderVisualOrder() { const takerTokenAddress = this.state.parsedOrder.taker.token.address; const takerToken = this.props.tokenByAddress[takerTokenAddress]; const orderTakerAmount = new BigNumber(this.state.parsedOrder.taker.amount); @@ -212,32 +196,30 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { amount: this.props.orderFillAmount, symbol: takerToken.symbol, }; - const orderTaker = !_.isEmpty(this.state.parsedOrder.taker.address) ? this.state.parsedOrder.taker.address : - this.props.userAddress; + const orderTaker = !_.isEmpty(this.state.parsedOrder.taker.address) + ? this.state.parsedOrder.taker.address + : this.props.userAddress; const parsedOrderExpiration = new BigNumber(this.state.parsedOrder.expiration); const exchangeRate = orderMakerAmount.div(orderTakerAmount); let orderReceiveAmount = 0; if (!_.isUndefined(this.props.orderFillAmount)) { const orderReceiveAmountBigNumber = exchangeRate.mul(this.props.orderFillAmount); - orderReceiveAmount = this.formatCurrencyAmount(orderReceiveAmountBigNumber, makerToken.decimals); + orderReceiveAmount = this._formatCurrencyAmount(orderReceiveAmountBigNumber, makerToken.decimals); } - const isUserMaker = !_.isUndefined(this.state.parsedOrder) && - this.state.parsedOrder.maker.address === this.props.userAddress; + const isUserMaker = + !_.isUndefined(this.state.parsedOrder) && this.state.parsedOrder.maker.address === this.props.userAddress; const expiryDate = utils.convertToReadableDateTimeFromUnixTimestamp(parsedOrderExpiration); return ( <div className="pt3 pb1"> - <div className="clearfix pb2" style={{width: '100%'}}> + <div className="clearfix pb2" style={{ width: '100%' }}> <div className="inline left">Order details</div> - <div className="inline right" style={{minWidth: 208}}> - <div className="col col-4 pl2" style={{color: '#BEBEBE'}}> + <div className="inline right" style={{ minWidth: 208 }}> + <div className="col col-4 pl2" style={{ color: colors.grey }}> Maker: </div> <div className="col col-2 pr1"> - <Identicon - address={this.state.parsedOrder.maker.address} - diameter={23} - /> + <Identicon address={this.state.parsedOrder.maker.address} diameter={23} /> </div> <div className="col col-6"> <EthereumAddress @@ -261,123 +243,112 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { isMakerTokenAddressInRegistry={this.state.isMakerTokenAddressInRegistry} isTakerTokenAddressInRegistry={this.state.isTakerTokenAddressInRegistry} /> - <div className="center pt3 pb2"> - Expires: {expiryDate} UTC - </div> + <div className="center pt3 pb2">Expires: {expiryDate} UTC</div> </div> </div> - {!isUserMaker && - <div className="clearfix mx-auto" style={{width: 315, height: 108}}> - <div className="col col-7" style={{maxWidth: 235}}> - <TokenAmountInput - label="Fill amount" - onChange={this.onFillAmountChange.bind(this)} - shouldShowIncompleteErrs={false} - token={fillToken} - tokenState={fillTokenState} - amount={fillAssetToken.amount} - shouldCheckBalance={true} - shouldCheckAllowance={true} - /> - </div> - <div - className="col col-5 pl1" - style={{color: CUSTOM_LIGHT_GRAY, paddingTop: 39}} - > - = {accounting.formatNumber(orderReceiveAmount, 6)} {makerToken.symbol} - </div> + {!isUserMaker && ( + <div className="clearfix mx-auto relative" style={{ width: 235, height: 108 }}> + <TokenAmountInput + label="Fill amount" + onChange={this._onFillAmountChange.bind(this)} + shouldShowIncompleteErrs={false} + token={fillToken} + tokenState={fillTokenState} + amount={fillAssetToken.amount} + shouldCheckBalance={true} + shouldCheckAllowance={true} + /> + <div + className="absolute sm-hide xs-hide" + style={{ + color: colors.grey400, + right: -247, + top: 39, + width: 242, + }} + > + = {accounting.formatNumber(orderReceiveAmount, 6)} {makerToken.symbol} + </div> </div> - } + )} <div> - {isUserMaker ? + {isUserMaker ? ( <div> <RaisedButton - style={{width: '100%'}} + style={{ width: '100%' }} disabled={this.state.isCancelling} label={this.state.isCancelling ? 'Cancelling order...' : 'Cancel order'} - onClick={this.onCancelOrderClickFireAndForgetAsync.bind(this)} + onClick={this._onCancelOrderClickFireAndForgetAsync.bind(this)} /> - {this.state.didCancelOrderSucceed && - <Alert - type={AlertTypes.SUCCESS} - message={this.renderCancelSuccessMsg()} - /> - } - </div> : + {this.state.didCancelOrderSucceed && ( + <Alert type={AlertTypes.SUCCESS} message={this._renderCancelSuccessMsg()} /> + )} + </div> + ) : ( <div> <RaisedButton - style={{width: '100%'}} + style={{ width: '100%' }} disabled={this.state.isFilling} label={this.state.isFilling ? 'Filling order...' : 'Fill order'} - onClick={this.onFillOrderClick.bind(this)} + onClick={this._onFillOrderClick.bind(this)} /> - {!_.isEmpty(this.state.globalErrMsg) && + {!_.isEmpty(this.state.globalErrMsg) && ( <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} /> - } - {this.state.didFillOrderSucceed && - <Alert - type={AlertTypes.SUCCESS} - message={this.renderFillSuccessMsg()} - /> - } + )} + {this.state.didFillOrderSucceed && ( + <Alert type={AlertTypes.SUCCESS} message={this._renderFillSuccessMsg()} /> + )} </div> - } + )} </div> </div> ); } - private renderFillSuccessMsg() { + private _renderFillSuccessMsg() { return ( <div> Order successfully filled. See the trade details in your{' '} - <Link - to={`${WebsitePaths.Portal}/trades`} - style={{color: 'white'}} - > + <Link to={`${WebsitePaths.Portal}/trades`} style={{ color: colors.white }}> trade history </Link> </div> ); } - private renderCancelSuccessMsg() { - return ( - <div> - Order successfully cancelled. - </div> - ); + private _renderCancelSuccessMsg() { + return <div>Order successfully cancelled.</div>; } - private onFillOrderClick() { + private _onFillOrderClick() { if (!this.state.isMakerTokenAddressInRegistry || !this.state.isTakerTokenAddressInRegistry) { this.setState({ isFillWarningDialogOpen: true, }); } else { // tslint:disable-next-line:no-floating-promises - this.onFillOrderClickFireAndForgetAsync(); + this._onFillOrderClickFireAndForgetAsync(); } } - private onFillWarningClosed(didUserCancel: boolean) { + private _onFillWarningClosed(didUserCancel: boolean) { this.setState({ isFillWarningDialogOpen: false, }); if (!didUserCancel) { // tslint:disable-next-line:no-floating-promises - this.onFillOrderClickFireAndForgetAsync(); + this._onFillOrderClickFireAndForgetAsync(); } } - private onFillAmountChange(isValid: boolean, amount?: BigNumber) { + private _onFillAmountChange(isValid: boolean, amount?: BigNumber) { this.props.dispatcher.updateOrderFillAmount(amount); } - private onFillOrderJSONChanged(event: any) { + private _onFillOrderJSONChanged(event: any) { const orderJSON = event.target.value; this.setState({ didOrderValidationRun: _.isEmpty(orderJSON) && _.isEmpty(this.state.orderJSONErrMsg), didFillOrderSucceed: false, }); // tslint:disable-next-line:no-floating-promises - this.validateFillOrderFireAndForgetAsync(orderJSON); + this._validateFillOrderFireAndForgetAsync(orderJSON); } - private async checkForUntrackedTokensAndAskToAdd() { + private async _checkForUntrackedTokensAndAskToAdd() { if (!_.isEmpty(this.state.orderJSONErrMsg)) { return; } @@ -389,22 +360,24 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { const isUnseenMakerToken = _.isUndefined(makerTokenIfExists); const isMakerTokenTracked = !_.isUndefined(makerTokenIfExists) && makerTokenIfExists.isTracked; if (isUnseenMakerToken) { - tokensToTrack.push(_.assign({}, this.state.parsedOrder.maker.token, { + tokensToTrack.push({ + ...this.state.parsedOrder.maker.token, iconUrl: undefined, isTracked: false, isRegistered: false, - })); + }); } else if (!isMakerTokenTracked) { tokensToTrack.push(makerTokenIfExists); } const isUnseenTakerToken = _.isUndefined(takerTokenIfExists); const isTakerTokenTracked = !_.isUndefined(takerTokenIfExists) && takerTokenIfExists.isTracked; if (isUnseenTakerToken) { - tokensToTrack.push(_.assign({}, this.state.parsedOrder.taker.token, { + tokensToTrack.push({ + ...this.state.parsedOrder.taker.token, iconUrl: undefined, isTracked: false, isRegistered: false, - })); + }); } else if (!isTakerTokenTracked) { tokensToTrack.push(takerTokenIfExists); } @@ -419,12 +392,12 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { }); } } - private async validateFillOrderFireAndForgetAsync(orderJSON: string) { + private async _validateFillOrderFireAndForgetAsync(orderJSON: string) { let orderJSONErrMsg = ''; let parsedOrder: Order; try { const order = JSON.parse(orderJSON); - const validationResult = this.validator.validate(order, orderSchema); + const validationResult = this._validator.validate(order, orderSchema); if (validationResult.errors.length > 0) { orderJSONErrMsg = 'Submitted order JSON is not a valid order'; utils.consoleLog(`Unexpected order JSON validation error: ${validationResult.errors.join(', ')}`); @@ -517,10 +490,10 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { unavailableTakerAmount, }); - await this.checkForUntrackedTokensAndAskToAdd(); + await this._checkForUntrackedTokensAndAskToAdd(); } - private async onFillOrderClickFireAndForgetAsync(): Promise<void> { - if (!_.isEmpty(this.props.blockchainErr) || _.isEmpty(this.props.userAddress)) { + private async _onFillOrderClickFireAndForgetAsync(): Promise<void> { + if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); return; } @@ -531,8 +504,6 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { }); const parsedOrder = this.state.parsedOrder; - const orderHash = parsedOrder.signature.hash; - const unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); const takerFillAmount = this.props.orderFillAmount; if (_.isUndefined(this.props.userAddress)) { @@ -565,11 +536,12 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { if (_.isEmpty(globalErrMsg)) { try { await this.props.blockchain.validateFillOrderThrowIfInvalidAsync( - signedOrder, takerFillAmount, this.props.userAddress); - } catch (err) { - globalErrMsg = utils.zeroExErrToHumanReadableErrMsg( - err.message, parsedOrder.taker.address, + signedOrder, + takerFillAmount, + this.props.userAddress, ); + } catch (err) { + globalErrMsg = utils.zeroExErrToHumanReadableErrMsg(err.message, parsedOrder.taker.address); } } if (!_.isEmpty(globalErrMsg)) { @@ -581,7 +553,8 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { } try { const orderFilledAmount: BigNumber = await this.props.blockchain.fillOrderAsync( - signedOrder, this.props.orderFillAmount, + signedOrder, + this.props.orderFillAmount, ); // After fill completes, let's update the token balances const makerToken = this.props.tokenByAddress[parsedOrder.maker.token.address]; @@ -605,15 +578,15 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { } globalErrMsg = 'Failed to fill order, please refresh and try again'; utils.consoleLog(`${err}`); - await errorReporter.reportAsync(err); this.setState({ globalErrMsg, }); + await errorReporter.reportAsync(err); return; } } - private async onCancelOrderClickFireAndForgetAsync(): Promise<void> { - if (!_.isEmpty(this.props.blockchainErr) || _.isEmpty(this.props.userAddress)) { + private async _onCancelOrderClickFireAndForgetAsync(): Promise<void> { + if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); return; } @@ -655,8 +628,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { const unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash); const availableTakerTokenAmount = takerTokenAmount.minus(unavailableTakerAmount); try { - await this.props.blockchain.validateCancelOrderThrowIfInvalidAsync( - signedOrder, availableTakerTokenAmount); + await this.props.blockchain.validateCancelOrderThrowIfInvalidAsync(signedOrder, availableTakerTokenAmount); } catch (err) { globalErrMsg = utils.zeroExErrToHumanReadableErrMsg(err.message, parsedOrder.taker.address); } @@ -668,9 +640,7 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { return; } try { - await this.props.blockchain.cancelOrderAsync( - signedOrder, availableTakerTokenAmount, - ); + await this.props.blockchain.cancelOrderAsync(signedOrder, availableTakerTokenAmount); this.setState({ isCancelling: false, didCancelOrderSucceed: true, @@ -688,19 +658,19 @@ export class FillOrder extends React.Component<FillOrderProps, FillOrderState> { } globalErrMsg = 'Failed to cancel order, please refresh and try again'; utils.consoleLog(`${err}`); - await errorReporter.reportAsync(err); this.setState({ globalErrMsg, }); + await errorReporter.reportAsync(err); return; } } - private formatCurrencyAmount(amount: BigNumber, decimals: number): number { + private _formatCurrencyAmount(amount: BigNumber, decimals: number): number { const unitAmount = ZeroEx.toUnitAmount(amount, decimals); const roundedUnitAmount = Math.round(unitAmount.toNumber() * 100000) / 100000; return roundedUnitAmount; } - private onToggleTrackConfirmDialog(didConfirmTokenTracking: boolean) { + private _onToggleTrackConfirmDialog(didConfirmTokenTracking: boolean) { if (!didConfirmTokenTracking) { this.setState({ orderJSON: '', diff --git a/packages/website/ts/components/fill_order_json.tsx b/packages/website/ts/components/fill_order_json.tsx index f4db1f74e..f8e43481a 100644 --- a/packages/website/ts/components/fill_order_json.tsx +++ b/packages/website/ts/components/fill_order_json.tsx @@ -1,13 +1,13 @@ -import {ZeroEx} from '0x.js'; -import BigNumber from 'bignumber.js'; +import { ZeroEx } from '0x.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Paper from 'material-ui/Paper'; import TextField from 'material-ui/TextField'; import * as React from 'react'; -import {Blockchain} from 'ts/blockchain'; -import {Side, TokenByAddress} from 'ts/types'; -import {constants} from 'ts/utils/constants'; -import {utils} from 'ts/utils/utils'; +import { Blockchain } from 'ts/blockchain'; +import { Side, TokenByAddress } from 'ts/types'; +import { constants } from 'ts/utils/constants'; +import { utils } from 'ts/utils/utils'; interface FillOrderJSONProps { blockchain: Blockchain; @@ -24,11 +24,11 @@ export class FillOrderJSON extends React.Component<FillOrderJSONProps, FillOrder const tokenAddresses = _.keys(this.props.tokenByAddress); const exchangeContract = this.props.blockchain.getExchangeContractAddressIfExists(); const hintSideToAssetToken = { - [Side.deposit]: { + [Side.Deposit]: { amount: new BigNumber(35), address: tokenAddresses[0], }, - [Side.receive]: { + [Side.Receive]: { amount: new BigNumber(89), address: tokenAddresses[1], }, @@ -41,17 +41,28 @@ export class FillOrderJSON extends React.Component<FillOrderJSONProps, FillOrder v: 27, }; const hintSalt = ZeroEx.generatePseudoRandomSalt(); - const hintOrder = utils.generateOrder(this.props.networkId, exchangeContract, hintSideToAssetToken, - hintOrderExpiryTimestamp, '', '', constants.MAKER_FEE, - constants.TAKER_FEE, constants.FEE_RECIPIENT_ADDRESS, - hintSignatureData, this.props.tokenByAddress, hintSalt); + const feeRecipient = constants.NULL_ADDRESS; + const hintOrder = utils.generateOrder( + this.props.networkId, + exchangeContract, + hintSideToAssetToken, + hintOrderExpiryTimestamp, + '', + '', + constants.MAKER_FEE, + constants.TAKER_FEE, + feeRecipient, + hintSignatureData, + this.props.tokenByAddress, + hintSalt, + ); const hintOrderJSON = `${JSON.stringify(hintOrder, null, '\t').substring(0, 500)}...`; return ( <div> - <Paper className="p1 overflow-hidden" style={{height: 164}}> + <Paper className="p1 overflow-hidden" style={{ height: 164 }}> <TextField id="orderJSON" - hintStyle={{bottom: 0, top: 0}} + hintStyle={{ bottom: 0, top: 0 }} fullWidth={true} value={this.props.orderJSON} onChange={this.props.onFillOrderJSONChanged.bind(this)} @@ -59,8 +70,8 @@ export class FillOrderJSON extends React.Component<FillOrderJSONProps, FillOrder multiLine={true} rows={6} rowsMax={6} - underlineStyle={{display: 'none'}} - textareaStyle={{marginTop: 0}} + underlineStyle={{ display: 'none' }} + textareaStyle={{ marginTop: 0 }} /> </Paper> </div> diff --git a/packages/website/ts/components/fill_warning_dialog.tsx b/packages/website/ts/components/fill_warning_dialog.tsx index 83e46cc8f..165d21b34 100644 --- a/packages/website/ts/components/fill_warning_dialog.tsx +++ b/packages/website/ts/components/fill_warning_dialog.tsx @@ -1,11 +1,11 @@ import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; +import { colors } from 'ts/utils/colors'; interface FillWarningDialogProps { isOpen: boolean; - onToggleDialog: () => void; + onToggleDialog: (didUserCancel: boolean) => void; } export function FillWarningDialog(props: FillWarningDialogProps) { @@ -13,7 +13,7 @@ export function FillWarningDialog(props: FillWarningDialogProps) { return ( <Dialog title="Warning" - titleStyle={{fontWeight: 100, color: colors.red500}} + titleStyle={{ fontWeight: 100, color: colors.red500 }} actions={[ <FlatButton key="fillWarningCancel" @@ -31,15 +31,11 @@ export function FillWarningDialog(props: FillWarningDialogProps) { autoScrollBodyContent={true} modal={true} > - <div className="pt2" style={{color: colors.grey700}}> + <div className="pt2" style={{ color: colors.grey700 }}> <div> - At least one of the tokens in this order was not found in the - token registry smart contract and may be counterfeit. It is your - responsibility to verify the token addresses on Etherscan ( - <a - href="https://0xproject.com/wiki#Verifying-Custom-Tokens" - target="_blank" - > + At least one of the tokens in this order was not found in the token registry smart contract and may + be counterfeit. It is your responsibility to verify the token addresses on Etherscan ( + <a href="https://0xproject.com/wiki#Verifying-Custom-Tokens" target="_blank"> See this how-to guide </a>) before filling an order. <b>This action may result in the loss of funds</b>. </div> diff --git a/packages/website/ts/components/flash_messages/token_send_completed.tsx b/packages/website/ts/components/flash_messages/token_send_completed.tsx index fef7520f6..18f371624 100644 --- a/packages/website/ts/components/flash_messages/token_send_completed.tsx +++ b/packages/website/ts/components/flash_messages/token_send_completed.tsx @@ -1,9 +1,10 @@ -import {ZeroEx} from '0x.js'; -import BigNumber from 'bignumber.js'; +import { ZeroEx } from '0x.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import * as React from 'react'; -import {Token} from 'ts/types'; -import {utils} from 'ts/utils/utils'; +import { Token } from 'ts/types'; +import { colors } from 'ts/utils/colors'; +import { utils } from 'ts/utils/utils'; interface TokenSendCompletedProps { etherScanLinkIfExists?: string; @@ -16,16 +17,11 @@ interface TokenSendCompletedState {} export class TokenSendCompleted extends React.Component<TokenSendCompletedProps, TokenSendCompletedState> { public render() { - const etherScanLink = !_.isUndefined(this.props.etherScanLinkIfExists) && - ( - <a - style={{color: 'white'}} - href={`${this.props.etherScanLinkIfExists}`} - target="_blank" - > - Verify on Etherscan - </a> - ); + const etherScanLink = !_.isUndefined(this.props.etherScanLinkIfExists) && ( + <a style={{ color: colors.white }} href={`${this.props.etherScanLinkIfExists}`} target="_blank"> + Verify on Etherscan + </a> + ); const amountInUnits = ZeroEx.toUnitAmount(this.props.amountInBaseUnits, this.props.token.decimals); const truncatedAddress = utils.getAddressBeginAndEnd(this.props.toAddress); return ( diff --git a/packages/website/ts/components/flash_messages/transaction_submitted.tsx b/packages/website/ts/components/flash_messages/transaction_submitted.tsx index cef3e2b1e..862e382dd 100644 --- a/packages/website/ts/components/flash_messages/transaction_submitted.tsx +++ b/packages/website/ts/components/flash_messages/transaction_submitted.tsx @@ -1,5 +1,6 @@ import * as _ from 'lodash'; import * as React from 'react'; +import { colors } from 'ts/utils/colors'; interface TransactionSubmittedProps { etherScanLinkIfExists?: string; @@ -15,11 +16,7 @@ export class TransactionSubmitted extends React.Component<TransactionSubmittedPr return ( <div> Transaction submitted to the network:{' '} - <a - style={{color: 'white'}} - href={`${this.props.etherScanLinkIfExists}`} - target="_blank" - > + <a style={{ color: colors.white }} href={`${this.props.etherScanLinkIfExists}`} target="_blank"> Verify on Etherscan </a> </div> diff --git a/packages/website/ts/components/footer.tsx b/packages/website/ts/components/footer.tsx index 5e3c0479a..a0f1a0c96 100644 --- a/packages/website/ts/components/footer.tsx +++ b/packages/website/ts/components/footer.tsx @@ -1,14 +1,9 @@ import * as _ from 'lodash'; import * as React from 'react'; -import { - Link, -} from 'react-router-dom'; -import {HashLink} from 'react-router-hash-link'; -import { - Link as ScrollLink, -} from 'react-scroll'; -import {Styles, WebsitePaths} from 'ts/types'; -import {constants} from 'ts/utils/constants'; +import { Link } from 'react-router-dom'; +import { WebsitePaths } from 'ts/types'; +import { colors } from 'ts/utils/colors'; +import { constants } from 'ts/utils/constants'; interface MenuItemsBySection { [sectionName: string]: FooterMenuItem[]; @@ -18,7 +13,6 @@ interface FooterMenuItem { title: string; path?: string; isExternal?: boolean; - fileName?: string; } enum Sections { @@ -28,9 +22,6 @@ enum Sections { } const ICON_DIMENSION = 16; -const CUSTOM_DARK_GRAY = '#393939'; -const CUSTOM_LIGHT_GRAY = '#CACACA'; -const CUSTOM_LIGHTEST_GRAY = '#9E9E9E'; const menuItemsBySection: MenuItemsBySection = { Documentation: [ { @@ -63,26 +54,27 @@ const menuItemsBySection: MenuItemsBySection = { { title: 'Rocket.chat', isExternal: true, - path: constants.ZEROEX_CHAT_URL, - fileName: 'rocketchat.png', + path: constants.URL_ZEROEX_CHAT, }, { title: 'Blog', isExternal: true, - path: constants.BLOG_URL, - fileName: 'medium.png', + path: constants.URL_BLOG, }, { title: 'Twitter', isExternal: true, - path: constants.TWITTER_URL, - fileName: 'twitter.png', + path: constants.URL_TWITTER, }, { title: 'Reddit', isExternal: true, - path: constants.REDDIT_URL, - fileName: 'reddit.png', + path: constants.URL_REDDIT, + }, + { + title: 'Forum', + isExternal: true, + path: constants.URL_DISCOURSE_FORUM, }, ], Organization: [ @@ -94,7 +86,7 @@ const menuItemsBySection: MenuItemsBySection = { { title: 'Careers', isExternal: true, - path: constants.ANGELLIST_URL, + path: constants.URL_ANGELLIST, }, { title: 'Contact', @@ -104,34 +96,40 @@ const menuItemsBySection: MenuItemsBySection = { ], }; const linkStyle = { - color: 'white', + color: colors.white, cursor: 'pointer', }; -const titleToIcon: {[title: string]: string} = { +const titleToIcon: { [title: string]: string } = { 'Rocket.chat': 'rocketchat.png', - 'Blog': 'medium.png', - 'Twitter': 'twitter.png', - 'Reddit': 'reddit.png', + Blog: 'medium.png', + Twitter: 'twitter.png', + Reddit: 'reddit.png', + Forum: 'discourse.png', }; -export interface FooterProps { - location: Location; -} +export interface FooterProps {} interface FooterState {} export class Footer extends React.Component<FooterProps, FooterState> { public render() { return ( - <div className="relative pb4 pt2" style={{backgroundColor: CUSTOM_DARK_GRAY}}> - <div className="mx-auto max-width-4 md-px2 lg-px0 py4 clearfix" style={{color: 'white'}}> + <div className="relative pb4 pt2" style={{ backgroundColor: colors.darkerGrey }}> + <div className="mx-auto max-width-4 md-px2 lg-px0 py4 clearfix" style={{ color: colors.white }}> <div className="col lg-col-4 md-col-4 col-12 left"> - <div className="sm-mx-auto" style={{width: 148}}> + <div className="sm-mx-auto" style={{ width: 148 }}> <div> <img src="/images/protocol_logo_white.png" height="30" /> </div> - <div style={{fontSize: 11, color: CUSTOM_LIGHTEST_GRAY, paddingLeft: 37, paddingTop: 2}}> + <div + style={{ + fontSize: 11, + color: colors.grey, + paddingLeft: 37, + paddingTop: 2, + }} + > © ZeroEx, Intl. </div> </div> @@ -139,20 +137,20 @@ export class Footer extends React.Component<FooterProps, FooterState> { <div className="col lg-col-8 md-col-8 col-12 lg-pl4 md-pl4"> <div className="col lg-col-4 md-col-4 col-12"> <div className="lg-right md-right sm-center"> - {this.renderHeader(Sections.Documentation)} - {_.map(menuItemsBySection[Sections.Documentation], this.renderMenuItem.bind(this))} + {this._renderHeader(Sections.Documentation)} + {_.map(menuItemsBySection[Sections.Documentation], this._renderMenuItem.bind(this))} </div> </div> <div className="col lg-col-4 md-col-4 col-12 lg-pr2 md-pr2"> <div className="lg-right md-right sm-center"> - {this.renderHeader(Sections.Community)} - {_.map(menuItemsBySection[Sections.Community], this.renderMenuItem.bind(this))} + {this._renderHeader(Sections.Community)} + {_.map(menuItemsBySection[Sections.Community], this._renderMenuItem.bind(this))} </div> </div> <div className="col lg-col-4 md-col-4 col-12"> <div className="lg-right md-right sm-center"> - {this.renderHeader(Sections.Organization)} - {_.map(menuItemsBySection[Sections.Organization], this.renderMenuItem.bind(this))} + {this._renderHeader(Sections.Organization)} + {_.map(menuItemsBySection[Sections.Organization], this._renderMenuItem.bind(this))} </div> </div> </div> @@ -160,100 +158,55 @@ export class Footer extends React.Component<FooterProps, FooterState> { </div> ); } - private renderIcon(fileName: string) { + private _renderIcon(fileName: string) { return ( - <div style={{height: ICON_DIMENSION, width: ICON_DIMENSION}}> - <img src={`/images/social/${fileName}`} style={{width: ICON_DIMENSION}} /> + <div style={{ height: ICON_DIMENSION, width: ICON_DIMENSION }}> + <img src={`/images/social/${fileName}`} style={{ width: ICON_DIMENSION }} /> </div> ); } - private renderMenuItem(item: FooterMenuItem) { + private _renderMenuItem(item: FooterMenuItem) { const iconIfExists = titleToIcon[item.title]; return ( - <div - key={item.title} - className="sm-center" - style={{fontSize: 13, paddingTop: 25}} - > - {item.isExternal ? - <a - className="text-decoration-none" - style={linkStyle} - target="_blank" - href={item.path} - > - {!_.isUndefined(iconIfExists) ? - <div className="sm-mx-auto" style={{width: 65}}> + <div key={item.title} className="sm-center" style={{ fontSize: 13, paddingTop: 25 }}> + {item.isExternal ? ( + <a className="text-decoration-none" style={linkStyle} target="_blank" href={item.path}> + {!_.isUndefined(iconIfExists) ? ( + <div className="sm-mx-auto" style={{ width: 65 }}> <div className="flex"> - <div className="pr1"> - {this.renderIcon(iconIfExists)} - </div> + <div className="pr1">{this._renderIcon(iconIfExists)}</div> <div>{item.title}</div> </div> - </div> : + </div> + ) : ( item.title - } - </a> : - <Link - to={item.path} - style={linkStyle} - className="text-decoration-none" - > + )} + </a> + ) : ( + <Link to={item.path} style={linkStyle} className="text-decoration-none"> <div> - {!_.isUndefined(iconIfExists) && - <div className="pr1"> - {this.renderIcon(iconIfExists)} - </div> - } + {!_.isUndefined(iconIfExists) && ( + <div className="pr1">{this._renderIcon(iconIfExists)}</div> + )} {item.title} </div> </Link> - } + )} </div> ); } - private renderHeader(title: string) { + private _renderHeader(title: string) { const headerStyle = { textTransform: 'uppercase', - color: CUSTOM_LIGHT_GRAY, + color: colors.grey400, letterSpacing: 2, fontFamily: 'Roboto Mono', fontSize: 13, }; return ( - <div - className="lg-pb2 md-pb2 sm-pt4" - style={headerStyle} - > + <div className="lg-pb2 md-pb2 sm-pt4" style={headerStyle}> {title} </div> ); } - private renderHomepageLink(title: string) { - const hash = title.toLowerCase(); - if (this.props.location.pathname === WebsitePaths.Home) { - return ( - <ScrollLink - style={linkStyle} - to={hash} - smooth={true} - offset={0} - duration={constants.HOME_SCROLL_DURATION_MS} - containerId="home" - > - {title} - </ScrollLink> - ); - } else { - return ( - <HashLink - to={`/#${hash}`} - className="text-decoration-none" - style={linkStyle} - > - {title} - </HashLink> - ); - } - } } diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx index 633d6a017..df7d87cfd 100644 --- a/packages/website/ts/components/generate_order/asset_picker.tsx +++ b/packages/website/ts/components/generate_order/asset_picker.tsx @@ -1,26 +1,14 @@ import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; -import GridList from 'material-ui/GridList/GridList'; -import GridTile from 'material-ui/GridList/GridTile'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {Blockchain} from 'ts/blockchain'; -import {NewTokenForm} from 'ts/components/generate_order/new_token_form'; -import {TrackTokenConfirmation} from 'ts/components/track_token_confirmation'; -import {TokenIcon} from 'ts/components/ui/token_icon'; -import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; -import {Dispatcher} from 'ts/redux/dispatcher'; -import { - AssetToken, - DialogConfigs, - Styles, - Token, - TokenByAddress, - TokenState, - TokenVisibility, -} from 'ts/types'; -import {utils} from 'ts/utils/utils'; +import { Blockchain } from 'ts/blockchain'; +import { NewTokenForm } from 'ts/components/generate_order/new_token_form'; +import { TrackTokenConfirmation } from 'ts/components/track_token_confirmation'; +import { TokenIcon } from 'ts/components/ui/token_icon'; +import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { DialogConfigs, Token, TokenByAddress, TokenState, TokenVisibility } from 'ts/types'; const TOKEN_ICON_DIMENSION = 100; const TILE_DIMENSION = 146; @@ -53,7 +41,7 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt public static defaultProps: Partial<AssetPickerProps> = { tokenVisibility: TokenVisibility.ALL, }; - private dialogConfigsByAssetView: {[assetView: string]: DialogConfigs}; + private _dialogConfigsByAssetView: { [assetView: string]: DialogConfigs }; constructor(props: AssetPickerProps) { super(props); this.state = { @@ -62,7 +50,7 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt chosenTrackTokenAddress: undefined, isAddingTokenToTracked: false, }; - this.dialogConfigsByAssetView = { + this._dialogConfigsByAssetView = { [AssetViews.ASSET_PICKER]: { title: 'Select token', isModal: false, @@ -80,45 +68,41 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt <FlatButton key="noTracking" label="No" - onTouchTap={this.onTrackConfirmationRespondedAsync.bind(this, false)} + onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)} />, <FlatButton key="yesTrack" label="Yes" - onTouchTap={this.onTrackConfirmationRespondedAsync.bind(this, true)} + onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)} />, ], }, }; } public render() { - const dialogConfigs: DialogConfigs = this.dialogConfigsByAssetView[this.state.assetView]; + const dialogConfigs: DialogConfigs = this._dialogConfigsByAssetView[this.state.assetView]; return ( <Dialog title={dialogConfigs.title} - titleStyle={{fontWeight: 100}} + titleStyle={{ fontWeight: 100 }} modal={dialogConfigs.isModal} open={this.props.isOpen} actions={dialogConfigs.actions} - onRequestClose={this.onCloseDialog.bind(this)} + onRequestClose={this._onCloseDialog.bind(this)} > - {this.state.assetView === AssetViews.ASSET_PICKER && - this.renderAssetPicker() - } - {this.state.assetView === AssetViews.NEW_TOKEN_FORM && + {this.state.assetView === AssetViews.ASSET_PICKER && this._renderAssetPicker()} + {this.state.assetView === AssetViews.NEW_TOKEN_FORM && ( <NewTokenForm blockchain={this.props.blockchain} - onNewTokenSubmitted={this.onNewTokenSubmitted.bind(this)} + onNewTokenSubmitted={this._onNewTokenSubmitted.bind(this)} tokenByAddress={this.props.tokenByAddress} /> - } - {this.state.assetView === AssetViews.CONFIRM_TRACK_TOKEN && - this.renderConfirmTrackToken() - } + )} + {this.state.assetView === AssetViews.CONFIRM_TRACK_TOKEN && this._renderConfirmTrackToken()} </Dialog> ); } - private renderConfirmTrackToken() { + private _renderConfirmTrackToken() { const token = this.props.tokenByAddress[this.state.chosenTrackTokenAddress]; return ( <TrackTokenConfirmation @@ -129,22 +113,29 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt /> ); } - private renderAssetPicker() { + private _renderAssetPicker() { return ( <div className="clearfix flex flex-wrap" - style={{overflowY: 'auto', maxWidth: 720, maxHeight: 356, marginBottom: 10}} + style={{ + overflowY: 'auto', + maxWidth: 720, + maxHeight: 356, + marginBottom: 10, + }} > - {this.renderGridTiles()} + {this._renderGridTiles()} </div> ); } - private renderGridTiles() { + private _renderGridTiles() { let isHovered; let tileStyles; const gridTiles = _.map(this.props.tokenByAddress, (token: Token, address: string) => { - if ((this.props.tokenVisibility === TokenVisibility.TRACKED && !token.isTracked) || - (this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked)) { + if ( + (this.props.tokenVisibility === TokenVisibility.TRACKED && !token.isTracked) || + (this.props.tokenVisibility === TokenVisibility.UNTRACKED && token.isTracked) + ) { return null; // Skip } isHovered = this.state.hoveredAddress === address; @@ -155,11 +146,15 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt return ( <div key={address} - style={{width: TILE_DIMENSION, height: TILE_DIMENSION, ...tileStyles}} + style={{ + width: TILE_DIMENSION, + height: TILE_DIMENSION, + ...tileStyles, + }} className="p2 mx-auto" - onClick={this.onChooseToken.bind(this, address)} - onMouseEnter={this.onToggleHover.bind(this, address, true)} - onMouseLeave={this.onToggleHover.bind(this, address, false)} + onClick={this._onChooseToken.bind(this, address)} + onMouseEnter={this._onToggleHover.bind(this, address, true)} + onMouseLeave={this._onToggleHover.bind(this, address, false)} > <div className="p1 center"> <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} /> @@ -175,40 +170,44 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt opacity: isHovered ? 0.6 : 1, }; if (this.props.tokenVisibility !== TokenVisibility.TRACKED) { - gridTiles.push(( + gridTiles.push( <div key={otherTokenKey} - style={{width: TILE_DIMENSION, height: TILE_DIMENSION, ...tileStyles}} + style={{ + width: TILE_DIMENSION, + height: TILE_DIMENSION, + ...tileStyles, + }} className="p2 mx-auto" - onClick={this.onCustomAssetChosen.bind(this)} - onMouseEnter={this.onToggleHover.bind(this, otherTokenKey, true)} - onMouseLeave={this.onToggleHover.bind(this, otherTokenKey, false)} + onClick={this._onCustomAssetChosen.bind(this)} + onMouseEnter={this._onToggleHover.bind(this, otherTokenKey, true)} + onMouseLeave={this._onToggleHover.bind(this, otherTokenKey, false)} > <div className="p1 center"> <i - style={{fontSize: 105, paddingLeft: 1, paddingRight: 1}} + style={{ fontSize: 105, paddingLeft: 1, paddingRight: 1 }} className="zmdi zmdi-plus-circle" /> </div> <div className="center">Other ERC20 Token</div> - </div> - )); + </div>, + ); } return gridTiles; } - private onToggleHover(address: string, isHovered: boolean) { + private _onToggleHover(address: string, isHovered: boolean) { const hoveredAddress = isHovered ? address : undefined; this.setState({ hoveredAddress, }); } - private onCloseDialog() { + private _onCloseDialog() { this.setState({ assetView: AssetViews.ASSET_PICKER, }); this.props.onTokenChosen(this.props.currentTokenAddress); } - private onChooseToken(tokenAddress: string) { + private _onChooseToken(tokenAddress: string) { const token = this.props.tokenByAddress[tokenAddress]; if (token.isTracked) { this.props.onTokenChosen(tokenAddress); @@ -219,27 +218,12 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt }); } } - private getTitle() { - switch (this.state.assetView) { - case AssetViews.ASSET_PICKER: - return 'Select token'; - - case AssetViews.NEW_TOKEN_FORM: - return 'Add an ERC20 token'; - - case AssetViews.CONFIRM_TRACK_TOKEN: - return 'Tracking confirmation'; - - default: - throw utils.spawnSwitchErr('assetView', this.state.assetView); - } - } - private onCustomAssetChosen() { + private _onCustomAssetChosen() { this.setState({ assetView: AssetViews.NEW_TOKEN_FORM, }); } - private onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) { + private _onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) { this.props.dispatcher.updateTokenStateByAddress({ [newToken.address]: newTokenState, }); @@ -250,14 +234,14 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt }); this.props.onTokenChosen(newToken.address); } - private async onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { + private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) { if (!didUserAcceptTracking) { this.setState({ isAddingTokenToTracked: false, assetView: AssetViews.ASSET_PICKER, chosenTrackTokenAddress: undefined, }); - this.onCloseDialog(); + this._onCloseDialog(); return; } this.setState({ @@ -265,15 +249,16 @@ export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerSt }); const tokenAddress = this.state.chosenTrackTokenAddress; const token = this.props.tokenByAddress[tokenAddress]; - const newTokenEntry = _.assign({}, token); + const newTokenEntry = { + ...token, + }; newTokenEntry.isTracked = true; trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry); - const [ - balance, - allowance, - ] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(token.address); + const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( + token.address, + ); this.props.dispatcher.updateTokenStateByAddress({ [token.address]: { balance, diff --git a/packages/website/ts/components/generate_order/generate_order_form.tsx b/packages/website/ts/components/generate_order/generate_order_form.tsx index 72edf08cf..3ae0d48a7 100644 --- a/packages/website/ts/components/generate_order/generate_order_form.tsx +++ b/packages/website/ts/components/generate_order/generate_order_form.tsx @@ -1,24 +1,23 @@ -import {Order, ZeroEx} from '0x.js'; -import BigNumber from 'bignumber.js'; +import { Order, ZeroEx } from '0x.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; import Divider from 'material-ui/Divider'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {Blockchain} from 'ts/blockchain'; -import {ExpirationInput} from 'ts/components/inputs/expiration_input'; -import {HashInput} from 'ts/components/inputs/hash_input'; -import {IdenticonAddressInput} from 'ts/components/inputs/identicon_address_input'; -import {TokenAmountInput} from 'ts/components/inputs/token_amount_input'; -import {TokenInput} from 'ts/components/inputs/token_input'; -import {OrderJSON} from 'ts/components/order_json'; -import {Alert} from 'ts/components/ui/alert'; -import {HelpTooltip} from 'ts/components/ui/help_tooltip'; -import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button'; -import {SwapIcon} from 'ts/components/ui/swap_icon'; -import {Dispatcher} from 'ts/redux/dispatcher'; -import {orderSchema} from 'ts/schemas/order_schema'; -import {SchemaValidator} from 'ts/schemas/validator'; +import { Blockchain } from 'ts/blockchain'; +import { ExpirationInput } from 'ts/components/inputs/expiration_input'; +import { HashInput } from 'ts/components/inputs/hash_input'; +import { IdenticonAddressInput } from 'ts/components/inputs/identicon_address_input'; +import { TokenAmountInput } from 'ts/components/inputs/token_amount_input'; +import { TokenInput } from 'ts/components/inputs/token_input'; +import { OrderJSON } from 'ts/components/order_json'; +import { Alert } from 'ts/components/ui/alert'; +import { HelpTooltip } from 'ts/components/ui/help_tooltip'; +import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; +import { SwapIcon } from 'ts/components/ui/swap_icon'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { orderSchema } from 'ts/schemas/order_schema'; +import { SchemaValidator } from 'ts/schemas/validator'; import { AlertTypes, BlockchainErrs, @@ -30,8 +29,9 @@ import { TokenByAddress, TokenStateByAddress, } from 'ts/types'; -import {errorReporter} from 'ts/utils/error_reporter'; -import {utils} from 'ts/utils/utils'; +import { colors } from 'ts/utils/colors'; +import { errorReporter } from 'ts/utils/error_reporter'; +import { utils } from 'ts/utils/utils'; enum SigningState { UNSIGNED, @@ -62,17 +62,8 @@ interface GenerateOrderFormState { signingState: SigningState; } -const style = { - paper: { - display: 'inline-block', - position: 'relative', - textAlign: 'center', - width: '100%', - }, -}; - -export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, any> { - private validator: SchemaValidator; +export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, GenerateOrderFormState> { + private _validator: SchemaValidator; constructor(props: GenerateOrderFormProps) { super(props); this.state = { @@ -80,20 +71,21 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a shouldShowIncompleteErrs: false, signingState: SigningState.UNSIGNED, }; - this.validator = new SchemaValidator(); + this._validator = new SchemaValidator(); } public componentDidMount() { window.scrollTo(0, 0); } public render() { const dispatcher = this.props.dispatcher; - const depositTokenAddress = this.props.sideToAssetToken[Side.deposit].address; + const depositTokenAddress = this.props.sideToAssetToken[Side.Deposit].address; const depositToken = this.props.tokenByAddress[depositTokenAddress]; const depositTokenState = this.props.tokenStateByAddress[depositTokenAddress]; - const receiveTokenAddress = this.props.sideToAssetToken[Side.receive].address; + const receiveTokenAddress = this.props.sideToAssetToken[Side.Receive].address; const receiveToken = this.props.tokenByAddress[receiveTokenAddress]; const receiveTokenState = this.props.tokenStateByAddress[receiveTokenAddress]; - const takerExplanation = 'If a taker is specified, only they are<br> \ + const takerExplanation = + 'If a taker is specified, only they are<br> \ allowed to fill this order. If no taker is<br> \ specified, anyone is able to fill it.'; const exchangeContractIfExists = this.props.blockchain.getExchangeContractAddressIfExists(); @@ -101,7 +93,7 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a <div className="clearfix mb2 lg-px4 md-px4 sm-px2"> <h3>Generate an order</h3> <Divider /> - <div className="mx-auto" style={{maxWidth: 580}}> + <div className="mx-auto" style={{ maxWidth: 580 }}> <div className="pt3"> <div className="mx-auto clearfix"> <div className="lg-col md-col lg-col-5 md-col-5 sm-col sm-col-5 sm-pb2"> @@ -111,9 +103,9 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a blockchainErr={this.props.blockchainErr} dispatcher={this.props.dispatcher} label="Selling" - side={Side.deposit} + side={Side.Deposit} networkId={this.props.networkId} - assetToken={this.props.sideToAssetToken[Side.deposit]} + assetToken={this.props.sideToAssetToken[Side.Deposit]} updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)} tokenByAddress={this.props.tokenByAddress} /> @@ -121,8 +113,8 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a label="Sell amount" token={depositToken} tokenState={depositTokenState} - amount={this.props.sideToAssetToken[Side.deposit].amount} - onChange={this.onTokenAmountChange.bind(this, depositToken, Side.deposit)} + amount={this.props.sideToAssetToken[Side.Deposit].amount} + onChange={this._onTokenAmountChange.bind(this, depositToken, Side.Deposit)} shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} shouldCheckBalance={true} shouldCheckAllowance={true} @@ -130,9 +122,7 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a </div> <div className="lg-col md-col lg-col-2 md-col-2 sm-col sm-col-2 xs-hide"> <div className="p1"> - <SwapIcon - swapTokensFn={dispatcher.swapAssetTokenSymbols.bind(dispatcher)} - /> + <SwapIcon swapTokensFn={dispatcher.swapAssetTokenSymbols.bind(dispatcher)} /> </div> </div> <div className="lg-col md-col lg-col-5 md-col-5 sm-col sm-col-5 sm-pb2"> @@ -142,9 +132,9 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a blockchainErr={this.props.blockchainErr} dispatcher={this.props.dispatcher} label="Buying" - side={Side.receive} + side={Side.Receive} networkId={this.props.networkId} - assetToken={this.props.sideToAssetToken[Side.receive]} + assetToken={this.props.sideToAssetToken[Side.Receive]} updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)} tokenByAddress={this.props.tokenByAddress} /> @@ -152,8 +142,8 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a label="Receive amount" token={receiveToken} tokenState={receiveTokenState} - amount={this.props.sideToAssetToken[Side.receive].amount} - onChange={this.onTokenAmountChange.bind(this, receiveToken, Side.receive)} + amount={this.props.sideToAssetToken[Side.Receive].amount} + onChange={this._onTokenAmountChange.bind(this, receiveToken, Side.Receive)} shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs} shouldCheckBalance={false} shouldCheckAllowance={false} @@ -163,7 +153,7 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a </div> <div className="pt1 sm-pb2 lg-px4 md-px4"> <div className="lg-px3 md-px3"> - <div style={{fontSize: 12, color: colors.grey500}}>Expiration</div> + <div style={{ fontSize: 12, color: colors.grey }}>Expiration</div> <ExpirationInput orderExpiryTimestamp={this.props.orderExpiryTimestamp} updateOrderExpiry={dispatcher.updateOrderExpiry.bind(dispatcher)} @@ -174,13 +164,11 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a <IdenticonAddressInput label="Taker" initialAddress={this.props.orderTakerAddress} - updateOrderAddress={this.updateOrderAddress.bind(this)} + updateOrderAddress={this._updateOrderAddress.bind(this)} /> <div className="pt3"> <div className="pl1"> - <HelpTooltip - explanation={takerExplanation} - /> + <HelpTooltip explanation={takerExplanation} /> </div> </div> </div> @@ -198,20 +186,20 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a labelReady="Sign hash" labelLoading="Signing..." labelComplete="Hash signed!" - onClickAsyncFn={this.onSignClickedAsync.bind(this)} + onClickAsyncFn={this._onSignClickedAsync.bind(this)} /> </div> - {this.state.globalErrMsg !== '' && + {this.state.globalErrMsg !== '' && ( <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} /> - } + )} </div> </div> <Dialog title="Order JSON" - titleStyle={{fontWeight: 100}} + titleStyle={{ fontWeight: 100 }} modal={false} open={this.state.signingState === SigningState.SIGNED} - onRequestClose={this.onCloseOrderJSONDialog.bind(this)} + onRequestClose={this._onCloseOrderJSONDialog.bind(this)} > <OrderJSON exchangeContractIfExists={exchangeContractIfExists} @@ -231,10 +219,13 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a </div> ); } - private onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber) { - this.props.dispatcher.updateChosenAssetToken(side, {address: token.address, amount}); + private _onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber) { + this.props.dispatcher.updateChosenAssetToken(side, { + address: token.address, + amount, + }); } - private onCloseOrderJSONDialog() { + private _onCloseOrderJSONDialog() { // Upon closing the order JSON dialog, we update the orderSalt stored in the Redux store // with a new value so that if a user signs the identical order again, the newly signed // orderHash will not collide with the previously generated orderHash. @@ -243,22 +234,27 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a signingState: SigningState.UNSIGNED, }); } - private async onSignClickedAsync(): Promise<boolean> { - if (this.props.blockchainErr !== '') { + private async _onSignClickedAsync(): Promise<boolean> { + if (this.props.blockchainErr !== BlockchainErrs.NoError) { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); return false; } // Check if all required inputs were supplied - const debitToken = this.props.sideToAssetToken[Side.deposit]; + const debitToken = this.props.sideToAssetToken[Side.Deposit]; const debitBalance = this.props.tokenStateByAddress[debitToken.address].balance; const debitAllowance = this.props.tokenStateByAddress[debitToken.address].allowance; - const receiveAmount = this.props.sideToAssetToken[Side.receive].amount; - if (!_.isUndefined(debitToken.amount) && !_.isUndefined(receiveAmount) && - debitToken.amount.gt(0) && receiveAmount.gt(0) && + const receiveAmount = this.props.sideToAssetToken[Side.Receive].amount; + if ( + !_.isUndefined(debitToken.amount) && + !_.isUndefined(receiveAmount) && + debitToken.amount.gt(0) && + receiveAmount.gt(0) && this.props.userAddress !== '' && - debitBalance.gte(debitToken.amount) && debitAllowance.gte(debitToken.amount)) { - const didSignSuccessfully = await this.signTransactionAsync(); + debitBalance.gte(debitToken.amount) && + debitAllowance.gte(debitToken.amount) + ) { + const didSignSuccessfully = await this._signTransactionAsync(); if (didSignSuccessfully) { this.setState({ globalErrMsg: '', @@ -279,7 +275,7 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a return false; } } - private async signTransactionAsync(): Promise<boolean> { + private async _signTransactionAsync(): Promise<boolean> { this.setState({ signingState: SigningState.SIGNING, }); @@ -287,7 +283,7 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a if (_.isUndefined(exchangeContractAddr)) { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); this.setState({ - isSigning: false, + signingState: SigningState.UNSIGNED, }); return false; } @@ -312,19 +308,28 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a let globalErrMsg = ''; try { const signatureData = await this.props.blockchain.signOrderHashAsync(orderHash); - const order = utils.generateOrder(this.props.networkId, exchangeContractAddr, this.props.sideToAssetToken, - hashData.orderExpiryTimestamp, this.props.orderTakerAddress, - this.props.userAddress, hashData.makerFee, hashData.takerFee, - hashData.feeRecipientAddress, signatureData, this.props.tokenByAddress, - hashData.orderSalt); - const validationResult = this.validator.validate(order, orderSchema); + const order = utils.generateOrder( + this.props.networkId, + exchangeContractAddr, + this.props.sideToAssetToken, + hashData.orderExpiryTimestamp, + this.props.orderTakerAddress, + this.props.userAddress, + hashData.makerFee, + hashData.takerFee, + hashData.feeRecipientAddress, + signatureData, + this.props.tokenByAddress, + hashData.orderSalt, + ); + const validationResult = this._validator.validate(order, orderSchema); if (validationResult.errors.length > 0) { globalErrMsg = 'Order signing failed. Please refresh and try again'; utils.consoleLog(`Unexpected error occured: Order validation failed: ${validationResult.errors}`); } } catch (err) { - const errMsg = '' + err; + const errMsg = `${err}`; if (utils.didUserDenyWeb3Request(errMsg)) { globalErrMsg = 'User denied sign request'; } else { @@ -340,7 +345,7 @@ export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, a }); return globalErrMsg === ''; } - private updateOrderAddress(address?: string): void { + private _updateOrderAddress(address?: string): void { if (!_.isUndefined(address)) { this.props.dispatcher.updateOrderTakerAddress(address); } diff --git a/packages/website/ts/components/generate_order/new_token_form.tsx b/packages/website/ts/components/generate_order/new_token_form.tsx index 7e2c61ae8..63645be9a 100644 --- a/packages/website/ts/components/generate_order/new_token_form.tsx +++ b/packages/website/ts/components/generate_order/new_token_form.tsx @@ -1,15 +1,14 @@ -import BigNumber from 'bignumber.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; -import {colors} from 'material-ui/styles'; import TextField from 'material-ui/TextField'; import * as React from 'react'; -import {Blockchain} from 'ts/blockchain'; -import {AddressInput} from 'ts/components/inputs/address_input'; -import {Alert} from 'ts/components/ui/alert'; -import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button'; -import {RequiredLabel} from 'ts/components/ui/required_label'; -import {AlertTypes, Token, TokenByAddress, TokenState} from 'ts/types'; -import {constants} from 'ts/utils/constants'; +import { Blockchain } from 'ts/blockchain'; +import { AddressInput } from 'ts/components/inputs/address_input'; +import { Alert } from 'ts/components/ui/alert'; +import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; +import { RequiredLabel } from 'ts/components/ui/required_label'; +import { AlertTypes, Token, TokenByAddress, TokenState } from 'ts/types'; +import { colors } from 'ts/utils/colors'; interface NewTokenFormProps { blockchain: Blockchain; @@ -46,25 +45,25 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor } public render() { return ( - <div className="mx-auto pb2" style={{width: 256}}> + <div className="mx-auto pb2" style={{ width: 256 }}> <div> <TextField floatingLabelFixed={true} - floatingLabelStyle={{color: colors.grey500}} + floatingLabelStyle={{ color: colors.grey }} floatingLabelText={<RequiredLabel label="Name" />} value={this.state.name} errorText={this.state.nameErrText} - onChange={this.onTokenNameChanged.bind(this)} + onChange={this._onTokenNameChanged.bind(this)} /> </div> <div> <TextField floatingLabelFixed={true} - floatingLabelStyle={{color: colors.grey500}} + floatingLabelStyle={{ color: colors.grey }} floatingLabelText={<RequiredLabel label="Symbol" />} value={this.state.symbol} errorText={this.state.symbolErrText} - onChange={this.onTokenSymbolChanged.bind(this)} + onChange={this._onTokenSymbolChanged.bind(this)} /> </div> <div> @@ -73,38 +72,36 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor label="Contract address" initialAddress="" shouldShowIncompleteErrs={this.state.shouldShowAddressIncompleteErr} - updateAddress={this.onTokenAddressChanged.bind(this)} + updateAddress={this._onTokenAddressChanged.bind(this)} /> </div> <div> <TextField floatingLabelFixed={true} - floatingLabelStyle={{color: colors.grey500}} + floatingLabelStyle={{ color: colors.grey }} floatingLabelText={<RequiredLabel label="Decimals" />} value={this.state.decimals} errorText={this.state.decimalsErrText} - onChange={this.onTokenDecimalsChanged.bind(this)} + onChange={this._onTokenDecimalsChanged.bind(this)} /> </div> - <div className="pt2 mx-auto" style={{width: 120}}> + <div className="pt2 mx-auto" style={{ width: 120 }}> <LifeCycleRaisedButton labelReady="Add" labelLoading="Adding..." labelComplete="Added!" - onClickAsyncFn={this.onAddNewTokenClickAsync.bind(this)} + onClickAsyncFn={this._onAddNewTokenClickAsync.bind(this)} /> </div> - {this.state.globalErrMsg !== '' && - <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} /> - } + {this.state.globalErrMsg !== '' && <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} />} </div> ); } - private async onAddNewTokenClickAsync() { + private async _onAddNewTokenClickAsync() { // Trigger validation of name and symbol - this.onTokenNameChanged(undefined, this.state.name); - this.onTokenSymbolChanged(undefined, this.state.symbol); - this.onTokenDecimalsChanged(undefined, this.state.decimals); + this._onTokenNameChanged(undefined, this.state.name); + this._onTokenSymbolChanged(undefined, this.state.symbol); + this._onTokenDecimalsChanged(undefined, this.state.decimals); const isAddressIncomplete = this.state.address === ''; let doesContractExist = false; @@ -117,18 +114,21 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor let allowance = new BigNumber(0); if (doesContractExist) { try { - [ - balance, - allowance, - ] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(this.state.address); + [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync( + this.state.address, + ); } catch (err) { hasBalanceAllowanceErr = true; } } let globalErrMsg = ''; - if (this.state.nameErrText !== '' || this.state.symbolErrText !== '' || - this.state.decimalsErrText !== '' || isAddressIncomplete) { + if ( + this.state.nameErrText !== '' || + this.state.symbolErrText !== '' || + this.state.decimalsErrText !== '' || + isAddressIncomplete + ) { globalErrMsg = 'Please fix the above issues'; } else if (!doesContractExist) { globalErrMsg = 'No contract found at supplied address'; @@ -161,15 +161,15 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor }; this.props.onNewTokenSubmitted(newToken, newTokenState); } - private onTokenNameChanged(e: any, name: string) { + private _onTokenNameChanged(e: any, name: string) { let nameErrText = ''; const maxLength = 30; const tokens = _.values(this.props.tokenByAddress); - const tokenWithNameIfExists = _.find(tokens, {name}); + const tokenWithNameIfExists = _.find(tokens, { name }); const tokenWithNameExists = !_.isUndefined(tokenWithNameIfExists); if (name === '') { nameErrText = 'Name is required'; - } else if (!this.isValidName(name)) { + } else if (!this._isValidName(name)) { nameErrText = 'Name should only contain letters, digits and spaces'; } else if (name.length > maxLength) { nameErrText = `Max length is ${maxLength}`; @@ -182,15 +182,15 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor nameErrText, }); } - private onTokenSymbolChanged(e: any, symbol: string) { + private _onTokenSymbolChanged(e: any, symbol: string) { let symbolErrText = ''; const maxLength = 5; const tokens = _.values(this.props.tokenByAddress); - const tokenWithSymbolExists = !_.isUndefined(_.find(tokens, {symbol})); + const tokenWithSymbolExists = !_.isUndefined(_.find(tokens, { symbol })); if (symbol === '') { symbolErrText = 'Symbol is required'; - } else if (!this.isLetters(symbol)) { - symbolErrText = 'Can only include letters'; + } else if (!this._isAlphanumeric(symbol)) { + symbolErrText = 'Can only include alphanumeric characters'; } else if (symbol.length > maxLength) { symbolErrText = `Max length is ${maxLength}`; } else if (tokenWithSymbolExists) { @@ -202,12 +202,12 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor symbolErrText, }); } - private onTokenDecimalsChanged(e: any, decimals: string) { + private _onTokenDecimalsChanged(e: any, decimals: string) { let decimalsErrText = ''; const maxLength = 2; if (decimals === '') { decimalsErrText = 'Decimals is required'; - } else if (!this.isInteger(decimals)) { + } else if (!this._isInteger(decimals)) { decimalsErrText = 'Must be an integer'; } else if (decimals.length > maxLength) { decimalsErrText = `Max length is ${maxLength}`; @@ -218,20 +218,20 @@ export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFor decimalsErrText, }); } - private onTokenAddressChanged(address?: string) { + private _onTokenAddressChanged(address?: string) { if (!_.isUndefined(address)) { this.setState({ address, }); } } - private isValidName(input: string) { + private _isValidName(input: string) { return /^[a-z0-9 ]+$/i.test(input); } - private isInteger(input: string) { + private _isInteger(input: string) { return /^[0-9]+$/i.test(input); } - private isLetters(input: string) { - return /^[a-zA-Z]+$/i.test(input); + private _isAlphanumeric(input: string) { + return /^[a-zA-Z0-9]+$/i.test(input); } } diff --git a/packages/website/ts/components/inputs/address_input.tsx b/packages/website/ts/components/inputs/address_input.tsx index 8b03b8d12..dd4131140 100644 --- a/packages/website/ts/components/inputs/address_input.tsx +++ b/packages/website/ts/components/inputs/address_input.tsx @@ -1,10 +1,9 @@ -import {isAddress} from 'ethereum-address'; +import { addressUtils } from '@0xproject/utils'; import * as _ from 'lodash'; -import {colors} from 'material-ui/styles'; import TextField from 'material-ui/TextField'; import * as React from 'react'; -import {Blockchain} from 'ts/blockchain'; -import {RequiredLabel} from 'ts/components/ui/required_label'; +import { RequiredLabel } from 'ts/components/ui/required_label'; +import { colors } from 'ts/utils/colors'; interface AddressInputProps { disabled?: boolean; @@ -31,16 +30,14 @@ export class AddressInput extends React.Component<AddressInputProps, AddressInpu }; } public componentWillReceiveProps(nextProps: AddressInputProps) { - if (nextProps.shouldShowIncompleteErrs && this.props.isRequired && - this.state.address === '') { - this.setState({ - errMsg: 'Address is required', - }); + if (nextProps.shouldShowIncompleteErrs && this.props.isRequired && this.state.address === '') { + this.setState({ + errMsg: 'Address is required', + }); } } public render() { - const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : - this.props.label; + const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : this.props.label; const labelDisplay = this.props.shouldHideLabel ? 'hidden' : 'block'; const hintText = this.props.hintText ? this.props.hintText : ''; return ( @@ -51,18 +48,18 @@ export class AddressInput extends React.Component<AddressInputProps, AddressInpu fullWidth={true} hintText={hintText} floatingLabelFixed={true} - floatingLabelStyle={{color: colors.grey500, display: labelDisplay}} + floatingLabelStyle={{ color: colors.grey, display: labelDisplay }} floatingLabelText={label} errorText={this.state.errMsg} value={this.state.address} - onChange={this.onOrderTakerAddressUpdated.bind(this)} + onChange={this._onOrderTakerAddressUpdated.bind(this)} /> </div> ); } - private onOrderTakerAddressUpdated(e: any) { + private _onOrderTakerAddressUpdated(e: any) { const address = e.target.value.toLowerCase(); - const isValidAddress = isAddress(address) || address === ''; + const isValidAddress = addressUtils.isAddress(address) || address === ''; const errMsg = isValidAddress ? '' : 'Invalid ethereum address'; this.setState({ address, diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx index 4c15ed4a0..da46db4f4 100644 --- a/packages/website/ts/components/inputs/allowance_toggle.tsx +++ b/packages/website/ts/components/inputs/allowance_toggle.tsx @@ -1,12 +1,12 @@ -import BigNumber from 'bignumber.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Toggle from 'material-ui/Toggle'; import * as React from 'react'; -import {Blockchain} from 'ts/blockchain'; -import {Dispatcher} from 'ts/redux/dispatcher'; -import {BalanceErrs, Token, TokenState} from 'ts/types'; -import {errorReporter} from 'ts/utils/error_reporter'; -import {utils} from 'ts/utils/utils'; +import { Blockchain } from 'ts/blockchain'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { BalanceErrs, Token, TokenState } from 'ts/types'; +import { errorReporter } from 'ts/utils/error_reporter'; +import { utils } from 'ts/utils/utils'; const DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); @@ -46,22 +46,21 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow <div> <Toggle disabled={this.state.isSpinnerVisible} - toggled={this.isAllowanceSet()} - onToggle={this.onToggleAllowanceAsync.bind(this, this.props.token)} + toggled={this._isAllowanceSet()} + onToggle={this._onToggleAllowanceAsync.bind(this)} /> </div> - {this.state.isSpinnerVisible && - <div className="pl1" style={{paddingTop: 3}}> + {this.state.isSpinnerVisible && ( + <div className="pl1" style={{ paddingTop: 3 }}> <i className="zmdi zmdi-spinner zmdi-hc-spin" /> </div> - } + )} </div> ); } - private async onToggleAllowanceAsync() { + private async _onToggleAllowanceAsync(): Promise<void> { if (this.props.userAddress === '') { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); - return false; } this.setState({ @@ -69,7 +68,7 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow }); let newAllowanceAmountInBaseUnits = new BigNumber(0); - if (!this.isAllowanceSet()) { + if (!this._isAllowanceSet()) { newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS; } try { @@ -78,17 +77,17 @@ export class AllowanceToggle extends React.Component<AllowanceToggleProps, Allow this.setState({ isSpinnerVisible: false, }); - const errMsg = '' + err; + const errMsg = `${err}`; if (_.includes(errMsg, 'User denied transaction')) { - return false; + return; } utils.consoleLog(`Unexpected error encountered: ${err}`); utils.consoleLog(err.stack); - await errorReporter.reportAsync(err); this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed); + await errorReporter.reportAsync(err); } } - private isAllowanceSet() { + private _isAllowanceSet() { return !this.props.tokenState.allowance.eq(0); } } diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx index 7ddefc3b9..ddc434b51 100644 --- a/packages/website/ts/components/inputs/balance_bounded_input.tsx +++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx @@ -1,12 +1,12 @@ -import BigNumber from 'bignumber.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; -import {colors} from 'material-ui/styles'; import TextField from 'material-ui/TextField'; import * as React from 'react'; -import {Link} from 'react-router-dom'; -import {RequiredLabel} from 'ts/components/ui/required_label'; -import {InputErrMsg, ValidatedBigNumberCallback, WebsitePaths} from 'ts/types'; -import {utils} from 'ts/utils/utils'; +import { Link } from 'react-router-dom'; +import { RequiredLabel } from 'ts/components/ui/required_label'; +import { InputErrMsg, ValidatedBigNumberCallback, WebsitePaths } from 'ts/types'; +import { colors } from 'ts/utils/colors'; +import { utils } from 'ts/utils/utils'; interface BalanceBoundedInputProps { label?: string; @@ -25,8 +25,7 @@ interface BalanceBoundedInputState { amountString: string; } -export class BalanceBoundedInput extends - React.Component<BalanceBoundedInputProps, BalanceBoundedInputState> { +export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProps, BalanceBoundedInputState> { public static defaultProps: Partial<BalanceBoundedInputProps> = { shouldShowIncompleteErrs: false, shouldHideVisitBalancesLink: false, @@ -35,7 +34,7 @@ export class BalanceBoundedInput extends super(props); const amountString = this.props.amount ? this.props.amount.toString() : ''; this.state = { - errMsg: this.validate(amountString, props.balance), + errMsg: this._validate(amountString, props.balance), amountString, }; } @@ -57,14 +56,14 @@ export class BalanceBoundedInput extends if (shouldResetState) { const amountString = nextProps.amount.toString(); this.setState({ - errMsg: this.validate(amountString, nextProps.balance), + errMsg: this._validate(amountString, nextProps.balance), amountString, }); } } else if (isCurrentAmountNumeric) { const amountString = ''; this.setState({ - errMsg: this.validate(amountString, nextProps.balance), + errMsg: this._validate(amountString, nextProps.balance), amountString, }); } @@ -74,39 +73,42 @@ export class BalanceBoundedInput extends if (this.props.shouldShowIncompleteErrs && this.state.amountString === '') { errorText = 'This field is required'; } - let label: React.ReactNode|string = ''; + let label: React.ReactNode | string = ''; if (!_.isUndefined(this.props.label)) { - label = <RequiredLabel label={this.props.label}/>; + label = <RequiredLabel label={this.props.label} />; } return ( <TextField fullWidth={true} floatingLabelText={label} floatingLabelFixed={true} - floatingLabelStyle={{color: colors.grey500, width: 206}} + floatingLabelStyle={{ color: colors.grey, width: 206 }} errorText={errorText} value={this.state.amountString} - hintText={<span style={{textTransform: 'capitalize'}}>amount</span>} - onChange={this.onValueChange.bind(this)} - underlineStyle={{width: 'calc(100% + 50px)'}} + hintText={<span style={{ textTransform: 'capitalize' }}>amount</span>} + onChange={this._onValueChange.bind(this)} + underlineStyle={{ width: 'calc(100% + 50px)' }} /> ); } - private onValueChange(e: any, amountString: string) { - const errMsg = this.validate(amountString, this.props.balance); - this.setState({ - amountString, - errMsg, - }, () => { - const isValid = _.isUndefined(errMsg); - if (utils.isNumeric(amountString)) { - this.props.onChange(isValid, new BigNumber(amountString)); - } else { - this.props.onChange(isValid); - } - }); + private _onValueChange(e: any, amountString: string) { + const errMsg = this._validate(amountString, this.props.balance); + this.setState( + { + amountString, + errMsg, + }, + () => { + const isValid = _.isUndefined(errMsg); + if (utils.isNumeric(amountString)) { + this.props.onChange(isValid, new BigNumber(amountString)); + } else { + this.props.onChange(isValid); + } + }, + ); } - private validate(amountString: string, balance: BigNumber): InputErrMsg { + private _validate(amountString: string, balance: BigNumber): InputErrMsg { if (!utils.isNumeric(amountString)) { return amountString !== '' ? 'Must be a number' : ''; } @@ -115,17 +117,12 @@ export class BalanceBoundedInput extends return 'Cannot be zero'; } if (this.props.shouldCheckBalance && amount.gt(balance)) { - return ( - <span> - Insufficient balance.{' '} - {this.renderIncreaseBalanceLink()} - </span> - ); + return <span>Insufficient balance. {this._renderIncreaseBalanceLink()}</span>; } const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount); return errMsg; } - private renderIncreaseBalanceLink() { + private _renderIncreaseBalanceLink() { if (this.props.shouldHideVisitBalancesLink) { return null; } @@ -133,25 +130,19 @@ export class BalanceBoundedInput extends const increaseBalanceText = 'Increase balance'; const linkStyle = { cursor: 'pointer', - color: colors.grey900, + color: colors.darkestGrey, textDecoration: 'underline', display: 'inline', }; if (_.isUndefined(this.props.onVisitBalancesPageClick)) { return ( - <Link - to={`${WebsitePaths.Portal}/balances`} - style={linkStyle} - > + <Link to={`${WebsitePaths.Portal}/balances`} style={linkStyle}> {increaseBalanceText} </Link> ); } else { return ( - <div - onClick={this.props.onVisitBalancesPageClick} - style={linkStyle} - > + <div onClick={this.props.onVisitBalancesPageClick} style={linkStyle}> {increaseBalanceText} </div> ); diff --git a/packages/website/ts/components/inputs/eth_amount_input.tsx b/packages/website/ts/components/inputs/eth_amount_input.tsx index 5c5e23eef..a66f92c8c 100644 --- a/packages/website/ts/components/inputs/eth_amount_input.tsx +++ b/packages/website/ts/components/inputs/eth_amount_input.tsx @@ -1,10 +1,10 @@ -import {ZeroEx} from '0x.js'; -import BigNumber from 'bignumber.js'; +import { ZeroEx } from '0x.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import * as React from 'react'; -import {BalanceBoundedInput} from 'ts/components/inputs/balance_bounded_input'; -import {ValidatedBigNumberCallback} from 'ts/types'; -import {constants} from 'ts/utils/constants'; +import { BalanceBoundedInput } from 'ts/components/inputs/balance_bounded_input'; +import { ValidatedBigNumberCallback } from 'ts/types'; +import { constants } from 'ts/utils/constants'; interface EthAmountInputProps { label?: string; @@ -21,31 +21,29 @@ interface EthAmountInputState {} export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmountInputState> { public render() { - const amount = this.props.amount ? - ZeroEx.toUnitAmount(this.props.amount, constants.ETH_DECIMAL_PLACES) : - undefined; + const amount = this.props.amount + ? ZeroEx.toUnitAmount(this.props.amount, constants.DECIMAL_PLACES_ETH) + : undefined; return ( - <div className="flex overflow-hidden" style={{height: 63}}> + <div className="flex overflow-hidden" style={{ height: 63 }}> <BalanceBoundedInput label={this.props.label} balance={this.props.balance} amount={amount} - onChange={this.onChange.bind(this)} + onChange={this._onChange.bind(this)} shouldCheckBalance={this.props.shouldCheckBalance} shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} shouldHideVisitBalancesLink={this.props.shouldHideVisitBalancesLink} /> - <div style={{paddingTop: _.isUndefined(this.props.label) ? 15 : 40}}> - ETH - </div> + <div style={{ paddingTop: _.isUndefined(this.props.label) ? 15 : 40 }}>ETH</div> </div> ); } - private onChange(isValid: boolean, amount?: BigNumber) { - const baseUnitAmountIfExists = _.isUndefined(amount) ? - undefined : - ZeroEx.toBaseUnitAmount(amount, constants.ETH_DECIMAL_PLACES); + private _onChange(isValid: boolean, amount?: BigNumber) { + const baseUnitAmountIfExists = _.isUndefined(amount) + ? undefined + : ZeroEx.toBaseUnitAmount(amount, constants.DECIMAL_PLACES_ETH); this.props.onChange(isValid, baseUnitAmountIfExists); } } diff --git a/packages/website/ts/components/inputs/expiration_input.tsx b/packages/website/ts/components/inputs/expiration_input.tsx index d3d3d258d..e473648d2 100644 --- a/packages/website/ts/components/inputs/expiration_input.tsx +++ b/packages/website/ts/components/inputs/expiration_input.tsx @@ -1,10 +1,10 @@ -import BigNumber from 'bignumber.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import DatePicker from 'material-ui/DatePicker'; import TimePicker from 'material-ui/TimePicker'; import * as moment from 'moment'; import * as React from 'react'; -import {utils} from 'ts/utils/utils'; +import { utils } from 'ts/utils/utils'; interface ExpirationInputProps { orderExpiryTimestamp: BigNumber; @@ -17,11 +17,11 @@ interface ExpirationInputState { } export class ExpirationInput extends React.Component<ExpirationInputProps, ExpirationInputState> { - private earliestPickableMoment: moment.Moment; + private _earliestPickableMoment: moment.Moment; constructor(props: ExpirationInputProps) { super(props); // Set the earliest pickable date to today at 00:00, so users can only pick the current or later dates - this.earliestPickableMoment = moment().startOf('day'); + this._earliestPickableMoment = moment().startOf('day'); const expirationMoment = utils.convertToMomentFromUnixTimestamp(props.orderExpiryTimestamp); const initialOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec(); const didUserSetExpiry = !initialOrderExpiryTimestamp.eq(props.orderExpiryTimestamp); @@ -42,13 +42,10 @@ export class ExpirationInput extends React.Component<ExpirationInputProps, Expir mode="landscape" autoOk={true} value={date} - onChange={this.onDateChanged.bind(this)} - shouldDisableDate={this.shouldDisableDate.bind(this)} + onChange={this._onDateChanged.bind(this)} + shouldDisableDate={this._shouldDisableDate.bind(this)} /> - <div - className="absolute" - style={{fontSize: 20, right: 40, top: 13, pointerEvents: 'none'}} - > + <div className="absolute" style={{ fontSize: 20, right: 40, top: 13, pointerEvents: 'none' }}> <i className="zmdi zmdi-calendar" /> </div> </div> @@ -58,29 +55,24 @@ export class ExpirationInput extends React.Component<ExpirationInputProps, Expir hintText="Time" autoOk={true} value={time} - onChange={this.onTimeChanged.bind(this)} + onChange={this._onTimeChanged.bind(this)} /> - <div - className="absolute" - style={{fontSize: 20, right: 9, top: 13, pointerEvents: 'none'}} - > + <div className="absolute" style={{ fontSize: 20, right: 9, top: 13, pointerEvents: 'none' }}> <i className="zmdi zmdi-time" /> </div> </div> - <div - onClick={this.clearDates.bind(this)} - className="col col-1 pt2" - style={{textAlign: 'right'}} - > - <i style={{fontSize: 16, cursor: 'pointer'}} className="zmdi zmdi-close" /> + <div onClick={this._clearDates.bind(this)} className="col col-1 pt2" style={{ textAlign: 'right' }}> + <i style={{ fontSize: 16, cursor: 'pointer' }} className="zmdi zmdi-close" /> </div> </div> ); } - private shouldDisableDate(date: Date): boolean { - return moment(date).startOf('day').isBefore(this.earliestPickableMoment); + private _shouldDisableDate(date: Date): boolean { + return moment(date) + .startOf('day') + .isBefore(this._earliestPickableMoment); } - private clearDates() { + private _clearDates() { this.setState({ dateMoment: undefined, timeMoment: undefined, @@ -88,7 +80,7 @@ export class ExpirationInput extends React.Component<ExpirationInputProps, Expir const defaultDateTime = utils.initialOrderExpiryUnixTimestampSec(); this.props.updateOrderExpiry(defaultDateTime); } - private onDateChanged(e: any, date: Date) { + private _onDateChanged(e: any, date: Date) { const dateMoment = moment(date); this.setState({ dateMoment, @@ -96,12 +88,12 @@ export class ExpirationInput extends React.Component<ExpirationInputProps, Expir const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, this.state.timeMoment); this.props.updateOrderExpiry(timestamp); } - private onTimeChanged(e: any, time: Date) { + private _onTimeChanged(e: any, time: Date) { const timeMoment = moment(time); this.setState({ timeMoment, }); - const dateMoment = _.isUndefined(this.state.dateMoment) ? moment() : this.state.dateMoment; + const dateMoment = _.isUndefined(this.state.dateMoment) ? moment() : this.state.dateMoment; const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, timeMoment); this.props.updateOrderExpiry(timestamp); } diff --git a/packages/website/ts/components/inputs/hash_input.tsx b/packages/website/ts/components/inputs/hash_input.tsx index 25e7b5009..5a3d34fe6 100644 --- a/packages/website/ts/components/inputs/hash_input.tsx +++ b/packages/website/ts/components/inputs/hash_input.tsx @@ -1,11 +1,11 @@ -import {Order, ZeroEx} from '0x.js'; +import { Order, ZeroEx } from '0x.js'; import * as _ from 'lodash'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); -import {Blockchain} from 'ts/blockchain'; -import {FakeTextField} from 'ts/components/ui/fake_text_field'; -import {HashData, Styles} from 'ts/types'; -import {constants} from 'ts/utils/constants'; +import { Blockchain } from 'ts/blockchain'; +import { FakeTextField } from 'ts/components/ui/fake_text_field'; +import { HashData, Styles } from 'ts/types'; +import { constants } from 'ts/utils/constants'; const styles: Styles = { textField: { @@ -27,15 +27,11 @@ interface HashInputState {} export class HashInput extends React.Component<HashInputProps, HashInputState> { public render() { - const msgHashHex = this.props.blockchainIsLoaded ? this.generateMessageHashHex() : ''; + const msgHashHex = this.props.blockchainIsLoaded ? this._generateMessageHashHex() : ''; return ( <div> <FakeTextField label={this.props.label}> - <div - style={styles.textField} - data-tip={true} - data-for="hashTooltip" - > + <div style={styles.textField} data-tip={true} data-for="hashTooltip"> {msgHashHex} </div> </FakeTextField> @@ -43,7 +39,7 @@ export class HashInput extends React.Component<HashInputProps, HashInputState> { </div> ); } - private generateMessageHashHex() { + private _generateMessageHashHex() { const exchangeContractAddress = this.props.blockchain.getExchangeContractAddressIfExists(); const hashData = this.props.hashData; const order: Order = { diff --git a/packages/website/ts/components/inputs/identicon_address_input.tsx b/packages/website/ts/components/inputs/identicon_address_input.tsx index 692a092d9..4cf9af64d 100644 --- a/packages/website/ts/components/inputs/identicon_address_input.tsx +++ b/packages/website/ts/components/inputs/identicon_address_input.tsx @@ -1,11 +1,9 @@ import * as _ from 'lodash'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {Blockchain} from 'ts/blockchain'; -import {AddressInput} from 'ts/components/inputs/address_input'; -import {Identicon} from 'ts/components/ui/identicon'; -import {InputLabel} from 'ts/components/ui/input_label'; -import {RequiredLabel} from 'ts/components/ui/required_label'; +import { AddressInput } from 'ts/components/inputs/address_input'; +import { Identicon } from 'ts/components/ui/identicon'; +import { InputLabel } from 'ts/components/ui/input_label'; +import { RequiredLabel } from 'ts/components/ui/required_label'; interface IdenticonAddressInputProps { initialAddress: string; @@ -26,28 +24,27 @@ export class IdenticonAddressInput extends React.Component<IdenticonAddressInput }; } public render() { - const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : - this.props.label; + const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : this.props.label; return ( - <div className="relative" style={{width: '100%'}}> + <div className="relative" style={{ width: '100%' }}> <InputLabel text={label} /> <div className="flex"> - <div className="col col-1 pb1 pr1" style={{paddingTop: 13}}> + <div className="col col-1 pb1 pr1" style={{ paddingTop: 13 }}> <Identicon address={this.state.address} diameter={26} /> </div> - <div className="col col-11 pb1 pl1" style={{height: 65}}> + <div className="col col-11 pb1 pl1" style={{ height: 65 }}> <AddressInput hintText="e.g 0x75bE4F78AA3699B3A348c84bDB2a96c3Db..." shouldHideLabel={true} initialAddress={this.props.initialAddress} - updateAddress={this.updateAddress.bind(this)} + updateAddress={this._updateAddress.bind(this)} /> </div> </div> </div> ); } - private updateAddress(address?: string): void { + private _updateAddress(address?: string): void { this.setState({ address, }); diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx index f39341a4f..63966d759 100644 --- a/packages/website/ts/components/inputs/token_amount_input.tsx +++ b/packages/website/ts/components/inputs/token_amount_input.tsx @@ -1,16 +1,16 @@ -import {ZeroEx} from '0x.js'; -import BigNumber from 'bignumber.js'; +import { ZeroEx } from '0x.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {Link} from 'react-router-dom'; -import {BalanceBoundedInput} from 'ts/components/inputs/balance_bounded_input'; -import {InputErrMsg, Token, TokenState, ValidatedBigNumberCallback, WebsitePaths} from 'ts/types'; +import { Link } from 'react-router-dom'; +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 { - label: string; token: Token; tokenState: TokenState; + label?: string; amount?: BigNumber; shouldShowIncompleteErrs: boolean; shouldCheckBalance: boolean; @@ -19,51 +19,52 @@ interface TokenAmountInputProps { onVisitBalancesPageClick?: () => void; } -interface TokenAmountInputState {} +interface TokenAmountInputState {} export class TokenAmountInput extends React.Component<TokenAmountInputProps, TokenAmountInputState> { public render() { - const amount = this.props.amount ? - ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals) : - undefined; + const amount = this.props.amount + ? ZeroEx.toUnitAmount(this.props.amount, this.props.token.decimals) + : undefined; + const hasLabel = !_.isUndefined(this.props.label); return ( - <div className="flex overflow-hidden" style={{height: 84}}> + <div className="flex overflow-hidden" style={{ height: hasLabel ? 84 : 62 }}> <BalanceBoundedInput label={this.props.label} amount={amount} balance={ZeroEx.toUnitAmount(this.props.tokenState.balance, this.props.token.decimals)} - onChange={this.onChange.bind(this)} - validate={this.validate.bind(this)} + onChange={this._onChange.bind(this)} + validate={this._validate.bind(this)} shouldCheckBalance={this.props.shouldCheckBalance} shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs} onVisitBalancesPageClick={this.props.onVisitBalancesPageClick} /> - <div style={{paddingTop: 39}}> - {this.props.token.symbol} - </div> + <div style={{ paddingTop: hasLabel ? 39 : 14 }}>{this.props.token.symbol}</div> </div> ); } - private onChange(isValid: boolean, amount?: BigNumber) { + private _onChange(isValid: boolean, amount?: BigNumber) { let baseUnitAmount; if (!_.isUndefined(amount)) { baseUnitAmount = ZeroEx.toBaseUnitAmount(amount, this.props.token.decimals); } this.props.onChange(isValid, baseUnitAmount); } - private validate(amount: BigNumber): InputErrMsg { + private _validate(amount: BigNumber): InputErrMsg { if (this.props.shouldCheckAllowance && amount.gt(this.props.tokenState.allowance)) { return ( <span> Insufficient allowance.{' '} <Link to={`${WebsitePaths.Portal}/balances`} - style={{cursor: 'pointer', color: colors.grey900}} + style={{ cursor: 'pointer', color: colors.darkestGrey }} > - Set allowance + Set allowance </Link> </span> ); + } else { + return undefined; } } } diff --git a/packages/website/ts/components/inputs/token_input.tsx b/packages/website/ts/components/inputs/token_input.tsx index 8daa84650..5df19b28c 100644 --- a/packages/website/ts/components/inputs/token_input.tsx +++ b/packages/website/ts/components/inputs/token_input.tsx @@ -1,13 +1,13 @@ import * as _ from 'lodash'; import Paper from 'material-ui/Paper'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {Blockchain} from 'ts/blockchain'; -import {AssetPicker} from 'ts/components/generate_order/asset_picker'; -import {InputLabel} from 'ts/components/ui/input_label'; -import {TokenIcon} from 'ts/components/ui/token_icon'; -import {Dispatcher} from 'ts/redux/dispatcher'; -import {AssetToken, BlockchainErrs, Side, Token, TokenByAddress, TokenState} from 'ts/types'; +import { Blockchain } from 'ts/blockchain'; +import { AssetPicker } from 'ts/components/generate_order/asset_picker'; +import { InputLabel } from 'ts/components/ui/input_label'; +import { TokenIcon } from 'ts/components/ui/token_icon'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { AssetToken, BlockchainErrs, Side, Token, TokenByAddress } from 'ts/types'; +import { colors } from 'ts/utils/colors'; const TOKEN_ICON_DIMENSION = 80; @@ -51,18 +51,15 @@ export class TokenInput extends React.Component<TokenInputProps, TokenInputState </div> <Paper zDepth={1} - style={{cursor: 'pointer'}} - onMouseEnter={this.onToggleHover.bind(this, true)} - onMouseLeave={this.onToggleHover.bind(this, false)} - onClick={this.onAssetClicked.bind(this)} + style={{ cursor: 'pointer' }} + onMouseEnter={this._onToggleHover.bind(this, true)} + onMouseLeave={this._onToggleHover.bind(this, false)} + onClick={this._onAssetClicked.bind(this)} > - <div - className="mx-auto pt2" - style={{width: TOKEN_ICON_DIMENSION, ...iconStyles}} - > + <div className="mx-auto pt2" style={{ width: TOKEN_ICON_DIMENSION, ...iconStyles }}> <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} /> </div> - <div className="py1 center" style={{color: colors.grey500}}> + <div className="py1 center" style={{ color: colors.grey }}> {token.name} </div> </Paper> @@ -73,13 +70,13 @@ export class TokenInput extends React.Component<TokenInputProps, TokenInputState dispatcher={this.props.dispatcher} isOpen={this.state.isPickerOpen} currentTokenAddress={this.props.assetToken.address} - onTokenChosen={this.onTokenChosen.bind(this)} + onTokenChosen={this._onTokenChosen.bind(this)} tokenByAddress={this.props.tokenByAddress} /> </div> ); } - private onTokenChosen(tokenAddress: string) { + private _onTokenChosen(tokenAddress: string) { const assetToken: AssetToken = { address: tokenAddress, amount: this.props.assetToken.amount, @@ -89,13 +86,13 @@ export class TokenInput extends React.Component<TokenInputProps, TokenInputState isPickerOpen: false, }); } - private onToggleHover(isHoveringIcon: boolean) { + private _onToggleHover(isHoveringIcon: boolean) { this.setState({ isHoveringIcon, }); } - private onAssetClicked() { - if (this.props.blockchainErr !== '') { + private _onAssetClicked() { + if (this.props.blockchainErr !== BlockchainErrs.NoError) { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); return; } diff --git a/packages/website/ts/components/order_json.tsx b/packages/website/ts/components/order_json.tsx index 073abe419..1b6b32a04 100644 --- a/packages/website/ts/components/order_json.tsx +++ b/packages/website/ts/components/order_json.tsx @@ -1,15 +1,14 @@ -import BigNumber from 'bignumber.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Paper from 'material-ui/Paper'; -import {colors} from 'material-ui/styles'; import TextField from 'material-ui/TextField'; import * as React from 'react'; -import {CopyIcon} from 'ts/components/ui/copy_icon'; -import {Order, SideToAssetToken, SignatureData, TokenByAddress, WebsitePaths} from 'ts/types'; -import {configs} from 'ts/utils/configs'; -import {constants} from 'ts/utils/constants'; -import {errorReporter} from 'ts/utils/error_reporter'; -import {utils} from 'ts/utils/utils'; +import { CopyIcon } from 'ts/components/ui/copy_icon'; +import { SideToAssetToken, SignatureData, TokenByAddress, WebsitePaths } from 'ts/types'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; +import { errorReporter } from 'ts/utils/error_reporter'; +import { utils } from 'ts/utils/utils'; interface OrderJSONProps { exchangeContractIfExists: string; @@ -37,73 +36,79 @@ export class OrderJSON extends React.Component<OrderJSONProps, OrderJSONState> { shareLink: '', }; // tslint:disable-next-line:no-floating-promises - this.setShareLinkAsync(); + this._setShareLinkAsync(); } public render() { - const order = utils.generateOrder(this.props.networkId, this.props.exchangeContractIfExists, - this.props.sideToAssetToken, this.props.orderExpiryTimestamp, - this.props.orderTakerAddress, this.props.orderMakerAddress, - this.props.orderMakerFee, this.props.orderTakerFee, - this.props.orderFeeRecipient, this.props.orderSignatureData, - this.props.tokenByAddress, this.props.orderSalt); + const order = utils.generateOrder( + this.props.networkId, + this.props.exchangeContractIfExists, + this.props.sideToAssetToken, + this.props.orderExpiryTimestamp, + this.props.orderTakerAddress, + this.props.orderMakerAddress, + this.props.orderMakerFee, + this.props.orderTakerFee, + this.props.orderFeeRecipient, + this.props.orderSignatureData, + this.props.tokenByAddress, + this.props.orderSalt, + ); const orderJSON = JSON.stringify(order); return ( <div> <div className="pb2"> - You have successfully generated and cryptographically signed an order! The{' '} - following JSON contains the order parameters and cryptographic signature that{' '} - your counterparty will need to execute a trade with you. + You have successfully generated and cryptographically signed an order! The following JSON contains + the order parameters and cryptographic signature that your counterparty will need to execute a trade + with you. </div> <div className="pb2 flex"> - <div - className="inline-block pl1" - style={{top: 1}} - > + <div className="inline-block pl1" style={{ top: 1 }}> <CopyIcon data={orderJSON} callToAction="Copy" /> </div> </div> <Paper className="center overflow-hidden"> <TextField id="orderJSON" - style={{width: 710}} + style={{ width: 710 }} value={JSON.stringify(order, null, '\t')} multiLine={true} rows={2} rowsMax={8} - underlineStyle={{display: 'none'}} + underlineStyle={{ display: 'none' }} /> </Paper> <div className="pt3 pb2 center"> + <div>Share your signed order!</div> <div> - Share your signed order! - </div> - <div> - <div className="mx-auto overflow-hidden" style={{width: 152}}> - <TextField - id={`${this.state.shareLink}-bitly`} - value={this.state.shareLink} - /> + <div className="mx-auto overflow-hidden" style={{ width: 152 }}> + <TextField id={`${this.state.shareLink}-bitly`} value={this.state.shareLink} /> </div> </div> - <div className="mx-auto pt1 flex" style={{width: 91}}> + <div className="mx-auto pt1 flex" style={{ width: 91 }}> <div> <i - style={{cursor: 'pointer', fontSize: 29}} - onClick={this.shareViaFacebook.bind(this)} + style={{ cursor: 'pointer', fontSize: 29 }} + onClick={this._shareViaFacebook.bind(this)} className="zmdi zmdi-facebook-box" /> </div> - <div className="pl1" style={{position: 'relative', width: 28}}> + <div className="pl1" style={{ position: 'relative', width: 28 }}> <i - style={{cursor: 'pointer', fontSize: 32, position: 'absolute', top: -2, left: 8}} - onClick={this.shareViaEmailAsync.bind(this)} + style={{ + cursor: 'pointer', + fontSize: 32, + position: 'absolute', + top: -2, + left: 8, + }} + onClick={this._shareViaEmailAsync.bind(this)} className="zmdi zmdi-email" /> </div> <div className="pl1"> <i - style={{cursor: 'pointer', fontSize: 29}} - onClick={this.shareViaTwitterAsync.bind(this)} + style={{ cursor: 'pointer', fontSize: 29 }} + onClick={this._shareViaTwitterAsync.bind(this)} className="zmdi zmdi-twitter-box" /> </div> @@ -112,35 +117,38 @@ export class OrderJSON extends React.Component<OrderJSONProps, OrderJSONState> { </div> ); } - private async shareViaTwitterAsync() { + private async _shareViaTwitterAsync() { const tweetText = encodeURIComponent(`Fill my order using the 0x protocol: ${this.state.shareLink}`); window.open(`https://twitter.com/intent/tweet?text=${tweetText}`, 'Share your order', 'width=500,height=400'); } - private async shareViaFacebook() { - (window as any).FB.ui({ - display: 'popup', - href: this.state.shareLink, - method: 'share', - }, _.noop); + private async _shareViaFacebook() { + (window as any).FB.ui( + { + display: 'popup', + href: this.state.shareLink, + method: 'share', + }, + _.noop, + ); } - private async shareViaEmailAsync() { - const encodedSubject = encodeURIComponent('Let\'s trade using the 0x protocol'); + private async _shareViaEmailAsync() { + const encodedSubject = encodeURIComponent("Let's trade using the 0x protocol"); const encodedBody = encodeURIComponent(`I generated an order with the 0x protocol. You can see and fill it here: ${this.state.shareLink}`); const mailToLink = `mailto:mail@example.org?subject=${encodedSubject}&body=${encodedBody}`; window.open(mailToLink, '_blank'); } - private async setShareLinkAsync() { - const shareLink = await this.generateShareLinkAsync(); + private async _setShareLinkAsync() { + const shareLink = await this._generateShareLinkAsync(); this.setState({ shareLink, }); } - private async generateShareLinkAsync(): Promise<string> { - const longUrl = encodeURIComponent(this.getOrderUrl()); - const bitlyRequestUrl = constants.BITLY_ENDPOINT + '/v3/shorten?' + - 'access_token=' + constants.BITLY_ACCESS_TOKEN + - '&longUrl=' + longUrl; + private async _generateShareLinkAsync(): Promise<string> { + const longUrl = encodeURIComponent(this._getOrderUrl()); + const bitlyRequestUrl = `${constants.URL_BITLY_API}/v3/shorten?access_token=${ + configs.BITLY_ACCESS_TOKEN + }&longUrl=${longUrl}`; const response = await fetch(bitlyRequestUrl); const responseBody = await response.text(); const bodyObj = JSON.parse(responseBody); @@ -150,14 +158,23 @@ You can see and fill it here: ${this.state.shareLink}`); await errorReporter.reportAsync(new Error(`Bitly returned non-200: ${JSON.stringify(response)}`)); return ''; } - return (bodyObj).data.url; + return bodyObj.data.url; } - private getOrderUrl() { - const order = utils.generateOrder(this.props.networkId, this.props.exchangeContractIfExists, - this.props.sideToAssetToken, this.props.orderExpiryTimestamp, this.props.orderTakerAddress, - this.props.orderMakerAddress, this.props.orderMakerFee, this.props.orderTakerFee, - this.props.orderFeeRecipient, this.props.orderSignatureData, this.props.tokenByAddress, - this.props.orderSalt); + private _getOrderUrl() { + const order = utils.generateOrder( + this.props.networkId, + this.props.exchangeContractIfExists, + this.props.sideToAssetToken, + this.props.orderExpiryTimestamp, + this.props.orderTakerAddress, + this.props.orderMakerAddress, + this.props.orderMakerFee, + this.props.orderTakerFee, + this.props.orderFeeRecipient, + this.props.orderSignatureData, + this.props.tokenByAddress, + this.props.orderSalt, + ); const orderJSONString = JSON.stringify(order); const orderUrl = `${configs.BASE_URL}${WebsitePaths.Portal}/fill?order=${orderJSONString}`; return orderUrl; diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx index 62a5d2eac..e2e28e8b6 100644 --- a/packages/website/ts/components/portal.tsx +++ b/packages/website/ts/components/portal.tsx @@ -1,44 +1,41 @@ -import BigNumber from 'bignumber.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Paper from 'material-ui/Paper'; -import RaisedButton from 'material-ui/RaisedButton'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; 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 {PortalDisclaimerDialog} from 'ts/components/dialogs/portal_disclaimer_dialog'; -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 {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 { Route, Switch } from 'react-router-dom'; +import { Blockchain } from 'ts/blockchain'; +import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_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'; +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 { 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 { orderSchema } from 'ts/schemas/order_schema'; +import { SchemaValidator } from 'ts/schemas/validator'; import { BlockchainErrs, - Fill, HashData, Order, ScreenWidths, - Side, - Styles, Token, TokenByAddress, TokenStateByAddress, WebsitePaths, } from 'ts/types'; -import {configs} from 'ts/utils/configs'; -import {constants} from 'ts/utils/constants'; -import {utils} from 'ts/utils/utils'; +import { colors } from 'ts/utils/colors'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; +import { utils } from 'ts/utils/utils'; const THROTTLE_TIMEOUT = 100; @@ -60,69 +57,57 @@ export interface PortalAllProps { shouldBlockchainErrDialogBeOpen: boolean; userSuppliedOrderCache: Order; location: Location; - flashMessage?: string|React.ReactNode; + flashMessage?: string | React.ReactNode; } interface PortalAllState { prevNetworkId: number; prevNodeVersion: string; prevUserAddress: string; - hasAcceptedDisclaimer: boolean; + prevPathname: string; + isDisclaimerDialogOpen: boolean; + isWethNoticeDialogOpen: boolean; } -const styles: Styles = { - button: { - color: 'white', - }, - headline: { - fontSize: 20, - fontWeight: 400, - marginBottom: 12, - paddingTop: 16, - }, - inkBar: { - background: colors.amber600, - }, - menuItem: { - padding: '0px 16px 0px 48px', - }, - tabItemContainer: { - background: colors.blueGrey500, - borderRadius: '4px 4px 0 0', - }, -}; - export class Portal extends React.Component<PortalAllProps, PortalAllState> { - private blockchain: Blockchain; - private sharedOrderIfExists: Order; - private throttledScreenWidthUpdate: () => void; + private _blockchain: Blockchain; + private _sharedOrderIfExists: Order; + private _throttledScreenWidthUpdate: () => void; + public static hasAlreadyDismissedWethNotice() { + const didDismissWethNotice = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE); + const hasAlreadyDismissedWethNotice = !_.isUndefined(didDismissWethNotice) && !_.isEmpty(didDismissWethNotice); + return hasAlreadyDismissedWethNotice; + } constructor(props: PortalAllProps) { super(props); - this.sharedOrderIfExists = this.getSharedOrderIfExists(); - this.throttledScreenWidthUpdate = _.throttle(this.updateScreenWidth.bind(this), THROTTLE_TIMEOUT); + this._sharedOrderIfExists = this._getSharedOrderIfExists(); + this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT); + + const isViewingBalances = _.includes(props.location.pathname, `${WebsitePaths.Portal}/balances`); + const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice(); + + const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER); + const hasAcceptedDisclaimer = + !_.isUndefined(didAcceptPortalDisclaimer) && !_.isEmpty(didAcceptPortalDisclaimer); this.state = { prevNetworkId: this.props.networkId, prevNodeVersion: this.props.nodeVersion, prevUserAddress: this.props.userAddress, - hasAcceptedDisclaimer: false, + prevPathname: this.props.location.pathname, + isDisclaimerDialogOpen: !hasAcceptedDisclaimer, + isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, }; } public componentDidMount() { - window.addEventListener('resize', this.throttledScreenWidthUpdate); + window.addEventListener('resize', this._throttledScreenWidthUpdate); window.scrollTo(0, 0); } public componentWillMount() { - this.blockchain = new Blockchain(this.props.dispatcher); - const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.ACCEPT_DISCLAIMER_LOCAL_STORAGE_KEY); - const hasAcceptedDisclaimer = !_.isUndefined(didAcceptPortalDisclaimer) && - !_.isEmpty(didAcceptPortalDisclaimer); - this.setState({ - hasAcceptedDisclaimer, - }); + this._blockchain = new Blockchain(this.props.dispatcher); } public componentWillUnmount() { - this.blockchain.destroy(); - window.removeEventListener('resize', this.throttledScreenWidthUpdate); + this._blockchain.destroy(); + window.removeEventListener('resize', this._throttledScreenWidthUpdate); // We re-set the entire redux state when the portal is unmounted so that when it is re-rendered // the initialization process always occurs from the same base state. This helps avoid // initialization inconsistencies (i.e While the portal was unrendered, the user might have @@ -132,19 +117,18 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { public componentWillReceiveProps(nextProps: PortalAllProps) { if (nextProps.networkId !== this.state.prevNetworkId) { // tslint:disable-next-line:no-floating-promises - this.blockchain.networkIdUpdatedFireAndForgetAsync(nextProps.networkId); + this._blockchain.networkIdUpdatedFireAndForgetAsync(nextProps.networkId); this.setState({ prevNetworkId: nextProps.networkId, }); } if (nextProps.userAddress !== this.state.prevUserAddress) { // tslint:disable-next-line:no-floating-promises - this.blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress); - if (!_.isEmpty(nextProps.userAddress) && - nextProps.blockchainIsLoaded) { + this._blockchain.userAddressUpdatedFireAndForgetAsync(nextProps.userAddress); + if (!_.isEmpty(nextProps.userAddress) && nextProps.blockchainIsLoaded) { const tokens = _.values(nextProps.tokenByAddress); // tslint:disable-next-line:no-floating-promises - this.updateBalanceAndAllowanceWithLoadingScreenAsync(tokens); + this._updateBalanceAndAllowanceWithLoadingScreenAsync(tokens); } this.setState({ prevUserAddress: nextProps.userAddress, @@ -152,105 +136,128 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { } if (nextProps.nodeVersion !== this.state.prevNodeVersion) { // tslint:disable-next-line:no-floating-promises - this.blockchain.nodeVersionUpdatedFireAndForgetAsync(nextProps.nodeVersion); + this._blockchain.nodeVersionUpdatedFireAndForgetAsync(nextProps.nodeVersion); + } + if (nextProps.location.pathname !== this.state.prevPathname) { + const isViewingBalances = _.includes(nextProps.location.pathname, `${WebsitePaths.Portal}/balances`); + const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice(); + this.setState({ + prevPathname: nextProps.location.pathname, + isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances, + }); } } public render() { - const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher - .updateShouldBlockchainErrDialogBeOpen.bind(this.props.dispatcher); + const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen.bind( + this.props.dispatcher, + ); const portalStyle: React.CSSProperties = { minHeight: '100vh', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', }; + const portalMenuContainerStyle: React.CSSProperties = { + overflow: 'hidden', + backgroundColor: colors.darkestGrey, + color: colors.white, + }; return ( <div style={portalStyle}> - <DocumentTitle title="0x Portal DApp"/> + <DocumentTitle title="0x Portal DApp" /> <TopBar userAddress={this.props.userAddress} blockchainIsLoaded={this.props.blockchainIsLoaded} location={this.props.location} /> - <div id="portal" className="mx-auto max-width-4 pt4" style={{width: '100%'}}> + <div id="portal" className="mx-auto max-width-4" style={{ width: '100%' }}> <Paper className="mb3 mt2"> - {!configs.isMainnetEnabled && this.props.networkId === constants.MAINNET_NETWORK_ID ? + {!configs.IS_MAINNET_ENABLED && this.props.networkId === constants.NETWORK_ID_MAINNET ? ( <div className="p3 center"> <div className="h2 py2">Mainnet unavailable</div> <div className="mx-auto pb2 pt2"> - <img - src="/images/zrx_token.png" - style={{width: 150}} - /> + <img src="/images/zrx_token.png" style={{ width: 150 }} /> </div> <div> 0x portal is currently unavailable on the Ethereum mainnet. - <div> - To try it out, switch to the Kovan test network - (networkId: 42). - </div> - <div className="py2"> - Check back soon! - </div> + <div>To try it out, switch to the Kovan test network (networkId: 42).</div> + <div className="py2">Check back soon!</div> </div> - </div> : + </div> + ) : ( <div className="mx-auto flex"> - <div - className="col col-2 pr2 pt1 sm-hide xs-hide" - style={{overflow: 'hidden', backgroundColor: 'rgb(39, 39, 39)', color: 'white'}} - > - <PortalMenu menuItemStyle={{color: 'white'}} /> + <div className="col col-2 pr2 pt1 sm-hide xs-hide" style={portalMenuContainerStyle}> + <PortalMenu menuItemStyle={{ color: colors.white }} /> </div> <div className="col col-12 lg-col-10 md-col-10 sm-col sm-col-12"> - <div className="py2" style={{backgroundColor: colors.grey50}}> - {this.props.blockchainIsLoaded ? + <div className="py2" style={{ backgroundColor: colors.grey50 }}> + {this.props.blockchainIsLoaded ? ( <Switch> <Route + path={`${WebsitePaths.Portal}/weth`} + render={this._renderEthWrapper.bind(this)} + /> + <Route path={`${WebsitePaths.Portal}/fill`} - render={this.renderFillOrder.bind(this)} + render={this._renderFillOrder.bind(this)} /> <Route path={`${WebsitePaths.Portal}/balances`} - render={this.renderTokenBalances.bind(this)} + render={this._renderTokenBalances.bind(this)} /> <Route path={`${WebsitePaths.Portal}/trades`} - component={this.renderTradeHistory.bind(this)} + component={this._renderTradeHistory.bind(this)} /> <Route path={`${WebsitePaths.Home}`} - render={this.renderGenerateOrderForm.bind(this)} + render={this._renderGenerateOrderForm.bind(this)} /> - </Switch> : + </Switch> + ) : ( <Loading /> - } + )} </div> </div> </div> - } + )} </Paper> <BlockchainErrDialog - blockchain={this.blockchain} + blockchain={this._blockchain} blockchainErr={this.props.blockchainErr} isOpen={this.props.shouldBlockchainErrDialogBeOpen} userAddress={this.props.userAddress} toggleDialogFn={updateShouldBlockchainErrDialogBeOpen} networkId={this.props.networkId} /> - <PortalDisclaimerDialog - isOpen={!this.state.hasAcceptedDisclaimer} - onToggleDialog={this.onPortalDisclaimerAccepted.bind(this)} + <WrappedEthSectionNoticeDialog + isOpen={this.state.isWethNoticeDialogOpen} + onToggleDialog={this._onWethNoticeAccepted.bind(this)} /> - <FlashMessage - dispatcher={this.props.dispatcher} - flashMessage={this.props.flashMessage} + <PortalDisclaimerDialog + isOpen={this.state.isDisclaimerDialogOpen} + onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)} /> + <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} /> </div> - <Footer location={this.props.location} /> + <Footer /> </div> ); } - private renderTradeHistory() { + private _renderEthWrapper() { + return ( + <EthWrappers + networkId={this.props.networkId} + blockchain={this._blockchain} + dispatcher={this.props.dispatcher} + tokenByAddress={this.props.tokenByAddress} + tokenStateByAddress={this.props.tokenStateByAddress} + userAddress={this.props.userAddress} + userEtherBalance={this.props.userEtherBalance} + /> + ); + } + private _renderTradeHistory() { return ( <TradeHistory tokenByAddress={this.props.tokenByAddress} @@ -259,10 +266,10 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { /> ); } - private renderTokenBalances() { + private _renderTokenBalances() { return ( <TokenBalances - blockchain={this.blockchain} + blockchain={this._blockchain} blockchainErr={this.props.blockchainErr} blockchainIsLoaded={this.props.blockchainIsLoaded} dispatcher={this.props.dispatcher} @@ -275,16 +282,16 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { /> ); } - private renderFillOrder(match: any, location: Location, history: History) { - const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache) ? - this.props.userSuppliedOrderCache : - this.sharedOrderIfExists; + private _renderFillOrder(match: any, location: Location, history: History) { + const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache) + ? this.props.userSuppliedOrderCache + : this._sharedOrderIfExists; return ( <FillOrder - blockchain={this.blockchain} + blockchain={this._blockchain} blockchainErr={this.props.blockchainErr} initialOrder={initialFillOrder} - isOrderInUrl={!_.isUndefined(this.sharedOrderIfExists)} + isOrderInUrl={!_.isUndefined(this._sharedOrderIfExists)} orderFillAmount={this.props.orderFillAmount} networkId={this.props.networkId} userAddress={this.props.userAddress} @@ -294,25 +301,31 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { /> ); } - private renderGenerateOrderForm(match: any, location: Location, history: History) { + private _renderGenerateOrderForm(match: any, location: Location, history: History) { return ( <GenerateOrderForm - blockchain={this.blockchain} + blockchain={this._blockchain} hashData={this.props.hashData} dispatcher={this.props.dispatcher} /> ); } - private onPortalDisclaimerAccepted() { - localStorage.setItem(constants.ACCEPT_DISCLAIMER_LOCAL_STORAGE_KEY, 'set'); + private _onPortalDisclaimerAccepted() { + localStorage.setItem(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER, 'set'); + this.setState({ + isDisclaimerDialogOpen: false, + }); + } + private _onWethNoticeAccepted() { + localStorage.setItem(constants.LOCAL_STORAGE_KEY_DISMISS_WETH_NOTICE, 'set'); this.setState({ - hasAcceptedDisclaimer: true, + isWethNoticeDialogOpen: false, }); } - private getSharedOrderIfExists(): Order { + private _getSharedOrderIfExists(): Order | undefined { const queryString = window.location.search; if (queryString.length === 0) { - return; + return undefined; } const queryParams = queryString.substring(1).split('&'); const orderQueryParam = _.find(queryParams, queryParam => { @@ -320,11 +333,11 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { return queryPair[0] === 'order'; }); if (_.isUndefined(orderQueryParam)) { - return; + return undefined; } const orderPair = orderQueryParam.split('='); if (orderPair.length !== 2) { - return; + return undefined; } const validator = new SchemaValidator(); @@ -332,17 +345,17 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { const validationResult = validator.validate(order, orderSchema); if (validationResult.errors.length > 0) { utils.consoleLog(`Invalid shared order: ${validationResult.errors}`); - return; + return undefined; } return order; } - private updateScreenWidth() { + private _updateScreenWidth() { const newScreenWidth = utils.getScreenWidth(); this.props.dispatcher.updateScreenWidth(newScreenWidth); } - private async updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) { + private async _updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) { this.props.dispatcher.updateBlockchainIsLoaded(false); - await this.blockchain.updateTokenBalancesAndAllowancesAsync(tokens); + await this._blockchain.updateTokenBalancesAndAllowancesAsync(tokens); this.props.dispatcher.updateBlockchainIsLoaded(true); } } diff --git a/packages/website/ts/components/portal_menu.tsx b/packages/website/ts/components/portal_menu.tsx index 869df3e71..a2f9340c8 100644 --- a/packages/website/ts/components/portal_menu.tsx +++ b/packages/website/ts/components/portal_menu.tsx @@ -1,8 +1,7 @@ import * as _ from 'lodash'; import * as React from 'react'; -import {Link} from 'react-router-dom'; -import {MenuItem} from 'ts/components/ui/menu_item'; -import {WebsitePaths} from 'ts/types'; +import { MenuItem } from 'ts/components/ui/menu_item'; +import { WebsitePaths } from 'ts/types'; export interface PortalMenuProps { menuItemStyle: React.CSSProperties; @@ -24,7 +23,7 @@ export class PortalMenu extends React.Component<PortalMenuProps, PortalMenuState to={`${WebsitePaths.Portal}`} onClick={this.props.onClick.bind(this)} > - {this.renderMenuItemWithIcon('Generate order', 'zmdi-arrow-right-top')} + {this._renderMenuItemWithIcon('Generate order', 'zmdi-arrow-right-top')} </MenuItem> <MenuItem style={this.props.menuItemStyle} @@ -32,7 +31,7 @@ export class PortalMenu extends React.Component<PortalMenuProps, PortalMenuState to={`${WebsitePaths.Portal}/fill`} onClick={this.props.onClick.bind(this)} > - {this.renderMenuItemWithIcon('Fill order', 'zmdi-arrow-left-bottom')} + {this._renderMenuItemWithIcon('Fill order', 'zmdi-arrow-left-bottom')} </MenuItem> <MenuItem style={this.props.menuItemStyle} @@ -40,7 +39,7 @@ export class PortalMenu extends React.Component<PortalMenuProps, PortalMenuState to={`${WebsitePaths.Portal}/balances`} onClick={this.props.onClick.bind(this)} > - {this.renderMenuItemWithIcon('Balances', 'zmdi-balance-wallet')} + {this._renderMenuItemWithIcon('Balances', 'zmdi-balance-wallet')} </MenuItem> <MenuItem style={this.props.menuItemStyle} @@ -48,20 +47,26 @@ export class PortalMenu extends React.Component<PortalMenuProps, PortalMenuState to={`${WebsitePaths.Portal}/trades`} onClick={this.props.onClick.bind(this)} > - {this.renderMenuItemWithIcon('Trade history', 'zmdi-format-list-bulleted')} + {this._renderMenuItemWithIcon('Trade history', 'zmdi-format-list-bulleted')} + </MenuItem> + <MenuItem + style={this.props.menuItemStyle} + className="py2" + to={`${WebsitePaths.Portal}/weth`} + onClick={this.props.onClick.bind(this)} + > + {this._renderMenuItemWithIcon('Wrap ETH', 'zmdi-circle-o')} </MenuItem> </div> ); } - private renderMenuItemWithIcon(title: string, iconName: string) { + private _renderMenuItemWithIcon(title: string, iconName: string) { return ( - <div className="flex" style={{fontWeight: 100}}> + <div className="flex" style={{ fontWeight: 100 }}> <div className="pr1 pl2"> - <i style={{fontSize: 20}} className={`zmdi ${iconName}`} /> - </div> - <div className="pl1"> - {title} + <i style={{ fontSize: 20 }} className={`zmdi ${iconName}`} /> </div> + <div className="pl1">{title}</div> </div> ); } diff --git a/packages/website/ts/components/send_button.tsx b/packages/website/ts/components/send_button.tsx index da8dd2a9b..f94ec346a 100644 --- a/packages/website/ts/components/send_button.tsx +++ b/packages/website/ts/components/send_button.tsx @@ -1,15 +1,13 @@ -import {ZeroEx} from '0x.js'; -import BigNumber from 'bignumber.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import RaisedButton from 'material-ui/RaisedButton'; import * as React from 'react'; -import {Blockchain} from 'ts/blockchain'; -import {SendDialog} from 'ts/components/dialogs/send_dialog'; -import {Dispatcher} from 'ts/redux/dispatcher'; -import {BlockchainCallErrs, Token, TokenState} from 'ts/types'; -import {constants} from 'ts/utils/constants'; -import {errorReporter} from 'ts/utils/error_reporter'; -import {utils} from 'ts/utils/utils'; +import { Blockchain } from 'ts/blockchain'; +import { SendDialog } from 'ts/components/dialogs/send_dialog'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { BlockchainCallErrs, Token, TokenState } from 'ts/types'; +import { errorReporter } from 'ts/utils/error_reporter'; +import { utils } from 'ts/utils/utils'; interface SendButtonProps { token: Token; @@ -33,36 +31,36 @@ export class SendButton extends React.Component<SendButtonProps, SendButtonState }; } public render() { - const labelStyle = this.state.isSending ? {fontSize: 10} : {}; + const labelStyle = this.state.isSending ? { fontSize: 10 } : {}; return ( <div> <RaisedButton - style={{width: '100%'}} + style={{ width: '100%' }} labelStyle={labelStyle} disabled={this.state.isSending} label={this.state.isSending ? 'Sending...' : 'Send'} - onClick={this.toggleSendDialog.bind(this)} + onClick={this._toggleSendDialog.bind(this)} /> <SendDialog isOpen={this.state.isSendDialogVisible} - onComplete={this.onSendAmountSelectedAsync.bind(this)} - onCancelled={this.toggleSendDialog.bind(this)} + onComplete={this._onSendAmountSelectedAsync.bind(this)} + onCancelled={this._toggleSendDialog.bind(this)} token={this.props.token} tokenState={this.props.tokenState} /> </div> ); } - private toggleSendDialog() { + private _toggleSendDialog() { this.setState({ isSendDialogVisible: !this.state.isSendDialogVisible, }); } - private async onSendAmountSelectedAsync(recipient: string, value: BigNumber) { + private async _onSendAmountSelectedAsync(recipient: string, value: BigNumber) { this.setState({ isSending: true, }); - this.toggleSendDialog(); + this._toggleSendDialog(); const token = this.props.token; const tokenState = this.props.tokenState; let balance = tokenState.balance; @@ -72,14 +70,14 @@ export class SendButton extends React.Component<SendButtonProps, SendButtonState this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance); } catch (err) { const errMsg = `${err}`; - if (_.includes(errMsg, BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES)) { + if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); return; } else if (!_.includes(errMsg, 'User denied transaction')) { utils.consoleLog(`Unexpected error encountered: ${err}`); utils.consoleLog(err.stack); - await errorReporter.reportAsync(err); this.props.onError(); + await errorReporter.reportAsync(err); } } this.setState({ diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx index ae5ef9222..2cef413c7 100644 --- a/packages/website/ts/components/token_balances.tsx +++ b/packages/website/ts/components/token_balances.tsx @@ -1,5 +1,5 @@ -import {ZeroEx} from '0x.js'; -import BigNumber from 'bignumber.js'; +import { ZeroEx } from '0x.js'; +import { BigNumber } from '@0xproject/utils'; import DharmaLoanFrame from 'dharma-loan-frame'; import * as _ from 'lodash'; import Dialog from 'material-ui/Dialog'; @@ -7,31 +7,21 @@ import Divider from 'material-ui/Divider'; import FlatButton from 'material-ui/FlatButton'; import FloatingActionButton from 'material-ui/FloatingActionButton'; import RaisedButton from 'material-ui/RaisedButton'; -import {colors} from 'material-ui/styles'; import ContentAdd from 'material-ui/svg-icons/content/add'; import ContentRemove from 'material-ui/svg-icons/content/remove'; -import { - Table, - TableBody, - TableHeader, - TableHeaderColumn, - TableRow, - TableRowColumn, -} from 'material-ui/Table'; -import QueryString = require('query-string'); +import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn } from 'material-ui/Table'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); import firstBy = require('thenby'); -import {Blockchain} from 'ts/blockchain'; -import {EthWethConversionButton} from 'ts/components/eth_weth_conversion_button'; -import {AssetPicker} from 'ts/components/generate_order/asset_picker'; -import {AllowanceToggle} from 'ts/components/inputs/allowance_toggle'; -import {SendButton} from 'ts/components/send_button'; -import {HelpTooltip} from 'ts/components/ui/help_tooltip'; -import {LifeCycleRaisedButton} from 'ts/components/ui/lifecycle_raised_button'; -import {TokenIcon} from 'ts/components/ui/token_icon'; -import {trackedTokenStorage} from 'ts/local_storage/tracked_token_storage'; -import {Dispatcher} from 'ts/redux/dispatcher'; +import { Blockchain } from 'ts/blockchain'; +import { AssetPicker } from 'ts/components/generate_order/asset_picker'; +import { AllowanceToggle } from 'ts/components/inputs/allowance_toggle'; +import { SendButton } from 'ts/components/send_button'; +import { HelpTooltip } from 'ts/components/ui/help_tooltip'; +import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; +import { TokenIcon } from 'ts/components/ui/token_icon'; +import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; +import { Dispatcher } from 'ts/redux/dispatcher'; import { BalanceErrs, BlockchainCallErrs, @@ -44,10 +34,11 @@ import { TokenStateByAddress, TokenVisibility, } from 'ts/types'; -import {configs} from 'ts/utils/configs'; -import {constants} from 'ts/utils/constants'; -import {errorReporter} from 'ts/utils/error_reporter'; -import {utils} from 'ts/utils/utils'; +import { colors } from 'ts/utils/colors'; +import { configs } from 'ts/utils/configs'; +import { constants } from 'ts/utils/constants'; +import { errorReporter } from 'ts/utils/error_reporter'; +import { utils } from 'ts/utils/utils'; const ETHER_ICON_PATH = '/images/ether.png'; const ETHER_TOKEN_SYMBOL = 'WETH'; @@ -117,7 +108,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala 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, 18); + const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, constants.DECIMAL_PLACES_ZRX); this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`); } this.setState({ @@ -135,7 +126,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala key="errorOkBtn" label="Ok" primary={true} - onTouchTap={this.onErrorDialogToggle.bind(this, false)} + onTouchTap={this._onErrorDialogToggle.bind(this, false)} />, ]; const dharmaDialogActions = [ @@ -143,10 +134,10 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala key="dharmaCloseBtn" label="Close" primary={true} - onTouchTap={this.onDharmaDialogToggle.bind(this, false)} + onTouchTap={this._onDharmaDialogToggle.bind(this, false)} />, ]; - const isTestNetwork = this.props.networkId === constants.TESTNET_NETWORK_ID; + const isTestNetwork = this.props.networkId === constants.NETWORK_ID_TESTNET; const dharmaButtonColumnStyle = { paddingLeft: 3, display: isTestNetwork ? 'table-cell' : 'none', @@ -155,194 +146,141 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala display: isTestNetwork ? 'none' : 'table-cell', }; const allTokenRowHeight = _.size(this.props.tokenByAddress) * TOKEN_TABLE_ROW_HEIGHT; - const tokenTableHeight = allTokenRowHeight < MAX_TOKEN_TABLE_HEIGHT ? - allTokenRowHeight : - MAX_TOKEN_TABLE_HEIGHT; - const isSmallScreen = this.props.screenWidth === ScreenWidths.SM; + const tokenTableHeight = + allTokenRowHeight < MAX_TOKEN_TABLE_HEIGHT ? allTokenRowHeight : MAX_TOKEN_TABLE_HEIGHT; + const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; - const dharmaLoanExplanation = 'If you need access to larger amounts of ether,<br> \ + const dharmaLoanExplanation = + 'If you need access to larger amounts of ether,<br> \ you can request a loan from the Dharma Loan<br> \ network. Your loan should be funded in 5<br> \ minutes or less.'; - const allowanceExplanation = '0x smart contracts require access to your<br> \ + const allowanceExplanation = + '0x smart contracts require access to your<br> \ token balances in order to execute trades.<br> \ - Toggling permissions sets an allowance for the<br> \ + Toggling sets an allowance for the<br> \ smart contract so you can start trading that token.'; return ( <div className="lg-px4 md-px4 sm-px1 pb2"> <h3>{isTestNetwork ? 'Test ether' : 'Ether'}</h3> <Divider /> <div className="pt2 pb2"> - {isTestNetwork ? - '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. \ - You can convert between Ether and Ether Tokens by clicking the "convert" button below.' - } + {isTestNetwork + ? '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. \ + You can convert between Ether and Ether Tokens from the "Wrap ETH" tab.'} </div> - <Table - selectable={false} - style={styles.bgColor} - > + <Table selectable={false} style={styles.bgColor}> <TableHeader displaySelectAll={false} adjustForCheckbox={false}> <TableRow> <TableHeaderColumn>Currency</TableHeaderColumn> <TableHeaderColumn>Balance</TableHeaderColumn> - <TableRowColumn - className="sm-hide xs-hide" - style={stubColumnStyle} - /> - { - isTestNetwork && - <TableHeaderColumn - style={{paddingLeft: 3}} - > + <TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} /> + {isTestNetwork && ( + <TableHeaderColumn style={{ paddingLeft: 3 }}> {isSmallScreen ? 'Faucet' : 'Request from faucet'} </TableHeaderColumn> - } - { - isTestNetwork && - <TableHeaderColumn - style={dharmaButtonColumnStyle} - > + )} + {isTestNetwork && ( + <TableHeaderColumn style={dharmaButtonColumnStyle}> {isSmallScreen ? 'Loan' : 'Request Dharma loan'} - <HelpTooltip - style={{paddingLeft: 4}} - explanation={dharmaLoanExplanation} - /> + <HelpTooltip style={{ paddingLeft: 4 }} explanation={dharmaLoanExplanation} /> </TableHeaderColumn> - } + )} </TableRow> </TableHeader> <TableBody displayRowCheckbox={false}> <TableRow key="ETH"> <TableRowColumn className="py1"> - <img - style={{width: ICON_DIMENSION, height: ICON_DIMENSION}} - src={ETHER_ICON_PATH} - /> + <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} /> </TableRowColumn> <TableRowColumn> {this.props.userEtherBalance.toFixed(PRECISION)} ETH - {this.state.isBalanceSpinnerVisible && + {this.state.isBalanceSpinnerVisible && ( <span className="pl1"> <i className="zmdi zmdi-spinner zmdi-hc-spin" /> </span> - } + )} </TableRowColumn> - <TableRowColumn - className="sm-hide xs-hide" - style={stubColumnStyle} - /> - { - isTestNetwork && - <TableRowColumn style={{paddingLeft: 3}}> + <TableRowColumn className="sm-hide xs-hide" style={stubColumnStyle} /> + {isTestNetwork && ( + <TableRowColumn style={{ paddingLeft: 3 }}> <LifeCycleRaisedButton labelReady="Request" labelLoading="Sending..." labelComplete="Sent!" - onClickAsyncFn={this.faucetRequestAsync.bind(this, true)} + onClickAsyncFn={this._faucetRequestAsync.bind(this, true)} /> </TableRowColumn> - } - { - isTestNetwork && + )} + {isTestNetwork && ( <TableRowColumn style={dharmaButtonColumnStyle}> <RaisedButton label="Request" - style={{width: '100%'}} - onTouchTap={this.onDharmaDialogToggle.bind(this)} + style={{ width: '100%' }} + onTouchTap={this._onDharmaDialogToggle.bind(this)} /> </TableRowColumn> - } + )} </TableRow> </TableBody> </Table> - <div className="clearfix" style={{paddingBottom: 1}}> + <div className="clearfix" style={{ paddingBottom: 1 }}> <div className="col col-10"> - <h3 className="pt2"> - {isTestNetwork ? 'Test tokens' : 'Tokens'} - </h3> + <h3 className="pt2">{isTestNetwork ? 'Test tokens' : 'Tokens'}</h3> </div> <div className="col col-1 pt3 align-right"> - <FloatingActionButton - mini={true} - zDepth={0} - onClick={this.onAddTokenClicked.bind(this)} - > + <FloatingActionButton mini={true} zDepth={0} onClick={this._onAddTokenClicked.bind(this)}> <ContentAdd /> </FloatingActionButton> </div> <div className="col col-1 pt3 align-right"> - <FloatingActionButton - mini={true} - zDepth={0} - onClick={this.onRemoveTokenClicked.bind(this)} - > + <FloatingActionButton mini={true} zDepth={0} onClick={this._onRemoveTokenClicked.bind(this)}> <ContentRemove /> </FloatingActionButton> </div> </div> <Divider /> <div className="pt2 pb2"> - {isTestNetwork ? - '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.' - } + {isTestNetwork + ? "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."} </div> - <Table - selectable={false} - bodyStyle={{height: tokenTableHeight}} - style={styles.bgColor} - > + <Table selectable={false} bodyStyle={{ height: tokenTableHeight }} style={styles.bgColor}> <TableHeader displaySelectAll={false} adjustForCheckbox={false}> <TableRow> - <TableHeaderColumn - colSpan={tokenColSpan} - > - Token - </TableHeaderColumn> - <TableHeaderColumn style={{paddingLeft: 3}}>Balance</TableHeaderColumn> - <TableHeaderColumn> - <div className="inline-block">{!isSmallScreen && 'Trade '}Permissions</div> - <HelpTooltip - style={{paddingLeft: 4}} - explanation={allowanceExplanation} - /> - </TableHeaderColumn> + <TableHeaderColumn colSpan={tokenColSpan}>Token</TableHeaderColumn> + <TableHeaderColumn style={{ paddingLeft: 3 }}>Balance</TableHeaderColumn> <TableHeaderColumn> - Action + <div className="inline-block">Allowance</div> + <HelpTooltip style={{ paddingLeft: 4 }} explanation={allowanceExplanation} /> </TableHeaderColumn> - {this.props.screenWidth !== ScreenWidths.SM && - <TableHeaderColumn> - Send - </TableHeaderColumn> - } + <TableHeaderColumn>Action</TableHeaderColumn> + {this.props.screenWidth !== ScreenWidths.Sm && <TableHeaderColumn>Send</TableHeaderColumn>} </TableRow> </TableHeader> - <TableBody displayRowCheckbox={false}> - {this.renderTokenTableRows()} - </TableBody> + <TableBody displayRowCheckbox={false}>{this._renderTokenTableRows()}</TableBody> </Table> <Dialog title="Oh oh" - titleStyle={{fontWeight: 100}} + titleStyle={{ fontWeight: 100 }} actions={errorDialogActions} open={!_.isUndefined(this.state.errorType)} - onRequestClose={this.onErrorDialogToggle.bind(this, false)} + onRequestClose={this._onErrorDialogToggle.bind(this, false)} > - {this.renderErrorDialogBody()} + {this._renderErrorDialogBody()} </Dialog> <Dialog title="Request Dharma Loan" - titleStyle={{fontWeight: 100, backgroundColor: 'rgb(250, 250, 250)'}} - bodyStyle={{backgroundColor: 'rgb(37, 37, 37)'}} - actionsContainerStyle={{backgroundColor: 'rgb(250, 250, 250)'}} + titleStyle={{ fontWeight: 100, backgroundColor: colors.white }} + bodyStyle={{ backgroundColor: colors.dharmaDarkGrey }} + actionsContainerStyle={{ backgroundColor: colors.white }} autoScrollBodyContent={true} actions={dharmaDialogActions} open={this.state.isDharmaDialogVisible} > - {this.renderDharmaLoanFrame()} + {this._renderDharmaLoanFrame()} </Dialog> <AssetPicker userAddress={this.props.userAddress} @@ -351,58 +289,62 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala dispatcher={this.props.dispatcher} isOpen={this.state.isTokenPickerOpen} currentTokenAddress={''} - onTokenChosen={this.onAssetTokenPicked.bind(this)} + onTokenChosen={this._onAssetTokenPicked.bind(this)} tokenByAddress={this.props.tokenByAddress} tokenVisibility={this.state.isAddingToken ? TokenVisibility.UNTRACKED : TokenVisibility.TRACKED} /> </div> ); } - private renderTokenTableRows() { - if (!this.props.blockchainIsLoaded || this.props.blockchainErr !== '') { + private _renderTokenTableRows() { + if (!this.props.blockchainIsLoaded || this.props.blockchainErr !== BlockchainErrs.NoError) { return ''; } - const isSmallScreen = this.props.screenWidth === ScreenWidths.SM; + const isSmallScreen = this.props.screenWidth === ScreenWidths.Sm; const tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG; const actionPaddingX = isSmallScreen ? 2 : 24; const allTokens = _.values(this.props.tokenByAddress); const trackedTokens = _.filter(allTokens, t => t.isTracked); const trackedTokensStartingWithEtherToken = trackedTokens.sort( - firstBy((t: Token) => (t.symbol !== ETHER_TOKEN_SYMBOL)) - .thenBy((t: Token) => (t.symbol !== ZRX_TOKEN_SYMBOL)) - .thenBy('address'), + firstBy((t: Token) => t.symbol !== ETHER_TOKEN_SYMBOL) + .thenBy((t: Token) => t.symbol !== ZRX_TOKEN_SYMBOL) + .thenBy('address'), ); const tableRows = _.map( trackedTokensStartingWithEtherToken, - this.renderTokenRow.bind(this, tokenColSpan, actionPaddingX), + this._renderTokenRow.bind(this, tokenColSpan, actionPaddingX), ); return tableRows; } - private renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token) { + private _renderTokenRow(tokenColSpan: number, actionPaddingX: number, token: Token) { const tokenState = this.props.tokenStateByAddress[token.address]; - const tokenLink = utils.getEtherScanLinkIfExists(token.address, this.props.networkId, - EtherscanLinkSuffixes.address); - const isMintable = _.includes(configs.symbolsOfMintableTokens, token.symbol) && - this.props.networkId !== constants.MAINNET_NETWORK_ID; + const tokenLink = utils.getEtherScanLinkIfExists( + token.address, + this.props.networkId, + EtherscanLinkSuffixes.Address, + ); + const isMintable = + _.includes(configs.SYMBOLS_OF_MINTABLE_TOKENS, token.symbol) && + this.props.networkId !== constants.NETWORK_ID_MAINNET; return ( - <TableRow key={token.address} style={{height: TOKEN_TABLE_ROW_HEIGHT}}> - <TableRowColumn - colSpan={tokenColSpan} - > - {_.isUndefined(tokenLink) ? - this.renderTokenName(token) : - <a href={tokenLink} target="_blank" style={{textDecoration: 'none'}}> - {this.renderTokenName(token)} + <TableRow key={token.address} style={{ height: TOKEN_TABLE_ROW_HEIGHT }}> + <TableRowColumn colSpan={tokenColSpan}> + {_.isUndefined(tokenLink) ? ( + this._renderTokenName(token) + ) : ( + <a href={tokenLink} target="_blank" style={{ textDecoration: 'none' }}> + {this._renderTokenName(token)} </a> - } + )} </TableRowColumn> - <TableRowColumn style={{paddingRight: 3, paddingLeft: 3}}> - {this.renderAmount(tokenState.balance, token.decimals)} {token.symbol} - {this.state.isZRXSpinnerVisible && token.symbol === ZRX_TOKEN_SYMBOL && - <span className="pl1"> - <i className="zmdi zmdi-spinner zmdi-hc-spin" /> - </span> - } + <TableRowColumn style={{ paddingRight: 3, paddingLeft: 3 }}> + {this._renderAmount(tokenState.balance, token.decimals)} {token.symbol} + {this.state.isZRXSpinnerVisible && + token.symbol === ZRX_TOKEN_SYMBOL && ( + <span className="pl1"> + <i className="zmdi zmdi-spinner zmdi-hc-spin" /> + </span> + )} </TableRowColumn> <TableRowColumn> <AllowanceToggle @@ -410,57 +352,49 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala dispatcher={this.props.dispatcher} token={token} tokenState={tokenState} - onErrorOccurred={this.onErrorOccurred.bind(this)} + onErrorOccurred={this._onErrorOccurred.bind(this)} userAddress={this.props.userAddress} /> </TableRowColumn> - <TableRowColumn - style={{paddingLeft: actionPaddingX, paddingRight: actionPaddingX}} - > - {isMintable && + <TableRowColumn style={{ paddingLeft: actionPaddingX, paddingRight: actionPaddingX }}> + {isMintable && ( <LifeCycleRaisedButton labelReady="Mint" - labelLoading={<span style={{fontSize: 12}}>Minting...</span>} + labelLoading={<span style={{ fontSize: 12 }}>Minting...</span>} labelComplete="Minted!" - onClickAsyncFn={this.onMintTestTokensAsync.bind(this, token)} + onClickAsyncFn={this._onMintTestTokensAsync.bind(this, token)} /> - } - {token.symbol === ETHER_TOKEN_SYMBOL && - <EthWethConversionButton - blockchain={this.props.blockchain} - dispatcher={this.props.dispatcher} - ethToken={this.getWrappedEthToken()} - ethTokenState={tokenState} - userEtherBalance={this.props.userEtherBalance} - onError={this.onEthWethConversionFailed.bind(this)} - /> - } - {token.symbol === ZRX_TOKEN_SYMBOL && this.props.networkId === constants.TESTNET_NETWORK_ID && - <LifeCycleRaisedButton - labelReady="Request" - labelLoading="Sending..." - labelComplete="Sent!" - onClickAsyncFn={this.faucetRequestAsync.bind(this, false)} - /> - } + )} + {token.symbol === ZRX_TOKEN_SYMBOL && + this.props.networkId === constants.NETWORK_ID_TESTNET && ( + <LifeCycleRaisedButton + labelReady="Request" + labelLoading="Sending..." + labelComplete="Sent!" + onClickAsyncFn={this._faucetRequestAsync.bind(this, false)} + /> + )} </TableRowColumn> - {this.props.screenWidth !== ScreenWidths.SM && + {this.props.screenWidth !== ScreenWidths.Sm && ( <TableRowColumn - style={{paddingLeft: actionPaddingX, paddingRight: actionPaddingX}} + style={{ + paddingLeft: actionPaddingX, + paddingRight: actionPaddingX, + }} > <SendButton blockchain={this.props.blockchain} dispatcher={this.props.dispatcher} token={token} tokenState={tokenState} - onError={this.onSendFailed.bind(this)} + onError={this._onSendFailed.bind(this)} /> </TableRowColumn> - } + )} </TableRow> ); } - private onAssetTokenPicked(tokenAddress: string) { + private _onAssetTokenPicked(tokenAddress: string) { if (_.isEmpty(tokenAddress)) { this.setState({ isTokenPickerOpen: false, @@ -468,13 +402,14 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala return; } const token = this.props.tokenByAddress[tokenAddress]; - const isDefaultTrackedToken = _.includes(configs.defaultTrackedTokenSymbols, token.symbol); + const isDefaultTrackedToken = _.includes(configs.DEFAULT_TRACKED_TOKEN_SYMBOLS, token.symbol); if (!this.state.isAddingToken && !isDefaultTrackedToken) { if (token.isRegistered) { // Remove the token from tracked tokens - const newToken = _.assign({}, token, { + const newToken = { + ...token, isTracked: false, - }); + }; this.props.dispatcher.updateTokenByAddress([newToken]); } else { this.props.dispatcher.removeTokenToTokenByAddress(token); @@ -488,82 +423,57 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala isTokenPickerOpen: false, }); } - private onEthWethConversionFailed() { - this.setState({ - errorType: BalanceErrs.wethConversionFailed, - }); - } - private onSendFailed() { + private _onSendFailed() { this.setState({ errorType: BalanceErrs.sendFailed, }); } - private renderAmount(amount: BigNumber, decimals: number) { + private _renderAmount(amount: BigNumber, decimals: number) { const unitAmount = ZeroEx.toUnitAmount(amount, decimals); return unitAmount.toNumber().toFixed(PRECISION); } - private renderTokenName(token: Token) { + private _renderTokenName(token: Token) { const tooltipId = `tooltip-${token.address}`; return ( <div className="flex"> <TokenIcon token={token} diameter={ICON_DIMENSION} /> - <div - data-tip={true} - data-for={tooltipId} - className="mt2 ml2 sm-hide xs-hide" - > + <div data-tip={true} data-for={tooltipId} className="mt2 ml2 sm-hide xs-hide"> {token.name} </div> <ReactTooltip id={tooltipId}>{token.address}</ReactTooltip> </div> ); } - private renderErrorDialogBody() { + private _renderErrorDialogBody() { switch (this.state.errorType) { case BalanceErrs.incorrectNetworkForFaucet: return ( <div> - Our faucet can only send test Ether to addresses on the {constants.TESTNET_NAME} - {' '}testnet (networkId {constants.TESTNET_NETWORK_ID}). 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 {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. </div> ); case BalanceErrs.faucetRequestFailed: return ( <div> - An unexpected error occurred while trying to request test Ether from our faucet. - {' '}Please refresh the page and try again. + An unexpected error occurred while trying to request test Ether from our faucet. Please refresh + the page and try again. </div> ); case BalanceErrs.faucetQueueIsFull: - return ( - <div> - Our test Ether faucet queue is full. Please try requesting test Ether again later. - </div> - ); + return <div>Our test Ether faucet queue is full. Please try requesting test Ether again later.</div>; case BalanceErrs.mintingFailed: - return ( - <div> - Minting your test tokens failed unexpectedly. Please refresh the page and try again. - </div> - ); - - case BalanceErrs.wethConversionFailed: - return ( - <div> - Converting between Ether and Ether Tokens failed unexpectedly. - Please refresh the page and try again. - </div> - ); + return <div>Minting your test tokens failed unexpectedly. Please refresh the page and try again.</div>; case BalanceErrs.allowanceSettingFailed: return ( <div> - An unexpected error occurred while trying to set your test token allowance. - {' '}Please refresh the page and try again. + An unexpected error occurred while trying to set your test token allowance. Please refresh the + page and try again. </div> ); @@ -574,12 +484,12 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala throw utils.spawnSwitchErr('errorType', this.state.errorType); } } - private renderDharmaLoanFrame() { + private _renderDharmaLoanFrame() { if (utils.isUserOnMobile()) { return ( <h4 style={{ textAlign: 'center' }}> - We apologize -- Dharma loan requests are not available on - mobile yet. Please try again through your desktop browser. + We apologize -- Dharma loan requests are not available on mobile yet. Please try again through your + desktop browser. </h4> ); } else { @@ -592,20 +502,20 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala ); } } - private onErrorOccurred(errorType: BalanceErrs) { + private _onErrorOccurred(errorType: BalanceErrs) { this.setState({ errorType, }); } - private async onMintTestTokensAsync(token: Token): Promise<boolean> { + private async _onMintTestTokensAsync(token: Token): Promise<boolean> { try { await this.props.blockchain.mintTestTokensAsync(token); const amount = ZeroEx.toUnitAmount(constants.MINT_AMOUNT, token.decimals); this.props.dispatcher.showFlashMessage(`Successfully minted ${amount.toString(10)} ${token.symbol}`); return true; } catch (err) { - const errMsg = '' + err; - if (_.includes(errMsg, BlockchainCallErrs.USER_HAS_NO_ASSOCIATED_ADDRESSES)) { + const errMsg = `${err}`; + if (_.includes(errMsg, BlockchainCallErrs.UserHasNoAssociatedAddresses)) { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); return false; } @@ -614,14 +524,14 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala } utils.consoleLog(`Unexpected error encountered: ${err}`); utils.consoleLog(err.stack); - await errorReporter.reportAsync(err); this.setState({ errorType: BalanceErrs.mintingFailed, }); + await errorReporter.reportAsync(err); return false; } } - private async faucetRequestAsync(isEtherRequest: boolean): Promise<boolean> { + private async _faucetRequestAsync(isEtherRequest: boolean): Promise<boolean> { if (this.props.userAddress === '') { this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true); return false; @@ -629,7 +539,7 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala // If on another network other then the testnet our faucet serves test ether // from, we must show user an error message - if (this.props.blockchain.networkId !== constants.TESTNET_NETWORK_ID) { + if (this.props.blockchain.networkId !== constants.NETWORK_ID_TESTNET) { this.setState({ errorType: BalanceErrs.incorrectNetworkForFaucet, }); @@ -639,17 +549,18 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala await utils.sleepAsync(ARTIFICIAL_FAUCET_REQUEST_DELAY); const segment = isEtherRequest ? 'ether' : 'zrx'; - const response = await fetch(`${constants.ETHER_FAUCET_ENDPOINT}/${segment}/${this.props.userAddress}`); + const response = await fetch(`${constants.URL_ETHER_FAUCET}/${segment}/${this.props.userAddress}`); const responseBody = await response.text(); if (response.status !== constants.SUCCESS_STATUS) { utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`); - await errorReporter.reportAsync(new Error(`Faucet returned non-200: ${JSON.stringify(response)}`)); - const errorType = response.status === constants.UNAVAILABLE_STATUS ? - BalanceErrs.faucetQueueIsFull : - BalanceErrs.faucetRequestFailed; + const errorType = + response.status === constants.UNAVAILABLE_STATUS + ? BalanceErrs.faucetQueueIsFull + : BalanceErrs.faucetRequestFailed; this.setState({ errorType, }); + await errorReporter.reportAsync(new Error(`Faucet returned non-200: ${JSON.stringify(response)}`)); return false; } @@ -670,28 +581,23 @@ export class TokenBalances extends React.Component<TokenBalancesProps, TokenBala } return true; } - private onErrorDialogToggle(isOpen: boolean) { + private _onErrorDialogToggle(isOpen: boolean) { this.setState({ errorType: undefined, }); } - private onDharmaDialogToggle() { + private _onDharmaDialogToggle() { this.setState({ isDharmaDialogVisible: !this.state.isDharmaDialogVisible, }); } - private getWrappedEthToken() { - const tokens = _.values(this.props.tokenByAddress); - const wrappedEthToken = _.find(tokens, {symbol: ETHER_TOKEN_SYMBOL}); - return wrappedEthToken; - } - private onAddTokenClicked() { + private _onAddTokenClicked() { this.setState({ isTokenPickerOpen: true, isAddingToken: true, }); } - private onRemoveTokenClicked() { + private _onRemoveTokenClicked() { this.setState({ isTokenPickerOpen: true, isAddingToken: false, diff --git a/packages/website/ts/components/top_bar.tsx b/packages/website/ts/components/top_bar.tsx index 4398fe667..11d3e7cc2 100644 --- a/packages/website/ts/components/top_bar.tsx +++ b/packages/website/ts/components/top_bar.tsx @@ -1,29 +1,18 @@ import * as _ from 'lodash'; -import AppBar from 'material-ui/AppBar'; import Drawer from 'material-ui/Drawer'; import MenuItem from 'material-ui/MenuItem'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {Link} from 'react-router-dom'; -import {HashLink} from 'react-router-hash-link'; -import { - animateScroll, - Link as ScrollLink, -} from 'react-scroll'; +import { Link } from 'react-router-dom'; import ReactTooltip = require('react-tooltip'); -import {PortalMenu} from 'ts/components/portal_menu'; -import {TopBarMenuItem} from 'ts/components/top_bar_menu_item'; -import {DropDownMenuItem} from 'ts/components/ui/drop_down_menu_item'; -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 {DocsMenu, MenuSubsectionsBySection, Styles, TypeDocNode, WebsitePaths} from 'ts/types'; -import {configs} from 'ts/utils/configs'; -import {constants} from 'ts/utils/constants'; -import {typeDocUtils} from 'ts/utils/typedoc_utils'; - -const CUSTOM_DARK_GRAY = '#231F20'; -const SECTION_HEADER_COLOR = 'rgb(234, 234, 234)'; +import { PortalMenu } from 'ts/components/portal_menu'; +import { TopBarMenuItem } from 'ts/components/top_bar_menu_item'; +import { DropDownMenuItem } from 'ts/components/ui/drop_down_menu_item'; +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 { DocsMenu, MenuSubsectionsBySection, Styles, WebsitePaths } from 'ts/types'; +import { colors } from 'ts/utils/colors'; +import { constants } from 'ts/utils/constants'; interface TopBarProps { userAddress?: string; @@ -52,16 +41,11 @@ const styles: Styles = { whiteSpace: 'nowrap', width: 70, }, - addressPopover: { - backgroundColor: colors.blueGrey500, - color: 'white', - padding: 3, - }, topBar: { - backgroundColor: 'white', + backgroundcolor: colors.white, height: 59, width: '100%', - position: 'fixed', + position: 'relative', top: 0, zIndex: 1100, paddingBottom: 1, @@ -71,7 +55,7 @@ const styles: Styles = { }, menuItem: { fontSize: 14, - color: CUSTOM_DARK_GRAY, + color: colors.darkestGrey, paddingTop: 6, paddingBottom: 6, marginTop: 17, @@ -97,40 +81,28 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { const isFullWidthPage = this.props.shouldFullWidth; const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`; const developerSectionMenuItems = [ - <Link - key="subMenuItem-zeroEx" - to={WebsitePaths.ZeroExJs} - className="text-decoration-none" - > - <MenuItem style={{fontSize: styles.menuItem.fontSize}} primaryText="0x.js" /> + <Link key="subMenuItem-zeroEx" to={WebsitePaths.ZeroExJs} className="text-decoration-none"> + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x.js" /> </Link>, - <Link - key="subMenuItem-smartContracts" - to={WebsitePaths.SmartContracts} - className="text-decoration-none" - > - <MenuItem style={{fontSize: styles.menuItem.fontSize}} primaryText="Smart Contracts" /> + <Link key="subMenuItem-smartContracts" to={WebsitePaths.SmartContracts} className="text-decoration-none"> + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Smart Contracts" /> </Link>, - <Link - key="subMenuItem-0xconnect" - to={WebsitePaths.Connect} - className="text-decoration-none" - > - <MenuItem style={{fontSize: styles.menuItem.fontSize}} primaryText="0x Connect" /> + <Link key="subMenuItem-0xconnect" to={WebsitePaths.Connect} className="text-decoration-none"> + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x Connect" /> </Link>, <a key="subMenuItem-standard-relayer-api" target="_blank" className="text-decoration-none" - href={constants.STANDARD_RELAYER_API_GITHUB} + href={constants.URL_STANDARD_RELAYER_API_GITHUB} > - <MenuItem style={{fontSize: styles.menuItem.fontSize}} primaryText="Standard Relayer API" /> + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Standard Relayer API" /> </a>, <a key="subMenuItem-github" target="_blank" className="text-decoration-none" - href={constants.GITHUB_URL} + href={constants.URL_GITHUB_ORG} > <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="GitHub" /> </a>, @@ -140,10 +112,10 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { className="text-decoration-none" href={`${WebsitePaths.Whitepaper}`} > - <MenuItem style={{fontSize: styles.menuItem.fontSize}} primaryText="Whitepaper" /> + <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Whitepaper" /> </a>, ]; - const bottomBorderStyle = this.shouldDisplayBottomBar() ? styles.bottomBar : {}; + 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`; @@ -154,19 +126,17 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { paddingTop: 16, }; return ( - <div style={{...styles.topBar, ...bottomBorderStyle, ...this.props.style}} className="pb1"> + <div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style }} className="pb1"> <div className={parentClassNames}> - <div className="col col-2 sm-pl2 md-pl2 lg-pl0" style={{paddingTop: 15}}> + <div className="col col-2 sm-pl2 md-pl2 lg-pl0" style={{ paddingTop: 15 }}> <Link to={`${WebsitePaths.Home}`} className="text-decoration-none"> <img src={logoUrl} height="30" /> </Link> </div> <div className={`col col-${isFullWidthPage ? '8' : '9'} lg-hide md-hide`} /> <div className={`col col-${isFullWidthPage ? '6' : '5'} sm-hide xs-hide`} /> - {!this.isViewingPortal() && - <div - className={menuClasses} - > + {!this._isViewingPortal() && ( + <div className={menuClasses}> <div className="flex justify-between"> <DropDownMenuItem title="Developers" @@ -196,110 +166,97 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { /> </div> </div> - } - {this.props.blockchainIsLoaded && !_.isEmpty(this.props.userAddress) && - <div className="col col-5"> - {this.renderUser()} - </div> - } - {!this.isViewingPortal() && - <div - className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`} - > - <div style={menuIconStyle}> - <i - className="zmdi zmdi-menu" - onClick={this.onMenuButtonClick.bind(this)} - /> - </div> + )} + {this.props.blockchainIsLoaded && + !_.isEmpty(this.props.userAddress) && ( + <div className="col col-5 sm-hide xs-hide">{this._renderUser()}</div> + )} + <div className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}> + <div style={menuIconStyle}> + <i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} /> </div> - } + </div> </div> - {this.renderDrawer()} + {this._renderDrawer()} </div> ); } - private renderDrawer() { + private _renderDrawer() { return ( <Drawer open={this.state.isDrawerOpen} docked={false} openSecondary={true} - onRequestChange={this.onMenuButtonClick.bind(this)} + onRequestChange={this._onMenuButtonClick.bind(this)} > - {this.renderPortalMenu()} - {this.renderDocsMenu()} - {this.renderWiki()} - <div className="pl1 py1 mt3" style={{backgroundColor: SECTION_HEADER_COLOR}}>Website</div> + {this._renderPortalMenu()} + {this._renderDocsMenu()} + {this._renderWiki()} + <div className="pl1 py1 mt3" style={{ backgroundColor: colors.lightGrey }}> + Website + </div> <Link to={WebsitePaths.Home} className="text-decoration-none"> <MenuItem className="py2">Home</MenuItem> </Link> <Link to={`${WebsitePaths.Wiki}`} className="text-decoration-none"> <MenuItem className="py2">Wiki</MenuItem> </Link> - {!this.isViewing0xjsDocs() && + {!this._isViewing0xjsDocs() && ( <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> <MenuItem className="py2">0x.js Docs</MenuItem> </Link> - } - {!this.isViewingConnectDocs() && + )} + {!this._isViewingConnectDocs() && ( <Link to={WebsitePaths.Connect} className="text-decoration-none"> <MenuItem className="py2">0x Connect Docs</MenuItem> </Link> - } - {!this.isViewingSmartContractsDocs() && + )} + {!this._isViewingSmartContractsDocs() && ( <Link to={WebsitePaths.SmartContracts} className="text-decoration-none"> <MenuItem className="py2">Smart Contract Docs</MenuItem> </Link> - } - {!this.isViewingPortal() && + )} + {!this._isViewingPortal() && ( <Link to={`${WebsitePaths.Portal}`} className="text-decoration-none"> <MenuItem className="py2">Portal DApp</MenuItem> </Link> - } - <a - className="text-decoration-none" - target="_blank" - href={`${WebsitePaths.Whitepaper}`} - > + )} + <a className="text-decoration-none" target="_blank" href={`${WebsitePaths.Whitepaper}`}> <MenuItem className="py2">Whitepaper</MenuItem> </a> <Link to={`${WebsitePaths.About}`} className="text-decoration-none"> <MenuItem className="py2">About</MenuItem> </Link> - <a - className="text-decoration-none" - target="_blank" - href={constants.BLOG_URL} - > + <a className="text-decoration-none" target="_blank" href={constants.URL_BLOG}> <MenuItem className="py2">Blog</MenuItem> </a> <Link to={`${WebsitePaths.FAQ}`} className="text-decoration-none"> - <MenuItem - className="py2" - onTouchTap={this.onMenuButtonClick.bind(this)} - > + <MenuItem className="py2" onTouchTap={this._onMenuButtonClick.bind(this)}> FAQ </MenuItem> </Link> </Drawer> ); } - private renderDocsMenu() { - if (!this.isViewing0xjsDocs() && !this.isViewingSmartContractsDocs() && !this.isViewingConnectDocs() - || _.isUndefined(this.props.menu)) { - return; + 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 ( <div className="lg-hide md-hide"> - <div className="pl1 py1" style={{backgroundColor: SECTION_HEADER_COLOR}}>{sectionTitle}</div> + <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> + {sectionTitle} + </div> <NestedSidebarMenu topLevelMenu={this.props.menu} menuSubsectionsBySection={this.props.menuSubsectionsBySection} shouldDisplaySectionHeaders={false} - onMenuItemClick={this.onMenuButtonClick.bind(this)} + onMenuItemClick={this._onMenuButtonClick.bind(this)} selectedVersion={this.props.docsVersion} docPath={this.props.docsInfo.websitePath} versions={this.props.availableDocVersions} @@ -307,51 +264,45 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { </div> ); } - private renderWiki() { - if (!this.isViewingWiki()) { - return; + private _renderWiki(): React.ReactNode { + if (!this._isViewingWiki()) { + return undefined; } return ( <div className="lg-hide md-hide"> - <div className="pl1 py1" style={{backgroundColor: SECTION_HEADER_COLOR}}>0x Protocol Wiki</div> + <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> + 0x Protocol Wiki + </div> <NestedSidebarMenu topLevelMenu={this.props.menuSubsectionsBySection} menuSubsectionsBySection={this.props.menuSubsectionsBySection} shouldDisplaySectionHeaders={false} - onMenuItemClick={this.onMenuButtonClick.bind(this)} + onMenuItemClick={this._onMenuButtonClick.bind(this)} /> </div> ); } - private renderPortalMenu() { - if (!this.isViewingPortal()) { - return; + private _renderPortalMenu(): React.ReactNode { + if (!this._isViewingPortal()) { + return undefined; } return ( <div className="lg-hide md-hide"> - <div className="pl1 py1" style={{backgroundColor: SECTION_HEADER_COLOR}}>Portal DApp</div> - <PortalMenu - menuItemStyle={{color: 'black'}} - onClick={this.onMenuButtonClick.bind(this)} - /> + <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> + Portal DApp + </div> + <PortalMenu menuItemStyle={{ color: 'black' }} onClick={this._onMenuButtonClick.bind(this)} /> </div> ); } - private renderUser() { + private _renderUser() { const userAddress = this.props.userAddress; const identiconDiameter = 26; return ( - <div - className="flex right lg-pr0 md-pr2 sm-pr2" - style={{paddingTop: 16}} - > - <div - style={styles.address} - data-tip={true} - data-for="userAddressTooltip" - > + <div className="flex right lg-pr0 md-pr2 sm-pr2" style={{ paddingTop: 16 }}> + <div style={styles.address} data-tip={true} data-for="userAddressTooltip"> {!_.isEmpty(userAddress) ? userAddress : ''} </div> <ReactTooltip id="userAddressTooltip">{userAddress}</ReactTooltip> @@ -361,31 +312,36 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { </div> ); } - private onMenuButtonClick() { + private _onMenuButtonClick() { this.setState({ isDrawerOpen: !this.state.isDrawerOpen, }); } - private isViewingPortal() { + private _isViewingPortal() { return _.includes(this.props.location.pathname, WebsitePaths.Portal); } - private isViewingFAQ() { + private _isViewingFAQ() { return _.includes(this.props.location.pathname, WebsitePaths.FAQ); } - private isViewing0xjsDocs() { + private _isViewing0xjsDocs() { return _.includes(this.props.location.pathname, WebsitePaths.ZeroExJs); } - private isViewingConnectDocs() { + private _isViewingConnectDocs() { return _.includes(this.props.location.pathname, WebsitePaths.Connect); } - private isViewingSmartContractsDocs() { + private _isViewingSmartContractsDocs() { return _.includes(this.props.location.pathname, WebsitePaths.SmartContracts); } - private isViewingWiki() { + private _isViewingWiki() { return _.includes(this.props.location.pathname, WebsitePaths.Wiki); } - private shouldDisplayBottomBar() { - return this.isViewingWiki() || this.isViewing0xjsDocs() || this.isViewingFAQ() || - this.isViewingSmartContractsDocs() || this.isViewingConnectDocs(); + private _shouldDisplayBottomBar() { + return ( + this._isViewingWiki() || + this._isViewing0xjsDocs() || + this._isViewingFAQ() || + this._isViewingSmartContractsDocs() || + this._isViewingConnectDocs() + ); } } diff --git a/packages/website/ts/components/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar_menu_item.tsx index de429fba6..96ee86142 100644 --- a/packages/website/ts/components/top_bar_menu_item.tsx +++ b/packages/website/ts/components/top_bar_menu_item.tsx @@ -1,11 +1,10 @@ import * as _ from 'lodash'; import * as React from 'react'; -import {Link} from 'react-router-dom'; -import {Styles} from 'ts/types'; +import { Link } from 'react-router-dom'; +import { colors } from 'ts/utils/colors'; -const CUSTOM_DARK_GRAY = '#231F20'; const DEFAULT_STYLE = { - color: CUSTOM_DARK_GRAY, + color: colors.darkestGrey, }; interface TopBarMenuItemProps { @@ -27,24 +26,24 @@ export class TopBarMenuItem extends React.Component<TopBarMenuItemProps, TopBarM isNightVersion: false, }; public render() { - const primaryStyles = this.props.isPrimary ? { - borderRadius: 4, - border: `1px solid ${this.props.isNightVersion ? '#979797' : 'rgb(230, 229, 229)'}`, - marginTop: 15, - paddingLeft: 9, - paddingRight: 9, - width: 77, - } : {}; + 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) ? - CUSTOM_DARK_GRAY : - menuItemColor; + const linkColor = _.isUndefined(menuItemColor) ? colors.darkestGrey : menuItemColor; return ( <div className={`center ${this.props.className}`} - style={{...this.props.style, ...primaryStyles, color: menuItemColor}} + style={{ ...this.props.style, ...primaryStyles, color: menuItemColor }} > - <Link to={this.props.path} className="text-decoration-none" style={{color: linkColor}}> + <Link to={this.props.path} className="text-decoration-none" style={{ color: linkColor }}> {this.props.title} </Link> </div> diff --git a/packages/website/ts/components/track_token_confirmation.tsx b/packages/website/ts/components/track_token_confirmation.tsx index b9b2ef18a..76971aefa 100644 --- a/packages/website/ts/components/track_token_confirmation.tsx +++ b/packages/website/ts/components/track_token_confirmation.tsx @@ -1,11 +1,9 @@ import * as _ from 'lodash'; -import Dialog from 'material-ui/Dialog'; -import FlatButton from 'material-ui/FlatButton'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {Party} from 'ts/components/ui/party'; -import {Token, TokenByAddress} from 'ts/types'; -import {utils} from 'ts/utils/utils'; +import { Party } from 'ts/components/ui/party'; +import { Token, TokenByAddress } from 'ts/types'; +import { colors } from 'ts/utils/colors'; +import { utils } from 'ts/utils/utils'; interface TrackTokenConfirmationProps { tokens: Token[]; @@ -16,25 +14,23 @@ interface TrackTokenConfirmationProps { interface TrackTokenConfirmationState {} -export class TrackTokenConfirmation extends - React.Component<TrackTokenConfirmationProps, TrackTokenConfirmationState> { +export class TrackTokenConfirmation extends React.Component<TrackTokenConfirmationProps, TrackTokenConfirmationState> { public render() { const isMultipleTokens = this.props.tokens.length > 1; const allTokens = _.values(this.props.tokenByAddress); return ( - <div style={{color: colors.grey700}}> - {this.props.isAddingTokenToTracked ? + <div style={{ color: colors.grey700 }}> + {this.props.isAddingTokenToTracked ? ( <div className="py4 my4 center"> <span className="pr1"> <i className="zmdi zmdi-spinner zmdi-hc-spin" /> </span> <span>Adding token{isMultipleTokens && 's'}...</span> - </div> : + </div> + ) : ( <div> - <div> - You do not currently track the following token{isMultipleTokens && 's'}: - </div> - <div className="py2 clearfix mx-auto center" style={{width: 355}}> + <div>You do not currently track the following token{isMultipleTokens && 's'}:</div> + <div className="py2 clearfix mx-auto center" style={{ width: 355 }}> {_.map(this.props.tokens, (token: Token) => ( <div key={`token-profile-${token.name}`} @@ -52,13 +48,13 @@ export class TrackTokenConfirmation extends ))} </div> <div> - Tracking a token adds it to the balances section of 0x Portal and - allows you to generate/fill orders involving the token + Tracking a token adds it to the balances section of 0x Portal and allows you to + generate/fill orders involving the token {isMultipleTokens && 's'}. Would you like to start tracking{' '} - {isMultipleTokens ? 'these' : 'this'}{' '}token? + {isMultipleTokens ? 'these' : 'this'} token? </div> </div> - } + )} </div> ); } diff --git a/packages/website/ts/components/trade_history/trade_history.tsx b/packages/website/ts/components/trade_history/trade_history.tsx index 59f85a03d..635358627 100644 --- a/packages/website/ts/components/trade_history/trade_history.tsx +++ b/packages/website/ts/components/trade_history/trade_history.tsx @@ -2,10 +2,10 @@ import * as _ from 'lodash'; import Divider from 'material-ui/Divider'; import Paper from 'material-ui/Paper'; import * as React from 'react'; -import {TradeHistoryItem} from 'ts/components/trade_history/trade_history_item'; -import {tradeHistoryStorage} from 'ts/local_storage/trade_history_storage'; -import {Fill, TokenByAddress} from 'ts/types'; -import {utils} from 'ts/utils/utils'; +import { TradeHistoryItem } from 'ts/components/trade_history/trade_history_item'; +import { tradeHistoryStorage } from 'ts/local_storage/trade_history_storage'; +import { Fill, TokenByAddress } from 'ts/types'; +import { utils } from 'ts/utils/utils'; const FILL_POLLING_INTERVAL = 1000; @@ -20,19 +20,19 @@ interface TradeHistoryState { } export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistoryState> { - private fillPollingIntervalId: number; + private _fillPollingIntervalId: number; public constructor(props: TradeHistoryProps) { super(props); - const sortedFills = this.getSortedFills(); + const sortedFills = this._getSortedFills(); this.state = { sortedFills, }; } public componentWillMount() { - this.startPollingForFills(); + this._startPollingForFills(); } public componentWillUnmount() { - this.stopPollingForFills(); + this._stopPollingForFills(); } public componentDidMount() { window.scrollTo(0, 0); @@ -42,16 +42,16 @@ export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistor <div className="lg-px4 md-px4 sm-px2"> <h3>Trade history</h3> <Divider /> - <div className="pt2" style={{height: 608, overflow: 'scroll'}}> - {this.renderTrades()} + <div className="pt2" style={{ height: 608, overflow: 'scroll' }}> + {this._renderTrades()} </div> </div> ); } - private renderTrades() { - const numNonCustomFills = this.numFillsWithoutCustomERC20Tokens(); + private _renderTrades() { + const numNonCustomFills = this._numFillsWithoutCustomERC20Tokens(); if (numNonCustomFills === 0) { - return this.renderEmptyNotice(); + return this._renderEmptyNotice(); } return _.map(this.state.sortedFills, (fill, index) => { @@ -66,14 +66,14 @@ export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistor ); }); } - private renderEmptyNotice() { + private _renderEmptyNotice() { return ( - <Paper className="mt1 p2 mx-auto center" style={{width: '80%'}}> + <Paper className="mt1 p2 mx-auto center" style={{ width: '80%' }}> No filled orders yet. </Paper> ); } - private numFillsWithoutCustomERC20Tokens() { + private _numFillsWithoutCustomERC20Tokens() { let numNonCustomFills = 0; const tokens = _.values(this.props.tokenByAddress); _.each(this.state.sortedFills, fill => { @@ -93,9 +93,9 @@ export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistor }); return numNonCustomFills; } - private startPollingForFills() { - this.fillPollingIntervalId = window.setInterval(() => { - const sortedFills = this.getSortedFills(); + private _startPollingForFills() { + this._fillPollingIntervalId = window.setInterval(() => { + const sortedFills = this._getSortedFills(); if (!utils.deepEqual(sortedFills, this.state.sortedFills)) { this.setState({ sortedFills, @@ -103,10 +103,10 @@ export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistor } }, FILL_POLLING_INTERVAL); } - private stopPollingForFills() { - clearInterval(this.fillPollingIntervalId); + private _stopPollingForFills() { + clearInterval(this._fillPollingIntervalId); } - private getSortedFills() { + private _getSortedFills() { const fillsByHash = tradeHistoryStorage.getUserFillsByHash(this.props.userAddress, this.props.networkId); const fills = _.values(fillsByHash); const sortedFills = _.sortBy(fills, [(fill: Fill) => fill.blockTimestamp * -1]); diff --git a/packages/website/ts/components/trade_history/trade_history_item.tsx b/packages/website/ts/components/trade_history/trade_history_item.tsx index 4dcceadb7..7e42e64e6 100644 --- a/packages/website/ts/components/trade_history/trade_history_item.tsx +++ b/packages/website/ts/components/trade_history/trade_history_item.tsx @@ -1,14 +1,14 @@ -import {ZeroEx} from '0x.js'; -import BigNumber from 'bignumber.js'; +import { ZeroEx } from '0x.js'; +import { BigNumber } from '@0xproject/utils'; import * as _ from 'lodash'; import Paper from 'material-ui/Paper'; -import {colors} from 'material-ui/styles'; import * as moment from 'moment'; import * as React from 'react'; import * as ReactTooltip from 'react-tooltip'; -import {EtherScanIcon} from 'ts/components/ui/etherscan_icon'; -import {Party} from 'ts/components/ui/party'; -import {EtherscanLinkSuffixes, Fill, Token, TokenByAddress} from 'ts/types'; +import { EtherScanIcon } from 'ts/components/ui/etherscan_icon'; +import { Party } from 'ts/components/ui/party'; +import { EtherscanLinkSuffixes, Fill, Token, TokenByAddress } from 'ts/types'; +import { colors } from 'ts/utils/colors'; const PRECISION = 5; const IDENTICON_DIAMETER = 40; @@ -44,30 +44,26 @@ export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, Tra fontWeight: 100, display: 'inline-block', }; - const amountColClassNames = 'col col-12 lg-col-4 md-col-4 lg-py2 md-py2 sm-py1 lg-pr2 md-pr2 \ + const amountColClassNames = + 'col col-12 lg-col-4 md-col-4 lg-py2 md-py2 sm-py1 lg-pr2 md-pr2 \ lg-right-align md-right-align sm-center'; return ( - <Paper - className="py1" - style={{margin: '3px 3px 15px 3px'}} - > + <Paper className="py1" style={{ margin: '3px 3px 15px 3px' }}> <div className="clearfix"> - <div className="col col-12 lg-col-1 md-col-1 pt2 lg-pl3 md-pl3"> - {this.renderDate()} - </div> + <div className="col col-12 lg-col-1 md-col-1 pt2 lg-pl3 md-pl3">{this._renderDate()}</div> <div className="col col-12 lg-col-6 md-col-6 lg-pl3 md-pl3" - style={{fontSize: 12, fontWeight: 100}} + style={{ fontSize: 12, fontWeight: 100 }} > - <div className="flex sm-mx-auto xs-mx-auto" style={{paddingTop: 4, width: 224}}> + <div className="flex sm-mx-auto xs-mx-auto" style={{ paddingTop: 4, width: 224 }}> <Party label="Maker" address={fill.maker} identiconDiameter={IDENTICON_DIAMETER} networkId={this.props.networkId} /> - <i style={{fontSize: 30}} className="zmdi zmdi-swap py3" /> + <i style={{ fontSize: 30 }} className="zmdi zmdi-swap py3" /> <Party label="Taker" address={fill.taker} @@ -76,18 +72,15 @@ export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, Tra /> </div> </div> - <div - className={amountColClassNames} - style={amountColStyle} - > - {this.renderAmounts(makerToken, takerToken)} + <div className={amountColClassNames} style={amountColStyle}> + {this._renderAmounts(makerToken, takerToken)} </div> <div className="col col-12 lg-col-1 md-col-1 lg-pr3 md-pr3 lg-py3 md-py3 sm-pb1 sm-center"> - <div className="pt1 lg-right md-right sm-mx-auto" style={{width: 13}}> + <div className="pt1 lg-right md-right sm-mx-auto" style={{ width: 13 }}> <EtherScanIcon addressOrTxHash={fill.transactionHash} networkId={this.props.networkId} - etherscanLinkSuffixes={EtherscanLinkSuffixes.tx} + etherscanLinkSuffixes={EtherscanLinkSuffixes.Tx} /> </div> </div> @@ -95,7 +88,7 @@ export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, Tra </Paper> ); } - private renderAmounts(makerToken: Token, takerToken: Token) { + private _renderAmounts(makerToken: Token, takerToken: Token) { const fill = this.props.fill; const filledTakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledTakerTokenAmount, takerToken.decimals); const filledMakerTokenAmountInUnits = ZeroEx.toUnitAmount(fill.filledMakerTokenAmount, takerToken.decimals); @@ -124,31 +117,26 @@ export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, Tra givenToken = takerToken; } else { // This condition should never be hit - throw new Error('Found Fill that wasn\'t performed by this user'); + throw new Error("Found Fill that wasn't performed by this user"); } return ( <div> - <div - style={{color: colors.green400, fontSize: 16}} - > - <span>+{' '}</span> - {this.renderAmount(receiveAmount, receiveToken.symbol, receiveToken.decimals)} + <div style={{ color: colors.green400, fontSize: 16 }}> + <span>+ </span> + {this._renderAmount(receiveAmount, receiveToken.symbol, receiveToken.decimals)} </div> - <div - className="pb1 inline-block" - style={{color: colors.red200, fontSize: 16}} - > - <span>-{' '}</span> - {this.renderAmount(givenAmount, givenToken.symbol, givenToken.decimals)} + <div className="pb1 inline-block" style={{ color: colors.red200, fontSize: 16 }}> + <span>- </span> + {this._renderAmount(givenAmount, givenToken.symbol, givenToken.decimals)} </div> - <div style={{color: colors.grey400, fontSize: 14}}> + <div style={{ color: colors.grey400, fontSize: 14 }}> {exchangeRate.toFixed(PRECISION)} {givenToken.symbol}/{receiveToken.symbol} </div> </div> ); } - private renderDate() { + private _renderDate() { const blockMoment = moment.unix(this.props.fill.blockTimestamp); if (!blockMoment.isValid()) { return null; @@ -160,17 +148,18 @@ export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, Tra const dateTooltipId = `${this.props.fill.transactionHash}-date`; return ( - <div - data-tip={true} - data-for={dateTooltipId} - > - <div className="center pt1" style={{fontSize: 13}}>{monthAbreviation}</div> - <div className="center" style={{fontSize: 24, fontWeight: 100}}>{dayOfMonth}</div> + <div data-tip={true} data-for={dateTooltipId}> + <div className="center pt1" style={{ fontSize: 13 }}> + {monthAbreviation} + </div> + <div className="center" style={{ fontSize: 24, fontWeight: 100 }}> + {dayOfMonth} + </div> <ReactTooltip id={dateTooltipId}>{formattedBlockDate}</ReactTooltip> </div> ); } - private renderAmount(amount: BigNumber, symbol: string, decimals: number) { + private _renderAmount(amount: BigNumber, symbol: string, decimals: number) { const unitAmount = ZeroEx.toUnitAmount(amount, decimals); return ( <span> diff --git a/packages/website/ts/components/ui/alert.tsx b/packages/website/ts/components/ui/alert.tsx index 71d2388f2..54881b499 100644 --- a/packages/website/ts/components/ui/alert.tsx +++ b/packages/website/ts/components/ui/alert.tsx @@ -1,19 +1,17 @@ -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {AlertTypes} from 'ts/types'; - -const CUSTOM_GREEN = 'rgb(137, 199, 116)'; +import { AlertTypes } from 'ts/types'; +import { colors } from 'ts/utils/colors'; interface AlertProps { type: AlertTypes; - message: string|React.ReactNode; + message: string | React.ReactNode; } export function Alert(props: AlertProps) { const isAlert = props.type === AlertTypes.ERROR; const errMsgStyles = { - background: isAlert ? colors.red200 : CUSTOM_GREEN, - color: 'white', + background: isAlert ? colors.red200 : colors.lightestGreen, + color: colors.white, marginTop: 10, padding: 4, paddingLeft: 8, diff --git a/packages/website/ts/components/ui/badge.tsx b/packages/website/ts/components/ui/badge.tsx index 15d5ea227..7f7ea006e 100644 --- a/packages/website/ts/components/ui/badge.tsx +++ b/packages/website/ts/components/ui/badge.tsx @@ -1,7 +1,6 @@ import * as _ from 'lodash'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {Styles} from 'ts/types'; +import { Styles } from 'ts/types'; const styles: Styles = { badge: { @@ -43,14 +42,14 @@ export class Badge extends React.Component<BadgeProps, BadgeState> { <div className="p1 center" style={badgeStyle} - onMouseOver={this.setHoverState.bind(this, true)} - onMouseOut={this.setHoverState.bind(this, false)} + onMouseOver={this._setHoverState.bind(this, true)} + onMouseOut={this._setHoverState.bind(this, false)} > {this.props.title} </div> ); } - private setHoverState(isHovering: boolean) { + private _setHoverState(isHovering: boolean) { this.setState({ isHovering, }); diff --git a/packages/website/ts/components/ui/copy_icon.tsx b/packages/website/ts/components/ui/copy_icon.tsx index 5f8e6a060..df55e0922 100644 --- a/packages/website/ts/components/ui/copy_icon.tsx +++ b/packages/website/ts/components/ui/copy_icon.tsx @@ -1,9 +1,9 @@ import * as _ from 'lodash'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; import * as CopyToClipboard from 'react-copy-to-clipboard'; import * as ReactDOM from 'react-dom'; import ReactTooltip = require('react-tooltip'); +import { colors } from 'ts/utils/colors'; interface CopyIconProps { data: string; @@ -15,8 +15,8 @@ interface CopyIconState { } export class CopyIcon extends React.Component<CopyIconProps, CopyIconState> { - private copyTooltipTimeoutId: number; - private copyable: HTMLInputElement; + private _copyTooltipTimeoutId: number; + private _copyable: HTMLInputElement; constructor(props: CopyIconProps) { super(props); this.state = { @@ -25,57 +25,55 @@ export class CopyIcon extends React.Component<CopyIconProps, CopyIconState> { } public componentDidUpdate() { // Remove tooltip if hover away - if (!this.state.isHovering && this.copyTooltipTimeoutId) { - clearInterval(this.copyTooltipTimeoutId); - this.hideTooltip(); + if (!this.state.isHovering && this._copyTooltipTimeoutId) { + clearInterval(this._copyTooltipTimeoutId); + this._hideTooltip(); } } public render() { return ( <div className="inline-block"> - <CopyToClipboard text={this.props.data} onCopy={this.onCopy.bind(this)}> - <div - className="inline flex" - style={{cursor: 'pointer', color: colors.amber600}} - ref={this.setRefToProperty.bind(this)} - data-tip={true} - data-for="copy" - data-event="click" - data-iscapture={true} // This let's the click event continue to propogate - onMouseOver={this.setHoverState.bind(this, true)} - onMouseOut={this.setHoverState.bind(this, false)} - > - <div> - <i style={{fontSize: 15}} className="zmdi zmdi-copy" /> - </div> - {this.props.callToAction && - <div className="pl1">{this.props.callToAction}</div> - } + <CopyToClipboard text={this.props.data} onCopy={this._onCopy.bind(this)}> + <div + className="inline flex" + style={{ cursor: 'pointer', color: colors.amber600 }} + ref={this._setRefToProperty.bind(this)} + data-tip={true} + data-for="copy" + data-event="click" + data-iscapture={true} // This let's the click event continue to propogate + onMouseOver={this._setHoverState.bind(this, true)} + onMouseOut={this._setHoverState.bind(this, false)} + > + <div> + <i style={{ fontSize: 15 }} className="zmdi zmdi-copy" /> </div> - </CopyToClipboard> + {this.props.callToAction && <div className="pl1">{this.props.callToAction}</div>} + </div> + </CopyToClipboard> <ReactTooltip id="copy">Copied!</ReactTooltip> </div> ); } - private setRefToProperty(el: HTMLInputElement) { - this.copyable = el; + private _setRefToProperty(el: HTMLInputElement) { + this._copyable = el; } - private setHoverState(isHovering: boolean) { + private _setHoverState(isHovering: boolean) { this.setState({ isHovering, }); } - private onCopy() { - if (this.copyTooltipTimeoutId) { - clearInterval(this.copyTooltipTimeoutId); + private _onCopy() { + if (this._copyTooltipTimeoutId) { + clearInterval(this._copyTooltipTimeoutId); } const tooltipLifespanMs = 1000; - this.copyTooltipTimeoutId = window.setTimeout(() => { - this.hideTooltip(); + this._copyTooltipTimeoutId = window.setTimeout(() => { + this._hideTooltip(); }, tooltipLifespanMs); } - private hideTooltip() { - ReactTooltip.hide(ReactDOM.findDOMNode(this.copyable)); + private _hideTooltip() { + ReactTooltip.hide(ReactDOM.findDOMNode(this._copyable)); } } diff --git a/packages/website/ts/components/ui/drop_down_menu_item.tsx b/packages/website/ts/components/ui/drop_down_menu_item.tsx index 05b88f7ce..a578fb4f9 100644 --- a/packages/website/ts/components/ui/drop_down_menu_item.tsx +++ b/packages/website/ts/components/ui/drop_down_menu_item.tsx @@ -1,16 +1,10 @@ import * as _ from 'lodash'; import Menu from 'material-ui/Menu'; -import MenuItem from 'material-ui/MenuItem'; import Popover from 'material-ui/Popover'; import * as React from 'react'; -import {Link} from 'react-router-dom'; -import { - Link as ScrollLink, -} from 'react-scroll'; -import {Styles, WebsitePaths} from 'ts/types'; +import { colors } from 'ts/utils/colors'; const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300; -const CUSTOM_LIGHT_GRAY = '#848484'; const DEFAULT_STYLE = { fontSize: 14, }; @@ -34,8 +28,8 @@ export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, Dro menuItemStyle: DEFAULT_STYLE, isNightVersion: false, }; - private isHovering: boolean; - private popoverCloseCheckIntervalId: number; + private _isHovering: boolean; + private _popoverCloseCheckIntervalId: number; constructor(props: DropDownMenuItemProps) { super(props); this.state = { @@ -43,73 +37,66 @@ export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, Dro }; } public componentDidMount() { - this.popoverCloseCheckIntervalId = window.setInterval(() => { - this.checkIfShouldClosePopover(); + this._popoverCloseCheckIntervalId = window.setInterval(() => { + this._checkIfShouldClosePopover(); }, CHECK_CLOSE_POPOVER_INTERVAL_MS); } public componentWillUnmount() { - window.clearInterval(this.popoverCloseCheckIntervalId); + window.clearInterval(this._popoverCloseCheckIntervalId); } public render() { const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color; return ( <div - style={{...this.props.style, color: colorStyle}} - onMouseEnter={this.onHover.bind(this)} - onMouseLeave={this.onHoverOff.bind(this)} + style={{ ...this.props.style, color: colorStyle }} + onMouseEnter={this._onHover.bind(this)} + onMouseLeave={this._onHoverOff.bind(this)} > <div className="flex relative"> - <div style={{paddingRight: 10}}> - {this.props.title} - </div> - <div className="absolute" style={{paddingLeft: 3, right: 3, top: -2}}> - <i className="zmdi zmdi-caret-right" style={{fontSize: 22}} /> + <div style={{ paddingRight: 10 }}>{this.props.title}</div> + <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}> + <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} /> </div> </div> <Popover open={this.state.isDropDownOpen} anchorEl={this.state.anchorEl} - anchorOrigin={{horizontal: 'middle', vertical: 'bottom'}} - targetOrigin={{horizontal: 'middle', vertical: 'top'}} - onRequestClose={this.closePopover.bind(this)} + anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }} + targetOrigin={{ horizontal: 'middle', vertical: 'top' }} + onRequestClose={this._closePopover.bind(this)} useLayerForClickAway={false} > - <div - onMouseEnter={this.onHover.bind(this)} - onMouseLeave={this.onHoverOff.bind(this)} - > - <Menu style={{color: CUSTOM_LIGHT_GRAY}}> - {this.props.subMenuItems} - </Menu> + <div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}> + <Menu style={{ color: colors.grey }}>{this.props.subMenuItems}</Menu> </div> </Popover> </div> ); } - private onHover(event: React.FormEvent<HTMLInputElement>) { - this.isHovering = true; - this.checkIfShouldOpenPopover(event); + private _onHover(event: React.FormEvent<HTMLInputElement>) { + this._isHovering = true; + this._checkIfShouldOpenPopover(event); } - private checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>) { + private _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>) { if (this.state.isDropDownOpen) { return; // noop } this.setState({ - isDropDownOpen: true, - anchorEl: event.currentTarget, + isDropDownOpen: true, + anchorEl: event.currentTarget, }); } - private onHoverOff(event: React.FormEvent<HTMLInputElement>) { - this.isHovering = false; + private _onHoverOff(event: React.FormEvent<HTMLInputElement>) { + this._isHovering = false; } - private checkIfShouldClosePopover() { - if (!this.state.isDropDownOpen || this.isHovering) { + private _checkIfShouldClosePopover() { + if (!this.state.isDropDownOpen || this._isHovering) { return; // noop } - this.closePopover(); + this._closePopover(); } - private closePopover() { + private _closePopover() { this.setState({ isDropDownOpen: false, }); diff --git a/packages/website/ts/components/ui/ethereum_address.tsx b/packages/website/ts/components/ui/ethereum_address.tsx index b3bc0bc59..b75d97e39 100644 --- a/packages/website/ts/components/ui/ethereum_address.tsx +++ b/packages/website/ts/components/ui/ethereum_address.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; import ReactTooltip = require('react-tooltip'); -import {EtherScanIcon} from 'ts/components/ui/etherscan_icon'; -import {EtherscanLinkSuffixes} from 'ts/types'; -import {utils} from 'ts/utils/utils'; +import { EtherScanIcon } from 'ts/components/ui/etherscan_icon'; +import { EtherscanLinkSuffixes } from 'ts/types'; +import { utils } from 'ts/utils/utils'; interface EthereumAddressProps { address: string; @@ -14,19 +14,14 @@ export const EthereumAddress = (props: EthereumAddressProps) => { const truncatedAddress = utils.getAddressBeginAndEnd(props.address); return ( <div> - <div - className="inline" - style={{fontSize: 13}} - data-tip={true} - data-for={tooltipId} - > + <div className="inline" style={{ fontSize: 13 }} data-tip={true} data-for={tooltipId}> {truncatedAddress} </div> <div className="pl1 inline"> <EtherScanIcon addressOrTxHash={props.address} networkId={props.networkId} - etherscanLinkSuffixes={EtherscanLinkSuffixes.address} + etherscanLinkSuffixes={EtherscanLinkSuffixes.Address} /> </div> <ReactTooltip id={tooltipId}>{props.address}</ReactTooltip> diff --git a/packages/website/ts/components/ui/etherscan_icon.tsx b/packages/website/ts/components/ui/etherscan_icon.tsx index 9b4d172f1..3b17bd0fa 100644 --- a/packages/website/ts/components/ui/etherscan_icon.tsx +++ b/packages/website/ts/components/ui/etherscan_icon.tsx @@ -1,9 +1,9 @@ import * as _ from 'lodash'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); -import {EtherscanLinkSuffixes} from 'ts/types'; -import {utils} from 'ts/utils/utils'; +import { EtherscanLinkSuffixes } from 'ts/types'; +import { colors } from 'ts/utils/colors'; +import { utils } from 'ts/utils/utils'; interface EtherScanIconProps { addressOrTxHash: string; @@ -13,38 +13,29 @@ interface EtherScanIconProps { export const EtherScanIcon = (props: EtherScanIconProps) => { const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( - props.addressOrTxHash, props.networkId, EtherscanLinkSuffixes.address, + props.addressOrTxHash, + props.networkId, + EtherscanLinkSuffixes.Address, ); const transactionTooltipId = `${props.addressOrTxHash}-etherscan-icon-tooltip`; return ( <div className="inline"> - {!_.isUndefined(etherscanLinkIfExists) ? - <a - href={etherscanLinkIfExists} - target="_blank" - > + {!_.isUndefined(etherscanLinkIfExists) ? ( + <a href={etherscanLinkIfExists} target="_blank"> {renderIcon()} - </a> : - <div - className="inline" - data-tip={true} - data-for={transactionTooltipId} - > + </a> + ) : ( + <div className="inline" data-tip={true} data-for={transactionTooltipId}> {renderIcon()} <ReactTooltip id={transactionTooltipId}> Your network (id: {props.networkId}) is not supported by Etherscan </ReactTooltip> </div> - } + )} </div> ); }; function renderIcon() { - return ( - <i - style={{color: colors.amber600}} - className="zmdi zmdi-open-in-new" - /> - ); + return <i style={{ color: colors.amber600 }} className="zmdi zmdi-open-in-new" />; } diff --git a/packages/website/ts/components/ui/fake_text_field.tsx b/packages/website/ts/components/ui/fake_text_field.tsx index 90bc47f01..f3d9410f6 100644 --- a/packages/website/ts/components/ui/fake_text_field.tsx +++ b/packages/website/ts/components/ui/fake_text_field.tsx @@ -1,7 +1,6 @@ -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {InputLabel} from 'ts/components/ui/input_label'; -import {Styles} from 'ts/types'; +import { InputLabel } from 'ts/components/ui/input_label'; +import { Styles } from 'ts/types'; const styles: Styles = { hr: { @@ -26,7 +25,7 @@ export function FakeTextField(props: FakeTextFieldProps) { return ( <div className="relative"> {props.label !== '' && <InputLabel text={props.label} />} - <div className="pb2" style={{height: 23}}> + <div className="pb2" style={{ height: 23 }}> {props.children} </div> <hr style={styles.hr} /> diff --git a/packages/website/ts/components/ui/flash_message.tsx b/packages/website/ts/components/ui/flash_message.tsx index ab4edbbb0..2cb1fc764 100644 --- a/packages/website/ts/components/ui/flash_message.tsx +++ b/packages/website/ts/components/ui/flash_message.tsx @@ -1,13 +1,13 @@ import * as _ from 'lodash'; import Snackbar from 'material-ui/Snackbar'; import * as React from 'react'; -import {Dispatcher} from 'ts/redux/dispatcher'; +import { Dispatcher } from 'ts/redux/dispatcher'; const SHOW_DURATION_MS = 4000; interface FlashMessageProps { dispatcher: Dispatcher; - flashMessage?: string|React.ReactNode; + flashMessage?: string | React.ReactNode; showDurationMs?: number; bodyStyle?: React.CSSProperties; } @@ -26,7 +26,7 @@ export class FlashMessage extends React.Component<FlashMessageProps, FlashMessag open={true} message={this.props.flashMessage} autoHideDuration={this.props.showDurationMs} - onRequestClose={this.onClose.bind(this)} + onRequestClose={this._onClose.bind(this)} bodyStyle={this.props.bodyStyle} /> ); @@ -34,7 +34,7 @@ export class FlashMessage extends React.Component<FlashMessageProps, FlashMessag return null; } } - private onClose() { + private _onClose() { this.props.dispatcher.hideFlashMessage(); } } diff --git a/packages/website/ts/components/ui/help_tooltip.tsx b/packages/website/ts/components/ui/help_tooltip.tsx index 003b795ef..d827eebb9 100644 --- a/packages/website/ts/components/ui/help_tooltip.tsx +++ b/packages/website/ts/components/ui/help_tooltip.tsx @@ -9,13 +9,13 @@ interface HelpTooltipProps { export const HelpTooltip = (props: HelpTooltipProps) => { return ( <div - style={{...props.style}} + style={{ ...props.style }} className="inline-block" data-tip={props.explanation} data-for="helpTooltip" data-multiline={true} > - <i style={{fontSize: 16}} className="zmdi zmdi-help" /> + <i style={{ fontSize: 16 }} className="zmdi zmdi-help" /> <ReactTooltip id="helpTooltip" /> </div> ); diff --git a/packages/website/ts/components/ui/identicon.tsx b/packages/website/ts/components/ui/identicon.tsx index a741ae43b..bad6c2a78 100644 --- a/packages/website/ts/components/ui/identicon.tsx +++ b/packages/website/ts/components/ui/identicon.tsx @@ -1,7 +1,7 @@ import blockies = require('blockies'); import * as _ from 'lodash'; import * as React from 'react'; -import {constants} from 'ts/utils/constants'; +import { constants } from 'ts/utils/constants'; interface IdenticonProps { address: string; @@ -27,9 +27,21 @@ export class Identicon extends React.Component<IdenticonProps, IdenticonState> { return ( <div className="circle mx-auto relative transitionFix" - style={{width: diameter, height: diameter, overflow: 'hidden', ...this.props.style}} + style={{ + width: diameter, + height: diameter, + overflow: 'hidden', + ...this.props.style, + }} > - <img src={icon.toDataURL()} style={{width: diameter, height: diameter, imageRendering: 'pixelated'}}/> + <img + src={icon.toDataURL()} + style={{ + width: diameter, + height: diameter, + imageRendering: 'pixelated', + }} + /> </div> ); } diff --git a/packages/website/ts/components/ui/input_label.tsx b/packages/website/ts/components/ui/input_label.tsx index 852097519..e2009ad20 100644 --- a/packages/website/ts/components/ui/input_label.tsx +++ b/packages/website/ts/components/ui/input_label.tsx @@ -1,5 +1,5 @@ -import {colors} from 'material-ui/styles'; import * as React from 'react'; +import { colors } from 'ts/utils/colors'; export interface InputLabelProps { text: string | Element | React.ReactNode; @@ -7,7 +7,7 @@ export interface InputLabelProps { const styles = { label: { - color: colors.grey500, + color: colors.grey, fontSize: 12, pointerEvents: 'none', textAlign: 'left', @@ -21,7 +21,5 @@ const styles = { }; export const InputLabel = (props: InputLabelProps) => { - return ( - <label style={styles.label}>{props.text}</label> - ); + return <label style={styles.label}>{props.text}</label>; }; diff --git a/packages/website/ts/components/ui/labeled_switcher.tsx b/packages/website/ts/components/ui/labeled_switcher.tsx deleted file mode 100644 index 80a8fbe94..000000000 --- a/packages/website/ts/components/ui/labeled_switcher.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import * as _ from 'lodash'; -import {colors} from 'material-ui/styles'; -import * as React from 'react'; - -const CUSTOM_BLUE = '#63A6F1'; - -interface LabeledSwitcherProps { - labelLeft: string; - labelRight: string; - isLeftInitiallySelected: boolean; - onLeftLabelClickAsync: () => Promise<boolean>; - onRightLabelClickAsync: () => Promise<boolean>; -} - -interface LabeledSwitcherState { - isLeftSelected: boolean; -} - -export class LabeledSwitcher extends React.Component<LabeledSwitcherProps, LabeledSwitcherState> { - constructor(props: LabeledSwitcherProps) { - super(props); - this.state = { - isLeftSelected: props.isLeftInitiallySelected, - }; - } - public render() { - const isLeft = true; - return ( - <div - className="rounded clearfix" - > - {this.renderLabel(this.props.labelLeft, isLeft, this.state.isLeftSelected)} - {this.renderLabel(this.props.labelRight, !isLeft, !this.state.isLeftSelected)} - </div> - ); - } - private renderLabel(title: string, isLeft: boolean, isSelected: boolean) { - const borderStyle = `2px solid ${isSelected ? '#4F8BCF' : '#DADADA'}`; - const style = { - cursor: 'pointer', - backgroundColor: isSelected ? CUSTOM_BLUE : colors.grey200, - color: isSelected ? 'white' : '#A5A5A5', - boxShadow: isSelected ? `inset 0px 0px 4px #4083CE` : 'inset 0px 0px 4px #F7F6F6', - borderTop: borderStyle, - borderBottom: borderStyle, - [isLeft ? 'borderLeft' : 'borderRight']: borderStyle, - paddingTop: 12, - paddingBottom: 12, - }; - return ( - <div - className={`col col-6 center p1 ${isLeft ? 'rounded-left' : 'rounded-right'}`} - style={style} - onClick={this.onLabelClickAsync.bind(this, isLeft)} - > - {title} - </div> - ); - } - private async onLabelClickAsync(isLeft: boolean): Promise<void> { - this.setState({ - isLeftSelected: isLeft, - }); - const didSucceed = isLeft ? - await this.props.onLeftLabelClickAsync() : - await this.props.onRightLabelClickAsync(); - if (!didSucceed) { - this.setState({ - isLeftSelected: !isLeft, - }); - } - } -} diff --git a/packages/website/ts/components/ui/lifecycle_raised_button.tsx b/packages/website/ts/components/ui/lifecycle_raised_button.tsx index cba94ca8c..8ff856a75 100644 --- a/packages/website/ts/components/ui/lifecycle_raised_button.tsx +++ b/packages/website/ts/components/ui/lifecycle_raised_button.tsx @@ -1,25 +1,24 @@ import * as _ from 'lodash'; import RaisedButton from 'material-ui/RaisedButton'; import * as React from 'react'; -import {Blockchain} from 'ts/blockchain'; -import {Token} from 'ts/types'; -import {utils} from 'ts/utils/utils'; +import { colors } from 'ts/utils/colors'; +import { utils } from 'ts/utils/utils'; const COMPLETE_STATE_SHOW_LENGTH_MS = 2000; enum ButtonState { - READY, - LOADING, - COMPLETE, + READY, + LOADING, + COMPLETE, } interface LifeCycleRaisedButtonProps { isHidden?: boolean; isDisabled?: boolean; isPrimary?: boolean; - labelReady: React.ReactNode|string; - labelLoading: React.ReactNode|string; - labelComplete: React.ReactNode|string; + labelReady: React.ReactNode | string; + labelLoading: React.ReactNode | string; + labelComplete: React.ReactNode | string; onClickAsyncFn: () => Promise<boolean>; backgroundColor?: string; labelColor?: string; @@ -29,15 +28,14 @@ interface LifeCycleRaisedButtonState { buttonState: ButtonState; } -export class LifeCycleRaisedButton extends - React.Component<LifeCycleRaisedButtonProps, LifeCycleRaisedButtonState> { +export class LifeCycleRaisedButton extends React.Component<LifeCycleRaisedButtonProps, LifeCycleRaisedButtonState> { public static defaultProps: Partial<LifeCycleRaisedButtonProps> = { isDisabled: false, - backgroundColor: 'white', - labelColor: 'rgb(97, 97, 97)', + backgroundColor: colors.white, + labelColor: colors.darkGrey, }; - private buttonTimeoutId: number; - private didUnmount: boolean; + private _buttonTimeoutId: number; + private _didUnmount: boolean; constructor(props: LifeCycleRaisedButtonProps) { super(props); this.state = { @@ -45,8 +43,8 @@ export class LifeCycleRaisedButton extends }; } public componentWillUnmount() { - clearTimeout(this.buttonTimeoutId); - this.didUnmount = true; + clearTimeout(this._buttonTimeoutId); + this._didUnmount = true; } public render() { if (this.props.isHidden) { @@ -71,7 +69,7 @@ export class LifeCycleRaisedButton extends <RaisedButton primary={this.props.isPrimary} label={label} - style={{width: '100%'}} + style={{ width: '100%' }} backgroundColor={this.props.backgroundColor} labelColor={this.props.labelColor} onTouchTap={this.onClickAsync.bind(this)} @@ -84,14 +82,14 @@ export class LifeCycleRaisedButton extends buttonState: ButtonState.LOADING, }); const didSucceed = await this.props.onClickAsyncFn(); - if (this.didUnmount) { + if (this._didUnmount) { return; // noop since unmount called before async callback returned. } if (didSucceed) { this.setState({ buttonState: ButtonState.COMPLETE, }); - this.buttonTimeoutId = window.setTimeout(() => { + this._buttonTimeoutId = window.setTimeout(() => { this.setState({ buttonState: ButtonState.READY, }); diff --git a/packages/website/ts/components/ui/loading.tsx b/packages/website/ts/components/ui/loading.tsx index 83636b5ff..aa319e9e9 100644 --- a/packages/website/ts/components/ui/loading.tsx +++ b/packages/website/ts/components/ui/loading.tsx @@ -1,9 +1,9 @@ import * as _ from 'lodash'; import Paper from 'material-ui/Paper'; import * as React from 'react'; -import {DefaultPlayer as Video} from 'react-html5video'; +import { DefaultPlayer as Video } from 'react-html5video'; import 'react-html5video/dist/styles.css'; -import {utils} from 'ts/utils/utils'; +import { utils } from 'ts/utils/utils'; interface LoadingProps {} @@ -12,11 +12,12 @@ interface LoadingState {} export class Loading extends React.Component<LoadingProps, LoadingState> { public render() { return ( - <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{height: 500}}> - <Paper className="mx-auto" style={{maxWidth: 400}}> - {utils.isUserOnMobile() ? - <img className="p1" src="/gifs/0xAnimation.gif" width="96%" /> : - <div style={{pointerEvents: 'none'}}> + <div className="pt4 sm-px2 sm-pt2 sm-m1" style={{ height: 500 }}> + <Paper className="mx-auto" style={{ maxWidth: 400 }}> + {utils.isUserOnMobile() ? ( + <img className="p1" src="/gifs/0xAnimation.gif" width="96%" /> + ) : ( + <div style={{ pointerEvents: 'none' }}> <Video autoPlay={true} loop={true} @@ -27,8 +28,10 @@ export class Loading extends React.Component<LoadingProps, LoadingState> { <source src="/videos/0xAnimation.mp4" type="video/mp4" /> </Video> </div> - } - <div className="center pt2" style={{paddingBottom: 11}}>Connecting to the blockchain...</div> + )} + <div className="center pt2" style={{ paddingBottom: 11 }}> + Connecting to the blockchain... + </div> </Paper> </div> ); diff --git a/packages/website/ts/components/ui/menu_item.tsx b/packages/website/ts/components/ui/menu_item.tsx index 862f28457..3482f436c 100644 --- a/packages/website/ts/components/ui/menu_item.tsx +++ b/packages/website/ts/components/ui/menu_item.tsx @@ -1,9 +1,6 @@ import * as _ from 'lodash'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {Link} from 'react-router-dom'; -import {Styles} from 'ts/types'; -import {constants} from 'ts/utils/constants'; +import { Link } from 'react-router-dom'; interface MenuItemProps { to: string; @@ -33,20 +30,20 @@ export class MenuItem extends React.Component<MenuItemProps, MenuItemState> { opacity: this.state.isHovering ? 0.5 : 1, }; return ( - <Link to={this.props.to} style={{textDecoration: 'none', ...this.props.style}}> + <Link to={this.props.to} style={{ textDecoration: 'none', ...this.props.style }}> <div onClick={this.props.onClick.bind(this)} className={`mx-auto ${this.props.className}`} style={menuItemStyles} - onMouseEnter={this.onToggleHover.bind(this, true)} - onMouseLeave={this.onToggleHover.bind(this, false)} + onMouseEnter={this._onToggleHover.bind(this, true)} + onMouseLeave={this._onToggleHover.bind(this, false)} > {this.props.children} </div> </Link> ); } - private onToggleHover(isHovering: boolean) { + private _onToggleHover(isHovering: boolean) { this.setState({ isHovering, }); diff --git a/packages/website/ts/components/ui/party.tsx b/packages/website/ts/components/ui/party.tsx index 5bafa6071..ca2577b61 100644 --- a/packages/website/ts/components/ui/party.tsx +++ b/packages/website/ts/components/ui/party.tsx @@ -1,16 +1,14 @@ import * as _ from 'lodash'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; import ReactTooltip = require('react-tooltip'); -import {EthereumAddress} from 'ts/components/ui/ethereum_address'; -import {Identicon} from 'ts/components/ui/identicon'; -import {EtherscanLinkSuffixes} from 'ts/types'; -import {utils} from 'ts/utils/utils'; +import { EthereumAddress } from 'ts/components/ui/ethereum_address'; +import { Identicon } from 'ts/components/ui/identicon'; +import { EtherscanLinkSuffixes } from 'ts/types'; +import { colors } from 'ts/utils/colors'; +import { utils } from 'ts/utils/utils'; -const MIN_ADDRESS_WIDTH = 60; const IMAGE_DIMENSION = 100; const IDENTICON_DIAMETER = 95; -const CHECK_MARK_GREEN = 'rgb(0, 195, 62)'; interface PartyProps { label: string; @@ -33,10 +31,7 @@ export class Party extends React.Component<PartyProps, PartyState> { public render() { const label = this.props.label; const address = this.props.address; - const tooltipId = `${label}-${address}-tooltip`; const identiconDiameter = this.props.identiconDiameter; - const addressWidth = identiconDiameter > MIN_ADDRESS_WIDTH ? - identiconDiameter : MIN_ADDRESS_WIDTH; const emptyIdenticonStyles = { width: identiconDiameter, height: identiconDiameter, @@ -49,100 +44,94 @@ export class Party extends React.Component<PartyProps, PartyState> { height: IMAGE_DIMENSION, }; const etherscanLinkIfExists = utils.getEtherScanLinkIfExists( - this.props.address, this.props.networkId, EtherscanLinkSuffixes.address, + this.props.address, + this.props.networkId, + EtherscanLinkSuffixes.Address, ); const isRegistered = this.props.isInTokenRegistry; const registeredTooltipId = `${this.props.address}-${isRegistered}-registeredTooltip`; const uniqueNameAndSymbolTooltipId = `${this.props.address}-${isRegistered}-uniqueTooltip`; return ( - <div style={{overflow: 'hidden'}}> + <div style={{ overflow: 'hidden' }}> <div className="pb1 center">{label}</div> - {_.isEmpty(address) ? - <div - className="circle mx-auto" - style={emptyIdenticonStyles} - /> : - <a - href={etherscanLinkIfExists} - target="_blank" - > - {isRegistered && !_.isUndefined(this.props.alternativeImage) ? - <img - style={tokenImageStyle} - src={this.props.alternativeImage} - /> : - <div - className="mx-auto" - style={{height: identiconDiameter, width: identiconDiameter}} - > - <Identicon - address={this.props.address} - diameter={identiconDiameter} - style={this.props.identiconStyle} - /> - </div> - } - </a> - } - <div - className="mx-auto center pt1" - > - <div style={{height: 25}}> + {_.isEmpty(address) ? ( + <div className="circle mx-auto" style={emptyIdenticonStyles} /> + ) : ( + <a href={etherscanLinkIfExists} target="_blank"> + {isRegistered && !_.isUndefined(this.props.alternativeImage) ? ( + <img style={tokenImageStyle} src={this.props.alternativeImage} /> + ) : ( + <div className="mx-auto" style={{ height: identiconDiameter, width: identiconDiameter }}> + <Identicon + address={this.props.address} + diameter={identiconDiameter} + style={this.props.identiconStyle} + /> + </div> + )} + </a> + )} + <div className="mx-auto center pt1"> + <div style={{ height: 25 }}> <EthereumAddress address={address} networkId={this.props.networkId} /> </div> - {!_.isUndefined(this.props.isInTokenRegistry) && + {!_.isUndefined(this.props.isInTokenRegistry) && ( <div> <div data-tip={true} data-for={registeredTooltipId} className="mx-auto" - style={{fontSize: 13, width: 127}} + style={{ fontSize: 13, width: 127 }} > - <span style={{color: isRegistered ? CHECK_MARK_GREEN : colors.red500}}> + <span + style={{ + color: isRegistered ? colors.brightGreen : colors.red500, + }} + > <i className={`zmdi ${isRegistered ? 'zmdi-check-circle' : 'zmdi-alert-triangle'}`} /> </span>{' '} <span>{isRegistered ? 'Registered' : 'Unregistered'} token</span> <ReactTooltip id={registeredTooltipId}> - {isRegistered ? + {isRegistered ? ( <div> This token address was found in the token registry<br /> smart contract and is therefore believed to be a<br /> legitimate token. - </div> : + </div> + ) : ( <div> This token is not included in the token registry<br /> smart contract. We cannot guarantee the legitimacy<br /> of this token. Make sure to verify its address on Etherscan. </div> - } + )} </ReactTooltip> </div> </div> - } - {!_.isUndefined(this.props.hasUniqueNameAndSymbol) && !this.props.hasUniqueNameAndSymbol && - <div> - <div - data-tip={true} - data-for={uniqueNameAndSymbolTooltipId} - className="mx-auto" - style={{fontSize: 13, width: 127}} - > - <span style={{color: colors.red500}}> - <i - className="zmdi zmdi-alert-octagon" - /> - </span>{' '} - <span>Suspicious token</span> - <ReactTooltip id={uniqueNameAndSymbolTooltipId}> - This token shares it's name, symbol or both with<br /> - a token in the 0x Token Registry but it has a different<br /> - smart contract address. This is most likely a scam token! - </ReactTooltip> + )} + {!_.isUndefined(this.props.hasUniqueNameAndSymbol) && + !this.props.hasUniqueNameAndSymbol && ( + <div> + <div + data-tip={true} + data-for={uniqueNameAndSymbolTooltipId} + className="mx-auto" + style={{ fontSize: 13, width: 127 }} + > + <span style={{ color: colors.red500 }}> + <i className="zmdi zmdi-alert-octagon" /> + </span>{' '} + <span>Suspicious token</span> + <ReactTooltip id={uniqueNameAndSymbolTooltipId}> + This token shares it's name, symbol or both with<br /> + a token in the 0x Token Registry but it has a different<br /> + smart contract address. This is most likely a scam token! + </ReactTooltip> + </div> </div> - </div> - } + )} </div> </div> ); diff --git a/packages/website/ts/components/ui/required_label.tsx b/packages/website/ts/components/ui/required_label.tsx index db69d7278..a5e7a22ce 100644 --- a/packages/website/ts/components/ui/required_label.tsx +++ b/packages/website/ts/components/ui/required_label.tsx @@ -1,15 +1,15 @@ -import {colors} from 'material-ui/styles'; import * as React from 'react'; +import { colors } from 'ts/utils/colors'; export interface RequiredLabelProps { - label: string|React.ReactNode; + label: string | React.ReactNode; } export const RequiredLabel = (props: RequiredLabelProps) => { return ( <span> {props.label} - <span style={{color: colors.red600}}>*</span> + <span style={{ color: colors.red600 }}>*</span> </span> ); }; diff --git a/packages/website/ts/components/ui/simple_loading.tsx b/packages/website/ts/components/ui/simple_loading.tsx index d55d7851d..81744196d 100644 --- a/packages/website/ts/components/ui/simple_loading.tsx +++ b/packages/website/ts/components/ui/simple_loading.tsx @@ -1,5 +1,4 @@ import CircularProgress from 'material-ui/CircularProgress'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; export interface SimpleLoadingProps { @@ -8,15 +7,10 @@ export interface SimpleLoadingProps { export const SimpleLoading = (props: SimpleLoadingProps) => { return ( - <div className="mx-auto pt3" style={{maxWidth: 400, height: 409}}> - <div - className="relative" - style={{top: '50%', transform: 'translateY(-50%)', height: 95}} - > + <div className="mx-auto pt3" style={{ maxWidth: 400, height: 409 }}> + <div className="relative" style={{ top: '50%', transform: 'translateY(-50%)', height: 95 }}> <CircularProgress /> - <div className="pt3 pb3"> - {props.message} - </div> + <div className="pt3 pb3">{props.message}</div> </div> </div> ); diff --git a/packages/website/ts/components/ui/swap_icon.tsx b/packages/website/ts/components/ui/swap_icon.tsx index 2e6ae89bb..c41592287 100644 --- a/packages/website/ts/components/ui/swap_icon.tsx +++ b/packages/website/ts/components/ui/swap_icon.tsx @@ -1,7 +1,6 @@ import * as _ from 'lodash'; -import {colors} from 'material-ui/styles'; import * as React from 'react'; -import {constants} from 'ts/utils/constants'; +import { colors } from 'ts/utils/colors'; interface SwapIconProps { swapTokensFn: () => void; @@ -26,19 +25,16 @@ export class SwapIcon extends React.Component<SwapIconProps, SwapIconState> { return ( <div className="mx-auto pt4" - style={{cursor: 'pointer', height: 50, width: 37.5}} + style={{ cursor: 'pointer', height: 50, width: 37.5 }} onClick={this.props.swapTokensFn} - onMouseEnter={this.onToggleHover.bind(this, true)} - onMouseLeave={this.onToggleHover.bind(this, false)} + onMouseEnter={this._onToggleHover.bind(this, true)} + onMouseLeave={this._onToggleHover.bind(this, false)} > - <i - style={swapStyles} - className="zmdi zmdi-swap" - /> + <i style={swapStyles} className="zmdi zmdi-swap" /> </div> ); } - private onToggleHover(isHovering: boolean) { + private _onToggleHover(isHovering: boolean) { this.setState({ isHovering, }); diff --git a/packages/website/ts/components/ui/token_icon.tsx b/packages/website/ts/components/ui/token_icon.tsx index d3a7c9a8c..ff57a96de 100644 --- a/packages/website/ts/components/ui/token_icon.tsx +++ b/packages/website/ts/components/ui/token_icon.tsx @@ -1,7 +1,7 @@ import * as _ from 'lodash'; import * as React from 'react'; -import {Identicon} from 'ts/components/ui/identicon'; -import {Token} from 'ts/types'; +import { Identicon } from 'ts/components/ui/identicon'; +import { Token } from 'ts/types'; interface TokenIconProps { token: Token; @@ -16,13 +16,11 @@ export class TokenIcon extends React.Component<TokenIconProps, TokenIconState> { const diameter = this.props.diameter; return ( <div> - {(token.isRegistered && !_.isUndefined(token.iconUrl)) ? - <img - style={{width: diameter, height: diameter}} - src={token.iconUrl} - /> : + {token.isRegistered && !_.isUndefined(token.iconUrl) ? ( + <img style={{ width: diameter, height: diameter }} src={token.iconUrl} /> + ) : ( <Identicon address={token.address} diameter={diameter} /> - } + )} </div> ); } diff --git a/packages/website/ts/components/visual_order.tsx b/packages/website/ts/components/visual_order.tsx index 037a321ff..092954086 100644 --- a/packages/website/ts/components/visual_order.tsx +++ b/packages/website/ts/components/visual_order.tsx @@ -1,10 +1,9 @@ -import {ZeroEx} from '0x.js'; +import { ZeroEx } from '0x.js'; import * as _ from 'lodash'; import * as React from 'react'; -import {Party} from 'ts/components/ui/party'; -import {AssetToken, Token, TokenByAddress} from 'ts/types'; -import {constants} from 'ts/utils/constants'; -import {utils} from 'ts/utils/utils'; +import { Party } from 'ts/components/ui/party'; +import { AssetToken, Token, TokenByAddress } from 'ts/types'; +import { utils } from 'ts/utils/utils'; const PRECISION = 5; @@ -43,13 +42,13 @@ export class VisualOrder extends React.Component<VisualOrderProps, VisualOrderSt </div> <div className="col col-2 center pt1"> <div className="pb1"> - {this.renderAmount(this.props.takerAssetToken, this.props.takerToken)} + {this._renderAmount(this.props.takerAssetToken, this.props.takerToken)} </div> <div className="lg-p2 md-p2 sm-p1"> - <img src="/images/trade_arrows.png" style={{width: 47}} /> + <img src="/images/trade_arrows.png" style={{ width: 47 }} /> </div> <div className="pt1"> - {this.renderAmount(this.props.makerAssetToken, this.props.makerToken)} + {this._renderAmount(this.props.makerAssetToken, this.props.makerToken)} </div> </div> <div className="col col-5 center"> @@ -66,10 +65,10 @@ export class VisualOrder extends React.Component<VisualOrderProps, VisualOrderSt </div> ); } - private renderAmount(assetToken: AssetToken, token: Token) { + private _renderAmount(assetToken: AssetToken, token: Token) { const unitAmount = ZeroEx.toUnitAmount(assetToken.amount, token.decimals); return ( - <div style={{fontSize: 13}}> + <div style={{ fontSize: 13 }}> {unitAmount.toNumber().toFixed(PRECISION)} {token.symbol} </div> ); |