Commit 9de76c69 authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

feat: [DetailsV2] Data Page Header (#6549)

* hide header on mobile

* add buy and offer buttons, thumbnail, text

* handle no sell orders and add tests

* rehide on mobile

* breakpoint optimizations

* design feedback

---------
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>
parent 85d1b901
...@@ -44,7 +44,7 @@ const LeftColumn = styled(Column)` ...@@ -44,7 +44,7 @@ const LeftColumn = styled(Column)`
export const DataPage = ({ asset }: { asset: GenieAsset }) => { export const DataPage = ({ asset }: { asset: GenieAsset }) => {
return ( return (
<DataPageContainer> <DataPageContainer>
<DataPageHeader /> <DataPageHeader asset={asset} />
<ContentContainer> <ContentContainer>
<LeftColumn> <LeftColumn>
{!!asset.traits?.length && <DataPageTraits asset={asset} />} {!!asset.traits?.length && <DataPageTraits asset={asset} />}
......
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()
})
import styled from 'styled-components/macro' import { Trans } from '@lingui/macro'
import { ButtonGray, ButtonPrimary } from 'components/Button'
import Column from 'components/Column'
import Row from 'components/Row'
import { HandHoldingDollarIcon, VerifiedIcon } from 'nft/components/icons'
import { GenieAsset } from 'nft/types'
import { formatEth } from 'nft/utils'
import styled, { css } from 'styled-components/macro'
import { BREAKPOINTS, ThemedText } from 'theme'
import { containerStyles } from './shared' const HeaderContainer = styled(Row)`
gap: 24px;
@media screen and (max-width: ${BREAKPOINTS.md}px) {
display: none;
}
`
const HeaderContainer = styled.div` const AssetImage = styled.img`
width: 96px;
height: 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;
`
const ButtonStyles = css`
width: min-content;
flex-shrink: 0; flex-shrink: 0;
border-radius: 16px;
`
const BuyButton = styled(ButtonPrimary)`
display: flex;
flex-direction: row;
padding: 16px 24px;
gap: 8px;
line-height: 24px;
white-space: nowrap;
${ButtonStyles}
`
${containerStyles} const Price = styled.div`
color: ${({ theme }) => theme.accentTextLightSecondary};
`
const MakeOfferButtonSmall = styled(ButtonPrimary)`
padding: 16px;
${ButtonStyles}
`
padding-left: 0px; const MakeOfferButtonLarge = styled(ButtonGray)`
white-space: nowrap;
${ButtonStyles}
` `
export const DataPageHeader = () => { export const DataPageHeader = ({ asset }: { asset: GenieAsset }) => {
return <HeaderContainer>Header</HeaderContainer> const price = asset.sellorders?.[0]?.price.value
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">
{price ? (
<>
<BuyButton>
<Trans>Buy</Trans>
<Price>{formatEth(price)} ETH</Price>
</BuyButton>
<MakeOfferButtonSmall>
<HandHoldingDollarIcon />
</MakeOfferButtonSmall>
</>
) : (
<MakeOfferButtonLarge>
<Trans>Make an offer</Trans>
</MakeOfferButtonLarge>
)}
</Row>
</HeaderContainer>
)
} }
...@@ -1350,3 +1350,12 @@ export const UniswapMagentaIcon = (props: SVGProps) => ( ...@@ -1350,3 +1350,12 @@ export const UniswapMagentaIcon = (props: SVGProps) => (
/> />
</svg> </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>
)
import { MediaType, NftActivityType, NftStandard, OrderStatus } from 'graphql/data/__generated__/types-and-hooks' import {
import { ActivityEvent, CollectionInfoForAsset, GenieAsset, Markets, WalletAsset } from 'nft/types' MediaType,
NftActivityType,
NftMarketplace,
NftStandard,
OrderStatus,
OrderType,
} from 'graphql/data/__generated__/types-and-hooks'
import { ActivityEvent, CollectionInfoForAsset, GenieAsset, Markets, SellOrder, WalletAsset } from 'nft/types'
export const TEST_NFT_ASSET: GenieAsset = { export const TEST_NFT_ASSET: GenieAsset = {
id: 'TmZ0QXNzZXQ6MHhlZDVhZjM4ODY1MzU2N2FmMmYzODhlNjIyNGRjN2M0YjMyNDFjNTQ0XzMzMTg=', id: 'TmZ0QXNzZXQ6MHhlZDVhZjM4ODY1MzU2N2FmMmYzODhlNjIyNGRjN2M0YjMyNDFjNTQ0XzMzMTg=',
...@@ -212,3 +219,22 @@ export const TEST_NFT_COLLECTION_INFO_FOR_ASSET: CollectionInfoForAsset = { ...@@ -212,3 +219,22 @@ export const TEST_NFT_COLLECTION_INFO_FOR_ASSET: CollectionInfoForAsset = {
isVerified: true, isVerified: true,
totalSupply: 10000, totalSupply: 10000,
} }
export const TEST_SELL_ORDER: SellOrder = {
address: '0x29d7ebca656665c1a52a92f830e413e394db6b4f',
createdAt: 1683561510000,
endAt: 1699528045000,
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: {},
}
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