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