aboutsummaryrefslogtreecommitdiffstats
path: root/packages/instant/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'packages/instant/src/components')
-rw-r--r--packages/instant/src/components/buy_button.tsx14
-rw-r--r--packages/instant/src/components/instant_heading.tsx15
-rw-r--r--packages/instant/src/components/order_details.tsx265
-rw-r--r--packages/instant/src/components/payment_method.tsx11
-rw-r--r--packages/instant/src/components/section_header.tsx20
-rw-r--r--packages/instant/src/components/zero_ex_instant_provider.tsx1
6 files changed, 226 insertions, 100 deletions
diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx
index 1489b94d4..5c9c28ae4 100644
--- a/packages/instant/src/components/buy_button.tsx
+++ b/packages/instant/src/components/buy_button.tsx
@@ -10,6 +10,7 @@ import { WEB_3_WRAPPER_TRANSACTION_FAILED_ERROR_MSG_PREFIX } from '../constants'
import { ColorOption } from '../style/theme';
import { AffiliateInfo, Asset, ZeroExInstantError } from '../types';
import { analytics } from '../util/analytics';
+import { errorReporter } from '../util/error_reporter';
import { gasPriceEstimator } from '../util/gas_price_estimator';
import { util } from '../util/util';
@@ -82,13 +83,18 @@ export class BuyButton extends React.Component<BuyButtonProps> {
});
} catch (e) {
if (e instanceof Error) {
- if (e.message === AssetBuyerError.SignatureRequestDenied) {
+ if (e.message === AssetBuyerError.TransactionValueTooLow) {
+ analytics.trackBuySimulationFailed(buyQuote);
+ this.props.onValidationFail(buyQuote, AssetBuyerError.TransactionValueTooLow);
+ return;
+ } else if (e.message === AssetBuyerError.SignatureRequestDenied) {
analytics.trackBuySignatureDenied(buyQuote);
this.props.onSignatureDenied(buyQuote);
return;
- } else if (e.message === AssetBuyerError.TransactionValueTooLow) {
- analytics.trackBuySimulationFailed(buyQuote);
- this.props.onValidationFail(buyQuote, AssetBuyerError.TransactionValueTooLow);
+ } else {
+ errorReporter.report(e);
+ analytics.trackBuyUnknownError(buyQuote, e.message);
+ this.props.onValidationFail(buyQuote, ZeroExInstantError.CouldNotSubmitTransaction);
return;
}
}
diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx
index 816cc5c33..5b1f9592d 100644
--- a/packages/instant/src/components/instant_heading.tsx
+++ b/packages/instant/src/components/instant_heading.tsx
@@ -113,20 +113,23 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
}
private readonly _renderEthAmount = (): React.ReactNode => {
+ const ethAmount = format.ethBaseUnitAmount(
+ this.props.totalEthBaseUnitAmount,
+ 4,
+ <AmountPlaceholder isPulsating={false} color={PLACEHOLDER_COLOR} />,
+ );
+
+ const fontSize = _.isString(ethAmount) && ethAmount.length >= 13 ? '14px' : '16px';
return (
<Text
- fontSize="16px"
+ fontSize={fontSize}
textAlign="right"
width="100%"
fontColor={ColorOption.white}
fontWeight={500}
noWrap={true}
>
- {format.ethBaseUnitAmount(
- this.props.totalEthBaseUnitAmount,
- 4,
- <AmountPlaceholder isPulsating={false} color={PLACEHOLDER_COLOR} />,
- )}
+ {ethAmount}
</Text>
);
};
diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx
index a8e0e2513..9c10ef9e6 100644
--- a/packages/instant/src/components/order_details.tsx
+++ b/packages/instant/src/components/order_details.tsx
@@ -4,124 +4,227 @@ import * as _ from 'lodash';
import * as React from 'react';
import { oc } from 'ts-optchain';
-import { BIG_NUMBER_ZERO } from '../constants';
+import { BIG_NUMBER_ZERO, DEFAULT_UNKOWN_ASSET_NAME } from '../constants';
import { ColorOption } from '../style/theme';
+import { BaseCurrency } from '../types';
import { format } from '../util/format';
import { AmountPlaceholder } from './amount_placeholder';
+import { SectionHeader } from './section_header';
import { Container } from './ui/container';
import { Flex } from './ui/flex';
-import { Text } from './ui/text';
+import { Text, TextProps } from './ui/text';
export interface OrderDetailsProps {
buyQuoteInfo?: BuyQuoteInfo;
selectedAssetUnitAmount?: BigNumber;
ethUsdPrice?: BigNumber;
isLoading: boolean;
+ assetName?: string;
+ baseCurrency: BaseCurrency;
+ onBaseCurrencySwitchEth: () => void;
+ onBaseCurrencySwitchUsd: () => void;
}
export class OrderDetails extends React.Component<OrderDetailsProps> {
public render(): React.ReactNode {
- const { buyQuoteInfo, ethUsdPrice, selectedAssetUnitAmount } = this.props;
- const buyQuoteAccessor = oc(buyQuoteInfo);
- const assetEthBaseUnitAmount = buyQuoteAccessor.assetEthAmount();
- const feeEthBaseUnitAmount = buyQuoteAccessor.feeEthAmount();
- const totalEthBaseUnitAmount = buyQuoteAccessor.totalEthAmount();
- const pricePerTokenEth =
- !_.isUndefined(assetEthBaseUnitAmount) &&
- !_.isUndefined(selectedAssetUnitAmount) &&
- !selectedAssetUnitAmount.eq(BIG_NUMBER_ZERO)
- ? assetEthBaseUnitAmount.div(selectedAssetUnitAmount).ceil()
- : undefined;
+ const shouldShowUsdError = this.props.baseCurrency === BaseCurrency.USD && this._hadErrorFetchingUsdPrice();
return (
<Container width="100%" flexGrow={1} padding="20px 20px 0px 20px">
- <Container marginBottom="10px">
- <Text
- letterSpacing="1px"
- fontColor={ColorOption.primaryColor}
- fontWeight={600}
- textTransform="uppercase"
- fontSize="14px"
- >
- Order Details
- </Text>
- </Container>
- <EthAmountRow
- rowLabel="Token Price"
- ethAmount={pricePerTokenEth}
- ethUsdPrice={ethUsdPrice}
- isLoading={this.props.isLoading}
+ <Container marginBottom="10px">{this._renderHeader()}</Container>
+ {shouldShowUsdError ? this._renderErrorFetchingUsdPrice() : this._renderRows()}
+ </Container>
+ );
+ }
+
+ private _renderRows(): React.ReactNode {
+ const { buyQuoteInfo } = this.props;
+ return (
+ <React.Fragment>
+ <OrderDetailsRow
+ labelText={this._assetAmountLabel()}
+ primaryValue={this._displayAmountOrPlaceholder(buyQuoteInfo && buyQuoteInfo.assetEthAmount)}
/>
- <EthAmountRow
- rowLabel="Fee"
- ethAmount={feeEthBaseUnitAmount}
- ethUsdPrice={ethUsdPrice}
- isLoading={this.props.isLoading}
+ <OrderDetailsRow
+ labelText="Fee"
+ primaryValue={this._displayAmountOrPlaceholder(buyQuoteInfo && buyQuoteInfo.feeEthAmount)}
/>
- <EthAmountRow
- rowLabel="Total Cost"
- ethAmount={totalEthBaseUnitAmount}
- ethUsdPrice={ethUsdPrice}
- shouldEmphasize={true}
- isLoading={this.props.isLoading}
+ <OrderDetailsRow
+ labelText="Total Cost"
+ isLabelBold={true}
+ primaryValue={this._displayAmountOrPlaceholder(buyQuoteInfo && buyQuoteInfo.totalEthAmount)}
+ isPrimaryValueBold={true}
+ secondaryValue={this._totalCostSecondaryValue()}
/>
- </Container>
+ </React.Fragment>
);
}
-}
-export interface EthAmountRowProps {
- rowLabel: string;
- ethAmount?: BigNumber;
- isEthAmountInBaseUnits?: boolean;
- ethUsdPrice?: BigNumber;
- shouldEmphasize?: boolean;
- isLoading: boolean;
+ private _renderErrorFetchingUsdPrice(): React.ReactNode {
+ return (
+ <Text>
+ There was an error fetching the USD price.
+ <Text
+ onClick={this.props.onBaseCurrencySwitchEth}
+ fontWeight={700}
+ fontColor={ColorOption.primaryColor}
+ >
+ Click here
+ </Text>
+ {' to view ETH prices'}
+ </Text>
+ );
+ }
+
+ private _hadErrorFetchingUsdPrice(): boolean {
+ return this.props.ethUsdPrice ? this.props.ethUsdPrice.equals(BIG_NUMBER_ZERO) : false;
+ }
+
+ private _totalCostSecondaryValue(): React.ReactNode {
+ const secondaryCurrency = this.props.baseCurrency === BaseCurrency.USD ? BaseCurrency.ETH : BaseCurrency.USD;
+
+ const canDisplayCurrency =
+ secondaryCurrency === BaseCurrency.ETH ||
+ (secondaryCurrency === BaseCurrency.USD && this.props.ethUsdPrice && !this._hadErrorFetchingUsdPrice());
+
+ if (this.props.buyQuoteInfo && canDisplayCurrency) {
+ return this._displayAmount(secondaryCurrency, this.props.buyQuoteInfo.totalEthAmount);
+ } else {
+ return undefined;
+ }
+ }
+
+ private _displayAmountOrPlaceholder(weiAmount?: BigNumber): React.ReactNode {
+ const { baseCurrency, isLoading } = this.props;
+
+ if (_.isUndefined(weiAmount)) {
+ return (
+ <Container opacity={0.5}>
+ <AmountPlaceholder color={ColorOption.lightGrey} isPulsating={isLoading} />
+ </Container>
+ );
+ }
+
+ return this._displayAmount(baseCurrency, weiAmount);
+ }
+
+ private _displayAmount(currency: BaseCurrency, weiAmount: BigNumber): React.ReactNode {
+ switch (currency) {
+ case BaseCurrency.USD:
+ return format.ethBaseUnitAmountInUsd(weiAmount, this.props.ethUsdPrice, 2, '');
+ case BaseCurrency.ETH:
+ return format.ethBaseUnitAmount(weiAmount, 4, '');
+ }
+ }
+
+ private _assetAmountLabel(): React.ReactNode {
+ const { assetName, baseCurrency } = this.props;
+ const numTokens = this.props.selectedAssetUnitAmount;
+
+ // Display as 0 if we have a selected asset
+ const displayNumTokens =
+ assetName && assetName !== DEFAULT_UNKOWN_ASSET_NAME && _.isUndefined(numTokens)
+ ? new BigNumber(0)
+ : numTokens;
+ if (!_.isUndefined(displayNumTokens)) {
+ let numTokensWithSymbol: React.ReactNode = displayNumTokens.toString();
+ if (assetName) {
+ numTokensWithSymbol += ` ${assetName}`;
+ }
+ const pricePerTokenWei = this._pricePerTokenWei();
+ if (pricePerTokenWei) {
+ const atPriceDisplay = (
+ <Text fontColor={ColorOption.lightGrey}>
+ @ {this._displayAmount(baseCurrency, pricePerTokenWei)}
+ </Text>
+ );
+ numTokensWithSymbol = (
+ <React.Fragment>
+ {numTokensWithSymbol} {atPriceDisplay}
+ </React.Fragment>
+ );
+ }
+ return numTokensWithSymbol;
+ }
+ return 'Token Amount';
+ }
+
+ private _pricePerTokenWei(): BigNumber | undefined {
+ const buyQuoteAccessor = oc(this.props.buyQuoteInfo);
+ const assetTotalInWei = buyQuoteAccessor.assetEthAmount();
+ const selectedAssetUnitAmount = this.props.selectedAssetUnitAmount;
+ return !_.isUndefined(assetTotalInWei) &&
+ !_.isUndefined(selectedAssetUnitAmount) &&
+ !selectedAssetUnitAmount.eq(BIG_NUMBER_ZERO)
+ ? assetTotalInWei.div(selectedAssetUnitAmount).ceil()
+ : undefined;
+ }
+
+ private _baseCurrencyChoice(choice: BaseCurrency): React.ReactNode {
+ const onClick =
+ choice === BaseCurrency.ETH ? this.props.onBaseCurrencySwitchEth : this.props.onBaseCurrencySwitchUsd;
+ const isSelected = this.props.baseCurrency === choice;
+
+ const textStyle: TextProps = { onClick, fontSize: '12px' };
+ if (isSelected) {
+ textStyle.fontColor = ColorOption.primaryColor;
+ textStyle.fontWeight = 700;
+ } else {
+ textStyle.fontColor = ColorOption.lightGrey;
+ }
+ return <Text {...textStyle}>{choice}</Text>;
+ }
+
+ private _renderHeader(): React.ReactNode {
+ return (
+ <Flex justify="space-between">
+ <SectionHeader>Order Details</SectionHeader>
+ <Container>
+ {this._baseCurrencyChoice(BaseCurrency.ETH)}
+ <Container marginLeft="5px" marginRight="5px" display="inline">
+ <Text fontSize="12px" fontColor={ColorOption.feintGrey}>
+ /
+ </Text>
+ </Container>
+ {this._baseCurrencyChoice(BaseCurrency.USD)}
+ </Container>
+ </Flex>
+ );
+ }
}
-export class EthAmountRow extends React.Component<EthAmountRowProps> {
- public static defaultProps = {
- shouldEmphasize: false,
- isEthAmountInBaseUnits: true,
- };
+export interface OrderDetailsRowProps {
+ labelText: React.ReactNode;
+ isLabelBold?: boolean;
+ isPrimaryValueBold?: boolean;
+ primaryValue: React.ReactNode;
+ secondaryValue?: React.ReactNode;
+}
+export class OrderDetailsRow extends React.Component<OrderDetailsRowProps, {}> {
public render(): React.ReactNode {
- const { rowLabel, ethAmount, isEthAmountInBaseUnits, shouldEmphasize, isLoading } = this.props;
-
- const fontWeight = shouldEmphasize ? 700 : 400;
- const ethFormatter = isEthAmountInBaseUnits ? format.ethBaseUnitAmount : format.ethUnitAmount;
return (
<Container padding="10px 0px" borderTop="1px dashed" borderColor={ColorOption.feintGrey}>
<Flex justify="space-between">
- <Text fontWeight={fontWeight} fontColor={ColorOption.grey}>
- {rowLabel}
+ <Text fontWeight={this.props.isLabelBold ? 700 : 400} fontColor={ColorOption.grey}>
+ {this.props.labelText}
</Text>
- <Container>
- {this._renderUsdSection()}
- <Text fontWeight={fontWeight} fontColor={ColorOption.grey}>
- {ethFormatter(
- ethAmount,
- 4,
- <Container opacity={0.5}>
- <AmountPlaceholder color={ColorOption.lightGrey} isPulsating={isLoading} />
- </Container>,
- )}
- </Text>
- </Container>
+ <Container>{this._renderValues()}</Container>
</Flex>
</Container>
);
}
- private _renderUsdSection(): React.ReactNode {
- const usdFormatter = this.props.isEthAmountInBaseUnits
- ? format.ethBaseUnitAmountInUsd
- : format.ethUnitAmountInUsd;
- const shouldHideUsdPriceSection = _.isUndefined(this.props.ethUsdPrice) || _.isUndefined(this.props.ethAmount);
- return shouldHideUsdPriceSection ? null : (
+
+ private _renderValues(): React.ReactNode {
+ const secondaryValueNode: React.ReactNode = this.props.secondaryValue && (
<Container marginRight="3px" display="inline-block">
- <Text fontColor={ColorOption.lightGrey}>
- ({usdFormatter(this.props.ethAmount, this.props.ethUsdPrice)})
- </Text>
+ <Text fontColor={ColorOption.lightGrey}>({this.props.secondaryValue})</Text>
</Container>
);
+ return (
+ <React.Fragment>
+ {secondaryValueNode}
+ <Text fontWeight={this.props.isPrimaryValueBold ? 700 : 400}>{this.props.primaryValue}</Text>
+ </React.Fragment>
+ );
}
}
diff --git a/packages/instant/src/components/payment_method.tsx b/packages/instant/src/components/payment_method.tsx
index 7c93f1d1c..abadf4bd6 100644
--- a/packages/instant/src/components/payment_method.tsx
+++ b/packages/instant/src/components/payment_method.tsx
@@ -8,6 +8,7 @@ import { envUtil } from '../util/env';
import { CoinbaseWalletLogo } from './coinbase_wallet_logo';
import { MetaMaskLogo } from './meta_mask_logo';
import { PaymentMethodDropdown } from './payment_method_dropdown';
+import { SectionHeader } from './section_header';
import { Circle } from './ui/circle';
import { Container } from './ui/container';
import { Flex } from './ui/flex';
@@ -29,15 +30,7 @@ export class PaymentMethod extends React.Component<PaymentMethodProps> {
<Container width="100%" height="120px" padding="20px 20px 0px 20px">
<Container marginBottom="12px">
<Flex justify="space-between">
- <Text
- letterSpacing="1px"
- fontColor={ColorOption.primaryColor}
- fontWeight={600}
- textTransform="uppercase"
- fontSize="14px"
- >
- {this._renderTitleText()}
- </Text>
+ <SectionHeader>{this._renderTitleText()}</SectionHeader>
{this._renderTitleLabel()}
</Flex>
</Container>
diff --git a/packages/instant/src/components/section_header.tsx b/packages/instant/src/components/section_header.tsx
new file mode 100644
index 000000000..d0974ebdc
--- /dev/null
+++ b/packages/instant/src/components/section_header.tsx
@@ -0,0 +1,20 @@
+import * as React from 'react';
+
+import { ColorOption } from '../style/theme';
+
+import { Text } from './ui/text';
+
+export interface SectionHeaderProps {}
+export const SectionHeader: React.StatelessComponent<SectionHeaderProps> = props => {
+ return (
+ <Text
+ letterSpacing="1px"
+ fontColor={ColorOption.primaryColor}
+ fontWeight={600}
+ textTransform="uppercase"
+ fontSize="12px"
+ >
+ {props.children}
+ </Text>
+ );
+};
diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx
index 204115fa9..2de327cd7 100644
--- a/packages/instant/src/components/zero_ex_instant_provider.tsx
+++ b/packages/instant/src/components/zero_ex_instant_provider.tsx
@@ -122,6 +122,7 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
window,
state.selectedAsset,
this.props.affiliateInfo,
+ state.baseCurrency,
),
);
analytics.trackInstantOpened();