feat: new review design (#6451)
* test: swap flow cypress tests * fix: use default parameter * feat: use Swap Component on TDP * feat: auto nav for TDP tokens * chore: merge * chore: merge * chore: merge * chore: merge * fix: remove extra inputCurrency URL parsing logic * fix: undo last change * fix: pass expected chain id to swap component * fix: search for default tokens on unconnected networks if needed * test: e2e test for l2 token * fix: delete irrelevant tests * fix: address comments * fix: lint error * test: update TDP e2e tests * fix: use pageChainId for filter * fix: rename chainId * fix: typecheck * fix: chainId bug * fix: chainId required fixes * fix: bad merge in e2e test * fix: remove unused test util * fix: remove unnecessary variable * fix: token defaults * fix: address comments * fix: address comments and fix tests * fix: e2e test formatting, remove Maybe<> * fix: remove unused variable * fix: use feature flag for swap component on TDP * fix: back button * feat: copy review screen UI from widgetg * fix: modal padding * feat: add final detail row * fix: remove widget comment * fix: update unit tests * fix: code style consistency * fix: remove padding from AutoColumn * fix: update snapshots * fix: use semantic gaps * fix: more px and gaps * fix: design feedbacks * fix: button radius in summary modal * fix: design nits * feat: update design of summary modal * fix: font weight and vertical spacing * fix: update snapshots * fix: css nits * fix: modal flicker when refetching trade * fix: comments * fix: code style improvements * feat: require trade to be defined * fix: remove extra props from ThemedTexts * fix: one more trans * fix: remove unused export * feat: remove undefined checks and other fixes * fix: update test * fix: add missing dollar sign * fix: remove null check and update test * fix: remove max width from detail row value * fix: remove isOpen prop * fix: isopen
This commit is contained in:
parent
1f755e8b0d
commit
504e09d3dc
@ -296,7 +296,7 @@ export function ButtonConfirmed({
|
||||
}
|
||||
}
|
||||
|
||||
export function ButtonError({ error, ...rest }: { error?: boolean } & ButtonProps) {
|
||||
export function ButtonError({ error, ...rest }: { error?: boolean } & BaseButtonProps) {
|
||||
if (error) {
|
||||
return <ButtonErrorStyle {...rest} />
|
||||
} else {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import styled, { DefaultTheme } from 'styled-components/macro'
|
||||
|
||||
type Gap = keyof DefaultTheme['grids']
|
||||
import styled from 'styled-components/macro'
|
||||
import { Gap } from 'theme'
|
||||
|
||||
export const Column = styled.div<{
|
||||
gap?: Gap
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Box } from 'rebass/styled-components'
|
||||
import styled, { DefaultTheme } from 'styled-components/macro'
|
||||
|
||||
type Gap = keyof DefaultTheme['grids']
|
||||
import styled from 'styled-components/macro'
|
||||
import { Gap } from 'theme'
|
||||
|
||||
// TODO(WEB-3289):
|
||||
// Setting `width: 100%` by default prevents composability in complex flex layouts.
|
||||
@ -14,7 +13,7 @@ const Row = styled(Box)<{
|
||||
padding?: string
|
||||
border?: string
|
||||
borderRadius?: string
|
||||
gap?: string
|
||||
gap?: Gap | string
|
||||
}>`
|
||||
width: ${({ width }) => width ?? '100%'};
|
||||
display: flex;
|
||||
|
@ -9,6 +9,7 @@ import { useRef } from 'react'
|
||||
import { useModalIsOpen, useToggleSettingsMenu } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Divider } from 'theme'
|
||||
|
||||
import MaxSlippageSettings from './MaxSlippageSettings'
|
||||
import MenuButton from './MenuButton'
|
||||
@ -40,14 +41,6 @@ const MenuFlyout = styled(AutoColumn)`
|
||||
padding: 1rem;
|
||||
`
|
||||
|
||||
const Divider = styled.div`
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border-width: 0;
|
||||
margin: 0;
|
||||
background-color: ${({ theme }) => theme.backgroundOutline};
|
||||
`
|
||||
|
||||
export default function SettingsTab({ autoSlippage }: { autoSlippage: Percent }) {
|
||||
const { chainId } = useWeb3React()
|
||||
const showDeadlineSettings = Boolean(chainId && !L2_CHAIN_IDS.includes(chainId))
|
||||
|
@ -20,7 +20,7 @@ import { TransactionSummary } from '../AccountDetails/TransactionSummary'
|
||||
import { ButtonLight, ButtonPrimary } from '../Button'
|
||||
import { AutoColumn, ColumnCenter } from '../Column'
|
||||
import Modal from '../Modal'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import Row, { RowBetween, RowFixed } from '../Row'
|
||||
import AnimatedConfirmation from './AnimatedConfirmation'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
@ -28,16 +28,12 @@ const Wrapper = styled.div`
|
||||
border-radius: 20px;
|
||||
outline: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
`
|
||||
const Section = styled(AutoColumn)<{ inline?: boolean }>`
|
||||
padding: ${({ inline }) => (inline ? '0' : '0')};
|
||||
padding: 16px;
|
||||
`
|
||||
|
||||
const BottomSection = styled(Section)`
|
||||
const BottomSection = styled(AutoColumn)`
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
padding-bottom: 10px;
|
||||
`
|
||||
|
||||
const ConfirmedIcon = styled(ColumnCenter)<{ inline?: boolean }>`
|
||||
@ -50,6 +46,10 @@ const StyledLogo = styled.img`
|
||||
margin-left: 6px;
|
||||
`
|
||||
|
||||
const ConfirmationModalContentWrapper = styled(AutoColumn)`
|
||||
padding-bottom: 12px;
|
||||
`
|
||||
|
||||
function ConfirmationPendingContent({
|
||||
onDismiss,
|
||||
pendingText,
|
||||
@ -59,8 +59,6 @@ function ConfirmationPendingContent({
|
||||
pendingText: ReactNode
|
||||
inline?: boolean // not in modal
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<AutoColumn gap="md">
|
||||
@ -74,15 +72,15 @@ function ConfirmationPendingContent({
|
||||
<CustomLightSpinner src={Circle} alt="loader" size={inline ? '40px' : '90px'} />
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="md" justify="center">
|
||||
<Text fontWeight={500} fontSize={20} color={theme.textPrimary} textAlign="center">
|
||||
<ThemedText.SubHeaderLarge color="textPrimary" textAlign="center">
|
||||
<Trans>Waiting for confirmation</Trans>
|
||||
</Text>
|
||||
<Text fontWeight={600} fontSize={16} color={theme.textPrimary} textAlign="center">
|
||||
</ThemedText.SubHeaderLarge>
|
||||
<ThemedText.SubHeader color="textPrimary" textAlign="center">
|
||||
{pendingText}
|
||||
</Text>
|
||||
<Text fontWeight={400} fontSize={12} color={theme.textSecondary} textAlign="center" marginBottom="12px">
|
||||
</ThemedText.SubHeader>
|
||||
<ThemedText.SubHeaderSmall color="textSecondary" textAlign="center" marginBottom="12px">
|
||||
<Trans>Confirm this transaction in your wallet</Trans>
|
||||
</Text>
|
||||
</ThemedText.SubHeaderSmall>
|
||||
</AutoColumn>
|
||||
</AutoColumn>
|
||||
</Wrapper>
|
||||
@ -125,7 +123,7 @@ function TransactionSubmittedContent({
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Section inline={inline}>
|
||||
<AutoColumn>
|
||||
{!inline && (
|
||||
<RowBetween>
|
||||
<div />
|
||||
@ -135,7 +133,7 @@ function TransactionSubmittedContent({
|
||||
<ConfirmedIcon inline={inline}>
|
||||
<ArrowUpCircle strokeWidth={1} size={inline ? '40px' : '75px'} color={theme.accentActive} />
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="md" justify="center" style={{ paddingBottom: '12px' }}>
|
||||
<ConfirmationModalContentWrapper gap="md" justify="center">
|
||||
<ThemedText.MediumHeader textAlign="center">
|
||||
<Trans>Transaction submitted</Trans>
|
||||
</ThemedText.MediumHeader>
|
||||
@ -154,19 +152,19 @@ function TransactionSubmittedContent({
|
||||
</ButtonLight>
|
||||
)}
|
||||
<ButtonPrimary onClick={onDismiss} style={{ margin: '20px 0 0 0' }} data-testid="dismiss-tx-confirmation">
|
||||
<Text fontWeight={600} fontSize={20} color={theme.accentTextLightPrimary}>
|
||||
<ThemedText.HeadlineSmall color={theme.accentTextLightPrimary}>
|
||||
{inline ? <Trans>Return</Trans> : <Trans>Close</Trans>}
|
||||
</Text>
|
||||
</ThemedText.HeadlineSmall>
|
||||
</ButtonPrimary>
|
||||
{chainId && hash && (
|
||||
<ExternalLink href={getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)}>
|
||||
<Text fontWeight={600} fontSize={14} color={theme.accentAction}>
|
||||
<ThemedText.Link color={theme.accentAction}>
|
||||
<Trans>View on {chainId === SupportedChainId.MAINNET ? 'Etherscan' : 'Block Explorer'}</Trans>
|
||||
</Text>
|
||||
</ThemedText.Link>
|
||||
</ExternalLink>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</Section>
|
||||
</ConfirmationModalContentWrapper>
|
||||
</AutoColumn>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@ -184,15 +182,15 @@ export function ConfirmationModalContent({
|
||||
}) {
|
||||
return (
|
||||
<Wrapper>
|
||||
<Section>
|
||||
<RowBetween>
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
{title}
|
||||
</Text>
|
||||
<AutoColumn gap="sm">
|
||||
<Row>
|
||||
<Row justify="center" marginLeft="24px">
|
||||
<ThemedText.SubHeader>{title}</ThemedText.SubHeader>
|
||||
</Row>
|
||||
<CloseIcon onClick={onDismiss} data-cy="confirmation-close-icon" />
|
||||
</RowBetween>
|
||||
</Row>
|
||||
{topContent()}
|
||||
</Section>
|
||||
</AutoColumn>
|
||||
{bottomContent && <BottomSection gap="12px">{bottomContent()}</BottomSection>}
|
||||
</Wrapper>
|
||||
)
|
||||
@ -202,7 +200,7 @@ export function TransactionErrorContent({ message, onDismiss }: { message: React
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<Wrapper>
|
||||
<Section>
|
||||
<AutoColumn>
|
||||
<RowBetween>
|
||||
<Text fontWeight={600} fontSize={16}>
|
||||
<Trans>Error</Trans>
|
||||
@ -213,7 +211,7 @@ export function TransactionErrorContent({ message, onDismiss }: { message: React
|
||||
<AlertTriangle color={theme.accentCritical} style={{ strokeWidth: 1 }} size={90} />
|
||||
<ThemedText.MediumHeader textAlign="center">{message}</ThemedText.MediumHeader>
|
||||
</AutoColumn>
|
||||
</Section>
|
||||
</AutoColumn>
|
||||
<BottomSection gap="12px">
|
||||
<ButtonPrimary onClick={onDismiss}>
|
||||
<Trans>Dismiss</Trans>
|
||||
@ -252,7 +250,7 @@ function L2Content({
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Section inline={inline}>
|
||||
<AutoColumn>
|
||||
{!inline && (
|
||||
<RowBetween mb="16px">
|
||||
<Badge>
|
||||
@ -277,7 +275,7 @@ function L2Content({
|
||||
)}
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="md" justify="center">
|
||||
<Text fontWeight={500} fontSize={20} textAlign="center">
|
||||
<ThemedText.SubHeaderLarge textAlign="center">
|
||||
{!hash ? (
|
||||
<Trans>Confirm transaction in wallet</Trans>
|
||||
) : !confirmed ? (
|
||||
@ -287,20 +285,20 @@ function L2Content({
|
||||
) : (
|
||||
<Trans>Error</Trans>
|
||||
)}
|
||||
</Text>
|
||||
<Text fontWeight={400} fontSize={16} textAlign="center">
|
||||
</ThemedText.SubHeaderLarge>
|
||||
<ThemedText.BodySecondary textAlign="center">
|
||||
{transaction ? <TransactionSummary info={transaction.info} /> : pendingText}
|
||||
</Text>
|
||||
</ThemedText.BodySecondary>
|
||||
{chainId && hash ? (
|
||||
<ExternalLink href={getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)}>
|
||||
<Text fontWeight={500} fontSize={14} color={theme.accentAction}>
|
||||
<ThemedText.SubHeaderSmall color={theme.accentAction}>
|
||||
<Trans>View on Explorer</Trans>
|
||||
</Text>
|
||||
</ThemedText.SubHeaderSmall>
|
||||
</ExternalLink>
|
||||
) : (
|
||||
<div style={{ height: '17px' }} />
|
||||
)}
|
||||
<Text color={theme.textTertiary} style={{ margin: '20px 0 0 0' }} fontSize="14px">
|
||||
<ThemedText.SubHeaderSmall color={theme.textTertiary} marginTop="20px">
|
||||
{!secondsToConfirm ? (
|
||||
<div style={{ height: '24px' }} />
|
||||
) : (
|
||||
@ -311,14 +309,14 @@ function L2Content({
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</Text>
|
||||
</ThemedText.SubHeaderSmall>
|
||||
<ButtonPrimary onClick={onDismiss} style={{ margin: '4px 0 0 0' }}>
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
<ThemedText.SubHeaderLarge>
|
||||
{inline ? <Trans>Return</Trans> : <Trans>Close</Trans>}
|
||||
</Text>
|
||||
</ThemedText.SubHeaderLarge>
|
||||
</ButtonPrimary>
|
||||
</AutoColumn>
|
||||
</Section>
|
||||
</AutoColumn>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Trace } from '@uniswap/analytics'
|
||||
import { InterfaceModalName } from '@uniswap/analytics-events'
|
||||
import { sendAnalyticsEvent, Trace } from '@uniswap/analytics'
|
||||
import { InterfaceModalName, SwapEventName, SwapPriceUpdateUserResponse } from '@uniswap/analytics-events'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { ReactNode, useCallback, useMemo, useState } from 'react'
|
||||
import { getPriceUpdateBasisPoints } from 'lib/utils/analytics'
|
||||
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import { formatSwapPriceUpdatedEventProperties } from 'utils/loggingFormatters'
|
||||
import { tradeMeaningfullyDiffers } from 'utils/tradeMeaningFullyDiffer'
|
||||
|
||||
import TransactionConfirmationModal, {
|
||||
@ -20,21 +22,17 @@ export default function ConfirmSwapModal({
|
||||
allowedSlippage,
|
||||
onConfirm,
|
||||
onDismiss,
|
||||
recipient,
|
||||
swapErrorMessage,
|
||||
isOpen,
|
||||
attemptingTxn,
|
||||
txHash,
|
||||
swapQuoteReceivedDate,
|
||||
fiatValueInput,
|
||||
fiatValueOutput,
|
||||
}: {
|
||||
isOpen: boolean
|
||||
trade: InterfaceTrade | undefined
|
||||
trade: InterfaceTrade
|
||||
originalTrade: InterfaceTrade | undefined
|
||||
attemptingTxn: boolean
|
||||
txHash: string | undefined
|
||||
recipient: string | null
|
||||
allowedSlippage: Percent
|
||||
onAcceptChanges: () => void
|
||||
onConfirm: () => void
|
||||
@ -44,35 +42,34 @@ export default function ConfirmSwapModal({
|
||||
fiatValueInput: { data?: number; isLoading: boolean }
|
||||
fiatValueOutput: { data?: number; isLoading: boolean }
|
||||
}) {
|
||||
// shouldLogModalCloseEvent lets the child SwapModalHeader component know when modal has been closed
|
||||
// and an event triggered by modal closing should be logged.
|
||||
const [shouldLogModalCloseEvent, setShouldLogModalCloseEvent] = useState(false)
|
||||
const showAcceptChanges = useMemo(
|
||||
() => Boolean(trade && originalTrade && tradeMeaningfullyDiffers(trade, originalTrade)),
|
||||
() => Boolean(originalTrade && tradeMeaningfullyDiffers(trade, originalTrade)),
|
||||
[originalTrade, trade]
|
||||
)
|
||||
|
||||
const [lastExecutionPrice, setLastExecutionPrice] = useState(trade?.executionPrice)
|
||||
const [priceUpdate, setPriceUpdate] = useState<number>()
|
||||
useEffect(() => {
|
||||
if (lastExecutionPrice && !trade.executionPrice.equalTo(lastExecutionPrice)) {
|
||||
setPriceUpdate(getPriceUpdateBasisPoints(lastExecutionPrice, trade.executionPrice))
|
||||
setLastExecutionPrice(trade.executionPrice)
|
||||
}
|
||||
}, [lastExecutionPrice, setLastExecutionPrice, trade])
|
||||
|
||||
const onModalDismiss = useCallback(() => {
|
||||
if (isOpen) setShouldLogModalCloseEvent(true)
|
||||
sendAnalyticsEvent(
|
||||
SwapEventName.SWAP_PRICE_UPDATE_ACKNOWLEDGED,
|
||||
formatSwapPriceUpdatedEventProperties(trade, priceUpdate, SwapPriceUpdateUserResponse.REJECTED)
|
||||
)
|
||||
onDismiss()
|
||||
}, [isOpen, onDismiss])
|
||||
}, [onDismiss, priceUpdate, trade])
|
||||
|
||||
const modalHeader = useCallback(() => {
|
||||
return trade ? (
|
||||
<SwapModalHeader
|
||||
trade={trade}
|
||||
shouldLogModalCloseEvent={shouldLogModalCloseEvent}
|
||||
setShouldLogModalCloseEvent={setShouldLogModalCloseEvent}
|
||||
allowedSlippage={allowedSlippage}
|
||||
recipient={recipient}
|
||||
showAcceptChanges={showAcceptChanges}
|
||||
onAcceptChanges={onAcceptChanges}
|
||||
/>
|
||||
) : null
|
||||
}, [allowedSlippage, onAcceptChanges, recipient, showAcceptChanges, trade, shouldLogModalCloseEvent])
|
||||
return <SwapModalHeader trade={trade} allowedSlippage={allowedSlippage} />
|
||||
}, [allowedSlippage, trade])
|
||||
|
||||
const modalBottom = useCallback(() => {
|
||||
return trade ? (
|
||||
return (
|
||||
<SwapModalFooter
|
||||
onConfirm={onConfirm}
|
||||
trade={trade}
|
||||
@ -83,25 +80,28 @@ export default function ConfirmSwapModal({
|
||||
swapQuoteReceivedDate={swapQuoteReceivedDate}
|
||||
fiatValueInput={fiatValueInput}
|
||||
fiatValueOutput={fiatValueOutput}
|
||||
showAcceptChanges={showAcceptChanges}
|
||||
onAcceptChanges={onAcceptChanges}
|
||||
/>
|
||||
) : null
|
||||
)
|
||||
}, [
|
||||
trade,
|
||||
onConfirm,
|
||||
txHash,
|
||||
allowedSlippage,
|
||||
showAcceptChanges,
|
||||
swapErrorMessage,
|
||||
trade,
|
||||
allowedSlippage,
|
||||
txHash,
|
||||
swapQuoteReceivedDate,
|
||||
fiatValueInput,
|
||||
fiatValueOutput,
|
||||
onAcceptChanges,
|
||||
])
|
||||
|
||||
// text to show while loading
|
||||
const pendingText = (
|
||||
<Trans>
|
||||
Swapping {trade?.inputAmount?.toSignificant(6)} {trade?.inputAmount?.currency?.symbol} for{' '}
|
||||
{trade?.outputAmount?.toSignificant(6)} {trade?.outputAmount?.currency?.symbol}
|
||||
Swapping {trade.inputAmount.toSignificant(6)} {trade.inputAmount.currency?.symbol} for{' '}
|
||||
{trade.outputAmount.toSignificant(6)} {trade.outputAmount.currency?.symbol}
|
||||
</Trans>
|
||||
)
|
||||
|
||||
@ -111,7 +111,7 @@ export default function ConfirmSwapModal({
|
||||
<TransactionErrorContent onDismiss={onModalDismiss} message={swapErrorMessage} />
|
||||
) : (
|
||||
<ConfirmationModalContent
|
||||
title={<Trans>Confirm Swap</Trans>}
|
||||
title={<Trans>Review Swap</Trans>}
|
||||
onDismiss={onModalDismiss}
|
||||
topContent={modalHeader}
|
||||
bottomContent={modalBottom}
|
||||
@ -123,13 +123,13 @@ export default function ConfirmSwapModal({
|
||||
return (
|
||||
<Trace modal={InterfaceModalName.CONFIRM_SWAP}>
|
||||
<TransactionConfirmationModal
|
||||
isOpen={isOpen}
|
||||
isOpen
|
||||
onDismiss={onModalDismiss}
|
||||
attemptingTxn={attemptingTxn}
|
||||
hash={txHash}
|
||||
content={confirmationContent}
|
||||
pendingText={pendingText}
|
||||
currencyToAdd={trade?.outputAmount.currency}
|
||||
currencyToAdd={trade.outputAmount.currency}
|
||||
/>
|
||||
</Trace>
|
||||
)
|
||||
|
@ -1,27 +1,103 @@
|
||||
import { TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT } from 'test-utils/constants'
|
||||
import { render, screen } from 'test-utils/render'
|
||||
import { TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT, TEST_TRADE_EXACT_OUTPUT } from 'test-utils/constants'
|
||||
import { render, screen, within } from 'test-utils/render'
|
||||
|
||||
import SwapModalFooter from './SwapModalFooter'
|
||||
|
||||
const swapErrorMessage = 'swap error'
|
||||
const fiatValue = { data: 123, isLoading: false }
|
||||
|
||||
describe('SwapModalFooter.tsx', () => {
|
||||
it('renders with a disabled button with no account', () => {
|
||||
it('matches base snapshot, test trade exact input', () => {
|
||||
const { asFragment } = render(
|
||||
<SwapModalFooter
|
||||
trade={TEST_TRADE_EXACT_INPUT}
|
||||
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
|
||||
hash={undefined}
|
||||
onConfirm={() => null}
|
||||
disabledConfirm
|
||||
swapErrorMessage={swapErrorMessage}
|
||||
onConfirm={jest.fn()}
|
||||
swapErrorMessage={undefined}
|
||||
disabledConfirm={false}
|
||||
swapQuoteReceivedDate={undefined}
|
||||
fiatValueInput={fiatValue}
|
||||
fiatValueOutput={fiatValue}
|
||||
fiatValueInput={{
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
}}
|
||||
fiatValueOutput={{
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
}}
|
||||
showAcceptChanges={false}
|
||||
onAcceptChanges={jest.fn()}
|
||||
/>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
expect(screen.getByTestId('confirm-swap-button')).toBeDisabled()
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
'The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert.'
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText('The fee paid to miners who process your transaction. This must be paid in $ETH.')
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByText('The impact your trade has on the market price of this pool.')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows accept changes section when available', () => {
|
||||
const mockAcceptChanges = jest.fn()
|
||||
render(
|
||||
<SwapModalFooter
|
||||
trade={TEST_TRADE_EXACT_INPUT}
|
||||
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
|
||||
hash={undefined}
|
||||
onConfirm={jest.fn()}
|
||||
swapErrorMessage={undefined}
|
||||
disabledConfirm={false}
|
||||
swapQuoteReceivedDate={undefined}
|
||||
fiatValueInput={{
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
}}
|
||||
fiatValueOutput={{
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
}}
|
||||
showAcceptChanges={true}
|
||||
onAcceptChanges={mockAcceptChanges}
|
||||
/>
|
||||
)
|
||||
const showAcceptChanges = screen.getByTestId('show-accept-changes')
|
||||
expect(showAcceptChanges).toBeInTheDocument()
|
||||
expect(within(showAcceptChanges).getByText('Price updated')).toBeVisible()
|
||||
expect(within(showAcceptChanges).getByText('Accept')).toBeVisible()
|
||||
})
|
||||
|
||||
it('test trade exact output, no recipient', () => {
|
||||
render(
|
||||
<SwapModalFooter
|
||||
trade={TEST_TRADE_EXACT_OUTPUT}
|
||||
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
|
||||
hash={undefined}
|
||||
onConfirm={jest.fn()}
|
||||
swapErrorMessage={undefined}
|
||||
disabledConfirm={false}
|
||||
swapQuoteReceivedDate={undefined}
|
||||
fiatValueInput={{
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
}}
|
||||
fiatValueOutput={{
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
}}
|
||||
showAcceptChanges={true}
|
||||
onAcceptChanges={jest.fn()}
|
||||
/>
|
||||
)
|
||||
expect(
|
||||
screen.getByText(
|
||||
'The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will revert.'
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText('The fee paid to miners who process your transaction. This must be paid in $ETH.')
|
||||
).toBeInTheDocument()
|
||||
expect(screen.getByText('The impact your trade has on the market price of this pool.')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
@ -1,105 +1,48 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { formatPriceImpact } from '@uniswap/conedison/format'
|
||||
import { Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Column from 'components/Column'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import useTransactionDeadline from 'hooks/useTransactionDeadline'
|
||||
import {
|
||||
formatPercentInBasisPointsNumber,
|
||||
formatPercentNumber,
|
||||
formatToDecimal,
|
||||
getDurationFromDateMilliseconds,
|
||||
getDurationUntilTimestampSeconds,
|
||||
getTokenAddress,
|
||||
} from 'lib/utils/analytics'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { ReactNode } from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { RouterPreference } from 'state/routing/slice'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import { useRouterPreference, useUserSlippageTolerance } from 'state/user/hooks'
|
||||
import getRoutingDiagramEntries, { RoutingDiagramEntry } from 'utils/getRoutingDiagramEntries'
|
||||
import { computeRealizedPriceImpact } from 'utils/prices'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { formatTransactionAmount, priceToPreciseFloat } from 'utils/formatNumbers'
|
||||
import getRoutingDiagramEntries from 'utils/getRoutingDiagramEntries'
|
||||
import { formatSwapButtonClickEventProperties } from 'utils/loggingFormatters'
|
||||
import { getPriceImpactWarning } from 'utils/prices'
|
||||
|
||||
import { ButtonError } from '../Button'
|
||||
import { AutoRow } from '../Row'
|
||||
import { SwapCallbackError } from './styleds'
|
||||
import { ButtonError, SmallButtonPrimary } from '../Button'
|
||||
import Row, { AutoRow, RowBetween, RowFixed } from '../Row'
|
||||
import { SwapCallbackError, SwapShowAcceptChanges } from './styleds'
|
||||
import { Label } from './SwapModalHeaderAmount'
|
||||
|
||||
interface AnalyticsEventProps {
|
||||
trade: InterfaceTrade
|
||||
hash: string | undefined
|
||||
allowedSlippage: Percent
|
||||
transactionDeadlineSecondsSinceEpoch: number | undefined
|
||||
isAutoSlippage: boolean
|
||||
isAutoRouterApi: boolean
|
||||
swapQuoteReceivedDate: Date | undefined
|
||||
routes: RoutingDiagramEntry[]
|
||||
fiatValueInput?: number
|
||||
fiatValueOutput?: number
|
||||
}
|
||||
const DetailsContainer = styled(Column)`
|
||||
padding: 0 8px;
|
||||
`
|
||||
|
||||
const formatRoutesEventProperties = (routes: RoutingDiagramEntry[]) => {
|
||||
const routesEventProperties: Record<string, any[]> = {
|
||||
routes_percentages: [],
|
||||
routes_protocols: [],
|
||||
}
|
||||
const StyledAlertTriangle = styled(AlertTriangle)`
|
||||
margin-right: 8px;
|
||||
min-width: 24px;
|
||||
`
|
||||
|
||||
routes.forEach((route, index) => {
|
||||
routesEventProperties['routes_percentages'].push(formatPercentNumber(route.percent))
|
||||
routesEventProperties['routes_protocols'].push(route.protocol)
|
||||
routesEventProperties[`route_${index}_input_currency_symbols`] = route.path.map(
|
||||
(pathStep) => pathStep[0].symbol ?? ''
|
||||
)
|
||||
routesEventProperties[`route_${index}_output_currency_symbols`] = route.path.map(
|
||||
(pathStep) => pathStep[1].symbol ?? ''
|
||||
)
|
||||
routesEventProperties[`route_${index}_input_currency_addresses`] = route.path.map((pathStep) =>
|
||||
getTokenAddress(pathStep[0])
|
||||
)
|
||||
routesEventProperties[`route_${index}_output_currency_addresses`] = route.path.map((pathStep) =>
|
||||
getTokenAddress(pathStep[1])
|
||||
)
|
||||
routesEventProperties[`route_${index}_fee_amounts_hundredths_of_bps`] = route.path.map((pathStep) => pathStep[2])
|
||||
})
|
||||
const ConfirmButton = styled(ButtonError)`
|
||||
height: 56px;
|
||||
margin-top: 10px;
|
||||
`
|
||||
|
||||
return routesEventProperties
|
||||
}
|
||||
|
||||
const formatAnalyticsEventProperties = ({
|
||||
trade,
|
||||
hash,
|
||||
allowedSlippage,
|
||||
transactionDeadlineSecondsSinceEpoch,
|
||||
isAutoSlippage,
|
||||
isAutoRouterApi,
|
||||
swapQuoteReceivedDate,
|
||||
routes,
|
||||
fiatValueInput,
|
||||
fiatValueOutput,
|
||||
}: AnalyticsEventProps) => ({
|
||||
estimated_network_fee_usd: trade.gasUseEstimateUSD ?? undefined,
|
||||
transaction_hash: hash,
|
||||
transaction_deadline_seconds: getDurationUntilTimestampSeconds(transactionDeadlineSecondsSinceEpoch),
|
||||
token_in_address: getTokenAddress(trade.inputAmount.currency),
|
||||
token_out_address: getTokenAddress(trade.outputAmount.currency),
|
||||
token_in_symbol: trade.inputAmount.currency.symbol,
|
||||
token_out_symbol: trade.outputAmount.currency.symbol,
|
||||
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
|
||||
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
|
||||
token_in_amount_usd: fiatValueInput,
|
||||
token_out_amount_usd: fiatValueOutput,
|
||||
price_impact_basis_points: formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)),
|
||||
allowed_slippage_basis_points: formatPercentInBasisPointsNumber(allowedSlippage),
|
||||
is_auto_router_api: isAutoRouterApi,
|
||||
is_auto_slippage: isAutoSlippage,
|
||||
chain_id:
|
||||
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
|
||||
? trade.inputAmount.currency.chainId
|
||||
: undefined,
|
||||
duration_from_first_quote_to_swap_submission_milliseconds: swapQuoteReceivedDate
|
||||
? getDurationFromDateMilliseconds(swapQuoteReceivedDate)
|
||||
: undefined,
|
||||
swap_quote_block_number: trade.blockNumber,
|
||||
...formatRoutesEventProperties(routes),
|
||||
})
|
||||
const DetailRowValue = styled(ThemedText.BodySmall)`
|
||||
text-align: right;
|
||||
overflow-wrap: break-word;
|
||||
`
|
||||
|
||||
export default function SwapModalFooter({
|
||||
trade,
|
||||
@ -111,6 +54,8 @@ export default function SwapModalFooter({
|
||||
swapQuoteReceivedDate,
|
||||
fiatValueInput,
|
||||
fiatValueOutput,
|
||||
showAcceptChanges,
|
||||
onAcceptChanges,
|
||||
}: {
|
||||
trade: InterfaceTrade
|
||||
hash: string | undefined
|
||||
@ -121,46 +66,142 @@ export default function SwapModalFooter({
|
||||
swapQuoteReceivedDate: Date | undefined
|
||||
fiatValueInput: { data?: number; isLoading: boolean }
|
||||
fiatValueOutput: { data?: number; isLoading: boolean }
|
||||
showAcceptChanges: boolean
|
||||
onAcceptChanges: () => void
|
||||
}) {
|
||||
const transactionDeadlineSecondsSinceEpoch = useTransactionDeadline()?.toNumber() // in seconds since epoch
|
||||
const isAutoSlippage = useUserSlippageTolerance()[0] === 'auto'
|
||||
const [routerPreference] = useRouterPreference()
|
||||
const routes = getRoutingDiagramEntries(trade)
|
||||
const theme = useTheme()
|
||||
const { chainId } = useWeb3React()
|
||||
const nativeCurrency = useNativeCurrency(chainId)
|
||||
|
||||
const label = `${trade.executionPrice.baseCurrency?.symbol} `
|
||||
const labelInverted = `${trade.executionPrice.quoteCurrency?.symbol}`
|
||||
const formattedPrice = formatTransactionAmount(priceToPreciseFloat(trade.executionPrice))
|
||||
|
||||
return (
|
||||
<>
|
||||
<AutoRow>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
element={InterfaceElementName.CONFIRM_SWAP_BUTTON}
|
||||
name={SwapEventName.SWAP_SUBMITTED_BUTTON_CLICKED}
|
||||
properties={formatAnalyticsEventProperties({
|
||||
trade,
|
||||
hash,
|
||||
allowedSlippage,
|
||||
transactionDeadlineSecondsSinceEpoch,
|
||||
isAutoSlippage,
|
||||
isAutoRouterApi: routerPreference === RouterPreference.AUTO || routerPreference === RouterPreference.API,
|
||||
swapQuoteReceivedDate,
|
||||
routes,
|
||||
fiatValueInput: fiatValueInput.data,
|
||||
fiatValueOutput: fiatValueOutput.data,
|
||||
})}
|
||||
>
|
||||
<ButtonError
|
||||
data-testid="confirm-swap-button"
|
||||
onClick={onConfirm}
|
||||
disabled={disabledConfirm}
|
||||
style={{ margin: '10px 0 0 0' }}
|
||||
id={InterfaceElementName.CONFIRM_SWAP_BUTTON}
|
||||
<DetailsContainer gap="md">
|
||||
<ThemedText.BodySmall>
|
||||
<Row align="flex-start" justify="space-between" gap="sm">
|
||||
<Label>
|
||||
<Trans>Exchange rate</Trans>
|
||||
</Label>
|
||||
<DetailRowValue>{`1 ${labelInverted} = ${formattedPrice ?? '-'} ${label}`}</DetailRowValue>
|
||||
</Row>
|
||||
</ThemedText.BodySmall>
|
||||
<ThemedText.BodySmall>
|
||||
<Row align="flex-start" justify="space-between" gap="sm">
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Trans>
|
||||
The fee paid to miners who process your transaction. This must be paid in ${nativeCurrency.symbol}.
|
||||
</Trans>
|
||||
}
|
||||
>
|
||||
<Label cursor="help">
|
||||
<Trans>Network fee</Trans>
|
||||
</Label>
|
||||
</MouseoverTooltip>
|
||||
<DetailRowValue>{trade.gasUseEstimateUSD ? `~$${trade.gasUseEstimateUSD}` : '-'}</DetailRowValue>
|
||||
</Row>
|
||||
</ThemedText.BodySmall>
|
||||
<ThemedText.BodySmall>
|
||||
<Row align="flex-start" justify="space-between" gap="sm">
|
||||
<MouseoverTooltip text={<Trans>The impact your trade has on the market price of this pool.</Trans>}>
|
||||
<Label cursor="help">
|
||||
<Trans>Price impact</Trans>
|
||||
</Label>
|
||||
</MouseoverTooltip>
|
||||
<DetailRowValue color={getPriceImpactWarning(trade.priceImpact)}>
|
||||
{trade.priceImpact ? formatPriceImpact(trade.priceImpact) : '-'}
|
||||
</DetailRowValue>
|
||||
</Row>
|
||||
</ThemedText.BodySmall>
|
||||
<ThemedText.BodySmall>
|
||||
<Row align="flex-start" justify="space-between" gap="sm">
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
trade.tradeType === TradeType.EXACT_INPUT ? (
|
||||
<Trans>
|
||||
The minimum amount you are guaranteed to receive. If the price slips any further, your transaction
|
||||
will revert.
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
The maximum amount you are guaranteed to spend. If the price slips any further, your transaction
|
||||
will revert.
|
||||
</Trans>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Label cursor="help">
|
||||
{trade.tradeType === TradeType.EXACT_INPUT ? (
|
||||
<Trans>Minimum received</Trans>
|
||||
) : (
|
||||
<Trans>Maximum sent</Trans>
|
||||
)}
|
||||
</Label>
|
||||
</MouseoverTooltip>
|
||||
<DetailRowValue>
|
||||
{trade.tradeType === TradeType.EXACT_INPUT
|
||||
? `${trade.minimumAmountOut(allowedSlippage).toSignificant(6)} ${trade.outputAmount.currency.symbol}`
|
||||
: `${trade.maximumAmountIn(allowedSlippage).toSignificant(6)} ${trade.inputAmount.currency.symbol}`}
|
||||
</DetailRowValue>
|
||||
</Row>
|
||||
</ThemedText.BodySmall>
|
||||
</DetailsContainer>
|
||||
{showAcceptChanges ? (
|
||||
<SwapShowAcceptChanges data-testid="show-accept-changes">
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<StyledAlertTriangle size={20} />
|
||||
<ThemedText.DeprecatedMain color={theme.accentAction}>
|
||||
<Trans>Price updated</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
</RowFixed>
|
||||
<SmallButtonPrimary onClick={onAcceptChanges}>
|
||||
<Trans>Accept</Trans>
|
||||
</SmallButtonPrimary>
|
||||
</RowBetween>
|
||||
</SwapShowAcceptChanges>
|
||||
) : (
|
||||
<AutoRow>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
element={InterfaceElementName.CONFIRM_SWAP_BUTTON}
|
||||
name={SwapEventName.SWAP_SUBMITTED_BUTTON_CLICKED}
|
||||
properties={formatSwapButtonClickEventProperties({
|
||||
trade,
|
||||
hash,
|
||||
allowedSlippage,
|
||||
transactionDeadlineSecondsSinceEpoch,
|
||||
isAutoSlippage,
|
||||
isAutoRouterApi: routerPreference === RouterPreference.AUTO || routerPreference === RouterPreference.API,
|
||||
swapQuoteReceivedDate,
|
||||
routes,
|
||||
fiatValueInput: fiatValueInput.data,
|
||||
fiatValueOutput: fiatValueOutput.data,
|
||||
})}
|
||||
>
|
||||
<Text fontSize={20} fontWeight={500}>
|
||||
<Trans>Confirm Swap</Trans>
|
||||
</Text>
|
||||
</ButtonError>
|
||||
</TraceEvent>
|
||||
{swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
|
||||
</AutoRow>
|
||||
<ConfirmButton
|
||||
data-testid="confirm-swap-button"
|
||||
onClick={onConfirm}
|
||||
disabled={disabledConfirm}
|
||||
$borderRadius="12px"
|
||||
id={InterfaceElementName.CONFIRM_SWAP_BUTTON}
|
||||
>
|
||||
<ThemedText.HeadlineSmall color="accentTextLightPrimary">
|
||||
<Trans>Swap</Trans>
|
||||
</ThemedText.HeadlineSmall>
|
||||
</ConfirmButton>
|
||||
</TraceEvent>
|
||||
|
||||
{swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
|
||||
</AutoRow>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,83 +1,44 @@
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import {
|
||||
TEST_ALLOWED_SLIPPAGE,
|
||||
TEST_RECIPIENT_ADDRESS,
|
||||
TEST_TRADE_EXACT_INPUT,
|
||||
TEST_TRADE_EXACT_OUTPUT,
|
||||
} from 'test-utils/constants'
|
||||
import { render, screen, within } from 'test-utils/render'
|
||||
import noop from 'utils/noop'
|
||||
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
|
||||
import { TEST_ALLOWED_SLIPPAGE, TEST_TRADE_EXACT_INPUT, TEST_TRADE_EXACT_OUTPUT } from 'test-utils/constants'
|
||||
import { render, screen } from 'test-utils/render'
|
||||
|
||||
import SwapModalHeader from './SwapModalHeader'
|
||||
|
||||
jest.mock('@uniswap/analytics')
|
||||
const mockSendAnalyticsEvent = sendAnalyticsEvent as jest.MockedFunction<typeof sendAnalyticsEvent>
|
||||
|
||||
describe('SwapModalHeader.tsx', () => {
|
||||
let sendAnalyticsEventMock: jest.Mock<any, any>
|
||||
|
||||
beforeAll(() => {
|
||||
sendAnalyticsEventMock = jest.fn()
|
||||
})
|
||||
|
||||
it('matches base snapshot for test trade with exact input', () => {
|
||||
it('matches base snapshot, test trade exact input', () => {
|
||||
const { asFragment } = render(
|
||||
<SwapModalHeader
|
||||
trade={TEST_TRADE_EXACT_INPUT}
|
||||
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
|
||||
shouldLogModalCloseEvent={false}
|
||||
showAcceptChanges={false}
|
||||
setShouldLogModalCloseEvent={noop}
|
||||
onAcceptChanges={noop}
|
||||
recipient={TEST_RECIPIENT_ADDRESS}
|
||||
/>
|
||||
<SwapModalHeader trade={TEST_TRADE_EXACT_INPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
expect(screen.getByText(/Output is estimated. You will receive at least /i)).toBeInTheDocument()
|
||||
expect(screen.getByTestId('INPUT-amount')).toHaveTextContent(
|
||||
`${formatCurrencyAmount(TEST_TRADE_EXACT_INPUT.inputAmount, NumberType.TokenTx)} ${
|
||||
TEST_TRADE_EXACT_INPUT.inputAmount.currency.symbol ?? ''
|
||||
}`
|
||||
)
|
||||
expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent(
|
||||
`${formatCurrencyAmount(TEST_TRADE_EXACT_INPUT.outputAmount, NumberType.TokenTx)} ${
|
||||
TEST_TRADE_EXACT_INPUT.outputAmount.currency.symbol ?? ''
|
||||
}`
|
||||
)
|
||||
})
|
||||
|
||||
it('shows accept changes section and logs amplitude event', () => {
|
||||
const setShouldLogModalCloseEventFn = jest.fn()
|
||||
mockSendAnalyticsEvent.mockImplementation(sendAnalyticsEventMock)
|
||||
render(
|
||||
<SwapModalHeader
|
||||
trade={TEST_TRADE_EXACT_INPUT}
|
||||
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
|
||||
shouldLogModalCloseEvent
|
||||
showAcceptChanges
|
||||
setShouldLogModalCloseEvent={setShouldLogModalCloseEventFn}
|
||||
onAcceptChanges={noop}
|
||||
recipient={TEST_RECIPIENT_ADDRESS}
|
||||
/>
|
||||
it('test trade exact output, no recipient', () => {
|
||||
const { asFragment } = render(
|
||||
<SwapModalHeader trade={TEST_TRADE_EXACT_OUTPUT} allowedSlippage={TEST_ALLOWED_SLIPPAGE} />
|
||||
)
|
||||
expect(setShouldLogModalCloseEventFn).toHaveBeenCalledWith(false)
|
||||
const showAcceptChanges = screen.getByTestId('show-accept-changes')
|
||||
expect(showAcceptChanges).toBeInTheDocument()
|
||||
expect(within(showAcceptChanges).getByText('Price Updated')).toBeVisible()
|
||||
expect(within(showAcceptChanges).getByText('Accept')).toBeVisible()
|
||||
expect(sendAnalyticsEventMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('renders correctly for test trade with exact output and no recipient', () => {
|
||||
const rendered = render(
|
||||
<SwapModalHeader
|
||||
trade={TEST_TRADE_EXACT_OUTPUT}
|
||||
allowedSlippage={TEST_ALLOWED_SLIPPAGE}
|
||||
shouldLogModalCloseEvent={false}
|
||||
showAcceptChanges={false}
|
||||
setShouldLogModalCloseEvent={noop}
|
||||
onAcceptChanges={noop}
|
||||
recipient={null}
|
||||
/>
|
||||
)
|
||||
expect(rendered.queryByTestId('recipient-info')).toBeNull()
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
expect(screen.getByText(/Input is estimated. You will sell at most/i)).toBeInTheDocument()
|
||||
expect(screen.getByTestId('input-symbol')).toHaveTextContent(
|
||||
TEST_TRADE_EXACT_OUTPUT.inputAmount.currency.symbol ?? ''
|
||||
|
||||
expect(screen.getByTestId('INPUT-amount')).toHaveTextContent(
|
||||
`${formatCurrencyAmount(TEST_TRADE_EXACT_OUTPUT.inputAmount, NumberType.TokenTx)} ${
|
||||
TEST_TRADE_EXACT_OUTPUT.inputAmount.currency.symbol ?? ''
|
||||
}`
|
||||
)
|
||||
expect(screen.getByTestId('output-symbol')).toHaveTextContent(
|
||||
TEST_TRADE_EXACT_OUTPUT.outputAmount.currency.symbol ?? ''
|
||||
expect(screen.getByTestId('OUTPUT-amount')).toHaveTextContent(
|
||||
`${formatCurrencyAmount(TEST_TRADE_EXACT_OUTPUT.outputAmount, NumberType.TokenTx)} ${
|
||||
TEST_TRADE_EXACT_OUTPUT.outputAmount.currency.symbol ?? ''
|
||||
}`
|
||||
)
|
||||
expect(screen.getByTestId('input-amount')).toHaveTextContent(TEST_TRADE_EXACT_OUTPUT.inputAmount.toExact())
|
||||
expect(screen.getByTestId('output-amount')).toHaveTextContent(TEST_TRADE_EXACT_OUTPUT.outputAmount.toExact())
|
||||
})
|
||||
})
|
||||
|
@ -1,216 +1,72 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { SwapEventName, SwapPriceUpdateUserResponse } from '@uniswap/analytics-events'
|
||||
import { Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import Column, { AutoColumn } from 'components/Column'
|
||||
import { useUSDPrice } from 'hooks/useUSDPrice'
|
||||
import { getPriceUpdateBasisPoints } from 'lib/utils/analytics'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { AlertTriangle, ArrowDown } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { Field } from 'state/swap/actions'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Divider, ThemedText } from 'theme'
|
||||
|
||||
import { ThemedText } from '../../theme'
|
||||
import { isAddress, shortenAddress } from '../../utils'
|
||||
import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
import { LightCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { FiatValue } from '../CurrencyInputPanel/FiatValue'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import TradePrice from '../swap/TradePrice'
|
||||
import { AdvancedSwapDetails } from './AdvancedSwapDetails'
|
||||
import { SwapShowAcceptChanges, TruncatedText } from './styleds'
|
||||
import { SwapModalHeaderAmount } from './SwapModalHeaderAmount'
|
||||
|
||||
const ArrowWrapper = styled.div`
|
||||
padding: 4px;
|
||||
border-radius: 12px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
position: relative;
|
||||
margin-top: -18px;
|
||||
margin-bottom: -18px;
|
||||
left: calc(50% - 16px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border: 4px solid;
|
||||
border-color: ${({ theme }) => theme.backgroundModule};
|
||||
z-index: 2;
|
||||
const Rule = styled(Divider)`
|
||||
margin: 16px 2px 24px 2px;
|
||||
`
|
||||
|
||||
const formatAnalyticsEventProperties = (
|
||||
trade: InterfaceTrade,
|
||||
priceUpdate: number | undefined,
|
||||
response: SwapPriceUpdateUserResponse
|
||||
) => ({
|
||||
chain_id:
|
||||
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
|
||||
? trade.inputAmount.currency.chainId
|
||||
: undefined,
|
||||
response,
|
||||
token_in_symbol: trade.inputAmount.currency.symbol,
|
||||
token_out_symbol: trade.outputAmount.currency.symbol,
|
||||
price_update_basis_points: priceUpdate,
|
||||
})
|
||||
const HeaderContainer = styled(AutoColumn)`
|
||||
margin-top: 16px;
|
||||
`
|
||||
|
||||
export default function SwapModalHeader({
|
||||
trade,
|
||||
shouldLogModalCloseEvent,
|
||||
setShouldLogModalCloseEvent,
|
||||
allowedSlippage,
|
||||
recipient,
|
||||
showAcceptChanges,
|
||||
onAcceptChanges,
|
||||
}: {
|
||||
trade: InterfaceTrade
|
||||
shouldLogModalCloseEvent: boolean
|
||||
setShouldLogModalCloseEvent: (shouldLog: boolean) => void
|
||||
allowedSlippage: Percent
|
||||
recipient: string | null
|
||||
showAcceptChanges: boolean
|
||||
onAcceptChanges: () => void
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
|
||||
const [lastExecutionPrice, setLastExecutionPrice] = useState(trade.executionPrice)
|
||||
const [priceUpdate, setPriceUpdate] = useState<number | undefined>()
|
||||
|
||||
const fiatValueInput = useUSDPrice(trade.inputAmount)
|
||||
const fiatValueOutput = useUSDPrice(trade.outputAmount)
|
||||
|
||||
useEffect(() => {
|
||||
if (!trade.executionPrice.equalTo(lastExecutionPrice)) {
|
||||
setPriceUpdate(getPriceUpdateBasisPoints(lastExecutionPrice, trade.executionPrice))
|
||||
setLastExecutionPrice(trade.executionPrice)
|
||||
}
|
||||
}, [lastExecutionPrice, setLastExecutionPrice, trade.executionPrice])
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldLogModalCloseEvent && showAcceptChanges) {
|
||||
sendAnalyticsEvent(
|
||||
SwapEventName.SWAP_PRICE_UPDATE_ACKNOWLEDGED,
|
||||
formatAnalyticsEventProperties(trade, priceUpdate, SwapPriceUpdateUserResponse.REJECTED)
|
||||
)
|
||||
}
|
||||
setShouldLogModalCloseEvent(false)
|
||||
}, [shouldLogModalCloseEvent, showAcceptChanges, setShouldLogModalCloseEvent, trade, priceUpdate])
|
||||
|
||||
return (
|
||||
<AutoColumn gap="4px" style={{ marginTop: '1rem' }}>
|
||||
<LightCard padding="0.75rem 1rem">
|
||||
<AutoColumn gap="sm">
|
||||
<RowBetween align="center">
|
||||
<RowFixed gap="0px">
|
||||
<TruncatedText
|
||||
fontSize={24}
|
||||
fontWeight={500}
|
||||
color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? theme.accentAction : ''}
|
||||
data-testid="input-amount"
|
||||
>
|
||||
{trade.inputAmount.toSignificant(6)}
|
||||
</TruncatedText>
|
||||
</RowFixed>
|
||||
<RowFixed gap="0px">
|
||||
<CurrencyLogo currency={trade.inputAmount.currency} size="20px" style={{ marginRight: '12px' }} />
|
||||
<Text fontSize={20} fontWeight={500} data-testid="input-symbol">
|
||||
{trade.inputAmount.currency.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<FiatValue fiatValue={fiatValueInput} />
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
</LightCard>
|
||||
<ArrowWrapper>
|
||||
<ArrowDown size="16" color={theme.textPrimary} />
|
||||
</ArrowWrapper>
|
||||
<LightCard padding="0.75rem 1rem" style={{ marginBottom: '0.25rem' }}>
|
||||
<AutoColumn gap="sm">
|
||||
<RowBetween align="flex-end">
|
||||
<RowFixed gap="0px">
|
||||
<TruncatedText fontSize={24} fontWeight={500} data-testid="output-amount">
|
||||
{trade.outputAmount.toSignificant(6)}
|
||||
</TruncatedText>
|
||||
</RowFixed>
|
||||
<RowFixed gap="0px">
|
||||
<CurrencyLogo currency={trade.outputAmount.currency} size="20px" style={{ marginRight: '12px' }} />
|
||||
<Text fontSize={20} fontWeight={500} data-testid="output-symbol">
|
||||
{trade.outputAmount.currency.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<ThemedText.DeprecatedBody fontSize={14} color={theme.textTertiary}>
|
||||
<FiatValue
|
||||
fiatValue={fiatValueOutput}
|
||||
priceImpact={computeFiatValuePriceImpact(fiatValueInput.data, fiatValueOutput.data)}
|
||||
/>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
</LightCard>
|
||||
<RowBetween style={{ marginTop: '0.25rem', padding: '0 1rem' }}>
|
||||
<TradePrice price={trade.executionPrice} />
|
||||
</RowBetween>
|
||||
<LightCard style={{ padding: '.75rem', marginTop: '0.5rem' }}>
|
||||
<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} />
|
||||
</LightCard>
|
||||
{showAcceptChanges ? (
|
||||
<SwapShowAcceptChanges justify="flex-start" gap="0px" data-testid="show-accept-changes">
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<AlertTriangle size={20} style={{ marginRight: '8px', minWidth: 24 }} />
|
||||
<ThemedText.DeprecatedMain color={theme.accentAction}>
|
||||
<Trans>Price Updated</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
</RowFixed>
|
||||
<ButtonPrimary
|
||||
style={{ padding: '.5rem', width: 'fit-content', fontSize: '0.825rem', borderRadius: '12px' }}
|
||||
onClick={onAcceptChanges}
|
||||
>
|
||||
<Trans>Accept</Trans>
|
||||
</ButtonPrimary>
|
||||
</RowBetween>
|
||||
</SwapShowAcceptChanges>
|
||||
) : null}
|
||||
|
||||
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '.75rem 1rem' }}>
|
||||
{trade.tradeType === TradeType.EXACT_INPUT ? (
|
||||
<ThemedText.DeprecatedItalic fontWeight={400} textAlign="left" style={{ width: '100%' }}>
|
||||
<Trans>
|
||||
Output is estimated. You will receive at least{' '}
|
||||
<b>
|
||||
{trade.minimumAmountOut(allowedSlippage).toSignificant(6)} {trade.outputAmount.currency.symbol}
|
||||
</b>{' '}
|
||||
or the transaction will revert.
|
||||
</Trans>
|
||||
</ThemedText.DeprecatedItalic>
|
||||
) : (
|
||||
<ThemedText.DeprecatedItalic fontWeight={400} textAlign="left" style={{ width: '100%' }}>
|
||||
<Trans>
|
||||
Input is estimated. You will sell at most{' '}
|
||||
<b>
|
||||
{trade.maximumAmountIn(allowedSlippage).toSignificant(6)} {trade.inputAmount.currency.symbol}
|
||||
</b>{' '}
|
||||
or the transaction will revert.
|
||||
</Trans>
|
||||
</ThemedText.DeprecatedItalic>
|
||||
)}
|
||||
</AutoColumn>
|
||||
{recipient !== null ? (
|
||||
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }} data-testid="recipient-info">
|
||||
<ThemedText.DeprecatedMain>
|
||||
<Trans>
|
||||
Output will be sent to{' '}
|
||||
<b title={recipient}>{isAddress(recipient) ? shortenAddress(recipient) : recipient}</b>
|
||||
</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
</AutoColumn>
|
||||
) : null}
|
||||
</AutoColumn>
|
||||
<HeaderContainer gap="sm">
|
||||
<Column gap="lg">
|
||||
<SwapModalHeaderAmount
|
||||
field={Field.INPUT}
|
||||
label={<Trans>You pay</Trans>}
|
||||
amount={trade.inputAmount}
|
||||
usdAmount={fiatValueInput.data}
|
||||
/>
|
||||
<SwapModalHeaderAmount
|
||||
field={Field.OUTPUT}
|
||||
label={<Trans>You receive</Trans>}
|
||||
amount={trade.outputAmount}
|
||||
usdAmount={fiatValueOutput.data}
|
||||
tooltipText={
|
||||
trade.tradeType === TradeType.EXACT_INPUT ? (
|
||||
<ThemedText.Caption>
|
||||
<Trans>
|
||||
Output is estimated. You will receive at least{' '}
|
||||
<b>
|
||||
{trade.minimumAmountOut(allowedSlippage).toSignificant(6)} {trade.outputAmount.currency.symbol}
|
||||
</b>{' '}
|
||||
or the transaction will revert.
|
||||
</Trans>
|
||||
</ThemedText.Caption>
|
||||
) : (
|
||||
<ThemedText.Caption>
|
||||
<Trans>
|
||||
Input is estimated. You will sell at most{' '}
|
||||
<b>
|
||||
{trade.maximumAmountIn(allowedSlippage).toSignificant(6)} {trade.inputAmount.currency.symbol}
|
||||
</b>{' '}
|
||||
or the transaction will revert.
|
||||
</Trans>
|
||||
</ThemedText.Caption>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Column>
|
||||
<Rule />
|
||||
</HeaderContainer>
|
||||
)
|
||||
}
|
||||
|
68
src/components/swap/SwapModalHeaderAmount.tsx
Normal file
68
src/components/swap/SwapModalHeaderAmount.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { formatCurrencyAmount, formatNumber, NumberType } from '@uniswap/conedison/format'
|
||||
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
|
||||
import Column from 'components/Column'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import Row from 'components/Row'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import { useWindowSize } from 'hooks/useWindowSize'
|
||||
import { PropsWithChildren, ReactNode } from 'react'
|
||||
import { TextProps } from 'rebass'
|
||||
import { Field } from 'state/swap/actions'
|
||||
import styled from 'styled-components/macro'
|
||||
import { BREAKPOINTS, ThemedText } from 'theme'
|
||||
|
||||
const MAX_AMOUNT_STR_LENGTH = 9
|
||||
|
||||
export const Label = styled(ThemedText.BodySmall)<{ cursor?: string }>`
|
||||
cursor: ${({ cursor }) => cursor};
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
margin-right: 8px;
|
||||
`
|
||||
|
||||
const ResponsiveHeadline = ({ children, ...textProps }: PropsWithChildren<TextProps>) => {
|
||||
const { width } = useWindowSize()
|
||||
|
||||
if (width && width < BREAKPOINTS.xs) {
|
||||
return <ThemedText.HeadlineMedium {...textProps}>{children}</ThemedText.HeadlineMedium>
|
||||
}
|
||||
|
||||
return <ThemedText.HeadlineLarge {...textProps}>{children}</ThemedText.HeadlineLarge>
|
||||
}
|
||||
|
||||
interface AmountProps {
|
||||
field: Field
|
||||
tooltipText?: ReactNode
|
||||
label: ReactNode
|
||||
amount: CurrencyAmount<Currency> | undefined
|
||||
usdAmount?: number
|
||||
}
|
||||
|
||||
export function SwapModalHeaderAmount({ tooltipText, label, amount, usdAmount, field }: AmountProps) {
|
||||
let formattedAmount = formatCurrencyAmount(amount, NumberType.TokenTx)
|
||||
if (formattedAmount.length > MAX_AMOUNT_STR_LENGTH) {
|
||||
formattedAmount = formatCurrencyAmount(amount, NumberType.SwapTradeAmount)
|
||||
}
|
||||
|
||||
return (
|
||||
<Row align="center" justify="space-between" gap="md">
|
||||
<Column gap="xs">
|
||||
<ThemedText.BodySecondary>
|
||||
<MouseoverTooltip text={tooltipText} disabled={!tooltipText}>
|
||||
<Label cursor="help">{label}</Label>
|
||||
</MouseoverTooltip>
|
||||
</ThemedText.BodySecondary>
|
||||
<Column gap="xs">
|
||||
<ResponsiveHeadline data-testid={`${field}-amount`}>
|
||||
{formattedAmount} {amount?.currency.symbol}
|
||||
</ResponsiveHeadline>
|
||||
{usdAmount && (
|
||||
<ThemedText.BodySmall color="textTertiary">
|
||||
{formatNumber(usdAmount, NumberType.FiatTokenQuantity)}
|
||||
</ThemedText.BodySmall>
|
||||
)}
|
||||
</Column>
|
||||
</Column>
|
||||
{amount?.currency && <CurrencyLogo currency={amount.currency} size="36px" />}
|
||||
</Row>
|
||||
)
|
||||
}
|
@ -1,7 +1,172 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SwapModalFooter.tsx renders with a disabled button with no account 1`] = `
|
||||
exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = `
|
||||
<DocumentFragment>
|
||||
.c3 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: flex-start;
|
||||
-webkit-box-align: flex-start;
|
||||
-ms-flex-align: flex-start;
|
||||
align-items: flex-start;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
color: #0D111C;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
display: inline-block;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
color: #7780A0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
cursor: help;
|
||||
color: #7780A0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
text-align: right;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div
|
||||
class="c2 css-zhpkf8"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c2 c5 css-zhpkf8"
|
||||
>
|
||||
Exchange rate
|
||||
</div>
|
||||
<div
|
||||
class="c2 c6 css-zhpkf8"
|
||||
>
|
||||
1 DEF = 1.00 ABC
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 css-zhpkf8"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c2 c8 css-zhpkf8"
|
||||
cursor="help"
|
||||
>
|
||||
Network fee
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 c6 css-zhpkf8"
|
||||
>
|
||||
~$1.00
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 css-zhpkf8"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c2 c8 css-zhpkf8"
|
||||
cursor="help"
|
||||
>
|
||||
Price impact
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c6 css-zhpkf8"
|
||||
>
|
||||
105566.373%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 css-zhpkf8"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c2 c8 css-zhpkf8"
|
||||
cursor="help"
|
||||
>
|
||||
Minimum received
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c2 c6 css-zhpkf8"
|
||||
>
|
||||
0.00000000000000098 DEF
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
.c0 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
@ -58,6 +223,10 @@ exports[`SwapModalFooter.tsx renders with a disabled button with no account 1`]
|
||||
margin: !important;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
color: #F5F6FC;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
@ -146,106 +315,24 @@ exports[`SwapModalFooter.tsx renders with a disabled button with no account 1`]
|
||||
}
|
||||
|
||||
.c6 {
|
||||
background-color: rgba(250,43,57,0.1);
|
||||
border-radius: 1rem;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
font-size: 0.825rem;
|
||||
width: 100%;
|
||||
padding: 3rem 1.25rem 1rem 1rem;
|
||||
margin-top: -2rem;
|
||||
color: #FA2B39;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.c6 p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
background-color: rgba(250,43,57,0.1);
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
border-radius: 12px;
|
||||
min-width: 48px;
|
||||
height: 48px;
|
||||
height: 56px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0 c1 c2"
|
||||
>
|
||||
<button
|
||||
class="c3 c4 c5"
|
||||
class="c3 c4 c5 c6"
|
||||
data-testid="confirm-swap-button"
|
||||
disabled=""
|
||||
id="confirm-swap-or-send"
|
||||
style="margin: 10px 0px 0px 0px;"
|
||||
>
|
||||
<div
|
||||
class="css-10ob8xa"
|
||||
class="c7 css-iapcxi"
|
||||
>
|
||||
Confirm Swap
|
||||
Swap
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
class="c6"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<p
|
||||
style="word-break: break-word;"
|
||||
>
|
||||
swap error
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
@ -1,21 +1,14 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact input 1`] = `
|
||||
exports[`SwapModalHeader.tsx matches base snapshot, test trade exact input 1`] = `
|
||||
<DocumentFragment>
|
||||
.c1 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
.c3 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
.c4 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
@ -26,99 +19,30 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
gap: 0px;
|
||||
}
|
||||
|
||||
.c16 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: flex-end;
|
||||
-webkit-box-align: flex-end;
|
||||
-ms-flex-align: flex-end;
|
||||
align-items: flex-end;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
margin: -0px;
|
||||
}
|
||||
|
||||
.c24 {
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.c18 {
|
||||
color: #0D111C;
|
||||
}
|
||||
|
||||
.c23 {
|
||||
.c6 {
|
||||
color: #7780A0;
|
||||
}
|
||||
|
||||
.c21 {
|
||||
.c8 {
|
||||
color: #0D111C;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border-width: 0;
|
||||
margin: 0;
|
||||
background-color: #D2D9EE;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.c19 {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
border: 1px solid #E8ECFB;
|
||||
background-color: #F5F6FC;
|
||||
}
|
||||
|
||||
.c20 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -130,63 +54,39 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
gap: 12px;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
display: grid;
|
||||
grid-auto-rows: auto;
|
||||
grid-row-gap: 4px;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
display: grid;
|
||||
grid-auto-rows: auto;
|
||||
grid-row-gap: 8px;
|
||||
}
|
||||
|
||||
.c25 {
|
||||
display: grid;
|
||||
grid-auto-rows: auto;
|
||||
grid-row-gap: 8px;
|
||||
justify-items: flex-start;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
border-radius: 12px;
|
||||
border-radius: 12px;
|
||||
height: 24px;
|
||||
width: 50%;
|
||||
width: 50%;
|
||||
-webkit-animation: fAQEyV 1.5s infinite;
|
||||
animation: fAQEyV 1.5s infinite;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
background: linear-gradient( to left,#E8ECFB 25%,#fff 50%,#E8ECFB 75% );
|
||||
will-change: background-position;
|
||||
background-size: 400%;
|
||||
}
|
||||
|
||||
.c22 {
|
||||
display: inline-block;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.c14 {
|
||||
border-radius: 4px;
|
||||
width: 4rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
.c11 {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(white 60%,#ffffff00 calc(70% + 1px));
|
||||
box-shadow: 0 0 1px white;
|
||||
}
|
||||
|
||||
.c11 {
|
||||
.c10 {
|
||||
position: relative;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
@ -194,362 +94,334 @@ exports[`SwapModalHeader.tsx matches base snapshot for test trade with exact inp
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.c17 {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
.c7 {
|
||||
display: inline-block;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
cursor: help;
|
||||
color: #7780A0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
margin: 16px 2px 24px 2px;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
<div
|
||||
class="c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c5"
|
||||
>
|
||||
<div
|
||||
class="c6 css-1jljtub"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c8 c9 css-zhpkf8"
|
||||
cursor="help"
|
||||
>
|
||||
You pay
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
>
|
||||
<div
|
||||
class="c8 css-xdrz3"
|
||||
data-testid="INPUT-amount"
|
||||
>
|
||||
<0.00001 ABC
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c10"
|
||||
>
|
||||
<img
|
||||
alt="ABC logo"
|
||||
class="c11"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000001/logo.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c5"
|
||||
>
|
||||
<div
|
||||
class="c6 css-1jljtub"
|
||||
>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c8 c9 css-zhpkf8"
|
||||
cursor="help"
|
||||
>
|
||||
You receive
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
>
|
||||
<div
|
||||
class="c8 css-xdrz3"
|
||||
data-testid="OUTPUT-amount"
|
||||
>
|
||||
<0.00001 DEF
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c10"
|
||||
>
|
||||
<img
|
||||
alt="DEF logo"
|
||||
class="c11"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000002/logo.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c12 c13"
|
||||
/>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`SwapModalHeader.tsx test trade exact output, no recipient 1`] = `
|
||||
<DocumentFragment>
|
||||
.c3 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
color: #7780A0;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
color: #0D111C;
|
||||
}
|
||||
|
||||
.c12 {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border-width: 0;
|
||||
margin: 0;
|
||||
background-color: #D2D9EE;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
padding: 0;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-gap: 0.25rem;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
text-align: left;
|
||||
-webkit-flex-wrap: wrap;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
display: grid;
|
||||
grid-auto-rows: auto;
|
||||
grid-row-gap: 8px;
|
||||
}
|
||||
|
||||
.c11 {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(white 60%,#ffffff00 calc(70% + 1px));
|
||||
box-shadow: 0 0 1px white;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
text-overflow: ellipsis;
|
||||
max-width: 220px;
|
||||
overflow: hidden;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.c15 {
|
||||
padding: 4px;
|
||||
border-radius: 12px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
position: relative;
|
||||
margin-top: -18px;
|
||||
margin-bottom: -18px;
|
||||
left: calc(50% - 16px);
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
background-color: #FFFFFF;
|
||||
border: 4px solid;
|
||||
border-color: #F5F6FC;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
display: inline-block;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
cursor: help;
|
||||
color: #7780A0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.c13 {
|
||||
margin: 16px 2px 24px 2px;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
style="margin-top: 1rem;"
|
||||
class="c0 c1"
|
||||
>
|
||||
<div
|
||||
class="c1 c2 c3"
|
||||
class="c2"
|
||||
>
|
||||
<div
|
||||
class="c4"
|
||||
class="c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c5 c6 c7"
|
||||
class="c5"
|
||||
>
|
||||
<div
|
||||
class="c5 c8 c9"
|
||||
class="c6 css-1jljtub"
|
||||
>
|
||||
<div
|
||||
class="c10 css-13xjr5l"
|
||||
data-testid="input-amount"
|
||||
>
|
||||
0.000000000000001
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c5 c8 c9"
|
||||
>
|
||||
<div
|
||||
class="c11"
|
||||
style="margin-right: 12px;"
|
||||
>
|
||||
<img
|
||||
alt="ABC logo"
|
||||
class="c12"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000001/logo.png"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="css-10ob8xa"
|
||||
data-testid="input-symbol"
|
||||
>
|
||||
ABC
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c5 c6 c7"
|
||||
>
|
||||
<div
|
||||
class="css-zhpkf8"
|
||||
>
|
||||
<div
|
||||
class="c13 c14"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c15"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="16"
|
||||
stroke="#0D111C"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="5"
|
||||
y2="19"
|
||||
/>
|
||||
<polyline
|
||||
points="19 12 12 19 5 12"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="c1 c2 c3"
|
||||
style="margin-bottom: 0.25rem;"
|
||||
>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c5 c16 c7"
|
||||
>
|
||||
<div
|
||||
class="c5 c8 c9"
|
||||
>
|
||||
<div
|
||||
class="c10 css-1kwqs79"
|
||||
data-testid="output-amount"
|
||||
>
|
||||
0.000000000000001
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c5 c8 c9"
|
||||
>
|
||||
<div
|
||||
class="c11"
|
||||
style="margin-right: 12px;"
|
||||
>
|
||||
<img
|
||||
alt="DEF logo"
|
||||
class="c12"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000002/logo.png"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="css-10ob8xa"
|
||||
data-testid="output-symbol"
|
||||
>
|
||||
DEF
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c5 c6 c7"
|
||||
>
|
||||
<div
|
||||
class="css-zhpkf8"
|
||||
>
|
||||
<div
|
||||
class="css-zhpkf8"
|
||||
>
|
||||
<div
|
||||
class="c13 c14"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c5 c6 c7"
|
||||
style="margin-top: 0.25rem; padding: 0px 1rem;"
|
||||
>
|
||||
<button
|
||||
class="c17"
|
||||
title="1 DEF = 1.00 ABC "
|
||||
>
|
||||
<div
|
||||
class="c18 css-zhpkf8"
|
||||
>
|
||||
1 DEF = 1.00 ABC
|
||||
</div>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="c5 c19 c3"
|
||||
style="padding: .75rem; margin-top: 0.5rem;"
|
||||
>
|
||||
<div
|
||||
class="c20"
|
||||
>
|
||||
<div
|
||||
class="c21"
|
||||
/>
|
||||
<div
|
||||
class="c5 c6 c7"
|
||||
>
|
||||
<div
|
||||
class="c22"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c23 css-zhpkf8"
|
||||
>
|
||||
Network fee
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c18 css-zhpkf8"
|
||||
>
|
||||
~$1.00
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c5 c6 c7"
|
||||
>
|
||||
<div
|
||||
class="c5 c6 c24"
|
||||
>
|
||||
<div
|
||||
class="c22"
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c23 css-zhpkf8"
|
||||
class="c8 c9 css-zhpkf8"
|
||||
cursor="help"
|
||||
>
|
||||
Minimum output
|
||||
You pay
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c18 css-zhpkf8"
|
||||
class="c5"
|
||||
>
|
||||
0.00000000000000098 DEF
|
||||
<div
|
||||
class="c8 css-xdrz3"
|
||||
data-testid="INPUT-amount"
|
||||
>
|
||||
<0.00001 ABC
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c5 c6 c7"
|
||||
class="c10"
|
||||
>
|
||||
<img
|
||||
alt="ABC logo"
|
||||
class="c11"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000001/logo.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c3 c4"
|
||||
>
|
||||
<div
|
||||
class="c5"
|
||||
>
|
||||
<div
|
||||
class="c5 c6 c24"
|
||||
class="c6 css-1jljtub"
|
||||
>
|
||||
<div
|
||||
class="c22"
|
||||
class="c7"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c23 css-zhpkf8"
|
||||
class="c8 c9 css-zhpkf8"
|
||||
cursor="help"
|
||||
>
|
||||
Expected output
|
||||
You receive
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c18 css-zhpkf8"
|
||||
class="c5"
|
||||
>
|
||||
0.000000000000001 DEF
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c21"
|
||||
/>
|
||||
<div
|
||||
class="c5 c6 c7"
|
||||
>
|
||||
<div
|
||||
class="c23 css-zhpkf8"
|
||||
>
|
||||
Order routing
|
||||
</div>
|
||||
<div
|
||||
class="c22"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="c18 css-zhpkf8"
|
||||
>
|
||||
Uniswap API
|
||||
</div>
|
||||
<div
|
||||
class="c8 css-xdrz3"
|
||||
data-testid="OUTPUT-amount"
|
||||
>
|
||||
<0.00001 GHI
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c25"
|
||||
style="padding: .75rem 1rem;"
|
||||
>
|
||||
<div
|
||||
class="c23 css-k51stg"
|
||||
style="width: 100%;"
|
||||
>
|
||||
Output is estimated. You will receive at least
|
||||
<b>
|
||||
0.00000000000000098 DEF
|
||||
</b>
|
||||
or the transaction will revert.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c25"
|
||||
data-testid="recipient-info"
|
||||
style="padding: 12px 0px 0px 0px;"
|
||||
>
|
||||
<div
|
||||
class="c23 css-8mokm4"
|
||||
>
|
||||
Output will be sent to
|
||||
<b
|
||||
title="0x0000000000000000000000000000000000000004"
|
||||
<div
|
||||
class="c10"
|
||||
>
|
||||
0x0000...0004
|
||||
</b>
|
||||
<img
|
||||
alt="GHI logo"
|
||||
class="c11"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x0000000000000000000000000000000000000003/logo.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c12 c13"
|
||||
/>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
@ -2,7 +2,6 @@ import { SupportedChainId } from 'constants/chains'
|
||||
import { transparentize } from 'polished'
|
||||
import { ReactNode } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
@ -64,13 +63,6 @@ export const ArrowWrapper = styled.div<{ clickable: boolean }>`
|
||||
: null}
|
||||
`
|
||||
|
||||
export const TruncatedText = styled(Text)`
|
||||
text-overflow: ellipsis;
|
||||
max-width: 220px;
|
||||
overflow: hidden;
|
||||
text-align: right;
|
||||
`
|
||||
|
||||
// styles
|
||||
export const Dots = styled.span`
|
||||
&::after {
|
||||
@ -136,7 +128,7 @@ export function SwapCallbackError({ error }: { error: ReactNode }) {
|
||||
export const SwapShowAcceptChanges = styled(AutoColumn)`
|
||||
background-color: ${({ theme }) => transparentize(0.95, theme.deprecated_primary3)};
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
padding: 0.5rem;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
margin-top: 8px;
|
||||
`
|
||||
|
@ -46,7 +46,7 @@ import useIsArgentWallet from '../../hooks/useIsArgentWallet'
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
import { useTokenBalance } from '../../state/connection/hooks'
|
||||
import { TransactionType } from '../../state/transactions/types'
|
||||
import { BackArrow, ExternalLink, ThemedText } from '../../theme'
|
||||
import { BackArrowLink, ExternalLink, ThemedText } from '../../theme'
|
||||
import { isAddress } from '../../utils'
|
||||
import { calculateGasMargin } from '../../utils/calculateGasMargin'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
@ -725,7 +725,7 @@ export default function MigrateV2Pair() {
|
||||
<BodyWrapper style={{ padding: 24 }}>
|
||||
<AutoColumn gap="16px">
|
||||
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
|
||||
<BackArrow to="/migrate/v2" />
|
||||
<BackArrowLink to="/migrate/v2" />
|
||||
<ThemedText.DeprecatedMediumHeader>
|
||||
<Trans>Migrate V2 Liquidity</Trans>
|
||||
</ThemedText.DeprecatedMediumHeader>
|
||||
|
@ -20,7 +20,7 @@ import { Dots } from '../../components/swap/styleds'
|
||||
import { V2_FACTORY_ADDRESSES } from '../../constants/addresses'
|
||||
import { useTokenBalancesWithLoadingIndicator } from '../../state/connection/hooks'
|
||||
import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks'
|
||||
import { BackArrow, StyledInternalLink, ThemedText } from '../../theme'
|
||||
import { BackArrowLink, StyledInternalLink, ThemedText } from '../../theme'
|
||||
import { BodyWrapper } from '../AppBody'
|
||||
|
||||
function EmptyState({ message }: { message: ReactNode }) {
|
||||
@ -116,7 +116,7 @@ export default function MigrateV2() {
|
||||
<BodyWrapper style={{ padding: 24 }}>
|
||||
<AutoColumn gap="16px">
|
||||
<AutoRow style={{ alignItems: 'center', justifyContent: 'space-between' }} gap="8px">
|
||||
<BackArrow to="/pools" />
|
||||
<BackArrowLink to="/pools" />
|
||||
<ThemedText.DeprecatedMediumHeader>
|
||||
<Trans>Migrate V2 Liquidity</Trans>
|
||||
</ThemedText.DeprecatedMediumHeader>
|
||||
|
@ -571,22 +571,22 @@ export function Swap({
|
||||
showCancel={true}
|
||||
/>
|
||||
<SwapHeader autoSlippage={autoSlippage} />
|
||||
<ConfirmSwapModal
|
||||
isOpen={showConfirm}
|
||||
trade={trade}
|
||||
originalTrade={tradeToConfirm}
|
||||
onAcceptChanges={handleAcceptChanges}
|
||||
attemptingTxn={attemptingTxn}
|
||||
txHash={txHash}
|
||||
recipient={recipient}
|
||||
allowedSlippage={allowedSlippage}
|
||||
onConfirm={handleSwap}
|
||||
swapErrorMessage={swapErrorMessage}
|
||||
onDismiss={handleConfirmDismiss}
|
||||
swapQuoteReceivedDate={swapQuoteReceivedDate}
|
||||
fiatValueInput={fiatValueTradeInput}
|
||||
fiatValueOutput={fiatValueTradeOutput}
|
||||
/>
|
||||
{trade && showConfirm && (
|
||||
<ConfirmSwapModal
|
||||
trade={trade}
|
||||
originalTrade={tradeToConfirm}
|
||||
onAcceptChanges={handleAcceptChanges}
|
||||
attemptingTxn={attemptingTxn}
|
||||
txHash={txHash}
|
||||
allowedSlippage={allowedSlippage}
|
||||
onConfirm={handleSwap}
|
||||
swapErrorMessage={swapErrorMessage}
|
||||
onDismiss={handleConfirmDismiss}
|
||||
swapQuoteReceivedDate={swapQuoteReceivedDate}
|
||||
fiatValueInput={fiatValueTradeInput}
|
||||
fiatValueOutput={fiatValueTradeOutput}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'relative' }}>
|
||||
<SwapSection>
|
||||
|
@ -467,14 +467,15 @@ export const SpinnerSVG = styled.svg`
|
||||
${SpinnerCss}
|
||||
`
|
||||
|
||||
const BackArrowLink = styled(StyledInternalLink)`
|
||||
const BackArrowIcon = styled(ArrowLeft)`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
export function BackArrow({ to }: { to: string }) {
|
||||
|
||||
export function BackArrowLink({ to }: { to: string }) {
|
||||
return (
|
||||
<BackArrowLink to={to}>
|
||||
<ArrowLeft />
|
||||
</BackArrowLink>
|
||||
<StyledInternalLink to={to}>
|
||||
<BackArrowIcon />
|
||||
</StyledInternalLink>
|
||||
)
|
||||
}
|
||||
|
||||
@ -523,3 +524,11 @@ export const GlowEffect = styled.div`
|
||||
export const CautionTriangle = styled(AlertTriangle)`
|
||||
color: ${({ theme }) => theme.accentWarning};
|
||||
`
|
||||
|
||||
export const Divider = styled.div`
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
border-width: 0;
|
||||
margin: 0;
|
||||
background-color: ${({ theme }) => theme.backgroundOutline};
|
||||
`
|
||||
|
@ -52,6 +52,9 @@ export const ThemedText = {
|
||||
MediumHeader(props: TextProps) {
|
||||
return <TextWrapper fontWeight={400} fontSize={20} color="textPrimary" {...props} />
|
||||
},
|
||||
SubHeaderLarge(props: TextProps) {
|
||||
return <TextWrapper fontWeight={500} fontSize={20} color="textPrimary" {...props} />
|
||||
},
|
||||
SubHeader(props: TextProps) {
|
||||
return <TextWrapper fontWeight={500} fontSize={16} color="textPrimary" lineHeight="24px" {...props} />
|
||||
},
|
||||
|
@ -67,15 +67,18 @@ const fonts = {
|
||||
code: 'courier, courier new, serif',
|
||||
}
|
||||
|
||||
const gapValues = {
|
||||
xs: '4px',
|
||||
sm: '8px',
|
||||
md: '12px',
|
||||
lg: '24px',
|
||||
xl: '32px',
|
||||
}
|
||||
export type Gap = keyof typeof gapValues
|
||||
|
||||
function getSettings(darkMode: boolean) {
|
||||
return {
|
||||
grids: {
|
||||
xs: '4px',
|
||||
sm: '8px',
|
||||
md: '12px',
|
||||
lg: '24px',
|
||||
xl: '32px',
|
||||
},
|
||||
grids: gapValues,
|
||||
fonts,
|
||||
|
||||
// shadows
|
||||
@ -118,7 +121,7 @@ export const ThemedGlobalStyle = createGlobalStyle`
|
||||
background-color: ${({ theme }) => theme.background} !important;
|
||||
}
|
||||
|
||||
summary::-webkit-details-marker {
|
||||
summary::-webkit-details-marker {
|
||||
display:none;
|
||||
}
|
||||
|
||||
|
107
src/utils/loggingFormatters.ts
Normal file
107
src/utils/loggingFormatters.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { SwapPriceUpdateUserResponse } from '@uniswap/analytics-events'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import {
|
||||
formatPercentInBasisPointsNumber,
|
||||
formatPercentNumber,
|
||||
formatToDecimal,
|
||||
getDurationFromDateMilliseconds,
|
||||
getDurationUntilTimestampSeconds,
|
||||
getTokenAddress,
|
||||
} from 'lib/utils/analytics'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
|
||||
import { RoutingDiagramEntry } from './getRoutingDiagramEntries'
|
||||
import { computeRealizedPriceImpact } from './prices'
|
||||
|
||||
const formatRoutesEventProperties = (routes: RoutingDiagramEntry[]) => {
|
||||
const routesEventProperties: Record<string, any[]> = {
|
||||
routes_percentages: [],
|
||||
routes_protocols: [],
|
||||
}
|
||||
|
||||
routes.forEach((route, index) => {
|
||||
routesEventProperties['routes_percentages'].push(formatPercentNumber(route.percent))
|
||||
routesEventProperties['routes_protocols'].push(route.protocol)
|
||||
routesEventProperties[`route_${index}_input_currency_symbols`] = route.path.map(
|
||||
(pathStep) => pathStep[0].symbol ?? ''
|
||||
)
|
||||
routesEventProperties[`route_${index}_output_currency_symbols`] = route.path.map(
|
||||
(pathStep) => pathStep[1].symbol ?? ''
|
||||
)
|
||||
routesEventProperties[`route_${index}_input_currency_addresses`] = route.path.map((pathStep) =>
|
||||
getTokenAddress(pathStep[0])
|
||||
)
|
||||
routesEventProperties[`route_${index}_output_currency_addresses`] = route.path.map((pathStep) =>
|
||||
getTokenAddress(pathStep[1])
|
||||
)
|
||||
routesEventProperties[`route_${index}_fee_amounts_hundredths_of_bps`] = route.path.map((pathStep) => pathStep[2])
|
||||
})
|
||||
|
||||
return routesEventProperties
|
||||
}
|
||||
|
||||
export const formatSwapPriceUpdatedEventProperties = (
|
||||
trade: InterfaceTrade,
|
||||
priceUpdate: number | undefined,
|
||||
response: SwapPriceUpdateUserResponse
|
||||
) => ({
|
||||
chain_id:
|
||||
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
|
||||
? trade.inputAmount.currency.chainId
|
||||
: undefined,
|
||||
response,
|
||||
token_in_symbol: trade.inputAmount.currency.symbol,
|
||||
token_out_symbol: trade.outputAmount.currency.symbol,
|
||||
price_update_basis_points: priceUpdate,
|
||||
})
|
||||
|
||||
interface AnalyticsEventProps {
|
||||
trade: InterfaceTrade
|
||||
hash: string | undefined
|
||||
allowedSlippage: Percent
|
||||
transactionDeadlineSecondsSinceEpoch: number | undefined
|
||||
isAutoSlippage: boolean
|
||||
isAutoRouterApi: boolean
|
||||
swapQuoteReceivedDate: Date | undefined
|
||||
routes: RoutingDiagramEntry[]
|
||||
fiatValueInput?: number
|
||||
fiatValueOutput?: number
|
||||
}
|
||||
|
||||
export const formatSwapButtonClickEventProperties = ({
|
||||
trade,
|
||||
hash,
|
||||
allowedSlippage,
|
||||
transactionDeadlineSecondsSinceEpoch,
|
||||
isAutoSlippage,
|
||||
isAutoRouterApi,
|
||||
swapQuoteReceivedDate,
|
||||
routes,
|
||||
fiatValueInput,
|
||||
fiatValueOutput,
|
||||
}: AnalyticsEventProps) => ({
|
||||
estimated_network_fee_usd: trade.gasUseEstimateUSD,
|
||||
transaction_hash: hash,
|
||||
transaction_deadline_seconds: getDurationUntilTimestampSeconds(transactionDeadlineSecondsSinceEpoch),
|
||||
token_in_address: trade ? getTokenAddress(trade.inputAmount.currency) : undefined,
|
||||
token_out_address: trade ? getTokenAddress(trade.outputAmount.currency) : undefined,
|
||||
token_in_symbol: trade.inputAmount.currency.symbol,
|
||||
token_out_symbol: trade.outputAmount.currency.symbol,
|
||||
token_in_amount: trade ? formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals) : undefined,
|
||||
token_out_amount: trade ? formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals) : undefined,
|
||||
token_in_amount_usd: fiatValueInput,
|
||||
token_out_amount_usd: fiatValueOutput,
|
||||
price_impact_basis_points: trade ? formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)) : undefined,
|
||||
allowed_slippage_basis_points: formatPercentInBasisPointsNumber(allowedSlippage),
|
||||
is_auto_router_api: isAutoRouterApi,
|
||||
is_auto_slippage: isAutoSlippage,
|
||||
chain_id:
|
||||
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
|
||||
? trade.inputAmount.currency.chainId
|
||||
: undefined,
|
||||
duration_from_first_quote_to_swap_submission_milliseconds: swapQuoteReceivedDate
|
||||
? getDurationFromDateMilliseconds(swapQuoteReceivedDate)
|
||||
: undefined,
|
||||
swap_quote_block_number: trade.blockNumber,
|
||||
...formatRoutesEventProperties(routes),
|
||||
})
|
Loading…
Reference in New Issue
Block a user