diff options
author | Steve Klebanoff <steve.klebanoff@gmail.com> | 2018-12-22 06:57:16 +0800 |
---|---|---|
committer | Steve Klebanoff <steve.klebanoff@gmail.com> | 2018-12-22 06:57:16 +0800 |
commit | e144ebbb936c74b4af0e1c1776578208ea148de4 (patch) | |
tree | c5ab2823603b25d11d46493e9c8f79801bef0dcd /packages/instant/src/components | |
parent | aa9aa1f58a4e63b5e5c7863f3b7afb020d7712c5 (diff) | |
parent | 9b540fd8e52e7578d3749e6d9ef9cd97d602ffb3 (diff) | |
download | dexon-sol-tools-e144ebbb936c74b4af0e1c1776578208ea148de4.tar dexon-sol-tools-e144ebbb936c74b4af0e1c1776578208ea148de4.tar.gz dexon-sol-tools-e144ebbb936c74b4af0e1c1776578208ea148de4.tar.bz2 dexon-sol-tools-e144ebbb936c74b4af0e1c1776578208ea148de4.tar.lz dexon-sol-tools-e144ebbb936c74b4af0e1c1776578208ea148de4.tar.xz dexon-sol-tools-e144ebbb936c74b4af0e1c1776578208ea148de4.tar.zst dexon-sol-tools-e144ebbb936c74b4af0e1c1776578208ea148de4.zip |
Merge branch 'development' into feature/instant/tell-amount-available
Diffstat (limited to 'packages/instant/src/components')
-rw-r--r-- | packages/instant/src/components/buy_button.tsx | 14 | ||||
-rw-r--r-- | packages/instant/src/components/instant_heading.tsx | 15 | ||||
-rw-r--r-- | packages/instant/src/components/order_details.tsx | 265 | ||||
-rw-r--r-- | packages/instant/src/components/payment_method.tsx | 11 | ||||
-rw-r--r-- | packages/instant/src/components/section_header.tsx | 20 | ||||
-rw-r--r-- | packages/instant/src/components/zero_ex_instant_provider.tsx | 1 |
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(); |