Compare commits
233 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
e1d489a6bc | ||
|
|
9b31e7b66d | ||
|
|
5696c61354 | ||
|
|
b30679c9f9 | ||
|
|
1572410c47 | ||
|
|
f3d5fe08b6 | ||
|
|
247fbfdf01 | ||
|
|
f391f1c719 | ||
|
|
d38854749b | ||
|
|
10a1963801 | ||
|
|
be7e808fff | ||
|
|
ef9a59a96b | ||
|
|
78b6ef60ac | ||
|
|
779a699ff0 | ||
|
|
02e5478c6e | ||
|
|
6520dd33fa | ||
|
|
c479239ab0 | ||
|
|
55c3c527f5 | ||
|
|
8b98597566 | ||
|
|
bfcda30c04 | ||
|
|
c7f0af6902 | ||
|
|
49b09148c6 | ||
|
|
8e2307cbdb | ||
|
|
5978d1ec09 | ||
|
|
c0638e9033 | ||
|
|
ceafe40c65 | ||
|
|
86ee1dd666 | ||
|
|
95be7b1d5b | ||
|
|
7ce022b28e | ||
|
|
dd8233f869 | ||
|
|
947a078161 | ||
|
|
7add78ff80 | ||
|
|
fdf511e283 | ||
|
|
99e491c4fc | ||
|
|
ad84da10c9 | ||
|
|
72a8270084 | ||
|
|
62575092db | ||
|
|
299f4c8afb | ||
|
|
f9eb46a09b | ||
|
|
2970f9f1cb | ||
|
|
ce79774de9 | ||
|
|
cbefbba02c | ||
|
|
3acbcbc690 | ||
|
|
7b086848bf | ||
|
|
77a6d158ea | ||
|
|
28be15ef9f | ||
|
|
96dc7e0998 | ||
|
|
c8f1c98639 | ||
|
|
8c3ba8bac3 | ||
|
|
c563dd5a39 | ||
|
|
bd8cd71452 | ||
|
|
61d0fd9062 | ||
|
|
68709ae65b | ||
|
|
ac1e83ea9f | ||
|
|
6215911719 | ||
|
|
fc81c6e37d | ||
|
|
9d5e0701e7 | ||
|
|
66bdfd8a94 | ||
|
|
935694630b | ||
|
|
f8399fd03c | ||
|
|
429ade5b20 | ||
|
|
5c21dd9852 | ||
|
|
2ce5990f60 | ||
|
|
d56851561b | ||
|
|
5325b5f8b4 | ||
|
|
27936cf3f5 | ||
|
|
ff6f43d7aa | ||
|
|
f1443671ef | ||
|
|
a95697daf8 | ||
|
|
0835744006 | ||
|
|
f5df2fed09 | ||
|
|
5e7f6333b1 | ||
|
|
2aa4ec6a38 | ||
|
|
a70ef4326d | ||
|
|
6edc73784c | ||
|
|
da6e13130b | ||
|
|
4ef4ea8f58 | ||
|
|
4438818f38 | ||
|
|
12eb337444 | ||
|
|
44163f54b1 | ||
|
|
4b282d7813 | ||
|
|
f862a3f975 | ||
|
|
48d5955185 | ||
|
|
dbf5c63ece | ||
|
|
37d2603406 | ||
|
|
9bb1ca2970 | ||
|
|
2abae0ee4c | ||
|
|
9f8355ed7b | ||
|
|
c5bed1c6fb | ||
|
|
1411a92146 | ||
|
|
d016bdd87c | ||
|
|
491ae578ab | ||
|
|
1df685f31e | ||
|
|
02aeb43e62 | ||
|
|
1d849927ef | ||
|
|
1893d258b5 | ||
|
|
ed7f126bd0 | ||
|
|
9a38c4e58d | ||
|
|
99f3998941 | ||
|
|
30fa88e3af | ||
|
|
d951172a81 | ||
|
|
eb35d3a2a0 | ||
|
|
87455fc096 | ||
|
|
054d92cb9c | ||
|
|
36109a1fe7 | ||
|
|
8f8fe9ddad | ||
|
|
2b279e00f9 | ||
|
|
9f5c588bdd | ||
|
|
415b3a1548 | ||
|
|
4e7b8264c3 | ||
|
|
0ef6d1625a | ||
|
|
0258460821 | ||
|
|
2246afcefb | ||
|
|
e0767b1cb7 | ||
|
|
15dd02fe6a | ||
|
|
562a386de7 | ||
|
|
99bea34f14 | ||
|
|
58f1c6ff84 | ||
|
|
b2481d6ba8 | ||
|
|
eaa9b51913 | ||
|
|
6f68980d86 | ||
|
|
9e05c178b4 | ||
|
|
d98808dae9 | ||
|
|
01a749a755 | ||
|
|
c233ba1175 | ||
|
|
b1dc415fb5 | ||
|
|
1242aef466 | ||
|
|
7f2bb6c6ae | ||
|
|
4b1b6098f3 | ||
|
|
53a6acc199 | ||
|
|
bd0a32b07c | ||
|
|
7d480494ba | ||
|
|
5caed66b39 | ||
|
|
136c16bbae | ||
|
|
691dcd269c | ||
|
|
a5cb1f05dc | ||
|
|
4a14db2d4c | ||
|
|
a57c19bb45 | ||
|
|
9fd6ab01de | ||
|
|
48aa11403f | ||
|
|
31b0c3dc04 | ||
|
|
d575c72127 | ||
|
|
d400b9094d | ||
|
|
fda9d29d5e | ||
|
|
d74c05008b | ||
|
|
c8f365ca31 | ||
|
|
66ab3e21c8 | ||
|
|
d4b15a6de4 | ||
|
|
fe61365a11 | ||
|
|
c5047a9301 | ||
|
|
d4f8f2a600 | ||
|
|
d41a5a4874 |
@@ -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"
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/jsx-curly-brace-presence": ["error", {"props": "never", "children": "never" }],
|
||||
"object-shorthand": ["error", "always"],
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
|
||||
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
@uniswap/web
|
||||
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'
|
||||
|
||||
5
.github/workflows/release.yaml
vendored
@@ -88,10 +88,9 @@ jobs:
|
||||
- name: Update DNS with new IPFS hash
|
||||
env:
|
||||
CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}
|
||||
RECORD_DOMAIN: 'uniswap.org'
|
||||
RECORD_NAME: '_dnslink.app'
|
||||
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
|
||||
uses: textileio/cloudflare-update-dnslink@0fe7b7a1ffc865db3a4da9773f0f987447ad5848
|
||||
CLOUDFLARE_GATEWAY_ID: ${{ secrets.CLOUDFLARE_GATEWAY_ID }}
|
||||
uses: Uniswap/cloudflare-update-web3-gateway@b3205288b1c6d0acb63fa3bd8fb686c72a9e3f3e
|
||||
with:
|
||||
cid: ${{ steps.pinata.outputs.hash }}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
34
package.json
@@ -116,14 +116,13 @@
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amplitude/analytics-browser": "^1.1.4",
|
||||
"@coinbase/wallet-sdk": "^3.3.0",
|
||||
"@fontsource/ibm-plex-mono": "^4.5.1",
|
||||
"@fontsource/inter": "^4.5.1",
|
||||
"@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",
|
||||
@@ -132,10 +131,14 @@
|
||||
"@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",
|
||||
"@uniswap/redux-multicall": "^1.1.6",
|
||||
"@uniswap/redux-multicall": "^1.1.8",
|
||||
"@uniswap/router-sdk": "^1.3.0",
|
||||
"@uniswap/sdk-core": "^3.0.1",
|
||||
"@uniswap/smart-order-router": "^2.10.0",
|
||||
@@ -146,7 +149,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",
|
||||
@@ -158,17 +161,17 @@
|
||||
"@visx/react-spring": "^2.12.2",
|
||||
"@visx/responsive": "^2.10.0",
|
||||
"@visx/shape": "^2.11.1",
|
||||
"@walletconnect/ethereum-provider": "1.7.1",
|
||||
"@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",
|
||||
"@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.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",
|
||||
@@ -213,6 +216,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",
|
||||
|
||||
@@ -1,119 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
|
||||
<!--
|
||||
<!--
|
||||
%PUBLIC_URL% will be replaced with the URL of the `public` folder during build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="%PUBLIC_URL%/favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="%PUBLIC_URL%/images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="%PUBLIC_URL%/images/512x512_App_Icon.png" />
|
||||
<link rel="shortcut icon" type="image/png" href="%PUBLIC_URL%/favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="%PUBLIC_URL%/images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="%PUBLIC_URL%/images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
|
||||
/>
|
||||
|
||||
<!--
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="%PUBLIC_URL%/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="%PUBLIC_URL%/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
Explicitly load Inter var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#background-radial-gradient {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: #212429;
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
#background-radial-gradient {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background-color: #f7f8fa;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: #212429;
|
||||
}
|
||||
}
|
||||
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
</html>
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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 |
6
public/nft/svgs/marketplaces/sudoswap.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 14.4C0 6.4471 6.4471 0 14.4 0H33.6C41.5529 0 48 6.4471 48 14.4V33.6C48 41.5529 41.5529 48 33.6 48H14.4C6.4471 48 0 41.5529 0 33.6V14.4Z" fill="#B9B9FF"/>
|
||||
<path d="M9.29999 15H12L15.3 24L12 33H9.29999L12.6 24L9.29999 15Z" fill="#121212"/>
|
||||
<path d="M23.5727 33.192C22.1807 33.192 21.0367 32.872 20.1407 32.232C19.2607 31.592 18.8207 30.72 18.8207 29.616H21.2447C21.2447 30.112 21.4607 30.496 21.8927 30.768C22.3407 31.024 22.9167 31.152 23.6207 31.152H24.6287C25.4607 31.152 26.0767 30.992 26.4767 30.672C26.8767 30.336 27.0767 29.896 27.0767 29.352C27.0767 28.808 26.8847 28.384 26.5007 28.08C26.1167 27.776 25.5727 27.56 24.8687 27.432L23.0687 27.168C20.4447 26.752 19.1327 25.48 19.1327 23.352C19.1327 22.136 19.5327 21.208 20.3327 20.568C21.1327 19.928 22.2767 19.608 23.7647 19.608H24.6767C26.0527 19.608 27.1567 19.92 27.9887 20.544C28.8207 21.152 29.2367 21.96 29.2367 22.968H26.8127C26.8127 22.568 26.6127 22.248 26.2127 22.008C25.8287 21.768 25.3007 21.648 24.6287 21.648H23.7167C22.9807 21.648 22.4207 21.8 22.0367 22.104C21.6527 22.392 21.4607 22.808 21.4607 23.352C21.4607 24.28 22.1167 24.848 23.4287 25.056L25.2287 25.344C26.6687 25.568 27.7247 25.992 28.3967 26.616C29.0687 27.224 29.4047 28.104 29.4047 29.256C29.4047 30.488 28.9967 31.456 28.1807 32.16C27.3807 32.848 26.1807 33.192 24.5807 33.192H23.5727Z" fill="#121212"/>
|
||||
<path d="M38.7 15H36L32.7 24L36 33H38.7L35.4 24L38.7 15Z" fill="#121212"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.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 |
@@ -1,76 +0,0 @@
|
||||
import { createContext, memo, PropsWithChildren, useContext, useEffect, useMemo } from 'react'
|
||||
|
||||
import { sendAnalyticsEvent } from '.'
|
||||
import { ElementName, EventName, ModalName, PageName, SectionName } from './constants'
|
||||
|
||||
export interface ITraceContext {
|
||||
// Highest order context: eg Swap or Explore.
|
||||
page?: PageName
|
||||
|
||||
// Enclosed section name. For contexts with modals, refers to the
|
||||
// section of the page from which the user triggered the modal.
|
||||
section?: SectionName
|
||||
|
||||
modal?: ModalName
|
||||
|
||||
// Element name mostly used to identify events sources
|
||||
// Does not need to be unique given the higher order page and section.
|
||||
element?: ElementName
|
||||
}
|
||||
|
||||
export const TraceContext = createContext<ITraceContext>({})
|
||||
|
||||
export function useTrace(trace?: ITraceContext): ITraceContext {
|
||||
const parentTrace = useContext(TraceContext)
|
||||
return useMemo(() => ({ ...parentTrace, ...trace }), [parentTrace, trace])
|
||||
}
|
||||
|
||||
type TraceProps = {
|
||||
shouldLogImpression?: boolean // whether to log impression on mount
|
||||
name?: EventName
|
||||
properties?: Record<string, unknown>
|
||||
} & ITraceContext
|
||||
|
||||
/**
|
||||
* Sends an analytics event on mount (if shouldLogImpression is set),
|
||||
* and propagates the context to child traces.
|
||||
*/
|
||||
export const Trace = memo(
|
||||
({
|
||||
shouldLogImpression,
|
||||
name,
|
||||
children,
|
||||
page,
|
||||
section,
|
||||
modal,
|
||||
element,
|
||||
properties,
|
||||
}: PropsWithChildren<TraceProps>) => {
|
||||
const parentTrace = useTrace()
|
||||
|
||||
const combinedProps = useMemo(
|
||||
() => ({
|
||||
...parentTrace,
|
||||
...Object.fromEntries(Object.entries({ page, section, modal, element }).filter(([_, v]) => v !== undefined)),
|
||||
}),
|
||||
[element, parentTrace, page, modal, section]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldLogImpression) {
|
||||
const commitHash = process.env.REACT_APP_GIT_COMMIT_HASH
|
||||
sendAnalyticsEvent(name ?? EventName.PAGE_VIEWED, {
|
||||
...combinedProps,
|
||||
...properties,
|
||||
git_commit_hash: commitHash,
|
||||
})
|
||||
}
|
||||
// Impressions should only be logged on mount.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return <TraceContext.Provider value={combinedProps}>{children}</TraceContext.Provider>
|
||||
}
|
||||
)
|
||||
|
||||
Trace.displayName = 'Trace'
|
||||
@@ -1,75 +0,0 @@
|
||||
import { Children, cloneElement, isValidElement, memo, PropsWithChildren, SyntheticEvent } from 'react'
|
||||
|
||||
import { sendAnalyticsEvent } from '.'
|
||||
import { Event, EventName } from './constants'
|
||||
import { ITraceContext, Trace, TraceContext } from './Trace'
|
||||
|
||||
type TraceEventProps = {
|
||||
events: Event[]
|
||||
name: EventName
|
||||
properties?: Record<string, unknown>
|
||||
shouldLogImpression?: boolean
|
||||
} & ITraceContext
|
||||
|
||||
/**
|
||||
* Analytics instrumentation component that wraps event callbacks with logging logic.
|
||||
*
|
||||
* @example
|
||||
* <TraceEvent events={[Event.onClick]} element={ElementName.SWAP_BUTTON}>
|
||||
* <Button onClick={() => console.log('clicked')}>Click me</Button>
|
||||
* </TraceEvent>
|
||||
*/
|
||||
export const TraceEvent = memo((props: PropsWithChildren<TraceEventProps>) => {
|
||||
const { shouldLogImpression, name, properties, events, children, ...traceProps } = props
|
||||
|
||||
return (
|
||||
<Trace {...traceProps}>
|
||||
<TraceContext.Consumer>
|
||||
{(traceContext) =>
|
||||
Children.map(children, (child) => {
|
||||
if (!isValidElement(child)) {
|
||||
return child
|
||||
}
|
||||
|
||||
// For each child, augment event handlers defined in `events` with event tracing.
|
||||
return cloneElement(
|
||||
child,
|
||||
getEventHandlers(child, traceContext, events, name, properties, shouldLogImpression)
|
||||
)
|
||||
})
|
||||
}
|
||||
</TraceContext.Consumer>
|
||||
</Trace>
|
||||
)
|
||||
})
|
||||
|
||||
TraceEvent.displayName = 'TraceEvent'
|
||||
|
||||
/**
|
||||
* Given a set of child element and event props, returns a spreadable
|
||||
* object of the event handlers augmented with analytics logging.
|
||||
*/
|
||||
function getEventHandlers(
|
||||
child: React.ReactElement,
|
||||
traceContext: ITraceContext,
|
||||
events: Event[],
|
||||
name: EventName,
|
||||
properties?: Record<string, unknown>,
|
||||
shouldLogImpression = true
|
||||
) {
|
||||
const eventHandlers: Partial<Record<Event, (e: SyntheticEvent<Element, Event>) => void>> = {}
|
||||
|
||||
for (const event of events) {
|
||||
eventHandlers[event] = (eventHandlerArgs: unknown) => {
|
||||
// call child event handler with original arguments, must be in array
|
||||
const args = Array.isArray(eventHandlerArgs) ? eventHandlerArgs : [eventHandlerArgs]
|
||||
child.props[event]?.apply(child, args)
|
||||
|
||||
// augment handler with analytics logging
|
||||
if (shouldLogImpression) sendAnalyticsEvent(name, { ...traceContext, ...properties, action: event })
|
||||
}
|
||||
}
|
||||
|
||||
// return a spreadable event handler object
|
||||
return eventHandlers
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
/**
|
||||
* Event names that can occur in this application.
|
||||
*
|
||||
* Subject to change as new features are added and new events are defined
|
||||
* and logged.
|
||||
*/
|
||||
export enum EventName {
|
||||
APP_LOADED = 'Application Loaded',
|
||||
APPROVE_TOKEN_TXN_SUBMITTED = 'Approve Token Transaction Submitted',
|
||||
CONNECT_WALLET_BUTTON_CLICKED = 'Connect Wallet Button Clicked',
|
||||
EXPLORE_BANNER_CLICKED = 'Explore Banner Clicked',
|
||||
EXPLORE_SEARCH_SELECTED = 'Explore Search Selected',
|
||||
EXPLORE_TOKEN_ROW_CLICKED = 'Explore Token Row Clicked',
|
||||
PAGE_VIEWED = 'Page Viewed',
|
||||
NAVBAR_RESULT_SELECTED = 'Navbar Result Selected',
|
||||
NAVBAR_SEARCH_SELECTED = 'Navbar Search Selected',
|
||||
NAVBAR_SEARCH_EXITED = 'Navbar Search Exited',
|
||||
NFT_ACTIVITY_SELECTED = 'NFT Activity Selected',
|
||||
NFT_FILTER_OPENED = 'NFT Collection Filter Opened',
|
||||
NFT_FILTER_SELECTED = 'NFT Filter Selected',
|
||||
NFT_TRENDING_ROW_SELECTED = 'Trending Row Selected',
|
||||
SWAP_AUTOROUTER_VISUALIZATION_EXPANDED = 'Swap Autorouter Visualization Expanded',
|
||||
SWAP_DETAILS_EXPANDED = 'Swap Details Expanded',
|
||||
SWAP_MAX_TOKEN_AMOUNT_SELECTED = 'Swap Max Token Amount Selected',
|
||||
SWAP_PRICE_UPDATE_ACKNOWLEDGED = 'Swap Price Update Acknowledged',
|
||||
SWAP_QUOTE_RECEIVED = 'Swap Quote Received',
|
||||
SWAP_SIGNED = 'Swap Signed',
|
||||
SWAP_SUBMITTED_BUTTON_CLICKED = 'Swap Submit Button Clicked',
|
||||
SWAP_TOKENS_REVERSED = 'Swap Tokens Reversed',
|
||||
SWAP_TRANSACTION_COMPLETED = 'Swap Transaction Completed',
|
||||
TOKEN_IMPORTED = 'Token Imported',
|
||||
TOKEN_SELECTED = 'Token Selected',
|
||||
TOKEN_SELECTOR_OPENED = 'Token Selector Opened',
|
||||
WALLET_CONNECT_TXN_COMPLETED = 'Wallet Connect Transaction Completed',
|
||||
WALLET_SELECTED = 'Wallet Selected',
|
||||
WEB_VITALS = 'Web Vitals',
|
||||
WRAP_TOKEN_TXN_INVALIDATED = 'Wrap Token Transaction Invalidated',
|
||||
WRAP_TOKEN_TXN_SUBMITTED = 'Wrap Token Transaction Submitted',
|
||||
// alphabetize additional event names.
|
||||
}
|
||||
|
||||
export enum CUSTOM_USER_PROPERTIES {
|
||||
ALL_WALLET_ADDRESSES_CONNECTED = 'all_wallet_addresses_connected',
|
||||
ALL_WALLET_CHAIN_IDS = 'all_wallet_chain_ids',
|
||||
USER_AGENT = 'user_agent',
|
||||
BROWSER = 'browser',
|
||||
DARK_MODE = 'is_dark_mode',
|
||||
EXPERT_MODE = 'is_expert_mode',
|
||||
SCREEN_RESOLUTION_HEIGHT = 'screen_resolution_height',
|
||||
SCREEN_RESOLUTION_WIDTH = 'screen_resolution_width',
|
||||
WALLET_ADDRESS = 'wallet_address',
|
||||
WALLET_TYPE = 'wallet_type',
|
||||
}
|
||||
|
||||
export enum BROWSER {
|
||||
FIREFOX = 'Mozilla Firefox',
|
||||
SAMSUNG = 'Samsung Internet',
|
||||
OPERA = 'Opera',
|
||||
INTERNET_EXPLORER = 'Microsoft Internet Explorer',
|
||||
EDGE = 'Microsoft Edge (Legacy)',
|
||||
EDGE_CHROMIUM = 'Microsoft Edge (Chromium)',
|
||||
CHROME = 'Google Chrome or Chromium',
|
||||
SAFARI = 'Apple Safari',
|
||||
BRAVE = 'Brave',
|
||||
UNKNOWN = 'unknown',
|
||||
}
|
||||
|
||||
export enum WALLET_CONNECTION_RESULT {
|
||||
SUCCEEDED = 'Succeeded',
|
||||
FAILED = 'Failed',
|
||||
}
|
||||
|
||||
export enum SWAP_PRICE_UPDATE_USER_RESPONSE {
|
||||
ACCEPTED = 'Accepted',
|
||||
REJECTED = 'Rejected',
|
||||
}
|
||||
|
||||
/**
|
||||
* Known pages in the app. Highest order context.
|
||||
*/
|
||||
export enum PageName {
|
||||
NFT_COLLECTION_PAGE = 'nft-collection-page',
|
||||
NFT_DETAILS_PAGE = 'nft-details-page',
|
||||
NFT_EXPLORE_PAGE = 'nft-explore-page',
|
||||
TOKEN_DETAILS_PAGE = 'token-details',
|
||||
TOKENS_PAGE = 'tokens-page',
|
||||
POOL_PAGE = 'pool-page',
|
||||
SWAP_PAGE = 'swap-page',
|
||||
VOTE_PAGE = 'vote-page',
|
||||
// alphabetize additional page names.
|
||||
}
|
||||
|
||||
/**
|
||||
* Sections. Disambiguates low-level elements that may share a name.
|
||||
* eg a `back` button in a modal will have the same `element`,
|
||||
* but a different `section`.
|
||||
*/
|
||||
export enum SectionName {
|
||||
CURRENCY_INPUT_PANEL = 'swap-currency-input',
|
||||
CURRENCY_OUTPUT_PANEL = 'swap-currency-output',
|
||||
NAVBAR_SEARCH = 'Navbar Search',
|
||||
WIDGET = 'widget',
|
||||
// alphabetize additional section names.
|
||||
}
|
||||
|
||||
/** Known modals for analytics purposes. */
|
||||
export enum ModalName {
|
||||
CONFIRM_SWAP = 'confirm-swap-modal',
|
||||
TOKEN_SELECTOR = 'token-selector-modal',
|
||||
// alphabetize additional modal names.
|
||||
}
|
||||
|
||||
/**
|
||||
* Known element names for analytics purposes.
|
||||
* Use to identify low-level components given a TraceContext
|
||||
*/
|
||||
export enum ElementName {
|
||||
AUTOROUTER_VISUALIZATION_ROW = 'expandable-autorouter-visualization-row',
|
||||
COMMON_BASES_CURRENCY_BUTTON = 'common-bases-currency-button',
|
||||
CONFIRM_SWAP_BUTTON = 'confirm-swap-or-send',
|
||||
CONNECT_WALLET_BUTTON = 'connect-wallet-button',
|
||||
EXPLORE_BANNER = 'explore-banner',
|
||||
EXPLORE_SEARCH_INPUT = 'explore_search_input',
|
||||
IMPORT_TOKEN_BUTTON = 'import-token-button',
|
||||
MAX_TOKEN_AMOUNT_BUTTON = 'max-token-amount-button',
|
||||
NAVBAR_SEARCH_INPUT = 'navbar-search-input',
|
||||
NFT_ACTIVITY_TAB = 'nft-activity-tab',
|
||||
NFT_FILTER_BUTTON = 'nft-filter-button',
|
||||
NFT_FILTER_OPTION = 'nft-filter-option',
|
||||
NFT_TRENDING_ROW = 'nft-trending-row',
|
||||
PRICE_UPDATE_ACCEPT_BUTTON = 'price-update-accept-button',
|
||||
SWAP_BUTTON = 'swap-button',
|
||||
SWAP_DETAILS_DROPDOWN = 'swap-details-dropdown',
|
||||
SWAP_TOKENS_REVERSE_ARROW_BUTTON = 'swap-tokens-reverse-arrow-button',
|
||||
TOKEN_SELECTOR_ROW = 'token-selector-row',
|
||||
WALLET_TYPE_OPTION = 'wallet-type-option',
|
||||
// alphabetize additional element names.
|
||||
}
|
||||
|
||||
/**
|
||||
* Known events that trigger callbacks.
|
||||
* @example
|
||||
* <TraceEvent events={[Event.onClick]} element={name}>
|
||||
*/
|
||||
export enum Event {
|
||||
onClick = 'onClick',
|
||||
onFocus = 'onFocus',
|
||||
onKeyPress = 'onKeyPress',
|
||||
onSelect = 'onSelect',
|
||||
// alphabetize additional events.
|
||||
}
|
||||
|
||||
/** Known navbar search result types */
|
||||
export enum NavBarSearchTypes {
|
||||
COLLECTION_SUGGESTION = 'collection-suggestion',
|
||||
COLLECTION_TRENDING = 'collection-trending',
|
||||
RECENT_SEARCH = 'recent',
|
||||
TOKEN_SUGGESTION = 'token-suggestion',
|
||||
TOKEN_TRENDING = 'token-trending',
|
||||
}
|
||||
|
||||
/**
|
||||
* Known Filter Types for NFTs
|
||||
*/
|
||||
export enum FilterTypes {
|
||||
MARKETPLACE = 'Marketplace',
|
||||
PRICE_RANGE = 'Price Range',
|
||||
TRAIT = 'Trait',
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import { Identify, identify, init, track } from '@amplitude/analytics-browser'
|
||||
import { isProductionEnv } from 'utils/env'
|
||||
|
||||
const DUMMY_KEY = '00000000000000000000000000000000'
|
||||
const PROXY_URL = process.env.REACT_APP_AMPLITUDE_PROXY_URL
|
||||
|
||||
/**
|
||||
* Initializes Amplitude SDK and configures it to send events to a Uniswap reverse proxy,
|
||||
* which relays to events to relevant Amplitude endpoints. You must be a
|
||||
* member of the organization on Amplitude to view logged events.
|
||||
*/
|
||||
export function initializeAnalytics() {
|
||||
if (typeof PROXY_URL === 'undefined') {
|
||||
console.error('REACT_APP_AMPLITUDE_PROXY_URL is undefined, Amplitude analytics will not run.')
|
||||
return
|
||||
}
|
||||
init(
|
||||
DUMMY_KEY,
|
||||
/* userId= */ undefined, // User ID should be undefined to let Amplitude default to Device ID
|
||||
/* options= */
|
||||
{
|
||||
// Configure the SDK to work with alternate endpoint
|
||||
serverUrl: PROXY_URL,
|
||||
// Disable tracking of private user information by Amplitude
|
||||
trackingOptions: {
|
||||
ipAddress: false,
|
||||
carrier: false,
|
||||
city: false,
|
||||
region: false,
|
||||
dma: false, // designated market area
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/** Sends an event to Amplitude. */
|
||||
export function sendAnalyticsEvent(eventName: string, eventProperties?: Record<string, unknown>) {
|
||||
if (!PROXY_URL) {
|
||||
console.log(`[analytics(${eventName})]: ${JSON.stringify(eventProperties)}`)
|
||||
return
|
||||
}
|
||||
|
||||
track(eventName, { ...eventProperties, origin })
|
||||
}
|
||||
|
||||
type Value = string | number | boolean | string[] | number[]
|
||||
|
||||
/**
|
||||
* Class that exposes methods to mutate the User Model's properties in
|
||||
* Amplitude that represents the current session's user.
|
||||
*
|
||||
* See https://help.amplitude.com/hc/en-us/articles/115002380567-User-properties-and-event-properties
|
||||
* for details.
|
||||
*/
|
||||
class UserModel {
|
||||
private log(method: string, ...parameters: unknown[]) {
|
||||
console.debug(`[amplitude(Identify)]: ${method}(${parameters})`)
|
||||
}
|
||||
|
||||
private call(mutate: (event: Identify) => Identify) {
|
||||
if (!isProductionEnv()) {
|
||||
const log = (_: Identify, method: string) => this.log.bind(this, method)
|
||||
mutate(new Proxy(new Identify(), { get: log }))
|
||||
return
|
||||
}
|
||||
identify(mutate(new Identify()))
|
||||
}
|
||||
|
||||
set(key: string, value: Value) {
|
||||
this.call((event) => event.set(key, value))
|
||||
}
|
||||
|
||||
setOnce(key: string, value: Value) {
|
||||
this.call((event) => event.setOnce(key, value))
|
||||
}
|
||||
|
||||
add(key: string, value: number) {
|
||||
this.call((event) => event.add(key, value))
|
||||
}
|
||||
|
||||
postInsert(key: string, value: string | number) {
|
||||
this.call((event) => event.postInsert(key, value))
|
||||
}
|
||||
|
||||
remove(key: string, value: string | number) {
|
||||
this.call((event) => event.remove(key, value))
|
||||
}
|
||||
}
|
||||
|
||||
export const user = new UserModel()
|
||||
BIN
src/assets/images/airdopBackground.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 42 KiB |
@@ -75,7 +75,7 @@ function ClaimSummary({ info: { recipient, uniAmountRaw } }: { info: ClaimTransa
|
||||
const { ENSName } = useENSName()
|
||||
return typeof uniAmountRaw === 'string' ? (
|
||||
<Trans>
|
||||
Claim <FormattedCurrencyAmount rawAmount={uniAmountRaw} symbol={'UNI'} decimals={18} sigFigs={4} /> for{' '}
|
||||
Claim <FormattedCurrencyAmount rawAmount={uniAmountRaw} symbol="UNI" decimals={18} sigFigs={4} /> for{' '}
|
||||
{ENSName ?? recipient}
|
||||
</Trans>
|
||||
) : (
|
||||
|
||||
@@ -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)`
|
||||
@@ -302,7 +303,7 @@ export default function AccountDetails({
|
||||
</UpperSection>
|
||||
{!!pendingTransactions.length || !!confirmedTransactions.length ? (
|
||||
<LowerSection>
|
||||
<AutoRow mb={'1rem'} style={{ justifyContent: 'space-between' }}>
|
||||
<AutoRow mb="1rem" style={{ justifyContent: 'space-between' }}>
|
||||
<ThemedText.DeprecatedBody>
|
||||
<Trans>Recent Transactions</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -57,7 +57,6 @@ const FailedText = ({ transactionState }: { transactionState: TransactionState }
|
||||
const FormattedCurrencyAmount = ({
|
||||
rawAmount,
|
||||
currencyId,
|
||||
sigFigs = 2,
|
||||
}: {
|
||||
rawAmount: string
|
||||
currencyId: string
|
||||
@@ -67,7 +66,7 @@ const FormattedCurrencyAmount = ({
|
||||
|
||||
return currency ? (
|
||||
<HighlightText>
|
||||
{formatAmount(rawAmount, currency.decimals, sigFigs)} {currency.symbol}
|
||||
{formatAmount(rawAmount, currency.decimals, /* sigFigs= */ 6)} {currency.symbol}
|
||||
</HighlightText>
|
||||
) : null
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
233
src/components/AidropModal/index.tsx
Normal file
@@ -0,0 +1,233 @@
|
||||
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 { ChevronRightIcon } from 'nft/components/icons'
|
||||
import { useState } from 'react'
|
||||
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};
|
||||
margin-top: 40px;
|
||||
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 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 AirdropModal = () => {
|
||||
const [isClaimed, setClaimed] = useState(false)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [totalAmount] = useState(300)
|
||||
const isOpen = useModalIsOpen(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
|
||||
const usdcAirdropToggle = useToggleModal(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
|
||||
const dismiss = () => {
|
||||
usdcAirdropToggle()
|
||||
|
||||
setTimeout(() => {
|
||||
setClaimed(false)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
setIsSubmitting(true)
|
||||
|
||||
setTimeout(() => {
|
||||
setIsSubmitting(false)
|
||||
setClaimed(true)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal hideBorder isOpen={isOpen} onDismiss={dismiss} 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/" target="_blank">
|
||||
<ThemedText.Link>
|
||||
<div style={{ display: 'flex', alignItems: 'center', textAlign: 'center', justifyContent: 'center' }}>
|
||||
<span style={{ marginRight: 8 }}>Etherscan</span>
|
||||
<ChevronRightIcon />
|
||||
</div>
|
||||
</ThemedText.Link>
|
||||
</EtherscanLink>
|
||||
|
||||
<CloseButton size={ButtonSize.medium} emphasis={ButtonEmphasis.medium} onClick={dismiss}>
|
||||
Close
|
||||
</CloseButton>
|
||||
</ClaimContainer>
|
||||
) : (
|
||||
<>
|
||||
<ImageContainer>
|
||||
<TextContainer>
|
||||
<SyledCloseIcon onClick={dismiss} stroke="white" />
|
||||
<MainHeader>Uniswap NFT Airdrop</MainHeader>
|
||||
<USDCLabel>{totalAmount} USDC</USDCLabel>
|
||||
<Line />
|
||||
<RewardsDetailsContainer>
|
||||
<RewardsText>Trading rewards</RewardsText> <CurrencyText>300 USDC</CurrencyText>
|
||||
</RewardsDetailsContainer>
|
||||
<RewardsDetailsContainer>
|
||||
<RewardsText>Genie NFT holder rewards</RewardsText> <CurrencyText>0</CurrencyText>
|
||||
</RewardsDetailsContainer>
|
||||
</TextContainer>
|
||||
<StyledImage src={airdropBackgroundv2} />
|
||||
</ImageContainer>
|
||||
<Body>
|
||||
<RewardsInformationText>
|
||||
As a long time supporter of Genie you’ve been awarded {totalAmount} USDC tokens. Read more about Uniswap
|
||||
NFT.
|
||||
</RewardsInformationText>
|
||||
<LinkWrap href="https://uniswap.org/blog/uniswap-nft-aggregator-announcement" target="_blank">
|
||||
<ThemedText.Link>Read more about Uniswap NFT.</ThemedText.Link>
|
||||
</LinkWrap>
|
||||
|
||||
<ClaimButton
|
||||
onClick={submit}
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { darken } from 'polished'
|
||||
import { Check, ChevronDown } from 'react-feather'
|
||||
import { Button as RebassButton, ButtonProps as ButtonPropsOriginal } from 'rebass/styled-components'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import styled, { DefaultTheme, useTheme } from 'styled-components/macro'
|
||||
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
@@ -345,54 +345,176 @@ export function ButtonRadioChecked({ active = false, children, ...rest }: { acti
|
||||
if (!active) {
|
||||
return (
|
||||
<ButtonOutlined $borderRadius="12px" padding="12px 8px" {...rest}>
|
||||
{<RowBetween>{children}</RowBetween>}
|
||||
<RowBetween>{children}</RowBetween>
|
||||
</ButtonOutlined>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ActiveOutlined {...rest} padding="12px 8px" $borderRadius="12px">
|
||||
{
|
||||
<RowBetween>
|
||||
{children}
|
||||
<CheckboxWrapper>
|
||||
<Circle>
|
||||
<ResponsiveCheck size={13} stroke={theme.deprecated_white} />
|
||||
</Circle>
|
||||
</CheckboxWrapper>
|
||||
</RowBetween>
|
||||
}
|
||||
<RowBetween>
|
||||
{children}
|
||||
<CheckboxWrapper>
|
||||
<Circle>
|
||||
<ResponsiveCheck size={13} stroke={theme.deprecated_white} />
|
||||
</Circle>
|
||||
</CheckboxWrapper>
|
||||
</RowBetween>
|
||||
</ActiveOutlined>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const MediumButton = styled.button`
|
||||
const ButtonOverlay = styled.div`
|
||||
background-color: transparent;
|
||||
bottom: 0;
|
||||
border-radius: 16px;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transition: 150ms ease background-color;
|
||||
width: 100%;
|
||||
`
|
||||
export enum ButtonSize {
|
||||
small,
|
||||
medium,
|
||||
large,
|
||||
}
|
||||
export enum ButtonEmphasis {
|
||||
high,
|
||||
promotional,
|
||||
highSoft,
|
||||
medium,
|
||||
low,
|
||||
warning,
|
||||
destructive,
|
||||
}
|
||||
interface BaseButtonProps {
|
||||
size: ButtonSize
|
||||
emphasis: ButtonEmphasis
|
||||
}
|
||||
|
||||
function pickThemeButtonBackgroundColor({ theme, emphasis }: { theme: DefaultTheme; emphasis: ButtonEmphasis }) {
|
||||
switch (emphasis) {
|
||||
case ButtonEmphasis.high:
|
||||
return theme.accentAction
|
||||
case ButtonEmphasis.promotional:
|
||||
return theme.accentTextLightPrimary
|
||||
case ButtonEmphasis.highSoft:
|
||||
return theme.accentActionSoft
|
||||
case ButtonEmphasis.low:
|
||||
return 'transparent'
|
||||
case ButtonEmphasis.warning:
|
||||
return theme.accentWarningSoft
|
||||
case ButtonEmphasis.destructive:
|
||||
return theme.accentCritical
|
||||
case ButtonEmphasis.medium:
|
||||
default:
|
||||
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:
|
||||
case ButtonEmphasis.promotional:
|
||||
return theme.accentTextLightPrimary
|
||||
case ButtonEmphasis.highSoft:
|
||||
return theme.accentAction
|
||||
case ButtonEmphasis.low:
|
||||
return theme.textSecondary
|
||||
case ButtonEmphasis.warning:
|
||||
return theme.accentWarning
|
||||
case ButtonEmphasis.destructive:
|
||||
return theme.accentTextDarkPrimary
|
||||
case ButtonEmphasis.medium:
|
||||
default:
|
||||
return theme.textPrimary
|
||||
}
|
||||
}
|
||||
|
||||
const BaseThemeButton = styled.button<BaseButtonProps>`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
background-color: ${pickThemeButtonBackgroundColor};
|
||||
border-radius: 16px;
|
||||
border: 0;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
color: ${pickThemeButtonTextColor};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: ${pickThemeButtonFontSize};
|
||||
font-weight: 600;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
transition: 150ms ease background-color opacity;
|
||||
line-height: ${pickThemeButtonLineHeight};
|
||||
padding: ${pickThemeButtonPadding};
|
||||
position: relative;
|
||||
transition: 150ms ease opacity;
|
||||
|
||||
:active {
|
||||
${ButtonOverlay} {
|
||||
background-color: ${({ theme }) => theme.stateOverlayPressed};
|
||||
}
|
||||
}
|
||||
:disabled {
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
}
|
||||
:hover {
|
||||
background-color: ${({ theme }) => theme.stateOverlayHover};
|
||||
}
|
||||
:active {
|
||||
background-color: ${({ theme }) => theme.stateOverlayPressed};
|
||||
}
|
||||
:focus {
|
||||
background-color: ${({ theme }) => theme.stateOverlayPressed};
|
||||
${ButtonOverlay} {
|
||||
background-color: ${({ theme }) => theme.stateOverlayPressed};
|
||||
}
|
||||
}
|
||||
:hover {
|
||||
${ButtonOverlay} {
|
||||
background-color: ${({ theme }) => theme.stateOverlayHover};
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
interface ThemeButtonProps extends React.ComponentPropsWithoutRef<'button'>, BaseButtonProps {}
|
||||
|
||||
export const ThemeButton = ({ children, ...rest }: ThemeButtonProps) => {
|
||||
return (
|
||||
<BaseThemeButton {...rest}>
|
||||
<ButtonOverlay />
|
||||
{children}
|
||||
</BaseThemeButton>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,15 +14,15 @@ export const LightCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
`
|
||||
|
||||
export const LightGreyCard = styled(Card)`
|
||||
export const LightGrayCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
`
|
||||
|
||||
export const GreyCard = styled(Card)`
|
||||
export const GrayCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg3};
|
||||
`
|
||||
|
||||
export const DarkGreyCard = styled(Card)`
|
||||
export const DarkGrayCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
`
|
||||
|
||||
|
||||
@@ -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,7 +1,7 @@
|
||||
import { SparkLineLoadingBubble } from 'components/Tokens/TokenTable/TokenRow'
|
||||
import { curveCardinal, scaleLinear } from 'd3'
|
||||
import { PricePoint } from 'graphql/data/TokenPrice'
|
||||
import { SparklineMap, TopToken } from 'graphql/data/TopTokens'
|
||||
import { PricePoint } from 'graphql/data/util'
|
||||
import { TimePeriod } from 'graphql/data/util'
|
||||
import { memo } from 'react'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
47
src/components/Common/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { css } from 'styled-components/macro'
|
||||
|
||||
export const ScrollBarStyles = css<{ $isHorizontalScroll?: boolean }>`
|
||||
// Firefox scrollbar styling
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: ${({ theme }) => `${theme.backgroundOutline} transparent`};
|
||||
height: 100%;
|
||||
|
||||
// safari and chrome scrollbar styling
|
||||
::-webkit-scrollbar {
|
||||
background: transparent;
|
||||
|
||||
// Set height for horizontal scrolls
|
||||
${({ $isHorizontalScroll }) => {
|
||||
return $isHorizontalScroll
|
||||
? css`
|
||||
height: 4px;
|
||||
overflow-x: scroll;
|
||||
`
|
||||
: css`
|
||||
width: 4px;
|
||||
overflow-y: scroll;
|
||||
`
|
||||
}}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: ${({ theme }) => theme.backgroundOutline};
|
||||
border-radius: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
export const OpacityHoverState = css`
|
||||
&:hover {
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: ${({ theme }) => theme.opacity.click};
|
||||
}
|
||||
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `opacity ${duration.medium} ${timing.ease}`};
|
||||
`
|
||||
@@ -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>
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ElementName, Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
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;
|
||||
@@ -281,7 +282,7 @@ export default function SwapCurrencyInputPanel({
|
||||
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
|
||||
</span>
|
||||
) : currency ? (
|
||||
<CurrencyLogo style={{ marginRight: '2px' }} currency={currency} size={'24px'} />
|
||||
<CurrencyLogo style={{ marginRight: '2px' }} currency={currency} size="24px" />
|
||||
) : null}
|
||||
{pair ? (
|
||||
<StyledTokenName className="pair-name-container">
|
||||
@@ -301,7 +302,7 @@ export default function SwapCurrencyInputPanel({
|
||||
</Aligner>
|
||||
</CurrencySelect>
|
||||
</InputRow>
|
||||
{!hideInput && !hideBalance && currency && (
|
||||
{Boolean(!hideInput && !hideBalance) && (
|
||||
<FiatRow>
|
||||
<RowBetween>
|
||||
<LoadingOpacityContainer $loading={loading}>
|
||||
@@ -325,7 +326,7 @@ export default function SwapCurrencyInputPanel({
|
||||
</ThemedText.DeprecatedBody>
|
||||
{showMaxButton && selectedCurrencyBalance ? (
|
||||
<TraceEvent
|
||||
events={[Event.onClick]}
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED}
|
||||
element={ElementName.MAX_TOKEN_AMOUNT_BUTTON}
|
||||
>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ElementName, Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
@@ -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;
|
||||
@@ -269,7 +270,7 @@ export default function CurrencyInputPanel({
|
||||
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
|
||||
</span>
|
||||
) : currency ? (
|
||||
<CurrencyLogo style={{ marginRight: '0.5rem' }} currency={currency} size={'24px'} />
|
||||
<CurrencyLogo style={{ marginRight: '0.5rem' }} currency={currency} size="24px" />
|
||||
) : null}
|
||||
{pair ? (
|
||||
<StyledTokenName className="pair-name-container">
|
||||
@@ -314,7 +315,7 @@ export default function CurrencyInputPanel({
|
||||
</ThemedText.DeprecatedBody>
|
||||
{showMaxButton && selectedCurrencyBalance ? (
|
||||
<TraceEvent
|
||||
events={[Event.onClick]}
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED}
|
||||
element={ElementName.MAX_TOKEN_AMOUNT_BUTTON}
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -98,7 +98,7 @@ export default class ErrorBoundary extends React.Component<PropsWithChildren<unk
|
||||
return (
|
||||
<FallbackWrapper>
|
||||
<BodyWrapper>
|
||||
<AutoColumn gap={'md'}>
|
||||
<AutoColumn gap="md">
|
||||
<SomethingWentWrongWrapper>
|
||||
<ThemedText.DeprecatedLabel fontSize={24} fontWeight={600}>
|
||||
<Trans>Something went wrong</Trans>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { NftGraphQlVariant, useNftGraphQlFlag } from 'featureFlags/flags/nftGraphQl'
|
||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
|
||||
@@ -205,12 +204,6 @@ export default function FeatureFlagModal() {
|
||||
</Header>
|
||||
<FeatureFlagGroup name="Phase 1">
|
||||
<FeatureFlagOption variant={NftVariant} value={useNftFlag()} featureFlag={FeatureFlag.nft} label="NFTs" />
|
||||
<FeatureFlagOption
|
||||
variant={NftGraphQlVariant}
|
||||
value={useNftGraphQlFlag()}
|
||||
featureFlag={FeatureFlag.nftGraphQl}
|
||||
label="NFT GraphQL Endpoints"
|
||||
/>
|
||||
</FeatureFlagGroup>
|
||||
<FeatureFlagGroup name="Debug">
|
||||
<FeatureFlagOption
|
||||
|
||||
@@ -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'
|
||||
@@ -9,9 +10,9 @@ import sockImg from '../../assets/svg/socks.svg'
|
||||
import { useHasSocks } from '../../hooks/useSocksBalance'
|
||||
import Identicon from '../Identicon'
|
||||
|
||||
const IconWrapper = styled.div<{ size?: number }>`
|
||||
export const IconWrapper = styled.div<{ size?: number }>`
|
||||
position: relative;
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
${flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 8px;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import jazzicon from '@metamask/jazzicon'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const StyledIdenticon = styled.div<{ iconSize: number }>`
|
||||
height: ${({ iconSize }) => `${iconSize}px`};
|
||||
width: ${({ iconSize }) => `${iconSize}px`};
|
||||
border-radius: 1.125rem;
|
||||
border-radius: 50%;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg4};
|
||||
font-size: initial;
|
||||
`
|
||||
@@ -41,10 +41,12 @@ export default function Identicon({ size }: { size?: number }) {
|
||||
return
|
||||
}, [icon, iconRef])
|
||||
|
||||
const handleError = useCallback(() => setFetchable(false), [])
|
||||
|
||||
return (
|
||||
<StyledIdenticon iconSize={iconSize}>
|
||||
{avatar && fetchable ? (
|
||||
<StyledAvatar alt="avatar" src={avatar} onError={() => setFetchable(false)}></StyledAvatar>
|
||||
<StyledAvatar alt="avatar" src={avatar} onError={handleError}></StyledAvatar>
|
||||
) : (
|
||||
<span ref={iconRef} />
|
||||
)}
|
||||
|
||||
@@ -213,7 +213,7 @@ export const Brush = ({
|
||||
visible={showLabels || hovering}
|
||||
>
|
||||
<TooltipBackground y="0" x="-30" height="30" width="60" rx="8" />
|
||||
<Tooltip transform={`scale(-1, 1)`} y="15" dominantBaseline="middle">
|
||||
<Tooltip transform="scale(-1, 1)" y="15" dominantBaseline="middle">
|
||||
{brushLabelValue('w', localBrushExtent[0])}
|
||||
</Tooltip>
|
||||
</LabelGroup>
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import useHttpLocations from '../../hooks/useHttpLocations'
|
||||
import Logo from '../Logo'
|
||||
|
||||
const StyledListLogo = styled(Logo)<{ size: string }>`
|
||||
width: ${({ size }) => size};
|
||||
height: ${({ size }) => size};
|
||||
`
|
||||
|
||||
export default function ListLogo({
|
||||
logoURI,
|
||||
style,
|
||||
size = '24px',
|
||||
alt,
|
||||
symbol,
|
||||
}: {
|
||||
logoURI: string
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
alt?: string
|
||||
symbol?: string
|
||||
}) {
|
||||
const srcs: string[] = useHttpLocations(logoURI)
|
||||
|
||||
return <StyledListLogo alt={alt} size={size} symbol={symbol} srcs={srcs} style={style} />
|
||||
}
|
||||
@@ -14,7 +14,7 @@ const StyledSVG = styled.svg<{ size: string; stroke?: string }>`
|
||||
height: ${({ size }) => size};
|
||||
width: ${({ size }) => size};
|
||||
path {
|
||||
stroke: ${({ stroke, theme }) => theme.accentActive};
|
||||
stroke: ${({ stroke, theme }) => stroke ?? theme.accentActive};
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -27,9 +27,11 @@ const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ scrollOverlay?: bool
|
||||
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({
|
||||
const StyledDialogContent = styled(
|
||||
({ hideBorder, maxWidth, minHeight, maxHeight, mobile, isOpen, scrollOverlay, ...rest }) => (
|
||||
<AnimatedDialogContent {...rest} />
|
||||
)
|
||||
).attrs({
|
||||
'aria-label': 'dialog',
|
||||
})`
|
||||
overflow-y: auto;
|
||||
@@ -37,7 +39,7 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, scro
|
||||
&[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;
|
||||
@@ -45,8 +47,7 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, scro
|
||||
overflow-x: hidden;
|
||||
|
||||
align-self: ${({ mobile }) => mobile && 'flex-end'};
|
||||
|
||||
max-width: 420px;
|
||||
max-width: ${({ maxWidth }) => maxWidth}px;
|
||||
${({ maxHeight }) =>
|
||||
maxHeight &&
|
||||
css`
|
||||
@@ -83,9 +84,11 @@ interface ModalProps {
|
||||
onDismiss: () => void
|
||||
minHeight?: number | false
|
||||
maxHeight?: number
|
||||
maxWidth?: number
|
||||
initialFocusRef?: React.RefObject<any>
|
||||
children?: React.ReactNode
|
||||
scrollOverlay?: boolean
|
||||
hideBorder?: boolean
|
||||
}
|
||||
|
||||
export default function Modal({
|
||||
@@ -93,9 +96,11 @@ export default function Modal({
|
||||
onDismiss,
|
||||
minHeight = false,
|
||||
maxHeight = 90,
|
||||
maxWidth = 420,
|
||||
initialFocusRef,
|
||||
children,
|
||||
scrollOverlay,
|
||||
hideBorder = false,
|
||||
}: ModalProps) {
|
||||
const fadeTransition = useTransition(isOpen, {
|
||||
config: { duration: 200 },
|
||||
@@ -141,6 +146,8 @@ export default function Modal({
|
||||
maxHeight={maxHeight}
|
||||
mobile={isMobile}
|
||||
scrollOverlay={scrollOverlay}
|
||||
hideBorder={hideBorder}
|
||||
maxWidth={maxWidth}
|
||||
>
|
||||
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
|
||||
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}
|
||||
|
||||
@@ -27,9 +27,9 @@ export function LoadingView({ children, onDismiss }: { children: any; onDismiss:
|
||||
<CloseIcon onClick={onDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
|
||||
<CustomLightSpinner src={Circle} alt="loader" size="90px" />
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="100px" justify={'center'}>
|
||||
<AutoColumn gap="100px" justify="center">
|
||||
{children}
|
||||
<ThemedText.DeprecatedSubHeader>
|
||||
<Trans>Confirm this transaction in your wallet</Trans>
|
||||
@@ -60,7 +60,7 @@ export function SubmittedView({
|
||||
<ConfirmedIcon>
|
||||
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.deprecated_primary1} />
|
||||
</ConfirmedIcon>
|
||||
<AutoColumn gap="100px" justify={'center'}>
|
||||
<AutoColumn gap="100px" justify="center">
|
||||
{children}
|
||||
{chainId && hash && (
|
||||
<ExternalLink
|
||||
|
||||
51
src/components/NavBar/Bag.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { NavIcon } from 'components/NavBar/NavIcon'
|
||||
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'
|
||||
|
||||
const CounterDot = styled.div`
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
border-radius: 100px;
|
||||
color: ${({ theme }) => theme.accentTextLightPrimary};
|
||||
font-size: 10px;
|
||||
line-height: 12px;
|
||||
min-height: 16px;
|
||||
min-width: 16px;
|
||||
padding: 2px 4px;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
text-align: center;
|
||||
top: 4px;
|
||||
`
|
||||
|
||||
export const Bag = () => {
|
||||
const itemsInBag = useBag((state) => state.itemsInBag)
|
||||
const sellAssets = useSellAsset((state) => state.sellAssets)
|
||||
const isProfilePage = useIsNftProfilePage()
|
||||
|
||||
const { bagExpanded, setBagExpanded } = useBag(
|
||||
({ bagExpanded, setBagExpanded }) => ({ bagExpanded, setBagExpanded }),
|
||||
shallow
|
||||
)
|
||||
|
||||
const handleIconClick = useCallback(() => {
|
||||
setBagExpanded({ bagExpanded: !bagExpanded })
|
||||
}, [bagExpanded, setBagExpanded])
|
||||
|
||||
const bagQuantity = isProfilePage ? sellAssets.length : itemsInBag.length
|
||||
const bagHasItems = bagQuantity > 0
|
||||
|
||||
return (
|
||||
<NavIcon isActive={bagExpanded} onClick={handleIconClick}>
|
||||
{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>
|
||||
)
|
||||
}
|
||||
@@ -42,7 +42,7 @@ const PrimaryMenuRow = ({
|
||||
<Row onClick={close}>{children}</Row>
|
||||
</NavLink>
|
||||
) : (
|
||||
<Row as="a" href={href} target={'_blank'} rel={'noopener noreferrer'} className={styles.MenuRow}>
|
||||
<Row as="a" href={href} target="_blank" rel="noopener noreferrer" className={styles.MenuRow}>
|
||||
{children}
|
||||
</Row>
|
||||
)}
|
||||
@@ -123,7 +123,7 @@ export const MenuDropdown = () => {
|
||||
<>
|
||||
<Box position="relative" ref={ref}>
|
||||
<NavIcon isActive={isOpen} onClick={toggleOpen}>
|
||||
<EllipsisIcon width={20} height={20} />
|
||||
<EllipsisIcon viewBox="0 0 20 20" width={24} height={24} />
|
||||
</NavIcon>
|
||||
|
||||
{isOpen && (
|
||||
|
||||
@@ -4,6 +4,8 @@ import { sprinkles, vars } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const navIcon = style([
|
||||
sprinkles({
|
||||
alignItems: 'center',
|
||||
background: 'transparent',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -11,7 +13,6 @@ export const navIcon = style([
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
padding: '10',
|
||||
borderRadius: '8',
|
||||
transition: '250',
|
||||
}),
|
||||
|
||||
@@ -14,10 +14,10 @@ export const NavIcon = ({ children, isActive, onClick }: NavIconProps) => {
|
||||
<Box
|
||||
as="button"
|
||||
className={styles.navIcon}
|
||||
background={isActive ? 'accentActiveSoft' : 'none'}
|
||||
color={isActive ? 'textPrimary' : 'textSecondary'}
|
||||
onClick={onClick}
|
||||
height="40"
|
||||
width="40"
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { ElementName, Event, EventName, SectionName } from 'analytics/constants'
|
||||
import { Trace } from 'analytics/Trace'
|
||||
import { useTrace } from 'analytics/Trace'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import { sendAnalyticsEvent, Trace, TraceEvent, useTrace } from '@uniswap/analytics'
|
||||
import { BrowserEvent, ElementName, EventName, SectionName } from '@uniswap/analytics-events'
|
||||
import clsx from 'clsx'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { organizeSearchResults } from 'lib/utils/searchBar'
|
||||
import { Box } from 'nft/components/Box'
|
||||
@@ -35,6 +33,7 @@ export const SearchBar = () => {
|
||||
const phase1Flag = useNftFlag()
|
||||
const isMobile = useIsMobile()
|
||||
const isTablet = useIsTablet()
|
||||
const isPhase1 = phase1Flag === NftVariant.Enabled
|
||||
|
||||
useOnClickOutside(searchRef, () => {
|
||||
isOpen && toggleOpen()
|
||||
@@ -47,6 +46,7 @@ export const SearchBar = () => {
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
enabled: !!debouncedSearchValue.length && isPhase1,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -57,10 +57,11 @@ export const SearchBar = () => {
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
enabled: !!debouncedSearchValue.length,
|
||||
}
|
||||
)
|
||||
|
||||
const isNFTPage = pathname.includes('/nfts')
|
||||
const isNFTPage = useIsNftPage()
|
||||
|
||||
const [reducedTokens, reducedCollections] = organizeSearchResults(isNFTPage, tokens ?? [], collections ?? [])
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
@@ -137,7 +138,7 @@ export const SearchBar = () => {
|
||||
</Box>
|
||||
</Box>
|
||||
<TraceEvent
|
||||
events={[Event.onFocus]}
|
||||
events={[BrowserEvent.onFocus]}
|
||||
name={EventName.NAVBAR_SEARCH_SELECTED}
|
||||
element={ElementName.NAVBAR_SEARCH_INPUT}
|
||||
properties={{ ...trace }}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { NavBarSearchTypes, SectionName } from 'analytics/constants'
|
||||
import { useTrace } from 'analytics/Trace'
|
||||
import { useTrace } from '@uniswap/analytics'
|
||||
import { NavBarSearchTypes, SectionName } from '@uniswap/analytics-events'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { subheadSmall } from 'nft/css/common.css'
|
||||
@@ -47,7 +48,7 @@ export const SearchBarDropdownSection = ({
|
||||
}: SearchBarDropdownSectionProps) => {
|
||||
return (
|
||||
<Column gap="12">
|
||||
<Row paddingX="16" paddingY="4" gap="8" color="grey300" className={subheadSmall} style={{ lineHeight: '20px' }}>
|
||||
<Row paddingX="16" paddingY="4" gap="8" color="gray300" className={subheadSmall} style={{ lineHeight: '20px' }}>
|
||||
{headerIcon ? headerIcon : null}
|
||||
<Box>{header}</Box>
|
||||
</Row>
|
||||
@@ -113,10 +114,11 @@ export const SearchBarDropdown = ({
|
||||
const { history: searchHistory, updateItem: updateSearchHistory } = useSearchHistory()
|
||||
const shortenedHistory = useMemo(() => searchHistory.slice(0, 2), [searchHistory])
|
||||
const { pathname } = useLocation()
|
||||
const isNFTPage = pathname.includes('/nfts')
|
||||
const isNFTPage = useIsNftPage()
|
||||
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'],
|
||||
@@ -134,6 +136,7 @@ export const SearchBarDropdown = ({
|
||||
stats: {
|
||||
total_supply: collection.totalSupply,
|
||||
one_day_change: collection.floorChange,
|
||||
floor_price: formatEthPrice(collection.floor?.toString()),
|
||||
},
|
||||
}))
|
||||
.slice(0, isNFTPage ? 3 : 2)
|
||||
@@ -202,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 ? (
|
||||
@@ -340,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,22 +0,0 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
import { sprinkles } from 'nft/css/sprinkles.css'
|
||||
|
||||
export const bagQuantity = style([
|
||||
sprinkles({
|
||||
position: 'absolute',
|
||||
top: '4',
|
||||
right: '4',
|
||||
backgroundColor: 'accentAction',
|
||||
borderRadius: 'round',
|
||||
color: 'explicitWhite',
|
||||
textAlign: 'center',
|
||||
fontWeight: 'semibold',
|
||||
paddingY: '1',
|
||||
paddingX: '4',
|
||||
}),
|
||||
{
|
||||
fontSize: '8px',
|
||||
lineHeight: '12px',
|
||||
minWidth: '14px',
|
||||
},
|
||||
])
|
||||
@@ -1,47 +0,0 @@
|
||||
import { NavIcon } from 'components/NavBar/NavIcon'
|
||||
import * as styles from 'components/NavBar/ShoppingBag.css'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { BagIcon, HundredsOverflowIcon, TagIcon } from 'nft/components/icons'
|
||||
import { useBag, useSellAsset } from 'nft/hooks'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
export const ShoppingBag = () => {
|
||||
const itemsInBag = useBag((state) => state.itemsInBag)
|
||||
const sellAssets = useSellAsset((state) => state.sellAssets)
|
||||
const [bagQuantity, setBagQuantity] = useState(0)
|
||||
const [sellQuantity, setSellQuantity] = useState(0)
|
||||
const location = useLocation()
|
||||
|
||||
const toggleBag = useBag((s) => s.toggleBag)
|
||||
|
||||
useEffect(() => {
|
||||
setBagQuantity(itemsInBag.length)
|
||||
}, [itemsInBag])
|
||||
|
||||
useEffect(() => {
|
||||
setSellQuantity(sellAssets.length)
|
||||
}, [sellAssets])
|
||||
|
||||
const isProfilePage = location.pathname === '/profile'
|
||||
|
||||
return (
|
||||
<NavIcon onClick={toggleBag}>
|
||||
{isProfilePage ? (
|
||||
<>
|
||||
<TagIcon width={20} height={20} />
|
||||
{sellQuantity ? (
|
||||
<Box className={styles.bagQuantity}>{sellQuantity > 99 ? <HundredsOverflowIcon /> : sellQuantity}</Box>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<BagIcon width={20} height={20} />
|
||||
{bagQuantity ? (
|
||||
<Box className={styles.bagQuantity}>{bagQuantity > 99 ? <HundredsOverflowIcon /> : bagQuantity}</Box>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</NavIcon>
|
||||
)
|
||||
}
|
||||
@@ -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 { sendAnalyticsEvent } from 'analytics'
|
||||
import { EventName } from 'analytics/constants'
|
||||
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
|
||||
@@ -93,13 +99,13 @@ export const CollectionRow = ({
|
||||
<Box className={styles.primaryText}>{collection.name}</Box>
|
||||
{collection.isVerified && <VerifiedIcon className={styles.suggestionIcon} />}
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>{putCommas(collection.stats.total_supply)} items</Box>
|
||||
<Box className={styles.secondaryText}>{putCommas(collection?.stats?.total_supply ?? 0)} items</Box>
|
||||
</Column>
|
||||
</Row>
|
||||
{collection.floorPrice ? (
|
||||
{collection.stats?.floor_price ? (
|
||||
<Column className={styles.suggestionSecondaryContainer}>
|
||||
<Row gap="4">
|
||||
<Box className={styles.primaryText}>{ethNumberStandardFormatter(collection.floorPrice)} ETH</Box>
|
||||
<Box className={styles.primaryText}>{ethNumberStandardFormatter(collection.stats?.floor_price)} ETH</Box>
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>Floor</Box>
|
||||
</Column>
|
||||
@@ -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 && (
|
||||
|
||||
@@ -3,16 +3,17 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import Web3Status from 'components/Web3Status'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Row } from 'nft/components/Flex'
|
||||
import { UniIcon } from 'nft/components/icons'
|
||||
import { ReactNode } from 'react'
|
||||
import { NavLink, NavLinkProps, useLocation } from 'react-router-dom'
|
||||
|
||||
import { Bag } from './Bag'
|
||||
import { ChainSelector } from './ChainSelector'
|
||||
import { MenuDropdown } from './MenuDropdown'
|
||||
import { SearchBar } from './SearchBar'
|
||||
import { ShoppingBag } from './ShoppingBag'
|
||||
import * as styles from './style.css'
|
||||
|
||||
interface MenuItemProps {
|
||||
@@ -48,6 +49,8 @@ const PageTabs = () => {
|
||||
pathname.startsWith('/increase') ||
|
||||
pathname.startsWith('/find')
|
||||
|
||||
const isNftPage = useIsNftPage()
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem href="/swap" isActive={pathname.startsWith('/swap')}>
|
||||
@@ -57,11 +60,11 @@ const PageTabs = () => {
|
||||
<Trans>Tokens</Trans>
|
||||
</MenuItem>
|
||||
{nftFlag === NftVariant.Enabled && (
|
||||
<MenuItem href="/nfts" isActive={pathname.startsWith('/nfts')}>
|
||||
<MenuItem href="/nfts" isActive={isNftPage}>
|
||||
<Trans>NFTs</Trans>
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem href="/pool" id={'pool-nav-link'} isActive={isPoolActive}>
|
||||
<MenuItem href="/pool" id="pool-nav-link" isActive={isPoolActive}>
|
||||
<Trans>Pool</Trans>
|
||||
</MenuItem>
|
||||
</>
|
||||
@@ -69,8 +72,7 @@ const PageTabs = () => {
|
||||
}
|
||||
|
||||
const Navbar = () => {
|
||||
const { pathname } = useLocation()
|
||||
const isNftPage = pathname.startsWith('/nfts') || pathname.startsWith('/profile')
|
||||
const isNftPage = useIsNftPage()
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -100,7 +102,7 @@ const Navbar = () => {
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<MenuDropdown />
|
||||
</Box>
|
||||
{isNftPage && <ShoppingBag />}
|
||||
{isNftPage && <Bag />}
|
||||
{!isNftPage && (
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<ChainSelector />
|
||||
|
||||
@@ -12,9 +12,6 @@ export const nav = style([
|
||||
zIndex: '2',
|
||||
background: 'backgroundFloating',
|
||||
}),
|
||||
{
|
||||
backdropFilter: 'blur(24px)',
|
||||
},
|
||||
])
|
||||
|
||||
export const logoContainer = style([
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { RowFixed } from 'components/Row'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
|
||||
import useGasPrice from 'hooks/useGasPrice'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import useMachineTimeMs from 'hooks/useMachineTime'
|
||||
import JSBI from 'jsbi'
|
||||
import useBlockNumber from 'lib/hooks/useBlockNumber'
|
||||
@@ -106,6 +107,7 @@ export default function Polling() {
|
||||
const machineTime = useMachineTimeMs(NETWORK_HEALTH_CHECK_MS)
|
||||
const blockTime = useCurrentBlockTimestamp()
|
||||
const theme = useTheme()
|
||||
const isNftPage = useIsNftPage()
|
||||
|
||||
const ethGasPrice = useGasPrice()
|
||||
const priceGwei = ethGasPrice ? JSBI.divide(ethGasPrice, JSBI.BigInt(1000000000)) : undefined
|
||||
@@ -135,11 +137,11 @@ export default function Polling() {
|
||||
|
||||
//TODO - chainlink gas oracle is really slow. Can we get a better data source?
|
||||
|
||||
return (
|
||||
return isNftPage ? null : (
|
||||
<>
|
||||
<RowFixed>
|
||||
<StyledPolling onMouseEnter={() => setIsHover(true)} onMouseLeave={() => setIsHover(false)} warning={warning}>
|
||||
<ExternalLink href={'https://etherscan.io/gastracker'}>
|
||||
<ExternalLink href="https://etherscan.io/gastracker">
|
||||
{priceGwei ? (
|
||||
<RowFixed style={{ marginRight: '8px' }}>
|
||||
<ThemedText.DeprecatedMain fontSize="11px" mr="8px" color={theme.deprecated_text3}>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Options, Placement } from '@popperjs/core'
|
||||
import Portal from '@reach/portal'
|
||||
import useInterval from 'lib/hooks/useInterval'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import React, { CSSProperties, useCallback, useMemo, useState } from 'react'
|
||||
import { usePopper } from 'react-popper'
|
||||
import styled from 'styled-components/macro'
|
||||
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;
|
||||
@@ -74,11 +75,26 @@ const Arrow = styled.div`
|
||||
export interface PopoverProps {
|
||||
content: React.ReactNode
|
||||
show: boolean
|
||||
children: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
placement?: Placement
|
||||
offsetX?: number
|
||||
offsetY?: number
|
||||
hideArrow?: boolean
|
||||
showInline?: boolean
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export default function Popover({ content, show, children, placement = 'auto' }: PopoverProps) {
|
||||
export default function Popover({
|
||||
content,
|
||||
show,
|
||||
children,
|
||||
placement = 'auto',
|
||||
offsetX = 8,
|
||||
offsetY = 8,
|
||||
hideArrow = false,
|
||||
showInline = false,
|
||||
style,
|
||||
}: PopoverProps) {
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLDivElement | null>(null)
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null)
|
||||
const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null)
|
||||
@@ -88,12 +104,12 @@ export default function Popover({ content, show, children, placement = 'auto' }:
|
||||
placement,
|
||||
strategy: 'fixed',
|
||||
modifiers: [
|
||||
{ name: 'offset', options: { offset: [8, 8] } },
|
||||
{ name: 'offset', options: { offset: [offsetX, offsetY] } },
|
||||
{ name: 'arrow', options: { element: arrowElement } },
|
||||
{ name: 'preventOverflow', options: { padding: 8 } },
|
||||
],
|
||||
}),
|
||||
[arrowElement, placement]
|
||||
[arrowElement, offsetX, offsetY, placement]
|
||||
)
|
||||
|
||||
const { styles, update, attributes } = usePopper(referenceElement, popperElement, options)
|
||||
@@ -103,18 +119,24 @@ export default function Popover({ content, show, children, placement = 'auto' }:
|
||||
}, [update])
|
||||
useInterval(updateCallback, show ? 100 : null)
|
||||
|
||||
return (
|
||||
return showInline ? (
|
||||
<PopoverContainer show={show}>{content}</PopoverContainer>
|
||||
) : (
|
||||
<>
|
||||
<ReferenceElement ref={setReferenceElement as any}>{children}</ReferenceElement>
|
||||
<ReferenceElement style={style} ref={setReferenceElement as any}>
|
||||
{children}
|
||||
</ReferenceElement>
|
||||
<Portal>
|
||||
<PopoverContainer show={show} ref={setPopperElement as any} style={styles.popper} {...attributes.popper}>
|
||||
{content}
|
||||
<Arrow
|
||||
className={`arrow-${attributes.popper?.['data-popper-placement'] ?? ''}`}
|
||||
ref={setArrowElement as any}
|
||||
style={styles.arrow}
|
||||
{...attributes.arrow}
|
||||
/>
|
||||
{!hideArrow && (
|
||||
<Arrow
|
||||
className={`arrow-${attributes.popper?.['data-popper-placement'] ?? ''}`}
|
||||
ref={setArrowElement as any}
|
||||
style={styles.arrow}
|
||||
{...attributes.arrow}
|
||||
/>
|
||||
)}
|
||||
</PopoverContainer>
|
||||
</Portal>
|
||||
</>
|
||||
|
||||
@@ -121,7 +121,7 @@ export default function ClaimPopup() {
|
||||
</ThemedText.DeprecatedSubHeader>
|
||||
</AutoColumn>
|
||||
<AutoColumn style={{ zIndex: 10 }} justify="center">
|
||||
<ButtonPrimary padding="8px" $borderRadius="8px" width={'fit-content'} onClick={handleToggleSelfClaimModal}>
|
||||
<ButtonPrimary padding="8px" $borderRadius="8px" width="fit-content" onClick={handleToggleSelfClaimModal}>
|
||||
<Trans>Claim your UNI tokens</Trans>
|
||||
</ButtonPrimary>
|
||||
</AutoColumn>
|
||||
|
||||
@@ -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 '.'
|
||||
@@ -143,7 +143,7 @@ export default function V2PositionCard({ pair, border, stakedBalance }: Position
|
||||
</RowFixed>
|
||||
{token0Deposited ? (
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft="6px">
|
||||
{token0Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency0} />
|
||||
@@ -161,7 +161,7 @@ export default function V2PositionCard({ pair, border, stakedBalance }: Position
|
||||
</RowFixed>
|
||||
{token1Deposited ? (
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft="6px">
|
||||
{token1Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency1} />
|
||||
|
||||
@@ -18,11 +18,11 @@ import { ExternalLink, ThemedText } from '../../theme'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
import { unwrappedToken } from '../../utils/unwrappedToken'
|
||||
import { ButtonEmpty, ButtonPrimary, ButtonSecondary } from '../Button'
|
||||
import { GreyCard, LightCard } from '../Card'
|
||||
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'
|
||||
|
||||
@@ -78,7 +78,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
|
||||
return (
|
||||
<>
|
||||
{userPoolBalance && JSBI.greaterThan(userPoolBalance.quotient, JSBI.BigInt(0)) ? (
|
||||
<GreyCard border={border}>
|
||||
<GrayCard border={border}>
|
||||
<AutoColumn gap="12px">
|
||||
<FixedHeightRow>
|
||||
<RowFixed>
|
||||
@@ -115,7 +115,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
|
||||
</Text>
|
||||
{token0Deposited ? (
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft="6px">
|
||||
{token0Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
@@ -129,7 +129,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
|
||||
</Text>
|
||||
{token1Deposited ? (
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft="6px">
|
||||
{token1Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
@@ -139,7 +139,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
|
||||
</FixedHeightRow>
|
||||
</AutoColumn>
|
||||
</AutoColumn>
|
||||
</GreyCard>
|
||||
</GrayCard>
|
||||
) : (
|
||||
<LightCard>
|
||||
<ThemedText.DeprecatedSubHeader style={{ textAlign: 'center' }}>
|
||||
@@ -254,7 +254,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
</RowFixed>
|
||||
{token0Deposited ? (
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft="6px">
|
||||
{token0Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency0} />
|
||||
@@ -272,7 +272,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
</RowFixed>
|
||||
{token1Deposited ? (
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft="6px">
|
||||
{token1Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency1} />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import PositionListItem from 'components/PositionListItem'
|
||||
import Toggle from 'components/Toggle'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { MEDIA_WIDTHS } from 'theme'
|
||||
@@ -10,7 +9,8 @@ const DesktopHeader = styled.div`
|
||||
display: none;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
padding: 8px;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
align-items: center;
|
||||
@@ -25,12 +25,14 @@ const DesktopHeader = styled.div`
|
||||
|
||||
const MobileHeader = styled.div`
|
||||
font-weight: medium;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
padding: 8px;
|
||||
font-weight: 500;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
display: none;
|
||||
@@ -38,8 +40,8 @@ const MobileHeader = styled.div`
|
||||
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.deprecated_upToExtraSmall}px) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -49,16 +51,12 @@ const ToggleWrap = styled.div`
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const ToggleLabel = styled.div`
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
margin-right: 10px;
|
||||
`
|
||||
|
||||
const MobileTogglePosition = styled.div`
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.deprecated_upToExtraSmall}px) {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
}
|
||||
const ToggleLabel = styled.button`
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
font-size: 1rem;
|
||||
`
|
||||
|
||||
type PositionListProps = React.PropsWithChildren<{
|
||||
@@ -79,34 +77,26 @@ export default function PositionList({
|
||||
<Trans>Your positions</Trans>
|
||||
{positions && ' (' + positions.length + ')'}
|
||||
</div>
|
||||
<ToggleWrap>
|
||||
<ToggleLabel>
|
||||
<Trans>Show closed positions</Trans>
|
||||
</ToggleLabel>
|
||||
<Toggle
|
||||
id="desktop-hide-closed-positions"
|
||||
isActive={!userHideClosedPositions}
|
||||
toggle={() => {
|
||||
setUserHideClosedPositions(!userHideClosedPositions)
|
||||
}}
|
||||
/>
|
||||
</ToggleWrap>
|
||||
|
||||
<ToggleLabel
|
||||
id="desktop-hide-closed-positions"
|
||||
onClick={() => {
|
||||
setUserHideClosedPositions(!userHideClosedPositions)
|
||||
}}
|
||||
>
|
||||
{userHideClosedPositions ? <Trans>Show closed positions</Trans> : <Trans>Hide closed positions</Trans>}
|
||||
</ToggleLabel>
|
||||
</DesktopHeader>
|
||||
<MobileHeader>
|
||||
<Trans>Your positions</Trans>
|
||||
<ToggleWrap>
|
||||
<ToggleLabel>
|
||||
<Trans>Show closed positions</Trans>
|
||||
<ToggleLabel
|
||||
onClick={() => {
|
||||
setUserHideClosedPositions(!userHideClosedPositions)
|
||||
}}
|
||||
>
|
||||
{userHideClosedPositions ? <Trans>Show closed positions</Trans> : <Trans>Hide closed positions</Trans>}
|
||||
</ToggleLabel>
|
||||
<MobileTogglePosition>
|
||||
<Toggle
|
||||
id="mobile-hide-closed-positions"
|
||||
isActive={!userHideClosedPositions}
|
||||
toggle={() => {
|
||||
setUserHideClosedPositions(!userHideClosedPositions)
|
||||
}}
|
||||
/>
|
||||
</MobileTogglePosition>
|
||||
</ToggleWrap>
|
||||
</MobileHeader>
|
||||
{positions.map((p) => {
|
||||
|
||||
@@ -23,29 +23,22 @@ import { DAI, USDC_MAINNET, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../co
|
||||
|
||||
const LinkRow = styled(Link)`
|
||||
align-items: center;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
justify-content: space-between;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
margin: 8px 0;
|
||||
padding: 16px;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
|
||||
&:last-of-type {
|
||||
margin: 8px 0 0 0;
|
||||
}
|
||||
& > div:not(:first-child) {
|
||||
text-align: center;
|
||||
}
|
||||
:hover {
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
background-color: ${({ theme }) => theme.hoverDefault};
|
||||
}
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
@@ -54,7 +47,7 @@ const LinkRow = styled(Link)`
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
flex-direction: column;
|
||||
row-gap: 12px;
|
||||
row-gap: 8px;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -74,29 +67,17 @@ const RangeLineItem = styled(DataLineItem)`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
border-radius: 12px;
|
||||
padding: 8px 0;
|
||||
`};
|
||||
`
|
||||
|
||||
const DoubleArrow = styled.span`
|
||||
margin: 0 2px;
|
||||
color: ${({ theme }) => theme.deprecated_text3};
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
margin: 4px;
|
||||
padding: 20px;
|
||||
`};
|
||||
`
|
||||
|
||||
const RangeText = styled.span`
|
||||
/* background-color: ${({ theme }) => theme.deprecated_bg2}; */
|
||||
padding: 0.25rem 0.5rem;
|
||||
padding: 0.25rem 0.25rem;
|
||||
border-radius: 8px;
|
||||
`
|
||||
|
||||
@@ -123,7 +104,7 @@ const DataText = styled.div`
|
||||
font-size: 18px;
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
font-size: 14px;
|
||||
font-size: 18px;
|
||||
`};
|
||||
`
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import Card, { DarkGreyCard } from 'components/Card'
|
||||
import Card, { DarkGrayCard } from 'components/Card'
|
||||
import Row, { AutoRow, RowBetween } from 'components/Row'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { ArrowDown, Info, X } from 'react-feather'
|
||||
@@ -124,11 +124,11 @@ export function PrivacyPolicy() {
|
||||
<AutoColumn gap="16px">
|
||||
<AutoColumn gap="8px" style={{ width: '100%' }}>
|
||||
<StyledExternalCard>
|
||||
<ExternalLink href={'https://uniswap.org/terms-of-service'}>
|
||||
<ExternalLink href="https://uniswap.org/terms-of-service">
|
||||
<RowBetween>
|
||||
<AutoRow gap="4px">
|
||||
<Info size={20} />
|
||||
<ThemedText.DeprecatedMain fontSize={14} color={'deprecated_primaryText1'}>
|
||||
<ThemedText.DeprecatedMain fontSize={14} color="deprecated_primaryText1">
|
||||
<Trans>Uniswap Labs' Terms of Service</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
</AutoRow>
|
||||
@@ -137,12 +137,12 @@ export function PrivacyPolicy() {
|
||||
</ExternalLink>
|
||||
</StyledExternalCard>
|
||||
<StyledExternalCard>
|
||||
<ExternalLink href={'https://uniswap.org/disclaimer/'}>
|
||||
<ExternalLink href="https://uniswap.org/privacy-policy/">
|
||||
<RowBetween>
|
||||
<AutoRow gap="4px">
|
||||
<Info size={20} />
|
||||
<ThemedText.DeprecatedMain fontSize={14} color={'deprecated_primaryText1'}>
|
||||
<Trans>Protocol Disclaimer</Trans>
|
||||
<ThemedText.DeprecatedMain fontSize={14} color="deprecated_primaryText1">
|
||||
<Trans>Privacy Policy</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
</AutoRow>
|
||||
<StyledLinkOut size={20} />
|
||||
@@ -155,17 +155,17 @@ export function PrivacyPolicy() {
|
||||
</ThemedText.DeprecatedMain>
|
||||
<AutoColumn gap="12px">
|
||||
{EXTERNAL_APIS.map(({ name, description }, i) => (
|
||||
<DarkGreyCard key={i}>
|
||||
<DarkGrayCard key={i}>
|
||||
<AutoColumn gap="8px">
|
||||
<AutoRow gap="4px">
|
||||
<Info size={18} />
|
||||
<ThemedText.DeprecatedMain fontSize={14} color={'deprecated_text1'}>
|
||||
<ThemedText.DeprecatedMain fontSize={14} color="deprecated_text1">
|
||||
{name}
|
||||
</ThemedText.DeprecatedMain>
|
||||
</AutoRow>
|
||||
<ThemedText.DeprecatedMain fontSize={14}>{description}</ThemedText.DeprecatedMain>
|
||||
</AutoColumn>
|
||||
</DarkGreyCard>
|
||||
</DarkGrayCard>
|
||||
))}
|
||||
<ThemedText.DeprecatedBody fontSize={12}>
|
||||
<Row justify="center" marginBottom="1rem">
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function ProgressCircles({ steps, disabled = false, ...rest }: Pr
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<Wrapper justify={'center'} {...rest}>
|
||||
<Wrapper justify="center" {...rest}>
|
||||
<Grouping>
|
||||
{steps.map((step, i) => {
|
||||
return (
|
||||
|
||||
@@ -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'
|
||||
@@ -99,9 +99,9 @@ export default function RoutingDiagram({
|
||||
<Wrapper>
|
||||
{routes.map((entry, index) => (
|
||||
<RouteContainerRow key={index}>
|
||||
<CurrencyLogo currency={tokenIn} size={'20px'} />
|
||||
<CurrencyLogo currency={tokenIn} size="20px" />
|
||||
<Route entry={entry} />
|
||||
<CurrencyLogo currency={tokenOut} size={'20px'} />
|
||||
<CurrencyLogo currency={tokenOut} size="20px" />
|
||||
</RouteContainerRow>
|
||||
))}
|
||||
</Wrapper>
|
||||
@@ -117,7 +117,7 @@ function Route({ entry: { percent, path, protocol } }: { entry: RoutingDiagramEn
|
||||
<OpaqueBadge>
|
||||
{protocol === Protocol.MIXED ? (
|
||||
<MixedProtocolBadge>
|
||||
<BadgeText fontSize={12}>{'V3 + V2'}</BadgeText>
|
||||
<BadgeText fontSize={12}>V3 + V2</BadgeText>
|
||||
</MixedProtocolBadge>
|
||||
) : (
|
||||
<ProtocolBadge>
|
||||
|
||||
@@ -110,7 +110,7 @@ exports[`renders multi route 1`] = `
|
||||
}
|
||||
|
||||
.c6 path {
|
||||
stroke: #99A1BD;
|
||||
stroke: #98A1C0;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
@@ -129,9 +129,9 @@ exports[`renders multi route 1`] = `
|
||||
}
|
||||
|
||||
.c9 {
|
||||
background-color: #C9D0E7;
|
||||
background-color: #B8C0DC;
|
||||
border-radius: 4px;
|
||||
color: #5E6887;
|
||||
color: #7780A0;
|
||||
font-size: 10px;
|
||||
padding: 2px 4px;
|
||||
z-index: 1021;
|
||||
@@ -348,7 +348,7 @@ exports[`renders single route 1`] = `
|
||||
}
|
||||
|
||||
.c6 path {
|
||||
stroke: #99A1BD;
|
||||
stroke: #98A1C0;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
@@ -367,9 +367,9 @@ exports[`renders single route 1`] = `
|
||||
}
|
||||
|
||||
.c9 {
|
||||
background-color: #C9D0E7;
|
||||
background-color: #B8C0DC;
|
||||
border-radius: 4px;
|
||||
color: #5E6887;
|
||||
color: #7780A0;
|
||||
font-size: 10px;
|
||||
padding: 2px 4px;
|
||||
z-index: 1021;
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import { AlertCircle, ArrowLeft } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { CloseIcon, ThemedText } from 'theme'
|
||||
|
||||
import TokenImportCard from './TokenImportCard'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`
|
||||
const Button = styled(ButtonPrimary)`
|
||||
margin-top: 1em;
|
||||
padding: 10px 1em;
|
||||
`
|
||||
const Content = styled.div`
|
||||
padding: 1em;
|
||||
`
|
||||
const Copy = styled(ThemedText.DeprecatedBody)`
|
||||
text-align: center;
|
||||
margin: 0 2em 1em !important;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
`
|
||||
const Header = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
`
|
||||
const Icon = styled(AlertCircle)`
|
||||
stroke: ${({ theme }) => theme.deprecated_text2};
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
`
|
||||
interface BlockedTokenProps {
|
||||
onBack: (() => void) | undefined
|
||||
onDismiss: (() => void) | undefined
|
||||
blockedTokens: Token[]
|
||||
}
|
||||
|
||||
const BlockedToken = ({ onBack, onDismiss, blockedTokens }: BlockedTokenProps) => (
|
||||
<Wrapper>
|
||||
<Header>
|
||||
{onBack ? <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} /> : <div />}
|
||||
<ThemedText.DeprecatedMediumHeader>
|
||||
<Trans>Token not supported</Trans>
|
||||
</ThemedText.DeprecatedMediumHeader>
|
||||
{onDismiss ? <CloseIcon onClick={onDismiss} /> : <div />}
|
||||
</Header>
|
||||
<Icon />
|
||||
<Content>
|
||||
<Copy>
|
||||
<Trans>This token is not supported in the Uniswap Labs app</Trans>
|
||||
</Copy>
|
||||
<TokenImportCard token={blockedTokens[0]} />
|
||||
<Button disabled>
|
||||
<Trans>Import</Trans>
|
||||
</Button>
|
||||
</Content>
|
||||
</Wrapper>
|
||||
)
|
||||
export default BlockedToken
|
||||
@@ -1,12 +1,12 @@
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { ElementName, Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import { getTokenAddress } from 'analytics/utils'
|
||||
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'
|
||||
import { getTokenAddress } from 'lib/utils/analytics'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { currencyId } from 'utils/currencyId'
|
||||
@@ -69,7 +69,7 @@ export default function CommonBases({
|
||||
|
||||
return (
|
||||
<TraceEvent
|
||||
events={[Event.onClick, Event.onKeyPress]}
|
||||
events={[BrowserEvent.onClick, BrowserEvent.onKeyPress]}
|
||||
name={EventName.TOKEN_SELECTED}
|
||||
properties={formatAnalyticsEventProperties(currency, searchQuery, isAddressSearch)}
|
||||
element={ElementName.COMMON_BASES_CURRENCY_BUTTON}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
exports[`renders currency rows correctly when currencies list is non-empty 1`] = `
|
||||
<DocumentFragment>
|
||||
.c9 {
|
||||
color: #99A1BD;
|
||||
.c10 {
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
.c8 {
|
||||
margin-left: 4px;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
@@ -18,10 +18,10 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
.c9 {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
color: #99A1BD;
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
@@ -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;
|
||||
@@ -90,7 +90,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
}
|
||||
|
||||
.c3:hover {
|
||||
background-color: #C9D0E714;
|
||||
background-color: #B8C0DC14;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
@@ -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-1j6a53a"
|
||||
>
|
||||
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-1j6a53a"
|
||||
>
|
||||
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-1j6a53a"
|
||||
>
|
||||
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}`
|
||||
@@ -52,8 +52,8 @@ it('renders loading rows when isLoading is true', () => {
|
||||
showImportView={noOp}
|
||||
setImportToken={noOp}
|
||||
isLoading={true}
|
||||
searchQuery={''}
|
||||
isAddressSearch={''}
|
||||
searchQuery=""
|
||||
isAddressSearch=""
|
||||
/>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
@@ -70,8 +70,8 @@ it('renders currency rows correctly when currencies list is non-empty', () => {
|
||||
showImportView={noOp}
|
||||
setImportToken={noOp}
|
||||
isLoading={false}
|
||||
searchQuery={''}
|
||||
isAddressSearch={''}
|
||||
searchQuery=""
|
||||
isAddressSearch=""
|
||||
/>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
|
||||
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ElementName, Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
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> }) {
|
||||
@@ -133,7 +130,7 @@ export function CurrencyRow({
|
||||
// only show add or remove buttons if not on selected list
|
||||
return (
|
||||
<TraceEvent
|
||||
events={[Event.onClick, Event.onKeyPress]}
|
||||
events={[BrowserEvent.onClick, BrowserEvent.onKeyPress]}
|
||||
name={EventName.TOKEN_SELECTED}
|
||||
properties={{ is_imported_by_user: customAdded, ...eventProperties }}
|
||||
element={ElementName.TOKEN_SELECTOR_ROW}
|
||||
@@ -151,17 +148,18 @@ export function CurrencyRow({
|
||||
<Column>
|
||||
<CurrencyLogo
|
||||
currency={currency}
|
||||
size={'36px'}
|
||||
size="36px"
|
||||
style={{ opacity: isBlockedToken ? blockedTokenOpacity : '1' }}
|
||||
/>
|
||||
</Column>
|
||||
<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}>
|
||||
<ThemedText.DeprecatedDarkGray ml="0px" fontSize="12px" fontWeight={300}>
|
||||
{currency.symbol}
|
||||
</ThemedText.DeprecatedDarkGray>
|
||||
</AutoColumn>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { Trace } from '@uniswap/analytics'
|
||||
import { EventName, ModalName } from '@uniswap/analytics-events'
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { EventName, ModalName } from 'analytics/constants'
|
||||
import { Trace } from 'analytics/Trace'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
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 ListLogo from 'components/ListLogo'
|
||||
import { AutoRow, RowFixed } from 'components/Row'
|
||||
import { useIsTokenActive, useIsUserAddedToken } from 'hooks/Tokens'
|
||||
import { CSSProperties } from 'react'
|
||||
import { CheckCircle } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
|
||||
|
||||
const TokenSection = styled.div<{ dim?: boolean }>`
|
||||
padding: 4px 20px;
|
||||
height: 56px;
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(auto, 1fr) auto;
|
||||
grid-gap: 16px;
|
||||
align-items: center;
|
||||
|
||||
opacity: ${({ dim }) => (dim ? '0.4' : '1')};
|
||||
`
|
||||
|
||||
const CheckIcon = styled(CheckCircle)`
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-right: 6px;
|
||||
stroke: ${({ theme }) => theme.deprecated_green1};
|
||||
`
|
||||
|
||||
const NameOverflow = styled.div`
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 140px;
|
||||
font-size: 12px;
|
||||
`
|
||||
|
||||
export default function ImportRow({
|
||||
token,
|
||||
style,
|
||||
dim,
|
||||
showImportView,
|
||||
setImportToken,
|
||||
}: {
|
||||
token: Token
|
||||
style?: CSSProperties
|
||||
dim?: boolean
|
||||
showImportView: () => void
|
||||
setImportToken: (token: Token) => void
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
|
||||
// check if already active on list or local storage tokens
|
||||
const isAdded = useIsUserAddedToken(token)
|
||||
const isActive = useIsTokenActive(token)
|
||||
|
||||
const list = token instanceof WrappedTokenInfo ? token.list : undefined
|
||||
|
||||
return (
|
||||
<TokenSection tabIndex={0} style={style}>
|
||||
<CurrencyLogo currency={token} size={'24px'} style={{ opacity: dim ? '0.6' : '1' }} />
|
||||
<AutoColumn gap="4px" style={{ opacity: dim ? '0.6' : '1' }}>
|
||||
<AutoRow>
|
||||
<ThemedText.DeprecatedBody fontWeight={500}>{token.symbol}</ThemedText.DeprecatedBody>
|
||||
<ThemedText.DeprecatedDarkGray ml="8px" fontWeight={300}>
|
||||
<NameOverflow title={token.name}>{token.name}</NameOverflow>
|
||||
</ThemedText.DeprecatedDarkGray>
|
||||
</AutoRow>
|
||||
{list && list.logoURI && (
|
||||
<RowFixed>
|
||||
<ThemedText.DeprecatedSmall mr="4px" color={theme.deprecated_text3}>
|
||||
<Trans>via {list.name} </Trans>
|
||||
</ThemedText.DeprecatedSmall>
|
||||
<ListLogo logoURI={list.logoURI} size="12px" />
|
||||
</RowFixed>
|
||||
)}
|
||||
</AutoColumn>
|
||||
{!isActive && !isAdded ? (
|
||||
<ButtonPrimary
|
||||
width="fit-content"
|
||||
padding="6px 12px"
|
||||
fontWeight={500}
|
||||
fontSize="14px"
|
||||
onClick={() => {
|
||||
setImportToken && setImportToken(token)
|
||||
showImportView()
|
||||
}}
|
||||
>
|
||||
<Trans>Import</Trans>
|
||||
</ButtonPrimary>
|
||||
) : (
|
||||
<RowFixed style={{ minWidth: 'fit-content' }}>
|
||||
<CheckIcon />
|
||||
<ThemedText.DeprecatedMain color={theme.deprecated_green1}>
|
||||
<Trans>Active</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
</RowFixed>
|
||||
)}
|
||||
</TokenSection>
|
||||
)
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
import { Plural, Trans } from '@lingui/macro'
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { ElementName, Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { SectionBreak } from 'components/swap/styleds'
|
||||
import { useUnsupportedTokens } from 'hooks/Tokens'
|
||||
import { AlertCircle, ArrowLeft } from 'react-feather'
|
||||
import { useAddUserToken } from 'state/user/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { CloseIcon, ThemedText } from 'theme'
|
||||
|
||||
import BlockedToken from './BlockedToken'
|
||||
import { PaddedColumn } from './styleds'
|
||||
import TokenImportCard from './TokenImportCard'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
`
|
||||
|
||||
interface ImportProps {
|
||||
tokens: Token[]
|
||||
list?: TokenList
|
||||
onBack?: () => void
|
||||
onDismiss?: () => void
|
||||
handleCurrencySelect?: (currency: Currency) => void
|
||||
}
|
||||
|
||||
const formatAnalyticsEventProperties = (tokens: Token[]) => ({
|
||||
token_symbols: tokens.map((token) => token?.symbol),
|
||||
token_addresses: tokens.map((token) => token?.address),
|
||||
token_chain_ids: tokens.map((token) => token?.chainId),
|
||||
})
|
||||
|
||||
export function ImportToken(props: ImportProps) {
|
||||
const { tokens, list, onBack, onDismiss, handleCurrencySelect } = props
|
||||
const theme = useTheme()
|
||||
|
||||
const addToken = useAddUserToken()
|
||||
|
||||
const unsupportedTokens = useUnsupportedTokens()
|
||||
const unsupportedSet = new Set(Object.keys(unsupportedTokens))
|
||||
const intersection = new Set(tokens.filter((token) => unsupportedSet.has(token.address)))
|
||||
if (intersection.size > 0) {
|
||||
return <BlockedToken onBack={onBack} onDismiss={onDismiss} blockedTokens={Array.from(intersection)} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<PaddedColumn gap="14px" style={{ width: '100%', flex: '1 1' }}>
|
||||
<RowBetween>
|
||||
{onBack ? <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} /> : <div />}
|
||||
<ThemedText.DeprecatedMediumHeader>
|
||||
<Plural value={tokens.length} _1="Import token" other="Import tokens" />
|
||||
</ThemedText.DeprecatedMediumHeader>
|
||||
{onDismiss ? <CloseIcon onClick={onDismiss} /> : <div />}
|
||||
</RowBetween>
|
||||
</PaddedColumn>
|
||||
<SectionBreak />
|
||||
<AutoColumn gap="md" style={{ marginBottom: '32px', padding: '1rem' }}>
|
||||
<AutoColumn justify="center" style={{ textAlign: 'center', gap: '16px', padding: '1rem' }}>
|
||||
<AlertCircle size={48} stroke={theme.deprecated_text2} strokeWidth={1} />
|
||||
<ThemedText.DeprecatedBody fontWeight={400} fontSize={16}>
|
||||
<Trans>
|
||||
This token doesn't appear on the active token list(s). Make sure this is the token that you want to
|
||||
trade.
|
||||
</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoColumn>
|
||||
{tokens.map((token) => (
|
||||
<TokenImportCard token={token} list={list} key={'import' + token.address} />
|
||||
))}
|
||||
<TraceEvent
|
||||
events={[Event.onClick]}
|
||||
name={EventName.TOKEN_IMPORTED}
|
||||
properties={formatAnalyticsEventProperties(tokens)}
|
||||
element={ElementName.IMPORT_TOKEN_BUTTON}
|
||||
>
|
||||
<ButtonPrimary
|
||||
altDisabledStyle={true}
|
||||
$borderRadius="20px"
|
||||
padding="10px 1rem"
|
||||
onClick={() => {
|
||||
tokens.map((token) => addToken(token))
|
||||
handleCurrencySelect && handleCurrencySelect(tokens[0])
|
||||
}}
|
||||
className=".token-dismiss-button"
|
||||
>
|
||||
<Trans>Import</Trans>
|
||||
</ButtonPrimary>
|
||||
</TraceEvent>
|
||||
</AutoColumn>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Card from 'components/Card'
|
||||
import Column from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import Row, { RowBetween, RowFixed } from 'components/Row'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { ChangeEvent, RefObject, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { useRemoveUserAddedToken, useUserAddedTokens } from 'state/user/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ButtonText, ExternalLink, ExternalLinkIcon, ThemedText, TrashIcon } from 'theme'
|
||||
import { isAddress } from 'utils'
|
||||
|
||||
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
|
||||
import { CurrencyModalView } from './CurrencySearchModal'
|
||||
import ImportRow from './ImportRow'
|
||||
import { PaddedColumn, SearchInput, Separator } from './styleds'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
height: calc(100% - 60px);
|
||||
position: relative;
|
||||
padding-bottom: 80px;
|
||||
`
|
||||
|
||||
const Footer = styled.div`
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
border-radius: 20px;
|
||||
border-top-right-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-top: 1px solid ${({ theme }) => theme.deprecated_bg3};
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
`
|
||||
|
||||
export default function ManageTokens({
|
||||
setModalView,
|
||||
setImportToken,
|
||||
}: {
|
||||
setModalView: (view: CurrencyModalView) => void
|
||||
setImportToken: (token: Token) => void
|
||||
}) {
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState<string>('')
|
||||
const theme = useTheme()
|
||||
|
||||
// manage focus on modal show
|
||||
const inputRef = useRef<HTMLInputElement>()
|
||||
const handleInput = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
const input = event.target.value
|
||||
const checksummedInput = isAddress(input)
|
||||
setSearchQuery(checksummedInput || input)
|
||||
}, [])
|
||||
|
||||
// if they input an address, use it
|
||||
const isAddressSearch = isAddress(searchQuery)
|
||||
const searchToken = useToken(searchQuery)
|
||||
|
||||
// all tokens for local lisr
|
||||
const userAddedTokens: Token[] = useUserAddedTokens()
|
||||
const removeToken = useRemoveUserAddedToken()
|
||||
|
||||
const handleRemoveAll = useCallback(() => {
|
||||
if (chainId && userAddedTokens) {
|
||||
userAddedTokens.map((token) => {
|
||||
return removeToken(chainId, token.address)
|
||||
})
|
||||
}
|
||||
}, [removeToken, userAddedTokens, chainId])
|
||||
|
||||
const tokenList = useMemo(() => {
|
||||
return (
|
||||
chainId &&
|
||||
userAddedTokens.map((token) => (
|
||||
<RowBetween key={token.address} width="100%">
|
||||
<RowFixed>
|
||||
<CurrencyLogo currency={token} size={'20px'} />
|
||||
<ExternalLink href={getExplorerLink(chainId, token.address, ExplorerDataType.ADDRESS)}>
|
||||
<ThemedText.DeprecatedMain ml={'10px'} fontWeight={600}>
|
||||
{token.symbol}
|
||||
</ThemedText.DeprecatedMain>
|
||||
</ExternalLink>
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
<TrashIcon onClick={() => removeToken(chainId, token.address)} />
|
||||
<ExternalLinkIcon href={getExplorerLink(chainId, token.address, ExplorerDataType.ADDRESS)} />
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
))
|
||||
)
|
||||
}, [userAddedTokens, chainId, removeToken])
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Column style={{ width: '100%', height: '100%', flex: '1 1' }}>
|
||||
<PaddedColumn gap="14px">
|
||||
<Row>
|
||||
<SearchInput
|
||||
type="text"
|
||||
id="token-search-input"
|
||||
placeholder={'0x0000'}
|
||||
value={searchQuery}
|
||||
autoComplete="off"
|
||||
ref={inputRef as RefObject<HTMLInputElement>}
|
||||
onChange={handleInput}
|
||||
/>
|
||||
</Row>
|
||||
{searchQuery !== '' && !isAddressSearch && (
|
||||
<ThemedText.DeprecatedError error={true}>
|
||||
<Trans>Enter valid token address</Trans>
|
||||
</ThemedText.DeprecatedError>
|
||||
)}
|
||||
{searchToken && (
|
||||
<Card backgroundColor={theme.deprecated_bg2} padding="10px 0">
|
||||
<ImportRow
|
||||
token={searchToken}
|
||||
showImportView={() => setModalView(CurrencyModalView.importToken)}
|
||||
setImportToken={setImportToken}
|
||||
style={{ height: 'fit-content' }}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</PaddedColumn>
|
||||
<Separator />
|
||||
<PaddedColumn gap="lg" style={{ overflow: 'auto', marginBottom: '10px' }}>
|
||||
<RowBetween>
|
||||
<ThemedText.DeprecatedMain fontWeight={600}>
|
||||
<Trans>{userAddedTokens?.length} Custom Tokens</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
{userAddedTokens.length > 0 && (
|
||||
<ButtonText onClick={handleRemoveAll}>
|
||||
<ThemedText.DeprecatedBlue>
|
||||
<Trans>Clear all</Trans>
|
||||
</ThemedText.DeprecatedBlue>
|
||||
</ButtonText>
|
||||
)}
|
||||
</RowBetween>
|
||||
{tokenList}
|
||||
</PaddedColumn>
|
||||
</Column>
|
||||
<Footer>
|
||||
<ThemedText.DeprecatedDarkGray>
|
||||
<Trans>Tip: Custom tokens are stored locally in your browser</Trans>
|
||||
</ThemedText.DeprecatedDarkGray>
|
||||
</Footer>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Card from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import ListLogo from 'components/ListLogo'
|
||||
import { RowFixed } from 'components/Row'
|
||||
import { transparentize } from 'polished'
|
||||
import { AlertCircle } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
const WarningWrapper = styled(Card)<{ highWarning: boolean }>`
|
||||
background-color: ${({ theme, highWarning }) =>
|
||||
highWarning ? transparentize(0.8, theme.deprecated_red1) : transparentize(0.8, theme.deprecated_yellow2)};
|
||||
width: fit-content;
|
||||
`
|
||||
|
||||
const AddressText = styled(ThemedText.DeprecatedBlue)`
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
font-size: 10px;
|
||||
`}
|
||||
`
|
||||
interface TokenImportCardProps {
|
||||
list?: TokenList
|
||||
token: Token
|
||||
}
|
||||
const TokenImportCard = ({ list, token }: TokenImportCardProps) => {
|
||||
const theme = useTheme()
|
||||
const { chainId } = useWeb3React()
|
||||
return (
|
||||
<Card backgroundColor={theme.deprecated_bg2} padding="2rem">
|
||||
<AutoColumn gap="10px" justify="center">
|
||||
<CurrencyLogo currency={token} size={'32px'} />
|
||||
<AutoColumn gap="4px" justify="center">
|
||||
<ThemedText.DeprecatedBody ml="8px" mr="8px" fontWeight={500} fontSize={20}>
|
||||
{token.symbol}
|
||||
</ThemedText.DeprecatedBody>
|
||||
<ThemedText.DeprecatedDarkGray fontWeight={400} fontSize={14}>
|
||||
{token.name}
|
||||
</ThemedText.DeprecatedDarkGray>
|
||||
</AutoColumn>
|
||||
{chainId && (
|
||||
<ExternalLink href={getExplorerLink(chainId, token.address, ExplorerDataType.ADDRESS)}>
|
||||
<AddressText fontSize={12}>{token.address}</AddressText>
|
||||
</ExternalLink>
|
||||
)}
|
||||
{list !== undefined ? (
|
||||
<RowFixed>
|
||||
{list.logoURI && <ListLogo logoURI={list.logoURI} size="16px" />}
|
||||
<ThemedText.DeprecatedSmall ml="6px" fontSize={14} color={theme.deprecated_text3}>
|
||||
<Trans>via {list.name} token list</Trans>
|
||||
</ThemedText.DeprecatedSmall>
|
||||
</RowFixed>
|
||||
) : (
|
||||
<WarningWrapper $borderRadius="4px" padding="4px" highWarning={true}>
|
||||
<RowFixed>
|
||||
<AlertCircle stroke={theme.deprecated_red1} size="10px" />
|
||||
<ThemedText.DeprecatedBody color={theme.deprecated_red1} ml="4px" fontSize="10px" fontWeight={500}>
|
||||
<Trans>Unknown Source</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</RowFixed>
|
||||
</WarningWrapper>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default TokenImportCard
|
||||
@@ -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">
|
||||
@@ -164,7 +163,7 @@ export default function SettingsTab({ placeholderSlippage }: { placeholderSlippa
|
||||
</Text>
|
||||
<ButtonError
|
||||
error={true}
|
||||
padding={'12px'}
|
||||
padding="12px"
|
||||
onClick={() => {
|
||||
const confirmWord = t`confirm`
|
||||
if (window.prompt(t`Please type the word "${confirmWord}" to enable expert mode.`) === confirmWord) {
|
||||
|
||||
@@ -14,7 +14,7 @@ exports[`ResizableTextArea renders correctly 1`] = `
|
||||
background-color: #F5F6FC;
|
||||
-webkit-transition: color 300ms step-start;
|
||||
transition: color 300ms step-start;
|
||||
color: #0E111A;
|
||||
color: #0D111C;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
@@ -34,19 +34,19 @@ exports[`ResizableTextArea renders correctly 1`] = `
|
||||
}
|
||||
|
||||
.c0::-webkit-input-placeholder {
|
||||
color: #99A1BD;
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
.c0::-moz-placeholder {
|
||||
color: #99A1BD;
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
.c0:-ms-input-placeholder {
|
||||
color: #99A1BD;
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
.c0::placeholder {
|
||||
color: #99A1BD;
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
<textarea
|
||||
@@ -77,7 +77,7 @@ exports[`TextInput renders correctly 1`] = `
|
||||
background-color: #F5F6FC;
|
||||
-webkit-transition: color 300ms step-start;
|
||||
transition: color 300ms step-start;
|
||||
color: #0E111A;
|
||||
color: #0D111C;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
@@ -96,19 +96,19 @@ exports[`TextInput renders correctly 1`] = `
|
||||
}
|
||||
|
||||
.c0::-webkit-input-placeholder {
|
||||
color: #99A1BD;
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
.c0::-moz-placeholder {
|
||||
color: #99A1BD;
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
.c0:-ms-input-placeholder {
|
||||
color: #99A1BD;
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
.c0::placeholder {
|
||||
color: #99A1BD;
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
<div
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { ImportToken } from 'components/SearchModal/ImportToken'
|
||||
|
||||
import Modal from '../Modal'
|
||||
|
||||
export default function TokenWarningModal({
|
||||
isOpen,
|
||||
tokens,
|
||||
onConfirm,
|
||||
onDismiss,
|
||||
}: {
|
||||
isOpen: boolean
|
||||
tokens: Token[]
|
||||
onConfirm: () => void
|
||||
onDismiss: () => void
|
||||
}) {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={100}>
|
||||
<ImportToken tokens={tokens} handleCurrencySelect={onConfirm} />
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -99,10 +99,10 @@ export function AboutSection({ address, description, homepageUrl, twitterName }:
|
||||
<Trans>Links</Trans>
|
||||
</ThemedText.SubHeaderSmall>
|
||||
<ResourcesContainer>
|
||||
<Resource name={'Etherscan'} link={`https://etherscan.io/address/${address}`} />
|
||||
<Resource name={'More analytics'} link={`https://info.uniswap.org/#/tokens/${address}`} />
|
||||
{homepageUrl && <Resource name={'Website'} link={homepageUrl} />}
|
||||
{twitterName && <Resource name={'Twitter'} link={`https://twitter.com/${twitterName}`} />}
|
||||
<Resource name="Etherscan" link={`https://etherscan.io/address/${address}`} />
|
||||
<Resource name="More analytics" link={`https://info.uniswap.org/#/tokens/${address}`} />
|
||||
{homepageUrl && <Resource name="Website" link={homepageUrl} />}
|
||||
{twitterName && <Resource name="Twitter" link={`https://twitter.com/${twitterName}`} />}
|
||||
</ResourcesContainer>
|
||||
</AboutContainer>
|
||||
)
|
||||
|
||||
@@ -1,16 +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 { formatToDecimal } from 'analytics/utils'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
||||
import { CHAIN_ID_TO_BACKEND_NAME } from 'graphql/data/util'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
|
||||
import { useMemo } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { StyledInternalLink } from 'theme'
|
||||
import { currencyAmountToPreciseFloat, formatDollar } from 'utils/formatNumbers'
|
||||
|
||||
const BalancesCard = styled.div`
|
||||
box-shadow: ${({ theme }) => theme.shallowShadow};
|
||||
@@ -47,49 +42,27 @@ const BalanceRow = styled.div`
|
||||
`
|
||||
const BalanceItem = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const BalanceLink = styled(StyledInternalLink)`
|
||||
color: unset;
|
||||
`
|
||||
|
||||
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 chain = CHAIN_ID_TO_BACKEND_NAME[token.chainId].toLowerCase()
|
||||
const formattedBalance = formatCurrencyAmount(balance, NumberType.TokenNonTx)
|
||||
const formattedUsdValue = formatCurrencyAmount(useStablecoinValue(balance), NumberType.FiatTokenStats)
|
||||
|
||||
if (!account || !balance) return null
|
||||
return (
|
||||
<BalancesCard>
|
||||
<BalanceSection>
|
||||
<Trans>Your balance</Trans>
|
||||
<BalanceLink to={`/tokens/${chain}/${token.isNative ? NATIVE_CHAIN_ID : token.address}`}>
|
||||
<BalanceRow>
|
||||
<BalanceItem>
|
||||
<CurrencyLogo currency={token} />
|
||||
{formattedBalance} {token.symbol}
|
||||
</BalanceItem>
|
||||
<BalanceItem>{formattedUsdValue}</BalanceItem>
|
||||
</BalanceRow>
|
||||
</BalanceLink>
|
||||
<BalanceRow>
|
||||
<BalanceItem>
|
||||
<CurrencyLogo currency={token} />
|
||||
{formattedBalance} {token.symbol}
|
||||
</BalanceItem>
|
||||
<BalanceItem>{formattedUsdValue}</BalanceItem>
|
||||
</BalanceRow>
|
||||
</BalanceSection>
|
||||
</BalancesCard>
|
||||
)
|
||||
|
||||
@@ -1,112 +1,77 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, NativeCurrency, Token } from '@uniswap/sdk-core'
|
||||
import { ParentSize } from '@visx/responsive'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { TokenQueryData } from 'graphql/data/Token'
|
||||
import { PriceDurations } from 'graphql/data/TokenPrice'
|
||||
import { TopToken } from 'graphql/data/TopTokens'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
|
||||
import { ChartContainer, LoadingChart } from 'components/Tokens/TokenDetails/Skeleton'
|
||||
import { TokenPriceQuery, tokenPriceQuery } from 'graphql/data/TokenPrice'
|
||||
import { isPricePoint, PricePoint } from 'graphql/data/util'
|
||||
import { TimePeriod } from 'graphql/data/util'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
|
||||
import styled from 'styled-components/macro'
|
||||
import { textFadeIn } from 'theme/animations'
|
||||
import { startTransition, Suspense, useMemo, useState } from 'react'
|
||||
import { PreloadedQuery, usePreloadedQuery } from 'react-relay'
|
||||
|
||||
import { filterTimeAtom } from '../state'
|
||||
import { L2NetworkLogo, LogoContainer } from '../TokenTable/TokenRow'
|
||||
import PriceChart from './PriceChart'
|
||||
import ShareButton from './ShareButton'
|
||||
import TimePeriodSelector from './TimeSelector'
|
||||
|
||||
export const ChartHeader = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
gap: 4px;
|
||||
margin-bottom: 24px;
|
||||
`
|
||||
export const TokenInfoContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`
|
||||
export const ChartContainer = styled.div`
|
||||
display: flex;
|
||||
height: 436px;
|
||||
align-items: center;
|
||||
`
|
||||
export const TokenNameCell = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
align-items: center;
|
||||
${textFadeIn}
|
||||
`
|
||||
const TokenSymbol = styled.span`
|
||||
text-transform: uppercase;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
const TokenActions = styled.div`
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
function usePreloadedTokenPriceQuery(priceQueryReference: PreloadedQuery<TokenPriceQuery>): PricePoint[] | undefined {
|
||||
const queryData = usePreloadedQuery(tokenPriceQuery, priceQueryReference)
|
||||
|
||||
export function useTokenLogoURI(
|
||||
token: NonNullable<TokenQueryData> | NonNullable<TopToken>,
|
||||
nativeCurrency?: Token | NativeCurrency
|
||||
) {
|
||||
const chainId = CHAIN_NAME_TO_CHAIN_ID[token.chain]
|
||||
return [
|
||||
...useCurrencyLogoURIs(nativeCurrency),
|
||||
...useCurrencyLogoURIs({ ...token, chainId }),
|
||||
token.project?.logoUrl,
|
||||
][0]
|
||||
// Appends the current price to the end of the priceHistory array
|
||||
const priceHistory = useMemo(() => {
|
||||
const market = queryData.tokens?.[0]?.market
|
||||
const priceHistory = market?.priceHistory?.filter(isPricePoint)
|
||||
const currentPrice = market?.price?.value
|
||||
if (Array.isArray(priceHistory) && currentPrice !== undefined) {
|
||||
const timestamp = Date.now() / 1000
|
||||
return [...priceHistory, { timestamp, value: currentPrice }]
|
||||
}
|
||||
return priceHistory
|
||||
}, [queryData])
|
||||
|
||||
return priceHistory
|
||||
}
|
||||
|
||||
export default function ChartSection({
|
||||
token,
|
||||
currency,
|
||||
nativeCurrency,
|
||||
prices,
|
||||
priceQueryReference,
|
||||
refetchTokenPrices,
|
||||
}: {
|
||||
token: NonNullable<TokenQueryData>
|
||||
currency?: Currency | null
|
||||
nativeCurrency?: Token | NativeCurrency
|
||||
prices?: PriceDurations
|
||||
priceQueryReference: PreloadedQuery<TokenPriceQuery> | null | undefined
|
||||
refetchTokenPrices: RefetchPricesFunction
|
||||
}) {
|
||||
const chainId = CHAIN_NAME_TO_CHAIN_ID[token.chain]
|
||||
const L2Icon = getChainInfo(chainId)?.circleLogoUrl
|
||||
const timePeriod = useAtomValue(filterTimeAtom)
|
||||
|
||||
const logoSrc = useTokenLogoURI(token, nativeCurrency)
|
||||
if (!priceQueryReference) {
|
||||
return <LoadingChart />
|
||||
}
|
||||
|
||||
return (
|
||||
<ChartHeader>
|
||||
<TokenInfoContainer>
|
||||
<TokenNameCell>
|
||||
<LogoContainer>
|
||||
<CurrencyLogo
|
||||
src={logoSrc}
|
||||
size={'32px'}
|
||||
symbol={nativeCurrency?.symbol ?? token.symbol}
|
||||
currency={nativeCurrency ? undefined : currency}
|
||||
/>
|
||||
<L2NetworkLogo networkUrl={L2Icon} size={'16px'} />
|
||||
</LogoContainer>
|
||||
{nativeCurrency?.name ?? token.name ?? <Trans>Name not found</Trans>}
|
||||
<TokenSymbol>{nativeCurrency?.symbol ?? token.symbol ?? <Trans>Symbol not found</Trans>}</TokenSymbol>
|
||||
</TokenNameCell>
|
||||
<TokenActions>
|
||||
{token.name && token.symbol && token.address && <ShareButton token={token} isNative={!!nativeCurrency} />}
|
||||
</TokenActions>
|
||||
</TokenInfoContainer>
|
||||
<Suspense fallback={<LoadingChart />}>
|
||||
<ChartContainer>
|
||||
<ParentSize>
|
||||
{({ width }) => <PriceChart prices={prices ? prices?.[timePeriod] : null} width={width} height={436} />}
|
||||
</ParentSize>
|
||||
<Chart priceQueryReference={priceQueryReference} refetchTokenPrices={refetchTokenPrices} />
|
||||
</ChartContainer>
|
||||
</ChartHeader>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
export type RefetchPricesFunction = (t: TimePeriod) => void
|
||||
function Chart({
|
||||
priceQueryReference,
|
||||
refetchTokenPrices,
|
||||
}: {
|
||||
priceQueryReference: PreloadedQuery<TokenPriceQuery>
|
||||
refetchTokenPrices: RefetchPricesFunction
|
||||
}) {
|
||||
const prices = usePreloadedTokenPriceQuery(priceQueryReference)
|
||||
// Initializes time period to global & maintain separate time period for subsequent changes
|
||||
const [timePeriod, setTimePeriod] = useState(useAtomValue(filterTimeAtom))
|
||||
|
||||
return (
|
||||
<ChartContainer>
|
||||
<ParentSize>
|
||||
{({ width }) => <PriceChart prices={prices ?? null} width={width} height={436} timePeriod={timePeriod} />}
|
||||
</ParentSize>
|
||||
<TimePeriodSelector
|
||||
currentTimePeriod={timePeriod}
|
||||
onTimeChange={(t: TimePeriod) => {
|
||||
startTransition(() => refetchTokenPrices(t))
|
||||
setTimePeriod(t)
|
||||
}}
|
||||
/>
|
||||
</ChartContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,16 +1,15 @@
|
||||
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'
|
||||
import { GlyphCircle } from '@visx/glyph'
|
||||
import { Line } from '@visx/shape'
|
||||
import AnimatedInLineChart from 'components/Charts/AnimatedInLineChart'
|
||||
import { filterTimeAtom } from 'components/Tokens/state'
|
||||
import { bisect, curveCardinal, NumberValue, scaleLinear, timeDay, timeHour, timeMinute, timeMonth } from 'd3'
|
||||
import { PricePoint } from 'graphql/data/TokenPrice'
|
||||
import { PricePoint } from 'graphql/data/util'
|
||||
import { TimePeriod } from 'graphql/data/util'
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
import { useAtom } from 'jotai'
|
||||
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { ArrowDownRight, ArrowUpRight, TrendingUp } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
@@ -22,10 +21,6 @@ import {
|
||||
monthYearDayFormatter,
|
||||
weekFormatter,
|
||||
} from 'utils/formatChartTimes'
|
||||
import { formatDollar } from 'utils/formatNumbers'
|
||||
|
||||
import { MEDIUM_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { DISPLAYS, ORDERED_TIMES } from '../TokenTable/TimeSelector'
|
||||
|
||||
export const DATA_EMPTY = { value: 0, timestamp: 0 }
|
||||
|
||||
@@ -52,9 +47,9 @@ export function getDeltaArrow(delta: number | null | undefined) {
|
||||
if (delta === null || delta === undefined) {
|
||||
return null
|
||||
} else if (Math.sign(delta) < 0) {
|
||||
return <StyledDownArrow size={16} key="arrow-down" />
|
||||
return <StyledDownArrow size={24} key="arrow-down" />
|
||||
}
|
||||
return <StyledUpArrow size={16} key="arrow-up" />
|
||||
return <StyledUpArrow size={24} key="arrow-up" />
|
||||
}
|
||||
|
||||
export function formatDelta(delta: number | null | undefined) {
|
||||
@@ -86,46 +81,6 @@ const ArrowCell = styled.div`
|
||||
padding-left: 2px;
|
||||
display: flex;
|
||||
`
|
||||
export const TimeOptionsWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`
|
||||
export const TimeOptionsContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 4px;
|
||||
gap: 4px;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
border-radius: 16px;
|
||||
height: 40px;
|
||||
padding: 4px;
|
||||
width: fit-content;
|
||||
|
||||
@media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
border: none;
|
||||
}
|
||||
`
|
||||
const TimeButton = styled.button<{ active: boolean }>`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: ${({ theme, active }) => (active ? theme.backgroundInteractive : 'transparent')};
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 12px;
|
||||
line-height: 20px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: ${({ theme, active }) => (active ? theme.textPrimary : theme.textSecondary)};
|
||||
transition-duration: ${({ theme }) => theme.transition.duration.fast};
|
||||
:hover {
|
||||
${({ active, theme }) => !active && `opacity: ${theme.opacity.hover};`}
|
||||
}
|
||||
`
|
||||
|
||||
const margin = { top: 100, bottom: 48, crosshair: 72 }
|
||||
const timeOptionsHeight = 44
|
||||
@@ -134,10 +89,20 @@ interface PriceChartProps {
|
||||
width: number
|
||||
height: number
|
||||
prices: PricePoint[] | undefined | null
|
||||
timePeriod: TimePeriod
|
||||
}
|
||||
|
||||
export function PriceChart({ width, height, prices }: PriceChartProps) {
|
||||
const [timePeriod, setTimePeriod] = useAtom(filterTimeAtom)
|
||||
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()
|
||||
|
||||
@@ -271,7 +236,7 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
|
||||
return (
|
||||
<>
|
||||
<ChartHeader>
|
||||
<TokenPrice>{formatDollar({ num: displayPrice.value, isPrice: true })}</TokenPrice>
|
||||
<TokenPrice>{formatDisplayPrice(displayPrice.value)}</TokenPrice>
|
||||
<DeltaContainer>
|
||||
{formattedDelta}
|
||||
<ArrowCell>{arrow}</ArrowCell>
|
||||
@@ -282,9 +247,7 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
|
||||
width={width}
|
||||
height={graphHeight}
|
||||
message={
|
||||
prices === null ? (
|
||||
<Trans>Loading chart data</Trans>
|
||||
) : prices?.length === 0 ? (
|
||||
prices?.length === 0 ? (
|
||||
<Trans>This token doesn't have chart data because it hasn't been traded on Uniswap v3</Trans>
|
||||
) : (
|
||||
<Trans>Missing chart data</Trans>
|
||||
@@ -310,7 +273,7 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
|
||||
tickStroke={theme.backgroundOutline}
|
||||
tickLength={4}
|
||||
hideTicks={true}
|
||||
tickTransform={'translate(0 -5)'}
|
||||
tickTransform="translate(0 -5)"
|
||||
tickValues={ticks}
|
||||
top={graphHeight - 1}
|
||||
tickLabelProps={() => ({
|
||||
@@ -367,7 +330,7 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
|
||||
y={0}
|
||||
width={width}
|
||||
height={graphHeight}
|
||||
fill={'transparent'}
|
||||
fill="transparent"
|
||||
onTouchStart={handleHover}
|
||||
onTouchMove={handleHover}
|
||||
onMouseMove={handleHover}
|
||||
@@ -375,21 +338,6 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
<TimeOptionsWrapper>
|
||||
<TimeOptionsContainer>
|
||||
{ORDERED_TIMES.map((time) => (
|
||||
<TimeButton
|
||||
key={DISPLAYS[time]}
|
||||
active={timePeriod === time}
|
||||
onClick={() => {
|
||||
setTimePeriod(time)
|
||||
}}
|
||||
>
|
||||
{DISPLAYS[time]}
|
||||
</TimeButton>
|
||||
))}
|
||||
</TimeOptionsContainer>
|
||||
</TimeOptionsWrapper>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||