aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/instant/src/components/animations/slide_animation.tsx2
-rw-r--r--packages/instant/src/components/buy_button.tsx1
-rw-r--r--packages/instant/src/components/buy_order_progress.tsx2
-rw-r--r--packages/instant/src/components/buy_order_state_buttons.tsx2
-rw-r--r--packages/instant/src/components/erc20_token_selector.tsx7
-rw-r--r--packages/instant/src/components/install_wallet_panel_content.tsx32
-rw-r--r--packages/instant/src/components/meta_mask_logo.tsx78
-rw-r--r--packages/instant/src/components/payment_method.tsx139
-rw-r--r--packages/instant/src/components/placing_order_button.tsx4
-rw-r--r--packages/instant/src/components/scaling_input.tsx2
-rw-r--r--packages/instant/src/components/secondary_button.tsx2
-rw-r--r--packages/instant/src/components/sliding_error.tsx3
-rw-r--r--packages/instant/src/components/sliding_panel.tsx22
-rw-r--r--packages/instant/src/components/standard_panel_content.tsx60
-rw-r--r--packages/instant/src/components/standard_sliding_panel.tsx29
-rw-r--r--packages/instant/src/components/timed_progress_bar.tsx46
-rw-r--r--packages/instant/src/components/ui/button.tsx33
-rw-r--r--packages/instant/src/components/ui/icon.tsx6
-rw-r--r--packages/instant/src/components/ui/text.tsx9
-rw-r--r--packages/instant/src/components/zero_ex_instant.tsx3
-rw-r--r--packages/instant/src/components/zero_ex_instant_container.tsx15
-rw-r--r--packages/instant/src/components/zero_ex_instant_overlay.tsx2
-rw-r--r--packages/instant/src/components/zero_ex_instant_provider.tsx7
-rw-r--r--packages/instant/src/constants.ts3
-rw-r--r--packages/instant/src/containers/connected_account_payment_method.ts59
-rw-r--r--packages/instant/src/containers/connected_buy_order_progress_or_payment_method.tsx36
-rw-r--r--packages/instant/src/containers/current_standard_sliding_panel.ts31
-rw-r--r--packages/instant/src/containers/latest_error.tsx3
-rw-r--r--packages/instant/src/containers/selected_erc20_asset_amount_input.ts12
-rw-r--r--packages/instant/src/redux/actions.ts7
-rw-r--r--packages/instant/src/redux/async_data.ts78
-rw-r--r--packages/instant/src/redux/reducer.ts37
-rw-r--r--packages/instant/src/types.ts12
-rw-r--r--packages/instant/src/util/asset.ts2
-rw-r--r--packages/instant/src/util/etherscan.ts3
-rw-r--r--packages/instant/src/util/heartbeater_factory.ts4
-rw-r--r--packages/instant/src/util/util.ts1
-rw-r--r--packages/instant/tslint.json3
38 files changed, 644 insertions, 153 deletions
diff --git a/packages/instant/src/components/animations/slide_animation.tsx b/packages/instant/src/components/animations/slide_animation.tsx
index 9adb1c674..5992bcba7 100644
--- a/packages/instant/src/components/animations/slide_animation.tsx
+++ b/packages/instant/src/components/animations/slide_animation.tsx
@@ -1,10 +1,10 @@
import * as React from 'react';
import { OptionallyScreenSpecific } from '../../style/media';
+import { SlideAnimationState } from '../../types';
import { PositionAnimation, PositionAnimationSettings } from './position_animation';
-export type SlideAnimationState = 'slidIn' | 'slidOut' | 'none';
export interface SlideAnimationProps {
animationState: SlideAnimationState;
slideInSettings: OptionallyScreenSpecific<PositionAnimationSettings>;
diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx
index 877ab275c..8b6121e43 100644
--- a/packages/instant/src/components/buy_button.tsx
+++ b/packages/instant/src/components/buy_button.tsx
@@ -43,7 +43,6 @@ export class BuyButton extends React.Component<BuyButtonProps> {
onClick={this._handleClick}
isDisabled={shouldDisableButton}
fontColor={ColorOption.white}
- fontSize="20px"
>
Buy
</Button>
diff --git a/packages/instant/src/components/buy_order_progress.tsx b/packages/instant/src/components/buy_order_progress.tsx
index bc7319423..6568de91b 100644
--- a/packages/instant/src/components/buy_order_progress.tsx
+++ b/packages/instant/src/components/buy_order_progress.tsx
@@ -12,7 +12,6 @@ export interface BuyOrderProgressProps {
export const BuyOrderProgress: React.StatelessComponent<BuyOrderProgressProps> = props => {
const { buyOrderState } = props;
-
if (
buyOrderState.processState === OrderProcessState.Processing ||
buyOrderState.processState === OrderProcessState.Success ||
@@ -30,6 +29,5 @@ export const BuyOrderProgress: React.StatelessComponent<BuyOrderProgressProps> =
</Container>
);
}
-
return null;
};
diff --git a/packages/instant/src/components/buy_order_state_buttons.tsx b/packages/instant/src/components/buy_order_state_buttons.tsx
index 6041bf4f5..e563bec73 100644
--- a/packages/instant/src/components/buy_order_state_buttons.tsx
+++ b/packages/instant/src/components/buy_order_state_buttons.tsx
@@ -35,7 +35,7 @@ export const BuyOrderStateButtons: React.StatelessComponent<BuyOrderStateButtonP
if (props.buyOrderProcessingState === OrderProcessState.Failure) {
return (
<Flex justify="space-between">
- <Button width="48%" onClick={props.onRetry} fontColor={ColorOption.white} fontSize="16px">
+ <Button width="48%" onClick={props.onRetry} fontColor={ColorOption.white}>
Back
</Button>
<SecondaryButton width="48%" onClick={props.onViewTransaction}>
diff --git a/packages/instant/src/components/erc20_token_selector.tsx b/packages/instant/src/components/erc20_token_selector.tsx
index 3503ff31a..d4a77c278 100644
--- a/packages/instant/src/components/erc20_token_selector.tsx
+++ b/packages/instant/src/components/erc20_token_selector.tsx
@@ -29,13 +29,18 @@ export class ERC20TokenSelector extends React.Component<ERC20TokenSelectorProps>
const { tokens, onTokenSelect } = this.props;
return (
<Container height="100%">
+ <Container marginBottom="10px">
+ <Text fontColor={ColorOption.darkGrey} fontSize="18px" fontWeight="600" lineHeight="22px">
+ Select Token
+ </Text>
+ </Container>
<SearchInput
placeholder="Search tokens..."
width="100%"
value={this.state.searchQuery}
onChange={this._handleSearchInputChange}
/>
- <Container overflow="scroll" height="calc(100% - 80px)" marginTop="10px">
+ <Container overflow="scroll" height="calc(100% - 90px)" marginTop="10px">
{_.map(tokens, token => {
if (!this._isTokenQueryMatch(token)) {
return null;
diff --git a/packages/instant/src/components/install_wallet_panel_content.tsx b/packages/instant/src/components/install_wallet_panel_content.tsx
new file mode 100644
index 000000000..546874212
--- /dev/null
+++ b/packages/instant/src/components/install_wallet_panel_content.tsx
@@ -0,0 +1,32 @@
+import * as React from 'react';
+
+import { META_MASK_CHROME_STORE_URL, META_MASK_SITE_URL } from '../constants';
+import { ColorOption } from '../style/theme';
+
+import { MetaMaskLogo } from './meta_mask_logo';
+import { StandardPanelContent } from './standard_panel_content';
+import { Button } from './ui/button';
+
+export interface InstallWalletPanelContentProps {}
+
+export const InstallWalletPanelContent: React.StatelessComponent<InstallWalletPanelContentProps> = () => (
+ <StandardPanelContent
+ image={<MetaMaskLogo width={85} height={80} />}
+ title="Install MetaMask"
+ description="Please install the MetaMask wallet extension from the Chrome Store."
+ moreInfoSettings={{
+ href: META_MASK_SITE_URL,
+ text: 'What is MetaMask?',
+ }}
+ action={
+ <Button
+ href={META_MASK_CHROME_STORE_URL}
+ width="100%"
+ fontColor={ColorOption.white}
+ backgroundColor={ColorOption.darkOrange}
+ >
+ Get Chrome Extension
+ </Button>
+ }
+ />
+);
diff --git a/packages/instant/src/components/meta_mask_logo.tsx b/packages/instant/src/components/meta_mask_logo.tsx
new file mode 100644
index 000000000..d1ad10c23
--- /dev/null
+++ b/packages/instant/src/components/meta_mask_logo.tsx
@@ -0,0 +1,78 @@
+import * as React from 'react';
+
+export interface MetaMaskLogoProps {
+ width?: number;
+ height?: number;
+}
+
+export const MetaMaskLogo: React.StatelessComponent<MetaMaskLogoProps> = ({ width, height }) => (
+ <svg width={width} height={height} viewBox="0 0 85 80" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path d="M80.578 0L47.7107 24.8648L53.542 10.2702L80.578 0Z" fill="#E2761B" />
+ <path d="M4.24075 0L37.1081 25.4053L31.2768 10.2702L4.24075 0Z" fill="#E4761B" />
+ <path d="M68.9152 57.8379L59.9032 71.8919L78.9874 77.2973L84.2886 58.3785L68.9152 57.8379Z" fill="#E4761B" />
+ <path d="M0.53006 58.3785L5.83124 77.2973L24.9155 71.8919L15.9035 57.8379L0.53006 58.3785Z" fill="#E4761B" />
+ <path d="M23.8552 34.5941L18.554 42.7022L37.1082 43.7833L36.5781 23.2428L23.8552 34.5941Z" fill="#E4761B" />
+ <path d="M60.9635 34.5941L47.7106 23.2428V43.7833L66.2647 42.7022L60.9635 34.5941Z" fill="#E4761B" />
+ <path d="M24.9156 71.8914L36.0481 66.4861L26.5059 58.378L24.9156 71.8914Z" fill="#E4761B" />
+ <path d="M48.7709 66.4861L59.9034 71.8914L58.313 58.378L48.7709 66.4861Z" fill="#E4761B" />
+ <path d="M59.9034 71.8919L48.7709 66.4865L49.301 73.5135V76.7567L59.9034 71.8919Z" fill="#D7C1B3" />
+ <path d="M24.9157 71.892L35.518 76.7568V73.5136L36.0482 66.4866L24.9157 71.892Z" fill="#D7C1B3" />
+ <path d="M35.5179 53.5138L25.9758 50.8111L32.8673 47.5678L35.5179 53.5138Z" fill="#233447" />
+ <path d="M49.3009 53.5138L51.9515 47.5678L58.843 50.8111L49.3009 53.5138Z" fill="#233447" />
+ <path d="M24.9155 71.892L26.5059 57.838L15.9035 58.3785L24.9155 71.892Z" fill="#CD6116" />
+ <path d="M58.313 57.838L59.9034 71.892L68.9154 58.3785L58.313 57.838Z" fill="#CD6116" />
+ <path
+ d="M66.2648 42.7025L47.7106 43.7836L49.301 53.5132L51.9516 47.5673L58.8431 50.8106L66.2648 42.7025Z"
+ fill="#CD6116"
+ />
+ <path
+ d="M25.9758 50.8106L32.8673 47.5673L35.5179 53.5132L37.1083 43.7836L18.5541 42.7025L25.9758 50.8106Z"
+ fill="#CD6116"
+ />
+ <path d="M18.5541 42.7024L26.5059 58.378L25.9758 50.8105L18.5541 42.7024Z" fill="#E4751F" />
+ <path d="M58.8431 50.8106L58.313 58.3781L66.2647 42.7025L58.8431 50.8106Z" fill="#E4751F" />
+ <path d="M37.1083 43.7838L35.518 53.5135L37.6384 65.4053L38.1686 49.7297L37.1083 43.7838Z" fill="#E4751F" />
+ <path d="M47.7105 43.7838L46.6503 49.7297L47.1804 65.4053L49.3009 53.5135L47.7105 43.7838Z" fill="#E4751F" />
+ <path
+ d="M49.301 53.5134L47.1805 65.4052L48.7709 66.4863L58.313 58.3782L58.8431 50.8107L49.301 53.5134Z"
+ fill="#F6851B"
+ />
+ <path
+ d="M25.9758 50.8107L26.5059 58.3782L36.048 66.4863L37.6384 65.4052L35.5179 53.5134L25.9758 50.8107Z"
+ fill="#F6851B"
+ />
+ <path
+ d="M49.3011 76.7568V73.5135L48.771 72.973H36.0482L35.518 73.5135V76.7568L24.9157 71.8919L28.6265 75.1351L36.0482 80H48.771L56.1927 75.1351L59.9035 71.8919L49.3011 76.7568Z"
+ fill="#C0AD9E"
+ />
+ <path
+ d="M48.771 66.486L47.1806 65.405H37.6385L36.0482 66.486L35.518 73.513L36.0482 72.9725H48.771L49.3011 73.513L48.771 66.486Z"
+ fill="#161616"
+ />
+ <path
+ d="M82.1685 26.4864L84.8191 12.9729L80.5781 0L48.771 24.3242L60.9637 34.5945L78.4576 39.9998L82.1685 35.6755L80.5781 34.0539L83.2287 31.8918L81.1082 30.2702L83.7588 28.108L82.1685 26.4864Z"
+ fill="#763D16"
+ />
+ <path
+ d="M0 12.9729L2.65059 26.4864L1.06024 28.108L3.71083 30.2702L1.59036 31.8918L4.24095 34.0539L2.65059 35.6755L6.36142 39.9998L23.8553 34.5945L36.0481 24.3242L4.24095 0L0 12.9729Z"
+ fill="#763D16"
+ />
+ <path
+ d="M78.4575 39.9993L60.9636 34.5939L66.2648 42.702L58.313 58.3776H68.9154H84.2888L78.4575 39.9993Z"
+ fill="#F6851B"
+ />
+ <path
+ d="M23.8554 34.5939L6.36147 39.9993L0.530167 58.3776H15.9036H26.506L18.5542 42.702L23.8554 34.5939Z"
+ fill="#F6851B"
+ />
+ <path
+ d="M47.7106 43.7833L48.7709 24.3239L53.5419 10.2699H31.2769L36.048 24.3239L37.1083 43.7833L37.6384 49.7292V65.4048H47.1805V49.7292L47.7106 43.7833Z"
+ fill="#F6851B"
+ />
+ </svg>
+);
+
+MetaMaskLogo.defaultProps = {
+ width: 85,
+ height: 80,
+};
diff --git a/packages/instant/src/components/payment_method.tsx b/packages/instant/src/components/payment_method.tsx
index 8c0b47d72..49ec22164 100644
--- a/packages/instant/src/components/payment_method.tsx
+++ b/packages/instant/src/components/payment_method.tsx
@@ -1,45 +1,132 @@
-import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import * as React from 'react';
import { ColorOption } from '../style/theme';
-import { Network } from '../types';
+import { Account, AccountState, Network } from '../types';
+import { MetaMaskLogo } from './meta_mask_logo';
import { PaymentMethodDropdown } from './payment_method_dropdown';
import { Circle } from './ui/circle';
import { Container } from './ui/container';
import { Flex } from './ui/flex';
+import { Icon } from './ui/icon';
import { Text } from './ui/text';
-export interface PaymentMethodProps {}
+export interface PaymentMethodProps {
+ account: Account;
+ network: Network;
+ onInstallWalletClick: () => void;
+ onUnlockWalletClick: () => void;
+}
-export const PaymentMethod: React.StatelessComponent<PaymentMethodProps> = () => (
- <Container padding="20px" width="100%">
- <Container marginBottom="10px">
- <Flex justify="space-between">
- <Text
- letterSpacing="1px"
- fontColor={ColorOption.primaryColor}
- fontWeight={600}
- textTransform="uppercase"
- fontSize="14px"
- >
- Payment Method
- </Text>
- <Flex>
- <Circle color={ColorOption.green} diameter={8} />
+export class PaymentMethod extends React.Component<PaymentMethodProps> {
+ public render(): React.ReactNode {
+ return (
+ <Container padding="20px" width="100%">
+ <Container marginBottom="12px">
+ <Flex justify="space-between">
+ <Text
+ letterSpacing="1px"
+ fontColor={ColorOption.primaryColor}
+ fontWeight={600}
+ textTransform="uppercase"
+ fontSize="14px"
+ >
+ {this._renderTitleText()}
+ </Text>
+ <Flex>{this._renderTitleLabel()}</Flex>
+ </Flex>
+ </Container>
+ {this._renderMainContent()}
+ </Container>
+ );
+ }
+ private readonly _renderTitleText = (): string => {
+ const { account } = this.props;
+ switch (account.state) {
+ case AccountState.Loading:
+ return 'loading...';
+ case AccountState.Locked:
+ case AccountState.None:
+ return 'connect your wallet';
+ case AccountState.Ready:
+ return 'payment method';
+ }
+ };
+ private readonly _renderTitleLabel = (): React.ReactNode => {
+ const { account } = this.props;
+ if (account.state === AccountState.Ready || account.state === AccountState.Locked) {
+ const circleColor: ColorOption = account.state === AccountState.Ready ? ColorOption.green : ColorOption.red;
+ return (
+ <React.Fragment>
+ <Circle diameter={8} color={circleColor} />
<Container marginLeft="3px">
<Text fontColor={ColorOption.darkGrey} fontSize="12px">
MetaMask
</Text>
</Container>
- </Flex>
- </Flex>
- </Container>
- <PaymentMethodDropdown
- accountAddress="0xa1b2c3d4e5f6g7h8j9k10"
- accountEthBalanceInWei={new BigNumber(10500000000000000000)}
- network={Network.Mainnet}
- />
+ </React.Fragment>
+ );
+ }
+ return null;
+ };
+ private readonly _renderMainContent = (): React.ReactNode => {
+ const { account, network } = this.props;
+ switch (account.state) {
+ case AccountState.Loading:
+ // Just take up the same amount of space as the other states.
+ return <Container height="52px" />;
+ case AccountState.Locked:
+ return (
+ <WalletPrompt
+ onClick={this.props.onUnlockWalletClick}
+ image={<Icon width={13} icon="lock" color={ColorOption.black} />}
+ >
+ Please Unlock MetaMask
+ </WalletPrompt>
+ );
+ case AccountState.None:
+ return (
+ <WalletPrompt
+ onClick={this.props.onInstallWalletClick}
+ image={<MetaMaskLogo width={19} height={18} />}
+ >
+ Install MetaMask
+ </WalletPrompt>
+ );
+ case AccountState.Ready:
+ return (
+ <PaymentMethodDropdown
+ accountAddress={account.address}
+ accountEthBalanceInWei={account.ethBalanceInWei}
+ network={network}
+ />
+ );
+ }
+ };
+}
+
+interface WalletPromptProps {
+ image: React.ReactNode;
+ onClick?: () => void;
+}
+
+const WalletPrompt: React.StatelessComponent<WalletPromptProps> = ({ onClick, image, children }) => (
+ <Container
+ padding="14.5px"
+ border={`1px solid ${ColorOption.darkOrange}`}
+ backgroundColor={ColorOption.lightOrange}
+ width="100%"
+ borderRadius="4px"
+ onClick={onClick}
+ cursor={onClick ? 'pointer' : undefined}
+ boxShadowOnHover={!!onClick}
+ >
+ <Flex>
+ <Container marginRight="10px">{image}</Container>
+ <Text fontSize="16px" fontColor={ColorOption.darkOrange}>
+ {children}
+ </Text>
+ </Flex>
</Container>
);
diff --git a/packages/instant/src/components/placing_order_button.tsx b/packages/instant/src/components/placing_order_button.tsx
index d774d7d27..2516b90b1 100644
--- a/packages/instant/src/components/placing_order_button.tsx
+++ b/packages/instant/src/components/placing_order_button.tsx
@@ -7,9 +7,9 @@ import { Container } from './ui/container';
import { Spinner } from './ui/spinner';
export const PlacingOrderButton: React.StatelessComponent<{}> = props => (
- <Button isDisabled={true} width="100%" fontColor={ColorOption.white} fontSize="20px">
+ <Button isDisabled={true} width="100%" fontColor={ColorOption.white}>
<Container display="inline-block" position="relative" top="3px" marginRight="8px">
- <Spinner widthPx={20} heightPx={20} />
+ <Spinner widthPx={16} heightPx={16} />
</Container>
Placing Order&hellip;
</Button>
diff --git a/packages/instant/src/components/scaling_input.tsx b/packages/instant/src/components/scaling_input.tsx
index 1abadb78b..e1599a316 100644
--- a/packages/instant/src/components/scaling_input.tsx
+++ b/packages/instant/src/components/scaling_input.tsx
@@ -156,8 +156,6 @@ export class ScalingInput extends React.Component<ScalingInputProps, ScalingInpu
return `${width}px`;
}
return `${textLengthThreshold}ch`;
- default:
- return '1ch';
}
};
private readonly _calculateFontSize = (phase: ScalingInputPhase): number => {
diff --git a/packages/instant/src/components/secondary_button.tsx b/packages/instant/src/components/secondary_button.tsx
index df0539606..705390e28 100644
--- a/packages/instant/src/components/secondary_button.tsx
+++ b/packages/instant/src/components/secondary_button.tsx
@@ -15,8 +15,6 @@ export const SecondaryButton: React.StatelessComponent<SecondaryButtonProps> = p
borderColor={ColorOption.lightGrey}
width={props.width}
onClick={props.onClick}
- fontColor={ColorOption.primaryColor}
- fontSize="16px"
{...buttonProps}
>
{props.children}
diff --git a/packages/instant/src/components/sliding_error.tsx b/packages/instant/src/components/sliding_error.tsx
index a8d4e391c..b59e2a905 100644
--- a/packages/instant/src/components/sliding_error.tsx
+++ b/packages/instant/src/components/sliding_error.tsx
@@ -3,9 +3,10 @@ import * as React from 'react';
import { ScreenSpecification } from '../style/media';
import { ColorOption } from '../style/theme';
import { zIndex } from '../style/z_index';
+import { SlideAnimationState } from '../types';
import { PositionAnimationSettings } from './animations/position_animation';
-import { SlideAnimation, SlideAnimationState } from './animations/slide_animation';
+import { SlideAnimation } from './animations/slide_animation';
import { Container } from './ui/container';
import { Flex } from './ui/flex';
diff --git a/packages/instant/src/components/sliding_panel.tsx b/packages/instant/src/components/sliding_panel.tsx
index 9d16f9560..7f9037049 100644
--- a/packages/instant/src/components/sliding_panel.tsx
+++ b/packages/instant/src/components/sliding_panel.tsx
@@ -2,35 +2,25 @@ import * as React from 'react';
import { ColorOption } from '../style/theme';
import { zIndex } from '../style/z_index';
+import { SlideAnimationState } from '../types';
import { PositionAnimationSettings } from './animations/position_animation';
-import { SlideAnimation, SlideAnimationState } from './animations/slide_animation';
+import { SlideAnimation } from './animations/slide_animation';
import { Container } from './ui/container';
import { Flex } from './ui/flex';
import { Icon } from './ui/icon';
-import { Text } from './ui/text';
export interface PanelProps {
- title?: string;
onClose?: () => void;
}
-export const Panel: React.StatelessComponent<PanelProps> = ({ title, children, onClose }) => (
+export const Panel: React.StatelessComponent<PanelProps> = ({ children, onClose }) => (
<Container backgroundColor={ColorOption.white} width="100%" height="100%" zIndex={zIndex.panel} padding="20px">
- <Flex justify="space-between">
- {title && (
- <Container marginTop="3px">
- <Text fontColor={ColorOption.darkGrey} fontSize="18px" fontWeight="600" lineHeight="22px">
- {title}
- </Text>
- </Container>
- )}
- <Container position="relative" bottom="7px">
- <Icon width={12} color={ColorOption.lightGrey} icon="closeX" onClick={onClose} />
- </Container>
+ <Flex justify="flex-end">
+ <Icon padding="5px" width={12} color={ColorOption.lightGrey} icon="closeX" onClick={onClose} />
</Flex>
- <Container marginTop="10px" height="100%">
+ <Container position="relative" top="-10px" height="100%">
{children}
</Container>
</Container>
diff --git a/packages/instant/src/components/standard_panel_content.tsx b/packages/instant/src/components/standard_panel_content.tsx
new file mode 100644
index 000000000..89e4da70c
--- /dev/null
+++ b/packages/instant/src/components/standard_panel_content.tsx
@@ -0,0 +1,60 @@
+import * as React from 'react';
+
+import { ColorOption } from '../style/theme';
+
+import { Container } from './ui/container';
+import { Flex } from './ui/flex';
+import { Text } from './ui/text';
+
+export interface MoreInfoSettings {
+ text: string;
+ href: string;
+}
+
+export interface StandardPanelContentProps {
+ image: React.ReactNode;
+ title: string;
+ description: string;
+ moreInfoSettings?: MoreInfoSettings;
+ action: React.ReactNode;
+}
+
+const SPACING_BETWEEN_PX = '20px';
+
+export const StandardPanelContent: React.StatelessComponent<StandardPanelContentProps> = ({
+ image,
+ title,
+ description,
+ moreInfoSettings,
+ action,
+}) => (
+ <Container height="100%">
+ <Flex direction="column" height="calc(100% - 58px)">
+ <Container marginBottom={SPACING_BETWEEN_PX}>{image}</Container>
+ <Container marginBottom={SPACING_BETWEEN_PX}>
+ <Text fontSize="20px" fontWeight={700} fontColor={ColorOption.black}>
+ {title}
+ </Text>
+ </Container>
+ <Container marginBottom={SPACING_BETWEEN_PX}>
+ <Text fontSize="14px" fontColor={ColorOption.grey} center={true}>
+ {description}
+ </Text>
+ </Container>
+ <Container marginBottom={SPACING_BETWEEN_PX}>
+ {moreInfoSettings && (
+ <Text
+ center={true}
+ fontSize="13px"
+ textDecorationLine="underline"
+ fontColor={ColorOption.lightGrey}
+ href={moreInfoSettings.href}
+ >
+ {moreInfoSettings.text}
+ </Text>
+ )}
+ </Container>
+ </Flex>
+ <Container>{action}</Container>
+ </Container>
+);
diff --git a/packages/instant/src/components/standard_sliding_panel.tsx b/packages/instant/src/components/standard_sliding_panel.tsx
new file mode 100644
index 000000000..f587ff79a
--- /dev/null
+++ b/packages/instant/src/components/standard_sliding_panel.tsx
@@ -0,0 +1,29 @@
+import * as React from 'react';
+
+import { SlideAnimationState, StandardSlidingPanelContent, StandardSlidingPanelSettings } from '../types';
+
+import { InstallWalletPanelContent } from './install_wallet_panel_content';
+import { SlidingPanel } from './sliding_panel';
+
+export interface StandardSlidingPanelProps extends StandardSlidingPanelSettings {
+ onClose: () => void;
+}
+
+export class StandardSlidingPanel extends React.Component<StandardSlidingPanelProps> {
+ public render(): React.ReactNode {
+ const { animationState, content, onClose } = this.props;
+ return (
+ <SlidingPanel animationState={animationState} onClose={onClose}>
+ {this._getNodeForContent(content)}
+ </SlidingPanel>
+ );
+ }
+ private readonly _getNodeForContent = (content: StandardSlidingPanelContent): React.ReactNode => {
+ switch (content) {
+ case StandardSlidingPanelContent.InstallWallet:
+ return <InstallWalletPanelContent />;
+ case StandardSlidingPanelContent.None:
+ return null;
+ }
+ };
+}
diff --git a/packages/instant/src/components/timed_progress_bar.tsx b/packages/instant/src/components/timed_progress_bar.tsx
index 59aaa33a1..8465b9cd0 100644
--- a/packages/instant/src/components/timed_progress_bar.tsx
+++ b/packages/instant/src/components/timed_progress_bar.tsx
@@ -2,7 +2,7 @@ import * as _ from 'lodash';
import * as React from 'react';
import { PROGRESS_FINISH_ANIMATION_TIME_MS, PROGRESS_STALL_AT_WIDTH } from '../constants';
-import { ColorOption, keyframes, styled } from '../style/theme';
+import { ColorOption, css, keyframes, styled } from '../style/theme';
import { Container } from './ui/container';
@@ -20,15 +20,11 @@ export class TimedProgressBar extends React.Component<TimedProgressBarProps, {}>
private readonly _barRef = React.createRef<HTMLDivElement>();
public render(): React.ReactNode {
- const timedProgressProps = this._calculateTimedProgressProps();
- return (
- <Container width="100%" backgroundColor={ColorOption.lightGrey} borderRadius="6px">
- <TimedProgress {...timedProgressProps} ref={this._barRef as any} />
- </Container>
- );
+ const widthAnimationSettings = this._calculateWidthAnimationSettings();
+ return <ProgressBar animationSettings={widthAnimationSettings} ref={this._barRef} />;
}
- private _calculateTimedProgressProps(): TimedProgressProps {
+ private _calculateWidthAnimationSettings(): WidthAnimationSettings {
if (this.props.hasEnded) {
if (!this._barRef.current) {
throw new Error('ended but no reference');
@@ -60,21 +56,45 @@ const expandingWidthKeyframes = (fromWidth: string, toWidth: string) => {
`;
};
-interface TimedProgressProps {
+export interface WidthAnimationSettings {
timeMs: number;
fromWidth: string;
toWidth: string;
}
-export const TimedProgress =
+interface ProgressProps {
+ width?: string;
+ animationSettings?: WidthAnimationSettings;
+}
+
+export const Progress =
styled.div <
- TimedProgressProps >
+ ProgressProps >
`
&& {
background-color: ${props => props.theme[ColorOption.primaryColor]};
border-radius: 6px;
height: 6px;
- animation: ${props => expandingWidthKeyframes(props.fromWidth, props.toWidth)}
- ${props => props.timeMs}ms linear 1 forwards;
+ ${props => (props.width ? `width: ${props.width};` : '')}
+ ${props =>
+ props.animationSettings
+ ? css`
+ animation: ${expandingWidthKeyframes(
+ props.animationSettings.fromWidth,
+ props.animationSettings.toWidth,
+ )}
+ ${props.animationSettings.timeMs}ms linear 1 forwards;
+ `
+ : ''}
}
`;
+
+export interface ProgressBarProps extends ProgressProps {}
+
+export const ProgressBar: React.ComponentType<ProgressBarProps & React.ClassAttributes<{}>> = React.forwardRef(
+ (props, ref) => (
+ <Container width="100%" backgroundColor={ColorOption.lightGrey} borderRadius="6px">
+ <Progress {...props} ref={ref as any} />
+ </Container>
+ ),
+);
diff --git a/packages/instant/src/components/ui/button.tsx b/packages/instant/src/components/ui/button.tsx
index b90221bf4..e77b1b5d1 100644
--- a/packages/instant/src/components/ui/button.tsx
+++ b/packages/instant/src/components/ui/button.tsx
@@ -2,6 +2,9 @@ import { darken, saturate } from 'polished';
import * as React from 'react';
import { ColorOption, styled } from '../../style/theme';
+import { util } from '../../util/util';
+
+export type ButtonOnClickHandler = (event: React.MouseEvent<HTMLElement>) => void;
export interface ButtonProps {
backgroundColor?: ColorOption;
@@ -12,15 +15,26 @@ export interface ButtonProps {
padding?: string;
type?: string;
isDisabled?: boolean;
- onClick?: (event: React.MouseEvent<HTMLElement>) => void;
+ href?: string;
+ onClick?: ButtonOnClickHandler;
className?: string;
}
-const PlainButton: React.StatelessComponent<ButtonProps> = ({ children, isDisabled, onClick, type, className }) => (
- <button type={type} className={className} onClick={isDisabled ? undefined : onClick} disabled={isDisabled}>
- {children}
- </button>
-);
+const PlainButton: React.StatelessComponent<ButtonProps> = ({
+ children,
+ isDisabled,
+ onClick,
+ href,
+ type,
+ className,
+}) => {
+ const computedOnClick = isDisabled ? undefined : href ? util.createOpenUrlInNewWindow(href) : onClick;
+ return (
+ <button type={type} className={className} onClick={computedOnClick} disabled={isDisabled}>
+ {children}
+ </button>
+ );
+};
const darkenOnHoverAmount = 0.1;
const darkenOnActiveAmount = 0.2;
@@ -31,7 +45,7 @@ export const Button = styled(PlainButton)`
box-sizing: border-box;
font-size: ${props => props.fontSize};
font-family: 'Inter UI', sans-serif;
- font-weight: 600;
+ font-weight: 500;
color: ${props => props.fontColor && props.theme[props.fontColor]};
cursor: ${props => (props.isDisabled ? 'default' : 'pointer')};
transition: background-color, opacity 0.5s ease;
@@ -64,11 +78,10 @@ export const Button = styled(PlainButton)`
Button.defaultProps = {
backgroundColor: ColorOption.primaryColor,
- borderColor: ColorOption.primaryColor,
width: 'auto',
isDisabled: false,
- padding: '.6em 1.2em',
- fontSize: '15px',
+ padding: '.82em 1.2em',
+ fontSize: '16px',
};
Button.displayName = 'Button';
diff --git a/packages/instant/src/components/ui/icon.tsx b/packages/instant/src/components/ui/icon.tsx
index a88fa87dd..811142b5b 100644
--- a/packages/instant/src/components/ui/icon.tsx
+++ b/packages/instant/src/components/ui/icon.tsx
@@ -20,6 +20,7 @@ interface IconInfoMapping {
success: IconInfo;
chevron: IconInfo;
search: IconInfo;
+ lock: IconInfo;
}
const ICONS: IconInfoMapping = {
closeX: {
@@ -58,6 +59,11 @@ const ICONS: IconInfoMapping = {
path:
'M8.39404 5.19727C8.39404 6.96289 6.96265 8.39453 5.19702 8.39453C3.4314 8.39453 2 6.96289 2 5.19727C2 3.43164 3.4314 2 5.19702 2C6.96265 2 8.39404 3.43164 8.39404 5.19727ZM8.09668 9.51074C7.26855 10.0684 6.27075 10.3945 5.19702 10.3945C2.3269 10.3945 0 8.06738 0 5.19727C0 2.32715 2.3269 0 5.19702 0C8.06738 0 10.394 2.32715 10.394 5.19727C10.394 6.27051 10.0686 7.26855 9.51074 8.09668L13.6997 12.2861L12.2854 13.7002L8.09668 9.51074Z',
},
+ lock: {
+ viewBox: '0 0 13 16',
+ path:
+ 'M6.47619 0C3.79509 0 1.60489 2.21216 1.60489 4.92014V6.33135C0.717479 6.33135 0 7.05602 0 7.95232V14.379C0 15.2753 0.717479 16 1.60489 16H11.3475C12.2349 16 12.9524 15.2753 12.9524 14.379V7.95232C12.9524 7.05602 12.2349 6.33135 11.3475 6.33135V4.92014C11.3475 2.21216 9.1573 0 6.47619 0ZM9.6482 6.33135H3.30418V4.92014C3.30418 3.16567 4.72026 1.71633 6.47619 1.71633C8.23213 1.71633 9.6482 3.16567 9.6482 4.92014V6.33135Z',
+ },
};
export interface IconProps {
diff --git a/packages/instant/src/components/ui/text.tsx b/packages/instant/src/components/ui/text.tsx
index 4fe429d25..fd14cc4d1 100644
--- a/packages/instant/src/components/ui/text.tsx
+++ b/packages/instant/src/components/ui/text.tsx
@@ -2,6 +2,7 @@ import { darken } from 'polished';
import * as React from 'react';
import { ColorOption, styled } from '../../style/theme';
+import { util } from '../../util/util';
export interface TextProps {
fontColor?: ColorOption;
@@ -20,10 +21,16 @@ export interface TextProps {
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
noWrap?: boolean;
display?: string;
+ href?: string;
}
+export const Text: React.StatelessComponent<TextProps> = ({ href, onClick, ...rest }) => {
+ const computedOnClick = href ? util.createOpenUrlInNewWindow(href) : onClick;
+ return <StyledText {...rest} onClick={computedOnClick} />;
+};
+
const darkenOnHoverAmount = 0.3;
-export const Text =
+export const StyledText =
styled.div <
TextProps >
`
diff --git a/packages/instant/src/components/zero_ex_instant.tsx b/packages/instant/src/components/zero_ex_instant.tsx
index b945f9908..2267b4dbf 100644
--- a/packages/instant/src/components/zero_ex_instant.tsx
+++ b/packages/instant/src/components/zero_ex_instant.tsx
@@ -1,8 +1,9 @@
import * as React from 'react';
+import { ZeroExInstantContainer } from '../components/zero_ex_instant_container';
+
import { INJECTED_DIV_CLASS } from '../constants';
-import { ZeroExInstantContainer } from './zero_ex_instant_container';
import { ZeroExInstantProvider, ZeroExInstantProviderProps } from './zero_ex_instant_provider';
export type ZeroExInstantProps = ZeroExInstantProviderProps;
diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx
index 5748e064e..c0a197590 100644
--- a/packages/instant/src/components/zero_ex_instant_container.tsx
+++ b/packages/instant/src/components/zero_ex_instant_container.tsx
@@ -1,26 +1,29 @@
import * as React from 'react';
import { AvailableERC20TokenSelector } from '../containers/available_erc20_token_selector';
+import { ConnectedBuyOrderProgressOrPaymentMethod } from '../containers/connected_buy_order_progress_or_payment_method';
+import { CurrentStandardSlidingPanel } from '../containers/current_standard_sliding_panel';
import { LatestBuyQuoteOrderDetails } from '../containers/latest_buy_quote_order_details';
import { LatestError } from '../containers/latest_error';
-import { SelectedAssetBuyOrderProgress } from '../containers/selected_asset_buy_order_progress';
import { SelectedAssetBuyOrderStateButtons } from '../containers/selected_asset_buy_order_state_buttons';
import { SelectedAssetInstantHeading } from '../containers/selected_asset_instant_heading';
import { ColorOption } from '../style/theme';
import { zIndex } from '../style/z_index';
+import { OrderProcessState, SlideAnimationState } from '../types';
-import { SlideAnimationState } from './animations/slide_animation';
import { CSSReset } from './css_reset';
import { SlidingPanel } from './sliding_panel';
import { Container } from './ui/container';
import { Flex } from './ui/flex';
-export interface ZeroExInstantContainerProps {}
+export interface ZeroExInstantContainerProps {
+ orderProcessState: OrderProcessState;
+}
export interface ZeroExInstantContainerState {
tokenSelectionPanelAnimationState: SlideAnimationState;
}
-export class ZeroExInstantContainer extends React.Component<ZeroExInstantContainerProps, ZeroExInstantContainerState> {
+export class ZeroExInstantContainer extends React.Component<{}, ZeroExInstantContainerState> {
public state = {
tokenSelectionPanelAnimationState: 'none' as SlideAnimationState,
};
@@ -47,19 +50,19 @@ export class ZeroExInstantContainer extends React.Component<ZeroExInstantContain
>
<Flex direction="column" justify="flex-start" height="100%">
<SelectedAssetInstantHeading onSelectAssetClick={this._handleSymbolClick} />
- <SelectedAssetBuyOrderProgress />
+ <ConnectedBuyOrderProgressOrPaymentMethod />
<LatestBuyQuoteOrderDetails />
<Container padding="20px" width="100%">
<SelectedAssetBuyOrderStateButtons />
</Container>
</Flex>
<SlidingPanel
- title="Select Token"
animationState={this.state.tokenSelectionPanelAnimationState}
onClose={this._handlePanelClose}
>
<AvailableERC20TokenSelector onTokenSelect={this._handlePanelClose} />
</SlidingPanel>
+ <CurrentStandardSlidingPanel />
</Container>
</Container>
</React.Fragment>
diff --git a/packages/instant/src/components/zero_ex_instant_overlay.tsx b/packages/instant/src/components/zero_ex_instant_overlay.tsx
index 10438ab7a..2856ea3e3 100644
--- a/packages/instant/src/components/zero_ex_instant_overlay.tsx
+++ b/packages/instant/src/components/zero_ex_instant_overlay.tsx
@@ -1,12 +1,12 @@
import * as React from 'react';
+import { ZeroExInstantContainer } from '../components/zero_ex_instant_container';
import { ColorOption } from '../style/theme';
import { Container } from './ui/container';
import { Flex } from './ui/flex';
import { Icon } from './ui/icon';
import { Overlay } from './ui/overlay';
-import { ZeroExInstantContainer } from './zero_ex_instant_container';
import { ZeroExInstantProvider, ZeroExInstantProviderProps } from './zero_ex_instant_provider';
export interface ZeroExInstantOverlayProps extends ZeroExInstantProviderProps {
diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx
index e64579518..18e71edb6 100644
--- a/packages/instant/src/components/zero_ex_instant_provider.tsx
+++ b/packages/instant/src/components/zero_ex_instant_provider.tsx
@@ -91,12 +91,13 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
}
public componentDidMount(): void {
const state = this._store.getState();
+ const dispatch = this._store.dispatch;
// tslint:disable-next-line:no-floating-promises
- asyncData.fetchEthPriceAndDispatchToStore(this._store);
+ asyncData.fetchEthPriceAndDispatchToStore(dispatch);
// fetch available assets if none are specified
if (_.isUndefined(state.availableAssets)) {
// tslint:disable-next-line:no-floating-promises
- asyncData.fetchAvailableAssetDatasAndDispatchToStore(this._store);
+ asyncData.fetchAvailableAssetDatasAndDispatchToStore(state, dispatch);
}
if (state.providerState.account.state !== AccountState.None) {
this._accountUpdateHeartbeat = generateAccountHeartbeater({
@@ -112,7 +113,7 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
});
this._buyQuoteHeartbeat.start(BUY_QUOTE_UPDATE_INTERVAL_TIME_MS);
// tslint:disable-next-line:no-floating-promises
- asyncData.fetchCurrentBuyQuoteAndDispatchToStore({ store: this._store, shouldSetPending: true });
+ asyncData.fetchCurrentBuyQuoteAndDispatchToStore(state, dispatch, true);
// warm up the gas price estimator cache just in case we can't
// grab the gas price estimate when submitting the transaction
// tslint:disable-next-line:no-floating-promises
diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts
index 110a8248a..2bf7849ec 100644
--- a/packages/instant/src/constants.ts
+++ b/packages/instant/src/constants.ts
@@ -19,6 +19,9 @@ export const ETH_GAS_STATION_API_BASE_URL = 'https://ethgasstation.info';
export const COINBASE_API_BASE_URL = 'https://api.coinbase.com/v2';
export const PROGRESS_STALL_AT_WIDTH = '95%';
export const PROGRESS_FINISH_ANIMATION_TIME_MS = 200;
+export const META_MASK_CHROME_STORE_URL =
+ 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn?hl=en';
+export const META_MASK_SITE_URL = 'https://metamask.io/';
export const ETHEREUM_NODE_URL_BY_NETWORK = {
[Network.Mainnet]: 'https://mainnet.infura.io/',
[Network.Kovan]: 'https://kovan.infura.io/',
diff --git a/packages/instant/src/containers/connected_account_payment_method.ts b/packages/instant/src/containers/connected_account_payment_method.ts
new file mode 100644
index 000000000..65b3710a6
--- /dev/null
+++ b/packages/instant/src/containers/connected_account_payment_method.ts
@@ -0,0 +1,59 @@
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+
+import { PaymentMethod, PaymentMethodProps } from '../components/payment_method';
+import { Action, actions } from '../redux/actions';
+import { asyncData } from '../redux/async_data';
+import { State } from '../redux/reducer';
+import { Network, Omit, ProviderState, StandardSlidingPanelContent } from '../types';
+
+export interface ConnectedAccountPaymentMethodProps {}
+
+interface ConnectedState {
+ network: Network;
+ providerState: ProviderState;
+}
+
+interface ConnectedDispatch {
+ onInstallWalletClick: () => void;
+ unlockWalletAndDispatchToStore: (providerState: ProviderState) => void;
+}
+
+type ConnectedProps = Omit<PaymentMethodProps, keyof ConnectedAccountPaymentMethodProps>;
+
+type FinalProps = ConnectedProps & ConnectedAccountPaymentMethodProps;
+
+const mapStateToProps = (state: State, _ownProps: ConnectedAccountPaymentMethodProps): ConnectedState => ({
+ network: state.network,
+ providerState: state.providerState,
+});
+
+const mapDispatchToProps = (
+ dispatch: Dispatch<Action>,
+ ownProps: ConnectedAccountPaymentMethodProps,
+): ConnectedDispatch => ({
+ onInstallWalletClick: () => dispatch(actions.openStandardSlidingPanel(StandardSlidingPanelContent.InstallWallet)),
+ unlockWalletAndDispatchToStore: async (providerState: ProviderState) =>
+ asyncData.fetchAccountInfoAndDispatchToStore(providerState, dispatch, true),
+});
+
+const mergeProps = (
+ connectedState: ConnectedState,
+ connectedDispatch: ConnectedDispatch,
+ ownProps: ConnectedAccountPaymentMethodProps,
+): FinalProps => ({
+ ...ownProps,
+ network: connectedState.network,
+ account: connectedState.providerState.account,
+ onInstallWalletClick: connectedDispatch.onInstallWalletClick,
+ onUnlockWalletClick: () => {
+ connectedDispatch.unlockWalletAndDispatchToStore(connectedState.providerState);
+ },
+});
+
+export const ConnectedAccountPaymentMethod: React.ComponentClass<ConnectedAccountPaymentMethodProps> = connect(
+ mapStateToProps,
+ mapDispatchToProps,
+ mergeProps,
+)(PaymentMethod);
diff --git a/packages/instant/src/containers/connected_buy_order_progress_or_payment_method.tsx b/packages/instant/src/containers/connected_buy_order_progress_or_payment_method.tsx
new file mode 100644
index 000000000..05071c8c3
--- /dev/null
+++ b/packages/instant/src/containers/connected_buy_order_progress_or_payment_method.tsx
@@ -0,0 +1,36 @@
+import * as React from 'react';
+import { connect } from 'react-redux';
+
+import { State } from '../redux/reducer';
+import { OrderProcessState } from '../types';
+
+import { ConnectedAccountPaymentMethod } from './connected_account_payment_method';
+import { SelectedAssetBuyOrderProgress } from './selected_asset_buy_order_progress';
+
+interface BuyOrderProgressOrPaymentMethodProps {
+ orderProcessState: OrderProcessState;
+}
+export const BuyOrderProgressOrPaymentMethod = (props: BuyOrderProgressOrPaymentMethodProps) => {
+ const { orderProcessState } = props;
+ if (
+ orderProcessState === OrderProcessState.Processing ||
+ orderProcessState === OrderProcessState.Success ||
+ orderProcessState === OrderProcessState.Failure
+ ) {
+ return <SelectedAssetBuyOrderProgress />;
+ }
+ if (orderProcessState === OrderProcessState.None) {
+ return <ConnectedAccountPaymentMethod />;
+ }
+ return null;
+};
+
+interface ConnectedState extends BuyOrderProgressOrPaymentMethodProps {}
+
+export interface ConnectedBuyOrderProgressOrPaymentMethodProps {}
+const mapStateToProps = (state: State, _ownProps: ConnectedBuyOrderProgressOrPaymentMethodProps): ConnectedState => ({
+ orderProcessState: state.buyOrderState.processState,
+});
+export const ConnectedBuyOrderProgressOrPaymentMethod: React.ComponentClass<
+ ConnectedBuyOrderProgressOrPaymentMethodProps
+> = connect(mapStateToProps)(BuyOrderProgressOrPaymentMethod);
diff --git a/packages/instant/src/containers/current_standard_sliding_panel.ts b/packages/instant/src/containers/current_standard_sliding_panel.ts
new file mode 100644
index 000000000..82ac7fa1b
--- /dev/null
+++ b/packages/instant/src/containers/current_standard_sliding_panel.ts
@@ -0,0 +1,31 @@
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+
+import { StandardSlidingPanel } from '../components/standard_sliding_panel';
+import { Action, actions } from '../redux/actions';
+import { State } from '../redux/reducer';
+import { StandardSlidingPanelSettings } from '../types';
+
+export interface CurrentStandardSlidingPanelProps {}
+
+interface ConnectedState extends StandardSlidingPanelSettings {}
+
+interface ConnectedDispatch {
+ onClose: () => void;
+}
+
+const mapStateToProps = (state: State, _ownProps: CurrentStandardSlidingPanelProps): ConnectedState =>
+ state.standardSlidingPanelSettings;
+
+const mapDispatchToProps = (
+ dispatch: Dispatch<Action>,
+ ownProps: CurrentStandardSlidingPanelProps,
+): ConnectedDispatch => ({
+ onClose: () => dispatch(actions.closeStandardSlidingPanel()),
+});
+
+export const CurrentStandardSlidingPanel: React.ComponentClass<CurrentStandardSlidingPanelProps> = connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(StandardSlidingPanel);
diff --git a/packages/instant/src/containers/latest_error.tsx b/packages/instant/src/containers/latest_error.tsx
index c0da181f1..b7cfdb504 100644
--- a/packages/instant/src/containers/latest_error.tsx
+++ b/packages/instant/src/containers/latest_error.tsx
@@ -3,7 +3,6 @@ import * as React from 'react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
-import { SlideAnimationState } from '../components/animations/slide_animation';
import { SlidingError } from '../components/sliding_error';
import { Overlay } from '../components/ui/overlay';
import { Action } from '../redux/actions';
@@ -11,7 +10,7 @@ import { State } from '../redux/reducer';
import { ScreenWidths } from '../style/media';
import { generateOverlayBlack } from '../style/theme';
import { zIndex } from '../style/z_index';
-import { Asset, DisplayStatus, Omit } from '../types';
+import { Asset, DisplayStatus, Omit, SlideAnimationState } from '../types';
import { errorFlasher } from '../util/error_flasher';
export interface LatestErrorComponentProps {
diff --git a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
index 7dd0d874e..8b0070228 100644
--- a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
+++ b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
@@ -6,11 +6,11 @@ import * as React from 'react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
-import { ERC20AssetAmountInput } from '../components/erc20_asset_amount_input';
+import { ERC20AssetAmountInput, ERC20AssetAmountInputProps } from '../components/erc20_asset_amount_input';
import { Action, actions } from '../redux/actions';
import { State } from '../redux/reducer';
import { ColorOption } from '../style/theme';
-import { AffiliateInfo, ERC20Asset, OrderProcessState } from '../types';
+import { AffiliateInfo, ERC20Asset, Omit, OrderProcessState } from '../types';
import { buyQuoteUpdater } from '../util/buy_quote_updater';
export interface SelectedERC20AssetAmountInputProps {
@@ -37,13 +37,7 @@ interface ConnectedDispatch {
) => void;
}
-interface ConnectedProps {
- value?: BigNumber;
- asset?: ERC20Asset;
- onChange: (value?: BigNumber, asset?: ERC20Asset) => void;
- isDisabled: boolean;
- numberOfAssetsAvailable?: number;
-}
+type ConnectedProps = Omit<ERC20AssetAmountInputProps, keyof SelectedERC20AssetAmountInputProps>;
type FinalProps = ConnectedProps & SelectedERC20AssetAmountInputProps;
diff --git a/packages/instant/src/redux/actions.ts b/packages/instant/src/redux/actions.ts
index ba50fab7a..77e3dec12 100644
--- a/packages/instant/src/redux/actions.ts
+++ b/packages/instant/src/redux/actions.ts
@@ -2,7 +2,7 @@ import { BuyQuote } from '@0x/asset-buyer';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
-import { ActionsUnion, AddressAndEthBalanceInWei, Asset } from '../types';
+import { ActionsUnion, AddressAndEthBalanceInWei, Asset, StandardSlidingPanelContent } from '../types';
export interface PlainAction<T extends string> {
type: T;
@@ -41,6 +41,8 @@ export enum ActionTypes {
HIDE_ERROR = 'HIDE_ERROR',
CLEAR_ERROR = 'CLEAR_ERROR',
RESET_AMOUNT = 'RESET_AMOUNT',
+ OPEN_STANDARD_SLIDING_PANEL = 'OPEN_STANDARD_SLIDING_PANEL',
+ CLOSE_STANDARD_SLIDING_PANEL = 'CLOSE_STANDARD_SLIDING_PANEL',
}
export const actions = {
@@ -67,4 +69,7 @@ export const actions = {
hideError: () => createAction(ActionTypes.HIDE_ERROR),
clearError: () => createAction(ActionTypes.CLEAR_ERROR),
resetAmount: () => createAction(ActionTypes.RESET_AMOUNT),
+ openStandardSlidingPanel: (content: StandardSlidingPanelContent) =>
+ createAction(ActionTypes.OPEN_STANDARD_SLIDING_PANEL, content),
+ closeStandardSlidingPanel: () => createAction(ActionTypes.CLOSE_STANDARD_SLIDING_PANEL),
};
diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts
index 3f5c06974..a1952e429 100644
--- a/packages/instant/src/redux/async_data.ts
+++ b/packages/instant/src/redux/async_data.ts
@@ -1,94 +1,90 @@
import { AssetProxyId } from '@0x/types';
+import { Web3Wrapper } from '@0x/web3-wrapper';
import * as _ from 'lodash';
+import { Dispatch } from 'redux';
import { BIG_NUMBER_ZERO } from '../constants';
-import { AccountState, ERC20Asset, OrderProcessState } from '../types';
+import { AccountState, ERC20Asset, OrderProcessState, ProviderState } from '../types';
import { assetUtils } from '../util/asset';
import { buyQuoteUpdater } from '../util/buy_quote_updater';
import { coinbaseApi } from '../util/coinbase_api';
import { errorFlasher } from '../util/error_flasher';
import { actions } from './actions';
-import { Store } from './store';
+import { State } from './reducer';
export const asyncData = {
- fetchEthPriceAndDispatchToStore: async (store: Store) => {
+ fetchEthPriceAndDispatchToStore: async (dispatch: Dispatch) => {
try {
const ethUsdPrice = await coinbaseApi.getEthUsdPrice();
- store.dispatch(actions.updateEthUsdPrice(ethUsdPrice));
+ dispatch(actions.updateEthUsdPrice(ethUsdPrice));
} catch (e) {
const errorMessage = 'Error fetching ETH/USD price';
- errorFlasher.flashNewErrorMessage(store.dispatch, errorMessage);
- store.dispatch(actions.updateEthUsdPrice(BIG_NUMBER_ZERO));
+ errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
+ dispatch(actions.updateEthUsdPrice(BIG_NUMBER_ZERO));
}
},
- fetchAvailableAssetDatasAndDispatchToStore: async (store: Store) => {
- const { providerState, assetMetaDataMap, network } = store.getState();
+ fetchAvailableAssetDatasAndDispatchToStore: async (state: State, dispatch: Dispatch) => {
+ const { providerState, assetMetaDataMap, network } = state;
const assetBuyer = providerState.assetBuyer;
try {
const assetDatas = await assetBuyer.getAvailableAssetDatasAsync();
const assets = assetUtils.createAssetsFromAssetDatas(assetDatas, assetMetaDataMap, network);
- store.dispatch(actions.setAvailableAssets(assets));
+ dispatch(actions.setAvailableAssets(assets));
} catch (e) {
const errorMessage = 'Could not find any assets';
- errorFlasher.flashNewErrorMessage(store.dispatch, errorMessage);
+ errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
// On error, just specify that none are available
- store.dispatch(actions.setAvailableAssets([]));
+ dispatch(actions.setAvailableAssets([]));
}
},
- fetchAccountInfoAndDispatchToStore: async (options: { store: Store; shouldSetToLoading: boolean }) => {
- const { store, shouldSetToLoading } = options;
- const { providerState } = store.getState();
+ fetchAccountInfoAndDispatchToStore: async (
+ providerState: ProviderState,
+ dispatch: Dispatch,
+ shouldAttemptUnlock: boolean = false,
+ shouldSetToLoading: boolean = false,
+ ) => {
const web3Wrapper = providerState.web3Wrapper;
const provider = providerState.provider;
if (shouldSetToLoading && providerState.account.state !== AccountState.Loading) {
- store.dispatch(actions.setAccountStateLoading());
+ dispatch(actions.setAccountStateLoading());
}
let availableAddresses: string[];
try {
// TODO(bmillman): Add support at the web3Wrapper level for calling `eth_requestAccounts` instead of calling enable here
const isPrivacyModeEnabled = !_.isUndefined((provider as any).enable);
- availableAddresses = isPrivacyModeEnabled
- ? await (provider as any).enable()
- : await web3Wrapper.getAvailableAddressesAsync();
+ availableAddresses =
+ isPrivacyModeEnabled && shouldAttemptUnlock
+ ? await (provider as any).enable()
+ : await web3Wrapper.getAvailableAddressesAsync();
} catch (e) {
- store.dispatch(actions.setAccountStateLocked());
+ dispatch(actions.setAccountStateLocked());
return;
}
if (!_.isEmpty(availableAddresses)) {
const activeAddress = availableAddresses[0];
- store.dispatch(actions.setAccountStateReady(activeAddress));
+ dispatch(actions.setAccountStateReady(activeAddress));
// tslint:disable-next-line:no-floating-promises
- asyncData.fetchAccountBalanceAndDispatchToStore(store);
+ asyncData.fetchAccountBalanceAndDispatchToStore(activeAddress, providerState.web3Wrapper, dispatch);
} else {
- store.dispatch(actions.setAccountStateLocked());
+ dispatch(actions.setAccountStateLocked());
}
},
- fetchAccountBalanceAndDispatchToStore: async (store: Store) => {
- const { providerState } = store.getState();
- const web3Wrapper = providerState.web3Wrapper;
- const account = providerState.account;
- if (account.state !== AccountState.Ready) {
- return;
- }
+ fetchAccountBalanceAndDispatchToStore: async (address: string, web3Wrapper: Web3Wrapper, dispatch: Dispatch) => {
try {
- const address = account.address;
const ethBalanceInWei = await web3Wrapper.getBalanceInWeiAsync(address);
- store.dispatch(actions.updateAccountEthBalance({ address, ethBalanceInWei }));
+ dispatch(actions.updateAccountEthBalance({ address, ethBalanceInWei }));
} catch (e) {
// leave balance as is
return;
}
},
- fetchCurrentBuyQuoteAndDispatchToStore: async (options: { store: Store; shouldSetPending: boolean }) => {
- const { store, shouldSetPending } = options;
- const {
- buyOrderState,
- providerState,
- selectedAsset,
- selectedAssetUnitAmount,
- affiliateInfo,
- } = store.getState();
+ fetchCurrentBuyQuoteAndDispatchToStore: async (
+ state: State,
+ dispatch: Dispatch,
+ shouldSetPending: boolean = false,
+ ) => {
+ const { buyOrderState, providerState, selectedAsset, selectedAssetUnitAmount, affiliateInfo } = state;
const assetBuyer = providerState.assetBuyer;
if (
!_.isUndefined(selectedAssetUnitAmount) &&
@@ -98,7 +94,7 @@ export const asyncData = {
) {
await buyQuoteUpdater.updateBuyQuoteAsync(
assetBuyer,
- store.dispatch,
+ dispatch,
selectedAsset as ERC20Asset,
selectedAssetUnitAmount,
shouldSetPending,
diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts
index 77c99627a..dfc2b89f3 100644
--- a/packages/instant/src/redux/reducer.ts
+++ b/packages/instant/src/redux/reducer.ts
@@ -19,6 +19,8 @@ import {
OrderProcessState,
OrderState,
ProviderState,
+ StandardSlidingPanelContent,
+ StandardSlidingPanelSettings,
} from '../types';
import { Action, ActionTypes } from './actions';
@@ -30,6 +32,7 @@ export interface DefaultState {
buyOrderState: OrderState;
latestErrorDisplayStatus: DisplayStatus;
quoteRequestState: AsyncProcessState;
+ standardSlidingPanelSettings: StandardSlidingPanelSettings;
}
// State that is required but needs to be derived from the props
@@ -56,6 +59,10 @@ export const DEFAULT_STATE: DefaultState = {
buyOrderState: { processState: OrderProcessState.None },
latestErrorDisplayStatus: DisplayStatus.Hidden,
quoteRequestState: AsyncProcessState.None,
+ standardSlidingPanelSettings: {
+ animationState: 'none',
+ content: StandardSlidingPanelContent.None,
+ },
};
export const createReducer = (initialState: State) => {
@@ -66,11 +73,19 @@ export const createReducer = (initialState: State) => {
case ActionTypes.SET_ACCOUNT_STATE_LOCKED:
return reduceStateWithAccount(state, LOCKED_ACCOUNT);
case ActionTypes.SET_ACCOUNT_STATE_READY: {
- const account: AccountReady = {
+ const address = action.data;
+ let newAccount: AccountReady = {
state: AccountState.Ready,
- address: action.data,
+ address,
};
- return reduceStateWithAccount(state, account);
+ const currentAccount = state.providerState.account;
+ if (currentAccount.state === AccountState.Ready && currentAccount.address === address) {
+ newAccount = {
+ ...newAccount,
+ ethBalanceInWei: currentAccount.ethBalanceInWei,
+ };
+ }
+ return reduceStateWithAccount(state, newAccount);
}
case ActionTypes.UPDATE_ACCOUNT_ETH_BALANCE: {
const { address, ethBalanceInWei } = action.data;
@@ -211,6 +226,22 @@ export const createReducer = (initialState: State) => {
...state,
availableAssets: action.data,
};
+ case ActionTypes.OPEN_STANDARD_SLIDING_PANEL:
+ return {
+ ...state,
+ standardSlidingPanelSettings: {
+ content: action.data,
+ animationState: 'slidIn',
+ },
+ };
+ case ActionTypes.CLOSE_STANDARD_SLIDING_PANEL:
+ return {
+ ...state,
+ standardSlidingPanelSettings: {
+ content: state.standardSlidingPanelSettings.content,
+ animationState: 'slidOut',
+ },
+ };
default:
return state;
}
diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts
index b43a82d46..b6f449f38 100644
--- a/packages/instant/src/types.ts
+++ b/packages/instant/src/types.ts
@@ -125,3 +125,15 @@ export interface AddressAndEthBalanceInWei {
address: string;
ethBalanceInWei: BigNumber;
}
+
+export type SlideAnimationState = 'slidIn' | 'slidOut' | 'none';
+
+export enum StandardSlidingPanelContent {
+ None = 'NONE',
+ InstallWallet = 'INSTALL_WALLET',
+}
+
+export interface StandardSlidingPanelSettings {
+ animationState: SlideAnimationState;
+ content: StandardSlidingPanelContent;
+}
diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts
index fbfbb19f3..40560d3eb 100644
--- a/packages/instant/src/util/asset.ts
+++ b/packages/instant/src/util/asset.ts
@@ -80,8 +80,6 @@ export const assetUtils = {
return metaData.symbol.toUpperCase();
case AssetProxyId.ERC721:
return metaData.name;
- default:
- return defaultName;
}
},
formattedSymbolForAsset: (asset?: ERC20Asset, defaultName: string = '???'): string => {
diff --git a/packages/instant/src/util/etherscan.ts b/packages/instant/src/util/etherscan.ts
index 4d62c4d9f..f9bf82827 100644
--- a/packages/instant/src/util/etherscan.ts
+++ b/packages/instant/src/util/etherscan.ts
@@ -8,9 +8,8 @@ const etherscanPrefix = (networkId: number): string | undefined => {
return 'kovan.';
case Network.Mainnet:
return '';
- default:
- return undefined;
}
+ return '';
};
export const etherscanUtil = {
diff --git a/packages/instant/src/util/heartbeater_factory.ts b/packages/instant/src/util/heartbeater_factory.ts
index 96a8ac4e6..06fcdb8bb 100644
--- a/packages/instant/src/util/heartbeater_factory.ts
+++ b/packages/instant/src/util/heartbeater_factory.ts
@@ -10,13 +10,13 @@ export interface HeartbeatFactoryOptions {
export const generateAccountHeartbeater = (options: HeartbeatFactoryOptions): Heartbeater => {
const { store, shouldPerformImmediatelyOnStart } = options;
return new Heartbeater(async () => {
- await asyncData.fetchAccountInfoAndDispatchToStore({ store, shouldSetToLoading: false });
+ await asyncData.fetchAccountInfoAndDispatchToStore(store.getState().providerState, store.dispatch, false);
}, shouldPerformImmediatelyOnStart);
};
export const generateBuyQuoteHeartbeater = (options: HeartbeatFactoryOptions): Heartbeater => {
const { store, shouldPerformImmediatelyOnStart } = options;
return new Heartbeater(async () => {
- await asyncData.fetchCurrentBuyQuoteAndDispatchToStore({ store, shouldSetPending: false });
+ await asyncData.fetchCurrentBuyQuoteAndDispatchToStore(store.getState(), store.dispatch, false);
}, shouldPerformImmediatelyOnStart);
};
diff --git a/packages/instant/src/util/util.ts b/packages/instant/src/util/util.ts
index 232a86850..29b6b1d2b 100644
--- a/packages/instant/src/util/util.ts
+++ b/packages/instant/src/util/util.ts
@@ -2,4 +2,5 @@ import * as _ from 'lodash';
export const util = {
boundNoop: _.noop.bind(_),
+ createOpenUrlInNewWindow: (href: string) => () => window.open(href, '_blank'),
};
diff --git a/packages/instant/tslint.json b/packages/instant/tslint.json
index 08b76be97..d43ee8da7 100644
--- a/packages/instant/tslint.json
+++ b/packages/instant/tslint.json
@@ -3,6 +3,7 @@
"rules": {
"custom-no-magic-numbers": false,
"semicolon": [true, "always", "ignore-bound-class-methods"],
- "max-classes-per-file": false
+ "max-classes-per-file": false,
+ "switch-default": false
}
}