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:
Charles Bachmeier 2023-02-01 13:41:50 -08:00 committed by GitHub
parent 35a03e2681
commit 48833f27e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 219 additions and 68 deletions

@ -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>&nbsp;{content.length}&nbsp; <Trans>Approve</Trans>&nbsp;{content.length}&nbsp;
@ -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