Compare commits
389 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4529e3cc88 | ||
|
|
4d47470f33 | ||
|
|
aedc020646 | ||
|
|
fd8085722e | ||
|
|
a60a85db54 | ||
|
|
ad2472eac6 | ||
|
|
f4d4acacae | ||
|
|
a5d7af192c | ||
|
|
21a2863ae3 | ||
|
|
1f871d4e73 | ||
|
|
3690936aff | ||
|
|
e95e2321b4 | ||
|
|
8b1bf09ff1 | ||
|
|
6383e9e4bf | ||
|
|
515ce9253d | ||
|
|
23ed384802 | ||
|
|
9ae31aa26e | ||
|
|
642c489240 | ||
|
|
778ea8ee42 | ||
|
|
36900fe6c6 | ||
|
|
40eb28f1e1 | ||
|
|
7c3ee78715 | ||
|
|
ccac51ec1f | ||
|
|
77747f9f6f | ||
|
|
0622ff30f6 | ||
|
|
982c99b07f | ||
|
|
484a7d49f6 | ||
|
|
082591d5dd | ||
|
|
aa03f97890 | ||
|
|
fd12a0d6e7 | ||
|
|
0176c74430 | ||
|
|
35613cc979 | ||
|
|
b0d71f10e9 | ||
|
|
1cfb3d8034 | ||
|
|
a650807c96 | ||
|
|
6efd7ca779 | ||
|
|
d658720505 | ||
|
|
bba40846e1 | ||
|
|
8fa6c87015 | ||
|
|
933d02b275 | ||
|
|
7738a6b9e0 | ||
|
|
2db4b1da3d | ||
|
|
06c85b744f | ||
|
|
aa29ea80a9 | ||
|
|
3613dc2d4a | ||
|
|
e22554b4c4 | ||
|
|
5f1625f5dc | ||
|
|
c1e6dd7335 | ||
|
|
45419c2739 | ||
|
|
c2342a86d6 | ||
|
|
504cd5b848 | ||
|
|
1faf13639c | ||
|
|
69c084ebe7 | ||
|
|
e5ac7e77da | ||
|
|
0614358a5e | ||
|
|
42784e6121 | ||
|
|
830500dc3b | ||
|
|
5af32592aa | ||
|
|
0f36a99e98 | ||
|
|
5fe9f3f6e8 | ||
|
|
167fff16a0 | ||
|
|
a6eff7823a | ||
|
|
1aba4fbcd7 | ||
|
|
714d215cda | ||
|
|
0f32ed34f7 | ||
|
|
3703e843f8 | ||
|
|
3c3158f443 | ||
|
|
f933e538e6 | ||
|
|
02b678b4a9 | ||
|
|
c25a2cfc00 | ||
|
|
a5d75cad5b | ||
|
|
40784963a5 | ||
|
|
6ca8e4f664 | ||
|
|
91c0580825 | ||
|
|
c518501e7b | ||
|
|
d59e4f334e | ||
|
|
4c3528a03d | ||
|
|
ed82f9ff8a | ||
|
|
dd5a22ce83 | ||
|
|
185c1f6772 | ||
|
|
93e6b65cb3 | ||
|
|
965a745d5e | ||
|
|
6d97590c0f | ||
|
|
8e7ab6f8c3 | ||
|
|
61729610c2 | ||
|
|
53860dd8e4 | ||
|
|
19028c1d82 | ||
|
|
6676d80707 | ||
|
|
f9f804c381 | ||
|
|
0a0b56b13d | ||
|
|
480f3f29f3 | ||
|
|
5f2072f449 | ||
|
|
4e144c7fb5 | ||
|
|
cfbb6a7129 | ||
|
|
61f03af20a | ||
|
|
5eb1274f97 | ||
|
|
9eb7d45aea | ||
|
|
2e1d4fdda1 | ||
|
|
e85b6e4cc6 | ||
|
|
9c334bc865 | ||
|
|
6a833fc740 | ||
|
|
80ed8eb6c2 | ||
|
|
a14d2df8e6 | ||
|
|
09511b06f2 | ||
|
|
9def686344 | ||
|
|
a88c083758 | ||
|
|
1a6cad4a8f | ||
|
|
505b3f2a20 | ||
|
|
1b5a145738 | ||
|
|
dbdd3a8e16 | ||
|
|
78e438294f | ||
|
|
2c014c6f38 | ||
|
|
7b3b7864ad | ||
|
|
e35eefbeb3 | ||
|
|
049a7d1d6a | ||
|
|
28d6c6454e | ||
|
|
f96ecb59eb | ||
|
|
a4c54ff953 | ||
|
|
d4cb32c4c3 | ||
|
|
efb76200ce | ||
|
|
a96bdaad04 | ||
|
|
b89860dc53 | ||
|
|
e640dccebd | ||
|
|
4710b832fa | ||
|
|
fb07666e23 | ||
|
|
22f64a98a1 | ||
|
|
50f6401a8a | ||
|
|
6480b947ef | ||
|
|
f41cbbb58f | ||
|
|
0cb098b9d4 | ||
|
|
76f5638583 | ||
|
|
c840de73db | ||
|
|
ab4271b2ff | ||
|
|
6f0586c596 | ||
|
|
9f33ed06dd | ||
|
|
85f4cec829 | ||
|
|
5e23501d58 | ||
|
|
6bc98363cc | ||
|
|
e15ccc3c79 | ||
|
|
e8880be1d9 | ||
|
|
0e9b05405d | ||
|
|
55d85d2623 | ||
|
|
7fb7517a1a | ||
|
|
1a6fe3c1a8 | ||
|
|
a49ff49185 | ||
|
|
ca1dc593d9 | ||
|
|
afacc4a348 | ||
|
|
91157b7a43 | ||
|
|
d5e676efb5 | ||
|
|
9ac83bea7e | ||
|
|
53b9a847ca | ||
|
|
04f9127961 | ||
|
|
5364eb5715 | ||
|
|
5126e24d19 | ||
|
|
a06bb79039 | ||
|
|
a446dc7f10 | ||
|
|
e903a335d6 | ||
|
|
3be5e9b5fe | ||
|
|
818e98328e | ||
|
|
aa3225c21c | ||
|
|
35d66f1e09 | ||
|
|
e156635f77 | ||
|
|
d2a97c62ed | ||
|
|
6aa999f713 | ||
|
|
0a3f8636e7 | ||
|
|
f5de7178d9 | ||
|
|
6c203cc990 | ||
|
|
3a40159147 | ||
|
|
c6f6bd446b | ||
|
|
416212be3b | ||
|
|
ce9f4525a3 | ||
|
|
23a250aae0 | ||
|
|
5d5e0f4596 | ||
|
|
d6199e0f61 | ||
|
|
a611cd03d8 | ||
|
|
bacc9667e8 | ||
|
|
41776655dc | ||
|
|
a350f59811 | ||
|
|
bdc336e188 | ||
|
|
8f44adb038 | ||
|
|
e4ae705eb1 | ||
|
|
1afc36454d | ||
|
|
1b65d6a1ce | ||
|
|
677fabf71a | ||
|
|
e138e0ecf2 | ||
|
|
60bd2db4c1 | ||
|
|
c7dd0f06e7 | ||
|
|
76dc71e442 | ||
|
|
88bb048920 | ||
|
|
1344e57c4d | ||
|
|
0b18bf0813 | ||
|
|
1195be5747 | ||
|
|
3aa98d626b | ||
|
|
362873d968 | ||
|
|
2fe5e487f4 | ||
|
|
5b418c68af | ||
|
|
18f64d6dac | ||
|
|
af2725ec25 | ||
|
|
6fd5dc0cfc | ||
|
|
ce5c0ff453 | ||
|
|
b419b85694 | ||
|
|
9d37b1bb55 | ||
|
|
57371fb47e | ||
|
|
e9dd0c90e0 | ||
|
|
f373a52da4 | ||
|
|
e4473e3007 | ||
|
|
da2f168eeb | ||
|
|
76e3caa659 | ||
|
|
7fa91d1442 | ||
|
|
d999388876 | ||
|
|
82c7657402 | ||
|
|
cb4101e606 | ||
|
|
92b0433184 | ||
|
|
21e5208d5c | ||
|
|
6b09aa9457 | ||
|
|
21594343b5 | ||
|
|
f29c9f8440 | ||
|
|
a6549dd340 | ||
|
|
9dabacef9b | ||
|
|
80921e782f | ||
|
|
183beddc72 | ||
|
|
7ce718df22 | ||
|
|
82fd79f0e2 | ||
|
|
d24c62ec4b | ||
|
|
2ea70d1894 | ||
|
|
448090b534 | ||
|
|
b1fd484894 | ||
|
|
aeb636cf8a | ||
|
|
4240908c4c | ||
|
|
d53ba64218 | ||
|
|
89ce5a9805 | ||
|
|
1790492f17 | ||
|
|
304cd72eed | ||
|
|
9dba68b34c | ||
|
|
588567b900 | ||
|
|
ef964ab120 | ||
|
|
f3d64b65da | ||
|
|
a9200b2c39 | ||
|
|
209fd33780 | ||
|
|
940c1dbb8e | ||
|
|
5dce68a62f | ||
|
|
9dd8ad1db6 | ||
|
|
0b40f72f0c | ||
|
|
875171e36a | ||
|
|
8e9a20a6c8 | ||
|
|
1a31560374 | ||
|
|
d528b28203 | ||
|
|
c1cb712087 | ||
|
|
ceed5e0b4c | ||
|
|
a449338252 | ||
|
|
9662344e24 | ||
|
|
1536e18784 | ||
|
|
c19431eb20 | ||
|
|
634e38529c | ||
|
|
083ec425d0 | ||
|
|
e8c09db146 | ||
|
|
9ab4d952ef | ||
|
|
48883cce8d | ||
|
|
3d820d39b7 | ||
|
|
4fdca48a97 | ||
|
|
7834ab7979 | ||
|
|
dee808cc57 | ||
|
|
4c23f62a24 | ||
|
|
93633a81a7 | ||
|
|
7465a0e999 | ||
|
|
6aac978754 | ||
|
|
62775f6091 | ||
|
|
97b3725c19 | ||
|
|
46563ee565 | ||
|
|
c68624e048 | ||
|
|
0fa1c5e6ea | ||
|
|
a7fd60987e | ||
|
|
51fe44d53a | ||
|
|
edf67e8e45 | ||
|
|
babfebccef | ||
|
|
ea02d9e919 | ||
|
|
bb3b236cd9 | ||
|
|
1d3fd512ae | ||
|
|
5dbd0ae782 | ||
|
|
b95621758c | ||
|
|
ce51ffae75 | ||
|
|
8cd32138ac | ||
|
|
785c2a6712 | ||
|
|
56a9952546 | ||
|
|
7059a12a25 | ||
|
|
da01254247 | ||
|
|
54a7b943ce | ||
|
|
ec6c843db6 | ||
|
|
7a6bb369d4 | ||
|
|
66a38c8e51 | ||
|
|
f79d2f821e | ||
|
|
72c5e64f74 | ||
|
|
01a44d49b0 | ||
|
|
f0412f5d47 | ||
|
|
2887ee9ac8 | ||
|
|
79d5211db4 | ||
|
|
d05e5d028b | ||
|
|
d3a415ee96 | ||
|
|
db9ac38c64 | ||
|
|
f27d119181 | ||
|
|
f2b85621f5 | ||
|
|
d7bc0aaf4c | ||
|
|
f50e0ca9f9 | ||
|
|
84960b0cef | ||
|
|
42646003fe | ||
|
|
321b8df3a2 | ||
|
|
f1990ff001 | ||
|
|
b48af759f1 | ||
|
|
3969f0414d | ||
|
|
9dc6c60a1a | ||
|
|
4b686a0147 | ||
|
|
f9f8eea6f6 | ||
|
|
3f6dc180cf | ||
|
|
7ce58b55a1 | ||
|
|
e8d1067313 | ||
|
|
c12b0a6dab | ||
|
|
effc3d1c4d | ||
|
|
63a9bd4fbf | ||
|
|
ca829a355c | ||
|
|
8464fc70fe | ||
|
|
dd68f89bf9 | ||
|
|
e42991c066 | ||
|
|
6eb699712d | ||
|
|
1d6662dfe3 | ||
|
|
258be9bf65 | ||
|
|
8592703931 | ||
|
|
44ecc9a203 | ||
|
|
b19e7809ea | ||
|
|
43a0bf4c31 | ||
|
|
546423512a | ||
|
|
b13acb33ed | ||
|
|
82c3193d21 | ||
|
|
503fc37a4b | ||
|
|
1788c9f3c0 | ||
|
|
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 |
8
.env
@@ -1,9 +1,7 @@
|
||||
# These API keys are intentionally public. Please do not report them - thank you for your concern.
|
||||
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
|
||||
REACT_APP_AWS_API_REGION="us-east-2"
|
||||
REACT_APP_AWS_API_ACCESS_KEY="AKIAYJJWW6AQ47ODATHN"
|
||||
REACT_APP_AWS_API_ACCESS_SECRET="V9PoU0FhBP3cX760rPs9jMG/MIuDNLX6hYvVcaYO"
|
||||
REACT_APP_AWS_X_API_KEY="z9dReS5UtHu7iTrUsTuWRozLthi3AxOZlvobrIdr14"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://beta.api.uniswap.org/v1/graphql"
|
||||
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
|
||||
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
|
||||
REACT_APP_SENTRY_DSN="https://a3c62e400b8748b5a8d007150e2f38b7@o1037921.ingest.sentry.io/4504255148851200"
|
||||
ESLINT_NO_DEV_ERRORS=true
|
||||
|
||||
@@ -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"
|
||||
|
||||
2
.eslintignore
Normal file
@@ -0,0 +1,2 @@
|
||||
*.config.ts
|
||||
*.d.ts
|
||||
@@ -11,6 +11,14 @@
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
},
|
||||
"import/parsers": {
|
||||
"@typescript-eslint/parser": [".ts", ".tsx"]
|
||||
},
|
||||
"import/resolver": {
|
||||
"typescript": {
|
||||
"alwaysTryTypes": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"ignorePatterns": [
|
||||
@@ -38,10 +46,12 @@
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"prettier/@typescript-eslint",
|
||||
"plugin:prettier/recommended"
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:import/typescript"
|
||||
],
|
||||
"plugins": ["simple-import-sort", "unused-imports"],
|
||||
"plugins": ["import", "simple-import-sort", "unused-imports"],
|
||||
"rules": {
|
||||
"import/no-unused-modules": [2, { "unusedExports": true }],
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error",
|
||||
@@ -52,6 +62,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'
|
||||
|
||||
49
.github/workflows/release.yaml
vendored
@@ -71,30 +71,6 @@ jobs:
|
||||
with:
|
||||
cidv0: ${{ steps.pinata.outputs.hash }}
|
||||
|
||||
- uses: actions/cache@v3
|
||||
id: cypress-cache
|
||||
with:
|
||||
path: /home/runner/.cache/Cypress
|
||||
key: ${{ runner.os }}-cypress-${{ hashFiles('node_modules/cypress') }}
|
||||
- if: steps.cypress-cache.outputs.cache-hit != 'true'
|
||||
run: yarn cypress install
|
||||
- uses: cypress-io/github-action@v4
|
||||
with:
|
||||
install: false
|
||||
browser: chrome
|
||||
config-file: cypress.release.config.ts
|
||||
config: baseUrl=https://cloudflare-ipfs.com/ipfs/${{ steps.pinata.outputs.hash }}
|
||||
|
||||
- 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
|
||||
with:
|
||||
cid: ${{ steps.pinata.outputs.hash }}
|
||||
|
||||
- name: Release
|
||||
uses: actions/create-release@v1.1.0
|
||||
env:
|
||||
@@ -120,3 +96,28 @@ jobs:
|
||||
- [ipfs://${{ steps.upload.outputs.hash }}/](ipfs://${{ steps.pinata.outputs.hash }}/)
|
||||
|
||||
${{ needs.tag.outputs.changelog }}
|
||||
|
||||
- name: Setup node@16 (required by Cloudflare Pages)
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Update Cloudflare Pages deployment
|
||||
uses: cloudflare/pages-action@364c7ca09a4b57837c5967871d64a2c31adb8c0d
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
|
||||
directory: build
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload source maps to Sentry
|
||||
uses: getsentry/action-release@bd5f874fcda966ba48139b0140fb3ec0cb3aabdd
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
with:
|
||||
environment: production
|
||||
sourcemaps: './build/static/js'
|
||||
url_prefix: '/static/js'
|
||||
27
.github/workflows/revert.yaml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Revert
|
||||
on:
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn prepare
|
||||
- run: yarn build
|
||||
|
||||
- name: Setup node@16 (required by Cloudflare Pages)
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Update Cloudflare Pages deployment
|
||||
uses: cloudflare/pages-action@364c7ca09a4b57837c5967871d64a2c31adb8c0d
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
|
||||
directory: build
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
2
.github/workflows/test.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: npx yarn-deduplicate --strategy=highest --list --fail
|
||||
- run: yarn yarn-deduplicate --strategy=highest --list --fail
|
||||
|
||||
unit-tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
25
.snyk
Normal file
@@ -0,0 +1,25 @@
|
||||
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
|
||||
version: v1.25.0
|
||||
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
|
||||
ignore:
|
||||
SNYK-JS-OPENZEPPELINCONTRACTS-2964946:
|
||||
- '*':
|
||||
reason: None Given
|
||||
expires: 2099-01-01T00:00:00.000Z
|
||||
created: 2022-12-08T16:25:57.347Z
|
||||
SNYK-JS-OPENZEPPELINCONTRACTS-2958047:
|
||||
- '*':
|
||||
reason: None Given
|
||||
expires: 2099-01-01T00:00:00.000Z
|
||||
created: 2022-12-08T16:26:09.720Z
|
||||
SNYK-JS-OPENZEPPELINCONTRACTS-2958050:
|
||||
- '*':
|
||||
reason: None Given
|
||||
expires: 2099-01-01T00:00:00.000Z
|
||||
created: 2022-12-08T16:26:17.702Z
|
||||
SNYK-JS-OPENZEPPELINCONTRACTS-2965580:
|
||||
- '*':
|
||||
reason: None Given
|
||||
expires: 2099-01-01T00:00:00.000Z
|
||||
created: 2022-12-08T16:26:34.283Z
|
||||
patch: {}
|
||||
2
.vscode/settings.json
vendored
@@ -16,4 +16,4 @@
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,16 @@ module.exports = {
|
||||
(plugin) => plugin instanceof MiniCssExtractPlugin
|
||||
)
|
||||
if (instanceOfMiniCssExtractPlugin !== undefined) instanceOfMiniCssExtractPlugin.options.ignoreOrder = true
|
||||
|
||||
// We're currently on Webpack 4.x that doesn't support the `exports` field in package.json.
|
||||
// See https://github.com/webpack/webpack/issues/9509.
|
||||
//
|
||||
// In case you need to add more modules, make sure to remap them to the correct path.
|
||||
//
|
||||
// Map @uniswap/conedison to its dist folder.
|
||||
// This is required because conedison uses * to redirect all imports to its dist.
|
||||
webpackConfig.resolve.alias['@uniswap/conedison'] = '@uniswap/conedison/dist'
|
||||
|
||||
return webpackConfig
|
||||
},
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineConfig } from 'cypress'
|
||||
export default defineConfig({
|
||||
projectId: 'yp82ef',
|
||||
videoUploadOnPasses: false,
|
||||
defaultCommandTimeout: 10000,
|
||||
defaultCommandTimeout: 24000, // 2x average block time
|
||||
chromeWebSecurity: false,
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
|
||||
@@ -5,10 +5,6 @@ describe('Landing Page', () => {
|
||||
cy.screenshot()
|
||||
})
|
||||
|
||||
it('redirects to url /swap', () => {
|
||||
cy.url().should('include', '/swap')
|
||||
})
|
||||
|
||||
it('allows navigation to pool', () => {
|
||||
cy.get('#pool-nav-link').click()
|
||||
cy.url().should('include', '/pool')
|
||||
|
||||
64
cypress/e2e/nfts.test.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
const COLLECTION_ADDRESS = '0xbd3531da5cf5857e7cfaa92426877b022e612cf8'
|
||||
|
||||
describe('Testing nfts', () => {
|
||||
before(() => {
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
it('should load nft leaderboard', () => {
|
||||
cy.get(getTestSelector('nft-nav')).first().click()
|
||||
cy.get(getTestSelector('nft-nav')).first().should('exist')
|
||||
cy.get(getTestSelector('nft-nav')).first().click()
|
||||
cy.get(getTestSelector('nft-trending-collection')).its('length').should('be.gte', 25)
|
||||
})
|
||||
|
||||
it('should load pudgy penguin collection page', () => {
|
||||
cy.visit(`/#/nfts/collection/${COLLECTION_ADDRESS}`)
|
||||
cy.get(getTestSelector('nft-collection-asset')).should('exist')
|
||||
cy.get(getTestSelector('nft-collection-filter-buy-now')).should('not.exist')
|
||||
cy.get(getTestSelector('nft-filter')).first().click()
|
||||
cy.get(getTestSelector('nft-collection-filter-buy-now')).should('exist')
|
||||
})
|
||||
|
||||
it('should be able to open bag and open sweep', () => {
|
||||
cy.get(getTestSelector('nft-sweep-button')).first().click()
|
||||
cy.get(getTestSelector('nft-empty-bag')).should('exist')
|
||||
cy.get(getTestSelector('nft-sweep-slider')).should('exist')
|
||||
})
|
||||
|
||||
it('should be able to navigate to activity', () => {
|
||||
cy.get(getTestSelector('nft-activity')).first().click()
|
||||
cy.get(getTestSelector('nft-activity-row')).should('exist')
|
||||
})
|
||||
|
||||
it('should go to the details page', () => {
|
||||
cy.visit(`/#/nfts/collection/${COLLECTION_ADDRESS}`)
|
||||
cy.get(getTestSelector('nft-filter')).first().click()
|
||||
cy.get(getTestSelector('nft-collection-filter-buy-now')).click()
|
||||
cy.get(getTestSelector('nft-details-link')).first().click()
|
||||
cy.get(getTestSelector('nft-details-traits')).should('exist')
|
||||
cy.get(getTestSelector('nft-details-activity')).should('exist')
|
||||
cy.get(getTestSelector('nft-details-description')).should('exist')
|
||||
cy.get(getTestSelector('nft-details-asset-details')).should('exist')
|
||||
})
|
||||
|
||||
it('should toggle buy now on details page', () => {
|
||||
cy.get(getTestSelector('nft-details-description-text')).should('exist')
|
||||
cy.get(getTestSelector('nft-details-description')).click()
|
||||
cy.get(getTestSelector('nft-details-description-text')).should('not.exist')
|
||||
cy.get(getTestSelector('nft-details-toggle-bag')).eq(1).click()
|
||||
cy.get(getTestSelector('nft-bag')).should('exist')
|
||||
})
|
||||
|
||||
it('should go view my nfts', () => {
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('nft-view-self-nfts')).click()
|
||||
cy.get(getTestSelector('nft-explore-nfts-button')).should('exist')
|
||||
cy.get(getTestSelector('nft-no-nfts-selected')).should('exist')
|
||||
cy.get(getTestSelector('nft-bag-close-icon')).click()
|
||||
cy.get(getTestSelector('nft-explore-nfts-button')).click()
|
||||
cy.get(getTestSelector('nft-welcome-modal')).should('exist')
|
||||
})
|
||||
})
|
||||
@@ -3,8 +3,8 @@ describe('Redirect', () => {
|
||||
cy.visit('/create-proposal')
|
||||
cy.url().should('match', /\/vote\/create-proposal/)
|
||||
})
|
||||
it('should redirect to /swap when visiting nonexist url', () => {
|
||||
it('should redirect to /not-found when visiting nonexist url', () => {
|
||||
cy.visit('/none-exist-url')
|
||||
cy.url().should('match', /\/swap/)
|
||||
cy.url().should('match', /\/not-found/)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -22,10 +22,4 @@ describe('Remove Liquidity', () => {
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH')
|
||||
})
|
||||
|
||||
it.skip('token not in storage is loaded', () => {
|
||||
cy.visit('/remove/v2/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'SKL')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
|
||||
})
|
||||
})
|
||||
|
||||
20
cypress/e2e/token.test.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { getTestSelector, getTestSelectorStartsWith } from '../utils'
|
||||
|
||||
describe('Testing tokens on uniswap page', () => {
|
||||
before(() => {
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
it('should load token leaderboard', () => {
|
||||
cy.visit('/tokens/ethereum')
|
||||
cy.get(getTestSelectorStartsWith('token-table')).its('length').should('be.gte', 25)
|
||||
})
|
||||
|
||||
it('should load go to ethereum token and return to token list page', () => {
|
||||
cy.visit('/tokens/ethereum')
|
||||
cy.get(getTestSelector('token-table-row-Ether')).click()
|
||||
cy.get(getTestSelector('token-details-stats')).should('exist')
|
||||
cy.get(getTestSelector('token-details-return-button')).click()
|
||||
cy.get(getTestSelectorStartsWith('token-table')).its('length').should('be.gte', 25)
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,8 @@
|
||||
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Wallet Dropdown', () => {
|
||||
before(() => {
|
||||
cy.visit('/', { featureFlags: [FeatureFlag.navBar, FeatureFlag.tokenSafety] })
|
||||
cy.visit('/pool')
|
||||
})
|
||||
|
||||
it('should change the theme', () => {
|
||||
@@ -31,6 +30,8 @@ describe('Wallet Dropdown', () => {
|
||||
cy.get(getTestSelector('wallet-disconnect')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).contains('Dark theme').should('exist')
|
||||
cy.get(getTestSelector('wallet-select-theme')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).contains('Light theme').should('exist')
|
||||
})
|
||||
|
||||
it('should select a language when not connected', () => {
|
||||
|
||||
@@ -65,6 +65,17 @@ beforeEach(() => {
|
||||
res.headers['origin'] = 'http://localhost:3000'
|
||||
res.continue()
|
||||
})
|
||||
|
||||
// Graphql security policies are based on Origin headers.
|
||||
// These are stripped by cypress because chromeWebSecurity === false; this adds them back in.
|
||||
cy.intercept('https://api.uniswap.org/v1/graphql', (res) => {
|
||||
res.headers['origin'] = 'https://app.uniswap.org'
|
||||
res.continue()
|
||||
})
|
||||
cy.intercept('https://beta.api.uniswap.org/v1/graphql', (res) => {
|
||||
res.headers['origin'] = 'https://app.uniswap.org'
|
||||
res.continue()
|
||||
})
|
||||
})
|
||||
|
||||
Cypress.on('uncaught:exception', (_err, _runnable) => {
|
||||
|
||||
@@ -12,12 +12,7 @@ import { Wallet } from '@ethersproject/wallet'
|
||||
const TEST_PRIVATE_KEY = '0xe580410d7c37d26c6ad1a837bbae46bc27f9066a466fb3a66e770523b4666d19'
|
||||
|
||||
// address of the above key
|
||||
export const TEST_ADDRESS_NEVER_USE = new Wallet(TEST_PRIVATE_KEY).address
|
||||
|
||||
export const TEST_ADDRESS_NEVER_USE_SHORTENED = `${TEST_ADDRESS_NEVER_USE.substr(
|
||||
0,
|
||||
6
|
||||
)}...${TEST_ADDRESS_NEVER_USE.substr(-4, 4)}`
|
||||
const TEST_ADDRESS_NEVER_USE = new Wallet(TEST_PRIVATE_KEY).address
|
||||
|
||||
const provider = new JsonRpcProvider('https://goerli.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
|
||||
const signer = new Wallet(TEST_PRIVATE_KEY, provider)
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export const getTestSelector = (selectorId: string) => `[data-testid=${selectorId}]`
|
||||
|
||||
export const getTestSelectorStartsWith = (selectorId: string) => `[data-testid^=${selectorId}]`
|
||||
|
||||
@@ -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)
|
||||
|
||||
46
package.json
@@ -20,6 +20,7 @@
|
||||
"start": "craco start",
|
||||
"build": "craco build",
|
||||
"serve": "serve build -l 3000",
|
||||
"deduplicate": "yarn-deduplicate --strategy=highest",
|
||||
"lint": "yarn eslint .",
|
||||
"test": "craco test --coverage",
|
||||
"cypress:open": "cypress open --browser chrome --e2e",
|
||||
@@ -98,7 +99,9 @@
|
||||
"env-cmd": "^10.1.0",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.2",
|
||||
"eslint-plugin-better-styled-components": "^1.1.2",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
@@ -113,7 +116,8 @@
|
||||
"relay-compiler": "^14.1.0",
|
||||
"serve": "^11.3.2",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.4.3"
|
||||
"typescript": "^4.4.3",
|
||||
"yarn-deduplicate": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coinbase/wallet-sdk": "^3.3.0",
|
||||
@@ -122,7 +126,7 @@
|
||||
"@lingui/core": "^3.14.0",
|
||||
"@lingui/macro": "^3.14.0",
|
||||
"@lingui/react": "^3.14.0",
|
||||
"@looksrare/sdk": "^0.7.1",
|
||||
"@looksrare/sdk": "^0.10.2",
|
||||
"@metamask/jazzicon": "^2.0.0",
|
||||
"@opensea/seaport-js": "^1.0.2",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
@@ -130,24 +134,29 @@
|
||||
"@reach/portal": "^0.10.3",
|
||||
"@react-hook/window-scroll": "^1.3.0",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@sentry/react": "7.20.1",
|
||||
"@types/react-relay": "^13.0.2",
|
||||
"@uniswap/analytics": "1.0.3",
|
||||
"@uniswap/analytics-events": "1.0.4",
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@uniswap/analytics": "1.2.0",
|
||||
"@uniswap/analytics-events": "1.3.1",
|
||||
"@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/permit2-sdk": "1.2.0",
|
||||
"@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",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.30",
|
||||
"@uniswap/universal-router-sdk": "1.3.0",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v2-sdk": "^3.0.1",
|
||||
"@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.22.11",
|
||||
"@vanilla-extract/css": "^1.7.2",
|
||||
"@vanilla-extract/css-utils": "^0.1.2",
|
||||
"@vanilla-extract/dynamic": "^2.0.2",
|
||||
@@ -159,17 +168,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",
|
||||
@@ -197,7 +206,6 @@
|
||||
"qs": "^6.9.4",
|
||||
"rc-slider": "^10.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-confetti": "^6.0.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-feather": "^2.0.8",
|
||||
"react-ga4": "^1.4.1",
|
||||
@@ -208,12 +216,14 @@
|
||||
"react-query": "^3.39.1",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-relay": "^14.1.0",
|
||||
"react-relay-network-modern": "^6.2.1",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-spring": "^9.5.5",
|
||||
"react-table": "^7.8.0",
|
||||
"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",
|
||||
|
||||
@@ -89,7 +89,9 @@
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html {
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,5 +26,5 @@
|
||||
"iconPath": "./images/256x256_App_Icon_Pink.svg",
|
||||
"short_name": "Uniswap",
|
||||
"start_url": ".",
|
||||
"theme_color": "#FC72FFs"
|
||||
}
|
||||
"theme_color": "#FC72FF"
|
||||
}
|
||||
3
public/nft/svgs/marketplaces/looksrare-grey.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.64864 2L1 7.65256L10.5 17.1487L20 7.65256L14.3514 2H6.64864ZM6.13513 5.59458C8.5352 3.18398 12.4648 3.18396 14.8649 5.59456L16.9189 7.64866L14.8649 9.70272C12.4648 12.1133 8.5352 12.1133 6.13513 9.70274L4.08109 7.64866L6.13513 5.59458ZM7.54702 7.64848C7.54702 9.27987 8.86966 10.6012 10.4997 10.6012C12.1298 10.6012 13.4524 9.27987 13.4524 7.64848C13.4524 6.01708 12.1298 4.69576 10.4997 4.69576C8.86966 4.69576 7.54702 6.01708 7.54702 7.64848ZM10.4997 8.93225C9.791 8.93225 9.21593 8.35778 9.21593 7.64848C9.21593 6.93917 9.791 6.3647 10.4997 6.3647C11.2084 6.3647 11.7835 6.93917 11.7835 7.64848C11.7835 8.35778 11.2084 8.93225 10.4997 8.93225Z" fill="#5D6785"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 820 B |
3
public/nft/svgs/marketplaces/opensea-grey.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 1C5.0302 1 1 5.0302 1 10C1 14.9698 5.0302 19 10 19C14.9698 19 19 14.9698 19 10C19 5.0302 14.9716 1 10 1ZM5.4406 10.3024L5.4784 10.2412L7.8202 6.5782C7.8544 6.526 7.9354 6.5314 7.9606 6.589C8.3512 7.4656 8.6896 8.5564 8.5312 9.235C8.4646 9.514 8.2792 9.892 8.0704 10.2412C8.0434 10.2916 8.0146 10.342 7.9822 10.3906C7.9678 10.4122 7.9426 10.4248 7.9156 10.4248H5.509C5.4442 10.4248 5.4064 10.3546 5.4406 10.3024ZM15.8752 11.5624C15.8752 11.5966 15.8554 11.6254 15.8266 11.638C15.6448 11.7154 15.0238 12.0016 14.7664 12.3598C14.1076 13.276 13.6054 14.5864 12.4804 14.5864H7.7896C6.1264 14.5864 4.78 13.2346 4.78 11.566V11.512C4.78 11.4688 4.816 11.4328 4.861 11.4328H7.4746C7.5268 11.4328 7.5646 11.4796 7.561 11.5318C7.5412 11.701 7.5736 11.8756 7.6546 12.034C7.8094 12.349 8.1316 12.5452 8.479 12.5452H9.7732V11.5354H8.4934C8.4286 11.5354 8.389 11.4598 8.4268 11.4058C8.4412 11.3842 8.4556 11.3626 8.4736 11.3374C8.5942 11.1646 8.767 10.8982 8.9398 10.594C9.0568 10.3888 9.1702 10.1692 9.262 9.9496C9.28 9.91 9.2944 9.8686 9.3106 9.829C9.3358 9.7588 9.361 9.6922 9.379 9.6274C9.397 9.5716 9.4132 9.514 9.4276 9.46C9.4708 9.2728 9.4888 9.0748 9.4888 8.8696C9.4888 8.7886 9.4852 8.704 9.478 8.6248C9.4744 8.5366 9.4636 8.4484 9.4528 8.3602C9.4456 8.2828 9.4312 8.2054 9.4168 8.1262C9.397 8.0092 9.3718 7.8922 9.343 7.7752L9.3322 7.7302C9.3106 7.6492 9.2908 7.5736 9.2656 7.4926C9.1918 7.2406 9.109 6.994 9.019 6.7636C8.9866 6.6718 8.9506 6.5836 8.9128 6.4972C8.8588 6.364 8.803 6.2434 8.7526 6.13C8.7256 6.0778 8.704 6.031 8.6824 5.9824C8.6572 5.9284 8.632 5.8744 8.605 5.8222C8.587 5.7826 8.5654 5.7448 8.551 5.7088L8.3926 5.4172C8.371 5.3776 8.407 5.329 8.4502 5.3416L9.4402 5.6098H9.4438C9.4456 5.6098 9.4456 5.6098 9.4474 5.6098L9.577 5.6476L9.721 5.6872L9.7732 5.7016V5.1148C9.7732 4.8304 10 4.6 10.2826 4.6C10.423 4.6 10.5508 4.6576 10.6408 4.7512C10.7326 4.8448 10.7902 4.9726 10.7902 5.1148V5.9878L10.8964 6.0166C10.9036 6.0202 10.9126 6.0238 10.9198 6.0292C10.945 6.0472 10.9828 6.076 11.0296 6.112C11.0674 6.1408 11.107 6.1768 11.1538 6.2146C11.2492 6.292 11.3644 6.391 11.4886 6.5044C11.521 6.5332 11.5534 6.562 11.584 6.5926C11.7442 6.742 11.9242 6.9166 12.097 7.111C12.1456 7.1668 12.1924 7.2208 12.241 7.2802C12.2878 7.3396 12.34 7.3972 12.3832 7.4548C12.4426 7.5322 12.5038 7.6132 12.5596 7.6978C12.5848 7.7374 12.6154 7.7788 12.6388 7.8184C12.7108 7.9246 12.772 8.0344 12.8314 8.1442C12.8566 8.1946 12.8818 8.2504 12.9034 8.3044C12.97 8.452 13.0222 8.6014 13.0546 8.7526C13.0654 8.785 13.0726 8.8192 13.0762 8.8516V8.8588C13.087 8.902 13.0906 8.9488 13.0942 8.9974C13.1086 9.1504 13.1014 9.3052 13.069 9.46C13.0546 9.5248 13.0366 9.586 13.015 9.6526C12.9916 9.7156 12.97 9.7804 12.9412 9.8434C12.8854 9.9712 12.8206 10.1008 12.7432 10.2196C12.718 10.2646 12.6874 10.3114 12.6586 10.3564C12.6262 10.4032 12.592 10.4482 12.5632 10.4914C12.5218 10.5472 12.4786 10.6048 12.4336 10.657C12.394 10.711 12.3544 10.765 12.3094 10.8136C12.2482 10.8874 12.1888 10.9558 12.1258 11.0224C12.0898 11.0656 12.0502 11.1106 12.0088 11.1502C11.9692 11.1952 11.9278 11.2348 11.8918 11.2708C11.8288 11.3338 11.7784 11.3806 11.7352 11.422L11.6326 11.5138C11.6182 11.5282 11.5984 11.5354 11.5786 11.5354H10.7902V12.5452H11.782C12.0034 12.5452 12.214 12.4678 12.385 12.322C12.4426 12.2716 12.6964 12.052 12.997 11.7208C13.0078 11.7082 13.0204 11.701 13.0348 11.6974L15.7726 10.9054C15.8248 10.891 15.8752 10.9288 15.8752 10.9828V11.5624Z" fill="#5D6785"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
3
public/nft/svgs/marketplaces/uniswap-magenta.svg
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
5
public/nft/svgs/marketplaces/x2y2-grey.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.146 4.52803C15.767 3.18049 13.8805 2.35 11.8 2.35C7.57502 2.35 4.15 5.77502 4.15 10C4.15 14.225 7.57502 17.65 11.8 17.65C13.8805 17.65 15.767 16.8195 17.146 15.472C15.501 17.617 12.912 19 10 19C5.02944 19 1 14.9706 1 10C1 5.02944 5.02944 1 10 1C12.912 1 15.501 2.38301 17.146 4.52803Z" fill="#5D6785"/>
|
||||
<path d="M6.08317 14.3776C7.18644 15.4556 8.69563 16.12 10.36 16.12C13.74 16.12 16.48 13.38 16.48 10C16.48 6.62002 13.74 3.88 10.36 3.88C8.69563 3.88 7.18644 4.54439 6.08317 5.62243C7.39916 3.90641 9.47037 2.8 11.8 2.8C15.7765 2.8 19 6.02355 19 10C19 13.9764 15.7764 17.2 11.8 17.2C9.47037 17.2 7.39916 16.0936 6.08317 14.3776Z" fill="#5D6785"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.4 10C15.4 12.9823 12.9823 15.4 10 15.4C7.01766 15.4 4.6 12.9823 4.6 10C4.6 7.01766 7.01766 4.6 10 4.6C12.9823 4.6 15.4 7.01766 15.4 10ZM13.6 10C13.6 11.9882 11.9882 13.6 10 13.6C8.01177 13.6 6.4 11.9882 6.4 10C6.4 8.01178 8.01177 6.4 10 6.4C11.9882 6.4 13.6 8.01178 13.6 10Z" fill="#5D6785"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
218
src/abis/uniswap-nft-airdrop-claim.json
Normal file
@@ -0,0 +1,218 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token_",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "merkleRoot_",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "endTime_",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "AlreadyClaimed",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "ClaimWindowFinished",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "EndTimeInPast",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "InvalidProof",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "NoWithdrawDuringClaim",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Claimed",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "previousOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnershipTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32[]",
|
||||
"name": "merkleProof",
|
||||
"type": "bytes32[]"
|
||||
}
|
||||
],
|
||||
"name": "claim",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "endTime",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "isClaimed",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "merkleRoot",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "renounceOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "token",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "withdraw",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
BIN
src/assets/images/404-page-dark.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
src/assets/images/404-page-light.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
src/assets/images/airdopBackground.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 19 KiB |
BIN
src/assets/images/welcomeModal-dark.jpg
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
src/assets/images/welcomeModal-dark@2x.jpg
Normal file
|
After Width: | Height: | Size: 449 KiB |
BIN
src/assets/images/welcomeModal-dark@3x.jpg
Normal file
|
After Width: | Height: | Size: 927 KiB |
BIN
src/assets/images/welcomeModal-light.jpg
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
src/assets/images/welcomeModal-light@2x.jpg
Normal file
|
After Width: | Height: | Size: 364 KiB |
BIN
src/assets/images/welcomeModal-light@3x.jpg
Normal file
|
After Width: | Height: | Size: 718 KiB |
4
src/assets/svg/eye.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="54" height="40" viewBox="0 0 54 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.3335 19.9997C1.3335 19.9997 10.6668 1.33301 27.0002 1.33301C43.3335 1.33301 52.6668 19.9997 52.6668 19.9997C52.6668 19.9997 43.3335 38.6663 27.0002 38.6663C10.6668 38.6663 1.3335 19.9997 1.3335 19.9997Z" stroke="#98A1C0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M27.0002 26.9997C30.8662 26.9997 34.0002 23.8657 34.0002 19.9997C34.0002 16.1337 30.8662 12.9997 27.0002 12.9997C23.1342 12.9997 20.0002 16.1337 20.0002 19.9997C20.0002 23.8657 23.1342 26.9997 27.0002 26.9997Z" stroke="#98A1C0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 705 B |
@@ -27,12 +27,12 @@ const TransactionState = styled(ExternalLink)<{ pending: boolean; success?: bool
|
||||
padding: 0.25rem 0rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.825rem;
|
||||
color: ${({ theme }) => theme.deprecated_primary1};
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
`
|
||||
|
||||
const IconWrapper = styled.div<{ pending: boolean; success?: boolean }>`
|
||||
color: ${({ pending, success, theme }) =>
|
||||
pending ? theme.deprecated_primary1 : success ? theme.deprecated_green1 : theme.deprecated_red1};
|
||||
pending ? theme.accentAction : success ? theme.accentSuccess : theme.accentFailure};
|
||||
`
|
||||
|
||||
export default function Transaction({ hash }: { hash: string }) {
|
||||
|
||||
@@ -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,10 +21,10 @@ 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')};
|
||||
color: ${(props) => (props.color === 'blue' ? ({ theme }) => theme.accentAction : 'inherit')};
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
padding: 1rem;
|
||||
`};
|
||||
@@ -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};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
|
||||
div {
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
${flexColumnNoWrap};
|
||||
align-items: center;
|
||||
}
|
||||
`
|
||||
@@ -90,18 +91,18 @@ const YourAccount = styled.div`
|
||||
`
|
||||
|
||||
const LowerSection = styled.div`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
${flexColumnNoWrap};
|
||||
padding: 1.5rem;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
|
||||
h5 {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
color: ${({ theme }) => theme.deprecated_text3};
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -128,14 +129,14 @@ const AccountControl = styled.div`
|
||||
`
|
||||
|
||||
const AddressLink = styled(ExternalLink)`
|
||||
color: ${({ theme }) => theme.deprecated_text3};
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
margin-left: 1rem;
|
||||
font-size: 0.825rem;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
text-decoration: none !important;
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.deprecated_text2};
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -159,11 +160,11 @@ const WalletName = styled.div`
|
||||
width: initial;
|
||||
font-size: 0.825rem;
|
||||
font-weight: 500;
|
||||
color: ${({ theme }) => theme.deprecated_text3};
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
`
|
||||
|
||||
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>
|
||||
@@ -315,7 +316,7 @@ export default function AccountDetails({
|
||||
</LowerSection>
|
||||
) : (
|
||||
<LowerSection>
|
||||
<ThemedText.DeprecatedBody color={theme.deprecated_text1}>
|
||||
<ThemedText.DeprecatedBody color={theme.textPrimary}>
|
||||
<Trans>Your transactions will appear here...</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</LowerSection>
|
||||
|
||||
@@ -5,7 +5,7 @@ import styled, { css } from 'styled-components/macro'
|
||||
|
||||
import { nativeOnChain } from '../../constants/tokens'
|
||||
import { useCurrency } from '../../hooks/Tokens'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import CurrencyLogo from '../Logo/CurrencyLogo'
|
||||
|
||||
const CurrencyWrap = styled.div`
|
||||
position: relative;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { SupportedChainId } from 'constants/chains'
|
||||
import { useMemo } from 'react'
|
||||
import { AlertTriangle, CheckCircle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink } from 'theme'
|
||||
import { colors } from 'theme/colors'
|
||||
|
||||
import { TransactionDetails } from '../../state/transactions/types'
|
||||
@@ -17,13 +18,14 @@ export enum TransactionState {
|
||||
Failed,
|
||||
}
|
||||
|
||||
const Grid = styled.a`
|
||||
const Grid = styled(ExternalLink)<{ isLastTransactionInList?: boolean }>`
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
grid-template-columns: 44px auto 24px;
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
border-bottom: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
|
||||
border-bottom: ${({ theme, isLastTransactionInList }) =>
|
||||
isLastTransactionInList ? 'none' : `1px solid ${theme.backgroundOutline}`};
|
||||
padding: 12px;
|
||||
|
||||
&:hover {
|
||||
@@ -46,7 +48,13 @@ const IconStyleWrap = styled.span`
|
||||
height: 16px;
|
||||
`
|
||||
|
||||
export const TransactionSummary = ({ transactionDetails }: { transactionDetails: TransactionDetails }) => {
|
||||
export const TransactionSummary = ({
|
||||
transactionDetails,
|
||||
isLastTransactionInList = false,
|
||||
}: {
|
||||
transactionDetails: TransactionDetails
|
||||
isLastTransactionInList?: boolean
|
||||
}) => {
|
||||
const { chainId = 1 } = useWeb3React()
|
||||
const tx = transactionDetails
|
||||
const { explorer } = getChainInfoOrDefault(chainId ? chainId : SupportedChainId.MAINNET)
|
||||
@@ -67,7 +75,7 @@ export const TransactionSummary = ({ transactionDetails }: { transactionDetails:
|
||||
const link = `${explorer}tx/${hash}`
|
||||
|
||||
return chainId ? (
|
||||
<Grid href={link} target="_blank">
|
||||
<Grid href={link} target="_blank" isLastTransactionInList={isLastTransactionInList}>
|
||||
<LogoView info={info} />
|
||||
<TextContainer as="span">
|
||||
<TransactionBody info={info} transactionState={transactionState} />
|
||||
|
||||
@@ -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};
|
||||
@@ -25,7 +26,7 @@ const ContainerRow = styled.div<{ error: boolean }>`
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 1.25rem;
|
||||
border: 1px solid ${({ error, theme }) => (error ? theme.deprecated_red1 : theme.deprecated_bg2)};
|
||||
border: 1px solid ${({ error, theme }) => (error ? theme.accentFailure : theme.backgroundInteractive)};
|
||||
transition: border-color 300ms ${({ error }) => (error ? 'step-end' : 'step-start')},
|
||||
color 500ms ${({ error }) => (error ? 'step-end' : 'step-start')};
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
@@ -44,7 +45,7 @@ const Input = styled.input<{ error?: boolean }>`
|
||||
width: 0;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
transition: color 300ms ${({ error }) => (error ? 'step-end' : 'step-start')};
|
||||
color: ${({ error, theme }) => (error ? theme.deprecated_red1 : theme.deprecated_text1)};
|
||||
color: ${({ error, theme }) => (error ? theme.accentFailure : theme.textPrimary)};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
@@ -108,7 +109,7 @@ export default function AddressInputPanel({
|
||||
<InputContainer>
|
||||
<AutoColumn gap="md">
|
||||
<RowBetween>
|
||||
<ThemedText.DeprecatedBlack color={theme.deprecated_text2} fontWeight={500} fontSize={14}>
|
||||
<ThemedText.DeprecatedBlack color={theme.textSecondary} fontWeight={500} fontSize={14}>
|
||||
{label ?? <Trans>Recipient</Trans>}
|
||||
</ThemedText.DeprecatedBlack>
|
||||
{address && chainId && (
|
||||
|
||||
335
src/components/AirdropModal/index.tsx
Normal file
@@ -0,0 +1,335 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import type { TransactionResponse } from '@ethersproject/providers'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import uniswapNftAirdropClaim from 'abis/uniswap-nft-airdrop-claim.json'
|
||||
import airdropBackgroundv2 from 'assets/images/airdopBackground.png'
|
||||
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
|
||||
import { OpacityHoverState } from 'components/Common'
|
||||
import Loader from 'components/Loader'
|
||||
import { UNISWAP_NFT_AIRDROP_CLAIM_ADDRESS } from 'constants/addresses'
|
||||
import { useContract } from 'hooks/useContract'
|
||||
import { ChevronRightIcon } from 'nft/components/icons'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { CollectionRewardsFetcher } from 'nft/queries/genie/GetAirdorpMerkle'
|
||||
import { Airdrop, Rewards } from 'nft/types/airdrop'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import styled from 'styled-components/macro'
|
||||
import { CloseIcon, ThemedText } from 'theme'
|
||||
|
||||
import Modal from '../Modal'
|
||||
|
||||
const ModalWrap = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const Body = styled.div`
|
||||
padding: 28px 20px 20px 20px;
|
||||
`
|
||||
|
||||
const ClaimButton = styled(ThemeButton)`
|
||||
width: 100%;
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
`
|
||||
|
||||
const Line = styled.div`
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: ${({ theme }) => theme.white};
|
||||
opacity: 0.24;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
`
|
||||
|
||||
const LinkWrap = styled.a`
|
||||
text-decoration: none;
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const ImageContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledImage = styled.img`
|
||||
width: 100%;
|
||||
height: 170px;
|
||||
`
|
||||
|
||||
const USDCLabel = styled.div`
|
||||
font-weight: 700;
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
margin-top: 8px;
|
||||
color: white;
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
`
|
||||
|
||||
const RewardsDetailsContainer = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const CurrencyText = styled.span`
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 14.5px;
|
||||
`
|
||||
|
||||
const ClaimContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
height: 380px;
|
||||
padding: 60px 28px;
|
||||
padding-bottom: 20px;
|
||||
`
|
||||
|
||||
const SuccessText = styled.div`
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
const EtherscanLink = styled.a`
|
||||
text-decoration: none;
|
||||
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const CloseButton = styled(ThemeButton)`
|
||||
max-width: 68px;
|
||||
margin-top: auto;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
`
|
||||
|
||||
const SyledCloseIcon = styled(CloseIcon)`
|
||||
float: right;
|
||||
height: 24px;
|
||||
|
||||
${OpacityHoverState}
|
||||
`
|
||||
|
||||
const Error = styled.div`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.accentCritical};
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
border-radius: 16px;
|
||||
padding: 12px 20px;
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
`
|
||||
|
||||
const ReactLinkWrap = styled.div`
|
||||
margin-bottom: 40px;
|
||||
`
|
||||
|
||||
const RewardsText = styled.span`
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
|
||||
&:first-child {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
const RewardsInformationText = styled.span`
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
margin-bottom: 28px;
|
||||
`
|
||||
|
||||
const MainHeader = styled.span`
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme }) => theme.white};
|
||||
`
|
||||
|
||||
const EtherscanLinkWrap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
enum RewardAmounts {
|
||||
tradingRewardAmount = 300,
|
||||
holderRewardAmount = 1000,
|
||||
combinedAmount = 1300,
|
||||
}
|
||||
|
||||
const AirdropModal = () => {
|
||||
const { account, provider } = useWeb3React()
|
||||
const [claim, setClaim] = useState<Rewards>()
|
||||
const [isClaimed, setIsClaimed] = useState(false)
|
||||
const [hash, setHash] = useState('')
|
||||
const [error, setError] = useState(false)
|
||||
const setIsClaimAvailable = useIsNftClaimAvailable((state) => state.setIsClaimAvailable)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [totalAmount, setTotalAmount] = useState(0)
|
||||
const isOpen = useModalIsOpen(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
|
||||
const usdcAirdropToggle = useToggleModal(ApplicationModal.UNISWAP_NFT_AIRDROP_CLAIM)
|
||||
const contract = useContract(UNISWAP_NFT_AIRDROP_CLAIM_ADDRESS, uniswapNftAirdropClaim)
|
||||
|
||||
const displayError = () => {
|
||||
setIsSubmitting(false)
|
||||
setError(true)
|
||||
setTimeout(() => {
|
||||
setError(false)
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (account && provider && contract) {
|
||||
;(async () => {
|
||||
try {
|
||||
const { data } = await CollectionRewardsFetcher(account)
|
||||
const claim = data.find((claim) => claim?.rewardType === Airdrop.GENIE_UNISWAP_USDC_AIRDROP)
|
||||
|
||||
if (!claim) return
|
||||
|
||||
const [isClaimed] = await contract.connect(provider).functions.isClaimed(claim?.index)
|
||||
|
||||
if (claim && isClaimed === false) {
|
||||
const usdAmount = BigNumber.from(claim.amount).div(10 ** 6)
|
||||
setClaim(claim)
|
||||
setTotalAmount(usdAmount.toNumber())
|
||||
setIsClaimAvailable(true)
|
||||
}
|
||||
} catch (err) {
|
||||
displayError()
|
||||
}
|
||||
})()
|
||||
}
|
||||
}, [account, contract, provider, setIsClaimAvailable])
|
||||
|
||||
const makeClaim = async () => {
|
||||
try {
|
||||
if (contract && claim && claim.amount && claim.merkleProof && provider) {
|
||||
setIsSubmitting(true)
|
||||
|
||||
const response: TransactionResponse = await contract
|
||||
.connect(provider?.getSigner())
|
||||
.functions.claim(claim.index, account, claim?.amount, claim?.merkleProof)
|
||||
|
||||
await response.wait()
|
||||
|
||||
setHash(response.hash)
|
||||
setIsSubmitting(false)
|
||||
setIsClaimed(true)
|
||||
setIsClaimAvailable(false)
|
||||
}
|
||||
} catch (err) {
|
||||
setIsSubmitting(false)
|
||||
displayError()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal hideBorder isOpen={isOpen} onDismiss={usdcAirdropToggle} maxHeight={90} maxWidth={400}>
|
||||
<ModalWrap>
|
||||
{isClaimed ? (
|
||||
<ClaimContainer>
|
||||
<ThemedText.HeadlineSmall>Congratulations!</ThemedText.HeadlineSmall>
|
||||
<SuccessText>
|
||||
You have successfully claimed {totalAmount} USDC. Thank you for supporting Genie.xyz.
|
||||
</SuccessText>
|
||||
<EtherscanLink href={`https://etherscan.io/tx/${hash}`} target="_blank">
|
||||
<ThemedText.Link>
|
||||
<EtherscanLinkWrap>
|
||||
<span>Etherscan</span>
|
||||
<ChevronRightIcon />
|
||||
</EtherscanLinkWrap>
|
||||
</ThemedText.Link>
|
||||
</EtherscanLink>
|
||||
|
||||
<CloseButton size={ButtonSize.medium} emphasis={ButtonEmphasis.medium} onClick={usdcAirdropToggle}>
|
||||
Close
|
||||
</CloseButton>
|
||||
</ClaimContainer>
|
||||
) : (
|
||||
<>
|
||||
<ImageContainer>
|
||||
<TextContainer>
|
||||
<SyledCloseIcon onClick={usdcAirdropToggle} stroke="white" />
|
||||
<MainHeader>Uniswap NFT Airdrop</MainHeader>
|
||||
<USDCLabel>{totalAmount} USDC</USDCLabel>
|
||||
<Line />
|
||||
<RewardsDetailsContainer>
|
||||
<RewardsText>Trading rewards</RewardsText>{' '}
|
||||
<CurrencyText>
|
||||
{totalAmount === RewardAmounts.tradingRewardAmount || totalAmount === RewardAmounts.combinedAmount
|
||||
? `${RewardAmounts.tradingRewardAmount} USDC`
|
||||
: '0'}
|
||||
</CurrencyText>
|
||||
</RewardsDetailsContainer>
|
||||
<RewardsDetailsContainer>
|
||||
<RewardsText>Genie NFT holder rewards</RewardsText>{' '}
|
||||
<CurrencyText>
|
||||
{totalAmount !== RewardAmounts.tradingRewardAmount
|
||||
? `${RewardAmounts.holderRewardAmount} USDC`
|
||||
: '0'}
|
||||
</CurrencyText>
|
||||
</RewardsDetailsContainer>
|
||||
</TextContainer>
|
||||
<StyledImage src={airdropBackgroundv2} />
|
||||
</ImageContainer>
|
||||
<Body>
|
||||
<RewardsInformationText>
|
||||
As a long time supporter of Genie, you’ve been awarded {totalAmount} USDC tokens.
|
||||
</RewardsInformationText>
|
||||
<ReactLinkWrap>
|
||||
<LinkWrap href="https://uniswap.org/blog/uniswap-nft-aggregator-announcement" target="_blank">
|
||||
<ThemedText.Link>Read more about Uniswap NFT.</ThemedText.Link>
|
||||
</LinkWrap>
|
||||
</ReactLinkWrap>
|
||||
|
||||
{error && (
|
||||
<Error>
|
||||
<AlertTriangle />
|
||||
Claim USDC failed. Please try again later
|
||||
</Error>
|
||||
)}
|
||||
|
||||
<ClaimButton
|
||||
onClick={makeClaim}
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting && <Loader stroke="white" />}
|
||||
<span>Claim{isSubmitting && 'ing'} USDC</span>
|
||||
</ClaimButton>
|
||||
</Body>
|
||||
</>
|
||||
)}
|
||||
</ModalWrap>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AirdropModal
|
||||
@@ -17,7 +17,7 @@ const BadgeText = styled.div`
|
||||
`
|
||||
|
||||
const ActiveDot = styled.span`
|
||||
background-color: ${({ theme }) => theme.deprecated_success};
|
||||
background-color: ${({ theme }) => theme.accentSuccess};
|
||||
border-radius: 50%;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
|
||||
@@ -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,27 +16,27 @@ 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
|
||||
return theme.accentFailure
|
||||
case BadgeVariant.POSITIVE:
|
||||
return theme.deprecated_success
|
||||
return theme.accentSuccess
|
||||
case BadgeVariant.PRIMARY:
|
||||
return theme.deprecated_primary1
|
||||
return theme.accentAction
|
||||
case BadgeVariant.WARNING:
|
||||
return theme.deprecated_warning
|
||||
return theme.accentWarning
|
||||
case BadgeVariant.WARNING_OUTLINE:
|
||||
return 'transparent'
|
||||
default:
|
||||
return theme.deprecated_bg2
|
||||
return theme.backgroundInteractive
|
||||
}
|
||||
}
|
||||
|
||||
function pickBorder(variant: BadgeVariant | undefined, theme: DefaultTheme): string {
|
||||
switch (variant) {
|
||||
case BadgeVariant.WARNING_OUTLINE:
|
||||
return `1px solid ${theme.deprecated_warning}`
|
||||
return `1px solid ${theme.accentWarning}`
|
||||
default:
|
||||
return 'unset'
|
||||
}
|
||||
@@ -46,15 +45,15 @@ function pickBorder(variant: BadgeVariant | undefined, theme: DefaultTheme): str
|
||||
function pickFontColor(variant: BadgeVariant | undefined, theme: DefaultTheme): string {
|
||||
switch (variant) {
|
||||
case BadgeVariant.NEGATIVE:
|
||||
return readableColor(theme.deprecated_error)
|
||||
return readableColor(theme.accentFailure)
|
||||
case BadgeVariant.POSITIVE:
|
||||
return readableColor(theme.deprecated_success)
|
||||
return readableColor(theme.accentSuccess)
|
||||
case BadgeVariant.WARNING:
|
||||
return readableColor(theme.deprecated_warning)
|
||||
return readableColor(theme.accentWarning)
|
||||
case BadgeVariant.WARNING_OUTLINE:
|
||||
return theme.deprecated_warning
|
||||
return theme.accentWarning
|
||||
default:
|
||||
return readableColor(theme.deprecated_bg2)
|
||||
return readableColor(theme.backgroundInteractive)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -22,7 +22,7 @@ export const BaseButton = styled(RebassButton)<
|
||||
border-radius: ${({ $borderRadius }) => $borderRadius ?? '20px'};
|
||||
outline: none;
|
||||
border: 1px solid transparent;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -57,21 +57,21 @@ export const ButtonPrimary = styled(BaseButton)`
|
||||
padding: 16px;
|
||||
color: ${({ theme }) => theme.accentTextLightPrimary};
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.deprecated_primary1)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.deprecated_primary1)};
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.accentAction)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.accentAction)};
|
||||
}
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => darken(0.05, theme.deprecated_primary1)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.accentAction)};
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.deprecated_primary1)};
|
||||
background-color: ${({ theme }) => darken(0.1, theme.deprecated_primary1)};
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.accentAction)};
|
||||
background-color: ${({ theme }) => darken(0.1, theme.accentAction)};
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme, altDisabledStyle, disabled }) =>
|
||||
altDisabledStyle ? (disabled ? theme.deprecated_primary1 : theme.deprecated_bg2) : theme.deprecated_bg2};
|
||||
altDisabledStyle ? (disabled ? theme.accentAction : theme.backgroundInteractive) : theme.backgroundInteractive};
|
||||
color: ${({ altDisabledStyle, disabled, theme }) =>
|
||||
altDisabledStyle ? (disabled ? theme.deprecated_white : theme.deprecated_text2) : theme.deprecated_text2};
|
||||
altDisabledStyle ? (disabled ? theme.white : theme.textSecondary) : theme.textSecondary};
|
||||
cursor: auto;
|
||||
box-shadow: none;
|
||||
border: 1px solid transparent;
|
||||
@@ -79,6 +79,13 @@ export const ButtonPrimary = styled(BaseButton)`
|
||||
}
|
||||
`
|
||||
|
||||
export const SmallButtonPrimary = styled(ButtonPrimary)`
|
||||
width: auto;
|
||||
font-size: 16px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 12px;
|
||||
`
|
||||
|
||||
export const ButtonLight = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.accentActionSoft};
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
@@ -110,21 +117,21 @@ export const ButtonLight = styled(BaseButton)`
|
||||
|
||||
export const ButtonGray = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
color: ${({ theme }) => theme.deprecated_text2};
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.deprecated_bg2)};
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.backgroundInteractive)};
|
||||
}
|
||||
&:active {
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.1, theme.deprecated_bg2)};
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.1, theme.backgroundInteractive)};
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonSecondary = styled(BaseButton)`
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_primary4};
|
||||
color: ${({ theme }) => theme.deprecated_primary1};
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
background-color: transparent;
|
||||
font-size: 16px;
|
||||
border-radius: 12px;
|
||||
@@ -151,14 +158,14 @@ export const ButtonSecondary = styled(BaseButton)`
|
||||
`
|
||||
|
||||
export const ButtonOutlined = styled(BaseButton)`
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg2};
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
background-color: transparent;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.deprecated_bg4};
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.deprecated_bg4};
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.textTertiary};
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.deprecated_bg4};
|
||||
@@ -191,7 +198,7 @@ export const ButtonYellow = styled(BaseButton)`
|
||||
|
||||
export const ButtonEmpty = styled(BaseButton)`
|
||||
background-color: transparent;
|
||||
color: ${({ theme }) => theme.deprecated_primary1};
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -217,11 +224,9 @@ export const ButtonText = styled(BaseButton)`
|
||||
background: none;
|
||||
text-decoration: none;
|
||||
&:focus {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
text-decoration: underline;
|
||||
}
|
||||
&:hover {
|
||||
// text-decoration: underline;
|
||||
opacity: 0.9;
|
||||
}
|
||||
&:active {
|
||||
@@ -235,38 +240,38 @@ export const ButtonText = styled(BaseButton)`
|
||||
|
||||
const ButtonConfirmedStyle = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg3};
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
/* border: 1px solid ${({ theme }) => theme.deprecated_green1}; */
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
/* border: 1px solid ${({ theme }) => theme.accentSuccess}; */
|
||||
|
||||
&:disabled {
|
||||
opacity: 50%;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
color: ${({ theme }) => theme.deprecated_text2};
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
cursor: auto;
|
||||
}
|
||||
`
|
||||
|
||||
const ButtonErrorStyle = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.deprecated_red1};
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_red1};
|
||||
background-color: ${({ theme }) => theme.accentFailure};
|
||||
border: 1px solid ${({ theme }) => theme.accentFailure};
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.deprecated_red1)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.deprecated_red1)};
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.accentFailure)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.accentFailure)};
|
||||
}
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => darken(0.05, theme.deprecated_red1)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.accentFailure)};
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.deprecated_red1)};
|
||||
background-color: ${({ theme }) => darken(0.1, theme.deprecated_red1)};
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.accentFailure)};
|
||||
background-color: ${({ theme }) => darken(0.1, theme.accentFailure)};
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
box-shadow: none;
|
||||
background-color: ${({ theme }) => theme.deprecated_red1};
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_red1};
|
||||
background-color: ${({ theme }) => theme.accentFailure};
|
||||
border: 1px solid ${({ theme }) => theme.accentFailure};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -314,14 +319,14 @@ export function ButtonDropdownLight({ disabled = false, children, ...rest }: { d
|
||||
|
||||
const ActiveOutlined = styled(ButtonOutlined)`
|
||||
border: 1px solid;
|
||||
border-color: ${({ theme }) => theme.deprecated_primary1};
|
||||
border-color: ${({ theme }) => theme.accentAction};
|
||||
`
|
||||
|
||||
const Circle = styled.div`
|
||||
height: 17px;
|
||||
width: 17px;
|
||||
border-radius: 50%;
|
||||
background-color: ${({ theme }) => theme.deprecated_primary1};
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -345,54 +350,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.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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,24 +10,20 @@ const Card = styled(Box)<{ width?: string; padding?: string; border?: string; $b
|
||||
export default Card
|
||||
|
||||
export const LightCard = styled(Card)`
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg2};
|
||||
border: 1px solid ${({ theme }) => theme.backgroundInteractive};
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
`
|
||||
|
||||
export const LightGrayCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
`
|
||||
|
||||
export const GrayCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg3};
|
||||
`
|
||||
|
||||
export const DarkGrayCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
`
|
||||
|
||||
export const DarkCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg0};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
`
|
||||
|
||||
export const OutlineCard = styled(Card)`
|
||||
@@ -42,6 +38,6 @@ export const YellowCard = styled(Card)`
|
||||
|
||||
export const BlueCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.deprecated_primary5};
|
||||
color: ${({ theme }) => theme.deprecated_blue2};
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
border-radius: 12px;
|
||||
`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Group } from '@visx/group'
|
||||
import { LinePath } from '@visx/shape'
|
||||
import { easeCubicInOut } from 'd3'
|
||||
import { easeSinOut } from 'd3'
|
||||
import ms from 'ms.macro'
|
||||
import React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { animated, useSpring } from 'react-spring'
|
||||
@@ -11,8 +12,8 @@ import { LineChartProps } from './LineChart'
|
||||
type AnimatedInLineChartProps<T> = Omit<LineChartProps<T>, 'height' | 'width' | 'children'>
|
||||
|
||||
const config = {
|
||||
duration: 800,
|
||||
easing: easeCubicInOut,
|
||||
duration: ms`0.8s`,
|
||||
easing: easeSinOut,
|
||||
}
|
||||
|
||||
// code reference: https://airbnb.io/visx/lineradial
|
||||
@@ -91,4 +92,4 @@ function AnimatedInLineChart<T>({
|
||||
)
|
||||
}
|
||||
|
||||
export default AnimatedInLineChart
|
||||
export default React.memo(AnimatedInLineChart) as typeof AnimatedInLineChart
|
||||
|
||||
86
src/components/Charts/FadeInLineChart.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Group } from '@visx/group'
|
||||
import { LinePath } from '@visx/shape'
|
||||
import { easeCubicInOut } from 'd3'
|
||||
import React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { animated, useSpring } from 'react-spring'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { LineChartProps } from './LineChart'
|
||||
|
||||
type FadedInLineChartProps<T> = Omit<LineChartProps<T>, 'height' | 'width' | 'children'> & { dashed?: boolean }
|
||||
|
||||
const config = {
|
||||
duration: 3000,
|
||||
easing: easeCubicInOut,
|
||||
}
|
||||
|
||||
// code reference: https://airbnb.io/visx/lineradial
|
||||
|
||||
function FadedInLineChart<T>({
|
||||
data,
|
||||
getX,
|
||||
getY,
|
||||
marginTop,
|
||||
curve,
|
||||
color,
|
||||
strokeWidth,
|
||||
dashed,
|
||||
}: FadedInLineChartProps<T>) {
|
||||
const lineRef = useRef<SVGPathElement>(null)
|
||||
const [lineLength, setLineLength] = useState(0)
|
||||
const [shouldAnimate, setShouldAnimate] = useState(false)
|
||||
const [hasAnimatedIn, setHasAnimatedIn] = useState(false)
|
||||
|
||||
const spring = useSpring({
|
||||
frame: shouldAnimate ? 0 : 1,
|
||||
config,
|
||||
onRest: () => {
|
||||
setShouldAnimate(false)
|
||||
setHasAnimatedIn(true)
|
||||
},
|
||||
})
|
||||
|
||||
// We need to check to see after the "invisble" line has been drawn
|
||||
// what the length is to be able to animate in the line for the first time
|
||||
// This will run on each render to see if there is a new line length
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => {
|
||||
if (lineRef.current) {
|
||||
const length = lineRef.current.getTotalLength()
|
||||
if (length !== lineLength) {
|
||||
setLineLength(length)
|
||||
}
|
||||
if (length > 0 && !shouldAnimate && !hasAnimatedIn) {
|
||||
setShouldAnimate(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
const theme = useTheme()
|
||||
const lineColor = color ?? theme.accentAction
|
||||
|
||||
return (
|
||||
<Group top={marginTop}>
|
||||
<LinePath curve={curve} x={getX} y={getY}>
|
||||
{({ path }) => {
|
||||
const d = path(data) || ''
|
||||
return (
|
||||
<>
|
||||
<animated.path
|
||||
d={d}
|
||||
ref={lineRef}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeOpacity={hasAnimatedIn ? 1 : spring.frame.to((v) => 1 - v)}
|
||||
fill="none"
|
||||
stroke={lineColor}
|
||||
strokeDasharray={dashed ? '4,4' : undefined}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</LinePath>
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(FadedInLineChart) as typeof FadedInLineChart
|
||||
@@ -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,9 +1,14 @@
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { DefaultTheme } from 'styled-components/macro'
|
||||
|
||||
const Column = styled.div`
|
||||
type Gap = keyof DefaultTheme['grids']
|
||||
|
||||
export const Column = styled.div<{
|
||||
gap?: Gap
|
||||
}>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: ${({ gap, theme }) => gap && theme.grids[gap]};
|
||||
`
|
||||
export const ColumnCenter = styled(Column)`
|
||||
width: 100%;
|
||||
@@ -11,12 +16,12 @@ export const ColumnCenter = styled(Column)`
|
||||
`
|
||||
|
||||
export const AutoColumn = styled.div<{
|
||||
gap?: 'sm' | 'md' | 'lg' | string
|
||||
gap?: Gap | string
|
||||
justify?: 'stretch' | 'center' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'space-between'
|
||||
}>`
|
||||
display: grid;
|
||||
grid-auto-rows: auto;
|
||||
grid-row-gap: ${({ gap }) => (gap === 'sm' && '8px') || (gap === 'md' && '12px') || (gap === 'lg' && '24px') || gap};
|
||||
grid-row-gap: ${({ gap, theme }) => (gap && theme.grids[gap as Gap]) || gap};
|
||||
justify-items: ${({ justify }) => justify && justify};
|
||||
`
|
||||
|
||||
|
||||
@@ -29,3 +29,19 @@ export const ScrollBarStyles = css<{ $isHorizontalScroll?: boolean }>`
|
||||
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,32 +0,0 @@
|
||||
import ReactConfetti from 'react-confetti'
|
||||
|
||||
import { useWindowSize } from '../../hooks/useWindowSize'
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
export default function Confetti({ start, variant }: { start: boolean; variant?: string }) {
|
||||
const { width, height } = useWindowSize()
|
||||
|
||||
const _variant = variant ? variant : height && width && height > 1.5 * width ? 'bottom' : variant
|
||||
|
||||
return start && width && height ? (
|
||||
<ReactConfetti
|
||||
style={{ zIndex: 1401 }}
|
||||
numberOfPieces={400}
|
||||
recycle={false}
|
||||
run={true}
|
||||
width={width}
|
||||
height={height}
|
||||
confettiSource={{
|
||||
h: height,
|
||||
w: width,
|
||||
x: 0,
|
||||
y: _variant === 'top' ? height * 0.25 : _variant === 'bottom' ? height * 0.75 : height * 0.5,
|
||||
}}
|
||||
initialVelocityX={15}
|
||||
initialVelocityY={30}
|
||||
gravity={0.45}
|
||||
tweenDuration={100}
|
||||
wind={0.05}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
@@ -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>
|
||||
@@ -54,7 +49,7 @@ export default function ConnectedAccountBlocked(props: ConnectedAccountBlockedPr
|
||||
fontSize={14}
|
||||
iconSize={16}
|
||||
gap={6}
|
||||
color={theme.deprecated_primary1}
|
||||
color={theme.accentAction}
|
||||
iconPosition="right"
|
||||
>
|
||||
compliance@uniswap.org
|
||||
|
||||
@@ -1,45 +1,72 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t } from '@lingui/macro'
|
||||
import { formatCurrencyAmount, formatPriceImpact, NumberType } from '@uniswap/conedison/format'
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import { useMemo } from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { ThemedText } from '../../theme'
|
||||
import { warningSeverity } from '../../utils/prices'
|
||||
import { MouseoverTooltip } from '../Tooltip'
|
||||
|
||||
const FiatLoadingBubble = styled(LoadingBubble)`
|
||||
border-radius: 4px;
|
||||
width: 4rem;
|
||||
height: 1rem;
|
||||
`
|
||||
|
||||
export function FiatValue({
|
||||
fiatValue,
|
||||
priceImpact,
|
||||
isLoading = false,
|
||||
}: {
|
||||
fiatValue: CurrencyAmount<Currency> | null | undefined
|
||||
priceImpact?: Percent
|
||||
isLoading?: boolean
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const [showLoadingPlaceholder, setShowLoadingPlaceholder] = useState(false)
|
||||
const priceImpactColor = useMemo(() => {
|
||||
if (!priceImpact) return undefined
|
||||
if (priceImpact.lessThan('0')) return theme.deprecated_green1
|
||||
if (priceImpact.lessThan('0')) return theme.accentSuccess
|
||||
const severity = warningSeverity(priceImpact)
|
||||
if (severity < 1) return theme.deprecated_text3
|
||||
if (severity < 1) return theme.textTertiary
|
||||
if (severity < 3) return theme.deprecated_yellow1
|
||||
return theme.deprecated_red1
|
||||
}, [priceImpact, theme.deprecated_green1, theme.deprecated_red1, theme.deprecated_text3, theme.deprecated_yellow1])
|
||||
return theme.accentFailure
|
||||
}, [priceImpact, theme.accentSuccess, theme.accentFailure, theme.textTertiary, theme.deprecated_yellow1])
|
||||
|
||||
const p = Number(fiatValue?.toFixed())
|
||||
const visibleDecimalPlaces = p < 1.05 ? 4 : 2
|
||||
useEffect(() => {
|
||||
const stale = false
|
||||
let timeoutId = 0
|
||||
if (isLoading && !fiatValue) {
|
||||
timeoutId = setTimeout(() => {
|
||||
if (!stale) setShowLoadingPlaceholder(true)
|
||||
}, 200) as unknown as number
|
||||
} else {
|
||||
setShowLoadingPlaceholder(false)
|
||||
}
|
||||
return () => clearTimeout(timeoutId)
|
||||
}, [isLoading, fiatValue])
|
||||
|
||||
return (
|
||||
<ThemedText.DeprecatedBody fontSize={14} color={theme.textSecondary}>
|
||||
{fiatValue && <>${fiatValue?.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })}</>}
|
||||
{priceImpact ? (
|
||||
<span style={{ color: priceImpactColor }}>
|
||||
{' '}
|
||||
<MouseoverTooltip text={t`The estimated difference between the USD values of input and output amounts.`}>
|
||||
(<Trans>{priceImpact.multiply(-1).toSignificant(3)}%</Trans>)
|
||||
</MouseoverTooltip>
|
||||
</span>
|
||||
) : null}
|
||||
{showLoadingPlaceholder ? (
|
||||
<FiatLoadingBubble />
|
||||
) : (
|
||||
<div>
|
||||
{fiatValue && <>{formatCurrencyAmount(fiatValue, NumberType.FiatTokenPrice)}</>}
|
||||
{priceImpact && (
|
||||
<span style={{ color: priceImpactColor }}>
|
||||
{' '}
|
||||
<MouseoverTooltip text={t`The estimated difference between the USD values of input and output amounts.`}>
|
||||
(<Trans>{formatPriceImpact(priceImpact)}</Trans>)
|
||||
</MouseoverTooltip>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</ThemedText.DeprecatedBody>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,18 +6,19 @@ import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { ReactNode, useCallback, useEffect, 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;
|
||||
@@ -61,7 +62,7 @@ const CurrencySelect = styled(ButtonGray)<{
|
||||
background-color: ${({ selected, theme }) => (selected ? theme.backgroundInteractive : theme.accentAction)};
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')};
|
||||
color: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
color: ${({ selected, theme }) => (selected ? theme.textPrimary : theme.white)};
|
||||
cursor: pointer;
|
||||
height: unset;
|
||||
border-radius: 16px;
|
||||
@@ -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;
|
||||
@@ -120,7 +121,7 @@ const LabelRow = styled.div`
|
||||
|
||||
span:hover {
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => darken(0.2, theme.deprecated_text2)};
|
||||
color: ${({ theme }) => darken(0.2, theme.textSecondary)};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -143,7 +144,7 @@ const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
|
||||
margin-left: 8px;
|
||||
|
||||
path {
|
||||
stroke: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
stroke: ${({ selected, theme }) => (selected ? theme.textPrimary : theme.white)};
|
||||
stroke-width: 2px;
|
||||
}
|
||||
`
|
||||
@@ -228,6 +229,7 @@ export default function SwapCurrencyInputPanel({
|
||||
...rest
|
||||
}: SwapCurrencyInputPanelProps) {
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const [fiatValueIsLoading, setFiatValueIsLoading] = useState(false)
|
||||
const { account, chainId } = useWeb3React()
|
||||
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
|
||||
const theme = useTheme()
|
||||
@@ -238,6 +240,10 @@ export default function SwapCurrencyInputPanel({
|
||||
|
||||
const chainAllowed = isSupportedChain(chainId)
|
||||
|
||||
useEffect(() => {
|
||||
!!value && !fiatValue ? setFiatValueIsLoading(true) : setFiatValueIsLoading(false)
|
||||
}, [fiatValueIsLoading, value, fiatValue])
|
||||
|
||||
return (
|
||||
<InputPanel id={id} hideInput={hideInput} {...rest}>
|
||||
{locked && (
|
||||
@@ -281,7 +287,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,11 +307,11 @@ export default function SwapCurrencyInputPanel({
|
||||
</Aligner>
|
||||
</CurrencySelect>
|
||||
</InputRow>
|
||||
{!hideInput && !hideBalance && currency && (
|
||||
{Boolean(!hideInput && !hideBalance) && (
|
||||
<FiatRow>
|
||||
<RowBetween>
|
||||
<LoadingOpacityContainer $loading={loading}>
|
||||
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
|
||||
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} isLoading={fiatValueIsLoading} />
|
||||
</LoadingOpacityContainer>
|
||||
{account ? (
|
||||
<RowFixed style={{ height: '17px' }}>
|
||||
|
||||
@@ -11,24 +11,25 @@ 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)};
|
||||
background-color: ${({ theme, hideInput }) => (hideInput ? 'transparent' : theme.backgroundInteractive)};
|
||||
z-index: 1;
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
transition: height 1s ease;
|
||||
@@ -40,7 +41,7 @@ const FixedContainer = styled.div`
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
border-radius: 20px;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -49,7 +50,7 @@ const FixedContainer = styled.div`
|
||||
|
||||
const Container = styled.div<{ hideInput: boolean; disabled: boolean }>`
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg0};
|
||||
border: 1px solid ${({ theme }) => theme.backgroundSurface};
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
${({ theme, hideInput, disabled }) =>
|
||||
@@ -69,11 +70,11 @@ const CurrencySelect = styled(ButtonGray)<{
|
||||
disabled?: boolean
|
||||
}>`
|
||||
align-items: center;
|
||||
background-color: ${({ selected, theme }) => (selected ? theme.deprecated_bg2 : theme.deprecated_primary1)};
|
||||
background-color: ${({ selected, theme }) => (selected ? theme.backgroundInteractive : theme.accentAction)};
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')};
|
||||
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
|
||||
color: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
color: ${({ selected, theme }) => (selected ? theme.textPrimary : theme.white)};
|
||||
cursor: pointer;
|
||||
border-radius: 16px;
|
||||
outline: none;
|
||||
@@ -88,29 +89,28 @@ const CurrencySelect = styled(ButtonGray)<{
|
||||
margin-left: ${({ hideInput }) => (hideInput ? '0' : '12px')};
|
||||
:focus,
|
||||
:hover {
|
||||
background-color: ${({ selected, theme }) =>
|
||||
selected ? theme.deprecated_bg3 : darken(0.05, theme.deprecated_primary1)};
|
||||
background-color: ${({ selected, theme }) => (selected ? theme.deprecated_bg3 : darken(0.05, theme.accentAction))};
|
||||
}
|
||||
visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
|
||||
`
|
||||
|
||||
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};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
padding: 0 1rem 1rem;
|
||||
span:hover {
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => darken(0.2, theme.deprecated_text2)};
|
||||
color: ${({ theme }) => darken(0.2, theme.textSecondary)};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -132,7 +132,7 @@ const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
|
||||
height: 35%;
|
||||
|
||||
path {
|
||||
stroke: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
stroke: ${({ selected, theme }) => (selected ? theme.textPrimary : theme.white)};
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
`
|
||||
@@ -147,7 +147,7 @@ const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
|
||||
background-color: ${({ theme }) => theme.deprecated_primary5};
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.deprecated_primary1};
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
@@ -269,7 +269,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">
|
||||
@@ -299,7 +299,7 @@ export default function CurrencyInputPanel({
|
||||
<RowFixed style={{ height: '17px' }}>
|
||||
<ThemedText.DeprecatedBody
|
||||
onClick={onMax}
|
||||
color={theme.deprecated_text3}
|
||||
color={theme.textTertiary}
|
||||
fontWeight={500}
|
||||
fontSize={14}
|
||||
style={{ display: 'inline', cursor: 'pointer' }}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink } from 'theme'
|
||||
|
||||
import { isL2ChainId } from '../../utils/chains'
|
||||
|
||||
const Root = styled.div`
|
||||
background-color: ${({ theme }) => (theme.darkMode ? '#888D9B' : '#CED0D9')};
|
||||
border-radius: 18px;
|
||||
color: black;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 14px;
|
||||
margin: 12px auto;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
max-width: 880px;
|
||||
`
|
||||
const WarningIcon = styled(AlertOctagon)`
|
||||
margin: auto 16px auto 0;
|
||||
min-height: 22px;
|
||||
min-width: 22px;
|
||||
`
|
||||
const ReadMoreLink = styled(ExternalLink)`
|
||||
color: black;
|
||||
text-decoration: underline;
|
||||
`
|
||||
|
||||
function Wrapper({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<Root>
|
||||
<WarningIcon />
|
||||
<div>{children}</div>
|
||||
</Root>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a downtime warning for the network if it's relevant
|
||||
*/
|
||||
export default function DowntimeWarning() {
|
||||
const { chainId } = useWeb3React()
|
||||
if (!isL2ChainId(chainId)) {
|
||||
return null
|
||||
}
|
||||
|
||||
switch (chainId) {
|
||||
case SupportedChainId.OPTIMISM:
|
||||
case SupportedChainId.OPTIMISM_GOERLI:
|
||||
return (
|
||||
<Wrapper>
|
||||
<Trans>
|
||||
Optimism is in Beta and may experience downtime. Optimism expects planned downtime to upgrade the network in
|
||||
the near future. During downtime, your position will not earn fees and you will be unable to remove
|
||||
liquidity.{' '}
|
||||
<ReadMoreLink href="https://help.uniswap.org/en/articles/5406082-what-happens-if-the-optimistic-ethereum-network-experiences-downtime">
|
||||
Read more.
|
||||
</ReadMoreLink>
|
||||
</Trans>
|
||||
</Wrapper>
|
||||
)
|
||||
case SupportedChainId.ARBITRUM_ONE:
|
||||
case SupportedChainId.ARBITRUM_RINKEBY:
|
||||
return (
|
||||
<Wrapper>
|
||||
<Trans>
|
||||
Arbitrum is in Beta and may experience downtime. During downtime, your position will not earn fees and you
|
||||
will be unable to remove liquidity.{' '}
|
||||
<ReadMoreLink href="https://help.uniswap.org/en/articles/5576122-arbitrum-network-downtime">
|
||||
Read more.
|
||||
</ReadMoreLink>
|
||||
</Trans>
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,156 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import React, { ErrorInfo, PropsWithChildren } from 'react'
|
||||
import * as Sentry from '@sentry/react'
|
||||
import { ButtonLight, SmallButtonPrimary } from 'components/Button'
|
||||
import { ChevronUpIcon } from 'nft/components/icons'
|
||||
import { useIsMobile } from 'nft/hooks'
|
||||
import React, { PropsWithChildren, useState } from 'react'
|
||||
import { Copy } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import store, { AppState } from '../../state'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
import { userAgent } from '../../utils/userAgent'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { AutoRow } from '../Row'
|
||||
import { CopyToClipboard, ExternalLink, ThemedText } from '../../theme'
|
||||
import { Column } from '../Column'
|
||||
|
||||
const FallbackWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
`
|
||||
|
||||
const BodyWrapper = styled.div<{ margin?: string }>`
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
margin: auto;
|
||||
padding: 1rem;
|
||||
`
|
||||
|
||||
const SmallButtonLight = styled(ButtonLight)`
|
||||
font-size: 16px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 12px;
|
||||
`
|
||||
|
||||
const StretchedRow = styled.div`
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
|
||||
> * {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
`
|
||||
|
||||
const Code = styled.code`
|
||||
font-weight: 300;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
word-wrap: break-word;
|
||||
width: 100%;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-family: ${({ theme }) => theme.fonts.code};
|
||||
overflow: scroll;
|
||||
max-height: calc(100vh - 450px);
|
||||
`
|
||||
|
||||
const Separator = styled.div`
|
||||
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
`
|
||||
|
||||
const CodeBlockWrapper = styled.div`
|
||||
background: ${({ theme }) => theme.deprecated_bg0};
|
||||
overflow: auto;
|
||||
white-space: pre;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: ${({ theme }) => theme.backgroundModule};
|
||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
|
||||
0px 24px 32px rgba(0, 0, 0, 0.01);
|
||||
border-radius: 24px;
|
||||
padding: 18px 24px;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
padding: 24px;
|
||||
gap: 10px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
|
||||
const LinkWrapper = styled.div`
|
||||
color: ${({ theme }) => theme.deprecated_blue1};
|
||||
padding: 6px 24px;
|
||||
const ShowMoreButton = styled.div`
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const SomethingWentWrongWrapper = styled.div`
|
||||
padding: 6px 24px;
|
||||
const CopyIcon = styled(Copy)`
|
||||
stroke: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
|
||||
type ErrorBoundaryState = {
|
||||
error: Error | null
|
||||
const ShowMoreIcon = styled(ChevronUpIcon)<{ $isExpanded?: boolean }>`
|
||||
transform: ${({ $isExpanded }) => ($isExpanded ? 'none' : 'rotate(180deg)')};
|
||||
`
|
||||
|
||||
const CodeTitle = styled.div`
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
word-break: break-word;
|
||||
`
|
||||
|
||||
const Fallback = ({ error, eventId }: { error: Error; eventId: string | null }) => {
|
||||
const [isExpanded, setExpanded] = useState(false)
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
const errorId = eventId || 'unknown-error'
|
||||
|
||||
// @todo: ThemedText components should be responsive by default
|
||||
const [Title, Description] = isMobile
|
||||
? [ThemedText.HeadlineSmall, ThemedText.BodySmall]
|
||||
: [ThemedText.HeadlineLarge, ThemedText.BodySecondary]
|
||||
|
||||
return (
|
||||
<FallbackWrapper>
|
||||
<BodyWrapper>
|
||||
<Column gap="xl">
|
||||
<Column gap="sm">
|
||||
<Title textAlign="center">
|
||||
<Trans>Something went wrong</Trans>
|
||||
</Title>
|
||||
<Description textAlign="center" color="textSecondary">
|
||||
<Trans>
|
||||
Sorry, an error occured while processing your request. If you request support, be sure to provide your
|
||||
error ID.
|
||||
</Trans>
|
||||
</Description>
|
||||
</Column>
|
||||
<CodeBlockWrapper>
|
||||
<CodeTitle>
|
||||
<ThemedText.SubHeader fontWeight={500}>Error ID: {errorId}</ThemedText.SubHeader>
|
||||
<CopyToClipboard toCopy={errorId}>
|
||||
<CopyIcon />
|
||||
</CopyToClipboard>
|
||||
</CodeTitle>
|
||||
<Separator />
|
||||
{isExpanded && (
|
||||
<>
|
||||
<Code>{error.stack}</Code>
|
||||
<Separator />
|
||||
</>
|
||||
)}
|
||||
<ShowMoreButton onClick={() => setExpanded((s) => !s)}>
|
||||
<ThemedText.Link color="textSecondary">
|
||||
<Trans>{isExpanded ? 'Show less' : 'Show more'}</Trans>
|
||||
</ThemedText.Link>
|
||||
<ShowMoreIcon $isExpanded={isExpanded} secondaryWidth="20" secondaryHeight="20" />
|
||||
</ShowMoreButton>
|
||||
</CodeBlockWrapper>
|
||||
<StretchedRow>
|
||||
<SmallButtonPrimary onClick={() => window.location.reload()}>
|
||||
<Trans>Reload the app</Trans>
|
||||
</SmallButtonPrimary>
|
||||
<ExternalLink id="get-support-on-discord" href="https://discord.gg/FCfyBSbCU5" target="_blank">
|
||||
<SmallButtonLight>
|
||||
<Trans>Get support</Trans>
|
||||
</SmallButtonLight>
|
||||
</ExternalLink>
|
||||
</StretchedRow>
|
||||
</Column>
|
||||
</BodyWrapper>
|
||||
</FallbackWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
const IS_UNISWAP = window.location.hostname === 'app.uniswap.org'
|
||||
|
||||
async function updateServiceWorker(): Promise<ServiceWorkerRegistration> {
|
||||
const ready = await navigator.serviceWorker.ready
|
||||
// the return type of update is incorrectly typed as Promise<void>. See
|
||||
@@ -55,157 +158,34 @@ async function updateServiceWorker(): Promise<ServiceWorkerRegistration> {
|
||||
return ready.update() as unknown as Promise<ServiceWorkerRegistration>
|
||||
}
|
||||
|
||||
export default class ErrorBoundary extends React.Component<PropsWithChildren<unknown>, ErrorBoundaryState> {
|
||||
constructor(props: PropsWithChildren<unknown>) {
|
||||
super(props)
|
||||
this.state = { error: null }
|
||||
}
|
||||
const updateServiceWorkerInBackground = async () => {
|
||||
try {
|
||||
const registration = await updateServiceWorker()
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
updateServiceWorker()
|
||||
.then(async (registration) => {
|
||||
// We want to refresh only if we detect a new service worker is waiting to be activated.
|
||||
// See details about it: https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle
|
||||
if (registration?.waiting) {
|
||||
await registration.unregister()
|
||||
// We want to refresh only if we detect a new service worker is waiting to be activated.
|
||||
// See details about it: https://web.dev/service-worker-lifecycle/
|
||||
if (registration?.waiting) {
|
||||
await registration.unregister()
|
||||
|
||||
// Makes Workbox call skipWaiting(). For more info on skipWaiting see: https://developer.chrome.com/docs/workbox/handling-service-worker-updates/
|
||||
registration.waiting.postMessage({ type: 'SKIP_WAITING' })
|
||||
|
||||
// Once the service worker is unregistered, we can reload the page to let
|
||||
// the browser download a fresh copy of our app (invalidating the cache)
|
||||
window.location.reload()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to update service worker', error)
|
||||
})
|
||||
return { error }
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
sendEvent('exception', {
|
||||
description: error.toString() + errorInfo.toString(),
|
||||
fatal: true,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { error } = this.state
|
||||
|
||||
if (error !== null) {
|
||||
const encodedBody = encodeURIComponent(issueBody(error))
|
||||
return (
|
||||
<FallbackWrapper>
|
||||
<BodyWrapper>
|
||||
<AutoColumn gap={'md'}>
|
||||
<SomethingWentWrongWrapper>
|
||||
<ThemedText.DeprecatedLabel fontSize={24} fontWeight={600}>
|
||||
<Trans>Something went wrong</Trans>
|
||||
</ThemedText.DeprecatedLabel>
|
||||
</SomethingWentWrongWrapper>
|
||||
<CodeBlockWrapper>
|
||||
<code>
|
||||
<ThemedText.DeprecatedMain fontSize={10}>{error.stack}</ThemedText.DeprecatedMain>
|
||||
</code>
|
||||
</CodeBlockWrapper>
|
||||
{IS_UNISWAP ? (
|
||||
<AutoRow>
|
||||
<LinkWrapper>
|
||||
<ExternalLink
|
||||
id="create-github-issue-link"
|
||||
href={`https://github.com/Uniswap/uniswap-interface/issues/new?assignees=&labels=bug&body=${encodedBody}&title=${encodeURIComponent(
|
||||
`Crash report: \`${error.name}${error.message && `: ${error.message}`}\``
|
||||
)}`}
|
||||
target="_blank"
|
||||
>
|
||||
<ThemedText.DeprecatedLink fontSize={16}>
|
||||
<Trans>Create an issue on GitHub</Trans>
|
||||
<span>↗</span>
|
||||
</ThemedText.DeprecatedLink>
|
||||
</ExternalLink>
|
||||
</LinkWrapper>
|
||||
<LinkWrapper>
|
||||
<ExternalLink id="get-support-on-discord" href="https://discord.gg/FCfyBSbCU5" target="_blank">
|
||||
<ThemedText.DeprecatedLink fontSize={16}>
|
||||
<Trans>Get support on Discord</Trans>
|
||||
<span>↗</span>
|
||||
</ThemedText.DeprecatedLink>
|
||||
</ExternalLink>
|
||||
</LinkWrapper>
|
||||
</AutoRow>
|
||||
) : null}
|
||||
</AutoColumn>
|
||||
</BodyWrapper>
|
||||
</FallbackWrapper>
|
||||
)
|
||||
// Makes Workbox call skipWaiting().
|
||||
// For more info on skipWaiting see: https://web.dev/service-worker-lifecycle/#skip-the-waiting-phase
|
||||
registration.waiting.postMessage({ type: 'SKIP_WAITING' })
|
||||
}
|
||||
return this.props.children
|
||||
} catch (error) {
|
||||
console.error('Failed to update service worker', error)
|
||||
}
|
||||
}
|
||||
|
||||
function getRelevantState(): null | keyof AppState {
|
||||
const path = window.location.hash
|
||||
if (!path.startsWith('#/')) {
|
||||
return null
|
||||
}
|
||||
const pieces = path.substring(2).split(/[/\\?]/)
|
||||
switch (pieces[0]) {
|
||||
case 'swap':
|
||||
return 'swap'
|
||||
case 'add':
|
||||
if (pieces[1] === 'v2') return 'mint'
|
||||
else return 'mintV3'
|
||||
case 'remove':
|
||||
if (pieces[1] === 'v2') return 'burn'
|
||||
else return 'burnV3'
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function issueBody(error: Error): string {
|
||||
const relevantState = getRelevantState()
|
||||
const deviceData = userAgent
|
||||
return `## URL
|
||||
|
||||
${window.location.href}
|
||||
|
||||
${
|
||||
relevantState
|
||||
? `## \`${relevantState}\` state
|
||||
|
||||
\`\`\`json
|
||||
${JSON.stringify(store.getState()[relevantState], null, 2)}
|
||||
\`\`\`
|
||||
`
|
||||
: ''
|
||||
}
|
||||
${
|
||||
error.name &&
|
||||
`## Error
|
||||
|
||||
\`\`\`
|
||||
${error.name}${error.message && `: ${error.message}`}
|
||||
\`\`\`
|
||||
`
|
||||
}
|
||||
${
|
||||
error.stack &&
|
||||
`## Stacktrace
|
||||
|
||||
\`\`\`
|
||||
${error.stack}
|
||||
\`\`\`
|
||||
`
|
||||
}
|
||||
${
|
||||
deviceData &&
|
||||
`## Device data
|
||||
|
||||
\`\`\`json
|
||||
${JSON.stringify(deviceData, null, 2)}
|
||||
\`\`\`
|
||||
`
|
||||
}
|
||||
`
|
||||
export default function ErrorBoundary({ children }: PropsWithChildren): JSX.Element {
|
||||
return (
|
||||
<Sentry.ErrorBoundary
|
||||
fallback={({ error, eventId }) => <Fallback error={error} eventId={eventId} />}
|
||||
beforeCapture={(scope) => {
|
||||
scope.setLevel('fatal')
|
||||
}}
|
||||
onError={updateServiceWorkerInBackground}
|
||||
>
|
||||
{children}
|
||||
</Sentry.ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { Permit2Variant, usePermit2Flag } from 'featureFlags/flags/permit2'
|
||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
|
||||
@@ -202,9 +202,12 @@ export default function FeatureFlagModal() {
|
||||
<X size={24} />
|
||||
</CloseButton>
|
||||
</Header>
|
||||
<FeatureFlagGroup name="Phase 1">
|
||||
<FeatureFlagOption variant={NftVariant} value={useNftFlag()} featureFlag={FeatureFlag.nft} label="NFTs" />
|
||||
</FeatureFlagGroup>
|
||||
<FeatureFlagOption
|
||||
variant={Permit2Variant}
|
||||
value={usePermit2Flag()}
|
||||
featureFlag={FeatureFlag.permit2}
|
||||
label="Permit 2 / Universal Router"
|
||||
/>
|
||||
<FeatureFlagGroup name="Debug">
|
||||
<FeatureFlagOption
|
||||
variant={TraceJsonRpcVariant}
|
||||
|
||||
@@ -34,8 +34,8 @@ const pulse = (color: string) => keyframes`
|
||||
}
|
||||
`
|
||||
const FocusedOutlineCard = styled(Card)<{ pulsing: boolean }>`
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg2};
|
||||
animation: ${({ pulsing, theme }) => pulsing && pulse(theme.deprecated_primary1)} 0.6s linear;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundInteractive};
|
||||
animation: ${({ pulsing, theme }) => pulsing && pulse(theme.accentAction)} 0.6s linear;
|
||||
align-self: center;
|
||||
`
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import { ConnectionType } from 'connection'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import styled from 'styled-components/macro'
|
||||
import { flexColumnNoWrap } from 'theme/styles'
|
||||
|
||||
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
|
||||
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
|
||||
@@ -11,7 +12,7 @@ import Identicon from '../Identicon'
|
||||
|
||||
export const IconWrapper = styled.div<{ size?: number }>`
|
||||
position: relative;
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
${flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 8px;
|
||||
|
||||
@@ -36,9 +36,9 @@ const SmallButton = styled(ButtonGray)`
|
||||
`
|
||||
|
||||
const FocusedOutlineCard = styled(OutlineCard)<{ active?: boolean; pulsing?: boolean }>`
|
||||
border-color: ${({ active, theme }) => active && theme.deprecated_blue1};
|
||||
border-color: ${({ active, theme }) => active && theme.accentAction};
|
||||
padding: 12px;
|
||||
animation: ${({ pulsing, theme }) => pulsing && pulse(theme.deprecated_blue1)} 0.8s linear;
|
||||
animation: ${({ pulsing, theme }) => pulsing && pulse(theme.accentAction)} 0.8s linear;
|
||||
`
|
||||
|
||||
const StyledInput = styled(NumericalInput)<{ usePercent?: boolean }>`
|
||||
@@ -58,13 +58,13 @@ const StyledInput = styled(NumericalInput)<{ usePercent?: boolean }>`
|
||||
`
|
||||
|
||||
const InputTitle = styled(ThemedText.DeprecatedSmall)`
|
||||
color: ${({ theme }) => theme.deprecated_text2};
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
const ButtonLabel = styled(ThemedText.DeprecatedWhite)<{ disabled: boolean }>`
|
||||
color: ${({ theme, disabled }) => (disabled ? theme.deprecated_text2 : theme.deprecated_text1)} !important;
|
||||
color: ${({ theme, disabled }) => (disabled ? theme.textSecondary : theme.textPrimary)} !important;
|
||||
`
|
||||
|
||||
interface StepCounterProps {
|
||||
|
||||
@@ -6,8 +6,8 @@ import { ChartEntry } from './types'
|
||||
|
||||
const Path = styled.path<{ fill: string | undefined }>`
|
||||
opacity: 0.5;
|
||||
stroke: ${({ fill, theme }) => fill ?? theme.deprecated_blue2};
|
||||
fill: ${({ fill, theme }) => fill ?? theme.deprecated_blue2};
|
||||
stroke: ${({ fill, theme }) => fill ?? theme.accentAction};
|
||||
fill: ${({ fill, theme }) => fill ?? theme.accentAction};
|
||||
`
|
||||
|
||||
export const Area = ({
|
||||
|
||||
@@ -8,7 +8,7 @@ const StyledGroup = styled.g`
|
||||
}
|
||||
|
||||
text {
|
||||
color: ${({ theme }) => theme.deprecated_text2};
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
transform: translateY(5px);
|
||||
}
|
||||
`
|
||||
|
||||
@@ -18,7 +18,7 @@ const HandleAccent = styled.path`
|
||||
pointer-events: none;
|
||||
|
||||
stroke-width: 1.5;
|
||||
stroke: ${({ theme }) => theme.deprecated_white};
|
||||
stroke: ${({ theme }) => theme.white};
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
`
|
||||
|
||||
@@ -28,13 +28,13 @@ const LabelGroup = styled.g<{ visible: boolean }>`
|
||||
`
|
||||
|
||||
const TooltipBackground = styled.rect`
|
||||
fill: ${({ theme }) => theme.deprecated_bg2};
|
||||
fill: ${({ theme }) => theme.backgroundInteractive};
|
||||
`
|
||||
|
||||
const Tooltip = styled.text`
|
||||
text-anchor: middle;
|
||||
font-size: 13px;
|
||||
fill: ${({ theme }) => theme.deprecated_text1};
|
||||
fill: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
|
||||
// flips the handles draggers when close to the container edges
|
||||
@@ -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>
|
||||
|
||||
@@ -9,8 +9,8 @@ import { Line } from './Line'
|
||||
import { ChartEntry, LiquidityChartRangeInputProps } from './types'
|
||||
import Zoom, { ZoomOverlay } from './Zoom'
|
||||
|
||||
export const xAccessor = (d: ChartEntry) => d.price0
|
||||
export const yAccessor = (d: ChartEntry) => d.activeLiquidity
|
||||
const xAccessor = (d: ChartEntry) => d.price0
|
||||
const yAccessor = (d: ChartEntry) => d.activeLiquidity
|
||||
|
||||
export function Chart({
|
||||
id = 'liquidityChartRangeInput',
|
||||
|
||||
@@ -5,7 +5,7 @@ import styled from 'styled-components/macro'
|
||||
const StyledLine = styled.line`
|
||||
opacity: 0.5;
|
||||
stroke-width: 2;
|
||||
stroke: ${({ theme }) => theme.deprecated_text1};
|
||||
stroke: ${({ theme }) => theme.textPrimary};
|
||||
fill: none;
|
||||
`
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ const Wrapper = styled.div<{ count: number }>`
|
||||
|
||||
const Button = styled(ButtonGray)`
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
}
|
||||
|
||||
width: 32px;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import * as Sentry from '@sentry/react'
|
||||
import { Currency, Price, Token } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn, ColumnCenter } from 'components/Column'
|
||||
import Loader from 'components/Loader'
|
||||
import { format } from 'd3'
|
||||
@@ -157,7 +157,7 @@ export default function LiquidityChartRangeInput({
|
||||
)
|
||||
|
||||
if (error) {
|
||||
sendEvent('exception', { description: error.toString(), fatal: false })
|
||||
Sentry.captureMessage(error.toString(), 'log')
|
||||
}
|
||||
|
||||
const isUninitialized = !currencyA || !currencyB || (formattedData === undefined && !isLoading)
|
||||
@@ -167,7 +167,7 @@ export default function LiquidityChartRangeInput({
|
||||
{isUninitialized ? (
|
||||
<InfoBox
|
||||
message={<Trans>Your position will appear here.</Trans>}
|
||||
icon={<Inbox size={56} stroke={theme.deprecated_text1} />}
|
||||
icon={<Inbox size={56} stroke={theme.textPrimary} />}
|
||||
/>
|
||||
) : isLoading ? (
|
||||
<InfoBox icon={<Loader size="40px" stroke={theme.deprecated_text4} />} />
|
||||
@@ -189,12 +189,12 @@ export default function LiquidityChartRangeInput({
|
||||
margins={{ top: 10, right: 2, bottom: 20, left: 0 }}
|
||||
styles={{
|
||||
area: {
|
||||
selection: theme.deprecated_blue1,
|
||||
selection: theme.accentAction,
|
||||
},
|
||||
brush: {
|
||||
handle: {
|
||||
west: saturate(0.1, tokenAColor) ?? theme.deprecated_red1,
|
||||
east: saturate(0.1, tokenBColor) ?? theme.deprecated_blue1,
|
||||
west: saturate(0.1, tokenAColor) ?? theme.accentFailure,
|
||||
east: saturate(0.1, tokenBColor) ?? theme.accentAction,
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
@@ -5,12 +5,12 @@ export interface ChartEntry {
|
||||
price0: number
|
||||
}
|
||||
|
||||
export interface Dimensions {
|
||||
interface Dimensions {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export interface Margins {
|
||||
interface Margins {
|
||||
top: number
|
||||
right: number
|
||||
bottom: number
|
||||
|
||||
@@ -18,7 +18,7 @@ export const LoadingRows = styled.div`
|
||||
background: linear-gradient(
|
||||
to left,
|
||||
${({ theme }) => theme.deprecated_bg1} 25%,
|
||||
${({ theme }) => theme.deprecated_bg2} 50%,
|
||||
${({ theme }) => theme.backgroundInteractive} 50%,
|
||||
${({ theme }) => theme.deprecated_bg1} 75%
|
||||
);
|
||||
background-size: 400%;
|
||||
|
||||
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'
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,38 +1,12 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
|
||||
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
|
||||
import { L2_CHAIN_IDS } from 'constants/chains'
|
||||
import { LOCALE_LABEL, SUPPORTED_LOCALES, SupportedLocale } from 'constants/locales'
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
import { useLocationLinkProps } from 'hooks/useLocationLinkProps'
|
||||
import { FunctionComponent, PropsWithChildren, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
BookOpen,
|
||||
Check,
|
||||
ChevronLeft,
|
||||
Coffee,
|
||||
FileText,
|
||||
Flag,
|
||||
Globe,
|
||||
HelpCircle,
|
||||
Info,
|
||||
MessageCircle,
|
||||
Moon,
|
||||
Sun,
|
||||
} from 'react-feather'
|
||||
import { FunctionComponent, PropsWithChildren, useRef } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useDarkModeManager } from 'state/user/hooks'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { isDevelopmentEnv, isStagingEnv } from 'utils/env'
|
||||
|
||||
import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
import { useModalIsOpen, useToggleModal } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
|
||||
export enum FlyoutAlignment {
|
||||
LEFT = 'LEFT',
|
||||
@@ -41,41 +15,10 @@ export enum FlyoutAlignment {
|
||||
|
||||
const StyledMenuIcon = styled(MenuIcon)`
|
||||
path {
|
||||
stroke: ${({ theme }) => theme.deprecated_text1};
|
||||
stroke: ${({ theme }) => theme.textPrimary};
|
||||
}
|
||||
`
|
||||
|
||||
const StyledMenuButton = styled.button`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 40px;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg0};
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg0};
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 16px;
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg3};
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-top: 2px;
|
||||
}
|
||||
`
|
||||
|
||||
const UNIbutton = styled(ButtonPrimary)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg3};
|
||||
background: radial-gradient(174.47% 188.91% at 1.84% 0%, #ff007a 0%, #2172e5 100%), #edeef2;
|
||||
border: none;
|
||||
`
|
||||
|
||||
const StyledMenu = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -92,7 +35,7 @@ const MenuFlyout = styled.span<{ flyoutAlignment?: FlyoutAlignment }>`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
|
||||
0px 24px 32px rgba(0, 0, 0, 0.01);
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg0};
|
||||
border: 1px solid ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 12px;
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
@@ -124,9 +67,9 @@ const MenuItem = styled(ExternalLink)`
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.5rem;
|
||||
justify-content: space-between;
|
||||
color: ${({ theme }) => theme.deprecated_text2};
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -135,9 +78,11 @@ const MenuItem = styled(ExternalLink)`
|
||||
const InternalMenuItem = styled(Link)`
|
||||
flex: 1;
|
||||
padding: 0.5rem 0.5rem;
|
||||
color: ${({ theme }) => theme.deprecated_text2};
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
width: max-content;
|
||||
text-decoration: none;
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -146,180 +91,7 @@ const InternalMenuItem = styled(Link)`
|
||||
}
|
||||
`
|
||||
|
||||
const InternalLinkMenuItem = styled(InternalMenuItem)`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.5rem;
|
||||
justify-content: space-between;
|
||||
text-decoration: none;
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
|
||||
const ToggleMenuItem = styled.button`
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.5rem;
|
||||
justify-content: space-between;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: ${({ theme }) => theme.deprecated_text2};
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
|
||||
function LanguageMenuItem({ locale, active, key }: { locale: SupportedLocale; active: boolean; key: string }) {
|
||||
const { to, onClick } = useLocationLinkProps(locale)
|
||||
|
||||
if (!to) return null
|
||||
|
||||
return (
|
||||
<InternalLinkMenuItem onClick={onClick} key={key} to={to}>
|
||||
<div>{LOCALE_LABEL[locale]}</div>
|
||||
{active && <Check opacity={0.6} size={16} />}
|
||||
</InternalLinkMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
function LanguageMenu({ close }: { close: () => void }) {
|
||||
const activeLocale = useActiveLocale()
|
||||
|
||||
return (
|
||||
<MenuFlyout>
|
||||
<ToggleMenuItem onClick={close}>
|
||||
<ChevronLeft size={16} />
|
||||
</ToggleMenuItem>
|
||||
{SUPPORTED_LOCALES.map((locale) => (
|
||||
<LanguageMenuItem locale={locale} active={activeLocale === locale} key={locale} />
|
||||
))}
|
||||
</MenuFlyout>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Menu() {
|
||||
const { account, chainId } = useWeb3React()
|
||||
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const open = useModalIsOpen(ApplicationModal.MENU)
|
||||
const toggleMenu = useToggleModal(ApplicationModal.MENU)
|
||||
useOnClickOutside(node, open ? toggleMenu : undefined)
|
||||
const togglePrivacyPolicy = useToggleModal(ApplicationModal.PRIVACY_POLICY)
|
||||
const openFeatureFlagsModal = useToggleModal(ApplicationModal.FEATURE_FLAGS)
|
||||
const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
|
||||
const showUNIClaimOption = Boolean(!!account && !!chainId && !L2_CHAIN_IDS.includes(chainId))
|
||||
|
||||
const [darkMode, toggleDarkMode] = useDarkModeManager()
|
||||
|
||||
const [menu, setMenu] = useState<'main' | 'lang'>('main')
|
||||
|
||||
useEffect(() => {
|
||||
setMenu('main')
|
||||
}, [open])
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451 */}
|
||||
<StyledMenu ref={node as any}>
|
||||
<StyledMenuButton onClick={toggleMenu} aria-label={t`Menu`}>
|
||||
<StyledMenuIcon />
|
||||
</StyledMenuButton>
|
||||
|
||||
{open &&
|
||||
(() => {
|
||||
switch (menu) {
|
||||
case 'lang':
|
||||
return <LanguageMenu close={() => setMenu('main')} />
|
||||
case 'main':
|
||||
default:
|
||||
return (
|
||||
<MenuFlyout>
|
||||
<MenuItem href="https://uniswap.org/">
|
||||
<div>
|
||||
<Trans>About</Trans>
|
||||
</div>
|
||||
<Info opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
<MenuItem href="https://help.uniswap.org/">
|
||||
<div>
|
||||
<Trans>Help Center</Trans>
|
||||
</div>
|
||||
<HelpCircle opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
<MenuItem href="https://uniswap.canny.io/feature-requests">
|
||||
<div>
|
||||
<Trans>Request Features</Trans>
|
||||
</div>
|
||||
<Coffee opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
<MenuItem href="https://discord.gg/FCfyBSbCU5">
|
||||
<div>
|
||||
<Trans>Discord</Trans>
|
||||
</div>
|
||||
<MessageCircle opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
<ToggleMenuItem onClick={() => setMenu('lang')}>
|
||||
<div>
|
||||
<Trans>Language</Trans>
|
||||
</div>
|
||||
<Globe opacity={0.6} size={16} />
|
||||
</ToggleMenuItem>
|
||||
<ToggleMenuItem onClick={() => toggleDarkMode()}>
|
||||
<div>{darkMode ? <Trans>Light Theme</Trans> : <Trans>Dark Theme</Trans>}</div>
|
||||
{darkMode ? <Sun opacity={0.6} size={16} /> : <Moon opacity={0.6} size={16} />}
|
||||
</ToggleMenuItem>
|
||||
<MenuItem href="https://docs.uniswap.org/">
|
||||
<div>
|
||||
<Trans>Docs</Trans>
|
||||
</div>
|
||||
<BookOpen opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
<ToggleMenuItem onClick={() => togglePrivacyPolicy()}>
|
||||
<div>
|
||||
<Trans>Legal & Privacy</Trans>
|
||||
</div>
|
||||
<FileText opacity={0.6} size={16} />
|
||||
</ToggleMenuItem>
|
||||
{(isDevelopmentEnv() || isStagingEnv()) && (
|
||||
<ToggleMenuItem onClick={openFeatureFlagsModal}>
|
||||
Feature Flags <Flag opacity={0.6} size={16} />
|
||||
</ToggleMenuItem>
|
||||
)}
|
||||
{showUNIClaimOption && (
|
||||
<UNIbutton
|
||||
onClick={openClaimModal}
|
||||
padding="8px 16px"
|
||||
width="100%"
|
||||
$borderRadius="12px"
|
||||
mt="0.5rem"
|
||||
>
|
||||
<Trans>Claim UNI</Trans>
|
||||
</UNIbutton>
|
||||
)}
|
||||
</MenuFlyout>
|
||||
)
|
||||
}
|
||||
})()}
|
||||
</StyledMenu>
|
||||
<PrivacyPolicyModal />
|
||||
<FeatureFlagModal />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
interface NewMenuProps {
|
||||
interface MenuProps {
|
||||
flyoutAlignment?: FlyoutAlignment
|
||||
ToggleUI?: FunctionComponent<PropsWithChildren<unknown>>
|
||||
menuItems: {
|
||||
@@ -329,20 +101,12 @@ interface NewMenuProps {
|
||||
}[]
|
||||
}
|
||||
|
||||
const NewMenuFlyout = styled(MenuFlyout)`
|
||||
top: 3rem !important;
|
||||
`
|
||||
const NewMenuItem = styled(InternalMenuItem)`
|
||||
width: max-content;
|
||||
text-decoration: none;
|
||||
`
|
||||
|
||||
const ExternalMenuItem = styled(MenuItem)`
|
||||
width: max-content;
|
||||
text-decoration: none;
|
||||
`
|
||||
|
||||
export const NewMenu = ({ flyoutAlignment = FlyoutAlignment.RIGHT, ToggleUI, menuItems, ...rest }: NewMenuProps) => {
|
||||
export const Menu = ({ flyoutAlignment = FlyoutAlignment.RIGHT, ToggleUI, menuItems, ...rest }: MenuProps) => {
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const open = useModalIsOpen(ApplicationModal.POOL_OVERVIEW_OPTIONS)
|
||||
const toggle = useToggleModal(ApplicationModal.POOL_OVERVIEW_OPTIONS)
|
||||
@@ -352,19 +116,19 @@ export const NewMenu = ({ flyoutAlignment = FlyoutAlignment.RIGHT, ToggleUI, men
|
||||
<StyledMenu ref={node as any} {...rest}>
|
||||
<ToggleElement onClick={toggle} />
|
||||
{open && (
|
||||
<NewMenuFlyout flyoutAlignment={flyoutAlignment}>
|
||||
<MenuFlyout flyoutAlignment={flyoutAlignment}>
|
||||
{menuItems.map(({ content, link, external }, i) =>
|
||||
external ? (
|
||||
<ExternalMenuItem href={link} key={i}>
|
||||
{content}
|
||||
</ExternalMenuItem>
|
||||
) : (
|
||||
<NewMenuItem to={link} key={i}>
|
||||
<InternalMenuItem to={link} key={i}>
|
||||
{content}
|
||||
</NewMenuItem>
|
||||
</InternalMenuItem>
|
||||
)
|
||||
)}
|
||||
</NewMenuFlyout>
|
||||
</MenuFlyout>
|
||||
)}
|
||||
</StyledMenu>
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Z_INDEX } from 'theme/zIndex'
|
||||
import { isMobile } from '../../utils/userAgent'
|
||||
|
||||
const AnimatedDialogOverlay = animated(DialogOverlay)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ scrollOverlay?: boolean }>`
|
||||
&[data-reach-dialog-overlay] {
|
||||
z-index: ${Z_INDEX.modalBackdrop};
|
||||
@@ -24,49 +24,51 @@ const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ scrollOverlay?: bool
|
||||
}
|
||||
`
|
||||
|
||||
type StyledDialogProps = {
|
||||
$minHeight?: number | false
|
||||
$maxHeight?: number
|
||||
$isBottomSheet?: boolean
|
||||
$scrollOverlay?: boolean
|
||||
$hideBorder?: boolean
|
||||
$maxWidth: number
|
||||
}
|
||||
|
||||
const AnimatedDialogContent = animated(DialogContent)
|
||||
// destructure to not pass custom props to Dialog DOM element
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, scrollOverlay, ...rest }) => (
|
||||
<AnimatedDialogContent {...rest} />
|
||||
)).attrs({
|
||||
'aria-label': 'dialog',
|
||||
})`
|
||||
const StyledDialogContent = styled(AnimatedDialogContent)<StyledDialogProps>`
|
||||
overflow-y: auto;
|
||||
|
||||
&[data-reach-dialog-content] {
|
||||
margin: auto;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg0};
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg1};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border: ${({ theme, $hideBorder }) => !$hideBorder && `1px solid ${theme.deprecated_bg1}`};
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
padding: 0px;
|
||||
width: 50vw;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
align-self: ${({ mobile }) => mobile && 'flex-end'};
|
||||
|
||||
max-width: 420px;
|
||||
${({ maxHeight }) =>
|
||||
maxHeight &&
|
||||
align-self: ${({ $isBottomSheet }) => $isBottomSheet && 'flex-end'};
|
||||
max-width: ${({ $maxWidth }) => $maxWidth}px;
|
||||
${({ $maxHeight }) =>
|
||||
$maxHeight &&
|
||||
css`
|
||||
max-height: ${maxHeight}vh;
|
||||
max-height: ${$maxHeight}vh;
|
||||
`}
|
||||
${({ minHeight }) =>
|
||||
minHeight &&
|
||||
${({ $minHeight }) =>
|
||||
$minHeight &&
|
||||
css`
|
||||
min-height: ${minHeight}vh;
|
||||
min-height: ${$minHeight}vh;
|
||||
`}
|
||||
display: ${({ scrollOverlay }) => (scrollOverlay ? 'inline-table' : 'flex')};
|
||||
display: ${({ $scrollOverlay }) => ($scrollOverlay ? 'inline-table' : 'flex')};
|
||||
border-radius: 20px;
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
width: 65vw;
|
||||
margin: auto;
|
||||
`}
|
||||
${({ theme, mobile }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
${({ theme, $isBottomSheet }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
width: 85vw;
|
||||
${
|
||||
mobile &&
|
||||
$isBottomSheet &&
|
||||
css`
|
||||
width: 100vw;
|
||||
border-radius: 20px;
|
||||
@@ -80,12 +82,16 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, scro
|
||||
|
||||
interface ModalProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
onDismiss?: () => void
|
||||
onSwipe?: () => void
|
||||
minHeight?: number | false
|
||||
maxHeight?: number
|
||||
maxWidth?: number
|
||||
initialFocusRef?: React.RefObject<any>
|
||||
children?: React.ReactNode
|
||||
scrollOverlay?: boolean
|
||||
hideBorder?: boolean
|
||||
isBottomSheet?: boolean
|
||||
}
|
||||
|
||||
export default function Modal({
|
||||
@@ -93,9 +99,13 @@ export default function Modal({
|
||||
onDismiss,
|
||||
minHeight = false,
|
||||
maxHeight = 90,
|
||||
maxWidth = 420,
|
||||
initialFocusRef,
|
||||
children,
|
||||
onSwipe = onDismiss,
|
||||
scrollOverlay,
|
||||
isBottomSheet = isMobile,
|
||||
hideBorder = false,
|
||||
}: ModalProps) {
|
||||
const fadeTransition = useTransition(isOpen, {
|
||||
config: { duration: 200 },
|
||||
@@ -111,7 +121,7 @@ export default function Modal({
|
||||
y: state.down ? state.movement[1] : 0,
|
||||
})
|
||||
if (state.movement[1] > 300 || (state.velocity > 3 && state.direction[1] > 0)) {
|
||||
onDismiss()
|
||||
onSwipe?.()
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -122,7 +132,6 @@ export default function Modal({
|
||||
({ opacity }, item) =>
|
||||
item && (
|
||||
<StyledDialogOverlay
|
||||
as={AnimatedDialogOverlay}
|
||||
style={{ opacity: opacity.to({ range: [0.0, 1.0], output: [0, 1] }) }}
|
||||
onDismiss={onDismiss}
|
||||
initialFocusRef={initialFocusRef}
|
||||
@@ -136,11 +145,13 @@ export default function Modal({
|
||||
style: { transform: y.interpolate((y) => `translateY(${(y as number) > 0 ? y : 0}px)`) },
|
||||
}
|
||||
: {})}
|
||||
aria-label="dialog content"
|
||||
minHeight={minHeight}
|
||||
maxHeight={maxHeight}
|
||||
mobile={isMobile}
|
||||
scrollOverlay={scrollOverlay}
|
||||
aria-label="dialog"
|
||||
$minHeight={minHeight}
|
||||
$maxHeight={maxHeight}
|
||||
$isBottomSheet={isBottomSheet}
|
||||
$scrollOverlay={scrollOverlay}
|
||||
$hideBorder={hideBorder}
|
||||
$maxWidth={maxWidth}
|
||||
>
|
||||
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
|
||||
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}
|
||||
|
||||
@@ -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>
|
||||
@@ -58,9 +58,9 @@ export function SubmittedView({
|
||||
<CloseIcon onClick={onDismiss} />
|
||||
</RowBetween>
|
||||
<ConfirmedIcon>
|
||||
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.deprecated_primary1} />
|
||||
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.accentAction} />
|
||||
</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>
|
||||
)}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { sprinkles, vars } from '../../nft/css/sprinkles.css'
|
||||
export const navIcon = style([
|
||||
sprinkles({
|
||||
alignItems: 'center',
|
||||
background: 'transparent',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
@@ -14,7 +14,6 @@ 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"
|
||||
|
||||
@@ -3,8 +3,11 @@ import { buttonTextSmall, subhead, subheadSmall } from 'nft/css/common.css'
|
||||
|
||||
import { breakpoints, sprinkles, vars } from '../../nft/css/sprinkles.css'
|
||||
|
||||
const DESKTOP_NAVBAR_WIDTH = 360
|
||||
const MAGNIFYING_GLASS_ICON_WIDTH = 28
|
||||
const DESKTOP_NAVBAR_WIDTH = 330
|
||||
const DESKTOP_NAVBAR_WIDTH_MD = 360
|
||||
const DESKTOP_NAVBAR_WIDTH_L = 480
|
||||
const DESKTOP_NAVBAR_WIDTH_XL = 520
|
||||
const DESKTOP_NAVBAR_WIDTH_XXL = 640
|
||||
|
||||
const baseSearchStyle = style([
|
||||
sprinkles({
|
||||
@@ -12,13 +15,37 @@ const baseSearchStyle = style([
|
||||
width: { sm: 'viewWidth' },
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '1px',
|
||||
borderColor: 'backgroundOutline',
|
||||
borderColor: 'searchOutline',
|
||||
}),
|
||||
{
|
||||
backdropFilter: 'blur(60px)',
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.sm}px)`]: {
|
||||
width: `${DESKTOP_NAVBAR_WIDTH_MD}px`,
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const baseSearchNftStyle = style([
|
||||
baseSearchStyle,
|
||||
{
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.md}px)`]: {
|
||||
width: `${DESKTOP_NAVBAR_WIDTH}px`,
|
||||
},
|
||||
[`screen and (min-width: ${breakpoints.lg}px)`]: {
|
||||
width: `${DESKTOP_NAVBAR_WIDTH_MD}px`,
|
||||
},
|
||||
[`screen and (min-width: ${breakpoints.xl}px)`]: {
|
||||
width: `${DESKTOP_NAVBAR_WIDTH_L}px`,
|
||||
},
|
||||
[`screen and (min-width: ${breakpoints.xxl}px)`]: {
|
||||
width: `${DESKTOP_NAVBAR_WIDTH_XL}px`,
|
||||
},
|
||||
[`screen and (min-width: ${breakpoints.xxxl}px)`]: {
|
||||
width: `${DESKTOP_NAVBAR_WIDTH_XXL}px`,
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
@@ -30,31 +57,46 @@ export const searchBarContainer = style([
|
||||
zIndex: '3',
|
||||
display: 'inline-block',
|
||||
}),
|
||||
])
|
||||
|
||||
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 - MAGNIFYING_GLASS_ICON_WIDTH}px`,
|
||||
top: '-3px',
|
||||
},
|
||||
},
|
||||
backdropFilter: 'blur(60px)',
|
||||
borderRadius: '12px',
|
||||
},
|
||||
])
|
||||
|
||||
export const searchBar = style([
|
||||
baseSearchStyle,
|
||||
sprinkles({
|
||||
color: 'textTertiary',
|
||||
color: 'textSecondary',
|
||||
paddingX: '16',
|
||||
background: 'backgroundSurface',
|
||||
}),
|
||||
])
|
||||
|
||||
export const nftSearchBar = style([
|
||||
baseSearchNftStyle,
|
||||
sprinkles({
|
||||
color: 'textSecondary',
|
||||
paddingX: '16',
|
||||
}),
|
||||
{
|
||||
backdropFilter: 'blur(60px)',
|
||||
},
|
||||
])
|
||||
|
||||
export const searchBarInput = style([
|
||||
sprinkles({
|
||||
padding: '0',
|
||||
fontWeight: 'normal',
|
||||
fontSize: '16',
|
||||
color: { default: 'textPrimary', placeholder: 'textTertiary' },
|
||||
color: { default: 'textPrimary', placeholder: 'textSecondary' },
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
lineHeight: '24',
|
||||
@@ -62,15 +104,16 @@ export const searchBarInput = style([
|
||||
}),
|
||||
])
|
||||
|
||||
export const searchBarDropdown = style([
|
||||
baseSearchStyle,
|
||||
export const searchBarDropdownNft = style([
|
||||
baseSearchNftStyle,
|
||||
sprinkles({
|
||||
borderBottomLeftRadius: '12',
|
||||
borderBottomRightRadius: '12',
|
||||
background: 'backgroundSurface',
|
||||
height: { sm: 'viewHeight', md: 'auto' },
|
||||
backgroundColor: 'backgroundSurface',
|
||||
}),
|
||||
{
|
||||
backdropFilter: 'blur(60px)',
|
||||
borderTop: 'none',
|
||||
},
|
||||
])
|
||||
@@ -168,9 +211,6 @@ export const notFoundContainer = style([
|
||||
}),
|
||||
])
|
||||
|
||||
const visibilityTransition = `visibility ${vars.time[125]}, opacity ${vars.time[125]}`
|
||||
const delayedTransitionProperties = `padding 0s ${vars.time[125]}, height 0s ${vars.time[125]}`
|
||||
|
||||
export const hidden = style([
|
||||
sprinkles({
|
||||
visibility: 'hidden',
|
||||
@@ -178,10 +218,6 @@ export const hidden = style([
|
||||
padding: '0',
|
||||
height: '0',
|
||||
}),
|
||||
{
|
||||
transition: `${visibilityTransition}, ${delayedTransitionProperties}`,
|
||||
transitionTimingFunction: 'ease-in',
|
||||
},
|
||||
])
|
||||
export const visible = style([
|
||||
sprinkles({
|
||||
@@ -189,22 +225,8 @@ export const visible = style([
|
||||
opacity: '1',
|
||||
height: 'full',
|
||||
}),
|
||||
{
|
||||
transition: `${visibilityTransition}`,
|
||||
transitionTimingFunction: 'ease-out',
|
||||
},
|
||||
])
|
||||
|
||||
export const searchContentCentered = style({
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.lg}px)`]: {
|
||||
transform: `translateX(${DESKTOP_NAVBAR_WIDTH / 4}px)`,
|
||||
transition: `transform ${vars.time[125]}`,
|
||||
transitionTimingFunction: 'ease-out',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const searchContentLeftAlign = style({
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.lg}px)`]: {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t } from '@lingui/macro'
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
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'
|
||||
@@ -14,15 +13,32 @@ import { magicalGradientOnHover } from 'nft/css/common.css'
|
||||
import { useIsMobile, useIsTablet } from 'nft/hooks'
|
||||
import { fetchSearchCollections } from 'nft/queries'
|
||||
import { fetchSearchTokens } from 'nft/queries/genie/SearchTokensFetcher'
|
||||
import { ChangeEvent, useEffect, useReducer, useRef, useState } from 'react'
|
||||
import { ChangeEvent, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
|
||||
import { useQuery } from 'react-query'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { ChevronLeftIcon, MagnifyingGlassIcon, NavMagnifyingGlassIcon } from '../../nft/components/icons'
|
||||
import { NavIcon } from './NavIcon'
|
||||
import * as styles from './SearchBar.css'
|
||||
import { SearchBarDropdown } from './SearchBarDropdown'
|
||||
|
||||
const KeyShortCut = styled.div`
|
||||
background-color: ${({ theme }) => theme.hoverState};
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
padding: 0px 8px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
line-height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0.6;
|
||||
backdrop-filter: blur(60px);
|
||||
`
|
||||
|
||||
export const SearchBar = () => {
|
||||
const [isOpen, toggleOpen] = useReducer((state: boolean) => !state, false)
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
@@ -30,7 +46,6 @@ export const SearchBar = () => {
|
||||
const searchRef = useRef<HTMLDivElement>(null)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const { pathname } = useLocation()
|
||||
const phase1Flag = useNftFlag()
|
||||
const isMobile = useIsMobile()
|
||||
const isTablet = useIsTablet()
|
||||
|
||||
@@ -45,7 +60,7 @@ export const SearchBar = () => {
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
enabled: !!debouncedSearchValue.length && phase1Flag === NftVariant.Enabled,
|
||||
enabled: !!debouncedSearchValue.length,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -92,10 +107,7 @@ export const SearchBar = () => {
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
const placeholderText = phase1Flag === NftVariant.Enabled ? t`Search tokens and NFT collections` : t`Search tokens`
|
||||
const isMobileOrTablet = isMobile || isTablet
|
||||
const showCenteredSearchContent =
|
||||
!isOpen && phase1Flag !== NftVariant.Enabled && !isMobileOrTablet && searchValue.length === 0
|
||||
|
||||
const trace = useTrace({ section: SectionName.NAVBAR_SEARCH })
|
||||
|
||||
@@ -104,78 +116,111 @@ export const SearchBar = () => {
|
||||
hasInput: debouncedSearchValue && debouncedSearchValue.length > 0,
|
||||
...trace,
|
||||
}
|
||||
const placeholderText = useMemo(() => {
|
||||
return isMobileOrTablet ? t`Search` : t`Search tokens and NFT collections`
|
||||
}, [isMobileOrTablet])
|
||||
|
||||
const handleKeyPress = useCallback(
|
||||
(event: any) => {
|
||||
if (event.key === '/') {
|
||||
event.preventDefault()
|
||||
!isOpen && toggleOpen()
|
||||
}
|
||||
},
|
||||
[isOpen]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const innerRef = inputRef.current
|
||||
|
||||
if (innerRef !== null) {
|
||||
//only mount the listener when input available as ref
|
||||
document.addEventListener('keydown', handleKeyPress)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (innerRef !== null) {
|
||||
document.removeEventListener('keydown', handleKeyPress)
|
||||
}
|
||||
}
|
||||
}, [handleKeyPress, inputRef])
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
<Trace section={SectionName.NAVBAR_SEARCH}>
|
||||
<Box
|
||||
position={{ sm: 'fixed', md: 'absolute' }}
|
||||
width={{ sm: isOpen ? 'viewWidth' : 'auto', md: 'auto' }}
|
||||
ref={searchRef}
|
||||
className={styles.searchBarContainer}
|
||||
display={{ sm: isOpen ? 'inline-block' : 'none', xl: 'inline-block' }}
|
||||
<Trace section={SectionName.NAVBAR_SEARCH}>
|
||||
<Box
|
||||
position={{ sm: 'fixed', md: 'absolute', xl: 'relative' }}
|
||||
width={{ sm: isOpen ? 'viewWidth' : 'auto', md: 'auto' }}
|
||||
ref={searchRef}
|
||||
className={styles.searchBarContainerNft}
|
||||
display={{ sm: isOpen ? 'inline-block' : 'none', xl: 'inline-block' }}
|
||||
>
|
||||
<Row
|
||||
className={clsx(
|
||||
styles.nftSearchBar,
|
||||
!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'}
|
||||
backgroundColor={isOpen ? 'backgroundSurface' : 'searchBackground'}
|
||||
onClick={() => !isOpen && toggleOpen()}
|
||||
gap="12"
|
||||
>
|
||||
<Row
|
||||
className={clsx(
|
||||
` ${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'}
|
||||
onClick={() => !isOpen && toggleOpen()}
|
||||
gap="12"
|
||||
>
|
||||
<Box className={showCenteredSearchContent ? styles.searchContentCentered : styles.searchContentLeftAlign}>
|
||||
<Box display={{ sm: 'none', md: 'flex' }}>
|
||||
<MagnifyingGlassIcon />
|
||||
</Box>
|
||||
<Box display={{ sm: 'flex', md: 'none' }} color="textTertiary" onClick={toggleOpen}>
|
||||
<ChevronLeftIcon />
|
||||
</Box>
|
||||
<Box className={styles.searchContentLeftAlign}>
|
||||
<Box display={{ sm: 'none', md: 'flex' }}>
|
||||
<MagnifyingGlassIcon />
|
||||
</Box>
|
||||
<Box display={{ sm: 'flex', md: 'none' }} color="textTertiary" onClick={toggleOpen}>
|
||||
<ChevronLeftIcon />
|
||||
</Box>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onFocus]}
|
||||
name={EventName.NAVBAR_SEARCH_SELECTED}
|
||||
element={ElementName.NAVBAR_SEARCH_INPUT}
|
||||
properties={{ ...trace }}
|
||||
>
|
||||
<Box
|
||||
as="input"
|
||||
placeholder={placeholderText}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
!isOpen && toggleOpen()
|
||||
setSearchValue(event.target.value)
|
||||
}}
|
||||
onBlur={() => sendAnalyticsEvent(EventName.NAVBAR_SEARCH_EXITED, navbarSearchEventProperties)}
|
||||
className={`${styles.searchBarInput} ${
|
||||
showCenteredSearchContent ? styles.searchContentCentered : styles.searchContentLeftAlign
|
||||
}`}
|
||||
value={searchValue}
|
||||
ref={inputRef}
|
||||
width={phase1Flag === NftVariant.Enabled || isOpen ? 'full' : '160'}
|
||||
/>
|
||||
</TraceEvent>
|
||||
</Row>
|
||||
<Box className={clsx(isOpen ? styles.visible : styles.hidden)}>
|
||||
{isOpen && (
|
||||
<SearchBarDropdown
|
||||
toggleOpen={toggleOpen}
|
||||
tokens={reducedTokens}
|
||||
collections={reducedCollections}
|
||||
queryText={debouncedSearchValue}
|
||||
hasInput={debouncedSearchValue.length > 0}
|
||||
isLoading={tokensAreLoading || (collectionsAreLoading && phase1Flag === NftVariant.Enabled)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onFocus]}
|
||||
name={EventName.NAVBAR_SEARCH_SELECTED}
|
||||
element={ElementName.NAVBAR_SEARCH_INPUT}
|
||||
properties={{ ...trace }}
|
||||
>
|
||||
<Trans
|
||||
id={placeholderText}
|
||||
render={({ translation }) => (
|
||||
<Box
|
||||
as="input"
|
||||
placeholder={translation as string}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
!isOpen && toggleOpen()
|
||||
setSearchValue(event.target.value)
|
||||
}}
|
||||
onBlur={() => sendAnalyticsEvent(EventName.NAVBAR_SEARCH_EXITED, navbarSearchEventProperties)}
|
||||
className={`${styles.searchBarInput} ${styles.searchContentLeftAlign}`}
|
||||
value={searchValue}
|
||||
ref={inputRef}
|
||||
width="full"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</TraceEvent>
|
||||
{!isOpen && <KeyShortCut>/</KeyShortCut>}
|
||||
</Row>
|
||||
<Box className={clsx(isOpen ? styles.visible : styles.hidden)}>
|
||||
{isOpen && (
|
||||
<SearchBarDropdown
|
||||
toggleOpen={toggleOpen}
|
||||
tokens={reducedTokens}
|
||||
collections={reducedCollections}
|
||||
queryText={debouncedSearchValue}
|
||||
hasInput={debouncedSearchValue.length > 0}
|
||||
isLoading={tokensAreLoading || collectionsAreLoading}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
{isMobileOrTablet && (
|
||||
<NavIcon onClick={toggleOpen}>
|
||||
<NavMagnifyingGlassIcon />
|
||||
</NavIcon>
|
||||
</Trace>
|
||||
</Box>
|
||||
)}
|
||||
</Trace>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
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'
|
||||
@@ -35,7 +34,7 @@ interface SearchBarDropdownSectionProps {
|
||||
eventProperties: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const SearchBarDropdownSection = ({
|
||||
const SearchBarDropdownSection = ({
|
||||
toggleOpen,
|
||||
suggestions,
|
||||
header,
|
||||
@@ -116,7 +115,6 @@ export const SearchBarDropdown = ({
|
||||
const { pathname } = useLocation()
|
||||
const isNFTPage = useIsNftPage()
|
||||
const isTokenPage = pathname.includes('/tokens')
|
||||
const phase1Flag = useNftFlag()
|
||||
const [resultsState, setResultsState] = useState<ReactNode>()
|
||||
|
||||
const { data: trendingCollectionResults, isLoading: trendingCollectionsAreLoading } = useQuery(
|
||||
@@ -156,7 +154,7 @@ export const SearchBarDropdown = ({
|
||||
trendingTokenResults?.forEach(updateSearchHistory)
|
||||
}, [trendingTokenResults, updateSearchHistory])
|
||||
|
||||
const trendingTokensLength = phase1Flag === NftVariant.Enabled ? (isTokenPage ? 3 : 2) : 4
|
||||
const trendingTokensLength = isTokenPage ? 3 : 2
|
||||
const trendingTokens = useMemo(
|
||||
() =>
|
||||
trendingTokenResults
|
||||
@@ -204,10 +202,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 ? (
|
||||
@@ -230,24 +228,22 @@ export const SearchBarDropdown = ({
|
||||
)
|
||||
|
||||
const collectionSearchResults =
|
||||
phase1Flag === NftVariant.Enabled ? (
|
||||
collections.length > 0 ? (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={showCollectionsFirst ? 0 : tokens.length}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={collections}
|
||||
eventProperties={{
|
||||
suggestion_type: NavBarSearchTypes.COLLECTION_SUGGESTION,
|
||||
...eventProperties,
|
||||
}}
|
||||
header={<Trans>NFT Collections</Trans>}
|
||||
/>
|
||||
) : (
|
||||
<Box className={styles.notFoundContainer}>No NFT collections found.</Box>
|
||||
)
|
||||
) : null
|
||||
collections.length > 0 ? (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={showCollectionsFirst ? 0 : tokens.length}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={collections}
|
||||
eventProperties={{
|
||||
suggestion_type: NavBarSearchTypes.COLLECTION_SUGGESTION,
|
||||
...eventProperties,
|
||||
}}
|
||||
header={<Trans>NFT Collections</Trans>}
|
||||
/>
|
||||
) : (
|
||||
<Box className={styles.notFoundContainer}>No NFT collections found.</Box>
|
||||
)
|
||||
|
||||
const currentState = () =>
|
||||
hasInput ? (
|
||||
@@ -299,7 +295,7 @@ export const SearchBarDropdown = ({
|
||||
isLoading={trendingTokensAreLoading}
|
||||
/>
|
||||
)}
|
||||
{!isTokenPage && phase1Flag === NftVariant.Enabled && (
|
||||
{!isTokenPage && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={shortenedHistory.length + (isNFTPage ? 0 : trendingTokens?.length ?? 0)}
|
||||
@@ -329,7 +325,6 @@ export const SearchBarDropdown = ({
|
||||
trendingTokens,
|
||||
trendingTokensAreLoading,
|
||||
hoveredIndex,
|
||||
phase1Flag,
|
||||
toggleOpen,
|
||||
shortenedHistory,
|
||||
hasInput,
|
||||
@@ -342,7 +337,7 @@ export const SearchBarDropdown = ({
|
||||
])
|
||||
|
||||
return (
|
||||
<Box className={styles.searchBarDropdown}>
|
||||
<Box className={styles.searchBarDropdownNft}>
|
||||
<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 } from 'nft/components/icons'
|
||||
import { useBag, useSellAsset } from 'nft/hooks'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import shallow from 'zustand/shallow'
|
||||
|
||||
export const ShoppingBag = () => {
|
||||
const itemsInBag = useBag((state) => state.itemsInBag)
|
||||
const [bagQuantity, setBagQuantity] = useState(0)
|
||||
|
||||
const { bagExpanded, setBagExpanded } = useBag(
|
||||
({ bagExpanded, setBagExpanded }) => ({ bagExpanded, setBagExpanded }),
|
||||
shallow
|
||||
)
|
||||
const { isSellMode, resetSellAssets, setIsSellMode } = useSellAsset(
|
||||
({ isSellMode, reset, setIsSellMode }) => ({
|
||||
isSellMode,
|
||||
resetSellAssets: reset,
|
||||
setIsSellMode,
|
||||
}),
|
||||
shallow
|
||||
)
|
||||
const handleIconClick = useCallback(() => {
|
||||
if (isSellMode && bagExpanded) {
|
||||
resetSellAssets()
|
||||
setIsSellMode(false)
|
||||
}
|
||||
setBagExpanded({ bagExpanded: !bagExpanded })
|
||||
}, [bagExpanded, isSellMode, resetSellAssets, setBagExpanded, setIsSellMode])
|
||||
|
||||
useEffect(() => {
|
||||
setBagQuantity(itemsInBag.length)
|
||||
}, [itemsInBag])
|
||||
|
||||
const bagHasItems = bagQuantity > 0
|
||||
|
||||
return (
|
||||
<NavIcon onClick={handleIconClick}>
|
||||
<BagIcon viewBox="0 0 20 20" width={24} height={24} />
|
||||
{bagHasItems && (
|
||||
<Box className={styles.bagQuantity}>{bagQuantity > 99 ? <HundredsOverflowIcon /> : bagQuantity}</Box>
|
||||
)}
|
||||
</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 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,30 @@ 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 { getDeltaArrow } from '../Tokens/TokenDetails/PriceChart'
|
||||
import * as styles from './SearchBar.css'
|
||||
|
||||
const StyledLogoContainer = styled(LogoContainer)`
|
||||
margin-right: 8px;
|
||||
`
|
||||
const PriceChangeContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const PriceChangeText = styled.span<{ isNegative: boolean }>`
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: ${({ theme, isNegative }) => (isNegative ? theme.accentFailure : theme.accentSuccess)};
|
||||
`
|
||||
|
||||
const ArrowCell = styled.span`
|
||||
padding-top: 5px;
|
||||
padding-right: 3px;
|
||||
`
|
||||
|
||||
interface CollectionRowProps {
|
||||
collection: GenieCollection
|
||||
isHovered: boolean
|
||||
@@ -127,8 +149,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
|
||||
)
|
||||
@@ -157,6 +177,8 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index,
|
||||
}
|
||||
}, [toggleOpen, isHovered, token, navigate, handleClick, tokenDetailsPath])
|
||||
|
||||
const arrow = getDeltaArrow(token.price24hChange, 18)
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={tokenDetailsPath}
|
||||
@@ -167,21 +189,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,13 +212,16 @@ 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 && (
|
||||
<Box className={styles.secondaryText} color={token.price24hChange >= 0 ? 'green400' : 'red400'}>
|
||||
{token.price24hChange.toFixed(2)}%
|
||||
</Box>
|
||||
<PriceChangeContainer>
|
||||
<ArrowCell>{arrow}</ArrowCell>
|
||||
<PriceChangeText isNegative={token.price24hChange < 0}>
|
||||
{Math.abs(token.price24hChange).toFixed(2)}%
|
||||
</PriceChangeText>
|
||||
</PriceChangeContainer>
|
||||
)}
|
||||
</Column>
|
||||
</Link>
|
||||
|
||||