aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website/ts')
-rw-r--r--packages/website/ts/blockchain.ts6
-rw-r--r--packages/website/ts/blockchain_watcher.ts89
-rw-r--r--packages/website/ts/components/onboarding/portal_onboarding_flow.tsx32
-rw-r--r--packages/website/ts/components/portal/drawer_menu.tsx28
-rw-r--r--packages/website/ts/components/portal/portal.tsx19
-rw-r--r--packages/website/ts/components/relayer_index/relayer_grid_tile.tsx8
-rw-r--r--packages/website/ts/components/relayer_index/relayer_top_tokens.tsx13
-rw-r--r--packages/website/ts/components/top_bar/provider_display.tsx36
-rw-r--r--packages/website/ts/components/top_bar/top_bar.tsx9
-rw-r--r--packages/website/ts/components/ui/identicon.tsx34
-rw-r--r--packages/website/ts/components/ui/image.tsx4
-rw-r--r--packages/website/ts/components/wallet/wallet.tsx32
-rw-r--r--packages/website/ts/components/wallet/wrap_ether_item.tsx18
-rw-r--r--packages/website/ts/containers/portal.ts4
-rw-r--r--packages/website/ts/containers/portal_onboarding_flow.ts2
-rw-r--r--packages/website/ts/utils/utils.ts19
16 files changed, 233 insertions, 120 deletions
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts
index 3ebdd1dee..46a4d6629 100644
--- a/packages/website/ts/blockchain.ts
+++ b/packages/website/ts/blockchain.ts
@@ -229,7 +229,7 @@ export class Blockchain {
shouldPollUserAddress,
);
this._contractWrappers.setProvider(provider, this.networkId);
- this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceState();
+ await this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceStateAsync();
this._dispatcher.updateProviderType(ProviderType.Ledger);
}
public async updateProviderToInjectedAsync(): Promise<void> {
@@ -259,7 +259,7 @@ export class Blockchain {
this._contractWrappers.setProvider(provider, this.networkId);
await this.fetchTokenInformationAsync();
- this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceState();
+ await this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceStateAsync();
this._dispatcher.updateProviderType(ProviderType.Injected);
delete this._ledgerSubprovider;
delete this._cachedProvider;
@@ -816,7 +816,7 @@ export class Blockchain {
this._userAddressIfExists = userAddresses[0];
this._dispatcher.updateUserAddress(this._userAddressIfExists);
await this.fetchTokenInformationAsync();
- this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceState();
+ await this._blockchainWatcher.startEmittingNetworkConnectionAndUserBalanceStateAsync();
await this._rehydrateStoreWithContractEventsAsync();
}
private _updateProviderName(injectedWeb3: Web3): void {
diff --git a/packages/website/ts/blockchain_watcher.ts b/packages/website/ts/blockchain_watcher.ts
index 3890a9e57..c576db6ac 100644
--- a/packages/website/ts/blockchain_watcher.ts
+++ b/packages/website/ts/blockchain_watcher.ts
@@ -34,56 +34,15 @@ export class BlockchainWatcher {
public updatePrevUserAddress(userAddress: string): void {
this._prevUserAddressIfExists = userAddress;
}
- public startEmittingNetworkConnectionAndUserBalanceState(): void {
+ public async startEmittingNetworkConnectionAndUserBalanceStateAsync(): Promise<void> {
if (!_.isUndefined(this._watchNetworkAndBalanceIntervalId)) {
return; // we are already emitting the state
}
-
- let prevNodeVersion: string;
this._prevUserEtherBalanceInWei = undefined;
this._dispatcher.updateNetworkId(this._prevNetworkId);
+ await this._updateNetworkAndBalanceAsync();
this._watchNetworkAndBalanceIntervalId = intervalUtils.setAsyncExcludingInterval(
- async () => {
- // Check for network state changes
- let currentNetworkId;
- try {
- currentNetworkId = await this._web3Wrapper.getNetworkIdAsync();
- } catch (err) {
- // Noop
- }
- if (currentNetworkId !== this._prevNetworkId) {
- this._prevNetworkId = currentNetworkId;
- this._dispatcher.updateNetworkId(currentNetworkId);
- }
-
- // Check for node version changes
- const currentNodeVersion = await this._web3Wrapper.getNodeVersionAsync();
- if (currentNodeVersion !== prevNodeVersion) {
- prevNodeVersion = currentNodeVersion;
- this._dispatcher.updateNodeVersion(currentNodeVersion);
- }
-
- if (this._shouldPollUserAddress) {
- const addresses = await this._web3Wrapper.getAvailableAddressesAsync();
- const userAddressIfExists = addresses[0];
- // Update makerAddress on network change
- if (this._prevUserAddressIfExists !== userAddressIfExists) {
- this._prevUserAddressIfExists = userAddressIfExists;
- this._dispatcher.updateUserAddress(userAddressIfExists);
- }
-
- // Check for user ether balance changes
- if (!_.isUndefined(userAddressIfExists)) {
- await this._updateUserWeiBalanceAsync(userAddressIfExists);
- }
- } else {
- // This logic is primarily for the Ledger, since we don't regularly poll for the address
- // we simply update the balance for the last fetched address.
- if (!_.isUndefined(this._prevUserAddressIfExists)) {
- await this._updateUserWeiBalanceAsync(this._prevUserAddressIfExists);
- }
- }
- },
+ this._updateNetworkAndBalanceAsync.bind(this),
5000,
(err: Error) => {
logUtils.log(`Watching network and balances failed: ${err.stack}`);
@@ -91,6 +50,48 @@ export class BlockchainWatcher {
},
);
}
+ private async _updateNetworkAndBalanceAsync(): Promise<void> {
+ // Check for network state changes
+ let prevNodeVersion: string;
+ let currentNetworkId;
+ try {
+ currentNetworkId = await this._web3Wrapper.getNetworkIdAsync();
+ } catch (err) {
+ // Noop
+ }
+ if (currentNetworkId !== this._prevNetworkId) {
+ this._prevNetworkId = currentNetworkId;
+ this._dispatcher.updateNetworkId(currentNetworkId);
+ }
+
+ // Check for node version changes
+ const currentNodeVersion = await this._web3Wrapper.getNodeVersionAsync();
+ if (currentNodeVersion !== prevNodeVersion) {
+ prevNodeVersion = currentNodeVersion;
+ this._dispatcher.updateNodeVersion(currentNodeVersion);
+ }
+
+ if (this._shouldPollUserAddress) {
+ const addresses = await this._web3Wrapper.getAvailableAddressesAsync();
+ const userAddressIfExists = addresses[0];
+ // Update makerAddress on network change
+ if (this._prevUserAddressIfExists !== userAddressIfExists) {
+ this._prevUserAddressIfExists = userAddressIfExists;
+ this._dispatcher.updateUserAddress(userAddressIfExists);
+ }
+
+ // Check for user ether balance changes
+ if (!_.isUndefined(userAddressIfExists)) {
+ await this._updateUserWeiBalanceAsync(userAddressIfExists);
+ }
+ } else {
+ // This logic is primarily for the Ledger, since we don't regularly poll for the address
+ // we simply update the balance for the last fetched address.
+ if (!_.isUndefined(this._prevUserAddressIfExists)) {
+ await this._updateUserWeiBalanceAsync(this._prevUserAddressIfExists);
+ }
+ }
+ }
private async _updateUserWeiBalanceAsync(userAddress: string): Promise<void> {
const balanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(userAddress);
if (_.isUndefined(this._prevUserEtherBalanceInWei) || !balanceInWei.eq(this._prevUserEtherBalanceInWei)) {
diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
index 4283022e2..7e40192f6 100644
--- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
+++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
@@ -1,5 +1,7 @@
+import { constants as sharedConstants } from '@0xproject/react-shared';
import * as _ from 'lodash';
import * as React from 'react';
+import { RouteComponentProps, withRouter } from 'react-router';
import { BigNumber } from '@0xproject/utils';
import { Blockchain } from 'ts/blockchain';
@@ -13,9 +15,11 @@ import { UnlockWalletOnboardingStep } from 'ts/components/onboarding/unlock_wall
import { WrapEthOnboardingStep } from 'ts/components/onboarding/wrap_eth_onboarding_step';
import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle';
import { ProviderType, Token, TokenByAddress, TokenStateByAddress } from 'ts/types';
+import { analytics } from 'ts/utils/analytics';
import { utils } from 'ts/utils/utils';
-export interface PortalOnboardingFlowProps {
+export interface PortalOnboardingFlowProps extends RouteComponentProps<any> {
+ networkId: number;
blockchain: Blockchain;
stepIndex: number;
isRunning: boolean;
@@ -32,9 +36,15 @@ export interface PortalOnboardingFlowProps {
refetchTokenStateAsync: (tokenAddress: string) => Promise<void>;
}
-export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowProps> {
+class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProps> {
+ private _unlisten: () => void;
public componentDidMount(): void {
this._overrideOnboardingStateIfShould();
+ // If there is a route change, just close onboarding.
+ this._unlisten = this.props.history.listen(() => this.props.updateIsRunning(false));
+ }
+ public componentWillUnmount(): void {
+ this._unlisten();
}
public componentDidUpdate(): void {
this._overrideOnboardingStateIfShould();
@@ -45,8 +55,8 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr
steps={this._getSteps()}
stepIndex={this.props.stepIndex}
isRunning={this.props.isRunning}
- onClose={this.props.updateIsRunning.bind(this, false)}
- updateOnboardingStep={this.props.updateOnboardingStep}
+ onClose={this._closeOnboarding.bind(this)}
+ updateOnboardingStep={this._updateOnboardingStep.bind(this)}
/>
);
}
@@ -181,9 +191,21 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr
}
private _autoStartOnboardingIfShould(): void {
if (!this.props.isRunning && !this.props.hasBeenSeen && this.props.blockchainIsLoaded) {
+ const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
+ analytics.logEvent('Portal', 'Onboarding Started - Automatic', networkName, this.props.stepIndex);
this.props.updateIsRunning(true);
}
}
+ private _updateOnboardingStep(stepIndex: number): void {
+ const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
+ this.props.updateOnboardingStep(stepIndex);
+ analytics.logEvent('Portal', 'Update Onboarding Step', networkName, stepIndex);
+ }
+ private _closeOnboarding(): void {
+ const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
+ this.props.updateIsRunning(false);
+ analytics.logEvent('Portal', 'Onboarding Closed', networkName, this.props.stepIndex);
+ }
private _renderZrxAllowanceToggle(): React.ReactNode {
const zrxToken = utils.getZrxToken(this.props.tokenByAddress);
return this._renderAllowanceToggle(zrxToken);
@@ -209,3 +231,5 @@ export class PortalOnboardingFlow extends React.Component<PortalOnboardingFlowPr
);
}
}
+
+export const PortalOnboardingFlow = withRouter(PlainPortalOnboardingFlow);
diff --git a/packages/website/ts/components/portal/drawer_menu.tsx b/packages/website/ts/components/portal/drawer_menu.tsx
index 8ac2b9091..4bd07769f 100644
--- a/packages/website/ts/components/portal/drawer_menu.tsx
+++ b/packages/website/ts/components/portal/drawer_menu.tsx
@@ -2,10 +2,12 @@ import { Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
import * as React from 'react';
+import { Blockchain } from 'ts/blockchain';
import { defaultMenuItemEntries, Menu } from 'ts/components/portal/menu';
import { Identicon } from 'ts/components/ui/identicon';
+import { Text } from 'ts/components/ui/text';
import { colors } from 'ts/style/colors';
-import { WebsitePaths } from 'ts/types';
+import { ProviderType, WebsitePaths } from 'ts/types';
import { utils } from 'ts/utils/utils';
const IDENTICON_DIAMETER = 45;
@@ -25,14 +27,15 @@ const styles: Styles = {
MozBorderRadius: BORDER_RADIUS,
WebkitBorderRadius: BORDER_RADIUS,
},
- userAddress: {
- color: colors.white,
- },
};
export interface DrawerMenuProps {
selectedPath?: string;
userAddress?: string;
+ injectedProviderName: string;
+ providerType: ProviderType;
+ blockchain?: Blockchain;
+ blockchainIsLoaded: boolean;
}
export const DrawerMenu = (props: DrawerMenuProps) => {
const relayerItemEntry = {
@@ -41,9 +44,15 @@ export const DrawerMenu = (props: DrawerMenuProps) => {
iconName: 'zmdi-portable-wifi',
};
const menuItemEntries = _.concat(relayerItemEntry, defaultMenuItemEntries);
+ const displayMessage = utils.getReadableAccountState(
+ props.blockchainIsLoaded && !_.isUndefined(props.blockchain),
+ props.providerType,
+ props.injectedProviderName,
+ props.userAddress,
+ );
return (
<div style={styles.root}>
- <Header userAddress={props.userAddress} />
+ <Header userAddress={props.userAddress} displayMessage={displayMessage} />
<Menu selectedPath={props.selectedPath} menuItemEntries={menuItemEntries} />
</div>
);
@@ -51,17 +60,16 @@ export const DrawerMenu = (props: DrawerMenuProps) => {
interface HeaderProps {
userAddress?: string;
+ displayMessage: string;
}
const Header = (props: HeaderProps) => {
return (
<div className="flex flex-center py4">
<div className="flex flex-column mx-auto">
<Identicon address={props.userAddress} diameter={IDENTICON_DIAMETER} style={styles.identicon} />
- {!_.isUndefined(props.userAddress) && (
- <div className="pt2" style={styles.userAddress}>
- {utils.getAddressBeginAndEnd(props.userAddress)}
- </div>
- )}
+ <Text className="pt2" fontColor={colors.white}>
+ {props.displayMessage}
+ </Text>
</div>
</div>
);
diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx
index 28a303793..67314678b 100644
--- a/packages/website/ts/components/portal/portal.tsx
+++ b/packages/website/ts/components/portal/portal.tsx
@@ -1,4 +1,4 @@
-import { colors, Styles } from '@0xproject/react-shared';
+import { colors, constants as sharedConstants, Styles } from '@0xproject/react-shared';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
@@ -33,6 +33,7 @@ import { localStorage } from 'ts/local_storage/local_storage';
import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage';
import { FullscreenMessage } from 'ts/pages/fullscreen_message';
import { Dispatcher } from 'ts/redux/dispatcher';
+import { zIndex } from 'ts/style/z_index';
import {
BlockchainErrs,
HashData,
@@ -46,6 +47,7 @@ import {
TokenVisibility,
WebsitePaths,
} from 'ts/types';
+import { analytics } from 'ts/utils/analytics';
import { backendClient } from 'ts/utils/backend_client';
import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
@@ -73,6 +75,8 @@ export interface PortalProps {
flashMessage?: string | React.ReactNode;
lastForceTokenStateRefetch: number;
translate: Translate;
+ isPortalOnboardingShowing: boolean;
+ portalOnboardingStep: number;
}
interface PortalState {
@@ -155,9 +159,6 @@ export class Portal extends React.Component<PortalProps, PortalState> {
}
public componentWillMount(): void {
this._blockchain = new Blockchain(this.props.dispatcher);
- const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress);
- // tslint:disable-next-line:no-floating-promises
- this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses);
}
public componentWillUnmount(): void {
this._blockchain.destroy();
@@ -168,6 +169,13 @@ export class Portal extends React.Component<PortalProps, PortalState> {
// become disconnected from their backing Ethereum node, changed user accounts, etc...)
this.props.dispatcher.resetState();
}
+ public componentDidUpdate(prevProps: PortalProps): void {
+ if (!prevProps.blockchainIsLoaded && this.props.blockchainIsLoaded) {
+ const trackedTokenAddresses = _.keys(this.state.trackedTokenStateByAddress);
+ // tslint:disable-next-line:no-floating-promises
+ this._fetchBalancesAndAllowancesAsync(trackedTokenAddresses);
+ }
+ }
public componentWillReceiveProps(nextProps: PortalProps): void {
if (nextProps.networkId !== this.state.prevNetworkId) {
// tslint:disable-next-line:no-floating-promises
@@ -335,6 +343,7 @@ export class Portal extends React.Component<PortalProps, PortalState> {
return (
<div>
<Wallet
+ style={this.props.isPortalOnboardingShowing ? { zIndex: zIndex.aboveOverlay } : undefined}
userAddress={this.props.userAddress}
networkId={this.props.networkId}
blockchain={this._blockchain}
@@ -384,6 +393,8 @@ export class Portal extends React.Component<PortalProps, PortalState> {
}
private _startOnboarding(): void {
+ const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
+ analytics.logEvent('Portal', 'Onboarding Started - Manual', networkName, this.props.portalOnboardingStep);
this.props.dispatcher.updatePortalOnboardingShowing(true);
}
private _renderWalletSection(): React.ReactNode {
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 98d6dc0b3..23860856b 100644
--- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
@@ -1,7 +1,8 @@
-import { Styles } from '@0xproject/react-shared';
+import { constants as sharedConstants, Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
import { GridTile } from 'material-ui/GridList';
import * as React from 'react';
+import { analytics } from 'ts/utils/analytics';
import { TopTokens } from 'ts/components/relayer_index/relayer_top_tokens';
import { Container } from 'ts/components/ui/container';
@@ -66,6 +67,9 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (
const link = props.relayerInfo.appUrl || props.relayerInfo.url;
const topTokens = props.relayerInfo.topTokens;
const weeklyTxnVolume = props.relayerInfo.weeklyTxnVolume;
+ const networkName = sharedConstants.NETWORK_NAME_BY_ID[props.networkId];
+ const eventLabel = `${props.relayerInfo.name}-${networkName}`;
+ const trackRelayerClick = () => analytics.logEvent('Portal', 'Relayer Click', eventLabel);
const headerImageUrl = props.relayerInfo.logoImgUrl;
const headerBackgroundColor =
!_.isUndefined(headerImageUrl) && !_.isUndefined(props.relayerInfo.primaryColor)
@@ -74,7 +78,7 @@ export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (
return (
<Island style={styles.root} Component={GridTile}>
<div style={styles.innerDiv}>
- <a href={link} target="_blank" style={{ textDecoration: 'none' }}>
+ <a href={link} target="_blank" style={{ textDecoration: 'none' }} onClick={trackRelayerClick}>
<div
className="flex items-center"
style={{ ...styles.header, backgroundColor: headerBackgroundColor }}
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 a5754180b..b599e7123 100644
--- a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx
@@ -1,6 +1,13 @@
-import { colors, EtherscanLinkSuffixes, Styles, utils as sharedUtils } from '@0xproject/react-shared';
+import {
+ colors,
+ constants as sharedConstants,
+ EtherscanLinkSuffixes,
+ Styles,
+ utils as sharedUtils,
+} from '@0xproject/react-shared';
import * as _ from 'lodash';
import * as React from 'react';
+import { analytics } from 'ts/utils/analytics';
import { WebsiteBackendTokenInfo } from 'ts/types';
@@ -61,6 +68,9 @@ class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> {
cursor: 'pointer',
opacity: this.state.isHovering ? 0.5 : 1,
};
+ const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
+ const eventLabel = `${this.props.tokenInfo.symbol}-${networkName}`;
+ const trackTokenClick = () => analytics.logEvent('Portal', 'Token Click', eventLabel);
return (
<a
href={tokenLinkFromToken(this.props.tokenInfo, this.props.networkId)}
@@ -68,6 +78,7 @@ class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> {
style={style}
onMouseEnter={this._onToggleHover.bind(this, true)}
onMouseLeave={this._onToggleHover.bind(this, false)}
+ onClick={trackTokenClick}
>
{this.props.tokenInfo.symbol}
</a>
diff --git a/packages/website/ts/components/top_bar/provider_display.tsx b/packages/website/ts/components/top_bar/provider_display.tsx
index cb7c9b483..1e8855c14 100644
--- a/packages/website/ts/components/top_bar/provider_display.tsx
+++ b/packages/website/ts/components/top_bar/provider_display.tsx
@@ -6,8 +6,11 @@ import * as React from 'react';
import { Blockchain } from 'ts/blockchain';
import { ProviderPicker } from 'ts/components/top_bar/provider_picker';
+import { Container } from 'ts/components/ui/container';
import { DropDown } from 'ts/components/ui/drop_down';
import { Identicon } from 'ts/components/ui/identicon';
+import { Image } from 'ts/components/ui/image';
+import { Text } from 'ts/components/ui/text';
import { Dispatcher } from 'ts/redux/dispatcher';
import { colors } from 'ts/style/colors';
import { ProviderType } from 'ts/types';
@@ -40,23 +43,16 @@ const styles: Styles = {
export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> {
public render(): React.ReactNode {
- const isAddressAvailable = !_.isEmpty(this.props.userAddress);
const isExternallyInjectedProvider = utils.isExternallyInjected(
this.props.providerType,
this.props.injectedProviderName,
);
- let displayMessage;
- if (!this._isBlockchainReady()) {
- displayMessage = 'loading account';
- } else if (isAddressAvailable) {
- displayMessage = utils.getAddressBeginAndEnd(this.props.userAddress);
- // tslint:disable-next-line: prefer-conditional-expression
- } else if (isExternallyInjectedProvider) {
- displayMessage = 'Account locked';
- } else {
- displayMessage = '0x0000...0000';
- }
-
+ const displayMessage = utils.getReadableAccountState(
+ this._isBlockchainReady(),
+ this.props.providerType,
+ this.props.injectedProviderName,
+ this.props.userAddress,
+ );
// If the "injected" provider is our fallback public node, then we want to
// show the "connect a wallet" message instead of the providerName
const injectedProviderName = isExternallyInjectedProvider
@@ -66,7 +62,7 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi
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 p1" style={styles.root}>
+ <div className="flex items-center p1" style={styles.root}>
<div>
{this._isBlockchainReady() ? (
<Identicon address={this.props.userAddress} diameter={ROOT_HEIGHT} />
@@ -74,13 +70,13 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi
<CircularProgress size={ROOT_HEIGHT} thickness={2} />
)}
</div>
- <div style={{ marginLeft: 12, paddingTop: 3 }}>
- <div style={{ fontSize: 16, color: colors.darkGrey }}>{displayMessage}</div>
- </div>
+ <Container marginLeft="12px" marginRight="12px">
+ <Text fontSize="14px" fontColor={colors.darkGrey}>
+ {displayMessage}
+ </Text>
+ </Container>
{isProviderMetamask && (
- <div style={{ marginLeft: 16 }}>
- <img src="/images/metamask_icon.png" style={{ width: ROOT_HEIGHT, height: ROOT_HEIGHT }} />
- </div>
+ <Image src="/images/metamask_icon.png" height={ROOT_HEIGHT} width={ROOT_HEIGHT} />
)}
</div>
);
diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx
index 1a69827a4..537edc7bb 100644
--- a/packages/website/ts/components/top_bar/top_bar.tsx
+++ b/packages/website/ts/components/top_bar/top_bar.tsx
@@ -297,7 +297,14 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
openSecondary={true}
onRequestChange={this._onMenuButtonClick.bind(this)}
>
- <DrawerMenu selectedPath={this.props.location.pathname} userAddress={this.props.userAddress} />
+ <DrawerMenu
+ selectedPath={this.props.location.pathname}
+ userAddress={this.props.userAddress}
+ injectedProviderName={this.props.injectedProviderName}
+ providerType={this.props.providerType}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
+ blockchain={this.props.blockchain}
+ />
</Drawer>
);
}
diff --git a/packages/website/ts/components/ui/identicon.tsx b/packages/website/ts/components/ui/identicon.tsx
index 83c86a144..30df995c8 100644
--- a/packages/website/ts/components/ui/identicon.tsx
+++ b/packages/website/ts/components/ui/identicon.tsx
@@ -1,7 +1,9 @@
import blockies = require('blockies');
import * as _ from 'lodash';
import * as React from 'react';
-import { constants } from 'ts/utils/constants';
+
+import { Image } from 'ts/components/ui/image';
+import { colors } from 'ts/style/colors';
interface IdenticonProps {
address: string;
@@ -16,14 +18,9 @@ export class Identicon extends React.Component<IdenticonProps, IdenticonState> {
style: {},
};
public render(): React.ReactNode {
- let address = this.props.address;
- if (_.isEmpty(address)) {
- address = constants.NULL_ADDRESS;
- }
+ const address = this.props.address;
const diameter = this.props.diameter;
- const icon = blockies({
- seed: address.toLowerCase(),
- });
+ const radius = diameter / 2;
return (
<div
className="circle mx-auto relative transitionFix"
@@ -34,14 +31,19 @@ export class Identicon extends React.Component<IdenticonProps, IdenticonState> {
...this.props.style,
}}
>
- <img
- src={icon.toDataURL()}
- style={{
- width: diameter,
- height: diameter,
- imageRendering: 'pixelated',
- }}
- />
+ {!_.isEmpty(address) ? (
+ <Image
+ src={blockies({
+ seed: address.toLowerCase(),
+ }).toDataURL()}
+ height={diameter}
+ width={diameter}
+ />
+ ) : (
+ <svg height={diameter} width={diameter}>
+ <circle cx={radius} cy={radius} r={radius} fill={colors.grey200} />
+ </svg>
+ )}
</div>
);
}
diff --git a/packages/website/ts/components/ui/image.tsx b/packages/website/ts/components/ui/image.tsx
index 0958d2e5e..369dc8b7e 100644
--- a/packages/website/ts/components/ui/image.tsx
+++ b/packages/website/ts/components/ui/image.tsx
@@ -5,7 +5,8 @@ export interface ImageProps {
className?: string;
src?: string;
fallbackSrc?: string;
- height?: string;
+ height?: string | number;
+ width?: string | number;
}
interface ImageState {
imageLoadFailed: boolean;
@@ -26,6 +27,7 @@ export class Image extends React.Component<ImageProps, ImageState> {
onError={this._onError.bind(this)}
src={src}
height={this.props.height}
+ width={this.props.width}
/>
);
}
diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx
index 3a6d9942d..ac2fe0d31 100644
--- a/packages/website/ts/components/wallet/wallet.tsx
+++ b/packages/website/ts/components/wallet/wallet.tsx
@@ -1,9 +1,15 @@
-import { EtherscanLinkSuffixes, Styles, utils as sharedUtils } from '@0xproject/react-shared';
+import {
+ constants as sharedConstants,
+ EtherscanLinkSuffixes,
+ Styles,
+ utils as sharedUtils,
+} from '@0xproject/react-shared';
import { BigNumber, errorUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
import CircularProgress from 'material-ui/CircularProgress';
import FloatingActionButton from 'material-ui/FloatingActionButton';
+
import { ListItem } from 'material-ui/List';
import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
import ContentAdd from 'material-ui/svg-icons/content/add';
@@ -23,7 +29,6 @@ import { WrapEtherItem } from 'ts/components/wallet/wrap_ether_item';
import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle';
import { Dispatcher } from 'ts/redux/dispatcher';
import { colors } from 'ts/style/colors';
-import { zIndex } from 'ts/style/z_index';
import {
BlockchainErrs,
ProviderType,
@@ -35,6 +40,7 @@ import {
TokenStateByAddress,
WebsitePaths,
} from 'ts/types';
+import { analytics } from 'ts/utils/analytics';
import { constants } from 'ts/utils/constants';
import { utils } from 'ts/utils/utils';
import { styles as walletItemStyles } from 'ts/utils/wallet_item_styles';
@@ -59,6 +65,7 @@ export interface WalletProps {
onAddToken: () => void;
onRemoveToken: () => void;
refetchTokenStateAsync: (tokenAddress: string) => Promise<void>;
+ style: React.CSSProperties;
}
interface WalletState {
@@ -79,7 +86,6 @@ interface AccessoryItemConfig {
const styles: Styles = {
root: {
width: '100%',
- zIndex: zIndex.aboveOverlay,
position: 'relative',
},
footerItemInnerDiv: {
@@ -134,6 +140,9 @@ const NO_ALLOWANCE_TOGGLE_SPACE_WIDTH = 56;
const ACCOUNT_PATH = `${WebsitePaths.Portal}/account`;
export class Wallet extends React.Component<WalletProps, WalletState> {
+ public static defaultProps = {
+ style: {},
+ };
constructor(props: WalletProps) {
super(props);
this.state = {
@@ -141,11 +150,10 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
isHoveringSidebar: false,
};
}
-
public render(): React.ReactNode {
const isBlockchainLoaded = this.props.blockchainIsLoaded && this.props.blockchainErr === BlockchainErrs.NoError;
return (
- <Island className="flex flex-column wallet" style={styles.root}>
+ <Island className="flex flex-column wallet" style={{ ...styles.root, ...this.props.style }}>
{isBlockchainLoaded ? this._renderLoadedRows() : this._renderLoadingRows()}
</Island>
);
@@ -269,7 +277,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
<ListItem
primaryText={
<div className="flex right" style={styles.manageYourWalletText}>
- {'manage your wallet'}
+ manage your wallet
</div>
// https://github.com/palantir/tslint-react/issues/140
// tslint:disable-next-line:jsx-curly-spacing
@@ -488,18 +496,26 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
}
}
const onClick = isWrappedEtherDirectionOpen
- ? this._closeWrappedEtherActionRow.bind(this)
+ ? this._closeWrappedEtherActionRow.bind(this, wrappedEtherDirection)
: this._openWrappedEtherActionRow.bind(this, wrappedEtherDirection);
return (
<IconButton iconName={buttonIconName} labelText={buttonLabel} onClick={onClick} color={colors.mediumBlue} />
);
}
private _openWrappedEtherActionRow(wrappedEtherDirection: Side): void {
+ const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
+ const action =
+ wrappedEtherDirection === Side.Deposit ? 'Wallet - Wrap ETH Opened' : 'Wallet - Unwrap WETH Opened';
+ analytics.logEvent('Portal', action, networkName);
this.setState({
wrappedEtherDirection,
});
}
- private _closeWrappedEtherActionRow(): void {
+ private _closeWrappedEtherActionRow(wrappedEtherDirection: Side): void {
+ const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
+ const action =
+ wrappedEtherDirection === Side.Deposit ? 'Wallet - Wrap ETH Closed' : 'Wallet - Unwrap WETH Closed';
+ analytics.logEvent('Portal', action, networkName);
this.setState({
wrappedEtherDirection: undefined,
});
diff --git a/packages/website/ts/components/wallet/wrap_ether_item.tsx b/packages/website/ts/components/wallet/wrap_ether_item.tsx
index f65257142..d6135ce4d 100644
--- a/packages/website/ts/components/wallet/wrap_ether_item.tsx
+++ b/packages/website/ts/components/wallet/wrap_ether_item.tsx
@@ -1,4 +1,4 @@
-import { Styles } from '@0xproject/react-shared';
+import { constants as sharedConstants, Styles } from '@0xproject/react-shared';
import { BigNumber, logUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
@@ -11,6 +11,7 @@ import { TokenAmountInput } from 'ts/components/inputs/token_amount_input';
import { Dispatcher } from 'ts/redux/dispatcher';
import { colors } from 'ts/style/colors';
import { BlockchainCallErrs, Side, Token } from 'ts/types';
+import { analytics } from 'ts/utils/analytics';
import { constants } from 'ts/utils/constants';
import { errorReporter } from 'ts/utils/error_reporter';
import { utils } from 'ts/utils/utils';
@@ -186,6 +187,7 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
this.setState({
isEthConversionHappening: true,
});
+ const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
try {
const etherToken = this.props.etherToken;
const amountToConvert = this.state.currentInputAmount;
@@ -193,10 +195,12 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
await this.props.blockchain.convertEthToWrappedEthTokensAsync(etherToken.address, amountToConvert);
const ethAmount = Web3Wrapper.toUnitAmount(amountToConvert, constants.DECIMAL_PLACES_ETH);
this.props.dispatcher.showFlashMessage(`Successfully wrapped ${ethAmount.toString()} ETH to WETH`);
+ analytics.logEvent('Portal', 'Wrap ETH Successfully', networkName);
} else {
await this.props.blockchain.convertWrappedEthTokensToEthAsync(etherToken.address, amountToConvert);
const tokenAmount = Web3Wrapper.toUnitAmount(amountToConvert, etherToken.decimals);
this.props.dispatcher.showFlashMessage(`Successfully unwrapped ${tokenAmount.toString()} WETH to ETH`);
+ analytics.logEvent('Portal', 'Unwrap WETH Successfully', networkName);
}
await this.props.refetchEthTokenStateAsync();
this.props.onConversionSuccessful();
@@ -207,11 +211,13 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
} else if (!utils.didUserDenyWeb3Request(errMsg)) {
logUtils.log(`Unexpected error encountered: ${err}`);
logUtils.log(err.stack);
- const errorMsg =
- this.props.direction === Side.Deposit
- ? 'Failed to wrap your ETH. Please try again.'
- : 'Failed to unwrap your WETH. Please try again.';
- this.props.dispatcher.showFlashMessage(errorMsg);
+ if (this.props.direction === Side.Deposit) {
+ this.props.dispatcher.showFlashMessage('Failed to wrap your ETH. Please try again.');
+ analytics.logEvent('Portal', 'Wrap ETH Failed', networkName);
+ } else {
+ this.props.dispatcher.showFlashMessage('Failed to unwrap your WETH. Please try again.');
+ analytics.logEvent('Portal', 'Unwrap WETH Failed', networkName);
+ }
await errorReporter.reportAsync(err);
}
}
diff --git a/packages/website/ts/containers/portal.ts b/packages/website/ts/containers/portal.ts
index 5876e65f5..6747cdf4e 100644
--- a/packages/website/ts/containers/portal.ts
+++ b/packages/website/ts/containers/portal.ts
@@ -28,6 +28,8 @@ interface ConnectedState {
userSuppliedOrderCache: Order;
flashMessage?: string | React.ReactNode;
translate: Translate;
+ isPortalOnboardingShowing: boolean;
+ portalOnboardingStep: number;
}
interface ConnectedDispatch {
@@ -76,6 +78,8 @@ const mapStateToProps = (state: State, _ownProps: PortalComponentProps): Connect
userSuppliedOrderCache: state.userSuppliedOrderCache,
flashMessage: state.flashMessage,
translate: state.translate,
+ isPortalOnboardingShowing: state.isPortalOnboardingShowing,
+ portalOnboardingStep: state.portalOnboardingStep,
};
};
diff --git a/packages/website/ts/containers/portal_onboarding_flow.ts b/packages/website/ts/containers/portal_onboarding_flow.ts
index 746adf0ba..ba2b8f512 100644
--- a/packages/website/ts/containers/portal_onboarding_flow.ts
+++ b/packages/website/ts/containers/portal_onboarding_flow.ts
@@ -15,6 +15,7 @@ interface PortalOnboardingFlowProps {
}
interface ConnectedState {
+ networkId: number;
stepIndex: number;
isRunning: boolean;
userAddress: string;
@@ -32,6 +33,7 @@ interface ConnectedDispatch {
}
const mapStateToProps = (state: State, _ownProps: PortalOnboardingFlowProps): ConnectedState => ({
+ networkId: state.networkId,
stepIndex: state.portalOnboardingStep,
isRunning: state.isPortalOnboardingShowing,
userAddress: state.userAddress,
diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts
index 414361c1b..0bd3dbcfa 100644
--- a/packages/website/ts/utils/utils.ts
+++ b/packages/website/ts/utils/utils.ts
@@ -190,6 +190,25 @@ export const utils = {
const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287
return truncatedAddress;
},
+ getReadableAccountState(
+ isBlockchainReady: boolean,
+ providerType: ProviderType,
+ injectedProviderName: string,
+ userAddress?: string,
+ ): string {
+ const isAddressAvailable = !_.isUndefined(userAddress) && !_.isEmpty(userAddress);
+ const isExternallyInjectedProvider = utils.isExternallyInjected(providerType, injectedProviderName);
+ if (!isBlockchainReady) {
+ return 'Loading account';
+ } else if (isAddressAvailable) {
+ return utils.getAddressBeginAndEnd(userAddress);
+ // tslint:disable-next-line: prefer-conditional-expression
+ } else if (isExternallyInjectedProvider) {
+ return 'Account locked';
+ } else {
+ return 'No wallet detected';
+ }
+ },
hasUniqueNameAndSymbol(tokens: Token[], token: Token): boolean {
if (token.isRegistered) {
return true; // Since it's registered, it is the canonical token