Commit 1536e187 authored by Jack Short's avatar Jack Short Committed by GitHub

style: updating details (#5371)

* style: adjusting details page

* moving owners

* removing traits if no traits

* adding accent colors

* trait grid breakpoint

* some comments

* addressing comments
parent c19431eb
...@@ -6,7 +6,6 @@ import { reduceFilters } from 'nft/components/collection/Activity' ...@@ -6,7 +6,6 @@ import { reduceFilters } from 'nft/components/collection/Activity'
import { LoadingSparkle } from 'nft/components/common/Loading/LoadingSparkle' import { LoadingSparkle } from 'nft/components/common/Loading/LoadingSparkle'
import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails' import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails'
import { Center } from 'nft/components/Flex' import { Center } from 'nft/components/Flex'
import { VerifiedIcon } from 'nft/components/icons'
import { ActivityFetcher } from 'nft/queries/genie/ActivityFetcher' import { ActivityFetcher } from 'nft/queries/genie/ActivityFetcher'
import { ActivityEventResponse, ActivityEventType, CollectionInfoForAsset, GenieAsset } from 'nft/types' import { ActivityEventResponse, ActivityEventType, CollectionInfoForAsset, GenieAsset } from 'nft/types'
import { shortenAddress } from 'nft/utils/address' import { shortenAddress } from 'nft/utils/address'
...@@ -27,17 +26,6 @@ import DetailsContainer from './DetailsContainer' ...@@ -27,17 +26,6 @@ import DetailsContainer from './DetailsContainer'
import InfoContainer from './InfoContainer' import InfoContainer from './InfoContainer'
import TraitsContainer from './TraitsContainer' import TraitsContainer from './TraitsContainer'
const CollectionHeader = styled.span`
display: flex;
align-items: center;
font-size: 16px;
line-height: 24px;
color: ${({ theme }) => theme.textPrimary};
margin-top: 28px;
text-decoration: none;
${OpacityHoverState};
`
const AssetPriceDetailsContainer = styled.div` const AssetPriceDetailsContainer = styled.div`
margin-top: 20px; margin-top: 20px;
display: none; display: none;
...@@ -46,15 +34,6 @@ const AssetPriceDetailsContainer = styled.div` ...@@ -46,15 +34,6 @@ const AssetPriceDetailsContainer = styled.div`
} }
` `
const AssetHeader = styled.div`
display: flex;
align-items: center;
font-size: 36px;
line-height: 36px;
color: ${({ theme }) => theme.textPrimary};
margin-top: 8px;
`
const MediaContainer = styled.div` const MediaContainer = styled.div`
display: flex; display: flex;
justify-content: center; justify-content: center;
...@@ -118,10 +97,6 @@ const Link = styled(RouterLink)` ...@@ -118,10 +97,6 @@ const Link = styled(RouterLink)`
${OpacityHoverState}; ${OpacityHoverState};
` `
const DefaultLink = styled(RouterLink)`
text-decoration: none;
`
const ActivitySelectContainer = styled.div` const ActivitySelectContainer = styled.div`
display: flex; display: flex;
gap: 8px; gap: 8px;
...@@ -150,8 +125,11 @@ const ContentNotAvailable = styled.div` ...@@ -150,8 +125,11 @@ const ContentNotAvailable = styled.div`
const FilterBox = styled.div<{ isActive?: boolean }>` const FilterBox = styled.div<{ isActive?: boolean }>`
box-sizing: border-box; box-sizing: border-box;
background-color: ${({ theme }) => theme.backgroundInteractive}; background-color: ${({ theme }) => theme.backgroundInteractive};
font-size: 14px;
font-weight: 600;
line-height: 14px;
color: ${({ theme }) => theme.textPrimary}; color: ${({ theme }) => theme.textPrimary};
padding: 12px 16px; padding: 8px 16px;
border-radius: 12px; border-radius: 12px;
cursor: pointer; cursor: pointer;
box-sizing: border-box; box-sizing: border-box;
...@@ -383,43 +361,39 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => { ...@@ -383,43 +361,39 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
<AssetView asset={asset} mediaType={assetMediaType} dominantColor={dominantColor} /> <AssetView asset={asset} mediaType={assetMediaType} dominantColor={dominantColor} />
)} )}
</MediaContainer> </MediaContainer>
<DefaultLink to={`/nfts/collection/${asset.address}`}>
<CollectionHeader>
{collection.collectionName} {collection.isVerified && <VerifiedIcon />}
</CollectionHeader>
</DefaultLink>
<AssetHeader>{asset.name ?? `${asset.collectionName} #${asset.tokenId}`}</AssetHeader>
<AssetPriceDetailsContainer> <AssetPriceDetailsContainer>
<AssetPriceDetails asset={asset} collection={collection} /> <AssetPriceDetails asset={asset} collection={collection} />
</AssetPriceDetailsContainer> </AssetPriceDetailsContainer>
<InfoContainer {asset.traits && (
primaryHeader="Traits" <InfoContainer
defaultOpen primaryHeader="Traits"
secondaryHeader={ defaultOpen
rarityProvider && rarity && rarity.score ? ( secondaryHeader={
<MouseoverTooltip rarityProvider && rarity && rarity.score ? (
text={ <MouseoverTooltip
<HoverContainer> text={
<HoverImageContainer> <HoverContainer>
<img src={rarityProviderLogo} alt="cardLogo" width={16} /> <HoverImageContainer>
</HoverImageContainer> <img src={rarityProviderLogo} alt="cardLogo" width={16} />
<ContainerText> </HoverImageContainer>
{`Ranking by ${rarity.provider === 'Genie' ? fallbackProvider : rarity.provider}`} <ContainerText>
</ContainerText> {`Ranking by ${rarity.provider === 'Genie' ? fallbackProvider : rarity.provider}`}
</HoverContainer> </ContainerText>
} </HoverContainer>
placement="top" }
> placement="top"
<RarityWrap>Rarity: {putCommas(rarity.score)}</RarityWrap> >
</MouseoverTooltip> <RarityWrap>Rarity: {putCommas(rarity.score)}</RarityWrap>
) : null </MouseoverTooltip>
} ) : null
> }
<TraitsContainer asset={asset} /> >
</InfoContainer> <TraitsContainer asset={asset} />
</InfoContainer>
)}
<InfoContainer <InfoContainer
primaryHeader="Activity" primaryHeader="Activity"
defaultOpen
secondaryHeader={formattedPrice ? `Last Sale: ${formattedPrice} ETH` : undefined} secondaryHeader={formattedPrice ? `Last Sale: ${formattedPrice} ETH` : undefined}
> >
<> <>
......
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { OpacityHoverState } from 'components/Common' import { OpacityHoverState } from 'components/Common'
import { useNftBalanceQuery } from 'graphql/data/nft/NftBalance' import { useNftBalanceQuery } from 'graphql/data/nft/NftBalance'
import useCopyClipboard from 'hooks/useCopyClipboard' import { CancelListingIcon, VerifiedIcon } from 'nft/components/icons'
import { CancelListingIcon } from 'nft/components/icons'
import { useBag, useProfilePageState, useSellAsset } from 'nft/hooks' import { useBag, useProfilePageState, useSellAsset } from 'nft/hooks'
import { CollectionInfoForAsset, GenieAsset, ProfilePageStateType, WalletAsset } from 'nft/types' import { CollectionInfoForAsset, GenieAsset, ProfilePageStateType, WalletAsset } from 'nft/types'
import { ethNumberStandardFormatter, formatEthPrice, getMarketplaceIcon, timeLeft, useUsdPrice } from 'nft/utils' import {
ethNumberStandardFormatter,
formatEthPrice,
generateTweetForAsset,
getMarketplaceIcon,
timeLeft,
useUsdPrice,
} from 'nft/utils'
import { shortenAddress } from 'nft/utils/address' import { shortenAddress } from 'nft/utils/address'
import { useMemo } from 'react' import { useMemo } from 'react'
import { Upload } from 'react-feather' import { Upload } from 'react-feather'
...@@ -13,6 +19,9 @@ import { Link, useNavigate } from 'react-router-dom' ...@@ -13,6 +19,9 @@ import { Link, useNavigate } from 'react-router-dom'
import styled, { css, useTheme } from 'styled-components/macro' import styled, { css, useTheme } from 'styled-components/macro'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
const TWITTER_WIDTH = 560
const TWITTER_HEIGHT = 480
interface AssetPriceDetailsProps { interface AssetPriceDetailsProps {
asset: GenieAsset asset: GenieAsset
collection: CollectionInfoForAsset collection: CollectionInfoForAsset
...@@ -45,12 +54,15 @@ const hoverState = css` ...@@ -45,12 +54,15 @@ const hoverState = css`
` `
const Container = styled.div` const Container = styled.div`
display: flex;
flex-direction: column;
width: 100%; width: 100%;
gap: 24px;
@media (min-width: 960px) { @media (min-width: calc(960px + 1px)) {
position: fixed; position: fixed;
width: 360px; width: 360px;
margin-top: -6px; margin-top: 20px;
} }
` `
...@@ -129,7 +141,8 @@ const DiscoveryContainer = styled.div` ...@@ -129,7 +141,8 @@ const DiscoveryContainer = styled.div`
` `
const OwnerText = styled.a` const OwnerText = styled.a`
font-size: 14px; font-size: 16px;
font-weight: 600;
line-height: 20px; line-height: 20px;
color: ${({ theme }) => theme.textSecondary}; color: ${({ theme }) => theme.textSecondary};
text-decoration: none; text-decoration: none;
...@@ -140,9 +153,48 @@ const OwnerText = styled.a` ...@@ -140,9 +153,48 @@ const OwnerText = styled.a`
const OwnerInformationContainer = styled.div` const OwnerInformationContainer = styled.div`
color: ${({ theme }) => theme.textSecondary}; color: ${({ theme }) => theme.textSecondary};
display: flex; display: flex;
flex-direction: column;
justify-content: space-between; justify-content: space-between;
padding: 0 8px; padding: 0 8px;
margin-bottom: 20px; `
const AssetInfoContainer = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
`
const AssetHeader = styled.div`
display: -webkit-box;
align-items: center;
font-size: 28px;
font-weight: 500;
line-height: 36px;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
color: ${({ theme }) => theme.textPrimary};
`
const CollectionNameContainer = styled.div`
display: flex;
justify-content: space-between;
`
const CollectionHeader = styled.span`
display: flex;
align-items: center;
font-size: 16px;
font-weight: 400;
line-height: 24px;
color: ${({ theme }) => theme.textPrimary};
text-decoration: none;
${OpacityHoverState};
`
const DefaultLink = styled(Link)`
text-decoration: none;
` `
export const OwnerContainer = ({ asset }: { asset: GenieAsset }) => { export const OwnerContainer = ({ asset }: { asset: GenieAsset }) => {
...@@ -168,48 +220,46 @@ export const OwnerContainer = ({ asset }: { asset: GenieAsset }) => { ...@@ -168,48 +220,46 @@ export const OwnerContainer = ({ asset }: { asset: GenieAsset }) => {
} }
return ( return (
<Container> <BestPriceContainer>
<BestPriceContainer> <HeaderRow>
<HeaderRow> <ThemedText.SubHeader color="accentAction" fontWeight={500} lineHeight="24px">
<ThemedText.SubHeader fontWeight={500} lineHeight="24px"> {listing ? 'Your Price' : 'List for Sale'}
{listing ? 'Your Price' : 'List for Sale'} </ThemedText.SubHeader>
</ThemedText.SubHeader> {listing && <MarketplaceIcon alt={listing.marketplace} src={getMarketplaceIcon(listing.marketplace)} />}
{listing && <MarketplaceIcon alt={listing.marketplace} src={getMarketplaceIcon(listing.marketplace)} />} </HeaderRow>
</HeaderRow> <PriceRow>
<PriceRow> {listing ? (
{listing ? (
<>
<ThemedText.MediumHeader fontSize="28px" lineHeight="36px">
{formatEthPrice(asset.priceInfo.ETHPrice)}
</ThemedText.MediumHeader>
{USDPrice && (
<ThemedText.BodySecondary lineHeight="24px">
{ethNumberStandardFormatter(USDPrice, true, true)}
</ThemedText.BodySecondary>
)}
</>
) : (
<ThemedText.BodySecondary fontSize="14px" lineHeight="20px">
Get the best price for your NFT by selling with Uniswap.
</ThemedText.BodySecondary>
)}
</PriceRow>
{expirationDate && (
<ThemedText.BodySecondary fontSize="14px">Sale ends: {timeLeft(expirationDate)}</ThemedText.BodySecondary>
)}
{!listing ? (
<BuyNowButton assetInBag={false} margin={true} useAccentColor={true} onClick={goToListPage}>
<ThemedText.SubHeader lineHeight="20px">List</ThemedText.SubHeader>
</BuyNowButton>
) : (
<> <>
<BuyNowButton assetInBag={false} margin={true} useAccentColor={false} onClick={goToListPage}> <ThemedText.MediumHeader fontSize="28px" lineHeight="36px">
<ThemedText.SubHeader lineHeight="20px">Adjust listing</ThemedText.SubHeader> {formatEthPrice(asset.priceInfo.ETHPrice)}
</BuyNowButton> </ThemedText.MediumHeader>
{USDPrice && (
<ThemedText.BodySecondary lineHeight="24px">
{ethNumberStandardFormatter(USDPrice, true, true)}
</ThemedText.BodySecondary>
)}
</> </>
) : (
<ThemedText.BodySecondary fontSize="14px" lineHeight="20px">
Get the best price for your NFT by selling with Uniswap.
</ThemedText.BodySecondary>
)} )}
</BestPriceContainer> </PriceRow>
</Container> {expirationDate && (
<ThemedText.BodySecondary fontSize="14px">Sale ends: {timeLeft(expirationDate)}</ThemedText.BodySecondary>
)}
{!listing ? (
<BuyNowButton assetInBag={false} margin={true} useAccentColor={true} onClick={goToListPage}>
<ThemedText.SubHeader lineHeight="20px">List</ThemedText.SubHeader>
</BuyNowButton>
) : (
<>
<BuyNowButton assetInBag={false} margin={true} useAccentColor={false} onClick={goToListPage}>
<ThemedText.SubHeader lineHeight="20px">Adjust listing</ThemedText.SubHeader>
</BuyNowButton>
</>
)}
</BestPriceContainer>
) )
} }
...@@ -258,7 +308,6 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps) ...@@ -258,7 +308,6 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps)
const bagExpanded = useBag((s) => s.bagExpanded) const bagExpanded = useBag((s) => s.bagExpanded)
const USDPrice = useUsdPrice(asset) const USDPrice = useUsdPrice(asset)
const [, setCopied] = useCopyClipboard()
const { assetInBag } = useMemo(() => { const { assetInBag } = useMemo(() => {
return { return {
...@@ -268,40 +317,40 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps) ...@@ -268,40 +317,40 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps)
} }
}, [asset, itemsInBag]) }, [asset, itemsInBag])
const isOwner = asset.owner ? account?.toLowerCase() === asset.owner?.address?.toLowerCase() : false const shareTweet = () => {
window.open(
if (isOwner) { generateTweetForAsset(asset),
return <OwnerContainer asset={asset} /> 'newwindow',
`left=${(window.screen.width - TWITTER_WIDTH) / 2}, top=${
(window.screen.height - TWITTER_HEIGHT) / 2
}, width=${TWITTER_WIDTH}, height=${TWITTER_HEIGHT}`
)
} }
const isOwner = asset.owner ? account?.toLowerCase() === asset.owner?.address?.toLowerCase() : false
const isForSale = cheapestOrder && asset.priceInfo
return ( return (
<Container> <Container>
<OwnerInformationContainer> <AssetInfoContainer>
<OwnerText <CollectionNameContainer>
target="_blank" <DefaultLink to={`/nfts/collection/${asset.address}`}>
href={`https://etherscan.io/address/${asset.owner.address}`} <CollectionHeader>
rel="noopener noreferrer" {collection.collectionName} {collection.isVerified && <VerifiedIcon />}
> </CollectionHeader>
{asset.tokenType === 'ERC1155' ? ( </DefaultLink>
'' <UploadLink onClick={shareTweet} target="_blank">
) : ( <Upload size={20} strokeWidth={2} />
<span> Seller: {isOwner ? 'you' : asset.owner.address && shortenAddress(asset.owner.address, 2, 4)}</span> </UploadLink>
)} </CollectionNameContainer>
</OwnerText> <AssetHeader>{asset.name ?? `${asset.collectionName} #${asset.tokenId}`}</AssetHeader>
<UploadLink </AssetInfoContainer>
onClick={() => { {isOwner ? (
setCopied(window.location.href) <OwnerContainer asset={asset} />
}} ) : isForSale ? (
target="_blank"
>
<Upload size={20} strokeWidth={2} />
</UploadLink>
</OwnerInformationContainer>
{cheapestOrder && asset.priceInfo ? (
<BestPriceContainer> <BestPriceContainer>
<HeaderRow> <HeaderRow>
<ThemedText.SubHeader fontWeight={500} lineHeight="24px"> <ThemedText.SubHeader color="accentAction" fontWeight={500} lineHeight="24px">
Best Price Best Price
</ThemedText.SubHeader> </ThemedText.SubHeader>
<MarketplaceIcon alt={cheapestOrder.marketplace} src={getMarketplaceIcon(cheapestOrder.marketplace)} /> <MarketplaceIcon alt={cheapestOrder.marketplace} src={getMarketplaceIcon(cheapestOrder.marketplace)} />
...@@ -342,6 +391,24 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps) ...@@ -342,6 +391,24 @@ export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps)
) : ( ) : (
<NotForSale collectionName={collection.collectionName ?? 'this collection'} collectionUrl={asset.address} /> <NotForSale collectionName={collection.collectionName ?? 'this collection'} collectionUrl={asset.address} />
)} )}
{isForSale && (
<OwnerInformationContainer>
<ThemedText.BodySmall color="textSecondary" lineHeight="20px">
Seller:
</ThemedText.BodySmall>
<OwnerText
target="_blank"
href={`https://etherscan.io/address/${asset.owner.address}`}
rel="noopener noreferrer"
>
{asset.tokenType === 'ERC1155' ? (
''
) : (
<span> {isOwner ? 'You' : asset.owner.address && shortenAddress(asset.owner.address, 2, 4)}</span>
)}
</OwnerText>
</OwnerInformationContainer>
)}
</Container> </Container>
) )
} }
...@@ -11,7 +11,7 @@ const Grid = styled.div` ...@@ -11,7 +11,7 @@ const Grid = styled.div`
gap: 16px; gap: 16px;
max-width: 780px; max-width: 780px;
@media (max-width: 960px) { @media (max-width: 1080px) {
grid-template-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr;
} }
......
...@@ -28,3 +28,11 @@ export const getAssetHref = (asset: GenieAsset | WalletAsset, origin?: DetailsOr ...@@ -28,3 +28,11 @@ export const getAssetHref = (asset: GenieAsset | WalletAsset, origin?: DetailsOr
export const getMarketplaceIcon = (marketplace: string) => { export const getMarketplaceIcon = (marketplace: string) => {
return `/nft/svgs/marketplaces/${marketplace.toLowerCase()}.svg` return `/nft/svgs/marketplaces/${marketplace.toLowerCase()}.svg`
} }
export const generateTweetForAsset = (asset: GenieAsset): string => {
return `https://twitter.com/intent/tweet?text=Check%20out%20${
asset.name ?? `${asset.collectionName}%20%23${asset.tokenId}`
}%20(${asset.collectionName})%20https://app.uniswap.org/%23/nfts/asset/${asset.address}/${
asset.tokenId
}%20via%20@uniswap`
}
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