Merge branch 'v2-prototype' of https://github.com/0xProject/0x-monorepo into bug/website/onboarding-improvements
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { constants as sharedConstants } from '@0xproject/react-shared';
|
||||
import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
import { RouteComponentProps, withRouter } from 'react-router';
|
||||
@@ -14,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 extends RouteComponentProps<any> {
|
||||
networkId: number;
|
||||
blockchain: Blockchain;
|
||||
stepIndex: number;
|
||||
isRunning: boolean;
|
||||
@@ -52,8 +55,8 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
|
||||
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)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -188,9 +191,21 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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';
|
||||
@@ -47,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';
|
||||
@@ -75,6 +76,7 @@ export interface PortalProps {
|
||||
lastForceTokenStateRefetch: number;
|
||||
translate: Translate;
|
||||
isPortalOnboardingShowing: boolean;
|
||||
portalOnboardingStep: number;
|
||||
}
|
||||
|
||||
interface PortalState {
|
||||
@@ -387,6 +389,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 {
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -39,7 +39,7 @@ const styles: Styles = {
|
||||
const CELL_HEIGHT = 290;
|
||||
const NUMBER_OF_COLUMNS_LARGE = 3;
|
||||
const NUMBER_OF_COLUMNS_MEDIUM = 2;
|
||||
const NUMBER_OF_COLUMNS_SMALL = 1;
|
||||
const NUMBER_OF_COLUMNS_SMALL = 2;
|
||||
const GRID_PADDING = 20;
|
||||
|
||||
export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerIndexState> {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
@@ -34,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';
|
||||
@@ -489,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,
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ interface ConnectedState {
|
||||
flashMessage?: string | React.ReactNode;
|
||||
translate: Translate;
|
||||
isPortalOnboardingShowing: boolean;
|
||||
portalOnboardingStep: number;
|
||||
}
|
||||
|
||||
interface ConnectedDispatch {
|
||||
@@ -78,6 +79,7 @@ const mapStateToProps = (state: State, _ownProps: PortalComponentProps): Connect
|
||||
flashMessage: state.flashMessage,
|
||||
translate: state.translate,
|
||||
isPortalOnboardingShowing: state.isPortalOnboardingShowing,
|
||||
portalOnboardingStep: state.portalOnboardingStep,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -173,6 +173,7 @@ const docsInfoConfig: DocsInfoConfig = {
|
||||
'OrderStateInvalid',
|
||||
'OrderState',
|
||||
'OrderStateWatcherConfig',
|
||||
'OrderWatcherConfig',
|
||||
'FilterObject',
|
||||
'OrderRelevantState',
|
||||
'JSONRPCRequestPayload',
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user