Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1fd484894 | ||
|
|
aeb636cf8a | ||
|
|
4240908c4c | ||
|
|
d53ba64218 | ||
|
|
89ce5a9805 | ||
|
|
1790492f17 | ||
|
|
304cd72eed | ||
|
|
9dba68b34c | ||
|
|
588567b900 | ||
|
|
ef964ab120 | ||
|
|
f3d64b65da | ||
|
|
a9200b2c39 | ||
|
|
209fd33780 | ||
|
|
940c1dbb8e | ||
|
|
5dce68a62f | ||
|
|
9dd8ad1db6 | ||
|
|
0b40f72f0c | ||
|
|
875171e36a | ||
|
|
8e9a20a6c8 | ||
|
|
1a31560374 | ||
|
|
d528b28203 | ||
|
|
c1cb712087 | ||
|
|
ceed5e0b4c | ||
|
|
a449338252 | ||
|
|
9662344e24 | ||
|
|
1536e18784 | ||
|
|
c19431eb20 | ||
|
|
634e38529c | ||
|
|
083ec425d0 | ||
|
|
e8c09db146 | ||
|
|
9ab4d952ef | ||
|
|
48883cce8d | ||
|
|
3d820d39b7 | ||
|
|
4fdca48a97 | ||
|
|
7834ab7979 | ||
|
|
dee808cc57 | ||
|
|
4c23f62a24 | ||
|
|
93633a81a7 | ||
|
|
7465a0e999 | ||
|
|
6aac978754 | ||
|
|
62775f6091 | ||
|
|
97b3725c19 | ||
|
|
46563ee565 | ||
|
|
c68624e048 | ||
|
|
0fa1c5e6ea | ||
|
|
a7fd60987e | ||
|
|
51fe44d53a | ||
|
|
edf67e8e45 | ||
|
|
babfebccef | ||
|
|
ea02d9e919 | ||
|
|
bb3b236cd9 | ||
|
|
1d3fd512ae | ||
|
|
5dbd0ae782 | ||
|
|
b95621758c | ||
|
|
ce51ffae75 | ||
|
|
8cd32138ac | ||
|
|
785c2a6712 | ||
|
|
56a9952546 | ||
|
|
7059a12a25 | ||
|
|
da01254247 | ||
|
|
54a7b943ce | ||
|
|
ec6c843db6 | ||
|
|
7a6bb369d4 | ||
|
|
66a38c8e51 | ||
|
|
f79d2f821e | ||
|
|
72c5e64f74 | ||
|
|
01a44d49b0 | ||
|
|
f0412f5d47 | ||
|
|
2887ee9ac8 | ||
|
|
79d5211db4 | ||
|
|
d05e5d028b | ||
|
|
d3a415ee96 | ||
|
|
db9ac38c64 | ||
|
|
f27d119181 | ||
|
|
f2b85621f5 | ||
|
|
d7bc0aaf4c | ||
|
|
f50e0ca9f9 | ||
|
|
84960b0cef | ||
|
|
42646003fe | ||
|
|
321b8df3a2 | ||
|
|
f1990ff001 | ||
|
|
b48af759f1 | ||
|
|
3969f0414d | ||
|
|
9dc6c60a1a | ||
|
|
4b686a0147 | ||
|
|
f9f8eea6f6 | ||
|
|
3f6dc180cf | ||
|
|
7ce58b55a1 | ||
|
|
e8d1067313 | ||
|
|
c12b0a6dab | ||
|
|
effc3d1c4d | ||
|
|
63a9bd4fbf | ||
|
|
ca829a355c | ||
|
|
8464fc70fe | ||
|
|
dd68f89bf9 | ||
|
|
e42991c066 | ||
|
|
6eb699712d | ||
|
|
1d6662dfe3 | ||
|
|
258be9bf65 | ||
|
|
8592703931 | ||
|
|
44ecc9a203 | ||
|
|
b19e7809ea | ||
|
|
43a0bf4c31 | ||
|
|
546423512a | ||
|
|
b13acb33ed | ||
|
|
82c3193d21 | ||
|
|
503fc37a4b | ||
|
|
1788c9f3c0 |
3
.env
@@ -6,5 +6,4 @@ REACT_APP_AWS_API_ACCESS_KEY="AKIAYJJWW6AQ47ODATHN"
|
||||
REACT_APP_AWS_API_ACCESS_SECRET="V9PoU0FhBP3cX760rPs9jMG/MIuDNLX6hYvVcaYO"
|
||||
REACT_APP_AWS_X_API_KEY="z9dReS5UtHu7iTrUsTuWRozLthi3AxOZlvobrIdr14"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://beta.api.uniswap.org/v1/graphql"
|
||||
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
|
||||
ESLINT_NO_DEV_ERRORS=true
|
||||
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
|
||||
@@ -4,3 +4,4 @@ REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"
|
||||
REACT_APP_FIREBASE_KEY="AIzaSyBcZWwTcTJHj_R6ipZcrJkXdq05PuX0Rs0"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://api.uniswap.org/v1/graphql"
|
||||
THE_GRAPH_SCHEMA_ENDPOINT="https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3"
|
||||
|
||||
3
.github/dependabot.yml
vendored
@@ -6,5 +6,6 @@ updates:
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
allow:
|
||||
- dependency-name: '@uniswap/token-lists'
|
||||
- dependency-name: '@uniswap/default-token-list'
|
||||
- dependency-name: '@uniswap/token-lists'
|
||||
- dependency-name: '@uniswap/widgets'
|
||||
|
||||
2
.github/workflows/test.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: npx yarn-deduplicate --strategy=highest --list --fail
|
||||
- run: yarn yarn-deduplicate --strategy=highest --list --fail
|
||||
|
||||
unit-tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
2
.vscode/settings.json
vendored
@@ -16,4 +16,4 @@
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,16 @@ module.exports = {
|
||||
(plugin) => plugin instanceof MiniCssExtractPlugin
|
||||
)
|
||||
if (instanceOfMiniCssExtractPlugin !== undefined) instanceOfMiniCssExtractPlugin.options.ignoreOrder = true
|
||||
|
||||
// We're currently on Webpack 4.x that doesn't support the `exports` field in package.json.
|
||||
// See https://github.com/webpack/webpack/issues/9509.
|
||||
//
|
||||
// In case you need to add more modules, make sure to remap them to the correct path.
|
||||
//
|
||||
// Map @uniswap/conedison to its dist folder.
|
||||
// This is required because conedison uses * to redirect all imports to its dist.
|
||||
webpackConfig.resolve.alias['@uniswap/conedison'] = '@uniswap/conedison/dist'
|
||||
|
||||
return webpackConfig
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,10 +5,18 @@ const dataConfig = require('./relay.config')
|
||||
const thegraphConfig = require('./relay_thegraph.config')
|
||||
/* eslint-enable */
|
||||
|
||||
const THEGRAPH_API_URL = 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'
|
||||
exec(`get-graphql-schema ${THEGRAPH_API_URL} > ${thegraphConfig.schema}`)
|
||||
function fetchSchema(url, outputFile) {
|
||||
exec(
|
||||
`get-graphql-schema --h Origin=https://app.uniswap.org ${url} | tee ${outputFile}.temp`,
|
||||
(error, stdout, stderr) => {
|
||||
if (error || stderr) {
|
||||
console.log(`Failed to fetch schema from ${url}`)
|
||||
} else if (stdout) {
|
||||
exec(`mv ${outputFile}.temp ${outputFile}`)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
console.log(process.env.REACT_APP_AWS_API_ENDPOINT)
|
||||
exec(
|
||||
`get-graphql-schema --h Origin=https://app.uniswap.org ${process.env.REACT_APP_AWS_API_ENDPOINT} > ${dataConfig.schema}`
|
||||
)
|
||||
fetchSchema(process.env.THE_GRAPH_SCHEMA_ENDPOINT, thegraphConfig.schema)
|
||||
fetchSchema(process.env.REACT_APP_AWS_API_ENDPOINT, dataConfig.schema)
|
||||
|
||||
31
package.json
@@ -20,6 +20,7 @@
|
||||
"start": "craco start",
|
||||
"build": "craco build",
|
||||
"serve": "serve build -l 3000",
|
||||
"deduplicate": "yarn-deduplicate --strategy=highest",
|
||||
"lint": "yarn eslint .",
|
||||
"test": "craco test --coverage",
|
||||
"cypress:open": "cypress open --browser chrome --e2e",
|
||||
@@ -113,7 +114,8 @@
|
||||
"relay-compiler": "^14.1.0",
|
||||
"serve": "^11.3.2",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.4.3"
|
||||
"typescript": "^4.4.3",
|
||||
"yarn-deduplicate": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coinbase/wallet-sdk": "^3.3.0",
|
||||
@@ -122,7 +124,7 @@
|
||||
"@lingui/core": "^3.14.0",
|
||||
"@lingui/macro": "^3.14.0",
|
||||
"@lingui/react": "^3.14.0",
|
||||
"@looksrare/sdk": "^0.7.1",
|
||||
"@looksrare/sdk": "^0.10.2",
|
||||
"@metamask/jazzicon": "^2.0.0",
|
||||
"@opensea/seaport-js": "^1.0.2",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
@@ -131,8 +133,10 @@
|
||||
"@react-hook/window-scroll": "^1.3.0",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@types/react-relay": "^13.0.2",
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@uniswap/analytics": "1.1.1",
|
||||
"@uniswap/analytics-events": "1.1.0",
|
||||
"@uniswap/conedison": "^1.1.0",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
@@ -147,7 +151,7 @@
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.9.0",
|
||||
"@uniswap/widgets": "^2.18.0",
|
||||
"@uniswap/widgets": "^2.19.2",
|
||||
"@vanilla-extract/css": "^1.7.2",
|
||||
"@vanilla-extract/css-utils": "^0.1.2",
|
||||
"@vanilla-extract/dynamic": "^2.0.2",
|
||||
@@ -160,16 +164,16 @@
|
||||
"@visx/responsive": "^2.10.0",
|
||||
"@visx/shape": "^2.11.1",
|
||||
"@walletconnect/ethereum-provider": "^1.8.0",
|
||||
"@web3-react/coinbase-wallet": "^8.0.34-beta.0",
|
||||
"@web3-react/core": "^8.0.35-beta.0",
|
||||
"@web3-react/eip1193": "^8.0.26-beta.0",
|
||||
"@web3-react/empty": "^8.0.20-beta.0",
|
||||
"@web3-react/gnosis-safe": "^8.0.6-beta.0",
|
||||
"@web3-react/metamask": "^8.0.28-beta.0",
|
||||
"@web3-react/network": "^8.0.27-beta.0",
|
||||
"@web3-react/types": "^8.0.20-beta.0",
|
||||
"@web3-react/url": "^8.0.25-beta.0",
|
||||
"@web3-react/walletconnect": "^8.0.35-beta.0",
|
||||
"@web3-react/coinbase-wallet": "8.0.34-beta.0",
|
||||
"@web3-react/core": "8.0.35-beta.0",
|
||||
"@web3-react/eip1193": "8.0.26-beta.0",
|
||||
"@web3-react/empty": "8.0.20-beta.0",
|
||||
"@web3-react/gnosis-safe": "8.0.7-beta.0",
|
||||
"@web3-react/metamask": "8.0.28-beta.0",
|
||||
"@web3-react/network": "8.0.27-beta.0",
|
||||
"@web3-react/types": "8.0.20-beta.0",
|
||||
"@web3-react/url": "8.0.25-beta.0",
|
||||
"@web3-react/walletconnect": "8.0.36-beta.0",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
"array.prototype.flatmap": "^1.2.4",
|
||||
"cids": "^1.0.0",
|
||||
@@ -214,6 +218,7 @@
|
||||
"react-use-gesture": "^6.0.14",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
"react-window": "^1.8.5",
|
||||
"react-window-infinite-loader": "^1.0.8",
|
||||
"rebass": "^4.0.7",
|
||||
"redux": "^4.1.2",
|
||||
"redux-localstorage-simple": "^2.3.1",
|
||||
|
||||
@@ -26,5 +26,5 @@
|
||||
"iconPath": "./images/256x256_App_Icon_Pink.svg",
|
||||
"short_name": "Uniswap",
|
||||
"start_url": ".",
|
||||
"theme_color": "#FC72FFs"
|
||||
}
|
||||
"theme_color": "#FC72FF"
|
||||
}
|
||||
3
public/nft/svgs/marketplaces/looksrare-grey.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.64864 2L1 7.65256L10.5 17.1487L20 7.65256L14.3514 2H6.64864ZM6.13513 5.59458C8.5352 3.18398 12.4648 3.18396 14.8649 5.59456L16.9189 7.64866L14.8649 9.70272C12.4648 12.1133 8.5352 12.1133 6.13513 9.70274L4.08109 7.64866L6.13513 5.59458ZM7.54702 7.64848C7.54702 9.27987 8.86966 10.6012 10.4997 10.6012C12.1298 10.6012 13.4524 9.27987 13.4524 7.64848C13.4524 6.01708 12.1298 4.69576 10.4997 4.69576C8.86966 4.69576 7.54702 6.01708 7.54702 7.64848ZM10.4997 8.93225C9.791 8.93225 9.21593 8.35778 9.21593 7.64848C9.21593 6.93917 9.791 6.3647 10.4997 6.3647C11.2084 6.3647 11.7835 6.93917 11.7835 7.64848C11.7835 8.35778 11.2084 8.93225 10.4997 8.93225Z" fill="#5D6785"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 820 B |
3
public/nft/svgs/marketplaces/opensea-grey.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 1C5.0302 1 1 5.0302 1 10C1 14.9698 5.0302 19 10 19C14.9698 19 19 14.9698 19 10C19 5.0302 14.9716 1 10 1ZM5.4406 10.3024L5.4784 10.2412L7.8202 6.5782C7.8544 6.526 7.9354 6.5314 7.9606 6.589C8.3512 7.4656 8.6896 8.5564 8.5312 9.235C8.4646 9.514 8.2792 9.892 8.0704 10.2412C8.0434 10.2916 8.0146 10.342 7.9822 10.3906C7.9678 10.4122 7.9426 10.4248 7.9156 10.4248H5.509C5.4442 10.4248 5.4064 10.3546 5.4406 10.3024ZM15.8752 11.5624C15.8752 11.5966 15.8554 11.6254 15.8266 11.638C15.6448 11.7154 15.0238 12.0016 14.7664 12.3598C14.1076 13.276 13.6054 14.5864 12.4804 14.5864H7.7896C6.1264 14.5864 4.78 13.2346 4.78 11.566V11.512C4.78 11.4688 4.816 11.4328 4.861 11.4328H7.4746C7.5268 11.4328 7.5646 11.4796 7.561 11.5318C7.5412 11.701 7.5736 11.8756 7.6546 12.034C7.8094 12.349 8.1316 12.5452 8.479 12.5452H9.7732V11.5354H8.4934C8.4286 11.5354 8.389 11.4598 8.4268 11.4058C8.4412 11.3842 8.4556 11.3626 8.4736 11.3374C8.5942 11.1646 8.767 10.8982 8.9398 10.594C9.0568 10.3888 9.1702 10.1692 9.262 9.9496C9.28 9.91 9.2944 9.8686 9.3106 9.829C9.3358 9.7588 9.361 9.6922 9.379 9.6274C9.397 9.5716 9.4132 9.514 9.4276 9.46C9.4708 9.2728 9.4888 9.0748 9.4888 8.8696C9.4888 8.7886 9.4852 8.704 9.478 8.6248C9.4744 8.5366 9.4636 8.4484 9.4528 8.3602C9.4456 8.2828 9.4312 8.2054 9.4168 8.1262C9.397 8.0092 9.3718 7.8922 9.343 7.7752L9.3322 7.7302C9.3106 7.6492 9.2908 7.5736 9.2656 7.4926C9.1918 7.2406 9.109 6.994 9.019 6.7636C8.9866 6.6718 8.9506 6.5836 8.9128 6.4972C8.8588 6.364 8.803 6.2434 8.7526 6.13C8.7256 6.0778 8.704 6.031 8.6824 5.9824C8.6572 5.9284 8.632 5.8744 8.605 5.8222C8.587 5.7826 8.5654 5.7448 8.551 5.7088L8.3926 5.4172C8.371 5.3776 8.407 5.329 8.4502 5.3416L9.4402 5.6098H9.4438C9.4456 5.6098 9.4456 5.6098 9.4474 5.6098L9.577 5.6476L9.721 5.6872L9.7732 5.7016V5.1148C9.7732 4.8304 10 4.6 10.2826 4.6C10.423 4.6 10.5508 4.6576 10.6408 4.7512C10.7326 4.8448 10.7902 4.9726 10.7902 5.1148V5.9878L10.8964 6.0166C10.9036 6.0202 10.9126 6.0238 10.9198 6.0292C10.945 6.0472 10.9828 6.076 11.0296 6.112C11.0674 6.1408 11.107 6.1768 11.1538 6.2146C11.2492 6.292 11.3644 6.391 11.4886 6.5044C11.521 6.5332 11.5534 6.562 11.584 6.5926C11.7442 6.742 11.9242 6.9166 12.097 7.111C12.1456 7.1668 12.1924 7.2208 12.241 7.2802C12.2878 7.3396 12.34 7.3972 12.3832 7.4548C12.4426 7.5322 12.5038 7.6132 12.5596 7.6978C12.5848 7.7374 12.6154 7.7788 12.6388 7.8184C12.7108 7.9246 12.772 8.0344 12.8314 8.1442C12.8566 8.1946 12.8818 8.2504 12.9034 8.3044C12.97 8.452 13.0222 8.6014 13.0546 8.7526C13.0654 8.785 13.0726 8.8192 13.0762 8.8516V8.8588C13.087 8.902 13.0906 8.9488 13.0942 8.9974C13.1086 9.1504 13.1014 9.3052 13.069 9.46C13.0546 9.5248 13.0366 9.586 13.015 9.6526C12.9916 9.7156 12.97 9.7804 12.9412 9.8434C12.8854 9.9712 12.8206 10.1008 12.7432 10.2196C12.718 10.2646 12.6874 10.3114 12.6586 10.3564C12.6262 10.4032 12.592 10.4482 12.5632 10.4914C12.5218 10.5472 12.4786 10.6048 12.4336 10.657C12.394 10.711 12.3544 10.765 12.3094 10.8136C12.2482 10.8874 12.1888 10.9558 12.1258 11.0224C12.0898 11.0656 12.0502 11.1106 12.0088 11.1502C11.9692 11.1952 11.9278 11.2348 11.8918 11.2708C11.8288 11.3338 11.7784 11.3806 11.7352 11.422L11.6326 11.5138C11.6182 11.5282 11.5984 11.5354 11.5786 11.5354H10.7902V12.5452H11.782C12.0034 12.5452 12.214 12.4678 12.385 12.322C12.4426 12.2716 12.6964 12.052 12.997 11.7208C13.0078 11.7082 13.0204 11.701 13.0348 11.6974L15.7726 10.9054C15.8248 10.891 15.8752 10.9288 15.8752 10.9828V11.5624Z" fill="#5D6785"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
3
public/nft/svgs/marketplaces/uniswap-magenta.svg
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
5
public/nft/svgs/marketplaces/x2y2-grey.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.146 4.52803C15.767 3.18049 13.8805 2.35 11.8 2.35C7.57502 2.35 4.15 5.77502 4.15 10C4.15 14.225 7.57502 17.65 11.8 17.65C13.8805 17.65 15.767 16.8195 17.146 15.472C15.501 17.617 12.912 19 10 19C5.02944 19 1 14.9706 1 10C1 5.02944 5.02944 1 10 1C12.912 1 15.501 2.38301 17.146 4.52803Z" fill="#5D6785"/>
|
||||
<path d="M6.08317 14.3776C7.18644 15.4556 8.69563 16.12 10.36 16.12C13.74 16.12 16.48 13.38 16.48 10C16.48 6.62002 13.74 3.88 10.36 3.88C8.69563 3.88 7.18644 4.54439 6.08317 5.62243C7.39916 3.90641 9.47037 2.8 11.8 2.8C15.7765 2.8 19 6.02355 19 10C19 13.9764 15.7764 17.2 11.8 17.2C9.47037 17.2 7.39916 16.0936 6.08317 14.3776Z" fill="#5D6785"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4 10C15.4 12.9823 12.9823 15.4 10 15.4C7.01766 15.4 4.6 12.9823 4.6 10C4.6 7.01766 7.01766 4.6 10 4.6C12.9823 4.6 15.4 7.01766 15.4 10ZM13.6 10C13.6 11.9882 11.9882 13.6 10 13.6C8.01177 13.6 6.4 11.9882 6.4 10C6.4 8.01178 8.01177 6.4 10 6.4C11.9882 6.4 13.6 8.01178 13.6 10Z" fill="#5D6785"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
218
src/abis/uniswap-nft-airdrop-claim.json
Normal file
@@ -0,0 +1,218 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token_",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "merkleRoot_",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "endTime_",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "AlreadyClaimed",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "ClaimWindowFinished",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "EndTimeInPast",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "InvalidProof",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "NoWithdrawDuringClaim",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Claimed",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "previousOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnershipTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32[]",
|
||||
"name": "merkleProof",
|
||||
"type": "bytes32[]"
|
||||
}
|
||||
],
|
||||
"name": "claim",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "endTime",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "isClaimed",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "merkleRoot",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "renounceOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "token",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "withdraw",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
BIN
src/assets/images/airdopBackground.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
src/assets/images/welcomeModal-dark.jpg
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
src/assets/images/welcomeModal-dark@2x.jpg
Normal file
|
After Width: | Height: | Size: 449 KiB |
BIN
src/assets/images/welcomeModal-dark@3x.jpg
Normal file
|
After Width: | Height: | Size: 927 KiB |
BIN
src/assets/images/welcomeModal-light.jpg
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
src/assets/images/welcomeModal-light@2x.jpg
Normal file
|
After Width: | Height: | Size: 364 KiB |
BIN
src/assets/images/welcomeModal-light@3x.jpg
Normal file
|
After Width: | Height: | Size: 718 KiB |
4
src/assets/svg/eye.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="54" height="40" viewBox="0 0 54 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.3335 19.9997C1.3335 19.9997 10.6668 1.33301 27.0002 1.33301C43.3335 1.33301 52.6668 19.9997 52.6668 19.9997C52.6668 19.9997 43.3335 38.6663 27.0002 38.6663C10.6668 38.6663 1.3335 19.9997 1.3335 19.9997Z" stroke="#98A1C0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M27.0002 26.9997C30.8662 26.9997 34.0002 23.8657 34.0002 19.9997C34.0002 16.1337 30.8662 12.9997 27.0002 12.9997C23.1342 12.9997 20.0002 16.1337 20.0002 19.9997C20.0002 23.8657 23.1342 26.9997 27.0002 26.9997Z" stroke="#98A1C0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 705 B |
@@ -7,6 +7,7 @@ import { useAppDispatch } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import { removeConnectedWallet } from 'state/wallets/reducer'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
|
||||
import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
@@ -20,7 +21,7 @@ import { AutoRow } from '../Row'
|
||||
import Transaction from './Transaction'
|
||||
|
||||
const HeaderRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
${flexRowNoWrap};
|
||||
padding: 1rem 1rem;
|
||||
font-weight: 500;
|
||||
color: ${(props) => (props.color === 'blue' ? ({ theme }) => theme.deprecated_primary1 : 'inherit')};
|
||||
@@ -60,14 +61,14 @@ const InfoCard = styled.div`
|
||||
`
|
||||
|
||||
const AccountGroupingRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
${flexColumnNoWrap};
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 400;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
|
||||
div {
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
${flexColumnNoWrap};
|
||||
align-items: center;
|
||||
}
|
||||
`
|
||||
@@ -90,7 +91,7 @@ const YourAccount = styled.div`
|
||||
`
|
||||
|
||||
const LowerSection = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
${flexColumnNoWrap};
|
||||
padding: 1.5rem;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
@@ -163,7 +164,7 @@ const WalletName = styled.div`
|
||||
`
|
||||
|
||||
const TransactionListWrapper = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
${flexColumnNoWrap};
|
||||
`
|
||||
|
||||
const WalletAction = styled(ButtonSecondary)`
|
||||
|
||||
@@ -5,7 +5,7 @@ import styled, { css } from 'styled-components/macro'
|
||||
|
||||
import { nativeOnChain } from '../../constants/tokens'
|
||||
import { useCurrency } from '../../hooks/Tokens'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
|
||||
const CurrencyWrap = styled.div`
|
||||
position: relative;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { t } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ChangeEvent, ReactNode, useCallback } from 'react'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { flexColumnNoWrap } from 'theme/styles'
|
||||
|
||||
import useENS from '../../hooks/useENS'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
@@ -12,7 +13,7 @@ import { AutoColumn } from '../Column'
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
const InputPanel = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
${flexColumnNoWrap};
|
||||
position: relative;
|
||||
border-radius: 1.25rem;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
|
||||
333
src/components/AirdropModal/index.tsx
Normal file
@@ -0,0 +1,333 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import type { TransactionResponse } from '@ethersproject/providers'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import uniswapNftAirdropClaim from 'abis/uniswap-nft-airdrop-claim.json'
|
||||
import airdropBackgroundv2 from 'assets/images/airdopBackground.png'
|
||||
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
|
||||
import { OpacityHoverState } from 'components/Common'
|
||||
import Loader from 'components/Loader'
|
||||
import { UNISWAP_NFT_AIRDROP_CLAIM_ADDRESS } from 'constants/addresses'
|
||||
import { useContract } from 'hooks/useContract'
|
||||
import { ChevronRightIcon } from 'nft/components/icons'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { CollectionRewardsFetcher } from 'nft/queries/genie/GetAirdorpMerkle'
|
||||
import { Airdrop, Rewards } from 'nft/types/airdrop'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import styled from 'styled-components/macro'
|
||||
import { CloseIcon, ThemedText } from 'theme'
|
||||
|
||||
import Modal from '../Modal'
|
||||
|
||||
const ModalWrap = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const Body = styled.div`
|
||||
padding: 28px 20px 20px 20px;
|
||||
`
|
||||
|
||||
const ClaimButton = styled(ThemeButton)`
|
||||
width: 100%;
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
`
|
||||
|
||||
const Line = styled.div`
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: ${({ theme }) => theme.white};
|
||||
opacity: 0.24;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
`
|
||||
|
||||
const LinkWrap = styled.a`
|
||||
text-decoration: none;
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const ImageContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledImage = styled.img`
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
`
|
||||
|
||||
const USDCLabel = styled.div`
|
||||
font-weight: 700;
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
margin-top: 8px;
|
||||
color: white;
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
`
|
||||
|
||||
const RewardsDetailsContainer = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const CurrencyText = styled.span`
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 14.5px;
|
||||
`
|
||||
|
||||
const ClaimContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
height: 380px;
|
||||
padding: 60px 28px;
|
||||
padding-bottom: 20px;
|
||||
`
|
||||
|
||||
const SuccessText = styled.div`
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
const EtherscanLink = styled.a`
|
||||
text-decoration: none;
|
||||
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const CloseButton = styled(ThemeButton)`
|
||||
max-width: 68px;
|
||||
margin-top: auto;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
`
|
||||
|
||||
const SyledCloseIcon = styled(CloseIcon)`
|
||||
float: right;
|
||||
height: 24px;
|
||||
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const Error = styled.div`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.accentCritical};
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
border-radius: 16px;
|
||||
padding: 12px 20px;
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
`
|
||||
|
||||
const ReactLinkWrap = styled.div`
|
||||
margin-bottom: 40px;
|
||||
`
|
||||
|
||||
const RewardsText = styled.span`
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
|
||||
&:first-child {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
const RewardsInformationText = styled.span`
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
margin-bottom: 28px;
|
||||
`
|
||||
|
||||
const MainHeader = styled.span`
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
`
|
||||
|
||||
const EtherscanLinkWrap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
enum RewardAmounts {
|
||||
tradingRewardAmount = 300,
|
||||
holderRewardAmount = 1000,
|
||||
combinedAmount = 1300,
|
||||
}
|
||||
|
||||
const AirdropModal = () => {
|
||||
const { account, provider } = useWeb3React()
|
||||
const [claim, setClaim] = useState<Rewards>()
|
||||
const [isClaimed, setIsClaimed] = useState(false)
|
||||
const [hash, setHash] = useState('')
|
||||
const [error, setError] = useState(false)
|
||||
const setIsClaimAvailable = useIsNftClaimAvailable((state) => state.setIsClaimAvailable)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [totalAmount, setTotalAmount] = useState(0)
|
||||
const isOpen = useModalIsOpen(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
|
||||
const usdcAirdropToggle = useToggleModal(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
|
||||
const contract = useContract(UNISWAP_NFT_AIRDROP_CLAIM_ADDRESS, uniswapNftAirdropClaim)
|
||||
|
||||
const displayError = () => {
|
||||
setIsSubmitting(false)
|
||||
setError(true)
|
||||
setTimeout(() => {
|
||||
setError(false)
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (account && provider && contract) {
|
||||
;(async () => {
|
||||
try {
|
||||
const { data } = await CollectionRewardsFetcher(account)
|
||||
const claim = data.find((claim) => claim?.rewardType === Airdrop.GENIE_UNISWAP_USDC_AIRDROP)
|
||||
|
||||
if (!claim) return
|
||||
|
||||
const [isClaimed] = await contract.connect(provider).functions.isClaimed(claim?.index)
|
||||
|
||||
if (claim && isClaimed === false) {
|
||||
const usdAmount = BigNumber.from(claim.amount).div(10 ** 6)
|
||||
setClaim(claim)
|
||||
setTotalAmount(usdAmount.toNumber())
|
||||
setIsClaimAvailable(true)
|
||||
}
|
||||
} catch (err) {
|
||||
displayError()
|
||||
}
|
||||
})()
|
||||
}
|
||||
}, [account, contract, provider, setIsClaimAvailable])
|
||||
|
||||
const makeClaim = async () => {
|
||||
try {
|
||||
if (contract && claim && claim.amount && claim.merkleProof && provider) {
|
||||
setIsSubmitting(true)
|
||||
|
||||
const response: TransactionResponse = await contract
|
||||
.connect(provider?.getSigner())
|
||||
.functions.claim(claim.index, account, claim?.amount, claim?.merkleProof)
|
||||
|
||||
setHash(response.hash)
|
||||
setIsSubmitting(false)
|
||||
setIsClaimed(true)
|
||||
setIsClaimAvailable(false)
|
||||
}
|
||||
} catch (err) {
|
||||
setIsSubmitting(false)
|
||||
displayError()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal hideBorder isOpen={isOpen} onDismiss={usdcAirdropToggle} maxHeight={90} maxWidth={400}>
|
||||
<ModalWrap>
|
||||
{isClaimed ? (
|
||||
<ClaimContainer>
|
||||
<ThemedText.HeadlineSmall>Congratulations!</ThemedText.HeadlineSmall>
|
||||
<SuccessText>
|
||||
You have successfully claimed {totalAmount} USDC. Thank you for supporting Genie.xyz.
|
||||
</SuccessText>
|
||||
<EtherscanLink href={`https://etherscan.io/tx/${hash}`} target="_blank">
|
||||
<ThemedText.Link>
|
||||
<EtherscanLinkWrap>
|
||||
<span>Etherscan</span>
|
||||
<ChevronRightIcon />
|
||||
</EtherscanLinkWrap>
|
||||
</ThemedText.Link>
|
||||
</EtherscanLink>
|
||||
|
||||
<CloseButton size={ButtonSize.medium} emphasis={ButtonEmphasis.medium} onClick={usdcAirdropToggle}>
|
||||
Close
|
||||
</CloseButton>
|
||||
</ClaimContainer>
|
||||
) : (
|
||||
<>
|
||||
<ImageContainer>
|
||||
<TextContainer>
|
||||
<SyledCloseIcon onClick={usdcAirdropToggle} stroke="white" />
|
||||
<MainHeader>Uniswap NFT Airdrop</MainHeader>
|
||||
<USDCLabel>{totalAmount} USDC</USDCLabel>
|
||||
<Line />
|
||||
<RewardsDetailsContainer>
|
||||
<RewardsText>Trading rewards</RewardsText>{' '}
|
||||
<CurrencyText>
|
||||
{totalAmount === RewardAmounts.tradingRewardAmount || totalAmount === RewardAmounts.combinedAmount
|
||||
? `${RewardAmounts.tradingRewardAmount} USDC`
|
||||
: '0'}
|
||||
</CurrencyText>
|
||||
</RewardsDetailsContainer>
|
||||
<RewardsDetailsContainer>
|
||||
<RewardsText>Genie NFT holder rewards</RewardsText>{' '}
|
||||
<CurrencyText>
|
||||
{totalAmount !== RewardAmounts.tradingRewardAmount
|
||||
? `${RewardAmounts.holderRewardAmount} USDC`
|
||||
: '0'}
|
||||
</CurrencyText>
|
||||
</RewardsDetailsContainer>
|
||||
</TextContainer>
|
||||
<StyledImage src={airdropBackgroundv2} />
|
||||
</ImageContainer>
|
||||
<Body>
|
||||
<RewardsInformationText>
|
||||
As a long time supporter of Genie, you’ve been awarded {totalAmount} USDC tokens.
|
||||
</RewardsInformationText>
|
||||
<ReactLinkWrap>
|
||||
<LinkWrap href="https://uniswap.org/blog/uniswap-nft-aggregator-announcement" target="_blank">
|
||||
<ThemedText.Link>Read more about Uniswap NFT.</ThemedText.Link>
|
||||
</LinkWrap>
|
||||
</ReactLinkWrap>
|
||||
|
||||
{error && (
|
||||
<Error>
|
||||
<AlertTriangle />
|
||||
Claim USDC failed. Please try again later
|
||||
</Error>
|
||||
)}
|
||||
|
||||
<ClaimButton
|
||||
onClick={makeClaim}
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting && <Loader stroke="white" />}
|
||||
<span>Claim{isSubmitting && 'ing'} USDC</span>
|
||||
</ClaimButton>
|
||||
</Body>
|
||||
</>
|
||||
)}
|
||||
</ModalWrap>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AirdropModal
|
||||
@@ -1,7 +1,6 @@
|
||||
import { readableColor } from 'polished'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import styled, { DefaultTheme } from 'styled-components/macro'
|
||||
import { Color } from 'theme/styled'
|
||||
|
||||
export enum BadgeVariant {
|
||||
DEFAULT = 'DEFAULT',
|
||||
@@ -17,7 +16,7 @@ interface BadgeProps {
|
||||
variant?: BadgeVariant
|
||||
}
|
||||
|
||||
function pickBackgroundColor(variant: BadgeVariant | undefined, theme: DefaultTheme): Color {
|
||||
function pickBackgroundColor(variant: BadgeVariant | undefined, theme: DefaultTheme): string {
|
||||
switch (variant) {
|
||||
case BadgeVariant.NEGATIVE:
|
||||
return theme.deprecated_error
|
||||
|
||||
@@ -414,7 +414,42 @@ function pickThemeButtonBackgroundColor({ theme, emphasis }: { theme: DefaultThe
|
||||
return theme.backgroundInteractive
|
||||
}
|
||||
}
|
||||
|
||||
function pickThemeButtonFontSize({ size }: { size: ButtonSize }) {
|
||||
switch (size) {
|
||||
case ButtonSize.large:
|
||||
return '20px'
|
||||
case ButtonSize.medium:
|
||||
return '16px'
|
||||
case ButtonSize.small:
|
||||
return '14px'
|
||||
default:
|
||||
return '16px'
|
||||
}
|
||||
}
|
||||
function pickThemeButtonLineHeight({ size }: { size: ButtonSize }) {
|
||||
switch (size) {
|
||||
case ButtonSize.large:
|
||||
return '24px'
|
||||
case ButtonSize.medium:
|
||||
return '20px'
|
||||
case ButtonSize.small:
|
||||
return '16px'
|
||||
default:
|
||||
return '20px'
|
||||
}
|
||||
}
|
||||
function pickThemeButtonPadding({ size }: { size: ButtonSize }) {
|
||||
switch (size) {
|
||||
case ButtonSize.large:
|
||||
return '16px'
|
||||
case ButtonSize.medium:
|
||||
return '10px 12px'
|
||||
case ButtonSize.small:
|
||||
return '8px'
|
||||
default:
|
||||
return '10px 12px'
|
||||
}
|
||||
}
|
||||
function pickThemeButtonTextColor({ theme, emphasis }: { theme: DefaultTheme; emphasis: ButtonEmphasis }) {
|
||||
switch (emphasis) {
|
||||
case ButtonEmphasis.high:
|
||||
@@ -443,10 +478,12 @@ const BaseThemeButton = styled.button<BaseButtonProps>`
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: ${pickThemeButtonFontSize};
|
||||
font-weight: 600;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
line-height: ${pickThemeButtonLineHeight};
|
||||
padding: ${pickThemeButtonPadding};
|
||||
position: relative;
|
||||
transition: 150ms ease opacity;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { CurveFactory } from 'd3'
|
||||
import React from 'react'
|
||||
import { ReactNode } from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
import { Color } from 'theme/styled'
|
||||
|
||||
export interface LineChartProps<T> {
|
||||
data: T[]
|
||||
@@ -12,7 +11,7 @@ export interface LineChartProps<T> {
|
||||
getY: (t: T) => number
|
||||
marginTop?: number
|
||||
curve: CurveFactory
|
||||
color?: Color
|
||||
color?: string
|
||||
strokeWidth: number
|
||||
children?: ReactNode
|
||||
width: number
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import Column from 'components/Column'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import { BlockedIcon } from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
|
||||
@@ -13,11 +13,6 @@ const ContentWrapper = styled(Column)`
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
`
|
||||
const WarningIcon = styled(AlertOctagon)`
|
||||
min-height: 22px;
|
||||
min-width: 22px;
|
||||
color: ${({ theme }) => theme.deprecated_warning};
|
||||
`
|
||||
const Copy = styled(CopyHelper)`
|
||||
font-size: 12px;
|
||||
`
|
||||
@@ -32,7 +27,7 @@ export default function ConnectedAccountBlocked(props: ConnectedAccountBlockedPr
|
||||
return (
|
||||
<Modal isOpen={props.isOpen} onDismiss={Function.prototype()}>
|
||||
<ContentWrapper>
|
||||
<WarningIcon />
|
||||
<BlockedIcon size="22px" />
|
||||
<ThemedText.DeprecatedLargeHeader lineHeight={2} marginBottom={1} marginTop={1}>
|
||||
<Trans>Blocked Address</Trans>
|
||||
</ThemedText.DeprecatedLargeHeader>
|
||||
|
||||
@@ -6,18 +6,19 @@ import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Lock } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
|
||||
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'
|
||||
@@ -25,7 +26,7 @@ import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
|
||||
import { FiatValue } from './FiatValue'
|
||||
|
||||
const InputPanel = styled.div<{ hideInput?: boolean }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
${flexColumnNoWrap};
|
||||
position: relative;
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
z-index: 1;
|
||||
@@ -106,13 +107,13 @@ const CurrencySelect = styled(ButtonGray)<{
|
||||
`
|
||||
|
||||
const InputRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
${flexRowNoWrap};
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const LabelRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
${flexRowNoWrap};
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 0.75rem;
|
||||
@@ -301,7 +302,7 @@ export default function SwapCurrencyInputPanel({
|
||||
</Aligner>
|
||||
</CurrencySelect>
|
||||
</InputRow>
|
||||
{!hideInput && !hideBalance && currency && (
|
||||
{Boolean(!hideInput && !hideBalance) && (
|
||||
<FiatRow>
|
||||
<RowBetween>
|
||||
<LoadingOpacityContainer $loading={loading}>
|
||||
|
||||
@@ -11,21 +11,22 @@ import { darken } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Lock } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
|
||||
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 CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
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 }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
${flexColumnNoWrap};
|
||||
position: relative;
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
background-color: ${({ theme, hideInput }) => (hideInput ? 'transparent' : theme.deprecated_bg2)};
|
||||
@@ -95,14 +96,14 @@ const CurrencySelect = styled(ButtonGray)<{
|
||||
`
|
||||
|
||||
const InputRow = styled.div<{ selected: boolean }>`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
${flexRowNoWrap};
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: ${({ selected }) => (selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 1rem 1rem')};
|
||||
`
|
||||
|
||||
const LabelRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
${flexRowNoWrap};
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
font-size: 0.75rem;
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
|
||||
import React, { useMemo } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import Logo from '../Logo'
|
||||
|
||||
const StyledLogo = styled(Logo)<{ size: string }>`
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
background: radial-gradient(white 50%, #ffffff00 calc(75% + 1px), #ffffff00 100%);
|
||||
border-radius: 50%;
|
||||
-mox-box-shadow: 0 0 1px black;
|
||||
-webkit-box-shadow: 0 0 1px black;
|
||||
box-shadow: 0 0 1px black;
|
||||
border: 0px solid rgba(255, 255, 255, 0);
|
||||
`
|
||||
|
||||
const StyledNativeLogo = styled(StyledLogo)`
|
||||
-mox-box-shadow: 0 0 1px white;
|
||||
-webkit-box-shadow: 0 0 1px white;
|
||||
box-shadow: 0 0 1px white;
|
||||
`
|
||||
|
||||
export default function CurrencyLogo({
|
||||
currency,
|
||||
symbol,
|
||||
size = '24px',
|
||||
style,
|
||||
src,
|
||||
...rest
|
||||
}: {
|
||||
currency?: Currency | null
|
||||
symbol?: string | null
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
src?: string | null
|
||||
}) {
|
||||
const logoURIs = useCurrencyLogoURIs(currency)
|
||||
const srcs = useMemo(() => (src ? [src, ...logoURIs] : logoURIs), [src, logoURIs])
|
||||
const props = {
|
||||
alt: `${currency?.symbol ?? 'token'} logo`,
|
||||
size,
|
||||
srcs,
|
||||
symbol: symbol ?? currency?.symbol,
|
||||
style,
|
||||
...rest,
|
||||
}
|
||||
|
||||
return currency?.isNative ? <StyledNativeLogo {...props} /> : <StyledLogo {...props} />
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
|
||||
const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>`
|
||||
position: relative;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import { ConnectionType } from 'connection'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import styled from 'styled-components/macro'
|
||||
import { flexColumnNoWrap } from 'theme/styles'
|
||||
|
||||
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
|
||||
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
|
||||
@@ -11,7 +12,7 @@ import Identicon from '../Identicon'
|
||||
|
||||
export const IconWrapper = styled.div<{ size?: number }>`
|
||||
position: relative;
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
${flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 8px;
|
||||
|
||||
68
src/components/Logo/AssetLogo.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useTokenLogoSource from 'hooks/useAssetLogoSource'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
export 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'};
|
||||
`
|
||||
|
||||
const LogoImage = styled.img<{ size: string }>`
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
background: radial-gradient(white 60%, #ffffff00 calc(70% + 1px));
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 1px white;
|
||||
`
|
||||
|
||||
export type AssetLogoBaseProps = {
|
||||
symbol?: string | null
|
||||
backupImg?: string | null
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
type AssetLogoProps = AssetLogoBaseProps & { isNative?: boolean; address?: string | null; chainId?: number }
|
||||
|
||||
// TODO(cartcrom): add prop to optionally render an L2Icon w/ the logo
|
||||
/**
|
||||
* Renders an image by prioritizing a list of sources, and then eventually a fallback triangle alert
|
||||
*/
|
||||
export default function AssetLogo({
|
||||
isNative,
|
||||
address,
|
||||
chainId = SupportedChainId.MAINNET,
|
||||
symbol,
|
||||
backupImg,
|
||||
size = '24px',
|
||||
style,
|
||||
...rest
|
||||
}: AssetLogoProps) {
|
||||
const imageProps = {
|
||||
alt: `${symbol ?? 'token'} logo`,
|
||||
size,
|
||||
style,
|
||||
...rest,
|
||||
}
|
||||
|
||||
const [src, nextSrc] = useTokenLogoSource(address, chainId, isNative, backupImg)
|
||||
|
||||
if (src) {
|
||||
return <LogoImage {...imageProps} src={src} onError={nextSrc} />
|
||||
} else {
|
||||
return (
|
||||
<MissingImageLogo size={size}>
|
||||
{/* use only first 3 characters of Symbol for design reasons */}
|
||||
{symbol?.toUpperCase().replace('$', '').replace(/\s+/g, '').slice(0, 3)}
|
||||
</MissingImageLogo>
|
||||
)
|
||||
}
|
||||
}
|
||||
21
src/components/Logo/CurrencyLogo.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { TokenInfo } from '@uniswap/token-lists'
|
||||
|
||||
import AssetLogo, { AssetLogoBaseProps } from './AssetLogo'
|
||||
|
||||
export default function CurrencyLogo(
|
||||
props: AssetLogoBaseProps & {
|
||||
currency?: Currency | null
|
||||
}
|
||||
) {
|
||||
return (
|
||||
<AssetLogo
|
||||
isNative={props.currency?.isNative}
|
||||
chainId={props.currency?.chainId}
|
||||
address={props.currency?.wrapped.address}
|
||||
symbol={props.symbol ?? props.currency?.symbol}
|
||||
backupImg={(props.currency as TokenInfo)?.logoURI}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
25
src/components/Logo/QueryTokenLogo.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
||||
import { TokenQueryData } from 'graphql/data/Token'
|
||||
import { TopToken } from 'graphql/data/TopTokens'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
|
||||
|
||||
import AssetLogo, { AssetLogoBaseProps } from './AssetLogo'
|
||||
|
||||
export default function QueryTokenLogo(
|
||||
props: AssetLogoBaseProps & {
|
||||
token?: TopToken | TokenQueryData
|
||||
}
|
||||
) {
|
||||
const chainId = props.token?.chain ? CHAIN_NAME_TO_CHAIN_ID[props.token?.chain] : undefined
|
||||
|
||||
return (
|
||||
<AssetLogo
|
||||
isNative={props.token?.address === NATIVE_CHAIN_ID}
|
||||
chainId={chainId}
|
||||
address={props.token?.address}
|
||||
symbol={props.token?.symbol}
|
||||
backupImg={props.token?.project?.logoUrl}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { useState } from 'react'
|
||||
import { ImageProps } from 'rebass'
|
||||
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, size, symbol, ...rest }: LogoProps) {
|
||||
const [, refresh] = useState<number>(0)
|
||||
|
||||
const src: string | undefined = srcs.find((src) => !BAD_SRCS[src])
|
||||
|
||||
if (src) {
|
||||
return (
|
||||
<img
|
||||
{...rest}
|
||||
alt={alt}
|
||||
src={src}
|
||||
style={style}
|
||||
onError={() => {
|
||||
if (src) BAD_SRCS[src] = true
|
||||
refresh((i) => i + 1)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<MissingImageLogo size={size}>
|
||||
{/* use only first 3 characters of Symbol for design reasons */}
|
||||
{symbol?.toUpperCase().replace('$', '').replace(/\s+/g, '').slice(0, 3)}
|
||||
</MissingImageLogo>
|
||||
)
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { Z_INDEX } from 'theme/zIndex'
|
||||
import { isMobile } from '../../utils/userAgent'
|
||||
|
||||
const AnimatedDialogOverlay = animated(DialogOverlay)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ scrollOverlay?: boolean }>`
|
||||
&[data-reach-dialog-overlay] {
|
||||
z-index: ${Z_INDEX.modalBackdrop};
|
||||
@@ -24,49 +24,51 @@ const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ scrollOverlay?: bool
|
||||
}
|
||||
`
|
||||
|
||||
type StyledDialogProps = {
|
||||
$minHeight?: number | false
|
||||
$maxHeight?: number
|
||||
$isBottomSheet?: boolean
|
||||
$scrollOverlay?: boolean
|
||||
$hideBorder?: boolean
|
||||
$maxWidth: number
|
||||
}
|
||||
|
||||
const AnimatedDialogContent = animated(DialogContent)
|
||||
// destructure to not pass custom props to Dialog DOM element
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, scrollOverlay, ...rest }) => (
|
||||
<AnimatedDialogContent {...rest} />
|
||||
)).attrs({
|
||||
'aria-label': 'dialog',
|
||||
})`
|
||||
const StyledDialogContent = styled(AnimatedDialogContent)<StyledDialogProps>`
|
||||
overflow-y: auto;
|
||||
|
||||
&[data-reach-dialog-content] {
|
||||
margin: auto;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg0};
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg1};
|
||||
border: ${({ theme, $hideBorder }) => !$hideBorder && `1px solid ${theme.deprecated_bg1}`};
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
padding: 0px;
|
||||
width: 50vw;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
align-self: ${({ mobile }) => mobile && 'flex-end'};
|
||||
|
||||
max-width: 420px;
|
||||
${({ maxHeight }) =>
|
||||
maxHeight &&
|
||||
align-self: ${({ $isBottomSheet }) => $isBottomSheet && 'flex-end'};
|
||||
max-width: ${({ $maxWidth }) => $maxWidth}px;
|
||||
${({ $maxHeight }) =>
|
||||
$maxHeight &&
|
||||
css`
|
||||
max-height: ${maxHeight}vh;
|
||||
max-height: ${$maxHeight}vh;
|
||||
`}
|
||||
${({ minHeight }) =>
|
||||
minHeight &&
|
||||
${({ $minHeight }) =>
|
||||
$minHeight &&
|
||||
css`
|
||||
min-height: ${minHeight}vh;
|
||||
min-height: ${$minHeight}vh;
|
||||
`}
|
||||
display: ${({ scrollOverlay }) => (scrollOverlay ? 'inline-table' : 'flex')};
|
||||
display: ${({ $scrollOverlay }) => ($scrollOverlay ? 'inline-table' : 'flex')};
|
||||
border-radius: 20px;
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
width: 65vw;
|
||||
margin: auto;
|
||||
`}
|
||||
${({ theme, mobile }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
${({ theme, $isBottomSheet }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
width: 85vw;
|
||||
${
|
||||
mobile &&
|
||||
$isBottomSheet &&
|
||||
css`
|
||||
width: 100vw;
|
||||
border-radius: 20px;
|
||||
@@ -80,12 +82,16 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, scro
|
||||
|
||||
interface ModalProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
onDismiss?: () => void
|
||||
onSwipe?: () => void
|
||||
minHeight?: number | false
|
||||
maxHeight?: number
|
||||
maxWidth?: number
|
||||
initialFocusRef?: React.RefObject<any>
|
||||
children?: React.ReactNode
|
||||
scrollOverlay?: boolean
|
||||
hideBorder?: boolean
|
||||
isBottomSheet?: boolean
|
||||
}
|
||||
|
||||
export default function Modal({
|
||||
@@ -93,9 +99,13 @@ export default function Modal({
|
||||
onDismiss,
|
||||
minHeight = false,
|
||||
maxHeight = 90,
|
||||
maxWidth = 420,
|
||||
initialFocusRef,
|
||||
children,
|
||||
onSwipe = onDismiss,
|
||||
scrollOverlay,
|
||||
isBottomSheet = isMobile,
|
||||
hideBorder = false,
|
||||
}: ModalProps) {
|
||||
const fadeTransition = useTransition(isOpen, {
|
||||
config: { duration: 200 },
|
||||
@@ -111,7 +121,7 @@ export default function Modal({
|
||||
y: state.down ? state.movement[1] : 0,
|
||||
})
|
||||
if (state.movement[1] > 300 || (state.velocity > 3 && state.direction[1] > 0)) {
|
||||
onDismiss()
|
||||
onSwipe?.()
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -122,7 +132,6 @@ export default function Modal({
|
||||
({ opacity }, item) =>
|
||||
item && (
|
||||
<StyledDialogOverlay
|
||||
as={AnimatedDialogOverlay}
|
||||
style={{ opacity: opacity.to({ range: [0.0, 1.0], output: [0, 1] }) }}
|
||||
onDismiss={onDismiss}
|
||||
initialFocusRef={initialFocusRef}
|
||||
@@ -136,11 +145,13 @@ export default function Modal({
|
||||
style: { transform: y.interpolate((y) => `translateY(${(y as number) > 0 ? y : 0}px)`) },
|
||||
}
|
||||
: {})}
|
||||
aria-label="dialog content"
|
||||
minHeight={minHeight}
|
||||
maxHeight={maxHeight}
|
||||
mobile={isMobile}
|
||||
scrollOverlay={scrollOverlay}
|
||||
aria-label="dialog"
|
||||
$minHeight={minHeight}
|
||||
$maxHeight={maxHeight}
|
||||
$isBottomSheet={isBottomSheet}
|
||||
$scrollOverlay={scrollOverlay}
|
||||
$hideBorder={hideBorder}
|
||||
$maxWidth={maxWidth}
|
||||
>
|
||||
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
|
||||
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { NavIcon } from 'components/NavBar/NavIcon'
|
||||
import { BagIcon, HundredsOverflowIcon } from 'nft/components/icons'
|
||||
import { useBag } from 'nft/hooks'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useIsNftProfilePage } from 'hooks/useIsNftPage'
|
||||
import { BagIcon, HundredsOverflowIcon, TagIcon } from 'nft/components/icons'
|
||||
import { useBag, useSellAsset } from 'nft/hooks'
|
||||
import { useCallback } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import shallow from 'zustand/shallow'
|
||||
|
||||
@@ -22,7 +23,8 @@ const CounterDot = styled.div`
|
||||
|
||||
export const Bag = () => {
|
||||
const itemsInBag = useBag((state) => state.itemsInBag)
|
||||
const [bagQuantity, setBagQuantity] = useState(0)
|
||||
const sellAssets = useSellAsset((state) => state.sellAssets)
|
||||
const isProfilePage = useIsNftProfilePage()
|
||||
|
||||
const { bagExpanded, setBagExpanded } = useBag(
|
||||
({ bagExpanded, setBagExpanded }) => ({ bagExpanded, setBagExpanded }),
|
||||
@@ -33,15 +35,16 @@ export const Bag = () => {
|
||||
setBagExpanded({ bagExpanded: !bagExpanded })
|
||||
}, [bagExpanded, setBagExpanded])
|
||||
|
||||
useEffect(() => {
|
||||
setBagQuantity(itemsInBag.length)
|
||||
}, [itemsInBag])
|
||||
|
||||
const bagQuantity = isProfilePage ? sellAssets.length : itemsInBag.length
|
||||
const bagHasItems = bagQuantity > 0
|
||||
|
||||
return (
|
||||
<NavIcon isActive={bagExpanded} onClick={handleIconClick}>
|
||||
<BagIcon viewBox="0 0 24 24" width={24} height={24} />
|
||||
{isProfilePage ? (
|
||||
<TagIcon viewBox="0 0 24 24" width={24} height={24} />
|
||||
) : (
|
||||
<BagIcon viewBox="0 0 24 24" width={24} height={24} strokeWidth="2px" />
|
||||
)}
|
||||
{bagHasItems && <CounterDot>{bagQuantity > 99 ? <HundredsOverflowIcon /> : bagQuantity}</CounterDot>}
|
||||
</NavIcon>
|
||||
)
|
||||
|
||||
@@ -4,6 +4,9 @@ import { buttonTextSmall, subhead, subheadSmall } from 'nft/css/common.css'
|
||||
import { breakpoints, sprinkles, vars } from '../../nft/css/sprinkles.css'
|
||||
|
||||
const DESKTOP_NAVBAR_WIDTH = 360
|
||||
const DESKTOP_NAVBAR_WIDTH_L = 440
|
||||
const DESKTOP_NAVBAR_WIDTH_XL = 540
|
||||
const DESKTOP_NAVBAR_WIDTH_XXL = 640
|
||||
const MAGNIFYING_GLASS_ICON_WIDTH = 28
|
||||
|
||||
const baseSearchStyle = style([
|
||||
@@ -23,6 +26,27 @@ const baseSearchStyle = style([
|
||||
},
|
||||
])
|
||||
|
||||
const baseSearchNftStyle = style([
|
||||
baseSearchStyle,
|
||||
{
|
||||
borderWidth: '2px',
|
||||
'@media': {
|
||||
[`screen and (min-width: 1024px) and (max-width: 1080px)`]: {
|
||||
width: `${330}px`,
|
||||
},
|
||||
[`screen and (min-width: 1190px) and (max-width: 1380px)`]: {
|
||||
width: `${DESKTOP_NAVBAR_WIDTH_L}px`,
|
||||
},
|
||||
[`screen and (min-width: 1380px) and (max-width: 1479px)`]: {
|
||||
width: `${DESKTOP_NAVBAR_WIDTH_XL}px`,
|
||||
},
|
||||
[`screen and (min-width: ${1480}px)`]: {
|
||||
width: `${DESKTOP_NAVBAR_WIDTH_XXL}px`,
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export const searchBarContainer = style([
|
||||
sprinkles({
|
||||
right: '0',
|
||||
@@ -40,6 +64,35 @@ export const searchBarContainer = style([
|
||||
},
|
||||
])
|
||||
|
||||
export const searchBarContainerNft = style([
|
||||
sprinkles({
|
||||
right: '0',
|
||||
top: '0',
|
||||
zIndex: '3',
|
||||
display: 'inline-block',
|
||||
}),
|
||||
{
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.lg}px)`]: {
|
||||
right: `-${DESKTOP_NAVBAR_WIDTH / 2}px`,
|
||||
top: '-6px',
|
||||
},
|
||||
[`screen and (min-width: 1024px) and (max-width: 1080px)`]: {
|
||||
right: `-${300 / 2}px`,
|
||||
},
|
||||
[`screen and (min-width: 1190px) and (max-width: 1380px)`]: {
|
||||
right: `-${DESKTOP_NAVBAR_WIDTH_L / 2}px`,
|
||||
},
|
||||
[`screen and (min-width: 1380px) and (max-width: 1479px)`]: {
|
||||
right: `-${DESKTOP_NAVBAR_WIDTH_XL / 2}px`,
|
||||
},
|
||||
[`screen and (min-width: ${1480}px)`]: {
|
||||
right: `-${DESKTOP_NAVBAR_WIDTH_XXL / 2}px`,
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export const searchBar = style([
|
||||
baseSearchStyle,
|
||||
sprinkles({
|
||||
@@ -49,6 +102,15 @@ export const searchBar = style([
|
||||
}),
|
||||
])
|
||||
|
||||
export const nftSearchBar = style([
|
||||
baseSearchNftStyle,
|
||||
sprinkles({
|
||||
color: 'textTertiary',
|
||||
paddingX: '16',
|
||||
background: 'backgroundSurface',
|
||||
}),
|
||||
])
|
||||
|
||||
export const searchBarInput = style([
|
||||
sprinkles({
|
||||
padding: '0',
|
||||
@@ -75,6 +137,19 @@ export const searchBarDropdown = style([
|
||||
},
|
||||
])
|
||||
|
||||
export const searchBarDropdownNft = style([
|
||||
baseSearchNftStyle,
|
||||
sprinkles({
|
||||
borderBottomLeftRadius: '12',
|
||||
borderBottomRightRadius: '12',
|
||||
background: 'backgroundSurface',
|
||||
height: { sm: 'viewHeight', md: 'auto' },
|
||||
}),
|
||||
{
|
||||
borderTop: 'none',
|
||||
},
|
||||
])
|
||||
|
||||
export const suggestionRow = style([
|
||||
sprinkles({
|
||||
display: 'flex',
|
||||
|
||||
@@ -33,6 +33,7 @@ export const SearchBar = () => {
|
||||
const phase1Flag = useNftFlag()
|
||||
const isMobile = useIsMobile()
|
||||
const isTablet = useIsTablet()
|
||||
const isPhase1 = phase1Flag === NftVariant.Enabled
|
||||
|
||||
useOnClickOutside(searchRef, () => {
|
||||
isOpen && toggleOpen()
|
||||
@@ -45,7 +46,7 @@ export const SearchBar = () => {
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
enabled: !!debouncedSearchValue.length && phase1Flag === NftVariant.Enabled,
|
||||
enabled: !!debouncedSearchValue.length && isPhase1,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -112,19 +113,19 @@ export const SearchBar = () => {
|
||||
position={{ sm: 'fixed', md: 'absolute' }}
|
||||
width={{ sm: isOpen ? 'viewWidth' : 'auto', md: 'auto' }}
|
||||
ref={searchRef}
|
||||
className={styles.searchBarContainer}
|
||||
className={isPhase1 ? styles.searchBarContainerNft : styles.searchBarContainer}
|
||||
display={{ sm: isOpen ? 'inline-block' : 'none', xl: 'inline-block' }}
|
||||
>
|
||||
<Row
|
||||
className={clsx(
|
||||
` ${styles.searchBar} ${!isOpen && !isMobile && magicalGradientOnHover} ${
|
||||
isMobileOrTablet && (isOpen ? styles.visible : styles.hidden)
|
||||
}`
|
||||
` ${isPhase1 ? styles.nftSearchBar : styles.searchBar} ${
|
||||
!isOpen && !isMobile && magicalGradientOnHover
|
||||
} ${isMobileOrTablet && (isOpen ? styles.visible : styles.hidden)}`
|
||||
)}
|
||||
borderRadius={isOpen || isMobileOrTablet ? undefined : '12'}
|
||||
borderTopRightRadius={isOpen && !isMobile ? '12' : undefined}
|
||||
borderTopLeftRadius={isOpen && !isMobile ? '12' : undefined}
|
||||
borderBottomWidth={isOpen || isMobileOrTablet ? '0px' : '1px'}
|
||||
borderBottomWidth={isOpen || isMobileOrTablet ? '0px' : isPhase1 ? '2px' : '1px'}
|
||||
onClick={() => !isOpen && toggleOpen()}
|
||||
gap="12"
|
||||
>
|
||||
|
||||
@@ -118,6 +118,7 @@ export const SearchBarDropdown = ({
|
||||
const isTokenPage = pathname.includes('/tokens')
|
||||
const phase1Flag = useNftFlag()
|
||||
const [resultsState, setResultsState] = useState<ReactNode>()
|
||||
const isPhase1 = phase1Flag === NftVariant.Enabled
|
||||
|
||||
const { data: trendingCollectionResults, isLoading: trendingCollectionsAreLoading } = useQuery(
|
||||
['trendingCollections', 'eth', 'twenty_four_hours'],
|
||||
@@ -204,10 +205,10 @@ export const SearchBarDropdown = ({
|
||||
(isNFTPage && (hasVerifiedCollection || !hasVerifiedToken)) ||
|
||||
(!isNFTPage && !hasVerifiedToken && hasVerifiedCollection)
|
||||
|
||||
const trace = useTrace({ section: SectionName.NAVBAR_SEARCH })
|
||||
const trace = JSON.stringify(useTrace({ section: SectionName.NAVBAR_SEARCH }))
|
||||
|
||||
useEffect(() => {
|
||||
const eventProperties = { total_suggestions: totalSuggestions, query_text: queryText, ...trace }
|
||||
const eventProperties = { total_suggestions: totalSuggestions, query_text: queryText, ...JSON.parse(trace) }
|
||||
if (!isLoading) {
|
||||
const tokenSearchResults =
|
||||
tokens.length > 0 ? (
|
||||
@@ -342,7 +343,7 @@ export const SearchBarDropdown = ({
|
||||
])
|
||||
|
||||
return (
|
||||
<Box className={styles.searchBarDropdown}>
|
||||
<Box className={isPhase1 ? styles.searchBarDropdownNft : styles.searchBarDropdown}>
|
||||
<Box opacity={isLoading ? '0.3' : '1'} transition="125">
|
||||
{resultsState}
|
||||
</Box>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { EventName } from '@uniswap/analytics-events'
|
||||
import { formatUSDPrice } from '@uniswap/conedison/format'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import clsx from 'clsx'
|
||||
import AssetLogo from 'components/Logo/AssetLogo'
|
||||
import { L2NetworkLogo, LogoContainer } from 'components/Tokens/TokenTable/TokenRow'
|
||||
import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
||||
import { checkWarning } from 'constants/tokenSafety'
|
||||
import { getTokenDetailsURL } from 'graphql/data/util'
|
||||
import uriToHttp from 'lib/utils/uriToHttp'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { VerifiedIcon } from 'nft/components/icons'
|
||||
@@ -18,10 +20,14 @@ 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 { formatDollar } from 'utils/formatNumbers'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import * as styles from './SearchBar.css'
|
||||
|
||||
const StyledLogoContainer = styled(LogoContainer)`
|
||||
margin-right: 8px;
|
||||
`
|
||||
|
||||
interface CollectionRowProps {
|
||||
collection: GenieCollection
|
||||
isHovered: boolean
|
||||
@@ -127,8 +133,6 @@ interface TokenRowProps {
|
||||
}
|
||||
|
||||
export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index, eventProperties }: TokenRowProps) => {
|
||||
const [brokenImage, setBrokenImage] = useState(false)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const addToSearchHistory = useSearchHistory(
|
||||
(state: { addItem: (item: FungibleToken | GenieCollection) => void }) => state.addItem
|
||||
)
|
||||
@@ -167,21 +171,17 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
|
||||
style={{ background: isHovered ? vars.color.lightGrayOverlay : 'none' }}
|
||||
>
|
||||
<Row style={{ width: '65%' }}>
|
||||
{!brokenImage && token.logoURI ? (
|
||||
<LogoContainer>
|
||||
<Box
|
||||
as="img"
|
||||
src={token.logoURI.includes('ipfs://') ? uriToHttp(token.logoURI)[0] : token.logoURI}
|
||||
alt={token.name}
|
||||
className={clsx(loaded ? styles.suggestionImage : styles.imageHolder)}
|
||||
onError={() => setBrokenImage(true)}
|
||||
onLoad={() => setLoaded(true)}
|
||||
/>
|
||||
<L2NetworkLogo networkUrl={L2Icon} size="16px" />
|
||||
</LogoContainer>
|
||||
) : (
|
||||
<Box className={styles.imageHolder} />
|
||||
)}
|
||||
<StyledLogoContainer>
|
||||
<AssetLogo
|
||||
isNative={token.address === NATIVE_CHAIN_ID}
|
||||
address={token.address}
|
||||
chainId={token.chainId}
|
||||
symbol={token.symbol}
|
||||
size="36px"
|
||||
backupImg={token.logoURI}
|
||||
/>
|
||||
<L2NetworkLogo networkUrl={L2Icon} size="16px" />
|
||||
</StyledLogoContainer>
|
||||
<Column className={styles.suggestionPrimaryContainer}>
|
||||
<Row gap="4" width="full">
|
||||
<Box className={styles.primaryText}>{token.name}</Box>
|
||||
@@ -194,7 +194,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
|
||||
<Column className={styles.suggestionSecondaryContainer}>
|
||||
{token.priceUsd && (
|
||||
<Row gap="4">
|
||||
<Box className={styles.primaryText}>{formatDollar({ num: token.priceUsd, isPrice: true })}</Box>
|
||||
<Box className={styles.primaryText}>{formatUSDPrice(token.priceUsd)}</Box>
|
||||
</Row>
|
||||
)}
|
||||
{token.price24hChange && (
|
||||
|
||||
@@ -9,12 +9,13 @@ import { resetMintState } from 'state/mint/actions'
|
||||
import { resetMintState as resetMintV3State } from 'state/mint/v3/actions'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { flexRowNoWrap } from 'theme/styles'
|
||||
|
||||
import Row, { RowBetween } from '../Row'
|
||||
import SettingsTab from '../Settings'
|
||||
|
||||
const Tabs = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
${flexRowNoWrap};
|
||||
align-items: center;
|
||||
border-radius: 3rem;
|
||||
justify-content: space-evenly;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
const PopoverContainer = styled.div<{ show: boolean }>`
|
||||
z-index: ${Z_INDEX.popover};
|
||||
pointer-events: none;
|
||||
visibility: ${(props) => (props.show ? 'visible' : 'hidden')};
|
||||
opacity: ${(props) => (props.show ? 1 : 0)};
|
||||
transition: visibility 150ms linear, opacity 150ms linear;
|
||||
|
||||
@@ -19,9 +19,9 @@ import { unwrappedToken } from '../../utils/unwrappedToken'
|
||||
import { ButtonEmpty, ButtonPrimary, ButtonSecondary } from '../Button'
|
||||
import { LightCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { CardNoise } from '../earn/styled'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
import { AutoRow, RowBetween, RowFixed } from '../Row'
|
||||
import { Dots } from '../swap/styleds'
|
||||
import { FixedHeightRow } from '.'
|
||||
|
||||
@@ -20,9 +20,9 @@ import { unwrappedToken } from '../../utils/unwrappedToken'
|
||||
import { ButtonEmpty, ButtonPrimary, ButtonSecondary } from '../Button'
|
||||
import { GrayCard, LightCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { CardNoise } from '../earn/styled'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
import { AutoRow, RowBetween, RowFixed } from '../Row'
|
||||
import { Dots } from '../swap/styleds'
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import { Position } from '@uniswap/v3-sdk'
|
||||
import RangeBadge from 'components/Badge/RangeBadge'
|
||||
import { LightCard } from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
import { Break } from 'components/earn/styled'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import RateToggle from 'components/RateToggle'
|
||||
import { RowBetween, RowFixed } from 'components/Row'
|
||||
import JSBI from 'jsbi'
|
||||
|
||||
@@ -28,7 +28,7 @@ const multiRoute: RoutingDiagramEntry[] = [
|
||||
]
|
||||
|
||||
jest.mock(
|
||||
'components/CurrencyLogo',
|
||||
'components/Logo/CurrencyLogo',
|
||||
() =>
|
||||
({ currency }: { currency: Currency }) =>
|
||||
`CurrencyLogo currency=${currency.symbol}`
|
||||
|
||||
@@ -3,8 +3,8 @@ import { Protocol } from '@uniswap/router-sdk'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import Badge from 'components/Badge'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import Row, { AutoRow } from 'components/Row'
|
||||
import { RoutingDiagramEntry } from 'components/swap/SwapRoute'
|
||||
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
|
||||
|
||||
@@ -2,7 +2,7 @@ import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { COMMON_BASES } from 'constants/routing'
|
||||
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
exports[`renders currency rows correctly when currencies list is non-empty 1`] = `
|
||||
<DocumentFragment>
|
||||
.c9 {
|
||||
.c10 {
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
.c8 {
|
||||
margin-left: 4px;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
@@ -18,7 +18,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
.c9 {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
color: #98A1C0;
|
||||
@@ -73,7 +73,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
.c11 {
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
@@ -100,6 +100,10 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
margin-left: 0.3em;
|
||||
}
|
||||
|
||||
<div
|
||||
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||
>
|
||||
@@ -132,38 +136,42 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<svg
|
||||
<div
|
||||
class="c8"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
class="c9"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c9 css-1j6a53a"
|
||||
class="c10 css-yfjwjl"
|
||||
>
|
||||
DAI
|
||||
</div>
|
||||
@@ -172,7 +180,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c10"
|
||||
class="c0 c1 c11"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
@@ -203,38 +211,42 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<svg
|
||||
<div
|
||||
class="c8"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
class="c9"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c9 css-1j6a53a"
|
||||
class="c10 css-yfjwjl"
|
||||
>
|
||||
USDC
|
||||
</div>
|
||||
@@ -243,7 +255,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c10"
|
||||
class="c0 c1 c11"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
@@ -274,38 +286,42 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<svg
|
||||
<div
|
||||
class="c8"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
class="c9"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c9 css-1j6a53a"
|
||||
class="c10 css-yfjwjl"
|
||||
>
|
||||
WBTC
|
||||
</div>
|
||||
@@ -314,7 +330,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c10"
|
||||
class="c0 c1 c11"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@ const mockCurrencyAmt = {
|
||||
}
|
||||
|
||||
jest.mock(
|
||||
'components/CurrencyLogo',
|
||||
'components/Logo/CurrencyLogo',
|
||||
() =>
|
||||
({ currency }: { currency: Currency }) =>
|
||||
`CurrencyLogo currency=${currency.symbol}`
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import { checkWarning } from 'constants/tokenSafety'
|
||||
import { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'
|
||||
import { XOctagon } from 'react-feather'
|
||||
import { Check } from 'react-feather'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
@@ -16,8 +15,8 @@ import { useCurrencyBalance } from '../../../state/connection/hooks'
|
||||
import { WrappedTokenInfo } from '../../../state/lists/wrappedTokenInfo'
|
||||
import { ThemedText } from '../../../theme'
|
||||
import Column, { AutoColumn } from '../../Column'
|
||||
import CurrencyLogo from '../../CurrencyLogo'
|
||||
import Loader from '../../Loader'
|
||||
import CurrencyLogo from '../../Logo/CurrencyLogo'
|
||||
import Row, { RowFixed } from '../../Row'
|
||||
import { MouseoverTooltip } from '../../Tooltip'
|
||||
import { LoadingRows, MenuItem } from '../styleds'
|
||||
@@ -61,10 +60,8 @@ const Tag = styled.div`
|
||||
margin-right: 4px;
|
||||
`
|
||||
|
||||
export const BlockedTokenIcon = styled(XOctagon)<{ size?: string }>`
|
||||
export const WarningContainer = styled.div`
|
||||
margin-left: 0.3em;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
`
|
||||
|
||||
function Balance({ balance }: { balance: CurrencyAmount<Currency> }) {
|
||||
@@ -158,8 +155,9 @@ export function CurrencyRow({
|
||||
<AutoColumn style={{ opacity: isBlockedToken ? blockedTokenOpacity : '1' }}>
|
||||
<Row>
|
||||
<CurrencyName title={currency.name}>{currency.name}</CurrencyName>
|
||||
<TokenSafetyIcon warning={warning} />
|
||||
{isBlockedToken && <BlockedTokenIcon />}
|
||||
<WarningContainer>
|
||||
<TokenSafetyIcon warning={warning} />
|
||||
</WarningContainer>
|
||||
</Row>
|
||||
<ThemedText.DeprecatedDarkGray ml="0px" fontSize="12px" fontWeight={300}>
|
||||
{currency.symbol}
|
||||
|
||||
@@ -17,8 +17,9 @@ import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import { useAllTokenBalances } from 'state/connection/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { UserAddedToken } from 'types/tokens'
|
||||
|
||||
import { useActiveTokens, useIsUserAddedToken, useSearchInactiveTokenLists, useToken } from '../../hooks/Tokens'
|
||||
import { useAllTokens, useIsUserAddedToken, useSearchInactiveTokenLists, useToken } from '../../hooks/Tokens'
|
||||
import { CloseIcon, ThemedText } from '../../theme'
|
||||
import { isAddress } from '../../utils'
|
||||
import Column from '../Column'
|
||||
@@ -66,15 +67,8 @@ export function CurrencySearch({
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState<string>('')
|
||||
const debouncedQuery = useDebounce(searchQuery, 200)
|
||||
|
||||
// Only display 'imported' tokens when the search filter has input
|
||||
const defaultTokens = useActiveTokens(debouncedQuery.length > 0)
|
||||
|
||||
// if they input an address, use it
|
||||
const isAddressSearch = isAddress(debouncedQuery)
|
||||
|
||||
const searchToken = useToken(debouncedQuery)
|
||||
|
||||
const searchTokenIsAdded = useIsUserAddedToken(searchToken)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -87,14 +81,27 @@ export function CurrencySearch({
|
||||
}
|
||||
}, [isAddressSearch])
|
||||
|
||||
const defaultTokens = useAllTokens()
|
||||
const filteredTokens: Token[] = useMemo(() => {
|
||||
return Object.values(defaultTokens).filter(getTokenFilter(debouncedQuery))
|
||||
}, [defaultTokens, debouncedQuery])
|
||||
|
||||
const [balances, balancesAreLoading] = useAllTokenBalances()
|
||||
const sortedTokens: Token[] = useMemo(
|
||||
() => (!balancesAreLoading ? [...filteredTokens].sort(tokenComparator.bind(null, balances)) : []),
|
||||
[balances, filteredTokens, balancesAreLoading]
|
||||
() =>
|
||||
!balancesAreLoading
|
||||
? filteredTokens
|
||||
.filter((token) => {
|
||||
// If there is no query, filter out unselected user-added tokens with no balance.
|
||||
if (!debouncedQuery && token instanceof UserAddedToken) {
|
||||
if (selectedCurrency?.equals(token) || otherSelectedCurrency?.equals(token)) return true
|
||||
return balances[token.address]?.greaterThan(0)
|
||||
}
|
||||
return true
|
||||
})
|
||||
.sort(tokenComparator.bind(null, balances))
|
||||
: [],
|
||||
[balances, balancesAreLoading, debouncedQuery, filteredTokens, otherSelectedCurrency, selectedCurrency]
|
||||
)
|
||||
const isLoading = Boolean(balancesAreLoading && !tokenLoaderTimerElapsed)
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ const ModalContentWrapper = styled.div`
|
||||
export default function SettingsTab({ placeholderSlippage }: { placeholderSlippage: Percent }) {
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const node = useRef<HTMLDivElement | null>(null)
|
||||
const open = useModalIsOpen(ApplicationModal.SETTINGS)
|
||||
const toggle = useToggleSettingsMenu()
|
||||
|
||||
@@ -139,8 +139,7 @@ export default function SettingsTab({ placeholderSlippage }: { placeholderSlippa
|
||||
useOnClickOutside(node, open ? toggle : undefined)
|
||||
|
||||
return (
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451
|
||||
<StyledMenu ref={node as any}>
|
||||
<StyledMenu ref={node}>
|
||||
<Modal isOpen={showConfirmation} onDismiss={() => setShowConfirmation(false)} maxHeight={100}>
|
||||
<ModalContentWrapper>
|
||||
<AutoColumn gap="lg">
|
||||
|
||||
@@ -1,24 +1,43 @@
|
||||
import { Warning, WARNING_LEVEL } from 'constants/tokenSafety'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { AlertTriangle, Slash } from 'react-feather'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
|
||||
const VerifiedContainer = styled.div`
|
||||
const WarningContainer = styled.div`
|
||||
margin-left: 4px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
export const WarningIcon = styled(AlertTriangle)<{ size?: string }>`
|
||||
const WarningIconStyle = css<{ size?: string }>`
|
||||
width: ${({ size }) => size ?? '1em'};
|
||||
height: ${({ size }) => size ?? '1em'};
|
||||
`
|
||||
|
||||
const WarningIcon = styled(AlertTriangle)`
|
||||
${WarningIconStyle};
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
`
|
||||
|
||||
export const BlockedIcon = styled(Slash)`
|
||||
${WarningIconStyle}
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
|
||||
export default function TokenSafetyIcon({ warning }: { warning: Warning | null }) {
|
||||
if (warning?.level !== WARNING_LEVEL.UNKNOWN) return null
|
||||
return (
|
||||
<VerifiedContainer>
|
||||
<WarningIcon />
|
||||
</VerifiedContainer>
|
||||
)
|
||||
switch (warning?.level) {
|
||||
case WARNING_LEVEL.BLOCKED:
|
||||
return (
|
||||
<WarningContainer>
|
||||
<BlockedIcon strokeWidth={2.5} />
|
||||
</WarningContainer>
|
||||
)
|
||||
case WARNING_LEVEL.UNKNOWN:
|
||||
return (
|
||||
<WarningContainer>
|
||||
<WarningIcon />
|
||||
</WarningContainer>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { WARNING_LEVEL } from 'constants/tokenSafety'
|
||||
import { useTokenWarningColor } from 'hooks/useTokenWarningColor'
|
||||
import { ReactNode } from 'react'
|
||||
import { AlertOctagon, AlertTriangle } from 'react-feather'
|
||||
import { AlertTriangle, Slash } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Color } from 'theme/styled'
|
||||
|
||||
const Label = styled.div<{ color: Color }>`
|
||||
const Label = styled.div<{ color: string }>`
|
||||
padding: 4px 4px;
|
||||
font-size: 12px;
|
||||
background-color: ${({ color }) => color + '1F'};
|
||||
@@ -31,7 +30,7 @@ export default function TokenSafetyLabel({ level, canProceed, children }: TokenW
|
||||
return (
|
||||
<Label color={useTokenWarningColor(level)}>
|
||||
<Title marginRight="5px">{children}</Title>
|
||||
{canProceed ? <AlertTriangle strokeWidth={2.5} size="14px" /> : <AlertOctagon strokeWidth={2.5} size="14px" />}
|
||||
{canProceed ? <AlertTriangle strokeWidth={2.5} size="14px" /> : <Slash strokeWidth={2.5} size="14px" />}
|
||||
</Label>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { getWarningCopy, TOKEN_SAFETY_ARTICLE, Warning } from 'constants/tokenSafety'
|
||||
import { useTokenWarningColor } from 'hooks/useTokenWarningColor'
|
||||
import { AlertOctagon, AlertTriangle } from 'react-feather'
|
||||
import { AlertTriangle, Slash } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink } from 'theme'
|
||||
import { Color } from 'theme/styled'
|
||||
|
||||
const Label = styled.div<{ color: Color }>`
|
||||
const Label = styled.div<{ color: string }>`
|
||||
width: 100%;
|
||||
padding: 12px 20px 16px;
|
||||
background-color: ${({ color }) => color + '1F'};
|
||||
@@ -52,7 +51,7 @@ export default function TokenWarningMessage({ warning, tokenAddress }: TokenWarn
|
||||
return (
|
||||
<Label color={color}>
|
||||
<TitleRow>
|
||||
{warning.canProceed ? <AlertTriangle size="16px" /> : <AlertOctagon size="16px" />}
|
||||
{warning.canProceed ? <AlertTriangle size="16px" /> : <Slash size="16px" />}
|
||||
<Title marginLeft="7px">{warning.message}</Title>
|
||||
</TitleRow>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import TokenSafetyLabel from 'components/TokenSafety/TokenSafetyLabel'
|
||||
import { checkWarning, getWarningCopy, TOKEN_SAFETY_ARTICLE, Warning } from 'constants/tokenSafety'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { darken } from 'polished'
|
||||
import { useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { textFadeIn } from 'theme/animations'
|
||||
import { textFadeIn } from 'theme/styles'
|
||||
|
||||
import Resource from './Resource'
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
|
||||
import { formatToDecimal } from 'lib/utils/analytics'
|
||||
import { useMemo } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { currencyAmountToPreciseFloat, formatDollar } from 'utils/formatNumbers'
|
||||
|
||||
const BalancesCard = styled.div`
|
||||
box-shadow: ${({ theme }) => theme.shallowShadow};
|
||||
@@ -44,29 +42,14 @@ const BalanceRow = styled.div`
|
||||
`
|
||||
const BalanceItem = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
export function useFormatBalance(balance: CurrencyAmount<Currency> | undefined) {
|
||||
return useMemo(
|
||||
() => (balance ? formatToDecimal(balance, Math.min(balance.currency.decimals, 2)) : undefined),
|
||||
[balance]
|
||||
)
|
||||
}
|
||||
|
||||
export function useFormatUsdValue(usdValue: CurrencyAmount<Token> | null) {
|
||||
return useMemo(() => {
|
||||
const float = usdValue ? currencyAmountToPreciseFloat(usdValue) : undefined
|
||||
if (!float) return undefined
|
||||
return formatDollar({ num: float, isPrice: true })
|
||||
}, [usdValue])
|
||||
}
|
||||
|
||||
export default function BalanceSummary({ token }: { token: Currency }) {
|
||||
const { account } = useWeb3React()
|
||||
const balance = useCurrencyBalance(account, token)
|
||||
const formattedBalance = useFormatBalance(balance)
|
||||
const usdValue = useStablecoinValue(balance)
|
||||
const formattedUsdValue = useFormatUsdValue(usdValue)
|
||||
const formattedBalance = formatCurrencyAmount(balance, NumberType.TokenNonTx)
|
||||
const formattedUsdValue = formatCurrencyAmount(useStablecoinValue(balance), NumberType.FiatTokenStats)
|
||||
|
||||
if (!account || !balance) return null
|
||||
return (
|
||||
|
||||
54
src/components/Tokens/TokenDetails/InvalidTokenDetails.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { ReactComponent as EyeIcon } from '../../../assets/svg/eye.svg'
|
||||
|
||||
const InvalidDetailsContainer = styled.div`
|
||||
padding-top: 128px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const InvalidDetailsText = styled.span`
|
||||
margin-top: 28px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
text-align: center;
|
||||
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
line-height: 28px;
|
||||
`
|
||||
|
||||
const TokenExploreButton = styled.button`
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
padding: 12px 16px;
|
||||
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
`
|
||||
|
||||
export default function InvalidTokenDetails({ chainName }: { chainName?: string }) {
|
||||
const navigate = useNavigate()
|
||||
return (
|
||||
<InvalidDetailsContainer>
|
||||
<EyeIcon />
|
||||
<InvalidDetailsText>
|
||||
{chainName ? (
|
||||
<Trans>{`This token doesn't exist on ${chainName}`}</Trans>
|
||||
) : (
|
||||
<Trans>This token doesn't exist</Trans>
|
||||
)}
|
||||
</InvalidDetailsText>
|
||||
<TokenExploreButton onClick={() => navigate('/tokens')}>
|
||||
<Trans>Explore tokens</Trans>
|
||||
</TokenExploreButton>
|
||||
</InvalidDetailsContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
||||
@@ -8,8 +9,6 @@ import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
|
||||
import styled from 'styled-components/macro'
|
||||
import { StyledInternalLink } from 'theme'
|
||||
|
||||
import { useFormatBalance, useFormatUsdValue } from './BalanceSummary'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
@@ -84,9 +83,8 @@ const SwapButton = styled(StyledInternalLink)`
|
||||
export default function MobileBalanceSummaryFooter({ token }: { token: Currency }) {
|
||||
const { account } = useWeb3React()
|
||||
const balance = useCurrencyBalance(account, token)
|
||||
const formattedBalance = useFormatBalance(balance)
|
||||
const usdValue = useStablecoinValue(balance)
|
||||
const formattedUsdValue = useFormatUsdValue(usdValue)
|
||||
const formattedBalance = formatCurrencyAmount(balance, NumberType.TokenNonTx)
|
||||
const formattedUsdValue = formatCurrencyAmount(useStablecoinValue(balance), NumberType.FiatTokenStats)
|
||||
const chain = CHAIN_ID_TO_BACKEND_NAME[token.chainId].toLowerCase()
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { formatUSDPrice } from '@uniswap/conedison/format'
|
||||
import { AxisBottom, TickFormatter } from '@visx/axis'
|
||||
import { localPoint } from '@visx/event'
|
||||
import { EventType } from '@visx/event/lib/types'
|
||||
@@ -20,7 +21,6 @@ import {
|
||||
monthYearDayFormatter,
|
||||
weekFormatter,
|
||||
} from 'utils/formatChartTimes'
|
||||
import { formatDollar } from 'utils/formatNumbers'
|
||||
|
||||
export const DATA_EMPTY = { value: 0, timestamp: 0 }
|
||||
|
||||
@@ -92,6 +92,16 @@ interface PriceChartProps {
|
||||
timePeriod: TimePeriod
|
||||
}
|
||||
|
||||
function formatDisplayPrice(value: number) {
|
||||
const str = value.toFixed(9)
|
||||
const [digits, decimals] = str.split('.')
|
||||
// Displays longer string for numbers < $2 to show changes in both stablecoins & small values
|
||||
if (digits === '0' || digits === '1')
|
||||
return `$${digits + '.' + decimals.substring(0, 2) + decimals.substring(2).replace(/0+$/, '')}`
|
||||
|
||||
return formatUSDPrice(value)
|
||||
}
|
||||
|
||||
export function PriceChart({ width, height, prices, timePeriod }: PriceChartProps) {
|
||||
const locale = useActiveLocale()
|
||||
const theme = useTheme()
|
||||
@@ -104,9 +114,7 @@ export function PriceChart({ width, height, prices, timePeriod }: PriceChartProp
|
||||
|
||||
// set display price to ending price when prices have changed.
|
||||
useEffect(() => {
|
||||
if (prices) {
|
||||
setDisplayPrice(endingPrice)
|
||||
}
|
||||
setDisplayPrice(endingPrice)
|
||||
}, [prices, endingPrice])
|
||||
const [crosshair, setCrosshair] = useState<number | null>(null)
|
||||
|
||||
@@ -226,7 +234,7 @@ export function PriceChart({ width, height, prices, timePeriod }: PriceChartProp
|
||||
return (
|
||||
<>
|
||||
<ChartHeader>
|
||||
<TokenPrice>{formatDollar({ num: displayPrice.value, isPrice: true })}</TokenPrice>
|
||||
<TokenPrice>{formatDisplayPrice(displayPrice.value)}</TokenPrice>
|
||||
<DeltaContainer>
|
||||
{formattedDelta}
|
||||
<ArrowCell>{arrow}</ArrowCell>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
||||
import { TokenQueryData } from 'graphql/data/Token'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { useRef } from 'react'
|
||||
import { Twitter } from 'react-feather'
|
||||
@@ -63,12 +64,7 @@ const ShareAction = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
interface TokenInfo {
|
||||
token: NonNullable<TokenQueryData>
|
||||
isNative: boolean
|
||||
}
|
||||
|
||||
export default function ShareButton(tokenInfo: TokenInfo) {
|
||||
export default function ShareButton({ currency }: { currency: Currency }) {
|
||||
const theme = useTheme()
|
||||
const node = useRef<HTMLDivElement | null>(null)
|
||||
const open = useModalIsOpen(ApplicationModal.SHARE)
|
||||
@@ -76,12 +72,16 @@ export default function ShareButton(tokenInfo: TokenInfo) {
|
||||
useOnClickOutside(node, open ? toggleShare : undefined)
|
||||
const positionX = (window.screen.width - TWITTER_WIDTH) / 2
|
||||
const positionY = (window.screen.height - TWITTER_HEIGHT) / 2
|
||||
const tokenAddress = tokenInfo.isNative ? NATIVE_CHAIN_ID : tokenInfo.token.address
|
||||
const address = currency.isNative ? NATIVE_CHAIN_ID : currency.wrapped.address
|
||||
|
||||
const shareTweet = () => {
|
||||
toggleShare()
|
||||
window.open(
|
||||
`https://twitter.com/intent/tweet?text=Check%20out%20${tokenInfo.token.name}%20(${tokenInfo.token.symbol})%20https://app.uniswap.org/%23/tokens/${tokenInfo.token.chain}/${tokenAddress}%20via%20@uniswap`,
|
||||
`https://twitter.com/intent/tweet?text=Check%20out%20${currency.name}%20(${
|
||||
currency.symbol
|
||||
})%20https://app.uniswap.org/%23/tokens/${chainIdToBackendName(
|
||||
currency.chainId
|
||||
).toLowerCase()}/${address}%20via%20@uniswap`,
|
||||
'newwindow',
|
||||
`left=${positionX}, top=${positionY}, width=${TWITTER_WIDTH}, height=${TWITTER_HEIGHT}`
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { WIDGET_WIDTH } from 'components/Widget'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { textFadeIn } from 'theme/animations'
|
||||
import { textFadeIn } from 'theme/styles'
|
||||
|
||||
import { LoadingBubble } from '../loading'
|
||||
import { LogoContainer } from '../TokenTable/TokenRow'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { formatNumber, NumberType } from '@uniswap/conedison/format'
|
||||
import { ReactNode } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { textFadeIn } from 'theme/animations'
|
||||
import { formatDollar } from 'utils/formatNumbers'
|
||||
import { textFadeIn } from 'theme/styles'
|
||||
|
||||
import { TokenSortMethod } from '../state'
|
||||
import { HEADER_DESCRIPTIONS } from '../TokenTable/TokenRow'
|
||||
@@ -69,7 +69,7 @@ function Stat({
|
||||
{description && <InfoTip text={description}></InfoTip>}
|
||||
</StatTitle>
|
||||
|
||||
<StatPrice>{formatDollar({ num: value, isPrice })}</StatPrice>
|
||||
<StatPrice>{formatNumber(value, NumberType.FiatTokenStats)}</StatPrice>
|
||||
</StatWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Trace } from '@uniswap/analytics'
|
||||
import { PageName } from '@uniswap/analytics-events'
|
||||
import { Currency, NativeCurrency, Token } from '@uniswap/sdk-core'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import { AboutSection } from 'components/Tokens/TokenDetails/About'
|
||||
import AddressSection from 'components/Tokens/TokenDetails/AddressSection'
|
||||
import BalanceSummary from 'components/Tokens/TokenDetails/BalanceSummary'
|
||||
@@ -24,24 +25,24 @@ import TokenSafetyMessage from 'components/TokenSafety/TokenSafetyMessage'
|
||||
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
|
||||
import Widget from 'components/Widget'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { DEFAULT_ERC20_DECIMALS, NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
|
||||
import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens'
|
||||
import { checkWarning } from 'constants/tokenSafety'
|
||||
import { TokenPriceQuery } from 'graphql/data/__generated__/TokenPriceQuery.graphql'
|
||||
import { Chain, TokenQuery } from 'graphql/data/Token'
|
||||
import { QueryToken, tokenQuery, TokenQueryData } from 'graphql/data/Token'
|
||||
import { TopToken } from 'graphql/data/TopTokens'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
|
||||
import { Chain, TokenQuery, TokenQueryData } from 'graphql/data/Token'
|
||||
import { QueryToken, tokenQuery } from 'graphql/data/Token'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID, getTokenDetailsURL } from 'graphql/data/util'
|
||||
import { useIsUserAddedTokenOnChain } from 'hooks/Tokens'
|
||||
import { useOnGlobalChainSwitch } from 'hooks/useGlobalChainSwitch'
|
||||
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
|
||||
import { UNKNOWN_TOKEN_SYMBOL, useTokenFromActiveNetwork } from 'lib/hooks/useCurrency'
|
||||
import { useCallback, useMemo, useState, useTransition } from 'react'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import { PreloadedQuery, usePreloadedQuery } from 'react-relay'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { isAddress } from 'utils'
|
||||
|
||||
import { RefetchPricesFunction } from './ChartSection'
|
||||
import InvalidTokenDetails from './InvalidTokenDetails'
|
||||
|
||||
const TokenSymbol = styled.span`
|
||||
text-transform: uppercase;
|
||||
@@ -53,65 +54,100 @@ const TokenActions = styled.div`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
|
||||
export function useTokenLogoURI(token?: TokenQueryData | TopToken, nativeCurrency?: Token | NativeCurrency) {
|
||||
const chainId = token ? CHAIN_NAME_TO_CHAIN_ID[token.chain] : SupportedChainId.MAINNET
|
||||
return [
|
||||
...useCurrencyLogoURIs(nativeCurrency),
|
||||
...useCurrencyLogoURIs({ ...token, chainId }),
|
||||
token?.project?.logoUrl,
|
||||
][0]
|
||||
function useOnChainToken(address: string | undefined, skip: boolean) {
|
||||
const token = useTokenFromActiveNetwork(skip || !address ? undefined : address)
|
||||
|
||||
if (skip || !address || (token && token?.symbol === UNKNOWN_TOKEN_SYMBOL)) {
|
||||
return undefined
|
||||
} else {
|
||||
return token
|
||||
}
|
||||
}
|
||||
|
||||
// Selects most relevant token based on data available, preferring native > query > on-chain
|
||||
// Token will be null if still loading from on-chain, and undefined if unavailable
|
||||
function useRelevantToken(
|
||||
address: string | undefined,
|
||||
pageChainId: number,
|
||||
tokenQueryData: TokenQueryData | undefined
|
||||
) {
|
||||
const { chainId: activeChainId } = useWeb3React()
|
||||
const queryToken = useMemo(() => {
|
||||
if (!address) return undefined
|
||||
if (address === NATIVE_CHAIN_ID) return nativeOnChain(pageChainId)
|
||||
if (tokenQueryData) return new QueryToken(tokenQueryData)
|
||||
return undefined
|
||||
}, [pageChainId, address, tokenQueryData])
|
||||
// fetches on-chain token if query data is missing and page chain matches global chain (else fetch won't work)
|
||||
const skipOnChainFetch = Boolean(queryToken) || pageChainId !== activeChainId
|
||||
const onChainToken = useOnChainToken(address, skipOnChainFetch)
|
||||
|
||||
return useMemo(
|
||||
() => ({ token: queryToken ?? onChainToken, didFetchFromChain: !queryToken }),
|
||||
[onChainToken, queryToken]
|
||||
)
|
||||
}
|
||||
|
||||
type TokenDetailsProps = {
|
||||
tokenAddress: string | undefined
|
||||
urlAddress: string | undefined
|
||||
chain: Chain
|
||||
tokenQueryReference: PreloadedQuery<TokenQuery>
|
||||
priceQueryReference: PreloadedQuery<TokenPriceQuery> | null | undefined
|
||||
refetchTokenPrices: RefetchPricesFunction
|
||||
}
|
||||
export default function TokenDetails({
|
||||
tokenAddress,
|
||||
urlAddress,
|
||||
chain,
|
||||
tokenQueryReference,
|
||||
priceQueryReference,
|
||||
refetchTokenPrices,
|
||||
}: TokenDetailsProps) {
|
||||
if (!tokenAddress) {
|
||||
throw new Error(`Invalid token details route: tokenAddress param is undefined`)
|
||||
if (!urlAddress) {
|
||||
throw new Error('Invalid token details route: tokenAddress param is undefined')
|
||||
}
|
||||
const address = useMemo(
|
||||
() => (urlAddress === NATIVE_CHAIN_ID ? urlAddress : isAddress(urlAddress) || undefined),
|
||||
[urlAddress]
|
||||
)
|
||||
|
||||
const pageChainId = CHAIN_NAME_TO_CHAIN_ID[chain]
|
||||
const nativeCurrency = nativeOnChain(pageChainId)
|
||||
const isNative = tokenAddress === NATIVE_CHAIN_ID
|
||||
|
||||
const tokenQueryData = usePreloadedQuery(tokenQuery, tokenQueryReference).tokens?.[0]
|
||||
const token = useMemo(() => {
|
||||
if (isNative) return nativeCurrency
|
||||
if (tokenQueryData) return new QueryToken(tokenQueryData)
|
||||
return new Token(pageChainId, tokenAddress, DEFAULT_ERC20_DECIMALS)
|
||||
}, [isNative, nativeCurrency, pageChainId, tokenAddress, tokenQueryData])
|
||||
const crossChainMap = useMemo(
|
||||
() =>
|
||||
tokenQueryData?.project?.tokens.reduce((map, current) => {
|
||||
if (current) map[current.chain] = current.address
|
||||
return map
|
||||
}, {} as { [key: string]: string | undefined }) ?? {},
|
||||
[tokenQueryData]
|
||||
)
|
||||
|
||||
const tokenWarning = tokenAddress ? checkWarning(tokenAddress) : null
|
||||
const { token, didFetchFromChain } = useRelevantToken(address, pageChainId, tokenQueryData)
|
||||
|
||||
const tokenWarning = address ? checkWarning(address) : null
|
||||
const isBlockedToken = tokenWarning?.canProceed === false
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
// Wrapping navigate in a transition prevents Suspense from unnecessarily showing fallbacks again.
|
||||
const [isPending, startTokenTransition] = useTransition()
|
||||
const navigateToTokenForChain = useCallback(
|
||||
(chain: Chain) => {
|
||||
const chainName = chain.toLowerCase()
|
||||
const token = tokenQueryData?.project?.tokens.find((token) => token.chain === chain && token.address)
|
||||
const address = isNative ? NATIVE_CHAIN_ID : token?.address
|
||||
(update: Chain) => {
|
||||
if (!address) return
|
||||
startTokenTransition(() => navigate(`/tokens/${chainName}/${address}`))
|
||||
const bridgedAddress = crossChainMap[update]
|
||||
if (bridgedAddress) {
|
||||
startTokenTransition(() => navigate(getTokenDetailsURL(bridgedAddress, update)))
|
||||
} else if (didFetchFromChain || token?.isNative) {
|
||||
startTokenTransition(() => navigate(getTokenDetailsURL(address, update)))
|
||||
}
|
||||
},
|
||||
[isNative, navigate, startTokenTransition, tokenQueryData?.project?.tokens]
|
||||
[address, crossChainMap, didFetchFromChain, navigate, token?.isNative]
|
||||
)
|
||||
useOnGlobalChainSwitch(navigateToTokenForChain)
|
||||
|
||||
const navigateToWidgetSelectedToken = useCallback(
|
||||
(token: Currency) => {
|
||||
const address = token.isNative ? NATIVE_CHAIN_ID : token.address
|
||||
startTokenTransition(() => navigate(`/tokens/${chain.toLowerCase()}/${address}`))
|
||||
startTokenTransition(() => navigate(getTokenDetailsURL(address, chain)))
|
||||
},
|
||||
[chain, navigate]
|
||||
)
|
||||
@@ -119,7 +155,7 @@ export default function TokenDetails({
|
||||
const [continueSwap, setContinueSwap] = useState<{ resolve: (value: boolean | PromiseLike<boolean>) => void }>()
|
||||
|
||||
// Show token safety modal if Swap-reviewing a warning token, at all times if the current token is blocked
|
||||
const shouldShowSpeedbump = !useIsUserAddedTokenOnChain(tokenAddress, pageChainId) && tokenWarning !== null
|
||||
const shouldShowSpeedbump = !useIsUserAddedTokenOnChain(address, pageChainId) && tokenWarning !== null
|
||||
const onReviewSwapClick = useCallback(
|
||||
() => new Promise<boolean>((resolve) => (shouldShowSpeedbump ? setContinueSwap({ resolve }) : resolve(true))),
|
||||
[shouldShowSpeedbump]
|
||||
@@ -133,13 +169,20 @@ export default function TokenDetails({
|
||||
[continueSwap, setContinueSwap]
|
||||
)
|
||||
|
||||
const logoSrc = useTokenLogoURI(tokenQueryData, isNative ? nativeCurrency : undefined)
|
||||
const L2Icon = getChainInfo(pageChainId)?.circleLogoUrl
|
||||
|
||||
// address will never be undefined if token is defined; address is checked here to appease typechecker
|
||||
if (token === undefined || !address) {
|
||||
return <InvalidTokenDetails chainName={address && getChainInfo(pageChainId)?.label} />
|
||||
}
|
||||
return (
|
||||
<Trace page={PageName.TOKEN_DETAILS_PAGE} properties={{ tokenAddress, tokenName: token?.name }} shouldLogImpression>
|
||||
<Trace
|
||||
page={PageName.TOKEN_DETAILS_PAGE}
|
||||
properties={{ tokenAddress: address, tokenName: token?.name }}
|
||||
shouldLogImpression
|
||||
>
|
||||
<TokenDetailsLayout>
|
||||
{tokenQueryData && !isPending ? (
|
||||
{token && !isPending ? (
|
||||
<LeftPanel>
|
||||
<BreadcrumbNavLink to={`/tokens/${chain.toLowerCase()}`}>
|
||||
<ArrowLeft size={14} /> Tokens
|
||||
@@ -147,40 +190,33 @@ export default function TokenDetails({
|
||||
<TokenInfoContainer>
|
||||
<TokenNameCell>
|
||||
<LogoContainer>
|
||||
<CurrencyLogo
|
||||
src={logoSrc}
|
||||
size="32px"
|
||||
symbol={isNative ? nativeCurrency?.symbol : token?.symbol}
|
||||
currency={isNative ? nativeCurrency : token}
|
||||
/>
|
||||
<CurrencyLogo currency={token} size="32px" />
|
||||
<L2NetworkLogo networkUrl={L2Icon} size="16px" />
|
||||
</LogoContainer>
|
||||
{token?.name ?? <Trans>Name not found</Trans>}
|
||||
<TokenSymbol>{token?.symbol ?? <Trans>Symbol not found</Trans>}</TokenSymbol>
|
||||
{token.name ?? <Trans>Name not found</Trans>}
|
||||
<TokenSymbol>{token.symbol ?? <Trans>Symbol not found</Trans>}</TokenSymbol>
|
||||
</TokenNameCell>
|
||||
<TokenActions>
|
||||
{tokenQueryData?.name && tokenQueryData.symbol && tokenQueryData.address && (
|
||||
<ShareButton token={tokenQueryData} isNative={!!nativeCurrency} />
|
||||
)}
|
||||
<ShareButton currency={token} />
|
||||
</TokenActions>
|
||||
</TokenInfoContainer>
|
||||
<ChartSection priceQueryReference={priceQueryReference} refetchTokenPrices={refetchTokenPrices} />
|
||||
<StatsSection
|
||||
TVL={tokenQueryData.market?.totalValueLocked?.value}
|
||||
volume24H={tokenQueryData.market?.volume24H?.value}
|
||||
priceHigh52W={tokenQueryData.market?.priceHigh52W?.value}
|
||||
priceLow52W={tokenQueryData.market?.priceLow52W?.value}
|
||||
TVL={tokenQueryData?.market?.totalValueLocked?.value}
|
||||
volume24H={tokenQueryData?.market?.volume24H?.value}
|
||||
priceHigh52W={tokenQueryData?.market?.priceHigh52W?.value}
|
||||
priceLow52W={tokenQueryData?.market?.priceLow52W?.value}
|
||||
/>
|
||||
{!isNative && (
|
||||
{!token.isNative && (
|
||||
<>
|
||||
<Hr />
|
||||
<AboutSection
|
||||
address={tokenQueryData.address ?? ''}
|
||||
description={tokenQueryData.project?.description}
|
||||
homepageUrl={tokenQueryData.project?.homepageUrl}
|
||||
twitterName={tokenQueryData.project?.twitterName}
|
||||
address={address}
|
||||
description={tokenQueryData?.project?.description}
|
||||
homepageUrl={tokenQueryData?.project?.homepageUrl}
|
||||
twitterName={tokenQueryData?.project?.twitterName}
|
||||
/>
|
||||
<AddressSection address={tokenQueryData.address ?? ''} />
|
||||
<AddressSection address={address} />
|
||||
</>
|
||||
)}
|
||||
</LeftPanel>
|
||||
@@ -190,25 +226,23 @@ export default function TokenDetails({
|
||||
|
||||
<RightPanel>
|
||||
<Widget
|
||||
token={token ?? nativeCurrency}
|
||||
token={token ?? undefined}
|
||||
onTokenChange={navigateToWidgetSelectedToken}
|
||||
onReviewSwapClick={onReviewSwapClick}
|
||||
/>
|
||||
{tokenWarning && <TokenSafetyMessage tokenAddress={tokenAddress ?? ''} warning={tokenWarning} />}
|
||||
{tokenWarning && <TokenSafetyMessage tokenAddress={address} warning={tokenWarning} />}
|
||||
{token && <BalanceSummary token={token} />}
|
||||
</RightPanel>
|
||||
{token && <MobileBalanceSummaryFooter token={token} />}
|
||||
|
||||
{tokenAddress && (
|
||||
<TokenSafetyModal
|
||||
isOpen={isBlockedToken || !!continueSwap}
|
||||
tokenAddress={tokenAddress}
|
||||
onContinue={() => onResolveSwap(true)}
|
||||
onBlocked={() => navigate(-1)}
|
||||
onCancel={() => onResolveSwap(false)}
|
||||
showCancel={true}
|
||||
/>
|
||||
)}
|
||||
<TokenSafetyModal
|
||||
isOpen={isBlockedToken || !!continueSwap}
|
||||
tokenAddress={address}
|
||||
onContinue={() => onResolveSwap(true)}
|
||||
onBlocked={() => navigate(-1)}
|
||||
onCancel={() => onResolveSwap(false)}
|
||||
showCancel={true}
|
||||
/>
|
||||
</TokenDetailsLayout>
|
||||
</Trace>
|
||||
)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { EventName } from '@uniswap/analytics-events'
|
||||
import { formatNumber, formatUSDPrice, NumberType } from '@uniswap/conedison/format'
|
||||
import { ParentSize } from '@visx/responsive'
|
||||
import SparklineChart from 'components/Charts/SparklineChart'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import QueryTokenLogo from 'components/Logo/QueryTokenLogo'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SparklineMap, TopToken } from 'graphql/data/TopTokens'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID, getTokenDetailsURL } from 'graphql/data/util'
|
||||
@@ -14,7 +15,6 @@ import { ArrowDown, ArrowUp } from 'react-feather'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import styled, { css, useTheme } from 'styled-components/macro'
|
||||
import { ClickableStyle } from 'theme'
|
||||
import { formatDollar } from 'utils/formatNumbers'
|
||||
|
||||
import {
|
||||
LARGE_MEDIA_BREAKPOINT,
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
TokenSortMethod,
|
||||
useSetSortMethod,
|
||||
} from '../state'
|
||||
import { useTokenLogoURI } from '../TokenDetails'
|
||||
import InfoTip from '../TokenDetails/InfoTip'
|
||||
import { formatDelta, getDeltaArrow } from '../TokenDetails/PriceChart'
|
||||
|
||||
@@ -438,7 +437,8 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
|
||||
const lowercaseChainName = useParams<{ chainName?: string }>().chainName?.toUpperCase() ?? 'ethereum'
|
||||
const filterNetwork = lowercaseChainName.toUpperCase()
|
||||
const L2Icon = getChainInfo(CHAIN_NAME_TO_CHAIN_ID[filterNetwork])?.circleLogoUrl
|
||||
const chainId = CHAIN_NAME_TO_CHAIN_ID[filterNetwork]
|
||||
const L2Icon = getChainInfo(chainId)?.circleLogoUrl
|
||||
const timePeriod = useAtomValue(filterTimeAtom)
|
||||
const delta = token.market?.pricePercentChange?.value
|
||||
const arrow = getDeltaArrow(delta)
|
||||
@@ -446,7 +446,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
const rank = sortAscending ? tokenListLength - tokenListIndex : tokenListIndex + 1
|
||||
|
||||
const exploreTokenSelectedEventProperties = {
|
||||
chain_id: filterNetwork,
|
||||
chain_id: chainId,
|
||||
token_address: tokenAddress,
|
||||
token_symbol: tokenSymbol,
|
||||
token_list_index: tokenListIndex,
|
||||
@@ -469,7 +469,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
tokenInfo={
|
||||
<ClickableName>
|
||||
<LogoContainer>
|
||||
<CurrencyLogo src={useTokenLogoURI(token)} symbol={tokenSymbol} />
|
||||
<QueryTokenLogo token={token} />
|
||||
<L2NetworkLogo networkUrl={L2Icon} />
|
||||
</LogoContainer>
|
||||
<TokenInfoCell>
|
||||
@@ -481,7 +481,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
price={
|
||||
<ClickableContent>
|
||||
<PriceInfoCell>
|
||||
{formatDollar({ num: token.market?.price?.value, isPrice: true, lessPreciseStablecoinValues: true })}
|
||||
{formatUSDPrice(token.market?.price?.value)}
|
||||
<PercentChangeInfoCell>
|
||||
{formattedDelta}
|
||||
{arrow}
|
||||
@@ -495,8 +495,14 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
{arrow}
|
||||
</ClickableContent>
|
||||
}
|
||||
tvl={<ClickableContent>{formatDollar({ num: token.market?.totalValueLocked?.value })}</ClickableContent>}
|
||||
volume={<ClickableContent>{formatDollar({ num: token.market?.volume?.value })}</ClickableContent>}
|
||||
tvl={
|
||||
<ClickableContent>
|
||||
{formatNumber(token.market?.totalValueLocked?.value, NumberType.FiatTokenStats)}
|
||||
</ClickableContent>
|
||||
}
|
||||
volume={
|
||||
<ClickableContent>{formatNumber(token.market?.volume?.value, NumberType.FiatTokenStats)}</ClickableContent>
|
||||
}
|
||||
sparkLine={
|
||||
<SparkLine>
|
||||
<ParentSize>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { transparentize } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { ReactNode, useCallback, useEffect, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import Popover, { PopoverProps } from '../Popover'
|
||||
|
||||
export const TooltipContainer = styled.div`
|
||||
max-width: 256px;
|
||||
cursor: default;
|
||||
padding: 0.6rem 1rem;
|
||||
font-weight: 400;
|
||||
word-break: break-word;
|
||||
@@ -18,7 +19,11 @@ export const TooltipContainer = styled.div`
|
||||
|
||||
interface TooltipProps extends Omit<PopoverProps, 'content'> {
|
||||
text: ReactNode
|
||||
open?: () => void
|
||||
close?: () => void
|
||||
noOp?: () => void
|
||||
disableHover?: boolean // disable the hover and content display
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
interface TooltipContentProps extends Omit<PopoverProps, 'content'> {
|
||||
@@ -29,8 +34,19 @@ interface TooltipContentProps extends Omit<PopoverProps, 'content'> {
|
||||
disableHover?: boolean // disable the hover and content display
|
||||
}
|
||||
|
||||
export default function Tooltip({ text, ...rest }: TooltipProps) {
|
||||
return <Popover content={text && <TooltipContainer>{text}</TooltipContainer>} {...rest} />
|
||||
export default function Tooltip({ text, open, close, noOp, disableHover, ...rest }: TooltipProps) {
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
text && (
|
||||
<TooltipContainer onMouseEnter={disableHover ? noOp : open} onMouseLeave={disableHover ? noOp : close}>
|
||||
{text}
|
||||
</TooltipContainer>
|
||||
)
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function TooltipContent({ content, wrap = false, ...rest }: TooltipContentProps) {
|
||||
@@ -38,14 +54,36 @@ function TooltipContent({ content, wrap = false, ...rest }: TooltipContentProps)
|
||||
}
|
||||
|
||||
/** Standard text tooltip. */
|
||||
export function MouseoverTooltip({ text, disableHover, children, ...rest }: Omit<TooltipProps, 'show'>) {
|
||||
export function MouseoverTooltip({ text, disableHover, children, timeout, ...rest }: Omit<TooltipProps, 'show'>) {
|
||||
const [show, setShow] = useState(false)
|
||||
const open = useCallback(() => setShow(true), [setShow])
|
||||
const close = useCallback(() => setShow(false), [setShow])
|
||||
|
||||
useEffect(() => {
|
||||
if (show && timeout) {
|
||||
const tooltipTimer = setTimeout(() => {
|
||||
setShow(false)
|
||||
}, timeout)
|
||||
|
||||
return () => {
|
||||
clearTimeout(tooltipTimer)
|
||||
}
|
||||
}
|
||||
return
|
||||
}, [timeout, show])
|
||||
|
||||
const noOp = () => null
|
||||
return (
|
||||
<Tooltip {...rest} show={show} text={disableHover ? null : text}>
|
||||
<div onMouseEnter={disableHover ? noOp : open} onMouseLeave={disableHover ? noOp : close}>
|
||||
<Tooltip
|
||||
{...rest}
|
||||
open={open}
|
||||
close={close}
|
||||
noOp={noOp}
|
||||
disableHover={disableHover}
|
||||
show={show}
|
||||
text={disableHover ? null : text}
|
||||
>
|
||||
<div onMouseEnter={disableHover ? noOp : open} onMouseLeave={disableHover || timeout ? noOp : close}>
|
||||
{children}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
@@ -3,12 +3,15 @@ import AddressClaimModal from 'components/claim/AddressClaimModal'
|
||||
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
|
||||
import NftExploreBanner from 'nft/components/nftExploreBanner/NftExploreBanner'
|
||||
import { lazy } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
|
||||
const Bag = lazy(() => import('nft/components/bag/Bag'))
|
||||
const TransactionCompleteModal = lazy(() => import('nft/components/collection/TransactionCompleteModal'))
|
||||
const AirdropModal = lazy(() => import('components/AirdropModal'))
|
||||
|
||||
export default function TopLevelModals() {
|
||||
const addressClaimOpen = useModalIsOpen(ApplicationModal.ADDRESS_CLAIM)
|
||||
@@ -16,6 +19,12 @@ export default function TopLevelModals() {
|
||||
|
||||
const blockedAccountModalOpen = useModalIsOpen(ApplicationModal.BLOCKED_ACCOUNT)
|
||||
const { account } = useWeb3React()
|
||||
const location = useLocation()
|
||||
const isNftEnabled = useNftFlag() === NftVariant.Enabled
|
||||
const pageShowsNftPromoBanner =
|
||||
location.pathname.startsWith('/swap') ||
|
||||
location.pathname.startsWith('/tokens') ||
|
||||
location.pathname.startsWith('/pool')
|
||||
|
||||
useAccountRiskCheck(account)
|
||||
const open = Boolean(blockedAccountModalOpen && account)
|
||||
@@ -24,7 +33,13 @@ export default function TopLevelModals() {
|
||||
<AddressClaimModal isOpen={addressClaimOpen} onDismiss={addressClaimToggle} />
|
||||
<ConnectedAccountBlocked account={account} isOpen={open} />
|
||||
<Bag />
|
||||
{useNftFlag() === NftVariant.Enabled && <TransactionCompleteModal />}
|
||||
{isNftEnabled && (
|
||||
<>
|
||||
<TransactionCompleteModal />
|
||||
<AirdropModal />
|
||||
{pageShowsNftPromoBanner && <NftExploreBanner />}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import useCopyClipboard from 'hooks/useCopyClipboard'
|
||||
import useStablecoinPrice from 'hooks/useStablecoinPrice'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { ProfilePageStateType } from 'nft/types'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { Copy, ExternalLink, Power } from 'react-feather'
|
||||
@@ -23,11 +24,11 @@ import { shortenAddress } from '../../nft/utils/address'
|
||||
import { useCloseModal, useToggleModal } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/claim/hooks'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
import { ButtonEmphasis, ButtonSize, ThemeButton } from '../Button'
|
||||
import StatusIcon from '../Identicon/StatusIcon'
|
||||
import IconButton, { IconHoverText } from './IconButton'
|
||||
|
||||
const WalletButton = styled(ButtonPrimary)`
|
||||
const WalletButton = styled(ThemeButton)`
|
||||
border-radius: 12px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
@@ -117,6 +118,7 @@ const AuthenticatedHeader = () => {
|
||||
const setSellPageState = useProfilePageState((state) => state.setProfilePageState)
|
||||
const resetSellAssets = useSellAsset((state) => state.reset)
|
||||
const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters)
|
||||
const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable)
|
||||
|
||||
const unclaimedAmount: CurrencyAmount<Token> | undefined = useUserUnclaimedAmount(account)
|
||||
const isUnclaimed = useUserHasAvailableClaim(account)
|
||||
@@ -124,6 +126,7 @@ const AuthenticatedHeader = () => {
|
||||
const nativeCurrency = useNativeCurrency()
|
||||
const nativeCurrencyPrice = useStablecoinPrice(nativeCurrency ?? undefined) || 0
|
||||
const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
|
||||
const openNftModal = useToggleModal(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
|
||||
const disconnect = useCallback(() => {
|
||||
if (connector && connector.deactivate) {
|
||||
connector.deactivate()
|
||||
@@ -177,15 +180,20 @@ const AuthenticatedHeader = () => {
|
||||
<USDText>${amountUSD.toFixed(2)} USD</USDText>
|
||||
</BalanceWrapper>
|
||||
{nftFlag === NftVariant.Enabled && (
|
||||
<ProfileButton onClick={navigateToProfile}>
|
||||
<ProfileButton onClick={navigateToProfile} size={ButtonSize.medium} emphasis={ButtonEmphasis.medium}>
|
||||
<Trans>View and sell NFTs</Trans>
|
||||
</ProfileButton>
|
||||
)}
|
||||
{isUnclaimed && (
|
||||
<UNIButton onClick={openClaimModal}>
|
||||
<UNIButton onClick={openClaimModal} size={ButtonSize.medium} emphasis={ButtonEmphasis.medium}>
|
||||
<Trans>Claim</Trans> {unclaimedAmount?.toFixed(0, { groupSeparator: ',' } ?? '-')} <Trans>reward</Trans>
|
||||
</UNIButton>
|
||||
)}
|
||||
{nftFlag === NftVariant.Enabled && isClaimAvailable && (
|
||||
<UNIButton size={ButtonSize.medium} emphasis={ButtonEmphasis.medium} onClick={openNftModal}>
|
||||
<Trans>Claim Uniswap NFT Airdrop</Trans>
|
||||
</UNIButton>
|
||||
)}
|
||||
</Column>
|
||||
</AuthenticatedHeaderWrapper>
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import ms from 'ms.macro'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { flexColumnNoWrap } from 'theme/styles'
|
||||
|
||||
import { useAllTransactions } from '../../state/transactions/hooks'
|
||||
import { clearAllTransactions } from '../../state/transactions/reducer'
|
||||
@@ -20,7 +21,7 @@ const Divider = styled.div`
|
||||
`
|
||||
|
||||
const TransactionListWrapper = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
${flexColumnNoWrap};
|
||||
`
|
||||
|
||||
interface TransactionInformation {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
|
||||
import React from 'react'
|
||||
import { Check } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
|
||||
|
||||
import { ExternalLink } from '../../theme'
|
||||
|
||||
@@ -20,7 +21,7 @@ const InfoCard = styled.button<{ isActive?: boolean }>`
|
||||
`
|
||||
|
||||
const CheckIcon = styled(Check)`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
${flexColumnNoWrap};
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
align-items: center;
|
||||
@@ -41,7 +42,7 @@ const OptionCard = styled(InfoCard as any)`
|
||||
`
|
||||
|
||||
const OptionCardLeft = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
${flexColumnNoWrap};
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
`
|
||||
@@ -60,7 +61,7 @@ const OptionCardClickable = styled(OptionCard as any)<{
|
||||
`
|
||||
|
||||
const HeaderText = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
${flexRowNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${(props) =>
|
||||
@@ -76,7 +77,7 @@ const SubHeader = styled.div`
|
||||
`
|
||||
|
||||
const IconWrapper = styled.div<{ size?: number | null }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
${flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-right: 12px;
|
||||
|
||||
@@ -4,11 +4,12 @@ import { ButtonEmpty, ButtonPrimary } from 'components/Button'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
|
||||
|
||||
import Loader from '../Loader'
|
||||
|
||||
const PendingSection = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
${flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
@@ -33,14 +34,14 @@ const AlertTriangleIcon = styled(AlertTriangle)`
|
||||
`
|
||||
|
||||
const LoaderContainer = styled.div`
|
||||
${flexRowNoWrap};
|
||||
margin: 16px 0;
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const LoadingMessage = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
${flexRowNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12px;
|
||||
@@ -51,13 +52,13 @@ const LoadingMessage = styled.div`
|
||||
`
|
||||
|
||||
const ErrorGroup = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
${flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
`
|
||||
|
||||
const LoadingWrapper = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
${flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import { useConnectedWallets } from 'state/wallets/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
|
||||
import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
@@ -48,7 +49,7 @@ const CloseColor = styled(Close)`
|
||||
`
|
||||
|
||||
const Wrapper = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
${flexColumnNoWrap};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
outline: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
@@ -58,7 +59,7 @@ const Wrapper = styled.div`
|
||||
`
|
||||
|
||||
const HeaderRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
${flexRowNoWrap};
|
||||
padding: 1rem 1rem;
|
||||
font-weight: 600;
|
||||
size: 16px;
|
||||
|
||||
@@ -7,6 +7,7 @@ import WalletDropdown from 'components/WalletDropdown'
|
||||
import { getConnection } from 'connection/utils'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { Portal } from 'nft/components/common/Portal'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { getIsValidSwapQuote } from 'pages/Swap'
|
||||
import { darken } from 'polished'
|
||||
import { useMemo, useRef } from 'react'
|
||||
@@ -14,6 +15,8 @@ import { AlertTriangle, ChevronDown, ChevronUp } from 'react-feather'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import { useDerivedSwapInfo } from 'state/swap/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { colors } from 'theme/colors'
|
||||
import { flexRowNoWrap } from 'theme/styles'
|
||||
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
import {
|
||||
@@ -53,7 +56,7 @@ const ChevronWrapper = styled.button`
|
||||
`
|
||||
|
||||
const Web3StatusGeneric = styled(ButtonSecondary)`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
${flexRowNoWrap};
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
@@ -79,7 +82,7 @@ const Web3StatusError = styled(Web3StatusGeneric)`
|
||||
`
|
||||
|
||||
const Web3StatusConnectWrapper = styled.div<{ faded?: boolean }>`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
${flexRowNoWrap};
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.accentActionSoft};
|
||||
border-radius: ${FULL_BORDER_RADIUS}px;
|
||||
@@ -94,11 +97,16 @@ const Web3StatusConnectWrapper = styled.div<{ faded?: boolean }>`
|
||||
}
|
||||
`
|
||||
|
||||
const Web3StatusConnected = styled(Web3StatusGeneric)<{ pending?: boolean; isNftActive?: boolean }>`
|
||||
const Web3StatusConnected = styled(Web3StatusGeneric)<{
|
||||
pending?: boolean
|
||||
isNftActive?: boolean
|
||||
isClaimAvailable?: boolean
|
||||
}>`
|
||||
background-color: ${({ pending, theme }) => (pending ? theme.deprecated_primary1 : theme.deprecated_bg1)};
|
||||
border: 1px solid ${({ pending, theme }) => (pending ? theme.deprecated_primary1 : theme.deprecated_bg1)};
|
||||
color: ${({ pending, theme }) => (pending ? theme.deprecated_white : theme.deprecated_text1)};
|
||||
font-weight: 500;
|
||||
border: ${({ isClaimAvailable }) => isClaimAvailable && `1px solid ${colors.purple300}`};
|
||||
:hover,
|
||||
:focus {
|
||||
border: 1px solid ${({ theme }) => darken(0.05, theme.deprecated_bg3)};
|
||||
@@ -201,6 +209,7 @@ function Web3StatusInner() {
|
||||
const toggleWalletDropdown = useToggleWalletDropdown()
|
||||
const toggleWalletModal = useToggleWalletModal()
|
||||
const walletIsOpen = useModalIsOpen(ApplicationModal.WALLET_DROPDOWN)
|
||||
const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable)
|
||||
|
||||
const error = useAppSelector((state) => state.connection.errorByConnectionType[getConnection(connector).type])
|
||||
const isNftActive = useNftFlag() === NftVariant.Enabled
|
||||
@@ -240,6 +249,7 @@ function Web3StatusInner() {
|
||||
isNftActive={isNftActive}
|
||||
onClick={toggleWallet}
|
||||
pending={hasPendingTransactions}
|
||||
isClaimAvailable={isClaimAvailable}
|
||||
>
|
||||
{!hasPendingTransactions && <StatusIcon size={24} connectionType={connectionType} />}
|
||||
{hasPendingTransactions ? (
|
||||
|
||||
@@ -21,12 +21,12 @@ import {
|
||||
} from 'lib/utils/analytics'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useIsDarkMode } from 'state/user/hooks'
|
||||
import { DARK_THEME, LIGHT_THEME } from 'theme/widget'
|
||||
import { computeRealizedPriceImpact } from 'utils/prices'
|
||||
import { switchChain } from 'utils/switchChain'
|
||||
|
||||
import { useSyncWidgetInputs } from './inputs'
|
||||
import { useSyncWidgetSettings } from './settings'
|
||||
import { DARK_THEME, LIGHT_THEME } from './theme'
|
||||
import { useSyncWidgetTransactions } from './transactions'
|
||||
|
||||
export const WIDGET_WIDTH = 360
|
||||
|
||||
45
src/components/Widget/theme.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { darkTheme, lightTheme } from 'theme/colors'
|
||||
|
||||
const fonts = {
|
||||
fontFamily: 'Inter custom',
|
||||
}
|
||||
|
||||
export const LIGHT_THEME = {
|
||||
// surface
|
||||
container: lightTheme.backgroundSurface,
|
||||
interactive: lightTheme.backgroundInteractive,
|
||||
module: lightTheme.backgroundModule,
|
||||
accent: lightTheme.accentAction,
|
||||
dialog: lightTheme.backgroundBackdrop,
|
||||
outline: lightTheme.backgroundOutline,
|
||||
// text
|
||||
primary: lightTheme.textPrimary,
|
||||
secondary: lightTheme.textSecondary,
|
||||
onInteractive: lightTheme.accentTextDarkPrimary,
|
||||
// state
|
||||
success: lightTheme.accentSuccess,
|
||||
warning: lightTheme.accentWarning,
|
||||
error: lightTheme.accentCritical,
|
||||
|
||||
...fonts,
|
||||
}
|
||||
|
||||
export const DARK_THEME = {
|
||||
// surface
|
||||
container: darkTheme.backgroundSurface,
|
||||
interactive: darkTheme.backgroundInteractive,
|
||||
module: darkTheme.backgroundModule,
|
||||
accent: darkTheme.accentAction,
|
||||
dialog: darkTheme.backgroundBackdrop,
|
||||
outline: darkTheme.backgroundOutline,
|
||||
// text
|
||||
primary: darkTheme.textPrimary,
|
||||
secondary: darkTheme.textSecondary,
|
||||
onInteractive: darkTheme.accentTextLightPrimary,
|
||||
// state
|
||||
success: darkTheme.accentSuccess,
|
||||
warning: darkTheme.accentWarning,
|
||||
error: darkTheme.accentCritical,
|
||||
|
||||
...fonts,
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import { ButtonPrimary } from '../Button'
|
||||
import { LightCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { FiatValue } from '../CurrencyInputPanel/FiatValue'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import TradePrice from '../swap/TradePrice'
|
||||
import { AdvancedSwapDetails } from './AdvancedSwapDetails'
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import { ButtonEmpty } from 'components/Button'
|
||||
import Card, { OutlineCard } from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import Modal from 'components/Modal'
|
||||
import { AutoRow, RowBetween } from 'components/Row'
|
||||
import { useState } from 'react'
|
||||
|
||||
38
src/constants/TokenLogoLookupTable.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import store from 'state'
|
||||
|
||||
import { DEFAULT_LIST_OF_LISTS } from './lists'
|
||||
|
||||
class TokenLogoLookupTable {
|
||||
private dict: { [key: string]: string[] | undefined } = {}
|
||||
private initialized = false
|
||||
|
||||
initialize() {
|
||||
const dict: { [key: string]: string[] | undefined } = {}
|
||||
|
||||
DEFAULT_LIST_OF_LISTS.forEach((list) =>
|
||||
store.getState().lists.byUrl[list].current?.tokens.forEach((token) => {
|
||||
if (token.logoURI) {
|
||||
const lowercaseAddress = token.address.toLowerCase()
|
||||
const currentEntry = dict[lowercaseAddress]
|
||||
if (currentEntry) {
|
||||
currentEntry.push(token.logoURI)
|
||||
} else {
|
||||
dict[lowercaseAddress] = [token.logoURI]
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
this.dict = dict
|
||||
this.initialized = true
|
||||
}
|
||||
getIcons(address?: string | null) {
|
||||
if (!address) return undefined
|
||||
|
||||
if (!this.initialized) {
|
||||
this.initialize()
|
||||
}
|
||||
return this.dict[address.toLowerCase()]
|
||||
}
|
||||
}
|
||||
|
||||
export default new TokenLogoLookupTable()
|
||||
@@ -8,6 +8,8 @@ type AddressMap = { [chainId: number]: string }
|
||||
|
||||
export const UNI_ADDRESS: AddressMap = constructSameAddressMap('0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
|
||||
|
||||
export const UNISWAP_NFT_AIRDROP_CLAIM_ADDRESS = '0x8B799381ac40b838BBA4131ffB26197C432AFe78'
|
||||
|
||||
export const V2_FACTORY_ADDRESSES: AddressMap = constructSameAddressMap(V2_FACTORY_ADDRESS)
|
||||
export const V2_ROUTER_ADDRESS: AddressMap = constructSameAddressMap('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D')
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ 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'
|
||||
import ms from 'ms.macro'
|
||||
import { colorsDark } from 'theme/colors'
|
||||
import { darkTheme } from 'theme/colors'
|
||||
|
||||
import { SupportedChainId, SupportedL1ChainId, SupportedL2ChainId } from './chains'
|
||||
import { ARBITRUM_LIST, CELO_LIST, OPTIMISM_LIST } from './lists'
|
||||
@@ -61,7 +61,7 @@ const CHAIN_INFO: ChainInfoMap = {
|
||||
label: 'Ethereum',
|
||||
logoUrl: ethereumLogoUrl,
|
||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
||||
color: colorsDark.chain_1,
|
||||
color: darkTheme.chain_1,
|
||||
},
|
||||
[SupportedChainId.RINKEBY]: {
|
||||
networkType: NetworkType.L1,
|
||||
@@ -71,7 +71,7 @@ const CHAIN_INFO: ChainInfoMap = {
|
||||
label: 'Rinkeby',
|
||||
logoUrl: ethereumLogoUrl,
|
||||
nativeCurrency: { name: 'Rinkeby Ether', symbol: 'rETH', decimals: 18 },
|
||||
color: colorsDark.chain_4,
|
||||
color: darkTheme.chain_4,
|
||||
},
|
||||
[SupportedChainId.ROPSTEN]: {
|
||||
networkType: NetworkType.L1,
|
||||
@@ -81,7 +81,7 @@ const CHAIN_INFO: ChainInfoMap = {
|
||||
label: 'Ropsten',
|
||||
logoUrl: ethereumLogoUrl,
|
||||
nativeCurrency: { name: 'Ropsten Ether', symbol: 'ropETH', decimals: 18 },
|
||||
color: colorsDark.chain_3,
|
||||
color: darkTheme.chain_3,
|
||||
},
|
||||
[SupportedChainId.KOVAN]: {
|
||||
networkType: NetworkType.L1,
|
||||
@@ -91,7 +91,7 @@ const CHAIN_INFO: ChainInfoMap = {
|
||||
label: 'Kovan',
|
||||
logoUrl: ethereumLogoUrl,
|
||||
nativeCurrency: { name: 'Kovan Ether', symbol: 'kovETH', decimals: 18 },
|
||||
color: colorsDark.chain_420,
|
||||
color: darkTheme.chain_420,
|
||||
},
|
||||
[SupportedChainId.GOERLI]: {
|
||||
networkType: NetworkType.L1,
|
||||
@@ -101,7 +101,7 @@ const CHAIN_INFO: ChainInfoMap = {
|
||||
label: 'Görli',
|
||||
logoUrl: ethereumLogoUrl,
|
||||
nativeCurrency: { name: 'Görli Ether', symbol: 'görETH', decimals: 18 },
|
||||
color: colorsDark.chain_5,
|
||||
color: darkTheme.chain_5,
|
||||
},
|
||||
[SupportedChainId.OPTIMISM]: {
|
||||
networkType: NetworkType.L2,
|
||||
@@ -118,8 +118,8 @@ const CHAIN_INFO: ChainInfoMap = {
|
||||
statusPage: 'https://optimism.io/status',
|
||||
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ',
|
||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
||||
color: colorsDark.chain_10,
|
||||
backgroundColor: colorsDark.chain_10_background,
|
||||
color: darkTheme.chain_10,
|
||||
backgroundColor: darkTheme.chain_10_background,
|
||||
},
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: {
|
||||
networkType: NetworkType.L2,
|
||||
@@ -134,7 +134,7 @@ const CHAIN_INFO: ChainInfoMap = {
|
||||
statusPage: 'https://optimism.io/status',
|
||||
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ',
|
||||
nativeCurrency: { name: 'Optimism Goerli Ether', symbol: 'görOpETH', decimals: 18 },
|
||||
color: colorsDark.chain_420,
|
||||
color: darkTheme.chain_420,
|
||||
},
|
||||
[SupportedChainId.ARBITRUM_ONE]: {
|
||||
networkType: NetworkType.L2,
|
||||
@@ -149,8 +149,8 @@ const CHAIN_INFO: ChainInfoMap = {
|
||||
defaultListUrl: ARBITRUM_LIST,
|
||||
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137787-uniswap-on-arbitrum',
|
||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
||||
color: colorsDark.chain_42,
|
||||
backgroundColor: colorsDark.chain_42161_background,
|
||||
color: darkTheme.chain_42,
|
||||
backgroundColor: darkTheme.chain_42161_background,
|
||||
},
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: {
|
||||
networkType: NetworkType.L2,
|
||||
@@ -164,7 +164,7 @@ const CHAIN_INFO: ChainInfoMap = {
|
||||
defaultListUrl: ARBITRUM_LIST,
|
||||
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137787-uniswap-on-arbitrum',
|
||||
nativeCurrency: { name: 'Rinkeby Arbitrum Ether', symbol: 'rinkArbETH', decimals: 18 },
|
||||
color: colorsDark.chain_421611,
|
||||
color: darkTheme.chain_421611,
|
||||
},
|
||||
[SupportedChainId.POLYGON]: {
|
||||
networkType: NetworkType.L1,
|
||||
@@ -177,8 +177,8 @@ const CHAIN_INFO: ChainInfoMap = {
|
||||
logoUrl: polygonMaticLogo,
|
||||
circleLogoUrl: polygonCircleLogoUrl,
|
||||
nativeCurrency: { name: 'Polygon Matic', symbol: 'MATIC', decimals: 18 },
|
||||
color: colorsDark.chain_137,
|
||||
backgroundColor: colorsDark.chain_137_background,
|
||||
color: darkTheme.chain_137,
|
||||
backgroundColor: darkTheme.chain_137_background,
|
||||
},
|
||||
[SupportedChainId.POLYGON_MUMBAI]: {
|
||||
networkType: NetworkType.L1,
|
||||
|
||||
@@ -34,13 +34,7 @@ export const DEFAULT_INACTIVE_LIST_URLS: string[] = [
|
||||
ARBITRUM_LIST,
|
||||
OPTIMISM_LIST,
|
||||
CELO_LIST,
|
||||
...UNSUPPORTED_LIST_URLS,
|
||||
]
|
||||
|
||||
// this is the default list of lists that are exposed to users
|
||||
// lower index == higher priority for token import
|
||||
const DEFAULT_LIST_OF_LISTS_TO_DISPLAY: string[] = [...DEFAULT_ACTIVE_LIST_URLS, ...DEFAULT_INACTIVE_LIST_URLS]
|
||||
|
||||
export const DEFAULT_LIST_OF_LISTS: string[] = [
|
||||
...DEFAULT_LIST_OF_LISTS_TO_DISPLAY,
|
||||
...UNSUPPORTED_LIST_URLS, // need to load dynamic unsupported tokens as well
|
||||
]
|
||||
export const DEFAULT_LIST_OF_LISTS: string[] = [...DEFAULT_ACTIVE_LIST_URLS, ...DEFAULT_INACTIVE_LIST_URLS]
|
||||
|
||||
@@ -54,7 +54,7 @@ export function getWarningCopy(warning: Warning | null, plural = false) {
|
||||
export type Warning = {
|
||||
level: WARNING_LEVEL
|
||||
message: JSX.Element
|
||||
/* canProceed determines whether triangle/octagon alert icon is used, and
|
||||
/* canProceed determines whether triangle/slash alert icon is used, and
|
||||
whether this token is supported/able to be traded */
|
||||
canProceed: boolean
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ export const tokenQuery = graphql`
|
||||
twitterName
|
||||
logoUrl
|
||||
tokens {
|
||||
chain
|
||||
address
|
||||
chain @required(action: LOG)
|
||||
address @required(action: LOG)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,14 +61,14 @@ export type TokenQueryData = NonNullable<TokenQuery$data['tokens']>[number]
|
||||
|
||||
// TODO: Return a QueryToken from useTokenQuery instead of TokenQueryData to make it more usable in Currency-centric interfaces.
|
||||
export class QueryToken extends WrappedTokenInfo {
|
||||
constructor(data: NonNullable<TokenQueryData>) {
|
||||
constructor(data: NonNullable<TokenQueryData>, logoSrc?: string) {
|
||||
super({
|
||||
chainId: CHAIN_NAME_TO_CHAIN_ID[data.chain],
|
||||
address: data.address,
|
||||
decimals: data.decimals ?? DEFAULT_ERC20_DECIMALS,
|
||||
symbol: data.symbol ?? '',
|
||||
name: data.name ?? '',
|
||||
logoURI: data.project?.logoUrl ?? undefined,
|
||||
logoURI: logoSrc ?? data.project?.logoUrl ?? undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,50 +14,38 @@ import { useUserAddedTokens, useUserAddedTokensOnChain } from '../state/user/hoo
|
||||
import { TokenAddressMap, useUnsupportedTokenList } from './../state/lists/hooks'
|
||||
|
||||
// reduce token map into standard address <-> Token mapping, optionally include user added tokens
|
||||
function useTokensFromMap(tokenMap: TokenAddressMap, includeUserAdded: boolean): { [address: string]: Token } {
|
||||
function useTokensFromMap(tokenMap: TokenAddressMap): { [address: string]: Token } {
|
||||
const { chainId } = useWeb3React()
|
||||
const userAddedTokens = useUserAddedTokens()
|
||||
|
||||
return useMemo(() => {
|
||||
if (!chainId) return {}
|
||||
|
||||
// reduce to just tokens
|
||||
const mapWithoutUrls = Object.keys(tokenMap[chainId] ?? {}).reduce<{ [address: string]: Token }>(
|
||||
(newMap, address) => {
|
||||
newMap[address] = tokenMap[chainId][address].token
|
||||
return newMap
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
if (includeUserAdded) {
|
||||
return (
|
||||
userAddedTokens
|
||||
// reduce into all ALL_TOKENS filtered by the current chain
|
||||
.reduce<{ [address: string]: Token }>(
|
||||
(tokenMap, token) => {
|
||||
tokenMap[token.address] = token
|
||||
return tokenMap
|
||||
},
|
||||
// must make a copy because reduce modifies the map, and we do not
|
||||
// want to make a copy in every iteration
|
||||
{ ...mapWithoutUrls }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return mapWithoutUrls
|
||||
}, [chainId, userAddedTokens, tokenMap, includeUserAdded])
|
||||
return Object.keys(tokenMap[chainId] ?? {}).reduce<{ [address: string]: Token }>((newMap, address) => {
|
||||
newMap[address] = tokenMap[chainId][address].token
|
||||
return newMap
|
||||
}, {})
|
||||
}, [chainId, tokenMap])
|
||||
}
|
||||
|
||||
export function useAllTokens(): { [address: string]: Token } {
|
||||
const allTokens = useCombinedActiveList()
|
||||
return useTokensFromMap(allTokens, true)
|
||||
}
|
||||
|
||||
export function useActiveTokens(includeUserAdded: boolean): { [address: string]: Token } {
|
||||
const allTokens = useCombinedActiveList()
|
||||
return useTokensFromMap(allTokens, includeUserAdded)
|
||||
const tokensFromMap = useTokensFromMap(allTokens)
|
||||
const userAddedTokens = useUserAddedTokens()
|
||||
return useMemo(() => {
|
||||
return (
|
||||
userAddedTokens
|
||||
// reduce into all ALL_TOKENS filtered by the current chain
|
||||
.reduce<{ [address: string]: Token }>(
|
||||
(tokenMap, token) => {
|
||||
tokenMap[token.address] = token
|
||||
return tokenMap
|
||||
},
|
||||
// must make a copy because reduce modifies the map, and we do not
|
||||
// want to make a copy in every iteration
|
||||
{ ...tokensFromMap }
|
||||
)
|
||||
)
|
||||
}, [tokensFromMap, userAddedTokens])
|
||||
}
|
||||
|
||||
type BridgeInfo = Record<
|
||||
@@ -73,7 +61,7 @@ export function useUnsupportedTokens(): { [address: string]: Token } {
|
||||
const { chainId } = useWeb3React()
|
||||
const listsByUrl = useAllLists()
|
||||
const unsupportedTokensMap = useUnsupportedTokenList()
|
||||
const unsupportedTokens = useTokensFromMap(unsupportedTokensMap, false)
|
||||
const unsupportedTokens = useTokensFromMap(unsupportedTokensMap)
|
||||
|
||||
// checks the default L2 lists to see if `bridgeInfo` has an L1 address value that is unsupported
|
||||
const l2InferredBlockedTokens: typeof unsupportedTokens = useMemo(() => {
|
||||
|
||||
81
src/hooks/useAssetLogoSource.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import TokenLogoLookupTable from 'constants/TokenLogoLookupTable'
|
||||
import { chainIdToNetworkName, getNativeLogoURI } from 'lib/hooks/useCurrencyLogoURIs'
|
||||
import uriToHttp from 'lib/utils/uriToHttp'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { isAddress } from 'utils'
|
||||
|
||||
export const BAD_SRCS: { [tokenAddress: string]: true } = {}
|
||||
|
||||
// Converts uri's into fetchable urls
|
||||
function parseLogoSources(uris: string[]) {
|
||||
const urls: string[] = []
|
||||
uris.forEach((uri) => urls.push(...uriToHttp(uri)))
|
||||
return urls
|
||||
}
|
||||
|
||||
// Parses uri's, favors non-coingecko images, and improves coingecko logo quality
|
||||
function prioritizeLogoSources(uris: string[]) {
|
||||
const parsedUris = uris.map((uri) => uriToHttp(uri)).flat(1)
|
||||
const preferredUris: string[] = []
|
||||
|
||||
// Consolidate duplicate coingecko urls into one fallback source
|
||||
let coingeckoUrl: string | undefined = undefined
|
||||
|
||||
parsedUris.forEach((uri) => {
|
||||
if (uri.startsWith('https://assets.coingecko')) {
|
||||
if (!coingeckoUrl) {
|
||||
coingeckoUrl = uri.replace(/small|thumb/g, 'large')
|
||||
}
|
||||
} else {
|
||||
preferredUris.push(uri)
|
||||
}
|
||||
})
|
||||
// Places coingecko urls in the back of the source array
|
||||
return coingeckoUrl ? [...preferredUris, coingeckoUrl] : preferredUris
|
||||
}
|
||||
|
||||
function getInitialUrl(address?: string | null, chainId?: number | null, isNative?: boolean) {
|
||||
if (chainId && isNative) return getNativeLogoURI(chainId)
|
||||
|
||||
const networkName = chainId ? chainIdToNetworkName(chainId) : 'ethereum'
|
||||
const checksummedAddress = isAddress(address)
|
||||
if (checksummedAddress) {
|
||||
return `https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/${networkName}/assets/${checksummedAddress}/logo.png`
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export default function useAssetLogoSource(
|
||||
address?: string | null,
|
||||
chainId?: number | null,
|
||||
isNative?: boolean,
|
||||
backupImg?: string | null
|
||||
): [string | undefined, () => void] {
|
||||
const [current, setCurrent] = useState<string | undefined>(getInitialUrl(address, chainId, isNative))
|
||||
const [fallbackSrcs, setFallbackSrcs] = useState<string[] | undefined>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
setCurrent(getInitialUrl(address, chainId, isNative))
|
||||
setFallbackSrcs(undefined)
|
||||
}, [address, chainId, isNative])
|
||||
|
||||
const nextSrc = useCallback(() => {
|
||||
if (current) {
|
||||
BAD_SRCS[current] = true
|
||||
}
|
||||
// Parses and stores logo sources from tokenlists if assets repo url fails
|
||||
if (!fallbackSrcs) {
|
||||
const uris = TokenLogoLookupTable.getIcons(address) ?? []
|
||||
if (backupImg) uris.push(backupImg)
|
||||
const tokenListIcons = prioritizeLogoSources(parseLogoSources(uris))
|
||||
|
||||
setCurrent(tokenListIcons.find((src) => !BAD_SRCS[src]))
|
||||
setFallbackSrcs(tokenListIcons)
|
||||
} else {
|
||||
setCurrent(fallbackSrcs.find((src) => !BAD_SRCS[src]))
|
||||
}
|
||||
}, [current, fallbackSrcs, address, backupImg])
|
||||
|
||||
return [current, nextSrc]
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { useMemo } from 'react'
|
||||
|
||||
import useENSContentHash from './useENSContentHash'
|
||||
|
||||
export default function useHttpLocations(uri: string | undefined): string[] {
|
||||
export default function useHttpLocations(uri: string | undefined | null): string[] {
|
||||
const ens = useMemo(() => (uri ? parseENSAddress(uri) : undefined), [uri])
|
||||
const resolvedContentHash = useENSContentHash(ens?.ensName)
|
||||
return useMemo(() => {
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { RefObject, useEffect, useRef } from 'react'
|
||||
|
||||
export function useOnClickOutside<T extends HTMLElement>(
|
||||
node: RefObject<T | undefined>,
|
||||
handler: undefined | (() => void)
|
||||
) {
|
||||
const handlerRef = useRef<undefined | (() => void)>(handler)
|
||||
useEffect(() => {
|
||||
handlerRef.current = handler
|
||||
}, [handler])
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (node.current?.contains(e.target as Node) ?? false) {
|
||||
return
|
||||
}
|
||||
if (handlerRef.current) handlerRef.current()
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
}
|
||||
}, [node])
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import LogsUpdater from './state/logs/updater'
|
||||
import TransactionUpdater from './state/transactions/updater'
|
||||
import UserUpdater from './state/user/updater'
|
||||
import ThemeProvider, { ThemedGlobalStyle } from './theme'
|
||||
import RadialGradientByChainUpdater from './theme/RadialGradientByChainUpdater'
|
||||
import RadialGradientByChainUpdater from './theme/components/RadialGradientByChainUpdater'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ function parseStringOrBytes32(str: string | undefined, bytes32: string | undefin
|
||||
: defaultValue
|
||||
}
|
||||
|
||||
export const UNKNOWN_TOKEN_SYMBOL = 'UNKNOWN'
|
||||
export const UNKNOWN_TOKEN_NAME = 'Unknown Token'
|
||||
|
||||
/**
|
||||
* Returns a Token from the tokenAddress.
|
||||
* Returns null if token is loading or null was passed.
|
||||
@@ -51,11 +54,11 @@ export function useTokenFromActiveNetwork(tokenAddress: string | undefined): Tok
|
||||
const parsedDecimals = useMemo(() => decimals?.result?.[0] ?? DEFAULT_ERC20_DECIMALS, [decimals.result])
|
||||
|
||||
const parsedSymbol = useMemo(
|
||||
() => parseStringOrBytes32(symbol.result?.[0], symbolBytes32.result?.[0], 'UNKNOWN'),
|
||||
() => parseStringOrBytes32(symbol.result?.[0], symbolBytes32.result?.[0], UNKNOWN_TOKEN_SYMBOL),
|
||||
[symbol.result, symbolBytes32.result]
|
||||
)
|
||||
const parsedName = useMemo(
|
||||
() => parseStringOrBytes32(tokenName.result?.[0], tokenNameBytes32.result?.[0], 'Unknown Token'),
|
||||
() => parseStringOrBytes32(tokenName.result?.[0], tokenNameBytes32.result?.[0], UNKNOWN_TOKEN_NAME),
|
||||
[tokenName.result, tokenNameBytes32.result]
|
||||
)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { isCelo, NATIVE_CHAIN_ID, nativeOnChain } from '../../constants/tokens'
|
||||
|
||||
type Network = 'ethereum' | 'arbitrum' | 'optimism' | 'polygon'
|
||||
|
||||
function chainIdToNetworkName(networkId: SupportedChainId): Network {
|
||||
export function chainIdToNetworkName(networkId: SupportedChainId): Network {
|
||||
switch (networkId) {
|
||||
case SupportedChainId.MAINNET:
|
||||
return 'ethereum'
|
||||
@@ -60,7 +60,7 @@ export default function useCurrencyLogoURIs(
|
||||
isToken?: boolean
|
||||
address?: string
|
||||
chainId: number
|
||||
logoURI?: string
|
||||
logoURI?: string | null
|
||||
}
|
||||
| null
|
||||
| undefined
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: uniswap-interface\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: 2022-11-15 00:17\n"
|
||||
"PO-Revision-Date: 2022-11-17 01:33\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: af_ZA\n"
|
||||
"Language-Team: Afrikaans\n"
|
||||
@@ -196,6 +196,10 @@ msgstr "Voeg {0}/{1} V3 likiditeit by"
|
||||
msgid "Added"
|
||||
msgstr "Bygevoeg"
|
||||
|
||||
#: src/nft/components/collection/CollectionAsset.tsx
|
||||
msgid "Added to bag"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/TransactionConfirmationModal/index.tsx
|
||||
msgid "Added {0}"
|
||||
msgstr "{0}bygevoeg"
|
||||
@@ -350,6 +354,10 @@ msgstr "Beste prys roete kos ~{formattedGasPriceString} in gas."
|
||||
msgid "Blocked Address"
|
||||
msgstr "Geblokkeerde adres"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "Blocked from trading"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/PositionCard/index.tsx
|
||||
msgid "By adding liquidity you'll earn 0.3% of all trades on this pair proportional to your share of the pool. Fees are added to the pool, accrue in real time and can be claimed by withdrawing your liquidity."
|
||||
msgstr "Deur likiditeit by te voeg, verdien u 0,3% van alle transaksies op hierdie paar in verhouding tot u deel van die poel. Fooie word by die poel gevoeg, word intyds toegeval en kan geëis word deur u likiditeit te onttrek."
|
||||
@@ -570,7 +578,7 @@ msgstr "Kontrak adres"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
#: src/nft/components/profile/view/ProfileAccountDetails.tsx
|
||||
#: src/theme/components.tsx
|
||||
#: src/theme/components/index.tsx
|
||||
msgid "Copied!"
|
||||
msgstr "Gekopieer!"
|
||||
|
||||
@@ -713,6 +721,10 @@ msgstr "Deposito van likiditeit"
|
||||
msgid "Description"
|
||||
msgstr "Beskrywing"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "Deselected"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/RemoveLiquidity/index.tsx
|
||||
msgid "Detailed"
|
||||
msgstr "Gedetailleerd"
|
||||
@@ -746,6 +758,10 @@ msgstr "Dokumentasie"
|
||||
msgid "Don’t see one of your v2 positions? <0>Import it.</0>"
|
||||
msgstr "Sien u nie een van u v2-posisies nie? <0> Voer dit in.</0>"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "ERC-1155 support coming soon"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/vote/DelegateModal.tsx
|
||||
msgid "Earned UNI tokens represent voting shares in Uniswap governance."
|
||||
msgstr "Verdiende UNI-tekens verteenwoordig stemgeregtigde aandele in Uniswap-bestuur."
|
||||
@@ -1537,6 +1553,10 @@ msgstr "Verwyder vloeibaarheid"
|
||||
msgid "Removed"
|
||||
msgstr "Verwyder"
|
||||
|
||||
#: src/nft/components/collection/CollectionAsset.tsx
|
||||
msgid "Removed from bag"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/AccountDetailsV2/TransactionBody.tsx
|
||||
msgid "Removing"
|
||||
msgstr "Verwydering"
|
||||
@@ -1595,6 +1615,10 @@ msgstr "Kies 'n aksie"
|
||||
msgid "Select token"
|
||||
msgstr "Kies teken"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "Selected"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/AddLiquidity/index.tsx
|
||||
msgid "Selected Range"
|
||||
msgstr "Geselekteerde reeks"
|
||||
@@ -2680,10 +2704,6 @@ msgstr "{SOCKS_AMOUNT} UNI"
|
||||
msgid "{USER_AMOUNT} UNI"
|
||||
msgstr "{USER_AMOUNT} UNI"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "{disabledTooltipText}"
|
||||
msgstr "{disabledTooltipText}"
|
||||
|
||||
#: src/components/Polling/ChainConnectivityWarning.tsx
|
||||
msgid "{label} might be down right now, or you may have lost your network connection."
|
||||
msgstr "{label} is dalk op die oomblik af, of jy het dalk jou netwerkverbinding verloor."
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: uniswap-interface\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: 2022-11-15 00:17\n"
|
||||
"PO-Revision-Date: 2022-11-17 01:33\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: ar_SA\n"
|
||||
"Language-Team: Arabic\n"
|
||||
@@ -196,6 +196,10 @@ msgstr "إضافة سيولة {0}/{1} V3"
|
||||
msgid "Added"
|
||||
msgstr "مضاف"
|
||||
|
||||
#: src/nft/components/collection/CollectionAsset.tsx
|
||||
msgid "Added to bag"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/TransactionConfirmationModal/index.tsx
|
||||
msgid "Added {0}"
|
||||
msgstr "تم إضافة {0}"
|
||||
@@ -350,6 +354,10 @@ msgstr "تكاليف مسار أفضل الأسعار ~{formattedGasPriceString}
|
||||
msgid "Blocked Address"
|
||||
msgstr "العنوان المحظور"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "Blocked from trading"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/PositionCard/index.tsx
|
||||
msgid "By adding liquidity you'll earn 0.3% of all trades on this pair proportional to your share of the pool. Fees are added to the pool, accrue in real time and can be claimed by withdrawing your liquidity."
|
||||
msgstr "من خلال إضافة السيولة ستكسب 0.3٪ من جميع المعاملات على هذا الزوج بما يتناسب مع نصيبك من المجموعة. تضاف الرسوم إلى المجموعة، تتراكم في الوقت الحقيقي ويمكن المطالبة بها بسحب السيولة الخاصة بك."
|
||||
@@ -570,7 +578,7 @@ msgstr "عنوان العقد"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
#: src/nft/components/profile/view/ProfileAccountDetails.tsx
|
||||
#: src/theme/components.tsx
|
||||
#: src/theme/components/index.tsx
|
||||
msgid "Copied!"
|
||||
msgstr "نسخ!"
|
||||
|
||||
@@ -713,6 +721,10 @@ msgstr "ايداع السيولة"
|
||||
msgid "Description"
|
||||
msgstr "الوصف"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "Deselected"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/RemoveLiquidity/index.tsx
|
||||
msgid "Detailed"
|
||||
msgstr "مفصل"
|
||||
@@ -746,6 +758,10 @@ msgstr "توثيق"
|
||||
msgid "Don’t see one of your v2 positions? <0>Import it.</0>"
|
||||
msgstr "لا ترى أحد مراكز v2 الخاصة بك؟ <0>قم باستيراده.</0>"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "ERC-1155 support coming soon"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/vote/DelegateModal.tsx
|
||||
msgid "Earned UNI tokens represent voting shares in Uniswap governance."
|
||||
msgstr "تمثل رموز UNI التي تم الحصول عليها حصص التصويت في إدارة Uniswap."
|
||||
@@ -1537,6 +1553,10 @@ msgstr "إزالة السيولة"
|
||||
msgid "Removed"
|
||||
msgstr "إزالة"
|
||||
|
||||
#: src/nft/components/collection/CollectionAsset.tsx
|
||||
msgid "Removed from bag"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/AccountDetailsV2/TransactionBody.tsx
|
||||
msgid "Removing"
|
||||
msgstr "إزالة"
|
||||
@@ -1595,6 +1615,10 @@ msgstr "حدد إجراء"
|
||||
msgid "Select token"
|
||||
msgstr "حدد رمز"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "Selected"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/AddLiquidity/index.tsx
|
||||
msgid "Selected Range"
|
||||
msgstr "نطاق محدد"
|
||||
@@ -2680,10 +2704,6 @@ msgstr "{SOCKS_AMOUNT} UNI"
|
||||
msgid "{USER_AMOUNT} UNI"
|
||||
msgstr "{USER_AMOUNT} UNI"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "{disabledTooltipText}"
|
||||
msgstr "{disabledTooltipText}"
|
||||
|
||||
#: src/components/Polling/ChainConnectivityWarning.tsx
|
||||
msgid "{label} might be down right now, or you may have lost your network connection."
|
||||
msgstr "قد يكون الرقم {label} معطلاً الآن ، أو ربما فقدت اتصالك بالشبكة."
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: uniswap-interface\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: 2022-11-15 00:17\n"
|
||||
"PO-Revision-Date: 2022-11-17 01:33\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: ca_ES\n"
|
||||
"Language-Team: Catalan\n"
|
||||
@@ -196,6 +196,10 @@ msgstr "Afegir {0}/{1} liquiditat V3"
|
||||
msgid "Added"
|
||||
msgstr "Afegit"
|
||||
|
||||
#: src/nft/components/collection/CollectionAsset.tsx
|
||||
msgid "Added to bag"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/TransactionConfirmationModal/index.tsx
|
||||
msgid "Added {0}"
|
||||
msgstr "Afegit {0}"
|
||||
@@ -350,6 +354,10 @@ msgstr "La ruta al millor preu costa ~{formattedGasPriceString} en gasolina."
|
||||
msgid "Blocked Address"
|
||||
msgstr "Adreça bloquejada"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "Blocked from trading"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/PositionCard/index.tsx
|
||||
msgid "By adding liquidity you'll earn 0.3% of all trades on this pair proportional to your share of the pool. Fees are added to the pool, accrue in real time and can be claimed by withdrawing your liquidity."
|
||||
msgstr "Si afegiu liquiditat, guanyareu el 0,3% de totes les operacions d’aquest parell proporcional a la vostra quota de grup. Les comissions s’afegeixen al grup, s’acumulen en temps real i es poden reclamar retirant la vostra liquiditat."
|
||||
@@ -570,7 +578,7 @@ msgstr "Adreça del contracte"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
#: src/nft/components/profile/view/ProfileAccountDetails.tsx
|
||||
#: src/theme/components.tsx
|
||||
#: src/theme/components/index.tsx
|
||||
msgid "Copied!"
|
||||
msgstr "Copiat!"
|
||||
|
||||
@@ -713,6 +721,10 @@ msgstr "Dipòsit de liquiditat"
|
||||
msgid "Description"
|
||||
msgstr "Descripció"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "Deselected"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/RemoveLiquidity/index.tsx
|
||||
msgid "Detailed"
|
||||
msgstr "Detallada"
|
||||
@@ -746,6 +758,10 @@ msgstr "Documentació"
|
||||
msgid "Don’t see one of your v2 positions? <0>Import it.</0>"
|
||||
msgstr "No veieu cap de les vostres posicions v2? <0> Importeu-la.</0>"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "ERC-1155 support coming soon"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/vote/DelegateModal.tsx
|
||||
msgid "Earned UNI tokens represent voting shares in Uniswap governance."
|
||||
msgstr "Les fitxes UNI obtingudes representen accions de vot en la governança Uniswap."
|
||||
@@ -1537,6 +1553,10 @@ msgstr "Elimina la liquiditat"
|
||||
msgid "Removed"
|
||||
msgstr "Eliminat"
|
||||
|
||||
#: src/nft/components/collection/CollectionAsset.tsx
|
||||
msgid "Removed from bag"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/AccountDetailsV2/TransactionBody.tsx
|
||||
msgid "Removing"
|
||||
msgstr "Eliminant"
|
||||
@@ -1595,6 +1615,10 @@ msgstr "Seleccioneu una acció"
|
||||
msgid "Select token"
|
||||
msgstr "Seleccioneu el testimoni"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "Selected"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/AddLiquidity/index.tsx
|
||||
msgid "Selected Range"
|
||||
msgstr "Interval seleccionat"
|
||||
@@ -2680,10 +2704,6 @@ msgstr "{SOCKS_AMOUNT} UNI"
|
||||
msgid "{USER_AMOUNT} UNI"
|
||||
msgstr "{USER_AMOUNT} UNI"
|
||||
|
||||
#: src/nft/components/profile/view/ViewMyNftsAsset.tsx
|
||||
msgid "{disabledTooltipText}"
|
||||
msgstr "{disabledTooltipText}"
|
||||
|
||||
#: src/components/Polling/ChainConnectivityWarning.tsx
|
||||
msgid "{label} might be down right now, or you may have lost your network connection."
|
||||
msgstr "És possible que {label} estigui inactiva en aquest moment o que hagis perdut la connexió a la xarxa."
|
||||
|
||||