Compare commits

...

6 Commits

Author SHA1 Message Date
Jack Short
5ea7b1de3f feat: enable pay with any token (#6002) 2023-02-22 08:25:09 -05:00
Charles Bachmeier
6efe8f3260 feat: [ListV2] Several Small Polish Changes (#5998)
* NFT-1075 update floor and last price to use body primary

* NFT-1080 update image thumbnail size and adjusted row accordingly

* NFT-1081 force page refresh on success screen

* NFT-1089 update same price icons

* NFT-1082 show warning colors over focus

* remove unused state var

* replace margin with padding for sticky header

* NFT-1109 properly calc if listing date is over 6mo

* NFT-1076 change listing button text to light and mobile profile bar to backgroundSurface

* NFT-1079 persist row data when listing to multiple markets

* perf improvement

---------

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2023-02-21 13:56:26 -08:00
lynn
9ac28a4571 fix: token selector scroll margin (#6005)
* fix

* update tests

* remove gql file

* fix breakng snapshot
2023-02-21 16:40:36 -05:00
eddie
bde1421ffb fix: use ipfs token list as fallback for unsupported list (#6007) 2023-02-21 12:55:40 -08:00
eddie
3dceb45d9e fix: use ipfs token list temporarily (#6006)
fix: use ipfs token list as fallback
2023-02-21 12:19:39 -08:00
Jack Short
7b589561bc fix: not using stablecoin to calc price impact pwat (#5997)
* feat: implementing graphql endpoint

* changing from hook to function call

* initial gql routing works

* feat: initial pwatRouting setup

* sending correct amount

* removing console

* it is working

* sufficient balance

* 0 if no inputCurrency

* removing value to send if erc20

* removing console

* permit2 optional flag

* removing not necessary stuff

* mobile fixes

* overlay needs to be here

* changing swap amount to pool reserves

* refactoring routing logic

* no route found button state

* better price loading for insufficient liquidity

* refactoring graphql routing code

* overflow

* initial comments

* resetting bag status on input currency change

* locking

* done

* remove helper text for eth

* fix: pay with any token routing bug

* reordering button

* zindex

* price updated

* keeping debounce

* feat: adding amplitude events for pwat

* bumping analytics version

* types and hooks

* moving erc20 flag to useSendTransaction

* why did i put it in a hook

* no return

* fix: not using usdc value to calculate price impact

* refactor + loading state
2023-02-21 12:55:35 -05:00
17 changed files with 215 additions and 118 deletions

View File

@@ -105,7 +105,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
}
<div
style="padding-right: 8px; padding-top: 8px;"
style="padding-right: 4px;"
>
<div
class="CurrencyList_scrollbarStyle__1pi21y70"
@@ -388,7 +388,7 @@ exports[`renders loading rows when isLoading is true 1`] = `
}
<div
style="padding-right: 8px; padding-top: 8px;"
style="padding-right: 4px;"
>
<div
class="CurrencyList_scrollbarStyle__1pi21y70"

View File

@@ -290,7 +290,7 @@ export default function CurrencyList({
}, [])
return (
<div style={{ paddingRight: '8px', paddingTop: '8px' }}>
<div style={{ paddingRight: '4px' }}>
{isLoading ? (
<FixedSizeList
className={styles.scrollbarStyle}

View File

@@ -1,6 +1,6 @@
export const UNI_LIST = 'https://tokens.uniswap.org'
export const UNI_EXTENDED_LIST = 'https://extendedtokens.uniswap.org/'
const UNI_UNSUPPORTED_LIST = 'https://unsupportedtokens.uniswap.org/'
export const UNI_LIST = 'https://gateway.ipfs.io/ipns/tokens.uniswap.org'
export const UNI_EXTENDED_LIST = 'https://gateway.ipfs.io/ipns/extendedtokens.uniswap.org'
const UNI_UNSUPPORTED_LIST = 'https://gateway.ipfs.io/ipns/unsupportedtokens.uniswap.org'
const AAVE_LIST = 'tokenlist.aave.eth'
const BA_LIST = 'https://raw.githubusercontent.com/The-Blockchain-Association/sec-notice-list/master/ba-sec-list.json'
const CMC_ALL_LIST = 'https://api.coinmarketcap.com/data-api/v3/uniswap/all.json'

View File

@@ -1,7 +1,7 @@
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useGqlRoutingFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.gqlRouting)
return useBaseFlag(FeatureFlag.gqlRouting, BaseVariant.Enabled)
}
export { BaseVariant as GqlRoutingVariant }

View File

@@ -4,7 +4,7 @@ import { useWeb3React } from '@web3-react/core'
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function usePayWithAnyTokenFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.payWithAnyToken)
return useBaseFlag(FeatureFlag.payWithAnyToken, BaseVariant.Enabled)
}
export function usePayWithAnyTokenEnabled(): boolean {

View File

@@ -96,11 +96,6 @@ export enum Currency {
Usd = 'USD'
}
export enum DatasourceProvider {
Alternate = 'ALTERNATE',
Legacy = 'LEGACY'
}
export type Dimensions = {
__typename?: 'Dimensions';
height?: Maybe<Scalars['Float']>;
@@ -145,6 +140,49 @@ export enum MarketSortableField {
Volume = 'VOLUME'
}
export type NftActivity = {
__typename?: 'NftActivity';
address: Scalars['String'];
asset?: Maybe<NftAsset>;
fromAddress: Scalars['String'];
id: Scalars['ID'];
marketplace?: Maybe<NftMarketplace>;
orderStatus?: Maybe<OrderStatus>;
price?: Maybe<Amount>;
quantity?: Maybe<Scalars['Int']>;
timestamp: Scalars['Int'];
toAddress?: Maybe<Scalars['String']>;
tokenId?: Maybe<Scalars['String']>;
transactionHash?: Maybe<Scalars['String']>;
type: NftActivityType;
url?: Maybe<Scalars['String']>;
};
export type NftActivityConnection = {
__typename?: 'NftActivityConnection';
edges: Array<NftActivityEdge>;
pageInfo: PageInfo;
};
export type NftActivityEdge = {
__typename?: 'NftActivityEdge';
cursor: Scalars['String'];
node: NftActivity;
};
export type NftActivityFilterInput = {
activityTypes?: InputMaybe<Array<NftActivityType>>;
address?: InputMaybe<Scalars['String']>;
tokenId?: InputMaybe<Scalars['String']>;
};
export enum NftActivityType {
CancelListing = 'CANCEL_LISTING',
Listing = 'LISTING',
Sale = 'SALE',
Transfer = 'TRANSFER'
}
export type NftApproval = {
__typename?: 'NftApproval';
approvedAddress: Scalars['String'];
@@ -196,7 +234,6 @@ export type NftAssetListingsArgs = {
after?: InputMaybe<Scalars['String']>;
asc?: InputMaybe<Scalars['Boolean']>;
before?: InputMaybe<Scalars['String']>;
datasource?: InputMaybe<DatasourceProvider>;
first?: InputMaybe<Scalars['Int']>;
last?: InputMaybe<Scalars['Int']>;
};
@@ -311,7 +348,6 @@ export type NftCollection = {
export type NftCollectionMarketsArgs = {
currencies: Array<Currency>;
datasource?: InputMaybe<DatasourceProvider>;
};
export type NftCollectionConnection = {
@@ -602,6 +638,7 @@ export type PortfolioTokensTotalDenominatedValueChangeArgs = {
export type Query = {
__typename?: 'Query';
nftActivity?: Maybe<NftActivityConnection>;
nftAssets?: Maybe<NftAssetConnection>;
nftBalances?: Maybe<NftBalanceConnection>;
nftCollections?: Maybe<NftCollectionConnection>;
@@ -617,13 +654,20 @@ export type Query = {
};
export type QueryNftActivityArgs = {
chain?: InputMaybe<Chain>;
cursor?: InputMaybe<Scalars['String']>;
filter?: InputMaybe<NftActivityFilterInput>;
limit?: InputMaybe<Scalars['Int']>;
};
export type QueryNftAssetsArgs = {
address: Scalars['String'];
after?: InputMaybe<Scalars['String']>;
asc?: InputMaybe<Scalars['Boolean']>;
before?: InputMaybe<Scalars['String']>;
chain?: InputMaybe<Chain>;
datasource?: InputMaybe<DatasourceProvider>;
filter?: InputMaybe<NftAssetsFilterInput>;
first?: InputMaybe<Scalars['Int']>;
last?: InputMaybe<Scalars['Int']>;
@@ -636,7 +680,6 @@ export type QueryNftBalancesArgs = {
before?: InputMaybe<Scalars['String']>;
chain?: InputMaybe<Chain>;
cursor?: InputMaybe<Scalars['String']>;
datasource?: InputMaybe<DatasourceProvider>;
filter?: InputMaybe<NftBalancesFilterInput>;
first?: InputMaybe<Scalars['Int']>;
last?: InputMaybe<Scalars['Int']>;
@@ -646,12 +689,10 @@ export type QueryNftBalancesArgs = {
export type QueryNftCollectionsArgs = {
after?: InputMaybe<Scalars['String']>;
before?: InputMaybe<Scalars['String']>;
datasource?: InputMaybe<DatasourceProvider>;
chain?: InputMaybe<Chain>;
cursor?: InputMaybe<Scalars['String']>;
filter?: InputMaybe<NftCollectionsFilterInput>;
first?: InputMaybe<Scalars['Int']>;
last?: InputMaybe<Scalars['Int']>;
limit?: InputMaybe<Scalars['Int']>;
};
@@ -670,7 +711,6 @@ export type QueryNftRouteArgs = {
export type QueryPortfoliosArgs = {
ownerAddresses: Array<Scalars['String']>;
useAltDataSource?: InputMaybe<Scalars['Boolean']>;
};

View File

@@ -4,8 +4,7 @@ import { parseEther } from '@ethersproject/units'
import { t, Trans } from '@lingui/macro'
import { sendAnalyticsEvent, TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, NFTEventName } from '@uniswap/analytics-events'
import { formatPriceImpact } from '@uniswap/conedison/format'
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import Column from 'components/Column'
import Loader from 'components/Loader'
@@ -25,6 +24,7 @@ import { useBag } from 'nft/hooks/useBag'
import useDerivedPayWithAnyTokenSwapInfo from 'nft/hooks/useDerivedPayWithAnyTokenSwapInfo'
import usePayWithAnyTokenSwap from 'nft/hooks/usePayWithAnyTokenSwap'
import usePermit2Approval from 'nft/hooks/usePermit2Approval'
import { PriceImpact, usePriceImpact } from 'nft/hooks/usePriceImpact'
import { useTokenInput } from 'nft/hooks/useTokenInput'
import { useWalletBalance } from 'nft/hooks/useWalletBalance'
import { BagStatus } from 'nft/types'
@@ -35,14 +35,9 @@ import { useToggleWalletModal } from 'state/application/hooks'
import { InterfaceTrade, TradeState } from 'state/routing/types'
import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from 'theme'
import { computeFiatValuePriceImpact } from 'utils/computeFiatValuePriceImpact'
import { warningSeverity } from 'utils/prices'
import { switchChain } from 'utils/switchChain'
import shallow from 'zustand/shallow'
const LOW_SEVERITY_THRESHOLD = 1
const MEDIUM_SEVERITY_THRESHOLD = 3
const FooterContainer = styled.div`
padding: 0px 12px;
`
@@ -124,7 +119,7 @@ const PayButton = styled.button<{ $backgroundColor: string; $color: string }>`
const FiatLoadingBubble = styled(LoadingBubble)`
border-radius: 4px;
width: 4rem;
height: 1rem;
height: 20px;
align-self: end;
`
const PriceImpactContainer = styled(Row)`
@@ -240,13 +235,11 @@ const InputCurrencyValue = ({
const FiatValue = ({
usdcValue,
priceImpact,
priceImpactColor,
tradeState,
usingPayWithAnyToken,
}: {
usdcValue: CurrencyAmount<Token> | null
priceImpact: Percent | undefined
priceImpactColor: string | undefined
priceImpact: PriceImpact | undefined
tradeState: TradeState
usingPayWithAnyToken: boolean
}) => {
@@ -260,13 +253,13 @@ const FiatValue = ({
return (
<PriceImpactContainer>
{priceImpact && priceImpactColor && (
{priceImpact && (
<>
<MouseoverTooltip text={t`The estimated difference between the USD values of input and output amounts.`}>
<PriceImpactRow>
<AlertTriangle color={priceImpactColor} size="16px" />
<ThemedText.BodySmall style={{ color: priceImpactColor }} lineHeight="20px">
(<Trans>{formatPriceImpact(priceImpact)}</Trans>)
<AlertTriangle color={priceImpact.priceImpactSeverity.color} size="16px" />
<ThemedText.BodySmall style={{ color: priceImpact.priceImpactSeverity.color }} lineHeight="20px">
(<Trans>{priceImpact.displayPercentage()}</Trans>)
</ThemedText.BodySmall>
</PriceImpactRow>
</MouseoverTooltip>
@@ -342,30 +335,11 @@ export const BagFooter = ({ totalEthPrice, fetchAssets, eventProperties }: BagFo
shouldUsePayWithAnyToken
)
usePayWithAnyTokenSwap(trade, allowance, allowedSlippage)
const priceImpact = usePriceImpact(trade)
const fiatValueTradeInput = useStablecoinValue(trade?.inputAmount)
const fiatValueTradeOutput = useStablecoinValue(parsedOutputAmount)
const usdcValue = usingPayWithAnyToken ? fiatValueTradeInput : fiatValueTradeOutput
const stablecoinPriceImpact = useMemo(
() =>
tradeState === TradeState.SYNCING || !trade
? undefined
: computeFiatValuePriceImpact(fiatValueTradeInput, fiatValueTradeOutput),
[fiatValueTradeInput, fiatValueTradeOutput, tradeState, trade]
)
const { priceImpactWarning, priceImpactColor } = useMemo(() => {
const severity = warningSeverity(stablecoinPriceImpact)
if (severity < LOW_SEVERITY_THRESHOLD) {
return { priceImpactWarning: false, priceImpactColor: undefined }
}
if (severity < MEDIUM_SEVERITY_THRESHOLD) {
return { priceImpactWarning: false, priceImpactColor: theme.accentWarning }
}
return { priceImpactWarning: true, priceImpactColor: theme.accentCritical }
}, [stablecoinPriceImpact, theme.accentCritical, theme.accentWarning])
const { balance: balanceInEth } = useWalletBalance()
const sufficientBalance = useMemo(() => {
@@ -468,11 +442,11 @@ export const BagFooter = ({ totalEthPrice, fetchAssets, eventProperties }: BagFo
warningTextColor = theme.accentAction
warningText = <Trans>Price updated</Trans>
buttonText = <Trans>Pay</Trans>
} else if (priceImpactWarning && priceImpactColor) {
} else if (priceImpact && priceImpact.priceImpactSeverity.type === 'error') {
disabled = false
buttonColor = priceImpactColor
buttonColor = priceImpact.priceImpactSeverity.color
helperText = <Trans>Price impact warning</Trans>
helperTextColor = priceImpactColor
helperTextColor = priceImpact.priceImpactSeverity.color
buttonText = <Trans>Pay Anyway</Trans>
} else if (sufficientBalance === true) {
disabled = false
@@ -506,8 +480,7 @@ export const BagFooter = ({ totalEthPrice, fetchAssets, eventProperties }: BagFo
usingPayWithAnyToken,
tradeState,
allowance.state,
priceImpactWarning,
priceImpactColor,
priceImpact,
connector,
toggleWalletModal,
setBagExpanded,
@@ -522,6 +495,8 @@ export const BagFooter = ({ totalEthPrice, fetchAssets, eventProperties }: BagFo
...eventProperties,
}
console.log(bagStatus)
return (
<FooterContainer>
<Footer>
@@ -562,8 +537,7 @@ export const BagFooter = ({ totalEthPrice, fetchAssets, eventProperties }: BagFo
</CurrencyRow>
<FiatValue
usdcValue={usdcValue}
priceImpact={stablecoinPriceImpact}
priceImpactColor={priceImpactColor}
priceImpact={priceImpact}
tradeState={tradeState}
usingPayWithAnyToken={usingPayWithAnyToken}
/>
@@ -584,8 +558,7 @@ export const BagFooter = ({ totalEthPrice, fetchAssets, eventProperties }: BagFo
</Row>
<FiatValue
usdcValue={usdcValue}
priceImpact={stablecoinPriceImpact}
priceImpactColor={priceImpactColor}
priceImpact={priceImpact}
tradeState={tradeState}
usingPayWithAnyToken={usingPayWithAnyToken}
/>
@@ -602,7 +575,7 @@ export const BagFooter = ({ totalEthPrice, fetchAssets, eventProperties }: BagFo
<Helper color={helperTextColor}>{helperText}</Helper>
<ActionButton
onClick={handleClick}
disabled={disabled}
disabled={disabled || isPending}
backgroundColor={buttonColor}
textColor={buttonTextColor}
>

View File

@@ -103,8 +103,9 @@ export const ListingButton = ({ onClick, buttonText, showWarningOverride = false
return asset.expirationTime != null && isNaN(asset.expirationTime)
})
const overMaxExpiration = sellAssets.some((asset) => {
return asset.expirationTime != null && asset.expirationTime - Date.now() > ms`180 days`
return asset.expirationTime != null && asset.expirationTime * 1000 - Date.now() > ms`180 days`
})
const listingsMissingPrice: [WalletAsset, Listing][] = []
const listingsBelowFloor: [WalletAsset, Listing][] = []
const listingsAboveSellOrderFloor: [WalletAsset, Listing][] = []
@@ -285,7 +286,7 @@ export const ListingButton = ({ onClick, buttonText, showWarningOverride = false
onClick={() => listingStatus !== ListingStatus.APPROVED && warningWrappedClick()}
type="button"
style={{
color: showResolveIssues ? theme.accentTextDarkPrimary : theme.white,
color: showResolveIssues ? theme.accentTextLightPrimary : theme.white,
opacity:
![ListingStatus.DEFINED, ListingStatus.FAILED, ListingStatus.CONTINUE].includes(listingStatus) ||
(disableListButton && !showResolveIssues)

View File

@@ -240,21 +240,36 @@ export const RarityVerifiedIcon = () => (
</svg>
)
export const EditPriceIcon = (props: SVGProps) => (
<svg {...props} width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.4713 5.06496L13.2161 4.28041C13.5935 3.88317 13.6233 3.33696 13.2459 2.95958L12.9877 2.69144C12.65 2.35378 12.084 2.40344 11.7165 2.77089L10.9419 3.52565L12.4713 5.06496ZM3.10986 13.2347L5.14573 12.3806L11.7463 5.78L10.2169 4.26055L3.61635 10.8711L2.72255 12.8374C2.62324 13.0658 2.88145 13.324 3.10986 13.2347Z"
fill="#70757A"
/>
</svg>
)
export const AttachPriceIcon = (props: SVGProps) => (
<svg {...props} width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M7.76353 9.88671L8.53195 9.10825C7.93931 9.05803 7.51242 8.86216 7.20605 8.5558C6.35728 7.70702 6.35728 6.50669 7.20103 5.66796L8.86844 3.99553C9.71722 3.15178 10.9125 3.14676 11.7613 3.99553C12.6151 4.84932 12.6051 6.04464 11.7663 6.88839L10.9125 7.73716C11.0732 8.10881 11.1285 8.56082 11.0381 8.95256L12.4745 7.5212C13.71 6.29073 13.715 4.53292 12.4694 3.28738C11.2189 2.03682 9.47112 2.04687 8.23563 3.28236L6.48786 5.03013C5.25237 6.26562 5.24735 8.01841 6.49289 9.26394C6.78418 9.56026 7.18597 9.78124 7.76353 9.88671ZM8.23061 5.64285L7.46219 6.42131C8.05483 6.47655 8.48172 6.6674 8.78809 6.97376C9.64188 7.82254 9.63686 9.02287 8.79311 9.86662L7.1257 11.534C6.27693 12.3828 5.08161 12.3828 4.23786 11.534C3.38407 10.6802 3.38909 9.48995 4.23284 8.6462L5.08161 7.7924C4.9209 7.42577 4.87068 6.97376 4.95605 6.577L3.51967 8.01339C2.28418 9.24385 2.27916 10.9966 3.52469 12.2422C4.77525 13.4927 6.52302 13.4827 7.75851 12.2522L9.50628 10.4994C10.7418 9.26394 10.7468 7.51115 9.50126 6.26562C9.20996 5.97432 8.8132 5.75334 8.23061 5.64285Z"
fill="#4673FA"
/>
export const BrokenLinkIcon = (props: SVGProps) => (
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<g clipPath="url(#clip0_79_4612)">
<path
d="M14.4344 11.3181L16.9344 8.81813C17.6934 8.03229 18.1133 6.97978 18.1039 5.8873C18.0944 4.79481 17.6562 3.74976 16.8836 2.97722C16.1111 2.20469 15.066 1.76649 13.9735 1.75699C12.8811 1.7475 11.8286 2.16748 11.0427 2.92647L9.60938 4.35147"
stroke="#98A1C0"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5.20088 8.75098L2.70088 11.251C1.94189 12.0368 1.52191 13.0893 1.53141 14.1818C1.5409 15.2743 1.9791 16.3194 2.75164 17.0919C3.52417 17.8644 4.56922 18.3026 5.66171 18.3121C6.7542 18.3216 7.80671 17.9016 8.59255 17.1426L10.0175 15.7176"
stroke="#98A1C0"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5 3.24316L14.7368 16.6952"
stroke="#98A1C0"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<defs>
<clipPath id="clip0_79_4612">
<rect width="20" height="20" fill="white" transform="translate(0.128906 0.0341797)" />
</clipPath>
</defs>
</svg>
)

View File

@@ -136,13 +136,18 @@ export const MarketplaceRow = ({
toggleExpandMarketplaceRows,
rowHovered,
}: MarketplaceRowProps) => {
const [listPrice, setListPrice] = useState<number>()
const [globalOverride, setGlobalOverride] = useState(false)
const showGlobalPrice = globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride && globalPrice
const setAssetListPrice = useSellAsset((state) => state.setAssetListPrice)
const removeAssetMarketplace = useSellAsset((state) => state.removeAssetMarketplace)
const [marketIconHovered, toggleMarketIconHovered] = useReducer((s) => !s, false)
const [marketRowHovered, toggleMarketRowHovered] = useReducer((s) => !s, false)
const [listPrice, setListPrice] = useState<number | undefined>(
() =>
asset.newListings?.find((listing) =>
expandMarketplaceRows ? listing.marketplace.name === selectedMarkets?.[0].name : !!listing.price
)?.price
)
const [globalOverride, setGlobalOverride] = useState(false)
const showGlobalPrice = globalPriceMethod === SetPriceMethod.SAME_PRICE && !globalOverride && globalPrice
const price = showGlobalPrice ? globalPrice : listPrice
@@ -198,7 +203,7 @@ export const MarketplaceRow = ({
if (!listPrice) setListPrice(globalPrice)
price = listPrice ? listPrice : globalPrice
} else {
price = globalPrice
price = listPrice
}
if (selectedMarkets.length) for (const marketplace of selectedMarkets) setAssetListPrice(asset, price, marketplace)
else setAssetListPrice(asset, price)
@@ -228,14 +233,14 @@ export const MarketplaceRow = ({
return (
<Row onMouseEnter={toggleMarketRowHovered} onMouseLeave={toggleMarketRowHovered}>
<FloorPriceInfo>
<ThemedText.BodySmall color="textSecondary" lineHeight="20px">
<ThemedText.BodyPrimary color="textSecondary" lineHeight="24px">
{asset.floorPrice ? `${asset.floorPrice.toFixed(3)} ETH` : '-'}
</ThemedText.BodySmall>
</ThemedText.BodyPrimary>
</FloorPriceInfo>
<LastPriceInfo>
<ThemedText.BodySmall color="textSecondary" lineHeight="20px">
<ThemedText.BodyPrimary color="textSecondary" lineHeight="24px">
{asset.lastPrice ? `${asset.lastPrice.toFixed(3)} ETH` : '-'}
</ThemedText.BodySmall>
</ThemedText.BodyPrimary>
</LastPriceInfo>
<Row flex="2">

View File

@@ -8,7 +8,7 @@ import { Overlay } from 'nft/components/modals/Overlay'
import { useNFTList, useSellAsset } from 'nft/hooks'
import { ListingStatus } from 'nft/types'
import { fetchPrice } from 'nft/utils'
import { useEffect, useMemo, useReducer, useState } from 'react'
import { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
import { X } from 'react-feather'
import styled from 'styled-components/macro'
import { BREAKPOINTS, ThemedText } from 'theme'
@@ -112,24 +112,28 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [allCollectionsApproved])
const closeModalOnClick = useCallback(() => {
listingStatus === ListingStatus.APPROVED ? window.location.reload() : overlayClick()
}, [listingStatus, overlayClick])
// In the case that a user removes all listings via retry logic, close modal
useEffect(() => {
!listings.length && overlayClick()
}, [listings, overlayClick])
!listings.length && closeModalOnClick()
}, [listings, closeModalOnClick])
return (
<Portal>
<Trace modal={InterfaceModalName.NFT_LISTING}>
<ListModalWrapper>
{listingStatus === ListingStatus.APPROVED ? (
<SuccessScreen overlayClick={overlayClick} />
<SuccessScreen overlayClick={closeModalOnClick} />
) : (
<>
<TitleRow>
<ThemedText.HeadlineSmall lineHeight="28px">
<Trans>List NFTs</Trans>
</ThemedText.HeadlineSmall>
<X size={24} cursor="pointer" onClick={overlayClick} />
<X size={24} cursor="pointer" onClick={closeModalOnClick} />
</TitleRow>
<ListModalSection
sectionType={Section.APPROVE}
@@ -147,7 +151,7 @@ export const ListModal = ({ overlayClick }: { overlayClick: () => void }) => {
)}
</ListModalWrapper>
</Trace>
<Overlay onClick={overlayClick} />
<Overlay onClick={closeModalOnClick} />
</Portal>
)
}

View File

@@ -12,6 +12,8 @@ import { opacify } from 'theme/utils'
import { MarketplaceRow } from './MarketplaceRow'
import { SetPriceMethod } from './NFTListingsGrid'
const IMAGE_THUMBNAIL_SIZE = 60
const NFTListRowWrapper = styled(Row)`
padding: 24px 0px;
align-items: center;
@@ -23,8 +25,8 @@ const NFTListRowWrapper = styled(Row)`
`
const RemoveIconContainer = styled.div`
width: 44px;
height: 44px;
width: ${IMAGE_THUMBNAIL_SIZE}px;
height: ${IMAGE_THUMBNAIL_SIZE}px;
padding-left: 12px;
align-self: flex-start;
align-items: center;
@@ -51,8 +53,8 @@ const NFTInfoWrapper = styled(Row)`
`
const NFTImage = styled.img`
width: 44px;
height: 44px;
width: ${IMAGE_THUMBNAIL_SIZE}px;
height: ${IMAGE_THUMBNAIL_SIZE}px;
border-radius: 8px;
margin-right: 8px;
`
@@ -85,6 +87,7 @@ const MarketPlaceRowWrapper = styled(Column)`
gap: 24px;
flex: 1.5;
margin-right: 12px;
padding: 6px 0px;
@media screen and (min-width: ${BREAKPOINTS.md}px) {
flex: 2;

View File

@@ -28,7 +28,7 @@ const TableHeader = styled.div`
line-height: 20px;
@media screen and (min-width: ${BREAKPOINTS.sm}px) {
margin-left: 48px;
padding-left: 48px;
}
`

View File

@@ -1,14 +1,14 @@
import { Trans } from '@lingui/macro'
import Column from 'components/Column'
import Row from 'components/Row'
import { AttachPriceIcon, EditPriceIcon } from 'nft/components/icons'
import { BrokenLinkIcon } from 'nft/components/icons'
import { NumericInput } from 'nft/components/layout/Input'
import { body } from 'nft/css/common.css'
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 { AlertTriangle, Link } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
import { BREAKPOINTS } from 'theme'
import { colors } from 'theme/colors'
@@ -19,7 +19,7 @@ const PriceTextInputWrapper = styled(Column)`
`
const InputWrapper = styled(Row)<{ borderColor: string }>`
height: 44px;
height: 48px;
color: ${({ theme }) => theme.textTertiary};
padding: 12px;
border: 2px solid;
@@ -34,12 +34,17 @@ const CurrencyWrapper = styled.div<{ listPrice: number | undefined }>`
`
const GlobalPriceIcon = styled.div`
display: block;
display: flex;
cursor: pointer;
position: absolute;
top: -6px;
right: -4px;
bottom: 32px;
right: -10px;
background-color: ${({ theme }) => theme.backgroundSurface};
border-radius: 50%;
height: 28px;
width: 28px;
align-items: center;
justify-content: center;
`
const WarningRow = styled(Row)`
@@ -104,7 +109,6 @@ export const PriceTextInput = ({
warning,
asset,
}: PriceTextInputProps) => {
const [focused, setFocused] = useState(false)
const [warningType, setWarningType] = useState(WarningType.NONE)
const removeMarketplaceWarning = useSellAsset((state) => state.removeMarketplaceWarning)
const removeSellAsset = useSellAsset((state) => state.removeSellAsset)
@@ -128,7 +132,7 @@ export const PriceTextInput = ({
const warningColor =
showResolveIssues && !listPrice
? colors.red400
: warningType !== WarningType.NONE && !focused
: warningType !== WarningType.NONE
? (warningType === WarningType.BELOW_FLOOR && percentBelowFloor >= 20) ||
warningType === WarningType.ALREADY_LISTED
? colors.red400
@@ -151,10 +155,6 @@ export const PriceTextInput = ({
placeholder="0"
backgroundColor="none"
width={{ sm: '54', md: '68' }}
onFocus={() => setFocused(true)}
onBlur={() => {
setFocused(false)
}}
ref={inputRef}
onChange={(v: FormEvent<HTMLInputElement>) => {
if (!listPrice && v.currentTarget.value.includes('.') && parseFloat(v.currentTarget.value) === 0) {
@@ -167,7 +167,7 @@ export const PriceTextInput = ({
<CurrencyWrapper listPrice={listPrice}>&nbsp;ETH</CurrencyWrapper>
{(isGlobalPrice || globalOverride) && (
<GlobalPriceIcon onClick={() => setGlobalOverride(!globalOverride)}>
{globalOverride ? <AttachPriceIcon /> : <EditPriceIcon />}
{globalOverride ? <BrokenLinkIcon /> : <Link size={20} color={warningColor} />}
</GlobalPriceIcon>
)}
</InputWrapper>

View File

@@ -138,7 +138,7 @@ export const ProfilePage = () => {
borderRadius="12"
paddingX="16"
paddingY="12"
background="backgroundModule"
background="backgroundSurface"
borderStyle="solid"
borderColor="backgroundOutline"
borderWidth="1px"

View File

@@ -0,0 +1,50 @@
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { useMemo } from 'react'
import { InterfaceTrade } from 'state/routing/types'
import { useTheme } from 'styled-components/macro'
import { computeRealizedPriceImpact, getPriceImpactWarning } from 'utils/prices'
export interface PriceImpact {
priceImpactSeverity: PriceImpactSeverity
displayPercentage(): string
}
interface PriceImpactSeverity {
type: 'warning' | 'error'
color: string
}
export function usePriceImpact(trade?: InterfaceTrade<Currency, Currency, TradeType>): PriceImpact | undefined {
const theme = useTheme()
return useMemo(() => {
const marketPriceImpact = trade ? computeRealizedPriceImpact(trade) : undefined
const priceImpactWarning = marketPriceImpact ? getPriceImpactWarning(marketPriceImpact) : undefined
const warningColor =
priceImpactWarning === 'error'
? theme.accentCritical
: priceImpactWarning === 'warning'
? theme.accentWarning
: undefined
return marketPriceImpact && priceImpactWarning && warningColor
? {
priceImpactSeverity: {
type: priceImpactWarning,
color: warningColor,
},
displayPercentage: () => toHumanReadablePercent(marketPriceImpact),
}
: undefined
}, [theme.accentCritical, theme.accentWarning, trade])
}
function toHumanReadablePercent(priceImpact: Percent): string {
const sign = priceImpact.lessThan(0) ? '+' : ''
const exactFloat = (Number(priceImpact.numerator) / Number(priceImpact.denominator)) * 100
if (exactFloat < 0.005) {
return '0.00%'
}
const number = parseFloat(priceImpact.multiply(-1)?.toFixed(2))
return `${sign}${number}%`
}

View File

@@ -92,3 +92,9 @@ export function warningSeverity(priceImpact: Percent | undefined): WarningSeveri
}
return 0
}
export function getPriceImpactWarning(priceImpact: Percent): 'warning' | 'error' | undefined {
if (priceImpact.greaterThan(ALLOWED_PRICE_IMPACT_HIGH)) return 'error'
if (priceImpact.greaterThan(ALLOWED_PRICE_IMPACT_MEDIUM)) return 'warning'
return
}