diff options
Diffstat (limited to 'packages/instant/src')
36 files changed, 201 insertions, 100 deletions
diff --git a/packages/instant/src/components/amount_placeholder.tsx b/packages/instant/src/components/amount_placeholder.tsx index 29ce8fafb..290e34a07 100644 --- a/packages/instant/src/components/amount_placeholder.tsx +++ b/packages/instant/src/components/amount_placeholder.tsx @@ -30,3 +30,5 @@ export const AmountPlaceholder: React.StatelessComponent<AmountPlaceholderProps> return <PlainPlaceholder color={props.color} />; } }; + +AmountPlaceholder.displayName = 'AmountPlaceholder'; diff --git a/packages/instant/src/components/animations/slide_animation.tsx b/packages/instant/src/components/animations/slide_animation.tsx index 5992bcba7..6ac47e9a6 100644 --- a/packages/instant/src/components/animations/slide_animation.tsx +++ b/packages/instant/src/components/animations/slide_animation.tsx @@ -11,6 +11,7 @@ export interface SlideAnimationProps { slideOutSettings: OptionallyScreenSpecific<PositionAnimationSettings>; zIndex?: OptionallyScreenSpecific<number>; height?: string; + onAnimationEnd?: () => void; } export const SlideAnimation: React.StatelessComponent<SlideAnimationProps> = props => { @@ -19,8 +20,15 @@ export const SlideAnimation: React.StatelessComponent<SlideAnimationProps> = pro } const positionSettings = props.animationState === 'slidIn' ? props.slideInSettings : props.slideOutSettings; return ( - <PositionAnimation height={props.height} positionSettings={positionSettings} zIndex={props.zIndex}> + <PositionAnimation + onAnimationEnd={props.onAnimationEnd} + height={props.height} + positionSettings={positionSettings} + zIndex={props.zIndex} + > {props.children} </PositionAnimation> ); }; + +SlideAnimation.displayName = 'SlideAnimation'; diff --git a/packages/instant/src/components/buy_button.tsx b/packages/instant/src/components/buy_button.tsx index 1489b94d4..551e857a5 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'; @@ -31,7 +32,7 @@ export interface BuyButtonProps { onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void; } -export class BuyButton extends React.Component<BuyButtonProps> { +export class BuyButton extends React.PureComponent<BuyButtonProps> { public static defaultProps = { onClick: util.boundNoop, onBuySuccess: util.boundNoop, @@ -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/buy_order_progress.tsx b/packages/instant/src/components/buy_order_progress.tsx index a19f5a4d0..11ac5d5e0 100644 --- a/packages/instant/src/components/buy_order_progress.tsx +++ b/packages/instant/src/components/buy_order_progress.tsx @@ -31,3 +31,5 @@ export const BuyOrderProgress: React.StatelessComponent<BuyOrderProgressProps> = } return null; }; + +BuyOrderProgress.displayName = 'BuyOrderProgress'; diff --git a/packages/instant/src/components/buy_order_state_buttons.tsx b/packages/instant/src/components/buy_order_state_buttons.tsx index 833818900..1214559d1 100644 --- a/packages/instant/src/components/buy_order_state_buttons.tsx +++ b/packages/instant/src/components/buy_order_state_buttons.tsx @@ -71,3 +71,5 @@ export const BuyOrderStateButtons: React.StatelessComponent<BuyOrderStateButtonP /> ); }; + +BuyOrderStateButtons.displayName = 'BuyOrderStateButtons'; diff --git a/packages/instant/src/components/erc20_asset_amount_input.tsx b/packages/instant/src/components/erc20_asset_amount_input.tsx index 4da82eb73..0418f9165 100644 --- a/packages/instant/src/components/erc20_asset_amount_input.tsx +++ b/packages/instant/src/components/erc20_asset_amount_input.tsx @@ -31,7 +31,7 @@ export interface ERC20AssetAmountInputState { currentFontSizePx: number; } -export class ERC20AssetAmountInput extends React.Component<ERC20AssetAmountInputProps, ERC20AssetAmountInputState> { +export class ERC20AssetAmountInput extends React.PureComponent<ERC20AssetAmountInputProps, ERC20AssetAmountInputState> { public static defaultProps = { onChange: util.boundNoop, isDisabled: false, diff --git a/packages/instant/src/components/erc20_token_selector.tsx b/packages/instant/src/components/erc20_token_selector.tsx index cb8a8c797..a26fb5cf5 100644 --- a/packages/instant/src/components/erc20_token_selector.tsx +++ b/packages/instant/src/components/erc20_token_selector.tsx @@ -21,12 +21,12 @@ export interface ERC20TokenSelectorState { searchQuery: string; } -export class ERC20TokenSelector extends React.Component<ERC20TokenSelectorProps> { +export class ERC20TokenSelector extends React.PureComponent<ERC20TokenSelectorProps> { public state: ERC20TokenSelectorState = { searchQuery: '', }; public render(): React.ReactNode { - const { tokens, onTokenSelect } = this.props; + const { tokens } = this.props; return ( <Container height="100%"> <Container marginBottom="10px"> @@ -42,12 +42,11 @@ export class ERC20TokenSelector extends React.Component<ERC20TokenSelectorProps> tabIndex={-1} /> <Container overflow="scroll" height="calc(100% - 90px)" marginTop="10px"> - {_.map(tokens, token => { - if (!this._isTokenQueryMatch(token)) { - return null; - } - return <TokenSelectorRow key={token.assetData} token={token} onClick={onTokenSelect} />; - })} + <TokenRowFilter + tokens={tokens} + onClick={this._handleTokenClick} + searchQuery={this.state.searchQuery} + /> </Container> </Container> ); @@ -59,8 +58,32 @@ export class ERC20TokenSelector extends React.Component<ERC20TokenSelectorProps> }); analytics.trackTokenSelectorSearched(searchQuery); }; + private readonly _handleTokenClick = (token: ERC20Asset): void => { + this.props.onTokenSelect(token); + }; +} + +interface TokenRowFilterProps { + tokens: ERC20Asset[]; + onClick: (token: ERC20Asset) => void; + searchQuery: string; +} + +class TokenRowFilter extends React.Component<TokenRowFilterProps> { + public render(): React.ReactNode { + return _.map(this.props.tokens, token => { + if (!this._isTokenQueryMatch(token)) { + return null; + } + return <TokenSelectorRow key={token.assetData} token={token} onClick={this.props.onClick} />; + }); + } + public shouldComponentUpdate(nextProps: TokenRowFilterProps): boolean { + const arePropsDeeplyEqual = _.isEqual(nextProps, this.props); + return !arePropsDeeplyEqual; + } private readonly _isTokenQueryMatch = (token: ERC20Asset): boolean => { - const { searchQuery } = this.state; + const { searchQuery } = this.props; const searchQueryLowerCase = searchQuery.toLowerCase().trim(); if (searchQueryLowerCase === '') { return true; @@ -76,7 +99,7 @@ interface TokenSelectorRowProps { onClick: (token: ERC20Asset) => void; } -class TokenSelectorRow extends React.Component<TokenSelectorRowProps> { +class TokenSelectorRow extends React.PureComponent<TokenSelectorRowProps> { public render(): React.ReactNode { const { token } = this.props; const circleColor = token.metaData.primaryColor || 'black'; @@ -131,21 +154,23 @@ const getTokenIcon = (symbol: string): React.StatelessComponent | undefined => { } }; -const TokenSelectorRowIcon: React.StatelessComponent<TokenSelectorRowIconProps> = props => { - const { token } = props; - const iconUrlIfExists = token.metaData.iconUrl; +class TokenSelectorRowIcon extends React.PureComponent<TokenSelectorRowIconProps> { + public render(): React.ReactNode { + const { token } = this.props; + const iconUrlIfExists = token.metaData.iconUrl; - const TokenIcon = getTokenIcon(token.metaData.symbol); - const displaySymbol = assetUtils.bestNameForAsset(token); - if (!_.isUndefined(iconUrlIfExists)) { - return <img src={iconUrlIfExists} />; - } else if (!_.isUndefined(TokenIcon)) { - return <TokenIcon />; - } else { - return ( - <Text fontColor={ColorOption.white} fontSize="8px"> - {displaySymbol} - </Text> - ); + const TokenIcon = getTokenIcon(token.metaData.symbol); + const displaySymbol = assetUtils.bestNameForAsset(token); + if (!_.isUndefined(iconUrlIfExists)) { + return <img src={iconUrlIfExists} />; + } else if (!_.isUndefined(TokenIcon)) { + return <TokenIcon />; + } else { + return ( + <Text fontColor={ColorOption.white} fontSize="8px"> + {displaySymbol} + </Text> + ); + } } -}; +} diff --git a/packages/instant/src/components/install_wallet_panel_content.tsx b/packages/instant/src/components/install_wallet_panel_content.tsx index 481d82da0..1af3dafbb 100644 --- a/packages/instant/src/components/install_wallet_panel_content.tsx +++ b/packages/instant/src/components/install_wallet_panel_content.tsx @@ -18,7 +18,7 @@ import { Button } from './ui/button'; export interface InstallWalletPanelContentProps {} -export class InstallWalletPanelContent extends React.Component<InstallWalletPanelContentProps> { +export class InstallWalletPanelContent extends React.PureComponent<InstallWalletPanelContentProps> { public render(): React.ReactNode { const panelProps = this._getStandardPanelContentProps(); return <StandardPanelContent {...panelProps} />; diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 5b1f9592d..e943f68d7 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -28,7 +28,7 @@ const ICON_WIDTH = 34; const ICON_HEIGHT = 34; const ICON_COLOR = ColorOption.white; -export class InstantHeading extends React.Component<InstantHeadingProps, {}> { +export class InstantHeading extends React.PureComponent<InstantHeadingProps, {}> { public render(): React.ReactNode { const iconOrAmounts = this._renderIcon() || this._renderAmountsSection(); return ( diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 9c10ef9e6..4db20b13e 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -26,7 +26,7 @@ export interface OrderDetailsProps { onBaseCurrencySwitchEth: () => void; onBaseCurrencySwitchUsd: () => void; } -export class OrderDetails extends React.Component<OrderDetailsProps> { +export class OrderDetails extends React.PureComponent<OrderDetailsProps> { public render(): React.ReactNode { const shouldShowUsdError = this.props.baseCurrency === BaseCurrency.USD && this._hadErrorFetchingUsdPrice(); return ( @@ -200,7 +200,7 @@ export interface OrderDetailsRowProps { primaryValue: React.ReactNode; secondaryValue?: React.ReactNode; } -export class OrderDetailsRow extends React.Component<OrderDetailsRowProps, {}> { +export class OrderDetailsRow extends React.PureComponent<OrderDetailsRowProps, {}> { public render(): React.ReactNode { return ( <Container padding="10px 0px" borderTop="1px dashed" borderColor={ColorOption.feintGrey}> diff --git a/packages/instant/src/components/payment_method.tsx b/packages/instant/src/components/payment_method.tsx index abadf4bd6..ada9f7bab 100644 --- a/packages/instant/src/components/payment_method.tsx +++ b/packages/instant/src/components/payment_method.tsx @@ -24,7 +24,7 @@ export interface PaymentMethodProps { onUnlockWalletClick: () => void; } -export class PaymentMethod extends React.Component<PaymentMethodProps> { +export class PaymentMethod extends React.PureComponent<PaymentMethodProps> { public render(): React.ReactNode { return ( <Container width="100%" height="120px" padding="20px 20px 0px 20px"> diff --git a/packages/instant/src/components/payment_method_dropdown.tsx b/packages/instant/src/components/payment_method_dropdown.tsx index 872ac0831..e463e3eae 100644 --- a/packages/instant/src/components/payment_method_dropdown.tsx +++ b/packages/instant/src/components/payment_method_dropdown.tsx @@ -16,7 +16,7 @@ export interface PaymentMethodDropdownProps { network: Network; } -export class PaymentMethodDropdown extends React.Component<PaymentMethodDropdownProps> { +export class PaymentMethodDropdown extends React.PureComponent<PaymentMethodDropdownProps> { public render(): React.ReactNode { const { accountAddress, accountEthBalanceInWei } = this.props; const value = format.ethAddress(accountAddress); diff --git a/packages/instant/src/components/placing_order_button.tsx b/packages/instant/src/components/placing_order_button.tsx index 2516b90b1..528a305dc 100644 --- a/packages/instant/src/components/placing_order_button.tsx +++ b/packages/instant/src/components/placing_order_button.tsx @@ -14,3 +14,5 @@ export const PlacingOrderButton: React.StatelessComponent<{}> = props => ( Placing Order… </Button> ); + +PlacingOrderButton.displayName = 'PlacingOrderButton'; diff --git a/packages/instant/src/components/scaling_amount_input.tsx b/packages/instant/src/components/scaling_amount_input.tsx index 4feb0502d..7dc1fdc0c 100644 --- a/packages/instant/src/components/scaling_amount_input.tsx +++ b/packages/instant/src/components/scaling_amount_input.tsx @@ -26,7 +26,7 @@ interface ScalingAmountInputState { } const { stringToMaybeBigNumber, areMaybeBigNumbersEqual } = maybeBigNumberUtil; -export class ScalingAmountInput extends React.Component<ScalingAmountInputProps, ScalingAmountInputState> { +export class ScalingAmountInput extends React.PureComponent<ScalingAmountInputProps, ScalingAmountInputState> { public static defaultProps = { onAmountChange: util.boundNoop, onFontSizeChange: util.boundNoop, diff --git a/packages/instant/src/components/scaling_input.tsx b/packages/instant/src/components/scaling_input.tsx index 00aea37da..c31de1fb5 100644 --- a/packages/instant/src/components/scaling_input.tsx +++ b/packages/instant/src/components/scaling_input.tsx @@ -51,7 +51,7 @@ const defaultScalingSettings: ScalingSettings = { additionalInputSpaceInCh: 0.4, }; -export class ScalingInput extends React.Component<ScalingInputProps> { +export class ScalingInput extends React.PureComponent<ScalingInputProps> { public static defaultProps = { onChange: util.boundNoop, onFontSizeChange: util.boundNoop, diff --git a/packages/instant/src/components/secondary_button.tsx b/packages/instant/src/components/secondary_button.tsx index 705390e28..0714ce287 100644 --- a/packages/instant/src/components/secondary_button.tsx +++ b/packages/instant/src/components/secondary_button.tsx @@ -24,3 +24,4 @@ export const SecondaryButton: React.StatelessComponent<SecondaryButtonProps> = p SecondaryButton.defaultProps = { width: '100%', }; +SecondaryButton.displayName = 'SecondaryButton'; diff --git a/packages/instant/src/components/section_header.tsx b/packages/instant/src/components/section_header.tsx index d0974ebdc..2185b67ba 100644 --- a/packages/instant/src/components/section_header.tsx +++ b/packages/instant/src/components/section_header.tsx @@ -18,3 +18,4 @@ export const SectionHeader: React.StatelessComponent<SectionHeaderProps> = props </Text> ); }; +SectionHeader.displayName = 'SectionHeader'; diff --git a/packages/instant/src/components/sliding_error.tsx b/packages/instant/src/components/sliding_error.tsx index b59e2a905..c7c6732cf 100644 --- a/packages/instant/src/components/sliding_error.tsx +++ b/packages/instant/src/components/sliding_error.tsx @@ -38,6 +38,8 @@ export const Error: React.StatelessComponent<ErrorProps> = props => ( </Container> ); +Error.displayName = 'Error'; + export interface SlidingErrorProps extends ErrorProps { animationState: SlideAnimationState; } @@ -94,3 +96,5 @@ export const SlidingError: React.StatelessComponent<SlidingErrorProps> = props = </SlideAnimation> ); }; + +SlidingError.displayName = 'SlidingError'; diff --git a/packages/instant/src/components/sliding_panel.tsx b/packages/instant/src/components/sliding_panel.tsx index 7f9037049..9b09a0d80 100644 --- a/packages/instant/src/components/sliding_panel.tsx +++ b/packages/instant/src/components/sliding_panel.tsx @@ -26,42 +26,48 @@ export const Panel: React.StatelessComponent<PanelProps> = ({ children, onClose </Container> ); +Panel.displayName = 'Panel'; + export interface SlidingPanelProps extends PanelProps { animationState: SlideAnimationState; + onAnimationEnd?: () => void; } -export const SlidingPanel: React.StatelessComponent<SlidingPanelProps> = props => { - if (props.animationState === 'none') { - return null; +export class SlidingPanel extends React.PureComponent<SlidingPanelProps> { + public render(): React.ReactNode { + if (this.props.animationState === 'none') { + return null; + } + const { animationState, onAnimationEnd, ...rest } = this.props; + const slideAmount = '100%'; + const slideUpSettings: PositionAnimationSettings = { + duration: '0.3s', + timingFunction: 'ease-in-out', + top: { + from: slideAmount, + to: '0px', + }, + position: 'absolute', + }; + const slideDownSettings: PositionAnimationSettings = { + duration: '0.3s', + timingFunction: 'ease-out', + top: { + from: '0px', + to: slideAmount, + }, + position: 'absolute', + }; + return ( + <SlideAnimation + slideInSettings={slideUpSettings} + slideOutSettings={slideDownSettings} + animationState={animationState} + height="100%" + onAnimationEnd={onAnimationEnd} + > + <Panel {...rest} /> + </SlideAnimation> + ); } - const { animationState, ...rest } = props; - const slideAmount = '100%'; - const slideUpSettings: PositionAnimationSettings = { - duration: '0.3s', - timingFunction: 'ease-in-out', - top: { - from: slideAmount, - to: '0px', - }, - position: 'absolute', - }; - const slideDownSettings: PositionAnimationSettings = { - duration: '0.3s', - timingFunction: 'ease-out', - top: { - from: '0px', - to: slideAmount, - }, - position: 'absolute', - }; - return ( - <SlideAnimation - slideInSettings={slideUpSettings} - slideOutSettings={slideDownSettings} - animationState={animationState} - height="100%" - > - <Panel {...rest} /> - </SlideAnimation> - ); -}; +} diff --git a/packages/instant/src/components/standard_panel_content.tsx b/packages/instant/src/components/standard_panel_content.tsx index 79b7bff24..f2987df82 100644 --- a/packages/instant/src/components/standard_panel_content.tsx +++ b/packages/instant/src/components/standard_panel_content.tsx @@ -71,3 +71,5 @@ export const StandardPanelContent: React.StatelessComponent<StandardPanelContent <Container>{action}</Container> </Container> ); + +StandardPanelContent.displayName = 'StandardPanelContent'; diff --git a/packages/instant/src/components/standard_sliding_panel.tsx b/packages/instant/src/components/standard_sliding_panel.tsx index 9f517d273..bcc9d3dce 100644 --- a/packages/instant/src/components/standard_sliding_panel.tsx +++ b/packages/instant/src/components/standard_sliding_panel.tsx @@ -9,7 +9,7 @@ export interface StandardSlidingPanelProps extends StandardSlidingPanelSettings onClose: () => void; } -export class StandardSlidingPanel extends React.Component<StandardSlidingPanelProps> { +export class StandardSlidingPanel extends React.PureComponent<StandardSlidingPanelProps> { public render(): React.ReactNode { const { animationState, content, onClose } = this.props; return ( diff --git a/packages/instant/src/components/time_counter.tsx b/packages/instant/src/components/time_counter.tsx index f9b68163c..93dc497d5 100644 --- a/packages/instant/src/components/time_counter.tsx +++ b/packages/instant/src/components/time_counter.tsx @@ -16,7 +16,7 @@ interface TimeCounterState { elapsedSeconds: number; } -export class TimeCounter extends React.Component<TimeCounterProps, TimeCounterState> { +export class TimeCounter extends React.PureComponent<TimeCounterProps, TimeCounterState> { public state = { elapsedSeconds: 0, }; diff --git a/packages/instant/src/components/timed_progress_bar.tsx b/packages/instant/src/components/timed_progress_bar.tsx index fb3927088..b1644b871 100644 --- a/packages/instant/src/components/timed_progress_bar.tsx +++ b/packages/instant/src/components/timed_progress_bar.tsx @@ -17,7 +17,7 @@ export interface TimedProgressBarProps { * Goes from 0% -> PROGRESS_STALL_AT_WIDTH over time of expectedTimeMs * When hasEnded set to true, goes to 100% through animation of PROGRESS_FINISH_ANIMATION_TIME_MS length of time */ -export class TimedProgressBar extends React.Component<TimedProgressBarProps, {}> { +export class TimedProgressBar extends React.PureComponent<TimedProgressBarProps, {}> { private readonly _barRef = React.createRef<HTMLDivElement>(); public render(): React.ReactNode { diff --git a/packages/instant/src/components/ui/container.tsx b/packages/instant/src/components/ui/container.tsx index 636eb8fc9..58d7d5871 100644 --- a/packages/instant/src/components/ui/container.tsx +++ b/packages/instant/src/components/ui/container.tsx @@ -36,6 +36,7 @@ export interface ContainerProps { cursor?: string; overflow?: string; darkenOnHover?: boolean; + rawHoverColor?: string; boxShadowOnHover?: boolean; flexGrow?: string | number; } @@ -87,6 +88,7 @@ export const Container = background-color: ${props => getBackgroundColor(props.theme, props.backgroundColor, props.rawBackgroundColor)}; border-color: ${props => (props.borderColor ? props.theme[props.borderColor] : 'none')}; &:hover { + ${props => (props.rawHoverColor ? `background-color: ${props.rawHoverColor}` : '')} ${props => props.darkenOnHover ? `background-color: ${ diff --git a/packages/instant/src/components/ui/dropdown.tsx b/packages/instant/src/components/ui/dropdown.tsx index 02e87d639..8788d3d59 100644 --- a/packages/instant/src/components/ui/dropdown.tsx +++ b/packages/instant/src/components/ui/dropdown.tsx @@ -1,7 +1,8 @@ import * as _ from 'lodash'; +import { transparentize } from 'polished'; import * as React from 'react'; -import { ColorOption, completelyTransparent } from '../../style/theme'; +import { ColorOption, completelyTransparent, ThemeConsumer } from '../../style/theme'; import { zIndex } from '../../style/z_index'; import { Container } from './container'; @@ -26,7 +27,7 @@ export interface DropdownState { isOpen: boolean; } -export class Dropdown extends React.Component<DropdownProps, DropdownState> { +export class Dropdown extends React.PureComponent<DropdownProps, DropdownState> { public static defaultProps = { items: [], }; @@ -121,20 +122,26 @@ export interface DropdownItemProps extends DropdownItemConfig { } export const DropdownItem: React.StatelessComponent<DropdownItemProps> = ({ text, onClick, isLast }) => ( - <Container - onClick={onClick} - cursor="pointer" - darkenOnHover={true} - backgroundColor={ColorOption.white} - padding="0.8em" - borderTop="0" - border="1px solid" - borderRadius={isLast ? '0px 0px 4px 4px' : undefined} - width="100%" - borderColor={ColorOption.feintGrey} - > - <Text fontSize="14px" fontColor={ColorOption.darkGrey}> - {text} - </Text> - </Container> + <ThemeConsumer> + {theme => ( + <Container + onClick={onClick} + cursor="pointer" + rawHoverColor={transparentize(0.9, theme[ColorOption.primaryColor])} + backgroundColor={ColorOption.white} + padding="0.8em" + borderTop="0" + border="1px solid" + borderRadius={isLast ? '0px 0px 4px 4px' : undefined} + width="100%" + borderColor={ColorOption.feintGrey} + > + <Text fontSize="14px" fontColor={ColorOption.darkGrey}> + {text} + </Text> + </Container> + )} + </ThemeConsumer> ); + +DropdownItem.displayName = 'DropdownItem'; diff --git a/packages/instant/src/components/wallet_prompt.tsx b/packages/instant/src/components/wallet_prompt.tsx index c07cfe7b5..10433767f 100644 --- a/packages/instant/src/components/wallet_prompt.tsx +++ b/packages/instant/src/components/wallet_prompt.tsx @@ -45,3 +45,5 @@ WalletPrompt.defaultProps = { primaryColor: ColorOption.darkOrange, secondaryColor: ColorOption.lightOrange, }; + +WalletPrompt.displayName = 'WalletPrompt'; diff --git a/packages/instant/src/components/zero_ex_instant.tsx b/packages/instant/src/components/zero_ex_instant.tsx index 2267b4dbf..e9cb48e61 100644 --- a/packages/instant/src/components/zero_ex_instant.tsx +++ b/packages/instant/src/components/zero_ex_instant.tsx @@ -17,3 +17,5 @@ export const ZeroExInstant: React.StatelessComponent<ZeroExInstantProps> = props </div> ); }; + +ZeroExInstant.displayName = 'ZeroExInstant'; diff --git a/packages/instant/src/components/zero_ex_instant_container.tsx b/packages/instant/src/components/zero_ex_instant_container.tsx index 0337c7714..e8c64d5d1 100644 --- a/packages/instant/src/components/zero_ex_instant_container.tsx +++ b/packages/instant/src/components/zero_ex_instant_container.tsx @@ -24,7 +24,10 @@ export interface ZeroExInstantContainerState { tokenSelectionPanelAnimationState: SlideAnimationState; } -export class ZeroExInstantContainer extends React.Component<ZeroExInstantContainerProps, ZeroExInstantContainerState> { +export class ZeroExInstantContainer extends React.PureComponent< + ZeroExInstantContainerProps, + ZeroExInstantContainerState +> { public state = { tokenSelectionPanelAnimationState: 'none' as SlideAnimationState, }; @@ -60,6 +63,7 @@ export class ZeroExInstantContainer extends React.Component<ZeroExInstantContain <SlidingPanel animationState={this.state.tokenSelectionPanelAnimationState} onClose={this._handlePanelCloseClickedX} + onAnimationEnd={this._handleSlidingPanelAnimationEnd} > <AvailableERC20TokenSelector onTokenSelect={this._handlePanelCloseAfterChose} /> </SlidingPanel> @@ -98,4 +102,11 @@ export class ZeroExInstantContainer extends React.Component<ZeroExInstantContain tokenSelectionPanelAnimationState: 'slidOut', }); }; + private readonly _handleSlidingPanelAnimationEnd = (): void => { + if (this.state.tokenSelectionPanelAnimationState === 'slidOut') { + // When the slidOut animation completes, don't keep the panel mounted. + // Performance optimization + this.setState({ tokenSelectionPanelAnimationState: 'none' }); + } + }; } diff --git a/packages/instant/src/components/zero_ex_instant_overlay.tsx b/packages/instant/src/components/zero_ex_instant_overlay.tsx index 96e560691..38a716091 100644 --- a/packages/instant/src/components/zero_ex_instant_overlay.tsx +++ b/packages/instant/src/components/zero_ex_instant_overlay.tsx @@ -49,3 +49,5 @@ export const ZeroExInstantOverlay: React.StatelessComponent<ZeroExInstantOverlay </ZeroExInstantProvider> ); }; + +ZeroExInstantOverlay.displayName = 'ZeroExInstantOverlay'; diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx index 2de327cd7..ec8e82ee3 100644 --- a/packages/instant/src/components/zero_ex_instant_provider.tsx +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -21,7 +21,7 @@ import { providerStateFactory } from '../util/provider_state_factory'; export type ZeroExInstantProviderProps = ZeroExInstantBaseConfig; -export class ZeroExInstantProvider extends React.Component<ZeroExInstantProviderProps> { +export class ZeroExInstantProvider extends React.PureComponent<ZeroExInstantProviderProps> { private readonly _store: Store; private _accountUpdateHeartbeat?: Heartbeater; private _buyQuoteHeartbeat?: Heartbeater; diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index 975dfcbea..22f0cb6a4 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -72,5 +72,6 @@ export const PROVIDER_TYPE_TO_NAME: { [key in ProviderType]: string } = { [ProviderType.Mist]: 'Mist', [ProviderType.CoinbaseWallet]: 'Coinbase Wallet', [ProviderType.Parity]: 'Parity', + [ProviderType.TrustWallet]: 'Trust Wallet', [ProviderType.Fallback]: 'Fallback', }; diff --git a/packages/instant/src/containers/latest_error.tsx b/packages/instant/src/containers/latest_error.tsx index 0d4349124..6da4558ef 100644 --- a/packages/instant/src/containers/latest_error.tsx +++ b/packages/instant/src/containers/latest_error.tsx @@ -14,7 +14,7 @@ import { zIndex } from '../style/z_index'; import { Asset, DisplayStatus, Omit, SlideAnimationState } from '../types'; import { errorFlasher } from '../util/error_flasher'; -export interface LatestErrorComponentProps { +interface LatestErrorComponentProps { asset?: Asset; latestErrorMessage?: string; animationState: SlideAnimationState; @@ -22,7 +22,7 @@ export interface LatestErrorComponentProps { onOverlayClick: () => void; } -export const LatestErrorComponent: React.StatelessComponent<LatestErrorComponentProps> = props => { +const LatestErrorComponent: React.StatelessComponent<LatestErrorComponentProps> = props => { if (!props.latestErrorMessage) { // Render a hidden SlidingError such that instant does not move when a real error is rendered. return ( diff --git a/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts b/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts index 80943a96f..4da99cf04 100644 --- a/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts +++ b/packages/instant/src/containers/selected_asset_buy_order_state_buttons.ts @@ -95,6 +95,9 @@ const mapDispatchToProps = ( if (error === ZeroExInstantError.InsufficientETH) { const errorMessage = "You don't have enough ETH"; errorFlasher.flashNewErrorMessage(dispatch, errorMessage); + } else if (error === ZeroExInstantError.CouldNotSubmitTransaction) { + const errorMessage = 'Could not submit transaction'; + errorFlasher.flashNewErrorMessage(dispatch, errorMessage); } else { errorFlasher.flashNewErrorMessage(dispatch); } diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index e7c920f36..ae672c919 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -96,6 +96,7 @@ export enum Network { export enum ZeroExInstantError { AssetMetaDataNotAvailable = 'ASSET_META_DATA_NOT_AVAILABLE', InsufficientETH = 'INSUFFICIENT_ETH', + CouldNotSubmitTransaction = 'COULD_NOT_SUBMIT_TRANSACTION', } export type SimpleHandler = () => void; @@ -181,6 +182,7 @@ export enum ProviderType { Mist = 'MIST', CoinbaseWallet = 'COINBASE_WALLET', Cipher = 'CIPHER', + TrustWallet = 'TRUST_WALLET', Fallback = 'FALLBACK', } diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index 6c63907dc..4faeaaf5a 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -46,6 +46,7 @@ enum EventNames { BUY_STARTED = 'Buy - Started', BUY_SIGNATURE_DENIED = 'Buy - Signature Denied', BUY_SIMULATION_FAILED = 'Buy - Simulation Failed', + BUY_UNKNOWN_ERROR = 'Buy - Unknown Error', BUY_TX_SUBMITTED = 'Buy - Tx Submitted', BUY_TX_SUCCEEDED = 'Buy - Tx Succeeded', BUY_TX_FAILED = 'Buy - Tx Failed', @@ -189,6 +190,11 @@ export const analytics = { trackingEventFnWithPayload(EventNames.BUY_SIGNATURE_DENIED)(buyQuoteEventProperties(buyQuote)), trackBuySimulationFailed: (buyQuote: BuyQuote) => trackingEventFnWithPayload(EventNames.BUY_SIMULATION_FAILED)(buyQuoteEventProperties(buyQuote)), + trackBuyUnknownError: (buyQuote: BuyQuote, errorMessage: string) => + trackingEventFnWithPayload(EventNames.BUY_UNKNOWN_ERROR)({ + ...buyQuoteEventProperties(buyQuote), + errorMessage, + }), trackBuyTxSubmitted: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) => trackingEventFnWithPayload(EventNames.BUY_TX_SUBMITTED)({ ...buyQuoteEventProperties(buyQuote), diff --git a/packages/instant/src/util/env.ts b/packages/instant/src/util/env.ts index 0fda0cc0e..aedf4f5d6 100644 --- a/packages/instant/src/util/env.ts +++ b/packages/instant/src/util/env.ts @@ -44,6 +44,8 @@ export const envUtil = { getProviderType(provider: Provider): ProviderType | undefined { if (provider.constructor.name === 'EthereumProvider') { return ProviderType.Mist; + } else if ((provider as any).isTrust) { + return ProviderType.TrustWallet; } else if ((provider as any).isParity) { return ProviderType.Parity; } else if ((provider as any).isMetaMask) { |