Rewrite localstorage context (#749)
* Rewrite the local storage context into a redux store for user data * Separate out the mega methods * Fix infinite loop * Missing dependency * Missing dependency, rename version field
This commit is contained in:
parent
b28ad2865d
commit
19a53cd999
@ -48,6 +48,7 @@
|
|||||||
"react-spring": "^8.0.27",
|
"react-spring": "^8.0.27",
|
||||||
"react-use-gesture": "^6.0.14",
|
"react-use-gesture": "^6.0.14",
|
||||||
"rebass": "^4.0.7",
|
"rebass": "^4.0.7",
|
||||||
|
"redux-localstorage-simple": "^2.2.0",
|
||||||
"styled-components": "^4.2.0",
|
"styled-components": "^4.2.0",
|
||||||
"swr": "0.1.18",
|
"swr": "0.1.18",
|
||||||
"use-media": "^1.4.0"
|
"use-media": "^1.4.0"
|
||||||
|
@ -16,7 +16,7 @@ import { AutoRow, RowBetween, RowFixed } from '../Row'
|
|||||||
import { ROUTER_ADDRESS } from '../../constants'
|
import { ROUTER_ADDRESS } from '../../constants'
|
||||||
import { useTokenAllowance } from '../../data/Allowances'
|
import { useTokenAllowance } from '../../data/Allowances'
|
||||||
import { useAddressBalance, useAllBalances } from '../../contexts/Balances'
|
import { useAddressBalance, useAllBalances } from '../../contexts/Balances'
|
||||||
import { useLocalStorageTokens } from '../../contexts/LocalStorage'
|
import { useAddUserToken, useFetchTokenByAddress } from '../../state/user/hooks'
|
||||||
import { usePair } from '../../data/Reserves'
|
import { usePair } from '../../data/Reserves'
|
||||||
import { useAllTokens, useToken } from '../../contexts/Tokens'
|
import { useAllTokens, useToken } from '../../contexts/Tokens'
|
||||||
import { usePendingApproval, useTransactionAdder } from '../../contexts/Transactions'
|
import { usePendingApproval, useTransactionAdder } from '../../contexts/Transactions'
|
||||||
@ -113,7 +113,8 @@ function ExchangePage({ sendingInput = false, history, params }: ExchangePagePro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ensure input + output tokens are added to localstorage
|
// ensure input + output tokens are added to localstorage
|
||||||
const [, { fetchTokenByAddress, addToken }] = useLocalStorageTokens()
|
const fetchTokenByAddress = useFetchTokenByAddress()
|
||||||
|
const addToken = useAddUserToken()
|
||||||
const allTokens = useAllTokens()
|
const allTokens = useAllTokens()
|
||||||
const inputTokenAddress = fieldData[Field.INPUT].address
|
const inputTokenAddress = fieldData[Field.INPUT].address
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { Send, Sun, Moon } from 'react-feather'
|
import { Send, Sun, Moon } from 'react-feather'
|
||||||
import { useDarkModeManager } from '../../contexts/LocalStorage'
|
import { useDarkModeManager } from '../../state/user/hooks'
|
||||||
|
|
||||||
import { ButtonSecondary } from '../Button'
|
import { ButtonSecondary } from '../Button'
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import { isMobile } from 'react-device-detect'
|
|||||||
import { YellowCard } from '../Card'
|
import { YellowCard } from '../Card'
|
||||||
import { useWeb3React } from '../../hooks'
|
import { useWeb3React } from '../../hooks'
|
||||||
import { useAddressBalance } from '../../contexts/Balances'
|
import { useAddressBalance } from '../../contexts/Balances'
|
||||||
import { useDarkModeManager } from '../../contexts/LocalStorage'
|
import { useDarkModeManager } from '../../state/user/hooks'
|
||||||
|
|
||||||
import Logo from '../../assets/svg/logo.svg'
|
import Logo from '../../assets/svg/logo.svg'
|
||||||
import Wordmark from '../../assets/svg/wordmark.svg'
|
import Wordmark from '../../assets/svg/wordmark.svg'
|
||||||
|
@ -16,7 +16,7 @@ import { ButtonPrimary, ButtonDropwdown, ButtonDropwdownLight } from '../Button'
|
|||||||
import { useToken } from '../../contexts/Tokens'
|
import { useToken } from '../../contexts/Tokens'
|
||||||
import { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
import { useAddressBalance } from '../../contexts/Balances'
|
import { useAddressBalance } from '../../contexts/Balances'
|
||||||
import { useLocalStoragePairAdder } from '../../contexts/LocalStorage'
|
import { usePairAdder } from '../../state/user/hooks'
|
||||||
import { usePair } from '../../data/Reserves'
|
import { usePair } from '../../data/Reserves'
|
||||||
|
|
||||||
const Fields = {
|
const Fields = {
|
||||||
@ -35,7 +35,7 @@ function PoolFinder({ history }: RouteComponentProps) {
|
|||||||
const token1: Token = useToken(token1Address)
|
const token1: Token = useToken(token1Address)
|
||||||
|
|
||||||
const pair: Pair = usePair(token0, token1)
|
const pair: Pair = usePair(token0, token1)
|
||||||
const addPair = useLocalStoragePairAdder()
|
const addPair = usePairAdder()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pair) {
|
if (pair) {
|
||||||
|
@ -24,7 +24,12 @@ import { RowBetween, RowFixed, AutoRow } from '../Row'
|
|||||||
|
|
||||||
import { isAddress } from '../../utils'
|
import { isAddress } from '../../utils'
|
||||||
import { useWeb3React } from '../../hooks'
|
import { useWeb3React } from '../../hooks'
|
||||||
import { useLocalStorageTokens, useAllDummyPairs } from '../../contexts/LocalStorage'
|
import {
|
||||||
|
useAllDummyPairs,
|
||||||
|
useFetchTokenByAddress,
|
||||||
|
useAddUserToken,
|
||||||
|
useRemoveUserAddedToken
|
||||||
|
} from '../../state/user/hooks'
|
||||||
import { useAllBalances } from '../../contexts/Balances'
|
import { useAllBalances } from '../../contexts/Balances'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useToken, useAllTokens, ALL_TOKENS } from '../../contexts/Tokens'
|
import { useToken, useAllTokens, ALL_TOKENS } from '../../contexts/Tokens'
|
||||||
@ -179,7 +184,9 @@ function SearchModal({
|
|||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const [sortDirection, setSortDirection] = useState(true)
|
const [sortDirection, setSortDirection] = useState(true)
|
||||||
|
|
||||||
const [, { fetchTokenByAddress, addToken, removeTokenByAddress }] = useLocalStorageTokens()
|
const fetchTokenByAddress = useFetchTokenByAddress()
|
||||||
|
const addToken = useAddUserToken()
|
||||||
|
const removeTokenByAddress = useRemoveUserAddedToken()
|
||||||
|
|
||||||
// if the current input is an address, and we don't have the token in context, try to fetch it
|
// if the current input is an address, and we don't have the token in context, try to fetch it
|
||||||
const token = useToken(searchQuery)
|
const token = useToken(searchQuery)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import QRCode from 'qrcode.react'
|
import QRCode from 'qrcode.react'
|
||||||
import { useDarkModeManager } from '../../contexts/LocalStorage'
|
import { useDarkModeManager } from '../../state/user/hooks'
|
||||||
|
|
||||||
const QRCodeWrapper = styled.div`
|
const QRCodeWrapper = styled.div`
|
||||||
${({ theme }) => theme.flexColumnNoWrap};
|
${({ theme }) => theme.flexColumnNoWrap};
|
||||||
|
@ -6,7 +6,7 @@ import { useAllTokens } from './Tokens'
|
|||||||
import { useWeb3React, useDebounce } from '../hooks'
|
import { useWeb3React, useDebounce } from '../hooks'
|
||||||
|
|
||||||
import { getEtherBalance, getTokenBalance, isAddress } from '../utils'
|
import { getEtherBalance, getTokenBalance, isAddress } from '../utils'
|
||||||
import { useAllDummyPairs } from './LocalStorage'
|
import { useAllDummyPairs } from '../state/user/hooks'
|
||||||
|
|
||||||
const LOCAL_STORAGE_KEY = 'BALANCES'
|
const LOCAL_STORAGE_KEY = 'BALANCES'
|
||||||
const SHORT_BLOCK_TIMEOUT = (60 * 2) / 15 // in seconds, represented as a block number delta
|
const SHORT_BLOCK_TIMEOUT = (60 * 2) / 15 // in seconds, represented as a block number delta
|
||||||
|
@ -1,325 +0,0 @@
|
|||||||
import React, { createContext, useContext, useMemo, useCallback, useEffect, useState } from 'react'
|
|
||||||
import { Token, Pair, TokenAmount, JSBI, WETH, ChainId } from '@uniswap/sdk'
|
|
||||||
import { getTokenDecimals, getTokenSymbol, getTokenName, isAddress } from '../utils'
|
|
||||||
import { useWeb3React } from '@web3-react/core'
|
|
||||||
import { useAllTokens } from './Tokens'
|
|
||||||
|
|
||||||
enum LocalStorageKeys {
|
|
||||||
VERSION = 'version',
|
|
||||||
LAST_SAVED = 'lastSaved',
|
|
||||||
BETA_MESSAGE_DISMISSED = 'betaMessageDismissed',
|
|
||||||
MIGRATION_MESSAGE_DISMISSED = 'migrationMessageDismissed',
|
|
||||||
DARK_MODE = 'darkMode',
|
|
||||||
TOKENS = 'tokens',
|
|
||||||
PAIRS = 'pairs'
|
|
||||||
}
|
|
||||||
|
|
||||||
function useLocalStorage<T, S = T>(
|
|
||||||
key: LocalStorageKeys,
|
|
||||||
defaultValue: T,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
{ serialize, deserialize }: { serialize: (toSerialize: T) => S; deserialize: (toDeserialize: S) => T } = {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
serialize: (toSerialize): S => (toSerialize as unknown) as S,
|
|
||||||
deserialize: (toDeserialize): T => (toDeserialize as unknown) as T
|
|
||||||
}
|
|
||||||
): [T, (value: T) => void] {
|
|
||||||
const [value, setValue] = useState(() => {
|
|
||||||
try {
|
|
||||||
return deserialize(JSON.parse(window.localStorage.getItem(key))) ?? defaultValue
|
|
||||||
} catch {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
try {
|
|
||||||
window.localStorage.setItem(key, JSON.stringify(serialize(value)))
|
|
||||||
} catch {}
|
|
||||||
}, [key, serialize, value])
|
|
||||||
|
|
||||||
return [value, setValue]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SerializedToken {
|
|
||||||
chainId: number
|
|
||||||
address: string
|
|
||||||
decimals: number
|
|
||||||
symbol: string
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function serializeToken(token: Token): SerializedToken {
|
|
||||||
return {
|
|
||||||
chainId: token.chainId,
|
|
||||||
address: token.address,
|
|
||||||
decimals: token.decimals,
|
|
||||||
symbol: token.symbol,
|
|
||||||
name: token.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function deserializeToken(serializedToken: SerializedToken): Token {
|
|
||||||
return new Token(
|
|
||||||
serializedToken.chainId,
|
|
||||||
serializedToken.address,
|
|
||||||
serializedToken.decimals,
|
|
||||||
serializedToken.symbol,
|
|
||||||
serializedToken.name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const LocalStorageContext = createContext<[any, any]>([{}, {}])
|
|
||||||
|
|
||||||
function useLocalStorageContext() {
|
|
||||||
return useContext(LocalStorageContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Provider({ children }: { children: React.ReactNode }) {
|
|
||||||
// global localstorage state
|
|
||||||
const [version, setVersion] = useLocalStorage<number>(LocalStorageKeys.VERSION, 0)
|
|
||||||
const [lastSaved, setLastSaved] = useLocalStorage<number>(LocalStorageKeys.LAST_SAVED, Math.floor(Date.now() / 1000))
|
|
||||||
const [betaMessageDismissed, setBetaMessageDismissed] = useLocalStorage<boolean>(
|
|
||||||
LocalStorageKeys.BETA_MESSAGE_DISMISSED,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
const [migrationMessageDismissed, setMigrationMessageDismissed] = useLocalStorage<boolean>(
|
|
||||||
LocalStorageKeys.MIGRATION_MESSAGE_DISMISSED,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
const [darkMode, setDarkMode] = useLocalStorage<boolean>(
|
|
||||||
LocalStorageKeys.DARK_MODE,
|
|
||||||
window?.matchMedia('(prefers-color-scheme: dark)')?.matches ? true : false
|
|
||||||
)
|
|
||||||
const [tokens, setTokens] = useLocalStorage<Token[], SerializedToken[]>(LocalStorageKeys.TOKENS, [], {
|
|
||||||
serialize: (tokens: Token[]) => tokens.map(serializeToken),
|
|
||||||
deserialize: (serializedTokens: SerializedToken[]) => serializedTokens.map(deserializeToken)
|
|
||||||
})
|
|
||||||
const [pairs, setPairs] = useLocalStorage<Token[][], SerializedToken[][]>(LocalStorageKeys.PAIRS, [], {
|
|
||||||
serialize: (nestedTokens: Token[][]) => nestedTokens.map(tokens => tokens.map(serializeToken)),
|
|
||||||
deserialize: (serializedNestedTokens: SerializedToken[][]) =>
|
|
||||||
serializedNestedTokens.map(serializedTokens => serializedTokens.map(deserializeToken))
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LocalStorageContext.Provider
|
|
||||||
value={useMemo(
|
|
||||||
() => [
|
|
||||||
{ version, lastSaved, betaMessageDismissed, migrationMessageDismissed, darkMode, tokens, pairs },
|
|
||||||
{
|
|
||||||
setVersion,
|
|
||||||
setLastSaved,
|
|
||||||
setBetaMessageDismissed,
|
|
||||||
setMigrationMessageDismissed,
|
|
||||||
setDarkMode,
|
|
||||||
setTokens,
|
|
||||||
setPairs
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
version,
|
|
||||||
lastSaved,
|
|
||||||
betaMessageDismissed,
|
|
||||||
migrationMessageDismissed,
|
|
||||||
darkMode,
|
|
||||||
|
|
||||||
tokens,
|
|
||||||
pairs,
|
|
||||||
setVersion,
|
|
||||||
setLastSaved,
|
|
||||||
setBetaMessageDismissed,
|
|
||||||
setMigrationMessageDismissed,
|
|
||||||
setDarkMode,
|
|
||||||
setTokens,
|
|
||||||
setPairs
|
|
||||||
]
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</LocalStorageContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Updater() {
|
|
||||||
const [, { setDarkMode }] = useLocalStorageContext()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const darkHandler = (match: MediaQueryListEvent) => {
|
|
||||||
if (match.matches) {
|
|
||||||
setDarkMode(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const lightHandler = (match: MediaQueryListEvent) => {
|
|
||||||
if (match.matches) {
|
|
||||||
setDarkMode(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window?.matchMedia('(prefers-color-scheme: dark)')?.addListener(darkHandler)
|
|
||||||
window?.matchMedia('(prefers-color-scheme: light)')?.addListener(lightHandler)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window?.matchMedia('(prefers-color-scheme: dark)')?.removeListener(darkHandler)
|
|
||||||
window?.matchMedia('(prefers-color-scheme: light)')?.removeListener(lightHandler)
|
|
||||||
}
|
|
||||||
}, [setDarkMode])
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useBetaMessageManager() {
|
|
||||||
const [{ betaMessageDismissed }, { setBetaMessageDismissed }] = useLocalStorageContext()
|
|
||||||
|
|
||||||
const dismissBetaMessage = useCallback(() => {
|
|
||||||
setBetaMessageDismissed(true)
|
|
||||||
}, [setBetaMessageDismissed])
|
|
||||||
|
|
||||||
return [!betaMessageDismissed, dismissBetaMessage]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useMigrationMessageManager() {
|
|
||||||
const [{ migrationMessageDismissed }, { setMigrationMessageDismissed }] = useLocalStorageContext()
|
|
||||||
|
|
||||||
const dismissMigrationMessage = useCallback(() => {
|
|
||||||
setMigrationMessageDismissed(true)
|
|
||||||
}, [setMigrationMessageDismissed])
|
|
||||||
|
|
||||||
return [!migrationMessageDismissed, dismissMigrationMessage]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDarkModeManager() {
|
|
||||||
const [{ darkMode }, { setDarkMode }] = useLocalStorageContext()
|
|
||||||
|
|
||||||
const toggleSetDarkMode = useCallback(
|
|
||||||
value => {
|
|
||||||
setDarkMode(typeof value === 'boolean' ? value : !darkMode)
|
|
||||||
},
|
|
||||||
[darkMode, setDarkMode]
|
|
||||||
)
|
|
||||||
|
|
||||||
return [darkMode, toggleSetDarkMode]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useLocalStorageTokens(): [
|
|
||||||
Token[],
|
|
||||||
{
|
|
||||||
fetchTokenByAddress: (address: string) => Promise<Token | null>
|
|
||||||
addToken: (token: Token) => void
|
|
||||||
removeTokenByAddress: (chainId: number, address: string) => void
|
|
||||||
}
|
|
||||||
] {
|
|
||||||
const { library, chainId } = useWeb3React()
|
|
||||||
const [{ tokens }, { setTokens }] = useLocalStorageContext()
|
|
||||||
|
|
||||||
const fetchTokenByAddress = useCallback(
|
|
||||||
async (address: string) => {
|
|
||||||
const [decimals, symbol, name] = await Promise.all([
|
|
||||||
getTokenDecimals(address, library).catch(() => null),
|
|
||||||
getTokenSymbol(address, library).catch(() => 'UNKNOWN'),
|
|
||||||
getTokenName(address, library).catch(() => 'Unknown')
|
|
||||||
])
|
|
||||||
|
|
||||||
if (decimals === null) {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
return new Token(chainId, address, decimals, symbol, name)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[library, chainId]
|
|
||||||
)
|
|
||||||
|
|
||||||
const addToken = useCallback(
|
|
||||||
(token: Token) => {
|
|
||||||
setTokens(tokens => tokens.filter(currentToken => !currentToken.equals(token)).concat([token]))
|
|
||||||
},
|
|
||||||
[setTokens]
|
|
||||||
)
|
|
||||||
|
|
||||||
const removeTokenByAddress = useCallback(
|
|
||||||
(chainId: number, address: string) => {
|
|
||||||
setTokens(tokens =>
|
|
||||||
tokens.filter(
|
|
||||||
currentToken => !(currentToken.chainId === chainId && currentToken.address === isAddress(address))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
[setTokens]
|
|
||||||
)
|
|
||||||
|
|
||||||
return [tokens, { fetchTokenByAddress, addToken, removeTokenByAddress }]
|
|
||||||
}
|
|
||||||
|
|
||||||
const ZERO = JSBI.BigInt(0)
|
|
||||||
export function useLocalStoragePairAdder(): (pair: Pair) => void {
|
|
||||||
const [, { setPairs }] = useLocalStorageContext()
|
|
||||||
|
|
||||||
return useCallback(
|
|
||||||
(pair: Pair) => {
|
|
||||||
setPairs(pairs =>
|
|
||||||
pairs
|
|
||||||
.filter(tokens => !(tokens[0].equals(pair.token0) && tokens[1].equals(pair.token1)))
|
|
||||||
.concat([[pair.token0, pair.token1]])
|
|
||||||
)
|
|
||||||
},
|
|
||||||
[setPairs]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const bases = [
|
|
||||||
...Object.values(WETH),
|
|
||||||
new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'),
|
|
||||||
new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C')
|
|
||||||
]
|
|
||||||
|
|
||||||
export function useAllDummyPairs(): Pair[] {
|
|
||||||
const { chainId } = useWeb3React()
|
|
||||||
const tokens = useAllTokens()
|
|
||||||
const generatedPairs: Pair[] = useMemo(
|
|
||||||
() =>
|
|
||||||
Object.values(tokens)
|
|
||||||
// select only tokens on the current chain
|
|
||||||
.filter(token => token.chainId === chainId)
|
|
||||||
.flatMap(token => {
|
|
||||||
// for each token on the current chain,
|
|
||||||
return (
|
|
||||||
bases
|
|
||||||
// loop through all the bases valid for the current chain,
|
|
||||||
.filter(base => base.chainId === chainId)
|
|
||||||
// to construct pairs of the given token with each base
|
|
||||||
.map(base => {
|
|
||||||
if (base.equals(token)) {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
return new Pair(new TokenAmount(base, ZERO), new TokenAmount(token, ZERO))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(pair => !!pair)
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
[tokens, chainId]
|
|
||||||
)
|
|
||||||
|
|
||||||
const [{ pairs }] = useLocalStorageContext()
|
|
||||||
const userPairs = useMemo(
|
|
||||||
() =>
|
|
||||||
pairs
|
|
||||||
.filter(tokens => tokens[0].chainId === chainId)
|
|
||||||
.map(tokens => new Pair(new TokenAmount(tokens[0], ZERO), new TokenAmount(tokens[1], ZERO))),
|
|
||||||
[pairs, chainId]
|
|
||||||
)
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
return (
|
|
||||||
generatedPairs
|
|
||||||
.concat(userPairs)
|
|
||||||
// filter out duplicate pairs
|
|
||||||
.filter((pair, i, concatenatedPairs) => {
|
|
||||||
const firstAppearance = concatenatedPairs.findIndex(
|
|
||||||
concatenatedPair =>
|
|
||||||
concatenatedPair.token0.equals(pair.token0) && concatenatedPair.token1.equals(pair.token1)
|
|
||||||
)
|
|
||||||
return i === firstAppearance
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}, [generatedPairs, userPairs])
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
|
import { ChainId, Token, WETH } from '@uniswap/sdk'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { ChainId, WETH, Token } from '@uniswap/sdk'
|
|
||||||
import { useWeb3React } from '../hooks'
|
import { useWeb3React } from '../hooks'
|
||||||
import { useLocalStorageTokens } from './LocalStorage'
|
import { useUserAddedTokens } from '../state/user/hooks'
|
||||||
|
|
||||||
export const ALL_TOKENS = [
|
export const ALL_TOKENS = [
|
||||||
// WETH on all chains
|
// WETH on all chains
|
||||||
@ -47,28 +47,22 @@ export const ALL_TOKENS = [
|
|||||||
|
|
||||||
export function useAllTokens(): { [address: string]: Token } {
|
export function useAllTokens(): { [address: string]: Token } {
|
||||||
const { chainId } = useWeb3React()
|
const { chainId } = useWeb3React()
|
||||||
const [localStorageTokens] = useLocalStorageTokens()
|
const userAddedTokens = useUserAddedTokens()
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return (
|
return (
|
||||||
localStorageTokens
|
userAddedTokens
|
||||||
// filter to the current chain
|
|
||||||
.filter(token => token.chainId === chainId)
|
|
||||||
// reduce into all ALL_TOKENS filtered by the current chain
|
// reduce into all ALL_TOKENS filtered by the current chain
|
||||||
.reduce((tokenMap, token) => {
|
.reduce<{ [address: string]: Token }>((tokenMap, token) => {
|
||||||
return {
|
tokenMap[token.address] = token
|
||||||
...tokenMap,
|
return tokenMap
|
||||||
[token.address]: token
|
|
||||||
}
|
|
||||||
}, ALL_TOKENS?.[chainId] ?? {})
|
}, ALL_TOKENS?.[chainId] ?? {})
|
||||||
)
|
)
|
||||||
}, [localStorageTokens, chainId])
|
}, [userAddedTokens, chainId])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useToken(tokenAddress: string): Token {
|
export function useToken(tokenAddress: string): Token {
|
||||||
const tokens = useAllTokens()
|
const tokens = useAllTokens()
|
||||||
|
|
||||||
const token = tokens?.[tokenAddress]
|
return tokens?.[tokenAddress]
|
||||||
|
|
||||||
return token
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import { Provider } from 'react-redux'
|
|||||||
|
|
||||||
import { NetworkContextName } from './constants'
|
import { NetworkContextName } from './constants'
|
||||||
import { isMobile } from 'react-device-detect'
|
import { isMobile } from 'react-device-detect'
|
||||||
import LocalStorageContextProvider, { Updater as LocalStorageContextUpdater } from './contexts/LocalStorage'
|
import { Updater as LocalStorageContextUpdater } from './state/user/hooks'
|
||||||
import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions'
|
import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions'
|
||||||
import BalancesContextProvider, { Updater as BalancesContextUpdater } from './contexts/Balances'
|
import BalancesContextProvider, { Updater as BalancesContextUpdater } from './contexts/Balances'
|
||||||
import App from './pages/App'
|
import App from './pages/App'
|
||||||
@ -37,11 +37,9 @@ ReactGA.pageview(window.location.pathname + window.location.search)
|
|||||||
|
|
||||||
function ContextProviders({ children }: { children: React.ReactNode }) {
|
function ContextProviders({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<LocalStorageContextProvider>
|
<TransactionContextProvider>
|
||||||
<TransactionContextProvider>
|
<BalancesContextProvider>{children}</BalancesContextProvider>
|
||||||
<BalancesContextProvider>{children}</BalancesContextProvider>
|
</TransactionContextProvider>
|
||||||
</TransactionContextProvider>
|
|
||||||
</LocalStorageContextProvider>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ import { ROUTER_ADDRESS } from '../../constants'
|
|||||||
import { getRouterContract, calculateGasMargin, calculateSlippageAmount } from '../../utils'
|
import { getRouterContract, calculateGasMargin, calculateSlippageAmount } from '../../utils'
|
||||||
import { BigNumber } from '@ethersproject/bignumber'
|
import { BigNumber } from '@ethersproject/bignumber'
|
||||||
import { usePair } from '../../data/Reserves'
|
import { usePair } from '../../data/Reserves'
|
||||||
import { useLocalStorageTokens } from '../../contexts/LocalStorage'
|
import { useAddUserToken, useFetchTokenByAddress } from '../../state/user/hooks'
|
||||||
import { useAllTokens } from '../../contexts/Tokens'
|
import { useAllTokens } from '../../contexts/Tokens'
|
||||||
|
|
||||||
// denominated in bips
|
// denominated in bips
|
||||||
@ -187,7 +187,9 @@ function AddLiquidity({ token0, token1 }: AddLiquidityProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ensure input + output tokens are added to localstorage
|
// ensure input + output tokens are added to localstorage
|
||||||
const [, { fetchTokenByAddress, addToken }] = useLocalStorageTokens()
|
const fetchTokenByAddress = useFetchTokenByAddress()
|
||||||
|
const addToken = useAddUserToken()
|
||||||
|
|
||||||
const allTokens = useAllTokens()
|
const allTokens = useAllTokens()
|
||||||
const inputTokenAddress = fieldData[Field.INPUT].address
|
const inputTokenAddress = fieldData[Field.INPUT].address
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -16,7 +16,7 @@ import { AutoColumn, ColumnCenter } from '../../components/Column'
|
|||||||
import { useWeb3React } from '@web3-react/core'
|
import { useWeb3React } from '@web3-react/core'
|
||||||
import { useAllBalances, useAccountLPBalances } from '../../contexts/Balances'
|
import { useAllBalances, useAccountLPBalances } from '../../contexts/Balances'
|
||||||
import { usePair } from '../../data/Reserves'
|
import { usePair } from '../../data/Reserves'
|
||||||
import { useAllDummyPairs } from '../../contexts/LocalStorage'
|
import { useAllDummyPairs } from '../../state/user/hooks'
|
||||||
|
|
||||||
const Positions = styled.div`
|
const Positions = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
import { configureStore } from '@reduxjs/toolkit'
|
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
|
||||||
import application from './application/reducer'
|
import application from './application/reducer'
|
||||||
|
import user from './user/reducer'
|
||||||
|
import { save, load } from 'redux-localstorage-simple'
|
||||||
|
|
||||||
|
const PERSISTED_KEYS: string[] = ['user']
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
application
|
application,
|
||||||
}
|
user
|
||||||
|
},
|
||||||
|
middleware: [...getDefaultMiddleware(), save({ states: PERSISTED_KEYS })],
|
||||||
|
preloadedState: load({ states: PERSISTED_KEYS })
|
||||||
})
|
})
|
||||||
|
|
||||||
export default store
|
export default store
|
||||||
|
25
src/state/user/actions.ts
Normal file
25
src/state/user/actions.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { createAction } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
export interface SerializedToken {
|
||||||
|
chainId: number
|
||||||
|
address: string
|
||||||
|
decimals: number
|
||||||
|
symbol: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SerializedPair {
|
||||||
|
token0: SerializedToken
|
||||||
|
token1: SerializedToken
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateVersion = createAction<void>('updateVersion')
|
||||||
|
export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>('updateMatchesDarkMode')
|
||||||
|
export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('updateUserDarkMode')
|
||||||
|
export const addSerializedToken = createAction<{ serializedToken: SerializedToken }>('addSerializedToken')
|
||||||
|
export const removeSerializedToken = createAction<{ chainId: number; address: string }>('removeSerializedToken')
|
||||||
|
export const addSerializedPair = createAction<{ serializedPair: SerializedPair }>('addSerializedPair')
|
||||||
|
export const removeSerializedPair = createAction<{ chainId: number; tokenAAddress: string; tokenBAddress: string }>(
|
||||||
|
'removeSerializedPair'
|
||||||
|
)
|
||||||
|
export const dismissBetaMessage = createAction<void>('dismissBetaMessage')
|
235
src/state/user/hooks.tsx
Normal file
235
src/state/user/hooks.tsx
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
import { ChainId, JSBI, Pair, Token, TokenAmount, WETH } from '@uniswap/sdk'
|
||||||
|
import { useWeb3React } from '@web3-react/core'
|
||||||
|
import { useCallback, useEffect, useMemo } from 'react'
|
||||||
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux'
|
||||||
|
import { useAllTokens } from '../../contexts/Tokens'
|
||||||
|
import { getTokenDecimals, getTokenName, getTokenSymbol } from '../../utils'
|
||||||
|
import { AppDispatch, AppState } from '../index'
|
||||||
|
import {
|
||||||
|
addSerializedPair,
|
||||||
|
addSerializedToken,
|
||||||
|
dismissBetaMessage,
|
||||||
|
removeSerializedToken,
|
||||||
|
SerializedPair,
|
||||||
|
SerializedToken,
|
||||||
|
updateMatchesDarkMode,
|
||||||
|
updateUserDarkMode,
|
||||||
|
updateVersion
|
||||||
|
} from './actions'
|
||||||
|
|
||||||
|
function serializeToken(token: Token): SerializedToken {
|
||||||
|
return {
|
||||||
|
chainId: token.chainId,
|
||||||
|
address: token.address,
|
||||||
|
decimals: token.decimals,
|
||||||
|
symbol: token.symbol,
|
||||||
|
name: token.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deserializeToken(serializedToken: SerializedToken): Token {
|
||||||
|
return new Token(
|
||||||
|
serializedToken.chainId,
|
||||||
|
serializedToken.address,
|
||||||
|
serializedToken.decimals,
|
||||||
|
serializedToken.symbol,
|
||||||
|
serializedToken.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Updater() {
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(updateVersion())
|
||||||
|
}, [dispatch])
|
||||||
|
|
||||||
|
// keep dark mode in sync with the system
|
||||||
|
useEffect(() => {
|
||||||
|
const darkHandler = (match: MediaQueryListEvent) => {
|
||||||
|
dispatch(updateMatchesDarkMode({ matchesDarkMode: match.matches }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = window?.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
dispatch(updateMatchesDarkMode({ matchesDarkMode: match.matches }))
|
||||||
|
|
||||||
|
match?.addEventListener('change', darkHandler)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
match?.removeEventListener('change', darkHandler)
|
||||||
|
}
|
||||||
|
}, [dispatch])
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// this currently isn't used anywhere, but is kept as an example of how to store/update a simple boolean
|
||||||
|
export function useBetaMessageManager() {
|
||||||
|
const betaMessageDismissed = useSelector<AppState, boolean>(state => state.user.betaMessageDismissed)
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
|
||||||
|
const wrappedDismissBetaMessage = useCallback(() => {
|
||||||
|
dispatch(dismissBetaMessage())
|
||||||
|
}, [dispatch])
|
||||||
|
|
||||||
|
return [!betaMessageDismissed, wrappedDismissBetaMessage]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useIsDarkMode(): boolean {
|
||||||
|
const { userDarkMode, matchesDarkMode } = useSelector<AppState, { userDarkMode: boolean; matchesDarkMode: boolean }>(
|
||||||
|
({ user: { matchesDarkMode, userDarkMode } }) => ({
|
||||||
|
userDarkMode,
|
||||||
|
matchesDarkMode
|
||||||
|
}),
|
||||||
|
shallowEqual
|
||||||
|
)
|
||||||
|
|
||||||
|
return userDarkMode === null ? matchesDarkMode : userDarkMode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDarkModeManager(): [boolean, () => void] {
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
const darkMode = useIsDarkMode()
|
||||||
|
|
||||||
|
const toggleSetDarkMode = useCallback(() => {
|
||||||
|
dispatch(updateUserDarkMode({ userDarkMode: !darkMode }))
|
||||||
|
}, [darkMode, dispatch])
|
||||||
|
|
||||||
|
return [darkMode, toggleSetDarkMode]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFetchTokenByAddress(): (address: string) => Promise<Token | null> {
|
||||||
|
const { library, chainId } = useWeb3React()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
async (address: string) => {
|
||||||
|
const [decimals, symbol, name] = await Promise.all([
|
||||||
|
getTokenDecimals(address, library).catch(() => null),
|
||||||
|
getTokenSymbol(address, library).catch(() => 'UNKNOWN'),
|
||||||
|
getTokenName(address, library).catch(() => 'Unknown')
|
||||||
|
])
|
||||||
|
|
||||||
|
if (decimals === null) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return new Token(chainId, address, decimals, symbol, name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[library, chainId]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAddUserToken(): (token: Token) => void {
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
return useCallback(
|
||||||
|
(token: Token) => {
|
||||||
|
dispatch(addSerializedToken({ serializedToken: serializeToken(token) }))
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRemoveUserAddedToken(): (chainId: number, address: string) => void {
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
return useCallback(
|
||||||
|
(chainId: number, address: string) => {
|
||||||
|
dispatch(removeSerializedToken({ chainId, address }))
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUserAddedTokens(): Token[] {
|
||||||
|
const { chainId } = useWeb3React()
|
||||||
|
const serializedTokensMap = useSelector<AppState, AppState['user']['tokens']>(({ user: { tokens } }) => tokens)
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
return Object.values(serializedTokensMap[chainId] ?? {}).map(deserializeToken)
|
||||||
|
}, [serializedTokensMap, chainId])
|
||||||
|
}
|
||||||
|
|
||||||
|
const ZERO = JSBI.BigInt(0)
|
||||||
|
|
||||||
|
function serializePair(pair: Pair): SerializedPair {
|
||||||
|
return {
|
||||||
|
token0: serializeToken(pair.token0),
|
||||||
|
token1: serializeToken(pair.token1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePairAdder(): (pair: Pair) => void {
|
||||||
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
(pair: Pair) => {
|
||||||
|
dispatch(addSerializedPair({ serializedPair: serializePair(pair) }))
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const bases = [
|
||||||
|
...Object.values(WETH),
|
||||||
|
new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin'),
|
||||||
|
new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C')
|
||||||
|
]
|
||||||
|
|
||||||
|
export function useAllDummyPairs(): Pair[] {
|
||||||
|
const { chainId } = useWeb3React()
|
||||||
|
const tokens = useAllTokens()
|
||||||
|
|
||||||
|
const generatedPairs: Pair[] = useMemo(
|
||||||
|
() =>
|
||||||
|
Object.values(tokens)
|
||||||
|
// select only tokens on the current chain
|
||||||
|
.filter(token => token.chainId === chainId)
|
||||||
|
.flatMap(token => {
|
||||||
|
// for each token on the current chain,
|
||||||
|
return (
|
||||||
|
bases
|
||||||
|
// loop through all the bases valid for the current chain,
|
||||||
|
.filter(base => base.chainId === chainId)
|
||||||
|
// to construct pairs of the given token with each base
|
||||||
|
.map(base => {
|
||||||
|
if (base.equals(token)) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return new Pair(new TokenAmount(base, ZERO), new TokenAmount(token, ZERO))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(pair => !!pair)
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
[tokens, chainId]
|
||||||
|
)
|
||||||
|
|
||||||
|
const savedSerializedPairs = useSelector<AppState>(({ user: { pairs } }) => pairs)
|
||||||
|
|
||||||
|
const userPairs = useMemo(
|
||||||
|
() =>
|
||||||
|
Object.values<SerializedPair>(savedSerializedPairs[chainId] ?? {}).map(
|
||||||
|
pair =>
|
||||||
|
new Pair(
|
||||||
|
new TokenAmount(deserializeToken(pair.token0), ZERO),
|
||||||
|
new TokenAmount(deserializeToken(pair.token1), ZERO)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
[savedSerializedPairs, chainId]
|
||||||
|
)
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const cache: { [pairKey: string]: boolean } = {}
|
||||||
|
return (
|
||||||
|
generatedPairs
|
||||||
|
.concat(userPairs)
|
||||||
|
// filter out duplicate pairs
|
||||||
|
.filter(pair => {
|
||||||
|
const pairKey = `${pair.token0.address}:${pair.token1.address}`
|
||||||
|
if (cache[pairKey]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (cache[pairKey] = true)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}, [generatedPairs, userPairs])
|
||||||
|
}
|
104
src/state/user/reducer.ts
Normal file
104
src/state/user/reducer.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { createReducer } from '@reduxjs/toolkit'
|
||||||
|
import {
|
||||||
|
addSerializedPair,
|
||||||
|
addSerializedToken,
|
||||||
|
removeSerializedPair,
|
||||||
|
removeSerializedToken,
|
||||||
|
SerializedPair,
|
||||||
|
SerializedToken,
|
||||||
|
updateMatchesDarkMode,
|
||||||
|
updateUserDarkMode,
|
||||||
|
updateVersion
|
||||||
|
} from './actions'
|
||||||
|
|
||||||
|
const currentTimestamp = () => new Date().getTime()
|
||||||
|
|
||||||
|
interface UserState {
|
||||||
|
lastVersion: string
|
||||||
|
|
||||||
|
userDarkMode: boolean | null // the user's choice for dark mode or light mode
|
||||||
|
matchesDarkMode: boolean // whether the dark mode media query matches
|
||||||
|
|
||||||
|
betaMessageDismissed: boolean
|
||||||
|
|
||||||
|
tokens: {
|
||||||
|
[chainId: number]: {
|
||||||
|
[address: string]: SerializedToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs: {
|
||||||
|
[chainId: number]: {
|
||||||
|
// keyed by token0Address:token1Address
|
||||||
|
[key: string]: SerializedPair
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function pairKey(token0Address: string, token1Address: string) {
|
||||||
|
return `${token0Address};${token1Address}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: UserState = {
|
||||||
|
lastVersion: '',
|
||||||
|
|
||||||
|
userDarkMode: null,
|
||||||
|
matchesDarkMode: false,
|
||||||
|
|
||||||
|
betaMessageDismissed: false,
|
||||||
|
|
||||||
|
tokens: {},
|
||||||
|
pairs: {},
|
||||||
|
|
||||||
|
timestamp: currentTimestamp()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createReducer(initialState, builder =>
|
||||||
|
builder
|
||||||
|
.addCase(updateVersion, state => {
|
||||||
|
if (state.lastVersion !== process.env.REACT_APP_GIT_COMMIT_HASH) {
|
||||||
|
state.lastVersion = process.env.REACT_APP_GIT_COMMIT_HASH
|
||||||
|
// other stuff
|
||||||
|
}
|
||||||
|
state.timestamp = currentTimestamp()
|
||||||
|
})
|
||||||
|
.addCase(updateUserDarkMode, (state, action) => {
|
||||||
|
state.userDarkMode = action.payload.userDarkMode
|
||||||
|
state.timestamp = currentTimestamp()
|
||||||
|
})
|
||||||
|
.addCase(updateMatchesDarkMode, (state, action) => {
|
||||||
|
state.matchesDarkMode = action.payload.matchesDarkMode
|
||||||
|
state.timestamp = currentTimestamp()
|
||||||
|
})
|
||||||
|
.addCase(addSerializedToken, (state, { payload: { serializedToken } }) => {
|
||||||
|
state.tokens[serializedToken.chainId] = state.tokens[serializedToken.chainId] || {}
|
||||||
|
state.tokens[serializedToken.chainId][serializedToken.address] = serializedToken
|
||||||
|
state.timestamp = currentTimestamp()
|
||||||
|
})
|
||||||
|
.addCase(removeSerializedToken, (state, { payload: { address, chainId } }) => {
|
||||||
|
state.tokens[chainId] = state.tokens[chainId] || {}
|
||||||
|
delete state.tokens[chainId][address]
|
||||||
|
state.timestamp = currentTimestamp()
|
||||||
|
})
|
||||||
|
.addCase(addSerializedPair, (state, { payload: { serializedPair } }) => {
|
||||||
|
if (
|
||||||
|
serializedPair.token0.chainId === serializedPair.token1.chainId &&
|
||||||
|
serializedPair.token0.address !== serializedPair.token1.address
|
||||||
|
) {
|
||||||
|
const chainId = serializedPair.token0.chainId
|
||||||
|
state.pairs[chainId] = state.pairs[chainId] || {}
|
||||||
|
state.pairs[chainId][pairKey(serializedPair.token0.address, serializedPair.token1.address)] = serializedPair
|
||||||
|
}
|
||||||
|
state.timestamp = currentTimestamp()
|
||||||
|
})
|
||||||
|
.addCase(removeSerializedPair, (state, { payload: { chainId, tokenAAddress, tokenBAddress } }) => {
|
||||||
|
if (state.pairs[chainId]) {
|
||||||
|
// just delete both keys if either exists
|
||||||
|
delete state.pairs[chainId][pairKey(tokenAAddress, tokenBAddress)]
|
||||||
|
delete state.pairs[chainId][pairKey(tokenBAddress, tokenAAddress)]
|
||||||
|
}
|
||||||
|
state.timestamp = currentTimestamp()
|
||||||
|
})
|
||||||
|
)
|
@ -1,13 +1,16 @@
|
|||||||
import React, { useEffect } from 'react'
|
import React, { useEffect, useMemo } from 'react'
|
||||||
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import styled, {
|
import styled, {
|
||||||
ThemeProvider as StyledComponentsThemeProvider,
|
ThemeProvider as StyledComponentsThemeProvider,
|
||||||
createGlobalStyle,
|
createGlobalStyle,
|
||||||
css,
|
css,
|
||||||
DefaultTheme
|
DefaultTheme
|
||||||
} from 'styled-components'
|
} from 'styled-components'
|
||||||
|
import { AppDispatch, AppState } from '../state'
|
||||||
|
import { updateUserDarkMode } from '../state/user/actions'
|
||||||
import { getQueryParam, checkSupportedTheme } from '../utils'
|
import { getQueryParam, checkSupportedTheme } from '../utils'
|
||||||
import { SUPPORTED_THEMES } from '../constants'
|
import { SUPPORTED_THEMES } from '../constants'
|
||||||
import { useDarkModeManager } from '../contexts/LocalStorage'
|
import { useIsDarkMode } from '../state/user/hooks'
|
||||||
import { Text, TextProps } from 'rebass'
|
import { Text, TextProps } from 'rebass'
|
||||||
import { Colors } from './styled'
|
import { Colors } from './styled'
|
||||||
|
|
||||||
@ -115,21 +118,28 @@ export function theme(darkMode: boolean): DefaultTheme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ThemeProvider({ children }: { children: React.ReactNode }) {
|
export default function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||||
const [darkMode, toggleDarkMode] = useDarkModeManager()
|
const dispatch = useDispatch<AppDispatch>()
|
||||||
|
const userDarkMode = useSelector<AppState, boolean | null>(state => state.user.userDarkMode)
|
||||||
|
const darkMode = useIsDarkMode()
|
||||||
|
|
||||||
const themeURL = checkSupportedTheme(getQueryParam(window.location, 'theme'))
|
const themeURL = checkSupportedTheme(getQueryParam(window.location, 'theme'))
|
||||||
const themeToRender = themeURL
|
const urlContainsDarkMode: boolean | null = themeURL
|
||||||
? themeURL.toUpperCase() === SUPPORTED_THEMES.DARK
|
? themeURL.toUpperCase() === SUPPORTED_THEMES.DARK
|
||||||
? true
|
? true
|
||||||
: themeURL.toUpperCase() === SUPPORTED_THEMES.LIGHT
|
: themeURL.toUpperCase() === SUPPORTED_THEMES.LIGHT
|
||||||
? false
|
? false
|
||||||
: darkMode
|
: null
|
||||||
: darkMode
|
: null
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
themeURL && toggleDarkMode(themeToRender)
|
if (urlContainsDarkMode !== null && userDarkMode === null) {
|
||||||
}, [toggleDarkMode, themeToRender, themeURL])
|
dispatch(updateUserDarkMode({ userDarkMode: urlContainsDarkMode }))
|
||||||
|
}
|
||||||
|
}, [dispatch, userDarkMode, urlContainsDarkMode])
|
||||||
|
|
||||||
return <StyledComponentsThemeProvider theme={theme(themeToRender)}>{children}</StyledComponentsThemeProvider>
|
const themeObject = useMemo(() => theme(darkMode), [darkMode])
|
||||||
|
|
||||||
|
return <StyledComponentsThemeProvider theme={themeObject}>{children}</StyledComponentsThemeProvider>
|
||||||
}
|
}
|
||||||
|
|
||||||
const TextWrapper = styled(Text)<{ color: keyof Colors }>`
|
const TextWrapper = styled(Text)<{ color: keyof Colors }>`
|
||||||
|
25
yarn.lock
25
yarn.lock
@ -5267,6 +5267,11 @@ clone-deep@^4.0.1:
|
|||||||
kind-of "^6.0.2"
|
kind-of "^6.0.2"
|
||||||
shallow-clone "^3.0.0"
|
shallow-clone "^3.0.0"
|
||||||
|
|
||||||
|
clone-function@>=1.0.1:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/clone-function/-/clone-function-1.0.6.tgz#428471937750bca9c48ecbfbc16f6e232f74a03d"
|
||||||
|
integrity sha1-QoRxk3dQvKnEjsv7wW9uIy90oD0=
|
||||||
|
|
||||||
clone-response@^1.0.2:
|
clone-response@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
|
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
|
||||||
@ -12069,6 +12074,11 @@ object-copy@^0.1.0:
|
|||||||
define-property "^0.2.5"
|
define-property "^0.2.5"
|
||||||
kind-of "^3.0.3"
|
kind-of "^3.0.3"
|
||||||
|
|
||||||
|
object-foreach@>=0.1.2:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-foreach/-/object-foreach-0.1.2.tgz#d7421c5b40e3b6a3ef57ac624368d21d8f8d2dec"
|
||||||
|
integrity sha1-10IcW0DjtqPvV6xiQ2jSHY+NLew=
|
||||||
|
|
||||||
object-hash@^2.0.1:
|
object-hash@^2.0.1:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea"
|
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea"
|
||||||
@ -12097,6 +12107,14 @@ object-keys@~0.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336"
|
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336"
|
||||||
integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=
|
integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=
|
||||||
|
|
||||||
|
object-merge@2.5.1:
|
||||||
|
version "2.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-merge/-/object-merge-2.5.1.tgz#077e8915ce38ea7294788448c5dd339e34df4227"
|
||||||
|
integrity sha1-B36JFc446nKUeIRIxd0znjTfQic=
|
||||||
|
dependencies:
|
||||||
|
clone-function ">=1.0.1"
|
||||||
|
object-foreach ">=0.1.2"
|
||||||
|
|
||||||
object-path@0.11.4:
|
object-path@0.11.4:
|
||||||
version "0.11.4"
|
version "0.11.4"
|
||||||
resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949"
|
resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949"
|
||||||
@ -14316,6 +14334,13 @@ recursive-readdir@2.2.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimatch "3.0.4"
|
minimatch "3.0.4"
|
||||||
|
|
||||||
|
redux-localstorage-simple@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/redux-localstorage-simple/-/redux-localstorage-simple-2.2.0.tgz#f60a70b0d858626d5db861b3db353ff848427f01"
|
||||||
|
integrity sha512-BmgnJ3NkxTDvNsnHAZrRVDgODafg2Vtb17q2F2LEhuJ+EderZBJA6aqRsyqZC32BJWpu8PPtferv4Io9dpUf3w==
|
||||||
|
dependencies:
|
||||||
|
object-merge "2.5.1"
|
||||||
|
|
||||||
redux-thunk@^2.3.0:
|
redux-thunk@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
|
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
|
||||||
|
Loading…
Reference in New Issue
Block a user