Commit 7bf74102 authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

feat: profile listing sidebar (#4809)

* Add sell header to Bag

* split bag content to its own file

* empty tag state

* continue button

* file re-arranging and add profile select row content

* update padding

* better null check
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>
parent 0017e2fc
......@@ -10,7 +10,6 @@ import { useLocation } from 'react-router-dom'
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
const Cart = lazy(() => import('nft/components/profile/modal/ListingTag'))
const Bag = lazy(() => import('nft/components/bag/Bag'))
const TransactionCompleteModal = lazy(() => import('nft/components/collection/TransactionCompleteModal'))
......@@ -30,7 +29,6 @@ export default function TopLevelModals() {
<ConnectedAccountBlocked account={account} isOpen={open} />
{useTokensFlag() === TokensVariant.Enabled &&
(location.pathname.includes('/pool') || location.pathname.includes('/swap')) && <TokensBanner />}
<Cart />
<Bag />
{useNftFlag() === NftVariant.Enabled && <TransactionCompleteModal />}
</>
......
import { style } from '@vanilla-extract/css'
import { subhead } from 'nft/css/common.css'
import { breakpoints, sprinkles, vars } from 'nft/css/sprinkles.css'
import { breakpoints, sprinkles } from 'nft/css/sprinkles.css'
export const bagContainer = style([
sprinkles({
......@@ -35,27 +34,3 @@ export const assetsContainer = style([
scrollbarWidth: 'none',
},
])
export const header = style([
subhead,
sprinkles({
color: 'textPrimary',
justifyContent: 'space-between',
}),
{
lineHeight: '24px',
},
])
export const clearAll = style([
sprinkles({
color: 'textTertiary',
cursor: 'pointer',
fontWeight: 'semibold',
}),
{
':hover': {
color: vars.color.blue400,
},
},
])
......@@ -3,21 +3,28 @@ import { formatEther } from '@ethersproject/units'
import { useWeb3React } from '@web3-react/core'
import { parseEther } from 'ethers/lib/utils'
import { BagFooter } from 'nft/components/bag/BagFooter'
import { BagRow, PriceChangeBagRow, UnavailableAssetsHeaderRow } from 'nft/components/bag/BagRow'
import ListingModal from 'nft/components/bag/profile/ListingModal'
import { Box } from 'nft/components/Box'
import { Portal } from 'nft/components/common/Portal'
import { Center, Column, Row } from 'nft/components/Flex'
import { BagCloseIcon, LargeBagIcon } from 'nft/components/icons'
import { Center, Column } from 'nft/components/Flex'
import { LargeBagIcon, LargeTagIcon } from 'nft/components/icons'
import { Overlay } from 'nft/components/modals/Overlay'
import { subhead } from 'nft/css/common.css'
import { buttonTextMedium, commonButtonStyles, subhead } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css'
import { useBag, useIsMobile, useSendTransaction, useTransactionResponse, useWalletBalance } from 'nft/hooks'
import {
useBag,
useIsMobile,
useProfilePageState,
useSellAsset,
useSendTransaction,
useTransactionResponse,
useWalletBalance,
} from 'nft/hooks'
import { fetchRoute } from 'nft/queries'
import { BagItemStatus, BagStatus, RouteResponse, TxStateType } from 'nft/types'
import { BagItemStatus, BagStatus, ProfilePageStateType, RouteResponse, TxStateType } from 'nft/types'
import { buildSellObject } from 'nft/utils/buildSellObject'
import { recalculateBagUsingPooledAssets } from 'nft/utils/calcPoolPrice'
import { fetchPrice } from 'nft/utils/fetchPrice'
import { roundAndPluralize } from 'nft/utils/roundAndPluralize'
import { combineBuyItemsWithTxRoute } from 'nft/utils/txRoute/combineItemsWithTxRoute'
import { sortUpdatedAssets } from 'nft/utils/updatedAssets'
import { useEffect, useMemo, useRef, useState } from 'react'
......@@ -25,22 +32,36 @@ import { useQuery, useQueryClient } from 'react-query'
import { useLocation } from 'react-router-dom'
import * as styles from './Bag.css'
import { BagContent } from './BagContent'
import { BagHeader } from './BagHeader'
import { ProfileBagContent } from './profile/ProfileBagContent'
const EmptyState = () => {
const { pathname } = useLocation()
const isProfilePage = pathname.startsWith('/profile')
return (
<Center height="full">
<Column gap="12">
<Column gap={isProfilePage ? '16' : '12'}>
<Center>
<LargeBagIcon color={themeVars.colors.textTertiary} />
{isProfilePage ? (
<LargeTagIcon color={themeVars.colors.textTertiary} />
) : (
<LargeBagIcon color={themeVars.colors.textTertiary} />
)}
</Center>
<Column gap="16">
<Center className={subhead} style={{ lineHeight: '24px' }}>
Your bag is empty
</Center>
<Center fontSize="12" fontWeight="normal" color="textSecondary" style={{ lineHeight: '16px' }}>
Selected NFTs will appear here
</Center>
</Column>
{isProfilePage ? (
<span className={subhead}>No NFTs Selected</span>
) : (
<Column gap="16">
<Center className={subhead} style={{ lineHeight: '24px' }}>
Your bag is empty
</Center>
<Center fontSize="12" fontWeight="normal" color="textSecondary" style={{ lineHeight: '16px' }}>
Selected NFTs will appear here
</Center>
</Column>
)}
</Column>
</Center>
)
......@@ -64,52 +85,21 @@ const ScrollingIndicator = ({ top, show }: SeparatorProps) => (
/>
)
interface BagHeaderProps {
numberOfAssets: number
toggleBag: () => void
resetFlow: () => void
}
const BagHeader = ({ numberOfAssets, toggleBag, resetFlow }: BagHeaderProps) => {
return (
<Column gap="4" paddingX="32" marginBottom="20">
<Row className={styles.header}>
My bag
<Box display="flex" padding="2" color="textSecondary" cursor="pointer" onClick={toggleBag}>
<BagCloseIcon />
</Box>
</Row>
{numberOfAssets > 0 && (
<Box fontSize="14" fontWeight="normal" style={{ lineHeight: '20px' }} color="textPrimary">
{roundAndPluralize(numberOfAssets, 'NFT')} ·{' '}
<Box
as="span"
className={styles.clearAll}
onClick={() => {
resetFlow()
}}
>
Clear all
</Box>
</Box>
)}
</Column>
)
}
const Bag = () => {
const { account } = useWeb3React()
const bagStatus = useBag((s) => s.bagStatus)
const setBagStatus = useBag((s) => s.setBagStatus)
const markAssetAsReviewed = useBag((s) => s.markAssetAsReviewed)
const didOpenUnavailableAssets = useBag((s) => s.didOpenUnavailableAssets)
const setDidOpenUnavailableAssets = useBag((s) => s.setDidOpenUnavailableAssets)
const bagIsLocked = useBag((s) => s.isLocked)
const setLocked = useBag((s) => s.setLocked)
const reset = useBag((s) => s.reset)
const resetSellAssets = useSellAsset((state) => state.reset)
const sellAssets = useSellAsset((state) => state.sellAssets)
const setProfilePageState = useProfilePageState((state) => state.setProfilePageState)
const profilePageState = useProfilePageState((state) => state.state)
const uncheckedItemsInBag = useBag((s) => s.itemsInBag)
const setItemsInBag = useBag((s) => s.setItemsInBag)
const removeAssetFromBag = useBag((s) => s.removeAssetFromBag)
const bagExpanded = useBag((s) => s.bagExpanded)
const toggleBag = useBag((s) => s.toggleBag)
const setTotalEthPrice = useBag((s) => s.setTotalEthPrice)
......@@ -121,7 +111,7 @@ const Bag = () => {
const { pathname } = useLocation()
const isProfilePage = pathname.startsWith('/profile')
const isNFTPage = pathname.startsWith('/nfts')
const shouldShowBag = isNFTPage && !isProfilePage
const shouldShowBag = isNFTPage || isProfilePage
const isMobile = useIsMobile()
const sendTransaction = useSendTransaction((state) => state.sendTransaction)
......@@ -255,31 +245,6 @@ const Bag = () => {
useSendTransaction.subscribe((state) => (transactionStateRef.current = state.state))
}, [])
const { unchangedAssets, priceChangedAssets, unavailableAssets, availableItems } = useMemo(() => {
const unchangedAssets = itemsInBag
.filter((item) => item.status === BagItemStatus.ADDED_TO_BAG || item.status === BagItemStatus.REVIEWED)
.map((item) => item.asset)
const priceChangedAssets = itemsInBag
.filter((item) => item.status === BagItemStatus.REVIEWING_PRICE_CHANGE)
.map((item) => item.asset)
const unavailableAssets = itemsInBag
.filter((item) => item.status === BagItemStatus.UNAVAILABLE)
.map((item) => item.asset)
const availableItems = itemsInBag.filter((item) => item.status !== BagItemStatus.UNAVAILABLE)
return { unchangedAssets, priceChangedAssets, unavailableAssets, availableItems }
}, [itemsInBag])
useEffect(() => {
const hasAssetsInReview = priceChangedAssets.length > 0
const hasAssets = itemsInBag.length > 0
if (bagStatus === BagStatus.IN_REVIEW && !hasAssetsInReview) {
if (hasAssets) setBagStatus(BagStatus.CONFIRM_REVIEW)
else setBagStatus(BagStatus.ADDING_TO_BAG)
}
}, [bagStatus, itemsInBag, priceChangedAssets, setBagStatus])
useEffect(() => {
if (bagIsLocked && !isOpen) setModalIsOpen(true)
}, [bagIsLocked, isOpen])
......@@ -307,7 +272,7 @@ const Bag = () => {
setTotalUsdPrice(totalUsdPrice)
}, [totalEthPrice, totalUsdPrice, setTotalEthPrice, setTotalUsdPrice])
const hasAssetsToShow = itemsInBag.length > 0 || unavailableAssets.length > 0
const hasAssetsToShow = itemsInBag.length > 0
const scrollHandler = (event: React.UIEvent<HTMLDivElement>) => {
const scrollTop = event.currentTarget.scrollTop
......@@ -322,57 +287,52 @@ const Bag = () => {
{bagExpanded && shouldShowBag ? (
<Portal>
<Column zIndex={isMobile || isOpen ? 'modal' : '3'} className={styles.bagContainer}>
<BagHeader numberOfAssets={itemsInBag.length} toggleBag={toggleBag} resetFlow={reset} />
{itemsInBag.length === 0 && bagStatus === BagStatus.ADDING_TO_BAG && <EmptyState />}
<ScrollingIndicator top show={userCanScroll && scrollProgress > 0} />
<Column ref={scrollRef} className={styles.assetsContainer} onScroll={scrollHandler} gap="12">
<Column display={priceChangedAssets.length > 0 || unavailableAssets.length > 0 ? 'flex' : 'none'}>
{unavailableAssets.length > 0 && (
<UnavailableAssetsHeaderRow
assets={unavailableAssets}
usdPrice={fetchedPriceData}
clearUnavailableAssets={() => setItemsInBag(availableItems)}
didOpenUnavailableAssets={didOpenUnavailableAssets}
setDidOpenUnavailableAssets={setDidOpenUnavailableAssets}
isMobile={isMobile}
{!(isProfilePage && profilePageState === ProfilePageStateType.LISTING) ? (
<>
<BagHeader
numberOfAssets={isProfilePage ? sellAssets.length : itemsInBag.length}
toggleBag={toggleBag}
resetFlow={isProfilePage ? resetSellAssets : reset}
isProfilePage={isProfilePage}
/>
{(!isProfilePage && itemsInBag.length === 0 && bagStatus === BagStatus.ADDING_TO_BAG) ||
(isProfilePage && sellAssets.length === 0 && <EmptyState />)}
<ScrollingIndicator top show={userCanScroll && scrollProgress > 0} />
<Column ref={scrollRef} className={styles.assetsContainer} onScroll={scrollHandler} gap="12">
{isProfilePage ? <ProfileBagContent /> : <BagContent />}
</Column>
<ScrollingIndicator show={userCanScroll && scrollProgress < 100} />
{hasAssetsToShow && !isProfilePage && (
<BagFooter
balance={balance}
sufficientBalance={sufficientBalance}
isConnected={isConnected}
totalEthPrice={totalEthPrice}
totalUsdPrice={totalUsdPrice}
bagStatus={bagStatus}
fetchAssets={fetchAssets}
assetsAreInReview={itemsInBag.some((item) => item.status === BagItemStatus.REVIEWING_PRICE_CHANGE)}
/>
)}
{priceChangedAssets.map((asset, index) => (
<PriceChangeBagRow
key={asset.id}
asset={asset}
usdPrice={fetchedPriceData}
markAssetAsReviewed={markAssetAsReviewed}
top={index === 0 && unavailableAssets.length === 0}
isMobile={isMobile}
/>
))}
</Column>
<Column gap="8">
{unchangedAssets.map((asset) => (
<BagRow
key={asset.id}
asset={asset}
usdPrice={fetchedPriceData}
removeAsset={removeAssetFromBag}
showRemove={true}
isMobile={isMobile}
/>
))}
</Column>
</Column>
<ScrollingIndicator show={userCanScroll && scrollProgress < 100} />
{hasAssetsToShow && (
<BagFooter
balance={balance}
sufficientBalance={sufficientBalance}
isConnected={isConnected}
totalEthPrice={totalEthPrice}
totalUsdPrice={totalUsdPrice}
bagStatus={bagStatus}
fetchAssets={fetchAssets}
assetsAreInReview={itemsInBag.some((item) => item.status === BagItemStatus.REVIEWING_PRICE_CHANGE)}
/>
{sellAssets.length !== 0 && isProfilePage && (
<Box
marginTop="32"
marginX="28"
paddingY="10"
className={`${buttonTextMedium} ${commonButtonStyles}`}
backgroundColor="accentAction"
textAlign="center"
onClick={() => {
isMobile && toggleBag()
setProfilePageState(ProfilePageStateType.LISTING)
}}
>
Continue
</Box>
)}
</>
) : (
<ListingModal />
)}
</Column>
{isOpen && <Overlay onClick={() => (!bagIsLocked ? setModalIsOpen(false) : undefined)} />}
......
import { BagRow, PriceChangeBagRow, UnavailableAssetsHeaderRow } from 'nft/components/bag/BagRow'
import { Column } from 'nft/components/Flex'
import { useBag, useIsMobile } from 'nft/hooks'
import { BagItemStatus, BagStatus } from 'nft/types'
import { recalculateBagUsingPooledAssets } from 'nft/utils/calcPoolPrice'
import { fetchPrice } from 'nft/utils/fetchPrice'
import { useEffect, useMemo } from 'react'
import { useQuery } from 'react-query'
export const BagContent = () => {
const bagStatus = useBag((s) => s.bagStatus)
const setBagStatus = useBag((s) => s.setBagStatus)
const markAssetAsReviewed = useBag((s) => s.markAssetAsReviewed)
const didOpenUnavailableAssets = useBag((s) => s.didOpenUnavailableAssets)
const setDidOpenUnavailableAssets = useBag((s) => s.setDidOpenUnavailableAssets)
const uncheckedItemsInBag = useBag((s) => s.itemsInBag)
const setItemsInBag = useBag((s) => s.setItemsInBag)
const removeAssetFromBag = useBag((s) => s.removeAssetFromBag)
const isMobile = useIsMobile()
const itemsInBag = useMemo(() => {
return recalculateBagUsingPooledAssets(uncheckedItemsInBag)
}, [uncheckedItemsInBag])
const { data: fetchedPriceData } = useQuery(['fetchPrice', {}], () => fetchPrice(), {})
const { unchangedAssets, priceChangedAssets, unavailableAssets, availableItems } = useMemo(() => {
const unchangedAssets = itemsInBag
.filter((item) => item.status === BagItemStatus.ADDED_TO_BAG || item.status === BagItemStatus.REVIEWED)
.map((item) => item.asset)
const priceChangedAssets = itemsInBag
.filter((item) => item.status === BagItemStatus.REVIEWING_PRICE_CHANGE)
.map((item) => item.asset)
const unavailableAssets = itemsInBag
.filter((item) => item.status === BagItemStatus.UNAVAILABLE)
.map((item) => item.asset)
const availableItems = itemsInBag.filter((item) => item.status !== BagItemStatus.UNAVAILABLE)
return { unchangedAssets, priceChangedAssets, unavailableAssets, availableItems }
}, [itemsInBag])
useEffect(() => {
const hasAssetsInReview = priceChangedAssets.length > 0
const hasAssets = itemsInBag.length > 0
if (bagStatus === BagStatus.IN_REVIEW && !hasAssetsInReview) {
if (hasAssets) setBagStatus(BagStatus.CONFIRM_REVIEW)
else setBagStatus(BagStatus.ADDING_TO_BAG)
}
}, [bagStatus, itemsInBag, priceChangedAssets, setBagStatus])
return (
<>
<Column display={priceChangedAssets.length > 0 || unavailableAssets.length > 0 ? 'flex' : 'none'}>
{unavailableAssets.length > 0 && (
<UnavailableAssetsHeaderRow
assets={unavailableAssets}
usdPrice={fetchedPriceData}
clearUnavailableAssets={() => setItemsInBag(availableItems)}
didOpenUnavailableAssets={didOpenUnavailableAssets}
setDidOpenUnavailableAssets={setDidOpenUnavailableAssets}
isMobile={isMobile}
/>
)}
{priceChangedAssets.map((asset, index) => (
<PriceChangeBagRow
key={asset.id}
asset={asset}
usdPrice={fetchedPriceData}
markAssetAsReviewed={markAssetAsReviewed}
top={index === 0 && unavailableAssets.length === 0}
isMobile={isMobile}
/>
))}
</Column>
<Column gap="8">
{unchangedAssets.map((asset) => (
<BagRow
key={asset.id}
asset={asset}
usdPrice={fetchedPriceData}
removeAsset={removeAssetFromBag}
showRemove={true}
isMobile={isMobile}
/>
))}
</Column>
</>
)
}
import { style } from '@vanilla-extract/css'
import { subhead } from 'nft/css/common.css'
import { sprinkles, vars } from 'nft/css/sprinkles.css'
export const header = style([
subhead,
sprinkles({
color: 'textPrimary',
justifyContent: 'space-between',
}),
{
lineHeight: '24px',
},
])
export const clearAll = style([
sprinkles({
color: 'textTertiary',
cursor: 'pointer',
fontWeight: 'semibold',
}),
{
':hover': {
color: vars.color.blue400,
},
},
])
import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex'
import { BagCloseIcon } from 'nft/components/icons'
import { roundAndPluralize } from 'nft/utils/roundAndPluralize'
import * as styles from './BagHeader.css'
interface BagHeaderProps {
numberOfAssets: number
toggleBag: () => void
resetFlow: () => void
isProfilePage: boolean
}
export const BagHeader = ({ numberOfAssets, toggleBag, resetFlow, isProfilePage }: BagHeaderProps) => {
return (
<Column gap="4" paddingX="32" marginBottom="20">
<Row className={styles.header}>
{isProfilePage ? 'Sell NFTs' : 'My bag'}
<Box display="flex" padding="2" color="textSecondary" cursor="pointer" onClick={toggleBag}>
<BagCloseIcon />
</Box>
</Row>
{numberOfAssets > 0 && (
<Box fontSize="14" fontWeight="normal" style={{ lineHeight: '20px' }} color="textPrimary">
{roundAndPluralize(numberOfAssets, 'NFT')} ·{' '}
<Box
as="span"
className={styles.clearAll}
onClick={() => {
resetFlow()
}}
>
Clear all
</Box>
</Box>
)}
</Column>
)
}
import { style } from '@vanilla-extract/css'
import { buttonTextSmall } from 'nft/css/common.css'
import { sprinkles } from 'nft/css/sprinkles.css'
export const tagAssetImage = style([
sprinkles({
borderRadius: '8',
height: '52',
width: '52',
marginRight: '12',
marginLeft: '8',
cursor: 'pointer',
}),
{
boxSizing: 'border-box',
},
])
export const tagAssetName = style([
sprinkles({
fontWeight: 'medium',
overflow: 'hidden',
marginRight: 'auto',
marginTop: '4',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}),
])
export const tagAssetCollectionName = style([
sprinkles({
fontWeight: 'normal',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
height: '56',
width: '56',
marginX: '12',
}),
{
maxWidth: '65%',
},
])
export const tagAssetRowBottom = style([
sprinkles({
width: 'full',
display: 'flex',
flexWrap: 'nowrap',
marginRight: '4',
}),
{
marginTop: '-10px',
},
])
export const removeAsset = style([
......@@ -56,8 +17,8 @@ export const removeAsset = style([
cursor: 'pointer',
}),
{
bottom: '-12px',
left: '22px',
bottom: '-4px',
left: '24px',
},
])
......@@ -67,16 +28,14 @@ export const removeIcon = style([
}),
])
export const tagAssetInfo = style([
export const removeBagRowButton = style([
buttonTextSmall,
sprinkles({
fontSize: '14',
background: 'backgroundInteractive',
color: 'textPrimary',
display: 'flex',
flexWrap: 'wrap',
width: 'full',
overflowX: 'hidden',
paddingX: '14',
paddingY: '12',
borderRadius: '12',
cursor: 'pointer',
}),
{
lineHeight: '17px',
},
])
import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex'
import { bodySmall, subhead } from 'nft/css/common.css'
import { useIsMobile, useSellAsset } from 'nft/hooks'
import { WalletAsset } from 'nft/types'
import { useState } from 'react'
import * as styles from './ProfileAssetRow.css'
const ProfileAssetRow = ({ asset }: { asset: WalletAsset }) => {
const removeAsset = useSellAsset((state) => state.removeSellAsset)
const isMobile = useIsMobile()
const [hovered, setHovered] = useState(false)
const handleHover = () => setHovered(!hovered)
return (
<Row paddingY="8" position="relative" onMouseEnter={handleHover} onMouseLeave={handleHover}>
<div>
<Box
display={isMobile ? 'flex' : 'none'}
className={styles.removeAsset}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
removeAsset(asset)
}}
>
<img className={styles.removeIcon} src={'/nft/svgs/minusCircle.svg'} alt="Remove item" />
</Box>
<img className={styles.tagAssetImage} src={asset.image_url} alt={asset.name} />
</div>
<Column gap="4" overflow="hidden" flexWrap="nowrap">
<Box overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap" className={subhead}>
{asset.name ?? `#${asset.tokenId}`}
</Box>
<Box overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap" className={bodySmall}>
{asset.collection?.name}
</Box>
</Column>
{hovered && !isMobile && (
<Box
marginLeft="auto"
marginRight="0"
className={styles.removeBagRowButton}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
removeAsset(asset)
}}
>
Remove
</Box>
)}
</Row>
)
}
export default ProfileAssetRow
import { Column } from 'nft/components/Flex'
import { useSellAsset } from 'nft/hooks'
import ProfileAssetRow from './ProfileAssetRow'
export const ProfileBagContent = () => {
const sellAssets = useSellAsset((state) => state.sellAssets)
return (
<Column>
{sellAssets.length ? sellAssets.map((asset, index) => <ProfileAssetRow asset={asset} key={index} />) : null}
</Column>
)
}
......@@ -1396,6 +1396,19 @@ export const LargeBagIcon = (props: SVGProps) => (
</svg>
)
export const LargeTagIcon = (props: SVGProps) => (
<svg width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M55.6215 80.6815L55.621 80.6819C55.0823 81.2212 54.4427 81.649 53.7386 81.9409C53.0345 82.2328 52.2797 82.383 51.5175 82.383C50.7553 82.383 50.0006 82.2328 49.2965 81.9409C48.5923 81.649 47.9527 81.2212 47.414 80.6819L47.413 80.681L17.7 51.0025V17.7H51.0029L80.714 47.411C80.7141 47.4111 80.7142 47.4112 80.7143 47.4113C81.7943 48.498 82.4006 49.9679 82.4006 51.5C82.4006 53.032 81.7944 54.5017 80.7146 55.5884C80.7144 55.5886 80.7142 55.5888 80.714 55.589L55.6215 80.6815Z"
stroke="currentColor"
strokeWidth="2.4"
strokeLinecap="round"
strokeLinejoin="round"
/>
<circle cx="34" cy="34" r="3" fill="currentColor" />
</svg>
)
export const CircularCloseIcon = (props: SVGProps) => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<circle cx="8" cy="8" r="8" fill="#293249" />
......
import clsx from 'clsx'
import ms from 'ms.macro'
import { ListingButton } from 'nft/components/bag/profile/ListingButton'
import { getListingState } from 'nft/components/bag/profile/utils'
import { Box } from 'nft/components/Box'
import { SortDropdown } from 'nft/components/common/SortDropdown'
import { Column, Row } from 'nft/components/Flex'
......@@ -41,8 +43,6 @@ import { ListingMarkets } from 'nft/utils/listNfts'
import { pluralize } from 'nft/utils/roundAndPluralize'
import { Dispatch, FormEvent, useEffect, useMemo, useRef, useState } from 'react'
import { ListingButton } from '../modal/ListingButton'
import { getListingState } from '../modal/utils'
import * as styles from './ListPage.css'
const SelectMarketplacesModal = ({
......
import { style } from '@vanilla-extract/css'
import { breakpoints, sprinkles } from 'nft/css/sprinkles.css'
export const tagContainer = style([
sprinkles({
borderRadius: '20',
borderWidth: '1px',
borderStyle: 'solid',
borderColor: 'backgroundOutline',
}),
{
'@media': {
[`screen and (max-width: ${breakpoints.md}px)`]: {
borderRadius: '0',
},
},
},
])
export const tagAssets = style([
sprinkles({
maxHeight: 'inherit',
}),
{
'@media': {
[`screen and (min-width: ${breakpoints.md}px)`]: {
maxHeight: '55vh',
},
},
'::-webkit-scrollbar': { display: 'none' },
scrollbarWidth: 'none',
},
])
export const closeIcon = style({
transform: 'rotate(90deg)',
float: 'right',
paddingTop: '1px',
})
export const orderButton = style([
sprinkles({
width: 'full',
paddingY: '12',
paddingX: '0',
}),
{
':hover': {
boxShadow: 'none',
},
},
])
import Loader from 'components/Loader'
import { Box } from 'nft/components/Box'
import { Column } from 'nft/components/Flex'
import { CloseDropDownIcon } from 'nft/components/icons'
import { bodySmall, buttonMedium, headlineSmall } from 'nft/css/common.css'
import { useBag, useIsMobile, useProfilePageState, useSellAsset } from 'nft/hooks'
import { ProfilePageStateType } from 'nft/types'
import { lazy, Suspense } from 'react'
import { useLocation } from 'react-router-dom'
import * as styles from './ListingTag.css'
const CartSellAssetRow = lazy(() => import('./TagAssetRow'))
const ListingModal = lazy(() => import('./ListingModal'))
const Cart = () => {
const { pathname } = useLocation()
const isProfilePage = pathname.startsWith('/profile')
const sellAssets = useSellAsset((state) => state.sellAssets)
const setSellPageState = useProfilePageState((state) => state.setProfilePageState)
const sellPageState = useProfilePageState((state) => state.state)
const toggleCart = useBag((state) => state.toggleBag)
const isMobile = useIsMobile()
const bagExpanded = useBag((s) => s.bagExpanded)
return (
<Box
zIndex={{ sm: '3', md: '2' }}
width={{ sm: 'full', md: 'auto' }}
height={{ sm: 'full', md: 'auto' }}
position="fixed"
left={{ sm: '0', md: 'unset' }}
right={{ sm: 'unset', md: '0' }}
top={{ sm: '0', md: 'unset' }}
display={bagExpanded && isProfilePage ? 'flex' : 'none'}
>
<Suspense fallback={<Loader />}>
<Column
marginTop={{ sm: '0', md: '4' }}
marginRight={{ sm: '0', md: '20' }}
className={styles.tagContainer}
width={{ sm: 'full', md: '288' }}
height={{ sm: 'full', md: 'auto' }}
backgroundColor="backgroundSurface"
marginLeft="0"
justifyContent="flex-start"
>
{sellPageState === ProfilePageStateType.LISTING ? (
<ListingModal />
) : (
<>
<BagHeader bagQuantity={sellAssets.length} />
<Column
overflowX="hidden"
overflowY="auto"
position="relative"
paddingTop="6"
paddingBottom="6"
height="full"
className={styles.tagAssets}
>
{sellAssets.length
? sellAssets.map((asset, index) => <CartSellAssetRow asset={asset} key={index} />)
: null}
</Column>
<Box padding="12">
<Box
as="button"
className={`${buttonMedium} ${styles.orderButton}`}
disabled={sellAssets.length === 0}
onClick={() => {
isMobile && toggleCart()
setSellPageState(ProfilePageStateType.LISTING)
}}
>
Continue
</Box>
</Box>
</>
)}
</Column>
</Suspense>
</Box>
)
}
const BagHeader = ({ bagQuantity }: { bagQuantity: number }) => {
const toggleCart = useBag((state) => state.toggleBag)
const resetSellAssets = useSellAsset((state) => state.reset)
const isMobile = useIsMobile()
return (
<Box position="relative" zIndex="2" paddingTop="20" paddingLeft="12" paddingRight="12">
{isMobile ? (
<Box
as="button"
border="none"
color="textSecondary"
background="black"
className={styles.closeIcon}
onClick={toggleCart}
>
<CloseDropDownIcon />
</Box>
) : null}
<Box className={headlineSmall} paddingTop="0" paddingBottom="8" paddingX="0" margin="0">
{'Selected items'}
</Box>
{bagQuantity > 0 ? (
<Box className={bodySmall} paddingTop="0" paddingBottom="8" paddingX="0" marginY="0" marginX="auto">
{bagQuantity} {bagQuantity === 1 ? 'NFT' : 'NFTs'}
<Box as="span" position="relative" paddingRight="2" paddingLeft="4" style={{ fontSize: '8px', top: '-2px' }}>
&#x2022;
</Box>
<Box as="span" color="blue400" onClick={resetSellAssets} cursor="pointer" paddingLeft="2">
Remove all
</Box>
</Box>
) : null}
</Box>
)
}
export default Cart
import { Box } from 'nft/components/Box'
import { bodySmall, subheadSmall } from 'nft/css/common.css'
import { useSellAsset } from 'nft/hooks'
import { WalletAsset } from 'nft/types'
import { useEffect, useRef, useState } from 'react'
import * as styles from './TagAssetRow.css'
const CartSellAssetRow = ({ asset }: { asset: WalletAsset }) => {
const removeAsset = useSellAsset((state) => state.removeSellAsset)
const [hovered, setHovered] = useState(false)
const handleHover = () => setHovered(!hovered)
const assetRowRef = useRef<HTMLDivElement>()
useEffect(() => {
if (hovered && assetRowRef.current && assetRowRef.current.matches(':hover') === false) setHovered(false)
}, [hovered])
return (
<Box display="flex" padding="4" marginBottom="4" borderRadius="8" position="relative">
<Box
onMouseEnter={handleHover}
onMouseLeave={handleHover}
onClick={() => {
removeAsset(asset)
}}
>
<Box visibility={hovered ? 'visible' : 'hidden'} className={styles.removeAsset}>
<img className={styles.removeIcon} src={'/nft/svgs/minusCircle.svg'} alt="Remove item" />
</Box>
<img className={styles.tagAssetImage} src={asset.image_url} alt={asset.name} />
</Box>
<Box className={styles.tagAssetInfo}>
<Box className={`${subheadSmall} ${styles.tagAssetName}`}>{asset.name || `#${asset.tokenId}`}</Box>
<Box className={styles.tagAssetRowBottom}>
<Box className={`${bodySmall} ${styles.tagAssetCollectionName}`}>{asset.collection?.name}</Box>
</Box>
</Box>
</Box>
)
}
export default CartSellAssetRow
......@@ -25,7 +25,7 @@ export const headlineLarge = sprinkles({ fontWeight: 'normal', fontSize: '36', l
export const headlineMedium = sprinkles({ fontWeight: 'normal', fontSize: '28', lineHeight: '36' })
export const headlineSmall = sprinkles({ fontWeight: 'normal', fontSize: '20', lineHeight: '28' })
export const subhead = sprinkles({ fontWeight: 'medium', fontSize: '16', lineHeight: '16' })
export const subhead = sprinkles({ fontWeight: 'medium', fontSize: '16', lineHeight: '24' })
export const subheadSmall = sprinkles({ fontWeight: 'medium', fontSize: '14', lineHeight: '14' })
export const body = sprinkles({ fontWeight: 'normal', fontSize: '16', lineHeight: '24' })
......@@ -37,7 +37,7 @@ export const buttonTextLarge = sprinkles({ fontWeight: 'semibold', fontSize: '20
export const buttonTextMedium = sprinkles({ fontWeight: 'semibold', fontSize: '16', lineHeight: '20' })
export const buttonTextSmall = sprinkles({ fontWeight: 'semibold', fontSize: '14', lineHeight: '16' })
const commonButtonStyles = style([
export const commonButtonStyles = style([
sprinkles({
borderRadius: '12',
transition: '250',
......
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