feat: Log NFT Sell events (#5106)
* Log profile page view * Log sell flow started * Add Start Listing event * Add constant for list modal + useTrace * Log sell item added * Log listing completed * Fix usd_value property * Move log to startListingFlow * Use Set to remove duplicate marketplaces * Move listing completed event
This commit is contained in:
parent
dbf5c63ece
commit
48d5955185
@ -24,6 +24,11 @@ export enum EventName {
|
||||
NFT_BUY_BAG_SUCCEEDED = 'NFT Buy Bag Succeeded',
|
||||
NFT_FILTER_OPENED = 'NFT Collection Filter Opened',
|
||||
NFT_FILTER_SELECTED = 'NFT Filter Selected',
|
||||
NFT_LISTING_SIGNED = 'NFT Listing Signed',
|
||||
NFT_LISTING_COMPLETED = 'NFT Listing Success',
|
||||
NFT_SELL_ITEM_ADDED = 'NFT Sell Item Added',
|
||||
NFT_SELL_SELECTED = 'NFT Sell Selected',
|
||||
NFT_SELL_START_LISTING = 'NFT Sell Start Listing',
|
||||
NFT_TRENDING_ROW_SELECTED = 'Trending Row Selected',
|
||||
SWAP_AUTOROUTER_VISUALIZATION_EXPANDED = 'Swap Autorouter Visualization Expanded',
|
||||
SWAP_DETAILS_EXPANDED = 'Swap Details Expanded',
|
||||
@ -88,6 +93,7 @@ export enum PageName {
|
||||
NFT_COLLECTION_PAGE = 'nft-collection-page',
|
||||
NFT_DETAILS_PAGE = 'nft-details-page',
|
||||
NFT_EXPLORE_PAGE = 'nft-explore-page',
|
||||
NFT_PROFILE_PAGE = 'nft-profile-page',
|
||||
TOKEN_DETAILS_PAGE = 'token-details',
|
||||
TOKENS_PAGE = 'tokens-page',
|
||||
POOL_PAGE = 'pool-page',
|
||||
@ -112,6 +118,7 @@ export enum SectionName {
|
||||
/** Known modals for analytics purposes. */
|
||||
export enum ModalName {
|
||||
CONFIRM_SWAP = 'confirm-swap-modal',
|
||||
NFT_LISTING = 'nft-listing-modal',
|
||||
NFT_TX_COMPLETE = 'nft-tx-complete-modal',
|
||||
TOKEN_SELECTOR = 'token-selector-modal',
|
||||
// alphabetize additional modal names.
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { addressesByNetwork, SupportedChainId } from '@looksrare/sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { EventName, ModalName } from 'analytics/constants'
|
||||
import { Trace } from 'analytics/Trace'
|
||||
import { useTrace } from 'analytics/Trace'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { ChevronLeftIcon, XMarkIcon } from 'nft/components/icons'
|
||||
@ -8,6 +12,7 @@ import { themeVars } from 'nft/css/sprinkles.css'
|
||||
import { useBag, useIsMobile, useNFTList, useSellAsset } from 'nft/hooks'
|
||||
import { logListing, looksRareNonceFetcher } from 'nft/queries'
|
||||
import { AssetRow, CollectionRow, ListingRow, ListingStatus } from 'nft/types'
|
||||
import { fetchPrice } from 'nft/utils/fetchPrice'
|
||||
import { pluralize } from 'nft/utils/roundAndPluralize'
|
||||
import { Dispatch, useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
@ -34,6 +39,7 @@ const ListingModal = () => {
|
||||
const toggleCart = useBag((state) => state.toggleBag)
|
||||
const looksRareNonceRef = useRef(looksRareNonce)
|
||||
const isMobile = useIsMobile()
|
||||
const trace = useTrace({ modal: ModalName.NFT_LISTING })
|
||||
|
||||
useEffect(() => {
|
||||
useNFTList.subscribe((state) => (looksRareNonceRef.current = state.looksRareNonce))
|
||||
@ -41,6 +47,29 @@ const ListingModal = () => {
|
||||
|
||||
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
|
||||
|
||||
const [ethPriceInUSD, setEthPriceInUSD] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
fetchPrice().then((price) => {
|
||||
setEthPriceInUSD(price || 0)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const startListingEventProperties = {
|
||||
collection_addresses: sellAssets.map((asset) => asset.asset_contract.address),
|
||||
token_ids: sellAssets.map((asset) => asset.tokenId),
|
||||
marketplaces: Array.from(new Set(listings.map((asset) => asset.marketplace.name))),
|
||||
list_quantity: listings.length,
|
||||
usd_value: ethPriceInUSD * totalEthListingValue,
|
||||
...trace,
|
||||
}
|
||||
|
||||
const approvalEventProperties = {
|
||||
list_quantity: listings.length,
|
||||
usd_value: ethPriceInUSD * totalEthListingValue,
|
||||
...trace,
|
||||
}
|
||||
|
||||
// when all collections have been approved, auto start the signing process
|
||||
useEffect(() => {
|
||||
collectionsRequiringApproval?.length &&
|
||||
@ -60,6 +89,7 @@ const ListingModal = () => {
|
||||
|
||||
const startListingFlow = async () => {
|
||||
if (!signer) return
|
||||
sendAnalyticsEvent(EventName.NFT_SELL_START_LISTING, { ...startListingEventProperties })
|
||||
setListingStatus(ListingStatus.SIGNING)
|
||||
const addresses = addressesByNetwork[SupportedChainId.MAINNET]
|
||||
const signerAddress = await signer.getAddress()
|
||||
@ -111,6 +141,11 @@ const ListingModal = () => {
|
||||
} else if (!paused) {
|
||||
setListingStatus(ListingStatus.FAILED)
|
||||
}
|
||||
sendAnalyticsEvent(EventName.NFT_LISTING_COMPLETED, {
|
||||
signatures_requested: listings.length,
|
||||
signatures_approved: listings.filter((asset) => asset.status === ListingStatus.APPROVED),
|
||||
...approvalEventProperties,
|
||||
})
|
||||
await logListing(listings, (await signer?.getAddress()) ?? '')
|
||||
}
|
||||
|
||||
@ -144,93 +179,100 @@ const ListingModal = () => {
|
||||
const showSuccessScreen = useMemo(() => listingStatus === ListingStatus.APPROVED, [listingStatus])
|
||||
|
||||
return (
|
||||
<Column paddingTop="20" paddingBottom="20" paddingLeft="12" paddingRight="12">
|
||||
<Row className={headlineSmall} marginBottom="10">
|
||||
{isMobile && !showSuccessScreen && (
|
||||
<Box paddingTop="4" marginRight="4" onClick={toggleCart}>
|
||||
<ChevronLeftIcon height={28} width={28} />
|
||||
<Trace modal={ModalName.NFT_LISTING}>
|
||||
<Column paddingTop="20" paddingBottom="20" paddingLeft="12" paddingRight="12">
|
||||
<Row className={headlineSmall} marginBottom="10">
|
||||
{isMobile && !showSuccessScreen && (
|
||||
<Box paddingTop="4" marginRight="4" onClick={toggleCart}>
|
||||
<ChevronLeftIcon height={28} width={28} />
|
||||
</Box>
|
||||
)}
|
||||
{showSuccessScreen ? 'Success!' : `Listing ${sellAssets.length} NFTs`}
|
||||
<Box
|
||||
as="button"
|
||||
border="none"
|
||||
color="textSecondary"
|
||||
backgroundColor="backgroundSurface"
|
||||
marginLeft="auto"
|
||||
marginRight="0"
|
||||
paddingRight="0"
|
||||
display={{ sm: 'flex', md: 'none' }}
|
||||
cursor="pointer"
|
||||
onClick={toggleCart}
|
||||
>
|
||||
<XMarkIcon height={28} width={28} fill={themeVars.colors.textPrimary} />
|
||||
</Box>
|
||||
)}
|
||||
{showSuccessScreen ? 'Success!' : `Listing ${sellAssets.length} NFTs`}
|
||||
<Box
|
||||
as="button"
|
||||
border="none"
|
||||
color="textSecondary"
|
||||
backgroundColor="backgroundSurface"
|
||||
marginLeft="auto"
|
||||
marginRight="0"
|
||||
paddingRight="0"
|
||||
display={{ sm: 'flex', md: 'none' }}
|
||||
cursor="pointer"
|
||||
onClick={toggleCart}
|
||||
>
|
||||
<XMarkIcon height={28} width={28} fill={themeVars.colors.textPrimary} />
|
||||
</Box>
|
||||
</Row>
|
||||
<Column overflowX="hidden" overflowY="auto" style={{ maxHeight: '60vh' }}>
|
||||
</Row>
|
||||
<Column overflowX="hidden" overflowY="auto" style={{ maxHeight: '60vh' }}>
|
||||
{showSuccessScreen ? (
|
||||
<Trace
|
||||
name={EventName.NFT_LISTING_COMPLETED}
|
||||
properties={{ list_quantity: listings.length, usd_value: ethPriceInUSD * totalEthListingValue, ...trace }}
|
||||
>
|
||||
<ListingSection
|
||||
sectionTitle={`Listed ${listings.length} item${pluralize(listings.length)} for sale`}
|
||||
rows={listings}
|
||||
index={0}
|
||||
openIndex={openIndex}
|
||||
isSuccessScreen={true}
|
||||
/>
|
||||
</Trace>
|
||||
) : (
|
||||
<>
|
||||
<ListingSection
|
||||
sectionTitle={`Approve ${collectionsRequiringApproval.length} collection${pluralize(
|
||||
collectionsRequiringApproval.length
|
||||
)}`}
|
||||
title="COLLECTIONS"
|
||||
rows={collectionsRequiringApproval}
|
||||
index={1}
|
||||
openIndex={openIndex}
|
||||
/>
|
||||
<ListingSection
|
||||
sectionTitle={`Confirm ${listings.length} listing${pluralize(listings.length)}`}
|
||||
caption="Now you can sign to list each item"
|
||||
title="NFTS"
|
||||
rows={listings}
|
||||
index={2}
|
||||
openIndex={openIndex}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Column>
|
||||
<hr className={styles.sectionDivider} />
|
||||
<Row className={subhead} marginTop="12" marginBottom={showSuccessScreen ? '8' : '20'}>
|
||||
Return if sold
|
||||
<Row className={subheadSmall} marginLeft="auto" marginRight="0">
|
||||
{totalEthListingValue}
|
||||
ETH
|
||||
</Row>
|
||||
</Row>
|
||||
{showSuccessScreen ? (
|
||||
<ListingSection
|
||||
sectionTitle={`Listed ${listings.length} item${pluralize(listings.length)} for sale`}
|
||||
rows={listings}
|
||||
index={0}
|
||||
openIndex={openIndex}
|
||||
isSuccessScreen={true}
|
||||
/>
|
||||
<Box as="span" className={caption} color="textSecondary">
|
||||
Status:{' '}
|
||||
<Box as="span" color="green200">
|
||||
Confirmed
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
<ListingSection
|
||||
sectionTitle={`Approve ${collectionsRequiringApproval.length} collection${pluralize(
|
||||
collectionsRequiringApproval.length
|
||||
)}`}
|
||||
title="COLLECTIONS"
|
||||
rows={collectionsRequiringApproval}
|
||||
index={1}
|
||||
openIndex={openIndex}
|
||||
/>
|
||||
<ListingSection
|
||||
sectionTitle={`Confirm ${listings.length} listing${pluralize(listings.length)}`}
|
||||
caption="Now you can sign to list each item"
|
||||
title="NFTS"
|
||||
rows={listings}
|
||||
index={2}
|
||||
openIndex={openIndex}
|
||||
/>
|
||||
</>
|
||||
<ListingButton onClick={clickStartListingFlow} buttonText={'Start listing'} showWarningOverride={isMobile} />
|
||||
)}
|
||||
{(listingStatus === ListingStatus.PENDING || listingStatus === ListingStatus.SIGNING) && (
|
||||
<Box
|
||||
as="button"
|
||||
border="none"
|
||||
backgroundColor="backgroundSurface"
|
||||
cursor="pointer"
|
||||
color="orange"
|
||||
className={styles.button}
|
||||
onClick={clickStopListing}
|
||||
type="button"
|
||||
>
|
||||
Stop listing
|
||||
</Box>
|
||||
)}
|
||||
</Column>
|
||||
<hr className={styles.sectionDivider} />
|
||||
<Row className={subhead} marginTop="12" marginBottom={showSuccessScreen ? '8' : '20'}>
|
||||
Return if sold
|
||||
<Row className={subheadSmall} marginLeft="auto" marginRight="0">
|
||||
{totalEthListingValue}
|
||||
ETH
|
||||
</Row>
|
||||
</Row>
|
||||
{showSuccessScreen ? (
|
||||
<Box as="span" className={caption} color="textSecondary">
|
||||
Status:{' '}
|
||||
<Box as="span" color="green200">
|
||||
Confirmed
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<ListingButton onClick={clickStartListingFlow} buttonText={'Start listing'} showWarningOverride={isMobile} />
|
||||
)}
|
||||
{(listingStatus === ListingStatus.PENDING || listingStatus === ListingStatus.SIGNING) && (
|
||||
<Box
|
||||
as="button"
|
||||
border="none"
|
||||
backgroundColor="backgroundSurface"
|
||||
cursor="pointer"
|
||||
color="orange"
|
||||
className={styles.button}
|
||||
onClick={clickStopListing}
|
||||
type="button"
|
||||
>
|
||||
Stop listing
|
||||
</Box>
|
||||
)}
|
||||
</Column>
|
||||
</Trace>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import { useNftBalanceQuery } from 'graphql/data/nft/NftBalance'
|
||||
import { AnimatedBox, Box } from 'nft/components/Box'
|
||||
import { assetList } from 'nft/components/collection/CollectionNfts.css'
|
||||
@ -137,10 +139,16 @@ export const ProfilePage = () => {
|
||||
/>
|
||||
<Row gap="8" flexWrap="nowrap">
|
||||
{isSellMode && <SelectAllButton ownerAssets={ownerAssets ?? []} />}
|
||||
<SellModeButton className={buttonTextMedium} active={isSellMode} onClick={handleSellModeClick}>
|
||||
<TagIcon height={20} width={20} />
|
||||
Sell
|
||||
</SellModeButton>
|
||||
<TraceEvent
|
||||
events={[Event.onClick]}
|
||||
name={EventName.NFT_SELL_SELECTED}
|
||||
shouldLogImpression={!isSellMode}
|
||||
>
|
||||
<SellModeButton className={buttonTextMedium} active={isSellMode} onClick={handleSellModeClick}>
|
||||
<TagIcon height={20} width={20} />
|
||||
Sell
|
||||
</SellModeButton>
|
||||
</TraceEvent>
|
||||
</Row>
|
||||
</Row>
|
||||
<Row>
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { EventName } from 'analytics/constants'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { VerifiedIcon } from 'nft/components/icons'
|
||||
@ -33,6 +35,11 @@ export const WalletAssetDisplay = ({ asset, isSellMode }: { asset: WalletAsset;
|
||||
|
||||
const handleSelect = () => {
|
||||
isSelected ? removeSellAsset(asset) : selectSellAsset(asset)
|
||||
!isSelected &&
|
||||
sendAnalyticsEvent(EventName.NFT_SELL_ITEM_ADDED, {
|
||||
collection_address: asset.asset_contract.address,
|
||||
token_id: asset.tokenId,
|
||||
})
|
||||
if (
|
||||
!cartExpanded &&
|
||||
!sellAssets.find(
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { PageName } from 'analytics/constants'
|
||||
import { Trace } from 'analytics/Trace'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Center, Column, Row } from 'nft/components/Flex'
|
||||
import { ChevronLeftIcon, XMarkIcon } from 'nft/components/icons'
|
||||
@ -45,42 +47,44 @@ const Profile = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className={styles.mobileSellWrapper}>
|
||||
{/* <Head> TODO: figure out metadata tagging
|
||||
<Trace page={PageName.NFT_PROFILE_PAGE} shouldLogImpression>
|
||||
<Box className={styles.mobileSellWrapper}>
|
||||
{/* <Head> TODO: figure out metadata tagging
|
||||
<title>Genie | Sell</title>
|
||||
</Head> */}
|
||||
<Row className={styles.mobileSellHeader}>
|
||||
{sellPageState === ProfilePageStateType.LISTING && (
|
||||
<Box marginRight="4" onClick={() => setSellPageState(ProfilePageStateType.VIEWING)}>
|
||||
<ChevronLeftIcon height={28} width={28} />
|
||||
<Row className={styles.mobileSellHeader}>
|
||||
{sellPageState === ProfilePageStateType.LISTING && (
|
||||
<Box marginRight="4" onClick={() => setSellPageState(ProfilePageStateType.VIEWING)}>
|
||||
<ChevronLeftIcon height={28} width={28} />
|
||||
</Box>
|
||||
)}
|
||||
<Box className={headlineSmall} paddingBottom="4" style={{ lineHeight: '28px' }}>
|
||||
{sellPageState === ProfilePageStateType.VIEWING ? 'Select NFTs' : 'Create Listing'}
|
||||
</Box>
|
||||
<Box cursor="pointer" marginLeft="auto" marginRight="0" onClick={exitSellFlow}>
|
||||
<XMarkIcon height={28} width={28} fill={themeVars.colors.textPrimary} />
|
||||
</Box>
|
||||
</Row>
|
||||
{account != null ? (
|
||||
<Box style={{ width: `calc(100% - ${cartExpanded ? SHOPPING_BAG_WIDTH : 0}px)` }}>
|
||||
{sellPageState === ProfilePageStateType.VIEWING ? <ProfilePage /> : <ListPage />}
|
||||
</Box>
|
||||
) : (
|
||||
<Column as="section" gap="60" className={styles.section}>
|
||||
<div style={{ minHeight: '70vh' }}>
|
||||
<Center className={styles.notConnected} flexDirection="column">
|
||||
<Box as="span" className={headlineMedium} color="textSecondary" marginBottom="24" display="block">
|
||||
No items to display
|
||||
</Box>
|
||||
<Box as="button" className={buttonMedium} onClick={toggleWalletModal}>
|
||||
Connect Wallet
|
||||
</Box>
|
||||
</Center>
|
||||
</div>
|
||||
</Column>
|
||||
)}
|
||||
<Box className={headlineSmall} paddingBottom="4" style={{ lineHeight: '28px' }}>
|
||||
{sellPageState === ProfilePageStateType.VIEWING ? 'Select NFTs' : 'Create Listing'}
|
||||
</Box>
|
||||
<Box cursor="pointer" marginLeft="auto" marginRight="0" onClick={exitSellFlow}>
|
||||
<XMarkIcon height={28} width={28} fill={themeVars.colors.textPrimary} />
|
||||
</Box>
|
||||
</Row>
|
||||
{account != null ? (
|
||||
<Box style={{ width: `calc(100% - ${cartExpanded ? SHOPPING_BAG_WIDTH : 0}px)` }}>
|
||||
{sellPageState === ProfilePageStateType.VIEWING ? <ProfilePage /> : <ListPage />}
|
||||
</Box>
|
||||
) : (
|
||||
<Column as="section" gap="60" className={styles.section}>
|
||||
<div style={{ minHeight: '70vh' }}>
|
||||
<Center className={styles.notConnected} flexDirection="column">
|
||||
<Box as="span" className={headlineMedium} color="textSecondary" marginBottom="24" display="block">
|
||||
No items to display
|
||||
</Box>
|
||||
<Box as="button" className={buttonMedium} onClick={toggleWalletModal}>
|
||||
Connect Wallet
|
||||
</Box>
|
||||
</Center>
|
||||
</div>
|
||||
</Column>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Trace>
|
||||
)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user