Commit 873d0ea2 authored by Jack Short's avatar Jack Short Committed by GitHub

chore: breaking details into two containers (#4822)

* holding

* breaking up code into two areas for development purposes
parent db4987f5
import { style } from '@vanilla-extract/css'
import { center, subhead } from '../../css/common.css'
import { sprinkles, vars } from '../../css/sprinkles.css'
export const image = style([
sprinkles({ borderRadius: '20', height: 'full', alignSelf: 'center' }),
{
width: 'calc(90vh - 165px)',
height: 'calc(90vh - 165px)',
maxHeight: '678px',
maxWidth: '678px',
boxShadow: `0px 20px 50px var(--shadow), 0px 10px 50px rgba(70, 115, 250, 0.2)`,
'@media': {
'(max-width: 1024px)': {
maxHeight: '64vh',
maxWidth: '64vh',
},
'(max-width: 640px)': {
maxHeight: '56vh',
maxWidth: '56vh',
},
},
},
])
export const container = style([
center,
{
minHeight: 'calc(100vh - 97px)',
},
])
export const marketplace = sprinkles({ borderRadius: '4' })
export const tab = style([
subhead,
sprinkles({ color: 'textSecondary', border: 'none', padding: '0', background: 'transparent', cursor: 'pointer' }),
{
selectors: {
'&[data-active="true"]': {
textDecoration: 'underline',
textDecorationColor: vars.color.genieBlue,
textUnderlineOffset: '8px',
textDecorationThickness: '2px',
color: vars.color.textPrimary,
},
},
},
])
export const creator = style({
'@media': {
'(max-width: 640px)': {
display: 'none',
},
},
})
export const columns = style([
sprinkles({
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
width: 'full',
paddingLeft: { sm: '16', lg: '24', xl: '52' },
paddingRight: { sm: '16', lg: '24', xl: '52' },
paddingBottom: { sm: '16', lg: '24', xl: '52' },
paddingTop: '16',
gap: { sm: '32', lg: '28', xl: '52' },
}),
{
boxSizing: 'border-box',
'@media': {
'(max-width: 1024px)': {
flexDirection: 'column',
alignItems: 'center',
},
},
},
])
export const column = style({
maxWidth: '50%',
width: '50%',
alignSelf: 'center',
'@media': {
'(max-width: 1024px)': {
maxWidth: 'calc(88%)',
width: 'calc(88%)',
},
},
})
export const columnRight = style({
maxHeight: 'calc(100vh - 165px)',
overflow: 'scroll',
'@media': {
'(max-width: 1024px)': {
maxHeight: '100%',
},
},
selectors: {
'&::-webkit-scrollbar': {
display: 'none',
},
},
scrollbarWidth: 'none',
})
export const audioControls = style({
position: 'absolute',
left: '0',
right: '0',
textAlign: 'center',
marginRight: 'auto',
marginLeft: 'auto',
bottom: 'calc(10%)',
})
import { useWeb3React } from '@web3-react/core'
import clsx from 'clsx'
import { MouseoverTooltip } from 'components/Tooltip/index'
import useENSName from 'hooks/useENSName'
import { AnimatedBox, Box } from 'nft/components/Box'
import { CollectionProfile } from 'nft/components/details/CollectionProfile'
import { Details } from 'nft/components/details/Details'
import { Traits } from 'nft/components/details/Traits'
import { Center, Column, Row } from 'nft/components/Flex'
import { CloseDropDownIcon, CornerDownLeftIcon, Eth2Icon, ShareIcon, SuspiciousIcon } from 'nft/components/icons'
import { ExpandableText } from 'nft/components/layout/ExpandableText'
import { badge, bodySmall, caption, headlineMedium, subhead } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css'
import { useBag } from 'nft/hooks'
import { useTimeout } from 'nft/hooks/useTimeout'
import { fetchSingleAsset } from 'nft/queries'
import { CollectionInfoForAsset, GenieAsset, SellOrder } from 'nft/types'
import { shortenAddress } from 'nft/utils/address'
import { formatEthPrice } from 'nft/utils/currency'
import { isAssetOwnedByUser } from 'nft/utils/isAssetOwnedByUser'
import { isAudio } from 'nft/utils/isAudio'
import { isVideo } from 'nft/utils/isVideo'
import { fallbackProvider, rarityProviderLogo } from 'nft/utils/rarity'
import { toSignificant } from 'nft/utils/toSignificant'
import qs from 'query-string'
import { useEffect, useMemo, useState } from 'react'
import ReactMarkdown from 'react-markdown'
import { useQuery } from 'react-query'
import { Link, useLocation, useNavigate } from 'react-router-dom'
import { useSpring } from 'react-spring'
import * as styles from './AssetDetails.css'
const AudioPlayer = ({
imageUrl,
animationUrl,
name,
collectionName,
dominantColor,
}: GenieAsset & { dominantColor: [number, number, number] }) => {
return (
<Box position="relative" display="inline-block" alignSelf="center">
<Box as="audio" className={styles.audioControls} width="292" controls src={animationUrl} />
<img
className={styles.image}
src={imageUrl}
alt={name || collectionName}
style={{
['--shadow' as string]: `rgba(${dominantColor.join(', ')}, 0.5)`,
minWidth: '300px',
minHeight: '300px',
}}
/>
</Box>
)
}
const formatter = Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'short' })
const CountdownTimer = ({ sellOrder }: { sellOrder: SellOrder }) => {
const { date, expires } = useMemo(() => {
const date = new Date(sellOrder.orderClosingDate)
return {
date,
expires: formatter.format(date),
}
}, [sellOrder])
const [days, hours, minutes, seconds] = useTimeout(date)
return (
<MouseoverTooltip text={<Box fontSize="12">Expires {expires}</Box>}>
<Box as="span" fontWeight="normal" className={caption} color="textSecondary">
Expires: {days !== 0 ? `${days} days` : ''} {hours !== 0 ? `${hours} hours` : ''} {minutes} minutes {seconds}{' '}
seconds
</Box>
</MouseoverTooltip>
)
}
const AssetView = ({
mediaType,
asset,
dominantColor,
}: {
mediaType: 'image' | 'video' | 'audio'
asset: GenieAsset
dominantColor: [number, number, number]
}) => {
const style = { ['--shadow' as string]: `rgba(${dominantColor.join(', ')}, 0.5)` }
switch (mediaType) {
case 'video':
return <video src={asset.animationUrl} className={styles.image} autoPlay controls muted loop style={style} />
case 'image':
return (
<img className={styles.image} src={asset.imageUrl} alt={asset.name || asset.collectionName} style={style} />
)
case 'audio':
return <AudioPlayer {...asset} dominantColor={dominantColor} />
}
}
enum MediaType {
Audio = 'audio',
Video = 'video',
Image = 'image',
}
interface AssetDetailsProps {
tokenId: string
contractAddress: string
}
export const AssetDetails = ({ tokenId, contractAddress }: AssetDetailsProps) => {
const { data } = useQuery(['assetDetail', contractAddress, tokenId], () =>
fetchSingleAsset({ contractAddress, tokenId })
)
const { pathname, search } = useLocation()
const navigate = useNavigate()
const addAssetToBag = useBag((state) => state.addAssetToBag)
const removeAssetFromBag = useBag((state) => state.removeAssetFromBag)
const itemsInBag = useBag((state) => state.itemsInBag)
const bagExpanded = useBag((state) => state.bagExpanded)
const [creatorAddress, setCreatorAddress] = useState('')
const [ownerAddress, setOwnerAddress] = useState('')
const [dominantColor] = useState<[number, number, number]>([0, 0, 0])
const creatorEnsName = useENSName(creatorAddress)
const ownerEnsName = useENSName(ownerAddress)
const parsed = qs.parse(search)
const asset = useMemo(() => (data ? data[0] : ({} as GenieAsset)), [data])
const collection = useMemo(() => (data ? data[1] : ({} as CollectionInfoForAsset)), [data])
const { gridWidthOffset } = useSpring({
gridWidthOffset: bagExpanded ? 324 : 0,
})
const [showTraits, setShowTraits] = useState(true)
const [isSelected, setSelected] = useState(false)
const [isOwned, setIsOwned] = useState(false)
const { account: address, provider } = useWeb3React()
const { rarityProvider, rarityLogo } = useMemo(
() =>
asset.rarity
? {
rarityProvider: asset.rarity.providers.find(
({ provider: _provider }) => _provider === asset.rarity?.primaryProvider
),
rarityLogo: rarityProviderLogo[asset.rarity.primaryProvider] || '',
}
: {},
[asset.rarity]
)
const assetMediaType = useMemo(() => {
if (isAudio(asset.animationUrl)) {
return MediaType.Audio
} else if (isVideo(asset.animationUrl)) {
return MediaType.Video
}
return MediaType.Image
}, [asset])
useEffect(() => {
if (asset.creator) setCreatorAddress(asset.creator.address)
if (asset.owner) setOwnerAddress(asset.owner)
}, [asset])
useEffect(() => {
setSelected(
!!itemsInBag.find((item) => item.asset.tokenId === asset.tokenId && item.asset.address === asset.address)
)
}, [asset, itemsInBag])
useEffect(() => {
if (provider) {
isAssetOwnedByUser({
tokenId: asset.tokenId,
userAddress: address || '',
assetAddress: asset.address,
tokenType: asset.tokenType,
provider,
}).then(setIsOwned)
}
}, [asset, address, provider])
return (
<AnimatedBox
style={{
// @ts-ignore
width: gridWidthOffset.to((x) => `calc(100% - ${x}px)`),
}}
className={styles.container}
>
<div className={styles.columns}>
<Column className={styles.column}>
{assetMediaType === MediaType.Image ? (
<img
className={styles.image}
src={asset.imageUrl}
alt={asset.name || collection.collectionName}
style={{ ['--shadow' as string]: `rgba(${dominantColor.join(', ')}, 0.5)` }}
/>
) : (
<AssetView asset={asset} mediaType={assetMediaType} dominantColor={dominantColor} />
)}
</Column>
<Column className={clsx(styles.column, styles.columnRight)} width="full">
<Column>
<Row
marginBottom="8"
alignItems="center"
textAlign="center"
justifyContent={rarityProvider ? 'space-between' : 'flex-end'}
>
{rarityProvider && (
<MouseoverTooltip
text={
<Row gap="4">
<img src={rarityLogo} width={16} alt={rarityProvider.provider} />
Ranking by{' '}
{asset.rarity?.primaryProvider === 'Genie' ? fallbackProvider : asset.rarity?.primaryProvider}
</Row>
}
>
<Center
paddingLeft="6"
paddingRight="4"
className={badge}
backgroundColor="backgroundSurface"
color="textPrimary"
borderRadius="4"
>
#{rarityProvider.rank} <img src="/nft/svgs/rarity.svg" height={15} width={15} alt="Rarity rank" />
</Center>
</MouseoverTooltip>
)}
<Row gap="12">
<Center
as="button"
padding="0"
border="none"
background="transparent"
cursor="pointer"
onClick={async () => {
await navigator.clipboard.writeText(`${window.location.hostname}/#${pathname}`)
}}
>
<ShareIcon />
</Center>
<Center
as="button"
border="none"
width="32"
height="32"
padding="0"
background="transparent"
cursor="pointer"
onClick={() => {
if (!parsed.origin || parsed.origin === 'collection') {
navigate(`/nfts/collection/${asset.address}`)
} else if (parsed.origin === 'profile') {
navigate('/profile', undefined)
} else if (parsed.origin === 'explore') {
navigate(`/nfts`, undefined)
} else if (parsed.origin === 'activity') {
navigate(`/nfts/collection/${asset.address}/activity`, undefined)
}
}}
>
{parsed.origin ? (
<CornerDownLeftIcon width="28" height="28" />
) : (
<CloseDropDownIcon color={themeVars.colors.textSecondary} />
)}
</Center>
</Row>
</Row>
<Row as="h1" marginTop="0" marginBottom="12" gap="2" className={headlineMedium}>
{asset.openseaSusFlag && (
<Box marginTop="8">
<MouseoverTooltip text={<Box fontWeight="normal">Reported for suspicious activity on OpenSea</Box>}>
<SuspiciousIcon height="30" width="30" viewBox="0 0 16 17" />
</MouseoverTooltip>
</Box>
)}
{asset.name || `${collection.collectionName} #${asset.tokenId}`}
</Row>
{collection.collectionDescription ? (
<ExpandableText>
<ReactMarkdown
allowedTypes={['link', 'paragraph', 'strong', 'code', 'emphasis', 'text']}
source={collection.collectionDescription}
/>
</ExpandableText>
) : null}
<Row
justifyContent={{
sm: 'space-between',
}}
gap={{
sm: 'unset',
}}
marginBottom="36"
>
{ownerAddress.length > 0 && (
<a
target="_blank"
rel="noreferrer"
href={`https://etherscan.io/address/${asset.owner}`}
style={{ textDecoration: 'none' }}
>
<CollectionProfile
label="Owner"
avatarUrl=""
name={ownerEnsName.ENSName ?? shortenAddress(ownerAddress, 0, 4)}
/>
</a>
)}
<Link to={`/nfts/collection/${asset.address}`} style={{ textDecoration: 'none' }}>
<CollectionProfile
label="Collection"
avatarUrl={collection.collectionImageUrl}
name={collection.collectionName}
isVerified={collection.isVerified}
/>
</Link>
{creatorAddress ? (
<a
target="_blank"
rel="noreferrer"
href={`https://etherscan.io/address/${creatorAddress}`}
style={{ textDecoration: 'none' }}
>
<CollectionProfile
label="Creator"
avatarUrl={asset.creator.profile_img_url}
name={creatorEnsName.ENSName ?? shortenAddress(creatorAddress, 0, 4)}
isVerified
className={styles.creator}
/>
</a>
) : null}
</Row>
</Column>
{asset.priceInfo && !isOwned ? (
<Row
marginTop="8"
marginBottom="40"
justifyContent="space-between"
borderRadius="12"
paddingTop="16"
paddingBottom="16"
paddingLeft="16"
paddingRight="24"
background="accentActiveSoft"
>
<Column justifyContent="flex-start" gap="8">
<Row gap="12" as="a" target="_blank" rel="norefferer">
<a href={asset.sellorders[0].marketplaceUrl} rel="noreferrer" target="_blank">
<img
className={styles.marketplace}
src={`/nft/svgs/marketplaces/${asset.sellorders[0].marketplace}.svg`}
height={16}
width={16}
alt="Markeplace"
/>
</a>
<Row as="span" className={subhead} color="textPrimary">
{formatEthPrice(asset.priceInfo.ETHPrice)} <Eth2Icon />
</Row>
<Box as="span" color="textSecondary" className={bodySmall}>
${toSignificant(asset.priceInfo.USDPrice)}
</Box>
</Row>
{asset.sellorders?.[0].orderClosingDate ? <CountdownTimer sellOrder={asset.sellorders[0]} /> : null}
</Column>
<Box
as="button"
paddingTop="14"
paddingBottom="14"
fontWeight="medium"
textAlign="center"
fontSize="14"
style={{ width: '244px' }}
color={isSelected ? 'genieBlue' : 'explicitWhite'}
border="none"
borderRadius="12"
background={isSelected ? 'explicitWhite' : 'genieBlue'}
transition="250"
boxShadow={{ hover: 'elevation' }}
onClick={() => {
if (isSelected) {
removeAssetFromBag(asset)
} else addAssetToBag(asset)
setSelected((x) => !x)
}}
>
{isSelected ? 'Added to Bag' : 'Buy Now'}
</Box>
</Row>
) : null}
<Row gap="32" marginBottom="20">
<button data-active={showTraits} onClick={() => setShowTraits(true)} className={styles.tab}>
Traits
</button>
<button data-active={!showTraits} onClick={() => setShowTraits(false)} className={styles.tab}>
Details
</button>
</Row>
{showTraits ? (
<Traits collectionAddress={asset.address} traits={asset.traits ?? []} />
) : (
<Details
contractAddress={contractAddress}
tokenId={tokenId}
tokenType={asset.tokenType}
blockchain="Ethereum"
metadataUrl={asset.externalLink}
totalSupply={collection.totalSupply}
/>
)}
</Column>
</div>
</AnimatedBox>
)
}
export const AssetPriceDetails = () => {
return <div>Holder for price details</div>
}
import { useWeb3React } from '@web3-react/core'
import clsx from 'clsx'
import useENSName from 'hooks/useENSName'
import qs from 'query-string'
import { useEffect, useMemo, useState } from 'react'
import ReactMarkdown from 'react-markdown'
import { useQuery } from 'react-query'
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'
import { useSpring } from 'react-spring'
import { AssetDetails } from 'nft/components/details/AssetDetails'
import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails'
import { useParams } from 'react-router-dom'
import styled from 'styled-components/macro'
import { MouseoverTooltip } from '../../../components/Tooltip/index'
import { AnimatedBox, Box } from '../../components/Box'
import { CollectionProfile } from '../../components/details/CollectionProfile'
import { Details } from '../../components/details/Details'
import { Traits } from '../../components/details/Traits'
import { Center, Column, Row } from '../../components/Flex'
import { CloseDropDownIcon, CornerDownLeftIcon, Eth2Icon, ShareIcon, SuspiciousIcon } from '../../components/icons'
import { ExpandableText } from '../../components/layout/ExpandableText'
import { badge, bodySmall, caption, headlineMedium, subhead } from '../../css/common.css'
import { themeVars } from '../../css/sprinkles.css'
import { useBag } from '../../hooks'
import { useTimeout } from '../../hooks/useTimeout'
import { fetchSingleAsset } from '../../queries'
import { CollectionInfoForAsset, GenieAsset, SellOrder } from '../../types'
import { shortenAddress } from '../../utils/address'
import { formatEthPrice } from '../../utils/currency'
import { isAssetOwnedByUser } from '../../utils/isAssetOwnedByUser'
import { isAudio } from '../../utils/isAudio'
import { isVideo } from '../../utils/isVideo'
import { fallbackProvider, rarityProviderLogo } from '../../utils/rarity'
import { toSignificant } from '../../utils/toSignificant'
import * as styles from './Asset.css'
const AudioPlayer = ({
imageUrl,
animationUrl,
name,
collectionName,
dominantColor,
}: GenieAsset & { dominantColor: [number, number, number] }) => {
return (
<Box position="relative" display="inline-block" alignSelf="center">
<Box as="audio" className={styles.audioControls} width="292" controls src={animationUrl} />
<img
className={styles.image}
src={imageUrl}
alt={name || collectionName}
style={{
['--shadow' as string]: `rgba(${dominantColor.join(', ')}, 0.5)`,
minWidth: '300px',
minHeight: '300px',
}}
/>
</Box>
)
}
const formatter = Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'short' })
const CountdownTimer = ({ sellOrder }: { sellOrder: SellOrder }) => {
const { date, expires } = useMemo(() => {
const date = new Date(sellOrder.orderClosingDate)
return {
date,
expires: formatter.format(date),
}
}, [sellOrder])
const [days, hours, minutes, seconds] = useTimeout(date)
return (
<MouseoverTooltip text={<Box fontSize="12">Expires {expires}</Box>}>
<Box as="span" fontWeight="normal" className={caption} color="textSecondary">
Expires: {days !== 0 ? `${days} days` : ''} {hours !== 0 ? `${hours} hours` : ''} {minutes} minutes {seconds}{' '}
seconds
</Box>
</MouseoverTooltip>
)
}
const AssetView = ({
mediaType,
asset,
dominantColor,
}: {
mediaType: 'image' | 'video' | 'audio'
asset: GenieAsset
dominantColor: [number, number, number]
}) => {
const style = { ['--shadow' as string]: `rgba(${dominantColor.join(', ')}, 0.5)` }
switch (mediaType) {
case 'video':
return <video src={asset.animationUrl} className={styles.image} autoPlay controls muted loop style={style} />
case 'image':
return (
<img className={styles.image} src={asset.imageUrl} alt={asset.name || asset.collectionName} style={style} />
)
case 'audio':
return <AudioPlayer {...asset} dominantColor={dominantColor} />
}
}
enum MediaType {
Audio = 'audio',
Video = 'video',
Image = 'image',
}
const AssetContainer = styled.div`
display: flex;
padding-right: 116px;
padding-left: 116px;
`
const Asset = () => {
const { tokenId = '', contractAddress = '' } = useParams()
const { data } = useQuery(['assetDetail', contractAddress, tokenId], () =>
fetchSingleAsset({ contractAddress, tokenId })
)
const { pathname, search } = useLocation()
const navigate = useNavigate()
const addAssetToBag = useBag((state) => state.addAssetToBag)
const removeAssetFromBag = useBag((state) => state.removeAssetFromBag)
const itemsInBag = useBag((state) => state.itemsInBag)
const bagExpanded = useBag((state) => state.bagExpanded)
const [creatorAddress, setCreatorAddress] = useState('')
const [ownerAddress, setOwnerAddress] = useState('')
const [dominantColor] = useState<[number, number, number]>([0, 0, 0])
const creatorEnsName = useENSName(creatorAddress)
const ownerEnsName = useENSName(ownerAddress)
const parsed = qs.parse(search)
const asset = useMemo(() => (data ? data[0] : ({} as GenieAsset)), [data])
const collection = useMemo(() => (data ? data[1] : ({} as CollectionInfoForAsset)), [data])
const { gridWidthOffset } = useSpring({
gridWidthOffset: bagExpanded ? 324 : 0,
})
const [showTraits, setShowTraits] = useState(true)
const [isSelected, setSelected] = useState(false)
const [isOwned, setIsOwned] = useState(false)
const { account: address, provider } = useWeb3React()
const { rarityProvider, rarityLogo } = useMemo(
() =>
asset.rarity
? {
rarityProvider: asset.rarity.providers.find(
({ provider: _provider }) => _provider === asset.rarity?.primaryProvider
),
rarityLogo: rarityProviderLogo[asset.rarity.primaryProvider] || '',
}
: {},
[asset.rarity]
)
const assetMediaType = useMemo(() => {
if (isAudio(asset.animationUrl)) {
return MediaType.Audio
} else if (isVideo(asset.animationUrl)) {
return MediaType.Video
}
return MediaType.Image
}, [asset])
useEffect(() => {
if (asset.creator) setCreatorAddress(asset.creator.address)
if (asset.owner) setOwnerAddress(asset.owner)
}, [asset])
useEffect(() => {
setSelected(
!!itemsInBag.find((item) => item.asset.tokenId === asset.tokenId && item.asset.address === asset.address)
)
}, [asset, itemsInBag])
useEffect(() => {
if (provider) {
isAssetOwnedByUser({
tokenId: asset.tokenId,
userAddress: address || '',
assetAddress: asset.address,
tokenType: asset.tokenType,
provider,
}).then(setIsOwned)
}
}, [asset, address, provider])
return (
<AnimatedBox
style={{
// @ts-ignore
width: gridWidthOffset.to((x) => `calc(100% - ${x}px)`),
}}
className={styles.container}
>
<div className={styles.columns}>
<Column className={styles.column}>
{assetMediaType === MediaType.Image ? (
<img
className={styles.image}
src={asset.imageUrl}
alt={asset.name || collection.collectionName}
style={{ ['--shadow' as string]: `rgba(${dominantColor.join(', ')}, 0.5)` }}
/>
) : (
<AssetView asset={asset} mediaType={assetMediaType} dominantColor={dominantColor} />
)}
</Column>
<Column className={clsx(styles.column, styles.columnRight)} width="full">
<Column>
<Row
marginBottom="8"
alignItems="center"
textAlign="center"
justifyContent={rarityProvider ? 'space-between' : 'flex-end'}
>
{rarityProvider && (
<MouseoverTooltip
text={
<Row gap="4">
<img src={rarityLogo} width={16} alt={rarityProvider.provider} />
Ranking by{' '}
{asset.rarity?.primaryProvider === 'Genie' ? fallbackProvider : asset.rarity?.primaryProvider}
</Row>
}
>
<Center
paddingLeft="6"
paddingRight="4"
className={badge}
backgroundColor="backgroundSurface"
color="textPrimary"
borderRadius="4"
>
#{rarityProvider.rank} <img src="/nft/svgs/rarity.svg" height={15} width={15} alt="Rarity rank" />
</Center>
</MouseoverTooltip>
)}
<Row gap="12">
<Center
as="button"
padding="0"
border="none"
background="transparent"
cursor="pointer"
onClick={async () => {
await navigator.clipboard.writeText(`${window.location.hostname}/#${pathname}`)
}}
>
<ShareIcon />
</Center>
<Center
as="button"
border="none"
width="32"
height="32"
padding="0"
background="transparent"
cursor="pointer"
onClick={() => {
if (!parsed.origin || parsed.origin === 'collection') {
navigate(`/nfts/collection/${asset.address}`)
} else if (parsed.origin === 'profile') {
navigate('/profile', undefined)
} else if (parsed.origin === 'explore') {
navigate(`/nfts`, undefined)
} else if (parsed.origin === 'activity') {
navigate(`/nfts/collection/${asset.address}/activity`, undefined)
}
}}
>
{parsed.origin ? (
<CornerDownLeftIcon width="28" height="28" />
) : (
<CloseDropDownIcon color={themeVars.colors.textSecondary} />
)}
</Center>
</Row>
</Row>
<Row as="h1" marginTop="0" marginBottom="12" gap="2" className={headlineMedium}>
{asset.openseaSusFlag && (
<Box marginTop="8">
<MouseoverTooltip text={<Box fontWeight="normal">Reported for suspicious activity on OpenSea</Box>}>
<SuspiciousIcon height="30" width="30" viewBox="0 0 16 17" />
</MouseoverTooltip>
</Box>
)}
{asset.name || `${collection.collectionName} #${asset.tokenId}`}
</Row>
{collection.collectionDescription ? (
<ExpandableText>
<ReactMarkdown
allowedTypes={['link', 'paragraph', 'strong', 'code', 'emphasis', 'text']}
source={collection.collectionDescription}
/>
</ExpandableText>
) : null}
<Row
justifyContent={{
sm: 'space-between',
}}
gap={{
sm: 'unset',
}}
marginBottom="36"
>
{ownerAddress.length > 0 && (
<a
target="_blank"
rel="noreferrer"
href={`https://etherscan.io/address/${asset.owner}`}
style={{ textDecoration: 'none' }}
>
<CollectionProfile
label="Owner"
avatarUrl=""
name={ownerEnsName.ENSName ?? shortenAddress(ownerAddress, 0, 4)}
/>
</a>
)}
<Link to={`/nfts/collection/${asset.address}`} style={{ textDecoration: 'none' }}>
<CollectionProfile
label="Collection"
avatarUrl={collection.collectionImageUrl}
name={collection.collectionName}
isVerified={collection.isVerified}
/>
</Link>
{creatorAddress ? (
<a
target="_blank"
rel="noreferrer"
href={`https://etherscan.io/address/${creatorAddress}`}
style={{ textDecoration: 'none' }}
>
<CollectionProfile
label="Creator"
avatarUrl={asset.creator.profile_img_url}
name={creatorEnsName.ENSName ?? shortenAddress(creatorAddress, 0, 4)}
isVerified
className={styles.creator}
/>
</a>
) : null}
</Row>
</Column>
{asset.priceInfo && !isOwned ? (
<Row
marginTop="8"
marginBottom="40"
justifyContent="space-between"
borderRadius="12"
paddingTop="16"
paddingBottom="16"
paddingLeft="16"
paddingRight="24"
background="accentActiveSoft"
>
<Column justifyContent="flex-start" gap="8">
<Row gap="12" as="a" target="_blank" rel="norefferer">
<a href={asset.sellorders[0].marketplaceUrl} rel="noreferrer" target="_blank">
<img
className={styles.marketplace}
src={`/nft/svgs/marketplaces/${asset.sellorders[0].marketplace}.svg`}
height={16}
width={16}
alt="Markeplace"
/>
</a>
<Row as="span" className={subhead} color="textPrimary">
{formatEthPrice(asset.priceInfo.ETHPrice)} <Eth2Icon />
</Row>
<Box as="span" color="textSecondary" className={bodySmall}>
${toSignificant(asset.priceInfo.USDPrice)}
</Box>
</Row>
{asset.sellorders?.[0].orderClosingDate ? <CountdownTimer sellOrder={asset.sellorders[0]} /> : null}
</Column>
<Box
as="button"
paddingTop="14"
paddingBottom="14"
fontWeight="medium"
textAlign="center"
fontSize="14"
style={{ width: '244px' }}
color={isSelected ? 'genieBlue' : 'explicitWhite'}
border="none"
borderRadius="12"
background={isSelected ? 'explicitWhite' : 'genieBlue'}
transition="250"
boxShadow={{ hover: 'elevation' }}
onClick={() => {
if (isSelected) {
removeAssetFromBag(asset)
} else addAssetToBag(asset)
setSelected((x) => !x)
}}
>
{isSelected ? 'Added to Bag' : 'Buy Now'}
</Box>
</Row>
) : null}
<Row gap="32" marginBottom="20">
<button data-active={showTraits} onClick={() => setShowTraits(true)} className={styles.tab}>
Traits
</button>
<button data-active={!showTraits} onClick={() => setShowTraits(false)} className={styles.tab}>
Details
</button>
</Row>
{showTraits ? (
<Traits collectionAddress={asset.address} traits={asset.traits ?? []} />
) : (
<Details
contractAddress={contractAddress}
tokenId={tokenId}
tokenType={asset.tokenType}
blockchain="Ethereum"
metadataUrl={asset.externalLink}
totalSupply={collection.totalSupply}
/>
)}
</Column>
</div>
</AnimatedBox>
<AssetContainer>
<AssetDetails tokenId={tokenId} contractAddress={contractAddress} />
<AssetPriceDetails />
</AssetContainer>
)
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment