Compare commits

...

9 Commits

Author SHA1 Message Date
Moody Salem
a5ff3beb92 add a comment for the previous change 2020-06-22 16:12:02 -05:00
Moody Salem
35ccf425f6 improvement(modals): do not focus inputs automatically on mobile 2020-06-22 16:11:32 -05:00
Moody Salem
fe030412cd improvement(pair search modal): show exact symbol match pairs first, filter before sorting 2020-06-22 14:37:52 -05:00
Moody Salem
4d5a43351f fix(trustwallet confirm modal): fix the confirmation modal to not be obscured by the trustwallet bar 2020-06-15 23:09:33 -04:00
Moody Salem
ac1bc3b3a6 cleanup(modal): clean up modal code a bit 2020-06-15 16:46:38 -04:00
Noah Zinsmeister
d1063d50ed add COMP, mUSD, STAKE 2020-06-15 16:24:39 -04:00
Moody Salem
46fc74e90f chore(deploy): trigger a deploy and also trigger deploys on .env.production changes 2020-06-15 10:19:46 -04:00
Moody Salem
2c4f4092d8 improvement(advanced): always show advanced (#890)
* always show advanced

* fix test

* always show the price, less jitter

* show tokens in price field

* fix the dropdown sticking around when switching between swap/send

* lint

* fix ios scrolling the modal body into the viewport bug

* don't use react-spring for simple slide animation

* safer price impact constants
2020-06-15 10:13:12 -04:00
Moody Salem
aac7268dc8 just drop the git commit hash (more trouble than it's worth) 2020-06-15 09:36:39 -04:00
18 changed files with 187 additions and 198 deletions

View File

@@ -10,6 +10,7 @@ on:
- v2
paths:
- '.github/workflows/release.yaml'
- '.env.production'
jobs:
bump_version:

View File

@@ -37,7 +37,6 @@ describe('Swap', () => {
cy.get('#swap-currency-input .token-amount-input').should('be.visible')
cy.get('#swap-currency-input .token-amount-input').type('0.001', { force: true, delay: 200 })
cy.get('#swap-currency-output .token-amount-input').should('not.equal', '')
cy.get('#show-advanced').click()
cy.get('#swap-button').click()
cy.get('#confirm-swap-or-send').should('contain', 'Confirm Swap')
})

View File

@@ -80,7 +80,7 @@
},
"scripts": {
"start": "react-scripts start",
"build": "cross-env REACT_APP_GIT_COMMIT_HASH=$(git show -s --format=%H) react-scripts build",
"build": "react-scripts build",
"ipfs-build": "cross-env PUBLIC_URL=\".\" react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",

View File

@@ -77,9 +77,7 @@ const MenuItem = styled(ExternalLink)`
}
`
const CODE_LINK = !!process.env.REACT_APP_GIT_COMMIT_HASH
? `https://github.com/Uniswap/uniswap-frontend/tree/${process.env.REACT_APP_GIT_COMMIT_HASH}`
: 'https://github.com/Uniswap/uniswap-frontend'
const CODE_LINK = 'https://github.com/Uniswap/uniswap-frontend'
export default function Menu() {
const node = useRef<HTMLDivElement>()

View File

@@ -18,6 +18,7 @@ const StyledDialogOverlay = styled(({ mobile, ...rest }) => <AnimatedDialogOverl
align-items: center;
justify-content: center;
background-color: transparent;
overflow: hidden;
${({ mobile }) =>
mobile &&
@@ -69,12 +70,10 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
border-radius: 20px;
${({ theme }) => theme.mediaWidth.upToMedium`
width: 65vw;
max-height: 65vh;
margin: 0;
`}
${({ theme, mobile }) => theme.mediaWidth.upToSmall`
width: 85vw;
max-height: 66vh;
${mobile &&
css`
width: 100vw;
@@ -86,14 +85,6 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
}
`
const HiddenCloseButton = styled.button`
margin: 0;
padding: 0;
width: 0;
height: 0;
border: none;
`
interface ModalProps {
isOpen: boolean
onDismiss: () => void
@@ -118,7 +109,7 @@ export default function Modal({
leave: { opacity: 0 }
})
const [{ xy }, set] = useSpring(() => ({ xy: [0, 0] }))
const [{ y }, set] = useSpring(() => ({ y: 0, config: { mass: 1, tension: 210, friction: 20 } }))
const bind = useGesture({
onDrag: state => {
let velocity = state.velocity
@@ -129,8 +120,7 @@ export default function Modal({
velocity = 8
}
set({
xy: state.down ? state.movement : [0, 0],
config: { mass: 1, tension: 210, friction: 20 }
y: state.down ? state.movement[1] : 0
})
if (velocity > 3 && state.direction[1] > 0) {
onDismiss()
@@ -151,6 +141,8 @@ export default function Modal({
initialFocusRef={initialFocusRef}
mobile={true}
>
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
{initialFocusRef ? null : <div tabIndex={1} />}
<Spring // animation for entrance and exit
from={{
transform: isOpen ? 'translateY(200px)' : 'translateY(100px)'
@@ -163,18 +155,17 @@ export default function Modal({
<animated.div
{...bind()}
style={{
transform: (xy as any).interpolate((x, y) => `translate3d(${0}px,${y > 0 ? y : 0}px,0)`)
transform: y.interpolate(y => `translateY(${y > 0 ? y : 0}px)`)
}}
>
<StyledDialogContent
ariaLabel="test"
aria-label="dialog content"
style={props}
hidden={true}
minHeight={minHeight}
maxHeight={maxHeight}
mobile={isMobile ?? undefined}
mobile={isMobile}
>
<HiddenCloseButton onClick={onDismiss} />
{children}
</StyledDialogContent>
</animated.div>
@@ -192,8 +183,13 @@ export default function Modal({
({ item, key, props }) =>
item && (
<StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}>
<StyledDialogContent hidden={true} minHeight={minHeight} maxHeight={maxHeight} isOpen={isOpen}>
<HiddenCloseButton onClick={onDismiss} />
<StyledDialogContent
aria-label="dialog content"
hidden={true}
minHeight={minHeight}
maxHeight={maxHeight}
isOpen={isOpen}
>
{children}
</StyledDialogContent>
</StyledDialogOverlay>

View File

@@ -54,17 +54,23 @@ function PairSearchModal({ history, isOpen, onDismiss }: PairSearchModalProps) {
setSearchQuery(checksummedInput || input)
}
const filteredPairs = useMemo(() => {
return filterPairs(allPairs, searchQuery)
}, [allPairs, searchQuery])
const sortedPairList = useMemo(() => {
return allPairs.sort((a, b): number => {
const query = searchQuery.toLowerCase()
const queryMatches = (pair: Pair): boolean =>
pair.token0.symbol.toLowerCase() === query || pair.token1.symbol.toLowerCase() === query
return filteredPairs.sort((a, b): number => {
const [aMatches, bMatches] = [queryMatches(a), queryMatches(b)]
if (aMatches && !bMatches) return -1
if (bMatches && !aMatches) return 1
const balanceA = allPairBalances[a.liquidityToken.address]
const balanceB = allPairBalances[b.liquidityToken.address]
return pairComparator(a, b, balanceA, balanceB)
})
}, [allPairs, allPairBalances])
const filteredPairs = useMemo(() => {
return filterPairs(sortedPairList, searchQuery)
}, [searchQuery, sortedPairList])
}, [searchQuery, filteredPairs, allPairBalances])
const selectPair = useCallback(
(pair: Pair) => {
@@ -110,7 +116,7 @@ function PairSearchModal({ history, isOpen, onDismiss }: PairSearchModalProps) {
</PaddedColumn>
<div style={{ width: '100%', height: '1px', backgroundColor: theme.bg2 }} />
<PairList
pairs={filteredPairs}
pairs={sortedPairList}
focusTokenAddress={focusedToken?.address}
onAddLiquidity={selectPair}
onSelectPair={selectPair}

View File

@@ -1,10 +1,10 @@
import { Trade, TradeType } from '@uniswap/sdk'
import React, { useContext } from 'react'
import { ChevronUp, ChevronRight } from 'react-feather'
import { Text, Flex } from 'rebass'
import { ChevronRight } from 'react-feather'
import { Flex } from 'rebass'
import { ThemeContext } from 'styled-components'
import { Field } from '../../state/swap/actions'
import { CursorPointer, TYPE } from '../../theme'
import { TYPE } from '../../theme'
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown } from '../../utils/prices'
import { AutoColumn } from '../Column'
import { SectionBreak } from './styleds'
@@ -67,29 +67,21 @@ function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippag
export interface AdvancedSwapDetailsProps {
trade?: Trade
onDismiss: () => void
}
export function AdvancedSwapDetails({ trade, onDismiss }: AdvancedSwapDetailsProps) {
export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
const theme = useContext(ThemeContext)
const [allowedSlippage] = useUserSlippageTolerance()
const showRoute = trade?.route?.path?.length > 2
return (
<AutoColumn gap="md">
<CursorPointer>
<RowBetween onClick={onDismiss} padding={'8px 20px'}>
<Text fontSize={16} color={theme.text2} fontWeight={500} style={{ userSelect: 'none' }}>
Hide Advanced
</Text>
<ChevronUp color={theme.text2} />
</RowBetween>
</CursorPointer>
<SectionBreak />
{trade && <TradeSummary trade={trade} allowedSlippage={allowedSlippage} />}
{trade?.route?.path?.length > 2 && <SectionBreak />}
{trade?.route?.path?.length > 2 && (
<AutoColumn style={{ padding: '0 20px' }}>
{showRoute && <SectionBreak />}
{showRoute && (
<AutoColumn style={{ padding: '0 24px' }}>
<RowFixed>
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
Route

View File

@@ -1,35 +1,30 @@
import React, { useContext } from 'react'
import { ChevronDown } from 'react-feather'
import { Text } from 'rebass'
import { ThemeContext } from 'styled-components'
import { CursorPointer } from '../../theme'
import { RowBetween } from '../Row'
import React from 'react'
import styled from 'styled-components'
import useLast from '../../hooks/useLast'
import { AdvancedSwapDetails, AdvancedSwapDetailsProps } from './AdvancedSwapDetails'
import { AdvancedDropdown } from './styleds'
export default function AdvancedSwapDetailsDropdown({
showAdvanced,
setShowAdvanced,
...rest
}: Omit<AdvancedSwapDetailsProps, 'onDismiss'> & {
showAdvanced: boolean
setShowAdvanced: (showAdvanced: boolean) => void
}) {
const theme = useContext(ThemeContext)
const AdvancedDetailsFooter = styled.div<{ show: boolean }>`
padding-top: calc(16px + 2rem);
padding-bottom: 20px;
margin-top: -2rem;
width: 100%;
max-width: 400px;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
color: ${({ theme }) => theme.text2};
background-color: ${({ theme }) => theme.advancedBG};
z-index: -1;
transform: ${({ show }) => (show ? 'translateY(0%)' : 'translateY(-100%)')};
transition: transform 300ms ease-in-out;
`
export default function AdvancedSwapDetailsDropdown({ trade, ...rest }: AdvancedSwapDetailsProps) {
const lastTrade = useLast(trade)
return (
<AdvancedDropdown>
{showAdvanced ? (
<AdvancedSwapDetails {...rest} onDismiss={() => setShowAdvanced(false)} />
) : (
<CursorPointer>
<RowBetween onClick={() => setShowAdvanced(true)} padding={'8px 20px'} id="show-advanced">
<Text fontSize={16} fontWeight={500} style={{ userSelect: 'none' }}>
Show Advanced
</Text>
<ChevronDown color={theme.text2} />
</RowBetween>
</CursorPointer>
)}
</AdvancedDropdown>
<AdvancedDetailsFooter show={Boolean(trade)}>
<AdvancedSwapDetails {...rest} trade={lastTrade} />
</AdvancedDetailsFooter>
)
}

View File

@@ -1,5 +1,5 @@
import React from 'react'
import { Trade } from '@uniswap/sdk'
import { Price, Token } from '@uniswap/sdk'
import { useContext } from 'react'
import { Repeat } from 'react-feather'
import { Text } from 'rebass'
@@ -7,20 +7,19 @@ import { ThemeContext } from 'styled-components'
import { StyledBalanceMaxMini } from './styleds'
interface TradePriceProps {
trade?: Trade
price?: Price
inputToken?: Token
outputToken?: Token
showInverted: boolean
setShowInverted: (showInverted: boolean) => void
}
export default function TradePrice({ trade, showInverted, setShowInverted }: TradePriceProps) {
export default function TradePrice({ price, inputToken, outputToken, showInverted, setShowInverted }: TradePriceProps) {
const theme = useContext(ThemeContext)
const inputToken = trade?.inputAmount?.token
const outputToken = trade?.outputAmount?.token
const price = showInverted
? trade?.executionPrice?.toSignificant(6)
: trade?.executionPrice?.invert()?.toSignificant(6)
const formattedPrice = showInverted ? price?.toSignificant(6) : price?.invert()?.toSignificant(6)
const show = Boolean(inputToken && outputToken)
const label = showInverted
? `${outputToken?.symbol} per ${inputToken?.symbol}`
: `${inputToken?.symbol} per ${outputToken?.symbol}`
@@ -32,10 +31,16 @@ export default function TradePrice({ trade, showInverted, setShowInverted }: Tra
color={theme.text2}
style={{ justifyContent: 'center', alignItems: 'center', display: 'flex' }}
>
{price && `${price} ${label}`}
<StyledBalanceMaxMini onClick={() => setShowInverted(!showInverted)}>
<Repeat size={14} />
</StyledBalanceMaxMini>
{show ? (
<>
{formattedPrice ?? '-'} {label}
<StyledBalanceMaxMini onClick={() => setShowInverted(!showInverted)}>
<Repeat size={14} />
</StyledBalanceMaxMini>
</>
) : (
'-'
)}
</Text>
)
}

View File

@@ -21,19 +21,6 @@ export const ArrowWrapper = styled.div`
}
`
export const AdvancedDropdown = styled.div`
padding-top: calc(10px + 2rem);
padding-bottom: 10px;
margin-top: -2rem;
width: 100%;
max-width: 400px;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
color: ${({ theme }) => theme.text2};
background-color: ${({ theme }) => theme.advancedBG};
z-index: -1;
`
export const SectionBreak = styled.div`
height: 1px;
width: 100%;
@@ -71,7 +58,7 @@ export const StyledNumerical = styled(NumericalInput)`
color: ${({ theme }) => theme.text4};
}
`
export const StyledBalanceMaxMini = styled.button<{ active?: boolean }>`
export const StyledBalanceMaxMini = styled.button`
height: 22px;
width: 22px;
background-color: ${({ theme }) => theme.bg2};

View File

@@ -151,13 +151,12 @@ export const ONE_BIPS = new Percent(JSBI.BigInt(1), JSBI.BigInt(10000))
export const BIPS_BASE = JSBI.BigInt(10000)
// used for warning states
export const ALLOWED_PRICE_IMPACT_LOW: Percent = new Percent(JSBI.BigInt(100), BIPS_BASE) // 1%
export const ALLOWED_PRICE_IMPACT_MEDIUM: Percent = new Percent(JSBI.BigInt(500), BIPS_BASE) // 5%
export const ALLOWED_PRICE_IMPACT_HIGH: Percent = new Percent(JSBI.BigInt(1000), BIPS_BASE) // 10%
// for non expert mode disable swaps above this
export const BLOCKED_PRICE_IMPACT_NON_EXPERT: Percent = new Percent(JSBI.BigInt(5000), BIPS_BASE) // 50%
export const ALLOWED_PRICE_IMPACT_MEDIUM: Percent = new Percent(JSBI.BigInt(300), BIPS_BASE) // 3%
export const ALLOWED_PRICE_IMPACT_HIGH: Percent = new Percent(JSBI.BigInt(500), BIPS_BASE) // 5%
// if the price slippage exceeds this number, force the user to type 'confirm' to execute
export const PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN: Percent = new Percent(JSBI.BigInt(2500), BIPS_BASE) // 25%
export const PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN: Percent = new Percent(JSBI.BigInt(1000), BIPS_BASE) // 10%
// for non expert mode disable swaps above this
export const BLOCKED_PRICE_IMPACT_NON_EXPERT: Percent = new Percent(JSBI.BigInt(1500), BIPS_BASE) // 15%
// used to ensure the user doesn't send so much ETH so they end up with <.01
export const MIN_ETH: JSBI = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(16)) // .01 ETH

View File

@@ -19,6 +19,7 @@ export default [
new Token(ChainId.MAINNET, '0x39AA39c021dfbaE8faC545936693aC917d5E7563', 8, 'cUSDC', 'Compound USD Coin'),
new Token(ChainId.MAINNET, '0xaaAEBE6Fe48E54f431b0C390CfaF0b017d09D42d', 4, 'CEL', 'Celsius'),
new Token(ChainId.MAINNET, '0x06AF07097C9Eeb7fD685c692751D5C66dB49c215', 18, 'CHAI', 'Chai'),
new Token(ChainId.MAINNET, '0xc00e94Cb662C3520282E6f5717214004A7f26888', 18, 'COMP', 'Compound'),
new Token(ChainId.MAINNET, '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359', 18, 'SAI', 'Dai Stablecoin v1.0 (SAI)'),
new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'),
new Token(ChainId.MAINNET, '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', 18, 'DATA', 'Streamr DATAcoin'),
@@ -64,6 +65,7 @@ export default [
new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 18, 'MKR', 'Maker'),
new Token(ChainId.MAINNET, '0xec67005c4E498Ec7f55E092bd1d35cbC47C91892', 18, 'MLN', 'Melon Token'),
new Token(ChainId.MAINNET, '0x957c30aB0426e0C93CD8241E2c60392d08c6aC8e', 0, 'MOD', 'Modum Token'),
new Token(ChainId.MAINNET, '0xe2f2a5C287993345a840Db3B0845fbC70f5935a5', 18, 'mUSD', 'mStable USD'),
new Token(ChainId.MAINNET, '0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206', 18, 'NEXO', 'Nexo'),
new Token(ChainId.MAINNET, '0x1776e1F26f98b1A5dF9cD347953a26dd3Cb46671', 18, 'NMR', 'Numeraire'),
new Token(ChainId.MAINNET, '0x985dd3D42De1e256d09e1c10F112bCCB8015AD41', 18, 'OCEAN', 'OceanToken'),
@@ -94,6 +96,7 @@ export default [
new Token(ChainId.MAINNET, '0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F', 18, 'SNX', 'Synthetix Network Token'),
new Token(ChainId.MAINNET, '0x23B608675a2B2fB1890d3ABBd85c5775c51691d5', 18, 'SOCKS', 'Unisocks Edition 0'),
new Token(ChainId.MAINNET, '0x42d6622deCe394b54999Fbd73D108123806f6a18', 18, 'SPANK', 'SPANK'),
new Token(ChainId.MAINNET, '0x0Ae055097C6d159879521C384F1D2123D1f195e6', 18, 'STAKE', 'STAKE'),
new Token(ChainId.MAINNET, '0xB64ef51C888972c908CFacf59B47C1AfBC0Ab8aC', 8, 'STORJ', 'StorjToken'),
new Token(ChainId.MAINNET, '0x57Ab1ec28D129707052df4dF418D58a2D46d5f51', 18, 'sUSD', 'Synth sUSD'),
new Token(ChainId.MAINNET, '0x261EfCdD24CeA98652B9700800a13DfBca4103fF', 18, 'sXAU', 'Synth sXAU'),

13
src/hooks/useLast.ts Normal file
View File

@@ -0,0 +1,13 @@
import { useEffect, useState } from 'react'
/**
* Returns the last truthy value of type T
* @param value changing value
*/
export default function useLast<T>(value: T | undefined | null): T | null | undefined {
const [last, setLast] = useState<T | null | undefined>(value)
useEffect(() => {
setLast(last => value ?? last)
}, [value])
return last
}

View File

@@ -270,8 +270,8 @@ export default function AddLiquidity({ match: { params } }: RouteComponentProps<
const PriceBar = () => {
return (
<AutoColumn gap="md" justify="space-between">
<AutoRow justify="space-between">
<AutoColumn gap="md">
<AutoRow justify="space-around" gap="4px">
<AutoColumn justify="center">
<TYPE.black>{price?.toSignificant(6) ?? '0'}</TYPE.black>
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>

View File

@@ -10,11 +10,9 @@ import Card, { BlueCard, GreyCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import QuestionHelper from '../../components/QuestionHelper'
import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
import { AutoRow, RowBetween } from '../../components/Row'
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
import FormattedPriceImpact from '../../components/swap/FormattedPriceImpact'
import SwapModalFooter from '../../components/swap/SwapModalFooter'
import { ArrowWrapper, BottomGrouping, Dots, InputGroup, StyledNumerical, Wrapper } from '../../components/swap/styleds'
import TradePrice from '../../components/swap/TradePrice'
@@ -98,7 +96,6 @@ export default function Send() {
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
// modal and loading
const [showAdvanced, setShowAdvanced] = useState<boolean>(false) // toggling slippage, deadline, etc. on and off
const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
const [txHash, setTxHash] = useState<string>('')
@@ -476,14 +473,20 @@ export default function Send() {
}}
/>
</AutoColumn>
{!noRoute && tokens[Field.OUTPUT] && tokens[Field.INPUT] && (
{sendingWithSwap && (
<Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
<AutoColumn gap="4px">
<RowBetween align="center">
<Text fontWeight={500} fontSize={14} color={theme.text2}>
Price
</Text>
<TradePrice showInverted={showInverted} setShowInverted={setShowInverted} trade={bestTrade} />
<TradePrice
inputToken={tokens[Field.INPUT]}
outputToken={tokens[Field.OUTPUT]}
price={bestTrade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
@@ -500,20 +503,6 @@ export default function Send() {
</ClickableText>
</RowBetween>
)}
{bestTrade && severity > 1 && (
<RowBetween>
<TYPE.main
style={{ justifyContent: 'center', alignItems: 'center', display: 'flex' }}
fontSize={14}
>
Price Impact
</TYPE.main>
<RowFixed>
<FormattedPriceImpact priceImpact={priceImpactWithoutFee} />
<QuestionHelper text="The difference between the market price and estimated price due to trade size." />
</RowFixed>
</RowBetween>
)}
</AutoColumn>
</Card>
)}
@@ -588,9 +577,7 @@ export default function Send() {
</Wrapper>
</AppBody>
{bestTrade && (
<AdvancedSwapDetailsDropdown trade={bestTrade} showAdvanced={showAdvanced} setShowAdvanced={setShowAdvanced} />
)}
<AdvancedSwapDetailsDropdown trade={bestTrade} />
</>
)
}

View File

@@ -9,11 +9,9 @@ import Card, { GreyCard } from '../../components/Card'
import { AutoColumn } from '../../components/Column'
import ConfirmationModal from '../../components/ConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import QuestionHelper from '../../components/QuestionHelper'
import { RowBetween, RowFixed } from '../../components/Row'
import { RowBetween } from '../../components/Row'
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
import FormattedPriceImpact from '../../components/swap/FormattedPriceImpact'
import { ArrowWrapper, BottomGrouping, Dots, Wrapper } from '../../components/swap/styleds'
import SwapModalFooter from '../../components/swap/SwapModalFooter'
import SwapModalHeader from '../../components/swap/SwapModalHeader'
@@ -84,7 +82,6 @@ export default function Swap() {
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
// modal and loading
const [showAdvanced, setShowAdvanced] = useState<boolean>(false) // toggling slippage, deadline, etc. on and off
const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
const [txHash, setTxHash] = useState<string>('')
@@ -284,44 +281,33 @@ export default function Swap() {
/>
</>
{!noRoute && tokens[Field.OUTPUT] && tokens[Field.INPUT] && (
<Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
<AutoColumn gap="4px">
<Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
<AutoColumn gap="4px">
<RowBetween align="center">
<Text fontWeight={500} fontSize={14} color={theme.text2}>
Price
</Text>
<TradePrice
inputToken={tokens[Field.INPUT]}
outputToken={tokens[Field.OUTPUT]}
price={bestTrade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<Text fontWeight={500} fontSize={14} color={theme.text2}>
Price
</Text>
<TradePrice trade={bestTrade} showInverted={showInverted} setShowInverted={setShowInverted} />
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
Slippage Tolerance
</ClickableText>
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
{allowedSlippage ? allowedSlippage / 100 : '-'}%
</ClickableText>
</RowBetween>
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
Slippage Tolerance
</ClickableText>
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
{allowedSlippage ? allowedSlippage / 100 : '-'}%
</ClickableText>
</RowBetween>
)}
{bestTrade && priceImpactSeverity > 1 && (
<RowBetween>
<TYPE.main
style={{ justifyContent: 'center', alignItems: 'center', display: 'flex' }}
fontSize={14}
>
Price Impact
</TYPE.main>
<RowFixed>
<FormattedPriceImpact priceImpact={priceImpactWithoutFee} />
<QuestionHelper text="The difference between the market price and estimated price due to trade size." />
</RowFixed>
</RowBetween>
)}
</AutoColumn>
</Card>
)}
)}
</AutoColumn>
</Card>
</AutoColumn>
<BottomGrouping>
{!account ? (
@@ -385,9 +371,7 @@ export default function Swap() {
</Wrapper>
</AppBody>
{bestTrade && (
<AdvancedSwapDetailsDropdown trade={bestTrade} showAdvanced={showAdvanced} setShowAdvanced={setShowAdvanced} />
)}
<AdvancedSwapDetailsDropdown trade={bestTrade} />
</>
)
}

View File

@@ -0,0 +1,29 @@
import { createStore, Store } from 'redux'
import { DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../../constants'
import { updateVersion } from './actions'
import reducer, { initialState, UserState } from './reducer'
describe('swap reducer', () => {
let store: Store<UserState>
beforeEach(() => {
store = createStore(reducer, initialState)
})
describe('updateVersion', () => {
it('has no timestamp originally', () => {
expect(store.getState().lastUpdateVersionTimestamp).toBeUndefined()
})
it('sets the lastUpdateVersionTimestamp', () => {
const time = new Date().getTime()
store.dispatch(updateVersion())
expect(store.getState().lastUpdateVersionTimestamp).toBeGreaterThanOrEqual(time)
})
it('sets allowed slippage and deadline', () => {
store = createStore(reducer, { ...initialState, userDeadline: undefined, userSlippageTolerance: undefined })
store.dispatch(updateVersion())
expect(store.getState().userDeadline).toEqual(DEFAULT_DEADLINE_FROM_NOW)
expect(store.getState().userSlippageTolerance).toEqual(INITIAL_ALLOWED_SLIPPAGE)
})
})
})

View File

@@ -1,4 +1,4 @@
import { INITIAL_ALLOWED_SLIPPAGE, DEFAULT_DEADLINE_FROM_NOW } from './../../constants/index'
import { INITIAL_ALLOWED_SLIPPAGE, DEFAULT_DEADLINE_FROM_NOW } from '../../constants'
import { createReducer } from '@reduxjs/toolkit'
import {
addSerializedPair,
@@ -18,8 +18,9 @@ import {
const currentTimestamp = () => new Date().getTime()
interface UserState {
lastVersion: string
export interface UserState {
// the timestamp of the last updateVersion action
lastUpdateVersionTimestamp?: number
userDarkMode: boolean | null // the user's choice for dark mode or light mode
matchesDarkMode: boolean // whether the dark mode media query matches
@@ -59,8 +60,7 @@ function pairKey(token0Address: string, token1Address: string) {
return `${token0Address};${token1Address}`
}
const initialState: UserState = {
lastVersion: '',
export const initialState: UserState = {
userDarkMode: null,
matchesDarkMode: false,
userExpertMode: false,
@@ -71,25 +71,20 @@ const initialState: UserState = {
timestamp: currentTimestamp()
}
const GIT_COMMIT_HASH: string | undefined = process.env.REACT_APP_GIT_COMMIT_HASH
export default createReducer(initialState, builder =>
builder
.addCase(updateVersion, state => {
if (GIT_COMMIT_HASH && state.lastVersion !== GIT_COMMIT_HASH) {
state.lastVersion = GIT_COMMIT_HASH
// slippage isnt being tracked in local storage, reset to default
if (typeof state.userSlippageTolerance !== 'number') {
state.userSlippageTolerance = INITIAL_ALLOWED_SLIPPAGE
}
// deadline isnt being tracked in local storage, reset to default
if (typeof state.userDeadline !== 'number') {
state.userDeadline = DEFAULT_DEADLINE_FROM_NOW
}
// slippage isnt being tracked in local storage, reset to default
if (typeof state.userSlippageTolerance !== 'number') {
state.userSlippageTolerance = INITIAL_ALLOWED_SLIPPAGE
}
state.timestamp = currentTimestamp()
// deadline isnt being tracked in local storage, reset to default
if (typeof state.userDeadline !== 'number') {
state.userDeadline = DEFAULT_DEADLINE_FROM_NOW
}
state.lastUpdateVersionTimestamp = currentTimestamp()
})
.addCase(updateUserDarkMode, (state, action) => {
state.userDarkMode = action.payload.userDarkMode