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:
parent
ed7f126bd0
commit
1893d258b5
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user