Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb725f51ce | ||
|
|
4d4462368b | ||
|
|
6fe5d4363d | ||
|
|
b46fa27084 | ||
|
|
ade2440613 | ||
|
|
4dc4620b60 | ||
|
|
202c2662f1 | ||
|
|
d2afd71c81 | ||
|
|
bad1ce2618 | ||
|
|
f194845b2b | ||
|
|
98d4e108e6 | ||
|
|
ab43ed1900 | ||
|
|
b147e047a5 | ||
|
|
bbb616f56c | ||
|
|
35a429ea65 | ||
|
|
bd16543c10 | ||
|
|
cbdeae276e | ||
|
|
e733113963 | ||
|
|
272b030b89 | ||
|
|
472a553d13 | ||
|
|
3a1bff146c | ||
|
|
b82b9acc54 | ||
|
|
fac3845756 | ||
|
|
9381a74f1d | ||
|
|
f6662a3208 | ||
|
|
134af82d90 | ||
|
|
75175b8e54 | ||
|
|
e3d8599dc7 | ||
|
|
77ee69ad52 | ||
|
|
4b82838f80 | ||
|
|
a177829976 | ||
|
|
b8b4f960dd | ||
|
|
2466414307 | ||
|
|
a3a32f0d68 | ||
|
|
ee001f86f0 | ||
|
|
87d6975bd8 | ||
|
|
4cab4e27ff | ||
|
|
f105f0995b | ||
|
|
18e89a7353 | ||
|
|
445f9a67a4 | ||
|
|
4c039c900c | ||
|
|
2c2e0a3419 | ||
|
|
1b43e0b28a | ||
|
|
a23f7782b2 | ||
|
|
723db9d0ea | ||
|
|
cbf165dc40 | ||
|
|
6f3579acf1 | ||
|
|
c9e2f86e57 | ||
|
|
8c0199119e | ||
|
|
5e6e6be888 | ||
|
|
79fb6485b1 | ||
|
|
29baaaf2ed | ||
|
|
f824fb25c2 | ||
|
|
28a6ea7e1a | ||
|
|
65566faf17 | ||
|
|
eb4f90e669 | ||
|
|
40308158ca | ||
|
|
d0d5240474 | ||
|
|
751ce8e6d6 | ||
|
|
748a5eadc0 | ||
|
|
5659fe21ea | ||
|
|
bc899b74a3 | ||
|
|
516c8b05a4 | ||
|
|
8502f9e303 | ||
|
|
bc90d416e6 | ||
|
|
2fd1cd72fd | ||
|
|
3a2276dcd1 | ||
|
|
c432c583f6 | ||
|
|
9b8f5ed8f4 | ||
|
|
0713a15028 | ||
|
|
62c502615f | ||
|
|
fdbe4b8f5e | ||
|
|
33c73f4dc8 | ||
|
|
c207a576e7 | ||
|
|
aa426514f3 | ||
|
|
ba9c28892e | ||
|
|
49c31ddfc8 | ||
|
|
4424205814 | ||
|
|
874f3fb737 | ||
|
|
ac27c89a44 | ||
|
|
0e530cf92e | ||
|
|
ed66b00b20 | ||
|
|
84fb05239b | ||
|
|
7500bbc0be | ||
|
|
c43c8de6cd | ||
|
|
9d40db5b21 | ||
|
|
a61eca36ae | ||
|
|
60479a442f | ||
|
|
5257188f70 | ||
|
|
7599239983 | ||
|
|
1561c0d000 | ||
|
|
1f740cf8c0 | ||
|
|
55c5f03004 | ||
|
|
a345cff614 | ||
|
|
85d8566cfa | ||
|
|
44d68e3ef0 | ||
|
|
04bd4900b0 | ||
|
|
81f277b36f | ||
|
|
575660d3e8 | ||
|
|
1e692491f1 | ||
|
|
b3639b3453 | ||
|
|
53ebf37b40 | ||
|
|
624ec33652 | ||
|
|
2890040118 | ||
|
|
68db8b3e23 |
@@ -23,7 +23,8 @@
|
||||
"lint": "yarn eslint .",
|
||||
"test": "craco test --coverage",
|
||||
"cypress:open": "cypress open --browser chrome --e2e",
|
||||
"cypress:run": "cypress run --browser chrome --e2e"
|
||||
"cypress:run": "cypress run --browser chrome --e2e",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
@@ -104,6 +105,8 @@
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"jest-styled-components": "^7.0.8",
|
||||
"ms.macro": "^2.0.0",
|
||||
"patch-package": "^6.4.7",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.7.1",
|
||||
"react-scripts": "^4.0.3",
|
||||
"relay-compiler": "^14.1.0",
|
||||
@@ -142,7 +145,7 @@
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.9.0",
|
||||
"@uniswap/widgets": "^2.1.1",
|
||||
"@uniswap/widgets": "^2.7.0",
|
||||
"@vanilla-extract/css": "^1.7.2",
|
||||
"@vanilla-extract/css-utils": "^0.1.2",
|
||||
"@vanilla-extract/dynamic": "^2.0.2",
|
||||
@@ -172,7 +175,6 @@
|
||||
"clsx": "^1.1.1",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"d3": "^7.6.1",
|
||||
"d3-curve-circlecorners": "^0.1.6",
|
||||
"ethers": "^5.1.4",
|
||||
"firebase": "^9.1.3",
|
||||
"focus-visible": "^5.2.0",
|
||||
|
||||
22
patches/@vanilla-extract+css+1.7.2.patch
Normal file
22
patches/@vanilla-extract+css+1.7.2.patch
Normal file
@@ -0,0 +1,22 @@
|
||||
diff --git a/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js b/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js
|
||||
index 6e40061..10283a2 100644
|
||||
--- a/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js
|
||||
+++ b/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js
|
||||
@@ -177,11 +177,13 @@ function generateIdentifier(debugId) {
|
||||
var fileScopeHash = hash__default["default"](packageName ? "".concat(packageName).concat(filePath) : filePath);
|
||||
var identifier = "".concat(fileScopeHash).concat(refCount);
|
||||
|
||||
- if (adapter_dist_vanillaExtractCssAdapter.getIdentOption() === 'debug') {
|
||||
- var devPrefix = getDevPrefix(debugId);
|
||||
+ if (process.env.VANILLA_EXTRACT_DEV_PREFIX) {
|
||||
+ if (adapter_dist_vanillaExtractCssAdapter.getIdentOption() === 'debug') {
|
||||
+ var devPrefix = getDevPrefix(debugId);
|
||||
|
||||
- if (devPrefix) {
|
||||
- identifier = "".concat(devPrefix, "__").concat(identifier);
|
||||
+ if (devPrefix) {
|
||||
+ identifier = "".concat(devPrefix, "__").concat(identifier);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/assets/images/tokensPromoDark.png
Normal file
BIN
src/assets/images/tokensPromoDark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
BIN
src/assets/images/tokensPromoLight.png
Normal file
BIN
src/assets/images/tokensPromoLight.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
3
src/assets/svg/socks.svg
Normal file
3
src/assets/svg/socks.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="10" height="12" viewBox="0 0 10 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.75 11C4.75 11 5.22377 11.1391 5.80923 10.5828L7.64433 8.65908C8.35405 7.91508 8.74998 6.92678 8.74989 5.89856C8.7498 4.72716 8.74971 3.31706 8.74991 2.50009C8.74996 2.22391 8.77618 2 8.5 2H8.25M6.74898 5.75L6.74979 2L6.74991 1.50009C6.74996 1.22391 6.52609 1 6.24991 1H4.25167C3.97553 1 3.75167 1.22386 3.75167 1.5V4.75039C3.75167 5.29859 3.52665 5.82276 3.12922 6.20034L1.6891 7.56856C1.10364 8.12478 1.10363 9.0266 1.68909 9.58283C2.12197 9.99409 2.75372 10.1013 3.29025 9.90438C3.47937 9.83497 3.65665 9.72779 3.80923 9.58283L5.80923 7.6827M6.74898 5.75L6.7487 6.36119C6.74861 6.63517 6.63611 6.89711 6.43748 7.08582L5.80923 7.6827M6.74898 5.75H6.4384C5.67845 5.75 5.19623 6.56419 5.56146 7.23061L5.80923 7.6827" stroke="white" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 871 B |
@@ -24,7 +24,7 @@ const HeaderRow = styled.div`
|
||||
padding: 1rem 1rem;
|
||||
font-weight: 500;
|
||||
color: ${(props) => (props.color === 'blue' ? ({ theme }) => theme.deprecated_primary1 : 'inherit')};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
padding: 1rem;
|
||||
`};
|
||||
`
|
||||
@@ -74,7 +74,7 @@ const AccountGroupingRow = styled.div`
|
||||
|
||||
const AccountSection = styled.div`
|
||||
padding: 0rem 1rem;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0rem 1rem 1.5rem 1rem;`};
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`padding: 0rem 1rem 1.5rem 1rem;`};
|
||||
`
|
||||
|
||||
const YourAccount = styled.div`
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { UNI_ADDRESS } from 'constants/addresses'
|
||||
import { TransactionInfo, TransactionType } from 'state/transactions/types'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
|
||||
import { nativeOnChain } from '../../constants/tokens'
|
||||
import { useCurrency } from '../../hooks/Tokens'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
|
||||
@@ -32,22 +35,38 @@ interface CurrencyPair {
|
||||
currencyId1: string | undefined
|
||||
}
|
||||
|
||||
const getCurrency = ({ info }: { info: TransactionInfo }): CurrencyPair => {
|
||||
const getCurrency = ({ info, chainId }: { info: TransactionInfo; chainId: number | undefined }): CurrencyPair => {
|
||||
switch (info.type) {
|
||||
case TransactionType.ADD_LIQUIDITY_V3_POOL:
|
||||
case TransactionType.REMOVE_LIQUIDITY_V3:
|
||||
case TransactionType.CREATE_V3_POOL:
|
||||
const { baseCurrencyId, quoteCurrencyId } = info
|
||||
return { currencyId0: baseCurrencyId, currencyId1: quoteCurrencyId }
|
||||
case TransactionType.SWAP:
|
||||
const { inputCurrencyId, outputCurrencyId } = info
|
||||
return { currencyId0: inputCurrencyId, currencyId1: outputCurrencyId }
|
||||
case TransactionType.WRAP:
|
||||
const { unwrapped } = info
|
||||
const native = info.chainId ? nativeOnChain(info.chainId) : undefined
|
||||
const base = 'ETH'
|
||||
const wrappedCurrency = native?.wrapped.address ?? 'WETH'
|
||||
return { currencyId0: unwrapped ? wrappedCurrency : base, currencyId1: unwrapped ? base : wrappedCurrency }
|
||||
case TransactionType.COLLECT_FEES:
|
||||
const { currencyId0, currencyId1 } = info
|
||||
return { currencyId0, currencyId1 }
|
||||
case TransactionType.APPROVAL:
|
||||
return { currencyId0: info.tokenAddress, currencyId1: undefined }
|
||||
case TransactionType.CLAIM:
|
||||
const uniAddress = chainId ? UNI_ADDRESS[chainId] : undefined
|
||||
return { currencyId0: uniAddress, currencyId1: undefined }
|
||||
default:
|
||||
return { currencyId0: undefined, currencyId1: undefined }
|
||||
}
|
||||
}
|
||||
|
||||
const LogoView = ({ info }: { info: TransactionInfo }) => {
|
||||
const { currencyId0, currencyId1 } = getCurrency({ info })
|
||||
const { chainId } = useWeb3React()
|
||||
const { currencyId0, currencyId1 } = getCurrency({ info, chainId })
|
||||
const currency0 = useCurrency(currencyId0)
|
||||
const currency1 = useCurrency(currencyId1)
|
||||
const isCentered = !(currency0 && currency1)
|
||||
|
||||
@@ -3,15 +3,22 @@ import { Fraction, TradeType } from '@uniswap/sdk-core'
|
||||
import JSBI from 'jsbi'
|
||||
import {
|
||||
AddLiquidityV3PoolTransactionInfo,
|
||||
ApproveTransactionInfo,
|
||||
ClaimTransactionInfo,
|
||||
CollectFeesTransactionInfo,
|
||||
ExactInputSwapTransactionInfo,
|
||||
ExactOutputSwapTransactionInfo,
|
||||
RemoveLiquidityV3TransactionInfo,
|
||||
TransactionInfo,
|
||||
TransactionType,
|
||||
WrapTransactionInfo,
|
||||
} from 'state/transactions/types'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { useCurrency } from '../../hooks/Tokens'
|
||||
import { nativeOnChain } from '../../constants/tokens'
|
||||
import { useCurrency, useToken } from '../../hooks/Tokens'
|
||||
import useENSName from '../../hooks/useENSName'
|
||||
import { shortenAddress } from '../../utils'
|
||||
import { TransactionState } from './index'
|
||||
|
||||
const HighlightText = styled.span`
|
||||
@@ -19,6 +26,10 @@ const HighlightText = styled.span`
|
||||
font-weight: 600;
|
||||
`
|
||||
|
||||
const BodyWrap = styled.div`
|
||||
line-height: 20px;
|
||||
`
|
||||
|
||||
interface ActionProps {
|
||||
pending: JSX.Element
|
||||
success: JSX.Element
|
||||
@@ -85,13 +96,13 @@ const SwapSummary = ({
|
||||
const { rawAmountFrom, rawAmountTo } = getRawAmounts(info)
|
||||
|
||||
return (
|
||||
<>
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<FormattedCurrencyAmount rawAmount={rawAmountFrom} currencyId={info.inputCurrencyId} sigFigs={2} />{' '}
|
||||
<Trans>for </Trans>{' '}
|
||||
<FormattedCurrencyAmount rawAmount={rawAmountTo} currencyId={info.outputCurrencyId} sigFigs={2} />{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</>
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -112,7 +123,7 @@ const AddLiquidityV3PoolSummary = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<BodyWrap>
|
||||
{createPool ? (
|
||||
<CreateV3PoolSummary info={info} transactionState={transactionState} />
|
||||
) : (
|
||||
@@ -124,7 +135,7 @@ const AddLiquidityV3PoolSummary = ({
|
||||
</>
|
||||
)}{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</>
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -143,13 +154,13 @@ const RemoveLiquidityV3Summary = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<FormattedCurrencyAmount rawAmount={expectedAmountBaseRaw} currencyId={baseCurrencyId} sigFigs={2} />{' '}
|
||||
<Trans>and</Trans>{' '}
|
||||
<FormattedCurrencyAmount rawAmount={expectedAmountQuoteRaw} currencyId={quoteCurrencyId} sigFigs={2} />{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</>
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -170,13 +181,135 @@ const CreateV3PoolSummary = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<HighlightText>
|
||||
{baseCurrency?.symbol}/{quoteCurrency?.symbol}{' '}
|
||||
</HighlightText>
|
||||
<Trans>Pool</Trans> <FailedText transactionState={transactionState} />
|
||||
</>
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const CollectFeesSummary = ({
|
||||
info,
|
||||
transactionState,
|
||||
}: {
|
||||
info: CollectFeesTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const { currencyId0, expectedCurrencyOwed0 = '0', expectedCurrencyOwed1 = '0', currencyId1 } = info
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Collecting</Trans>,
|
||||
success: <Trans>Collected</Trans>,
|
||||
failed: <Trans>Collect</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<FormattedCurrencyAmount rawAmount={expectedCurrencyOwed0} currencyId={currencyId0} sigFigs={2} />{' '}
|
||||
<Trans>and</Trans>{' '}
|
||||
<FormattedCurrencyAmount rawAmount={expectedCurrencyOwed1} currencyId={currencyId1} sigFigs={2} />{' '}
|
||||
<Trans>fees</Trans> <FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const ApprovalSummary = ({
|
||||
info,
|
||||
transactionState,
|
||||
}: {
|
||||
info: ApproveTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const token = useToken(info.tokenAddress)
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Approving</Trans>,
|
||||
success: <Trans>Approved</Trans>,
|
||||
failed: <Trans>Approve</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} /> <HighlightText>{token?.symbol}</HighlightText>{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const ClaimSummary = ({
|
||||
info: { recipient, uniAmountRaw },
|
||||
transactionState,
|
||||
}: {
|
||||
info: ClaimTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const { ENSName } = useENSName()
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Claiming</Trans>,
|
||||
success: <Trans>Claimed</Trans>,
|
||||
failed: <Trans>Claim</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
{uniAmountRaw && (
|
||||
<>
|
||||
<Action {...actionProps} />{' '}
|
||||
<HighlightText>
|
||||
{formatAmount(uniAmountRaw, 18, 4)}
|
||||
UNI{' '}
|
||||
</HighlightText>{' '}
|
||||
<Trans>for</Trans> <HighlightText>{ENSName ?? shortenAddress(recipient)}</HighlightText>
|
||||
</>
|
||||
)}{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const WrapSummary = ({
|
||||
info: { chainId, currencyAmountRaw, unwrapped },
|
||||
transactionState,
|
||||
}: {
|
||||
info: WrapTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const native = chainId ? nativeOnChain(chainId) : undefined
|
||||
const from = unwrapped ? native?.wrapped.symbol ?? 'WETH' : native?.symbol ?? 'ETH'
|
||||
const to = unwrapped ? native?.symbol ?? 'ETH' : native?.wrapped.symbol ?? 'WETH'
|
||||
|
||||
const amount = formatAmount(currencyAmountRaw, 18, 6)
|
||||
const actionProps = unwrapped
|
||||
? {
|
||||
transactionState,
|
||||
pending: <Trans>Unwrapping</Trans>,
|
||||
success: <Trans>Unwrapped</Trans>,
|
||||
failed: <Trans>Unwrap</Trans>,
|
||||
}
|
||||
: {
|
||||
transactionState,
|
||||
pending: <Trans>Wrapping</Trans>,
|
||||
success: <Trans>Wrapped</Trans>,
|
||||
failed: <Trans>Wrap</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<HighlightText>
|
||||
{amount} {from}
|
||||
</HighlightText>{' '}
|
||||
<Trans>to</Trans>{' '}
|
||||
<HighlightText>
|
||||
{amount} {to}
|
||||
</HighlightText>{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -188,6 +321,14 @@ const TransactionBody = ({ info, transactionState }: { info: TransactionInfo; tr
|
||||
return <AddLiquidityV3PoolSummary info={info} transactionState={transactionState} />
|
||||
case TransactionType.REMOVE_LIQUIDITY_V3:
|
||||
return <RemoveLiquidityV3Summary info={info} transactionState={transactionState} />
|
||||
case TransactionType.WRAP:
|
||||
return <WrapSummary info={info} transactionState={transactionState} />
|
||||
case TransactionType.COLLECT_FEES:
|
||||
return <CollectFeesSummary info={info} transactionState={transactionState} />
|
||||
case TransactionType.APPROVAL:
|
||||
return <ApprovalSummary info={info} transactionState={transactionState} />
|
||||
case TransactionType.CLAIM:
|
||||
return <ClaimSummary info={info} transactionState={transactionState} />
|
||||
default:
|
||||
return <span />
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ const Grid = styled.a`
|
||||
text-decoration: none;
|
||||
border-bottom: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
|
||||
padding: 12px;
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
transition: 250ms background-color ease;
|
||||
}
|
||||
`
|
||||
|
||||
const TextContainer = styled.span`
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
|
||||
import { nativeOnChain } from '../../constants/tokens'
|
||||
|
||||
/**
|
||||
* Event names that can occur in this application.
|
||||
*
|
||||
@@ -41,21 +37,7 @@ export enum CUSTOM_USER_PROPERTIES {
|
||||
SCREEN_RESOLUTION_HEIGHT = 'screen_resolution_height',
|
||||
SCREEN_RESOLUTION_WIDTH = 'screen_resolution_width',
|
||||
WALLET_ADDRESS = 'wallet_address',
|
||||
WALLET_NATIVE_CURRENCY_AMOUNT = 'wallet_native_currency_amount',
|
||||
WALLET_NATIVE_CURRENCY_BALANCE_USD = 'wallet_native_currency_balance_usd',
|
||||
WALLET_TYPE = 'wallet_type',
|
||||
WALLET_USDC_AMOUNT = 'wallet_usdc_amount',
|
||||
WALLET_USDC_BALANCE_USD = 'wallet_usdc_balance_usd',
|
||||
WALLET_WETH_AMOUNT = 'wallet_weth_amount',
|
||||
WALLET_WETH_BALANCE_USD = 'wallet_weth_balance_usd',
|
||||
}
|
||||
|
||||
const ETH = nativeOnChain(1)
|
||||
const USDC = new Token(1, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC')
|
||||
|
||||
export const TOKENS_TO_TRACK = {
|
||||
USDC,
|
||||
WETH: ETH.wrapped,
|
||||
}
|
||||
|
||||
export enum BROWSER {
|
||||
|
||||
@@ -176,23 +176,28 @@ export const ButtonOutlined = styled(BaseButton)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonYellow = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.deprecated_yellow3};
|
||||
color: white;
|
||||
export const ButtonYellow = styled(BaseButton)<{ redesignFlag?: boolean }>`
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentWarningSoft : theme.deprecated_yellow3)};
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentWarning : 'white')};
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.deprecated_yellow3)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.deprecated_yellow3)};
|
||||
box-shadow: ${({ theme, redesignFlag }) => !redesignFlag && `0 0 0 1pt ${theme.deprecated_yellow3}`};
|
||||
background-color: ${({ theme, redesignFlag }) =>
|
||||
redesignFlag ? theme.accentWarningSoft : darken(0.05, theme.deprecated_yellow3)};
|
||||
}
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => darken(0.05, theme.deprecated_yellow3)};
|
||||
background: ${({ theme, redesignFlag }) => redesignFlag && theme.stateOverlayHover};
|
||||
mix-blend-mode: ${({ redesignFlag }) => redesignFlag && 'normal'};
|
||||
background-color: ${({ theme, redesignFlag }) => !redesignFlag && darken(0.05, theme.deprecated_yellow3)};
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.deprecated_yellow3)};
|
||||
background-color: ${({ theme }) => darken(0.1, theme.deprecated_yellow3)};
|
||||
box-shadow: ${({ theme, redesignFlag }) => !redesignFlag && `0 0 0 1pt ${darken(0.1, theme.deprecated_yellow3)}`};
|
||||
background-color: ${({ theme, redesignFlag }) =>
|
||||
redesignFlag ? theme.accentWarningSoft : darken(0.1, theme.deprecated_yellow3)};
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme }) => theme.deprecated_yellow3};
|
||||
opacity: 50%;
|
||||
background-color: ${({ theme, redesignFlag }) =>
|
||||
redesignFlag ? theme.accentWarningSoft : theme.deprecated_yellow3};
|
||||
opacity: ${({ redesignFlag }) => (redesignFlag ? '60%' : '50%')};
|
||||
cursor: auto;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Group } from '@visx/group'
|
||||
import { LinePath } from '@visx/shape'
|
||||
import { CurveFactory } from 'd3'
|
||||
import { radius } from 'd3-curve-circlecorners'
|
||||
import React from 'react'
|
||||
import { ReactNode } from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
@@ -12,7 +11,7 @@ interface LineChartProps<T> {
|
||||
getX: (t: T) => number
|
||||
getY: (t: T) => number
|
||||
marginTop?: number
|
||||
curve?: CurveFactory
|
||||
curve: CurveFactory
|
||||
color?: Color
|
||||
strokeWidth: number
|
||||
children?: ReactNode
|
||||
@@ -37,7 +36,7 @@ function LineChart<T>({
|
||||
<svg width={width} height={height}>
|
||||
<Group top={marginTop}>
|
||||
<LinePath
|
||||
curve={curve ?? radius(0.25)}
|
||||
curve={curve}
|
||||
stroke={color ?? theme.accentAction}
|
||||
strokeWidth={strokeWidth}
|
||||
data={data}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { scaleLinear } from 'd3'
|
||||
import { curveCardinalOpen, scaleLinear } from 'd3'
|
||||
import React from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
|
||||
@@ -37,6 +37,7 @@ function SparklineChart({ width, height }: SparklineChartProps) {
|
||||
data={pricePoints}
|
||||
getX={(p: PricePoint) => timeScale(p.timestamp)}
|
||||
getY={(p: PricePoint) => rdScale(p.value)}
|
||||
curve={curveCardinalOpen.tension(0.9)}
|
||||
marginTop={0}
|
||||
color={isPositive ? theme.accentSuccess : theme.accentFailure}
|
||||
strokeWidth={1.5}
|
||||
|
||||
423
src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx
Normal file
423
src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx
Normal file
@@ -0,0 +1,423 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Lock } from 'react-feather'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
|
||||
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
import { useCurrencyBalance } from '../../state/connection/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { ButtonGray } from '../Button'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { Input as NumericalInput } from '../NumericalInput'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
|
||||
import { FiatValue } from './FiatValue'
|
||||
|
||||
const InputPanel = styled.div<{ hideInput?: boolean; redesignFlag: boolean }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
position: relative;
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
background-color: ${({ theme, redesignFlag, hideInput }) =>
|
||||
redesignFlag ? 'transparent' : hideInput ? 'transparent' : theme.deprecated_bg2};
|
||||
z-index: 1;
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
transition: height 1s ease;
|
||||
will-change: height;
|
||||
`
|
||||
|
||||
const FixedContainer = styled.div<{ redesignFlag: boolean }>`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
border-radius: 20px;
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg2)};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
`
|
||||
|
||||
const Container = styled.div<{ hideInput: boolean; disabled: boolean; redesignFlag: boolean }>`
|
||||
min-height: ${({ redesignFlag }) => redesignFlag && '69px'};
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg0)};
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg1)};
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
${({ theme, hideInput, disabled, redesignFlag }) =>
|
||||
!redesignFlag &&
|
||||
!disabled &&
|
||||
`
|
||||
:focus,
|
||||
:hover {
|
||||
border: 1px solid ${hideInput ? ' transparent' : theme.deprecated_bg3};
|
||||
}
|
||||
`}
|
||||
`
|
||||
|
||||
const CurrencySelect = styled(ButtonGray)<{
|
||||
visible: boolean
|
||||
selected: boolean
|
||||
hideInput?: boolean
|
||||
disabled?: boolean
|
||||
redesignFlag: boolean
|
||||
}>`
|
||||
align-items: center;
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
redesignFlag
|
||||
? selected
|
||||
? theme.backgroundSurface
|
||||
: theme.accentAction
|
||||
: selected
|
||||
? theme.deprecated_bg2
|
||||
: theme.deprecated_primary1};
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')};
|
||||
color: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
cursor: pointer;
|
||||
height: ${({ hideInput, redesignFlag }) => (redesignFlag ? 'unset' : hideInput ? '2.8rem' : '2.4rem')};
|
||||
border-radius: 16px;
|
||||
outline: none;
|
||||
user-select: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
padding: ${({ selected, redesignFlag }) =>
|
||||
redesignFlag ? (selected ? '4px 8px 4px 4px' : '6px 6px 6px 8px') : '0 8px'};
|
||||
gap: ${({ redesignFlag }) => (redesignFlag ? '8px' : '0px')};
|
||||
justify-content: space-between;
|
||||
margin-left: ${({ hideInput }) => (hideInput ? '0' : '12px')};
|
||||
:focus,
|
||||
:hover {
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
selected
|
||||
? redesignFlag
|
||||
? theme.backgroundSurface
|
||||
: theme.deprecated_bg3
|
||||
: darken(0.05, theme.deprecated_primary1)};
|
||||
}
|
||||
visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
|
||||
`
|
||||
const InputCurrencySelect = styled(CurrencySelect)<{ redesignFlag: boolean }>`
|
||||
background-color: ${({ theme, selected, redesignFlag }) =>
|
||||
redesignFlag && (selected ? theme.backgroundModule : theme.accentAction)};
|
||||
:focus,
|
||||
:hover {
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
selected
|
||||
? redesignFlag
|
||||
? theme.backgroundInteractive
|
||||
: theme.deprecated_bg3
|
||||
: darken(0.05, theme.deprecated_primary1)};
|
||||
}
|
||||
`
|
||||
|
||||
const InputRow = styled.div<{ selected: boolean; redesignFlag: boolean }>`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: ${({ selected, redesignFlag }) =>
|
||||
redesignFlag ? '0px' : selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 1rem 1rem'};
|
||||
`
|
||||
|
||||
const LabelRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
padding: 0 1rem 1rem;
|
||||
|
||||
span:hover {
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => darken(0.2, theme.deprecated_text2)};
|
||||
}
|
||||
`
|
||||
|
||||
const FiatRow = styled(LabelRow)<{ redesignFlag: boolean }>`
|
||||
justify-content: flex-end;
|
||||
min-height: ${({ redesignFlag }) => redesignFlag && '32px'};
|
||||
padding: ${({ redesignFlag }) => redesignFlag && '8px 0px'};
|
||||
height: ${({ redesignFlag }) => !redesignFlag && '24px'};
|
||||
`
|
||||
|
||||
const NoBalanceState = styled.div`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
font-weight: 400;
|
||||
justify-content: space-between;
|
||||
padding: 0px 4px 1px 4px;
|
||||
`
|
||||
const NoBalanceDash = styled.span`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
font-variant: small-caps;
|
||||
font-feature-settings: 'pnum' on, 'lnum' on;
|
||||
`
|
||||
|
||||
const Aligner = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledDropDown = styled(DropDown)<{ selected: boolean; redesignFlag: boolean }>`
|
||||
margin: 0 0.25rem 0 0.35rem;
|
||||
height: 35%;
|
||||
margin-left: ${({ redesignFlag }) => redesignFlag && '8px'};
|
||||
|
||||
path {
|
||||
stroke: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
stroke-width: ${({ redesignFlag }) => (redesignFlag ? '2px' : '1.5px')};
|
||||
}
|
||||
`
|
||||
|
||||
const StyledTokenName = styled.span<{ active?: boolean; redesignFlag: boolean }>`
|
||||
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
|
||||
font-size: ${({ active }) => (active ? '18px' : '18px')};
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
|
||||
`
|
||||
|
||||
const StyledBalanceMax = styled.button<{ disabled?: boolean; redesignFlag: boolean }>`
|
||||
background-color: transparent;
|
||||
background-color: ${({ theme, redesignFlag }) => !redesignFlag && theme.deprecated_primary5};
|
||||
border: none;
|
||||
text-transform: ${({ redesignFlag }) => !redesignFlag && 'uppercase'};
|
||||
border-radius: ${({ redesignFlag }) => !redesignFlag && '12px'};
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentAction : theme.deprecated_primary1)};
|
||||
cursor: pointer;
|
||||
font-size: ${({ redesignFlag }) => (redesignFlag ? '14px' : '11px')};
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
|
||||
margin-left: ${({ redesignFlag }) => (redesignFlag ? '0px' : '0.25rem')};
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
padding: 4px 6px;
|
||||
pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
|
||||
|
||||
:hover {
|
||||
opacity: ${({ disabled }) => (!disabled ? 0.8 : 0.4)};
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline: none;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean; redesignFlag: boolean }>`
|
||||
${loadingOpacityMixin};
|
||||
text-align: left;
|
||||
font-variant: ${({ redesignFlag }) => redesignFlag && 'small-caps'};
|
||||
font-feature-settings: ${({ redesignFlag }) => redesignFlag && 'pnum on, lnum on'};
|
||||
`
|
||||
|
||||
interface SwapCurrencyInputPanelProps {
|
||||
value: string
|
||||
onUserInput: (value: string) => void
|
||||
onMax?: () => void
|
||||
showMaxButton: boolean
|
||||
label?: ReactNode
|
||||
onCurrencySelect?: (currency: Currency) => void
|
||||
currency?: Currency | null
|
||||
hideBalance?: boolean
|
||||
pair?: Pair | null
|
||||
hideInput?: boolean
|
||||
otherCurrency?: Currency | null
|
||||
fiatValue?: CurrencyAmount<Token> | null
|
||||
priceImpact?: Percent
|
||||
id: string
|
||||
showCommonBases?: boolean
|
||||
showCurrencyAmount?: boolean
|
||||
disableNonToken?: boolean
|
||||
renderBalance?: (amount: CurrencyAmount<Currency>) => ReactNode
|
||||
locked?: boolean
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
export default function SwapCurrencyInputPanel({
|
||||
value,
|
||||
onUserInput,
|
||||
onMax,
|
||||
showMaxButton,
|
||||
onCurrencySelect,
|
||||
currency,
|
||||
otherCurrency,
|
||||
id,
|
||||
showCommonBases,
|
||||
showCurrencyAmount,
|
||||
disableNonToken,
|
||||
renderBalance,
|
||||
fiatValue,
|
||||
priceImpact,
|
||||
hideBalance = false,
|
||||
pair = null, // used for double token logo
|
||||
hideInput = false,
|
||||
locked = false,
|
||||
loading = false,
|
||||
...rest
|
||||
}: SwapCurrencyInputPanelProps) {
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const { account, chainId } = useWeb3React()
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
|
||||
const theme = useTheme()
|
||||
const { pathname } = useLocation()
|
||||
const isAddLiquidityPage = pathname.includes('/add') && !pathname.includes('/add/v2')
|
||||
|
||||
const handleDismissSearch = useCallback(() => {
|
||||
setModalOpen(false)
|
||||
}, [setModalOpen])
|
||||
|
||||
const chainAllowed = isSupportedChain(chainId)
|
||||
|
||||
return (
|
||||
<InputPanel id={id} hideInput={hideInput} {...rest} redesignFlag={redesignFlagEnabled}>
|
||||
{locked && (
|
||||
<FixedContainer redesignFlag={redesignFlagEnabled}>
|
||||
<AutoColumn gap="sm" justify="center">
|
||||
<Lock />
|
||||
<ThemedText.DeprecatedLabel fontSize="12px" textAlign="center" padding="0 12px">
|
||||
<Trans>The market price is outside your specified price range. Single-asset deposit only.</Trans>
|
||||
</ThemedText.DeprecatedLabel>
|
||||
</AutoColumn>
|
||||
</FixedContainer>
|
||||
)}
|
||||
<Container hideInput={hideInput} disabled={!chainAllowed} redesignFlag={redesignFlagEnabled}>
|
||||
<InputRow
|
||||
style={hideInput ? { padding: '0', borderRadius: '8px' } : {}}
|
||||
selected={!onCurrencySelect}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
>
|
||||
{!hideInput && (
|
||||
<StyledNumericalInput
|
||||
className="token-amount-input"
|
||||
value={value}
|
||||
onUserInput={onUserInput}
|
||||
disabled={!chainAllowed}
|
||||
$loading={loading}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
/>
|
||||
)}
|
||||
|
||||
<InputCurrencySelect
|
||||
disabled={!chainAllowed}
|
||||
visible={currency !== undefined}
|
||||
selected={!!currency}
|
||||
hideInput={hideInput}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
className="open-currency-select-button"
|
||||
onClick={() => {
|
||||
if (onCurrencySelect) {
|
||||
setModalOpen(true)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Aligner>
|
||||
<RowFixed>
|
||||
{pair ? (
|
||||
<span style={{ marginRight: '0.5rem' }}>
|
||||
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
|
||||
</span>
|
||||
) : currency ? (
|
||||
<CurrencyLogo style={{ marginRight: '2px' }} currency={currency} size={'24px'} />
|
||||
) : null}
|
||||
{pair ? (
|
||||
<StyledTokenName className="pair-name-container" redesignFlag={redesignFlagEnabled}>
|
||||
{pair?.token0.symbol}:{pair?.token1.symbol}
|
||||
</StyledTokenName>
|
||||
) : (
|
||||
<StyledTokenName
|
||||
className="token-symbol-container"
|
||||
active={Boolean(currency && currency.symbol)}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
>
|
||||
{(currency && currency.symbol && currency.symbol.length > 20
|
||||
? currency.symbol.slice(0, 4) +
|
||||
'...' +
|
||||
currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length)
|
||||
: currency?.symbol) || <Trans>Select token</Trans>}
|
||||
</StyledTokenName>
|
||||
)}
|
||||
</RowFixed>
|
||||
{onCurrencySelect && <StyledDropDown selected={!!currency} redesignFlag={redesignFlagEnabled} />}
|
||||
</Aligner>
|
||||
</InputCurrencySelect>
|
||||
</InputRow>
|
||||
{redesignFlagEnabled && !currency && !isAddLiquidityPage && (
|
||||
<NoBalanceState>
|
||||
<FiatRow redesignFlag={redesignFlagEnabled}>
|
||||
<RowBetween>
|
||||
<NoBalanceDash>-</NoBalanceDash>
|
||||
<NoBalanceDash>-</NoBalanceDash>
|
||||
</RowBetween>
|
||||
</FiatRow>
|
||||
</NoBalanceState>
|
||||
)}
|
||||
{!hideInput && !hideBalance && currency && (
|
||||
<FiatRow redesignFlag={redesignFlagEnabled}>
|
||||
<RowBetween>
|
||||
<LoadingOpacityContainer $loading={loading}>
|
||||
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
|
||||
</LoadingOpacityContainer>
|
||||
{account ? (
|
||||
<RowFixed style={{ height: '17px' }}>
|
||||
<ThemedText.DeprecatedBody
|
||||
onClick={onMax}
|
||||
color={theme.deprecated_text3}
|
||||
fontWeight={500}
|
||||
fontSize={14}
|
||||
style={{ display: 'inline', cursor: 'pointer' }}
|
||||
>
|
||||
{!hideBalance && currency && selectedCurrencyBalance ? (
|
||||
renderBalance ? (
|
||||
renderBalance(selectedCurrencyBalance)
|
||||
) : (
|
||||
<Trans>Balance: {formatCurrencyAmount(selectedCurrencyBalance, 4)}</Trans>
|
||||
)
|
||||
) : null}
|
||||
</ThemedText.DeprecatedBody>
|
||||
{showMaxButton && selectedCurrencyBalance ? (
|
||||
<TraceEvent
|
||||
events={[Event.onClick]}
|
||||
name={EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED}
|
||||
element={ElementName.MAX_TOKEN_AMOUNT_BUTTON}
|
||||
>
|
||||
<StyledBalanceMax onClick={onMax} redesignFlag={redesignFlagEnabled}>
|
||||
<Trans>Max</Trans>
|
||||
</StyledBalanceMax>
|
||||
</TraceEvent>
|
||||
) : null}
|
||||
</RowFixed>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
</RowBetween>
|
||||
</FiatRow>
|
||||
)}
|
||||
</Container>
|
||||
{onCurrencySelect && (
|
||||
<CurrencySearchModal
|
||||
isOpen={modalOpen}
|
||||
onDismiss={handleDismissSearch}
|
||||
onCurrencySelect={onCurrencySelect}
|
||||
selectedCurrency={currency}
|
||||
otherSelectedCurrency={otherCurrency}
|
||||
showCommonBases={showCommonBases}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
disableNonToken={disableNonToken}
|
||||
/>
|
||||
)}
|
||||
</InputPanel>
|
||||
)
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Lock } from 'react-feather'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
|
||||
@@ -26,37 +25,35 @@ import { RowBetween, RowFixed } from '../Row'
|
||||
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
|
||||
import { FiatValue } from './FiatValue'
|
||||
|
||||
const InputPanel = styled.div<{ hideInput?: boolean; redesignFlag: boolean }>`
|
||||
const InputPanel = styled.div<{ hideInput?: boolean }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
position: relative;
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
background-color: ${({ theme, redesignFlag, hideInput }) =>
|
||||
redesignFlag ? 'transparent' : hideInput ? 'transparent' : theme.deprecated_bg2};
|
||||
background-color: ${({ theme, hideInput }) => (hideInput ? 'transparent' : theme.deprecated_bg2)};
|
||||
z-index: 1;
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
transition: height 1s ease;
|
||||
will-change: height;
|
||||
`
|
||||
|
||||
const FixedContainer = styled.div<{ redesignFlag: boolean }>`
|
||||
const FixedContainer = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
border-radius: 20px;
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg2)};
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
`
|
||||
|
||||
const Container = styled.div<{ hideInput: boolean; disabled: boolean; redesignFlag: boolean }>`
|
||||
const Container = styled.div<{ hideInput: boolean; disabled: boolean }>`
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg0)};
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg1)};
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg0};
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
${({ theme, hideInput, disabled, redesignFlag }) =>
|
||||
!redesignFlag &&
|
||||
${({ theme, hideInput, disabled }) =>
|
||||
!disabled &&
|
||||
`
|
||||
:focus,
|
||||
@@ -71,65 +68,38 @@ const CurrencySelect = styled(ButtonGray)<{
|
||||
selected: boolean
|
||||
hideInput?: boolean
|
||||
disabled?: boolean
|
||||
redesignFlag: boolean
|
||||
}>`
|
||||
align-items: center;
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
redesignFlag
|
||||
? selected
|
||||
? theme.backgroundSurface
|
||||
: theme.accentAction
|
||||
: selected
|
||||
? theme.deprecated_bg2
|
||||
: theme.deprecated_primary1};
|
||||
background-color: ${({ selected, theme }) => (selected ? theme.deprecated_bg2 : theme.deprecated_primary1)};
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')};
|
||||
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
|
||||
color: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
cursor: pointer;
|
||||
height: ${({ hideInput, redesignFlag }) => (redesignFlag ? 'unset' : hideInput ? '2.8rem' : '2.4rem')};
|
||||
border-radius: 16px;
|
||||
outline: none;
|
||||
user-select: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
font-weight: 500;
|
||||
height: ${({ hideInput }) => (hideInput ? '2.8rem' : '2.4rem')};
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
padding: ${({ selected, redesignFlag }) =>
|
||||
redesignFlag ? (selected ? '4px 8px 4px 4px' : '6px 6px 6px 8px') : '0 8px'};
|
||||
gap: ${({ redesignFlag }) => (redesignFlag ? '8px' : '0px')};
|
||||
padding: 0 8px;
|
||||
justify-content: space-between;
|
||||
margin-left: ${({ hideInput }) => (hideInput ? '0' : '12px')};
|
||||
:focus,
|
||||
:hover {
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
selected
|
||||
? redesignFlag
|
||||
? theme.backgroundSurface
|
||||
: theme.deprecated_bg3
|
||||
: darken(0.05, theme.deprecated_primary1)};
|
||||
background-color: ${({ selected, theme }) =>
|
||||
selected ? theme.deprecated_bg3 : darken(0.05, theme.deprecated_primary1)};
|
||||
}
|
||||
visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
|
||||
`
|
||||
const InputCurrencySelect = styled(CurrencySelect)<{ redesignFlag: boolean }>`
|
||||
background-color: ${({ theme, selected, redesignFlag }) =>
|
||||
redesignFlag && (selected ? theme.backgroundModule : theme.accentAction)};
|
||||
:focus,
|
||||
:hover {
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
selected
|
||||
? redesignFlag
|
||||
? theme.backgroundInteractive
|
||||
: theme.deprecated_bg3
|
||||
: darken(0.05, theme.deprecated_primary1)};
|
||||
}
|
||||
`
|
||||
|
||||
const InputRow = styled.div<{ selected: boolean; redesignFlag: boolean }>`
|
||||
const InputRow = styled.div<{ selected: boolean }>`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: ${({ selected, redesignFlag }) =>
|
||||
redesignFlag ? '0px' : selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 1rem 1rem'};
|
||||
padding: ${({ selected }) => (selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 1rem 1rem')};
|
||||
`
|
||||
|
||||
const LabelRow = styled.div`
|
||||
@@ -139,7 +109,6 @@ const LabelRow = styled.div`
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
padding: 0 1rem 1rem;
|
||||
|
||||
span:hover {
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => darken(0.2, theme.deprecated_text2)};
|
||||
@@ -148,20 +117,8 @@ const LabelRow = styled.div`
|
||||
|
||||
const FiatRow = styled(LabelRow)<{ redesignFlag: boolean }>`
|
||||
justify-content: flex-end;
|
||||
padding: ${({ redesignFlag }) => redesignFlag && '8px 0px'};
|
||||
height: ${({ redesignFlag }) => !redesignFlag && '24px'};
|
||||
`
|
||||
|
||||
const NoBalanceState = styled.div`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
font-weight: 400;
|
||||
justify-content: space-between;
|
||||
padding: 0px 4px;
|
||||
`
|
||||
const NoBalanceDash = styled.span`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
font-variant: small-caps;
|
||||
font-feature-settings: 'pnum' on, 'lnum' on;
|
||||
padding: ${({ redesignFlag }) => redesignFlag && '0px 1rem 0.75rem'};
|
||||
height: ${({ redesignFlag }) => (redesignFlag ? '32px' : '16px')};
|
||||
`
|
||||
|
||||
const Aligner = styled.span`
|
||||
@@ -171,34 +128,31 @@ const Aligner = styled.span`
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledDropDown = styled(DropDown)<{ selected: boolean; redesignFlag: boolean }>`
|
||||
const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
|
||||
margin: 0 0.25rem 0 0.35rem;
|
||||
height: 35%;
|
||||
margin-left: ${({ redesignFlag }) => redesignFlag && '8px'};
|
||||
|
||||
path {
|
||||
stroke: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
stroke-width: ${({ redesignFlag }) => (redesignFlag ? '2px' : '1.5px')};
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledTokenName = styled.span<{ active?: boolean; redesignFlag: boolean }>`
|
||||
const StyledTokenName = styled.span<{ active?: boolean }>`
|
||||
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
|
||||
font-size: ${({ active }) => (active ? '18px' : '18px')};
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
|
||||
`
|
||||
|
||||
const StyledBalanceMax = styled.button<{ disabled?: boolean; redesignFlag: boolean }>`
|
||||
const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
|
||||
background-color: transparent;
|
||||
background-color: ${({ theme, redesignFlag }) => !redesignFlag && theme.deprecated_primary5};
|
||||
background-color: ${({ theme }) => theme.deprecated_primary5};
|
||||
border: none;
|
||||
text-transform: ${({ redesignFlag }) => !redesignFlag && 'uppercase'};
|
||||
border-radius: ${({ redesignFlag }) => !redesignFlag && '12px'};
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentAction : theme.deprecated_primary1)};
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.deprecated_primary1};
|
||||
cursor: pointer;
|
||||
font-size: ${({ redesignFlag }) => (redesignFlag ? '14px' : '11px')};
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
|
||||
margin-left: ${({ redesignFlag }) => (redesignFlag ? '0px' : '0.25rem')};
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
margin-left: 0.25rem;
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
padding: 4px 6px;
|
||||
pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
|
||||
@@ -212,11 +166,9 @@ const StyledBalanceMax = styled.button<{ disabled?: boolean; redesignFlag: boole
|
||||
}
|
||||
`
|
||||
|
||||
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean; redesignFlag: boolean }>`
|
||||
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean }>`
|
||||
${loadingOpacityMixin};
|
||||
text-align: left;
|
||||
font-variant: ${({ redesignFlag }) => redesignFlag && 'small-caps'};
|
||||
font-feature-settings: ${({ redesignFlag }) => redesignFlag && 'pnum on, lnum on'};
|
||||
`
|
||||
|
||||
interface CurrencyInputPanelProps {
|
||||
@@ -266,12 +218,10 @@ export default function CurrencyInputPanel({
|
||||
}: CurrencyInputPanelProps) {
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const { account, chainId } = useWeb3React()
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
|
||||
const theme = useTheme()
|
||||
const { pathname } = useLocation()
|
||||
const isAddLiquidityPage = pathname.includes('/add') && !pathname.includes('/add/v2')
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
|
||||
const handleDismissSearch = useCallback(() => {
|
||||
setModalOpen(false)
|
||||
@@ -280,9 +230,9 @@ export default function CurrencyInputPanel({
|
||||
const chainAllowed = isSupportedChain(chainId)
|
||||
|
||||
return (
|
||||
<InputPanel id={id} hideInput={hideInput} {...rest} redesignFlag={redesignFlagEnabled}>
|
||||
<InputPanel id={id} hideInput={hideInput} {...rest}>
|
||||
{locked && (
|
||||
<FixedContainer redesignFlag={redesignFlagEnabled}>
|
||||
<FixedContainer>
|
||||
<AutoColumn gap="sm" justify="center">
|
||||
<Lock />
|
||||
<ThemedText.DeprecatedLabel fontSize="12px" textAlign="center" padding="0 12px">
|
||||
@@ -291,12 +241,8 @@ export default function CurrencyInputPanel({
|
||||
</AutoColumn>
|
||||
</FixedContainer>
|
||||
)}
|
||||
<Container hideInput={hideInput} disabled={!chainAllowed} redesignFlag={redesignFlagEnabled}>
|
||||
<InputRow
|
||||
style={hideInput ? { padding: '0', borderRadius: '8px' } : {}}
|
||||
selected={!onCurrencySelect}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
>
|
||||
<Container hideInput={hideInput} disabled={!chainAllowed}>
|
||||
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={!onCurrencySelect}>
|
||||
{!hideInput && (
|
||||
<StyledNumericalInput
|
||||
className="token-amount-input"
|
||||
@@ -304,16 +250,14 @@ export default function CurrencyInputPanel({
|
||||
onUserInput={onUserInput}
|
||||
disabled={!chainAllowed}
|
||||
$loading={loading}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
/>
|
||||
)}
|
||||
|
||||
<InputCurrencySelect
|
||||
<CurrencySelect
|
||||
disabled={!chainAllowed}
|
||||
visible={currency !== undefined}
|
||||
selected={!!currency}
|
||||
hideInput={hideInput}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
className="open-currency-select-button"
|
||||
onClick={() => {
|
||||
if (onCurrencySelect) {
|
||||
@@ -328,40 +272,26 @@ export default function CurrencyInputPanel({
|
||||
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
|
||||
</span>
|
||||
) : currency ? (
|
||||
<CurrencyLogo style={{ marginRight: '2px' }} currency={currency} size={'24px'} />
|
||||
<CurrencyLogo style={{ marginRight: '0.5rem' }} currency={currency} size={'24px'} />
|
||||
) : null}
|
||||
{pair ? (
|
||||
<StyledTokenName className="pair-name-container" redesignFlag={redesignFlagEnabled}>
|
||||
<StyledTokenName className="pair-name-container">
|
||||
{pair?.token0.symbol}:{pair?.token1.symbol}
|
||||
</StyledTokenName>
|
||||
) : (
|
||||
<StyledTokenName
|
||||
className="token-symbol-container"
|
||||
active={Boolean(currency && currency.symbol)}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
>
|
||||
<StyledTokenName className="token-symbol-container" active={Boolean(currency && currency.symbol)}>
|
||||
{(currency && currency.symbol && currency.symbol.length > 20
|
||||
? currency.symbol.slice(0, 4) +
|
||||
'...' +
|
||||
currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length)
|
||||
: currency?.symbol) || <Trans>Select token</Trans>}
|
||||
: currency?.symbol) || <Trans>Select a token</Trans>}
|
||||
</StyledTokenName>
|
||||
)}
|
||||
</RowFixed>
|
||||
{onCurrencySelect && <StyledDropDown selected={!!currency} redesignFlag={redesignFlagEnabled} />}
|
||||
{onCurrencySelect && <StyledDropDown selected={!!currency} />}
|
||||
</Aligner>
|
||||
</InputCurrencySelect>
|
||||
</CurrencySelect>
|
||||
</InputRow>
|
||||
{redesignFlagEnabled && !currency && !isAddLiquidityPage && (
|
||||
<NoBalanceState>
|
||||
<FiatRow redesignFlag={redesignFlagEnabled}>
|
||||
<RowBetween>
|
||||
<NoBalanceDash>-</NoBalanceDash>
|
||||
<NoBalanceDash>-</NoBalanceDash>
|
||||
</RowBetween>
|
||||
</FiatRow>
|
||||
</NoBalanceState>
|
||||
)}
|
||||
{!hideInput && !hideBalance && currency && (
|
||||
<FiatRow redesignFlag={redesignFlagEnabled}>
|
||||
<RowBetween>
|
||||
@@ -391,8 +321,8 @@ export default function CurrencyInputPanel({
|
||||
name={EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED}
|
||||
element={ElementName.MAX_TOKEN_AMOUNT_BUTTON}
|
||||
>
|
||||
<StyledBalanceMax onClick={onMax} redesignFlag={redesignFlagEnabled}>
|
||||
<Trans>Max</Trans>
|
||||
<StyledBalanceMax onClick={onMax}>
|
||||
<Trans>MAX</Trans>
|
||||
</StyledBalanceMax>
|
||||
</TraceEvent>
|
||||
) : null}
|
||||
|
||||
@@ -24,11 +24,13 @@ const StyledNativeLogo = styled(StyledLogo)`
|
||||
|
||||
export default function CurrencyLogo({
|
||||
currency,
|
||||
symbol,
|
||||
size = '24px',
|
||||
style,
|
||||
...rest
|
||||
}: {
|
||||
currency?: Currency | null
|
||||
symbol?: string | null
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
}) {
|
||||
@@ -36,6 +38,7 @@ export default function CurrencyLogo({
|
||||
alt: `${currency?.symbol ?? 'token'} logo`,
|
||||
size,
|
||||
srcs: useCurrencyLogoURIs(currency),
|
||||
symbol: symbol ?? currency?.symbol,
|
||||
style,
|
||||
...rest,
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ interface DoubleCurrencyLogoProps {
|
||||
}
|
||||
|
||||
const HigherLogo = styled(CurrencyLogo)`
|
||||
z-index: 2;
|
||||
z-index: 1;
|
||||
`
|
||||
const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>`
|
||||
position: absolute;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { TokensVariant, useTokensFlag } from 'featureFlags/flags/tokens'
|
||||
import { TokenSafetyVariant, useTokenSafetyFlag } from 'featureFlags/flags/tokenSafety'
|
||||
import { TokensNetworkFilterVariant, useTokensNetworkFilterFlag } from 'featureFlags/flags/tokensNetworkFilter'
|
||||
import { useWalletFlag, WalletVariant } from 'featureFlags/flags/wallet'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { useModalIsOpen, useToggleFeatureFlags } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
@@ -46,7 +45,14 @@ const Row = styled.div`
|
||||
|
||||
const CloseButton = styled.button`
|
||||
cursor: pointer;
|
||||
background: 'transparent';
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
|
||||
const ToggleButton = styled.button`
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
@@ -60,7 +66,6 @@ const Header = styled(Row)`
|
||||
const FlagName = styled.span`
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
padding-left: 8px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
const FlagGroupName = styled.span`
|
||||
@@ -116,17 +121,53 @@ function Variant({ option }: { option: string }) {
|
||||
return <option value={option}>{option}</option>
|
||||
}
|
||||
|
||||
function FeatureFlagOption({
|
||||
variants,
|
||||
featureFlag,
|
||||
value,
|
||||
label,
|
||||
}: {
|
||||
variants: string[]
|
||||
interface FeatureFlagProps {
|
||||
variant: Record<string, string>
|
||||
featureFlag: FeatureFlag
|
||||
value: string
|
||||
label: string
|
||||
}) {
|
||||
}
|
||||
|
||||
function FeatureFlagGroup({ name, children }: PropsWithChildren<{ name: string }>) {
|
||||
// type FeatureFlagOption = { props: FeatureFlagProps }
|
||||
const togglableOptions = Children.toArray(children)
|
||||
.filter<ReactElement<FeatureFlagProps>>(
|
||||
(child): child is ReactElement<FeatureFlagProps> =>
|
||||
child instanceof Object && 'type' in child && child.type === FeatureFlagOption
|
||||
)
|
||||
.map(({ props }) => props)
|
||||
.filter(({ variant }) => {
|
||||
const values = Object.values(variant)
|
||||
return values.includes(BaseVariant.Control) && values.includes(BaseVariant.Enabled)
|
||||
})
|
||||
|
||||
const setFeatureFlags = useUpdateAtom(featureFlagSettings)
|
||||
const allEnabled = togglableOptions.every(({ value }) => value === BaseVariant.Enabled)
|
||||
const onToggle = useCallback(() => {
|
||||
setFeatureFlags((flags) => ({
|
||||
...flags,
|
||||
...togglableOptions.reduce(
|
||||
(flags, { featureFlag }) => ({
|
||||
...flags,
|
||||
[featureFlag]: allEnabled ? BaseVariant.Control : BaseVariant.Enabled,
|
||||
}),
|
||||
{}
|
||||
),
|
||||
}))
|
||||
}, [allEnabled, setFeatureFlags, togglableOptions])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row key={name}>
|
||||
<FlagGroupName>{name}</FlagGroupName>
|
||||
<ToggleButton onClick={onToggle}>{allEnabled ? 'Disable' : 'Enable'} group</ToggleButton>
|
||||
</Row>
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function FeatureFlagOption({ variant, featureFlag, value, label }: FeatureFlagProps) {
|
||||
const updateFlag = useUpdateFlag()
|
||||
const [count, setCount] = useState(0)
|
||||
const featureFlags = useAtomValue(featureFlagSettings)
|
||||
@@ -145,7 +186,7 @@ function FeatureFlagOption({
|
||||
}}
|
||||
value={featureFlags[featureFlag]}
|
||||
>
|
||||
{variants.map((variant) => (
|
||||
{Object.values(variant).map((variant) => (
|
||||
<Variant key={variant} option={variant} />
|
||||
))}
|
||||
</FlagVariantSelection>
|
||||
@@ -165,51 +206,42 @@ export default function FeatureFlagModal() {
|
||||
<X size={24} />
|
||||
</CloseButton>
|
||||
</Header>
|
||||
<FlagGroupName>Phase 1</FlagGroupName>
|
||||
<FeatureFlagOption
|
||||
variants={Object.values(NftVariant)}
|
||||
value={useNftFlag()}
|
||||
featureFlag={FeatureFlag.nft}
|
||||
label="NFTs"
|
||||
/>
|
||||
<FlagGroupName>Phase 0</FlagGroupName>
|
||||
<FeatureFlagOption
|
||||
variants={Object.values(RedesignVariant)}
|
||||
value={useRedesignFlag()}
|
||||
featureFlag={FeatureFlag.redesign}
|
||||
label="Redesign"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variants={Object.values(NavBarVariant)}
|
||||
value={useNavBarFlag()}
|
||||
featureFlag={FeatureFlag.navBar}
|
||||
label="NavBar"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variants={Object.values(TokensVariant)}
|
||||
value={useTokensFlag()}
|
||||
featureFlag={FeatureFlag.tokens}
|
||||
label="Tokens"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variants={Object.values(TokensNetworkFilterVariant)}
|
||||
value={useTokensNetworkFilterFlag()}
|
||||
featureFlag={FeatureFlag.tokensNetworkFilter}
|
||||
label="Tokens Network Filter"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variants={Object.values(TokenSafetyVariant)}
|
||||
value={useTokenSafetyFlag()}
|
||||
featureFlag={FeatureFlag.tokenSafety}
|
||||
label="Token Safety"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variants={Object.values(WalletVariant)}
|
||||
value={useWalletFlag()}
|
||||
featureFlag={FeatureFlag.wallet}
|
||||
label="Wallet Flag"
|
||||
/>
|
||||
<SaveButton onClick={() => window.location.reload()}>Save Settings</SaveButton>
|
||||
<FeatureFlagGroup name="Phase 0">
|
||||
<FeatureFlagOption
|
||||
variant={RedesignVariant}
|
||||
value={useRedesignFlag()}
|
||||
featureFlag={FeatureFlag.redesign}
|
||||
label="Redesign"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={NavBarVariant}
|
||||
value={useNavBarFlag()}
|
||||
featureFlag={FeatureFlag.navBar}
|
||||
label="NavBar"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={TokensVariant}
|
||||
value={useTokensFlag()}
|
||||
featureFlag={FeatureFlag.tokens}
|
||||
label="Tokens"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={TokensNetworkFilterVariant}
|
||||
value={useTokensNetworkFilterFlag()}
|
||||
featureFlag={FeatureFlag.tokensNetworkFilter}
|
||||
label="Tokens Network Filter"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={TokenSafetyVariant}
|
||||
value={useTokenSafetyFlag()}
|
||||
featureFlag={FeatureFlag.tokenSafety}
|
||||
label="Token Safety"
|
||||
/>
|
||||
</FeatureFlagGroup>
|
||||
<FeatureFlagGroup name="Phase 1">
|
||||
<FeatureFlagOption variant={NftVariant} value={useNftFlag()} featureFlag={FeatureFlag.nft} label="NFTs" />
|
||||
</FeatureFlagGroup>
|
||||
<SaveButton onClick={() => window.location.reload()}>Reload</SaveButton>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const ResponsiveText = styled(ThemedText.DeprecatedLabel)`
|
||||
line-height: 16px;
|
||||
font-size: 14px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
`};
|
||||
|
||||
@@ -39,7 +39,7 @@ const Wrapper = styled.div`
|
||||
padding: 16px 20px;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToMedium}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -11,7 +11,6 @@ import { useCloseModal, useModalIsOpen, useOpenModal, useToggleModal } from 'sta
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
import { isChainAllowed } from 'utils/switchChain'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
|
||||
const ActiveRowLinkList = styled.div`
|
||||
@@ -53,7 +52,7 @@ const FlyoutMenu = styled.div`
|
||||
width: 272px;
|
||||
z-index: 99;
|
||||
padding-top: 10px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
top: 40px;
|
||||
}
|
||||
`
|
||||
@@ -112,7 +111,7 @@ const NetworkLabel = styled.div`
|
||||
`
|
||||
const SelectorLabel = styled(NetworkLabel)`
|
||||
display: none;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
display: block;
|
||||
margin-right: 8px;
|
||||
}
|
||||
@@ -126,7 +125,7 @@ const NetworkAlertLabel = styled(NetworkLabel)`
|
||||
font-size: 1rem;
|
||||
width: fit-content;
|
||||
font-weight: 500;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
@@ -153,12 +152,12 @@ const SelectorControls = styled.div<{ supportedChain: boolean }>`
|
||||
}
|
||||
`
|
||||
const SelectorLogo = styled(Logo)`
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
`
|
||||
const SelectorWrapper = styled.div`
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
position: relative;
|
||||
}
|
||||
`
|
||||
@@ -328,18 +327,16 @@ export default function NetworkSelector() {
|
||||
<FlyoutHeader>
|
||||
<Trans>Select a {!onSupportedChain ? ' supported ' : ''}network</Trans>
|
||||
</FlyoutHeader>
|
||||
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) =>
|
||||
isChainAllowed(chainId) ? (
|
||||
<Row
|
||||
onSelectChain={async (targetChainId: SupportedChainId) => {
|
||||
await selectChain(targetChainId)
|
||||
closeModal()
|
||||
}}
|
||||
targetChain={chainId}
|
||||
key={chainId}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) => (
|
||||
<Row
|
||||
onSelectChain={async (targetChainId: SupportedChainId) => {
|
||||
await selectChain(targetChainId)
|
||||
closeModal()
|
||||
}}
|
||||
targetChain={chainId}
|
||||
key={chainId}
|
||||
/>
|
||||
))}
|
||||
</FlyoutMenuContents>
|
||||
</FlyoutMenu>
|
||||
)}
|
||||
|
||||
@@ -26,7 +26,7 @@ const StyledPolling = styled.div<{ warning: boolean }>`
|
||||
color: ${({ theme, warning }) => (warning ? theme.deprecated_yellow3 : theme.deprecated_green1)};
|
||||
transition: 250ms ease color;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
display: none;
|
||||
`}
|
||||
`
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useScrollPosition from '@react-hook/window-scroll'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import WalletDropdown from 'components/WalletDropdown'
|
||||
import { getChainInfoOrDefault } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { TokensVariant, useTokensFlag } from 'featureFlags/flags/tokens'
|
||||
import { useWalletFlag, WalletVariant } from 'featureFlags/flags/wallet'
|
||||
import { darken } from 'polished'
|
||||
import { NavLink, useLocation } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
@@ -48,16 +46,16 @@ const HeaderFrame = styled.div<{ showBackground: boolean }>`
|
||||
transition: background-position 0.1s, box-shadow 0.1s;
|
||||
background-blend-mode: hard-light;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToLarge`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToLarge`
|
||||
grid-template-columns: 48px 1fr 1fr;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
padding: 1rem;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
padding: 1rem;
|
||||
grid-template-columns: 36px 1fr;
|
||||
`};
|
||||
@@ -83,7 +81,7 @@ const HeaderElement = styled.div`
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
align-items: center;
|
||||
`};
|
||||
`
|
||||
@@ -99,13 +97,13 @@ const HeaderLinks = styled(Row)`
|
||||
grid-gap: 10px;
|
||||
overflow: auto;
|
||||
align-items: center;
|
||||
${({ theme }) => theme.mediaWidth.upToLarge`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToLarge`
|
||||
justify-self: start;
|
||||
`};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
justify-self: center;
|
||||
`};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
justify-self: center;
|
||||
@@ -159,7 +157,7 @@ const UNIWrapper = styled.span`
|
||||
`
|
||||
|
||||
const BalanceText = styled(Text)`
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToExtraSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
@@ -170,7 +168,7 @@ const Title = styled.a`
|
||||
pointer-events: auto;
|
||||
justify-self: flex-start;
|
||||
margin-right: 12px;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
justify-self: center;
|
||||
`};
|
||||
:hover {
|
||||
@@ -219,12 +217,6 @@ const StyledNavLink = styled(NavLink)`
|
||||
}
|
||||
`
|
||||
|
||||
const WalletDropdownWrapper = styled.div`
|
||||
position: absolute;
|
||||
top: 75px;
|
||||
right: 20px;
|
||||
`
|
||||
|
||||
const StyledExternalLink = styled(ExternalLink)`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: left;
|
||||
@@ -252,7 +244,6 @@ const StyledExternalLink = styled(ExternalLink)`
|
||||
`
|
||||
|
||||
export default function Header() {
|
||||
const walletFlag = useWalletFlag()
|
||||
const tokensFlag = useTokensFlag()
|
||||
|
||||
const { account, chainId } = useWeb3React()
|
||||
@@ -355,11 +346,6 @@ export default function Header() {
|
||||
) : null}
|
||||
<Web3Status />
|
||||
</AccountElement>
|
||||
{walletFlag === WalletVariant.Enabled && (
|
||||
<WalletDropdownWrapper>
|
||||
<WalletDropdown />
|
||||
</WalletDropdownWrapper>
|
||||
)}
|
||||
</HeaderElement>
|
||||
<HeaderElement>
|
||||
<Menu />
|
||||
|
||||
32
src/components/Icons/index.tsx
Normal file
32
src/components/Icons/index.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { ChevronDown, ChevronUp } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
export const StyledChevronDown = styled(ChevronDown)<{ customColor?: string }>`
|
||||
color: ${({ theme, customColor }) => customColor ?? theme.textSecondary};
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.accentActionSoft};
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.fast}ms color ${timing.in}`};
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledChevronUp = styled(ChevronUp)<{ customColor?: string }>`
|
||||
color: ${({ theme, customColor }) => customColor ?? theme.textSecondary};
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.accentActionSoft};
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.fast}ms color ${timing.in}`};
|
||||
}
|
||||
`
|
||||
@@ -1,11 +1,18 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ConnectionType } from 'connection'
|
||||
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import styled from 'styled-components/macro'
|
||||
import { colors } from 'theme/colors'
|
||||
|
||||
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
|
||||
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
|
||||
import sockImg from '../../assets/svg/socks.svg'
|
||||
import { useHasSocks } from '../../hooks/useSocksBalance'
|
||||
import Identicon from '../Identicon'
|
||||
|
||||
const IconWrapper = styled.div<{ size?: number }>`
|
||||
position: relative;
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -15,24 +22,62 @@ const IconWrapper = styled.div<{ size?: number }>`
|
||||
height: ${({ size }) => (size ? size + 'px' : '32px')};
|
||||
width: ${({ size }) => (size ? size + 'px' : '32px')};
|
||||
}
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
align-items: flex-end;
|
||||
`};
|
||||
`
|
||||
|
||||
export default function StatusIcon({ connectionType, size }: { connectionType: ConnectionType; size?: number }) {
|
||||
let image
|
||||
switch (connectionType) {
|
||||
case ConnectionType.INJECTED:
|
||||
image = <Identicon />
|
||||
break
|
||||
case ConnectionType.WALLET_CONNECT:
|
||||
image = <img src={WalletConnectIcon} alt="WalletConnect" />
|
||||
break
|
||||
case ConnectionType.COINBASE_WALLET:
|
||||
image = <img src={CoinbaseWalletIcon} alt="Coinbase Wallet" />
|
||||
break
|
||||
const SockContainer = styled.div`
|
||||
position: absolute;
|
||||
background-color: ${colors.pink400};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
bottom: -5px;
|
||||
right: -5px;
|
||||
`
|
||||
|
||||
const SockImg = styled.img`
|
||||
width: 7.5px;
|
||||
height: 10px;
|
||||
margin-top: 3px;
|
||||
`
|
||||
|
||||
const Socks = () => {
|
||||
return (
|
||||
<SockContainer>
|
||||
<SockImg src={sockImg} />
|
||||
</SockContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const useIcon = (connectionType: ConnectionType) => {
|
||||
const { account } = useWeb3React()
|
||||
const { avatar } = useENSAvatar(account ?? undefined)
|
||||
const isNavbarEnabled = useNavBarFlag() === NavBarVariant.Enabled
|
||||
|
||||
if ((isNavbarEnabled && avatar) || connectionType === ConnectionType.INJECTED) {
|
||||
return <Identicon />
|
||||
} else if (connectionType === ConnectionType.WALLET_CONNECT) {
|
||||
return <img src={WalletConnectIcon} alt="WalletConnect" />
|
||||
} else if (connectionType === ConnectionType.COINBASE_WALLET) {
|
||||
return <img src={CoinbaseWalletIcon} alt="Coinbase Wallet" />
|
||||
}
|
||||
|
||||
return <IconWrapper size={size ?? 16}>{image}</IconWrapper>
|
||||
return undefined
|
||||
}
|
||||
|
||||
export default function StatusIcon({ connectionType, size }: { connectionType: ConnectionType; size?: number }) {
|
||||
const hasSocks = useHasSocks()
|
||||
const isNavbarEnabled = useNavBarFlag() === NavBarVariant.Enabled
|
||||
const icon = useIcon(connectionType)
|
||||
|
||||
return (
|
||||
<IconWrapper size={size ?? 16}>
|
||||
{isNavbarEnabled && hasSocks && <Socks />}
|
||||
{icon}
|
||||
</IconWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import jazzicon from '@metamask/jazzicon'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const StyledIdenticon = styled.div`
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
const StyledIdenticon = styled.div<{ isNavbarEnabled: boolean }>`
|
||||
height: ${({ isNavbarEnabled }) => (isNavbarEnabled ? '24px' : '1rem')};
|
||||
width: ${({ isNavbarEnabled }) => (isNavbarEnabled ? '24px' : '1rem')};
|
||||
border-radius: 1.125rem;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg4};
|
||||
font-size: initial;
|
||||
@@ -22,8 +23,10 @@ export default function Identicon() {
|
||||
const { account } = useWeb3React()
|
||||
const { avatar } = useENSAvatar(account ?? undefined)
|
||||
const [fetchable, setFetchable] = useState(true)
|
||||
const isNavbarEnabled = useNavBarFlag() === NavBarVariant.Enabled
|
||||
const iconSize = isNavbarEnabled ? 24 : 16
|
||||
|
||||
const icon = useMemo(() => account && jazzicon(16, parseInt(account.slice(2, 10), 16)), [account])
|
||||
const icon = useMemo(() => account && jazzicon(iconSize, parseInt(account.slice(2, 10), 16)), [account, iconSize])
|
||||
const iconRef = useRef<HTMLDivElement>(null)
|
||||
useLayoutEffect(() => {
|
||||
const current = iconRef.current
|
||||
@@ -41,7 +44,7 @@ export default function Identicon() {
|
||||
}, [icon, iconRef])
|
||||
|
||||
return (
|
||||
<StyledIdenticon>
|
||||
<StyledIdenticon isNavbarEnabled={isNavbarEnabled}>
|
||||
{avatar && fetchable ? (
|
||||
<StyledAvatar alt="avatar" src={avatar} onError={() => setFetchable(false)}></StyledAvatar>
|
||||
) : (
|
||||
|
||||
@@ -48,11 +48,11 @@ const StyledInput = styled(NumericalInput)<{ usePercent?: boolean }>`
|
||||
font-weight: 500;
|
||||
padding: 0 10px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
font-size: 16px;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToExtraSmall`
|
||||
font-size: 12px;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -14,13 +14,15 @@ export default function ListLogo({
|
||||
style,
|
||||
size = '24px',
|
||||
alt,
|
||||
symbol,
|
||||
}: {
|
||||
logoURI: string
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
alt?: string
|
||||
symbol?: string
|
||||
}) {
|
||||
const srcs: string[] = useHttpLocations(logoURI)
|
||||
|
||||
return <StyledListLogo alt={alt} size={size} srcs={srcs} style={style} />
|
||||
return <StyledListLogo alt={alt} size={size} symbol={symbol} srcs={srcs} style={style} />
|
||||
}
|
||||
|
||||
@@ -1,22 +1,34 @@
|
||||
import { useState } from 'react'
|
||||
import { Slash } from 'react-feather'
|
||||
import { ImageProps } from 'rebass'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const BAD_SRCS: { [tokenAddress: string]: true } = {}
|
||||
|
||||
interface LogoProps extends Pick<ImageProps, 'style' | 'alt' | 'className'> {
|
||||
srcs: string[]
|
||||
symbol?: string
|
||||
size?: string
|
||||
}
|
||||
|
||||
const MissingImageLogo = styled.div<{ size?: string }>`
|
||||
--size: ${({ size }) => size};
|
||||
border-radius: 100px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
font-size: calc(var(--size) / 3);
|
||||
font-weight: 500;
|
||||
height: ${({ size }) => size ?? '24px'};
|
||||
line-height: ${({ size }) => size ?? '24px'};
|
||||
text-align: center;
|
||||
width: ${({ size }) => size ?? '24px'};
|
||||
`
|
||||
|
||||
/**
|
||||
* Renders an image by sequentially trying a list of URIs, and then eventually a fallback triangle alert
|
||||
*/
|
||||
export default function Logo({ srcs, alt, style, ...rest }: LogoProps) {
|
||||
export default function Logo({ srcs, alt, style, size, symbol, ...rest }: LogoProps) {
|
||||
const [, refresh] = useState<number>(0)
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
const src: string | undefined = srcs.find((src) => !BAD_SRCS[src])
|
||||
|
||||
if (src) {
|
||||
@@ -34,5 +46,10 @@ export default function Logo({ srcs, alt, style, ...rest }: LogoProps) {
|
||||
)
|
||||
}
|
||||
|
||||
return <Slash {...rest} style={{ ...style, color: theme.deprecated_bg4 }} />
|
||||
return (
|
||||
<MissingImageLogo size={size}>
|
||||
{/* use only first 3 characters of Symbol for design reasons */}
|
||||
{symbol?.toUpperCase().replace('$', '').replace(/\s+/g, '').slice(0, 3)}
|
||||
</MissingImageLogo>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ const MenuFlyout = styled.span<{ flyoutAlignment?: FlyoutAlignment }>`
|
||||
: css`
|
||||
left: 0rem;
|
||||
`};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
bottom: unset;
|
||||
right: 0;
|
||||
left: unset;
|
||||
|
||||
@@ -59,11 +59,11 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, rede
|
||||
`}
|
||||
display: flex;
|
||||
border-radius: 20px;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
width: 65vw;
|
||||
margin: 0;
|
||||
`}
|
||||
${({ theme, mobile }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme, mobile }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
width: 85vw;
|
||||
${
|
||||
mobile &&
|
||||
|
||||
@@ -1,37 +1,47 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
import { lightGrayOverlayOnHover } from 'nft/css/common.css'
|
||||
|
||||
import { sprinkles } from '../../nft/css/sprinkles.css'
|
||||
import { breakpoints, sprinkles } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const ChainSwitcher = style([
|
||||
lightGrayOverlayOnHover,
|
||||
sprinkles({
|
||||
background: 'lightGrayContainer',
|
||||
borderRadius: '8',
|
||||
paddingY: '8',
|
||||
paddingX: '12',
|
||||
cursor: 'pointer',
|
||||
border: 'none',
|
||||
color: 'blackBlue',
|
||||
background: 'none',
|
||||
}),
|
||||
])
|
||||
|
||||
export const ChainSwitcherRow = style([
|
||||
lightGrayOverlayOnHover,
|
||||
sprinkles({
|
||||
border: 'none',
|
||||
color: 'blackBlue',
|
||||
justifyContent: 'space-between',
|
||||
paddingX: '16',
|
||||
paddingY: '12',
|
||||
paddingX: '8',
|
||||
paddingY: '8',
|
||||
cursor: 'pointer',
|
||||
color: 'blackBlue',
|
||||
borderRadius: '12',
|
||||
width: { sm: 'full' },
|
||||
}),
|
||||
{
|
||||
lineHeight: '24px',
|
||||
width: '308px',
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.sm}px)`]: {
|
||||
width: '204px',
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export const Image = style([
|
||||
sprinkles({
|
||||
width: '28',
|
||||
height: '28',
|
||||
width: '20',
|
||||
height: '20',
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -41,9 +51,3 @@ export const Icon = style([
|
||||
marginRight: '12',
|
||||
}),
|
||||
])
|
||||
|
||||
export const Indicator = style([
|
||||
sprinkles({
|
||||
marginLeft: '8',
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { StyledChevronDown, StyledChevronUp } from 'components/Icons'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import useSelectChain from 'hooks/useSelectChain'
|
||||
import useSyncChainQuery from 'hooks/useSyncChainQuery'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Portal } from 'nft/components/common/Portal'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { NewChevronDownIcon, NewChevronUpIcon } from 'nft/components/icons'
|
||||
import { CheckMarkIcon } from 'nft/components/icons'
|
||||
import { CheckMarkIcon, TokenWarningRedIcon } from 'nft/components/icons'
|
||||
import { subhead } from 'nft/css/common.css'
|
||||
import { themeVars, vars } from 'nft/css/sprinkles.css'
|
||||
import { useIsMobile } from 'nft/hooks'
|
||||
import { ReactNode, useReducer, useRef } from 'react'
|
||||
import { isChainAllowed } from 'utils/switchChain'
|
||||
|
||||
import * as styles from './ChainSwitcher.css'
|
||||
import { NavDropdown } from './NavDropdown'
|
||||
@@ -27,18 +29,20 @@ const ChainRow = ({
|
||||
const { label, logoUrl } = getChainInfo(targetChain)
|
||||
|
||||
return (
|
||||
<Row
|
||||
as="button"
|
||||
background={active ? 'lightGrayContainer' : 'none'}
|
||||
className={`${styles.ChainSwitcherRow} ${subhead}`}
|
||||
onClick={() => onSelectChain(targetChain)}
|
||||
>
|
||||
<ChainDetails>
|
||||
<img src={logoUrl} alt={label} className={styles.Icon} />
|
||||
{label}
|
||||
</ChainDetails>
|
||||
{active && <CheckMarkIcon width={20} height={20} />}
|
||||
</Row>
|
||||
<Column borderRadius="12">
|
||||
<Row
|
||||
as="button"
|
||||
background="none"
|
||||
className={`${styles.ChainSwitcherRow} ${subhead}`}
|
||||
onClick={() => onSelectChain(targetChain)}
|
||||
>
|
||||
<ChainDetails>
|
||||
<img src={logoUrl} alt={label} className={styles.Icon} />
|
||||
{label}
|
||||
</ChainDetails>
|
||||
{active && <CheckMarkIcon width={20} height={20} color={vars.color.blue400} />}
|
||||
</Row>
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -53,56 +57,73 @@ const NETWORK_SELECTOR_CHAINS = [
|
||||
]
|
||||
|
||||
interface ChainSwitcherProps {
|
||||
isMobile?: boolean
|
||||
leftAlign?: boolean
|
||||
}
|
||||
|
||||
export const ChainSwitcher = ({ isMobile }: ChainSwitcherProps) => {
|
||||
export const ChainSwitcher = ({ leftAlign }: ChainSwitcherProps) => {
|
||||
const { chainId } = useWeb3React()
|
||||
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
useOnClickOutside(ref, isOpen ? toggleOpen : undefined)
|
||||
const modalRef = useRef<HTMLDivElement>(null)
|
||||
useOnClickOutside(ref, isOpen ? toggleOpen : undefined, [modalRef])
|
||||
|
||||
const info = chainId ? getChainInfo(chainId) : undefined
|
||||
|
||||
const selectChain = useSelectChain()
|
||||
useSyncChainQuery()
|
||||
|
||||
if (!chainId || !info) {
|
||||
if (!chainId) {
|
||||
return null
|
||||
}
|
||||
|
||||
const isSupported = !!info
|
||||
|
||||
const dropdown = (
|
||||
<NavDropdown top="56" left={leftAlign ? '0' : 'auto'} right={leftAlign ? 'auto' : '0'} ref={modalRef}>
|
||||
<Column marginX="8">
|
||||
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) => (
|
||||
<ChainRow
|
||||
onSelectChain={async (targetChainId: SupportedChainId) => {
|
||||
await selectChain(targetChainId)
|
||||
toggleOpen()
|
||||
}}
|
||||
targetChain={chainId}
|
||||
key={chainId}
|
||||
/>
|
||||
))}
|
||||
</Column>
|
||||
</NavDropdown>
|
||||
)
|
||||
|
||||
return (
|
||||
<Box position="relative" ref={ref}>
|
||||
<Row as="button" gap="8" className={styles.ChainSwitcher} onClick={toggleOpen}>
|
||||
<img src={info.logoUrl} alt={info.label} className={styles.Image} />
|
||||
<Box as="span" className={subhead} color="explicitWhite" style={{ lineHeight: '20px' }}>
|
||||
{info.label}
|
||||
</Box>
|
||||
{isOpen ? (
|
||||
<NewChevronUpIcon width={16} height={16} color="darkGray" />
|
||||
<Row
|
||||
as="button"
|
||||
gap="8"
|
||||
className={styles.ChainSwitcher}
|
||||
background={isOpen ? 'accentActiveSoft' : 'none'}
|
||||
onClick={toggleOpen}
|
||||
>
|
||||
{!isSupported ? (
|
||||
<>
|
||||
<TokenWarningRedIcon fill={themeVars.colors.darkGray} width={24} height={24} />
|
||||
<Box as="span" className={subhead} display={{ sm: 'none', xxl: 'flex' }} style={{ lineHeight: '20px' }}>
|
||||
Unsupported
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<NewChevronDownIcon width={16} height={16} color="darkGray" />
|
||||
<>
|
||||
<img src={info.logoUrl} alt={info.label} className={styles.Image} />
|
||||
<Box as="span" className={subhead} display={{ sm: 'none', xxl: 'flex' }} style={{ lineHeight: '20px' }}>
|
||||
{info.label}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
{isOpen ? <StyledChevronUp /> : <StyledChevronDown />}
|
||||
</Row>
|
||||
{isOpen && (
|
||||
<NavDropdown top={60} leftAligned={isMobile}>
|
||||
<Column gap="4">
|
||||
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) =>
|
||||
isChainAllowed(chainId) ? (
|
||||
<ChainRow
|
||||
onSelectChain={async (targetChainId: SupportedChainId) => {
|
||||
await selectChain(targetChainId)
|
||||
toggleOpen()
|
||||
}}
|
||||
targetChain={chainId}
|
||||
key={chainId}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
</Column>
|
||||
</NavDropdown>
|
||||
)}
|
||||
{isOpen && (isMobile ? <Portal>{dropdown}</Portal> : <>{dropdown}</>)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
|
||||
import { sprinkles, themeVars } from '../../nft/css/sprinkles.css'
|
||||
import { sprinkles, themeVars, vars } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const hover = style([
|
||||
sprinkles({
|
||||
transition: '250',
|
||||
borderRadius: '12',
|
||||
}),
|
||||
{
|
||||
':hover': {
|
||||
background: vars.color.lightGrayOverlay,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export const MenuRow = style([
|
||||
hover,
|
||||
sprinkles({
|
||||
color: 'blackBlue',
|
||||
paddingY: '12',
|
||||
width: 'max',
|
||||
marginRight: '52',
|
||||
paddingY: '8',
|
||||
paddingX: '8',
|
||||
width: 'full',
|
||||
whiteSpace: 'nowrap',
|
||||
}),
|
||||
{
|
||||
lineHeight: '24px',
|
||||
@@ -22,8 +36,10 @@ export const PrimaryText = style([
|
||||
])
|
||||
|
||||
export const SecondaryText = style([
|
||||
hover,
|
||||
sprinkles({
|
||||
paddingY: '8',
|
||||
paddingX: '8',
|
||||
color: 'darkGray',
|
||||
}),
|
||||
{
|
||||
@@ -34,6 +50,7 @@ export const SecondaryText = style([
|
||||
export const Separator = style([
|
||||
sprinkles({
|
||||
height: '0',
|
||||
marginX: '16',
|
||||
}),
|
||||
{
|
||||
borderTop: 'solid',
|
||||
@@ -45,6 +62,6 @@ export const Separator = style([
|
||||
export const IconRow = style([
|
||||
sprinkles({
|
||||
paddingX: '16',
|
||||
paddingY: '8',
|
||||
justifyContent: { sm: 'center', md: 'flex-start' },
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
|
||||
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
@@ -124,58 +125,78 @@ export const MenuDropdown = () => {
|
||||
return (
|
||||
<>
|
||||
<Box position="relative" ref={ref}>
|
||||
<NavIcon onClick={toggleOpen}>
|
||||
<EllipsisIcon width={28} height={28} />
|
||||
<NavIcon isActive={isOpen} onClick={toggleOpen}>
|
||||
<EllipsisIcon />
|
||||
</NavIcon>
|
||||
|
||||
{isOpen && (
|
||||
<NavDropdown top={60}>
|
||||
<Column gap="12">
|
||||
<Column paddingX="16" gap="4">
|
||||
<NavDropdown top={{ sm: 'unset', lg: '56' }} bottom={{ sm: '56', lg: 'unset' }} right="0">
|
||||
<Column gap="16">
|
||||
<Column paddingX="8" gap="4">
|
||||
{nftFlag === NftVariant.Enabled && (
|
||||
<PrimaryMenuRow to="/nft/sell" close={toggleOpen}>
|
||||
<Icon>
|
||||
<ThinTagIcon width={24} height={24} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>Sell NFTs</PrimaryMenuRow.Text>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Sell NFTs</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
)}
|
||||
<PrimaryMenuRow to="/vote" close={toggleOpen}>
|
||||
<Icon>
|
||||
<GovernanceIcon width={24} height={24} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>Vote in governance</PrimaryMenuRow.Text>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Vote in governance</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
<PrimaryMenuRow href="https://info.uniswap.org/#/">
|
||||
<Icon>
|
||||
<BarChartIcon width={24} height={24} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>View token analytics ↗</PrimaryMenuRow.Text>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>View token analytics</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
</Column>
|
||||
<Separator />
|
||||
<Column paddingX="16" gap="4">
|
||||
<SecondaryLinkedText href="https://help.uniswap.org/en/">Help center ↗</SecondaryLinkedText>
|
||||
<SecondaryLinkedText href="https://docs.uniswap.org/">Documentation ↗</SecondaryLinkedText>
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection={{ sm: 'row', md: 'column' }}
|
||||
flexWrap="wrap"
|
||||
alignItems={{ sm: 'center', md: 'flex-start' }}
|
||||
paddingX="8"
|
||||
>
|
||||
<SecondaryLinkedText href="https://help.uniswap.org/en/">
|
||||
<Trans>Help center</Trans> ↗
|
||||
</SecondaryLinkedText>
|
||||
<SecondaryLinkedText href="https://docs.uniswap.org/">
|
||||
<Trans>Documentation</Trans> ↗
|
||||
</SecondaryLinkedText>
|
||||
<SecondaryLinkedText
|
||||
onClick={() => {
|
||||
toggleOpen()
|
||||
togglePrivacyPolicy()
|
||||
}}
|
||||
>{`Legal & Privacy`}</SecondaryLinkedText>
|
||||
>
|
||||
<Trans>Legal & Privacy</Trans> ↗
|
||||
</SecondaryLinkedText>
|
||||
{(isDevelopmentEnv() || isStagingEnv()) && (
|
||||
<SecondaryLinkedText onClick={openFeatureFlagsModal}>{`Feature Flags`}</SecondaryLinkedText>
|
||||
<SecondaryLinkedText onClick={openFeatureFlagsModal}>
|
||||
<Trans>Feature Flags</Trans>
|
||||
</SecondaryLinkedText>
|
||||
)}
|
||||
</Column>
|
||||
</Box>
|
||||
<IconRow>
|
||||
<Icon href="https://discord.com/invite/FCfyBSbCU5">
|
||||
<DiscordIconMenu width={24} height={24} color={themeVars.colors.darkGray} />
|
||||
<DiscordIconMenu className={styles.hover} width={24} height={24} color={themeVars.colors.darkGray} />
|
||||
</Icon>
|
||||
<Icon href="https://twitter.com/Uniswap">
|
||||
<TwitterIconMenu width={24} height={24} color={themeVars.colors.darkGray} />
|
||||
<TwitterIconMenu className={styles.hover} width={24} height={24} color={themeVars.colors.darkGray} />
|
||||
</Icon>
|
||||
<Icon href="https://github.com/Uniswap">
|
||||
<GithubIconMenu width={24} height={24} color={themeVars.colors.darkGray} />
|
||||
<GithubIconMenu className={styles.hover} width={24} height={24} color={themeVars.colors.darkGray} />
|
||||
</Icon>
|
||||
</IconRow>
|
||||
</Column>
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
import { subhead } from 'nft/css/common.css'
|
||||
|
||||
import { sprinkles } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const sidebar = style([
|
||||
sprinkles({
|
||||
display: 'flex',
|
||||
position: 'fixed',
|
||||
background: 'white',
|
||||
height: 'full',
|
||||
top: '0',
|
||||
left: '0',
|
||||
right: '0',
|
||||
bottom: '0',
|
||||
paddingBottom: '16',
|
||||
justifyContent: 'space-between',
|
||||
}),
|
||||
{
|
||||
zIndex: 20,
|
||||
},
|
||||
])
|
||||
|
||||
export const icon = style([
|
||||
sprinkles({
|
||||
width: '32',
|
||||
height: '32',
|
||||
}),
|
||||
])
|
||||
|
||||
export const iconContainer = style([
|
||||
sprinkles({
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
color: 'darkGray',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
justifyContent: 'flex-end',
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
padding: '6',
|
||||
}),
|
||||
])
|
||||
|
||||
export const linkRow = style([
|
||||
subhead,
|
||||
sprinkles({
|
||||
color: 'blackBlue',
|
||||
width: 'full',
|
||||
paddingLeft: '16',
|
||||
paddingY: '12',
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
{
|
||||
lineHeight: '24px',
|
||||
textDecoration: 'none',
|
||||
},
|
||||
])
|
||||
|
||||
export const activeLinkRow = style([
|
||||
linkRow,
|
||||
sprinkles({
|
||||
background: 'lightGrayButton',
|
||||
}),
|
||||
])
|
||||
|
||||
export const separator = style([
|
||||
sprinkles({
|
||||
height: '0',
|
||||
borderStyle: 'solid',
|
||||
borderColor: 'medGray',
|
||||
borderWidth: '1px',
|
||||
marginY: '8',
|
||||
marginX: '16',
|
||||
}),
|
||||
])
|
||||
|
||||
export const extraLinkRow = style([
|
||||
subhead,
|
||||
sprinkles({
|
||||
width: 'full',
|
||||
color: 'blackBlue',
|
||||
paddingY: '12',
|
||||
paddingLeft: '16',
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
{
|
||||
lineHeight: '24px',
|
||||
textDecoration: 'none',
|
||||
},
|
||||
])
|
||||
|
||||
export const bottomExternalLinks = style([
|
||||
sprinkles({
|
||||
gap: '4',
|
||||
paddingX: '4',
|
||||
width: 'max',
|
||||
flexWrap: 'wrap',
|
||||
}),
|
||||
])
|
||||
|
||||
export const bottomJointExternalLinksContainer = style([
|
||||
sprinkles({
|
||||
paddingX: '8',
|
||||
paddingY: '4',
|
||||
color: 'darkGray',
|
||||
fontWeight: 'medium',
|
||||
fontSize: '12',
|
||||
}),
|
||||
{
|
||||
lineHeight: '20px',
|
||||
},
|
||||
])
|
||||
|
||||
export const IconRow = style([
|
||||
sprinkles({
|
||||
gap: '12',
|
||||
width: 'max',
|
||||
}),
|
||||
])
|
||||
@@ -1,249 +0,0 @@
|
||||
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
|
||||
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Portal } from 'nft/components/common/Portal'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import {
|
||||
BarChartIconMobile,
|
||||
BulletIcon,
|
||||
CloseIcon,
|
||||
DiscordIconMenuMobile,
|
||||
GithubIconMenuMobile,
|
||||
GovernanceIconMobile,
|
||||
HamburgerIcon,
|
||||
ThinTagIconMobile,
|
||||
TwitterIconMenuMobile,
|
||||
} from 'nft/components/icons'
|
||||
import { themeVars } from 'nft/css/sprinkles.css'
|
||||
import { ReactNode, useReducer } from 'react'
|
||||
import { NavLink, NavLinkProps, useLocation } from 'react-router-dom'
|
||||
import { useToggleModal, useTogglePrivacyPolicy } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import { isDevelopmentEnv, isStagingEnv } from 'utils/env'
|
||||
|
||||
import * as styles from './MobileSidebar.css'
|
||||
import { NavIcon } from './NavIcon'
|
||||
|
||||
interface NavLinkRowProps {
|
||||
href: string
|
||||
id?: NavLinkProps['id']
|
||||
isActive?: boolean
|
||||
close: () => void
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const NavLinkRow = ({ href, id, isActive, close, children }: NavLinkRowProps) => {
|
||||
return (
|
||||
<NavLink to={href} className={isActive ? styles.activeLinkRow : styles.linkRow} id={id} onClick={close}>
|
||||
{children}
|
||||
</NavLink>
|
||||
)
|
||||
}
|
||||
|
||||
const ExtraLinkRow = ({
|
||||
to,
|
||||
href,
|
||||
close,
|
||||
children,
|
||||
}: {
|
||||
to?: NavLinkProps['to']
|
||||
href?: string
|
||||
close: () => void
|
||||
children: ReactNode
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{to ? (
|
||||
<NavLink to={to} className={styles.extraLinkRow}>
|
||||
<Row gap="12" onClick={close}>
|
||||
{children}
|
||||
</Row>
|
||||
</NavLink>
|
||||
) : (
|
||||
<Row
|
||||
as="a"
|
||||
href={href}
|
||||
target={'_blank'}
|
||||
rel={'noopener noreferrer'}
|
||||
gap="12"
|
||||
onClick={close}
|
||||
className={styles.extraLinkRow}
|
||||
>
|
||||
{children}
|
||||
</Row>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const BottomExternalLink = ({
|
||||
href,
|
||||
onClick,
|
||||
children,
|
||||
}: {
|
||||
href?: string
|
||||
onClick?: () => void
|
||||
children: ReactNode
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
as={href ? 'a' : 'div'}
|
||||
href={href ?? undefined}
|
||||
target={href ? '_blank' : undefined}
|
||||
rel={href ? 'noopener noreferrer' : undefined}
|
||||
className={`${styles.bottomJointExternalLinksContainer}`}
|
||||
onClick={onClick}
|
||||
cursor="pointer"
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const Icon = ({ href, children }: { href?: string; children: ReactNode }) => {
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
as={href ? 'a' : 'div'}
|
||||
href={href ?? undefined}
|
||||
target={href ? '_blank' : undefined}
|
||||
rel={href ? 'noopener noreferrer' : undefined}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
color="blackBlue"
|
||||
background="none"
|
||||
border="none"
|
||||
justifyContent="center"
|
||||
textAlign="center"
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const IconRow = ({ children }: { children: ReactNode }) => {
|
||||
return <Row className={styles.IconRow}>{children}</Row>
|
||||
}
|
||||
|
||||
const Seperator = () => {
|
||||
return <Box className={styles.separator} />
|
||||
}
|
||||
|
||||
export const MobileSideBar = () => {
|
||||
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
|
||||
const togglePrivacyPolicy = useTogglePrivacyPolicy()
|
||||
const openFeatureFlagsModal = useToggleModal(ApplicationModal.FEATURE_FLAGS)
|
||||
const { pathname } = useLocation()
|
||||
const nftFlag = useNftFlag()
|
||||
const isPoolActive =
|
||||
pathname.startsWith('/pool') ||
|
||||
pathname.startsWith('/add') ||
|
||||
pathname.startsWith('/remove') ||
|
||||
pathname.startsWith('/increase') ||
|
||||
pathname.startsWith('/find')
|
||||
|
||||
return (
|
||||
<>
|
||||
<NavIcon onClick={toggleOpen}>
|
||||
<HamburgerIcon width={28} height={28} />
|
||||
</NavIcon>
|
||||
{isOpen && (
|
||||
<Portal>
|
||||
<Column className={styles.sidebar}>
|
||||
<Column>
|
||||
<Row justifyContent="flex-end" marginTop="14" marginBottom="20" marginRight="8">
|
||||
<Box as="button" onClick={toggleOpen} className={styles.iconContainer}>
|
||||
<CloseIcon className={styles.icon} />
|
||||
</Box>
|
||||
</Row>
|
||||
<Column gap="4">
|
||||
<NavLinkRow href="/swap" close={toggleOpen} isActive={pathname.startsWith('/swap')}>
|
||||
Swap
|
||||
</NavLinkRow>
|
||||
<NavLinkRow href="/tokens" close={toggleOpen} isActive={pathname.startsWith('/tokens')}>
|
||||
Tokens
|
||||
</NavLinkRow>
|
||||
{nftFlag === NftVariant.Enabled && (
|
||||
<NavLinkRow href="/nfts" close={toggleOpen} isActive={pathname.startsWith('/nfts')}>
|
||||
NFTs
|
||||
</NavLinkRow>
|
||||
)}
|
||||
<NavLinkRow href="/pool" id={'pool-nav-link'} isActive={isPoolActive} close={toggleOpen}>
|
||||
Pool
|
||||
</NavLinkRow>
|
||||
</Column>
|
||||
<Seperator />
|
||||
<Column gap="4">
|
||||
{nftFlag === NftVariant.Enabled && (
|
||||
<ExtraLinkRow to="/nft/sell" close={toggleOpen}>
|
||||
<Icon>
|
||||
<ThinTagIconMobile width={24} height={24} />
|
||||
</Icon>
|
||||
Sell NFTs
|
||||
</ExtraLinkRow>
|
||||
)}
|
||||
<ExtraLinkRow to="/vote" close={toggleOpen}>
|
||||
<Icon>
|
||||
<GovernanceIconMobile width={24} height={24} />
|
||||
</Icon>
|
||||
Vote in governance
|
||||
</ExtraLinkRow>
|
||||
<ExtraLinkRow href="https://info.uniswap.org/#/" close={toggleOpen}>
|
||||
<Icon>
|
||||
<BarChartIconMobile width={24} height={24} />
|
||||
</Icon>
|
||||
View token analytics ↗
|
||||
</ExtraLinkRow>
|
||||
</Column>
|
||||
</Column>
|
||||
<Column>
|
||||
<Row justifyContent="center" marginBottom="12" flexWrap="wrap">
|
||||
<Row className={styles.bottomExternalLinks}>
|
||||
<BottomExternalLink href="https://help.uniswap.org/en/" onClick={toggleOpen}>
|
||||
Help center ↗
|
||||
</BottomExternalLink>
|
||||
<BulletIcon />
|
||||
<BottomExternalLink href="https://docs.uniswap.org/" onClick={toggleOpen}>
|
||||
Documentation ↗
|
||||
</BottomExternalLink>
|
||||
<BulletIcon />
|
||||
<BottomExternalLink
|
||||
onClick={() => {
|
||||
toggleOpen()
|
||||
togglePrivacyPolicy()
|
||||
}}
|
||||
>
|
||||
{`Legal & Privacy`}
|
||||
</BottomExternalLink>
|
||||
</Row>
|
||||
{(isDevelopmentEnv() || isStagingEnv()) && (
|
||||
<>
|
||||
<BulletIcon />
|
||||
<BottomExternalLink onClick={openFeatureFlagsModal}>{`Feature Flags`}</BottomExternalLink>
|
||||
</>
|
||||
)}
|
||||
</Row>
|
||||
<Row justifyContent="center">
|
||||
<IconRow>
|
||||
<Icon href="https://discord.com/invite/FCfyBSbCU5">
|
||||
<DiscordIconMenuMobile width={32} height={32} color={themeVars.colors.darkGray} />
|
||||
</Icon>
|
||||
<Icon href="https://twitter.com/Uniswap">
|
||||
<TwitterIconMenuMobile width={32} height={32} color={themeVars.colors.darkGray} />
|
||||
</Icon>
|
||||
<Icon href="https://github.com/Uniswap">
|
||||
<GithubIconMenuMobile width={32} height={32} color={themeVars.colors.darkGray} />
|
||||
</Icon>
|
||||
</IconRow>
|
||||
</Row>
|
||||
</Column>
|
||||
</Column>
|
||||
</Portal>
|
||||
)}
|
||||
<PrivacyPolicyModal />
|
||||
<FeatureFlagModal />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -2,18 +2,44 @@ import { style } from '@vanilla-extract/css'
|
||||
|
||||
import { sprinkles } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const NavDropdown = style([
|
||||
const baseNavDropdown = style([
|
||||
sprinkles({
|
||||
position: 'absolute',
|
||||
background: 'white95',
|
||||
borderRadius: '12',
|
||||
background: 'lightGray',
|
||||
borderStyle: 'solid',
|
||||
borderColor: 'medGray',
|
||||
paddingY: '20',
|
||||
borderWidth: '1px',
|
||||
paddingBottom: '8',
|
||||
paddingTop: '8',
|
||||
zIndex: '2',
|
||||
}),
|
||||
{
|
||||
boxShadow: '0px 4px 12px 0px #00000026',
|
||||
zIndex: 10,
|
||||
},
|
||||
])
|
||||
|
||||
export const NavDropdown = style([
|
||||
baseNavDropdown,
|
||||
sprinkles({
|
||||
position: 'absolute',
|
||||
borderRadius: '12',
|
||||
}),
|
||||
{},
|
||||
])
|
||||
|
||||
export const mobileNavDropdown = style([
|
||||
baseNavDropdown,
|
||||
sprinkles({
|
||||
position: 'fixed',
|
||||
borderTopRightRadius: '12',
|
||||
borderTopLeftRadius: '12',
|
||||
top: 'unset',
|
||||
bottom: '56',
|
||||
left: '0',
|
||||
right: '0',
|
||||
width: 'full',
|
||||
}),
|
||||
{
|
||||
borderRightWidth: '0px',
|
||||
borderLeftWidth: '0px',
|
||||
},
|
||||
])
|
||||
|
||||
@@ -1,37 +1,12 @@
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { ReactNode } from 'react'
|
||||
import { Box, BoxProps } from 'nft/components/Box'
|
||||
import { useIsMobile } from 'nft/hooks'
|
||||
import { ForwardedRef, forwardRef } from 'react'
|
||||
|
||||
import * as styles from './NavDropdown.css'
|
||||
|
||||
interface NavDropdownProps {
|
||||
top: number
|
||||
right?: number
|
||||
leftAligned?: boolean
|
||||
horizontalPadding?: boolean
|
||||
centerHorizontally?: boolean
|
||||
children: ReactNode
|
||||
}
|
||||
export const NavDropdown = forwardRef((props: BoxProps, ref: ForwardedRef<HTMLElement>) => {
|
||||
const isMobile = useIsMobile()
|
||||
return <Box ref={ref} className={isMobile ? styles.mobileNavDropdown : styles.NavDropdown} {...props} />
|
||||
})
|
||||
|
||||
export const NavDropdown = ({
|
||||
top,
|
||||
centerHorizontally,
|
||||
leftAligned,
|
||||
horizontalPadding,
|
||||
children,
|
||||
}: NavDropdownProps) => {
|
||||
return (
|
||||
<Box
|
||||
paddingX={horizontalPadding ? '16' : undefined}
|
||||
style={{
|
||||
top: `${top}px`,
|
||||
left: centerHorizontally ? '50%' : leftAligned ? '0px' : 'auto',
|
||||
right: centerHorizontally || leftAligned ? 'auto' : '10px',
|
||||
transform: centerHorizontally ? 'translateX(-50%)' : 'unset',
|
||||
zIndex: 3,
|
||||
}}
|
||||
className={styles.NavDropdown}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
NavDropdown.displayName = 'NavDropdown'
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
|
||||
import { sprinkles, themeVars } from '../../nft/css/sprinkles.css'
|
||||
import { sprinkles, vars } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const navIcon = style([
|
||||
sprinkles({
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
color: 'blackBlue',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
padding: '8',
|
||||
borderRadius: '8',
|
||||
transition: '250',
|
||||
}),
|
||||
{
|
||||
':hover': {
|
||||
background: themeVars.colors.lightGrayContainer,
|
||||
background: vars.color.lightGrayOverlay,
|
||||
},
|
||||
zIndex: 2,
|
||||
zIndex: 1,
|
||||
},
|
||||
])
|
||||
|
||||
@@ -5,12 +5,19 @@ import * as styles from './NavIcon.css'
|
||||
|
||||
interface NavIconProps {
|
||||
children: ReactNode
|
||||
isActive?: boolean
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
export const NavIcon = ({ children, onClick }: NavIconProps) => {
|
||||
export const NavIcon = ({ children, isActive, onClick }: NavIconProps) => {
|
||||
return (
|
||||
<Box as="button" className={styles.navIcon} onClick={onClick}>
|
||||
<Box
|
||||
as="button"
|
||||
className={styles.navIcon}
|
||||
background={isActive ? 'accentActiveSoft' : 'none'}
|
||||
color={isActive ? 'blackBlue' : 'darkGray'}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
|
||||
import { subhead } from '../../nft/css/common.css'
|
||||
import { sprinkles } from '../../nft/css/sprinkles.css'
|
||||
import { sprinkles, vars } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const nav = style([
|
||||
sprinkles({
|
||||
@@ -20,7 +20,7 @@ export const nav = style([
|
||||
export const logoContainer = style([
|
||||
sprinkles({
|
||||
display: 'flex',
|
||||
marginRight: { mobile: '12', desktopXl: '20' },
|
||||
marginRight: { sm: '12', xxl: '20' },
|
||||
alignItems: 'center',
|
||||
}),
|
||||
])
|
||||
@@ -34,23 +34,14 @@ export const logo = style([
|
||||
|
||||
export const baseContainer = style([
|
||||
sprinkles({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}),
|
||||
])
|
||||
|
||||
export const baseMobileContainer = style([
|
||||
sprinkles({
|
||||
display: 'flex',
|
||||
width: 'full',
|
||||
alignItems: 'center',
|
||||
marginY: '2',
|
||||
}),
|
||||
])
|
||||
|
||||
export const baseSideContainer = style([
|
||||
baseContainer,
|
||||
sprinkles({
|
||||
display: 'flex',
|
||||
width: 'full',
|
||||
flex: '1',
|
||||
flexShrink: '2',
|
||||
@@ -64,19 +55,13 @@ export const leftSideContainer = style([
|
||||
}),
|
||||
])
|
||||
|
||||
export const leftSideMobileContainer = style([
|
||||
baseMobileContainer,
|
||||
sprinkles({
|
||||
justifyContent: 'flex-start',
|
||||
}),
|
||||
])
|
||||
|
||||
export const middleContainer = style([
|
||||
baseContainer,
|
||||
sprinkles({
|
||||
flex: '1',
|
||||
flexShrink: '1',
|
||||
justifyContent: 'center',
|
||||
display: { sm: 'none', xl: 'flex' },
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -94,10 +79,17 @@ const baseMenuItem = style([
|
||||
paddingX: '16',
|
||||
marginY: '4',
|
||||
borderRadius: '12',
|
||||
transition: '250',
|
||||
height: 'min',
|
||||
width: 'full',
|
||||
textAlign: 'center',
|
||||
}),
|
||||
{
|
||||
lineHeight: '24px',
|
||||
textDecoration: 'none',
|
||||
':hover': {
|
||||
background: vars.color.lightGrayOverlay,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
@@ -108,30 +100,25 @@ export const menuItem = style([
|
||||
}),
|
||||
])
|
||||
|
||||
export const rightSideMobileContainer = style([
|
||||
baseMobileContainer,
|
||||
sprinkles({
|
||||
justifyContent: 'flex-end',
|
||||
}),
|
||||
])
|
||||
|
||||
export const activeMenuItem = style([
|
||||
baseMenuItem,
|
||||
sprinkles({
|
||||
color: 'blackBlue',
|
||||
background: 'backgroundFloating',
|
||||
}),
|
||||
])
|
||||
|
||||
export const mobileWalletContainer = style([
|
||||
export const mobileBottomBar = style([
|
||||
sprinkles({
|
||||
position: 'fixed',
|
||||
display: 'flex',
|
||||
display: { sm: 'flex', lg: 'none' },
|
||||
bottom: '0',
|
||||
right: '1/2',
|
||||
marginY: '0',
|
||||
marginX: 'auto',
|
||||
right: '0',
|
||||
left: '0',
|
||||
justifyContent: 'space-between',
|
||||
paddingY: '4',
|
||||
paddingX: '8',
|
||||
height: '56',
|
||||
background: 'lightGray',
|
||||
}),
|
||||
{
|
||||
transform: 'translate(50%,-50%)',
|
||||
},
|
||||
])
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import Web3Status from 'components/Web3Status'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { useWindowSize } from 'hooks/useWindowSize'
|
||||
import { ReactNode } from 'react'
|
||||
import { NavLink, NavLinkProps, useLocation } from 'react-router-dom'
|
||||
|
||||
import { Box } from '../../nft/components/Box'
|
||||
import { Row } from '../../nft/components/Flex'
|
||||
import { UniIcon, UniIconMobile } from '../../nft/components/icons'
|
||||
import { breakpoints } from '../../nft/css/sprinkles.css'
|
||||
import { UniIcon } from '../../nft/components/icons'
|
||||
import { ChainSwitcher } from './ChainSwitcher'
|
||||
import { MenuDropdown } from './MenuDropdown'
|
||||
import { MobileSideBar } from './MobileSidebar'
|
||||
import * as styles from './Navbar.css'
|
||||
import { SearchBar } from './SearchBar'
|
||||
|
||||
@@ -34,41 +32,10 @@ const MenuItem = ({ href, id, isActive, children }: MenuItemProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
const MobileNavbar = () => {
|
||||
return (
|
||||
<>
|
||||
<nav className={styles.nav}>
|
||||
<Box display="flex" height="full" flexWrap="nowrap" alignItems="stretch">
|
||||
<Box className={styles.leftSideMobileContainer}>
|
||||
<Box as="a" href="#/swap" className={styles.logoContainer}>
|
||||
<UniIconMobile width="44" height="44" className={styles.logo} />
|
||||
</Box>
|
||||
<ChainSwitcher isMobile={true} />
|
||||
</Box>
|
||||
<Box className={styles.rightSideMobileContainer}>
|
||||
<Row gap="16">
|
||||
<SearchBar />
|
||||
<MobileSideBar />
|
||||
</Row>
|
||||
</Box>
|
||||
</Box>
|
||||
</nav>
|
||||
<Box className={styles.mobileWalletContainer}>
|
||||
<Web3Status />
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const Navbar = () => {
|
||||
const { width: windowWidth } = useWindowSize()
|
||||
const PageTabs = () => {
|
||||
const { pathname } = useLocation()
|
||||
const nftFlag = useNftFlag()
|
||||
|
||||
if (windowWidth && windowWidth < breakpoints.desktopXl) {
|
||||
return <MobileNavbar />
|
||||
}
|
||||
|
||||
const isPoolActive =
|
||||
pathname.startsWith('/pool') ||
|
||||
pathname.startsWith('/add') ||
|
||||
@@ -77,41 +44,68 @@ const Navbar = () => {
|
||||
pathname.startsWith('/find')
|
||||
|
||||
return (
|
||||
<nav className={styles.nav}>
|
||||
<Box display="flex" height="full" flexWrap="nowrap" alignItems="stretch">
|
||||
<Box className={styles.leftSideContainer}>
|
||||
<Box as="a" href="#/swap" className={styles.logoContainer}>
|
||||
<UniIcon width="48" height="48" className={styles.logo} />
|
||||
<>
|
||||
<MenuItem href="/swap" isActive={pathname.startsWith('/swap')}>
|
||||
<Trans>Swap</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem href="/tokens" isActive={pathname.startsWith('/tokens')}>
|
||||
<Trans>Tokens</Trans>
|
||||
</MenuItem>
|
||||
{nftFlag === NftVariant.Enabled && (
|
||||
<MenuItem href="/nfts" isActive={pathname.startsWith('/nfts')}>
|
||||
<Trans>NFTs</Trans>
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem href="/pool" id={'pool-nav-link'} isActive={isPoolActive}>
|
||||
<Trans>Pool</Trans>
|
||||
</MenuItem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const Navbar = () => {
|
||||
return (
|
||||
<>
|
||||
<nav className={styles.nav}>
|
||||
<Box display="flex" height="full" flexWrap="nowrap" alignItems="stretch">
|
||||
<Box className={styles.leftSideContainer}>
|
||||
<Box as="a" href="#/swap" className={styles.logoContainer}>
|
||||
<UniIcon width="48" height="48" className={styles.logo} />
|
||||
</Box>
|
||||
<Box display={{ sm: 'flex', lg: 'none' }}>
|
||||
<ChainSwitcher leftAlign={true} />
|
||||
</Box>
|
||||
<Row gap="8" display={{ sm: 'none', lg: 'flex' }}>
|
||||
<PageTabs />
|
||||
</Row>
|
||||
</Box>
|
||||
<Box className={styles.middleContainer}>
|
||||
<SearchBar />
|
||||
</Box>
|
||||
<Box className={styles.rightSideContainer}>
|
||||
<Row gap="12">
|
||||
<Box display={{ sm: 'flex', xl: 'none' }}>
|
||||
<SearchBar />
|
||||
</Box>
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<MenuDropdown />
|
||||
</Box>
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<ChainSwitcher />
|
||||
</Box>
|
||||
|
||||
<Web3Status />
|
||||
</Row>
|
||||
</Box>
|
||||
<Row gap="8">
|
||||
<MenuItem href="/swap" isActive={pathname.startsWith('/swap')}>
|
||||
Swap
|
||||
</MenuItem>
|
||||
<MenuItem href="/tokens" isActive={pathname.startsWith('/tokens')}>
|
||||
Tokens
|
||||
</MenuItem>
|
||||
{nftFlag === NftVariant.Enabled && (
|
||||
<MenuItem href="/nfts" isActive={pathname.startsWith('/nfts')}>
|
||||
NFTs
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem href="/pool" id={'pool-nav-link'} isActive={isPoolActive}>
|
||||
Pool
|
||||
</MenuItem>
|
||||
</Row>
|
||||
</Box>
|
||||
<Box className={styles.middleContainer}>
|
||||
<SearchBar />
|
||||
</Box>
|
||||
<Box className={styles.rightSideContainer}>
|
||||
<Row gap="12">
|
||||
<MenuDropdown />
|
||||
<ChainSwitcher />
|
||||
<Web3Status />
|
||||
</Row>
|
||||
</nav>
|
||||
<Box className={styles.mobileBottomBar}>
|
||||
<PageTabs />
|
||||
<Box marginY="4">
|
||||
<MenuDropdown />
|
||||
</Box>
|
||||
</Box>
|
||||
</nav>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,20 +3,39 @@ import { buttonTextSmall, subhead, subheadSmall } from 'nft/css/common.css'
|
||||
|
||||
import { breakpoints, sprinkles, vars } from '../../nft/css/sprinkles.css'
|
||||
|
||||
const DESKTOP_NAVBAR_WIDTH = '360px'
|
||||
const DESKTOP_NAVBAR_WIDTH = 360
|
||||
|
||||
const baseSearchStyle = style([
|
||||
sprinkles({
|
||||
borderStyle: 'solid',
|
||||
borderColor: 'lightGrayButton',
|
||||
borderWidth: '1px',
|
||||
paddingY: '12',
|
||||
width: { mobile: 'viewWidth' },
|
||||
width: { sm: 'viewWidth' },
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '1px',
|
||||
borderColor: 'medGray',
|
||||
}),
|
||||
{
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.tabletSm}px)`]: {
|
||||
width: DESKTOP_NAVBAR_WIDTH,
|
||||
[`screen and (min-width: ${breakpoints.sm}px)`]: {
|
||||
width: `${DESKTOP_NAVBAR_WIDTH}px`,
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export const searchBarContainer = style([
|
||||
sprinkles({
|
||||
right: '0',
|
||||
top: '0',
|
||||
zIndex: '3',
|
||||
display: 'inline-block',
|
||||
}),
|
||||
{
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.sm}px)`]: {
|
||||
top: '-24px',
|
||||
},
|
||||
[`screen and (min-width: ${breakpoints.lg}px)`]: {
|
||||
right: `-${DESKTOP_NAVBAR_WIDTH / 2}px`,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -29,6 +48,7 @@ export const searchBar = style([
|
||||
color: 'placeholder',
|
||||
paddingX: '16',
|
||||
cursor: 'pointer',
|
||||
background: 'lightGray',
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -47,12 +67,9 @@ export const searchBarInput = style([
|
||||
export const searchBarDropdown = style([
|
||||
baseSearchStyle,
|
||||
sprinkles({
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
top: '48',
|
||||
borderBottomLeftRadius: '12',
|
||||
borderBottomRightRadius: '12',
|
||||
background: 'white',
|
||||
background: 'lightGray',
|
||||
}),
|
||||
{
|
||||
borderTop: 'none',
|
||||
@@ -72,7 +89,7 @@ export const suggestionRow = style([
|
||||
{
|
||||
':hover': {
|
||||
cursor: 'pointer',
|
||||
background: vars.color.lightGrayButton,
|
||||
background: vars.color.lightGrayOverlay,
|
||||
},
|
||||
textDecoration: 'none',
|
||||
},
|
||||
@@ -133,14 +150,6 @@ export const suggestionIcon = sprinkles({
|
||||
flexShrink: '0',
|
||||
})
|
||||
|
||||
export const magnifyingGlassIcon = style([
|
||||
sprinkles({
|
||||
width: '20',
|
||||
height: '20',
|
||||
marginRight: '12',
|
||||
}),
|
||||
])
|
||||
|
||||
export const sectionHeader = style([
|
||||
subheadSmall,
|
||||
sprinkles({
|
||||
@@ -156,6 +165,5 @@ export const notFoundContainer = style([
|
||||
sprinkles({
|
||||
paddingY: '4',
|
||||
paddingLeft: '16',
|
||||
marginTop: '20',
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import clsx from 'clsx'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { useWindowSize } from 'hooks/useWindowSize'
|
||||
import { organizeSearchResults } from 'lib/utils/searchBar'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { Overlay } from 'nft/components/modals/Overlay'
|
||||
import { subheadSmall } from 'nft/css/common.css'
|
||||
import { breakpoints } from 'nft/css/sprinkles.css'
|
||||
import { useSearchHistory } from 'nft/hooks'
|
||||
import { magicalGradientOnHover, subheadSmall } from 'nft/css/common.css'
|
||||
import { useIsMobile, useSearchHistory } from 'nft/hooks'
|
||||
import { fetchSearchCollections, fetchTrendingCollections } from 'nft/queries'
|
||||
import { fetchSearchTokens } from 'nft/queries/genie/SearchTokensFetcher'
|
||||
import { fetchTrendingTokens } from 'nft/queries/genie/TrendingTokensFetcher'
|
||||
@@ -32,7 +33,7 @@ import { CollectionRow, SkeletonRow, TokenRow } from './SuggestionRow'
|
||||
interface SearchBarDropdownSectionProps {
|
||||
toggleOpen: () => void
|
||||
suggestions: (GenieCollection | FungibleToken)[]
|
||||
header: string
|
||||
header: JSX.Element
|
||||
headerIcon?: JSX.Element
|
||||
hoveredIndex: number | undefined
|
||||
startingIndex: number
|
||||
@@ -89,7 +90,7 @@ interface SearchBarDropdownProps {
|
||||
}
|
||||
|
||||
export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }: SearchBarDropdownProps) => {
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(undefined)
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(0)
|
||||
const searchHistory = useSearchHistory(
|
||||
(state: { history: (FungibleToken | GenieCollection)[] }) => state.history
|
||||
).slice(0, 2)
|
||||
@@ -106,10 +107,12 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }:
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={tokens}
|
||||
header={'Tokens'}
|
||||
header={<Trans>Tokens</Trans>}
|
||||
/>
|
||||
) : (
|
||||
<Box className={styles.notFoundContainer}>No tokens found.</Box>
|
||||
<Box className={styles.notFoundContainer}>
|
||||
<Trans>No tokens found.</Trans>
|
||||
</Box>
|
||||
)
|
||||
|
||||
const collectionSearchResults =
|
||||
@@ -121,7 +124,7 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }:
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={collections}
|
||||
header={'NFT Collections'}
|
||||
header={<Trans>NFT Collections</Trans>}
|
||||
/>
|
||||
) : (
|
||||
<Box className={styles.notFoundContainer}>No NFT collections found.</Box>
|
||||
@@ -138,7 +141,7 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }:
|
||||
return {
|
||||
...collection,
|
||||
collectionAddress: collection.address,
|
||||
floorPrice: formatEthPrice(collection.floor.toString()),
|
||||
floorPrice: formatEthPrice(collection.floor?.toString()),
|
||||
stats: {
|
||||
total_supply: collection.totalSupply,
|
||||
one_day_change: collection.floorChange,
|
||||
@@ -182,6 +185,7 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }:
|
||||
setHoveredIndex(hoveredIndex - 1)
|
||||
}
|
||||
} else if (event.key === 'ArrowDown') {
|
||||
event.preventDefault()
|
||||
if (hoveredIndex && hoveredIndex === totalSuggestions - 1) {
|
||||
setHoveredIndex(0)
|
||||
} else {
|
||||
@@ -224,7 +228,7 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }:
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={searchHistory}
|
||||
header={'Recent searches'}
|
||||
header={<Trans>Recent searches</Trans>}
|
||||
headerIcon={<ClockIcon />}
|
||||
/>
|
||||
)}
|
||||
@@ -235,7 +239,7 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }:
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={trendingTokens ?? []}
|
||||
header={'Popular tokens'}
|
||||
header={<Trans>Popular tokens</Trans>}
|
||||
headerIcon={<TrendingArrow />}
|
||||
/>
|
||||
)}
|
||||
@@ -246,7 +250,7 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }:
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={trendingCollections as unknown as GenieCollection[]}
|
||||
header={'Popular NFT collections'}
|
||||
header={<Trans>Popular NFT collections</Trans>}
|
||||
headerIcon={<TrendingArrow />}
|
||||
/>
|
||||
)}
|
||||
@@ -265,9 +269,10 @@ export const SearchBar = () => {
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
const debouncedSearchValue = useDebounce(searchValue, 300)
|
||||
const searchRef = useRef<HTMLDivElement>(null)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const { pathname } = useLocation()
|
||||
const { width: windowWidth } = useWindowSize()
|
||||
const phase1Flag = useNftFlag()
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
useOnClickOutside(searchRef, () => {
|
||||
isOpen && toggleOpen()
|
||||
@@ -317,38 +322,44 @@ export const SearchBar = () => {
|
||||
setSearchValue('')
|
||||
}, [pathname])
|
||||
|
||||
const isMobile = useMemo(() => windowWidth && windowWidth <= breakpoints.tabletSm, [windowWidth])
|
||||
// auto set cursor when searchbar is opened
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
inputRef.current?.focus()
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
const placeholderText = phase1Flag === NftVariant.Enabled ? t`Search tokens and NFT collections` : t`Search tokens`
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box position="relative">
|
||||
<Box
|
||||
position={{ mobile: isOpen ? 'absolute' : 'relative', tabletSm: 'relative' }}
|
||||
top={{ mobile: '0', tabletSm: 'unset' }}
|
||||
left={{ mobile: '0', tabletSm: 'unset' }}
|
||||
width={{ mobile: isOpen ? 'viewWidth' : 'auto', tabletSm: 'auto' }}
|
||||
position={isOpen ? { sm: 'fixed', md: 'absolute' } : 'static'}
|
||||
width={{ sm: isOpen ? 'viewWidth' : 'auto', md: 'auto' }}
|
||||
ref={searchRef}
|
||||
style={{ zIndex: '1000' }}
|
||||
className={styles.searchBarContainer}
|
||||
>
|
||||
<Row
|
||||
className={styles.searchBar}
|
||||
className={clsx(`${styles.searchBar} ${!isOpen && magicalGradientOnHover}`)}
|
||||
borderRadius={isOpen ? undefined : '12'}
|
||||
borderTopRightRadius={isOpen && !isMobile ? '12' : undefined}
|
||||
borderTopLeftRadius={isOpen && !isMobile ? '12' : undefined}
|
||||
display={{ mobile: isOpen ? 'flex' : 'none', desktopXl: 'flex' }}
|
||||
borderBottomWidth={isOpen ? '0px' : '1px'}
|
||||
display={{ sm: isOpen ? 'flex' : 'none', xl: 'flex' }}
|
||||
justifyContent={isOpen || phase1Flag === NftVariant.Enabled ? 'flex-start' : 'center'}
|
||||
background={isOpen ? 'white' : 'lightGrayContainer'}
|
||||
onFocus={() => !isOpen && toggleOpen()}
|
||||
onClick={() => !isOpen && toggleOpen()}
|
||||
gap="12"
|
||||
>
|
||||
<Box display={{ mobile: 'none', tabletSm: 'flex' }}>
|
||||
<MagnifyingGlassIcon className={styles.magnifyingGlassIcon} />
|
||||
<Box display={{ sm: 'none', md: 'flex' }}>
|
||||
<MagnifyingGlassIcon />
|
||||
</Box>
|
||||
<Box display={{ mobile: 'flex', tabletSm: 'none' }} color="blackBlue" onClick={toggleOpen}>
|
||||
<ChevronLeftIcon className={styles.magnifyingGlassIcon} />
|
||||
<Box display={{ sm: 'flex', md: 'none' }} color="placeholder" onClick={toggleOpen}>
|
||||
<ChevronLeftIcon />
|
||||
</Box>
|
||||
<Box
|
||||
as="input"
|
||||
placeholder={`Search tokens${phase1Flag === NftVariant.Enabled ? ' and NFT collections' : ''}`}
|
||||
placeholder={placeholderText}
|
||||
width={isOpen || phase1Flag === NftVariant.Enabled ? 'full' : '120'}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
!isOpen && toggleOpen()
|
||||
@@ -356,26 +367,27 @@ export const SearchBar = () => {
|
||||
}}
|
||||
className={styles.searchBarInput}
|
||||
value={searchValue}
|
||||
ref={inputRef}
|
||||
/>
|
||||
</Row>
|
||||
<Box display={{ mobile: isOpen ? 'none' : 'flex', desktopXl: 'none' }}>
|
||||
<Box display={{ sm: isOpen ? 'none' : 'flex', xl: 'none' }}>
|
||||
<NavIcon onClick={toggleOpen}>
|
||||
<NavMagnifyingGlassIcon width={28} height={28} />
|
||||
</NavIcon>
|
||||
</Box>
|
||||
{isOpen &&
|
||||
(searchValue.length > 0 && (tokensAreLoading || collectionsAreLoading) ? (
|
||||
(debouncedSearchValue.length > 0 && (tokensAreLoading || collectionsAreLoading) ? (
|
||||
<SkeletonRow />
|
||||
) : (
|
||||
<SearchBarDropdown
|
||||
toggleOpen={toggleOpen}
|
||||
tokens={reducedTokens}
|
||||
collections={reducedCollections}
|
||||
hasInput={searchValue.length > 0}
|
||||
hasInput={debouncedSearchValue.length > 0}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
{isOpen && <Overlay />}
|
||||
</>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,14 +4,13 @@ import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { vars } from 'nft/css/sprinkles.css'
|
||||
import { useSearchHistory } from 'nft/hooks'
|
||||
// import { fetchSearchCollections, fetchTrendingCollections } from 'nft/queries'
|
||||
import { FungibleToken, GenieCollection } from 'nft/types'
|
||||
import { ethNumberStandardFormatter } from 'nft/utils/currency'
|
||||
import { putCommas } from 'nft/utils/putCommas'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
|
||||
import { TokenWarningRedIcon, VerifiedIcon } from '../../nft/components/icons'
|
||||
import { VerifiedIcon } from '../../nft/components/icons'
|
||||
import * as styles from './SearchBar.css'
|
||||
|
||||
interface CollectionRowProps {
|
||||
@@ -56,7 +55,7 @@ export const CollectionRow = ({ collection, isHovered, setHoveredIndex, toggleOp
|
||||
onMouseEnter={() => !isHovered && setHoveredIndex(index)}
|
||||
onMouseLeave={() => isHovered && setHoveredIndex(undefined)}
|
||||
className={styles.suggestionRow}
|
||||
style={{ background: isHovered ? vars.color.lightGrayButton : 'none' }}
|
||||
style={{ background: isHovered ? vars.color.lightGrayOverlay : 'none' }}
|
||||
>
|
||||
<Row style={{ width: '60%' }}>
|
||||
{!brokenImage && collection.imageUrl ? (
|
||||
@@ -79,14 +78,14 @@ export const CollectionRow = ({ collection, isHovered, setHoveredIndex, toggleOp
|
||||
<Box className={styles.secondaryText}>{putCommas(collection.stats.total_supply)} items</Box>
|
||||
</Column>
|
||||
</Row>
|
||||
{collection.floorPrice && (
|
||||
{collection.floorPrice ? (
|
||||
<Column className={styles.suggestionSecondaryContainer}>
|
||||
<Row gap="4">
|
||||
<Box className={styles.primaryText}>{ethNumberStandardFormatter(collection.floorPrice)} ETH</Box>
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>Floor</Box>
|
||||
</Column>
|
||||
)}
|
||||
) : null}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -129,13 +128,12 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index
|
||||
|
||||
return (
|
||||
<Link
|
||||
// TODO connect with explore token URI
|
||||
to={`/tokens/${token.address}`}
|
||||
onClick={handleClick}
|
||||
onMouseEnter={() => !isHovered && setHoveredIndex(index)}
|
||||
onMouseLeave={() => isHovered && setHoveredIndex(undefined)}
|
||||
className={styles.suggestionRow}
|
||||
style={{ background: isHovered ? vars.color.lightGrayButton : 'none' }}
|
||||
style={{ background: isHovered ? vars.color.lightGrayOverlay : 'none' }}
|
||||
>
|
||||
<Row style={{ width: '65%' }}>
|
||||
{!brokenImage && token.logoURI ? (
|
||||
@@ -153,11 +151,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index
|
||||
<Column className={styles.suggestionPrimaryContainer}>
|
||||
<Row gap="4" width="full">
|
||||
<Box className={styles.primaryText}>{token.name}</Box>
|
||||
{token.onDefaultList ? (
|
||||
<VerifiedIcon className={styles.suggestionIcon} />
|
||||
) : (
|
||||
<TokenWarningRedIcon className={styles.suggestionIcon} />
|
||||
)}
|
||||
{token.onDefaultList && <VerifiedIcon className={styles.suggestionIcon} />}
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>{token.symbol}</Box>
|
||||
</Column>
|
||||
|
||||
@@ -23,7 +23,7 @@ const Tabs = styled.div`
|
||||
const StyledHistoryLink = styled(HistoryLink)<{ flex: string | undefined }>`
|
||||
flex: ${({ flex }) => flex ?? 'none'};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
flex: none;
|
||||
margin-right: 10px;
|
||||
`};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { animated } from 'react-spring'
|
||||
@@ -29,7 +30,7 @@ const Popup = styled.div`
|
||||
padding-right: 35px;
|
||||
overflow: hidden;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
min-width: 290px;
|
||||
&:not(:last-of-type) {
|
||||
margin-right: 20px;
|
||||
@@ -57,6 +58,7 @@ export default function PopupItem({
|
||||
popKey: string
|
||||
}) {
|
||||
const removePopup = useRemovePopup()
|
||||
const navbarFlag = useNavBarFlag()
|
||||
const removeThisPopup = useCallback(() => removePopup(popKey), [popKey, removePopup])
|
||||
useEffect(() => {
|
||||
if (removeAfterMs === null) return undefined
|
||||
@@ -71,23 +73,24 @@ export default function PopupItem({
|
||||
}, [removeAfterMs, removeThisPopup])
|
||||
|
||||
const theme = useTheme()
|
||||
const faderStyle = useSpring({
|
||||
from: { width: '100%' },
|
||||
to: { width: '0%' },
|
||||
config: { duration: removeAfterMs ?? undefined },
|
||||
})
|
||||
|
||||
let popupContent
|
||||
if ('txn' in content) {
|
||||
const {
|
||||
txn: { hash },
|
||||
} = content
|
||||
if (navbarFlag === NavBarVariant.Enabled) return null
|
||||
|
||||
popupContent = <TransactionPopup hash={hash} />
|
||||
} else if ('failedSwitchNetwork' in content) {
|
||||
popupContent = <FailedNetworkSwitchPopup chainId={content.failedSwitchNetwork} />
|
||||
}
|
||||
|
||||
const faderStyle = useSpring({
|
||||
from: { width: '100%' },
|
||||
to: { width: '0%' },
|
||||
config: { duration: removeAfterMs ?? undefined },
|
||||
})
|
||||
|
||||
return (
|
||||
<Popup>
|
||||
<StyledClose color={theme.deprecated_text2} onClick={removeThisPopup} />
|
||||
|
||||
@@ -21,7 +21,7 @@ const Wrapper = styled(AutoColumn)`
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
overflow: hidden;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
max-width: 100%;
|
||||
`}
|
||||
`
|
||||
|
||||
@@ -17,7 +17,7 @@ const MobilePopupWrapper = styled.div<{ height: string | number }>`
|
||||
margin-bottom: ${({ height }) => (height ? '20px' : 0)};
|
||||
|
||||
display: none;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
display: block;
|
||||
padding-top: 20px;
|
||||
`};
|
||||
@@ -35,8 +35,8 @@ const MobilePopupInner = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const StopOverflowQuery = `@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium + 1}px) and (max-width: ${
|
||||
MEDIA_WIDTHS.upToMedium + 500
|
||||
const StopOverflowQuery = `@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToMedium + 1}px) and (max-width: ${
|
||||
MEDIA_WIDTHS.deprecated_upToMedium + 500
|
||||
}px)`
|
||||
|
||||
const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean; xlPadding: boolean }>`
|
||||
@@ -47,7 +47,7 @@ const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean; xlPadding:
|
||||
width: 100%;
|
||||
z-index: 3;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ const DesktopHeader = styled.div`
|
||||
font-weight: 500;
|
||||
padding: 8px;
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -32,11 +32,11 @@ const MobileHeader = styled.div`
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.upToExtraSmall}px) {
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.deprecated_upToExtraSmall}px) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
@@ -55,7 +55,7 @@ const ToggleLabel = styled.div`
|
||||
`
|
||||
|
||||
const MobileTogglePosition = styled.div`
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.upToExtraSmall}px) {
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.deprecated_upToExtraSmall}px) {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
@@ -48,11 +48,11 @@ const LinkRow = styled(Link)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
}
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
/* flex-direction: row; */
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
flex-direction: column;
|
||||
row-gap: 12px;
|
||||
`};
|
||||
@@ -61,7 +61,7 @@ const LinkRow = styled(Link)`
|
||||
const BadgeText = styled.div`
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
font-size: 12px;
|
||||
`};
|
||||
`
|
||||
@@ -78,7 +78,7 @@ const RangeLineItem = styled(DataLineItem)`
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
border-radius: 12px;
|
||||
padding: 8px 0;
|
||||
@@ -88,7 +88,7 @@ const RangeLineItem = styled(DataLineItem)`
|
||||
const DoubleArrow = styled.span`
|
||||
margin: 0 2px;
|
||||
color: ${({ theme }) => theme.deprecated_text3};
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
margin: 4px;
|
||||
padding: 20px;
|
||||
`};
|
||||
@@ -104,7 +104,7 @@ const ExtentsText = styled.span`
|
||||
color: ${({ theme }) => theme.deprecated_text3};
|
||||
font-size: 14px;
|
||||
margin-right: 4px;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
@@ -122,7 +122,7 @@ const DataText = styled.div`
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
font-size: 14px;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -13,7 +13,7 @@ import styled from 'styled-components/macro'
|
||||
import { currencyId } from 'utils/currencyId'
|
||||
|
||||
const MobileWrapper = styled(AutoColumn)`
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -128,7 +128,7 @@ function CurrencyRow({
|
||||
eventProperties,
|
||||
}: {
|
||||
currency: Currency
|
||||
onSelect: () => void
|
||||
onSelect: (hasWarning: boolean) => void
|
||||
isSelected: boolean
|
||||
otherSelected: boolean
|
||||
style: CSSProperties
|
||||
@@ -159,13 +159,13 @@ function CurrencyRow({
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
style={style}
|
||||
className={`token-item-${key}`}
|
||||
onKeyPress={(e) => (!isSelected && e.key === 'Enter' ? onSelect() : null)}
|
||||
onClick={() => (isSelected ? null : onSelect())}
|
||||
onKeyPress={(e) => (!isSelected && e.key === 'Enter' ? onSelect(!!warning) : null)}
|
||||
onClick={() => (isSelected ? null : onSelect(!!warning))}
|
||||
disabled={isSelected}
|
||||
selected={otherSelected}
|
||||
>
|
||||
<Column>
|
||||
<CurrencyLogo currency={currency} size={'24px'} />
|
||||
<CurrencyLogo currency={currency} size={'36px'} />
|
||||
</Column>
|
||||
<AutoColumn>
|
||||
<Row>
|
||||
@@ -279,7 +279,7 @@ export default function CurrencyList({
|
||||
currencies: Currency[]
|
||||
otherListTokens?: WrappedTokenInfo[]
|
||||
selectedCurrency?: Currency | null
|
||||
onCurrencySelect: (currency: Currency) => void
|
||||
onCurrencySelect: (currency: Currency, hasWarning?: boolean) => void
|
||||
otherCurrency?: Currency | null
|
||||
fixedListRef?: MutableRefObject<FixedSizeList | undefined>
|
||||
showImportView: () => void
|
||||
@@ -308,7 +308,7 @@ export default function CurrencyList({
|
||||
|
||||
const isSelected = Boolean(currency && selectedCurrency && selectedCurrency.equals(currency))
|
||||
const otherSelected = Boolean(currency && otherCurrency && otherCurrency.equals(currency))
|
||||
const handleSelect = () => currency && onCurrencySelect(currency)
|
||||
const handleSelect = (hasWarning: boolean) => currency && onCurrencySelect(currency, hasWarning)
|
||||
|
||||
const token = currency?.wrapped
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ interface CurrencySearchProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
selectedCurrency?: Currency | null
|
||||
onCurrencySelect: (currency: Currency) => void
|
||||
onCurrencySelect: (currency: Currency, hasWarning?: boolean) => void
|
||||
otherSelectedCurrency?: Currency | null
|
||||
showCommonBases?: boolean
|
||||
showCurrencyAmount?: boolean
|
||||
@@ -136,9 +136,9 @@ export function CurrencySearch({
|
||||
}, [debouncedQuery, native, filteredSortedTokens])
|
||||
|
||||
const handleCurrencySelect = useCallback(
|
||||
(currency: Currency) => {
|
||||
onCurrencySelect(currency)
|
||||
onDismiss()
|
||||
(currency: Currency, hasWarning?: boolean) => {
|
||||
onCurrencySelect(currency, hasWarning)
|
||||
if (!hasWarning) onDismiss()
|
||||
},
|
||||
[onDismiss, onCurrencySelect]
|
||||
)
|
||||
|
||||
@@ -3,8 +3,9 @@ import { TokenList } from '@uniswap/token-lists'
|
||||
import TokenSafety from 'components/TokenSafety'
|
||||
import { TokenSafetyVariant, useTokenSafetyFlag } from 'featureFlags/flags/tokenSafety'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { memo, useCallback, useEffect, useState } from 'react'
|
||||
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
|
||||
import { useUserAddedTokens } from 'state/user/hooks'
|
||||
|
||||
import useLast from '../../hooks/useLast'
|
||||
import Modal from '../Modal'
|
||||
@@ -29,9 +30,10 @@ export enum CurrencyModalView {
|
||||
manage,
|
||||
importToken,
|
||||
importList,
|
||||
tokenSafety,
|
||||
}
|
||||
|
||||
export default function CurrencySearchModal({
|
||||
export default memo(function CurrencySearchModal({
|
||||
isOpen,
|
||||
onDismiss,
|
||||
onCurrencySelect,
|
||||
@@ -43,6 +45,7 @@ export default function CurrencySearchModal({
|
||||
}: CurrencySearchModalProps) {
|
||||
const [modalView, setModalView] = useState<CurrencyModalView>(CurrencyModalView.manage)
|
||||
const lastOpen = useLast(isOpen)
|
||||
const userAddedTokens = useUserAddedTokens()
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && !lastOpen) {
|
||||
@@ -50,12 +53,28 @@ export default function CurrencySearchModal({
|
||||
}
|
||||
}, [isOpen, lastOpen])
|
||||
|
||||
const showTokenSafetySpeedbump = (token: Token) => {
|
||||
setWarningToken(token)
|
||||
setModalView(CurrencyModalView.tokenSafety)
|
||||
}
|
||||
|
||||
const tokenSafetyFlag = useTokenSafetyFlag()
|
||||
|
||||
const handleCurrencySelect = useCallback(
|
||||
(currency: Currency) => {
|
||||
onCurrencySelect(currency)
|
||||
onDismiss()
|
||||
(currency: Currency, hasWarning?: boolean) => {
|
||||
if (
|
||||
tokenSafetyFlag === TokenSafetyVariant.Enabled &&
|
||||
hasWarning &&
|
||||
currency.isToken &&
|
||||
!userAddedTokens.find((token) => token.equals(currency))
|
||||
) {
|
||||
showTokenSafetySpeedbump(currency)
|
||||
} else {
|
||||
onCurrencySelect(currency)
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
[onDismiss, onCurrencySelect]
|
||||
[onDismiss, onCurrencySelect, tokenSafetyFlag, userAddedTokens]
|
||||
)
|
||||
|
||||
// for token import view
|
||||
@@ -68,6 +87,9 @@ export default function CurrencySearchModal({
|
||||
const [importList, setImportList] = useState<TokenList | undefined>()
|
||||
const [listURL, setListUrl] = useState<string | undefined>()
|
||||
|
||||
// used for token safety
|
||||
const [warningToken, setWarningToken] = useState<Token | undefined>()
|
||||
|
||||
const showImportView = useCallback(() => setModalView(CurrencyModalView.importToken), [setModalView])
|
||||
const showManageView = useCallback(() => setModalView(CurrencyModalView.manage), [setModalView])
|
||||
const handleBackImport = useCallback(
|
||||
@@ -75,8 +97,6 @@ export default function CurrencySearchModal({
|
||||
[setModalView, prevView]
|
||||
)
|
||||
|
||||
const tokenSafetyFlag = useTokenSafetyFlag()
|
||||
|
||||
// change min height if not searching
|
||||
let minHeight: number | undefined = 80
|
||||
let content = null
|
||||
@@ -98,25 +118,34 @@ export default function CurrencySearchModal({
|
||||
/>
|
||||
)
|
||||
break
|
||||
case CurrencyModalView.tokenSafety:
|
||||
minHeight = undefined
|
||||
if (tokenSafetyFlag === TokenSafetyVariant.Enabled && warningToken) {
|
||||
content = (
|
||||
<TokenSafety
|
||||
tokenAddress={warningToken.address}
|
||||
onContinue={() => handleCurrencySelect(warningToken)}
|
||||
onCancel={() => setModalView(CurrencyModalView.search)}
|
||||
showCancel={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
break
|
||||
case CurrencyModalView.importToken:
|
||||
if (importToken) {
|
||||
minHeight = undefined
|
||||
content =
|
||||
tokenSafetyFlag === TokenSafetyVariant.Enabled ? (
|
||||
<TokenSafety
|
||||
tokenAddress={importToken.address}
|
||||
onContinue={() => handleCurrencySelect(importToken)}
|
||||
onCancel={handleBackImport}
|
||||
/>
|
||||
) : (
|
||||
<ImportToken
|
||||
tokens={[importToken]}
|
||||
onDismiss={onDismiss}
|
||||
list={importToken instanceof WrappedTokenInfo ? importToken.list : undefined}
|
||||
onBack={handleBackImport}
|
||||
handleCurrencySelect={handleCurrencySelect}
|
||||
/>
|
||||
)
|
||||
if (tokenSafetyFlag === TokenSafetyVariant.Enabled) {
|
||||
showTokenSafetySpeedbump(importToken)
|
||||
}
|
||||
content = (
|
||||
<ImportToken
|
||||
tokens={[importToken]}
|
||||
onDismiss={onDismiss}
|
||||
list={importToken instanceof WrappedTokenInfo ? importToken.list : undefined}
|
||||
onBack={handleBackImport}
|
||||
handleCurrencySelect={handleCurrencySelect}
|
||||
/>
|
||||
)
|
||||
}
|
||||
break
|
||||
case CurrencyModalView.importList:
|
||||
@@ -142,4 +171,4 @@ export default function CurrencySearchModal({
|
||||
{content}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -23,7 +23,7 @@ const AddressText = styled(ThemedText.DeprecatedBlue)`
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
font-size: 10px;
|
||||
`}
|
||||
`
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import searchIcon from 'assets/svg/search.svg'
|
||||
import { LoadingRows as BaseLoadingRows } from 'components/Loader/styled'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
@@ -37,14 +38,18 @@ export const MenuItem = styled(RowBetween)<{ redesignFlag?: boolean }>`
|
||||
`
|
||||
|
||||
export const SearchInput = styled.input<{ redesignFlag?: boolean }>`
|
||||
background: no-repeat scroll 7px 7px;
|
||||
background-image: url(${searchIcon});
|
||||
background-size: 20px 20px;
|
||||
background-position: 12px center;
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
padding-left: 40px;
|
||||
height: ${({ redesignFlag }) => redesignFlag && '40px'};
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
background: none;
|
||||
background-color: ${({ theme, redesignFlag }) => redesignFlag && theme.backgroundModule};
|
||||
border: none;
|
||||
outline: none;
|
||||
@@ -62,8 +67,9 @@ export const SearchInput = styled.input<{ redesignFlag?: boolean }>`
|
||||
}
|
||||
transition: border 100ms;
|
||||
:focus {
|
||||
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_primary1)};
|
||||
background-color: ${({ theme, redesignFlag }) => redesignFlag && theme.accentActionSoft};
|
||||
border: 1px solid
|
||||
${({ theme, redesignFlag }) => (redesignFlag ? theme.accentActiveSoft : theme.deprecated_primary1)};
|
||||
background-color: ${({ theme, redesignFlag }) => redesignFlag && theme.backgroundSurface};
|
||||
outline: none;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -99,7 +99,7 @@ const MenuFlyout = styled.span<{ redesignFlag: boolean }>`
|
||||
z-index: 100;
|
||||
color: ${({ theme, redesignFlag }) => redesignFlag && theme.textPrimary};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
min-width: 18.125rem;
|
||||
`};
|
||||
|
||||
|
||||
@@ -6,13 +6,18 @@ import styled, { keyframes } from 'styled-components/macro'
|
||||
const Wrapper = styled.button<{ isActive?: boolean; activeElement?: boolean; redesignFlag: boolean }>`
|
||||
align-items: center;
|
||||
background: ${({ isActive, theme, redesignFlag }) =>
|
||||
redesignFlag && isActive ? theme.accentActionSoft : theme.deprecated_bg1};
|
||||
border: none;
|
||||
redesignFlag && isActive
|
||||
? theme.accentActionSoft
|
||||
: redesignFlag && !isActive
|
||||
? 'transparent'
|
||||
: theme.deprecated_bg1};
|
||||
border: ${({ redesignFlag, theme, isActive }) =>
|
||||
redesignFlag && !isActive ? `1px solid ${theme.backgroundOutline}` : 'none'};
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
outline: none;
|
||||
padding: 0.4rem 0.4rem;
|
||||
padding: ${({ redesignFlag }) => (redesignFlag ? '4px' : '0.4rem 0.4rem')};
|
||||
width: fit-content;
|
||||
`
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ interface TokenSafetyModalProps {
|
||||
secondTokenAddress?: string
|
||||
onContinue: () => void
|
||||
onCancel: () => void
|
||||
showCancel?: boolean
|
||||
}
|
||||
|
||||
export default function TokenSafetyModal({
|
||||
@@ -15,6 +16,7 @@ export default function TokenSafetyModal({
|
||||
secondTokenAddress,
|
||||
onContinue,
|
||||
onCancel,
|
||||
showCancel,
|
||||
}: TokenSafetyModalProps) {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={onCancel}>
|
||||
@@ -23,6 +25,7 @@ export default function TokenSafetyModal({
|
||||
secondTokenAddress={secondTokenAddress}
|
||||
onCancel={onCancel}
|
||||
onContinue={onContinue}
|
||||
showCancel={showCancel}
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -4,14 +4,13 @@ import { ButtonPrimary } from 'components/Button'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import TokenSafetyLabel from 'components/TokenSafety/TokenSafetyLabel'
|
||||
import { checkWarning, getWarningCopy, TOKEN_SAFETY_ARTICLE, Warning, WARNING_LEVEL } from 'constants/tokenSafety'
|
||||
import { checkWarning, getWarningCopy, TOKEN_SAFETY_ARTICLE, Warning } from 'constants/tokenSafety'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { ExternalLink as LinkIconFeather } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import { useAddUserToken } from 'state/user/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ButtonText, CopyLinkIcon, ExternalLink } from 'theme'
|
||||
import { Color } from 'theme/styled'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
@@ -46,61 +45,52 @@ const InfoText = styled(Text)`
|
||||
text-align: center;
|
||||
`
|
||||
|
||||
const StyledButton = styled(ButtonPrimary)<{ buttonColor: Color; textColor: Color }>`
|
||||
color: ${({ textColor }) => textColor};
|
||||
background-color: ${({ buttonColor }) => buttonColor};
|
||||
const StyledButton = styled(ButtonPrimary)`
|
||||
margin-top: 24px;
|
||||
width: 100%;
|
||||
:hover {
|
||||
background-color: ${({ buttonColor, theme }) => buttonColor ?? theme.accentAction};
|
||||
}
|
||||
font-weight: 600;
|
||||
`
|
||||
|
||||
const StyledCancelButton = styled(ButtonText)<{ color?: Color }>`
|
||||
const StyledCancelButton = styled(ButtonText)`
|
||||
margin-top: 16px;
|
||||
color: ${({ color, theme }) => color ?? theme.accentAction};
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
`
|
||||
|
||||
const StyledCloseButton = styled(StyledButton)`
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
opacity: 0.6;
|
||||
transition: opacity 250ms ease;
|
||||
}
|
||||
`
|
||||
|
||||
const Buttons = ({
|
||||
warning,
|
||||
onContinue,
|
||||
onCancel,
|
||||
showCancel,
|
||||
}: {
|
||||
warning: Warning
|
||||
onContinue: () => void
|
||||
onCancel: () => void
|
||||
showCancel?: boolean
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
let textColor, buttonColor, cancelColor
|
||||
switch (warning.level) {
|
||||
case WARNING_LEVEL.MEDIUM:
|
||||
textColor = theme.white
|
||||
buttonColor = theme.accentAction
|
||||
cancelColor = theme.accentAction
|
||||
break
|
||||
case WARNING_LEVEL.UNKNOWN:
|
||||
textColor = theme.accentFailure
|
||||
buttonColor = theme.accentFailureSoft
|
||||
cancelColor = theme.textPrimary
|
||||
break
|
||||
case WARNING_LEVEL.BLOCKED:
|
||||
textColor = theme.textPrimary
|
||||
buttonColor = theme.backgroundInteractive
|
||||
break
|
||||
}
|
||||
return warning.canProceed ? (
|
||||
<>
|
||||
<StyledButton buttonColor={buttonColor} textColor={textColor} onClick={onContinue}>
|
||||
<StyledButton onClick={onContinue}>
|
||||
<Trans>I understand</Trans>
|
||||
</StyledButton>
|
||||
<StyledCancelButton color={cancelColor} onClick={onCancel}>
|
||||
Cancel
|
||||
</StyledCancelButton>
|
||||
{showCancel && <StyledCancelButton onClick={onCancel}>Cancel</StyledCancelButton>}
|
||||
</>
|
||||
) : (
|
||||
<StyledButton buttonColor={buttonColor} textColor={textColor} onClick={onCancel}>
|
||||
<StyledCloseButton onClick={onCancel}>
|
||||
<Trans>Close</Trans>
|
||||
</StyledButton>
|
||||
</StyledCloseButton>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -124,8 +114,8 @@ const ExplorerContainer = styled.div`
|
||||
height: 32px;
|
||||
margin-top: 10px;
|
||||
font-size: 20px;
|
||||
background-color: ${({ theme }) => theme.accentActiveSoft};
|
||||
color: ${({ theme }) => theme.accentActive};
|
||||
background-color: ${({ theme }) => theme.accentActionSoft};
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
border-radius: 8px;
|
||||
padding: 2px 12px;
|
||||
display: flex;
|
||||
@@ -190,14 +180,25 @@ function ExplorerView({ token }: { token: Token }) {
|
||||
}
|
||||
}
|
||||
|
||||
const StyledExternalLink = styled(ExternalLink)`
|
||||
font-weight: 600;
|
||||
`
|
||||
|
||||
interface TokenSafetyProps {
|
||||
tokenAddress: string | null
|
||||
secondTokenAddress?: string
|
||||
onContinue: () => void
|
||||
onCancel: () => void
|
||||
showCancel?: boolean
|
||||
}
|
||||
|
||||
export default function TokenSafety({ tokenAddress, secondTokenAddress, onContinue, onCancel }: TokenSafetyProps) {
|
||||
export default function TokenSafety({
|
||||
tokenAddress,
|
||||
secondTokenAddress,
|
||||
onContinue,
|
||||
onCancel,
|
||||
showCancel,
|
||||
}: TokenSafetyProps) {
|
||||
const logos = []
|
||||
const urls = []
|
||||
|
||||
@@ -254,13 +255,13 @@ export default function TokenSafety({ tokenAddress, secondTokenAddress, onContin
|
||||
<ShortColumn>
|
||||
<InfoText>
|
||||
{description}{' '}
|
||||
<ExternalLink href={TOKEN_SAFETY_ARTICLE}>
|
||||
<StyledExternalLink href={TOKEN_SAFETY_ARTICLE}>
|
||||
<Trans>Learn more</Trans>
|
||||
</ExternalLink>
|
||||
</StyledExternalLink>
|
||||
</InfoText>
|
||||
</ShortColumn>
|
||||
<LinkColumn>{urls}</LinkColumn>
|
||||
<Buttons warning={displayWarning} onContinue={acknowledge} onCancel={onCancel} />
|
||||
<Buttons warning={displayWarning} onContinue={acknowledge} onCancel={onCancel} showCancel={showCancel} />
|
||||
</Container>
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
@@ -15,7 +15,9 @@ const BalancesCard = styled.div`
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
padding: 20px;
|
||||
box-shadow: ${({ theme }) => theme.shallowShadow};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
|
||||
border-radius: 16px;
|
||||
`
|
||||
const ErrorState = styled.div`
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { useNetworkTokenBalances } from 'hooks/useNetworkTokenBalances'
|
||||
import { useState } from 'react'
|
||||
@@ -82,7 +83,7 @@ const SwapButton = styled.button`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
color: ${({ theme }) => theme.accentTextLightPrimary};
|
||||
padding: 12px 16px;
|
||||
width: 120px;
|
||||
height: 44px;
|
||||
@@ -152,7 +153,9 @@ export default function FooterBalanceSummary({
|
||||
) : error ? (
|
||||
<ErrorState>
|
||||
<AlertTriangle size={17} />
|
||||
<ErrorText>There was an error fetching your balance</ErrorText>
|
||||
<ErrorText>
|
||||
<Trans>There was an error fetching your balance</Trans>
|
||||
</ErrorText>
|
||||
</ErrorState>
|
||||
) : (
|
||||
<BalanceInfo>
|
||||
@@ -165,16 +168,21 @@ export default function FooterBalanceSummary({
|
||||
</BalanceTotal>
|
||||
{multipleBalances && (
|
||||
<ViewAll onClick={() => setShowMultipleBalances(!showMultipleBalances)}>
|
||||
{showMultipleBalances ? 'Hide' : 'View'} all balances
|
||||
<Trans>{showMultipleBalances ? 'Hide' : 'View'} all balances</Trans>
|
||||
</ViewAll>
|
||||
)}
|
||||
</BalanceInfo>
|
||||
)}
|
||||
<SwapButton onClick={() => (window.location.href = 'https://app.uniswap.org/#/swap')}>Swap</SwapButton>
|
||||
<SwapButton onClick={() => (window.location.href = 'https://app.uniswap.org/#/swap')}>
|
||||
<Trans>Swap</Trans>
|
||||
</SwapButton>
|
||||
</TotalBalancesSection>
|
||||
{showMultipleBalances && (
|
||||
<NetworkBalancesSection>
|
||||
<NetworkBalancesLabel>Your balances by network</NetworkBalancesLabel> {networkBalances}
|
||||
<NetworkBalancesLabel>
|
||||
<Trans>Your balances by network</Trans>
|
||||
</NetworkBalancesLabel>
|
||||
{networkBalances}
|
||||
</NetworkBalancesSection>
|
||||
)}
|
||||
<FakeFooterNavBar>**leaving space for updated nav footer**</FakeFooterNavBar>
|
||||
|
||||
@@ -3,8 +3,8 @@ import styled, { useTheme } from 'styled-components/macro'
|
||||
import { LoadingBubble } from '../loading'
|
||||
import { DeltaContainer, TokenPrice } from './PriceChart'
|
||||
import {
|
||||
AboutContainer,
|
||||
AboutHeader,
|
||||
AboutSection,
|
||||
BreadcrumbNavLink,
|
||||
ChartContainer,
|
||||
ChartHeader,
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
TokenInfoContainer,
|
||||
TokenNameCell,
|
||||
TopArea,
|
||||
} from './TokenDetail'
|
||||
} from './TokenDetailContainers'
|
||||
|
||||
const LoadingChartContainer = styled(ChartContainer)`
|
||||
height: 336px;
|
||||
@@ -34,15 +34,17 @@ const TitleLoadingBubble = styled(LoadingDetailBubble)`
|
||||
const SquareLoadingBubble = styled(LoadingDetailBubble)`
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 10px;
|
||||
`
|
||||
const PriceLoadingBubble = styled(SquareLoadingBubble)`
|
||||
height: 40px;
|
||||
`
|
||||
const LongLoadingBubble = styled(LoadingDetailBubble)`
|
||||
margin-top: 6px;
|
||||
width: 100%;
|
||||
`
|
||||
const HalfLoadingBubble = styled(LoadingDetailBubble)`
|
||||
margin-top: 6px;
|
||||
width: 50%;
|
||||
`
|
||||
const IconLoadingBubble = styled(LoadingDetailBubble)`
|
||||
@@ -118,16 +120,6 @@ export default function LoadingTokenDetail() {
|
||||
</LoadingChartContainer>
|
||||
<Space heightSize={32} />
|
||||
</ChartHeader>
|
||||
<AboutSection>
|
||||
<AboutHeader>
|
||||
<SquareLoadingBubble />
|
||||
</AboutHeader>
|
||||
<LongLoadingBubble />
|
||||
<LongLoadingBubble />
|
||||
<HalfLoadingBubble />
|
||||
|
||||
<ResourcesContainer>{null}</ResourcesContainer>
|
||||
</AboutSection>
|
||||
<StatsSection>
|
||||
<StatsLoadingContainer>
|
||||
<StatPair>
|
||||
@@ -152,6 +144,16 @@ export default function LoadingTokenDetail() {
|
||||
</StatPair>
|
||||
</StatsLoadingContainer>
|
||||
</StatsSection>
|
||||
<AboutContainer>
|
||||
<AboutHeader>
|
||||
<SquareLoadingBubble />
|
||||
</AboutHeader>
|
||||
<LongLoadingBubble />
|
||||
<LongLoadingBubble />
|
||||
<HalfLoadingBubble />
|
||||
|
||||
<ResourcesContainer>{null}</ResourcesContainer>
|
||||
</AboutContainer>
|
||||
<ContractAddressSection>{null}</ContractAddressSection>
|
||||
</TopArea>
|
||||
)
|
||||
|
||||
@@ -5,10 +5,10 @@ import { EventType } from '@visx/event/lib/types'
|
||||
import { GlyphCircle } from '@visx/glyph'
|
||||
import { Line } from '@visx/shape'
|
||||
import { filterTimeAtom } from 'components/Tokens/state'
|
||||
import { bisect, curveBasis, NumberValue, scaleLinear } from 'd3'
|
||||
import { bisect, curveCardinal, NumberValue, scaleLinear } from 'd3'
|
||||
import { useTokenPriceQuery } from 'graphql/data/TokenPriceQuery'
|
||||
import { TimePeriod } from 'graphql/data/TopTokenQuery'
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
import { TimePeriod } from 'hooks/useExplorePageQuery'
|
||||
import { useAtom } from 'jotai'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { ArrowDownRight, ArrowUpRight } from 'react-feather'
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
dayHourFormatter,
|
||||
hourFormatter,
|
||||
monthDayFormatter,
|
||||
monthFormatter,
|
||||
monthTickFormatter,
|
||||
monthYearDayFormatter,
|
||||
monthYearFormatter,
|
||||
weekFormatter,
|
||||
@@ -47,17 +47,26 @@ const StyledDownArrow = styled(ArrowDownRight)`
|
||||
color: ${({ theme }) => theme.accentFailure};
|
||||
`
|
||||
|
||||
export function getDelta(start: number, current: number) {
|
||||
const delta = (current / start - 1) * 100
|
||||
const isPositive = Math.sign(delta) > 0
|
||||
export function calculateDelta(start: number, current: number) {
|
||||
return (current / start - 1) * 100
|
||||
}
|
||||
|
||||
const formattedDelta = delta.toFixed(2) + '%'
|
||||
if (isPositive) {
|
||||
return ['+' + formattedDelta, <StyledUpArrow size={16} key="arrow-up" />]
|
||||
export function getDeltaArrow(delta: number) {
|
||||
if (Math.sign(delta) > 0) {
|
||||
return <StyledUpArrow size={16} key="arrow-up" />
|
||||
} else if (delta === 0) {
|
||||
return [formattedDelta, null]
|
||||
return null
|
||||
} else {
|
||||
return <StyledDownArrow size={16} key="arrow-down" />
|
||||
}
|
||||
return [formattedDelta, <StyledDownArrow size={16} key="arrow-down" />]
|
||||
}
|
||||
|
||||
export function formatDelta(delta: number) {
|
||||
let formattedDelta = delta.toFixed(2) + '%'
|
||||
if (Math.sign(delta) > 0) {
|
||||
formattedDelta = '+' + formattedDelta
|
||||
}
|
||||
return formattedDelta
|
||||
}
|
||||
|
||||
export const ChartHeader = styled.div`
|
||||
@@ -72,6 +81,7 @@ export const DeltaContainer = styled.div`
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 4px;
|
||||
`
|
||||
const ArrowCell = styled.div`
|
||||
padding-left: 2px;
|
||||
@@ -130,7 +140,7 @@ function tickFormat(
|
||||
case TimePeriod.MONTH:
|
||||
return [monthDayFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
case TimePeriod.YEAR:
|
||||
return [monthFormatter(locale), monthYearDayFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
return [monthTickFormatter(locale), monthYearDayFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
case TimePeriod.ALL:
|
||||
return [monthYearFormatter(locale), monthYearDayFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
}
|
||||
@@ -165,8 +175,9 @@ export function PriceChart({ width, height, token }: PriceChartProps) {
|
||||
const [crosshair, setCrosshair] = useState<number | null>(null)
|
||||
|
||||
const graphWidth = width + crosshairDateOverhang
|
||||
const graphHeight = height - timeOptionsHeight
|
||||
const graphInnerHeight = graphHeight - margin.top - margin.bottom
|
||||
// TODO: remove this logic after suspense is properly added
|
||||
const graphHeight = height - timeOptionsHeight > 0 ? height - timeOptionsHeight : 0
|
||||
const graphInnerHeight = graphHeight - margin.top - margin.bottom > 0 ? graphHeight - margin.top - margin.bottom : 0
|
||||
|
||||
// Defining scales
|
||||
// x scale
|
||||
@@ -177,7 +188,7 @@ export function PriceChart({ width, height, token }: PriceChartProps) {
|
||||
const handleHover = useCallback(
|
||||
(event: Element | EventType) => {
|
||||
const { x } = localPoint(event) || { x: 0 }
|
||||
const x0 = timeScale.invert(x) // get timestamp from the scale
|
||||
const x0 = timeScale.invert(x) // get timestamp from the scalexw
|
||||
const index = bisect(
|
||||
pricePoints.map((x) => x.timestamp),
|
||||
x0,
|
||||
@@ -215,16 +226,21 @@ export function PriceChart({ width, height, token }: PriceChartProps) {
|
||||
timePeriod,
|
||||
locale
|
||||
)
|
||||
const [delta, arrow] = getDelta(startingPrice.value, displayPrice.value)
|
||||
const delta = calculateDelta(startingPrice.value, displayPrice.value)
|
||||
const formattedDelta = formatDelta(delta)
|
||||
const arrow = getDeltaArrow(delta)
|
||||
const crosshairEdgeMax = width * 0.85
|
||||
const crosshairAtEdge = !!crosshair && crosshair > crosshairEdgeMax
|
||||
|
||||
/* Default curve doesn't look good for the ALL chart */
|
||||
const curveTension = timePeriod === TimePeriod.ALL ? 0.75 : 0.9
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChartHeader>
|
||||
<TokenPrice>${displayPrice.value.toFixed(2)}</TokenPrice>
|
||||
<TokenPrice>${displayPrice.value < 0.000001 ? '<0.000001' : displayPrice.value.toFixed(6)}</TokenPrice>
|
||||
<DeltaContainer>
|
||||
{delta}
|
||||
{formattedDelta}
|
||||
<ArrowCell>{arrow}</ArrowCell>
|
||||
</DeltaContainer>
|
||||
</ChartHeader>
|
||||
@@ -233,8 +249,7 @@ export function PriceChart({ width, height, token }: PriceChartProps) {
|
||||
getX={(p: PricePoint) => timeScale(p.timestamp)}
|
||||
getY={(p: PricePoint) => rdScale(p.value)}
|
||||
marginTop={margin.top}
|
||||
/* Default curve doesn't look good for the ALL chart */
|
||||
curve={timePeriod === TimePeriod.ALL ? curveBasis : undefined}
|
||||
curve={curveCardinal.tension(curveTension)}
|
||||
strokeWidth={2}
|
||||
width={graphWidth}
|
||||
height={graphHeight}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { useRef } from 'react'
|
||||
import { Twitter } from 'react-feather'
|
||||
@@ -62,6 +63,7 @@ const ShareAction = styled.div`
|
||||
interface TokenInfo {
|
||||
tokenName: string
|
||||
tokenSymbol: string
|
||||
tokenAddress: string
|
||||
}
|
||||
|
||||
export default function ShareButton(tokenInfo: TokenInfo) {
|
||||
@@ -76,7 +78,7 @@ export default function ShareButton(tokenInfo: TokenInfo) {
|
||||
const shareTweet = () => {
|
||||
toggleShare()
|
||||
window.open(
|
||||
`https://twitter.com/intent/tweet?text=Check%20out%20${tokenInfo.tokenName}%20(${tokenInfo.tokenSymbol})%20https://app.uniswap.org/%23/tokens/${tokenInfo.tokenSymbol}%20via%20@uniswap`,
|
||||
`https://twitter.com/intent/tweet?text=Check%20out%20${tokenInfo.tokenName}%20(${tokenInfo.tokenSymbol})%20https://app.uniswap.org/%23/tokens/${tokenInfo.tokenAddress}%20via%20@uniswap`,
|
||||
'newwindow',
|
||||
`left=${positionX}, top=${positionY}, width=${TWITTER_WIDTH}, height=${TWITTER_HEIGHT}`
|
||||
)
|
||||
@@ -97,13 +99,13 @@ export default function ShareButton(tokenInfo: TokenInfo) {
|
||||
toCopy={window.location.href}
|
||||
ref={copyHelperRef}
|
||||
>
|
||||
Copy Link
|
||||
<Trans>Copy Link</Trans>
|
||||
</CopyHelper>
|
||||
</ShareAction>
|
||||
|
||||
<ShareAction onClick={shareTweet}>
|
||||
<Twitter color={theme.textPrimary} size={20} strokeWidth={1.5} />
|
||||
Share to Twitter
|
||||
<Trans>Share to Twitter</Trans>
|
||||
</ShareAction>
|
||||
</ShareActions>
|
||||
)}
|
||||
|
||||
@@ -1,60 +1,46 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ParentSize } from '@visx/responsive'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import PriceChart from 'components/Tokens/TokenDetails/PriceChart'
|
||||
import { VerifiedIcon } from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { checkWarning } from 'constants/tokenSafety'
|
||||
import { nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
|
||||
import { checkWarning, WARNING_LEVEL } from 'constants/tokenSafety'
|
||||
import { chainIdToChainName, useTokenDetailQuery } from 'graphql/data/TokenDetailQuery'
|
||||
import { useCurrency, useIsUserAddedToken, useToken } from 'hooks/Tokens'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { useCallback } from 'react'
|
||||
import { darken } from 'polished'
|
||||
import { Suspense, useCallback } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { ArrowLeft, Heart, TrendingUp } from 'react-feather'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { ArrowLeft, Heart } from 'react-feather'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ClickableStyle, CopyContractAddress } from 'theme'
|
||||
import { formatDollarAmount } from 'utils/formatDollarAmt'
|
||||
|
||||
import { favoritesAtom, filterNetworkAtom, useToggleFavorite } from '../state'
|
||||
import { ClickFavorited } from '../TokenTable/TokenRow'
|
||||
import { Wave } from './LoadingTokenDetail'
|
||||
import LoadingTokenDetail from './LoadingTokenDetail'
|
||||
import Resource from './Resource'
|
||||
import ShareButton from './ShareButton'
|
||||
import {
|
||||
AboutContainer,
|
||||
AboutHeader,
|
||||
BreadcrumbNavLink,
|
||||
ChartContainer,
|
||||
ChartHeader,
|
||||
ContractAddressSection,
|
||||
ResourcesContainer,
|
||||
Stat,
|
||||
StatPair,
|
||||
StatsSection,
|
||||
TokenInfoContainer,
|
||||
TokenNameCell,
|
||||
TopArea,
|
||||
} from './TokenDetailContainers'
|
||||
|
||||
export const AboutSection = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 24px 0px;
|
||||
`
|
||||
export const AboutHeader = styled.span`
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
`
|
||||
export const BreadcrumbNavLink = styled(Link)`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
text-decoration: none;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
}
|
||||
`
|
||||
export const ChartHeader = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
gap: 4px;
|
||||
margin-bottom: 24px;
|
||||
`
|
||||
const ContractAddress = styled.button`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
@@ -62,12 +48,10 @@ const ContractAddress = styled.button`
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
min-height: 38px;
|
||||
padding: 0px;
|
||||
cursor: pointer;
|
||||
`
|
||||
export const ContractAddressSection = styled.div`
|
||||
padding: 24px 0px;
|
||||
`
|
||||
const Contract = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -75,62 +59,19 @@ const Contract = styled.div`
|
||||
font-size: 14px;
|
||||
gap: 4px;
|
||||
`
|
||||
export const ChartContainer = styled.div`
|
||||
display: flex;
|
||||
height: 436px;
|
||||
align-items: center;
|
||||
`
|
||||
export const Stat = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 14px;
|
||||
min-width: 168px;
|
||||
flex: 1;
|
||||
gap: 4px;
|
||||
padding: 24px 0px;
|
||||
`
|
||||
const StatPrice = styled.span`
|
||||
font-size: 28px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
export const StatsSection = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
export const StatPair = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
export const TokenNameCell = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
align-items: center;
|
||||
`
|
||||
const TokenActions = styled.div`
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
export const TokenInfoContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`
|
||||
const TokenSymbol = styled.span`
|
||||
text-transform: uppercase;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
export const TopArea = styled.div`
|
||||
max-width: 832px;
|
||||
overflow: hidden;
|
||||
`
|
||||
export const ResourcesContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
`
|
||||
const NetworkBadge = styled.div<{ networkColor?: string; backgroundColor?: string }>`
|
||||
border-radius: 5px;
|
||||
padding: 4px 8px;
|
||||
@@ -147,36 +88,99 @@ const FavoriteIcon = styled(Heart)<{ isFavorited: boolean }>`
|
||||
color: ${({ isFavorited, theme }) => (isFavorited ? theme.accentAction : theme.textSecondary)};
|
||||
fill: ${({ isFavorited, theme }) => (isFavorited ? theme.accentAction : 'transparent')};
|
||||
`
|
||||
const ChartEmpty = styled.div`
|
||||
display: flex;
|
||||
height: 400px;
|
||||
align-items: center;
|
||||
`
|
||||
const NoInfoAvailable = styled.span`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
`
|
||||
const MissingChartData = styled.div`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
display: flex;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
padding: 8px 0px;
|
||||
margin-top: -40px;
|
||||
const TokenDescriptionContainer = styled.div`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
max-height: fit-content;
|
||||
padding-top: 16px;
|
||||
line-height: 24px;
|
||||
white-space: pre-wrap;
|
||||
`
|
||||
const MissingData = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
const TruncateDescriptionButton = styled.div`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
padding-top: 14px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: ${({ theme }) => darken(0.1, theme.textSecondary)};
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
const TRUNCATE_CHARACTER_COUNT = 400
|
||||
|
||||
type TokenDetailData = {
|
||||
description: string | null | undefined
|
||||
homepageUrl: string | null | undefined
|
||||
twitterName: string | null | undefined
|
||||
}
|
||||
|
||||
const truncateDescription = (desc: string) => {
|
||||
//trim the string to the maximum length
|
||||
let tokenDescriptionTruncated = desc.slice(0, TRUNCATE_CHARACTER_COUNT)
|
||||
//re-trim if we are in the middle of a word
|
||||
tokenDescriptionTruncated = `${tokenDescriptionTruncated.slice(
|
||||
0,
|
||||
Math.min(tokenDescriptionTruncated.length, tokenDescriptionTruncated.lastIndexOf(' '))
|
||||
)}...`
|
||||
return tokenDescriptionTruncated
|
||||
}
|
||||
|
||||
export function AboutSection({ address, tokenDetailData }: { address: string; tokenDetailData: TokenDetailData }) {
|
||||
const [isDescriptionTruncated, setIsDescriptionTruncated] = useState(true)
|
||||
|
||||
const shouldTruncate =
|
||||
tokenDetailData && tokenDetailData.description
|
||||
? tokenDetailData.description.length > TRUNCATE_CHARACTER_COUNT
|
||||
: false
|
||||
|
||||
const tokenDescription =
|
||||
tokenDetailData && tokenDetailData.description && shouldTruncate && isDescriptionTruncated
|
||||
? truncateDescription(tokenDetailData.description)
|
||||
: tokenDetailData.description
|
||||
|
||||
return (
|
||||
<AboutContainer>
|
||||
<AboutHeader>
|
||||
<Trans>About</Trans>
|
||||
</AboutHeader>
|
||||
<TokenDescriptionContainer>
|
||||
{(!tokenDetailData || !tokenDetailData.description) && (
|
||||
<NoInfoAvailable>
|
||||
<Trans>No token information available</Trans>
|
||||
</NoInfoAvailable>
|
||||
)}
|
||||
{tokenDescription}
|
||||
{shouldTruncate && (
|
||||
<TruncateDescriptionButton onClick={() => setIsDescriptionTruncated(!isDescriptionTruncated)}>
|
||||
{isDescriptionTruncated ? <Trans>Read more</Trans> : <Trans>Hide</Trans>}
|
||||
</TruncateDescriptionButton>
|
||||
)}
|
||||
</TokenDescriptionContainer>
|
||||
<ResourcesContainer>
|
||||
<Resource name={'Etherscan'} link={`https://etherscan.io/address/${address}`} />
|
||||
<Resource name={'Protocol info'} link={`https://info.uniswap.org/#/tokens/${address}`} />
|
||||
{tokenDetailData?.homepageUrl && <Resource name={'Website'} link={tokenDetailData.homepageUrl} />}
|
||||
{tokenDetailData?.twitterName && (
|
||||
<Resource name={'Twitter'} link={`https://twitter.com/${tokenDetailData.twitterName}`} />
|
||||
)}
|
||||
</ResourcesContainer>
|
||||
</AboutContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default function LoadedTokenDetail({ address }: { address: string }) {
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
const token = useToken(address)
|
||||
const currency = useCurrency(address)
|
||||
let currency = useCurrency(address)
|
||||
const favoriteTokens = useAtomValue<string[]>(favoritesAtom)
|
||||
const isFavorited = favoriteTokens.includes(address)
|
||||
const toggleFavorite = useToggleFavorite(address)
|
||||
@@ -188,15 +192,38 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
|
||||
const handleDismissWarning = useCallback(() => {
|
||||
setWarningModalOpen(false)
|
||||
}, [setWarningModalOpen])
|
||||
const handleCancel = useCallback(() => {
|
||||
setWarningModalOpen(false)
|
||||
warning && warning.level === WARNING_LEVEL.BLOCKED && navigate(-1)
|
||||
}, [setWarningModalOpen, navigate, warning])
|
||||
const chainInfo = getChainInfo(token?.chainId)
|
||||
const networkLabel = chainInfo?.label
|
||||
const networkBadgebackgroundColor = chainInfo?.backgroundColor
|
||||
const filterNetwork = useAtomValue(filterNetworkAtom)
|
||||
const tokenDetailData = useTokenDetailQuery(address, chainIdToChainName(filterNetwork))
|
||||
const relevantTokenDetailData = (({ description, homepageUrl, twitterName }) => ({
|
||||
description,
|
||||
homepageUrl,
|
||||
twitterName,
|
||||
}))(tokenDetailData)
|
||||
|
||||
// catch token error and loading state
|
||||
if (!token || !token.name || !token.symbol) {
|
||||
return (
|
||||
if (!token || !token.name || !token.symbol || !connectedChainId) {
|
||||
return <LoadingTokenDetail />
|
||||
}
|
||||
|
||||
const wrappedNativeCurrency = WRAPPED_NATIVE_CURRENCY[connectedChainId]
|
||||
const isWrappedNativeToken = wrappedNativeCurrency?.address === token.address
|
||||
|
||||
if (isWrappedNativeToken) {
|
||||
currency = nativeOnChain(connectedChainId)
|
||||
}
|
||||
|
||||
const tokenName = isWrappedNativeToken && currency ? currency.name : tokenDetailData.name
|
||||
const defaultTokenSymbol = tokenDetailData.tokens?.[0]?.symbol ?? token.symbol
|
||||
const tokenSymbol = isWrappedNativeToken && currency ? currency.symbol : defaultTokenSymbol
|
||||
|
||||
return (
|
||||
<Suspense fallback={<LoadingTokenDetail />}>
|
||||
<TopArea>
|
||||
<BreadcrumbNavLink to="/tokens">
|
||||
<ArrowLeft size={14} /> Tokens
|
||||
@@ -204,9 +231,9 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
|
||||
<ChartHeader>
|
||||
<TokenInfoContainer>
|
||||
<TokenNameCell>
|
||||
<CurrencyLogo currency={currency} size={'32px'} />
|
||||
<Trans>{!token ? 'Name not found' : token.name}</Trans>
|
||||
<TokenSymbol>{token && token.symbol}</TokenSymbol>
|
||||
<CurrencyLogo currency={currency} size={'32px'} symbol={tokenSymbol} />
|
||||
{tokenName ?? <Trans>Name not found</Trans>}
|
||||
<TokenSymbol>{tokenSymbol ?? <Trans>Symbol not found</Trans>}</TokenSymbol>
|
||||
{!warning && <VerifiedIcon size="20px" />}
|
||||
{networkBadgebackgroundColor && (
|
||||
<NetworkBadge networkColor={chainInfo?.color} backgroundColor={networkBadgebackgroundColor}>
|
||||
@@ -214,143 +241,65 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
|
||||
</NetworkBadge>
|
||||
)}
|
||||
</TokenNameCell>
|
||||
<TokenActions>
|
||||
{tokenName && tokenSymbol && (
|
||||
<ShareButton tokenName={tokenName} tokenSymbol={tokenSymbol} tokenAddress={address} />
|
||||
)}
|
||||
<ClickFavorited onClick={toggleFavorite}>
|
||||
<FavoriteIcon isFavorited={isFavorited} />
|
||||
</ClickFavorited>
|
||||
</TokenActions>
|
||||
</TokenInfoContainer>
|
||||
<ChartEmpty>
|
||||
<Wave />
|
||||
<Wave />
|
||||
</ChartEmpty>
|
||||
<MissingChartData>
|
||||
<TrendingUp size={12} />
|
||||
Missing chart data
|
||||
</MissingChartData>
|
||||
<ChartContainer>
|
||||
<ParentSize>{({ width, height }) => <PriceChart token={token} width={width} height={height} />}</ParentSize>
|
||||
</ChartContainer>
|
||||
</ChartHeader>
|
||||
<MissingData>
|
||||
<AboutSection>
|
||||
<AboutHeader>
|
||||
<Trans>About</Trans>
|
||||
</AboutHeader>
|
||||
<NoInfoAvailable>
|
||||
<Trans>No token information available</Trans>
|
||||
</NoInfoAvailable>
|
||||
<ResourcesContainer>
|
||||
<Resource name={'Etherscan'} link={`https://etherscan.io/address/${address}`} />
|
||||
<Resource name={'Protocol Info'} link={`https://info.uniswap.org/#/tokens/${address}`} />
|
||||
</ResourcesContainer>
|
||||
</AboutSection>
|
||||
<StatsSection>
|
||||
<NoInfoAvailable>
|
||||
<Trans>No stats available</Trans>
|
||||
</NoInfoAvailable>
|
||||
</StatsSection>
|
||||
<ContractAddressSection>
|
||||
<Contract>
|
||||
Contract Address
|
||||
<ContractAddress>
|
||||
<CopyContractAddress address={address} />
|
||||
</ContractAddress>
|
||||
</Contract>
|
||||
</ContractAddressSection>
|
||||
</MissingData>
|
||||
<StatsSection>
|
||||
<StatPair>
|
||||
<Stat>
|
||||
<Trans>Market cap</Trans>
|
||||
<StatPrice>
|
||||
{tokenDetailData.marketCap?.value ? formatDollarAmount(tokenDetailData.marketCap?.value) : '-'}
|
||||
</StatPrice>
|
||||
</Stat>
|
||||
<Stat>
|
||||
24H volume
|
||||
<StatPrice>
|
||||
{tokenDetailData.volume24h?.value ? formatDollarAmount(tokenDetailData.volume24h?.value) : '-'}
|
||||
</StatPrice>
|
||||
</Stat>
|
||||
</StatPair>
|
||||
<StatPair>
|
||||
<Stat>
|
||||
52W low
|
||||
<StatPrice>
|
||||
{tokenDetailData.priceLow52W?.value ? formatDollarAmount(tokenDetailData.priceLow52W?.value) : '-'}
|
||||
</StatPrice>
|
||||
</Stat>
|
||||
<Stat>
|
||||
52W high
|
||||
<StatPrice>
|
||||
{tokenDetailData.priceHigh52W?.value ? formatDollarAmount(tokenDetailData.priceHigh52W?.value) : '-'}
|
||||
</StatPrice>
|
||||
</Stat>
|
||||
</StatPair>
|
||||
</StatsSection>
|
||||
<AboutSection address={address} tokenDetailData={relevantTokenDetailData} />
|
||||
<ContractAddressSection>
|
||||
<Contract>
|
||||
<Trans>Contract address</Trans>
|
||||
<ContractAddress>
|
||||
<CopyContractAddress address={address} />
|
||||
</ContractAddress>
|
||||
</Contract>
|
||||
</ContractAddressSection>
|
||||
<TokenSafetyModal
|
||||
isOpen={warningModalOpen}
|
||||
tokenAddress={address}
|
||||
onCancel={() => navigate(-1)}
|
||||
onCancel={handleCancel}
|
||||
onContinue={handleDismissWarning}
|
||||
/>
|
||||
</TopArea>
|
||||
)
|
||||
}
|
||||
|
||||
const tokenName = tokenDetailData.name
|
||||
const tokenSymbol = tokenDetailData.tokens?.[0].symbol?.toUpperCase()
|
||||
|
||||
return (
|
||||
<TopArea>
|
||||
<BreadcrumbNavLink to="/tokens">
|
||||
<ArrowLeft size={14} /> Tokens
|
||||
</BreadcrumbNavLink>
|
||||
<ChartHeader>
|
||||
<TokenInfoContainer>
|
||||
<TokenNameCell>
|
||||
<CurrencyLogo currency={currency} size={'32px'} />
|
||||
{tokenName ?? <Trans>Name not found</Trans>}
|
||||
<TokenSymbol>{tokenSymbol ?? <Trans>Symbol not found</Trans>}</TokenSymbol>
|
||||
{!warning && <VerifiedIcon size="20px" />}
|
||||
{networkBadgebackgroundColor && (
|
||||
<NetworkBadge networkColor={chainInfo?.color} backgroundColor={networkBadgebackgroundColor}>
|
||||
{networkLabel}
|
||||
</NetworkBadge>
|
||||
)}
|
||||
</TokenNameCell>
|
||||
<TokenActions>
|
||||
{tokenName && tokenSymbol && <ShareButton tokenName={tokenName} tokenSymbol={tokenSymbol} />}
|
||||
<ClickFavorited onClick={toggleFavorite}>
|
||||
<FavoriteIcon isFavorited={isFavorited} />
|
||||
</ClickFavorited>
|
||||
</TokenActions>
|
||||
</TokenInfoContainer>
|
||||
<ChartContainer>
|
||||
<ParentSize>{({ width, height }) => <PriceChart token={token} width={width} height={height} />}</ParentSize>
|
||||
</ChartContainer>
|
||||
</ChartHeader>
|
||||
<AboutSection>
|
||||
<AboutHeader>
|
||||
<Trans>About</Trans>
|
||||
</AboutHeader>
|
||||
{tokenDetailData.description}
|
||||
<ResourcesContainer>
|
||||
<Resource name={'Etherscan'} link={`https://etherscan.io/address/${address}`} />
|
||||
<Resource name={'Protocol Info'} link={`https://info.uniswap.org/#/tokens/${address}`} />
|
||||
{tokenDetailData.homepageUrl && <Resource name={'Website'} link={tokenDetailData.homepageUrl} />}
|
||||
{tokenDetailData.twitterName && (
|
||||
<Resource name={'Twitter'} link={`https://twitter.com/${tokenDetailData.twitterName}`} />
|
||||
)}
|
||||
</ResourcesContainer>
|
||||
</AboutSection>
|
||||
<StatsSection>
|
||||
<StatPair>
|
||||
<Stat>
|
||||
Market cap
|
||||
<StatPrice>
|
||||
{tokenDetailData.marketCap?.value ? formatDollarAmount(tokenDetailData.marketCap?.value) : '-'}
|
||||
</StatPrice>
|
||||
</Stat>
|
||||
<Stat>
|
||||
24H volume
|
||||
<StatPrice>
|
||||
{tokenDetailData.volume24h?.value ? formatDollarAmount(tokenDetailData.volume24h?.value) : '-'}
|
||||
</StatPrice>
|
||||
</Stat>
|
||||
</StatPair>
|
||||
<StatPair>
|
||||
<Stat>
|
||||
52W low
|
||||
<StatPrice>
|
||||
{tokenDetailData.priceLow52W?.value ? formatDollarAmount(tokenDetailData.priceLow52W?.value) : '-'}
|
||||
</StatPrice>
|
||||
</Stat>
|
||||
<Stat>
|
||||
52W high
|
||||
<StatPrice>
|
||||
{tokenDetailData.priceHigh52W?.value ? formatDollarAmount(tokenDetailData.priceHigh52W?.value) : '-'}
|
||||
</StatPrice>
|
||||
</Stat>
|
||||
</StatPair>
|
||||
</StatsSection>
|
||||
<ContractAddressSection>
|
||||
<Contract>
|
||||
Contract Address
|
||||
<ContractAddress>
|
||||
<CopyContractAddress address={address} />
|
||||
</ContractAddress>
|
||||
</Contract>
|
||||
</ContractAddressSection>
|
||||
<TokenSafetyModal
|
||||
isOpen={warningModalOpen}
|
||||
tokenAddress={address}
|
||||
onCancel={() => navigate(-1)}
|
||||
onContinue={handleDismissWarning}
|
||||
/>
|
||||
</TopArea>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
81
src/components/Tokens/TokenDetails/TokenDetailContainers.tsx
Normal file
81
src/components/Tokens/TokenDetails/TokenDetailContainers.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
export const AboutContainer = styled.div`
|
||||
gap: 16px;
|
||||
padding: 24px 0px;
|
||||
`
|
||||
export const AboutHeader = styled.span`
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
`
|
||||
export const BreadcrumbNavLink = styled(Link)`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
text-decoration: none;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
}
|
||||
`
|
||||
export const ChartHeader = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
gap: 4px;
|
||||
margin-bottom: 24px;
|
||||
`
|
||||
export const ContractAddressSection = styled.div`
|
||||
padding: 36px 0px;
|
||||
`
|
||||
export const ChartContainer = styled.div`
|
||||
display: flex;
|
||||
height: 436px;
|
||||
align-items: center;
|
||||
`
|
||||
export const Stat = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 14px;
|
||||
min-width: 168px;
|
||||
flex: 1;
|
||||
gap: 4px;
|
||||
padding: 24px 0px;
|
||||
`
|
||||
export const StatsSection = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
export const StatPair = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
export const TokenNameCell = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
align-items: center;
|
||||
`
|
||||
export const TokenInfoContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`
|
||||
export const TopArea = styled.div`
|
||||
max-width: 832px;
|
||||
overflow: hidden;
|
||||
`
|
||||
export const ResourcesContainer = styled.div`
|
||||
display: flex;
|
||||
padding-top: 12px;
|
||||
gap: 14px;
|
||||
`
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useAtom } from 'jotai'
|
||||
import { Heart } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { SMALL_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { SMALLEST_MOBILE_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { showFavoritesAtom } from '../state'
|
||||
|
||||
const FavoriteButtonContent = styled.div`
|
||||
@@ -14,9 +15,9 @@ const FavoriteButtonContent = styled.div`
|
||||
const StyledFavoriteButton = styled.button<{ active: boolean }>`
|
||||
padding: 0px 16px;
|
||||
border-radius: 12px;
|
||||
background-color: ${({ theme, active }) => (active ? theme.accentAction : theme.backgroundInteractive)};
|
||||
background-color: ${({ theme, active }) => (active ? theme.accentActiveSoft : theme.backgroundInteractive)};
|
||||
border: none;
|
||||
color: ${({ theme, active }) => (active ? theme.white : theme.textPrimary)};
|
||||
color: ${({ theme, active }) => (active ? theme.accentActive : theme.textPrimary)};
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
@@ -26,7 +27,7 @@ const StyledFavoriteButton = styled.button<{ active: boolean }>`
|
||||
}
|
||||
`
|
||||
const FavoriteText = styled.span`
|
||||
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
|
||||
@media only screen and (max-width: ${SMALLEST_MOBILE_MEDIA_BREAKPOINT}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
@@ -37,8 +38,14 @@ export default function FavoriteButton() {
|
||||
return (
|
||||
<StyledFavoriteButton onClick={() => setShowFavorites(!showFavorites)} active={showFavorites}>
|
||||
<FavoriteButtonContent>
|
||||
<Heart size={17} color={showFavorites ? theme.white : theme.textPrimary} fill="transparent" />
|
||||
<FavoriteText>Favorites</FavoriteText>
|
||||
<Heart
|
||||
size={17}
|
||||
color={showFavorites ? theme.accentActive : theme.textPrimary}
|
||||
fill={showFavorites ? theme.accentActive : 'transparent'}
|
||||
/>
|
||||
<FavoriteText>
|
||||
<Trans>Favorites</Trans>
|
||||
</FavoriteText>
|
||||
</FavoriteButtonContent>
|
||||
</StyledFavoriteButton>
|
||||
)
|
||||
|
||||
@@ -63,7 +63,7 @@ const MenuTimeFlyout = styled.span`
|
||||
const StyledMenuButton = styled.button<{ open: boolean }>`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: ${({ theme, open }) => (open ? theme.blue200 : theme.textPrimary)};
|
||||
color: ${({ theme, open }) => (open ? theme.accentActive : theme.textPrimary)};
|
||||
border: none;
|
||||
background-color: ${({ theme, open }) => (open ? theme.accentActiveSoft : theme.backgroundInteractive)};
|
||||
margin: 0;
|
||||
@@ -115,7 +115,7 @@ const StyledMenuContent = styled.div`
|
||||
|
||||
const Chevron = styled.span<{ open: boolean }>`
|
||||
padding-top: 1px;
|
||||
color: ${({ open, theme }) => (open ? theme.blue200 : theme.textSecondary)};
|
||||
color: ${({ open, theme }) => (open ? theme.accentActive : theme.textSecondary)};
|
||||
`
|
||||
const NetworkLabel = styled.div`
|
||||
display: flex;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import searchIcon from 'assets/svg/search.svg'
|
||||
import xIcon from 'assets/svg/x.svg'
|
||||
import { useAtom } from 'jotai'
|
||||
@@ -16,24 +17,25 @@ const SearchInput = styled.input`
|
||||
background-image: url(${searchIcon});
|
||||
background-size: 20px 20px;
|
||||
background-position: 12px center;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
border-radius: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
height: 100%;
|
||||
width: min(300px, 100%);
|
||||
width: min(200px, 100%);
|
||||
font-size: 16px;
|
||||
padding-left: 40px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
|
||||
:hover {
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline: none;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border: 1.5px solid ${({ theme }) => theme.accentActionSoft};
|
||||
border-color: ${({ theme }) => theme.accentActionSoft};
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
}
|
||||
@@ -57,14 +59,20 @@ export default function SearchBar() {
|
||||
const [filterString, setFilterString] = useAtom(filterStringAtom)
|
||||
return (
|
||||
<SearchBarContainer>
|
||||
<SearchInput
|
||||
type="search"
|
||||
placeholder="Search tokens"
|
||||
id="searchBar"
|
||||
autoComplete="off"
|
||||
value={filterString}
|
||||
onChange={({ target: { value } }) => setFilterString(value)}
|
||||
/>
|
||||
<Trans
|
||||
render={({ translation }) => (
|
||||
<SearchInput
|
||||
type="search"
|
||||
placeholder={`${translation}`}
|
||||
id="searchBar"
|
||||
autoComplete="off"
|
||||
value={filterString}
|
||||
onChange={({ target: { value } }) => setFilterString(value)}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
Filter tokens
|
||||
</Trans>
|
||||
</SearchBarContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TimePeriod } from 'hooks/useExplorePageQuery'
|
||||
import { TimePeriod } from 'graphql/data/TopTokenQuery'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { useAtom } from 'jotai'
|
||||
import { useRef } from 'react'
|
||||
@@ -81,7 +81,7 @@ const StyledMenuButton = styled.button<{ open: boolean }>`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
color: ${({ theme, open }) => (open ? theme.blue200 : theme.textPrimary)};
|
||||
color: ${({ theme, open }) => (open ? theme.accentActive : theme.textPrimary)};
|
||||
margin: 0;
|
||||
background-color: ${({ theme, open }) => (open ? theme.accentActiveSoft : theme.backgroundInteractive)};
|
||||
padding: 6px 12px 6px 12px;
|
||||
@@ -131,7 +131,7 @@ const StyledMenuContent = styled.div`
|
||||
|
||||
const Chevron = styled.span<{ open: boolean }>`
|
||||
padding-top: 1px;
|
||||
color: ${({ open, theme }) => (open ? theme.blue200 : theme.textSecondary)};
|
||||
color: ${({ open, theme }) => (open ? theme.accentActive : theme.textSecondary)};
|
||||
`
|
||||
|
||||
// TODO: change this to reflect data pipeline
|
||||
|
||||
@@ -5,17 +5,14 @@ import { EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import SparklineChart from 'components/Charts/SparklineChart'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { chainIdToChainName } from 'graphql/data/TokenDetailQuery'
|
||||
import { useTokenPriceQuery } from 'graphql/data/TokenPriceQuery'
|
||||
import { useTokenRowQuery } from 'graphql/data/TokenRowQuery'
|
||||
import { useCurrency, useToken } from 'hooks/Tokens'
|
||||
import { TimePeriod, TokenData } from 'hooks/useExplorePageQuery'
|
||||
import { TimePeriod, TokenData } from 'graphql/data/TopTokenQuery'
|
||||
import { useCurrency } from 'hooks/Tokens'
|
||||
import { useAtom } from 'jotai'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { ReactNode } from 'react'
|
||||
import { ArrowDown, ArrowUp, Heart } from 'react-feather'
|
||||
import { Link } from 'react-router-dom'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import styled, { css, useTheme } from 'styled-components/macro'
|
||||
import { formatDollarAmount } from 'utils/formatDollarAmt'
|
||||
|
||||
import {
|
||||
@@ -35,7 +32,7 @@ import {
|
||||
useSetSortCategory,
|
||||
useToggleFavorite,
|
||||
} from '../state'
|
||||
import { DATA_EMPTY, getDelta, PricePoint } from '../TokenDetails/PriceChart'
|
||||
import { formatDelta, getDeltaArrow } from '../TokenDetails/PriceChart'
|
||||
import { Category, SortDirection } from '../types'
|
||||
import { DISPLAYS } from './TimeSelector'
|
||||
|
||||
@@ -44,20 +41,37 @@ const Cell = styled.div`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
const StyledTokenRow = styled.div`
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
const StyledTokenRow = styled.div<{ first?: boolean; last?: boolean; loading?: boolean }>`
|
||||
background-color: transparent;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 7fr 4fr 4fr 4fr 4fr 5fr 1.2fr;
|
||||
font-size: 15px;
|
||||
grid-template-columns: 1fr 7fr 4fr 4fr 4fr 4fr 5fr 1.2fr;
|
||||
height: 60px;
|
||||
line-height: 24px;
|
||||
|
||||
max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT};
|
||||
min-width: 390px;
|
||||
padding: 0px 12px;
|
||||
padding-top: ${({ first }) => (first ? '4px' : '0px')};
|
||||
padding-bottom: ${({ last }) => (last ? '4px' : '0px')};
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => css`background-color ${duration.medium} ${timing.ease}`};
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.accentActionSoft};
|
||||
${({ loading, theme }) =>
|
||||
!loading &&
|
||||
css`
|
||||
background-color: ${theme.hoverDefault};
|
||||
`}
|
||||
${({ last }) =>
|
||||
last &&
|
||||
css`
|
||||
border-radius: 0px 0px 8px 8px;
|
||||
`}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT}) {
|
||||
@@ -125,6 +139,7 @@ const StyledHeaderRow = styled(StyledTokenRow)`
|
||||
line-height: 16px;
|
||||
padding: 0px 12px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
@@ -134,9 +149,11 @@ const StyledHeaderRow = styled(StyledTokenRow)`
|
||||
justify-content: space-between;
|
||||
}
|
||||
`
|
||||
const ListNumberCell = styled(Cell)`
|
||||
|
||||
const ListNumberCell = styled(Cell)<{ header: boolean }>`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
min-width: 32px;
|
||||
height: ${({ header }) => (header ? '48px' : '60px')};
|
||||
|
||||
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
|
||||
display: none;
|
||||
@@ -147,10 +164,11 @@ const DataCell = styled(Cell)<{ sortable: boolean }>`
|
||||
min-width: 80px;
|
||||
user-select: ${({ sortable }) => (sortable ? 'none' : 'unset')};
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme, sortable }) => sortable && theme.white};
|
||||
background-color: ${({ theme, sortable }) => sortable && theme.accentActionSoft};
|
||||
}
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => css`background-color ${duration.medium} ${timing.ease}`};
|
||||
`
|
||||
const MarketCapCell = styled(DataCell)`
|
||||
padding-right: 8px;
|
||||
@@ -203,6 +221,10 @@ const HeaderCellWrapper = styled.span<{ onClick?: () => void }>`
|
||||
height: 100%;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
opacity: 60%;
|
||||
}
|
||||
`
|
||||
const SparkLineCell = styled(Cell)`
|
||||
padding: 0px 24px;
|
||||
@@ -244,6 +266,7 @@ const TokenName = styled.div`
|
||||
`
|
||||
const TokenSymbol = styled(Cell)`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
text-transform: uppercase;
|
||||
|
||||
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
|
||||
font-size: 12px;
|
||||
@@ -294,7 +317,7 @@ const LogoContainer = styled.div`
|
||||
|
||||
/* formatting for volume with timeframe header display */
|
||||
function getHeaderDisplay(category: string, timeframe: TimePeriod): string {
|
||||
if (category === Category.volume) return `${DISPLAYS[timeframe]} ${category}`
|
||||
if (category === Category.volume || category === Category.percentChange) return `${DISPLAYS[timeframe]} ${category}`
|
||||
return category
|
||||
}
|
||||
|
||||
@@ -341,9 +364,8 @@ function HeaderCell({
|
||||
|
||||
/* Token Row: skeleton row component */
|
||||
export function TokenRow({
|
||||
address,
|
||||
header,
|
||||
favorited,
|
||||
header,
|
||||
listNumber,
|
||||
tokenInfo,
|
||||
price,
|
||||
@@ -351,21 +373,24 @@ export function TokenRow({
|
||||
marketCap,
|
||||
volume,
|
||||
sparkLine,
|
||||
...rest
|
||||
}: {
|
||||
address: ReactNode
|
||||
header: boolean
|
||||
favorited: ReactNode
|
||||
first?: boolean
|
||||
header: boolean
|
||||
listNumber: ReactNode
|
||||
tokenInfo: ReactNode
|
||||
loading?: boolean
|
||||
marketCap: ReactNode
|
||||
price: ReactNode
|
||||
percentChange: ReactNode
|
||||
marketCap: ReactNode
|
||||
volume: ReactNode
|
||||
sparkLine: ReactNode
|
||||
tokenInfo: ReactNode
|
||||
volume: ReactNode
|
||||
last?: boolean
|
||||
}) {
|
||||
const rowCells = (
|
||||
<>
|
||||
<ListNumberCell>{listNumber}</ListNumberCell>
|
||||
<ListNumberCell header={header}>{listNumber}</ListNumberCell>
|
||||
<NameCell>{tokenInfo}</NameCell>
|
||||
<PriceCell sortable={header}>{price}</PriceCell>
|
||||
<PercentChangeCell sortable={header}>{percentChange}</PercentChangeCell>
|
||||
@@ -376,14 +401,13 @@ export function TokenRow({
|
||||
</>
|
||||
)
|
||||
if (header) return <StyledHeaderRow>{rowCells}</StyledHeaderRow>
|
||||
return <StyledTokenRow>{rowCells}</StyledTokenRow>
|
||||
return <StyledTokenRow {...rest}>{rowCells}</StyledTokenRow>
|
||||
}
|
||||
|
||||
/* Header Row: top header row component for table */
|
||||
export function HeaderRow() {
|
||||
return (
|
||||
<TokenRow
|
||||
address={null}
|
||||
header={true}
|
||||
favorited={null}
|
||||
listNumber="#"
|
||||
@@ -401,10 +425,10 @@ export function HeaderRow() {
|
||||
export function LoadingRow() {
|
||||
return (
|
||||
<TokenRow
|
||||
address={null}
|
||||
header={false}
|
||||
favorited={null}
|
||||
header={false}
|
||||
listNumber={<SmallLoadingBubble />}
|
||||
loading
|
||||
tokenInfo={
|
||||
<>
|
||||
<IconLoadingBubble />
|
||||
@@ -425,19 +449,18 @@ export default function LoadedRow({
|
||||
tokenAddress,
|
||||
tokenListIndex,
|
||||
tokenListLength,
|
||||
data,
|
||||
tokenData,
|
||||
timePeriod,
|
||||
}: {
|
||||
tokenAddress: string
|
||||
tokenListIndex: number
|
||||
tokenListLength: number
|
||||
data: TokenData
|
||||
tokenData: TokenData
|
||||
timePeriod: TimePeriod
|
||||
}) {
|
||||
const token = useToken(tokenAddress)
|
||||
const currency = useCurrency(tokenAddress)
|
||||
const tokenName = token?.name ?? ''
|
||||
const tokenSymbol = token?.symbol ?? ''
|
||||
const tokenName = tokenData.name
|
||||
const tokenSymbol = tokenData.symbol
|
||||
const theme = useTheme()
|
||||
const [favoriteTokens] = useAtom(favoritesAtom)
|
||||
const isFavorited = favoriteTokens.includes(tokenAddress)
|
||||
@@ -445,22 +468,14 @@ export default function LoadedRow({
|
||||
const filterString = useAtomValue(filterStringAtom)
|
||||
const filterNetwork = useAtomValue(filterNetworkAtom)
|
||||
const L2Icon = getChainInfo(filterNetwork).circleLogoUrl
|
||||
|
||||
// TODO: make delta shareable and fix based on future changes
|
||||
const pricePoints: PricePoint[] = useTokenPriceQuery(tokenAddress, timePeriod, 'ETHEREUM').filter(
|
||||
(p): p is PricePoint => Boolean(p && p.value)
|
||||
)
|
||||
const hasData = pricePoints.length !== 0
|
||||
|
||||
/* TODO: Implement API calls & cache to use here */
|
||||
const startingPrice = hasData ? pricePoints[0] : DATA_EMPTY
|
||||
const endingPrice = hasData ? pricePoints[pricePoints.length - 1] : DATA_EMPTY
|
||||
const [delta, arrow] = getDelta(startingPrice.value, endingPrice.value)
|
||||
const delta = tokenData.percentChange?.[timePeriod]?.value
|
||||
const arrow = delta ? getDeltaArrow(delta) : null
|
||||
const formattedDelta = delta ? formatDelta(delta) : null
|
||||
|
||||
const exploreTokenSelectedEventProperties = {
|
||||
chain_id: filterNetwork,
|
||||
token_address: tokenAddress,
|
||||
token_symbol: token?.symbol,
|
||||
token_symbol: tokenSymbol,
|
||||
token_list_index: tokenListIndex,
|
||||
token_list_length: tokenListLength,
|
||||
time_frame: timePeriod,
|
||||
@@ -468,8 +483,6 @@ export default function LoadedRow({
|
||||
}
|
||||
|
||||
const heartColor = isFavorited ? theme.accentActive : undefined
|
||||
// TODO: consider using backend network?
|
||||
const tokenRowData = useTokenRowQuery(tokenAddress, timePeriod, chainIdToChainName(filterNetwork))
|
||||
// TODO: currency logo sizing mobile (32px) vs. desktop (24px)
|
||||
return (
|
||||
<StyledLink
|
||||
@@ -477,7 +490,6 @@ export default function LoadedRow({
|
||||
onClick={() => sendAnalyticsEvent(EventName.EXPLORE_TOKEN_ROW_CLICKED, exploreTokenSelectedEventProperties)}
|
||||
>
|
||||
<TokenRow
|
||||
address={tokenAddress}
|
||||
header={false}
|
||||
favorited={
|
||||
<ClickFavorited
|
||||
@@ -493,7 +505,7 @@ export default function LoadedRow({
|
||||
tokenInfo={
|
||||
<ClickableName>
|
||||
<LogoContainer>
|
||||
<CurrencyLogo currency={currency} />
|
||||
<CurrencyLogo currency={currency} symbol={tokenSymbol} />
|
||||
<L2NetworkLogo networkUrl={L2Icon} />
|
||||
</LogoContainer>
|
||||
<TokenInfoCell>
|
||||
@@ -505,9 +517,9 @@ export default function LoadedRow({
|
||||
price={
|
||||
<ClickableContent>
|
||||
<PriceInfoCell>
|
||||
{tokenRowData.price?.value ? formatDollarAmount(tokenRowData.price?.value) : '-'}
|
||||
{tokenData.price?.value ? formatDollarAmount(tokenData.price?.value) : '-'}
|
||||
<PercentChangeInfoCell>
|
||||
{delta}
|
||||
{formattedDelta}
|
||||
{arrow}
|
||||
</PercentChangeInfoCell>
|
||||
</PriceInfoCell>
|
||||
@@ -515,18 +527,20 @@ export default function LoadedRow({
|
||||
}
|
||||
percentChange={
|
||||
<ClickableContent>
|
||||
{delta}
|
||||
{formattedDelta}
|
||||
{arrow}
|
||||
</ClickableContent>
|
||||
}
|
||||
marketCap={
|
||||
<ClickableContent>
|
||||
{tokenRowData.marketCap?.value ? formatDollarAmount(tokenRowData.marketCap?.value) : '-'}
|
||||
{tokenData.marketCap?.value ? formatDollarAmount(tokenData.marketCap?.value) : '-'}
|
||||
</ClickableContent>
|
||||
}
|
||||
volume={
|
||||
<ClickableContent>
|
||||
{tokenRowData.volume?.value ? formatDollarAmount(tokenRowData.volume?.value) : '-'}
|
||||
{tokenData.volume?.[timePeriod]?.value
|
||||
? formatDollarAmount(tokenData.volume?.[timePeriod]?.value ?? undefined)
|
||||
: '-'}
|
||||
</ClickableContent>
|
||||
}
|
||||
sparkLine={
|
||||
@@ -534,6 +548,8 @@ export default function LoadedRow({
|
||||
<ParentSize>{({ width, height }) => <SparklineChart width={width} height={height} />}</ParentSize>
|
||||
</SparkLine>
|
||||
}
|
||||
first={tokenListIndex === 0}
|
||||
last={tokenListIndex === tokenListLength - 1}
|
||||
/>
|
||||
</StyledLink>
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import {
|
||||
favoritesAtom,
|
||||
filterStringAtom,
|
||||
@@ -6,10 +7,9 @@ import {
|
||||
sortCategoryAtom,
|
||||
sortDirectionAtom,
|
||||
} from 'components/Tokens/state'
|
||||
import { useAllTokens } from 'hooks/Tokens'
|
||||
import { TimePeriod, TokenData } from 'hooks/useExplorePageQuery'
|
||||
import { TimePeriod, TokenData } from 'graphql/data/TopTokenQuery'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { ReactNode, useCallback, useMemo } from 'react'
|
||||
import { ReactNode, Suspense, useCallback, useMemo } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
@@ -21,7 +21,7 @@ const GridContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT};
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
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);
|
||||
margin-left: auto;
|
||||
@@ -44,38 +44,36 @@ const NoTokenDisplay = styled.div`
|
||||
gap: 8px;
|
||||
`
|
||||
const TokenRowsContainer = styled.div`
|
||||
padding: 4px 0px;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
function useFilteredTokens(addresses: string[]) {
|
||||
function useFilteredTokens(tokens: TokenData[] | undefined) {
|
||||
const filterString = useAtomValue(filterStringAtom)
|
||||
const favoriteTokens = useAtomValue(favoritesAtom)
|
||||
const favoriteTokenAddresses = useAtomValue(favoritesAtom)
|
||||
const showFavorites = useAtomValue(showFavoritesAtom)
|
||||
const shownTokens = showFavorites ? favoriteTokens : addresses
|
||||
const allTokens = useAllTokens()
|
||||
const shownTokens =
|
||||
showFavorites && tokens ? tokens.filter((token) => favoriteTokenAddresses.includes(token.address)) : tokens
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
shownTokens.filter((tokenAddress) => {
|
||||
const token = allTokens[tokenAddress]
|
||||
const tokenName = token?.name ?? ''
|
||||
const tokenSymbol = token?.symbol ?? ''
|
||||
|
||||
(shownTokens ?? []).filter((token) => {
|
||||
if (!token.address) {
|
||||
return false
|
||||
}
|
||||
if (!filterString) {
|
||||
return true
|
||||
}
|
||||
const lowercaseFilterString = filterString.toLowerCase()
|
||||
const addressIncludesFilterString = tokenAddress.toLowerCase().includes(lowercaseFilterString)
|
||||
const nameIncludesFilterString = tokenName.toLowerCase().includes(lowercaseFilterString)
|
||||
const symbolIncludesFilterString = tokenSymbol.toLowerCase().includes(lowercaseFilterString)
|
||||
const addressIncludesFilterString = token?.address?.toLowerCase().includes(lowercaseFilterString)
|
||||
const nameIncludesFilterString = token?.name?.toLowerCase().includes(lowercaseFilterString)
|
||||
const symbolIncludesFilterString = token?.symbol?.toLowerCase().includes(lowercaseFilterString)
|
||||
return nameIncludesFilterString || symbolIncludesFilterString || addressIncludesFilterString
|
||||
}),
|
||||
[allTokens, shownTokens, filterString]
|
||||
[shownTokens, filterString]
|
||||
)
|
||||
}
|
||||
|
||||
function useSortedTokens(addresses: string[], tokenData: TokenData | null) {
|
||||
function useSortedTokens(tokenData: TokenData[] | null) {
|
||||
const sortCategory = useAtomValue(sortCategoryAtom)
|
||||
const sortDirection = useAtomValue(sortDirectionAtom)
|
||||
const timePeriod = useAtomValue<TimePeriod>(filterTimeAtom)
|
||||
@@ -94,39 +92,38 @@ function useSortedTokens(addresses: string[], tokenData: TokenData | null) {
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
addresses.sort((token1Address, token2Address) => {
|
||||
tokenData &&
|
||||
tokenData.sort((token1, token2) => {
|
||||
if (!tokenData) {
|
||||
return 0
|
||||
}
|
||||
const token1 = tokenData[token1Address] as any
|
||||
const token2 = tokenData[token2Address] as any
|
||||
|
||||
// fix delta/percent change property
|
||||
if (!token1 || !token2 || !sortDirection || !sortCategory) {
|
||||
return 0
|
||||
}
|
||||
let a: number
|
||||
let b: number
|
||||
let a: number | null | undefined
|
||||
let b: number | null | undefined
|
||||
switch (sortCategory) {
|
||||
case Category.marketCap:
|
||||
a = token1.marketCap
|
||||
b = token2.marketCap
|
||||
break
|
||||
case Category.percentChange:
|
||||
a = token1.delta
|
||||
b = token2.delta
|
||||
a = token1.marketCap?.value
|
||||
b = token2.marketCap?.value
|
||||
break
|
||||
case Category.price:
|
||||
a = token1.price
|
||||
b = token2.price
|
||||
a = token1.price?.value
|
||||
b = token2.price?.value
|
||||
break
|
||||
case Category.volume:
|
||||
a = token1.volume[timePeriod]
|
||||
b = token2.volume[timePeriod]
|
||||
a = token1.volume?.[timePeriod]?.value
|
||||
b = token2.volume?.[timePeriod]?.value
|
||||
break
|
||||
case Category.percentChange:
|
||||
a = token1.percentChange?.[timePeriod]?.value
|
||||
b = token2.percentChange?.[timePeriod]?.value
|
||||
break
|
||||
}
|
||||
return sortFn(a, b)
|
||||
}),
|
||||
[addresses, tokenData, sortDirection, sortCategory, sortFn, timePeriod]
|
||||
[tokenData, sortDirection, sortCategory, sortFn, timePeriod]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -143,7 +140,7 @@ const LOADING_ROWS = Array.from({ length: 100 })
|
||||
.fill(0)
|
||||
.map((_item, index) => <LoadingRow key={index} />)
|
||||
|
||||
function LoadingTokenTable() {
|
||||
export function LoadingTokenTable() {
|
||||
return (
|
||||
<GridContainer>
|
||||
<HeaderRow />
|
||||
@@ -152,58 +149,51 @@ function LoadingTokenTable() {
|
||||
)
|
||||
}
|
||||
|
||||
interface TokenTableProps {
|
||||
data: TokenData | null
|
||||
error: string | null
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
export default function TokenTable({ data, error, loading }: TokenTableProps) {
|
||||
export default function TokenTable({ data }: { data: TokenData[] | undefined }) {
|
||||
const showFavorites = useAtomValue<boolean>(showFavoritesAtom)
|
||||
const timePeriod = useAtomValue<TimePeriod>(filterTimeAtom)
|
||||
const topTokenAddresses = data ? Object.keys(data) : []
|
||||
const filteredTokens = useFilteredTokens(topTokenAddresses)
|
||||
const filteredAndSortedTokens = useSortedTokens(filteredTokens, data)
|
||||
const filteredTokens = useFilteredTokens(data)
|
||||
const sortedFilteredTokens = useSortedTokens(filteredTokens)
|
||||
|
||||
/* loading and error state */
|
||||
if (loading) {
|
||||
return <LoadingTokenTable />
|
||||
} else if (error || data === null) {
|
||||
if (data === null) {
|
||||
return (
|
||||
<NoTokensState
|
||||
message={
|
||||
<>
|
||||
<AlertTriangle size={16} />
|
||||
An error occured loading tokens. Please try again.
|
||||
<Trans>An error occured loading tokens. Please try again.</Trans>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (showFavorites && filteredAndSortedTokens.length === 0) {
|
||||
return <NoTokensState message="You have no favorited tokens" />
|
||||
if (showFavorites && sortedFilteredTokens?.length === 0) {
|
||||
return <NoTokensState message={<Trans>You have no favorited tokens</Trans>} />
|
||||
}
|
||||
|
||||
if (!showFavorites && filteredAndSortedTokens.length === 0) {
|
||||
return <NoTokensState message="No tokens found" />
|
||||
if (!showFavorites && sortedFilteredTokens?.length === 0) {
|
||||
return <NoTokensState message={<Trans>No tokens found</Trans>} />
|
||||
}
|
||||
|
||||
return (
|
||||
<GridContainer>
|
||||
<HeaderRow />
|
||||
<TokenRowsContainer>
|
||||
{filteredAndSortedTokens.map((tokenAddress, index) => (
|
||||
<LoadedRow
|
||||
key={tokenAddress}
|
||||
tokenAddress={tokenAddress}
|
||||
tokenListIndex={index}
|
||||
tokenListLength={filteredAndSortedTokens.length}
|
||||
data={data}
|
||||
timePeriod={timePeriod}
|
||||
/>
|
||||
))}
|
||||
</TokenRowsContainer>
|
||||
</GridContainer>
|
||||
<Suspense fallback={<LoadingTokenTable />}>
|
||||
<GridContainer>
|
||||
<HeaderRow />
|
||||
<TokenRowsContainer>
|
||||
{sortedFilteredTokens?.map((token, index) => (
|
||||
<LoadedRow
|
||||
key={token.address}
|
||||
tokenAddress={token.address}
|
||||
tokenListIndex={index}
|
||||
tokenListLength={sortedFilteredTokens.length}
|
||||
tokenData={token}
|
||||
timePeriod={timePeriod}
|
||||
/>
|
||||
))}
|
||||
</TokenRowsContainer>
|
||||
</GridContainer>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
80
src/components/Tokens/TokensBanner.tsx
Normal file
80
src/components/Tokens/TokensBanner.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { X } from 'react-feather'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useShowTokensPromoBanner } from 'state/user/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { opacify } from 'theme/utils'
|
||||
|
||||
import tokensPromoDark from '../../assets/images/tokensPromoDark.png'
|
||||
import tokensPromoLight from '../../assets/images/tokensPromoLight.png'
|
||||
|
||||
const PopupContainer = styled.div<{ show: boolean }>`
|
||||
position: absolute;
|
||||
display: ${({ show }) => (show ? 'flex' : 'none')};
|
||||
flex-direction: column;
|
||||
padding: 12px 16px 12px 20px;
|
||||
gap: 8px;
|
||||
bottom: 48px;
|
||||
right: 16px;
|
||||
width: 320px;
|
||||
height: 88px;
|
||||
z-index: 5;
|
||||
background-color: ${({ theme }) => (theme.darkMode ? theme.backgroundScrim : opacify(60, '#FDF0F8'))};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
border-radius: 12px;
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
|
||||
background-image: url(${({ theme }) => (theme.darkMode ? `${tokensPromoDark}` : `${tokensPromoLight}`)});
|
||||
background-size: cover;
|
||||
background-blend-mode: overlay;
|
||||
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.slow}ms opacity ${timing.in}`};
|
||||
`
|
||||
const Header = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`
|
||||
const HeaderText = styled(Link)`
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
text-decoration: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
const Description = styled(Link)`
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
width: 75%;
|
||||
text-decoration: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
|
||||
export default function TokensBanner() {
|
||||
const theme = useTheme()
|
||||
const [showTokensPromoBanner, setShowTokensPromoBanner] = useShowTokensPromoBanner()
|
||||
|
||||
const closeBanner = () => {
|
||||
setShowTokensPromoBanner(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<PopupContainer show={showTokensPromoBanner}>
|
||||
<Header>
|
||||
<HeaderText to={'/tokens'} onClick={closeBanner}>
|
||||
Explore Top Tokens
|
||||
</HeaderText>
|
||||
<X size={20} color={theme.textSecondary} onClick={closeBanner} style={{ cursor: 'pointer' }} />
|
||||
</Header>
|
||||
|
||||
<Description to={'/tokens'} onClick={closeBanner}>
|
||||
Check out the new explore tab to discover and learn more
|
||||
</Description>
|
||||
</PopupContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { TimePeriod } from 'hooks/useExplorePageQuery'
|
||||
import { TimePeriod } from 'graphql/data/TopTokenQuery'
|
||||
import { atom, useAtom } from 'jotai'
|
||||
import { atomWithReset, atomWithStorage } from 'jotai/utils'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export enum Category {
|
||||
percentChange = '% Change',
|
||||
percentChange = 'Change',
|
||||
marketCap = 'Market Cap',
|
||||
price = 'Price',
|
||||
volume = 'Volume',
|
||||
|
||||
@@ -7,7 +7,7 @@ import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import ms from 'ms.macro'
|
||||
import { darken } from 'polished'
|
||||
import { useState } from 'react'
|
||||
import { useSetUserSlippageTolerance, useUserSlippageTolerance, useUserTransactionTTL } from 'state/user/hooks'
|
||||
import { useUserSlippageTolerance, useUserTransactionTTL } from 'state/user/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { ThemedText } from '../../theme'
|
||||
@@ -64,6 +64,10 @@ const Input = styled.input<{ redesignFlag: boolean }>`
|
||||
}
|
||||
color: ${({ theme, color }) => (color === 'red' ? theme.deprecated_red1 : theme.deprecated_text1)};
|
||||
text-align: right;
|
||||
|
||||
::placeholder {
|
||||
color: ${({ theme, redesignFlag }) => redesignFlag && theme.textTertiary};
|
||||
}
|
||||
`
|
||||
|
||||
const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean; redesignFlag: boolean }>`
|
||||
@@ -91,7 +95,7 @@ const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean;
|
||||
|
||||
const SlippageEmojiContainer = styled.span`
|
||||
color: #f3841e;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
display: none;
|
||||
`}
|
||||
`
|
||||
@@ -108,8 +112,7 @@ export default function TransactionSettings({ placeholderSlippage }: Transaction
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
|
||||
const userSlippageTolerance = useUserSlippageTolerance()
|
||||
const setUserSlippageTolerance = useSetUserSlippageTolerance()
|
||||
const [userSlippageTolerance, setUserSlippageTolerance] = useUserSlippageTolerance()
|
||||
|
||||
const [deadline, setDeadline] = useUserTransactionTTL()
|
||||
|
||||
|
||||
@@ -81,6 +81,10 @@ const HeaderWrapper = styled.div`
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const AuthenticatedHeaderWrapper = styled.div`
|
||||
padding: 0 16px;
|
||||
`
|
||||
|
||||
const AuthenticatedHeader = () => {
|
||||
const { account, chainId, connector } = useWeb3React()
|
||||
const [isCopied, setCopied] = useCopyClipboard()
|
||||
@@ -115,7 +119,7 @@ const AuthenticatedHeader = () => {
|
||||
}, [balanceString, nativeCurrencyPrice])
|
||||
|
||||
return (
|
||||
<>
|
||||
<AuthenticatedHeaderWrapper>
|
||||
<HeaderWrapper>
|
||||
<StatusWrapper>
|
||||
<FlexContainer>
|
||||
@@ -144,7 +148,7 @@ const AuthenticatedHeader = () => {
|
||||
</UNIbutton>
|
||||
)}
|
||||
</Column>
|
||||
</>
|
||||
</AuthenticatedHeaderWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,16 @@ const ConnectButton = styled(ButtonPrimary)`
|
||||
width: 288px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
|
||||
width: 100%;
|
||||
}
|
||||
`
|
||||
|
||||
const Divider = styled.div`
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
`
|
||||
@@ -33,18 +39,23 @@ const ToggleMenuItem = styled.button`
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
border-radius: 12px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 8px 0px;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
padding: 12px 8px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
transition: 250ms color ease;
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.fast}ms all ${timing.in}`};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -69,9 +80,9 @@ const IconWrap = styled.span`
|
||||
`
|
||||
|
||||
const DefaultMenuWrap = styled.div`
|
||||
padding: 0 16px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 8px;
|
||||
`
|
||||
|
||||
const DefaultText = styled.span`
|
||||
|
||||
@@ -24,7 +24,11 @@ const IconStyles = css`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
:hover {
|
||||
background-color: ${({ theme }) => theme.hoverState};
|
||||
transition: background-color 200ms linear;
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.fast}ms background-color ${timing.in}`};
|
||||
|
||||
${IconHoverText} {
|
||||
opacity: 1;
|
||||
|
||||
@@ -18,24 +18,25 @@ const InternalMenuItem = styled(Link)`
|
||||
}
|
||||
`
|
||||
|
||||
const InternalLinkMenuItem = styled(InternalMenuItem)<{ isActive: boolean }>`
|
||||
const InternalLinkMenuItem = styled(InternalMenuItem)`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
justify-content: space-between;
|
||||
text-decoration: none;
|
||||
background-color: ${({ isActive, theme }) => isActive && theme.accentActionSoft};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.fast}ms background-color ${timing.in}`};
|
||||
}
|
||||
`
|
||||
|
||||
const LanguageWrap = styled.div`
|
||||
margin-top: 16px;
|
||||
`
|
||||
|
||||
function LanguageMenuItem({ locale, isActive }: { locale: SupportedLocale; isActive: boolean }) {
|
||||
const { to, onClick } = useLocationLinkProps(locale)
|
||||
const theme = useTheme()
|
||||
@@ -43,11 +44,11 @@ function LanguageMenuItem({ locale, isActive }: { locale: SupportedLocale; isAct
|
||||
if (!to) return null
|
||||
|
||||
return (
|
||||
<InternalLinkMenuItem isActive={isActive} onClick={onClick} to={to}>
|
||||
<InternalLinkMenuItem onClick={onClick} to={to}>
|
||||
<Text fontSize={16} fontWeight={400} lineHeight="24px">
|
||||
{LOCALE_LABEL[locale]}
|
||||
</Text>
|
||||
{isActive && <Check color={theme.accentAction} opacity={1} size={20} />}
|
||||
{isActive && <Check color={theme.accentActive} opacity={1} size={20} />}
|
||||
</InternalLinkMenuItem>
|
||||
)
|
||||
}
|
||||
@@ -57,11 +58,9 @@ const LanguageMenu = ({ onClose }: { onClose: () => void }) => {
|
||||
|
||||
return (
|
||||
<SlideOutMenu title={<Trans>Language</Trans>} onClose={onClose}>
|
||||
<LanguageWrap>
|
||||
{SUPPORTED_LOCALES.map((locale) => (
|
||||
<LanguageMenuItem locale={locale} isActive={activeLocale === locale} key={locale} />
|
||||
))}
|
||||
</LanguageWrap>
|
||||
{SUPPORTED_LOCALES.map((locale) => (
|
||||
<LanguageMenuItem locale={locale} isActive={activeLocale === locale} key={locale} />
|
||||
))}
|
||||
</SlideOutMenu>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import { ChevronLeft } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const BackSection = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 0 16px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
cursor: default;
|
||||
:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
|
||||
const Menu = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 16px;
|
||||
overflow-y: scroll;
|
||||
overflow: auto;
|
||||
|
||||
// Firefox scrollbar styling
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: ${({ theme }) => `${theme.backgroundOutline} transparent`};
|
||||
|
||||
// safari and chrome scrollbar styling
|
||||
::-webkit-scrollbar {
|
||||
background: transparent;
|
||||
width: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
margin-top: 40px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: ${({ theme }) => theme.backgroundOutline};
|
||||
border-radius: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
const Header = styled.span`
|
||||
@@ -29,12 +35,20 @@ const Header = styled.span`
|
||||
const ClearAll = styled.div`
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
margin-left: auto;
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
|
||||
:hover {
|
||||
opacity: 0.6;
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.fast}ms opacity ${timing.in}`};
|
||||
}
|
||||
`
|
||||
|
||||
const StyledChevron = styled(ChevronLeft)`
|
||||
@@ -42,10 +56,37 @@ const StyledChevron = styled(ChevronLeft)`
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
transition: 250ms color ease;
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.fast}ms color ${timing.in}`};
|
||||
}
|
||||
`
|
||||
|
||||
const BackSection = styled.div`
|
||||
position: absolute;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
width: 99%;
|
||||
padding: 0 16px 16px 16px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
cursor: default;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
z-index: 1;
|
||||
`
|
||||
|
||||
const BackSectionContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const ChildrenContainer = styled.div`
|
||||
margin-top: 40px;
|
||||
`
|
||||
|
||||
export const SlideOutMenu = ({
|
||||
children,
|
||||
onClose,
|
||||
@@ -59,10 +100,13 @@ export const SlideOutMenu = ({
|
||||
}) => (
|
||||
<Menu>
|
||||
<BackSection>
|
||||
<StyledChevron onClick={onClose} size={24} />
|
||||
<Header>{title}</Header>
|
||||
{onClear && <ClearAll onClick={onClear}>Clear All</ClearAll>}
|
||||
<BackSectionContainer>
|
||||
<StyledChevron onClick={onClose} size={24} />
|
||||
<Header>{title}</Header>
|
||||
{onClear && <ClearAll onClick={onClear}>Clear All</ClearAll>}
|
||||
</BackSectionContainer>
|
||||
</BackSection>
|
||||
{children}
|
||||
|
||||
<ChildrenContainer>{children}</ChildrenContainer>
|
||||
</Menu>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Z_INDEX } from 'theme'
|
||||
|
||||
import { useModalIsOpen } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import DefaultMenu from './DefaultMenu'
|
||||
import LanguageMenu from './LanguageMenu'
|
||||
import { TransactionHistoryMenu } from './TransactionMenu'
|
||||
@@ -9,14 +12,22 @@ const WalletWrapper = styled.div`
|
||||
border-radius: 12px;
|
||||
width: 320px;
|
||||
max-height: 376px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 16px;
|
||||
top: 60px;
|
||||
right: 70px;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
padding: 16px 0;
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
|
||||
width: 100%;
|
||||
border-bottom-right-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
box-shadow: unset;
|
||||
}
|
||||
`
|
||||
|
||||
export enum MenuState {
|
||||
@@ -25,15 +36,36 @@ export enum MenuState {
|
||||
TRANSACTIONS = 'TRANSACTIONS',
|
||||
}
|
||||
|
||||
const WalletDropdownWrapper = styled.div`
|
||||
position: absolute;
|
||||
top: 65px;
|
||||
right: 20px;
|
||||
z-index: ${Z_INDEX.dropdown};
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
|
||||
top: unset;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 56px;
|
||||
}
|
||||
`
|
||||
|
||||
const WalletDropdown = () => {
|
||||
const [menu, setMenu] = useState<MenuState>(MenuState.DEFAULT)
|
||||
const walletDropdownOpen = useModalIsOpen(ApplicationModal.WALLET_DROPDOWN)
|
||||
|
||||
return (
|
||||
<WalletWrapper>
|
||||
{menu === MenuState.TRANSACTIONS && <TransactionHistoryMenu onClose={() => setMenu(MenuState.DEFAULT)} />}
|
||||
{menu === MenuState.LANGUAGE && <LanguageMenu onClose={() => setMenu(MenuState.DEFAULT)} />}
|
||||
{menu === MenuState.DEFAULT && <DefaultMenu setMenu={setMenu} />}
|
||||
</WalletWrapper>
|
||||
<>
|
||||
{walletDropdownOpen && (
|
||||
<WalletDropdownWrapper>
|
||||
<WalletWrapper>
|
||||
{menu === MenuState.TRANSACTIONS && <TransactionHistoryMenu onClose={() => setMenu(MenuState.DEFAULT)} />}
|
||||
{menu === MenuState.LANGUAGE && <LanguageMenu onClose={() => setMenu(MenuState.DEFAULT)} />}
|
||||
{menu === MenuState.DEFAULT && <DefaultMenu setMenu={setMenu} />}
|
||||
</WalletWrapper>
|
||||
</WalletDropdownWrapper>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ const CheckIcon = styled(Check)`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
align-items: flex-end;
|
||||
`};
|
||||
`
|
||||
@@ -112,7 +112,7 @@ const IconWrapperDeprecated = styled.div<{ size?: number | null }>`
|
||||
height: ${({ size }) => (size ? size + 'px' : '24px')};
|
||||
width: ${({ size }) => (size ? size + 'px' : '24px')};
|
||||
}
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
align-items: flex-end;
|
||||
`};
|
||||
`
|
||||
@@ -127,7 +127,7 @@ const IconWrapper = styled.div<{ size?: number | null }>`
|
||||
height: ${({ size }) => (size ? size + 'px' : '28px')};
|
||||
width: ${({ size }) => (size ? size + 'px' : '28px')};
|
||||
}
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
align-items: flex-end;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { sendAnalyticsEvent, user } from 'components/AmplitudeAnalytics'
|
||||
import {
|
||||
CUSTOM_USER_PROPERTIES,
|
||||
EventName,
|
||||
TOKENS_TO_TRACK,
|
||||
WALLET_CONNECTION_RESULT,
|
||||
} from 'components/AmplitudeAnalytics/constants'
|
||||
import { formatToDecimal } from 'components/AmplitudeAnalytics/utils'
|
||||
import { CUSTOM_USER_PROPERTIES, EventName, WALLET_CONNECTION_RESULT } from 'components/AmplitudeAnalytics/constants'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsInjected, getIsMetaMask } from 'connection/utils'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||
import useCurrencyBalance, { useTokenBalance } from 'lib/hooks/useCurrencyBalance'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import { updateConnectionError } from 'state/connection/reducer'
|
||||
@@ -71,7 +61,7 @@ const HeaderRow = styled.div<{ redesignFlag?: boolean }>`
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
|
||||
size: ${({ redesignFlag }) => redesignFlag && '16px'};
|
||||
color: ${(props) => (props.color === 'blue' ? ({ theme }) => theme.deprecated_primary1 : 'inherit')};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
padding: 1rem;
|
||||
`};
|
||||
`
|
||||
@@ -82,7 +72,7 @@ const ContentWrapper = styled.div<{ redesignFlag?: boolean }>`
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0 1rem 1rem 1rem`};
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`padding: 0 1rem 1rem 1rem`};
|
||||
`
|
||||
|
||||
const UpperSection = styled.div`
|
||||
@@ -105,7 +95,7 @@ const UpperSection = styled.div`
|
||||
const OptionGrid = styled.div`
|
||||
display: grid;
|
||||
grid-gap: 10px;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
grid-template-columns: 1fr;
|
||||
grid-gap: 10px;
|
||||
`};
|
||||
@@ -148,32 +138,6 @@ const sendAnalyticsEventAndUserInfo = (
|
||||
user.postInsert(CUSTOM_USER_PROPERTIES.ALL_WALLET_ADDRESSES_CONNECTED, account)
|
||||
}
|
||||
|
||||
function useLogToken(
|
||||
tokenBalanceUsdValue: string | undefined,
|
||||
tokenBalance: CurrencyAmount<Token | Currency> | undefined,
|
||||
shouldLogTokenBalance: boolean,
|
||||
setShouldLogTokenBalance: (shouldLog: boolean) => void,
|
||||
tokenAmountProperty: string,
|
||||
tokenUsdBalanceProperty: string
|
||||
) {
|
||||
useEffect(() => {
|
||||
if (shouldLogTokenBalance && tokenBalance && tokenBalanceUsdValue) {
|
||||
const tokenBalanceUsd = tokenBalanceUsdValue ? parseFloat(tokenBalanceUsdValue) : 0
|
||||
const tokenBalanceAmount = formatToDecimal(tokenBalance, tokenBalance.currency.decimals)
|
||||
user.set(tokenAmountProperty, tokenBalanceAmount)
|
||||
user.set(tokenUsdBalanceProperty, tokenBalanceUsd)
|
||||
setShouldLogTokenBalance(false)
|
||||
}
|
||||
}, [
|
||||
tokenBalanceUsdValue,
|
||||
tokenBalance,
|
||||
shouldLogTokenBalance,
|
||||
setShouldLogTokenBalance,
|
||||
tokenAmountProperty,
|
||||
tokenUsdBalanceProperty,
|
||||
])
|
||||
}
|
||||
|
||||
export default function WalletModal({
|
||||
pendingTransactions,
|
||||
confirmedTransactions,
|
||||
@@ -191,9 +155,6 @@ export default function WalletModal({
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT)
|
||||
const [lastActiveWalletAddress, setLastActiveWalletAddress] = useState<string | undefined>(account)
|
||||
const [shouldLogUsdcBalance, setShouldLogUsdcBalance] = useState(false)
|
||||
const [shouldLogWethBalance, setShouldLogWethBalance] = useState(false)
|
||||
const [shouldLogNativeBalance, setShouldLogNativeBalance] = useState(false)
|
||||
|
||||
const [pendingConnector, setPendingConnector] = useState<Connector | undefined>()
|
||||
const pendingError = useAppSelector((state) =>
|
||||
@@ -203,15 +164,6 @@ export default function WalletModal({
|
||||
const walletModalOpen = useModalIsOpen(ApplicationModal.WALLET)
|
||||
const toggleWalletModal = useToggleWalletModal()
|
||||
|
||||
const native = useNativeCurrency()
|
||||
const usdcBalance = useTokenBalance(account, TOKENS_TO_TRACK.USDC)
|
||||
const wethBalance = useTokenBalance(account, TOKENS_TO_TRACK.WETH)
|
||||
const nativeCurrencyBalance = useCurrencyBalance(account, native)
|
||||
|
||||
const usdcBalanceUsdValue = useStablecoinValue(usdcBalance)?.toFixed(2)
|
||||
const wethBalanceUsdValue = useStablecoinValue(wethBalance)?.toFixed(2)
|
||||
const nativeCurrencyBalanceUsdValue = useStablecoinValue(nativeCurrencyBalance)?.toFixed(2)
|
||||
|
||||
const openOptions = useCallback(() => {
|
||||
setWalletView(WALLET_VIEWS.OPTIONS)
|
||||
}, [setWalletView])
|
||||
@@ -236,40 +188,11 @@ export default function WalletModal({
|
||||
const isReconnect =
|
||||
connectedWallets.filter((wallet) => wallet.account === account && wallet.walletType === walletType).length > 0
|
||||
sendAnalyticsEventAndUserInfo(account, walletType, chainId, isReconnect)
|
||||
setShouldLogNativeBalance(true)
|
||||
setShouldLogUsdcBalance(true)
|
||||
setShouldLogWethBalance(true)
|
||||
if (!isReconnect) addWalletToConnectedWallets({ account, walletType })
|
||||
}
|
||||
setLastActiveWalletAddress(account)
|
||||
}, [connectedWallets, addWalletToConnectedWallets, lastActiveWalletAddress, account, connector, chainId])
|
||||
|
||||
// Send wallet balances info once it becomes available.
|
||||
useLogToken(
|
||||
nativeCurrencyBalanceUsdValue,
|
||||
nativeCurrencyBalance,
|
||||
shouldLogNativeBalance,
|
||||
setShouldLogNativeBalance,
|
||||
CUSTOM_USER_PROPERTIES.WALLET_NATIVE_CURRENCY_AMOUNT,
|
||||
CUSTOM_USER_PROPERTIES.WALLET_NATIVE_CURRENCY_BALANCE_USD
|
||||
)
|
||||
useLogToken(
|
||||
usdcBalanceUsdValue,
|
||||
usdcBalance,
|
||||
shouldLogUsdcBalance,
|
||||
setShouldLogUsdcBalance,
|
||||
CUSTOM_USER_PROPERTIES.WALLET_USDC_AMOUNT,
|
||||
CUSTOM_USER_PROPERTIES.WALLET_USDC_BALANCE_USD
|
||||
)
|
||||
useLogToken(
|
||||
wethBalanceUsdValue,
|
||||
wethBalance,
|
||||
shouldLogWethBalance,
|
||||
setShouldLogWethBalance,
|
||||
CUSTOM_USER_PROPERTIES.WALLET_WETH_AMOUNT,
|
||||
CUSTOM_USER_PROPERTIES.WALLET_WETH_BALANCE_USD
|
||||
)
|
||||
|
||||
const tryActivation = useCallback(
|
||||
async (connector: Connector) => {
|
||||
const connectionType = getConnection(connector).type
|
||||
|
||||
@@ -3,17 +3,28 @@ import { t, Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import { StyledChevronDown, StyledChevronUp } from 'components/Icons'
|
||||
import WalletDropdown from 'components/WalletDropdown'
|
||||
import { getConnection } from 'connection/utils'
|
||||
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
|
||||
import { Portal } from 'nft/components/common/Portal'
|
||||
import { getIsValidSwapQuote } from 'pages/Swap'
|
||||
import { darken } from 'polished'
|
||||
import { useMemo } from 'react'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import { useDerivedSwapInfo } from 'state/swap/hooks'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import styled, { css, useTheme } from 'styled-components/macro'
|
||||
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
import { useHasSocks } from '../../hooks/useSocksBalance'
|
||||
import { useToggleWalletModal } from '../../state/application/hooks'
|
||||
import {
|
||||
useCloseModal,
|
||||
useModalIsOpen,
|
||||
useToggleWalletDropdown,
|
||||
useToggleWalletModal,
|
||||
} from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { isTransactionRecent, useAllTransactions } from '../../state/transactions/hooks'
|
||||
import { TransactionDetails } from '../../state/transactions/types'
|
||||
import { shortenAddress } from '../../utils'
|
||||
@@ -49,10 +60,26 @@ const Web3StatusError = styled(Web3StatusGeneric)`
|
||||
}
|
||||
`
|
||||
|
||||
const Web3StatusConnectNavbar = styled.button<{ faded?: boolean }>`
|
||||
dispay: flex;
|
||||
align-items: center;
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
background-color: ${({ theme }) => theme.accentActionSoft};
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 8px 12px;
|
||||
|
||||
:hover,
|
||||
:active,
|
||||
:focus {
|
||||
border: none;
|
||||
}
|
||||
`
|
||||
|
||||
const Web3StatusConnect = styled(Web3StatusGeneric)<{ faded?: boolean }>`
|
||||
background-color: ${({ theme }) => theme.deprecated_primary4};
|
||||
border: none;
|
||||
|
||||
color: ${({ theme }) => theme.deprecated_primaryText1};
|
||||
font-weight: 500;
|
||||
|
||||
@@ -125,6 +152,29 @@ function Sock() {
|
||||
)
|
||||
}
|
||||
|
||||
const VerticalDivider = styled.div`
|
||||
height: 20px;
|
||||
margin: 0px 4px;
|
||||
width: 1px;
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
`
|
||||
|
||||
const StyledConnect = styled.div`
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
margin-right: 8px;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.accentActionSoft};
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.fast}ms color ${timing.in}`};
|
||||
}
|
||||
`
|
||||
|
||||
function Web3StatusInner() {
|
||||
const { account, connector, chainId, ENSName } = useWeb3React()
|
||||
const connectionType = getConnection(connector).type
|
||||
@@ -133,6 +183,11 @@ function Web3StatusInner() {
|
||||
inputError: swapInputError,
|
||||
} = useDerivedSwapInfo()
|
||||
const validSwapQuote = getIsValidSwapQuote(trade, tradeState, swapInputError)
|
||||
const navbarFlagEnabled = useNavBarFlag() === NavBarVariant.Enabled
|
||||
const theme = useTheme()
|
||||
const toggleWalletDropdown = useToggleWalletDropdown()
|
||||
const toggleWalletModal = useToggleWalletModal()
|
||||
const walletIsOpen = useIsOpen()
|
||||
|
||||
const error = useAppSelector((state) => state.connection.errorByConnectionType[getConnection(connector).type])
|
||||
|
||||
@@ -147,13 +202,13 @@ function Web3StatusInner() {
|
||||
|
||||
const hasPendingTransactions = !!pending.length
|
||||
const hasSocks = useHasSocks()
|
||||
const toggleWalletModal = useToggleWalletModal()
|
||||
const toggleWallet = navbarFlagEnabled ? toggleWalletDropdown : toggleWalletModal
|
||||
|
||||
if (!chainId) {
|
||||
return null
|
||||
} else if (error) {
|
||||
return (
|
||||
<Web3StatusError onClick={toggleWalletModal}>
|
||||
<Web3StatusError onClick={toggleWallet}>
|
||||
<NetworkIcon />
|
||||
<Text>
|
||||
<Trans>Error</Trans>
|
||||
@@ -162,11 +217,8 @@ function Web3StatusInner() {
|
||||
)
|
||||
} else if (account) {
|
||||
return (
|
||||
<Web3StatusConnected
|
||||
data-testid="web3-status-connected"
|
||||
onClick={toggleWalletModal}
|
||||
pending={hasPendingTransactions}
|
||||
>
|
||||
<Web3StatusConnected data-testid="web3-status-connected" onClick={toggleWallet} pending={hasPendingTransactions}>
|
||||
{navbarFlagEnabled && !hasPendingTransactions && <StatusIcon connectionType={connectionType} />}
|
||||
{hasPendingTransactions ? (
|
||||
<RowBetween>
|
||||
<Text>
|
||||
@@ -176,11 +228,18 @@ function Web3StatusInner() {
|
||||
</RowBetween>
|
||||
) : (
|
||||
<>
|
||||
{hasSocks ? <Sock /> : null}
|
||||
{hasSocks && !navbarFlagEnabled ? <Sock /> : null}
|
||||
<Text>{ENSName || shortenAddress(account)}</Text>
|
||||
{navbarFlagEnabled ? (
|
||||
walletIsOpen ? (
|
||||
<StyledChevronUp onClick={toggleWalletDropdown} />
|
||||
) : (
|
||||
<StyledChevronDown onClick={toggleWalletDropdown} />
|
||||
)
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
{!hasPendingTransactions && <StatusIcon connectionType={connectionType} />}
|
||||
{!navbarFlagEnabled && !hasPendingTransactions && <StatusIcon connectionType={connectionType} />}
|
||||
</Web3StatusConnected>
|
||||
)
|
||||
} else {
|
||||
@@ -191,20 +250,47 @@ function Web3StatusInner() {
|
||||
properties={{ received_swap_quote: validSwapQuote }}
|
||||
element={ElementName.CONNECT_WALLET_BUTTON}
|
||||
>
|
||||
<Web3StatusConnect onClick={toggleWalletModal} faded={!account}>
|
||||
<Text>
|
||||
<Trans>Connect Wallet</Trans>
|
||||
</Text>
|
||||
</Web3StatusConnect>
|
||||
{navbarFlagEnabled ? (
|
||||
<Web3StatusConnectNavbar faded={!account}>
|
||||
<StyledConnect onClick={toggleWalletModal}>
|
||||
<Trans>Connect</Trans>
|
||||
</StyledConnect>
|
||||
<VerticalDivider />
|
||||
{walletIsOpen ? (
|
||||
<StyledChevronUp customColor={theme.accentAction} onClick={toggleWalletDropdown} />
|
||||
) : (
|
||||
<StyledChevronDown customColor={theme.accentAction} onClick={toggleWalletDropdown} />
|
||||
)}
|
||||
</Web3StatusConnectNavbar>
|
||||
) : (
|
||||
<Web3StatusConnect onClick={toggleWallet} faded={!account}>
|
||||
<Text>
|
||||
<Trans>Connect Wallet</Trans>
|
||||
</Text>
|
||||
</Web3StatusConnect>
|
||||
)}
|
||||
</TraceEvent>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const useIsOpen = () => {
|
||||
const walletDropdownOpen = useModalIsOpen(ApplicationModal.WALLET_DROPDOWN)
|
||||
const navbarFlag = useNavBarFlag()
|
||||
|
||||
return useMemo(() => navbarFlag === NavBarVariant.Enabled && walletDropdownOpen, [navbarFlag, walletDropdownOpen])
|
||||
}
|
||||
|
||||
export default function Web3Status() {
|
||||
const { ENSName } = useWeb3React()
|
||||
|
||||
const allTransactions = useAllTransactions()
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const walletRef = useRef<HTMLDivElement>(null)
|
||||
const closeModal = useCloseModal(ApplicationModal.WALLET_DROPDOWN)
|
||||
const isOpen = useIsOpen()
|
||||
|
||||
useOnClickOutside(ref, isOpen ? closeModal : undefined, [walletRef])
|
||||
|
||||
const sortedRecentTransactions = useMemo(() => {
|
||||
const txs = Object.values(allTransactions)
|
||||
@@ -215,9 +301,14 @@ export default function Web3Status() {
|
||||
const confirmed = sortedRecentTransactions.filter((tx) => tx.receipt).map((tx) => tx.hash)
|
||||
|
||||
return (
|
||||
<>
|
||||
<span ref={ref}>
|
||||
<Web3StatusInner />
|
||||
<WalletModal ENSName={ENSName ?? undefined} pendingTransactions={pending} confirmedTransactions={confirmed} />
|
||||
</>
|
||||
<Portal>
|
||||
<span ref={walletRef}>
|
||||
<WalletDropdown />
|
||||
</span>
|
||||
</Portal>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
50
src/components/Widget/index.tsx
Normal file
50
src/components/Widget/index.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Currency, SwapWidget } from '@uniswap/widgets'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { RPC_URLS } from 'constants/networks'
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
import { useMemo } from 'react'
|
||||
import { useIsDarkMode } from 'state/user/hooks'
|
||||
import { DARK_THEME, LIGHT_THEME } from 'theme/widget'
|
||||
|
||||
import { useSyncWidgetInputs } from './inputs'
|
||||
import { useSyncWidgetSettings } from './settings'
|
||||
import { useSyncWidgetTransactions } from './transactions'
|
||||
|
||||
export const WIDGET_WIDTH = 320
|
||||
|
||||
const WIDGET_ROUTER_URL = 'https://api.uniswap.org/v1/'
|
||||
|
||||
export interface WidgetProps {
|
||||
defaultToken?: Currency
|
||||
}
|
||||
|
||||
export default function Widget({ defaultToken }: WidgetProps) {
|
||||
const locale = useActiveLocale()
|
||||
const darkMode = useIsDarkMode()
|
||||
const theme = useMemo(() => (darkMode ? DARK_THEME : LIGHT_THEME), [darkMode])
|
||||
const { provider } = useWeb3React()
|
||||
|
||||
const { inputs, tokenSelector } = useSyncWidgetInputs(defaultToken)
|
||||
const { settings } = useSyncWidgetSettings()
|
||||
const { transactions } = useSyncWidgetTransactions()
|
||||
|
||||
return (
|
||||
<>
|
||||
<SwapWidget
|
||||
disableBranding
|
||||
hideConnectionUI
|
||||
jsonRpcUrlMap={RPC_URLS}
|
||||
routerUrl={WIDGET_ROUTER_URL}
|
||||
width={WIDGET_WIDTH}
|
||||
locale={locale}
|
||||
theme={theme}
|
||||
// defaultChainId is excluded - it is always inferred from the passed provider
|
||||
provider={provider}
|
||||
{...inputs}
|
||||
{...settings}
|
||||
{...transactions}
|
||||
/>
|
||||
{tokenSelector}
|
||||
</>
|
||||
)
|
||||
}
|
||||
88
src/components/Widget/inputs.tsx
Normal file
88
src/components/Widget/inputs.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Currency, Field, SwapController, SwapEventHandlers, TradeType } from '@uniswap/widgets'
|
||||
import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
|
||||
/**
|
||||
* Integrates the Widget's inputs.
|
||||
* Treats the Widget as a controlled component, using the app's own token selector for selection.
|
||||
*/
|
||||
export function useSyncWidgetInputs(defaultToken?: Currency) {
|
||||
const [type, setType] = useState(TradeType.EXACT_INPUT)
|
||||
const [amount, setAmount] = useState('')
|
||||
const onAmountChange = useCallback((field: Field, amount: string) => {
|
||||
setType(toTradeType(field))
|
||||
setAmount(amount)
|
||||
}, [])
|
||||
|
||||
const [tokens, setTokens] = useState<{ [Field.INPUT]?: Currency; [Field.OUTPUT]?: Currency }>({
|
||||
[Field.OUTPUT]: defaultToken,
|
||||
})
|
||||
const onSwitchTokens = useCallback(() => {
|
||||
setType((type) => invertTradeType(type))
|
||||
setTokens((tokens) => ({
|
||||
[Field.INPUT]: tokens[Field.OUTPUT],
|
||||
[Field.OUTPUT]: tokens[Field.INPUT],
|
||||
}))
|
||||
}, [])
|
||||
|
||||
const [selectingField, setSelectingField] = useState<Field>()
|
||||
const otherField = useMemo(() => (selectingField === Field.INPUT ? Field.OUTPUT : Field.INPUT), [selectingField])
|
||||
const [selectingToken, otherToken] = useMemo(() => {
|
||||
if (selectingField === undefined) return [undefined, undefined]
|
||||
return [tokens[selectingField], tokens[otherField]]
|
||||
}, [otherField, selectingField, tokens])
|
||||
const onTokenSelectorClick = useCallback((field: Field) => {
|
||||
setSelectingField(field)
|
||||
return false
|
||||
}, [])
|
||||
const onTokenSelect = useCallback(
|
||||
(token: Currency) => {
|
||||
if (selectingField === undefined) return
|
||||
setType(TradeType.EXACT_INPUT)
|
||||
setTokens(() => {
|
||||
return {
|
||||
[otherField]: token === otherToken ? selectingToken : otherToken,
|
||||
[selectingField]: token,
|
||||
}
|
||||
})
|
||||
},
|
||||
[otherField, otherToken, selectingField, selectingToken]
|
||||
)
|
||||
const tokenSelector = (
|
||||
<CurrencySearchModal
|
||||
isOpen={selectingField !== undefined}
|
||||
onDismiss={() => setSelectingField(undefined)}
|
||||
selectedCurrency={selectingToken}
|
||||
otherSelectedCurrency={otherToken}
|
||||
onCurrencySelect={onTokenSelect}
|
||||
/>
|
||||
)
|
||||
|
||||
const value: SwapController = useMemo(() => ({ type, amount, ...tokens }), [amount, tokens, type])
|
||||
const valueHandlers: SwapEventHandlers = useMemo(
|
||||
() => ({ onAmountChange, onSwitchTokens, onTokenSelectorClick }),
|
||||
[onAmountChange, onSwitchTokens, onTokenSelectorClick]
|
||||
)
|
||||
|
||||
return { inputs: { value, ...valueHandlers }, tokenSelector }
|
||||
}
|
||||
|
||||
// TODO(zzmp): Move to @uniswap/widgets.
|
||||
function toTradeType(modifiedField: Field) {
|
||||
switch (modifiedField) {
|
||||
case Field.INPUT:
|
||||
return TradeType.EXACT_INPUT
|
||||
case Field.OUTPUT:
|
||||
return TradeType.EXACT_OUTPUT
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(zzmp): Include in @uniswap/sdk-core (on TradeType, if possible).
|
||||
function invertTradeType(tradeType: TradeType) {
|
||||
switch (tradeType) {
|
||||
case TradeType.EXACT_INPUT:
|
||||
return TradeType.EXACT_OUTPUT
|
||||
case TradeType.EXACT_OUTPUT:
|
||||
return TradeType.EXACT_INPUT
|
||||
}
|
||||
}
|
||||
57
src/components/Widget/settings.ts
Normal file
57
src/components/Widget/settings.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { Slippage, SwapEventHandlers, SwapSettingsController } from '@uniswap/widgets'
|
||||
import { DEFAULT_DEADLINE_FROM_NOW } from 'constants/misc'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useUserSlippageTolerance, useUserTransactionTTL } from 'state/user/hooks'
|
||||
|
||||
/**
|
||||
* Integrates the Widget's settings, keeping the widget and app settings in sync.
|
||||
* NB: This acts as an integration layer, so certain values are duplicated in order to translate
|
||||
* between app and widget representations.
|
||||
*/
|
||||
export function useSyncWidgetSettings() {
|
||||
const [appTtl, setAppTtl] = useUserTransactionTTL()
|
||||
const [widgetTtl, setWidgetTtl] = useState<number | undefined>(appTtl / 60)
|
||||
const onTransactionDeadlineChange = useCallback(
|
||||
(widgetTtl: number | undefined) => {
|
||||
setWidgetTtl(widgetTtl)
|
||||
const appTtl = widgetTtl === undefined ? widgetTtl : widgetTtl * 60
|
||||
setAppTtl(appTtl ?? DEFAULT_DEADLINE_FROM_NOW)
|
||||
},
|
||||
[setAppTtl]
|
||||
)
|
||||
|
||||
const [appSlippage, setAppSlippage] = useUserSlippageTolerance()
|
||||
const [widgetSlippage, setWidgetSlippage] = useState<string | undefined>(
|
||||
appSlippage === 'auto' ? undefined : appSlippage.toFixed(2)
|
||||
)
|
||||
const onSlippageChange = useCallback(
|
||||
(widgetSlippage: Slippage) => {
|
||||
setWidgetSlippage(widgetSlippage.max)
|
||||
if (widgetSlippage.auto || !widgetSlippage.max) {
|
||||
setAppSlippage('auto')
|
||||
} else {
|
||||
setAppSlippage(new Percent(Math.floor(Number(widgetSlippage.max) * 100), 10_000))
|
||||
}
|
||||
},
|
||||
[setAppSlippage]
|
||||
)
|
||||
|
||||
const onSettingsReset = useCallback(() => {
|
||||
setWidgetTtl(undefined)
|
||||
setAppTtl(DEFAULT_DEADLINE_FROM_NOW)
|
||||
setWidgetSlippage(undefined)
|
||||
setAppSlippage('auto')
|
||||
}, [setAppSlippage, setAppTtl])
|
||||
|
||||
const settings: SwapSettingsController = useMemo(() => {
|
||||
const auto = appSlippage === 'auto'
|
||||
return { slippage: { auto, max: widgetSlippage }, transactionTtl: widgetTtl }
|
||||
}, [widgetSlippage, widgetTtl, appSlippage])
|
||||
const settingsHandlers: SwapEventHandlers = useMemo(
|
||||
() => ({ onSettingsReset, onSlippageChange, onTransactionDeadlineChange }),
|
||||
[onSettingsReset, onSlippageChange, onTransactionDeadlineChange]
|
||||
)
|
||||
|
||||
return { settings: { settings, ...settingsHandlers } }
|
||||
}
|
||||
70
src/components/Widget/transactions.ts
Normal file
70
src/components/Widget/transactions.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
TradeType,
|
||||
Transaction,
|
||||
TransactionEventHandlers,
|
||||
TransactionInfo,
|
||||
TransactionType as WidgetTransactionType,
|
||||
} from '@uniswap/widgets'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useTransactionAdder } from 'state/transactions/hooks'
|
||||
import {
|
||||
ExactInputSwapTransactionInfo,
|
||||
ExactOutputSwapTransactionInfo,
|
||||
TransactionType as AppTransactionType,
|
||||
WrapTransactionInfo,
|
||||
} from 'state/transactions/types'
|
||||
import { currencyId } from 'utils/currencyId'
|
||||
|
||||
/** Integrates the Widget's transactions, showing the widget's transactions in the app. */
|
||||
export function useSyncWidgetTransactions() {
|
||||
const { chainId } = useWeb3React()
|
||||
const addTransaction = useTransactionAdder()
|
||||
|
||||
const onTxSubmit = useCallback(
|
||||
(_hash: string, transaction: Transaction<TransactionInfo>) => {
|
||||
const { type, response } = transaction.info
|
||||
|
||||
if (!type || !response) {
|
||||
return
|
||||
} else if (type === WidgetTransactionType.WRAP || type === WidgetTransactionType.UNWRAP) {
|
||||
const { amount } = transaction.info
|
||||
|
||||
addTransaction(response, {
|
||||
type: AppTransactionType.WRAP,
|
||||
unwrapped: type === WidgetTransactionType.UNWRAP,
|
||||
currencyAmountRaw: amount.quotient.toString(),
|
||||
chainId,
|
||||
} as WrapTransactionInfo)
|
||||
} else if (type === WidgetTransactionType.SWAP) {
|
||||
const { slippageTolerance, trade, tradeType } = transaction.info
|
||||
const baseTxInfo = {
|
||||
type: AppTransactionType.SWAP,
|
||||
tradeType,
|
||||
inputCurrencyId: currencyId(trade.inputAmount.currency),
|
||||
outputCurrencyId: currencyId(trade.outputAmount.currency),
|
||||
}
|
||||
if (tradeType === TradeType.EXACT_OUTPUT) {
|
||||
addTransaction(response, {
|
||||
...baseTxInfo,
|
||||
maximumInputCurrencyAmountRaw: trade.maximumAmountIn(slippageTolerance).quotient.toString(),
|
||||
outputCurrencyAmountRaw: trade.outputAmount.quotient.toString(),
|
||||
expectedInputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
|
||||
} as ExactOutputSwapTransactionInfo)
|
||||
} else {
|
||||
addTransaction(response, {
|
||||
...baseTxInfo,
|
||||
inputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
|
||||
expectedOutputCurrencyAmountRaw: trade.outputAmount.quotient.toString(),
|
||||
minimumOutputCurrencyAmountRaw: trade.minimumAmountOut(slippageTolerance).quotient.toString(),
|
||||
} as ExactInputSwapTransactionInfo)
|
||||
}
|
||||
}
|
||||
},
|
||||
[addTransaction, chainId]
|
||||
)
|
||||
|
||||
const txHandlers: TransactionEventHandlers = useMemo(() => ({ onTxSubmit }), [onTxSubmit])
|
||||
|
||||
return { transactions: { ...txHandlers } }
|
||||
}
|
||||
@@ -26,7 +26,7 @@ const StatContainer = styled.div`
|
||||
margin-bottom: 1rem;
|
||||
margin-right: 1rem;
|
||||
margin-left: 1rem;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
@@ -57,7 +57,7 @@ const TopSection = styled.div`
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
z-index: 1;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
grid-template-columns: 48px 1fr 96px;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { ModalName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { Trace } from 'components/AmplitudeAnalytics/Trace'
|
||||
import { ReactNode, useCallback, useMemo, useState } from 'react'
|
||||
@@ -27,6 +27,8 @@ export default function ConfirmSwapModal({
|
||||
attemptingTxn,
|
||||
txHash,
|
||||
swapQuoteReceivedDate,
|
||||
fiatValueInput,
|
||||
fiatValueOutput,
|
||||
}: {
|
||||
isOpen: boolean
|
||||
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
|
||||
@@ -40,6 +42,8 @@ export default function ConfirmSwapModal({
|
||||
swapErrorMessage: ReactNode | undefined
|
||||
onDismiss: () => void
|
||||
swapQuoteReceivedDate: Date | undefined
|
||||
fiatValueInput?: CurrencyAmount<Token> | null
|
||||
fiatValueOutput?: CurrencyAmount<Token> | null
|
||||
}) {
|
||||
// shouldLogModalCloseEvent lets the child SwapModalHeader component know when modal has been closed
|
||||
// and an event triggered by modal closing should be logged.
|
||||
@@ -73,14 +77,26 @@ export default function ConfirmSwapModal({
|
||||
<SwapModalFooter
|
||||
onConfirm={onConfirm}
|
||||
trade={trade}
|
||||
txHash={txHash}
|
||||
hash={txHash}
|
||||
allowedSlippage={allowedSlippage}
|
||||
disabledConfirm={showAcceptChanges}
|
||||
swapErrorMessage={swapErrorMessage}
|
||||
swapQuoteReceivedDate={swapQuoteReceivedDate}
|
||||
fiatValueInput={fiatValueInput}
|
||||
fiatValueOutput={fiatValueOutput}
|
||||
/>
|
||||
) : null
|
||||
}, [onConfirm, showAcceptChanges, swapErrorMessage, trade, allowedSlippage, txHash, swapQuoteReceivedDate])
|
||||
}, [
|
||||
onConfirm,
|
||||
showAcceptChanges,
|
||||
swapErrorMessage,
|
||||
trade,
|
||||
allowedSlippage,
|
||||
txHash,
|
||||
swapQuoteReceivedDate,
|
||||
fiatValueInput,
|
||||
fiatValueOutput,
|
||||
])
|
||||
|
||||
// text to show while loading
|
||||
const pendingText = (
|
||||
|
||||
@@ -67,7 +67,7 @@ const StyledPolling = styled.div`
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
transition: 250ms ease color;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
display: none;
|
||||
`}
|
||||
`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import {
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
getDurationUntilTimestampSeconds,
|
||||
getTokenAddress,
|
||||
} from 'components/AmplitudeAnalytics/utils'
|
||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||
import useTransactionDeadline from 'hooks/useTransactionDeadline'
|
||||
import { ReactNode } from 'react'
|
||||
import { Text } from 'rebass'
|
||||
@@ -25,15 +24,15 @@ import { getTokenPath, RoutingDiagramEntry } from './SwapRoute'
|
||||
|
||||
interface AnalyticsEventProps {
|
||||
trade: InterfaceTrade<Currency, Currency, TradeType>
|
||||
txHash: string | undefined
|
||||
hash: string | undefined
|
||||
allowedSlippage: Percent
|
||||
transactionDeadlineSecondsSinceEpoch: number | undefined
|
||||
isAutoSlippage: boolean
|
||||
isAutoRouterApi: boolean
|
||||
tokenInAmountUsd: string | undefined
|
||||
tokenOutAmountUsd: string | undefined
|
||||
swapQuoteReceivedDate: Date | undefined
|
||||
routes: RoutingDiagramEntry[]
|
||||
fiatValueInput?: CurrencyAmount<Token> | null
|
||||
fiatValueOutput?: CurrencyAmount<Token> | null
|
||||
}
|
||||
|
||||
const formatRoutesEventProperties = (routes: RoutingDiagramEntry[]) => {
|
||||
@@ -65,27 +64,27 @@ const formatRoutesEventProperties = (routes: RoutingDiagramEntry[]) => {
|
||||
|
||||
const formatAnalyticsEventProperties = ({
|
||||
trade,
|
||||
txHash,
|
||||
hash,
|
||||
allowedSlippage,
|
||||
transactionDeadlineSecondsSinceEpoch,
|
||||
isAutoSlippage,
|
||||
isAutoRouterApi,
|
||||
tokenInAmountUsd,
|
||||
tokenOutAmountUsd,
|
||||
swapQuoteReceivedDate,
|
||||
routes,
|
||||
fiatValueInput,
|
||||
fiatValueOutput,
|
||||
}: AnalyticsEventProps) => ({
|
||||
estimated_network_fee_usd: trade.gasUseEstimateUSD ? formatToDecimal(trade.gasUseEstimateUSD, 2) : undefined,
|
||||
transaction_hash: txHash,
|
||||
transaction_hash: hash,
|
||||
transaction_deadline_seconds: getDurationUntilTimestampSeconds(transactionDeadlineSecondsSinceEpoch),
|
||||
token_in_amount_usd: tokenInAmountUsd ? parseFloat(tokenInAmountUsd) : undefined,
|
||||
token_out_amount_usd: tokenOutAmountUsd ? parseFloat(tokenOutAmountUsd) : undefined,
|
||||
token_in_address: getTokenAddress(trade.inputAmount.currency),
|
||||
token_out_address: getTokenAddress(trade.outputAmount.currency),
|
||||
token_in_symbol: trade.inputAmount.currency.symbol,
|
||||
token_out_symbol: trade.outputAmount.currency.symbol,
|
||||
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
|
||||
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
|
||||
token_in_amount_usd: fiatValueInput ? parseFloat(fiatValueInput.toFixed(2)) : undefined,
|
||||
token_out_amount_usd: fiatValueOutput ? parseFloat(fiatValueOutput.toFixed(2)) : undefined,
|
||||
price_impact_basis_points: formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)),
|
||||
allowed_slippage_basis_points: formatPercentInBasisPointsNumber(allowedSlippage),
|
||||
is_auto_router_api: isAutoRouterApi,
|
||||
@@ -104,25 +103,27 @@ const formatAnalyticsEventProperties = ({
|
||||
export default function SwapModalFooter({
|
||||
trade,
|
||||
allowedSlippage,
|
||||
txHash,
|
||||
hash,
|
||||
onConfirm,
|
||||
swapErrorMessage,
|
||||
disabledConfirm,
|
||||
swapQuoteReceivedDate,
|
||||
fiatValueInput,
|
||||
fiatValueOutput,
|
||||
}: {
|
||||
trade: InterfaceTrade<Currency, Currency, TradeType>
|
||||
txHash: string | undefined
|
||||
hash: string | undefined
|
||||
allowedSlippage: Percent
|
||||
onConfirm: () => void
|
||||
swapErrorMessage: ReactNode | undefined
|
||||
disabledConfirm: boolean
|
||||
swapQuoteReceivedDate: Date | undefined
|
||||
fiatValueInput?: CurrencyAmount<Token> | null
|
||||
fiatValueOutput?: CurrencyAmount<Token> | null
|
||||
}) {
|
||||
const transactionDeadlineSecondsSinceEpoch = useTransactionDeadline()?.toNumber() // in seconds since epoch
|
||||
const isAutoSlippage = useUserSlippageTolerance() === 'auto'
|
||||
const isAutoSlippage = useUserSlippageTolerance()[0] === 'auto'
|
||||
const [clientSideRouter] = useClientSideRouter()
|
||||
const tokenInAmountUsd = useStablecoinValue(trade.inputAmount)?.toFixed(2)
|
||||
const tokenOutAmountUsd = useStablecoinValue(trade.outputAmount)?.toFixed(2)
|
||||
const routes = getTokenPath(trade)
|
||||
|
||||
return (
|
||||
@@ -134,15 +135,15 @@ export default function SwapModalFooter({
|
||||
name={EventName.SWAP_SUBMITTED}
|
||||
properties={formatAnalyticsEventProperties({
|
||||
trade,
|
||||
txHash,
|
||||
hash,
|
||||
allowedSlippage,
|
||||
transactionDeadlineSecondsSinceEpoch,
|
||||
isAutoSlippage,
|
||||
isAutoRouterApi: !clientSideRouter,
|
||||
tokenInAmountUsd,
|
||||
tokenOutAmountUsd,
|
||||
swapQuoteReceivedDate,
|
||||
routes,
|
||||
fiatValueInput,
|
||||
fiatValueOutput,
|
||||
})}
|
||||
>
|
||||
<ButtonError
|
||||
|
||||
@@ -40,7 +40,7 @@ const StyledButtonEmpty = styled(ButtonEmpty)`
|
||||
const AddressText = styled(ThemedText.DeprecatedBlue)`
|
||||
font-size: 12px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
font-size: 10px;
|
||||
`}
|
||||
`
|
||||
|
||||
@@ -8,10 +8,18 @@ import { Z_INDEX } from 'theme'
|
||||
|
||||
import { AutoColumn } from '../Column'
|
||||
|
||||
export const PageWrapper = styled.div<{ redesignFlag: boolean }>`
|
||||
padding: 0 8px;
|
||||
export const PageWrapper = styled.div<{ redesignFlag: boolean; navBarFlag: boolean }>`
|
||||
padding: ${({ navBarFlag }) => (navBarFlag ? '68px 8px 0px' : '0px 8px')};
|
||||
max-width: ${({ redesignFlag }) => (redesignFlag ? '420px' : '480px')};
|
||||
width: 100%;
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
|
||||
padding-top: ${({ navBarFlag }) => (navBarFlag ? '48px' : '0px')};
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
|
||||
padding-top: ${({ navBarFlag }) => (navBarFlag ? '20px' : '0px')};
|
||||
}
|
||||
`
|
||||
|
||||
// Mostly copied from `AppBody` but it was getting too hard to maintain backwards compatibility.
|
||||
@@ -154,7 +162,7 @@ export const ResponsiveTooltipContainer = styled(TooltipContainer)<{ origin?: st
|
||||
padding: 1rem;
|
||||
width: ${({ width }) => width ?? 'auto'};
|
||||
|
||||
${({ theme, origin }) => theme.mediaWidth.upToExtraSmall`
|
||||
${({ theme, origin }) => theme.deprecated_mediaWidth.deprecated_upToExtraSmall`
|
||||
transform: scale(0.8);
|
||||
transform-origin: ${origin ?? 'top left'};
|
||||
`}
|
||||
|
||||
@@ -2,8 +2,7 @@ import celoCircleLogoUrl from 'assets/images/celoCircle.png'
|
||||
import ethereumLogoUrl from 'assets/images/ethereum-logo.png'
|
||||
import optimismCircleLogoUrl from 'assets/images/optimismCircle.png'
|
||||
import polygonCircleLogoUrl from 'assets/images/polygonCircle.png'
|
||||
import arbitrumLogoUrl from 'assets/svg/arbitrum_logo.svg'
|
||||
import arbitrumCircleLogoUrl from 'assets/svg/arbitrum_logo.svg'
|
||||
import { default as arbitrumCircleLogoUrl, default as arbitrumLogoUrl } from 'assets/svg/arbitrum_logo.svg'
|
||||
import celoLogo from 'assets/svg/celo_logo.svg'
|
||||
import optimismLogoUrl from 'assets/svg/optimistic_ethereum.svg'
|
||||
import polygonMaticLogo from 'assets/svg/polygon-matic-logo.svg'
|
||||
|
||||
@@ -2,8 +2,7 @@ import { Plural, Trans } from '@lingui/macro'
|
||||
|
||||
import WarningCache, { TOKEN_LIST_TYPES } from './TokenSafetyLookupTable'
|
||||
|
||||
// TODO: Replace this with Steph's article when it is available.
|
||||
export const TOKEN_SAFETY_ARTICLE = 'https://help.uniswap.org/en/'
|
||||
export const TOKEN_SAFETY_ARTICLE = 'https://support.uniswap.org/hc/en-us/articles/8723118437133'
|
||||
|
||||
export enum WARNING_LEVEL {
|
||||
MEDIUM,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user