aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts/components
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website/ts/components')
-rw-r--r--packages/website/ts/components/dialogs/blockchain_err_dialog.tsx282
-rw-r--r--packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx290
-rw-r--r--packages/website/ts/components/dialogs/ledger_config_dialog.tsx460
-rw-r--r--packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx52
-rw-r--r--packages/website/ts/components/dialogs/send_dialog.tsx200
-rw-r--r--packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx160
-rw-r--r--packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx70
-rw-r--r--packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx46
-rw-r--r--packages/website/ts/components/eth_weth_conversion_button.tsx208
-rw-r--r--packages/website/ts/components/eth_wrappers.tsx674
-rw-r--r--packages/website/ts/components/fill_order.tsx1270
-rw-r--r--packages/website/ts/components/fill_order_json.tsx124
-rw-r--r--packages/website/ts/components/fill_warning_dialog.tsx70
-rw-r--r--packages/website/ts/components/flash_messages/token_send_completed.tsx38
-rw-r--r--packages/website/ts/components/flash_messages/transaction_submitted.tsx30
-rw-r--r--packages/website/ts/components/footer.tsx362
-rw-r--r--packages/website/ts/components/generate_order/asset_picker.tsx498
-rw-r--r--packages/website/ts/components/generate_order/generate_order_form.tsx620
-rw-r--r--packages/website/ts/components/generate_order/new_token_form.tsx418
-rw-r--r--packages/website/ts/components/inputs/address_input.tsx112
-rw-r--r--packages/website/ts/components/inputs/allowance_toggle.tsx142
-rw-r--r--packages/website/ts/components/inputs/balance_bounded_input.tsx264
-rw-r--r--packages/website/ts/components/inputs/eth_amount_input.tsx68
-rw-r--r--packages/website/ts/components/inputs/expiration_input.tsx168
-rw-r--r--packages/website/ts/components/inputs/hash_input.tsx86
-rw-r--r--packages/website/ts/components/inputs/identicon_address_input.tsx76
-rw-r--r--packages/website/ts/components/inputs/token_amount_input.tsx108
-rw-r--r--packages/website/ts/components/inputs/token_input.tsx164
-rw-r--r--packages/website/ts/components/order_json.tsx322
-rw-r--r--packages/website/ts/components/portal.tsx630
-rw-r--r--packages/website/ts/components/portal_menu.tsx122
-rw-r--r--packages/website/ts/components/send_button.tsx136
-rw-r--r--packages/website/ts/components/token_balances.tsx1066
-rw-r--r--packages/website/ts/components/top_bar.tsx634
-rw-r--r--packages/website/ts/components/top_bar_menu_item.tsx74
-rw-r--r--packages/website/ts/components/track_token_confirmation.tsx94
-rw-r--r--packages/website/ts/components/trade_history/trade_history.tsx190
-rw-r--r--packages/website/ts/components/trade_history/trade_history_item.tsx282
-rw-r--r--packages/website/ts/components/ui/alert.tsx30
-rw-r--r--packages/website/ts/components/ui/badge.tsx84
-rw-r--r--packages/website/ts/components/ui/copy_icon.tsx126
-rw-r--r--packages/website/ts/components/ui/drop_down_menu_item.tsx170
-rw-r--r--packages/website/ts/components/ui/ethereum_address.tsx38
-rw-r--r--packages/website/ts/components/ui/etherscan_icon.tsx52
-rw-r--r--packages/website/ts/components/ui/fake_text_field.tsx44
-rw-r--r--packages/website/ts/components/ui/flash_message.tsx52
-rw-r--r--packages/website/ts/components/ui/help_tooltip.tsx28
-rw-r--r--packages/website/ts/components/ui/identicon.tsx72
-rw-r--r--packages/website/ts/components/ui/input_label.tsx28
-rw-r--r--packages/website/ts/components/ui/lifecycle_raised_button.tsx166
-rw-r--r--packages/website/ts/components/ui/loading.tsx52
-rw-r--r--packages/website/ts/components/ui/menu_item.tsx78
-rw-r--r--packages/website/ts/components/ui/party.tsx240
-rw-r--r--packages/website/ts/components/ui/required_label.tsx14
-rw-r--r--packages/website/ts/components/ui/simple_loading.tsx18
-rw-r--r--packages/website/ts/components/ui/swap_icon.tsx60
-rw-r--r--packages/website/ts/components/ui/token_icon.tsx30
-rw-r--r--packages/website/ts/components/visual_order.tsx120
58 files changed, 6056 insertions, 6056 deletions
diff --git a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx
index f555ca6b1..e0f61a29b 100644
--- a/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx
+++ b/packages/website/ts/components/dialogs/blockchain_err_dialog.tsx
@@ -9,150 +9,150 @@ import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
interface BlockchainErrDialogProps {
- blockchain: Blockchain;
- blockchainErr: BlockchainErrs;
- isOpen: boolean;
- userAddress: string;
- toggleDialogFn: (isOpen: boolean) => void;
- networkId: number;
+ blockchain: Blockchain;
+ blockchainErr: BlockchainErrs;
+ isOpen: boolean;
+ userAddress: string;
+ toggleDialogFn: (isOpen: boolean) => void;
+ networkId: number;
}
export class BlockchainErrDialog extends React.Component<BlockchainErrDialogProps, undefined> {
- public render() {
- const dialogActions = [
- <FlatButton
- key="blockchainErrOk"
- label="Ok"
- primary={true}
- onTouchTap={this.props.toggleDialogFn.bind(this.props.toggleDialogFn, false)}
- />,
- ];
+ public render() {
+ const dialogActions = [
+ <FlatButton
+ key="blockchainErrOk"
+ label="Ok"
+ primary={true}
+ onTouchTap={this.props.toggleDialogFn.bind(this.props.toggleDialogFn, false)}
+ />,
+ ];
- const hasWalletAddress = this.props.userAddress !== '';
- return (
- <Dialog
- title={this._getTitle(hasWalletAddress)}
- titleStyle={{ fontWeight: 100 }}
- actions={dialogActions}
- open={this.props.isOpen}
- 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>
- </Dialog>
- );
- }
- 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.DisconnectedFromEthereumNode) {
- return 'Disconnected from Ethereum network';
- } else {
- return 'Unexpected error';
- }
- }
- 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.DisconnectedFromEthereumNode) {
- return this._renderDisconnectedFromNode();
- } else {
- return this._renderUnexpectedErrorExplanation();
- }
- }
- private _renderDisconnectedFromNode() {
- return (
- <div>
- 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.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 _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:
- </div>
- <h4>1. Metamask chrome extension</h4>
- <div>
- You can install the{' '}
- <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank">
- Metamask
- </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.
- </div>
- </div>
- <h4>Parity Signer</h4>
- <div>
- 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.
- </div>
- </div>
- );
- }
- 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.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.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.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 \
+ const hasWalletAddress = this.props.userAddress !== '';
+ return (
+ <Dialog
+ title={this._getTitle(hasWalletAddress)}
+ titleStyle={{ fontWeight: 100 }}
+ actions={dialogActions}
+ open={this.props.isOpen}
+ 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>
+ </Dialog>
+ );
+ }
+ 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.DisconnectedFromEthereumNode) {
+ return 'Disconnected from Ethereum network';
+ } else {
+ return 'Unexpected error';
+ }
+ }
+ 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.DisconnectedFromEthereumNode) {
+ return this._renderDisconnectedFromNode();
+ } else {
+ return this._renderUnexpectedErrorExplanation();
+ }
+ }
+ private _renderDisconnectedFromNode() {
+ return (
+ <div>
+ 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.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 _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:
+ </div>
+ <h4>1. Metamask chrome extension</h4>
+ <div>
+ You can install the{' '}
+ <a href={constants.URL_METAMASK_CHROME_STORE} target="_blank">
+ Metamask
+ </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.
+ </div>
+ </div>
+ <h4>Parity Signer</h4>
+ <div>
+ 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.
+ </div>
+ </div>
+ );
+ }
+ 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.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.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.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>
- );
- }
+ : '`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 661cc1d8c..45ba5cc9e 100644
--- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx
+++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx
@@ -8,156 +8,156 @@ 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;
- token: Token;
- tokenState: TokenState;
- etherBalance: BigNumber;
+ direction: Side;
+ onComplete: (direction: Side, value: BigNumber) => void;
+ onCancelled: () => void;
+ isOpen: boolean;
+ token: Token;
+ tokenState: TokenState;
+ etherBalance: BigNumber;
}
interface EthWethConversionDialogState {
- value?: BigNumber;
- shouldShowIncompleteErrs: boolean;
- hasErrors: boolean;
+ value?: BigNumber;
+ shouldShowIncompleteErrs: boolean;
+ hasErrors: boolean;
}
export class EthWethConversionDialog extends React.Component<
- EthWethConversionDialogProps,
- EthWethConversionDialogState
+ EthWethConversionDialogProps,
+ EthWethConversionDialogState
> {
- constructor() {
- super();
- this.state = {
- shouldShowIncompleteErrs: false,
- 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)} />,
- ];
- const title = this.props.direction === Side.Deposit ? 'Wrap ETH' : 'Unwrap WETH';
- return (
- <Dialog
- title={title}
- titleStyle={{ fontWeight: 100 }}
- actions={convertDialogActions}
- contentStyle={{ width: 448 }}
- open={this.props.isOpen}
- >
- {this._renderConversionDialogBody()}
- </Dialog>
- );
- }
- 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 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 _onMaxClick() {
- this.setState({
- value: this.props.tokenState.balance,
- });
- }
- private _onValueChange(isValid: boolean, amount?: BigNumber) {
- this.setState({
- value: amount,
- hasErrors: !isValid,
- });
- }
- private _onConvertClick() {
- if (this.state.hasErrors) {
- this.setState({
- shouldShowIncompleteErrs: true,
- });
- } else {
- const value = this.state.value;
- this.setState({
- value: undefined,
- });
- this.props.onComplete(this.props.direction, value);
- }
- }
- private _onCancel() {
- this.setState({
- value: undefined,
- });
- this.props.onCancelled();
- }
+ constructor() {
+ super();
+ this.state = {
+ shouldShowIncompleteErrs: false,
+ 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)} />,
+ ];
+ const title = this.props.direction === Side.Deposit ? 'Wrap ETH' : 'Unwrap WETH';
+ return (
+ <Dialog
+ title={title}
+ titleStyle={{ fontWeight: 100 }}
+ actions={convertDialogActions}
+ contentStyle={{ width: 448 }}
+ open={this.props.isOpen}
+ >
+ {this._renderConversionDialogBody()}
+ </Dialog>
+ );
+ }
+ 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 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 _onMaxClick() {
+ this.setState({
+ value: this.props.tokenState.balance,
+ });
+ }
+ private _onValueChange(isValid: boolean, amount?: BigNumber) {
+ this.setState({
+ value: amount,
+ hasErrors: !isValid,
+ });
+ }
+ private _onConvertClick() {
+ if (this.state.hasErrors) {
+ this.setState({
+ shouldShowIncompleteErrs: true,
+ });
+ } else {
+ const value = this.state.value;
+ this.setState({
+ value: undefined,
+ });
+ this.props.onComplete(this.props.direction, value);
+ }
+ }
+ private _onCancel() {
+ this.setState({
+ value: undefined,
+ });
+ this.props.onCancelled();
+ }
}
diff --git a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx
index 60db93c52..8b7760a1a 100644
--- a/packages/website/ts/components/dialogs/ledger_config_dialog.tsx
+++ b/packages/website/ts/components/dialogs/ledger_config_dialog.tsx
@@ -17,245 +17,245 @@ import { utils } from 'ts/utils/utils';
const VALID_ETHEREUM_DERIVATION_PATH_PREFIX = `44'/60'`;
enum LedgerSteps {
- CONNECT,
- SELECT_ADDRESS,
+ CONNECT,
+ SELECT_ADDRESS,
}
interface LedgerConfigDialogProps {
- isOpen: boolean;
- toggleDialogFn: (isOpen: boolean) => void;
- dispatcher: Dispatcher;
- blockchain: Blockchain;
- networkId: number;
+ isOpen: boolean;
+ toggleDialogFn: (isOpen: boolean) => void;
+ dispatcher: Dispatcher;
+ blockchain: Blockchain;
+ networkId: number;
}
interface LedgerConfigDialogState {
- didConnectFail: boolean;
- stepIndex: LedgerSteps;
- userAddresses: string[];
- addressBalances: BigNumber[];
- derivationPath: string;
- derivationErrMsg: string;
+ didConnectFail: boolean;
+ stepIndex: LedgerSteps;
+ userAddresses: string[];
+ addressBalances: BigNumber[];
+ derivationPath: string;
+ derivationErrMsg: string;
}
export class LedgerConfigDialog extends React.Component<LedgerConfigDialogProps, LedgerConfigDialogState> {
- constructor(props: LedgerConfigDialogProps) {
- super(props);
- this.state = {
- didConnectFail: false,
- stepIndex: LedgerSteps.CONNECT,
- userAddresses: [],
- addressBalances: [],
- derivationPath: configs.DEFAULT_DERIVATION_PATH,
- derivationErrMsg: '',
- };
- }
- public render() {
- const dialogActions = [
- <FlatButton key="ledgerConnectCancel" label="Cancel" onTouchTap={this._onClose.bind(this)} />,
- ];
- const dialogTitle =
- this.state.stepIndex === LedgerSteps.CONNECT ? 'Connect to your Ledger' : 'Select desired address';
- return (
- <Dialog
- title={dialogTitle}
- titleStyle={{ fontWeight: 100 }}
- actions={dialogActions}
- open={this.props.isOpen}
- onRequestClose={this._onClose.bind(this)}
- autoScrollBodyContent={true}
- 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>
- </Dialog>
- );
- }
- private _renderConnectStep() {
- return (
- <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">
- 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>
- </li>
- </ol>
- <div className="center pb3">
- <LifeCycleRaisedButton
- isPrimary={true}
- labelReady="Connect to Ledger"
- labelLoading="Connecting..."
- labelComplete="Connected!"
- onClickAsyncFn={this._onConnectLedgerClickAsync.bind(this, true)}
- />
- {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() {
- return (
- <div>
- <div>
- <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>
- </Table>
- </div>
- <div className="flex pt2" style={{ height: 100 }}>
- <div className="overflow-hidden" style={{ width: 180 }}>
- <TextField
- floatingLabelFixed={true}
- floatingLabelStyle={{ color: colors.grey }}
- floatingLabelText="Update path derivation (advanced)"
- value={this.state.derivationPath}
- errorText={this.state.derivationErrMsg}
- onChange={this._onDerivationPathChanged.bind(this)}
- />
- </div>
- <div className="pl2" style={{ paddingTop: 28 }}>
- <LifeCycleRaisedButton
- labelReady="Update"
- labelLoading="Updating..."
- labelComplete="Updated!"
- onClickAsyncFn={this._onFetchAddressesForDerivationPathAsync.bind(this)}
- />
- </div>
- </div>
- </div>
- );
- }
- 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.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 }}>
- <TableRowColumn colSpan={2}>
- <div data-tip={true} data-for={addressTooltipId}>
- {userAddress}
- </div>
- <ReactTooltip id={addressTooltipId}>{userAddress}</ReactTooltip>
- </TableRowColumn>
- <TableRowColumn>
- <div data-tip={true} data-for={balanceTooltipId}>
- {balanceString}
- </div>
- <ReactTooltip id={balanceTooltipId}>{balanceString}</ReactTooltip>
- </TableRowColumn>
- </TableRow>
- );
- });
- return rows;
- }
- private _onClose() {
- this.setState({
- didConnectFail: false,
- });
- const isOpen = false;
- this.props.toggleDialogFn(isOpen);
- }
- private _onAddressSelected(selectedRowIndexes: number[]) {
- const selectedRowIndex = selectedRowIndexes[0];
- this.props.blockchain.updateLedgerDerivationIndex(selectedRowIndex);
- const selectedAddress = this.state.userAddresses[selectedRowIndex];
- const selectAddressBalance = this.state.addressBalances[selectedRowIndex];
- this.props.dispatcher.updateUserAddress(selectedAddress);
- this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress);
- this.props.dispatcher.updateUserEtherBalance(selectAddressBalance);
- this.setState({
- stepIndex: LedgerSteps.CONNECT,
- });
- const isOpen = false;
- this.props.toggleDialogFn(isOpen);
- }
- private async _onFetchAddressesForDerivationPathAsync(): Promise<boolean> {
- const currentlySetPath = this.props.blockchain.getLedgerDerivationPathIfExists();
- let didSucceed;
- if (currentlySetPath === this.state.derivationPath) {
- didSucceed = true;
- return didSucceed;
- }
- this.props.blockchain.updateLedgerDerivationPathIfExists(this.state.derivationPath);
- didSucceed = await this._fetchAddressesAndBalancesAsync();
- if (!didSucceed) {
- this.setState({
- derivationErrMsg: 'Failed to connect to Ledger.',
- });
- }
- return didSucceed;
- }
- private async _fetchAddressesAndBalancesAsync() {
- let userAddresses: string[];
- const addressBalances: BigNumber[] = [];
- try {
- userAddresses = await this._getUserAddressesAsync();
- for (const address of userAddresses) {
- const balance = await this.props.blockchain.getBalanceInEthAsync(address);
- addressBalances.push(balance);
- }
- } catch (err) {
- utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`);
- this.setState({
- didConnectFail: true,
- });
- return false;
- }
- this.setState({
- userAddresses,
- addressBalances,
- });
- return true;
- }
- private _onDerivationPathChanged(e: any, derivationPath: string) {
- let derivationErrMsg = '';
- if (!_.startsWith(derivationPath, VALID_ETHEREUM_DERIVATION_PATH_PREFIX)) {
- derivationErrMsg = 'Must be valid Ethereum path.';
- }
+ constructor(props: LedgerConfigDialogProps) {
+ super(props);
+ this.state = {
+ didConnectFail: false,
+ stepIndex: LedgerSteps.CONNECT,
+ userAddresses: [],
+ addressBalances: [],
+ derivationPath: configs.DEFAULT_DERIVATION_PATH,
+ derivationErrMsg: '',
+ };
+ }
+ public render() {
+ const dialogActions = [
+ <FlatButton key="ledgerConnectCancel" label="Cancel" onTouchTap={this._onClose.bind(this)} />,
+ ];
+ const dialogTitle =
+ this.state.stepIndex === LedgerSteps.CONNECT ? 'Connect to your Ledger' : 'Select desired address';
+ return (
+ <Dialog
+ title={dialogTitle}
+ titleStyle={{ fontWeight: 100 }}
+ actions={dialogActions}
+ open={this.props.isOpen}
+ onRequestClose={this._onClose.bind(this)}
+ autoScrollBodyContent={true}
+ 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>
+ </Dialog>
+ );
+ }
+ private _renderConnectStep() {
+ return (
+ <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">
+ 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>
+ </li>
+ </ol>
+ <div className="center pb3">
+ <LifeCycleRaisedButton
+ isPrimary={true}
+ labelReady="Connect to Ledger"
+ labelLoading="Connecting..."
+ labelComplete="Connected!"
+ onClickAsyncFn={this._onConnectLedgerClickAsync.bind(this, true)}
+ />
+ {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() {
+ return (
+ <div>
+ <div>
+ <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>
+ </Table>
+ </div>
+ <div className="flex pt2" style={{ height: 100 }}>
+ <div className="overflow-hidden" style={{ width: 180 }}>
+ <TextField
+ floatingLabelFixed={true}
+ floatingLabelStyle={{ color: colors.grey }}
+ floatingLabelText="Update path derivation (advanced)"
+ value={this.state.derivationPath}
+ errorText={this.state.derivationErrMsg}
+ onChange={this._onDerivationPathChanged.bind(this)}
+ />
+ </div>
+ <div className="pl2" style={{ paddingTop: 28 }}>
+ <LifeCycleRaisedButton
+ labelReady="Update"
+ labelLoading="Updating..."
+ labelComplete="Updated!"
+ onClickAsyncFn={this._onFetchAddressesForDerivationPathAsync.bind(this)}
+ />
+ </div>
+ </div>
+ </div>
+ );
+ }
+ 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.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 }}>
+ <TableRowColumn colSpan={2}>
+ <div data-tip={true} data-for={addressTooltipId}>
+ {userAddress}
+ </div>
+ <ReactTooltip id={addressTooltipId}>{userAddress}</ReactTooltip>
+ </TableRowColumn>
+ <TableRowColumn>
+ <div data-tip={true} data-for={balanceTooltipId}>
+ {balanceString}
+ </div>
+ <ReactTooltip id={balanceTooltipId}>{balanceString}</ReactTooltip>
+ </TableRowColumn>
+ </TableRow>
+ );
+ });
+ return rows;
+ }
+ private _onClose() {
+ this.setState({
+ didConnectFail: false,
+ });
+ const isOpen = false;
+ this.props.toggleDialogFn(isOpen);
+ }
+ private _onAddressSelected(selectedRowIndexes: number[]) {
+ const selectedRowIndex = selectedRowIndexes[0];
+ this.props.blockchain.updateLedgerDerivationIndex(selectedRowIndex);
+ const selectedAddress = this.state.userAddresses[selectedRowIndex];
+ const selectAddressBalance = this.state.addressBalances[selectedRowIndex];
+ this.props.dispatcher.updateUserAddress(selectedAddress);
+ this.props.blockchain.updateWeb3WrapperPrevUserAddress(selectedAddress);
+ this.props.dispatcher.updateUserEtherBalance(selectAddressBalance);
+ this.setState({
+ stepIndex: LedgerSteps.CONNECT,
+ });
+ const isOpen = false;
+ this.props.toggleDialogFn(isOpen);
+ }
+ private async _onFetchAddressesForDerivationPathAsync(): Promise<boolean> {
+ const currentlySetPath = this.props.blockchain.getLedgerDerivationPathIfExists();
+ let didSucceed;
+ if (currentlySetPath === this.state.derivationPath) {
+ didSucceed = true;
+ return didSucceed;
+ }
+ this.props.blockchain.updateLedgerDerivationPathIfExists(this.state.derivationPath);
+ didSucceed = await this._fetchAddressesAndBalancesAsync();
+ if (!didSucceed) {
+ this.setState({
+ derivationErrMsg: 'Failed to connect to Ledger.',
+ });
+ }
+ return didSucceed;
+ }
+ private async _fetchAddressesAndBalancesAsync() {
+ let userAddresses: string[];
+ const addressBalances: BigNumber[] = [];
+ try {
+ userAddresses = await this._getUserAddressesAsync();
+ for (const address of userAddresses) {
+ const balance = await this.props.blockchain.getBalanceInEthAsync(address);
+ addressBalances.push(balance);
+ }
+ } catch (err) {
+ utils.consoleLog(`Ledger error: ${JSON.stringify(err)}`);
+ this.setState({
+ didConnectFail: true,
+ });
+ return false;
+ }
+ this.setState({
+ userAddresses,
+ addressBalances,
+ });
+ return true;
+ }
+ private _onDerivationPathChanged(e: any, derivationPath: string) {
+ let derivationErrMsg = '';
+ if (!_.startsWith(derivationPath, VALID_ETHEREUM_DERIVATION_PATH_PREFIX)) {
+ derivationErrMsg = 'Must be valid Ethereum path.';
+ }
- this.setState({
- derivationPath,
- derivationErrMsg,
- });
- }
- private async _onConnectLedgerClickAsync() {
- const didSucceed = await this._fetchAddressesAndBalancesAsync();
- if (didSucceed) {
- this.setState({
- stepIndex: LedgerSteps.SELECT_ADDRESS,
- });
- }
- return didSucceed;
- }
- private async _getUserAddressesAsync(): Promise<string[]> {
- let userAddresses: string[];
- userAddresses = await this.props.blockchain.getUserAccountsAsync();
+ this.setState({
+ derivationPath,
+ derivationErrMsg,
+ });
+ }
+ private async _onConnectLedgerClickAsync() {
+ const didSucceed = await this._fetchAddressesAndBalancesAsync();
+ if (didSucceed) {
+ this.setState({
+ stepIndex: LedgerSteps.SELECT_ADDRESS,
+ });
+ }
+ return didSucceed;
+ }
+ private async _getUserAddressesAsync(): Promise<string[]> {
+ let userAddresses: string[];
+ userAddresses = await this.props.blockchain.getUserAccountsAsync();
- if (_.isEmpty(userAddresses)) {
- throw new Error('No addresses retrieved.');
- }
- return userAddresses;
- }
+ if (_.isEmpty(userAddresses)) {
+ throw new Error('No addresses retrieved.');
+ }
+ return userAddresses;
+ }
}
diff --git a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx
index 3ecc454a0..1c5efc978 100644
--- a/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx
+++ b/packages/website/ts/components/dialogs/portal_disclaimer_dialog.tsx
@@ -4,33 +4,33 @@ import * as React from 'react';
import { colors } from 'ts/utils/colors';
interface PortalDisclaimerDialogProps {
- isOpen: boolean;
- onToggleDialog: () => void;
+ isOpen: boolean;
+ onToggleDialog: () => void;
}
export function PortalDisclaimerDialog(props: PortalDisclaimerDialogProps) {
- return (
- <Dialog
- title="0x Portal Disclaimer"
- titleStyle={{ fontWeight: 100 }}
- actions={[<FlatButton key="portalAgree" label="I Agree" onTouchTap={props.onToggleDialog} />]}
- open={props.isOpen}
- onRequestClose={props.onToggleDialog}
- autoScrollBodyContent={true}
- modal={true}
- >
- <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.
- </div>
- </div>
- </Dialog>
- );
+ return (
+ <Dialog
+ title="0x Portal Disclaimer"
+ titleStyle={{ fontWeight: 100 }}
+ actions={[<FlatButton key="portalAgree" label="I Agree" onTouchTap={props.onToggleDialog} />]}
+ open={props.isOpen}
+ onRequestClose={props.onToggleDialog}
+ autoScrollBodyContent={true}
+ modal={true}
+ >
+ <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.
+ </div>
+ </div>
+ </Dialog>
+ );
}
diff --git a/packages/website/ts/components/dialogs/send_dialog.tsx b/packages/website/ts/components/dialogs/send_dialog.tsx
index b3dbce598..b9022cd9b 100644
--- a/packages/website/ts/components/dialogs/send_dialog.tsx
+++ b/packages/website/ts/components/dialogs/send_dialog.tsx
@@ -8,110 +8,110 @@ import { TokenAmountInput } from 'ts/components/inputs/token_amount_input';
import { Token, TokenState } from 'ts/types';
interface SendDialogProps {
- onComplete: (recipient: string, value: BigNumber) => void;
- onCancelled: () => void;
- isOpen: boolean;
- token: Token;
- tokenState: TokenState;
+ onComplete: (recipient: string, value: BigNumber) => void;
+ onCancelled: () => void;
+ isOpen: boolean;
+ token: Token;
+ tokenState: TokenState;
}
interface SendDialogState {
- value?: BigNumber;
- recipient: string;
- shouldShowIncompleteErrs: boolean;
- isAmountValid: boolean;
+ value?: BigNumber;
+ recipient: string;
+ shouldShowIncompleteErrs: boolean;
+ isAmountValid: boolean;
}
export class SendDialog extends React.Component<SendDialogProps, SendDialogState> {
- constructor() {
- super();
- this.state = {
- recipient: '',
- shouldShowIncompleteErrs: false,
- isAmountValid: false,
- };
- }
- public render() {
- const transferDialogActions = [
- <FlatButton key="cancelTransfer" label="Cancel" onTouchTap={this._onCancel.bind(this)} />,
- <FlatButton
- key="sendTransfer"
- disabled={this._hasErrors()}
- label="Send"
- primary={true}
- onTouchTap={this._onSendClick.bind(this)}
- />,
- ];
- return (
- <Dialog
- title="I want to send"
- titleStyle={{ fontWeight: 100 }}
- actions={transferDialogActions}
- open={this.props.isOpen}
- >
- {this._renderSendDialogBody()}
- </Dialog>
- );
- }
- private _renderSendDialogBody() {
- return (
- <div className="mx-auto" style={{ maxWidth: 300 }}>
- <div style={{ height: 80 }}>
- <AddressInput
- initialAddress={this.state.recipient}
- updateAddress={this._onRecipientChange.bind(this)}
- isRequired={true}
- label={'Recipient address'}
- hintText={'Address'}
- />
- </div>
- <TokenAmountInput
- label="Amount to send"
- 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}
- />
- </div>
- );
- }
- private _onRecipientChange(recipient?: string) {
- this.setState({
- shouldShowIncompleteErrs: false,
- recipient,
- });
- }
- private _onValueChange(isValid: boolean, amount?: BigNumber) {
- this.setState({
- isAmountValid: isValid,
- value: amount,
- });
- }
- private _onSendClick() {
- if (this._hasErrors()) {
- this.setState({
- shouldShowIncompleteErrs: true,
- });
- } else {
- const value = this.state.value;
- this.setState({
- recipient: undefined,
- value: undefined,
- });
- this.props.onComplete(this.state.recipient, value);
- }
- }
- private _onCancel() {
- this.setState({
- value: undefined,
- });
- this.props.onCancelled();
- }
- private _hasErrors() {
- return _.isUndefined(this.state.recipient) || _.isUndefined(this.state.value) || !this.state.isAmountValid;
- }
+ constructor() {
+ super();
+ this.state = {
+ recipient: '',
+ shouldShowIncompleteErrs: false,
+ isAmountValid: false,
+ };
+ }
+ public render() {
+ const transferDialogActions = [
+ <FlatButton key="cancelTransfer" label="Cancel" onTouchTap={this._onCancel.bind(this)} />,
+ <FlatButton
+ key="sendTransfer"
+ disabled={this._hasErrors()}
+ label="Send"
+ primary={true}
+ onTouchTap={this._onSendClick.bind(this)}
+ />,
+ ];
+ return (
+ <Dialog
+ title="I want to send"
+ titleStyle={{ fontWeight: 100 }}
+ actions={transferDialogActions}
+ open={this.props.isOpen}
+ >
+ {this._renderSendDialogBody()}
+ </Dialog>
+ );
+ }
+ private _renderSendDialogBody() {
+ return (
+ <div className="mx-auto" style={{ maxWidth: 300 }}>
+ <div style={{ height: 80 }}>
+ <AddressInput
+ initialAddress={this.state.recipient}
+ updateAddress={this._onRecipientChange.bind(this)}
+ isRequired={true}
+ label={'Recipient address'}
+ hintText={'Address'}
+ />
+ </div>
+ <TokenAmountInput
+ label="Amount to send"
+ 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}
+ />
+ </div>
+ );
+ }
+ private _onRecipientChange(recipient?: string) {
+ this.setState({
+ shouldShowIncompleteErrs: false,
+ recipient,
+ });
+ }
+ private _onValueChange(isValid: boolean, amount?: BigNumber) {
+ this.setState({
+ isAmountValid: isValid,
+ value: amount,
+ });
+ }
+ private _onSendClick() {
+ if (this._hasErrors()) {
+ this.setState({
+ shouldShowIncompleteErrs: true,
+ });
+ } else {
+ const value = this.state.value;
+ this.setState({
+ recipient: undefined,
+ value: undefined,
+ });
+ this.props.onComplete(this.state.recipient, value);
+ }
+ }
+ private _onCancel() {
+ this.setState({
+ value: undefined,
+ });
+ this.props.onCancelled();
+ }
+ 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 3f29d46f8..b1804e95c 100644
--- a/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx
+++ b/packages/website/ts/components/dialogs/track_token_confirmation_dialog.tsx
@@ -9,94 +9,94 @@ import { Dispatcher } from 'ts/redux/dispatcher';
import { Token, TokenByAddress } from 'ts/types';
interface TrackTokenConfirmationDialogProps {
- tokens: Token[];
- tokenByAddress: TokenByAddress;
- isOpen: boolean;
- onToggleDialog: (didConfirmTokenTracking: boolean) => void;
- dispatcher: Dispatcher;
- networkId: number;
- blockchain: Blockchain;
- userAddress: string;
+ tokens: Token[];
+ tokenByAddress: TokenByAddress;
+ isOpen: boolean;
+ onToggleDialog: (didConfirmTokenTracking: boolean) => void;
+ dispatcher: Dispatcher;
+ networkId: number;
+ blockchain: Blockchain;
+ userAddress: string;
}
interface TrackTokenConfirmationDialogState {
- isAddingTokenToTracked: boolean;
+ isAddingTokenToTracked: boolean;
}
export class TrackTokenConfirmationDialog extends React.Component<
- TrackTokenConfirmationDialogProps,
- TrackTokenConfirmationDialogState
+ TrackTokenConfirmationDialogProps,
+ TrackTokenConfirmationDialogState
> {
- constructor(props: TrackTokenConfirmationDialogProps) {
- super(props);
- this.state = {
- isAddingTokenToTracked: false,
- };
- }
- public render() {
- const tokens = this.props.tokens;
- return (
- <Dialog
- title="Tracking confirmation"
- titleStyle={{ fontWeight: 100 }}
- actions={[
- <FlatButton
- key="trackNo"
- label="No"
- onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)}
- />,
- <FlatButton
- key="trackYes"
- label="Yes"
- onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)}
- />,
- ]}
- open={this.props.isOpen}
- onRequestClose={this.props.onToggleDialog.bind(this, false)}
- autoScrollBodyContent={true}
- >
- <div className="pt2">
- <TrackTokenConfirmation
- tokens={tokens}
- networkId={this.props.networkId}
- tokenByAddress={this.props.tokenByAddress}
- isAddingTokenToTracked={this.state.isAddingTokenToTracked}
- />
- </div>
- </Dialog>
- );
- }
- private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) {
- if (!didUserAcceptTracking) {
- this.props.onToggleDialog(didUserAcceptTracking);
- return;
- }
- this.setState({
- isAddingTokenToTracked: true,
- });
- for (const token of this.props.tokens) {
- const newTokenEntry = {
- ...token,
- };
+ constructor(props: TrackTokenConfirmationDialogProps) {
+ super(props);
+ this.state = {
+ isAddingTokenToTracked: false,
+ };
+ }
+ public render() {
+ const tokens = this.props.tokens;
+ return (
+ <Dialog
+ title="Tracking confirmation"
+ titleStyle={{ fontWeight: 100 }}
+ actions={[
+ <FlatButton
+ key="trackNo"
+ label="No"
+ onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)}
+ />,
+ <FlatButton
+ key="trackYes"
+ label="Yes"
+ onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)}
+ />,
+ ]}
+ open={this.props.isOpen}
+ onRequestClose={this.props.onToggleDialog.bind(this, false)}
+ autoScrollBodyContent={true}
+ >
+ <div className="pt2">
+ <TrackTokenConfirmation
+ tokens={tokens}
+ networkId={this.props.networkId}
+ tokenByAddress={this.props.tokenByAddress}
+ isAddingTokenToTracked={this.state.isAddingTokenToTracked}
+ />
+ </div>
+ </Dialog>
+ );
+ }
+ private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) {
+ if (!didUserAcceptTracking) {
+ this.props.onToggleDialog(didUserAcceptTracking);
+ return;
+ }
+ this.setState({
+ isAddingTokenToTracked: true,
+ });
+ for (const token of this.props.tokens) {
+ const newTokenEntry = {
+ ...token,
+ };
- newTokenEntry.isTracked = true;
- trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry);
- this.props.dispatcher.updateTokenByAddress([newTokenEntry]);
+ newTokenEntry.isTracked = true;
+ trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry);
+ this.props.dispatcher.updateTokenByAddress([newTokenEntry]);
- const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(
- token.address,
- );
- this.props.dispatcher.updateTokenStateByAddress({
- [token.address]: {
- balance,
- allowance,
- },
- });
- }
+ const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(
+ token.address,
+ );
+ this.props.dispatcher.updateTokenStateByAddress({
+ [token.address]: {
+ balance,
+ allowance,
+ },
+ });
+ }
- this.setState({
- isAddingTokenToTracked: false,
- });
- this.props.onToggleDialog(didUserAcceptTracking);
- }
+ this.setState({
+ isAddingTokenToTracked: false,
+ });
+ this.props.onToggleDialog(didUserAcceptTracking);
+ }
}
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 098e3e26d..2ea51d07b 100644
--- a/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx
+++ b/packages/website/ts/components/dialogs/u2f_not_supported_dialog.tsx
@@ -5,42 +5,42 @@ import { colors } from 'ts/utils/colors';
import { constants } from 'ts/utils/constants';
interface U2fNotSupportedDialogProps {
- isOpen: boolean;
- onToggleDialog: () => void;
+ isOpen: boolean;
+ onToggleDialog: () => void;
}
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)} />]}
- open={props.isOpen}
- onRequestClose={props.onToggleDialog.bind(this)}
- autoScrollBodyContent={true}
- >
- <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.
- </div>
- <div>
- <ul>
- <li className="pb1">Chrome version 38 or later</li>
- <li className="pb1">Opera version 40 of later</li>
- <li>
- Firefox with{' '}
- <a
- href={constants.URL_FIREFOX_U2F_ADDON}
- target="_blank"
- style={{ textDecoration: 'underline' }}
- >
- this extension
- </a>.
- </li>
- </ul>
- </div>
- </div>
- </Dialog>
- );
+ return (
+ <Dialog
+ title="U2F Not Supported"
+ 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>
+ 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>
+ <li className="pb1">Chrome version 38 or later</li>
+ <li className="pb1">Opera version 40 of later</li>
+ <li>
+ Firefox with{' '}
+ <a
+ href={constants.URL_FIREFOX_U2F_ADDON}
+ target="_blank"
+ style={{ textDecoration: 'underline' }}
+ >
+ this extension
+ </a>.
+ </li>
+ </ul>
+ </div>
+ </div>
+ </Dialog>
+ );
}
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
index 9e91ff12d..98436eb50 100644
--- a/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx
+++ b/packages/website/ts/components/dialogs/wrapped_eth_section_notice_dialog.tsx
@@ -4,30 +4,30 @@ import { colors } from 'material-ui/styles';
import * as React from 'react';
interface WrappedEthSectionNoticeDialogProps {
- isOpen: boolean;
- onToggleDialog: () => void;
+ 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>
- );
+ 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 300e71f1f..af1b33eef 100644
--- a/packages/website/ts/components/eth_weth_conversion_button.tsx
+++ b/packages/website/ts/components/eth_weth_conversion_button.tsx
@@ -12,115 +12,115 @@ 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;
- isOutdatedWrappedEther: boolean;
- onConversionSuccessful?: () => void;
- isDisabled?: boolean;
+ direction: Side;
+ ethToken: Token;
+ ethTokenState: TokenState;
+ dispatcher: Dispatcher;
+ blockchain: Blockchain;
+ userEtherBalance: BigNumber;
+ isOutdatedWrappedEther: boolean;
+ onConversionSuccessful?: () => void;
+ isDisabled?: boolean;
}
interface EthWethConversionButtonState {
- isEthConversionDialogVisible: boolean;
- isEthConversionHappening: boolean;
+ isEthConversionDialogVisible: boolean;
+ isEthConversionHappening: boolean;
}
export class EthWethConversionButton extends React.Component<
- EthWethConversionButtonProps,
- EthWethConversionButtonState
+ EthWethConversionButtonProps,
+ EthWethConversionButtonState
> {
- public static defaultProps: Partial<EthWethConversionButtonProps> = {
- isDisabled: false,
- onConversionSuccessful: _.noop,
- };
- public constructor(props: EthWethConversionButtonProps) {
- super(props);
- this.state = {
- isEthConversionDialogVisible: false,
- isEthConversionHappening: false,
- };
- }
- public render() {
- 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%' }}
- labelStyle={labelStyle}
- 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)}
- etherBalance={this.props.userEtherBalance}
- token={this.props.ethToken}
- tokenState={this.props.ethTokenState}
- />
- </div>
- );
- }
- private _toggleConversionDialog() {
- this.setState({
- isEthConversionDialogVisible: !this.state.isEthConversionDialogVisible,
- });
- }
- private async _onConversionAmountSelectedAsync(direction: Side, value: BigNumber) {
- this.setState({
- isEthConversionHappening: true,
- });
- this._toggleConversionDialog();
- const token = this.props.ethToken;
- const tokenState = this.props.ethTokenState;
- let balance = tokenState.balance;
- try {
- if (direction === Side.Deposit) {
- await this.props.blockchain.convertEthToWrappedEthTokensAsync(token.address, value);
- const ethAmount = ZeroEx.toUnitAmount(value, constants.DECIMAL_PLACES_ETH);
- this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`);
- balance = balance.plus(value);
- } else {
- await this.props.blockchain.convertWrappedEthTokensToEthAsync(token.address, value);
- const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals);
- this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`);
- balance = balance.minus(value);
- }
- if (!this.props.isOutdatedWrappedEther) {
- this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance);
- }
- this.props.onConversionSuccessful();
- } catch (err) {
- 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.setState({
- isEthConversionHappening: false,
- });
- }
+ public static defaultProps: Partial<EthWethConversionButtonProps> = {
+ isDisabled: false,
+ onConversionSuccessful: _.noop,
+ };
+ public constructor(props: EthWethConversionButtonProps) {
+ super(props);
+ this.state = {
+ isEthConversionDialogVisible: false,
+ isEthConversionHappening: false,
+ };
+ }
+ public render() {
+ 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%' }}
+ labelStyle={labelStyle}
+ 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)}
+ etherBalance={this.props.userEtherBalance}
+ token={this.props.ethToken}
+ tokenState={this.props.ethTokenState}
+ />
+ </div>
+ );
+ }
+ private _toggleConversionDialog() {
+ this.setState({
+ isEthConversionDialogVisible: !this.state.isEthConversionDialogVisible,
+ });
+ }
+ private async _onConversionAmountSelectedAsync(direction: Side, value: BigNumber) {
+ this.setState({
+ isEthConversionHappening: true,
+ });
+ this._toggleConversionDialog();
+ const token = this.props.ethToken;
+ const tokenState = this.props.ethTokenState;
+ let balance = tokenState.balance;
+ try {
+ if (direction === Side.Deposit) {
+ await this.props.blockchain.convertEthToWrappedEthTokensAsync(token.address, value);
+ const ethAmount = ZeroEx.toUnitAmount(value, constants.DECIMAL_PLACES_ETH);
+ this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`);
+ balance = balance.plus(value);
+ } else {
+ await this.props.blockchain.convertWrappedEthTokensToEthAsync(token.address, value);
+ const tokenAmount = ZeroEx.toUnitAmount(value, token.decimals);
+ this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`);
+ balance = balance.minus(value);
+ }
+ if (!this.props.isOutdatedWrappedEther) {
+ this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance);
+ }
+ this.props.onConversionSuccessful();
+ } catch (err) {
+ 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.setState({
+ isEthConversionHappening: false,
+ });
+ }
}
diff --git a/packages/website/ts/components/eth_wrappers.tsx b/packages/website/ts/components/eth_wrappers.tsx
index d074ec787..1593d51f0 100644
--- a/packages/website/ts/components/eth_wrappers.tsx
+++ b/packages/website/ts/components/eth_wrappers.tsx
@@ -10,13 +10,13 @@ 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,
+ EtherscanLinkSuffixes,
+ OutdatedWrappedEtherByNetworkId,
+ Side,
+ Token,
+ TokenByAddress,
+ TokenState,
+ TokenStateByAddress,
} from 'ts/types';
import { colors } from 'ts/utils/colors';
import { configs } from 'ts/utils/configs';
@@ -30,345 +30,345 @@ const ETHER_ICON_PATH = '/images/ether.png';
const OUTDATED_WETH_ICON_PATH = '/images/wrapped_eth_gray.png';
interface OutdatedWETHAddressToIsStateLoaded {
- [address: string]: boolean;
+ [address: string]: boolean;
}
interface OutdatedWETHStateByAddress {
- [address: string]: TokenState;
+ [address: string]: TokenState;
}
interface EthWrappersProps {
- networkId: number;
- blockchain: Blockchain;
- dispatcher: Dispatcher;
- tokenByAddress: TokenByAddress;
- tokenStateByAddress: TokenStateByAddress;
- userAddress: string;
- userEtherBalance: BigNumber;
+ networkId: number;
+ blockchain: Blockchain;
+ dispatcher: Dispatcher;
+ tokenByAddress: TokenByAddress;
+ tokenStateByAddress: TokenStateByAddress;
+ userAddress: string;
+ userEtherBalance: BigNumber;
}
interface EthWrappersState {
- outdatedWETHAddressToIsStateLoaded: OutdatedWETHAddressToIsStateLoaded;
- outdatedWETHStateByAddress: OutdatedWETHStateByAddress;
+ 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;
- }
+ 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 1a150e9ee..249ee419e 100644
--- a/packages/website/ts/components/fill_order.tsx
+++ b/packages/website/ts/components/fill_order.tsx
@@ -26,665 +26,665 @@ import { errorReporter } from 'ts/utils/error_reporter';
import { utils } from 'ts/utils/utils';
interface FillOrderProps {
- blockchain: Blockchain;
- blockchainErr: BlockchainErrs;
- orderFillAmount: BigNumber;
- isOrderInUrl: boolean;
- networkId: number;
- userAddress: string;
- tokenByAddress: TokenByAddress;
- tokenStateByAddress: TokenStateByAddress;
- initialOrder: Order;
- dispatcher: Dispatcher;
+ blockchain: Blockchain;
+ blockchainErr: BlockchainErrs;
+ orderFillAmount: BigNumber;
+ isOrderInUrl: boolean;
+ networkId: number;
+ userAddress: string;
+ tokenByAddress: TokenByAddress;
+ tokenStateByAddress: TokenStateByAddress;
+ initialOrder: Order;
+ dispatcher: Dispatcher;
}
interface FillOrderState {
- didOrderValidationRun: boolean;
- areAllInvolvedTokensTracked: boolean;
- globalErrMsg: string;
- orderJSON: string;
- orderJSONErrMsg: string;
- parsedOrder: Order;
- didFillOrderSucceed: boolean;
- didCancelOrderSucceed: boolean;
- unavailableTakerAmount: BigNumber;
- isMakerTokenAddressInRegistry: boolean;
- isTakerTokenAddressInRegistry: boolean;
- isFillWarningDialogOpen: boolean;
- isFilling: boolean;
- isCancelling: boolean;
- isConfirmingTokenTracking: boolean;
- tokensToTrack: Token[];
+ didOrderValidationRun: boolean;
+ areAllInvolvedTokensTracked: boolean;
+ globalErrMsg: string;
+ orderJSON: string;
+ orderJSONErrMsg: string;
+ parsedOrder: Order;
+ didFillOrderSucceed: boolean;
+ didCancelOrderSucceed: boolean;
+ unavailableTakerAmount: BigNumber;
+ isMakerTokenAddressInRegistry: boolean;
+ isTakerTokenAddressInRegistry: boolean;
+ isFillWarningDialogOpen: boolean;
+ isFilling: boolean;
+ isCancelling: boolean;
+ isConfirmingTokenTracking: boolean;
+ tokensToTrack: Token[];
}
export class FillOrder extends React.Component<FillOrderProps, FillOrderState> {
- private _validator: SchemaValidator;
- constructor(props: FillOrderProps) {
- super(props);
- this.state = {
- globalErrMsg: '',
- didOrderValidationRun: false,
- areAllInvolvedTokensTracked: false,
- didFillOrderSucceed: false,
- didCancelOrderSucceed: false,
- orderJSON: _.isUndefined(this.props.initialOrder) ? '' : JSON.stringify(this.props.initialOrder),
- orderJSONErrMsg: '',
- parsedOrder: this.props.initialOrder,
- unavailableTakerAmount: new BigNumber(0),
- isMakerTokenAddressInRegistry: false,
- isTakerTokenAddressInRegistry: false,
- isFillWarningDialogOpen: false,
- isFilling: false,
- isCancelling: false,
- isConfirmingTokenTracking: false,
- tokensToTrack: [],
- };
- this._validator = new SchemaValidator();
- }
- public componentWillMount() {
- if (!_.isEmpty(this.state.orderJSON)) {
- // tslint:disable-next-line:no-floating-promises
- this._validateFillOrderFireAndForgetAsync(this.state.orderJSON);
- }
- }
- public componentDidMount() {
- window.scrollTo(0, 0);
- }
- public render() {
- return (
- <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}>
- <h3>Fill an order</h3>
- <Divider />
- <div>
- {!this.props.isOrderInUrl && (
- <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)}
- />
- {this._renderOrderJsonNotices()}
- </div>
- )}
- <div>
- {!_.isUndefined(this.state.parsedOrder) &&
- this.state.didOrderValidationRun &&
- this.state.areAllInvolvedTokensTracked &&
- this._renderVisualOrder()}
- </div>
- {this.props.isOrderInUrl && (
- <div className="pt2">
- <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)}
- />
- </CardText>
- </Card>
- {this._renderOrderJsonNotices()}
- </div>
- )}
- </div>
- <FillWarningDialog
- isOpen={this.state.isFillWarningDialogOpen}
- onToggleDialog={this._onFillWarningClosed.bind(this)}
- />
- <TrackTokenConfirmationDialog
- userAddress={this.props.userAddress}
- networkId={this.props.networkId}
- blockchain={this.props.blockchain}
- tokenByAddress={this.props.tokenByAddress}
- dispatcher={this.props.dispatcher}
- tokens={this.state.tokensToTrack}
- isOpen={this.state.isConfirmingTokenTracking}
- onToggleDialog={this._onToggleTrackConfirmDialog.bind(this)}
- />
- </div>
- );
- }
- 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) && (
- <Alert type={AlertTypes.ERROR} message={this.state.orderJSONErrMsg} />
- )}
- </div>
- );
- }
- 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);
- const orderMakerAmount = new BigNumber(this.state.parsedOrder.maker.amount);
- const takerAssetToken = {
- amount: orderTakerAmount.minus(this.state.unavailableTakerAmount),
- symbol: takerToken.symbol,
- };
- const fillToken = this.props.tokenByAddress[takerToken.address];
- const fillTokenState = this.props.tokenStateByAddress[takerToken.address];
- const makerTokenAddress = this.state.parsedOrder.maker.token.address;
- const makerToken = this.props.tokenByAddress[makerTokenAddress];
- const makerAssetToken = {
- amount: orderMakerAmount.times(takerAssetToken.amount).div(orderTakerAmount),
- symbol: makerToken.symbol,
- };
- const fillAssetToken = {
- amount: this.props.orderFillAmount,
- symbol: takerToken.symbol,
- };
- 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);
+ private _validator: SchemaValidator;
+ constructor(props: FillOrderProps) {
+ super(props);
+ this.state = {
+ globalErrMsg: '',
+ didOrderValidationRun: false,
+ areAllInvolvedTokensTracked: false,
+ didFillOrderSucceed: false,
+ didCancelOrderSucceed: false,
+ orderJSON: _.isUndefined(this.props.initialOrder) ? '' : JSON.stringify(this.props.initialOrder),
+ orderJSONErrMsg: '',
+ parsedOrder: this.props.initialOrder,
+ unavailableTakerAmount: new BigNumber(0),
+ isMakerTokenAddressInRegistry: false,
+ isTakerTokenAddressInRegistry: false,
+ isFillWarningDialogOpen: false,
+ isFilling: false,
+ isCancelling: false,
+ isConfirmingTokenTracking: false,
+ tokensToTrack: [],
+ };
+ this._validator = new SchemaValidator();
+ }
+ public componentWillMount() {
+ if (!_.isEmpty(this.state.orderJSON)) {
+ // tslint:disable-next-line:no-floating-promises
+ this._validateFillOrderFireAndForgetAsync(this.state.orderJSON);
+ }
+ }
+ public componentDidMount() {
+ window.scrollTo(0, 0);
+ }
+ public render() {
+ return (
+ <div className="clearfix lg-px4 md-px4 sm-px2" style={{ minHeight: 600 }}>
+ <h3>Fill an order</h3>
+ <Divider />
+ <div>
+ {!this.props.isOrderInUrl && (
+ <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)}
+ />
+ {this._renderOrderJsonNotices()}
+ </div>
+ )}
+ <div>
+ {!_.isUndefined(this.state.parsedOrder) &&
+ this.state.didOrderValidationRun &&
+ this.state.areAllInvolvedTokensTracked &&
+ this._renderVisualOrder()}
+ </div>
+ {this.props.isOrderInUrl && (
+ <div className="pt2">
+ <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)}
+ />
+ </CardText>
+ </Card>
+ {this._renderOrderJsonNotices()}
+ </div>
+ )}
+ </div>
+ <FillWarningDialog
+ isOpen={this.state.isFillWarningDialogOpen}
+ onToggleDialog={this._onFillWarningClosed.bind(this)}
+ />
+ <TrackTokenConfirmationDialog
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this.props.blockchain}
+ tokenByAddress={this.props.tokenByAddress}
+ dispatcher={this.props.dispatcher}
+ tokens={this.state.tokensToTrack}
+ isOpen={this.state.isConfirmingTokenTracking}
+ onToggleDialog={this._onToggleTrackConfirmDialog.bind(this)}
+ />
+ </div>
+ );
+ }
+ 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) && (
+ <Alert type={AlertTypes.ERROR} message={this.state.orderJSONErrMsg} />
+ )}
+ </div>
+ );
+ }
+ 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);
+ const orderMakerAmount = new BigNumber(this.state.parsedOrder.maker.amount);
+ const takerAssetToken = {
+ amount: orderTakerAmount.minus(this.state.unavailableTakerAmount),
+ symbol: takerToken.symbol,
+ };
+ const fillToken = this.props.tokenByAddress[takerToken.address];
+ const fillTokenState = this.props.tokenStateByAddress[takerToken.address];
+ const makerTokenAddress = this.state.parsedOrder.maker.token.address;
+ const makerToken = this.props.tokenByAddress[makerTokenAddress];
+ const makerAssetToken = {
+ amount: orderMakerAmount.times(takerAssetToken.amount).div(orderTakerAmount),
+ symbol: makerToken.symbol,
+ };
+ const fillAssetToken = {
+ amount: this.props.orderFillAmount,
+ symbol: takerToken.symbol,
+ };
+ 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);
- }
- 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="inline left">Order details</div>
- <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} />
- </div>
- <div className="col col-6">
- <EthereumAddress
- address={this.state.parsedOrder.maker.address}
- networkId={this.props.networkId}
- />
- </div>
- </div>
- </div>
- <div className="lg-px4 md-px4 sm-px0">
- <div className="lg-px4 md-px4 sm-px1 pt1">
- <VisualOrder
- orderTakerAddress={orderTaker}
- orderMakerAddress={this.state.parsedOrder.maker.address}
- makerAssetToken={makerAssetToken}
- takerAssetToken={takerAssetToken}
- tokenByAddress={this.props.tokenByAddress}
- makerToken={makerToken}
- takerToken={takerToken}
- networkId={this.props.networkId}
- isMakerTokenAddressInRegistry={this.state.isMakerTokenAddressInRegistry}
- isTakerTokenAddressInRegistry={this.state.isTakerTokenAddressInRegistry}
- />
- <div className="center pt3 pb2">Expires: {expiryDate} UTC</div>
- </div>
- </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 ? (
- <div>
- <RaisedButton
- style={{ width: '100%' }}
- disabled={this.state.isCancelling}
- label={this.state.isCancelling ? 'Cancelling order...' : 'Cancel order'}
- onClick={this._onCancelOrderClickFireAndForgetAsync.bind(this)}
- />
- {this.state.didCancelOrderSucceed && (
- <Alert type={AlertTypes.SUCCESS} message={this._renderCancelSuccessMsg()} />
- )}
- </div>
- ) : (
- <div>
- <RaisedButton
- style={{ width: '100%' }}
- disabled={this.state.isFilling}
- label={this.state.isFilling ? 'Filling order...' : 'Fill order'}
- onClick={this._onFillOrderClick.bind(this)}
- />
- {!_.isEmpty(this.state.globalErrMsg) && (
- <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} />
- )}
- {this.state.didFillOrderSucceed && (
- <Alert type={AlertTypes.SUCCESS} message={this._renderFillSuccessMsg()} />
- )}
- </div>
- )}
- </div>
- </div>
- );
- }
- private _renderFillSuccessMsg() {
- return (
- <div>
- Order successfully filled. See the trade details in your{' '}
- <Link to={`${WebsitePaths.Portal}/trades`} style={{ color: colors.white }}>
- trade history
- </Link>
- </div>
- );
- }
- private _renderCancelSuccessMsg() {
- return <div>Order successfully cancelled.</div>;
- }
- private _onFillOrderClick() {
- if (!this.state.isMakerTokenAddressInRegistry || !this.state.isTakerTokenAddressInRegistry) {
- this.setState({
- isFillWarningDialogOpen: true,
- });
- } else {
- // tslint:disable-next-line:no-floating-promises
- this._onFillOrderClickFireAndForgetAsync();
- }
- }
- private _onFillWarningClosed(didUserCancel: boolean) {
- this.setState({
- isFillWarningDialogOpen: false,
- });
- if (!didUserCancel) {
- // tslint:disable-next-line:no-floating-promises
- this._onFillOrderClickFireAndForgetAsync();
- }
- }
- private _onFillAmountChange(isValid: boolean, amount?: BigNumber) {
- this.props.dispatcher.updateOrderFillAmount(amount);
- }
- 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);
- }
- private async _checkForUntrackedTokensAndAskToAdd() {
- if (!_.isEmpty(this.state.orderJSONErrMsg)) {
- return;
- }
+ let orderReceiveAmount = 0;
+ if (!_.isUndefined(this.props.orderFillAmount)) {
+ const orderReceiveAmountBigNumber = exchangeRate.mul(this.props.orderFillAmount);
+ orderReceiveAmount = this._formatCurrencyAmount(orderReceiveAmountBigNumber, makerToken.decimals);
+ }
+ 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="inline left">Order details</div>
+ <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} />
+ </div>
+ <div className="col col-6">
+ <EthereumAddress
+ address={this.state.parsedOrder.maker.address}
+ networkId={this.props.networkId}
+ />
+ </div>
+ </div>
+ </div>
+ <div className="lg-px4 md-px4 sm-px0">
+ <div className="lg-px4 md-px4 sm-px1 pt1">
+ <VisualOrder
+ orderTakerAddress={orderTaker}
+ orderMakerAddress={this.state.parsedOrder.maker.address}
+ makerAssetToken={makerAssetToken}
+ takerAssetToken={takerAssetToken}
+ tokenByAddress={this.props.tokenByAddress}
+ makerToken={makerToken}
+ takerToken={takerToken}
+ networkId={this.props.networkId}
+ isMakerTokenAddressInRegistry={this.state.isMakerTokenAddressInRegistry}
+ isTakerTokenAddressInRegistry={this.state.isTakerTokenAddressInRegistry}
+ />
+ <div className="center pt3 pb2">Expires: {expiryDate} UTC</div>
+ </div>
+ </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 ? (
+ <div>
+ <RaisedButton
+ style={{ width: '100%' }}
+ disabled={this.state.isCancelling}
+ label={this.state.isCancelling ? 'Cancelling order...' : 'Cancel order'}
+ onClick={this._onCancelOrderClickFireAndForgetAsync.bind(this)}
+ />
+ {this.state.didCancelOrderSucceed && (
+ <Alert type={AlertTypes.SUCCESS} message={this._renderCancelSuccessMsg()} />
+ )}
+ </div>
+ ) : (
+ <div>
+ <RaisedButton
+ style={{ width: '100%' }}
+ disabled={this.state.isFilling}
+ label={this.state.isFilling ? 'Filling order...' : 'Fill order'}
+ onClick={this._onFillOrderClick.bind(this)}
+ />
+ {!_.isEmpty(this.state.globalErrMsg) && (
+ <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} />
+ )}
+ {this.state.didFillOrderSucceed && (
+ <Alert type={AlertTypes.SUCCESS} message={this._renderFillSuccessMsg()} />
+ )}
+ </div>
+ )}
+ </div>
+ </div>
+ );
+ }
+ private _renderFillSuccessMsg() {
+ return (
+ <div>
+ Order successfully filled. See the trade details in your{' '}
+ <Link to={`${WebsitePaths.Portal}/trades`} style={{ color: colors.white }}>
+ trade history
+ </Link>
+ </div>
+ );
+ }
+ private _renderCancelSuccessMsg() {
+ return <div>Order successfully cancelled.</div>;
+ }
+ private _onFillOrderClick() {
+ if (!this.state.isMakerTokenAddressInRegistry || !this.state.isTakerTokenAddressInRegistry) {
+ this.setState({
+ isFillWarningDialogOpen: true,
+ });
+ } else {
+ // tslint:disable-next-line:no-floating-promises
+ this._onFillOrderClickFireAndForgetAsync();
+ }
+ }
+ private _onFillWarningClosed(didUserCancel: boolean) {
+ this.setState({
+ isFillWarningDialogOpen: false,
+ });
+ if (!didUserCancel) {
+ // tslint:disable-next-line:no-floating-promises
+ this._onFillOrderClickFireAndForgetAsync();
+ }
+ }
+ private _onFillAmountChange(isValid: boolean, amount?: BigNumber) {
+ this.props.dispatcher.updateOrderFillAmount(amount);
+ }
+ 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);
+ }
+ private async _checkForUntrackedTokensAndAskToAdd() {
+ if (!_.isEmpty(this.state.orderJSONErrMsg)) {
+ return;
+ }
- const makerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.maker.token.address];
- const takerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.taker.token.address];
+ const makerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.maker.token.address];
+ const takerTokenIfExists = this.props.tokenByAddress[this.state.parsedOrder.taker.token.address];
- const tokensToTrack = [];
- const isUnseenMakerToken = _.isUndefined(makerTokenIfExists);
- const isMakerTokenTracked = !_.isUndefined(makerTokenIfExists) && makerTokenIfExists.isTracked;
- if (isUnseenMakerToken) {
- 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({
- ...this.state.parsedOrder.taker.token,
- iconUrl: undefined,
- isTracked: false,
- isRegistered: false,
- });
- } else if (!isTakerTokenTracked) {
- tokensToTrack.push(takerTokenIfExists);
- }
- if (!_.isEmpty(tokensToTrack)) {
- this.setState({
- isConfirmingTokenTracking: true,
- tokensToTrack,
- });
- } else {
- this.setState({
- areAllInvolvedTokensTracked: true,
- });
- }
- }
- private async _validateFillOrderFireAndForgetAsync(orderJSON: string) {
- let orderJSONErrMsg = '';
- let parsedOrder: Order;
- try {
- const order = JSON.parse(orderJSON);
- 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(', ')}`);
- return;
- }
- parsedOrder = order;
+ const tokensToTrack = [];
+ const isUnseenMakerToken = _.isUndefined(makerTokenIfExists);
+ const isMakerTokenTracked = !_.isUndefined(makerTokenIfExists) && makerTokenIfExists.isTracked;
+ if (isUnseenMakerToken) {
+ 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({
+ ...this.state.parsedOrder.taker.token,
+ iconUrl: undefined,
+ isTracked: false,
+ isRegistered: false,
+ });
+ } else if (!isTakerTokenTracked) {
+ tokensToTrack.push(takerTokenIfExists);
+ }
+ if (!_.isEmpty(tokensToTrack)) {
+ this.setState({
+ isConfirmingTokenTracking: true,
+ tokensToTrack,
+ });
+ } else {
+ this.setState({
+ areAllInvolvedTokensTracked: true,
+ });
+ }
+ }
+ private async _validateFillOrderFireAndForgetAsync(orderJSON: string) {
+ let orderJSONErrMsg = '';
+ let parsedOrder: Order;
+ try {
+ const order = JSON.parse(orderJSON);
+ 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(', ')}`);
+ return;
+ }
+ parsedOrder = order;
- const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists();
- const makerAmount = new BigNumber(parsedOrder.maker.amount);
- const takerAmount = new BigNumber(parsedOrder.taker.amount);
- const expiration = new BigNumber(parsedOrder.expiration);
- const salt = new BigNumber(parsedOrder.salt);
- const parsedMakerFee = new BigNumber(parsedOrder.maker.feeAmount);
- const parsedTakerFee = new BigNumber(parsedOrder.taker.feeAmount);
+ const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists();
+ const makerAmount = new BigNumber(parsedOrder.maker.amount);
+ const takerAmount = new BigNumber(parsedOrder.taker.amount);
+ const expiration = new BigNumber(parsedOrder.expiration);
+ const salt = new BigNumber(parsedOrder.salt);
+ const parsedMakerFee = new BigNumber(parsedOrder.maker.feeAmount);
+ const parsedTakerFee = new BigNumber(parsedOrder.taker.feeAmount);
- const zeroExOrder: ZeroExOrder = {
- exchangeContractAddress: parsedOrder.exchangeContract,
- expirationUnixTimestampSec: expiration,
- feeRecipient: parsedOrder.feeRecipient,
- maker: parsedOrder.maker.address,
- makerFee: parsedMakerFee,
- makerTokenAddress: parsedOrder.maker.token.address,
- makerTokenAmount: makerAmount,
- salt,
- taker: _.isEmpty(parsedOrder.taker.address) ? constants.NULL_ADDRESS : parsedOrder.taker.address,
- takerFee: parsedTakerFee,
- takerTokenAddress: parsedOrder.taker.token.address,
- takerTokenAmount: takerAmount,
- };
- const orderHash = ZeroEx.getOrderHashHex(zeroExOrder);
+ const zeroExOrder: ZeroExOrder = {
+ exchangeContractAddress: parsedOrder.exchangeContract,
+ expirationUnixTimestampSec: expiration,
+ feeRecipient: parsedOrder.feeRecipient,
+ maker: parsedOrder.maker.address,
+ makerFee: parsedMakerFee,
+ makerTokenAddress: parsedOrder.maker.token.address,
+ makerTokenAmount: makerAmount,
+ salt,
+ taker: _.isEmpty(parsedOrder.taker.address) ? constants.NULL_ADDRESS : parsedOrder.taker.address,
+ takerFee: parsedTakerFee,
+ takerTokenAddress: parsedOrder.taker.token.address,
+ takerTokenAmount: takerAmount,
+ };
+ const orderHash = ZeroEx.getOrderHashHex(zeroExOrder);
- const signature = parsedOrder.signature;
- const isValidSignature = ZeroEx.isValidSignature(signature.hash, signature, parsedOrder.maker.address);
- if (this.props.networkId !== parsedOrder.networkId) {
- orderJSONErrMsg = `This order was made on another Ethereum network
+ const signature = parsedOrder.signature;
+ const isValidSignature = ZeroEx.isValidSignature(signature.hash, signature, parsedOrder.maker.address);
+ if (this.props.networkId !== parsedOrder.networkId) {
+ orderJSONErrMsg = `This order was made on another Ethereum network
(id: ${parsedOrder.networkId}). Connect to this network to fill.`;
- parsedOrder = undefined;
- } else if (exchangeContractAddr !== parsedOrder.exchangeContract) {
- orderJSONErrMsg = 'This order was made using a deprecated 0x Exchange contract.';
- parsedOrder = undefined;
- } else if (orderHash !== signature.hash) {
- orderJSONErrMsg = 'Order hash does not match supplied plaintext values';
- parsedOrder = undefined;
- } else if (!isValidSignature) {
- orderJSONErrMsg = 'Order signature is invalid';
- parsedOrder = undefined;
- } else {
- // Update user supplied order cache so that if they navigate away from fill view
- // e.g to set a token allowance, when they come back, the fill order persists
- this.props.dispatcher.updateUserSuppliedOrderCache(parsedOrder);
- }
- } catch (err) {
- utils.consoleLog(`Validate order err: ${err}`);
- if (!_.isEmpty(orderJSON)) {
- orderJSONErrMsg = 'Submitted order JSON is not valid JSON';
- }
- this.setState({
- didOrderValidationRun: true,
- orderJSON,
- orderJSONErrMsg,
- parsedOrder,
- });
- return;
- }
+ parsedOrder = undefined;
+ } else if (exchangeContractAddr !== parsedOrder.exchangeContract) {
+ orderJSONErrMsg = 'This order was made using a deprecated 0x Exchange contract.';
+ parsedOrder = undefined;
+ } else if (orderHash !== signature.hash) {
+ orderJSONErrMsg = 'Order hash does not match supplied plaintext values';
+ parsedOrder = undefined;
+ } else if (!isValidSignature) {
+ orderJSONErrMsg = 'Order signature is invalid';
+ parsedOrder = undefined;
+ } else {
+ // Update user supplied order cache so that if they navigate away from fill view
+ // e.g to set a token allowance, when they come back, the fill order persists
+ this.props.dispatcher.updateUserSuppliedOrderCache(parsedOrder);
+ }
+ } catch (err) {
+ utils.consoleLog(`Validate order err: ${err}`);
+ if (!_.isEmpty(orderJSON)) {
+ orderJSONErrMsg = 'Submitted order JSON is not valid JSON';
+ }
+ this.setState({
+ didOrderValidationRun: true,
+ orderJSON,
+ orderJSONErrMsg,
+ parsedOrder,
+ });
+ return;
+ }
- let unavailableTakerAmount = new BigNumber(0);
- if (!_.isEmpty(orderJSONErrMsg)) {
- // Clear cache entry if user updates orderJSON to invalid entry
- this.props.dispatcher.updateUserSuppliedOrderCache(undefined);
- } else {
- const orderHash = parsedOrder.signature.hash;
- unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash);
- const isMakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync(
- parsedOrder.maker.token.address,
- );
- const isTakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync(
- parsedOrder.taker.token.address,
- );
- this.setState({
- isMakerTokenAddressInRegistry,
- isTakerTokenAddressInRegistry,
- });
- }
+ let unavailableTakerAmount = new BigNumber(0);
+ if (!_.isEmpty(orderJSONErrMsg)) {
+ // Clear cache entry if user updates orderJSON to invalid entry
+ this.props.dispatcher.updateUserSuppliedOrderCache(undefined);
+ } else {
+ const orderHash = parsedOrder.signature.hash;
+ unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash);
+ const isMakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync(
+ parsedOrder.maker.token.address,
+ );
+ const isTakerTokenAddressInRegistry = await this.props.blockchain.isAddressInTokenRegistryAsync(
+ parsedOrder.taker.token.address,
+ );
+ this.setState({
+ isMakerTokenAddressInRegistry,
+ isTakerTokenAddressInRegistry,
+ });
+ }
- this.setState({
- didOrderValidationRun: true,
- orderJSON,
- orderJSONErrMsg,
- parsedOrder,
- unavailableTakerAmount,
- });
+ this.setState({
+ didOrderValidationRun: true,
+ orderJSON,
+ orderJSONErrMsg,
+ parsedOrder,
+ unavailableTakerAmount,
+ });
- await this._checkForUntrackedTokensAndAskToAdd();
- }
- private async _onFillOrderClickFireAndForgetAsync(): Promise<void> {
- if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) {
- this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
- return;
- }
+ await this._checkForUntrackedTokensAndAskToAdd();
+ }
+ private async _onFillOrderClickFireAndForgetAsync(): Promise<void> {
+ if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return;
+ }
- this.setState({
- isFilling: true,
- didFillOrderSucceed: false,
- });
+ this.setState({
+ isFilling: true,
+ didFillOrderSucceed: false,
+ });
- const parsedOrder = this.state.parsedOrder;
- const takerFillAmount = this.props.orderFillAmount;
+ const parsedOrder = this.state.parsedOrder;
+ const takerFillAmount = this.props.orderFillAmount;
- if (_.isUndefined(this.props.userAddress)) {
- this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
- this.setState({
- isFilling: false,
- });
- return;
- }
- let globalErrMsg = '';
+ if (_.isUndefined(this.props.userAddress)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ this.setState({
+ isFilling: false,
+ });
+ return;
+ }
+ let globalErrMsg = '';
- if (_.isUndefined(takerFillAmount)) {
- globalErrMsg = 'You must specify a fill amount';
- }
+ if (_.isUndefined(takerFillAmount)) {
+ globalErrMsg = 'You must specify a fill amount';
+ }
- const signedOrder = this.props.blockchain.portalOrderToSignedOrder(
- parsedOrder.maker.address,
- parsedOrder.taker.address,
- parsedOrder.maker.token.address,
- parsedOrder.taker.token.address,
- new BigNumber(parsedOrder.maker.amount),
- new BigNumber(parsedOrder.taker.amount),
- new BigNumber(parsedOrder.maker.feeAmount),
- new BigNumber(parsedOrder.taker.feeAmount),
- new BigNumber(this.state.parsedOrder.expiration),
- parsedOrder.feeRecipient,
- parsedOrder.signature,
- new BigNumber(parsedOrder.salt),
- );
- if (_.isEmpty(globalErrMsg)) {
- try {
- await this.props.blockchain.validateFillOrderThrowIfInvalidAsync(
- signedOrder,
- takerFillAmount,
- this.props.userAddress,
- );
- } catch (err) {
- globalErrMsg = utils.zeroExErrToHumanReadableErrMsg(err.message, parsedOrder.taker.address);
- }
- }
- if (!_.isEmpty(globalErrMsg)) {
- this.setState({
- isFilling: false,
- globalErrMsg,
- });
- return;
- }
- try {
- const orderFilledAmount: BigNumber = await this.props.blockchain.fillOrderAsync(
- signedOrder,
- this.props.orderFillAmount,
- );
- // After fill completes, let's update the token balances
- const makerToken = this.props.tokenByAddress[parsedOrder.maker.token.address];
- const takerToken = this.props.tokenByAddress[parsedOrder.taker.token.address];
- const tokens = [makerToken, takerToken];
- await this.props.blockchain.updateTokenBalancesAndAllowancesAsync(tokens);
- this.setState({
- isFilling: false,
- didFillOrderSucceed: true,
- globalErrMsg: '',
- unavailableTakerAmount: this.state.unavailableTakerAmount.plus(orderFilledAmount),
- });
- return;
- } catch (err) {
- this.setState({
- isFilling: false,
- });
- const errMsg = `${err}`;
- if (_.includes(errMsg, 'User denied transaction signature')) {
- return;
- }
- globalErrMsg = 'Failed to fill order, please refresh and try again';
- utils.consoleLog(`${err}`);
- this.setState({
- globalErrMsg,
- });
- await errorReporter.reportAsync(err);
- return;
- }
- }
- private async _onCancelOrderClickFireAndForgetAsync(): Promise<void> {
- if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) {
- this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
- return;
- }
+ const signedOrder = this.props.blockchain.portalOrderToSignedOrder(
+ parsedOrder.maker.address,
+ parsedOrder.taker.address,
+ parsedOrder.maker.token.address,
+ parsedOrder.taker.token.address,
+ new BigNumber(parsedOrder.maker.amount),
+ new BigNumber(parsedOrder.taker.amount),
+ new BigNumber(parsedOrder.maker.feeAmount),
+ new BigNumber(parsedOrder.taker.feeAmount),
+ new BigNumber(this.state.parsedOrder.expiration),
+ parsedOrder.feeRecipient,
+ parsedOrder.signature,
+ new BigNumber(parsedOrder.salt),
+ );
+ if (_.isEmpty(globalErrMsg)) {
+ try {
+ await this.props.blockchain.validateFillOrderThrowIfInvalidAsync(
+ signedOrder,
+ takerFillAmount,
+ this.props.userAddress,
+ );
+ } catch (err) {
+ globalErrMsg = utils.zeroExErrToHumanReadableErrMsg(err.message, parsedOrder.taker.address);
+ }
+ }
+ if (!_.isEmpty(globalErrMsg)) {
+ this.setState({
+ isFilling: false,
+ globalErrMsg,
+ });
+ return;
+ }
+ try {
+ const orderFilledAmount: BigNumber = await this.props.blockchain.fillOrderAsync(
+ signedOrder,
+ this.props.orderFillAmount,
+ );
+ // After fill completes, let's update the token balances
+ const makerToken = this.props.tokenByAddress[parsedOrder.maker.token.address];
+ const takerToken = this.props.tokenByAddress[parsedOrder.taker.token.address];
+ const tokens = [makerToken, takerToken];
+ await this.props.blockchain.updateTokenBalancesAndAllowancesAsync(tokens);
+ this.setState({
+ isFilling: false,
+ didFillOrderSucceed: true,
+ globalErrMsg: '',
+ unavailableTakerAmount: this.state.unavailableTakerAmount.plus(orderFilledAmount),
+ });
+ return;
+ } catch (err) {
+ this.setState({
+ isFilling: false,
+ });
+ const errMsg = `${err}`;
+ if (_.includes(errMsg, 'User denied transaction signature')) {
+ return;
+ }
+ globalErrMsg = 'Failed to fill order, please refresh and try again';
+ utils.consoleLog(`${err}`);
+ this.setState({
+ globalErrMsg,
+ });
+ await errorReporter.reportAsync(err);
+ return;
+ }
+ }
+ private async _onCancelOrderClickFireAndForgetAsync(): Promise<void> {
+ if (this.props.blockchainErr !== BlockchainErrs.NoError || _.isEmpty(this.props.userAddress)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return;
+ }
- this.setState({
- isCancelling: true,
- didCancelOrderSucceed: false,
- });
+ this.setState({
+ isCancelling: true,
+ didCancelOrderSucceed: false,
+ });
- const parsedOrder = this.state.parsedOrder;
- const orderHash = parsedOrder.signature.hash;
- const takerAddress = this.props.userAddress;
+ const parsedOrder = this.state.parsedOrder;
+ const orderHash = parsedOrder.signature.hash;
+ const takerAddress = this.props.userAddress;
- if (_.isUndefined(takerAddress)) {
- this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
- this.setState({
- isFilling: false,
- });
- return;
- }
- let globalErrMsg = '';
+ if (_.isUndefined(takerAddress)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ this.setState({
+ isFilling: false,
+ });
+ return;
+ }
+ let globalErrMsg = '';
- const takerTokenAmount = new BigNumber(parsedOrder.taker.amount);
+ const takerTokenAmount = new BigNumber(parsedOrder.taker.amount);
- const signedOrder = this.props.blockchain.portalOrderToSignedOrder(
- parsedOrder.maker.address,
- parsedOrder.taker.address,
- parsedOrder.maker.token.address,
- parsedOrder.taker.token.address,
- new BigNumber(parsedOrder.maker.amount),
- takerTokenAmount,
- new BigNumber(parsedOrder.maker.feeAmount),
- new BigNumber(parsedOrder.taker.feeAmount),
- new BigNumber(this.state.parsedOrder.expiration),
- parsedOrder.feeRecipient,
- parsedOrder.signature,
- new BigNumber(parsedOrder.salt),
- );
- const unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash);
- const availableTakerTokenAmount = takerTokenAmount.minus(unavailableTakerAmount);
- try {
- await this.props.blockchain.validateCancelOrderThrowIfInvalidAsync(signedOrder, availableTakerTokenAmount);
- } catch (err) {
- globalErrMsg = utils.zeroExErrToHumanReadableErrMsg(err.message, parsedOrder.taker.address);
- }
- if (!_.isEmpty(globalErrMsg)) {
- this.setState({
- isCancelling: false,
- globalErrMsg,
- });
- return;
- }
- try {
- await this.props.blockchain.cancelOrderAsync(signedOrder, availableTakerTokenAmount);
- this.setState({
- isCancelling: false,
- didCancelOrderSucceed: true,
- globalErrMsg: '',
- unavailableTakerAmount: takerTokenAmount,
- });
- return;
- } catch (err) {
- this.setState({
- isCancelling: false,
- });
- const errMsg = `${err}`;
- if (_.includes(errMsg, 'User denied transaction signature')) {
- return;
- }
- globalErrMsg = 'Failed to cancel order, please refresh and try again';
- utils.consoleLog(`${err}`);
- this.setState({
- globalErrMsg,
- });
- await errorReporter.reportAsync(err);
- return;
- }
- }
- 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) {
- if (!didConfirmTokenTracking) {
- this.setState({
- orderJSON: '',
- orderJSONErrMsg: '',
- parsedOrder: undefined,
- });
- } else {
- this.setState({
- areAllInvolvedTokensTracked: true,
- });
- }
- this.setState({
- isConfirmingTokenTracking: !this.state.isConfirmingTokenTracking,
- tokensToTrack: [],
- });
- }
+ const signedOrder = this.props.blockchain.portalOrderToSignedOrder(
+ parsedOrder.maker.address,
+ parsedOrder.taker.address,
+ parsedOrder.maker.token.address,
+ parsedOrder.taker.token.address,
+ new BigNumber(parsedOrder.maker.amount),
+ takerTokenAmount,
+ new BigNumber(parsedOrder.maker.feeAmount),
+ new BigNumber(parsedOrder.taker.feeAmount),
+ new BigNumber(this.state.parsedOrder.expiration),
+ parsedOrder.feeRecipient,
+ parsedOrder.signature,
+ new BigNumber(parsedOrder.salt),
+ );
+ const unavailableTakerAmount = await this.props.blockchain.getUnavailableTakerAmountAsync(orderHash);
+ const availableTakerTokenAmount = takerTokenAmount.minus(unavailableTakerAmount);
+ try {
+ await this.props.blockchain.validateCancelOrderThrowIfInvalidAsync(signedOrder, availableTakerTokenAmount);
+ } catch (err) {
+ globalErrMsg = utils.zeroExErrToHumanReadableErrMsg(err.message, parsedOrder.taker.address);
+ }
+ if (!_.isEmpty(globalErrMsg)) {
+ this.setState({
+ isCancelling: false,
+ globalErrMsg,
+ });
+ return;
+ }
+ try {
+ await this.props.blockchain.cancelOrderAsync(signedOrder, availableTakerTokenAmount);
+ this.setState({
+ isCancelling: false,
+ didCancelOrderSucceed: true,
+ globalErrMsg: '',
+ unavailableTakerAmount: takerTokenAmount,
+ });
+ return;
+ } catch (err) {
+ this.setState({
+ isCancelling: false,
+ });
+ const errMsg = `${err}`;
+ if (_.includes(errMsg, 'User denied transaction signature')) {
+ return;
+ }
+ globalErrMsg = 'Failed to cancel order, please refresh and try again';
+ utils.consoleLog(`${err}`);
+ this.setState({
+ globalErrMsg,
+ });
+ await errorReporter.reportAsync(err);
+ return;
+ }
+ }
+ 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) {
+ if (!didConfirmTokenTracking) {
+ this.setState({
+ orderJSON: '',
+ orderJSONErrMsg: '',
+ parsedOrder: undefined,
+ });
+ } else {
+ this.setState({
+ areAllInvolvedTokensTracked: true,
+ });
+ }
+ this.setState({
+ isConfirmingTokenTracking: !this.state.isConfirmingTokenTracking,
+ tokensToTrack: [],
+ });
+ }
} // tslint:disable:max-file-line-count
diff --git a/packages/website/ts/components/fill_order_json.tsx b/packages/website/ts/components/fill_order_json.tsx
index f8e43481a..3446b8a39 100644
--- a/packages/website/ts/components/fill_order_json.tsx
+++ b/packages/website/ts/components/fill_order_json.tsx
@@ -10,71 +10,71 @@ import { constants } from 'ts/utils/constants';
import { utils } from 'ts/utils/utils';
interface FillOrderJSONProps {
- blockchain: Blockchain;
- tokenByAddress: TokenByAddress;
- networkId: number;
- orderJSON: string;
- onFillOrderJSONChanged: (event: any) => void;
+ blockchain: Blockchain;
+ tokenByAddress: TokenByAddress;
+ networkId: number;
+ orderJSON: string;
+ onFillOrderJSONChanged: (event: any) => void;
}
interface FillOrderJSONState {}
export class FillOrderJSON extends React.Component<FillOrderJSONProps, FillOrderJSONState> {
- public render() {
- const tokenAddresses = _.keys(this.props.tokenByAddress);
- const exchangeContract = this.props.blockchain.getExchangeContractAddressIfExists();
- const hintSideToAssetToken = {
- [Side.Deposit]: {
- amount: new BigNumber(35),
- address: tokenAddresses[0],
- },
- [Side.Receive]: {
- amount: new BigNumber(89),
- address: tokenAddresses[1],
- },
- };
- const hintOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec();
- const hintSignatureData = {
- hash: '0xf965a9978a0381ab58f5a2408ad967c...',
- r: '0xf01103f759e2289a28593eaf22e5820032...',
- s: '937862111edcba395f8a9e0cc1b2c5e12320...',
- v: 27,
- };
- const hintSalt = ZeroEx.generatePseudoRandomSalt();
- 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 }}>
- <TextField
- id="orderJSON"
- hintStyle={{ bottom: 0, top: 0 }}
- fullWidth={true}
- value={this.props.orderJSON}
- onChange={this.props.onFillOrderJSONChanged.bind(this)}
- hintText={hintOrderJSON}
- multiLine={true}
- rows={6}
- rowsMax={6}
- underlineStyle={{ display: 'none' }}
- textareaStyle={{ marginTop: 0 }}
- />
- </Paper>
- </div>
- );
- }
+ public render() {
+ const tokenAddresses = _.keys(this.props.tokenByAddress);
+ const exchangeContract = this.props.blockchain.getExchangeContractAddressIfExists();
+ const hintSideToAssetToken = {
+ [Side.Deposit]: {
+ amount: new BigNumber(35),
+ address: tokenAddresses[0],
+ },
+ [Side.Receive]: {
+ amount: new BigNumber(89),
+ address: tokenAddresses[1],
+ },
+ };
+ const hintOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec();
+ const hintSignatureData = {
+ hash: '0xf965a9978a0381ab58f5a2408ad967c...',
+ r: '0xf01103f759e2289a28593eaf22e5820032...',
+ s: '937862111edcba395f8a9e0cc1b2c5e12320...',
+ v: 27,
+ };
+ const hintSalt = ZeroEx.generatePseudoRandomSalt();
+ 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 }}>
+ <TextField
+ id="orderJSON"
+ hintStyle={{ bottom: 0, top: 0 }}
+ fullWidth={true}
+ value={this.props.orderJSON}
+ onChange={this.props.onFillOrderJSONChanged.bind(this)}
+ hintText={hintOrderJSON}
+ multiLine={true}
+ rows={6}
+ rowsMax={6}
+ 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 165d21b34..40b04723d 100644
--- a/packages/website/ts/components/fill_warning_dialog.tsx
+++ b/packages/website/ts/components/fill_warning_dialog.tsx
@@ -4,42 +4,42 @@ import * as React from 'react';
import { colors } from 'ts/utils/colors';
interface FillWarningDialogProps {
- isOpen: boolean;
- onToggleDialog: (didUserCancel: boolean) => void;
+ isOpen: boolean;
+ onToggleDialog: (didUserCancel: boolean) => void;
}
export function FillWarningDialog(props: FillWarningDialogProps) {
- const didCancel = true;
- return (
- <Dialog
- title="Warning"
- titleStyle={{ fontWeight: 100, color: colors.red500 }}
- actions={[
- <FlatButton
- key="fillWarningCancel"
- label="Cancel"
- onTouchTap={props.onToggleDialog.bind(this, didCancel)}
- />,
- <FlatButton
- key="fillWarningContinue"
- label="Fill Order"
- onTouchTap={props.onToggleDialog.bind(this, !didCancel)}
- />,
- ]}
- open={props.isOpen}
- onRequestClose={props.onToggleDialog.bind(this)}
- autoScrollBodyContent={true}
- modal={true}
- >
- <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">
- See this how-to guide
- </a>) before filling an order. <b>This action may result in the loss of funds</b>.
- </div>
- </div>
- </Dialog>
- );
+ const didCancel = true;
+ return (
+ <Dialog
+ title="Warning"
+ titleStyle={{ fontWeight: 100, color: colors.red500 }}
+ actions={[
+ <FlatButton
+ key="fillWarningCancel"
+ label="Cancel"
+ onTouchTap={props.onToggleDialog.bind(this, didCancel)}
+ />,
+ <FlatButton
+ key="fillWarningContinue"
+ label="Fill Order"
+ onTouchTap={props.onToggleDialog.bind(this, !didCancel)}
+ />,
+ ]}
+ open={props.isOpen}
+ onRequestClose={props.onToggleDialog.bind(this)}
+ autoScrollBodyContent={true}
+ modal={true}
+ >
+ <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">
+ See this how-to guide
+ </a>) before filling an order. <b>This action may result in the loss of funds</b>.
+ </div>
+ </div>
+ </Dialog>
+ );
}
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 18f371624..07fca1ddc 100644
--- a/packages/website/ts/components/flash_messages/token_send_completed.tsx
+++ b/packages/website/ts/components/flash_messages/token_send_completed.tsx
@@ -7,28 +7,28 @@ import { colors } from 'ts/utils/colors';
import { utils } from 'ts/utils/utils';
interface TokenSendCompletedProps {
- etherScanLinkIfExists?: string;
- token: Token;
- toAddress: string;
- amountInBaseUnits: BigNumber;
+ etherScanLinkIfExists?: string;
+ token: Token;
+ toAddress: string;
+ amountInBaseUnits: BigNumber;
}
interface TokenSendCompletedState {}
export class TokenSendCompleted extends React.Component<TokenSendCompletedProps, TokenSendCompletedState> {
- public render() {
- 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 (
- <div>
- {`Sent ${amountInUnits} ${this.props.token.symbol} to ${truncatedAddress}: `}
- {etherScanLink}
- </div>
- );
- }
+ public render() {
+ 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 (
+ <div>
+ {`Sent ${amountInUnits} ${this.props.token.symbol} to ${truncatedAddress}: `}
+ {etherScanLink}
+ </div>
+ );
+ }
}
diff --git a/packages/website/ts/components/flash_messages/transaction_submitted.tsx b/packages/website/ts/components/flash_messages/transaction_submitted.tsx
index 862e382dd..14464b923 100644
--- a/packages/website/ts/components/flash_messages/transaction_submitted.tsx
+++ b/packages/website/ts/components/flash_messages/transaction_submitted.tsx
@@ -3,24 +3,24 @@ import * as React from 'react';
import { colors } from 'ts/utils/colors';
interface TransactionSubmittedProps {
- etherScanLinkIfExists?: string;
+ etherScanLinkIfExists?: string;
}
interface TransactionSubmittedState {}
export class TransactionSubmitted extends React.Component<TransactionSubmittedProps, TransactionSubmittedState> {
- public render() {
- if (_.isUndefined(this.props.etherScanLinkIfExists)) {
- return <div>Transaction submitted to the network</div>;
- } else {
- return (
- <div>
- Transaction submitted to the network:{' '}
- <a style={{ color: colors.white }} href={`${this.props.etherScanLinkIfExists}`} target="_blank">
- Verify on Etherscan
- </a>
- </div>
- );
- }
- }
+ public render() {
+ if (_.isUndefined(this.props.etherScanLinkIfExists)) {
+ return <div>Transaction submitted to the network</div>;
+ } else {
+ return (
+ <div>
+ Transaction submitted to the network:{' '}
+ <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 a0f1a0c96..3a342591c 100644
--- a/packages/website/ts/components/footer.tsx
+++ b/packages/website/ts/components/footer.tsx
@@ -6,106 +6,106 @@ import { colors } from 'ts/utils/colors';
import { constants } from 'ts/utils/constants';
interface MenuItemsBySection {
- [sectionName: string]: FooterMenuItem[];
+ [sectionName: string]: FooterMenuItem[];
}
interface FooterMenuItem {
- title: string;
- path?: string;
- isExternal?: boolean;
+ title: string;
+ path?: string;
+ isExternal?: boolean;
}
enum Sections {
- Documentation = 'Documentation',
- Community = 'Community',
- Organization = 'Organization',
+ Documentation = 'Documentation',
+ Community = 'Community',
+ Organization = 'Organization',
}
const ICON_DIMENSION = 16;
const menuItemsBySection: MenuItemsBySection = {
- Documentation: [
- {
- title: '0x.js',
- path: WebsitePaths.ZeroExJs,
- },
- {
- title: '0x Smart Contracts',
- path: WebsitePaths.SmartContracts,
- },
- {
- title: '0x Connect',
- path: WebsitePaths.Connect,
- },
- {
- title: 'Whitepaper',
- path: WebsitePaths.Whitepaper,
- isExternal: true,
- },
- {
- title: 'Wiki',
- path: WebsitePaths.Wiki,
- },
- {
- title: 'FAQ',
- path: WebsitePaths.FAQ,
- },
- ],
- Community: [
- {
- title: 'Rocket.chat',
- isExternal: true,
- path: constants.URL_ZEROEX_CHAT,
- },
- {
- title: 'Blog',
- isExternal: true,
- path: constants.URL_BLOG,
- },
- {
- title: 'Twitter',
- isExternal: true,
- path: constants.URL_TWITTER,
- },
- {
- title: 'Reddit',
- isExternal: true,
- path: constants.URL_REDDIT,
- },
- {
- title: 'Forum',
- isExternal: true,
- path: constants.URL_DISCOURSE_FORUM,
- },
- ],
- Organization: [
- {
- title: 'About',
- isExternal: false,
- path: WebsitePaths.About,
- },
- {
- title: 'Careers',
- isExternal: true,
- path: constants.URL_ANGELLIST,
- },
- {
- title: 'Contact',
- isExternal: true,
- path: 'mailto:team@0xproject.com',
- },
- ],
+ Documentation: [
+ {
+ title: '0x.js',
+ path: WebsitePaths.ZeroExJs,
+ },
+ {
+ title: '0x Smart Contracts',
+ path: WebsitePaths.SmartContracts,
+ },
+ {
+ title: '0x Connect',
+ path: WebsitePaths.Connect,
+ },
+ {
+ title: 'Whitepaper',
+ path: WebsitePaths.Whitepaper,
+ isExternal: true,
+ },
+ {
+ title: 'Wiki',
+ path: WebsitePaths.Wiki,
+ },
+ {
+ title: 'FAQ',
+ path: WebsitePaths.FAQ,
+ },
+ ],
+ Community: [
+ {
+ title: 'Rocket.chat',
+ isExternal: true,
+ path: constants.URL_ZEROEX_CHAT,
+ },
+ {
+ title: 'Blog',
+ isExternal: true,
+ path: constants.URL_BLOG,
+ },
+ {
+ title: 'Twitter',
+ isExternal: true,
+ path: constants.URL_TWITTER,
+ },
+ {
+ title: 'Reddit',
+ isExternal: true,
+ path: constants.URL_REDDIT,
+ },
+ {
+ title: 'Forum',
+ isExternal: true,
+ path: constants.URL_DISCOURSE_FORUM,
+ },
+ ],
+ Organization: [
+ {
+ title: 'About',
+ isExternal: false,
+ path: WebsitePaths.About,
+ },
+ {
+ title: 'Careers',
+ isExternal: true,
+ path: constants.URL_ANGELLIST,
+ },
+ {
+ title: 'Contact',
+ isExternal: true,
+ path: 'mailto:team@0xproject.com',
+ },
+ ],
};
const linkStyle = {
- color: colors.white,
- cursor: 'pointer',
+ color: colors.white,
+ cursor: 'pointer',
};
const titleToIcon: { [title: string]: string } = {
- 'Rocket.chat': 'rocketchat.png',
- Blog: 'medium.png',
- Twitter: 'twitter.png',
- Reddit: 'reddit.png',
- Forum: 'discourse.png',
+ 'Rocket.chat': 'rocketchat.png',
+ Blog: 'medium.png',
+ Twitter: 'twitter.png',
+ Reddit: 'reddit.png',
+ Forum: 'discourse.png',
};
export interface FooterProps {}
@@ -113,100 +113,100 @@ export interface FooterProps {}
interface FooterState {}
export class Footer extends React.Component<FooterProps, FooterState> {
- public render() {
- return (
- <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>
- <img src="/images/protocol_logo_white.png" height="30" />
- </div>
- <div
- style={{
- fontSize: 11,
- color: colors.grey,
- paddingLeft: 37,
- paddingTop: 2,
- }}
- >
- © ZeroEx, Intl.
- </div>
- </div>
- </div>
- <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))}
- </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))}
- </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))}
- </div>
- </div>
- </div>
- </div>
- </div>
- );
- }
- private _renderIcon(fileName: string) {
- return (
- <div style={{ height: ICON_DIMENSION, width: ICON_DIMENSION }}>
- <img src={`/images/social/${fileName}`} style={{ width: ICON_DIMENSION }} />
- </div>
- );
- }
- 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 className="flex">
- <div className="pr1">{this._renderIcon(iconIfExists)}</div>
- <div>{item.title}</div>
- </div>
- </div>
- ) : (
- item.title
- )}
- </a>
- ) : (
- <Link to={item.path} style={linkStyle} className="text-decoration-none">
- <div>
- {!_.isUndefined(iconIfExists) && (
- <div className="pr1">{this._renderIcon(iconIfExists)}</div>
- )}
- {item.title}
- </div>
- </Link>
- )}
- </div>
- );
- }
- private _renderHeader(title: string) {
- const headerStyle = {
- textTransform: 'uppercase',
- color: colors.grey400,
- letterSpacing: 2,
- fontFamily: 'Roboto Mono',
- fontSize: 13,
- };
- return (
- <div className="lg-pb2 md-pb2 sm-pt4" style={headerStyle}>
- {title}
- </div>
- );
- }
+ public render() {
+ return (
+ <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>
+ <img src="/images/protocol_logo_white.png" height="30" />
+ </div>
+ <div
+ style={{
+ fontSize: 11,
+ color: colors.grey,
+ paddingLeft: 37,
+ paddingTop: 2,
+ }}
+ >
+ © ZeroEx, Intl.
+ </div>
+ </div>
+ </div>
+ <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))}
+ </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))}
+ </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))}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private _renderIcon(fileName: string) {
+ return (
+ <div style={{ height: ICON_DIMENSION, width: ICON_DIMENSION }}>
+ <img src={`/images/social/${fileName}`} style={{ width: ICON_DIMENSION }} />
+ </div>
+ );
+ }
+ 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 className="flex">
+ <div className="pr1">{this._renderIcon(iconIfExists)}</div>
+ <div>{item.title}</div>
+ </div>
+ </div>
+ ) : (
+ item.title
+ )}
+ </a>
+ ) : (
+ <Link to={item.path} style={linkStyle} className="text-decoration-none">
+ <div>
+ {!_.isUndefined(iconIfExists) && (
+ <div className="pr1">{this._renderIcon(iconIfExists)}</div>
+ )}
+ {item.title}
+ </div>
+ </Link>
+ )}
+ </div>
+ );
+ }
+ private _renderHeader(title: string) {
+ const headerStyle = {
+ textTransform: 'uppercase',
+ color: colors.grey400,
+ letterSpacing: 2,
+ fontFamily: 'Roboto Mono',
+ fontSize: 13,
+ };
+ return (
+ <div className="lg-pb2 md-pb2 sm-pt4" style={headerStyle}>
+ {title}
+ </div>
+ );
+ }
}
diff --git a/packages/website/ts/components/generate_order/asset_picker.tsx b/packages/website/ts/components/generate_order/asset_picker.tsx
index df7d87cfd..5eed2fabf 100644
--- a/packages/website/ts/components/generate_order/asset_picker.tsx
+++ b/packages/website/ts/components/generate_order/asset_picker.tsx
@@ -13,264 +13,264 @@ import { DialogConfigs, Token, TokenByAddress, TokenState, TokenVisibility } fro
const TOKEN_ICON_DIMENSION = 100;
const TILE_DIMENSION = 146;
enum AssetViews {
- ASSET_PICKER = 'ASSET_PICKER',
- NEW_TOKEN_FORM = 'NEW_TOKEN_FORM',
- CONFIRM_TRACK_TOKEN = 'CONFIRM_TRACK_TOKEN',
+ ASSET_PICKER = 'ASSET_PICKER',
+ NEW_TOKEN_FORM = 'NEW_TOKEN_FORM',
+ CONFIRM_TRACK_TOKEN = 'CONFIRM_TRACK_TOKEN',
}
interface AssetPickerProps {
- userAddress: string;
- blockchain: Blockchain;
- dispatcher: Dispatcher;
- networkId: number;
- isOpen: boolean;
- currentTokenAddress: string;
- onTokenChosen: (tokenAddress: string) => void;
- tokenByAddress: TokenByAddress;
- tokenVisibility?: TokenVisibility;
+ userAddress: string;
+ blockchain: Blockchain;
+ dispatcher: Dispatcher;
+ networkId: number;
+ isOpen: boolean;
+ currentTokenAddress: string;
+ onTokenChosen: (tokenAddress: string) => void;
+ tokenByAddress: TokenByAddress;
+ tokenVisibility?: TokenVisibility;
}
interface AssetPickerState {
- assetView: AssetViews;
- hoveredAddress: string | undefined;
- chosenTrackTokenAddress: string;
- isAddingTokenToTracked: boolean;
+ assetView: AssetViews;
+ hoveredAddress: string | undefined;
+ chosenTrackTokenAddress: string;
+ isAddingTokenToTracked: boolean;
}
export class AssetPicker extends React.Component<AssetPickerProps, AssetPickerState> {
- public static defaultProps: Partial<AssetPickerProps> = {
- tokenVisibility: TokenVisibility.ALL,
- };
- private _dialogConfigsByAssetView: { [assetView: string]: DialogConfigs };
- constructor(props: AssetPickerProps) {
- super(props);
- this.state = {
- assetView: AssetViews.ASSET_PICKER,
- hoveredAddress: undefined,
- chosenTrackTokenAddress: undefined,
- isAddingTokenToTracked: false,
- };
- this._dialogConfigsByAssetView = {
- [AssetViews.ASSET_PICKER]: {
- title: 'Select token',
- isModal: false,
- actions: [],
- },
- [AssetViews.NEW_TOKEN_FORM]: {
- title: 'Add an ERC20 token',
- isModal: false,
- actions: [],
- },
- [AssetViews.CONFIRM_TRACK_TOKEN]: {
- title: 'Tracking confirmation',
- isModal: true,
- actions: [
- <FlatButton
- key="noTracking"
- label="No"
- onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)}
- />,
- <FlatButton
- key="yesTrack"
- label="Yes"
- onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)}
- />,
- ],
- },
- };
- }
- public render() {
- const dialogConfigs: DialogConfigs = this._dialogConfigsByAssetView[this.state.assetView];
- return (
- <Dialog
- title={dialogConfigs.title}
- titleStyle={{ fontWeight: 100 }}
- modal={dialogConfigs.isModal}
- open={this.props.isOpen}
- actions={dialogConfigs.actions}
- onRequestClose={this._onCloseDialog.bind(this)}
- >
- {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)}
- tokenByAddress={this.props.tokenByAddress}
- />
- )}
- {this.state.assetView === AssetViews.CONFIRM_TRACK_TOKEN && this._renderConfirmTrackToken()}
- </Dialog>
- );
- }
- private _renderConfirmTrackToken() {
- const token = this.props.tokenByAddress[this.state.chosenTrackTokenAddress];
- return (
- <TrackTokenConfirmation
- tokens={[token]}
- tokenByAddress={this.props.tokenByAddress}
- networkId={this.props.networkId}
- isAddingTokenToTracked={this.state.isAddingTokenToTracked}
- />
- );
- }
- private _renderAssetPicker() {
- return (
- <div
- className="clearfix flex flex-wrap"
- style={{
- overflowY: 'auto',
- maxWidth: 720,
- maxHeight: 356,
- marginBottom: 10,
- }}
- >
- {this._renderGridTiles()}
- </div>
- );
- }
- 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)
- ) {
- return null; // Skip
- }
- isHovered = this.state.hoveredAddress === address;
- tileStyles = {
- cursor: 'pointer',
- opacity: isHovered ? 0.6 : 1,
- };
- return (
- <div
- key={address}
- 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)}
- >
- <div className="p1 center">
- <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} />
- </div>
- <div className="center">{token.name}</div>
- </div>
- );
- });
- const otherTokenKey = 'otherToken';
- isHovered = this.state.hoveredAddress === otherTokenKey;
- tileStyles = {
- cursor: 'pointer',
- opacity: isHovered ? 0.6 : 1,
- };
- if (this.props.tokenVisibility !== TokenVisibility.TRACKED) {
- gridTiles.push(
- <div
- key={otherTokenKey}
- 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)}
- >
- <div className="p1 center">
- <i
- style={{ fontSize: 105, paddingLeft: 1, paddingRight: 1 }}
- className="zmdi zmdi-plus-circle"
- />
- </div>
- <div className="center">Other ERC20 Token</div>
- </div>,
- );
- }
- return gridTiles;
- }
- private _onToggleHover(address: string, isHovered: boolean) {
- const hoveredAddress = isHovered ? address : undefined;
- this.setState({
- hoveredAddress,
- });
- }
- private _onCloseDialog() {
- this.setState({
- assetView: AssetViews.ASSET_PICKER,
- });
- this.props.onTokenChosen(this.props.currentTokenAddress);
- }
- private _onChooseToken(tokenAddress: string) {
- const token = this.props.tokenByAddress[tokenAddress];
- if (token.isTracked) {
- this.props.onTokenChosen(tokenAddress);
- } else {
- this.setState({
- assetView: AssetViews.CONFIRM_TRACK_TOKEN,
- chosenTrackTokenAddress: tokenAddress,
- });
- }
- }
- private _onCustomAssetChosen() {
- this.setState({
- assetView: AssetViews.NEW_TOKEN_FORM,
- });
- }
- private _onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) {
- this.props.dispatcher.updateTokenStateByAddress({
- [newToken.address]: newTokenState,
- });
- trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newToken);
- this.props.dispatcher.addTokenToTokenByAddress(newToken);
- this.setState({
- assetView: AssetViews.ASSET_PICKER,
- });
- this.props.onTokenChosen(newToken.address);
- }
- private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) {
- if (!didUserAcceptTracking) {
- this.setState({
- isAddingTokenToTracked: false,
- assetView: AssetViews.ASSET_PICKER,
- chosenTrackTokenAddress: undefined,
- });
- this._onCloseDialog();
- return;
- }
- this.setState({
- isAddingTokenToTracked: true,
- });
- const tokenAddress = this.state.chosenTrackTokenAddress;
- const token = this.props.tokenByAddress[tokenAddress];
- const newTokenEntry = {
- ...token,
- };
+ public static defaultProps: Partial<AssetPickerProps> = {
+ tokenVisibility: TokenVisibility.ALL,
+ };
+ private _dialogConfigsByAssetView: { [assetView: string]: DialogConfigs };
+ constructor(props: AssetPickerProps) {
+ super(props);
+ this.state = {
+ assetView: AssetViews.ASSET_PICKER,
+ hoveredAddress: undefined,
+ chosenTrackTokenAddress: undefined,
+ isAddingTokenToTracked: false,
+ };
+ this._dialogConfigsByAssetView = {
+ [AssetViews.ASSET_PICKER]: {
+ title: 'Select token',
+ isModal: false,
+ actions: [],
+ },
+ [AssetViews.NEW_TOKEN_FORM]: {
+ title: 'Add an ERC20 token',
+ isModal: false,
+ actions: [],
+ },
+ [AssetViews.CONFIRM_TRACK_TOKEN]: {
+ title: 'Tracking confirmation',
+ isModal: true,
+ actions: [
+ <FlatButton
+ key="noTracking"
+ label="No"
+ onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, false)}
+ />,
+ <FlatButton
+ key="yesTrack"
+ label="Yes"
+ onTouchTap={this._onTrackConfirmationRespondedAsync.bind(this, true)}
+ />,
+ ],
+ },
+ };
+ }
+ public render() {
+ const dialogConfigs: DialogConfigs = this._dialogConfigsByAssetView[this.state.assetView];
+ return (
+ <Dialog
+ title={dialogConfigs.title}
+ titleStyle={{ fontWeight: 100 }}
+ modal={dialogConfigs.isModal}
+ open={this.props.isOpen}
+ actions={dialogConfigs.actions}
+ onRequestClose={this._onCloseDialog.bind(this)}
+ >
+ {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)}
+ tokenByAddress={this.props.tokenByAddress}
+ />
+ )}
+ {this.state.assetView === AssetViews.CONFIRM_TRACK_TOKEN && this._renderConfirmTrackToken()}
+ </Dialog>
+ );
+ }
+ private _renderConfirmTrackToken() {
+ const token = this.props.tokenByAddress[this.state.chosenTrackTokenAddress];
+ return (
+ <TrackTokenConfirmation
+ tokens={[token]}
+ tokenByAddress={this.props.tokenByAddress}
+ networkId={this.props.networkId}
+ isAddingTokenToTracked={this.state.isAddingTokenToTracked}
+ />
+ );
+ }
+ private _renderAssetPicker() {
+ return (
+ <div
+ className="clearfix flex flex-wrap"
+ style={{
+ overflowY: 'auto',
+ maxWidth: 720,
+ maxHeight: 356,
+ marginBottom: 10,
+ }}
+ >
+ {this._renderGridTiles()}
+ </div>
+ );
+ }
+ 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)
+ ) {
+ return null; // Skip
+ }
+ isHovered = this.state.hoveredAddress === address;
+ tileStyles = {
+ cursor: 'pointer',
+ opacity: isHovered ? 0.6 : 1,
+ };
+ return (
+ <div
+ key={address}
+ 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)}
+ >
+ <div className="p1 center">
+ <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} />
+ </div>
+ <div className="center">{token.name}</div>
+ </div>
+ );
+ });
+ const otherTokenKey = 'otherToken';
+ isHovered = this.state.hoveredAddress === otherTokenKey;
+ tileStyles = {
+ cursor: 'pointer',
+ opacity: isHovered ? 0.6 : 1,
+ };
+ if (this.props.tokenVisibility !== TokenVisibility.TRACKED) {
+ gridTiles.push(
+ <div
+ key={otherTokenKey}
+ 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)}
+ >
+ <div className="p1 center">
+ <i
+ style={{ fontSize: 105, paddingLeft: 1, paddingRight: 1 }}
+ className="zmdi zmdi-plus-circle"
+ />
+ </div>
+ <div className="center">Other ERC20 Token</div>
+ </div>,
+ );
+ }
+ return gridTiles;
+ }
+ private _onToggleHover(address: string, isHovered: boolean) {
+ const hoveredAddress = isHovered ? address : undefined;
+ this.setState({
+ hoveredAddress,
+ });
+ }
+ private _onCloseDialog() {
+ this.setState({
+ assetView: AssetViews.ASSET_PICKER,
+ });
+ this.props.onTokenChosen(this.props.currentTokenAddress);
+ }
+ private _onChooseToken(tokenAddress: string) {
+ const token = this.props.tokenByAddress[tokenAddress];
+ if (token.isTracked) {
+ this.props.onTokenChosen(tokenAddress);
+ } else {
+ this.setState({
+ assetView: AssetViews.CONFIRM_TRACK_TOKEN,
+ chosenTrackTokenAddress: tokenAddress,
+ });
+ }
+ }
+ private _onCustomAssetChosen() {
+ this.setState({
+ assetView: AssetViews.NEW_TOKEN_FORM,
+ });
+ }
+ private _onNewTokenSubmitted(newToken: Token, newTokenState: TokenState) {
+ this.props.dispatcher.updateTokenStateByAddress({
+ [newToken.address]: newTokenState,
+ });
+ trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newToken);
+ this.props.dispatcher.addTokenToTokenByAddress(newToken);
+ this.setState({
+ assetView: AssetViews.ASSET_PICKER,
+ });
+ this.props.onTokenChosen(newToken.address);
+ }
+ private async _onTrackConfirmationRespondedAsync(didUserAcceptTracking: boolean) {
+ if (!didUserAcceptTracking) {
+ this.setState({
+ isAddingTokenToTracked: false,
+ assetView: AssetViews.ASSET_PICKER,
+ chosenTrackTokenAddress: undefined,
+ });
+ this._onCloseDialog();
+ return;
+ }
+ this.setState({
+ isAddingTokenToTracked: true,
+ });
+ const tokenAddress = this.state.chosenTrackTokenAddress;
+ const token = this.props.tokenByAddress[tokenAddress];
+ const newTokenEntry = {
+ ...token,
+ };
- newTokenEntry.isTracked = true;
- trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry);
+ newTokenEntry.isTracked = true;
+ trackedTokenStorage.addTrackedTokenToUser(this.props.userAddress, this.props.networkId, newTokenEntry);
- const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(
- token.address,
- );
- this.props.dispatcher.updateTokenStateByAddress({
- [token.address]: {
- balance,
- allowance,
- },
- });
- this.props.dispatcher.updateTokenByAddress([newTokenEntry]);
- this.setState({
- isAddingTokenToTracked: false,
- assetView: AssetViews.ASSET_PICKER,
- chosenTrackTokenAddress: undefined,
- });
- this.props.onTokenChosen(tokenAddress);
- }
+ const [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(
+ token.address,
+ );
+ this.props.dispatcher.updateTokenStateByAddress({
+ [token.address]: {
+ balance,
+ allowance,
+ },
+ });
+ this.props.dispatcher.updateTokenByAddress([newTokenEntry]);
+ this.setState({
+ isAddingTokenToTracked: false,
+ assetView: AssetViews.ASSET_PICKER,
+ chosenTrackTokenAddress: undefined,
+ });
+ this.props.onTokenChosen(tokenAddress);
+ }
}
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 3ae0d48a7..b10b2d609 100644
--- a/packages/website/ts/components/generate_order/generate_order_form.tsx
+++ b/packages/website/ts/components/generate_order/generate_order_form.tsx
@@ -19,335 +19,335 @@ import { Dispatcher } from 'ts/redux/dispatcher';
import { orderSchema } from 'ts/schemas/order_schema';
import { SchemaValidator } from 'ts/schemas/validator';
import {
- AlertTypes,
- BlockchainErrs,
- HashData,
- Side,
- SideToAssetToken,
- SignatureData,
- Token,
- TokenByAddress,
- TokenStateByAddress,
+ AlertTypes,
+ BlockchainErrs,
+ HashData,
+ Side,
+ SideToAssetToken,
+ SignatureData,
+ Token,
+ TokenByAddress,
+ TokenStateByAddress,
} from 'ts/types';
import { colors } from 'ts/utils/colors';
import { errorReporter } from 'ts/utils/error_reporter';
import { utils } from 'ts/utils/utils';
enum SigningState {
- UNSIGNED,
- SIGNING,
- SIGNED,
+ UNSIGNED,
+ SIGNING,
+ SIGNED,
}
interface GenerateOrderFormProps {
- blockchain: Blockchain;
- blockchainErr: BlockchainErrs;
- blockchainIsLoaded: boolean;
- dispatcher: Dispatcher;
- hashData: HashData;
- orderExpiryTimestamp: BigNumber;
- networkId: number;
- userAddress: string;
- orderSignatureData: SignatureData;
- orderTakerAddress: string;
- orderSalt: BigNumber;
- sideToAssetToken: SideToAssetToken;
- tokenByAddress: TokenByAddress;
- tokenStateByAddress: TokenStateByAddress;
+ blockchain: Blockchain;
+ blockchainErr: BlockchainErrs;
+ blockchainIsLoaded: boolean;
+ dispatcher: Dispatcher;
+ hashData: HashData;
+ orderExpiryTimestamp: BigNumber;
+ networkId: number;
+ userAddress: string;
+ orderSignatureData: SignatureData;
+ orderTakerAddress: string;
+ orderSalt: BigNumber;
+ sideToAssetToken: SideToAssetToken;
+ tokenByAddress: TokenByAddress;
+ tokenStateByAddress: TokenStateByAddress;
}
interface GenerateOrderFormState {
- globalErrMsg: string;
- shouldShowIncompleteErrs: boolean;
- signingState: SigningState;
+ globalErrMsg: string;
+ shouldShowIncompleteErrs: boolean;
+ signingState: SigningState;
}
export class GenerateOrderForm extends React.Component<GenerateOrderFormProps, GenerateOrderFormState> {
- private _validator: SchemaValidator;
- constructor(props: GenerateOrderFormProps) {
- super(props);
- this.state = {
- globalErrMsg: '',
- shouldShowIncompleteErrs: false,
- signingState: SigningState.UNSIGNED,
- };
- 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 depositToken = this.props.tokenByAddress[depositTokenAddress];
- const depositTokenState = this.props.tokenStateByAddress[depositTokenAddress];
- 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> \
+ private _validator: SchemaValidator;
+ constructor(props: GenerateOrderFormProps) {
+ super(props);
+ this.state = {
+ globalErrMsg: '',
+ shouldShowIncompleteErrs: false,
+ signingState: SigningState.UNSIGNED,
+ };
+ 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 depositToken = this.props.tokenByAddress[depositTokenAddress];
+ const depositTokenState = this.props.tokenStateByAddress[depositTokenAddress];
+ 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> \
allowed to fill this order. If no taker is<br> \
specified, anyone is able to fill it.';
- const exchangeContractIfExists = this.props.blockchain.getExchangeContractAddressIfExists();
- return (
- <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="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">
- <TokenInput
- userAddress={this.props.userAddress}
- blockchain={this.props.blockchain}
- blockchainErr={this.props.blockchainErr}
- dispatcher={this.props.dispatcher}
- label="Selling"
- side={Side.Deposit}
- networkId={this.props.networkId}
- assetToken={this.props.sideToAssetToken[Side.Deposit]}
- updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)}
- tokenByAddress={this.props.tokenByAddress}
- />
- <TokenAmountInput
- label="Sell amount"
- token={depositToken}
- tokenState={depositTokenState}
- amount={this.props.sideToAssetToken[Side.Deposit].amount}
- onChange={this._onTokenAmountChange.bind(this, depositToken, Side.Deposit)}
- shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
- shouldCheckBalance={true}
- shouldCheckAllowance={true}
- />
- </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)} />
- </div>
- </div>
- <div className="lg-col md-col lg-col-5 md-col-5 sm-col sm-col-5 sm-pb2">
- <TokenInput
- userAddress={this.props.userAddress}
- blockchain={this.props.blockchain}
- blockchainErr={this.props.blockchainErr}
- dispatcher={this.props.dispatcher}
- label="Buying"
- side={Side.Receive}
- networkId={this.props.networkId}
- assetToken={this.props.sideToAssetToken[Side.Receive]}
- updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)}
- tokenByAddress={this.props.tokenByAddress}
- />
- <TokenAmountInput
- label="Receive amount"
- token={receiveToken}
- tokenState={receiveTokenState}
- amount={this.props.sideToAssetToken[Side.Receive].amount}
- onChange={this._onTokenAmountChange.bind(this, receiveToken, Side.Receive)}
- shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
- shouldCheckBalance={false}
- shouldCheckAllowance={false}
- />
- </div>
- </div>
- </div>
- <div className="pt1 sm-pb2 lg-px4 md-px4">
- <div className="lg-px3 md-px3">
- <div style={{ fontSize: 12, color: colors.grey }}>Expiration</div>
- <ExpirationInput
- orderExpiryTimestamp={this.props.orderExpiryTimestamp}
- updateOrderExpiry={dispatcher.updateOrderExpiry.bind(dispatcher)}
- />
- </div>
- </div>
- <div className="pt1 flex mx-auto">
- <IdenticonAddressInput
- label="Taker"
- initialAddress={this.props.orderTakerAddress}
- updateOrderAddress={this._updateOrderAddress.bind(this)}
- />
- <div className="pt3">
- <div className="pl1">
- <HelpTooltip explanation={takerExplanation} />
- </div>
- </div>
- </div>
- <div>
- <HashInput
- blockchain={this.props.blockchain}
- blockchainIsLoaded={this.props.blockchainIsLoaded}
- hashData={this.props.hashData}
- label="Order Hash"
- />
- </div>
- <div className="pt2">
- <div className="center">
- <LifeCycleRaisedButton
- labelReady="Sign hash"
- labelLoading="Signing..."
- labelComplete="Hash signed!"
- onClickAsyncFn={this._onSignClickedAsync.bind(this)}
- />
- </div>
- {this.state.globalErrMsg !== '' && (
- <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} />
- )}
- </div>
- </div>
- <Dialog
- title="Order JSON"
- titleStyle={{ fontWeight: 100 }}
- modal={false}
- open={this.state.signingState === SigningState.SIGNED}
- onRequestClose={this._onCloseOrderJSONDialog.bind(this)}
- >
- <OrderJSON
- exchangeContractIfExists={exchangeContractIfExists}
- orderExpiryTimestamp={this.props.orderExpiryTimestamp}
- orderSignatureData={this.props.orderSignatureData}
- orderTakerAddress={this.props.orderTakerAddress}
- orderMakerAddress={this.props.userAddress}
- orderSalt={this.props.orderSalt}
- orderMakerFee={this.props.hashData.makerFee}
- orderTakerFee={this.props.hashData.takerFee}
- orderFeeRecipient={this.props.hashData.feeRecipientAddress}
- networkId={this.props.networkId}
- sideToAssetToken={this.props.sideToAssetToken}
- tokenByAddress={this.props.tokenByAddress}
- />
- </Dialog>
- </div>
- );
- }
- private _onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber) {
- this.props.dispatcher.updateChosenAssetToken(side, {
- address: token.address,
- amount,
- });
- }
- 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.
- this.props.dispatcher.updateOrderSalt(ZeroEx.generatePseudoRandomSalt());
- this.setState({
- signingState: SigningState.UNSIGNED,
- });
- }
- private async _onSignClickedAsync(): Promise<boolean> {
- if (this.props.blockchainErr !== BlockchainErrs.NoError) {
- this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
- return false;
- }
+ const exchangeContractIfExists = this.props.blockchain.getExchangeContractAddressIfExists();
+ return (
+ <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="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">
+ <TokenInput
+ userAddress={this.props.userAddress}
+ blockchain={this.props.blockchain}
+ blockchainErr={this.props.blockchainErr}
+ dispatcher={this.props.dispatcher}
+ label="Selling"
+ side={Side.Deposit}
+ networkId={this.props.networkId}
+ assetToken={this.props.sideToAssetToken[Side.Deposit]}
+ updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)}
+ tokenByAddress={this.props.tokenByAddress}
+ />
+ <TokenAmountInput
+ label="Sell amount"
+ token={depositToken}
+ tokenState={depositTokenState}
+ amount={this.props.sideToAssetToken[Side.Deposit].amount}
+ onChange={this._onTokenAmountChange.bind(this, depositToken, Side.Deposit)}
+ shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
+ shouldCheckBalance={true}
+ shouldCheckAllowance={true}
+ />
+ </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)} />
+ </div>
+ </div>
+ <div className="lg-col md-col lg-col-5 md-col-5 sm-col sm-col-5 sm-pb2">
+ <TokenInput
+ userAddress={this.props.userAddress}
+ blockchain={this.props.blockchain}
+ blockchainErr={this.props.blockchainErr}
+ dispatcher={this.props.dispatcher}
+ label="Buying"
+ side={Side.Receive}
+ networkId={this.props.networkId}
+ assetToken={this.props.sideToAssetToken[Side.Receive]}
+ updateChosenAssetToken={dispatcher.updateChosenAssetToken.bind(dispatcher)}
+ tokenByAddress={this.props.tokenByAddress}
+ />
+ <TokenAmountInput
+ label="Receive amount"
+ token={receiveToken}
+ tokenState={receiveTokenState}
+ amount={this.props.sideToAssetToken[Side.Receive].amount}
+ onChange={this._onTokenAmountChange.bind(this, receiveToken, Side.Receive)}
+ shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
+ shouldCheckBalance={false}
+ shouldCheckAllowance={false}
+ />
+ </div>
+ </div>
+ </div>
+ <div className="pt1 sm-pb2 lg-px4 md-px4">
+ <div className="lg-px3 md-px3">
+ <div style={{ fontSize: 12, color: colors.grey }}>Expiration</div>
+ <ExpirationInput
+ orderExpiryTimestamp={this.props.orderExpiryTimestamp}
+ updateOrderExpiry={dispatcher.updateOrderExpiry.bind(dispatcher)}
+ />
+ </div>
+ </div>
+ <div className="pt1 flex mx-auto">
+ <IdenticonAddressInput
+ label="Taker"
+ initialAddress={this.props.orderTakerAddress}
+ updateOrderAddress={this._updateOrderAddress.bind(this)}
+ />
+ <div className="pt3">
+ <div className="pl1">
+ <HelpTooltip explanation={takerExplanation} />
+ </div>
+ </div>
+ </div>
+ <div>
+ <HashInput
+ blockchain={this.props.blockchain}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
+ hashData={this.props.hashData}
+ label="Order Hash"
+ />
+ </div>
+ <div className="pt2">
+ <div className="center">
+ <LifeCycleRaisedButton
+ labelReady="Sign hash"
+ labelLoading="Signing..."
+ labelComplete="Hash signed!"
+ onClickAsyncFn={this._onSignClickedAsync.bind(this)}
+ />
+ </div>
+ {this.state.globalErrMsg !== '' && (
+ <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} />
+ )}
+ </div>
+ </div>
+ <Dialog
+ title="Order JSON"
+ titleStyle={{ fontWeight: 100 }}
+ modal={false}
+ open={this.state.signingState === SigningState.SIGNED}
+ onRequestClose={this._onCloseOrderJSONDialog.bind(this)}
+ >
+ <OrderJSON
+ exchangeContractIfExists={exchangeContractIfExists}
+ orderExpiryTimestamp={this.props.orderExpiryTimestamp}
+ orderSignatureData={this.props.orderSignatureData}
+ orderTakerAddress={this.props.orderTakerAddress}
+ orderMakerAddress={this.props.userAddress}
+ orderSalt={this.props.orderSalt}
+ orderMakerFee={this.props.hashData.makerFee}
+ orderTakerFee={this.props.hashData.takerFee}
+ orderFeeRecipient={this.props.hashData.feeRecipientAddress}
+ networkId={this.props.networkId}
+ sideToAssetToken={this.props.sideToAssetToken}
+ tokenByAddress={this.props.tokenByAddress}
+ />
+ </Dialog>
+ </div>
+ );
+ }
+ private _onTokenAmountChange(token: Token, side: Side, isValid: boolean, amount?: BigNumber) {
+ this.props.dispatcher.updateChosenAssetToken(side, {
+ address: token.address,
+ amount,
+ });
+ }
+ 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.
+ this.props.dispatcher.updateOrderSalt(ZeroEx.generatePseudoRandomSalt());
+ this.setState({
+ signingState: SigningState.UNSIGNED,
+ });
+ }
+ 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 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) &&
- this.props.userAddress !== '' &&
- debitBalance.gte(debitToken.amount) &&
- debitAllowance.gte(debitToken.amount)
- ) {
- const didSignSuccessfully = await this._signTransactionAsync();
- if (didSignSuccessfully) {
- this.setState({
- globalErrMsg: '',
- shouldShowIncompleteErrs: false,
- });
- }
- return didSignSuccessfully;
- } else {
- let globalErrMsg = 'You must fix the above errors in order to generate a valid order';
- if (this.props.userAddress === '') {
- globalErrMsg = 'You must enable wallet communication';
- this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
- }
- this.setState({
- globalErrMsg,
- shouldShowIncompleteErrs: true,
- });
- return false;
- }
- }
- private async _signTransactionAsync(): Promise<boolean> {
- this.setState({
- signingState: SigningState.SIGNING,
- });
- const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists();
- if (_.isUndefined(exchangeContractAddr)) {
- this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
- this.setState({
- signingState: SigningState.UNSIGNED,
- });
- return false;
- }
- const hashData = this.props.hashData;
+ // Check if all required inputs were supplied
+ 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) &&
+ this.props.userAddress !== '' &&
+ debitBalance.gte(debitToken.amount) &&
+ debitAllowance.gte(debitToken.amount)
+ ) {
+ const didSignSuccessfully = await this._signTransactionAsync();
+ if (didSignSuccessfully) {
+ this.setState({
+ globalErrMsg: '',
+ shouldShowIncompleteErrs: false,
+ });
+ }
+ return didSignSuccessfully;
+ } else {
+ let globalErrMsg = 'You must fix the above errors in order to generate a valid order';
+ if (this.props.userAddress === '') {
+ globalErrMsg = 'You must enable wallet communication';
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ }
+ this.setState({
+ globalErrMsg,
+ shouldShowIncompleteErrs: true,
+ });
+ return false;
+ }
+ }
+ private async _signTransactionAsync(): Promise<boolean> {
+ this.setState({
+ signingState: SigningState.SIGNING,
+ });
+ const exchangeContractAddr = this.props.blockchain.getExchangeContractAddressIfExists();
+ if (_.isUndefined(exchangeContractAddr)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ this.setState({
+ signingState: SigningState.UNSIGNED,
+ });
+ return false;
+ }
+ const hashData = this.props.hashData;
- const zeroExOrder: Order = {
- exchangeContractAddress: exchangeContractAddr,
- expirationUnixTimestampSec: hashData.orderExpiryTimestamp,
- feeRecipient: hashData.feeRecipientAddress,
- maker: hashData.orderMakerAddress,
- makerFee: hashData.makerFee,
- makerTokenAddress: hashData.depositTokenContractAddr,
- makerTokenAmount: hashData.depositAmount,
- salt: hashData.orderSalt,
- taker: hashData.orderTakerAddress,
- takerFee: hashData.takerFee,
- takerTokenAddress: hashData.receiveTokenContractAddr,
- takerTokenAmount: hashData.receiveAmount,
- };
- const orderHash = ZeroEx.getOrderHashHex(zeroExOrder);
+ const zeroExOrder: Order = {
+ exchangeContractAddress: exchangeContractAddr,
+ expirationUnixTimestampSec: hashData.orderExpiryTimestamp,
+ feeRecipient: hashData.feeRecipientAddress,
+ maker: hashData.orderMakerAddress,
+ makerFee: hashData.makerFee,
+ makerTokenAddress: hashData.depositTokenContractAddr,
+ makerTokenAmount: hashData.depositAmount,
+ salt: hashData.orderSalt,
+ taker: hashData.orderTakerAddress,
+ takerFee: hashData.takerFee,
+ takerTokenAddress: hashData.receiveTokenContractAddr,
+ takerTokenAmount: hashData.receiveAmount,
+ };
+ const orderHash = ZeroEx.getOrderHashHex(zeroExOrder);
- 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);
- if (validationResult.errors.length > 0) {
- globalErrMsg = 'Order signing failed. Please refresh and try again';
- utils.consoleLog(`Unexpected error occured: Order validation failed:
+ 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);
+ 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}`;
- if (utils.didUserDenyWeb3Request(errMsg)) {
- globalErrMsg = 'User denied sign request';
- } else {
- globalErrMsg = 'An unexpected error occured. Please try refreshing the page';
- utils.consoleLog(`Unexpected error occured: ${err}`);
- utils.consoleLog(err.stack);
- await errorReporter.reportAsync(err);
- }
- }
- this.setState({
- signingState: globalErrMsg === '' ? SigningState.SIGNED : SigningState.UNSIGNED,
- globalErrMsg,
- });
- return globalErrMsg === '';
- }
- private _updateOrderAddress(address?: string): void {
- if (!_.isUndefined(address)) {
- this.props.dispatcher.updateOrderTakerAddress(address);
- }
- }
+ }
+ } catch (err) {
+ const errMsg = `${err}`;
+ if (utils.didUserDenyWeb3Request(errMsg)) {
+ globalErrMsg = 'User denied sign request';
+ } else {
+ globalErrMsg = 'An unexpected error occured. Please try refreshing the page';
+ utils.consoleLog(`Unexpected error occured: ${err}`);
+ utils.consoleLog(err.stack);
+ await errorReporter.reportAsync(err);
+ }
+ }
+ this.setState({
+ signingState: globalErrMsg === '' ? SigningState.SIGNED : SigningState.UNSIGNED,
+ globalErrMsg,
+ });
+ return globalErrMsg === '';
+ }
+ 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 63645be9a..d61aac92a 100644
--- a/packages/website/ts/components/generate_order/new_token_form.tsx
+++ b/packages/website/ts/components/generate_order/new_token_form.tsx
@@ -11,227 +11,227 @@ import { AlertTypes, Token, TokenByAddress, TokenState } from 'ts/types';
import { colors } from 'ts/utils/colors';
interface NewTokenFormProps {
- blockchain: Blockchain;
- tokenByAddress: TokenByAddress;
- onNewTokenSubmitted: (token: Token, tokenState: TokenState) => void;
+ blockchain: Blockchain;
+ tokenByAddress: TokenByAddress;
+ onNewTokenSubmitted: (token: Token, tokenState: TokenState) => void;
}
interface NewTokenFormState {
- globalErrMsg: string;
- name: string;
- nameErrText: string;
- symbol: string;
- symbolErrText: string;
- address: string;
- shouldShowAddressIncompleteErr: boolean;
- decimals: string;
- decimalsErrText: string;
+ globalErrMsg: string;
+ name: string;
+ nameErrText: string;
+ symbol: string;
+ symbolErrText: string;
+ address: string;
+ shouldShowAddressIncompleteErr: boolean;
+ decimals: string;
+ decimalsErrText: string;
}
export class NewTokenForm extends React.Component<NewTokenFormProps, NewTokenFormState> {
- constructor(props: NewTokenFormProps) {
- super(props);
- this.state = {
- address: '',
- globalErrMsg: '',
- name: '',
- nameErrText: '',
- shouldShowAddressIncompleteErr: false,
- symbol: '',
- symbolErrText: '',
- decimals: '18',
- decimalsErrText: '',
- };
- }
- public render() {
- return (
- <div className="mx-auto pb2" style={{ width: 256 }}>
- <div>
- <TextField
- floatingLabelFixed={true}
- floatingLabelStyle={{ color: colors.grey }}
- floatingLabelText={<RequiredLabel label="Name" />}
- value={this.state.name}
- errorText={this.state.nameErrText}
- onChange={this._onTokenNameChanged.bind(this)}
- />
- </div>
- <div>
- <TextField
- floatingLabelFixed={true}
- floatingLabelStyle={{ color: colors.grey }}
- floatingLabelText={<RequiredLabel label="Symbol" />}
- value={this.state.symbol}
- errorText={this.state.symbolErrText}
- onChange={this._onTokenSymbolChanged.bind(this)}
- />
- </div>
- <div>
- <AddressInput
- isRequired={true}
- label="Contract address"
- initialAddress=""
- shouldShowIncompleteErrs={this.state.shouldShowAddressIncompleteErr}
- updateAddress={this._onTokenAddressChanged.bind(this)}
- />
- </div>
- <div>
- <TextField
- floatingLabelFixed={true}
- floatingLabelStyle={{ color: colors.grey }}
- floatingLabelText={<RequiredLabel label="Decimals" />}
- value={this.state.decimals}
- errorText={this.state.decimalsErrText}
- onChange={this._onTokenDecimalsChanged.bind(this)}
- />
- </div>
- <div className="pt2 mx-auto" style={{ width: 120 }}>
- <LifeCycleRaisedButton
- labelReady="Add"
- labelLoading="Adding..."
- labelComplete="Added!"
- onClickAsyncFn={this._onAddNewTokenClickAsync.bind(this)}
- />
- </div>
- {this.state.globalErrMsg !== '' && <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} />}
- </div>
- );
- }
- 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);
+ constructor(props: NewTokenFormProps) {
+ super(props);
+ this.state = {
+ address: '',
+ globalErrMsg: '',
+ name: '',
+ nameErrText: '',
+ shouldShowAddressIncompleteErr: false,
+ symbol: '',
+ symbolErrText: '',
+ decimals: '18',
+ decimalsErrText: '',
+ };
+ }
+ public render() {
+ return (
+ <div className="mx-auto pb2" style={{ width: 256 }}>
+ <div>
+ <TextField
+ floatingLabelFixed={true}
+ floatingLabelStyle={{ color: colors.grey }}
+ floatingLabelText={<RequiredLabel label="Name" />}
+ value={this.state.name}
+ errorText={this.state.nameErrText}
+ onChange={this._onTokenNameChanged.bind(this)}
+ />
+ </div>
+ <div>
+ <TextField
+ floatingLabelFixed={true}
+ floatingLabelStyle={{ color: colors.grey }}
+ floatingLabelText={<RequiredLabel label="Symbol" />}
+ value={this.state.symbol}
+ errorText={this.state.symbolErrText}
+ onChange={this._onTokenSymbolChanged.bind(this)}
+ />
+ </div>
+ <div>
+ <AddressInput
+ isRequired={true}
+ label="Contract address"
+ initialAddress=""
+ shouldShowIncompleteErrs={this.state.shouldShowAddressIncompleteErr}
+ updateAddress={this._onTokenAddressChanged.bind(this)}
+ />
+ </div>
+ <div>
+ <TextField
+ floatingLabelFixed={true}
+ floatingLabelStyle={{ color: colors.grey }}
+ floatingLabelText={<RequiredLabel label="Decimals" />}
+ value={this.state.decimals}
+ errorText={this.state.decimalsErrText}
+ onChange={this._onTokenDecimalsChanged.bind(this)}
+ />
+ </div>
+ <div className="pt2 mx-auto" style={{ width: 120 }}>
+ <LifeCycleRaisedButton
+ labelReady="Add"
+ labelLoading="Adding..."
+ labelComplete="Added!"
+ onClickAsyncFn={this._onAddNewTokenClickAsync.bind(this)}
+ />
+ </div>
+ {this.state.globalErrMsg !== '' && <Alert type={AlertTypes.ERROR} message={this.state.globalErrMsg} />}
+ </div>
+ );
+ }
+ 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);
- const isAddressIncomplete = this.state.address === '';
- let doesContractExist = false;
- if (!isAddressIncomplete) {
- doesContractExist = await this.props.blockchain.doesContractExistAtAddressAsync(this.state.address);
- }
+ const isAddressIncomplete = this.state.address === '';
+ let doesContractExist = false;
+ if (!isAddressIncomplete) {
+ doesContractExist = await this.props.blockchain.doesContractExistAtAddressAsync(this.state.address);
+ }
- let hasBalanceAllowanceErr = false;
- let balance = new BigNumber(0);
- let allowance = new BigNumber(0);
- if (doesContractExist) {
- try {
- [balance, allowance] = await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(
- this.state.address,
- );
- } catch (err) {
- hasBalanceAllowanceErr = true;
- }
- }
+ let hasBalanceAllowanceErr = false;
+ let balance = new BigNumber(0);
+ let allowance = new BigNumber(0);
+ if (doesContractExist) {
+ try {
+ [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
- ) {
- globalErrMsg = 'Please fix the above issues';
- } else if (!doesContractExist) {
- globalErrMsg = 'No contract found at supplied address';
- } else if (hasBalanceAllowanceErr) {
- globalErrMsg = 'Unsuccessful call to `balanceOf` and/or `allowance` on supplied contract address';
- } else if (!isAddressIncomplete && !_.isUndefined(this.props.tokenByAddress[this.state.address])) {
- globalErrMsg = 'A token already exists with this address';
- }
+ let globalErrMsg = '';
+ 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';
+ } else if (hasBalanceAllowanceErr) {
+ globalErrMsg = 'Unsuccessful call to `balanceOf` and/or `allowance` on supplied contract address';
+ } else if (!isAddressIncomplete && !_.isUndefined(this.props.tokenByAddress[this.state.address])) {
+ globalErrMsg = 'A token already exists with this address';
+ }
- if (globalErrMsg !== '') {
- this.setState({
- globalErrMsg,
- shouldShowAddressIncompleteErr: isAddressIncomplete,
- });
- return;
- }
+ if (globalErrMsg !== '') {
+ this.setState({
+ globalErrMsg,
+ shouldShowAddressIncompleteErr: isAddressIncomplete,
+ });
+ return;
+ }
- const newToken: Token = {
- address: this.state.address,
- decimals: _.parseInt(this.state.decimals),
- iconUrl: undefined,
- name: this.state.name,
- symbol: this.state.symbol.toUpperCase(),
- isTracked: true,
- isRegistered: false,
- };
- const newTokenState: TokenState = {
- balance,
- allowance,
- };
- this.props.onNewTokenSubmitted(newToken, newTokenState);
- }
- private _onTokenNameChanged(e: any, name: string) {
- let nameErrText = '';
- const maxLength = 30;
- const tokens = _.values(this.props.tokenByAddress);
- const tokenWithNameIfExists = _.find(tokens, { name });
- const tokenWithNameExists = !_.isUndefined(tokenWithNameIfExists);
- if (name === '') {
- nameErrText = 'Name is required';
- } else if (!this._isValidName(name)) {
- nameErrText = 'Name should only contain letters, digits and spaces';
- } else if (name.length > maxLength) {
- nameErrText = `Max length is ${maxLength}`;
- } else if (tokenWithNameExists) {
- nameErrText = 'Token with this name already exists';
- }
+ const newToken: Token = {
+ address: this.state.address,
+ decimals: _.parseInt(this.state.decimals),
+ iconUrl: undefined,
+ name: this.state.name,
+ symbol: this.state.symbol.toUpperCase(),
+ isTracked: true,
+ isRegistered: false,
+ };
+ const newTokenState: TokenState = {
+ balance,
+ allowance,
+ };
+ this.props.onNewTokenSubmitted(newToken, newTokenState);
+ }
+ private _onTokenNameChanged(e: any, name: string) {
+ let nameErrText = '';
+ const maxLength = 30;
+ const tokens = _.values(this.props.tokenByAddress);
+ const tokenWithNameIfExists = _.find(tokens, { name });
+ const tokenWithNameExists = !_.isUndefined(tokenWithNameIfExists);
+ if (name === '') {
+ nameErrText = 'Name is required';
+ } else if (!this._isValidName(name)) {
+ nameErrText = 'Name should only contain letters, digits and spaces';
+ } else if (name.length > maxLength) {
+ nameErrText = `Max length is ${maxLength}`;
+ } else if (tokenWithNameExists) {
+ nameErrText = 'Token with this name already exists';
+ }
- this.setState({
- name,
- nameErrText,
- });
- }
- private _onTokenSymbolChanged(e: any, symbol: string) {
- let symbolErrText = '';
- const maxLength = 5;
- const tokens = _.values(this.props.tokenByAddress);
- const tokenWithSymbolExists = !_.isUndefined(_.find(tokens, { symbol }));
- if (symbol === '') {
- symbolErrText = 'Symbol is required';
- } else if (!this._isAlphanumeric(symbol)) {
- symbolErrText = 'Can only include alphanumeric characters';
- } else if (symbol.length > maxLength) {
- symbolErrText = `Max length is ${maxLength}`;
- } else if (tokenWithSymbolExists) {
- symbolErrText = 'Token with symbol already exists';
- }
+ this.setState({
+ name,
+ nameErrText,
+ });
+ }
+ private _onTokenSymbolChanged(e: any, symbol: string) {
+ let symbolErrText = '';
+ const maxLength = 5;
+ const tokens = _.values(this.props.tokenByAddress);
+ const tokenWithSymbolExists = !_.isUndefined(_.find(tokens, { symbol }));
+ if (symbol === '') {
+ symbolErrText = 'Symbol is required';
+ } else if (!this._isAlphanumeric(symbol)) {
+ symbolErrText = 'Can only include alphanumeric characters';
+ } else if (symbol.length > maxLength) {
+ symbolErrText = `Max length is ${maxLength}`;
+ } else if (tokenWithSymbolExists) {
+ symbolErrText = 'Token with symbol already exists';
+ }
- this.setState({
- symbol,
- symbolErrText,
- });
- }
- private _onTokenDecimalsChanged(e: any, decimals: string) {
- let decimalsErrText = '';
- const maxLength = 2;
- if (decimals === '') {
- decimalsErrText = 'Decimals is required';
- } else if (!this._isInteger(decimals)) {
- decimalsErrText = 'Must be an integer';
- } else if (decimals.length > maxLength) {
- decimalsErrText = `Max length is ${maxLength}`;
- }
+ this.setState({
+ symbol,
+ symbolErrText,
+ });
+ }
+ private _onTokenDecimalsChanged(e: any, decimals: string) {
+ let decimalsErrText = '';
+ const maxLength = 2;
+ if (decimals === '') {
+ decimalsErrText = 'Decimals is required';
+ } else if (!this._isInteger(decimals)) {
+ decimalsErrText = 'Must be an integer';
+ } else if (decimals.length > maxLength) {
+ decimalsErrText = `Max length is ${maxLength}`;
+ }
- this.setState({
- decimals,
- decimalsErrText,
- });
- }
- private _onTokenAddressChanged(address?: string) {
- if (!_.isUndefined(address)) {
- this.setState({
- address,
- });
- }
- }
- private _isValidName(input: string) {
- return /^[a-z0-9 ]+$/i.test(input);
- }
- private _isInteger(input: string) {
- return /^[0-9]+$/i.test(input);
- }
- private _isAlphanumeric(input: string) {
- return /^[a-zA-Z0-9]+$/i.test(input);
- }
+ this.setState({
+ decimals,
+ decimalsErrText,
+ });
+ }
+ private _onTokenAddressChanged(address?: string) {
+ if (!_.isUndefined(address)) {
+ this.setState({
+ address,
+ });
+ }
+ }
+ private _isValidName(input: string) {
+ return /^[a-z0-9 ]+$/i.test(input);
+ }
+ private _isInteger(input: string) {
+ return /^[0-9]+$/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 dd4131140..236bf9a00 100644
--- a/packages/website/ts/components/inputs/address_input.tsx
+++ b/packages/website/ts/components/inputs/address_input.tsx
@@ -6,66 +6,66 @@ import { RequiredLabel } from 'ts/components/ui/required_label';
import { colors } from 'ts/utils/colors';
interface AddressInputProps {
- disabled?: boolean;
- initialAddress: string;
- isRequired?: boolean;
- hintText?: string;
- shouldHideLabel?: boolean;
- label?: string;
- shouldShowIncompleteErrs?: boolean;
- updateAddress: (address?: string) => void;
+ disabled?: boolean;
+ initialAddress: string;
+ isRequired?: boolean;
+ hintText?: string;
+ shouldHideLabel?: boolean;
+ label?: string;
+ shouldShowIncompleteErrs?: boolean;
+ updateAddress: (address?: string) => void;
}
interface AddressInputState {
- address: string;
- errMsg: string;
+ address: string;
+ errMsg: string;
}
export class AddressInput extends React.Component<AddressInputProps, AddressInputState> {
- constructor(props: AddressInputProps) {
- super(props);
- this.state = {
- address: this.props.initialAddress,
- errMsg: '',
- };
- }
- public componentWillReceiveProps(nextProps: AddressInputProps) {
- 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 labelDisplay = this.props.shouldHideLabel ? 'hidden' : 'block';
- const hintText = this.props.hintText ? this.props.hintText : '';
- return (
- <div className="overflow-hidden">
- <TextField
- id={`address-field-${this.props.label}`}
- disabled={_.isUndefined(this.props.disabled) ? false : this.props.disabled}
- fullWidth={true}
- hintText={hintText}
- floatingLabelFixed={true}
- floatingLabelStyle={{ color: colors.grey, display: labelDisplay }}
- floatingLabelText={label}
- errorText={this.state.errMsg}
- value={this.state.address}
- onChange={this._onOrderTakerAddressUpdated.bind(this)}
- />
- </div>
- );
- }
- private _onOrderTakerAddressUpdated(e: any) {
- const address = e.target.value.toLowerCase();
- const isValidAddress = addressUtils.isAddress(address) || address === '';
- const errMsg = isValidAddress ? '' : 'Invalid ethereum address';
- this.setState({
- address,
- errMsg,
- });
- const addressIfValid = isValidAddress ? address : undefined;
- this.props.updateAddress(addressIfValid);
- }
+ constructor(props: AddressInputProps) {
+ super(props);
+ this.state = {
+ address: this.props.initialAddress,
+ errMsg: '',
+ };
+ }
+ public componentWillReceiveProps(nextProps: AddressInputProps) {
+ 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 labelDisplay = this.props.shouldHideLabel ? 'hidden' : 'block';
+ const hintText = this.props.hintText ? this.props.hintText : '';
+ return (
+ <div className="overflow-hidden">
+ <TextField
+ id={`address-field-${this.props.label}`}
+ disabled={_.isUndefined(this.props.disabled) ? false : this.props.disabled}
+ fullWidth={true}
+ hintText={hintText}
+ floatingLabelFixed={true}
+ floatingLabelStyle={{ color: colors.grey, display: labelDisplay }}
+ floatingLabelText={label}
+ errorText={this.state.errMsg}
+ value={this.state.address}
+ onChange={this._onOrderTakerAddressUpdated.bind(this)}
+ />
+ </div>
+ );
+ }
+ private _onOrderTakerAddressUpdated(e: any) {
+ const address = e.target.value.toLowerCase();
+ const isValidAddress = addressUtils.isAddress(address) || address === '';
+ const errMsg = isValidAddress ? '' : 'Invalid ethereum address';
+ this.setState({
+ address,
+ errMsg,
+ });
+ const addressIfValid = isValidAddress ? address : undefined;
+ this.props.updateAddress(addressIfValid);
+ }
}
diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx
index da46db4f4..245784824 100644
--- a/packages/website/ts/components/inputs/allowance_toggle.tsx
+++ b/packages/website/ts/components/inputs/allowance_toggle.tsx
@@ -11,83 +11,83 @@ import { utils } from 'ts/utils/utils';
const DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1);
interface AllowanceToggleProps {
- blockchain: Blockchain;
- dispatcher: Dispatcher;
- onErrorOccurred: (errType: BalanceErrs) => void;
- token: Token;
- tokenState: TokenState;
- userAddress: string;
+ blockchain: Blockchain;
+ dispatcher: Dispatcher;
+ onErrorOccurred: (errType: BalanceErrs) => void;
+ token: Token;
+ tokenState: TokenState;
+ userAddress: string;
}
interface AllowanceToggleState {
- isSpinnerVisible: boolean;
- prevAllowance: BigNumber;
+ isSpinnerVisible: boolean;
+ prevAllowance: BigNumber;
}
export class AllowanceToggle extends React.Component<AllowanceToggleProps, AllowanceToggleState> {
- constructor(props: AllowanceToggleProps) {
- super(props);
- this.state = {
- isSpinnerVisible: false,
- prevAllowance: props.tokenState.allowance,
- };
- }
- public componentWillReceiveProps(nextProps: AllowanceToggleProps) {
- if (!nextProps.tokenState.allowance.eq(this.state.prevAllowance)) {
- this.setState({
- isSpinnerVisible: false,
- prevAllowance: nextProps.tokenState.allowance,
- });
- }
- }
- public render() {
- return (
- <div className="flex">
- <div>
- <Toggle
- disabled={this.state.isSpinnerVisible}
- toggled={this._isAllowanceSet()}
- onToggle={this._onToggleAllowanceAsync.bind(this)}
- />
- </div>
- {this.state.isSpinnerVisible && (
- <div className="pl1" style={{ paddingTop: 3 }}>
- <i className="zmdi zmdi-spinner zmdi-hc-spin" />
- </div>
- )}
- </div>
- );
- }
- private async _onToggleAllowanceAsync(): Promise<void> {
- if (this.props.userAddress === '') {
- this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
- }
+ constructor(props: AllowanceToggleProps) {
+ super(props);
+ this.state = {
+ isSpinnerVisible: false,
+ prevAllowance: props.tokenState.allowance,
+ };
+ }
+ public componentWillReceiveProps(nextProps: AllowanceToggleProps) {
+ if (!nextProps.tokenState.allowance.eq(this.state.prevAllowance)) {
+ this.setState({
+ isSpinnerVisible: false,
+ prevAllowance: nextProps.tokenState.allowance,
+ });
+ }
+ }
+ public render() {
+ return (
+ <div className="flex">
+ <div>
+ <Toggle
+ disabled={this.state.isSpinnerVisible}
+ toggled={this._isAllowanceSet()}
+ onToggle={this._onToggleAllowanceAsync.bind(this)}
+ />
+ </div>
+ {this.state.isSpinnerVisible && (
+ <div className="pl1" style={{ paddingTop: 3 }}>
+ <i className="zmdi zmdi-spinner zmdi-hc-spin" />
+ </div>
+ )}
+ </div>
+ );
+ }
+ private async _onToggleAllowanceAsync(): Promise<void> {
+ if (this.props.userAddress === '') {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ }
- this.setState({
- isSpinnerVisible: true,
- });
+ this.setState({
+ isSpinnerVisible: true,
+ });
- let newAllowanceAmountInBaseUnits = new BigNumber(0);
- if (!this._isAllowanceSet()) {
- newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS;
- }
- try {
- await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits);
- } catch (err) {
- this.setState({
- isSpinnerVisible: false,
- });
- const errMsg = `${err}`;
- if (_.includes(errMsg, 'User denied transaction')) {
- return;
- }
- utils.consoleLog(`Unexpected error encountered: ${err}`);
- utils.consoleLog(err.stack);
- this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed);
- await errorReporter.reportAsync(err);
- }
- }
- private _isAllowanceSet() {
- return !this.props.tokenState.allowance.eq(0);
- }
+ let newAllowanceAmountInBaseUnits = new BigNumber(0);
+ if (!this._isAllowanceSet()) {
+ newAllowanceAmountInBaseUnits = DEFAULT_ALLOWANCE_AMOUNT_IN_BASE_UNITS;
+ }
+ try {
+ await this.props.blockchain.setProxyAllowanceAsync(this.props.token, newAllowanceAmountInBaseUnits);
+ } catch (err) {
+ this.setState({
+ isSpinnerVisible: false,
+ });
+ const errMsg = `${err}`;
+ if (_.includes(errMsg, 'User denied transaction')) {
+ return;
+ }
+ utils.consoleLog(`Unexpected error encountered: ${err}`);
+ utils.consoleLog(err.stack);
+ this.props.onErrorOccurred(BalanceErrs.allowanceSettingFailed);
+ await errorReporter.reportAsync(err);
+ }
+ }
+ 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 ddc434b51..5cc91994e 100644
--- a/packages/website/ts/components/inputs/balance_bounded_input.tsx
+++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx
@@ -9,143 +9,143 @@ import { colors } from 'ts/utils/colors';
import { utils } from 'ts/utils/utils';
interface BalanceBoundedInputProps {
- label?: string;
- balance: BigNumber;
- amount?: BigNumber;
- onChange: ValidatedBigNumberCallback;
- shouldShowIncompleteErrs?: boolean;
- shouldCheckBalance: boolean;
- validate?: (amount: BigNumber) => InputErrMsg;
- onVisitBalancesPageClick?: () => void;
- shouldHideVisitBalancesLink?: boolean;
+ label?: string;
+ balance: BigNumber;
+ amount?: BigNumber;
+ onChange: ValidatedBigNumberCallback;
+ shouldShowIncompleteErrs?: boolean;
+ shouldCheckBalance: boolean;
+ validate?: (amount: BigNumber) => InputErrMsg;
+ onVisitBalancesPageClick?: () => void;
+ shouldHideVisitBalancesLink?: boolean;
}
interface BalanceBoundedInputState {
- errMsg: InputErrMsg;
- amountString: string;
+ errMsg: InputErrMsg;
+ amountString: string;
}
export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProps, BalanceBoundedInputState> {
- public static defaultProps: Partial<BalanceBoundedInputProps> = {
- shouldShowIncompleteErrs: false,
- shouldHideVisitBalancesLink: false,
- };
- constructor(props: BalanceBoundedInputProps) {
- super(props);
- const amountString = this.props.amount ? this.props.amount.toString() : '';
- this.state = {
- errMsg: this._validate(amountString, props.balance),
- amountString,
- };
- }
- public componentWillReceiveProps(nextProps: BalanceBoundedInputProps) {
- if (nextProps === this.props) {
- return;
- }
- const isCurrentAmountNumeric = utils.isNumeric(this.state.amountString);
- if (!_.isUndefined(nextProps.amount)) {
- let shouldResetState = false;
- if (!isCurrentAmountNumeric) {
- shouldResetState = true;
- } else {
- const currentAmount = new BigNumber(this.state.amountString);
- if (!currentAmount.eq(nextProps.amount) || !nextProps.balance.eq(this.props.balance)) {
- shouldResetState = true;
- }
- }
- if (shouldResetState) {
- const amountString = nextProps.amount.toString();
- this.setState({
- errMsg: this._validate(amountString, nextProps.balance),
- amountString,
- });
- }
- } else if (isCurrentAmountNumeric) {
- const amountString = '';
- this.setState({
- errMsg: this._validate(amountString, nextProps.balance),
- amountString,
- });
- }
- }
- public render() {
- let errorText = this.state.errMsg;
- if (this.props.shouldShowIncompleteErrs && this.state.amountString === '') {
- errorText = 'This field is required';
- }
- let label: React.ReactNode | string = '';
- if (!_.isUndefined(this.props.label)) {
- label = <RequiredLabel label={this.props.label} />;
- }
- return (
- <TextField
- fullWidth={true}
- floatingLabelText={label}
- floatingLabelFixed={true}
- 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)' }}
- />
- );
- }
- 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 {
- if (!utils.isNumeric(amountString)) {
- return amountString !== '' ? 'Must be a number' : '';
- }
- const amount = new BigNumber(amountString);
- if (amount.eq(0)) {
- return 'Cannot be zero';
- }
- if (this.props.shouldCheckBalance && amount.gt(balance)) {
- return <span>Insufficient balance. {this._renderIncreaseBalanceLink()}</span>;
- }
- const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount);
- return errMsg;
- }
- private _renderIncreaseBalanceLink() {
- if (this.props.shouldHideVisitBalancesLink) {
- return null;
- }
+ public static defaultProps: Partial<BalanceBoundedInputProps> = {
+ shouldShowIncompleteErrs: false,
+ shouldHideVisitBalancesLink: false,
+ };
+ constructor(props: BalanceBoundedInputProps) {
+ super(props);
+ const amountString = this.props.amount ? this.props.amount.toString() : '';
+ this.state = {
+ errMsg: this._validate(amountString, props.balance),
+ amountString,
+ };
+ }
+ public componentWillReceiveProps(nextProps: BalanceBoundedInputProps) {
+ if (nextProps === this.props) {
+ return;
+ }
+ const isCurrentAmountNumeric = utils.isNumeric(this.state.amountString);
+ if (!_.isUndefined(nextProps.amount)) {
+ let shouldResetState = false;
+ if (!isCurrentAmountNumeric) {
+ shouldResetState = true;
+ } else {
+ const currentAmount = new BigNumber(this.state.amountString);
+ if (!currentAmount.eq(nextProps.amount) || !nextProps.balance.eq(this.props.balance)) {
+ shouldResetState = true;
+ }
+ }
+ if (shouldResetState) {
+ const amountString = nextProps.amount.toString();
+ this.setState({
+ errMsg: this._validate(amountString, nextProps.balance),
+ amountString,
+ });
+ }
+ } else if (isCurrentAmountNumeric) {
+ const amountString = '';
+ this.setState({
+ errMsg: this._validate(amountString, nextProps.balance),
+ amountString,
+ });
+ }
+ }
+ public render() {
+ let errorText = this.state.errMsg;
+ if (this.props.shouldShowIncompleteErrs && this.state.amountString === '') {
+ errorText = 'This field is required';
+ }
+ let label: React.ReactNode | string = '';
+ if (!_.isUndefined(this.props.label)) {
+ label = <RequiredLabel label={this.props.label} />;
+ }
+ return (
+ <TextField
+ fullWidth={true}
+ floatingLabelText={label}
+ floatingLabelFixed={true}
+ 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)' }}
+ />
+ );
+ }
+ 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 {
+ if (!utils.isNumeric(amountString)) {
+ return amountString !== '' ? 'Must be a number' : '';
+ }
+ const amount = new BigNumber(amountString);
+ if (amount.eq(0)) {
+ return 'Cannot be zero';
+ }
+ if (this.props.shouldCheckBalance && amount.gt(balance)) {
+ return <span>Insufficient balance. {this._renderIncreaseBalanceLink()}</span>;
+ }
+ const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount);
+ return errMsg;
+ }
+ private _renderIncreaseBalanceLink() {
+ if (this.props.shouldHideVisitBalancesLink) {
+ return null;
+ }
- const increaseBalanceText = 'Increase balance';
- const linkStyle = {
- cursor: 'pointer',
- color: colors.darkestGrey,
- textDecoration: 'underline',
- display: 'inline',
- };
- if (_.isUndefined(this.props.onVisitBalancesPageClick)) {
- return (
- <Link to={`${WebsitePaths.Portal}/balances`} style={linkStyle}>
- {increaseBalanceText}
- </Link>
- );
- } else {
- return (
- <div onClick={this.props.onVisitBalancesPageClick} style={linkStyle}>
- {increaseBalanceText}
- </div>
- );
- }
- }
+ const increaseBalanceText = 'Increase balance';
+ const linkStyle = {
+ cursor: 'pointer',
+ color: colors.darkestGrey,
+ textDecoration: 'underline',
+ display: 'inline',
+ };
+ if (_.isUndefined(this.props.onVisitBalancesPageClick)) {
+ return (
+ <Link to={`${WebsitePaths.Portal}/balances`} style={linkStyle}>
+ {increaseBalanceText}
+ </Link>
+ );
+ } else {
+ return (
+ <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 a66f92c8c..7f9747094 100644
--- a/packages/website/ts/components/inputs/eth_amount_input.tsx
+++ b/packages/website/ts/components/inputs/eth_amount_input.tsx
@@ -7,43 +7,43 @@ import { ValidatedBigNumberCallback } from 'ts/types';
import { constants } from 'ts/utils/constants';
interface EthAmountInputProps {
- label?: string;
- balance: BigNumber;
- amount?: BigNumber;
- onChange: ValidatedBigNumberCallback;
- shouldShowIncompleteErrs: boolean;
- onVisitBalancesPageClick?: () => void;
- shouldCheckBalance: boolean;
- shouldHideVisitBalancesLink?: boolean;
+ label?: string;
+ balance: BigNumber;
+ amount?: BigNumber;
+ onChange: ValidatedBigNumberCallback;
+ shouldShowIncompleteErrs: boolean;
+ onVisitBalancesPageClick?: () => void;
+ shouldCheckBalance: boolean;
+ shouldHideVisitBalancesLink?: boolean;
}
interface EthAmountInputState {}
export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmountInputState> {
- public render() {
- const amount = this.props.amount
- ? ZeroEx.toUnitAmount(this.props.amount, constants.DECIMAL_PLACES_ETH)
- : undefined;
- return (
- <div className="flex overflow-hidden" style={{ height: 63 }}>
- <BalanceBoundedInput
- label={this.props.label}
- balance={this.props.balance}
- amount={amount}
- 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>
- );
- }
- private _onChange(isValid: boolean, amount?: BigNumber) {
- const baseUnitAmountIfExists = _.isUndefined(amount)
- ? undefined
- : ZeroEx.toBaseUnitAmount(amount, constants.DECIMAL_PLACES_ETH);
- this.props.onChange(isValid, baseUnitAmountIfExists);
- }
+ public render() {
+ const amount = this.props.amount
+ ? ZeroEx.toUnitAmount(this.props.amount, constants.DECIMAL_PLACES_ETH)
+ : undefined;
+ return (
+ <div className="flex overflow-hidden" style={{ height: 63 }}>
+ <BalanceBoundedInput
+ label={this.props.label}
+ balance={this.props.balance}
+ amount={amount}
+ 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>
+ );
+ }
+ 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 e473648d2..cb4ed7bd0 100644
--- a/packages/website/ts/components/inputs/expiration_input.tsx
+++ b/packages/website/ts/components/inputs/expiration_input.tsx
@@ -7,94 +7,94 @@ import * as React from 'react';
import { utils } from 'ts/utils/utils';
interface ExpirationInputProps {
- orderExpiryTimestamp: BigNumber;
- updateOrderExpiry: (unixTimestampSec: BigNumber) => void;
+ orderExpiryTimestamp: BigNumber;
+ updateOrderExpiry: (unixTimestampSec: BigNumber) => void;
}
interface ExpirationInputState {
- dateMoment: moment.Moment;
- timeMoment: moment.Moment;
+ dateMoment: moment.Moment;
+ timeMoment: moment.Moment;
}
export class ExpirationInput extends React.Component<ExpirationInputProps, ExpirationInputState> {
- 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');
- const expirationMoment = utils.convertToMomentFromUnixTimestamp(props.orderExpiryTimestamp);
- const initialOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec();
- const didUserSetExpiry = !initialOrderExpiryTimestamp.eq(props.orderExpiryTimestamp);
- this.state = {
- dateMoment: didUserSetExpiry ? expirationMoment : undefined,
- timeMoment: didUserSetExpiry ? expirationMoment : undefined,
- };
- }
- public render() {
- const date = this.state.dateMoment ? this.state.dateMoment.toDate() : undefined;
- const time = this.state.timeMoment ? this.state.timeMoment.toDate() : undefined;
- return (
- <div className="clearfix">
- <div className="col col-6 overflow-hidden pr3 flex relative">
- <DatePicker
- className="overflow-hidden"
- hintText="Date"
- mode="landscape"
- autoOk={true}
- value={date}
- onChange={this._onDateChanged.bind(this)}
- shouldDisableDate={this._shouldDisableDate.bind(this)}
- />
- <div className="absolute" style={{ fontSize: 20, right: 40, top: 13, pointerEvents: 'none' }}>
- <i className="zmdi zmdi-calendar" />
- </div>
- </div>
- <div className="col col-5 overflow-hidden flex relative">
- <TimePicker
- className="overflow-hidden"
- hintText="Time"
- autoOk={true}
- value={time}
- onChange={this._onTimeChanged.bind(this)}
- />
- <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>
- </div>
- );
- }
- private _shouldDisableDate(date: Date): boolean {
- return moment(date)
- .startOf('day')
- .isBefore(this._earliestPickableMoment);
- }
- private _clearDates() {
- this.setState({
- dateMoment: undefined,
- timeMoment: undefined,
- });
- const defaultDateTime = utils.initialOrderExpiryUnixTimestampSec();
- this.props.updateOrderExpiry(defaultDateTime);
- }
- private _onDateChanged(e: any, date: Date) {
- const dateMoment = moment(date);
- this.setState({
- dateMoment,
- });
- const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, this.state.timeMoment);
- this.props.updateOrderExpiry(timestamp);
- }
- private _onTimeChanged(e: any, time: Date) {
- const timeMoment = moment(time);
- this.setState({
- timeMoment,
- });
- const dateMoment = _.isUndefined(this.state.dateMoment) ? moment() : this.state.dateMoment;
- const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, timeMoment);
- this.props.updateOrderExpiry(timestamp);
- }
+ 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');
+ const expirationMoment = utils.convertToMomentFromUnixTimestamp(props.orderExpiryTimestamp);
+ const initialOrderExpiryTimestamp = utils.initialOrderExpiryUnixTimestampSec();
+ const didUserSetExpiry = !initialOrderExpiryTimestamp.eq(props.orderExpiryTimestamp);
+ this.state = {
+ dateMoment: didUserSetExpiry ? expirationMoment : undefined,
+ timeMoment: didUserSetExpiry ? expirationMoment : undefined,
+ };
+ }
+ public render() {
+ const date = this.state.dateMoment ? this.state.dateMoment.toDate() : undefined;
+ const time = this.state.timeMoment ? this.state.timeMoment.toDate() : undefined;
+ return (
+ <div className="clearfix">
+ <div className="col col-6 overflow-hidden pr3 flex relative">
+ <DatePicker
+ className="overflow-hidden"
+ hintText="Date"
+ mode="landscape"
+ autoOk={true}
+ value={date}
+ onChange={this._onDateChanged.bind(this)}
+ shouldDisableDate={this._shouldDisableDate.bind(this)}
+ />
+ <div className="absolute" style={{ fontSize: 20, right: 40, top: 13, pointerEvents: 'none' }}>
+ <i className="zmdi zmdi-calendar" />
+ </div>
+ </div>
+ <div className="col col-5 overflow-hidden flex relative">
+ <TimePicker
+ className="overflow-hidden"
+ hintText="Time"
+ autoOk={true}
+ value={time}
+ onChange={this._onTimeChanged.bind(this)}
+ />
+ <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>
+ </div>
+ );
+ }
+ private _shouldDisableDate(date: Date): boolean {
+ return moment(date)
+ .startOf('day')
+ .isBefore(this._earliestPickableMoment);
+ }
+ private _clearDates() {
+ this.setState({
+ dateMoment: undefined,
+ timeMoment: undefined,
+ });
+ const defaultDateTime = utils.initialOrderExpiryUnixTimestampSec();
+ this.props.updateOrderExpiry(defaultDateTime);
+ }
+ private _onDateChanged(e: any, date: Date) {
+ const dateMoment = moment(date);
+ this.setState({
+ dateMoment,
+ });
+ const timestamp = utils.convertToUnixTimestampSeconds(dateMoment, this.state.timeMoment);
+ this.props.updateOrderExpiry(timestamp);
+ }
+ private _onTimeChanged(e: any, time: Date) {
+ const timeMoment = moment(time);
+ this.setState({
+ timeMoment,
+ });
+ 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 5a3d34fe6..36d7e6140 100644
--- a/packages/website/ts/components/inputs/hash_input.tsx
+++ b/packages/website/ts/components/inputs/hash_input.tsx
@@ -8,55 +8,55 @@ import { HashData, Styles } from 'ts/types';
import { constants } from 'ts/utils/constants';
const styles: Styles = {
- textField: {
- overflow: 'hidden',
- paddingTop: 8,
- textOverflow: 'ellipsis',
- whiteSpace: 'nowrap',
- },
+ textField: {
+ overflow: 'hidden',
+ paddingTop: 8,
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ },
};
interface HashInputProps {
- blockchain: Blockchain;
- blockchainIsLoaded: boolean;
- hashData: HashData;
- label: string;
+ blockchain: Blockchain;
+ blockchainIsLoaded: boolean;
+ hashData: HashData;
+ label: string;
}
interface HashInputState {}
export class HashInput extends React.Component<HashInputProps, HashInputState> {
- public render() {
- const msgHashHex = this.props.blockchainIsLoaded ? this._generateMessageHashHex() : '';
- return (
- <div>
- <FakeTextField label={this.props.label}>
- <div style={styles.textField} data-tip={true} data-for="hashTooltip">
- {msgHashHex}
- </div>
- </FakeTextField>
- <ReactTooltip id="hashTooltip">{msgHashHex}</ReactTooltip>
- </div>
- );
- }
- private _generateMessageHashHex() {
- const exchangeContractAddress = this.props.blockchain.getExchangeContractAddressIfExists();
- const hashData = this.props.hashData;
- const order: Order = {
- exchangeContractAddress,
- expirationUnixTimestampSec: hashData.orderExpiryTimestamp,
- feeRecipient: hashData.feeRecipientAddress,
- maker: _.isEmpty(hashData.orderMakerAddress) ? constants.NULL_ADDRESS : hashData.orderMakerAddress,
- makerFee: hashData.makerFee,
- makerTokenAddress: hashData.depositTokenContractAddr,
- makerTokenAmount: hashData.depositAmount,
- salt: hashData.orderSalt,
- taker: hashData.orderTakerAddress,
- takerFee: hashData.takerFee,
- takerTokenAddress: hashData.receiveTokenContractAddr,
- takerTokenAmount: hashData.receiveAmount,
- };
- const orderHash = ZeroEx.getOrderHashHex(order);
- return orderHash;
- }
+ public render() {
+ const msgHashHex = this.props.blockchainIsLoaded ? this._generateMessageHashHex() : '';
+ return (
+ <div>
+ <FakeTextField label={this.props.label}>
+ <div style={styles.textField} data-tip={true} data-for="hashTooltip">
+ {msgHashHex}
+ </div>
+ </FakeTextField>
+ <ReactTooltip id="hashTooltip">{msgHashHex}</ReactTooltip>
+ </div>
+ );
+ }
+ private _generateMessageHashHex() {
+ const exchangeContractAddress = this.props.blockchain.getExchangeContractAddressIfExists();
+ const hashData = this.props.hashData;
+ const order: Order = {
+ exchangeContractAddress,
+ expirationUnixTimestampSec: hashData.orderExpiryTimestamp,
+ feeRecipient: hashData.feeRecipientAddress,
+ maker: _.isEmpty(hashData.orderMakerAddress) ? constants.NULL_ADDRESS : hashData.orderMakerAddress,
+ makerFee: hashData.makerFee,
+ makerTokenAddress: hashData.depositTokenContractAddr,
+ makerTokenAmount: hashData.depositAmount,
+ salt: hashData.orderSalt,
+ taker: hashData.orderTakerAddress,
+ takerFee: hashData.takerFee,
+ takerTokenAddress: hashData.receiveTokenContractAddr,
+ takerTokenAmount: hashData.receiveAmount,
+ };
+ const orderHash = ZeroEx.getOrderHashHex(order);
+ return orderHash;
+ }
}
diff --git a/packages/website/ts/components/inputs/identicon_address_input.tsx b/packages/website/ts/components/inputs/identicon_address_input.tsx
index 4cf9af64d..f14cb4e9c 100644
--- a/packages/website/ts/components/inputs/identicon_address_input.tsx
+++ b/packages/website/ts/components/inputs/identicon_address_input.tsx
@@ -6,48 +6,48 @@ import { InputLabel } from 'ts/components/ui/input_label';
import { RequiredLabel } from 'ts/components/ui/required_label';
interface IdenticonAddressInputProps {
- initialAddress: string;
- isRequired?: boolean;
- label: string;
- updateOrderAddress: (address?: string) => void;
+ initialAddress: string;
+ isRequired?: boolean;
+ label: string;
+ updateOrderAddress: (address?: string) => void;
}
interface IdenticonAddressInputState {
- address: string;
+ address: string;
}
export class IdenticonAddressInput extends React.Component<IdenticonAddressInputProps, IdenticonAddressInputState> {
- constructor(props: IdenticonAddressInputProps) {
- super(props);
- this.state = {
- address: props.initialAddress,
- };
- }
- public render() {
- const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : this.props.label;
- return (
- <div className="relative" style={{ width: '100%' }}>
- <InputLabel text={label} />
- <div className="flex">
- <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 }}>
- <AddressInput
- hintText="e.g 0x75bE4F78AA3699B3A348c84bDB2a96c3Db..."
- shouldHideLabel={true}
- initialAddress={this.props.initialAddress}
- updateAddress={this._updateAddress.bind(this)}
- />
- </div>
- </div>
- </div>
- );
- }
- private _updateAddress(address?: string): void {
- this.setState({
- address,
- });
- this.props.updateOrderAddress(address);
- }
+ constructor(props: IdenticonAddressInputProps) {
+ super(props);
+ this.state = {
+ address: props.initialAddress,
+ };
+ }
+ public render() {
+ const label = this.props.isRequired ? <RequiredLabel label={this.props.label} /> : this.props.label;
+ return (
+ <div className="relative" style={{ width: '100%' }}>
+ <InputLabel text={label} />
+ <div className="flex">
+ <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 }}>
+ <AddressInput
+ hintText="e.g 0x75bE4F78AA3699B3A348c84bDB2a96c3Db..."
+ shouldHideLabel={true}
+ initialAddress={this.props.initialAddress}
+ updateAddress={this._updateAddress.bind(this)}
+ />
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private _updateAddress(address?: string): void {
+ this.setState({
+ address,
+ });
+ this.props.updateOrderAddress(address);
+ }
}
diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx
index 63966d759..0a71b2c00 100644
--- a/packages/website/ts/components/inputs/token_amount_input.tsx
+++ b/packages/website/ts/components/inputs/token_amount_input.tsx
@@ -8,63 +8,63 @@ import { InputErrMsg, Token, TokenState, ValidatedBigNumberCallback, WebsitePath
import { colors } from 'ts/utils/colors';
interface TokenAmountInputProps {
- token: Token;
- tokenState: TokenState;
- label?: string;
- amount?: BigNumber;
- shouldShowIncompleteErrs: boolean;
- shouldCheckBalance: boolean;
- shouldCheckAllowance: boolean;
- onChange: ValidatedBigNumberCallback;
- onVisitBalancesPageClick?: () => void;
+ token: Token;
+ tokenState: TokenState;
+ label?: string;
+ amount?: BigNumber;
+ shouldShowIncompleteErrs: boolean;
+ shouldCheckBalance: boolean;
+ shouldCheckAllowance: boolean;
+ onChange: ValidatedBigNumberCallback;
+ onVisitBalancesPageClick?: () => void;
}
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 hasLabel = !_.isUndefined(this.props.label);
- return (
- <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)}
- shouldCheckBalance={this.props.shouldCheckBalance}
- shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs}
- onVisitBalancesPageClick={this.props.onVisitBalancesPageClick}
- />
- <div style={{ paddingTop: hasLabel ? 39 : 14 }}>{this.props.token.symbol}</div>
- </div>
- );
- }
- 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 {
- if (this.props.shouldCheckAllowance && amount.gt(this.props.tokenState.allowance)) {
- return (
- <span>
- Insufficient allowance.{' '}
- <Link
- to={`${WebsitePaths.Portal}/balances`}
- style={{ cursor: 'pointer', color: colors.darkestGrey }}
- >
- Set allowance
- </Link>
- </span>
- );
- } else {
- return undefined;
- }
- }
+ public render() {
+ 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: 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)}
+ shouldCheckBalance={this.props.shouldCheckBalance}
+ shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs}
+ onVisitBalancesPageClick={this.props.onVisitBalancesPageClick}
+ />
+ <div style={{ paddingTop: hasLabel ? 39 : 14 }}>{this.props.token.symbol}</div>
+ </div>
+ );
+ }
+ 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 {
+ if (this.props.shouldCheckAllowance && amount.gt(this.props.tokenState.allowance)) {
+ return (
+ <span>
+ Insufficient allowance.{' '}
+ <Link
+ to={`${WebsitePaths.Portal}/balances`}
+ style={{ cursor: 'pointer', color: colors.darkestGrey }}
+ >
+ 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 5df19b28c..3aceacb22 100644
--- a/packages/website/ts/components/inputs/token_input.tsx
+++ b/packages/website/ts/components/inputs/token_input.tsx
@@ -12,93 +12,93 @@ import { colors } from 'ts/utils/colors';
const TOKEN_ICON_DIMENSION = 80;
interface TokenInputProps {
- blockchain: Blockchain;
- blockchainErr: BlockchainErrs;
- dispatcher: Dispatcher;
- label: string;
- side: Side;
- networkId: number;
- assetToken: AssetToken;
- updateChosenAssetToken: (side: Side, token: AssetToken) => void;
- tokenByAddress: TokenByAddress;
- userAddress: string;
+ blockchain: Blockchain;
+ blockchainErr: BlockchainErrs;
+ dispatcher: Dispatcher;
+ label: string;
+ side: Side;
+ networkId: number;
+ assetToken: AssetToken;
+ updateChosenAssetToken: (side: Side, token: AssetToken) => void;
+ tokenByAddress: TokenByAddress;
+ userAddress: string;
}
interface TokenInputState {
- isHoveringIcon: boolean;
- isPickerOpen: boolean;
- trackCandidateTokenIfExists?: Token;
+ isHoveringIcon: boolean;
+ isPickerOpen: boolean;
+ trackCandidateTokenIfExists?: Token;
}
export class TokenInput extends React.Component<TokenInputProps, TokenInputState> {
- constructor(props: TokenInputProps) {
- super(props);
- this.state = {
- isHoveringIcon: false,
- isPickerOpen: false,
- };
- }
- public render() {
- const token = this.props.tokenByAddress[this.props.assetToken.address];
- const iconStyles = {
- cursor: 'pointer',
- opacity: this.state.isHoveringIcon ? 0.5 : 1,
- };
- return (
- <div className="relative">
- <div className="pb1">
- <InputLabel text={this.props.label} />
- </div>
- <Paper
- zDepth={1}
- 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 }}>
- <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} />
- </div>
- <div className="py1 center" style={{ color: colors.grey }}>
- {token.name}
- </div>
- </Paper>
- <AssetPicker
- userAddress={this.props.userAddress}
- networkId={this.props.networkId}
- blockchain={this.props.blockchain}
- dispatcher={this.props.dispatcher}
- isOpen={this.state.isPickerOpen}
- currentTokenAddress={this.props.assetToken.address}
- onTokenChosen={this._onTokenChosen.bind(this)}
- tokenByAddress={this.props.tokenByAddress}
- />
- </div>
- );
- }
- private _onTokenChosen(tokenAddress: string) {
- const assetToken: AssetToken = {
- address: tokenAddress,
- amount: this.props.assetToken.amount,
- };
- this.props.updateChosenAssetToken(this.props.side, assetToken);
- this.setState({
- isPickerOpen: false,
- });
- }
- private _onToggleHover(isHoveringIcon: boolean) {
- this.setState({
- isHoveringIcon,
- });
- }
- private _onAssetClicked() {
- if (this.props.blockchainErr !== BlockchainErrs.NoError) {
- this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
- return;
- }
+ constructor(props: TokenInputProps) {
+ super(props);
+ this.state = {
+ isHoveringIcon: false,
+ isPickerOpen: false,
+ };
+ }
+ public render() {
+ const token = this.props.tokenByAddress[this.props.assetToken.address];
+ const iconStyles = {
+ cursor: 'pointer',
+ opacity: this.state.isHoveringIcon ? 0.5 : 1,
+ };
+ return (
+ <div className="relative">
+ <div className="pb1">
+ <InputLabel text={this.props.label} />
+ </div>
+ <Paper
+ zDepth={1}
+ 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 }}>
+ <TokenIcon token={token} diameter={TOKEN_ICON_DIMENSION} />
+ </div>
+ <div className="py1 center" style={{ color: colors.grey }}>
+ {token.name}
+ </div>
+ </Paper>
+ <AssetPicker
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ isOpen={this.state.isPickerOpen}
+ currentTokenAddress={this.props.assetToken.address}
+ onTokenChosen={this._onTokenChosen.bind(this)}
+ tokenByAddress={this.props.tokenByAddress}
+ />
+ </div>
+ );
+ }
+ private _onTokenChosen(tokenAddress: string) {
+ const assetToken: AssetToken = {
+ address: tokenAddress,
+ amount: this.props.assetToken.amount,
+ };
+ this.props.updateChosenAssetToken(this.props.side, assetToken);
+ this.setState({
+ isPickerOpen: false,
+ });
+ }
+ private _onToggleHover(isHoveringIcon: boolean) {
+ this.setState({
+ isHoveringIcon,
+ });
+ }
+ private _onAssetClicked() {
+ if (this.props.blockchainErr !== BlockchainErrs.NoError) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return;
+ }
- this.setState({
- isPickerOpen: true,
- });
- }
+ this.setState({
+ isPickerOpen: true,
+ });
+ }
}
diff --git a/packages/website/ts/components/order_json.tsx b/packages/website/ts/components/order_json.tsx
index 1b6b32a04..1640a178e 100644
--- a/packages/website/ts/components/order_json.tsx
+++ b/packages/website/ts/components/order_json.tsx
@@ -11,172 +11,172 @@ import { errorReporter } from 'ts/utils/error_reporter';
import { utils } from 'ts/utils/utils';
interface OrderJSONProps {
- exchangeContractIfExists: string;
- orderExpiryTimestamp: BigNumber;
- orderSignatureData: SignatureData;
- orderTakerAddress: string;
- orderMakerAddress: string;
- orderSalt: BigNumber;
- orderMakerFee: BigNumber;
- orderTakerFee: BigNumber;
- orderFeeRecipient: string;
- networkId: number;
- sideToAssetToken: SideToAssetToken;
- tokenByAddress: TokenByAddress;
+ exchangeContractIfExists: string;
+ orderExpiryTimestamp: BigNumber;
+ orderSignatureData: SignatureData;
+ orderTakerAddress: string;
+ orderMakerAddress: string;
+ orderSalt: BigNumber;
+ orderMakerFee: BigNumber;
+ orderTakerFee: BigNumber;
+ orderFeeRecipient: string;
+ networkId: number;
+ sideToAssetToken: SideToAssetToken;
+ tokenByAddress: TokenByAddress;
}
interface OrderJSONState {
- shareLink: string;
+ shareLink: string;
}
export class OrderJSON extends React.Component<OrderJSONProps, OrderJSONState> {
- constructor(props: OrderJSONProps) {
- super(props);
- this.state = {
- shareLink: '',
- };
- // tslint:disable-next-line:no-floating-promises
- 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 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.
- </div>
- <div className="pb2 flex">
- <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 }}
- value={JSON.stringify(order, null, '\t')}
- multiLine={true}
- rows={2}
- rowsMax={8}
- underlineStyle={{ display: 'none' }}
- />
- </Paper>
- <div className="pt3 pb2 center">
- <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>
- </div>
- <div className="mx-auto pt1 flex" style={{ width: 91 }}>
- <div>
- <i
- style={{ cursor: 'pointer', fontSize: 29 }}
- onClick={this._shareViaFacebook.bind(this)}
- className="zmdi zmdi-facebook-box"
- />
- </div>
- <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)}
- className="zmdi zmdi-email"
- />
- </div>
- <div className="pl1">
- <i
- style={{ cursor: 'pointer', fontSize: 29 }}
- onClick={this._shareViaTwitterAsync.bind(this)}
- className="zmdi zmdi-twitter-box"
- />
- </div>
- </div>
- </div>
- </div>
- );
- }
- 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 _shareViaEmailAsync() {
- const encodedSubject = encodeURIComponent("Let's trade using the 0x protocol");
- const encodedBody = encodeURIComponent(`I generated an order with the 0x protocol.
+ constructor(props: OrderJSONProps) {
+ super(props);
+ this.state = {
+ shareLink: '',
+ };
+ // tslint:disable-next-line:no-floating-promises
+ 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 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.
+ </div>
+ <div className="pb2 flex">
+ <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 }}
+ value={JSON.stringify(order, null, '\t')}
+ multiLine={true}
+ rows={2}
+ rowsMax={8}
+ underlineStyle={{ display: 'none' }}
+ />
+ </Paper>
+ <div className="pt3 pb2 center">
+ <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>
+ </div>
+ <div className="mx-auto pt1 flex" style={{ width: 91 }}>
+ <div>
+ <i
+ style={{ cursor: 'pointer', fontSize: 29 }}
+ onClick={this._shareViaFacebook.bind(this)}
+ className="zmdi zmdi-facebook-box"
+ />
+ </div>
+ <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)}
+ className="zmdi zmdi-email"
+ />
+ </div>
+ <div className="pl1">
+ <i
+ style={{ cursor: 'pointer', fontSize: 29 }}
+ onClick={this._shareViaTwitterAsync.bind(this)}
+ className="zmdi zmdi-twitter-box"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+ 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 _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();
- this.setState({
- shareLink,
- });
- }
- 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);
- if (response.status !== 200 || bodyObj.status_code !== 200) {
- // TODO: Show error message in UI
- utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`);
- await errorReporter.reportAsync(new Error(`Bitly returned non-200: ${JSON.stringify(response)}`));
- return '';
- }
- 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,
- );
- const orderJSONString = JSON.stringify(order);
- const orderUrl = `${configs.BASE_URL}${WebsitePaths.Portal}/fill?order=${orderJSONString}`;
- return orderUrl;
- }
+ const mailToLink = `mailto:mail@example.org?subject=${encodedSubject}&body=${encodedBody}`;
+ window.open(mailToLink, '_blank');
+ }
+ private async _setShareLinkAsync() {
+ const shareLink = await this._generateShareLinkAsync();
+ this.setState({
+ shareLink,
+ });
+ }
+ 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);
+ if (response.status !== 200 || bodyObj.status_code !== 200) {
+ // TODO: Show error message in UI
+ utils.consoleLog(`Unexpected status code: ${response.status} -> ${responseBody}`);
+ await errorReporter.reportAsync(new Error(`Bitly returned non-200: ${JSON.stringify(response)}`));
+ return '';
+ }
+ 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,
+ );
+ 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 e2e28e8b6..e163a1fa2 100644
--- a/packages/website/ts/components/portal.tsx
+++ b/packages/website/ts/components/portal.tsx
@@ -23,14 +23,14 @@ import { Dispatcher } from 'ts/redux/dispatcher';
import { orderSchema } from 'ts/schemas/order_schema';
import { SchemaValidator } from 'ts/schemas/validator';
import {
- BlockchainErrs,
- HashData,
- Order,
- ScreenWidths,
- Token,
- TokenByAddress,
- TokenStateByAddress,
- WebsitePaths,
+ BlockchainErrs,
+ HashData,
+ Order,
+ ScreenWidths,
+ Token,
+ TokenByAddress,
+ TokenStateByAddress,
+ WebsitePaths,
} from 'ts/types';
import { colors } from 'ts/utils/colors';
import { configs } from 'ts/utils/configs';
@@ -42,320 +42,320 @@ const THROTTLE_TIMEOUT = 100;
export interface PortalPassedProps {}
export interface PortalAllProps {
- blockchainErr: BlockchainErrs;
- blockchainIsLoaded: boolean;
- dispatcher: Dispatcher;
- hashData: HashData;
- networkId: number;
- nodeVersion: string;
- orderFillAmount: BigNumber;
- screenWidth: ScreenWidths;
- tokenByAddress: TokenByAddress;
- tokenStateByAddress: TokenStateByAddress;
- userEtherBalance: BigNumber;
- userAddress: string;
- shouldBlockchainErrDialogBeOpen: boolean;
- userSuppliedOrderCache: Order;
- location: Location;
- flashMessage?: string | React.ReactNode;
+ blockchainErr: BlockchainErrs;
+ blockchainIsLoaded: boolean;
+ dispatcher: Dispatcher;
+ hashData: HashData;
+ networkId: number;
+ nodeVersion: string;
+ orderFillAmount: BigNumber;
+ screenWidth: ScreenWidths;
+ tokenByAddress: TokenByAddress;
+ tokenStateByAddress: TokenStateByAddress;
+ userEtherBalance: BigNumber;
+ userAddress: string;
+ shouldBlockchainErrDialogBeOpen: boolean;
+ userSuppliedOrderCache: Order;
+ location: Location;
+ flashMessage?: string | React.ReactNode;
}
interface PortalAllState {
- prevNetworkId: number;
- prevNodeVersion: string;
- prevUserAddress: string;
- prevPathname: string;
- isDisclaimerDialogOpen: boolean;
- isWethNoticeDialogOpen: boolean;
+ prevNetworkId: number;
+ prevNodeVersion: string;
+ prevUserAddress: string;
+ prevPathname: string;
+ isDisclaimerDialogOpen: boolean;
+ isWethNoticeDialogOpen: boolean;
}
export class Portal extends React.Component<PortalAllProps, PortalAllState> {
- 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);
+ 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);
- const isViewingBalances = _.includes(props.location.pathname, `${WebsitePaths.Portal}/balances`);
- const hasAlreadyDismissedWethNotice = Portal.hasAlreadyDismissedWethNotice();
+ 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,
- prevPathname: this.props.location.pathname,
- isDisclaimerDialogOpen: !hasAcceptedDisclaimer,
- isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances,
- };
- }
- public componentDidMount() {
- window.addEventListener('resize', this._throttledScreenWidthUpdate);
- window.scrollTo(0, 0);
- }
- public componentWillMount() {
- this._blockchain = new Blockchain(this.props.dispatcher);
- }
- public componentWillUnmount() {
- 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
- // become disconnected from their backing Ethereum node, changes user accounts, etc...)
- this.props.dispatcher.resetState();
- }
- public componentWillReceiveProps(nextProps: PortalAllProps) {
- if (nextProps.networkId !== this.state.prevNetworkId) {
- // tslint:disable-next-line:no-floating-promises
- 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) {
- const tokens = _.values(nextProps.tokenByAddress);
- // tslint:disable-next-line:no-floating-promises
- this._updateBalanceAndAllowanceWithLoadingScreenAsync(tokens);
- }
- this.setState({
- prevUserAddress: nextProps.userAddress,
- });
- }
- if (nextProps.nodeVersion !== this.state.prevNodeVersion) {
- // tslint:disable-next-line:no-floating-promises
- 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 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" />
- <TopBar
- userAddress={this.props.userAddress}
- blockchainIsLoaded={this.props.blockchainIsLoaded}
- location={this.props.location}
- />
- <div id="portal" className="mx-auto max-width-4" style={{ width: '100%' }}>
- <Paper className="mb3 mt2">
- {!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 }} />
- </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>
- </div>
- ) : (
- <div className="mx-auto flex">
- <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 ? (
- <Switch>
- <Route
- path={`${WebsitePaths.Portal}/weth`}
- render={this._renderEthWrapper.bind(this)}
- />
- <Route
- path={`${WebsitePaths.Portal}/fill`}
- render={this._renderFillOrder.bind(this)}
- />
- <Route
- path={`${WebsitePaths.Portal}/balances`}
- render={this._renderTokenBalances.bind(this)}
- />
- <Route
- path={`${WebsitePaths.Portal}/trades`}
- component={this._renderTradeHistory.bind(this)}
- />
- <Route
- path={`${WebsitePaths.Home}`}
- render={this._renderGenerateOrderForm.bind(this)}
- />
- </Switch>
- ) : (
- <Loading />
- )}
- </div>
- </div>
- </div>
- )}
- </Paper>
- <BlockchainErrDialog
- blockchain={this._blockchain}
- blockchainErr={this.props.blockchainErr}
- isOpen={this.props.shouldBlockchainErrDialogBeOpen}
- userAddress={this.props.userAddress}
- toggleDialogFn={updateShouldBlockchainErrDialogBeOpen}
- networkId={this.props.networkId}
- />
- <WrappedEthSectionNoticeDialog
- isOpen={this.state.isWethNoticeDialogOpen}
- onToggleDialog={this._onWethNoticeAccepted.bind(this)}
- />
- <PortalDisclaimerDialog
- isOpen={this.state.isDisclaimerDialogOpen}
- onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)}
- />
- <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} />
- </div>
- <Footer />
- </div>
- );
- }
- 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}
- userAddress={this.props.userAddress}
- networkId={this.props.networkId}
- />
- );
- }
- private _renderTokenBalances() {
- return (
- <TokenBalances
- blockchain={this._blockchain}
- blockchainErr={this.props.blockchainErr}
- blockchainIsLoaded={this.props.blockchainIsLoaded}
- dispatcher={this.props.dispatcher}
- screenWidth={this.props.screenWidth}
- tokenByAddress={this.props.tokenByAddress}
- tokenStateByAddress={this.props.tokenStateByAddress}
- userAddress={this.props.userAddress}
- userEtherBalance={this.props.userEtherBalance}
- networkId={this.props.networkId}
- />
- );
- }
- private _renderFillOrder(match: any, location: Location, history: History) {
- const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache)
- ? this.props.userSuppliedOrderCache
- : this._sharedOrderIfExists;
- return (
- <FillOrder
- blockchain={this._blockchain}
- blockchainErr={this.props.blockchainErr}
- initialOrder={initialFillOrder}
- isOrderInUrl={!_.isUndefined(this._sharedOrderIfExists)}
- orderFillAmount={this.props.orderFillAmount}
- networkId={this.props.networkId}
- userAddress={this.props.userAddress}
- tokenByAddress={this.props.tokenByAddress}
- tokenStateByAddress={this.props.tokenStateByAddress}
- dispatcher={this.props.dispatcher}
- />
- );
- }
- private _renderGenerateOrderForm(match: any, location: Location, history: History) {
- return (
- <GenerateOrderForm
- blockchain={this._blockchain}
- hashData={this.props.hashData}
- dispatcher={this.props.dispatcher}
- />
- );
- }
- 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({
- isWethNoticeDialogOpen: false,
- });
- }
- private _getSharedOrderIfExists(): Order | undefined {
- const queryString = window.location.search;
- if (queryString.length === 0) {
- return undefined;
- }
- const queryParams = queryString.substring(1).split('&');
- const orderQueryParam = _.find(queryParams, queryParam => {
- const queryPair = queryParam.split('=');
- return queryPair[0] === 'order';
- });
- if (_.isUndefined(orderQueryParam)) {
- return undefined;
- }
- const orderPair = orderQueryParam.split('=');
- if (orderPair.length !== 2) {
- return undefined;
- }
+ 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,
+ prevPathname: this.props.location.pathname,
+ isDisclaimerDialogOpen: !hasAcceptedDisclaimer,
+ isWethNoticeDialogOpen: !hasAlreadyDismissedWethNotice && isViewingBalances,
+ };
+ }
+ public componentDidMount() {
+ window.addEventListener('resize', this._throttledScreenWidthUpdate);
+ window.scrollTo(0, 0);
+ }
+ public componentWillMount() {
+ this._blockchain = new Blockchain(this.props.dispatcher);
+ }
+ public componentWillUnmount() {
+ 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
+ // become disconnected from their backing Ethereum node, changes user accounts, etc...)
+ this.props.dispatcher.resetState();
+ }
+ public componentWillReceiveProps(nextProps: PortalAllProps) {
+ if (nextProps.networkId !== this.state.prevNetworkId) {
+ // tslint:disable-next-line:no-floating-promises
+ 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) {
+ const tokens = _.values(nextProps.tokenByAddress);
+ // tslint:disable-next-line:no-floating-promises
+ this._updateBalanceAndAllowanceWithLoadingScreenAsync(tokens);
+ }
+ this.setState({
+ prevUserAddress: nextProps.userAddress,
+ });
+ }
+ if (nextProps.nodeVersion !== this.state.prevNodeVersion) {
+ // tslint:disable-next-line:no-floating-promises
+ 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 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" />
+ <TopBar
+ userAddress={this.props.userAddress}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
+ location={this.props.location}
+ />
+ <div id="portal" className="mx-auto max-width-4" style={{ width: '100%' }}>
+ <Paper className="mb3 mt2">
+ {!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 }} />
+ </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>
+ </div>
+ ) : (
+ <div className="mx-auto flex">
+ <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 ? (
+ <Switch>
+ <Route
+ path={`${WebsitePaths.Portal}/weth`}
+ render={this._renderEthWrapper.bind(this)}
+ />
+ <Route
+ path={`${WebsitePaths.Portal}/fill`}
+ render={this._renderFillOrder.bind(this)}
+ />
+ <Route
+ path={`${WebsitePaths.Portal}/balances`}
+ render={this._renderTokenBalances.bind(this)}
+ />
+ <Route
+ path={`${WebsitePaths.Portal}/trades`}
+ component={this._renderTradeHistory.bind(this)}
+ />
+ <Route
+ path={`${WebsitePaths.Home}`}
+ render={this._renderGenerateOrderForm.bind(this)}
+ />
+ </Switch>
+ ) : (
+ <Loading />
+ )}
+ </div>
+ </div>
+ </div>
+ )}
+ </Paper>
+ <BlockchainErrDialog
+ blockchain={this._blockchain}
+ blockchainErr={this.props.blockchainErr}
+ isOpen={this.props.shouldBlockchainErrDialogBeOpen}
+ userAddress={this.props.userAddress}
+ toggleDialogFn={updateShouldBlockchainErrDialogBeOpen}
+ networkId={this.props.networkId}
+ />
+ <WrappedEthSectionNoticeDialog
+ isOpen={this.state.isWethNoticeDialogOpen}
+ onToggleDialog={this._onWethNoticeAccepted.bind(this)}
+ />
+ <PortalDisclaimerDialog
+ isOpen={this.state.isDisclaimerDialogOpen}
+ onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)}
+ />
+ <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} />
+ </div>
+ <Footer />
+ </div>
+ );
+ }
+ 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}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ />
+ );
+ }
+ private _renderTokenBalances() {
+ return (
+ <TokenBalances
+ blockchain={this._blockchain}
+ blockchainErr={this.props.blockchainErr}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
+ dispatcher={this.props.dispatcher}
+ screenWidth={this.props.screenWidth}
+ tokenByAddress={this.props.tokenByAddress}
+ tokenStateByAddress={this.props.tokenStateByAddress}
+ userAddress={this.props.userAddress}
+ userEtherBalance={this.props.userEtherBalance}
+ networkId={this.props.networkId}
+ />
+ );
+ }
+ private _renderFillOrder(match: any, location: Location, history: History) {
+ const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache)
+ ? this.props.userSuppliedOrderCache
+ : this._sharedOrderIfExists;
+ return (
+ <FillOrder
+ blockchain={this._blockchain}
+ blockchainErr={this.props.blockchainErr}
+ initialOrder={initialFillOrder}
+ isOrderInUrl={!_.isUndefined(this._sharedOrderIfExists)}
+ orderFillAmount={this.props.orderFillAmount}
+ networkId={this.props.networkId}
+ userAddress={this.props.userAddress}
+ tokenByAddress={this.props.tokenByAddress}
+ tokenStateByAddress={this.props.tokenStateByAddress}
+ dispatcher={this.props.dispatcher}
+ />
+ );
+ }
+ private _renderGenerateOrderForm(match: any, location: Location, history: History) {
+ return (
+ <GenerateOrderForm
+ blockchain={this._blockchain}
+ hashData={this.props.hashData}
+ dispatcher={this.props.dispatcher}
+ />
+ );
+ }
+ 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({
+ isWethNoticeDialogOpen: false,
+ });
+ }
+ private _getSharedOrderIfExists(): Order | undefined {
+ const queryString = window.location.search;
+ if (queryString.length === 0) {
+ return undefined;
+ }
+ const queryParams = queryString.substring(1).split('&');
+ const orderQueryParam = _.find(queryParams, queryParam => {
+ const queryPair = queryParam.split('=');
+ return queryPair[0] === 'order';
+ });
+ if (_.isUndefined(orderQueryParam)) {
+ return undefined;
+ }
+ const orderPair = orderQueryParam.split('=');
+ if (orderPair.length !== 2) {
+ return undefined;
+ }
- const validator = new SchemaValidator();
- const order = JSON.parse(decodeURIComponent(orderPair[1]));
- const validationResult = validator.validate(order, orderSchema);
- if (validationResult.errors.length > 0) {
- utils.consoleLog(`Invalid shared order: ${validationResult.errors}`);
- return undefined;
- }
- return order;
- }
- private _updateScreenWidth() {
- const newScreenWidth = utils.getScreenWidth();
- this.props.dispatcher.updateScreenWidth(newScreenWidth);
- }
- private async _updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) {
- this.props.dispatcher.updateBlockchainIsLoaded(false);
- await this._blockchain.updateTokenBalancesAndAllowancesAsync(tokens);
- this.props.dispatcher.updateBlockchainIsLoaded(true);
- }
+ const validator = new SchemaValidator();
+ const order = JSON.parse(decodeURIComponent(orderPair[1]));
+ const validationResult = validator.validate(order, orderSchema);
+ if (validationResult.errors.length > 0) {
+ utils.consoleLog(`Invalid shared order: ${validationResult.errors}`);
+ return undefined;
+ }
+ return order;
+ }
+ private _updateScreenWidth() {
+ const newScreenWidth = utils.getScreenWidth();
+ this.props.dispatcher.updateScreenWidth(newScreenWidth);
+ }
+ private async _updateBalanceAndAllowanceWithLoadingScreenAsync(tokens: Token[]) {
+ this.props.dispatcher.updateBlockchainIsLoaded(false);
+ await this._blockchain.updateTokenBalancesAndAllowancesAsync(tokens);
+ this.props.dispatcher.updateBlockchainIsLoaded(true);
+ }
}
diff --git a/packages/website/ts/components/portal_menu.tsx b/packages/website/ts/components/portal_menu.tsx
index a2f9340c8..b025f527e 100644
--- a/packages/website/ts/components/portal_menu.tsx
+++ b/packages/website/ts/components/portal_menu.tsx
@@ -4,70 +4,70 @@ import { MenuItem } from 'ts/components/ui/menu_item';
import { WebsitePaths } from 'ts/types';
export interface PortalMenuProps {
- menuItemStyle: React.CSSProperties;
- onClick?: () => void;
+ menuItemStyle: React.CSSProperties;
+ onClick?: () => void;
}
interface PortalMenuState {}
export class PortalMenu extends React.Component<PortalMenuProps, PortalMenuState> {
- public static defaultProps: Partial<PortalMenuProps> = {
- onClick: _.noop,
- };
- public render() {
- return (
- <div>
- <MenuItem
- style={this.props.menuItemStyle}
- className="py2"
- to={`${WebsitePaths.Portal}`}
- onClick={this.props.onClick.bind(this)}
- >
- {this._renderMenuItemWithIcon('Generate order', 'zmdi-arrow-right-top')}
- </MenuItem>
- <MenuItem
- style={this.props.menuItemStyle}
- className="py2"
- to={`${WebsitePaths.Portal}/fill`}
- onClick={this.props.onClick.bind(this)}
- >
- {this._renderMenuItemWithIcon('Fill order', 'zmdi-arrow-left-bottom')}
- </MenuItem>
- <MenuItem
- style={this.props.menuItemStyle}
- className="py2"
- to={`${WebsitePaths.Portal}/balances`}
- onClick={this.props.onClick.bind(this)}
- >
- {this._renderMenuItemWithIcon('Balances', 'zmdi-balance-wallet')}
- </MenuItem>
- <MenuItem
- style={this.props.menuItemStyle}
- className="py2"
- to={`${WebsitePaths.Portal}/trades`}
- onClick={this.props.onClick.bind(this)}
- >
- {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) {
- return (
- <div className="flex" style={{ fontWeight: 100 }}>
- <div className="pr1 pl2">
- <i style={{ fontSize: 20 }} className={`zmdi ${iconName}`} />
- </div>
- <div className="pl1">{title}</div>
- </div>
- );
- }
+ public static defaultProps: Partial<PortalMenuProps> = {
+ onClick: _.noop,
+ };
+ public render() {
+ return (
+ <div>
+ <MenuItem
+ style={this.props.menuItemStyle}
+ className="py2"
+ to={`${WebsitePaths.Portal}`}
+ onClick={this.props.onClick.bind(this)}
+ >
+ {this._renderMenuItemWithIcon('Generate order', 'zmdi-arrow-right-top')}
+ </MenuItem>
+ <MenuItem
+ style={this.props.menuItemStyle}
+ className="py2"
+ to={`${WebsitePaths.Portal}/fill`}
+ onClick={this.props.onClick.bind(this)}
+ >
+ {this._renderMenuItemWithIcon('Fill order', 'zmdi-arrow-left-bottom')}
+ </MenuItem>
+ <MenuItem
+ style={this.props.menuItemStyle}
+ className="py2"
+ to={`${WebsitePaths.Portal}/balances`}
+ onClick={this.props.onClick.bind(this)}
+ >
+ {this._renderMenuItemWithIcon('Balances', 'zmdi-balance-wallet')}
+ </MenuItem>
+ <MenuItem
+ style={this.props.menuItemStyle}
+ className="py2"
+ to={`${WebsitePaths.Portal}/trades`}
+ onClick={this.props.onClick.bind(this)}
+ >
+ {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) {
+ return (
+ <div className="flex" style={{ fontWeight: 100 }}>
+ <div className="pr1 pl2">
+ <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 f94ec346a..f97d9250b 100644
--- a/packages/website/ts/components/send_button.tsx
+++ b/packages/website/ts/components/send_button.tsx
@@ -10,78 +10,78 @@ import { errorReporter } from 'ts/utils/error_reporter';
import { utils } from 'ts/utils/utils';
interface SendButtonProps {
- token: Token;
- tokenState: TokenState;
- dispatcher: Dispatcher;
- blockchain: Blockchain;
- onError: () => void;
+ token: Token;
+ tokenState: TokenState;
+ dispatcher: Dispatcher;
+ blockchain: Blockchain;
+ onError: () => void;
}
interface SendButtonState {
- isSendDialogVisible: boolean;
- isSending: boolean;
+ isSendDialogVisible: boolean;
+ isSending: boolean;
}
export class SendButton extends React.Component<SendButtonProps, SendButtonState> {
- public constructor(props: SendButtonProps) {
- super(props);
- this.state = {
- isSendDialogVisible: false,
- isSending: false,
- };
- }
- public render() {
- const labelStyle = this.state.isSending ? { fontSize: 10 } : {};
- return (
- <div>
- <RaisedButton
- style={{ width: '100%' }}
- labelStyle={labelStyle}
- disabled={this.state.isSending}
- label={this.state.isSending ? 'Sending...' : 'Send'}
- onClick={this._toggleSendDialog.bind(this)}
- />
- <SendDialog
- isOpen={this.state.isSendDialogVisible}
- onComplete={this._onSendAmountSelectedAsync.bind(this)}
- onCancelled={this._toggleSendDialog.bind(this)}
- token={this.props.token}
- tokenState={this.props.tokenState}
- />
- </div>
- );
- }
- private _toggleSendDialog() {
- this.setState({
- isSendDialogVisible: !this.state.isSendDialogVisible,
- });
- }
- private async _onSendAmountSelectedAsync(recipient: string, value: BigNumber) {
- this.setState({
- isSending: true,
- });
- this._toggleSendDialog();
- const token = this.props.token;
- const tokenState = this.props.tokenState;
- let balance = tokenState.balance;
- try {
- await this.props.blockchain.transferAsync(token, recipient, value);
- balance = balance.minus(value);
- this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance);
- } catch (err) {
- const errMsg = `${err}`;
- 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);
- this.props.onError();
- await errorReporter.reportAsync(err);
- }
- }
- this.setState({
- isSending: false,
- });
- }
+ public constructor(props: SendButtonProps) {
+ super(props);
+ this.state = {
+ isSendDialogVisible: false,
+ isSending: false,
+ };
+ }
+ public render() {
+ const labelStyle = this.state.isSending ? { fontSize: 10 } : {};
+ return (
+ <div>
+ <RaisedButton
+ style={{ width: '100%' }}
+ labelStyle={labelStyle}
+ disabled={this.state.isSending}
+ label={this.state.isSending ? 'Sending...' : 'Send'}
+ onClick={this._toggleSendDialog.bind(this)}
+ />
+ <SendDialog
+ isOpen={this.state.isSendDialogVisible}
+ onComplete={this._onSendAmountSelectedAsync.bind(this)}
+ onCancelled={this._toggleSendDialog.bind(this)}
+ token={this.props.token}
+ tokenState={this.props.tokenState}
+ />
+ </div>
+ );
+ }
+ private _toggleSendDialog() {
+ this.setState({
+ isSendDialogVisible: !this.state.isSendDialogVisible,
+ });
+ }
+ private async _onSendAmountSelectedAsync(recipient: string, value: BigNumber) {
+ this.setState({
+ isSending: true,
+ });
+ this._toggleSendDialog();
+ const token = this.props.token;
+ const tokenState = this.props.tokenState;
+ let balance = tokenState.balance;
+ try {
+ await this.props.blockchain.transferAsync(token, recipient, value);
+ balance = balance.minus(value);
+ this.props.dispatcher.replaceTokenBalanceByAddress(token.address, balance);
+ } catch (err) {
+ const errMsg = `${err}`;
+ 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);
+ this.props.onError();
+ await errorReporter.reportAsync(err);
+ }
+ }
+ this.setState({
+ isSending: false,
+ });
+ }
}
diff --git a/packages/website/ts/components/token_balances.tsx b/packages/website/ts/components/token_balances.tsx
index 2cef413c7..480652c34 100644
--- a/packages/website/ts/components/token_balances.tsx
+++ b/packages/website/ts/components/token_balances.tsx
@@ -23,16 +23,16 @@ 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,
- BlockchainErrs,
- EtherscanLinkSuffixes,
- ScreenWidths,
- Styles,
- Token,
- TokenByAddress,
- TokenStateByAddress,
- TokenVisibility,
+ BalanceErrs,
+ BlockchainCallErrs,
+ BlockchainErrs,
+ EtherscanLinkSuffixes,
+ ScreenWidths,
+ Styles,
+ Token,
+ TokenByAddress,
+ TokenStateByAddress,
+ TokenVisibility,
} from 'ts/types';
import { colors } from 'ts/utils/colors';
import { configs } from 'ts/utils/configs';
@@ -53,554 +53,554 @@ const TOKEN_COL_SPAN_LG = 2;
const TOKEN_COL_SPAN_SM = 1;
const styles: Styles = {
- bgColor: {
- backgroundColor: colors.grey50,
- },
+ bgColor: {
+ backgroundColor: colors.grey50,
+ },
};
interface TokenBalancesProps {
- blockchain: Blockchain;
- blockchainErr: BlockchainErrs;
- blockchainIsLoaded: boolean;
- dispatcher: Dispatcher;
- screenWidth: ScreenWidths;
- tokenByAddress: TokenByAddress;
- tokenStateByAddress: TokenStateByAddress;
- userAddress: string;
- userEtherBalance: BigNumber;
- networkId: number;
+ blockchain: Blockchain;
+ blockchainErr: BlockchainErrs;
+ blockchainIsLoaded: boolean;
+ dispatcher: Dispatcher;
+ screenWidth: ScreenWidths;
+ tokenByAddress: TokenByAddress;
+ tokenStateByAddress: TokenStateByAddress;
+ userAddress: string;
+ userEtherBalance: BigNumber;
+ networkId: number;
}
interface TokenBalancesState {
- errorType: BalanceErrs;
- isBalanceSpinnerVisible: boolean;
- isDharmaDialogVisible: boolean;
- isZRXSpinnerVisible: boolean;
- currentZrxBalance?: BigNumber;
- isTokenPickerOpen: boolean;
- isAddingToken: boolean;
+ errorType: BalanceErrs;
+ isBalanceSpinnerVisible: boolean;
+ isDharmaDialogVisible: boolean;
+ isZRXSpinnerVisible: boolean;
+ currentZrxBalance?: BigNumber;
+ isTokenPickerOpen: boolean;
+ isAddingToken: boolean;
}
export class TokenBalances extends React.Component<TokenBalancesProps, TokenBalancesState> {
- public constructor(props: TokenBalancesProps) {
- super(props);
- this.state = {
- errorType: undefined,
- isBalanceSpinnerVisible: false,
- isZRXSpinnerVisible: false,
- isDharmaDialogVisible: DharmaLoanFrame.isAuthTokenPresent(),
- isTokenPickerOpen: false,
- isAddingToken: false,
- };
- }
- public componentWillReceiveProps(nextProps: TokenBalancesProps) {
- if (nextProps.userEtherBalance !== this.props.userEtherBalance) {
- if (this.state.isBalanceSpinnerVisible) {
- const receivedAmount = nextProps.userEtherBalance.minus(this.props.userEtherBalance);
- this.props.dispatcher.showFlashMessage(`Received ${receivedAmount.toString(10)} Kovan Ether`);
- }
- this.setState({
- isBalanceSpinnerVisible: false,
- });
- }
- const nextZrxToken = _.find(_.values(nextProps.tokenByAddress), t => t.symbol === ZRX_TOKEN_SYMBOL);
- const nextZrxTokenBalance = nextProps.tokenStateByAddress[nextZrxToken.address].balance;
- if (!_.isUndefined(this.state.currentZrxBalance) && !nextZrxTokenBalance.eq(this.state.currentZrxBalance)) {
- if (this.state.isZRXSpinnerVisible) {
- const receivedAmount = nextZrxTokenBalance.minus(this.state.currentZrxBalance);
- const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, constants.DECIMAL_PLACES_ZRX);
- this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`);
- }
- this.setState({
- isZRXSpinnerVisible: false,
- currentZrxBalance: undefined,
- });
- }
- }
- public componentDidMount() {
- window.scrollTo(0, 0);
- }
- public render() {
- const errorDialogActions = [
- <FlatButton
- key="errorOkBtn"
- label="Ok"
- primary={true}
- onTouchTap={this._onErrorDialogToggle.bind(this, false)}
- />,
- ];
- const dharmaDialogActions = [
- <FlatButton
- key="dharmaCloseBtn"
- label="Close"
- primary={true}
- onTouchTap={this._onDharmaDialogToggle.bind(this, false)}
- />,
- ];
- const isTestNetwork = this.props.networkId === constants.NETWORK_ID_TESTNET;
- const dharmaButtonColumnStyle = {
- paddingLeft: 3,
- display: isTestNetwork ? 'table-cell' : 'none',
- };
- const stubColumnStyle = {
- 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 tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG;
- const dharmaLoanExplanation =
- 'If you need access to larger amounts of ether,<br> \
+ public constructor(props: TokenBalancesProps) {
+ super(props);
+ this.state = {
+ errorType: undefined,
+ isBalanceSpinnerVisible: false,
+ isZRXSpinnerVisible: false,
+ isDharmaDialogVisible: DharmaLoanFrame.isAuthTokenPresent(),
+ isTokenPickerOpen: false,
+ isAddingToken: false,
+ };
+ }
+ public componentWillReceiveProps(nextProps: TokenBalancesProps) {
+ if (nextProps.userEtherBalance !== this.props.userEtherBalance) {
+ if (this.state.isBalanceSpinnerVisible) {
+ const receivedAmount = nextProps.userEtherBalance.minus(this.props.userEtherBalance);
+ this.props.dispatcher.showFlashMessage(`Received ${receivedAmount.toString(10)} Kovan Ether`);
+ }
+ this.setState({
+ isBalanceSpinnerVisible: false,
+ });
+ }
+ const nextZrxToken = _.find(_.values(nextProps.tokenByAddress), t => t.symbol === ZRX_TOKEN_SYMBOL);
+ const nextZrxTokenBalance = nextProps.tokenStateByAddress[nextZrxToken.address].balance;
+ if (!_.isUndefined(this.state.currentZrxBalance) && !nextZrxTokenBalance.eq(this.state.currentZrxBalance)) {
+ if (this.state.isZRXSpinnerVisible) {
+ const receivedAmount = nextZrxTokenBalance.minus(this.state.currentZrxBalance);
+ const receiveAmountInUnits = ZeroEx.toUnitAmount(receivedAmount, constants.DECIMAL_PLACES_ZRX);
+ this.props.dispatcher.showFlashMessage(`Received ${receiveAmountInUnits.toString(10)} Kovan ZRX`);
+ }
+ this.setState({
+ isZRXSpinnerVisible: false,
+ currentZrxBalance: undefined,
+ });
+ }
+ }
+ public componentDidMount() {
+ window.scrollTo(0, 0);
+ }
+ public render() {
+ const errorDialogActions = [
+ <FlatButton
+ key="errorOkBtn"
+ label="Ok"
+ primary={true}
+ onTouchTap={this._onErrorDialogToggle.bind(this, false)}
+ />,
+ ];
+ const dharmaDialogActions = [
+ <FlatButton
+ key="dharmaCloseBtn"
+ label="Close"
+ primary={true}
+ onTouchTap={this._onDharmaDialogToggle.bind(this, false)}
+ />,
+ ];
+ const isTestNetwork = this.props.networkId === constants.NETWORK_ID_TESTNET;
+ const dharmaButtonColumnStyle = {
+ paddingLeft: 3,
+ display: isTestNetwork ? 'table-cell' : 'none',
+ };
+ const stubColumnStyle = {
+ 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 tokenColSpan = isSmallScreen ? TOKEN_COL_SPAN_SM : TOKEN_COL_SPAN_LG;
+ 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 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 \
+ 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. \
+ : '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}>
- <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 }}>
- {isSmallScreen ? 'Faucet' : 'Request from faucet'}
- </TableHeaderColumn>
- )}
- {isTestNetwork && (
- <TableHeaderColumn style={dharmaButtonColumnStyle}>
- {isSmallScreen ? 'Loan' : 'Request Dharma loan'}
- <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} />
- </TableRowColumn>
- <TableRowColumn>
- {this.props.userEtherBalance.toFixed(PRECISION)} ETH
- {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 }}>
- <LifeCycleRaisedButton
- labelReady="Request"
- labelLoading="Sending..."
- labelComplete="Sent!"
- onClickAsyncFn={this._faucetRequestAsync.bind(this, true)}
- />
- </TableRowColumn>
- )}
- {isTestNetwork && (
- <TableRowColumn style={dharmaButtonColumnStyle}>
- <RaisedButton
- label="Request"
- style={{ width: '100%' }}
- onTouchTap={this._onDharmaDialogToggle.bind(this)}
- />
- </TableRowColumn>
- )}
- </TableRow>
- </TableBody>
- </Table>
- <div className="clearfix" style={{ paddingBottom: 1 }}>
- <div className="col col-10">
- <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)}>
- <ContentAdd />
- </FloatingActionButton>
- </div>
- <div className="col col-1 pt3 align-right">
- <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."}
- </div>
- <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">Allowance</div>
- <HelpTooltip style={{ paddingLeft: 4 }} explanation={allowanceExplanation} />
- </TableHeaderColumn>
- <TableHeaderColumn>Action</TableHeaderColumn>
- {this.props.screenWidth !== ScreenWidths.Sm && <TableHeaderColumn>Send</TableHeaderColumn>}
- </TableRow>
- </TableHeader>
- <TableBody displayRowCheckbox={false}>{this._renderTokenTableRows()}</TableBody>
- </Table>
- <Dialog
- title="Oh oh"
- titleStyle={{ fontWeight: 100 }}
- actions={errorDialogActions}
- open={!_.isUndefined(this.state.errorType)}
- onRequestClose={this._onErrorDialogToggle.bind(this, false)}
- >
- {this._renderErrorDialogBody()}
- </Dialog>
- <Dialog
- title="Request Dharma Loan"
- titleStyle={{ fontWeight: 100, backgroundColor: colors.white }}
- bodyStyle={{ backgroundColor: colors.dharmaDarkGrey }}
- actionsContainerStyle={{ backgroundColor: colors.white }}
- autoScrollBodyContent={true}
- actions={dharmaDialogActions}
- open={this.state.isDharmaDialogVisible}
- >
- {this._renderDharmaLoanFrame()}
- </Dialog>
- <AssetPicker
- userAddress={this.props.userAddress}
- networkId={this.props.networkId}
- blockchain={this.props.blockchain}
- dispatcher={this.props.dispatcher}
- isOpen={this.state.isTokenPickerOpen}
- currentTokenAddress={''}
- 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 !== BlockchainErrs.NoError) {
- return '';
- }
- 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'),
- );
- const tableRows = _.map(
- trackedTokensStartingWithEtherToken,
- this._renderTokenRow.bind(this, tokenColSpan, actionPaddingX),
- );
- return tableRows;
- }
- 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.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)}
- </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>
- <TableRowColumn>
- <AllowanceToggle
- blockchain={this.props.blockchain}
- dispatcher={this.props.dispatcher}
- token={token}
- tokenState={tokenState}
- onErrorOccurred={this._onErrorOccurred.bind(this)}
- userAddress={this.props.userAddress}
- />
- </TableRowColumn>
- <TableRowColumn style={{ paddingLeft: actionPaddingX, paddingRight: actionPaddingX }}>
- {isMintable && (
- <LifeCycleRaisedButton
- labelReady="Mint"
- labelLoading={<span style={{ fontSize: 12 }}>Minting...</span>}
- labelComplete="Minted!"
- onClickAsyncFn={this._onMintTestTokensAsync.bind(this, token)}
- />
- )}
- {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 && (
- <TableRowColumn
- style={{
- paddingLeft: actionPaddingX,
- paddingRight: actionPaddingX,
- }}
- >
- <SendButton
- blockchain={this.props.blockchain}
- dispatcher={this.props.dispatcher}
- token={token}
- tokenState={tokenState}
- onError={this._onSendFailed.bind(this)}
- />
- </TableRowColumn>
- )}
- </TableRow>
- );
- }
- private _onAssetTokenPicked(tokenAddress: string) {
- if (_.isEmpty(tokenAddress)) {
- this.setState({
- isTokenPickerOpen: false,
- });
- return;
- }
- const token = this.props.tokenByAddress[tokenAddress];
- 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 = {
- ...token,
- isTracked: false,
- };
- this.props.dispatcher.updateTokenByAddress([newToken]);
- } else {
- this.props.dispatcher.removeTokenToTokenByAddress(token);
- }
- this.props.dispatcher.removeFromTokenStateByAddress(tokenAddress);
- trackedTokenStorage.removeTrackedToken(this.props.userAddress, this.props.networkId, tokenAddress);
- } else if (isDefaultTrackedToken) {
- this.props.dispatcher.showFlashMessage(`Cannot remove ${token.name} because it's a default token`);
- }
- this.setState({
- isTokenPickerOpen: false,
- });
- }
- private _onSendFailed() {
- this.setState({
- errorType: BalanceErrs.sendFailed,
- });
- }
- private _renderAmount(amount: BigNumber, decimals: number) {
- const unitAmount = ZeroEx.toUnitAmount(amount, decimals);
- return unitAmount.toNumber().toFixed(PRECISION);
- }
- 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">
- {token.name}
- </div>
- <ReactTooltip id={tooltipId}>{token.address}</ReactTooltip>
- </div>
- );
- }
- 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.NETWORK_ID_TESTNET}). Please make sure you are connected to the{' '}
- {constants.TESTNET_NAME} testnet and try requesting ether again.
- </div>
- );
+ </div>
+ <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 }}>
+ {isSmallScreen ? 'Faucet' : 'Request from faucet'}
+ </TableHeaderColumn>
+ )}
+ {isTestNetwork && (
+ <TableHeaderColumn style={dharmaButtonColumnStyle}>
+ {isSmallScreen ? 'Loan' : 'Request Dharma loan'}
+ <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} />
+ </TableRowColumn>
+ <TableRowColumn>
+ {this.props.userEtherBalance.toFixed(PRECISION)} ETH
+ {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 }}>
+ <LifeCycleRaisedButton
+ labelReady="Request"
+ labelLoading="Sending..."
+ labelComplete="Sent!"
+ onClickAsyncFn={this._faucetRequestAsync.bind(this, true)}
+ />
+ </TableRowColumn>
+ )}
+ {isTestNetwork && (
+ <TableRowColumn style={dharmaButtonColumnStyle}>
+ <RaisedButton
+ label="Request"
+ style={{ width: '100%' }}
+ onTouchTap={this._onDharmaDialogToggle.bind(this)}
+ />
+ </TableRowColumn>
+ )}
+ </TableRow>
+ </TableBody>
+ </Table>
+ <div className="clearfix" style={{ paddingBottom: 1 }}>
+ <div className="col col-10">
+ <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)}>
+ <ContentAdd />
+ </FloatingActionButton>
+ </div>
+ <div className="col col-1 pt3 align-right">
+ <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."}
+ </div>
+ <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">Allowance</div>
+ <HelpTooltip style={{ paddingLeft: 4 }} explanation={allowanceExplanation} />
+ </TableHeaderColumn>
+ <TableHeaderColumn>Action</TableHeaderColumn>
+ {this.props.screenWidth !== ScreenWidths.Sm && <TableHeaderColumn>Send</TableHeaderColumn>}
+ </TableRow>
+ </TableHeader>
+ <TableBody displayRowCheckbox={false}>{this._renderTokenTableRows()}</TableBody>
+ </Table>
+ <Dialog
+ title="Oh oh"
+ titleStyle={{ fontWeight: 100 }}
+ actions={errorDialogActions}
+ open={!_.isUndefined(this.state.errorType)}
+ onRequestClose={this._onErrorDialogToggle.bind(this, false)}
+ >
+ {this._renderErrorDialogBody()}
+ </Dialog>
+ <Dialog
+ title="Request Dharma Loan"
+ titleStyle={{ fontWeight: 100, backgroundColor: colors.white }}
+ bodyStyle={{ backgroundColor: colors.dharmaDarkGrey }}
+ actionsContainerStyle={{ backgroundColor: colors.white }}
+ autoScrollBodyContent={true}
+ actions={dharmaDialogActions}
+ open={this.state.isDharmaDialogVisible}
+ >
+ {this._renderDharmaLoanFrame()}
+ </Dialog>
+ <AssetPicker
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ isOpen={this.state.isTokenPickerOpen}
+ currentTokenAddress={''}
+ 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 !== BlockchainErrs.NoError) {
+ return '';
+ }
+ 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'),
+ );
+ const tableRows = _.map(
+ trackedTokensStartingWithEtherToken,
+ this._renderTokenRow.bind(this, tokenColSpan, actionPaddingX),
+ );
+ return tableRows;
+ }
+ 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.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)}
+ </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>
+ <TableRowColumn>
+ <AllowanceToggle
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ token={token}
+ tokenState={tokenState}
+ onErrorOccurred={this._onErrorOccurred.bind(this)}
+ userAddress={this.props.userAddress}
+ />
+ </TableRowColumn>
+ <TableRowColumn style={{ paddingLeft: actionPaddingX, paddingRight: actionPaddingX }}>
+ {isMintable && (
+ <LifeCycleRaisedButton
+ labelReady="Mint"
+ labelLoading={<span style={{ fontSize: 12 }}>Minting...</span>}
+ labelComplete="Minted!"
+ onClickAsyncFn={this._onMintTestTokensAsync.bind(this, token)}
+ />
+ )}
+ {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 && (
+ <TableRowColumn
+ style={{
+ paddingLeft: actionPaddingX,
+ paddingRight: actionPaddingX,
+ }}
+ >
+ <SendButton
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ token={token}
+ tokenState={tokenState}
+ onError={this._onSendFailed.bind(this)}
+ />
+ </TableRowColumn>
+ )}
+ </TableRow>
+ );
+ }
+ private _onAssetTokenPicked(tokenAddress: string) {
+ if (_.isEmpty(tokenAddress)) {
+ this.setState({
+ isTokenPickerOpen: false,
+ });
+ return;
+ }
+ const token = this.props.tokenByAddress[tokenAddress];
+ 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 = {
+ ...token,
+ isTracked: false,
+ };
+ this.props.dispatcher.updateTokenByAddress([newToken]);
+ } else {
+ this.props.dispatcher.removeTokenToTokenByAddress(token);
+ }
+ this.props.dispatcher.removeFromTokenStateByAddress(tokenAddress);
+ trackedTokenStorage.removeTrackedToken(this.props.userAddress, this.props.networkId, tokenAddress);
+ } else if (isDefaultTrackedToken) {
+ this.props.dispatcher.showFlashMessage(`Cannot remove ${token.name} because it's a default token`);
+ }
+ this.setState({
+ isTokenPickerOpen: false,
+ });
+ }
+ private _onSendFailed() {
+ this.setState({
+ errorType: BalanceErrs.sendFailed,
+ });
+ }
+ private _renderAmount(amount: BigNumber, decimals: number) {
+ const unitAmount = ZeroEx.toUnitAmount(amount, decimals);
+ return unitAmount.toNumber().toFixed(PRECISION);
+ }
+ 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">
+ {token.name}
+ </div>
+ <ReactTooltip id={tooltipId}>{token.address}</ReactTooltip>
+ </div>
+ );
+ }
+ 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.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.
- </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.
+ </div>
+ );
- case BalanceErrs.faucetQueueIsFull:
- return <div>Our test Ether faucet queue is full. Please try requesting test Ether again later.</div>;
+ case BalanceErrs.faucetQueueIsFull:
+ 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.mintingFailed:
+ 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.
- </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.
+ </div>
+ );
- case undefined:
- return null; // No error to show
+ case undefined:
+ return null; // No error to show
- default:
- throw utils.spawnSwitchErr('errorType', this.state.errorType);
- }
- }
- 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.
- </h4>
- );
- } else {
- return (
- <DharmaLoanFrame
- partner="0x"
- env={utils.getCurrentEnvironment()}
- screenWidth={this.props.screenWidth}
- />
- );
- }
- }
- private _onErrorOccurred(errorType: BalanceErrs) {
- this.setState({
- errorType,
- });
- }
- 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.UserHasNoAssociatedAddresses)) {
- this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
- return false;
- }
- if (_.includes(errMsg, 'User denied transaction')) {
- return false;
- }
- utils.consoleLog(`Unexpected error encountered: ${err}`);
- utils.consoleLog(err.stack);
- this.setState({
- errorType: BalanceErrs.mintingFailed,
- });
- await errorReporter.reportAsync(err);
- return false;
- }
- }
- private async _faucetRequestAsync(isEtherRequest: boolean): Promise<boolean> {
- if (this.props.userAddress === '') {
- this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
- return false;
- }
+ default:
+ throw utils.spawnSwitchErr('errorType', this.state.errorType);
+ }
+ }
+ 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.
+ </h4>
+ );
+ } else {
+ return (
+ <DharmaLoanFrame
+ partner="0x"
+ env={utils.getCurrentEnvironment()}
+ screenWidth={this.props.screenWidth}
+ />
+ );
+ }
+ }
+ private _onErrorOccurred(errorType: BalanceErrs) {
+ this.setState({
+ errorType,
+ });
+ }
+ 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.UserHasNoAssociatedAddresses)) {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return false;
+ }
+ if (_.includes(errMsg, 'User denied transaction')) {
+ return false;
+ }
+ utils.consoleLog(`Unexpected error encountered: ${err}`);
+ utils.consoleLog(err.stack);
+ this.setState({
+ errorType: BalanceErrs.mintingFailed,
+ });
+ await errorReporter.reportAsync(err);
+ return false;
+ }
+ }
+ private async _faucetRequestAsync(isEtherRequest: boolean): Promise<boolean> {
+ if (this.props.userAddress === '') {
+ this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen(true);
+ return false;
+ }
- // 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.NETWORK_ID_TESTNET) {
- this.setState({
- errorType: BalanceErrs.incorrectNetworkForFaucet,
- });
- return false;
- }
+ // 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.NETWORK_ID_TESTNET) {
+ this.setState({
+ errorType: BalanceErrs.incorrectNetworkForFaucet,
+ });
+ return false;
+ }
- await utils.sleepAsync(ARTIFICIAL_FAUCET_REQUEST_DELAY);
+ await utils.sleepAsync(ARTIFICIAL_FAUCET_REQUEST_DELAY);
- const segment = isEtherRequest ? 'ether' : 'zrx';
- 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}`);
- 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;
- }
+ const segment = isEtherRequest ? 'ether' : 'zrx';
+ 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}`);
+ 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;
+ }
- if (isEtherRequest) {
- this.setState({
- isBalanceSpinnerVisible: true,
- });
- } else {
- const tokens = _.values(this.props.tokenByAddress);
- const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL);
- const zrxTokenState = this.props.tokenStateByAddress[zrxToken.address];
- this.setState({
- isZRXSpinnerVisible: true,
- currentZrxBalance: zrxTokenState.balance,
- });
- // tslint:disable-next-line:no-floating-promises
- this.props.blockchain.pollTokenBalanceAsync(zrxToken);
- }
- return true;
- }
- private _onErrorDialogToggle(isOpen: boolean) {
- this.setState({
- errorType: undefined,
- });
- }
- private _onDharmaDialogToggle() {
- this.setState({
- isDharmaDialogVisible: !this.state.isDharmaDialogVisible,
- });
- }
- private _onAddTokenClicked() {
- this.setState({
- isTokenPickerOpen: true,
- isAddingToken: true,
- });
- }
- private _onRemoveTokenClicked() {
- this.setState({
- isTokenPickerOpen: true,
- isAddingToken: false,
- });
- }
+ if (isEtherRequest) {
+ this.setState({
+ isBalanceSpinnerVisible: true,
+ });
+ } else {
+ const tokens = _.values(this.props.tokenByAddress);
+ const zrxToken = _.find(tokens, t => t.symbol === ZRX_TOKEN_SYMBOL);
+ const zrxTokenState = this.props.tokenStateByAddress[zrxToken.address];
+ this.setState({
+ isZRXSpinnerVisible: true,
+ currentZrxBalance: zrxTokenState.balance,
+ });
+ // tslint:disable-next-line:no-floating-promises
+ this.props.blockchain.pollTokenBalanceAsync(zrxToken);
+ }
+ return true;
+ }
+ private _onErrorDialogToggle(isOpen: boolean) {
+ this.setState({
+ errorType: undefined,
+ });
+ }
+ private _onDharmaDialogToggle() {
+ this.setState({
+ isDharmaDialogVisible: !this.state.isDharmaDialogVisible,
+ });
+ }
+ private _onAddTokenClicked() {
+ this.setState({
+ isTokenPickerOpen: true,
+ isAddingToken: true,
+ });
+ }
+ private _onRemoveTokenClicked() {
+ this.setState({
+ isTokenPickerOpen: true,
+ isAddingToken: false,
+ });
+ }
} // tslint:disable:max-file-line-count
diff --git a/packages/website/ts/components/top_bar.tsx b/packages/website/ts/components/top_bar.tsx
index 11d3e7cc2..1f111cb07 100644
--- a/packages/website/ts/components/top_bar.tsx
+++ b/packages/website/ts/components/top_bar.tsx
@@ -15,333 +15,333 @@ import { colors } from 'ts/utils/colors';
import { constants } from 'ts/utils/constants';
interface TopBarProps {
- userAddress?: string;
- blockchainIsLoaded: boolean;
- location: Location;
- docsVersion?: string;
- availableDocVersions?: string[];
- menu?: DocsMenu;
- menuSubsectionsBySection?: MenuSubsectionsBySection;
- shouldFullWidth?: boolean;
- docsInfo?: DocsInfo;
- style?: React.CSSProperties;
- isNightVersion?: boolean;
+ userAddress?: string;
+ blockchainIsLoaded: boolean;
+ location: Location;
+ docsVersion?: string;
+ availableDocVersions?: string[];
+ menu?: DocsMenu;
+ menuSubsectionsBySection?: MenuSubsectionsBySection;
+ shouldFullWidth?: boolean;
+ docsInfo?: DocsInfo;
+ style?: React.CSSProperties;
+ isNightVersion?: boolean;
}
interface TopBarState {
- isDrawerOpen: boolean;
+ isDrawerOpen: boolean;
}
const styles: Styles = {
- address: {
- marginRight: 12,
- overflow: 'hidden',
- paddingTop: 4,
- textOverflow: 'ellipsis',
- whiteSpace: 'nowrap',
- width: 70,
- },
- topBar: {
- backgroundcolor: colors.white,
- height: 59,
- width: '100%',
- position: 'relative',
- top: 0,
- zIndex: 1100,
- paddingBottom: 1,
- },
- bottomBar: {
- boxShadow: 'rgba(0, 0, 0, 0.187647) 0px 1px 3px',
- },
- menuItem: {
- fontSize: 14,
- color: colors.darkestGrey,
- paddingTop: 6,
- paddingBottom: 6,
- marginTop: 17,
- cursor: 'pointer',
- fontWeight: 400,
- },
+ address: {
+ marginRight: 12,
+ overflow: 'hidden',
+ paddingTop: 4,
+ textOverflow: 'ellipsis',
+ whiteSpace: 'nowrap',
+ width: 70,
+ },
+ topBar: {
+ backgroundcolor: colors.white,
+ height: 59,
+ width: '100%',
+ position: 'relative',
+ top: 0,
+ zIndex: 1100,
+ paddingBottom: 1,
+ },
+ bottomBar: {
+ boxShadow: 'rgba(0, 0, 0, 0.187647) 0px 1px 3px',
+ },
+ menuItem: {
+ fontSize: 14,
+ color: colors.darkestGrey,
+ paddingTop: 6,
+ paddingBottom: 6,
+ marginTop: 17,
+ cursor: 'pointer',
+ fontWeight: 400,
+ },
};
export class TopBar extends React.Component<TopBarProps, TopBarState> {
- public static defaultProps: Partial<TopBarProps> = {
- shouldFullWidth: false,
- style: {},
- isNightVersion: false,
- };
- constructor(props: TopBarProps) {
- super(props);
- this.state = {
- isDrawerOpen: false,
- };
- }
- public render() {
- const isNightVersion = this.props.isNightVersion;
- const isFullWidthPage = this.props.shouldFullWidth;
- const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`;
- const developerSectionMenuItems = [
- <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>,
- <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.URL_STANDARD_RELAYER_API_GITHUB}
- >
- <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Standard Relayer API" />
- </a>,
- <a
- key="subMenuItem-github"
- target="_blank"
- className="text-decoration-none"
- href={constants.URL_GITHUB_ORG}
- >
- <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="GitHub" />
- </a>,
- <a
- key="subMenuItem-whitePaper"
- target="_blank"
- className="text-decoration-none"
- href={`${WebsitePaths.Whitepaper}`}
- >
- <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Whitepaper" />
- </a>,
- ];
- const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {};
- const fullWidthClasses = isFullWidthPage ? 'pr4' : '';
- const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png';
- const menuClasses = `col col-${isFullWidthPage ? '4' : '5'} ${fullWidthClasses} lg-pr0 md-pr2 sm-hide xs-hide`;
- const menuIconStyle = {
- fontSize: 25,
- color: isNightVersion ? 'white' : 'black',
- cursor: 'pointer',
- paddingTop: 16,
- };
- return (
- <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 }}>
- <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}>
- <div className="flex justify-between">
- <DropDownMenuItem
- title="Developers"
- subMenuItems={developerSectionMenuItems}
- style={styles.menuItem}
- isNightVersion={isNightVersion}
- />
- <TopBarMenuItem
- title="Wiki"
- path={`${WebsitePaths.Wiki}`}
- style={styles.menuItem}
- isNightVersion={isNightVersion}
- />
- <TopBarMenuItem
- title="About"
- path={`${WebsitePaths.About}`}
- style={styles.menuItem}
- isNightVersion={isNightVersion}
- />
- <TopBarMenuItem
- title="Portal DApp"
- path={`${WebsitePaths.Portal}`}
- isPrimary={true}
- style={styles.menuItem}
- className={`${isFullWidthPage && 'md-hide'}`}
- isNightVersion={isNightVersion}
- />
- </div>
- </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()}
- </div>
- );
- }
- private _renderDrawer() {
- return (
- <Drawer
- open={this.state.isDrawerOpen}
- docked={false}
- openSecondary={true}
- onRequestChange={this._onMenuButtonClick.bind(this)}
- >
- {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() && (
- <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none">
- <MenuItem className="py2">0x.js Docs</MenuItem>
- </Link>
- )}
- {!this._isViewingConnectDocs() && (
- <Link to={WebsitePaths.Connect} className="text-decoration-none">
- <MenuItem className="py2">0x Connect Docs</MenuItem>
- </Link>
- )}
- {!this._isViewingSmartContractsDocs() && (
- <Link to={WebsitePaths.SmartContracts} className="text-decoration-none">
- <MenuItem className="py2">Smart Contract Docs</MenuItem>
- </Link>
- )}
- {!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}`}>
- <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.URL_BLOG}>
- <MenuItem className="py2">Blog</MenuItem>
- </a>
- <Link to={`${WebsitePaths.FAQ}`} className="text-decoration-none">
- <MenuItem className="py2" onTouchTap={this._onMenuButtonClick.bind(this)}>
- FAQ
- </MenuItem>
- </Link>
- </Drawer>
- );
- }
- private _renderDocsMenu(): React.ReactNode {
- if (
- (!this._isViewing0xjsDocs() && !this._isViewingSmartContractsDocs() && !this._isViewingConnectDocs()) ||
- _.isUndefined(this.props.menu)
- ) {
- return undefined;
- }
+ public static defaultProps: Partial<TopBarProps> = {
+ shouldFullWidth: false,
+ style: {},
+ isNightVersion: false,
+ };
+ constructor(props: TopBarProps) {
+ super(props);
+ this.state = {
+ isDrawerOpen: false,
+ };
+ }
+ public render() {
+ const isNightVersion = this.props.isNightVersion;
+ const isFullWidthPage = this.props.shouldFullWidth;
+ const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`;
+ const developerSectionMenuItems = [
+ <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>,
+ <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.URL_STANDARD_RELAYER_API_GITHUB}
+ >
+ <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Standard Relayer API" />
+ </a>,
+ <a
+ key="subMenuItem-github"
+ target="_blank"
+ className="text-decoration-none"
+ href={constants.URL_GITHUB_ORG}
+ >
+ <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="GitHub" />
+ </a>,
+ <a
+ key="subMenuItem-whitePaper"
+ target="_blank"
+ className="text-decoration-none"
+ href={`${WebsitePaths.Whitepaper}`}
+ >
+ <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Whitepaper" />
+ </a>,
+ ];
+ const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {};
+ const fullWidthClasses = isFullWidthPage ? 'pr4' : '';
+ const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png';
+ const menuClasses = `col col-${isFullWidthPage ? '4' : '5'} ${fullWidthClasses} lg-pr0 md-pr2 sm-hide xs-hide`;
+ const menuIconStyle = {
+ fontSize: 25,
+ color: isNightVersion ? 'white' : 'black',
+ cursor: 'pointer',
+ paddingTop: 16,
+ };
+ return (
+ <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 }}>
+ <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}>
+ <div className="flex justify-between">
+ <DropDownMenuItem
+ title="Developers"
+ subMenuItems={developerSectionMenuItems}
+ style={styles.menuItem}
+ isNightVersion={isNightVersion}
+ />
+ <TopBarMenuItem
+ title="Wiki"
+ path={`${WebsitePaths.Wiki}`}
+ style={styles.menuItem}
+ isNightVersion={isNightVersion}
+ />
+ <TopBarMenuItem
+ title="About"
+ path={`${WebsitePaths.About}`}
+ style={styles.menuItem}
+ isNightVersion={isNightVersion}
+ />
+ <TopBarMenuItem
+ title="Portal DApp"
+ path={`${WebsitePaths.Portal}`}
+ isPrimary={true}
+ style={styles.menuItem}
+ className={`${isFullWidthPage && 'md-hide'}`}
+ isNightVersion={isNightVersion}
+ />
+ </div>
+ </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()}
+ </div>
+ );
+ }
+ private _renderDrawer() {
+ return (
+ <Drawer
+ open={this.state.isDrawerOpen}
+ docked={false}
+ openSecondary={true}
+ onRequestChange={this._onMenuButtonClick.bind(this)}
+ >
+ {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() && (
+ <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none">
+ <MenuItem className="py2">0x.js Docs</MenuItem>
+ </Link>
+ )}
+ {!this._isViewingConnectDocs() && (
+ <Link to={WebsitePaths.Connect} className="text-decoration-none">
+ <MenuItem className="py2">0x Connect Docs</MenuItem>
+ </Link>
+ )}
+ {!this._isViewingSmartContractsDocs() && (
+ <Link to={WebsitePaths.SmartContracts} className="text-decoration-none">
+ <MenuItem className="py2">Smart Contract Docs</MenuItem>
+ </Link>
+ )}
+ {!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}`}>
+ <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.URL_BLOG}>
+ <MenuItem className="py2">Blog</MenuItem>
+ </a>
+ <Link to={`${WebsitePaths.FAQ}`} className="text-decoration-none">
+ <MenuItem className="py2" onTouchTap={this._onMenuButtonClick.bind(this)}>
+ FAQ
+ </MenuItem>
+ </Link>
+ </Drawer>
+ );
+ }
+ 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: colors.lightGrey }}>
- {sectionTitle}
- </div>
- <NestedSidebarMenu
- topLevelMenu={this.props.menu}
- menuSubsectionsBySection={this.props.menuSubsectionsBySection}
- shouldDisplaySectionHeaders={false}
- onMenuItemClick={this._onMenuButtonClick.bind(this)}
- selectedVersion={this.props.docsVersion}
- docPath={this.props.docsInfo.websitePath}
- versions={this.props.availableDocVersions}
- />
- </div>
- );
- }
- private _renderWiki(): React.ReactNode {
- if (!this._isViewingWiki()) {
- return undefined;
- }
+ const sectionTitle = `${this.props.docsInfo.displayName} Docs`;
+ return (
+ <div className="lg-hide md-hide">
+ <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)}
+ selectedVersion={this.props.docsVersion}
+ docPath={this.props.docsInfo.websitePath}
+ versions={this.props.availableDocVersions}
+ />
+ </div>
+ );
+ }
+ private _renderWiki(): React.ReactNode {
+ if (!this._isViewingWiki()) {
+ return undefined;
+ }
- return (
- <div className="lg-hide md-hide">
- <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)}
- />
- </div>
- );
- }
- private _renderPortalMenu(): React.ReactNode {
- if (!this._isViewingPortal()) {
- return undefined;
- }
+ return (
+ <div className="lg-hide md-hide">
+ <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)}
+ />
+ </div>
+ );
+ }
+ private _renderPortalMenu(): React.ReactNode {
+ if (!this._isViewingPortal()) {
+ return undefined;
+ }
- return (
- <div className="lg-hide md-hide">
- <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}>
- Portal DApp
- </div>
- <PortalMenu menuItemStyle={{ color: 'black' }} onClick={this._onMenuButtonClick.bind(this)} />
- </div>
- );
- }
- 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">
- {!_.isEmpty(userAddress) ? userAddress : ''}
- </div>
- <ReactTooltip id="userAddressTooltip">{userAddress}</ReactTooltip>
- <div>
- <Identicon address={userAddress} diameter={identiconDiameter} />
- </div>
- </div>
- );
- }
- private _onMenuButtonClick() {
- this.setState({
- isDrawerOpen: !this.state.isDrawerOpen,
- });
- }
- private _isViewingPortal() {
- return _.includes(this.props.location.pathname, WebsitePaths.Portal);
- }
- private _isViewingFAQ() {
- return _.includes(this.props.location.pathname, WebsitePaths.FAQ);
- }
- private _isViewing0xjsDocs() {
- return _.includes(this.props.location.pathname, WebsitePaths.ZeroExJs);
- }
- private _isViewingConnectDocs() {
- return _.includes(this.props.location.pathname, WebsitePaths.Connect);
- }
- private _isViewingSmartContractsDocs() {
- return _.includes(this.props.location.pathname, WebsitePaths.SmartContracts);
- }
- private _isViewingWiki() {
- return _.includes(this.props.location.pathname, WebsitePaths.Wiki);
- }
- private _shouldDisplayBottomBar() {
- return (
- this._isViewingWiki() ||
- this._isViewing0xjsDocs() ||
- this._isViewingFAQ() ||
- this._isViewingSmartContractsDocs() ||
- this._isViewingConnectDocs()
- );
- }
+ return (
+ <div className="lg-hide md-hide">
+ <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}>
+ Portal DApp
+ </div>
+ <PortalMenu menuItemStyle={{ color: 'black' }} onClick={this._onMenuButtonClick.bind(this)} />
+ </div>
+ );
+ }
+ 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">
+ {!_.isEmpty(userAddress) ? userAddress : ''}
+ </div>
+ <ReactTooltip id="userAddressTooltip">{userAddress}</ReactTooltip>
+ <div>
+ <Identicon address={userAddress} diameter={identiconDiameter} />
+ </div>
+ </div>
+ );
+ }
+ private _onMenuButtonClick() {
+ this.setState({
+ isDrawerOpen: !this.state.isDrawerOpen,
+ });
+ }
+ private _isViewingPortal() {
+ return _.includes(this.props.location.pathname, WebsitePaths.Portal);
+ }
+ private _isViewingFAQ() {
+ return _.includes(this.props.location.pathname, WebsitePaths.FAQ);
+ }
+ private _isViewing0xjsDocs() {
+ return _.includes(this.props.location.pathname, WebsitePaths.ZeroExJs);
+ }
+ private _isViewingConnectDocs() {
+ return _.includes(this.props.location.pathname, WebsitePaths.Connect);
+ }
+ private _isViewingSmartContractsDocs() {
+ return _.includes(this.props.location.pathname, WebsitePaths.SmartContracts);
+ }
+ private _isViewingWiki() {
+ return _.includes(this.props.location.pathname, WebsitePaths.Wiki);
+ }
+ private _shouldDisplayBottomBar() {
+ return (
+ this._isViewingWiki() ||
+ this._isViewing0xjsDocs() ||
+ this._isViewingFAQ() ||
+ this._isViewingSmartContractsDocs() ||
+ this._isViewingConnectDocs()
+ );
+ }
}
diff --git a/packages/website/ts/components/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar_menu_item.tsx
index 96ee86142..0138740ba 100644
--- a/packages/website/ts/components/top_bar_menu_item.tsx
+++ b/packages/website/ts/components/top_bar_menu_item.tsx
@@ -4,49 +4,49 @@ import { Link } from 'react-router-dom';
import { colors } from 'ts/utils/colors';
const DEFAULT_STYLE = {
- color: colors.darkestGrey,
+ color: colors.darkestGrey,
};
interface TopBarMenuItemProps {
- title: string;
- path?: string;
- isPrimary?: boolean;
- style?: React.CSSProperties;
- className?: string;
- isNightVersion?: boolean;
+ title: string;
+ path?: string;
+ isPrimary?: boolean;
+ style?: React.CSSProperties;
+ className?: string;
+ isNightVersion?: boolean;
}
interface TopBarMenuItemState {}
export class TopBarMenuItem extends React.Component<TopBarMenuItemProps, TopBarMenuItemState> {
- public static defaultProps: Partial<TopBarMenuItemProps> = {
- isPrimary: false,
- style: DEFAULT_STYLE,
- className: '',
- isNightVersion: false,
- };
- public render() {
- const primaryStyles = this.props.isPrimary
- ? {
- borderRadius: 4,
- border: `1px solid ${this.props.isNightVersion ? colors.grey : colors.greyishPink}`,
- marginTop: 15,
- paddingLeft: 9,
- paddingRight: 9,
- width: 77,
- }
- : {};
- const menuItemColor = this.props.isNightVersion ? 'white' : this.props.style.color;
- const linkColor = _.isUndefined(menuItemColor) ? colors.darkestGrey : menuItemColor;
- return (
- <div
- className={`center ${this.props.className}`}
- style={{ ...this.props.style, ...primaryStyles, color: menuItemColor }}
- >
- <Link to={this.props.path} className="text-decoration-none" style={{ color: linkColor }}>
- {this.props.title}
- </Link>
- </div>
- );
- }
+ public static defaultProps: Partial<TopBarMenuItemProps> = {
+ isPrimary: false,
+ style: DEFAULT_STYLE,
+ className: '',
+ isNightVersion: false,
+ };
+ public render() {
+ const primaryStyles = this.props.isPrimary
+ ? {
+ borderRadius: 4,
+ border: `1px solid ${this.props.isNightVersion ? colors.grey : colors.greyishPink}`,
+ marginTop: 15,
+ paddingLeft: 9,
+ paddingRight: 9,
+ width: 77,
+ }
+ : {};
+ const menuItemColor = this.props.isNightVersion ? 'white' : this.props.style.color;
+ const linkColor = _.isUndefined(menuItemColor) ? colors.darkestGrey : menuItemColor;
+ return (
+ <div
+ className={`center ${this.props.className}`}
+ style={{ ...this.props.style, ...primaryStyles, color: menuItemColor }}
+ >
+ <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 76971aefa..1887bd11c 100644
--- a/packages/website/ts/components/track_token_confirmation.tsx
+++ b/packages/website/ts/components/track_token_confirmation.tsx
@@ -6,56 +6,56 @@ import { colors } from 'ts/utils/colors';
import { utils } from 'ts/utils/utils';
interface TrackTokenConfirmationProps {
- tokens: Token[];
- tokenByAddress: TokenByAddress;
- networkId: number;
- isAddingTokenToTracked: boolean;
+ tokens: Token[];
+ tokenByAddress: TokenByAddress;
+ networkId: number;
+ isAddingTokenToTracked: boolean;
}
interface 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 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>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}`}
- className={`col col-${isMultipleTokens ? '6' : '12'} px2`}
- >
- <Party
- label={token.name}
- address={token.address}
- networkId={this.props.networkId}
- alternativeImage={token.iconUrl}
- isInTokenRegistry={token.isRegistered}
- hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, token)}
- />
- </div>
- ))}
- </div>
- <div>
- 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?
- </div>
- </div>
- )}
- </div>
- );
- }
+ 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 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>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}`}
+ className={`col col-${isMultipleTokens ? '6' : '12'} px2`}
+ >
+ <Party
+ label={token.name}
+ address={token.address}
+ networkId={this.props.networkId}
+ alternativeImage={token.iconUrl}
+ isInTokenRegistry={token.isRegistered}
+ hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, token)}
+ />
+ </div>
+ ))}
+ </div>
+ <div>
+ 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?
+ </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 635358627..3963135f7 100644
--- a/packages/website/ts/components/trade_history/trade_history.tsx
+++ b/packages/website/ts/components/trade_history/trade_history.tsx
@@ -10,106 +10,106 @@ import { utils } from 'ts/utils/utils';
const FILL_POLLING_INTERVAL = 1000;
interface TradeHistoryProps {
- tokenByAddress: TokenByAddress;
- userAddress: string;
- networkId: number;
+ tokenByAddress: TokenByAddress;
+ userAddress: string;
+ networkId: number;
}
interface TradeHistoryState {
- sortedFills: Fill[];
+ sortedFills: Fill[];
}
export class TradeHistory extends React.Component<TradeHistoryProps, TradeHistoryState> {
- private _fillPollingIntervalId: number;
- public constructor(props: TradeHistoryProps) {
- super(props);
- const sortedFills = this._getSortedFills();
- this.state = {
- sortedFills,
- };
- }
- public componentWillMount() {
- this._startPollingForFills();
- }
- public componentWillUnmount() {
- this._stopPollingForFills();
- }
- public componentDidMount() {
- window.scrollTo(0, 0);
- }
- public render() {
- return (
- <div className="lg-px4 md-px4 sm-px2">
- <h3>Trade history</h3>
- <Divider />
- <div className="pt2" style={{ height: 608, overflow: 'scroll' }}>
- {this._renderTrades()}
- </div>
- </div>
- );
- }
- private _renderTrades() {
- const numNonCustomFills = this._numFillsWithoutCustomERC20Tokens();
- if (numNonCustomFills === 0) {
- return this._renderEmptyNotice();
- }
+ private _fillPollingIntervalId: number;
+ public constructor(props: TradeHistoryProps) {
+ super(props);
+ const sortedFills = this._getSortedFills();
+ this.state = {
+ sortedFills,
+ };
+ }
+ public componentWillMount() {
+ this._startPollingForFills();
+ }
+ public componentWillUnmount() {
+ this._stopPollingForFills();
+ }
+ public componentDidMount() {
+ window.scrollTo(0, 0);
+ }
+ public render() {
+ return (
+ <div className="lg-px4 md-px4 sm-px2">
+ <h3>Trade history</h3>
+ <Divider />
+ <div className="pt2" style={{ height: 608, overflow: 'scroll' }}>
+ {this._renderTrades()}
+ </div>
+ </div>
+ );
+ }
+ private _renderTrades() {
+ const numNonCustomFills = this._numFillsWithoutCustomERC20Tokens();
+ if (numNonCustomFills === 0) {
+ return this._renderEmptyNotice();
+ }
- return _.map(this.state.sortedFills, (fill, index) => {
- return (
- <TradeHistoryItem
- key={`${fill.orderHash}-${fill.filledTakerTokenAmount}-${index}`}
- fill={fill}
- tokenByAddress={this.props.tokenByAddress}
- userAddress={this.props.userAddress}
- networkId={this.props.networkId}
- />
- );
- });
- }
- private _renderEmptyNotice() {
- return (
- <Paper className="mt1 p2 mx-auto center" style={{ width: '80%' }}>
- No filled orders yet.
- </Paper>
- );
- }
- private _numFillsWithoutCustomERC20Tokens() {
- let numNonCustomFills = 0;
- const tokens = _.values(this.props.tokenByAddress);
- _.each(this.state.sortedFills, fill => {
- const takerToken = _.find(tokens, token => {
- return token.address === fill.takerToken;
- });
- const makerToken = _.find(tokens, token => {
- return token.address === fill.makerToken;
- });
- // For now we don't show history items for orders using custom ERC20
- // tokens the client does not know how to display.
- // TODO: Try to retrieve the name/symbol of an unknown token in order to display it
- // Be sure to remove similar logic in trade_history_item.tsx
- if (!_.isUndefined(takerToken) && !_.isUndefined(makerToken)) {
- numNonCustomFills += 1;
- }
- });
- return numNonCustomFills;
- }
- private _startPollingForFills() {
- this._fillPollingIntervalId = window.setInterval(() => {
- const sortedFills = this._getSortedFills();
- if (!utils.deepEqual(sortedFills, this.state.sortedFills)) {
- this.setState({
- sortedFills,
- });
- }
- }, FILL_POLLING_INTERVAL);
- }
- private _stopPollingForFills() {
- clearInterval(this._fillPollingIntervalId);
- }
- 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]);
- return sortedFills;
- }
+ return _.map(this.state.sortedFills, (fill, index) => {
+ return (
+ <TradeHistoryItem
+ key={`${fill.orderHash}-${fill.filledTakerTokenAmount}-${index}`}
+ fill={fill}
+ tokenByAddress={this.props.tokenByAddress}
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ />
+ );
+ });
+ }
+ private _renderEmptyNotice() {
+ return (
+ <Paper className="mt1 p2 mx-auto center" style={{ width: '80%' }}>
+ No filled orders yet.
+ </Paper>
+ );
+ }
+ private _numFillsWithoutCustomERC20Tokens() {
+ let numNonCustomFills = 0;
+ const tokens = _.values(this.props.tokenByAddress);
+ _.each(this.state.sortedFills, fill => {
+ const takerToken = _.find(tokens, token => {
+ return token.address === fill.takerToken;
+ });
+ const makerToken = _.find(tokens, token => {
+ return token.address === fill.makerToken;
+ });
+ // For now we don't show history items for orders using custom ERC20
+ // tokens the client does not know how to display.
+ // TODO: Try to retrieve the name/symbol of an unknown token in order to display it
+ // Be sure to remove similar logic in trade_history_item.tsx
+ if (!_.isUndefined(takerToken) && !_.isUndefined(makerToken)) {
+ numNonCustomFills += 1;
+ }
+ });
+ return numNonCustomFills;
+ }
+ private _startPollingForFills() {
+ this._fillPollingIntervalId = window.setInterval(() => {
+ const sortedFills = this._getSortedFills();
+ if (!utils.deepEqual(sortedFills, this.state.sortedFills)) {
+ this.setState({
+ sortedFills,
+ });
+ }
+ }, FILL_POLLING_INTERVAL);
+ }
+ private _stopPollingForFills() {
+ clearInterval(this._fillPollingIntervalId);
+ }
+ 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]);
+ return sortedFills;
+ }
}
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 7e42e64e6..118bde3b9 100644
--- a/packages/website/ts/components/trade_history/trade_history_item.tsx
+++ b/packages/website/ts/components/trade_history/trade_history_item.tsx
@@ -14,157 +14,157 @@ const PRECISION = 5;
const IDENTICON_DIAMETER = 40;
interface TradeHistoryItemProps {
- fill: Fill;
- tokenByAddress: TokenByAddress;
- userAddress: string;
- networkId: number;
+ fill: Fill;
+ tokenByAddress: TokenByAddress;
+ userAddress: string;
+ networkId: number;
}
interface TradeHistoryItemState {}
export class TradeHistoryItem extends React.Component<TradeHistoryItemProps, TradeHistoryItemState> {
- public render() {
- const fill = this.props.fill;
- const tokens = _.values(this.props.tokenByAddress);
- const takerToken = _.find(tokens, token => {
- return token.address === fill.takerToken;
- });
- const makerToken = _.find(tokens, token => {
- return token.address === fill.makerToken;
- });
- // For now we don't show history items for orders using custom ERC20
- // tokens the client does not know how to display.
- // TODO: Try to retrieve the name/symbol of an unknown token in order to display it
- // Be sure to remove similar logic in trade_history.tsx
- if (_.isUndefined(takerToken) || _.isUndefined(makerToken)) {
- return null;
- }
+ public render() {
+ const fill = this.props.fill;
+ const tokens = _.values(this.props.tokenByAddress);
+ const takerToken = _.find(tokens, token => {
+ return token.address === fill.takerToken;
+ });
+ const makerToken = _.find(tokens, token => {
+ return token.address === fill.makerToken;
+ });
+ // For now we don't show history items for orders using custom ERC20
+ // tokens the client does not know how to display.
+ // TODO: Try to retrieve the name/symbol of an unknown token in order to display it
+ // Be sure to remove similar logic in trade_history.tsx
+ if (_.isUndefined(takerToken) || _.isUndefined(makerToken)) {
+ return null;
+ }
- const amountColStyle: React.CSSProperties = {
- 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 amountColStyle: React.CSSProperties = {
+ 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 \
lg-right-align md-right-align sm-center';
- return (
- <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-6 md-col-6 lg-pl3 md-pl3"
- style={{ fontSize: 12, fontWeight: 100 }}
- >
- <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" />
- <Party
- label="Taker"
- address={fill.taker}
- identiconDiameter={IDENTICON_DIAMETER}
- networkId={this.props.networkId}
- />
- </div>
- </div>
- <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 }}>
- <EtherScanIcon
- addressOrTxHash={fill.transactionHash}
- networkId={this.props.networkId}
- etherscanLinkSuffixes={EtherscanLinkSuffixes.Tx}
- />
- </div>
- </div>
- </div>
- </Paper>
- );
- }
- 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);
- let exchangeRate = filledTakerTokenAmountInUnits.div(filledMakerTokenAmountInUnits);
- const fillMakerTokenAmount = ZeroEx.toBaseUnitAmount(filledMakerTokenAmountInUnits, makerToken.decimals);
+ return (
+ <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-6 md-col-6 lg-pl3 md-pl3"
+ style={{ fontSize: 12, fontWeight: 100 }}
+ >
+ <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" />
+ <Party
+ label="Taker"
+ address={fill.taker}
+ identiconDiameter={IDENTICON_DIAMETER}
+ networkId={this.props.networkId}
+ />
+ </div>
+ </div>
+ <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 }}>
+ <EtherScanIcon
+ addressOrTxHash={fill.transactionHash}
+ networkId={this.props.networkId}
+ etherscanLinkSuffixes={EtherscanLinkSuffixes.Tx}
+ />
+ </div>
+ </div>
+ </div>
+ </Paper>
+ );
+ }
+ 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);
+ let exchangeRate = filledTakerTokenAmountInUnits.div(filledMakerTokenAmountInUnits);
+ const fillMakerTokenAmount = ZeroEx.toBaseUnitAmount(filledMakerTokenAmountInUnits, makerToken.decimals);
- let receiveAmount;
- let receiveToken;
- let givenAmount;
- let givenToken;
- if (this.props.userAddress === fill.maker && this.props.userAddress === fill.taker) {
- receiveAmount = new BigNumber(0);
- givenAmount = new BigNumber(0);
- receiveToken = makerToken;
- givenToken = takerToken;
- } else if (this.props.userAddress === fill.maker) {
- receiveAmount = fill.filledTakerTokenAmount;
- givenAmount = fillMakerTokenAmount;
- receiveToken = takerToken;
- givenToken = makerToken;
- exchangeRate = new BigNumber(1).div(exchangeRate);
- } else if (this.props.userAddress === fill.taker) {
- receiveAmount = fillMakerTokenAmount;
- givenAmount = fill.filledTakerTokenAmount;
- receiveToken = makerToken;
- givenToken = takerToken;
- } else {
- // This condition should never be hit
- throw new Error("Found Fill that wasn't performed by this user");
- }
+ let receiveAmount;
+ let receiveToken;
+ let givenAmount;
+ let givenToken;
+ if (this.props.userAddress === fill.maker && this.props.userAddress === fill.taker) {
+ receiveAmount = new BigNumber(0);
+ givenAmount = new BigNumber(0);
+ receiveToken = makerToken;
+ givenToken = takerToken;
+ } else if (this.props.userAddress === fill.maker) {
+ receiveAmount = fill.filledTakerTokenAmount;
+ givenAmount = fillMakerTokenAmount;
+ receiveToken = takerToken;
+ givenToken = makerToken;
+ exchangeRate = new BigNumber(1).div(exchangeRate);
+ } else if (this.props.userAddress === fill.taker) {
+ receiveAmount = fillMakerTokenAmount;
+ givenAmount = fill.filledTakerTokenAmount;
+ receiveToken = makerToken;
+ givenToken = takerToken;
+ } else {
+ // This condition should never be hit
+ 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>
- <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 }}>
- {exchangeRate.toFixed(PRECISION)} {givenToken.symbol}/{receiveToken.symbol}
- </div>
- </div>
- );
- }
- private _renderDate() {
- const blockMoment = moment.unix(this.props.fill.blockTimestamp);
- if (!blockMoment.isValid()) {
- return null;
- }
+ return (
+ <div>
+ <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>
+ <div style={{ color: colors.grey400, fontSize: 14 }}>
+ {exchangeRate.toFixed(PRECISION)} {givenToken.symbol}/{receiveToken.symbol}
+ </div>
+ </div>
+ );
+ }
+ private _renderDate() {
+ const blockMoment = moment.unix(this.props.fill.blockTimestamp);
+ if (!blockMoment.isValid()) {
+ return null;
+ }
- const dayOfMonth = blockMoment.format('D');
- const monthAbreviation = blockMoment.format('MMM');
- const formattedBlockDate = blockMoment.format('H:mmA - MMMM D, YYYY');
- const dateTooltipId = `${this.props.fill.transactionHash}-date`;
+ const dayOfMonth = blockMoment.format('D');
+ const monthAbreviation = blockMoment.format('MMM');
+ const formattedBlockDate = blockMoment.format('H:mmA - MMMM D, YYYY');
+ 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>
- <ReactTooltip id={dateTooltipId}>{formattedBlockDate}</ReactTooltip>
- </div>
- );
- }
- private _renderAmount(amount: BigNumber, symbol: string, decimals: number) {
- const unitAmount = ZeroEx.toUnitAmount(amount, decimals);
- return (
- <span>
- {unitAmount.toFixed(PRECISION)} {symbol}
- </span>
- );
- }
+ 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>
+ <ReactTooltip id={dateTooltipId}>{formattedBlockDate}</ReactTooltip>
+ </div>
+ );
+ }
+ private _renderAmount(amount: BigNumber, symbol: string, decimals: number) {
+ const unitAmount = ZeroEx.toUnitAmount(amount, decimals);
+ return (
+ <span>
+ {unitAmount.toFixed(PRECISION)} {symbol}
+ </span>
+ );
+ }
}
diff --git a/packages/website/ts/components/ui/alert.tsx b/packages/website/ts/components/ui/alert.tsx
index 54881b499..91ef8f76e 100644
--- a/packages/website/ts/components/ui/alert.tsx
+++ b/packages/website/ts/components/ui/alert.tsx
@@ -3,23 +3,23 @@ import { AlertTypes } from 'ts/types';
import { colors } from 'ts/utils/colors';
interface AlertProps {
- type: AlertTypes;
- message: string | React.ReactNode;
+ type: AlertTypes;
+ message: string | React.ReactNode;
}
export function Alert(props: AlertProps) {
- const isAlert = props.type === AlertTypes.ERROR;
- const errMsgStyles = {
- background: isAlert ? colors.red200 : colors.lightestGreen,
- color: colors.white,
- marginTop: 10,
- padding: 4,
- paddingLeft: 8,
- };
+ const isAlert = props.type === AlertTypes.ERROR;
+ const errMsgStyles = {
+ background: isAlert ? colors.red200 : colors.lightestGreen,
+ color: colors.white,
+ marginTop: 10,
+ padding: 4,
+ paddingLeft: 8,
+ };
- return (
- <div className="rounded center" style={errMsgStyles}>
- {props.message}
- </div>
- );
+ return (
+ <div className="rounded center" style={errMsgStyles}>
+ {props.message}
+ </div>
+ );
}
diff --git a/packages/website/ts/components/ui/badge.tsx b/packages/website/ts/components/ui/badge.tsx
index 7f7ea006e..1d2d81af6 100644
--- a/packages/website/ts/components/ui/badge.tsx
+++ b/packages/website/ts/components/ui/badge.tsx
@@ -3,55 +3,55 @@ import * as React from 'react';
import { Styles } from 'ts/types';
const styles: Styles = {
- badge: {
- width: 50,
- fontSize: 11,
- height: 10,
- borderRadius: 5,
- marginTop: 25,
- lineHeight: 0.9,
- fontFamily: 'Roboto Mono',
- marginLeft: 3,
- marginRight: 3,
- },
+ badge: {
+ width: 50,
+ fontSize: 11,
+ height: 10,
+ borderRadius: 5,
+ marginTop: 25,
+ lineHeight: 0.9,
+ fontFamily: 'Roboto Mono',
+ marginLeft: 3,
+ marginRight: 3,
+ },
};
interface BadgeProps {
- title: string;
- backgroundColor: string;
+ title: string;
+ backgroundColor: string;
}
interface BadgeState {
- isHovering: boolean;
+ isHovering: boolean;
}
export class Badge extends React.Component<BadgeProps, BadgeState> {
- constructor(props: BadgeProps) {
- super(props);
- this.state = {
- isHovering: false,
- };
- }
- public render() {
- const badgeStyle = {
- ...styles.badge,
- backgroundColor: this.props.backgroundColor,
- opacity: this.state.isHovering ? 0.7 : 1,
- };
- return (
- <div
- className="p1 center"
- style={badgeStyle}
- onMouseOver={this._setHoverState.bind(this, true)}
- onMouseOut={this._setHoverState.bind(this, false)}
- >
- {this.props.title}
- </div>
- );
- }
- private _setHoverState(isHovering: boolean) {
- this.setState({
- isHovering,
- });
- }
+ constructor(props: BadgeProps) {
+ super(props);
+ this.state = {
+ isHovering: false,
+ };
+ }
+ public render() {
+ const badgeStyle = {
+ ...styles.badge,
+ backgroundColor: this.props.backgroundColor,
+ opacity: this.state.isHovering ? 0.7 : 1,
+ };
+ return (
+ <div
+ className="p1 center"
+ style={badgeStyle}
+ onMouseOver={this._setHoverState.bind(this, true)}
+ onMouseOut={this._setHoverState.bind(this, false)}
+ >
+ {this.props.title}
+ </div>
+ );
+ }
+ 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 df55e0922..72ab77e1f 100644
--- a/packages/website/ts/components/ui/copy_icon.tsx
+++ b/packages/website/ts/components/ui/copy_icon.tsx
@@ -6,74 +6,74 @@ import ReactTooltip = require('react-tooltip');
import { colors } from 'ts/utils/colors';
interface CopyIconProps {
- data: string;
- callToAction?: string;
+ data: string;
+ callToAction?: string;
}
interface CopyIconState {
- isHovering: boolean;
+ isHovering: boolean;
}
export class CopyIcon extends React.Component<CopyIconProps, CopyIconState> {
- private _copyTooltipTimeoutId: number;
- private _copyable: HTMLInputElement;
- constructor(props: CopyIconProps) {
- super(props);
- this.state = {
- isHovering: false,
- };
- }
- public componentDidUpdate() {
- // Remove tooltip if hover away
- 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>}
- </div>
- </CopyToClipboard>
- <ReactTooltip id="copy">Copied!</ReactTooltip>
- </div>
- );
- }
- private _setRefToProperty(el: HTMLInputElement) {
- this._copyable = el;
- }
- private _setHoverState(isHovering: boolean) {
- this.setState({
- isHovering,
- });
- }
- private _onCopy() {
- if (this._copyTooltipTimeoutId) {
- clearInterval(this._copyTooltipTimeoutId);
- }
+ private _copyTooltipTimeoutId: number;
+ private _copyable: HTMLInputElement;
+ constructor(props: CopyIconProps) {
+ super(props);
+ this.state = {
+ isHovering: false,
+ };
+ }
+ public componentDidUpdate() {
+ // Remove tooltip if hover away
+ 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>}
+ </div>
+ </CopyToClipboard>
+ <ReactTooltip id="copy">Copied!</ReactTooltip>
+ </div>
+ );
+ }
+ private _setRefToProperty(el: HTMLInputElement) {
+ this._copyable = el;
+ }
+ private _setHoverState(isHovering: boolean) {
+ this.setState({
+ isHovering,
+ });
+ }
+ private _onCopy() {
+ if (this._copyTooltipTimeoutId) {
+ clearInterval(this._copyTooltipTimeoutId);
+ }
- const tooltipLifespanMs = 1000;
- this._copyTooltipTimeoutId = window.setTimeout(() => {
- this._hideTooltip();
- }, tooltipLifespanMs);
- }
- private _hideTooltip() {
- ReactTooltip.hide(ReactDOM.findDOMNode(this._copyable));
- }
+ const tooltipLifespanMs = 1000;
+ this._copyTooltipTimeoutId = window.setTimeout(() => {
+ this._hideTooltip();
+ }, tooltipLifespanMs);
+ }
+ 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 a578fb4f9..64f88f318 100644
--- a/packages/website/ts/components/ui/drop_down_menu_item.tsx
+++ b/packages/website/ts/components/ui/drop_down_menu_item.tsx
@@ -6,99 +6,99 @@ import { colors } from 'ts/utils/colors';
const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300;
const DEFAULT_STYLE = {
- fontSize: 14,
+ fontSize: 14,
};
interface DropDownMenuItemProps {
- title: string;
- subMenuItems: React.ReactNode[];
- style?: React.CSSProperties;
- menuItemStyle?: React.CSSProperties;
- isNightVersion?: boolean;
+ title: string;
+ subMenuItems: React.ReactNode[];
+ style?: React.CSSProperties;
+ menuItemStyle?: React.CSSProperties;
+ isNightVersion?: boolean;
}
interface DropDownMenuItemState {
- isDropDownOpen: boolean;
- anchorEl?: HTMLInputElement;
+ isDropDownOpen: boolean;
+ anchorEl?: HTMLInputElement;
}
export class DropDownMenuItem extends React.Component<DropDownMenuItemProps, DropDownMenuItemState> {
- public static defaultProps: Partial<DropDownMenuItemProps> = {
- style: DEFAULT_STYLE,
- menuItemStyle: DEFAULT_STYLE,
- isNightVersion: false,
- };
- private _isHovering: boolean;
- private _popoverCloseCheckIntervalId: number;
- constructor(props: DropDownMenuItemProps) {
- super(props);
- this.state = {
- isDropDownOpen: false,
- };
- }
- public componentDidMount() {
- this._popoverCloseCheckIntervalId = window.setInterval(() => {
- this._checkIfShouldClosePopover();
- }, CHECK_CLOSE_POPOVER_INTERVAL_MS);
- }
- public componentWillUnmount() {
- window.clearInterval(this._popoverCloseCheckIntervalId);
- }
- public render() {
- const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color;
- return (
- <div
- 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>
- </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)}
- useLayerForClickAway={false}
- >
- <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 _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>) {
- if (this.state.isDropDownOpen) {
- return; // noop
- }
+ public static defaultProps: Partial<DropDownMenuItemProps> = {
+ style: DEFAULT_STYLE,
+ menuItemStyle: DEFAULT_STYLE,
+ isNightVersion: false,
+ };
+ private _isHovering: boolean;
+ private _popoverCloseCheckIntervalId: number;
+ constructor(props: DropDownMenuItemProps) {
+ super(props);
+ this.state = {
+ isDropDownOpen: false,
+ };
+ }
+ public componentDidMount() {
+ this._popoverCloseCheckIntervalId = window.setInterval(() => {
+ this._checkIfShouldClosePopover();
+ }, CHECK_CLOSE_POPOVER_INTERVAL_MS);
+ }
+ public componentWillUnmount() {
+ window.clearInterval(this._popoverCloseCheckIntervalId);
+ }
+ public render() {
+ const colorStyle = this.props.isNightVersion ? 'white' : this.props.style.color;
+ return (
+ <div
+ 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>
+ </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)}
+ useLayerForClickAway={false}
+ >
+ <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 _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>) {
+ if (this.state.isDropDownOpen) {
+ return; // noop
+ }
- this.setState({
- isDropDownOpen: true,
- anchorEl: event.currentTarget,
- });
- }
- private _onHoverOff(event: React.FormEvent<HTMLInputElement>) {
- this._isHovering = false;
- }
- private _checkIfShouldClosePopover() {
- if (!this.state.isDropDownOpen || this._isHovering) {
- return; // noop
- }
- this._closePopover();
- }
- private _closePopover() {
- this.setState({
- isDropDownOpen: false,
- });
- }
+ this.setState({
+ isDropDownOpen: true,
+ anchorEl: event.currentTarget,
+ });
+ }
+ private _onHoverOff(event: React.FormEvent<HTMLInputElement>) {
+ this._isHovering = false;
+ }
+ private _checkIfShouldClosePopover() {
+ if (!this.state.isDropDownOpen || this._isHovering) {
+ return; // noop
+ }
+ this._closePopover();
+ }
+ private _closePopover() {
+ this.setState({
+ isDropDownOpen: false,
+ });
+ }
}
diff --git a/packages/website/ts/components/ui/ethereum_address.tsx b/packages/website/ts/components/ui/ethereum_address.tsx
index b75d97e39..ba51135be 100644
--- a/packages/website/ts/components/ui/ethereum_address.tsx
+++ b/packages/website/ts/components/ui/ethereum_address.tsx
@@ -5,26 +5,26 @@ import { EtherscanLinkSuffixes } from 'ts/types';
import { utils } from 'ts/utils/utils';
interface EthereumAddressProps {
- address: string;
- networkId: number;
+ address: string;
+ networkId: number;
}
export const EthereumAddress = (props: EthereumAddressProps) => {
- const tooltipId = `${props.address}-ethereum-address`;
- const truncatedAddress = utils.getAddressBeginAndEnd(props.address);
- return (
- <div>
- <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}
- />
- </div>
- <ReactTooltip id={tooltipId}>{props.address}</ReactTooltip>
- </div>
- );
+ const tooltipId = `${props.address}-ethereum-address`;
+ const truncatedAddress = utils.getAddressBeginAndEnd(props.address);
+ return (
+ <div>
+ <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}
+ />
+ </div>
+ <ReactTooltip id={tooltipId}>{props.address}</ReactTooltip>
+ </div>
+ );
};
diff --git a/packages/website/ts/components/ui/etherscan_icon.tsx b/packages/website/ts/components/ui/etherscan_icon.tsx
index 3b17bd0fa..5b224c3e1 100644
--- a/packages/website/ts/components/ui/etherscan_icon.tsx
+++ b/packages/website/ts/components/ui/etherscan_icon.tsx
@@ -6,36 +6,36 @@ import { colors } from 'ts/utils/colors';
import { utils } from 'ts/utils/utils';
interface EtherScanIconProps {
- addressOrTxHash: string;
- etherscanLinkSuffixes: EtherscanLinkSuffixes;
- networkId: number;
+ addressOrTxHash: string;
+ etherscanLinkSuffixes: EtherscanLinkSuffixes;
+ networkId: number;
}
export const EtherScanIcon = (props: EtherScanIconProps) => {
- const etherscanLinkIfExists = utils.getEtherScanLinkIfExists(
- props.addressOrTxHash,
- props.networkId,
- EtherscanLinkSuffixes.Address,
- );
- const transactionTooltipId = `${props.addressOrTxHash}-etherscan-icon-tooltip`;
- return (
- <div className="inline">
- {!_.isUndefined(etherscanLinkIfExists) ? (
- <a href={etherscanLinkIfExists} target="_blank">
- {renderIcon()}
- </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>
- );
+ const etherscanLinkIfExists = utils.getEtherScanLinkIfExists(
+ props.addressOrTxHash,
+ props.networkId,
+ EtherscanLinkSuffixes.Address,
+ );
+ const transactionTooltipId = `${props.addressOrTxHash}-etherscan-icon-tooltip`;
+ return (
+ <div className="inline">
+ {!_.isUndefined(etherscanLinkIfExists) ? (
+ <a href={etherscanLinkIfExists} target="_blank">
+ {renderIcon()}
+ </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 f3d9410f6..6d321bb46 100644
--- a/packages/website/ts/components/ui/fake_text_field.tsx
+++ b/packages/website/ts/components/ui/fake_text_field.tsx
@@ -3,32 +3,32 @@ import { InputLabel } from 'ts/components/ui/input_label';
import { Styles } from 'ts/types';
const styles: Styles = {
- hr: {
- borderBottom: '1px solid rgb(224, 224, 224)',
- borderLeft: 'none rgb(224, 224, 224)',
- borderRight: 'none rgb(224, 224, 224)',
- borderTop: 'none rgb(224, 224, 224)',
- bottom: 6,
- boxSizing: 'content-box',
- margin: 0,
- position: 'absolute',
- width: '100%',
- },
+ hr: {
+ borderBottom: '1px solid rgb(224, 224, 224)',
+ borderLeft: 'none rgb(224, 224, 224)',
+ borderRight: 'none rgb(224, 224, 224)',
+ borderTop: 'none rgb(224, 224, 224)',
+ bottom: 6,
+ boxSizing: 'content-box',
+ margin: 0,
+ position: 'absolute',
+ width: '100%',
+ },
};
interface FakeTextFieldProps {
- label?: React.ReactNode | string;
- children?: any;
+ label?: React.ReactNode | string;
+ children?: any;
}
export function FakeTextField(props: FakeTextFieldProps) {
- return (
- <div className="relative">
- {props.label !== '' && <InputLabel text={props.label} />}
- <div className="pb2" style={{ height: 23 }}>
- {props.children}
- </div>
- <hr style={styles.hr} />
- </div>
- );
+ return (
+ <div className="relative">
+ {props.label !== '' && <InputLabel text={props.label} />}
+ <div className="pb2" style={{ height: 23 }}>
+ {props.children}
+ </div>
+ <hr style={styles.hr} />
+ </div>
+ );
}
diff --git a/packages/website/ts/components/ui/flash_message.tsx b/packages/website/ts/components/ui/flash_message.tsx
index 2cb1fc764..57a66d21f 100644
--- a/packages/website/ts/components/ui/flash_message.tsx
+++ b/packages/website/ts/components/ui/flash_message.tsx
@@ -6,35 +6,35 @@ import { Dispatcher } from 'ts/redux/dispatcher';
const SHOW_DURATION_MS = 4000;
interface FlashMessageProps {
- dispatcher: Dispatcher;
- flashMessage?: string | React.ReactNode;
- showDurationMs?: number;
- bodyStyle?: React.CSSProperties;
+ dispatcher: Dispatcher;
+ flashMessage?: string | React.ReactNode;
+ showDurationMs?: number;
+ bodyStyle?: React.CSSProperties;
}
interface FlashMessageState {}
export class FlashMessage extends React.Component<FlashMessageProps, FlashMessageState> {
- public static defaultProps: Partial<FlashMessageProps> = {
- showDurationMs: SHOW_DURATION_MS,
- bodyStyle: {},
- };
- public render() {
- if (!_.isUndefined(this.props.flashMessage)) {
- return (
- <Snackbar
- open={true}
- message={this.props.flashMessage}
- autoHideDuration={this.props.showDurationMs}
- onRequestClose={this._onClose.bind(this)}
- bodyStyle={this.props.bodyStyle}
- />
- );
- } else {
- return null;
- }
- }
- private _onClose() {
- this.props.dispatcher.hideFlashMessage();
- }
+ public static defaultProps: Partial<FlashMessageProps> = {
+ showDurationMs: SHOW_DURATION_MS,
+ bodyStyle: {},
+ };
+ public render() {
+ if (!_.isUndefined(this.props.flashMessage)) {
+ return (
+ <Snackbar
+ open={true}
+ message={this.props.flashMessage}
+ autoHideDuration={this.props.showDurationMs}
+ onRequestClose={this._onClose.bind(this)}
+ bodyStyle={this.props.bodyStyle}
+ />
+ );
+ } else {
+ return null;
+ }
+ }
+ 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 d827eebb9..c946a70cb 100644
--- a/packages/website/ts/components/ui/help_tooltip.tsx
+++ b/packages/website/ts/components/ui/help_tooltip.tsx
@@ -2,21 +2,21 @@ import * as React from 'react';
import ReactTooltip = require('react-tooltip');
interface HelpTooltipProps {
- style?: React.CSSProperties;
- explanation: React.ReactNode;
+ style?: React.CSSProperties;
+ explanation: React.ReactNode;
}
export const HelpTooltip = (props: HelpTooltipProps) => {
- return (
- <div
- style={{ ...props.style }}
- className="inline-block"
- data-tip={props.explanation}
- data-for="helpTooltip"
- data-multiline={true}
- >
- <i style={{ fontSize: 16 }} className="zmdi zmdi-help" />
- <ReactTooltip id="helpTooltip" />
- </div>
- );
+ return (
+ <div
+ style={{ ...props.style }}
+ className="inline-block"
+ data-tip={props.explanation}
+ data-for="helpTooltip"
+ data-multiline={true}
+ >
+ <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 bad6c2a78..bad7a657d 100644
--- a/packages/website/ts/components/ui/identicon.tsx
+++ b/packages/website/ts/components/ui/identicon.tsx
@@ -4,45 +4,45 @@ import * as React from 'react';
import { constants } from 'ts/utils/constants';
interface IdenticonProps {
- address: string;
- diameter: number;
- style?: React.CSSProperties;
+ address: string;
+ diameter: number;
+ style?: React.CSSProperties;
}
interface IdenticonState {}
export class Identicon extends React.Component<IdenticonProps, IdenticonState> {
- public static defaultProps: Partial<IdenticonProps> = {
- style: {},
- };
- public render() {
- let address = this.props.address;
- if (_.isEmpty(address)) {
- address = constants.NULL_ADDRESS;
- }
- const diameter = this.props.diameter;
- const icon = blockies({
- seed: address.toLowerCase(),
- });
- return (
- <div
- className="circle mx-auto relative transitionFix"
- style={{
- width: diameter,
- height: diameter,
- overflow: 'hidden',
- ...this.props.style,
- }}
- >
- <img
- src={icon.toDataURL()}
- style={{
- width: diameter,
- height: diameter,
- imageRendering: 'pixelated',
- }}
- />
- </div>
- );
- }
+ public static defaultProps: Partial<IdenticonProps> = {
+ style: {},
+ };
+ public render() {
+ let address = this.props.address;
+ if (_.isEmpty(address)) {
+ address = constants.NULL_ADDRESS;
+ }
+ const diameter = this.props.diameter;
+ const icon = blockies({
+ seed: address.toLowerCase(),
+ });
+ return (
+ <div
+ className="circle mx-auto relative transitionFix"
+ style={{
+ width: diameter,
+ height: diameter,
+ overflow: 'hidden',
+ ...this.props.style,
+ }}
+ >
+ <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 e2009ad20..1cbda6692 100644
--- a/packages/website/ts/components/ui/input_label.tsx
+++ b/packages/website/ts/components/ui/input_label.tsx
@@ -2,24 +2,24 @@ import * as React from 'react';
import { colors } from 'ts/utils/colors';
export interface InputLabelProps {
- text: string | Element | React.ReactNode;
+ text: string | Element | React.ReactNode;
}
const styles = {
- label: {
- color: colors.grey,
- fontSize: 12,
- pointerEvents: 'none',
- textAlign: 'left',
- transform: 'scale(0.75) translate(0px, -28px)',
- transformOrigin: 'left top 0px',
- transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',
- userSelect: 'none',
- width: 240,
- zIndex: 1,
- },
+ label: {
+ color: colors.grey,
+ fontSize: 12,
+ pointerEvents: 'none',
+ textAlign: 'left',
+ transform: 'scale(0.75) translate(0px, -28px)',
+ transformOrigin: 'left top 0px',
+ transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',
+ userSelect: 'none',
+ width: 240,
+ zIndex: 1,
+ },
};
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/lifecycle_raised_button.tsx b/packages/website/ts/components/ui/lifecycle_raised_button.tsx
index 8ff856a75..fd23912f1 100644
--- a/packages/website/ts/components/ui/lifecycle_raised_button.tsx
+++ b/packages/website/ts/components/ui/lifecycle_raised_button.tsx
@@ -7,97 +7,97 @@ 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;
- onClickAsyncFn: () => Promise<boolean>;
- backgroundColor?: string;
- labelColor?: string;
+ isHidden?: boolean;
+ isDisabled?: boolean;
+ isPrimary?: boolean;
+ labelReady: React.ReactNode | string;
+ labelLoading: React.ReactNode | string;
+ labelComplete: React.ReactNode | string;
+ onClickAsyncFn: () => Promise<boolean>;
+ backgroundColor?: string;
+ labelColor?: string;
}
interface LifeCycleRaisedButtonState {
- buttonState: ButtonState;
+ buttonState: ButtonState;
}
export class LifeCycleRaisedButton extends React.Component<LifeCycleRaisedButtonProps, LifeCycleRaisedButtonState> {
- public static defaultProps: Partial<LifeCycleRaisedButtonProps> = {
- isDisabled: false,
- backgroundColor: colors.white,
- labelColor: colors.darkGrey,
- };
- private _buttonTimeoutId: number;
- private _didUnmount: boolean;
- constructor(props: LifeCycleRaisedButtonProps) {
- super(props);
- this.state = {
- buttonState: ButtonState.READY,
- };
- }
- public componentWillUnmount() {
- clearTimeout(this._buttonTimeoutId);
- this._didUnmount = true;
- }
- public render() {
- if (this.props.isHidden) {
- return <span />;
- }
+ public static defaultProps: Partial<LifeCycleRaisedButtonProps> = {
+ isDisabled: false,
+ backgroundColor: colors.white,
+ labelColor: colors.darkGrey,
+ };
+ private _buttonTimeoutId: number;
+ private _didUnmount: boolean;
+ constructor(props: LifeCycleRaisedButtonProps) {
+ super(props);
+ this.state = {
+ buttonState: ButtonState.READY,
+ };
+ }
+ public componentWillUnmount() {
+ clearTimeout(this._buttonTimeoutId);
+ this._didUnmount = true;
+ }
+ public render() {
+ if (this.props.isHidden) {
+ return <span />;
+ }
- let label;
- switch (this.state.buttonState) {
- case ButtonState.READY:
- label = this.props.labelReady;
- break;
- case ButtonState.LOADING:
- label = this.props.labelLoading;
- break;
- case ButtonState.COMPLETE:
- label = this.props.labelComplete;
- break;
- default:
- throw utils.spawnSwitchErr('ButtonState', this.state.buttonState);
- }
- return (
- <RaisedButton
- primary={this.props.isPrimary}
- label={label}
- style={{ width: '100%' }}
- backgroundColor={this.props.backgroundColor}
- labelColor={this.props.labelColor}
- onTouchTap={this.onClickAsync.bind(this)}
- disabled={this.props.isDisabled || this.state.buttonState !== ButtonState.READY}
- />
- );
- }
- public async onClickAsync() {
- this.setState({
- buttonState: ButtonState.LOADING,
- });
- const didSucceed = await this.props.onClickAsyncFn();
- if (this._didUnmount) {
- return; // noop since unmount called before async callback returned.
- }
- if (didSucceed) {
- this.setState({
- buttonState: ButtonState.COMPLETE,
- });
- this._buttonTimeoutId = window.setTimeout(() => {
- this.setState({
- buttonState: ButtonState.READY,
- });
- }, COMPLETE_STATE_SHOW_LENGTH_MS);
- } else {
- this.setState({
- buttonState: ButtonState.READY,
- });
- }
- }
+ let label;
+ switch (this.state.buttonState) {
+ case ButtonState.READY:
+ label = this.props.labelReady;
+ break;
+ case ButtonState.LOADING:
+ label = this.props.labelLoading;
+ break;
+ case ButtonState.COMPLETE:
+ label = this.props.labelComplete;
+ break;
+ default:
+ throw utils.spawnSwitchErr('ButtonState', this.state.buttonState);
+ }
+ return (
+ <RaisedButton
+ primary={this.props.isPrimary}
+ label={label}
+ style={{ width: '100%' }}
+ backgroundColor={this.props.backgroundColor}
+ labelColor={this.props.labelColor}
+ onTouchTap={this.onClickAsync.bind(this)}
+ disabled={this.props.isDisabled || this.state.buttonState !== ButtonState.READY}
+ />
+ );
+ }
+ public async onClickAsync() {
+ this.setState({
+ buttonState: ButtonState.LOADING,
+ });
+ const didSucceed = await this.props.onClickAsyncFn();
+ if (this._didUnmount) {
+ return; // noop since unmount called before async callback returned.
+ }
+ if (didSucceed) {
+ this.setState({
+ buttonState: ButtonState.COMPLETE,
+ });
+ this._buttonTimeoutId = window.setTimeout(() => {
+ this.setState({
+ buttonState: ButtonState.READY,
+ });
+ }, COMPLETE_STATE_SHOW_LENGTH_MS);
+ } else {
+ this.setState({
+ buttonState: ButtonState.READY,
+ });
+ }
+ }
}
diff --git a/packages/website/ts/components/ui/loading.tsx b/packages/website/ts/components/ui/loading.tsx
index aa319e9e9..e9bfe3316 100644
--- a/packages/website/ts/components/ui/loading.tsx
+++ b/packages/website/ts/components/ui/loading.tsx
@@ -10,30 +10,30 @@ interface LoadingProps {}
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' }}>
- <Video
- autoPlay={true}
- loop={true}
- muted={true}
- controls={[]}
- poster="/images/loading_poster.png"
- >
- <source src="/videos/0xAnimation.mp4" type="video/mp4" />
- </Video>
- </div>
- )}
- <div className="center pt2" style={{ paddingBottom: 11 }}>
- Connecting to the blockchain...
- </div>
- </Paper>
- </div>
- );
- }
+ 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' }}>
+ <Video
+ autoPlay={true}
+ loop={true}
+ muted={true}
+ controls={[]}
+ poster="/images/loading_poster.png"
+ >
+ <source src="/videos/0xAnimation.mp4" type="video/mp4" />
+ </Video>
+ </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 3482f436c..956b5eae8 100644
--- a/packages/website/ts/components/ui/menu_item.tsx
+++ b/packages/website/ts/components/ui/menu_item.tsx
@@ -3,49 +3,49 @@ import * as React from 'react';
import { Link } from 'react-router-dom';
interface MenuItemProps {
- to: string;
- style?: React.CSSProperties;
- onClick?: () => void;
- className?: string;
+ to: string;
+ style?: React.CSSProperties;
+ onClick?: () => void;
+ className?: string;
}
interface MenuItemState {
- isHovering: boolean;
+ isHovering: boolean;
}
export class MenuItem extends React.Component<MenuItemProps, MenuItemState> {
- public static defaultProps: Partial<MenuItemProps> = {
- onClick: _.noop,
- className: '',
- };
- public constructor(props: MenuItemProps) {
- super(props);
- this.state = {
- isHovering: false,
- };
- }
- public render() {
- const menuItemStyles = {
- cursor: 'pointer',
- opacity: this.state.isHovering ? 0.5 : 1,
- };
- return (
- <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)}
- >
- {this.props.children}
- </div>
- </Link>
- );
- }
- private _onToggleHover(isHovering: boolean) {
- this.setState({
- isHovering,
- });
- }
+ public static defaultProps: Partial<MenuItemProps> = {
+ onClick: _.noop,
+ className: '',
+ };
+ public constructor(props: MenuItemProps) {
+ super(props);
+ this.state = {
+ isHovering: false,
+ };
+ }
+ public render() {
+ const menuItemStyles = {
+ cursor: 'pointer',
+ opacity: this.state.isHovering ? 0.5 : 1,
+ };
+ return (
+ <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)}
+ >
+ {this.props.children}
+ </div>
+ </Link>
+ );
+ }
+ 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 ca2577b61..ef3c7b425 100644
--- a/packages/website/ts/components/ui/party.tsx
+++ b/packages/website/ts/components/ui/party.tsx
@@ -11,129 +11,129 @@ const IMAGE_DIMENSION = 100;
const IDENTICON_DIAMETER = 95;
interface PartyProps {
- label: string;
- address: string;
- networkId: number;
- alternativeImage?: string;
- identiconDiameter?: number;
- identiconStyle?: React.CSSProperties;
- isInTokenRegistry?: boolean;
- hasUniqueNameAndSymbol?: boolean;
+ label: string;
+ address: string;
+ networkId: number;
+ alternativeImage?: string;
+ identiconDiameter?: number;
+ identiconStyle?: React.CSSProperties;
+ isInTokenRegistry?: boolean;
+ hasUniqueNameAndSymbol?: boolean;
}
interface PartyState {}
export class Party extends React.Component<PartyProps, PartyState> {
- public static defaultProps: Partial<PartyProps> = {
- identiconStyle: {},
- identiconDiameter: IDENTICON_DIAMETER,
- };
- public render() {
- const label = this.props.label;
- const address = this.props.address;
- const identiconDiameter = this.props.identiconDiameter;
- const emptyIdenticonStyles = {
- width: identiconDiameter,
- height: identiconDiameter,
- backgroundColor: 'lightgray',
- marginTop: 13,
- marginBottom: 10,
- };
- const tokenImageStyle = {
- width: IMAGE_DIMENSION,
- height: IMAGE_DIMENSION,
- };
- const etherscanLinkIfExists = utils.getEtherScanLinkIfExists(
- 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 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 }}>
- <EthereumAddress address={address} networkId={this.props.networkId} />
- </div>
- {!_.isUndefined(this.props.isInTokenRegistry) && (
- <div>
- <div
- data-tip={true}
- data-for={registeredTooltipId}
- className="mx-auto"
- style={{ fontSize: 13, width: 127 }}
- >
- <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 ? (
- <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>
- 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>
- </div>
- </div>
- )}
- </div>
- </div>
- );
- }
+ public static defaultProps: Partial<PartyProps> = {
+ identiconStyle: {},
+ identiconDiameter: IDENTICON_DIAMETER,
+ };
+ public render() {
+ const label = this.props.label;
+ const address = this.props.address;
+ const identiconDiameter = this.props.identiconDiameter;
+ const emptyIdenticonStyles = {
+ width: identiconDiameter,
+ height: identiconDiameter,
+ backgroundColor: 'lightgray',
+ marginTop: 13,
+ marginBottom: 10,
+ };
+ const tokenImageStyle = {
+ width: IMAGE_DIMENSION,
+ height: IMAGE_DIMENSION,
+ };
+ const etherscanLinkIfExists = utils.getEtherScanLinkIfExists(
+ 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 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 }}>
+ <EthereumAddress address={address} networkId={this.props.networkId} />
+ </div>
+ {!_.isUndefined(this.props.isInTokenRegistry) && (
+ <div>
+ <div
+ data-tip={true}
+ data-for={registeredTooltipId}
+ className="mx-auto"
+ style={{ fontSize: 13, width: 127 }}
+ >
+ <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 ? (
+ <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>
+ 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>
+ </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 a5e7a22ce..638683427 100644
--- a/packages/website/ts/components/ui/required_label.tsx
+++ b/packages/website/ts/components/ui/required_label.tsx
@@ -2,14 +2,14 @@ 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>
- );
+ return (
+ <span>
+ {props.label}
+ <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 81744196d..9f1fd5a13 100644
--- a/packages/website/ts/components/ui/simple_loading.tsx
+++ b/packages/website/ts/components/ui/simple_loading.tsx
@@ -2,16 +2,16 @@ import CircularProgress from 'material-ui/CircularProgress';
import * as React from 'react';
export interface SimpleLoadingProps {
- message: string;
+ message: string;
}
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 }}>
- <CircularProgress />
- <div className="pt3 pb3">{props.message}</div>
- </div>
- </div>
- );
+ return (
+ <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>
+ </div>
+ );
};
diff --git a/packages/website/ts/components/ui/swap_icon.tsx b/packages/website/ts/components/ui/swap_icon.tsx
index c41592287..99e3450de 100644
--- a/packages/website/ts/components/ui/swap_icon.tsx
+++ b/packages/website/ts/components/ui/swap_icon.tsx
@@ -3,40 +3,40 @@ import * as React from 'react';
import { colors } from 'ts/utils/colors';
interface SwapIconProps {
- swapTokensFn: () => void;
+ swapTokensFn: () => void;
}
interface SwapIconState {
- isHovering: boolean;
+ isHovering: boolean;
}
export class SwapIcon extends React.Component<SwapIconProps, SwapIconState> {
- public constructor(props: SwapIconProps) {
- super(props);
- this.state = {
- isHovering: false,
- };
- }
- public render() {
- const swapStyles = {
- color: this.state.isHovering ? colors.amber600 : colors.amber800,
- fontSize: 50,
- };
- return (
- <div
- className="mx-auto pt4"
- style={{ cursor: 'pointer', height: 50, width: 37.5 }}
- onClick={this.props.swapTokensFn}
- onMouseEnter={this._onToggleHover.bind(this, true)}
- onMouseLeave={this._onToggleHover.bind(this, false)}
- >
- <i style={swapStyles} className="zmdi zmdi-swap" />
- </div>
- );
- }
- private _onToggleHover(isHovering: boolean) {
- this.setState({
- isHovering,
- });
- }
+ public constructor(props: SwapIconProps) {
+ super(props);
+ this.state = {
+ isHovering: false,
+ };
+ }
+ public render() {
+ const swapStyles = {
+ color: this.state.isHovering ? colors.amber600 : colors.amber800,
+ fontSize: 50,
+ };
+ return (
+ <div
+ className="mx-auto pt4"
+ style={{ cursor: 'pointer', height: 50, width: 37.5 }}
+ onClick={this.props.swapTokensFn}
+ onMouseEnter={this._onToggleHover.bind(this, true)}
+ onMouseLeave={this._onToggleHover.bind(this, false)}
+ >
+ <i style={swapStyles} className="zmdi zmdi-swap" />
+ </div>
+ );
+ }
+ 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 ff57a96de..a729821ce 100644
--- a/packages/website/ts/components/ui/token_icon.tsx
+++ b/packages/website/ts/components/ui/token_icon.tsx
@@ -4,24 +4,24 @@ import { Identicon } from 'ts/components/ui/identicon';
import { Token } from 'ts/types';
interface TokenIconProps {
- token: Token;
- diameter: number;
+ token: Token;
+ diameter: number;
}
interface TokenIconState {}
export class TokenIcon extends React.Component<TokenIconProps, TokenIconState> {
- public render() {
- const token = this.props.token;
- const diameter = this.props.diameter;
- return (
- <div>
- {token.isRegistered && !_.isUndefined(token.iconUrl) ? (
- <img style={{ width: diameter, height: diameter }} src={token.iconUrl} />
- ) : (
- <Identicon address={token.address} diameter={diameter} />
- )}
- </div>
- );
- }
+ public render() {
+ const token = this.props.token;
+ const diameter = this.props.diameter;
+ return (
+ <div>
+ {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 092954086..a9779ac62 100644
--- a/packages/website/ts/components/visual_order.tsx
+++ b/packages/website/ts/components/visual_order.tsx
@@ -8,69 +8,69 @@ import { utils } from 'ts/utils/utils';
const PRECISION = 5;
interface VisualOrderProps {
- orderTakerAddress: string;
- orderMakerAddress: string;
- makerAssetToken: AssetToken;
- takerAssetToken: AssetToken;
- makerToken: Token;
- takerToken: Token;
- networkId: number;
- tokenByAddress: TokenByAddress;
- isMakerTokenAddressInRegistry: boolean;
- isTakerTokenAddressInRegistry: boolean;
+ orderTakerAddress: string;
+ orderMakerAddress: string;
+ makerAssetToken: AssetToken;
+ takerAssetToken: AssetToken;
+ makerToken: Token;
+ takerToken: Token;
+ networkId: number;
+ tokenByAddress: TokenByAddress;
+ isMakerTokenAddressInRegistry: boolean;
+ isTakerTokenAddressInRegistry: boolean;
}
interface VisualOrderState {}
export class VisualOrder extends React.Component<VisualOrderProps, VisualOrderState> {
- public render() {
- const allTokens = _.values(this.props.tokenByAddress);
- const makerImage = this.props.makerToken.iconUrl;
- const takerImage = this.props.takerToken.iconUrl;
- return (
- <div>
- <div className="clearfix">
- <div className="col col-5 center">
- <Party
- label="Send"
- address={this.props.takerToken.address}
- alternativeImage={takerImage}
- networkId={this.props.networkId}
- isInTokenRegistry={this.props.isTakerTokenAddressInRegistry}
- hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, this.props.takerToken)}
- />
- </div>
- <div className="col col-2 center pt1">
- <div className="pb1">
- {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 }} />
- </div>
- <div className="pt1">
- {this._renderAmount(this.props.makerAssetToken, this.props.makerToken)}
- </div>
- </div>
- <div className="col col-5 center">
- <Party
- label="Receive"
- address={this.props.makerToken.address}
- alternativeImage={makerImage}
- networkId={this.props.networkId}
- isInTokenRegistry={this.props.isMakerTokenAddressInRegistry}
- hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, this.props.makerToken)}
- />
- </div>
- </div>
- </div>
- );
- }
- private _renderAmount(assetToken: AssetToken, token: Token) {
- const unitAmount = ZeroEx.toUnitAmount(assetToken.amount, token.decimals);
- return (
- <div style={{ fontSize: 13 }}>
- {unitAmount.toNumber().toFixed(PRECISION)} {token.symbol}
- </div>
- );
- }
+ public render() {
+ const allTokens = _.values(this.props.tokenByAddress);
+ const makerImage = this.props.makerToken.iconUrl;
+ const takerImage = this.props.takerToken.iconUrl;
+ return (
+ <div>
+ <div className="clearfix">
+ <div className="col col-5 center">
+ <Party
+ label="Send"
+ address={this.props.takerToken.address}
+ alternativeImage={takerImage}
+ networkId={this.props.networkId}
+ isInTokenRegistry={this.props.isTakerTokenAddressInRegistry}
+ hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, this.props.takerToken)}
+ />
+ </div>
+ <div className="col col-2 center pt1">
+ <div className="pb1">
+ {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 }} />
+ </div>
+ <div className="pt1">
+ {this._renderAmount(this.props.makerAssetToken, this.props.makerToken)}
+ </div>
+ </div>
+ <div className="col col-5 center">
+ <Party
+ label="Receive"
+ address={this.props.makerToken.address}
+ alternativeImage={makerImage}
+ networkId={this.props.networkId}
+ isInTokenRegistry={this.props.isMakerTokenAddressInRegistry}
+ hasUniqueNameAndSymbol={utils.hasUniqueNameAndSymbol(allTokens, this.props.makerToken)}
+ />
+ </div>
+ </div>
+ </div>
+ );
+ }
+ private _renderAmount(assetToken: AssetToken, token: Token) {
+ const unitAmount = ZeroEx.toUnitAmount(assetToken.amount, token.decimals);
+ return (
+ <div style={{ fontSize: 13 }}>
+ {unitAmount.toNumber().toFixed(PRECISION)} {token.symbol}
+ </div>
+ );
+ }
}