feat: [ListV2] Add retry and remove logic to listings and collection approvals (#5923)
* add remove/retry buttons * add retry logic functionality * add scroll to active row * properly update rejected status * replace loadingicon with loader --------- Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
This commit is contained in:
parent
7229637c4c
commit
5def0dd166
181
src/nft/components/profile/list/Modal/ContentRow.tsx
Normal file
181
src/nft/components/profile/list/Modal/ContentRow.tsx
Normal file
@ -0,0 +1,181 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import Column from 'components/Column'
|
||||
import Loader from 'components/Loader'
|
||||
import Row from 'components/Row'
|
||||
import { VerifiedIcon } from 'nft/components/icons'
|
||||
import { AssetRow, CollectionRow, ListingStatus } from 'nft/types'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { Check, XOctagon } from 'react-feather'
|
||||
import styled, { css, useTheme } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { opacify } from 'theme/utils'
|
||||
|
||||
const ContentColumn = styled(Column)<{ failed: boolean }>`
|
||||
background-color: ${({ theme, failed }) => failed && opacify(12, theme.accentCritical)};
|
||||
border-radius: 12px;
|
||||
padding-bottom: ${({ failed }) => failed && '16px'};
|
||||
`
|
||||
|
||||
const ContentRowWrapper = styled(Row)<{ active: boolean; failed: boolean }>`
|
||||
padding: 16px;
|
||||
border: ${({ failed, theme }) => !failed && `1px solid ${theme.backgroundOutline}`};
|
||||
border-radius: 12px;
|
||||
opacity: ${({ active, failed }) => (active || failed ? '1' : '0.6')};
|
||||
`
|
||||
|
||||
const CollectionIcon = styled.img`
|
||||
border-radius: 100px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
z-index: 1;
|
||||
`
|
||||
|
||||
const AssetIcon = styled.img`
|
||||
border-radius: 4px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
z-index: 1;
|
||||
`
|
||||
|
||||
const MarketplaceIcon = styled.img`
|
||||
border-radius: 4px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-left: -4px;
|
||||
margin-right: 12px;
|
||||
`
|
||||
|
||||
const ContentName = styled(ThemedText.SubHeaderSmall)`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 40%;
|
||||
`
|
||||
|
||||
const ProceedText = styled.span`
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
|
||||
const FailedText = styled.span`
|
||||
font-weight: 600;
|
||||
font-size: 10px;
|
||||
line-height: 12px;
|
||||
color: ${({ theme }) => theme.accentCritical};
|
||||
margin-left: 4px;
|
||||
`
|
||||
|
||||
const StyledVerifiedIcon = styled(VerifiedIcon)`
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-left: 4px;
|
||||
`
|
||||
|
||||
const IconWrapper = styled.div`
|
||||
margin-left: auto;
|
||||
margin-right: 0px;
|
||||
`
|
||||
|
||||
const ButtonRow = styled(Row)`
|
||||
padding: 0px 16px;
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const failedButtonStyle = css`
|
||||
width: 152px;
|
||||
cursor: pointer;
|
||||
padding: 8px 0px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
`
|
||||
|
||||
const RemoveButton = styled.button`
|
||||
background-color: ${({ theme }) => theme.accentCritical};
|
||||
color: ${({ theme }) => theme.accentTextDarkPrimary};
|
||||
${failedButtonStyle}
|
||||
`
|
||||
|
||||
const RetryButton = styled.button`
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
${failedButtonStyle}
|
||||
`
|
||||
|
||||
export const ContentRow = ({
|
||||
row,
|
||||
isCollectionApprovalSection,
|
||||
removeRow,
|
||||
}: {
|
||||
row: AssetRow
|
||||
isCollectionApprovalSection: boolean
|
||||
removeRow: (row: AssetRow) => void
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const rowRef = useRef<HTMLDivElement>()
|
||||
const failed = row.status === ListingStatus.FAILED || row.status === ListingStatus.REJECTED
|
||||
|
||||
useEffect(() => {
|
||||
row.status === ListingStatus.SIGNING && rowRef.current?.scroll
|
||||
}, [row.status])
|
||||
|
||||
return (
|
||||
<ContentColumn failed={failed}>
|
||||
<ContentRowWrapper
|
||||
active={row.status === ListingStatus.SIGNING || row.status === ListingStatus.APPROVED}
|
||||
failed={failed}
|
||||
ref={rowRef}
|
||||
>
|
||||
{isCollectionApprovalSection ? <CollectionIcon src={row.images[0]} /> : <AssetIcon src={row.images[0]} />}
|
||||
<MarketplaceIcon src={row.images[1]} />
|
||||
<ContentName>{row.name}</ContentName>
|
||||
{isCollectionApprovalSection && (row as CollectionRow).isVerified && <StyledVerifiedIcon />}
|
||||
<IconWrapper>
|
||||
{row.status === ListingStatus.DEFINED || row.status === ListingStatus.PENDING ? (
|
||||
<Loader
|
||||
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} />
|
||||
) : (
|
||||
failed && (
|
||||
<Row>
|
||||
<XOctagon height="20" width="20" color={theme.accentCritical} />
|
||||
<FailedText>
|
||||
{row.status === ListingStatus.FAILED ? <Trans>Failed</Trans> : <Trans>Rejected</Trans>}
|
||||
</FailedText>
|
||||
</Row>
|
||||
)
|
||||
)}
|
||||
</IconWrapper>
|
||||
</ContentRowWrapper>
|
||||
{failed && (
|
||||
<ButtonRow justify="space-between">
|
||||
<RemoveButton onClick={() => removeRow(row)}>
|
||||
<Trans>Remove</Trans>
|
||||
</RemoveButton>
|
||||
<RetryButton onClick={row.callback}>
|
||||
<Trans>Retry</Trans>
|
||||
</RetryButton>
|
||||
</ButtonRow>
|
||||
)}
|
||||
</ContentColumn>
|
||||
)
|
||||
}
|
@ -92,6 +92,11 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [allCollectionsApproved])
|
||||
|
||||
// In the case that a user removes all listings via retry logic, close modal
|
||||
useEffect(() => {
|
||||
!listings.length && overlayClick()
|
||||
}, [listings, overlayClick])
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Trace modal={InterfaceModalName.NFT_LISTING}>
|
||||
|
@ -3,21 +3,18 @@ import Column from 'components/Column'
|
||||
import { ScrollBarStyles } from 'components/Common'
|
||||
import Row from 'components/Row'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import {
|
||||
ChevronUpIcon,
|
||||
ListingModalWindowActive,
|
||||
ListingModalWindowClosed,
|
||||
LoadingIcon,
|
||||
VerifiedIcon,
|
||||
} from 'nft/components/icons'
|
||||
import { AssetRow, CollectionRow, ListingStatus } from 'nft/types'
|
||||
import { ChevronUpIcon, ListingModalWindowActive, ListingModalWindowClosed } from 'nft/components/icons'
|
||||
import { useSellAsset } from 'nft/hooks'
|
||||
import { AssetRow, CollectionRow, ListingRow, ListingStatus } from 'nft/types'
|
||||
import { useMemo } from 'react'
|
||||
import { Check, Info } from 'react-feather'
|
||||
import { Info } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { colors } from 'theme/colors'
|
||||
import { TRANSITION_DURATIONS } from 'theme/styles'
|
||||
|
||||
import { ContentRow } from './ContentRow'
|
||||
|
||||
const SectionHeader = styled(Row)`
|
||||
justify-content: space-between;
|
||||
`
|
||||
@ -56,62 +53,7 @@ const StyledInfoIcon = styled(Info)`
|
||||
|
||||
const ContentRowContainer = styled(Column)`
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
const ContentRow = styled(Row)<{ active: boolean }>`
|
||||
padding: 16px;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
border-radius: 12px;
|
||||
opacity: ${({ active }) => (active ? '1' : '0.6')};
|
||||
`
|
||||
|
||||
const CollectionIcon = styled.img`
|
||||
border-radius: 100px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
z-index: 1;
|
||||
`
|
||||
|
||||
const AssetIcon = styled.img`
|
||||
border-radius: 4px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
z-index: 1;
|
||||
`
|
||||
|
||||
const MarketplaceIcon = styled.img`
|
||||
border-radius: 4px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-left: -4px;
|
||||
margin-right: 12px;
|
||||
`
|
||||
|
||||
const ContentName = styled(ThemedText.SubHeaderSmall)`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
max-width: 50%;
|
||||
`
|
||||
|
||||
const ProceedText = styled.span`
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
|
||||
const StyledVerifiedIcon = styled(VerifiedIcon)`
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-left: 4px;
|
||||
`
|
||||
|
||||
const IconWrapper = styled.div`
|
||||
margin-left: auto;
|
||||
margin-right: 0px;
|
||||
scroll-behavior: smooth;
|
||||
`
|
||||
|
||||
export const enum Section {
|
||||
@ -128,8 +70,24 @@ interface ListModalSectionProps {
|
||||
|
||||
export const ListModalSection = ({ sectionType, active, content, toggleSection }: ListModalSectionProps) => {
|
||||
const theme = useTheme()
|
||||
const sellAssets = useSellAsset((state) => state.sellAssets)
|
||||
const removeAssetMarketplace = useSellAsset((state) => state.removeAssetMarketplace)
|
||||
const allContentApproved = useMemo(() => !content.some((row) => row.status !== ListingStatus.APPROVED), [content])
|
||||
const isCollectionApprovalSection = sectionType === Section.APPROVE
|
||||
const removeRow = (row: AssetRow) => {
|
||||
// collections
|
||||
if (isCollectionApprovalSection) {
|
||||
const collectionRow = row as CollectionRow
|
||||
for (const asset of sellAssets)
|
||||
if (asset.asset_contract.address === collectionRow.collectionAddress)
|
||||
removeAssetMarketplace(asset, collectionRow.marketplace)
|
||||
}
|
||||
// listings
|
||||
else {
|
||||
const listingRow = row as ListingRow
|
||||
removeAssetMarketplace(listingRow.asset, listingRow.marketplace)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Column>
|
||||
<SectionHeader>
|
||||
@ -174,40 +132,14 @@ export const ListModalSection = ({ sectionType, active, content, toggleSection }
|
||||
</Row>
|
||||
)}
|
||||
<ContentRowContainer>
|
||||
{content.map((row) => {
|
||||
return (
|
||||
<ContentRow
|
||||
key={row.name}
|
||||
active={row.status === ListingStatus.SIGNING || row.status === ListingStatus.APPROVED}
|
||||
>
|
||||
{isCollectionApprovalSection ? (
|
||||
<CollectionIcon src={row.images[0]} />
|
||||
) : (
|
||||
<AssetIcon src={row.images[0]} />
|
||||
)}
|
||||
<MarketplaceIcon src={row.images[1]} />
|
||||
<ContentName>{row.name}</ContentName>
|
||||
{isCollectionApprovalSection && (row as CollectionRow).isVerified && <StyledVerifiedIcon />}
|
||||
<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>
|
||||
)
|
||||
})}
|
||||
{content.map((row: AssetRow) => (
|
||||
<ContentRow
|
||||
row={row}
|
||||
key={row.name}
|
||||
removeRow={removeRow}
|
||||
isCollectionApprovalSection={isCollectionApprovalSection}
|
||||
/>
|
||||
))}
|
||||
</ContentRowContainer>
|
||||
</SectionBody>
|
||||
)}
|
||||
|
@ -47,6 +47,8 @@ export const useNFTList = create<NFTListState>()(
|
||||
return ListingStatus.APPROVED
|
||||
case ListingStatus.FAILED:
|
||||
return listing.status === ListingStatus.SIGNING ? ListingStatus.SIGNING : ListingStatus.FAILED
|
||||
case ListingStatus.REJECTED:
|
||||
return listing.status === ListingStatus.SIGNING ? ListingStatus.SIGNING : ListingStatus.REJECTED
|
||||
default:
|
||||
return listing.status
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user