feat: Buy Bag events (#5062)
* Add activity boolean to Collection page event * Log add-to-bag events * WIP * Bag update events * Log pay event * Bag success event * Add bag signed event * Move formatting function out to util * Format event properties with utility function * Remove console log and fix event on details page * Remove commented code * Fix event names to follow convention * Move priceChangedAssets logging to useEffect * Add modal constant and useTrace * Fix typo
This commit is contained in:
parent
fda9d29d5e
commit
d400b9094d
@ -16,6 +16,12 @@ export enum EventName {
|
||||
NAVBAR_SEARCH_SELECTED = 'Navbar Search Selected',
|
||||
NAVBAR_SEARCH_EXITED = 'Navbar Search Exited',
|
||||
NFT_ACTIVITY_SELECTED = 'NFT Activity Selected',
|
||||
NFT_BUY_ADDED = 'NFT Buy Bag Added',
|
||||
NFT_BUY_BAG_CHANGED = 'NFT Buy Bag Changed',
|
||||
NFT_BUY_BAG_PAY = 'NFT Buy Bag Pay Clicked',
|
||||
NFT_BUY_BAG_REFUNDED = 'NFT Buy Bag Refunded',
|
||||
NFT_BUY_BAG_SIGNED = 'NFT Buy Bag Signed',
|
||||
NFT_BUY_BAG_SUCCEEDED = 'NFT Buy Bag Succeeded',
|
||||
NFT_FILTER_OPENED = 'NFT Collection Filter Opened',
|
||||
NFT_FILTER_SELECTED = 'NFT Filter Selected',
|
||||
NFT_TRENDING_ROW_SELECTED = 'Trending Row Selected',
|
||||
@ -106,6 +112,7 @@ export enum SectionName {
|
||||
/** Known modals for analytics purposes. */
|
||||
export enum ModalName {
|
||||
CONFIRM_SWAP = 'confirm-swap-modal',
|
||||
NFT_TX_COMPLETE = 'nft-tx-complete-modal',
|
||||
TOKEN_SELECTOR = 'token-selector-modal',
|
||||
// alphabetize additional modal names.
|
||||
}
|
||||
@ -125,6 +132,7 @@ export enum ElementName {
|
||||
MAX_TOKEN_AMOUNT_BUTTON = 'max-token-amount-button',
|
||||
NAVBAR_SEARCH_INPUT = 'navbar-search-input',
|
||||
NFT_ACTIVITY_TAB = 'nft-activity-tab',
|
||||
NFT_BUY_BAG_PAY_BUTTON = 'nft-buy-bag-pay-button',
|
||||
NFT_FILTER_BUTTON = 'nft-filter-button',
|
||||
NFT_FILTER_OPTION = 'nft-filter-option',
|
||||
NFT_TRENDING_ROW = 'nft-trending-row',
|
||||
|
@ -20,11 +20,14 @@ import {
|
||||
} from 'nft/hooks'
|
||||
import { fetchRoute } from 'nft/queries'
|
||||
import { BagItemStatus, BagStatus, ProfilePageStateType, RouteResponse, TxStateType } from 'nft/types'
|
||||
import { buildSellObject } from 'nft/utils/buildSellObject'
|
||||
import { recalculateBagUsingPooledAssets } from 'nft/utils/calcPoolPrice'
|
||||
import { fetchPrice } from 'nft/utils/fetchPrice'
|
||||
import {
|
||||
buildSellObject,
|
||||
fetchPrice,
|
||||
formatAssetEventProperties,
|
||||
recalculateBagUsingPooledAssets,
|
||||
sortUpdatedAssets,
|
||||
} from 'nft/utils'
|
||||
import { combineBuyItemsWithTxRoute } from 'nft/utils/txRoute/combineItemsWithTxRoute'
|
||||
import { sortUpdatedAssets } from 'nft/utils/updatedAssets'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useQuery, useQueryClient } from 'react-query'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
@ -283,6 +286,10 @@ const Bag = () => {
|
||||
bagStatus={bagStatus}
|
||||
fetchAssets={fetchAssets}
|
||||
assetsAreInReview={itemsInBag.some((item) => item.status === BagItemStatus.REVIEWING_PRICE_CHANGE)}
|
||||
eventProperties={{
|
||||
usd_value: totalUsdPrice,
|
||||
...formatAssetEventProperties(itemsInBag.map((item) => item.asset)),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isSellingAssets && isProfilePage && (
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { EventName } from 'analytics/constants'
|
||||
import { Trace } from 'analytics/Trace'
|
||||
import { BagRow, PriceChangeBagRow, UnavailableAssetsHeaderRow } from 'nft/components/bag/BagRow'
|
||||
import { Column } from 'nft/components/Flex'
|
||||
import { useBag, useIsMobile } from 'nft/hooks'
|
||||
import { BagItemStatus, BagStatus } from 'nft/types'
|
||||
import { recalculateBagUsingPooledAssets } from 'nft/utils/calcPoolPrice'
|
||||
import { fetchPrice } from 'nft/utils/fetchPrice'
|
||||
import { formatAssetEventProperties } from 'nft/utils/formatEventProperties'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useQuery } from 'react-query'
|
||||
|
||||
@ -44,16 +48,32 @@ export const BagContent = () => {
|
||||
const hasAssetsInReview = priceChangedAssets.length > 0
|
||||
const hasAssets = itemsInBag.length > 0
|
||||
|
||||
if (hasAssetsInReview)
|
||||
sendAnalyticsEvent(EventName.NFT_BUY_BAG_CHANGED, {
|
||||
usd_value: fetchedPriceData,
|
||||
bag_quantity: itemsInBag,
|
||||
...formatAssetEventProperties(priceChangedAssets),
|
||||
})
|
||||
|
||||
if (bagStatus === BagStatus.IN_REVIEW && !hasAssetsInReview) {
|
||||
if (hasAssets) setBagStatus(BagStatus.CONFIRM_REVIEW)
|
||||
else setBagStatus(BagStatus.ADDING_TO_BAG)
|
||||
}
|
||||
}, [bagStatus, itemsInBag, priceChangedAssets, setBagStatus])
|
||||
}, [bagStatus, itemsInBag, priceChangedAssets, setBagStatus, fetchedPriceData])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Column display={priceChangedAssets.length > 0 || unavailableAssets.length > 0 ? 'flex' : 'none'}>
|
||||
{unavailableAssets.length > 0 && (
|
||||
<Trace
|
||||
name={EventName.NFT_BUY_BAG_CHANGED}
|
||||
properties={{
|
||||
usd_value: fetchedPriceData,
|
||||
bag_quantity: itemsInBag,
|
||||
...formatAssetEventProperties(unavailableAssets),
|
||||
}}
|
||||
shouldLogImpression
|
||||
>
|
||||
<UnavailableAssetsHeaderRow
|
||||
assets={unavailableAssets}
|
||||
usdPrice={fetchedPriceData}
|
||||
@ -62,6 +82,7 @@ export const BagContent = () => {
|
||||
setDidOpenUnavailableAssets={setDidOpenUnavailableAssets}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
</Trace>
|
||||
)}
|
||||
{priceChangedAssets.map((asset, index) => (
|
||||
<PriceChangeBagRow
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ElementName, Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import Loader from 'components/Loader'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
@ -48,6 +50,7 @@ interface BagFooterProps {
|
||||
bagStatus: BagStatus
|
||||
fetchAssets: () => void
|
||||
assetsAreInReview: boolean
|
||||
eventProperties: Record<string, unknown>
|
||||
}
|
||||
|
||||
const PENDING_BAG_STATUSES = [
|
||||
@ -65,6 +68,7 @@ export const BagFooter = ({
|
||||
bagStatus,
|
||||
fetchAssets,
|
||||
assetsAreInReview,
|
||||
eventProperties,
|
||||
}: BagFooterProps) => {
|
||||
const toggleWalletModal = useToggleWalletModal()
|
||||
const walletModalIsOpen = useModalIsOpen(ApplicationModal.WALLET)
|
||||
@ -102,6 +106,13 @@ export const BagFooter = ({
|
||||
)}
|
||||
</WarningText>
|
||||
)}
|
||||
<TraceEvent
|
||||
events={[Event.onClick]}
|
||||
name={EventName.NFT_BUY_BAG_PAY}
|
||||
element={ElementName.NFT_BUY_BAG_PAY_BUTTON}
|
||||
properties={{ ...eventProperties }}
|
||||
shouldLogImpression={isConnected && !isDisabled}
|
||||
>
|
||||
<Row
|
||||
as="button"
|
||||
color="explicitWhite"
|
||||
@ -124,6 +135,7 @@ export const BagFooter = ({
|
||||
? 'Transaction pending'
|
||||
: 'Pay'}
|
||||
</Row>
|
||||
</TraceEvent>
|
||||
</Footer>
|
||||
</Column>
|
||||
)
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { ChainId } from '@uniswap/smart-order-router'
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { EventName, PageName } from 'analytics/constants'
|
||||
import { useTrace } from 'analytics/Trace'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
@ -96,6 +99,15 @@ export const BuyCell = ({
|
||||
return itemsInBag.some((item) => asset.tokenId === item.asset.tokenId && asset.address === item.asset.address)
|
||||
}, [asset, itemsInBag])
|
||||
|
||||
const trace = useTrace({ page: PageName.NFT_COLLECTION_PAGE })
|
||||
|
||||
const eventProperties = {
|
||||
collection_address: asset.address,
|
||||
token_id: asset.tokenId,
|
||||
token_type: asset.tokenType,
|
||||
...trace,
|
||||
}
|
||||
|
||||
return (
|
||||
<Column display={{ sm: 'none', lg: 'flex' }} height="full" justifyContent="center" marginX="auto">
|
||||
{event.eventType === ActivityEventType.Listing && event.orderStatus ? (
|
||||
@ -106,6 +118,7 @@ export const BuyCell = ({
|
||||
e.preventDefault()
|
||||
isSelected ? removeAsset([asset]) : selectAsset([asset])
|
||||
!isSelected && !cartExpanded && !isMobile && toggleCart()
|
||||
!isSelected && sendAnalyticsEvent(EventName.NFT_BUY_ADDED, { eventProperties })
|
||||
}}
|
||||
disabled={event.orderStatus !== OrderStatus.VALID}
|
||||
>
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { EventName, PageName } from 'analytics/constants'
|
||||
import { useTrace } from 'analytics/Trace'
|
||||
import { useBag } from 'nft/hooks'
|
||||
import { GenieAsset, Markets, UniformHeight } from 'nft/types'
|
||||
import { formatWeiToDecimal, isAudio, isVideo, rarityProviderLogo } from 'nft/utils'
|
||||
@ -36,6 +39,7 @@ export const CollectionAsset = ({
|
||||
const itemsInBag = useBag((state) => state.itemsInBag)
|
||||
const bagExpanded = useBag((state) => state.bagExpanded)
|
||||
const toggleBag = useBag((state) => state.toggleBag)
|
||||
const trace = useTrace({ page: PageName.NFT_COLLECTION_PAGE })
|
||||
|
||||
const { quantity, isSelected } = useMemo(() => {
|
||||
return {
|
||||
@ -72,6 +76,13 @@ export const CollectionAsset = ({
|
||||
}
|
||||
}, [asset])
|
||||
|
||||
const eventProperties = {
|
||||
collection_address: asset.address,
|
||||
token_id: asset.tokenId,
|
||||
token_type: asset.tokenType,
|
||||
...trace,
|
||||
}
|
||||
|
||||
return (
|
||||
<Card.Container
|
||||
asset={asset}
|
||||
@ -79,6 +90,7 @@ export const CollectionAsset = ({
|
||||
addAssetToBag={() => {
|
||||
addAssetsToBag([asset])
|
||||
!bagExpanded && !isMobile && toggleBag()
|
||||
sendAnalyticsEvent(EventName.NFT_BUY_ADDED, { ...eventProperties })
|
||||
}}
|
||||
removeAssetFromBag={() => {
|
||||
removeAssetsFromBag([asset])
|
||||
|
@ -1,3 +1,6 @@
|
||||
import { EventName, ModalName } from 'analytics/constants'
|
||||
import { Trace } from 'analytics/Trace'
|
||||
import { useTrace } from 'analytics/Trace'
|
||||
import clsx from 'clsx'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Portal } from 'nft/components/common/Portal'
|
||||
@ -16,6 +19,7 @@ import {
|
||||
parseTransactionResponse,
|
||||
shortenTxHash,
|
||||
} from 'nft/utils'
|
||||
import { formatAssetEventProperties } from 'nft/utils/formatEventProperties'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
@ -33,6 +37,7 @@ const TxCompleteModal = () => {
|
||||
const isMobile = useIsMobile()
|
||||
const txHashUrl = getExplorerLink(1, txHash, ExplorerDataType.TRANSACTION)
|
||||
const shouldShowModal = (txState === TxStateType.Success || txState === TxStateType.Failed) && txState
|
||||
const trace = useTrace({ modal: ModalName.NFT_TX_COMPLETE })
|
||||
const {
|
||||
nftsPurchased,
|
||||
nftsNotPurchased,
|
||||
@ -73,6 +78,17 @@ const TxCompleteModal = () => {
|
||||
<Box className={styles.modalContainer} onClick={closeTxCompleteScreen}>
|
||||
{/* Successfully purchased NFTs */}
|
||||
{showPurchasedModal && (
|
||||
<Trace
|
||||
name={EventName.NFT_BUY_BAG_SUCCEEDED}
|
||||
properties={{
|
||||
buy_quantity: nftsPurchased.length,
|
||||
usd_value: totalPurchaseValue,
|
||||
transaction_hash: txHash,
|
||||
...formatAssetEventProperties(nftsPurchased),
|
||||
...trace,
|
||||
}}
|
||||
shouldLogImpression
|
||||
>
|
||||
<Box className={styles.successModal} onClick={stopPropagation}>
|
||||
<UniIcon color={vars.color.pink400} width="36" height="36" className={styles.uniLogo} />
|
||||
<Box display="flex" flexWrap="wrap" width="full" height="min">
|
||||
@ -128,11 +144,23 @@ const TxCompleteModal = () => {
|
||||
</a>
|
||||
</Box>
|
||||
</Box>
|
||||
</Trace>
|
||||
)}
|
||||
{/* NFTs that were not purchased ie Refunds */}
|
||||
{showRefundModal &&
|
||||
/* Showing both purchases & refunds */
|
||||
(showPurchasedModal ? (
|
||||
<Trace
|
||||
name={EventName.NFT_BUY_BAG_REFUNDED}
|
||||
properties={{
|
||||
buy_quantity: nftsPurchased.length,
|
||||
fail_quantity: nftsNotPurchased.length,
|
||||
refund_amount_usd: totalUSDRefund,
|
||||
transaction_hash: txHash,
|
||||
...trace,
|
||||
}}
|
||||
shouldLogImpression
|
||||
>
|
||||
<Box className={styles.mixedRefundModal} onClick={stopPropagation}>
|
||||
<Box
|
||||
height="full"
|
||||
@ -145,8 +173,8 @@ const TxCompleteModal = () => {
|
||||
<p className={styles.subtitle}>Instant Refund</p>
|
||||
<p className={styles.interStd}>
|
||||
Uniswap returned{' '}
|
||||
<span style={{ fontWeight: '700' }}>{formatEthPrice(totalRefundValue.toString())} ETH</span> back
|
||||
to your wallet for unavailable items.
|
||||
<span style={{ fontWeight: '700' }}>{formatEthPrice(totalRefundValue.toString())} ETH</span>{' '}
|
||||
back to your wallet for unavailable items.
|
||||
</p>
|
||||
<Box
|
||||
display="flex"
|
||||
@ -191,8 +219,19 @@ const TxCompleteModal = () => {
|
||||
</Box>
|
||||
<Box className={styles.refundOverflowFade} />
|
||||
</Box>
|
||||
</Trace>
|
||||
) : (
|
||||
// Only showing when all assets are unavailable
|
||||
<Trace
|
||||
name={EventName.NFT_BUY_BAG_REFUNDED}
|
||||
properties={{
|
||||
buy_quantity: 0,
|
||||
fail_quantity: nftsNotPurchased.length,
|
||||
refund_amount_usd: totalUSDRefund,
|
||||
...trace,
|
||||
}}
|
||||
shouldLogImpression
|
||||
>
|
||||
<Box className={styles.fullRefundModal} onClick={stopPropagation}>
|
||||
<Box marginLeft="auto" marginRight="auto" display="flex">
|
||||
{txState === TxStateType.Success ? (
|
||||
@ -296,6 +335,7 @@ const TxCompleteModal = () => {
|
||||
Return to Marketplace
|
||||
</Box>
|
||||
</Box>
|
||||
</Trace>
|
||||
))}
|
||||
</Box>
|
||||
</Portal>
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { EventName, PageName } from 'analytics/constants'
|
||||
import { useTrace } from 'analytics/Trace'
|
||||
import clsx from 'clsx'
|
||||
import { MouseoverTooltip } from 'components/Tooltip/index'
|
||||
import useENSName from 'hooks/useENSName'
|
||||
@ -132,6 +135,15 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
|
||||
const [isOwned, setIsOwned] = useState(false)
|
||||
const { account: address, provider } = useWeb3React()
|
||||
|
||||
const trace = useTrace({ page: PageName.NFT_DETAILS_PAGE })
|
||||
|
||||
const eventProperties = {
|
||||
collection_address: asset.address,
|
||||
token_id: asset.tokenId,
|
||||
token_type: asset.tokenType,
|
||||
...trace,
|
||||
}
|
||||
|
||||
const { rarityProvider, rarityLogo } = useMemo(
|
||||
() =>
|
||||
asset.rarity
|
||||
@ -394,7 +406,10 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
removeAssetsFromBag([asset])
|
||||
} else addAssetsToBag([asset])
|
||||
} else {
|
||||
addAssetsToBag([asset])
|
||||
sendAnalyticsEvent(EventName.NFT_BUY_ADDED, { ...eventProperties })
|
||||
}
|
||||
setSelected((x) => !x)
|
||||
}}
|
||||
>
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { EventName, PageName } from 'analytics/constants'
|
||||
import { useTrace } from 'analytics/Trace'
|
||||
import { CancelListingIcon, MinusIcon, PlusIcon } from 'nft/components/icons'
|
||||
import { useBag } from 'nft/hooks'
|
||||
import { CollectionInfoForAsset, GenieAsset, TokenType } from 'nft/types'
|
||||
@ -196,6 +199,14 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps)
|
||||
const USDPrice = useUsdPrice(asset)
|
||||
const isErc1555 = asset.tokenType === TokenType.ERC1155
|
||||
|
||||
const trace = useTrace({ page: PageName.NFT_DETAILS_PAGE })
|
||||
const eventProperties = {
|
||||
collection_address: asset.address,
|
||||
token_id: asset.tokenId,
|
||||
token_type: asset.tokenType,
|
||||
...trace,
|
||||
}
|
||||
|
||||
const { quantity, assetInBag } = useMemo(() => {
|
||||
return {
|
||||
quantity: itemsInBag.filter(
|
||||
@ -242,7 +253,10 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps)
|
||||
assetInBag={assetInBag}
|
||||
margin={true}
|
||||
useAccentColor={true}
|
||||
onClick={() => (assetInBag ? removeAssetsFromBag([asset]) : addAssetsToBag([asset]))}
|
||||
onClick={() => {
|
||||
assetInBag ? removeAssetsFromBag([asset]) : addAssetsToBag([asset])
|
||||
!assetInBag && sendAnalyticsEvent(EventName.NFT_BUY_ADDED, { ...eventProperties })
|
||||
}}
|
||||
>
|
||||
<ThemedText.SubHeader lineHeight={'20px'}>{assetInBag ? 'Remove' : 'Buy Now'}</ThemedText.SubHeader>
|
||||
</BuyNowButton>
|
||||
|
@ -3,6 +3,8 @@ import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { hexStripZeros } from '@ethersproject/bytes'
|
||||
import { ContractReceipt } from '@ethersproject/contracts'
|
||||
import type { JsonRpcSigner } from '@ethersproject/providers'
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { EventName } from 'analytics/constants'
|
||||
import create from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
@ -48,6 +50,7 @@ export const useSendTransaction = create<TxState>()(
|
||||
const res = await signer.sendTransaction(tx)
|
||||
set({ state: TxStateType.Confirming })
|
||||
set({ txHash: res.hash })
|
||||
sendAnalyticsEvent(EventName.NFT_BUY_BAG_SIGNED, { transaction_hash: res.hash })
|
||||
|
||||
const txReceipt = await res.wait()
|
||||
|
||||
|
@ -91,7 +91,7 @@ const Collection = () => {
|
||||
<>
|
||||
<Trace
|
||||
page={PageName.NFT_COLLECTION_PAGE}
|
||||
properties={{ collection_address: contractAddress, chain_id: chainId }}
|
||||
properties={{ collection_address: contractAddress, chain_id: chainId, is_activity_view: isActivityToggled }}
|
||||
shouldLogImpression
|
||||
>
|
||||
<Column width="full">
|
||||
|
7
src/nft/utils/formatEventProperties.ts
Normal file
7
src/nft/utils/formatEventProperties.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { GenieAsset } from 'nft/types'
|
||||
|
||||
export const formatAssetEventProperties = (assets: GenieAsset[]) => ({
|
||||
collection_addresses: assets.map((asset) => asset.address),
|
||||
token_ids: assets.map((asset) => asset.tokenId),
|
||||
token_types: assets.map((asset) => asset.tokenType),
|
||||
})
|
@ -5,6 +5,7 @@ export * from './calcPoolPrice'
|
||||
export * from './carousel'
|
||||
export * from './currency'
|
||||
export * from './fetchPrice'
|
||||
export * from './formatEventProperties'
|
||||
export * from './isAudio'
|
||||
export * from './isVideo'
|
||||
export * from './listNfts'
|
||||
|
Loading…
Reference in New Issue
Block a user