Add Custom url parameters (#423)

* query params for input and output currencies

* add slippage option

* add slippage cusytom param

* updated for sender address

* add field and amount support

* update params for pool page

* finish basic url support

* update app format

* update error checking to top level

* update for all pages

* fix build

* param updates

* fix slippage to basis points, update theme text, refactor to minimize lookups

* fix code styles

* update theme logic, remove extra setting, update rounding

* remove eslint comment errors

* remove logs, ignore lock

* remove lock
This commit is contained in:
Ian Lapham 2019-09-17 18:47:32 -04:00 committed by Noah Zinsmeister
parent 61d6556a0d
commit 4f566ab0c2
18 changed files with 302 additions and 104 deletions

2
.gitignore vendored

@ -27,3 +27,5 @@ notes.txt
.idea/ .idea/
.vscode/ .vscode/
package-lock.json

@ -10,6 +10,7 @@
"@uniswap/sdk": "^1.0.0-beta.4", "@uniswap/sdk": "^1.0.0-beta.4",
"copy-to-clipboard": "^3.2.0", "copy-to-clipboard": "^3.2.0",
"escape-string-regexp": "^2.0.0", "escape-string-regexp": "^2.0.0",
"history": "^4.9.0",
"ethers": "^4.0.36", "ethers": "^4.0.36",
"i18next": "^15.0.9", "i18next": "^15.0.9",
"i18next-browser-languagedetector": "^3.0.1", "i18next-browser-languagedetector": "^3.0.1",

@ -75,7 +75,8 @@ export default function AddressInputPanel({ title, initialInput = '', onChange =
const { library } = useWeb3Context() const { library } = useWeb3Context()
const [input, setInput] = useState(initialInput) const [input, setInput] = useState(initialInput.address ? initialInput.address : '')
const debouncedInput = useDebounce(input, 150) const debouncedInput = useDebounce(input, 150)
const [data, setData] = useState({ address: undefined, name: undefined }) const [data, setData] = useState({ address: undefined, name: undefined })

@ -336,9 +336,9 @@ export default function CurrencyInputPanel({
<Input <Input
type="number" type="number"
min="0" min="0"
step="0.000000000000000001"
error={!!errorMessage} error={!!errorMessage}
placeholder="0.0" placeholder="0.0"
step="0.000000000000000001"
onChange={e => onValueChange(e.target.value)} onChange={e => onValueChange(e.target.value)}
onKeyPress={e => { onKeyPress={e => {
const charCode = e.which ? e.which : e.keyCode const charCode = e.which ? e.which : e.keyCode

@ -1,5 +1,6 @@
import React, { useState, useReducer, useEffect } from 'react' import React, { useState, useReducer, useEffect } from 'react'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { createBrowserHistory } from 'history'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useWeb3Context } from 'web3-react' import { useWeb3Context } from 'web3-react'
@ -119,13 +120,17 @@ function calculateEtherTokenInputFromOutput(outputAmount, inputReserve, outputRe
return numerator.div(denominator).add(ethers.constants.One) return numerator.div(denominator).add(ethers.constants.One)
} }
function getInitialSwapState(outputCurrency) { function getInitialSwapState(state) {
return { return {
independentValue: '', // this is a user input independentValue: state.exactFieldURL && state.exactAmountURL ? state.exactAmountURL : '', // this is a user input
dependentValue: '', // this is a calculated number dependentValue: '', // this is a calculated number
independentField: INPUT, independentField: state.exactFieldURL === 'output' ? OUTPUT : INPUT,
inputCurrency: 'ETH', inputCurrency: state.inputCurrencyURL ? state.inputCurrencyURL : 'ETH',
outputCurrency: outputCurrency ? outputCurrency : '' outputCurrency: state.outputCurrencyURL
? state.outputCurrencyURL
: state.initialCurrency
? state.initialCurrency
: ''
} }
} }
@ -235,14 +240,32 @@ function getMarketRate(
} }
} }
export default function ExchangePage({ initialCurrency, sending }) { export default function ExchangePage({ initialCurrency, sending = false, params }) {
const { t } = useTranslation() const { t } = useTranslation()
const { account } = useWeb3Context() const { account } = useWeb3Context()
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
const [rawSlippage, setRawSlippage] = useState(ALLOWED_SLIPPAGE_DEFAULT) // check if URL specifies valid slippage, if so use as default
const [rawTokenSlippage, setRawTokenSlippage] = useState(TOKEN_ALLOWED_SLIPPAGE_DEFAULT) const initialSlippage = (token = false) => {
let slippage = Number.parseInt(params.slippage)
if (!isNaN(slippage) && (slippage === 0 || slippage >= 1)) {
return slippage // round to match custom input availability
}
// check for token <-> token slippage option
return token ? TOKEN_ALLOWED_SLIPPAGE_DEFAULT : ALLOWED_SLIPPAGE_DEFAULT
}
// check URL params for recipient, only on send page
const initialRecipient = () => {
if (sending && params.recipient) {
return params.recipient
}
return ''
}
const [rawSlippage, setRawSlippage] = useState(() => initialSlippage())
const [rawTokenSlippage, setRawTokenSlippage] = useState(() => initialSlippage(true))
const allowedSlippageBig = ethers.utils.bigNumberify(rawSlippage) const allowedSlippageBig = ethers.utils.bigNumberify(rawSlippage)
const tokenAllowedSlippageBig = ethers.utils.bigNumberify(rawTokenSlippage) const tokenAllowedSlippageBig = ethers.utils.bigNumberify(rawTokenSlippage)
@ -253,11 +276,21 @@ export default function ExchangePage({ initialCurrency, sending }) {
}, []) }, [])
// core swap state // core swap state
const [swapState, dispatchSwapState] = useReducer(swapStateReducer, initialCurrency, getInitialSwapState) const [swapState, dispatchSwapState] = useReducer(
swapStateReducer,
{
initialCurrency: initialCurrency,
inputCurrencyURL: params.inputCurrency,
outputCurrencyURL: params.outputCurrency,
exactFieldURL: params.exactField,
exactAmountURL: params.exactAmount
},
getInitialSwapState
)
const { independentValue, dependentValue, independentField, inputCurrency, outputCurrency } = swapState const { independentValue, dependentValue, independentField, inputCurrency, outputCurrency } = swapState
const [recipient, setRecipient] = useState({ address: '', name: '' }) const [recipient, setRecipient] = useState({ address: initialRecipient(), name: '' })
const [recipientError, setRecipientError] = useState() const [recipientError, setRecipientError] = useState()
// get swap type from the currency types // get swap type from the currency types
@ -468,6 +501,11 @@ export default function ExchangePage({ initialCurrency, sending }) {
t t
]) ])
useEffect(() => {
const history = createBrowserHistory()
history.push(window.location.pathname + '')
}, [])
const [inverted, setInverted] = useState(false) const [inverted, setInverted] = useState(false)
const exchangeRate = getExchangeRate(inputValueParsed, inputDecimals, outputValueParsed, outputDecimals) const exchangeRate = getExchangeRate(inputValueParsed, inputDecimals, outputValueParsed, outputDecimals)
const exchangeRateInverted = getExchangeRate(inputValueParsed, inputDecimals, outputValueParsed, outputDecimals, true) const exchangeRateInverted = getExchangeRate(inputValueParsed, inputDecimals, outputValueParsed, outputDecimals, true)
@ -655,7 +693,7 @@ export default function ExchangePage({ initialCurrency, sending }) {
<DownArrow active={isValid} alt="arrow" /> <DownArrow active={isValid} alt="arrow" />
</DownArrowBackground> </DownArrowBackground>
</OversizedPanel> </OversizedPanel>
<AddressInputPanel onChange={setRecipient} onError={setRecipientError} /> <AddressInputPanel onChange={setRecipient} onError={setRecipientError} initialInput={recipient} />
</> </>
) : ( ) : (
'' ''

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react' import React, { useState, useEffect, useRef, useCallback } from 'react'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled, { css, keyframes } from 'styled-components' import styled, { css, keyframes } from 'styled-components'
@ -502,40 +502,94 @@ export default function TransactionDetails(props) {
checkBounds(debouncedInput) checkBounds(debouncedInput)
} }
// destructure props for to limit effect callbacks
const setRawSlippage = props.setRawSlippage
const setRawTokenSlippage = props.setRawTokenSlippage
const setcustomSlippageError = props.setcustomSlippageError
const updateSlippage = useCallback(
newSlippage => {
// round to 2 decimals to prevent ethers error
let numParsed = parseInt(newSlippage * 100)
// set both slippage values in parents
setRawSlippage(numParsed)
setRawTokenSlippage(numParsed)
},
[setRawSlippage, setRawTokenSlippage]
)
// used for slippage presets // used for slippage presets
const setFromFixed = (index, slippage) => { const setFromFixed = useCallback(
(index, slippage) => {
// update slippage in parent, reset errors and input state // update slippage in parent, reset errors and input state
updateSlippage(slippage) updateSlippage(slippage)
setWarningType(WARNING_TYPE.none) setWarningType(WARNING_TYPE.none)
setActiveIndex(index) setActiveIndex(index)
props.setcustomSlippageError('valid`') setcustomSlippageError('valid`')
} },
[setcustomSlippageError, updateSlippage]
)
const checkBounds = slippageValue => { /**
* @todo
* Breaks without useState here, able to
* break input parsing if typing is faster than
* debounce time
*/
const [initialSlippage] = useState(props.rawSlippage)
useEffect(() => {
switch (Number.parseInt(initialSlippage)) {
case 10:
setFromFixed(1, 0.1)
break
case 50:
setFromFixed(2, 0.5)
break
case 100:
setFromFixed(3, 1)
break
default:
// restrict to 2 decimal places
let acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
// if its within accepted decimal limit, update the input state
if (acceptableValues.some(val => val.test(initialSlippage / 100))) {
setUserInput(initialSlippage / 100)
setActiveIndex(4)
}
}
}, [initialSlippage, setFromFixed])
const checkBounds = useCallback(
slippageValue => {
setWarningType(WARNING_TYPE.none) setWarningType(WARNING_TYPE.none)
props.setcustomSlippageError('valid') setcustomSlippageError('valid')
if (slippageValue === '' || slippageValue === '.') { if (slippageValue === '' || slippageValue === '.') {
props.setcustomSlippageError('invalid') setcustomSlippageError('invalid')
return setWarningType(WARNING_TYPE.emptyInput) return setWarningType(WARNING_TYPE.emptyInput)
} }
// check bounds and set errors // check bounds and set errors
if (Number(slippageValue) < 0 || Number(slippageValue) > 50) { if (Number(slippageValue) < 0 || Number(slippageValue) > 50) {
props.setcustomSlippageError('invalid') setcustomSlippageError('invalid')
return setWarningType(WARNING_TYPE.invalidEntryBound) return setWarningType(WARNING_TYPE.invalidEntryBound)
} }
if (Number(slippageValue) >= 0 && Number(slippageValue) < 0.1) { if (Number(slippageValue) >= 0 && Number(slippageValue) < 0.1) {
props.setcustomSlippageError('valid') setcustomSlippageError('valid')
setWarningType(WARNING_TYPE.riskyEntryLow) setWarningType(WARNING_TYPE.riskyEntryLow)
} }
if (Number(slippageValue) > 5) { if (Number(slippageValue) > 5) {
props.setcustomSlippageError('warning') setcustomSlippageError('warning')
setWarningType(WARNING_TYPE.riskyEntryHigh) setWarningType(WARNING_TYPE.riskyEntryHigh)
} }
//update the actual slippage value in parent //update the actual slippage value in parent
updateSlippage(Number(slippageValue)) updateSlippage(Number(slippageValue))
} },
[setcustomSlippageError, updateSlippage]
)
// check that the theyve entered number and correct decimal // check that the theyve entered number and correct decimal
const parseInput = e => { const parseInput = e => {
@ -549,15 +603,6 @@ export default function TransactionDetails(props) {
} }
} }
const updateSlippage = newSlippage => {
// round to 2 decimals to prevent ethers error
let numParsed = parseInt(newSlippage * 100)
// set both slippage values in parents
props.setRawSlippage(numParsed)
props.setRawTokenSlippage(numParsed)
}
const b = text => <Bold>{text}</Bold> const b = text => <Bold>{text}</Bold>
const renderTransactionDetails = () => { const renderTransactionDetails = () => {

@ -4,3 +4,8 @@ export const FACTORY_ADDRESSES = {
4: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36', 4: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36',
42: '0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30' 42: '0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30'
} }
export const SUPPORTED_THEMES = {
DARK: 'DARK',
LIGHT: 'LIGHT'
}

@ -68,14 +68,12 @@ export function useFetchAllBalances() {
Object.keys(allTokens).map(async k => { Object.keys(allTokens).map(async k => {
let balance = null let balance = null
let ethRate = null let ethRate = null
if (isAddress(k) || k === 'ETH') { if (isAddress(k) || k === 'ETH') {
if (k === 'ETH') { if (k === 'ETH') {
balance = await getEtherBalance(account, library).catch(() => null) balance = await getEtherBalance(account, library).catch(() => null)
ethRate = ONE ethRate = ONE
} else { } else {
balance = await getTokenBalance(k, account, library).catch(() => null) balance = await getTokenBalance(k, account, library).catch(() => null)
// only get values for tokens with positive balances // only get values for tokens with positive balances
if (!!balance && balance.gt(ZERO)) { if (!!balance && balance.gt(ZERO)) {
const tokenReserves = await getTokenReserves(k, library).catch(() => null) const tokenReserves = await getTokenReserves(k, library).catch(() => null)

@ -95,11 +95,14 @@ export function useBetaMessageManager() {
export function useDarkModeManager() { export function useDarkModeManager() {
const [state, { updateKey }] = useLocalStorageContext() const [state, { updateKey }] = useLocalStorageContext()
const isDarkMode = state[DARK_MODE] let isDarkMode = state[DARK_MODE]
const toggleDarkMode = useCallback(() => { const toggleDarkMode = useCallback(
updateKey(DARK_MODE, !isDarkMode) value => {
}, [updateKey, isDarkMode]) updateKey(DARK_MODE, value === false || value === true ? value : !isDarkMode)
},
[updateKey, isDarkMode]
)
return [state[DARK_MODE], toggleDarkMode] return [state[DARK_MODE], toggleDarkMode]
} }

@ -7,7 +7,7 @@ import Header from '../components/Header'
import Footer from '../components/Footer' import Footer from '../components/Footer'
import NavigationTabs from '../components/NavigationTabs' import NavigationTabs from '../components/NavigationTabs'
import { isAddress } from '../utils' import { isAddress, getAllQueryParams } from '../utils'
const Swap = lazy(() => import('./Swap')) const Swap = lazy(() => import('./Swap'))
const Send = lazy(() => import('./Send')) const Send = lazy(() => import('./Send'))
@ -48,6 +48,7 @@ const Body = styled.div`
` `
export default function App() { export default function App() {
const params = getAllQueryParams()
return ( return (
<> <>
<Suspense fallback={null}> <Suspense fallback={null}>
@ -63,27 +64,33 @@ export default function App() {
{/* this Suspense is for route code-splitting */} {/* this Suspense is for route code-splitting */}
<Suspense fallback={null}> <Suspense fallback={null}>
<Switch> <Switch>
<Route exact strict path="/swap" component={Swap} /> <Route exact strict path="/swap" component={() => <Swap params={params} />} />
<Route <Route
exact exact
strict strict
path="/swap/:tokenAddress?" path="/swap/:tokenAddress?"
render={({ match }) => { render={({ match, location }) => {
if (isAddress(match.params.tokenAddress)) { if (isAddress(match.params.tokenAddress)) {
return <Swap initialCurrency={isAddress(match.params.tokenAddress)} /> return (
<Swap
location={location}
initialCurrency={isAddress(match.params.tokenAddress)}
params={params}
/>
)
} else { } else {
return <Redirect to={{ pathname: '/swap' }} /> return <Redirect to={{ pathname: '/swap' }} />
} }
}} }}
/> />
<Route exact strict path="/send" component={Send} /> <Route exact strict path="/send" component={() => <Send params={params} />} />
<Route <Route
exact exact
strict strict
path="/send/:tokenAddress?" path="/send/:tokenAddress?"
render={({ match }) => { render={({ match, location }) => {
if (isAddress(match.params.tokenAddress)) { if (isAddress(match.params.tokenAddress)) {
return <Send initialCurrency={isAddress(match.params.tokenAddress)} /> return <Send initialCurrency={isAddress(match.params.tokenAddress)} params={params} />
} else { } else {
return <Redirect to={{ pathname: '/send' }} /> return <Redirect to={{ pathname: '/send' }} />
} }
@ -96,7 +103,7 @@ export default function App() {
'/create-exchange', '/create-exchange',
'/create-exchange/:tokenAddress?' '/create-exchange/:tokenAddress?'
]} ]}
component={Pool} component={() => <Pool params={params} />}
/> />
<Redirect to="/swap" /> <Redirect to="/swap" />
</Switch> </Switch>

@ -1,6 +1,7 @@
import React, { useReducer, useState, useCallback, useEffect, useMemo } from 'react' import React, { useReducer, useState, useCallback, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useWeb3Context } from 'web3-react' import { useWeb3Context } from 'web3-react'
import { createBrowserHistory } from 'history'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import styled from 'styled-components' import styled from 'styled-components'
@ -118,11 +119,13 @@ function calculateSlippageBounds(value) {
} }
} }
const initialAddLiquidityState = { function initialAddLiquidityState(state) {
inputValue: '', return {
outputValue: '', inputValue: state.ethAmountURL ? state.ethAmountURL : '',
lastEditedField: INPUT, outputValue: state.tokenAmountURL && !state.ethAmountURL ? state.tokenAmountURL : '',
outputCurrency: '' lastEditedField: state.tokenAmountURL && state.ethAmountURL === '' ? OUTPUT : INPUT,
outputCurrency: state.tokenURL ? state.tokenURL : ''
}
} }
function addLiquidityStateReducer(state, action) { function addLiquidityStateReducer(state, action) {
@ -153,7 +156,7 @@ function addLiquidityStateReducer(state, action) {
} }
} }
default: { default: {
return initialAddLiquidityState return initialAddLiquidityState()
} }
} }
} }
@ -189,11 +192,21 @@ function getMarketRate(reserveETH, reserveToken, decimals, invert = false) {
return getExchangeRate(reserveETH, 18, reserveToken, decimals, invert) return getExchangeRate(reserveETH, 18, reserveToken, decimals, invert)
} }
export default function AddLiquidity() { export default function AddLiquidity({ params }) {
const { t } = useTranslation() const { t } = useTranslation()
const { library, active, account } = useWeb3Context() const { library, active, account } = useWeb3Context()
const [addLiquidityState, dispatchAddLiquidityState] = useReducer(addLiquidityStateReducer, initialAddLiquidityState) // clear url of query
useEffect(() => {
const history = createBrowserHistory()
history.push(window.location.pathname + '')
}, [])
const [addLiquidityState, dispatchAddLiquidityState] = useReducer(
addLiquidityStateReducer,
{ ethAmountURL: params.ethAmount, tokenAmountURL: params.tokenAmount, tokenURL: params.token },
initialAddLiquidityState
)
const { inputValue, outputValue, lastEditedField, outputCurrency } = addLiquidityState const { inputValue, outputValue, lastEditedField, outputCurrency } = addLiquidityState
const inputCurrency = 'ETH' const inputCurrency = 'ETH'

@ -1,11 +1,11 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { withRouter } from 'react-router' import { withRouter } from 'react-router'
import { useWeb3Context } from 'web3-react' import { useWeb3Context } from 'web3-react'
import { createBrowserHistory } from 'history'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import styled from 'styled-components' import styled from 'styled-components'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { Button } from '../../theme' import { Button } from '../../theme'
import AddressInputPanel from '../../components/AddressInputPanel' import AddressInputPanel from '../../components/AddressInputPanel'
import OversizedPanel from '../../components/OversizedPanel' import OversizedPanel from '../../components/OversizedPanel'
@ -54,13 +54,13 @@ const Flex = styled.div`
} }
` `
function CreateExchange({ history, location }) { function CreateExchange({ location, params }) {
const { t } = useTranslation() const { t } = useTranslation()
const { account } = useWeb3Context() const { account } = useWeb3Context()
const factory = useFactoryContract() const factory = useFactoryContract()
const [tokenAddress, setTokenAddress] = useState({ const [tokenAddress, setTokenAddress] = useState({
address: '', address: params.tokenAddress ? params.tokenAddress : '',
name: '' name: ''
}) })
const [tokenAddressError, setTokenAddressError] = useState() const [tokenAddressError, setTokenAddressError] = useState()
@ -68,12 +68,11 @@ function CreateExchange({ history, location }) {
const { name, symbol, decimals, exchangeAddress } = useTokenDetails(tokenAddress.address) const { name, symbol, decimals, exchangeAddress } = useTokenDetails(tokenAddress.address)
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
// clear location state, if it exists // clear url of query
useEffect(() => { useEffect(() => {
if (location.state) { const history = createBrowserHistory()
history.replace(location.pathname) history.push(window.location.pathname + '')
} }, [])
}, []) // eslint-disable-line react-hooks/exhaustive-deps
// validate everything // validate everything
const [errorMessage, setErrorMessage] = useState(!account && t('noWallet')) const [errorMessage, setErrorMessage] = useState(!account && t('noWallet'))
@ -118,7 +117,11 @@ function CreateExchange({ history, location }) {
<> <>
<AddressInputPanel <AddressInputPanel
title={t('tokenAddress')} title={t('tokenAddress')}
initialInput={(location.state && location.state.tokenAddress) || ''} initialInput={
params.tokenAddress
? { address: params.tokenAddress }
: { address: (location.state && location.state.tokenAddress) || '' }
}
onChange={setTokenAddress} onChange={setTokenAddress}
onError={setTokenAddressError} onError={setTokenAddressError}
/> />

@ -1,6 +1,7 @@
import React, { useState, useEffect, useCallback } from 'react' import React, { useState, useEffect, useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { createBrowserHistory } from 'history'
import { useWeb3Context } from 'web3-react' import { useWeb3Context } from 'web3-react'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import styled from 'styled-components' import styled from 'styled-components'
@ -141,14 +142,20 @@ function calculateSlippageBounds(value) {
} }
} }
export default function RemoveLiquidity() { export default function RemoveLiquidity({ params }) {
const { library, account, active } = useWeb3Context() const { library, account, active } = useWeb3Context()
const { t } = useTranslation() const { t } = useTranslation()
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
const [outputCurrency, setOutputCurrency] = useState('') // clear url of query
const [value, setValue] = useState('') useEffect(() => {
const history = createBrowserHistory()
history.push(window.location.pathname + '')
}, [])
const [outputCurrency, setOutputCurrency] = useState(params.poolTokenAddress)
const [value, setValue] = useState(params.poolTokenAmount ? params.poolTokenAmount : '')
const [inputError, setInputError] = useState() const [inputError, setInputError] = useState()
const [valueParsed, setValueParsed] = useState() const [valueParsed, setValueParsed] = useState()
// parse value // parse value

@ -1,27 +1,32 @@
import React, { Suspense, lazy, useEffect } from 'react' import React, { Suspense, lazy, useEffect } from 'react'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { Switch, Route, Redirect } from 'react-router-dom' import { Switch, Route, Redirect } from 'react-router-dom'
import ModeSelector from './ModeSelector' import ModeSelector from './ModeSelector'
const AddLiquidity = lazy(() => import('./AddLiquidity')) const AddLiquidity = lazy(() => import('./AddLiquidity'))
const RemoveLiquidity = lazy(() => import('./RemoveLiquidity')) const RemoveLiquidity = lazy(() => import('./RemoveLiquidity'))
const CreateExchange = lazy(() => import('./CreateExchange')) const CreateExchange = lazy(() => import('./CreateExchange'))
export default function Pool() { export default function Pool({ params }) {
useEffect(() => { useEffect(() => {
ReactGA.pageview(window.location.pathname + window.location.search) ReactGA.pageview(window.location.pathname + window.location.search)
}, []) }, [])
const AddLiquidityParams = () => <AddLiquidity params={params} />
const RemoveLiquidityParams = () => <RemoveLiquidity params={params} />
const CreateExchangeParams = () => <CreateExchange params={params} />
return ( return (
<> <>
<ModeSelector /> <ModeSelector />
{/* this Suspense is for route code-splitting */} {/* this Suspense is for route code-splitting */}
<Suspense fallback={null}> <Suspense fallback={null}>
<Switch> <Switch>
<Route exact strict path="/add-liquidity" component={AddLiquidity} /> <Route exact strict path="/add-liquidity" component={AddLiquidityParams} />
<Route exact strict path="/remove-liquidity" component={RemoveLiquidity} /> <Route exact strict path="/remove-liquidity" component={RemoveLiquidityParams} />
<Route exact strict path="/create-exchange" component={CreateExchange} /> <Route exact strict path="/create-exchange" component={CreateExchangeParams} />
<Route <Route
path="/create-exchange/:tokenAddress" path="/create-exchange/:tokenAddress"
render={({ match }) => { render={({ match }) => {

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import ExchangePage from '../../components/ExchangePage' import ExchangePage from '../../components/ExchangePage'
export default function Send({ initialCurrency }) { export default function Send({ initialCurrency, params }) {
return <ExchangePage initialCurrency={initialCurrency} sending={true} /> return <ExchangePage initialCurrency={initialCurrency} params={params} sending={true} />
} }

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import ExchangePage from '../../components/ExchangePage' import ExchangePage from '../../components/ExchangePage'
export default function Swap({ initialCurrency }) { export default function Swap({ initialCurrency, params }) {
return <ExchangePage initialCurrency={initialCurrency} /> return <ExchangePage initialCurrency={initialCurrency} params={params} />
} }

@ -1,5 +1,7 @@
import React from 'react' import React, { useEffect } from 'react'
import { ThemeProvider as StyledComponentsThemeProvider, createGlobalStyle, css } from 'styled-components' import { ThemeProvider as StyledComponentsThemeProvider, createGlobalStyle, css } from 'styled-components'
import { getQueryParam, checkSupportedTheme } from '../utils'
import { SUPPORTED_THEMES } from '../constants'
import { useDarkModeManager } from '../contexts/LocalStorage' import { useDarkModeManager } from '../contexts/LocalStorage'
export * from './components' export * from './components'
@ -32,6 +34,22 @@ const flexRowNoWrap = css`
const white = '#FFFFFF' const white = '#FFFFFF'
const black = '#000000' const black = '#000000'
export default function ThemeProvider({ children }) {
const [darkMode, toggleDarkMode] = useDarkModeManager()
const themeURL = checkSupportedTheme(getQueryParam(window.location, 'theme'))
const themeToRender = themeURL
? themeURL.toUpperCase() === SUPPORTED_THEMES.DARK
? true
: themeURL.toUpperCase() === SUPPORTED_THEMES.LIGHT
? false
: darkMode
: darkMode
useEffect(() => {
toggleDarkMode(themeToRender)
}, [toggleDarkMode, themeToRender])
return <StyledComponentsThemeProvider theme={theme(themeToRender)}>{children}</StyledComponentsThemeProvider>
}
const theme = darkMode => ({ const theme = darkMode => ({
white, white,
black, black,
@ -84,12 +102,6 @@ const theme = darkMode => ({
flexRowNoWrap flexRowNoWrap
}) })
export default function ThemeProvider({ children }) {
const [darkMode] = useDarkModeManager()
return <StyledComponentsThemeProvider theme={theme(darkMode)}>{children}</StyledComponentsThemeProvider>
}
export const GlobalStyle = createGlobalStyle` export const GlobalStyle = createGlobalStyle`
@import url('https://rsms.me/inter/inter.css'); @import url('https://rsms.me/inter/inter.css');
html { font-family: 'Inter', sans-serif; } html { font-family: 'Inter', sans-serif; }

@ -4,7 +4,7 @@ import FACTORY_ABI from '../constants/abis/factory'
import EXCHANGE_ABI from '../constants/abis/exchange' import EXCHANGE_ABI from '../constants/abis/exchange'
import ERC20_ABI from '../constants/abis/erc20' import ERC20_ABI from '../constants/abis/erc20'
import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32' import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32'
import { FACTORY_ADDRESSES } from '../constants' import { FACTORY_ADDRESSES, SUPPORTED_THEMES } from '../constants'
import { formatFixed } from '@uniswap/sdk' import { formatFixed } from '@uniswap/sdk'
import UncheckedJsonRpcSigner from './signer' import UncheckedJsonRpcSigner from './signer'
@ -33,6 +33,7 @@ const ETHERSCAN_PREFIXES = {
5: 'goerli.', 5: 'goerli.',
42: 'kovan.' 42: 'kovan.'
} }
export function getEtherscanLink(networkId, data, type) { export function getEtherscanLink(networkId, data, type) {
const prefix = `https://${ETHERSCAN_PREFIXES[networkId] || ETHERSCAN_PREFIXES[1]}etherscan.io` const prefix = `https://${ETHERSCAN_PREFIXES[networkId] || ETHERSCAN_PREFIXES[1]}etherscan.io`
@ -47,6 +48,63 @@ export function getEtherscanLink(networkId, data, type) {
} }
} }
export function getQueryParam(windowLocation, name) {
var q = windowLocation.search.match(new RegExp('[?&]' + name + '=([^&#?]*)'))
return q && q[1]
}
export function getAllQueryParams() {
let params = {}
params.theme = checkSupportedTheme(getQueryParam(window.location, 'theme'))
params.inputCurrency = isAddress(getQueryParam(window.location, 'inputCurrency'))
? getQueryParam(window.location, 'inputCurrency')
: ''
params.outputCurrency = isAddress(getQueryParam(window.location, 'outputCurrency'))
? getQueryParam(window.location, 'outputCurrency')
: ''
params.slippage = !isNaN(getQueryParam(window.location, 'slippage')) ? getQueryParam(window.location, 'slippage') : ''
params.exactField = getQueryParam(window.location, 'exactField')
params.exactAmount = !isNaN(getQueryParam(window.location, 'exactAmount'))
? getQueryParam(window.location, 'exactAmount')
: ''
params.theme = checkSupportedTheme(getQueryParam(window.location, 'theme'))
params.recipient = isAddress(getQueryParam(window.location, 'recipient'))
? getQueryParam(window.location, 'recipient')
: ''
// Add Liquidity params
params.ethAmount = !isNaN(getQueryParam(window.location, 'ethAmount'))
? getQueryParam(window.location, 'ethAmount')
: ''
params.tokenAmount = !isNaN(getQueryParam(window.location, 'tokenAmount'))
? getQueryParam(window.location, 'tokenAmount')
: ''
params.token = isAddress(getQueryParam(window.location, 'token')) ? getQueryParam(window.location, 'token') : ''
// Remove liquidity params
params.poolTokenAmount = !isNaN(getQueryParam(window.location, 'poolTokenAmount'))
? getQueryParam(window.location, 'poolTokenAmount')
: ''
params.poolTokenAddress = isAddress(getQueryParam(window.location, 'poolTokenAddress'))
? getQueryParam(window.location, 'poolTokenAddress')
: ''
// Create Exchange params
params.tokenAddress = isAddress(getQueryParam(window.location, 'tokenAddress'))
? getQueryParam(window.location, 'tokenAddress')
: ''
return params
}
export function checkSupportedTheme(themeName) {
if (themeName && themeName.toUpperCase() in SUPPORTED_THEMES) {
return themeName.toUpperCase()
}
return null
}
export function getNetworkName(networkId) { export function getNetworkName(networkId) {
switch (networkId) { switch (networkId) {
case 1: { case 1: {