From 4c2cb5b0c170d6d772b342669441abce3232c35f Mon Sep 17 00:00:00 2001 From: Callil Capuozzo Date: Mon, 3 May 2021 16:32:40 -0400 Subject: [PATCH] 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 --- src/components/Button/index.tsx | 29 ++-- src/components/CurrencyInputPanel/index.tsx | 105 ++++++------- src/components/Header/index.tsx | 44 +----- src/components/Popups/PopupItem.tsx | 2 +- src/components/Popups/index.tsx | 2 +- src/components/ProgressSteps/index.tsx | 24 ++- src/components/Settings/index.tsx | 11 +- src/components/Tooltip/index.tsx | 25 ++- .../TransactionConfirmationModal/index.tsx | 2 +- src/components/swap/AdvancedSwapDetails.tsx | 66 +------- src/components/swap/BetterTradeLink.tsx | 63 ++++---- src/components/swap/ConfirmSwapModal.tsx | 3 +- src/components/swap/FormattedPriceImpact.tsx | 4 +- src/components/swap/SwapHeader.tsx | 41 ++--- src/components/swap/SwapModalFooter.tsx | 43 +----- src/components/swap/SwapModalHeader.tsx | 144 +++++++++++++----- src/components/swap/TradePrice.tsx | 25 +-- src/components/swap/styleds.tsx | 21 ++- src/pages/Swap/index.tsx | 142 ++++++++++++----- src/state/application/reducer.ts | 2 +- src/theme/index.tsx | 8 +- 21 files changed, 422 insertions(+), 384 deletions(-) diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index c819d3f832..bc17412cfc 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -1,6 +1,6 @@ import React from 'react' import styled from 'styled-components' -import { darken, lighten } from 'polished' +import { darken } from 'polished' import { RowBetween } from '../Row' import { ChevronDown, Check } from 'react-feather' @@ -32,6 +32,15 @@ const Base = styled(RebassButton)<{ z-index: 1; &:disabled { 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 { background-color: ${({ theme, altDisabledStyle, disabled }) => - altDisabledStyle ? (disabled ? theme.bg3 : theme.primary1) : theme.bg3}; - color: ${({ theme, altDisabledStyle, disabled }) => - altDisabledStyle ? (disabled ? theme.text3 : 'white') : theme.text3}; + altDisabledStyle ? (disabled ? theme.primary1 : theme.primary1) : theme.primary1}; + color: white; cursor: auto; box-shadow: none; border: 1px solid transparent; 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)` - background-color: ${({ theme }) => lighten(0.5, theme.green1)}; - color: ${({ theme }) => theme.green1}; - border: 1px solid ${({ theme }) => theme.green1}; + background-color: ${({ theme }) => theme.bg3}; + color: ${({ theme }) => theme.text1}; + /* border: 1px solid ${({ theme }) => theme.green1}; */ &:disabled { - opacity: 50%; + /* opacity: 50%; */ + background-color: ${({ theme }) => theme.bg2}; + color: ${({ theme }) => theme.text2}; cursor: auto; } ` diff --git a/src/components/CurrencyInputPanel/index.tsx b/src/components/CurrencyInputPanel/index.tsx index 0a621bebcc..a26112a124 100644 --- a/src/components/CurrencyInputPanel/index.tsx +++ b/src/components/CurrencyInputPanel/index.tsx @@ -9,6 +9,7 @@ import { useCurrencyBalance } from '../../state/wallet/hooks' import CurrencySearchModal from '../SearchModal/CurrencySearchModal' import CurrencyLogo from '../CurrencyLogo' import DoubleCurrencyLogo from '../DoubleLogo' +import { ButtonGray } from '../Button' import { RowBetween, RowFixed } from '../Row' import { TYPE } from '../../theme' 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; - font-size: 20px; + font-size: 24px; font-weight: 500; background-color: ${({ selected, theme }) => (selected ? theme.bg2 : theme.primary1)}; color: ${({ selected, theme }) => (selected ? theme.text1 : theme.white)}; - border-radius: 12px; - /* box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')}; */ + border-radius: 16px; + 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); - outline: none; cursor: pointer; user-select: none; border: none; - - height: ${({ hideInput }) => (hideInput ? '2.8rem' : '2.4rem')}; + height: ${({ hideInput }) => (hideInput ? '2.4rem' : '2.4rem')}; width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')}; - + padding: 0 8px; + justify-content: space-between; + margin-right: 12px; :focus, :hover { 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 }>` ${({ theme }) => theme.flexRowNoWrap} 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` @@ -88,13 +89,17 @@ const LabelRow = styled.div` color: ${({ theme }) => theme.text1}; font-size: 0.75rem; line-height: 1rem; - padding: 0rem 1rem 0.75rem 1rem; + padding: 0 1rem 1rem; span:hover { cursor: pointer; color: ${({ theme }) => darken(0.2, theme.text2)}; } ` +const FiatRow = styled(LabelRow)` + justify-content: flex-end; +` + const Aligner = styled.span` display: flex; align-items: center; @@ -102,7 +107,7 @@ const Aligner = styled.span` ` const StyledDropDown = styled(DropDown)<{ selected: boolean }>` - margin: 0 0.25rem 0 0.5rem; + margin: 0 0.25rem 0 0.35rem; height: 35%; path { @@ -113,27 +118,27 @@ const StyledDropDown = styled(DropDown)<{ selected: boolean }>` const StyledTokenName = styled.span<{ active?: boolean }>` ${({ 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 }>` - background-color: ${({ theme }) => theme.primary5}; - border: 1px solid ${({ theme }) => theme.primary5}; - border-radius: 0.5rem; + background-color: transparent; + border: none; + border-radius: 12px; font-size: 14px; - font-weight: 500; cursor: pointer; - /* margin-left: 0.5rem; */ + padding: 0; color: ${({ theme }) => theme.primary1}; opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)}; pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')}; + margin-left: 0.25rem; :hover { - border: 1px solid ${({ theme }) => theme.primary1}; + /* border: 1px solid ${({ theme }) => theme.primary1}; */ } :focus { - border: 1px solid ${({ theme }) => theme.primary1}; + /* border: 1px solid ${({ theme }) => theme.primary1}; */ outline: none; } @@ -166,7 +171,6 @@ export default function CurrencyInputPanel({ onUserInput, onMax, showMaxButton, - label = 'Input', onCurrencySelect, currency, otherCurrency, @@ -210,19 +214,6 @@ export default function CurrencyInputPanel({ )} - {!hideInput && ( - - - - {label} - - - {fiatValueOfTypedAmount ? '~' : ''}${fiatValueOfTypedAmount?.toSignificant(4) ?? '-'} - - - - )} - {!hideInput && ( - { - onUserInput(val) - }} - /> + <> + {' '} + { + onUserInput(val) + }} + /> + )} {!hideInput && !hideBalance && ( - + {account && ( - + @@ -287,17 +281,26 @@ export default function CurrencyInputPanel({ selectedCurrencyBalance?.toSignificant(4) + ' ' + currency.symbol - : ' '} + : '-'} + {showMaxButton && selectedCurrencyBalance ? ( + + (Max) + + ) : ( + + {''} + + )} )} - {showMaxButton && ( - - Max - - )} + + + {fiatValueOfTypedAmount ? '~' : ''}$ + {fiatValueOfTypedAmount ? Number(fiatValueOfTypedAmount?.toSignificant(6)).toLocaleString('en') : '-'} + - + )} {onCurrencySelect && ( diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index c01666e80d..46cb4e3fc3 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -1,4 +1,4 @@ -import { ChainId, TokenAmount } from '@uniswap/sdk-core' +import { ChainId } from '@uniswap/sdk-core' import React, { useState } from 'react' import { Text } from 'rebass' import { NavLink } from 'react-router-dom' @@ -9,11 +9,11 @@ import styled from 'styled-components' import Logo from '../../assets/svg/logo.svg' import LogoDark from '../../assets/svg/logo_white.svg' + import { useActiveWeb3React } from '../../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 { CountUp } from 'use-count-up' import { TYPE, ExternalLink } from '../../theme' import { YellowCard } from '../Card' @@ -28,7 +28,6 @@ import { useUserHasSubmittedClaim } from '../../state/transactions/hooks' import { Dots } from '../swap/styleds' import Modal from '../Modal' import UniBalanceContent from './UniBalanceContent' -import usePrevious from '../../hooks/usePrevious' const HeaderFrame = styled.div` display: grid; @@ -40,10 +39,10 @@ const HeaderFrame = styled.div` width: 100%; top: 0; 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; z-index: 21; - background-color: ${({ theme }) => theme.bg0}; + background-color: ${({ theme }) => theme.bg1}; ${({ theme }) => theme.mediaWidth.upToMedium` display: flex; @@ -314,14 +313,9 @@ export default function Header() { const { claimTxn } = useUserHasSubmittedClaim(account ?? undefined) - const aggregateBalance: TokenAmount | undefined = useAggregateUniBalance() - const [showUniBalanceModal, setShowUniBalanceModal] = useState(false) const showClaimPopup = useShowClaimPopup() - const countUpValue = aggregateBalance?.toFixed(0) ?? '0' - const countUpValuePrevious = usePrevious(countUpValue) ?? '0' - return ( @@ -376,33 +370,7 @@ export default function Header() { )} - {/* I want to put this in the overflow menu now */} - {!availableClaim && aggregateBalance && ( - setShowUniBalanceModal(true)}> - - {account && ( - - - - - - )} - UNI - - - - )} + {account && userEthBalance ? ( diff --git a/src/components/Popups/PopupItem.tsx b/src/components/Popups/PopupItem.tsx index 26e8b3e369..ddff689575 100644 --- a/src/components/Popups/PopupItem.tsx +++ b/src/components/Popups/PopupItem.tsx @@ -21,7 +21,7 @@ export const Popup = styled.div` display: inline-block; width: 100%; padding: 1em; - background-color: ${({ theme }) => theme.bg1}; + background-color: ${({ theme }) => theme.bg0}; position: relative; border-radius: 10px; padding: 20px; diff --git a/src/components/Popups/index.tsx b/src/components/Popups/index.tsx index 935fdc28b2..13365fdbd5 100644 --- a/src/components/Popups/index.tsx +++ b/src/components/Popups/index.tsx @@ -33,7 +33,7 @@ const MobilePopupInner = styled.div` const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>` position: fixed; - top: ${({ extraPadding }) => (extraPadding ? '108px' : '88px')}; + top: ${({ extraPadding }) => (extraPadding ? '72px' : '88px')}; right: 1rem; max-width: 355px !important; width: 100%; diff --git a/src/components/ProgressSteps/index.tsx b/src/components/ProgressSteps/index.tsx index e385330534..fba550265e 100644 --- a/src/components/ProgressSteps/index.tsx +++ b/src/components/ProgressSteps/index.tsx @@ -2,26 +2,26 @@ import React, { useContext } from 'react' import styled from 'styled-components' import { AutoColumn } from '../Column' import { ThemeContext } from 'styled-components' -import { ArrowDown } from 'react-feather' +import { TYPE } from '../../theme' const Wrapper = styled(AutoColumn)` - margin-left: 8px; + margin-right: 8px; height: 100%; ` const Grouping = styled(AutoColumn)` width: fit-content; padding: 4px; - background-color: ${({ theme }) => theme.bg2}; + /* background-color: ${({ theme }) => theme.bg2}; */ border-radius: 16px; ` const Circle = styled.div<{ confirmed?: boolean; disabled?: boolean }>` - width: 100%; - height: 100%; + width: 48px; + height: 48px; background-color: ${({ theme, confirmed, disabled }) => disabled ? theme.bg3 : confirmed ? theme.green1 : theme.primary1}; - border-radius: 12px; + border-radius: 50%; color: ${({ theme, disabled }) => (disabled ? theme.text3 : theme.text1)}; display: flex; align-items: center; @@ -37,12 +37,6 @@ const CircleRow = styled.div` align-items: center; ` -const StyledArrowDown = styled(ArrowDown)` - margin: 0.5rem; - min-height: 14px; - /* color: ${({ theme }) => theme.text1}; */ -` - interface ProgressCirclesProps { steps: boolean[] disabled?: boolean @@ -68,13 +62,13 @@ export default function ProgressCircles({ steps, disabled = false, ...rest }: Pr return ( - {step ? '✓' : i + 1} + {step ? '✓' : i + 1 + '.'} - + | ) })} - {steps.length + 1} + {steps.length + 1 + '.'} ) diff --git a/src/components/Settings/index.tsx b/src/components/Settings/index.tsx index 186a177e1d..0c5b6855e5 100644 --- a/src/components/Settings/index.tsx +++ b/src/components/Settings/index.tsx @@ -21,8 +21,6 @@ import QuestionHelper from '../QuestionHelper' import { RowBetween, RowFixed } from '../Row' import Toggle from '../Toggle' import TransactionSettings from '../TransactionSettings' -import { Moon, Sun } from 'react-feather' -import { useDarkModeManager } from '../../state/user/hooks' const StyledMenuIcon = styled(Settings)` height: 20px; @@ -86,6 +84,7 @@ const StyledMenu = styled.div` const MenuFlyout = styled.span` min-width: 20.125rem; 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), 0px 24px 32px rgba(0, 0, 0, 0.01); border-radius: 12px; @@ -93,7 +92,7 @@ const MenuFlyout = styled.span` flex-direction: column; font-size: 1rem; position: absolute; - top: 3rem; + top: 2rem; right: 0rem; z-index: 100; @@ -134,8 +133,6 @@ export default function SettingsTab() { // show confirmation view before turning on const [showConfirmation, setShowConfirmation] = useState(false) - const [darkMode, toggleDarkMode] = useDarkModeManager() - useOnClickOutside(node, open ? toggle : undefined) return ( @@ -245,10 +242,6 @@ export default function SettingsTab() { }} /> - {/* WIP */} - toggleDarkMode()}> - {darkMode ? : } - )} diff --git a/src/components/Tooltip/index.tsx b/src/components/Tooltip/index.tsx index 5814445dac..09adccf67d 100644 --- a/src/components/Tooltip/index.tsx +++ b/src/components/Tooltip/index.tsx @@ -3,9 +3,9 @@ import styled from 'styled-components' import Popover, { PopoverProps } from '../Popover' const TooltipContainer = styled.div` - width: 228px; + width: 256px; padding: 0.6rem 1rem; - line-height: 150%; + /* line-height: 150%; */ font-weight: 400; ` @@ -13,10 +13,18 @@ interface TooltipProps extends Omit { text: string } +interface TooltipContentProps extends Omit { + content: React.ReactNode +} + export default function Tooltip({ text, ...rest }: TooltipProps) { return {text}} {...rest} /> } +export function TooltipContent({ content, ...rest }: TooltipContentProps) { + return {content}} {...rest} /> +} + export function MouseoverTooltip({ children, ...rest }: Omit) { const [show, setShow] = useState(false) const open = useCallback(() => setShow(true), [setShow]) @@ -29,3 +37,16 @@ export function MouseoverTooltip({ children, ...rest }: Omit ) } + +export function MouseoverTooltipContent({ content, children, ...rest }: Omit) { + const [show, setShow] = useState(false) + const open = useCallback(() => setShow(true), [setShow]) + const close = useCallback(() => setShow(false), [setShow]) + return ( + +
+ {children} +
+
+ ) +} diff --git a/src/components/TransactionConfirmationModal/index.tsx b/src/components/TransactionConfirmationModal/index.tsx index 3fd4144885..5bcb70428c 100644 --- a/src/components/TransactionConfirmationModal/index.tsx +++ b/src/components/TransactionConfirmationModal/index.tsx @@ -20,7 +20,7 @@ const Wrapper = styled.div` padding: 1rem; ` const Section = styled(AutoColumn)<{ inline?: boolean }>` - padding: ${({ inline }) => (inline ? '0' : '24px')}; + padding: ${({ inline }) => (inline ? '0' : '0')}; ` const BottomSection = styled(Section)` diff --git a/src/components/swap/AdvancedSwapDetails.tsx b/src/components/swap/AdvancedSwapDetails.tsx index 199d3a4401..1b86b07ce6 100644 --- a/src/components/swap/AdvancedSwapDetails.tsx +++ b/src/components/swap/AdvancedSwapDetails.tsx @@ -1,14 +1,10 @@ -import { TradeType } from '@uniswap/sdk-core' import { Trade as V2Trade } from '@uniswap/v2-sdk' import { Trade as V3Trade } from '@uniswap/v3-sdk' import React, { useContext } from 'react' import { ThemeContext } from 'styled-components' -import { Field } from '../../state/swap/actions' -import { useUserSlippageTolerance } from '../../state/user/hooks' import { TYPE } from '../../theme' -import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown } from '../../utils/prices' +import { computeTradePriceBreakdown } from '../../utils/prices' import { AutoColumn } from '../Column' -import QuestionHelper from '../QuestionHelper' import { RowBetween, RowFixed } from '../Row' import FormattedPriceImpact from './FormattedPriceImpact' import SwapRoute from './SwapRoute' @@ -16,74 +12,28 @@ import SwapRoute from './SwapRoute' function TradeSummary({ trade }: { trade: V2Trade | V3Trade }) { const theme = useContext(ThemeContext) 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 ( <> - + - - Price - - - - {'1 ' + - price?.quoteCurrency?.symbol + - ' = ' + - price?.invert()?.toSignificant(6) + - ' ' + - price?.baseCurrency?.symbol} - - - - - - Inverted Price - - - - {'1 ' + price?.baseCurrency?.symbol + ' = ' + price?.toSignificant(6) + ' ' + price?.quoteCurrency?.symbol} - - - - - - {isExactIn ? 'Minimum received' : 'Maximum sold'} - - - - - - {isExactIn - ? `${slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4)} ${trade.outputAmount.currency.symbol}` ?? - '-' - : `${slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4)} ${trade.inputAmount.currency.symbol}` ?? - '-'} - - - - - - + Price Impact - + {/* */} - + Liquidity Provider Fee - + {/* */} - + {realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${trade.inputAmount.currency.symbol}` : '-'} @@ -116,7 +66,7 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) { Route - + {/* */} diff --git a/src/components/swap/BetterTradeLink.tsx b/src/components/swap/BetterTradeLink.tsx index e3b6acff79..b9e21047bb 100644 --- a/src/components/swap/BetterTradeLink.tsx +++ b/src/components/swap/BetterTradeLink.tsx @@ -1,28 +1,14 @@ import { stringify } from 'qs' -import React, { useContext, useMemo } from 'react' +import React, { useMemo } from 'react' import { useLocation } from 'react-router' -import { Text } from 'rebass' -import { ThemeContext } from 'styled-components' +import { Link } from 'react-router-dom' + import useParsedQueryString from '../../hooks/useParsedQueryString' import useToggledVersion, { DEFAULT_VERSION, Version } from '../../hooks/useToggledVersion' +import { TYPE } from '../../theme' +import { ButtonPrimary } from '../Button' -import { StyledInternalLink } from '../../theme' -import { YellowCard } from '../Card' -import { AutoColumn } from '../Column' - -function VersionLinkContainer({ children }: { children: React.ReactNode }) { - const theme = useContext(ThemeContext) - - return ( - - - - {children} - - - - ) -} +import { Zap } from 'react-feather' export default function BetterTradeLink({ version, @@ -45,12 +31,24 @@ export default function BetterTradeLink({ }, [location, search, version]) return ( - - {otherTradeNonexistent ? 'This trade can be executed on ' : 'There is a better price for this trade on '} - - Uniswap {version.toUpperCase()} ↗ - - + + + + {otherTradeNonexistent + ? `No liquidity! Click to trade with ${version.toUpperCase()}` + : `Get a better price on ${version.toUpperCase()}`} + + ) } @@ -70,11 +68,12 @@ export function DefaultVersionLink() { }, [location, search]) return ( - - Showing {version.toUpperCase()} price.{' '} - - Switch to Uniswap {DEFAULT_VERSION.toUpperCase()} ↗ - - + + Showing {version.toUpperCase()} price. Switch to Uniswap {DEFAULT_VERSION.toUpperCase()} ↗ + ) } diff --git a/src/components/swap/ConfirmSwapModal.tsx b/src/components/swap/ConfirmSwapModal.tsx index 5a07acb5d4..2b81f44f77 100644 --- a/src/components/swap/ConfirmSwapModal.tsx +++ b/src/components/swap/ConfirmSwapModal.tsx @@ -81,10 +81,9 @@ export default function ConfirmSwapModal({ trade={trade} disabledConfirm={showAcceptChanges} swapErrorMessage={swapErrorMessage} - allowedSlippage={allowedSlippage} /> ) : null - }, [allowedSlippage, onConfirm, showAcceptChanges, swapErrorMessage, trade]) + }, [onConfirm, showAcceptChanges, swapErrorMessage, trade]) // text to show while loading const pendingText = `Swapping ${trade?.maximumAmountIn(allowedSlippage)?.toSignificant(6)} ${ diff --git a/src/components/swap/FormattedPriceImpact.tsx b/src/components/swap/FormattedPriceImpact.tsx index 8eafa63e27..b7c0b009ef 100644 --- a/src/components/swap/FormattedPriceImpact.tsx +++ b/src/components/swap/FormattedPriceImpact.tsx @@ -9,7 +9,7 @@ import { ErrorText, ErrorPill } from './styleds' */ export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) { return ( - + {priceImpact ? priceImpact.lessThan(ONE_BIPS) ? `-${priceImpact.toFixed(2)}%` @@ -21,7 +21,7 @@ export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Pe export function SmallFormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) { return ( - + {priceImpact ? priceImpact.lessThan(ONE_BIPS) ? `(-${priceImpact.toFixed(2)}%)` diff --git a/src/components/swap/SwapHeader.tsx b/src/components/swap/SwapHeader.tsx index 9951056ea6..8a71fad582 100644 --- a/src/components/swap/SwapHeader.tsx +++ b/src/components/swap/SwapHeader.tsx @@ -1,51 +1,28 @@ import React from 'react' import styled from 'styled-components' -import { Version } from '../../hooks/useToggledVersion' import Settings from '../Settings' + import { RowBetween, RowFixed } from '../Row' import { TYPE } from '../../theme' -// import { Info } from 'react-feather' - const StyledSwapHeader = styled.div` padding: 1rem 1.25rem 0.5rem 1.25rem; width: 100%; 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 { - toggledVersion: Version -} - -export default function SwapHeader({ toggledVersion }: SwapHeaderProps) { +export default function SwapHeader() { return ( - - Swap ({toggledVersion}) - - {/* Send icon appears here when expert mode is toggled on */} - {/* onChangeRecipient('')} /> */} - {/* This info icon should open info.uniswap.org with the pair */} - {/*{trade && (*/} - {/* */} - {/* */} - {/* */} - {/*)}*/} - + + Swap{' '} + + + + {/* */} + {/*
*/}
diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index a55ea0fec9..2f96fbcd44 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -1,61 +1,30 @@ import { Trade as V2Trade } from '@uniswap/v2-sdk' import { Trade as V3Trade } from '@uniswap/v3-sdk' -import { Percent } from '@uniswap/sdk-core' -import React, { useContext, useMemo, useState } from 'react' -import { Repeat } from 'react-feather' + +import React, { useMemo } from 'react' import { Text } from 'rebass' -import { ThemeContext } from 'styled-components' -import { computeTradePriceBreakdown, formatExecutionPrice, warningSeverity } from '../../utils/prices' +import { computeTradePriceBreakdown, warningSeverity } from '../../utils/prices' import { ButtonError } from '../Button' -import { AutoColumn } from '../Column' -import { AutoRow, RowBetween } from '../Row' -import { StyledBalanceMaxMini, SwapCallbackError } from './styleds' +import {} from '../Column' +import { AutoRow } from '../Row' +import { SwapCallbackError } from './styleds' export default function SwapModalFooter({ trade, onConfirm, - allowedSlippage, swapErrorMessage, disabledConfirm, }: { trade: V2Trade | V3Trade - allowedSlippage: Percent onConfirm: () => void swapErrorMessage: string | undefined disabledConfirm: boolean }) { - const [showInverted, setShowInverted] = useState(false) - const theme = useContext(ThemeContext) const { priceImpactWithoutFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade]) const severity = warningSeverity(priceImpactWithoutFee) return ( <> - - - - Price - - - {formatExecutionPrice(trade, showInverted, allowedSlippage)} - setShowInverted(!showInverted)}> - - - - - - theme.bg1}; + /* border: 4px solid ${({ theme }) => theme.bg0}; */ + z-index: 2; +` + export default function SwapModalHeader({ trade, allowedSlippage, @@ -31,41 +55,82 @@ export default function SwapModalHeader({ const theme = useContext(ThemeContext) + const [showInverted, setShowInverted] = useState(false) + return ( - - - - - - {maximumAmountIn.toSignificant(6)} - - - - - {trade.inputAmount.currency.symbol} - - - - - - - - - - - {minimumAmountOut.toSignificant(6)} - - - - - {trade.outputAmount.currency.symbol} - - + + + + + + {'From'} + + + {'$-'} + + + + + + + {trade.inputAmount.currency.symbol} + + + + + {maximumAmountIn.toSignificant(6)} + + + + + + + + + + + + + {'To'} + + + {'$-'} + + + + + + + {trade.outputAmount.currency.symbol} + + + + + {minimumAmountOut.toSignificant(6)} + + + + + + + + {'Price:'} + + + + + + + {showAcceptChanges ? ( @@ -82,9 +147,10 @@ export default function SwapModalHeader({ ) : null} - + + {/* {trade.tradeType === TradeType.EXACT_INPUT ? ( - + {`Output is estimated. You will receive at least `} {minimumAmountOut.toSignificant(6)} {trade.outputAmount.currency.symbol} @@ -92,7 +158,7 @@ export default function SwapModalHeader({ {' or the transaction will revert.'} ) : ( - + {`Input is estimated. You will sell at most `} {maximumAmountIn.toSignificant(6)} {trade.inputAmount.currency.symbol} @@ -100,7 +166,7 @@ export default function SwapModalHeader({ {' or the transaction will revert.'} )} - + */} {recipient !== null ? ( diff --git a/src/components/swap/TradePrice.tsx b/src/components/swap/TradePrice.tsx index f45d875935..4a11cf5fbe 100644 --- a/src/components/swap/TradePrice.tsx +++ b/src/components/swap/TradePrice.tsx @@ -4,8 +4,6 @@ import { useContext } from 'react' import { Text } from 'rebass' import styled, { ThemeContext } from 'styled-components' -import { StyledBalanceMaxMini } from './styleds' -import Switch from '../../assets/svg/switch.svg' interface TradePriceProps { price: Price @@ -13,11 +11,19 @@ interface TradePriceProps { setShowInverted: (showInverted: boolean) => void } -const StyledPriceContainer = styled.div` - justify-content: flex-end; +const StyledPriceContainer = styled.button` + justify-content: center; align-items: center; 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) { @@ -25,7 +31,7 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra let formattedPrice: string try { - formattedPrice = showInverted ? price.toSignificant(6) : price.invert()?.toSignificant(6) + formattedPrice = showInverted ? price.toSignificant(4) : price.invert()?.toSignificant(4) } catch (error) { formattedPrice = '0' } @@ -35,14 +41,11 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra const flipPrice = useCallback(() => setShowInverted(!showInverted), [setShowInverted, showInverted]) return ( - +
- + {'1 ' + labelInverted + ' = ' + formattedPrice ?? '-'} {label} - - logo -
) diff --git a/src/components/swap/styleds.tsx b/src/components/swap/styleds.tsx index 458b9fc794..a1d92620cf 100644 --- a/src/components/swap/styleds.tsx +++ b/src/components/swap/styleds.tsx @@ -1,5 +1,7 @@ import { transparentize } from 'polished' import React from 'react' +import { Link } from 'react-router-dom' + import { AlertTriangle } from 'react-feather' import styled, { css } from 'styled-components' import { Text } from 'rebass' @@ -84,10 +86,11 @@ export const StyledBalanceMaxMini = styled.button` background-color: ${({ theme }) => theme.bg1}; border: none; border-radius: 8px; - padding: 0.25rem 0.35rem; + padding: 0; font-size: 0.875rem; font-weight: 400; - + opacity: 0.6; + margin-right: 0.5rem; cursor: pointer; color: ${({ theme }) => theme.text1}; display: flex; @@ -106,8 +109,9 @@ export const StyledBalanceMaxMini = styled.button` export const TruncatedText = styled(Text)` text-overflow: ellipsis; - width: 220px; + max-width: 220px; overflow: hidden; + text-align: right; ` // styles @@ -184,3 +188,14 @@ export const Separator = styled.div` height: 1px; 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; +` diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 0e3f1a1582..9ee3430648 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -1,28 +1,28 @@ import { CurrencyAmount, Token } from '@uniswap/sdk-core' import { Trade as V2Trade } from '@uniswap/v2-sdk' import { Trade as V3Trade } from '@uniswap/v3-sdk' +import { AdvancedSwapDetails } from 'components/swap/AdvancedSwapDetails' import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter' +import { MouseoverTooltip, MouseoverTooltipContent } from 'components/Tooltip' import JSBI from 'jsbi' 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 { RouteComponentProps } from 'react-router-dom' import { Text } from 'rebass' import { ThemeContext } from 'styled-components' 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 { AutoColumn } from '../../components/Column' import CurrencyInputPanel from '../../components/CurrencyInputPanel' import CurrencyLogo from '../../components/CurrencyLogo' import Loader from '../../components/Loader' -import ProgressSteps from '../../components/ProgressSteps' -import { AutoRow } from '../../components/Row' -import BetterTradeLink from '../../components/swap/BetterTradeLink' +import { AutoRow, RowBetween, RowFixed } from '../../components/Row' import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee' import ConfirmSwapModal from '../../components/swap/ConfirmSwapModal' + import { ArrowWrapper, BottomGrouping, Dots, SwapCallbackError, Wrapper } from '../../components/swap/styleds' -import SwapHeader from '../../components/swap/SwapHeader' import TradePrice from '../../components/swap/TradePrice' import TokenWarningModal from '../../components/TokenWarningModal' import { useActiveWeb3React } from '../../hooks' @@ -46,11 +46,15 @@ import { import { useExpertModeManager, useUserSingleHopOnly, useUserSlippageTolerance } from '../../state/user/hooks' import { LinkStyledButton, TYPE } from '../../theme' import { getTradeVersion } from '../../utils/getTradeVersion' -import { isTradeBetter } from '../../utils/isTradeBetter' import { maxAmountSpend } from '../../utils/maxAmountSpend' import { computeTradePriceBreakdown, warningSeverity } from '../../utils/prices' 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) { const loadedUrlParams = useDefaultsFromURLSearch() @@ -321,7 +325,7 @@ export default function Swap({ history }: RouteComponentProps) { onDismiss={handleDismissTokenWarning} /> - + - { setApprovalSubmitted(false) // reset 2 step UI for approvals onSwitchTokens() }} color={currencies[Field.INPUT] && currencies[Field.OUTPUT] ? theme.text1 : theme.text3} - style={{ transform: 'rotate(90deg)' }} /> ) : null} - {trade ? ( - - ) : null} - {[V3TradeState.VALID, V3TradeState.SYNCING, V3TradeState.NO_ROUTE_FOUND].includes(v3TradeState) ? ( - toggledVersion === Version.v3 && isTradeBetter(v3Trade, v2Trade) ? ( - - ) : toggledVersion === Version.v2 && isTradeBetter(v2Trade, v3Trade) ? ( - - ) : null - ) : null} + + + + {[V3TradeState.VALID, V3TradeState.SYNCING, V3TradeState.NO_ROUTE_FOUND].includes(v3TradeState) && + (toggledVersion === Version.v3 && isTradeBetter(v3Trade, v2Trade) ? ( + + ) : toggledVersion === Version.v2 && isTradeBetter(v2Trade, v3Trade) ? ( + + ) : ( + toggledVersion === Version.v2 && ( + +   + + Routed via V2 + + + ) + ))} + + {toggledVersion === Version.v3 && trade && isTradeBetter(v2Trade, v3Trade) && ( + + V3 + + )} + + + {trade ? ( + + ) : ( + + )} + {trade && ( + }> + + + )} + + + {swapIsUnsupported ? ( @@ -447,19 +514,29 @@ export default function Swap({ history }: RouteComponentProps) { {/* 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} {approvalState === ApprovalState.PENDING ? ( ) : (approvalSubmitted && approvalState === ApprovalState.APPROVED) || signatureState === UseERC20PermitState.SIGNED ? ( - + ) : ( - + + + )}
@@ -491,13 +568,6 @@ export default function Swap({ history }: RouteComponentProps) {
- {showApproveFlow && ( - - )} ) : ( .addCase(setOpenModal, (state, action) => { 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([ { key: key || nanoid(), diff --git a/src/theme/index.tsx b/src/theme/index.tsx index 8bab3812ef..7d0346737d 100644 --- a/src/theme/index.tsx +++ b/src/theme/index.tsx @@ -47,9 +47,9 @@ export function colors(darkMode: boolean): Colors { text5: darkMode ? '#2C2F36' : '#EDEEF2', // backgrounds / greys - bg0: darkMode ? '#191B1F' : '#FFFFFF', - bg1: darkMode ? '#212429' : '#F7F8FA', - bg2: darkMode ? '#2C2F36' : '#EDEEF2', + bg0: darkMode ? '#191B1F' : '#F7F8FA', + bg1: darkMode ? '#212429' : '#EDEEF2', + bg2: darkMode ? '#2C2F36' : '#F0F0F0', bg3: darkMode ? '#40444F' : '#CED0D9', bg4: darkMode ? '#565A69' : '#888D9B', bg5: darkMode ? '#6C7284' : '#888D9B', @@ -221,7 +221,7 @@ html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -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; } `