Commit ea02d9e9 authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

feat: List Page (#5323)

* working dropdown button

* memo text

* working dropdown modal

* click outside

* move grid to its own file

* add file

* re-organized page layout

* header updates

* text change

* margin

* ETH

* text input

* respond to comments

* move listinggrid to its own file

* add file

* organize imports

* remove 0 margin

* undo testing
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>
parent bb3b236c
...@@ -45,7 +45,7 @@ export const ChevronUpIcon = ({ ...@@ -45,7 +45,7 @@ export const ChevronUpIcon = ({
) )
export const BackArrowIcon = (props: SVGProps) => ( export const BackArrowIcon = (props: SVGProps) => (
<svg {...props} xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28"> <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28" {...props}>
<path d="M5.676 13.645c0 .263.114.527.316.72l5.801 5.792c.21.202.44.3.694.3.554 0 .958-.396.958-.933a.934.934 0 00-.29-.694l-1.977-2.004-2.55-2.329 2.049.123h10.652c.58 0 .985-.404.985-.975 0-.58-.405-.985-.985-.985H10.677l-2.04.123 2.54-2.329 1.978-2.004a.934.934 0 00.29-.694c0-.536-.404-.932-.958-.932-.255 0-.492.097-.72.317l-5.775 5.774a1.012 1.012 0 00-.316.73z" /> <path d="M5.676 13.645c0 .263.114.527.316.72l5.801 5.792c.21.202.44.3.694.3.554 0 .958-.396.958-.933a.934.934 0 00-.29-.694l-1.977-2.004-2.55-2.329 2.049.123h10.652c.58 0 .985-.404.985-.975 0-.58-.405-.985-.985-.985H10.677l-2.04.123 2.54-2.329 1.978-2.004a.934.934 0 00.29-.694c0-.536-.404-.932-.958-.932-.255 0-.492.097-.72.317l-5.775 5.774a1.012 1.012 0 00-.316.73z" />
</svg> </svg>
) )
......
...@@ -31,7 +31,7 @@ export const dropdown = style({ ...@@ -31,7 +31,7 @@ export const dropdown = style({
export const removeAsset = style({ export const removeAsset = style({
top: '31px', top: '31px',
left: '15px', left: '8px',
}) })
export const removeMarketplace = style({ export const removeMarketplace = style({
......
import { SMALL_MEDIA_BREAKPOINT } from 'components/Tokens/constants'
import { ListingButton } from 'nft/components/bag/profile/ListingButton' import { ListingButton } from 'nft/components/bag/profile/ListingButton'
import { getListingState } from 'nft/components/bag/profile/utils' import { getListingState } from 'nft/components/bag/profile/utils'
import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex' import { Column, Row } from 'nft/components/Flex'
import { BackArrowIcon } from 'nft/components/icons' import { BackArrowIcon } from 'nft/components/icons'
import { headlineLarge, headlineSmall } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css' import { themeVars } from 'nft/css/sprinkles.css'
import { useBag, useNFTList, useProfilePageState, useSellAsset } from 'nft/hooks' import { useBag, useIsMobile, useNFTList, useProfilePageState, useSellAsset } from 'nft/hooks'
import { ListingStatus, ProfilePageStateType } from 'nft/types' import { ListingStatus, ProfilePageStateType } from 'nft/types'
import { ListingMarkets } from 'nft/utils/listNfts' import { ListingMarkets } from 'nft/utils/listNfts'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
...@@ -16,11 +17,42 @@ import { SetDurationModal } from './SetDurationModal' ...@@ -16,11 +17,42 @@ import { SetDurationModal } from './SetDurationModal'
const MarketWrap = styled.section` const MarketWrap = styled.section`
gap: 48px; gap: 48px;
padding-left: 18px; margin: 0px auto;
padding-right: 48x; padding: 0px 16px;
margin-left: auto;
margin-right: auto;
max-width: 1200px; max-width: 1200px;
width: 100%;
@media screen and (min-width: ${SMALL_MEDIA_BREAKPOINT}) {
padding: 0px 44px;
}
`
const ListingHeader = styled(Row)`
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
margin-top: 18px;
@media screen and (min-width: ${SMALL_MEDIA_BREAKPOINT}) {
margin-top: 40px;
}
`
const GridWrapper = styled.div`
margin-top: 24px;
@media screen and (min-width: ${SMALL_MEDIA_BREAKPOINT}) {
margin-left: 40px;
}
`
const MobileListButtonWrapper = styled.div`
display: flex;
margin: 14px 16px 32px 16px;
@media screen and (min-width: ${SMALL_MEDIA_BREAKPOINT}) {
display: none;
}
` `
export const ListPage = () => { export const ListPage = () => {
...@@ -32,6 +64,7 @@ export const ListPage = () => { ...@@ -32,6 +64,7 @@ export const ListPage = () => {
const collectionsRequiringApproval = useNFTList((state) => state.collectionsRequiringApproval) const collectionsRequiringApproval = useNFTList((state) => state.collectionsRequiringApproval)
const listingStatus = useNFTList((state) => state.listingStatus) const listingStatus = useNFTList((state) => state.listingStatus)
const setListingStatus = useNFTList((state) => state.setListingStatus) const setListingStatus = useNFTList((state) => state.setListingStatus)
const isMobile = useIsMobile()
useEffect(() => { useEffect(() => {
const state = getListingState(collectionsRequiringApproval, listings) const state = getListingState(collectionsRequiringApproval, listings)
...@@ -53,31 +86,31 @@ export const ListPage = () => { ...@@ -53,31 +86,31 @@ export const ListPage = () => {
}, [selectedMarkets]) }, [selectedMarkets])
return ( return (
<Column display="flex" flexWrap="nowrap"> <Column>
<Column marginLeft="14" display="flex">
<Box
aria-label="Back"
as="button"
border="none"
onClick={() => setSellPageState(ProfilePageStateType.VIEWING)}
type="button"
backgroundColor="transparent"
cursor="pointer"
width="min"
>
<BackArrowIcon fill={themeVars.colors.textSecondary} />
</Box>
</Column>
<MarketWrap> <MarketWrap>
<Row flexWrap={{ sm: 'wrap', lg: 'nowrap' }}> <ListingHeader>
<SelectMarketplacesDropdown setSelectedMarkets={setSelectedMarkets} selectedMarkets={selectedMarkets} /> <Row gap="4" marginBottom={{ sm: '18', md: '0' }}>
<SetDurationModal /> <BackArrowIcon
</Row> height={isMobile ? 20 : 32}
<NFTListingsGrid selectedMarkets={selectedMarkets} /> width={isMobile ? 20 : 32}
fill={themeVars.colors.textSecondary}
onClick={() => setSellPageState(ProfilePageStateType.VIEWING)}
cursor="pointer"
/>
<div className={isMobile ? headlineSmall : headlineLarge}>Sell NFTs</div>
</Row>
<Row gap="12">
<SelectMarketplacesDropdown setSelectedMarkets={setSelectedMarkets} selectedMarkets={selectedMarkets} />
<SetDurationModal />
</Row>
</ListingHeader>
<GridWrapper>
<NFTListingsGrid selectedMarkets={selectedMarkets} />
</GridWrapper>
</MarketWrap> </MarketWrap>
<Box display={{ sm: 'flex', md: 'none' }} marginTop="14" marginX="16" marginBottom="32"> <MobileListButtonWrapper>
<ListingButton onClick={toggleBag} buttonText="Continue listing" /> <ListingButton onClick={toggleBag} buttonText="Continue listing" />
</Box> </MobileListButtonWrapper>
</Column> </Column>
) )
} }
...@@ -3,7 +3,7 @@ import { SortDropdown } from 'nft/components/common/SortDropdown' ...@@ -3,7 +3,7 @@ import { SortDropdown } from 'nft/components/common/SortDropdown'
import { Column, Row } from 'nft/components/Flex' import { Column, Row } from 'nft/components/Flex'
import { AttachPriceIcon, EditPriceIcon, RowsCollpsedIcon, RowsExpandedIcon, VerifiedIcon } from 'nft/components/icons' import { AttachPriceIcon, EditPriceIcon, RowsCollpsedIcon, RowsExpandedIcon, VerifiedIcon } from 'nft/components/icons'
import { NumericInput } from 'nft/components/layout/Input' import { NumericInput } from 'nft/components/layout/Input'
import { badge, body, bodySmall, headlineSmall, subheadSmall } from 'nft/css/common.css' import { badge, body, bodySmall, subheadSmall } from 'nft/css/common.css'
import { useSellAsset } from 'nft/hooks' import { useSellAsset } from 'nft/hooks'
import { DropDownOption, ListingMarket, ListingWarning, WalletAsset } from 'nft/types' import { DropDownOption, ListingMarket, ListingWarning, WalletAsset } from 'nft/types'
import { formatEth, formatUsdPrice } from 'nft/utils/currency' import { formatEth, formatUsdPrice } from 'nft/utils/currency'
...@@ -43,47 +43,46 @@ export const NFTListingsGrid = ({ selectedMarkets }: { selectedMarkets: ListingM ...@@ -43,47 +43,46 @@ export const NFTListingsGrid = ({ selectedMarkets }: { selectedMarkets: ListingM
return ( return (
<Column> <Column>
<Row className={headlineSmall}>Create your listings</Row>
<Row marginTop="20"> <Row marginTop="20">
<Column <Column
marginLeft={selectedMarkets.length > 1 ? '36' : '0'} marginLeft={selectedMarkets.length > 1 ? '36' : '0'}
transition="500" transition="500"
className={badge} className={bodySmall}
color="textSecondary" color="textSecondary"
flex="2" flex="2"
> >
YOUR NFTS NFT
</Column> </Column>
<Row flex={{ sm: '1', md: '3' }}> <Row flex={{ sm: '1.5', md: '3' }}>
<Column className={subheadSmall} style={{ flex: '1.5' }}> <Column className={subheadSmall} flex="1.5">
<SortDropdown dropDownOptions={priceDropdownOptions} mini miniPrompt="Set price by" /> <SortDropdown dropDownOptions={priceDropdownOptions} mini miniPrompt="Set price by" />
</Column> </Column>
<Column <Column
className={badge} className={bodySmall}
color="textSecondary" color="textSecondary"
flex="1" flex="1"
display={{ sm: 'none', md: 'flex' }} display={{ sm: 'none', md: 'flex' }}
textAlign="right" textAlign="right"
> >
MARKETPLACE FEE Marketplace fee
</Column> </Column>
<Column <Column
className={badge} className={bodySmall}
color="textSecondary" color="textSecondary"
flex="1" flex="1"
display={{ sm: 'none', md: 'flex' }} display={{ sm: 'none', md: 'flex' }}
textAlign="right" textAlign="right"
> >
ROYALTIES Royalties
</Column> </Column>
<Column <Column
className={badge} className={bodySmall}
color="textSecondary" color="textSecondary"
style={{ flex: '1.5' }} style={{ flex: '1.5' }}
display={{ sm: 'none', md: 'flex' }} display={{ sm: 'none', md: 'flex' }}
textAlign="right" textAlign="right"
> >
YOU RECEIVE You receive
</Column> </Column>
</Row> </Row>
</Row> </Row>
...@@ -263,7 +262,7 @@ const EthPriceDisplay = ({ ethPrice = 0 }: { ethPrice?: number }) => { ...@@ -263,7 +262,7 @@ const EthPriceDisplay = ({ ethPrice = 0 }: { ethPrice?: number }) => {
{formatUsdPrice(ethPrice * ethConversion)} {formatUsdPrice(ethPrice * ethConversion)}
</Box> </Box>
) : ( ) : (
'- Eth' '- ETH'
)} )}
</Row> </Row>
</Column> </Column>
......
import { SMALL_MEDIA_BREAKPOINT } from 'components/Tokens/constants'
import { useOnClickOutside } from 'hooks/useOnClickOutside' import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { Column, Row } from 'nft/components/Flex' import { Column, Row } from 'nft/components/Flex'
import { ChevronUpIcon } from 'nft/components/icons' import { ChevronUpIcon } from 'nft/components/icons'
import { Checkbox } from 'nft/components/layout/Checkbox' import { Checkbox } from 'nft/components/layout/Checkbox'
import { buttonTextMedium, caption } from 'nft/css/common.css' import { buttonTextMedium, caption } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css'
import { ListingMarket } from 'nft/types' import { ListingMarket } from 'nft/types'
import { ListingMarkets } from 'nft/utils/listNfts' import { ListingMarkets } from 'nft/utils/listNfts'
import { Dispatch, FormEvent, useMemo, useReducer, useRef } from 'react' import { Dispatch, FormEvent, useMemo, useReducer, useRef } from 'react'
...@@ -43,6 +45,7 @@ interface MarketplaceRowProps { ...@@ -43,6 +45,7 @@ interface MarketplaceRowProps {
const MarketplaceRow = ({ market, setSelectedMarkets, selectedMarkets }: MarketplaceRowProps) => { const MarketplaceRow = ({ market, setSelectedMarkets, selectedMarkets }: MarketplaceRowProps) => {
const isSelected = selectedMarkets.includes(market) const isSelected = selectedMarkets.includes(market)
const [hovered, toggleHovered] = useReducer((s) => !s, false) const [hovered, toggleHovered] = useReducer((s) => !s, false)
const toggleSelected = () => { const toggleSelected = () => {
if (selectedMarkets.length === 1 && isSelected) return if (selectedMarkets.length === 1 && isSelected) return
isSelected isSelected
...@@ -74,13 +77,16 @@ const MarketplaceRow = ({ market, setSelectedMarkets, selectedMarkets }: Marketp ...@@ -74,13 +77,16 @@ const MarketplaceRow = ({ market, setSelectedMarkets, selectedMarkets }: Marketp
const HeaderButtonWrap = styled(Row)` const HeaderButtonWrap = styled(Row)`
padding: 12px; padding: 12px;
border-radius: 12px; border-radius: 12px;
width: 220px; width: 180px;
justify-content: space-between; justify-content: space-between;
background: ${({ theme }) => theme.backgroundModule}; background: ${({ theme }) => theme.backgroundModule};
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background-color: ${({ theme }) => theme.backgroundInteractive}; background-color: ${({ theme }) => theme.backgroundInteractive};
} }
@media screen and (min-width: ${SMALL_MEDIA_BREAKPOINT}) {
width: 220px;
}
` `
const HeaderButtonContentWrapper = styled.div` const HeaderButtonContentWrapper = styled.div`
...@@ -101,6 +107,7 @@ const MarketIcon = styled.img<{ index: number; totalSelected: number }>` ...@@ -101,6 +107,7 @@ const MarketIcon = styled.img<{ index: number; totalSelected: number }>`
const Chevron = styled(ChevronUpIcon)<{ isOpen: boolean }>` const Chevron = styled(ChevronUpIcon)<{ isOpen: boolean }>`
height: 20px; height: 20px;
width: 20px; width: 20px;
fill: ${({ theme }) => theme.textPrimary};
transition: ${({ transition: ${({
theme: { theme: {
transition: { duration }, transition: { duration },
...@@ -121,7 +128,7 @@ const DropdownWrapper = styled(Column)<{ isOpen: boolean }>` ...@@ -121,7 +128,7 @@ const DropdownWrapper = styled(Column)<{ isOpen: boolean }>`
display: ${({ isOpen }) => (isOpen ? 'flex' : 'none')}; display: ${({ isOpen }) => (isOpen ? 'flex' : 'none')};
position: absolute; position: absolute;
top: 52px; top: 52px;
width: 220px; width: 100%;
border-radius: 12px; border-radius: 12px;
gap: 12px; gap: 12px;
z-index: ${Z_INDEX.modalBackdrop}; z-index: ${Z_INDEX.modalBackdrop};
...@@ -159,7 +166,7 @@ export const SelectMarketplacesDropdown = ({ ...@@ -159,7 +166,7 @@ export const SelectMarketplacesDropdown = ({
{dropdownDisplayText} {dropdownDisplayText}
</HeaderButtonContentWrapper> </HeaderButtonContentWrapper>
<Chevron isOpen={isOpen} /> <Chevron isOpen={isOpen} secondaryColor={themeVars.colors.textPrimary} />
</HeaderButtonWrap> </HeaderButtonWrap>
<DropdownWrapper isOpen={isOpen}> <DropdownWrapper isOpen={isOpen}>
{ListingMarkets.map((market) => { {ListingMarkets.map((market) => {
......
...@@ -13,6 +13,7 @@ import { ThemedText } from 'theme' ...@@ -13,6 +13,7 @@ import { ThemedText } from 'theme'
const ModalWrapper = styled(Column)` const ModalWrapper = styled(Column)`
gap: 4px; gap: 4px;
position: relative;
` `
const InputWrapper = styled(Row)<{ isInvalid: boolean }>` const InputWrapper = styled(Row)<{ isInvalid: boolean }>`
...@@ -40,6 +41,8 @@ const DropdownWrapper = styled(ThemedText.BodyPrimary)` ...@@ -40,6 +41,8 @@ const DropdownWrapper = styled(ThemedText.BodyPrimary)`
const ErrorMessage = styled(Row)` const ErrorMessage = styled(Row)`
color: ${({ theme }) => theme.accentCritical}; color: ${({ theme }) => theme.accentCritical};
gap: 4px; gap: 4px;
position: absolute;
top: 44px;
` `
const WarningIcon = styled(AlertTriangle)` const WarningIcon = styled(AlertTriangle)`
...@@ -108,6 +111,7 @@ export const SetDurationModal = () => { ...@@ -108,6 +111,7 @@ export const SetDurationModal = () => {
<InputWrapper isInvalid={errorState !== ErrorState.valid}> <InputWrapper isInvalid={errorState !== ErrorState.valid}>
<NumericInput <NumericInput
as="input" as="input"
type="number"
pattern="[0-9]" pattern="[0-9]"
borderStyle="none" borderStyle="none"
className={bodySmall} className={bodySmall}
......
...@@ -338,7 +338,7 @@ const layoutStyles = defineProperties({ ...@@ -338,7 +338,7 @@ const layoutStyles = defineProperties({
zIndex: zIndices, zIndex: zIndices,
gap: spacing, gap: spacing,
flexShrink: spacing, flexShrink: spacing,
flex: ['1', '2', '3'], flex: ['1', '1.5', '2', '3'],
flexWrap: ['nowrap', 'wrap', 'wrap-reverse'], flexWrap: ['nowrap', 'wrap', 'wrap-reverse'],
display: ['none', 'block', 'flex', 'inline-flex', 'inline-block', 'grid', 'inline'], display: ['none', 'block', 'flex', 'inline-flex', 'inline-block', 'grid', 'inline'],
whiteSpace: ['nowrap'], whiteSpace: ['nowrap'],
......
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