Commit 6131e6bf authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

chore: Cleanup and refactor significant portion of listing logic (#6042)

* NFT-91 move listing mode to shared

* move expiration setting out of useEffect

* simplify price input color logic

* unused boolean

* simplify removal of local listing market

* handle global same pricing

* undo local market change

* added comment

* undo expiration changes

* remove old listing state logic

* formatting

* small cleanup

* cleanup

* deprecate global listing state

* use stablecoin values

* remove unused pausing functionality

* remove unused pausing functionality

* remove unused warning logic

* remove unused royalty field

* use royalty helper fn

* simplify global vs normal price input

* simplified price setting logic

* price inputs need to respond to global price method changes

* slight simplifcations

* move dynamic price logic to hook

* move utils file

* move enum to shared

* only usdc check

---------
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>
parent 59796359
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics' import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics'
import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events' import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events'
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import Column from 'components/Column' import Column from 'components/Column'
import Row from 'components/Row' import Row from 'components/Row'
import { approveCollectionRow, getListingState, getTotalEthValue, verifyStatus } from 'nft/components/bag/profile/utils' import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { ListingButton } from 'nft/components/profile/list/ListingButton' import { ListingButton } from 'nft/components/profile/list/ListingButton'
import {
approveCollectionRow,
getTotalEthValue,
useSubscribeListingState,
verifyStatus,
} from 'nft/components/profile/list/utils'
import { useIsMobile, useNFTList, useProfilePageState, useSellAsset } from 'nft/hooks' import { useIsMobile, useNFTList, useProfilePageState, useSellAsset } from 'nft/hooks'
import { LIST_PAGE_MARGIN, LIST_PAGE_MARGIN_MOBILE } from 'nft/pages/profile/shared' import { LIST_PAGE_MARGIN, LIST_PAGE_MARGIN_MOBILE } from 'nft/pages/profile/shared'
import { looksRareNonceFetcher } from 'nft/queries' import { looksRareNonceFetcher } from 'nft/queries'
import { ListingStatus, ProfilePageStateType } from 'nft/types' import { ProfilePageStateType } from 'nft/types'
import { fetchPrice, formatEth, formatUsdPrice } from 'nft/utils' import { formatEth } from 'nft/utils'
import { ListingMarkets } from 'nft/utils/listNfts' import { ListingMarkets } from 'nft/utils/listNfts'
import { useEffect, useMemo, useReducer, useState } from 'react' import { useEffect, useMemo, useReducer, useState } from 'react'
import { ArrowLeft } from 'react-feather' import { ArrowLeft } from 'react-feather'
...@@ -185,26 +194,10 @@ export const ListPage = () => { ...@@ -185,26 +194,10 @@ export const ListPage = () => {
}), }),
shallow shallow
) )
const { const { listings, collectionsRequiringApproval, setLooksRareNonce, setCollectionStatusAndCallback } = useNFTList(
({ listings, collectionsRequiringApproval, setLooksRareNonce, setCollectionStatusAndCallback }) => ({
listings, listings,
collectionsRequiringApproval, collectionsRequiringApproval,
listingStatus,
setListingStatus,
setLooksRareNonce,
setCollectionStatusAndCallback,
} = useNFTList(
({
listings,
collectionsRequiringApproval,
listingStatus,
setListingStatus,
setLooksRareNonce,
setCollectionStatusAndCallback,
}) => ({
listings,
collectionsRequiringApproval,
listingStatus,
setListingStatus,
setLooksRareNonce, setLooksRareNonce,
setCollectionStatusAndCallback, setCollectionStatusAndCallback,
}), }),
...@@ -212,31 +205,16 @@ export const ListPage = () => { ...@@ -212,31 +205,16 @@ export const ListPage = () => {
) )
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets]) const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
const nativeCurrency = useNativeCurrency()
const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency)
const usdcValue = useStablecoinValue(parsedAmount)
const usdcAmount = formatCurrencyAmount(usdcValue, NumberType.FiatTokenPrice)
const [showListModal, toggleShowListModal] = useReducer((s) => !s, false) const [showListModal, toggleShowListModal] = useReducer((s) => !s, false)
const [selectedMarkets, setSelectedMarkets] = useState([ListingMarkets[0]]) // default marketplace: x2y2 const [selectedMarkets, setSelectedMarkets] = useState([ListingMarkets[0]]) // default marketplace: x2y2
const [ethPriceInUSD, setEthPriceInUSD] = useState(0)
const signer = provider?.getSigner() const signer = provider?.getSigner()
useEffect(() => { // instantiate listings and collections to approve when users modify input data
fetchPrice().then((price) => { useSubscribeListingState()
setEthPriceInUSD(price ?? 0)
})
}, [])
// TODO with removal of list v1 see if this logic can be removed
useEffect(() => {
const state = getListingState(collectionsRequiringApproval, listings)
if (state.allListingsApproved) setListingStatus(ListingStatus.APPROVED)
else if (state.anyPaused && !state.anyActiveFailures && !state.anyActiveSigning && !state.anyActiveRejections) {
setListingStatus(ListingStatus.CONTINUE)
} else if (state.anyPaused) setListingStatus(ListingStatus.PAUSED)
else if (state.anyActiveSigning) setListingStatus(ListingStatus.SIGNING)
else if (state.allListingsPending || (state.allCollectionsPending && state.allListingsDefined))
setListingStatus(ListingStatus.PENDING)
else if (state.anyActiveFailures && listingStatus !== ListingStatus.PAUSED) setListingStatus(ListingStatus.FAILED)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [listings, collectionsRequiringApproval])
useEffect(() => { useEffect(() => {
setGlobalMarketplaces(selectedMarkets) setGlobalMarketplaces(selectedMarkets)
...@@ -247,14 +225,13 @@ export const ListPage = () => { ...@@ -247,14 +225,13 @@ export const ListPage = () => {
token_ids: sellAssets.map((asset) => asset.tokenId), token_ids: sellAssets.map((asset) => asset.tokenId),
marketplaces: Array.from(new Set(listings.map((asset) => asset.marketplace.name))), marketplaces: Array.from(new Set(listings.map((asset) => asset.marketplace.name))),
list_quantity: listings.length, list_quantity: listings.length,
usd_value: ethPriceInUSD * totalEthListingValue, usd_value: usdcAmount,
...trace, ...trace,
} }
const startListingFlow = async () => { const startListingFlow = async () => {
if (!signer) return if (!signer) return
sendAnalyticsEvent(NFTEventName.NFT_SELL_START_LISTING, { ...startListingEventProperties }) sendAnalyticsEvent(NFTEventName.NFT_SELL_START_LISTING, { ...startListingEventProperties })
setListingStatus(ListingStatus.SIGNING)
const signerAddress = await signer.getAddress() const signerAddress = await signer.getAddress()
const nonce = await looksRareNonceFetcher(signerAddress) const nonce = await looksRareNonceFetcher(signerAddress)
setLooksRareNonce(nonce ?? 0) setLooksRareNonce(nonce ?? 0)
...@@ -317,9 +294,7 @@ export const ListPage = () => { ...@@ -317,9 +294,7 @@ export const ListPage = () => {
<EthValueWrapper totalEthListingValue={!!totalEthListingValue}> <EthValueWrapper totalEthListingValue={!!totalEthListingValue}>
{totalEthListingValue > 0 ? formatEth(totalEthListingValue) : '-'} ETH {totalEthListingValue > 0 ? formatEth(totalEthListingValue) : '-'} ETH
</EthValueWrapper> </EthValueWrapper>
{!!totalEthListingValue && !!ethPriceInUSD && ( {!!usdcValue && <UsdValue>{usdcAmount}</UsdValue>}
<UsdValue>{formatUsdPrice(totalEthListingValue * ethPriceInUSD)}</UsdValue>
)}
</ProceedsWrapper> </ProceedsWrapper>
<ListingButton onClick={showModalAndStartListing} /> <ListingButton onClick={showModalAndStartListing} />
</ProceedsAndButtonWrapper> </ProceedsAndButtonWrapper>
......
...@@ -2,15 +2,13 @@ import { Plural, t, Trans } from '@lingui/macro' ...@@ -2,15 +2,13 @@ import { Plural, t, Trans } from '@lingui/macro'
import { BaseButton } from 'components/Button' import { BaseButton } from 'components/Button'
import ms from 'ms.macro' import ms from 'ms.macro'
import { BelowFloorWarningModal } from 'nft/components/profile/list/Modal/BelowFloorWarningModal' import { BelowFloorWarningModal } from 'nft/components/profile/list/Modal/BelowFloorWarningModal'
import { useIsMobile, useNFTList, useSellAsset } from 'nft/hooks' import { useIsMobile, useSellAsset } from 'nft/hooks'
import { Listing, ListingStatus, WalletAsset } from 'nft/types' import { Listing, WalletAsset } from 'nft/types'
import { useEffect, useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { BREAKPOINTS } from 'theme' import { BREAKPOINTS } from 'theme'
import shallow from 'zustand/shallow' import shallow from 'zustand/shallow'
import { getListings } from '../../bag/profile/utils'
const BELOW_FLOOR_PRICE_THRESHOLD = 0.8 const BELOW_FLOOR_PRICE_THRESHOLD = 0.8
const StyledListingButton = styled(BaseButton)<{ showResolveIssues: boolean; missingPrices: boolean }>` const StyledListingButton = styled(BaseButton)<{ showResolveIssues: boolean; missingPrices: boolean }>`
...@@ -44,25 +42,9 @@ export const ListingButton = ({ onClick }: { onClick: () => void }) => { ...@@ -44,25 +42,9 @@ export const ListingButton = ({ onClick }: { onClick: () => void }) => {
}), }),
shallow shallow
) )
const { setListingStatus, setListings, setCollectionsRequiringApproval } = useNFTList(
({ setListingStatus, setListings, setCollectionsRequiringApproval }) => ({
setListingStatus,
setListings,
setCollectionsRequiringApproval,
}),
shallow
)
const [showWarning, setShowWarning] = useState(false) const [showWarning, setShowWarning] = useState(false)
const isMobile = useIsMobile() const isMobile = useIsMobile()
// instantiate listings and collections to approve when user's modify input data
useEffect(() => {
const [newCollectionsToApprove, newListings] = getListings(sellAssets)
setListings(newListings)
setCollectionsRequiringApproval(newCollectionsToApprove)
setListingStatus(ListingStatus.DEFINED)
}, [sellAssets, setCollectionsRequiringApproval, setListingStatus, setListings])
// Find issues with item listing data // Find issues with item listing data
const [listingsMissingPrice, listingsBelowFloor] = useMemo(() => { const [listingsMissingPrice, listingsBelowFloor] = useMemo(() => {
const missingExpiration = sellAssets.some((asset) => { const missingExpiration = sellAssets.some((asset) => {
......
...@@ -4,19 +4,18 @@ import Column from 'components/Column' ...@@ -4,19 +4,18 @@ import Column from 'components/Column'
import Row from 'components/Row' import Row from 'components/Row'
import { MouseoverTooltip } from 'components/Tooltip' import { MouseoverTooltip } from 'components/Tooltip'
import { RowsCollpsedIcon, RowsExpandedIcon } from 'nft/components/icons' import { RowsCollpsedIcon, RowsExpandedIcon } from 'nft/components/icons'
import { getRoyalty, useHandleGlobalPriceToggle, useSyncPriceWithGlobalMethod } from 'nft/components/profile/list/utils'
import { useSellAsset } from 'nft/hooks' import { useSellAsset } from 'nft/hooks'
import { ListingMarket, ListingWarning, WalletAsset } from 'nft/types' import { ListingMarket, WalletAsset } from 'nft/types'
import { LOOKS_RARE_CREATOR_BASIS_POINTS } from 'nft/utils'
import { formatEth, formatUsdPrice } from 'nft/utils/currency' import { formatEth, formatUsdPrice } from 'nft/utils/currency'
import { fetchPrice } from 'nft/utils/fetchPrice' import { fetchPrice } from 'nft/utils/fetchPrice'
import { Dispatch, DispatchWithoutAction, useEffect, useMemo, useReducer, useState } from 'react' import { Dispatch, DispatchWithoutAction, useCallback, useEffect, useMemo, useReducer, useState } from 'react'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { BREAKPOINTS, ThemedText } from 'theme' import { BREAKPOINTS, ThemedText } from 'theme'
import { SetPriceMethod } from './NFTListingsGrid'
import { PriceTextInput } from './PriceTextInput' import { PriceTextInput } from './PriceTextInput'
import { RoyaltyTooltip } from './RoyaltyTooltip' import { RoyaltyTooltip } from './RoyaltyTooltip'
import { RemoveIconWrap } from './shared' import { RemoveIconWrap, SetPriceMethod } from './shared'
const LastPriceInfo = styled(Column)` const LastPriceInfo = styled(Column)`
text-align: left; text-align: left;
...@@ -104,13 +103,6 @@ const ReturnColumn = styled(Column)` ...@@ -104,13 +103,6 @@ const ReturnColumn = styled(Column)`
} }
` `
const getRoyalty = (listingMarket: ListingMarket, asset: WalletAsset) => {
// LooksRare is a unique case where royalties for creators are a flat 0.5% or 50 basis points
const baseFee = listingMarket.name === 'LooksRare' ? LOOKS_RARE_CREATOR_BASIS_POINTS : asset.basisPoints ?? 0
return baseFee * 0.01
}
interface MarketplaceRowProps { interface MarketplaceRowProps {
globalPriceMethod?: SetPriceMethod globalPriceMethod?: SetPriceMethod
globalPrice?: number globalPrice?: number
...@@ -118,7 +110,6 @@ interface MarketplaceRowProps { ...@@ -118,7 +110,6 @@ interface MarketplaceRowProps {
selectedMarkets: ListingMarket[] selectedMarkets: ListingMarket[]
removeMarket?: () => void removeMarket?: () => void
asset: WalletAsset asset: WalletAsset
showMarketplaceLogo: boolean
expandMarketplaceRows?: boolean expandMarketplaceRows?: boolean
rowHovered?: boolean rowHovered?: boolean
toggleExpandMarketplaceRows: DispatchWithoutAction toggleExpandMarketplaceRows: DispatchWithoutAction
...@@ -131,7 +122,6 @@ export const MarketplaceRow = ({ ...@@ -131,7 +122,6 @@ export const MarketplaceRow = ({
selectedMarkets, selectedMarkets,
removeMarket = undefined, removeMarket = undefined,
asset, asset,
showMarketplaceLogo,
expandMarketplaceRows, expandMarketplaceRows,
toggleExpandMarketplaceRows, toggleExpandMarketplaceRows,
rowHovered, rowHovered,
...@@ -147,9 +137,16 @@ export const MarketplaceRow = ({ ...@@ -147,9 +137,16 @@ export const MarketplaceRow = ({
)?.price )?.price
) )
const [globalOverride, setGlobalOverride] = useState(false) const [globalOverride, setGlobalOverride] = useState(false)
const showGlobalPrice = globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride && globalPrice
const showGlobalPrice = globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride
const price = showGlobalPrice ? globalPrice : listPrice const price = showGlobalPrice ? globalPrice : listPrice
const setPrice = useCallback(
(price?: number) => {
showGlobalPrice ? setGlobalPrice(price) : setListPrice(price)
for (const marketplace of selectedMarkets) setAssetListPrice(asset, price, marketplace)
},
[asset, selectedMarkets, setAssetListPrice, setGlobalPrice, showGlobalPrice]
)
const fees = useMemo(() => { const fees = useMemo(() => {
if (selectedMarkets.length === 1) { if (selectedMarkets.length === 1) {
...@@ -168,68 +165,25 @@ export const MarketplaceRow = ({ ...@@ -168,68 +165,25 @@ export const MarketplaceRow = ({
const feeInEth = price && (price * fees) / 100 const feeInEth = price && (price * fees) / 100
const userReceives = price && feeInEth && price - feeInEth const userReceives = price && feeInEth && price - feeInEth
useMemo(() => { useHandleGlobalPriceToggle(globalOverride, setListPrice, setPrice, listPrice, globalPrice)
for (const market of selectedMarkets) { useSyncPriceWithGlobalMethod(
if (market && asset && asset.basisPoints) { asset,
market.royalty = (market.name === 'LooksRare' ? LOOKS_RARE_CREATOR_BASIS_POINTS : asset.basisPoints) * 0.01 setListPrice,
} setGlobalPrice,
} setGlobalOverride,
}, [asset, selectedMarkets]) listPrice,
globalPrice,
useEffect(() => { globalPriceMethod
if (globalPriceMethod === SetPriceMethod.FLOOR_PRICE) { )
setListPrice(asset?.floorPrice)
setGlobalPrice(asset.floorPrice)
} else if (globalPriceMethod === SetPriceMethod.LAST_PRICE) {
setListPrice(asset.lastPrice)
setGlobalPrice(asset.lastPrice)
} else if (globalPriceMethod === SetPriceMethod.SAME_PRICE)
listPrice && !globalPrice ? setGlobalPrice(listPrice) : setListPrice(globalPrice)
setGlobalOverride(false)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [globalPriceMethod])
useEffect(() => {
if (selectedMarkets.length)
for (const marketplace of selectedMarkets) setAssetListPrice(asset, listPrice, marketplace)
else setAssetListPrice(asset, listPrice)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [listPrice])
useEffect(() => {
let price: number | undefined = undefined
if (globalOverride) {
if (!listPrice) setListPrice(globalPrice)
price = listPrice ? listPrice : globalPrice
} else {
price = listPrice
}
if (selectedMarkets.length) for (const marketplace of selectedMarkets) setAssetListPrice(asset, price, marketplace)
else setAssetListPrice(asset, price)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [globalOverride])
// When in Same Price Mode and not overriding, update local price when global price changes
useEffect(() => { useEffect(() => {
if (globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride) { if (showGlobalPrice) {
if (selectedMarkets.length) setPrice(globalPrice)
for (const marketplace of selectedMarkets) setAssetListPrice(asset, globalPrice, marketplace)
else setAssetListPrice(asset, globalPrice)
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [globalPrice]) }, [globalPrice])
let warning: ListingWarning | undefined = undefined
if (asset.listingWarnings && asset.listingWarnings?.length > 0) {
if (showMarketplaceLogo) {
for (const listingWarning of asset.listingWarnings) {
if (listingWarning.marketplace.name === selectedMarkets[0].name) warning = listingWarning
}
} else {
warning = asset.listingWarnings[0]
}
}
return ( return (
<Row onMouseEnter={toggleMarketRowHovered} onMouseLeave={toggleMarketRowHovered}> <Row onMouseEnter={toggleMarketRowHovered} onMouseLeave={toggleMarketRowHovered}>
<FloorPriceInfo> <FloorPriceInfo>
...@@ -263,27 +217,14 @@ export const MarketplaceRow = ({ ...@@ -263,27 +217,14 @@ export const MarketplaceRow = ({
))} ))}
</MarketIconsWrapper> </MarketIconsWrapper>
)} )}
{globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride ? (
<PriceTextInput
listPrice={globalPrice}
setListPrice={setGlobalPrice}
isGlobalPrice={true}
setGlobalOverride={setGlobalOverride}
globalOverride={globalOverride}
warning={warning}
asset={asset}
/>
) : (
<PriceTextInput <PriceTextInput
listPrice={listPrice} listPrice={price}
setListPrice={setListPrice} setListPrice={setPrice}
isGlobalPrice={false} isGlobalPrice={showGlobalPrice}
setGlobalOverride={setGlobalOverride} setGlobalOverride={setGlobalOverride}
globalOverride={globalOverride} globalOverride={globalOverride}
warning={warning}
asset={asset} asset={asset}
/> />
)}
{rowHovered && ((expandMarketplaceRows && marketRowHovered) || selectedMarkets.length > 1) && ( {rowHovered && ((expandMarketplaceRows && marketRowHovered) || selectedMarkets.length > 1) && (
<ExpandMarketIconWrapper onClick={toggleExpandMarketplaceRows}> <ExpandMarketIconWrapper onClick={toggleExpandMarketplaceRows}>
{expandMarketplaceRows ? <RowsExpandedIcon /> : <RowsCollpsedIcon />} {expandMarketplaceRows ? <RowsExpandedIcon /> : <RowsCollpsedIcon />}
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { sendAnalyticsEvent, Trace, useTrace } from '@uniswap/analytics' import { sendAnalyticsEvent, Trace, useTrace } from '@uniswap/analytics'
import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events' import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events'
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import { getTotalEthValue, signListingRow } from 'nft/components/bag/profile/utils' import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { Portal } from 'nft/components/common/Portal' import { Portal } from 'nft/components/common/Portal'
import { Overlay } from 'nft/components/modals/Overlay' import { Overlay } from 'nft/components/modals/Overlay'
import { getTotalEthValue, signListingRow } from 'nft/components/profile/list/utils'
import { useNFTList, useSellAsset } from 'nft/hooks' import { useNFTList, useSellAsset } from 'nft/hooks'
import { ListingStatus } from 'nft/types' import { ListingStatus } from 'nft/types'
import { fetchPrice } from 'nft/utils' import { useCallback, useEffect, useMemo, useReducer } from 'react'
import { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
import { X } from 'react-feather' import { X } from 'react-feather'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { BREAKPOINTS, ThemedText } from 'theme' import { BREAKPOINTS, ThemedText } from 'theme'
...@@ -46,23 +49,15 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => { ...@@ -46,23 +49,15 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
const signer = provider?.getSigner() const signer = provider?.getSigner()
const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING }) const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING })
const sellAssets = useSellAsset((state) => state.sellAssets) const sellAssets = useSellAsset((state) => state.sellAssets)
const { const { setListingStatusAndCallback, setLooksRareNonce, getLooksRareNonce, collectionsRequiringApproval, listings } =
listingStatus, useNFTList(
setListingStatusAndCallback,
setLooksRareNonce,
getLooksRareNonce,
collectionsRequiringApproval,
listings,
} = useNFTList(
({ ({
listingStatus,
setListingStatusAndCallback, setListingStatusAndCallback,
setLooksRareNonce, setLooksRareNonce,
getLooksRareNonce, getLooksRareNonce,
collectionsRequiringApproval, collectionsRequiringApproval,
listings, listings,
}) => ({ }) => ({
listingStatus,
setListingStatusAndCallback, setListingStatusAndCallback,
setLooksRareNonce, setLooksRareNonce,
getLooksRareNonce, getLooksRareNonce,
...@@ -77,33 +72,37 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => { ...@@ -77,33 +72,37 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
(s) => (s === Section.APPROVE ? Section.SIGN : Section.APPROVE), (s) => (s === Section.APPROVE ? Section.SIGN : Section.APPROVE),
Section.APPROVE Section.APPROVE
) )
const [ethPriceInUSD, setEthPriceInUSD] = useState(0) const nativeCurrency = useNativeCurrency()
const parsedAmount = tryParseCurrencyAmount(totalEthListingValue.toString(), nativeCurrency)
useEffect(() => { const usdcValue = useStablecoinValue(parsedAmount)
fetchPrice().then((price) => { const usdcAmount = formatCurrencyAmount(usdcValue, NumberType.FiatTokenPrice)
setEthPriceInUSD(price || 0)
})
}, [])
const allCollectionsApproved = useMemo( const allCollectionsApproved = useMemo(
() => collectionsRequiringApproval.every((collection) => collection.status === ListingStatus.APPROVED), () => collectionsRequiringApproval.every((collection) => collection.status === ListingStatus.APPROVED),
[collectionsRequiringApproval] [collectionsRequiringApproval]
) )
const allListingsApproved = useMemo(
() => listings.every((listing) => listing.status === ListingStatus.APPROVED),
[listings]
)
const signListings = async () => { const signListings = async () => {
if (!signer || !provider) return if (!signer || !provider) return
// sign listings // sign listings
for (const listing of listings) { for (const listing of listings) {
await signListingRow(listing, signer, provider, getLooksRareNonce, setLooksRareNonce, setListingStatusAndCallback) await signListingRow(listing, signer, provider, getLooksRareNonce, setLooksRareNonce, setListingStatusAndCallback)
} }
sendAnalyticsEvent(NFTEventName.NFT_LISTING_COMPLETED, { sendAnalyticsEvent(NFTEventName.NFT_LISTING_COMPLETED, {
signatures_approved: listings.filter((asset) => asset.status === ListingStatus.APPROVED), signatures_approved: listings.filter((asset) => asset.status === ListingStatus.APPROVED),
list_quantity: listings.length, list_quantity: listings.length,
usd_value: ethPriceInUSD * totalEthListingValue, usd_value: usdcAmount,
...trace, ...trace,
}) })
} }
// Once all collections have been approved, go to next section and start signing listings
useEffect(() => { useEffect(() => {
if (allCollectionsApproved) { if (allCollectionsApproved) {
signListings() signListings()
...@@ -113,8 +112,8 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => { ...@@ -113,8 +112,8 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
}, [allCollectionsApproved]) }, [allCollectionsApproved])
const closeModalOnClick = useCallback(() => { const closeModalOnClick = useCallback(() => {
listingStatus === ListingStatus.APPROVED ? window.location.reload() : overlayClick() allListingsApproved ? window.location.reload() : overlayClick()
}, [listingStatus, overlayClick]) }, [allListingsApproved, overlayClick])
// In the case that a user removes all listings via retry logic, close modal // In the case that a user removes all listings via retry logic, close modal
useEffect(() => { useEffect(() => {
...@@ -125,7 +124,7 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => { ...@@ -125,7 +124,7 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
<Portal> <Portal>
<Trace modal={InterfaceModalName.NFT_LISTING}> <Trace modal={InterfaceModalName.NFT_LISTING}>
<ListModalWrapper> <ListModalWrapper>
{listingStatus === ListingStatus.APPROVED ? ( {allListingsApproved ? (
<SuccessScreen overlayClick={closeModalOnClick} /> <SuccessScreen overlayClick={closeModalOnClick} />
) : ( ) : (
<> <>
......
...@@ -6,7 +6,7 @@ import Row from 'components/Row' ...@@ -6,7 +6,7 @@ import Row from 'components/Row'
import { useStablecoinValue } from 'hooks/useStablecoinPrice' import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useNativeCurrency from 'lib/hooks/useNativeCurrency' import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
import { getTotalEthValue } from 'nft/components/bag/profile/utils' import { getTotalEthValue } from 'nft/components/profile/list/utils'
import { useSellAsset } from 'nft/hooks' import { useSellAsset } from 'nft/hooks'
import { formatEth, generateTweetForList, pluralize } from 'nft/utils' import { formatEth, generateTweetForList, pluralize } from 'nft/utils'
import { useMemo } from 'react' import { useMemo } from 'react'
......
...@@ -10,7 +10,7 @@ import { BREAKPOINTS, ThemedText } from 'theme' ...@@ -10,7 +10,7 @@ import { BREAKPOINTS, ThemedText } from 'theme'
import { opacify } from 'theme/utils' import { opacify } from 'theme/utils'
import { MarketplaceRow } from './MarketplaceRow' import { MarketplaceRow } from './MarketplaceRow'
import { SetPriceMethod } from './NFTListingsGrid' import { SetPriceMethod } from './shared'
const IMAGE_THUMBNAIL_SIZE = 60 const IMAGE_THUMBNAIL_SIZE = 60
...@@ -123,10 +123,10 @@ export const NFTListRow = ({ ...@@ -123,10 +123,10 @@ export const NFTListRow = ({
const [hovered, toggleHovered] = useReducer((s) => !s, false) const [hovered, toggleHovered] = useReducer((s) => !s, false)
const theme = useTheme() const theme = useTheme()
// Keep localMarkets up to date with changes to globalMarkets
useEffect(() => { useEffect(() => {
setLocalMarkets(JSON.parse(JSON.stringify(selectedMarkets))) setLocalMarkets(JSON.parse(JSON.stringify(selectedMarkets)))
selectedMarkets.length < 2 && expandMarketplaceRows && toggleExpandMarketplaceRows() }, [selectedMarkets])
}, [expandMarketplaceRows, selectedMarkets])
return ( return (
<NFTListRowWrapper <NFTListRowWrapper
...@@ -161,17 +161,16 @@ export const NFTListRow = ({ ...@@ -161,17 +161,16 @@ export const NFTListRow = ({
</TokenInfoWrapper> </TokenInfoWrapper>
</NFTInfoWrapper> </NFTInfoWrapper>
<MarketPlaceRowWrapper> <MarketPlaceRowWrapper>
{expandMarketplaceRows ? ( {expandMarketplaceRows && localMarkets.length > 1 ? (
localMarkets.map((market, index) => { localMarkets.map((market) => {
return ( return (
<MarketplaceRow <MarketplaceRow
globalPriceMethod={globalPriceMethod} globalPriceMethod={globalPriceMethod}
globalPrice={globalPrice} globalPrice={globalPrice}
setGlobalPrice={setGlobalPrice} setGlobalPrice={setGlobalPrice}
selectedMarkets={[market]} selectedMarkets={[market]}
removeMarket={() => localMarkets.splice(index, 1)} removeMarket={() => setLocalMarkets(localMarkets.filter((oldMarket) => oldMarket.name !== market.name))}
asset={asset} asset={asset}
showMarketplaceLogo={true}
key={asset.name + market.name} key={asset.name + market.name}
expandMarketplaceRows={expandMarketplaceRows} expandMarketplaceRows={expandMarketplaceRows}
rowHovered={hovered} rowHovered={hovered}
...@@ -186,7 +185,6 @@ export const NFTListRow = ({ ...@@ -186,7 +185,6 @@ export const NFTListRow = ({
setGlobalPrice={setGlobalPrice} setGlobalPrice={setGlobalPrice}
selectedMarkets={localMarkets} selectedMarkets={localMarkets}
asset={asset} asset={asset}
showMarketplaceLogo={false}
rowHovered={hovered} rowHovered={hovered}
toggleExpandMarketplaceRows={toggleExpandMarketplaceRows} toggleExpandMarketplaceRows={toggleExpandMarketplaceRows}
/> />
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
// eslint-disable-next-line no-restricted-imports
import Column from 'components/Column' import Column from 'components/Column'
import Row from 'components/Row' import Row from 'components/Row'
import { useOnClickOutside } from 'hooks/useOnClickOutside' import { useOnClickOutside } from 'hooks/useOnClickOutside'
...@@ -12,6 +11,7 @@ import { BREAKPOINTS } from 'theme' ...@@ -12,6 +11,7 @@ import { BREAKPOINTS } from 'theme'
import { Dropdown } from './Dropdown' import { Dropdown } from './Dropdown'
import { NFTListRow } from './NFTListRow' import { NFTListRow } from './NFTListRow'
import { SetPriceMethod } from './shared'
const TableHeader = styled.div` const TableHeader = styled.div`
display: flex; display: flex;
...@@ -143,13 +143,6 @@ const RowDivider = styled.hr` ...@@ -143,13 +143,6 @@ const RowDivider = styled.hr`
border-color: ${({ theme }) => theme.backgroundInteractive}; border-color: ${({ theme }) => theme.backgroundInteractive};
` `
export enum SetPriceMethod {
SAME_PRICE,
FLOOR_PRICE,
LAST_PRICE,
CUSTOM,
}
export const NFTListingsGrid = ({ selectedMarkets }: { selectedMarkets: ListingMarket[] }) => { export const NFTListingsGrid = ({ selectedMarkets }: { selectedMarkets: ListingMarket[] }) => {
const sellAssets = useSellAsset((state) => state.sellAssets) const sellAssets = useSellAsset((state) => state.sellAssets)
const [globalPriceMethod, setGlobalPriceMethod] = useState(SetPriceMethod.CUSTOM) const [globalPriceMethod, setGlobalPriceMethod] = useState(SetPriceMethod.CUSTOM)
......
...@@ -3,16 +3,19 @@ import Column from 'components/Column' ...@@ -3,16 +3,19 @@ import Column from 'components/Column'
import Row from 'components/Row' import Row from 'components/Row'
import { BrokenLinkIcon } from 'nft/components/icons' import { BrokenLinkIcon } from 'nft/components/icons'
import { NumericInput } from 'nft/components/layout/Input' import { NumericInput } from 'nft/components/layout/Input'
import { useUpdateInputAndWarnings } from 'nft/components/profile/list/utils'
import { body } from 'nft/css/common.css' import { body } from 'nft/css/common.css'
import { useSellAsset } from 'nft/hooks' import { useSellAsset } from 'nft/hooks'
import { ListingWarning, WalletAsset } from 'nft/types' import { WalletAsset } from 'nft/types'
import { formatEth } from 'nft/utils/currency' import { formatEth } from 'nft/utils/currency'
import { Dispatch, FormEvent, useEffect, useRef, useState } from 'react' import { Dispatch, useRef, useState } from 'react'
import { AlertTriangle, Link } from 'react-feather' import { AlertTriangle, Link } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro' import styled, { useTheme } from 'styled-components/macro'
import { BREAKPOINTS } from 'theme' import { BREAKPOINTS } from 'theme'
import { colors } from 'theme/colors' import { colors } from 'theme/colors'
import { WarningType } from './shared'
const PriceTextInputWrapper = styled(Column)` const PriceTextInputWrapper = styled(Column)`
gap: 12px; gap: 12px;
position: relative; position: relative;
...@@ -71,12 +74,6 @@ const WarningAction = styled.div` ...@@ -71,12 +74,6 @@ const WarningAction = styled.div`
color: ${({ theme }) => theme.accentAction}; color: ${({ theme }) => theme.accentAction};
` `
enum WarningType {
BELOW_FLOOR,
ALREADY_LISTED,
NONE,
}
const getWarningMessage = (warning: WarningType) => { const getWarningMessage = (warning: WarningType) => {
let message = <></> let message = <></>
switch (warning) { switch (warning) {
...@@ -96,7 +93,6 @@ interface PriceTextInputProps { ...@@ -96,7 +93,6 @@ interface PriceTextInputProps {
isGlobalPrice: boolean isGlobalPrice: boolean
setGlobalOverride: Dispatch<boolean> setGlobalOverride: Dispatch<boolean>
globalOverride: boolean globalOverride: boolean
warning?: ListingWarning
asset: WalletAsset asset: WalletAsset
} }
...@@ -106,42 +102,35 @@ export const PriceTextInput = ({ ...@@ -106,42 +102,35 @@ export const PriceTextInput = ({
isGlobalPrice, isGlobalPrice,
setGlobalOverride, setGlobalOverride,
globalOverride, globalOverride,
warning,
asset, asset,
}: PriceTextInputProps) => { }: PriceTextInputProps) => {
const [warningType, setWarningType] = useState(WarningType.NONE) const [warningType, setWarningType] = useState(WarningType.NONE)
const removeMarketplaceWarning = useSellAsset((state) => state.removeMarketplaceWarning)
const removeSellAsset = useSellAsset((state) => state.removeSellAsset) const removeSellAsset = useSellAsset((state) => state.removeSellAsset)
const showResolveIssues = useSellAsset((state) => state.showResolveIssues) const showResolveIssues = useSellAsset((state) => state.showResolveIssues)
const inputRef = useRef() as React.MutableRefObject<HTMLInputElement> const inputRef = useRef() as React.MutableRefObject<HTMLInputElement>
const theme = useTheme() const theme = useTheme()
useEffect(() => {
inputRef.current.value = listPrice !== undefined ? `${listPrice}` : ''
setWarningType(WarningType.NONE)
if (!warning && listPrice) {
if (listPrice < (asset?.floorPrice ?? 0)) setWarningType(WarningType.BELOW_FLOOR)
else if (asset.floor_sell_order_price && listPrice >= asset.floor_sell_order_price)
setWarningType(WarningType.ALREADY_LISTED)
} else if (warning && listPrice && listPrice >= 0) removeMarketplaceWarning(asset, warning)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [listPrice])
const percentBelowFloor = (1 - (listPrice ?? 0) / (asset.floorPrice ?? 0)) * 100 const percentBelowFloor = (1 - (listPrice ?? 0) / (asset.floorPrice ?? 0)) * 100
const warningColor = const warningColor =
showResolveIssues && !listPrice (showResolveIssues && !listPrice) ||
? colors.red400 warningType === WarningType.ALREADY_LISTED ||
: warningType !== WarningType.NONE (warningType === WarningType.BELOW_FLOOR && percentBelowFloor >= 20)
? (warningType === WarningType.BELOW_FLOOR && percentBelowFloor >= 20) ||
warningType === WarningType.ALREADY_LISTED
? colors.red400 ? colors.red400
: theme.accentWarning : warningType === WarningType.BELOW_FLOOR
: isGlobalPrice ? theme.accentWarning
: isGlobalPrice || !!listPrice
? theme.accentAction ? theme.accentAction
: listPrice != null : theme.textSecondary
? theme.textSecondary
: theme.accentAction const setPrice = (event: React.ChangeEvent<HTMLInputElement>) => {
if (!listPrice && event.target.value.includes('.') && parseFloat(event.target.value) === 0) {
return
}
const val = parseFloat(event.target.value)
setListPrice(isNaN(val) ? undefined : val)
}
useUpdateInputAndWarnings(setWarningType, inputRef, asset, listPrice)
return ( return (
<PriceTextInputWrapper> <PriceTextInputWrapper>
...@@ -156,13 +145,7 @@ export const PriceTextInput = ({ ...@@ -156,13 +145,7 @@ export const PriceTextInput = ({
backgroundColor="none" backgroundColor="none"
width={{ sm: '54', md: '68' }} width={{ sm: '54', md: '68' }}
ref={inputRef} ref={inputRef}
onChange={(v: FormEvent<HTMLInputElement>) => { onChange={setPrice}
if (!listPrice && v.currentTarget.value.includes('.') && parseFloat(v.currentTarget.value) === 0) {
return
}
const val = parseFloat(v.currentTarget.value)
setListPrice(isNaN(val) ? undefined : val)
}}
/> />
<CurrencyWrapper listPrice={listPrice}>&nbsp;ETH</CurrencyWrapper> <CurrencyWrapper listPrice={listPrice}>&nbsp;ETH</CurrencyWrapper>
{(isGlobalPrice || globalOverride) && ( {(isGlobalPrice || globalOverride) && (
...@@ -172,9 +155,7 @@ export const PriceTextInput = ({ ...@@ -172,9 +155,7 @@ export const PriceTextInput = ({
)} )}
</InputWrapper> </InputWrapper>
<WarningMessage $color={warningColor}> <WarningMessage $color={warningColor}>
{warning {warningType !== WarningType.NONE && (
? warning.message
: warningType !== WarningType.NONE && (
<WarningRow> <WarningRow>
<AlertTriangle height={16} width={16} color={warningColor} /> <AlertTriangle height={16} width={16} color={warningColor} />
<span> <span>
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import Column from 'components/Column' import Column from 'components/Column'
import Row from 'components/Row' import Row from 'components/Row'
import { getRoyalty } from 'nft/components/profile/list/utils'
import { ListingMarket, WalletAsset } from 'nft/types' import { ListingMarket, WalletAsset } from 'nft/types'
import { formatEth } from 'nft/utils' import { formatEth } from 'nft/utils'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
...@@ -50,7 +51,7 @@ export const RoyaltyTooltip = ({ ...@@ -50,7 +51,7 @@ export const RoyaltyTooltip = ({
asset: WalletAsset asset: WalletAsset
fees?: number fees?: number
}) => { }) => {
const maxRoyalty = Math.max(...selectedMarkets.map((market) => market.royalty ?? 0)) const maxRoyalty = Math.max(...selectedMarkets.map((market) => getRoyalty(market, asset) ?? 0)).toFixed(2)
return ( return (
<RoyaltyContainer> <RoyaltyContainer>
{selectedMarkets.map((market) => ( {selectedMarkets.map((market) => (
......
...@@ -14,3 +14,16 @@ export const TitleRow = styled(Row)` ...@@ -14,3 +14,16 @@ export const TitleRow = styled(Row)`
justify-content: space-between; justify-content: space-between;
margin-bottom: 8px; margin-bottom: 8px;
` `
export enum SetPriceMethod {
SAME_PRICE,
FLOOR_PRICE,
LAST_PRICE,
CUSTOM,
}
export enum WarningType {
BELOW_FLOOR,
ALREADY_LISTED,
NONE,
}
import type { JsonRpcSigner, Web3Provider } from '@ethersproject/providers' import type { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
import { addressesByNetwork, SupportedChainId } from '@looksrare/sdk' import { addressesByNetwork, SupportedChainId } from '@looksrare/sdk'
import { SetPriceMethod, WarningType } from 'nft/components/profile/list/shared'
import { useNFTList, useSellAsset } from 'nft/hooks'
import { LOOKSRARE_MARKETPLACE_CONTRACT, X2Y2_TRANSFER_CONTRACT } from 'nft/queries' import { LOOKSRARE_MARKETPLACE_CONTRACT, X2Y2_TRANSFER_CONTRACT } from 'nft/queries'
import { OPENSEA_CROSS_CHAIN_CONDUIT } from 'nft/queries/openSea' import { OPENSEA_CROSS_CHAIN_CONDUIT } from 'nft/queries/openSea'
import { CollectionRow, ListingMarket, ListingRow, ListingStatus, WalletAsset } from 'nft/types' import { CollectionRow, ListingMarket, ListingRow, ListingStatus, WalletAsset } from 'nft/types'
import { approveCollection, LOOKS_RARE_CREATOR_BASIS_POINTS, signListing } from 'nft/utils/listNfts' import { approveCollection, LOOKS_RARE_CREATOR_BASIS_POINTS, signListing } from 'nft/utils/listNfts'
import { Dispatch, useEffect } from 'react'
import shallow from 'zustand/shallow'
export async function approveCollectionRow( export async function approveCollectionRow(
collectionRow: CollectionRow, collectionRow: CollectionRow,
...@@ -12,10 +16,9 @@ export async function approveCollectionRow( ...@@ -12,10 +16,9 @@ export async function approveCollectionRow(
collection: CollectionRow, collection: CollectionRow,
status: ListingStatus, status: ListingStatus,
callback?: () => Promise<void> callback?: () => Promise<void>
) => void, ) => void
pauseAllRows?: () => void
) { ) {
const callback = () => approveCollectionRow(collectionRow, signer, setCollectionStatusAndCallback, pauseAllRows) const callback = () => approveCollectionRow(collectionRow, signer, setCollectionStatusAndCallback)
setCollectionStatusAndCallback(collectionRow, ListingStatus.SIGNING, callback) setCollectionStatusAndCallback(collectionRow, ListingStatus.SIGNING, callback)
const { marketplace, collectionAddress } = collectionRow const { marketplace, collectionAddress } = collectionRow
const addresses = addressesByNetwork[SupportedChainId.MAINNET] const addresses = addressesByNetwork[SupportedChainId.MAINNET]
...@@ -31,11 +34,6 @@ export async function approveCollectionRow( ...@@ -31,11 +34,6 @@ export async function approveCollectionRow(
(await approveCollection(spender, collectionAddress, signer, (newStatus: ListingStatus) => (await approveCollection(spender, collectionAddress, signer, (newStatus: ListingStatus) =>
setCollectionStatusAndCallback(collectionRow, newStatus, callback) setCollectionStatusAndCallback(collectionRow, newStatus, callback)
)) ))
if (
(collectionRow.status === ListingStatus.REJECTED || collectionRow.status === ListingStatus.FAILED) &&
pauseAllRows
)
pauseAllRows()
} }
export async function signListingRow( export async function signListingRow(
...@@ -44,31 +42,18 @@ export async function signListingRow( ...@@ -44,31 +42,18 @@ export async function signListingRow(
provider: Web3Provider, provider: Web3Provider,
getLooksRareNonce: () => number, getLooksRareNonce: () => number,
setLooksRareNonce: (nonce: number) => void, setLooksRareNonce: (nonce: number) => void,
setListingStatusAndCallback: (listing: ListingRow, status: ListingStatus, callback?: () => Promise<void>) => void, setListingStatusAndCallback: (listing: ListingRow, status: ListingStatus, callback?: () => Promise<void>) => void
pauseAllRows?: () => void
) { ) {
const looksRareNonce = getLooksRareNonce() const looksRareNonce = getLooksRareNonce()
const callback = () => { const callback = () => {
return signListingRow( return signListingRow(listing, signer, provider, getLooksRareNonce, setLooksRareNonce, setListingStatusAndCallback)
listing,
signer,
provider,
getLooksRareNonce,
setLooksRareNonce,
setListingStatusAndCallback,
pauseAllRows
)
} }
setListingStatusAndCallback(listing, ListingStatus.SIGNING, callback) setListingStatusAndCallback(listing, ListingStatus.SIGNING, callback)
const { asset, marketplace } = listing const { asset, marketplace } = listing
const res = await signListing(marketplace, asset, signer, provider, looksRareNonce, (newStatus: ListingStatus) => const res = await signListing(marketplace, asset, signer, provider, looksRareNonce, (newStatus: ListingStatus) =>
setListingStatusAndCallback(listing, newStatus, callback) setListingStatusAndCallback(listing, newStatus, callback)
) )
if (listing.status === ListingStatus.REJECTED && pauseAllRows) {
pauseAllRows()
} else {
res && listing.marketplace.name === 'LooksRare' && setLooksRareNonce(looksRareNonce + 1) res && listing.marketplace.name === 'LooksRare' && setLooksRareNonce(looksRareNonce + 1)
}
} }
export const getTotalEthValue = (sellAssets: WalletAsset[]) => { export const getTotalEthValue = (sellAssets: WalletAsset[]) => {
...@@ -86,7 +71,7 @@ export const getTotalEthValue = (sellAssets: WalletAsset[]) => { ...@@ -86,7 +71,7 @@ export const getTotalEthValue = (sellAssets: WalletAsset[]) => {
return total ? Math.round(total * 10000 + Number.EPSILON) / 10000 : 0 return total ? Math.round(total * 10000 + Number.EPSILON) / 10000 : 0
} }
export const getListings = (sellAssets: WalletAsset[]): [CollectionRow[], ListingRow[]] => { const getListings = (sellAssets: WalletAsset[]): [CollectionRow[], ListingRow[]] => {
const newCollectionsToApprove: CollectionRow[] = [] const newCollectionsToApprove: CollectionRow[] = []
const newListings: ListingRow[] = [] const newListings: ListingRow[] = []
...@@ -123,69 +108,89 @@ export const getListings = (sellAssets: WalletAsset[]): [CollectionRow[], Listin ...@@ -123,69 +108,89 @@ export const getListings = (sellAssets: WalletAsset[]): [CollectionRow[], Listin
return [newCollectionsToApprove, newListings] return [newCollectionsToApprove, newListings]
} }
type ListingState = { export const verifyStatus = (status: ListingStatus) => {
allListingsPending: boolean return status !== ListingStatus.PAUSED && status !== ListingStatus.APPROVED
allListingsDefined: boolean
allListingsApproved: boolean
allCollectionsPending: boolean
allCollectionsDefined: boolean
anyActiveSigning: boolean
anyActiveFailures: boolean
anyActiveRejections: boolean
anyPaused: boolean
} }
export const getListingState = ( export function useSubscribeListingState() {
collectionsRequiringApproval: CollectionRow[], const sellAssets = useSellAsset((state) => state.sellAssets)
listings: ListingRow[] const { setListings, setCollectionsRequiringApproval } = useNFTList(
): ListingState => { ({ setListings, setCollectionsRequiringApproval }) => ({
let allListingsPending = true setListings,
let allListingsDefined = true setCollectionsRequiringApproval,
let allListingsApproved = true }),
let allCollectionsPending = true shallow
let allCollectionsDefined = true )
let anyActiveSigning = false useEffect(() => {
let anyActiveFailures = false const [newCollectionsToApprove, newListings] = getListings(sellAssets)
let anyActiveRejections = false setListings(newListings)
let anyPaused = false setCollectionsRequiringApproval(newCollectionsToApprove)
}, [sellAssets, setCollectionsRequiringApproval, setListings])
}
if (collectionsRequiringApproval.length === 0) { export function useHandleGlobalPriceToggle(
allCollectionsDefined = allCollectionsPending = false globalOverride: boolean,
} setListPrice: Dispatch<number | undefined>,
for (const collection of collectionsRequiringApproval) { setPrice: (price?: number) => void,
if (collection.status !== ListingStatus.PENDING) allCollectionsPending = false listPrice?: number,
if (collection.status !== ListingStatus.DEFINED) allCollectionsDefined = false globalPrice?: number
if (collection.status === ListingStatus.SIGNING) anyActiveSigning = true ) {
else if (collection.status === ListingStatus.FAILED) anyActiveFailures = true useEffect(() => {
else if (collection.status === ListingStatus.REJECTED) anyActiveRejections = true let price: number | undefined
else if (collection.status === ListingStatus.PAUSED) anyPaused = true if (globalOverride) {
if (!listPrice) setListPrice(globalPrice)
price = globalPrice
} else {
price = listPrice
} }
setPrice(price)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [globalOverride])
}
if (listings.length === 0) { export function useSyncPriceWithGlobalMethod(
allListingsApproved = allListingsDefined = allListingsPending = false asset: WalletAsset,
} setListPrice: Dispatch<number | undefined>,
for (const listing of listings) { setGlobalPrice: Dispatch<number | undefined>,
if (listing.status !== ListingStatus.PENDING) allListingsPending = false setGlobalOverride: Dispatch<boolean>,
if (listing.status !== ListingStatus.DEFINED) allListingsDefined = false listPrice?: number,
if (listing.status !== ListingStatus.APPROVED) allListingsApproved = false globalPrice?: number,
if (listing.status === ListingStatus.SIGNING) anyActiveSigning = true globalPriceMethod?: SetPriceMethod
else if (listing.status === ListingStatus.FAILED) anyActiveFailures = true ) {
else if (listing.status === ListingStatus.REJECTED) anyActiveRejections = true useEffect(() => {
else if (listing.status === ListingStatus.PAUSED) anyPaused = true if (globalPriceMethod === SetPriceMethod.FLOOR_PRICE) {
} setListPrice(asset?.floorPrice)
return { setGlobalPrice(asset.floorPrice)
allListingsPending, } else if (globalPriceMethod === SetPriceMethod.LAST_PRICE) {
allListingsDefined, setListPrice(asset.lastPrice)
allListingsApproved, setGlobalPrice(asset.lastPrice)
allCollectionsPending, } else if (globalPriceMethod === SetPriceMethod.SAME_PRICE)
allCollectionsDefined, listPrice && !globalPrice ? setGlobalPrice(listPrice) : setListPrice(globalPrice)
anyActiveSigning,
anyActiveFailures, setGlobalOverride(false)
anyActiveRejections, // eslint-disable-next-line react-hooks/exhaustive-deps
anyPaused, }, [globalPriceMethod])
}
} }
export const verifyStatus = (status: ListingStatus) => { export function useUpdateInputAndWarnings(
return status !== ListingStatus.PAUSED && status !== ListingStatus.APPROVED setWarningType: Dispatch<WarningType>,
inputRef: React.MutableRefObject<HTMLInputElement>,
asset: WalletAsset,
listPrice?: number
) {
useEffect(() => {
setWarningType(WarningType.NONE)
const price = listPrice ?? 0
inputRef.current.value = `${price}`
if (price < (asset?.floorPrice ?? 0) && price > 0) setWarningType(WarningType.BELOW_FLOOR)
else if (asset.floor_sell_order_price && price >= asset.floor_sell_order_price)
setWarningType(WarningType.ALREADY_LISTED)
}, [asset?.floorPrice, asset.floor_sell_order_price, inputRef, listPrice, setWarningType])
}
export const getRoyalty = (listingMarket: ListingMarket, asset: WalletAsset) => {
// LooksRare is a unique case where royalties for creators are a flat 0.5% or 50 basis points
const baseFee = listingMarket.name === 'LooksRare' ? LOOKS_RARE_CREATOR_BASIS_POINTS : asset.basisPoints ?? 0
return baseFee * 0.01
} }
...@@ -4,12 +4,10 @@ import { devtools } from 'zustand/middleware' ...@@ -4,12 +4,10 @@ import { devtools } from 'zustand/middleware'
interface NFTListState { interface NFTListState {
looksRareNonce: number looksRareNonce: number
listingStatus: ListingStatus
listings: ListingRow[] listings: ListingRow[]
collectionsRequiringApproval: CollectionRow[] collectionsRequiringApproval: CollectionRow[]
setLooksRareNonce: (nonce: number) => void setLooksRareNonce: (nonce: number) => void
getLooksRareNonce: () => number getLooksRareNonce: () => number
setListingStatus: (status: ListingStatus) => void
setListings: (listings: ListingRow[]) => void setListings: (listings: ListingRow[]) => void
setCollectionsRequiringApproval: (collections: CollectionRow[]) => void setCollectionsRequiringApproval: (collections: CollectionRow[]) => void
setListingStatusAndCallback: (listing: ListingRow, status: ListingStatus, callback?: () => Promise<void>) => void setListingStatusAndCallback: (listing: ListingRow, status: ListingStatus, callback?: () => Promise<void>) => void
...@@ -23,7 +21,6 @@ interface NFTListState { ...@@ -23,7 +21,6 @@ interface NFTListState {
export const useNFTList = create<NFTListState>()( export const useNFTList = create<NFTListState>()(
devtools((set, get) => ({ devtools((set, get) => ({
looksRareNonce: 0, looksRareNonce: 0,
listingStatus: ListingStatus.DEFINED,
listings: [], listings: [],
collectionsRequiringApproval: [], collectionsRequiringApproval: [],
setLooksRareNonce: (nonce) => setLooksRareNonce: (nonce) =>
...@@ -33,10 +30,6 @@ export const useNFTList = create<NFTListState>()( ...@@ -33,10 +30,6 @@ export const useNFTList = create<NFTListState>()(
getLooksRareNonce: () => { getLooksRareNonce: () => {
return get().looksRareNonce return get().looksRareNonce
}, },
setListingStatus: (status) =>
set(() => {
return { listingStatus: status }
}),
setListings: (listings) => setListings: (listings) =>
set(() => { set(() => {
const updatedListings = listings.map((listing) => { const updatedListings = listings.map((listing) => {
......
import create from 'zustand' import create from 'zustand'
import { devtools } from 'zustand/middleware' import { devtools } from 'zustand/middleware'
import { ListingMarket, ListingWarning, WalletAsset } from '../types' import { ListingMarket, WalletAsset } from '../types'
interface SellAssetState { interface SellAssetState {
sellAssets: WalletAsset[] sellAssets: WalletAsset[]
...@@ -16,10 +16,6 @@ interface SellAssetState { ...@@ -16,10 +16,6 @@ interface SellAssetState {
removeAssetMarketplace: (asset: WalletAsset, marketplace: ListingMarket) => void removeAssetMarketplace: (asset: WalletAsset, marketplace: ListingMarket) => void
toggleShowResolveIssues: () => void toggleShowResolveIssues: () => void
setIssues: (issues: number) => void setIssues: (issues: number) => void
// TODO: After merging v2, see if this marketplace logic can be removed
addMarketplaceWarning: (asset: WalletAsset, warning: ListingWarning) => void
removeMarketplaceWarning: (asset: WalletAsset, warning: ListingWarning, setGlobalOverride?: boolean) => void
removeAllMarketplaceWarnings: () => void
} }
export const useSellAsset = create<SellAssetState>()( export const useSellAsset = create<SellAssetState>()(
...@@ -118,47 +114,6 @@ export const useSellAsset = create<SellAssetState>()( ...@@ -118,47 +114,6 @@ export const useSellAsset = create<SellAssetState>()(
return { sellAssets: assetsCopy } return { sellAssets: assetsCopy }
}) })
}, },
addMarketplaceWarning: (asset, warning) => {
set(({ sellAssets }) => {
const assetsCopy = [...sellAssets]
asset.listingWarnings?.push(warning)
const index = sellAssets.findIndex(
(n) => n.tokenId === asset.tokenId && n.asset_contract.address === asset.asset_contract.address
)
assetsCopy[index] = asset
return { sellAssets: assetsCopy }
})
},
removeMarketplaceWarning: (asset, warning, setGlobalOverride?) => {
set(({ sellAssets }) => {
const assetsCopy = [...sellAssets]
if (asset.listingWarnings === undefined || asset.newListings === undefined) return { sellAssets: assetsCopy }
const warningIndex =
asset.listingWarnings?.findIndex((n) => n.marketplace.name === warning.marketplace.name) ?? -1
asset.listingWarnings?.splice(warningIndex, 1)
if (warning?.message?.includes('LISTING BELOW FLOOR')) {
if (setGlobalOverride) {
asset.newListings?.forEach((listing) => (listing.overrideFloorPrice = true))
} else {
const listingIndex =
asset.newListings?.findIndex((n) => n.marketplace.name === warning.marketplace.name) ?? -1
asset.newListings[listingIndex].overrideFloorPrice = true
}
}
const index = sellAssets.findIndex(
(n) => n.tokenId === asset.tokenId && n.asset_contract.address === asset.asset_contract.address
)
assetsCopy[index] = asset
return { sellAssets: assetsCopy }
})
},
removeAllMarketplaceWarnings: () => {
set(({ sellAssets }) => {
const assetsCopy = [...sellAssets]
assetsCopy.map((asset) => (asset.listingWarnings = []))
return { sellAssets: assetsCopy }
})
},
toggleShowResolveIssues: () => { toggleShowResolveIssues: () => {
set(({ showResolveIssues }) => { set(({ showResolveIssues }) => {
return { showResolveIssues: !showResolveIssues } return { showResolveIssues: !showResolveIssues }
......
...@@ -7,8 +7,8 @@ import { XXXL_BAG_WIDTH } from 'nft/components/bag/Bag' ...@@ -7,8 +7,8 @@ import { XXXL_BAG_WIDTH } from 'nft/components/bag/Bag'
import { ListPage } from 'nft/components/profile/list/ListPage' import { ListPage } from 'nft/components/profile/list/ListPage'
import { ProfilePage } from 'nft/components/profile/view/ProfilePage' import { ProfilePage } from 'nft/components/profile/view/ProfilePage'
import { ProfilePageLoadingSkeleton } from 'nft/components/profile/view/ProfilePageLoadingSkeleton' import { ProfilePageLoadingSkeleton } from 'nft/components/profile/view/ProfilePageLoadingSkeleton'
import { useBag, useNFTList, useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks' import { useBag, useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
import { ListingStatus, ProfilePageStateType } from 'nft/types' import { ProfilePageStateType } from 'nft/types'
import { Suspense, useEffect, useRef } from 'react' import { Suspense, useEffect, useRef } from 'react'
import { useToggleWalletModal } from 'state/application/hooks' import { useToggleWalletModal } from 'state/application/hooks'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
...@@ -62,15 +62,8 @@ const ConnectWalletButton = styled(ButtonPrimary)` ...@@ -62,15 +62,8 @@ const ConnectWalletButton = styled(ButtonPrimary)`
const ProfileContent = () => { const ProfileContent = () => {
const sellPageState = useProfilePageState((state) => state.state) const sellPageState = useProfilePageState((state) => state.state)
const setSellPageState = useProfilePageState((state) => state.setProfilePageState) const setSellPageState = useProfilePageState((state) => state.setProfilePageState)
const removeAllMarketplaceWarnings = useSellAsset((state) => state.removeAllMarketplaceWarnings)
const resetSellAssets = useSellAsset((state) => state.reset) const resetSellAssets = useSellAsset((state) => state.reset)
const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters) const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters)
const setListingStatus = useNFTList((state) => state.setListingStatus)
useEffect(() => {
removeAllMarketplaceWarnings()
setListingStatus(ListingStatus.DEFINED)
}, [removeAllMarketplaceWarnings, sellPageState, setListingStatus])
const { account } = useWeb3React() const { account } = useWeb3React()
const accountRef = useRef(account) const accountRef = useRef(account)
......
...@@ -6,11 +6,6 @@ export interface ListingMarket { ...@@ -6,11 +6,6 @@ export interface ListingMarket {
name: string name: string
fee: number fee: number
icon: string icon: string
royalty?: number
}
export interface ListingWarning {
marketplace: ListingMarket
message: string
} }
export interface SellOrder { export interface SellOrder {
...@@ -72,7 +67,6 @@ export interface WalletAsset { ...@@ -72,7 +67,6 @@ export interface WalletAsset {
marketAgnosticPrice?: number marketAgnosticPrice?: number
newListings?: Listing[] newListings?: Listing[]
marketplaces?: ListingMarket[] marketplaces?: ListingMarket[]
listingWarnings?: ListingWarning[]
} }
export interface WalletCollection { export interface WalletCollection {
......
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