aboutsummaryrefslogtreecommitdiffstats
path: root/packages/instant/src/containers
diff options
context:
space:
mode:
Diffstat (limited to 'packages/instant/src/containers')
-rw-r--r--packages/instant/src/containers/available_erc20_token_selector.ts45
-rw-r--r--packages/instant/src/containers/latest_buy_quote_order_details.ts30
-rw-r--r--packages/instant/src/containers/latest_error.tsx60
-rw-r--r--packages/instant/src/containers/selected_asset_amount_input.tsx36
-rw-r--r--packages/instant/src/containers/selected_asset_buy_order_progress.ts13
-rw-r--r--packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts104
-rw-r--r--packages/instant/src/containers/selected_asset_instant_heading.ts34
-rw-r--r--packages/instant/src/containers/selected_asset_theme_provider.ts32
-rw-r--r--packages/instant/src/containers/selected_erc20_asset_amount_input.ts116
9 files changed, 434 insertions, 36 deletions
diff --git a/packages/instant/src/containers/available_erc20_token_selector.ts b/packages/instant/src/containers/available_erc20_token_selector.ts
new file mode 100644
index 000000000..4d4218d22
--- /dev/null
+++ b/packages/instant/src/containers/available_erc20_token_selector.ts
@@ -0,0 +1,45 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+
+import { State } from '../redux/reducer';
+import { ERC20Asset } from '../types';
+import { assetUtils } from '../util/asset';
+
+import { ERC20TokenSelector } from '../components/erc20_token_selector';
+import { Action, actions } from '../redux/actions';
+
+export interface AvailableERC20TokenSelectorProps {
+ onTokenSelect?: (token: ERC20Asset) => void;
+}
+
+interface ConnectedState {
+ tokens: ERC20Asset[];
+}
+
+interface ConnectedDispatch {
+ onTokenSelect: (token: ERC20Asset) => void;
+}
+
+const mapStateToProps = (state: State, _ownProps: AvailableERC20TokenSelectorProps): ConnectedState => ({
+ tokens: assetUtils.getERC20AssetsFromAssets(state.availableAssets || []),
+});
+
+const mapDispatchToProps = (
+ dispatch: Dispatch<Action>,
+ ownProps: AvailableERC20TokenSelectorProps,
+): ConnectedDispatch => ({
+ onTokenSelect: (token: ERC20Asset) => {
+ dispatch(actions.updateSelectedAsset(token));
+ dispatch(actions.resetAmount());
+ if (ownProps.onTokenSelect) {
+ ownProps.onTokenSelect(token);
+ }
+ },
+});
+
+export const AvailableERC20TokenSelector: React.ComponentClass<AvailableERC20TokenSelectorProps> = connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(ERC20TokenSelector);
diff --git a/packages/instant/src/containers/latest_buy_quote_order_details.ts b/packages/instant/src/containers/latest_buy_quote_order_details.ts
new file mode 100644
index 000000000..2b59ed3ae
--- /dev/null
+++ b/packages/instant/src/containers/latest_buy_quote_order_details.ts
@@ -0,0 +1,30 @@
+import { BuyQuoteInfo } from '@0x/asset-buyer';
+import { BigNumber } from '@0x/utils';
+import * as _ from 'lodash';
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { oc } from 'ts-optchain';
+
+import { State } from '../redux/reducer';
+
+import { OrderDetails } from '../components/order_details';
+import { AsyncProcessState } from '../types';
+
+export interface LatestBuyQuoteOrderDetailsProps {}
+
+interface ConnectedState {
+ buyQuoteInfo?: BuyQuoteInfo;
+ ethUsdPrice?: BigNumber;
+ isLoading: boolean;
+}
+
+const mapStateToProps = (state: State, _ownProps: LatestBuyQuoteOrderDetailsProps): ConnectedState => ({
+ // use the worst case quote info
+ buyQuoteInfo: oc(state).latestBuyQuote.worstCaseQuoteInfo(),
+ ethUsdPrice: state.ethUsdPrice,
+ isLoading: state.quoteRequestState === AsyncProcessState.Pending,
+});
+
+export const LatestBuyQuoteOrderDetails: React.ComponentClass<LatestBuyQuoteOrderDetailsProps> = connect(
+ mapStateToProps,
+)(OrderDetails);
diff --git a/packages/instant/src/containers/latest_error.tsx b/packages/instant/src/containers/latest_error.tsx
new file mode 100644
index 000000000..c0da181f1
--- /dev/null
+++ b/packages/instant/src/containers/latest_error.tsx
@@ -0,0 +1,60 @@
+import * as React from 'react';
+
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+
+import { SlideAnimationState } from '../components/animations/slide_animation';
+import { SlidingError } from '../components/sliding_error';
+import { Overlay } from '../components/ui/overlay';
+import { Action } from '../redux/actions';
+import { State } from '../redux/reducer';
+import { ScreenWidths } from '../style/media';
+import { generateOverlayBlack } from '../style/theme';
+import { zIndex } from '../style/z_index';
+import { Asset, DisplayStatus, Omit } from '../types';
+import { errorFlasher } from '../util/error_flasher';
+
+export interface LatestErrorComponentProps {
+ asset?: Asset;
+ latestErrorMessage?: string;
+ animationState: SlideAnimationState;
+ shouldRenderOverlay: boolean;
+ onOverlayClick: () => void;
+}
+
+export const LatestErrorComponent: React.StatelessComponent<LatestErrorComponentProps> = props => {
+ if (!props.latestErrorMessage) {
+ return <div />;
+ }
+ return (
+ <React.Fragment>
+ <SlidingError animationState={props.animationState} icon="😢" message={props.latestErrorMessage} />
+ {props.shouldRenderOverlay && (
+ <Overlay
+ onClick={props.onOverlayClick}
+ zIndex={zIndex.containerOverlay}
+ showMaxWidth={ScreenWidths.Sm}
+ backgroundColor={generateOverlayBlack(0.4)}
+ />
+ )}
+ </React.Fragment>
+ );
+};
+
+export interface LatestErrorProps {}
+interface ConnectedState extends Omit<LatestErrorComponentProps, 'onOverlayClick'> {}
+const mapStateToProps = (state: State, _ownProps: LatestErrorProps): ConnectedState => ({
+ asset: state.selectedAsset,
+ latestErrorMessage: state.latestErrorMessage,
+ animationState: state.latestErrorDisplayStatus === DisplayStatus.Present ? 'slidIn' : 'slidOut',
+ shouldRenderOverlay: state.latestErrorDisplayStatus === DisplayStatus.Present,
+});
+
+type ConnectedDispatch = Pick<LatestErrorComponentProps, 'onOverlayClick'>;
+const mapDispatchToProps = (dispatch: Dispatch<Action>, _ownProps: LatestErrorProps): ConnectedDispatch => ({
+ onOverlayClick: () => {
+ errorFlasher.clearError(dispatch);
+ },
+});
+
+export const LatestError = connect(mapStateToProps, mapDispatchToProps)(LatestErrorComponent);
diff --git a/packages/instant/src/containers/selected_asset_amount_input.tsx b/packages/instant/src/containers/selected_asset_amount_input.tsx
deleted file mode 100644
index 800a4c568..000000000
--- a/packages/instant/src/containers/selected_asset_amount_input.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { BigNumber } from '@0xproject/utils';
-import * as React from 'react';
-import { connect } from 'react-redux';
-import { Dispatch } from 'redux';
-
-import { State } from '../redux/reducer';
-import { ColorOption } from '../style/theme';
-import { Action, ActionTypes } from '../types';
-
-import { AmountInput } from '../components/amount_input';
-
-export interface SelectedAssetAmountInputProps {
- fontColor?: ColorOption;
- fontSize?: string;
-}
-
-interface ConnectedState {
- value?: BigNumber;
-}
-
-interface ConnectedDispatch {
- onChange?: (value?: BigNumber) => void;
-}
-
-const mapStateToProps = (state: State, _ownProps: SelectedAssetAmountInputProps): ConnectedState => ({
- value: state.selectedAssetAmount,
-});
-
-const mapDispatchToProps = (dispatch: Dispatch<Action>): ConnectedDispatch => ({
- onChange: value => dispatch({ type: ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT, data: value }),
-});
-
-export const SelectedAssetAmountInput: React.ComponentClass<SelectedAssetAmountInputProps> = connect(
- mapStateToProps,
- mapDispatchToProps,
-)(AmountInput);
diff --git a/packages/instant/src/containers/selected_asset_buy_order_progress.ts b/packages/instant/src/containers/selected_asset_buy_order_progress.ts
new file mode 100644
index 000000000..7c8c24676
--- /dev/null
+++ b/packages/instant/src/containers/selected_asset_buy_order_progress.ts
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux';
+
+import { BuyOrderProgress } from '../components/buy_order_progress';
+import { State } from '../redux/reducer';
+import { OrderState } from '../types';
+
+interface ConnectedState {
+ buyOrderState: OrderState;
+}
+const mapStateToProps = (state: State, _ownProps: {}): ConnectedState => ({
+ buyOrderState: state.buyOrderState,
+});
+export const SelectedAssetBuyOrderProgress = connect(mapStateToProps)(BuyOrderProgress);
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
new file mode 100644
index 000000000..610335243
--- /dev/null
+++ b/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts
@@ -0,0 +1,104 @@
+import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer';
+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 { BuyOrderStateButtons } from '../components/buy_order_state_buttons';
+import { Action, actions } from '../redux/actions';
+import { State } from '../redux/reducer';
+import { AccountState, AffiliateInfo, OrderProcessState, ZeroExInstantError } from '../types';
+import { errorFlasher } from '../util/error_flasher';
+import { etherscanUtil } from '../util/etherscan';
+
+interface ConnectedState {
+ accountAddress?: string;
+ accountEthBalanceInWei?: BigNumber;
+ buyQuote?: BuyQuote;
+ buyOrderProcessingState: OrderProcessState;
+ assetBuyer: AssetBuyer;
+ web3Wrapper: Web3Wrapper;
+ affiliateInfo?: AffiliateInfo;
+ onViewTransaction: () => void;
+}
+
+interface ConnectedDispatch {
+ onValidationPending: (buyQuote: BuyQuote) => void;
+ onSignatureDenied: (buyQuote: BuyQuote) => void;
+ onBuyProcessing: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) => void;
+ onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void;
+ onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void;
+ onRetry: () => void;
+ onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void;
+}
+export interface SelectedAssetBuyOrderStateButtons {}
+const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButtons): ConnectedState => {
+ const assetBuyer = state.providerState.assetBuyer;
+ const web3Wrapper = state.providerState.web3Wrapper;
+ const account = state.providerState.account;
+ const accountAddress = account.state === AccountState.Ready ? account.address : undefined;
+ const accountEthBalanceInWei = account.state === AccountState.Ready ? account.ethBalanceInWei : undefined;
+ return {
+ accountAddress,
+ accountEthBalanceInWei,
+ buyOrderProcessingState: state.buyOrderState.processState,
+ assetBuyer,
+ web3Wrapper,
+ 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>,
+ ownProps: SelectedAssetBuyOrderStateButtons,
+): ConnectedDispatch => ({
+ onValidationPending: (buyQuote: BuyQuote) => {
+ dispatch(actions.setBuyOrderStateValidating());
+ },
+ onBuyProcessing: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) => {
+ dispatch(actions.setBuyOrderStateProcessing(txHash, startTimeUnix, expectedEndTimeUnix));
+ },
+ onBuySuccess: (buyQuote: BuyQuote, txHash: string) => dispatch(actions.setBuyOrderStateSuccess(txHash)),
+ onBuyFailure: (buyQuote: BuyQuote, txHash: string) => dispatch(actions.setBuyOrderStateFailure(txHash)),
+ onSignatureDenied: () => {
+ dispatch(actions.resetAmount());
+ const errorMessage = 'You denied this transaction';
+ errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
+ },
+ onValidationFail: (buyQuote, error) => {
+ dispatch(actions.setBuyOrderStateNone());
+ if (error === ZeroExInstantError.InsufficientETH) {
+ const errorMessage = "You don't have enough ETH";
+ errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
+ } else {
+ errorFlasher.flashNewErrorMessage(dispatch);
+ }
+ },
+ onRetry: () => {
+ dispatch(actions.resetAmount());
+ },
+});
+
+export const SelectedAssetBuyOrderStateButtons: React.ComponentClass<SelectedAssetBuyOrderStateButtons> = connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(BuyOrderStateButtons);
diff --git a/packages/instant/src/containers/selected_asset_instant_heading.ts b/packages/instant/src/containers/selected_asset_instant_heading.ts
new file mode 100644
index 000000000..a407279e6
--- /dev/null
+++ b/packages/instant/src/containers/selected_asset_instant_heading.ts
@@ -0,0 +1,34 @@
+import { BigNumber } from '@0x/utils';
+import * as _ from 'lodash';
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { oc } from 'ts-optchain';
+
+import { State } from '../redux/reducer';
+import { AsyncProcessState, ERC20Asset, OrderState } from '../types';
+
+import { InstantHeading } from '../components/instant_heading';
+
+export interface InstantHeadingProps {
+ onSelectAssetClick?: (asset?: ERC20Asset) => void;
+}
+
+interface ConnectedState {
+ selectedAssetAmount?: BigNumber;
+ totalEthBaseAmount?: BigNumber;
+ ethUsdPrice?: BigNumber;
+ quoteRequestState: AsyncProcessState;
+ buyOrderState: OrderState;
+}
+
+const mapStateToProps = (state: State, _ownProps: InstantHeadingProps): ConnectedState => ({
+ selectedAssetAmount: state.selectedAssetAmount,
+ totalEthBaseAmount: oc(state).latestBuyQuote.worstCaseQuoteInfo.totalEthAmount(),
+ ethUsdPrice: state.ethUsdPrice,
+ quoteRequestState: state.quoteRequestState,
+ buyOrderState: state.buyOrderState,
+});
+
+export const SelectedAssetInstantHeading: React.ComponentClass<InstantHeadingProps> = connect(mapStateToProps)(
+ InstantHeading,
+);
diff --git a/packages/instant/src/containers/selected_asset_theme_provider.ts b/packages/instant/src/containers/selected_asset_theme_provider.ts
new file mode 100644
index 000000000..6e6b83d73
--- /dev/null
+++ b/packages/instant/src/containers/selected_asset_theme_provider.ts
@@ -0,0 +1,32 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+import { connect } from 'react-redux';
+
+import { State } from '../redux/reducer';
+import { Theme, theme as defaultTheme, ThemeProvider } from '../style/theme';
+import { Asset } from '../types';
+
+export interface SelectedAssetThemeProviderProps {}
+
+interface ConnectedState {
+ theme: Theme;
+}
+
+const getTheme = (asset?: Asset): Theme => {
+ if (!_.isUndefined(asset) && !_.isUndefined(asset.metaData.primaryColor)) {
+ return {
+ ...defaultTheme,
+ primaryColor: asset.metaData.primaryColor,
+ };
+ }
+ return defaultTheme;
+};
+
+const mapStateToProps = (state: State, _ownProps: SelectedAssetThemeProviderProps): ConnectedState => {
+ const theme = getTheme(state.selectedAsset);
+ return { theme };
+};
+
+export const SelectedAssetThemeProvider: React.ComponentClass<SelectedAssetThemeProviderProps> = connect(
+ mapStateToProps,
+)(ThemeProvider);
diff --git a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
new file mode 100644
index 000000000..93ff3db70
--- /dev/null
+++ b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts
@@ -0,0 +1,116 @@
+import { AssetBuyer } from '@0x/asset-buyer';
+import { AssetProxyId } from '@0x/types';
+import { BigNumber } from '@0x/utils';
+import * as _ from 'lodash';
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+
+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 { buyQuoteUpdater } from '../util/buy_quote_updater';
+
+export interface SelectedERC20AssetAmountInputProps {
+ fontColor?: ColorOption;
+ startingFontSizePx: number;
+ onSelectAssetClick?: (asset?: ERC20Asset) => void;
+}
+
+interface ConnectedState {
+ assetBuyer: AssetBuyer;
+ value?: BigNumber;
+ asset?: ERC20Asset;
+ isDisabled: boolean;
+ numberOfAssetsAvailable?: number;
+ affiliateInfo?: AffiliateInfo;
+}
+
+interface ConnectedDispatch {
+ updateBuyQuote: (
+ assetBuyer: AssetBuyer,
+ value?: BigNumber,
+ asset?: ERC20Asset,
+ affiliateInfo?: AffiliateInfo,
+ ) => void;
+}
+
+interface ConnectedProps {
+ value?: BigNumber;
+ asset?: ERC20Asset;
+ onChange: (value?: BigNumber, asset?: ERC20Asset) => void;
+ isDisabled: boolean;
+ numberOfAssetsAvailable?: number;
+}
+
+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 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,
+ value: state.selectedAssetAmount,
+ asset: selectedAsset,
+ isDisabled,
+ numberOfAssetsAvailable,
+ affiliateInfo: state.affiliateInfo,
+ };
+};
+
+const debouncedUpdateBuyQuoteAsync = _.debounce(buyQuoteUpdater.updateBuyQuoteAsync.bind(buyQuoteUpdater), 200, {
+ trailing: true,
+}) as typeof buyQuoteUpdater.updateBuyQuoteAsync;
+
+const mapDispatchToProps = (
+ dispatch: Dispatch<Action>,
+ _ownProps: SelectedERC20AssetAmountInputProps,
+): ConnectedDispatch => ({
+ updateBuyQuote: (assetBuyer, value, asset, affiliateInfo) => {
+ // Update the input
+ dispatch(actions.updateSelectedAssetAmount(value));
+ // invalidate the last buy quote.
+ dispatch(actions.updateLatestBuyQuote(undefined));
+ // reset our buy state
+ dispatch(actions.setBuyOrderStateNone());
+
+ 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
+ debouncedUpdateBuyQuoteAsync(assetBuyer, dispatch, asset, value, true, affiliateInfo);
+ }
+ },
+});
+
+const mergeProps = (
+ connectedState: ConnectedState,
+ connectedDispatch: ConnectedDispatch,
+ ownProps: SelectedERC20AssetAmountInputProps,
+): FinalProps => {
+ return {
+ ...ownProps,
+ asset: connectedState.asset,
+ value: connectedState.value,
+ onChange: (value, asset) => {
+ connectedDispatch.updateBuyQuote(connectedState.assetBuyer, value, asset, connectedState.affiliateInfo);
+ },
+ isDisabled: connectedState.isDisabled,
+ numberOfAssetsAvailable: connectedState.numberOfAssetsAvailable,
+ };
+};
+
+export const SelectedERC20AssetAmountInput: React.ComponentClass<SelectedERC20AssetAmountInputProps> = connect(
+ mapStateToProps,
+ mapDispatchToProps,
+ mergeProps,
+)(ERC20AssetAmountInput);