Commit 9cac9f82 authored by Charles Bachmeier's avatar Charles Bachmeier Committed by GitHub

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: default avatarCharles Bachmeier <charlie@genie.xyz>
parent 5def0dd1
......@@ -314,6 +314,7 @@ export const ListPage = () => {
<ListingButton
onClick={handleV2Click}
buttonText={anyListingsMissingPrice && !isMobile ? t`Set prices to continue` : t`Start listing`}
showWarningOverride={true}
/>
</ListingButtonWrapper>
</ProceedsAndButtonWrapper>
......
......@@ -22,7 +22,7 @@ const PastPriceInfo = styled(Column)`
display: none;
flex: 1;
@media screen and (min-width: ${BREAKPOINTS.xxl}px) {
@media screen and (min-width: ${BREAKPOINTS.xl}px) {
display: flex;
}
`
......
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 collection’s 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 { formatEth } from 'nft/utils/currency'
import { Dispatch, FormEvent, useEffect, useRef, useState } from 'react'
import { AlertTriangle } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
import { colors } from 'theme/colors'
......@@ -42,7 +43,11 @@ const GlobalPriceIcon = styled.div`
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;
width: max-content;
position: absolute;
......@@ -50,17 +55,16 @@ const WarningMessage = styled(Row)<{ warningType: WarningType }>`
font-weight: 600;
font-size: 10px;
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) {
right: unset;
}
`
const WarningAction = styled.div<{ warningType: WarningType }>`
margin-left: 8px;
const WarningAction = styled.div`
cursor: pointer;
color: ${({ warningType, theme }) => (warningType === WarningType.BELOW_FLOOR ? theme.accentAction : colors.red400)};
color: ${({ theme }) => theme.accentAction};
`
enum WarningType {
......@@ -73,10 +77,10 @@ const getWarningMessage = (warning: WarningType) => {
let message = <></>
switch (warning) {
case WarningType.BELOW_FLOOR:
message = <Trans>LISTING BELOW FLOOR </Trans>
message = <Trans>below floor price.</Trans>
break
case WarningType.ALREADY_LISTED:
message = <Trans>ALREADY LISTED FOR </Trans>
message = <Trans>Already listed at</Trans>
break
}
return message
......@@ -107,6 +111,7 @@ export const PriceTextInput = ({
const [warningType, setWarningType] = useState(WarningType.NONE)
const removeMarketplaceWarning = useSellAsset((state) => state.removeMarketplaceWarning)
const removeSellAsset = useSellAsset((state) => state.removeSellAsset)
const showResolveIssues = useSellAsset((state) => state.showResolveIssues)
const inputRef = useRef() as React.MutableRefObject<HTMLInputElement>
const theme = useTheme()
......@@ -121,9 +126,16 @@ export const PriceTextInput = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [listPrice])
const borderColor =
warningType !== WarningType.NONE && !focused
const percentBelowFloor = (1 - (listPrice ?? 0) / (asset.floorPrice ?? 0)) * 100
const warningColor =
showResolveIssues && !listPrice
? colors.red400
: warningType !== WarningType.NONE && !focused
? (warningType === WarningType.BELOW_FLOOR && percentBelowFloor >= 20) ||
warningType === WarningType.ALREADY_LISTED
? colors.red400
: theme.accentWarning
: isGlobalPrice
? theme.accentAction
: listPrice != null
......@@ -132,7 +144,7 @@ export const PriceTextInput = ({
return (
<PriceTextInputWrapper>
<InputWrapper borderColor={borderColor}>
<InputWrapper borderColor={warningColor}>
<NumericInput
as="input"
pattern="[0-9]"
......@@ -164,27 +176,27 @@ export const PriceTextInput = ({
</GlobalPriceIcon>
)}
</InputWrapper>
<WarningMessage warningType={warningType}>
<WarningMessage $color={warningColor}>
{warning
? warning.message
: warningType !== WarningType.NONE && (
<>
{getWarningMessage(warningType)}
&nbsp;
{warningType === WarningType.BELOW_FLOOR
? formatEth(asset?.floorPrice ?? 0)
: formatEth(asset?.floor_sell_order_price ?? 0)}
ETH
<WarningRow>
<AlertTriangle height={16} width={16} color={warningColor} />
<span>
{warningType === WarningType.BELOW_FLOOR && `${percentBelowFloor.toFixed(0)}% `}
{getWarningMessage(warningType)}
&nbsp;
{warningType === WarningType.ALREADY_LISTED && `${formatEth(asset?.floor_sell_order_price ?? 0)} ETH`}
</span>
<WarningAction
warningType={warningType}
onClick={() => {
warningType === WarningType.ALREADY_LISTED && removeSellAsset(asset)
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>
</>
</WarningRow>
)}
</WarningMessage>
</PriceTextInputWrapper>
......
......@@ -5,6 +5,7 @@ import { ListingMarket, ListingWarning, WalletAsset } from '../types'
interface SellAssetState {
sellAssets: WalletAsset[]
showResolveIssues: boolean
selectSellAsset: (asset: WalletAsset) => void
removeSellAsset: (asset: WalletAsset) => void
reset: () => void
......@@ -16,12 +17,14 @@ interface SellAssetState {
addMarketplaceWarning: (asset: WalletAsset, warning: ListingWarning) => void
removeMarketplaceWarning: (asset: WalletAsset, warning: ListingWarning, setGlobalOverride?: boolean) => void
removeAllMarketplaceWarnings: () => void
toggleShowResolveIssues: () => void
}
export const useSellAsset = create<SellAssetState>()(
devtools(
(set) => ({
sellAssets: [],
showResolveIssues: false,
selectSellAsset: (asset) =>
set(({ sellAssets }) => {
if (sellAssets.length === 0) return { sellAssets: [asset] }
......@@ -153,6 +156,11 @@ export const useSellAsset = create<SellAssetState>()(
return { sellAssets: assetsCopy }
})
},
toggleShowResolveIssues: () => {
set(({ showResolveIssues }) => {
return { showResolveIssues: !showResolveIssues }
})
},
}),
{ name: 'useSelectAsset' }
)
......
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