aboutsummaryrefslogtreecommitdiffstats
path: root/packages/instant/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/instant/src')
-rw-r--r--packages/instant/src/components/amount_placeholder.tsx2
-rw-r--r--packages/instant/src/components/animations/position_animation.tsx66
-rw-r--r--packages/instant/src/components/animations/slide_animation.tsx12
-rw-r--r--packages/instant/src/components/buy_button.tsx22
-rw-r--r--packages/instant/src/components/buy_order_progress.tsx10
-rw-r--r--packages/instant/src/components/buy_order_state_buttons.tsx20
-rw-r--r--packages/instant/src/components/css_reset.tsx33
-rw-r--r--packages/instant/src/components/erc20_asset_amount_input.tsx6
-rw-r--r--packages/instant/src/components/erc20_token_selector.tsx8
-rw-r--r--packages/instant/src/components/instant_heading.tsx20
-rw-r--r--packages/instant/src/components/order_details.tsx7
-rw-r--r--packages/instant/src/components/placing_order_button.tsx10
-rw-r--r--packages/instant/src/components/scaling_input.tsx2
-rw-r--r--packages/instant/src/components/search_input.tsx5
-rw-r--r--packages/instant/src/components/secondary_button.tsx8
-rw-r--r--packages/instant/src/components/sliding_error.tsx41
-rw-r--r--packages/instant/src/components/sliding_panel.tsx9
-rw-r--r--packages/instant/src/components/timed_progress_bar.tsx14
-rw-r--r--packages/instant/src/components/ui/button.tsx61
-rw-r--r--packages/instant/src/components/ui/circle.tsx10
-rw-r--r--packages/instant/src/components/ui/container.tsx82
-rw-r--r--packages/instant/src/components/ui/flex.tsx26
-rw-r--r--packages/instant/src/components/ui/icon.tsx20
-rw-r--r--packages/instant/src/components/ui/index.ts9
-rw-r--r--packages/instant/src/components/ui/input.tsx23
-rw-r--r--packages/instant/src/components/ui/overlay.tsx22
-rw-r--r--packages/instant/src/components/ui/text.tsx52
-rw-r--r--packages/instant/src/components/zero_ex_instant.tsx10
-rw-r--r--packages/instant/src/components/zero_ex_instant_container.tsx61
-rw-r--r--packages/instant/src/components/zero_ex_instant_overlay.tsx2
-rw-r--r--packages/instant/src/components/zero_ex_instant_provider.tsx76
-rw-r--r--packages/instant/src/constants.ts9
-rw-r--r--packages/instant/src/containers/latest_buy_quote_order_details.ts2
-rw-r--r--packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts48
-rw-r--r--packages/instant/src/containers/selected_erc20_asset_amount_input.ts67
-rw-r--r--packages/instant/src/data/asset_data_network_mapping.ts8
-rw-r--r--packages/instant/src/data/asset_meta_data_map.ts2
-rw-r--r--packages/instant/src/index.umd.ts3
-rw-r--r--packages/instant/src/redux/async_data.ts43
-rw-r--r--packages/instant/src/redux/reducer.ts295
-rw-r--r--packages/instant/src/redux/store.ts7
-rw-r--r--packages/instant/src/style/media.ts51
-rw-r--r--packages/instant/src/style/theme.ts4
-rw-r--r--packages/instant/src/style/z_index.ts3
-rw-r--r--packages/instant/src/types.ts55
-rw-r--r--packages/instant/src/util/asset_buyer_factory.ts17
-rw-r--r--packages/instant/src/util/buy_quote_updater.ts56
-rw-r--r--packages/instant/src/util/injected_provider.ts16
-rw-r--r--packages/instant/src/util/provider_factory.ts34
-rw-r--r--packages/instant/src/util/provider_state_factory.ts69
50 files changed, 963 insertions, 575 deletions
diff --git a/packages/instant/src/components/amount_placeholder.tsx b/packages/instant/src/components/amount_placeholder.tsx
index 6ef8f0ac3..29ce8fafb 100644
--- a/packages/instant/src/components/amount_placeholder.tsx
+++ b/packages/instant/src/components/amount_placeholder.tsx
@@ -4,7 +4,7 @@ import { ColorOption } from '../style/theme';
import { Pulse } from './animations/pulse';
-import { Text } from './ui';
+import { Text } from './ui/text';
interface PlainPlaceholder {
color: ColorOption;
diff --git a/packages/instant/src/components/animations/position_animation.tsx b/packages/instant/src/components/animations/position_animation.tsx
index 4bb21befb..576d29c07 100644
--- a/packages/instant/src/components/animations/position_animation.tsx
+++ b/packages/instant/src/components/animations/position_animation.tsx
@@ -1,5 +1,6 @@
-import { Keyframes } from 'styled-components';
+import { InterpolationValue } from 'styled-components';
+import { media, OptionallyScreenSpecific, stylesForMedia } from '../../style/media';
import { css, keyframes, styled } from '../../style/theme';
export interface TransitionInfo {
@@ -51,30 +52,57 @@ export interface PositionAnimationSettings {
right?: TransitionInfo;
timingFunction: string;
duration?: string;
+ position?: string;
}
-export interface PositionAnimationProps extends PositionAnimationSettings {
- position: string;
+const generatePositionAnimationCss = (positionSettings: PositionAnimationSettings) => {
+ return css`
+ animation-name: ${slideKeyframeGenerator(
+ positionSettings.position || 'relative',
+ positionSettings.top,
+ positionSettings.bottom,
+ positionSettings.left,
+ positionSettings.right,
+ )};
+ animation-duration: ${positionSettings.duration || '0.3s'};
+ animation-timing-function: ${positionSettings.timingFunction};
+ animation-delay: 0s;
+ animation-iteration-count: 1;
+ animation-fill-mode: forwards;
+ position: ${positionSettings.position || 'relative'};
+ width: 100%;
+ `;
+};
+
+export interface PositionAnimationProps {
+ positionSettings: OptionallyScreenSpecific<PositionAnimationSettings>;
+ zIndex?: OptionallyScreenSpecific<number>;
}
+const defaultAnimation = (positionSettings: OptionallyScreenSpecific<PositionAnimationSettings>) => {
+ const bestDefault = 'default' in positionSettings ? positionSettings.default : positionSettings;
+ return generatePositionAnimationCss(bestDefault);
+};
+const animationForSize = (
+ positionSettings: OptionallyScreenSpecific<PositionAnimationSettings>,
+ sizeKey: 'sm' | 'md' | 'lg',
+ mediaFn: (...args: any[]) => InterpolationValue[],
+) => {
+ // checking default makes sure we have a PositionAnimationSettings object
+ // and then we check to see if we have a setting for the specific `sizeKey`
+ const animationSettingsForSize = 'default' in positionSettings && positionSettings[sizeKey];
+ return animationSettingsForSize && mediaFn`${generatePositionAnimationCss(animationSettingsForSize)}`;
+};
+
export const PositionAnimation =
styled.div <
PositionAnimationProps >
`
- animation-name: ${props =>
- css`
- ${slideKeyframeGenerator(props.position, props.top, props.bottom, props.left, props.right)};
- `};
- animation-duration: ${props => props.duration || '0.3s'};
- animation-timing-function: ${props => props.timingFunction};
- animation-delay: 0s;
- animation-iteration-count: 1;
- animation-fill-mode: forwards;
- position: ${props => props.position};
- height: 100%;
- width: 100%;
+ && {
+ ${props => props.zIndex && stylesForMedia<number>('z-index', props.zIndex)}
+ ${props => defaultAnimation(props.positionSettings)}
+ ${props => animationForSize(props.positionSettings, 'sm', media.small)}
+ ${props => animationForSize(props.positionSettings, 'md', media.medium)}
+ ${props => animationForSize(props.positionSettings, 'lg', media.large)}
+ }
`;
-
-PositionAnimation.defaultProps = {
- position: 'relative',
-};
diff --git a/packages/instant/src/components/animations/slide_animation.tsx b/packages/instant/src/components/animations/slide_animation.tsx
index 66a314c7f..122229dee 100644
--- a/packages/instant/src/components/animations/slide_animation.tsx
+++ b/packages/instant/src/components/animations/slide_animation.tsx
@@ -1,22 +1,24 @@
import * as React from 'react';
+import { OptionallyScreenSpecific } from '../../style/media';
+
import { PositionAnimation, PositionAnimationSettings } from './position_animation';
export type SlideAnimationState = 'slidIn' | 'slidOut' | 'none';
export interface SlideAnimationProps {
- position: string;
animationState: SlideAnimationState;
- slideInSettings: PositionAnimationSettings;
- slideOutSettings: PositionAnimationSettings;
+ slideInSettings: OptionallyScreenSpecific<PositionAnimationSettings>;
+ slideOutSettings: OptionallyScreenSpecific<PositionAnimationSettings>;
+ zIndex?: OptionallyScreenSpecific<number>;
}
export const SlideAnimation: React.StatelessComponent<SlideAnimationProps> = props => {
if (props.animationState === 'none') {
return <React.Fragment>{props.children}</React.Fragment>;
}
- const propsToUse = props.animationState === 'slidIn' ? props.slideInSettings : props.slideOutSettings;
+ const positionSettings = props.animationState === 'slidIn' ? props.slideInSettings : props.slideOutSettings;
return (
- <PositionAnimation position={props.position} {...propsToUse}>
+ <PositionAnimation positionSettings={positionSettings} zIndex={props.zIndex}>
{props.children}
</PositionAnimation>
);
diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx
index 9d9a8540c..5b07e7416 100644
--- a/packages/instant/src/components/buy_button.tsx
+++ b/packages/instant/src/components/buy_button.tsx
@@ -12,11 +12,11 @@ import { balanceUtil } from '../util/balance';
import { gasPriceEstimator } from '../util/gas_price_estimator';
import { util } from '../util/util';
-import { Button, Text } from './ui';
+import { Button } from './ui/button';
export interface BuyButtonProps {
buyQuote?: BuyQuote;
- assetBuyer?: AssetBuyer;
+ assetBuyer: AssetBuyer;
affiliateInfo?: AffiliateInfo;
onValidationPending: (buyQuote: BuyQuote) => void;
onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void;
@@ -33,23 +33,29 @@ export class BuyButton extends React.Component<BuyButtonProps> {
onBuyFailure: util.boundNoop,
};
public render(): React.ReactNode {
- const shouldDisableButton = _.isUndefined(this.props.buyQuote) || _.isUndefined(this.props.assetBuyer);
+ const shouldDisableButton = _.isUndefined(this.props.buyQuote);
return (
- <Button width="100%" onClick={this._handleClick} isDisabled={shouldDisableButton}>
- <Text fontColor={ColorOption.white} fontWeight={600} fontSize="20px">
- Buy
- </Text>
+ <Button
+ width="100%"
+ onClick={this._handleClick}
+ isDisabled={shouldDisableButton}
+ fontColor={ColorOption.white}
+ fontSize="20px"
+ >
+ Buy
</Button>
);
}
private readonly _handleClick = async () => {
// The button is disabled when there is no buy quote anyway.
const { buyQuote, assetBuyer, affiliateInfo } = this.props;
- if (_.isUndefined(buyQuote) || _.isUndefined(assetBuyer)) {
+ if (_.isUndefined(buyQuote)) {
return;
}
this.props.onValidationPending(buyQuote);
+
+ // TODO(bmillman): move address and balance fetching to the async state
const web3Wrapper = new Web3Wrapper(assetBuyer.provider);
const takerAddress = await getBestAddress(web3Wrapper);
diff --git a/packages/instant/src/components/buy_order_progress.tsx b/packages/instant/src/components/buy_order_progress.tsx
index e259e5606..bc7319423 100644
--- a/packages/instant/src/components/buy_order_progress.tsx
+++ b/packages/instant/src/components/buy_order_progress.tsx
@@ -3,7 +3,7 @@ import * as React from 'react';
import { TimedProgressBar } from '../components/timed_progress_bar';
import { TimeCounter } from '../components/time_counter';
-import { Container } from '../components/ui';
+import { Container } from '../components/ui/container';
import { OrderProcessState, OrderState } from '../types';
export interface BuyOrderProgressProps {
@@ -14,12 +14,12 @@ export const BuyOrderProgress: React.StatelessComponent<BuyOrderProgressProps> =
const { buyOrderState } = props;
if (
- buyOrderState.processState === OrderProcessState.PROCESSING ||
- buyOrderState.processState === OrderProcessState.SUCCESS ||
- buyOrderState.processState === OrderProcessState.FAILURE
+ buyOrderState.processState === OrderProcessState.Processing ||
+ buyOrderState.processState === OrderProcessState.Success ||
+ buyOrderState.processState === OrderProcessState.Failure
) {
const progress = buyOrderState.progress;
- const hasEnded = buyOrderState.processState !== OrderProcessState.PROCESSING;
+ const hasEnded = buyOrderState.processState !== OrderProcessState.Processing;
const expectedTimeMs = progress.expectedEndTimeUnix - progress.startTimeUnix;
return (
<Container padding="20px 20px 0px 20px" width="100%">
diff --git a/packages/instant/src/components/buy_order_state_buttons.tsx b/packages/instant/src/components/buy_order_state_buttons.tsx
index 5c074a67a..bdac25cf2 100644
--- a/packages/instant/src/components/buy_order_state_buttons.tsx
+++ b/packages/instant/src/components/buy_order_state_buttons.tsx
@@ -7,12 +7,14 @@ import { AffiliateInfo, OrderProcessState, ZeroExInstantError } from '../types';
import { BuyButton } from './buy_button';
import { PlacingOrderButton } from './placing_order_button';
import { SecondaryButton } from './secondary_button';
-import { Button, Flex, Text } from './ui';
+
+import { Button } from './ui/button';
+import { Flex } from './ui/flex';
export interface BuyOrderStateButtonProps {
buyQuote?: BuyQuote;
buyOrderProcessingState: OrderProcessState;
- assetBuyer?: AssetBuyer;
+ assetBuyer: AssetBuyer;
affiliateInfo?: AffiliateInfo;
onViewTransaction: () => void;
onValidationPending: (buyQuote: BuyQuote) => void;
@@ -25,13 +27,11 @@ export interface BuyOrderStateButtonProps {
}
export const BuyOrderStateButtons: React.StatelessComponent<BuyOrderStateButtonProps> = props => {
- if (props.buyOrderProcessingState === OrderProcessState.FAILURE) {
+ if (props.buyOrderProcessingState === OrderProcessState.Failure) {
return (
<Flex justify="space-between">
- <Button width="48%" onClick={props.onRetry}>
- <Text fontColor={ColorOption.white} fontWeight={600} fontSize="16px">
- Back
- </Text>
+ <Button width="48%" onClick={props.onRetry} fontColor={ColorOption.white} fontSize="16px">
+ Back
</Button>
<SecondaryButton width="48%" onClick={props.onViewTransaction}>
Details
@@ -39,11 +39,11 @@ export const BuyOrderStateButtons: React.StatelessComponent<BuyOrderStateButtonP
</Flex>
);
} else if (
- props.buyOrderProcessingState === OrderProcessState.SUCCESS ||
- props.buyOrderProcessingState === OrderProcessState.PROCESSING
+ props.buyOrderProcessingState === OrderProcessState.Success ||
+ props.buyOrderProcessingState === OrderProcessState.Processing
) {
return <SecondaryButton onClick={props.onViewTransaction}>View Transaction</SecondaryButton>;
- } else if (props.buyOrderProcessingState === OrderProcessState.VALIDATING) {
+ } else if (props.buyOrderProcessingState === OrderProcessState.Validating) {
return <PlacingOrderButton />;
}
diff --git a/packages/instant/src/components/css_reset.tsx b/packages/instant/src/components/css_reset.tsx
new file mode 100644
index 000000000..a1dd2e05c
--- /dev/null
+++ b/packages/instant/src/components/css_reset.tsx
@@ -0,0 +1,33 @@
+import { INJECTED_DIV_CLASS } from '../constants';
+import { createGlobalStyle } from '../style/theme';
+
+export interface CSSResetProps {}
+
+/*
+* Derived from
+* https://github.com/jtrost/Complete-CSS-Reset
+*/
+export const CSSReset = createGlobalStyle`
+ .${INJECTED_DIV_CLASS} {
+ a, abbr, area, article, aside, audio, b, bdo, blockquote, body, button,
+ canvas, caption, cite, code, col, colgroup, command, datalist, dd, del,
+ details, dialog, dfn, div, dl, dt, em, embed, fieldset, figure, form,
+ h1, h2, h3, h4, h5, h6, head, header, hgroup, hr, html, i, iframe, img,
+ input, ins, keygen, kbd, label, legend, li, map, mark, menu, meter, nav,
+ noscript, object, ol, optgroup, option, output, p, param, pre, progress,
+ q, rp, rt, ruby, samp, section, select, small, span, strong, sub, sup,
+ table, tbody, td, textarea, tfoot, th, thead, time, tr, ul, var, video {
+ background: transparent;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ margin: 0;
+ outline: none;
+ padding: 0;
+ text-align: left;
+ text-decoration: none;
+ vertical-align: baseline;
+ z-index: 1;
+ }
+ }
+`;
diff --git a/packages/instant/src/components/erc20_asset_amount_input.tsx b/packages/instant/src/components/erc20_asset_amount_input.tsx
index 6ad0e92e7..f21c21b87 100644
--- a/packages/instant/src/components/erc20_asset_amount_input.tsx
+++ b/packages/instant/src/components/erc20_asset_amount_input.tsx
@@ -8,7 +8,11 @@ import { assetUtils } from '../util/asset';
import { util } from '../util/util';
import { ScalingAmountInput } from './scaling_amount_input';
-import { Container, Flex, Icon, Text } from './ui';
+
+import { Container } from './ui/container';
+import { Flex } from './ui/flex';
+import { Icon } from './ui/icon';
+import { Text } from './ui/text';
// Asset amounts only apply to ERC20 assets
export interface ERC20AssetAmountInputProps {
diff --git a/packages/instant/src/components/erc20_token_selector.tsx b/packages/instant/src/components/erc20_token_selector.tsx
index 481778495..76d5c66ff 100644
--- a/packages/instant/src/components/erc20_token_selector.tsx
+++ b/packages/instant/src/components/erc20_token_selector.tsx
@@ -6,7 +6,11 @@ import { ERC20Asset } from '../types';
import { assetUtils } from '../util/asset';
import { SearchInput } from './search_input';
-import { Circle, Container, Flex, Text } from './ui';
+
+import { Circle } from './ui/circle';
+import { Container } from './ui/container';
+import { Flex } from './ui/flex';
+import { Text } from './ui/text';
export interface ERC20TokenSelectorProps {
tokens: ERC20Asset[];
@@ -31,7 +35,7 @@ export class ERC20TokenSelector extends React.Component<ERC20TokenSelectorProps>
value={this.state.searchQuery}
onChange={this._handleSearchInputChange}
/>
- <Container overflow="scroll" height="275px" marginTop="10px">
+ <Container overflow="scroll" height={{ default: '275px', sm: '75vh' }} marginTop="10px">
{_.map(tokens, token => {
if (!this._isTokenQueryMatch(token)) {
return null;
diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx
index 80d7a3ee2..b07776b2c 100644
--- a/packages/instant/src/components/instant_heading.tsx
+++ b/packages/instant/src/components/instant_heading.tsx
@@ -8,7 +8,11 @@ import { AsyncProcessState, ERC20Asset, OrderProcessState, OrderState } from '..
import { format } from '../util/format';
import { AmountPlaceholder } from './amount_placeholder';
-import { Container, Flex, Icon, Spinner, Text } from './ui';
+import { Container } from './ui/container';
+import { Flex } from './ui/flex';
+import { Icon } from './ui/icon';
+import { Spinner } from './ui/spinner';
+import { Text } from './ui/text';
export interface InstantHeadingProps {
selectedAssetAmount?: BigNumber;
@@ -73,11 +77,11 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
private _renderIcon(): React.ReactNode {
const processState = this.props.buyOrderState.processState;
- if (processState === OrderProcessState.FAILURE) {
+ if (processState === OrderProcessState.Failure) {
return <Icon icon="failed" width={ICON_WIDTH} height={ICON_HEIGHT} color={ICON_COLOR} />;
- } else if (processState === OrderProcessState.PROCESSING) {
+ } else if (processState === OrderProcessState.Processing) {
return <Spinner widthPx={ICON_HEIGHT} heightPx={ICON_HEIGHT} />;
- } else if (processState === OrderProcessState.SUCCESS) {
+ } else if (processState === OrderProcessState.Success) {
return <Icon icon="success" width={ICON_WIDTH} height={ICON_HEIGHT} color={ICON_COLOR} />;
}
return undefined;
@@ -85,11 +89,11 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
private _renderTopText(): React.ReactNode {
const processState = this.props.buyOrderState.processState;
- if (processState === OrderProcessState.FAILURE) {
+ if (processState === OrderProcessState.Failure) {
return 'Order failed';
- } else if (processState === OrderProcessState.PROCESSING) {
+ } else if (processState === OrderProcessState.Processing) {
return 'Processing Order...';
- } else if (processState === OrderProcessState.SUCCESS) {
+ } else if (processState === OrderProcessState.Success) {
return 'Tokens received!';
}
@@ -97,7 +101,7 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
}
private _renderPlaceholderOrAmount(amountFunction: () => React.ReactNode): React.ReactNode {
- if (this.props.quoteRequestState === AsyncProcessState.PENDING) {
+ if (this.props.quoteRequestState === AsyncProcessState.Pending) {
return <AmountPlaceholder isPulsating={true} color={PLACEHOLDER_COLOR} />;
}
if (_.isUndefined(this.props.selectedAssetAmount)) {
diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx
index 704009d89..9abd7137e 100644
--- a/packages/instant/src/components/order_details.tsx
+++ b/packages/instant/src/components/order_details.tsx
@@ -8,7 +8,10 @@ import { ColorOption } from '../style/theme';
import { format } from '../util/format';
import { AmountPlaceholder } from './amount_placeholder';
-import { Container, Flex, Text } from './ui';
+
+import { Container } from './ui/container';
+import { Flex } from './ui/flex';
+import { Text } from './ui/text';
export interface OrderDetailsProps {
buyQuoteInfo?: BuyQuoteInfo;
@@ -23,7 +26,7 @@ export class OrderDetails extends React.Component<OrderDetailsProps> {
const ethTokenFee = buyQuoteAccessor.feeEthAmount();
const totalEthAmount = buyQuoteAccessor.totalEthAmount();
return (
- <Container padding="20px" width="100%">
+ <Container padding="20px" width="100%" flexGrow={1}>
<Container marginBottom="10px">
<Text
letterSpacing="1px"
diff --git a/packages/instant/src/components/placing_order_button.tsx b/packages/instant/src/components/placing_order_button.tsx
index e5a6371e6..d774d7d27 100644
--- a/packages/instant/src/components/placing_order_button.tsx
+++ b/packages/instant/src/components/placing_order_button.tsx
@@ -2,15 +2,15 @@ import * as React from 'react';
import { ColorOption } from '../style/theme';
-import { Button, Container, Spinner, Text } from './ui';
+import { Button } from './ui/button';
+import { Container } from './ui/container';
+import { Spinner } from './ui/spinner';
export const PlacingOrderButton: React.StatelessComponent<{}> = props => (
- <Button isDisabled={true} width="100%">
+ <Button isDisabled={true} width="100%" fontColor={ColorOption.white} fontSize="20px">
<Container display="inline-block" position="relative" top="3px" marginRight="8px">
<Spinner widthPx={20} heightPx={20} />
</Container>
- <Text fontColor={ColorOption.white} fontWeight={600} fontSize="20px">
- Placing Order&hellip;
- </Text>
+ Placing Order&hellip;
</Button>
);
diff --git a/packages/instant/src/components/scaling_input.tsx b/packages/instant/src/components/scaling_input.tsx
index 11748b729..1abadb78b 100644
--- a/packages/instant/src/components/scaling_input.tsx
+++ b/packages/instant/src/components/scaling_input.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
import { ColorOption } from '../style/theme';
import { util } from '../util/util';
-import { Input } from './ui';
+import { Input } from './ui/input';
export enum ScalingInputPhase {
FixedFontSize,
diff --git a/packages/instant/src/components/search_input.tsx b/packages/instant/src/components/search_input.tsx
index f082eaa16..3a693b9f8 100644
--- a/packages/instant/src/components/search_input.tsx
+++ b/packages/instant/src/components/search_input.tsx
@@ -3,7 +3,10 @@ import * as React from 'react';
import { ColorOption } from '../style/theme';
-import { Container, Flex, Icon, Input, InputProps } from './ui';
+import { Container } from './ui/container';
+import { Flex } from './ui/flex';
+import { Icon } from './ui/icon';
+import { Input, InputProps } from './ui/input';
export interface SearchInputProps extends InputProps {
backgroundColor?: ColorOption;
diff --git a/packages/instant/src/components/secondary_button.tsx b/packages/instant/src/components/secondary_button.tsx
index ca698c77a..df0539606 100644
--- a/packages/instant/src/components/secondary_button.tsx
+++ b/packages/instant/src/components/secondary_button.tsx
@@ -3,7 +3,7 @@ import * as React from 'react';
import { ColorOption } from '../style/theme';
-import { Button, ButtonProps, Text } from './ui';
+import { Button, ButtonProps } from './ui/button';
export interface SecondaryButtonProps extends ButtonProps {}
@@ -15,11 +15,11 @@ export const SecondaryButton: React.StatelessComponent<SecondaryButtonProps> = p
borderColor={ColorOption.lightGrey}
width={props.width}
onClick={props.onClick}
+ fontColor={ColorOption.primaryColor}
+ fontSize="16px"
{...buttonProps}
>
- <Text fontColor={ColorOption.primaryColor} fontWeight={600} fontSize="16px">
- {props.children}
- </Text>
+ {props.children}
</Button>
);
};
diff --git a/packages/instant/src/components/sliding_error.tsx b/packages/instant/src/components/sliding_error.tsx
index 17643fd7d..462199d78 100644
--- a/packages/instant/src/components/sliding_error.tsx
+++ b/packages/instant/src/components/sliding_error.tsx
@@ -1,11 +1,15 @@
import * as React from 'react';
+import { ScreenSpecification } from '../style/media';
import { ColorOption } from '../style/theme';
+import { zIndex } from '../style/z_index';
import { PositionAnimationSettings } from './animations/position_animation';
import { SlideAnimation, SlideAnimationState } from './animations/slide_animation';
-import { Container, Flex, Text } from './ui';
+import { Container } from './ui/container';
+import { Flex } from './ui/flex';
+import { Text } from './ui/text';
export interface ErrorProps {
icon: string;
@@ -19,6 +23,7 @@ export const Error: React.StatelessComponent<ErrorProps> = props => (
backgroundColor={ColorOption.lightOrange}
width="100%"
borderRadius="6px"
+ marginTop="10px"
marginBottom="10px"
>
<Flex justify="flex-start">
@@ -37,25 +42,51 @@ export interface SlidingErrorProps extends ErrorProps {
}
export const SlidingError: React.StatelessComponent<SlidingErrorProps> = props => {
const slideAmount = '120px';
- const slideUpSettings: PositionAnimationSettings = {
+
+ const desktopSlideIn: PositionAnimationSettings = {
timingFunction: 'ease-in',
top: {
from: slideAmount,
to: '0px',
},
+ position: 'relative',
};
- const slideDownSettings: PositionAnimationSettings = {
+ const desktopSlideOut: PositionAnimationSettings = {
timingFunction: 'cubic-bezier(0.25, 0.1, 0.25, 1)',
top: {
from: '0px',
to: slideAmount,
},
+ position: 'relative',
+ };
+
+ const mobileSlideIn: PositionAnimationSettings = {
+ duration: '0.5s',
+ timingFunction: 'ease-in',
+ top: { from: '-120px', to: '0px' },
+ position: 'fixed',
+ };
+ const moblieSlideOut: PositionAnimationSettings = {
+ duration: '0.5s',
+ timingFunction: 'ease-in',
+ top: { from: '0px', to: '-120px' },
+ position: 'fixed',
+ };
+
+ const slideUpSettings: ScreenSpecification<PositionAnimationSettings> = {
+ default: desktopSlideIn,
+ sm: mobileSlideIn,
};
+ const slideOutSettings: ScreenSpecification<PositionAnimationSettings> = {
+ default: desktopSlideOut,
+ sm: moblieSlideOut,
+ };
+
return (
<SlideAnimation
- position="relative"
slideInSettings={slideUpSettings}
- slideOutSettings={slideDownSettings}
+ slideOutSettings={slideOutSettings}
+ zIndex={{ sm: zIndex.errorPopUp, default: zIndex.errorPopBehind }}
animationState={props.animationState}
>
<Error icon={props.icon} message={props.message} />
diff --git a/packages/instant/src/components/sliding_panel.tsx b/packages/instant/src/components/sliding_panel.tsx
index ea1d6b9a1..a5d15c401 100644
--- a/packages/instant/src/components/sliding_panel.tsx
+++ b/packages/instant/src/components/sliding_panel.tsx
@@ -5,7 +5,11 @@ import { zIndex } from '../style/z_index';
import { PositionAnimationSettings } from './animations/position_animation';
import { SlideAnimation, SlideAnimationState } from './animations/slide_animation';
-import { Container, Flex, Icon, Text } from './ui';
+
+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;
@@ -47,6 +51,7 @@ export const SlidingPanel: React.StatelessComponent<SlidingPanelProps> = props =
from: slideAmount,
to: '0px',
},
+ position: 'absolute',
};
const slideDownSettings: PositionAnimationSettings = {
duration: '0.3s',
@@ -55,10 +60,10 @@ export const SlidingPanel: React.StatelessComponent<SlidingPanelProps> = props =
from: '0px',
to: slideAmount,
},
+ position: 'absolute',
};
return (
<SlideAnimation
- position="absolute"
slideInSettings={slideUpSettings}
slideOutSettings={slideDownSettings}
animationState={animationState}
diff --git a/packages/instant/src/components/timed_progress_bar.tsx b/packages/instant/src/components/timed_progress_bar.tsx
index f2a6f5745..59aaa33a1 100644
--- a/packages/instant/src/components/timed_progress_bar.tsx
+++ b/packages/instant/src/components/timed_progress_bar.tsx
@@ -70,9 +70,11 @@ export const TimedProgress =
styled.div <
TimedProgressProps >
`
- 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;
- `;
+ && {
+ 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;
+ }
+`;
diff --git a/packages/instant/src/components/ui/button.tsx b/packages/instant/src/components/ui/button.tsx
index 5274d835b..b90221bf4 100644
--- a/packages/instant/src/components/ui/button.tsx
+++ b/packages/instant/src/components/ui/button.tsx
@@ -6,6 +6,8 @@ import { ColorOption, styled } from '../../style/theme';
export interface ButtonProps {
backgroundColor?: ColorOption;
borderColor?: ColorOption;
+ fontColor?: ColorOption;
+ fontSize?: string;
width?: string;
padding?: string;
type?: string;
@@ -24,29 +26,39 @@ const darkenOnHoverAmount = 0.1;
const darkenOnActiveAmount = 0.2;
const saturateOnFocusAmount = 0.2;
export const Button = styled(PlainButton)`
- cursor: ${props => (props.isDisabled ? 'default' : 'pointer')};
- transition: background-color, opacity 0.5s ease;
- padding: ${props => props.padding};
- border-radius: 3px;
- outline: none;
- width: ${props => props.width};
- background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')};
- border: ${props => (props.borderColor ? `1px solid ${props.theme[props.borderColor]}` : 'none')};
- &:hover {
- background-color: ${props =>
- !props.isDisabled
- ? darken(darkenOnHoverAmount, props.theme[props.backgroundColor || 'white'])
- : ''} !important;
- }
- &:active {
- background-color: ${props =>
- !props.isDisabled ? darken(darkenOnActiveAmount, props.theme[props.backgroundColor || 'white']) : ''};
- }
- &:disabled {
- opacity: 0.5;
- }
- &:focus {
- background-color: ${props => saturate(saturateOnFocusAmount, props.theme[props.backgroundColor || 'white'])};
+ && {
+ all: initial;
+ box-sizing: border-box;
+ font-size: ${props => props.fontSize};
+ font-family: 'Inter UI', sans-serif;
+ font-weight: 600;
+ color: ${props => props.fontColor && props.theme[props.fontColor]};
+ cursor: ${props => (props.isDisabled ? 'default' : 'pointer')};
+ transition: background-color, opacity 0.5s ease;
+ padding: ${props => props.padding};
+ border-radius: 3px;
+ text-align: center;
+ outline: none;
+ width: ${props => props.width};
+ background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')};
+ border: ${props => (props.borderColor ? `1px solid ${props.theme[props.borderColor]}` : 'none')};
+ &:hover {
+ background-color: ${props =>
+ !props.isDisabled
+ ? darken(darkenOnHoverAmount, props.theme[props.backgroundColor || 'white'])
+ : ''} !important;
+ }
+ &:active {
+ background-color: ${props =>
+ !props.isDisabled ? darken(darkenOnActiveAmount, props.theme[props.backgroundColor || 'white']) : ''};
+ }
+ &:disabled {
+ opacity: 0.5;
+ }
+ &:focus {
+ background-color: ${props =>
+ saturate(saturateOnFocusAmount, props.theme[props.backgroundColor || 'white'])};
+ }
}
`;
@@ -55,7 +67,8 @@ Button.defaultProps = {
borderColor: ColorOption.primaryColor,
width: 'auto',
isDisabled: false,
- padding: '1em 2.2em',
+ padding: '.6em 1.2em',
+ fontSize: '15px',
};
Button.displayName = 'Button';
diff --git a/packages/instant/src/components/ui/circle.tsx b/packages/instant/src/components/ui/circle.tsx
index eec2777d2..26764ec71 100644
--- a/packages/instant/src/components/ui/circle.tsx
+++ b/packages/instant/src/components/ui/circle.tsx
@@ -9,10 +9,12 @@ export const Circle =
styled.div <
CircleProps >
`
- width: ${props => props.diameter}px;
- height: ${props => props.diameter}px;
- background-color: ${props => props.fillColor};
- border-radius: 50%;
+ && {
+ width: ${props => props.diameter}px;
+ height: ${props => props.diameter}px;
+ background-color: ${props => props.fillColor};
+ border-radius: 50%;
+ }
`;
Circle.displayName = 'Circle';
diff --git a/packages/instant/src/components/ui/container.tsx b/packages/instant/src/components/ui/container.tsx
index a0a187e5f..c42082ed5 100644
--- a/packages/instant/src/components/ui/container.tsx
+++ b/packages/instant/src/components/ui/container.tsx
@@ -1,17 +1,18 @@
import { darken } from 'polished';
+import { MediaChoice, stylesForMedia } from '../../style/media';
import { ColorOption, styled } from '../../style/theme';
import { cssRuleIfExists } from '../../style/util';
export interface ContainerProps {
- display?: string;
+ display?: MediaChoice;
position?: string;
top?: string;
right?: string;
bottom?: string;
left?: string;
- width?: string;
- height?: string;
+ width?: MediaChoice;
+ height?: MediaChoice;
maxWidth?: string;
margin?: string;
marginTop?: string;
@@ -33,47 +34,52 @@ export interface ContainerProps {
cursor?: string;
overflow?: string;
darkenOnHover?: boolean;
+ flexGrow?: string | number;
}
export const Container =
styled.div <
ContainerProps >
`
- box-sizing: border-box;
- ${props => cssRuleIfExists(props, 'display')}
- ${props => cssRuleIfExists(props, 'position')}
- ${props => cssRuleIfExists(props, 'top')}
- ${props => cssRuleIfExists(props, 'right')}
- ${props => cssRuleIfExists(props, 'bottom')}
- ${props => cssRuleIfExists(props, 'left')}
- ${props => cssRuleIfExists(props, 'width')}
- ${props => cssRuleIfExists(props, 'height')}
- ${props => cssRuleIfExists(props, 'max-width')}
- ${props => cssRuleIfExists(props, 'margin')}
- ${props => cssRuleIfExists(props, 'margin-top')}
- ${props => cssRuleIfExists(props, 'margin-right')}
- ${props => cssRuleIfExists(props, 'margin-bottom')}
- ${props => cssRuleIfExists(props, 'margin-left')}
- ${props => cssRuleIfExists(props, 'padding')}
- ${props => cssRuleIfExists(props, 'border-radius')}
- ${props => cssRuleIfExists(props, 'border')}
- ${props => cssRuleIfExists(props, 'border-top')}
- ${props => cssRuleIfExists(props, 'border-bottom')}
- ${props => cssRuleIfExists(props, 'z-index')}
- ${props => cssRuleIfExists(props, 'white-space')}
- ${props => cssRuleIfExists(props, 'opacity')}
- ${props => cssRuleIfExists(props, 'cursor')}
- ${props => cssRuleIfExists(props, 'overflow')}
- ${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')};
- background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')};
- border-color: ${props => (props.borderColor ? props.theme[props.borderColor] : 'none')};
- &:hover {
- ${props =>
- props.darkenOnHover
- ? `background-color: ${
- props.backgroundColor ? darken(0.05, props.theme[props.backgroundColor]) : 'none'
- }`
- : ''};
+ && {
+ all: initial;
+ box-sizing: border-box;
+ ${props => cssRuleIfExists(props, 'flex-grow')}
+ ${props => cssRuleIfExists(props, 'position')}
+ ${props => cssRuleIfExists(props, 'top')}
+ ${props => cssRuleIfExists(props, 'right')}
+ ${props => cssRuleIfExists(props, 'bottom')}
+ ${props => cssRuleIfExists(props, 'left')}
+ ${props => cssRuleIfExists(props, 'max-width')}
+ ${props => cssRuleIfExists(props, 'margin')}
+ ${props => cssRuleIfExists(props, 'margin-top')}
+ ${props => cssRuleIfExists(props, 'margin-right')}
+ ${props => cssRuleIfExists(props, 'margin-bottom')}
+ ${props => cssRuleIfExists(props, 'margin-left')}
+ ${props => cssRuleIfExists(props, 'padding')}
+ ${props => cssRuleIfExists(props, 'border-radius')}
+ ${props => cssRuleIfExists(props, 'border')}
+ ${props => cssRuleIfExists(props, 'border-top')}
+ ${props => cssRuleIfExists(props, 'border-bottom')}
+ ${props => cssRuleIfExists(props, 'z-index')}
+ ${props => cssRuleIfExists(props, 'white-space')}
+ ${props => cssRuleIfExists(props, 'opacity')}
+ ${props => cssRuleIfExists(props, 'cursor')}
+ ${props => cssRuleIfExists(props, 'overflow')}
+ ${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')};
+ ${props => props.display && stylesForMedia<string>('display', props.display)}
+ ${props => props.width && stylesForMedia<string>('width', props.width)}
+ ${props => props.height && stylesForMedia<string>('height', props.height)}
+ background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')};
+ border-color: ${props => (props.borderColor ? props.theme[props.borderColor] : 'none')};
+ &:hover {
+ ${props =>
+ props.darkenOnHover
+ ? `background-color: ${
+ props.backgroundColor ? darken(0.05, props.theme[props.backgroundColor]) : 'none'
+ }`
+ : ''};
+ }
}
`;
diff --git a/packages/instant/src/components/ui/flex.tsx b/packages/instant/src/components/ui/flex.tsx
index 29c6511bb..5b00138b8 100644
--- a/packages/instant/src/components/ui/flex.tsx
+++ b/packages/instant/src/components/ui/flex.tsx
@@ -1,3 +1,4 @@
+import { MediaChoice, stylesForMedia } from '../../style/media';
import { ColorOption, styled } from '../../style/theme';
import { cssRuleIfExists } from '../../style/util';
@@ -6,24 +7,29 @@ export interface FlexProps {
flexWrap?: 'wrap' | 'nowrap';
justify?: 'flex-start' | 'center' | 'space-around' | 'space-between' | 'space-evenly' | 'flex-end';
align?: 'flex-start' | 'center' | 'space-around' | 'space-between' | 'space-evenly' | 'flex-end';
- width?: string;
- height?: string;
+ width?: MediaChoice;
+ height?: MediaChoice;
backgroundColor?: ColorOption;
inline?: boolean;
+ flexGrow?: number | string;
}
export const Flex =
styled.div <
FlexProps >
`
- display: ${props => (props.inline ? 'inline-flex' : 'flex')};
- flex-direction: ${props => props.direction};
- flex-wrap: ${props => props.flexWrap};
- justify-content: ${props => props.justify};
- align-items: ${props => props.align};
- ${props => cssRuleIfExists(props, 'width')}
- ${props => cssRuleIfExists(props, 'height')}
- background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')};
+ && {
+ all: initial;
+ display: ${props => (props.inline ? 'inline-flex' : 'flex')};
+ flex-direction: ${props => props.direction};
+ flex-wrap: ${props => props.flexWrap};
+ ${props => cssRuleIfExists(props, 'flexGrow')}
+ justify-content: ${props => props.justify};
+ align-items: ${props => props.align};
+ background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')};
+ ${props => (props.width ? stylesForMedia('width', props.width) : '')}
+ ${props => (props.height ? stylesForMedia('height', props.height) : '')}
+ }
`;
Flex.defaultProps = {
diff --git a/packages/instant/src/components/ui/icon.tsx b/packages/instant/src/components/ui/icon.tsx
index 94ea26900..2679dad1a 100644
--- a/packages/instant/src/components/ui/icon.tsx
+++ b/packages/instant/src/components/ui/icon.tsx
@@ -101,15 +101,17 @@ const PlainIcon: React.StatelessComponent<IconProps> = props => {
};
export const Icon = withTheme(styled(PlainIcon)`
- cursor: ${props => (!_.isUndefined(props.onClick) ? 'pointer' : 'default')};
- transition: opacity 0.5s ease;
- padding: ${props => props.padding};
- opacity: ${props => (!_.isUndefined(props.onClick) ? 0.7 : 1)};
- &:hover {
- opacity: 1;
- }
- &:active {
- opacity: 1;
+ && {
+ cursor: ${props => (!_.isUndefined(props.onClick) ? 'pointer' : 'default')};
+ transition: opacity 0.5s ease;
+ padding: ${props => props.padding};
+ opacity: ${props => (!_.isUndefined(props.onClick) ? 0.7 : 1)};
+ &:hover {
+ opacity: 1;
+ }
+ &:active {
+ opacity: 1;
+ }
}
`);
diff --git a/packages/instant/src/components/ui/index.ts b/packages/instant/src/components/ui/index.ts
deleted file mode 100644
index 87f5c11a1..000000000
--- a/packages/instant/src/components/ui/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export { Text, TextProps, Title } from './text';
-export { Circle, CircleProps } from './circle';
-export { Button, ButtonProps } from './button';
-export { Flex, FlexProps } from './flex';
-export { Container, ContainerProps } from './container';
-export { Input, InputProps } from './input';
-export { Icon, IconProps } from './icon';
-export { Spinner, SpinnerProps } from './spinner';
-export { Overlay, OverlayProps } from './overlay';
diff --git a/packages/instant/src/components/ui/input.tsx b/packages/instant/src/components/ui/input.tsx
index a884ff7cb..2fb408db4 100644
--- a/packages/instant/src/components/ui/input.tsx
+++ b/packages/instant/src/components/ui/input.tsx
@@ -16,17 +16,20 @@ export const Input =
styled.input <
InputProps >
`
- font-size: ${props => props.fontSize};
- width: ${props => props.width};
- padding: 0.1em 0em;
- font-family: 'Inter UI';
- color: ${props => props.theme[props.fontColor || 'white']};
- background: transparent;
- outline: none;
- border: none;
- &::placeholder {
+ && {
+ all: initial;
+ font-size: ${props => props.fontSize};
+ width: ${props => props.width};
+ padding: 0.1em 0em;
+ font-family: 'Inter UI';
color: ${props => props.theme[props.fontColor || 'white']};
- opacity: 0.5;
+ background: transparent;
+ outline: none;
+ border: none;
+ &::placeholder {
+ color: ${props => props.theme[props.fontColor || 'white']};
+ opacity: 0.5;
+ }
}
`;
diff --git a/packages/instant/src/components/ui/overlay.tsx b/packages/instant/src/components/ui/overlay.tsx
index f1706c874..8c9572615 100644
--- a/packages/instant/src/components/ui/overlay.tsx
+++ b/packages/instant/src/components/ui/overlay.tsx
@@ -15,20 +15,24 @@ export interface OverlayProps {
const PlainOverlay: React.StatelessComponent<OverlayProps> = ({ children, className, onClose }) => (
<Flex height="100vh" className={className}>
- <Container position="absolute" top="0px" right="0px">
+ <Container position="absolute" top="0px" right="0px" display={{ default: 'initial', sm: 'none' }}>
<Icon height={18} width={18} color={ColorOption.white} icon="closeX" onClick={onClose} padding="2em 2em" />
</Container>
- <div>{children}</div>
+ <Container width={{ default: 'auto', sm: '100%' }} height={{ default: 'auto', sm: '100%' }}>
+ {children}
+ </Container>
</Flex>
);
export const Overlay = styled(PlainOverlay)`
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: ${props => props.zIndex}
- background-color: ${overlayBlack};
+ && {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: ${props => props.zIndex}
+ background-color: ${overlayBlack};
+ }
`;
Overlay.defaultProps = {
diff --git a/packages/instant/src/components/ui/text.tsx b/packages/instant/src/components/ui/text.tsx
index cba6e7b20..c6a76ff18 100644
--- a/packages/instant/src/components/ui/text.tsx
+++ b/packages/instant/src/components/ui/text.tsx
@@ -27,25 +27,28 @@ export const Text =
styled.div <
TextProps >
`
- font-family: ${props => props.fontFamily};
- font-style: ${props => props.fontStyle};
- font-weight: ${props => props.fontWeight};
- font-size: ${props => props.fontSize};
- opacity: ${props => props.opacity};
- text-decoration-line: ${props => props.textDecorationLine};
- ${props => (props.lineHeight ? `line-height: ${props.lineHeight}` : '')};
- ${props => (props.center ? 'text-align: center' : '')};
- color: ${props => props.fontColor && props.theme[props.fontColor]};
- ${props => (props.minHeight ? `min-height: ${props.minHeight}` : '')};
- ${props => (props.onClick ? 'cursor: pointer' : '')};
- transition: color 0.5s ease;
- ${props => (props.noWrap ? 'white-space: nowrap' : '')};
- ${props => (props.display ? `display: ${props.display}` : '')};
- ${props => (props.letterSpacing ? `letter-spacing: ${props.letterSpacing}` : '')};
- ${props => (props.textTransform ? `text-transform: ${props.textTransform}` : '')};
- &:hover {
- ${props =>
- props.onClick ? `color: ${darken(darkenOnHoverAmount, props.theme[props.fontColor || 'white'])}` : ''};
+ && {
+ all: initial;
+ font-family: 'Inter UI', sans-serif;
+ font-style: ${props => props.fontStyle};
+ font-weight: ${props => props.fontWeight};
+ font-size: ${props => props.fontSize};
+ opacity: ${props => props.opacity};
+ text-decoration-line: ${props => props.textDecorationLine};
+ ${props => (props.lineHeight ? `line-height: ${props.lineHeight}` : '')};
+ ${props => (props.center ? 'text-align: center' : '')};
+ color: ${props => props.fontColor && props.theme[props.fontColor]};
+ ${props => (props.minHeight ? `min-height: ${props.minHeight}` : '')};
+ ${props => (props.onClick ? 'cursor: pointer' : '')};
+ transition: color 0.5s ease;
+ ${props => (props.noWrap ? 'white-space: nowrap' : '')};
+ ${props => (props.display ? `display: ${props.display}` : '')};
+ ${props => (props.letterSpacing ? `letter-spacing: ${props.letterSpacing}` : '')};
+ ${props => (props.textTransform ? `text-transform: ${props.textTransform}` : '')};
+ &:hover {
+ ${props =>
+ props.onClick ? `color: ${darken(darkenOnHoverAmount, props.theme[props.fontColor || 'white'])}` : ''};
+ }
}
`;
@@ -61,14 +64,3 @@ Text.defaultProps = {
};
Text.displayName = 'Text';
-
-export const Title: React.StatelessComponent<TextProps> = props => <Text {...props} />;
-
-Title.defaultProps = {
- fontSize: '20px',
- fontWeight: 600,
- opacity: 1,
- fontColor: ColorOption.primaryColor,
-};
-
-Title.displayName = 'Title';
diff --git a/packages/instant/src/components/zero_ex_instant.tsx b/packages/instant/src/components/zero_ex_instant.tsx
index 907c42e7a..b945f9908 100644
--- a/packages/instant/src/components/zero_ex_instant.tsx
+++ b/packages/instant/src/components/zero_ex_instant.tsx
@@ -1,5 +1,7 @@
import * as React from 'react';
+import { INJECTED_DIV_CLASS } from '../constants';
+
import { ZeroExInstantContainer } from './zero_ex_instant_container';
import { ZeroExInstantProvider, ZeroExInstantProviderProps } from './zero_ex_instant_provider';
@@ -7,8 +9,10 @@ export type ZeroExInstantProps = ZeroExInstantProviderProps;
export const ZeroExInstant: React.StatelessComponent<ZeroExInstantProps> = props => {
return (
- <ZeroExInstantProvider {...props}>
- <ZeroExInstantContainer />
- </ZeroExInstantProvider>
+ <div className={INJECTED_DIV_CLASS}>
+ <ZeroExInstantProvider {...props}>
+ <ZeroExInstantContainer />
+ </ZeroExInstantProvider>
+ </div>
);
};
diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx
index c1bd17502..d2216b54f 100644
--- a/packages/instant/src/components/zero_ex_instant_container.tsx
+++ b/packages/instant/src/components/zero_ex_instant_container.tsx
@@ -12,8 +12,11 @@ import { ColorOption } from '../style/theme';
import { zIndex } from '../style/z_index';
import { SlideAnimationState } from './animations/slide_animation';
+import { CSSReset } from './css_reset';
import { SlidingPanel } from './sliding_panel';
-import { Container, Flex } from './ui';
+import { Container } from './ui/container';
+import { Flex } from './ui/flex';
+
export interface ZeroExInstantContainerProps {}
export interface ZeroExInstantContainerState {
tokenSelectionPanelAnimationState: SlideAnimationState;
@@ -25,35 +28,43 @@ export class ZeroExInstantContainer extends React.Component<ZeroExInstantContain
};
public render(): React.ReactNode {
return (
- <Container width="350px" position="relative">
- <Container zIndex={zIndex.errorPopup} position="relative">
- <LatestError />
- </Container>
+ <React.Fragment>
+ <CSSReset />
<Container
- zIndex={zIndex.mainContainer}
+ width={{ default: '350px', sm: '100%' }}
+ height={{ default: 'auto', sm: '100%' }}
position="relative"
- backgroundColor={ColorOption.white}
- borderRadius="3px"
- hasBoxShadow={true}
- overflow="hidden"
>
- <Flex direction="column" justify="flex-start">
- <SelectedAssetInstantHeading onSelectAssetClick={this._handleSymbolClick} />
- <SelectedAssetBuyOrderProgress />
- <LatestBuyQuoteOrderDetails />
- <Container padding="20px" width="100%">
- <SelectedAssetBuyOrderStateButtons />
- </Container>
- </Flex>
- <SlidingPanel
- title="Select Token"
- animationState={this.state.tokenSelectionPanelAnimationState}
- onClose={this._handlePanelClose}
+ <Container position="relative">
+ <LatestError />
+ </Container>
+ <Container
+ zIndex={zIndex.mainContainer}
+ position="relative"
+ backgroundColor={ColorOption.white}
+ borderRadius="3px"
+ hasBoxShadow={true}
+ overflow="hidden"
+ height="100%"
>
- <AvailableERC20TokenSelector onTokenSelect={this._handlePanelClose} />
- </SlidingPanel>
+ <Flex direction="column" justify="flex-start" height="100%">
+ <SelectedAssetInstantHeading onSelectAssetClick={this._handleSymbolClick} />
+ <SelectedAssetBuyOrderProgress />
+ <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>
+ </Container>
</Container>
- </Container>
+ </React.Fragment>
);
}
private readonly _handleSymbolClick = (): void => {
diff --git a/packages/instant/src/components/zero_ex_instant_overlay.tsx b/packages/instant/src/components/zero_ex_instant_overlay.tsx
index 8f872f896..3461600e1 100644
--- a/packages/instant/src/components/zero_ex_instant_overlay.tsx
+++ b/packages/instant/src/components/zero_ex_instant_overlay.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
-import { Overlay } from './ui';
+import { Overlay } from './ui/overlay';
import { ZeroExInstantContainer } from './zero_ex_instant_container';
import { ZeroExInstantProvider, ZeroExInstantProviderProps } from './zero_ex_instant_provider';
diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx
index 0b9408329..58e78c522 100644
--- a/packages/instant/src/components/zero_ex_instant_provider.tsx
+++ b/packages/instant/src/components/zero_ex_instant_provider.tsx
@@ -1,23 +1,20 @@
-import { AssetBuyer } from '@0x/asset-buyer';
-import { ObjectMap, SignedOrder } from '@0x/types';
+import { ObjectMap } from '@0x/types';
import { BigNumber } from '@0x/utils';
-import { Web3Wrapper } from '@0x/web3-wrapper';
import { Provider } from 'ethereum-types';
import * as _ from 'lodash';
import * as React from 'react';
import { Provider as ReduxProvider } from 'react-redux';
-import { oc } from 'ts-optchain';
import { SelectedAssetThemeProvider } from '../containers/selected_asset_theme_provider';
import { asyncData } from '../redux/async_data';
-import { INITIAL_STATE, State } from '../redux/reducer';
+import { DEFAULT_STATE, DefaultState, State } from '../redux/reducer';
import { store, Store } from '../redux/store';
import { fonts } from '../style/fonts';
-import { AffiliateInfo, AssetMetaData, Network } from '../types';
+import { AffiliateInfo, AssetMetaData, Network, OrderSource } from '../types';
import { assetUtils } from '../util/asset';
import { errorFlasher } from '../util/error_flasher';
import { gasPriceEstimator } from '../util/gas_price_estimator';
-import { getInjectedProvider } from '../util/injected_provider';
+import { providerStateFactory } from '../util/provider_state_factory';
fonts.include();
@@ -25,7 +22,7 @@ export type ZeroExInstantProviderProps = ZeroExInstantProviderRequiredProps &
Partial<ZeroExInstantProviderOptionalProps>;
export interface ZeroExInstantProviderRequiredProps {
- orderSource: string | SignedOrder[];
+ orderSource: OrderSource;
}
export interface ZeroExInstantProviderOptionalProps {
@@ -41,30 +38,27 @@ export interface ZeroExInstantProviderOptionalProps {
export class ZeroExInstantProvider extends React.Component<ZeroExInstantProviderProps> {
private readonly _store: Store;
// TODO(fragosti): Write tests for this beast once we inject a provider.
- private static _mergeInitialStateWithProps(props: ZeroExInstantProviderProps, state: State = INITIAL_STATE): State {
- const networkId = props.networkId || state.network;
- // TODO: Proper wallet connect flow
- const provider = props.provider || getInjectedProvider();
- const assetBuyerOptions = {
+ private static _mergeDefaultStateWithProps(
+ props: ZeroExInstantProviderProps,
+ defaultState: DefaultState = DEFAULT_STATE,
+ ): State {
+ // use the networkId passed in with the props, otherwise default to that of the default state (1, mainnet)
+ const networkId = props.networkId || defaultState.network;
+ // construct the ProviderState
+ const providerState = providerStateFactory.getInitialProviderState(
+ props.orderSource,
networkId,
- };
- let assetBuyer;
- if (_.isString(props.orderSource)) {
- assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(
- provider,
- props.orderSource,
- assetBuyerOptions,
- );
- } else {
- assetBuyer = AssetBuyer.getAssetBuyerForProvidedOrders(provider, props.orderSource, assetBuyerOptions);
- }
+ props.provider,
+ );
+ // merge the additional additionalAssetMetaDataMap with our default map
const completeAssetMetaDataMap = {
...props.additionalAssetMetaDataMap,
- ...state.assetMetaDataMap,
+ ...defaultState.assetMetaDataMap,
};
+ // construct the final state
const storeStateFromProps: State = {
- ...state,
- assetBuyer,
+ ...defaultState,
+ providerState,
network: networkId,
selectedAsset: _.isUndefined(props.defaultSelectedAssetData)
? undefined
@@ -74,7 +68,7 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
networkId,
),
selectedAssetAmount: _.isUndefined(props.defaultAssetBuyAmount)
- ? state.selectedAssetAmount
+ ? undefined
: new BigNumber(props.defaultAssetBuyAmount),
availableAssets: _.isUndefined(props.availableAssetDatas)
? undefined
@@ -86,10 +80,9 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
}
constructor(props: ZeroExInstantProviderProps) {
super(props);
- const initialAppState = ZeroExInstantProvider._mergeInitialStateWithProps(this.props, INITIAL_STATE);
+ const initialAppState = ZeroExInstantProvider._mergeDefaultStateWithProps(this.props);
this._store = store.create(initialAppState);
}
-
public componentDidMount(): void {
const state = this._store.getState();
// tslint:disable-next-line:no-floating-promises
@@ -99,16 +92,15 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
// tslint:disable-next-line:no-floating-promises
asyncData.fetchAvailableAssetDatasAndDispatchToStore(this._store);
}
-
+ // tslint:disable-next-line:no-floating-promises
+ asyncData.fetchCurrentBuyQuoteAndDispatchToStore(this._store);
// 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
gasPriceEstimator.getGasInfoAsync();
-
// tslint:disable-next-line:no-floating-promises
this._flashErrorIfWrongNetwork();
}
-
public render(): React.ReactNode {
return (
<ReduxProvider store={this._store}>
@@ -116,19 +108,15 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
</ReduxProvider>
);
}
-
private readonly _flashErrorIfWrongNetwork = async (): Promise<void> => {
const msToShowError = 30000; // 30 seconds
- const network = this._store.getState().network;
- const assetBuyerIfExists = this._store.getState().assetBuyer;
- const providerIfExists = oc(assetBuyerIfExists).provider();
- if (!_.isUndefined(providerIfExists)) {
- const web3Wrapper = new Web3Wrapper(providerIfExists);
- const networkOfProvider = await web3Wrapper.getNetworkIdAsync();
- if (network !== networkOfProvider) {
- const errorMessage = `Wrong network detected. Try switching to ${Network[network]}.`;
- errorFlasher.flashNewErrorMessage(this._store.dispatch, errorMessage, msToShowError);
- }
+ const state = this._store.getState();
+ const network = state.network;
+ const web3Wrapper = state.providerState.web3Wrapper;
+ const networkOfProvider = await web3Wrapper.getNetworkIdAsync();
+ if (network !== networkOfProvider) {
+ const errorMessage = `Wrong network detected. Try switching to ${Network[network]}.`;
+ errorFlasher.flashNewErrorMessage(this._store.dispatch, errorMessage, msToShowError);
}
};
}
diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts
index 15105b65c..34548f26f 100644
--- a/packages/instant/src/constants.ts
+++ b/packages/instant/src/constants.ts
@@ -1,7 +1,11 @@
import { BigNumber } from '@0x/utils';
+
+import { Network } from './types';
+
export const BIG_NUMBER_ZERO = new BigNumber(0);
export const ETH_DECIMALS = 18;
export const DEFAULT_ZERO_EX_CONTAINER_SELECTOR = '#zeroExInstantContainer';
+export const INJECTED_DIV_CLASS = 'zeroExInstantResetRoot';
export const INJECTED_DIV_ID = 'zeroExInstant';
export const WEB_3_WRAPPER_TRANSACTION_FAILED_ERROR_MSG_PREFIX = 'Transaction failed';
export const GWEI_IN_WEI = new BigNumber(1000000000);
@@ -13,3 +17,8 @@ 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 ETHEREUM_NODE_URL_BY_NETWORK = {
+ [Network.Mainnet]: 'https://mainnet.infura.io/',
+ [Network.Kovan]: 'https://kovan.infura.io/',
+};
+export const BLOCK_POLLING_INTERVAL_MS = 10000; // 10s
diff --git a/packages/instant/src/containers/latest_buy_quote_order_details.ts b/packages/instant/src/containers/latest_buy_quote_order_details.ts
index 092aaaf20..2b59ed3ae 100644
--- a/packages/instant/src/containers/latest_buy_quote_order_details.ts
+++ b/packages/instant/src/containers/latest_buy_quote_order_details.ts
@@ -22,7 +22,7 @@ const mapStateToProps = (state: State, _ownProps: LatestBuyQuoteOrderDetailsProp
// use the worst case quote info
buyQuoteInfo: oc(state).latestBuyQuote.worstCaseQuoteInfo(),
ethUsdPrice: state.ethUsdPrice,
- isLoading: state.quoteRequestState === AsyncProcessState.PENDING,
+ isLoading: state.quoteRequestState === AsyncProcessState.Pending,
});
export const LatestBuyQuoteOrderDetails: React.ComponentClass<LatestBuyQuoteOrderDetailsProps> = connect(
diff --git a/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts b/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts
index 72d99f844..c3a5e88b9 100644
--- a/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts
+++ b/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts
@@ -14,7 +14,7 @@ import { etherscanUtil } from '../util/etherscan';
interface ConnectedState {
buyQuote?: BuyQuote;
buyOrderProcessingState: OrderProcessState;
- assetBuyer?: AssetBuyer;
+ assetBuyer: AssetBuyer;
affiliateInfo?: AffiliateInfo;
onViewTransaction: () => void;
}
@@ -29,29 +29,31 @@ interface ConnectedDispatch {
onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void;
}
export interface SelectedAssetBuyOrderStateButtons {}
-const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButtons): ConnectedState => ({
- buyOrderProcessingState: state.buyOrderState.processState,
- assetBuyer: state.assetBuyer,
- buyQuote: state.latestBuyQuote,
- affiliateInfo: state.affiliateInfo,
- onViewTransaction: () => {
- if (
- state.assetBuyer &&
- (state.buyOrderState.processState === OrderProcessState.PROCESSING ||
- state.buyOrderState.processState === OrderProcessState.SUCCESS ||
- state.buyOrderState.processState === OrderProcessState.FAILURE)
- ) {
- const etherscanUrl = etherscanUtil.getEtherScanTxnAddressIfExists(
- state.buyOrderState.txHash,
- state.assetBuyer.networkId,
- );
- if (etherscanUrl) {
- window.open(etherscanUrl, '_blank');
- return;
+const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButtons): ConnectedState => {
+ const assetBuyer = state.providerState.assetBuyer;
+ return {
+ buyOrderProcessingState: state.buyOrderState.processState,
+ assetBuyer,
+ buyQuote: state.latestBuyQuote,
+ affiliateInfo: state.affiliateInfo,
+ onViewTransaction: () => {
+ if (
+ state.buyOrderState.processState === OrderProcessState.Processing ||
+ state.buyOrderState.processState === OrderProcessState.Success ||
+ state.buyOrderState.processState === OrderProcessState.Failure
+ ) {
+ const etherscanUrl = etherscanUtil.getEtherScanTxnAddressIfExists(
+ state.buyOrderState.txHash,
+ assetBuyer.networkId,
+ );
+ if (etherscanUrl) {
+ window.open(etherscanUrl, '_blank');
+ return;
+ }
}
- }
- },
-});
+ },
+ };
+};
const mapDispatchToProps = (
dispatch: Dispatch<Action>,
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 f7d56c564..c550aef04 100644
--- a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
+++ b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
@@ -1,20 +1,17 @@
-import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer';
+import { AssetBuyer } from '@0x/asset-buyer';
import { AssetProxyId } from '@0x/types';
import { BigNumber } from '@0x/utils';
-import { Web3Wrapper } from '@0x/web3-wrapper';
import * as _ from 'lodash';
import * as React from 'react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
-import { oc } from 'ts-optchain';
import { ERC20AssetAmountInput } 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 { assetUtils } from '../util/asset';
-import { errorFlasher } from '../util/error_flasher';
+import { buyQuoteUpdater } from '../util/buy_quote_updater';
export interface SelectedERC20AssetAmountInputProps {
fontColor?: ColorOption;
@@ -23,7 +20,7 @@ export interface SelectedERC20AssetAmountInputProps {
}
interface ConnectedState {
- assetBuyer?: AssetBuyer;
+ assetBuyer: AssetBuyer;
value?: BigNumber;
asset?: ERC20Asset;
isDisabled: boolean;
@@ -33,7 +30,7 @@ interface ConnectedState {
interface ConnectedDispatch {
updateBuyQuote: (
- assetBuyer?: AssetBuyer,
+ assetBuyer: AssetBuyer,
value?: BigNumber,
asset?: ERC20Asset,
affiliateInfo?: AffiliateInfo,
@@ -52,15 +49,16 @@ type FinalProps = ConnectedProps & SelectedERC20AssetAmountInputProps;
const mapStateToProps = (state: State, _ownProps: SelectedERC20AssetAmountInputProps): ConnectedState => {
const processState = state.buyOrderState.processState;
- const isEnabled = processState === OrderProcessState.NONE || processState === OrderProcessState.FAILURE;
+ const isEnabled = processState === OrderProcessState.None || processState === OrderProcessState.Failure;
const isDisabled = !isEnabled;
const selectedAsset =
!_.isUndefined(state.selectedAsset) && state.selectedAsset.metaData.assetProxyId === AssetProxyId.ERC20
? (state.selectedAsset as ERC20Asset)
: undefined;
const numberOfAssetsAvailable = _.isUndefined(state.availableAssets) ? undefined : state.availableAssets.length;
+ const assetBuyer = state.providerState.assetBuyer;
return {
- assetBuyer: state.assetBuyer,
+ assetBuyer,
value: state.selectedAssetAmount,
asset: selectedAsset,
isDisabled,
@@ -69,52 +67,9 @@ const mapStateToProps = (state: State, _ownProps: SelectedERC20AssetAmountInputP
};
};
-const updateBuyQuoteAsync = async (
- assetBuyer: AssetBuyer,
- dispatch: Dispatch<Action>,
- asset: ERC20Asset,
- assetAmount: BigNumber,
- affiliateInfo?: AffiliateInfo,
-): Promise<void> => {
- // get a new buy quote.
- const baseUnitValue = Web3Wrapper.toBaseUnitAmount(assetAmount, asset.metaData.decimals);
-
- // mark quote as pending
- dispatch(actions.setQuoteRequestStatePending());
-
- const feePercentage = oc(affiliateInfo).feePercentage();
- let newBuyQuote: BuyQuote | undefined;
- try {
- newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue, { feePercentage });
- } catch (error) {
- dispatch(actions.setQuoteRequestStateFailure());
- let errorMessage;
- if (error.message === AssetBuyerError.InsufficientAssetLiquidity) {
- const assetName = assetUtils.bestNameForAsset(asset, 'of this asset');
- errorMessage = `Not enough ${assetName} available`;
- } else if (error.message === AssetBuyerError.InsufficientZrxLiquidity) {
- errorMessage = 'Not enough ZRX available';
- } else if (
- error.message === AssetBuyerError.StandardRelayerApiError ||
- error.message.startsWith(AssetBuyerError.AssetUnavailable)
- ) {
- const assetName = assetUtils.bestNameForAsset(asset, 'This asset');
- errorMessage = `${assetName} is currently unavailable`;
- }
- if (!_.isUndefined(errorMessage)) {
- errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
- } else {
- throw error;
- }
- return;
- }
- // We have a successful new buy quote
- errorFlasher.clearError(dispatch);
- // invalidate the last buy quote.
- dispatch(actions.updateLatestBuyQuote(newBuyQuote));
-};
-
-const debouncedUpdateBuyQuoteAsync = _.debounce(updateBuyQuoteAsync, 200, { trailing: true });
+const debouncedUpdateBuyQuoteAsync = _.debounce(buyQuoteUpdater.updateBuyQuoteAsync.bind(buyQuoteUpdater), 200, {
+ trailing: true,
+});
const mapDispatchToProps = (
dispatch: Dispatch<Action>,
@@ -128,7 +83,7 @@ const mapDispatchToProps = (
// reset our buy state
dispatch(actions.setBuyOrderStateNone());
- if (!_.isUndefined(value) && value.greaterThan(0) && !_.isUndefined(asset) && !_.isUndefined(assetBuyer)) {
+ if (!_.isUndefined(value) && value.greaterThan(0) && !_.isUndefined(asset)) {
// even if it's debounced, give them the illusion it's loading
dispatch(actions.setQuoteRequestStatePending());
// tslint:disable-next-line:no-floating-promises
diff --git a/packages/instant/src/data/asset_data_network_mapping.ts b/packages/instant/src/data/asset_data_network_mapping.ts
index 43bd34697..4fd0a25ed 100644
--- a/packages/instant/src/data/asset_data_network_mapping.ts
+++ b/packages/instant/src/data/asset_data_network_mapping.ts
@@ -26,7 +26,8 @@ export const assetDataNetworkMapping: AssetDataByNetwork[] = [
// MKR
{
[Network.Mainnet]: '0xf47261b00000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2',
- [Network.Kovan]: '0xf47261b00000000000000000000000000xbf5e8e38659562fda594fbb3ec5a3576d02a9e0a',
+ // 0x Kovan MKR
+ [Network.Kovan]: '0xf47261b00000000000000000000000007b6b10caa9e8e9552ba72638ea5b47c25afea1f3',
},
// BAT
{
@@ -45,8 +46,9 @@ export const assetDataNetworkMapping: AssetDataByNetwork[] = [
},
// GNT
{
- [Network.Mainnet]: '0xf47261b0000000000000000000000000a74476443119A942dE498590Fe1f2454d7D4aC0d',
- [Network.Kovan]: '0xf47261b00000000000000000000000006986fa3646f408905ecb1876bfd355d25039ee3a',
+ [Network.Mainnet]: '0xf47261b0000000000000000000000000a74476443119a942de498590fe1f2454d7d4ac0d',
+ // 0x Kovan GNT
+ [Network.Kovan]: '0xf47261b000000000000000000000000031fb614e223706f15d0d3c5f4b08bdf0d5c78623',
},
// SUB
{
diff --git a/packages/instant/src/data/asset_meta_data_map.ts b/packages/instant/src/data/asset_meta_data_map.ts
index 8a0f29e21..970b6c383 100644
--- a/packages/instant/src/data/asset_meta_data_map.ts
+++ b/packages/instant/src/data/asset_meta_data_map.ts
@@ -54,7 +54,7 @@ export const assetMetaDataMap: ObjectMap<AssetMetaData> = {
symbol: 'mana',
name: 'Decentraland',
},
- '0xf47261b0000000000000000000000000a74476443119A942dE498590Fe1f2454d7D4aC0d': {
+ '0xf47261b0000000000000000000000000a74476443119a942de498590fe1f2454d7d4ac0d': {
assetProxyId: AssetProxyId.ERC20,
decimals: 18,
primaryColor: '#263469',
diff --git a/packages/instant/src/index.umd.ts b/packages/instant/src/index.umd.ts
index 59d1e646f..0274db30c 100644
--- a/packages/instant/src/index.umd.ts
+++ b/packages/instant/src/index.umd.ts
@@ -2,7 +2,7 @@ import * as _ from 'lodash';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
-import { DEFAULT_ZERO_EX_CONTAINER_SELECTOR, INJECTED_DIV_ID } from './constants';
+import { DEFAULT_ZERO_EX_CONTAINER_SELECTOR, INJECTED_DIV_CLASS, INJECTED_DIV_ID } from './constants';
import { ZeroExInstantOverlay, ZeroExInstantOverlayProps } from './index';
import { assert } from './util/assert';
@@ -41,6 +41,7 @@ export const render = (props: ZeroExInstantOverlayProps, selector: string = DEFA
const appendTo = appendToIfExists as Element;
const injectedDiv = document.createElement('div');
injectedDiv.setAttribute('id', INJECTED_DIV_ID);
+ injectedDiv.setAttribute('class', INJECTED_DIV_CLASS);
appendTo.appendChild(injectedDiv);
const instantOverlayProps = {
...props,
diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts
index 0e05c13da..839a90778 100644
--- a/packages/instant/src/redux/async_data.ts
+++ b/packages/instant/src/redux/async_data.ts
@@ -1,7 +1,10 @@
+import { AssetProxyId } from '@0x/types';
import * as _ from 'lodash';
import { BIG_NUMBER_ZERO } from '../constants';
+import { ERC20Asset } 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';
@@ -20,18 +23,34 @@ export const asyncData = {
}
},
fetchAvailableAssetDatasAndDispatchToStore: async (store: Store) => {
- const { assetBuyer, assetMetaDataMap, network } = store.getState();
- if (!_.isUndefined(assetBuyer)) {
- try {
- const assetDatas = await assetBuyer.getAvailableAssetDatasAsync();
- const assets = assetUtils.createAssetsFromAssetDatas(assetDatas, assetMetaDataMap, network);
- store.dispatch(actions.setAvailableAssets(assets));
- } catch (e) {
- const errorMessage = 'Could not find any assets';
- errorFlasher.flashNewErrorMessage(store.dispatch, errorMessage);
- // On error, just specify that none are available
- store.dispatch(actions.setAvailableAssets([]));
- }
+ const { providerState, assetMetaDataMap, network } = store.getState();
+ const assetBuyer = providerState.assetBuyer;
+ try {
+ const assetDatas = await assetBuyer.getAvailableAssetDatasAsync();
+ const assets = assetUtils.createAssetsFromAssetDatas(assetDatas, assetMetaDataMap, network);
+ store.dispatch(actions.setAvailableAssets(assets));
+ } catch (e) {
+ const errorMessage = 'Could not find any assets';
+ errorFlasher.flashNewErrorMessage(store.dispatch, errorMessage);
+ // On error, just specify that none are available
+ store.dispatch(actions.setAvailableAssets([]));
+ }
+ },
+ fetchCurrentBuyQuoteAndDispatchToStore: async (store: Store) => {
+ const { providerState, selectedAsset, selectedAssetAmount, affiliateInfo } = store.getState();
+ const assetBuyer = providerState.assetBuyer;
+ if (
+ !_.isUndefined(selectedAssetAmount) &&
+ !_.isUndefined(selectedAsset) &&
+ selectedAsset.metaData.assetProxyId === AssetProxyId.ERC20
+ ) {
+ await buyQuoteUpdater.updateBuyQuoteAsync(
+ assetBuyer,
+ store.dispatch,
+ selectedAsset as ERC20Asset,
+ selectedAssetAmount,
+ affiliateInfo,
+ );
}
},
};
diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts
index bc435d069..4a939839a 100644
--- a/packages/instant/src/redux/reducer.ts
+++ b/packages/instant/src/redux/reducer.ts
@@ -1,4 +1,4 @@
-import { AssetBuyer, BuyQuote } from '@0x/asset-buyer';
+import { BuyQuote } from '@0x/asset-buyer';
import { AssetProxyId, ObjectMap } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
@@ -14,172 +14,181 @@ import {
Network,
OrderProcessState,
OrderState,
+ ProviderState,
} from '../types';
import { Action, ActionTypes } from './actions';
-export interface State {
+// State that is required and we have defaults for, before props are passed in
+export interface DefaultState {
network: Network;
- assetBuyer?: AssetBuyer;
assetMetaDataMap: ObjectMap<AssetMetaData>;
- selectedAsset?: Asset;
- availableAssets?: Asset[];
- selectedAssetAmount?: BigNumber;
buyOrderState: OrderState;
- ethUsdPrice?: BigNumber;
- latestBuyQuote?: BuyQuote;
- quoteRequestState: AsyncProcessState;
- latestErrorMessage?: string;
latestErrorDisplayStatus: DisplayStatus;
- affiliateInfo?: AffiliateInfo;
+ quoteRequestState: AsyncProcessState;
+}
+
+// State that is required but needs to be derived from the props
+interface PropsDerivedState {
+ providerState: ProviderState;
}
-export const INITIAL_STATE: State = {
+// State that is optional
+interface OptionalState {
+ selectedAsset: Asset;
+ availableAssets: Asset[];
+ selectedAssetAmount: BigNumber;
+ ethUsdPrice: BigNumber;
+ latestBuyQuote: BuyQuote;
+ latestErrorMessage: string;
+ affiliateInfo: AffiliateInfo;
+}
+
+export type State = DefaultState & PropsDerivedState & Partial<OptionalState>;
+
+export const DEFAULT_STATE: DefaultState = {
network: Network.Mainnet,
- selectedAssetAmount: undefined,
- availableAssets: undefined,
assetMetaDataMap,
- buyOrderState: { processState: OrderProcessState.NONE },
- ethUsdPrice: undefined,
- latestBuyQuote: undefined,
- latestErrorMessage: undefined,
+ buyOrderState: { processState: OrderProcessState.None },
latestErrorDisplayStatus: DisplayStatus.Hidden,
- quoteRequestState: AsyncProcessState.NONE,
- affiliateInfo: undefined,
+ quoteRequestState: AsyncProcessState.None,
};
-export const reducer = (state: State = INITIAL_STATE, action: Action): State => {
- switch (action.type) {
- case ActionTypes.UPDATE_ETH_USD_PRICE:
- return {
- ...state,
- ethUsdPrice: action.data,
- };
- case ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT:
- return {
- ...state,
- selectedAssetAmount: action.data,
- };
- case ActionTypes.UPDATE_LATEST_BUY_QUOTE:
- const newBuyQuoteIfExists = action.data;
- const shouldUpdate =
- _.isUndefined(newBuyQuoteIfExists) || doesBuyQuoteMatchState(newBuyQuoteIfExists, state);
- if (shouldUpdate) {
+export const createReducer = (initialState: State) => {
+ const reducer = (state: State = initialState, action: Action): State => {
+ switch (action.type) {
+ case ActionTypes.UPDATE_ETH_USD_PRICE:
return {
...state,
- latestBuyQuote: newBuyQuoteIfExists,
- quoteRequestState: AsyncProcessState.SUCCESS,
+ ethUsdPrice: action.data,
};
- } else {
- return state;
- }
-
- case ActionTypes.SET_QUOTE_REQUEST_STATE_PENDING:
- return {
- ...state,
- latestBuyQuote: undefined,
- quoteRequestState: AsyncProcessState.PENDING,
- };
- case ActionTypes.SET_QUOTE_REQUEST_STATE_FAILURE:
- return {
- ...state,
- latestBuyQuote: undefined,
- quoteRequestState: AsyncProcessState.FAILURE,
- };
- case ActionTypes.SET_BUY_ORDER_STATE_NONE:
- return {
- ...state,
- buyOrderState: { processState: OrderProcessState.NONE },
- };
- case ActionTypes.SET_BUY_ORDER_STATE_VALIDATING:
- return {
- ...state,
- buyOrderState: { processState: OrderProcessState.VALIDATING },
- };
- case ActionTypes.SET_BUY_ORDER_STATE_PROCESSING:
- const processingData = action.data;
- const { startTimeUnix, expectedEndTimeUnix } = processingData;
- return {
- ...state,
- buyOrderState: {
- processState: OrderProcessState.PROCESSING,
- txHash: processingData.txHash,
- progress: {
- startTimeUnix,
- expectedEndTimeUnix,
- },
- },
- };
- case ActionTypes.SET_BUY_ORDER_STATE_FAILURE:
- const failureTxHash = action.data;
- if ('txHash' in state.buyOrderState) {
- if (state.buyOrderState.txHash === failureTxHash) {
- const { txHash, progress } = state.buyOrderState;
+ case ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT:
+ return {
+ ...state,
+ selectedAssetAmount: action.data,
+ };
+ case ActionTypes.UPDATE_LATEST_BUY_QUOTE:
+ const newBuyQuoteIfExists = action.data;
+ const shouldUpdate =
+ _.isUndefined(newBuyQuoteIfExists) || doesBuyQuoteMatchState(newBuyQuoteIfExists, state);
+ if (shouldUpdate) {
return {
...state,
- buyOrderState: {
- processState: OrderProcessState.FAILURE,
- txHash,
- progress,
- },
+ latestBuyQuote: newBuyQuoteIfExists,
+ quoteRequestState: AsyncProcessState.Success,
};
+ } else {
+ return state;
}
- }
- return state;
- case ActionTypes.SET_BUY_ORDER_STATE_SUCCESS:
- const successTxHash = action.data;
- if ('txHash' in state.buyOrderState) {
- if (state.buyOrderState.txHash === successTxHash) {
- const { txHash, progress } = state.buyOrderState;
- return {
- ...state,
- buyOrderState: {
- processState: OrderProcessState.SUCCESS,
- txHash,
- progress,
+
+ case ActionTypes.SET_QUOTE_REQUEST_STATE_PENDING:
+ return {
+ ...state,
+ latestBuyQuote: undefined,
+ quoteRequestState: AsyncProcessState.Pending,
+ };
+ case ActionTypes.SET_QUOTE_REQUEST_STATE_FAILURE:
+ return {
+ ...state,
+ latestBuyQuote: undefined,
+ quoteRequestState: AsyncProcessState.Failure,
+ };
+ case ActionTypes.SET_BUY_ORDER_STATE_NONE:
+ return {
+ ...state,
+ buyOrderState: { processState: OrderProcessState.None },
+ };
+ case ActionTypes.SET_BUY_ORDER_STATE_VALIDATING:
+ return {
+ ...state,
+ buyOrderState: { processState: OrderProcessState.Validating },
+ };
+ case ActionTypes.SET_BUY_ORDER_STATE_PROCESSING:
+ const processingData = action.data;
+ const { startTimeUnix, expectedEndTimeUnix } = processingData;
+ return {
+ ...state,
+ buyOrderState: {
+ processState: OrderProcessState.Processing,
+ txHash: processingData.txHash,
+ progress: {
+ startTimeUnix,
+ expectedEndTimeUnix,
},
- };
+ },
+ };
+ case ActionTypes.SET_BUY_ORDER_STATE_FAILURE:
+ const failureTxHash = action.data;
+ if ('txHash' in state.buyOrderState) {
+ if (state.buyOrderState.txHash === failureTxHash) {
+ const { txHash, progress } = state.buyOrderState;
+ return {
+ ...state,
+ buyOrderState: {
+ processState: OrderProcessState.Failure,
+ txHash,
+ progress,
+ },
+ };
+ }
}
- }
- return state;
- case ActionTypes.SET_ERROR_MESSAGE:
- return {
- ...state,
- latestErrorMessage: action.data,
- latestErrorDisplayStatus: DisplayStatus.Present,
- };
- case ActionTypes.HIDE_ERROR:
- return {
- ...state,
- latestErrorDisplayStatus: DisplayStatus.Hidden,
- };
- case ActionTypes.CLEAR_ERROR:
- return {
- ...state,
- latestErrorMessage: undefined,
- latestErrorDisplayStatus: DisplayStatus.Hidden,
- };
- case ActionTypes.UPDATE_SELECTED_ASSET:
- return {
- ...state,
- selectedAsset: action.data,
- };
- case ActionTypes.RESET_AMOUNT:
- return {
- ...state,
- latestBuyQuote: undefined,
- quoteRequestState: AsyncProcessState.NONE,
- buyOrderState: { processState: OrderProcessState.NONE },
- selectedAssetAmount: undefined,
- };
- case ActionTypes.SET_AVAILABLE_ASSETS:
- return {
- ...state,
- availableAssets: action.data,
- };
- default:
- return state;
- }
+ return state;
+ case ActionTypes.SET_BUY_ORDER_STATE_SUCCESS:
+ const successTxHash = action.data;
+ if ('txHash' in state.buyOrderState) {
+ if (state.buyOrderState.txHash === successTxHash) {
+ const { txHash, progress } = state.buyOrderState;
+ return {
+ ...state,
+ buyOrderState: {
+ processState: OrderProcessState.Success,
+ txHash,
+ progress,
+ },
+ };
+ }
+ }
+ return state;
+ case ActionTypes.SET_ERROR_MESSAGE:
+ return {
+ ...state,
+ latestErrorMessage: action.data,
+ latestErrorDisplayStatus: DisplayStatus.Present,
+ };
+ case ActionTypes.HIDE_ERROR:
+ return {
+ ...state,
+ latestErrorDisplayStatus: DisplayStatus.Hidden,
+ };
+ case ActionTypes.CLEAR_ERROR:
+ return {
+ ...state,
+ latestErrorMessage: undefined,
+ latestErrorDisplayStatus: DisplayStatus.Hidden,
+ };
+ case ActionTypes.UPDATE_SELECTED_ASSET:
+ return {
+ ...state,
+ selectedAsset: action.data,
+ };
+ case ActionTypes.RESET_AMOUNT:
+ return {
+ ...state,
+ latestBuyQuote: undefined,
+ quoteRequestState: AsyncProcessState.None,
+ buyOrderState: { processState: OrderProcessState.None },
+ selectedAssetAmount: undefined,
+ };
+ case ActionTypes.SET_AVAILABLE_ASSETS:
+ return {
+ ...state,
+ availableAssets: action.data,
+ };
+ default:
+ return state;
+ }
+ };
+ return reducer;
};
const doesBuyQuoteMatchState = (buyQuote: BuyQuote, state: State): boolean => {
diff --git a/packages/instant/src/redux/store.ts b/packages/instant/src/redux/store.ts
index 01deb8690..20710765d 100644
--- a/packages/instant/src/redux/store.ts
+++ b/packages/instant/src/redux/store.ts
@@ -2,12 +2,13 @@ import * as _ from 'lodash';
import { createStore, Store as ReduxStore } from 'redux';
import { devToolsEnhancer } from 'redux-devtools-extension/developmentOnly';
-import { reducer, State } from './reducer';
+import { createReducer, State } from './reducer';
export type Store = ReduxStore<State>;
export const store = {
- create: (state: State): Store => {
- return createStore(reducer, state, devToolsEnhancer({}));
+ create: (initialState: State): Store => {
+ const reducer = createReducer(initialState);
+ return createStore(reducer, initialState, devToolsEnhancer({}));
},
};
diff --git a/packages/instant/src/style/media.ts b/packages/instant/src/style/media.ts
new file mode 100644
index 000000000..5e7aaba37
--- /dev/null
+++ b/packages/instant/src/style/media.ts
@@ -0,0 +1,51 @@
+import { InterpolationValue } from 'styled-components';
+
+import { css } from './theme';
+
+export enum ScreenWidths {
+ Sm = 40,
+ Md = 52,
+ Lg = 64,
+}
+
+const generateMediaWrapper = (screenWidth: ScreenWidths) => (...args: any[]) => css`
+ @media (max-width: ${screenWidth}em) {
+ ${css.apply(css, args)};
+ }
+`;
+
+export const media = {
+ small: generateMediaWrapper(ScreenWidths.Sm),
+ medium: generateMediaWrapper(ScreenWidths.Md),
+ large: generateMediaWrapper(ScreenWidths.Lg),
+};
+
+export interface ScreenSpecification<T> {
+ default: T;
+ sm?: T;
+ md?: T;
+ lg?: T;
+}
+export type OptionallyScreenSpecific<T> = T | ScreenSpecification<T>;
+export type MediaChoice = OptionallyScreenSpecific<string>;
+/**
+ * Given a css property name and a OptionallyScreenSpecific value,
+ * generates css properties with screen-specific viewport styling
+ */
+export function stylesForMedia<T extends string | number>(
+ cssPropertyName: string,
+ choice: OptionallyScreenSpecific<T>,
+): InterpolationValue[] {
+ if (typeof choice === 'object') {
+ return css`
+ ${cssPropertyName}: ${choice.default};
+ ${choice.lg && media.large`${cssPropertyName}: ${choice.lg}`}
+ ${choice.md && media.medium`${cssPropertyName}: ${choice.md}`}
+ ${choice.sm && media.small`${cssPropertyName}: ${choice.sm}`}
+ `;
+ } else {
+ return css`
+ ${cssPropertyName}: ${choice};
+ `;
+ }
+}
diff --git a/packages/instant/src/style/theme.ts b/packages/instant/src/style/theme.ts
index d10c9b72c..8dada2d28 100644
--- a/packages/instant/src/style/theme.ts
+++ b/packages/instant/src/style/theme.ts
@@ -1,6 +1,6 @@
import * as styledComponents from 'styled-components';
-const { default: styled, css, keyframes, withTheme, ThemeProvider } = styledComponents;
+const { default: styled, css, keyframes, withTheme, createGlobalStyle, ThemeProvider } = styledComponents;
export type Theme = { [key in ColorOption]: string };
@@ -33,4 +33,4 @@ export const theme: Theme = {
export const transparentWhite = 'rgba(255,255,255,0.3)';
export const overlayBlack = 'rgba(0, 0, 0, 0.6)';
-export { styled, css, keyframes, withTheme, ThemeProvider };
+export { styled, css, keyframes, withTheme, createGlobalStyle, ThemeProvider };
diff --git a/packages/instant/src/style/z_index.ts b/packages/instant/src/style/z_index.ts
index 727a5189d..03623f044 100644
--- a/packages/instant/src/style/z_index.ts
+++ b/packages/instant/src/style/z_index.ts
@@ -1,5 +1,6 @@
export const zIndex = {
- errorPopup: 1,
+ errorPopBehind: 1,
mainContainer: 2,
panel: 3,
+ errorPopUp: 4,
};
diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts
index 449bc0f31..d65f70008 100644
--- a/packages/instant/src/types.ts
+++ b/packages/instant/src/types.ts
@@ -1,20 +1,23 @@
-import { AssetProxyId, ObjectMap } from '@0x/types';
+import { AssetBuyer, BigNumber } from '@0x/asset-buyer';
+import { AssetProxyId, ObjectMap, SignedOrder } from '@0x/types';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import { Provider } from 'ethereum-types';
// Reusable
export type Maybe<T> = T | undefined;
export enum AsyncProcessState {
- NONE = 'None',
- PENDING = 'Pending',
- SUCCESS = 'Success',
- FAILURE = 'Failure',
+ None = 'NONE',
+ Pending = 'PENDING',
+ Success = 'SUCCESS',
+ Failure = 'FAILURE',
}
export enum OrderProcessState {
- NONE = 'None',
- VALIDATING = 'Validating',
- PROCESSING = 'Processing',
- SUCCESS = 'Success',
- FAILURE = 'Failure',
+ None = 'NONE',
+ Validating = 'VALIDATING',
+ Processing = 'PROCESSING',
+ Success = 'SUCCESS',
+ Failure = 'FAILURE',
}
export interface SimulatedProgress {
@@ -23,10 +26,10 @@ export interface SimulatedProgress {
}
interface OrderStatePreTx {
- processState: OrderProcessState.NONE | OrderProcessState.VALIDATING;
+ processState: OrderProcessState.None | OrderProcessState.Validating;
}
interface OrderStatePostTx {
- processState: OrderProcessState.PROCESSING | OrderProcessState.SUCCESS | OrderProcessState.FAILURE;
+ processState: OrderProcessState.Processing | OrderProcessState.Success | OrderProcessState.Failure;
txHash: string;
progress: SimulatedProgress;
}
@@ -89,3 +92,31 @@ export interface AffiliateInfo {
feeRecipient: string;
feePercentage: number;
}
+
+export interface ProviderState {
+ provider: Provider;
+ assetBuyer: AssetBuyer;
+ web3Wrapper: Web3Wrapper;
+ account: Account;
+}
+
+export enum AccountState {
+ Loading = 'LOADING',
+ Ready = 'READY',
+ Locked = 'LOCKED', // TODO(bmillman): break this up into locked / privacy mode enabled
+ Error = 'ERROR',
+ None = 'NONE,',
+}
+
+export interface AccountReady {
+ state: AccountState.Ready;
+ address: string;
+ ethBalanceInWei?: BigNumber;
+}
+export interface AccountNotReady {
+ state: AccountState.None | AccountState.Loading | AccountState.Locked | AccountState.Error;
+}
+
+export type Account = AccountReady | AccountNotReady;
+
+export type OrderSource = string | SignedOrder[];
diff --git a/packages/instant/src/util/asset_buyer_factory.ts b/packages/instant/src/util/asset_buyer_factory.ts
new file mode 100644
index 000000000..5ba46223c
--- /dev/null
+++ b/packages/instant/src/util/asset_buyer_factory.ts
@@ -0,0 +1,17 @@
+import { AssetBuyer, AssetBuyerOpts } from '@0x/asset-buyer';
+import { Provider } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { Network, OrderSource } from '../types';
+
+export const assetBuyerFactory = {
+ getAssetBuyer: (provider: Provider, orderSource: OrderSource, network: Network): AssetBuyer => {
+ const assetBuyerOptions: Partial<AssetBuyerOpts> = {
+ networkId: network,
+ };
+ const assetBuyer = _.isString(orderSource)
+ ? AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(provider, orderSource, assetBuyerOptions)
+ : AssetBuyer.getAssetBuyerForProvidedOrders(provider, orderSource, assetBuyerOptions);
+ return assetBuyer;
+ },
+};
diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts
new file mode 100644
index 000000000..e697d3ef7
--- /dev/null
+++ b/packages/instant/src/util/buy_quote_updater.ts
@@ -0,0 +1,56 @@
+import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer';
+import { BigNumber } from '@0x/utils';
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import * as _ from 'lodash';
+import { Dispatch } from 'redux';
+import { oc } from 'ts-optchain';
+
+import { Action, actions } from '../redux/actions';
+import { AffiliateInfo, ERC20Asset } from '../types';
+import { assetUtils } from '../util/asset';
+import { errorFlasher } from '../util/error_flasher';
+
+export const buyQuoteUpdater = {
+ updateBuyQuoteAsync: async (
+ assetBuyer: AssetBuyer,
+ dispatch: Dispatch<Action>,
+ asset: ERC20Asset,
+ assetAmount: BigNumber,
+ affiliateInfo?: AffiliateInfo,
+ ): Promise<void> => {
+ // get a new buy quote.
+ const baseUnitValue = Web3Wrapper.toBaseUnitAmount(assetAmount, asset.metaData.decimals);
+ // mark quote as pending
+ dispatch(actions.setQuoteRequestStatePending());
+ const feePercentage = oc(affiliateInfo).feePercentage();
+ let newBuyQuote: BuyQuote | undefined;
+ try {
+ newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue, { feePercentage });
+ } catch (error) {
+ dispatch(actions.setQuoteRequestStateFailure());
+ let errorMessage;
+ if (error.message === AssetBuyerError.InsufficientAssetLiquidity) {
+ const assetName = assetUtils.bestNameForAsset(asset, 'of this asset');
+ errorMessage = `Not enough ${assetName} available`;
+ } else if (error.message === AssetBuyerError.InsufficientZrxLiquidity) {
+ errorMessage = 'Not enough ZRX available';
+ } else if (
+ error.message === AssetBuyerError.StandardRelayerApiError ||
+ error.message.startsWith(AssetBuyerError.AssetUnavailable)
+ ) {
+ const assetName = assetUtils.bestNameForAsset(asset, 'This asset');
+ errorMessage = `${assetName} is currently unavailable`;
+ }
+ if (!_.isUndefined(errorMessage)) {
+ errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
+ } else {
+ throw error;
+ }
+ return;
+ }
+ // We have a successful new buy quote
+ errorFlasher.clearError(dispatch);
+ // invalidate the last buy quote.
+ dispatch(actions.updateLatestBuyQuote(newBuyQuote));
+ },
+};
diff --git a/packages/instant/src/util/injected_provider.ts b/packages/instant/src/util/injected_provider.ts
deleted file mode 100644
index 40f9e2da5..000000000
--- a/packages/instant/src/util/injected_provider.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Provider } from 'ethereum-types';
-import * as _ from 'lodash';
-
-export const getInjectedProvider = (): Provider => {
- const injectedProviderIfExists = (window as any).ethereum;
- if (!_.isUndefined(injectedProviderIfExists)) {
- // TODO: call enable here when implementing wallet connection flow
- return injectedProviderIfExists;
- }
- const injectedWeb3IfExists = (window as any).web3;
- if (!_.isUndefined(injectedWeb3IfExists.currentProvider)) {
- return injectedWeb3IfExists.currentProvider;
- } else {
- throw new Error(`No injected web3 found`);
- }
-};
diff --git a/packages/instant/src/util/provider_factory.ts b/packages/instant/src/util/provider_factory.ts
new file mode 100644
index 000000000..603f7674d
--- /dev/null
+++ b/packages/instant/src/util/provider_factory.ts
@@ -0,0 +1,34 @@
+import { EmptyWalletSubprovider, RPCSubprovider, Web3ProviderEngine } from '@0x/subproviders';
+import { Provider } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { BLOCK_POLLING_INTERVAL_MS, ETHEREUM_NODE_URL_BY_NETWORK } from '../constants';
+import { Maybe, Network } from '../types';
+
+export const providerFactory = {
+ getInjectedProviderIfExists: (): Maybe<Provider> => {
+ const injectedProviderIfExists = (window as any).ethereum;
+ if (!_.isUndefined(injectedProviderIfExists)) {
+ return injectedProviderIfExists;
+ }
+ const injectedWeb3IfExists = (window as any).web3;
+ if (!_.isUndefined(injectedWeb3IfExists) && !_.isUndefined(injectedWeb3IfExists.currentProvider)) {
+ return injectedWeb3IfExists.currentProvider;
+ }
+ return undefined;
+ },
+ getFallbackNoSigningProvider: (network: Network): Provider => {
+ const providerEngine = new Web3ProviderEngine({
+ pollingInterval: BLOCK_POLLING_INTERVAL_MS,
+ });
+ // Intercept calls to `eth_accounts` and always return empty
+ providerEngine.addProvider(new EmptyWalletSubprovider());
+ // Construct an RPC subprovider, all data based requests will be sent via the RPCSubprovider
+ // TODO(bmillman): make this more resilient to infura failures
+ const rpcUrl = ETHEREUM_NODE_URL_BY_NETWORK[network];
+ providerEngine.addProvider(new RPCSubprovider(rpcUrl));
+ // // Start the Provider Engine
+ providerEngine.start();
+ return providerEngine;
+ },
+};
diff --git a/packages/instant/src/util/provider_state_factory.ts b/packages/instant/src/util/provider_state_factory.ts
new file mode 100644
index 000000000..18b188d89
--- /dev/null
+++ b/packages/instant/src/util/provider_state_factory.ts
@@ -0,0 +1,69 @@
+import { Web3Wrapper } from '@0x/web3-wrapper';
+import { Provider } from 'ethereum-types';
+import * as _ from 'lodash';
+
+import { AccountNotReady, AccountState, Maybe, Network, OrderSource, ProviderState } from '../types';
+
+import { assetBuyerFactory } from './asset_buyer_factory';
+import { providerFactory } from './provider_factory';
+
+const LOADING_ACCOUNT: AccountNotReady = {
+ state: AccountState.Loading,
+};
+const NO_ACCOUNT: AccountNotReady = {
+ state: AccountState.None,
+};
+
+export const providerStateFactory = {
+ getInitialProviderState: (orderSource: OrderSource, network: Network, provider?: Provider): ProviderState => {
+ if (!_.isUndefined(provider)) {
+ return providerStateFactory.getInitialProviderStateFromProvider(orderSource, network, provider);
+ }
+ const providerStateFromWindowIfExits = providerStateFactory.getInitialProviderStateFromWindowIfExists(
+ orderSource,
+ network,
+ );
+ if (providerStateFromWindowIfExits) {
+ return providerStateFromWindowIfExits;
+ } else {
+ return providerStateFactory.getInitialProviderStateFallback(orderSource, network);
+ }
+ },
+ getInitialProviderStateFromProvider: (
+ orderSource: OrderSource,
+ network: Network,
+ provider: Provider,
+ ): ProviderState => {
+ const providerState: ProviderState = {
+ provider,
+ web3Wrapper: new Web3Wrapper(provider),
+ assetBuyer: assetBuyerFactory.getAssetBuyer(provider, orderSource, network),
+ account: LOADING_ACCOUNT,
+ };
+ return providerState;
+ },
+ getInitialProviderStateFromWindowIfExists: (orderSource: OrderSource, network: Network): Maybe<ProviderState> => {
+ const injectedProviderIfExists = providerFactory.getInjectedProviderIfExists();
+ if (!_.isUndefined(injectedProviderIfExists)) {
+ const providerState: ProviderState = {
+ provider: injectedProviderIfExists,
+ web3Wrapper: new Web3Wrapper(injectedProviderIfExists),
+ assetBuyer: assetBuyerFactory.getAssetBuyer(injectedProviderIfExists, orderSource, network),
+ account: LOADING_ACCOUNT,
+ };
+ return providerState;
+ } else {
+ return undefined;
+ }
+ },
+ getInitialProviderStateFallback: (orderSource: OrderSource, network: Network): ProviderState => {
+ const provider = providerFactory.getFallbackNoSigningProvider(network);
+ const providerState: ProviderState = {
+ provider,
+ web3Wrapper: new Web3Wrapper(provider),
+ assetBuyer: assetBuyerFactory.getAssetBuyer(provider, orderSource, network),
+ account: NO_ACCOUNT,
+ };
+ return providerState;
+ },
+};