Swap polish 02 (#93)

* rework swap UI

* Add v2/v3 route toggle UX

* improve button and progress styles

* put optional route in swap header

* Further refinements

* Alt swap layout

* clean up

* Tweak route ui

* merge main

* update swap header

* adjust currency select ui
This commit is contained in:
Callil Capuozzo 2021-05-03 16:32:40 -04:00 committed by GitHub
parent d7785942b1
commit 4c2cb5b0c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 422 additions and 384 deletions

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { darken, lighten } from 'polished' import { darken } from 'polished'
import { RowBetween } from '../Row' import { RowBetween } from '../Row'
import { ChevronDown, Check } from 'react-feather' import { ChevronDown, Check } from 'react-feather'
@ -32,6 +32,15 @@ const Base = styled(RebassButton)<{
z-index: 1; z-index: 1;
&:disabled { &:disabled {
cursor: auto; cursor: auto;
pointer-events: none;
}
will-change: transform;
transition: transform 450ms ease;
transform: perspective(1px) translateZ(0);
&:hover {
transform: scale(0.99);
} }
> * { > * {
@ -59,14 +68,14 @@ export const ButtonPrimary = styled(Base)`
} }
&:disabled { &:disabled {
background-color: ${({ theme, altDisabledStyle, disabled }) => background-color: ${({ theme, altDisabledStyle, disabled }) =>
altDisabledStyle ? (disabled ? theme.bg3 : theme.primary1) : theme.bg3}; altDisabledStyle ? (disabled ? theme.primary1 : theme.primary1) : theme.primary1};
color: ${({ theme, altDisabledStyle, disabled }) => color: white;
altDisabledStyle ? (disabled ? theme.text3 : 'white') : theme.text3};
cursor: auto; cursor: auto;
box-shadow: none; box-shadow: none;
border: 1px solid transparent; border: 1px solid transparent;
outline: none; outline: none;
opacity: ${({ altDisabledStyle }) => (altDisabledStyle ? '0.5' : '1')}; opacity: 0.4;
opacity: ${({ altDisabledStyle }) => (altDisabledStyle ? '0.5' : '0.4')};
} }
` `
@ -270,12 +279,14 @@ export const ButtonWhite = styled(Base)`
` `
const ButtonConfirmedStyle = styled(Base)` const ButtonConfirmedStyle = styled(Base)`
background-color: ${({ theme }) => lighten(0.5, theme.green1)}; background-color: ${({ theme }) => theme.bg3};
color: ${({ theme }) => theme.green1}; color: ${({ theme }) => theme.text1};
border: 1px solid ${({ theme }) => theme.green1}; /* border: 1px solid ${({ theme }) => theme.green1}; */
&:disabled { &:disabled {
opacity: 50%; /* opacity: 50%; */
background-color: ${({ theme }) => theme.bg2};
color: ${({ theme }) => theme.text2};
cursor: auto; cursor: auto;
} }
` `

@ -9,6 +9,7 @@ import { useCurrencyBalance } from '../../state/wallet/hooks'
import CurrencySearchModal from '../SearchModal/CurrencySearchModal' import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
import CurrencyLogo from '../CurrencyLogo' import CurrencyLogo from '../CurrencyLogo'
import DoubleCurrencyLogo from '../DoubleLogo' import DoubleCurrencyLogo from '../DoubleLogo'
import { ButtonGray } from '../Button'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { Input as NumericalInput } from '../NumericalInput' import { Input as NumericalInput } from '../NumericalInput'
@ -52,24 +53,24 @@ const Container = styled.div<{ hideInput: boolean }>`
} }
` `
const CurrencySelect = styled.button<{ selected: boolean; hideInput?: boolean }>` const CurrencySelect = styled(ButtonGray)<{ selected: boolean; hideInput?: boolean }>`
align-items: center; align-items: center;
font-size: 20px; font-size: 24px;
font-weight: 500; font-weight: 500;
background-color: ${({ selected, theme }) => (selected ? theme.bg2 : theme.primary1)}; background-color: ${({ selected, theme }) => (selected ? theme.bg2 : theme.primary1)};
color: ${({ selected, theme }) => (selected ? theme.text1 : theme.white)}; color: ${({ selected, theme }) => (selected ? theme.text1 : theme.white)};
border-radius: 12px; border-radius: 16px;
/* box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')}; */ box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')};
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075); box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
outline: none; outline: none;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
border: none; border: none;
height: ${({ hideInput }) => (hideInput ? '2.4rem' : '2.4rem')};
height: ${({ hideInput }) => (hideInput ? '2.8rem' : '2.4rem')};
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')}; width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
padding: 0 8px;
justify-content: space-between;
margin-right: 12px;
:focus, :focus,
:hover { :hover {
background-color: ${({ selected, theme }) => (selected ? theme.bg3 : darken(0.05, theme.primary1))}; background-color: ${({ selected, theme }) => (selected ? theme.bg3 : darken(0.05, theme.primary1))};
@ -79,7 +80,7 @@ const CurrencySelect = styled.button<{ selected: boolean; hideInput?: boolean }>
const InputRow = styled.div<{ selected: boolean }>` const InputRow = styled.div<{ selected: boolean }>`
${({ theme }) => theme.flexRowNoWrap} ${({ theme }) => theme.flexRowNoWrap}
align-items: center; align-items: center;
padding: ${({ selected }) => (selected ? '.75rem 0.75rem .75rem 1rem' : '.75rem 0.75rem .75rem 1rem')}; padding: ${({ selected }) => (selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 0.75rem 1rem')};
` `
const LabelRow = styled.div` const LabelRow = styled.div`
@ -88,13 +89,17 @@ const LabelRow = styled.div`
color: ${({ theme }) => theme.text1}; color: ${({ theme }) => theme.text1};
font-size: 0.75rem; font-size: 0.75rem;
line-height: 1rem; line-height: 1rem;
padding: 0rem 1rem 0.75rem 1rem; padding: 0 1rem 1rem;
span:hover { span:hover {
cursor: pointer; cursor: pointer;
color: ${({ theme }) => darken(0.2, theme.text2)}; color: ${({ theme }) => darken(0.2, theme.text2)};
} }
` `
const FiatRow = styled(LabelRow)`
justify-content: flex-end;
`
const Aligner = styled.span` const Aligner = styled.span`
display: flex; display: flex;
align-items: center; align-items: center;
@ -102,7 +107,7 @@ const Aligner = styled.span`
` `
const StyledDropDown = styled(DropDown)<{ selected: boolean }>` const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
margin: 0 0.25rem 0 0.5rem; margin: 0 0.25rem 0 0.35rem;
height: 35%; height: 35%;
path { path {
@ -113,27 +118,27 @@ const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
const StyledTokenName = styled.span<{ active?: boolean }>` const StyledTokenName = styled.span<{ active?: boolean }>`
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')} ${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
font-size: ${({ active }) => (active ? '20px' : '18px')}; font-size: ${({ active }) => (active ? '18px' : '18px')};
` `
const StyledBalanceMax = styled.button<{ disabled?: boolean }>` const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
background-color: ${({ theme }) => theme.primary5}; background-color: transparent;
border: 1px solid ${({ theme }) => theme.primary5}; border: none;
border-radius: 0.5rem; border-radius: 12px;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
/* margin-left: 0.5rem; */ padding: 0;
color: ${({ theme }) => theme.primary1}; color: ${({ theme }) => theme.primary1};
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)}; opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')}; pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
margin-left: 0.25rem;
:hover { :hover {
border: 1px solid ${({ theme }) => theme.primary1}; /* border: 1px solid ${({ theme }) => theme.primary1}; */
} }
:focus { :focus {
border: 1px solid ${({ theme }) => theme.primary1}; /* border: 1px solid ${({ theme }) => theme.primary1}; */
outline: none; outline: none;
} }
@ -166,7 +171,6 @@ export default function CurrencyInputPanel({
onUserInput, onUserInput,
onMax, onMax,
showMaxButton, showMaxButton,
label = 'Input',
onCurrencySelect, onCurrencySelect,
currency, currency,
otherCurrency, otherCurrency,
@ -210,19 +214,6 @@ export default function CurrencyInputPanel({
</FixedContainer> </FixedContainer>
)} )}
<Container hideInput={hideInput}> <Container hideInput={hideInput}>
{!hideInput && (
<LabelRow style={{ padding: ' 1rem 1rem 0rem 1rem' }}>
<RowBetween>
<TYPE.body color={theme.text3} fontWeight={500} fontSize={14}>
{label}
</TYPE.body>
<TYPE.label>
{fiatValueOfTypedAmount ? '~' : ''}${fiatValueOfTypedAmount?.toSignificant(4) ?? '-'}
</TYPE.label>
</RowBetween>
</LabelRow>
)}
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={!onCurrencySelect}> <InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={!onCurrencySelect}>
<CurrencySelect <CurrencySelect
selected={!!currency} selected={!!currency}
@ -261,24 +252,27 @@ export default function CurrencyInputPanel({
</Aligner> </Aligner>
</CurrencySelect> </CurrencySelect>
{!hideInput && ( {!hideInput && (
<NumericalInput <>
className="token-amount-input" {' '}
value={value} <NumericalInput
onUserInput={(val) => { className="token-amount-input"
onUserInput(val) value={value}
}} onUserInput={(val) => {
/> onUserInput(val)
}}
/>
</>
)} )}
</InputRow> </InputRow>
{!hideInput && !hideBalance && ( {!hideInput && !hideBalance && (
<LabelRow> <FiatRow>
<RowBetween> <RowBetween>
{account && ( {account && (
<RowFixed> <RowFixed style={{ height: '17px' }}>
<TYPE.body <TYPE.body
onClick={onMax} onClick={onMax}
color={theme.text3} color={theme.text2}
fontWeight={500} fontWeight={400}
fontSize={14} fontSize={14}
style={{ display: 'inline', cursor: 'pointer' }} style={{ display: 'inline', cursor: 'pointer' }}
> >
@ -287,17 +281,26 @@ export default function CurrencyInputPanel({
selectedCurrencyBalance?.toSignificant(4) + selectedCurrencyBalance?.toSignificant(4) +
' ' + ' ' +
currency.symbol currency.symbol
: ' '} : '-'}
</TYPE.body> </TYPE.body>
{showMaxButton && selectedCurrencyBalance ? (
<StyledBalanceMax disabled={!showMaxButton} onClick={onMax}>
(Max)
</StyledBalanceMax>
) : (
<StyledBalanceMax disabled={!showMaxButton} onClick={onMax}>
{''}
</StyledBalanceMax>
)}
</RowFixed> </RowFixed>
)} )}
{showMaxButton && (
<StyledBalanceMax disabled={!showMaxButton} onClick={onMax}> <TYPE.body fontSize={14} color={fiatValueOfTypedAmount ? theme.text2 : theme.text4}>
Max {fiatValueOfTypedAmount ? '~' : ''}$
</StyledBalanceMax> {fiatValueOfTypedAmount ? Number(fiatValueOfTypedAmount?.toSignificant(6)).toLocaleString('en') : '-'}
)} </TYPE.body>
</RowBetween> </RowBetween>
</LabelRow> </FiatRow>
)} )}
</Container> </Container>
{onCurrencySelect && ( {onCurrencySelect && (

@ -1,4 +1,4 @@
import { ChainId, TokenAmount } from '@uniswap/sdk-core' import { ChainId } from '@uniswap/sdk-core'
import React, { useState } from 'react' import React, { useState } from 'react'
import { Text } from 'rebass' import { Text } from 'rebass'
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
@ -9,11 +9,11 @@ import styled from 'styled-components'
import Logo from '../../assets/svg/logo.svg' import Logo from '../../assets/svg/logo.svg'
import LogoDark from '../../assets/svg/logo_white.svg' import LogoDark from '../../assets/svg/logo_white.svg'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useDarkModeManager } from '../../state/user/hooks' import { useDarkModeManager } from '../../state/user/hooks'
import { useETHBalances, useAggregateUniBalance } from '../../state/wallet/hooks' import { useETHBalances } from '../../state/wallet/hooks'
import { CardNoise } from '../earn/styled' import { CardNoise } from '../earn/styled'
import { CountUp } from 'use-count-up'
import { TYPE, ExternalLink } from '../../theme' import { TYPE, ExternalLink } from '../../theme'
import { YellowCard } from '../Card' import { YellowCard } from '../Card'
@ -28,7 +28,6 @@ import { useUserHasSubmittedClaim } from '../../state/transactions/hooks'
import { Dots } from '../swap/styleds' import { Dots } from '../swap/styleds'
import Modal from '../Modal' import Modal from '../Modal'
import UniBalanceContent from './UniBalanceContent' import UniBalanceContent from './UniBalanceContent'
import usePrevious from '../../hooks/usePrevious'
const HeaderFrame = styled.div` const HeaderFrame = styled.div`
display: grid; display: grid;
@ -40,10 +39,10 @@ const HeaderFrame = styled.div`
width: 100%; width: 100%;
top: 0; top: 0;
position: relative; position: relative;
border-bottom: 1px solid rgba(0, 0, 0, 0.1); /* border-bottom: 1px solid rgba(0, 0, 0, 0.1); */
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
z-index: 21; z-index: 21;
background-color: ${({ theme }) => theme.bg0}; background-color: ${({ theme }) => theme.bg1};
${({ theme }) => theme.mediaWidth.upToMedium` ${({ theme }) => theme.mediaWidth.upToMedium`
display: flex; display: flex;
@ -314,14 +313,9 @@ export default function Header() {
const { claimTxn } = useUserHasSubmittedClaim(account ?? undefined) const { claimTxn } = useUserHasSubmittedClaim(account ?? undefined)
const aggregateBalance: TokenAmount | undefined = useAggregateUniBalance()
const [showUniBalanceModal, setShowUniBalanceModal] = useState(false) const [showUniBalanceModal, setShowUniBalanceModal] = useState(false)
const showClaimPopup = useShowClaimPopup() const showClaimPopup = useShowClaimPopup()
const countUpValue = aggregateBalance?.toFixed(0) ?? '0'
const countUpValuePrevious = usePrevious(countUpValue) ?? '0'
return ( return (
<HeaderFrame> <HeaderFrame>
<ClaimModal /> <ClaimModal />
@ -376,33 +370,7 @@ export default function Header() {
<CardNoise /> <CardNoise />
</UNIWrapper> </UNIWrapper>
)} )}
{/* I want to put this in the overflow menu now */}
{!availableClaim && aggregateBalance && (
<UNIWrapper onClick={() => setShowUniBalanceModal(true)}>
<UNIAmount active={!!account && !availableClaim} style={{ pointerEvents: 'auto' }}>
{account && (
<HideSmall>
<TYPE.white
style={{
paddingRight: '.4rem',
}}
>
<CountUp
key={countUpValue}
isCounting
start={parseFloat(countUpValuePrevious)}
end={parseFloat(countUpValue)}
thousandsSeparator={','}
duration={1}
/>
</TYPE.white>
</HideSmall>
)}
UNI
</UNIAmount>
<CardNoise />
</UNIWrapper>
)}
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}> <AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
{account && userEthBalance ? ( {account && userEthBalance ? (
<BalanceText style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}> <BalanceText style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>

@ -21,7 +21,7 @@ export const Popup = styled.div`
display: inline-block; display: inline-block;
width: 100%; width: 100%;
padding: 1em; padding: 1em;
background-color: ${({ theme }) => theme.bg1}; background-color: ${({ theme }) => theme.bg0};
position: relative; position: relative;
border-radius: 10px; border-radius: 10px;
padding: 20px; padding: 20px;

@ -33,7 +33,7 @@ const MobilePopupInner = styled.div`
const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>` const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>`
position: fixed; position: fixed;
top: ${({ extraPadding }) => (extraPadding ? '108px' : '88px')}; top: ${({ extraPadding }) => (extraPadding ? '72px' : '88px')};
right: 1rem; right: 1rem;
max-width: 355px !important; max-width: 355px !important;
width: 100%; width: 100%;

@ -2,26 +2,26 @@ import React, { useContext } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { ArrowDown } from 'react-feather' import { TYPE } from '../../theme'
const Wrapper = styled(AutoColumn)` const Wrapper = styled(AutoColumn)`
margin-left: 8px; margin-right: 8px;
height: 100%; height: 100%;
` `
const Grouping = styled(AutoColumn)` const Grouping = styled(AutoColumn)`
width: fit-content; width: fit-content;
padding: 4px; padding: 4px;
background-color: ${({ theme }) => theme.bg2}; /* background-color: ${({ theme }) => theme.bg2}; */
border-radius: 16px; border-radius: 16px;
` `
const Circle = styled.div<{ confirmed?: boolean; disabled?: boolean }>` const Circle = styled.div<{ confirmed?: boolean; disabled?: boolean }>`
width: 100%; width: 48px;
height: 100%; height: 48px;
background-color: ${({ theme, confirmed, disabled }) => background-color: ${({ theme, confirmed, disabled }) =>
disabled ? theme.bg3 : confirmed ? theme.green1 : theme.primary1}; disabled ? theme.bg3 : confirmed ? theme.green1 : theme.primary1};
border-radius: 12px; border-radius: 50%;
color: ${({ theme, disabled }) => (disabled ? theme.text3 : theme.text1)}; color: ${({ theme, disabled }) => (disabled ? theme.text3 : theme.text1)};
display: flex; display: flex;
align-items: center; align-items: center;
@ -37,12 +37,6 @@ const CircleRow = styled.div`
align-items: center; align-items: center;
` `
const StyledArrowDown = styled(ArrowDown)`
margin: 0.5rem;
min-height: 14px;
/* color: ${({ theme }) => theme.text1}; */
`
interface ProgressCirclesProps { interface ProgressCirclesProps {
steps: boolean[] steps: boolean[]
disabled?: boolean disabled?: boolean
@ -68,13 +62,13 @@ export default function ProgressCircles({ steps, disabled = false, ...rest }: Pr
return ( return (
<CircleRow key={i}> <CircleRow key={i}>
<Circle confirmed={step} disabled={disabled || (!steps[i - 1] && i !== 0)}> <Circle confirmed={step} disabled={disabled || (!steps[i - 1] && i !== 0)}>
{step ? '✓' : i + 1} {step ? '✓' : i + 1 + '.'}
</Circle> </Circle>
<StyledArrowDown size="16" color={theme.text3} /> <TYPE.main color={theme.text4}>|</TYPE.main>
</CircleRow> </CircleRow>
) )
})} })}
<Circle disabled={disabled || !steps[steps.length - 1]}>{steps.length + 1}</Circle> <Circle disabled={disabled || !steps[steps.length - 1]}>{steps.length + 1 + '.'}</Circle>
</Grouping> </Grouping>
</Wrapper> </Wrapper>
) )

@ -21,8 +21,6 @@ import QuestionHelper from '../QuestionHelper'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import Toggle from '../Toggle' import Toggle from '../Toggle'
import TransactionSettings from '../TransactionSettings' import TransactionSettings from '../TransactionSettings'
import { Moon, Sun } from 'react-feather'
import { useDarkModeManager } from '../../state/user/hooks'
const StyledMenuIcon = styled(Settings)` const StyledMenuIcon = styled(Settings)`
height: 20px; height: 20px;
@ -86,6 +84,7 @@ const StyledMenu = styled.div`
const MenuFlyout = styled.span` const MenuFlyout = styled.span`
min-width: 20.125rem; min-width: 20.125rem;
background-color: ${({ theme }) => theme.bg2}; background-color: ${({ theme }) => theme.bg2};
border: 1px solid ${({ theme }) => theme.bg3};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.01); 0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 12px; border-radius: 12px;
@ -93,7 +92,7 @@ const MenuFlyout = styled.span`
flex-direction: column; flex-direction: column;
font-size: 1rem; font-size: 1rem;
position: absolute; position: absolute;
top: 3rem; top: 2rem;
right: 0rem; right: 0rem;
z-index: 100; z-index: 100;
@ -134,8 +133,6 @@ export default function SettingsTab() {
// show confirmation view before turning on // show confirmation view before turning on
const [showConfirmation, setShowConfirmation] = useState(false) const [showConfirmation, setShowConfirmation] = useState(false)
const [darkMode, toggleDarkMode] = useDarkModeManager()
useOnClickOutside(node, open ? toggle : undefined) useOnClickOutside(node, open ? toggle : undefined)
return ( return (
@ -245,10 +242,6 @@ export default function SettingsTab() {
}} }}
/> />
</RowBetween> </RowBetween>
{/* WIP */}
<StyledMenuButton onClick={() => toggleDarkMode()}>
{darkMode ? <Moon color={theme.text2} size={20} /> : <Sun size={20} />}
</StyledMenuButton>
</AutoColumn> </AutoColumn>
</MenuFlyout> </MenuFlyout>
)} )}

@ -3,9 +3,9 @@ import styled from 'styled-components'
import Popover, { PopoverProps } from '../Popover' import Popover, { PopoverProps } from '../Popover'
const TooltipContainer = styled.div` const TooltipContainer = styled.div`
width: 228px; width: 256px;
padding: 0.6rem 1rem; padding: 0.6rem 1rem;
line-height: 150%; /* line-height: 150%; */
font-weight: 400; font-weight: 400;
` `
@ -13,10 +13,18 @@ interface TooltipProps extends Omit<PopoverProps, 'content'> {
text: string text: string
} }
interface TooltipContentProps extends Omit<PopoverProps, 'content'> {
content: React.ReactNode
}
export default function Tooltip({ text, ...rest }: TooltipProps) { export default function Tooltip({ text, ...rest }: TooltipProps) {
return <Popover content={<TooltipContainer>{text}</TooltipContainer>} {...rest} /> return <Popover content={<TooltipContainer>{text}</TooltipContainer>} {...rest} />
} }
export function TooltipContent({ content, ...rest }: TooltipContentProps) {
return <Popover content={<TooltipContainer>{content}</TooltipContainer>} {...rest} />
}
export function MouseoverTooltip({ children, ...rest }: Omit<TooltipProps, 'show'>) { export function MouseoverTooltip({ children, ...rest }: Omit<TooltipProps, 'show'>) {
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
const open = useCallback(() => setShow(true), [setShow]) const open = useCallback(() => setShow(true), [setShow])
@ -29,3 +37,16 @@ export function MouseoverTooltip({ children, ...rest }: Omit<TooltipProps, 'show
</Tooltip> </Tooltip>
) )
} }
export function MouseoverTooltipContent({ content, children, ...rest }: Omit<TooltipContentProps, 'show'>) {
const [show, setShow] = useState(false)
const open = useCallback(() => setShow(true), [setShow])
const close = useCallback(() => setShow(false), [setShow])
return (
<TooltipContent {...rest} show={show} content={content}>
<div onMouseEnter={open} onMouseLeave={close}>
{children}
</div>
</TooltipContent>
)
}

@ -20,7 +20,7 @@ const Wrapper = styled.div`
padding: 1rem; padding: 1rem;
` `
const Section = styled(AutoColumn)<{ inline?: boolean }>` const Section = styled(AutoColumn)<{ inline?: boolean }>`
padding: ${({ inline }) => (inline ? '0' : '24px')}; padding: ${({ inline }) => (inline ? '0' : '0')};
` `
const BottomSection = styled(Section)` const BottomSection = styled(Section)`

@ -1,14 +1,10 @@
import { TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk' import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk' import { Trade as V3Trade } from '@uniswap/v3-sdk'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { Field } from '../../state/swap/actions'
import { useUserSlippageTolerance } from '../../state/user/hooks'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown } from '../../utils/prices' import { computeTradePriceBreakdown } from '../../utils/prices'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import QuestionHelper from '../QuestionHelper'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import FormattedPriceImpact from './FormattedPriceImpact' import FormattedPriceImpact from './FormattedPriceImpact'
import SwapRoute from './SwapRoute' import SwapRoute from './SwapRoute'
@ -16,74 +12,28 @@ import SwapRoute from './SwapRoute'
function TradeSummary({ trade }: { trade: V2Trade | V3Trade }) { function TradeSummary({ trade }: { trade: V2Trade | V3Trade }) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const { priceImpactWithoutFee, realizedLPFee } = computeTradePriceBreakdown(trade) const { priceImpactWithoutFee, realizedLPFee } = computeTradePriceBreakdown(trade)
const isExactIn = trade.tradeType === TradeType.EXACT_INPUT
const [allowedSlippage] = useUserSlippageTolerance()
const slippageAdjustedAmounts = computeSlippageAdjustedAmounts(trade, allowedSlippage)
const price = trade.executionPrice
return ( return (
<> <>
<AutoColumn style={{ padding: '8px 16px' }} gap="8px"> <AutoColumn gap={'8px'}>
<RowBetween> <RowBetween>
<RowFixed> <RowFixed>
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}> <TYPE.black fontSize={12} fontWeight={400} color={theme.text2}>
Price
</TYPE.black>
</RowFixed>
<TYPE.black color={theme.text1} fontSize={14}>
{'1 ' +
price?.quoteCurrency?.symbol +
' = ' +
price?.invert()?.toSignificant(6) +
' ' +
price?.baseCurrency?.symbol}
</TYPE.black>
</RowBetween>
<RowBetween>
<RowFixed>
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
Inverted Price
</TYPE.black>
</RowFixed>
<TYPE.black color={theme.text1} fontSize={14}>
{'1 ' + price?.baseCurrency?.symbol + ' = ' + price?.toSignificant(6) + ' ' + price?.quoteCurrency?.symbol}
</TYPE.black>
</RowBetween>
<RowBetween>
<RowFixed>
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
{isExactIn ? 'Minimum received' : 'Maximum sold'}
</TYPE.black>
<QuestionHelper text="Your transaction will revert if there is a large, unfavorable price movement before it is confirmed." />
</RowFixed>
<RowFixed>
<TYPE.black color={theme.text1} fontSize={14}>
{isExactIn
? `${slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4)} ${trade.outputAmount.currency.symbol}` ??
'-'
: `${slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4)} ${trade.inputAmount.currency.symbol}` ??
'-'}
</TYPE.black>
</RowFixed>
</RowBetween>
<RowBetween>
<RowFixed>
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
Price Impact Price Impact
</TYPE.black> </TYPE.black>
<QuestionHelper text="The difference between the market price and estimated price due to trade size." /> {/* <QuestionHelper text="The difference between the market price and estimated price due to trade size." /> */}
</RowFixed> </RowFixed>
<FormattedPriceImpact priceImpact={priceImpactWithoutFee} /> <FormattedPriceImpact priceImpact={priceImpactWithoutFee} />
</RowBetween> </RowBetween>
<RowBetween> <RowBetween>
<RowFixed> <RowFixed>
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}> <TYPE.black fontSize={12} fontWeight={400} color={theme.text2}>
Liquidity Provider Fee Liquidity Provider Fee
</TYPE.black> </TYPE.black>
<QuestionHelper text="A portion of each trade goes to liquidity providers as a protocol incentive." /> {/* <QuestionHelper text="A portion of each trade goes to liquidity providers as a protocol incentive." /> */}
</RowFixed> </RowFixed>
<TYPE.black fontSize={14} color={theme.text1}> <TYPE.black fontSize={12} color={theme.text1}>
{realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${trade.inputAmount.currency.symbol}` : '-'} {realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${trade.inputAmount.currency.symbol}` : '-'}
</TYPE.black> </TYPE.black>
</RowBetween> </RowBetween>
@ -116,7 +66,7 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}> <TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
Route Route
</TYPE.black> </TYPE.black>
<QuestionHelper text="Routing through these tokens resulted in the best price for your trade." /> {/* <QuestionHelper text="Routing through these tokens resulted in the best price for your trade." /> */}
</span> </span>
<SwapRoute trade={trade} /> <SwapRoute trade={trade} />
</RowBetween> </RowBetween>

@ -1,28 +1,14 @@
import { stringify } from 'qs' import { stringify } from 'qs'
import React, { useContext, useMemo } from 'react' import React, { useMemo } from 'react'
import { useLocation } from 'react-router' import { useLocation } from 'react-router'
import { Text } from 'rebass' import { Link } from 'react-router-dom'
import { ThemeContext } from 'styled-components'
import useParsedQueryString from '../../hooks/useParsedQueryString' import useParsedQueryString from '../../hooks/useParsedQueryString'
import useToggledVersion, { DEFAULT_VERSION, Version } from '../../hooks/useToggledVersion' import useToggledVersion, { DEFAULT_VERSION, Version } from '../../hooks/useToggledVersion'
import { TYPE } from '../../theme'
import { ButtonPrimary } from '../Button'
import { StyledInternalLink } from '../../theme' import { Zap } from 'react-feather'
import { YellowCard } from '../Card'
import { AutoColumn } from '../Column'
function VersionLinkContainer({ children }: { children: React.ReactNode }) {
const theme = useContext(ThemeContext)
return (
<YellowCard style={{ marginTop: '4px', padding: '0.5rem 0.5rem' }}>
<AutoColumn gap="sm" justify="center" style={{ alignItems: 'center', textAlign: 'center' }}>
<Text lineHeight="145.23%;" fontSize={14} fontWeight={400} color={theme.text1}>
{children}
</Text>
</AutoColumn>
</YellowCard>
)
}
export default function BetterTradeLink({ export default function BetterTradeLink({
version, version,
@ -45,12 +31,24 @@ export default function BetterTradeLink({
}, [location, search, version]) }, [location, search, version])
return ( return (
<VersionLinkContainer> <ButtonPrimary
{otherTradeNonexistent ? 'This trade can be executed on ' : 'There is a better price for this trade on '} as={Link}
<StyledInternalLink to={linkDestination}> to={linkDestination}
<b>Uniswap {version.toUpperCase()} </b> style={{
</StyledInternalLink> width: 'fit-content',
</VersionLinkContainer> padding: '.2rem .5rem',
wordBreak: 'keep-all',
height: '24px',
marginLeft: '.25rem',
}}
>
<Zap size={12} style={{ marginRight: '0.25rem' }} />
<TYPE.small style={{ lineHeight: '120%' }} fontSize={12}>
{otherTradeNonexistent
? `No liquidity! Click to trade with ${version.toUpperCase()}`
: `Get a better price on ${version.toUpperCase()}`}
</TYPE.small>
</ButtonPrimary>
) )
} }
@ -70,11 +68,12 @@ export function DefaultVersionLink() {
}, [location, search]) }, [location, search])
return ( return (
<VersionLinkContainer> <ButtonPrimary
Showing {version.toUpperCase()} price.{' '} as={Link}
<StyledInternalLink to={linkDestination}> to={linkDestination}
<b>Switch to Uniswap {DEFAULT_VERSION.toUpperCase()} </b> style={{ width: 'fit-content', marginTop: '4px', padding: '0.5rem 0.5rem' }}
</StyledInternalLink> >
</VersionLinkContainer> Showing {version.toUpperCase()} price. <b>Switch to Uniswap {DEFAULT_VERSION.toUpperCase()} </b>
</ButtonPrimary>
) )
} }

@ -81,10 +81,9 @@ export default function ConfirmSwapModal({
trade={trade} trade={trade}
disabledConfirm={showAcceptChanges} disabledConfirm={showAcceptChanges}
swapErrorMessage={swapErrorMessage} swapErrorMessage={swapErrorMessage}
allowedSlippage={allowedSlippage}
/> />
) : null ) : null
}, [allowedSlippage, onConfirm, showAcceptChanges, swapErrorMessage, trade]) }, [onConfirm, showAcceptChanges, swapErrorMessage, trade])
// text to show while loading // text to show while loading
const pendingText = `Swapping ${trade?.maximumAmountIn(allowedSlippage)?.toSignificant(6)} ${ const pendingText = `Swapping ${trade?.maximumAmountIn(allowedSlippage)?.toSignificant(6)} ${

@ -9,7 +9,7 @@ import { ErrorText, ErrorPill } from './styleds'
*/ */
export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) { export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) {
return ( return (
<ErrorText fontWeight={500} fontSize={14} severity={warningSeverity(priceImpact)}> <ErrorText fontWeight={500} fontSize={12} severity={warningSeverity(priceImpact)}>
{priceImpact {priceImpact
? priceImpact.lessThan(ONE_BIPS) ? priceImpact.lessThan(ONE_BIPS)
? `-${priceImpact.toFixed(2)}%` ? `-${priceImpact.toFixed(2)}%`
@ -21,7 +21,7 @@ export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Pe
export function SmallFormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) { export function SmallFormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) {
return ( return (
<ErrorPill fontWeight={500} fontSize={14} severity={warningSeverity(priceImpact)}> <ErrorPill fontWeight={500} fontSize={12} severity={warningSeverity(priceImpact)}>
{priceImpact {priceImpact
? priceImpact.lessThan(ONE_BIPS) ? priceImpact.lessThan(ONE_BIPS)
? `(-${priceImpact.toFixed(2)}%)` ? `(-${priceImpact.toFixed(2)}%)`

@ -1,51 +1,28 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { Version } from '../../hooks/useToggledVersion'
import Settings from '../Settings' import Settings from '../Settings'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
// import { Info } from 'react-feather'
const StyledSwapHeader = styled.div` const StyledSwapHeader = styled.div`
padding: 1rem 1.25rem 0.5rem 1.25rem; padding: 1rem 1.25rem 0.5rem 1.25rem;
width: 100%; width: 100%;
color: ${({ theme }) => theme.text2}; color: ${({ theme }) => theme.text2};
` `
//
// const InfoLink = styled(ExternalLink)`
// width: 100%;
// text-align: center;
// font-size: 14px;
// height: 20px;
// margin-right: 8px;
// color: ${({ theme }) => theme.text1};
// `
interface SwapHeaderProps { export default function SwapHeader() {
toggledVersion: Version
}
export default function SwapHeader({ toggledVersion }: SwapHeaderProps) {
return ( return (
<StyledSwapHeader> <StyledSwapHeader>
<RowBetween> <RowBetween>
<TYPE.black fontWeight={500} fontSize={16} style={{ opacity: '0.6' }}>
Swap ({toggledVersion})
</TYPE.black>
<RowFixed> <RowFixed>
{/* Send icon appears here when expert mode is toggled on */} <TYPE.black fontWeight={500} fontSize={16} style={{ marginRight: '8px' }}>
{/* <Send style={{ marginRight: '16px' }} size="20" onClick={() => onChangeRecipient('')} /> */} Swap{' '}
{/* This info icon should open info.uniswap.org with the pair */} </TYPE.black>
{/*{trade && (*/} </RowFixed>
{/* <InfoLink*/} <RowFixed>
{/* href={'https://info.uniswap.org/pair/' + trade.route.pairs[0].liquidityToken.address}*/} {/* <TradeInfo disabled={!trade} trade={trade} /> */}
{/* target="_blank"*/} {/* <div style={{ width: '8px' }}></div> */}
{/* >*/}
{/* <Info size="20" style={{ opacity: '0.6' }} />*/}
{/* </InfoLink>*/}
{/*)}*/}
<Settings /> <Settings />
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>

@ -1,61 +1,30 @@
import { Trade as V2Trade } from '@uniswap/v2-sdk' import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk' import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { Percent } from '@uniswap/sdk-core'
import React, { useContext, useMemo, useState } from 'react' import React, { useMemo } from 'react'
import { Repeat } from 'react-feather'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ThemeContext } from 'styled-components' import { computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
import { computeTradePriceBreakdown, formatExecutionPrice, warningSeverity } from '../../utils/prices'
import { ButtonError } from '../Button' import { ButtonError } from '../Button'
import { AutoColumn } from '../Column' import {} from '../Column'
import { AutoRow, RowBetween } from '../Row' import { AutoRow } from '../Row'
import { StyledBalanceMaxMini, SwapCallbackError } from './styleds' import { SwapCallbackError } from './styleds'
export default function SwapModalFooter({ export default function SwapModalFooter({
trade, trade,
onConfirm, onConfirm,
allowedSlippage,
swapErrorMessage, swapErrorMessage,
disabledConfirm, disabledConfirm,
}: { }: {
trade: V2Trade | V3Trade trade: V2Trade | V3Trade
allowedSlippage: Percent
onConfirm: () => void onConfirm: () => void
swapErrorMessage: string | undefined swapErrorMessage: string | undefined
disabledConfirm: boolean disabledConfirm: boolean
}) { }) {
const [showInverted, setShowInverted] = useState<boolean>(false)
const theme = useContext(ThemeContext)
const { priceImpactWithoutFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade]) const { priceImpactWithoutFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade])
const severity = warningSeverity(priceImpactWithoutFee) const severity = warningSeverity(priceImpactWithoutFee)
return ( return (
<> <>
<AutoColumn gap="0px">
<RowBetween align="center">
<Text fontWeight={400} fontSize={14} color={theme.text2}>
Price
</Text>
<Text
fontWeight={500}
fontSize={14}
color={theme.text1}
style={{
justifyContent: 'center',
alignItems: 'center',
display: 'flex',
textAlign: 'right',
paddingLeft: '10px',
}}
>
{formatExecutionPrice(trade, showInverted, allowedSlippage)}
<StyledBalanceMaxMini onClick={() => setShowInverted(!showInverted)}>
<Repeat size={14} />
</StyledBalanceMaxMini>
</Text>
</RowBetween>
</AutoColumn>
<AutoRow> <AutoRow>
<ButtonError <ButtonError
onClick={onConfirm} onClick={onConfirm}

@ -1,10 +1,10 @@
import { Percent, TradeType } from '@uniswap/sdk-core' import { Percent, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk' import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk' import { Trade as V3Trade } from '@uniswap/v3-sdk'
import React, { useContext } from 'react' import React, { useContext, useState } from 'react'
import { ArrowDown, AlertTriangle } from 'react-feather' import { ArrowDown, AlertTriangle } from 'react-feather'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ThemeContext } from 'styled-components' import styled, { ThemeContext } from 'styled-components'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { ButtonPrimary } from '../Button' import { ButtonPrimary } from '../Button'
import { isAddress, shortenAddress } from '../../utils' import { isAddress, shortenAddress } from '../../utils'
@ -13,6 +13,30 @@ import CurrencyLogo from '../CurrencyLogo'
import { RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import { TruncatedText, SwapShowAcceptChanges } from './styleds' import { TruncatedText, SwapShowAcceptChanges } from './styleds'
import { AdvancedSwapDetails } from './AdvancedSwapDetails'
import { LightCard } from '../Card'
import { DarkGreyCard } from '../Card'
import TradePrice from '../swap/TradePrice'
export const ArrowWrapper = styled.div`
padding: 4px;
border-radius: 12px;
height: 32px;
width: 32px;
position: relative;
margin-top: -18px;
margin-bottom: -18px;
left: calc(50% - 16px);
/* transform: rotate(90deg); */
display: flex;
justify-content: center;
align-items: center;
background-color: ${({ theme }) => theme.bg1};
/* border: 4px solid ${({ theme }) => theme.bg0}; */
z-index: 2;
`
export default function SwapModalHeader({ export default function SwapModalHeader({
trade, trade,
allowedSlippage, allowedSlippage,
@ -31,41 +55,82 @@ export default function SwapModalHeader({
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const [showInverted, setShowInverted] = useState<boolean>(false)
return ( return (
<AutoColumn gap={'md'} style={{ marginTop: '20px' }}> <AutoColumn gap={'4px'} style={{ marginTop: '1rem' }}>
<RowBetween align="flex-end"> <DarkGreyCard padding="0.75rem 1rem">
<RowFixed gap={'0px'}> <AutoColumn gap={'8px'}>
<CurrencyLogo currency={trade.inputAmount.currency} size={'24px'} style={{ marginRight: '12px' }} /> <RowBetween>
<TruncatedText <TYPE.body color={theme.text3} fontWeight={500} fontSize={14}>
fontSize={24} {'From'}
fontWeight={500} </TYPE.body>
color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? theme.primary1 : ''} <TYPE.body fontSize={14} color={theme.text3}>
> {'$-'}
{maximumAmountIn.toSignificant(6)} </TYPE.body>
</TruncatedText> </RowBetween>
</RowFixed> <RowBetween align="center">
<RowFixed gap={'0px'}> <RowFixed gap={'0px'}>
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}> <CurrencyLogo currency={trade.inputAmount.currency} size={'20px'} style={{ marginRight: '12px' }} />
{trade.inputAmount.currency.symbol} <Text fontSize={20} fontWeight={500}>
</Text> {trade.inputAmount.currency.symbol}
</RowFixed> </Text>
</RowBetween> </RowFixed>
<RowFixed> <RowFixed gap={'0px'}>
<ArrowDown size="16" color={theme.text2} style={{ marginLeft: '4px', minWidth: '16px' }} /> <TruncatedText
</RowFixed> fontSize={24}
<RowBetween align="flex-end"> fontWeight={500}
<RowFixed gap={'0px'}> color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? theme.primary1 : ''}
<CurrencyLogo currency={trade.outputAmount.currency} size={'24px'} style={{ marginRight: '12px' }} /> >
<TruncatedText fontSize={24} fontWeight={500}> {maximumAmountIn.toSignificant(6)}
{minimumAmountOut.toSignificant(6)} </TruncatedText>
</TruncatedText> </RowFixed>
</RowFixed> </RowBetween>
<RowFixed gap={'0px'}> </AutoColumn>
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}> </DarkGreyCard>
{trade.outputAmount.currency.symbol} <ArrowWrapper>
</Text> <ArrowDown size="16" color={theme.text2} />
</RowFixed> </ArrowWrapper>
<DarkGreyCard padding="0.75rem 1rem" style={{ marginBottom: '0.25rem' }}>
<AutoColumn gap={'8px'}>
<RowBetween>
<TYPE.body color={theme.text3} fontWeight={500} fontSize={14}>
{'To'}
</TYPE.body>
<TYPE.body fontSize={14} color={theme.text3}>
{'$-'}
</TYPE.body>
</RowBetween>
<RowBetween align="flex-end">
<RowFixed gap={'0px'}>
<CurrencyLogo currency={trade.outputAmount.currency} size={'20px'} style={{ marginRight: '12px' }} />
<Text fontSize={20} fontWeight={500}>
{trade.outputAmount.currency.symbol}
</Text>
</RowFixed>
<RowFixed gap={'0px'}>
<TruncatedText fontSize={24} fontWeight={500}>
{minimumAmountOut.toSignificant(6)}
</TruncatedText>
</RowFixed>
</RowBetween>
</AutoColumn>
</DarkGreyCard>
<RowBetween style={{ marginTop: '0.25rem', padding: '0 1rem' }}>
<TYPE.body color={theme.text2} fontWeight={500} fontSize={14}>
{'Price:'}
</TYPE.body>
<TradePrice
price={trade.worstExecutionPrice(allowedSlippage)}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween> </RowBetween>
<LightCard style={{ padding: '.75rem', marginTop: '0.5rem' }}>
<AdvancedSwapDetails trade={trade} />
</LightCard>
{showAcceptChanges ? ( {showAcceptChanges ? (
<SwapShowAcceptChanges justify="flex-start" gap={'0px'}> <SwapShowAcceptChanges justify="flex-start" gap={'0px'}>
<RowBetween> <RowBetween>
@ -82,9 +147,10 @@ export default function SwapModalHeader({
</RowBetween> </RowBetween>
</SwapShowAcceptChanges> </SwapShowAcceptChanges>
) : null} ) : null}
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }}>
{/* <AutoColumn justify="flex-start" gap="sm" style={{ padding: '.75rem 1rem' }}>
{trade.tradeType === TradeType.EXACT_INPUT ? ( {trade.tradeType === TradeType.EXACT_INPUT ? (
<TYPE.italic textAlign="left" style={{ width: '100%' }}> <TYPE.italic fontWeight={400} textAlign="left" style={{ width: '100%' }}>
{`Output is estimated. You will receive at least `} {`Output is estimated. You will receive at least `}
<b> <b>
{minimumAmountOut.toSignificant(6)} {trade.outputAmount.currency.symbol} {minimumAmountOut.toSignificant(6)} {trade.outputAmount.currency.symbol}
@ -92,7 +158,7 @@ export default function SwapModalHeader({
{' or the transaction will revert.'} {' or the transaction will revert.'}
</TYPE.italic> </TYPE.italic>
) : ( ) : (
<TYPE.italic textAlign="left" style={{ width: '100%' }}> <TYPE.italic fontWeight={400} textAlign="left" style={{ width: '100%' }}>
{`Input is estimated. You will sell at most `} {`Input is estimated. You will sell at most `}
<b> <b>
{maximumAmountIn.toSignificant(6)} {trade.inputAmount.currency.symbol} {maximumAmountIn.toSignificant(6)} {trade.inputAmount.currency.symbol}
@ -100,7 +166,7 @@ export default function SwapModalHeader({
{' or the transaction will revert.'} {' or the transaction will revert.'}
</TYPE.italic> </TYPE.italic>
)} )}
</AutoColumn> </AutoColumn> */}
{recipient !== null ? ( {recipient !== null ? (
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }}> <AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }}>
<TYPE.main> <TYPE.main>

@ -4,8 +4,6 @@ import { useContext } from 'react'
import { Text } from 'rebass' import { Text } from 'rebass'
import styled, { ThemeContext } from 'styled-components' import styled, { ThemeContext } from 'styled-components'
import { StyledBalanceMaxMini } from './styleds'
import Switch from '../../assets/svg/switch.svg'
interface TradePriceProps { interface TradePriceProps {
price: Price price: Price
@ -13,11 +11,19 @@ interface TradePriceProps {
setShowInverted: (showInverted: boolean) => void setShowInverted: (showInverted: boolean) => void
} }
const StyledPriceContainer = styled.div` const StyledPriceContainer = styled.button`
justify-content: flex-end; justify-content: center;
align-items: center; align-items: center;
display: flex; display: flex;
width: 100%; width: fit-content;
padding: 0;
font-size: 0.875rem;
font-weight: 400;
background-color: transparent;
border: none;
margin-left: 1rem;
height: 24px;
cursor: pointer;
` `
export default function TradePrice({ price, showInverted, setShowInverted }: TradePriceProps) { export default function TradePrice({ price, showInverted, setShowInverted }: TradePriceProps) {
@ -25,7 +31,7 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra
let formattedPrice: string let formattedPrice: string
try { try {
formattedPrice = showInverted ? price.toSignificant(6) : price.invert()?.toSignificant(6) formattedPrice = showInverted ? price.toSignificant(4) : price.invert()?.toSignificant(4)
} catch (error) { } catch (error) {
formattedPrice = '0' formattedPrice = '0'
} }
@ -35,14 +41,11 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra
const flipPrice = useCallback(() => setShowInverted(!showInverted), [setShowInverted, showInverted]) const flipPrice = useCallback(() => setShowInverted(!showInverted), [setShowInverted, showInverted])
return ( return (
<StyledPriceContainer> <StyledPriceContainer onClick={flipPrice}>
<div style={{ alignItems: 'center', display: 'flex', width: 'fit-content' }}> <div style={{ alignItems: 'center', display: 'flex', width: 'fit-content' }}>
<Text fontWeight={500} fontSize={14} color={theme.text2}> <Text fontWeight={500} fontSize={14} color={theme.text1}>
{'1 ' + labelInverted + ' = ' + formattedPrice ?? '-'} {label} {'1 ' + labelInverted + ' = ' + formattedPrice ?? '-'} {label}
</Text> </Text>
<StyledBalanceMaxMini style={{ marginLeft: '0.5rem' }} onClick={flipPrice}>
<img width={'16px'} src={Switch} alt="logo" />
</StyledBalanceMaxMini>
</div> </div>
</StyledPriceContainer> </StyledPriceContainer>
) )

@ -1,5 +1,7 @@
import { transparentize } from 'polished' import { transparentize } from 'polished'
import React from 'react' import React from 'react'
import { Link } from 'react-router-dom'
import { AlertTriangle } from 'react-feather' import { AlertTriangle } from 'react-feather'
import styled, { css } from 'styled-components' import styled, { css } from 'styled-components'
import { Text } from 'rebass' import { Text } from 'rebass'
@ -84,10 +86,11 @@ export const StyledBalanceMaxMini = styled.button`
background-color: ${({ theme }) => theme.bg1}; background-color: ${({ theme }) => theme.bg1};
border: none; border: none;
border-radius: 8px; border-radius: 8px;
padding: 0.25rem 0.35rem; padding: 0;
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 400; font-weight: 400;
opacity: 0.6;
margin-right: 0.5rem;
cursor: pointer; cursor: pointer;
color: ${({ theme }) => theme.text1}; color: ${({ theme }) => theme.text1};
display: flex; display: flex;
@ -106,8 +109,9 @@ export const StyledBalanceMaxMini = styled.button`
export const TruncatedText = styled(Text)` export const TruncatedText = styled(Text)`
text-overflow: ellipsis; text-overflow: ellipsis;
width: 220px; max-width: 220px;
overflow: hidden; overflow: hidden;
text-align: right;
` `
// styles // styles
@ -184,3 +188,14 @@ export const Separator = styled.div`
height: 1px; height: 1px;
background-color: ${({ theme }) => theme.bg2}; background-color: ${({ theme }) => theme.bg2};
` `
export const V2TradeAlertWrapper = styled(Link)`
background-color: ${({ theme }) => theme.bg2};
display: flex;
align-items: center;
border-radius: 12px;
height: 22px;
margin-right: 0.5rem;
padding: 0 0.25rem 0 0.5rem;
text-decoration: none !important;
`

@ -1,28 +1,28 @@
import { CurrencyAmount, Token } from '@uniswap/sdk-core' import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk' import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk' import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { AdvancedSwapDetails } from 'components/swap/AdvancedSwapDetails'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter' import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
import { MouseoverTooltip, MouseoverTooltipContent } from 'components/Tooltip'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react' import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { ArrowDown, Repeat, Unlock } from 'react-feather' import { ArrowDown, HelpCircle, CheckCircle, Info, X } from 'react-feather'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { RouteComponentProps } from 'react-router-dom' import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import AddressInputPanel from '../../components/AddressInputPanel' import AddressInputPanel from '../../components/AddressInputPanel'
import { ButtonConfirmed, ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button' import { ButtonConfirmed, ButtonError, ButtonLight, ButtonPrimary, ButtonGray } from '../../components/Button'
import { GreyCard } from '../../components/Card' import { GreyCard } from '../../components/Card'
import { AutoColumn } from '../../components/Column' import { AutoColumn } from '../../components/Column'
import CurrencyInputPanel from '../../components/CurrencyInputPanel' import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import CurrencyLogo from '../../components/CurrencyLogo' import CurrencyLogo from '../../components/CurrencyLogo'
import Loader from '../../components/Loader' import Loader from '../../components/Loader'
import ProgressSteps from '../../components/ProgressSteps' import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
import { AutoRow } from '../../components/Row'
import BetterTradeLink from '../../components/swap/BetterTradeLink'
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee' import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
import ConfirmSwapModal from '../../components/swap/ConfirmSwapModal' import ConfirmSwapModal from '../../components/swap/ConfirmSwapModal'
import { ArrowWrapper, BottomGrouping, Dots, SwapCallbackError, Wrapper } from '../../components/swap/styleds' import { ArrowWrapper, BottomGrouping, Dots, SwapCallbackError, Wrapper } from '../../components/swap/styleds'
import SwapHeader from '../../components/swap/SwapHeader'
import TradePrice from '../../components/swap/TradePrice' import TradePrice from '../../components/swap/TradePrice'
import TokenWarningModal from '../../components/TokenWarningModal' import TokenWarningModal from '../../components/TokenWarningModal'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
@ -46,11 +46,15 @@ import {
import { useExpertModeManager, useUserSingleHopOnly, useUserSlippageTolerance } from '../../state/user/hooks' import { useExpertModeManager, useUserSingleHopOnly, useUserSlippageTolerance } from '../../state/user/hooks'
import { LinkStyledButton, TYPE } from '../../theme' import { LinkStyledButton, TYPE } from '../../theme'
import { getTradeVersion } from '../../utils/getTradeVersion' import { getTradeVersion } from '../../utils/getTradeVersion'
import { isTradeBetter } from '../../utils/isTradeBetter'
import { maxAmountSpend } from '../../utils/maxAmountSpend' import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { computeTradePriceBreakdown, warningSeverity } from '../../utils/prices' import { computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
import AppBody from '../AppBody' import AppBody from '../AppBody'
import { Link } from 'react-router-dom'
import { isTradeBetter } from '../../utils/isTradeBetter'
import BetterTradeLink from '../../components/swap/BetterTradeLink'
import SwapHeader from '../../components/swap/SwapHeader'
export default function Swap({ history }: RouteComponentProps) { export default function Swap({ history }: RouteComponentProps) {
const loadedUrlParams = useDefaultsFromURLSearch() const loadedUrlParams = useDefaultsFromURLSearch()
@ -321,7 +325,7 @@ export default function Swap({ history }: RouteComponentProps) {
onDismiss={handleDismissTokenWarning} onDismiss={handleDismissTokenWarning}
/> />
<AppBody> <AppBody>
<SwapHeader toggledVersion={toggledVersion} /> <SwapHeader />
<Wrapper id="swap-page"> <Wrapper id="swap-page">
<ConfirmSwapModal <ConfirmSwapModal
isOpen={showConfirm} isOpen={showConfirm}
@ -353,14 +357,13 @@ export default function Swap({ history }: RouteComponentProps) {
id="swap-currency-input" id="swap-currency-input"
/> />
<ArrowWrapper clickable> <ArrowWrapper clickable>
<Repeat <ArrowDown
size="16" size="16"
onClick={() => { onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens() onSwitchTokens()
}} }}
color={currencies[Field.INPUT] && currencies[Field.OUTPUT] ? theme.text1 : theme.text3} color={currencies[Field.INPUT] && currencies[Field.OUTPUT] ? theme.text1 : theme.text3}
style={{ transform: 'rotate(90deg)' }}
/> />
</ArrowWrapper> </ArrowWrapper>
<CurrencyInputPanel <CurrencyInputPanel
@ -368,7 +371,7 @@ export default function Swap({ history }: RouteComponentProps) {
onUserInput={handleTypeOutput} onUserInput={handleTypeOutput}
label={independentField === Field.INPUT && !showWrap ? 'To (at least)' : 'To'} label={independentField === Field.INPUT && !showWrap ? 'To (at least)' : 'To'}
showMaxButton={false} showMaxButton={false}
hideBalance={true} hideBalance={false}
showFiatValue showFiatValue
currency={currencies[Field.OUTPUT]} currency={currencies[Field.OUTPUT]}
onCurrencySelect={handleOutputSelect} onCurrencySelect={handleOutputSelect}
@ -391,20 +394,84 @@ export default function Swap({ history }: RouteComponentProps) {
<AddressInputPanel id="recipient" value={recipient} onChange={onChangeRecipient} /> <AddressInputPanel id="recipient" value={recipient} onChange={onChangeRecipient} />
</> </>
) : null} ) : null}
{trade ? (
<TradePrice <RowBetween style={{ justifyContent: !trade ? 'center' : 'space-between' }}>
price={trade.worstExecutionPrice(allowedSlippage)} <RowFixed>
showInverted={showInverted} {[V3TradeState.VALID, V3TradeState.SYNCING, V3TradeState.NO_ROUTE_FOUND].includes(v3TradeState) &&
setShowInverted={setShowInverted} (toggledVersion === Version.v3 && isTradeBetter(v3Trade, v2Trade) ? (
/> <BetterTradeLink version={Version.v2} otherTradeNonexistent={!v3Trade} />
) : null} ) : toggledVersion === Version.v2 && isTradeBetter(v2Trade, v3Trade) ? (
{[V3TradeState.VALID, V3TradeState.SYNCING, V3TradeState.NO_ROUTE_FOUND].includes(v3TradeState) ? ( <BetterTradeLink version={Version.v3} otherTradeNonexistent={!v2Trade} />
toggledVersion === Version.v3 && isTradeBetter(v3Trade, v2Trade) ? ( ) : (
<BetterTradeLink version={Version.v2} otherTradeNonexistent={!v3Trade} /> toggledVersion === Version.v2 && (
) : toggledVersion === Version.v2 && isTradeBetter(v2Trade, v3Trade) ? ( <ButtonGray
<BetterTradeLink version={Version.v3} otherTradeNonexistent={!v2Trade} /> width="fit-content"
) : null padding="0.1rem 0.5rem 0.1rem 0.35rem"
) : null} as={Link}
to="/swap"
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
height: '24px',
opacity: 0.8,
lineHeight: '120%',
marginLeft: '0.25rem',
}}
>
<X color={theme.text3} size={12} /> &nbsp;
<TYPE.main style={{ lineHeight: '120%' }} fontSize={12}>
Routed via V2
</TYPE.main>
</ButtonGray>
)
))}
{toggledVersion === Version.v3 && trade && isTradeBetter(v2Trade, v3Trade) && (
<ButtonGray
width="fit-content"
padding="0.1rem 0.5rem"
disabled
style={{
display: 'flex',
justifyContent: 'space-between',
height: '24px',
opacity: 0.4,
marginLeft: '0.25rem',
}}
>
<TYPE.black fontSize={12}>V3</TYPE.black>
</ButtonGray>
)}
</RowFixed>
<RowFixed>
{trade ? (
<TradePrice
price={trade.worstExecutionPrice(allowedSlippage)}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
) : (
<TYPE.main></TYPE.main>
)}
{trade && (
<MouseoverTooltipContent content={<AdvancedSwapDetails trade={trade} />}>
<Info
size={16}
style={{
display: 'flex',
justifyContent: 'space-between',
height: '24px',
opacity: 0.4,
margin: '0 .75rem 0 .5rem',
}}
color={theme.text1}
/>
</MouseoverTooltipContent>
)}
</RowFixed>
</RowBetween>
<BottomGrouping> <BottomGrouping>
{swapIsUnsupported ? ( {swapIsUnsupported ? (
<ButtonPrimary disabled={true}> <ButtonPrimary disabled={true}>
@ -447,19 +514,29 @@ export default function Swap({ history }: RouteComponentProps) {
<span style={{ display: 'flex', alignItems: 'center' }}> <span style={{ display: 'flex', alignItems: 'center' }}>
<CurrencyLogo <CurrencyLogo
currency={currencies[Field.INPUT]} currency={currencies[Field.INPUT]}
size={'16px'} size={'20px'}
style={{ marginRight: '8px' }} style={{ marginRight: '8px' }}
/> />
{/* we need to shorten this string on mobile */} {/* we need to shorten this string on mobile */}
{'Allow Uniswap to spend your ' + currencies[Field.INPUT]?.symbol} {approvalState === ApprovalState.APPROVED || signatureState === UseERC20PermitState.SIGNED
? currencies[Field.INPUT]?.symbol + ' unlocked for trading.'
: 'Allow Uniswap to spend your ' + currencies[Field.INPUT]?.symbol}
</span> </span>
{approvalState === ApprovalState.PENDING ? ( {approvalState === ApprovalState.PENDING ? (
<Loader stroke="white" /> <Loader stroke="white" />
) : (approvalSubmitted && approvalState === ApprovalState.APPROVED) || ) : (approvalSubmitted && approvalState === ApprovalState.APPROVED) ||
signatureState === UseERC20PermitState.SIGNED ? ( signatureState === UseERC20PermitState.SIGNED ? (
<Unlock size="16" stroke="white" /> <CheckCircle size="20" color={theme.green1} />
) : ( ) : (
<Unlock size="16" stroke="white" /> <MouseoverTooltip
text={
'You cannot swap until you give Uniswap permission to spend your ' +
currencies[Field.INPUT]?.symbol +
'. You only have to do this once per token and only when you are selling a token for the first time.'
}
>
<HelpCircle size="20" color={'white'} />
</MouseoverTooltip>
)} )}
</AutoRow> </AutoRow>
</ButtonConfirmed> </ButtonConfirmed>
@ -491,13 +568,6 @@ export default function Swap({ history }: RouteComponentProps) {
</Text> </Text>
</ButtonError> </ButtonError>
</AutoColumn> </AutoColumn>
{showApproveFlow && (
<ProgressSteps
steps={[
approvalState === ApprovalState.APPROVED || signatureState === UseERC20PermitState.SIGNED,
]}
/>
)}
</AutoRow> </AutoRow>
) : ( ) : (
<ButtonError <ButtonError

@ -28,7 +28,7 @@ export default createReducer(initialState, (builder) =>
.addCase(setOpenModal, (state, action) => { .addCase(setOpenModal, (state, action) => {
state.openModal = action.payload state.openModal = action.payload
}) })
.addCase(addPopup, (state, { payload: { content, key, removeAfterMs = 15000 } }) => { .addCase(addPopup, (state, { payload: { content, key, removeAfterMs = 25000 } }) => {
state.popupList = (key ? state.popupList.filter((popup) => popup.key !== key) : state.popupList).concat([ state.popupList = (key ? state.popupList.filter((popup) => popup.key !== key) : state.popupList).concat([
{ {
key: key || nanoid(), key: key || nanoid(),

@ -47,9 +47,9 @@ export function colors(darkMode: boolean): Colors {
text5: darkMode ? '#2C2F36' : '#EDEEF2', text5: darkMode ? '#2C2F36' : '#EDEEF2',
// backgrounds / greys // backgrounds / greys
bg0: darkMode ? '#191B1F' : '#FFFFFF', bg0: darkMode ? '#191B1F' : '#F7F8FA',
bg1: darkMode ? '#212429' : '#F7F8FA', bg1: darkMode ? '#212429' : '#EDEEF2',
bg2: darkMode ? '#2C2F36' : '#EDEEF2', bg2: darkMode ? '#2C2F36' : '#F0F0F0',
bg3: darkMode ? '#40444F' : '#CED0D9', bg3: darkMode ? '#40444F' : '#CED0D9',
bg4: darkMode ? '#565A69' : '#888D9B', bg4: darkMode ? '#565A69' : '#888D9B',
bg5: darkMode ? '#6C7284' : '#888D9B', bg5: darkMode ? '#6C7284' : '#888D9B',
@ -221,7 +221,7 @@ html {
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
font-feature-settings: 'ss01' on, 'ss02' on, 'cv01' on, 'cv03' on; font-feature-settings: 'ss01' on, 'cv01' on, 'cv03' on;
} }
` `