aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts/components/token_balances.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website/ts/components/token_balances.tsx')
-rw-r--r--packages/website/ts/components/token_balances.tsx1066
1 files changed, 533 insertions, 533 deletions
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