aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website/ts')
-rw-r--r--packages/website/ts/blockchain.ts31
-rw-r--r--packages/website/ts/blockchain_watcher.ts7
-rw-r--r--packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx2
-rw-r--r--packages/website/ts/components/dialogs/send_dialog.tsx1
-rw-r--r--packages/website/ts/components/inputs/allowance_toggle.tsx4
-rw-r--r--packages/website/ts/components/inputs/balance_bounded_input.tsx35
-rw-r--r--packages/website/ts/components/inputs/eth_amount_input.tsx4
-rw-r--r--packages/website/ts/components/inputs/token_amount_input.tsx2
-rw-r--r--packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx50
-rw-r--r--packages/website/ts/components/onboarding/congrats_onboarding_step.tsx2
-rw-r--r--packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx7
-rw-r--r--packages/website/ts/components/onboarding/intro_onboarding_step.tsx11
-rw-r--r--packages/website/ts/components/onboarding/onboarding_card.tsx33
-rw-r--r--packages/website/ts/components/onboarding/onboarding_flow.tsx74
-rw-r--r--packages/website/ts/components/onboarding/portal_onboarding_flow.tsx115
-rw-r--r--packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx3
-rw-r--r--packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx2
-rw-r--r--packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx132
-rw-r--r--packages/website/ts/components/portal/drawer_menu.tsx3
-rw-r--r--packages/website/ts/components/portal/portal.tsx66
-rw-r--r--packages/website/ts/components/portal/section.tsx4
-rw-r--r--packages/website/ts/components/relayer_index/relayer_grid_tile.tsx6
-rw-r--r--packages/website/ts/components/relayer_index/relayer_top_tokens.tsx63
-rw-r--r--packages/website/ts/components/top_bar/provider_display.tsx212
-rw-r--r--packages/website/ts/components/top_bar/provider_picker.tsx79
-rw-r--r--packages/website/ts/components/top_bar/top_bar.tsx4
-rw-r--r--packages/website/ts/components/ui/account_connection.tsx40
-rw-r--r--packages/website/ts/components/ui/animation.tsx8
-rw-r--r--packages/website/ts/components/ui/button.tsx2
-rw-r--r--packages/website/ts/components/ui/circle.tsx16
-rw-r--r--packages/website/ts/components/ui/container.tsx3
-rw-r--r--packages/website/ts/components/ui/drop_down.tsx52
-rw-r--r--packages/website/ts/components/ui/identicon.tsx6
-rw-r--r--packages/website/ts/components/ui/overlay.tsx3
-rw-r--r--packages/website/ts/components/ui/simple_menu.tsx88
-rw-r--r--packages/website/ts/components/ui/text.tsx10
-rw-r--r--packages/website/ts/components/wallet/body_overlay.tsx146
-rw-r--r--packages/website/ts/components/wallet/null_token_row.tsx41
-rw-r--r--packages/website/ts/components/wallet/placeholder.tsx25
-rw-r--r--packages/website/ts/components/wallet/standard_icon_row.tsx44
-rw-r--r--packages/website/ts/components/wallet/wallet.tsx433
-rw-r--r--packages/website/ts/components/wallet/wallet_disconnected_item.tsx100
-rw-r--r--packages/website/ts/components/wallet/wrap_ether_item.tsx7
-rw-r--r--packages/website/ts/containers/portal_onboarding_flow.ts4
-rw-r--r--packages/website/ts/containers/subproviders_documentation.ts3
-rw-r--r--packages/website/ts/index.tsx2
-rw-r--r--packages/website/ts/pages/about/about.tsx29
-rw-r--r--packages/website/ts/pages/jobs/list/list_item.tsx6
-rw-r--r--packages/website/ts/redux/reducer.ts8
-rw-r--r--packages/website/ts/redux/store.ts4
-rw-r--r--packages/website/ts/style/colors.ts4
-rw-r--r--packages/website/ts/style/media.ts14
-rw-r--r--packages/website/ts/types.ts29
-rw-r--r--packages/website/ts/utils/analytics.ts11
-rw-r--r--packages/website/ts/utils/configs.ts2
-rw-r--r--packages/website/ts/utils/constants.ts7
-rw-r--r--packages/website/ts/utils/utils.ts55
-rw-r--r--packages/website/ts/utils/wallet_item_styles.ts9
58 files changed, 1243 insertions, 920 deletions
diff --git a/packages/website/ts/blockchain.ts b/packages/website/ts/blockchain.ts
index d18c34c32..cc2afa28a 100644
--- a/packages/website/ts/blockchain.ts
+++ b/packages/website/ts/blockchain.ts
@@ -12,10 +12,10 @@ import {
import { isValidOrderHash, signOrderHashAsync } from '@0xproject/order-utils';
import { EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared';
import {
- InjectedWeb3Subprovider,
ledgerEthereumBrowserClientFactoryAsync,
LedgerSubprovider,
RedundantSubprovider,
+ SignerSubprovider,
Subprovider,
} from '@0xproject/subproviders';
import {
@@ -46,6 +46,7 @@ import {
Fill,
InjectedProviderObservable,
InjectedProviderUpdate,
+ InjectedWeb3,
Order as PortalOrder,
Providers,
ProviderType,
@@ -59,7 +60,6 @@ import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
import { errorReporter } from 'ts/utils/error_reporter';
import { utils } from 'ts/utils/utils';
-import Web3 = require('web3');
import ProviderEngine = require('web3-provider-engine');
import FilterSubprovider = require('web3-provider-engine/subproviders/filters');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
@@ -73,6 +73,8 @@ const providerToName: { [provider: string]: string } = {
[Providers.Metamask]: constants.PROVIDER_NAME_METAMASK,
[Providers.Parity]: constants.PROVIDER_NAME_PARITY_SIGNER,
[Providers.Mist]: constants.PROVIDER_NAME_MIST,
+ [Providers.Toshi]: constants.PROVIDER_NAME_TOSHI,
+ [Providers.Cipher]: constants.PROVIDER_NAME_CIPHER,
};
export class Blockchain {
@@ -95,8 +97,19 @@ export class Blockchain {
}
return providerNameIfExists;
}
- private static _getInjectedWeb3(): any {
- return (window as any).web3;
+ private static _getInjectedWeb3(): InjectedWeb3 {
+ const injectedWeb3IfExists = (window as any).web3;
+ // Our core assumptions about the injected web3 object is that it has the following
+ // properties and methods.
+ if (
+ _.isUndefined(injectedWeb3IfExists) ||
+ _.isUndefined(injectedWeb3IfExists.version) ||
+ _.isUndefined(injectedWeb3IfExists.version.getNetwork) ||
+ _.isUndefined(injectedWeb3IfExists.currentProvider)
+ ) {
+ return undefined;
+ }
+ return injectedWeb3IfExists;
}
private static async _getInjectedWeb3ProviderNetworkIdIfExistsAsync(): Promise<number | undefined> {
// Hack: We need to know the networkId the injectedWeb3 is connected to (if it is defined) in
@@ -117,7 +130,7 @@ export class Blockchain {
return networkIdIfExists;
}
private static async _getProviderAsync(
- injectedWeb3: Web3,
+ injectedWeb3: InjectedWeb3,
networkIdIfExists: number,
shouldUserLedgerProvider: boolean = false,
): Promise<[Provider, LedgerSubprovider | undefined]> {
@@ -151,7 +164,7 @@ export class Blockchain {
// We catch all requests involving a users account and send it to the injectedWeb3
// instance. All other requests go to the public hosted node.
const provider = new ProviderEngine();
- provider.addProvider(new InjectedWeb3Subprovider(injectedWeb3.currentProvider));
+ provider.addProvider(new SignerSubprovider(injectedWeb3.currentProvider));
provider.addProvider(new FilterSubprovider());
const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => {
return new RpcSubprovider({
@@ -832,10 +845,10 @@ export class Blockchain {
this._dispatcher.updateNetworkId(networkId);
await this._rehydrateStoreWithContractEventsAsync();
}
- private _updateProviderName(injectedWeb3: Web3): void {
- const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3);
+ private _updateProviderName(injectedWeb3IfExists: InjectedWeb3): void {
+ const doesInjectedWeb3Exist = !_.isUndefined(injectedWeb3IfExists);
const providerName = doesInjectedWeb3Exist
- ? Blockchain._getNameGivenProvider(injectedWeb3.currentProvider)
+ ? Blockchain._getNameGivenProvider(injectedWeb3IfExists.currentProvider)
: constants.PROVIDER_NAME_PUBLIC;
this._dispatcher.updateInjectedProviderName(providerName);
}
diff --git a/packages/website/ts/blockchain_watcher.ts b/packages/website/ts/blockchain_watcher.ts
index df5f73fd1..4b23aa98a 100644
--- a/packages/website/ts/blockchain_watcher.ts
+++ b/packages/website/ts/blockchain_watcher.ts
@@ -10,6 +10,7 @@ export class BlockchainWatcher {
private _watchBalanceIntervalId: NodeJS.Timer;
private _prevUserEtherBalanceInWei?: BigNumber;
private _prevUserAddressIfExists: string;
+ private _prevNodeVersionIfExists: string;
constructor(dispatcher: Dispatcher, web3Wrapper: Web3Wrapper, shouldPollUserAddress: boolean) {
this._dispatcher = dispatcher;
this._shouldPollUserAddress = shouldPollUserAddress;
@@ -43,11 +44,9 @@ export class BlockchainWatcher {
);
}
private async _updateBalanceAsync(): Promise<void> {
- let prevNodeVersion: string;
- // Check for node version changes
const currentNodeVersion = await this._web3Wrapper.getNodeVersionAsync();
- if (currentNodeVersion !== prevNodeVersion) {
- prevNodeVersion = currentNodeVersion;
+ if (this._prevNodeVersionIfExists !== currentNodeVersion) {
+ this._prevNodeVersionIfExists = currentNodeVersion;
this._dispatcher.updateNodeVersion(currentNodeVersion);
}
diff --git a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx
index 9ac78e80e..7b09cc92c 100644
--- a/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx
+++ b/packages/website/ts/components/dialogs/eth_weth_conversion_dialog.tsx
@@ -103,7 +103,6 @@ export class EthWethConversionDialog extends React.Component<
shouldCheckAllowance={false}
onChange={this._onValueChange.bind(this)}
amount={this.state.value}
- onVisitBalancesPageClick={this.props.onCancelled}
/>
) : (
<EthAmountInput
@@ -112,7 +111,6 @@ export class EthWethConversionDialog extends React.Component<
onChange={this._onValueChange.bind(this)}
shouldCheckBalance={true}
shouldShowIncompleteErrs={this.state.shouldShowIncompleteErrs}
- onVisitBalancesPageClick={this.props.onCancelled}
/>
)}
<div className="pt1" style={{ fontSize: 12 }}>
diff --git a/packages/website/ts/components/dialogs/send_dialog.tsx b/packages/website/ts/components/dialogs/send_dialog.tsx
index 421f18b4f..8a98fdf69 100644
--- a/packages/website/ts/components/dialogs/send_dialog.tsx
+++ b/packages/website/ts/components/dialogs/send_dialog.tsx
@@ -80,7 +80,6 @@ export class SendDialog extends React.Component<SendDialogProps, SendDialogState
shouldCheckAllowance={false}
onChange={this._onValueChange.bind(this)}
amount={this.state.value}
- onVisitBalancesPageClick={this.props.onCancelled}
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
/>
</div>
diff --git a/packages/website/ts/components/inputs/allowance_toggle.tsx b/packages/website/ts/components/inputs/allowance_toggle.tsx
index 0dd2a5aa5..0d5995696 100644
--- a/packages/website/ts/components/inputs/allowance_toggle.tsx
+++ b/packages/website/ts/components/inputs/allowance_toggle.tsx
@@ -48,10 +48,10 @@ const styles: Styles = {
width: 25,
},
offTrackStyle: {
- backgroundColor: colors.allowanceToggleOffTrack,
+ backgroundColor: colors.grey300,
},
onTrackStyle: {
- backgroundColor: colors.allowanceToggleOnTrack,
+ backgroundColor: colors.mediumBlue,
},
};
diff --git a/packages/website/ts/components/inputs/balance_bounded_input.tsx b/packages/website/ts/components/inputs/balance_bounded_input.tsx
index 968609030..f23beb436 100644
--- a/packages/website/ts/components/inputs/balance_bounded_input.tsx
+++ b/packages/website/ts/components/inputs/balance_bounded_input.tsx
@@ -3,9 +3,8 @@ import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import TextField from 'material-ui/TextField';
import * as React from 'react';
-import { Link } from 'react-router-dom';
import { RequiredLabel } from 'ts/components/ui/required_label';
-import { ValidatedBigNumberCallback, WebsitePaths } from 'ts/types';
+import { ValidatedBigNumberCallback } from 'ts/types';
import { utils } from 'ts/utils/utils';
interface BalanceBoundedInputProps {
@@ -18,8 +17,6 @@ interface BalanceBoundedInputProps {
shouldShowIncompleteErrs?: boolean;
shouldCheckBalance: boolean;
validate?: (amount: BigNumber) => React.ReactNode;
- onVisitBalancesPageClick?: () => void;
- shouldHideVisitBalancesLink?: boolean;
isDisabled?: boolean;
shouldShowErrs?: boolean;
shouldShowUnderline?: boolean;
@@ -35,7 +32,6 @@ interface BalanceBoundedInputState {
export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProps, BalanceBoundedInputState> {
public static defaultProps: Partial<BalanceBoundedInputProps> = {
shouldShowIncompleteErrs: false,
- shouldHideVisitBalancesLink: false,
isDisabled: false,
shouldShowErrs: true,
hintText: 'amount',
@@ -124,38 +120,11 @@ export class BalanceBoundedInput extends React.Component<BalanceBoundedInputProp
return 'Cannot be zero';
}
if (this.props.shouldCheckBalance && amount.gt(balance)) {
- return <span>Insufficient balance. {this._renderIncreaseBalanceLink()}</span>;
+ return <span>Insufficient balance.</span>;
}
const errMsg = _.isUndefined(this.props.validate) ? undefined : this.props.validate(amount);
return errMsg;
}
- private _renderIncreaseBalanceLink(): React.ReactNode {
- if (this.props.shouldHideVisitBalancesLink) {
- return null;
- }
-
- const increaseBalanceText = 'Increase balance';
- const linkStyle = {
- cursor: 'pointer',
- color: colors.darkestGrey,
- textDecoration: 'underline',
- display: 'inline',
- };
- if (_.isUndefined(this.props.onVisitBalancesPageClick)) {
- return (
- <Link to={`${WebsitePaths.Portal}/balances`} style={linkStyle}>
- {increaseBalanceText}
- </Link>
- );
- } else {
- return (
- <div onClick={this.props.onVisitBalancesPageClick} style={linkStyle}>
- {increaseBalanceText}
- </div>
- );
- }
- }
-
private _setAmountState(amount: string, balance: BigNumber, callback: () => void = _.noop): void {
const errorMsg = this._validate(amount, balance);
this.props.onErrorMsgChange(errorMsg);
diff --git a/packages/website/ts/components/inputs/eth_amount_input.tsx b/packages/website/ts/components/inputs/eth_amount_input.tsx
index 1f0f27410..552d4277a 100644
--- a/packages/website/ts/components/inputs/eth_amount_input.tsx
+++ b/packages/website/ts/components/inputs/eth_amount_input.tsx
@@ -14,9 +14,7 @@ interface EthAmountInputProps {
onChange: ValidatedBigNumberCallback;
onErrorMsgChange?: (errorMsg: React.ReactNode) => void;
shouldShowIncompleteErrs: boolean;
- onVisitBalancesPageClick?: () => void;
shouldCheckBalance: boolean;
- shouldHideVisitBalancesLink?: boolean;
shouldShowErrs?: boolean;
shouldShowUnderline?: boolean;
style?: React.CSSProperties;
@@ -46,8 +44,6 @@ export class EthAmountInput extends React.Component<EthAmountInputProps, EthAmou
onErrorMsgChange={this.props.onErrorMsgChange}
shouldCheckBalance={this.props.shouldCheckBalance}
shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs}
- onVisitBalancesPageClick={this.props.onVisitBalancesPageClick}
- shouldHideVisitBalancesLink={this.props.shouldHideVisitBalancesLink}
hintText={this.props.hintText}
shouldShowErrs={this.props.shouldShowErrs}
shouldShowUnderline={this.props.shouldShowUnderline}
diff --git a/packages/website/ts/components/inputs/token_amount_input.tsx b/packages/website/ts/components/inputs/token_amount_input.tsx
index a67120320..93ef516cf 100644
--- a/packages/website/ts/components/inputs/token_amount_input.tsx
+++ b/packages/website/ts/components/inputs/token_amount_input.tsx
@@ -21,7 +21,6 @@ interface TokenAmountInputProps {
shouldCheckAllowance: boolean;
onChange: ValidatedBigNumberCallback;
onErrorMsgChange?: (errorMsg: React.ReactNode) => void;
- onVisitBalancesPageClick?: () => void;
lastForceTokenStateRefetch: number;
shouldShowErrs?: boolean;
shouldShowUnderline?: boolean;
@@ -88,7 +87,6 @@ export class TokenAmountInput extends React.Component<TokenAmountInputProps, Tok
validate={this._validate.bind(this)}
shouldCheckBalance={this.props.shouldCheckBalance}
shouldShowIncompleteErrs={this.props.shouldShowIncompleteErrs}
- onVisitBalancesPageClick={this.props.onVisitBalancesPageClick}
isDisabled={!this.state.isBalanceAndAllowanceLoaded}
hintText={this.props.hintText}
shouldShowErrs={this.props.shouldShowErrs}
diff --git a/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx b/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx
index 31ce99d31..bccdc0c18 100644
--- a/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx
+++ b/packages/website/ts/components/onboarding/add_eth_onboarding_step.tsx
@@ -1,18 +1,42 @@
+import { BigNumber } from '@0xproject/utils';
import * as React from 'react';
import { Container } from 'ts/components/ui/container';
+import { Image } from 'ts/components/ui/image';
import { Text } from 'ts/components/ui/text';
+import { constants } from 'ts/utils/constants';
+import { utils } from 'ts/utils/utils';
-export interface AddEthOnboardingStepProps {}
+export interface AddEthOnboardingStepProps {
+ userEthBalanceInWei: BigNumber;
+}
-export const AddEthOnboardingStep: React.StatelessComponent<AddEthOnboardingStepProps> = () => (
- <div className="flex items-center flex-column">
- <Text> Before you begin you will need to send some ETH to your metamask wallet.</Text>
- <Container marginTop="15px" marginBottom="15px">
- <img src="/images/ether_alt.svg" height="50px" width="50px" />
- </Container>
- <Text>
- Click on the <img src="/images/metamask_icon.png" height="20px" width="20px" /> metamask extension in your
- browser and click either <b>BUY</b> or <b>DEPOSIT</b>.
- </Text>
- </div>
-);
+export const AddEthOnboardingStep: React.StatelessComponent<AddEthOnboardingStepProps> = props =>
+ props.userEthBalanceInWei.gt(0) ? (
+ <div className="flex items-center flex-column">
+ <Text>
+ Great! Looks like you already have{' '}
+ <b>
+ {utils.getFormattedAmount(
+ props.userEthBalanceInWei,
+ constants.DECIMAL_PLACES_ETH,
+ constants.ETHER_SYMBOL,
+ )}{' '}
+ </b>
+ in your wallet.
+ </Text>
+ <Container marginTop="15px" marginBottom="15px">
+ <Image src="/images/ether_alt.svg" height="50px" width="50px" />
+ </Container>
+ </div>
+ ) : (
+ <div className="flex items-center flex-column">
+ <Text> Before you begin you will need to send some ETH to your wallet.</Text>
+ <Container marginTop="15px" marginBottom="15px">
+ <Image src="/images/ether_alt.svg" height="50px" width="50px" />
+ </Container>
+ <Text className="xs-hide">
+ Click on the <Image src="/images/metamask_icon.png" height="20px" width="20px" /> MetaMask extension in
+ your browser and click either <b>BUY</b> or <b>DEPOSIT</b>.
+ </Text>
+ </div>
+ );
diff --git a/packages/website/ts/components/onboarding/congrats_onboarding_step.tsx b/packages/website/ts/components/onboarding/congrats_onboarding_step.tsx
index 3a8db8c36..8100fd2c0 100644
--- a/packages/website/ts/components/onboarding/congrats_onboarding_step.tsx
+++ b/packages/website/ts/components/onboarding/congrats_onboarding_step.tsx
@@ -10,6 +10,6 @@ export const CongratsOnboardingStep: React.StatelessComponent<CongratsOnboarding
<Container marginTop="25px" marginBottom="15px" className="flex justify-center">
<img src="/images/zrx_ecosystem.svg" height="150px" />
</Container>
- <Text>No need to log in. Each relayer automatically detects and connects to your metamask wallet.</Text>
+ <Text>No need to log in. Each relayer automatically detects and connects to your wallet.</Text>
</div>
);
diff --git a/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx b/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx
index a54496186..a95c464af 100644
--- a/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx
+++ b/packages/website/ts/components/onboarding/install_wallet_onboarding_step.tsx
@@ -8,11 +8,12 @@ export interface InstallWalletOnboardingStepProps {}
export const InstallWalletOnboardingStep: React.StatelessComponent<InstallWalletOnboardingStepProps> = () => (
<div className="flex items-center flex-column">
- <Container marginTop="15px" marginBottom="15px">
- <ActionAccountBalanceWallet style={{ width: '30px', height: '30px' }} color={colors.orange} />
- </Container>
<Text>
Before you begin, you need to connect to a wallet. This will be used across all 0x relayers and dApps.
</Text>
+ <Container marginTop="15px" marginBottom="15px">
+ <ActionAccountBalanceWallet style={{ width: '50px', height: '50px' }} color={colors.orange} />
+ </Container>
+ <Text>Please refresh the page once you've done this to continue!</Text>
</div>
);
diff --git a/packages/website/ts/components/onboarding/intro_onboarding_step.tsx b/packages/website/ts/components/onboarding/intro_onboarding_step.tsx
index 548839218..3a27b6854 100644
--- a/packages/website/ts/components/onboarding/intro_onboarding_step.tsx
+++ b/packages/website/ts/components/onboarding/intro_onboarding_step.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
import { Container } from 'ts/components/ui/container';
+import { Image } from 'ts/components/ui/image';
import { Text } from 'ts/components/ui/text';
export interface IntroOnboardingStepProps {}
@@ -7,15 +8,19 @@ export interface IntroOnboardingStepProps {}
export const IntroOnboardingStep: React.StatelessComponent<IntroOnboardingStepProps> = () => (
<div className="flex items-center flex-column">
<Text>
- In order to start trading on any 0x relayer in the 0x ecosystem, you need to complete two simple steps.
+ In order to start trading on any 0x relayer in the 0x ecosystem, you need to complete three simple steps.
</Text>
<Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-around">
<div className="flex flex-column items-center">
- <img src="/images/eth_token.svg" height="50px" width="50x" />
+ <Image src="/images/ether.png" height="50px" width="50px" />
+ <Text> Add ETH </Text>
+ </div>
+ <div className="flex flex-column items-center">
+ <Image src="/images/eth_token.svg" height="50px" width="50x" />
<Text> Wrap ETH </Text>
</div>
<div className="flex flex-column items-center">
- <img src="/images/fake_toggle.svg" height="50px" width="50px" />
+ <Image src="/images/fake_toggle.svg" height="50px" width="50px" />
<Text> Unlock tokens </Text>
</div>
</Container>
diff --git a/packages/website/ts/components/onboarding/onboarding_card.tsx b/packages/website/ts/components/onboarding/onboarding_card.tsx
index bc83b8034..ba5b3d6ea 100644
--- a/packages/website/ts/components/onboarding/onboarding_card.tsx
+++ b/packages/website/ts/components/onboarding/onboarding_card.tsx
@@ -1,6 +1,7 @@
import { colors } from '@0xproject/react-shared';
import * as React from 'react';
+import * as _ from 'lodash';
import { Button } from 'ts/components/ui/button';
import { Container } from 'ts/components/ui/container';
import { IconButton } from 'ts/components/ui/icon_button';
@@ -16,6 +17,7 @@ export interface OnboardingCardProps {
onClose: () => void;
onClickNext: () => void;
onClickBack: () => void;
+ onContinueButtonClick?: () => void;
continueButtonDisplay?: ContinueButtonDisplay;
shouldHideBackButton?: boolean;
shouldHideNextButton?: boolean;
@@ -28,6 +30,7 @@ export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({
content,
continueButtonDisplay,
continueButtonText,
+ onContinueButtonClick,
onClickNext,
onClickBack,
onClose,
@@ -36,7 +39,7 @@ export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({
borderRadius,
}) => (
<Island borderRadius={borderRadius}>
- <Container paddingRight="30px" paddingLeft="30px" maxWidth={350} paddingTop="15px" paddingBottom="15px">
+ <Container paddingRight="30px" paddingLeft="30px" paddingTop="15px" paddingBottom="15px">
<div className="flex flex-column">
<div className="flex justify-between">
<Title>{title}</Title>
@@ -52,7 +55,7 @@ export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({
{continueButtonDisplay && (
<Button
isDisabled={continueButtonDisplay === 'disabled'}
- onClick={onClickNext}
+ onClick={!_.isUndefined(onContinueButtonClick) ? onContinueButtonClick : onClickNext}
fontColor={colors.white}
fontSize="15px"
backgroundColor={colors.mediumBlue}
@@ -60,17 +63,21 @@ export const OnboardingCard: React.StatelessComponent<OnboardingCardProps> = ({
{continueButtonText}
</Button>
)}
- <Container className="flex justify-between" marginTop="15px">
- {!shouldHideBackButton && (
- <Text fontColor={colors.grey} onClick={onClickBack}>
- Back
- </Text>
- )}
- {!shouldHideNextButton && (
- <Text fontColor={colors.grey} onClick={onClickNext}>
- Skip
- </Text>
- )}
+ <Container className="clearfix" marginTop="15px">
+ <div className="left">
+ {!shouldHideBackButton && (
+ <Text fontColor={colors.grey} onClick={onClickBack}>
+ Back
+ </Text>
+ )}
+ </div>
+ <div className="right">
+ {!shouldHideNextButton && (
+ <Text fontColor={colors.grey} onClick={onClickNext}>
+ Skip
+ </Text>
+ )}
+ </div>
</Container>
</div>
</Container>
diff --git a/packages/website/ts/components/onboarding/onboarding_flow.tsx b/packages/website/ts/components/onboarding/onboarding_flow.tsx
index ec8d96191..c2b4a4ca7 100644
--- a/packages/website/ts/components/onboarding/onboarding_flow.tsx
+++ b/packages/website/ts/components/onboarding/onboarding_flow.tsx
@@ -6,16 +6,34 @@ import { ContinueButtonDisplay, OnboardingTooltip } from 'ts/components/onboardi
import { Animation } from 'ts/components/ui/animation';
import { Container } from 'ts/components/ui/container';
import { Overlay } from 'ts/components/ui/overlay';
+import { PointerDirection } from 'ts/components/ui/pointer';
+import { zIndex } from 'ts/style/z_index';
-export interface Step {
+export interface FixedPositionSettings {
+ type: 'fixed';
+ top?: string;
+ bottom?: string;
+ left?: string;
+ right?: string;
+ pointerDirection?: PointerDirection;
+}
+
+export interface TargetPositionSettings {
+ type: 'target';
target: string;
+ placement: Placement;
+}
+
+export interface Step {
+ // Provide either a CSS selector, or fixed position settings. Only applies to desktop.
+ position: TargetPositionSettings | FixedPositionSettings;
title?: string;
content: React.ReactNode;
- placement?: Placement;
shouldHideBackButton?: boolean;
shouldHideNextButton?: boolean;
continueButtonDisplay?: ContinueButtonDisplay;
continueButtonText?: string;
+ onContinueButtonClick?: () => void;
}
export interface OnboardingFlowProps {
@@ -38,40 +56,57 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
return null;
}
let onboardingElement = null;
+ const currentStep = this._getCurrentStep();
if (this.props.isMobile) {
- onboardingElement = <Animation type="easeUpFromBottom">{this._renderOnboardignCard()}</Animation>;
- } else {
+ onboardingElement = <Animation type="easeUpFromBottom">{this._renderOnboardingCard()}</Animation>;
+ } else if (currentStep.position.type === 'target') {
+ const { placement, target } = currentStep.position;
onboardingElement = (
- <Popper
- referenceElement={this._getElementForStep()}
- placement={this._getCurrentStep().placement}
- positionFixed={true}
- >
+ <Popper referenceElement={document.querySelector(target)} placement={placement} positionFixed={true}>
{this._renderPopperChildren.bind(this)}
</Popper>
);
+ } else if (currentStep.position.type === 'fixed') {
+ const { top, right, bottom, left, pointerDirection } = currentStep.position;
+ onboardingElement = (
+ <Container
+ position="fixed"
+ zIndex={zIndex.aboveOverlay}
+ top={top}
+ right={right}
+ bottom={bottom}
+ left={left}
+ >
+ {this._renderToolTip(pointerDirection)}
+ </Container>
+ );
}
if (this.props.disableOverlay) {
return onboardingElement;
}
- return <Overlay>{onboardingElement}</Overlay>;
- }
- private _getElementForStep(): Element {
- return document.querySelector(this._getCurrentStep().target);
+ return (
+ <div>
+ <Overlay onClick={this.props.onClose} />
+ {onboardingElement}
+ </div>
+ );
}
private _renderPopperChildren(props: PopperChildrenProps): React.ReactNode {
+ const customStyles = { zIndex: zIndex.aboveOverlay };
+ // On re-render, we want to re-center the popper.
+ props.scheduleUpdate();
return (
- <div ref={props.ref} style={props.style} data-placement={props.placement}>
+ <div ref={props.ref} style={{ ...props.style, ...customStyles }} data-placement={props.placement}>
{this._renderToolTip()}
</div>
);
}
- private _renderToolTip(): React.ReactNode {
+ private _renderToolTip(pointerDirection?: PointerDirection): React.ReactNode {
const { steps, stepIndex } = this.props;
const step = steps[stepIndex];
const isLastStep = steps.length - 1 === stepIndex;
return (
- <Container marginLeft="30px" maxWidth={350}>
+ <Container marginLeft="30px" width="400px">
<OnboardingTooltip
title={step.title}
content={step.content}
@@ -83,17 +118,19 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
onClickBack={this._goToPrevStep.bind(this)}
continueButtonDisplay={step.continueButtonDisplay}
continueButtonText={step.continueButtonText}
+ onContinueButtonClick={step.onContinueButtonClick}
+ pointerDirection={pointerDirection}
/>
</Container>
);
}
- private _renderOnboardignCard(): React.ReactNode {
+ private _renderOnboardingCard(): React.ReactNode {
const { steps, stepIndex } = this.props;
const step = steps[stepIndex];
const isLastStep = steps.length - 1 === stepIndex;
return (
- <Container position="relative" zIndex={1} maxWidth="100vw">
+ <Container position="relative" zIndex={1}>
<OnboardingCard
title={step.title}
content={step.content}
@@ -105,6 +142,7 @@ export class OnboardingFlow extends React.Component<OnboardingFlowProps> {
onClickBack={this._goToPrevStep.bind(this)}
continueButtonDisplay={step.continueButtonDisplay}
continueButtonText={step.continueButtonText}
+ onContinueButtonClick={step.onContinueButtonClick}
borderRadius="10px 10px 0px 0px"
/>
</Container>
diff --git a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
index 296b410fe..b7c5a9f64 100644
--- a/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
+++ b/packages/website/ts/components/onboarding/portal_onboarding_flow.tsx
@@ -9,10 +9,19 @@ import { AddEthOnboardingStep } from 'ts/components/onboarding/add_eth_onboardin
import { CongratsOnboardingStep } from 'ts/components/onboarding/congrats_onboarding_step';
import { InstallWalletOnboardingStep } from 'ts/components/onboarding/install_wallet_onboarding_step';
import { IntroOnboardingStep } from 'ts/components/onboarding/intro_onboarding_step';
-import { OnboardingFlow, Step } from 'ts/components/onboarding/onboarding_flow';
+import {
+ FixedPositionSettings,
+ OnboardingFlow,
+ Step,
+ TargetPositionSettings,
+} from 'ts/components/onboarding/onboarding_flow';
import { SetAllowancesOnboardingStep } from 'ts/components/onboarding/set_allowances_onboarding_step';
import { UnlockWalletOnboardingStep } from 'ts/components/onboarding/unlock_wallet_onboarding_step';
-import { WrapEthOnboardingStep } from 'ts/components/onboarding/wrap_eth_onboarding_step';
+import {
+ WrapEthOnboardingStep1,
+ WrapEthOnboardingStep2,
+ WrapEthOnboardingStep3,
+} from 'ts/components/onboarding/wrap_eth_onboarding_step';
import { AllowanceToggle } from 'ts/containers/inputs/allowance_toggle';
import { ProviderType, ScreenWidths, Token, TokenByAddress, TokenStateByAddress } from 'ts/types';
import { analytics } from 'ts/utils/analytics';
@@ -24,7 +33,7 @@ export interface PortalOnboardingFlowProps extends RouteComponentProps<any> {
stepIndex: number;
isRunning: boolean;
userAddress: string;
- hasBeenSeen: boolean;
+ hasBeenClosed: boolean;
providerType: ProviderType;
injectedProviderName: string;
blockchainIsLoaded: boolean;
@@ -40,15 +49,24 @@ export interface PortalOnboardingFlowProps extends RouteComponentProps<any> {
class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProps> {
private _unlisten: () => void;
public componentDidMount(): void {
- this._overrideOnboardingStateIfShould();
+ this._adjustStepIfShould();
// 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();
+ public componentDidUpdate(prevProps: PortalOnboardingFlowProps): void {
+ this._adjustStepIfShould();
+ if (!prevProps.isRunning && this.props.isRunning) {
+ // On mobile, make sure the wallet is completely visible.
+ if (this.props.screenWidth === ScreenWidths.Sm) {
+ document.querySelector('.wallet').scrollIntoView();
+ }
+ }
+ if (!prevProps.blockchainIsLoaded && this.props.blockchainIsLoaded) {
+ this._autoStartOnboardingIfShould();
+ }
}
public render(): React.ReactNode {
return (
@@ -64,71 +82,91 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
);
}
private _getSteps(): Step[] {
+ const nextToWalletPosition: TargetPositionSettings = {
+ type: 'target',
+ target: '.wallet',
+ placement: 'right',
+ };
+ const underMetamaskExtension: FixedPositionSettings = {
+ type: 'fixed',
+ top: '30px',
+ right: '10px',
+ pointerDirection: 'top',
+ };
const steps: Step[] = [
{
- target: '.wallet',
+ position: nextToWalletPosition,
title: '0x Ecosystem Setup',
content: <InstallWalletOnboardingStep />,
- placement: 'right',
shouldHideBackButton: true,
shouldHideNextButton: true,
},
{
- target: '.wallet',
+ position: underMetamaskExtension,
title: '0x Ecosystem Setup',
content: <UnlockWalletOnboardingStep />,
- placement: 'right',
shouldHideBackButton: true,
shouldHideNextButton: true,
},
{
- target: '.wallet',
+ position: nextToWalletPosition,
title: '0x Ecosystem Account Setup',
content: <IntroOnboardingStep />,
- placement: 'right',
shouldHideBackButton: true,
continueButtonDisplay: 'enabled',
},
{
- target: '.eth-row',
- title: 'Add ETH',
- content: <AddEthOnboardingStep />,
- placement: 'right',
+ position: nextToWalletPosition,
+ title: 'Step 1: Add ETH',
+ content: (
+ <AddEthOnboardingStep userEthBalanceInWei={this.props.userEtherBalanceInWei || new BigNumber(0)} />
+ ),
continueButtonDisplay: this._userHasVisibleEth() ? 'enabled' : 'disabled',
},
{
- target: '.weth-row',
- title: 'Step 1/2',
+ position: nextToWalletPosition,
+ title: 'Step 2: Wrap ETH',
+ content: <WrapEthOnboardingStep1 />,
+ continueButtonDisplay: 'enabled',
+ },
+ {
+ position: nextToWalletPosition,
+ title: 'Step 2: Wrap ETH',
+ content: <WrapEthOnboardingStep2 />,
+ continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled',
+ },
+ {
+ position: nextToWalletPosition,
+ title: 'Step 2: Wrap ETH',
content: (
- <WrapEthOnboardingStep
- formattedEthBalanceIfExists={
+ <WrapEthOnboardingStep3
+ formattedWethBalanceIfExists={
this._userHasVisibleWeth() ? this._getFormattedWethBalance() : undefined
}
/>
),
- placement: 'right',
- continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : undefined,
+ continueButtonDisplay: this._userHasVisibleWeth() ? 'enabled' : 'disabled',
},
{
- target: '.weth-row',
- title: 'Step 2/2',
+ position: nextToWalletPosition,
+ title: 'Step 3: Unlock Tokens',
content: (
<SetAllowancesOnboardingStep
zrxAllowanceToggle={this._renderZrxAllowanceToggle()}
ethAllowanceToggle={this._renderEthAllowanceToggle()}
+ doesUserHaveAllowancesForWethAndZrx={this._doesUserHaveAllowancesForWethAndZrx()}
/>
),
- placement: 'right',
- continueButtonDisplay: this._userHasAllowancesForWethAndZrx() ? 'enabled' : 'disabled',
+ continueButtonDisplay: this._doesUserHaveAllowancesForWethAndZrx() ? 'enabled' : 'disabled',
},
{
- target: '.wallet',
- title: '🎉 Congrats! The ecosystem awaits.',
+ position: nextToWalletPosition,
+ title: '🎉 The Ecosystem Awaits',
content: <CongratsOnboardingStep />,
- placement: 'right',
continueButtonDisplay: 'enabled',
shouldHideNextButton: true,
continueButtonText: 'Enter the 0x Ecosystem',
+ onContinueButtonClick: this._handleFinalStepContinueClick.bind(this),
},
];
return steps;
@@ -155,7 +193,7 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
private _userHasVisibleWeth(): boolean {
return this._getWethBalance() > new BigNumber(0);
}
- private _userHasAllowancesForWethAndZrx(): boolean {
+ private _doesUserHaveAllowancesForWethAndZrx(): boolean {
const ethToken = utils.getEthToken(this.props.tokenByAddress);
const zrxToken = utils.getZrxToken(this.props.tokenByAddress);
if (ethToken && zrxToken) {
@@ -167,11 +205,6 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
}
return false;
}
- private _overrideOnboardingStateIfShould(): void {
- this._autoStartOnboardingIfShould();
- this._adjustStepIfShould();
- }
-
private _adjustStepIfShould(): void {
const stepIndex = this.props.stepIndex;
if (this._isAddressAvailable()) {
@@ -195,7 +228,10 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
}
}
private _autoStartOnboardingIfShould(): void {
- if (!this.props.isRunning && !this.props.hasBeenSeen && this.props.blockchainIsLoaded) {
+ if (
+ (this.props.stepIndex === 0 && !this.props.isRunning && this.props.blockchainIsLoaded) ||
+ (!this.props.isRunning && !this.props.hasBeenClosed && 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);
@@ -238,6 +274,13 @@ class PlainPortalOnboardingFlow extends React.Component<PortalOnboardingFlowProp
/>
);
}
+ private _handleFinalStepContinueClick(): void {
+ if (utils.isMobile(this.props.screenWidth)) {
+ window.scrollTo(0, 0);
+ this.props.history.push('/portal');
+ }
+ this._closeOnboarding();
+ }
}
export const PortalOnboardingFlow = withRouter(PlainPortalOnboardingFlow);
diff --git a/packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx b/packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx
index 1ff248c40..5ddfe38d7 100644
--- a/packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx
+++ b/packages/website/ts/components/onboarding/set_allowances_onboarding_step.tsx
@@ -5,11 +5,13 @@ import { Text } from 'ts/components/ui/text';
export interface SetAllowancesOnboardingStepProps {
zrxAllowanceToggle: React.ReactNode;
ethAllowanceToggle: React.ReactNode;
+ doesUserHaveAllowancesForWethAndZrx: boolean;
}
export const SetAllowancesOnboardingStep: React.StatelessComponent<SetAllowancesOnboardingStepProps> = ({
ethAllowanceToggle,
zrxAllowanceToggle,
+ doesUserHaveAllowancesForWethAndZrx,
}) => (
<div className="flex items-center flex-column">
<Text>Unlock your tokens for trading. You only need to do this once for each token.</Text>
@@ -23,5 +25,6 @@ export const SetAllowancesOnboardingStep: React.StatelessComponent<SetAllowances
<Container marginTop="10px">{zrxAllowanceToggle}</Container>
</div>
</Container>
+ {doesUserHaveAllowancesForWethAndZrx && <Text>Perfect! Both your ZRX and WETH tokens are unlocked.</Text>}
</div>
);
diff --git a/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx b/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx
index 0039aa545..4ed7137d4 100644
--- a/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx
+++ b/packages/website/ts/components/onboarding/unlock_wallet_onboarding_step.tsx
@@ -10,7 +10,7 @@ export const UnlockWalletOnboardingStep: React.StatelessComponent<UnlockWalletOn
<Container marginTop="15px" marginBottom="15px">
<img src="/images/metamask_icon.png" height="50px" width="50px" />
</Container>
- <Text center={true}>Unlock your metamask extension to get started.</Text>
+ <Text center={true}>Unlock your MetaMask extension to get started.</Text>
</div>
</div>
);
diff --git a/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx b/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx
index b21b39341..4d336c80f 100644
--- a/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx
+++ b/packages/website/ts/components/onboarding/wrap_eth_onboarding_step.tsx
@@ -4,70 +4,78 @@ import { Container } from 'ts/components/ui/container';
import { IconButton } from 'ts/components/ui/icon_button';
import { Text } from 'ts/components/ui/text';
-export interface WrapEthOnboardingStepProps {
- formattedEthBalanceIfExists?: string;
+export interface WrapEthOnboardingStep1Props {}
+
+export const WrapEthOnboardingStep1: React.StatelessComponent<WrapEthOnboardingStep1Props> = () => (
+ <div className="flex items-center flex-column">
+ <Text>
+ You need to convert some of your ETH into tradeable <b>Wrapped ETH (WETH)</b>.
+ </Text>
+ <Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-center">
+ <div className="flex flex-column items-center">
+ <Text fontWeight={700}> 1 ETH </Text>
+ <img src="/images/eth_dollar.svg" height="75px" width="75x" />
+ </div>
+ <Container marginRight="25px" marginLeft="25px" position="relative" top="20px">
+ <Text fontSize="36px">=</Text>
+ </Container>
+ <div className="flex flex-column items-center">
+ <Text fontWeight={700}> 1 WETH </Text>
+ <img src="/images/eth_token_erc20.svg" height="75px" width="75px" />
+ </div>
+ </Container>
+ <Text>
+ Think of it like the coin version of a paper note. It has the same value, but some machines only take coins.
+ </Text>
+ </div>
+);
+
+export interface WrapEthOnboardingStep2Props {}
+
+export const WrapEthOnboardingStep2: React.StatelessComponent<WrapEthOnboardingStep2Props> = () => (
+ <div className="flex items-center flex-column">
+ <Text>Wrapping your ETH is a reversable transaction, so don't worry about losing your ETH.</Text>
+ <Text>
+ Click
+ <Container display="inline-block" marginLeft="10px" marginRight="10px">
+ <IconButton
+ iconName="zmdi-long-arrow-down"
+ color={colors.mediumBlue}
+ labelText="wrap"
+ display="inline-flex"
+ />
+ </Container>
+ to wrap your ETH.
+ </Text>
+ </div>
+);
+
+export interface WrapEthOnboardingStep3Props {
+ formattedWethBalanceIfExists?: string;
}
-export const WrapEthOnboardingStep: React.StatelessComponent<WrapEthOnboardingStepProps> = ({
- formattedEthBalanceIfExists,
-}) => {
- if (formattedEthBalanceIfExists) {
- return (
- <div className="flex items-center flex-column">
- <Text>Congrats you now have {formattedEthBalanceIfExists} in your wallet.</Text>
- <Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-center">
- <div className="flex flex-column items-center">
- <Text fontWeight={700}> 1 ETH </Text>
- <img src="/images/eth_dollar.svg" height="75px" width="75x" />
- </div>
- <Container marginRight="25px" marginLeft="25px" position="relative" top="20px">
- <Text fontSize="25px">
- <i className="zmdi zmdi-long-arrow-right" />
- </Text>
- </Container>
- <div className="flex flex-column items-center">
- <Text fontWeight={700}> 1 WETH </Text>
- <img src="/images/eth_token_erc20.svg" height="75px" width="75px" />
- </div>
- </Container>
+export const WrapEthOnboardingStep3: React.StatelessComponent<WrapEthOnboardingStep3Props> = ({
+ formattedWethBalanceIfExists,
+}) => (
+ <div className="flex items-center flex-column">
+ <Text>
+ You have <b>{formattedWethBalanceIfExists || '0 WETH'}</b> in your wallet.
+ {formattedWethBalanceIfExists && ' Great!'}
+ </Text>
+ <Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-center">
+ <div className="flex flex-column items-center">
+ <Text fontWeight={700}> 1 ETH </Text>
+ <img src="/images/eth_dollar.svg" height="75px" width="75x" />
</div>
- );
- } else {
- return (
- <div className="flex items-center flex-column">
- <Text>
- You need to convert some of your ETH into tradeable <b>Wrapped ETH (WETH)</b>.
- </Text>
- <Container width="100%" marginTop="25px" marginBottom="15px" className="flex justify-center">
- <div className="flex flex-column items-center">
- <Text fontWeight={700}> 1 ETH </Text>
- <img src="/images/eth_dollar.svg" height="75px" width="75x" />
- </div>
- <Container marginRight="25px" marginLeft="25px" position="relative" top="20px">
- <Text fontSize="36px">=</Text>
- </Container>
- <div className="flex flex-column items-center">
- <Text fontWeight={700}> 1 WETH </Text>
- <img src="/images/eth_token_erc20.svg" height="75px" width="75px" />
- </div>
- </Container>
- <Text>
- Think of it like the coin version of a paper note. It has the same value, but some machines only
- take coins.
- </Text>
- <Text>
- Click
- <Container display="inline-block" marginLeft="10px" marginRight="10px">
- <IconButton
- iconName="zmdi-long-arrow-down"
- color={colors.mediumBlue}
- labelText="wrap"
- display="inline-flex"
- />
- </Container>
- to wrap your ETH.
+ <Container marginRight="25px" marginLeft="25px" position="relative" top="20px">
+ <Text fontSize="25px">
+ <i className="zmdi zmdi-long-arrow-right" />
</Text>
+ </Container>
+ <div className="flex flex-column items-center">
+ <Text fontWeight={700}> 1 WETH </Text>
+ <img src="/images/eth_token_erc20.svg" height="75px" width="75px" />
</div>
- );
- }
-};
+ </Container>
+ </div>
+);
diff --git a/packages/website/ts/components/portal/drawer_menu.tsx b/packages/website/ts/components/portal/drawer_menu.tsx
index 205a60afc..a6707e86c 100644
--- a/packages/website/ts/components/portal/drawer_menu.tsx
+++ b/packages/website/ts/components/portal/drawer_menu.tsx
@@ -44,12 +44,13 @@ export const DrawerMenu = (props: DrawerMenuProps) => {
iconName: 'zmdi-portable-wifi',
};
const menuItemEntries = _.concat(relayerItemEntry, defaultMenuItemEntries);
- const displayMessage = utils.getReadableAccountState(
+ const accountState = utils.getAccountState(
props.blockchainIsLoaded && !_.isUndefined(props.blockchain),
props.providerType,
props.injectedProviderName,
props.userAddress,
);
+ const displayMessage = utils.getReadableAccountState(accountState, props.userAddress);
return (
<div style={styles.root}>
<Header userAddress={props.userAddress} displayMessage={displayMessage} />
diff --git a/packages/website/ts/components/portal/portal.tsx b/packages/website/ts/components/portal/portal.tsx
index 438c7b52f..8c3b5cfd7 100644
--- a/packages/website/ts/components/portal/portal.tsx
+++ b/packages/website/ts/components/portal/portal.tsx
@@ -1,10 +1,9 @@
import { colors, constants as sharedConstants } from '@0xproject/react-shared';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
-import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
import * as React from 'react';
import * as DocumentTitle from 'react-document-title';
-import { Route, RouteComponentProps, Switch } from 'react-router-dom';
+import { Link, Route, RouteComponentProps, Switch } from 'react-router-dom';
import { Blockchain } from 'ts/blockchain';
import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog';
@@ -24,7 +23,7 @@ import { TopBar, TopBarDisplayType } from 'ts/components/top_bar/top_bar';
import { TradeHistory } from 'ts/components/trade_history/trade_history';
import { Container } from 'ts/components/ui/container';
import { FlashMessage } from 'ts/components/ui/flash_message';
-import { Island } from 'ts/components/ui/island';
+import { Image } from 'ts/components/ui/image';
import { Text } from 'ts/components/ui/text';
import { Wallet } from 'ts/components/wallet/wallet';
import { GenerateOrderForm } from 'ts/containers/generate_order_form';
@@ -319,15 +318,14 @@ export class Portal extends React.Component<PortalProps, PortalState> {
);
}
private _renderWallet(): React.ReactNode {
- const startOnboarding = this._renderStartOnboarding();
const isMobile = utils.isMobile(this.props.screenWidth);
// We need room to scroll down for mobile onboarding
const marginBottom = isMobile ? '200px' : '15px';
return (
<div>
- <Container>
- {isMobile && <Container marginBottom="15px">{startOnboarding}</Container>}
- <Container marginBottom={marginBottom}>
+ <Container className="flex flex-column items-center">
+ {isMobile && <Container marginBottom="20px">{this._renderStartOnboarding()}</Container>}
+ <Container marginBottom={marginBottom} width="100%">
<Wallet
style={
!isMobile && this.props.isPortalOnboardingShowing
@@ -355,7 +353,7 @@ export class Portal extends React.Component<PortalProps, PortalState> {
refetchTokenStateAsync={this._refetchTokenStateAsync.bind(this)}
/>
</Container>
- {!isMobile && <Container marginTop="15px">{startOnboarding}</Container>}
+ {!isMobile && <Container marginTop="8px">{this._renderStartOnboarding()}</Container>}
</Container>
<PortalOnboardingFlow
blockchain={this._blockchain}
@@ -366,26 +364,24 @@ export class Portal extends React.Component<PortalProps, PortalState> {
);
}
private _renderStartOnboarding(): React.ReactNode {
- return (
- <Island>
- <Container
- marginTop="30px"
- marginBottom="30px"
- marginLeft="30px"
- marginRight="30px"
- className="flex justify-around items-center"
- >
- <ActionAccountBalanceWallet style={{ width: '30px', height: '30px' }} color={colors.orange} />
- <Text
- fontColor={colors.grey}
- fontSize="16px"
- center={true}
- onClick={this._startOnboarding.bind(this)}
- >
- Learn how to set up your account
- </Text>
+ const isMobile = utils.isMobile(this.props.screenWidth);
+ const shouldStartOnboarding = !isMobile || this.props.location.pathname === `${WebsitePaths.Portal}/account`;
+ const startOnboarding = (
+ <Container className="flex items-center center">
+ <Text fontColor={colors.mediumBlue} fontSize="16px" onClick={this._startOnboarding.bind(this)}>
+ Set up your account to start trading
+ </Text>
+ <Container marginLeft="8px" paddingTop="3px">
+ <Image src="/images/setup_account_icon.svg" height="20px" width="20x" />
</Container>
- </Island>
+ </Container>
+ );
+ return !shouldStartOnboarding ? (
+ <Link to={{ pathname: `${WebsitePaths.Portal}/account` }} style={{ textDecoration: 'none' }}>
+ {startOnboarding}
+ </Link>
+ ) : (
+ startOnboarding
);
}
@@ -393,10 +389,6 @@ export class Portal extends React.Component<PortalProps, PortalState> {
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);
- // On mobile, make sure the wallet is completely visible.
- if (this.props.screenWidth === ScreenWidths.Sm) {
- document.querySelector('.wallet').scrollIntoView();
- }
}
private _renderWalletSection(): React.ReactNode {
return <Section header={<TextHeader labelText="Your Account" />} body={this._renderWallet()} />;
@@ -535,11 +527,15 @@ export class Portal extends React.Component<PortalProps, PortalState> {
);
}
private _renderRelayerIndexSection(): React.ReactNode {
+ return <Section header={<TextHeader labelText="0x Relayers" />} body={this._renderRelayerIndex()} />;
+ }
+ private _renderRelayerIndex(): React.ReactNode {
+ const isMobile = utils.isMobile(this.props.screenWidth);
return (
- <Section
- header={<TextHeader labelText="0x Relayers" />}
- body={<RelayerIndex networkId={this.props.networkId} screenWidth={this.props.screenWidth} />}
- />
+ <Container className="flex flex-column items-center">
+ {isMobile && <Container marginBottom="20px">{this._renderStartOnboarding()}</Container>}
+ <RelayerIndex networkId={this.props.networkId} screenWidth={this.props.screenWidth} />
+ </Container>
);
}
private _renderNotFoundMessage(): React.ReactNode {
diff --git a/packages/website/ts/components/portal/section.tsx b/packages/website/ts/components/portal/section.tsx
index 455ed07c9..b6c9fd098 100644
--- a/packages/website/ts/components/portal/section.tsx
+++ b/packages/website/ts/components/portal/section.tsx
@@ -6,9 +6,9 @@ export interface SectionProps {
}
export const Section = (props: SectionProps) => {
return (
- <div className="flex flex-column" style={{ height: '100%' }}>
+ <div className="flex flex-column">
{props.header}
- <div className="flex-auto">{props.body}</div>
+ {props.body}
</div>
);
};
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 b26bf512b..02bc1b014 100644
--- a/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_grid_tile.tsx
@@ -9,6 +9,7 @@ import { Container } from 'ts/components/ui/container';
import { Image } from 'ts/components/ui/image';
import { Island } from 'ts/components/ui/island';
import { colors } from 'ts/style/colors';
+import { media } from 'ts/style/media';
import { styled } from 'ts/style/theme';
import { WebsiteBackendRelayerInfo } from 'ts/types';
import { utils } from 'ts/utils/utils';
@@ -55,7 +56,7 @@ const styles: Styles = {
};
const FALLBACK_IMG_SRC = '/images/relayer_fallback.png';
-const FALLBACK_PRIMARY_COLOR = colors.grey200;
+const FALLBACK_PRIMARY_COLOR = colors.grey300;
const NO_CONTENT_MESSAGE = '--';
const RELAYER_ICON_HEIGHT = '110px';
@@ -111,6 +112,9 @@ const GridTile = styled(PlainGridTile)`
&:hover {
transform: translate(0px, -3px);
}
+ ${media.small`
+ transform: none;
+ `};
`;
interface SectionProps {
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 f544fc924..c48b672e9 100644
--- a/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx
+++ b/packages/website/ts/components/relayer_index/relayer_top_tokens.tsx
@@ -2,44 +2,30 @@ 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 { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
import { WebsiteBackendTokenInfo } from 'ts/types';
+import { analytics } from 'ts/utils/analytics';
+import { utils } from 'ts/utils/utils';
export interface TopTokensProps {
tokens: WebsiteBackendTokenInfo[];
networkId: number;
}
-const styles: Styles = {
- tokenLabel: {
- textDecoration: 'none',
- color: colors.mediumBlue,
- fontSize: 14,
- },
- followingTokenLabel: {
- paddingLeft: 16,
- },
-};
-
export const TopTokens: React.StatelessComponent<TopTokensProps> = (props: TopTokensProps) => {
return (
<div className="flex">
- {_.map(props.tokens, (tokenInfo: WebsiteBackendTokenInfo, index: number) => {
- const firstItemStyle = { ...styles.tokenLabel, ...styles.followingTokenLabel };
- const style = index !== 0 ? firstItemStyle : styles.tokenLabel;
+ {_.map(props.tokens, (tokenInfo: WebsiteBackendTokenInfo) => {
return (
- <TokenLink
- key={tokenInfo.address}
- tokenInfo={tokenInfo}
- style={style}
- networkId={props.networkId}
- />
+ <Container key={tokenInfo.address} marginRight="16px">
+ <TokenLink tokenInfo={tokenInfo} networkId={props.networkId} />
+ </Container>
);
})}
</div>
@@ -48,12 +34,9 @@ export const TopTokens: React.StatelessComponent<TopTokensProps> = (props: TopTo
interface TokenLinkProps {
tokenInfo: WebsiteBackendTokenInfo;
- style: React.CSSProperties;
networkId: number;
}
-interface TokenLinkState {
- isHovering: boolean;
-}
+interface TokenLinkState {}
class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> {
constructor(props: TokenLinkProps) {
@@ -63,37 +46,21 @@ class TokenLink extends React.Component<TokenLinkProps, TokenLinkState> {
};
}
public render(): React.ReactNode {
- const style = {
- ...this.props.style,
- 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 onClick = (event: React.MouseEvent<HTMLElement>) => {
event.stopPropagation();
analytics.logEvent('Portal', 'Token Click', eventLabel);
+ const tokenLink = this._tokenLinkFromToken(this.props.tokenInfo, this.props.networkId);
+ utils.openUrl(tokenLink);
};
return (
- <a
- href={tokenLinkFromToken(this.props.tokenInfo, this.props.networkId)}
- target="_blank"
- style={style}
- onMouseEnter={this._onToggleHover.bind(this, true)}
- onMouseLeave={this._onToggleHover.bind(this, false)}
- onClick={onClick}
- >
+ <Text fontSize="14px" fontColor={colors.mediumBlue} onClick={onClick}>
{this.props.tokenInfo.symbol}
- </a>
+ </Text>
);
}
- private _onToggleHover(isHovering: boolean): void {
- this.setState({
- isHovering,
- });
+ private _tokenLinkFromToken(tokenInfo: WebsiteBackendTokenInfo, networkId: number): string {
+ return sharedUtils.getEtherScanLinkIfExists(tokenInfo.address, networkId, EtherscanLinkSuffixes.Address);
}
}
-
-function tokenLinkFromToken(tokenInfo: WebsiteBackendTokenInfo, networkId: number): string {
- 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 496e5cae0..806eaeea5 100644
--- a/packages/website/ts/components/top_bar/provider_display.tsx
+++ b/packages/website/ts/components/top_bar/provider_display.tsx
@@ -1,21 +1,26 @@
import { Styles } from '@0xproject/react-shared';
import * as _ from 'lodash';
import CircularProgress from 'material-ui/CircularProgress';
-import RaisedButton from 'material-ui/RaisedButton';
+import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
import * as React from 'react';
import { Blockchain } from 'ts/blockchain';
-import { ProviderPicker } from 'ts/components/top_bar/provider_picker';
+import { AccountConnection } from 'ts/components/ui/account_connection';
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 { Island } from 'ts/components/ui/island';
+import {
+ CopyAddressSimpleMenuItem,
+ DifferentWalletSimpleMenuItem,
+ GoToAccountManagementSimpleMenuItem,
+ SimpleMenu,
+} from 'ts/components/ui/simple_menu';
import { Text } from 'ts/components/ui/text';
import { Dispatcher } from 'ts/redux/dispatcher';
import { colors } from 'ts/style/colors';
-import { ProviderType } from 'ts/types';
-import { constants } from 'ts/utils/constants';
+import { AccountState, ProviderType } from 'ts/types';
import { utils } from 'ts/utils/utils';
const ROOT_HEIGHT = 24;
@@ -42,133 +47,108 @@ const styles: Styles = {
export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> {
public render(): React.ReactNode {
- const isExternallyInjectedProvider = utils.isExternallyInjected(
- this.props.providerType,
- this.props.injectedProviderName,
- );
- 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
- ? this.props.injectedProviderName
- : 'Connect a wallet';
- const providerTitle =
- this.props.providerType === ProviderType.Injected ? injectedProviderName : 'Ledger Nano S';
- const isProviderMetamask = providerTitle === constants.PROVIDER_NAME_METAMASK;
- const hoverActiveNode = (
- <Island className="flex items-center p1" style={styles.root}>
- <div>
- {this._isBlockchainReady() ? (
- <Identicon address={this.props.userAddress} diameter={ROOT_HEIGHT} />
- ) : (
- <CircularProgress size={ROOT_HEIGHT} thickness={2} />
- )}
- </div>
+ const activeNode = (
+ <Island className="flex items-center py1 px2" style={styles.root}>
+ {this._renderIcon()}
<Container marginLeft="12px" marginRight="12px">
- <Text fontSize="14px" fontColor={colors.darkGrey}>
- {displayMessage}
- </Text>
+ {this._renderDisplayMessage()}
</Container>
- {isProviderMetamask && (
- <Image src="/images/metamask_icon.png" height={ROOT_HEIGHT} width={ROOT_HEIGHT} />
- )}
+ {this._renderInjectedProvider()}
</Island>
);
- const hasLedgerProvider = this.props.providerType === ProviderType.Ledger;
- const horizontalPosition = isExternallyInjectedProvider || hasLedgerProvider ? 'left' : 'middle';
return (
<div style={{ width: 'fit-content', height: 48, float: 'right' }}>
<DropDown
- hoverActiveNode={hoverActiveNode}
- popoverContent={this.renderPopoverContent(isExternallyInjectedProvider, hasLedgerProvider)}
- anchorOrigin={{ horizontal: horizontalPosition, vertical: 'bottom' }}
- targetOrigin={{ horizontal: horizontalPosition, vertical: 'top' }}
+ activeNode={activeNode}
+ popoverContent={this._renderPopoverContent()}
+ anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }}
+ targetOrigin={{ horizontal: 'middle', vertical: 'top' }}
zDepth={1}
/>
</div>
);
}
- public renderPopoverContent(hasInjectedProvider: boolean, hasLedgerProvider: boolean): React.ReactNode {
- if (!this._isBlockchainReady()) {
- return null;
- } else if (hasInjectedProvider || hasLedgerProvider) {
- return (
- <ProviderPicker
- dispatcher={this.props.dispatcher}
- networkId={this.props.networkId}
- injectedProviderName={this.props.injectedProviderName}
- providerType={this.props.providerType}
- onToggleLedgerDialog={this.props.onToggleLedgerDialog}
- blockchain={this.props.blockchain}
- />
- );
- } else {
- // Nothing to connect to, show install/info popover
- return (
- <div className="px2" style={{ maxWidth: 420 }}>
- <div className="center h4 py2" style={{ color: colors.grey700 }}>
- Choose a wallet:
- </div>
- <div className="flex pb3">
- <div className="center px2">
- <div style={{ color: colors.darkGrey }}>Install a browser wallet</div>
- <div className="py2">
- <img src="/images/metamask_or_parity.png" width="135" />
- </div>
- <div>
- Use{' '}
- <a
- href={constants.URL_METAMASK_CHROME_STORE}
- target="_blank"
- style={{ color: colors.lightBlueA700 }}
- >
- Metamask
- </a>{' '}
- or{' '}
- <a
- href={constants.URL_PARITY_CHROME_STORE}
- target="_blank"
- style={{ color: colors.lightBlueA700 }}
- >
- Parity Signer
- </a>
- </div>
- </div>
- <div>
- <div
- className="pl1 ml1"
- style={{ borderLeft: `1px solid ${colors.grey300}`, height: 65 }}
- />
- <div className="py1">or</div>
- <div
- className="pl1 ml1"
- style={{ borderLeft: `1px solid ${colors.grey300}`, height: 68 }}
- />
- </div>
- <div className="px2 center">
- <div style={{ color: colors.darkGrey }}>Connect to a ledger hardware wallet</div>
- <div style={{ paddingTop: 21, paddingBottom: 29 }}>
- <img src="/images/ledger_icon.png" style={{ width: 80 }} />
- </div>
- <div>
- <RaisedButton
- style={{ width: '100%' }}
- label="Use Ledger"
- onClick={this.props.onToggleLedgerDialog}
- />
- </div>
- </div>
- </div>
- </div>
- );
+ private _renderPopoverContent(): React.ReactNode {
+ const accountState = this._getAccountState();
+ switch (accountState) {
+ case AccountState.Ready:
+ return (
+ <SimpleMenu>
+ <CopyAddressSimpleMenuItem userAddress={this.props.userAddress} />
+ <DifferentWalletSimpleMenuItem onClick={this.props.onToggleLedgerDialog} />
+ <GoToAccountManagementSimpleMenuItem />
+ </SimpleMenu>
+ );
+ case AccountState.Disconnected:
+ case AccountState.Locked:
+ case AccountState.Loading:
+ default:
+ return null;
+ }
+ }
+ private _renderIcon(): React.ReactNode {
+ const accountState = this._getAccountState();
+ switch (accountState) {
+ case AccountState.Ready:
+ return <Identicon address={this.props.userAddress} diameter={ROOT_HEIGHT} />;
+ case AccountState.Loading:
+ return <CircularProgress size={ROOT_HEIGHT} thickness={2} />;
+ case AccountState.Locked:
+ return <Image src="/images/lock_icon.svg" height="20px" width="20px" />;
+ case AccountState.Disconnected:
+ return <ActionAccountBalanceWallet color={colors.mediumBlue} />;
+ default:
+ return null;
+ }
+ }
+ private _renderDisplayMessage(): React.ReactNode {
+ const accountState = this._getAccountState();
+ const displayMessage = utils.getReadableAccountState(accountState, this.props.userAddress);
+ const fontColor = this._getDisplayMessageFontColor();
+ return (
+ <Text fontSize="16px" fontColor={fontColor} fontWeight={500}>
+ {displayMessage}
+ </Text>
+ );
+ }
+ private _getDisplayMessageFontColor(): string {
+ const accountState = this._getAccountState();
+ switch (accountState) {
+ case AccountState.Loading:
+ return colors.darkGrey;
+ case AccountState.Ready:
+ case AccountState.Locked:
+ case AccountState.Disconnected:
+ default:
+ return colors.black;
+ }
+ }
+ private _renderInjectedProvider(): React.ReactNode {
+ const accountState = this._getAccountState();
+ switch (accountState) {
+ case AccountState.Ready:
+ case AccountState.Locked:
+ return (
+ <AccountConnection
+ accountState={accountState}
+ injectedProviderName={this.props.injectedProviderName}
+ />
+ );
+ case AccountState.Disconnected:
+ case AccountState.Loading:
+ default:
+ return null;
}
}
private _isBlockchainReady(): boolean {
return this.props.blockchainIsLoaded && !_.isUndefined(this.props.blockchain);
}
+ private _getAccountState(): AccountState {
+ return utils.getAccountState(
+ this._isBlockchainReady(),
+ this.props.providerType,
+ this.props.injectedProviderName,
+ this.props.userAddress,
+ );
+ }
}
diff --git a/packages/website/ts/components/top_bar/provider_picker.tsx b/packages/website/ts/components/top_bar/provider_picker.tsx
deleted file mode 100644
index 7937f2e9d..000000000
--- a/packages/website/ts/components/top_bar/provider_picker.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { colors, constants as sharedConstants } from '@0xproject/react-shared';
-import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
-import * as React from 'react';
-import { Blockchain } from 'ts/blockchain';
-import { Dispatcher } from 'ts/redux/dispatcher';
-import { ProviderType } from 'ts/types';
-
-interface ProviderPickerProps {
- networkId: number;
- injectedProviderName: string;
- providerType: ProviderType;
- onToggleLedgerDialog: () => void;
- dispatcher: Dispatcher;
- blockchain: Blockchain;
-}
-
-interface ProviderPickerState {}
-
-export class ProviderPicker extends React.Component<ProviderPickerProps, ProviderPickerState> {
- public render(): React.ReactNode {
- const isLedgerSelected = this.props.providerType === ProviderType.Ledger;
- const menuStyle = {
- padding: 10,
- paddingTop: 15,
- paddingBottom: 15,
- };
- // Show dropdown with two options
- return (
- <div style={{ width: 225, overflow: 'hidden' }}>
- <RadioButtonGroup name="provider" defaultSelected={this.props.providerType}>
- <RadioButton
- onClick={this._onProviderRadioChanged.bind(this, ProviderType.Injected)}
- style={{ ...menuStyle, backgroundColor: !isLedgerSelected && colors.grey50 }}
- value={ProviderType.Injected}
- label={this._renderLabel(this.props.injectedProviderName, !isLedgerSelected)}
- />
- <RadioButton
- onClick={this._onProviderRadioChanged.bind(this, ProviderType.Ledger)}
- style={{ ...menuStyle, backgroundColor: isLedgerSelected && colors.grey50 }}
- value={ProviderType.Ledger}
- label={this._renderLabel('Ledger Nano S', isLedgerSelected)}
- />
- </RadioButtonGroup>
- </div>
- );
- }
- private _renderLabel(title: string, shouldShowNetwork: boolean): React.ReactNode {
- const label = (
- <div className="flex">
- <div style={{ fontSize: 14 }}>{title}</div>
- {shouldShowNetwork && this._renderNetwork()}
- </div>
- );
- return label;
- }
- private _renderNetwork(): React.ReactNode {
- const networkName = sharedConstants.NETWORK_NAME_BY_ID[this.props.networkId];
- return (
- <div className="flex" style={{ marginTop: 1 }}>
- <div className="relative" style={{ width: 14, paddingLeft: 14 }}>
- <img
- src={`/images/network_icons/${networkName.toLowerCase()}.png`}
- className="absolute"
- style={{ top: 6, width: 10 }}
- />
- </div>
- <div style={{ color: colors.lightGrey, fontSize: 11 }}>{networkName}</div>
- </div>
- );
- }
- private _onProviderRadioChanged(value: string): void {
- if (value === ProviderType.Ledger) {
- this.props.onToggleLedgerDialog();
- } else {
- // tslint:disable-next-line:no-floating-promises
- this.props.blockchain.updateProviderToInjectedAsync();
- }
- }
-}
diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx
index fac6c131f..778536663 100644
--- a/packages/website/ts/components/top_bar/top_bar.tsx
+++ b/packages/website/ts/components/top_bar/top_bar.tsx
@@ -199,7 +199,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
cursor: 'pointer',
paddingTop: 16,
};
- const hoverActiveNode = (
+ const activeNode = (
<div className="flex relative" style={{ color: menuIconStyle.color }}>
<div style={{ paddingRight: 10 }}>{this.props.translate.get(Key.Developers, Deco.Cap)}</div>
<div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}>
@@ -224,7 +224,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
<div className={menuClasses}>
<div className="flex justify-between">
<DropDown
- hoverActiveNode={hoverActiveNode}
+ activeNode={activeNode}
popoverContent={popoverContent}
anchorOrigin={{ horizontal: 'middle', vertical: 'bottom' }}
targetOrigin={{ horizontal: 'middle', vertical: 'top' }}
diff --git a/packages/website/ts/components/ui/account_connection.tsx b/packages/website/ts/components/ui/account_connection.tsx
new file mode 100644
index 000000000..6d0b90922
--- /dev/null
+++ b/packages/website/ts/components/ui/account_connection.tsx
@@ -0,0 +1,40 @@
+import * as React from 'react';
+
+import { Circle } from 'ts/components/ui/circle';
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+import { colors } from 'ts/style/colors';
+import { AccountState } from 'ts/types';
+
+export interface AccountConnectionProps {
+ accountState: AccountState;
+ injectedProviderName: string;
+}
+
+export const AccountConnection: React.StatelessComponent<AccountConnectionProps> = ({
+ accountState,
+ injectedProviderName,
+}) => {
+ return (
+ <Container className="flex items-center">
+ <Circle diameter={6} fillColor={getInjectedProviderColor(accountState)} />
+ <Container marginLeft="6px">
+ <Text fontSize="12px" lineHeight="14px" fontColor={colors.darkGrey}>
+ {injectedProviderName}
+ </Text>
+ </Container>
+ </Container>
+ );
+};
+
+const getInjectedProviderColor = (accountState: AccountState) => {
+ switch (accountState) {
+ case AccountState.Ready:
+ return colors.limeGreen;
+ case AccountState.Locked:
+ case AccountState.Loading:
+ case AccountState.Disconnected:
+ default:
+ return colors.red;
+ }
+};
diff --git a/packages/website/ts/components/ui/animation.tsx b/packages/website/ts/components/ui/animation.tsx
index 136f3d005..943e3bf28 100644
--- a/packages/website/ts/components/ui/animation.tsx
+++ b/packages/website/ts/components/ui/animation.tsx
@@ -14,21 +14,29 @@ const appearFromBottomFrames = keyframes`
position: fixed;
bottom: -500px;
left: 0px;
+ right: 0px;
}
to {
position: fixed;
bottom: 0px;
left: 0px;
+ right: 0px;
}
`;
+const stylesForAnimation: { [K in AnimationType]: string } = {
+ // Needed for safari
+ easeUpFromBottom: `position: fixed`,
+};
+
const animations: { [K in AnimationType]: string } = {
easeUpFromBottom: `${appearFromBottomFrames} 1s ease 0s 1 forwards`,
};
export const Animation = styled(PlainAnimation)`
animation: ${props => animations[props.type]};
+ ${props => stylesForAnimation[props.type]};
`;
Animation.displayName = 'Animation';
diff --git a/packages/website/ts/components/ui/button.tsx b/packages/website/ts/components/ui/button.tsx
index 02fa47480..1489a74a6 100644
--- a/packages/website/ts/components/ui/button.tsx
+++ b/packages/website/ts/components/ui/button.tsx
@@ -37,7 +37,7 @@ export const Button = styled(PlainButton)`
background-color: ${props => props.backgroundColor};
border: ${props => (props.borderColor ? `1px solid ${props.borderColor}` : 'none')};
&:hover {
- background-color: ${props => (!props.isDisabled ? darken(0.1, props.backgroundColor) : '')};
+ background-color: ${props => (!props.isDisabled ? darken(0.1, props.backgroundColor) : '')} !important;
}
&:active {
background-color: ${props => (!props.isDisabled ? darken(0.2, props.backgroundColor) : '')};
diff --git a/packages/website/ts/components/ui/circle.tsx b/packages/website/ts/components/ui/circle.tsx
new file mode 100644
index 000000000..75103d066
--- /dev/null
+++ b/packages/website/ts/components/ui/circle.tsx
@@ -0,0 +1,16 @@
+import * as React from 'react';
+
+export interface CircleProps {
+ className?: string;
+ diameter: number;
+ fillColor: string;
+}
+
+export const Circle: React.StatelessComponent<CircleProps> = ({ className, diameter, fillColor }) => {
+ const radius = diameter / 2;
+ return (
+ <svg className={className} height={diameter} width={diameter}>
+ <circle cx={radius} cy={radius} r={radius} fill={fillColor} />
+ </svg>
+ );
+};
diff --git a/packages/website/ts/components/ui/container.tsx b/packages/website/ts/components/ui/container.tsx
index a747ef01f..edbf8814b 100644
--- a/packages/website/ts/components/ui/container.tsx
+++ b/packages/website/ts/components/ui/container.tsx
@@ -14,7 +14,10 @@ export interface ContainerProps {
backgroundColor?: string;
borderRadius?: StringOrNum;
maxWidth?: StringOrNum;
+ maxHeight?: StringOrNum;
width?: StringOrNum;
+ height?: StringOrNum;
+ minWidth?: StringOrNum;
minHeight?: StringOrNum;
isHidden?: boolean;
className?: string;
diff --git a/packages/website/ts/components/ui/drop_down.tsx b/packages/website/ts/components/ui/drop_down.tsx
index 22cb942f8..4d5caef08 100644
--- a/packages/website/ts/components/ui/drop_down.tsx
+++ b/packages/website/ts/components/ui/drop_down.tsx
@@ -1,4 +1,4 @@
-import Popover, { PopoverAnimationVertical } from 'material-ui/Popover';
+import Popover from 'material-ui/Popover';
import * as React from 'react';
import { MaterialUIPosition } from 'ts/types';
@@ -7,13 +7,20 @@ const DEFAULT_STYLE = {
fontSize: 14,
};
-interface DropDownProps {
- hoverActiveNode: React.ReactNode;
+export enum DropdownMouseEvent {
+ Hover = 'hover',
+ Click = 'click',
+}
+
+export interface DropDownProps {
+ activeNode: React.ReactNode;
popoverContent: React.ReactNode;
anchorOrigin: MaterialUIPosition;
targetOrigin: MaterialUIPosition;
style?: React.CSSProperties;
zDepth?: number;
+ activateEvent?: DropdownMouseEvent;
+ closeEvent?: DropdownMouseEvent;
}
interface DropDownState {
@@ -25,6 +32,8 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> {
public static defaultProps: Partial<DropDownProps> = {
style: DEFAULT_STYLE,
zDepth: 1,
+ activateEvent: DropdownMouseEvent.Hover,
+ closeEvent: DropdownMouseEvent.Hover,
};
private _isHovering: boolean;
private _popoverCloseCheckIntervalId: number;
@@ -58,46 +67,61 @@ export class DropDown extends React.Component<DropDownProps, DropDownState> {
onMouseEnter={this._onHover.bind(this)}
onMouseLeave={this._onHoverOff.bind(this)}
>
- {this.props.hoverActiveNode}
+ <div onClick={this._onActiveNodeClick.bind(this)}>{this.props.activeNode}</div>
<Popover
open={this.state.isDropDownOpen}
anchorEl={this.state.anchorEl}
anchorOrigin={this.props.anchorOrigin}
targetOrigin={this.props.targetOrigin}
onRequestClose={this._closePopover.bind(this)}
- useLayerForClickAway={false}
- animation={PopoverAnimationVertical}
+ useLayerForClickAway={this.props.closeEvent === DropdownMouseEvent.Click}
+ animated={false}
zDepth={this.props.zDepth}
>
- <div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}>
+ <div
+ onMouseEnter={this._onHover.bind(this)}
+ onMouseLeave={this._onHoverOff.bind(this)}
+ onClick={this._closePopover.bind(this)}
+ >
{this.props.popoverContent}
</div>
</Popover>
</div>
);
}
+ private _onActiveNodeClick(event: React.FormEvent<HTMLInputElement>): void {
+ if (this.props.activateEvent === DropdownMouseEvent.Click) {
+ this.setState({
+ isDropDownOpen: true,
+ anchorEl: event.currentTarget,
+ });
+ }
+ }
private _onHover(event: React.FormEvent<HTMLInputElement>): void {
this._isHovering = true;
- this._checkIfShouldOpenPopover(event);
+ if (this.props.activateEvent === DropdownMouseEvent.Hover) {
+ this._checkIfShouldOpenPopover(event);
+ }
+ }
+ private _onHoverOff(): void {
+ this._isHovering = false;
}
private _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>): void {
if (this.state.isDropDownOpen) {
return; // noop
}
-
this.setState({
isDropDownOpen: true,
anchorEl: event.currentTarget,
});
}
- private _onHoverOff(): void {
- this._isHovering = false;
- }
private _checkIfShouldClosePopover(): void {
- if (!this.state.isDropDownOpen || this._isHovering) {
+ if (!this.state.isDropDownOpen) {
return; // noop
}
- this._closePopover();
+ if (this.props.closeEvent === DropdownMouseEvent.Hover && !this._isHovering) {
+ this._closePopover();
+ }
}
private _closePopover(): void {
this.setState({
diff --git a/packages/website/ts/components/ui/identicon.tsx b/packages/website/ts/components/ui/identicon.tsx
index cc1655962..b5b374973 100644
--- a/packages/website/ts/components/ui/identicon.tsx
+++ b/packages/website/ts/components/ui/identicon.tsx
@@ -2,6 +2,7 @@ import blockies = require('blockies');
import * as _ from 'lodash';
import * as React from 'react';
+import { Circle } from 'ts/components/ui/circle';
import { Image } from 'ts/components/ui/image';
import { colors } from 'ts/style/colors';
@@ -20,7 +21,6 @@ export class Identicon extends React.Component<IdenticonProps, IdenticonState> {
public render(): React.ReactNode {
const address = this.props.address;
const diameter = this.props.diameter;
- const radius = diameter / 2;
return (
<div
className="circle relative transitionFix"
@@ -40,9 +40,7 @@ export class Identicon extends React.Component<IdenticonProps, IdenticonState> {
width={diameter}
/>
) : (
- <svg height={diameter} width={diameter}>
- <circle cx={radius} cy={radius} r={radius} fill={colors.grey200} />
- </svg>
+ <Circle diameter={diameter} fillColor={colors.grey200} />
)}
</div>
);
diff --git a/packages/website/ts/components/ui/overlay.tsx b/packages/website/ts/components/ui/overlay.tsx
index 8b126a6d5..da26317de 100644
--- a/packages/website/ts/components/ui/overlay.tsx
+++ b/packages/website/ts/components/ui/overlay.tsx
@@ -4,7 +4,6 @@ import * as React from 'react';
import { zIndex } from 'ts/style/z_index';
export interface OverlayProps {
- children?: React.ReactNode;
style?: React.CSSProperties;
onClick?: () => void;
}
@@ -19,7 +18,7 @@ const style: React.CSSProperties = {
backgroundColor: 'rgba(0, 0, 0, 0.6)',
};
-export const Overlay: React.StatelessComponent = (props: OverlayProps) => (
+export const Overlay: React.StatelessComponent<OverlayProps> = props => (
<div style={{ ...style, ...props.style }} onClick={props.onClick}>
{props.children}
</div>
diff --git a/packages/website/ts/components/ui/simple_menu.tsx b/packages/website/ts/components/ui/simple_menu.tsx
new file mode 100644
index 000000000..74b8ef6ae
--- /dev/null
+++ b/packages/website/ts/components/ui/simple_menu.tsx
@@ -0,0 +1,88 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import * as CopyToClipboard from 'react-copy-to-clipboard';
+import { Link } from 'react-router-dom';
+
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+import { colors } from 'ts/style/colors';
+import { WebsitePaths } from 'ts/types';
+
+export interface SimpleMenuProps {
+ minWidth?: number | string;
+}
+
+export const SimpleMenu: React.StatelessComponent<SimpleMenuProps> = ({ children, minWidth }) => {
+ return (
+ <Container
+ marginLeft="16px"
+ marginRight="16px"
+ marginBottom="16px"
+ minWidth={minWidth}
+ className="flex flex-column"
+ >
+ {children}
+ </Container>
+ );
+};
+
+SimpleMenu.defaultProps = {
+ minWidth: '220px',
+};
+
+export interface SimpleMenuItemProps {
+ displayText: string;
+ onClick?: () => void;
+}
+export const SimpleMenuItem: React.StatelessComponent<SimpleMenuItemProps> = ({ displayText, onClick }) => {
+ // Falling back to _.noop for onclick retains the hovering effect
+ return (
+ <Container marginTop="16px" className="flex flex-column">
+ <Text
+ fontSize="14px"
+ fontColor={colors.darkGrey}
+ onClick={onClick || _.noop}
+ hoverColor={colors.mediumBlue}
+ >
+ {displayText}
+ </Text>
+ </Container>
+ );
+};
+
+export interface CopyAddressSimpleMenuItemProps {
+ userAddress: string;
+ onClick?: () => void;
+}
+export const CopyAddressSimpleMenuItem: React.StatelessComponent<CopyAddressSimpleMenuItemProps> = ({
+ userAddress,
+ onClick,
+}) => {
+ return (
+ <CopyToClipboard text={userAddress}>
+ <SimpleMenuItem displayText="Copy Address to Clipboard" onClick={onClick} />
+ </CopyToClipboard>
+ );
+};
+
+export interface GoToAccountManagementSimpleMenuItemProps {
+ onClick?: () => void;
+}
+export const GoToAccountManagementSimpleMenuItem: React.StatelessComponent<
+ GoToAccountManagementSimpleMenuItemProps
+> = ({ onClick }) => {
+ return (
+ <Link to={`${WebsitePaths.Portal}/account`} style={{ textDecoration: 'none' }}>
+ <SimpleMenuItem displayText="Manage Account..." onClick={onClick} />
+ </Link>
+ );
+};
+
+export interface DifferentWalletSimpleMenuItemProps {
+ onClick?: () => void;
+}
+export const DifferentWalletSimpleMenuItem: React.StatelessComponent<DifferentWalletSimpleMenuItemProps> = ({
+ onClick,
+}) => {
+ return <SimpleMenuItem displayText="Use a Different Wallet..." onClick={onClick} />;
+};
diff --git a/packages/website/ts/components/ui/text.tsx b/packages/website/ts/components/ui/text.tsx
index 1e2a123b7..315f72854 100644
--- a/packages/website/ts/components/ui/text.tsx
+++ b/packages/website/ts/components/ui/text.tsx
@@ -3,7 +3,7 @@ import { darken } from 'polished';
import * as React from 'react';
import { styled } from 'ts/style/theme';
-export type TextTag = 'p' | 'div' | 'span' | 'label' | 'h1' | 'h2' | 'h3' | 'h4';
+export type TextTag = 'p' | 'div' | 'span' | 'label' | 'h1' | 'h2' | 'h3' | 'h4' | 'i';
export interface TextProps {
className?: string;
@@ -15,7 +15,9 @@ export interface TextProps {
minHeight?: string;
center?: boolean;
fontWeight?: number | string;
- onClick?: () => void;
+ textDecorationLine?: string;
+ onClick?: (event: React.MouseEvent<HTMLElement>) => void;
+ hoverColor?: string;
}
const PlainText: React.StatelessComponent<TextProps> = ({ children, className, onClick, Tag }) => (
@@ -28,6 +30,7 @@ export const Text = styled(PlainText)`
font-family: ${props => props.fontFamily};
font-weight: ${props => props.fontWeight};
font-size: ${props => props.fontSize};
+ text-decoration-line: ${props => props.textDecorationLine};
${props => (props.lineHeight ? `line-height: ${props.lineHeight}` : '')};
${props => (props.center ? 'text-align: center' : '')};
color: ${props => props.fontColor};
@@ -35,7 +38,7 @@ export const Text = styled(PlainText)`
${props => (props.onClick ? 'cursor: pointer' : '')};
transition: color 0.5s ease;
&:hover {
- ${props => (props.onClick ? `color: ${darken(0.1, props.fontColor)}` : '')};
+ ${props => (props.onClick ? `color: ${props.hoverColor || darken(0.3, props.fontColor)}` : '')};
}
`;
@@ -45,6 +48,7 @@ Text.defaultProps = {
fontColor: colors.black,
fontSize: '15px',
lineHeight: '1.5em',
+ textDecorationLine: 'none',
Tag: 'div',
};
diff --git a/packages/website/ts/components/wallet/body_overlay.tsx b/packages/website/ts/components/wallet/body_overlay.tsx
new file mode 100644
index 000000000..5ced704f9
--- /dev/null
+++ b/packages/website/ts/components/wallet/body_overlay.tsx
@@ -0,0 +1,146 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+
+import { Blockchain } from 'ts/blockchain';
+import { Container } from 'ts/components/ui/container';
+import { Image } from 'ts/components/ui/image';
+import { Island } from 'ts/components/ui/island';
+import { Text } from 'ts/components/ui/text';
+import { Dispatcher } from 'ts/redux/dispatcher';
+import { colors } from 'ts/style/colors';
+import { styled } from 'ts/style/theme';
+import { AccountState, BrowserType, ProviderType } from 'ts/types';
+import { constants } from 'ts/utils/constants';
+import { utils } from 'ts/utils/utils';
+
+const METAMASK_IMG_SRC = '/images/metamask_icon.png';
+
+export interface BodyOverlayProps {
+ dispatcher: Dispatcher;
+ userAddress: string;
+ injectedProviderName: string;
+ providerType: ProviderType;
+ onToggleLedgerDialog: () => void;
+ blockchain?: Blockchain;
+ blockchainIsLoaded: boolean;
+}
+
+interface BodyOverlayState {}
+
+export class BodyOverlay extends React.Component<BodyOverlayProps, BodyOverlayState> {
+ public render(): React.ReactNode {
+ const accountState = this._getAccountState();
+ switch (accountState) {
+ case AccountState.Locked:
+ return <LockedOverlay onUseDifferentWalletClicked={this.props.onToggleLedgerDialog} />;
+ case AccountState.Disconnected:
+ return <DisconnectedOverlay onUseDifferentWalletClicked={this.props.onToggleLedgerDialog} />;
+ case AccountState.Ready:
+ case AccountState.Loading:
+ default:
+ return null;
+ }
+ }
+ private _isBlockchainReady(): boolean {
+ return this.props.blockchainIsLoaded && !_.isUndefined(this.props.blockchain);
+ }
+ private _getAccountState(): AccountState {
+ return utils.getAccountState(
+ this._isBlockchainReady(),
+ this.props.providerType,
+ this.props.injectedProviderName,
+ this.props.userAddress,
+ );
+ }
+}
+
+interface LockedOverlayProps {
+ className?: string;
+ onUseDifferentWalletClicked?: () => void;
+}
+const PlainLockedOverlay: React.StatelessComponent<LockedOverlayProps> = ({
+ className,
+ onUseDifferentWalletClicked,
+}) => (
+ <div className={className}>
+ <Container
+ className="flex flex-column items-center"
+ marginBottom="24px"
+ marginTop="24px"
+ marginLeft="48px"
+ marginRight="48px"
+ >
+ <Image src={METAMASK_IMG_SRC} height="70px" />
+ <Container marginTop="12px">
+ <Text fontColor={colors.metaMaskOrange} fontSize="16px" fontWeight="bold">
+ Please Unlock MetaMask
+ </Text>
+ </Container>
+ <UseDifferentWallet fontColor={colors.darkGrey} onClick={onUseDifferentWalletClicked} />
+ </Container>
+ </div>
+);
+const LockedOverlay = styled(PlainLockedOverlay)`
+ background: ${colors.metaMaskTransparentOrange};
+ border: 1px solid ${colors.metaMaskOrange};
+ border-radius: 10px;
+`;
+
+interface DisconnectedOverlayProps {
+ onUseDifferentWalletClicked?: () => void;
+}
+const DisconnectedOverlay = (props: DisconnectedOverlayProps) => {
+ return (
+ <div className="flex flex-column items-center">
+ <GetMetaMask />
+ <UseDifferentWallet fontColor={colors.mediumBlue} onClick={props.onUseDifferentWalletClicked} />
+ </div>
+ );
+};
+
+interface UseDifferentWallet {
+ fontColor: string;
+ onClick?: () => void;
+}
+const UseDifferentWallet = (props: UseDifferentWallet) => {
+ return (
+ <Container marginTop="12px">
+ <Text fontColor={props.fontColor} fontSize="16px" textDecorationLine="underline" onClick={props.onClick}>
+ Use a different wallet
+ </Text>
+ </Container>
+ );
+};
+
+const GetMetaMask = () => {
+ const browserType = utils.getBrowserType();
+ let extensionLink;
+ switch (browserType) {
+ case BrowserType.Chrome:
+ extensionLink = constants.URL_METAMASK_CHROME_STORE;
+ break;
+ case BrowserType.Firefox:
+ extensionLink = constants.URL_METAMASK_FIREFOX_STORE;
+ break;
+ case BrowserType.Opera:
+ extensionLink = constants.URL_METAMASK_OPERA_STORE;
+ break;
+ default:
+ extensionLink = constants.URL_METAMASK_HOMEPAGE;
+ }
+ return (
+ <a href={extensionLink} target="_blank" style={{ textDecoration: 'none' }}>
+ <Island
+ className="flex items-center py1 px2"
+ style={{ height: 28, borderRadius: 28, backgroundColor: colors.mediumBlue }}
+ >
+ <Image src={METAMASK_IMG_SRC} width="28px" />
+ <Container marginLeft="8px" marginRight="12px">
+ <Text fontColor={colors.white} fontSize="16px" fontWeight={500}>
+ Get MetaMask Wallet
+ </Text>
+ </Container>
+ </Island>
+ </a>
+ );
+};
diff --git a/packages/website/ts/components/wallet/null_token_row.tsx b/packages/website/ts/components/wallet/null_token_row.tsx
new file mode 100644
index 000000000..a1ec9871a
--- /dev/null
+++ b/packages/website/ts/components/wallet/null_token_row.tsx
@@ -0,0 +1,41 @@
+import * as React from 'react';
+
+import { Circle } from 'ts/components/ui/circle';
+import { Container } from 'ts/components/ui/container';
+import { Text } from 'ts/components/ui/text';
+import { PlaceHolder } from 'ts/components/wallet/placeholder';
+import { StandardIconRow } from 'ts/components/wallet/standard_icon_row';
+import { colors } from 'ts/style/colors';
+
+export interface NullTokenRowProps {
+ iconDimension: number;
+ fillColor: string;
+}
+
+export const NullTokenRow: React.StatelessComponent<NullTokenRowProps> = ({ iconDimension, fillColor }) => {
+ const icon = <Circle diameter={iconDimension} fillColor={fillColor} />;
+ const main = (
+ <div className="flex flex-column">
+ <PlaceHolder hideChildren={true} fillColor={fillColor}>
+ <Text fontSize="16px" fontWeight="bold" lineHeight="1em">
+ 0.00 XXX
+ </Text>
+ </PlaceHolder>
+ <Container marginTop="3px">
+ <PlaceHolder hideChildren={true} fillColor={fillColor}>
+ <Text fontSize="14px" fontColor={colors.darkGrey} lineHeight="1em">
+ $0.00
+ </Text>
+ </PlaceHolder>
+ </Container>
+ </div>
+ );
+ const accessory = (
+ <Container marginRight="12px">
+ <PlaceHolder hideChildren={true} fillColor={fillColor}>
+ <Container width="20px" height="14px" />
+ </PlaceHolder>
+ </Container>
+ );
+ return <StandardIconRow icon={icon} main={main} accessory={accessory} />;
+};
diff --git a/packages/website/ts/components/wallet/placeholder.tsx b/packages/website/ts/components/wallet/placeholder.tsx
new file mode 100644
index 000000000..bf40d2ea8
--- /dev/null
+++ b/packages/website/ts/components/wallet/placeholder.tsx
@@ -0,0 +1,25 @@
+import * as React from 'react';
+
+import { styled } from 'ts/style/theme';
+
+export interface PlaceHolderProps {
+ className?: string;
+ hideChildren: React.ReactNode;
+ fillColor: string;
+}
+
+const PlainPlaceHolder: React.StatelessComponent<PlaceHolderProps> = ({ className, hideChildren, children }) => {
+ const childrenVisibility = hideChildren ? 'hidden' : 'visible';
+ const childrenStyle: React.CSSProperties = { visibility: childrenVisibility };
+ return (
+ <div className={className}>
+ <div style={childrenStyle}>{children}</div>
+ </div>
+ );
+};
+
+export const PlaceHolder = styled(PlainPlaceHolder)`
+ background-color: ${props => (props.hideChildren ? props.fillColor : 'transparent')};
+ display: inline-block;
+ border-radius: 2px;
+`;
diff --git a/packages/website/ts/components/wallet/standard_icon_row.tsx b/packages/website/ts/components/wallet/standard_icon_row.tsx
new file mode 100644
index 000000000..1a2ec021b
--- /dev/null
+++ b/packages/website/ts/components/wallet/standard_icon_row.tsx
@@ -0,0 +1,44 @@
+import * as React from 'react';
+
+import { colors } from 'ts/style/colors';
+import { styled } from 'ts/style/theme';
+
+export interface StandardIconRowProps {
+ className?: string;
+ icon: React.ReactNode;
+ main: React.ReactNode;
+ accessory?: React.ReactNode;
+ minHeight?: string;
+ borderBottomColor?: string;
+ borderBottomStyle?: string;
+ borderWidth?: string;
+ backgroundColor?: string;
+}
+const PlainStandardIconRow: React.StatelessComponent<StandardIconRowProps> = ({ className, icon, main, accessory }) => {
+ return (
+ <div className={`flex items-center ${className}`}>
+ <div className="flex items-center px2">{icon}</div>
+ <div className="flex-none pr2">{main}</div>
+ <div className="flex-auto" />
+ <div>{accessory}</div>
+ </div>
+ );
+};
+
+export const StandardIconRow = styled(PlainStandardIconRow)`
+ min-height: ${props => props.minHeight};
+ border-bottom-color: ${props => props.borderBottomColor};
+ border-bottom-style: ${props => props.borderBottomStyle};
+ border-width: ${props => props.borderWidth};
+ background-color: ${props => props.backgroundColor};
+`;
+
+StandardIconRow.defaultProps = {
+ minHeight: '85px',
+ borderBottomColor: colors.walletBorder,
+ borderBottomStyle: 'solid',
+ borderWidth: '1px',
+ backgroundColor: colors.walletDefaultItemBackground,
+};
+
+StandardIconRow.displayName = 'StandardIconRow';
diff --git a/packages/website/ts/components/wallet/wallet.tsx b/packages/website/ts/components/wallet/wallet.tsx
index 785b2da88..1e0b9ec48 100644
--- a/packages/website/ts/components/wallet/wallet.tsx
+++ b/packages/website/ts/components/wallet/wallet.tsx
@@ -1,36 +1,37 @@
-import {
- constants as sharedConstants,
- EtherscanLinkSuffixes,
- Styles,
- utils as sharedUtils,
-} from '@0xproject/react-shared';
+import { constants as sharedConstants, EtherscanLinkSuffixes, 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';
-import ContentRemove from 'material-ui/svg-icons/content/remove';
import * as React from 'react';
-import { Link } from 'react-router-dom';
import firstBy = require('thenby');
import { Blockchain } from 'ts/blockchain';
+import { AccountConnection } from 'ts/components/ui/account_connection';
import { Container } from 'ts/components/ui/container';
+import { DropDown, DropdownMouseEvent } from 'ts/components/ui/drop_down';
import { IconButton } from 'ts/components/ui/icon_button';
import { Identicon } from 'ts/components/ui/identicon';
import { Island } from 'ts/components/ui/island';
+import {
+ CopyAddressSimpleMenuItem,
+ DifferentWalletSimpleMenuItem,
+ GoToAccountManagementSimpleMenuItem,
+ SimpleMenu,
+ SimpleMenuItem,
+} from 'ts/components/ui/simple_menu';
+import { Text } from 'ts/components/ui/text';
import { TokenIcon } from 'ts/components/ui/token_icon';
-import { WalletDisconnectedItem } from 'ts/components/wallet/wallet_disconnected_item';
+import { BodyOverlay } from 'ts/components/wallet/body_overlay';
+import { NullTokenRow } from 'ts/components/wallet/null_token_row';
+import { PlaceHolder } from 'ts/components/wallet/placeholder';
+import { StandardIconRow } from 'ts/components/wallet/standard_icon_row';
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 { styled } from 'ts/style/theme';
import {
+ AccountState,
BlockchainErrs,
ProviderType,
ScreenWidths,
@@ -39,12 +40,10 @@ import {
TokenByAddress,
TokenState,
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';
export interface WalletProps {
userAddress: string;
@@ -84,66 +83,14 @@ interface AccessoryItemConfig {
allowanceToggleConfig?: AllowanceToggleConfig;
}
-const styles: Styles = {
- root: {
- width: '100%',
- },
- footerItemInnerDiv: {
- paddingLeft: 24,
- borderTopColor: colors.walletBorder,
- borderTopStyle: 'solid',
- borderWidth: 1,
- },
- borderedItem: {
- borderBottomColor: colors.walletBorder,
- borderBottomStyle: 'solid',
- borderWidth: 1,
- },
- tokenItem: {
- backgroundColor: colors.walletDefaultItemBackground,
- minHeight: 85,
- },
- amountLabel: {
- fontWeight: 'bold',
- color: colors.black,
- },
- valueLabel: {
- color: colors.grey,
- fontSize: 14,
- },
- paddedItem: {
- paddingTop: 8,
- paddingBottom: 8,
- },
- bodyInnerDiv: {
- overflow: 'auto',
- WebkitOverflowScrolling: 'touch',
- },
- manageYourWalletText: {
- color: colors.mediumBlue,
- fontWeight: 'bold',
- },
- loadingBody: {
- height: 381,
- },
-};
-
const ETHER_ICON_PATH = '/images/ether.png';
const ICON_DIMENSION = 28;
const BODY_ITEM_KEY = 'BODY';
const HEADER_ITEM_KEY = 'HEADER';
-const FOOTER_ITEM_KEY = 'FOOTER';
-const DISCONNECTED_ITEM_KEY = 'DISCONNECTED';
const ETHER_ITEM_KEY = 'ETHER';
-const USD_DECIMAL_PLACES = 2;
const NO_ALLOWANCE_TOGGLE_SPACE_WIDTH = 56;
-const ACCOUNT_PATH = `${WebsitePaths.Portal}/account`;
-
-const ActionButton = styled(FloatingActionButton)`
- button {
- position: static !important;
- }
-`;
+const PLACEHOLDER_COLOR = colors.grey300;
+const LOADING_ROWS_COUNT = 6;
export class Wallet extends React.Component<WalletProps, WalletState> {
public static defaultProps = {
@@ -171,72 +118,138 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
}
}
public render(): React.ReactNode {
- const isBlockchainLoaded = this.props.blockchainIsLoaded && this.props.blockchainErr === BlockchainErrs.NoError;
return (
- <Island className="flex flex-column wallet" style={{ ...styles.root, ...this.props.style }}>
- {isBlockchainLoaded ? this._renderLoadedRows() : this._renderLoadingRows()}
+ <Island className="flex flex-column wallet" style={this.props.style}>
+ {this._isBlockchainReady() ? this._renderLoadedRows() : this._renderLoadingRows()}
</Island>
);
}
- private _renderLoadedRows(): React.ReactNode {
- const isAddressAvailable = !_.isEmpty(this.props.userAddress);
- return isAddressAvailable
- ? _.concat(this._renderConnectedHeaderRows(), this._renderBody(), this._renderFooterRows())
- : _.concat(this._renderDisconnectedHeaderRows(), this._renderDisconnectedRows());
- }
private _renderLoadingRows(): React.ReactNode {
- return _.concat(this._renderDisconnectedHeaderRows(), this._renderLoadingBodyRows());
+ return _.concat(this._renderLoadingHeaderRows(), this._renderLoadingBodyRows());
+ }
+ private _renderLoadingHeaderRows(): React.ReactElement<{}> {
+ return this._renderPlainHeaderRow('Loading...');
}
private _renderLoadingBodyRows(): React.ReactElement<{}> {
+ const bodyStyle = this._getBodyStyle();
+ const loadingRowsRange = _.range(LOADING_ROWS_COUNT);
return (
- <div key={BODY_ITEM_KEY} className="flex items-center" style={styles.loadingBody}>
- <div className="mx-auto">
- <CircularProgress size={40} thickness={5} />
- </div>
+ <div key={BODY_ITEM_KEY} className="flex flex-column" style={bodyStyle}>
+ {_.map(loadingRowsRange, index => {
+ return <NullTokenRow key={index} iconDimension={ICON_DIMENSION} fillColor={PLACEHOLDER_COLOR} />;
+ })}
+ <Container
+ className="flex items-center"
+ position="absolute"
+ width="100%"
+ height="100%"
+ maxHeight={bodyStyle.maxHeight}
+ >
+ <div className="mx-auto">
+ <BodyOverlay
+ dispatcher={this.props.dispatcher}
+ userAddress={this.props.userAddress}
+ injectedProviderName={this.props.injectedProviderName}
+ providerType={this.props.providerType}
+ onToggleLedgerDialog={this.props.onToggleLedgerDialog}
+ blockchain={this.props.blockchain}
+ blockchainIsLoaded={this.props.blockchainIsLoaded}
+ />
+ </div>
+ </Container>
</div>
);
}
+ private _renderLoadedRows(): React.ReactNode {
+ const isAddressAvailable = !_.isEmpty(this.props.userAddress);
+ return isAddressAvailable
+ ? _.concat(this._renderConnectedHeaderRows(), this._renderBody())
+ : _.concat(this._renderDisconnectedHeaderRows(), this._renderLoadingBodyRows());
+ }
private _renderDisconnectedHeaderRows(): React.ReactElement<{}> {
- const primaryText = 'wallet';
- return (
- <StandardIconRow
- key={HEADER_ITEM_KEY}
- icon={<ActionAccountBalanceWallet color={colors.mediumBlue} />}
- main={primaryText.toUpperCase()}
- style={styles.borderedItem}
- />
+ const isExternallyInjectedProvider = utils.isExternallyInjected(
+ this.props.providerType,
+ this.props.injectedProviderName,
);
+ const text = isExternallyInjectedProvider ? 'Please unlock MetaMask...' : 'Please connect a wallet...';
+ return this._renderPlainHeaderRow(text);
}
- private _renderDisconnectedRows(): React.ReactElement<{}> {
+ private _renderPlainHeaderRow(text: string): React.ReactElement<{}> {
return (
- <WalletDisconnectedItem
- key={DISCONNECTED_ITEM_KEY}
- providerType={this.props.providerType}
- injectedProviderName={this.props.injectedProviderName}
- onToggleLedgerDialog={this.props.onToggleLedgerDialog}
+ <StandardIconRow
+ key={HEADER_ITEM_KEY}
+ icon={<ActionAccountBalanceWallet color={colors.grey} />}
+ main={
+ <Text fontSize="16px" fontColor={colors.grey}>
+ {text}
+ </Text>
+ // https://github.com/palantir/tslint-react/issues/140
+ // tslint:disable-next-line:jsx-curly-spacing
+ }
+ minHeight="60px"
+ backgroundColor={colors.white}
/>
);
}
private _renderConnectedHeaderRows(): React.ReactElement<{}> {
+ const isMobile = this.props.screenWidth === ScreenWidths.Sm;
const userAddress = this.props.userAddress;
- const primaryText = utils.getAddressBeginAndEnd(userAddress);
+ const accountState = this._getAccountState();
+ const main = (
+ <div className="flex flex-column">
+ <Text fontSize="16px" lineHeight="19px" fontWeight={500}>
+ {utils.getAddressBeginAndEnd(userAddress)}
+ </Text>
+ <AccountConnection accountState={accountState} injectedProviderName={this.props.injectedProviderName} />
+ </div>
+ );
+ const onClick = _.noop;
+ const accessory = (
+ <DropDown
+ activeNode={
+ // this container gives the menu button more of a hover target for the drop down
+ // it prevents accidentally closing the menu by moving off of the button
+ <Container paddingLeft="100px" paddingRight="15px">
+ <Text
+ className="zmdi zmdi-more-horiz"
+ Tag="i"
+ fontSize="32px"
+ fontFamily="Material-Design-Iconic-Font"
+ fontColor={colors.darkGrey}
+ onClick={onClick}
+ hoverColor={colors.mediumBlue}
+ />
+ </Container>
+ }
+ popoverContent={
+ <SimpleMenu minWidth="150px">
+ <CopyAddressSimpleMenuItem userAddress={this.props.userAddress} />
+ <DifferentWalletSimpleMenuItem onClick={this.props.onToggleLedgerDialog} />
+ <SimpleMenuItem displayText="Add Tokens..." onClick={this.props.onAddToken} />
+ <SimpleMenuItem displayText="Remove Tokens..." onClick={this.props.onRemoveToken} />
+ <GoToAccountManagementSimpleMenuItem />
+ </SimpleMenu>
+ }
+ anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
+ targetOrigin={{ horizontal: 'right', vertical: 'top' }}
+ zDepth={1}
+ activateEvent={DropdownMouseEvent.Click}
+ closeEvent={isMobile ? DropdownMouseEvent.Click : DropdownMouseEvent.Hover}
+ />
+ );
return (
- <Link key={HEADER_ITEM_KEY} to={ACCOUNT_PATH} style={{ textDecoration: 'none' }}>
- <StandardIconRow
- icon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />}
- main={primaryText}
- style={styles.borderedItem}
- />
- </Link>
+ <StandardIconRow
+ key={HEADER_ITEM_KEY}
+ icon={<Identicon address={userAddress} diameter={ICON_DIMENSION} />}
+ main={main}
+ accessory={accessory}
+ minHeight="60px"
+ backgroundColor={colors.white}
+ />
);
}
private _renderBody(): React.ReactElement<{}> {
- const bodyStyle: React.CSSProperties = {
- ...styles.bodyInnerDiv,
- overflow: this.state.isHoveringSidebar ? 'auto' : 'hidden',
- // TODO: make this completely responsive
- maxHeight: this.props.screenWidth !== ScreenWidths.Sm ? 475 : undefined,
- };
+ const bodyStyle = this._getBodyStyle();
return (
<div
style={bodyStyle}
@@ -249,6 +262,17 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
</div>
);
}
+ private _getBodyStyle(): React.CSSProperties {
+ return {
+ overflow: 'auto',
+ WebkitOverflowScrolling: 'touch',
+ position: 'relative',
+ overflowY: this.state.isHoveringSidebar ? 'scroll' : 'hidden',
+ marginRight: this.state.isHoveringSidebar ? 0 : 4,
+ // TODO: make this completely responsive
+ maxHeight: this.props.screenWidth !== ScreenWidths.Sm ? 475 : undefined,
+ };
+ }
private _onSidebarHover(_event: React.FormEvent<HTMLInputElement>): void {
this.setState({
isHoveringSidebar: true,
@@ -259,51 +283,6 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
isHoveringSidebar: false,
});
}
- private _renderFooterRows(): React.ReactElement<{}> {
- return (
- <div key={FOOTER_ITEM_KEY}>
- <ListItem
- primaryText={
- <div className="flex">
- <ActionButton mini={true} zDepth={0} onClick={this.props.onAddToken}>
- <ContentAdd />
- </ActionButton>
- <ActionButton mini={true} zDepth={0} className="px1" onClick={this.props.onRemoveToken}>
- <ContentRemove />
- </ActionButton>
- <div
- style={{
- paddingLeft: 10,
- position: 'relative',
- top: '50%',
- transform: 'translateY(33%)',
- }}
- >
- add/remove tokens
- </div>
- </div>
- }
- disabled={true}
- innerDivStyle={styles.footerItemInnerDiv}
- style={styles.borderedItem}
- />
- {this.props.location.pathname !== ACCOUNT_PATH && (
- <Link to={ACCOUNT_PATH} style={{ textDecoration: 'none' }}>
- <ListItem
- primaryText={
- <div className="flex right" style={styles.manageYourWalletText}>
- manage your wallet
- </div>
- // https://github.com/palantir/tslint-react/issues/140
- // tslint:disable-next-line:jsx-curly-spacing
- }
- style={{ ...styles.paddedItem, ...styles.borderedItem }}
- />
- </Link>
- )}
- </div>
- );
- }
private _renderEthRows(): React.ReactNode {
const icon = <img style={{ width: ICON_DIMENSION, height: ICON_DIMENSION }} src={ETHER_ICON_PATH} />;
const primaryText = this._renderAmount(
@@ -325,7 +304,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
wrappedEtherDirection: Side.Deposit,
};
const key = ETHER_ITEM_KEY;
- return this._renderBalanceRow(key, icon, primaryText, secondaryText, accessoryItemConfig, false, 'eth-row');
+ return this._renderBalanceRow(key, icon, primaryText, secondaryText, accessoryItemConfig);
}
private _renderTokenRows(): React.ReactNode {
const trackedTokens = this.props.trackedTokens;
@@ -336,7 +315,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
);
return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this));
}
- private _renderTokenRow(token: Token, index: number): React.ReactNode {
+ private _renderTokenRow(token: Token): React.ReactNode {
const tokenState = this.props.trackedTokenStateByAddress[token.address];
if (_.isUndefined(tokenState)) {
return null;
@@ -364,16 +343,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
},
};
const key = token.address;
- const isLastRow = index === this.props.trackedTokens.length - 1;
- return this._renderBalanceRow(
- key,
- icon,
- primaryText,
- secondaryText,
- accessoryItemConfig,
- isLastRow,
- isWeth ? 'weth-row' : undefined,
- );
+ return this._renderBalanceRow(key, icon, primaryText, secondaryText, accessoryItemConfig);
}
private _renderBalanceRow(
key: string,
@@ -381,23 +351,31 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
primaryText: React.ReactNode,
secondaryText: React.ReactNode,
accessoryItemConfig: AccessoryItemConfig,
- isLastRow: boolean,
className?: string,
): React.ReactNode {
const shouldShowWrapEtherItem =
!_.isUndefined(this.state.wrappedEtherDirection) &&
this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection &&
!_.isUndefined(this.props.userEtherBalanceInWei);
- let additionalStyle;
- if (shouldShowWrapEtherItem) {
- additionalStyle = walletItemStyles.focusedItem;
- } else if (!isLastRow) {
- additionalStyle = styles.borderedItem;
- }
- const style = { ...styles.tokenItem, ...additionalStyle };
const etherToken = this._getEthToken();
+ const wrapEtherItem = shouldShowWrapEtherItem ? (
+ <WrapEtherItem
+ userAddress={this.props.userAddress}
+ networkId={this.props.networkId}
+ blockchain={this.props.blockchain}
+ dispatcher={this.props.dispatcher}
+ userEtherBalanceInWei={this.props.userEtherBalanceInWei}
+ direction={accessoryItemConfig.wrappedEtherDirection}
+ etherToken={etherToken}
+ lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
+ onConversionSuccessful={this._closeWrappedEtherActionRow.bind(this)}
+ // tslint:disable:jsx-no-lambda
+ refetchEthTokenStateAsync={async () => this.props.refetchTokenStateAsync(etherToken.address)}
+ />
+ ) : null;
return (
<div id={key} key={key} className={`flex flex-column ${className || ''}`}>
+ {this.state.wrappedEtherDirection === Side.Receive && wrapEtherItem}
<StandardIconRow
icon={icon}
main={
@@ -407,23 +385,8 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
</div>
}
accessory={this._renderAccessoryItems(accessoryItemConfig)}
- style={style}
/>
- {shouldShowWrapEtherItem && (
- <WrapEtherItem
- userAddress={this.props.userAddress}
- networkId={this.props.networkId}
- blockchain={this.props.blockchain}
- dispatcher={this.props.dispatcher}
- userEtherBalanceInWei={this.props.userEtherBalanceInWei}
- direction={accessoryItemConfig.wrappedEtherDirection}
- etherToken={etherToken}
- lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
- onConversionSuccessful={this._closeWrappedEtherActionRow.bind(this)}
- // tslint:disable:jsx-no-lambda
- refetchEthTokenStateAsync={async () => this.props.refetchTokenStateAsync(etherToken.address)}
- />
- )}
+ {this.state.wrappedEtherDirection === Side.Deposit && wrapEtherItem}
</div>
);
}
@@ -466,13 +429,19 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
): React.ReactNode {
if (isLoading) {
return (
- <PlaceHolder hideChildren={isLoading}>
- <div style={styles.amountLabel}>0.00 XXX</div>
+ <PlaceHolder hideChildren={isLoading} fillColor={PLACEHOLDER_COLOR}>
+ <Text fontSize="16px" fontWeight="bold" lineHeight="1em">
+ 0.00 XXX
+ </Text>
</PlaceHolder>
);
} else {
const result = utils.getFormattedAmount(amount, decimals, symbol);
- return <div style={styles.amountLabel}>{result}</div>;
+ return (
+ <Text fontSize="16px" fontWeight="bold" lineHeight="1em">
+ {result}
+ </Text>
+ );
}
}
private _renderValue(
@@ -481,22 +450,16 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
price?: BigNumber,
isLoading: boolean = false,
): React.ReactNode {
- let result;
- if (!isLoading) {
- if (_.isUndefined(price)) {
- result = '--';
- } else {
- const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals);
- const value = unitAmount.mul(price);
- const formattedAmount = value.toFixed(USD_DECIMAL_PLACES);
- result = `$${formattedAmount}`;
- }
- } else {
- result = '$0.00';
- }
+ const result = !isLoading
+ ? _.isUndefined(price)
+ ? '--'
+ : utils.getUsdValueFormattedAmount(amount, decimals, price)
+ : '$0.00';
return (
- <PlaceHolder hideChildren={isLoading}>
- <div style={styles.valueLabel}>{result}</div>
+ <PlaceHolder hideChildren={isLoading} fillColor={PLACEHOLDER_COLOR}>
+ <Text fontSize="14px" fontColor={colors.darkGrey} lineHeight="1em">
+ {result}
+ </Text>
</PlaceHolder>
);
}
@@ -549,41 +512,17 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
private _getEthToken(): Token {
return utils.getEthToken(this.props.tokenByAddress);
}
+ private _isBlockchainReady(): boolean {
+ return this.props.blockchainIsLoaded && !_.isUndefined(this.props.blockchain);
+ }
+ private _getAccountState(): AccountState {
+ return utils.getAccountState(
+ this._isBlockchainReady(),
+ this.props.providerType,
+ this.props.injectedProviderName,
+ this.props.userAddress,
+ );
+ }
}
-interface StandardIconRowProps {
- icon: React.ReactNode;
- main: React.ReactNode;
- accessory?: React.ReactNode;
- style?: React.CSSProperties;
-}
-const StandardIconRow = (props: StandardIconRowProps) => {
- return (
- <div className="flex items-center" style={props.style}>
- <div className="p2">{props.icon}</div>
- <div className="flex-none pr2 pt2 pb2">{props.main}</div>
- <div className="flex-auto" />
- <div>{props.accessory}</div>
- </div>
- );
-};
-interface PlaceHolderProps {
- hideChildren: React.ReactNode;
- children?: React.ReactNode;
-}
-const PlaceHolder = (props: PlaceHolderProps) => {
- const rootBackgroundColor = props.hideChildren ? colors.lightGrey : 'transparent';
- const rootStyle: React.CSSProperties = {
- backgroundColor: rootBackgroundColor,
- display: 'inline-block',
- borderRadius: 2,
- };
- const childrenVisibility = props.hideChildren ? 'hidden' : 'visible';
- const childrenStyle: React.CSSProperties = { visibility: childrenVisibility };
- return (
- <div style={rootStyle}>
- <div style={childrenStyle}>{props.children}</div>
- </div>
- );
-};
// tslint:disable:max-file-line-count
diff --git a/packages/website/ts/components/wallet/wallet_disconnected_item.tsx b/packages/website/ts/components/wallet/wallet_disconnected_item.tsx
deleted file mode 100644
index 024b28544..000000000
--- a/packages/website/ts/components/wallet/wallet_disconnected_item.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import { Styles } from '@0xproject/react-shared';
-import FlatButton from 'material-ui/FlatButton';
-import * as React from 'react';
-
-import { colors } from 'ts/style/colors';
-import { BrowserType, ProviderType } from 'ts/types';
-import { constants } from 'ts/utils/constants';
-import { utils } from 'ts/utils/utils';
-
-export interface WalletDisconnectedItemProps {
- providerType: ProviderType;
- injectedProviderName: string;
- onToggleLedgerDialog: () => void;
-}
-
-const styles: Styles = {
- button: {
- border: colors.walletBorder,
- borderStyle: 'solid',
- borderWidth: 1,
- height: 80,
- },
- hrefAdjustment: {
- paddingTop: 20, // HACK: For some reason when we set the href prop of a FlatButton material-ui reduces the top padding
- },
- otherWalletText: {
- fontSize: 14,
- color: colors.grey500,
- textDecoration: 'underline',
- },
-};
-
-const ITEM_HEIGHT = 381;
-const METAMASK_ICON_WIDTH = 35;
-const LEDGER_ICON_WIDTH = 30;
-const BUTTON_BOTTOM_PADDING = 80;
-
-export const WalletDisconnectedItem: React.StatelessComponent<WalletDisconnectedItemProps> = (
- props: WalletDisconnectedItemProps,
-) => {
- const isExternallyInjectedProvider = utils.isExternallyInjected(props.providerType, props.injectedProviderName);
- return (
- <div className="flex flex-center">
- <div className="mx-auto">
- <div className="table" style={{ height: ITEM_HEIGHT }}>
- <div className="table-cell align-middle">
- <ProviderButton isExternallyInjectedProvider={isExternallyInjectedProvider} />
- <div className="flex flex-center py2" style={{ paddingBottom: BUTTON_BOTTOM_PADDING }}>
- <div className="mx-auto">
- <div onClick={props.onToggleLedgerDialog} style={{ cursor: 'pointer' }}>
- <img src="/images/ledger_icon.png" style={{ width: LEDGER_ICON_WIDTH }} />
- <span className="px1" style={styles.otherWalletText}>
- user other wallet
- </span>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- );
-};
-
-interface ProviderButtonProps {
- isExternallyInjectedProvider: boolean;
-}
-
-const ProviderButton: React.StatelessComponent<ProviderButtonProps> = (props: ProviderButtonProps) => {
- const browserType = utils.getBrowserType();
- let extensionLink;
- if (!props.isExternallyInjectedProvider) {
- switch (browserType) {
- case BrowserType.Chrome:
- extensionLink = constants.URL_METAMASK_CHROME_STORE;
- break;
- case BrowserType.Firefox:
- extensionLink = constants.URL_METAMASK_FIREFOX_STORE;
- break;
- case BrowserType.Opera:
- extensionLink = constants.URL_METAMASK_OPERA_STORE;
- break;
- default:
- extensionLink = constants.URL_METAMASK_HOMEPAGE;
- }
- }
- return (
- <FlatButton
- label={props.isExternallyInjectedProvider ? 'Please unlock account' : 'Get Metamask Wallet Extension'}
- labelStyle={{ color: colors.black }}
- labelPosition="after"
- primary={true}
- icon={<img src="/images/metamask_icon.png" width={METAMASK_ICON_WIDTH.toString()} />}
- style={props.isExternallyInjectedProvider ? styles.button : { ...styles.button, ...styles.hrefAdjustment }}
- href={extensionLink}
- target={props.isExternallyInjectedProvider ? undefined : '_blank'}
- disabled={props.isExternallyInjectedProvider}
- />
- );
-};
diff --git a/packages/website/ts/components/wallet/wrap_ether_item.tsx b/packages/website/ts/components/wallet/wrap_ether_item.tsx
index d6135ce4d..2b4cf93fe 100644
--- a/packages/website/ts/components/wallet/wrap_ether_item.tsx
+++ b/packages/website/ts/components/wallet/wrap_ether_item.tsx
@@ -8,6 +8,7 @@ import * as React from 'react';
import { Blockchain } from 'ts/blockchain';
import { EthAmountInput } from 'ts/components/inputs/eth_amount_input';
import { TokenAmountInput } from 'ts/components/inputs/token_amount_input';
+import { Container } from 'ts/components/ui/container';
import { Dispatcher } from 'ts/redux/dispatcher';
import { colors } from 'ts/style/colors';
import { BlockchainCallErrs, Side, Token } from 'ts/types';
@@ -15,7 +16,6 @@ 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';
-import { styles as walletItemStyles } from 'ts/utils/wallet_item_styles';
export interface WrapEtherItemProps {
userAddress: string;
@@ -95,7 +95,7 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
const topLabelText = isWrappingEth ? 'Convert ETH into WETH 1:1' : 'Convert WETH into ETH 1:1';
return (
- <div className="flex" style={walletItemStyles.focusedItem}>
+ <Container className="flex" backgroundColor={colors.walletFocusedItemBackground} paddingTop="25px">
<div>{this._renderIsEthConversionHappeningSpinner()} </div>
<div className="flex flex-column">
<div style={styles.topLabel}>{topLabelText}</div>
@@ -143,7 +143,7 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
{this._renderErrorMsg()}
</div>
- </div>
+ </Container>
);
}
private _onValueChange(_isValid: boolean, amount?: BigNumber): void {
@@ -173,6 +173,7 @@ export class WrapEtherItem extends React.Component<WrapEtherItemProps, WrapEther
<FlatButton
backgroundColor={colors.wrapEtherConfirmationButton}
label={labelText}
+ style={{ zIndex: 0 }}
labelStyle={styles.wrapEtherConfirmationButtonLabel}
onClick={this._wrapEtherConfirmationActionAsync.bind(this)}
disabled={this.state.isEthConversionHappening}
diff --git a/packages/website/ts/containers/portal_onboarding_flow.ts b/packages/website/ts/containers/portal_onboarding_flow.ts
index 12daad021..a813205b1 100644
--- a/packages/website/ts/containers/portal_onboarding_flow.ts
+++ b/packages/website/ts/containers/portal_onboarding_flow.ts
@@ -19,7 +19,7 @@ interface ConnectedState {
stepIndex: number;
isRunning: boolean;
userAddress: string;
- hasBeenSeen: boolean;
+ hasBeenClosed: boolean;
providerType: ProviderType;
injectedProviderName: string;
blockchainIsLoaded: boolean;
@@ -43,7 +43,7 @@ const mapStateToProps = (state: State, _ownProps: PortalOnboardingFlowProps): Co
blockchainIsLoaded: state.blockchainIsLoaded,
userEtherBalanceInWei: state.userEtherBalanceInWei,
tokenByAddress: state.tokenByAddress,
- hasBeenSeen: state.hasPortalOnboardingBeenSeen,
+ hasBeenClosed: state.hasPortalOnboardingBeenClosed,
screenWidth: state.screenWidth,
});
diff --git a/packages/website/ts/containers/subproviders_documentation.ts b/packages/website/ts/containers/subproviders_documentation.ts
index 6d4230e53..567f6a37e 100644
--- a/packages/website/ts/containers/subproviders_documentation.ts
+++ b/packages/website/ts/containers/subproviders_documentation.ts
@@ -25,6 +25,7 @@ const docSections = {
emptyWalletSubprovider: 'emptyWalletSubprovider',
fakeGasEstimateSubprovider: 'fakeGasEstimateSubprovider',
injectedWeb3Subprovider: 'injectedWeb3Subprovider',
+ signerSubprovider: 'signerSubprovider',
redundantRPCSubprovider: 'redundantRPCSubprovider',
ganacheSubprovider: 'ganacheSubprovider',
nonceTrackerSubprovider: 'nonceTrackerSubprovider',
@@ -50,6 +51,7 @@ const docsInfoConfig: DocsInfoConfig = {
['emptyWallet-subprovider']: [docSections.emptyWalletSubprovider],
['fakeGasEstimate-subprovider']: [docSections.fakeGasEstimateSubprovider],
['injectedWeb3-subprovider']: [docSections.injectedWeb3Subprovider],
+ ['signer-subprovider']: [docSections.signerSubprovider],
['redundantRPC-subprovider']: [docSections.redundantRPCSubprovider],
['ganache-subprovider']: [docSections.ganacheSubprovider],
['nonceTracker-subprovider']: [docSections.nonceTrackerSubprovider],
@@ -69,6 +71,7 @@ const docsInfoConfig: DocsInfoConfig = {
[docSections.emptyWalletSubprovider]: ['"subproviders/src/subproviders/empty_wallet_subprovider"'],
[docSections.fakeGasEstimateSubprovider]: ['"subproviders/src/subproviders/fake_gas_estimate_subprovider"'],
[docSections.injectedWeb3Subprovider]: ['"subproviders/src/subproviders/injected_web3"'],
+ [docSections.signerSubprovider]: ['"subproviders/src/subproviders/signer"'],
[docSections.redundantRPCSubprovider]: ['"subproviders/src/subproviders/redundant_rpc"'],
[docSections.ganacheSubprovider]: ['"subproviders/src/subproviders/ganache"'],
[docSections.nonceTrackerSubprovider]: ['"subproviders/src/subproviders/nonce_tracker"'],
diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx
index 23387a95a..c7ccfdf1f 100644
--- a/packages/website/ts/index.tsx
+++ b/packages/website/ts/index.tsx
@@ -97,7 +97,6 @@ render(
<Route path={WebsitePaths.FAQ} component={FAQ as any} />
<Route path={WebsitePaths.About} component={About as any} />
<Route path={WebsitePaths.Wiki} component={Wiki as any} />
- <Route path={`${WebsitePaths.Docs}`} component={LazyZeroExJSDocumentation} />
<Route path={`${WebsitePaths.ZeroExJs}/:version?`} component={LazyZeroExJSDocumentation} />
<Route path={`${WebsitePaths.Connect}/:version?`} component={LazyConnectDocumentation} />
<Route
@@ -144,6 +143,7 @@ render(
component={LazySolCompilerDocumentation}
/>
+ <Route path={`${WebsitePaths.Docs}`} component={LazyZeroExJSDocumentation} />
<Route component={NotFound as any} />
</Switch>
</div>
diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx
index 0259af36f..5bb5d06a9 100644
--- a/packages/website/ts/pages/about/about.tsx
+++ b/packages/website/ts/pages/about/about.tsx
@@ -165,16 +165,24 @@ const teamRow5: ProfileInfo[] = [
},
];
-// const teamRow6: ProfileInfo[] = [
-// {
-// name: 'Chris Kalani',
-// title: 'Director of Design',
-// description: `Previously founded Wake (acquired by InVision). Early Facebook product designer.`,
-// image: 'images/team/chris.png',
-// linkedIn: 'https://www.linkedin.com/in/chriskalani/',
-// github: 'https://github.com/chriskalani',
-// },
-// ];
+const teamRow6: ProfileInfo[] = [
+ {
+ name: 'Alex Browne',
+ title: 'Engineer in Residence',
+ description: `Full-stack blockchain engineer. Previously at Plaid. Open source guru and footgun dismantler. Computer Science and Electrical Engineering at Duke.`,
+ image: 'images/team/alexbrowne.png',
+ linkedIn: 'https://www.linkedin.com/in/stephenalexbrowne/',
+ github: 'http://github.com/albrow',
+ },
+ // {
+ // name: 'Chris Kalani',
+ // title: 'Director of Design',
+ // description: `Previously founded Wake (acquired by InVision). Early Facebook product designer.`,
+ // image: 'images/team/chris.png',
+ // linkedIn: 'https://www.linkedin.com/in/chriskalani/',
+ // github: 'https://github.com/chriskalani',
+ // },
+];
const advisors: ProfileInfo[] = [
{
@@ -270,6 +278,7 @@ export class About extends React.Component<AboutProps, AboutState> {
<div className="clearfix">{this._renderProfiles(teamRow3)}</div>
<div className="clearfix">{this._renderProfiles(teamRow4)}</div>
<div className="clearfix">{this._renderProfiles(teamRow5)}</div>
+ <div className="clearfix">{this._renderProfiles(teamRow6)}</div>
</div>
<div className="pt3 pb2">
<div
diff --git a/packages/website/ts/pages/jobs/list/list_item.tsx b/packages/website/ts/pages/jobs/list/list_item.tsx
index d7838bc01..192433d39 100644
--- a/packages/website/ts/pages/jobs/list/list_item.tsx
+++ b/packages/website/ts/pages/jobs/list/list_item.tsx
@@ -1,14 +1,14 @@
import * as React from 'react';
+import { Circle } from 'ts/components/ui/circle';
+
export interface ListItemProps {
bulletColor?: string;
}
export const ListItem: React.StatelessComponent<ListItemProps> = ({ bulletColor, children }) => {
return (
<div className="flex items-center">
- <svg className="flex-none lg-px2 md-px2 sm-pl2" height="26" width="26">
- <circle cx="13" cy="13" r="13" fill={bulletColor || 'transparent'} />
- </svg>
+ <Circle className="flex-none lg-px2 md-px2 sm-pl2" diameter={26} fillColor={bulletColor || 'transparent'} />
<div className="flex-auto px2">{children}</div>
</div>
);
diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts
index ed6a4868e..caddabcf0 100644
--- a/packages/website/ts/redux/reducer.ts
+++ b/packages/website/ts/redux/reducer.ts
@@ -42,7 +42,7 @@ export interface State {
userEtherBalanceInWei?: BigNumber;
portalOnboardingStep: number;
isPortalOnboardingShowing: boolean;
- hasPortalOnboardingBeenSeen: boolean;
+ hasPortalOnboardingBeenClosed: boolean;
// Note: cache of supplied orderJSON in fill order step. Do not use for anything else.
userSuppliedOrderCache: Order;
@@ -85,7 +85,7 @@ export const INITIAL_STATE: State = {
userSuppliedOrderCache: undefined,
portalOnboardingStep: 0,
isPortalOnboardingShowing: false,
- hasPortalOnboardingBeenSeen: false,
+ hasPortalOnboardingBeenClosed: false,
// Docs
docsVersion: DEFAULT_DOCS_VERSION,
availableDocVersions: [DEFAULT_DOCS_VERSION],
@@ -311,7 +311,9 @@ export function reducer(state: State = INITIAL_STATE, action: Action): State {
return {
...state,
isPortalOnboardingShowing,
- hasPortalOnboardingBeenSeen: true,
+ hasPortalOnboardingBeenClosed: !isPortalOnboardingShowing ? true : state.hasPortalOnboardingBeenClosed,
+ // always start onboarding from the beginning
+ portalOnboardingStep: 0,
};
}
diff --git a/packages/website/ts/redux/store.ts b/packages/website/ts/redux/store.ts
index 203f068a1..2672e3f61 100644
--- a/packages/website/ts/redux/store.ts
+++ b/packages/website/ts/redux/store.ts
@@ -13,9 +13,11 @@ export const store: ReduxStore<State> = createStore(
);
store.subscribe(
_.throttle(() => {
+ const state = store.getState();
// Persisted state
stateStorage.saveState({
- hasPortalOnboardingBeenSeen: store.getState().hasPortalOnboardingBeenSeen,
+ hasPortalOnboardingBeenClosed: state.hasPortalOnboardingBeenClosed,
+ isPortalOnboardingShowing: state.isPortalOnboardingShowing,
});
}, ONE_SECOND),
);
diff --git a/packages/website/ts/style/colors.ts b/packages/website/ts/style/colors.ts
index 45be4fe7f..349845a09 100644
--- a/packages/website/ts/style/colors.ts
+++ b/packages/website/ts/style/colors.ts
@@ -6,13 +6,13 @@ const appColors = {
walletDefaultItemBackground: '#fbfbfc',
walletFocusedItemBackground: '#f0f1f4',
allowanceToggleShadow: 'rgba(0, 0, 0, 0)',
- allowanceToggleOffTrack: '#adadad',
- allowanceToggleOnTrack: sharedColors.mediumBlue,
wrapEtherConfirmationButton: sharedColors.mediumBlue,
drawerMenuBackground: '#4a4a4a',
menuItemDefaultSelectedBackground: '#424242',
jobsPageBackground: sharedColors.grey50,
jobsPageOpenPositionRow: sharedColors.grey100,
+ metaMaskOrange: '#f68c24',
+ metaMaskTransparentOrange: 'rgba(255, 248, 242, 0.8)',
};
export const colors = {
diff --git a/packages/website/ts/style/media.ts b/packages/website/ts/style/media.ts
new file mode 100644
index 000000000..3c992eb9f
--- /dev/null
+++ b/packages/website/ts/style/media.ts
@@ -0,0 +1,14 @@
+import { css } from 'ts/style/theme';
+import { ScreenWidths } from 'ts/types';
+
+const generateMediaWrapper = (screenWidth: ScreenWidths) => (...args: any[]) => css`
+ @media (max-width: ${screenWidth}) {
+ ${css.apply(css, args)};
+ }
+`;
+
+export const media = {
+ small: generateMediaWrapper(ScreenWidths.Sm),
+ medium: generateMediaWrapper(ScreenWidths.Md),
+ large: generateMediaWrapper(ScreenWidths.Lg),
+};
diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts
index 3211ddbd0..d9b2ef618 100644
--- a/packages/website/ts/types.ts
+++ b/packages/website/ts/types.ts
@@ -1,5 +1,6 @@
import { ECSignature } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
+import { Provider } from 'ethereum-types';
import * as React from 'react';
export enum Side {
@@ -215,10 +216,11 @@ export interface ContractEvent {
}
export type ValidatedBigNumberCallback = (isValid: boolean, amount?: BigNumber) => void;
+// Associated values are in `em` units
export enum ScreenWidths {
- Sm = 'SM',
- Md = 'MD',
- Lg = 'LG',
+ Sm = 40,
+ Md = 52,
+ Lg = 64,
}
export enum AlertTypes {
@@ -487,6 +489,8 @@ export enum Providers {
Parity = 'PARITY',
Metamask = 'METAMASK',
Mist = 'MIST',
+ Toshi = 'TOSHI',
+ Cipher = 'CIPHER',
}
export interface InjectedProviderUpdate {
@@ -564,4 +568,23 @@ export enum BrowserType {
Opera = 'Opera',
Other = 'Other',
}
+
+export enum AccountState {
+ Disconnected = 'Disconnected',
+ Ready = 'Ready',
+ Loading = 'Loading',
+ Locked = 'Locked',
+}
+
+export interface InjectedProvider extends Provider {
+ publicConfigStore?: InjectedProviderObservable;
+}
+
+// Minimal expected interface for an injected web3 object
+export interface InjectedWeb3 {
+ currentProvider: InjectedProvider;
+ version: {
+ getNetwork(cd: (err: Error, networkId: string) => void): void;
+ };
+}
// tslint:disable:max-file-line-count
diff --git a/packages/website/ts/utils/analytics.ts b/packages/website/ts/utils/analytics.ts
index 928e45bc3..f4bfa083f 100644
--- a/packages/website/ts/utils/analytics.ts
+++ b/packages/website/ts/utils/analytics.ts
@@ -1,8 +1,8 @@
import * as _ from 'lodash';
import * as ReactGA from 'react-ga';
+import { InjectedWeb3 } from 'ts/types';
import { configs } from 'ts/utils/configs';
import { utils } from 'ts/utils/utils';
-import * as Web3 from 'web3';
export const analytics = {
init(): void {
@@ -16,11 +16,12 @@ export const analytics = {
value,
});
},
- async logProviderAsync(web3IfExists: Web3): Promise<void> {
+ async logProviderAsync(web3IfExists: InjectedWeb3): Promise<void> {
await utils.onPageLoadAsync();
- const providerType = !_.isUndefined(web3IfExists)
- ? utils.getProviderType(web3IfExists.currentProvider)
- : 'NONE';
+ const providerType =
+ !_.isUndefined(web3IfExists) && !_.isUndefined(web3IfExists.currentProvider)
+ ? utils.getProviderType(web3IfExists.currentProvider)
+ : 'NONE';
ReactGA.ga('set', 'dimension1', providerType);
},
};
diff --git a/packages/website/ts/utils/configs.ts b/packages/website/ts/utils/configs.ts
index 2f89f8ccb..e8a486c35 100644
--- a/packages/website/ts/utils/configs.ts
+++ b/packages/website/ts/utils/configs.ts
@@ -11,7 +11,7 @@ const INFURA_API_KEY = 'T5WSC8cautR4KXyYgsRs';
export const configs = {
AMOUNT_DISPLAY_PRECSION: 5,
BACKEND_BASE_PROD_URL: 'https://website-api.0xproject.com',
- BACKEND_BASE_STAGING_URL: 'http://ec2-52-91-181-85.compute-1.amazonaws.com',
+ BACKEND_BASE_STAGING_URL: 'https://staging-website-api.0xproject.com',
BASE_URL,
BITLY_ACCESS_TOKEN: 'ffc4c1a31e5143848fb7c523b39f91b9b213d208',
DEFAULT_DERIVATION_PATH: `44'/60'/0'`,
diff --git a/packages/website/ts/utils/constants.ts b/packages/website/ts/utils/constants.ts
index a3f8eacb0..0c4b88780 100644
--- a/packages/website/ts/utils/constants.ts
+++ b/packages/website/ts/utils/constants.ts
@@ -6,7 +6,7 @@ export const constants = {
ETHER_TOKEN_SYMBOL: 'WETH',
ZRX_TOKEN_SYMBOL: 'ZRX',
ETHER_SYMBOL: 'ETH',
- TOKEN_AMOUNT_DISPLAY_PRECISION: 5,
+ TOKEN_AMOUNT_DISPLAY_PRECISION: 4,
GENESIS_ORDER_BLOCK_BY_NETWORK_ID: {
1: 4145578,
42: 3117574,
@@ -26,9 +26,11 @@ export const constants = {
NETWORK_ID_TESTRPC: 50,
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
PROVIDER_NAME_LEDGER: 'Ledger',
- PROVIDER_NAME_METAMASK: 'Metamask',
+ PROVIDER_NAME_METAMASK: 'MetaMask',
PROVIDER_NAME_PARITY_SIGNER: 'Parity Signer',
PROVIDER_NAME_MIST: 'Mist',
+ PROVIDER_NAME_CIPHER: 'Cipher Browser',
+ PROVIDER_NAME_TOSHI: 'Toshi',
PROVIDER_NAME_GENERIC: 'Injected Web3',
PROVIDER_NAME_PUBLIC: '0x Public',
ROLLBAR_ACCESS_TOKEN: 'a6619002b51c4464928201e6ea94de65',
@@ -38,6 +40,7 @@ export const constants = {
UNAVAILABLE_STATUS: 503,
TAKER_FEE: new BigNumber(0),
TESTNET_NAME: 'Kovan',
+ NUMERAL_USD_FORMAT: '$0,0.00',
PROJECT_URL_ETHFINEX: 'https://www.ethfinex.com/',
PROJECT_URL_AMADEUS: 'http://amadeusrelay.org',
PROJECT_URL_DDEX: 'https://ddex.io',
diff --git a/packages/website/ts/utils/utils.ts b/packages/website/ts/utils/utils.ts
index d12ae6a98..2cda41545 100644
--- a/packages/website/ts/utils/utils.ts
+++ b/packages/website/ts/utils/utils.ts
@@ -8,7 +8,10 @@ import * as bowser from 'bowser';
import deepEqual = require('deep-equal');
import * as _ from 'lodash';
import * as moment from 'moment';
+import * as numeral from 'numeral';
+
import {
+ AccountState,
BlockchainCallErrs,
BrowserType,
Environments,
@@ -26,9 +29,6 @@ import { configs } from 'ts/utils/configs';
import { constants } from 'ts/utils/constants';
import * as u2f from 'ts/vendor/u2f_api';
-const LG_MIN_EM = 64;
-const MD_MIN_EM = 52;
-
const isDogfood = (): boolean => _.includes(window.location.href, configs.DOMAIN_DOGFOOD);
export const utils = {
@@ -133,9 +133,9 @@ export const utils = {
// This logic mirrors the CSS media queries in BassCSS for the `lg-`, `md-` and `sm-` CSS
// class prefixes. Do not edit these.
- if (widthInEm > LG_MIN_EM) {
+ if (widthInEm > ScreenWidths.Lg) {
return ScreenWidths.Lg;
- } else if (widthInEm > MD_MIN_EM) {
+ } else if (widthInEm > ScreenWidths.Md) {
return ScreenWidths.Md;
} else {
return ScreenWidths.Sm;
@@ -192,23 +192,37 @@ export const utils = {
const truncatedAddress = `${address.substring(0, 6)}...${address.substr(-4)}`; // 0x3d5a...b287
return truncatedAddress;
},
- getReadableAccountState(
+ getReadableAccountState(accountState: AccountState, userAddress: string): string {
+ switch (accountState) {
+ case AccountState.Loading:
+ return 'Loading...';
+ case AccountState.Ready:
+ return utils.getAddressBeginAndEnd(userAddress);
+ case AccountState.Locked:
+ return 'Please Unlock';
+ case AccountState.Disconnected:
+ return 'Connect a Wallet';
+ default:
+ return '';
+ }
+ },
+ getAccountState(
isBlockchainReady: boolean,
providerType: ProviderType,
injectedProviderName: string,
userAddress?: string,
- ): string {
+ ): AccountState {
const isAddressAvailable = !_.isUndefined(userAddress) && !_.isEmpty(userAddress);
const isExternallyInjectedProvider = utils.isExternallyInjected(providerType, injectedProviderName);
if (!isBlockchainReady) {
- return 'Loading account';
+ return AccountState.Loading;
} else if (isAddressAvailable) {
- return utils.getAddressBeginAndEnd(userAddress);
+ return AccountState.Ready;
// tslint:disable-next-line: prefer-conditional-expression
} else if (isExternallyInjectedProvider) {
- return 'Account locked';
+ return AccountState.Locked;
} else {
- return 'No wallet detected';
+ return AccountState.Disconnected;
}
},
hasUniqueNameAndSymbol(tokens: Token[], token: Token): boolean {
@@ -309,6 +323,7 @@ export const utils = {
getProviderType(provider: Provider): Providers | string {
const constructorName = provider.constructor.name;
let parsedProviderName = constructorName;
+ // https://ethereum.stackexchange.com/questions/24266/elegant-way-to-detect-current-provider-int-web3-js
switch (constructorName) {
case 'EthereumProvider':
parsedProviderName = Providers.Mist;
@@ -322,6 +337,10 @@ export const utils = {
parsedProviderName = Providers.Parity;
} else if ((provider as any).isMetaMask) {
parsedProviderName = Providers.Metamask;
+ } else if (!_.isUndefined(_.get(window, 'SOFA'))) {
+ parsedProviderName = Providers.Toshi;
+ } else if (!_.isUndefined(_.get(window, '__CIPHER__'))) {
+ parsedProviderName = Providers.Cipher;
}
return parsedProviderName;
},
@@ -365,10 +384,20 @@ export const utils = {
},
getFormattedAmount(amount: BigNumber, decimals: number, symbol: string): string {
const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals);
- const precision = Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces());
- const formattedAmount = unitAmount.toFixed(precision);
+ // if the unit amount is less than 1, show the natural number of decimal places with a max of 4
+ // if the unit amount is greater than or equal to 1, show only 2 decimal places
+ const precision = unitAmount.lt(1)
+ ? Math.min(constants.TOKEN_AMOUNT_DISPLAY_PRECISION, unitAmount.decimalPlaces())
+ : 2;
+ const format = `0,0.${_.repeat('0', precision)}`;
+ const formattedAmount = numeral(unitAmount).format(format);
return `${formattedAmount} ${symbol}`;
},
+ getUsdValueFormattedAmount(amount: BigNumber, decimals: number, price: BigNumber): string {
+ const unitAmount = Web3Wrapper.toUnitAmount(amount, decimals);
+ const value = unitAmount.mul(price);
+ return numeral(value).format(constants.NUMERAL_USD_FORMAT);
+ },
openUrl(url: string): void {
window.open(url, '_blank');
},
diff --git a/packages/website/ts/utils/wallet_item_styles.ts b/packages/website/ts/utils/wallet_item_styles.ts
deleted file mode 100644
index 9d6033d74..000000000
--- a/packages/website/ts/utils/wallet_item_styles.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Styles } from '@0xproject/react-shared';
-
-import { colors } from 'ts/style/colors';
-
-export const styles: Styles = {
- focusedItem: {
- backgroundColor: colors.walletFocusedItemBackground,
- },
-};