feat: [ListV2] Connect modal styling to functionality (#5905)
* dynamic styles based on listing status * connect collection approval to v2 modal * import cleanup * add comments * connect sign listing logic * correct pending styles * correct pending status for collection approval * correct pending status for os listings * use check from react-feather --------- Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
This commit is contained in:
parent
35a03e2681
commit
48833f27e3
@ -1,4 +1,3 @@
|
|||||||
import { addressesByNetwork, SupportedChainId } from '@looksrare/sdk'
|
|
||||||
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 { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
@ -109,7 +108,6 @@ const ListingModal = () => {
|
|||||||
if (!signer) return
|
if (!signer) return
|
||||||
sendAnalyticsEvent(NFTEventName.NFT_SELL_START_LISTING, { ...startListingEventProperties })
|
sendAnalyticsEvent(NFTEventName.NFT_SELL_START_LISTING, { ...startListingEventProperties })
|
||||||
setListingStatus(ListingStatus.SIGNING)
|
setListingStatus(ListingStatus.SIGNING)
|
||||||
const addresses = addressesByNetwork[SupportedChainId.MAINNET]
|
|
||||||
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)
|
||||||
@ -118,7 +116,6 @@ const ListingModal = () => {
|
|||||||
setListingStatus(ListingStatus.SIGNING)
|
setListingStatus(ListingStatus.SIGNING)
|
||||||
setOpenIndex(1)
|
setOpenIndex(1)
|
||||||
}
|
}
|
||||||
const looksRareAddress = addresses.TRANSFER_MANAGER_ERC721
|
|
||||||
// for all unique collection, marketplace combos -> approve collections
|
// for all unique collection, marketplace combos -> approve collections
|
||||||
for (const collectionRow of collectionsRequiringApproval) {
|
for (const collectionRow of collectionsRequiringApproval) {
|
||||||
verifyStatus(collectionRow.status) &&
|
verifyStatus(collectionRow.status) &&
|
||||||
@ -128,7 +125,6 @@ const ListingModal = () => {
|
|||||||
collectionsRequiringApproval,
|
collectionsRequiringApproval,
|
||||||
setCollectionsRequiringApproval,
|
setCollectionsRequiringApproval,
|
||||||
signer,
|
signer,
|
||||||
looksRareAddress,
|
|
||||||
pauseAllRows
|
pauseAllRows
|
||||||
)
|
)
|
||||||
: approveCollectionRow(
|
: approveCollectionRow(
|
||||||
@ -136,7 +132,6 @@ const ListingModal = () => {
|
|||||||
collectionsRequiringApproval,
|
collectionsRequiringApproval,
|
||||||
setCollectionsRequiringApproval,
|
setCollectionsRequiringApproval,
|
||||||
signer,
|
signer,
|
||||||
looksRareAddress,
|
|
||||||
pauseAllRows
|
pauseAllRows
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import type { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
|
import type { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
|
||||||
|
import { addressesByNetwork, SupportedChainId } from '@looksrare/sdk'
|
||||||
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 { AssetRow, CollectionRow, ListingMarket, ListingRow, ListingStatus, WalletAsset } from 'nft/types'
|
import { AssetRow, CollectionRow, ListingMarket, ListingRow, ListingStatus, WalletAsset } from 'nft/types'
|
||||||
@ -31,8 +32,7 @@ export async function approveCollectionRow(
|
|||||||
collectionsRequiringApproval: CollectionRow[],
|
collectionsRequiringApproval: CollectionRow[],
|
||||||
setCollectionsRequiringApproval: Dispatch<CollectionRow[]>,
|
setCollectionsRequiringApproval: Dispatch<CollectionRow[]>,
|
||||||
signer: JsonRpcSigner,
|
signer: JsonRpcSigner,
|
||||||
looksRareAddress: string,
|
pauseAllRows?: () => void
|
||||||
pauseAllRows: () => void
|
|
||||||
) {
|
) {
|
||||||
updateStatus({
|
updateStatus({
|
||||||
listing: collectionRow,
|
listing: collectionRow,
|
||||||
@ -45,11 +45,11 @@ export async function approveCollectionRow(
|
|||||||
collectionsRequiringApproval,
|
collectionsRequiringApproval,
|
||||||
setCollectionsRequiringApproval,
|
setCollectionsRequiringApproval,
|
||||||
signer,
|
signer,
|
||||||
looksRareAddress,
|
|
||||||
pauseAllRows
|
pauseAllRows
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
const { marketplace, collectionAddress } = collectionRow
|
const { marketplace, collectionAddress } = collectionRow
|
||||||
|
const addresses = addressesByNetwork[SupportedChainId.MAINNET]
|
||||||
const spender =
|
const spender =
|
||||||
marketplace.name === 'OpenSea'
|
marketplace.name === 'OpenSea'
|
||||||
? OPENSEA_CROSS_CHAIN_CONDUIT
|
? OPENSEA_CROSS_CHAIN_CONDUIT
|
||||||
@ -57,7 +57,7 @@ export async function approveCollectionRow(
|
|||||||
? LOOKSRARE_MARKETPLACE_CONTRACT
|
? LOOKSRARE_MARKETPLACE_CONTRACT
|
||||||
: marketplace.name === 'X2Y2'
|
: marketplace.name === 'X2Y2'
|
||||||
? X2Y2_TRANSFER_CONTRACT
|
? X2Y2_TRANSFER_CONTRACT
|
||||||
: looksRareAddress
|
: addresses.TRANSFER_MANAGER_ERC721
|
||||||
!!collectionAddress &&
|
!!collectionAddress &&
|
||||||
(await approveCollection(spender, collectionAddress, signer, (newStatus: ListingStatus) =>
|
(await approveCollection(spender, collectionAddress, signer, (newStatus: ListingStatus) =>
|
||||||
updateStatus({
|
updateStatus({
|
||||||
@ -67,7 +67,11 @@ export async function approveCollectionRow(
|
|||||||
setRows: setCollectionsRequiringApproval as Dispatch<AssetRow[]>,
|
setRows: setCollectionsRequiringApproval as Dispatch<AssetRow[]>,
|
||||||
})
|
})
|
||||||
))
|
))
|
||||||
if (collectionRow.status === ListingStatus.REJECTED || collectionRow.status === ListingStatus.FAILED) pauseAllRows()
|
if (
|
||||||
|
(collectionRow.status === ListingStatus.REJECTED || collectionRow.status === ListingStatus.FAILED) &&
|
||||||
|
pauseAllRows
|
||||||
|
)
|
||||||
|
pauseAllRows()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function signListingRow(
|
export async function signListingRow(
|
||||||
@ -78,7 +82,7 @@ export async function signListingRow(
|
|||||||
provider: Web3Provider,
|
provider: Web3Provider,
|
||||||
getLooksRareNonce: () => number,
|
getLooksRareNonce: () => number,
|
||||||
setLooksRareNonce: (nonce: number) => void,
|
setLooksRareNonce: (nonce: number) => void,
|
||||||
pauseAllRows: () => void
|
pauseAllRows?: () => void
|
||||||
) {
|
) {
|
||||||
const looksRareNonce = getLooksRareNonce()
|
const looksRareNonce = getLooksRareNonce()
|
||||||
updateStatus({
|
updateStatus({
|
||||||
@ -108,7 +112,7 @@ export async function signListingRow(
|
|||||||
setRows: setListings as Dispatch<AssetRow[]>,
|
setRows: setListings as Dispatch<AssetRow[]>,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
if (listing.status === ListingStatus.REJECTED) pauseAllRows()
|
if (listing.status === ListingStatus.REJECTED && pauseAllRows) pauseAllRows()
|
||||||
else {
|
else {
|
||||||
res && listing.marketplace.name === 'LooksRare' && setLooksRareNonce(looksRareNonce + 1)
|
res && listing.marketplace.name === 'LooksRare' && setLooksRareNonce(looksRareNonce + 1)
|
||||||
const newStatus = res ? ListingStatus.APPROVED : ListingStatus.FAILED
|
const newStatus = res ? ListingStatus.APPROVED : ListingStatus.FAILED
|
||||||
|
@ -760,8 +760,8 @@ export const CancelListingIcon = (props: SVGProps) => (
|
|||||||
|
|
||||||
export const ListingModalWindowActive = (props: SVGProps) => (
|
export const ListingModalWindowActive = (props: SVGProps) => (
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||||
<circle cx="8" cy="8" r="8" fill={themeVars.colors.accentAction} fillOpacity="0.24" />
|
<circle cx="8" cy="8" r="8" fill={props.fill ? props.fill : themeVars.colors.accentAction} fillOpacity="0.24" />
|
||||||
<circle cx="8" cy="8" r="5" fill={themeVars.colors.accentAction} />
|
<circle cx="8" cy="8" r="5" fill={props.fill ? props.fill : themeVars.colors.accentAction} />
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
import { t, Trans } from '@lingui/macro'
|
import { t, Trans } from '@lingui/macro'
|
||||||
|
import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics'
|
||||||
|
import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events'
|
||||||
|
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 { SMALL_MEDIA_BREAKPOINT } from 'components/Tokens/constants'
|
import { SMALL_MEDIA_BREAKPOINT } from 'components/Tokens/constants'
|
||||||
import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2'
|
import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2'
|
||||||
import { ListingButton } from 'nft/components/bag/profile/ListingButton'
|
import { ListingButton } from 'nft/components/bag/profile/ListingButton'
|
||||||
import { getListingState, getTotalEthValue } from 'nft/components/bag/profile/utils'
|
import { approveCollectionRow, getListingState, getTotalEthValue, verifyStatus } from 'nft/components/bag/profile/utils'
|
||||||
import { BackArrowIcon } from 'nft/components/icons'
|
import { BackArrowIcon } from 'nft/components/icons'
|
||||||
import { headlineLarge, headlineSmall } from 'nft/css/common.css'
|
import { headlineLarge, headlineSmall } from 'nft/css/common.css'
|
||||||
import { themeVars } from 'nft/css/sprinkles.css'
|
import { themeVars } from 'nft/css/sprinkles.css'
|
||||||
import { useBag, useIsMobile, useNFTList, useProfilePageState, useSellAsset } from 'nft/hooks'
|
import { useBag, 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 { ListingStatus, ProfilePageStateType } from 'nft/types'
|
import { ListingStatus, ProfilePageStateType } from 'nft/types'
|
||||||
import { fetchPrice, formatEth, formatUsdPrice } from 'nft/utils'
|
import { fetchPrice, formatEth, formatUsdPrice } from 'nft/utils'
|
||||||
import { ListingMarkets } from 'nft/utils/listNfts'
|
import { ListingMarkets } from 'nft/utils/listNfts'
|
||||||
@ -17,6 +21,7 @@ import { useEffect, useMemo, useReducer, useState } from 'react'
|
|||||||
import styled, { css } from 'styled-components/macro'
|
import styled, { css } from 'styled-components/macro'
|
||||||
import { BREAKPOINTS, ThemedText } from 'theme'
|
import { BREAKPOINTS, ThemedText } from 'theme'
|
||||||
import { Z_INDEX } from 'theme/zIndex'
|
import { Z_INDEX } from 'theme/zIndex'
|
||||||
|
import shallow from 'zustand/shallow'
|
||||||
|
|
||||||
import { ListModal } from './Modal/ListModal'
|
import { ListModal } from './Modal/ListModal'
|
||||||
import { NFTListingsGrid } from './NFTListingsGrid'
|
import { NFTListingsGrid } from './NFTListingsGrid'
|
||||||
@ -150,21 +155,50 @@ const ListingButtonWrapper = styled.div`
|
|||||||
|
|
||||||
export const ListPage = () => {
|
export const ListPage = () => {
|
||||||
const { setProfilePageState: setSellPageState } = useProfilePageState()
|
const { setProfilePageState: setSellPageState } = useProfilePageState()
|
||||||
const setGlobalMarketplaces = useSellAsset((state) => state.setGlobalMarketplaces)
|
const { provider } = useWeb3React()
|
||||||
const [selectedMarkets, setSelectedMarkets] = useState([ListingMarkets[0]]) // default marketplace: x2y2
|
|
||||||
const toggleBag = useBag((s) => s.toggleBag)
|
const toggleBag = useBag((s) => s.toggleBag)
|
||||||
const listings = useNFTList((state) => state.listings)
|
|
||||||
const collectionsRequiringApproval = useNFTList((state) => state.collectionsRequiringApproval)
|
|
||||||
const listingStatus = useNFTList((state) => state.listingStatus)
|
|
||||||
const setListingStatus = useNFTList((state) => state.setListingStatus)
|
|
||||||
const sellAssets = useSellAsset((state) => state.sellAssets)
|
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
const isNftListV2 = useNftListV2Flag() === NftListV2Variant.Enabled
|
const isNftListV2 = useNftListV2Flag() === NftListV2Variant.Enabled
|
||||||
|
const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING })
|
||||||
|
const { setGlobalMarketplaces, sellAssets } = useSellAsset(
|
||||||
|
({ setGlobalMarketplaces, sellAssets }) => ({
|
||||||
|
setGlobalMarketplaces,
|
||||||
|
sellAssets,
|
||||||
|
}),
|
||||||
|
shallow
|
||||||
|
)
|
||||||
|
const {
|
||||||
|
listings,
|
||||||
|
collectionsRequiringApproval,
|
||||||
|
listingStatus,
|
||||||
|
setListingStatus,
|
||||||
|
setLooksRareNonce,
|
||||||
|
setCollectionsRequiringApproval,
|
||||||
|
} = useNFTList(
|
||||||
|
({
|
||||||
|
listings,
|
||||||
|
collectionsRequiringApproval,
|
||||||
|
listingStatus,
|
||||||
|
setListingStatus,
|
||||||
|
setLooksRareNonce,
|
||||||
|
setCollectionsRequiringApproval,
|
||||||
|
}) => ({
|
||||||
|
listings,
|
||||||
|
collectionsRequiringApproval,
|
||||||
|
listingStatus,
|
||||||
|
setListingStatus,
|
||||||
|
setLooksRareNonce,
|
||||||
|
setCollectionsRequiringApproval,
|
||||||
|
}),
|
||||||
|
shallow
|
||||||
|
)
|
||||||
|
|
||||||
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
|
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
|
||||||
const anyListingsMissingPrice = useMemo(() => !!listings.find((listing) => !listing.price), [listings])
|
const anyListingsMissingPrice = useMemo(() => !!listings.find((listing) => !listing.price), [listings])
|
||||||
const [ethPriceInUSD, setEthPriceInUSD] = useState(0)
|
|
||||||
const [showListModal, toggleShowListModal] = useReducer((s) => !s, false)
|
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(() => {
|
useEffect(() => {
|
||||||
fetchPrice().then((price) => {
|
fetchPrice().then((price) => {
|
||||||
@ -172,6 +206,7 @@ export const ListPage = () => {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// TODO with removal of list v1 see if this logic can be removed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const state = getListingState(collectionsRequiringApproval, listings)
|
const state = getListingState(collectionsRequiringApproval, listings)
|
||||||
|
|
||||||
@ -188,8 +223,43 @@ export const ListPage = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setGlobalMarketplaces(selectedMarkets)
|
setGlobalMarketplaces(selectedMarkets)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [selectedMarkets, setGlobalMarketplaces])
|
||||||
}, [selectedMarkets])
|
|
||||||
|
const startListingEventProperties = {
|
||||||
|
collection_addresses: sellAssets.map((asset) => asset.asset_contract.address),
|
||||||
|
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,
|
||||||
|
...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)
|
||||||
|
|
||||||
|
// for all unique collection, marketplace combos -> approve collections
|
||||||
|
for (const collectionRow of collectionsRequiringApproval) {
|
||||||
|
verifyStatus(collectionRow.status) &&
|
||||||
|
(isMobile
|
||||||
|
? await approveCollectionRow(
|
||||||
|
collectionRow,
|
||||||
|
collectionsRequiringApproval,
|
||||||
|
setCollectionsRequiringApproval,
|
||||||
|
signer
|
||||||
|
)
|
||||||
|
: approveCollectionRow(collectionRow, collectionsRequiringApproval, setCollectionsRequiringApproval, signer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleV2Click = () => {
|
||||||
|
toggleShowListModal()
|
||||||
|
startListingFlow()
|
||||||
|
}
|
||||||
|
|
||||||
const BannerText = isMobile ? (
|
const BannerText = isMobile ? (
|
||||||
<ThemedText.SubHeader lineHeight="24px">
|
<ThemedText.SubHeader lineHeight="24px">
|
||||||
@ -241,7 +311,7 @@ export const ListPage = () => {
|
|||||||
</ProceedsWrapper>
|
</ProceedsWrapper>
|
||||||
<ListingButtonWrapper>
|
<ListingButtonWrapper>
|
||||||
<ListingButton
|
<ListingButton
|
||||||
onClick={isNftListV2 ? toggleShowListModal : toggleBag}
|
onClick={handleV2Click}
|
||||||
buttonText={anyListingsMissingPrice && !isMobile ? t`Set prices to continue` : t`Start listing`}
|
buttonText={anyListingsMissingPrice && !isMobile ? t`Set prices to continue` : t`Start listing`}
|
||||||
/>
|
/>
|
||||||
</ListingButtonWrapper>
|
</ListingButtonWrapper>
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import { Trans } from '@lingui/macro'
|
import { Trans } from '@lingui/macro'
|
||||||
import { Trace } from '@uniswap/analytics'
|
import { sendAnalyticsEvent, Trace, useTrace } from '@uniswap/analytics'
|
||||||
import { InterfaceModalName } from '@uniswap/analytics-events'
|
import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events'
|
||||||
|
import { useWeb3React } from '@web3-react/core'
|
||||||
import Row from 'components/Row'
|
import Row from 'components/Row'
|
||||||
|
import { getTotalEthValue, signListingRow } from 'nft/components/bag/profile/utils'
|
||||||
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 { useNFTList } from 'nft/hooks'
|
import { useNFTList, useSellAsset } from 'nft/hooks'
|
||||||
import { useReducer } from 'react'
|
import { ListingStatus } from 'nft/types'
|
||||||
|
import { fetchPrice } from 'nft/utils'
|
||||||
|
import { 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'
|
||||||
@ -41,34 +45,85 @@ const TitleRow = styled(Row)`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
|
export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
|
||||||
|
const { provider } = useWeb3React()
|
||||||
|
const signer = provider?.getSigner()
|
||||||
|
const trace = useTrace({ modal: InterfaceModalName.NFT_LISTING })
|
||||||
|
const sellAssets = useSellAsset((state) => state.sellAssets)
|
||||||
const listings = useNFTList((state) => state.listings)
|
const listings = useNFTList((state) => state.listings)
|
||||||
const collectionsRequiringApproval = useNFTList((state) => state.collectionsRequiringApproval)
|
const collectionsRequiringApproval = useNFTList((state) => state.collectionsRequiringApproval)
|
||||||
|
const listingStatus = useNFTList((state) => state.listingStatus)
|
||||||
|
const setListings = useNFTList((state) => state.setListings)
|
||||||
|
const setLooksRareNonce = useNFTList((state) => state.setLooksRareNonce)
|
||||||
|
const getLooksRareNonce = useNFTList((state) => state.getLooksRareNonce)
|
||||||
|
|
||||||
|
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
|
||||||
const [openSection, toggleOpenSection] = useReducer(
|
const [openSection, toggleOpenSection] = useReducer(
|
||||||
(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)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchPrice().then((price) => {
|
||||||
|
setEthPriceInUSD(price || 0)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const allCollectionsApproved = useMemo(
|
||||||
|
() => collectionsRequiringApproval.every((collection) => collection.status === ListingStatus.APPROVED),
|
||||||
|
[collectionsRequiringApproval]
|
||||||
|
)
|
||||||
|
|
||||||
|
const signListings = async () => {
|
||||||
|
if (!signer || !provider) return
|
||||||
|
// sign listings
|
||||||
|
for (const listing of listings) {
|
||||||
|
await signListingRow(listing, listings, setListings, signer, provider, getLooksRareNonce, setLooksRareNonce)
|
||||||
|
}
|
||||||
|
sendAnalyticsEvent(NFTEventName.NFT_LISTING_COMPLETED, {
|
||||||
|
signatures_approved: listings.filter((asset) => asset.status === ListingStatus.APPROVED),
|
||||||
|
list_quantity: listings.length,
|
||||||
|
usd_value: ethPriceInUSD * totalEthListingValue,
|
||||||
|
...trace,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (allCollectionsApproved) {
|
||||||
|
signListings()
|
||||||
|
openSection === Section.APPROVE && toggleOpenSection()
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [allCollectionsApproved])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<Trace modal={InterfaceModalName.NFT_LISTING}>
|
<Trace modal={InterfaceModalName.NFT_LISTING}>
|
||||||
<ListModalWrapper>
|
<ListModalWrapper>
|
||||||
<TitleRow>
|
{listingStatus === ListingStatus.APPROVED ? (
|
||||||
<ThemedText.HeadlineSmall lineHeight="28px">
|
<>TODO Success Screen</>
|
||||||
<Trans>List NFTs</Trans>
|
) : (
|
||||||
</ThemedText.HeadlineSmall>
|
<>
|
||||||
<X size={24} cursor="pointer" onClick={overlayClick} />
|
<TitleRow>
|
||||||
</TitleRow>
|
<ThemedText.HeadlineSmall lineHeight="28px">
|
||||||
<ListModalSection
|
<Trans>List NFTs</Trans>
|
||||||
sectionType={Section.APPROVE}
|
</ThemedText.HeadlineSmall>
|
||||||
active={openSection === Section.APPROVE}
|
<X size={24} cursor="pointer" onClick={overlayClick} />
|
||||||
content={collectionsRequiringApproval}
|
</TitleRow>
|
||||||
toggleSection={toggleOpenSection}
|
<ListModalSection
|
||||||
/>
|
sectionType={Section.APPROVE}
|
||||||
<ListModalSection
|
active={openSection === Section.APPROVE}
|
||||||
sectionType={Section.SIGN}
|
content={collectionsRequiringApproval}
|
||||||
active={openSection === Section.SIGN}
|
toggleSection={toggleOpenSection}
|
||||||
content={listings}
|
/>
|
||||||
toggleSection={toggleOpenSection}
|
<ListModalSection
|
||||||
/>
|
sectionType={Section.SIGN}
|
||||||
|
active={openSection === Section.SIGN}
|
||||||
|
content={listings}
|
||||||
|
toggleSection={toggleOpenSection}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ListModalWrapper>
|
</ListModalWrapper>
|
||||||
</Trace>
|
</Trace>
|
||||||
<Overlay onClick={overlayClick} />
|
<Overlay onClick={overlayClick} />
|
||||||
|
@ -10,8 +10,9 @@ import {
|
|||||||
LoadingIcon,
|
LoadingIcon,
|
||||||
VerifiedIcon,
|
VerifiedIcon,
|
||||||
} from 'nft/components/icons'
|
} from 'nft/components/icons'
|
||||||
import { AssetRow, CollectionRow } from 'nft/types'
|
import { AssetRow, CollectionRow, ListingStatus } from 'nft/types'
|
||||||
import { Info } from 'react-feather'
|
import { useMemo } from 'react'
|
||||||
|
import { Check, Info } from 'react-feather'
|
||||||
import styled, { useTheme } from 'styled-components/macro'
|
import styled, { useTheme } from 'styled-components/macro'
|
||||||
import { ThemedText } from 'theme'
|
import { ThemedText } from 'theme'
|
||||||
import { colors } from 'theme/colors'
|
import { colors } from 'theme/colors'
|
||||||
@ -21,9 +22,10 @@ const SectionHeader = styled(Row)`
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
`
|
`
|
||||||
|
|
||||||
const SectionTitle = styled(ThemedText.SubHeader)<{ active: boolean }>`
|
const SectionTitle = styled(ThemedText.SubHeader)<{ active: boolean; approved: boolean }>`
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
color: ${({ theme, active }) => (active ? theme.textPrimary : theme.textSecondary)};
|
color: ${({ theme, active, approved }) =>
|
||||||
|
approved ? theme.accentSuccess : active ? theme.textPrimary : theme.textSecondary};
|
||||||
`
|
`
|
||||||
|
|
||||||
const SectionArrow = styled(ChevronUpIcon)<{ active: boolean }>`
|
const SectionArrow = styled(ChevronUpIcon)<{ active: boolean }>`
|
||||||
@ -56,11 +58,11 @@ const ContentRowContainer = styled(Column)`
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const ContentRow = styled(Row)`
|
const ContentRow = styled(Row)<{ active: boolean }>`
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
opacity: 0.6;
|
opacity: ${({ active }) => (active ? '1' : '0.6')};
|
||||||
`
|
`
|
||||||
|
|
||||||
const CollectionIcon = styled.img`
|
const CollectionIcon = styled.img`
|
||||||
@ -94,16 +96,20 @@ const ContentName = styled(ThemedText.SubHeaderSmall)`
|
|||||||
max-width: 50%;
|
max-width: 50%;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const ProceedText = styled.span`
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: ${({ theme }) => theme.textSecondary};
|
||||||
|
`
|
||||||
|
|
||||||
const StyledVerifiedIcon = styled(VerifiedIcon)`
|
const StyledVerifiedIcon = styled(VerifiedIcon)`
|
||||||
height: 16px;
|
height: 16px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const StyledLoadingIconBackground = styled(LoadingIcon)`
|
const IconWrapper = styled.div`
|
||||||
height: 14px;
|
|
||||||
width: 14px;
|
|
||||||
stroke: ${({ theme }) => theme.textTertiary};
|
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
`
|
`
|
||||||
@ -122,13 +128,18 @@ interface ListModalSectionProps {
|
|||||||
|
|
||||||
export const ListModalSection = ({ sectionType, active, content, toggleSection }: ListModalSectionProps) => {
|
export const ListModalSection = ({ sectionType, active, content, toggleSection }: ListModalSectionProps) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
const allContentApproved = useMemo(() => !content.some((row) => row.status !== ListingStatus.APPROVED), [content])
|
||||||
const isCollectionApprovalSection = sectionType === Section.APPROVE
|
const isCollectionApprovalSection = sectionType === Section.APPROVE
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<SectionHeader>
|
<SectionHeader>
|
||||||
<Row>
|
<Row>
|
||||||
{active ? <ListingModalWindowActive /> : <ListingModalWindowClosed />}
|
{active || allContentApproved ? (
|
||||||
<SectionTitle active={active} marginLeft="12px">
|
<ListingModalWindowActive fill={allContentApproved ? theme.accentSuccess : theme.accentAction} />
|
||||||
|
) : (
|
||||||
|
<ListingModalWindowClosed />
|
||||||
|
)}
|
||||||
|
<SectionTitle active={active} marginLeft="12px" approved={allContentApproved}>
|
||||||
{isCollectionApprovalSection ? (
|
{isCollectionApprovalSection ? (
|
||||||
<>
|
<>
|
||||||
<Trans>Approve</Trans> {content.length}
|
<Trans>Approve</Trans> {content.length}
|
||||||
@ -165,7 +176,10 @@ export const ListModalSection = ({ sectionType, active, content, toggleSection }
|
|||||||
<ContentRowContainer>
|
<ContentRowContainer>
|
||||||
{content.map((row) => {
|
{content.map((row) => {
|
||||||
return (
|
return (
|
||||||
<ContentRow key={row.name}>
|
<ContentRow
|
||||||
|
key={row.name}
|
||||||
|
active={row.status === ListingStatus.SIGNING || row.status === ListingStatus.APPROVED}
|
||||||
|
>
|
||||||
{isCollectionApprovalSection ? (
|
{isCollectionApprovalSection ? (
|
||||||
<CollectionIcon src={row.images[0]} />
|
<CollectionIcon src={row.images[0]} />
|
||||||
) : (
|
) : (
|
||||||
@ -174,7 +188,23 @@ export const ListModalSection = ({ sectionType, active, content, toggleSection }
|
|||||||
<MarketplaceIcon src={row.images[1]} />
|
<MarketplaceIcon src={row.images[1]} />
|
||||||
<ContentName>{row.name}</ContentName>
|
<ContentName>{row.name}</ContentName>
|
||||||
{isCollectionApprovalSection && (row as CollectionRow).isVerified && <StyledVerifiedIcon />}
|
{isCollectionApprovalSection && (row as CollectionRow).isVerified && <StyledVerifiedIcon />}
|
||||||
<StyledLoadingIconBackground />
|
<IconWrapper>
|
||||||
|
{row.status === ListingStatus.DEFINED || row.status === ListingStatus.PENDING ? (
|
||||||
|
<LoadingIcon
|
||||||
|
height="14px"
|
||||||
|
width="14px"
|
||||||
|
stroke={row.status === ListingStatus.PENDING ? theme.accentAction : theme.textTertiary}
|
||||||
|
/>
|
||||||
|
) : row.status === ListingStatus.SIGNING ? (
|
||||||
|
<ProceedText>
|
||||||
|
<Trans>Proceed in wallet</Trans>
|
||||||
|
</ProceedText>
|
||||||
|
) : (
|
||||||
|
row.status === ListingStatus.APPROVED && (
|
||||||
|
<Check height="20" width="20" stroke={theme.accentSuccess} />
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</IconWrapper>
|
||||||
</ContentRow>
|
</ContentRow>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
@ -12,6 +12,7 @@ interface SellAssetState {
|
|||||||
setAssetListPrice: (asset: WalletAsset, price?: number, marketplace?: ListingMarket) => void
|
setAssetListPrice: (asset: WalletAsset, price?: number, marketplace?: ListingMarket) => void
|
||||||
setGlobalMarketplaces: (marketplaces: ListingMarket[]) => void
|
setGlobalMarketplaces: (marketplaces: ListingMarket[]) => void
|
||||||
removeAssetMarketplace: (asset: WalletAsset, marketplace: ListingMarket) => void
|
removeAssetMarketplace: (asset: WalletAsset, marketplace: ListingMarket) => void
|
||||||
|
// TODO: After merging v2, see if this marketplace logic can be removed
|
||||||
addMarketplaceWarning: (asset: WalletAsset, warning: ListingWarning) => void
|
addMarketplaceWarning: (asset: WalletAsset, warning: ListingWarning) => void
|
||||||
removeMarketplaceWarning: (asset: WalletAsset, warning: ListingWarning, setGlobalOverride?: boolean) => void
|
removeMarketplaceWarning: (asset: WalletAsset, warning: ListingWarning, setGlobalOverride?: boolean) => void
|
||||||
removeAllMarketplaceWarnings: () => void
|
removeAllMarketplaceWarnings: () => void
|
||||||
|
@ -118,8 +118,3 @@ export enum ProfilePageStateType {
|
|||||||
VIEWING,
|
VIEWING,
|
||||||
LISTING,
|
LISTING,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ListingResponse {
|
|
||||||
TRY_AGAIN,
|
|
||||||
SUCCESS,
|
|
||||||
}
|
|
||||||
|
@ -92,7 +92,7 @@ export async function approveCollection(
|
|||||||
// setApprovalForAll() method
|
// setApprovalForAll() method
|
||||||
const ERC721Contract = new Contract(collectionAddress, ERC721, signer)
|
const ERC721Contract = new Contract(collectionAddress, ERC721, signer)
|
||||||
const signerAddress = await signer.getAddress()
|
const signerAddress = await signer.getAddress()
|
||||||
setStatus(ListingStatus.PENDING)
|
|
||||||
try {
|
try {
|
||||||
const approved = await ERC721Contract.isApprovedForAll(signerAddress, operator)
|
const approved = await ERC721Contract.isApprovedForAll(signerAddress, operator)
|
||||||
if (approved) {
|
if (approved) {
|
||||||
@ -160,6 +160,7 @@ export async function signListing(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const order = await executeAllActions()
|
const order = await executeAllActions()
|
||||||
|
setStatus(ListingStatus.PENDING)
|
||||||
const res = await PostOpenSeaSellOrder(order)
|
const res = await PostOpenSeaSellOrder(order)
|
||||||
if (res) setStatus(ListingStatus.APPROVED)
|
if (res) setStatus(ListingStatus.APPROVED)
|
||||||
return res
|
return res
|
||||||
|
Loading…
Reference in New Issue
Block a user