From 5cbe04acab982ead39f7794547de0f045952a1b7 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 7 Dec 2018 11:14:55 -0800 Subject: feat(instant): ETH/USD toggle --- packages/instant/src/components/order_details.tsx | 196 +++++++++++++++++---- .../containers/latest_buy_quote_order_details.ts | 33 ++-- packages/instant/src/redux/actions.ts | 4 +- packages/instant/src/redux/reducer.ts | 8 + packages/instant/src/types.ts | 5 + packages/instant/src/util/buy_quote.ts | 71 ++++++++ 6 files changed, 269 insertions(+), 48 deletions(-) create mode 100644 packages/instant/src/util/buy_quote.ts (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index a8e0e2513..85761a5b9 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -6,63 +6,92 @@ import { oc } from 'ts-optchain'; import { BIG_NUMBER_ZERO } from '../constants'; import { ColorOption } from '../style/theme'; +import { BaseCurrency } from '../types'; +import { buyQuoteUtil } from '../util/buy_quote'; import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; import { Container } from './ui/container'; import { Flex } from './ui/flex'; -import { Text } from './ui/text'; +import { Text, TextProps } from './ui/text'; + +interface BaseCurrenySwitchProps { + currencyName: string; + onClick: () => void; + isSelected: boolean; +} +const BaseCurrencySelector: React.StatelessComponent = props => { + const textStyle: TextProps = { onClick: props.onClick, fontSize: '12px' }; + if (props.isSelected) { + textStyle.fontColor = ColorOption.primaryColor; + textStyle.fontWeight = 700; + } + return {props.currencyName}; +}; 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 { 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 weiAmounts = buyQuoteUtil.getWeiAmounts(selectedAssetUnitAmount, buyQuoteInfo); + + const displayAmounts = + this.props.baseCurrency === BaseCurrency.USD + ? buyQuoteUtil.displayAmountsUsd(weiAmounts, ethUsdPrice) + : buyQuoteUtil.displayAmountsEth(weiAmounts, ethUsdPrice); + return ( - - Order Details - + + + Order Details + + + + + + / + + + + - - - + @@ -79,6 +108,103 @@ export interface EthAmountRowProps { isLoading: boolean; } +export interface OrderDetailsRowProps { + labelText: string; + isLabelBold?: boolean; + isLoading: boolean; + value?: React.ReactNode; +} +export class OrderDetailsRow extends React.Component { + public render(): React.ReactNode { + const { labelText, value, isLabelBold, isLoading } = this.props; + return ( + + + + {labelText} + + + {value || ( + + + + )} + + + + ); + } +} +export interface TotalCostRowProps { + displayPrimaryTotalCost?: React.ReactNode; + displaySecondaryTotalCost?: React.ReactNode; + isLoading: boolean; +} +export class TotalCostRow extends React.Component { + public render(): React.ReactNode { + let value: React.ReactNode; + if (this.props.displayPrimaryTotalCost) { + const secondaryText = this.props.displaySecondaryTotalCost && ( + + ({this.props.displaySecondaryTotalCost}) + + ); + value = ( + + {secondaryText} + + {this.props.displayPrimaryTotalCost} + + + ); + } + + return ( + + ); + } +} + +export interface TokenAmountRowProps { + assetName?: string; + displayPricePerToken?: React.ReactNode; + displayTotalPrice?: React.ReactNode; + isLoading: boolean; + numTokens?: BigNumber; +} +export class TokenAmountRow extends React.Component { + public static DEFAULT_TEXT: string = 'Token Price'; + public render(): React.ReactNode { + return ( + + ); + } + private _labelText(): string { + if (this.props.isLoading) { + return TokenAmountRow.DEFAULT_TEXT; + } + const { numTokens, displayPricePerToken, assetName } = this.props; + if (numTokens) { + let numTokensWithSymbol = numTokens.toString(); + + if (assetName) { + numTokensWithSymbol += ` ${assetName}`; + } + + if (displayPricePerToken) { + numTokensWithSymbol += ` @ ${displayPricePerToken}`; + } + return numTokensWithSymbol; + } + + return TokenAmountRow.DEFAULT_TEXT; + } +} + export class EthAmountRow extends React.Component { public static defaultProps = { shouldEmphasize: false, 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 5dfe535e7..148735c47 100644 --- a/packages/instant/src/containers/latest_buy_quote_order_details.ts +++ b/packages/instant/src/containers/latest_buy_quote_order_details.ts @@ -1,32 +1,41 @@ -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 { Dispatch } from 'redux'; import { oc } from 'ts-optchain'; +import { Action, actions } from '../redux/actions'; import { State } from '../redux/reducer'; -import { OrderDetails } from '../components/order_details'; -import { AsyncProcessState } from '../types'; +import { OrderDetails, OrderDetailsProps } from '../components/order_details'; +import { AsyncProcessState, BaseCurrency, Omit } from '../types'; +import { assetUtils } from '../util/asset'; -export interface LatestBuyQuoteOrderDetailsProps {} - -interface ConnectedState { - buyQuoteInfo?: BuyQuoteInfo; - selectedAssetUnitAmount?: BigNumber; - ethUsdPrice?: BigNumber; - isLoading: boolean; -} +type DispatchProperties = 'onBaseCurrencySwitchEth' | 'onBaseCurrencySwitchUsd'; +interface ConnectedState extends Omit {} const mapStateToProps = (state: State, _ownProps: LatestBuyQuoteOrderDetailsProps): ConnectedState => ({ // use the worst case quote info buyQuoteInfo: oc(state).latestBuyQuote.worstCaseQuoteInfo(), selectedAssetUnitAmount: state.selectedAssetUnitAmount, ethUsdPrice: state.ethUsdPrice, isLoading: state.quoteRequestState === AsyncProcessState.Pending, + assetName: assetUtils.bestNameForAsset(state.selectedAsset), + baseCurrency: state.baseCurrency, }); +interface ConnectedDispatch extends Pick {} +const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ + onBaseCurrencySwitchEth: () => { + dispatch(actions.updateBaseCurrency(BaseCurrency.ETH)); + }, + onBaseCurrencySwitchUsd: () => { + dispatch(actions.updateBaseCurrency(BaseCurrency.USD)); + }, +}); + +export interface LatestBuyQuoteOrderDetailsProps {} export const LatestBuyQuoteOrderDetails: React.ComponentClass = connect( mapStateToProps, + mapDispatchToProps, )(OrderDetails); diff --git a/packages/instant/src/redux/actions.ts b/packages/instant/src/redux/actions.ts index 77e3dec12..9d7a61fc7 100644 --- a/packages/instant/src/redux/actions.ts +++ b/packages/instant/src/redux/actions.ts @@ -2,7 +2,7 @@ import { BuyQuote } from '@0x/asset-buyer'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; -import { ActionsUnion, AddressAndEthBalanceInWei, Asset, StandardSlidingPanelContent } from '../types'; +import { ActionsUnion, AddressAndEthBalanceInWei, Asset, BaseCurrency, StandardSlidingPanelContent } from '../types'; export interface PlainAction { type: T; @@ -43,6 +43,7 @@ export enum ActionTypes { RESET_AMOUNT = 'RESET_AMOUNT', OPEN_STANDARD_SLIDING_PANEL = 'OPEN_STANDARD_SLIDING_PANEL', CLOSE_STANDARD_SLIDING_PANEL = 'CLOSE_STANDARD_SLIDING_PANEL', + UPDATE_BASE_CURRENCY = 'UPDATE_BASE_CURRENCY', } export const actions = { @@ -72,4 +73,5 @@ export const actions = { openStandardSlidingPanel: (content: StandardSlidingPanelContent) => createAction(ActionTypes.OPEN_STANDARD_SLIDING_PANEL, content), closeStandardSlidingPanel: () => createAction(ActionTypes.CLOSE_STANDARD_SLIDING_PANEL), + updateBaseCurrency: (baseCurrency: BaseCurrency) => createAction(ActionTypes.UPDATE_BASE_CURRENCY, baseCurrency), }; diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index a9a407b7d..4e734041f 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -14,6 +14,7 @@ import { Asset, AssetMetaData, AsyncProcessState, + BaseCurrency, DisplayStatus, Network, OrderProcessState, @@ -33,6 +34,7 @@ export interface DefaultState { latestErrorDisplayStatus: DisplayStatus; quoteRequestState: AsyncProcessState; standardSlidingPanelSettings: StandardSlidingPanelSettings; + baseCurrency: BaseCurrency; } // State that is required but needs to be derived from the props @@ -64,6 +66,7 @@ export const DEFAULT_STATE: DefaultState = { animationState: 'none', content: StandardSlidingPanelContent.None, }, + baseCurrency: BaseCurrency.ETH, }; export const createReducer = (initialState: State) => { @@ -243,6 +246,11 @@ export const createReducer = (initialState: State) => { animationState: 'slidOut', }, }; + case ActionTypes.UPDATE_BASE_CURRENCY: + return { + ...state, + baseCurrency: action.data, + }; default: return state; } diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index 1c7490e63..e7c920f36 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -26,6 +26,11 @@ export enum QuoteFetchOrigin { Heartbeat = 'Heartbeat', } +export enum BaseCurrency { + USD = 'USD', + ETH = 'ETH', +} + export interface SimulatedProgress { startTimeUnix: number; expectedEndTimeUnix: number; diff --git a/packages/instant/src/util/buy_quote.ts b/packages/instant/src/util/buy_quote.ts new file mode 100644 index 000000000..acd4d389c --- /dev/null +++ b/packages/instant/src/util/buy_quote.ts @@ -0,0 +1,71 @@ +import { BuyQuoteInfo } from '@0x/asset-buyer'; +import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; +import { oc } from 'ts-optchain'; + +import { format } from '../util/format'; + +import { BIG_NUMBER_ZERO } from '../constants'; + +export interface DisplayAmounts { + pricePerToken: React.ReactNode; + assetTotal: React.ReactNode; + feeTotal: React.ReactNode; + primaryGrandTotal: React.ReactNode; + secondaryGrandTotal?: React.ReactNode; +} + +export interface BuyQuoteWeiAmounts { + assetTotalInWei: BigNumber | undefined; + feeTotalInWei: BigNumber | undefined; + grandTotalInWei: BigNumber | undefined; + pricePerTokenInWei: BigNumber | undefined; +} + +const ethDisplayFormat = (amountInWei?: BigNumber) => { + return format.ethBaseUnitAmount(amountInWei, 4, ''); +}; +const usdDisplayFormat = (amountInWei?: BigNumber, ethUsdPrice?: BigNumber) => { + return format.ethBaseUnitAmountInUsd(amountInWei, ethUsdPrice, 2, ''); +}; + +export const buyQuoteUtil = { + getWeiAmounts: ( + selectedAssetUnitAmount: BigNumber | undefined, + buyQuoteInfo: BuyQuoteInfo | undefined, + ): BuyQuoteWeiAmounts => { + const buyQuoteAccessor = oc(buyQuoteInfo); + const assetTotalInWei = buyQuoteAccessor.assetEthAmount(); + const pricePerTokenInWei = + !_.isUndefined(assetTotalInWei) && + !_.isUndefined(selectedAssetUnitAmount) && + !selectedAssetUnitAmount.eq(BIG_NUMBER_ZERO) + ? assetTotalInWei.div(selectedAssetUnitAmount).ceil() + : undefined; + + return { + assetTotalInWei, + feeTotalInWei: buyQuoteAccessor.feeEthAmount(), + grandTotalInWei: buyQuoteAccessor.totalEthAmount(), + pricePerTokenInWei, + }; + }, + displayAmountsEth: (weiAmounts: BuyQuoteWeiAmounts, ethUsdPrice?: BigNumber): DisplayAmounts => { + return { + pricePerToken: ethDisplayFormat(weiAmounts.pricePerTokenInWei), + assetTotal: ethDisplayFormat(weiAmounts.assetTotalInWei), + feeTotal: ethDisplayFormat(weiAmounts.feeTotalInWei), + primaryGrandTotal: ethDisplayFormat(weiAmounts.grandTotalInWei), + secondaryGrandTotal: usdDisplayFormat(weiAmounts.grandTotalInWei, ethUsdPrice), + }; + }, + displayAmountsUsd: (weiAmounts: BuyQuoteWeiAmounts, ethUsdPrice?: BigNumber): DisplayAmounts => { + return { + pricePerToken: usdDisplayFormat(weiAmounts.pricePerTokenInWei, ethUsdPrice), + assetTotal: usdDisplayFormat(weiAmounts.assetTotalInWei, ethUsdPrice), + feeTotal: usdDisplayFormat(weiAmounts.feeTotalInWei, ethUsdPrice), + primaryGrandTotal: usdDisplayFormat(weiAmounts.grandTotalInWei, ethUsdPrice), + secondaryGrandTotal: ethDisplayFormat(weiAmounts.grandTotalInWei), + }; + }, +}; -- cgit v1.2.3 From 286474ca767e955b4f7fca985d3864511fa4dfbb Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 11 Dec 2018 14:43:07 -0800 Subject: Remove unused component --- packages/instant/src/components/order_details.tsx | 49 ----------------------- 1 file changed, 49 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 85761a5b9..2fcde5aa4 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -2,9 +2,7 @@ import { BuyQuoteInfo } from '@0x/asset-buyer'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import * as React from 'react'; -import { oc } from 'ts-optchain'; -import { BIG_NUMBER_ZERO } from '../constants'; import { ColorOption } from '../style/theme'; import { BaseCurrency } from '../types'; import { buyQuoteUtil } from '../util/buy_quote'; @@ -204,50 +202,3 @@ export class TokenAmountRow extends React.Component { return TokenAmountRow.DEFAULT_TEXT; } } - -export class EthAmountRow extends React.Component { - public static defaultProps = { - shouldEmphasize: false, - isEthAmountInBaseUnits: true, - }; - 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 ( - - - - {rowLabel} - - - {this._renderUsdSection()} - - {ethFormatter( - ethAmount, - 4, - - - , - )} - - - - - ); - } - 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 : ( - - - ({usdFormatter(this.props.ethAmount, this.props.ethUsdPrice)}) - - - ); - } -} -- cgit v1.2.3 From ee7d6fb3afad313fa699018c727c24db4f2024bd Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 11 Dec 2018 14:55:32 -0800 Subject: Show as 0 when selected --- packages/instant/src/components/order_details.tsx | 18 +++++++++++------- packages/instant/src/constants.ts | 1 + packages/instant/src/util/asset.ts | 3 ++- 3 files changed, 14 insertions(+), 8 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 2fcde5aa4..67090898e 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -3,10 +3,10 @@ import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import * as React from 'react'; +import { DEFAULT_UNKOWN_ASSET_NAME } from '../constants'; import { ColorOption } from '../style/theme'; import { BaseCurrency } from '../types'; import { buyQuoteUtil } from '../util/buy_quote'; -import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; @@ -171,7 +171,7 @@ export interface TokenAmountRowProps { numTokens?: BigNumber; } export class TokenAmountRow extends React.Component { - public static DEFAULT_TEXT: string = 'Token Price'; + public static DEFAULT_TEXT: string = 'Token Amount'; public render(): React.ReactNode { return ( { ); } private _labelText(): string { - if (this.props.isLoading) { - return TokenAmountRow.DEFAULT_TEXT; - } - const { numTokens, displayPricePerToken, assetName } = this.props; - if (numTokens) { + const { displayPricePerToken, assetName } = this.props; + + // Display as 0 if we have a selected asset + const numTokens = + assetName && assetName !== DEFAULT_UNKOWN_ASSET_NAME && _.isUndefined(this.props.numTokens) + ? 0 + : this.props.numTokens; + + if (!_.isUndefined(numTokens)) { let numTokensWithSymbol = numTokens.toString(); if (assetName) { diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index f83eb4ac7..975dfcbea 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -17,6 +17,7 @@ export const ONE_MINUTE_MS = ONE_SECOND_MS * 60; export const GIT_SHA = process.env.GIT_SHA; export const NODE_ENV = process.env.NODE_ENV; export const NPM_PACKAGE_VERSION = process.env.NPM_PACKAGE_VERSION; +export const DEFAULT_UNKOWN_ASSET_NAME = '???'; export const ACCOUNT_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 5; export const BUY_QUOTE_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 15; export const DEFAULT_GAS_PRICE = GWEI_IN_WEI.mul(6); diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index 13f84ef74..faaeb7c22 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -2,6 +2,7 @@ import { AssetBuyerError } from '@0x/asset-buyer'; import { AssetProxyId, ObjectMap } from '@0x/types'; import * as _ from 'lodash'; +import { DEFAULT_UNKOWN_ASSET_NAME } from '../constants'; import { assetDataNetworkMapping } from '../data/asset_data_network_mapping'; import { Asset, AssetMetaData, ERC20Asset, Network, ZeroExInstantError } from '../types'; @@ -71,7 +72,7 @@ export const assetUtils = { } return metaData; }, - bestNameForAsset: (asset?: Asset, defaultName: string = '???'): string => { + bestNameForAsset: (asset?: Asset, defaultName: string = DEFAULT_UNKOWN_ASSET_NAME): string => { if (_.isUndefined(asset)) { return defaultName; } -- cgit v1.2.3 From b1a73f5c742b96906e832e532dbdc3e4e9583c6d Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 11 Dec 2018 17:03:07 -0800 Subject: Refactor out custom components --- packages/instant/src/components/order_details.tsx | 137 +++++++++------------- 1 file changed, 55 insertions(+), 82 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 67090898e..9eaf2fb2e 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -28,6 +28,49 @@ const BaseCurrencySelector: React.StatelessComponent = p return {props.currencyName}; }; +const grandTotalDisplayValue = ( + displayPrimaryTotalCost?: React.ReactNode, + displaySecondaryTotalCost?: React.ReactNode, +): React.ReactNode => { + if (!displayPrimaryTotalCost) { + return undefined; + } + const secondaryText = displaySecondaryTotalCost && ( + + ({displaySecondaryTotalCost}) + + ); + return ( + + {secondaryText} + + {displayPrimaryTotalCost} + + + ); +}; + +const tokenAmountLabel = (displayPricePerToken?: React.ReactNode, assetName?: string, numTokens?: BigNumber) => { + // 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 = displayNumTokens.toString(); + + if (assetName) { + numTokensWithSymbol += ` ${assetName}`; + } + + if (displayPricePerToken) { + numTokensWithSymbol += ` @ ${displayPricePerToken}`; + } + return numTokensWithSymbol; + } + + return 'Token Amount'; +}; + export interface OrderDetailsProps { buyQuoteInfo?: BuyQuoteInfo; selectedAssetUnitAmount?: BigNumber; @@ -79,18 +122,21 @@ export class OrderDetails extends React.Component { - - - + ); @@ -133,76 +179,3 @@ export class OrderDetailsRow extends React.Component { ); } } -export interface TotalCostRowProps { - displayPrimaryTotalCost?: React.ReactNode; - displaySecondaryTotalCost?: React.ReactNode; - isLoading: boolean; -} -export class TotalCostRow extends React.Component { - public render(): React.ReactNode { - let value: React.ReactNode; - if (this.props.displayPrimaryTotalCost) { - const secondaryText = this.props.displaySecondaryTotalCost && ( - - ({this.props.displaySecondaryTotalCost}) - - ); - value = ( - - {secondaryText} - - {this.props.displayPrimaryTotalCost} - - - ); - } - - return ( - - ); - } -} - -export interface TokenAmountRowProps { - assetName?: string; - displayPricePerToken?: React.ReactNode; - displayTotalPrice?: React.ReactNode; - isLoading: boolean; - numTokens?: BigNumber; -} -export class TokenAmountRow extends React.Component { - public static DEFAULT_TEXT: string = 'Token Amount'; - public render(): React.ReactNode { - return ( - - ); - } - private _labelText(): string { - const { displayPricePerToken, assetName } = this.props; - - // Display as 0 if we have a selected asset - const numTokens = - assetName && assetName !== DEFAULT_UNKOWN_ASSET_NAME && _.isUndefined(this.props.numTokens) - ? 0 - : this.props.numTokens; - - if (!_.isUndefined(numTokens)) { - let numTokensWithSymbol = numTokens.toString(); - - if (assetName) { - numTokensWithSymbol += ` ${assetName}`; - } - - if (displayPricePerToken) { - numTokensWithSymbol += ` @ ${displayPricePerToken}`; - } - return numTokensWithSymbol; - } - - return TokenAmountRow.DEFAULT_TEXT; - } -} -- cgit v1.2.3 From 5f7ed71937c8319bb5613dc57f005848b1fa336c Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 11 Dec 2018 17:06:27 -0800 Subject: Delete old interface and rename BaseCurrencyChoice --- packages/instant/src/components/order_details.tsx | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 9eaf2fb2e..8bff0af14 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -14,12 +14,12 @@ import { Container } from './ui/container'; import { Flex } from './ui/flex'; import { Text, TextProps } from './ui/text'; -interface BaseCurrenySwitchProps { +interface BaseCurryChoiceProps { currencyName: string; onClick: () => void; isSelected: boolean; } -const BaseCurrencySelector: React.StatelessComponent = props => { +const BaseCurrencyChoice: React.StatelessComponent = props => { const textStyle: TextProps = { onClick: props.onClick, fontSize: '12px' }; if (props.isSelected) { textStyle.fontColor = ColorOption.primaryColor; @@ -106,7 +106,7 @@ export class OrderDetails extends React.Component { - { / - { } } -export interface EthAmountRowProps { - rowLabel: string; - ethAmount?: BigNumber; - isEthAmountInBaseUnits?: boolean; - ethUsdPrice?: BigNumber; - shouldEmphasize?: boolean; - isLoading: boolean; -} - export interface OrderDetailsRowProps { labelText: string; isLabelBold?: boolean; -- cgit v1.2.3 From 7aacf1f5a457e1166f5188836d30a8c38f3f2c68 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 08:23:40 -0800 Subject: Render OrderDetailsRow directly --- packages/instant/src/components/order_details.tsx | 39 +++++++++++++++++------ packages/instant/src/util/buy_quote.ts | 35 -------------------- 2 files changed, 29 insertions(+), 45 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 8bff0af14..f553351ff 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -7,6 +7,7 @@ import { DEFAULT_UNKOWN_ASSET_NAME } from '../constants'; import { ColorOption } from '../style/theme'; import { BaseCurrency } from '../types'; import { buyQuoteUtil } from '../util/buy_quote'; +import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; @@ -71,6 +72,19 @@ const tokenAmountLabel = (displayPricePerToken?: React.ReactNode, assetName?: st return 'Token Amount'; }; +const getDisplayAmount = ( + baseCurrency: BaseCurrency, + weiAmount?: BigNumber, + ethUsdPrice?: BigNumber, +): React.ReactNode => { + switch (baseCurrency) { + case BaseCurrency.USD: + return format.ethBaseUnitAmountInUsd(weiAmount, ethUsdPrice, 2, ''); + case BaseCurrency.ETH: + return format.ethBaseUnitAmount(weiAmount, 4, ''); + } +}; + export interface OrderDetailsProps { buyQuoteInfo?: BuyQuoteInfo; selectedAssetUnitAmount?: BigNumber; @@ -83,13 +97,14 @@ export interface OrderDetailsProps { } export class OrderDetails extends React.Component { public render(): React.ReactNode { - const { buyQuoteInfo, ethUsdPrice, selectedAssetUnitAmount } = this.props; - const weiAmounts = buyQuoteUtil.getWeiAmounts(selectedAssetUnitAmount, buyQuoteInfo); + const { baseCurrency, buyQuoteInfo, ethUsdPrice, selectedAssetUnitAmount } = this.props; - const displayAmounts = - this.props.baseCurrency === BaseCurrency.USD - ? buyQuoteUtil.displayAmountsUsd(weiAmounts, ethUsdPrice) - : buyQuoteUtil.displayAmountsEth(weiAmounts, ethUsdPrice); + const weiAmounts = buyQuoteUtil.getWeiAmounts(selectedAssetUnitAmount, buyQuoteInfo); + const secondaryCurrency = baseCurrency === BaseCurrency.USD ? BaseCurrency.ETH : BaseCurrency.USD; + const grandTotalValue = grandTotalDisplayValue( + getDisplayAmount(baseCurrency, weiAmounts.grandTotalInWei, ethUsdPrice), + getDisplayAmount(secondaryCurrency, weiAmounts.grandTotalInWei, ethUsdPrice), + ); return ( @@ -125,18 +140,22 @@ export class OrderDetails extends React.Component { + - ); diff --git a/packages/instant/src/util/buy_quote.ts b/packages/instant/src/util/buy_quote.ts index acd4d389c..0e880f51c 100644 --- a/packages/instant/src/util/buy_quote.ts +++ b/packages/instant/src/util/buy_quote.ts @@ -3,18 +3,8 @@ import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { oc } from 'ts-optchain'; -import { format } from '../util/format'; - import { BIG_NUMBER_ZERO } from '../constants'; -export interface DisplayAmounts { - pricePerToken: React.ReactNode; - assetTotal: React.ReactNode; - feeTotal: React.ReactNode; - primaryGrandTotal: React.ReactNode; - secondaryGrandTotal?: React.ReactNode; -} - export interface BuyQuoteWeiAmounts { assetTotalInWei: BigNumber | undefined; feeTotalInWei: BigNumber | undefined; @@ -22,13 +12,6 @@ export interface BuyQuoteWeiAmounts { pricePerTokenInWei: BigNumber | undefined; } -const ethDisplayFormat = (amountInWei?: BigNumber) => { - return format.ethBaseUnitAmount(amountInWei, 4, ''); -}; -const usdDisplayFormat = (amountInWei?: BigNumber, ethUsdPrice?: BigNumber) => { - return format.ethBaseUnitAmountInUsd(amountInWei, ethUsdPrice, 2, ''); -}; - export const buyQuoteUtil = { getWeiAmounts: ( selectedAssetUnitAmount: BigNumber | undefined, @@ -50,22 +33,4 @@ export const buyQuoteUtil = { pricePerTokenInWei, }; }, - displayAmountsEth: (weiAmounts: BuyQuoteWeiAmounts, ethUsdPrice?: BigNumber): DisplayAmounts => { - return { - pricePerToken: ethDisplayFormat(weiAmounts.pricePerTokenInWei), - assetTotal: ethDisplayFormat(weiAmounts.assetTotalInWei), - feeTotal: ethDisplayFormat(weiAmounts.feeTotalInWei), - primaryGrandTotal: ethDisplayFormat(weiAmounts.grandTotalInWei), - secondaryGrandTotal: usdDisplayFormat(weiAmounts.grandTotalInWei, ethUsdPrice), - }; - }, - displayAmountsUsd: (weiAmounts: BuyQuoteWeiAmounts, ethUsdPrice?: BigNumber): DisplayAmounts => { - return { - pricePerToken: usdDisplayFormat(weiAmounts.pricePerTokenInWei, ethUsdPrice), - assetTotal: usdDisplayFormat(weiAmounts.assetTotalInWei, ethUsdPrice), - feeTotal: usdDisplayFormat(weiAmounts.feeTotalInWei, ethUsdPrice), - primaryGrandTotal: usdDisplayFormat(weiAmounts.grandTotalInWei, ethUsdPrice), - secondaryGrandTotal: ethDisplayFormat(weiAmounts.grandTotalInWei), - }; - }, }; -- cgit v1.2.3 From 8923817b2ff6c8d3cffe6914634e75cdf06db4a9 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 08:25:20 -0800 Subject: Move header to helper --- packages/instant/src/components/order_details.tsx | 62 ++++++++++++----------- 1 file changed, 33 insertions(+), 29 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index f553351ff..5d306f43e 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -108,35 +108,7 @@ export class OrderDetails extends React.Component { return ( - - - - Order Details - - - - - - / - - - - - + {this._renderHeader()} { ); } + + private _renderHeader(): React.ReactNode { + return ( + + + Order Details + + + + + + / + + + + + ); + } } export interface OrderDetailsRowProps { -- cgit v1.2.3 From 3b9e8e669f179aeda7a3aa3f91c78477fa7f08cd Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:14:25 -0800 Subject: Refactor OrderDetails to use private instance methods --- packages/instant/src/components/order_details.tsx | 206 ++++++++++++---------- 1 file changed, 115 insertions(+), 91 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 5d306f43e..7e33e06ac 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -2,11 +2,11 @@ import { BuyQuoteInfo } from '@0x/asset-buyer'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import * as React from 'react'; +import { oc } from 'ts-optchain'; -import { DEFAULT_UNKOWN_ASSET_NAME } from '../constants'; +import { BIG_NUMBER_ZERO, DEFAULT_UNKOWN_ASSET_NAME } from '../constants'; import { ColorOption } from '../style/theme'; import { BaseCurrency } from '../types'; -import { buyQuoteUtil } from '../util/buy_quote'; import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; @@ -29,62 +29,6 @@ const BaseCurrencyChoice: React.StatelessComponent = props return {props.currencyName}; }; -const grandTotalDisplayValue = ( - displayPrimaryTotalCost?: React.ReactNode, - displaySecondaryTotalCost?: React.ReactNode, -): React.ReactNode => { - if (!displayPrimaryTotalCost) { - return undefined; - } - const secondaryText = displaySecondaryTotalCost && ( - - ({displaySecondaryTotalCost}) - - ); - return ( - - {secondaryText} - - {displayPrimaryTotalCost} - - - ); -}; - -const tokenAmountLabel = (displayPricePerToken?: React.ReactNode, assetName?: string, numTokens?: BigNumber) => { - // 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 = displayNumTokens.toString(); - - if (assetName) { - numTokensWithSymbol += ` ${assetName}`; - } - - if (displayPricePerToken) { - numTokensWithSymbol += ` @ ${displayPricePerToken}`; - } - return numTokensWithSymbol; - } - - return 'Token Amount'; -}; - -const getDisplayAmount = ( - baseCurrency: BaseCurrency, - weiAmount?: BigNumber, - ethUsdPrice?: BigNumber, -): React.ReactNode => { - switch (baseCurrency) { - case BaseCurrency.USD: - return format.ethBaseUnitAmountInUsd(weiAmount, ethUsdPrice, 2, ''); - case BaseCurrency.ETH: - return format.ethBaseUnitAmount(weiAmount, 4, ''); - } -}; - export interface OrderDetailsProps { buyQuoteInfo?: BuyQuoteInfo; selectedAssetUnitAmount?: BigNumber; @@ -97,42 +41,107 @@ export interface OrderDetailsProps { } export class OrderDetails extends React.Component { public render(): React.ReactNode { - const { baseCurrency, buyQuoteInfo, ethUsdPrice, selectedAssetUnitAmount } = this.props; - - const weiAmounts = buyQuoteUtil.getWeiAmounts(selectedAssetUnitAmount, buyQuoteInfo); - const secondaryCurrency = baseCurrency === BaseCurrency.USD ? BaseCurrency.ETH : BaseCurrency.USD; - const grandTotalValue = grandTotalDisplayValue( - getDisplayAmount(baseCurrency, weiAmounts.grandTotalInWei, ethUsdPrice), - getDisplayAmount(secondaryCurrency, weiAmounts.grandTotalInWei, ethUsdPrice), - ); + const { baseCurrency, buyQuoteInfo } = this.props; return ( {this._renderHeader()} + ); } + 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.props.ethUsdPrice.greaterThan(BIG_NUMBER_ZERO)); + + if (this.props.buyQuoteInfo && canDisplayCurrency) { + return this._displayAmount(secondaryCurrency, this.props.buyQuoteInfo.totalEthAmount); + } else { + return undefined; + } + } + + private _displayAmountOrPlaceholder(weiAmount?: BigNumber): React.ReactNode { + const { baseCurrency, ethUsdPrice, isLoading } = this.props; + + if (_.isUndefined(weiAmount)) { + return ( + + + + ); + } + + 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 _assetLabelText(): string { + const { assetName, baseCurrency, ethUsdPrice } = 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 = displayNumTokens.toString(); + + if (assetName) { + numTokensWithSymbol += ` ${assetName}`; + } + + const pricePerTokenWei = this._pricePerTokenWei(); + if (pricePerTokenWei) { + numTokensWithSymbol += ` @ ${this._displayAmount(baseCurrency, pricePerTokenWei)}`; + } + 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 _renderHeader(): React.ReactNode { return ( @@ -169,27 +178,42 @@ export class OrderDetails extends React.Component { export interface OrderDetailsRowProps { labelText: string; isLabelBold?: boolean; - isLoading: boolean; - value?: React.ReactNode; + isPrimaryValueBold?: boolean; + primaryValue: React.ReactNode; + secondaryValue?: React.ReactNode; } export class OrderDetailsRow extends React.Component { public render(): React.ReactNode { - const { labelText, value, isLabelBold, isLoading } = this.props; return ( - - {labelText} - - - {value || ( - - - - )} - + {this._renderLabel()} + {this._renderValues()} ); } + + private _renderLabel(): React.ReactNode { + return ( + + {this.props.labelText} + + ); + } + + private _renderValues(): React.ReactNode { + const secondaryValueNode: React.ReactNode = this.props.secondaryValue && ( + + ({this.props.secondaryValue}) + + ); + + return ( + + {secondaryValueNode} + {this.props.primaryValue} + + ); + } } -- cgit v1.2.3 From ad3d20b34267eda1a9b1d397fb15f2bce0eb221f Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:15:18 -0800 Subject: Default to USD --- packages/instant/src/redux/reducer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index 4e734041f..8c13c9c72 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -66,7 +66,7 @@ export const DEFAULT_STATE: DefaultState = { animationState: 'none', content: StandardSlidingPanelContent.None, }, - baseCurrency: BaseCurrency.ETH, + baseCurrency: BaseCurrency.USD, }; export const createReducer = (initialState: State) => { -- cgit v1.2.3 From aeefdeaef78fad38b84e728cceff2141eb302ab5 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:25:48 -0800 Subject: Remove unused util --- packages/instant/src/util/buy_quote.ts | 36 ---------------------------------- 1 file changed, 36 deletions(-) delete mode 100644 packages/instant/src/util/buy_quote.ts (limited to 'packages') diff --git a/packages/instant/src/util/buy_quote.ts b/packages/instant/src/util/buy_quote.ts deleted file mode 100644 index 0e880f51c..000000000 --- a/packages/instant/src/util/buy_quote.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { BuyQuoteInfo } from '@0x/asset-buyer'; -import { BigNumber } from '@0x/utils'; -import * as _ from 'lodash'; -import { oc } from 'ts-optchain'; - -import { BIG_NUMBER_ZERO } from '../constants'; - -export interface BuyQuoteWeiAmounts { - assetTotalInWei: BigNumber | undefined; - feeTotalInWei: BigNumber | undefined; - grandTotalInWei: BigNumber | undefined; - pricePerTokenInWei: BigNumber | undefined; -} - -export const buyQuoteUtil = { - getWeiAmounts: ( - selectedAssetUnitAmount: BigNumber | undefined, - buyQuoteInfo: BuyQuoteInfo | undefined, - ): BuyQuoteWeiAmounts => { - const buyQuoteAccessor = oc(buyQuoteInfo); - const assetTotalInWei = buyQuoteAccessor.assetEthAmount(); - const pricePerTokenInWei = - !_.isUndefined(assetTotalInWei) && - !_.isUndefined(selectedAssetUnitAmount) && - !selectedAssetUnitAmount.eq(BIG_NUMBER_ZERO) - ? assetTotalInWei.div(selectedAssetUnitAmount).ceil() - : undefined; - - return { - assetTotalInWei, - feeTotalInWei: buyQuoteAccessor.feeEthAmount(), - grandTotalInWei: buyQuoteAccessor.totalEthAmount(), - pricePerTokenInWei, - }; - }, -}; -- cgit v1.2.3 From 8a5609718aa6e31e30915300e38e47d30d528896 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:26:00 -0800 Subject: Refactor BaseCurrencyChoice to be done in helper function --- packages/instant/src/components/order_details.tsx | 46 ++++++++++------------- 1 file changed, 19 insertions(+), 27 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 7e33e06ac..298bd86c9 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -15,20 +15,6 @@ import { Container } from './ui/container'; import { Flex } from './ui/flex'; import { Text, TextProps } from './ui/text'; -interface BaseCurryChoiceProps { - currencyName: string; - onClick: () => void; - isSelected: boolean; -} -const BaseCurrencyChoice: React.StatelessComponent = props => { - const textStyle: TextProps = { onClick: props.onClick, fontSize: '12px' }; - if (props.isSelected) { - textStyle.fontColor = ColorOption.primaryColor; - textStyle.fontWeight = 700; - } - return {props.currencyName}; -}; - export interface OrderDetailsProps { buyQuoteInfo?: BuyQuoteInfo; selectedAssetUnitAmount?: BigNumber; @@ -41,7 +27,7 @@ export interface OrderDetailsProps { } export class OrderDetails extends React.Component { public render(): React.ReactNode { - const { baseCurrency, buyQuoteInfo } = this.props; + const { buyQuoteInfo } = this.props; return ( @@ -82,7 +68,7 @@ export class OrderDetails extends React.Component { } private _displayAmountOrPlaceholder(weiAmount?: BigNumber): React.ReactNode { - const { baseCurrency, ethUsdPrice, isLoading } = this.props; + const { baseCurrency, isLoading } = this.props; if (_.isUndefined(weiAmount)) { return ( @@ -105,7 +91,7 @@ export class OrderDetails extends React.Component { } private _assetLabelText(): string { - const { assetName, baseCurrency, ethUsdPrice } = this.props; + const { assetName, baseCurrency } = this.props; const numTokens = this.props.selectedAssetUnitAmount; // Display as 0 if we have a selected asset @@ -142,6 +128,20 @@ export class OrderDetails extends React.Component { : 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; + } + + return {choice}; + } + private _renderHeader(): React.ReactNode { return ( @@ -156,19 +156,11 @@ export class OrderDetails extends React.Component { - + {this._baseCurrencyChoice(BaseCurrency.ETH)} / - + {this._baseCurrencyChoice(BaseCurrency.USD)} ); -- cgit v1.2.3 From 99941972a3965f27b3607a382a476b79722151fc Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:29:23 -0800 Subject: Small clean up --- packages/instant/src/components/order_details.tsx | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 298bd86c9..cbe36be64 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -34,7 +34,7 @@ export class OrderDetails extends React.Component { {this._renderHeader()} { } } - private _assetLabelText(): string { + private _assetAmountLabel(): string { const { assetName, baseCurrency } = this.props; const numTokens = this.props.selectedAssetUnitAmount; @@ -99,21 +99,17 @@ export class OrderDetails extends React.Component { assetName && assetName !== DEFAULT_UNKOWN_ASSET_NAME && _.isUndefined(numTokens) ? new BigNumber(0) : numTokens; - if (!_.isUndefined(displayNumTokens)) { let numTokensWithSymbol = displayNumTokens.toString(); - if (assetName) { numTokensWithSymbol += ` ${assetName}`; } - const pricePerTokenWei = this._pricePerTokenWei(); if (pricePerTokenWei) { numTokensWithSymbol += ` @ ${this._displayAmount(baseCurrency, pricePerTokenWei)}`; } return numTokensWithSymbol; } - return 'Token Amount'; } @@ -138,7 +134,6 @@ export class OrderDetails extends React.Component { textStyle.fontColor = ColorOption.primaryColor; textStyle.fontWeight = 700; } - return {choice}; } @@ -154,7 +149,6 @@ export class OrderDetails extends React.Component { > Order Details - {this._baseCurrencyChoice(BaseCurrency.ETH)} @@ -179,28 +173,21 @@ export class OrderDetailsRow extends React.Component { return ( - {this._renderLabel()} + + {this.props.labelText} + {this._renderValues()} ); } - private _renderLabel(): React.ReactNode { - return ( - - {this.props.labelText} - - ); - } - private _renderValues(): React.ReactNode { const secondaryValueNode: React.ReactNode = this.props.secondaryValue && ( ({this.props.secondaryValue}) ); - return ( {secondaryValueNode} -- cgit v1.2.3 From aa8dfa88e113ca7a98887ed77dc8eb3ad39e5f41 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:37:39 -0800 Subject: Make primary value in total cost bold --- packages/instant/src/components/order_details.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index cbe36be64..537b9ee4e 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -45,6 +45,7 @@ export class OrderDetails extends React.Component { labelText="Total Cost" isLabelBold={true} primaryValue={this._displayAmountOrPlaceholder(buyQuoteInfo && buyQuoteInfo.totalEthAmount)} + isPrimaryValueBold={true} secondaryValue={this._totalCostSecondaryValue()} /> -- cgit v1.2.3 From fb99b5ce9d3639e1584214828216879f99dfa020 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:51:15 -0800 Subject: Show error when fetching usd prices --- packages/instant/src/components/order_details.tsx | 36 ++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 537b9ee4e..ea975a5f7 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -27,12 +27,19 @@ export interface OrderDetailsProps { } export class OrderDetails extends React.Component { public render(): React.ReactNode { - const { buyQuoteInfo } = this.props; - + const showUsdError = this.props.baseCurrency === BaseCurrency.USD && this._hadErrorFetchingUsdPrice(); return ( {this._renderHeader()} + {showUsdError ? this._renderErrorFetchingUsdPrice() : this._renderRows()} + + ); + } + private _renderRows(): React.ReactNode { + const { buyQuoteInfo } = this.props; + return ( + { isPrimaryValueBold={true} secondaryValue={this._totalCostSecondaryValue()} /> - + ); } + private _renderErrorFetchingUsdPrice(): React.ReactNode { + return ( + + There was an error fetching the USD price. + + Click here + + {' to view ETH prices'} + + ); + } + + private _hadErrorFetchingUsdPrice(): boolean { + return this.props.ethUsdPrice === BIG_NUMBER_ZERO; + } + private _totalCostSecondaryValue(): React.ReactNode { const secondaryCurrency = this.props.baseCurrency === BaseCurrency.USD ? BaseCurrency.ETH : BaseCurrency.USD; @@ -92,7 +119,8 @@ export class OrderDetails extends React.Component { } private _assetAmountLabel(): string { - const { assetName, baseCurrency } = this.props; + const { assetName, baseCurrency, ethUsdPrice } = this.props; + const numTokens = this.props.selectedAssetUnitAmount; // Display as 0 if we have a selected asset -- cgit v1.2.3 From 0690e68a833e0ca75d31614621aa0278009cc10c Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:54:06 -0800 Subject: Change base currency to ETH if we can't get USD price --- packages/instant/src/redux/async_data.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts index c67b222d1..8ef95add4 100644 --- a/packages/instant/src/redux/async_data.ts +++ b/packages/instant/src/redux/async_data.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { Dispatch } from 'redux'; import { BIG_NUMBER_ZERO } from '../constants'; -import { AccountState, ERC20Asset, OrderProcessState, ProviderState, QuoteFetchOrigin } from '../types'; +import { AccountState, BaseCurrency, ERC20Asset, OrderProcessState, ProviderState, QuoteFetchOrigin } from '../types'; import { analytics } from '../util/analytics'; import { assetUtils } from '../util/asset'; import { buyQuoteUpdater } from '../util/buy_quote_updater'; @@ -24,6 +24,7 @@ export const asyncData = { const errorMessage = 'Error fetching ETH/USD price'; errorFlasher.flashNewErrorMessage(dispatch, errorMessage); dispatch(actions.updateEthUsdPrice(BIG_NUMBER_ZERO)); + dispatch(actions.updateBaseCurrency(BaseCurrency.ETH)); errorReporter.report(e); } }, -- cgit v1.2.3 From 68faaaba5f4278d7a1bd5d02f97921fa3c4238f4 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 10:04:23 -0800 Subject: Analytics events for ETH/USD toggle and failure to fetch eth usd price --- packages/instant/src/components/zero_ex_instant_provider.tsx | 1 + packages/instant/src/redux/analytics_middleware.ts | 3 +++ packages/instant/src/redux/async_data.ts | 1 + packages/instant/src/util/analytics.ts | 9 +++++++++ 4 files changed, 14 insertions(+) (limited to 'packages') 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 next => middlewareAction analytics.trackInstallWalletModalClosed(); } break; + case ActionTypes.UPDATE_BASE_CURRENCY: + analytics.trackBaseCurrencyChanged(curState.baseCurrency); + analytics.addEventProperties({ baseCurrency: curState.baseCurrency }); } return nextAction; diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts index 8ef95add4..3961d8821 100644 --- a/packages/instant/src/redux/async_data.ts +++ b/packages/instant/src/redux/async_data.ts @@ -26,6 +26,7 @@ export const asyncData = { dispatch(actions.updateEthUsdPrice(BIG_NUMBER_ZERO)); dispatch(actions.updateBaseCurrency(BaseCurrency.ETH)); errorReporter.report(e); + analytics.trackUsdPriceFailed(); } }, fetchAvailableAssetDatasAndDispatchToStore: async (state: State, dispatch: Dispatch) => { diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index e6128f857..6c63907dc 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -6,6 +6,7 @@ import { GIT_SHA, HEAP_ENABLED, INSTANT_DISCHARGE_TARGET, NODE_ENV, NPM_PACKAGE_ import { AffiliateInfo, Asset, + BaseCurrency, Network, OrderProcessState, OrderSource, @@ -37,6 +38,7 @@ enum EventNames { ACCOUNT_UNLOCK_REQUESTED = 'Account - Unlock Requested', ACCOUNT_UNLOCK_DENIED = 'Account - Unlock Denied', ACCOUNT_ADDRESS_CHANGED = 'Account - Address Changed', + BASE_CURRENCY_CHANGED = 'Base Currency - Changed', PAYMENT_METHOD_DROPDOWN_OPENED = 'Payment Method - Dropdown Opened', PAYMENT_METHOD_OPENED_ETHERSCAN = 'Payment Method - Opened Etherscan', PAYMENT_METHOD_COPIED_ADDRESS = 'Payment Method - Copied Address', @@ -47,6 +49,7 @@ enum EventNames { BUY_TX_SUBMITTED = 'Buy - Tx Submitted', BUY_TX_SUCCEEDED = 'Buy - Tx Succeeded', BUY_TX_FAILED = 'Buy - Tx Failed', + USD_PRICE_FETCH_FAILED = 'USD Price - Fetch Failed', INSTALL_WALLET_CLICKED = 'Install Wallet - Clicked', INSTALL_WALLET_MODAL_OPENED = 'Install Wallet - Modal - Opened', INSTALL_WALLET_MODAL_CLICKED_EXPLANATION = 'Install Wallet - Modal - Clicked Explanation', @@ -118,6 +121,7 @@ export interface AnalyticsEventOptions { selectedAssetSymbol?: string; selectedAssetData?: string; selectedAssetDecimals?: number; + baseCurrency?: string; } export enum TokenSelectorClosedVia { ClickedX = 'Clicked X', @@ -141,6 +145,7 @@ export const analytics = { window: Window, selectedAsset?: Asset, affiliateInfo?: AffiliateInfo, + baseCurrency?: BaseCurrency, ): AnalyticsEventOptions => { const affiliateAddress = affiliateInfo ? affiliateInfo.feeRecipient : 'none'; const affiliateFeePercent = affiliateInfo ? parseFloat(affiliateInfo.feePercentage.toFixed(4)) : 0; @@ -159,6 +164,7 @@ export const analytics = { selectedAssetName: selectedAsset ? selectedAsset.metaData.name : 'none', selectedAssetData: selectedAsset ? selectedAsset.assetData : 'none', instantEnvironment: INSTANT_DISCHARGE_TARGET || `Local ${NODE_ENV}`, + baseCurrency, }; return eventOptions; }, @@ -170,6 +176,8 @@ export const analytics = { trackAccountUnlockDenied: trackingEventFnWithoutPayload(EventNames.ACCOUNT_UNLOCK_DENIED), trackAccountAddressChanged: (address: string) => trackingEventFnWithPayload(EventNames.ACCOUNT_ADDRESS_CHANGED)({ address }), + trackBaseCurrencyChanged: (currencyChangedTo: BaseCurrency) => + trackingEventFnWithPayload(EventNames.BASE_CURRENCY_CHANGED)({ currencyChangedTo }), trackPaymentMethodDropdownOpened: trackingEventFnWithoutPayload(EventNames.PAYMENT_METHOD_DROPDOWN_OPENED), trackPaymentMethodOpenedEtherscan: trackingEventFnWithoutPayload(EventNames.PAYMENT_METHOD_OPENED_ETHERSCAN), trackPaymentMethodCopiedAddress: trackingEventFnWithoutPayload(EventNames.PAYMENT_METHOD_COPIED_ADDRESS), @@ -230,4 +238,5 @@ export const analytics = { fetchOrigin, }); }, + trackUsdPriceFailed: trackingEventFnWithoutPayload(EventNames.USD_PRICE_FETCH_FAILED), }; -- cgit v1.2.3 From 756dc1e95ebbb8ef73ff8f712062997ea3d8c45e Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 10:07:45 -0800 Subject: Linting --- packages/instant/src/components/order_details.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index ea975a5f7..c069a8bcc 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -27,11 +27,11 @@ export interface OrderDetailsProps { } export class OrderDetails extends React.Component { public render(): React.ReactNode { - const showUsdError = this.props.baseCurrency === BaseCurrency.USD && this._hadErrorFetchingUsdPrice(); + const shouldShowUsdError = this.props.baseCurrency === BaseCurrency.USD && this._hadErrorFetchingUsdPrice(); return ( {this._renderHeader()} - {showUsdError ? this._renderErrorFetchingUsdPrice() : this._renderRows()} + {shouldShowUsdError ? this._renderErrorFetchingUsdPrice() : this._renderRows()} ); } @@ -119,8 +119,7 @@ export class OrderDetails extends React.Component { } private _assetAmountLabel(): string { - const { assetName, baseCurrency, ethUsdPrice } = this.props; - + const { assetName, baseCurrency } = this.props; const numTokens = this.props.selectedAssetUnitAmount; // Display as 0 if we have a selected asset -- cgit v1.2.3 From 65579c023670c09f39b4f7a733d7b703888d9d99 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 13:43:08 -0800 Subject: Abstract SectionHeader and make 12px per Chris's comment and figma design --- packages/instant/src/components/order_details.tsx | 21 +++++++++------------ packages/instant/src/components/payment_method.tsx | 11 ++--------- packages/instant/src/components/section_header.tsx | 22 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 21 deletions(-) create mode 100644 packages/instant/src/components/section_header.tsx (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index c069a8bcc..4aa375017 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -10,6 +10,7 @@ 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'; @@ -157,10 +158,12 @@ export class OrderDetails extends React.Component { choice === BaseCurrency.ETH ? this.props.onBaseCurrencySwitchEth : this.props.onBaseCurrencySwitchUsd; const isSelected = this.props.baseCurrency === choice; - const textStyle: TextProps = { onClick, fontSize: '12px ' }; + const textStyle: TextProps = { onClick, fontSize: '12px' }; if (isSelected) { textStyle.fontColor = ColorOption.primaryColor; textStyle.fontWeight = 700; + } else { + textStyle.fontColor = ColorOption.lightGrey; } return {choice}; } @@ -168,19 +171,13 @@ export class OrderDetails extends React.Component { private _renderHeader(): React.ReactNode { return ( - - Order Details - + Order Details {this._baseCurrencyChoice(BaseCurrency.ETH)} - - / + + + / + {this._baseCurrencyChoice(BaseCurrency.USD)} 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 { - - {this._renderTitleText()} - + {this._renderTitleText()} {this._renderTitleLabel()} diff --git a/packages/instant/src/components/section_header.tsx b/packages/instant/src/components/section_header.tsx new file mode 100644 index 000000000..a6be3d8af --- /dev/null +++ b/packages/instant/src/components/section_header.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; + +import { Text } from './ui/text'; + +export interface SectionHeaderProps { + children?: React.ReactNode; +} +export const SectionHeader: React.StatelessComponent<{}> = props => { + return ( + + {props.children} + + ); +}; -- cgit v1.2.3 From 9e5e1f568ba71927cec2255bf779322edb6f7d07 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 14:25:56 -0800 Subject: show as <$0.01 when less than a cent in USD, and also show 1 significant digit if rounded amount is 0 --- packages/instant/src/util/format.ts | 15 ++++++++++++--- packages/instant/test/util/format.test.ts | 10 ++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/util/format.ts b/packages/instant/src/util/format.ts index e9c432b2f..3aaf9a3a7 100644 --- a/packages/instant/src/util/format.ts +++ b/packages/instant/src/util/format.ts @@ -2,7 +2,7 @@ import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; -import { ETH_DECIMALS } from '../constants'; +import { BIG_NUMBER_ZERO, ETH_DECIMALS } from '../constants'; export const format = { ethBaseUnitAmount: ( @@ -25,7 +25,10 @@ export const format = { return defaultText; } const roundedAmount = ethUnitAmount.round(decimalPlaces).toDigits(decimalPlaces); - return `${roundedAmount} ETH`; + // Sometimes for small ETH amounts (i.e. 0.000045) the amount rounded to 4 decimalPlaces is 0 + // If that is the case, show to 1 significant digit + const displayAmount = roundedAmount.eq(BIG_NUMBER_ZERO) ? ethUnitAmount.toPrecision(1) : roundedAmount; + return `${displayAmount} ETH`; }, ethBaseUnitAmountInUsd: ( ethBaseUnitAmount?: BigNumber, @@ -48,7 +51,13 @@ export const format = { if (_.isUndefined(ethUnitAmount) || _.isUndefined(ethUsdPrice)) { return defaultText; } - return `$${ethUnitAmount.mul(ethUsdPrice).toFixed(decimalPlaces)}`; + const rawUsdPrice = ethUnitAmount.mul(ethUsdPrice); + const roundedUsdPrice = rawUsdPrice.toFixed(decimalPlaces); + if (roundedUsdPrice === '0.00' && rawUsdPrice.gt(BIG_NUMBER_ZERO)) { + return '<$0.01'; + } else { + return `$${roundedUsdPrice}`; + } }, ethAddress: (address: string): string => { return `0x${address.slice(2, 7)}…${address.slice(-5)}`; diff --git a/packages/instant/test/util/format.test.ts b/packages/instant/test/util/format.test.ts index fe0a63e6e..b74d6cdaa 100644 --- a/packages/instant/test/util/format.test.ts +++ b/packages/instant/test/util/format.test.ts @@ -41,6 +41,10 @@ describe('format', () => { it('converts BigNumber(5.3014059295032) to the string `5.301 ETH`', () => { expect(format.ethUnitAmount(BIG_NUMBER_IRRATIONAL)).toBe('5.301 ETH'); }); + it('shows 1 significant digit when rounded amount would be 0', () => { + expect(format.ethUnitAmount(new BigNumber(0.00000045))).toBe('0.0000005 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.00000044))).toBe('0.0000004 ETH'); + }); it('returns defaultText param when ethUnitAmount is not defined', () => { const defaultText = 'defaultText'; expect(format.ethUnitAmount(undefined, 4, defaultText)).toBe(defaultText); @@ -86,6 +90,12 @@ describe('format', () => { it('correctly formats 5.3014059295032 ETH to usd according to some price', () => { expect(format.ethUnitAmountInUsd(BIG_NUMBER_IRRATIONAL, BIG_NUMBER_FAKE_ETH_USD_PRICE)).toBe('$13.43'); }); + it('correctly formats amount that is less than 1 cent', () => { + expect(format.ethUnitAmountInUsd(new BigNumber(0.000001), BIG_NUMBER_FAKE_ETH_USD_PRICE)).toBe('<$0.01'); + }); + it('correctly formats exactly 1 cent', () => { + expect(format.ethUnitAmountInUsd(new BigNumber(0.0039), BIG_NUMBER_FAKE_ETH_USD_PRICE)).toBe('$0.01'); + }); it('returns defaultText param when ethUnitAmountInUsd or ethUsdPrice is not defined', () => { const defaultText = 'defaultText'; expect(format.ethUnitAmountInUsd(undefined, undefined, 2, defaultText)).toBe(defaultText); -- cgit v1.2.3 From 2e7d363f2cf76e383bb8723cfb0f2e3c1e4783d8 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 15:46:46 -0800 Subject: Use helper function to check for error --- packages/instant/src/components/order_details.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 4aa375017..6ce19742f 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -85,9 +85,7 @@ export class OrderDetails extends React.Component { const canDisplayCurrency = secondaryCurrency === BaseCurrency.ETH || - (secondaryCurrency === BaseCurrency.USD && - this.props.ethUsdPrice && - this.props.ethUsdPrice.greaterThan(BIG_NUMBER_ZERO)); + (secondaryCurrency === BaseCurrency.USD && this.props.ethUsdPrice && !this._hadErrorFetchingUsdPrice); if (this.props.buyQuoteInfo && canDisplayCurrency) { return this._displayAmount(secondaryCurrency, this.props.buyQuoteInfo.totalEthAmount); -- cgit v1.2.3 From 167a3fbc112120b6a583f3ec3c8fdd70d39df078 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 15:57:28 -0800 Subject: Use BN equals and call function --- packages/instant/src/components/order_details.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 6ce19742f..96bdb7150 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -77,7 +77,7 @@ export class OrderDetails extends React.Component { } private _hadErrorFetchingUsdPrice(): boolean { - return this.props.ethUsdPrice === BIG_NUMBER_ZERO; + return this.props.ethUsdPrice ? this.props.ethUsdPrice.equals(BIG_NUMBER_ZERO) : false; } private _totalCostSecondaryValue(): React.ReactNode { @@ -85,7 +85,7 @@ export class OrderDetails extends React.Component { const canDisplayCurrency = secondaryCurrency === BaseCurrency.ETH || - (secondaryCurrency === BaseCurrency.USD && this.props.ethUsdPrice && !this._hadErrorFetchingUsdPrice); + (secondaryCurrency === BaseCurrency.USD && this.props.ethUsdPrice && !this._hadErrorFetchingUsdPrice()); if (this.props.buyQuoteInfo && canDisplayCurrency) { return this._displayAmount(secondaryCurrency, this.props.buyQuoteInfo.totalEthAmount); -- cgit v1.2.3 From 8d54772389b28dec021aa81423ac84795e132581 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 17:34:14 -0800 Subject: show < 0.00001 ETH when amount gets really small --- packages/instant/src/util/format.ts | 21 ++++++++++++++++----- packages/instant/test/util/format.test.ts | 12 ++++++++++-- 2 files changed, 26 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/util/format.ts b/packages/instant/src/util/format.ts index 3aaf9a3a7..4adb63e21 100644 --- a/packages/instant/src/util/format.ts +++ b/packages/instant/src/util/format.ts @@ -20,14 +20,24 @@ export const format = { ethUnitAmount?: BigNumber, decimalPlaces: number = 4, defaultText: React.ReactNode = '0 ETH', + minUnitAmountToDisplay: BigNumber = new BigNumber('0.00001'), ): React.ReactNode => { if (_.isUndefined(ethUnitAmount)) { return defaultText; } - const roundedAmount = ethUnitAmount.round(decimalPlaces).toDigits(decimalPlaces); - // Sometimes for small ETH amounts (i.e. 0.000045) the amount rounded to 4 decimalPlaces is 0 - // If that is the case, show to 1 significant digit - const displayAmount = roundedAmount.eq(BIG_NUMBER_ZERO) ? ethUnitAmount.toPrecision(1) : roundedAmount; + let roundedAmount = ethUnitAmount.round(decimalPlaces).toDigits(decimalPlaces); + + if (roundedAmount.eq(BIG_NUMBER_ZERO) && ethUnitAmount.greaterThan(BIG_NUMBER_ZERO)) { + // Sometimes for small ETH amounts (i.e. 0.000045) the amount rounded to 4 decimalPlaces is 0 + // If that is the case, show to 1 significant digit + roundedAmount = new BigNumber(ethUnitAmount.toPrecision(1)); + } + + const displayAmount = + roundedAmount.greaterThan(BIG_NUMBER_ZERO) && roundedAmount.lessThan(minUnitAmountToDisplay) + ? `< ${minUnitAmountToDisplay.toString()}` + : roundedAmount.toString(); + return `${displayAmount} ETH`; }, ethBaseUnitAmountInUsd: ( @@ -35,12 +45,13 @@ export const format = { ethUsdPrice?: BigNumber, decimalPlaces: number = 2, defaultText: React.ReactNode = '$0.00', + minUnitAmountToDisplay: BigNumber = new BigNumber('0.00001'), ): React.ReactNode => { if (_.isUndefined(ethBaseUnitAmount) || _.isUndefined(ethUsdPrice)) { return defaultText; } const ethUnitAmount = Web3Wrapper.toUnitAmount(ethBaseUnitAmount, ETH_DECIMALS); - return format.ethUnitAmountInUsd(ethUnitAmount, ethUsdPrice, decimalPlaces); + return format.ethUnitAmountInUsd(ethUnitAmount, ethUsdPrice, decimalPlaces, minUnitAmountToDisplay); }, ethUnitAmountInUsd: ( ethUnitAmount?: BigNumber, diff --git a/packages/instant/test/util/format.test.ts b/packages/instant/test/util/format.test.ts index b74d6cdaa..38bf356ec 100644 --- a/packages/instant/test/util/format.test.ts +++ b/packages/instant/test/util/format.test.ts @@ -42,8 +42,16 @@ describe('format', () => { expect(format.ethUnitAmount(BIG_NUMBER_IRRATIONAL)).toBe('5.301 ETH'); }); it('shows 1 significant digit when rounded amount would be 0', () => { - expect(format.ethUnitAmount(new BigNumber(0.00000045))).toBe('0.0000005 ETH'); - expect(format.ethUnitAmount(new BigNumber(0.00000044))).toBe('0.0000004 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.00003))).toBe('0.00003 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.000034))).toBe('0.00003 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.000035))).toBe('0.00004 ETH'); + }); + it('shows < 0.00001 when hits threshold', () => { + expect(format.ethUnitAmount(new BigNumber(0.000011))).toBe('0.00001 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.00001))).toBe('0.00001 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.000009))).toBe('< 0.00001 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.0000000009))).toBe('< 0.00001 ETH'); + expect(format.ethUnitAmount(new BigNumber(0))).toBe('0 ETH'); }); it('returns defaultText param when ethUnitAmount is not defined', () => { const defaultText = 'defaultText'; -- cgit v1.2.3 From 193cd67d6bfffc35feccd46d5c1b5f23320dc8ce Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 17:43:27 -0800 Subject: A little bit of scaling logic for not cutting off text --- packages/instant/src/components/instant_heading.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 816cc5c33..99e531574 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 { } private readonly _renderEthAmount = (): React.ReactNode => { + const ethAmount = format.ethBaseUnitAmount( + this.props.totalEthBaseUnitAmount, + 4, + , + ); + + const fontSize = typeof ethAmount === 'string' && ethAmount.length >= 13 ? '14px' : '16px'; return ( - {format.ethBaseUnitAmount( - this.props.totalEthBaseUnitAmount, - 4, - , - )} + {ethAmount} ); }; -- cgit v1.2.3 From 9a1d2c055e176414ee1e7661d5acc764cc9a745d Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 13 Dec 2018 08:31:40 -0800 Subject: Fix SectionHeaderProps --- packages/instant/src/components/section_header.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/section_header.tsx b/packages/instant/src/components/section_header.tsx index a6be3d8af..d0974ebdc 100644 --- a/packages/instant/src/components/section_header.tsx +++ b/packages/instant/src/components/section_header.tsx @@ -4,10 +4,8 @@ import { ColorOption } from '../style/theme'; import { Text } from './ui/text'; -export interface SectionHeaderProps { - children?: React.ReactNode; -} -export const SectionHeader: React.StatelessComponent<{}> = props => { +export interface SectionHeaderProps {} +export const SectionHeader: React.StatelessComponent = props => { return ( Date: Thu, 13 Dec 2018 13:31:30 -0800 Subject: typeof -> isString --- packages/instant/src/components/instant_heading.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 99e531574..5b1f9592d 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -119,7 +119,7 @@ export class InstantHeading extends React.Component { , ); - const fontSize = typeof ethAmount === 'string' && ethAmount.length >= 13 ? '14px' : '16px'; + const fontSize = _.isString(ethAmount) && ethAmount.length >= 13 ? '14px' : '16px'; return ( Date: Thu, 13 Dec 2018 14:27:27 -0800 Subject: Show @ price in light grey --- packages/instant/src/components/order_details.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 96bdb7150..33a115044 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -117,7 +117,7 @@ export class OrderDetails extends React.Component { } } - private _assetAmountLabel(): string { + private _assetAmountLabel(): React.ReactNode { const { assetName, baseCurrency } = this.props; const numTokens = this.props.selectedAssetUnitAmount; @@ -127,13 +127,14 @@ export class OrderDetails extends React.Component { ? new BigNumber(0) : numTokens; if (!_.isUndefined(displayNumTokens)) { - let numTokensWithSymbol = displayNumTokens.toString(); + let numTokensWithSymbol: React.ReactNode = displayNumTokens.toString(); if (assetName) { numTokensWithSymbol += ` ${assetName}`; } const pricePerTokenWei = this._pricePerTokenWei(); if (pricePerTokenWei) { - numTokensWithSymbol += ` @ ${this._displayAmount(baseCurrency, pricePerTokenWei)}`; + const atPriceDisplay = @ {this._displayAmount(baseCurrency, pricePerTokenWei)}; + numTokensWithSymbol = {numTokensWithSymbol} {atPriceDisplay} } return numTokensWithSymbol; } @@ -185,7 +186,7 @@ export class OrderDetails extends React.Component { } export interface OrderDetailsRowProps { - labelText: string; + labelText: React.ReactNode; isLabelBold?: boolean; isPrimaryValueBold?: boolean; primaryValue: React.ReactNode; -- cgit v1.2.3 From 67422db4bd0b194296a1a584af7ead089e281715 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 14 Dec 2018 15:58:23 -0800 Subject: fix semicolon and apply prettier --- packages/instant/src/components/order_details.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 33a115044..9c10ef9e6 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -133,8 +133,16 @@ export class OrderDetails extends React.Component { } const pricePerTokenWei = this._pricePerTokenWei(); if (pricePerTokenWei) { - const atPriceDisplay = @ {this._displayAmount(baseCurrency, pricePerTokenWei)}; - numTokensWithSymbol = {numTokensWithSymbol} {atPriceDisplay} + const atPriceDisplay = ( + + @ {this._displayAmount(baseCurrency, pricePerTokenWei)} + + ); + numTokensWithSymbol = ( + + {numTokensWithSymbol} {atPriceDisplay} + + ); } return numTokensWithSymbol; } -- cgit v1.2.3 From 28713bdb3859e24b78e784722813b03e95788e1f Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 17 Dec 2018 11:36:03 -0800 Subject: Pull approval events for ZRX and DAI (#1430) --- packages/pipeline/src/scripts/pull_erc20_events.ts | 68 ++++++++++++++++------ 1 file changed, 49 insertions(+), 19 deletions(-) (limited to 'packages') diff --git a/packages/pipeline/src/scripts/pull_erc20_events.ts b/packages/pipeline/src/scripts/pull_erc20_events.ts index 0ad12c97a..bd520c610 100644 --- a/packages/pipeline/src/scripts/pull_erc20_events.ts +++ b/packages/pipeline/src/scripts/pull_erc20_events.ts @@ -1,10 +1,10 @@ -// tslint:disable:no-console import { getContractAddressesForNetworkOrThrow } from '@0x/contract-addresses'; import { web3Factory } from '@0x/dev-utils'; import { Web3ProviderEngine } from '@0x/subproviders'; +import { logUtils } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import 'reflect-metadata'; -import { Connection, ConnectionOptions, createConnection, Repository } from 'typeorm'; +import { Connection, ConnectionOptions, createConnection } from 'typeorm'; import { ERC20EventsSource } from '../data_sources/contract-wrappers/erc20_events'; import { ERC20ApprovalEvent } from '../entities'; @@ -16,33 +16,63 @@ const NETWORK_ID = 1; const START_BLOCK_OFFSET = 100; // Number of blocks before the last known block to consider when updating fill events. const BATCH_SAVE_SIZE = 1000; // Number of events to save at once. const BLOCK_FINALITY_THRESHOLD = 10; // When to consider blocks as final. Used to compute default endBlock. -const WETH_START_BLOCK = 4719568; // Block number when the WETH contract was deployed. let connection: Connection; +interface Token { + // name is used for logging only. + name: string; + address: string; + defaultStartBlock: number; +} + +const tokensToGetApprovalEvents: Token[] = [ + { + name: 'WETH', + address: getContractAddressesForNetworkOrThrow(NETWORK_ID).etherToken, + defaultStartBlock: 4719568, // Block when the WETH contract was deployed. + }, + { + name: 'ZRX', + address: getContractAddressesForNetworkOrThrow(NETWORK_ID).zrxToken, + defaultStartBlock: 4145415, // Block when the ZRX contract was deployed. + }, + { + name: 'DAI', + address: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + defaultStartBlock: 4752008, // Block when the DAI contract was deployed. + }, +]; + (async () => { connection = await createConnection(ormConfig as ConnectionOptions); const provider = web3Factory.getRpcProvider({ rpcUrl: INFURA_ROOT_URL, }); const endBlock = await calculateEndBlockAsync(provider); - await getAndSaveWETHApprovalEventsAsync(provider, endBlock); + for (const token of tokensToGetApprovalEvents) { + await getAndSaveApprovalEventsAsync(provider, token, endBlock); + } process.exit(0); })().catch(handleError); -async function getAndSaveWETHApprovalEventsAsync(provider: Web3ProviderEngine, endBlock: number): Promise { - console.log('Checking existing approval events...'); +async function getAndSaveApprovalEventsAsync( + provider: Web3ProviderEngine, + token: Token, + endBlock: number, +): Promise { + logUtils.log(`Getting approval events for ${token.name}...`); + logUtils.log('Checking existing approval events...'); const repository = connection.getRepository(ERC20ApprovalEvent); - const startBlock = (await getStartBlockAsync(repository)) || WETH_START_BLOCK; + const startBlock = (await getStartBlockAsync(token)) || token.defaultStartBlock; - console.log(`Getting WETH approval events starting at ${startBlock}...`); - const wethTokenAddress = getContractAddressesForNetworkOrThrow(NETWORK_ID).etherToken; - const eventsSource = new ERC20EventsSource(provider, NETWORK_ID, wethTokenAddress); + logUtils.log(`Getting approval events starting at ${startBlock}...`); + const eventsSource = new ERC20EventsSource(provider, NETWORK_ID, token.address); const eventLogs = await eventsSource.getApprovalEventsAsync(startBlock, endBlock); - console.log(`Parsing ${eventLogs.length} WETH approval events...`); + logUtils.log(`Parsing ${eventLogs.length} approval events...`); const events = parseERC20ApprovalEvents(eventLogs); - console.log(`Retrieved and parsed ${events.length} total WETH approval events.`); + logUtils.log(`Retrieved and parsed ${events.length} total approval events.`); await repository.save(events, { chunk: Math.ceil(events.length / BATCH_SAVE_SIZE) }); } @@ -52,15 +82,15 @@ async function calculateEndBlockAsync(provider: Web3ProviderEngine): Promise): Promise { - const fillEventCount = await repository.count(); - if (fillEventCount === 0) { - console.log(`No existing approval events found.`); - return null; - } +async function getStartBlockAsync(token: Token): Promise { const queryResult = await connection.query( - `SELECT block_number FROM raw.erc20_approval_events ORDER BY block_number DESC LIMIT 1`, + `SELECT block_number FROM raw.erc20_approval_events WHERE token_address = $1 ORDER BY block_number DESC LIMIT 1`, + [token.address], ); + if (queryResult.length === 0) { + logUtils.log(`No existing approval events found for ${token.name}.`); + return null; + } const lastKnownBlock = queryResult[0].block_number; return lastKnownBlock - START_BLOCK_OFFSET; } -- cgit v1.2.3 From 44d9cc53b87c5a19586c05030292b5b21351dc74 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 17 Dec 2018 17:07:22 -0800 Subject: Fix bug in pull_missing_blocks with incorrect start block (#1438) --- packages/pipeline/src/scripts/pull_missing_blocks.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/pipeline/src/scripts/pull_missing_blocks.ts b/packages/pipeline/src/scripts/pull_missing_blocks.ts index a5203824c..bb5385126 100644 --- a/packages/pipeline/src/scripts/pull_missing_blocks.ts +++ b/packages/pipeline/src/scripts/pull_missing_blocks.ts @@ -9,7 +9,7 @@ import { Web3Source } from '../data_sources/web3'; import { Block } from '../entities'; import * as ormConfig from '../ormconfig'; import { parseBlock } from '../parsers/web3'; -import { EXCHANGE_START_BLOCK, handleError, INFURA_ROOT_URL } from '../utils'; +import { handleError, INFURA_ROOT_URL } from '../utils'; // Number of blocks to save at once. const BATCH_SAVE_SIZE = 1000; @@ -37,22 +37,19 @@ interface MissingBlocksResponse { async function getAllMissingBlocksAsync(web3Source: Web3Source): Promise { const blocksRepository = connection.getRepository(Block); - let fromBlock = EXCHANGE_START_BLOCK; while (true) { - const blockNumbers = await getMissingBlockNumbersAsync(fromBlock); + const blockNumbers = await getMissingBlockNumbersAsync(); if (blockNumbers.length === 0) { // There are no more missing blocks. We're done. break; } await getAndSaveBlocksAsync(web3Source, blocksRepository, blockNumbers); - fromBlock = Math.max(...blockNumbers) + 1; } const totalBlocks = await blocksRepository.count(); console.log(`Done saving blocks. There are now ${totalBlocks} total blocks.`); } -async function getMissingBlockNumbersAsync(fromBlock: number): Promise { - console.log(`Checking for missing blocks starting at ${fromBlock}...`); +async function getMissingBlockNumbersAsync(): Promise { // Note(albrow): The easiest way to get all the blocks we need is to // consider all the events tables together in a single query. If this query // gets too slow, we should consider re-architecting so that we can work on @@ -66,13 +63,12 @@ async function getMissingBlockNumbersAsync(fromBlock: number): Promise ) SELECT DISTINCT(block_number) FROM all_events WHERE block_number NOT IN (SELECT number FROM raw.blocks) - AND block_number >= $1 - ORDER BY block_number ASC LIMIT $2`, - [fromBlock, MAX_BLOCKS_PER_QUERY], + ORDER BY block_number ASC LIMIT $1`, + [MAX_BLOCKS_PER_QUERY], )) as MissingBlocksResponse[]; const blockNumberStrings = R.pluck('block_number', response); const blockNumbers = R.map(parseInt, blockNumberStrings); - console.log(`Found ${blockNumbers.length} missing blocks in the given range.`); + console.log(`Found ${blockNumbers.length} missing blocks.`); return blockNumbers; } -- cgit v1.2.3 From 18e55830b5816c342d8e9d9f64ae035f9284ea80 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 18 Dec 2018 15:31:58 +0000 Subject: Fix version picker so it doesn't overflow onto two lines --- .../ts/components/documentation/sidebar_header.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/website/ts/components/documentation/sidebar_header.tsx b/packages/website/ts/components/documentation/sidebar_header.tsx index 9ced52c74..0ab24ab5e 100644 --- a/packages/website/ts/components/documentation/sidebar_header.tsx +++ b/packages/website/ts/components/documentation/sidebar_header.tsx @@ -24,7 +24,7 @@ export const SidebarHeader: React.StatelessComponent = ({ return ( - + = ({ {!_.isUndefined(docsVersion) && !_.isUndefined(availableDocVersions) && !_.isUndefined(onVersionSelected) && ( -
- +
+ + +
)} -- cgit v1.2.3 From 67df5a433d68a2af1a3a03a8bf431629a534dc97 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 18 Dec 2018 15:32:11 +0000 Subject: Fix OrderWatcher title to fix sidebar top --- packages/website/ts/containers/order_watcher_documentation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/website/ts/containers/order_watcher_documentation.ts b/packages/website/ts/containers/order_watcher_documentation.ts index ac92e6a22..683e1fe9f 100644 --- a/packages/website/ts/containers/order_watcher_documentation.ts +++ b/packages/website/ts/containers/order_watcher_documentation.ts @@ -24,7 +24,7 @@ const docsInfoConfig: DocsInfoConfig = { id: DocPackages.OrderWatcher, packageName: '@0x/order-watcher', type: SupportedDocJson.TypeDoc, - displayName: 'OrderWatcher', + displayName: 'Order Watcher', packageUrl: 'https://github.com/0xProject/0x-monorepo', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], -- cgit v1.2.3