import BigNumber from 'bignumber.js'; import * as _ from 'lodash'; import Paper from 'material-ui/Paper'; import * as React from 'react'; import * as DocumentTitle from 'react-document-title'; import {Route, Switch} from 'react-router-dom'; import {Blockchain} from 'ts/blockchain'; import {BlockchainErrDialog} from 'ts/components/dialogs/blockchain_err_dialog'; import {PortalDisclaimerDialog} from 'ts/components/dialogs/portal_disclaimer_dialog'; import {WrappedEthSectionNoticeDialog} from 'ts/components/dialogs/wrapped_eth_section_notice_dialog'; import {EthWrappers} from 'ts/components/eth_wrappers'; import {FillOrder} from 'ts/components/fill_order'; import {Footer} from 'ts/components/footer'; import {PortalMenu} from 'ts/components/portal_menu'; import {TokenBalances} from 'ts/components/token_balances'; import {TopBar} from 'ts/components/top_bar'; import {TradeHistory} from 'ts/components/trade_history/trade_history'; import {FlashMessage} from 'ts/components/ui/flash_message'; import {Loading} from 'ts/components/ui/loading'; import {GenerateOrderForm} from 'ts/containers/generate_order_form'; import {localStorage} from 'ts/local_storage/local_storage'; import {Dispatcher} from 'ts/redux/dispatcher'; import {orderSchema} from 'ts/schemas/order_schema'; import {SchemaValidator} from 'ts/schemas/validator'; import { BlockchainErrs, HashData, Order, ScreenWidths, Token, TokenByAddress, TokenStateByAddress, WebsitePaths, } from 'ts/types'; import {colors} from 'ts/utils/colors'; import {configs} from 'ts/utils/configs'; import {constants} from 'ts/utils/constants'; import {utils} from 'ts/utils/utils'; const 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; } interface PortalAllState { prevNetworkId: number; prevNodeVersion: string; prevUserAddress: string; prevPathname: string; isDisclaimerDialogOpen: boolean; isWethNoticeDialogOpen: boolean; } export class Portal extends React.Component { 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 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 (
{!configs.IS_MAINNET_ENABLED && this.props.networkId === constants.NETWORK_ID_MAINNET ?
Mainnet unavailable
0x portal is currently unavailable on the Ethereum mainnet.
To try it out, switch to the Kovan test network (networkId: 42).
Check back soon!
:
{this.props.blockchainIsLoaded ? : }
}
); } private renderEthWrapper() { return ( ); } private renderTradeHistory() { return ( ); } private renderTokenBalances() { return ( ); } private renderFillOrder(match: any, location: Location, history: History) { const initialFillOrder = !_.isUndefined(this.props.userSuppliedOrderCache) ? this.props.userSuppliedOrderCache : this.sharedOrderIfExists; return ( ); } private renderGenerateOrderForm(match: any, location: Location, history: History) { return ( ); } private onPortalDisclaimerAccepted() { localStorage.setItem(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER, 'set'); this.setState({ isDisclaimerDialogOpen: false, }); } private onWethNoticeAccepted() { localStorage.setItem(constants.DISMISS_WETH_NOTICE_LOCAL_STORAGE_KEY, 'set'); this.setState({ isWethNoticeDialogOpen: false, }); } private getSharedOrderIfExists(): Order { const queryString = window.location.search; if (queryString.length === 0) { return; } const queryParams = queryString.substring(1).split('&'); const orderQueryParam = _.find(queryParams, queryParam => { const queryPair = queryParam.split('='); return queryPair[0] === 'order'; }); if (_.isUndefined(orderQueryParam)) { return; } const orderPair = orderQueryParam.split('='); if (orderPair.length !== 2) { return; } 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; } 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); } }