diff options
author | Steve Klebanoff <steve.klebanoff@gmail.com> | 2018-11-30 07:45:35 +0800 |
---|---|---|
committer | Steve Klebanoff <steve.klebanoff@gmail.com> | 2018-11-30 07:45:35 +0800 |
commit | 702dbbae54ae2fba3330c7dcda68ad6ffe64abc0 (patch) | |
tree | e5f6da99570de8d0f49695f944e92b2423de0b85 /packages/instant | |
parent | 856f4b473b54ecb3dc707ea334890397c98606bf (diff) | |
parent | fc3641b49938ea9dc5e77e633e798ca19aa812ff (diff) | |
download | dexon-sol-tools-702dbbae54ae2fba3330c7dcda68ad6ffe64abc0.tar dexon-sol-tools-702dbbae54ae2fba3330c7dcda68ad6ffe64abc0.tar.gz dexon-sol-tools-702dbbae54ae2fba3330c7dcda68ad6ffe64abc0.tar.bz2 dexon-sol-tools-702dbbae54ae2fba3330c7dcda68ad6ffe64abc0.tar.lz dexon-sol-tools-702dbbae54ae2fba3330c7dcda68ad6ffe64abc0.tar.xz dexon-sol-tools-702dbbae54ae2fba3330c7dcda68ad6ffe64abc0.tar.zst dexon-sol-tools-702dbbae54ae2fba3330c7dcda68ad6ffe64abc0.zip |
Merge branch 'development' into feature/instant/misc-close-analytics
Diffstat (limited to 'packages/instant')
18 files changed, 186 insertions, 51 deletions
diff --git a/packages/instant/package.json b/packages/instant/package.json index 4422dc83f..c75e2408d 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -1,6 +1,6 @@ { "name": "@0x/instant", - "version": "1.0.1", + "version": "1.0.2", "engines": { "node": ">=6.12" }, @@ -46,14 +46,14 @@ "homepage": "https://github.com/0xProject/0x-monorepo/packages/instant/README.md", "dependencies": { "@0x/assert": "^1.0.18", - "@0x/asset-buyer": "^3.0.1", + "@0x/asset-buyer": "^3.0.2", "@0x/json-schemas": "^2.1.2", - "@0x/order-utils": "^3.0.3", - "@0x/subproviders": "^2.1.5", + "@0x/order-utils": "^3.0.4", + "@0x/subproviders": "^2.1.6", "@0x/types": "^1.3.0", "@0x/typescript-typings": "^3.0.4", "@0x/utils": "^2.0.6", - "@0x/web3-wrapper": "^3.1.5", + "@0x/web3-wrapper": "^3.1.6", "bowser": "^1.9.4", "copy-to-clipboard": "^3.0.8", "ethereum-types": "^1.1.2", diff --git a/packages/instant/src/components/erc20_token_selector.tsx b/packages/instant/src/components/erc20_token_selector.tsx index 0a3d4427a..f7d5a4fe4 100644 --- a/packages/instant/src/components/erc20_token_selector.tsx +++ b/packages/instant/src/components/erc20_token_selector.tsx @@ -19,12 +19,12 @@ export interface ERC20TokenSelectorProps { } export interface ERC20TokenSelectorState { - searchQuery?: string; + searchQuery: string; } export class ERC20TokenSelector extends React.Component<ERC20TokenSelectorProps> { public state: ERC20TokenSelectorState = { - searchQuery: undefined, + searchQuery: '', }; public render(): React.ReactNode { const { tokens, onTokenSelect } = this.props; @@ -62,10 +62,10 @@ export class ERC20TokenSelector extends React.Component<ERC20TokenSelectorProps> }; private readonly _isTokenQueryMatch = (token: ERC20Asset): boolean => { const { searchQuery } = this.state; - if (_.isUndefined(searchQuery)) { + const searchQueryLowerCase = searchQuery.toLowerCase().trim(); + if (searchQueryLowerCase === '') { return true; } - const searchQueryLowerCase = searchQuery.toLowerCase(); const tokenName = token.metaData.name.toLowerCase(); const tokenSymbol = token.metaData.symbol.toLowerCase(); return _.startsWith(tokenSymbol, searchQueryLowerCase) || _.startsWith(tokenName, searchQueryLowerCase); diff --git a/packages/instant/src/components/install_wallet_panel_content.tsx b/packages/instant/src/components/install_wallet_panel_content.tsx index 88c26f59c..481d82da0 100644 --- a/packages/instant/src/components/install_wallet_panel_content.tsx +++ b/packages/instant/src/components/install_wallet_panel_content.tsx @@ -8,7 +8,9 @@ import { } from '../constants'; import { ColorOption } from '../style/theme'; import { Browser } from '../types'; +import { analytics } from '../util/analytics'; import { envUtil } from '../util/env'; +import { util } from '../util/util'; import { MetaMaskLogo } from './meta_mask_logo'; import { StandardPanelContent, StandardPanelContentProps } from './standard_panel_content'; @@ -45,6 +47,10 @@ export class InstallWalletPanelContent extends React.Component<InstallWalletPane default: break; } + const onActionClick = () => { + analytics.trackInstallWalletModalClickedGet(); + util.createOpenUrlInNewWindow(actionUrl)(); + }; return { image: <MetaMaskLogo width={85} height={80} />, title: 'Install MetaMask', @@ -52,10 +58,11 @@ export class InstallWalletPanelContent extends React.Component<InstallWalletPane moreInfoSettings: { href: META_MASK_SITE_URL, text: 'What is MetaMask?', + onClick: analytics.trackInstallWalletModalClickedExplanation, }, action: ( <Button - href={actionUrl} + onClick={onActionClick} width="100%" fontColor={ColorOption.white} backgroundColor={ColorOption.darkOrange} diff --git a/packages/instant/src/components/scaling_input.tsx b/packages/instant/src/components/scaling_input.tsx index 129162a74..791692257 100644 --- a/packages/instant/src/components/scaling_input.tsx +++ b/packages/instant/src/components/scaling_input.tsx @@ -98,6 +98,12 @@ export class ScalingInput extends React.Component<ScalingInputProps, ScalingInpu inputWidthPx: this._getInputWidthInPx(), }; } + public componentDidMount(): void { + // Trigger an initial notification of the calculated fontSize. + const currentPhase = ScalingInput.getPhaseFromProps(this.props); + const currentFontSize = ScalingInput.calculateFontSizeFromProps(this.props, currentPhase); + this.props.onFontSizeChange(currentFontSize); + } public componentDidUpdate( prevProps: ScalingInputProps, prevState: ScalingInputState, diff --git a/packages/instant/src/components/standard_panel_content.tsx b/packages/instant/src/components/standard_panel_content.tsx index 582b3318e..79b7bff24 100644 --- a/packages/instant/src/components/standard_panel_content.tsx +++ b/packages/instant/src/components/standard_panel_content.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { ColorOption } from '../style/theme'; +import { util } from '../util/util'; import { Container } from './ui/container'; import { Flex } from './ui/flex'; @@ -9,6 +10,7 @@ import { Text } from './ui/text'; export interface MoreInfoSettings { text: string; href: string; + onClick?: () => void; } export interface StandardPanelContentProps { @@ -21,6 +23,15 @@ export interface StandardPanelContentProps { const SPACING_BETWEEN_PX = '20px'; +const onMoreInfoClick = (href: string, onClick?: () => void) => { + return () => { + if (onClick) { + onClick(); + } + util.createOpenUrlInNewWindow(href)(); + }; +}; + export const StandardPanelContent: React.StatelessComponent<StandardPanelContentProps> = ({ image, title, @@ -50,7 +61,7 @@ export const StandardPanelContent: React.StatelessComponent<StandardPanelContent fontSize="13px" textDecorationLine="underline" fontColor={ColorOption.lightGrey} - href={moreInfoSettings.href} + onClick={onMoreInfoClick(moreInfoSettings.href, moreInfoSettings.onClick)} > {moreInfoSettings.text} </Text> diff --git a/packages/instant/src/components/timed_progress_bar.tsx b/packages/instant/src/components/timed_progress_bar.tsx index 8465b9cd0..fb3927088 100644 --- a/packages/instant/src/components/timed_progress_bar.tsx +++ b/packages/instant/src/components/timed_progress_bar.tsx @@ -1,8 +1,9 @@ import * as _ from 'lodash'; +import { transparentize } from 'polished'; import * as React from 'react'; import { PROGRESS_FINISH_ANIMATION_TIME_MS, PROGRESS_STALL_AT_WIDTH } from '../constants'; -import { ColorOption, css, keyframes, styled } from '../style/theme'; +import { ColorOption, css, keyframes, styled, ThemeConsumer } from '../style/theme'; import { Container } from './ui/container'; @@ -93,8 +94,16 @@ export interface ProgressBarProps extends ProgressProps {} export const ProgressBar: React.ComponentType<ProgressBarProps & React.ClassAttributes<{}>> = React.forwardRef( (props, ref) => ( - <Container width="100%" backgroundColor={ColorOption.lightGrey} borderRadius="6px"> - <Progress {...props} ref={ref as any} /> - </Container> + <ThemeConsumer> + {theme => ( + <Container + width="100%" + borderRadius="6px" + rawBackgroundColor={transparentize(0.5, theme[ColorOption.primaryColor])} + > + <Progress {...props} ref={ref as any} /> + </Container> + )} + </ThemeConsumer> ), ); diff --git a/packages/instant/src/components/ui/container.tsx b/packages/instant/src/components/ui/container.tsx index 4dafe1386..a015ab5bc 100644 --- a/packages/instant/src/components/ui/container.tsx +++ b/packages/instant/src/components/ui/container.tsx @@ -27,6 +27,7 @@ export interface ContainerProps { borderBottom?: string; className?: string; backgroundColor?: ColorOption; + rawBackgroundColor?: string; hasBoxShadow?: boolean; zIndex?: number; whiteSpace?: string; @@ -38,6 +39,16 @@ export interface ContainerProps { flexGrow?: string | number; } +const getBackgroundColor = (theme: any, backgroundColor?: ColorOption, rawBackgroundColor?: string): string => { + if (backgroundColor) { + return theme[backgroundColor] as string; + } + if (rawBackgroundColor) { + return rawBackgroundColor; + } + return 'none'; +}; + export const Container = styled.div < ContainerProps > @@ -70,7 +81,7 @@ export const Container = ${props => props.width && stylesForMedia<string>('width', props.width)} ${props => props.height && stylesForMedia<string>('height', props.height)} ${props => props.borderRadius && stylesForMedia<string>('border-radius', props.borderRadius)} - background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')}; + background-color: ${props => getBackgroundColor(props.theme, props.backgroundColor, props.rawBackgroundColor)}; border-color: ${props => (props.borderColor ? props.theme[props.borderColor] : 'none')}; &:hover { ${props => diff --git a/packages/instant/src/components/ui/text.tsx b/packages/instant/src/components/ui/text.tsx index 8e573d7b9..282477758 100644 --- a/packages/instant/src/components/ui/text.tsx +++ b/packages/instant/src/components/ui/text.tsx @@ -1,4 +1,3 @@ -import { darken } from 'polished'; import * as React from 'react'; import { ColorOption, styled } from '../../style/theme'; @@ -31,7 +30,7 @@ export const Text: React.StatelessComponent<TextProps> = ({ href, onClick, ...re return <StyledText {...rest} onClick={computedOnClick} />; }; -const darkenOnHoverAmount = 0.3; +const opacityOnHoverAmount = 0.5; export const StyledText = styled.div < TextProps > @@ -56,8 +55,7 @@ export const StyledText = ${props => (props.textAlign ? `text-align: ${props.textAlign}` : '')}; ${props => (props.width ? `width: ${props.width}` : '')}; &:hover { - ${props => - props.onClick ? `color: ${darken(darkenOnHoverAmount, props.theme[props.fontColor || 'white'])}` : ''}; + ${props => (props.onClick ? `opacity: ${opacityOnHoverAmount};` : '')}; } } `; diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx index e006a5a5f..a4a03bbf4 100644 --- a/packages/instant/src/components/zero_ex_instant_provider.tsx +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -11,7 +11,7 @@ import { asyncData } from '../redux/async_data'; import { DEFAULT_STATE, DefaultState, State } from '../redux/reducer'; import { store, Store } from '../redux/store'; import { fonts } from '../style/fonts'; -import { AccountState, AffiliateInfo, AssetMetaData, Network, OrderSource } from '../types'; +import { AccountState, AffiliateInfo, AssetMetaData, Network, OrderSource, QuoteFetchOrigin } from '../types'; import { analytics, disableAnalytics } from '../util/analytics'; import { assetUtils } from '../util/asset'; import { errorFlasher } from '../util/error_flasher'; @@ -115,7 +115,9 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider this._buyQuoteHeartbeat.start(BUY_QUOTE_UPDATE_INTERVAL_TIME_MS); // Trigger first buyquote fetch // tslint:disable-next-line:no-floating-promises - asyncData.fetchCurrentBuyQuoteAndDispatchToStore(state, dispatch, { updateSilently: false }); + asyncData.fetchCurrentBuyQuoteAndDispatchToStore(state, dispatch, QuoteFetchOrigin.Manual, { + updateSilently: false, + }); // warm up the gas price estimator cache just in case we can't // grab the gas price estimate when submitting the transaction // tslint:disable-next-line:no-floating-promises diff --git a/packages/instant/src/containers/connected_account_payment_method.ts b/packages/instant/src/containers/connected_account_payment_method.ts index cdeb49a25..e9327a288 100644 --- a/packages/instant/src/containers/connected_account_payment_method.ts +++ b/packages/instant/src/containers/connected_account_payment_method.ts @@ -11,7 +11,7 @@ import { import { Action, actions } from '../redux/actions'; import { asyncData } from '../redux/async_data'; import { State } from '../redux/reducer'; -import { Network, Omit, OperatingSystem, ProviderState, StandardSlidingPanelContent } from '../types'; +import { Network, Omit, OperatingSystem, ProviderState, StandardSlidingPanelContent, WalletSuggestion } from '../types'; import { analytics } from '../util/analytics'; import { envUtil } from '../util/env'; @@ -60,23 +60,28 @@ const mergeProps = ( onUnlockWalletClick: () => connectedDispatch.unlockWalletAndDispatchToStore(connectedState.providerState), onInstallWalletClick: () => { const isMobile = envUtil.isMobileOperatingSystem(); - if (!isMobile) { + const walletSuggestion: WalletSuggestion = isMobile + ? WalletSuggestion.CoinbaseWallet + : WalletSuggestion.MetaMask; + + analytics.trackInstallWalletClicked(walletSuggestion); + if (walletSuggestion === WalletSuggestion.MetaMask) { connectedDispatch.openInstallWalletPanel(); - return; - } - const operatingSystem = envUtil.getOperatingSystem(); - let url = COINBASE_WALLET_SITE_URL; - switch (operatingSystem) { - case OperatingSystem.Android: - url = COINBASE_WALLET_ANDROID_APP_STORE_URL; - break; - case OperatingSystem.iOS: - url = COINBASE_WALLET_IOS_APP_STORE_URL; - break; - default: - break; + } else { + const operatingSystem = envUtil.getOperatingSystem(); + let url = COINBASE_WALLET_SITE_URL; + switch (operatingSystem) { + case OperatingSystem.Android: + url = COINBASE_WALLET_ANDROID_APP_STORE_URL; + break; + case OperatingSystem.iOS: + url = COINBASE_WALLET_IOS_APP_STORE_URL; + break; + default: + break; + } + window.open(url, '_blank'); } - window.open(url, '_blank'); }, }); diff --git a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts index a39bc46a2..cb9df527e 100644 --- a/packages/instant/src/containers/selected_erc20_asset_amount_input.ts +++ b/packages/instant/src/containers/selected_erc20_asset_amount_input.ts @@ -10,7 +10,7 @@ import { ERC20AssetAmountInput, ERC20AssetAmountInputProps } from '../components import { Action, actions } from '../redux/actions'; import { State } from '../redux/reducer'; import { ColorOption } from '../style/theme'; -import { AffiliateInfo, ERC20Asset, Omit, OrderProcessState } from '../types'; +import { AffiliateInfo, ERC20Asset, Omit, OrderProcessState, QuoteFetchOrigin } from '../types'; import { buyQuoteUpdater } from '../util/buy_quote_updater'; export interface SelectedERC20AssetAmountInputProps { @@ -88,7 +88,7 @@ const mapDispatchToProps = ( // even if it's debounced, give them the illusion it's loading dispatch(actions.setQuoteRequestStatePending()); // tslint:disable-next-line:no-floating-promises - debouncedUpdateBuyQuoteAsync(assetBuyer, dispatch, asset, value, { + debouncedUpdateBuyQuoteAsync(assetBuyer, dispatch, asset, value, QuoteFetchOrigin.Manual, { setPending: true, dispatchErrors: true, affiliateInfo, diff --git a/packages/instant/src/redux/analytics_middleware.ts b/packages/instant/src/redux/analytics_middleware.ts index 8aa76eb77..3dc5fe924 100644 --- a/packages/instant/src/redux/analytics_middleware.ts +++ b/packages/instant/src/redux/analytics_middleware.ts @@ -3,7 +3,7 @@ import * as _ from 'lodash'; import { Middleware } from 'redux'; import { ETH_DECIMALS } from '../constants'; -import { Account, AccountState } from '../types'; +import { Account, AccountState, StandardSlidingPanelContent } from '../types'; import { analytics } from '../util/analytics'; import { Action, ActionTypes } from './actions'; @@ -77,6 +77,18 @@ export const analyticsMiddleware: Middleware = store => next => middlewareAction }); } break; + case ActionTypes.OPEN_STANDARD_SLIDING_PANEL: + const openSlidingContent = curState.standardSlidingPanelSettings.content; + if (openSlidingContent === StandardSlidingPanelContent.InstallWallet) { + analytics.trackInstallWalletModalOpened(); + } + break; + case ActionTypes.CLOSE_STANDARD_SLIDING_PANEL: + const closeSlidingContent = curState.standardSlidingPanelSettings.content; + if (closeSlidingContent === StandardSlidingPanelContent.InstallWallet) { + analytics.trackInstallWalletModalClosed(); + } + break; } return nextAction; diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts index 6feb760e7..9fdcea3ca 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 } from '../types'; +import { AccountState, ERC20Asset, OrderProcessState, ProviderState, QuoteFetchOrigin } from '../types'; import { analytics } from '../util/analytics'; import { assetUtils } from '../util/asset'; import { buyQuoteUpdater } from '../util/buy_quote_updater'; @@ -84,6 +84,7 @@ export const asyncData = { fetchCurrentBuyQuoteAndDispatchToStore: async ( state: State, dispatch: Dispatch, + fetchOrigin: QuoteFetchOrigin, options: { updateSilently: boolean }, ) => { const { buyOrderState, providerState, selectedAsset, selectedAssetUnitAmount, affiliateInfo } = state; @@ -99,7 +100,12 @@ export const asyncData = { dispatch, selectedAsset as ERC20Asset, selectedAssetUnitAmount, - { setPending: !options.updateSilently, dispatchErrors: !options.updateSilently, affiliateInfo }, + fetchOrigin, + { + setPending: !options.updateSilently, + dispatchErrors: !options.updateSilently, + affiliateInfo, + }, ); } }, diff --git a/packages/instant/src/style/theme.ts b/packages/instant/src/style/theme.ts index a0751286b..71e4b7052 100644 --- a/packages/instant/src/style/theme.ts +++ b/packages/instant/src/style/theme.ts @@ -1,6 +1,14 @@ import * as styledComponents from 'styled-components'; -const { default: styled, css, keyframes, withTheme, createGlobalStyle, ThemeProvider } = styledComponents; +const { + default: styled, + css, + keyframes, + withTheme, + createGlobalStyle, + ThemeConsumer, + ThemeProvider, +} = styledComponents; export type Theme = { [key in ColorOption]: string }; @@ -45,4 +53,4 @@ export const generateOverlayBlack = (opacity = 0.6) => { return `rgba(0, 0, 0, ${opacity})`; }; -export { styled, css, keyframes, withTheme, createGlobalStyle, ThemeProvider }; +export { styled, css, keyframes, withTheme, createGlobalStyle, ThemeConsumer, ThemeProvider }; diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index 999d50fed..2d73ba29e 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -21,6 +21,11 @@ export enum OrderProcessState { Failure = 'FAILURE', } +export enum QuoteFetchOrigin { + Manual = 'Manual', + Heartbeat = 'Heartbeat', +} + export interface SimulatedProgress { startTimeUnix: number; expectedEndTimeUnix: number; @@ -149,6 +154,11 @@ export enum Browser { Other = 'OTHER', } +export enum WalletSuggestion { + CoinbaseWallet = 'Coinbase Wallet', + MetaMask = 'MetaMask', +} + export enum OperatingSystem { Android = 'ANDROID', iOS = 'IOS', diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index b1a2d2b63..2f76c4a76 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -1,7 +1,16 @@ import { BuyQuote } from '@0x/asset-buyer'; +import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; -import { AffiliateInfo, Asset, Network, OrderProcessState, OrderSource, ProviderState } from '../types'; +import { + AffiliateInfo, + Asset, + Network, + OrderSource, + ProviderState, + QuoteFetchOrigin, + WalletSuggestion, +} from '../types'; import { EventProperties, heapUtil } from './heap'; @@ -34,11 +43,18 @@ enum EventNames { BUY_TX_SUBMITTED = 'Buy - Tx Submitted', BUY_TX_SUCCEEDED = 'Buy - Tx Succeeded', BUY_TX_FAILED = 'Buy - Tx 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', + INSTALL_WALLET_MODAL_CLICKED_GET = 'Install Wallet - Modal - Clicked Get', + INSTALL_WALLET_MODAL_CLOSED = 'Install Wallet - Modal - Closed', TOKEN_SELECTOR_OPENED = 'Token Selector - Opened', TOKEN_SELECTOR_CLOSED = 'Token Selector - Closed', TOKEN_SELECTOR_CHOSE = 'Token Selector - Chose', TOKEN_SELECTOR_SEARCHED = 'Token Selector - Searched', TRANSACTION_VIEWED = 'Transaction - Viewed', + QUOTE_FETCHED = 'Quote - Fetched', + QUOTE_ERROR = 'Quote - Error', } const track = (eventName: EventNames, eventProperties: EventProperties = {}): void => { @@ -173,6 +189,14 @@ export const analytics = { expectedTxTimeMs: expectedEndTimeUnix - startTimeUnix, actualTxTimeMs: new Date().getTime() - startTimeUnix, }), + trackInstallWalletClicked: (walletSuggestion: WalletSuggestion) => + trackingEventFnWithPayload(EventNames.INSTALL_WALLET_CLICKED)({ walletSuggestion }), + trackInstallWalletModalClickedExplanation: trackingEventFnWithoutPayload( + EventNames.INSTALL_WALLET_MODAL_CLICKED_EXPLANATION, + ), + trackInstallWalletModalClickedGet: trackingEventFnWithoutPayload(EventNames.INSTALL_WALLET_MODAL_CLICKED_GET), + trackInstallWalletModalOpened: trackingEventFnWithoutPayload(EventNames.INSTALL_WALLET_MODAL_OPENED), + trackInstallWalletModalClosed: trackingEventFnWithoutPayload(EventNames.INSTALL_WALLET_MODAL_CLOSED), trackTokenSelectorOpened: trackingEventFnWithoutPayload(EventNames.TOKEN_SELECTOR_OPENED), trackTokenSelectorClosed: (closedVia: TokenSelectorClosedVia) => trackingEventFnWithPayload(EventNames.TOKEN_SELECTOR_CLOSED)({ closedVia }), @@ -182,4 +206,16 @@ export const analytics = { trackingEventFnWithPayload(EventNames.TOKEN_SELECTOR_SEARCHED)({ searchText }), trackTransactionViewed: (orderProcesState: OrderProcessState) => trackingEventFnWithPayload(EventNames.TRANSACTION_VIEWED)({ orderState: orderProcesState }), + trackQuoteFetched: (buyQuote: BuyQuote, fetchOrigin: QuoteFetchOrigin) => + trackingEventFnWithPayload(EventNames.QUOTE_FETCHED)({ + ...buyQuoteEventProperties(buyQuote), + fetchOrigin, + }), + trackQuoteError: (errorMessage: string, assetBuyAmount: BigNumber, fetchOrigin: QuoteFetchOrigin) => { + trackingEventFnWithPayload(EventNames.QUOTE_ERROR)({ + errorMessage, + assetBuyAmount: assetBuyAmount.toString(), + fetchOrigin, + }); + }, }; diff --git a/packages/instant/src/util/buy_quote_updater.ts b/packages/instant/src/util/buy_quote_updater.ts index 2fd16d781..c1899f8c1 100644 --- a/packages/instant/src/util/buy_quote_updater.ts +++ b/packages/instant/src/util/buy_quote_updater.ts @@ -6,7 +6,8 @@ import { Dispatch } from 'redux'; import { oc } from 'ts-optchain'; import { Action, actions } from '../redux/actions'; -import { AffiliateInfo, ERC20Asset } from '../types'; +import { AffiliateInfo, ERC20Asset, QuoteFetchOrigin } from '../types'; +import { analytics } from '../util/analytics'; import { assetUtils } from '../util/asset'; import { errorFlasher } from '../util/error_flasher'; @@ -16,7 +17,12 @@ export const buyQuoteUpdater = { dispatch: Dispatch<Action>, asset: ERC20Asset, assetUnitAmount: BigNumber, - options: { setPending: boolean; dispatchErrors: boolean; affiliateInfo?: AffiliateInfo }, + fetchOrigin: QuoteFetchOrigin, + options: { + setPending: boolean; + dispatchErrors: boolean; + affiliateInfo?: AffiliateInfo; + }, ): Promise<void> => { // get a new buy quote. const baseUnitValue = Web3Wrapper.toBaseUnitAmount(assetUnitAmount, asset.metaData.decimals); @@ -31,6 +37,7 @@ export const buyQuoteUpdater = { } catch (error) { if (options.dispatchErrors) { dispatch(actions.setQuoteRequestStateFailure()); + analytics.trackQuoteError(error.message ? error.message : 'other', baseUnitValue, fetchOrigin); let errorMessage; if (error.message === AssetBuyerError.InsufficientAssetLiquidity) { const assetName = assetUtils.bestNameForAsset(asset, 'of this asset'); @@ -58,5 +65,6 @@ export const buyQuoteUpdater = { errorFlasher.clearError(dispatch); // invalidate the last buy quote. dispatch(actions.updateLatestBuyQuote(newBuyQuote)); + analytics.trackQuoteFetched(newBuyQuote, fetchOrigin); }, }; diff --git a/packages/instant/src/util/heartbeater_factory.ts b/packages/instant/src/util/heartbeater_factory.ts index 2b852fb0d..cf29bf3ea 100644 --- a/packages/instant/src/util/heartbeater_factory.ts +++ b/packages/instant/src/util/heartbeater_factory.ts @@ -1,5 +1,6 @@ import { asyncData } from '../redux/async_data'; import { Store } from '../redux/store'; +import { QuoteFetchOrigin } from '../types'; import { Heartbeater } from './heartbeater'; @@ -17,8 +18,13 @@ export const generateAccountHeartbeater = (options: HeartbeatFactoryOptions): He export const generateBuyQuoteHeartbeater = (options: HeartbeatFactoryOptions): Heartbeater => { const { store, shouldPerformImmediatelyOnStart } = options; return new Heartbeater(async () => { - await asyncData.fetchCurrentBuyQuoteAndDispatchToStore(store.getState(), store.dispatch, { - updateSilently: true, - }); + await asyncData.fetchCurrentBuyQuoteAndDispatchToStore( + store.getState(), + store.dispatch, + QuoteFetchOrigin.Heartbeat, + { + updateSilently: true, + }, + ); }, shouldPerformImmediatelyOnStart); }; |