Commit d86120a2 authored by Greg Bugyis's avatar Greg Bugyis Committed by GitHub

feat: Collection page log events (#4971)

* Log collection page view

* Make property names/intention more clear

* Remove console log

* Add event for Activity tab

* Filter events (buy now, marketplaces, and price range)

* Handle trait items

* Bump collection stats mobile padding

* Use shouldLogImpression to conditionally fire event

* Adding back trace on Collection page, still necessary

* Add back address property

* Drop Buy Now log, not part of first set

* Update filter properties to match spreadsheet

* Only trigger price range log if inputs contain a value

* Fix ordering on Page Names

* Capitalize text

* Add constant for filter types

* Add chainId as property
parent e2fea4a5
...@@ -14,6 +14,9 @@ export enum EventName { ...@@ -14,6 +14,9 @@ export enum EventName {
PAGE_VIEWED = 'Page Viewed', PAGE_VIEWED = 'Page Viewed',
NAVBAR_SEARCH_SELECTED = 'Navbar Search Selected', NAVBAR_SEARCH_SELECTED = 'Navbar Search Selected',
NAVBAR_SEARCH_EXITED = 'Navbar Search Exited', NAVBAR_SEARCH_EXITED = 'Navbar Search Exited',
NFT_ACTIVITY_SELECTED = 'NFT Activity Selected',
NFT_FILTER_OPENED = 'NFT Collection Filter Opened',
NFT_FILTER_SELECTED = 'NFT Filter Selected',
SWAP_AUTOROUTER_VISUALIZATION_EXPANDED = 'Swap Autorouter Visualization Expanded', SWAP_AUTOROUTER_VISUALIZATION_EXPANDED = 'Swap Autorouter Visualization Expanded',
SWAP_DETAILS_EXPANDED = 'Swap Details Expanded', SWAP_DETAILS_EXPANDED = 'Swap Details Expanded',
SWAP_MAX_TOKEN_AMOUNT_SELECTED = 'Swap Max Token Amount Selected', SWAP_MAX_TOKEN_AMOUNT_SELECTED = 'Swap Max Token Amount Selected',
...@@ -74,6 +77,7 @@ export enum SWAP_PRICE_UPDATE_USER_RESPONSE { ...@@ -74,6 +77,7 @@ export enum SWAP_PRICE_UPDATE_USER_RESPONSE {
* Known pages in the app. Highest order context. * Known pages in the app. Highest order context.
*/ */
export enum PageName { export enum PageName {
NFT_COLLECTION_PAGE = 'nft-collection-page',
NFT_DETAILS_PAGE = 'nft-details-page', NFT_DETAILS_PAGE = 'nft-details-page',
TOKEN_DETAILS_PAGE = 'token-details', TOKEN_DETAILS_PAGE = 'token-details',
TOKENS_PAGE = 'tokens-page', TOKENS_PAGE = 'tokens-page',
...@@ -116,6 +120,9 @@ export enum ElementName { ...@@ -116,6 +120,9 @@ export enum ElementName {
IMPORT_TOKEN_BUTTON = 'import-token-button', IMPORT_TOKEN_BUTTON = 'import-token-button',
MAX_TOKEN_AMOUNT_BUTTON = 'max-token-amount-button', MAX_TOKEN_AMOUNT_BUTTON = 'max-token-amount-button',
NAVBAR_SEARCH_INPUT = 'navbar-search-input', NAVBAR_SEARCH_INPUT = 'navbar-search-input',
NFT_ACTIVITY_TAB = 'nft-activity-tab',
NFT_FILTER_BUTTON = 'nft-filter-button',
NFT_FILTER_OPTION = 'nft-filter-option',
PRICE_UPDATE_ACCEPT_BUTTON = 'price-update-accept-button', PRICE_UPDATE_ACCEPT_BUTTON = 'price-update-accept-button',
SWAP_BUTTON = 'swap-button', SWAP_BUTTON = 'swap-button',
SWAP_DETAILS_DROPDOWN = 'swap-details-dropdown', SWAP_DETAILS_DROPDOWN = 'swap-details-dropdown',
...@@ -137,3 +144,12 @@ export enum Event { ...@@ -137,3 +144,12 @@ export enum Event {
onSelect = 'onSelect', onSelect = 'onSelect',
// alphabetize additional events. // alphabetize additional events.
} }
/**
* Known Filter Types for NFTs
*/
export enum FilterTypes {
MARKETPLACE = 'Marketplace',
PRICE_RANGE = 'Price Range',
TRAIT = 'Trait',
}
import { ElementName, Event, EventName } from 'analytics/constants'
import { TraceEvent } from 'analytics/TraceEvent'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex' import { Row } from 'nft/components/Flex'
import { useIsCollectionLoading } from 'nft/hooks' import { useIsCollectionLoading } from 'nft/hooks'
...@@ -27,13 +29,19 @@ export const ActivitySwitcher = ({ ...@@ -27,13 +29,19 @@ export const ActivitySwitcher = ({
> >
Items Items
</Box> </Box>
<Box <TraceEvent
as="button" events={[Event.onClick]}
className={!showActivity ? styles.activitySwitcherToggle : styles.selectedActivitySwitcherToggle} element={ElementName.NFT_ACTIVITY_TAB}
onClick={() => !showActivity && toggleActivity()} name={EventName.NFT_ACTIVITY_SELECTED}
> >
Activity <Box
</Box> as="button"
className={!showActivity ? styles.activitySwitcherToggle : styles.selectedActivitySwitcherToggle}
onClick={() => !showActivity && toggleActivity()}
>
Activity
</Box>
</TraceEvent>
</> </>
)} )}
</Row> </Row>
......
import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'analytics/constants'
import { TraceEvent } from 'analytics/TraceEvent'
import clsx from 'clsx' import clsx from 'clsx'
import { loadingAnimation } from 'components/Loader/styled' import { loadingAnimation } from 'components/Loader/styled'
import useDebounce from 'hooks/useDebounce' import useDebounce from 'hooks/useDebounce'
...@@ -104,6 +107,7 @@ export const LoadingButton = styled.div` ...@@ -104,6 +107,7 @@ export const LoadingButton = styled.div`
` `
export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerified }: CollectionNftsProps) => { export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerified }: CollectionNftsProps) => {
const { chainId } = useWeb3React()
const traits = useCollectionFilters((state) => state.traits) const traits = useCollectionFilters((state) => state.traits)
const minPrice = useCollectionFilters((state) => state.minPrice) const minPrice = useCollectionFilters((state) => state.minPrice)
const maxPrice = useCollectionFilters((state) => state.maxPrice) const maxPrice = useCollectionFilters((state) => state.maxPrice)
...@@ -356,12 +360,20 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie ...@@ -356,12 +360,20 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
<Box backgroundColor="backgroundFloating" width="full" style={{ backdropFilter: 'blur(24px)' }}> <Box backgroundColor="backgroundFloating" width="full" style={{ backdropFilter: 'blur(24px)' }}>
<ActionsContainer> <ActionsContainer>
<Row gap="12"> <Row gap="12">
<FilterButton <TraceEvent
isMobile={isMobile} events={[Event.onClick]}
isFiltersExpanded={isFiltersExpanded} element={ElementName.NFT_FILTER_BUTTON}
onClick={() => setFiltersExpanded(!isFiltersExpanded)} name={EventName.NFT_FILTER_OPENED}
collectionCount={collectionNfts?.[0]?.totalCount ?? 0} shouldLogImpression={!isFiltersExpanded}
/> properties={{ collection_address: contractAddress, chain_id: chainId }}
>
<FilterButton
isMobile={isMobile}
isFiltersExpanded={isFiltersExpanded}
onClick={() => setFiltersExpanded(!isFiltersExpanded)}
collectionCount={collectionNfts?.[0]?.totalCount ?? 0}
/>
</TraceEvent>
<SortDropdown dropDownOptions={sortDropDownOptions} /> <SortDropdown dropDownOptions={sortDropDownOptions} />
<CollectionSearch /> <CollectionSearch />
</Row> </Row>
......
...@@ -277,7 +277,7 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob ...@@ -277,7 +277,7 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob
) )
return ( return (
<Row gap={{ sm: '20', md: '60' }} {...props}> <Row gap={{ sm: '36', md: '60' }} {...props}>
{isCollectionStatsLoading && statsLoadingSkeleton} {isCollectionStatsLoading && statsLoadingSkeleton}
{stats.floorPrice ? ( {stats.floorPrice ? (
<StatsItem label="Global floor" isMobile={isMobile ?? false}> <StatsItem label="Global floor" isMobile={isMobile ?? false}>
......
import { sendAnalyticsEvent } from 'analytics'
import { EventName, FilterTypes } from 'analytics/constants'
import clsx from 'clsx' import clsx from 'clsx'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import * as styles from 'nft/components/collection/Filters.css' import * as styles from 'nft/components/collection/Filters.css'
...@@ -48,6 +50,7 @@ const MarketplaceItem = ({ ...@@ -48,6 +50,7 @@ const MarketplaceItem = ({
removeMarket(value) removeMarket(value)
setCheckboxSelected(false) setCheckboxSelected(false)
} }
sendAnalyticsEvent(EventName.NFT_FILTER_SELECTED, { filter_type: FilterTypes.MARKETPLACE })
} }
return ( return (
......
import 'rc-slider/assets/index.css' import 'rc-slider/assets/index.css'
import { sendAnalyticsEvent } from 'analytics'
import { EventName, FilterTypes } from 'analytics/constants'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex' import { Row } from 'nft/components/Flex'
import { NumericInput } from 'nft/components/layout/Input' import { NumericInput } from 'nft/components/layout/Input'
...@@ -54,6 +56,8 @@ export const PriceRange = () => { ...@@ -54,6 +56,8 @@ export const PriceRange = () => {
const handleBlur: FocusEventHandler<HTMLInputElement> = (e) => { const handleBlur: FocusEventHandler<HTMLInputElement> = (e) => {
e.currentTarget.placeholder = placeholderText e.currentTarget.placeholder = placeholderText
setPlaceholderText('') setPlaceholderText('')
if (minPrice || maxPrice)
sendAnalyticsEvent(EventName.NFT_FILTER_SELECTED, { filter_type: FilterTypes.PRICE_RANGE })
} }
const updateMinPriceRange = (v: FormEvent<HTMLInputElement>) => { const updateMinPriceRange = (v: FormEvent<HTMLInputElement>) => {
......
import { sendAnalyticsEvent } from 'analytics'
import { EventName, FilterTypes } from 'analytics/constants'
import useDebounce from 'hooks/useDebounce' import useDebounce from 'hooks/useDebounce'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex' import { Column, Row } from 'nft/components/Flex'
...@@ -53,6 +55,7 @@ const TraitItem = ({ ...@@ -53,6 +55,7 @@ const TraitItem = ({
removeTrait(trait) removeTrait(trait)
setCheckboxSelected(false) setCheckboxSelected(false)
} }
sendAnalyticsEvent(EventName.NFT_FILTER_SELECTED, { filter_type: FilterTypes.TRAIT })
} }
const showFullTraitName = shouldShow && trait_type === trait.trait_type && trait_value === trait.trait_value const showFullTraitName = shouldShow && trait_type === trait.trait_type && trait_value === trait.trait_value
......
import { useWeb3React } from '@web3-react/core'
import { PageName } from 'analytics/constants'
import { Trace } from 'analytics/Trace'
import { MobileHoverBag } from 'nft/components/bag/MobileHoverBag' import { MobileHoverBag } from 'nft/components/bag/MobileHoverBag'
import { AnimatedBox, Box } from 'nft/components/Box' import { AnimatedBox, Box } from 'nft/components/Box'
import { Activity, ActivitySwitcher, CollectionNfts, CollectionStats, Filters } from 'nft/components/collection' import { Activity, ActivitySwitcher, CollectionNfts, CollectionStats, Filters } from 'nft/components/collection'
...@@ -25,6 +28,7 @@ const Collection = () => { ...@@ -25,6 +28,7 @@ const Collection = () => {
const isActivityToggled = pathname.includes('/activity') const isActivityToggled = pathname.includes('/activity')
const setMarketCount = useCollectionFilters((state) => state.setMarketCount) const setMarketCount = useCollectionFilters((state) => state.setMarketCount)
const isBagExpanded = useBag((state) => state.bagExpanded) const isBagExpanded = useBag((state) => state.bagExpanded)
const { chainId } = useWeb3React()
const { data: collectionStats, isLoading } = useQuery(['collectionStats', contractAddress], () => const { data: collectionStats, isLoading } = useQuery(['collectionStats', contractAddress], () =>
CollectionStatsFetcher(contractAddress as string) CollectionStatsFetcher(contractAddress as string)
...@@ -61,75 +65,81 @@ const Collection = () => { ...@@ -61,75 +65,81 @@ const Collection = () => {
return ( return (
<> <>
<Column width="full"> <Trace
{contractAddress ? ( page={PageName.NFT_COLLECTION_PAGE}
<> properties={{ collection_address: contractAddress, chain_id: chainId }}
{' '} shouldLogImpression
<Box width="full" height="160"> >
<Column width="full">
{contractAddress ? (
<>
{' '}
<Box width="full" height="160"> <Box width="full" height="160">
{isLoading ? ( <Box width="full" height="160">
<Box height="full" width="full" className={styles.loadingBanner} /> {isLoading ? (
) : ( <Box height="full" width="full" className={styles.loadingBanner} />
<Box ) : (
as="img" <Box
height="full" as="img"
width="full" height="full"
src={collectionStats?.bannerImageUrl} width="full"
className={isLoading ? styles.loadingBanner : styles.bannerImage} src={collectionStats?.bannerImageUrl}
background="none" className={isLoading ? styles.loadingBanner : styles.bannerImage}
/> background="none"
)} />
)}
</Box>
</Box> </Box>
</Box> <Column paddingX="32">
<Column paddingX="32"> {(isLoading || collectionStats !== undefined) && (
{(isLoading || collectionStats !== undefined) && ( <CollectionStats stats={collectionStats || ({} as GenieCollection)} isMobile={isMobile} />
<CollectionStats stats={collectionStats || ({} as GenieCollection)} isMobile={isMobile} /> )}
)}
<ActivitySwitcher <ActivitySwitcher
showActivity={isActivityToggled} showActivity={isActivityToggled}
toggleActivity={() => { toggleActivity={() => {
isFiltersExpanded && setFiltersExpanded(false) isFiltersExpanded && setFiltersExpanded(false)
toggleActivity() toggleActivity()
}} }}
/> />
</Column> </Column>
<Row alignItems="flex-start" position="relative" paddingX="48"> <Row alignItems="flex-start" position="relative" paddingX="48">
<Box position="sticky" top="72" width="0"> <Box position="sticky" top="72" width="0">
{isFiltersExpanded && <Filters traits={collectionStats?.traits ?? []} />} {isFiltersExpanded && <Filters traits={collectionStats?.traits ?? []} />}
</Box> </Box>
{/* @ts-ignore: https://github.com/microsoft/TypeScript/issues/34933 */} {/* @ts-ignore: https://github.com/microsoft/TypeScript/issues/34933 */}
<AnimatedBox <AnimatedBox
style={{ style={{
transform: gridX.to((x) => `translate(${x as number}px)`), transform: gridX.to((x) => `translate(${x as number}px)`),
width: gridWidthOffset.to((x) => `calc(100% - ${x as number}px)`), width: gridWidthOffset.to((x) => `calc(100% - ${x as number}px)`),
}} }}
> >
{isActivityToggled {isActivityToggled
? contractAddress && ( ? contractAddress && (
<Activity <Activity
contractAddress={contractAddress} contractAddress={contractAddress}
rarityVerified={collectionStats?.rarityVerified ?? false} rarityVerified={collectionStats?.rarityVerified ?? false}
collectionName={collectionStats?.name ?? ''} collectionName={collectionStats?.name ?? ''}
/> />
) )
: contractAddress && : contractAddress &&
(isLoading || collectionStats !== undefined) && ( (isLoading || collectionStats !== undefined) && (
<CollectionNfts <CollectionNfts
collectionStats={collectionStats || ({} as GenieCollection)} collectionStats={collectionStats || ({} as GenieCollection)}
contractAddress={contractAddress} contractAddress={contractAddress}
rarityVerified={collectionStats?.rarityVerified} rarityVerified={collectionStats?.rarityVerified}
/> />
)} )}
</AnimatedBox> </AnimatedBox>
</Row> </Row>
</> </>
) : ( ) : (
// TODO: Put no collection asset page here // TODO: Put no collection asset page here
!isLoading && <div className={styles.noCollectionAssets}>No collection assets exist at this address</div> !isLoading && <div className={styles.noCollectionAssets}>No collection assets exist at this address</div>
)} )}
</Column> </Column>
</Trace>
<MobileHoverBag /> <MobileHoverBag />
</> </>
) )
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment