feat: [ListV2] error and warning states (#5921)

* update error message for price inputs

* add grid and button warning states

* add list below floor warning modal

* only warn for prices 20% below floor

* highlight unentered price in red on button press

* missing dependency

* updated modal name and mobile height

* add new file

* fix column presence

* rookie mistake

* bulk zustand imports

* below floor threshold

* move issue check higher

* cleanup mouseEvent

* rename color var

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
This commit is contained in:
Charles Bachmeier 2023-02-06 13:00:29 -08:00 committed by GitHub
parent 5def0dd166
commit 9cac9f8299
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 325 additions and 114 deletions

@ -1,18 +1,25 @@
import { Plural, t } from '@lingui/macro'
import { NftListV2Variant, useNftListV2Flag } from 'featureFlags/flags/nftListV2'
import { useOnClickOutside } from 'hooks/useOnClickOutside' import { useOnClickOutside } from 'hooks/useOnClickOutside'
import ms from 'ms.macro' import ms from 'ms.macro'
import { Box } from 'nft/components/Box' import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex' import { Row } from 'nft/components/Flex'
import { ArrowRightIcon, HazardIcon, LoadingIcon, XMarkIcon } from 'nft/components/icons' import { ArrowRightIcon, HazardIcon, LoadingIcon, XMarkIcon } from 'nft/components/icons'
import { BelowFloorWarningModal } from 'nft/components/profile/list/Modal/BelowFloorWarningModal'
import { bodySmall } from 'nft/css/common.css' import { bodySmall } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css' import { themeVars } from 'nft/css/sprinkles.css'
import { useNFTList, useSellAsset } from 'nft/hooks' import { useNFTList, useSellAsset } from 'nft/hooks'
import { Listing, ListingStatus, WalletAsset } from 'nft/types' import { Listing, ListingStatus, WalletAsset } from 'nft/types'
import { pluralize } from 'nft/utils/roundAndPluralize' import { pluralize } from 'nft/utils/roundAndPluralize'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import { useTheme } from 'styled-components/macro'
import shallow from 'zustand/shallow'
import * as styles from './ListingModal.css' import * as styles from './ListingModal.css'
import { getListings } from './utils' import { getListings } from './utils'
const BELOW_FLOOR_PRICE_THRESHOLD = 0.8
interface ListingButtonProps { interface ListingButtonProps {
onClick: () => void onClick: () => void
buttonText: string buttonText: string
@ -20,18 +27,46 @@ interface ListingButtonProps {
} }
export const ListingButton = ({ onClick, buttonText, showWarningOverride = false }: ListingButtonProps) => { export const ListingButton = ({ onClick, buttonText, showWarningOverride = false }: ListingButtonProps) => {
const sellAssets = useSellAsset((state) => state.sellAssets) const {
const addMarketplaceWarning = useSellAsset((state) => state.addMarketplaceWarning) addMarketplaceWarning,
const removeAllMarketplaceWarnings = useSellAsset((state) => state.removeAllMarketplaceWarnings) sellAssets,
const listingStatus = useNFTList((state) => state.listingStatus) removeAllMarketplaceWarnings,
const setListingStatus = useNFTList((state) => state.setListingStatus) showResolveIssues,
const setListings = useNFTList((state) => state.setListings) toggleShowResolveIssues,
const setCollectionsRequiringApproval = useNFTList((state) => state.setCollectionsRequiringApproval) } = useSellAsset(
({
addMarketplaceWarning,
sellAssets,
removeAllMarketplaceWarnings,
showResolveIssues,
toggleShowResolveIssues,
}) => ({
addMarketplaceWarning,
sellAssets,
removeAllMarketplaceWarnings,
showResolveIssues,
toggleShowResolveIssues,
}),
shallow
)
const { listingStatus, setListingStatus, setListings, setCollectionsRequiringApproval } = useNFTList(
({ listingStatus, setListingStatus, setListings, setCollectionsRequiringApproval }) => ({
listingStatus,
setListingStatus,
setListings,
setCollectionsRequiringApproval,
}),
shallow
)
const isNftListV2 = useNftListV2Flag() === NftListV2Variant.Enabled
const [showWarning, setShowWarning] = useState(false) const [showWarning, setShowWarning] = useState(false)
const [canContinue, setCanContinue] = useState(false) const [canContinue, setCanContinue] = useState(false)
const [issues, setIssues] = useState(0)
const theme = useTheme()
const warningRef = useRef<HTMLDivElement>(null) const warningRef = useRef<HTMLDivElement>(null)
useOnClickOutside(warningRef, () => { useOnClickOutside(warningRef, () => {
setShowWarning(false) !isNftListV2 && setShowWarning(false)
}) })
useEffect(() => { useEffect(() => {
@ -71,13 +106,30 @@ export const ListingButton = ({ onClick, buttonText, showWarningOverride = false
for (const listing of asset.newListings) { for (const listing of asset.newListings) {
if (!listing.price) listingsMissingPrice.push([asset, listing]) if (!listing.price) listingsMissingPrice.push([asset, listing])
else if (isNaN(listing.price) || listing.price < 0) invalidPrices.push([asset, listing]) else if (isNaN(listing.price) || listing.price < 0) invalidPrices.push([asset, listing])
else if (listing.price < (asset?.floorPrice ?? 0) && !listing.overrideFloorPrice) else if (
listing.price < (asset?.floorPrice ?? 0) * BELOW_FLOOR_PRICE_THRESHOLD &&
!listing.overrideFloorPrice
)
listingsBelowFloor.push([asset, listing]) listingsBelowFloor.push([asset, listing])
else if (asset.floor_sell_order_price && listing.price > asset.floor_sell_order_price) else if (asset.floor_sell_order_price && listing.price > asset.floor_sell_order_price)
listingsAboveSellOrderFloor.push([asset, listing]) listingsAboveSellOrderFloor.push([asset, listing])
} }
} }
} }
// set number of issues
if (isNftListV2) {
const foundIssues =
Number(missingExpiration) +
Number(overMaxExpiration) +
listingsMissingPrice.length +
listingsAboveSellOrderFloor.length
setIssues(foundIssues)
!foundIssues && showResolveIssues && toggleShowResolveIssues()
// Only show Resolve Issue text if there was a user submitted error (ie not when page loads with no prices set)
if ((missingExpiration || overMaxExpiration || listingsAboveSellOrderFloor.length) && !showResolveIssues)
toggleShowResolveIssues()
}
const continueCheck = listingsBelowFloor.length === 0 && listingsAboveSellOrderFloor.length === 0 const continueCheck = listingsBelowFloor.length === 0 && listingsAboveSellOrderFloor.length === 0
setCanContinue(continueCheck) setCanContinue(continueCheck)
return [ return [
@ -90,7 +142,7 @@ export const ListingButton = ({ onClick, buttonText, showWarningOverride = false
listingsAboveSellOrderFloor, listingsAboveSellOrderFloor,
invalidPrices, invalidPrices,
] ]
}, [sellAssets]) }, [isNftListV2, sellAssets, showResolveIssues, toggleShowResolveIssues])
const [disableListButton, warningMessage] = useMemo(() => { const [disableListButton, warningMessage] = useMemo(() => {
const disableListButton = const disableListButton =
@ -158,94 +210,108 @@ export const ListingButton = ({ onClick, buttonText, showWarningOverride = false
} }
const warningWrappedClick = () => { const warningWrappedClick = () => {
if ((!disableListButton && canContinue) || showWarningOverride) onClick() if ((!disableListButton && canContinue) || showWarningOverride) {
else addWarningMessages() if (issues && isNftListV2 && !showResolveIssues) toggleShowResolveIssues()
else if (listingsBelowFloor.length) setShowWarning(true)
else onClick()
} else addWarningMessages()
} }
return ( return (
<Box position="relative" width="full"> <>
{!showWarningOverride && showWarning && warningMessage.length > 0 && ( <Box position="relative" width="full">
<Row {!showWarningOverride && showWarning && warningMessage.length > 0 && (
className={`${bodySmall} ${styles.warningTooltip}`} <Row
transition="250" className={`${bodySmall} ${styles.warningTooltip}`}
onClick={() => setShowWarning(false)} transition="250"
color="textSecondary" onClick={() => setShowWarning(false)}
zIndex="3" color="textSecondary"
borderRadius="4" zIndex="3"
backgroundColor="backgroundSurface" borderRadius="4"
height={!disableListButton ? '64' : '36'} backgroundColor="backgroundSurface"
maxWidth="276" height={!disableListButton ? '64' : '36'}
position="absolute" maxWidth="276"
left="24" position="absolute"
bottom="52" left="24"
flexWrap={!disableListButton ? 'wrap' : 'nowrap'} bottom="52"
style={{ maxWidth: !disableListButton ? '225px' : '' }} flexWrap={!disableListButton ? 'wrap' : 'nowrap'}
ref={warningRef} style={{ maxWidth: !disableListButton ? '225px' : '' }}
> ref={warningRef}
<HazardIcon /> >
<Box marginLeft="4" marginRight="8"> <HazardIcon />
{warningMessage} <Box marginLeft="4" marginRight="8">
</Box> {warningMessage}
{disableListButton ? (
<Box paddingTop="6">
<XMarkIcon fill={themeVars.colors.textSecondary} height="20" width="20" />
</Box> </Box>
) : ( {disableListButton ? (
<Row <Box paddingTop="6">
marginLeft="72" <XMarkIcon fill={themeVars.colors.textSecondary} height="20" width="20" />
cursor="pointer" </Box>
color="accentAction" ) : (
onClick={() => { <Row
setShowWarning(false) marginLeft="72"
setCanContinue(true) cursor="pointer"
onClick() color="accentAction"
}} onClick={() => {
> setShowWarning(false)
Continue setCanContinue(true)
<ArrowRightIcon height="20" width="20" /> onClick()
</Row> }}
)} >
</Row> Continue
)} <ArrowRightIcon height="20" width="20" />
<Box </Row>
as="button" )}
border="none"
backgroundColor="accentAction"
cursor={
[ListingStatus.APPROVED, ListingStatus.PENDING, ListingStatus.SIGNING].includes(listingStatus) ||
disableListButton
? 'default'
: 'pointer'
}
color="explicitWhite"
className={styles.button}
onClick={() => listingStatus !== ListingStatus.APPROVED && warningWrappedClick()}
type="button"
style={{
opacity:
![ListingStatus.DEFINED, ListingStatus.FAILED, ListingStatus.CONTINUE].includes(listingStatus) ||
disableListButton
? 0.3
: 1,
}}
>
{listingStatus === ListingStatus.SIGNING || listingStatus === ListingStatus.PENDING ? (
<Row gap="8">
<LoadingIcon stroke="backgroundSurface" height="20" width="20" />
{listingStatus === ListingStatus.PENDING ? 'Pending' : 'Proceed in wallet'}
</Row> </Row>
) : listingStatus === ListingStatus.APPROVED ? (
'Complete!'
) : listingStatus === ListingStatus.PAUSED ? (
'Paused'
) : listingStatus === ListingStatus.FAILED ? (
'Try again'
) : listingStatus === ListingStatus.CONTINUE ? (
'Continue'
) : (
buttonText
)} )}
<Box
as="button"
border="none"
backgroundColor={showResolveIssues ? 'accentFailure' : 'accentAction'}
cursor={
[ListingStatus.APPROVED, ListingStatus.PENDING, ListingStatus.SIGNING].includes(listingStatus) ||
disableListButton
? 'default'
: 'pointer'
}
className={styles.button}
onClick={() => listingStatus !== ListingStatus.APPROVED && warningWrappedClick()}
type="button"
style={{
color: showResolveIssues ? theme.accentTextDarkPrimary : theme.white,
opacity:
![ListingStatus.DEFINED, ListingStatus.FAILED, ListingStatus.CONTINUE].includes(listingStatus) ||
(disableListButton && !showResolveIssues)
? 0.3
: 1,
}}
>
{listingStatus === ListingStatus.SIGNING || listingStatus === ListingStatus.PENDING ? (
<Row gap="8">
<LoadingIcon stroke="backgroundSurface" height="20" width="20" />
{listingStatus === ListingStatus.PENDING ? 'Pending' : 'Proceed in wallet'}
</Row>
) : listingStatus === ListingStatus.APPROVED ? (
'Complete!'
) : listingStatus === ListingStatus.PAUSED ? (
'Paused'
) : listingStatus === ListingStatus.FAILED ? (
'Try again'
) : listingStatus === ListingStatus.CONTINUE ? (
'Continue'
) : showResolveIssues ? (
<Plural value={issues !== 1 ? 2 : 1} _1="Resolve issue" other={t`Resolve ${issues} issues`} />
) : (
buttonText
)}
</Box>
</Box> </Box>
</Box> {showWarning && (
<BelowFloorWarningModal
listingsBelowFloor={listingsBelowFloor}
closeModal={() => setShowWarning(false)}
startListing={onClick}
/>
)}
</>
) )
} }

@ -314,6 +314,7 @@ export const ListPage = () => {
<ListingButton <ListingButton
onClick={handleV2Click} onClick={handleV2Click}
buttonText={anyListingsMissingPrice && !isMobile ? t`Set prices to continue` : t`Start listing`} buttonText={anyListingsMissingPrice && !isMobile ? t`Set prices to continue` : t`Start listing`}
showWarningOverride={true}
/> />
</ListingButtonWrapper> </ListingButtonWrapper>
</ProceedsAndButtonWrapper> </ProceedsAndButtonWrapper>

@ -22,7 +22,7 @@ const PastPriceInfo = styled(Column)`
display: none; display: none;
flex: 1; flex: 1;
@media screen and (min-width: ${BREAKPOINTS.xxl}px) { @media screen and (min-width: ${BREAKPOINTS.xl}px) {
display: flex; display: flex;
} }
` `

@ -0,0 +1,124 @@
import { Plural, t, Trans } from '@lingui/macro'
import { ButtonPrimary } from 'components/Button'
import Column from 'components/Column'
import { Portal } from 'nft/components/common/Portal'
import { Overlay } from 'nft/components/modals/Overlay'
import { Listing, WalletAsset } from 'nft/types'
import React from 'react'
import { AlertTriangle, X } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
import { BREAKPOINTS, ThemedText } from 'theme'
import { Z_INDEX } from 'theme/zIndex'
const ModalWrapper = styled(Column)`
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 420px;
z-index: ${Z_INDEX.modal};
background: ${({ theme }) => theme.backgroundSurface};
border-radius: 20px;
border: 1px solid ${({ theme }) => theme.backgroundOutline};
box-shadow: ${({ theme }) => theme.deepShadow};
padding: 20px 24px 24px;
display: flex;
flex-direction: column;
gap: 12px;
@media screen and (max-width: ${BREAKPOINTS.sm}px) {
width: 100%;
}
`
const CloseIconWrapper = styled.div`
display: flex;
width: 100%;
justify-content: flex-end;
`
const CloseIcon = styled(X)`
cursor: pointer;
&:hover {
opacity: 0.6;
}
`
const HazardIconWrap = styled.div`
display: flex;
width: 100%;
justify-content: center;
align-items: center;
padding: 32px 120px;
`
const ContinueButton = styled(ButtonPrimary)`
font-weight: 600;
font-size: 20px;
line-height: 24px;
margin-top: 12px;
`
const EditListings = styled.span`
font-weight: 600;
font-size: 16px;
line-height: 20px;
color: ${({ theme }) => theme.accentAction};
text-align: center;
cursor: pointer;
padding: 12px 16px;
&:hover {
opacity: 0.6;
}
`
export const BelowFloorWarningModal = ({
listingsBelowFloor,
closeModal,
startListing,
}: {
listingsBelowFloor: [WalletAsset, Listing][]
closeModal: () => void
startListing: () => void
}) => {
const theme = useTheme()
const clickContinue = (e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
startListing()
closeModal()
}
return (
<Portal>
<ModalWrapper>
<CloseIconWrapper>
<CloseIcon width={24} height={24} onClick={closeModal} />{' '}
</CloseIconWrapper>
<HazardIconWrap>
<AlertTriangle height={90} width={90} color={theme.accentCritical} />
</HazardIconWrap>
<ThemedText.HeadlineSmall lineHeight="28px" textAlign="center">
<Trans>Low listing price</Trans>
</ThemedText.HeadlineSmall>
<ThemedText.BodyPrimary textAlign="center">
<Plural
value={listingsBelowFloor.length !== 1 ? 2 : 1}
_1={t`One NFT is listed ${(
(1 - (listingsBelowFloor[0][1].price ?? 0) / (listingsBelowFloor[0][0].floorPrice ?? 0)) *
100
).toFixed(0)}% `}
other={t`${listingsBelowFloor.length} NFTs are listed significantly `}
/>
&nbsp;
<Trans>below the collections floor price. Are you sure you want to continue?</Trans>
</ThemedText.BodyPrimary>
<ContinueButton onClick={clickContinue}>
<Trans>Continue</Trans>
</ContinueButton>
<EditListings onClick={closeModal}>
<Trans>Edit listings</Trans>
</EditListings>
</ModalWrapper>
<Overlay onClick={closeModal} />
</Portal>
)
}

@ -8,6 +8,7 @@ import { useSellAsset } from 'nft/hooks'
import { ListingWarning, WalletAsset } from 'nft/types' import { ListingWarning, WalletAsset } from 'nft/types'
import { formatEth } from 'nft/utils/currency' import { formatEth } from 'nft/utils/currency'
import { Dispatch, FormEvent, useEffect, useRef, useState } from 'react' import { Dispatch, FormEvent, useEffect, useRef, useState } from 'react'
import { AlertTriangle } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro' import styled, { useTheme } from 'styled-components/macro'
import { BREAKPOINTS } from 'theme' import { BREAKPOINTS } from 'theme'
import { colors } from 'theme/colors' import { colors } from 'theme/colors'
@ -42,7 +43,11 @@ const GlobalPriceIcon = styled.div`
background-color: ${({ theme }) => theme.backgroundSurface}; background-color: ${({ theme }) => theme.backgroundSurface};
` `
const WarningMessage = styled(Row)<{ warningType: WarningType }>` const WarningRow = styled(Row)`
gap: 4px;
`
const WarningMessage = styled(Row)<{ $color: string }>`
top: 52px; top: 52px;
width: max-content; width: max-content;
position: absolute; position: absolute;
@ -50,17 +55,16 @@ const WarningMessage = styled(Row)<{ warningType: WarningType }>`
font-weight: 600; font-weight: 600;
font-size: 10px; font-size: 10px;
line-height: 12px; line-height: 12px;
color: ${({ warningType, theme }) => (warningType === WarningType.BELOW_FLOOR ? colors.red400 : theme.textSecondary)}; color: ${({ $color }) => $color};
@media screen and (min-width: ${BREAKPOINTS.md}px) { @media screen and (min-width: ${BREAKPOINTS.md}px) {
right: unset; right: unset;
} }
` `
const WarningAction = styled.div<{ warningType: WarningType }>` const WarningAction = styled.div`
margin-left: 8px;
cursor: pointer; cursor: pointer;
color: ${({ warningType, theme }) => (warningType === WarningType.BELOW_FLOOR ? theme.accentAction : colors.red400)}; color: ${({ theme }) => theme.accentAction};
` `
enum WarningType { enum WarningType {
@ -73,10 +77,10 @@ const getWarningMessage = (warning: WarningType) => {
let message = <></> let message = <></>
switch (warning) { switch (warning) {
case WarningType.BELOW_FLOOR: case WarningType.BELOW_FLOOR:
message = <Trans>LISTING BELOW FLOOR </Trans> message = <Trans>below floor price.</Trans>
break break
case WarningType.ALREADY_LISTED: case WarningType.ALREADY_LISTED:
message = <Trans>ALREADY LISTED FOR </Trans> message = <Trans>Already listed at</Trans>
break break
} }
return message return message
@ -107,6 +111,7 @@ export const PriceTextInput = ({
const [warningType, setWarningType] = useState(WarningType.NONE) const [warningType, setWarningType] = useState(WarningType.NONE)
const removeMarketplaceWarning = useSellAsset((state) => state.removeMarketplaceWarning) const removeMarketplaceWarning = useSellAsset((state) => state.removeMarketplaceWarning)
const removeSellAsset = useSellAsset((state) => state.removeSellAsset) const removeSellAsset = useSellAsset((state) => state.removeSellAsset)
const showResolveIssues = useSellAsset((state) => state.showResolveIssues)
const inputRef = useRef() as React.MutableRefObject<HTMLInputElement> const inputRef = useRef() as React.MutableRefObject<HTMLInputElement>
const theme = useTheme() const theme = useTheme()
@ -121,9 +126,16 @@ export const PriceTextInput = ({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [listPrice]) }, [listPrice])
const borderColor = const percentBelowFloor = (1 - (listPrice ?? 0) / (asset.floorPrice ?? 0)) * 100
warningType !== WarningType.NONE && !focused
const warningColor =
showResolveIssues && !listPrice
? colors.red400 ? colors.red400
: warningType !== WarningType.NONE && !focused
? (warningType === WarningType.BELOW_FLOOR && percentBelowFloor >= 20) ||
warningType === WarningType.ALREADY_LISTED
? colors.red400
: theme.accentWarning
: isGlobalPrice : isGlobalPrice
? theme.accentAction ? theme.accentAction
: listPrice != null : listPrice != null
@ -132,7 +144,7 @@ export const PriceTextInput = ({
return ( return (
<PriceTextInputWrapper> <PriceTextInputWrapper>
<InputWrapper borderColor={borderColor}> <InputWrapper borderColor={warningColor}>
<NumericInput <NumericInput
as="input" as="input"
pattern="[0-9]" pattern="[0-9]"
@ -164,27 +176,27 @@ export const PriceTextInput = ({
</GlobalPriceIcon> </GlobalPriceIcon>
)} )}
</InputWrapper> </InputWrapper>
<WarningMessage warningType={warningType}> <WarningMessage $color={warningColor}>
{warning {warning
? warning.message ? warning.message
: warningType !== WarningType.NONE && ( : warningType !== WarningType.NONE && (
<> <WarningRow>
{getWarningMessage(warningType)} <AlertTriangle height={16} width={16} color={warningColor} />
&nbsp; <span>
{warningType === WarningType.BELOW_FLOOR {warningType === WarningType.BELOW_FLOOR && `${percentBelowFloor.toFixed(0)}% `}
? formatEth(asset?.floorPrice ?? 0) {getWarningMessage(warningType)}
: formatEth(asset?.floor_sell_order_price ?? 0)} &nbsp;
ETH {warningType === WarningType.ALREADY_LISTED && `${formatEth(asset?.floor_sell_order_price ?? 0)} ETH`}
</span>
<WarningAction <WarningAction
warningType={warningType}
onClick={() => { onClick={() => {
warningType === WarningType.ALREADY_LISTED && removeSellAsset(asset) warningType === WarningType.ALREADY_LISTED && removeSellAsset(asset)
setWarningType(WarningType.NONE) setWarningType(WarningType.NONE)
}} }}
> >
{warningType === WarningType.BELOW_FLOOR ? <Trans>DISMISS</Trans> : <Trans>REMOVE ITEM</Trans>} {warningType === WarningType.BELOW_FLOOR ? <Trans>Dismiss</Trans> : <Trans>Remove item</Trans>}
</WarningAction> </WarningAction>
</> </WarningRow>
)} )}
</WarningMessage> </WarningMessage>
</PriceTextInputWrapper> </PriceTextInputWrapper>

@ -5,6 +5,7 @@ import { ListingMarket, ListingWarning, WalletAsset } from '../types'
interface SellAssetState { interface SellAssetState {
sellAssets: WalletAsset[] sellAssets: WalletAsset[]
showResolveIssues: boolean
selectSellAsset: (asset: WalletAsset) => void selectSellAsset: (asset: WalletAsset) => void
removeSellAsset: (asset: WalletAsset) => void removeSellAsset: (asset: WalletAsset) => void
reset: () => void reset: () => void
@ -16,12 +17,14 @@ interface SellAssetState {
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
toggleShowResolveIssues: () => void
} }
export const useSellAsset = create<SellAssetState>()( export const useSellAsset = create<SellAssetState>()(
devtools( devtools(
(set) => ({ (set) => ({
sellAssets: [], sellAssets: [],
showResolveIssues: false,
selectSellAsset: (asset) => selectSellAsset: (asset) =>
set(({ sellAssets }) => { set(({ sellAssets }) => {
if (sellAssets.length === 0) return { sellAssets: [asset] } if (sellAssets.length === 0) return { sellAssets: [asset] }
@ -153,6 +156,11 @@ export const useSellAsset = create<SellAssetState>()(
return { sellAssets: assetsCopy } return { sellAssets: assetsCopy }
}) })
}, },
toggleShowResolveIssues: () => {
set(({ showResolveIssues }) => {
return { showResolveIssues: !showResolveIssues }
})
},
}), }),
{ name: 'useSelectAsset' } { name: 'useSelectAsset' }
) )