aboutsummaryrefslogtreecommitdiffstats
path: root/packages/instant/src/components
diff options
context:
space:
mode:
authorSteve Klebanoff <steve.klebanoff@gmail.com>2018-11-03 04:28:52 +0800
committerSteve Klebanoff <steve.klebanoff@gmail.com>2018-11-03 04:28:52 +0800
commitd160792923c76c5bd0f61e7af2580c158dddc3d4 (patch)
tree422a523890fd1243795890d51d6b2917ec6fdc69 /packages/instant/src/components
parentb0f2ab45e9761cc760b94d8567df8ba66956388c (diff)
parent6a57a7b5be151114bb06c171560976b09a8c4aa1 (diff)
downloaddexon-0x-contracts-d160792923c76c5bd0f61e7af2580c158dddc3d4.tar.gz
dexon-0x-contracts-d160792923c76c5bd0f61e7af2580c158dddc3d4.tar.zst
dexon-0x-contracts-d160792923c76c5bd0f61e7af2580c158dddc3d4.zip
Merge branch 'development' into fix/instant/decimal-fields-scaling-amount-input-bn
Diffstat (limited to 'packages/instant/src/components')
-rw-r--r--packages/instant/src/components/buy_button.tsx22
-rw-r--r--packages/instant/src/components/buy_order_progress.tsx35
-rw-r--r--packages/instant/src/components/buy_order_state_buttons.tsx7
-rw-r--r--packages/instant/src/components/time_counter.tsx78
-rw-r--r--packages/instant/src/components/timed_progress_bar.tsx78
-rw-r--r--packages/instant/src/components/zero_ex_instant_container.tsx5
-rw-r--r--packages/instant/src/components/zero_ex_instant_provider.tsx8
7 files changed, 220 insertions, 13 deletions
diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx
index 93bd8e635..12ac62601 100644
--- a/packages/instant/src/components/buy_button.tsx
+++ b/packages/instant/src/components/buy_button.tsx
@@ -1,10 +1,11 @@
import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer';
import * as _ from 'lodash';
import * as React from 'react';
+import { oc } from 'ts-optchain';
import { WEB_3_WRAPPER_TRANSACTION_FAILED_ERROR_MSG_PREFIX } from '../constants';
import { ColorOption } from '../style/theme';
-import { ZeroExInstantError } from '../types';
+import { AffiliateInfo, ZeroExInstantError } from '../types';
import { getBestAddress } from '../util/address';
import { balanceUtil } from '../util/balance';
import { gasPriceEstimator } from '../util/gas_price_estimator';
@@ -16,10 +17,11 @@ import { Button, Text } from './ui';
export interface BuyButtonProps {
buyQuote?: BuyQuote;
assetBuyer?: AssetBuyer;
+ affiliateInfo?: AffiliateInfo;
onValidationPending: (buyQuote: BuyQuote) => void;
onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void;
onSignatureDenied: (buyQuote: BuyQuote) => void;
- onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => void;
+ onBuyProcessing: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) => void;
onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void;
onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void;
}
@@ -42,7 +44,7 @@ export class BuyButton extends React.Component<BuyButtonProps> {
}
private readonly _handleClick = async () => {
// The button is disabled when there is no buy quote anyway.
- const { buyQuote, assetBuyer } = this.props;
+ const { buyQuote, assetBuyer, affiliateInfo } = this.props;
if (_.isUndefined(buyQuote) || _.isUndefined(assetBuyer)) {
return;
}
@@ -57,9 +59,14 @@ export class BuyButton extends React.Component<BuyButtonProps> {
}
let txHash: string | undefined;
- const gasPrice = await gasPriceEstimator.getFastAmountInWeiAsync();
+ const gasInfo = await gasPriceEstimator.getGasInfoAsync();
+ const feeRecipient = oc(affiliateInfo).feeRecipient();
try {
- txHash = await assetBuyer.executeBuyQuoteAsync(buyQuote, { takerAddress, gasPrice });
+ txHash = await assetBuyer.executeBuyQuoteAsync(buyQuote, {
+ feeRecipient,
+ takerAddress,
+ gasPrice: gasInfo.gasPriceInWei,
+ });
} catch (e) {
if (e instanceof Error) {
if (e.message === AssetBuyerError.SignatureRequestDenied) {
@@ -73,7 +80,9 @@ export class BuyButton extends React.Component<BuyButtonProps> {
throw e;
}
- this.props.onBuyProcessing(buyQuote, txHash);
+ const startTimeUnix = new Date().getTime();
+ const expectedEndTimeUnix = startTimeUnix + gasInfo.estimatedTimeMs;
+ this.props.onBuyProcessing(buyQuote, txHash, startTimeUnix, expectedEndTimeUnix);
try {
await web3Wrapper.awaitTransactionSuccessAsync(txHash);
} catch (e) {
@@ -83,6 +92,7 @@ export class BuyButton extends React.Component<BuyButtonProps> {
}
throw e;
}
+
this.props.onBuySuccess(buyQuote, txHash);
};
}
diff --git a/packages/instant/src/components/buy_order_progress.tsx b/packages/instant/src/components/buy_order_progress.tsx
new file mode 100644
index 000000000..e259e5606
--- /dev/null
+++ b/packages/instant/src/components/buy_order_progress.tsx
@@ -0,0 +1,35 @@
+import * as React from 'react';
+
+import { TimedProgressBar } from '../components/timed_progress_bar';
+
+import { TimeCounter } from '../components/time_counter';
+import { Container } from '../components/ui';
+import { OrderProcessState, OrderState } from '../types';
+
+export interface BuyOrderProgressProps {
+ buyOrderState: OrderState;
+}
+
+export const BuyOrderProgress: React.StatelessComponent<BuyOrderProgressProps> = props => {
+ const { buyOrderState } = props;
+
+ if (
+ buyOrderState.processState === OrderProcessState.PROCESSING ||
+ buyOrderState.processState === OrderProcessState.SUCCESS ||
+ buyOrderState.processState === OrderProcessState.FAILURE
+ ) {
+ const progress = buyOrderState.progress;
+ const hasEnded = buyOrderState.processState !== OrderProcessState.PROCESSING;
+ const expectedTimeMs = progress.expectedEndTimeUnix - progress.startTimeUnix;
+ return (
+ <Container padding="20px 20px 0px 20px" width="100%">
+ <Container marginBottom="5px">
+ <TimeCounter estimatedTimeMs={expectedTimeMs} hasEnded={hasEnded} key={progress.startTimeUnix} />
+ </Container>
+ <TimedProgressBar expectedTimeMs={expectedTimeMs} hasEnded={hasEnded} key={progress.startTimeUnix} />
+ </Container>
+ );
+ }
+
+ return null;
+};
diff --git a/packages/instant/src/components/buy_order_state_buttons.tsx b/packages/instant/src/components/buy_order_state_buttons.tsx
index 51e06a284..5c074a67a 100644
--- a/packages/instant/src/components/buy_order_state_buttons.tsx
+++ b/packages/instant/src/components/buy_order_state_buttons.tsx
@@ -2,7 +2,7 @@ import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer';
import * as React from 'react';
import { ColorOption } from '../style/theme';
-import { OrderProcessState, ZeroExInstantError } from '../types';
+import { AffiliateInfo, OrderProcessState, ZeroExInstantError } from '../types';
import { BuyButton } from './buy_button';
import { PlacingOrderButton } from './placing_order_button';
@@ -13,17 +13,17 @@ export interface BuyOrderStateButtonProps {
buyQuote?: BuyQuote;
buyOrderProcessingState: OrderProcessState;
assetBuyer?: AssetBuyer;
+ affiliateInfo?: AffiliateInfo;
onViewTransaction: () => void;
onValidationPending: (buyQuote: BuyQuote) => void;
onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void;
onSignatureDenied: (buyQuote: BuyQuote) => void;
- onBuyProcessing: (buyQuote: BuyQuote, txHash: string) => 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;
}
-// TODO: rename to buttons
export const BuyOrderStateButtons: React.StatelessComponent<BuyOrderStateButtonProps> = props => {
if (props.buyOrderProcessingState === OrderProcessState.FAILURE) {
return (
@@ -51,6 +51,7 @@ export const BuyOrderStateButtons: React.StatelessComponent<BuyOrderStateButtonP
<BuyButton
buyQuote={props.buyQuote}
assetBuyer={props.assetBuyer}
+ affiliateInfo={props.affiliateInfo}
onValidationPending={props.onValidationPending}
onValidationFail={props.onValidationFail}
onSignatureDenied={props.onSignatureDenied}
diff --git a/packages/instant/src/components/time_counter.tsx b/packages/instant/src/components/time_counter.tsx
new file mode 100644
index 000000000..f9b68163c
--- /dev/null
+++ b/packages/instant/src/components/time_counter.tsx
@@ -0,0 +1,78 @@
+import * as React from 'react';
+
+import { ONE_SECOND_MS } from '../constants';
+import { ColorOption } from '../style/theme';
+import { timeUtil } from '../util/time';
+
+import { Container } from './ui/container';
+import { Flex } from './ui/flex';
+import { Text } from './ui/text';
+
+export interface TimeCounterProps {
+ estimatedTimeMs: number;
+ hasEnded: boolean;
+}
+interface TimeCounterState {
+ elapsedSeconds: number;
+}
+
+export class TimeCounter extends React.Component<TimeCounterProps, TimeCounterState> {
+ public state = {
+ elapsedSeconds: 0,
+ };
+ private _timerId?: number;
+
+ public componentDidMount(): void {
+ this._setupTimerBasedOnProps();
+ }
+
+ public componentWillUnmount(): void {
+ this._clearTimer();
+ }
+
+ public componentDidUpdate(prevProps: TimeCounterProps): void {
+ if (prevProps.hasEnded !== this.props.hasEnded) {
+ this._setupTimerBasedOnProps();
+ }
+ }
+
+ public render(): React.ReactNode {
+ const estimatedTimeSeconds = this.props.estimatedTimeMs / ONE_SECOND_MS;
+ return (
+ <Flex justify="space-between">
+ <Container>
+ <Container marginRight="5px" display="inline">
+ <Text fontWeight={600} fontColor={ColorOption.grey}>
+ Est. Time
+ </Text>
+ </Container>
+ <Text fontColor={ColorOption.grey}>
+ ({timeUtil.secondsToHumanDescription(estimatedTimeSeconds)})
+ </Text>
+ </Container>
+ <Text fontColor={ColorOption.grey}>
+ Time: {timeUtil.secondsToStopwatchTime(this.state.elapsedSeconds)}
+ </Text>
+ </Flex>
+ );
+ }
+
+ private _setupTimerBasedOnProps(): void {
+ this.props.hasEnded ? this._clearTimer() : this._newTimer();
+ }
+
+ private _newTimer(): void {
+ this._clearTimer();
+ this._timerId = window.setInterval(() => {
+ this.setState({
+ elapsedSeconds: this.state.elapsedSeconds + 1,
+ });
+ }, ONE_SECOND_MS);
+ }
+
+ private _clearTimer(): void {
+ if (this._timerId) {
+ window.clearInterval(this._timerId);
+ }
+ }
+}
diff --git a/packages/instant/src/components/timed_progress_bar.tsx b/packages/instant/src/components/timed_progress_bar.tsx
new file mode 100644
index 000000000..f2a6f5745
--- /dev/null
+++ b/packages/instant/src/components/timed_progress_bar.tsx
@@ -0,0 +1,78 @@
+import * as _ from 'lodash';
+import * as React from 'react';
+
+import { PROGRESS_FINISH_ANIMATION_TIME_MS, PROGRESS_STALL_AT_WIDTH } from '../constants';
+import { ColorOption, keyframes, styled } from '../style/theme';
+
+import { Container } from './ui/container';
+
+export interface TimedProgressBarProps {
+ expectedTimeMs: number;
+ hasEnded: boolean;
+}
+
+/**
+ * Timed Progress Bar
+ * Goes from 0% -> PROGRESS_STALL_AT_WIDTH over time of expectedTimeMs
+ * When hasEnded set to true, goes to 100% through animation of PROGRESS_FINISH_ANIMATION_TIME_MS length of time
+ */
+export class TimedProgressBar extends React.Component<TimedProgressBarProps, {}> {
+ private readonly _barRef = React.createRef<HTMLDivElement>();
+
+ public render(): React.ReactNode {
+ const timedProgressProps = this._calculateTimedProgressProps();
+ return (
+ <Container width="100%" backgroundColor={ColorOption.lightGrey} borderRadius="6px">
+ <TimedProgress {...timedProgressProps} ref={this._barRef as any} />
+ </Container>
+ );
+ }
+
+ private _calculateTimedProgressProps(): TimedProgressProps {
+ if (this.props.hasEnded) {
+ if (!this._barRef.current) {
+ throw new Error('ended but no reference');
+ }
+ const fromWidth = `${this._barRef.current.offsetWidth}px`;
+ return {
+ timeMs: PROGRESS_FINISH_ANIMATION_TIME_MS,
+ fromWidth,
+ toWidth: '100%',
+ };
+ }
+
+ return {
+ timeMs: this.props.expectedTimeMs,
+ fromWidth: '0px',
+ toWidth: PROGRESS_STALL_AT_WIDTH,
+ };
+ }
+}
+
+const expandingWidthKeyframes = (fromWidth: string, toWidth: string) => {
+ return keyframes`
+ from {
+ width: ${fromWidth};
+ }
+ to {
+ width: ${toWidth};
+ }
+ `;
+};
+
+interface TimedProgressProps {
+ timeMs: number;
+ fromWidth: string;
+ toWidth: string;
+}
+
+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;
+ `;
diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx
index 009ad9b2a..f8e3935fb 100644
--- a/packages/instant/src/components/zero_ex_instant_container.tsx
+++ b/packages/instant/src/components/zero_ex_instant_container.tsx
@@ -4,13 +4,15 @@ import { LatestBuyQuoteOrderDetails } from '../containers/latest_buy_quote_order
import { LatestError } from '../containers/latest_error';
import { SelectedAssetBuyOrderStateButtons } from '../containers/selected_asset_buy_order_state_buttons';
import { SelectedAssetInstantHeading } from '../containers/selected_asset_instant_heading';
+
+import { SelectedAssetBuyOrderProgress } from '../containers/selected_asset_buy_order_progress';
+
import { ColorOption } from '../style/theme';
import { zIndex } from '../style/z_index';
import { SlideAnimationState } from './animations/slide_animation';
import { SlidingPanel } from './sliding_panel';
import { Container, Flex } from './ui';
-
export interface ZeroExInstantContainerProps {}
export interface ZeroExInstantContainerState {
tokenSelectionPanelAnimationState: SlideAnimationState;
@@ -36,6 +38,7 @@ export class ZeroExInstantContainer extends React.Component<ZeroExInstantContain
>
<Flex direction="column" justify="flex-start">
<SelectedAssetInstantHeading onSelectAssetClick={this._handleSymbolClick} />
+ <SelectedAssetBuyOrderProgress />
<LatestBuyQuoteOrderDetails />
<Container padding="20px" width="100%">
<SelectedAssetBuyOrderStateButtons />
diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx
index 9391c03f7..a09ab68dc 100644
--- a/packages/instant/src/components/zero_ex_instant_provider.tsx
+++ b/packages/instant/src/components/zero_ex_instant_provider.tsx
@@ -10,7 +10,7 @@ import { asyncData } from '../redux/async_data';
import { INITIAL_STATE, State } from '../redux/reducer';
import { store, Store } from '../redux/store';
import { fonts } from '../style/fonts';
-import { AssetMetaData, Network } from '../types';
+import { AffiliateInfo, AssetMetaData, Network } from '../types';
import { assetUtils } from '../util/asset';
import { errorFlasher } from '../util/error_flasher';
import { gasPriceEstimator } from '../util/gas_price_estimator';
@@ -29,9 +29,10 @@ export interface ZeroExInstantProviderRequiredProps {
}
export interface ZeroExInstantProviderOptionalProps {
- defaultAssetBuyAmount?: number;
+ defaultAssetBuyAmount: number;
additionalAssetMetaDataMap: ObjectMap<AssetMetaData>;
networkId: Network;
+ affiliateInfo: AffiliateInfo;
}
export class ZeroExInstantProvider extends React.Component<ZeroExInstantProviderProps> {
@@ -66,6 +67,7 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
? state.selectedAssetAmount
: new BigNumber(props.defaultAssetBuyAmount),
assetMetaDataMap: completeAssetMetaDataMap,
+ affiliateInfo: props.affiliateInfo,
};
return storeStateFromProps;
}
@@ -82,7 +84,7 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
// 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.getFastAmountInWeiAsync();
+ gasPriceEstimator.getGasInfoAsync();
// tslint:disable-next-line:no-floating-promises
this._flashErrorIfWrongNetwork();