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/legacy_portal/legacy_portal.tsx47
-rw-r--r--packages/website/ts/components/legacy_portal/legacy_portal_menu.tsx20
-rw-r--r--packages/website/ts/components/portal/portal.tsx263
-rw-r--r--packages/website/ts/components/relayer_index/relayer_grid_tile.tsx102
-rw-r--r--packages/website/ts/components/relayer_index/relayer_index.tsx70
-rw-r--r--packages/website/ts/components/relayer_index/relayer_top_tokens.tsx16
-rw-r--r--packages/website/ts/components/top_bar/provider_display.tsx36
-rw-r--r--packages/website/ts/components/top_bar/top_bar.tsx68
-rw-r--r--packages/website/ts/components/wallet/wallet.tsx67
9 files changed, 491 insertions, 198 deletions
diff --git a/packages/website/ts/components/legacy_portal/legacy_portal.tsx b/packages/website/ts/components/legacy_portal/legacy_portal.tsx
index 8942e4356..c45b20365 100644
--- a/packages/website/ts/components/legacy_portal/legacy_portal.tsx
+++ b/packages/website/ts/components/legacy_portal/legacy_portal.tsx
@@ -154,7 +154,6 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta
const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen.bind(
this.props.dispatcher,
);
- const isDevelopment = configs.ENVIRONMENT === Environments.DEVELOPMENT;
const portalStyle: React.CSSProperties = {
minHeight: '100vh',
display: 'flex',
@@ -204,18 +203,6 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta
<div className="py2" style={{ backgroundColor: colors.grey50 }}>
{this.props.blockchainIsLoaded ? (
<Switch>
- {isDevelopment && (
- <Route
- path={`${WebsitePaths.Portal}/wallet`}
- render={this._renderWallet.bind(this)}
- />
- )}
- {isDevelopment && (
- <Route
- path={`${WebsitePaths.Portal}/relayers`}
- render={this._renderRelayers.bind(this)}
- />
- )}
<Route
path={`${WebsitePaths.Portal}/weth`}
render={this._renderEthWrapper.bind(this)}
@@ -294,40 +281,6 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta
isLedgerDialogOpen: !this.state.isLedgerDialogOpen,
});
}
- private _renderWallet() {
- const allTokens = _.values(this.props.tokenByAddress);
- const trackedTokens = _.filter(allTokens, t => t.isTracked);
- return (
- <div className="flex flex-center">
- <div className="mx-auto">
- <Wallet
- userAddress={this.props.userAddress}
- networkId={this.props.networkId}
- blockchain={this._blockchain}
- blockchainIsLoaded={this.props.blockchainIsLoaded}
- blockchainErr={this.props.blockchainErr}
- dispatcher={this.props.dispatcher}
- tokenByAddress={this.props.tokenByAddress}
- trackedTokens={trackedTokens}
- userEtherBalanceInWei={this.props.userEtherBalanceInWei}
- lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
- injectedProviderName={this.props.injectedProviderName}
- providerType={this.props.providerType}
- onToggleLedgerDialog={this.onToggleLedgerDialog.bind(this)}
- />
- </div>
- </div>
- );
- }
- private _renderRelayers() {
- return (
- <div className="flex flex-center">
- <div className="mx-auto" style={{ width: 800 }}>
- <RelayerIndex networkId={this.props.networkId} />
- </div>
- </div>
- );
- }
private _renderEthWrapper() {
return (
<EthWrappers
diff --git a/packages/website/ts/components/legacy_portal/legacy_portal_menu.tsx b/packages/website/ts/components/legacy_portal/legacy_portal_menu.tsx
index 634d966ed..94113f066 100644
--- a/packages/website/ts/components/legacy_portal/legacy_portal_menu.tsx
+++ b/packages/website/ts/components/legacy_portal/legacy_portal_menu.tsx
@@ -58,26 +58,6 @@ export class LegacyPortalMenu extends React.Component<LegacyPortalMenuProps, Leg
>
{this._renderMenuItemWithIcon('Wrap ETH', 'zmdi-circle-o')}
</MenuItem>
- {configs.ENVIRONMENT === Environments.DEVELOPMENT && (
- <div>
- <MenuItem
- style={this.props.menuItemStyle}
- className="py2"
- to={`${WebsitePaths.Portal}/wallet`}
- onClick={this.props.onClick.bind(this)}
- >
- {this._renderMenuItemWithIcon('Wallet', 'zmdi-balance-wallet')}
- </MenuItem>
- <MenuItem
- style={this.props.menuItemStyle}
- className="py2"
- to={`${WebsitePaths.Portal}/relayers`}
- onClick={this.props.onClick.bind(this)}
- >
- {this._renderMenuItemWithIcon('Relayers', 'zmdi-input-antenna')}
- </MenuItem>
- </div>
- )}
</div>
);
}
diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx
new file mode 100644
index 000000000..b5e8150c4
--- /dev/null
+++ b/packages/website/ts/components/portal/portal.tsx
@@ -0,0 +1,263 @@
+import { colors, Styles } from '@0xproject/react-shared';
+import { BigNumber } from '@0xproject/utils';
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as DocumentTitle from 'react-document-title';
+
+import { Blockchain } from 'ts/blockchain';
+import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog';
+import { LedgerConfigDialog } from 'ts/components/dialogs/ledger_config_dialog';
+import { PortalDisclaimerDialog } from 'ts/components/dialogs/portal_disclaimer_dialog';
+import { AssetPicker } from 'ts/components/generate_order/asset_picker';
+import { RelayerIndex } from 'ts/components/relayer_index/relayer_index';
+import { TopBar, TopBarDisplayType } from 'ts/components/top_bar/top_bar';
+import { FlashMessage } from 'ts/components/ui/flash_message';
+import { Wallet } from 'ts/components/wallet/wallet';
+import { localStorage } from 'ts/local_storage/local_storage';
+import { Dispatcher } from 'ts/redux/dispatcher';
+import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAddress, TokenVisibility } from 'ts/types';
+import { constants } from 'ts/utils/constants';
+import { Translate } from 'ts/utils/translate';
+import { utils } from 'ts/utils/utils';
+
+export interface PortalProps {
+ blockchainErr: BlockchainErrs;
+ blockchainIsLoaded: boolean;
+ dispatcher: Dispatcher;
+ hashData: HashData;
+ injectedProviderName: string;
+ networkId: number;
+ nodeVersion: string;
+ orderFillAmount: BigNumber;
+ providerType: ProviderType;
+ screenWidth: ScreenWidths;
+ tokenByAddress: TokenByAddress;
+ userEtherBalanceInWei: BigNumber;
+ userAddress: string;
+ shouldBlockchainErrDialogBeOpen: boolean;
+ userSuppliedOrderCache: Order;
+ location: Location;
+ flashMessage?: string | React.ReactNode;
+ lastForceTokenStateRefetch: number;
+ translate: Translate;
+}
+
+interface PortalState {
+ prevNetworkId: number;
+ prevNodeVersion: string;
+ prevUserAddress: string;
+ prevPathname: string;
+ isDisclaimerDialogOpen: boolean;
+ isLedgerDialogOpen: boolean;
+ isAssetPickerDialogOpen: boolean;
+}
+
+const THROTTLE_TIMEOUT = 100;
+const TOP_BAR_HEIGHT = TopBar.heightForDisplayType(TopBarDisplayType.Expanded);
+
+const styles: Styles = {
+ root: {
+ width: '100%',
+ height: '100%',
+ backgroundColor: colors.lightestGrey,
+ },
+ body: {
+ height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`,
+ },
+ scrollContainer: {
+ height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`,
+ WebkitOverflowScrolling: 'touch',
+ overflow: 'auto',
+ },
+ title: {
+ fontWeight: 'bold',
+ fontSize: 20,
+ },
+};
+
+export class Portal extends React.Component<PortalProps, PortalState> {
+ private _blockchain: Blockchain;
+ private _throttledScreenWidthUpdate: () => void;
+ constructor(props: PortalProps) {
+ super(props);
+ this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT);
+ 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,
+ isAssetPickerDialogOpen: false,
+ isLedgerDialogOpen: false,
+ };
+ }
+ 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, changed user accounts, etc...)
+ this.props.dispatcher.resetState();
+ }
+ public componentWillReceiveProps(nextProps: PortalProps) {
+ 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) {
+ const newUserAddress = _.isEmpty(nextProps.userAddress) ? undefined : nextProps.userAddress;
+ // tslint:disable-next-line:no-floating-promises
+ this._blockchain.userAddressUpdatedFireAndForgetAsync(newUserAddress);
+ 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) {
+ this.setState({
+ prevPathname: nextProps.location.pathname,
+ });
+ }
+ }
+ public render() {
+ const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen.bind(
+ this.props.dispatcher,
+ );
+ const allTokens = _.values(this.props.tokenByAddress);
+ const trackedTokens = _.filter(allTokens, t => t.isTracked);
+ return (
+ <div style={styles.root}>
+ <DocumentTitle title="0x Portal DApp" />
+ <TopBar
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ injectedProviderName={this.props.injectedProviderName}
+ onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)}
+ dispatcher={this.props.dispatcher}
+ providerType={this.props.providerType}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
+ location={this.props.location}
+ blockchain={this._blockchain}
+ translate={this.props.translate}
+ displayType={TopBarDisplayType.Expanded}
+ style={{ backgroundColor: colors.lightestGrey }}
+ />
+ <div id="portal" style={styles.body}>
+ <div className="sm-flex flex-center">
+ <div className="flex-last px3">
+ <div className="py3" style={styles.title}>
+ Your Account
+ </div>
+ <Wallet
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this._blockchain}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
+ blockchainErr={this.props.blockchainErr}
+ dispatcher={this.props.dispatcher}
+ tokenByAddress={this.props.tokenByAddress}
+ trackedTokens={trackedTokens}
+ userEtherBalanceInWei={this.props.userEtherBalanceInWei}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ injectedProviderName={this.props.injectedProviderName}
+ providerType={this.props.providerType}
+ onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)}
+ onAddToken={this._onAddToken.bind(this)}
+ />
+ </div>
+ <div className="flex-auto px3" style={styles.scrollContainer}>
+ <div className="py3" style={styles.title}>
+ Explore 0x Ecosystem
+ </div>
+ <RelayerIndex networkId={this.props.networkId} />
+ </div>
+ </div>
+ <BlockchainErrDialog
+ blockchain={this._blockchain}
+ blockchainErr={this.props.blockchainErr}
+ isOpen={this.props.shouldBlockchainErrDialogBeOpen}
+ userAddress={this.props.userAddress}
+ toggleDialogFn={updateShouldBlockchainErrDialogBeOpen}
+ networkId={this.props.networkId}
+ />
+ <PortalDisclaimerDialog
+ isOpen={this.state.isDisclaimerDialogOpen}
+ onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)}
+ />
+ <FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} />
+ {this.props.blockchainIsLoaded && (
+ <LedgerConfigDialog
+ providerType={this.props.providerType}
+ networkId={this.props.networkId}
+ blockchain={this._blockchain}
+ dispatcher={this.props.dispatcher}
+ toggleDialogFn={this._onToggleLedgerDialog.bind(this)}
+ isOpen={this.state.isLedgerDialogOpen}
+ />
+ )}
+ <AssetPicker
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this._blockchain}
+ dispatcher={this.props.dispatcher}
+ isOpen={this.state.isAssetPickerDialogOpen}
+ currentTokenAddress={''}
+ onTokenChosen={this._onTokenChosen.bind(this)}
+ tokenByAddress={this.props.tokenByAddress}
+ tokenVisibility={TokenVisibility.UNTRACKED}
+ />
+ </div>
+ </div>
+ );
+ }
+ private _onTokenChosen(tokenAddress: string) {
+ if (_.isEmpty(tokenAddress)) {
+ this.setState({
+ isAssetPickerDialogOpen: false,
+ });
+ return;
+ }
+ const token = this.props.tokenByAddress[tokenAddress];
+ this.props.dispatcher.updateTokenByAddress([token]);
+ this.setState({
+ isAssetPickerDialogOpen: false,
+ });
+ }
+ private _onToggleLedgerDialog() {
+ this.setState({
+ isLedgerDialogOpen: !this.state.isLedgerDialogOpen,
+ });
+ }
+ private _onAddToken() {
+ this.setState({
+ isAssetPickerDialogOpen: !this.state.isAssetPickerDialogOpen,
+ });
+ }
+ private _onPortalDisclaimerAccepted() {
+ localStorage.setItem(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER, 'set');
+ this.setState({
+ isDisclaimerDialogOpen: false,
+ });
+ }
+ private _updateScreenWidth() {
+ const newScreenWidth = utils.getScreenWidth();
+ this.props.dispatcher.updateScreenWidth(newScreenWidth);
+ }
+}
diff --git a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
index 0b9b8165e..d88a59d15 100644
--- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
@@ -12,37 +12,6 @@ export interface RelayerGridTileProps {
networkId: number;
}
-// TODO: Get top tokens from remote
-const topTokens = [
- {
- address: '0x1dad4783cf3fe3085c1426157ab175a6119a04ba',
- decimals: 18,
- iconUrl: '/images/token_icons/makerdao.png',
- isRegistered: true,
- isTracked: true,
- name: 'Maker DAO',
- symbol: 'MKR',
- },
- {
- address: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
- decimals: 18,
- iconUrl: '/images/token_icons/melon.png',
- isRegistered: true,
- isTracked: true,
- name: 'Melon Token',
- symbol: 'MLN',
- },
- {
- address: '0xb18845c260f680d5b9d84649638813e342e4f8c9',
- decimals: 18,
- iconUrl: '/images/token_icons/augur.png',
- isRegistered: true,
- isTracked: true,
- name: 'Augur Reputation Token',
- symbol: 'REP',
- },
-];
-
const styles: Styles = {
root: {
backgroundColor: colors.white,
@@ -93,28 +62,63 @@ const styles: Styles = {
},
};
+const FALLBACK_IMG_SRC = '/images/landing/hero_chip_image.png';
+
export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (props: RelayerGridTileProps) => {
const link = props.relayerInfo.appUrl || props.relayerInfo.url;
return (
- <a href={link} target="_blank" style={{ textDecoration: 'none' }}>
- <GridTile style={styles.root}>
- <div style={styles.innerDiv}>
- <img src={props.relayerInfo.headerImgUrl} style={styles.header} />
- <div style={styles.body}>
- <div className="py1" style={styles.relayerNameLabel}>
- {props.relayerInfo.name}
- </div>
- <div style={styles.dailyTradeVolumeLabel}>{props.relayerInfo.dailyTxnVolume}</div>
- <div className="py1" style={styles.subLabel}>
- Daily Trade Volume
- </div>
- <TopTokens tokens={topTokens} networkId={props.networkId} />
- <div className="py1" style={styles.subLabel}>
- Top tokens
- </div>
+ <GridTile style={styles.root}>
+ <div style={styles.innerDiv}>
+ <a href={link} target="_blank" style={{ textDecoration: 'none' }}>
+ <ImgWithFallback
+ src={props.relayerInfo.headerImgUrl}
+ fallbackSrc={FALLBACK_IMG_SRC}
+ style={styles.header}
+ />
+ </a>
+ <div style={styles.body}>
+ <div className="py1" style={styles.relayerNameLabel}>
+ {props.relayerInfo.name}
+ </div>
+ <div style={styles.dailyTradeVolumeLabel}>{props.relayerInfo.dailyTxnVolume}</div>
+ <div className="py1" style={styles.subLabel}>
+ Daily Trade Volume
+ </div>
+ <TopTokens tokens={props.relayerInfo.topTokens} networkId={props.networkId} />
+ <div className="py1" style={styles.subLabel}>
+ Top tokens
</div>
</div>
- </GridTile>
- </a>
+ </div>
+ </GridTile>
);
};
+
+interface ImgWithFallbackProps {
+ src?: string;
+ fallbackSrc: string;
+ style: React.CSSProperties;
+}
+interface ImgWithFallbackState {
+ imageLoadFailed: boolean;
+}
+class ImgWithFallback extends React.Component<ImgWithFallbackProps, ImgWithFallbackState> {
+ constructor(props: ImgWithFallbackProps) {
+ super(props);
+ this.state = {
+ imageLoadFailed: false,
+ };
+ }
+ public render() {
+ if (this.state.imageLoadFailed || _.isUndefined(this.props.src)) {
+ return <img src={this.props.fallbackSrc} style={this.props.style} />;
+ } else {
+ return <img src={this.props.src} onError={this._onError.bind(this)} style={this.props.style} />;
+ }
+ }
+ private _onError() {
+ this.setState({
+ imageLoadFailed: true,
+ });
+ }
+}
diff --git a/packages/website/ts/components/relayer_index/relayer_index.tsx b/packages/website/ts/components/relayer_index/relayer_index.tsx
index c1ab4227a..dffd0f83f 100644
--- a/packages/website/ts/components/relayer_index/relayer_index.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_index.tsx
@@ -1,5 +1,7 @@
import { colors, Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
+import CircularProgress from 'material-ui/CircularProgress';
+import FlatButton from 'material-ui/FlatButton';
import { GridList } from 'material-ui/GridList';
import * as React from 'react';
@@ -32,9 +34,9 @@ const styles: Styles = {
},
};
-const CELL_HEIGHT = 260;
+const CELL_HEIGHT = 290;
const NUMBER_OF_COLUMNS = 4;
-const GRID_PADDING = 16;
+const GRID_PADDING = 20;
export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerIndexState> {
private _isUnmounted: boolean;
@@ -55,7 +57,24 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde
}
public render() {
const readyToRender = _.isUndefined(this.state.error) && !_.isUndefined(this.state.relayerInfos);
- if (readyToRender) {
+ if (!readyToRender) {
+ return (
+ <div className="col col-12" style={{ ...styles.root, height: '100%' }}>
+ <div
+ className="relative sm-px2 sm-pt2 sm-m1"
+ style={{ height: 122, top: '33%', transform: 'translateY(-50%)' }}
+ >
+ <div className="center pb2">
+ {_.isUndefined(this.state.error) ? (
+ <CircularProgress size={40} thickness={5} />
+ ) : (
+ <Retry onRetry={this._fetchRelayerInfosAsync.bind(this)} />
+ )}
+ </div>
+ </div>
+ </div>
+ );
+ } else {
return (
<div style={styles.root}>
<GridList
@@ -64,23 +83,22 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde
padding={GRID_PADDING}
style={styles.gridList}
>
- {this.state.relayerInfos.map((relayerInfo: WebsiteBackendRelayerInfo) => (
- <RelayerGridTile
- key={relayerInfo.name}
- relayerInfo={relayerInfo}
- networkId={this.props.networkId}
- />
+ {this.state.relayerInfos.map((relayerInfo: WebsiteBackendRelayerInfo, index) => (
+ <RelayerGridTile key={index} relayerInfo={relayerInfo} networkId={this.props.networkId} />
))}
</GridList>
</div>
);
- } else {
- // TODO: loading and error states with a scrolling container
- return null;
}
}
private async _fetchRelayerInfosAsync(): Promise<void> {
try {
+ if (!this._isUnmounted) {
+ this.setState({
+ relayerInfos: undefined,
+ error: undefined,
+ });
+ }
const relayerInfos = await backendClient.getRelayerInfosAsync();
if (!this._isUnmounted) {
this.setState({
@@ -96,3 +114,31 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde
}
}
}
+
+interface RetryProps {
+ onRetry: () => void;
+}
+const Retry = (props: RetryProps) => (
+ <div className="clearfix center" style={{ color: colors.black }}>
+ <div className="mx-auto inline-block align-middle" style={{ lineHeight: '44px', textAlign: 'center' }}>
+ <div className="h2" style={{ fontFamily: 'Roboto Mono' }}>
+ Something went wrong.
+ </div>
+ <div className="py3">
+ <FlatButton
+ label={'reload'}
+ backgroundColor={colors.black}
+ labelStyle={{
+ fontSize: 18,
+ fontFamily: 'Roboto Mono',
+ fontWeight: 'lighter',
+ color: colors.white,
+ textTransform: 'lowercase',
+ }}
+ style={{ width: 280, height: 62, borderRadius: 5 }}
+ onClick={props.onRetry}
+ />
+ </div>
+ </div>
+ </div>
+);
diff --git a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx
index 233590b78..db4d3a211 100644
--- a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx
@@ -3,10 +3,10 @@ import * as _ from 'lodash';
import * as React from 'react';
import { TokenIcon } from 'ts/components/ui/token_icon';
-import { Token } from 'ts/types';
+import { WebsiteBackendTokenInfo } from 'ts/types';
export interface TopTokensProps {
- tokens: Token[];
+ tokens: WebsiteBackendTokenInfo[];
networkId: number;
}
@@ -23,17 +23,17 @@ const styles: Styles = {
export const TopTokens: React.StatelessComponent<TopTokensProps> = (props: TopTokensProps) => {
return (
<div className="flex">
- {_.map(props.tokens, (token: Token, index: number) => {
+ {_.map(props.tokens, (tokenInfo: WebsiteBackendTokenInfo, index: number) => {
const firstItemStyle = { ...styles.tokenLabel, ...styles.followingTokenLabel };
const style = index !== 0 ? firstItemStyle : styles.tokenLabel;
return (
<a
- key={token.address}
- href={tokenLinkFromToken(token, props.networkId)}
+ key={tokenInfo.address}
+ href={tokenLinkFromToken(tokenInfo, props.networkId)}
target="_blank"
style={style}
>
- {token.symbol}
+ {tokenInfo.symbol}
</a>
);
})}
@@ -41,6 +41,6 @@ export const TopTokens: React.StatelessComponent<TopTokensProps> = (props: TopTo
);
};
-function tokenLinkFromToken(token: Token, networkId: number) {
- return sharedUtils.getEtherScanLinkIfExists(token.address, networkId, EtherscanLinkSuffixes.Address);
+function tokenLinkFromToken(tokenInfo: WebsiteBackendTokenInfo, networkId: number) {
+ return sharedUtils.getEtherScanLinkIfExists(tokenInfo.address, networkId, EtherscanLinkSuffixes.Address);
}
diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx
index 221c34f8c..4cd50dba9 100644
--- a/packages/website/ts/components/top_bar/provider_display.tsx
+++ b/packages/website/ts/components/top_bar/provider_display.tsx
@@ -1,4 +1,4 @@
-import { colors } from '@0xproject/react-shared';
+import { colors, Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
import RaisedButton from 'material-ui/RaisedButton';
import * as React from 'react';
@@ -11,9 +11,9 @@ import { ProviderType } from 'ts/types';
import { constants } from 'ts/utils/constants';
import { utils } from 'ts/utils/utils';
-const IDENTICON_DIAMETER = 32;
+const ROOT_HEIGHT = 24;
-interface ProviderDisplayProps {
+export interface ProviderDisplayProps {
dispatcher: Dispatcher;
userAddress: string;
networkId: number;
@@ -25,6 +25,15 @@ interface ProviderDisplayProps {
interface ProviderDisplayState {}
+const styles: Styles = {
+ root: {
+ height: ROOT_HEIGHT,
+ backgroundColor: colors.white,
+ borderRadius: ROOT_HEIGHT,
+ boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`,
+ },
+};
+
export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> {
public render() {
const isAddressAvailable = !_.isEmpty(this.props.userAddress);
@@ -40,21 +49,20 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi
: 'Connect a wallet';
const providerTitle =
this.props.providerType === ProviderType.Injected ? injectedProviderName : 'Ledger Nano S';
+ const isProviderMetamask = providerTitle === constants.PROVIDER_NAME_METAMASK;
const hoverActiveNode = (
- <div className="flex right lg-pr0 md-pr2 sm-pr2" style={{ paddingTop: 16 }}>
+ <div className="flex right lg-pr0 md-pr2 sm-pr2 p1" style={styles.root}>
<div>
- <Identicon address={this.props.userAddress} diameter={IDENTICON_DIAMETER} />
- </div>
- <div style={{ marginLeft: 12, paddingTop: 1 }}>
- <div style={{ fontSize: 12, color: colors.amber800 }}>{providerTitle}</div>
- <div style={{ fontSize: 14 }}>{displayAddress}</div>
+ <Identicon address={this.props.userAddress} diameter={ROOT_HEIGHT} />
</div>
- <div
- style={{ borderLeft: `1px solid ${colors.grey300}`, marginLeft: 17, paddingTop: 1 }}
- className="px2"
- >
- <i style={{ fontSize: 30, color: colors.grey300 }} className="zmdi zmdi zmdi-chevron-down" />
+ <div style={{ marginLeft: 12, paddingTop: 3 }}>
+ <div style={{ fontSize: 16, color: colors.darkGrey }}>{displayAddress}</div>
</div>
+ {isProviderMetamask && (
+ <div style={{ marginLeft: 16 }}>
+ <img src="/images/metamask_icon.png" style={{ width: ROOT_HEIGHT, height: ROOT_HEIGHT }} />
+ </div>
+ )}
</div>
);
const hasInjectedProvider =
diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx
index 7fb476ba0..2502bea6d 100644
--- a/packages/website/ts/components/top_bar/top_bar.tsx
+++ b/packages/website/ts/components/top_bar/top_bar.tsx
@@ -19,7 +19,12 @@ import { Deco, Key, ProviderType, WebsiteLegacyPaths, WebsitePaths } from 'ts/ty
import { constants } from 'ts/utils/constants';
import { Translate } from 'ts/utils/translate';
-interface TopBarProps {
+export enum TopBarDisplayType {
+ Default,
+ Expanded,
+}
+
+export interface TopBarProps {
userAddress?: string;
networkId?: number;
injectedProviderName?: string;
@@ -34,7 +39,7 @@ interface TopBarProps {
availableDocVersions?: string[];
menu?: DocsMenu;
menuSubsectionsBySection?: MenuSubsectionsBySection;
- shouldFullWidth?: boolean;
+ displayType?: TopBarDisplayType;
docsInfo?: DocsInfo;
style?: React.CSSProperties;
isNightVersion?: boolean;
@@ -47,17 +52,8 @@ interface TopBarState {
}
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,
@@ -78,12 +74,19 @@ const styles: Styles = {
},
};
+const DEFAULT_HEIGHT = 59;
+const EXPANDED_HEIGHT = 75;
+
export class TopBar extends React.Component<TopBarProps, TopBarState> {
public static defaultProps: Partial<TopBarProps> = {
- shouldFullWidth: false,
+ displayType: TopBarDisplayType.Default,
style: {},
isNightVersion: false,
};
+ public static heightForDisplayType(displayType: TopBarDisplayType) {
+ const result = displayType === TopBarDisplayType.Expanded ? EXPANDED_HEIGHT : DEFAULT_HEIGHT;
+ return result + 1;
+ }
constructor(props: TopBarProps) {
super(props);
this.state = {
@@ -92,8 +95,9 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
}
public render() {
const isNightVersion = this.props.isNightVersion;
- const isFullWidthPage = this.props.shouldFullWidth;
- const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`;
+ const isExpandedDisplayType = this.props.displayType === TopBarDisplayType.Expanded;
+ const parentClassNames = `flex mx-auto ${isExpandedDisplayType ? 'pl3 py1' : 'max-width-4'}`;
+ const height = isExpandedDisplayType ? EXPANDED_HEIGHT : DEFAULT_HEIGHT;
const developerSectionMenuItems = [
<Link key="subMenuItem-zeroEx" to={WebsitePaths.ZeroExJs} className="text-decoration-none">
<MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x.js" />
@@ -178,9 +182,11 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
</a>,
];
const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {};
- const fullWidthClasses = isFullWidthPage ? 'pr4' : '';
+ const fullWidthClasses = isExpandedDisplayType ? '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 menuClasses = `col col-${
+ isExpandedDisplayType ? '4' : '5'
+ } ${fullWidthClasses} lg-pr0 md-pr2 sm-hide xs-hide`;
const menuIconStyle = {
fontSize: 25,
color: isNightVersion ? 'white' : 'black',
@@ -197,15 +203,15 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
);
const popoverContent = <Menu style={{ color: colors.darkGrey }}>{developerSectionMenuItems}</Menu>;
return (
- <div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style }} className="pb1">
+ <div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style, ...{ height } }} className="pb1">
<div className={parentClassNames}>
<div className="col col-2 sm-pl1 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`} />
+ <div className={`col col-${isExpandedDisplayType ? '8' : '9'} lg-hide md-hide`} />
+ <div className={`col col-${isExpandedDisplayType ? '6' : '5'} sm-hide xs-hide`} />
{!this._isViewingPortal() && (
<div className={menuClasses}>
<div className="flex justify-between">
@@ -242,7 +248,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
path={`${WebsitePaths.Portal}`}
isPrimary={true}
style={styles.menuItem}
- className={`${isFullWidthPage && 'md-hide'}`}
+ className={`${isExpandedDisplayType && 'md-hide'}`}
isNightVersion={isNightVersion}
isExternal={false}
/>
@@ -250,7 +256,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
</div>
)}
{this.props.blockchainIsLoaded && (
- <div className="sm-hide xs-hide col col-5">
+ <div className="sm-hide xs-hide col col-5" style={{ paddingTop: 8, marginRight: 36 }}>
<ProviderDisplay
dispatcher={this.props.dispatcher}
userAddress={this.props.userAddress}
@@ -262,7 +268,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
/>
</div>
)}
- <div className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}>
+ <div className={`col ${isExpandedDisplayType ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}>
<div style={menuIconStyle}>
<i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} />
</div>
@@ -441,21 +447,6 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
</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,
@@ -511,7 +502,8 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
this._isViewingJsonSchemasDocs() ||
this._isViewingSolCovDocs() ||
this._isViewingSubprovidersDocs() ||
- this._isViewingConnectDocs()
+ this._isViewingConnectDocs() ||
+ this._isViewingPortal()
);
}
} // tslint:disable:max-file-line-count
diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx
index 057c712e5..a28012aaf 100644
--- a/packages/website/ts/components/wallet/wallet.tsx
+++ b/packages/website/ts/components/wallet/wallet.tsx
@@ -55,11 +55,13 @@ export interface WalletProps {
injectedProviderName: string;
providerType: ProviderType;
onToggleLedgerDialog: () => void;
+ onAddToken: () => void;
}
interface WalletState {
trackedTokenStateByAddress: TokenStateByAddress;
wrappedEtherDirection?: Side;
+ isHoveringSidebar: boolean;
}
interface AllowanceToggleConfig {
@@ -94,6 +96,9 @@ const styles: Styles = {
},
footerItemInnerDiv: {
paddingLeft: 24,
+ borderTopColor: colors.walletBorder,
+ borderTopStyle: 'solid',
+ borderWidth: 1,
},
borderedItem: {
borderBottomColor: colors.walletBorder,
@@ -114,7 +119,17 @@ const styles: Styles = {
paddingTop: 8,
paddingBottom: 8,
},
- accessoryItemsContainer: { width: 150, right: 8 },
+ accessoryItemsContainer: {
+ width: 150,
+ right: 8,
+ },
+ bodyInnerDiv: {
+ padding: 0,
+ // TODO: make this completely responsive
+ maxHeight: 475,
+ overflow: 'auto',
+ WebkitOverflowScrolling: 'touch',
+ },
};
const ETHER_ICON_PATH = '/images/ether.png';
@@ -139,6 +154,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
this.state = {
trackedTokenStateByAddress: initialTrackedTokenStateByAddress,
wrappedEtherDirection: undefined,
+ isHoveringSidebar: false,
};
}
public componentWillMount() {
@@ -184,12 +200,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
return (
<List style={styles.list}>
{isAddressAvailable
- ? _.concat(
- this._renderConnectedHeaderRows(),
- this._renderEthRows(),
- this._renderTokenRows(),
- this._renderFooterRows(),
- )
+ ? _.concat(this._renderConnectedHeaderRows(), this._renderBody(), this._renderFooterRows())
: _.concat(this._renderDisconnectedHeaderRows(), this._renderDisconnectedRows())}
</List>
);
@@ -230,9 +241,43 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
/>
);
}
+ private _renderBody() {
+ const bodyStyle: React.CSSProperties = {
+ ...styles.bodyInnerDiv,
+ overflow: this.state.isHoveringSidebar ? 'auto' : 'hidden',
+ };
+ return (
+ <ListItem
+ key="body"
+ innerDivStyle={bodyStyle}
+ onMouseEnter={this._onSidebarHover.bind(this)}
+ onMouseLeave={this._onSidebarHoverOff.bind(this)}
+ >
+ {this._renderEthRows()}
+ {this._renderTokenRows()}
+ </ListItem>
+ );
+ }
+ private _onSidebarHover(event: React.FormEvent<HTMLInputElement>) {
+ this.setState({
+ isHoveringSidebar: true,
+ });
+ }
+ private _onSidebarHoverOff() {
+ this.setState({
+ isHoveringSidebar: false,
+ });
+ }
private _renderFooterRows() {
const primaryText = '+ other tokens';
- return <ListItem key={FOOTER_ITEM_KEY} primaryText={primaryText} innerDivStyle={styles.footerItemInnerDiv} />;
+ return (
+ <ListItem
+ key={FOOTER_ITEM_KEY}
+ primaryText={primaryText}
+ innerDivStyle={styles.footerItemInnerDiv}
+ onClick={this.props.onAddToken}
+ />
+ );
}
private _renderEthRows() {
const primaryText = this._renderAmount(
@@ -293,7 +338,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
);
return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this));
}
- private _renderTokenRow(token: Token) {
+ private _renderTokenRow(token: Token, index: number) {
const tokenState = this.state.trackedTokenStateByAddress[token.address];
const tokenLink = sharedUtils.getEtherScanLinkIfExists(
token.address,
@@ -310,12 +355,14 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
tokenState,
},
};
+ // if this is the last item in the list, do not render the border, it is rendered by the footer
+ const borderedStyle = index !== this.props.trackedTokens.length - 1 ? styles.borderedItem : {};
const shouldShowWrapEtherItem =
!_.isUndefined(this.state.wrappedEtherDirection) &&
this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection;
const style = shouldShowWrapEtherItem
? { ...walletItemStyles.focusedItem, ...styles.paddedItem }
- : { ...styles.tokenItem, ...styles.borderedItem, ...styles.paddedItem };
+ : { ...styles.tokenItem, ...borderedStyle, ...styles.paddedItem };
const etherToken = this._getEthToken();
return (
<div key={token.address}>