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

feat: add listing modal (#4663)

* add listing modal

* add new files

* remove useeffect

* re-add useeffect and includes array

* position relative

* add listing datatype

* use pluralize

* readable const

* clsx

* parseFloat 0 default

* don't use any

* cant use months for ms
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>
parent 3d4b077b
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import ms from 'ms.macro'
import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex'
import { ArrowRightIcon, HazardIcon, LoadingIcon, XMarkIcon } from 'nft/components/icons'
import { bodySmall } from 'nft/css/common.css'
import { useNFTList, useSellAsset } from 'nft/hooks'
import { Listing, ListingStatus, WalletAsset } from 'nft/types'
import { pluralize } from 'nft/utils/roundAndPluralize'
import { useEffect, useMemo, useRef, useState } from 'react'
import * as styles from './ListingModal.css'
import { getListings } from './utils'
interface ListingButtonProps {
onClick: () => void
buttonText: string
showWarningOverride?: boolean
}
export const ListingButton = ({ onClick, buttonText, showWarningOverride = false }: ListingButtonProps) => {
const sellAssets = useSellAsset((state) => state.sellAssets)
const addMarketplaceWarning = useSellAsset((state) => state.addMarketplaceWarning)
const removeAllMarketplaceWarnings = useSellAsset((state) => state.removeAllMarketplaceWarnings)
const listingStatus = useNFTList((state) => state.listingStatus)
const setListingStatus = useNFTList((state) => state.setListingStatus)
const setListings = useNFTList((state) => state.setListings)
const setCollectionsRequiringApproval = useNFTList((state) => state.setCollectionsRequiringApproval)
const [showWarning, setShowWarning] = useState(false)
const [canContinue, setCanContinue] = useState(false)
const warningRef = useRef<HTMLDivElement>(null)
useOnClickOutside(warningRef, () => {
setShowWarning(false)
})
useEffect(() => {
const [newCollectionsToApprove, newListings] = getListings(sellAssets)
setListings(newListings)
setCollectionsRequiringApproval(newCollectionsToApprove)
setListingStatus(ListingStatus.DEFINED)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sellAssets])
const [
noMarketplacesSelected,
missingExpiration,
invalidExpiration,
overMaxExpiration,
listingsMissingPrice,
listingsBelowFloor,
listingsAboveSellOrderFloor,
invalidPrices,
] = useMemo(() => {
const noMarketplacesSelected = sellAssets.some(
(asset: WalletAsset) => asset.marketplaces === undefined || asset.marketplaces.length === 0
)
const missingExpiration = sellAssets.some((asset) => {
return asset.expirationTime != null && asset.expirationTime - Date.now() < ms`60 seconds`
})
const invalidExpiration = sellAssets.some((asset) => {
return asset.expirationTime != null && isNaN(asset.expirationTime)
})
const overMaxExpiration = sellAssets.some((asset) => {
return asset.expirationTime != null && asset.expirationTime - Date.now() > ms`180 days`
})
const listingsMissingPrice: [WalletAsset, Listing][] = []
const listingsBelowFloor: [WalletAsset, Listing][] = []
const listingsAboveSellOrderFloor: [WalletAsset, Listing][] = []
const invalidPrices: [WalletAsset, Listing][] = []
for (const asset of sellAssets) {
if (asset.newListings) {
for (const listing of asset.newListings) {
if (!listing.price) listingsMissingPrice.push([asset, listing])
else if (isNaN(parseFloat(listing.price)) || parseFloat(listing.price) < 0)
invalidPrices.push([asset, listing])
else if (parseFloat(listing.price) < asset.floorPrice && !listing.overrideFloorPrice)
listingsBelowFloor.push([asset, listing])
else if (asset.floor_sell_order_price && parseFloat(listing.price) > asset.floor_sell_order_price)
listingsAboveSellOrderFloor.push([asset, listing])
}
}
}
const continueCheck = listingsBelowFloor.length === 0 && listingsAboveSellOrderFloor.length === 0
setCanContinue(continueCheck)
return [
noMarketplacesSelected,
missingExpiration,
invalidExpiration,
overMaxExpiration,
listingsMissingPrice,
listingsBelowFloor,
listingsAboveSellOrderFloor,
invalidPrices,
]
}, [sellAssets])
const [disableListButton, warningMessage] = useMemo(() => {
const disableListButton =
noMarketplacesSelected ||
missingExpiration ||
invalidExpiration ||
overMaxExpiration ||
invalidPrices.length > 0 ||
listingsMissingPrice.length > 0
const warningMessage = noMarketplacesSelected
? 'No marketplaces selected'
: missingExpiration
? 'Set duration'
: invalidExpiration
? 'Invalid duration'
: overMaxExpiration
? 'Max duration is 6 months'
: listingsMissingPrice.length > 0
? `${listingsMissingPrice.length} item price${pluralize(listingsMissingPrice.length)} not set`
: invalidPrices.length > 0
? `${invalidPrices.length} price${pluralize(invalidPrices.length)} are invalid`
: listingsBelowFloor.length > 0
? `${listingsBelowFloor.length} item${pluralize(listingsBelowFloor.length)} listed below floor`
: listingsAboveSellOrderFloor.length > 0
? `${listingsAboveSellOrderFloor.length} item${pluralize(listingsAboveSellOrderFloor.length)} already listed`
: ''
return [disableListButton, warningMessage]
}, [
noMarketplacesSelected,
missingExpiration,
invalidExpiration,
overMaxExpiration,
listingsMissingPrice,
invalidPrices,
listingsBelowFloor,
listingsAboveSellOrderFloor,
])
useEffect(() => {
setShowWarning(false)
}, [warningMessage])
const addWarningMessages = () => {
removeAllMarketplaceWarnings()
if (!missingExpiration && !noMarketplacesSelected) {
if (listingsMissingPrice.length > 0) {
for (const [asset, listing] of listingsMissingPrice) {
addMarketplaceWarning(asset, {
message: 'PLEASE SET A PRICE',
marketplace: listing.marketplace,
})
}
} else if (invalidPrices.length > 0) {
for (const [asset, listing] of invalidPrices) {
!listing.overrideFloorPrice &&
addMarketplaceWarning(asset, {
message: `INVALID PRICE`,
marketplace: listing.marketplace,
})
}
}
}
setShowWarning(true)
}
const warningWrappedClick = () => {
if ((!disableListButton && canContinue) || showWarningOverride) onClick()
else addWarningMessages()
}
return (
<Box position="relative" width="full">
{!showWarningOverride && showWarning && warningMessage.length > 0 && (
<Row
className={`${bodySmall} ${styles.warningTooltip}`}
transition="250"
onClick={() => setShowWarning(false)}
color="darkGray"
zIndex="3"
borderRadius="4"
backgroundColor="white"
height={!disableListButton ? '64' : '36'}
maxWidth="276"
position="absolute"
left="24"
bottom="52"
flexWrap={!disableListButton ? 'wrap' : 'nowrap'}
style={{ maxWidth: !disableListButton ? '225px' : '' }}
ref={warningRef}
>
<HazardIcon />
<Box marginLeft="4" marginRight="8">
{warningMessage}
</Box>
{!!disableListButton ? (
<Box paddingTop="6">
<XMarkIcon fill="darkGray" height="20" width="20" />
</Box>
) : (
<Row
marginLeft="72"
cursor="pointer"
color="genieBlue"
onClick={() => {
setShowWarning(false)
setCanContinue(true)
onClick()
}}
>
Continue
<ArrowRightIcon height="20" width="20" />
</Row>
)}
</Row>
)}
<Box
as="button"
border="none"
backgroundColor="genieBlue"
cursor={
[ListingStatus.APPROVED, ListingStatus.PENDING, ListingStatus.SIGNING].includes(listingStatus) ||
disableListButton
? 'default'
: 'pointer'
}
color="explicitWhite"
className={styles.button}
onClick={() => listingStatus !== ListingStatus.APPROVED && warningWrappedClick()}
type="button"
style={{
opacity:
![ListingStatus.DEFINED, ListingStatus.FAILED, ListingStatus.CONTINUE].includes(listingStatus) ||
disableListButton
? 0.3
: 1,
}}
>
{listingStatus === ListingStatus.SIGNING || listingStatus === ListingStatus.PENDING ? (
<Row gap="8">
<LoadingIcon stroke="white" height="20" width="20" />
{listingStatus === ListingStatus.PENDING ? 'Pending' : 'Proceed in wallet'}
</Row>
) : listingStatus === ListingStatus.APPROVED ? (
'Complete!'
) : listingStatus === ListingStatus.PAUSED ? (
'Paused'
) : listingStatus === ListingStatus.FAILED ? (
'Try again'
) : listingStatus === ListingStatus.CONTINUE ? (
'Continue'
) : (
buttonText
)}
</Box>
</Box>
)
}
import { style } from '@vanilla-extract/css'
import { sprinkles } from 'nft/css/sprinkles.css'
export const chevron = style([
sprinkles({
height: '28',
width: '28',
transition: '250',
marginLeft: 'auto',
marginRight: '0',
}),
])
export const chevronDown = style({
transform: 'rotate(180deg)',
})
export const sectionDivider = style([
sprinkles({
borderRadius: '20',
marginTop: '8',
width: 'full',
borderWidth: '0.5px',
borderStyle: 'solid',
borderColor: 'medGray',
}),
])
export const button = style([
sprinkles({
height: '40',
width: 'full',
textAlign: 'center',
fontWeight: 'medium',
fontSize: '14',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'flex-end',
borderRadius: '12',
}),
{
lineHeight: '18px',
},
])
export const listingModalIcon = style([
sprinkles({
borderWidth: '1px',
borderStyle: 'solid',
borderColor: 'white',
}),
{
boxSizing: 'border-box',
marginLeft: '-2px',
},
])
export const warningTooltip = style([
sprinkles({
paddingTop: '8',
paddingRight: '8',
paddingBottom: '8',
paddingLeft: '12',
}),
{
boxShadow: '0px 4px 16px rgba(10, 10, 59, 0.2)',
},
])
export const listingSectionBorder = style([
sprinkles({
padding: '8',
borderRadius: '8',
borderColor: 'medGray',
borderStyle: 'solid',
borderWidth: '1px',
}),
])
import { addressesByNetwork, SupportedChainId } from '@looksrare/sdk'
import { useWeb3React } from '@web3-react/core'
import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex'
import { ChevronLeftIcon, XMarkIcon } from 'nft/components/icons'
import { caption, headlineSmall, subhead, subheadSmall } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css'
import { useBag, useIsMobile, useNFTList, useSellAsset } from 'nft/hooks'
import { logListing, looksRareNonceFetcher } from 'nft/queries'
import { AssetRow, CollectionRow, ListingRow, ListingStatus } from 'nft/types'
import { pluralize } from 'nft/utils/roundAndPluralize'
import { Dispatch, useEffect, useMemo, useRef, useState } from 'react'
import { ListingButton } from './ListingButton'
import * as styles from './ListingModal.css'
import { ListingSection } from './ListingSection'
import { approveCollectionRow, getTotalEthValue, pauseRow, resetRow, signListingRow, verifyStatus } from './utils'
const ListingModal = () => {
const { provider } = useWeb3React()
const sellAssets = useSellAsset((state) => state.sellAssets)
const signer = provider?.getSigner()
const listings = useNFTList((state) => state.listings)
const setListings = useNFTList((state) => state.setListings)
const collectionsRequiringApproval = useNFTList((state) => state.collectionsRequiringApproval)
const setCollectionsRequiringApproval = useNFTList((state) => state.setCollectionsRequiringApproval)
const [openIndex, setOpenIndex] = useState(0)
const listingStatus = useNFTList((state) => state.listingStatus)
const setListingStatus = useNFTList((state) => state.setListingStatus)
const [allCollectionsApproved, setAllCollectionsApproved] = useState(false)
const looksRareNonce = useNFTList((state) => state.looksRareNonce)
const setLooksRareNonce = useNFTList((state) => state.setLooksRareNonce)
const getLooksRareNonce = useNFTList((state) => state.getLooksRareNonce)
const toggleCart = useBag((state) => state.toggleBag)
const looksRareNonceRef = useRef(looksRareNonce)
const isMobile = useIsMobile()
useEffect(() => {
useNFTList.subscribe((state) => (looksRareNonceRef.current = state.looksRareNonce))
}, [])
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
// when all collections have been approved, auto start the signing process
useEffect(() => {
collectionsRequiringApproval?.length &&
setAllCollectionsApproved(
collectionsRequiringApproval.every((collection: CollectionRow) => collection.status === ListingStatus.APPROVED)
)
if (
allCollectionsApproved &&
(listingStatus === ListingStatus.PENDING || listingStatus === ListingStatus.CONTINUE)
) {
signListings()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [collectionsRequiringApproval, allCollectionsApproved])
// handles the modal wide listing state based on conglomeration of the wallet, collection, and listing states
const startListingFlow = async () => {
if (!signer) return
setListingStatus(ListingStatus.SIGNING)
const addresses = addressesByNetwork[SupportedChainId.MAINNET]
const signerAddress = await signer.getAddress()
const nonce = await looksRareNonceFetcher(signerAddress)
setLooksRareNonce(nonce ?? 0)
if (!collectionsRequiringApproval?.some((collection) => collection.status === ListingStatus.PAUSED)) {
setListingStatus(ListingStatus.SIGNING)
setOpenIndex(1)
}
const looksRareAddress = addresses.TRANSFER_MANAGER_ERC721
// for all unqiue collection, marketplace combos -> approve collections
for (const collectionRow of collectionsRequiringApproval) {
verifyStatus(collectionRow.status) &&
approveCollectionRow(
collectionRow,
collectionsRequiringApproval,
setCollectionsRequiringApproval,
signer,
looksRareAddress,
pauseAllRows
)
}
}
const signListings = async () => {
if (!signer || !provider) return
setListingStatus(ListingStatus.SIGNING)
setOpenIndex(2)
// sign listings
for (const listing of listings) {
verifyStatus(listing.status) &&
(await signListingRow(
listing,
listings,
setListings,
signer,
provider,
getLooksRareNonce,
setLooksRareNonce,
pauseAllRows
))
}
const allListingsSigned = listings.every((listing: ListingRow) => listing.status === ListingStatus.APPROVED)
const paused = listings.some((listing: ListingRow) => listing.status === ListingStatus.PAUSED)
if (allListingsSigned) {
setOpenIndex(0)
setListingStatus(ListingStatus.APPROVED)
} else if (!paused) {
setListingStatus(ListingStatus.FAILED)
}
await logListing(listings, (await signer?.getAddress()) ?? '')
}
const pauseAllRows = () => {
for (const collection of collectionsRequiringApproval) {
pauseRow(collection, collectionsRequiringApproval, setCollectionsRequiringApproval as Dispatch<AssetRow[]>)
}
for (const listing of listings) {
pauseRow(listing, listings, setListings as Dispatch<AssetRow[]>)
}
}
const resetAllRows = () => {
for (const collection of collectionsRequiringApproval) {
resetRow(collection, collectionsRequiringApproval, setCollectionsRequiringApproval as Dispatch<AssetRow[]>)
}
for (const listing of listings) {
resetRow(listing, listings, setListings as Dispatch<AssetRow[]>)
}
}
const clickStopListing = () => {
pauseAllRows()
}
const clickStartListingFlow = () => {
resetAllRows()
allCollectionsApproved ? signListings() : startListingFlow()
}
const showSuccessScreen = useMemo(() => listingStatus === ListingStatus.APPROVED, [listingStatus])
return (
<Column paddingTop="20" paddingBottom="20" paddingLeft="12" paddingRight="12">
<Row className={headlineSmall} marginBottom="10">
{isMobile && !showSuccessScreen && (
<Box paddingTop="4" marginRight="4" onClick={toggleCart}>
<ChevronLeftIcon height={28} width={28} />
</Box>
)}
{showSuccessScreen ? 'Success!' : `Listing ${sellAssets.length} NFTs`}
<Box
as="button"
border="none"
color="darkGray"
backgroundColor="white"
marginLeft="auto"
marginRight="0"
paddingRight="0"
display={{ sm: 'flex', md: 'none' }}
cursor="pointer"
onClick={toggleCart}
>
<XMarkIcon height={28} width={28} fill={themeVars.colors.blackBlue} />
</Box>
</Row>
<Column overflowX="hidden" overflowY="auto" style={{ maxHeight: '60vh' }}>
{showSuccessScreen ? (
<ListingSection
sectionTitle={`Listed ${listings.length} item${pluralize(listings.length)} for sale`}
rows={listings}
index={0}
openIndex={openIndex}
isSuccessScreen={true}
/>
) : (
<>
<ListingSection
sectionTitle={`Approve ${collectionsRequiringApproval.length} collection${pluralize(
collectionsRequiringApproval.length
)}`}
title="COLLECTIONS"
rows={collectionsRequiringApproval}
index={1}
openIndex={openIndex}
/>
<ListingSection
sectionTitle={`Confirm ${listings.length} listing${pluralize(listings.length)}`}
caption="Now you can sign to list each item"
title="NFTS"
rows={listings}
index={2}
openIndex={openIndex}
/>
</>
)}
</Column>
<hr className={styles.sectionDivider} />
<Row className={subhead} marginTop="12" marginBottom={showSuccessScreen ? '8' : '20'}>
Return if sold
<Row className={subheadSmall} marginLeft="auto" marginRight="0">
{totalEthListingValue}
&nbsp;ETH
</Row>
</Row>
{showSuccessScreen ? (
<Box as="span" className={caption} color="darkGray">
Status:{' '}
<Box as="span" color="green200">
Confirmed
</Box>
</Box>
) : (
<ListingButton onClick={clickStartListingFlow} buttonText={'Start listing'} showWarningOverride={isMobile} />
)}
{(listingStatus === ListingStatus.PENDING || listingStatus === ListingStatus.SIGNING) && (
<Box
as="button"
border="none"
backgroundColor="white"
cursor="pointer"
color="orange"
className={styles.button}
onClick={clickStopListing}
type="button"
>
Stop listing
</Box>
)}
</Column>
)
}
export default ListingModal
import clsx from 'clsx'
import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex'
import { ApprovedCheckmarkIcon, ChevronUpIcon, FailedListingIcon, LoadingIcon } from 'nft/components/icons'
import { badge, bodySmall, buttonTextSmall, subhead } from 'nft/css/common.css'
import { useSellAsset } from 'nft/hooks'
import { AssetRow, CollectionRow, ListingRow, ListingStatus } from 'nft/types'
import { formatEthPrice, numberToWei } from 'nft/utils/currency'
import { useEffect, useState } from 'react'
import * as styles from './ListingModal.css'
export const ListingSection = ({
sectionTitle,
caption = undefined,
title = undefined,
rows,
index,
openIndex,
isSuccessScreen = false,
}: {
sectionTitle: string
caption?: string
title?: string
rows: AssetRow[]
index: number
openIndex: number
isSuccessScreen?: boolean
}) => {
const [isOpen, setIsOpen] = useState(false)
const notAllApproved = rows.some((row: AssetRow) => row.status !== ListingStatus.APPROVED)
const sellAssets = useSellAsset((state) => state.sellAssets)
const removeAssetMarketplace = useSellAsset((state) => state.removeAssetMarketplace)
const removeRow = (row: any) => {
// collections
if (index === 1) {
for (const asset of sellAssets)
if (asset.asset_contract.address === row.collectionAddress) removeAssetMarketplace(asset, row.marketplace)
}
// listings
else removeAssetMarketplace(row.asset, row.marketplace)
}
useEffect(() => {
setIsOpen(index === openIndex)
}, [index, openIndex])
function getListingRowPrice(row: AssetRow): number | undefined {
const listingRow = row as ListingRow
const newListings = listingRow.asset.newListings
return parseFloat(
newListings?.find((listing) => listing.marketplace.name === listingRow.marketplace.name)?.price ?? '0'
)
}
const allApproved = !notAllApproved && rows.length > 0 && !isSuccessScreen
return (
<Row
flexWrap="wrap"
className={subhead}
marginTop="10"
marginBottom="10"
onClick={() => rows.length > 0 && setIsOpen(!isOpen)}
color={allApproved ? 'green' : 'blackBlue'}
>
{allApproved && <ApprovedCheckmarkIcon style={{ marginRight: '8px' }} />}
{sectionTitle}
{!isSuccessScreen && <ChevronUpIcon className={clsx(`${isOpen ? '' : styles.chevronDown} ${styles.chevron}`)} />}
{(isOpen || isSuccessScreen) && (
<Column
gap="12"
width="full"
paddingTop={isSuccessScreen ? '28' : 'auto'}
className={clsx(!isSuccessScreen && styles.listingSectionBorder)}
>
{caption && (
<Box color="blackBlue" fontWeight="normal" className={caption}>
{caption}
</Box>
)}
{title && (
<Box color="darkGray" className={badge}>
{title}
</Box>
)}
<Column gap="8">
{rows.map((row: AssetRow, index) => {
return (
<Column key={index} gap="8">
<Row>
{row.images.map((image, index) => {
return (
<Box
as="img"
height="20"
width="20"
borderRadius={index === 0 && (row as CollectionRow).collectionAddress ? 'round' : '4'}
style={{ zIndex: 2 - index }}
className={styles.listingModalIcon}
src={image}
alt={row.name}
key={index}
/>
)
})}
<Box
marginLeft="8"
marginRight="auto"
fontWeight="normal"
color="blackBlue"
textOverflow="ellipsis"
overflow="hidden"
maxWidth={{
sm: 'max',
md:
row.status === ListingStatus.REJECTED || row.status === ListingStatus.FAILED ? '120' : 'full',
}}
className={bodySmall}
>
{row.name}
</Box>
{isSuccessScreen ? (
getListingRowPrice(row) &&
`${formatEthPrice(numberToWei(getListingRowPrice(row) ?? 0).toString())} ETH`
) : row.status === ListingStatus.APPROVED ? (
<ApprovedCheckmarkIcon height="20" width="20" />
) : row.status === ListingStatus.FAILED || row.status === ListingStatus.REJECTED ? (
<Row gap="4">
<Box fontWeight="normal" fontSize="14" color="darkGray">
{row.status}
</Box>
<FailedListingIcon />
</Row>
) : (
row.status === ListingStatus.SIGNING && <LoadingIcon height="20" width="20" stroke="#4673FA" />
)}
</Row>
{(row.status === ListingStatus.FAILED || row.status === ListingStatus.REJECTED) && (
<Row gap="8" justifyContent="center">
<Box
width="120"
as="button"
className={buttonTextSmall}
borderRadius="12"
border="none"
color="red400"
height="32"
cursor="pointer"
style={{ backgroundColor: '#FA2B391A' }}
onClick={async (e) => {
e.stopPropagation()
removeRow(row)
}}
>
Remove
</Box>
<Box
width="120"
as="button"
className={buttonTextSmall}
borderRadius="12"
border="none"
color="genieBlue"
height="32"
cursor="pointer"
style={{ backgroundColor: '#4C82FB29' }}
onClick={async (e) => {
e.stopPropagation()
if (row.callback) {
await row.callback()
}
}}
>
Try again
</Box>
</Row>
)}
</Column>
)
})}
</Column>
</Column>
)}
</Row>
)
}
...@@ -11,6 +11,7 @@ import { useLocation } from 'react-router-dom' ...@@ -11,6 +11,7 @@ import { useLocation } from 'react-router-dom'
import * as styles from './ListingTag.css' import * as styles from './ListingTag.css'
const CartSellAssetRow = lazy(() => import('./TagAssetRow')) const CartSellAssetRow = lazy(() => import('./TagAssetRow'))
const ListingModal = lazy(() => import('./ListingModal'))
const Cart = () => { const Cart = () => {
const { pathname } = useLocation() const { pathname } = useLocation()
...@@ -44,7 +45,9 @@ const Cart = () => { ...@@ -44,7 +45,9 @@ const Cart = () => {
marginLeft="0" marginLeft="0"
justifyContent="flex-start" justifyContent="flex-start"
> >
{sellPageState === SellPageStateType.LISTING ? null : ( {sellPageState === SellPageStateType.LISTING ? (
<ListingModal />
) : (
<> <>
<BagHeader bagQuantity={sellAssets.length} /> <BagHeader bagQuantity={sellAssets.length} />
<Column <Column
......
This diff is collapsed.
...@@ -249,7 +249,7 @@ const flexAlignment = [ ...@@ -249,7 +249,7 @@ const flexAlignment = [
const overflow = ['hidden', 'inherit', 'scroll', 'visible', 'auto'] as const const overflow = ['hidden', 'inherit', 'scroll', 'visible', 'auto'] as const
const borderWidth = ['0px', '1px', '1.5px', '2px', '4px'] const borderWidth = ['0px', '0.5px', '1px', '1.5px', '2px', '4px']
const borderStyle = ['none', 'solid'] as const const borderStyle = ['none', 'solid'] as const
......
export * from './useBag' export * from './useBag'
export * from './useCollectionFilters' export * from './useCollectionFilters'
export * from './useFiltersExpanded' export * from './useFiltersExpanded'
export * from './useGenieList'
export * from './useIsMobile' export * from './useIsMobile'
export * from './useIsTablet' export * from './useIsTablet'
export * from './useMarketplaceSelect' export * from './useMarketplaceSelect'
export * from './useNFTList'
export * from './useNFTSelect' export * from './useNFTSelect'
export * from './useSearchHistory' export * from './useSearchHistory'
export * from './useSelectAsset' export * from './useSelectAsset'
......
import { AssetRow, ListingMarket } from '../../types' import { ListingMarket, ListingRow } from 'nft/types'
interface Listing extends AssetRow { interface Listing extends ListingRow {
marketplaces: ListingMarket[] marketplaces: ListingMarket[]
} }
export const logListing = async (listings: AssetRow[], userAddress: string): Promise<boolean> => { export const logListing = async (listings: ListingRow[], userAddress: string): Promise<boolean> => {
const url = `${process.env.REACT_APP_GENIE_API_URL}/logGenieList` const url = `${process.env.REACT_APP_GENIE_API_URL}/logGenieList`
const listingsConsolidated: Listing[] = listings.map((el) => ({ ...el, marketplaces: [] })) const listingsConsolidated: Listing[] = listings.map((el) => ({ ...el, marketplaces: [] }))
const marketplacesById: Record<string, ListingMarket[]> = {} const marketplacesById: Record<string, ListingMarket[]> = {}
......
...@@ -28,6 +28,12 @@ export interface SellOrder { ...@@ -28,6 +28,12 @@ export interface SellOrder {
tokenReserves?: number tokenReserves?: number
} }
export interface Listing {
price?: string
marketplace: ListingMarket
overrideFloorPrice?: boolean
}
export interface WalletAsset { export interface WalletAsset {
id?: string id?: string
image_url: string image_url: string
...@@ -60,11 +66,7 @@ export interface WalletAsset { ...@@ -60,11 +66,7 @@ export interface WalletAsset {
// Used for creating new listings // Used for creating new listings
expirationTime?: number expirationTime?: number
marketAgnosticPrice?: string marketAgnosticPrice?: string
newListings?: { newListings?: Listing[]
price?: string
marketplace: ListingMarket
overrideFloorPrice?: boolean
}[]
marketplaces?: ListingMarket[] marketplaces?: ListingMarket[]
listingWarnings?: ListingWarning[] listingWarnings?: ListingWarning[]
} }
...@@ -88,19 +90,19 @@ export enum ListingStatus { ...@@ -88,19 +90,19 @@ export enum ListingStatus {
SIGNING = 'Signing', SIGNING = 'Signing',
} }
export interface ListingRow { export interface AssetRow {
images: string[] images: string[]
name: string name: string
status: ListingStatus status: ListingStatus
callback?: () => Promise<void> callback?: () => Promise<void>
} }
export interface AssetRow extends ListingRow { export interface ListingRow extends AssetRow {
asset: WalletAsset asset: WalletAsset
marketplace: ListingMarket marketplace: ListingMarket
} }
export interface CollectionRow extends ListingRow { export interface CollectionRow extends AssetRow {
collectionAddress: string collectionAddress: string
marketplace: ListingMarket marketplace: ListingMarket
} }
......
...@@ -3,3 +3,5 @@ export const roundAndPluralize = (i: number, word: string) => { ...@@ -3,3 +3,5 @@ export const roundAndPluralize = (i: number, word: string) => {
return `${rounded} ${word}${rounded === 1 ? '' : 's'}` return `${rounded} ${word}${rounded === 1 ? '' : 's'}`
} }
export const pluralize = (number: number) => (number !== 1 ? 's' : '')
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