Compare commits
381 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
562a386de7 | ||
|
|
99bea34f14 | ||
|
|
58f1c6ff84 | ||
|
|
b2481d6ba8 | ||
|
|
eaa9b51913 | ||
|
|
6f68980d86 | ||
|
|
9e05c178b4 | ||
|
|
d98808dae9 | ||
|
|
01a749a755 | ||
|
|
c233ba1175 | ||
|
|
b1dc415fb5 | ||
|
|
1242aef466 | ||
|
|
7f2bb6c6ae | ||
|
|
4b1b6098f3 | ||
|
|
53a6acc199 | ||
|
|
bd0a32b07c | ||
|
|
7d480494ba | ||
|
|
5caed66b39 | ||
|
|
136c16bbae | ||
|
|
691dcd269c | ||
|
|
a5cb1f05dc | ||
|
|
4a14db2d4c | ||
|
|
a57c19bb45 | ||
|
|
9fd6ab01de | ||
|
|
48aa11403f | ||
|
|
31b0c3dc04 | ||
|
|
d575c72127 | ||
|
|
d400b9094d | ||
|
|
fda9d29d5e | ||
|
|
d74c05008b | ||
|
|
c8f365ca31 | ||
|
|
66ab3e21c8 | ||
|
|
d4b15a6de4 | ||
|
|
fe61365a11 | ||
|
|
c5047a9301 | ||
|
|
d4f8f2a600 | ||
|
|
d41a5a4874 | ||
|
|
2fe444f903 | ||
|
|
155bf2e873 | ||
|
|
6e282a6d13 | ||
|
|
d3a2e14d4d | ||
|
|
619c0a0f46 | ||
|
|
d4fb0913a4 | ||
|
|
734a15e350 | ||
|
|
d3cbcc769c | ||
|
|
ab43800d8e | ||
|
|
74accb2b7e | ||
|
|
289119833a | ||
|
|
8445d61df0 | ||
|
|
ffb6318475 | ||
|
|
b1a0c3d6a9 | ||
|
|
42edf9750e | ||
|
|
5398826400 | ||
|
|
d4884716e2 | ||
|
|
6712eafefe | ||
|
|
aba6c1a1f4 | ||
|
|
964001fe78 | ||
|
|
449c323fbb | ||
|
|
d290adb0b8 | ||
|
|
ac4e64db6c | ||
|
|
3a0c930113 | ||
|
|
58c94b7279 | ||
|
|
02d25daa4c | ||
|
|
ef57ff7611 | ||
|
|
bed0b3ab1c | ||
|
|
751ba3c62d | ||
|
|
83bc6db74e | ||
|
|
058aa52faf | ||
|
|
2e3950018a | ||
|
|
d86120a257 | ||
|
|
e2fea4a5fb | ||
|
|
1f810f84be | ||
|
|
f6ffc68ef7 | ||
|
|
b3c44f20d7 | ||
|
|
8a5045f635 | ||
|
|
c1607bbd52 | ||
|
|
dfbed6b89d | ||
|
|
c871e55d82 | ||
|
|
f15ac091b4 | ||
|
|
6037d74cfb | ||
|
|
d0e4659d32 | ||
|
|
2d87e692e6 | ||
|
|
f65fb5bc2b | ||
|
|
83597c0efe | ||
|
|
21ee680d3a | ||
|
|
627af50841 | ||
|
|
a955b3730e | ||
|
|
2f7c5b1df4 | ||
|
|
8fca286099 | ||
|
|
8be9701700 | ||
|
|
d66002dc75 | ||
|
|
b12e5270fa | ||
|
|
a717818920 | ||
|
|
d9434a1a9c | ||
|
|
a920a93b3d | ||
|
|
0987a311cf | ||
|
|
a97a6b7fa8 | ||
|
|
414b221727 | ||
|
|
bab2f47ac9 | ||
|
|
0323725543 | ||
|
|
f6a7c8568e | ||
|
|
a21bbfd5a7 | ||
|
|
5f431a1e26 | ||
|
|
bf13b4a917 | ||
|
|
c7ea77d292 | ||
|
|
00d674376e | ||
|
|
afaa52e5e7 | ||
|
|
443cfe7540 | ||
|
|
6768e4f4f7 | ||
|
|
a0e9211b71 | ||
|
|
aeeb3a248a | ||
|
|
3586a2884c | ||
|
|
5e2bdc4e4b | ||
|
|
f2a33b6f6b | ||
|
|
5462526f53 | ||
|
|
4388bbe0a2 | ||
|
|
6f2c09adea | ||
|
|
f9aadbbbdb | ||
|
|
f8c0525512 | ||
|
|
175ffade5e | ||
|
|
cba30fb0b1 | ||
|
|
604b854ef7 | ||
|
|
332843f428 | ||
|
|
cee32f9751 | ||
|
|
cb480706a2 | ||
|
|
4f74267144 | ||
|
|
f6b08e8ed1 | ||
|
|
0faaa3f0c4 | ||
|
|
6acc9300c0 | ||
|
|
70d33fb255 | ||
|
|
c0db592ab5 | ||
|
|
a5c5567936 | ||
|
|
cdd5b66d1b | ||
|
|
b65fffc5f7 | ||
|
|
a3a3e934a1 | ||
|
|
8a4e07e6b2 | ||
|
|
7f4413c79c | ||
|
|
55beaf65a2 | ||
|
|
20fe76ad29 | ||
|
|
0d5bc753ca | ||
|
|
79507a4b03 | ||
|
|
3a1be04a36 | ||
|
|
ec523e5235 | ||
|
|
c7b1aa2948 | ||
|
|
9370383f64 | ||
|
|
9856c03566 | ||
|
|
ec686bcaa5 | ||
|
|
06291a15a6 | ||
|
|
3e40a6f5c6 | ||
|
|
f91b48e214 | ||
|
|
e340f405b4 | ||
|
|
24fc39b016 | ||
|
|
2fc3f3c00e | ||
|
|
21e0faeb1e | ||
|
|
4b71a8d5f4 | ||
|
|
4075965252 | ||
|
|
3538312769 | ||
|
|
2924f36970 | ||
|
|
bf16dfa09c | ||
|
|
910e86d6a2 | ||
|
|
537fea103e | ||
|
|
87a6e2709b | ||
|
|
d704e78223 | ||
|
|
8ceabd513c | ||
|
|
4806c69053 | ||
|
|
c9f333003b | ||
|
|
33fa32cb07 | ||
|
|
5d1377af80 | ||
|
|
a75e239fd2 | ||
|
|
a663482dc6 | ||
|
|
e8d6235529 | ||
|
|
d8677d8a6d | ||
|
|
351f66a83e | ||
|
|
79c7c01964 | ||
|
|
f51474b66d | ||
|
|
14f01905d7 | ||
|
|
23eb31e6a2 | ||
|
|
e8d4f00f49 | ||
|
|
978e3f945d | ||
|
|
072f394476 | ||
|
|
5926d7037d | ||
|
|
52b51ee7d0 | ||
|
|
54f831ede4 | ||
|
|
c9ead63ff6 | ||
|
|
9ca74cf8d2 | ||
|
|
2a0d455419 | ||
|
|
9e959ca455 | ||
|
|
d3f6796bb9 | ||
|
|
64d6eeabcb | ||
|
|
859258c25c | ||
|
|
2338255a54 | ||
|
|
843afa93c3 | ||
|
|
5441e63825 | ||
|
|
7bf741027e | ||
|
|
0017e2fcc8 | ||
|
|
5c9c8b4cb7 | ||
|
|
873d0ea2a3 | ||
|
|
db4987f557 | ||
|
|
b0b61f886d | ||
|
|
5d4b25f417 | ||
|
|
c88d7c880b | ||
|
|
a96d13978b | ||
|
|
22b26de78d | ||
|
|
53b57879a3 | ||
|
|
d794cef770 | ||
|
|
19f175ba89 | ||
|
|
aaf105ef51 | ||
|
|
974308f939 | ||
|
|
0ec738a48a | ||
|
|
904f6e22f4 | ||
|
|
66fad96e61 | ||
|
|
9037930e56 | ||
|
|
d62013177d | ||
|
|
fc08ede58a | ||
|
|
995a62985e | ||
|
|
67d5a00a0c | ||
|
|
84364c9df2 | ||
|
|
446eb9f9a4 | ||
|
|
a73e814167 | ||
|
|
7125562c9d | ||
|
|
1361f99639 | ||
|
|
d70a87a89a | ||
|
|
2cb0d9527e | ||
|
|
1839e145ec | ||
|
|
8c1e41a3a8 | ||
|
|
9859c0b4dd | ||
|
|
1138101dd0 | ||
|
|
106ac7ea35 | ||
|
|
19b4ee463b | ||
|
|
2aea96c3ba | ||
|
|
b1fb499e29 | ||
|
|
64207f29b0 | ||
|
|
7b6ac6cfaa | ||
|
|
8a9ade5f12 | ||
|
|
a3e567bc8a | ||
|
|
a887666bf5 | ||
|
|
ed8aa08255 | ||
|
|
53f4fb9ede | ||
|
|
bb1ccb7f1a | ||
|
|
03fe90ad53 | ||
|
|
1601962f03 | ||
|
|
ac8e59acba | ||
|
|
5f6d17bfe2 | ||
|
|
3c5fe00c30 | ||
|
|
91754848af | ||
|
|
d8eb4d188a | ||
|
|
25d64911d4 | ||
|
|
888f02dbaa | ||
|
|
728a5653be | ||
|
|
a5dc0fddb8 | ||
|
|
134f30e81f | ||
|
|
9b07ac2be4 | ||
|
|
571a49ba6f | ||
|
|
077437e1f1 | ||
|
|
ba9e509d67 | ||
|
|
181ab149e3 | ||
|
|
5ef64c7dd1 | ||
|
|
0f6a675d0c | ||
|
|
ec3552bbde | ||
|
|
5783602694 | ||
|
|
9ba76992e4 | ||
|
|
d075ab6a74 | ||
|
|
4cdfeaae34 | ||
|
|
e54b46910a | ||
|
|
9558406c90 | ||
|
|
f735c34841 | ||
|
|
1aa4afad5f | ||
|
|
58005d81d6 | ||
|
|
b0381c58e6 | ||
|
|
99a3cfafc9 | ||
|
|
6c908eb710 | ||
|
|
dc15144a29 | ||
|
|
34431bcb75 | ||
|
|
0041b787ec | ||
|
|
868edc6028 | ||
|
|
d8c84a91f4 | ||
|
|
68282af457 | ||
|
|
0ecb732331 | ||
|
|
86785c726a | ||
|
|
10fe7f5213 | ||
|
|
4deab7554c | ||
|
|
b92c8007e4 | ||
|
|
029f3acbd5 | ||
|
|
0b9fda5b25 | ||
|
|
47816f2530 | ||
|
|
73023499aa | ||
|
|
241043b616 | ||
|
|
e97e117298 | ||
|
|
b63e95388c | ||
|
|
ef8fba1d49 | ||
|
|
be64c03d06 | ||
|
|
45682ca59e | ||
|
|
397b9d423e | ||
|
|
d9113fb6d4 | ||
|
|
5dc0df2132 | ||
|
|
7f2cc9a3e6 | ||
|
|
d3c4ca6e09 | ||
|
|
28538214d2 | ||
|
|
4f48f3372c | ||
|
|
9e070107a2 | ||
|
|
2a92b2992f | ||
|
|
e180153c3a | ||
|
|
e35f9e16a1 | ||
|
|
8e955e9257 | ||
|
|
9ca44652b3 | ||
|
|
3d89d72426 | ||
|
|
c2ffab3273 | ||
|
|
dbb62b613c | ||
|
|
6f2d6e31c9 | ||
|
|
944939a2e9 | ||
|
|
04164a550d | ||
|
|
16a5e15070 | ||
|
|
ee97d8d902 | ||
|
|
3d4b077b89 | ||
|
|
0f4a89d938 | ||
|
|
0d0ec12dbf | ||
|
|
afe30a2c02 | ||
|
|
2f9289a2c5 | ||
|
|
7a9d2e80d0 | ||
|
|
d4b8735c04 | ||
|
|
d31687d0bf | ||
|
|
470535dd33 | ||
|
|
27f53f1e99 | ||
|
|
e7d498c95e | ||
|
|
02b617d297 | ||
|
|
96d04e1a7d | ||
|
|
fe9d805d7c | ||
|
|
b6c136839e | ||
|
|
8c947a0e0d | ||
|
|
49c5cbbf3b | ||
|
|
efaefe2e44 | ||
|
|
ed95f1b966 | ||
|
|
7ecbc552aa | ||
|
|
dadc997398 | ||
|
|
b90d6b5ab0 | ||
|
|
db5c6f82fd | ||
|
|
f161f9617b | ||
|
|
9d3249e6bd | ||
|
|
f1c65afa98 | ||
|
|
80c1f0cdf9 | ||
|
|
ea0fe83d00 | ||
|
|
994836fba7 | ||
|
|
41aa1dcb0b | ||
|
|
3965d3fdd9 | ||
|
|
ff6fd8a6e9 | ||
|
|
0a6906b23e | ||
|
|
c38b5c0ce3 | ||
|
|
86f3b5a036 | ||
|
|
382a44f040 | ||
|
|
2d9604cd14 | ||
|
|
7930709bc3 | ||
|
|
6fe2c92cee | ||
|
|
884dee2db3 | ||
|
|
1f00c2a9c4 | ||
|
|
84070835df | ||
|
|
fb389137e7 | ||
|
|
c14b6a78ae | ||
|
|
a6c1c49f98 | ||
|
|
7848ad86bd | ||
|
|
882c15dada | ||
|
|
704ad222d9 | ||
|
|
cfee80ce3c | ||
|
|
eb95cedd72 | ||
|
|
99a7fb3383 | ||
|
|
7f4dbf9346 | ||
|
|
09b00c9974 | ||
|
|
b74fb8174d | ||
|
|
a7ec5a64b7 | ||
|
|
c619dcf65d | ||
|
|
1221d88e13 | ||
|
|
48d2ead71d | ||
|
|
ed7099bfd6 | ||
|
|
2604cdfdae | ||
|
|
94dc389812 | ||
|
|
4a8c621f46 | ||
|
|
477af8af4e | ||
|
|
a9a7d524aa | ||
|
|
1cdaff8ddf | ||
|
|
eeea3d2dcc | ||
|
|
f46b6a0697 | ||
|
|
622581ee0a |
4
.env
@@ -1,7 +1,9 @@
|
||||
# These API keys are intentionally public. Please do not report them - thank you for your concern.
|
||||
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||
REACT_APP_AMPLITUDE_TEST_KEY="add-the-real-test-key-if-you-need-to-test-amplitude-events"
|
||||
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"
|
||||
@@ -1,6 +1,6 @@
|
||||
REACT_APP_AMPLITUDE_KEY="b8f7dabddb1c3b03b394619767972160"
|
||||
REACT_APP_AMPLITUDE_TEST_KEY="1c694b28cd089acc2c386d518f93a775"
|
||||
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
|
||||
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
|
||||
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"
|
||||
|
||||
@@ -81,6 +81,18 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
{
|
||||
"name": "@ethersproject/providers",
|
||||
"message": "Please only use Providers instantiated in constants/providers to improve traceability.",
|
||||
"allowTypeImports": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
2
.github/workflows/release.yaml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Release
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * 1-4' # every day 12:00 UTC Monday-Thursday
|
||||
- cron: '0 12 * * 1-5' # every day 12:00 UTC Monday-Friday
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
2
.github/workflows/test.yml
vendored
@@ -5,8 +5,6 @@ on:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
1
.gitignore
vendored
@@ -10,6 +10,7 @@
|
||||
|
||||
# generated graphql types
|
||||
__generated__/
|
||||
schema.graphql
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
@@ -30,7 +30,7 @@ or visit [app.uniswap.org](https://app.uniswap.org).
|
||||
|
||||
Check out `useUnsupportedTokenList()` in [src/state/lists/hooks.ts](./src/state/lists/hooks.ts) for blocking tokens in your instance of the interface.
|
||||
|
||||
You can block an entire list of tokens by passing in a tokenlist like [here](./src/constants/lists.ts) or you can block specific tokens by adding them to [unsupported.tokenlist.json](./src/constants/tokenLists/unsupported.tokenlist.json).
|
||||
You can block an entire list of tokens by passing in a tokenlist like [here](./src/constants/lists.ts)
|
||||
|
||||
## Contributions
|
||||
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const { VanillaExtractPlugin } = require('@vanilla-extract/webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const { DefinePlugin } = require('webpack')
|
||||
|
||||
const commitHash = require('child_process').execSync('git rev-parse HEAD')
|
||||
|
||||
module.exports = {
|
||||
babel: {
|
||||
plugins: ['@vanilla-extract/babel-plugin'],
|
||||
},
|
||||
webpack: {
|
||||
plugins: [new VanillaExtractPlugin()],
|
||||
plugins: [
|
||||
new VanillaExtractPlugin(),
|
||||
new DefinePlugin({
|
||||
'process.env.REACT_APP_GIT_COMMIT_HASH': JSON.stringify(commitHash.toString()),
|
||||
}),
|
||||
],
|
||||
configure: (webpackConfig) => {
|
||||
const instanceOfMiniCssExtractPlugin = webpackConfig.plugins.find(
|
||||
(plugin) => plugin instanceof MiniCssExtractPlugin
|
||||
|
||||
@@ -21,20 +21,20 @@ describe('Add Liquidity', () => {
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('not.contain.text', 'ETH')
|
||||
})
|
||||
|
||||
it('token not in storage is loaded', () => {
|
||||
it.skip('token not in storage is loaded', () => {
|
||||
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('single token can be selected', () => {
|
||||
it.skip('single token can be selected', () => {
|
||||
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('loads fee tier distribution', () => {
|
||||
it.skip('loads fee tier distribution', () => {
|
||||
cy.fixture('feeTierDistribution.json').then((feeTierDistribution) => {
|
||||
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req: CyHttpMessages.IncomingHttpRequest) => {
|
||||
if (hasQuery(req, 'FeeTierDistributionQuery')) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
describe('Link', () => {
|
||||
it('should update route', () => {
|
||||
cy.visit('/')
|
||||
cy.get('[data-cy="pool-nav-link"]').click()
|
||||
cy.contains('Pool').click()
|
||||
cy.get('[data-cy="join-pool-button"]').should('exist')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
describe('Lists', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/swap')
|
||||
})
|
||||
|
||||
// @TODO check if default lists are active when we have them
|
||||
it('change list', () => {
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.get('.list-token-manage-button').click()
|
||||
})
|
||||
})
|
||||
@@ -23,7 +23,7 @@ describe('Remove Liquidity', () => {
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH')
|
||||
})
|
||||
|
||||
it('token not in storage is loaded', () => {
|
||||
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')
|
||||
|
||||
50
cypress/e2e/wallet-dropdown.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Wallet Dropdown', () => {
|
||||
before(() => {
|
||||
cy.visit('/', { featureFlags: [FeatureFlag.navBar, FeatureFlag.tokenSafety] })
|
||||
})
|
||||
|
||||
it('should change the theme', () => {
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).contains('Light theme').should('exist')
|
||||
})
|
||||
|
||||
it('should select a language', () => {
|
||||
cy.get(getTestSelector('wallet-select-language')).click()
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Taal')
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
})
|
||||
|
||||
it('should be able to view transactions', () => {
|
||||
cy.get(getTestSelector('wallet-transactions')).click()
|
||||
cy.get(getTestSelector('wallet-empty-transaction-text')).should('exist')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
})
|
||||
|
||||
it('should change the theme when not connected', () => {
|
||||
cy.get(getTestSelector('wallet-disconnect')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).contains('Dark theme').should('exist')
|
||||
})
|
||||
|
||||
it('should select a language when not connected', () => {
|
||||
cy.get(getTestSelector('wallet-select-language')).click()
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Taal')
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
})
|
||||
|
||||
it('should open the wallet connect modal from the drop down when not connected', () => {
|
||||
cy.get(getTestSelector('wallet-connect-wallet')).click()
|
||||
cy.get(getTestSelector('wallet-modal')).should('exist')
|
||||
cy.get(getTestSelector('wallet-modal-close')).click()
|
||||
})
|
||||
})
|
||||
@@ -1,30 +0,0 @@
|
||||
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/ethereum'
|
||||
|
||||
describe('Wallet', () => {
|
||||
before(() => {
|
||||
cy.visit('/swap')
|
||||
})
|
||||
|
||||
it('displays account details', () => {
|
||||
cy.get('[data-testid=web3-status-connected]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED).click()
|
||||
})
|
||||
|
||||
it('displays account view in wallet modal', () => {
|
||||
cy.get('[data-testid=web3-account-identifier-row]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
|
||||
})
|
||||
|
||||
it('changes back to the options grid', () => {
|
||||
cy.contains('Change').click()
|
||||
cy.get('[data-testid=option-grid]').should('exist')
|
||||
})
|
||||
|
||||
it('selects injected wallet option', () => {
|
||||
cy.contains('Injected').click()
|
||||
cy.get('[data-testid=web3-account-identifier-row]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
|
||||
})
|
||||
|
||||
it('shows connect buttons after disconnect', () => {
|
||||
cy.contains('Disconnect').click()
|
||||
cy.get('[data-testid=option-grid]').should('exist')
|
||||
})
|
||||
})
|
||||
@@ -9,6 +9,8 @@
|
||||
import { injected } from './ethereum'
|
||||
import assert = require('assert')
|
||||
|
||||
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
@@ -17,6 +19,7 @@ declare global {
|
||||
}
|
||||
interface VisitOptions {
|
||||
serviceWorker?: true
|
||||
featureFlags?: Array<FeatureFlag>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,11 +34,23 @@ Cypress.Commands.overwrite(
|
||||
cy.intercept('/service-worker.js', options?.serviceWorker ? undefined : { statusCode: 404 }).then(() => {
|
||||
original({
|
||||
...options,
|
||||
url: (url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url) + '?chain=rinkeby',
|
||||
url: (url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url) + '?chain=goerli',
|
||||
onBeforeLoad(win) {
|
||||
options?.onBeforeLoad?.(win)
|
||||
win.localStorage.clear()
|
||||
win.localStorage.setItem('redux_localstorage_simple_user', '{"selectedWallet":"INJECTED"}')
|
||||
|
||||
if (options?.featureFlags) {
|
||||
const featureFlags = options.featureFlags.reduce(
|
||||
(flags, flag) => ({
|
||||
...flags,
|
||||
[flag]: 'enabled',
|
||||
}),
|
||||
{}
|
||||
)
|
||||
win.localStorage.setItem('featureFlags', JSON.stringify(featureFlags))
|
||||
}
|
||||
|
||||
win.ethereum = injected
|
||||
},
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
import { Wallet } from '@ethersproject/wallet'
|
||||
|
||||
@@ -18,10 +19,10 @@ export const TEST_ADDRESS_NEVER_USE_SHORTENED = `${TEST_ADDRESS_NEVER_USE.substr
|
||||
6
|
||||
)}...${TEST_ADDRESS_NEVER_USE.substr(-4, 4)}`
|
||||
|
||||
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
|
||||
const provider = new JsonRpcProvider('https://goerli.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
|
||||
const signer = new Wallet(TEST_PRIVATE_KEY, provider)
|
||||
export const injected = new (class extends Eip1193Bridge {
|
||||
chainId = 4
|
||||
chainId = /* GOERLI= */ 5
|
||||
|
||||
async sendAsync(...args: any[]) {
|
||||
console.debug('sendAsync called', ...args)
|
||||
|
||||
1
cypress/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const getTestSelector = (selectorId: string) => `[data-testid=${selectorId}]`
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable */
|
||||
require('dotenv').config({ path: '.env.local' })
|
||||
require('dotenv').config({ path: '.env.production' })
|
||||
const { exec } = require('child_process')
|
||||
const dataConfig = require('./relay.config')
|
||||
const thegraphConfig = require('./relay_thegraph.config')
|
||||
@@ -8,11 +8,7 @@ const thegraphConfig = require('./relay_thegraph.config')
|
||||
const THEGRAPH_API_URL = 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'
|
||||
exec(`get-graphql-schema ${THEGRAPH_API_URL} > ${thegraphConfig.schema}`)
|
||||
|
||||
const API_URL = process.env.REACT_APP_GQL_API_URL
|
||||
const API_KEY = process.env.REACT_APP_GQL_API_KEY
|
||||
|
||||
if (API_URL && API_KEY) {
|
||||
exec(`get-graphql-schema ${API_URL} --h X-API-KEY=${API_KEY} > ${dataConfig.schema}`)
|
||||
} else {
|
||||
console.log('REACT_APP_GQL_API_URL or REACT_APP_GQL_API_KEY is missing from env.local')
|
||||
}
|
||||
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}`
|
||||
)
|
||||
|
||||
19
package.json
@@ -16,7 +16,7 @@
|
||||
"i18n:extract": "lingui extract --locale en-US",
|
||||
"i18n:compile": "yarn i18n:extract && lingui compile",
|
||||
"i18n:pseudo": "lingui extract --locale pseudo && lingui compile",
|
||||
"prepare": "yarn contracts:compile && yarn graphql:generate && yarn i18n:compile",
|
||||
"prepare": "yarn contracts:compile && yarn graphql:fetch && yarn graphql:generate && yarn i18n:compile",
|
||||
"start": "craco start",
|
||||
"build": "craco build",
|
||||
"serve": "serve build -l 3000",
|
||||
@@ -80,6 +80,7 @@
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-table": "^7.7.12",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.0",
|
||||
"@types/react-window": "^1.8.2",
|
||||
"@types/rebass": "^4.0.7",
|
||||
@@ -99,8 +100,8 @@
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-better-styled-components": "^1.1.2",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-react": "^7.19.0",
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"jest-styled-components": "^7.0.8",
|
||||
@@ -134,10 +135,10 @@
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
"@uniswap/redux-multicall": "^1.1.5",
|
||||
"@uniswap/redux-multicall": "^1.1.6",
|
||||
"@uniswap/router-sdk": "^1.3.0",
|
||||
"@uniswap/sdk-core": "^3.0.1",
|
||||
"@uniswap/smart-order-router": "^2.9.2",
|
||||
"@uniswap/smart-order-router": "^2.10.0",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.30",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
@@ -145,7 +146,7 @@
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.9.0",
|
||||
"@uniswap/widgets": "^2.7.0",
|
||||
"@uniswap/widgets": "^2.18.0",
|
||||
"@vanilla-extract/css": "^1.7.2",
|
||||
"@vanilla-extract/css-utils": "^0.1.2",
|
||||
"@vanilla-extract/dynamic": "^2.0.2",
|
||||
@@ -154,6 +155,7 @@
|
||||
"@visx/event": "^2.6.0",
|
||||
"@visx/glyph": "^2.10.0",
|
||||
"@visx/group": "^2.10.0",
|
||||
"@visx/react-spring": "^2.12.2",
|
||||
"@visx/responsive": "^2.10.0",
|
||||
"@visx/shape": "^2.11.1",
|
||||
"@walletconnect/ethereum-provider": "1.7.1",
|
||||
@@ -167,10 +169,8 @@
|
||||
"@web3-react/types": "^8.0.20-beta.0",
|
||||
"@web3-react/url": "^8.0.25-beta.0",
|
||||
"@web3-react/walletconnect": "^8.0.35-beta.0",
|
||||
"ajv": "^6.12.3",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
"array.prototype.flatmap": "^1.2.4",
|
||||
"aws4fetch": "^1.0.13",
|
||||
"cids": "^1.0.0",
|
||||
"clsx": "^1.1.1",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
@@ -194,6 +194,7 @@
|
||||
"polyfill-object.fromentries": "^1.0.1",
|
||||
"popper-max-size-modifier": "^0.2.0",
|
||||
"qs": "^6.9.4",
|
||||
"rc-slider": "^10.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-confetti": "^6.0.0",
|
||||
"react-dom": "^18.2.0",
|
||||
@@ -207,7 +208,7 @@
|
||||
"react-redux": "^8.0.2",
|
||||
"react-relay": "^14.1.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-spring": "^9.5.5",
|
||||
"react-table": "^7.8.0",
|
||||
"react-use-gesture": "^6.0.14",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
|
||||
@@ -1,127 +1,119 @@
|
||||
<!DOCTYPE html>
|
||||
<html translate="no">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<!--
|
||||
<title>Uniswap Interface</title>
|
||||
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
|
||||
|
||||
<!--
|
||||
%PUBLIC_URL% will be replaced with the URL of the `public` folder during build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="%PUBLIC_URL%/favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="%PUBLIC_URL%/images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="%PUBLIC_URL%/images/512x512_App_Icon.png" />
|
||||
<link rel="shortcut icon" type="image/png" href="%PUBLIC_URL%/favicon.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="%PUBLIC_URL%/images/192x192_App_Icon.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="%PUBLIC_URL%/images/512x512_App_Icon.png" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#ff007a" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#FC72FF" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'" />
|
||||
|
||||
<!--
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
<link rel="preconnect" href="https://www.google-analytics.com/" />
|
||||
|
||||
<link rel="preload" href="%PUBLIC_URL%/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="%PUBLIC_URL%/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
<style>
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
Explicitly load Inter var from public/ so it does not block LCP's critical path.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter custom';
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-named-instance: 'Regular';
|
||||
src: url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
|
||||
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2-variations'),
|
||||
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
@supports (font-variation-settings: normal) {
|
||||
* {
|
||||
font-family: 'Inter custom', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
button {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
#background-radial-gradient {
|
||||
background: linear-gradient(180deg, #202738 0%, #070816 100%);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
html {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
font-size: 16px;
|
||||
font-variant: none;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
font-feature-settings: 'ss01' on, 'ss02' on, 'cv01' on, 'cv03' on;
|
||||
}
|
||||
|
||||
#background-radial-gradient {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
background: radial-gradient(50% 50% at 50% 50%, #fc077d10 0%, rgba(255, 255, 255, 0) 100%);
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
background-color: #212429;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
min-height: 100%;
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: #212429;
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
html {
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
<!-- The root is the container of the app -->
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </div>
|
||||
</div>
|
||||
|
||||
<div id="background-radial-gradient"></div>
|
||||
<div id="background-radial-gradient"></div>
|
||||
</body>
|
||||
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -2,7 +2,10 @@
|
||||
"background_color": "#fff",
|
||||
"display": "standalone",
|
||||
"homepage_url": "https://app.uniswap.org",
|
||||
"providedBy": { "name": "Uniswap", "url": "https://uniswap.org" },
|
||||
"providedBy": {
|
||||
"name": "Uniswap",
|
||||
"url": "https://uniswap.org"
|
||||
},
|
||||
"icons": [
|
||||
{
|
||||
"src": "./images/192x192_App_Icon.png",
|
||||
@@ -23,5 +26,5 @@
|
||||
"iconPath": "./images/256x256_App_Icon_Pink.svg",
|
||||
"short_name": "Uniswap",
|
||||
"start_url": ".",
|
||||
"theme_color": "#ff007a"
|
||||
}
|
||||
"theme_color": "#FC72FFs"
|
||||
}
|
||||
6
public/nft/svgs/marketplaces/sudoswap.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 14.4C0 6.4471 6.4471 0 14.4 0H33.6C41.5529 0 48 6.4471 48 14.4V33.6C48 41.5529 41.5529 48 33.6 48H14.4C6.4471 48 0 41.5529 0 33.6V14.4Z" fill="#B9B9FF"/>
|
||||
<path d="M9.29999 15H12L15.3 24L12 33H9.29999L12.6 24L9.29999 15Z" fill="#121212"/>
|
||||
<path d="M23.5727 33.192C22.1807 33.192 21.0367 32.872 20.1407 32.232C19.2607 31.592 18.8207 30.72 18.8207 29.616H21.2447C21.2447 30.112 21.4607 30.496 21.8927 30.768C22.3407 31.024 22.9167 31.152 23.6207 31.152H24.6287C25.4607 31.152 26.0767 30.992 26.4767 30.672C26.8767 30.336 27.0767 29.896 27.0767 29.352C27.0767 28.808 26.8847 28.384 26.5007 28.08C26.1167 27.776 25.5727 27.56 24.8687 27.432L23.0687 27.168C20.4447 26.752 19.1327 25.48 19.1327 23.352C19.1327 22.136 19.5327 21.208 20.3327 20.568C21.1327 19.928 22.2767 19.608 23.7647 19.608H24.6767C26.0527 19.608 27.1567 19.92 27.9887 20.544C28.8207 21.152 29.2367 21.96 29.2367 22.968H26.8127C26.8127 22.568 26.6127 22.248 26.2127 22.008C25.8287 21.768 25.3007 21.648 24.6287 21.648H23.7167C22.9807 21.648 22.4207 21.8 22.0367 22.104C21.6527 22.392 21.4607 22.808 21.4607 23.352C21.4607 24.28 22.1167 24.848 23.4287 25.056L25.2287 25.344C26.6687 25.568 27.7247 25.992 28.3967 26.616C29.0687 27.224 29.4047 28.104 29.4047 29.256C29.4047 30.488 28.9967 31.456 28.1807 32.16C27.3807 32.848 26.1807 33.192 24.5807 33.192H23.5727Z" fill="#121212"/>
|
||||
<path d="M38.7 15H36L32.7 24L36 33H38.7L35.4 24L38.7 15Z" fill="#121212"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -20,6 +20,11 @@ export interface ITraceContext {
|
||||
|
||||
export const TraceContext = createContext<ITraceContext>({})
|
||||
|
||||
export function useTrace(trace?: ITraceContext): ITraceContext {
|
||||
const parentTrace = useContext(TraceContext)
|
||||
return useMemo(() => ({ ...parentTrace, ...trace }), [parentTrace, trace])
|
||||
}
|
||||
|
||||
type TraceProps = {
|
||||
shouldLogImpression?: boolean // whether to log impression on mount
|
||||
name?: EventName
|
||||
@@ -31,20 +36,34 @@ type TraceProps = {
|
||||
* and propagates the context to child traces.
|
||||
*/
|
||||
export const Trace = memo(
|
||||
({ shouldLogImpression, name, children, page, section, element, properties }: PropsWithChildren<TraceProps>) => {
|
||||
const parentTrace = useContext(TraceContext)
|
||||
({
|
||||
shouldLogImpression,
|
||||
name,
|
||||
children,
|
||||
page,
|
||||
section,
|
||||
modal,
|
||||
element,
|
||||
properties,
|
||||
}: PropsWithChildren<TraceProps>) => {
|
||||
const parentTrace = useTrace()
|
||||
|
||||
const combinedProps = useMemo(
|
||||
() => ({
|
||||
...parentTrace,
|
||||
...Object.fromEntries(Object.entries({ page, section, element }).filter(([_, v]) => v !== undefined)),
|
||||
...Object.fromEntries(Object.entries({ page, section, modal, element }).filter(([_, v]) => v !== undefined)),
|
||||
}),
|
||||
[element, parentTrace, page, section]
|
||||
[element, parentTrace, page, modal, section]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldLogImpression) {
|
||||
sendAnalyticsEvent(name ?? EventName.PAGE_VIEWED, { ...combinedProps, ...properties })
|
||||
const commitHash = process.env.REACT_APP_GIT_COMMIT_HASH
|
||||
sendAnalyticsEvent(name ?? EventName.PAGE_VIEWED, {
|
||||
...combinedProps,
|
||||
...properties,
|
||||
git_commit_hash: commitHash,
|
||||
})
|
||||
}
|
||||
// Impressions should only be logged on mount.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -8,14 +8,30 @@ export enum EventName {
|
||||
APP_LOADED = 'Application Loaded',
|
||||
APPROVE_TOKEN_TXN_SUBMITTED = 'Approve Token Transaction Submitted',
|
||||
CONNECT_WALLET_BUTTON_CLICKED = 'Connect Wallet Button Clicked',
|
||||
EXPLORE_BANNER_CLICKED = 'Explore Banner Clicked',
|
||||
EXPLORE_SEARCH_SELECTED = 'Explore Search Selected',
|
||||
EXPLORE_TOKEN_ROW_CLICKED = 'Explore Token Row Clicked',
|
||||
PAGE_VIEWED = 'Page Viewed',
|
||||
NAVBAR_RESULT_SELECTED = 'Navbar Result Selected',
|
||||
NAVBAR_SEARCH_SELECTED = 'Navbar Search Selected',
|
||||
NAVBAR_SEARCH_EXITED = 'Navbar Search Exited',
|
||||
NFT_ACTIVITY_SELECTED = 'NFT Activity Selected',
|
||||
NFT_BUY_ADDED = 'NFT Buy Bag Added',
|
||||
NFT_BUY_BAG_CHANGED = 'NFT Buy Bag Changed',
|
||||
NFT_BUY_BAG_PAY = 'NFT Buy Bag Pay Clicked',
|
||||
NFT_BUY_BAG_REFUNDED = 'NFT Buy Bag Refunded',
|
||||
NFT_BUY_BAG_SIGNED = 'NFT Buy Bag Signed',
|
||||
NFT_BUY_BAG_SUCCEEDED = 'NFT Buy Bag Succeeded',
|
||||
NFT_FILTER_OPENED = 'NFT Collection Filter Opened',
|
||||
NFT_FILTER_SELECTED = 'NFT Filter Selected',
|
||||
NFT_TRENDING_ROW_SELECTED = 'Trending Row Selected',
|
||||
SWAP_AUTOROUTER_VISUALIZATION_EXPANDED = 'Swap Autorouter Visualization Expanded',
|
||||
SWAP_DETAILS_EXPANDED = 'Swap Details Expanded',
|
||||
SWAP_MAX_TOKEN_AMOUNT_SELECTED = 'Swap Max Token Amount Selected',
|
||||
SWAP_PRICE_UPDATE_ACKNOWLEDGED = 'Swap Price Update Acknowledged',
|
||||
SWAP_QUOTE_RECEIVED = 'Swap Quote Received',
|
||||
SWAP_SUBMITTED = 'Swap Submitted',
|
||||
SWAP_SIGNED = 'Swap Signed',
|
||||
SWAP_SUBMITTED_BUTTON_CLICKED = 'Swap Submit Button Clicked',
|
||||
SWAP_TOKENS_REVERSED = 'Swap Tokens Reversed',
|
||||
SWAP_TRANSACTION_COMPLETED = 'Swap Transaction Completed',
|
||||
TOKEN_IMPORTED = 'Token Imported',
|
||||
@@ -24,6 +40,7 @@ export enum EventName {
|
||||
WALLET_CONNECT_TXN_COMPLETED = 'Wallet Connect Transaction Completed',
|
||||
WALLET_SELECTED = 'Wallet Selected',
|
||||
WEB_VITALS = 'Web Vitals',
|
||||
WRAP_TOKEN_TXN_INVALIDATED = 'Wrap Token Transaction Invalidated',
|
||||
WRAP_TOKEN_TXN_SUBMITTED = 'Wrap Token Transaction Submitted',
|
||||
// alphabetize additional event names.
|
||||
}
|
||||
@@ -31,6 +48,7 @@ export enum EventName {
|
||||
export enum CUSTOM_USER_PROPERTIES {
|
||||
ALL_WALLET_ADDRESSES_CONNECTED = 'all_wallet_addresses_connected',
|
||||
ALL_WALLET_CHAIN_IDS = 'all_wallet_chain_ids',
|
||||
USER_AGENT = 'user_agent',
|
||||
BROWSER = 'browser',
|
||||
DARK_MODE = 'is_dark_mode',
|
||||
EXPERT_MODE = 'is_expert_mode',
|
||||
@@ -49,6 +67,7 @@ export enum BROWSER {
|
||||
EDGE_CHROMIUM = 'Microsoft Edge (Chromium)',
|
||||
CHROME = 'Google Chrome or Chromium',
|
||||
SAFARI = 'Apple Safari',
|
||||
BRAVE = 'Brave',
|
||||
UNKNOWN = 'unknown',
|
||||
}
|
||||
|
||||
@@ -57,8 +76,6 @@ export enum WALLET_CONNECTION_RESULT {
|
||||
FAILED = 'Failed',
|
||||
}
|
||||
|
||||
export const NATIVE_CHAIN_ID = 'NATIVE'
|
||||
|
||||
export enum SWAP_PRICE_UPDATE_USER_RESPONSE {
|
||||
ACCEPTED = 'Accepted',
|
||||
REJECTED = 'Rejected',
|
||||
@@ -68,6 +85,10 @@ export enum SWAP_PRICE_UPDATE_USER_RESPONSE {
|
||||
* Known pages in the app. Highest order context.
|
||||
*/
|
||||
export enum PageName {
|
||||
NFT_COLLECTION_PAGE = 'nft-collection-page',
|
||||
NFT_DETAILS_PAGE = 'nft-details-page',
|
||||
NFT_EXPLORE_PAGE = 'nft-explore-page',
|
||||
TOKEN_DETAILS_PAGE = 'token-details',
|
||||
TOKENS_PAGE = 'tokens-page',
|
||||
POOL_PAGE = 'pool-page',
|
||||
SWAP_PAGE = 'swap-page',
|
||||
@@ -83,12 +104,15 @@ export enum PageName {
|
||||
export enum SectionName {
|
||||
CURRENCY_INPUT_PANEL = 'swap-currency-input',
|
||||
CURRENCY_OUTPUT_PANEL = 'swap-currency-output',
|
||||
NAVBAR_SEARCH = 'Navbar Search',
|
||||
WIDGET = 'widget',
|
||||
// alphabetize additional section names.
|
||||
}
|
||||
|
||||
/** Known modals for analytics purposes. */
|
||||
export enum ModalName {
|
||||
CONFIRM_SWAP = 'confirm-swap-modal',
|
||||
NFT_TX_COMPLETE = 'nft-tx-complete-modal',
|
||||
TOKEN_SELECTOR = 'token-selector-modal',
|
||||
// alphabetize additional modal names.
|
||||
}
|
||||
@@ -102,8 +126,16 @@ export enum ElementName {
|
||||
COMMON_BASES_CURRENCY_BUTTON = 'common-bases-currency-button',
|
||||
CONFIRM_SWAP_BUTTON = 'confirm-swap-or-send',
|
||||
CONNECT_WALLET_BUTTON = 'connect-wallet-button',
|
||||
EXPLORE_BANNER = 'explore-banner',
|
||||
EXPLORE_SEARCH_INPUT = 'explore_search_input',
|
||||
IMPORT_TOKEN_BUTTON = 'import-token-button',
|
||||
MAX_TOKEN_AMOUNT_BUTTON = 'max-token-amount-button',
|
||||
NAVBAR_SEARCH_INPUT = 'navbar-search-input',
|
||||
NFT_ACTIVITY_TAB = 'nft-activity-tab',
|
||||
NFT_BUY_BAG_PAY_BUTTON = 'nft-buy-bag-pay-button',
|
||||
NFT_FILTER_BUTTON = 'nft-filter-button',
|
||||
NFT_FILTER_OPTION = 'nft-filter-option',
|
||||
NFT_TRENDING_ROW = 'nft-trending-row',
|
||||
PRICE_UPDATE_ACCEPT_BUTTON = 'price-update-accept-button',
|
||||
SWAP_BUTTON = 'swap-button',
|
||||
SWAP_DETAILS_DROPDOWN = 'swap-details-dropdown',
|
||||
@@ -120,7 +152,26 @@ export enum ElementName {
|
||||
*/
|
||||
export enum Event {
|
||||
onClick = 'onClick',
|
||||
onFocus = 'onFocus',
|
||||
onKeyPress = 'onKeyPress',
|
||||
onSelect = 'onSelect',
|
||||
// alphabetize additional events.
|
||||
}
|
||||
|
||||
/** Known navbar search result types */
|
||||
export enum NavBarSearchTypes {
|
||||
COLLECTION_SUGGESTION = 'collection-suggestion',
|
||||
COLLECTION_TRENDING = 'collection-trending',
|
||||
RECENT_SEARCH = 'recent',
|
||||
TOKEN_SUGGESTION = 'token-suggestion',
|
||||
TOKEN_TRENDING = 'token-trending',
|
||||
}
|
||||
|
||||
/**
|
||||
* Known Filter Types for NFTs
|
||||
*/
|
||||
export enum FilterTypes {
|
||||
MARKETPLACE = 'Marketplace',
|
||||
PRICE_RANGE = 'Price Range',
|
||||
TRAIT = 'Trait',
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
import { Identify, identify, init, track } from '@amplitude/analytics-browser'
|
||||
import { isProductionEnv } from 'utils/env'
|
||||
|
||||
const DUMMY_KEY = '00000000000000000000000000000000'
|
||||
const PROXY_URL = process.env.REACT_APP_AMPLITUDE_PROXY_URL
|
||||
|
||||
/**
|
||||
* Initializes Amplitude with API key for project.
|
||||
*
|
||||
* Uniswap has two Amplitude projects: test and production. You must be a
|
||||
* member of the organization on Amplitude to view details.
|
||||
* Initializes Amplitude SDK and configures it to send events to a Uniswap reverse proxy,
|
||||
* which relays to events to relevant Amplitude endpoints. You must be a
|
||||
* member of the organization on Amplitude to view logged events.
|
||||
*/
|
||||
export function initializeAnalytics() {
|
||||
const API_KEY = isProductionEnv() ? process.env.REACT_APP_AMPLITUDE_KEY : process.env.REACT_APP_AMPLITUDE_TEST_KEY
|
||||
|
||||
if (typeof API_KEY === 'undefined') {
|
||||
const keyName = isProductionEnv() ? 'REACT_APP_AMPLITUDE_KEY' : 'REACT_APP_AMPLITUDE_TEST_KEY'
|
||||
console.error(`${keyName} is undefined, Amplitude analytics will not run.`)
|
||||
if (typeof PROXY_URL === 'undefined') {
|
||||
console.error('REACT_APP_AMPLITUDE_PROXY_URL is undefined, Amplitude analytics will not run.')
|
||||
return
|
||||
}
|
||||
|
||||
init(
|
||||
API_KEY,
|
||||
DUMMY_KEY,
|
||||
/* userId= */ undefined, // User ID should be undefined to let Amplitude default to Device ID
|
||||
/* options= */
|
||||
{
|
||||
// Configure the SDK to work with alternate endpoint
|
||||
serverUrl: PROXY_URL,
|
||||
// Disable tracking of private user information by Amplitude
|
||||
trackingOptions: {
|
||||
ipAddress: false,
|
||||
@@ -33,21 +33,14 @@ export function initializeAnalytics() {
|
||||
)
|
||||
}
|
||||
|
||||
/** Sends an approved (finalized) event to Amplitude production project. */
|
||||
/** Sends an event to Amplitude. */
|
||||
export function sendAnalyticsEvent(eventName: string, eventProperties?: Record<string, unknown>) {
|
||||
if (!isProductionEnv()) {
|
||||
console.log(`[amplitude(${eventName})]: ${JSON.stringify(eventProperties)}`)
|
||||
if (!PROXY_URL) {
|
||||
console.log(`[analytics(${eventName})]: ${JSON.stringify(eventProperties)}`)
|
||||
return
|
||||
}
|
||||
|
||||
track(eventName, eventProperties)
|
||||
}
|
||||
|
||||
/** Sends a draft event to Amplitude test project. */
|
||||
export function sendTestAnalyticsEvent(eventName: string, eventProperties?: Record<string, unknown>) {
|
||||
if (isProductionEnv()) return
|
||||
|
||||
track(eventName, eventProperties)
|
||||
track(eventName, { ...eventProperties, origin })
|
||||
}
|
||||
|
||||
type Value = string | number | boolean | string[] | number[]
|
||||
80
src/analytics/utils.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, CurrencyAmount, Percent, Price, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
||||
import { InterfaceTrade } from 'state/routing/types'
|
||||
import { computeRealizedPriceImpact } from 'utils/prices'
|
||||
|
||||
export const getDurationUntilTimestampSeconds = (futureTimestampInSecondsSinceEpoch?: number): number | undefined => {
|
||||
if (!futureTimestampInSecondsSinceEpoch) return undefined
|
||||
return futureTimestampInSecondsSinceEpoch - new Date().getTime() / 1000
|
||||
}
|
||||
|
||||
export const getDurationFromDateMilliseconds = (start?: Date): number | undefined => {
|
||||
if (!start) return undefined
|
||||
return new Date().getTime() - start.getTime()
|
||||
}
|
||||
|
||||
export const formatToDecimal = (
|
||||
intialNumberObject: Percent | CurrencyAmount<Token | Currency>,
|
||||
decimalPlace: number
|
||||
): number => parseFloat(intialNumberObject.toFixed(decimalPlace))
|
||||
|
||||
export const getTokenAddress = (currency: Currency) => (currency.isNative ? NATIVE_CHAIN_ID : currency.address)
|
||||
|
||||
export const formatPercentInBasisPointsNumber = (percent: Percent): number => parseFloat(percent.toFixed(2)) * 100
|
||||
|
||||
export const formatPercentNumber = (percent: Percent): number => parseFloat(percent.toFixed(2))
|
||||
|
||||
export const getPriceUpdateBasisPoints = (
|
||||
prevPrice: Price<Currency, Currency>,
|
||||
newPrice: Price<Currency, Currency>
|
||||
): number => {
|
||||
const changeFraction = newPrice.subtract(prevPrice).divide(prevPrice)
|
||||
const changePercentage = new Percent(changeFraction.numerator, changeFraction.denominator)
|
||||
return formatPercentInBasisPointsNumber(changePercentage)
|
||||
}
|
||||
|
||||
export const formatSwapSignedAnalyticsEventProperties = ({
|
||||
trade,
|
||||
txHash,
|
||||
}: {
|
||||
trade: InterfaceTrade<Currency, Currency, TradeType> | Trade<Currency, Currency, TradeType>
|
||||
txHash: string
|
||||
}) => ({
|
||||
transaction_hash: txHash,
|
||||
token_in_address: getTokenAddress(trade.inputAmount.currency),
|
||||
token_out_address: getTokenAddress(trade.outputAmount.currency),
|
||||
token_in_symbol: trade.inputAmount.currency.symbol,
|
||||
token_out_symbol: trade.outputAmount.currency.symbol,
|
||||
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
|
||||
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
|
||||
price_impact_basis_points: formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)),
|
||||
chain_id:
|
||||
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
|
||||
? trade.inputAmount.currency.chainId
|
||||
: undefined,
|
||||
})
|
||||
|
||||
export const formatSwapQuoteReceivedEventProperties = (
|
||||
trade: Trade<Currency, Currency, TradeType>,
|
||||
gasUseEstimateUSD?: CurrencyAmount<Token>,
|
||||
fetchingSwapQuoteStartTime?: Date
|
||||
) => {
|
||||
return {
|
||||
token_in_symbol: trade.inputAmount.currency.symbol,
|
||||
token_out_symbol: trade.outputAmount.currency.symbol,
|
||||
token_in_address: getTokenAddress(trade.inputAmount.currency),
|
||||
token_out_address: getTokenAddress(trade.outputAmount.currency),
|
||||
price_impact_basis_points: trade ? formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)) : undefined,
|
||||
estimated_network_fee_usd: gasUseEstimateUSD ? formatToDecimal(gasUseEstimateUSD, 2) : undefined,
|
||||
chain_id:
|
||||
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
|
||||
? trade.inputAmount.currency.chainId
|
||||
: undefined,
|
||||
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
|
||||
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
|
||||
quote_latency_milliseconds: fetchingSwapQuoteStartTime
|
||||
? getDurationFromDateMilliseconds(fetchingSwapQuoteStartTime)
|
||||
: undefined,
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/images/sizingImage.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
@@ -1,3 +1,186 @@
|
||||
<svg width="10" height="12" viewBox="0 0 10 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.75 11C4.75 11 5.22377 11.1391 5.80923 10.5828L7.64433 8.65908C8.35405 7.91508 8.74998 6.92678 8.74989 5.89856C8.7498 4.72716 8.74971 3.31706 8.74991 2.50009C8.74996 2.22391 8.77618 2 8.5 2H8.25M6.74898 5.75L6.74979 2L6.74991 1.50009C6.74996 1.22391 6.52609 1 6.24991 1H4.25167C3.97553 1 3.75167 1.22386 3.75167 1.5V4.75039C3.75167 5.29859 3.52665 5.82276 3.12922 6.20034L1.6891 7.56856C1.10364 8.12478 1.10363 9.0266 1.68909 9.58283C2.12197 9.99409 2.75372 10.1013 3.29025 9.90438C3.47937 9.83497 3.65665 9.72779 3.80923 9.58283L5.80923 7.6827M6.74898 5.75L6.7487 6.36119C6.74861 6.63517 6.63611 6.89711 6.43748 7.08582L5.80923 7.6827M6.74898 5.75H6.4384C5.67845 5.75 5.19623 6.56419 5.56146 7.23061L5.80923 7.6827" stroke="white" stroke-linecap="round"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20">
|
||||
<g filter="url(#a)">
|
||||
<path fill="url(#b)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
|
||||
<path fill="url(#c)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
|
||||
<path fill="url(#d)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
|
||||
<path fill="url(#e)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
|
||||
<path fill="url(#f)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
|
||||
</g>
|
||||
<g filter="url(#g)">
|
||||
<path fill="url(#h)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
|
||||
<path fill="url(#i)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
|
||||
<path fill="url(#j)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
|
||||
<path fill="url(#k)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
|
||||
<path fill="url(#l)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
|
||||
</g>
|
||||
<path fill="url(#m)" d="M17.654 5.776h-5.431v.673h5.431v-.673Z"/>
|
||||
<path fill="url(#n)" d="M17.654 5.776h-5.431v.673h5.431v-.673Z"/>
|
||||
<path fill="url(#o)" d="M10.955 3.51H5.523v.674h5.432V3.51Z"/>
|
||||
<path fill="url(#p)" d="M10.955 3.51H5.523v.674h5.432V3.51Z"/>
|
||||
<path fill="url(#q)" d="M17.606 4.523c.031.11.048.225.048.345v.328h-5.431v-.328c0-.12.016-.236.048-.345h5.335Z"/>
|
||||
<path fill="url(#r)" d="M17.606 4.523c.031.11.048.225.048.345v.328h-5.431v-.328c0-.12.016-.236.048-.345h5.335Z"/>
|
||||
<path fill="url(#s)" d="M10.907 2.257c.031.11.048.225.048.345v.329H5.523v-.329c0-.12.017-.235.049-.345h5.335Z"/>
|
||||
<path fill="url(#t)" d="M10.907 2.257c.031.11.048.225.048.345v.329H5.523v-.329c0-.12.017-.235.049-.345h5.335Z"/>
|
||||
<g filter="url(#u)">
|
||||
<path fill="url(#v)" d="M17.654 12.236h-3.116a.173.173 0 0 0-.176.17c0 1.255.904 2.3 2.096 2.518l.332-.349a3.125 3.125 0 0 0 .864-2.157v-.182Z"/>
|
||||
</g>
|
||||
<g filter="url(#w)">
|
||||
<path fill="url(#x)" d="M10.955 9.97H7.839a.173.173 0 0 0-.176.17c0 1.255.903 2.3 2.096 2.518l.332-.349a3.125 3.125 0 0 0 .864-2.156V9.97Z"/>
|
||||
</g>
|
||||
<g filter="url(#y)">
|
||||
<path fill="url(#z)" d="M13.243 18.21v-.05a3.424 3.424 0 0 0-3.856-3.397c-.511.986-.496 2.268.366 3.133.892.895 2.474.98 3.49.314Z"/>
|
||||
</g>
|
||||
<g filter="url(#A)">
|
||||
<path fill="url(#B)" d="M6.544 15.944v-.05a3.424 3.424 0 0 0-3.857-3.396c-.51.985-.495 2.267.366 3.132.892.896 2.475.98 3.49.314Z"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="b" x1="17.248" x2="13.193" y1="4.4" y2="16.827" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FDF2FF"/>
|
||||
<stop offset="1" stop-color="#FFECFB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="c" x1="17.654" x2="15.615" y1="11.854" y2="11.854" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFCCF1"/>
|
||||
<stop offset="1" stop-color="#FFC5EF" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="f" x1="14.99" x2="13.818" y1="16.932" y2="15.721" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".209" stop-color="#EDB0DF"/>
|
||||
<stop offset="1" stop-color="#ECAAED" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="h" x1="10.549" x2="6.493" y1="2.134" y2="14.562" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FDF2FF"/>
|
||||
<stop offset="1" stop-color="#FFECFB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="i" x1="10.954" x2="8.915" y1="9.588" y2="9.588" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFCCF1"/>
|
||||
<stop offset="1" stop-color="#FFC5EF" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="l" x1="8.29" x2="7.119" y1="14.666" y2="13.456" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".209" stop-color="#EDB0DF"/>
|
||||
<stop offset="1" stop-color="#ECAAED" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="m" x1="12.773" x2="16.915" y1="6.449" y2="6.449" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E95FDB"/>
|
||||
<stop offset="1" stop-color="#FF46CB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="n" x1="12.223" x2="12.793" y1="6.449" y2="6.449" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9F4977"/>
|
||||
<stop offset="1" stop-color="#CA5284" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="o" x1="6.074" x2="10.216" y1="4.184" y2="4.184" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E95FDB"/>
|
||||
<stop offset="1" stop-color="#FF46CB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="p" x1="5.523" x2="6.094" y1="4.184" y2="4.184" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9F4977"/>
|
||||
<stop offset="1" stop-color="#CA5284" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="q" x1="12.773" x2="16.915" y1="5.196" y2="5.196" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E95FDB"/>
|
||||
<stop offset="1" stop-color="#FF46CB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="r" x1="12.223" x2="12.793" y1="5.196" y2="5.196" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9F4977"/>
|
||||
<stop offset="1" stop-color="#CA5284" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="s" x1="6.074" x2="10.216" y1="2.931" y2="2.931" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E95FDB"/>
|
||||
<stop offset="1" stop-color="#FF46CB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="t" x1="5.523" x2="6.094" y1="2.931" y2="2.931" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9F4977"/>
|
||||
<stop offset="1" stop-color="#CA5284" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="d" cx="0" cy="0" r="1" gradientTransform="matrix(2.8125 0 0 6.01563 12.02 8.026)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFD1F5"/>
|
||||
<stop offset="1" stop-color="#FECAFF" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="e" cx="0" cy="0" r="1" gradientTransform="matrix(1.54348 -1.53472 2.30642 2.31958 11.806 16.12)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFC0FC"/>
|
||||
<stop offset="1" stop-color="#FFBCFC" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="j" cx="0" cy="0" r="1" gradientTransform="matrix(2.8125 0 0 6.01563 5.322 5.76)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFD1F5"/>
|
||||
<stop offset="1" stop-color="#FECAFF" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="k" cx="0" cy="0" r="1" gradientTransform="matrix(1.54347 -1.53473 2.30643 2.31957 5.107 13.854)" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFC0FC"/>
|
||||
<stop offset="1" stop-color="#FFBCFC" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="v" cx="0" cy="0" r="1" gradientTransform="matrix(-.42911 2.50572 -2.20218 -.37713 16.437 12.236)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".147" stop-color="#FF52CF"/>
|
||||
<stop offset="1" stop-color="#FB3FFF"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="x" cx="0" cy="0" r="1" gradientTransform="matrix(-.42911 2.50572 -2.20218 -.37713 9.738 9.97)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".147" stop-color="#FF52CF"/>
|
||||
<stop offset="1" stop-color="#FB3FFF"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="z" cx="0" cy="0" r="1" gradientTransform="rotate(167.471 5.464 9.437) scale(4.54024 4.83245)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".268" stop-color="#FF4EE3"/>
|
||||
<stop offset=".92" stop-color="#D12396"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="B" cx="0" cy="0" r="1" gradientTransform="rotate(167.471 2.239 7.937) scale(4.54024 4.83245)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".268" stop-color="#FF4EE3"/>
|
||||
<stop offset=".92" stop-color="#D12396"/>
|
||||
</radialGradient>
|
||||
<filter id="a" width="8.808" height="15.23" x="9.045" y="3.418" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx=".2" dy="-.2"/>
|
||||
<feGaussianBlur stdDeviation=".3"/>
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
|
||||
<feColorMatrix values="0 0 0 0 0.522043 0 0 0 0 0.119948 0 0 0 0 0.5875 0 0 0 1 0"/>
|
||||
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
|
||||
</filter>
|
||||
<filter id="g" width="8.808" height="15.23" x="2.346" y="1.152" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx=".2" dy="-.2"/>
|
||||
<feGaussianBlur stdDeviation=".3"/>
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
|
||||
<feColorMatrix values="0 0 0 0 0.545098 0 0 0 0 0.219608 0 0 0 0 0.512549 0 0 0 1 0"/>
|
||||
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
|
||||
</filter>
|
||||
<filter id="u" width="3.541" height="2.688" x="14.112" y="12.236" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx="-.25"/>
|
||||
<feGaussianBlur stdDeviation=".25"/>
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
|
||||
<feColorMatrix values="0 0 0 0 0.976471 0 0 0 0 0.145098 0 0 0 0 0.743686 0 0 0 1 0"/>
|
||||
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
|
||||
</filter>
|
||||
<filter id="w" width="3.541" height="2.688" x="7.413" y="9.97" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx="-.25"/>
|
||||
<feGaussianBlur stdDeviation=".25"/>
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
|
||||
<feColorMatrix values="0 0 0 0 0.976471 0 0 0 0 0.145098 0 0 0 0 0.743686 0 0 0 1 0"/>
|
||||
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
|
||||
</filter>
|
||||
<filter id="y" width="4.448" height="4.112" x="9.045" y="14.536" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx=".25" dy="-.2"/>
|
||||
<feGaussianBlur stdDeviation=".25"/>
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
|
||||
<feColorMatrix values="0 0 0 0 0.906118 0 0 0 0 0.329412 0 0 0 0 1 0 0 0 1 0"/>
|
||||
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
|
||||
</filter>
|
||||
<filter id="A" width="4.448" height="4.112" x="2.346" y="12.27" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||
<feOffset dx=".25" dy="-.2"/>
|
||||
<feGaussianBlur stdDeviation=".25"/>
|
||||
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
|
||||
<feColorMatrix values="0 0 0 0 0.906118 0 0 0 0 0.329412 0 0 0 0 1 0 0 0 1 0"/>
|
||||
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 871 B After Width: | Height: | Size: 13 KiB |
3
src/assets/svg/swap-arrows.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="M7.80333 4.8863C7.51044 5.17919 7.51044 5.65406 7.80333 5.94696C8.09622 6.23985 8.5711 6.23985 8.86399 5.94696L7.80333 4.8863ZM12.0837 1.66663L12.614 1.1363C12.3211 0.843403 11.8462 0.843403 11.5533 1.1363L12.0837 1.66663ZM15.3033 5.94696C15.5962 6.23985 16.0711 6.23985 16.364 5.94696C16.6569 5.65406 16.6569 5.17919 16.364 4.8863L15.3033 5.94696ZM11.3337 9.99996C11.3337 10.4142 11.6694 10.75 12.0837 10.75C12.4979 10.75 12.8337 10.4142 12.8337 9.99996H11.3337ZM12.1973 15.1136C12.4902 14.8207 12.4902 14.3459 12.1973 14.053C11.9044 13.7601 11.4296 13.7601 11.1367 14.053L12.1973 15.1136ZM7.91699 18.3333L7.38666 18.8636C7.52731 19.0043 7.71808 19.0833 7.91699 19.0833C8.1159 19.0833 8.30667 19.0043 8.44732 18.8636L7.91699 18.3333ZM4.69732 14.053C4.40443 13.7601 3.92956 13.7601 3.63666 14.053C3.34377 14.3459 3.34377 14.8207 3.63666 15.1136L4.69732 14.053ZM8.66699 10.8333C8.66699 10.4191 8.33121 10.0833 7.91699 10.0833C7.50278 10.0833 7.16699 10.4191 7.16699 10.8333H8.66699ZM8.86399 5.94696L12.614 2.19696L11.5533 1.1363L7.80333 4.8863L8.86399 5.94696ZM11.5533 2.19696L15.3033 5.94696L16.364 4.8863L12.614 1.1363L11.5533 2.19696ZM11.3337 1.66663V9.99996H12.8337V1.66663H11.3337ZM11.1367 14.053L7.38666 17.803L8.44732 18.8636L12.1973 15.1136L11.1367 14.053ZM8.44732 17.803L4.69732 14.053L3.63666 15.1136L7.38666 18.8636L8.44732 17.803ZM8.66699 18.3333L8.66699 10.8333H7.16699L7.16699 18.3333H8.66699Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.24453 18.0887C3.24331 19.0467 3.47372 19.7558 3.93576 20.2158C4.39658 20.6771 5.09574 20.904 6.03326 20.8967H8.11975C8.20693 20.8934 8.29386 20.9079 8.37521 20.9395C8.45656 20.9711 8.53062 21.019 8.5928 21.0802L10.0779 22.5484C10.7527 23.2226 11.4139 23.5578 12.0617 23.5541C12.7096 23.5504 13.3709 23.2152 14.0456 22.5484L15.5124 21.0802C15.5767 21.0182 15.6529 20.97 15.7365 20.9385C15.82 20.9069 15.9091 20.8927 15.9982 20.8967H18.0719C19.0192 20.8979 19.7251 20.6673 20.1896 20.2048C20.6541 19.7423 20.8864 19.0333 20.8864 18.0777V16.0021C20.8816 15.8222 20.9474 15.6476 21.0697 15.5157L22.5365 14.0475C23.2198 13.3758 23.559 12.7145 23.5541 12.0636C23.5492 11.4127 23.21 10.7508 22.5365 10.0779L21.0697 8.6097C20.9471 8.47802 20.8812 8.30329 20.8864 8.12336V6.04769C20.8851 5.09092 20.6547 4.3819 20.1951 3.92064C19.7355 3.45939 19.0278 3.22875 18.0719 3.22875H15.9982C15.9091 3.23242 15.8201 3.21807 15.7366 3.18653C15.6532 3.155 15.5769 3.10694 15.5124 3.04523L14.0456 1.57703C13.3709 0.902883 12.7096 0.567648 12.0617 0.571319C11.4139 0.574989 10.7527 0.910224 10.0779 1.57703L8.5928 3.04523C8.53043 3.10622 8.45638 3.15393 8.37508 3.18547C8.29377 3.21701 8.20689 3.23173 8.11975 3.22875H6.03326C5.08718 3.22998 4.38373 3.45877 3.92291 3.91513C3.4621 4.3715 3.23168 5.08235 3.23168 6.04769V8.12887C3.23683 8.3088 3.17096 8.48352 3.04833 8.6152L1.58154 10.0834C0.908042 10.7551 0.571289 11.417 0.571289 12.0691C0.571289 12.7213 0.912332 13.3844 1.59439 14.0585L3.06118 15.5267C3.18346 15.6586 3.24928 15.8332 3.24453 16.0131V18.0887Z" fill="#4C82FB"/>
|
||||
<path d="M3.24453 18.0887C3.24331 19.0467 3.47372 19.7558 3.93576 20.2158C4.39658 20.6771 5.09574 20.904 6.03326 20.8967H8.11975C8.20693 20.8934 8.29386 20.9079 8.37521 20.9395C8.45656 20.9711 8.53062 21.019 8.5928 21.0802L10.0779 22.5484C10.7527 23.2226 11.4139 23.5578 12.0617 23.5541C12.7096 23.5504 13.3709 23.2152 14.0456 22.5484L15.5124 21.0802C15.5767 21.0182 15.6529 20.97 15.7365 20.9385C15.82 20.9069 15.9091 20.8927 15.9982 20.8967H18.0719C19.0192 20.8979 19.7251 20.6673 20.1896 20.2048C20.6541 19.7423 20.8864 19.0333 20.8864 18.0777V16.0021C20.8816 15.8222 20.9474 15.6476 21.0697 15.5157L22.5365 14.0475C23.2198 13.3758 23.559 12.7145 23.5541 12.0636C23.5492 11.4127 23.21 10.7508 22.5365 10.0779L21.0697 8.6097C20.9471 8.47802 20.8812 8.30329 20.8864 8.12336V6.04769C20.8851 5.09092 20.6547 4.3819 20.1951 3.92064C19.7355 3.45939 19.0278 3.22875 18.0719 3.22875H15.9982C15.9091 3.23242 15.8201 3.21807 15.7366 3.18653C15.6532 3.155 15.5769 3.10694 15.5124 3.04523L14.0456 1.57703C13.3709 0.902883 12.7096 0.567648 12.0617 0.571319C11.4139 0.574989 10.7527 0.910224 10.0779 1.57703L8.5928 3.04523C8.53043 3.10622 8.45638 3.15393 8.37508 3.18547C8.29377 3.21701 8.20689 3.23173 8.11975 3.22875H6.03326C5.08718 3.22998 4.38373 3.45877 3.92291 3.91513C3.4621 4.3715 3.23168 5.08235 3.23168 6.04769V8.12887C3.23683 8.3088 3.17096 8.48352 3.04833 8.6152L1.58154 10.0834C0.908042 10.7551 0.571289 11.417 0.571289 12.0691C0.571289 12.7213 0.912332 13.3844 1.59439 14.0585L3.06118 15.5267C3.18346 15.6586 3.24928 15.8332 3.24453 16.0131V18.0887Z" fill="currentColor"/>
|
||||
<path d="M11.996 15.9909C11.7795 16.3208 11.4599 16.5064 11.0887 16.5064C10.7072 16.5064 10.4083 16.3517 10.1299 15.9909L7.69677 13.0216C7.5215 12.8051 7.42871 12.5783 7.42871 12.3309C7.42871 11.8154 7.82049 11.4133 8.32567 11.4133C8.63497 11.4133 8.8824 11.5267 9.12984 11.8463L11.0475 14.2897L15.1199 7.75329C15.3364 7.40275 15.6147 7.23779 15.924 7.23779C16.4086 7.23779 16.8622 7.57802 16.8622 8.0832C16.8622 8.32033 16.7385 8.56777 16.6045 8.78427L11.996 15.9909Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -145,7 +145,7 @@ const CloseIcon = styled.div`
|
||||
top: 14px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
|
||||
import { NATIVE_CHAIN_ID } from './constants'
|
||||
|
||||
export const getDurationUntilTimestampSeconds = (futureTimestampInSecondsSinceEpoch?: number): number | undefined => {
|
||||
if (!futureTimestampInSecondsSinceEpoch) return undefined
|
||||
return futureTimestampInSecondsSinceEpoch - new Date().getTime() / 1000
|
||||
}
|
||||
|
||||
export const getDurationFromDateMilliseconds = (start: Date): number => {
|
||||
return new Date().getTime() - start.getTime()
|
||||
}
|
||||
|
||||
export const formatToDecimal = (
|
||||
intialNumberObject: Percent | CurrencyAmount<Token | Currency>,
|
||||
decimalPlace: number
|
||||
): number => parseFloat(intialNumberObject.toFixed(decimalPlace))
|
||||
|
||||
export const getTokenAddress = (currency: Currency) => (currency.isNative ? NATIVE_CHAIN_ID : currency.address)
|
||||
|
||||
export const formatPercentInBasisPointsNumber = (percent: Percent): number => parseFloat(percent.toFixed(2)) * 100
|
||||
|
||||
export const formatPercentNumber = (percent: Percent): number => parseFloat(percent.toFixed(2))
|
||||
@@ -1,51 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
|
||||
const BLOCKED_ADDRESSES: string[] = [
|
||||
'0x7Db418b5D567A4e0E8c59Ad71BE1FcE48f3E6107',
|
||||
'0x72a5843cc08275C8171E582972Aa4fDa8C397B2A',
|
||||
'0x7F19720A857F834887FC9A7bC0a0fBe7Fc7f8102',
|
||||
'0xA7e5d5A720f06526557c513402f2e6B5fA20b008',
|
||||
'0x1da5821544e25c636c1417Ba96Ade4Cf6D2f9B5A',
|
||||
'0x9F4cda013E354b8fC285BF4b9A60460cEe7f7Ea9',
|
||||
'0x19Aa5Fe80D33a56D56c78e82eA5E50E5d80b4Dff',
|
||||
'0x2f389cE8bD8ff92De3402FFCe4691d17fC4f6535',
|
||||
'0xe7aa314c77F4233C18C6CC84384A9247c0cf367B',
|
||||
'0x7F367cC41522cE07553e823bf3be79A889DEbe1B',
|
||||
'0xd882cFc20F52f2599D84b8e8D58C7FB62cfE344b',
|
||||
'0x901bb9583b24D97e995513C6778dc6888AB6870e',
|
||||
'0x8576aCC5C05D6Ce88f4e49bf65BdF0C62F91353C',
|
||||
'0xC8a65Fadf0e0dDAf421F28FEAb69Bf6E2E589963',
|
||||
'0x308eD4B7b49797e1A98D3818bFF6fe5385410370',
|
||||
'0x67d40EE1A85bf4a4Bb7Ffae16De985e8427B',
|
||||
'0x6f1ca141a28907f78ebaa64fb83a9088b02a83',
|
||||
'0x6acdfba02d390b97ac2b2d42a63e85293bcc1',
|
||||
'0x48549a34ae37b12f6a30566245176994e17c6',
|
||||
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121',
|
||||
'0xC455f7fd3e0e12afd51fba5c106909934D8A0e4a',
|
||||
'0x3CBdeD43EFdAf0FC77b9C55F6fC9988fCC9b757d',
|
||||
'0x67d40EE1A85bf4a4Bb7Ffae16De985e8427B6b45',
|
||||
'0x6F1cA141A28907F78Ebaa64fb83A9088b02A8352',
|
||||
'0x6aCDFBA02D390b97Ac2b2d42A63E85293BCc160e',
|
||||
'0x48549a34ae37b12f6a30566245176994e17c6b4a',
|
||||
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0',
|
||||
'0xC455f7fd3e0e12afd51fba5c106909934D8A0e4a',
|
||||
'0x629e7Da20197a5429d30da36E77d06CdF796b71A',
|
||||
'0x7FF9cFad3877F21d41Da833E2F775dB0569eE3D9',
|
||||
'0x098B716B8Aaf21512996dC57EB0615e2383E2f96',
|
||||
'0xfEC8A60023265364D066a1212fDE3930F6Ae8da7',
|
||||
]
|
||||
|
||||
export default function Blocklist({ children }: { children: ReactNode }) {
|
||||
const { account } = useWeb3React()
|
||||
const blocked: boolean = useMemo(() => Boolean(account && BLOCKED_ADDRESSES.indexOf(account) !== -1), [account])
|
||||
if (blocked) {
|
||||
return (
|
||||
<div>
|
||||
<Trans>Blocked address</Trans>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <>{children}</>
|
||||
}
|
||||
@@ -50,12 +50,12 @@ export const BaseButton = styled(RebassButton)<
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonPrimary = styled(BaseButton)<{ redesignFlag?: boolean }>`
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentAction : theme.deprecated_primary1)};
|
||||
font-size: ${({ redesignFlag }) => redesignFlag && '20px'};
|
||||
font-weight: ${({ redesignFlag }) => redesignFlag && '600'};
|
||||
padding: ${({ redesignFlag }) => redesignFlag && '16px'};
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentTextLightPrimary : 'white')};
|
||||
export const ButtonPrimary = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
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)};
|
||||
@@ -79,35 +79,28 @@ export const ButtonPrimary = styled(BaseButton)<{ redesignFlag?: boolean }>`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonLight = styled(BaseButton)<{ redesignFlag?: boolean }>`
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentActionSoft : theme.deprecated_primary5)};
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentAction : theme.deprecated_primaryText1)};
|
||||
font-size: ${({ redesignFlag }) => (redesignFlag ? '20px' : '16px')};
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
|
||||
export const ButtonLight = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.accentActionSoft};
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1pt
|
||||
${({ theme, disabled, redesignFlag }) =>
|
||||
!disabled && (redesignFlag ? theme.accentActionSoft : darken(0.03, theme.deprecated_primary5))};
|
||||
background-color: ${({ theme, disabled, redesignFlag }) =>
|
||||
!disabled && (redesignFlag ? theme.accentActionSoft : darken(0.03, theme.deprecated_primary5))};
|
||||
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
|
||||
background-color: ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
|
||||
}
|
||||
&:hover {
|
||||
background-color: ${({ theme, disabled, redesignFlag }) =>
|
||||
!disabled && (redesignFlag ? theme.accentActionSoft : darken(0.03, theme.deprecated_primary5))};
|
||||
background-color: ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1pt
|
||||
${({ theme, disabled, redesignFlag }) =>
|
||||
!disabled && (redesignFlag ? theme.accentActionSoft : darken(0.05, theme.deprecated_primary5))};
|
||||
background-color: ${({ theme, disabled, redesignFlag }) =>
|
||||
!disabled && (redesignFlag ? theme.accentActionSoft : darken(0.05, theme.deprecated_primary5))};
|
||||
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
|
||||
background-color: ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
|
||||
}
|
||||
:disabled {
|
||||
opacity: 0.4;
|
||||
:hover {
|
||||
cursor: auto;
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_primary5)};
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
@@ -176,28 +169,22 @@ export const ButtonOutlined = styled(BaseButton)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonYellow = styled(BaseButton)<{ redesignFlag?: boolean }>`
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentWarningSoft : theme.deprecated_yellow3)};
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentWarning : 'white')};
|
||||
export const ButtonYellow = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
color: ${({ theme }) => theme.accentWarning};
|
||||
&:focus {
|
||||
box-shadow: ${({ theme, redesignFlag }) => !redesignFlag && `0 0 0 1pt ${theme.deprecated_yellow3}`};
|
||||
background-color: ${({ theme, redesignFlag }) =>
|
||||
redesignFlag ? theme.accentWarningSoft : darken(0.05, theme.deprecated_yellow3)};
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
}
|
||||
&:hover {
|
||||
background: ${({ theme, redesignFlag }) => redesignFlag && theme.stateOverlayHover};
|
||||
mix-blend-mode: ${({ redesignFlag }) => redesignFlag && 'normal'};
|
||||
background-color: ${({ theme, redesignFlag }) => !redesignFlag && darken(0.05, theme.deprecated_yellow3)};
|
||||
background: ${({ theme }) => theme.stateOverlayHover};
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
&:active {
|
||||
box-shadow: ${({ theme, redesignFlag }) => !redesignFlag && `0 0 0 1pt ${darken(0.1, theme.deprecated_yellow3)}`};
|
||||
background-color: ${({ theme, redesignFlag }) =>
|
||||
redesignFlag ? theme.accentWarningSoft : darken(0.1, theme.deprecated_yellow3)};
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme, redesignFlag }) =>
|
||||
redesignFlag ? theme.accentWarningSoft : theme.deprecated_yellow3};
|
||||
opacity: ${({ redesignFlag }) => (redesignFlag ? '60%' : '50%')};
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
opacity: 60%;
|
||||
cursor: auto;
|
||||
}
|
||||
`
|
||||
@@ -378,3 +365,34 @@ export function ButtonRadioChecked({ active = false, children, ...rest }: { acti
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const MediumButton = styled.button`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
border-radius: 16px;
|
||||
border: 0;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-weight: 600;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
transition: 150ms ease background-color opacity;
|
||||
|
||||
: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};
|
||||
}
|
||||
`
|
||||
|
||||
94
src/components/Charts/AnimatedInLineChart.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
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 AnimatedInLineChartProps<T> = Omit<LineChartProps<T>, 'height' | 'width' | 'children'>
|
||||
|
||||
const config = {
|
||||
duration: 800,
|
||||
easing: easeCubicInOut,
|
||||
}
|
||||
|
||||
// code reference: https://airbnb.io/visx/lineradial
|
||||
|
||||
function AnimatedInLineChart<T>({
|
||||
data,
|
||||
getX,
|
||||
getY,
|
||||
marginTop,
|
||||
curve,
|
||||
color,
|
||||
strokeWidth,
|
||||
}: AnimatedInLineChartProps<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 : 0}
|
||||
fill="none"
|
||||
stroke={lineColor}
|
||||
/>
|
||||
{shouldAnimate && lineLength !== 0 && (
|
||||
<animated.path
|
||||
d={d}
|
||||
strokeWidth={strokeWidth}
|
||||
fill="none"
|
||||
stroke={lineColor}
|
||||
strokeDashoffset={spring.frame.to((v) => v * lineLength)}
|
||||
strokeDasharray={lineLength}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</LinePath>
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
|
||||
export default AnimatedInLineChart
|
||||
@@ -6,7 +6,7 @@ import { ReactNode } from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
import { Color } from 'theme/styled'
|
||||
|
||||
interface LineChartProps<T> {
|
||||
export interface LineChartProps<T> {
|
||||
data: T[]
|
||||
getX: (t: T) => number
|
||||
getY: (t: T) => number
|
||||
|
||||
@@ -1,50 +1,78 @@
|
||||
import { curveCardinalOpen, scaleLinear } from 'd3'
|
||||
import React from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
import { SparkLineLoadingBubble } from 'components/Tokens/TokenTable/TokenRow'
|
||||
import { curveCardinal, scaleLinear } from 'd3'
|
||||
import { PricePoint } from 'graphql/data/TokenPrice'
|
||||
import { SparklineMap, TopToken } from 'graphql/data/TopTokens'
|
||||
import { TimePeriod } from 'graphql/data/util'
|
||||
import { memo } from 'react'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import data from './data.json'
|
||||
import { getPriceBounds } from '../Tokens/TokenDetails/PriceChart'
|
||||
import LineChart from './LineChart'
|
||||
|
||||
type PricePoint = { value: number; timestamp: number }
|
||||
|
||||
function getPriceBounds(pricePoints: PricePoint[]): [number, number] {
|
||||
const prices = pricePoints.map((x) => x.value)
|
||||
const min = Math.min(...prices)
|
||||
const max = Math.max(...prices)
|
||||
return [min, max]
|
||||
}
|
||||
const LoadingContainer = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
interface SparklineChartProps {
|
||||
width: number
|
||||
height: number
|
||||
tokenData: TopToken
|
||||
pricePercentChange: number | undefined | null
|
||||
timePeriod: TimePeriod
|
||||
sparklineMap: SparklineMap
|
||||
}
|
||||
|
||||
function SparklineChart({ width, height }: SparklineChartProps) {
|
||||
function _SparklineChart({
|
||||
width,
|
||||
height,
|
||||
tokenData,
|
||||
pricePercentChange,
|
||||
timePeriod,
|
||||
sparklineMap,
|
||||
}: SparklineChartProps) {
|
||||
const theme = useTheme()
|
||||
// for sparkline
|
||||
const pricePoints = tokenData?.address ? sparklineMap[tokenData.address] : null
|
||||
|
||||
// Don't display if there's one or less pricepoints
|
||||
if (!pricePoints || pricePoints.length <= 1) {
|
||||
return (
|
||||
<LoadingContainer>
|
||||
<SparkLineLoadingBubble />
|
||||
</LoadingContainer>
|
||||
)
|
||||
}
|
||||
|
||||
/* TODO: Implement API calls & cache to use here */
|
||||
const pricePoints = data.day
|
||||
const startingPrice = pricePoints[0]
|
||||
const endingPrice = pricePoints[pricePoints.length - 1]
|
||||
|
||||
const timeScale = scaleLinear().domain([startingPrice.timestamp, endingPrice.timestamp]).range([0, width])
|
||||
const rdScale = scaleLinear().domain(getPriceBounds(pricePoints)).range([height, 0])
|
||||
|
||||
const isPositive = endingPrice.value >= startingPrice.value
|
||||
const widthScale = scaleLinear()
|
||||
.domain(
|
||||
// the range of possible input values
|
||||
[startingPrice.timestamp, endingPrice.timestamp]
|
||||
)
|
||||
.range(
|
||||
// the range of possible output values that the inputs should be transformed to (see https://www.d3indepth.com/scales/ for details)
|
||||
[0, 110]
|
||||
)
|
||||
const rdScale = scaleLinear().domain(getPriceBounds(pricePoints)).range([30, 0])
|
||||
const curveTension = 0.9
|
||||
|
||||
return (
|
||||
<LineChart
|
||||
data={pricePoints}
|
||||
getX={(p: PricePoint) => timeScale(p.timestamp)}
|
||||
getX={(p: PricePoint) => widthScale(p.timestamp)}
|
||||
getY={(p: PricePoint) => rdScale(p.value)}
|
||||
curve={curveCardinalOpen.tension(0.9)}
|
||||
marginTop={0}
|
||||
color={isPositive ? theme.accentSuccess : theme.accentFailure}
|
||||
curve={curveCardinal.tension(curveTension)}
|
||||
marginTop={5}
|
||||
color={pricePercentChange && pricePercentChange < 0 ? theme.accentFailure : theme.accentSuccess}
|
||||
strokeWidth={1.5}
|
||||
width={width}
|
||||
height={height}
|
||||
></LineChart>
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(SparklineChart)
|
||||
export default memo(_SparklineChart)
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Trans } from '@lingui/macro'
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import HoverInlineText from 'components/HoverInlineText'
|
||||
import { useMemo } from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
|
||||
@@ -31,18 +30,8 @@ export function FiatValue({
|
||||
const visibleDecimalPlaces = p < 1.05 ? 4 : 2
|
||||
|
||||
return (
|
||||
<ThemedText.DeprecatedBody fontSize={14} color={fiatValue ? theme.deprecated_text3 : theme.deprecated_text4}>
|
||||
{fiatValue ? (
|
||||
<Trans>
|
||||
$
|
||||
<HoverInlineText
|
||||
text={fiatValue?.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })}
|
||||
textColor={fiatValue ? theme.deprecated_text3 : theme.deprecated_text4}
|
||||
/>
|
||||
</Trans>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<ThemedText.DeprecatedBody fontSize={14} color={theme.textSecondary}>
|
||||
{fiatValue && <>${fiatValue?.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })}</>}
|
||||
{priceImpact ? (
|
||||
<span style={{ color: priceImpactColor }}>
|
||||
{' '}
|
||||
|
||||
@@ -2,16 +2,14 @@ import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import { ElementName, Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Lock } from 'react-feather'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
|
||||
@@ -26,45 +24,31 @@ import { RowBetween, RowFixed } from '../Row'
|
||||
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
|
||||
import { FiatValue } from './FiatValue'
|
||||
|
||||
const InputPanel = styled.div<{ hideInput?: boolean; redesignFlag: boolean }>`
|
||||
const InputPanel = styled.div<{ hideInput?: boolean }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
position: relative;
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
background-color: ${({ theme, redesignFlag, hideInput }) =>
|
||||
redesignFlag ? 'transparent' : hideInput ? 'transparent' : theme.deprecated_bg2};
|
||||
z-index: 1;
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
transition: height 1s ease;
|
||||
will-change: height;
|
||||
`
|
||||
|
||||
const FixedContainer = styled.div<{ redesignFlag: boolean }>`
|
||||
const FixedContainer = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
border-radius: 20px;
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg2)};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
`
|
||||
|
||||
const Container = styled.div<{ hideInput: boolean; disabled: boolean; redesignFlag: boolean }>`
|
||||
min-height: ${({ redesignFlag }) => redesignFlag && '69px'};
|
||||
const Container = styled.div<{ hideInput: boolean }>`
|
||||
min-height: 44px;
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg0)};
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg1)};
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
${({ theme, hideInput, disabled, redesignFlag }) =>
|
||||
!redesignFlag &&
|
||||
!disabled &&
|
||||
`
|
||||
:focus,
|
||||
:hover {
|
||||
border: 1px solid ${hideInput ? ' transparent' : theme.deprecated_bg3};
|
||||
}
|
||||
`}
|
||||
`
|
||||
|
||||
const CurrencySelect = styled(ButtonGray)<{
|
||||
@@ -72,22 +56,14 @@ const CurrencySelect = styled(ButtonGray)<{
|
||||
selected: boolean
|
||||
hideInput?: boolean
|
||||
disabled?: boolean
|
||||
redesignFlag: boolean
|
||||
}>`
|
||||
align-items: center;
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
redesignFlag
|
||||
? selected
|
||||
? theme.backgroundSurface
|
||||
: theme.accentAction
|
||||
: selected
|
||||
? theme.deprecated_bg2
|
||||
: theme.deprecated_primary1};
|
||||
background-color: ${({ selected, theme }) => (selected ? theme.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)};
|
||||
cursor: pointer;
|
||||
height: ${({ hideInput, redesignFlag }) => (redesignFlag ? 'unset' : hideInput ? '2.8rem' : '2.4rem')};
|
||||
height: unset;
|
||||
border-radius: 16px;
|
||||
outline: none;
|
||||
user-select: none;
|
||||
@@ -95,51 +71,52 @@ const CurrencySelect = styled(ButtonGray)<{
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
padding: ${({ selected, redesignFlag }) =>
|
||||
redesignFlag ? (selected ? '4px 8px 4px 4px' : '6px 6px 6px 8px') : '0 8px'};
|
||||
gap: ${({ redesignFlag }) => (redesignFlag ? '8px' : '0px')};
|
||||
padding: ${({ selected }) => (selected ? '4px 8px 4px 4px' : '6px 6px 6px 8px')};
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
margin-left: ${({ hideInput }) => (hideInput ? '0' : '12px')};
|
||||
:focus,
|
||||
:hover {
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
selected
|
||||
? redesignFlag
|
||||
? theme.backgroundSurface
|
||||
: theme.deprecated_bg3
|
||||
: darken(0.05, theme.deprecated_primary1)};
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: ${({ theme, selected }) => (selected ? theme.backgroundInteractive : theme.accentAction)};
|
||||
}
|
||||
|
||||
&:before {
|
||||
background-size: 100%;
|
||||
border-radius: inherit;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:hover:before {
|
||||
background-color: ${({ theme }) => theme.stateOverlayHover};
|
||||
}
|
||||
|
||||
&:active:before {
|
||||
background-color: ${({ theme }) => theme.stateOverlayPressed};
|
||||
}
|
||||
|
||||
visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
|
||||
`
|
||||
const InputCurrencySelect = styled(CurrencySelect)<{ redesignFlag: boolean }>`
|
||||
background-color: ${({ theme, selected, redesignFlag }) =>
|
||||
redesignFlag && (selected ? theme.backgroundModule : theme.accentAction)};
|
||||
:focus,
|
||||
:hover {
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
selected
|
||||
? redesignFlag
|
||||
? theme.backgroundInteractive
|
||||
: theme.deprecated_bg3
|
||||
: darken(0.05, theme.deprecated_primary1)};
|
||||
}
|
||||
`
|
||||
|
||||
const InputRow = styled.div<{ selected: boolean; redesignFlag: boolean }>`
|
||||
const InputRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: ${({ selected, redesignFlag }) =>
|
||||
redesignFlag ? '0px' : selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 1rem 1rem'};
|
||||
`
|
||||
|
||||
const LabelRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
padding: 0 1rem 1rem;
|
||||
|
||||
span:hover {
|
||||
cursor: pointer;
|
||||
@@ -147,23 +124,10 @@ const LabelRow = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const FiatRow = styled(LabelRow)<{ redesignFlag: boolean }>`
|
||||
const FiatRow = styled(LabelRow)`
|
||||
justify-content: flex-end;
|
||||
min-height: ${({ redesignFlag }) => redesignFlag && '32px'};
|
||||
padding: ${({ redesignFlag }) => redesignFlag && '8px 0px'};
|
||||
height: ${({ redesignFlag }) => !redesignFlag && '24px'};
|
||||
`
|
||||
|
||||
const NoBalanceState = styled.div`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
font-weight: 400;
|
||||
justify-content: space-between;
|
||||
padding: 0px 4px 1px 4px;
|
||||
`
|
||||
const NoBalanceDash = styled.span`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
font-variant: small-caps;
|
||||
font-feature-settings: 'pnum' on, 'lnum' on;
|
||||
min-height: 20px;
|
||||
padding: 8px 0px 0px 0px;
|
||||
`
|
||||
|
||||
const Aligner = styled.span`
|
||||
@@ -173,34 +137,30 @@ const Aligner = styled.span`
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledDropDown = styled(DropDown)<{ selected: boolean; redesignFlag: boolean }>`
|
||||
const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
|
||||
margin: 0 0.25rem 0 0.35rem;
|
||||
height: 35%;
|
||||
margin-left: ${({ redesignFlag }) => redesignFlag && '8px'};
|
||||
margin-left: 8px;
|
||||
|
||||
path {
|
||||
stroke: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
stroke-width: ${({ redesignFlag }) => (redesignFlag ? '2px' : '1.5px')};
|
||||
stroke-width: 2px;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledTokenName = styled.span<{ active?: boolean; redesignFlag: boolean }>`
|
||||
const StyledTokenName = styled.span<{ active?: boolean }>`
|
||||
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
|
||||
font-size: ${({ active }) => (active ? '18px' : '18px')};
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
`
|
||||
|
||||
const StyledBalanceMax = styled.button<{ disabled?: boolean; redesignFlag: boolean }>`
|
||||
const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
|
||||
background-color: transparent;
|
||||
background-color: ${({ theme, redesignFlag }) => !redesignFlag && theme.deprecated_primary5};
|
||||
border: none;
|
||||
text-transform: ${({ redesignFlag }) => !redesignFlag && 'uppercase'};
|
||||
border-radius: ${({ redesignFlag }) => !redesignFlag && '12px'};
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentAction : theme.deprecated_primary1)};
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
cursor: pointer;
|
||||
font-size: ${({ redesignFlag }) => (redesignFlag ? '14px' : '11px')};
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
|
||||
margin-left: ${({ redesignFlag }) => (redesignFlag ? '0px' : '0.25rem')};
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
padding: 4px 6px;
|
||||
pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
|
||||
@@ -214,11 +174,12 @@ const StyledBalanceMax = styled.button<{ disabled?: boolean; redesignFlag: boole
|
||||
}
|
||||
`
|
||||
|
||||
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean; redesignFlag: boolean }>`
|
||||
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean }>`
|
||||
${loadingOpacityMixin};
|
||||
text-align: left;
|
||||
font-variant: ${({ redesignFlag }) => redesignFlag && 'small-caps'};
|
||||
font-feature-settings: ${({ redesignFlag }) => redesignFlag && 'pnum on, lnum on'};
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
font-variant: small-caps;
|
||||
`
|
||||
|
||||
interface SwapCurrencyInputPanelProps {
|
||||
@@ -268,12 +229,8 @@ export default function SwapCurrencyInputPanel({
|
||||
}: SwapCurrencyInputPanelProps) {
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const { account, chainId } = useWeb3React()
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
|
||||
const theme = useTheme()
|
||||
const { pathname } = useLocation()
|
||||
const isAddLiquidityPage = pathname.includes('/add') && !pathname.includes('/add/v2')
|
||||
|
||||
const handleDismissSearch = useCallback(() => {
|
||||
setModalOpen(false)
|
||||
@@ -282,9 +239,9 @@ export default function SwapCurrencyInputPanel({
|
||||
const chainAllowed = isSupportedChain(chainId)
|
||||
|
||||
return (
|
||||
<InputPanel id={id} hideInput={hideInput} {...rest} redesignFlag={redesignFlagEnabled}>
|
||||
<InputPanel id={id} hideInput={hideInput} {...rest}>
|
||||
{locked && (
|
||||
<FixedContainer redesignFlag={redesignFlagEnabled}>
|
||||
<FixedContainer>
|
||||
<AutoColumn gap="sm" justify="center">
|
||||
<Lock />
|
||||
<ThemedText.DeprecatedLabel fontSize="12px" textAlign="center" padding="0 12px">
|
||||
@@ -293,12 +250,8 @@ export default function SwapCurrencyInputPanel({
|
||||
</AutoColumn>
|
||||
</FixedContainer>
|
||||
)}
|
||||
<Container hideInput={hideInput} disabled={!chainAllowed} redesignFlag={redesignFlagEnabled}>
|
||||
<InputRow
|
||||
style={hideInput ? { padding: '0', borderRadius: '8px' } : {}}
|
||||
selected={!onCurrencySelect}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
>
|
||||
<Container hideInput={hideInput}>
|
||||
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}}>
|
||||
{!hideInput && (
|
||||
<StyledNumericalInput
|
||||
className="token-amount-input"
|
||||
@@ -306,16 +259,14 @@ export default function SwapCurrencyInputPanel({
|
||||
onUserInput={onUserInput}
|
||||
disabled={!chainAllowed}
|
||||
$loading={loading}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
/>
|
||||
)}
|
||||
|
||||
<InputCurrencySelect
|
||||
<CurrencySelect
|
||||
disabled={!chainAllowed}
|
||||
visible={currency !== undefined}
|
||||
selected={!!currency}
|
||||
hideInput={hideInput}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
className="open-currency-select-button"
|
||||
onClick={() => {
|
||||
if (onCurrencySelect) {
|
||||
@@ -333,15 +284,11 @@ export default function SwapCurrencyInputPanel({
|
||||
<CurrencyLogo style={{ marginRight: '2px' }} currency={currency} size={'24px'} />
|
||||
) : null}
|
||||
{pair ? (
|
||||
<StyledTokenName className="pair-name-container" redesignFlag={redesignFlagEnabled}>
|
||||
<StyledTokenName className="pair-name-container">
|
||||
{pair?.token0.symbol}:{pair?.token1.symbol}
|
||||
</StyledTokenName>
|
||||
) : (
|
||||
<StyledTokenName
|
||||
className="token-symbol-container"
|
||||
active={Boolean(currency && currency.symbol)}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
>
|
||||
<StyledTokenName className="token-symbol-container" active={Boolean(currency && currency.symbol)}>
|
||||
{(currency && currency.symbol && currency.symbol.length > 20
|
||||
? currency.symbol.slice(0, 4) +
|
||||
'...' +
|
||||
@@ -350,22 +297,12 @@ export default function SwapCurrencyInputPanel({
|
||||
</StyledTokenName>
|
||||
)}
|
||||
</RowFixed>
|
||||
{onCurrencySelect && <StyledDropDown selected={!!currency} redesignFlag={redesignFlagEnabled} />}
|
||||
{onCurrencySelect && <StyledDropDown selected={!!currency} />}
|
||||
</Aligner>
|
||||
</InputCurrencySelect>
|
||||
</CurrencySelect>
|
||||
</InputRow>
|
||||
{redesignFlagEnabled && !currency && !isAddLiquidityPage && (
|
||||
<NoBalanceState>
|
||||
<FiatRow redesignFlag={redesignFlagEnabled}>
|
||||
<RowBetween>
|
||||
<NoBalanceDash>-</NoBalanceDash>
|
||||
<NoBalanceDash>-</NoBalanceDash>
|
||||
</RowBetween>
|
||||
</FiatRow>
|
||||
</NoBalanceState>
|
||||
)}
|
||||
{!hideInput && !hideBalance && currency && (
|
||||
<FiatRow redesignFlag={redesignFlagEnabled}>
|
||||
<FiatRow>
|
||||
<RowBetween>
|
||||
<LoadingOpacityContainer $loading={loading}>
|
||||
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
|
||||
@@ -373,11 +310,10 @@ export default function SwapCurrencyInputPanel({
|
||||
{account ? (
|
||||
<RowFixed style={{ height: '17px' }}>
|
||||
<ThemedText.DeprecatedBody
|
||||
onClick={onMax}
|
||||
color={theme.deprecated_text3}
|
||||
fontWeight={500}
|
||||
color={theme.textSecondary}
|
||||
fontWeight={400}
|
||||
fontSize={14}
|
||||
style={{ display: 'inline', cursor: 'pointer' }}
|
||||
style={{ display: 'inline' }}
|
||||
>
|
||||
{!hideBalance && currency && selectedCurrencyBalance ? (
|
||||
renderBalance ? (
|
||||
@@ -393,7 +329,7 @@ export default function SwapCurrencyInputPanel({
|
||||
name={EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED}
|
||||
element={ElementName.MAX_TOKEN_AMOUNT_BUTTON}
|
||||
>
|
||||
<StyledBalanceMax onClick={onMax} redesignFlag={redesignFlagEnabled}>
|
||||
<StyledBalanceMax onClick={onMax}>
|
||||
<Trans>Max</Trans>
|
||||
</StyledBalanceMax>
|
||||
</TraceEvent>
|
||||
|
||||
@@ -2,12 +2,11 @@ import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import { ElementName, Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Lock } from 'react-feather'
|
||||
@@ -115,10 +114,10 @@ const LabelRow = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const FiatRow = styled(LabelRow)<{ redesignFlag: boolean }>`
|
||||
const FiatRow = styled(LabelRow)`
|
||||
justify-content: flex-end;
|
||||
padding: ${({ redesignFlag }) => redesignFlag && '0px 1rem 0.75rem'};
|
||||
height: ${({ redesignFlag }) => (redesignFlag ? '32px' : '16px')};
|
||||
padding: 0px 1rem 0.75rem;
|
||||
height: 32px;
|
||||
`
|
||||
|
||||
const Aligner = styled.span`
|
||||
@@ -140,7 +139,7 @@ const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
|
||||
|
||||
const StyledTokenName = styled.span<{ active?: boolean }>`
|
||||
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
|
||||
font-size: ${({ active }) => (active ? '18px' : '18px')};
|
||||
font-size: 20px;
|
||||
`
|
||||
|
||||
const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
|
||||
@@ -220,8 +219,6 @@ export default function CurrencyInputPanel({
|
||||
const { account, chainId } = useWeb3React()
|
||||
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
|
||||
const theme = useTheme()
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
|
||||
const handleDismissSearch = useCallback(() => {
|
||||
setModalOpen(false)
|
||||
@@ -293,7 +290,7 @@ export default function CurrencyInputPanel({
|
||||
</CurrencySelect>
|
||||
</InputRow>
|
||||
{!hideInput && !hideBalance && currency && (
|
||||
<FiatRow redesignFlag={redesignFlagEnabled}>
|
||||
<FiatRow>
|
||||
<RowBetween>
|
||||
<LoadingOpacityContainer $loading={loading}>
|
||||
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
|
||||
import React from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import Logo from '../Logo'
|
||||
@@ -27,17 +27,21 @@ export default function CurrencyLogo({
|
||||
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: useCurrencyLogoURIs(currency),
|
||||
srcs,
|
||||
symbol: symbol ?? currency?.symbol,
|
||||
style,
|
||||
...rest,
|
||||
|
||||
@@ -49,7 +49,7 @@ export default function DowntimeWarning() {
|
||||
|
||||
switch (chainId) {
|
||||
case SupportedChainId.OPTIMISM:
|
||||
case SupportedChainId.OPTIMISTIC_KOVAN:
|
||||
case SupportedChainId.OPTIMISM_GOERLI:
|
||||
return (
|
||||
<Wrapper>
|
||||
<Trans>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { TokensVariant, useTokensFlag } from 'featureFlags/flags/tokens'
|
||||
import { TokenSafetyVariant, useTokenSafetyFlag } from 'featureFlags/flags/tokenSafety'
|
||||
import { TokensNetworkFilterVariant, useTokensNetworkFilterFlag } from 'featureFlags/flags/tokensNetworkFilter'
|
||||
import { NftGraphQlVariant, useNftGraphQlFlag } from 'featureFlags/flags/nftGraphQl'
|
||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
@@ -206,40 +203,22 @@ export default function FeatureFlagModal() {
|
||||
<X size={24} />
|
||||
</CloseButton>
|
||||
</Header>
|
||||
<FeatureFlagGroup name="Phase 0">
|
||||
<FeatureFlagOption
|
||||
variant={RedesignVariant}
|
||||
value={useRedesignFlag()}
|
||||
featureFlag={FeatureFlag.redesign}
|
||||
label="Redesign"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={NavBarVariant}
|
||||
value={useNavBarFlag()}
|
||||
featureFlag={FeatureFlag.navBar}
|
||||
label="NavBar"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={TokensVariant}
|
||||
value={useTokensFlag()}
|
||||
featureFlag={FeatureFlag.tokens}
|
||||
label="Tokens"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={TokensNetworkFilterVariant}
|
||||
value={useTokensNetworkFilterFlag()}
|
||||
featureFlag={FeatureFlag.tokensNetworkFilter}
|
||||
label="Tokens Network Filter"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={TokenSafetyVariant}
|
||||
value={useTokenSafetyFlag()}
|
||||
featureFlag={FeatureFlag.tokenSafety}
|
||||
label="Token Safety"
|
||||
/>
|
||||
</FeatureFlagGroup>
|
||||
<FeatureFlagGroup name="Phase 1">
|
||||
<FeatureFlagOption variant={NftVariant} value={useNftFlag()} featureFlag={FeatureFlag.nft} label="NFTs" />
|
||||
<FeatureFlagOption
|
||||
variant={NftGraphQlVariant}
|
||||
value={useNftGraphQlFlag()}
|
||||
featureFlag={FeatureFlag.nftGraphQl}
|
||||
label="NFT GraphQL Endpoints"
|
||||
/>
|
||||
</FeatureFlagGroup>
|
||||
<FeatureFlagGroup name="Debug">
|
||||
<FeatureFlagOption
|
||||
variant={TraceJsonRpcVariant}
|
||||
value={useTraceJsonRpcFlag()}
|
||||
featureFlag={FeatureFlag.traceJsonRpc}
|
||||
label="Enables JSON-RPC tracing"
|
||||
/>
|
||||
</FeatureFlagGroup>
|
||||
<SaveButton onClick={() => window.location.reload()}>Reload</SaveButton>
|
||||
</Modal>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { ReactElement } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import SantaHat from '../../assets/images/santa-hat.png'
|
||||
|
||||
const SantaHatImage = styled.img`
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
height: 18px;
|
||||
`
|
||||
|
||||
const Christmas = <SantaHatImage src={SantaHat} alt="Santa hat" />
|
||||
|
||||
const DATE_TO_ORNAMENT: { [date: string]: ReactElement } = {
|
||||
'12-24': Christmas,
|
||||
'12-25': Christmas,
|
||||
}
|
||||
|
||||
const HolidayOrnament = () => {
|
||||
// months in javascript are 0 indexed...
|
||||
const today = `${new Date().getMonth() + 1}-${new Date().getDate()}`
|
||||
return DATE_TO_ORNAMENT[today] || null
|
||||
}
|
||||
|
||||
export default HolidayOrnament
|
||||
@@ -1,345 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useSelectChain from 'hooks/useSelectChain'
|
||||
import useSyncChainQuery from 'hooks/useSyncChainQuery'
|
||||
import { darken } from 'polished'
|
||||
import { useRef } from 'react'
|
||||
import { AlertTriangle, ArrowDownCircle, ChevronDown } from 'react-feather'
|
||||
import { useCloseModal, useModalIsOpen, useOpenModal, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
|
||||
const ActiveRowLinkList = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 8px;
|
||||
& > a {
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.deprecated_text2};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
& > a:first-child {
|
||||
margin: 0;
|
||||
margin-top: 0px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
`
|
||||
const ActiveRowWrapper = styled.div`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
`
|
||||
const FlyoutHeader = styled.div`
|
||||
color: ${({ theme }) => theme.deprecated_text2};
|
||||
cursor: default;
|
||||
font-weight: 400;
|
||||
`
|
||||
const FlyoutMenu = styled.div`
|
||||
position: absolute;
|
||||
top: 54px;
|
||||
width: 272px;
|
||||
z-index: 99;
|
||||
padding-top: 10px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
top: 40px;
|
||||
}
|
||||
`
|
||||
const FlyoutMenuContents = styled.div`
|
||||
align-items: flex-start;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg0};
|
||||
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: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 16px;
|
||||
overflow: auto;
|
||||
padding: 16px;
|
||||
& > *:not(:last-child) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
`
|
||||
const FlyoutRow = styled.div<{ active: boolean }>`
|
||||
align-items: center;
|
||||
background-color: ${({ active, theme }) => (active ? theme.deprecated_bg1 : 'transparent')};
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-weight: 500;
|
||||
justify-content: space-between;
|
||||
padding: 6px 8px;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
`
|
||||
const FlyoutRowActiveIndicator = styled.div`
|
||||
background-color: ${({ theme }) => theme.deprecated_green1};
|
||||
border-radius: 50%;
|
||||
height: 9px;
|
||||
width: 9px;
|
||||
`
|
||||
|
||||
const CircleContainer = styled.div`
|
||||
width: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
`
|
||||
const Logo = styled.img`
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-right: 8px;
|
||||
`
|
||||
const NetworkLabel = styled.div`
|
||||
flex: 1 1 auto;
|
||||
`
|
||||
const SelectorLabel = styled(NetworkLabel)`
|
||||
display: none;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
display: block;
|
||||
margin-right: 8px;
|
||||
}
|
||||
`
|
||||
const NetworkAlertLabel = styled(NetworkLabel)`
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin: 0 0.5rem 0 0.4rem;
|
||||
font-size: 1rem;
|
||||
width: fit-content;
|
||||
font-weight: 500;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
const SelectorControls = styled.div<{ supportedChain: boolean }>`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg0};
|
||||
border: 2px solid ${({ theme }) => theme.deprecated_bg0};
|
||||
border-radius: 16px;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
display: flex;
|
||||
font-weight: 500;
|
||||
justify-content: space-between;
|
||||
padding: 6px 8px;
|
||||
${({ supportedChain, theme }) =>
|
||||
!supportedChain &&
|
||||
`
|
||||
color: ${theme.deprecated_white};
|
||||
background-color: ${theme.deprecated_red1};
|
||||
border: 2px solid ${theme.deprecated_red1};
|
||||
`}
|
||||
cursor: default;
|
||||
:focus {
|
||||
background-color: ${({ theme }) => darken(0.1, theme.deprecated_red1)};
|
||||
}
|
||||
`
|
||||
const SelectorLogo = styled(Logo)`
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
`
|
||||
const SelectorWrapper = styled.div`
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
position: relative;
|
||||
}
|
||||
`
|
||||
const StyledChevronDown = styled(ChevronDown)`
|
||||
width: 16px;
|
||||
`
|
||||
|
||||
const NetworkIcon = styled(AlertTriangle)`
|
||||
margin-left: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
`
|
||||
|
||||
const BridgeLabel = ({ chainId }: { chainId: SupportedChainId }) => {
|
||||
switch (chainId) {
|
||||
case SupportedChainId.ARBITRUM_ONE:
|
||||
case SupportedChainId.ARBITRUM_RINKEBY:
|
||||
return <Trans>Arbitrum Bridge</Trans>
|
||||
case SupportedChainId.OPTIMISM:
|
||||
case SupportedChainId.OPTIMISTIC_KOVAN:
|
||||
return <Trans>Optimism Bridge</Trans>
|
||||
case SupportedChainId.POLYGON:
|
||||
case SupportedChainId.POLYGON_MUMBAI:
|
||||
return <Trans>Polygon Bridge</Trans>
|
||||
case SupportedChainId.CELO:
|
||||
case SupportedChainId.CELO_ALFAJORES:
|
||||
return <Trans>Portal Bridge</Trans>
|
||||
default:
|
||||
return <Trans>Bridge</Trans>
|
||||
}
|
||||
}
|
||||
const ExplorerLabel = ({ chainId }: { chainId: SupportedChainId }) => {
|
||||
switch (chainId) {
|
||||
case SupportedChainId.ARBITRUM_ONE:
|
||||
case SupportedChainId.ARBITRUM_RINKEBY:
|
||||
return <Trans>Arbiscan</Trans>
|
||||
case SupportedChainId.OPTIMISM:
|
||||
case SupportedChainId.OPTIMISTIC_KOVAN:
|
||||
return <Trans>Optimistic Etherscan</Trans>
|
||||
case SupportedChainId.POLYGON:
|
||||
case SupportedChainId.POLYGON_MUMBAI:
|
||||
return <Trans>Polygonscan</Trans>
|
||||
case SupportedChainId.CELO:
|
||||
case SupportedChainId.CELO_ALFAJORES:
|
||||
return <Trans>Blockscout</Trans>
|
||||
default:
|
||||
return <Trans>Etherscan</Trans>
|
||||
}
|
||||
}
|
||||
|
||||
function Row({
|
||||
targetChain,
|
||||
onSelectChain,
|
||||
}: {
|
||||
targetChain: SupportedChainId
|
||||
onSelectChain: (targetChain: number) => void
|
||||
}) {
|
||||
const { provider, chainId } = useWeb3React()
|
||||
if (!provider || !chainId) {
|
||||
return null
|
||||
}
|
||||
const active = chainId === targetChain
|
||||
const { helpCenterUrl, explorer, bridge, label, logoUrl } = getChainInfo(targetChain)
|
||||
|
||||
const rowContent = (
|
||||
<FlyoutRow onClick={() => onSelectChain(targetChain)} active={active}>
|
||||
<Logo src={logoUrl} />
|
||||
<NetworkLabel>{label}</NetworkLabel>
|
||||
{chainId === targetChain && (
|
||||
<CircleContainer>
|
||||
<FlyoutRowActiveIndicator />
|
||||
</CircleContainer>
|
||||
)}
|
||||
</FlyoutRow>
|
||||
)
|
||||
|
||||
if (active) {
|
||||
return (
|
||||
<ActiveRowWrapper>
|
||||
{rowContent}
|
||||
<ActiveRowLinkList>
|
||||
{bridge && (
|
||||
<ExternalLink href={bridge}>
|
||||
<BridgeLabel chainId={chainId} />
|
||||
<CircleContainer>
|
||||
<LinkOutCircle />
|
||||
</CircleContainer>
|
||||
</ExternalLink>
|
||||
)}
|
||||
{explorer && (
|
||||
<ExternalLink href={explorer}>
|
||||
<ExplorerLabel chainId={chainId} />
|
||||
<CircleContainer>
|
||||
<LinkOutCircle />
|
||||
</CircleContainer>
|
||||
</ExternalLink>
|
||||
)}
|
||||
{helpCenterUrl && (
|
||||
<ExternalLink href={helpCenterUrl}>
|
||||
<Trans>Help Center</Trans>
|
||||
<CircleContainer>
|
||||
<LinkOutCircle />
|
||||
</CircleContainer>
|
||||
</ExternalLink>
|
||||
)}
|
||||
</ActiveRowLinkList>
|
||||
</ActiveRowWrapper>
|
||||
)
|
||||
}
|
||||
return rowContent
|
||||
}
|
||||
|
||||
const NETWORK_SELECTOR_CHAINS = [
|
||||
SupportedChainId.MAINNET,
|
||||
SupportedChainId.POLYGON,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.CELO,
|
||||
]
|
||||
|
||||
export default function NetworkSelector() {
|
||||
const { chainId, provider } = useWeb3React()
|
||||
|
||||
const node = useRef<HTMLDivElement>(null)
|
||||
const isOpen = useModalIsOpen(ApplicationModal.NETWORK_SELECTOR)
|
||||
const openModal = useOpenModal(ApplicationModal.NETWORK_SELECTOR)
|
||||
const closeModal = useCloseModal(ApplicationModal.NETWORK_SELECTOR)
|
||||
const toggleModal = useToggleModal(ApplicationModal.NETWORK_SELECTOR)
|
||||
|
||||
const info = getChainInfo(chainId)
|
||||
|
||||
const selectChain = useSelectChain()
|
||||
useSyncChainQuery()
|
||||
|
||||
if (!chainId || !provider) {
|
||||
return null
|
||||
}
|
||||
|
||||
const onSupportedChain = info !== undefined
|
||||
|
||||
return (
|
||||
<SelectorWrapper
|
||||
ref={node}
|
||||
onMouseEnter={openModal}
|
||||
onMouseLeave={closeModal}
|
||||
onClick={isMobile ? toggleModal : undefined}
|
||||
>
|
||||
<SelectorControls supportedChain={onSupportedChain}>
|
||||
{onSupportedChain ? (
|
||||
<>
|
||||
<SelectorLogo src={info.logoUrl} />
|
||||
<SelectorLabel>{info.label}</SelectorLabel>
|
||||
<StyledChevronDown />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<NetworkIcon />
|
||||
<NetworkAlertLabel>Switch Network</NetworkAlertLabel>
|
||||
<StyledChevronDown />
|
||||
</>
|
||||
)}
|
||||
</SelectorControls>
|
||||
{isOpen && (
|
||||
<FlyoutMenu>
|
||||
<FlyoutMenuContents>
|
||||
<FlyoutHeader>
|
||||
<Trans>Select a {!onSupportedChain ? ' supported ' : ''}network</Trans>
|
||||
</FlyoutHeader>
|
||||
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) => (
|
||||
<Row
|
||||
onSelectChain={async (targetChainId: SupportedChainId) => {
|
||||
await selectChain(targetChainId)
|
||||
closeModal()
|
||||
}}
|
||||
targetChain={chainId}
|
||||
key={chainId}
|
||||
/>
|
||||
))}
|
||||
</FlyoutMenuContents>
|
||||
</FlyoutMenu>
|
||||
)}
|
||||
</SelectorWrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useScrollPosition from '@react-hook/window-scroll'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfoOrDefault } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { TokensVariant, useTokensFlag } from 'featureFlags/flags/tokens'
|
||||
import { darken } from 'polished'
|
||||
import { NavLink, useLocation } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import { useShowClaimPopup, useToggleSelfClaimModal } from 'state/application/hooks'
|
||||
import { useUserHasAvailableClaim } from 'state/claim/hooks'
|
||||
import { useNativeCurrencyBalances } from 'state/connection/hooks'
|
||||
import { useUserHasSubmittedClaim } from 'state/transactions/hooks'
|
||||
import { useDarkModeManager } from 'state/user/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { ReactComponent as Logo } from '../../assets/svg/logo.svg'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
import ClaimModal from '../claim/ClaimModal'
|
||||
import { CardNoise } from '../earn/styled'
|
||||
import Menu from '../Menu'
|
||||
import Row from '../Row'
|
||||
import { Dots } from '../swap/styleds'
|
||||
import Web3Status from '../Web3Status'
|
||||
import HolidayOrnament from './HolidayOrnament'
|
||||
import NetworkSelector from './NetworkSelector'
|
||||
|
||||
const HeaderFrame = styled.div<{ showBackground: boolean }>`
|
||||
display: grid;
|
||||
grid-template-columns: 120px 1fr 120px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
z-index: 21;
|
||||
position: relative;
|
||||
/* Background slide effect on scroll. */
|
||||
background-image: ${({ theme }) => `linear-gradient(to bottom, transparent 50%, ${theme.deprecated_bg0} 50% )}}`};
|
||||
background-position: ${({ showBackground }) => (showBackground ? '0 -100%' : '0 0')};
|
||||
background-size: 100% 200%;
|
||||
box-shadow: 0px 0px 0px 1px ${({ theme, showBackground }) => (showBackground ? theme.deprecated_bg2 : 'transparent;')};
|
||||
transition: background-position 0.1s, box-shadow 0.1s;
|
||||
background-blend-mode: hard-light;
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToLarge`
|
||||
grid-template-columns: 48px 1fr 1fr;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
padding: 1rem;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
padding: 1rem;
|
||||
grid-template-columns: 36px 1fr;
|
||||
`};
|
||||
`
|
||||
|
||||
const HeaderControls = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-self: flex-end;
|
||||
`
|
||||
|
||||
const HeaderElement = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
/* addresses safaris lack of support for "gap" */
|
||||
& > *:not(:first-child) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
align-items: center;
|
||||
`};
|
||||
`
|
||||
|
||||
const HeaderLinks = styled(Row)`
|
||||
justify-self: center;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg0};
|
||||
width: max-content;
|
||||
padding: 2px;
|
||||
border-radius: 16px;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-gap: 10px;
|
||||
overflow: auto;
|
||||
align-items: center;
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToLarge`
|
||||
justify-self: start;
|
||||
`};
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
justify-self: center;
|
||||
`};
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
justify-self: center;
|
||||
z-index: 99;
|
||||
position: fixed;
|
||||
bottom: 0; right: 50%;
|
||||
transform: translate(50%,-50%);
|
||||
margin: 0 auto;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg0};
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg2};
|
||||
box-shadow: 0px 6px 10px rgb(0 0 0 / 2%);
|
||||
`};
|
||||
`
|
||||
|
||||
const AccountElement = styled.div<{ active: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background-color: ${({ theme, active }) => (!active ? theme.deprecated_bg0 : theme.deprecated_bg0)};
|
||||
border-radius: 16px;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
|
||||
:focus {
|
||||
border: 1px solid blue;
|
||||
}
|
||||
`
|
||||
|
||||
const UNIAmount = styled(AccountElement)`
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
height: 36px;
|
||||
font-weight: 500;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg3};
|
||||
background: radial-gradient(174.47% 188.91% at 1.84% 0%, #ff007a 0%, #2172e5 100%), #edeef2;
|
||||
`
|
||||
|
||||
const UNIWrapper = styled.span`
|
||||
width: fit-content;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
:active {
|
||||
opacity: 0.9;
|
||||
}
|
||||
`
|
||||
|
||||
const BalanceText = styled(Text)`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToExtraSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
const Title = styled.a`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
pointer-events: auto;
|
||||
justify-self: flex-start;
|
||||
margin-right: 12px;
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
justify-self: center;
|
||||
`};
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
const UniIcon = styled.div`
|
||||
transition: transform 0.3s ease;
|
||||
:hover {
|
||||
transform: rotate(-5deg);
|
||||
}
|
||||
|
||||
position: relative;
|
||||
`
|
||||
|
||||
// can't be customized under react-router-dom v6
|
||||
// so we have to persist to the default one, i.e., .active
|
||||
const activeClassName = 'active'
|
||||
|
||||
const StyledNavLink = styled(NavLink)`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: left;
|
||||
border-radius: 3rem;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: ${({ theme }) => theme.deprecated_text2};
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
padding: 8px 12px;
|
||||
word-break: break-word;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
&.${activeClassName} {
|
||||
border-radius: 14px;
|
||||
font-weight: 600;
|
||||
justify-content: center;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
}
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
color: ${({ theme }) => darken(0.1, theme.deprecated_text1)};
|
||||
}
|
||||
`
|
||||
|
||||
const StyledExternalLink = styled(ExternalLink)`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: left;
|
||||
border-radius: 3rem;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: ${({ theme }) => theme.deprecated_text2};
|
||||
font-size: 1rem;
|
||||
width: fit-content;
|
||||
margin: 0 12px;
|
||||
font-weight: 500;
|
||||
|
||||
&.${activeClassName} {
|
||||
border-radius: 14px;
|
||||
font-weight: 600;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
}
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
color: ${({ theme }) => darken(0.1, theme.deprecated_text1)};
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
|
||||
export default function Header() {
|
||||
const tokensFlag = useTokensFlag()
|
||||
|
||||
const { account, chainId } = useWeb3React()
|
||||
|
||||
const userEthBalance = useNativeCurrencyBalances(account ? [account] : [])?.[account ?? '']
|
||||
const [darkMode] = useDarkModeManager()
|
||||
const { deprecated_white, deprecated_black } = useTheme()
|
||||
|
||||
const toggleClaimModal = useToggleSelfClaimModal()
|
||||
|
||||
const availableClaim: boolean = useUserHasAvailableClaim(account)
|
||||
|
||||
const { claimTxn } = useUserHasSubmittedClaim(account ?? undefined)
|
||||
|
||||
const showClaimPopup = useShowClaimPopup()
|
||||
|
||||
const scrollY = useScrollPosition()
|
||||
|
||||
const { pathname } = useLocation()
|
||||
|
||||
const {
|
||||
infoLink,
|
||||
nativeCurrency: { symbol: nativeCurrencySymbol },
|
||||
} = getChainInfoOrDefault(chainId)
|
||||
|
||||
// work around https://github.com/remix-run/react-router/issues/8161
|
||||
// as we can't pass function `({isActive}) => ''` to className with styled-components
|
||||
const isPoolActive =
|
||||
pathname.startsWith('/pool') ||
|
||||
pathname.startsWith('/add') ||
|
||||
pathname.startsWith('/remove') ||
|
||||
pathname.startsWith('/increase') ||
|
||||
pathname.startsWith('/find')
|
||||
|
||||
return (
|
||||
<HeaderFrame showBackground={scrollY > 45}>
|
||||
<ClaimModal />
|
||||
<Title href=".">
|
||||
<UniIcon>
|
||||
<Logo fill={darkMode ? deprecated_white : deprecated_black} width="24px" height="100%" title="logo" />
|
||||
<HolidayOrnament />
|
||||
</UniIcon>
|
||||
</Title>
|
||||
<HeaderLinks>
|
||||
<StyledNavLink id={`swap-nav-link`} to={'/swap'}>
|
||||
<Trans>Swap</Trans>
|
||||
</StyledNavLink>
|
||||
{tokensFlag === TokensVariant.Enabled && (
|
||||
<StyledNavLink id={`tokens-nav-link`} to={'/tokens'}>
|
||||
<Trans>Tokens</Trans>
|
||||
</StyledNavLink>
|
||||
)}
|
||||
<StyledNavLink
|
||||
data-cy="pool-nav-link"
|
||||
id={`pool-nav-link`}
|
||||
to={'/pool'}
|
||||
className={isPoolActive ? activeClassName : undefined}
|
||||
>
|
||||
<Trans>Pool</Trans>
|
||||
</StyledNavLink>
|
||||
{(!chainId || chainId === SupportedChainId.MAINNET) && (
|
||||
<StyledNavLink id={`vote-nav-link`} to={'/vote'}>
|
||||
<Trans>Vote</Trans>
|
||||
</StyledNavLink>
|
||||
)}
|
||||
<StyledExternalLink id={`charts-nav-link`} href={infoLink}>
|
||||
<Trans>Charts</Trans>
|
||||
<sup>↗</sup>
|
||||
</StyledExternalLink>
|
||||
</HeaderLinks>
|
||||
|
||||
<HeaderControls>
|
||||
<HeaderElement>
|
||||
<NetworkSelector />
|
||||
</HeaderElement>
|
||||
<HeaderElement>
|
||||
{availableClaim && !showClaimPopup && (
|
||||
<UNIWrapper onClick={toggleClaimModal}>
|
||||
<UNIAmount active={!!account && !availableClaim} style={{ pointerEvents: 'auto' }}>
|
||||
<ThemedText.DeprecatedWhite padding="0 2px">
|
||||
{claimTxn && !claimTxn?.receipt ? (
|
||||
<Dots>
|
||||
<Trans>Claiming UNI</Trans>
|
||||
</Dots>
|
||||
) : (
|
||||
<Trans>Claim UNI</Trans>
|
||||
)}
|
||||
</ThemedText.DeprecatedWhite>
|
||||
</UNIAmount>
|
||||
<CardNoise />
|
||||
</UNIWrapper>
|
||||
)}
|
||||
<AccountElement active={!!account}>
|
||||
{account && userEthBalance ? (
|
||||
<BalanceText style={{ flexShrink: 0, userSelect: 'none' }} pl="0.75rem" pr=".4rem" fontWeight={500}>
|
||||
<Trans>
|
||||
{userEthBalance?.toSignificant(3)} {nativeCurrencySymbol}
|
||||
</Trans>
|
||||
</BalanceText>
|
||||
) : null}
|
||||
<Web3Status />
|
||||
</AccountElement>
|
||||
</HeaderElement>
|
||||
<HeaderElement>
|
||||
<Menu />
|
||||
</HeaderElement>
|
||||
</HeaderControls>
|
||||
</HeaderFrame>
|
||||
)
|
||||
}
|
||||
@@ -10,7 +10,6 @@ const TextWrapper = styled.span<{
|
||||
textColor?: string
|
||||
}>`
|
||||
margin-left: ${({ margin }) => margin && '4px'};
|
||||
color: ${({ theme, link, textColor }) => (link ? theme.deprecated_blue1 : textColor ?? theme.deprecated_text1)};
|
||||
font-size: ${({ fontSize }) => fontSize ?? 'inherit'};
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import { ChevronDown, ChevronUp } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
export const StyledChevronDown = styled(ChevronDown)<{ customColor?: string }>`
|
||||
color: ${({ theme, customColor }) => customColor ?? theme.textSecondary};
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.accentActionSoft};
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.fast}ms color ${timing.in}`};
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledChevronUp = styled(ChevronUp)<{ customColor?: string }>`
|
||||
color: ${({ theme, customColor }) => customColor ?? theme.textSecondary};
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.accentActionSoft};
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.fast}ms color ${timing.in}`};
|
||||
}
|
||||
`
|
||||
@@ -1,9 +1,7 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ConnectionType } from 'connection'
|
||||
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import styled from 'styled-components/macro'
|
||||
import { colors } from 'theme/colors'
|
||||
|
||||
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
|
||||
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
|
||||
@@ -29,20 +27,18 @@ const IconWrapper = styled.div<{ size?: number }>`
|
||||
|
||||
const SockContainer = styled.div`
|
||||
position: absolute;
|
||||
background-color: ${colors.pink400};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
bottom: -5px;
|
||||
right: -5px;
|
||||
bottom: -4px;
|
||||
right: -4px;
|
||||
`
|
||||
|
||||
const SockImg = styled.img`
|
||||
width: 7.5px;
|
||||
height: 10px;
|
||||
margin-top: 3px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
`
|
||||
|
||||
const Socks = () => {
|
||||
@@ -56,9 +52,8 @@ const Socks = () => {
|
||||
const useIcon = (connectionType: ConnectionType) => {
|
||||
const { account } = useWeb3React()
|
||||
const { avatar } = useENSAvatar(account ?? undefined)
|
||||
const isNavbarEnabled = useNavBarFlag() === NavBarVariant.Enabled
|
||||
|
||||
if ((isNavbarEnabled && avatar) || connectionType === ConnectionType.INJECTED) {
|
||||
if (avatar || connectionType === ConnectionType.INJECTED) {
|
||||
return <Identicon />
|
||||
} else if (connectionType === ConnectionType.WALLET_CONNECT) {
|
||||
return <img src={WalletConnectIcon} alt="WalletConnect" />
|
||||
@@ -71,12 +66,11 @@ const useIcon = (connectionType: ConnectionType) => {
|
||||
|
||||
export default function StatusIcon({ connectionType, size }: { connectionType: ConnectionType; size?: number }) {
|
||||
const hasSocks = useHasSocks()
|
||||
const isNavbarEnabled = useNavBarFlag() === NavBarVariant.Enabled
|
||||
const icon = useIcon(connectionType)
|
||||
|
||||
return (
|
||||
<IconWrapper size={size ?? 16}>
|
||||
{isNavbarEnabled && hasSocks && <Socks />}
|
||||
{hasSocks && <Socks />}
|
||||
{icon}
|
||||
</IconWrapper>
|
||||
)
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import jazzicon from '@metamask/jazzicon'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const StyledIdenticon = styled.div<{ isNavbarEnabled: boolean }>`
|
||||
height: ${({ isNavbarEnabled }) => (isNavbarEnabled ? '24px' : '1rem')};
|
||||
width: ${({ isNavbarEnabled }) => (isNavbarEnabled ? '24px' : '1rem')};
|
||||
const StyledIdenticon = styled.div<{ iconSize: number }>`
|
||||
height: ${({ iconSize }) => `${iconSize}px`};
|
||||
width: ${({ iconSize }) => `${iconSize}px`};
|
||||
border-radius: 1.125rem;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg4};
|
||||
font-size: initial;
|
||||
@@ -19,12 +18,11 @@ const StyledAvatar = styled.img`
|
||||
border-radius: inherit;
|
||||
`
|
||||
|
||||
export default function Identicon() {
|
||||
export default function Identicon({ size }: { size?: number }) {
|
||||
const { account } = useWeb3React()
|
||||
const { avatar } = useENSAvatar(account ?? undefined)
|
||||
const [fetchable, setFetchable] = useState(true)
|
||||
const isNavbarEnabled = useNavBarFlag() === NavBarVariant.Enabled
|
||||
const iconSize = isNavbarEnabled ? 24 : 16
|
||||
const iconSize = size ?? 24
|
||||
|
||||
const icon = useMemo(() => account && jazzicon(iconSize, parseInt(account.slice(2, 10), 16)), [account, iconSize])
|
||||
const iconRef = useRef<HTMLDivElement>(null)
|
||||
@@ -44,7 +42,7 @@ export default function Identicon() {
|
||||
}, [icon, iconRef])
|
||||
|
||||
return (
|
||||
<StyledIdenticon isNavbarEnabled={isNavbarEnabled}>
|
||||
<StyledIdenticon iconSize={iconSize}>
|
||||
{avatar && fetchable ? (
|
||||
<StyledAvatar alt="avatar" src={avatar} onError={() => setFetchable(false)}></StyledAvatar>
|
||||
) : (
|
||||
|
||||
@@ -19,7 +19,7 @@ const HandleAccent = styled.path`
|
||||
|
||||
stroke-width: 1.5;
|
||||
stroke: ${({ theme }) => theme.deprecated_white};
|
||||
opacity: 0.6;
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
`
|
||||
|
||||
const LabelGroup = styled.g<{ visible: boolean }>`
|
||||
|
||||
@@ -176,7 +176,7 @@ export default function LiquidityChartRangeInput({
|
||||
message={<Trans>Liquidity data not available.</Trans>}
|
||||
icon={<CloudOff size={56} stroke={theme.deprecated_text4} />}
|
||||
/>
|
||||
) : !formattedData || formattedData === [] || !price ? (
|
||||
) : !formattedData || formattedData.length === 0 || !price ? (
|
||||
<InfoBox
|
||||
message={<Trans>There is no liquidity data.</Trans>}
|
||||
icon={<BarChart2 size={56} stroke={theme.deprecated_text4} />}
|
||||
|
||||
@@ -9,13 +9,12 @@ const rotate = keyframes`
|
||||
}
|
||||
`
|
||||
|
||||
const StyledSVG = styled.svg<{ size: string; stroke?: string; redesignFlag?: boolean }>`
|
||||
const StyledSVG = styled.svg<{ size: string; stroke?: string }>`
|
||||
animation: 2s ${rotate} linear infinite;
|
||||
height: ${({ size }) => size};
|
||||
width: ${({ size }) => size};
|
||||
path {
|
||||
stroke: ${({ stroke, redesignFlag, theme }) =>
|
||||
redesignFlag ? theme.accentActive : stroke ?? theme.deprecated_primary1};
|
||||
stroke: ${({ stroke, theme }) => theme.accentActive};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -27,25 +26,15 @@ export default function Loader({
|
||||
size = '16px',
|
||||
stroke,
|
||||
strokeWidth,
|
||||
redesignFlag,
|
||||
...rest
|
||||
}: {
|
||||
size?: string
|
||||
stroke?: string
|
||||
strokeWidth?: number
|
||||
redesignFlag?: boolean
|
||||
[k: string]: any
|
||||
}) {
|
||||
return (
|
||||
<StyledSVG
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
size={size}
|
||||
stroke={stroke}
|
||||
redesignFlag={redesignFlag}
|
||||
{...rest}
|
||||
>
|
||||
<StyledSVG viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" size={size} stroke={stroke} {...rest}>
|
||||
<path
|
||||
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 9.27455 20.9097 6.80375 19.1414 5"
|
||||
strokeWidth={strokeWidth ?? '2.5'}
|
||||
|
||||
@@ -278,7 +278,7 @@ export default function Menu() {
|
||||
</ToggleMenuItem>
|
||||
<ToggleMenuItem onClick={() => toggleDarkMode()}>
|
||||
<div>{darkMode ? <Trans>Light Theme</Trans> : <Trans>Dark Theme</Trans>}</div>
|
||||
{darkMode ? <Moon opacity={0.6} size={16} /> : <Sun opacity={0.6} size={16} />}
|
||||
{darkMode ? <Sun opacity={0.6} size={16} /> : <Moon opacity={0.6} size={16} />}
|
||||
</ToggleMenuItem>
|
||||
<MenuItem href="https://docs.uniswap.org/">
|
||||
<div>
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
import { DialogContent, DialogOverlay } from '@reach/dialog'
|
||||
import { transparentize } from 'polished'
|
||||
import React from 'react'
|
||||
import { animated, useSpring, useTransition } from 'react-spring'
|
||||
import { useGesture } from 'react-use-gesture'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
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)<{ redesignFlag?: boolean }>`
|
||||
const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ scrollOverlay?: boolean }>`
|
||||
&[data-reach-dialog-overlay] {
|
||||
z-index: 2;
|
||||
z-index: ${Z_INDEX.modalBackdrop};
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow-y: ${({ scrollOverlay }) => scrollOverlay && 'scroll'};
|
||||
justify-content: center;
|
||||
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundScrim : theme.deprecated_modalBG)};
|
||||
background-color: ${({ theme }) => theme.backgroundScrim};
|
||||
}
|
||||
`
|
||||
|
||||
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, redesignFlag, ...rest }) => (
|
||||
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, scrollOverlay, ...rest }) => (
|
||||
<AnimatedDialogContent {...rest} />
|
||||
)).attrs({
|
||||
'aria-label': 'dialog',
|
||||
@@ -34,17 +35,16 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, rede
|
||||
overflow-y: auto;
|
||||
|
||||
&[data-reach-dialog-content] {
|
||||
margin: 0 0 2rem 0;
|
||||
margin: auto;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg0};
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg1};
|
||||
box-shadow: ${({ theme, redesignFlag }) =>
|
||||
redesignFlag ? theme.deepShadow : `0 4px 8px 0 ${transparentize(0.95, theme.shadow1)}`};
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
padding: 0px;
|
||||
width: 50vw;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
align-self: ${({ mobile }) => (mobile ? 'flex-end' : 'center')};
|
||||
align-self: ${({ mobile }) => mobile && 'flex-end'};
|
||||
|
||||
max-width: 420px;
|
||||
${({ maxHeight }) =>
|
||||
@@ -57,11 +57,11 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, rede
|
||||
css`
|
||||
min-height: ${minHeight}vh;
|
||||
`}
|
||||
display: flex;
|
||||
display: ${({ scrollOverlay }) => (scrollOverlay ? 'inline-table' : 'flex')};
|
||||
border-radius: 20px;
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
width: 65vw;
|
||||
margin: 0;
|
||||
margin: auto;
|
||||
`}
|
||||
${({ theme, mobile }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
width: 85vw;
|
||||
@@ -85,7 +85,7 @@ interface ModalProps {
|
||||
maxHeight?: number
|
||||
initialFocusRef?: React.RefObject<any>
|
||||
children?: React.ReactNode
|
||||
redesignFlag?: boolean
|
||||
scrollOverlay?: boolean
|
||||
}
|
||||
|
||||
export default function Modal({
|
||||
@@ -95,9 +95,9 @@ export default function Modal({
|
||||
maxHeight = 90,
|
||||
initialFocusRef,
|
||||
children,
|
||||
redesignFlag,
|
||||
scrollOverlay,
|
||||
}: ModalProps) {
|
||||
const fadeTransition = useTransition(isOpen, null, {
|
||||
const fadeTransition = useTransition(isOpen, {
|
||||
config: { duration: 200 },
|
||||
from: { opacity: 0 },
|
||||
enter: { opacity: 1 },
|
||||
@@ -118,16 +118,16 @@ export default function Modal({
|
||||
|
||||
return (
|
||||
<>
|
||||
{fadeTransition.map(
|
||||
({ item, key, props }) =>
|
||||
{fadeTransition(
|
||||
({ opacity }, item) =>
|
||||
item && (
|
||||
<StyledDialogOverlay
|
||||
key={key}
|
||||
style={props}
|
||||
as={AnimatedDialogOverlay}
|
||||
style={{ opacity: opacity.to({ range: [0.0, 1.0], output: [0, 1] }) }}
|
||||
onDismiss={onDismiss}
|
||||
initialFocusRef={initialFocusRef}
|
||||
unstable_lockFocusAcrossFrames={false}
|
||||
redesignFlag={redesignFlag}
|
||||
scrollOverlay={scrollOverlay}
|
||||
>
|
||||
<StyledDialogContent
|
||||
{...(isMobile
|
||||
@@ -140,7 +140,7 @@ export default function Modal({
|
||||
minHeight={minHeight}
|
||||
maxHeight={maxHeight}
|
||||
mobile={isMobile}
|
||||
redesignFlag={redesignFlag}
|
||||
scrollOverlay={scrollOverlay}
|
||||
>
|
||||
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
|
||||
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}
|
||||
|
||||
23
src/components/NavBar/ChainSelector.css.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
import { lightGrayOverlayOnHover } from 'nft/css/common.css'
|
||||
|
||||
import { sprinkles } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const ChainSelector = style([
|
||||
lightGrayOverlayOnHover,
|
||||
sprinkles({
|
||||
borderRadius: '8',
|
||||
height: '40',
|
||||
cursor: 'pointer',
|
||||
border: 'none',
|
||||
color: 'textPrimary',
|
||||
background: 'none',
|
||||
}),
|
||||
])
|
||||
|
||||
export const Image = style([
|
||||
sprinkles({
|
||||
width: '20',
|
||||
height: '20',
|
||||
}),
|
||||
])
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { StyledChevronDown, StyledChevronUp } from 'components/Icons'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
@@ -8,46 +7,18 @@ import useSyncChainQuery from 'hooks/useSyncChainQuery'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Portal } from 'nft/components/common/Portal'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { CheckMarkIcon, TokenWarningRedIcon } from 'nft/components/icons'
|
||||
import { TokenWarningRedIcon } from 'nft/components/icons'
|
||||
import { subhead } from 'nft/css/common.css'
|
||||
import { themeVars, vars } from 'nft/css/sprinkles.css'
|
||||
import { themeVars } from 'nft/css/sprinkles.css'
|
||||
import { useIsMobile } from 'nft/hooks'
|
||||
import { ReactNode, useReducer, useRef } from 'react'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { ChevronDown, ChevronUp } from 'react-feather'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
|
||||
import * as styles from './ChainSwitcher.css'
|
||||
import * as styles from './ChainSelector.css'
|
||||
import ChainSelectorRow from './ChainSelectorRow'
|
||||
import { NavDropdown } from './NavDropdown'
|
||||
|
||||
const ChainRow = ({
|
||||
targetChain,
|
||||
onSelectChain,
|
||||
}: {
|
||||
targetChain: SupportedChainId
|
||||
onSelectChain: (targetChain: number) => void
|
||||
}) => {
|
||||
const { chainId } = useWeb3React()
|
||||
const active = chainId === targetChain
|
||||
const { label, logoUrl } = getChainInfo(targetChain)
|
||||
|
||||
return (
|
||||
<Column borderRadius="12">
|
||||
<Row
|
||||
as="button"
|
||||
background="none"
|
||||
className={`${styles.ChainSwitcherRow} ${subhead}`}
|
||||
onClick={() => onSelectChain(targetChain)}
|
||||
>
|
||||
<ChainDetails>
|
||||
<img src={logoUrl} alt={label} className={styles.Icon} />
|
||||
{label}
|
||||
</ChainDetails>
|
||||
{active && <CheckMarkIcon width={20} height={20} color={vars.color.blue400} />}
|
||||
</Row>
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
|
||||
const ChainDetails = ({ children }: { children: ReactNode }) => <Row>{children}</Row>
|
||||
|
||||
const NETWORK_SELECTOR_CHAINS = [
|
||||
SupportedChainId.MAINNET,
|
||||
SupportedChainId.POLYGON,
|
||||
@@ -56,24 +27,38 @@ const NETWORK_SELECTOR_CHAINS = [
|
||||
SupportedChainId.CELO,
|
||||
]
|
||||
|
||||
interface ChainSwitcherProps {
|
||||
interface ChainSelectorProps {
|
||||
leftAlign?: boolean
|
||||
}
|
||||
|
||||
export const ChainSwitcher = ({ leftAlign }: ChainSwitcherProps) => {
|
||||
export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
|
||||
const { chainId } = useWeb3React()
|
||||
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false)
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const modalRef = useRef<HTMLDivElement>(null)
|
||||
useOnClickOutside(ref, isOpen ? toggleOpen : undefined, [modalRef])
|
||||
useOnClickOutside(ref, () => setIsOpen(false), [modalRef])
|
||||
|
||||
const info = chainId ? getChainInfo(chainId) : undefined
|
||||
|
||||
const selectChain = useSelectChain()
|
||||
useSyncChainQuery()
|
||||
|
||||
const [pendingChainId, setPendingChainId] = useState<SupportedChainId | undefined>(undefined)
|
||||
|
||||
const onSelectChain = useCallback(
|
||||
async (targetChainId: SupportedChainId) => {
|
||||
setPendingChainId(targetChainId)
|
||||
await selectChain(targetChainId)
|
||||
setPendingChainId(undefined)
|
||||
setIsOpen(false)
|
||||
},
|
||||
[selectChain, setIsOpen]
|
||||
)
|
||||
|
||||
if (!chainId) {
|
||||
return null
|
||||
}
|
||||
@@ -82,33 +67,37 @@ export const ChainSwitcher = ({ leftAlign }: ChainSwitcherProps) => {
|
||||
|
||||
const dropdown = (
|
||||
<NavDropdown top="56" left={leftAlign ? '0' : 'auto'} right={leftAlign ? 'auto' : '0'} ref={modalRef}>
|
||||
<Column marginX="8">
|
||||
<Column paddingX="8">
|
||||
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) => (
|
||||
<ChainRow
|
||||
onSelectChain={async (targetChainId: SupportedChainId) => {
|
||||
await selectChain(targetChainId)
|
||||
toggleOpen()
|
||||
}}
|
||||
<ChainSelectorRow
|
||||
onSelectChain={onSelectChain}
|
||||
targetChain={chainId}
|
||||
key={chainId}
|
||||
isPending={chainId === pendingChainId}
|
||||
/>
|
||||
))}
|
||||
</Column>
|
||||
</NavDropdown>
|
||||
)
|
||||
|
||||
const chevronProps = {
|
||||
height: 20,
|
||||
width: 20,
|
||||
color: theme.textSecondary,
|
||||
}
|
||||
|
||||
return (
|
||||
<Box position="relative" ref={ref}>
|
||||
<Row
|
||||
as="button"
|
||||
gap="8"
|
||||
className={styles.ChainSwitcher}
|
||||
className={styles.ChainSelector}
|
||||
background={isOpen ? 'accentActiveSoft' : 'none'}
|
||||
onClick={toggleOpen}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
{!isSupported ? (
|
||||
<>
|
||||
<TokenWarningRedIcon fill={themeVars.colors.darkGray} width={24} height={24} />
|
||||
<TokenWarningRedIcon fill={themeVars.colors.textSecondary} width={24} height={24} />
|
||||
<Box as="span" className={subhead} display={{ sm: 'none', xxl: 'flex' }} style={{ lineHeight: '20px' }}>
|
||||
Unsupported
|
||||
</Box>
|
||||
@@ -121,7 +110,7 @@ export const ChainSwitcher = ({ leftAlign }: ChainSwitcherProps) => {
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
{isOpen ? <StyledChevronUp /> : <StyledChevronDown />}
|
||||
{isOpen ? <ChevronUp {...chevronProps} /> : <ChevronDown {...chevronProps} />}
|
||||
</Row>
|
||||
{isOpen && (isMobile ? <Portal>{dropdown}</Portal> : <>{dropdown}</>)}
|
||||
</Box>
|
||||
89
src/components/NavBar/ChainSelectorRow.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Loader from 'components/Loader'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { CheckMarkIcon } from 'nft/components/icons'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
const LOGO_SIZE = 20
|
||||
|
||||
const Container = styled.button`
|
||||
display: grid;
|
||||
background: none;
|
||||
grid-template-columns: min-content 1fr min-content;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
line-height: 24px;
|
||||
border: none;
|
||||
justify-content: space-between;
|
||||
padding: 10px 8px;
|
||||
cursor: pointer;
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
width: 240px;
|
||||
transition: ${({ theme }) => theme.transition.duration.medium} ${({ theme }) => theme.transition.timing.ease}
|
||||
background-color;
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.backgroundOutline};
|
||||
}
|
||||
`
|
||||
|
||||
const Label = styled.div`
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
font-size: 16px;
|
||||
`
|
||||
|
||||
const Status = styled.div`
|
||||
grid-column: 3;
|
||||
grid-row: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: ${LOGO_SIZE}px;
|
||||
`
|
||||
|
||||
const ApproveText = styled.div`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 12px;
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
`
|
||||
|
||||
const Logo = styled.img`
|
||||
height: ${LOGO_SIZE}px;
|
||||
width: ${LOGO_SIZE}px;
|
||||
margin-right: 12px;
|
||||
`
|
||||
|
||||
export default function ChainSelectorRow({
|
||||
targetChain,
|
||||
onSelectChain,
|
||||
isPending,
|
||||
}: {
|
||||
targetChain: SupportedChainId
|
||||
onSelectChain: (targetChain: number) => void
|
||||
isPending: boolean
|
||||
}) {
|
||||
const { chainId } = useWeb3React()
|
||||
const active = chainId === targetChain
|
||||
const { label, logoUrl } = getChainInfo(targetChain)
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<Container onClick={() => onSelectChain(targetChain)}>
|
||||
<Logo src={logoUrl} alt={label} />
|
||||
<Label>{label}</Label>
|
||||
{isPending && <ApproveText>Approve in wallet</ApproveText>}
|
||||
<Status>
|
||||
{active && <CheckMarkIcon width={LOGO_SIZE} height={LOGO_SIZE} color={theme.accentActive} />}
|
||||
{isPending && <Loader width={LOGO_SIZE} height={LOGO_SIZE} />}
|
||||
</Status>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
import { lightGrayOverlayOnHover } from 'nft/css/common.css'
|
||||
|
||||
import { breakpoints, sprinkles } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const ChainSwitcher = style([
|
||||
lightGrayOverlayOnHover,
|
||||
sprinkles({
|
||||
borderRadius: '8',
|
||||
paddingY: '8',
|
||||
paddingX: '12',
|
||||
cursor: 'pointer',
|
||||
border: 'none',
|
||||
color: 'blackBlue',
|
||||
background: 'none',
|
||||
}),
|
||||
])
|
||||
|
||||
export const ChainSwitcherRow = style([
|
||||
lightGrayOverlayOnHover,
|
||||
sprinkles({
|
||||
border: 'none',
|
||||
justifyContent: 'space-between',
|
||||
paddingX: '8',
|
||||
paddingY: '8',
|
||||
cursor: 'pointer',
|
||||
color: 'blackBlue',
|
||||
borderRadius: '12',
|
||||
width: { sm: 'full' },
|
||||
}),
|
||||
{
|
||||
lineHeight: '24px',
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.sm}px)`]: {
|
||||
width: '204px',
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export const Image = style([
|
||||
sprinkles({
|
||||
width: '20',
|
||||
height: '20',
|
||||
}),
|
||||
])
|
||||
|
||||
export const Icon = style([
|
||||
Image,
|
||||
sprinkles({
|
||||
marginRight: '12',
|
||||
}),
|
||||
])
|
||||
@@ -17,7 +17,7 @@ export const hover = style([
|
||||
export const MenuRow = style([
|
||||
hover,
|
||||
sprinkles({
|
||||
color: 'blackBlue',
|
||||
color: 'textPrimary',
|
||||
paddingY: '8',
|
||||
paddingX: '8',
|
||||
width: 'full',
|
||||
@@ -40,7 +40,8 @@ export const SecondaryText = style([
|
||||
sprinkles({
|
||||
paddingY: '8',
|
||||
paddingX: '8',
|
||||
color: 'darkGray',
|
||||
color: 'textSecondary',
|
||||
width: 'full',
|
||||
}),
|
||||
{
|
||||
lineHeight: '20px',
|
||||
@@ -54,7 +55,7 @@ export const Separator = style([
|
||||
}),
|
||||
{
|
||||
borderTop: 'solid',
|
||||
borderColor: themeVars.colors.medGray,
|
||||
borderColor: themeVars.colors.backgroundOutline,
|
||||
borderWidth: '1px',
|
||||
},
|
||||
])
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
|
||||
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
@@ -11,7 +10,6 @@ import {
|
||||
EllipsisIcon,
|
||||
GithubIconMenu,
|
||||
GovernanceIcon,
|
||||
ThinTagIcon,
|
||||
TwitterIconMenu,
|
||||
} from 'nft/components/icons'
|
||||
import { body, bodySmall } from 'nft/css/common.css'
|
||||
@@ -100,7 +98,7 @@ const Icon = ({ href, children }: { href?: string; children: ReactNode }) => {
|
||||
rel={href ? 'noopener noreferrer' : undefined}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
color="blackBlue"
|
||||
color="textPrimary"
|
||||
background="none"
|
||||
border="none"
|
||||
justifyContent="center"
|
||||
@@ -117,7 +115,6 @@ export const MenuDropdown = () => {
|
||||
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
|
||||
const togglePrivacyPolicy = useToggleModal(ApplicationModal.PRIVACY_POLICY)
|
||||
const openFeatureFlagsModal = useToggleModal(ApplicationModal.FEATURE_FLAGS)
|
||||
const nftFlag = useNftFlag()
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
useOnClickOutside(ref, isOpen ? toggleOpen : undefined)
|
||||
@@ -126,23 +123,13 @@ export const MenuDropdown = () => {
|
||||
<>
|
||||
<Box position="relative" ref={ref}>
|
||||
<NavIcon isActive={isOpen} onClick={toggleOpen}>
|
||||
<EllipsisIcon />
|
||||
<EllipsisIcon viewBox="0 0 20 20" width={24} height={24} />
|
||||
</NavIcon>
|
||||
|
||||
{isOpen && (
|
||||
<NavDropdown top={{ sm: 'unset', lg: '56' }} bottom={{ sm: '56', lg: 'unset' }} right="0">
|
||||
<Column gap="16">
|
||||
<Column paddingX="8" gap="4">
|
||||
{nftFlag === NftVariant.Enabled && (
|
||||
<PrimaryMenuRow to="/nft/sell" close={toggleOpen}>
|
||||
<Icon>
|
||||
<ThinTagIcon width={24} height={24} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Sell NFTs</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
)}
|
||||
<PrimaryMenuRow to="/vote" close={toggleOpen}>
|
||||
<Icon>
|
||||
<GovernanceIcon width={24} height={24} />
|
||||
@@ -156,7 +143,7 @@ export const MenuDropdown = () => {
|
||||
<BarChartIcon width={24} height={24} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>View token analytics</Trans>
|
||||
<Trans>View more analytics</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
</Column>
|
||||
@@ -190,13 +177,28 @@ export const MenuDropdown = () => {
|
||||
</Box>
|
||||
<IconRow>
|
||||
<Icon href="https://discord.com/invite/FCfyBSbCU5">
|
||||
<DiscordIconMenu className={styles.hover} width={24} height={24} color={themeVars.colors.darkGray} />
|
||||
<DiscordIconMenu
|
||||
className={styles.hover}
|
||||
width={24}
|
||||
height={24}
|
||||
color={themeVars.colors.textSecondary}
|
||||
/>
|
||||
</Icon>
|
||||
<Icon href="https://twitter.com/Uniswap">
|
||||
<TwitterIconMenu className={styles.hover} width={24} height={24} color={themeVars.colors.darkGray} />
|
||||
<TwitterIconMenu
|
||||
className={styles.hover}
|
||||
width={24}
|
||||
height={24}
|
||||
color={themeVars.colors.textSecondary}
|
||||
/>
|
||||
</Icon>
|
||||
<Icon href="https://github.com/Uniswap">
|
||||
<GithubIconMenu className={styles.hover} width={24} height={24} color={themeVars.colors.darkGray} />
|
||||
<GithubIconMenu
|
||||
className={styles.hover}
|
||||
width={24}
|
||||
height={24}
|
||||
color={themeVars.colors.textSecondary}
|
||||
/>
|
||||
</Icon>
|
||||
</IconRow>
|
||||
</Column>
|
||||
|
||||
@@ -4,9 +4,9 @@ import { sprinkles } from '../../nft/css/sprinkles.css'
|
||||
|
||||
const baseNavDropdown = style([
|
||||
sprinkles({
|
||||
background: 'lightGray',
|
||||
background: 'backgroundSurface',
|
||||
borderStyle: 'solid',
|
||||
borderColor: 'medGray',
|
||||
borderColor: 'backgroundOutline',
|
||||
borderWidth: '1px',
|
||||
paddingBottom: '8',
|
||||
paddingTop: '8',
|
||||
|
||||
@@ -4,6 +4,7 @@ import { sprinkles, vars } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const navIcon = style([
|
||||
sprinkles({
|
||||
alignItems: 'center',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -11,7 +12,6 @@ export const navIcon = style([
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
padding: '8',
|
||||
borderRadius: '8',
|
||||
transition: '250',
|
||||
}),
|
||||
|
||||
@@ -15,8 +15,10 @@ export const NavIcon = ({ children, isActive, onClick }: NavIconProps) => {
|
||||
as="button"
|
||||
className={styles.navIcon}
|
||||
background={isActive ? 'accentActiveSoft' : 'none'}
|
||||
color={isActive ? 'blackBlue' : 'darkGray'}
|
||||
color={isActive ? 'textPrimary' : 'textSecondary'}
|
||||
onClick={onClick}
|
||||
height="40"
|
||||
width="40"
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 baseSearchStyle = style([
|
||||
sprinkles({
|
||||
@@ -11,7 +12,7 @@ const baseSearchStyle = style([
|
||||
width: { sm: 'viewWidth' },
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '1px',
|
||||
borderColor: 'medGray',
|
||||
borderColor: 'backgroundOutline',
|
||||
}),
|
||||
{
|
||||
'@media': {
|
||||
@@ -31,11 +32,9 @@ export const searchBarContainer = style([
|
||||
}),
|
||||
{
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.sm}px)`]: {
|
||||
top: '-24px',
|
||||
},
|
||||
[`screen and (min-width: ${breakpoints.lg}px)`]: {
|
||||
right: `-${DESKTOP_NAVBAR_WIDTH / 2}px`,
|
||||
right: `-${DESKTOP_NAVBAR_WIDTH / 2 - MAGNIFYING_GLASS_ICON_WIDTH}px`,
|
||||
top: '-3px',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -44,11 +43,9 @@ export const searchBarContainer = style([
|
||||
export const searchBar = style([
|
||||
baseSearchStyle,
|
||||
sprinkles({
|
||||
height: 'full',
|
||||
color: 'placeholder',
|
||||
color: 'textTertiary',
|
||||
paddingX: '16',
|
||||
cursor: 'pointer',
|
||||
background: 'lightGray',
|
||||
background: 'backgroundSurface',
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -57,11 +54,12 @@ export const searchBarInput = style([
|
||||
padding: '0',
|
||||
fontWeight: 'normal',
|
||||
fontSize: '16',
|
||||
color: { default: 'blackBlue', placeholder: 'placeholder' },
|
||||
color: { default: 'textPrimary', placeholder: 'textTertiary' },
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
lineHeight: '24',
|
||||
height: 'full',
|
||||
}),
|
||||
{ lineHeight: '24px' },
|
||||
])
|
||||
|
||||
export const searchBarDropdown = style([
|
||||
@@ -69,7 +67,8 @@ export const searchBarDropdown = style([
|
||||
sprinkles({
|
||||
borderBottomLeftRadius: '12',
|
||||
borderBottomRightRadius: '12',
|
||||
background: 'lightGray',
|
||||
background: 'backgroundSurface',
|
||||
height: { sm: 'viewHeight', md: 'auto' },
|
||||
}),
|
||||
{
|
||||
borderTop: 'none',
|
||||
@@ -84,11 +83,10 @@ export const suggestionRow = style([
|
||||
justifyContent: 'space-between',
|
||||
paddingY: '8',
|
||||
paddingX: '16',
|
||||
transition: '250',
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
{
|
||||
':hover': {
|
||||
cursor: 'pointer',
|
||||
background: vars.color.lightGrayOverlay,
|
||||
},
|
||||
textDecoration: 'none',
|
||||
@@ -105,8 +103,10 @@ export const suggestionImage = sprinkles({
|
||||
export const suggestionPrimaryContainer = style([
|
||||
sprinkles({
|
||||
alignItems: 'flex-start',
|
||||
width: 'full',
|
||||
}),
|
||||
{
|
||||
width: '90%',
|
||||
},
|
||||
])
|
||||
|
||||
export const suggestionSecondaryContainer = sprinkles({
|
||||
@@ -120,7 +120,7 @@ export const primaryText = style([
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
color: 'blackBlue',
|
||||
color: 'textPrimary',
|
||||
}),
|
||||
{
|
||||
lineHeight: '24px',
|
||||
@@ -130,7 +130,7 @@ export const primaryText = style([
|
||||
export const secondaryText = style([
|
||||
buttonTextSmall,
|
||||
sprinkles({
|
||||
color: 'darkGray',
|
||||
color: 'textSecondary',
|
||||
}),
|
||||
{
|
||||
lineHeight: '20px',
|
||||
@@ -140,7 +140,7 @@ export const secondaryText = style([
|
||||
export const imageHolder = style([
|
||||
suggestionImage,
|
||||
sprinkles({
|
||||
background: 'loading',
|
||||
background: 'backgroundModule',
|
||||
flexShrink: '0',
|
||||
}),
|
||||
])
|
||||
@@ -153,7 +153,7 @@ export const suggestionIcon = sprinkles({
|
||||
export const sectionHeader = style([
|
||||
subheadSmall,
|
||||
sprinkles({
|
||||
color: 'darkGray',
|
||||
color: 'textSecondary',
|
||||
}),
|
||||
{
|
||||
lineHeight: '20px',
|
||||
@@ -167,3 +167,50 @@ export const notFoundContainer = style([
|
||||
paddingLeft: '16',
|
||||
}),
|
||||
])
|
||||
|
||||
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',
|
||||
opacity: '0',
|
||||
padding: '0',
|
||||
height: '0',
|
||||
}),
|
||||
{
|
||||
transition: `${visibilityTransition}, ${delayedTransitionProperties}`,
|
||||
transitionTimingFunction: 'ease-in',
|
||||
},
|
||||
])
|
||||
export const visible = style([
|
||||
sprinkles({
|
||||
visibility: 'visible',
|
||||
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)`]: {
|
||||
transform: 'translateX(0)',
|
||||
transition: `transform ${vars.time[125]}`,
|
||||
transitionTimingFunction: 'ease-in',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,268 +1,29 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { t } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { ElementName, Event, EventName, SectionName } from 'analytics/constants'
|
||||
import { Trace } from 'analytics/Trace'
|
||||
import { useTrace } from 'analytics/Trace'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import clsx from 'clsx'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { organizeSearchResults } from 'lib/utils/searchBar'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { Overlay } from 'nft/components/modals/Overlay'
|
||||
import { magicalGradientOnHover, subheadSmall } from 'nft/css/common.css'
|
||||
import { useIsMobile, useSearchHistory } from 'nft/hooks'
|
||||
import { fetchSearchCollections, fetchTrendingCollections } from 'nft/queries'
|
||||
import { Row } from 'nft/components/Flex'
|
||||
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 { fetchTrendingTokens } from 'nft/queries/genie/TrendingTokensFetcher'
|
||||
import { FungibleToken, GenieCollection, TimePeriod, TrendingCollection } from 'nft/types'
|
||||
import { formatEthPrice } from 'nft/utils/currency'
|
||||
import { ChangeEvent, useEffect, useMemo, useReducer, useRef, useState } from 'react'
|
||||
import { ChangeEvent, useEffect, useReducer, useRef, useState } from 'react'
|
||||
import { useQuery } from 'react-query'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ClockIcon,
|
||||
MagnifyingGlassIcon,
|
||||
NavMagnifyingGlassIcon,
|
||||
TrendingArrow,
|
||||
} from '../../nft/components/icons'
|
||||
import { ChevronLeftIcon, MagnifyingGlassIcon, NavMagnifyingGlassIcon } from '../../nft/components/icons'
|
||||
import { NavIcon } from './NavIcon'
|
||||
import * as styles from './SearchBar.css'
|
||||
import { CollectionRow, SkeletonRow, TokenRow } from './SuggestionRow'
|
||||
|
||||
interface SearchBarDropdownSectionProps {
|
||||
toggleOpen: () => void
|
||||
suggestions: (GenieCollection | FungibleToken)[]
|
||||
header: JSX.Element
|
||||
headerIcon?: JSX.Element
|
||||
hoveredIndex: number | undefined
|
||||
startingIndex: number
|
||||
setHoveredIndex: (index: number | undefined) => void
|
||||
}
|
||||
|
||||
export const SearchBarDropdownSection = ({
|
||||
toggleOpen,
|
||||
suggestions,
|
||||
header,
|
||||
headerIcon = undefined,
|
||||
hoveredIndex,
|
||||
startingIndex,
|
||||
setHoveredIndex,
|
||||
}: SearchBarDropdownSectionProps) => {
|
||||
return (
|
||||
<Column gap="12">
|
||||
<Row paddingX="16" paddingY="4" gap="8" color="grey300" className={subheadSmall} style={{ lineHeight: '20px' }}>
|
||||
{headerIcon ? headerIcon : null}
|
||||
<Box>{header}</Box>
|
||||
</Row>
|
||||
<Column gap="12">
|
||||
{suggestions?.map((suggestion, index) =>
|
||||
isCollection(suggestion) ? (
|
||||
<CollectionRow
|
||||
key={suggestion.address}
|
||||
collection={suggestion as GenieCollection}
|
||||
isHovered={hoveredIndex === index + startingIndex}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
index={index + startingIndex}
|
||||
/>
|
||||
) : (
|
||||
<TokenRow
|
||||
key={suggestion.address}
|
||||
token={suggestion as FungibleToken}
|
||||
isHovered={hoveredIndex === index + startingIndex}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
index={index + startingIndex}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Column>
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
|
||||
interface SearchBarDropdownProps {
|
||||
toggleOpen: () => void
|
||||
tokens: FungibleToken[]
|
||||
collections: GenieCollection[]
|
||||
hasInput: boolean
|
||||
}
|
||||
|
||||
export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }: SearchBarDropdownProps) => {
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(0)
|
||||
const searchHistory = useSearchHistory(
|
||||
(state: { history: (FungibleToken | GenieCollection)[] }) => state.history
|
||||
).slice(0, 2)
|
||||
const { pathname } = useLocation()
|
||||
const isNFTPage = pathname.includes('/nfts')
|
||||
const isTokenPage = pathname.includes('/tokens')
|
||||
const phase1Flag = useNftFlag()
|
||||
|
||||
const tokenSearchResults =
|
||||
tokens.length > 0 ? (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={isNFTPage ? collections.length : 0}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={tokens}
|
||||
header={<Trans>Tokens</Trans>}
|
||||
/>
|
||||
) : (
|
||||
<Box className={styles.notFoundContainer}>
|
||||
<Trans>No tokens found.</Trans>
|
||||
</Box>
|
||||
)
|
||||
|
||||
const collectionSearchResults =
|
||||
phase1Flag === NftVariant.Enabled ? (
|
||||
collections.length > 0 ? (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={isNFTPage ? 0 : tokens.length}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={collections}
|
||||
header={<Trans>NFT Collections</Trans>}
|
||||
/>
|
||||
) : (
|
||||
<Box className={styles.notFoundContainer}>No NFT collections found.</Box>
|
||||
)
|
||||
) : null
|
||||
|
||||
const { data: trendingCollectionResults } = useQuery(['trendingCollections', 'eth', 'twenty_four_hours'], () =>
|
||||
fetchTrendingCollections({ volumeType: 'eth', timePeriod: 'ONE_DAY' as TimePeriod, size: 3 })
|
||||
)
|
||||
|
||||
const trendingCollections = useMemo(() => {
|
||||
return trendingCollectionResults
|
||||
?.map((collection) => {
|
||||
return {
|
||||
...collection,
|
||||
collectionAddress: collection.address,
|
||||
floorPrice: formatEthPrice(collection.floor?.toString()),
|
||||
stats: {
|
||||
total_supply: collection.totalSupply,
|
||||
one_day_change: collection.floorChange,
|
||||
},
|
||||
}
|
||||
})
|
||||
.slice(0, isNFTPage ? 3 : 2)
|
||||
}, [isNFTPage, trendingCollectionResults])
|
||||
|
||||
const showTrendingCollections: boolean = useMemo(
|
||||
() => (trendingCollections?.length ?? 0) > 0 && !isTokenPage && phase1Flag === NftVariant.Enabled,
|
||||
[trendingCollections?.length, isTokenPage, phase1Flag]
|
||||
)
|
||||
|
||||
const { data: trendingTokenResults } = useQuery([], () => fetchTrendingTokens(4), {
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
})
|
||||
|
||||
const trendingTokensLength = phase1Flag === NftVariant.Enabled ? (isTokenPage ? 3 : 2) : 4
|
||||
|
||||
const trendingTokens = useMemo(() => {
|
||||
return trendingTokenResults?.slice(0, trendingTokensLength)
|
||||
}, [trendingTokenResults, trendingTokensLength])
|
||||
|
||||
const totalSuggestions = hasInput
|
||||
? tokens.length + collections.length
|
||||
: Math.min(searchHistory.length, 2) +
|
||||
(isNFTPage || !isTokenPage ? trendingCollections?.length ?? 0 : 0) +
|
||||
(isTokenPage || !isNFTPage ? trendingTokens?.length ?? 0 : 0)
|
||||
|
||||
// Close the modal on escape
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'ArrowUp') {
|
||||
event.preventDefault()
|
||||
if (!hoveredIndex) {
|
||||
setHoveredIndex(totalSuggestions - 1)
|
||||
} else {
|
||||
setHoveredIndex(hoveredIndex - 1)
|
||||
}
|
||||
} else if (event.key === 'ArrowDown') {
|
||||
event.preventDefault()
|
||||
if (hoveredIndex && hoveredIndex === totalSuggestions - 1) {
|
||||
setHoveredIndex(0)
|
||||
} else {
|
||||
setHoveredIndex((hoveredIndex ?? -1) + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', keyDownHandler)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyDownHandler)
|
||||
}
|
||||
}, [toggleOpen, hoveredIndex, totalSuggestions])
|
||||
|
||||
return (
|
||||
<Box className={styles.searchBarDropdown}>
|
||||
{hasInput ? (
|
||||
// Empty or Up to 8 combined tokens and nfts
|
||||
<Column gap="20">
|
||||
{isNFTPage ? (
|
||||
<>
|
||||
{collectionSearchResults}
|
||||
{tokenSearchResults}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{tokenSearchResults}
|
||||
{collectionSearchResults}
|
||||
</>
|
||||
)}
|
||||
</Column>
|
||||
) : (
|
||||
// Recent Searches, Trending Tokens, Trending Collections
|
||||
<Column gap="20">
|
||||
{searchHistory.length > 0 && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={0}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={searchHistory}
|
||||
header={<Trans>Recent searches</Trans>}
|
||||
headerIcon={<ClockIcon />}
|
||||
/>
|
||||
)}
|
||||
{(trendingTokens?.length ?? 0) > 0 && !isNFTPage && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={searchHistory.length}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={trendingTokens ?? []}
|
||||
header={<Trans>Popular tokens</Trans>}
|
||||
headerIcon={<TrendingArrow />}
|
||||
/>
|
||||
)}
|
||||
{showTrendingCollections && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={searchHistory.length + (isNFTPage ? 0 : trendingTokens?.length ?? 0)}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={trendingCollections as unknown as GenieCollection[]}
|
||||
header={<Trans>Popular NFT collections</Trans>}
|
||||
headerIcon={<TrendingArrow />}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function isCollection(suggestion: GenieCollection | FungibleToken | TrendingCollection) {
|
||||
return (suggestion as FungibleToken).decimals === undefined
|
||||
}
|
||||
import { SearchBarDropdown } from './SearchBarDropdown'
|
||||
|
||||
export const SearchBar = () => {
|
||||
const [isOpen, toggleOpen] = useReducer((state: boolean) => !state, false)
|
||||
@@ -273,6 +34,7 @@ export const SearchBar = () => {
|
||||
const { pathname } = useLocation()
|
||||
const phase1Flag = useNftFlag()
|
||||
const isMobile = useIsMobile()
|
||||
const isTablet = useIsTablet()
|
||||
|
||||
useOnClickOutside(searchRef, () => {
|
||||
isOpen && toggleOpen()
|
||||
@@ -302,6 +64,7 @@ export const SearchBar = () => {
|
||||
|
||||
const [reducedTokens, reducedCollections] = organizeSearchResults(isNFTPage, tokens ?? [], collections ?? [])
|
||||
|
||||
// close dropdown on escape
|
||||
useEffect(() => {
|
||||
const escapeKeyDownHandler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape' && isOpen) {
|
||||
@@ -330,64 +93,89 @@ 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 })
|
||||
|
||||
const navbarSearchEventProperties = {
|
||||
navbar_search_input_text: debouncedSearchValue,
|
||||
hasInput: debouncedSearchValue && debouncedSearchValue.length > 0,
|
||||
...trace,
|
||||
}
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
<Box
|
||||
position={isOpen ? { sm: 'fixed', md: 'absolute' } : 'static'}
|
||||
width={{ sm: isOpen ? 'viewWidth' : 'auto', md: 'auto' }}
|
||||
ref={searchRef}
|
||||
className={styles.searchBarContainer}
|
||||
>
|
||||
<Row
|
||||
className={clsx(`${styles.searchBar} ${!isOpen && magicalGradientOnHover}`)}
|
||||
borderRadius={isOpen ? undefined : '12'}
|
||||
borderTopRightRadius={isOpen && !isMobile ? '12' : undefined}
|
||||
borderTopLeftRadius={isOpen && !isMobile ? '12' : undefined}
|
||||
borderBottomWidth={isOpen ? '0px' : '1px'}
|
||||
display={{ sm: isOpen ? 'flex' : 'none', xl: 'flex' }}
|
||||
justifyContent={isOpen || phase1Flag === NftVariant.Enabled ? 'flex-start' : 'center'}
|
||||
onFocus={() => !isOpen && toggleOpen()}
|
||||
onClick={() => !isOpen && toggleOpen()}
|
||||
gap="12"
|
||||
<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' }}
|
||||
>
|
||||
<Box display={{ sm: 'none', md: 'flex' }}>
|
||||
<MagnifyingGlassIcon />
|
||||
<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>
|
||||
<TraceEvent
|
||||
events={[Event.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>
|
||||
<Box display={{ sm: 'flex', md: 'none' }} color="placeholder" onClick={toggleOpen}>
|
||||
<ChevronLeftIcon />
|
||||
</Box>
|
||||
<Box
|
||||
as="input"
|
||||
placeholder={placeholderText}
|
||||
width={isOpen || phase1Flag === NftVariant.Enabled ? 'full' : '120'}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
!isOpen && toggleOpen()
|
||||
setSearchValue(event.target.value)
|
||||
}}
|
||||
className={styles.searchBarInput}
|
||||
value={searchValue}
|
||||
ref={inputRef}
|
||||
/>
|
||||
</Row>
|
||||
<Box display={{ sm: isOpen ? 'none' : 'flex', xl: 'none' }}>
|
||||
<NavIcon onClick={toggleOpen}>
|
||||
<NavMagnifyingGlassIcon width={28} height={28} />
|
||||
</NavIcon>
|
||||
</Box>
|
||||
{isOpen &&
|
||||
(debouncedSearchValue.length > 0 && (tokensAreLoading || collectionsAreLoading) ? (
|
||||
<SkeletonRow />
|
||||
) : (
|
||||
<SearchBarDropdown
|
||||
toggleOpen={toggleOpen}
|
||||
tokens={reducedTokens}
|
||||
collections={reducedCollections}
|
||||
hasInput={debouncedSearchValue.length > 0}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
{isOpen && <Overlay />}
|
||||
<NavIcon onClick={toggleOpen}>
|
||||
<NavMagnifyingGlassIcon />
|
||||
</NavIcon>
|
||||
</Trace>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
350
src/components/NavBar/SearchBarDropdown.tsx
Normal file
@@ -0,0 +1,350 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { NavBarSearchTypes, SectionName } from 'analytics/constants'
|
||||
import { useTrace } from 'analytics/Trace'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { subheadSmall } from 'nft/css/common.css'
|
||||
import { useSearchHistory } from 'nft/hooks'
|
||||
import { fetchTrendingCollections } from 'nft/queries'
|
||||
import { fetchTrendingTokens } from 'nft/queries/genie/TrendingTokensFetcher'
|
||||
import { FungibleToken, GenieCollection, TimePeriod, TrendingCollection } from 'nft/types'
|
||||
import { formatEthPrice } from 'nft/utils/currency'
|
||||
import { ReactNode, useEffect, useMemo, useState } from 'react'
|
||||
import { useQuery } from 'react-query'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { ClockIcon, TrendingArrow } from '../../nft/components/icons'
|
||||
import * as styles from './SearchBar.css'
|
||||
import { CollectionRow, SkeletonRow, TokenRow } from './SuggestionRow'
|
||||
|
||||
function isCollection(suggestion: GenieCollection | FungibleToken | TrendingCollection) {
|
||||
return (suggestion as FungibleToken).decimals === undefined
|
||||
}
|
||||
|
||||
interface SearchBarDropdownSectionProps {
|
||||
toggleOpen: () => void
|
||||
suggestions: (GenieCollection | FungibleToken)[]
|
||||
header: JSX.Element
|
||||
headerIcon?: JSX.Element
|
||||
hoveredIndex: number | undefined
|
||||
startingIndex: number
|
||||
setHoveredIndex: (index: number | undefined) => void
|
||||
isLoading?: boolean
|
||||
eventProperties: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const SearchBarDropdownSection = ({
|
||||
toggleOpen,
|
||||
suggestions,
|
||||
header,
|
||||
headerIcon = undefined,
|
||||
hoveredIndex,
|
||||
startingIndex,
|
||||
setHoveredIndex,
|
||||
isLoading,
|
||||
eventProperties,
|
||||
}: SearchBarDropdownSectionProps) => {
|
||||
return (
|
||||
<Column gap="12">
|
||||
<Row paddingX="16" paddingY="4" gap="8" color="grey300" className={subheadSmall} style={{ lineHeight: '20px' }}>
|
||||
{headerIcon ? headerIcon : null}
|
||||
<Box>{header}</Box>
|
||||
</Row>
|
||||
<Column gap="12">
|
||||
{suggestions.map((suggestion, index) =>
|
||||
isLoading ? (
|
||||
<SkeletonRow key={index} />
|
||||
) : isCollection(suggestion) ? (
|
||||
<CollectionRow
|
||||
key={suggestion.address}
|
||||
collection={suggestion as GenieCollection}
|
||||
isHovered={hoveredIndex === index + startingIndex}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
index={index + startingIndex}
|
||||
eventProperties={{
|
||||
position: index + startingIndex,
|
||||
selected_search_result_name: suggestion.name,
|
||||
selected_search_result_address: suggestion.address,
|
||||
...eventProperties,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<TokenRow
|
||||
key={suggestion.address}
|
||||
token={suggestion as FungibleToken}
|
||||
isHovered={hoveredIndex === index + startingIndex}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
index={index + startingIndex}
|
||||
eventProperties={{
|
||||
position: index + startingIndex,
|
||||
selected_search_result_name: suggestion.name,
|
||||
selected_search_result_address: suggestion.address,
|
||||
...eventProperties,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Column>
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
|
||||
interface SearchBarDropdownProps {
|
||||
toggleOpen: () => void
|
||||
tokens: FungibleToken[]
|
||||
collections: GenieCollection[]
|
||||
queryText: string
|
||||
hasInput: boolean
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export const SearchBarDropdown = ({
|
||||
toggleOpen,
|
||||
tokens,
|
||||
collections,
|
||||
queryText,
|
||||
hasInput,
|
||||
isLoading,
|
||||
}: SearchBarDropdownProps) => {
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(0)
|
||||
const { history: searchHistory, updateItem: updateSearchHistory } = useSearchHistory()
|
||||
const shortenedHistory = useMemo(() => searchHistory.slice(0, 2), [searchHistory])
|
||||
const { pathname } = useLocation()
|
||||
const isNFTPage = pathname.includes('/nfts')
|
||||
const isTokenPage = pathname.includes('/tokens')
|
||||
const phase1Flag = useNftFlag()
|
||||
const [resultsState, setResultsState] = useState<ReactNode>()
|
||||
|
||||
const { data: trendingCollectionResults, isLoading: trendingCollectionsAreLoading } = useQuery(
|
||||
['trendingCollections', 'eth', 'twenty_four_hours'],
|
||||
() => fetchTrendingCollections({ volumeType: 'eth', timePeriod: 'ONE_DAY' as TimePeriod, size: 3 })
|
||||
)
|
||||
|
||||
const trendingCollections = useMemo(
|
||||
() =>
|
||||
trendingCollectionResults
|
||||
? trendingCollectionResults
|
||||
.map((collection) => ({
|
||||
...collection,
|
||||
collectionAddress: collection.address,
|
||||
floorPrice: formatEthPrice(collection.floor?.toString()),
|
||||
stats: {
|
||||
total_supply: collection.totalSupply,
|
||||
one_day_change: collection.floorChange,
|
||||
floor_price: formatEthPrice(collection.floor?.toString()),
|
||||
},
|
||||
}))
|
||||
.slice(0, isNFTPage ? 3 : 2)
|
||||
: [...Array<GenieCollection>(isNFTPage ? 3 : 2)],
|
||||
[isNFTPage, trendingCollectionResults]
|
||||
)
|
||||
|
||||
const { data: trendingTokenResults, isLoading: trendingTokensAreLoading } = useQuery(
|
||||
['trendingTokens'],
|
||||
() => fetchTrendingTokens(4),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
}
|
||||
)
|
||||
useEffect(() => {
|
||||
trendingTokenResults?.forEach(updateSearchHistory)
|
||||
}, [trendingTokenResults, updateSearchHistory])
|
||||
|
||||
const trendingTokensLength = phase1Flag === NftVariant.Enabled ? (isTokenPage ? 3 : 2) : 4
|
||||
const trendingTokens = useMemo(
|
||||
() =>
|
||||
trendingTokenResults
|
||||
? trendingTokenResults.slice(0, trendingTokensLength)
|
||||
: [...Array<FungibleToken>(trendingTokensLength)],
|
||||
[trendingTokenResults, trendingTokensLength]
|
||||
)
|
||||
|
||||
const totalSuggestions = hasInput
|
||||
? tokens.length + collections.length
|
||||
: Math.min(shortenedHistory.length, 2) +
|
||||
(isNFTPage || !isTokenPage ? trendingCollections?.length ?? 0 : 0) +
|
||||
(isTokenPage || !isNFTPage ? trendingTokens?.length ?? 0 : 0)
|
||||
|
||||
// Navigate search results via arrow keys
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'ArrowUp') {
|
||||
event.preventDefault()
|
||||
if (!hoveredIndex) {
|
||||
setHoveredIndex(totalSuggestions - 1)
|
||||
} else {
|
||||
setHoveredIndex(hoveredIndex - 1)
|
||||
}
|
||||
} else if (event.key === 'ArrowDown') {
|
||||
event.preventDefault()
|
||||
if (hoveredIndex && hoveredIndex === totalSuggestions - 1) {
|
||||
setHoveredIndex(0)
|
||||
} else {
|
||||
setHoveredIndex((hoveredIndex ?? -1) + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', keyDownHandler)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyDownHandler)
|
||||
}
|
||||
}, [toggleOpen, hoveredIndex, totalSuggestions])
|
||||
|
||||
const hasVerifiedCollection = collections.some((collection) => collection.isVerified)
|
||||
const hasVerifiedToken = tokens.some((token) => token.onDefaultList)
|
||||
const showCollectionsFirst =
|
||||
(isNFTPage && (hasVerifiedCollection || !hasVerifiedToken)) ||
|
||||
(!isNFTPage && !hasVerifiedToken && hasVerifiedCollection)
|
||||
|
||||
const trace = useTrace({ section: SectionName.NAVBAR_SEARCH })
|
||||
|
||||
useEffect(() => {
|
||||
const eventProperties = { total_suggestions: totalSuggestions, query_text: queryText, ...trace }
|
||||
if (!isLoading) {
|
||||
const tokenSearchResults =
|
||||
tokens.length > 0 ? (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={showCollectionsFirst ? collections.length : 0}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={tokens}
|
||||
eventProperties={{
|
||||
suggestion_type: NavBarSearchTypes.TOKEN_SUGGESTION,
|
||||
...eventProperties,
|
||||
}}
|
||||
header={<Trans>Tokens</Trans>}
|
||||
/>
|
||||
) : (
|
||||
<Box className={styles.notFoundContainer}>
|
||||
<Trans>No tokens found.</Trans>
|
||||
</Box>
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
const currentState = () =>
|
||||
hasInput ? (
|
||||
// Empty or Up to 8 combined tokens and nfts
|
||||
<Column gap="20">
|
||||
{showCollectionsFirst ? (
|
||||
<>
|
||||
{collectionSearchResults}
|
||||
{tokenSearchResults}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{tokenSearchResults}
|
||||
{collectionSearchResults}
|
||||
</>
|
||||
)}
|
||||
</Column>
|
||||
) : (
|
||||
// Recent Searches, Trending Tokens, Trending Collections
|
||||
<Column gap="20">
|
||||
{shortenedHistory.length > 0 && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={0}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={shortenedHistory}
|
||||
eventProperties={{
|
||||
suggestion_type: NavBarSearchTypes.RECENT_SEARCH,
|
||||
...eventProperties,
|
||||
}}
|
||||
header={<Trans>Recent searches</Trans>}
|
||||
headerIcon={<ClockIcon />}
|
||||
/>
|
||||
)}
|
||||
{!isNFTPage && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={shortenedHistory.length}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={trendingTokens}
|
||||
eventProperties={{
|
||||
suggestion_type: NavBarSearchTypes.TOKEN_TRENDING,
|
||||
...eventProperties,
|
||||
}}
|
||||
header={<Trans>Popular tokens</Trans>}
|
||||
headerIcon={<TrendingArrow />}
|
||||
isLoading={trendingTokensAreLoading}
|
||||
/>
|
||||
)}
|
||||
{!isTokenPage && phase1Flag === NftVariant.Enabled && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={shortenedHistory.length + (isNFTPage ? 0 : trendingTokens?.length ?? 0)}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={trendingCollections as unknown as GenieCollection[]}
|
||||
eventProperties={{
|
||||
suggestion_type: NavBarSearchTypes.COLLECTION_TRENDING,
|
||||
...eventProperties,
|
||||
}}
|
||||
header={<Trans>Popular NFT collections</Trans>}
|
||||
headerIcon={<TrendingArrow />}
|
||||
isLoading={trendingCollectionsAreLoading}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
)
|
||||
|
||||
setResultsState(currentState)
|
||||
}
|
||||
}, [
|
||||
isLoading,
|
||||
tokens,
|
||||
collections,
|
||||
trendingCollections,
|
||||
trendingCollectionsAreLoading,
|
||||
trendingTokens,
|
||||
trendingTokensAreLoading,
|
||||
hoveredIndex,
|
||||
phase1Flag,
|
||||
toggleOpen,
|
||||
shortenedHistory,
|
||||
hasInput,
|
||||
isNFTPage,
|
||||
isTokenPage,
|
||||
showCollectionsFirst,
|
||||
queryText,
|
||||
totalSuggestions,
|
||||
trace,
|
||||
])
|
||||
|
||||
return (
|
||||
<Box className={styles.searchBarDropdown}>
|
||||
<Box opacity={isLoading ? '0.3' : '1'} transition="125">
|
||||
{resultsState}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
22
src/components/NavBar/ShoppingBag.css.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
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',
|
||||
},
|
||||
])
|
||||
47
src/components/NavBar/ShoppingBag.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { NavIcon } from 'components/NavBar/NavIcon'
|
||||
import * as styles from 'components/NavBar/ShoppingBag.css'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { BagIcon, HundredsOverflowIcon, TagIcon } from 'nft/components/icons'
|
||||
import { useBag, useSellAsset } from 'nft/hooks'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
export const ShoppingBag = () => {
|
||||
const itemsInBag = useBag((state) => state.itemsInBag)
|
||||
const sellAssets = useSellAsset((state) => state.sellAssets)
|
||||
const [bagQuantity, setBagQuantity] = useState(0)
|
||||
const [sellQuantity, setSellQuantity] = useState(0)
|
||||
const location = useLocation()
|
||||
|
||||
const toggleBag = useBag((s) => s.toggleBag)
|
||||
|
||||
useEffect(() => {
|
||||
setBagQuantity(itemsInBag.length)
|
||||
}, [itemsInBag])
|
||||
|
||||
useEffect(() => {
|
||||
setSellQuantity(sellAssets.length)
|
||||
}, [sellAssets])
|
||||
|
||||
const isProfilePage = location.pathname === '/nfts/profile'
|
||||
|
||||
return (
|
||||
<NavIcon onClick={toggleBag}>
|
||||
{isProfilePage ? (
|
||||
<>
|
||||
<TagIcon viewBox="0 0 20 20" width={24} height={24} />
|
||||
{sellQuantity ? (
|
||||
<Box className={styles.bagQuantity}>{sellQuantity > 99 ? <HundredsOverflowIcon /> : sellQuantity}</Box>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<BagIcon viewBox="0 0 20 20" width={24} height={24} />
|
||||
{bagQuantity ? (
|
||||
<Box className={styles.bagQuantity}>{bagQuantity > 99 ? <HundredsOverflowIcon /> : bagQuantity}</Box>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</NavIcon>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,16 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { EventName } from 'analytics/constants'
|
||||
import clsx from 'clsx'
|
||||
import { L2NetworkLogo, LogoContainer } from 'components/Tokens/TokenTable/TokenRow'
|
||||
import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
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'
|
||||
import { vars } from 'nft/css/sprinkles.css'
|
||||
import { useSearchHistory } from 'nft/hooks'
|
||||
import { FungibleToken, GenieCollection } from 'nft/types'
|
||||
@@ -9,8 +18,8 @@ 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 { VerifiedIcon } from '../../nft/components/icons'
|
||||
import * as styles from './SearchBar.css'
|
||||
|
||||
interface CollectionRowProps {
|
||||
@@ -19,9 +28,17 @@ interface CollectionRowProps {
|
||||
setHoveredIndex: (index: number | undefined) => void
|
||||
toggleOpen: () => void
|
||||
index: number
|
||||
eventProperties: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const CollectionRow = ({ collection, isHovered, setHoveredIndex, toggleOpen, index }: CollectionRowProps) => {
|
||||
export const CollectionRow = ({
|
||||
collection,
|
||||
isHovered,
|
||||
setHoveredIndex,
|
||||
toggleOpen,
|
||||
index,
|
||||
eventProperties,
|
||||
}: CollectionRowProps) => {
|
||||
const [brokenImage, setBrokenImage] = useState(false)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const addToSearchHistory = useSearchHistory(
|
||||
@@ -32,7 +49,8 @@ export const CollectionRow = ({ collection, isHovered, setHoveredIndex, toggleOp
|
||||
const handleClick = useCallback(() => {
|
||||
addToSearchHistory(collection)
|
||||
toggleOpen()
|
||||
}, [addToSearchHistory, collection, toggleOpen])
|
||||
sendAnalyticsEvent(EventName.NAVBAR_RESULT_SELECTED, { ...eventProperties })
|
||||
}, [addToSearchHistory, collection, toggleOpen, eventProperties])
|
||||
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
@@ -75,13 +93,13 @@ export const CollectionRow = ({ collection, isHovered, setHoveredIndex, toggleOp
|
||||
<Box className={styles.primaryText}>{collection.name}</Box>
|
||||
{collection.isVerified && <VerifiedIcon className={styles.suggestionIcon} />}
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>{putCommas(collection.stats.total_supply)} items</Box>
|
||||
<Box className={styles.secondaryText}>{putCommas(collection?.stats?.total_supply ?? 0)} items</Box>
|
||||
</Column>
|
||||
</Row>
|
||||
{collection.floorPrice ? (
|
||||
{collection.stats?.floor_price ? (
|
||||
<Column className={styles.suggestionSecondaryContainer}>
|
||||
<Row gap="4">
|
||||
<Box className={styles.primaryText}>{ethNumberStandardFormatter(collection.floorPrice)} ETH</Box>
|
||||
<Box className={styles.primaryText}>{ethNumberStandardFormatter(collection.stats?.floor_price)} ETH</Box>
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>Floor</Box>
|
||||
</Column>
|
||||
@@ -90,15 +108,25 @@ export const CollectionRow = ({ collection, isHovered, setHoveredIndex, toggleOp
|
||||
)
|
||||
}
|
||||
|
||||
function useBridgedAddress(token: FungibleToken): [string | undefined, number | undefined, string | undefined] {
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
const bridgedAddress = connectedChainId ? token.extensions?.bridgeInfo?.[connectedChainId]?.tokenAddress : undefined
|
||||
if (bridgedAddress && connectedChainId) {
|
||||
return [bridgedAddress, connectedChainId, getChainInfo(connectedChainId)?.circleLogoUrl]
|
||||
}
|
||||
return [undefined, undefined, undefined]
|
||||
}
|
||||
|
||||
interface TokenRowProps {
|
||||
token: FungibleToken
|
||||
isHovered: boolean
|
||||
setHoveredIndex: (index: number | undefined) => void
|
||||
toggleOpen: () => void
|
||||
index: number
|
||||
eventProperties: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index }: TokenRowProps) => {
|
||||
export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index, eventProperties }: TokenRowProps) => {
|
||||
const [brokenImage, setBrokenImage] = useState(false)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const addToSearchHistory = useSearchHistory(
|
||||
@@ -109,14 +137,17 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index
|
||||
const handleClick = useCallback(() => {
|
||||
addToSearchHistory(token)
|
||||
toggleOpen()
|
||||
}, [addToSearchHistory, toggleOpen, token])
|
||||
sendAnalyticsEvent(EventName.NAVBAR_RESULT_SELECTED, { ...eventProperties })
|
||||
}, [addToSearchHistory, toggleOpen, token, eventProperties])
|
||||
|
||||
const [bridgedAddress, bridgedChain, L2Icon] = useBridgedAddress(token)
|
||||
const tokenDetailsPath = getTokenDetailsURL(bridgedAddress ?? token.address, undefined, bridgedChain ?? token.chainId)
|
||||
// Close the modal on escape
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter' && isHovered) {
|
||||
event.preventDefault()
|
||||
navigate(`/tokens/${token.address}`)
|
||||
navigate(tokenDetailsPath)
|
||||
handleClick()
|
||||
}
|
||||
}
|
||||
@@ -124,11 +155,11 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyDownHandler)
|
||||
}
|
||||
}, [toggleOpen, isHovered, token, navigate, handleClick])
|
||||
}, [toggleOpen, isHovered, token, navigate, handleClick, tokenDetailsPath])
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={`/tokens/${token.address}`}
|
||||
to={tokenDetailsPath}
|
||||
onClick={handleClick}
|
||||
onMouseEnter={() => !isHovered && setHoveredIndex(index)}
|
||||
onMouseLeave={() => isHovered && setHoveredIndex(undefined)}
|
||||
@@ -137,21 +168,24 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index
|
||||
>
|
||||
<Row style={{ width: '65%' }}>
|
||||
{!brokenImage && token.logoURI ? (
|
||||
<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)}
|
||||
/>
|
||||
<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} />
|
||||
)}
|
||||
<Column className={styles.suggestionPrimaryContainer}>
|
||||
<Row gap="4" width="full">
|
||||
<Box className={styles.primaryText}>{token.name}</Box>
|
||||
{token.onDefaultList && <VerifiedIcon className={styles.suggestionIcon} />}
|
||||
<TokenSafetyIcon warning={checkWarning(token.address)} />
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>{token.symbol}</Box>
|
||||
</Column>
|
||||
@@ -160,7 +194,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index
|
||||
<Column className={styles.suggestionSecondaryContainer}>
|
||||
{token.priceUsd && (
|
||||
<Row gap="4">
|
||||
<Box className={styles.primaryText}>{ethNumberStandardFormatter(token.priceUsd, true)}</Box>
|
||||
<Box className={styles.primaryText}>{formatDollar({ num: token.priceUsd, isPrice: true })}</Box>
|
||||
</Row>
|
||||
)}
|
||||
{token.price24hChange && (
|
||||
@@ -175,13 +209,21 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index
|
||||
|
||||
export const SkeletonRow = () => {
|
||||
return (
|
||||
<Box className={styles.searchBarDropdown}>
|
||||
<Row className={styles.suggestionRow}>
|
||||
<Row>
|
||||
<Box className={styles.imageHolder} />
|
||||
<Box borderRadius="round" height="16" width="160" background="loading" />
|
||||
</Row>
|
||||
<Row className={styles.suggestionRow}>
|
||||
<Row width="full">
|
||||
<Box className={styles.imageHolder} />
|
||||
<Column gap="4" width="full">
|
||||
<Row justifyContent="space-between">
|
||||
<Box borderRadius="round" height="20" background="backgroundModule" style={{ width: '180px' }} />
|
||||
<Box borderRadius="round" height="20" width="48" background="backgroundModule" />
|
||||
</Row>
|
||||
|
||||
<Row justifyContent="space-between">
|
||||
<Box borderRadius="round" height="16" width="120" background="backgroundModule" />
|
||||
<Box borderRadius="round" height="16" width="48" background="backgroundModule" />
|
||||
</Row>
|
||||
</Column>
|
||||
</Row>
|
||||
</Box>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import Navbar from './Navbar'
|
||||
|
||||
export default Navbar
|
||||
@@ -1,16 +1,19 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Web3Status from 'components/Web3Status'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Row } from 'nft/components/Flex'
|
||||
import { UniIcon } from 'nft/components/icons'
|
||||
import { ReactNode } from 'react'
|
||||
import { NavLink, NavLinkProps, useLocation } from 'react-router-dom'
|
||||
|
||||
import { Box } from '../../nft/components/Box'
|
||||
import { Row } from '../../nft/components/Flex'
|
||||
import { UniIcon } from '../../nft/components/icons'
|
||||
import { ChainSwitcher } from './ChainSwitcher'
|
||||
import { ChainSelector } from './ChainSelector'
|
||||
import { MenuDropdown } from './MenuDropdown'
|
||||
import * as styles from './Navbar.css'
|
||||
import { SearchBar } from './SearchBar'
|
||||
import { ShoppingBag } from './ShoppingBag'
|
||||
import * as styles from './style.css'
|
||||
|
||||
interface MenuItemProps {
|
||||
href: string
|
||||
@@ -35,6 +38,8 @@ const MenuItem = ({ href, id, isActive, children }: MenuItemProps) => {
|
||||
const PageTabs = () => {
|
||||
const { pathname } = useLocation()
|
||||
const nftFlag = useNftFlag()
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
const chainName = chainIdToBackendName(connectedChainId)
|
||||
|
||||
const isPoolActive =
|
||||
pathname.startsWith('/pool') ||
|
||||
@@ -48,7 +53,7 @@ const PageTabs = () => {
|
||||
<MenuItem href="/swap" isActive={pathname.startsWith('/swap')}>
|
||||
<Trans>Swap</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem href="/tokens" isActive={pathname.startsWith('/tokens')}>
|
||||
<MenuItem href={`/tokens/${chainName.toLowerCase()}`} isActive={pathname.startsWith('/tokens')}>
|
||||
<Trans>Tokens</Trans>
|
||||
</MenuItem>
|
||||
{nftFlag === NftVariant.Enabled && (
|
||||
@@ -64,6 +69,9 @@ const PageTabs = () => {
|
||||
}
|
||||
|
||||
const Navbar = () => {
|
||||
const { pathname } = useLocation()
|
||||
const isNftPage = pathname.startsWith('/nfts')
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav className={styles.nav}>
|
||||
@@ -72,9 +80,11 @@ const Navbar = () => {
|
||||
<Box as="a" href="#/swap" className={styles.logoContainer}>
|
||||
<UniIcon width="48" height="48" className={styles.logo} />
|
||||
</Box>
|
||||
<Box display={{ sm: 'flex', lg: 'none' }}>
|
||||
<ChainSwitcher leftAlign={true} />
|
||||
</Box>
|
||||
{!isNftPage && (
|
||||
<Box display={{ sm: 'flex', lg: 'none' }}>
|
||||
<ChainSelector leftAlign={true} />
|
||||
</Box>
|
||||
)}
|
||||
<Row gap="8" display={{ sm: 'none', lg: 'flex' }}>
|
||||
<PageTabs />
|
||||
</Row>
|
||||
@@ -90,9 +100,12 @@ const Navbar = () => {
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<MenuDropdown />
|
||||
</Box>
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<ChainSwitcher />
|
||||
</Box>
|
||||
{isNftPage && <ShoppingBag />}
|
||||
{!isNftPage && (
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<ChainSelector />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Web3Status />
|
||||
</Row>
|
||||
@@ -10,11 +10,8 @@ export const nav = style([
|
||||
width: 'full',
|
||||
height: '72',
|
||||
zIndex: '2',
|
||||
background: 'white08',
|
||||
background: 'backgroundFloating',
|
||||
}),
|
||||
{
|
||||
backdropFilter: 'blur(24px)',
|
||||
},
|
||||
])
|
||||
|
||||
export const logoContainer = style([
|
||||
@@ -28,7 +25,7 @@ export const logoContainer = style([
|
||||
export const logo = style([
|
||||
sprinkles({
|
||||
display: 'block',
|
||||
color: 'blackBlue',
|
||||
color: 'textPrimary',
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -96,14 +93,14 @@ const baseMenuItem = style([
|
||||
export const menuItem = style([
|
||||
baseMenuItem,
|
||||
sprinkles({
|
||||
color: 'darkGray',
|
||||
color: 'textSecondary',
|
||||
}),
|
||||
])
|
||||
|
||||
export const activeMenuItem = style([
|
||||
baseMenuItem,
|
||||
sprinkles({
|
||||
color: 'blackBlue',
|
||||
color: 'textPrimary',
|
||||
background: 'backgroundFloating',
|
||||
}),
|
||||
])
|
||||
@@ -119,6 +116,6 @@ export const mobileBottomBar = style([
|
||||
paddingY: '4',
|
||||
paddingX: '8',
|
||||
height: '56',
|
||||
background: 'lightGray',
|
||||
background: 'backgroundSurface',
|
||||
}),
|
||||
])
|
||||
@@ -37,7 +37,7 @@ const RootWrapper = styled.div`
|
||||
|
||||
const SHOULD_SHOW_ALERT = {
|
||||
[SupportedChainId.OPTIMISM]: true,
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: true,
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: true,
|
||||
[SupportedChainId.ARBITRUM_ONE]: true,
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: true,
|
||||
[SupportedChainId.POLYGON]: true,
|
||||
@@ -62,7 +62,7 @@ const BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID: {
|
||||
'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(90, 190, 170, 0.15) 0%, rgba(80, 160, 40, 0.15) 100%)',
|
||||
[SupportedChainId.OPTIMISM]:
|
||||
'radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.01) 0%, rgba(255, 255, 255, 0.04) 100%),radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.01) 0%, rgba(235, 0, 255, 0.01) 96%)',
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]:
|
||||
[SupportedChainId.OPTIMISM_GOERLI]:
|
||||
'radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.04) 100%),radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.04) 0%, rgba(235, 0, 255, 0.01 96%)',
|
||||
[SupportedChainId.ARBITRUM_ONE]:
|
||||
'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.01) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.05) 0%, rgba(33, 114, 229, 0.05) 100%), hsla(0, 0%, 100%, 0.05)',
|
||||
@@ -80,7 +80,7 @@ const BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID: {
|
||||
'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(63, 208, 137, 0.15) 0%, rgba(49, 205, 50, 0.15) 100%)',
|
||||
[SupportedChainId.OPTIMISM]:
|
||||
'radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.1)',
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]:
|
||||
[SupportedChainId.OPTIMISM_GOERLI]:
|
||||
'radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.1)',
|
||||
[SupportedChainId.ARBITRUM_ONE]:
|
||||
'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(circle at top left, hsla(206, 50%, 75%, 0.01), hsla(215, 79%, 51%, 0.12)), hsla(0, 0%, 100%, 0.1)',
|
||||
@@ -142,7 +142,7 @@ const TEXT_COLORS: { [chainId in NetworkAlertChains]: string } = {
|
||||
[SupportedChainId.CELO]: 'rgba(53, 178, 97)',
|
||||
[SupportedChainId.CELO_ALFAJORES]: 'rgba(53, 178, 97)',
|
||||
[SupportedChainId.OPTIMISM]: '#ff3856',
|
||||
[SupportedChainId.OPTIMISTIC_KOVAN]: '#ff3856',
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: '#ff3856',
|
||||
[SupportedChainId.ARBITRUM_ONE]: '#0490ed',
|
||||
[SupportedChainId.ARBITRUM_RINKEBY]: '#0490ed',
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { escapeRegExp } from '../../utils'
|
||||
|
||||
const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: string; redesignFlag: boolean }>`
|
||||
const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: string }>`
|
||||
color: ${({ error, theme }) => (error ? theme.deprecated_red1 : theme.deprecated_text1)};
|
||||
width: 0;
|
||||
position: relative;
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? 400 : 500)};
|
||||
font-weight: 400;
|
||||
outline: none;
|
||||
border: none;
|
||||
flex: 1 1 auto;
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg1)};
|
||||
background-color: transparent;
|
||||
font-size: ${({ fontSize }) => fontSize ?? '28px'};
|
||||
text-align: ${({ align }) => align && align};
|
||||
white-space: nowrap;
|
||||
@@ -36,7 +35,7 @@ const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: s
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textSecondary : theme.deprecated_text4)};
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -56,8 +55,6 @@ export const Input = React.memo(function InnerInput({
|
||||
align?: 'right' | 'left'
|
||||
prependSymbol?: string | undefined
|
||||
} & Omit<React.HTMLProps<HTMLInputElement>, 'ref' | 'onChange' | 'as'>) {
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
const enforcer = (nextUserInput: string) => {
|
||||
if (nextUserInput === '' || inputRegex.test(escapeRegExp(nextUserInput))) {
|
||||
onUserInput(nextUserInput)
|
||||
@@ -68,7 +65,6 @@ export const Input = React.memo(function InnerInput({
|
||||
<StyledInput
|
||||
{...rest}
|
||||
value={prependSymbol && value ? prependSymbol + value : value}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
onChange={(event) => {
|
||||
if (prependSymbol) {
|
||||
const value = event.target.value
|
||||
@@ -91,7 +87,7 @@ export const Input = React.memo(function InnerInput({
|
||||
// text-specific options
|
||||
type="text"
|
||||
pattern="^[0-9]*[.,]?[0-9]*$"
|
||||
placeholder={placeholder || (redesignFlagEnabled ? '0' : '0.0')}
|
||||
placeholder={placeholder || '0'}
|
||||
minLength={1}
|
||||
maxLength={79}
|
||||
spellCheck="false"
|
||||
|
||||
@@ -2,16 +2,19 @@ import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfoOrDefault, L2ChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
|
||||
const BodyRow = styled.div`
|
||||
color: ${({ theme }) => theme.deprecated_black};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
`
|
||||
const CautionIcon = styled(AlertOctagon)`
|
||||
color: ${({ theme }) => theme.deprecated_black};
|
||||
const CautionTriangle = styled(AlertTriangle)`
|
||||
color: ${({ theme }) => theme.accentWarning};
|
||||
`
|
||||
const Link = styled(ExternalLink)`
|
||||
color: ${({ theme }) => theme.deprecated_black};
|
||||
@@ -24,20 +27,21 @@ const TitleRow = styled.div`
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
const TitleText = styled.div`
|
||||
color: black;
|
||||
font-weight: 600;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
line-height: 24px;
|
||||
margin: 0px 12px;
|
||||
`
|
||||
const Wrapper = styled.div`
|
||||
background-color: ${({ theme }) => theme.deprecated_yellow3};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
bottom: 60px;
|
||||
display: none;
|
||||
max-width: 348px;
|
||||
padding: 16px 20px;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToMedium}px) {
|
||||
display: block;
|
||||
@@ -52,7 +56,7 @@ export function ChainConnectivityWarning() {
|
||||
return (
|
||||
<Wrapper>
|
||||
<TitleRow>
|
||||
<CautionIcon />
|
||||
<CautionTriangle />
|
||||
<TitleText>
|
||||
<Trans>Network Warning</Trans>
|
||||
</TitleText>
|
||||
@@ -61,7 +65,7 @@ export function ChainConnectivityWarning() {
|
||||
{chainId === SupportedChainId.MAINNET ? (
|
||||
<Trans>You may have lost your network connection.</Trans>
|
||||
) : (
|
||||
<Trans>You may have lost your network connection, or {label} might be down right now.</Trans>
|
||||
<Trans>{label} might be down right now, or you may have lost your network connection.</Trans>
|
||||
)}{' '}
|
||||
{(info as L2ChainInfo).statusPage !== undefined && (
|
||||
<span>
|
||||
@@ -4,9 +4,10 @@ import useInterval from 'lib/hooks/useInterval'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { usePopper } from 'react-popper'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
const PopoverContainer = styled.div<{ show: boolean }>`
|
||||
z-index: 9999;
|
||||
z-index: ${Z_INDEX.popover};
|
||||
visibility: ${(props) => (props.show ? 'visible' : 'hidden')};
|
||||
opacity: ${(props) => (props.show ? 1 : 0)};
|
||||
transition: visibility 150ms linear, opacity 150ms linear;
|
||||
@@ -15,6 +16,7 @@ const PopoverContainer = styled.div<{ show: boolean }>`
|
||||
|
||||
const ReferenceElement = styled.div`
|
||||
display: inline-block;
|
||||
height: inherit;
|
||||
`
|
||||
|
||||
const Arrow = styled.div`
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { animated } from 'react-spring'
|
||||
import { useSpring } from 'react-spring/web'
|
||||
import { useSpring } from 'react-spring'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
import { PopupContent } from '../../state/application/reducer'
|
||||
import FailedNetworkSwitchPopup from './FailedNetworkSwitchPopup'
|
||||
import TransactionPopup from './TransactionPopup'
|
||||
|
||||
const StyledClose = styled(X)`
|
||||
position: absolute;
|
||||
@@ -58,7 +56,6 @@ export default function PopupItem({
|
||||
popKey: string
|
||||
}) {
|
||||
const removePopup = useRemovePopup()
|
||||
const navbarFlag = useNavBarFlag()
|
||||
const removeThisPopup = useCallback(() => removePopup(popKey), [popKey, removePopup])
|
||||
useEffect(() => {
|
||||
if (removeAfterMs === null) return undefined
|
||||
@@ -80,22 +77,15 @@ export default function PopupItem({
|
||||
})
|
||||
|
||||
let popupContent
|
||||
if ('txn' in content) {
|
||||
const {
|
||||
txn: { hash },
|
||||
} = content
|
||||
if (navbarFlag === NavBarVariant.Enabled) return null
|
||||
|
||||
popupContent = <TransactionPopup hash={hash} />
|
||||
} else if ('failedSwitchNetwork' in content) {
|
||||
if ('failedSwitchNetwork' in content) {
|
||||
popupContent = <FailedNetworkSwitchPopup chainId={content.failedSwitchNetwork} />
|
||||
}
|
||||
|
||||
return (
|
||||
return popupContent ? (
|
||||
<Popup>
|
||||
<StyledClose color={theme.deprecated_text2} onClick={removeThisPopup} />
|
||||
{popupContent}
|
||||
{removeAfterMs !== null ? <AnimatedFader style={faderStyle} /> : null}
|
||||
</Popup>
|
||||
)
|
||||
) : null
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import { useEffect } from 'react'
|
||||
import { MessageCircle, X } from 'react-feather'
|
||||
import { useShowSurveyPopup } from 'state/user/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText, Z_INDEX } from 'theme'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
import BGImage from '../../assets/images/survey-orb.svg'
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ const StopOverflowQuery = `@media screen and (min-width: ${MEDIA_WIDTHS.deprecat
|
||||
|
||||
const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean; xlPadding: boolean }>`
|
||||
position: fixed;
|
||||
top: ${({ extraPadding }) => (extraPadding ? '64px' : '56px')};
|
||||
top: ${({ extraPadding }) => (extraPadding ? '72px' : '64px')};
|
||||
right: 1rem;
|
||||
max-width: 355px !important;
|
||||
width: 100%;
|
||||
@@ -52,7 +52,7 @@ const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean; xlPadding:
|
||||
`};
|
||||
|
||||
${StopOverflowQuery} {
|
||||
top: ${({ extraPadding, xlPadding }) => (xlPadding ? '64px' : extraPadding ? '64px' : '56px')};
|
||||
top: ${({ extraPadding, xlPadding }) => (xlPadding ? '72px' : extraPadding ? '72px' : '64px')};
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import PositionListItem from 'components/PositionListItem'
|
||||
import Toggle from 'components/Toggle'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { MEDIA_WIDTHS } from 'theme'
|
||||
@@ -10,7 +9,8 @@ const DesktopHeader = styled.div`
|
||||
display: none;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
padding: 8px;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
align-items: center;
|
||||
@@ -25,12 +25,14 @@ const DesktopHeader = styled.div`
|
||||
|
||||
const MobileHeader = styled.div`
|
||||
font-weight: medium;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
padding: 8px;
|
||||
font-weight: 500;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
display: none;
|
||||
@@ -38,8 +40,8 @@ const MobileHeader = styled.div`
|
||||
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.deprecated_upToExtraSmall}px) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -49,16 +51,12 @@ const ToggleWrap = styled.div`
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const ToggleLabel = styled.div`
|
||||
opacity: 0.6;
|
||||
margin-right: 10px;
|
||||
`
|
||||
|
||||
const MobileTogglePosition = styled.div`
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.deprecated_upToExtraSmall}px) {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
}
|
||||
const ToggleLabel = styled.button`
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
font-size: 1rem;
|
||||
`
|
||||
|
||||
type PositionListProps = React.PropsWithChildren<{
|
||||
@@ -79,34 +77,26 @@ export default function PositionList({
|
||||
<Trans>Your positions</Trans>
|
||||
{positions && ' (' + positions.length + ')'}
|
||||
</div>
|
||||
<ToggleWrap>
|
||||
<ToggleLabel>
|
||||
<Trans>Show closed positions</Trans>
|
||||
</ToggleLabel>
|
||||
<Toggle
|
||||
id="desktop-hide-closed-positions"
|
||||
isActive={!userHideClosedPositions}
|
||||
toggle={() => {
|
||||
setUserHideClosedPositions(!userHideClosedPositions)
|
||||
}}
|
||||
/>
|
||||
</ToggleWrap>
|
||||
|
||||
<ToggleLabel
|
||||
id="desktop-hide-closed-positions"
|
||||
onClick={() => {
|
||||
setUserHideClosedPositions(!userHideClosedPositions)
|
||||
}}
|
||||
>
|
||||
{userHideClosedPositions ? <Trans>Show closed positions</Trans> : <Trans>Hide closed positions</Trans>}
|
||||
</ToggleLabel>
|
||||
</DesktopHeader>
|
||||
<MobileHeader>
|
||||
<Trans>Your positions</Trans>
|
||||
<ToggleWrap>
|
||||
<ToggleLabel>
|
||||
<Trans>Show closed positions</Trans>
|
||||
<ToggleLabel
|
||||
onClick={() => {
|
||||
setUserHideClosedPositions(!userHideClosedPositions)
|
||||
}}
|
||||
>
|
||||
{userHideClosedPositions ? <Trans>Show closed positions</Trans> : <Trans>Hide closed positions</Trans>}
|
||||
</ToggleLabel>
|
||||
<MobileTogglePosition>
|
||||
<Toggle
|
||||
id="mobile-hide-closed-positions"
|
||||
isActive={!userHideClosedPositions}
|
||||
toggle={() => {
|
||||
setUserHideClosedPositions(!userHideClosedPositions)
|
||||
}}
|
||||
/>
|
||||
</MobileTogglePosition>
|
||||
</ToggleWrap>
|
||||
</MobileHeader>
|
||||
{positions.map((p) => {
|
||||
|
||||
@@ -23,29 +23,22 @@ import { DAI, USDC_MAINNET, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../co
|
||||
|
||||
const LinkRow = styled(Link)`
|
||||
align-items: center;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
justify-content: space-between;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
margin: 8px 0;
|
||||
padding: 16px;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
|
||||
&:last-of-type {
|
||||
margin: 8px 0 0 0;
|
||||
}
|
||||
& > div:not(:first-child) {
|
||||
text-align: center;
|
||||
}
|
||||
:hover {
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
background-color: ${({ theme }) => theme.hoverDefault};
|
||||
}
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
@@ -54,7 +47,7 @@ const LinkRow = styled(Link)`
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
flex-direction: column;
|
||||
row-gap: 12px;
|
||||
row-gap: 8px;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -74,29 +67,17 @@ const RangeLineItem = styled(DataLineItem)`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
border-radius: 12px;
|
||||
padding: 8px 0;
|
||||
`};
|
||||
`
|
||||
|
||||
const DoubleArrow = styled.span`
|
||||
margin: 0 2px;
|
||||
color: ${({ theme }) => theme.deprecated_text3};
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
margin: 4px;
|
||||
padding: 20px;
|
||||
`};
|
||||
`
|
||||
|
||||
const RangeText = styled.span`
|
||||
/* background-color: ${({ theme }) => theme.deprecated_bg2}; */
|
||||
padding: 0.25rem 0.5rem;
|
||||
padding: 0.25rem 0.25rem;
|
||||
border-radius: 8px;
|
||||
`
|
||||
|
||||
@@ -123,7 +104,7 @@ const DataText = styled.div`
|
||||
font-size: 18px;
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
font-size: 14px;
|
||||
font-size: 18px;
|
||||
`};
|
||||
`
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { HelpCircle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import Tooltip from '../Tooltip'
|
||||
|
||||
const QuestionWrapper = styled.div<{ redesignFlag: boolean }>`
|
||||
const QuestionWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -18,8 +17,7 @@ const QuestionWrapper = styled.div<{ redesignFlag: boolean }>`
|
||||
cursor: default;
|
||||
border-radius: 36px;
|
||||
font-size: 12px;
|
||||
border-radius: ${({ redesignFlag }) => redesignFlag && '12px'};
|
||||
color: ${({ theme, redesignFlag }) => !redesignFlag && theme.deprecated_text2};
|
||||
border-radius: 12px;
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
@@ -27,12 +25,12 @@ const QuestionWrapper = styled.div<{ redesignFlag: boolean }>`
|
||||
}
|
||||
`
|
||||
|
||||
const QuestionMark = styled.span<{ redesignFlag?: boolean }>`
|
||||
const QuestionMark = styled.span`
|
||||
font-size: 14px;
|
||||
margin-left: ${({ redesignFlag }) => redesignFlag && '8px'};
|
||||
align-items: ${({ redesignFlag }) => redesignFlag && 'center'};
|
||||
color: ${({ theme, redesignFlag }) => redesignFlag && theme.textSecondary};
|
||||
margin-top: ${({ redesignFlag }) => redesignFlag && '2.5px'};
|
||||
margin-left: 8px;
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
margin-top: 2.5px;
|
||||
`
|
||||
|
||||
export default function QuestionHelper({ text }: { text: ReactNode; size?: number }) {
|
||||
@@ -40,14 +38,12 @@ export default function QuestionHelper({ text }: { text: ReactNode; size?: numbe
|
||||
|
||||
const open = useCallback(() => setShow(true), [setShow])
|
||||
const close = useCallback(() => setShow(false), [setShow])
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
return (
|
||||
<span style={{ marginLeft: 4, display: 'flex', alignItems: 'center' }}>
|
||||
<Tooltip text={text} show={show}>
|
||||
<QuestionWrapper onClick={open} onMouseEnter={open} onMouseLeave={close} redesignFlag={redesignFlagEnabled}>
|
||||
<QuestionMark redesignFlag={redesignFlagEnabled}>
|
||||
{redesignFlagEnabled ? <HelpCircle size={16}></HelpCircle> : '?'}
|
||||
<QuestionWrapper onClick={open} onMouseEnter={open} onMouseLeave={close}>
|
||||
<QuestionMark>
|
||||
<HelpCircle size={16} />
|
||||
</QuestionMark>
|
||||
</QuestionWrapper>
|
||||
</Tooltip>
|
||||
|
||||
@@ -10,7 +10,8 @@ import { RoutingDiagramEntry } from 'components/swap/SwapRoute'
|
||||
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
|
||||
import { Box } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText, Z_INDEX } from 'theme'
|
||||
import { ThemedText } from 'theme'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
import { ReactComponent as DotLine } from '../../assets/svg/dot_line.svg'
|
||||
import { MouseoverTooltip } from '../Tooltip'
|
||||
|
||||
@@ -7,7 +7,7 @@ exports[`renders multi route 1`] = `
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
background-color: #EDEEF2;
|
||||
background-color: #E8ECFB;
|
||||
border: unset;
|
||||
border-radius: 0.5rem;
|
||||
color: #000;
|
||||
@@ -110,11 +110,11 @@ exports[`renders multi route 1`] = `
|
||||
}
|
||||
|
||||
.c6 path {
|
||||
stroke: #888D9B;
|
||||
stroke: #98A1C0;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
background-color: #EDEEF2;
|
||||
background-color: #E8ECFB;
|
||||
border-radius: 8px;
|
||||
display: grid;
|
||||
font-size: 12px;
|
||||
@@ -129,9 +129,9 @@ exports[`renders multi route 1`] = `
|
||||
}
|
||||
|
||||
.c9 {
|
||||
background-color: #CED0D9;
|
||||
background-color: #B8C0DC;
|
||||
border-radius: 4px;
|
||||
color: #565A69;
|
||||
color: #7780A0;
|
||||
font-size: 10px;
|
||||
padding: 2px 4px;
|
||||
z-index: 1021;
|
||||
@@ -245,7 +245,7 @@ exports[`renders single route 1`] = `
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
background-color: #EDEEF2;
|
||||
background-color: #E8ECFB;
|
||||
border: unset;
|
||||
border-radius: 0.5rem;
|
||||
color: #000;
|
||||
@@ -348,11 +348,11 @@ exports[`renders single route 1`] = `
|
||||
}
|
||||
|
||||
.c6 path {
|
||||
stroke: #888D9B;
|
||||
stroke: #98A1C0;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
background-color: #EDEEF2;
|
||||
background-color: #E8ECFB;
|
||||
border-radius: 8px;
|
||||
display: grid;
|
||||
font-size: 12px;
|
||||
@@ -367,9 +367,9 @@ exports[`renders single route 1`] = `
|
||||
}
|
||||
|
||||
.c9 {
|
||||
background-color: #CED0D9;
|
||||
background-color: #B8C0DC;
|
||||
border-radius: 4px;
|
||||
color: #565A69;
|
||||
color: #7780A0;
|
||||
font-size: 10px;
|
||||
padding: 2px 4px;
|
||||
z-index: 1021;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import { getTokenAddress } from 'components/AmplitudeAnalytics/utils'
|
||||
import { ElementName, Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import { getTokenAddress } from 'analytics/utils'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { COMMON_BASES } from 'constants/routing'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -18,17 +17,9 @@ const MobileWrapper = styled(AutoColumn)`
|
||||
`};
|
||||
`
|
||||
|
||||
const BaseWrapper = styled.div<{ disable?: boolean; redesignFlag?: boolean }>`
|
||||
border: 1px solid
|
||||
${({ theme, disable, redesignFlag }) =>
|
||||
disable
|
||||
? redesignFlag
|
||||
? theme.accentAction
|
||||
: 'transparent'
|
||||
: redesignFlag
|
||||
? theme.backgroundOutline
|
||||
: theme.deprecated_bg3};
|
||||
border-radius: ${({ redesignFlag }) => (redesignFlag ? '16px' : '10px')};
|
||||
const BaseWrapper = styled.div<{ disable?: boolean }>`
|
||||
border: 1px solid ${({ theme, disable }) => (disable ? theme.accentAction : theme.backgroundOutline)};
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
padding: 6px;
|
||||
padding-right: 12px;
|
||||
@@ -36,15 +27,11 @@ const BaseWrapper = styled.div<{ disable?: boolean; redesignFlag?: boolean }>`
|
||||
align-items: center;
|
||||
:hover {
|
||||
cursor: ${({ disable }) => !disable && 'pointer'};
|
||||
background-color: ${({ theme, disable, redesignFlag }) =>
|
||||
(redesignFlag && theme.hoverDefault) || (!disable && theme.deprecated_bg2)};
|
||||
background-color: ${({ theme }) => theme.hoverDefault};
|
||||
}
|
||||
|
||||
color: ${({ theme, disable, redesignFlag }) =>
|
||||
disable && (redesignFlag ? theme.accentAction : theme.deprecated_text3)};
|
||||
background-color: ${({ theme, disable, redesignFlag }) =>
|
||||
disable && (redesignFlag ? theme.accentActionSoft : theme.deprecated_bg3)};
|
||||
filter: ${({ disable, redesignFlag }) => disable && !redesignFlag && 'grayscale(1)'};
|
||||
color: ${({ theme, disable }) => disable && theme.accentAction};
|
||||
background-color: ${({ theme, disable }) => disable && theme.accentActionSoft};
|
||||
`
|
||||
|
||||
const formatAnalyticsEventProperties = (currency: Currency, searchQuery: string, isAddressSearch: string | false) => ({
|
||||
@@ -73,8 +60,6 @@ export default function CommonBases({
|
||||
isAddressSearch: string | false
|
||||
}) {
|
||||
const bases = typeof chainId !== 'undefined' ? COMMON_BASES[chainId] ?? [] : []
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
|
||||
return bases.length > 0 ? (
|
||||
<MobileWrapper gap="md">
|
||||
@@ -95,7 +80,6 @@ export default function CommonBases({
|
||||
onKeyPress={(e) => !isSelected && e.key === 'Enter' && onSelect(currency)}
|
||||
onClick={() => !isSelected && onSelect(currency)}
|
||||
disable={isSelected}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
key={currencyId(currency)}
|
||||
>
|
||||
<CurrencyLogoFromList currency={currency} />
|
||||
|
||||
@@ -2,14 +2,26 @@
|
||||
|
||||
exports[`renders currency rows correctly when currencies list is non-empty 1`] = `
|
||||
<DocumentFragment>
|
||||
.c7 {
|
||||
color: #6E727D;
|
||||
.c9 {
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
.c7 {
|
||||
margin-left: 4px;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
@@ -31,6 +43,12 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
grid-auto-rows: auto;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
@@ -55,7 +73,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
.c10 {
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
@@ -72,7 +90,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
}
|
||||
|
||||
.c3:hover {
|
||||
background-color: #EDEEF2;
|
||||
background-color: #B8C0DC14;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
@@ -100,6 +118,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
@@ -110,9 +129,41 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
>
|
||||
Dai Stablecoin
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<svg
|
||||
class="c8"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c7 css-1j6a53a"
|
||||
class="c9 css-1j6a53a"
|
||||
>
|
||||
DAI
|
||||
</div>
|
||||
@@ -121,7 +172,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c8"
|
||||
class="c0 c1 c10"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
@@ -138,6 +189,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
@@ -148,9 +200,41 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
>
|
||||
USD//C
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<svg
|
||||
class="c8"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c7 css-1j6a53a"
|
||||
class="c9 css-1j6a53a"
|
||||
>
|
||||
USDC
|
||||
</div>
|
||||
@@ -159,7 +243,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c8"
|
||||
class="c0 c1 c10"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
@@ -176,6 +260,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
@@ -186,9 +271,41 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
>
|
||||
Wrapped BTC
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<svg
|
||||
class="c8"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c7 css-1j6a53a"
|
||||
class="c9 css-1j6a53a"
|
||||
>
|
||||
WBTC
|
||||
</div>
|
||||
@@ -197,7 +314,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c8"
|
||||
class="c0 c1 c10"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
@@ -209,12 +326,74 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
|
||||
exports[`renders loading rows when isLoading is true 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
.c0 {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.c0 > div {
|
||||
-webkit-animation: fAQEyV 1.5s infinite;
|
||||
animation: fAQEyV 1.5s infinite;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
background: linear-gradient( to left,#F5F6FC 25%,#E8ECFB 50%,#F5F6FC 75% );
|
||||
background-size: 400%;
|
||||
border-radius: 12px;
|
||||
height: 2.4em;
|
||||
will-change: background-position;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
grid-column-gap: 0.5em;
|
||||
grid-template-columns: repeat(12,1fr);
|
||||
max-width: 960px;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
|
||||
.c1 > div:nth-child(4n + 1) {
|
||||
grid-column: 1 / 8;
|
||||
height: 1em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.c1 > div:nth-child(4n + 2) {
|
||||
grid-column: 12;
|
||||
height: 1em;
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.c1 > div:nth-child(4n + 3) {
|
||||
grid-column: 1 / 4;
|
||||
height: 0.75em;
|
||||
}
|
||||
|
||||
<div
|
||||
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||
>
|
||||
<div
|
||||
style="height: 0px; width: 100%;"
|
||||
/>
|
||||
style="height: 560px; width: 100%;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
@@ -1,33 +1,25 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import { LightGreyCard } from 'components/Card'
|
||||
import QuestionHelper from 'components/QuestionHelper'
|
||||
import { ElementName, Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import { checkWarning } from 'constants/tokenSafety'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { TokenSafetyVariant, useTokenSafetyFlag } from 'featureFlags/flags/tokenSafety'
|
||||
import { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'
|
||||
import { XOctagon } from 'react-feather'
|
||||
import { Check } from 'react-feather'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import TokenListLogo from '../../../assets/svg/tokenlist.svg'
|
||||
import { useIsUserAddedToken } from '../../../hooks/Tokens'
|
||||
import { useCurrencyBalance } from '../../../state/connection/hooks'
|
||||
import { useCombinedActiveList } from '../../../state/lists/hooks'
|
||||
import { WrappedTokenInfo } from '../../../state/lists/wrappedTokenInfo'
|
||||
import { ThemedText } from '../../../theme'
|
||||
import { isTokenOnList } from '../../../utils'
|
||||
import Column, { AutoColumn } from '../../Column'
|
||||
import CurrencyLogo from '../../CurrencyLogo'
|
||||
import Loader from '../../Loader'
|
||||
import Row, { RowBetween, RowFixed } from '../../Row'
|
||||
import Row, { RowFixed } from '../../Row'
|
||||
import { MouseoverTooltip } from '../../Tooltip'
|
||||
import ImportRow from '../ImportRow'
|
||||
import { LoadingRows, MenuItem } from '../styleds'
|
||||
|
||||
function currencyKey(currency: Currency): string {
|
||||
@@ -69,13 +61,12 @@ const Tag = styled.div`
|
||||
margin-right: 4px;
|
||||
`
|
||||
|
||||
const FixedContentRow = styled.div`
|
||||
padding: 4px 20px;
|
||||
height: 56px;
|
||||
display: grid;
|
||||
grid-gap: 16px;
|
||||
align-items: center;
|
||||
export const BlockedTokenIcon = styled(XOctagon)<{ size?: string }>`
|
||||
margin-left: 0.3em;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
`
|
||||
|
||||
function Balance({ balance }: { balance: CurrencyAmount<Currency> }) {
|
||||
return <StyledBalanceText title={balance.toExact()}>{balance.toSignificant(4)}</StyledBalanceText>
|
||||
}
|
||||
@@ -85,10 +76,6 @@ const TagContainer = styled.div`
|
||||
justify-content: flex-end;
|
||||
`
|
||||
|
||||
const TokenListLogoWrapper = styled.img`
|
||||
height: 20px;
|
||||
`
|
||||
|
||||
function TokenTags({ currency }: { currency: Currency }) {
|
||||
if (!(currency instanceof WrappedTokenInfo)) {
|
||||
return null
|
||||
@@ -118,7 +105,7 @@ function TokenTags({ currency }: { currency: Currency }) {
|
||||
)
|
||||
}
|
||||
|
||||
function CurrencyRow({
|
||||
export function CurrencyRow({
|
||||
currency,
|
||||
onSelect,
|
||||
isSelected,
|
||||
@@ -131,20 +118,17 @@ function CurrencyRow({
|
||||
onSelect: (hasWarning: boolean) => void
|
||||
isSelected: boolean
|
||||
otherSelected: boolean
|
||||
style: CSSProperties
|
||||
style?: CSSProperties
|
||||
showCurrencyAmount?: boolean
|
||||
eventProperties: Record<string, unknown>
|
||||
}) {
|
||||
const { account } = useWeb3React()
|
||||
const key = currencyKey(currency)
|
||||
const selectedTokenList = useCombinedActiveList()
|
||||
const isOnSelectedList = isTokenOnList(selectedTokenList, currency.isToken ? currency : undefined)
|
||||
const customAdded = useIsUserAddedToken(currency)
|
||||
const balance = useCurrencyBalance(account ?? undefined, currency)
|
||||
const warning = currency.isNative ? null : checkWarning(currency.address)
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
const tokenSafetyFlag = useTokenSafetyFlag()
|
||||
const isBlockedToken = !!warning && !warning.canProceed
|
||||
const blockedTokenOpacity = '0.6'
|
||||
|
||||
// only show add or remove buttons if not on selected list
|
||||
return (
|
||||
@@ -156,29 +140,29 @@ function CurrencyRow({
|
||||
>
|
||||
<MenuItem
|
||||
tabIndex={0}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
style={style}
|
||||
className={`token-item-${key}`}
|
||||
onKeyPress={(e) => (!isSelected && e.key === 'Enter' ? onSelect(!!warning) : null)}
|
||||
onClick={() => (isSelected ? null : onSelect(!!warning))}
|
||||
disabled={isSelected}
|
||||
selected={otherSelected}
|
||||
dim={isBlockedToken}
|
||||
>
|
||||
<Column>
|
||||
<CurrencyLogo currency={currency} size={'36px'} />
|
||||
<CurrencyLogo
|
||||
currency={currency}
|
||||
size={'36px'}
|
||||
style={{ opacity: isBlockedToken ? blockedTokenOpacity : '1' }}
|
||||
/>
|
||||
</Column>
|
||||
<AutoColumn>
|
||||
<AutoColumn style={{ opacity: isBlockedToken ? blockedTokenOpacity : '1' }}>
|
||||
<Row>
|
||||
<CurrencyName title={currency.name}>{currency.name}</CurrencyName>
|
||||
|
||||
{tokenSafetyFlag === TokenSafetyVariant.Enabled && <TokenSafetyIcon warning={warning} />}
|
||||
<TokenSafetyIcon warning={warning} />
|
||||
{isBlockedToken && <BlockedTokenIcon />}
|
||||
</Row>
|
||||
<ThemedText.DeprecatedDarkGray ml="0px" fontSize={'12px'} fontWeight={300}>
|
||||
{!currency.isNative && !isOnSelectedList && customAdded ? (
|
||||
<Trans>{currency.symbol} • Added by user</Trans>
|
||||
) : (
|
||||
currency.symbol
|
||||
)}
|
||||
{currency.symbol}
|
||||
</ThemedText.DeprecatedDarkGray>
|
||||
</AutoColumn>
|
||||
<Column>
|
||||
@@ -189,10 +173,9 @@ function CurrencyRow({
|
||||
{showCurrencyAmount ? (
|
||||
<RowFixed style={{ justifySelf: 'flex-end' }}>
|
||||
{balance ? <Balance balance={balance} /> : account ? <Loader /> : null}
|
||||
{redesignFlagEnabled && isSelected && <CheckIcon />}
|
||||
{isSelected && <CheckIcon />}
|
||||
</RowFixed>
|
||||
) : (
|
||||
redesignFlagEnabled &&
|
||||
isSelected && (
|
||||
<RowFixed style={{ justifySelf: 'flex-end' }}>
|
||||
<CheckIcon />
|
||||
@@ -204,44 +187,13 @@ function CurrencyRow({
|
||||
)
|
||||
}
|
||||
|
||||
const BREAK_LINE = 'BREAK'
|
||||
type BreakLine = typeof BREAK_LINE
|
||||
function isBreakLine(x: unknown): x is BreakLine {
|
||||
return x === BREAK_LINE
|
||||
}
|
||||
|
||||
function BreakLineComponent({ style }: { style: CSSProperties }) {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<FixedContentRow style={style}>
|
||||
<LightGreyCard padding="8px 12px" $borderRadius="8px">
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<TokenListLogoWrapper src={TokenListLogo} />
|
||||
<ThemedText.DeprecatedMain ml="6px" fontSize="12px" color={theme.deprecated_text1}>
|
||||
<Trans>Expanded results from inactive Token Lists</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
</RowFixed>
|
||||
<QuestionHelper
|
||||
text={
|
||||
<Trans>
|
||||
Tokens from inactive lists. Import specific tokens below or click Manage to activate more lists.
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
</RowBetween>
|
||||
</LightGreyCard>
|
||||
</FixedContentRow>
|
||||
)
|
||||
}
|
||||
|
||||
interface TokenRowProps {
|
||||
data: Array<Currency | BreakLine>
|
||||
data: Array<Currency>
|
||||
index: number
|
||||
style: CSSProperties
|
||||
}
|
||||
|
||||
const formatAnalyticsEventProperties = (
|
||||
export const formatAnalyticsEventProperties = (
|
||||
token: Token,
|
||||
index: number,
|
||||
data: any[],
|
||||
@@ -260,6 +212,14 @@ const formatAnalyticsEventProperties = (
|
||||
: { search_token_address_input: isAddressSearch }),
|
||||
})
|
||||
|
||||
const LoadingRow = () => (
|
||||
<LoadingRows>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</LoadingRows>
|
||||
)
|
||||
|
||||
export default function CurrencyList({
|
||||
height,
|
||||
currencies,
|
||||
@@ -268,8 +228,6 @@ export default function CurrencyList({
|
||||
onCurrencySelect,
|
||||
otherCurrency,
|
||||
fixedListRef,
|
||||
showImportView,
|
||||
setImportToken,
|
||||
showCurrencyAmount,
|
||||
isLoading,
|
||||
searchQuery,
|
||||
@@ -282,27 +240,21 @@ export default function CurrencyList({
|
||||
onCurrencySelect: (currency: Currency, hasWarning?: boolean) => void
|
||||
otherCurrency?: Currency | null
|
||||
fixedListRef?: MutableRefObject<FixedSizeList | undefined>
|
||||
showImportView: () => void
|
||||
setImportToken: (token: Token) => void
|
||||
showCurrencyAmount?: boolean
|
||||
isLoading: boolean
|
||||
searchQuery: string
|
||||
isAddressSearch: string | false
|
||||
}) {
|
||||
const itemData: (Currency | BreakLine)[] = useMemo(() => {
|
||||
const itemData: Currency[] = useMemo(() => {
|
||||
if (otherListTokens && otherListTokens?.length > 0) {
|
||||
return [...currencies, BREAK_LINE, ...otherListTokens]
|
||||
return [...currencies, ...otherListTokens]
|
||||
}
|
||||
return currencies
|
||||
}, [currencies, otherListTokens])
|
||||
|
||||
const Row = useCallback(
|
||||
function TokenRow({ data, index, style }: TokenRowProps) {
|
||||
const row: Currency | BreakLine = data[index]
|
||||
|
||||
if (isBreakLine(row)) {
|
||||
return <BreakLineComponent style={style} />
|
||||
}
|
||||
const row: Currency = data[index]
|
||||
|
||||
const currency = row
|
||||
|
||||
@@ -312,20 +264,8 @@ export default function CurrencyList({
|
||||
|
||||
const token = currency?.wrapped
|
||||
|
||||
const showImport = index > currencies.length
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingRows>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</LoadingRows>
|
||||
)
|
||||
} else if (showImport && token) {
|
||||
return (
|
||||
<ImportRow style={style} token={token} showImportView={showImportView} setImportToken={setImportToken} dim />
|
||||
)
|
||||
return LoadingRow()
|
||||
} else if (currency) {
|
||||
return (
|
||||
<CurrencyRow
|
||||
@@ -342,27 +282,19 @@ export default function CurrencyList({
|
||||
return null
|
||||
}
|
||||
},
|
||||
[
|
||||
currencies.length,
|
||||
onCurrencySelect,
|
||||
otherCurrency,
|
||||
selectedCurrency,
|
||||
setImportToken,
|
||||
showImportView,
|
||||
showCurrencyAmount,
|
||||
isLoading,
|
||||
isAddressSearch,
|
||||
searchQuery,
|
||||
]
|
||||
[onCurrencySelect, otherCurrency, selectedCurrency, showCurrencyAmount, isLoading, isAddressSearch, searchQuery]
|
||||
)
|
||||
|
||||
const itemKey = useCallback((index: number, data: typeof itemData) => {
|
||||
const currency = data[index]
|
||||
if (isBreakLine(currency)) return BREAK_LINE
|
||||
return currencyKey(currency)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
return isLoading ? (
|
||||
<FixedSizeList height={height} ref={fixedListRef as any} width="100%" itemData={[]} itemCount={10} itemSize={56}>
|
||||
{LoadingRow}
|
||||
</FixedSizeList>
|
||||
) : (
|
||||
<FixedSizeList
|
||||
height={height}
|
||||
ref={fixedListRef as any}
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { EventName, ModalName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { Trace } from 'components/AmplitudeAnalytics/Trace'
|
||||
import { EventName, ModalName } from 'analytics/constants'
|
||||
import { Trace } from 'analytics/Trace'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import useToggle from 'hooks/useToggle'
|
||||
@@ -13,40 +12,29 @@ import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { getTokenFilter } from 'lib/hooks/useTokenList/filtering'
|
||||
import { tokenComparator, useSortTokensByQuery } from 'lib/hooks/useTokenList/sorting'
|
||||
import { ChangeEvent, KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Edit } from 'react-feather'
|
||||
import AutoSizer from 'react-virtualized-auto-sizer'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import { useAllTokenBalances } from 'state/connection/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { useAllTokens, useIsUserAddedToken, useSearchInactiveTokenLists, useToken } from '../../hooks/Tokens'
|
||||
import { ButtonText, CloseIcon, IconWrapper, ThemedText } from '../../theme'
|
||||
import { useActiveTokens, useIsUserAddedToken, useSearchInactiveTokenLists, useToken } from '../../hooks/Tokens'
|
||||
import { CloseIcon, ThemedText } from '../../theme'
|
||||
import { isAddress } from '../../utils'
|
||||
import Column from '../Column'
|
||||
import Row, { RowBetween, RowFixed } from '../Row'
|
||||
import Row, { RowBetween } from '../Row'
|
||||
import CommonBases from './CommonBases'
|
||||
import { CurrencyRow, formatAnalyticsEventProperties } from './CurrencyList'
|
||||
import CurrencyList from './CurrencyList'
|
||||
import ImportRow from './ImportRow'
|
||||
import { PaddedColumn, SearchInput, Separator } from './styleds'
|
||||
|
||||
const ContentWrapper = styled(Column)<{ redesignFlag?: boolean }>`
|
||||
background-color: ${({ theme, redesignFlag }) => redesignFlag && theme.backgroundSurface};
|
||||
const ContentWrapper = styled(Column)`
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
width: 100%;
|
||||
flex: 1 1;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const Footer = styled.div`
|
||||
width: 100%;
|
||||
border-radius: 20px;
|
||||
padding: 20px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
border-top: 1px solid ${({ theme }) => theme.deprecated_bg2};
|
||||
`
|
||||
|
||||
interface CurrencySearchProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
@@ -56,9 +44,6 @@ interface CurrencySearchProps {
|
||||
showCommonBases?: boolean
|
||||
showCurrencyAmount?: boolean
|
||||
disableNonToken?: boolean
|
||||
showManageView: () => void
|
||||
showImportView: () => void
|
||||
setImportToken: (token: Token) => void
|
||||
}
|
||||
|
||||
export function CurrencySearch({
|
||||
@@ -70,13 +55,7 @@ export function CurrencySearch({
|
||||
disableNonToken,
|
||||
onDismiss,
|
||||
isOpen,
|
||||
showManageView,
|
||||
showImportView,
|
||||
setImportToken,
|
||||
}: CurrencySearchProps) {
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
|
||||
const { chainId } = useWeb3React()
|
||||
const theme = useTheme()
|
||||
|
||||
@@ -88,7 +67,8 @@ export function CurrencySearch({
|
||||
const [searchQuery, setSearchQuery] = useState<string>('')
|
||||
const debouncedQuery = useDebounce(searchQuery, 200)
|
||||
|
||||
const allTokens = useAllTokens()
|
||||
// Only display 'imported' tokens when the search filter has input
|
||||
const defaultTokens = useActiveTokens(debouncedQuery.length > 0)
|
||||
|
||||
// if they input an address, use it
|
||||
const isAddressSearch = isAddress(debouncedQuery)
|
||||
@@ -108,32 +88,30 @@ export function CurrencySearch({
|
||||
}, [isAddressSearch])
|
||||
|
||||
const filteredTokens: Token[] = useMemo(() => {
|
||||
return Object.values(allTokens).filter(getTokenFilter(debouncedQuery))
|
||||
}, [allTokens, debouncedQuery])
|
||||
return Object.values(defaultTokens).filter(getTokenFilter(debouncedQuery))
|
||||
}, [defaultTokens, debouncedQuery])
|
||||
|
||||
const [balances, balancesIsLoading] = useAllTokenBalances()
|
||||
const [balances, balancesAreLoading] = useAllTokenBalances()
|
||||
const sortedTokens: Token[] = useMemo(
|
||||
() => (!balancesIsLoading ? [...filteredTokens].sort(tokenComparator.bind(null, balances)) : []),
|
||||
[balances, filteredTokens, balancesIsLoading]
|
||||
() => (!balancesAreLoading ? [...filteredTokens].sort(tokenComparator.bind(null, balances)) : []),
|
||||
[balances, filteredTokens, balancesAreLoading]
|
||||
)
|
||||
const isLoading = Boolean(balancesAreLoading && !tokenLoaderTimerElapsed)
|
||||
|
||||
const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens)
|
||||
|
||||
const native = useNativeCurrency()
|
||||
const wrapped = native.wrapped
|
||||
|
||||
const filteredSortedTokensWithETH: Currency[] = useMemo(() => {
|
||||
// Use Celo ERC20 Implementation and exclude the native asset
|
||||
if (!native) {
|
||||
return filteredSortedTokens
|
||||
}
|
||||
|
||||
const searchCurrencies: Currency[] = useMemo(() => {
|
||||
const s = debouncedQuery.toLowerCase().trim()
|
||||
if (native.symbol?.toLowerCase()?.indexOf(s) !== -1) {
|
||||
// Always bump the native token to the top of the list.
|
||||
return [native, ...filteredSortedTokens.filter((t) => !t.equals(native))]
|
||||
}
|
||||
return filteredSortedTokens
|
||||
}, [debouncedQuery, native, filteredSortedTokens])
|
||||
|
||||
const tokens = filteredSortedTokens.filter((t) => !(t.equals(wrapped) || (disableNonToken && t.isNative)))
|
||||
const natives = (disableNonToken || native.equals(wrapped) ? [wrapped] : [native, wrapped]).filter(
|
||||
(n) => n.symbol?.toLowerCase()?.indexOf(s) !== -1 || n.name?.toLowerCase()?.indexOf(s) !== -1
|
||||
)
|
||||
return [...natives, ...tokens]
|
||||
}, [debouncedQuery, filteredSortedTokens, wrapped, disableNonToken, native])
|
||||
|
||||
const handleCurrencySelect = useCallback(
|
||||
(currency: Currency, hasWarning?: boolean) => {
|
||||
@@ -163,17 +141,17 @@ export function CurrencySearch({
|
||||
const s = debouncedQuery.toLowerCase().trim()
|
||||
if (s === native?.symbol?.toLowerCase()) {
|
||||
handleCurrencySelect(native)
|
||||
} else if (filteredSortedTokensWithETH.length > 0) {
|
||||
} else if (searchCurrencies.length > 0) {
|
||||
if (
|
||||
filteredSortedTokensWithETH[0].symbol?.toLowerCase() === debouncedQuery.trim().toLowerCase() ||
|
||||
filteredSortedTokensWithETH.length === 1
|
||||
searchCurrencies[0].symbol?.toLowerCase() === debouncedQuery.trim().toLowerCase() ||
|
||||
searchCurrencies.length === 1
|
||||
) {
|
||||
handleCurrencySelect(filteredSortedTokensWithETH[0])
|
||||
handleCurrencySelect(searchCurrencies[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[debouncedQuery, native, filteredSortedTokensWithETH, handleCurrencySelect]
|
||||
[debouncedQuery, native, searchCurrencies, handleCurrencySelect]
|
||||
)
|
||||
|
||||
// menu ui
|
||||
@@ -195,7 +173,7 @@ export function CurrencySearch({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ContentWrapper redesignFlag={redesignFlagEnabled}>
|
||||
<ContentWrapper>
|
||||
<Trace name={EventName.TOKEN_SELECTOR_OPENED} modal={ModalName.TOKEN_SELECTOR} shouldLogImpression>
|
||||
<PaddedColumn gap="16px">
|
||||
<RowBetween>
|
||||
@@ -210,7 +188,6 @@ export function CurrencySearch({
|
||||
id="token-search-input"
|
||||
placeholder={t`Search name or paste address`}
|
||||
autoComplete="off"
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
value={searchQuery}
|
||||
ref={inputRef as RefObject<HTMLInputElement>}
|
||||
onChange={handleInput}
|
||||
@@ -227,27 +204,38 @@ export function CurrencySearch({
|
||||
/>
|
||||
)}
|
||||
</PaddedColumn>
|
||||
<Separator redesignFlag={redesignFlagEnabled} />
|
||||
<Separator />
|
||||
{searchToken && !searchTokenIsAdded ? (
|
||||
<Column style={{ padding: '20px 0', height: '100%' }}>
|
||||
<ImportRow token={searchToken} showImportView={showImportView} setImportToken={setImportToken} />
|
||||
<CurrencyRow
|
||||
currency={searchToken}
|
||||
isSelected={Boolean(searchToken && selectedCurrency && selectedCurrency.equals(searchToken))}
|
||||
onSelect={(hasWarning: boolean) => searchToken && handleCurrencySelect(searchToken, hasWarning)}
|
||||
otherSelected={Boolean(searchToken && otherSelectedCurrency && otherSelectedCurrency.equals(searchToken))}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
eventProperties={formatAnalyticsEventProperties(
|
||||
searchToken,
|
||||
0,
|
||||
[searchToken],
|
||||
searchQuery,
|
||||
isAddressSearch
|
||||
)}
|
||||
/>
|
||||
</Column>
|
||||
) : filteredSortedTokens?.length > 0 || filteredInactiveTokens?.length > 0 ? (
|
||||
) : searchCurrencies?.length > 0 || filteredInactiveTokens?.length > 0 || isLoading ? (
|
||||
<div style={{ flex: '1' }}>
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
<CurrencyList
|
||||
height={height}
|
||||
currencies={disableNonToken ? filteredSortedTokens : filteredSortedTokensWithETH}
|
||||
currencies={searchCurrencies}
|
||||
otherListTokens={filteredInactiveTokens}
|
||||
onCurrencySelect={handleCurrencySelect}
|
||||
otherCurrency={otherSelectedCurrency}
|
||||
selectedCurrency={selectedCurrency}
|
||||
fixedListRef={fixedList}
|
||||
showImportView={showImportView}
|
||||
setImportToken={setImportToken}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
isLoading={balancesIsLoading && !tokenLoaderTimerElapsed}
|
||||
isLoading={isLoading}
|
||||
searchQuery={searchQuery}
|
||||
isAddressSearch={isAddressSearch}
|
||||
/>
|
||||
@@ -261,26 +249,6 @@ export function CurrencySearch({
|
||||
</ThemedText.DeprecatedMain>
|
||||
</Column>
|
||||
)}
|
||||
{!redesignFlagEnabled && (
|
||||
<Footer>
|
||||
<Row justify="center">
|
||||
<ButtonText
|
||||
onClick={showManageView}
|
||||
color={theme.deprecated_primary1}
|
||||
className="list-token-manage-button"
|
||||
>
|
||||
<RowFixed>
|
||||
<IconWrapper size="16px" marginRight="6px" stroke={theme.deprecated_primaryText1}>
|
||||
<Edit />
|
||||
</IconWrapper>
|
||||
<ThemedText.DeprecatedMain color={theme.deprecated_primaryText1}>
|
||||
<Trans>Manage Token Lists</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
</RowFixed>
|
||||
</ButtonText>
|
||||
</Row>
|
||||
</Footer>
|
||||
)}
|
||||
</Trace>
|
||||
</ContentWrapper>
|
||||
)
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import TokenSafety from 'components/TokenSafety'
|
||||
import { TokenSafetyVariant, useTokenSafetyFlag } from 'featureFlags/flags/tokenSafety'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { memo, useCallback, useEffect, useState } from 'react'
|
||||
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
|
||||
import { useUserAddedTokens } from 'state/user/hooks'
|
||||
|
||||
import useLast from '../../hooks/useLast'
|
||||
import { useWindowSize } from '../../hooks/useWindowSize'
|
||||
import Modal from '../Modal'
|
||||
import { CurrencySearch } from './CurrencySearch'
|
||||
import { ImportList } from './ImportList'
|
||||
import { ImportToken } from './ImportToken'
|
||||
import Manage from './Manage'
|
||||
|
||||
interface CurrencySearchModalProps {
|
||||
isOpen: boolean
|
||||
@@ -27,9 +21,7 @@ interface CurrencySearchModalProps {
|
||||
|
||||
export enum CurrencyModalView {
|
||||
search,
|
||||
manage,
|
||||
importToken,
|
||||
importList,
|
||||
tokenSafety,
|
||||
}
|
||||
|
||||
@@ -43,7 +35,7 @@ export default memo(function CurrencySearchModal({
|
||||
showCurrencyAmount = true,
|
||||
disableNonToken = false,
|
||||
}: CurrencySearchModalProps) {
|
||||
const [modalView, setModalView] = useState<CurrencyModalView>(CurrencyModalView.manage)
|
||||
const [modalView, setModalView] = useState<CurrencyModalView>(CurrencyModalView.search)
|
||||
const lastOpen = useLast(isOpen)
|
||||
const userAddedTokens = useUserAddedTokens()
|
||||
|
||||
@@ -58,50 +50,30 @@ export default memo(function CurrencySearchModal({
|
||||
setModalView(CurrencyModalView.tokenSafety)
|
||||
}
|
||||
|
||||
const tokenSafetyFlag = useTokenSafetyFlag()
|
||||
|
||||
const handleCurrencySelect = useCallback(
|
||||
(currency: Currency, hasWarning?: boolean) => {
|
||||
if (
|
||||
tokenSafetyFlag === TokenSafetyVariant.Enabled &&
|
||||
hasWarning &&
|
||||
currency.isToken &&
|
||||
!userAddedTokens.find((token) => token.equals(currency))
|
||||
) {
|
||||
if (hasWarning && currency.isToken && !userAddedTokens.find((token) => token.equals(currency))) {
|
||||
showTokenSafetySpeedbump(currency)
|
||||
} else {
|
||||
onCurrencySelect(currency)
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
[onDismiss, onCurrencySelect, tokenSafetyFlag, userAddedTokens]
|
||||
[onDismiss, onCurrencySelect, userAddedTokens]
|
||||
)
|
||||
|
||||
// for token import view
|
||||
const prevView = usePrevious(modalView)
|
||||
|
||||
// used for import token flow
|
||||
const [importToken, setImportToken] = useState<Token | undefined>()
|
||||
|
||||
// used for import list
|
||||
const [importList, setImportList] = useState<TokenList | undefined>()
|
||||
const [listURL, setListUrl] = useState<string | undefined>()
|
||||
|
||||
// used for token safety
|
||||
const [warningToken, setWarningToken] = useState<Token | undefined>()
|
||||
|
||||
const showImportView = useCallback(() => setModalView(CurrencyModalView.importToken), [setModalView])
|
||||
const showManageView = useCallback(() => setModalView(CurrencyModalView.manage), [setModalView])
|
||||
const handleBackImport = useCallback(
|
||||
() => setModalView(prevView && prevView !== CurrencyModalView.importToken ? prevView : CurrencyModalView.search),
|
||||
[setModalView, prevView]
|
||||
)
|
||||
|
||||
const { height: windowHeight } = useWindowSize()
|
||||
// change min height if not searching
|
||||
let minHeight: number | undefined = 80
|
||||
let modalHeight: number | undefined = 80
|
||||
let content = null
|
||||
switch (modalView) {
|
||||
case CurrencyModalView.search:
|
||||
if (windowHeight) {
|
||||
// Converts pixel units to vh for Modal component
|
||||
modalHeight = Math.min(Math.round((680 / windowHeight) * 100), 80)
|
||||
}
|
||||
content = (
|
||||
<CurrencySearch
|
||||
isOpen={isOpen}
|
||||
@@ -112,15 +84,12 @@ export default memo(function CurrencySearchModal({
|
||||
showCommonBases={showCommonBases}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
disableNonToken={disableNonToken}
|
||||
showImportView={showImportView}
|
||||
setImportToken={setImportToken}
|
||||
showManageView={showManageView}
|
||||
/>
|
||||
)
|
||||
break
|
||||
case CurrencyModalView.tokenSafety:
|
||||
minHeight = undefined
|
||||
if (tokenSafetyFlag === TokenSafetyVariant.Enabled && warningToken) {
|
||||
modalHeight = undefined
|
||||
if (warningToken) {
|
||||
content = (
|
||||
<TokenSafety
|
||||
tokenAddress={warningToken.address}
|
||||
@@ -131,43 +100,9 @@ export default memo(function CurrencySearchModal({
|
||||
)
|
||||
}
|
||||
break
|
||||
case CurrencyModalView.importToken:
|
||||
if (importToken) {
|
||||
minHeight = undefined
|
||||
if (tokenSafetyFlag === TokenSafetyVariant.Enabled) {
|
||||
showTokenSafetySpeedbump(importToken)
|
||||
}
|
||||
content = (
|
||||
<ImportToken
|
||||
tokens={[importToken]}
|
||||
onDismiss={onDismiss}
|
||||
list={importToken instanceof WrappedTokenInfo ? importToken.list : undefined}
|
||||
onBack={handleBackImport}
|
||||
handleCurrencySelect={handleCurrencySelect}
|
||||
/>
|
||||
)
|
||||
}
|
||||
break
|
||||
case CurrencyModalView.importList:
|
||||
minHeight = 40
|
||||
if (importList && listURL) {
|
||||
content = <ImportList list={importList} listURL={listURL} onDismiss={onDismiss} setModalView={setModalView} />
|
||||
}
|
||||
break
|
||||
case CurrencyModalView.manage:
|
||||
content = (
|
||||
<Manage
|
||||
onDismiss={onDismiss}
|
||||
setModalView={setModalView}
|
||||
setImportToken={setImportToken}
|
||||
setImportList={setImportList}
|
||||
setListUrl={setListUrl}
|
||||
/>
|
||||
)
|
||||
break
|
||||
}
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={80} minHeight={minHeight}>
|
||||
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={modalHeight} minHeight={modalHeight}>
|
||||
{content}
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import Card from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import ListLogo from 'components/ListLogo'
|
||||
import { AutoRow, RowBetween, RowFixed } from 'components/Row'
|
||||
import { SectionBreak } from 'components/swap/styleds'
|
||||
import { useFetchListCallback } from 'hooks/useFetchListCallback'
|
||||
import { transparentize } from 'polished'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { AlertTriangle, ArrowLeft } from 'react-feather'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { enableList, removeList } from 'state/lists/actions'
|
||||
import { useAllLists } from 'state/lists/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { CloseIcon, ThemedText } from 'theme'
|
||||
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { CurrencyModalView } from './CurrencySearchModal'
|
||||
import { Checkbox, PaddedColumn, TextDot } from './styleds'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
`
|
||||
|
||||
interface ImportProps {
|
||||
listURL: string
|
||||
list: TokenList
|
||||
onDismiss: () => void
|
||||
setModalView: (view: CurrencyModalView) => void
|
||||
}
|
||||
|
||||
export function ImportList({ listURL, list, setModalView, onDismiss }: ImportProps) {
|
||||
const theme = useTheme()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
// user must accept
|
||||
const [confirmed, setConfirmed] = useState(false)
|
||||
|
||||
const lists = useAllLists()
|
||||
const fetchList = useFetchListCallback()
|
||||
|
||||
// monitor is list is loading
|
||||
const adding = Boolean(lists[listURL]?.loadingRequestId)
|
||||
const [addError, setAddError] = useState<string | null>(null)
|
||||
|
||||
const handleAddList = useCallback(() => {
|
||||
if (adding) return
|
||||
setAddError(null)
|
||||
fetchList(listURL)
|
||||
.then(() => {
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Add List',
|
||||
label: listURL,
|
||||
})
|
||||
|
||||
// turn list on
|
||||
dispatch(enableList(listURL))
|
||||
// go back to lists
|
||||
setModalView(CurrencyModalView.manage)
|
||||
})
|
||||
.catch((error) => {
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Add List Failed',
|
||||
label: listURL,
|
||||
})
|
||||
setAddError(error.message)
|
||||
dispatch(removeList(listURL))
|
||||
})
|
||||
}, [adding, dispatch, fetchList, listURL, setModalView])
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<PaddedColumn gap="14px" style={{ width: '100%', flex: '1 1' }}>
|
||||
<RowBetween>
|
||||
<ArrowLeft style={{ cursor: 'pointer' }} onClick={() => setModalView(CurrencyModalView.manage)} />
|
||||
<ThemedText.DeprecatedMediumHeader>
|
||||
<Trans>Import List</Trans>
|
||||
</ThemedText.DeprecatedMediumHeader>
|
||||
<CloseIcon onClick={onDismiss} />
|
||||
</RowBetween>
|
||||
</PaddedColumn>
|
||||
<SectionBreak />
|
||||
<PaddedColumn gap="md">
|
||||
<AutoColumn gap="md">
|
||||
<Card backgroundColor={theme.deprecated_bg2} padding="12px 20px">
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
{list.logoURI && <ListLogo logoURI={list.logoURI} size="40px" />}
|
||||
<AutoColumn gap="sm" style={{ marginLeft: '20px' }}>
|
||||
<RowFixed>
|
||||
<ThemedText.DeprecatedBody fontWeight={600} mr="6px">
|
||||
{list.name}
|
||||
</ThemedText.DeprecatedBody>
|
||||
<TextDot />
|
||||
<ThemedText.DeprecatedMain fontSize={'16px'} ml="6px">
|
||||
<Trans>{list.tokens.length} tokens</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
</RowFixed>
|
||||
<ExternalLink href={`https://tokenlists.org/token-list?url=${listURL}`}>
|
||||
<ThemedText.DeprecatedMain fontSize={'12px'} color={theme.deprecated_blue1}>
|
||||
{listURL}
|
||||
</ThemedText.DeprecatedMain>
|
||||
</ExternalLink>
|
||||
</AutoColumn>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
</Card>
|
||||
<Card style={{ backgroundColor: transparentize(0.8, theme.deprecated_red1) }}>
|
||||
<AutoColumn justify="center" style={{ textAlign: 'center', gap: '16px', marginBottom: '12px' }}>
|
||||
<AlertTriangle stroke={theme.deprecated_red1} size={32} />
|
||||
<ThemedText.DeprecatedBody fontWeight={500} fontSize={20} color={theme.deprecated_red1}>
|
||||
<Trans>Import at your own risk</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoColumn>
|
||||
|
||||
<AutoColumn style={{ textAlign: 'center', gap: '16px', marginBottom: '12px' }}>
|
||||
<ThemedText.DeprecatedBody fontWeight={500} color={theme.deprecated_red1}>
|
||||
<Trans>
|
||||
By adding this list you are implicitly trusting that the data is correct. Anyone can create a list,
|
||||
including creating fake versions of existing lists and lists that claim to represent projects that do
|
||||
not have one.
|
||||
</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
<ThemedText.DeprecatedBody fontWeight={600} color={theme.deprecated_red1}>
|
||||
<Trans>If you purchase a token from this list, you may not be able to sell it back.</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoColumn>
|
||||
<AutoRow justify="center" style={{ cursor: 'pointer' }} onClick={() => setConfirmed(!confirmed)}>
|
||||
<Checkbox
|
||||
name="confirmed"
|
||||
type="checkbox"
|
||||
checked={confirmed}
|
||||
onChange={() => setConfirmed(!confirmed)}
|
||||
/>
|
||||
<ThemedText.DeprecatedBody ml="10px" fontSize="16px" color={theme.deprecated_red1} fontWeight={500}>
|
||||
<Trans>I understand</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</AutoRow>
|
||||
</Card>
|
||||
|
||||
<ButtonPrimary
|
||||
disabled={!confirmed}
|
||||
altDisabledStyle={true}
|
||||
$borderRadius="20px"
|
||||
padding="10px 1rem"
|
||||
onClick={handleAddList}
|
||||
>
|
||||
<Trans>Import</Trans>
|
||||
</ButtonPrimary>
|
||||
{addError ? (
|
||||
<ThemedText.DeprecatedError title={addError} style={{ textOverflow: 'ellipsis', overflow: 'hidden' }} error>
|
||||
{addError}
|
||||
</ThemedText.DeprecatedError>
|
||||
) : null}
|
||||
</AutoColumn>
|
||||
{/* </Card> */}
|
||||
</PaddedColumn>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Plural, Trans } from '@lingui/macro'
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import { ElementName, Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { RowBetween } from 'components/Row'
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { useState } from 'react'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { CloseIcon } from 'theme'
|
||||
|
||||
import { CurrencyModalView } from './CurrencySearchModal'
|
||||
import { ManageLists } from './ManageLists'
|
||||
import ManageTokens from './ManageTokens'
|
||||
import { PaddedColumn, Separator } from './styleds'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
`
|
||||
|
||||
const ToggleWrapper = styled(RowBetween)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg3};
|
||||
border-radius: 12px;
|
||||
padding: 6px;
|
||||
`
|
||||
|
||||
const ToggleOption = styled.div<{ active?: boolean }>`
|
||||
width: 48%;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
background-color: ${({ theme, active }) => (active ? theme.deprecated_bg1 : theme.deprecated_bg3)};
|
||||
color: ${({ theme, active }) => (active ? theme.deprecated_text1 : theme.deprecated_text2)};
|
||||
user-select: none;
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
}
|
||||
`
|
||||
|
||||
export default function Manage({
|
||||
onDismiss,
|
||||
setModalView,
|
||||
setImportList,
|
||||
setImportToken,
|
||||
setListUrl,
|
||||
}: {
|
||||
onDismiss: () => void
|
||||
setModalView: (view: CurrencyModalView) => void
|
||||
setImportToken: (token: Token) => void
|
||||
setImportList: (list: TokenList) => void
|
||||
setListUrl: (url: string) => void
|
||||
}) {
|
||||
// toggle between tokens and lists
|
||||
const [showLists, setShowLists] = useState(true)
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<PaddedColumn>
|
||||
<RowBetween>
|
||||
<ArrowLeft style={{ cursor: 'pointer' }} onClick={() => setModalView(CurrencyModalView.search)} />
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
<Trans>Manage</Trans>
|
||||
</Text>
|
||||
<CloseIcon onClick={onDismiss} />
|
||||
</RowBetween>
|
||||
</PaddedColumn>
|
||||
<Separator />
|
||||
<PaddedColumn style={{ paddingBottom: 0 }}>
|
||||
<ToggleWrapper>
|
||||
<ToggleOption onClick={() => setShowLists(!showLists)} active={showLists}>
|
||||
<Trans>Lists</Trans>
|
||||
</ToggleOption>
|
||||
<ToggleOption onClick={() => setShowLists(!showLists)} active={!showLists}>
|
||||
<Trans>Tokens</Trans>
|
||||
</ToggleOption>
|
||||
</ToggleWrapper>
|
||||
</PaddedColumn>
|
||||
{showLists ? (
|
||||
<ManageLists setModalView={setModalView} setImportList={setImportList} setListUrl={setListUrl} />
|
||||
) : (
|
||||
<ManageTokens setModalView={setModalView} setImportToken={setImportToken} />
|
||||
)}
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,415 +0,0 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import Card from 'components/Card'
|
||||
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
|
||||
import { useListColor } from 'hooks/useColor'
|
||||
import parseENSAddress from 'lib/utils/parseENSAddress'
|
||||
import uriToHttp from 'lib/utils/uriToHttp'
|
||||
import { ChangeEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { CheckCircle, Settings } from 'react-feather'
|
||||
import { usePopper } from 'react-popper'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { useFetchListCallback } from '../../hooks/useFetchListCallback'
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
import useToggle from '../../hooks/useToggle'
|
||||
import { acceptListUpdate, disableList, enableList, removeList } from '../../state/lists/actions'
|
||||
import { useActiveListUrls, useAllLists, useIsListActive } from '../../state/lists/hooks'
|
||||
import { ExternalLink, IconWrapper, LinkStyledButton, ThemedText } from '../../theme'
|
||||
import listVersionLabel from '../../utils/listVersionLabel'
|
||||
import { ButtonEmpty, ButtonPrimary } from '../Button'
|
||||
import Column, { AutoColumn } from '../Column'
|
||||
import ListLogo from '../ListLogo'
|
||||
import Row, { RowBetween, RowFixed } from '../Row'
|
||||
import Toggle from '../Toggle'
|
||||
import { CurrencyModalView } from './CurrencySearchModal'
|
||||
import { PaddedColumn, SearchInput, Separator, SeparatorDark } from './styleds'
|
||||
|
||||
const Wrapper = styled(Column)`
|
||||
flex: 1;
|
||||
overflow-y: hidden;
|
||||
`
|
||||
|
||||
const UnpaddedLinkStyledButton = styled(LinkStyledButton)`
|
||||
padding: 0;
|
||||
font-size: 1rem;
|
||||
opacity: ${({ disabled }) => (disabled ? '0.4' : '1')};
|
||||
`
|
||||
|
||||
const PopoverContainer = styled.div<{ show: boolean }>`
|
||||
z-index: 100;
|
||||
visibility: ${(props) => (props.show ? 'visible' : 'hidden')};
|
||||
opacity: ${(props) => (props.show ? 1 : 0)};
|
||||
transition: visibility 150ms linear, opacity 150ms linear;
|
||||
background: ${({ theme }) => theme.deprecated_bg2};
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg3};
|
||||
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);
|
||||
color: ${({ theme }) => theme.deprecated_text2};
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
grid-gap: 8px;
|
||||
font-size: 1rem;
|
||||
text-align: left;
|
||||
`
|
||||
|
||||
const StyledMenu = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border: none;
|
||||
`
|
||||
|
||||
const StyledTitleText = styled.div<{ active: boolean }>`
|
||||
font-size: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 600;
|
||||
color: ${({ theme, active }) => (active ? theme.deprecated_white : theme.deprecated_text2)};
|
||||
`
|
||||
|
||||
const StyledListUrlText = styled(ThemedText.DeprecatedMain)<{ active: boolean }>`
|
||||
font-size: 12px;
|
||||
color: ${({ theme, active }) => (active ? theme.deprecated_white : theme.deprecated_text2)};
|
||||
`
|
||||
|
||||
const RowWrapper = styled(Row)<{ bgColor: string; active: boolean; hasActiveTokens: boolean }>`
|
||||
background-color: ${({ bgColor, active, theme }) => (active ? bgColor ?? 'transparent' : theme.deprecated_bg2)};
|
||||
opacity: ${({ hasActiveTokens }) => (hasActiveTokens ? 1 : 0.4)};
|
||||
transition: 200ms;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
border-radius: 20px;
|
||||
`
|
||||
|
||||
function listUrlRowHTMLId(listUrl: string) {
|
||||
return `list-row-${listUrl.replace(/\./g, '-')}`
|
||||
}
|
||||
|
||||
const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
const { chainId } = useWeb3React()
|
||||
const listsByUrl = useAppSelector((state) => state.lists.byUrl)
|
||||
const dispatch = useAppDispatch()
|
||||
const { current: list, pendingUpdate: pending } = listsByUrl[listUrl]
|
||||
|
||||
const activeTokensOnThisChain = useMemo(() => {
|
||||
if (!list || !chainId) {
|
||||
return 0
|
||||
}
|
||||
return list.tokens.reduce((acc, cur) => (cur.chainId === chainId ? acc + 1 : acc), 0)
|
||||
}, [chainId, list])
|
||||
|
||||
const theme = useTheme()
|
||||
const listColor = useListColor(list?.logoURI)
|
||||
const isActive = useIsListActive(listUrl)
|
||||
|
||||
const [open, toggle] = useToggle(false)
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLDivElement>()
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement>()
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: 'auto',
|
||||
strategy: 'fixed',
|
||||
modifiers: [{ name: 'offset', options: { offset: [8, 8] } }],
|
||||
})
|
||||
|
||||
useOnClickOutside(node, open ? toggle : undefined)
|
||||
|
||||
const handleAcceptListUpdate = useCallback(() => {
|
||||
if (!pending) return
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Update List from List Select',
|
||||
label: listUrl,
|
||||
})
|
||||
dispatch(acceptListUpdate(listUrl))
|
||||
}, [dispatch, listUrl, pending])
|
||||
|
||||
const handleRemoveList = useCallback(() => {
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Start Remove List',
|
||||
label: listUrl,
|
||||
})
|
||||
if (window.prompt(t`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) {
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Confirm Remove List',
|
||||
label: listUrl,
|
||||
})
|
||||
dispatch(removeList(listUrl))
|
||||
}
|
||||
}, [dispatch, listUrl])
|
||||
|
||||
const handleEnableList = useCallback(() => {
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Enable List',
|
||||
label: listUrl,
|
||||
})
|
||||
dispatch(enableList(listUrl))
|
||||
}, [dispatch, listUrl])
|
||||
|
||||
const handleDisableList = useCallback(() => {
|
||||
sendEvent({
|
||||
category: 'Lists',
|
||||
action: 'Disable List',
|
||||
label: listUrl,
|
||||
})
|
||||
dispatch(disableList(listUrl))
|
||||
}, [dispatch, listUrl])
|
||||
|
||||
if (!list) return null
|
||||
|
||||
return (
|
||||
<RowWrapper
|
||||
active={isActive}
|
||||
hasActiveTokens={activeTokensOnThisChain > 0}
|
||||
bgColor={listColor}
|
||||
key={listUrl}
|
||||
id={listUrlRowHTMLId(listUrl)}
|
||||
>
|
||||
{list.logoURI ? (
|
||||
<ListLogo size="40px" style={{ marginRight: '1rem' }} logoURI={list.logoURI} alt={`${list.name} list logo`} />
|
||||
) : (
|
||||
<div style={{ width: '24px', height: '24px', marginRight: '1rem' }} />
|
||||
)}
|
||||
<Column style={{ flex: '1' }}>
|
||||
<Row>
|
||||
<StyledTitleText active={isActive}>{list.name}</StyledTitleText>
|
||||
</Row>
|
||||
<RowFixed mt="4px">
|
||||
<StyledListUrlText active={isActive} mr="6px">
|
||||
<Trans>{activeTokensOnThisChain} tokens</Trans>
|
||||
</StyledListUrlText>
|
||||
<StyledMenu ref={node as any}>
|
||||
<ButtonEmpty onClick={toggle} ref={setReferenceElement} padding="0">
|
||||
<Settings stroke={isActive ? theme.deprecated_bg1 : theme.deprecated_text1} size={12} />
|
||||
</ButtonEmpty>
|
||||
{open && (
|
||||
<PopoverContainer show={true} ref={setPopperElement as any} style={styles.popper} {...attributes.popper}>
|
||||
<div>{list && listVersionLabel(list.version)}</div>
|
||||
<SeparatorDark />
|
||||
<ExternalLink href={`https://tokenlists.org/token-list?url=${listUrl}`}>
|
||||
<Trans>View list</Trans>
|
||||
</ExternalLink>
|
||||
<UnpaddedLinkStyledButton onClick={handleRemoveList} disabled={Object.keys(listsByUrl).length === 1}>
|
||||
<Trans>Remove list</Trans>
|
||||
</UnpaddedLinkStyledButton>
|
||||
{pending && (
|
||||
<UnpaddedLinkStyledButton onClick={handleAcceptListUpdate}>
|
||||
<Trans>Update list</Trans>
|
||||
</UnpaddedLinkStyledButton>
|
||||
)}
|
||||
</PopoverContainer>
|
||||
)}
|
||||
</StyledMenu>
|
||||
</RowFixed>
|
||||
</Column>
|
||||
<Toggle
|
||||
isActive={isActive}
|
||||
bgColor={listColor}
|
||||
toggle={() => {
|
||||
isActive ? handleDisableList() : handleEnableList()
|
||||
}}
|
||||
/>
|
||||
</RowWrapper>
|
||||
)
|
||||
})
|
||||
|
||||
const ListContainer = styled.div`
|
||||
padding: 1rem;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
`
|
||||
|
||||
export function ManageLists({
|
||||
setModalView,
|
||||
setImportList,
|
||||
setListUrl,
|
||||
}: {
|
||||
setModalView: (view: CurrencyModalView) => void
|
||||
setImportList: (list: TokenList) => void
|
||||
setListUrl: (url: string) => void
|
||||
}) {
|
||||
const { chainId } = useWeb3React()
|
||||
const theme = useTheme()
|
||||
|
||||
const [listUrlInput, setListUrlInput] = useState<string>('')
|
||||
|
||||
const lists = useAllLists()
|
||||
|
||||
const tokenCountByListName = useMemo<Record<string, number>>(
|
||||
() =>
|
||||
Object.values(lists).reduce((acc, { current: list }) => {
|
||||
if (!list) {
|
||||
return acc
|
||||
}
|
||||
return {
|
||||
...acc,
|
||||
[list.name]: list.tokens.reduce((count: number, token) => (token.chainId === chainId ? count + 1 : count), 0),
|
||||
}
|
||||
}, {}),
|
||||
[chainId, lists]
|
||||
)
|
||||
|
||||
// sort by active but only if not visible
|
||||
const activeListUrls = useActiveListUrls()
|
||||
|
||||
const handleInput = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setListUrlInput(e.target.value)
|
||||
}, [])
|
||||
|
||||
const fetchList = useFetchListCallback()
|
||||
|
||||
const validUrl: boolean = useMemo(() => {
|
||||
return uriToHttp(listUrlInput).length > 0 || Boolean(parseENSAddress(listUrlInput))
|
||||
}, [listUrlInput])
|
||||
|
||||
const sortedLists = useMemo(() => {
|
||||
const listUrls = Object.keys(lists)
|
||||
return listUrls
|
||||
.filter((listUrl) => {
|
||||
// only show loaded lists, hide unsupported lists
|
||||
return Boolean(lists[listUrl].current) && !Boolean(UNSUPPORTED_LIST_URLS.includes(listUrl))
|
||||
})
|
||||
.sort((listUrlA, listUrlB) => {
|
||||
const { current: listA } = lists[listUrlA]
|
||||
const { current: listB } = lists[listUrlB]
|
||||
|
||||
// first filter on active lists
|
||||
if (activeListUrls?.includes(listUrlA) && !activeListUrls?.includes(listUrlB)) {
|
||||
return -1
|
||||
}
|
||||
if (!activeListUrls?.includes(listUrlA) && activeListUrls?.includes(listUrlB)) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (listA && listB) {
|
||||
if (tokenCountByListName[listA.name] > tokenCountByListName[listB.name]) {
|
||||
return -1
|
||||
}
|
||||
if (tokenCountByListName[listA.name] < tokenCountByListName[listB.name]) {
|
||||
return 1
|
||||
}
|
||||
return listA.name.toLowerCase() < listB.name.toLowerCase()
|
||||
? -1
|
||||
: listA.name.toLowerCase() === listB.name.toLowerCase()
|
||||
? 0
|
||||
: 1
|
||||
}
|
||||
if (listA) return -1
|
||||
if (listB) return 1
|
||||
return 0
|
||||
})
|
||||
}, [lists, activeListUrls, tokenCountByListName])
|
||||
|
||||
// temporary fetched list for import flow
|
||||
const [tempList, setTempList] = useState<TokenList>()
|
||||
const [addError, setAddError] = useState<string | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchTempList() {
|
||||
fetchList(listUrlInput, false)
|
||||
.then((list) => setTempList(list))
|
||||
.catch(() => setAddError(t`Error importing list`))
|
||||
}
|
||||
// if valid url, fetch details for card
|
||||
if (validUrl) {
|
||||
fetchTempList()
|
||||
} else {
|
||||
setTempList(undefined)
|
||||
listUrlInput !== '' && setAddError(t`Enter valid list location`)
|
||||
}
|
||||
|
||||
// reset error
|
||||
if (listUrlInput === '') {
|
||||
setAddError(undefined)
|
||||
}
|
||||
}, [fetchList, listUrlInput, validUrl])
|
||||
|
||||
// check if list is already imported
|
||||
const isImported = Object.keys(lists).includes(listUrlInput)
|
||||
|
||||
// set list values and have parent modal switch to import list view
|
||||
const handleImport = useCallback(() => {
|
||||
if (!tempList) return
|
||||
setImportList(tempList)
|
||||
setModalView(CurrencyModalView.importList)
|
||||
setListUrl(listUrlInput)
|
||||
}, [listUrlInput, setImportList, setListUrl, setModalView, tempList])
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<PaddedColumn gap="14px">
|
||||
<Row>
|
||||
<SearchInput
|
||||
type="text"
|
||||
id="list-add-input"
|
||||
placeholder={t`https:// or ipfs:// or ENS name`}
|
||||
value={listUrlInput}
|
||||
onChange={handleInput}
|
||||
/>
|
||||
</Row>
|
||||
{addError ? (
|
||||
<ThemedText.DeprecatedError title={addError} style={{ textOverflow: 'ellipsis', overflow: 'hidden' }} error>
|
||||
{addError}
|
||||
</ThemedText.DeprecatedError>
|
||||
) : null}
|
||||
</PaddedColumn>
|
||||
{tempList && (
|
||||
<PaddedColumn style={{ paddingTop: 0 }}>
|
||||
<Card backgroundColor={theme.deprecated_bg2} padding="12px 20px">
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
{tempList.logoURI && <ListLogo logoURI={tempList.logoURI} size="40px" />}
|
||||
<AutoColumn gap="4px" style={{ marginLeft: '20px' }}>
|
||||
<ThemedText.DeprecatedBody fontWeight={600}>{tempList.name}</ThemedText.DeprecatedBody>
|
||||
<ThemedText.DeprecatedMain fontSize={'12px'}>
|
||||
<Trans>{tempList.tokens.length} tokens</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
</AutoColumn>
|
||||
</RowFixed>
|
||||
{isImported ? (
|
||||
<RowFixed>
|
||||
<IconWrapper stroke={theme.deprecated_text2} size="16px" marginRight={'10px'}>
|
||||
<CheckCircle />
|
||||
</IconWrapper>
|
||||
<ThemedText.DeprecatedBody color={theme.deprecated_text2}>
|
||||
<Trans>Loaded</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</RowFixed>
|
||||
) : (
|
||||
<ButtonPrimary
|
||||
style={{ fontSize: '14px' }}
|
||||
padding="6px 8px"
|
||||
width="fit-content"
|
||||
onClick={handleImport}
|
||||
>
|
||||
<Trans>Import</Trans>
|
||||
</ButtonPrimary>
|
||||
)}
|
||||
</RowBetween>
|
||||
</Card>
|
||||
</PaddedColumn>
|
||||
)}
|
||||
<Separator />
|
||||
<ListContainer>
|
||||
<AutoColumn gap="md">
|
||||
{sortedLists.map((listUrl) => (
|
||||
<ListRow key={listUrl} listUrl={listUrl} />
|
||||
))}
|
||||
</AutoColumn>
|
||||
</ListContainer>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export const PaddedColumn = styled(AutoColumn)`
|
||||
padding: 20px;
|
||||
`
|
||||
|
||||
export const MenuItem = styled(RowBetween)<{ redesignFlag?: boolean }>`
|
||||
export const MenuItem = styled(RowBetween)<{ dim?: boolean }>`
|
||||
padding: 4px 20px;
|
||||
height: 56px;
|
||||
display: grid;
|
||||
@@ -31,13 +31,12 @@ export const MenuItem = styled(RowBetween)<{ redesignFlag?: boolean }>`
|
||||
cursor: ${({ disabled }) => !disabled && 'pointer'};
|
||||
pointer-events: ${({ disabled }) => disabled && 'none'};
|
||||
:hover {
|
||||
background-color: ${({ theme, disabled, redesignFlag }) =>
|
||||
(redesignFlag && theme.hoverDefault) || (!disabled && theme.deprecated_bg2)};
|
||||
background-color: ${({ theme }) => theme.hoverDefault};
|
||||
}
|
||||
opacity: ${({ disabled, selected }) => (disabled || selected ? 0.5 : 1)};
|
||||
opacity: ${({ disabled, selected, dim }) => (dim || disabled || selected ? 0.4 : 1)};
|
||||
`
|
||||
|
||||
export const SearchInput = styled.input<{ redesignFlag?: boolean }>`
|
||||
export const SearchInput = styled.input`
|
||||
background: no-repeat scroll 7px 7px;
|
||||
background-image: url(${searchIcon});
|
||||
background-size: 20px 20px;
|
||||
@@ -46,37 +45,36 @@ export const SearchInput = styled.input<{ redesignFlag?: boolean }>`
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
padding-left: 40px;
|
||||
height: ${({ redesignFlag }) => redesignFlag && '40px'};
|
||||
height: 40px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
background-color: ${({ theme, redesignFlag }) => redesignFlag && theme.backgroundModule};
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
border: none;
|
||||
outline: none;
|
||||
border-radius: ${({ redesignFlag }) => (redesignFlag ? '12px' : '20px')};
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
border-style: solid;
|
||||
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundOutline : theme.deprecated_bg3)};
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
-webkit-appearance: none;
|
||||
|
||||
font-size: ${({ redesignFlag }) => (redesignFlag ? '16px' : '18px')};
|
||||
font-size: 16px;
|
||||
|
||||
::placeholder {
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textTertiary : theme.deprecated_text3)};
|
||||
font-size: ${({ redesignFlag }) => redesignFlag && '16px'};
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
font-size: 16px;
|
||||
}
|
||||
transition: border 100ms;
|
||||
:focus {
|
||||
border: 1px solid
|
||||
${({ theme, redesignFlag }) => (redesignFlag ? theme.accentActiveSoft : theme.deprecated_primary1)};
|
||||
background-color: ${({ theme, redesignFlag }) => redesignFlag && theme.backgroundSurface};
|
||||
border: 1px solid ${({ theme }) => theme.accentActiveSoft};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
outline: none;
|
||||
}
|
||||
`
|
||||
export const Separator = styled.div<{ redesignFlag?: boolean }>`
|
||||
export const Separator = styled.div`
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundOutline : theme.deprecated_bg2)};
|
||||
background-color: ${({ theme }) => theme.backgroundOutline};
|
||||
`
|
||||
|
||||
export const SeparatorDark = styled.div`
|
||||
|
||||
@@ -3,7 +3,6 @@ import { t, Trans } from '@lingui/macro'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { isSupportedChainId } from 'lib/hooks/routing/clientSideSmartOrderRouter'
|
||||
import { useRef, useState } from 'react'
|
||||
import { Settings, X } from 'react-feather'
|
||||
@@ -23,16 +22,16 @@ import { RowBetween, RowFixed } from '../Row'
|
||||
import Toggle from '../Toggle'
|
||||
import TransactionSettings from '../TransactionSettings'
|
||||
|
||||
const StyledMenuIcon = styled(Settings)<{ redesignFlag: boolean }>`
|
||||
const StyledMenuIcon = styled(Settings)`
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
|
||||
> * {
|
||||
stroke: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textSecondary : theme.deprecated_text1)};
|
||||
stroke: ${({ theme }) => theme.textSecondary};
|
||||
}
|
||||
`
|
||||
|
||||
const StyledCloseIcon = styled(X)<{ redesignFlag: boolean }>`
|
||||
const StyledCloseIcon = styled(X)`
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
:hover {
|
||||
@@ -40,7 +39,7 @@ const StyledCloseIcon = styled(X)<{ redesignFlag: boolean }>`
|
||||
}
|
||||
|
||||
> * {
|
||||
stroke: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textSecondary : theme.deprecated_text1)};
|
||||
stroke: ${({ theme }) => theme.textSecondary};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -83,10 +82,10 @@ const StyledMenu = styled.div`
|
||||
text-align: left;
|
||||
`
|
||||
|
||||
const MenuFlyout = styled.span<{ redesignFlag: boolean }>`
|
||||
const MenuFlyout = styled.span`
|
||||
min-width: 20.125rem;
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundSurface : theme.deprecated_bg2)};
|
||||
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundOutline : theme.deprecated_bg3)};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
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: 12px;
|
||||
@@ -97,7 +96,7 @@ const MenuFlyout = styled.span<{ redesignFlag: boolean }>`
|
||||
top: 2rem;
|
||||
right: 0rem;
|
||||
z-index: 100;
|
||||
color: ${({ theme, redesignFlag }) => redesignFlag && theme.textPrimary};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
min-width: 18.125rem;
|
||||
@@ -123,8 +122,6 @@ const ModalContentWrapper = styled.div`
|
||||
|
||||
export default function SettingsTab({ placeholderSlippage }: { placeholderSlippage: Percent }) {
|
||||
const { chainId } = useWeb3React()
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const open = useModalIsOpen(ApplicationModal.SETTINGS)
|
||||
@@ -152,7 +149,7 @@ export default function SettingsTab({ placeholderSlippage }: { placeholderSlippa
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
<Trans>Are you sure?</Trans>
|
||||
</Text>
|
||||
<StyledCloseIcon onClick={() => setShowConfirmation(false)} redesignFlag={redesignFlagEnabled} />
|
||||
<StyledCloseIcon onClick={() => setShowConfirmation(false)} />
|
||||
</RowBetween>
|
||||
<Break />
|
||||
<AutoColumn gap="lg" style={{ padding: '0 2rem' }}>
|
||||
@@ -190,7 +187,7 @@ export default function SettingsTab({ placeholderSlippage }: { placeholderSlippa
|
||||
id="open-settings-dialog-button"
|
||||
aria-label={t`Transaction Settings`}
|
||||
>
|
||||
<StyledMenuIcon redesignFlag={redesignFlagEnabled} />
|
||||
<StyledMenuIcon />
|
||||
{expertMode ? (
|
||||
<EmojiWrapper>
|
||||
<span role="img" aria-label="wizard-icon">
|
||||
@@ -200,10 +197,10 @@ export default function SettingsTab({ placeholderSlippage }: { placeholderSlippa
|
||||
) : null}
|
||||
</StyledMenuButton>
|
||||
{open && (
|
||||
<MenuFlyout redesignFlag={redesignFlagEnabled}>
|
||||
<MenuFlyout>
|
||||
<AutoColumn gap="md" style={{ padding: '1rem' }}>
|
||||
<Text fontWeight={600} fontSize={14}>
|
||||
<Trans>{redesignFlagEnabled ? 'Settings' : 'Transaction Settings'}</Trans>
|
||||
<Trans>Settings</Trans>
|
||||
</Text>
|
||||
<TransactionSettings placeholderSlippage={placeholderSlippage} />
|
||||
<Text fontWeight={600} fontSize={14}>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { navigatorLocale, useActiveLocale } from '../../hooks/useActiveLocale'
|
||||
import { StyledInternalLink, ThemedText } from '../../theme'
|
||||
|
||||
const Container = styled(ThemedText.DeprecatedSmall)`
|
||||
opacity: 0.6;
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||