Commit 199eb8bf authored by Moody Salem's avatar Moody Salem Committed by GitHub

Refactor pages directory (#809)

* Revert "Temporarily disable the token warning for style fixes"

This reverts commit 70722b5e

* Move pages around and refactor to better support rendering content outside the app body

* Automatically add tokens in the add and remove liquidity pages

* Put the warning above the send/swap pages

* Add a margin below the token warning cards

* Save token warning dismissal state in user state

* Linting errors

* style fix
parent f1c6119f
import React, { useState, useEffect } from 'react'
import { withRouter, RouteComponentProps } from 'react-router-dom'
import { Token, WETH } from '@uniswap/sdk'
import Row, { AutoRow } from '../Row'
import TokenLogo from '../TokenLogo'
import SearchModal from '../SearchModal'
import AddLiquidity from '../../pages/Pool/AddLiquidity'
import { Text } from 'rebass'
import { Plus } from 'react-feather'
import { TYPE, Link } from '../../theme'
import { AutoColumn, ColumnCenter } from '../Column'
import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../Button'
import { useToken } from '../../hooks/Tokens'
import { useActiveWeb3React } from '../../hooks'
import { usePair } from '../../data/Reserves'
const Fields = {
TOKEN0: 0,
TOKEN1: 1
}
const STEP = {
SELECT_TOKENS: 'SELECT_TOKENS', // choose input and output tokens
READY_TO_CREATE: 'READY_TO_CREATE', // enable 'create' button
SHOW_CREATE_PAGE: 'SHOW_CREATE_PAGE' // show create page
}
function CreatePool({ history }: RouteComponentProps) {
const { chainId } = useActiveWeb3React()
const [showSearch, setShowSearch] = useState<boolean>(false)
const [activeField, setActiveField] = useState<number>(Fields.TOKEN0)
const [token0Address, setToken0Address] = useState<string>(WETH[chainId].address)
const [token1Address, setToken1Address] = useState<string>()
const token0: Token = useToken(token0Address)
const token1: Token = useToken(token1Address)
const [step, setStep] = useState<string>(STEP.SELECT_TOKENS)
const pair = usePair(token0, token1)
// if both tokens selected but pair doesnt exist, enable button to create pair
useEffect(() => {
if (token0Address && token1Address && pair === null) {
setStep(STEP.READY_TO_CREATE)
}
}, [pair, token0Address, token1Address])
// if theyve clicked create, show add liquidity page
if (step === STEP.SHOW_CREATE_PAGE) {
return <AddLiquidity token0={token0Address} token1={token1Address} />
}
return (
<AutoColumn gap="20px">
<AutoColumn gap="24px">
{!token0Address ? (
<ButtonDropwdown
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN0)
}}
>
<Text fontSize={20}>Select first token</Text>
</ButtonDropwdown>
) : (
<ButtonDropwdownLight
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN0)
}}
>
<Row align="flex-end">
<TokenLogo address={token0Address} />
<Text fontWeight={500} fontSize={20} marginLeft={'12px'}>
{token0?.symbol}{' '}
</Text>
<TYPE.darkGray fontWeight={500} fontSize={16} marginLeft={'8px'}>
{token0?.address === 'ETH' && '(default)'}
</TYPE.darkGray>
</Row>
</ButtonDropwdownLight>
)}
<ColumnCenter>
<Plus size="16" color="#888D9B" />
</ColumnCenter>
{!token1Address ? (
<ButtonDropwdown
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN1)
}}
disabled={step !== STEP.SELECT_TOKENS}
>
<Text fontSize={20}>Select second token</Text>
</ButtonDropwdown>
) : (
<ButtonDropwdownLight
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN1)
}}
>
<Row>
<TokenLogo address={token1Address} />
<Text fontWeight={500} fontSize={20} marginLeft={'12px'}>
{token1?.symbol}
</Text>
</Row>
</ButtonDropwdownLight>
)}
{pair ? ( // pair already exists - prompt to add liquidity to existing pool
<AutoRow padding="10px" justify="center">
<TYPE.body textAlign="center">
Pool already exists!
<Link onClick={() => history.push('/add/' + token0Address + '-' + token1Address)}> Join the pool.</Link>
</TYPE.body>
</AutoRow>
) : (
<ButtonPrimary disabled={step !== STEP.READY_TO_CREATE} onClick={() => setStep(STEP.SHOW_CREATE_PAGE)}>
<Text fontWeight={500} fontSize={20}>
Create Pool
</Text>
</ButtonPrimary>
)}
</AutoColumn>
<SearchModal
isOpen={showSearch}
filterType="tokens"
onTokenSelect={address => {
activeField === Fields.TOKEN0 ? setToken0Address(address) : setToken1Address(address)
}}
onDismiss={() => {
setShowSearch(false)
}}
hiddenToken={activeField === Fields.TOKEN0 ? token1Address : token0Address}
showCommonBases={activeField === Fields.TOKEN0}
/>
</AutoColumn>
)
}
export default withRouter(CreatePool)
import { Token } from '@uniswap/sdk' import { Token } from '@uniswap/sdk'
import { transparentize } from 'polished' import { transparentize } from 'polished'
import React, { useEffect, useMemo, useState } from 'react' import React, { useMemo } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { ALL_TOKENS, useAllTokens } from '../../hooks/Tokens' import { ALL_TOKENS, useAllTokens } from '../../hooks/Tokens'
import { Field } from '../../state/swap/actions' import { Field } from '../../state/swap/actions'
import { useTokenWarningDismissal } from '../../state/user/hooks'
import { Link, TYPE } from '../../theme'
import { getEtherscanLink } from '../../utils' import { getEtherscanLink } from '../../utils'
import { Link } from '../../theme' import PropsOfExcluding from '../../utils/props-of-excluding'
import QuestionHelper from '../Question' import QuestionHelper from '../Question'
import TokenLogo from '../TokenLogo' import TokenLogo from '../TokenLogo'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { TYPE } from '../../theme'
const Wrapper = styled.div<{ error: boolean }>` const Wrapper = styled.div<{ error: boolean }>`
background: ${({ theme, error }) => transparentize(0.9, error ? theme.red1 : theme.yellow1)}; background: ${({ theme, error }) => transparentize(0.9, error ? theme.red1 : theme.yellow1)};
...@@ -52,10 +53,6 @@ const CloseIcon = styled.div` ...@@ -52,10 +53,6 @@ const CloseIcon = styled.div`
} }
` `
interface TokenWarningCardProps {
token?: Token
}
const HELP_TEXT = ` const HELP_TEXT = `
The Uniswap V2 smart contracts are designed to support any ERC20 token on Ethereum. Any token can be The Uniswap V2 smart contracts are designed to support any ERC20 token on Ethereum. Any token can be
loaded into the interface by entering its Ethereum address into the search field or passing it as a URL loaded into the interface by entering its Ethereum address into the search field or passing it as a URL
...@@ -64,20 +61,21 @@ parameter. ...@@ -64,20 +61,21 @@ parameter.
const DUPLICATE_NAME_HELP_TEXT = `${HELP_TEXT} This token has the same name or symbol as another token in your list.` const DUPLICATE_NAME_HELP_TEXT = `${HELP_TEXT} This token has the same name or symbol as another token in your list.`
export default function TokenWarningCard({ token }: TokenWarningCardProps) { interface TokenWarningCardProps extends PropsOfExcluding<typeof Wrapper, 'error'> {
token?: Token
}
export default function TokenWarningCard({ token, ...rest }: TokenWarningCardProps) {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const [dismissed, setDismissed] = useState<boolean>(false)
const isDefaultToken = Boolean( const isDefaultToken = Boolean(
token && token.address && chainId && ALL_TOKENS[chainId] && ALL_TOKENS[chainId][token.address] token && token.address && chainId && ALL_TOKENS[chainId] && ALL_TOKENS[chainId][token.address]
) )
useEffect(() => {
setDismissed(false)
}, [token, setDismissed])
const tokenSymbol = token?.symbol?.toLowerCase() ?? '' const tokenSymbol = token?.symbol?.toLowerCase() ?? ''
const tokenName = token?.name?.toLowerCase() ?? '' const tokenName = token?.name?.toLowerCase() ?? ''
const [dismissed, dismissTokenWarning] = useTokenWarningDismissal(chainId, token)
const allTokens = useAllTokens() const allTokens = useAllTokens()
const duplicateNameOrSymbol = useMemo(() => { const duplicateNameOrSymbol = useMemo(() => {
...@@ -95,9 +93,9 @@ export default function TokenWarningCard({ token }: TokenWarningCardProps) { ...@@ -95,9 +93,9 @@ export default function TokenWarningCard({ token }: TokenWarningCardProps) {
if (isDefaultToken || !token || dismissed) return null if (isDefaultToken || !token || dismissed) return null
return ( return (
<Wrapper error={duplicateNameOrSymbol}> <Wrapper error={duplicateNameOrSymbol} {...rest}>
{duplicateNameOrSymbol ? null : ( {duplicateNameOrSymbol ? null : (
<CloseIcon onClick={() => setDismissed(true)}> <CloseIcon onClick={dismissTokenWarning}>
<CloseColor /> <CloseColor />
</CloseIcon> </CloseIcon>
)} )}
...@@ -123,17 +121,12 @@ export default function TokenWarningCard({ token }: TokenWarningCardProps) { ...@@ -123,17 +121,12 @@ export default function TokenWarningCard({ token }: TokenWarningCardProps) {
) )
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function TokenWarningCards({ tokens }: { tokens: { [field in Field]?: Token } }) { export function TokenWarningCards({ tokens }: { tokens: { [field in Field]?: Token } }) {
return null return (
// temporarily disabled for styling <>
// return ( {Object.keys(tokens).map(field =>
// <div style={{ width: '100%', position: 'absolute', top: 'calc(100% + 30px)' }}> tokens[field] ? <TokenWarningCard style={{ marginBottom: 10 }} key={field} token={tokens[field]} /> : null
// {Object.keys(tokens).map(field => ( )}
// <div key={field} style={{ marginBottom: 10 }}> </>
// <TokenWarningCard token={tokens[field]} /> )
// </div>
// ))}
// </div>
// )
} }
import React, { Suspense } from 'react' import React, { Suspense } from 'react'
import { BrowserRouter, Redirect, Route, RouteComponentProps, Switch } from 'react-router-dom' import { BrowserRouter, Route, Switch } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import GoogleAnalyticsReporter from '../components/analytics/GoogleAnalyticsReporter' import GoogleAnalyticsReporter from '../components/analytics/GoogleAnalyticsReporter'
import Create from '../components/CreatePool'
import Footer from '../components/Footer' import Footer from '../components/Footer'
import Header from '../components/Header' import Header from '../components/Header'
import NavigationTabs from '../components/NavigationTabs'
import Find from '../components/PoolFinder'
import Popups from '../components/Popups' import Popups from '../components/Popups'
import Web3ReactManager from '../components/Web3ReactManager' import Web3ReactManager from '../components/Web3ReactManager'
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader' import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
import { isAddress } from '../utils' import AddLiquidity from './AddLiquidity'
import CreatePool from './CreatePool'
import Pool from './Pool' import Pool from './Pool'
import Add from './Pool/AddLiquidity' import PoolFinder from './PoolFinder'
import Remove from './Pool/RemoveLiquidity' import RemoveLiquidity from './RemoveLiquidity'
import Send from './Send' import Send from './Send'
import Swap from './Swap' import Swap from './Swap'
import { RedirectPathToSwapOnly, RedirectToSwap } from './Swap/redirects'
const AppWrapper = styled.div` const AppWrapper = styled.div`
display: flex; display: flex;
...@@ -42,14 +38,9 @@ const BodyWrapper = styled.div` ...@@ -42,14 +38,9 @@ const BodyWrapper = styled.div`
padding-top: 160px; padding-top: 160px;
align-items: center; align-items: center;
flex: 1; flex: 1;
overflow: auto; overflow-y: auto;
overflow-x: hidden;
z-index: 10; z-index: 10;
transition: height 0.3s ease;
& > * {
max-width: calc(420px + 4rem);
width: 90%;
}
${({ theme }) => theme.mediaWidth.upToExtraSmall` ${({ theme }) => theme.mediaWidth.upToExtraSmall`
padding: 16px; padding: 16px;
...@@ -58,21 +49,7 @@ const BodyWrapper = styled.div` ...@@ -58,21 +49,7 @@ const BodyWrapper = styled.div`
z-index: 1; z-index: 1;
` `
const Body = styled.div` const BackgroundGradient = styled.div`
max-width: 420px;
width: 100%;
/* min-height: 340px; */
background: ${({ theme }) => theme.bg1};
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: 30px;
box-sizing: border-box;
padding: 1rem;
position: relative;
margin-bottom: 10rem;
`
const StyledRed = styled.div`
width: 100%; width: 100%;
height: 200vh; height: 200vh;
background: ${({ theme }) => `radial-gradient(50% 50% at 50% 50%, ${theme.primary1} 0%, ${theme.bg1} 100%)`}; background: ${({ theme }) => `radial-gradient(50% 50% at 50% 50%, ${theme.primary1} 0%, ${theme.bg1} 100%)`};
...@@ -91,102 +68,37 @@ const StyledRed = styled.div` ...@@ -91,102 +68,37 @@ const StyledRed = styled.div`
} }
` `
// Redirects to swap but only replace the pathname
function RedirectPathToSwapOnly({ location }: RouteComponentProps) {
return <Redirect to={{ ...location, pathname: '/swap' }} />
}
// Redirects from the /swap/:outputCurrency path to the /swap?outputCurrency=:outputCurrency format
function RedirectToSwap(props: RouteComponentProps<{ outputCurrency: string }>) {
const {
location: { search },
match: {
params: { outputCurrency }
}
} = props
return (
<Redirect
to={{
...props.location,
pathname: '/swap',
search:
search && search.length > 1
? `${search}&outputCurrency=${outputCurrency}`
: `?outputCurrency=${outputCurrency}`
}}
/>
)
}
export default function App() { export default function App() {
return ( return (
<> <Suspense fallback={null}>
<Suspense fallback={null}> <BrowserRouter>
<BrowserRouter> <Route component={GoogleAnalyticsReporter} />
<Route component={GoogleAnalyticsReporter} /> <Route component={DarkModeQueryParamReader} />
<Route component={DarkModeQueryParamReader} /> <AppWrapper>
<AppWrapper> <HeaderWrapper>
<HeaderWrapper> <Header />
<Header /> </HeaderWrapper>
</HeaderWrapper> <BodyWrapper>
<BodyWrapper> <Popups />
<Popups /> <Web3ReactManager>
<Web3ReactManager> <Switch>
<Body> <Route exact strict path="/swap" component={Swap} />
<NavigationTabs /> <Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} />
<Switch> <Route exact strict path="/send" component={Send} />
<Route exact strict path="/swap" component={Swap} /> <Route exact strict path="/find" component={PoolFinder} />
<Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} /> <Route exact strict path="/pool" component={Pool} />
<Route exact strict path="/send" component={Send} /> <Route exact strict path="/create" component={CreatePool} />
<Route exact strict path="/find" component={Find} /> <Route exact strict path="/add/:tokens" component={AddLiquidity} />
<Route exact strict path="/create" component={Create} /> <Route exact strict path="/remove/:tokens" component={RemoveLiquidity} />
<Route exact strict path="/pool" component={Pool} /> <Route component={RedirectPathToSwapOnly} />
<Route </Switch>
exact </Web3ReactManager>
strict <Footer />
path={'/add/:tokens'} </BodyWrapper>
component={({ match }) => { <BackgroundGradient />
const tokens = match.params.tokens.split('-') </AppWrapper>
const t0 = </BrowserRouter>
tokens?.[0] === 'ETH' ? 'ETH' : isAddress(tokens?.[0]) ? isAddress(tokens[0]) : undefined <div id="popover-container" />
const t1 = </Suspense>
tokens?.[1] === 'ETH' ? 'ETH' : isAddress(tokens?.[1]) ? isAddress(tokens[1]) : undefined
if (t0 && t1) {
return <Add token0={t0} token1={t1} />
} else {
return <Redirect to="/pool" />
}
}}
/>
<Route
exact
strict
path={'/remove/:tokens'}
component={({ match }) => {
const tokens = match.params.tokens.split('-')
const t0 =
tokens?.[0] === 'ETH' ? 'ETH' : isAddress(tokens?.[0]) ? isAddress(tokens[0]) : undefined
const t1 =
tokens?.[1] === 'ETH' ? 'ETH' : isAddress(tokens?.[1]) ? isAddress(tokens[1]) : undefined
if (t0 && t1) {
return <Remove token0={t0} token1={t1} />
} else {
return <Redirect to="/pool" />
}
}}
/>
<Route component={RedirectPathToSwapOnly} />
</Switch>
</Body>
</Web3ReactManager>
<Footer />
</BodyWrapper>
<StyledRed />
</AppWrapper>
</BrowserRouter>
<div id="popover-container" />
</Suspense>
</>
) )
} }
import React from 'react'
import styled from 'styled-components'
import NavigationTabs from '../components/NavigationTabs'
export const Body = styled.div`
max-width: 420px;
width: 100%;
/* min-height: 340px; */
background: ${({ theme }) => theme.bg1};
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: 30px;
box-sizing: border-box;
padding: 1rem;
position: relative;
margin-bottom: 10rem;
`
/**
* The styled container element that wraps the content of most pages and the tabs.
*/
export default function AppBody({ children }: { children: React.ReactNode }) {
return (
<Body>
<NavigationTabs />
<>{children}</>
</Body>
)
}
import React, { useState, useEffect } from 'react'
import { RouteComponentProps, Redirect } from 'react-router-dom'
import { Token, WETH } from '@uniswap/sdk'
import AppBody from '../AppBody'
import Row, { AutoRow } from '../../components/Row'
import TokenLogo from '../../components/TokenLogo'
import SearchModal from '../../components/SearchModal'
import { Text } from 'rebass'
import { Plus } from 'react-feather'
import { TYPE, Link } from '../../theme'
import { AutoColumn, ColumnCenter } from '../../components/Column'
import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../../components/Button'
import { useToken } from '../../hooks/Tokens'
import { useActiveWeb3React } from '../../hooks'
import { usePair } from '../../data/Reserves'
enum Fields {
TOKEN0 = 0,
TOKEN1 = 1
}
enum STEP {
SELECT_TOKENS = 'SELECT_TOKENS', // choose input and output tokens
READY_TO_CREATE = 'READY_TO_CREATE', // enable 'create' button
SHOW_CREATE_PAGE = 'SHOW_CREATE_PAGE' // show create page
}
export default function CreatePool({ history, location }: RouteComponentProps) {
const { chainId } = useActiveWeb3React()
const [showSearch, setShowSearch] = useState<boolean>(false)
const [activeField, setActiveField] = useState<number>(Fields.TOKEN0)
const [token0Address, setToken0Address] = useState<string>(WETH[chainId].address)
const [token1Address, setToken1Address] = useState<string>()
const token0: Token = useToken(token0Address)
const token1: Token = useToken(token1Address)
const [step, setStep] = useState<string>(STEP.SELECT_TOKENS)
const pair = usePair(token0, token1)
// if both tokens selected but pair doesnt exist, enable button to create pair
useEffect(() => {
if (token0Address && token1Address && pair === null) {
setStep(STEP.READY_TO_CREATE)
}
}, [pair, token0Address, token1Address])
// if theyve clicked create, show add liquidity page
if (step === STEP.SHOW_CREATE_PAGE) {
return <Redirect to={{ ...location, pathname: `/add/${token0Address}-${token1Address}` }} push={true} />
}
return (
<AppBody>
<AutoColumn gap="20px">
<AutoColumn gap="24px">
{!token0Address ? (
<ButtonDropwdown
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN0)
}}
>
<Text fontSize={20}>Select first token</Text>
</ButtonDropwdown>
) : (
<ButtonDropwdownLight
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN0)
}}
>
<Row align="flex-end">
<TokenLogo address={token0Address} />
<Text fontWeight={500} fontSize={20} marginLeft={'12px'}>
{token0?.symbol}{' '}
</Text>
<TYPE.darkGray fontWeight={500} fontSize={16} marginLeft={'8px'}>
{token0?.address === 'ETH' && '(default)'}
</TYPE.darkGray>
</Row>
</ButtonDropwdownLight>
)}
<ColumnCenter>
<Plus size="16" color="#888D9B" />
</ColumnCenter>
{!token1Address ? (
<ButtonDropwdown
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN1)
}}
disabled={step !== STEP.SELECT_TOKENS}
>
<Text fontSize={20}>Select second token</Text>
</ButtonDropwdown>
) : (
<ButtonDropwdownLight
onClick={() => {
setShowSearch(true)
setActiveField(Fields.TOKEN1)
}}
>
<Row>
<TokenLogo address={token1Address} />
<Text fontWeight={500} fontSize={20} marginLeft={'12px'}>
{token1?.symbol}
</Text>
</Row>
</ButtonDropwdownLight>
)}
{pair ? ( // pair already exists - prompt to add liquidity to existing pool
<AutoRow padding="10px" justify="center">
<TYPE.body textAlign="center">
Pool already exists!
<Link onClick={() => history.push('/add/' + token0Address + '-' + token1Address)}> Join the pool.</Link>
</TYPE.body>
</AutoRow>
) : (
<ButtonPrimary disabled={step !== STEP.READY_TO_CREATE} onClick={() => setStep(STEP.SHOW_CREATE_PAGE)}>
<Text fontWeight={500} fontSize={20}>
Create Pool
</Text>
</ButtonPrimary>
)}
</AutoColumn>
<SearchModal
isOpen={showSearch}
filterType="tokens"
onTokenSelect={address => {
activeField === Fields.TOKEN0 ? setToken0Address(address) : setToken1Address(address)
}}
onDismiss={() => {
setShowSearch(false)
}}
hiddenToken={activeField === Fields.TOKEN0 ? token1Address : token0Address}
showCommonBases={activeField === Fields.TOKEN0}
/>
</AutoColumn>
</AppBody>
)
}
import React, { useState, useContext } from 'react' import React, { useState, useContext } from 'react'
import styled, { ThemeContext } from 'styled-components' import styled, { ThemeContext } from 'styled-components'
import { JSBI, Pair } from '@uniswap/sdk' import { JSBI, Pair } from '@uniswap/sdk'
import { RouteComponentProps, withRouter } from 'react-router-dom' import { RouteComponentProps } from 'react-router-dom'
import Question from '../../components/Question' import Question from '../../components/Question'
import SearchModal from '../../components/SearchModal' import SearchModal from '../../components/SearchModal'
...@@ -17,6 +17,7 @@ import { AutoColumn, ColumnCenter } from '../../components/Column' ...@@ -17,6 +17,7 @@ import { AutoColumn, ColumnCenter } from '../../components/Column'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { usePair } from '../../data/Reserves' import { usePair } from '../../data/Reserves'
import { useAllDummyPairs } from '../../state/user/hooks' import { useAllDummyPairs } from '../../state/user/hooks'
import AppBody from '../AppBody'
const Positions = styled.div` const Positions = styled.div`
position: relative; position: relative;
...@@ -34,7 +35,7 @@ function PositionCardWrapper({ dummyPair }: { dummyPair: Pair }) { ...@@ -34,7 +35,7 @@ function PositionCardWrapper({ dummyPair }: { dummyPair: Pair }) {
return <PositionCard pair={pair} /> return <PositionCard pair={pair} />
} }
function Supply({ history }: RouteComponentProps) { export default function Pool({ history }: RouteComponentProps) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const [showPoolSearch, setShowPoolSearch] = useState(false) const [showPoolSearch, setShowPoolSearch] = useState(false)
...@@ -58,59 +59,60 @@ function Supply({ history }: RouteComponentProps) { ...@@ -58,59 +59,60 @@ function Supply({ history }: RouteComponentProps) {
}) })
return ( return (
<AutoColumn gap="lg" justify="center"> <AppBody>
<ButtonPrimary <AutoColumn gap="lg" justify="center">
id="join-pool-button" <ButtonPrimary
padding="16px" id="join-pool-button"
onClick={() => { padding="16px"
setShowPoolSearch(true) onClick={() => {
}} setShowPoolSearch(true)
> }}
<Text fontWeight={500} fontSize={20}> >
Join {filteredExchangeList?.length > 0 ? 'another' : 'a'} pool <Text fontWeight={500} fontSize={20}>
</Text> Join {filteredExchangeList?.length > 0 ? 'another' : 'a'} pool
</ButtonPrimary>
<Positions>
<AutoColumn gap="12px">
<RowBetween padding={'0 8px'}>
<Text color={theme.text1} fontWeight={500}>
Your Pooled Liquidity
</Text>
<Question text="When you add liquidity, you are given pool tokens that represent your share. If you don’t see a pool you joined in this list, try importing a pool below." />
</RowBetween>
{filteredExchangeList?.length === 0 && (
<LightCard
padding="40px
"
>
<TYPE.body color={theme.text3} textAlign="center">
No liquidity found.
</TYPE.body>
</LightCard>
)}
{filteredExchangeList}
<Text textAlign="center" fontSize={14} style={{ padding: '.5rem 0 .5rem 0' }}>
{filteredExchangeList?.length !== 0 ? `Don't see a pool you joined? ` : 'Already joined a pool? '}{' '}
<Link
id="import-pool-link"
onClick={() => {
history.push('/find')
}}
>
Import it.
</Link>
</Text> </Text>
</AutoColumn> </ButtonPrimary>
<FixedBottom> <Positions>
<ColumnCenter> <AutoColumn gap="12px">
<ButtonSecondary width="136px" padding="8px" borderRadius="10px" onClick={() => history.push('/create')}> <RowBetween padding={'0 8px'}>
+ Create Pool <Text color={theme.text1} fontWeight={500}>
</ButtonSecondary> Your Pooled Liquidity
</ColumnCenter> </Text>
</FixedBottom> <Question text="When you add liquidity, you are given pool tokens that represent your share. If you don’t see a pool you joined in this list, try importing a pool below." />
</Positions> </RowBetween>
<SearchModal isOpen={showPoolSearch} onDismiss={() => setShowPoolSearch(false)} /> {filteredExchangeList?.length === 0 && (
</AutoColumn> <LightCard
padding="40px
"
>
<TYPE.body color={theme.text3} textAlign="center">
No liquidity found.
</TYPE.body>
</LightCard>
)}
{filteredExchangeList}
<Text textAlign="center" fontSize={14} style={{ padding: '.5rem 0 .5rem 0' }}>
{filteredExchangeList?.length !== 0 ? `Don't see a pool you joined? ` : 'Already joined a pool? '}{' '}
<Link
id="import-pool-link"
onClick={() => {
history.push('/find')
}}
>
Import it.
</Link>
</Text>
</AutoColumn>
<FixedBottom>
<ColumnCenter>
<ButtonSecondary width="136px" padding="8px" borderRadius="10px" onClick={() => history.push('/create')}>
+ Create Pool
</ButtonSecondary>
</ColumnCenter>
</FixedBottom>
</Positions>
<SearchModal isOpen={showPoolSearch} onDismiss={() => setShowPoolSearch(false)} />
</AutoColumn>
</AppBody>
) )
} }
export default withRouter(Supply)
import React, { useState, useEffect } from 'react' import { JSBI, Pair, Token, TokenAmount } from '@uniswap/sdk'
import { RouteComponentProps, withRouter } from 'react-router-dom' import React, { useEffect, useState } from 'react'
import { TokenAmount, JSBI, Token, Pair } from '@uniswap/sdk'
import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
import Row from '../Row'
import TokenLogo from '../TokenLogo'
import SearchModal from '../SearchModal'
import PositionCard from '../PositionCard'
import { Link } from '../../theme'
import { Text } from 'rebass'
import { Plus } from 'react-feather' import { Plus } from 'react-feather'
import { LightCard } from '../Card' import { RouteComponentProps } from 'react-router-dom'
import { AutoColumn, ColumnCenter } from '../Column' import { Text } from 'rebass'
import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../Button' import { ButtonDropwdown, ButtonDropwdownLight, ButtonPrimary } from '../../components/Button'
import { LightCard } from '../../components/Card'
import { useToken } from '../../hooks/Tokens' import { AutoColumn, ColumnCenter } from '../../components/Column'
import PositionCard from '../../components/PositionCard'
import Row from '../../components/Row'
import SearchModal from '../../components/SearchModal'
import TokenLogo from '../../components/TokenLogo'
import { usePair } from '../../data/Reserves'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useToken } from '../../hooks/Tokens'
import { usePairAdder } from '../../state/user/hooks' import { usePairAdder } from '../../state/user/hooks'
import { usePair } from '../../data/Reserves' import { useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
import { Link } from '../../theme'
import AppBody from '../AppBody'
const Fields = { enum Fields {
TOKEN0: 0, TOKEN0 = 0,
TOKEN1: 1 TOKEN1 = 1
} }
function PoolFinder({ history }: RouteComponentProps) { export default function PoolFinder({ history }: RouteComponentProps) {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const [showSearch, setShowSearch] = useState<boolean>(false) const [showSearch, setShowSearch] = useState<boolean>(false)
const [activeField, setActiveField] = useState<number>(Fields.TOKEN0) const [activeField, setActiveField] = useState<number>(Fields.TOKEN0)
...@@ -51,7 +50,7 @@ function PoolFinder({ history }: RouteComponentProps) { ...@@ -51,7 +50,7 @@ function PoolFinder({ history }: RouteComponentProps) {
const allowImport: boolean = position && JSBI.greaterThan(position.raw, JSBI.BigInt(0)) const allowImport: boolean = position && JSBI.greaterThan(position.raw, JSBI.BigInt(0))
return ( return (
<> <AppBody>
<AutoColumn gap="md"> <AutoColumn gap="md">
{!token0Address ? ( {!token0Address ? (
<ButtonDropwdown <ButtonDropwdown
...@@ -168,8 +167,6 @@ function PoolFinder({ history }: RouteComponentProps) { ...@@ -168,8 +167,6 @@ function PoolFinder({ history }: RouteComponentProps) {
}} }}
hiddenToken={activeField === Fields.TOKEN0 ? token1Address : token0Address} hiddenToken={activeField === Fields.TOKEN0 ? token1Address : token0Address}
/> />
</> </AppBody>
) )
} }
export default withRouter(PoolFinder)
This diff is collapsed.
This diff is collapsed.
// Redirects to swap but only replace the pathname
import React from 'react'
import { Redirect, RouteComponentProps } from 'react-router-dom'
export function RedirectPathToSwapOnly({ location }: RouteComponentProps) {
return <Redirect to={{ ...location, pathname: '/swap' }} />
}
// Redirects from the /swap/:outputCurrency path to the /swap?outputCurrency=:outputCurrency format
export function RedirectToSwap(props: RouteComponentProps<{ outputCurrency: string }>) {
const {
location: { search },
match: {
params: { outputCurrency }
}
} = props
return (
<Redirect
to={{
...props.location,
pathname: '/swap',
search:
search && search.length > 1
? `${search}&outputCurrency=${outputCurrency}`
: `?outputCurrency=${outputCurrency}`
}}
/>
)
}
...@@ -22,3 +22,4 @@ export const addSerializedPair = createAction<{ serializedPair: SerializedPair } ...@@ -22,3 +22,4 @@ export const addSerializedPair = createAction<{ serializedPair: SerializedPair }
export const removeSerializedPair = createAction<{ chainId: number; tokenAAddress: string; tokenBAddress: string }>( export const removeSerializedPair = createAction<{ chainId: number; tokenAAddress: string; tokenBAddress: string }>(
'removeSerializedPair' 'removeSerializedPair'
) )
export const dismissTokenWarning = createAction<{ chainId: number; tokenAddress: string }>('dismissTokenWarning')
...@@ -8,6 +8,7 @@ import { AppDispatch, AppState } from '../index' ...@@ -8,6 +8,7 @@ import { AppDispatch, AppState } from '../index'
import { import {
addSerializedPair, addSerializedPair,
addSerializedToken, addSerializedToken,
dismissTokenWarning,
removeSerializedToken, removeSerializedToken,
SerializedPair, SerializedPair,
SerializedToken, SerializedToken,
...@@ -132,6 +133,28 @@ export function usePairAdder(): (pair: Pair) => void { ...@@ -132,6 +133,28 @@ export function usePairAdder(): (pair: Pair) => void {
) )
} }
/**
* Returns whether a token warning has been dismissed and a callback to dismiss it,
* iff it has not already been dismissed and is a valid token.
*/
export function useTokenWarningDismissal(chainId?: number, token?: Token): [boolean, null | (() => void)] {
const dismissalState = useSelector<AppState, AppState['user']['dismissedTokenWarnings']>(
state => state.user.dismissedTokenWarnings
)
const dispatch = useDispatch<AppDispatch>()
return useMemo(() => {
if (!chainId || !token) return [false, null]
const dismissed: boolean = dismissalState?.[chainId]?.[token.address] === true
const callback = dismissed ? null : () => dispatch(dismissTokenWarning({ chainId, tokenAddress: token.address }))
return [dismissed, callback]
}, [chainId, token, dismissalState, dispatch])
}
const bases = [ const bases = [
...Object.values(WETH), ...Object.values(WETH),
new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'), new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'),
......
...@@ -2,6 +2,7 @@ import { createReducer } from '@reduxjs/toolkit' ...@@ -2,6 +2,7 @@ import { createReducer } from '@reduxjs/toolkit'
import { import {
addSerializedPair, addSerializedPair,
addSerializedToken, addSerializedToken,
dismissTokenWarning,
removeSerializedPair, removeSerializedPair,
removeSerializedToken, removeSerializedToken,
SerializedPair, SerializedPair,
...@@ -25,6 +26,13 @@ interface UserState { ...@@ -25,6 +26,13 @@ interface UserState {
} }
} }
// the token warnings that the user has dismissed
dismissedTokenWarnings?: {
[chainId: number]: {
[tokenAddress: string]: true
}
}
pairs: { pairs: {
[chainId: number]: { [chainId: number]: {
// keyed by token0Address:token1Address // keyed by token0Address:token1Address
...@@ -79,6 +87,11 @@ export default createReducer(initialState, builder => ...@@ -79,6 +87,11 @@ export default createReducer(initialState, builder =>
delete state.tokens[chainId][address] delete state.tokens[chainId][address]
state.timestamp = currentTimestamp() state.timestamp = currentTimestamp()
}) })
.addCase(dismissTokenWarning, (state, { payload: { chainId, tokenAddress } }) => {
state.dismissedTokenWarnings = state.dismissedTokenWarnings ?? {}
state.dismissedTokenWarnings[chainId] = state.dismissedTokenWarnings[chainId] ?? {}
state.dismissedTokenWarnings[chainId][tokenAddress] = true
})
.addCase(addSerializedPair, (state, { payload: { serializedPair } }) => { .addCase(addSerializedPair, (state, { payload: { serializedPair } }) => {
if ( if (
serializedPair.token0.chainId === serializedPair.token1.chainId && serializedPair.token0.chainId === serializedPair.token1.chainId &&
......
import React from 'react'
/**
* Helper type that returns the props type of another component, excluding
* any of the keys passed as the optional second argument.
*/
type PropsOfExcluding<TComponent, TExcludingProps = void> = TComponent extends React.ComponentType<infer P>
? TExcludingProps extends string | number | symbol
? Omit<P, TExcludingProps>
: P
: unknown
export default PropsOfExcluding
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment