Commit 1df9da9e authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

chore: Refactor ListingButton with deprecation of Listing V1 (#6023)

* remove unused wrapper

* remove old listing modal

* simplify button styling

* deprecate outdated listing logic and move button to styled components

* remove unused linting protection

* remove unused functions from sellAssets hook

* undo and save this refactor for different PR

* remove more unused items

* hook dependencies

* more trash cleanup

* styled continue button

* slight continue button tweaks

* cleanup

* add new standard hover state

* no mixed conditionals

* lint

---------
Co-authored-by: default avatarCharles Bachmeier <charlie@genie.xyz>
parent b1e6d0ab
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { Trans } from '@lingui/macro'
import { sendAnalyticsEvent } from '@uniswap/analytics' import { sendAnalyticsEvent } from '@uniswap/analytics'
import { NFTEventName } from '@uniswap/analytics-events' import { NFTEventName } from '@uniswap/analytics-events'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
...@@ -6,12 +7,10 @@ import { GqlRoutingVariant, useGqlRoutingFlag } from 'featureFlags/flags/gqlRout ...@@ -6,12 +7,10 @@ import { GqlRoutingVariant, useGqlRoutingFlag } from 'featureFlags/flags/gqlRout
import { useNftRouteLazyQuery } from 'graphql/data/__generated__/types-and-hooks' import { useNftRouteLazyQuery } from 'graphql/data/__generated__/types-and-hooks'
import { useIsNftDetailsPage, useIsNftPage, useIsNftProfilePage } from 'hooks/useIsNftPage' import { useIsNftDetailsPage, useIsNftPage, useIsNftProfilePage } from 'hooks/useIsNftPage'
import { BagFooter } from 'nft/components/bag/BagFooter' import { BagFooter } from 'nft/components/bag/BagFooter'
import ListingModal from 'nft/components/bag/profile/ListingModal'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import { Portal } from 'nft/components/common/Portal' import { Portal } from 'nft/components/common/Portal'
import { Column } from 'nft/components/Flex' import { Column } from 'nft/components/Flex'
import { Overlay } from 'nft/components/modals/Overlay' import { Overlay } from 'nft/components/modals/Overlay'
import { buttonTextMedium, commonButtonStyles } from 'nft/css/common.css'
import { import {
useBag, useBag,
useIsMobile, useIsMobile,
...@@ -89,6 +88,24 @@ const DetailsPageBackground = styled.div` ...@@ -89,6 +88,24 @@ const DetailsPageBackground = styled.div`
height: 100%; height: 100%;
` `
const ContinueButton = styled.div`
background: ${({ theme }) => theme.accentAction};
color: ${({ theme }) => theme.accentTextLightPrimary};
margin: 32px 28px 16px;
padding: 10px 0px;
border-radius: 12px;
text-align: center;
font-size: 16px;
font-weight: 600;
line-height: 20px;
cursor: pointer;
transition: ${({ theme }) => theme.transition.duration.medium};
:hover {
opacity: ${({ theme }) => theme.opacity.hover};
}
`
const ScrollingIndicator = ({ top, show }: SeparatorProps) => ( const ScrollingIndicator = ({ top, show }: SeparatorProps) => (
<Box <Box
marginX="24" marginX="24"
...@@ -113,10 +130,7 @@ const Bag = () => { ...@@ -113,10 +130,7 @@ const Bag = () => {
shallow shallow
) )
const { profilePageState, setProfilePageState } = useProfilePageState( const { setProfilePageState } = useProfilePageState(({ setProfilePageState }) => ({ setProfilePageState }))
({ setProfilePageState, state }) => ({ profilePageState: state, setProfilePageState }),
shallow
)
const { const {
bagStatus, bagStatus,
...@@ -396,8 +410,6 @@ const Bag = () => { ...@@ -396,8 +410,6 @@ const Bag = () => {
return ( return (
<Portal> <Portal>
<BagContainer data-testid="nft-bag" raiseZIndex={isMobile || isModalOpen} isProfilePage={isProfilePage}> <BagContainer data-testid="nft-bag" raiseZIndex={isMobile || isModalOpen} isProfilePage={isProfilePage}>
{!(isProfilePage && profilePageState === ProfilePageStateType.LISTING) ? (
<>
<BagHeader <BagHeader
numberOfAssets={isProfilePage ? sellAssets.length : itemsInBag.length} numberOfAssets={isProfilePage ? sellAssets.length : itemsInBag.length}
closeBag={handleCloseBag} closeBag={handleCloseBag}
...@@ -413,15 +425,7 @@ const Bag = () => { ...@@ -413,15 +425,7 @@ const Bag = () => {
<BagFooter totalEthPrice={totalEthPrice} fetchAssets={fetchAssets} eventProperties={eventProperties} /> <BagFooter totalEthPrice={totalEthPrice} fetchAssets={fetchAssets} eventProperties={eventProperties} />
)} )}
{isSellingAssets && isProfilePage && ( {isSellingAssets && isProfilePage && (
<Box <ContinueButton
marginTop="32"
marginX="28"
marginBottom="16"
paddingY="10"
className={`${buttonTextMedium} ${commonButtonStyles}`}
backgroundColor="accentAction"
color="white"
textAlign="center"
onClick={() => { onClick={() => {
toggleBag() toggleBag()
setProfilePageState(ProfilePageStateType.LISTING) setProfilePageState(ProfilePageStateType.LISTING)
...@@ -432,12 +436,8 @@ const Bag = () => { ...@@ -432,12 +436,8 @@ const Bag = () => {
}) })
}} }}
> >
Continue <Trans>Continue</Trans>
</Box> </ContinueButton>
)}
</>
) : (
<ListingModal />
)} )}
</BagContainer> </BagContainer>
......
import { style } from '@vanilla-extract/css'
import { sprinkles } from 'nft/css/sprinkles.css'
export const chevron = style([
sprinkles({
height: '28',
width: '28',
transition: '250',
marginLeft: 'auto',
marginRight: '0',
}),
])
export const chevronDown = style({
transform: 'rotate(180deg)',
cursor: 'pointer',
})
export const sectionDivider = style([
sprinkles({
borderRadius: '20',
marginTop: '8',
width: 'full',
borderWidth: '0.5px',
borderStyle: 'solid',
borderColor: 'backgroundOutline',
}),
])
export const button = style([
sprinkles({
paddingX: { sm: '12', md: '16' },
paddingY: { sm: '10', md: '16' },
textAlign: 'center',
fontWeight: 'semibold',
fontSize: { sm: '16', md: '20' },
lineHeight: { sm: '20', md: '24' },
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'flex-end',
borderRadius: '12',
}),
])
export const listingModalIcon = style([
sprinkles({
borderWidth: '1px',
borderStyle: 'solid',
borderColor: 'backgroundSurface',
}),
{
boxSizing: 'border-box',
marginLeft: '-2px',
marginRight: '4px',
},
])
export const warningTooltip = style([
sprinkles({
paddingTop: '8',
paddingRight: '8',
paddingBottom: '8',
paddingLeft: '12',
}),
{
boxShadow: '0px 4px 16px rgba(10, 10, 59, 0.2)',
},
])
export const listingSectionBorder = style([
sprinkles({
padding: '8',
borderRadius: '8',
borderColor: 'backgroundOutline',
borderStyle: 'solid',
borderWidth: '1px',
}),
])
This diff is collapsed.
import clsx from 'clsx'
import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex'
import { ApprovedCheckmarkIcon, ChevronUpIcon, FailedListingIcon, LoadingIcon } from 'nft/components/icons'
import { badge, bodySmall, buttonTextSmall, subhead } from 'nft/css/common.css'
import { useSellAsset } from 'nft/hooks'
import { AssetRow, CollectionRow, ListingRow, ListingStatus } from 'nft/types'
import { formatEthPrice, numberToWei } from 'nft/utils/currency'
import { useEffect, useState } from 'react'
import * as styles from './ListingModal.css'
export const ListingSection = ({
sectionTitle,
caption = undefined,
title = undefined,
rows,
index,
openIndex,
isSuccessScreen = false,
}: {
sectionTitle: string
caption?: string
title?: string
rows: AssetRow[]
index: number
openIndex: number
isSuccessScreen?: boolean
}) => {
const [isOpen, setIsOpen] = useState(false)
const notAllApproved = rows.some((row: AssetRow) => row.status !== ListingStatus.APPROVED)
const sellAssets = useSellAsset((state) => state.sellAssets)
const removeAssetMarketplace = useSellAsset((state) => state.removeAssetMarketplace)
const removeRow = (row: any) => {
// collections
if (index === 1) {
for (const asset of sellAssets)
if (asset.asset_contract.address === row.collectionAddress) removeAssetMarketplace(asset, row.marketplace)
}
// listings
else removeAssetMarketplace(row.asset, row.marketplace)
}
useEffect(() => {
setIsOpen(index === openIndex)
}, [index, openIndex])
function getListingRowPrice(row: AssetRow): number | undefined {
const listingRow = row as ListingRow
const newListings = listingRow.asset.newListings
return newListings?.find((listing) => listing.marketplace.name === listingRow.marketplace.name)?.price ?? 0
}
const allApproved = !notAllApproved && rows.length > 0 && !isSuccessScreen
return (
<Row
flexWrap="wrap"
className={subhead}
marginTop="10"
marginBottom="10"
onClick={() => rows.length > 0 && setIsOpen(!isOpen)}
color={allApproved ? 'accentSuccess' : 'textPrimary'}
>
{allApproved && <ApprovedCheckmarkIcon style={{ marginRight: '8px' }} />}
{sectionTitle}
{!isSuccessScreen && <ChevronUpIcon className={clsx(`${isOpen ? '' : styles.chevronDown} ${styles.chevron}`)} />}
{(isOpen || isSuccessScreen) && (
<Column
gap="12"
width="full"
paddingTop={isSuccessScreen ? '28' : 'auto'}
className={clsx(!isSuccessScreen && styles.listingSectionBorder)}
>
{caption && (
<Box color="textPrimary" fontWeight="normal" className={caption}>
{caption}
</Box>
)}
{title && (
<Box color="textSecondary" className={badge}>
{title}
</Box>
)}
<Column gap="8">
{rows.map((row: AssetRow, index) => {
return (
<Column key={index} gap="8">
<Row>
{row.images?.map((image, index) => {
return (
<Box
as="img"
height="20"
width="20"
borderRadius={index === 0 && (row as CollectionRow).collectionAddress ? 'round' : '4'}
style={{ zIndex: 2 - index }}
className={styles.listingModalIcon}
src={image}
alt={row.name}
key={index}
/>
)
})}
<Box
marginLeft="8"
marginRight="auto"
fontWeight="normal"
color="textPrimary"
textOverflow="ellipsis"
overflow="hidden"
whiteSpace="nowrap"
maxWidth={{
sm: 'max',
md:
row.status === ListingStatus.REJECTED || row.status === ListingStatus.FAILED ? '120' : 'full',
}}
className={bodySmall}
>
{row.name}
</Box>
{isSuccessScreen ? (
getListingRowPrice(row) &&
`${formatEthPrice(numberToWei(getListingRowPrice(row) ?? 0).toString())} ETH`
) : row.status === ListingStatus.APPROVED ? (
<ApprovedCheckmarkIcon height="20" width="20" />
) : row.status === ListingStatus.FAILED || row.status === ListingStatus.REJECTED ? (
<Row gap="4">
<Box fontWeight="normal" fontSize="14" color="textSecondary">
{row.status}
</Box>
<FailedListingIcon />
</Row>
) : (
row.status === ListingStatus.SIGNING && <LoadingIcon height="20" width="20" stroke="#4673FA" />
)}
</Row>
{(row.status === ListingStatus.FAILED || row.status === ListingStatus.REJECTED) && (
<Row gap="8" justifyContent="center">
<Box
width="120"
as="button"
className={buttonTextSmall}
borderRadius="12"
border="none"
color="red400"
height="32"
cursor="pointer"
style={{ backgroundColor: '#FA2B391A' }}
onClick={async (e) => {
e.stopPropagation()
removeRow(row)
}}
>
Remove
</Box>
<Box
width="120"
as="button"
className={buttonTextSmall}
borderRadius="12"
border="none"
color="accentAction"
height="32"
cursor="pointer"
style={{ backgroundColor: '#4C82FB29' }}
onClick={async (e) => {
e.stopPropagation()
if (row.callback) {
await row.callback()
}
}}
>
Try again
</Box>
</Row>
)}
</Column>
)
})}
</Column>
</Column>
)}
</Row>
)
}
...@@ -2,30 +2,8 @@ import type { JsonRpcSigner, Web3Provider } from '@ethersproject/providers' ...@@ -2,30 +2,8 @@ import type { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
import { addressesByNetwork, SupportedChainId } from '@looksrare/sdk' 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 { CollectionRow, ListingMarket, ListingRow, ListingStatus, WalletAsset } from 'nft/types'
import { approveCollection, LOOKS_RARE_CREATOR_BASIS_POINTS, signListing } from 'nft/utils/listNfts' import { approveCollection, LOOKS_RARE_CREATOR_BASIS_POINTS, signListing } from 'nft/utils/listNfts'
import { Dispatch } from 'react'
const updateStatus = ({
listing,
newStatus,
rows,
setRows,
callback,
}: {
listing: AssetRow
newStatus: ListingStatus
rows: AssetRow[]
setRows: Dispatch<AssetRow[]>
callback?: () => Promise<void>
}) => {
const rowsCopy = [...rows]
const index = rows.findIndex((n) => n === listing)
listing.status = newStatus
if (callback) listing.callback = callback
rowsCopy[index] = listing
setRows(rowsCopy)
}
export async function approveCollectionRow( export async function approveCollectionRow(
collectionRow: CollectionRow, collectionRow: CollectionRow,
...@@ -211,27 +189,3 @@ export const getListingState = ( ...@@ -211,27 +189,3 @@ export const getListingState = (
export const verifyStatus = (status: ListingStatus) => { export const verifyStatus = (status: ListingStatus) => {
return status !== ListingStatus.PAUSED && status !== ListingStatus.APPROVED return status !== ListingStatus.PAUSED && status !== ListingStatus.APPROVED
} }
export const pauseRow = (row: AssetRow, rows: AssetRow[], setRows: Dispatch<AssetRow[]>) => {
if (row.status === ListingStatus.PENDING || row.status === ListingStatus.DEFINED)
updateStatus({
listing: row,
newStatus: ListingStatus.PAUSED,
rows,
setRows,
})
}
export const resetRow = (row: AssetRow, rows: AssetRow[], setRows: Dispatch<AssetRow[]>) => {
if (
row.status === ListingStatus.PAUSED ||
row.status === ListingStatus.FAILED ||
row.status === ListingStatus.REJECTED
)
updateStatus({
listing: row,
newStatus: ListingStatus.DEFINED,
rows,
setRows,
})
}
...@@ -304,30 +304,6 @@ export const ClockIcon = () => ( ...@@ -304,30 +304,6 @@ export const ClockIcon = () => (
</svg> </svg>
) )
export const LoadingIcon = (props: SVGProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
width="100px"
height="100px"
viewBox="0 0 100 100"
preserveAspectRatio="xMidYMid"
{...props}
>
<circle cx="50" cy="50" fill="none" strokeWidth="10" r="35" strokeDasharray="164.93361431346415 56.97787143782138">
<animateTransform
attributeName="transform"
type="rotate"
repeatCount="indefinite"
dur="1s"
values="0 50 50;360 50 50"
keyTimes="0;1"
{...props}
></animateTransform>
</circle>
</svg>
)
export const ApprovedCheckmarkIcon = (props: SVGProps) => ( export const ApprovedCheckmarkIcon = (props: SVGProps) => (
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}> <svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path <path
...@@ -337,15 +313,6 @@ export const ApprovedCheckmarkIcon = (props: SVGProps) => ( ...@@ -337,15 +313,6 @@ export const ApprovedCheckmarkIcon = (props: SVGProps) => (
</svg> </svg>
) )
export const FailedListingIcon = (props: SVGProps) => (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M9.9933 16.2444C13.5529 16.2444 16.4909 13.3064 16.4909 9.75307C16.4909 6.19978 13.5466 3.26172 9.98703 3.26172C6.43373 3.26172 3.50195 6.19978 3.50195 9.75307C3.50195 13.3064 6.44001 16.2444 9.9933 16.2444ZM8.12877 12.3207C7.78976 12.3207 7.62653 12.1324 7.62653 11.8624V7.63742C7.62653 7.36747 7.78976 7.17913 8.12877 7.17913H8.80678C9.14579 7.17913 9.30901 7.36747 9.30901 7.63742V11.8624C9.30901 12.1324 9.14579 12.3207 8.80678 12.3207H8.12877ZM11.1798 12.3207C10.8471 12.3207 10.6776 12.1324 10.6776 11.8624V7.63742C10.6776 7.36747 10.8471 7.17913 11.1798 7.17913H11.8641C12.1906 7.17913 12.3538 7.36747 12.3538 7.63742V11.8624C12.3538 12.1324 12.1906 12.3207 11.8641 12.3207H11.1798Z"
fill={themeVars.colors.textSecondary}
/>
</svg>
)
export const FilterIcon = (props: SVGProps) => ( export const FilterIcon = (props: SVGProps) => (
<svg width="20" height="20" viewBox="1 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}> <svg width="20" height="20" viewBox="1 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path <path
......
import { t, Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics' import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics'
import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events' import { InterfaceModalName, NFTEventName } from '@uniswap/analytics-events'
import { useWeb3React } from '@web3-react/core' import { useWeb3React } from '@web3-react/core'
import Column from 'components/Column' import Column from 'components/Column'
import Row from 'components/Row' import Row from 'components/Row'
import { ListingButton } from 'nft/components/bag/profile/ListingButton'
import { approveCollectionRow, getListingState, getTotalEthValue, verifyStatus } from 'nft/components/bag/profile/utils' import { approveCollectionRow, getListingState, getTotalEthValue, verifyStatus } from 'nft/components/bag/profile/utils'
import { ListingButton } from 'nft/components/profile/list/ListingButton'
import { useIsMobile, useNFTList, useProfilePageState, useSellAsset } from 'nft/hooks' import { useIsMobile, useNFTList, useProfilePageState, useSellAsset } from 'nft/hooks'
import { LIST_PAGE_MARGIN, LIST_PAGE_MARGIN_MOBILE } from 'nft/pages/profile/shared' import { LIST_PAGE_MARGIN, LIST_PAGE_MARGIN_MOBILE } from 'nft/pages/profile/shared'
import { looksRareNonceFetcher } from 'nft/queries' import { looksRareNonceFetcher } from 'nft/queries'
...@@ -212,7 +212,6 @@ export const ListPage = () => { ...@@ -212,7 +212,6 @@ export const ListPage = () => {
) )
const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets]) const totalEthListingValue = useMemo(() => getTotalEthValue(sellAssets), [sellAssets])
const anyListingsMissingPrice = useMemo(() => !!listings.find((listing) => !listing.price), [listings])
const [showListModal, toggleShowListModal] = useReducer((s) => !s, false) const [showListModal, toggleShowListModal] = useReducer((s) => !s, false)
const [selectedMarkets, setSelectedMarkets] = useState([ListingMarkets[0]]) // default marketplace: x2y2 const [selectedMarkets, setSelectedMarkets] = useState([ListingMarkets[0]]) // default marketplace: x2y2
const [ethPriceInUSD, setEthPriceInUSD] = useState(0) const [ethPriceInUSD, setEthPriceInUSD] = useState(0)
...@@ -269,7 +268,7 @@ export const ListPage = () => { ...@@ -269,7 +268,7 @@ export const ListPage = () => {
} }
} }
const handleV2Click = () => { const showModalAndStartListing = () => {
toggleShowListModal() toggleShowListModal()
startListingFlow() startListingFlow()
} }
...@@ -322,11 +321,7 @@ export const ListPage = () => { ...@@ -322,11 +321,7 @@ export const ListPage = () => {
<UsdValue>{formatUsdPrice(totalEthListingValue * ethPriceInUSD)}</UsdValue> <UsdValue>{formatUsdPrice(totalEthListingValue * ethPriceInUSD)}</UsdValue>
)} )}
</ProceedsWrapper> </ProceedsWrapper>
<ListingButton <ListingButton onClick={showModalAndStartListing} />
onClick={handleV2Click}
buttonText={anyListingsMissingPrice && !isMobile ? t`Set prices to continue` : t`Start listing`}
showWarningOverride={true}
/>
</ProceedsAndButtonWrapper> </ProceedsAndButtonWrapper>
</FloatingConfirmationBar> </FloatingConfirmationBar>
<Overlay /> <Overlay />
......
...@@ -18,27 +18,10 @@ export const subheadSmall = sprinkles({ fontWeight: 'medium', fontSize: '14', li ...@@ -18,27 +18,10 @@ export const subheadSmall = sprinkles({ fontWeight: 'medium', fontSize: '14', li
export const body = sprinkles({ fontWeight: 'normal', fontSize: '16', lineHeight: '24' }) export const body = sprinkles({ fontWeight: 'normal', fontSize: '16', lineHeight: '24' })
export const bodySmall = sprinkles({ fontWeight: 'normal', fontSize: '14', lineHeight: '20' }) export const bodySmall = sprinkles({ fontWeight: 'normal', fontSize: '14', lineHeight: '20' })
export const caption = sprinkles({ fontWeight: 'normal', fontSize: '12', lineHeight: '16' }) export const caption = sprinkles({ fontWeight: 'normal', fontSize: '12', lineHeight: '16' })
export const badge = sprinkles({ fontWeight: 'semibold', fontSize: '10', lineHeight: '12' })
export const buttonTextMedium = sprinkles({ fontWeight: 'semibold', fontSize: '16', lineHeight: '20' }) export const buttonTextMedium = sprinkles({ fontWeight: 'semibold', fontSize: '16', lineHeight: '20' })
export const buttonTextSmall = sprinkles({ fontWeight: 'semibold', fontSize: '14', lineHeight: '16' }) export const buttonTextSmall = sprinkles({ fontWeight: 'semibold', fontSize: '14', lineHeight: '16' })
export const commonButtonStyles = style([
sprinkles({
borderRadius: '12',
transition: '250',
}),
{
border: 'none',
':hover': {
cursor: 'pointer',
},
':disabled': {
cursor: 'auto',
},
},
])
const magicalGradient = style({ const magicalGradient = style({
selectors: { selectors: {
'&::before': { '&::before': {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment