feat: add marketplace and trait counts to assets query (#5137)

* working total count

* trait counts

* marketplace counts

* carousel card

* undo count refactor

* Filter styles

* remove any cast and handle 0
This commit is contained in:
Charles Bachmeier 2022-11-09 10:44:42 -05:00 committed by GitHub
parent ed7f126bd0
commit 1893d258b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 58 additions and 35 deletions

@ -2,12 +2,13 @@ import graphql from 'babel-plugin-relay/macro'
import { parseEther } from 'ethers/lib/utils' import { parseEther } from 'ethers/lib/utils'
import useInterval from 'lib/hooks/useInterval' import useInterval from 'lib/hooks/useInterval'
import ms from 'ms.macro' import ms from 'ms.macro'
import { GenieAsset, Rarity, SellOrder } from 'nft/types' import { GenieAsset } from 'nft/types'
import { useCallback, useMemo, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
import { fetchQuery, useLazyLoadQuery, usePaginationFragment, useRelayEnvironment } from 'react-relay' import { fetchQuery, useLazyLoadQuery, usePaginationFragment, useRelayEnvironment } from 'react-relay'
import { AssetPaginationQuery } from './__generated__/AssetPaginationQuery.graphql' import { AssetPaginationQuery } from './__generated__/AssetPaginationQuery.graphql'
import { AssetQuery, NftAssetsFilterInput, NftAssetSortableField } from './__generated__/AssetQuery.graphql' import { AssetQuery, NftAssetsFilterInput, NftAssetSortableField } from './__generated__/AssetQuery.graphql'
import { AssetQuery_nftAssets$data } from './__generated__/AssetQuery_nftAssets.graphql'
const assetPaginationQuery = graphql` const assetPaginationQuery = graphql`
fragment AssetQuery_nftAssets on Query @refetchable(queryName: "AssetPaginationQuery") { fragment AssetQuery_nftAssets on Query @refetchable(queryName: "AssetPaginationQuery") {
@ -91,6 +92,7 @@ const assetPaginationQuery = graphql`
metadataUrl metadataUrl
} }
} }
totalCount
} }
} }
` `
@ -110,6 +112,10 @@ const assetQuery = graphql`
} }
` `
type NftAssetsQueryAsset = NonNullable<
NonNullable<NonNullable<AssetQuery_nftAssets$data['nftAssets']>['edges']>[number]
>
export function useAssetsQuery( export function useAssetsQuery(
address: string, address: string,
orderBy: NftAssetSortableField, orderBy: NftAssetSortableField,
@ -152,14 +158,14 @@ export function useAssetsQuery(
// It is especially important for this to be memoized to avoid re-rendering from polling if data is unchanged. // It is especially important for this to be memoized to avoid re-rendering from polling if data is unchanged.
const assets: GenieAsset[] = useMemo( const assets: GenieAsset[] = useMemo(
() => () =>
data.nftAssets?.edges?.map((queryAsset: { node: any }) => { data.nftAssets?.edges?.map((queryAsset: NftAssetsQueryAsset) => {
const asset = queryAsset.node const asset = queryAsset.node
const ethPrice = parseEther( const ethPrice = parseEther(
asset.listings?.edges[0]?.node.price.value?.toLocaleString('fullwide', { useGrouping: false }) ?? '0' asset.listings?.edges[0]?.node.price.value?.toLocaleString('fullwide', { useGrouping: false }) ?? '0'
).toString() ).toString()
return { return {
id: asset.id, id: asset.id,
address: asset.collection?.nftContracts[0]?.address, address: asset?.collection?.nftContracts?.[0]?.address,
notForSale: asset.listings?.edges?.length === 0, notForSale: asset.listings?.edges?.length === 0,
collectionName: asset.collection?.name, collectionName: asset.collection?.name,
collectionSymbol: asset.collection?.image?.url, collectionSymbol: asset.collection?.image?.url,
@ -176,7 +182,7 @@ export function useAssetsQuery(
} }
: undefined, : undefined,
susFlag: asset.suspiciousFlag, susFlag: asset.suspiciousFlag,
sellorders: asset.listings?.edges.map((listingNode: { node: SellOrder }) => { sellorders: asset.listings?.edges.map((listingNode) => {
return { return {
...listingNode.node, ...listingNode.node,
protocolParameters: listingNode.node?.protocolParameters protocolParameters: listingNode.node?.protocolParameters
@ -186,12 +192,12 @@ export function useAssetsQuery(
}), }),
smallImageUrl: asset.smallImage?.url, smallImageUrl: asset.smallImage?.url,
tokenId: asset.tokenId, tokenId: asset.tokenId,
tokenType: asset.collection?.nftContracts[0]?.standard, tokenType: asset.collection?.nftContracts?.[0]?.standard,
// totalCount?: number, // TODO waiting for BE changes totalCount: data.nftAssets?.totalCount,
collectionIsVerified: asset.collection?.isVerified, collectionIsVerified: asset.collection?.isVerified,
rarity: { rarity: {
primaryProvider: 'Rarity Sniper', // TODO update when backend adds more providers primaryProvider: 'Rarity Sniper', // TODO update when backend adds more providers
providers: asset.rarities?.map((rarity: Rarity) => { providers: asset.rarities?.map((rarity) => {
return { return {
...rarity, ...rarity,
provider: 'Rarity Sniper', provider: 'Rarity Sniper',
@ -206,7 +212,7 @@ export function useAssetsQuery(
metadataUrl: asset.metadataUrl, metadataUrl: asset.metadataUrl,
} }
}), }),
[data.nftAssets?.edges] [data.nftAssets?.edges, data.nftAssets?.totalCount]
) )
return { assets, hasNext, isLoadingNext, loadNext } return { assets, hasNext, isLoadingNext, loadNext }

@ -68,6 +68,11 @@ const collectionQuery = graphql`
value value
currency currency
} }
marketplaces {
marketplace
listings
floorPrice
}
} }
} }
} }
@ -89,11 +94,12 @@ export function useCollectionQuery(address: string): GenieCollection | undefined
const traits = {} as Record<string, Trait[]> const traits = {} as Record<string, Trait[]>
if (queryCollection?.traits) { if (queryCollection?.traits) {
queryCollection?.traits.forEach((trait) => { queryCollection?.traits.forEach((trait) => {
if (trait.name && trait.values) { if (trait.name && trait.stats) {
traits[trait.name] = trait.values.map((value) => { traits[trait.name] = trait.stats.map((stats) => {
return { return {
trait_type: trait.name, trait_type: stats.name,
trait_value: value, trait_value: stats.value,
trait_count: stats.assets,
} as Trait } as Trait
}) })
} }
@ -120,7 +126,15 @@ export function useCollectionQuery(address: string): GenieCollection | undefined
} }
: {}, : {},
traits, traits,
// marketplaceCount: { marketplace: string; count: number }[], // TODO add when backend supports marketplaceCount: queryCollection?.markets
? market?.marketplaces?.map((market) => {
return {
marketplace: market.marketplace?.toLowerCase() ?? '',
count: market.listings ?? 0,
floorPrice: market.floorPrice ?? 0,
}
})
: undefined,
imageUrl: queryCollection?.image?.url ?? '', imageUrl: queryCollection?.image?.url ?? '',
twitterUrl: queryCollection?.twitterName ?? '', twitterUrl: queryCollection?.twitterName ?? '',
instagram: queryCollection?.instagramName ?? undefined, instagram: queryCollection?.instagramName ?? undefined,

@ -498,6 +498,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
<FilterButton <FilterButton
isMobile={isMobile} isMobile={isMobile}
isFiltersExpanded={isFiltersExpanded} isFiltersExpanded={isFiltersExpanded}
collectionCount={collectionNfts?.[0]?.totalCount ?? 0}
onClick={() => setFiltersExpanded(!isFiltersExpanded)} onClick={() => setFiltersExpanded(!isFiltersExpanded)}
/> />
</TraceEvent> </TraceEvent>

@ -4,15 +4,18 @@ import * as styles from 'nft/components/collection/FilterButton.css'
import { FilterIcon } from 'nft/components/icons' import { FilterIcon } from 'nft/components/icons'
import { buttonTextMedium } from 'nft/css/common.css' import { buttonTextMedium } from 'nft/css/common.css'
import { useIsCollectionLoading } from 'nft/hooks' import { useIsCollectionLoading } from 'nft/hooks'
import { putCommas } from 'nft/utils'
export const FilterButton = ({ export const FilterButton = ({
onClick, onClick,
isMobile, isMobile,
isFiltersExpanded, isFiltersExpanded,
collectionCount = 0,
}: { }: {
isMobile: boolean isMobile: boolean
isFiltersExpanded: boolean isFiltersExpanded: boolean
onClick: () => void onClick: () => void
collectionCount?: number
}) => { }) => {
const isCollectionNftsLoading = useIsCollectionLoading((state) => state.isCollectionNftsLoading) const isCollectionNftsLoading = useIsCollectionLoading((state) => state.isCollectionNftsLoading)
@ -37,7 +40,11 @@ export const FilterButton = ({
color="textPrimary" color="textPrimary"
> >
<FilterIcon /> <FilterIcon />
{!isMobile && !isFiltersExpanded && <Box className={buttonTextMedium}> Filter</Box>} {!isMobile ? (
<>
{!isFiltersExpanded && <Box className={buttonTextMedium}> Filter {putCommas(collectionCount)} results</Box>}
</>
) : null}
</Box> </Box>
) )
} }

@ -1,6 +1,7 @@
import { useIsMobile } from 'nft/hooks' import { useIsMobile } from 'nft/hooks'
import { fetchTrendingCollections } from 'nft/queries' import { fetchTrendingCollections } from 'nft/queries'
import { TimePeriod } from 'nft/types' import { TimePeriod } from 'nft/types'
import { Suspense } from 'react'
import { useQuery } from 'react-query' import { useQuery } from 'react-query'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
@ -88,11 +89,13 @@ const Banner = () => {
{collections ? ( {collections ? (
<Carousel> <Carousel>
{collections.map((collection) => ( {collections.map((collection) => (
<CarouselCard <Suspense fallback={<LoadingCarouselCard />} key={collection.address}>
key={collection.address} <CarouselCard
collection={collection} key={collection.address}
onClick={() => navigate(`/nfts/collection/${collection.address}`)} collection={collection}
/> onClick={() => navigate(`/nfts/collection/${collection.address}`)}
/>
</Suspense>
))} ))}
</Carousel> </Carousel>
) : ( ) : (

@ -1,10 +1,9 @@
import { loadingAnimation } from 'components/Loader/styled' import { loadingAnimation } from 'components/Loader/styled'
import { LoadingBubble } from 'components/Tokens/loading' import { LoadingBubble } from 'components/Tokens/loading'
import { useCollectionQuery } from 'graphql/data/nft/Collection'
import { VerifiedIcon } from 'nft/components/icons' import { VerifiedIcon } from 'nft/components/icons'
import { CollectionStatsFetcher } from 'nft/queries'
import { Markets, TrendingCollection } from 'nft/types' import { Markets, TrendingCollection } from 'nft/types'
import { formatWeiToDecimal } from 'nft/utils' import { formatWeiToDecimal } from 'nft/utils'
import { useQuery } from 'react-query'
import styled, { useTheme } from 'styled-components/macro' import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
@ -192,15 +191,7 @@ const MARKETS_ENUM_TO_NAME = {
} }
export const CarouselCard = ({ collection, onClick }: CarouselCardProps) => { export const CarouselCard = ({ collection, onClick }: CarouselCardProps) => {
const { data: collectionStats, isLoading } = useQuery( const gqlCollection = useCollectionQuery(collection.address)
['trendingCollectionStats', collection.address],
() => CollectionStatsFetcher(collection.address),
{
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
}
)
const theme = useTheme() const theme = useTheme()
@ -225,22 +216,23 @@ export const CarouselCard = ({ collection, onClick }: CarouselCardProps) => {
<HeaderOverlay /> <HeaderOverlay />
</CardHeaderContainer> </CardHeaderContainer>
<CardBottomContainer> <CardBottomContainer>
{isLoading || !collectionStats ? ( {!gqlCollection ? (
<LoadingTable /> <LoadingTable />
) : ( ) : (
<> <>
<HeaderRow>Uniswap</HeaderRow> <HeaderRow>Uniswap</HeaderRow>
<HeaderRow>{formatWeiToDecimal(collection.floor.toString())} ETH Floor</HeaderRow> <HeaderRow>{formatWeiToDecimal(collection.floor.toString())} ETH Floor</HeaderRow>
<HeaderRow>{collectionStats.marketplaceCount?.reduce((acc, cur) => acc + cur.count, 0)} Listings</HeaderRow> <HeaderRow>{gqlCollection.marketplaceCount?.reduce((acc, cur) => acc + cur.count, 0)} Listings</HeaderRow>
{MARKETS_TO_CHECK.map((market) => { {MARKETS_TO_CHECK.map((market) => {
const marketplace = collectionStats.marketplaceCount?.find( const marketplace = gqlCollection.marketplaceCount?.find(
(marketplace) => marketplace.marketplace === market (marketplace) => marketplace.marketplace === market
) )
return ( return (
<MarketplaceRow <MarketplaceRow
key={'trendingCollection' + collection.address} key={'trendingCollection' + collection.address}
marketplace={MARKETS_ENUM_TO_NAME[market]} marketplace={MARKETS_ENUM_TO_NAME[market]}
listings={marketplace?.count.toString()} listings={marketplace?.count?.toString()}
floor={marketplace?.floorPrice?.toString()}
/> />
) )
})} })}

@ -125,7 +125,7 @@ export interface GenieCollection {
total_volume?: number total_volume?: number
} }
traits?: Record<string, Trait[]> traits?: Record<string, Trait[]>
marketplaceCount?: { marketplace: string; count: number }[] marketplaceCount?: { marketplace: string; count: number; floorPrice: number }[]
imageUrl: string imageUrl: string
twitterUrl?: string twitterUrl?: string
instagram?: string instagram?: string