refactor: Add Collection Stats GraphQL query (#5022)
* add demo Asset Fetcher * new file * update fetcher * update query name * beginning integration type * uncomment * working mutant apes * comment out debug logging * pass in inputs to query * update collections to handle inf scroll * paginated query first attempt * wrapped assetQuery * building pagination, needs spread * working pagination * working sort * use cacheconfig * change query source in Collection page * passed in filters * fetch schema from main endpoint * delete unused relayenv * rename token_url * easy GenieAsset refactoring * add rarity * update price info * remove logging * remove redundancy * refactor usd price fetching for assets * update standard and address * remove unused cacheconfig * add gql collection query * dont repeat ethprice calc * unmemo bools * reduce duplicated usd price logic * cleanup imports * useUsd price hook * restructure Traits datatype * working traits * add new markets * resolve merge conflict * totalVolume workaround * update comment * fix for totalVolume bug * add sudoswap icon * deprecate unused vars in GenieCollection * interim rarity verified * cleanup * use forEach * add comment * undefined division * cleanup * usememo marketplace select * update % formatting * use updated prod schema * useLazyLoad * remove any cast * re-add null checks * respond to comments Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
This commit is contained in:
parent
c8f365ca31
commit
d74c05008b
6
public/nft/svgs/marketplaces/sudoswap.svg
Normal file
6
public/nft/svgs/marketplaces/sudoswap.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 14.4C0 6.4471 6.4471 0 14.4 0H33.6C41.5529 0 48 6.4471 48 14.4V33.6C48 41.5529 41.5529 48 33.6 48H14.4C6.4471 48 0 41.5529 0 33.6V14.4Z" fill="#B9B9FF"/>
|
||||
<path d="M9.29999 15H12L15.3 24L12 33H9.29999L12.6 24L9.29999 15Z" fill="#121212"/>
|
||||
<path d="M23.5727 33.192C22.1807 33.192 21.0367 32.872 20.1407 32.232C19.2607 31.592 18.8207 30.72 18.8207 29.616H21.2447C21.2447 30.112 21.4607 30.496 21.8927 30.768C22.3407 31.024 22.9167 31.152 23.6207 31.152H24.6287C25.4607 31.152 26.0767 30.992 26.4767 30.672C26.8767 30.336 27.0767 29.896 27.0767 29.352C27.0767 28.808 26.8847 28.384 26.5007 28.08C26.1167 27.776 25.5727 27.56 24.8687 27.432L23.0687 27.168C20.4447 26.752 19.1327 25.48 19.1327 23.352C19.1327 22.136 19.5327 21.208 20.3327 20.568C21.1327 19.928 22.2767 19.608 23.7647 19.608H24.6767C26.0527 19.608 27.1567 19.92 27.9887 20.544C28.8207 21.152 29.2367 21.96 29.2367 22.968H26.8127C26.8127 22.568 26.6127 22.248 26.2127 22.008C25.8287 21.768 25.3007 21.648 24.6287 21.648H23.7167C22.9807 21.648 22.4207 21.8 22.0367 22.104C21.6527 22.392 21.4607 22.808 21.4607 23.352C21.4607 24.28 22.1167 24.848 23.4287 25.056L25.2287 25.344C26.6687 25.568 27.7247 25.992 28.3967 26.616C29.0687 27.224 29.4047 28.104 29.4047 29.256C29.4047 30.488 28.9967 31.456 28.1807 32.16C27.3807 32.848 26.1807 33.192 24.5807 33.192H23.5727Z" fill="#121212"/>
|
||||
<path d="M38.7 15H36L32.7 24L36 33H38.7L35.4 24L38.7 15Z" fill="#121212"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -93,13 +93,13 @@ export const CollectionRow = ({
|
||||
<Box className={styles.primaryText}>{collection.name}</Box>
|
||||
{collection.isVerified && <VerifiedIcon className={styles.suggestionIcon} />}
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>{putCommas(collection.stats.total_supply)} items</Box>
|
||||
<Box className={styles.secondaryText}>{putCommas(collection.stats?.total_supply)} items</Box>
|
||||
</Column>
|
||||
</Row>
|
||||
{collection.floorPrice ? (
|
||||
{collection.stats?.floor_price ? (
|
||||
<Column className={styles.suggestionSecondaryContainer}>
|
||||
<Row gap="4">
|
||||
<Box className={styles.primaryText}>{ethNumberStandardFormatter(collection.floorPrice)} ETH</Box>
|
||||
<Box className={styles.primaryText}>{ethNumberStandardFormatter(collection.stats?.floor_price)} ETH</Box>
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>Floor</Box>
|
||||
</Column>
|
||||
|
@ -19,7 +19,7 @@ const nftHeaders = {
|
||||
// The issue below prevented using a custom var in metadata to gate which queries are for the nft endpoint vs base endpoint
|
||||
// This is a temporary solution before the two endpoints merge
|
||||
// https://github.com/relay-tools/relay-hooks/issues/215
|
||||
const NFT_QUERIES = ['AssetQuery', 'AssetPaginationQuery']
|
||||
const NFT_QUERIES = ['AssetQuery', 'AssetPaginationQuery', 'CollectionQuery']
|
||||
|
||||
const fetchQuery = (params: RequestParameters, variables: Variables): Promise<GraphQLResponse> => {
|
||||
const isNFT = NFT_QUERIES.includes(params.name)
|
||||
|
@ -1,9 +1,8 @@
|
||||
import graphql from 'babel-plugin-relay/macro'
|
||||
import { parseEther } from 'ethers/lib/utils'
|
||||
import { GenieAsset, Rarity } from 'nft/types'
|
||||
import { loadQuery, usePaginationFragment, usePreloadedQuery } from 'react-relay'
|
||||
import { useLazyLoadQuery, usePaginationFragment } from 'react-relay'
|
||||
|
||||
import RelayEnvironment from '../RelayEnvironment'
|
||||
import { AssetPaginationQuery } from './__generated__/AssetPaginationQuery.graphql'
|
||||
import { AssetQuery, NftAssetsFilterInput, NftAssetSortableField } from './__generated__/AssetQuery.graphql'
|
||||
|
||||
@ -117,7 +116,7 @@ export function useAssetsQuery(
|
||||
last?: number,
|
||||
before?: string
|
||||
) {
|
||||
const assetsQueryReference = loadQuery<AssetQuery>(RelayEnvironment, assetQuery, {
|
||||
const queryData = useLazyLoadQuery<AssetQuery>(assetQuery, {
|
||||
address,
|
||||
orderBy,
|
||||
asc,
|
||||
@ -127,7 +126,6 @@ export function useAssetsQuery(
|
||||
last,
|
||||
before,
|
||||
})
|
||||
const queryData = usePreloadedQuery<AssetQuery>(assetQuery, assetsQueryReference)
|
||||
const { data, hasNext, loadNext, isLoadingNext } = usePaginationFragment<AssetPaginationQuery, any>(
|
||||
assetPaginationQuery,
|
||||
queryData
|
||||
|
131
src/graphql/data/nft/Collection.ts
Normal file
131
src/graphql/data/nft/Collection.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import graphql from 'babel-plugin-relay/macro'
|
||||
import { Trait } from 'nft/hooks/useCollectionFilters'
|
||||
import { GenieCollection } from 'nft/types'
|
||||
import { useLazyLoadQuery } from 'react-relay'
|
||||
|
||||
import { CollectionQuery } from './__generated__/CollectionQuery.graphql'
|
||||
|
||||
const collectionQuery = graphql`
|
||||
query CollectionQuery($address: String!) {
|
||||
nftCollections(filter: { addresses: [$address] }) {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
bannerImage {
|
||||
url
|
||||
}
|
||||
collectionId
|
||||
description
|
||||
discordUrl
|
||||
homepageUrl
|
||||
image {
|
||||
url
|
||||
}
|
||||
instagramName
|
||||
isVerified
|
||||
name
|
||||
numAssets
|
||||
twitterName
|
||||
nftContracts {
|
||||
address
|
||||
chain
|
||||
name
|
||||
standard
|
||||
symbol
|
||||
totalSupply
|
||||
}
|
||||
traits {
|
||||
name
|
||||
values
|
||||
stats {
|
||||
name
|
||||
value
|
||||
assets
|
||||
listings
|
||||
}
|
||||
}
|
||||
markets(currencies: ETH) {
|
||||
floorPrice {
|
||||
currency
|
||||
value
|
||||
}
|
||||
owners
|
||||
totalVolume {
|
||||
value
|
||||
currency
|
||||
}
|
||||
listings {
|
||||
value
|
||||
}
|
||||
volume(duration: DAY) {
|
||||
value
|
||||
currency
|
||||
}
|
||||
volumePercentChange(duration: DAY) {
|
||||
value
|
||||
currency
|
||||
}
|
||||
floorPricePercentChange(duration: DAY) {
|
||||
value
|
||||
currency
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
endCursor
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export function useCollectionQuery(address: string): GenieCollection | undefined {
|
||||
const queryData = useLazyLoadQuery<CollectionQuery>(collectionQuery, { address })
|
||||
|
||||
const queryCollection = queryData.nftCollections?.edges[0]?.node
|
||||
const market = queryCollection?.markets && queryCollection?.markets[0]
|
||||
const traits = {} as Record<string, Trait[]>
|
||||
if (queryCollection?.traits) {
|
||||
queryCollection?.traits.forEach((trait) => {
|
||||
trait.values?.map((value) => {
|
||||
return {
|
||||
trait_type: trait.name,
|
||||
trait_value: value,
|
||||
} as Trait
|
||||
})
|
||||
})
|
||||
}
|
||||
return {
|
||||
address,
|
||||
isVerified: queryCollection?.isVerified ?? undefined,
|
||||
name: queryCollection?.name ?? undefined,
|
||||
description: queryCollection?.description ?? undefined,
|
||||
standard: queryCollection?.nftContracts ? queryCollection?.nftContracts[0]?.standard ?? undefined : undefined,
|
||||
bannerImageUrl: queryCollection?.bannerImage?.url,
|
||||
stats: queryCollection?.markets
|
||||
? {
|
||||
num_owners: market?.owners ?? undefined,
|
||||
floor_price: market?.floorPrice?.value ?? undefined,
|
||||
one_day_volume: market?.volume?.value ?? undefined,
|
||||
one_day_change: market?.volumePercentChange?.value ?? undefined,
|
||||
one_day_floor_change: market?.floorPricePercentChange?.value ?? undefined,
|
||||
banner_image_url: queryCollection?.bannerImage?.url ?? undefined,
|
||||
total_supply: queryCollection?.numAssets ?? undefined,
|
||||
total_listings: market?.listings?.value ?? undefined,
|
||||
total_volume: market?.totalVolume?.value ?? undefined,
|
||||
}
|
||||
: {},
|
||||
traits,
|
||||
// marketplaceCount: { marketplace: string; count: number }[], // TODO add when backend supports
|
||||
imageUrl: queryCollection?.image?.url,
|
||||
twitter: queryCollection?.twitterName ?? undefined,
|
||||
instagram: queryCollection?.instagramName ?? undefined,
|
||||
discordUrl: queryCollection?.discordUrl ?? undefined,
|
||||
externalUrl: queryCollection?.homepageUrl ?? undefined,
|
||||
rarityVerified: false, // TODO update when backend supports
|
||||
// isFoundation: boolean, // TODO ask backend to add
|
||||
}
|
||||
}
|
@ -169,7 +169,7 @@ const PriceTooltip = ({ price }: { price: string }) => (
|
||||
)
|
||||
|
||||
export const PriceCell = ({ marketplace, price }: { marketplace?: Markets; price?: string }) => {
|
||||
const formattedPrice = useMemo(() => (price ? putCommas(formatEthPrice(price)).toString() : null), [price])
|
||||
const formattedPrice = useMemo(() => (price ? putCommas(formatEthPrice(price))?.toString() : null), [price])
|
||||
|
||||
return (
|
||||
<Row display={{ sm: 'none', md: 'flex' }} gap="8">
|
||||
|
@ -44,7 +44,7 @@ import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import { CollectionAssetLoading } from './CollectionAssetLoading'
|
||||
import { marketPlaceItems } from './MarketplaceSelect'
|
||||
import { MARKETPLACE_ITEMS } from './MarketplaceSelect'
|
||||
import { Sweep } from './Sweep'
|
||||
import { TraitChip } from './TraitChip'
|
||||
|
||||
@ -409,9 +409,9 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
|
||||
}, [collectionStats, location])
|
||||
|
||||
useEffect(() => {
|
||||
if (collectionStats && collectionStats.floorPrice) {
|
||||
const lowValue = collectionStats.floorPrice
|
||||
const maxValue = 10 * collectionStats.floorPrice
|
||||
if (collectionStats && collectionStats.stats?.floor_price) {
|
||||
const lowValue = collectionStats.stats?.floor_price
|
||||
const maxValue = 10 * collectionStats.stats?.floor_price
|
||||
|
||||
if (priceRangeLow === '') {
|
||||
setPriceRangeLow(lowValue?.toFixed(2))
|
||||
@ -491,7 +491,7 @@ export const CollectionNfts = ({ contractAddress, collectionStats, rarityVerifie
|
||||
{markets.map((market) => (
|
||||
<TraitChip
|
||||
key={market}
|
||||
value={marketPlaceItems[market as keyof typeof marketPlaceItems]}
|
||||
value={MARKETPLACE_ITEMS[market as keyof typeof MARKETPLACE_ITEMS]}
|
||||
onClick={() => {
|
||||
scrollToTop()
|
||||
removeMarket(market)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import clsx from 'clsx'
|
||||
import { getDeltaArrow } from 'components/Tokens/TokenDetails/PriceChart'
|
||||
import { NftGraphQlVariant, useNftGraphQlFlag } from 'featureFlags/flags/nftGraphQl'
|
||||
import { Box, BoxProps } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { Marquee } from 'nft/components/layout/Marquee'
|
||||
@ -264,21 +265,27 @@ const statsLoadingSkeleton = (isMobile: boolean) =>
|
||||
)
|
||||
|
||||
const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMobile?: boolean } & BoxProps) => {
|
||||
const uniqueOwnersPercentage = stats.stats
|
||||
? roundWholePercentage((stats.stats.num_owners / stats.stats.total_supply) * 100)
|
||||
: 0
|
||||
const totalSupplyStr = stats.stats ? quantityFormatter(stats.stats.total_supply) : 0
|
||||
const isNftGraphQl = useNftGraphQlFlag() === NftGraphQlVariant.Enabled
|
||||
const uniqueOwnersPercentage =
|
||||
stats.stats && stats.stats.total_supply
|
||||
? roundWholePercentage(((stats.stats.num_owners ?? 0) / stats.stats.total_supply) * 100)
|
||||
: 0
|
||||
const totalSupplyStr = stats.stats ? quantityFormatter(stats.stats.total_supply ?? 0) : 0
|
||||
const listedPercentageStr =
|
||||
stats.stats && stats.stats.total_listings > 0
|
||||
? roundWholePercentage((stats.stats.total_listings / stats.stats.total_supply) * 100)
|
||||
stats.stats && stats.stats.total_supply
|
||||
? roundWholePercentage(((stats.stats.total_listings ?? 0) / stats.stats.total_supply) * 100)
|
||||
: 0
|
||||
const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading)
|
||||
|
||||
// round daily volume & floorPrice to 3 decimals or less
|
||||
const totalVolumeStr = volumeFormatter(stats.stats?.total_volume)
|
||||
const floorPriceStr = floorFormatter(stats.floorPrice)
|
||||
const totalVolumeStr = volumeFormatter(stats.stats?.total_volume ?? 0)
|
||||
const floorPriceStr = floorFormatter(stats.stats?.floor_price ?? 0)
|
||||
// graphQL formatted %age values out of 100, whereas v3 endpoint did a decimal between 0 & 1
|
||||
// TODO: remove feature flag gated logic when graphql migration is complete
|
||||
const floorChangeStr =
|
||||
stats.stats && stats.stats.one_day_floor_change ? Math.round(Math.abs(stats.stats.one_day_floor_change) * 100) : 0
|
||||
stats.stats && stats.stats.one_day_floor_change
|
||||
? Math.round(Math.abs(stats.stats.one_day_floor_change) * (isNftGraphQl ? 1 : 100))
|
||||
: 0
|
||||
const arrow = stats.stats && stats.stats.one_day_change ? getDeltaArrow(stats.stats.one_day_floor_change) : null
|
||||
|
||||
return (
|
||||
@ -287,23 +294,18 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob
|
||||
statsLoadingSkeleton(isMobile ?? false)
|
||||
) : (
|
||||
<>
|
||||
{stats.floorPrice ? (
|
||||
{stats.stats?.floor_price ? (
|
||||
<StatsItem label="Global floor" isMobile={isMobile ?? false}>
|
||||
{floorPriceStr} ETH
|
||||
</StatsItem>
|
||||
) : null}
|
||||
{stats.stats?.one_day_floor_change ? (
|
||||
<StatsItem label="24-Hour floor" isMobile={isMobile ?? false}>
|
||||
<StatsItem label="24-Hour Floor" isMobile={isMobile ?? false}>
|
||||
<PercentChange>
|
||||
{floorChangeStr}% {arrow}
|
||||
</PercentChange>
|
||||
</StatsItem>
|
||||
) : null}
|
||||
{stats.stats?.total_volume ? (
|
||||
<StatsItem label="Total volume" isMobile={isMobile ?? false}>
|
||||
{totalVolumeStr} ETH
|
||||
</StatsItem>
|
||||
) : null}
|
||||
{totalSupplyStr ? (
|
||||
<StatsItem label="Items" isMobile={isMobile ?? false}>
|
||||
{totalSupplyStr}
|
||||
@ -314,6 +316,11 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob
|
||||
{uniqueOwnersPercentage}%
|
||||
</StatsItem>
|
||||
) : null}
|
||||
{stats.stats?.total_volume ? (
|
||||
<StatsItem label="Total Volume" isMobile={isMobile ?? false}>
|
||||
{totalVolumeStr} ETH
|
||||
</StatsItem>
|
||||
) : null}
|
||||
{stats.stats?.total_listings && listedPercentageStr > 0 ? (
|
||||
<StatsItem label="Listed" isMobile={isMobile ?? false}>
|
||||
{listedPercentageStr}%
|
||||
@ -381,8 +388,8 @@ export const CollectionStats = ({ stats, isMobile }: { stats: GenieCollection; i
|
||||
<Box className={styles.statsText}>
|
||||
<CollectionName
|
||||
collectionStats={stats}
|
||||
name={stats.name}
|
||||
isVerified={stats.isVerified}
|
||||
name={stats.name ?? ''}
|
||||
isVerified={stats.isVerified ?? false}
|
||||
isMobile={isMobile}
|
||||
collectionSocialsIsOpen={collectionSocialsIsOpen}
|
||||
toggleCollectionSocials={toggleCollectionSocials}
|
||||
@ -390,7 +397,7 @@ export const CollectionStats = ({ stats, isMobile }: { stats: GenieCollection; i
|
||||
{!isMobile && (
|
||||
<>
|
||||
{(stats.description || isCollectionStatsLoading) && (
|
||||
<CollectionDescription description={stats.description} />
|
||||
<CollectionDescription description={stats.description ?? ''} />
|
||||
)}
|
||||
<StatsRow stats={stats} marginTop="20" />
|
||||
</>
|
||||
|
@ -8,18 +8,15 @@ import { subhead } from 'nft/css/common.css'
|
||||
import { useCollectionFilters } from 'nft/hooks'
|
||||
import { Trait } from 'nft/hooks/useCollectionFilters'
|
||||
import { TraitPosition } from 'nft/hooks/useTraitsOpen'
|
||||
import { groupBy } from 'nft/utils/groupBy'
|
||||
import { useMemo } from 'react'
|
||||
import { useReducer } from 'react'
|
||||
|
||||
import { TraitSelect } from './TraitSelect'
|
||||
|
||||
export const Filters = ({ traits }: { traits: Trait[] }) => {
|
||||
export const Filters = ({ traitsByGroup }: { traitsByGroup: Record<string, Trait[]> }) => {
|
||||
const { buyNow, setBuyNow } = useCollectionFilters((state) => ({
|
||||
buyNow: state.buyNow,
|
||||
setBuyNow: state.setBuyNow,
|
||||
}))
|
||||
const traitsByGroup: Record<string, Trait[]> = useMemo(() => (traits ? groupBy(traits, 'trait_type') : {}), [traits])
|
||||
const [buyNowHovered, toggleBuyNowHover] = useReducer((state) => !state, false)
|
||||
|
||||
const handleBuyNowToggle = () => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { EventName, FilterTypes } from 'analytics/constants'
|
||||
import clsx from 'clsx'
|
||||
import { NftGraphQlVariant, useNftGraphQlFlag } from 'featureFlags/flags/nftGraphQl'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import * as styles from 'nft/components/collection/Filters.css'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
@ -9,16 +10,18 @@ import { subheadSmall } from 'nft/css/common.css'
|
||||
import { useCollectionFilters } from 'nft/hooks/useCollectionFilters'
|
||||
import { useTraitsOpen } from 'nft/hooks/useTraitsOpen'
|
||||
import { TraitPosition } from 'nft/hooks/useTraitsOpen'
|
||||
import { FormEvent, useEffect, useReducer, useState } from 'react'
|
||||
import { FormEvent, useEffect, useMemo, useReducer, useState } from 'react'
|
||||
|
||||
import { Checkbox } from '../layout/Checkbox'
|
||||
|
||||
export const marketPlaceItems = {
|
||||
export const MARKETPLACE_ITEMS = {
|
||||
looksrare: 'LooksRare',
|
||||
nft20: 'NFT20',
|
||||
nftx: 'NFTX',
|
||||
opensea: 'OpenSea',
|
||||
x2y2: 'X2Y2',
|
||||
cryptopunks: 'LarvaLabs',
|
||||
sudoswap: 'SudoSwap',
|
||||
}
|
||||
|
||||
const MarketplaceItem = ({
|
||||
@ -84,6 +87,8 @@ const MarketplaceItem = ({
|
||||
)
|
||||
}
|
||||
|
||||
const GRAPHQL_MARKETS = ['cryptopunks', 'sudoswap']
|
||||
|
||||
export const MarketplaceSelect = () => {
|
||||
const {
|
||||
addMarket,
|
||||
@ -99,6 +104,23 @@ export const MarketplaceSelect = () => {
|
||||
|
||||
const [isOpen, setOpen] = useState(!!selectedMarkets.length)
|
||||
const setTraitsOpen = useTraitsOpen((state) => state.setTraitsOpen)
|
||||
const isNftGraphQl = useNftGraphQlFlag() === NftGraphQlVariant.Enabled
|
||||
|
||||
const MarketplaceItems = useMemo(
|
||||
() =>
|
||||
Object.entries(MARKETPLACE_ITEMS)
|
||||
.filter(([value]) => isNftGraphQl || !GRAPHQL_MARKETS.includes(value))
|
||||
.map(([value, title]) => (
|
||||
<MarketplaceItem
|
||||
key={value}
|
||||
title={title}
|
||||
value={value}
|
||||
count={marketCount?.[value] || 0}
|
||||
{...{ addMarket, removeMarket, isMarketSelected: selectedMarkets.includes(value) }}
|
||||
/>
|
||||
)),
|
||||
[addMarket, isNftGraphQl, marketCount, removeMarket, selectedMarkets]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -141,15 +163,7 @@ export const MarketplaceSelect = () => {
|
||||
</Box>
|
||||
</Box>
|
||||
<Column className={styles.filterDropDowns} paddingBottom="8" paddingLeft="0">
|
||||
{Object.entries(marketPlaceItems).map(([value, title]) => (
|
||||
<MarketplaceItem
|
||||
key={value}
|
||||
title={title}
|
||||
value={value}
|
||||
count={marketCount?.[value] || 0}
|
||||
{...{ addMarket, removeMarket, isMarketSelected: selectedMarkets.includes(value) }}
|
||||
/>
|
||||
))}
|
||||
{MarketplaceItems}
|
||||
</Column>
|
||||
</Box>
|
||||
</>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Trait } from 'nft/hooks'
|
||||
import qs from 'query-string'
|
||||
|
||||
import { badge } from '../../css/common.css'
|
||||
@ -5,12 +6,7 @@ import { Box } from '../Box'
|
||||
import { Column } from '../Flex'
|
||||
import * as styles from './Traits.css'
|
||||
|
||||
interface TraitProps {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
const Trait: React.FC<TraitProps> = ({ label, value }: TraitProps) => (
|
||||
const TraitRow: React.FC<Trait> = ({ trait_type, trait_value }: Trait) => (
|
||||
<Column backgroundColor="backgroundSurface" padding="16" gap="4" borderRadius="12">
|
||||
<Box
|
||||
as="span"
|
||||
@ -22,7 +18,7 @@ const Trait: React.FC<TraitProps> = ({ label, value }: TraitProps) => (
|
||||
style={{ textTransform: 'uppercase' }}
|
||||
maxWidth={{ sm: '120', md: '160' }}
|
||||
>
|
||||
{label}
|
||||
{trait_type}
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
@ -35,27 +31,18 @@ const Trait: React.FC<TraitProps> = ({ label, value }: TraitProps) => (
|
||||
textOverflow="ellipsis"
|
||||
maxWidth={{ sm: '120', md: '160' }}
|
||||
>
|
||||
{value}
|
||||
{trait_value}
|
||||
</Box>
|
||||
</Column>
|
||||
)
|
||||
|
||||
export const Traits = ({
|
||||
traits,
|
||||
collectionAddress,
|
||||
}: {
|
||||
traits: {
|
||||
value: string
|
||||
trait_type: string
|
||||
}[]
|
||||
collectionAddress: string
|
||||
}) => (
|
||||
export const Traits = ({ traits, collectionAddress }: { traits: Trait[]; collectionAddress: string }) => (
|
||||
<div className={styles.grid}>
|
||||
{traits.length === 0
|
||||
? 'No traits'
|
||||
: traits.map((item) => {
|
||||
const params = qs.stringify(
|
||||
{ traits: [`("${item.trait_type}","${item.value}")`] },
|
||||
{ traits: [`("${item.trait_type}","${item.trait_value}")`] },
|
||||
{
|
||||
arrayFormat: 'comma',
|
||||
}
|
||||
@ -63,11 +50,11 @@ export const Traits = ({
|
||||
|
||||
return (
|
||||
<a
|
||||
key={`${item.trait_type}-${item.value}`}
|
||||
key={`${item.trait_type}-${item.trait_value}`}
|
||||
href={`#/nfts/collection/${collectionAddress}?${params}`}
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
<Trait label={item.trait_type} value={item.value} />
|
||||
<TraitRow trait_type={item.trait_type} trait_value={item.trait_value} />
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
|
@ -66,7 +66,7 @@ const ActivityRow = ({ event, index, current }: { event: ActivityEvent; index: n
|
||||
const navigate = useNavigate()
|
||||
|
||||
const formattedPrice = useMemo(
|
||||
() => (event.price ? putCommas(formatEthPrice(event.price)).toString() : null),
|
||||
() => (event.price ? putCommas(formatEthPrice(event.price))?.toString() : null),
|
||||
[event.price]
|
||||
)
|
||||
|
||||
|
@ -145,7 +145,7 @@ export const ProfilePage = () => {
|
||||
if (ownerCollections?.length && collectionStats?.length) {
|
||||
const ownerCollectionsCopy = [...ownerCollections]
|
||||
for (const collection of ownerCollectionsCopy) {
|
||||
const floorPrice = collectionStats.find((stat) => stat.address === collection.address)?.floorPrice
|
||||
const floorPrice = collectionStats.find((stat) => stat.address === collection.address)?.stats?.floor_price
|
||||
collection.floorPrice = roundFloorPrice(floorPrice)
|
||||
}
|
||||
setWalletCollections(ownerCollectionsCopy)
|
||||
@ -156,7 +156,7 @@ export const ProfilePage = () => {
|
||||
if (ownerCollections?.length && collectionStats?.length) {
|
||||
const ownerCollectionsCopy = [...ownerCollections]
|
||||
for (const collection of ownerCollectionsCopy) {
|
||||
const floorPrice = collectionStats.find((stat) => stat.address === collection.address)?.floorPrice
|
||||
const floorPrice = collectionStats.find((stat) => stat.address === collection.address)?.stats?.floor_price //TODO update when changing walletStats endpoint to gql
|
||||
collection.floorPrice = floorPrice ? Math.round(floorPrice * 1000 + Number.EPSILON) / 1000 : 0 //round to at most 3 digits
|
||||
}
|
||||
setWalletCollections(ownerCollectionsCopy)
|
||||
|
@ -29,7 +29,7 @@ export const SortByQueries = {
|
||||
export type Trait = {
|
||||
trait_type: string
|
||||
trait_value: string
|
||||
trait_count: number
|
||||
trait_count?: number
|
||||
floorPrice?: number
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { PageName } from 'analytics/constants'
|
||||
import { Trace } from 'analytics/Trace'
|
||||
import { NftGraphQlVariant, useNftGraphQlFlag } from 'featureFlags/flags/nftGraphQl'
|
||||
import { useCollectionQuery } from 'graphql/data/nft/Collection'
|
||||
import { MobileHoverBag } from 'nft/components/bag/MobileHoverBag'
|
||||
import { AnimatedBox, Box } from 'nft/components/Box'
|
||||
import { Activity, ActivitySwitcher, CollectionNfts, CollectionStats, Filters } from 'nft/components/collection'
|
||||
@ -10,7 +12,7 @@ import { useBag, useCollectionFilters, useFiltersExpanded, useIsCollectionLoadin
|
||||
import * as styles from 'nft/pages/collection/index.css'
|
||||
import { CollectionStatsFetcher } from 'nft/queries'
|
||||
import { GenieCollection } from 'nft/types'
|
||||
import { Suspense, useEffect } from 'react'
|
||||
import { Suspense, useEffect, useMemo } from 'react'
|
||||
import { useQuery } from 'react-query'
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
import { useSpring } from 'react-spring'
|
||||
@ -42,12 +44,20 @@ const Collection = () => {
|
||||
const isActivityToggled = pathname.includes('/activity')
|
||||
const setMarketCount = useCollectionFilters((state) => state.setMarketCount)
|
||||
const isBagExpanded = useBag((state) => state.bagExpanded)
|
||||
const isNftGraphQl = useNftGraphQlFlag() === NftGraphQlVariant.Enabled
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const { data: collectionStats, isLoading } = useQuery(['collectionStats', contractAddress], () =>
|
||||
const { data: queryCollection, isLoading } = useQuery(['collectionStats', contractAddress], () =>
|
||||
CollectionStatsFetcher(contractAddress as string)
|
||||
)
|
||||
|
||||
const gqlCollection = useCollectionQuery(contractAddress as string)
|
||||
|
||||
const collectionStats = useMemo(
|
||||
() => (isNftGraphQl ? gqlCollection : queryCollection),
|
||||
[isNftGraphQl, gqlCollection, queryCollection]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setIsCollectionStatsLoading(isLoading)
|
||||
}, [isLoading, setIsCollectionStatsLoading])
|
||||
@ -119,7 +129,7 @@ const Collection = () => {
|
||||
</CollectionDescriptionSection>
|
||||
<CollectionDisplaySection>
|
||||
<Box position="sticky" top="72" width="0">
|
||||
{isFiltersExpanded && <Filters traits={collectionStats?.traits ?? []} />}
|
||||
{isFiltersExpanded && <Filters traitsByGroup={collectionStats?.traits ?? {}} />}
|
||||
</Box>
|
||||
|
||||
{/* @ts-ignore: https://github.com/microsoft/TypeScript/issues/34933 */}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { isAddress } from '@ethersproject/address'
|
||||
import { groupBy } from 'nft/utils/groupBy'
|
||||
|
||||
import { GenieCollection } from '../../types'
|
||||
|
||||
@ -55,5 +56,11 @@ export const CollectionStatsFetcher = async (addressOrName: string, recursive =
|
||||
})
|
||||
|
||||
const data = await r.json()
|
||||
return data?.data ? data.data[0] : {}
|
||||
const collections = data?.data.map((collection: Record<string, unknown>) => {
|
||||
return {
|
||||
...collection,
|
||||
traits: collection.traits && groupBy(collection.traits as unknown[], 'trait_type'),
|
||||
}
|
||||
})
|
||||
return collections[0]
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Trait } from 'nft/hooks/useCollectionFilters'
|
||||
|
||||
import { SellOrder } from '../sell'
|
||||
|
||||
export interface OpenSeaCollection {
|
||||
@ -97,49 +99,30 @@ export interface GenieAsset {
|
||||
owner: string
|
||||
creator: OpenSeaUser
|
||||
metadataUrl: string
|
||||
traits?: {
|
||||
trait_type: string
|
||||
value: string
|
||||
display_type?: any
|
||||
max_value?: any
|
||||
trait_count: number
|
||||
order?: any
|
||||
}[]
|
||||
traits?: Trait[]
|
||||
}
|
||||
|
||||
export interface GenieCollection {
|
||||
collectionAddress: string
|
||||
address: string
|
||||
indexingStatus: string
|
||||
isVerified: boolean
|
||||
name: string
|
||||
description: string
|
||||
standard: string
|
||||
isVerified?: boolean
|
||||
name?: string
|
||||
description?: string
|
||||
standard?: string
|
||||
bannerImageUrl?: string
|
||||
floorPrice: number
|
||||
stats: {
|
||||
num_owners: number
|
||||
floor_price: number
|
||||
one_day_volume: number
|
||||
one_day_change: number
|
||||
one_day_floor_change: number
|
||||
banner_image_url: string
|
||||
total_supply: number
|
||||
total_listings: number
|
||||
total_volume: number
|
||||
stats?: {
|
||||
num_owners?: number
|
||||
floor_price?: number
|
||||
one_day_volume?: number
|
||||
one_day_change?: number
|
||||
one_day_floor_change?: number
|
||||
banner_image_url?: string
|
||||
total_supply?: number
|
||||
total_listings?: number
|
||||
total_volume?: number
|
||||
}
|
||||
symbol: string
|
||||
traits: {
|
||||
trait_type: string
|
||||
trait_value: string
|
||||
trait_count: number
|
||||
floorSellOrder: PriceInfo
|
||||
floorPrice: number
|
||||
}[]
|
||||
numTraitsByAmount: { traitCount: number; numWithTrait: number }[]
|
||||
indexingStats: { openSea: { successfulExecutionDate: string; lastRequestedAt: string } }
|
||||
traits?: Record<string, Trait[]>
|
||||
marketplaceCount?: { marketplace: string; count: number }[]
|
||||
imageUrl: string
|
||||
imageUrl?: string
|
||||
twitter?: string
|
||||
instagram?: string
|
||||
discordUrl?: string
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const putCommas = (value: number) => {
|
||||
export const putCommas = (value?: number) => {
|
||||
try {
|
||||
if (!value) return value
|
||||
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||
|
@ -100,9 +100,9 @@ const urlParamsUtils = {
|
||||
clonedQuery['traits'] = clonedQuery['traits'].map((queryTrait: string) => {
|
||||
const modifiedTrait = trimTraitStr(queryTrait.replace(/(")/g, ''))
|
||||
const [trait_type, trait_value] = modifiedTrait.split(',')
|
||||
const traitInStats = collectionStats.traits.find(
|
||||
(item) => item.trait_type === trait_type && item.trait_value === trait_value
|
||||
)
|
||||
const traitInStats =
|
||||
collectionStats.traits &&
|
||||
collectionStats.traits[trait_type].find((trait) => trait.trait_value === trait_value)
|
||||
|
||||
/*
|
||||
For most cases, `traitInStats` is assigned. In case the trait
|
||||
|
Loading…
Reference in New Issue
Block a user