Compare commits

...

5 Commits

Author SHA1 Message Date
Charles Bachmeier
e8c689e1d4 feat: Migrate trending nfts endpoint to graphql (#6049)
* defined useTrendingCollections hook and made fields optional

* working trending collections table

* add gql file

* add gql to search suggestions and handle floor wei vs eth

* gql carousel

* fontWeight typo and skip if flag disabled

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-03-01 16:18:17 -08:00
cartcrom
70cd7272a1 fix: hide missing % changes/prices (#6046) 2023-03-01 10:23:06 -05:00
Jack Short
2b5769ac86 chore: removing VE from cards (#6038)
* moving cards to styled components

* converting rows to styled components

* image

* finished images

* handling all media

* removed VE from cards

* no content colors

* removing aspect ratio calc

* responding to all comments
2023-02-28 16:49:51 -05:00
Charles Bachmeier
b811afd134 feat: add new statsig backed feature flag for nft graphql migration (#6044)
* NFT-1113 add new statsig backed feature flag for nft graphql migration

* remove comment

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-02-28 13:38:10 -08:00
Charles Bachmeier
6131e6bfab chore: Cleanup and refactor significant portion of listing logic (#6042)
* NFT-91 move listing mode to shared

* move expiration setting out of useEffect

* simplify price input color logic

* unused boolean

* simplify removal of local listing market

* handle global same pricing

* undo local market change

* added comment

* undo expiration changes

* remove old listing state logic

* formatting

* small cleanup

* cleanup

* deprecate global listing state

* use stablecoin values

* remove unused pausing functionality

* remove unused pausing functionality

* remove unused warning logic

* remove unused royalty field

* use royalty helper fn

* simplify global vs normal price input

* simplified price setting logic

* price inputs need to respond to global price method changes

* slight simplifcations

* move dynamic price logic to hook

* move utils file

* move enum to shared

* only usdc check

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-02-28 11:47:22 -08:00
30 changed files with 900 additions and 852 deletions

View File

@@ -1,5 +1,6 @@
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
import { GqlRoutingVariant, useGqlRoutingFlag } from 'featureFlags/flags/gqlRouting'
import { NftGraphqlVariant, useNftGraphqlFlag } from 'featureFlags/flags/nftlGraphql'
import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken'
import { Permit2Variant, usePermit2Flag } from 'featureFlags/flags/permit2'
import { SwapWidgetVariant, useSwapWidgetFlag } from 'featureFlags/flags/swapWidget'
@@ -229,6 +230,12 @@ export default function FeatureFlagModal() {
featureFlag={FeatureFlag.gqlRouting}
label="GraphQL NFT Routing"
/>
<FeatureFlagOption
variant={NftGraphqlVariant}
value={useNftGraphqlFlag()}
featureFlag={FeatureFlag.nftGraphql}
label="Migrate NFT read endpoints to GQL"
/>
<FeatureFlagGroup name="Debug">
<FeatureFlagOption
variant={TraceJsonRpcVariant}

View File

@@ -2,7 +2,9 @@ import { Trans } from '@lingui/macro'
import { useTrace } from '@uniswap/analytics'
import { InterfaceSectionName, NavBarSearchTypes } from '@uniswap/analytics-events'
import { useWeb3React } from '@web3-react/core'
import { SafetyLevel } from 'graphql/data/__generated__/types-and-hooks'
import { useNftGraphqlEnabled } from 'featureFlags/flags/nftlGraphql'
import { HistoryDuration, SafetyLevel } from 'graphql/data/__generated__/types-and-hooks'
import { useTrendingCollections } from 'graphql/data/nft/TrendingCollections'
import { SearchToken } from 'graphql/data/SearchTokens'
import useTrendingTokens from 'graphql/data/TrendingTokens'
import { useIsNftPage } from 'hooks/useIsNftPage'
@@ -56,7 +58,7 @@ const SearchBarDropdownSection = ({
</Row>
<Column gap="12">
{suggestions.map((suggestion, index) =>
isLoading ? (
isLoading || !suggestion ? (
<SkeletonRow key={index} />
) : isCollection(suggestion) ? (
<CollectionRow
@@ -123,6 +125,7 @@ export const SearchBarDropdown = ({
const { pathname } = useLocation()
const isNFTPage = useIsNftPage()
const isNftGraphqlEnabled = useNftGraphqlEnabled()
const isTokenPage = pathname.includes('/tokens')
const [resultsState, setResultsState] = useState<ReactNode>()
@@ -131,24 +134,25 @@ export const SearchBarDropdown = ({
() => fetchTrendingCollections({ volumeType: 'eth', timePeriod: 'ONE_DAY' as TimePeriod, size: 3 })
)
const trendingCollections = useMemo(
() =>
trendingCollectionResults
? trendingCollectionResults
.map((collection) => ({
...collection,
collectionAddress: collection.address,
floorPrice: formatEthPrice(collection.floor?.toString()),
stats: {
total_supply: collection.totalSupply,
one_day_change: collection.floorChange,
floor_price: formatEthPrice(collection.floor?.toString()),
},
}))
.slice(0, isNFTPage ? 3 : 2)
: [...Array<GenieCollection>(isNFTPage ? 3 : 2)],
[isNFTPage, trendingCollectionResults]
)
const { data: gqlData, loading } = useTrendingCollections(3, HistoryDuration.Day)
const trendingCollections = useMemo(() => {
const gatedTrendingCollections = isNftGraphqlEnabled ? gqlData : trendingCollectionResults
return gatedTrendingCollections && (!isNftGraphqlEnabled || !loading)
? gatedTrendingCollections
.map((collection) => ({
...collection,
collectionAddress: collection.address,
floorPrice: isNftGraphqlEnabled ? collection.floor : formatEthPrice(collection.floor?.toString()),
stats: {
total_supply: collection.totalSupply,
one_day_change: collection.floorChange,
floor_price: isNftGraphqlEnabled ? collection.floor : formatEthPrice(collection.floor?.toString()),
},
}))
.slice(0, isNFTPage ? 3 : 2)
: [...Array<GenieCollection>(isNFTPage ? 3 : 2)]
}, [gqlData, isNFTPage, isNftGraphqlEnabled, loading, trendingCollectionResults])
const { data: trendingTokenData } = useTrendingTokens(useWeb3React().chainId)

View File

@@ -18,8 +18,9 @@ import { putCommas } from 'nft/utils/putCommas'
import { useCallback, useEffect, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { getDeltaArrow } from '../Tokens/TokenDetails/PriceChart'
import { DeltaText, getDeltaArrow } from '../Tokens/TokenDetails/PriceChart'
import { useAddRecentlySearchedAsset } from './RecentlySearchedAssets'
import * as styles from './SearchBar.css'
@@ -28,12 +29,6 @@ const PriceChangeContainer = styled.div`
align-items: center;
`
const PriceChangeText = styled.span<{ isNegative: boolean }>`
font-size: 14px;
line-height: 20px;
color: ${({ theme, isNegative }) => (isNegative ? theme.accentFailure : theme.accentSuccess)};
`
const ArrowCell = styled.span`
padding-top: 5px;
padding-right: 3px;
@@ -191,18 +186,20 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
</Row>
<Column className={styles.suggestionSecondaryContainer}>
{token.market?.price?.value && (
<Row gap="4">
<Box className={styles.primaryText}>{formatUSDPrice(token.market.price.value)}</Box>
</Row>
)}
{token.market?.pricePercentChange?.value && (
<PriceChangeContainer>
<ArrowCell>{arrow}</ArrowCell>
<PriceChangeText isNegative={token.market.pricePercentChange.value < 0}>
{Math.abs(token.market.pricePercentChange.value).toFixed(2)}%
</PriceChangeText>
</PriceChangeContainer>
{!!token.market?.price?.value && (
<>
<Row gap="4">
<Box className={styles.primaryText}>{formatUSDPrice(token.market.price.value)}</Box>
</Row>
<PriceChangeContainer>
<ArrowCell>{arrow}</ArrowCell>
<ThemedText.BodySmall>
<DeltaText delta={token.market?.pricePercentChange?.value}>
{Math.abs(token.market?.pricePercentChange?.value ?? 0).toFixed(2)}%
</DeltaText>
</ThemedText.BodySmall>
</PriceChangeContainer>
</>
)}
</Column>
</Link>

View File

@@ -8,4 +8,5 @@ export enum FeatureFlag {
swapWidget = 'swap_widget_replacement_enabled',
gqlRouting = 'gqlRouting',
statsigDummy = 'web_dummy_gate_amplitude_id',
nftGraphql = 'nft_graphql_migration',
}

View File

@@ -0,0 +1,11 @@
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useNftGraphqlFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.nftGraphql)
}
export function useNftGraphqlEnabled(): boolean {
return useNftGraphqlFlag() === BaseVariant.Enabled
}
export { BaseVariant as NftGraphqlVariant }

View File

@@ -675,9 +675,11 @@ export type QueryNftAssetsArgs = {
asc?: InputMaybe<Scalars['Boolean']>;
before?: InputMaybe<Scalars['String']>;
chain?: InputMaybe<Chain>;
cursor?: InputMaybe<Scalars['String']>;
filter?: InputMaybe<NftAssetsFilterInput>;
first?: InputMaybe<Scalars['Int']>;
last?: InputMaybe<Scalars['Int']>;
limit?: InputMaybe<Scalars['Int']>;
orderBy?: InputMaybe<NftAssetSortableField>;
};
@@ -1119,6 +1121,14 @@ export type NftRouteQueryVariables = Exact<{
export type NftRouteQuery = { __typename?: 'Query', nftRoute?: { __typename?: 'NftRouteResponse', id: string, calldata: string, toAddress: string, route?: Array<{ __typename?: 'NftTrade', amount: number, contractAddress: string, id: string, marketplace: NftMarketplace, tokenId: string, tokenType: NftStandard, price: { __typename?: 'TokenAmount', id: string, currency: Currency, value: string }, quotePrice?: { __typename?: 'TokenAmount', id: string, currency: Currency, value: string } }>, sendAmount: { __typename?: 'TokenAmount', id: string, currency: Currency, value: string } } };
export type TrendingCollectionsQueryVariables = Exact<{
size?: InputMaybe<Scalars['Int']>;
timePeriod?: InputMaybe<HistoryDuration>;
}>;
export type TrendingCollectionsQuery = { __typename?: 'Query', topCollections?: { __typename?: 'NftCollectionConnection', edges: Array<{ __typename?: 'NftCollectionEdge', node: { __typename?: 'NftCollection', name?: string, isVerified?: boolean, nftContracts?: Array<{ __typename?: 'NftContract', address: string, totalSupply?: number }>, image?: { __typename?: 'Image', url: string }, bannerImage?: { __typename?: 'Image', url: string }, markets?: Array<{ __typename?: 'NftCollectionMarket', owners?: number, floorPrice?: { __typename?: 'TimestampedAmount', value: number }, totalVolume?: { __typename?: 'TimestampedAmount', value: number }, volume?: { __typename?: 'TimestampedAmount', value: number }, volumePercentChange?: { __typename?: 'TimestampedAmount', value: number }, floorPricePercentChange?: { __typename?: 'TimestampedAmount', value: number }, sales?: { __typename?: 'TimestampedAmount', value: number }, listings?: { __typename?: 'TimestampedAmount', value: number } }> } }> } };
export const RecentlySearchedAssetsDocument = gql`
query RecentlySearchedAssets($collectionAddresses: [String!]!, $contracts: [ContractInput!]!) {
@@ -2117,4 +2127,82 @@ export function useNftRouteLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<N
}
export type NftRouteQueryHookResult = ReturnType<typeof useNftRouteQuery>;
export type NftRouteLazyQueryHookResult = ReturnType<typeof useNftRouteLazyQuery>;
export type NftRouteQueryResult = Apollo.QueryResult<NftRouteQuery, NftRouteQueryVariables>;
export type NftRouteQueryResult = Apollo.QueryResult<NftRouteQuery, NftRouteQueryVariables>;
export const TrendingCollectionsDocument = gql`
query TrendingCollections($size: Int, $timePeriod: HistoryDuration) {
topCollections(limit: $size, duration: $timePeriod) {
edges {
node {
name
nftContracts {
address
totalSupply
}
image {
url
}
bannerImage {
url
}
isVerified
markets(currencies: ETH) {
floorPrice {
value
}
owners
totalVolume {
value
}
volume(duration: $timePeriod) {
value
}
volumePercentChange(duration: $timePeriod) {
value
}
floorPricePercentChange(duration: $timePeriod) {
value
}
sales {
value
}
totalVolume {
value
}
listings {
value
}
}
}
}
}
}
`;
/**
* __useTrendingCollectionsQuery__
*
* To run a query within a React component, call `useTrendingCollectionsQuery` and pass it any options that fit your needs.
* When your component renders, `useTrendingCollectionsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useTrendingCollectionsQuery({
* variables: {
* size: // value for 'size'
* timePeriod: // value for 'timePeriod'
* },
* });
*/
export function useTrendingCollectionsQuery(baseOptions?: Apollo.QueryHookOptions<TrendingCollectionsQuery, TrendingCollectionsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<TrendingCollectionsQuery, TrendingCollectionsQueryVariables>(TrendingCollectionsDocument, options);
}
export function useTrendingCollectionsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<TrendingCollectionsQuery, TrendingCollectionsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<TrendingCollectionsQuery, TrendingCollectionsQueryVariables>(TrendingCollectionsDocument, options);
}
export type TrendingCollectionsQueryHookResult = ReturnType<typeof useTrendingCollectionsQuery>;
export type TrendingCollectionsLazyQueryHookResult = ReturnType<typeof useTrendingCollectionsLazyQuery>;
export type TrendingCollectionsQueryResult = Apollo.QueryResult<TrendingCollectionsQuery, TrendingCollectionsQueryVariables>;

View File

@@ -0,0 +1,95 @@
import { useNftGraphqlEnabled } from 'featureFlags/flags/nftlGraphql'
import gql from 'graphql-tag'
import { TrendingCollection } from 'nft/types'
import { useMemo } from 'react'
import { HistoryDuration, useTrendingCollectionsQuery } from '../__generated__/types-and-hooks'
gql`
query TrendingCollections($size: Int, $timePeriod: HistoryDuration) {
topCollections(limit: $size, duration: $timePeriod) {
edges {
node {
name
nftContracts {
address
totalSupply
}
image {
url
}
bannerImage {
url
}
isVerified
markets(currencies: ETH) {
floorPrice {
value
}
owners
totalVolume {
value
}
volume(duration: $timePeriod) {
value
}
volumePercentChange(duration: $timePeriod) {
value
}
floorPricePercentChange(duration: $timePeriod) {
value
}
sales {
value
}
listings {
value
}
}
}
}
}
}
`
export function useTrendingCollections(size: number, timePeriod: HistoryDuration) {
const isNftGraphqlEnabled = useNftGraphqlEnabled()
const { data, loading, error } = useTrendingCollectionsQuery({
variables: {
size,
timePeriod,
},
skip: !isNftGraphqlEnabled,
})
const trendingCollections: TrendingCollection[] | undefined = useMemo(
() =>
data?.topCollections?.edges?.map((edge) => {
const collection = edge?.node
return {
name: collection.name,
address: collection.nftContracts?.[0].address,
imageUrl: collection.image?.url,
bannerImageUrl: collection.bannerImage?.url,
isVerified: collection.isVerified,
volume: collection.markets?.[0].volume?.value,
volumeChange: collection.markets?.[0].volumePercentChange?.value,
floor: collection.markets?.[0].floorPrice?.value,
floorChange: collection.markets?.[0].floorPricePercentChange?.value,
marketCap: collection.markets?.[0].totalVolume?.value,
percentListed:
(collection.markets?.[0].listings?.value ?? 0) / (collection.nftContracts?.[0].totalSupply ?? 1),
owners: collection.markets?.[0].owners,
sales: collection.markets?.[0].sales?.value,
totalSupply: collection.nftContracts?.[0].totalSupply,
}
}),
[data?.topCollections?.edges]
)
return {
data: trendingCollections,
loading,
error,
}
}

View File

@@ -1,114 +0,0 @@
import { style } from '@vanilla-extract/css'
import { calc } from '@vanilla-extract/css-utils'
import { sprinkles, themeVars, vars } from 'nft/css/sprinkles.css'
export const card = style([
sprinkles({
overflow: 'hidden',
paddingBottom: '12',
borderRadius: '16',
}),
{
boxSizing: 'border-box',
WebkitBoxSizing: 'border-box',
boxShadow: vars.color.cardDropShadow,
backgroundColor: themeVars.colors.backgroundSurface,
':after': {
content: '',
position: 'absolute',
top: '0px',
right: ' 0px',
bottom: ' 0px',
left: '0px',
border: ' 1px solid',
borderRadius: '16px',
borderColor: '#5D678524',
pointerEvents: 'none',
},
},
])
export const loadingBackground = style({
background: `linear-gradient(270deg, ${themeVars.colors.backgroundOutline} 0%, ${themeVars.colors.backgroundSurface} 100%)`,
})
export const cardImageHover = style({
transform: 'scale(1.15)',
})
export const selectedCard = style([
card,
sprinkles({
background: 'backgroundSurface',
}),
{
':after': {
border: '2px solid',
borderColor: vars.color.accentAction,
},
},
])
export const button = style([
sprinkles({
display: 'flex',
width: 'full',
position: 'relative',
paddingY: '8',
marginTop: { sm: '8', md: '10' },
marginBottom: '12',
borderRadius: '12',
border: 'none',
justifyContent: 'center',
transition: '250',
cursor: 'pointer',
}),
{
lineHeight: '16px',
},
])
export const marketplaceIcon = style([
sprinkles({
display: 'inline-block',
width: '16',
height: '16',
borderRadius: '4',
flexShrink: '0',
marginLeft: '8',
}),
{
verticalAlign: 'top',
},
])
export const rarityInfo = style([
sprinkles({
display: 'flex',
borderRadius: '4',
height: '16',
color: 'textPrimary',
background: 'backgroundInteractive',
fontSize: '10',
fontWeight: 'semibold',
paddingX: '4',
}),
{
lineHeight: '12px',
letterSpacing: '0.04em',
backdropFilter: 'blur(6px)',
},
])
export const playbackSwitch = style([
sprinkles({
position: 'absolute',
width: '40',
height: '40',
zIndex: '1',
}),
{
marginLeft: calc.subtract('100%', '50px'),
transform: 'translateY(-56px)',
},
])

View File

@@ -1,10 +1,10 @@
import { BigNumber } from '@ethersproject/bignumber'
import clsx from 'clsx'
import { t, Trans } from '@lingui/macro'
import Column from 'components/Column'
import { OpacityHoverState } from 'components/Common'
import Row from 'components/Row'
import { MouseoverTooltip } from 'components/Tooltip'
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex'
import {
MinusIconLarge,
PauseButtonIcon,
@@ -14,8 +14,6 @@ import {
RarityVerifiedIcon,
VerifiedIcon,
} from 'nft/components/icons'
import { body, bodySmall, buttonTextMedium, subhead } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css'
import { useIsMobile } from 'nft/hooks'
import { GenieAsset, Rarity, UniformAspectRatio, UniformAspectRatios, WalletAsset } from 'nft/types'
import { fallbackProvider, isAudio, isVideo, putCommas } from 'nft/utils'
@@ -32,11 +30,11 @@ import {
useRef,
useState,
} from 'react'
import { AlertTriangle } from 'react-feather'
import { AlertTriangle, Pause, Play } from 'react-feather'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import * as styles from './Card.css'
import { colors } from 'theme/colors'
import { opacify } from 'theme/utils'
/* -------- ASSET CONTEXT -------- */
export interface CardContextProps {
@@ -166,6 +164,31 @@ const StyledImageContainer = styled.div<{ isDisabled?: boolean }>`
cursor: ${({ isDisabled }) => (isDisabled ? 'default' : 'pointer')};
`
const CardContainer = styled.div<{ selected: boolean }>`
position: relative;
border-radius: ${BORDER_RADIUS}px;
background-color: ${({ theme }) => theme.backgroundSurface};
overflow: hidden;
padding-bottom: 12px;
border-radius: 16px;
box-shadow: rgba(0, 0, 0, 10%) 0px 4px 12px;
box-sizing: border-box;
-webkit-box-sizing: border-box;
:after {
content: '';
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
border: ${({ selected }) => (selected ? '2px' : '1px')} solid;
border-radius: 16px;
border-color: ${({ theme, selected }) => (selected ? theme.accentAction : opacify(12, colors.gray500))};
pointer-events: none;
}
`
/* -------- ASSET CARD -------- */
interface CardProps {
asset: GenieAsset | WalletAsset
@@ -220,19 +243,16 @@ const Container = ({
return (
<CardContext.Provider value={providerValue}>
<Box
position="relative"
<CardContainer
selected={selected}
ref={assetRef}
borderRadius={BORDER_RADIUS}
className={selected ? styles.selectedCard : styles.card}
draggable={false}
onMouseEnter={toggleHover}
onMouseLeave={toggleHover}
transition="250"
onClick={isDisabled ? () => null : onClick ?? handleAssetInBag}
>
{children}
</Box>
</CardContainer>
</CardContext.Provider>
)
}
@@ -277,6 +297,13 @@ function getHeightFromAspectRatio(uniformAspectRatio: UniformAspectRatio, render
: renderedHeight
}
function getMediaAspectRatio(
uniformAspectRatio?: UniformAspectRatio,
setUniformAspectRatio?: (uniformAspectRatio: UniformAspectRatio) => void
): string {
return uniformAspectRatio === UniformAspectRatios.square || !setUniformAspectRatio ? '1' : 'auto'
}
interface ImageProps {
uniformAspectRatio?: UniformAspectRatio
setUniformAspectRatio?: (uniformAspectRatio: UniformAspectRatio) => void
@@ -284,6 +311,29 @@ interface ImageProps {
setRenderedHeight?: (renderedHeight: number | undefined) => void
}
const StyledMediaContainer = styled(Row)`
overflow: hidden;
border-top-left-radius: ${BORDER_RADIUS}px;
border-top-right-radius: ${BORDER_RADIUS}px;
`
const StyledImage = styled.img<{
hovered: boolean
imageLoading: boolean
$aspectRatio?: string
$hidden?: boolean
}>`
width: 100%;
aspect-ratio: ${({ $aspectRatio }) => $aspectRatio};
transition: ${({ theme }) => `${theme.transition.duration.medium} ${theme.transition.timing.ease} transform`};
will-change: transform;
object-fit: contain;
visibility: ${({ $hidden }) => ($hidden ? 'hidden' : 'visible')};
transform: ${({ hovered }) => hovered && 'scale(1.15)'};
background: ${({ theme, imageLoading }) =>
imageLoading && `linear-gradient(270deg, ${theme.backgroundOutline} 0%, ${theme.backgroundSurface} 100%)`};
`
const Image = ({
uniformAspectRatio = UniformAspectRatios.square,
setUniformAspectRatio,
@@ -300,40 +350,50 @@ const Image = ({
}
return (
<Box display="flex" overflow="hidden" borderTopLeftRadius={BORDER_RADIUS} borderTopRightRadius={BORDER_RADIUS}>
<Box
as="img"
width="full"
style={{
aspectRatio: `${uniformAspectRatio === UniformAspectRatios.square || !setUniformAspectRatio ? '1' : 'auto'}`,
transition: 'transform 0.25s ease 0s',
}}
<StyledMediaContainer>
<StyledImage
src={asset.imageUrl || asset.smallImageUrl}
objectFit="contain"
$aspectRatio={getMediaAspectRatio(uniformAspectRatio, setUniformAspectRatio)}
hovered={hovered && !isMobile}
imageLoading={!loaded}
draggable={false}
onError={() => setNoContent(true)}
onLoad={(e) => {
handleUniformAspectRatio(uniformAspectRatio, e, setUniformAspectRatio, renderedHeight, setRenderedHeight)
setLoaded(true)
}}
className={clsx(hovered && !isMobile && styles.cardImageHover, !loaded && styles.loadingBackground)}
/>
</Box>
</StyledMediaContainer>
)
}
function getMediaAspectRatio(
uniformAspectRatio: UniformAspectRatio,
setUniformAspectRatio?: (uniformAspectRatio: UniformAspectRatio) => void
): string {
return uniformAspectRatio === UniformAspectRatios.square || !setUniformAspectRatio ? '1' : 'auto'
}
interface MediaProps {
shouldPlay: boolean
setCurrentTokenPlayingMedia: (tokenId: string | undefined) => void
}
const PlaybackButton = styled.div`
position: absolute;
height: 40px;
width: 40px;
z-index: 1;
margin-left: calc(100% - 50px);
transform: translateY(-56px);
`
const StyledVideo = styled.video<{
$aspectRatio?: string
}>`
width: 100%;
aspect-ratio: ${({ $aspectRatio }) => $aspectRatio};
`
const StyledInnerMediaContainer = styled(Row)`
position: absolute;
left: 0px;
top: 0px;
`
const Video = ({
uniformAspectRatio = UniformAspectRatios.square,
setUniformAspectRatio,
@@ -360,52 +420,38 @@ const Video = ({
return (
<>
<Box display="flex" overflow="hidden">
<Box
as="img"
alt={asset.name || asset.tokenId}
width="full"
style={{
aspectRatio: getMediaAspectRatio(uniformAspectRatio, setUniformAspectRatio),
transition: 'transform 0.25s ease 0s',
willChange: 'transform',
}}
<StyledMediaContainer>
<StyledImage
src={asset.imageUrl || asset.smallImageUrl}
objectFit="contain"
alt={asset.name || asset.tokenId}
$aspectRatio={getMediaAspectRatio(uniformAspectRatio, setUniformAspectRatio)}
hovered={hovered && !isMobile}
imageLoading={!imageLoaded}
draggable={false}
onError={() => setNoContent(true)}
onLoad={(e) => {
handleUniformAspectRatio(uniformAspectRatio, e, setUniformAspectRatio, renderedHeight, setRenderedHeight)
setImageLoaded(true)
}}
visibility={shouldPlay ? 'hidden' : 'visible'}
className={clsx(hovered && !isMobile && styles.cardImageHover, !imageLoaded && styles.loadingBackground)}
$hidden={shouldPlay}
/>
</Box>
</StyledMediaContainer>
{shouldPlay ? (
<>
<Box className={styles.playbackSwitch}>
<PauseButtonIcon
width="100%"
height="100%"
<PlaybackButton>
<Pause
size="24px"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setCurrentTokenPlayingMedia(undefined)
}}
className="playback-icon"
/>
</Box>
<Box position="absolute" left="0" top="0" display="flex">
<Box
as="video"
</PlaybackButton>
<StyledInnerMediaContainer>
<StyledVideo
$aspectRatio={getMediaAspectRatio(uniformAspectRatio, setUniformAspectRatio)}
ref={vidRef}
width="full"
style={{
aspectRatio: `${
uniformAspectRatio === UniformAspectRatios.square || !setUniformAspectRatio ? '1' : 'auto'
}`,
}}
onEnded={(e) => {
e.preventDefault()
setCurrentTokenPlayingMedia(undefined)
@@ -414,29 +460,32 @@ const Video = ({
playsInline
>
<source src={asset.animationUrl} />
</Box>
</Box>
</StyledVideo>
</StyledInnerMediaContainer>
</>
) : (
<Box className={styles.playbackSwitch}>
<PlaybackButton>
{((!isMobile && hovered) || isMobile) && (
<PlayButtonIcon
width="100%"
height="100%"
<Play
size="24px"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setCurrentTokenPlayingMedia(asset.tokenId)
}}
className="playback-icon"
/>
)}
</Box>
</PlaybackButton>
)}
</>
)
}
const StyledAudio = styled.audio`
width: 100%;
height: 100%;
`
const Audio = ({
uniformAspectRatio = UniformAspectRatios.square,
setUniformAspectRatio,
@@ -463,29 +512,25 @@ const Audio = ({
return (
<>
<Box display="flex" overflow="hidden">
<Box
as="img"
alt={asset.name || asset.tokenId}
width="full"
style={{
aspectRatio: getMediaAspectRatio(uniformAspectRatio, setUniformAspectRatio),
transition: 'transform 0.4s ease 0s',
}}
<StyledMediaContainer>
<StyledImage
src={asset.imageUrl || asset.smallImageUrl}
objectFit="contain"
alt={asset.name || asset.tokenId}
$aspectRatio={getMediaAspectRatio(uniformAspectRatio, setUniformAspectRatio)}
hovered={hovered && !isMobile}
imageLoading={!imageLoaded}
draggable={false}
onError={() => setNoContent(true)}
onLoad={(e) => {
handleUniformAspectRatio(uniformAspectRatio, e, setUniformAspectRatio, renderedHeight, setRenderedHeight)
setImageLoaded(true)
setImageLoaded(true)
}}
className={clsx(hovered && !isMobile && styles.cardImageHover, !imageLoaded && styles.loadingBackground)}
/>
</Box>
</StyledMediaContainer>
{shouldPlay ? (
<>
<Box className={styles.playbackSwitch}>
<PlaybackButton>
<PauseButtonIcon
width="100%"
height="100%"
@@ -494,26 +539,22 @@ const Audio = ({
e.stopPropagation()
setCurrentTokenPlayingMedia(undefined)
}}
className="playback-icon"
/>
</Box>
<Box position="absolute" left="0" top="0" display="flex">
<Box
as="audio"
</PlaybackButton>
<StyledInnerMediaContainer>
<StyledAudio
ref={audRef}
width="full"
height="full"
onEnded={(e) => {
e.preventDefault()
setCurrentTokenPlayingMedia(undefined)
}}
>
<source src={asset.animationUrl} />
</Box>
</Box>
</StyledAudio>
</StyledInnerMediaContainer>
</>
) : (
<Box className={styles.playbackSwitch}>
<PlaybackButton>
{((!isMobile && hovered) || isMobile) && (
<PlayButtonIcon
width="100%"
@@ -523,10 +564,9 @@ const Audio = ({
e.stopPropagation()
setCurrentTokenPlayingMedia(asset.tokenId)
}}
className="playback-icon"
/>
)}
</Box>
</PlaybackButton>
)}
</>
)
@@ -537,36 +577,39 @@ interface CardDetailsContainerProps {
children: ReactNode
}
const StyledDetailsContainer = styled(Column)`
position: relative;
padding: 12px 12px 0px;
justify-content: space-between;
transition: ${({ theme }) => `${theme.transition.duration.medium}`};
`
const DetailsContainer = ({ children }: CardDetailsContainerProps) => {
return (
<Row
position="relative"
paddingX="12"
paddingTop="12"
justifyContent="space-between"
flexDirection="column"
transition="250"
>
{children}
</Row>
)
return <StyledDetailsContainer>{children}</StyledDetailsContainer>
}
const StyledInfoContainer = styled.div`
overflow: hidden;
width: 100%;
`
const InfoContainer = ({ children }: { children: ReactNode }) => {
return (
<Box overflow="hidden" width="full">
{children}
</Box>
)
return <StyledInfoContainer>{children}</StyledInfoContainer>
}
const TruncatedTextRow = styled(Row)`
const TruncatedTextRow = styled(ThemedText.BodySmall)`
display: flex;
padding: 2px;
white-space: pre;
text-overflow: ellipsis;
display: block;
overflow: hidden;
flex: 1;
`
const AssetNameRow = styled(TruncatedTextRow)`
color: ${({ theme }) => theme.textPrimary};
font-size: 16px !important;
font-weight: 400;
`
interface ProfileNftDetailsProps {
@@ -574,6 +617,18 @@ interface ProfileNftDetailsProps {
hideDetails: boolean
}
const PrimaryRowContainer = styled.div`
overflow: hidden;
width: 100%;
flex-wrap: nowrap;
`
const FloorPriceRow = styled(TruncatedTextRow)`
font-size: 16px;
font-weight: 600;
line-height: 20px;
`
const ProfileNftDetails = ({ asset, hideDetails }: ProfileNftDetailsProps) => {
const assetName = () => {
if (!asset.name && !asset.tokenId) return
@@ -583,89 +638,95 @@ const ProfileNftDetails = ({ asset, hideDetails }: ProfileNftDetailsProps) => {
const shouldShowUserListedPrice = !asset.notForSale && asset.asset_contract.tokenType !== NftStandard.Erc1155
return (
<Box overflow="hidden" width="full" flexWrap="nowrap">
<PrimaryRowContainer>
<PrimaryRow>
<PrimaryDetails>
<TruncatedTextRow className={bodySmall} style={{ color: themeVars.colors.textSecondary }}>
<TruncatedTextRow color="textSecondary">
{!!asset.asset_contract.name && <span>{asset.asset_contract.name}</span>}
</TruncatedTextRow>
{asset.collectionIsVerified && <VerifiedIcon height="18px" width="18px" />}
</PrimaryDetails>
{!hideDetails && <DetailsLink />}
</PrimaryRow>
<Row justifyItems="flex-start">
<TruncatedTextRow
className={body}
style={{
color: themeVars.colors.textPrimary,
}}
>
{assetName()}
</TruncatedTextRow>
<Row>
<AssetNameRow>{assetName()}</AssetNameRow>
{asset.susFlag && <Suspicious />}
</Row>
<TruncatedTextRow className={buttonTextMedium} style={{ color: themeVars.colors.textPrimary }}>
<FloorPriceRow>
{shouldShowUserListedPrice && asset.floor_sell_order_price
? `${floorFormatter(asset.floor_sell_order_price)} ETH`
: ' '}
</TruncatedTextRow>
</Box>
</FloorPriceRow>
</PrimaryRowContainer>
)
}
const PrimaryRow = ({ children }: { children: ReactNode }) => (
<Row gap="8" justifyContent="space-between">
{children}
</Row>
)
const StyledPrimaryRow = styled(Row)`
gap: 8px;
justify-content: space-between;
`
const PrimaryRow = ({ children }: { children: ReactNode }) => <StyledPrimaryRow>{children}</StyledPrimaryRow>
const StyledPrimaryDetails = styled(Row)`
justify-items: center;
overflow: hidden;
white-space: nowrap;
`
const PrimaryDetails = ({ children }: { children: ReactNode }) => (
<Row justifyItems="center" overflow="hidden" whiteSpace="nowrap">
{children}
</Row>
<StyledPrimaryDetails>{children}</StyledPrimaryDetails>
)
const PrimaryInfoContainer = styled.div`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-weight: 400;
font-size: 16px;
line-height: 24px;
`
const PrimaryInfo = ({ children }: { children: ReactNode }) => {
return (
<Box overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis" className={body}>
{children}
</Box>
)
return <PrimaryInfoContainer>{children}</PrimaryInfoContainer>
}
const SecondaryRow = ({ children }: { children: ReactNode }) => (
<Row height="20" justifyContent="space-between" marginTop="6">
{children}
</Row>
)
const StyledSecondaryRow = styled(Row)`
height: 20px;
justify-content: space-between;
margin-top: 6px;
`
const SecondaryRow = ({ children }: { children: ReactNode }) => <StyledSecondaryRow>{children}</StyledSecondaryRow>
const StyledSecondaryDetails = styled(Row)`
overflow: hidden;
white-space: nowrap;
`
const SecondaryDetails = ({ children }: { children: ReactNode }) => (
<Row overflow="hidden" whiteSpace="nowrap">
{children}
</Row>
<StyledSecondaryDetails>{children}</StyledSecondaryDetails>
)
const SecondaryInfoContainer = styled.div`
color: ${({ theme }) => theme.textPrimary};
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 20px;
`
const SecondaryInfo = ({ children }: { children: ReactNode }) => {
return (
<Box
color="textPrimary"
overflow="hidden"
whiteSpace="nowrap"
textOverflow="ellipsis"
style={{ lineHeight: '20px' }}
className={subhead}
>
{children}
</Box>
)
return <SecondaryInfoContainer>{children}</SecondaryInfoContainer>
}
const TertiaryInfoContainer = styled.div`
color: ${({ theme }) => theme.textSecondary};
margin-top: 8px;
`
const TertiaryInfo = ({ children }: { children: ReactNode }) => {
return (
<Box marginTop="8" color="textSecondary">
{children}
</Box>
)
return <TertiaryInfoContainer>{children}</TertiaryInfoContainer>
}
interface Erc1155ControlsInterface {
@@ -700,15 +761,18 @@ const Erc1155Controls = ({ quantity }: Erc1155ControlsInterface) => {
)
}
const StyledMarketplaceIcon = styled.img`
display: inline-block;
width: 16px;
height: 16px;
border-radius: 4px;
flex-shrink: 0;
margin-left: 8px;
vertical-align: top;
`
const MarketplaceIcon = ({ marketplace }: { marketplace: string }) => {
return (
<Box
as="img"
alt={marketplace}
src={`/nft/svgs/marketplaces/${marketplace}.svg`}
className={styles.marketplaceIcon}
/>
)
return <StyledMarketplaceIcon alt={marketplace} src={`/nft/svgs/marketplaces/${marketplace}.svg`} />
}
const DetailsLink = () => {
@@ -721,7 +785,7 @@ const DetailsLink = () => {
e.stopPropagation()
}}
>
<Box data-testid="nft-details-link">Details</Box>
<div data-testid="nft-details-link">Details</div>
</DetailsLinkContainer>
)
}
@@ -734,6 +798,28 @@ interface RankingProps {
rarityLogo?: string
}
const RarityLogoContainer = styled(Row)`
margin-right: 4px;
width: 16px;
`
const RarityText = styled(ThemedText.BodySmall)`
display: flex;
`
const RarityInfo = styled(Row)`
height: 16px;
border-radius: 4px;
color: ${({ theme }) => theme.textPrimary};
background: ${({ theme }) => theme.backgroundInteractive};
font-size: 10px;
font-weight: 600;
padding: 0px 4px;
line-height: 12px;
letter-spacing: 0.04em;
backdrop-filter: blur(6px);
`
const Ranking = ({ rarity, provider, rarityVerified, rarityLogo }: RankingProps) => {
const { asset } = useCardContext()
@@ -744,44 +830,49 @@ const Ranking = ({ rarity, provider, rarityVerified, rarityLogo }: RankingProps)
<MouseoverTooltip
text={
<Row>
<Box display="flex" marginRight="4">
<img src={rarityLogo} alt="cardLogo" width={16} />
</Box>
<Box width="full" className={bodySmall}>
<RarityLogoContainer>
<img src={rarityLogo} alt="cardLogo" width={16} height={16} />
</RarityLogoContainer>
<RarityText>
{rarityVerified
? `Verified by ${
('collectionName' in asset && asset.collectionName) ||
('asset_contract' in asset && asset.asset_contract?.name)
}`
: `Ranking by ${rarity.primaryProvider === 'Genie' ? fallbackProvider : rarity.primaryProvider}`}
</Box>
</RarityText>
</Row>
}
placement="top"
>
<Box className={styles.rarityInfo}>
<Box paddingTop="2" paddingBottom="2" display="flex">
{putCommas(provider.rank)}
</Box>
<Box display="flex" height="16">
{rarityVerified ? <RarityVerifiedIcon /> : null}
</Box>
</Box>
<RarityInfo>
<Row padding="2px 0px">{putCommas(provider.rank)}</Row>
<Row>{rarityVerified ? <RarityVerifiedIcon /> : null}</Row>
</RarityInfo>
</MouseoverTooltip>
</RankingContainer>
)}
</>
)
}
const SUSPICIOUS_TEXT = 'Blocked on OpenSea'
const SUSPICIOUS_TEXT = t`Blocked on OpenSea`
const SuspiciousIconContainer = styled(Row)`
flex-shrink: 0;
margin-left: 4px;
`
const PoolIconContainer = styled(SuspiciousIconContainer)`
color: ${({ theme }) => theme.textSecondary};
`
const Suspicious = () => {
return (
<MouseoverTooltip text={<Box className={bodySmall}>{SUSPICIOUS_TEXT}</Box>} placement="top">
<Box display="flex" flexShrink="0" marginLeft="4">
<MouseoverTooltip text={<ThemedText.BodySmall>{SUSPICIOUS_TEXT}</ThemedText.BodySmall>} placement="top">
<SuspiciousIconContainer>
<SuspiciousIcon />
</Box>
</SuspiciousIconContainer>
</MouseoverTooltip>
)
}
@@ -790,44 +881,46 @@ const Pool = () => {
return (
<MouseoverTooltip
text={
<Box className={bodySmall}>
<ThemedText.BodySmall>
This NFT is part of a liquidity pool. Buying this will increase the price of the remaining pooled NFTs.
</Box>
</ThemedText.BodySmall>
}
placement="top"
>
<Box display="flex" flexShrink="0" marginLeft="4" color="textSecondary">
<PoolIconContainer>
<PoolIcon width="20" height="20" />
</Box>
</PoolIconContainer>
</MouseoverTooltip>
)
}
const NoContentContainerBackground = styled.div<{ height?: number }>`
position: relative;
width: 100%;
height: ${({ height }) => (height ? `${height}px` : 'auto')};
padding-top: 100%;
background: ${({ theme }) =>
`linear-gradient(90deg, ${theme.backgroundSurface} 0%, ${theme.backgroundInteractive} 95.83%)`};
`
const NoContentText = styled(ThemedText.BodyPrimary)`
position: absolute;
text-align: center;
left: 50%;
top: 50%;
transform: translate3d(-50%, -50%, 0);
color: ${colors.gray500};
`
const NoContentContainer = ({ height }: { height?: number }) => (
<>
<Box
position="relative"
width="full"
style={{
height: height ? `${height}px` : 'auto',
paddingTop: '100%',
background: `linear-gradient(90deg, ${themeVars.colors.backgroundSurface} 0%, ${themeVars.colors.backgroundInteractive} 95.83%)`,
}}
>
<Box
position="absolute"
textAlign="center"
left="1/2"
top="1/2"
style={{ transform: 'translate3d(-50%, -50%, 0)' }}
color="gray500"
className={body}
>
Content not
<NoContentContainerBackground height={height}>
<NoContentText>
<Trans>Content not</Trans>
<br />
available yet
</Box>
</Box>
<Trans>available yet</Trans>
</NoContentText>
</NoContentContainerBackground>
</>
)

View File

@@ -1,3 +1,6 @@
import { useNftGraphqlEnabled } from 'featureFlags/flags/nftlGraphql'
import { HistoryDuration } from 'graphql/data/__generated__/types-and-hooks'
import { useTrendingCollections } from 'graphql/data/nft/TrendingCollections'
import { fetchTrendingCollections } from 'nft/queries'
import { TimePeriod } from 'nft/types'
import { calculateCardIndex } from 'nft/utils'
@@ -114,6 +117,7 @@ const TRENDING_COLLECTION_SIZE = 5
const Banner = () => {
const navigate = useNavigate()
const isNftGraphqlEnabled = useNftGraphqlEnabled()
const { data } = useQuery(
['trendingCollections'],
@@ -130,12 +134,18 @@ const Banner = () => {
refetchOnMount: false,
}
)
const collections = useMemo(
() => data?.filter((collection) => !EXCLUDED_COLLECTIONS.includes(collection.address)).slice(0, 5),
[data]
const { data: gqlData } = useTrendingCollections(
TRENDING_COLLECTION_SIZE + EXCLUDED_COLLECTIONS.length,
HistoryDuration.Day
)
const collections = useMemo(() => {
const gatedData = isNftGraphqlEnabled ? gqlData : data
return gatedData
?.filter((collection) => collection.address && !EXCLUDED_COLLECTIONS.includes(collection.address))
.slice(0, 5)
}, [data, gqlData, isNftGraphqlEnabled])
const [activeCollectionIdx, setActiveCollectionIdx] = useState(0)
const onToggleNextSlide = useCallback(
(direction: number) => {

View File

@@ -1,10 +1,11 @@
import { formatNumberOrString, NumberType } from '@uniswap/conedison/format'
import { loadingAnimation } from 'components/Loader/styled'
import { LoadingBubble } from 'components/Tokens/loading'
import { useNftGraphqlEnabled } from 'featureFlags/flags/nftlGraphql'
import { useCollection } from 'graphql/data/nft/Collection'
import { VerifiedIcon } from 'nft/components/icons'
import { Markets, TrendingCollection } from 'nft/types'
import { formatWeiToDecimal } from 'nft/utils'
import { ethNumberStandardFormatter, formatWeiToDecimal } from 'nft/utils'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme/components/text'
@@ -235,7 +236,8 @@ const MARKETS_ENUM_TO_NAME = {
}
export const CarouselCard = ({ collection, onClick }: CarouselCardProps) => {
const { data: gqlCollection, loading } = useCollection(collection.address)
const { data: gqlCollection, loading } = useCollection(collection.address ?? '')
const isNftGraphqlEnabled = useNftGraphqlEnabled()
if (loading) return <LoadingCarouselCard />
@@ -252,9 +254,14 @@ export const CarouselCard = ({ collection, onClick }: CarouselCardProps) => {
</FirstColumnTextWrapper>
</TableElement>
<TableElement>
<ThemedText.SubHeaderSmall color="userThemeColor">
{formatWeiToDecimal(collection.floor.toString())} ETH Floor
</ThemedText.SubHeaderSmall>
{collection.floor && (
<ThemedText.SubHeaderSmall color="userThemeColor">
{isNftGraphqlEnabled
? ethNumberStandardFormatter(collection.floor)
: formatWeiToDecimal(collection.floor.toString())}{' '}
ETH Floor
</ThemedText.SubHeaderSmall>
)}
</TableElement>
<TableElement>
<ThemedText.SubHeaderSmall color="userThemeColor">
@@ -304,7 +311,7 @@ const CollectionName = styled(ThemedText.MediumHeader)`
const CarouselCardHeader = ({ collection }: { collection: TrendingCollection }) => {
return (
<CardHeaderContainer src={collection.bannerImageUrl}>
<CardHeaderContainer src={collection.bannerImageUrl ?? ''}>
<CardHeaderColumn>
<CollectionImage src={collection.imageUrl} />
<CollectionNameContainer>

View File

@@ -1,4 +1,5 @@
import { formatEther } from '@ethersproject/units'
import { useNftGraphqlEnabled } from 'featureFlags/flags/nftlGraphql'
import { SquareArrowDownIcon, SquareArrowUpIcon, VerifiedIcon } from 'nft/components/icons'
import { useIsMobile } from 'nft/hooks'
import { Denomination } from 'nft/types'
@@ -114,9 +115,12 @@ export const EthCell = ({
usdPrice?: number
}) => {
const denominatedValue = getDenominatedValue(denomination, true, value, usdPrice)
const isNftGraphqlEnabled = useNftGraphqlEnabled()
const formattedValue = denominatedValue
? denomination === Denomination.ETH
? formatWeiToDecimal(denominatedValue.toString(), true) + ' ETH'
? isNftGraphqlEnabled
? ethNumberStandardFormatter(denominatedValue.toString(), false, true, false) + ' ETH'
: formatWeiToDecimal(denominatedValue.toString(), true) + ' ETH'
: ethNumberStandardFormatter(denominatedValue, true, false, true)
: '-'

View File

@@ -19,7 +19,9 @@ export enum ColumnHeaders {
const VOLUME_CHANGE_MAX_VALUE = 9999
const compareFloats = (a: number, b: number): 1 | -1 => {
const compareFloats = (a?: number, b?: number): 1 | -1 => {
if (!a) return -1
if (!b) return 1
return Math.round(a * 100000) >= Math.round(b * 100000) ? 1 : -1
}
@@ -123,7 +125,7 @@ const CollectionTable = ({ data, timePeriod }: { data: CollectionTableColumn[];
const { change } = cell.row.original.volume
return timePeriod === TimePeriod.AllTime ? (
<TextCell value="-" />
) : change >= VOLUME_CHANGE_MAX_VALUE ? (
) : change && change >= VOLUME_CHANGE_MAX_VALUE ? (
<ChangeCell change={change}>{`>${VOLUME_CHANGE_MAX_VALUE}`}%</ChangeCell>
) : (
<ChangeCell change={change} />

View File

@@ -1,4 +1,7 @@
import { OpacityHoverState } from 'components/Common'
import { useNftGraphqlEnabled } from 'featureFlags/flags/nftlGraphql'
import { HistoryDuration } from 'graphql/data/__generated__/types-and-hooks'
import { useTrendingCollections } from 'graphql/data/nft/TrendingCollections'
import ms from 'ms.macro'
import { CollectionTableColumn, Denomination, TimePeriod, VolumeType } from 'nft/types'
import { fetchPrice } from 'nft/utils'
@@ -29,7 +32,7 @@ const StyledHeader = styled.div`
color: ${({ theme }) => theme.textPrimary};
font-size: 36px;
line-height: 44px;
weight: 500;
font-weight: 500;
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
font-size: 20px;
@@ -69,9 +72,25 @@ const StyledSelectorText = styled(ThemedText.SubHeader)<{ active: boolean }>`
color: ${({ theme, active }) => (active ? theme.textPrimary : theme.textSecondary)};
`
function convertTimePeriodToHistoryDuration(timePeriod: TimePeriod): HistoryDuration {
switch (timePeriod) {
case TimePeriod.OneDay:
return HistoryDuration.Day
case TimePeriod.SevenDays:
return HistoryDuration.Week
case TimePeriod.ThirtyDays:
return HistoryDuration.Month
case TimePeriod.AllTime:
return HistoryDuration.Max
default:
return HistoryDuration.Day
}
}
const TrendingCollections = () => {
const [timePeriod, setTimePeriod] = useState<TimePeriod>(TimePeriod.OneDay)
const [isEthToggled, setEthToggled] = useState(true)
const isNftGraphqlEnabled = useNftGraphqlEnabled()
const { isSuccess, data } = useQuery(
['trendingCollections', timePeriod],
@@ -86,6 +105,8 @@ const TrendingCollections = () => {
}
)
const { data: gqlData, loading } = useTrendingCollections(100, convertTimePeriodToHistoryDuration(timePeriod))
const { data: usdPrice } = useQuery(['fetchPrice', {}], () => fetchPrice(), {
refetchOnReconnect: false,
refetchOnWindowFocus: false,
@@ -94,8 +115,10 @@ const TrendingCollections = () => {
})
const trendingCollections = useMemo(() => {
if (isSuccess && data) {
return data.map((d) => ({
const gatedData = isNftGraphqlEnabled ? gqlData : data
const dataLoaded = isNftGraphqlEnabled ? !loading : isSuccess
if (dataLoaded && gatedData) {
return gatedData.map((d) => ({
...d,
collection: {
name: d.name,
@@ -114,7 +137,6 @@ const TrendingCollections = () => {
},
owners: {
value: d.owners,
change: d.ownersChange,
},
sales: d.sales,
totalSupply: d.totalSupply,
@@ -122,7 +144,7 @@ const TrendingCollections = () => {
usdPrice,
}))
} else return [] as CollectionTableColumn[]
}, [data, isSuccess, isEthToggled, usdPrice])
}, [isNftGraphqlEnabled, gqlData, data, loading, isSuccess, isEthToggled, usdPrice])
return (
<ExploreContainer>

View File

@@ -1,16 +1,25 @@
import { Trans } from '@lingui/macro'
import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics'
import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events'
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
import { useWeb3React } from '@web3-react/core'
import Column from 'components/Column'
import Row from 'components/Row'
import { approveCollectionRow, getListingState, getTotalEthValue, verifyStatus } from 'nft/components/bag/profile/utils'
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { ListingButton } from 'nft/components/profile/list/ListingButton'
import {
approveCollectionRow,
getTotalEthValue,
useSubscribeListingState,
verifyStatus,
} from 'nft/components/profile/list/utils'
import { useIsMobile, useNFTList, useProfilePageState, useSellAsset } from 'nft/hooks'
import { LIST_PAGE_MARGIN, LIST_PAGE_MARGIN_MOBILE } from 'nft/pages/profile/shared'
import { looksRareNonceFetcher } from 'nft/queries'
import { ListingStatus, ProfilePageStateType } from 'nft/types'
import { fetchPrice, formatEth, formatUsdPrice } from 'nft/utils'
import { ProfilePageStateType } from 'nft/types'
import { formatEth } from 'nft/utils'
import { ListingMarkets } from 'nft/utils/listNfts'
import { useEffect, useMemo, useReducer, useState } from 'react'
import { ArrowLeft } from 'react-feather'
@@ -185,26 +194,10 @@ export const ListPage = () => {
}),
shallow
)
const {
listings,
collectionsRequiringApproval,
listingStatus,
setListingStatus,
setLooksRareNonce,
setCollectionStatusAndCallback,
} = useNFTList(
({
const { listings, collectionsRequiringApproval, setLooksRareNonce, setCollectionStatusAndCallback } = useNFTList(
({ listings, collectionsRequiringApproval, setLooksRareNonce, setCollectionStatusAndCallback }) => ({
listings,
collectionsRequiringApproval,
listingStatus,
setListingStatus,
setLooksRareNonce,
setCollectionStatusAndCallback,
}) => ({
listings,
collectionsRequiringApproval,
listingStatus,
setListingStatus,
setLooksRareNonce,
setCollectionStatusAndCallback,
}),
@@ -212,31 +205,16 @@ export const ListPage = () => {
)
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
const nativeCurrency = useNativeCurrency()
const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency)
const usdcValue = useStablecoinValue(parsedAmount)
const usdcAmount = formatCurrencyAmount(usdcValue, NumberType.FiatTokenPrice)
const [showListModal, toggleShowListModal] = useReducer((s) => !s, false)
const [selectedMarkets, setSelectedMarkets] = useState([ListingMarkets[0]]) // default marketplace: x2y2
const [ethPriceInUSD, setEthPriceInUSD] = useState(0)
const signer = provider?.getSigner()
useEffect(() => {
fetchPrice().then((price) => {
setEthPriceInUSD(price ?? 0)
})
}, [])
// TODO with removal of list v1 see if this logic can be removed
useEffect(() => {
const state = getListingState(collectionsRequiringApproval, listings)
if (state.allListingsApproved) setListingStatus(ListingStatus.APPROVED)
else if (state.anyPaused && !state.anyActiveFailures && !state.anyActiveSigning && !state.anyActiveRejections) {
setListingStatus(ListingStatus.CONTINUE)
} else if (state.anyPaused) setListingStatus(ListingStatus.PAUSED)
else if (state.anyActiveSigning) setListingStatus(ListingStatus.SIGNING)
else if (state.allListingsPending || (state.allCollectionsPending && state.allListingsDefined))
setListingStatus(ListingStatus.PENDING)
else if (state.anyActiveFailures && listingStatus !== ListingStatus.PAUSED) setListingStatus(ListingStatus.FAILED)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [listings, collectionsRequiringApproval])
// instantiate listings and collections to approve when users modify input data
useSubscribeListingState()
useEffect(() => {
setGlobalMarketplaces(selectedMarkets)
@@ -247,14 +225,13 @@ export const ListPage = () => {
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,
usd_value: usdcAmount,
...trace,
}
const startListingFlow = async () => {
if (!signer) return
sendAnalyticsEvent(NFTEventName.NFT_SELL_START_LISTING, { ...startListingEventProperties })
setListingStatus(ListingStatus.SIGNING)
const signerAddress = await signer.getAddress()
const nonce = await looksRareNonceFetcher(signerAddress)
setLooksRareNonce(nonce ?? 0)
@@ -317,9 +294,7 @@ export const ListPage = () => {
<EthValueWrapper totalEthListingValue={!!totalEthListingValue}>
{totalEthListingValue > 0 ? formatEth(totalEthListingValue) : '-'} ETH
</EthValueWrapper>
{!!totalEthListingValue && !!ethPriceInUSD && (
<UsdValue>{formatUsdPrice(totalEthListingValue * ethPriceInUSD)}</UsdValue>
)}
{!!usdcValue && <UsdValue>{usdcAmount}</UsdValue>}
</ProceedsWrapper>
<ListingButton onClick={showModalAndStartListing} />
</ProceedsAndButtonWrapper>

View File

@@ -2,15 +2,13 @@ import { Plural, t, Trans } from '@lingui/macro'
import { BaseButton } from 'components/Button'
import ms from 'ms.macro'
import { BelowFloorWarningModal } from 'nft/components/profile/list/Modal/BelowFloorWarningModal'
import { useIsMobile, useNFTList, useSellAsset } from 'nft/hooks'
import { Listing, ListingStatus, WalletAsset } from 'nft/types'
import { useEffect, useMemo, useState } from 'react'
import { useIsMobile, useSellAsset } from 'nft/hooks'
import { Listing, WalletAsset } from 'nft/types'
import { useMemo, useState } from 'react'
import styled from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
import shallow from 'zustand/shallow'
import { getListings } from '../../bag/profile/utils'
const BELOW_FLOOR_PRICE_THRESHOLD = 0.8
const StyledListingButton = styled(BaseButton)<{ showResolveIssues: boolean; missingPrices: boolean }>`
@@ -44,25 +42,9 @@ export const ListingButton = ({ onClick }: { onClick: () => void }) => {
}),
shallow
)
const { setListingStatus, setListings, setCollectionsRequiringApproval } = useNFTList(
({ setListingStatus, setListings, setCollectionsRequiringApproval }) => ({
setListingStatus,
setListings,
setCollectionsRequiringApproval,
}),
shallow
)
const [showWarning, setShowWarning] = useState(false)
const isMobile = useIsMobile()
// instantiate listings and collections to approve when user's modify input data
useEffect(() => {
const [newCollectionsToApprove, newListings] = getListings(sellAssets)
setListings(newListings)
setCollectionsRequiringApproval(newCollectionsToApprove)
setListingStatus(ListingStatus.DEFINED)
}, [sellAssets, setCollectionsRequiringApproval, setListingStatus, setListings])
// Find issues with item listing data
const [listingsMissingPrice, listingsBelowFloor] = useMemo(() => {
const missingExpiration = sellAssets.some((asset) => {

View File

@@ -4,19 +4,18 @@ import Column from 'components/Column'
import Row from 'components/Row'
import { MouseoverTooltip } from 'components/Tooltip'
import { RowsCollpsedIcon, RowsExpandedIcon } from 'nft/components/icons'
import { getRoyalty, useHandleGlobalPriceToggle, useSyncPriceWithGlobalMethod } from 'nft/components/profile/list/utils'
import { useSellAsset } from 'nft/hooks'
import { ListingMarket, ListingWarning, WalletAsset } from 'nft/types'
import { LOOKS_RARE_CREATOR_BASIS_POINTS } from 'nft/utils'
import { ListingMarket, WalletAsset } from 'nft/types'
import { formatEth, formatUsdPrice } from 'nft/utils/currency'
import { fetchPrice } from 'nft/utils/fetchPrice'
import { Dispatch, DispatchWithoutAction, useEffect, useMemo, useReducer, useState } from 'react'
import { Dispatch, DispatchWithoutAction, useCallback, useEffect, useMemo, useReducer, useState } from 'react'
import styled from 'styled-components/macro'
import { BREAKPOINTS, ThemedText } from 'theme'
import { SetPriceMethod } from './NFTListingsGrid'
import { PriceTextInput } from './PriceTextInput'
import { RoyaltyTooltip } from './RoyaltyTooltip'
import { RemoveIconWrap } from './shared'
import { RemoveIconWrap, SetPriceMethod } from './shared'
const LastPriceInfo = styled(Column)`
text-align: left;
@@ -104,13 +103,6 @@ const ReturnColumn = styled(Column)`
}
`
const getRoyalty = (listingMarket: ListingMarket, asset: WalletAsset) => {
// LooksRare is a unique case where royalties for creators are a flat 0.5% or 50 basis points
const baseFee = listingMarket.name === 'LooksRare' ? LOOKS_RARE_CREATOR_BASIS_POINTS : asset.basisPoints ?? 0
return baseFee * 0.01
}
interface MarketplaceRowProps {
globalPriceMethod?: SetPriceMethod
globalPrice?: number
@@ -118,7 +110,6 @@ interface MarketplaceRowProps {
selectedMarkets: ListingMarket[]
removeMarket?: () => void
asset: WalletAsset
showMarketplaceLogo: boolean
expandMarketplaceRows?: boolean
rowHovered?: boolean
toggleExpandMarketplaceRows: DispatchWithoutAction
@@ -131,7 +122,6 @@ export const MarketplaceRow = ({
selectedMarkets,
removeMarket = undefined,
asset,
showMarketplaceLogo,
expandMarketplaceRows,
toggleExpandMarketplaceRows,
rowHovered,
@@ -147,9 +137,16 @@ export const MarketplaceRow = ({
)?.price
)
const [globalOverride, setGlobalOverride] = useState(false)
const showGlobalPrice = globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride && globalPrice
const showGlobalPrice = globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride
const price = showGlobalPrice ? globalPrice : listPrice
const setPrice = useCallback(
(price?: number) => {
showGlobalPrice ? setGlobalPrice(price) : setListPrice(price)
for (const marketplace of selectedMarkets) setAssetListPrice(asset, price, marketplace)
},
[asset, selectedMarkets, setAssetListPrice, setGlobalPrice, showGlobalPrice]
)
const fees = useMemo(() => {
if (selectedMarkets.length === 1) {
@@ -168,68 +165,25 @@ export const MarketplaceRow = ({
const feeInEth = price && (price * fees) / 100
const userReceives = price && feeInEth && price - feeInEth
useMemo(() => {
for (const market of selectedMarkets) {
if (market && asset && asset.basisPoints) {
market.royalty = (market.name === 'LooksRare' ? LOOKS_RARE_CREATOR_BASIS_POINTS : asset.basisPoints) * 0.01
}
}
}, [asset, selectedMarkets])
useHandleGlobalPriceToggle(globalOverride, setListPrice, setPrice, listPrice, globalPrice)
useSyncPriceWithGlobalMethod(
asset,
setListPrice,
setGlobalPrice,
setGlobalOverride,
listPrice,
globalPrice,
globalPriceMethod
)
// When in Same Price Mode and not overriding, update local price when global price changes
useEffect(() => {
if (globalPriceMethod === SetPriceMethod.FLOOR_PRICE) {
setListPrice(asset?.floorPrice)
setGlobalPrice(asset.floorPrice)
} else if (globalPriceMethod === SetPriceMethod.LAST_PRICE) {
setListPrice(asset.lastPrice)
setGlobalPrice(asset.lastPrice)
} else if (globalPriceMethod === SetPriceMethod.SAME_PRICE)
listPrice && !globalPrice ? setGlobalPrice(listPrice) : setListPrice(globalPrice)
setGlobalOverride(false)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [globalPriceMethod])
useEffect(() => {
if (selectedMarkets.length)
for (const marketplace of selectedMarkets) setAssetListPrice(asset, listPrice, marketplace)
else setAssetListPrice(asset, listPrice)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [listPrice])
useEffect(() => {
let price: number | undefined = undefined
if (globalOverride) {
if (!listPrice) setListPrice(globalPrice)
price = listPrice ? listPrice : globalPrice
} else {
price = listPrice
}
if (selectedMarkets.length) for (const marketplace of selectedMarkets) setAssetListPrice(asset, price, marketplace)
else setAssetListPrice(asset, price)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [globalOverride])
useEffect(() => {
if (globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride) {
if (selectedMarkets.length)
for (const marketplace of selectedMarkets) setAssetListPrice(asset, globalPrice, marketplace)
else setAssetListPrice(asset, globalPrice)
if (showGlobalPrice) {
setPrice(globalPrice)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [globalPrice])
let warning: ListingWarning | undefined = undefined
if (asset.listingWarnings && asset.listingWarnings?.length > 0) {
if (showMarketplaceLogo) {
for (const listingWarning of asset.listingWarnings) {
if (listingWarning.marketplace.name === selectedMarkets[0].name) warning = listingWarning
}
} else {
warning = asset.listingWarnings[0]
}
}
return (
<Row onMouseEnter={toggleMarketRowHovered} onMouseLeave={toggleMarketRowHovered}>
<FloorPriceInfo>
@@ -263,27 +217,14 @@ export const MarketplaceRow = ({
))}
</MarketIconsWrapper>
)}
{globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride ? (
<PriceTextInput
listPrice={globalPrice}
setListPrice={setGlobalPrice}
isGlobalPrice={true}
setGlobalOverride={setGlobalOverride}
globalOverride={globalOverride}
warning={warning}
asset={asset}
/>
) : (
<PriceTextInput
listPrice={listPrice}
setListPrice={setListPrice}
isGlobalPrice={false}
setGlobalOverride={setGlobalOverride}
globalOverride={globalOverride}
warning={warning}
asset={asset}
/>
)}
<PriceTextInput
listPrice={price}
setListPrice={setPrice}
isGlobalPrice={showGlobalPrice}
setGlobalOverride={setGlobalOverride}
globalOverride={globalOverride}
asset={asset}
/>
{rowHovered && ((expandMarketplaceRows && marketRowHovered) || selectedMarkets.length > 1) && (
<ExpandMarketIconWrapper onClick={toggleExpandMarketplaceRows}>
{expandMarketplaceRows ? <RowsExpandedIcon /> : <RowsCollpsedIcon />}

View File

@@ -1,14 +1,17 @@
import { Trans } from '@lingui/macro'
import { sendAnalyticsEvent, Trace, useTrace } from '@uniswap/analytics'
import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events'
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
import { useWeb3React } from '@web3-react/core'
import { getTotalEthValue, signListingRow } from 'nft/components/bag/profile/utils'
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { Portal } from 'nft/components/common/Portal'
import { Overlay } from 'nft/components/modals/Overlay'
import { getTotalEthValue, signListingRow } from 'nft/components/profile/list/utils'
import { useNFTList, useSellAsset } from 'nft/hooks'
import { ListingStatus } from 'nft/types'
import { fetchPrice } from 'nft/utils'
import { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
import { useCallback, useEffect, useMemo, useReducer } from 'react'
import { X } from 'react-feather'
import styled from 'styled-components/macro'
import { BREAKPOINTS, ThemedText } from 'theme'
@@ -46,64 +49,60 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
const signer = provider?.getSigner()
const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING })
const sellAssets = useSellAsset((state) => state.sellAssets)
const {
listingStatus,
setListingStatusAndCallback,
setLooksRareNonce,
getLooksRareNonce,
collectionsRequiringApproval,
listings,
} = useNFTList(
({
listingStatus,
setListingStatusAndCallback,
setLooksRareNonce,
getLooksRareNonce,
collectionsRequiringApproval,
listings,
}) => ({
listingStatus,
setListingStatusAndCallback,
setLooksRareNonce,
getLooksRareNonce,
collectionsRequiringApproval,
listings,
}),
shallow
)
const { setListingStatusAndCallback, setLooksRareNonce, getLooksRareNonce, collectionsRequiringApproval, listings } =
useNFTList(
({
setListingStatusAndCallback,
setLooksRareNonce,
getLooksRareNonce,
collectionsRequiringApproval,
listings,
}) => ({
setListingStatusAndCallback,
setLooksRareNonce,
getLooksRareNonce,
collectionsRequiringApproval,
listings,
}),
shallow
)
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
const [openSection, toggleOpenSection] = useReducer(
(s) => (s === Section.APPROVE ? Section.SIGN : Section.APPROVE),
Section.APPROVE
)
const [ethPriceInUSD, setEthPriceInUSD] = useState(0)
useEffect(() => {
fetchPrice().then((price) => {
setEthPriceInUSD(price || 0)
})
}, [])
const nativeCurrency = useNativeCurrency()
const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency)
const usdcValue = useStablecoinValue(parsedAmount)
const usdcAmount = formatCurrencyAmount(usdcValue, NumberType.FiatTokenPrice)
const allCollectionsApproved = useMemo(
() => collectionsRequiringApproval.every((collection) => collection.status === ListingStatus.APPROVED),
[collectionsRequiringApproval]
)
const allListingsApproved = useMemo(
() => listings.every((listing) => listing.status === ListingStatus.APPROVED),
[listings]
)
const signListings = async () => {
if (!signer || !provider) return
// sign listings
for (const listing of listings) {
await signListingRow(listing, signer, provider, getLooksRareNonce, setLooksRareNonce, setListingStatusAndCallback)
}
sendAnalyticsEvent(NFTEventName.NFT_LISTING_COMPLETED, {
signatures_approved: listings.filter((asset) => asset.status === ListingStatus.APPROVED),
list_quantity: listings.length,
usd_value: ethPriceInUSD * totalEthListingValue,
usd_value: usdcAmount,
...trace,
})
}
// Once all collections have been approved, go to next section and start signing listings
useEffect(() => {
if (allCollectionsApproved) {
signListings()
@@ -113,8 +112,8 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
}, [allCollectionsApproved])
const closeModalOnClick = useCallback(() => {
listingStatus === ListingStatus.APPROVED ? window.location.reload() : overlayClick()
}, [listingStatus, overlayClick])
allListingsApproved ? window.location.reload() : overlayClick()
}, [allListingsApproved, overlayClick])
// In the case that a user removes all listings via retry logic, close modal
useEffect(() => {
@@ -125,7 +124,7 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
<Portal>
<Trace modal={InterfaceModalName.NFT_LISTING}>
<ListModalWrapper>
{listingStatus === ListingStatus.APPROVED ? (
{allListingsApproved ? (
<SuccessScreen overlayClick={closeModalOnClick} />
) : (
<>

View File

@@ -6,7 +6,7 @@ import Row from 'components/Row'
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { getTotalEthValue } from 'nft/components/bag/profile/utils'
import { getTotalEthValue } from 'nft/components/profile/list/utils'
import { useSellAsset } from 'nft/hooks'
import { formatEth, generateTweetForList, pluralize } from 'nft/utils'
import { useMemo } from 'react'

View File

@@ -10,7 +10,7 @@ import { BREAKPOINTS, ThemedText } from 'theme'
import { opacify } from 'theme/utils'
import { MarketplaceRow } from './MarketplaceRow'
import { SetPriceMethod } from './NFTListingsGrid'
import { SetPriceMethod } from './shared'
const IMAGE_THUMBNAIL_SIZE = 60
@@ -123,10 +123,10 @@ export const NFTListRow = ({
const [hovered, toggleHovered] = useReducer((s) => !s, false)
const theme = useTheme()
// Keep localMarkets up to date with changes to globalMarkets
useEffect(() => {
setLocalMarkets(JSON.parse(JSON.stringify(selectedMarkets)))
selectedMarkets.length < 2 && expandMarketplaceRows && toggleExpandMarketplaceRows()
}, [expandMarketplaceRows, selectedMarkets])
}, [selectedMarkets])
return (
<NFTListRowWrapper
@@ -161,17 +161,16 @@ export const NFTListRow = ({
</TokenInfoWrapper>
</NFTInfoWrapper>
<MarketPlaceRowWrapper>
{expandMarketplaceRows ? (
localMarkets.map((market, index) => {
{expandMarketplaceRows && localMarkets.length > 1 ? (
localMarkets.map((market) => {
return (
<MarketplaceRow
globalPriceMethod={globalPriceMethod}
globalPrice={globalPrice}
setGlobalPrice={setGlobalPrice}
selectedMarkets={[market]}
removeMarket={() => localMarkets.splice(index, 1)}
removeMarket={() => setLocalMarkets(localMarkets.filter((oldMarket) => oldMarket.name !== market.name))}
asset={asset}
showMarketplaceLogo={true}
key={asset.name + market.name}
expandMarketplaceRows={expandMarketplaceRows}
rowHovered={hovered}
@@ -186,7 +185,6 @@ export const NFTListRow = ({
setGlobalPrice={setGlobalPrice}
selectedMarkets={localMarkets}
asset={asset}
showMarketplaceLogo={false}
rowHovered={hovered}
toggleExpandMarketplaceRows={toggleExpandMarketplaceRows}
/>

View File

@@ -1,5 +1,4 @@
import { Trans } from '@lingui/macro'
// eslint-disable-next-line no-restricted-imports
import Column from 'components/Column'
import Row from 'components/Row'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
@@ -12,6 +11,7 @@ import { BREAKPOINTS } from 'theme'
import { Dropdown } from './Dropdown'
import { NFTListRow } from './NFTListRow'
import { SetPriceMethod } from './shared'
const TableHeader = styled.div`
display: flex;
@@ -143,13 +143,6 @@ const RowDivider = styled.hr`
border-color: ${({ theme }) => theme.backgroundInteractive};
`
export enum SetPriceMethod {
SAME_PRICE,
FLOOR_PRICE,
LAST_PRICE,
CUSTOM,
}
export const NFTListingsGrid = ({ selectedMarkets }: { selectedMarkets: ListingMarket[] }) => {
const sellAssets = useSellAsset((state) => state.sellAssets)
const [globalPriceMethod, setGlobalPriceMethod] = useState(SetPriceMethod.CUSTOM)

View File

@@ -3,16 +3,19 @@ import Column from 'components/Column'
import Row from 'components/Row'
import { BrokenLinkIcon } from 'nft/components/icons'
import { NumericInput } from 'nft/components/layout/Input'
import { useUpdateInputAndWarnings } from 'nft/components/profile/list/utils'
import { body } from 'nft/css/common.css'
import { useSellAsset } from 'nft/hooks'
import { ListingWarning, WalletAsset } from 'nft/types'
import { WalletAsset } from 'nft/types'
import { formatEth } from 'nft/utils/currency'
import { Dispatch, FormEvent, useEffect, useRef, useState } from 'react'
import { Dispatch, useRef, useState } from 'react'
import { AlertTriangle, Link } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
import { colors } from 'theme/colors'
import { WarningType } from './shared'
const PriceTextInputWrapper = styled(Column)`
gap: 12px;
position: relative;
@@ -71,12 +74,6 @@ const WarningAction = styled.div`
color: ${({ theme }) => theme.accentAction};
`
enum WarningType {
BELOW_FLOOR,
ALREADY_LISTED,
NONE,
}
const getWarningMessage = (warning: WarningType) => {
let message = <></>
switch (warning) {
@@ -96,7 +93,6 @@ interface PriceTextInputProps {
isGlobalPrice: boolean
setGlobalOverride: Dispatch<boolean>
globalOverride: boolean
warning?: ListingWarning
asset: WalletAsset
}
@@ -106,42 +102,35 @@ export const PriceTextInput = ({
isGlobalPrice,
setGlobalOverride,
globalOverride,
warning,
asset,
}: PriceTextInputProps) => {
const [warningType, setWarningType] = useState(WarningType.NONE)
const removeMarketplaceWarning = useSellAsset((state) => state.removeMarketplaceWarning)
const removeSellAsset = useSellAsset((state) => state.removeSellAsset)
const showResolveIssues = useSellAsset((state) => state.showResolveIssues)
const inputRef = useRef() as React.MutableRefObject<HTMLInputElement>
const theme = useTheme()
useEffect(() => {
inputRef.current.value = listPrice !== undefined ? `${listPrice}` : ''
setWarningType(WarningType.NONE)
if (!warning && listPrice) {
if (listPrice < (asset?.floorPrice ?? 0)) setWarningType(WarningType.BELOW_FLOOR)
else if (asset.floor_sell_order_price && listPrice >= asset.floor_sell_order_price)
setWarningType(WarningType.ALREADY_LISTED)
} else if (warning && listPrice && listPrice >= 0) removeMarketplaceWarning(asset, warning)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [listPrice])
const percentBelowFloor = (1 - (listPrice ?? 0) / (asset.floorPrice ?? 0)) * 100
const warningColor =
showResolveIssues && !listPrice
(showResolveIssues && !listPrice) ||
warningType === WarningType.ALREADY_LISTED ||
(warningType === WarningType.BELOW_FLOOR && percentBelowFloor >= 20)
? colors.red400
: warningType !== WarningType.NONE
? (warningType === WarningType.BELOW_FLOOR && percentBelowFloor >= 20) ||
warningType === WarningType.ALREADY_LISTED
? colors.red400
: theme.accentWarning
: isGlobalPrice
: warningType === WarningType.BELOW_FLOOR
? theme.accentWarning
: isGlobalPrice || !!listPrice
? theme.accentAction
: listPrice != null
? theme.textSecondary
: theme.accentAction
: theme.textSecondary
const setPrice = (event: React.ChangeEvent<HTMLInputElement>) => {
if (!listPrice && event.target.value.includes('.') && parseFloat(event.target.value) === 0) {
return
}
const val = parseFloat(event.target.value)
setListPrice(isNaN(val) ? undefined : val)
}
useUpdateInputAndWarnings(setWarningType, inputRef, asset, listPrice)
return (
<PriceTextInputWrapper>
@@ -156,13 +145,7 @@ export const PriceTextInput = ({
backgroundColor="none"
width={{ sm: '54', md: '68' }}
ref={inputRef}
onChange={(v: FormEvent<HTMLInputElement>) => {
if (!listPrice && v.currentTarget.value.includes('.') && parseFloat(v.currentTarget.value) === 0) {
return
}
const val = parseFloat(v.currentTarget.value)
setListPrice(isNaN(val) ? undefined : val)
}}
onChange={setPrice}
/>
<CurrencyWrapper listPrice={listPrice}>&nbsp;ETH</CurrencyWrapper>
{(isGlobalPrice || globalOverride) && (
@@ -172,27 +155,25 @@ export const PriceTextInput = ({
)}
</InputWrapper>
<WarningMessage $color={warningColor}>
{warning
? warning.message
: warningType !== WarningType.NONE && (
<WarningRow>
<AlertTriangle height={16} width={16} color={warningColor} />
<span>
{warningType === WarningType.BELOW_FLOOR && `${percentBelowFloor.toFixed(0)}% `}
{getWarningMessage(warningType)}
&nbsp;
{warningType === WarningType.ALREADY_LISTED && `${formatEth(asset?.floor_sell_order_price ?? 0)} ETH`}
</span>
<WarningAction
onClick={() => {
warningType === WarningType.ALREADY_LISTED && removeSellAsset(asset)
setWarningType(WarningType.NONE)
}}
>
{warningType === WarningType.BELOW_FLOOR ? <Trans>Dismiss</Trans> : <Trans>Remove item</Trans>}
</WarningAction>
</WarningRow>
)}
{warningType !== WarningType.NONE && (
<WarningRow>
<AlertTriangle height={16} width={16} color={warningColor} />
<span>
{warningType === WarningType.BELOW_FLOOR && `${percentBelowFloor.toFixed(0)}% `}
{getWarningMessage(warningType)}
&nbsp;
{warningType === WarningType.ALREADY_LISTED && `${formatEth(asset?.floor_sell_order_price ?? 0)} ETH`}
</span>
<WarningAction
onClick={() => {
warningType === WarningType.ALREADY_LISTED && removeSellAsset(asset)
setWarningType(WarningType.NONE)
}}
>
{warningType === WarningType.BELOW_FLOOR ? <Trans>Dismiss</Trans> : <Trans>Remove item</Trans>}
</WarningAction>
</WarningRow>
)}
</WarningMessage>
</PriceTextInputWrapper>
)

View File

@@ -1,6 +1,7 @@
import { Trans } from '@lingui/macro'
import Column from 'components/Column'
import Row from 'components/Row'
import { getRoyalty } from 'nft/components/profile/list/utils'
import { ListingMarket, WalletAsset } from 'nft/types'
import { formatEth } from 'nft/utils'
import styled from 'styled-components/macro'
@@ -50,7 +51,7 @@ export const RoyaltyTooltip = ({
asset: WalletAsset
fees?: number
}) => {
const maxRoyalty = Math.max(...selectedMarkets.map((market) => market.royalty ?? 0))
const maxRoyalty = Math.max(...selectedMarkets.map((market) => getRoyalty(market, asset) ?? 0)).toFixed(2)
return (
<RoyaltyContainer>
{selectedMarkets.map((market) => (

View File

@@ -14,3 +14,16 @@ export const TitleRow = styled(Row)`
justify-content: space-between;
margin-bottom: 8px;
`
export enum SetPriceMethod {
SAME_PRICE,
FLOOR_PRICE,
LAST_PRICE,
CUSTOM,
}
export enum WarningType {
BELOW_FLOOR,
ALREADY_LISTED,
NONE,
}

View File

@@ -1,9 +1,13 @@
import type { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
import { addressesByNetwork, SupportedChainId } from '@looksrare/sdk'
import { SetPriceMethod, WarningType } from 'nft/components/profile/list/shared'
import { useNFTList, useSellAsset } from 'nft/hooks'
import { LOOKSRARE_MARKETPLACE_CONTRACT, X2Y2_TRANSFER_CONTRACT } from 'nft/queries'
import { OPENSEA_CROSS_CHAIN_CONDUIT } from 'nft/queries/openSea'
import { CollectionRow, ListingMarket, ListingRow, ListingStatus, WalletAsset } from 'nft/types'
import { approveCollection, LOOKS_RARE_CREATOR_BASIS_POINTS, signListing } from 'nft/utils/listNfts'
import { Dispatch, useEffect } from 'react'
import shallow from 'zustand/shallow'
export async function approveCollectionRow(
collectionRow: CollectionRow,
@@ -12,10 +16,9 @@ export async function approveCollectionRow(
collection: CollectionRow,
status: ListingStatus,
callback?: () => Promise<void>
) => void,
pauseAllRows?: () => void
) => void
) {
const callback = () => approveCollectionRow(collectionRow, signer, setCollectionStatusAndCallback, pauseAllRows)
const callback = () => approveCollectionRow(collectionRow, signer, setCollectionStatusAndCallback)
setCollectionStatusAndCallback(collectionRow, ListingStatus.SIGNING, callback)
const { marketplace, collectionAddress } = collectionRow
const addresses = addressesByNetwork[SupportedChainId.MAINNET]
@@ -31,11 +34,6 @@ export async function approveCollectionRow(
(await approveCollection(spender, collectionAddress, signer, (newStatus: ListingStatus) =>
setCollectionStatusAndCallback(collectionRow, newStatus, callback)
))
if (
(collectionRow.status === ListingStatus.REJECTED || collectionRow.status === ListingStatus.FAILED) &&
pauseAllRows
)
pauseAllRows()
}
export async function signListingRow(
@@ -44,31 +42,18 @@ export async function signListingRow(
provider: Web3Provider,
getLooksRareNonce: () => number,
setLooksRareNonce: (nonce: number) => void,
setListingStatusAndCallback: (listing: ListingRow, status: ListingStatus, callback?: () => Promise<void>) => void,
pauseAllRows?: () => void
setListingStatusAndCallback: (listing: ListingRow, status: ListingStatus, callback?: () => Promise<void>) => void
) {
const looksRareNonce = getLooksRareNonce()
const callback = () => {
return signListingRow(
listing,
signer,
provider,
getLooksRareNonce,
setLooksRareNonce,
setListingStatusAndCallback,
pauseAllRows
)
return signListingRow(listing, signer, provider, getLooksRareNonce, setLooksRareNonce, setListingStatusAndCallback)
}
setListingStatusAndCallback(listing, ListingStatus.SIGNING, callback)
const { asset, marketplace } = listing
const res = await signListing(marketplace, asset, signer, provider, looksRareNonce, (newStatus: ListingStatus) =>
setListingStatusAndCallback(listing, newStatus, callback)
)
if (listing.status === ListingStatus.REJECTED && pauseAllRows) {
pauseAllRows()
} else {
res && listing.marketplace.name === 'LooksRare' && setLooksRareNonce(looksRareNonce + 1)
}
res && listing.marketplace.name === 'LooksRare' && setLooksRareNonce(looksRareNonce + 1)
}
export const getTotalEthValue = (sellAssets: WalletAsset[]) => {
@@ -86,7 +71,7 @@ export const getTotalEthValue = (sellAssets: WalletAsset[]) => {
return total ? Math.round(total * 10000 + Number.EPSILON) / 10000 : 0
}
export const getListings = (sellAssets: WalletAsset[]): [CollectionRow[], ListingRow[]] => {
const getListings = (sellAssets: WalletAsset[]): [CollectionRow[], ListingRow[]] => {
const newCollectionsToApprove: CollectionRow[] = []
const newListings: ListingRow[] = []
@@ -123,69 +108,89 @@ export const getListings = (sellAssets: WalletAsset[]): [CollectionRow[], Listin
return [newCollectionsToApprove, newListings]
}
type ListingState = {
allListingsPending: boolean
allListingsDefined: boolean
allListingsApproved: boolean
allCollectionsPending: boolean
allCollectionsDefined: boolean
anyActiveSigning: boolean
anyActiveFailures: boolean
anyActiveRejections: boolean
anyPaused: boolean
}
export const getListingState = (
collectionsRequiringApproval: CollectionRow[],
listings: ListingRow[]
): ListingState => {
let allListingsPending = true
let allListingsDefined = true
let allListingsApproved = true
let allCollectionsPending = true
let allCollectionsDefined = true
let anyActiveSigning = false
let anyActiveFailures = false
let anyActiveRejections = false
let anyPaused = false
if (collectionsRequiringApproval.length === 0) {
allCollectionsDefined = allCollectionsPending = false
}
for (const collection of collectionsRequiringApproval) {
if (collection.status !== ListingStatus.PENDING) allCollectionsPending = false
if (collection.status !== ListingStatus.DEFINED) allCollectionsDefined = false
if (collection.status === ListingStatus.SIGNING) anyActiveSigning = true
else if (collection.status === ListingStatus.FAILED) anyActiveFailures = true
else if (collection.status === ListingStatus.REJECTED) anyActiveRejections = true
else if (collection.status === ListingStatus.PAUSED) anyPaused = true
}
if (listings.length === 0) {
allListingsApproved = allListingsDefined = allListingsPending = false
}
for (const listing of listings) {
if (listing.status !== ListingStatus.PENDING) allListingsPending = false
if (listing.status !== ListingStatus.DEFINED) allListingsDefined = false
if (listing.status !== ListingStatus.APPROVED) allListingsApproved = false
if (listing.status === ListingStatus.SIGNING) anyActiveSigning = true
else if (listing.status === ListingStatus.FAILED) anyActiveFailures = true
else if (listing.status === ListingStatus.REJECTED) anyActiveRejections = true
else if (listing.status === ListingStatus.PAUSED) anyPaused = true
}
return {
allListingsPending,
allListingsDefined,
allListingsApproved,
allCollectionsPending,
allCollectionsDefined,
anyActiveSigning,
anyActiveFailures,
anyActiveRejections,
anyPaused,
}
}
export const verifyStatus = (status: ListingStatus) => {
return status !== ListingStatus.PAUSED && status !== ListingStatus.APPROVED
}
export function useSubscribeListingState() {
const sellAssets = useSellAsset((state) => state.sellAssets)
const { setListings, setCollectionsRequiringApproval } = useNFTList(
({ setListings, setCollectionsRequiringApproval }) => ({
setListings,
setCollectionsRequiringApproval,
}),
shallow
)
useEffect(() => {
const [newCollectionsToApprove, newListings] = getListings(sellAssets)
setListings(newListings)
setCollectionsRequiringApproval(newCollectionsToApprove)
}, [sellAssets, setCollectionsRequiringApproval, setListings])
}
export function useHandleGlobalPriceToggle(
globalOverride: boolean,
setListPrice: Dispatch<number | undefined>,
setPrice: (price?: number) => void,
listPrice?: number,
globalPrice?: number
) {
useEffect(() => {
let price: number | undefined
if (globalOverride) {
if (!listPrice) setListPrice(globalPrice)
price = globalPrice
} else {
price = listPrice
}
setPrice(price)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [globalOverride])
}
export function useSyncPriceWithGlobalMethod(
asset: WalletAsset,
setListPrice: Dispatch<number | undefined>,
setGlobalPrice: Dispatch<number | undefined>,
setGlobalOverride: Dispatch<boolean>,
listPrice?: number,
globalPrice?: number,
globalPriceMethod?: SetPriceMethod
) {
useEffect(() => {
if (globalPriceMethod === SetPriceMethod.FLOOR_PRICE) {
setListPrice(asset?.floorPrice)
setGlobalPrice(asset.floorPrice)
} else if (globalPriceMethod === SetPriceMethod.LAST_PRICE) {
setListPrice(asset.lastPrice)
setGlobalPrice(asset.lastPrice)
} else if (globalPriceMethod === SetPriceMethod.SAME_PRICE)
listPrice && !globalPrice ? setGlobalPrice(listPrice) : setListPrice(globalPrice)
setGlobalOverride(false)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [globalPriceMethod])
}
export function useUpdateInputAndWarnings(
setWarningType: Dispatch<WarningType>,
inputRef: React.MutableRefObject<HTMLInputElement>,
asset: WalletAsset,
listPrice?: number
) {
useEffect(() => {
setWarningType(WarningType.NONE)
const price = listPrice ?? 0
inputRef.current.value = `${price}`
if (price < (asset?.floorPrice ?? 0) && price > 0) setWarningType(WarningType.BELOW_FLOOR)
else if (asset.floor_sell_order_price && price >= asset.floor_sell_order_price)
setWarningType(WarningType.ALREADY_LISTED)
}, [asset?.floorPrice, asset.floor_sell_order_price, inputRef, listPrice, setWarningType])
}
export const getRoyalty = (listingMarket: ListingMarket, asset: WalletAsset) => {
// LooksRare is a unique case where royalties for creators are a flat 0.5% or 50 basis points
const baseFee = listingMarket.name === 'LooksRare' ? LOOKS_RARE_CREATOR_BASIS_POINTS : asset.basisPoints ?? 0
return baseFee * 0.01
}

View File

@@ -4,12 +4,10 @@ import { devtools } from 'zustand/middleware'
interface NFTListState {
looksRareNonce: number
listingStatus: ListingStatus
listings: ListingRow[]
collectionsRequiringApproval: CollectionRow[]
setLooksRareNonce: (nonce: number) => void
getLooksRareNonce: () => number
setListingStatus: (status: ListingStatus) => void
setListings: (listings: ListingRow[]) => void
setCollectionsRequiringApproval: (collections: CollectionRow[]) => void
setListingStatusAndCallback: (listing: ListingRow, status: ListingStatus, callback?: () => Promise<void>) => void
@@ -23,7 +21,6 @@ interface NFTListState {
export const useNFTList = create<NFTListState>()(
devtools((set, get) => ({
looksRareNonce: 0,
listingStatus: ListingStatus.DEFINED,
listings: [],
collectionsRequiringApproval: [],
setLooksRareNonce: (nonce) =>
@@ -33,10 +30,6 @@ export const useNFTList = create<NFTListState>()(
getLooksRareNonce: () => {
return get().looksRareNonce
},
setListingStatus: (status) =>
set(() => {
return { listingStatus: status }
}),
setListings: (listings) =>
set(() => {
const updatedListings = listings.map((listing) => {

View File

@@ -1,7 +1,7 @@
import create from 'zustand'
import { devtools } from 'zustand/middleware'
import { ListingMarket, ListingWarning, WalletAsset } from '../types'
import { ListingMarket, WalletAsset } from '../types'
interface SellAssetState {
sellAssets: WalletAsset[]
@@ -16,10 +16,6 @@ interface SellAssetState {
removeAssetMarketplace: (asset: WalletAsset, marketplace: ListingMarket) => void
toggleShowResolveIssues: () => void
setIssues: (issues: number) => void
// TODO: After merging v2, see if this marketplace logic can be removed
addMarketplaceWarning: (asset: WalletAsset, warning: ListingWarning) => void
removeMarketplaceWarning: (asset: WalletAsset, warning: ListingWarning, setGlobalOverride?: boolean) => void
removeAllMarketplaceWarnings: () => void
}
export const useSellAsset = create<SellAssetState>()(
@@ -118,47 +114,6 @@ export const useSellAsset = create<SellAssetState>()(
return { sellAssets: assetsCopy }
})
},
addMarketplaceWarning: (asset, warning) => {
set(({ sellAssets }) => {
const assetsCopy = [...sellAssets]
asset.listingWarnings?.push(warning)
const index = sellAssets.findIndex(
(n) => n.tokenId === asset.tokenId && n.asset_contract.address === asset.asset_contract.address
)
assetsCopy[index] = asset
return { sellAssets: assetsCopy }
})
},
removeMarketplaceWarning: (asset, warning, setGlobalOverride?) => {
set(({ sellAssets }) => {
const assetsCopy = [...sellAssets]
if (asset.listingWarnings === undefined || asset.newListings === undefined) return { sellAssets: assetsCopy }
const warningIndex =
asset.listingWarnings?.findIndex((n) => n.marketplace.name === warning.marketplace.name) ?? -1
asset.listingWarnings?.splice(warningIndex, 1)
if (warning?.message?.includes('LISTING BELOW FLOOR')) {
if (setGlobalOverride) {
asset.newListings?.forEach((listing) => (listing.overrideFloorPrice = true))
} else {
const listingIndex =
asset.newListings?.findIndex((n) => n.marketplace.name === warning.marketplace.name) ?? -1
asset.newListings[listingIndex].overrideFloorPrice = true
}
}
const index = sellAssets.findIndex(
(n) => n.tokenId === asset.tokenId && n.asset_contract.address === asset.asset_contract.address
)
assetsCopy[index] = asset
return { sellAssets: assetsCopy }
})
},
removeAllMarketplaceWarnings: () => {
set(({ sellAssets }) => {
const assetsCopy = [...sellAssets]
assetsCopy.map((asset) => (asset.listingWarnings = []))
return { sellAssets: assetsCopy }
})
},
toggleShowResolveIssues: () => {
set(({ showResolveIssues }) => {
return { showResolveIssues: !showResolveIssues }

View File

@@ -7,8 +7,8 @@ import { XXXL_BAG_WIDTH } from 'nft/components/bag/Bag'
import { ListPage } from 'nft/components/profile/list/ListPage'
import { ProfilePage } from 'nft/components/profile/view/ProfilePage'
import { ProfilePageLoadingSkeleton } from 'nft/components/profile/view/ProfilePageLoadingSkeleton'
import { useBag, useNFTList, useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
import { ListingStatus, ProfilePageStateType } from 'nft/types'
import { useBag, useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
import { ProfilePageStateType } from 'nft/types'
import { Suspense, useEffect, useRef } from 'react'
import { useToggleWalletModal } from 'state/application/hooks'
import styled from 'styled-components/macro'
@@ -62,15 +62,8 @@ const ConnectWalletButton = styled(ButtonPrimary)`
const ProfileContent = () => {
const sellPageState = useProfilePageState((state) => state.state)
const setSellPageState = useProfilePageState((state) => state.setProfilePageState)
const removeAllMarketplaceWarnings = useSellAsset((state) => state.removeAllMarketplaceWarnings)
const resetSellAssets = useSellAsset((state) => state.reset)
const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters)
const setListingStatus = useNFTList((state) => state.setListingStatus)
useEffect(() => {
removeAllMarketplaceWarnings()
setListingStatus(ListingStatus.DEFINED)
}, [removeAllMarketplaceWarnings, sellPageState, setListingStatus])
const { account } = useWeb3React()
const accountRef = useRef(account)

View File

@@ -35,21 +35,20 @@ export interface TransactionsResponse {
}
export interface TrendingCollection {
name: string
address: string
imageUrl: string
bannerImageUrl: string
isVerified: boolean
volume: number
volumeChange: number
floor: number
floorChange: number
marketCap: number
percentListed: number
owners: number
ownersChange: number
totalSupply: number
sales: number
name?: string
address?: string
imageUrl?: string
bannerImageUrl?: string
isVerified?: boolean
volume?: number
volumeChange?: number
floor?: number
floorChange?: number
marketCap?: number
percentListed?: number
owners?: number
totalSupply?: number
sales?: number
}
export enum Denomination {
@@ -59,26 +58,25 @@ export enum Denomination {
export interface CollectionTableColumn {
collection: {
name: string
address: string
logo: string
isVerified: boolean
name?: string
address?: string
logo?: string
isVerified?: boolean
}
volume: {
value: number
change: number
type: VolumeType
value?: number
change?: number
type?: VolumeType
}
floor: {
value: number
change: number
value?: number
change?: number
}
owners: {
value: number
change: number
value?: number
}
sales: number
totalSupply: number
sales?: number
totalSupply?: number
denomination: Denomination
usdPrice?: number
}

View File

@@ -6,11 +6,6 @@ export interface ListingMarket {
name: string
fee: number
icon: string
royalty?: number
}
export interface ListingWarning {
marketplace: ListingMarket
message: string
}
export interface SellOrder {
@@ -72,7 +67,6 @@ export interface WalletAsset {
marketAgnosticPrice?: number
newListings?: Listing[]
marketplaces?: ListingMarket[]
listingWarnings?: ListingWarning[]
}
export interface WalletCollection {