refactor: shelve Details redesign for now (#7077)

* shelve Details redesign for now

* remove feature flag enum

* no longer used utils
This commit is contained in:
Charles Bachmeier 2023-08-08 08:28:43 -07:00 committed by GitHub
parent 5307d113a8
commit a53e773e5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 12 additions and 7347 deletions

@ -1,7 +1,6 @@
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags' import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
import { useBaseEnabledFlag } from 'featureFlags/flags/baseEnabled' import { useBaseEnabledFlag } from 'featureFlags/flags/baseEnabled'
import { useForceUniswapXOnFlag } from 'featureFlags/flags/forceUniswapXOn' import { useForceUniswapXOnFlag } from 'featureFlags/flags/forceUniswapXOn'
import { DetailsV2Variant, useDetailsV2Flag } from 'featureFlags/flags/nftDetails'
import { useRoutingAPIForPriceFlag } from 'featureFlags/flags/priceRoutingApi' import { useRoutingAPIForPriceFlag } from 'featureFlags/flags/priceRoutingApi'
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc' import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
import { UniswapXVariant, useUniswapXFlag } from 'featureFlags/flags/uniswapx' import { UniswapXVariant, useUniswapXFlag } from 'featureFlags/flags/uniswapx'
@ -207,12 +206,6 @@ export default function FeatureFlagModal() {
<X size={24} /> <X size={24} />
</CloseButton> </CloseButton>
</Header> </Header>
<FeatureFlagOption
variant={DetailsV2Variant}
value={useDetailsV2Flag()}
featureFlag={FeatureFlag.detailsV2}
label="Use the new details page for nfts"
/>
<FeatureFlagOption <FeatureFlagOption
variant={UniswapXVariant} variant={UniswapXVariant}
value={useUniswapXFlag()} value={useUniswapXFlag()}

@ -1,11 +0,0 @@
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useDetailsV2Flag(): BaseVariant {
return useBaseFlag(FeatureFlag.detailsV2)
}
export function useDetailsV2Enabled(): boolean {
return useDetailsV2Flag() === BaseVariant.Enabled
}
export { BaseVariant as DetailsV2Variant }

@ -9,7 +9,6 @@ export enum FeatureFlag {
traceJsonRpc = 'traceJsonRpc', traceJsonRpc = 'traceJsonRpc',
permit2 = 'permit2', permit2 = 'permit2',
fiatOnRampButtonOnSwap = 'fiat_on_ramp_button_on_swap_page', fiatOnRampButtonOnSwap = 'fiat_on_ramp_button_on_swap_page',
detailsV2 = 'details_v2',
debounceSwapQuote = 'debounce_swap_quote', debounceSwapQuote = 'debounce_swap_quote',
uniswapXEnabled = 'uniswapx_enabled', // enables sending dutch_limit config to routing-api uniswapXEnabled = 'uniswapx_enabled', // enables sending dutch_limit config to routing-api
uniswapXSyntheticQuote = 'uniswapx_synthetic_quote', uniswapXSyntheticQuote = 'uniswapx_synthetic_quote',

@ -1,22 +0,0 @@
import { HistoryDuration } from 'graphql/data/__generated__/types-and-hooks'
import { Row } from 'nft/components/Flex'
import { useState } from 'react'
import styled from 'styled-components/macro'
import { SupportedTimePeriodsType, TimePeriodSwitcher } from './TimePeriodSwitcher'
const TableContentContainer = styled(Row)`
height: 568px;
justify-content: space-between;
align-items: flex-start;
`
export const ActivityTableContent = () => {
const [timePeriod, setTimePeriod] = useState<SupportedTimePeriodsType>(HistoryDuration.Week)
return (
<TableContentContainer>
<span>Activity Content</span>
<TimePeriodSwitcher activeTimePeriod={timePeriod} setTimePeriod={setTimePeriod} />
</TableContentContainer>
)
}

@ -1,53 +0,0 @@
import { Trans } from '@lingui/macro'
import { formatNumber } from '@uniswap/conedison/format'
import { ButtonPrimary } from 'components/Button'
import Loader from 'components/Icons/LoadingSpinner'
import { useBuyAssetCallback } from 'nft/hooks/useFetchAssets'
import { GenieAsset } from 'nft/types'
import styled from 'styled-components/macro'
import { OfferButton } from './OfferButton'
import { ButtonStyles } from './shared'
const StyledBuyButton = styled(ButtonPrimary)`
display: flex;
flex-direction: row;
padding: 16px 24px;
gap: 8px;
line-height: 24px;
white-space: nowrap;
${ButtonStyles}
`
const Price = styled.div`
color: ${({ theme }) => theme.accentTextLightSecondary};
`
export const BuyButton = ({ asset, onDataPage }: { asset: GenieAsset; onDataPage?: boolean }) => {
const { fetchAndPurchaseSingleAsset, isLoading: isLoadingRoute } = useBuyAssetCallback()
const price = asset.sellorders?.[0]?.price.value
if (!price) {
return <OfferButton />
}
return (
<>
<StyledBuyButton disabled={isLoadingRoute} onClick={() => fetchAndPurchaseSingleAsset(asset)}>
{isLoadingRoute ? (
<>
<Trans>Fetching Route</Trans>
<Loader size="24px" stroke="white" />
</>
) : (
<>
<Trans>Buy</Trans>
<Price>{formatNumber(price)} ETH</Price>
</>
)}
</StyledBuyButton>
{onDataPage && <OfferButton smallVersion />}
</>
)
}

@ -1,16 +0,0 @@
import { TEST_NFT_ASSET } from 'test-utils/nft/fixtures'
import { render } from 'test-utils/render'
import { DataPage } from './DataPage'
it('data page loads with header showing', () => {
const { asFragment } = render(<DataPage asset={TEST_NFT_ASSET} showDataHeader={true} />)
expect(asFragment()).toMatchSnapshot()
})
// The header is hidden via opacity: 0 to maintain its spacing, so it still exists in the DOM
// Therefore we can not check for its non-existence and instead rely on comparing the full generated snapshots
it('data page loads without header showing', () => {
const { asFragment } = render(<DataPage asset={TEST_NFT_ASSET} showDataHeader={false} />)
expect(asFragment()).toMatchSnapshot()
})

@ -1,84 +0,0 @@
import Column from 'components/Column'
import Row from 'components/Row'
import { GenieAsset } from 'nft/types'
import styled from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
import { DataPageDescription } from './DataPageDescription'
import { DataPageHeader } from './DataPageHeader'
import { DataPageTable } from './DataPageTable'
import { DataPageTraits } from './DataPageTraits'
const DataPagePaddingContainer = styled.div`
padding: 24px 64px;
height: 100vh;
width: 100%;
@media screen and (max-width: ${BREAKPOINTS.md}px) {
height: 100%;
}
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
padding: 24px 48px;
}
@media screen and (max-width: ${BREAKPOINTS.xs}px) {
padding: 24px 20px;
}
`
const DataPageContainer = styled(Column)`
height: 100%;
width: 100%;
gap: 36px;
max-width: ${({ theme }) => theme.maxWidth};
margin: 0 auto;
`
const HeaderContainer = styled.div<{ showDataHeader?: boolean }>`
position: sticky;
top: ${({ theme }) => `${theme.navHeight}px`};
padding-top: 16px;
backdrop-filter: blur(12px);
z-index: 1;
transition: ${({ theme }) => `opacity ${theme.transition.duration.fast}`};
opacity: ${({ showDataHeader }) => (showDataHeader ? '1' : '0')};
@media screen and (max-width: ${BREAKPOINTS.md}px) {
display: none;
}
`
const ContentContainer = styled(Row)`
gap: 24px;
padding-bottom: 45px;
@media screen and (max-width: ${BREAKPOINTS.lg}px) {
flex-wrap: wrap;
}
`
const LeftColumn = styled(Column)`
gap: 24px;
width: 100%;
align-self: flex-start;
`
export const DataPage = ({ asset, showDataHeader }: { asset: GenieAsset; showDataHeader: boolean }) => {
return (
<DataPagePaddingContainer>
<DataPageContainer>
<HeaderContainer showDataHeader={showDataHeader}>
<DataPageHeader asset={asset} />
</HeaderContainer>
<ContentContainer>
<LeftColumn>
{!!asset.traits?.length && <DataPageTraits asset={asset} />}
<DataPageDescription />
</LeftColumn>
<DataPageTable asset={asset} />
</ContentContainer>
</DataPageContainer>
</DataPagePaddingContainer>
)
}

@ -1,44 +0,0 @@
import { Trans } from '@lingui/macro'
import styled from 'styled-components/macro'
import { Tab, TabbedComponent } from './TabbedComponent'
const DescriptionContentContainer = styled.div`
height: 252px;
`
const DescriptionContent = () => {
return <DescriptionContentContainer>Description Content</DescriptionContentContainer>
}
const DetailsContent = () => {
return <DescriptionContentContainer>Details Content</DescriptionContentContainer>
}
enum DescriptionTabsKeys {
Description = 'description',
Details = 'details',
}
const DescriptionTabs: Map<string, Tab> = new Map([
[
DescriptionTabsKeys.Description,
{
title: <Trans>Description</Trans>,
key: DescriptionTabsKeys.Description,
content: <DescriptionContent />,
},
],
[
DescriptionTabsKeys.Details,
{
title: <Trans>Details</Trans>,
key: DescriptionTabsKeys.Details,
content: <DetailsContent />,
},
],
])
export const DataPageDescription = () => {
return <TabbedComponent tabs={DescriptionTabs} />
}

@ -1,18 +0,0 @@
import { TEST_NFT_ASSET, TEST_SELL_ORDER } from 'test-utils/nft/fixtures'
import { render } from 'test-utils/render'
import { DataPageHeader } from './DataPageHeader'
it('Header loads with asset with no sell orders', () => {
const { asFragment } = render(<DataPageHeader asset={TEST_NFT_ASSET} />)
expect(asFragment()).toMatchSnapshot()
})
it('Header loads with asset with a sell order', () => {
const assetWithOrder = {
...TEST_NFT_ASSET,
sellorders: [TEST_SELL_ORDER],
}
const { asFragment } = render(<DataPageHeader asset={assetWithOrder} />)
expect(asFragment()).toMatchSnapshot()
})

@ -1,48 +0,0 @@
import Column from 'components/Column'
import Row from 'components/Row'
import { VerifiedIcon } from 'nft/components/icons'
import { GenieAsset } from 'nft/types'
import styled from 'styled-components/macro'
import { BREAKPOINTS, ThemedText } from 'theme'
import { BuyButton } from './BuyButton'
const HeaderContainer = styled(Row)`
gap: 24px;
`
const AssetImage = styled.img`
width: 96px;
height: 96px;
border-radius: 20px;
object-fit: cover;
@media screen and (max-width: ${BREAKPOINTS.lg}px) {
display: none;
}
`
const AssetText = styled(Column)`
gap: 4px;
margin-right: auto;
`
export const DataPageHeader = ({ asset }: { asset: GenieAsset }) => {
return (
<HeaderContainer>
<AssetImage src={asset.imageUrl} />
<AssetText>
<Row gap="4px">
<ThemedText.SubHeaderSmall>{asset.collectionName}</ThemedText.SubHeaderSmall>
<VerifiedIcon width="16px" height="16px" />
</Row>
<ThemedText.HeadlineMedium>
{asset.name ?? `${asset.collectionName} #${asset.tokenId}`}
</ThemedText.HeadlineMedium>
</AssetText>
<Row justifySelf="flex-end" width="min-content" gap="12px">
<BuyButton asset={asset} onDataPage />
</Row>
</HeaderContainer>
)
}

@ -1,23 +0,0 @@
import { TEST_NFT_ASSET, TEST_OFFER, TEST_SELL_ORDER } from 'test-utils/nft/fixtures'
import { render } from 'test-utils/render'
import { ListingsTableContent } from './ListingsTableContent'
import { OffersTableContent } from './OffersTableContent'
it('data page offers table content loads with a given asset', () => {
const assetWithOffer = {
...TEST_NFT_ASSET,
offers: [TEST_OFFER],
}
const { asFragment } = render(<OffersTableContent asset={assetWithOffer} />)
expect(asFragment()).toMatchSnapshot()
})
it('data page listings table content loads with a given asset', () => {
const assetWithOrder = {
...TEST_NFT_ASSET,
sellorders: [TEST_SELL_ORDER],
}
const { asFragment } = render(<ListingsTableContent asset={assetWithOrder} />)
expect(asFragment()).toMatchSnapshot()
})

@ -1,50 +0,0 @@
import { Trans } from '@lingui/macro'
import { GenieAsset } from 'nft/types'
import { useMemo } from 'react'
import { ActivityTableContent } from './ActivityTableContent'
import { ListingsTableContent } from './ListingsTableContent'
import { OffersTableContent } from './OffersTableContent'
import { Tab, TabbedComponent } from './TabbedComponent'
export enum TableTabsKeys {
Activity = 'activity',
Offers = 'offers',
Listings = 'listings',
}
export const DataPageTable = ({ asset }: { asset: GenieAsset }) => {
const TableTabs: Map<string, Tab> = useMemo(
() =>
new Map([
[
TableTabsKeys.Activity,
{
title: <Trans>Activity</Trans>,
key: TableTabsKeys.Activity,
content: <ActivityTableContent />,
},
],
[
TableTabsKeys.Offers,
{
title: <Trans>Offers</Trans>,
key: TableTabsKeys.Offers,
content: <OffersTableContent asset={asset} />,
count: 11, // TODO Replace Placeholder with real data
},
],
[
TableTabsKeys.Listings,
{
title: <Trans>Listings</Trans>,
key: TableTabsKeys.Listings,
content: <ListingsTableContent asset={asset} />,
count: asset.sellorders?.length,
},
],
]),
[asset]
)
return <TabbedComponent tabs={TableTabs} />
}

@ -1,12 +0,0 @@
import { TEST_NFT_ASSET } from 'test-utils/nft/fixtures'
import { render } from 'test-utils/render'
import { DataPageTraits } from './DataPageTraits'
it('data page trait component does not load with asset with no traits', () => {
const { asFragment } = render(<DataPageTraits asset={TEST_NFT_ASSET} />)
expect(asFragment()).toMatchSnapshot()
})
// TODO(NFT-1114): add test for trait component with asset with traits when rarity is not randomly generated
// while rarities are randomly generated, snapshots will never match

@ -1,106 +0,0 @@
import { Trans } from '@lingui/macro'
import Column from 'components/Column'
import { ScrollBarStyles } from 'components/Common'
import Row from 'components/Row'
import { useSubscribeScrollState } from 'nft/hooks'
import { GenieAsset } from 'nft/types'
import { useMemo } from 'react'
import styled from 'styled-components/macro'
import { BREAKPOINTS, ThemedText } from 'theme'
import { Scrim } from './shared'
import { Tab, TabbedComponent } from './TabbedComponent'
import { TraitRow } from './TraitRow'
const TraitsHeaderContainer = styled(Row)`
padding: 0px 12px;
`
const TraitsHeader = styled(ThemedText.SubHeaderSmall)<{
$flex?: number
$justifyContent?: string
hideOnSmall?: boolean
}>`
display: flex;
line-height: 20px;
color: ${({ theme }) => theme.textSecondary};
flex: ${({ $flex }) => $flex ?? 1};
justify-content: ${({ $justifyContent }) => $justifyContent};
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
display: ${({ hideOnSmall }) => (hideOnSmall ? 'none' : 'flex')};
}
`
const TraitRowContainer = styled.div`
position: relative;
`
const TraitRowScrollableContainer = styled.div`
overflow-y: auto;
overflow-x: hidden;
max-height: 412px;
width: calc(100% + 6px);
${ScrollBarStyles}
`
const TraitsContent = ({ asset }: { asset: GenieAsset }) => {
const { userCanScroll, scrollRef, scrollProgress, scrollHandler } = useSubscribeScrollState()
// This is needed to prevent rerenders when handling scrolls
const traitRows = useMemo(() => {
return asset.traits?.map((trait) => (
<TraitRow collectionAddress={asset.address} trait={trait} key={trait.trait_type + ':' + trait.trait_value} />
))
}, [asset.address, asset.traits])
return (
<Column>
<TraitsHeaderContainer>
<TraitsHeader $flex={3}>
<Trans>Trait</Trans>
</TraitsHeader>
<TraitsHeader $flex={2}>
<Trans>Floor price</Trans>
</TraitsHeader>
<TraitsHeader hideOnSmall={true}>
<Trans>Quantity</Trans>
</TraitsHeader>
<TraitsHeader $flex={1.5} $justifyContent="flex-end">
<Trans>Rarity</Trans>
</TraitsHeader>
</TraitsHeaderContainer>
<TraitRowContainer>
{scrollProgress > 0 && <Scrim />}
<TraitRowScrollableContainer ref={scrollRef} onScroll={scrollHandler}>
{traitRows}
</TraitRowScrollableContainer>
{userCanScroll && scrollProgress !== 100 && <Scrim isBottom={true} />}
</TraitRowContainer>
</Column>
)
}
enum TraitTabsKeys {
Traits = 'traits',
}
export const DataPageTraits = ({ asset }: { asset: GenieAsset }) => {
const TraitTabs: Map<string, Tab> = useMemo(
() =>
new Map([
[
TraitTabsKeys.Traits,
{
title: <Trans>Traits</Trans>,
key: TraitTabsKeys.Traits,
content: <TraitsContent asset={asset} />,
count: asset.traits?.length,
},
],
]),
[asset]
)
return <TabbedComponent tabs={TraitTabs} />
}

@ -1,201 +0,0 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import Column from 'components/Column'
import Row from 'components/Row'
import { Unicon } from 'components/Unicon'
import useENSAvatar from 'hooks/useENSAvatar'
import useENSName from 'hooks/useENSName'
import { useIsMobile } from 'nft/hooks'
import { GenieAsset } from 'nft/types'
import { getLinkForTrait } from 'nft/utils'
import { ReactNode, useReducer } from 'react'
import { ChevronDown, DollarSign } from 'react-feather'
import { Link } from 'react-router-dom'
import styled from 'styled-components/macro'
import { BREAKPOINTS, ClickableStyle, EllipsisStyle, ExternalLink, LinkStyle, ThemedText } from 'theme'
import { isAddress, shortenAddress } from 'utils'
import { ExplorerDataType } from 'utils/getExplorerLink'
import { getExplorerLink } from 'utils/getExplorerLink'
const StyledBubble = styled(Row)`
background-color: ${({ theme }) => theme.backgroundSurface};
padding: 10px 12px 10px 8px;
border-radius: 20px;
max-width: 144px;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
max-width: 169px;
}
`
const StyledLabelMedium = styled.div`
font-weight: 600;
font-size: 16px;
line-height: 20px;
color: ${({ theme }) => theme.textPrimary};
${EllipsisStyle}
`
const StyledIcon = styled(Row)`
width: 24px;
height: 24px;
flex-shrink: 0;
color: ${({ theme }) => theme.accentAction};
border-radius: 100%;
overflow: hidden;
justify-content: center;
align-items: center;
`
const StyledLink = styled(Link)`
${ClickableStyle}
${LinkStyle}
`
const ConditionalLinkWrapper = ({
isExternal,
href,
children,
}: {
isExternal?: boolean
href: string
children: ReactNode
}) => {
return isExternal ? (
<ExternalLink href={href}>{children}</ExternalLink>
) : (
<StyledLink to={href}>{children}</StyledLink>
)
}
const InfoBubble = ({
title,
info,
icon,
href,
isExternal,
}: {
title: ReactNode
info: string
icon: ReactNode
href: string
isExternal?: boolean
}) => {
return (
<Column gap="sm">
<ThemedText.Caption color="textSecondary">{title}</ThemedText.Caption>
<ConditionalLinkWrapper isExternal={isExternal} href={href}>
<StyledBubble gap="sm">
<StyledIcon>{icon}</StyledIcon>
<StyledLabelMedium>{info}</StyledLabelMedium>
</StyledBubble>
</ConditionalLinkWrapper>
</Column>
)
}
const InfoChipDropdown = styled.button`
padding: 10px;
background-color: ${({ theme }) => theme.backgroundSurface};
color: ${({ theme }) => theme.textSecondary};
border-radius: 100%;
border: none;
cursor: pointer;
`
const InfoChipDropdownContainer = styled(Column)`
height: 100%;
margin-top: auto;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
display: none;
}
`
const Break = styled(Column)`
flex-basis: 100%;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
display: none;
}
`
const InfoChipsContainer = styled(Row)`
gap: 4px;
width: 100%;
flex-wrap: wrap;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
gap: 12px;
flex-wrap: nowrap;
}
`
const StyledChevron = styled(ChevronDown)<{ isOpen: boolean }>`
transform: ${({ isOpen }) => (isOpen ? 'rotate(180deg)' : 'rotate(0deg)')};
will-change: transform;
transition: transform ${({ theme }) => theme.transition.duration.medium};
`
export const InfoChips = ({ asset }: { asset: GenieAsset }) => {
const { chainId } = useWeb3React()
const isMobile = useIsMobile()
const [showExtraInfoChips, toggleShowExtraInfoChips] = useReducer((s) => !s, false)
const shouldShowExtraInfoChips = !isMobile || showExtraInfoChips
const topTrait = asset?.traits?.[0]
const traitCollectionAddress = topTrait && getLinkForTrait(topTrait, asset.address)
const isChecksummedAddress = isAddress(asset.ownerAddress)
const checksummedAddress = isChecksummedAddress ? isChecksummedAddress : undefined
const { ENSName } = useENSName(checksummedAddress)
const { avatar } = useENSAvatar(checksummedAddress)
const shortenedAddress = asset.ownerAddress ? shortenAddress(asset.ownerAddress) : ''
const addressToDisplay = ENSName ?? shortenedAddress
const avatarToDisplay = avatar ? (
<img src={avatar} width={24} height={24} />
) : (
<Unicon size={24} address={asset.ownerAddress ?? ''} />
)
return (
<Column gap="sm">
<InfoChipsContainer justify="center">
<InfoBubble
title={<Trans>Owner</Trans>}
info={addressToDisplay}
icon={avatarToDisplay}
href={getExplorerLink(chainId ?? 1, asset.ownerAddress ?? '', ExplorerDataType.ADDRESS)}
isExternal={true}
/>
{traitCollectionAddress && (
<>
<InfoBubble
title={<Trans>Trait Floor</Trans>}
info="5.3 ETH"
icon={<DollarSign size={20} />}
href={traitCollectionAddress}
/>
<InfoChipDropdownContainer>
<InfoChipDropdown onClick={toggleShowExtraInfoChips}>
<StyledChevron isOpen={showExtraInfoChips} size={20} display="block" />
</InfoChipDropdown>
</InfoChipDropdownContainer>
{shouldShowExtraInfoChips && (
<>
<Break />
<InfoBubble
title={<Trans>Top Trait</Trans>}
info={topTrait.trait_value}
icon=""
href={traitCollectionAddress}
/>
</>
)}
</>
)}
</InfoChipsContainer>
</Column>
)
}

@ -1,29 +0,0 @@
import { TEST_NFT_ASSET, TEST_NFT_COLLECTION_INFO_FOR_ASSET } from 'test-utils/nft/fixtures'
import { render } from 'test-utils/render'
import { LandingPage } from './LandingPage'
beforeEach(() => {
// IntersectionObserver isn't available in test environment
const mockIntersectionObserver = jest.fn()
mockIntersectionObserver.mockReturnValue({
observe: () => null,
unobserve: () => null,
disconnect: () => null,
})
window.IntersectionObserver = mockIntersectionObserver
})
describe('LandingPage', () => {
const mockSetShowDataHeader = jest.fn()
it('renders it correctly', () => {
const { asFragment } = render(
<LandingPage
asset={TEST_NFT_ASSET}
collection={TEST_NFT_COLLECTION_INFO_FOR_ASSET}
setShowDataHeader={mockSetShowDataHeader}
/>
)
expect(asFragment()).toMatchSnapshot()
})
})

@ -1,152 +0,0 @@
import Column, { ColumnCenter } from 'components/Column'
import Row from 'components/Row'
import { VerifiedIcon } from 'nft/components/icons'
import { CollectionInfoForAsset, GenieAsset } from 'nft/types'
import { useEffect, useRef } from 'react'
import styled from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
import { BuyButton } from './BuyButton'
import { InfoChips } from './InfoChips'
import { MediaRenderer } from './MediaRenderer'
const MAX_WIDTH = 560
const LandingPageContainer = styled.div`
display: flex;
flex-direction: column;
min-height: ${({ theme }) => `calc(100vh - ${theme.navHeight}px - ${theme.mobileBottomBarHeight}px)`};
align-items: center;
padding: 22px 20px 0px;
gap: 26px;
width: 100%;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
gap: 64px;
padding-top: 28px;
}
@media screen and (min-width: ${BREAKPOINTS.md}px) {
min-height: ${({ theme }) => `calc(100vh - ${theme.navHeight}px )`};
}
@media screen and (min-width: ${BREAKPOINTS.xl}px) {
flex-direction: row;
padding-top: 0px;
padding-bottom: ${({ theme }) => `${theme.navHeight}px`};
gap: 80px;
justify-content: center;
}
`
const InfoContainer = styled(ColumnCenter)`
gap: 40px;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
width: ${MAX_WIDTH}px;
}
`
const StyledHeadlineText = styled.div`
font-weight: 600;
font-size: 20px;
line-height: 28px;
text-align: center;
color: ${({ theme }) => theme.textPrimary};
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
line-height: 44px;
font-size: 36px;
}
`
const StyledSubheaderText = styled.div`
font-weight: 500;
font-size: 14px;
line-height: 20px;
text-align: center;
color: ${({ theme }) => theme.textSecondary};
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
line-height: 24px;
font-size: 16px;
}
`
const InfoDetailsContainer = styled(Column)`
gap: 4px;
align-items: center;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
${StyledHeadlineText} {
line-height: 44px;
font-size: 36px;
}
${StyledSubheaderText} {
line-height: 24px;
font-size: 16px;
}
}
`
const MediaContainer = styled.div`
position: relative;
width: 100%;
height: 100%;
filter: drop-shadow(0px 12px 20px rgba(0, 0, 0, 0.1));
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
width: ${MAX_WIDTH}px;
height: ${MAX_WIDTH}px;
}
`
interface LandingPageProps {
asset: GenieAsset
collection: CollectionInfoForAsset
setShowDataHeader: (showDataHeader: boolean) => void
}
export const LandingPage = ({ asset, collection, setShowDataHeader }: LandingPageProps) => {
const intersectionRef = useRef<HTMLDivElement>(null)
const observableRef = useRef(
new IntersectionObserver((entries) => {
if (!entries[0].isIntersecting) {
setShowDataHeader(true)
} else {
setShowDataHeader(false)
}
})
)
// Checks if the intersectionRef is in the viewport
// If it is not in the viewport, the data page header becomes visible
useEffect(() => {
const cachedRef = intersectionRef.current
const observer = observableRef.current
if (cachedRef && observer) {
observer.observe(cachedRef)
return () => observer.unobserve(cachedRef)
}
return
}, [intersectionRef, observableRef, setShowDataHeader])
return (
<LandingPageContainer>
<MediaContainer ref={intersectionRef}>
<MediaRenderer asset={asset} />
</MediaContainer>
<InfoContainer>
<InfoDetailsContainer>
<Row justify="center" gap="4px" align="center">
<StyledSubheaderText>{collection.collectionName}</StyledSubheaderText>
{collection.isVerified && <VerifiedIcon width="16px" height="16px" />}
</Row>
<StyledHeadlineText>{asset.name ?? `${asset.collectionName} #${asset.tokenId}`}</StyledHeadlineText>
</InfoDetailsContainer>
<InfoChips asset={asset} />
<BuyButton asset={asset} />
</InfoContainer>
</LandingPageContainer>
)
}

@ -1,25 +0,0 @@
import { Trans } from '@lingui/macro'
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { AddToBagIcon } from 'nft/components/icons'
import { useIsMobile } from 'nft/hooks'
import { GenieAsset } from 'nft/types'
import { useTheme } from 'styled-components/macro'
import { TableTabsKeys } from './DataPageTable'
import { TableContentComponent } from './TableContentComponent'
import { ContentRow, HeaderRow } from './TableRowComponent'
export const ListingsTableContent = ({ asset }: { asset: GenieAsset }) => {
const isMobile = useIsMobile()
const theme = useTheme()
const headers = <HeaderRow type={TableTabsKeys.Listings} is1155={asset.tokenType === NftStandard.Erc1155} />
const contentRows = (asset.sellorders || []).map((offer, index) => (
<ContentRow
key={'offer_' + index}
content={offer}
buttonCTA={isMobile ? <AddToBagIcon color={theme.textSecondary} /> : <Trans>Add to Bag</Trans>}
is1155={asset.tokenType === NftStandard.Erc1155}
/>
))
return <TableContentComponent headerRow={headers} contentRows={contentRows} type={TableTabsKeys.Offers} />
}

@ -1,31 +0,0 @@
import {
TEST_AUDIO_NFT_ASSET,
TEST_EMBEDDED_NFT_ASSET,
TEST_NFT_ASSET,
TEST_VIDEO_NFT_ASSET,
} from 'test-utils/nft/fixtures'
import { render } from 'test-utils/render'
import { MediaRenderer } from './MediaRenderer'
describe('Media renderer', () => {
it('renders image nft correctly', () => {
const { asFragment } = render(<MediaRenderer asset={TEST_NFT_ASSET} />)
expect(asFragment()).toMatchSnapshot()
})
it('renders an embedded nft correctly', () => {
const { asFragment } = render(<MediaRenderer asset={TEST_EMBEDDED_NFT_ASSET} />)
expect(asFragment()).toMatchSnapshot()
})
it('renders a video nft correctly', () => {
const { asFragment } = render(<MediaRenderer asset={TEST_VIDEO_NFT_ASSET} />)
expect(asFragment()).toMatchSnapshot()
})
it('renders an audio nft correctly', () => {
const { asFragment } = render(<MediaRenderer asset={TEST_AUDIO_NFT_ASSET} />)
expect(asFragment()).toMatchSnapshot()
})
})

@ -1,184 +0,0 @@
import { GenieAsset } from 'nft/types'
import { isAudio, isVideo } from 'nft/utils'
import { useState } from 'react'
import styled, { css } from 'styled-components/macro'
import { BREAKPOINTS, ThemedText } from 'theme'
const MediaStyle = css`
position: relative;
object-fit: contain;
height: 100%;
width: 100%;
aspect-ratio: 1;
z-index: 1;
`
const StyledImage = styled.img`
${MediaStyle}
`
const StyledVideo = styled.video`
${MediaStyle}
`
const MediaShadow = styled.img`
object-fit: contain;
height: 100%;
aspect-ratio: 1;
border-radius: 20px;
filter: blur(25px);
@media screen and (min-width: ${BREAKPOINTS.xl}px) {
filter: blur(50px);
}
`
const MediaShadowContainer = styled.div`
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
`
const StyledEmbed = styled.div`
position: relative;
overflow: hidden;
width: 100%;
padding-top: 100%;
z-index: 1;
`
const StyledIFrame = styled.iframe`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
`
const AudioContainer = styled.div`
position: relative;
`
const StyledAudio = styled.audio`
position: absolute;
left: 0;
bottom: 0;
z-index: 2;
width: 100%;
`
const AudioPlayer = ({ asset, onError }: { asset: GenieAsset; onError: () => void }) => {
return (
<AudioContainer>
<StyledImage
src={asset.imageUrl}
alt={asset.name ?? asset.collectionName + ' #' + asset.tokenId}
onError={onError}
/>
<StyledAudio controls src={asset.animationUrl} onError={onError} />
</AudioContainer>
)
}
const EmbeddedMediaPlayer = ({ asset, onError }: { asset: GenieAsset; onError: () => void }) => {
return (
<StyledEmbed>
<StyledIFrame
title={asset.name ?? `${asset.collectionName} #${asset.tokenId}`}
src={asset.animationUrl}
frameBorder={0}
sandbox="allow-scripts"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
onError={onError}
/>
</StyledEmbed>
)
}
const ContentNotAvailable = styled(ThemedText.BodySmall)`
display: flex;
background-color: ${({ theme }) => theme.backgroundSurface};
color: ${({ theme }) => theme.textSecondary};
align-items: center;
justify-content: center;
${MediaStyle}
`
// TODO: when assets query is moved to nxyz update with mediaType from the query
enum MediaType {
Audio = 'audio',
Video = 'video',
Image = 'image',
Raw = 'raw',
}
function assetMediaType(asset: GenieAsset): MediaType {
if (isAudio(asset.animationUrl ?? '')) {
return MediaType.Audio
} else if (isVideo(asset.animationUrl ?? '')) {
return MediaType.Video
} else if (asset.animationUrl) {
return MediaType.Raw
}
return MediaType.Image
}
const RenderMediaShadow = ({ imageUrl }: { imageUrl?: string }) => {
const [contentNotAvailable, setContentNotAvailable] = useState(false)
if (!imageUrl || contentNotAvailable) {
return null
}
return (
<MediaShadowContainer>
<MediaShadow src={imageUrl} onError={() => setContentNotAvailable(true)} />
</MediaShadowContainer>
)
}
const RenderMediaType = ({ asset }: { asset: GenieAsset }) => {
const [contentNotAvailable, setContentNotAvailable] = useState(false)
if (contentNotAvailable) {
return <ContentNotAvailable>Content not available</ContentNotAvailable>
}
switch (assetMediaType(asset)) {
case MediaType.Image:
return (
<StyledImage
src={asset.imageUrl}
alt={asset.name || asset.collectionName}
onError={() => setContentNotAvailable(true)}
/>
)
case MediaType.Video:
return (
<StyledVideo
src={asset.animationUrl}
autoPlay
controls
muted
loop
onError={() => setContentNotAvailable(true)}
/>
)
case MediaType.Audio:
return <AudioPlayer asset={asset} onError={() => setContentNotAvailable(true)} />
case MediaType.Raw:
return <EmbeddedMediaPlayer asset={asset} onError={() => setContentNotAvailable(true)} />
}
}
export const MediaRenderer = ({ asset }: { asset: GenieAsset }) => (
<>
<RenderMediaType asset={asset} />
<RenderMediaShadow imageUrl={asset.imageUrl} />
</>
)

@ -1,41 +0,0 @@
import { CollectionInfoForAsset, GenieAsset } from 'nft/types'
import { useState } from 'react'
import styled from 'styled-components/macro'
import { Z_INDEX } from 'theme/zIndex'
import { DataPage } from './DataPage'
import { LandingPage } from './LandingPage'
interface NftDetailsProps {
asset: GenieAsset
collection: CollectionInfoForAsset
}
const DetailsBackground = styled.div<{ backgroundImage: string }>`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: ${({ backgroundImage }) => `url(${backgroundImage})`};
filter: blur(100px);
opacity: ${({ theme }) => (theme.darkMode ? 0.2 : 0.24)};
`
const DetailsContentContainer = styled.div`
z-index: ${Z_INDEX.hover};
width: 100%;
`
export const NftDetails = ({ asset, collection }: NftDetailsProps) => {
const [showDataHeader, setShowDataHeader] = useState(false)
return (
<>
{asset.imageUrl && <DetailsBackground backgroundImage={asset.imageUrl} />}
<DetailsContentContainer>
<LandingPage asset={asset} collection={collection} setShowDataHeader={setShowDataHeader} />
<DataPage asset={asset} showDataHeader={showDataHeader} />
</DetailsContentContainer>
</>
)
}

@ -1,32 +0,0 @@
import { Trans } from '@lingui/macro'
import { ButtonGray, ButtonPrimary } from 'components/Button'
import { HandHoldingDollarIcon } from 'nft/components/icons'
import styled from 'styled-components/macro'
import { ButtonStyles } from './shared'
const MakeOfferButtonSmall = styled(ButtonPrimary)`
padding: 16px;
${ButtonStyles}
`
const MakeOfferButtonLarge = styled(ButtonGray)`
white-space: nowrap;
${ButtonStyles}
`
export const OfferButton = ({ smallVersion }: { smallVersion?: boolean }) => {
if (smallVersion) {
return (
<MakeOfferButtonSmall>
<HandHoldingDollarIcon />
</MakeOfferButtonSmall>
)
}
return (
<MakeOfferButtonLarge>
<Trans>Make an offer</Trans>
</MakeOfferButtonLarge>
)
}

@ -1,28 +0,0 @@
import { Trans } from '@lingui/macro'
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
import { useIsMobile } from 'nft/hooks'
import { GenieAsset } from 'nft/types'
import { Check } from 'react-feather'
import { useTheme } from 'styled-components/macro'
import { TEST_OFFER } from 'test-utils/nft/fixtures'
import { TableTabsKeys } from './DataPageTable'
import { TableContentComponent } from './TableContentComponent'
import { ContentRow, HeaderRow } from './TableRowComponent'
export const OffersTableContent = ({ asset }: { asset: GenieAsset }) => {
// TODO(NFT-1114) Replace with real offer data when BE supports
const mockOffers = new Array(11).fill(TEST_OFFER)
const isMobile = useIsMobile()
const theme = useTheme()
const headers = <HeaderRow type={TableTabsKeys.Offers} is1155={asset.tokenType === NftStandard.Erc1155} />
const contentRows = mockOffers.map((offer, index) => (
<ContentRow
key={'offer_' + index}
content={offer}
buttonCTA={isMobile ? <Check color={theme.textSecondary} height="20px" width="20px" /> : <Trans>Accept</Trans>}
is1155={asset.tokenType === NftStandard.Erc1155}
/>
))
return <TableContentComponent headerRow={headers} contentRows={contentRows} type={TableTabsKeys.Offers} />
}

@ -1,85 +0,0 @@
import { Trans } from '@lingui/macro'
import Row from 'components/Row'
import { Trait } from 'nft/types'
import styled from 'styled-components/macro'
import { colors } from 'theme/colors'
const RarityBar = styled.div<{ $color?: string }>`
background: ${({ $color, theme }) => $color ?? theme.backgroundOutline};
width: 2px;
height: 10px;
border-radius: 2px;
`
interface RarityValue {
threshold: number
color: string
caption: React.ReactNode
}
enum RarityLevel {
VeryCommon = 'Very Common',
Common = 'Common',
Rare = 'Rare',
VeryRare = 'Very Rare',
ExtremelyRare = 'Extremely Rare',
}
const RarityLevels: { [key in RarityLevel]: RarityValue } = {
[RarityLevel.VeryCommon]: {
threshold: 0.8,
color: colors.gray500,
caption: <Trans>Very common</Trans>,
},
[RarityLevel.Common]: {
threshold: 0.6,
color: colors.green300,
caption: <Trans>Common</Trans>,
},
[RarityLevel.Rare]: {
threshold: 0.4,
color: colors.blueVibrant,
caption: <Trans>Rare</Trans>,
},
[RarityLevel.VeryRare]: {
threshold: 0.2,
color: colors.purpleVibrant,
caption: <Trans>Very rare</Trans>,
},
[RarityLevel.ExtremelyRare]: {
threshold: 0,
color: colors.magentaVibrant,
caption: <Trans>Extremely rare</Trans>,
},
}
export function getRarityLevel(rarity: number) {
switch (true) {
case rarity > RarityLevels[RarityLevel.VeryCommon].threshold:
return RarityLevels[RarityLevel.VeryCommon]
case rarity > RarityLevels[RarityLevel.Common].threshold:
return RarityLevels[RarityLevel.Common]
case rarity > RarityLevels[RarityLevel.Rare].threshold:
return RarityLevels[RarityLevel.Rare]
case rarity > RarityLevels[RarityLevel.VeryRare].threshold:
return RarityLevels[RarityLevel.VeryRare]
case rarity >= RarityLevels[RarityLevel.ExtremelyRare].threshold:
return RarityLevels[RarityLevel.ExtremelyRare]
default:
return RarityLevels[RarityLevel.VeryCommon]
}
}
export const RarityGraph = ({ trait, rarity }: { trait: Trait; rarity: number }) => {
const rarityLevel = getRarityLevel(rarity)
return (
<Row gap="1.68px" justify="flex-end">
{Array.from({ length: 20 }).map((_, index) => (
<RarityBar
key={trait.trait_value + '_bar_' + index}
$color={index * 0.05 <= 1 - rarity ? rarityLevel?.color : undefined}
/>
))}
</Row>
)
}

@ -1,75 +0,0 @@
import Row from 'components/Row'
import { useState } from 'react'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { containerStyles } from './shared'
const TabbedComponentContainer = styled.div`
${containerStyles}
`
const TabsRow = styled(Row)`
gap: 32px;
width: 100;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
`
const Tab = styled(ThemedText.SubHeader)<{ isActive: boolean; numTabs: number }>`
color: ${({ theme, isActive }) => (isActive ? theme.textPrimary : theme.textTertiary)};
line-height: 24px;
cursor: ${({ numTabs }) => (numTabs > 1 ? 'pointer' : 'default')};
&:hover {
opacity: ${({ numTabs, theme }) => numTabs > 1 && theme.opacity.hover};
}
`
const TabNumBubble = styled(ThemedText.UtilityBadge)`
background: ${({ theme }) => theme.backgroundOutline};
border-radius: 4px;
padding: 2px 4px;
color: ${({ theme }) => theme.textSecondary};
line-height: 12px;
`
export interface Tab {
title: React.ReactNode
key: string
content: JSX.Element
count?: number
}
interface TabbedComponentProps {
tabs: Map<string, Tab>
defaultTabKey?: string
}
export const TabbedComponent = ({ tabs, defaultTabKey }: TabbedComponentProps) => {
const firstKey = tabs.keys().next().value
const [activeKey, setActiveKey] = useState(defaultTabKey ?? firstKey)
const activeContent = tabs.get(activeKey)?.content
const tabArray = Array.from(tabs.values())
return (
<TabbedComponentContainer>
<TabsRow>
{tabArray.map((tab) => (
<Tab
isActive={activeKey === tab.key}
numTabs={tabArray.length}
onClick={() => setActiveKey(tab.key)}
key={tab.key}
>
<Row gap="8px">
{tab.title}
{!!tab.count && <TabNumBubble>{tab.count > 10 ? '10+' : tab.count}</TabNumBubble>}
</Row>
</Tab>
))}
</TabsRow>
{activeContent}
</TabbedComponentContainer>
)
}

@ -1,55 +0,0 @@
import { ScrollBarStyles } from 'components/Common'
import { useSubscribeScrollState } from 'nft/hooks'
import styled from 'styled-components/macro'
import { TableTabsKeys } from './DataPageTable'
import { Scrim } from './shared'
const TableRowsContainer = styled.div`
position: relative;
`
const TableRowScrollableContainer = styled.div`
overflow-y: auto;
overflow-x: hidden;
max-height: 264px;
${ScrollBarStyles}
`
const TableHeaderRowContainer = styled.div<{ userCanScroll: boolean }>`
margin-right: ${({ userCanScroll }) => (userCanScroll ? '11px' : '0')};
`
const TableRowContainer = styled.div`
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
&:last-child {
border-bottom: none;
}
`
interface TableContentComponentProps {
headerRow: React.ReactNode
contentRows: React.ReactNode[]
type: TableTabsKeys
}
export const TableContentComponent = ({ headerRow, contentRows, type }: TableContentComponentProps) => {
const { userCanScroll, scrollRef, scrollProgress, scrollHandler } = useSubscribeScrollState()
return (
<>
<TableHeaderRowContainer userCanScroll={userCanScroll}>{headerRow}</TableHeaderRowContainer>
<TableRowsContainer>
{scrollProgress > 0 && <Scrim />}
<TableRowScrollableContainer ref={scrollRef} onScroll={scrollHandler}>
{contentRows.map((row, index) => (
<TableRowContainer key={type + '_row_' + index}>{row}</TableRowContainer>
))}
</TableRowScrollableContainer>
{userCanScroll && scrollProgress !== 100 && <Scrim isBottom={true} />}
</TableRowsContainer>
</>
)
}

@ -1,156 +0,0 @@
import { Trans } from '@lingui/macro'
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
import { useWeb3React } from '@web3-react/core'
import { OpacityHoverState } from 'components/Common'
import Row from 'components/Row'
import { OrderType } from 'graphql/data/__generated__/types-and-hooks'
import { useScreenSize } from 'hooks/useScreenSize'
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { HomeSearchIcon } from 'nft/components/icons'
import { Offer, SellOrder } from 'nft/types'
import { formatEth, getMarketplaceIcon, timeUntil } from 'nft/utils'
import styled from 'styled-components/macro'
import { BREAKPOINTS, ExternalLink, ThemedText } from 'theme'
import { shortenAddress } from 'utils'
import { TableTabsKeys } from './DataPageTable'
const TableCell = styled.div<{ $flex?: number; $justifyContent?: string; $color?: string; hideOnSmall?: boolean }>`
display: flex;
flex: ${({ $flex }) => $flex ?? 1};
justify-content: ${({ $justifyContent }) => $justifyContent};
color: ${({ $color }) => $color};
flex-shrink: 0;
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
display: ${({ hideOnSmall }) => (hideOnSmall ? 'none' : 'flex')};
}
`
const ActionButton = styled.div`
cursor: pointer;
white-space: nowrap;
${OpacityHoverState}
`
const USDPrice = styled(ThemedText.BodySmall)`
color: ${({ theme }) => theme.textSecondary};
line-height: 20px;
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
display: none;
}
@media screen and (min-width: ${BREAKPOINTS.lg}px) and (max-width: ${BREAKPOINTS.xl - 1}px) {
display: none;
}
`
const Link = styled(ExternalLink)`
height: 20px;
`
const PriceCell = ({ price }: { price: number }) => {
const { chainId } = useWeb3React()
const nativeCurrency = useNativeCurrency(chainId)
const parsedAmount = tryParseCurrencyAmount(price.toString(), nativeCurrency)
const usdValue = useStablecoinValue(parsedAmount)
return (
<Row gap="8px">
<ThemedText.LabelSmall color="textPrimary" lineHeight="16px">
{formatEth(price)}
</ThemedText.LabelSmall>
<USDPrice>{formatCurrencyAmount(usdValue, NumberType.FiatTokenPrice)}</USDPrice>
</Row>
)
}
export const HeaderRow = ({ type, is1155 }: { type: TableTabsKeys; is1155?: boolean }) => {
const screenSize = useScreenSize()
const isMobile = !screenSize['sm']
const isLargeScreen = screenSize['lg'] && !screenSize['xl']
const reducedPriceWidth = isMobile || isLargeScreen
return (
<Row gap="12px" padding="6px 6px 6px 0px">
<HomeSearchIcon />
<TableCell $flex={reducedPriceWidth ? 1 : 1.75}>
<ThemedText.SubHeaderSmall color="textSecondary">
<Trans>Price</Trans>
</ThemedText.SubHeaderSmall>
</TableCell>
{is1155 && (
<TableCell $flex={0.5}>
<ThemedText.SubHeaderSmall color="textSecondary">
<Trans>Quantity</Trans>
</ThemedText.SubHeaderSmall>
</TableCell>
)}
{(type === TableTabsKeys.Offers || is1155) && (
<TableCell hideOnSmall={true}>
<ThemedText.SubHeaderSmall color="textSecondary">
{type === TableTabsKeys.Offers ? <Trans>From</Trans> : <Trans>Seller</Trans>}
</ThemedText.SubHeaderSmall>
</TableCell>
)}
<TableCell $justifyContent="flex-end">
<ThemedText.SubHeaderSmall color="textSecondary">
<Trans>Expires in</Trans>
</ThemedText.SubHeaderSmall>
</TableCell>
{/* An empty cell is needed in the headers for proper vertical alignment with the action buttons */}
<TableCell $flex={isMobile ? 0.25 : 1}>&nbsp;</TableCell>
</Row>
)
}
export const ContentRow = ({
content,
buttonCTA,
is1155,
}: {
content: Offer | SellOrder
buttonCTA: React.ReactNode
is1155?: boolean
}) => {
const screenSize = useScreenSize()
const isMobile = !screenSize['sm']
const date = content.endAt && new Date(content.endAt)
const isSellOrder = 'type' in content && content.type === OrderType.Listing
const reducedPriceWidth = isMobile || (screenSize['lg'] && !screenSize['xl'])
return (
<Row gap="12px" padding="16px 6px 16px 0px">
<Link href={content.marketplaceUrl}>{getMarketplaceIcon(content.marketplace, '20')}</Link>
{content.price && (
<TableCell $flex={reducedPriceWidth ? 1 : 1.75}>
<PriceCell price={content.price.value} />
</TableCell>
)}
{is1155 && (
<TableCell $flex={0.5} $justifyContent="center">
<ThemedText.SubHeaderSmall color="textPrimary">{content.quantity}</ThemedText.SubHeaderSmall>
</TableCell>
)}
{(!isSellOrder || is1155) && (
<TableCell hideOnSmall={true}>
<Link href={`https://etherscan.io/address/${content.maker}`}>
<ThemedText.LabelSmall color="textPrimary">{shortenAddress(content.maker)}</ThemedText.LabelSmall>
</Link>
</TableCell>
)}
<TableCell $justifyContent="flex-end">
<ThemedText.LabelSmall color="textPrimary">
{date ? timeUntil(date) : <Trans>Never</Trans>}
</ThemedText.LabelSmall>
</TableCell>
<TableCell $flex={isMobile ? 0.25 : 1} $justifyContent="center">
<ActionButton>
<ThemedText.LabelSmall color="textSecondary">{buttonCTA}</ThemedText.LabelSmall>
</ActionButton>
</TableCell>
</Row>
)
}

@ -1,35 +0,0 @@
import userEvent from '@testing-library/user-event'
import { HistoryDuration } from 'graphql/data/__generated__/types-and-hooks'
import { act, render, screen } from 'test-utils/render'
import { TimePeriodSwitcher } from './TimePeriodSwitcher'
describe('NFT Details Activity Time Period Switcher', () => {
const mockSetTimePeriod = jest.fn()
it('renders when week is selected', () => {
render(<TimePeriodSwitcher activeTimePeriod={HistoryDuration.Week} setTimePeriod={mockSetTimePeriod} />)
expect(screen.queryByTestId('activity-time-period-switcher')?.textContent).toBe('1 week')
})
it('renders when month is selected', () => {
render(<TimePeriodSwitcher activeTimePeriod={HistoryDuration.Month} setTimePeriod={mockSetTimePeriod} />)
expect(screen.queryByTestId('activity-time-period-switcher')?.textContent).toBe('1 month')
})
it('renders when year is selected', () => {
render(<TimePeriodSwitcher activeTimePeriod={HistoryDuration.Year} setTimePeriod={mockSetTimePeriod} />)
expect(screen.queryByTestId('activity-time-period-switcher')?.textContent).toBe('1 year')
})
it('renders when all time is selected', () => {
render(<TimePeriodSwitcher activeTimePeriod={HistoryDuration.Max} setTimePeriod={mockSetTimePeriod} />)
expect(screen.queryByTestId('activity-time-period-switcher')?.textContent).toBe('All time')
})
it('renders dropdown when clicked', async () => {
render(<TimePeriodSwitcher activeTimePeriod={HistoryDuration.Max} setTimePeriod={mockSetTimePeriod} />)
await act(() => userEvent.click(screen.getByTestId('activity-time-period-switcher')))
expect(screen.queryByTestId('activity-time-period-switcher-dropdown')).toBeTruthy()
})
})

@ -1,119 +0,0 @@
import { Trans } from '@lingui/macro'
import Column from 'components/Column'
import { OpacityHoverState } from 'components/Common'
import Row from 'components/Row'
import { HistoryDuration } from 'graphql/data/__generated__/types-and-hooks'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { Dispatch, ReactNode, SetStateAction, useReducer, useRef } from 'react'
import { Check, ChevronDown } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from 'theme'
import { Z_INDEX } from 'theme/zIndex'
const SwitcherAndDropdownWrapper = styled.div`
position: relative;
`
const SwitcherWrapper = styled(Row)`
gap: 4px;
padding: 8px;
cursor: pointer;
border-radius: 12px;
width: 92px;
justify-content: space-between;
user-select: none;
background: ${({ theme }) => theme.backgroundInteractive};
${OpacityHoverState}
`
const Chevron = styled(ChevronDown)<{ $isOpen: boolean }>`
height: 16px;
width: 16px;
color: ${({ theme }) => theme.textSecondary};
transform: ${({ $isOpen }) => ($isOpen ? 'rotate(180deg)' : 'rotate(0deg)')};
transition: transform ${({ theme }) => theme.transition.duration.fast};
`
const TimeDropdownMenu = styled(Column)`
background-color: ${({ theme }) => theme.backgroundSurface};
box-shadow: ${({ theme }) => theme.deepShadow};
border: 0.5px solid ${({ theme }) => theme.backgroundOutline};
border-radius: 12px;
padding: 10px 8px;
gap: 8px;
position: absolute;
top: 42px;
z-index: ${Z_INDEX.dropdown}};
right: 0px;
width: 240px;
`
const DropdownContent = styled(Row)`
gap: 4px;
padding: 10px 8px;
width: 100%;
justify-content: space-between;
border-radius: 8px;
cursor: pointer;
&:hover {
background-color: ${({ theme }) => theme.stateOverlayHover};
}
`
const supportedTimePeriods = [
HistoryDuration.Week,
HistoryDuration.Month,
HistoryDuration.Year,
HistoryDuration.Max,
] as const
export type SupportedTimePeriodsType = (typeof supportedTimePeriods)[number]
const supportedTimePeriodsData: Record<SupportedTimePeriodsType, ReactNode> = {
[HistoryDuration.Week]: <Trans>1 week</Trans>,
[HistoryDuration.Month]: <Trans>1 month</Trans>,
[HistoryDuration.Year]: <Trans>1 year</Trans>,
[HistoryDuration.Max]: <Trans>All time</Trans>,
}
export const TimePeriodSwitcher = ({
activeTimePeriod,
setTimePeriod,
}: {
activeTimePeriod: SupportedTimePeriodsType
setTimePeriod: Dispatch<SetStateAction<SupportedTimePeriodsType>>
}) => {
const theme = useTheme()
const [isOpen, toggleIsOpen] = useReducer((isOpen) => !isOpen, false)
const menuRef = useRef<HTMLDivElement>(null)
useOnClickOutside(menuRef, () => {
isOpen && toggleIsOpen()
})
return (
<SwitcherAndDropdownWrapper ref={menuRef}>
<SwitcherWrapper onClick={toggleIsOpen} data-testid="activity-time-period-switcher">
<ThemedText.LabelSmall lineHeight="16px" color="textPrimary">
{supportedTimePeriodsData[activeTimePeriod]}
</ThemedText.LabelSmall>
<Chevron $isOpen={isOpen} />
</SwitcherWrapper>
{isOpen && (
<TimeDropdownMenu data-testid="activity-time-period-switcher-dropdown">
{supportedTimePeriods.map((timePeriod) => (
<DropdownContent
key={timePeriod}
onClick={() => {
setTimePeriod(timePeriod)
toggleIsOpen()
}}
>
<ThemedText.BodyPrimary lineHeight="24px">{supportedTimePeriodsData[timePeriod]}</ThemedText.BodyPrimary>
<Check size="16px" color={theme.accentActive} opacity={activeTimePeriod === timePeriod ? 1 : 0} />
</DropdownContent>
))}
</TimeDropdownMenu>
)}
</SwitcherAndDropdownWrapper>
)
}

@ -1,79 +0,0 @@
import Column from 'components/Column'
import Row from 'components/Row'
import { Trait } from 'nft/types'
import { formatEth, getLinkForTrait } from 'nft/utils'
import { Link } from 'react-router-dom'
import styled from 'styled-components/macro'
import { BREAKPOINTS, ThemedText } from 'theme'
import { getRarityLevel, RarityGraph } from './RarityGraph'
const TraitRowLink = styled(Link)`
text-decoration: none;
`
const SubheaderTiny = styled.div<{ $color?: string }>`
font-size: 10px;
line-height: 16px;
font-weight: 600;
color: ${({ theme, $color }) => ($color ? $color : theme.textSecondary)};
`
const SubheaderTinyHidden = styled(SubheaderTiny)`
opacity: 0;
`
const TraitRowContainer = styled(Row)`
padding: 12px 18px 12px 12px;
border-radius: 12px;
cursor: pointer;
text-decoration: none;
&:hover {
background: ${({ theme }) => theme.hoverDefault};
${SubheaderTinyHidden} {
opacity: 1;
}
}
`
const TraitColumnValue = styled(Column)<{ $flex?: number; $alignItems?: string }>`
gap: 4px;
flex: ${({ $flex }) => $flex ?? 3};
align-items: ${({ $alignItems }) => $alignItems};
`
const TraitRowValue = styled(ThemedText.BodySmall)<{ $flex?: number; $justifyContent?: string; hideOnSmall?: boolean }>`
display: flex;
line-height: 20px;
padding-top: 20px;
flex: ${({ $flex }) => $flex ?? 1};
justify-content: ${({ $justifyContent }) => $justifyContent};
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
display: ${({ hideOnSmall }) => (hideOnSmall ? 'none' : 'flex')};
}
`
export const TraitRow = ({ trait, collectionAddress }: { trait: Trait; collectionAddress: string }) => {
// TODO(NFT-1114): Replace with actual rarity, count, and floor price when BE supports
// rarity eventually should be number of items with this trait / total number of items, smaller rarity means more rare
const randomRarity = Math.random()
const rarityLevel = getRarityLevel(randomRarity)
return (
<TraitRowLink to={getLinkForTrait(trait, collectionAddress)}>
<TraitRowContainer>
<TraitColumnValue>
<SubheaderTiny>{trait.trait_type}</SubheaderTiny>
<ThemedText.BodyPrimary lineHeight="20px">{trait.trait_value}</ThemedText.BodyPrimary>
</TraitColumnValue>
<TraitRowValue $flex={2}>{formatEth(randomRarity * 1000)} ETH</TraitRowValue>
<TraitRowValue hideOnSmall={true}>{Math.round(randomRarity * 10000)}</TraitRowValue>
<TraitColumnValue $flex={1.5} $alignItems="flex-end">
<SubheaderTinyHidden $color={rarityLevel.color}>{rarityLevel.caption}</SubheaderTinyHidden>
<RarityGraph trait={trait} rarity={randomRarity} />
</TraitColumnValue>
</TraitRowContainer>
</TraitRowLink>
)
}

@ -1,617 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Header loads with asset with a sell order 1`] = `
<DocumentFragment>
.c0 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c9 {
box-sizing: border-box;
margin: 0;
min-width: 0;
justify-self: flex-end;
width: -webkit-min-content;
width: -moz-min-content;
width: min-content;
}
.c11 {
box-sizing: border-box;
margin: 0;
min-width: 0;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
display: inline-block;
text-align: center;
line-height: inherit;
-webkit-text-decoration: none;
text-decoration: none;
font-size: inherit;
padding-left: 16px;
padding-right: 16px;
padding-top: 8px;
padding-bottom: 8px;
color: white;
background-color: primary;
border: 0;
border-radius: 4px;
}
.c1 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c6 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 4px;
}
.c10 {
width: -webkit-min-content;
width: -moz-min-content;
width: min-content;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
}
.c7 {
color: #7780A0;
}
.c8 {
color: #0D111C;
}
.c4 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c12 {
padding: 16px;
width: 100%;
font-weight: 500;
text-align: center;
border-radius: 20px;
outline: none;
border: 1px solid transparent;
color: #0D111C;
-webkit-text-decoration: none;
text-decoration: none;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-flex-wrap: nowrap;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
cursor: pointer;
position: relative;
z-index: 1;
will-change: transform;
-webkit-transition: -webkit-transform 450ms ease;
-webkit-transition: transform 450ms ease;
transition: transform 450ms ease;
-webkit-transform: perspective(1px) translateZ(0);
-ms-transform: perspective(1px) translateZ(0);
transform: perspective(1px) translateZ(0);
}
.c12:disabled {
opacity: 50%;
cursor: auto;
pointer-events: none;
}
.c12 > * {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.c12 > a {
-webkit-text-decoration: none;
text-decoration: none;
}
.c13 {
background-color: #FB118E;
font-size: 20px;
font-weight: 600;
padding: 16px;
color: #F5F6FC;
}
.c13:focus {
box-shadow: 0 0 0 1pt #ee0481;
background-color: #ee0481;
}
.c13:hover {
background-color: #ee0481;
}
.c13:active {
box-shadow: 0 0 0 1pt #d50474;
background-color: #d50474;
}
.c13:disabled {
background-color: #E8ECFB;
color: #7780A0;
cursor: auto;
box-shadow: none;
border: 1px solid transparent;
outline: none;
}
.c16 {
padding: 16px;
width: -webkit-min-content;
width: -moz-min-content;
width: min-content;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
border-radius: 16px;
}
.c14 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
padding: 16px 24px;
gap: 8px;
line-height: 24px;
white-space: nowrap;
width: -webkit-min-content;
width: -moz-min-content;
width: min-content;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
border-radius: 16px;
}
.c15 {
color: #F5F6FCb8;
}
.c2 {
gap: 24px;
}
.c3 {
width: 96px;
height: 96px;
border-radius: 20px;
object-fit: cover;
}
.c5 {
gap: 4px;
margin-right: auto;
}
@media screen and (max-width:1024px) {
.c3 {
display: none;
}
}
<div
class="c0 c1 c2"
>
<img
class="c3"
src="https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/3318/50ed67ad647d0aa0cad0b830d136a677efc2fb72a44587bc35f2a5fb334a7fdf.png"
/>
<div
class="c4 c5"
>
<div
class="c0 c6"
>
<div
class="c7 css-1aekuku"
>
Azuki
</div>
<svg
fill="none"
height="16px"
viewBox="0 0 20 20"
width="16px"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.52795 13.8056C4.52719 14.4043 4.6712 14.8474 4.95997 15.135C5.24798 15.4233 5.68496 15.5651 6.27091 15.5605H7.57497C7.62945 15.5585 7.68379 15.5676 7.73463 15.5873C7.78547 15.607 7.83176 15.6369 7.87062 15.6752L8.79884 16.5928C9.22054 17.0142 9.63382 17.2237 10.0387 17.2214C10.4436 17.2191 10.8569 17.0096 11.2786 16.5928L12.1954 15.6752C12.2356 15.6365 12.2832 15.6063 12.3354 15.5866C12.3876 15.5669 12.4433 15.558 12.499 15.5605H13.7951C14.3871 15.5613 14.8283 15.4171 15.1186 15.1281C15.4089 14.839 15.5541 14.3959 15.5541 13.7987V12.5014C15.5511 12.389 15.5923 12.2799 15.6687 12.1974L16.5854 11.2798C17.0125 10.86 17.2245 10.4467 17.2214 10.0399C17.2184 9.63305 17.0064 9.21935 16.5854 8.79878L15.6687 7.88115C15.592 7.79886 15.5509 7.68965 15.5541 7.57719V6.2799C15.5533 5.68191 15.4093 5.23878 15.1221 4.95049C14.8348 4.66221 14.3925 4.51806 13.7951 4.51806H12.499C12.4433 4.52036 12.3877 4.51138 12.3355 4.49168C12.2834 4.47197 12.2357 4.44193 12.1954 4.40336L11.2786 3.48574C10.8569 3.06439 10.4436 2.85487 10.0387 2.85717C9.63382 2.85946 9.22054 3.06898 8.79884 3.48574L7.87062 4.40336C7.83164 4.44148 7.78536 4.4713 7.73454 4.49101C7.68373 4.51072 7.62943 4.51993 7.57497 4.51806H6.27091C5.67961 4.51883 5.23995 4.66182 4.95194 4.94705C4.66393 5.23228 4.51992 5.67656 4.51992 6.2799V7.58063C4.52314 7.69309 4.48197 7.80229 4.40533 7.88459L3.48859 8.80222C3.06765 9.22203 2.85718 9.63572 2.85718 10.0433C2.85718 10.4509 3.07033 10.8653 3.49662 11.2867L4.41336 12.2043C4.48979 12.2867 4.53092 12.3958 4.52795 12.5083V13.8056Z"
fill="#FB118E"
/>
<path
d="M9.99737 12.4943C9.86205 12.7005 9.6623 12.8164 9.43032 12.8164C9.19191 12.8164 9.00504 12.7198 8.83106 12.4943L7.31036 10.6385C7.20082 10.5032 7.14282 10.3614 7.14282 10.2068C7.14282 9.88458 7.38768 9.63327 7.70342 9.63327C7.89673 9.63327 8.05138 9.70415 8.20603 9.90391L9.40455 11.4311L11.9498 7.34577C12.0851 7.12669 12.2591 7.02359 12.4524 7.02359C12.7553 7.02359 13.0388 7.23623 13.0388 7.55197C13.0388 7.70017 12.9615 7.85482 12.8777 7.99014L9.99737 12.4943Z"
fill="white"
/>
</svg>
</div>
<div
class="c8 css-1tiu9da"
>
Azuki #3318
</div>
</div>
<div
class="c9 c10"
width="min-content"
>
<button
class="c11 c12 c13 c14"
>
Buy
<div
class="c15"
>
100.00M ETH
</div>
</button>
<button
class="c11 c12 c13 c16"
>
<svg
fill="none"
height="24"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 22H3C2.448 22 2 21.552 2 21V17C2 16.448 2.448 16 3 16H5C5.552 16 6 16.448 6 17V21C6 21.552 5.552 22 5 22ZM19.66 16.02C19.43 16.02 19.2 16.08 18.98 16.21L16.71 17.5699C16.5 18.7999 15.42 19.75 14.12 19.75H11C10.59 19.75 10.25 19.41 10.25 19C10.25 18.59 10.59 18.25 11 18.25H14.12C14.74 18.25 15.25 17.75 15.25 17.12C15.25 16.5 14.74 16 14.12 16H9C7.9 16 7 16.9 7 18V20C7 21.1 7.9 22 9 22H14.6C15.51 22 16.39 21.69 17.1 21.12L20.5 18.4C20.82 18.15 21 17.76 21 17.36C21 16.58 20.36 16.02 19.66 16.02ZM18 7.5C18 10.809 15.309 13.5 12 13.5C8.691 13.5 6 10.809 6 7.5C6 4.191 8.691 1.5 12 1.5C15.309 1.5 18 4.191 18 7.5ZM14.25 8.91199C14.25 7.96999 13.626 7.14894 12.731 6.91394L11.646 6.63403C11.535 6.60503 11.438 6.53894 11.363 6.43994C11.29 6.34394 11.25 6.21901 11.25 6.08801C11.25 5.77901 11.48 5.52698 11.764 5.52698H12.237C12.497 5.52698 12.717 5.74102 12.748 6.02502C12.792 6.43702 13.157 6.73194 13.575 6.68994C13.987 6.64594 14.284 6.27504 14.24 5.86304C14.146 4.99204 13.531 4.307 12.737 4.099V3.99902C12.737 3.58502 12.401 3.24902 11.987 3.24902C11.573 3.24902 11.237 3.58502 11.237 3.99902V4.10803C10.384 4.34703 9.75201 5.13904 9.75201 6.08704C9.75201 6.54404 9.90101 6.99004 10.169 7.34204C10.442 7.70604 10.833 7.96898 11.272 8.08398L12.357 8.36401C12.59 8.42501 12.753 8.65003 12.753 8.91003C12.753 9.06303 12.696 9.20696 12.593 9.31396C12.536 9.37296 12.416 9.46997 12.239 9.46997H11.766C11.506 9.46997 11.286 9.25605 11.255 8.97205C11.211 8.56005 10.847 8.26401 10.428 8.30701C10.016 8.35101 9.719 8.72203 9.763 9.13403C9.856 9.99303 10.456 10.671 11.236 10.889V11C11.236 11.414 11.572 11.75 11.986 11.75C12.4 11.75 12.736 11.414 12.736 11V10.9C13.085 10.808 13.408 10.6281 13.67 10.3571C14.044 9.96906 14.25 9.45499 14.25 8.91199Z"
fill="white"
/>
</svg>
</button>
</div>
</div>
</DocumentFragment>
`;
exports[`Header loads with asset with no sell orders 1`] = `
<DocumentFragment>
.c0 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c9 {
box-sizing: border-box;
margin: 0;
min-width: 0;
justify-self: flex-end;
width: -webkit-min-content;
width: -moz-min-content;
width: min-content;
}
.c11 {
box-sizing: border-box;
margin: 0;
min-width: 0;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
display: inline-block;
text-align: center;
line-height: inherit;
-webkit-text-decoration: none;
text-decoration: none;
font-size: inherit;
padding-left: 16px;
padding-right: 16px;
padding-top: 8px;
padding-bottom: 8px;
color: white;
background-color: primary;
border: 0;
border-radius: 4px;
}
.c1 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c6 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 4px;
}
.c10 {
width: -webkit-min-content;
width: -moz-min-content;
width: min-content;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 12px;
}
.c7 {
color: #7780A0;
}
.c8 {
color: #0D111C;
}
.c4 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c12 {
padding: 16px;
width: 100%;
font-weight: 500;
text-align: center;
border-radius: 20px;
outline: none;
border: 1px solid transparent;
color: #0D111C;
-webkit-text-decoration: none;
text-decoration: none;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-flex-wrap: nowrap;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
cursor: pointer;
position: relative;
z-index: 1;
will-change: transform;
-webkit-transition: -webkit-transform 450ms ease;
-webkit-transition: transform 450ms ease;
transition: transform 450ms ease;
-webkit-transform: perspective(1px) translateZ(0);
-ms-transform: perspective(1px) translateZ(0);
transform: perspective(1px) translateZ(0);
}
.c12:disabled {
opacity: 50%;
cursor: auto;
pointer-events: none;
}
.c12 > * {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.c12 > a {
-webkit-text-decoration: none;
text-decoration: none;
}
.c13 {
background-color: #F5F6FC;
color: #7780A0;
font-size: 16px;
font-weight: 500;
}
.c13:hover {
background-color: #d2daf7;
}
.c13:active {
background-color: #bdc8f3;
}
.c14 {
white-space: nowrap;
width: -webkit-min-content;
width: -moz-min-content;
width: min-content;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
border-radius: 16px;
}
.c2 {
gap: 24px;
}
.c3 {
width: 96px;
height: 96px;
border-radius: 20px;
object-fit: cover;
}
.c5 {
gap: 4px;
margin-right: auto;
}
@media screen and (max-width:1024px) {
.c3 {
display: none;
}
}
<div
class="c0 c1 c2"
>
<img
class="c3"
src="https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/3318/50ed67ad647d0aa0cad0b830d136a677efc2fb72a44587bc35f2a5fb334a7fdf.png"
/>
<div
class="c4 c5"
>
<div
class="c0 c6"
>
<div
class="c7 css-1aekuku"
>
Azuki
</div>
<svg
fill="none"
height="16px"
viewBox="0 0 20 20"
width="16px"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.52795 13.8056C4.52719 14.4043 4.6712 14.8474 4.95997 15.135C5.24798 15.4233 5.68496 15.5651 6.27091 15.5605H7.57497C7.62945 15.5585 7.68379 15.5676 7.73463 15.5873C7.78547 15.607 7.83176 15.6369 7.87062 15.6752L8.79884 16.5928C9.22054 17.0142 9.63382 17.2237 10.0387 17.2214C10.4436 17.2191 10.8569 17.0096 11.2786 16.5928L12.1954 15.6752C12.2356 15.6365 12.2832 15.6063 12.3354 15.5866C12.3876 15.5669 12.4433 15.558 12.499 15.5605H13.7951C14.3871 15.5613 14.8283 15.4171 15.1186 15.1281C15.4089 14.839 15.5541 14.3959 15.5541 13.7987V12.5014C15.5511 12.389 15.5923 12.2799 15.6687 12.1974L16.5854 11.2798C17.0125 10.86 17.2245 10.4467 17.2214 10.0399C17.2184 9.63305 17.0064 9.21935 16.5854 8.79878L15.6687 7.88115C15.592 7.79886 15.5509 7.68965 15.5541 7.57719V6.2799C15.5533 5.68191 15.4093 5.23878 15.1221 4.95049C14.8348 4.66221 14.3925 4.51806 13.7951 4.51806H12.499C12.4433 4.52036 12.3877 4.51138 12.3355 4.49168C12.2834 4.47197 12.2357 4.44193 12.1954 4.40336L11.2786 3.48574C10.8569 3.06439 10.4436 2.85487 10.0387 2.85717C9.63382 2.85946 9.22054 3.06898 8.79884 3.48574L7.87062 4.40336C7.83164 4.44148 7.78536 4.4713 7.73454 4.49101C7.68373 4.51072 7.62943 4.51993 7.57497 4.51806H6.27091C5.67961 4.51883 5.23995 4.66182 4.95194 4.94705C4.66393 5.23228 4.51992 5.67656 4.51992 6.2799V7.58063C4.52314 7.69309 4.48197 7.80229 4.40533 7.88459L3.48859 8.80222C3.06765 9.22203 2.85718 9.63572 2.85718 10.0433C2.85718 10.4509 3.07033 10.8653 3.49662 11.2867L4.41336 12.2043C4.48979 12.2867 4.53092 12.3958 4.52795 12.5083V13.8056Z"
fill="#FB118E"
/>
<path
d="M9.99737 12.4943C9.86205 12.7005 9.6623 12.8164 9.43032 12.8164C9.19191 12.8164 9.00504 12.7198 8.83106 12.4943L7.31036 10.6385C7.20082 10.5032 7.14282 10.3614 7.14282 10.2068C7.14282 9.88458 7.38768 9.63327 7.70342 9.63327C7.89673 9.63327 8.05138 9.70415 8.20603 9.90391L9.40455 11.4311L11.9498 7.34577C12.0851 7.12669 12.2591 7.02359 12.4524 7.02359C12.7553 7.02359 13.0388 7.23623 13.0388 7.55197C13.0388 7.70017 12.9615 7.85482 12.8777 7.99014L9.99737 12.4943Z"
fill="white"
/>
</svg>
</div>
<div
class="c8 css-1tiu9da"
>
Azuki #3318
</div>
</div>
<div
class="c9 c10"
width="min-content"
>
<button
class="c11 c12 c13 c14"
>
Make an offer
</button>
</div>
</div>
</DocumentFragment>
`;

@ -1,266 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`data page trait component does not load with asset with no traits 1`] = `
<DocumentFragment>
.c1 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c2 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c6 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 8px;
}
.c4 {
color: #0D111C;
}
.c9 {
color: #7780A0;
}
.c7 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c0 {
background: #FFFFFF;
border: 1px solid #D2D9EE;
border-radius: 16px;
padding: 16px 20px;
width: 100%;
-webkit-align-self: flex-start;
-ms-flex-item-align: start;
align-self: flex-start;
}
.c3 {
gap: 32px;
width: 100;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #D2D9EE;
}
.c5 {
color: #0D111C;
line-height: 24px;
cursor: default;
}
.c8 {
padding: 0px 12px;
}
.c10 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
line-height: 20px;
color: #7780A0;
-webkit-flex: 3;
-ms-flex: 3;
flex: 3;
}
.c11 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
line-height: 20px;
color: #7780A0;
-webkit-flex: 2;
-ms-flex: 2;
flex: 2;
}
.c12 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
line-height: 20px;
color: #7780A0;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}
.c13 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
line-height: 20px;
color: #7780A0;
-webkit-flex: 1.5;
-ms-flex: 1.5;
flex: 1.5;
-webkit-box-pack: end;
-webkit-justify-content: flex-end;
-ms-flex-pack: end;
justify-content: flex-end;
}
.c14 {
position: relative;
}
.c15 {
overflow-y: auto;
overflow-x: hidden;
max-height: 412px;
width: calc(100% + 6px);
-webkit-scrollbar-width: thin;
-moz-scrollbar-width: thin;
-ms-scrollbar-width: thin;
scrollbar-width: thin;
-webkit-scrollbar-color: #D2D9EE transparent;
-moz-scrollbar-color: #D2D9EE transparent;
-ms-scrollbar-color: #D2D9EE transparent;
scrollbar-color: #D2D9EE transparent;
height: 100%;
}
.c15::-webkit-scrollbar {
background: transparent;
width: 4px;
overflow-y: scroll;
}
.c15::-webkit-scrollbar-thumb {
background: #D2D9EE;
border-radius: 8px;
}
@media screen and (max-width:640px) {
.c10 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
}
@media screen and (max-width:640px) {
.c11 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
}
@media screen and (max-width:640px) {
.c12 {
display: none;
}
}
@media screen and (max-width:640px) {
.c13 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
}
<div
class="c0"
>
<div
class="c1 c2 c3"
>
<div
class="c4 c5 css-rjqmed"
>
<div
class="c1 c6"
>
Traits
</div>
</div>
</div>
<div
class="c7"
>
<div
class="c1 c2 c8"
>
<div
class="c9 c10 css-1aekuku"
>
Trait
</div>
<div
class="c9 c11 css-1aekuku"
>
Floor price
</div>
<div
class="c9 c12 css-1aekuku"
>
Quantity
</div>
<div
class="c9 c13 css-1aekuku"
>
Rarity
</div>
</div>
<div
class="c14"
>
<div
class="c15"
/>
</div>
</div>
</div>
</DocumentFragment>
`;

@ -1,558 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LandingPage renders it correctly 1`] = `
<DocumentFragment>
.c9 {
box-sizing: border-box;
margin: 0;
min-width: 0;
}
.c25 {
box-sizing: border-box;
margin: 0;
min-width: 0;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
display: inline-block;
text-align: center;
line-height: inherit;
-webkit-text-decoration: none;
text-decoration: none;
font-size: inherit;
padding-left: 16px;
padding-right: 16px;
padding-top: 8px;
padding-bottom: 8px;
color: white;
background-color: primary;
border: 0;
border-radius: 4px;
}
.c10 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
gap: 4px;
}
.c16 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
}
.c20 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 8px;
}
.c22 {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
padding: 0;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c18 {
color: #7780A0;
}
.c19 {
-webkit-text-decoration: none;
text-decoration: none;
cursor: pointer;
-webkit-transition-duration: 125ms;
transition-duration: 125ms;
color: #FB118E;
stroke: #FB118E;
font-weight: 500;
}
.c19:hover {
opacity: 0.6;
}
.c19:active {
opacity: 0.4;
}
.c5 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c15 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
gap: 8px;
}
.c6 {
width: 100%;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c26 {
padding: 16px;
width: 100%;
font-weight: 500;
text-align: center;
border-radius: 20px;
outline: none;
border: 1px solid transparent;
color: #0D111C;
-webkit-text-decoration: none;
text-decoration: none;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-flex-wrap: nowrap;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
cursor: pointer;
position: relative;
z-index: 1;
will-change: transform;
-webkit-transition: -webkit-transform 450ms ease;
-webkit-transition: transform 450ms ease;
transition: transform 450ms ease;
-webkit-transform: perspective(1px) translateZ(0);
-ms-transform: perspective(1px) translateZ(0);
transform: perspective(1px) translateZ(0);
}
.c26:disabled {
opacity: 50%;
cursor: auto;
pointer-events: none;
}
.c26 > * {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.c26 > a {
-webkit-text-decoration: none;
text-decoration: none;
}
.c27 {
background-color: #F5F6FC;
color: #7780A0;
font-size: 16px;
font-weight: 500;
}
.c27:hover {
background-color: #d2daf7;
}
.c27:active {
background-color: #bdc8f3;
}
.c28 {
white-space: nowrap;
width: -webkit-min-content;
width: -moz-min-content;
width: min-content;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
border-radius: 16px;
}
.c21 {
background-color: #FFFFFF;
padding: 10px 12px 10px 8px;
border-radius: 20px;
max-width: 144px;
}
.c24 {
font-weight: 600;
font-size: 16px;
line-height: 20px;
color: #0D111C;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.c23 {
width: 24px;
height: 24px;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
color: #FB118E;
border-radius: 100%;
overflow: hidden;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c17 {
gap: 4px;
width: 100%;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
}
.c2 {
position: relative;
object-fit: contain;
height: 100%;
width: 100%;
aspect-ratio: 1;
z-index: 1;
}
.c4 {
object-fit: contain;
height: 100%;
aspect-ratio: 1;
border-radius: 20px;
-webkit-filter: blur(25px);
filter: blur(25px);
}
.c3 {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.c0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
min-height: calc(100vh - 72px - 52px);
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
padding: 22px 20px 0px;
gap: 26px;
width: 100%;
}
.c7 {
gap: 40px;
}
.c14 {
font-weight: 600;
font-size: 20px;
line-height: 28px;
text-align: center;
color: #0D111C;
}
.c12 {
font-weight: 500;
font-size: 14px;
line-height: 20px;
text-align: center;
color: #7780A0;
}
.c8 {
gap: 4px;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c1 {
position: relative;
width: 100%;
height: 100%;
-webkit-filter: drop-shadow(0px 12px 20px rgba(0,0,0,0.1));
filter: drop-shadow(0px 12px 20px rgba(0,0,0,0.1));
}
@media screen and (min-width:640px) {
.c21 {
max-width: 169px;
}
}
@media screen and (min-width:640px) {
.c17 {
gap: 12px;
-webkit-flex-wrap: nowrap;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
}
}
@media screen and (min-width:1280px) {
.c4 {
-webkit-filter: blur(50px);
filter: blur(50px);
}
}
@media screen and (min-width:640px) {
.c0 {
gap: 64px;
padding-top: 28px;
}
}
@media screen and (min-width:768px) {
.c0 {
min-height: calc(100vh - 72px );
}
}
@media screen and (min-width:1280px) {
.c0 {
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
padding-top: 0px;
padding-bottom: 72px;
gap: 80px;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
}
}
@media screen and (min-width:640px) {
.c7 {
width: 560px;
}
}
@media screen and (min-width:640px) {
.c14 {
line-height: 44px;
font-size: 36px;
}
}
@media screen and (min-width:640px) {
.c12 {
line-height: 24px;
font-size: 16px;
}
}
@media screen and (min-width:640px) {
.c8 .c13 {
line-height: 44px;
font-size: 36px;
}
.c8 .c11 {
line-height: 24px;
font-size: 16px;
}
}
@media screen and (min-width:640px) {
.c1 {
width: 560px;
height: 560px;
}
}
<div
class="c0"
>
<div
class="c1"
>
<img
alt="Azuki #3318"
class="c2"
src="https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/3318/50ed67ad647d0aa0cad0b830d136a677efc2fb72a44587bc35f2a5fb334a7fdf.png"
/>
<div
class="c3"
>
<img
class="c4"
src="https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/3318/50ed67ad647d0aa0cad0b830d136a677efc2fb72a44587bc35f2a5fb334a7fdf.png"
/>
</div>
</div>
<div
class="c5 c6 c7"
>
<div
class="c5 c8"
>
<div
class="c9 c10"
>
<div
class="c11 c12"
>
Azuki
</div>
<svg
fill="none"
height="16px"
viewBox="0 0 20 20"
width="16px"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.52795 13.8056C4.52719 14.4043 4.6712 14.8474 4.95997 15.135C5.24798 15.4233 5.68496 15.5651 6.27091 15.5605H7.57497C7.62945 15.5585 7.68379 15.5676 7.73463 15.5873C7.78547 15.607 7.83176 15.6369 7.87062 15.6752L8.79884 16.5928C9.22054 17.0142 9.63382 17.2237 10.0387 17.2214C10.4436 17.2191 10.8569 17.0096 11.2786 16.5928L12.1954 15.6752C12.2356 15.6365 12.2832 15.6063 12.3354 15.5866C12.3876 15.5669 12.4433 15.558 12.499 15.5605H13.7951C14.3871 15.5613 14.8283 15.4171 15.1186 15.1281C15.4089 14.839 15.5541 14.3959 15.5541 13.7987V12.5014C15.5511 12.389 15.5923 12.2799 15.6687 12.1974L16.5854 11.2798C17.0125 10.86 17.2245 10.4467 17.2214 10.0399C17.2184 9.63305 17.0064 9.21935 16.5854 8.79878L15.6687 7.88115C15.592 7.79886 15.5509 7.68965 15.5541 7.57719V6.2799C15.5533 5.68191 15.4093 5.23878 15.1221 4.95049C14.8348 4.66221 14.3925 4.51806 13.7951 4.51806H12.499C12.4433 4.52036 12.3877 4.51138 12.3355 4.49168C12.2834 4.47197 12.2357 4.44193 12.1954 4.40336L11.2786 3.48574C10.8569 3.06439 10.4436 2.85487 10.0387 2.85717C9.63382 2.85946 9.22054 3.06898 8.79884 3.48574L7.87062 4.40336C7.83164 4.44148 7.78536 4.4713 7.73454 4.49101C7.68373 4.51072 7.62943 4.51993 7.57497 4.51806H6.27091C5.67961 4.51883 5.23995 4.66182 4.95194 4.94705C4.66393 5.23228 4.51992 5.67656 4.51992 6.2799V7.58063C4.52314 7.69309 4.48197 7.80229 4.40533 7.88459L3.48859 8.80222C3.06765 9.22203 2.85718 9.63572 2.85718 10.0433C2.85718 10.4509 3.07033 10.8653 3.49662 11.2867L4.41336 12.2043C4.48979 12.2867 4.53092 12.3958 4.52795 12.5083V13.8056Z"
fill="#FB118E"
/>
<path
d="M9.99737 12.4943C9.86205 12.7005 9.6623 12.8164 9.43032 12.8164C9.19191 12.8164 9.00504 12.7198 8.83106 12.4943L7.31036 10.6385C7.20082 10.5032 7.14282 10.3614 7.14282 10.2068C7.14282 9.88458 7.38768 9.63327 7.70342 9.63327C7.89673 9.63327 8.05138 9.70415 8.20603 9.90391L9.40455 11.4311L11.9498 7.34577C12.0851 7.12669 12.2591 7.02359 12.4524 7.02359C12.7553 7.02359 13.0388 7.23623 13.0388 7.55197C13.0388 7.70017 12.9615 7.85482 12.8777 7.99014L9.99737 12.4943Z"
fill="white"
/>
</svg>
</div>
<div
class="c13 c14"
>
Azuki #3318
</div>
</div>
<div
class="c15"
>
<div
class="c9 c16 c17"
>
<div
class="c15"
>
<div
class="c18 css-4u0e4f"
>
Owner
</div>
<a
class="c19"
href="https://etherscan.io/address/"
rel="noopener noreferrer"
target="_blank"
>
<div
class="c9 c20 c21"
>
<div
class="c9 c22 c23"
/>
<div
class="c24"
/>
</div>
</a>
</div>
</div>
</div>
<button
class="c25 c26 c27 c28"
>
Make an offer
</button>
</div>
</div>
</DocumentFragment>
`;

@ -1,260 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Media renderer renders a video nft correctly 1`] = `
<DocumentFragment>
.c0 {
position: relative;
object-fit: contain;
height: 100%;
width: 100%;
aspect-ratio: 1;
z-index: 1;
}
@media screen and (min-width:1280px) {
}
<video
autoplay=""
class="c0"
controls=""
loop=""
src="https://openseauserdata.com/files/5af92728200027caa4f3f5ae87a486a7.mp4"
/>
.c1 {
object-fit: contain;
height: 100%;
aspect-ratio: 1;
border-radius: 20px;
-webkit-filter: blur(25px);
filter: blur(25px);
}
.c0 {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
@media screen and (min-width:1280px) {
.c1 {
-webkit-filter: blur(50px);
filter: blur(50px);
}
}
<div
class="c0"
>
<img
class="c1"
src="https://i.seadn.io/gae/tkDbNhjjBZV2PmYaJbJOOigywZCrlcyGRxeQFkZS1YZyihyG5GoWNWj3N9f1T7YVuaxOqdxhfJylC9ejtoCvdgBE932vd7jorVqA?w=500&auto=format"
/>
</div>
</DocumentFragment>
`;
exports[`Media renderer renders an audio nft correctly 1`] = `
<DocumentFragment>
.c1 {
position: relative;
object-fit: contain;
height: 100%;
width: 100%;
aspect-ratio: 1;
z-index: 1;
}
.c0 {
position: relative;
}
.c2 {
position: absolute;
left: 0;
bottom: 0;
z-index: 2;
width: 100%;
}
@media screen and (min-width:1280px) {
}
<div
class="c0"
>
<img
alt="Death Row Session: Vol. 2 (420 Edition) #320"
class="c1"
src="https://i.seadn.io/gae/Kze9SBqn_6O0qrHKxspo1gRkkDV2A5EmTeWtvdS-dNxBsvi_wPXUYjc6De0sUC-DYzL093102mUftenWxwWuTelqsdw-ngoBC3o2XFU?w=500&auto=format"
/>
<audio
class="c2"
controls=""
src="https://openseauserdata.com/files/4a22253e44e10baa11484a2e43efefda.mp3"
/>
</div>
.c1 {
object-fit: contain;
height: 100%;
aspect-ratio: 1;
border-radius: 20px;
-webkit-filter: blur(25px);
filter: blur(25px);
}
.c0 {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
@media screen and (min-width:1280px) {
.c1 {
-webkit-filter: blur(50px);
filter: blur(50px);
}
}
<div
class="c0"
>
<img
class="c1"
src="https://i.seadn.io/gae/Kze9SBqn_6O0qrHKxspo1gRkkDV2A5EmTeWtvdS-dNxBsvi_wPXUYjc6De0sUC-DYzL093102mUftenWxwWuTelqsdw-ngoBC3o2XFU?w=500&auto=format"
/>
</div>
</DocumentFragment>
`;
exports[`Media renderer renders an embedded nft correctly 1`] = `
<DocumentFragment>
.c0 {
position: relative;
overflow: hidden;
width: 100%;
padding-top: 100%;
z-index: 1;
}
.c1 {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
@media screen and (min-width:1280px) {
}
<div
class="c0"
>
<iframe
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
class="c1"
frameborder="0"
sandbox="allow-scripts"
src="https://tokens.mathcastles.xyz/terraforms/token-html/7202"
title="Level 13 at {28, 3}"
/>
</div>
.c1 {
object-fit: contain;
height: 100%;
aspect-ratio: 1;
border-radius: 20px;
-webkit-filter: blur(25px);
filter: blur(25px);
}
.c0 {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
@media screen and (min-width:1280px) {
.c1 {
-webkit-filter: blur(50px);
filter: blur(50px);
}
}
<div
class="c0"
>
<img
class="c1"
src="https://cdn.center.app/v2/1/06ff92279474add6ce06176e2a65447396edf786d169d8ccc03fddfa45ce004f/bb01f8a2f093ea4619498dae58fc19e5ba3fa38a84cabf92948994609489d566.png"
/>
</div>
</DocumentFragment>
`;
exports[`Media renderer renders image nft correctly 1`] = `
<DocumentFragment>
.c0 {
position: relative;
object-fit: contain;
height: 100%;
width: 100%;
aspect-ratio: 1;
z-index: 1;
}
@media screen and (min-width:1280px) {
}
<img
alt="Azuki #3318"
class="c0"
src="https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/3318/50ed67ad647d0aa0cad0b830d136a677efc2fb72a44587bc35f2a5fb334a7fdf.png"
/>
.c1 {
object-fit: contain;
height: 100%;
aspect-ratio: 1;
border-radius: 20px;
-webkit-filter: blur(25px);
filter: blur(25px);
}
.c0 {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
@media screen and (min-width:1280px) {
.c1 {
-webkit-filter: blur(50px);
filter: blur(50px);
}
}
<div
class="c0"
>
<img
class="c1"
src="https://cdn.center.app/1/0xED5AF388653567Af2F388E6224dC7C4b3241C544/3318/50ed67ad647d0aa0cad0b830d136a677efc2fb72a44587bc35f2a5fb334a7fdf.png"
/>
</div>
</DocumentFragment>
`;

@ -1,38 +0,0 @@
import styled, { css } from 'styled-components/macro'
import { opacify } from 'theme/utils'
export const containerStyles = css`
background: ${({ theme }) => theme.backgroundSurface};
border: 1px solid ${({ theme }) => theme.backgroundOutline};
border-radius: 16px;
padding: 16px 20px;
width: 100%;
align-self: flex-start;
`
// Scrim that fades out the top and bottom of the scrollable container, isBottom changes the direction and placement of the fade
export const Scrim = styled.div<{ isBottom?: boolean }>`
position: absolute;
pointer-events: none;
height: 88px;
left: 0px;
right: 6px;
${({ isBottom }) =>
isBottom
? 'bottom: 0px'
: `
top: 0px;
transform: matrix(1, 0, 0, -1, 0, 0);
`};
background: ${({ theme }) =>
`linear-gradient(180deg, ${opacify(0, theme.backgroundSurface)} 0%, ${theme.backgroundSurface} 100%)`};
display: flex;
`
export const ButtonStyles = css`
width: min-content;
flex-shrink: 0;
border-radius: 16px;
`

@ -1350,41 +1350,3 @@ export const UniswapMagentaIcon = (props: SVGProps) => (
/> />
</svg> </svg>
) )
export const HomeSearchIcon = (props: SVGProps) => (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M17.898 7.57097L11.7212 2.49102C10.7237 1.67268 9.27795 1.67185 8.28045 2.49102L2.10379 7.57016C1.83796 7.78932 1.79877 8.18268 2.01794 8.45018C2.2371 8.71768 2.63213 8.75437 2.89796 8.53604L3.54209 8.00605V15.0002C3.54209 17.0152 4.65209 18.1252 6.66709 18.1252H13.3338C15.3488 18.1252 16.4588 17.0152 16.4588 15.0002V8.00605L17.1029 8.53604C17.2195 8.63187 17.3604 8.67845 17.5004 8.67845C17.6804 8.67845 17.8596 8.601 17.9829 8.451C18.2029 8.1835 18.1638 7.79014 17.898 7.57097ZM15.2088 15.0002C15.2088 16.3143 14.6479 16.8752 13.3338 16.8752H6.66709C5.35292 16.8752 4.79209 16.3143 4.79209 15.0002V6.97852L9.07462 3.45771C9.61045 3.01688 10.3913 3.01688 10.9271 3.45771L15.2096 6.97934V15.0002H15.2088ZM6.45875 10.7643C6.45875 12.4493 7.82958 13.8202 9.51458 13.8202C10.1312 13.8202 10.7038 13.6335 11.1838 13.3176L12.4746 14.6085C12.5962 14.7302 12.7563 14.7918 12.9163 14.7918C13.0763 14.7918 13.2363 14.731 13.358 14.6085C13.6021 14.3644 13.6021 13.9685 13.358 13.7243L12.0663 12.4326C12.3813 11.9518 12.568 11.3794 12.568 10.7627C12.568 9.07854 11.1971 7.70688 9.51295 7.70688C7.82962 7.70854 6.45875 9.07933 6.45875 10.7643ZM11.3196 10.7643C11.3196 11.7602 10.5096 12.5702 9.51458 12.5702C8.51875 12.5702 7.70875 11.7602 7.70875 10.7643C7.70875 9.7685 8.51875 8.9585 9.51458 8.9585C10.5096 8.9585 11.3196 9.7685 11.3196 10.7643Z"
fill="#7780A0"
/>
</svg>
)
export const AddToBagIcon = (props: SVGProps) => (
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M8.51389 18.25H5.44444C4.6467 18.25 4 17.653 4 16.9167V7.58333C4 6.84695 4.6467 6.25 5.44444 6.25H14.5556C15.3533 6.25 16 6.84695 16 7.58333V10.25"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7 6.25L7 5.45C7 4.60131 7.31607 3.78737 7.87868 3.18726C8.44129 2.58714 9.20435 2.25 10 2.25C10.7956 2.25 11.5587 2.58714 12.1213 3.18726C12.6839 3.78737 13 4.60131 13 5.45L13 6.25"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path d="M11 15.25H17" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14 12.25L14 18.25" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
export const HandHoldingDollarIcon = (props: SVGProps) => (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M5 22H3C2.448 22 2 21.552 2 21V17C2 16.448 2.448 16 3 16H5C5.552 16 6 16.448 6 17V21C6 21.552 5.552 22 5 22ZM19.66 16.02C19.43 16.02 19.2 16.08 18.98 16.21L16.71 17.5699C16.5 18.7999 15.42 19.75 14.12 19.75H11C10.59 19.75 10.25 19.41 10.25 19C10.25 18.59 10.59 18.25 11 18.25H14.12C14.74 18.25 15.25 17.75 15.25 17.12C15.25 16.5 14.74 16 14.12 16H9C7.9 16 7 16.9 7 18V20C7 21.1 7.9 22 9 22H14.6C15.51 22 16.39 21.69 17.1 21.12L20.5 18.4C20.82 18.15 21 17.76 21 17.36C21 16.58 20.36 16.02 19.66 16.02ZM18 7.5C18 10.809 15.309 13.5 12 13.5C8.691 13.5 6 10.809 6 7.5C6 4.191 8.691 1.5 12 1.5C15.309 1.5 18 4.191 18 7.5ZM14.25 8.91199C14.25 7.96999 13.626 7.14894 12.731 6.91394L11.646 6.63403C11.535 6.60503 11.438 6.53894 11.363 6.43994C11.29 6.34394 11.25 6.21901 11.25 6.08801C11.25 5.77901 11.48 5.52698 11.764 5.52698H12.237C12.497 5.52698 12.717 5.74102 12.748 6.02502C12.792 6.43702 13.157 6.73194 13.575 6.68994C13.987 6.64594 14.284 6.27504 14.24 5.86304C14.146 4.99204 13.531 4.307 12.737 4.099V3.99902C12.737 3.58502 12.401 3.24902 11.987 3.24902C11.573 3.24902 11.237 3.58502 11.237 3.99902V4.10803C10.384 4.34703 9.75201 5.13904 9.75201 6.08704C9.75201 6.54404 9.90101 6.99004 10.169 7.34204C10.442 7.70604 10.833 7.96898 11.272 8.08398L12.357 8.36401C12.59 8.42501 12.753 8.65003 12.753 8.91003C12.753 9.06303 12.696 9.20696 12.593 9.31396C12.536 9.37296 12.416 9.46997 12.239 9.46997H11.766C11.506 9.46997 11.286 9.25605 11.255 8.97205C11.211 8.56005 10.847 8.26401 10.428 8.30701C10.016 8.35101 9.719 8.72203 9.763 9.13403C9.856 9.99303 10.456 10.671 11.236 10.889V11C11.236 11.414 11.572 11.75 11.986 11.75C12.4 11.75 12.736 11.414 12.736 11V10.9C13.085 10.808 13.408 10.6281 13.67 10.3571C14.044 9.96906 14.25 9.45499 14.25 8.91199Z"
fill="white"
/>
</svg>
)

@ -1,16 +1,10 @@
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { useNftRouteLazyQuery } from 'graphql/data/__generated__/types-and-hooks' import { useNftRouteLazyQuery } from 'graphql/data/__generated__/types-and-hooks'
import { BagStatus, GenieAsset } from 'nft/types' import { BagStatus } from 'nft/types'
import { import { buildNftTradeInputFromBagItems, recalculateBagUsingPooledAssets } from 'nft/utils'
buildNftTradeInput,
buildNftTradeInputFromBagItems,
filterUpdatedAssetsByState,
recalculateBagUsingPooledAssets,
} from 'nft/utils'
import { getNextBagState, getPurchasableAssets } from 'nft/utils/bag' import { getNextBagState, getPurchasableAssets } from 'nft/utils/bag'
import { buildRouteResponse } from 'nft/utils/nftRoute' import { buildRouteResponse } from 'nft/utils/nftRoute'
import { compareAssetsWithTransactionRoute } from 'nft/utils/txRoute/combineItemsWithTxRoute' import { useCallback, useMemo } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { shallow } from 'zustand/shallow' import { shallow } from 'zustand/shallow'
import { useBag } from './useBag' import { useBag } from './useBag'
@ -106,48 +100,3 @@ export function useFetchAssets(): () => Promise<void> {
tokenTradeInput, tokenTradeInput,
]) ])
} }
export const useBuyAssetCallback = () => {
const { account } = useWeb3React()
const [fetchGqlRoute] = useNftRouteLazyQuery()
const purchaseAssets = usePurchaseAssets()
const [isLoading, setIsLoading] = useState(false)
const fetchAndPurchaseSingleAsset = useCallback(
async (asset: GenieAsset) => {
setIsLoading(true)
fetchGqlRoute({
variables: {
senderAddress: account ? account : '',
nftTrades: buildNftTradeInput([asset]),
tokenTrades: undefined,
},
pollInterval: 0,
fetchPolicy: 'no-cache',
onCompleted: (data) => {
setIsLoading(false)
if (!data.nftRoute || !data.nftRoute.route) {
return
}
const { route, routeResponse } = buildRouteResponse(data.nftRoute, false)
const { updatedAssets } = compareAssetsWithTransactionRoute([asset], route)
const { priceChanged, unavailable } = filterUpdatedAssetsByState(updatedAssets)
const invalidData = priceChanged.length > 0 || unavailable.length > 0
if (invalidData) {
return
}
purchaseAssets(routeResponse, updatedAssets, false)
},
})
},
[account, fetchGqlRoute, purchaseAssets]
)
return useMemo(() => ({ fetchAndPurchaseSingleAsset, isLoading }), [fetchAndPurchaseSingleAsset, isLoading])
}

@ -1,11 +1,9 @@
import { InterfacePageName } from '@uniswap/analytics-events' import { InterfacePageName } from '@uniswap/analytics-events'
import { Trace } from 'analytics' import { Trace } from 'analytics'
import { useDetailsV2Enabled } from 'featureFlags/flags/nftDetails'
import { useNftAssetDetails } from 'graphql/data/nft/Details' import { useNftAssetDetails } from 'graphql/data/nft/Details'
import { AssetDetails } from 'nft/components/details/AssetDetails' import { AssetDetails } from 'nft/components/details/AssetDetails'
import { AssetDetailsLoading } from 'nft/components/details/AssetDetailsLoading' import { AssetDetailsLoading } from 'nft/components/details/AssetDetailsLoading'
import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails' import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails'
import { NftDetails } from 'nft/components/details/detailsV2/NftDetails'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
@ -39,11 +37,10 @@ const AssetPriceDetailsContainer = styled.div`
const AssetPage = () => { const AssetPage = () => {
const { tokenId = '', contractAddress = '' } = useParams() const { tokenId = '', contractAddress = '' } = useParams()
const { data, loading } = useNftAssetDetails(contractAddress, tokenId) const { data, loading } = useNftAssetDetails(contractAddress, tokenId)
const detailsV2Enabled = useDetailsV2Enabled()
const [asset, collection] = data const [asset, collection] = data
if (loading && !detailsV2Enabled) return <AssetDetailsLoading /> if (loading) return <AssetDetailsLoading />
return ( return (
<> <>
<Trace <Trace
@ -52,16 +49,12 @@ const AssetPage = () => {
shouldLogImpression shouldLogImpression
> >
{!!asset && !!collection ? ( {!!asset && !!collection ? (
detailsV2Enabled ? ( <AssetContainer>
<NftDetails asset={asset} collection={collection} /> <AssetDetails collection={collection} asset={asset} />
) : ( <AssetPriceDetailsContainer>
<AssetContainer> <AssetPriceDetails collection={collection} asset={asset} />
<AssetDetails collection={collection} asset={asset} /> </AssetPriceDetailsContainer>
<AssetPriceDetailsContainer> </AssetContainer>
<AssetPriceDetails collection={collection} asset={asset} />
</AssetPriceDetailsContainer>
</AssetContainer>
)
) : null} ) : null}
</Trace> </Trace>
</> </>

@ -1,13 +1,5 @@
import { import { MediaType, NftActivityType, NftStandard, OrderStatus } from 'graphql/data/__generated__/types-and-hooks'
MediaType, import { ActivityEvent, GenieAsset, Markets, WalletAsset } from 'nft/types'
NftActivityType,
NftMarketplace,
NftStandard,
OrderStatus,
OrderType,
} from 'graphql/data/__generated__/types-and-hooks'
import ms from 'ms.macro'
import { ActivityEvent, CollectionInfoForAsset, GenieAsset, Markets, Offer, SellOrder, WalletAsset } from 'nft/types'
export const TEST_NFT_ASSET: GenieAsset = { export const TEST_NFT_ASSET: GenieAsset = {
id: 'TmZ0QXNzZXQ6MHhlZDVhZjM4ODY1MzU2N2FmMmYzODhlNjIyNGRjN2M0YjMyNDFjNTQ0XzMzMTg=', id: 'TmZ0QXNzZXQ6MHhlZDVhZjM4ODY1MzU2N2FmMmYzODhlNjIyNGRjN2M0YjMyNDFjNTQ0XzMzMTg=',
@ -42,108 +34,6 @@ export const TEST_NFT_ASSET: GenieAsset = {
creator: {}, creator: {},
} }
export const TEST_VIDEO_NFT_ASSET: GenieAsset = {
id: 'TmZ0QXNzZXQ6MHg0ZTFmNDE2MTNjOTA4NGZkYjllMzRlMTFmYWU5NDEyNDI3NDgwZTU2XzcyMDI=',
address: '0x6c9369dc930fd794ad0af7511f483d936a2ef7f3',
notForSale: false,
collectionName: 'Terraforms by Mathcastles',
imageUrl:
'https://i.seadn.io/gae/tkDbNhjjBZV2PmYaJbJOOigywZCrlcyGRxeQFkZS1YZyihyG5GoWNWj3N9f1T7YVuaxOqdxhfJylC9ejtoCvdgBE932vd7jorVqA?w=500&auto=format',
animationUrl: 'https://openseauserdata.com/files/5af92728200027caa4f3f5ae87a486a7.mp4',
mediaType: MediaType.Video,
marketplace: Markets.Opensea,
name: 'Aku Chapter IV: Aku x Ady #884',
priceInfo: {
ETHPrice: '1295000000000000000',
baseAsset: 'ETH',
baseDecimals: '18',
basePrice: '1295000000000000000',
},
susFlag: false,
tokenId: '1455',
tokenType: NftStandard.Erc721,
collectionIsVerified: false,
totalCount: 9910,
rarity: {
primaryProvider: 'Rarity Sniper',
providers: [
{
rank: 7079,
provider: 'Rarity Sniper',
},
],
},
creator: {},
}
export const TEST_AUDIO_NFT_ASSET: GenieAsset = {
id: 'TmZ0QXNzZXQ6MHg0ZTFmNDE2MTNjOTA4NGZkYjllMzRlMTFmYWU5NDEyNDI3NDgwZTU2XzcyMDI=',
address: '0x37a03d4af1d7046d1126987b20117a0fdcbf6535',
notForSale: false,
collectionName: 'Snoop Dogg on Sound XYZ',
imageUrl:
'https://i.seadn.io/gae/Kze9SBqn_6O0qrHKxspo1gRkkDV2A5EmTeWtvdS-dNxBsvi_wPXUYjc6De0sUC-DYzL093102mUftenWxwWuTelqsdw-ngoBC3o2XFU?w=500&auto=format',
animationUrl: 'https://openseauserdata.com/files/4a22253e44e10baa11484a2e43efefda.mp3',
mediaType: MediaType.Audio,
marketplace: Markets.Opensea,
name: 'Death Row Session: Vol. 2 (420 Edition) #320',
priceInfo: {
ETHPrice: '1295000000000000000',
baseAsset: 'ETH',
baseDecimals: '18',
basePrice: '1295000000000000000',
},
susFlag: false,
tokenId: '680564733841876926926749214863536423232',
tokenType: NftStandard.Erc721,
collectionIsVerified: false,
totalCount: 9910,
rarity: {
primaryProvider: 'Rarity Sniper',
providers: [
{
rank: 7079,
provider: 'Rarity Sniper',
},
],
},
creator: {},
}
export const TEST_EMBEDDED_NFT_ASSET: GenieAsset = {
id: 'TmZ0QXNzZXQ6MHg0ZTFmNDE2MTNjOTA4NGZkYjllMzRlMTFmYWU5NDEyNDI3NDgwZTU2XzcyMDI=',
address: '0x4e1f41613c9084fdb9e34e11fae9412427480e56',
notForSale: false,
collectionName: 'Terraforms by Mathcastles',
imageUrl:
'https://cdn.center.app/v2/1/06ff92279474add6ce06176e2a65447396edf786d169d8ccc03fddfa45ce004f/bb01f8a2f093ea4619498dae58fc19e5ba3fa38a84cabf92948994609489d566.png',
animationUrl: 'https://tokens.mathcastles.xyz/terraforms/token-html/7202',
mediaType: MediaType.Raw,
marketplace: Markets.Opensea,
name: 'Level 13 at {28, 3}',
priceInfo: {
ETHPrice: '1295000000000000000',
baseAsset: 'ETH',
baseDecimals: '18',
basePrice: '1295000000000000000',
},
susFlag: false,
tokenId: '7202',
tokenType: NftStandard.Erc721,
collectionIsVerified: false,
totalCount: 9910,
rarity: {
primaryProvider: 'Rarity Sniper',
providers: [
{
rank: 7079,
provider: 'Rarity Sniper',
},
],
},
creator: { address: '0xe72eb31b59f85b19499a0f3b3260011894fa0d65' },
}
export const TEST_NFT_WALLET_ASSET: WalletAsset = { export const TEST_NFT_WALLET_ASSET: WalletAsset = {
id: 'TmZ0QXNzZXQ6RVRIRVJFVU1fMHgyOTY1MkMyZTlEMzY1NjQzNEJjODEzM2M2OTI1OEM4ZDA1MjkwZjQxXzIzNTk=', id: 'TmZ0QXNzZXQ6RVRIRVJFVU1fMHgyOTY1MkMyZTlEMzY1NjQzNEJjODEzM2M2OTI1OEM4ZDA1MjkwZjQxXzIzNTk=',
imageUrl: 'https://c.neevacdn.net/image/upload/xyz/T96PksTnWGNh79CrzLn-zpYfqRWtD5wME0MBPL_Md6Q.png', imageUrl: 'https://c.neevacdn.net/image/upload/xyz/T96PksTnWGNh79CrzLn-zpYfqRWtD5wME0MBPL_Md6Q.png',
@ -208,50 +98,3 @@ export const TEST_NFT_ACTIVITY_EVENT: ActivityEvent = {
url: 'https://opensea.io/assets/0xed5af388653567af2f388e6224dc7c4b3241c544/5674', url: 'https://opensea.io/assets/0xed5af388653567af2f388e6224dc7c4b3241c544/5674',
eventTimestamp: 1682444662, eventTimestamp: 1682444662,
} }
export const TEST_NFT_COLLECTION_INFO_FOR_ASSET: CollectionInfoForAsset = {
collectionDescription:
'Take the red bean to join the garden. View the collection at [azuki.com/gallery](https://azuki.com/gallery).\r\n\r\nAzuki starts with a collection of 10,000 avatars that give you membership access to The Garden: a corner of the internet where artists, builders, and web3 enthusiasts meet to create a decentralized future. Azuki holders receive access to exclusive drops, experiences, and more. Visit [azuki.com](https://azuki.com) for more details.\r\n\r\nWe rise together. We build together. We grow together.',
collectionImageUrl:
'https://i.seadn.io/gae/H8jOCJuQokNqGBpkBN5wk1oZwO7LM8bNnrHCaekV2nKjnCqw6UB5oaH8XyNeBDj6bA_n1mjejzhFQUP3O1NfjFLHr3FOaeHcTOOT?w=500&auto=format',
collectionName: 'Azuki',
discordUrl: 'https://discord.gg/azuki',
externalUrl: 'http://www.azuki.com',
isVerified: true,
totalSupply: 10000,
}
const FIVE_MONTHS_MILLISECONDS = ms`165 days`
export const TEST_SELL_ORDER: SellOrder = {
address: '0x29d7ebca656665c1a52a92f830e413e394db6b4f',
createdAt: 1683561510000,
endAt: Date.now() + FIVE_MONTHS_MILLISECONDS,
id: 'TmZ0T3JkZXI6MHgyOWQ3ZWJjYTY1NjY2NWMxYTUyYTkyZjgzMGU0MTNlMzk0ZGI2YjRmXzY4MTVfMHg3OWVhNDQ5YzMzNzVlZDFhOWQ3ZDk5ZjgwNjgyMDllYTc0OGM2ZDQyXzQ5NzAwMDAwMDAwMDAwMDAwMDAwMF9vcGVuc2VhX01vbiBNYXkgMDggMjAyMyAxNTo1ODozMCBHTVQrMDAwMCAoQ29vcmRpbmF0ZWQgVW5pdmVyc2FsIFRpbWUp',
maker: '0x79ea449c3375ed1a9d7d99f8068209ea748c6d42',
marketplace: NftMarketplace.Opensea,
marketplaceUrl: 'https://opensea.io/assets/0x29d7ebca656665c1a52a92f830e413e394db6b4f/6815',
price: {
currency: 'ETH',
value: 99999999,
},
quantity: 1,
startAt: 1683561507000,
status: OrderStatus.Valid,
type: OrderType.Listing,
protocolParameters: {},
}
export const TEST_OFFER: Offer = {
createdAt: 1683561510000,
endAt: Date.now() + FIVE_MONTHS_MILLISECONDS,
id: 'TmZ0T3JkZXI6MHgyOWQ3ZWJjYTY1NjY2NWMxYTUyYTkyZjgzMGU0MTNlMzk0ZGI2YjRmXzY4MTVfMHg3OWVhNDQ5YzMzNzVlZDFhOWQ3ZDk5ZjgwNjgyMDllYTc0OGM2ZDQyXzQ5NzAwMDAwMDAwMDAwMDAwMDAwMF9vcGVuc2VhX01vbiBNYXkgMDggMjAyMyAxNTo1ODozMCBHTVQrMDAwMCAoQ29vcmRpbmF0ZWQgVW5pdmVyc2FsIFRpbWUp',
maker: '0x79ea449c3375ed1a9d7d99f8068209ea748c6d42',
marketplace: NftMarketplace.Opensea,
marketplaceUrl: 'https://opensea.io/assets/0x29d7ebca656665c1a52a92f830e413e394db6b4f/6815',
price: {
currency: 'ETH',
value: 123.456,
},
quantity: 1,
}