Compare commits
210 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
311bdc19b3 | ||
|
|
a6342d40f1 | ||
|
|
3686506f2a | ||
|
|
634d010d92 | ||
|
|
979f29ad62 | ||
|
|
4d3ed5d6ba | ||
|
|
ea79fbc2e0 | ||
|
|
4d19122bd2 | ||
|
|
13d7d2c992 | ||
|
|
02cf33e115 | ||
|
|
1f62cdf7a2 | ||
|
|
701e8fe116 | ||
|
|
3207e6026e | ||
|
|
b0d93dbc0e | ||
|
|
975a5e3434 | ||
|
|
07f52f02ff | ||
|
|
5f112611c8 | ||
|
|
1d82a4c71e | ||
|
|
6004c4be3e | ||
|
|
a00432c1c3 | ||
|
|
a184afa41e | ||
|
|
49fd256e79 | ||
|
|
c006edf696 | ||
|
|
b16dd2a930 | ||
|
|
08a8f669e8 | ||
|
|
95d91c8528 | ||
|
|
03902fe5c1 | ||
|
|
631052e6a5 | ||
|
|
c63bb84f09 | ||
|
|
f0d10ba0df | ||
|
|
88600d3a78 | ||
|
|
a1060d5f75 | ||
|
|
83762b3c6a | ||
|
|
7784f647e1 | ||
|
|
65ba56c5f0 | ||
|
|
963514cd51 | ||
|
|
6d0c8037a8 | ||
|
|
d5ef7f9b24 | ||
|
|
f86db00464 | ||
|
|
8884020fab | ||
|
|
db145658db | ||
|
|
55bbaffe15 | ||
|
|
e1c3ad8f54 | ||
|
|
730af2eed4 | ||
|
|
c625d15feb | ||
|
|
f9de85251c | ||
|
|
a186833b7a | ||
|
|
aa06db7684 | ||
|
|
f5b601ee99 | ||
|
|
bf30013b6c | ||
|
|
2bc2a2c76e | ||
|
|
28bf95123e | ||
|
|
b1d88317b9 | ||
|
|
f332315de9 | ||
|
|
2fbb86ac44 | ||
|
|
8d567e4d4a | ||
|
|
77fbccd3f1 | ||
|
|
41330ab080 | ||
|
|
4ba9d98e67 | ||
|
|
52790ac92e | ||
|
|
380ca11dfd | ||
|
|
e4261cc01b | ||
|
|
99148dfd3f | ||
|
|
8bf9a9ce8d | ||
|
|
b6c0c1e607 | ||
|
|
0739613abe | ||
|
|
3401dfdebe | ||
|
|
ab9c27edad | ||
|
|
f2dfc57002 | ||
|
|
318252b6a9 | ||
|
|
440c50dd3a | ||
|
|
eacf4e6049 | ||
|
|
ffbd2d107a | ||
|
|
2db29f205b | ||
|
|
3ae9e2af2a | ||
|
|
5ff2cff841 | ||
|
|
0c0968864b | ||
|
|
9230473c36 | ||
|
|
b791afac59 | ||
|
|
2899c8af45 | ||
|
|
5f13f7824a | ||
|
|
6690bc513d | ||
|
|
aea3c1f6e6 | ||
|
|
e0c625671b | ||
|
|
aa3c867222 | ||
|
|
2182e676d4 | ||
|
|
f0fc11a3b5 | ||
|
|
53d93afd78 | ||
|
|
b612b15e1e | ||
|
|
9f5764aab3 | ||
|
|
f6dea47907 | ||
|
|
8e9981e186 | ||
|
|
1509c430fd | ||
|
|
93073321db | ||
|
|
618760ff1b | ||
|
|
2d20fc939d | ||
|
|
8c1e9b394b | ||
|
|
9b9f431e6f | ||
|
|
cfe590d810 | ||
|
|
2ee9b16c49 | ||
|
|
c52cd2c38e | ||
|
|
43cefbdc6a | ||
|
|
ae86fefe8b | ||
|
|
8901605c32 | ||
|
|
568829d3b5 | ||
|
|
b0f4b4c735 | ||
|
|
a73d3af4b2 | ||
|
|
2c7b431e60 | ||
|
|
0805b2d9b8 | ||
|
|
6c8fc5a7f6 | ||
|
|
702258574d | ||
|
|
17bc229a36 | ||
|
|
458a7d90a0 | ||
|
|
72877fb0b6 | ||
|
|
27f756107e | ||
|
|
abfd87c517 | ||
|
|
b3d772bdb5 | ||
|
|
d9c82ebf49 | ||
|
|
5298a5ce29 | ||
|
|
4d3073d2d9 | ||
|
|
c732f407c6 | ||
|
|
361e0ca8d7 | ||
|
|
cc0a757ddd | ||
|
|
31632d3c5b | ||
|
|
9cfbec4b8b | ||
|
|
b5ac5d882f | ||
|
|
130606cab4 | ||
|
|
fa56919a06 | ||
|
|
633f65676e | ||
|
|
9bccb7ae3a | ||
|
|
8a2e4123c6 | ||
|
|
e93ddbedfa | ||
|
|
1667b56a04 | ||
|
|
6e995d6c09 | ||
|
|
f096112716 | ||
|
|
50c7d36164 | ||
|
|
8a7f1d51ce | ||
|
|
8f59f501cc | ||
|
|
197947835b | ||
|
|
94081b29a3 | ||
|
|
69183ed8c2 | ||
|
|
05e741b6c9 | ||
|
|
dfe550c43b | ||
|
|
65dfb40e44 | ||
|
|
25f5ed9983 | ||
|
|
9b1ef415c6 | ||
|
|
70f481430a | ||
|
|
942211eb00 | ||
|
|
6589c41800 | ||
|
|
14bac770b6 | ||
|
|
c9dbe2f0ac | ||
|
|
775cf57c30 | ||
|
|
f4935e9264 | ||
|
|
6b3914b7b9 | ||
|
|
41efb6f617 | ||
|
|
52df5f14cf | ||
|
|
b48a7c529f | ||
|
|
2eca50be93 | ||
|
|
be2ce18bfa | ||
|
|
fe13b29e5e | ||
|
|
02aac08489 | ||
|
|
8f29980b94 | ||
|
|
bb542ef0fb | ||
|
|
c2fe17615f | ||
|
|
a4cab75d09 | ||
|
|
882457cfef | ||
|
|
9a1bb5dbfb | ||
|
|
767cc85b3e | ||
|
|
47d726e544 | ||
|
|
32118520cd | ||
|
|
ada8a3af92 | ||
|
|
63694b32c0 | ||
|
|
c9ab94d799 | ||
|
|
a990c4af70 | ||
|
|
b5cc33c1f5 | ||
|
|
31da6cdb9d | ||
|
|
4079d8a517 | ||
|
|
e93fbfd31b | ||
|
|
7817846368 | ||
|
|
fc6a69a9af | ||
|
|
93a774092f | ||
|
|
55563e9bb2 | ||
|
|
f44aae2f53 | ||
|
|
7d674c33e7 | ||
|
|
79e6337629 | ||
|
|
3c8e8604b8 | ||
|
|
b763659788 | ||
|
|
1ac2581419 | ||
|
|
9f556680ed | ||
|
|
3f242f1b44 | ||
|
|
126688c753 | ||
|
|
374cc361a6 | ||
|
|
0198d0baa1 | ||
|
|
da2d7ba648 | ||
|
|
37cf492dd5 | ||
|
|
9e93f809a0 | ||
|
|
d9bd392e6d | ||
|
|
c48adc519f | ||
|
|
9f8983a92e | ||
|
|
c9d3d81768 | ||
|
|
c6f73c35ee | ||
|
|
7a2777b814 | ||
|
|
014595cdfb | ||
|
|
1b27d8dab0 | ||
|
|
c4d12c86a8 | ||
|
|
8d2feeb89e | ||
|
|
576b399768 | ||
|
|
eb8ce85872 | ||
|
|
1644e4f9c6 | ||
|
|
574fab54d1 |
@@ -27,6 +27,19 @@
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off"
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
{
|
||||
"name": "styled-components",
|
||||
"message": "Please import from styled-components/macro."
|
||||
}
|
||||
],
|
||||
"patterns": ["!styled-components/macro"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
.github/workflows/integration-tests.yaml
vendored
1
.github/workflows/integration-tests.yaml
vendored
@@ -46,5 +46,6 @@ jobs:
|
||||
- run: yarn integration-test
|
||||
env:
|
||||
CYPRESS_INTEGRATION_TEST_PRIVATE_KEY: ${{ secrets.CYPRESS_INTEGRATION_TEST_PRIVATE_KEY }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
|
||||
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,6 +7,7 @@
|
||||
/src/locales/**/*.ts
|
||||
/src/locales/**/*.json
|
||||
/src/locales/**/en-US.po
|
||||
/src/state/data/generated.ts
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
@@ -39,4 +40,4 @@ package-lock.json
|
||||
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
cypress/fixtures/example.json
|
||||
cypress/fixtures/example.json
|
||||
|
||||
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
/src/state/data/generated.ts
|
||||
@@ -1,46 +1,75 @@
|
||||
# Development
|
||||
|
||||
## Install Dependencies
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Generate locale files
|
||||
|
||||
```
|
||||
yarn i18n:extract
|
||||
```
|
||||
|
||||
## Run the interface
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
# Contributing
|
||||
|
||||
Thank you for your interest in contributing to the Uniswap interface! 🦄
|
||||
|
||||
# Development
|
||||
|
||||
## Running the interface locally
|
||||
|
||||
1. `yarn install`
|
||||
1. `yarn start`
|
||||
|
||||
## Creating a production build
|
||||
|
||||
1. `yarn install`
|
||||
1. `yarn build`
|
||||
|
||||
## Engineering standards
|
||||
|
||||
Code merged into the `main` branch of this repository should adhere to high standards of correctness and maintainability.
|
||||
Use your best judgment when applying these standards. If code is in the critical path, will be frequently visited, or
|
||||
makes large architectural changes, consider following all the standards.
|
||||
|
||||
- Have at least one engineer approve of large code refactorings
|
||||
- At least manually test small code changes, prefer automated tests
|
||||
- Thoroughly unit test when code is not obviously correct
|
||||
- If something breaks, add automated tests so it doesn't break again
|
||||
- Add integration tests for new pages or flows
|
||||
- Verify that all CI checks pass before merging
|
||||
- Have at least one product manager or designer approve of significant UX changes
|
||||
|
||||
## Guidelines
|
||||
|
||||
The following points should help guide your development:
|
||||
|
||||
- Security: the interface is safe to use
|
||||
- Avoid adding unnecessary dependencies due to [supply chain risk](https://github.com/LavaMoat/lavamoat#further-reading-on-software-supplychain-security)
|
||||
- Reproducibility: anyone can build the interface
|
||||
- Avoid adding steps to the development/build processes
|
||||
- The build must be deterministic, i.e. a particular commit hash always produces the same build
|
||||
- Decentralization: anyone can run the interface
|
||||
- An Ethereum node should be the only critical dependency
|
||||
- All other external dependencies should only enhance the UX ([graceful degradation](https://developer.mozilla.org/en-US/docs/Glossary/Graceful_degradation))
|
||||
- Accessibility: anyone can use the interface
|
||||
- The interface should be responsive, small and run well on low performance devices (majority of swaps on mobile!)
|
||||
|
||||
## Release process
|
||||
|
||||
Releases are cut automatically from the `main` branch Monday-Thursday in the morning according to the [release workflow](./.github/workflows/release.yaml).
|
||||
|
||||
Fix pull requests should be merged whenever ready and tested.
|
||||
If a fix is urgently needed in production, releases can be manually triggered on [GitHub](https://github.com/Uniswap/uniswap-interface/actions/workflows/release.yaml)
|
||||
after the fix is merged into `main`.
|
||||
|
||||
Features should not be merged into `main` until they are ready for users.
|
||||
When building larger features or collaborating with other developers, create a new branch from `main` to track its development.
|
||||
Use the automatic Vercel preview for sharing the feature to collect feedback.
|
||||
When the feature is ready for review, create a new pull request from the feature branch into `main` and request reviews from
|
||||
the appropriate UX reviewers (PMs or designers).
|
||||
|
||||
## Finding a first issue
|
||||
|
||||
Start with issues with the label
|
||||
[`good first issue`](https://github.com/Uniswap/uniswap-interface/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
|
||||
|
||||
## Pull requests
|
||||
|
||||
**Please open all pull requests against the `main` branch.**
|
||||
CI checks will run against all PRs.
|
||||
|
||||
# Translations
|
||||
|
||||
Help Uniswap reach a global audience!
|
||||
Uniswap uses [Crowdin](https://crowdin.com/project/uniswap-interface) for managing translations.
|
||||
[This workflow](./.github/workflows/crowdin.yaml) uploads new strings for translation to the Crowdin project whenever code using the [lingui translation macros](https://lingui.js.org/ref/macro.html) is merged into `main`.
|
||||
|
||||
Uniswap uses [Crowdin](https://crowdin.com/project/uniswap-interface)
|
||||
for managing translations. Whenever a new string is added to the project,
|
||||
it gets uploaded to Crowdin for translation by [this workflow](./.github/workflows/crowdin.yaml).
|
||||
|
||||
Every hour, translations are synced from Crowdin in [this other workflow](./.github/workflows/crowdin-sync.yaml).
|
||||
Every hour, translations are synced back down from Crowdin to the repository in [this other workflow](./.github/workflows/crowdin-sync.yaml).
|
||||
We sync to the repository on a schedule, rather than download translations at build time, so that builds are always reproducible.
|
||||
|
||||
You can contribute by joining Crowdin to proofread existing translations [here](https://crowdin.com/project/uniswap-interface/invite?d=93i5n413q403t4g473p443o4c3t2g3s21343u2c3n403l4b3v2735353i4g4k4l4g453j4g4o4j4e4k4b323l4a3h463s4g453q443m4e3t2b303s2a35353l403o443v293e303k4g4n4r4g483i4g4r4j4e4o473i5n4a3t463t4o4)
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ or visit [app.uniswap.org](https://app.uniswap.org).
|
||||
|
||||
## Contributions
|
||||
|
||||
See [CONTRIBUTING](./CONTRIBUTING.md).
|
||||
For steps on local deployment, development, and code contribution, please see [CONTRIBUTING](./CONTRIBUTING.md).
|
||||
|
||||
## Accessing Uniswap V2
|
||||
|
||||
|
||||
11
codegen.yml
Normal file
11
codegen.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
overrideExisting: true
|
||||
schema: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'
|
||||
documents: 'src/**/!(*.d).{ts,tsx}'
|
||||
generates:
|
||||
./src/state/data/generated.ts:
|
||||
plugins:
|
||||
- typescript
|
||||
- typescript-operations
|
||||
- typescript-rtk-query:
|
||||
importBaseApiFrom: './slice'
|
||||
exportHooks: true
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"projectId": "yp82ef",
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"pluginsFile": false,
|
||||
"fixturesFolder": false,
|
||||
"supportFile": "cypress/support/index.js",
|
||||
"video": false,
|
||||
"defaultCommandTimeout": 10000
|
||||
|
||||
25
cypress/fixtures/feeTierDistribution.json
Normal file
25
cypress/fixtures/feeTierDistribution.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"_meta": {
|
||||
"block": {
|
||||
"number": 99999999
|
||||
}
|
||||
},
|
||||
"asToken0": [
|
||||
{
|
||||
"feeTier": "500",
|
||||
"totalValueLockedToken0": "0",
|
||||
"totalValueLockedToken1": "1"
|
||||
},
|
||||
{
|
||||
"feeTier": "3000",
|
||||
"totalValueLockedToken0": "0",
|
||||
"totalValueLockedToken1": "7"
|
||||
},
|
||||
{
|
||||
"feeTier": "10000",
|
||||
"totalValueLockedToken0": "0",
|
||||
"totalValueLockedToken1": "2"
|
||||
}
|
||||
],
|
||||
"asToken1": []
|
||||
}
|
||||
@@ -1,4 +1,13 @@
|
||||
import { CyHttpMessages } from 'cypress/types/net-stubbing'
|
||||
import { aliasQuery, hasQuery } from '../utils/graphql-test-utils'
|
||||
|
||||
describe('Add Liquidity', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req) => {
|
||||
aliasQuery(req, 'feeTierDistribution')
|
||||
})
|
||||
})
|
||||
|
||||
it('loads the two correct tokens', () => {
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab/500')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
|
||||
@@ -23,4 +32,32 @@ describe('Add Liquidity', () => {
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('loads fee tier distribution', () => {
|
||||
cy.fixture('feeTierDistribution.json').then((feeTierDistribution) => {
|
||||
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req: CyHttpMessages.IncomingHttpRequest) => {
|
||||
if (hasQuery(req, 'feeTierDistribution')) {
|
||||
req.alias = 'feeTierDistributionQuery'
|
||||
|
||||
req.reply({
|
||||
body: {
|
||||
data: {
|
||||
...feeTierDistribution,
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
'access-control-allow-origin': '*',
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
|
||||
cy.wait('@feeTierDistributionQuery')
|
||||
|
||||
cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.3% fee tier')
|
||||
cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '70%')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ describe('Landing Page', () => {
|
||||
beforeEach(() => cy.visit('/'))
|
||||
it('loads swap page', () => {
|
||||
cy.get('#swap-page')
|
||||
cy.screenshot()
|
||||
})
|
||||
|
||||
it('redirects to url /swap', () => {
|
||||
|
||||
@@ -8,7 +8,9 @@ import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
import { Wallet } from '@ethersproject/wallet'
|
||||
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||
|
||||
const TEST_PRIVATE_KEY = Cypress.env('INTEGRATION_TEST_PRIVATE_KEY')
|
||||
// todo: figure out how env vars actually work in CI
|
||||
// const TEST_PRIVATE_KEY = Cypress.env('INTEGRATION_TEST_PRIVATE_KEY')
|
||||
const TEST_PRIVATE_KEY = '0xe580410d7c37d26c6ad1a837bbae46bc27f9066a466fb3a66e770523b4666d19'
|
||||
|
||||
// address of the above key
|
||||
export const TEST_ADDRESS_NEVER_USE = new Wallet(TEST_PRIVATE_KEY).address
|
||||
|
||||
12
cypress/utils/graphql-test-utils.ts
Normal file
12
cypress/utils/graphql-test-utils.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
// Utility to match GraphQL mutation based on the query name
|
||||
export const hasQuery = (req: any, queryName: string) => {
|
||||
const { body } = req
|
||||
return body.hasOwnProperty('query') && body.query.includes(queryName)
|
||||
}
|
||||
|
||||
// Alias query if queryName matches
|
||||
export const aliasQuery = (req: any, queryName: string) => {
|
||||
if (hasQuery(req, queryName)) {
|
||||
req.alias = `${queryName}Query`
|
||||
}
|
||||
}
|
||||
38
package.json
38
package.json
@@ -5,6 +5,11 @@
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@ethersproject/experimental": "^5.2.0",
|
||||
"@gnosis.pm/safe-apps-web3-react": "^0.6.0",
|
||||
"@graphql-codegen/cli": "1.21.5",
|
||||
"@graphql-codegen/typescript": "1.22.3",
|
||||
"@graphql-codegen/typescript-operations": "^1.18.2",
|
||||
"@graphql-codegen/typescript-rtk-query": "^1.1.1",
|
||||
"@lingui/cli": "^3.9.0",
|
||||
"@lingui/loader": "^3.9.0",
|
||||
"@lingui/macro": "^3.9.0",
|
||||
@@ -14,14 +19,16 @@
|
||||
"@reach/dialog": "^0.10.3",
|
||||
"@reach/portal": "^0.10.3",
|
||||
"@react-hook/window-scroll": "^1.3.0",
|
||||
"@reduxjs/toolkit": "^1.3.5",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@typechain/ethers-v5": "^7.0.0",
|
||||
"@types/d3": "^6.7.1",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/lingui__core": "^2.7.1",
|
||||
"@types/lingui__macro": "^2.7.4",
|
||||
"@types/lingui__react": "^2.8.3",
|
||||
"@types/lodash.flatmap": "^4.5.6",
|
||||
"@types/luxon": "^1.24.4",
|
||||
"@types/ms.macro": "^2.0.0",
|
||||
"@types/multicodec": "^1.0.0",
|
||||
"@types/node": "^13.13.5",
|
||||
"@types/qs": "^6.9.2",
|
||||
@@ -42,34 +49,37 @@
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.19",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.25",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v2-sdk": "^3.0.0-alpha.2",
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "1.0.0",
|
||||
"@uniswap/v3-sdk": "^3.0.0-alpha.9",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.2.1",
|
||||
"@web3-react/core": "^6.0.9",
|
||||
"@web3-react/fortmatic-connector": "^6.0.9",
|
||||
"@web3-react/injected-connector": "^6.0.7",
|
||||
"@web3-react/portis-connector": "^6.0.9",
|
||||
"@web3-react/walletconnect-connector": "^6.2.0",
|
||||
"@web3-react/walletlink-connector": "^6.2.0",
|
||||
"@web3-react/walletlink-connector": "^6.2.3",
|
||||
"ajv": "^6.12.3",
|
||||
"cids": "^1.0.0",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"cypress": "^4.11.0",
|
||||
"cypress": "^7.7.0",
|
||||
"d3": "^7.0.0",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-react": "^7.19.0",
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"ethers": "^5.2.0",
|
||||
"graphql": "^15.5.0",
|
||||
"graphql-request": "^3.4.0",
|
||||
"inter-ui": "^3.13.1",
|
||||
"lightweight-charts": "^3.3.0",
|
||||
"lodash.flatmap": "^4.5.0",
|
||||
"luxon": "^1.25.0",
|
||||
"ms.macro": "^2.0.0",
|
||||
"multicodec": "^3.0.1",
|
||||
"multihashes": "^4.0.2",
|
||||
"node-vibrant": "^3.1.5",
|
||||
@@ -82,6 +92,7 @@
|
||||
"react-dom": "^17.0.1",
|
||||
"react-feather": "^2.0.8",
|
||||
"react-ga": "^2.5.7",
|
||||
"react-is": "^17.0.2",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-popper": "^2.2.3",
|
||||
"react-redux": "^7.2.2",
|
||||
@@ -92,10 +103,11 @@
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
"react-window": "^1.8.5",
|
||||
"rebass": "^4.0.7",
|
||||
"redux-devtools-extension": "^2.13.9",
|
||||
"redux-localstorage-simple": "^2.3.1",
|
||||
"serve": "^11.3.2",
|
||||
"start-server-and-test": "^1.11.0",
|
||||
"styled-components": "^4.2.0",
|
||||
"styled-components": "^5.3.0",
|
||||
"styled-system": "^5.1.5",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.2.3",
|
||||
@@ -109,19 +121,21 @@
|
||||
"workbox-strategies": "^6.1.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@walletconnect/web3-provider": "1.4.2-rc.2"
|
||||
"@walletconnect/web3-provider": "1.5.0-rc.5"
|
||||
},
|
||||
"scripts": {
|
||||
"compile-contract-types": "yarn compile-external-abi-types && yarn compile-v3-contract-types",
|
||||
"compile-external-abi-types": "typechain --target ethers-v5 --out-dir src/abis/types './src/abis/**/*.json'",
|
||||
"compile-v3-contract-types": "typechain --target ethers-v5 --out-dir src/types/v3 './node_modules/@uniswap/?(v3-core|v3-periphery)/artifacts/contracts/**/*.json'",
|
||||
"build": "yarn compile-contract-types && yarn i18n:extract && yarn i18n:compile && react-scripts build",
|
||||
"build": "yarn compile-contract-types && yarn graphql:generate && yarn i18n:extract && yarn i18n:compile && react-scripts build",
|
||||
"i18n:extract": "lingui extract --locale en-US",
|
||||
"i18n:compile": "lingui compile",
|
||||
"integration-test": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run'",
|
||||
"integration-test": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run --record'",
|
||||
"graphql:generate": "graphql-codegen --config codegen.yml",
|
||||
"postinstall": "yarn compile-contract-types",
|
||||
"start": "yarn compile-contract-types && react-scripts start",
|
||||
"test": "react-scripts test --env=jsdom"
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"prestart": "yarn graphql:generate && touch src/locales/en-US.po"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app",
|
||||
|
||||
11
public/images/256x256_App_Icon_Pink.svg
Normal file
11
public/images/256x256_App_Icon_Pink.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="257" height="256" viewBox="0 0 257 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M96.4347 60.2768C94.3434 59.9524 94.2552 59.9143 95.2394 59.7631C97.1254 59.473 101.579 59.8683 104.648 60.5981C111.812 62.3011 118.331 66.6637 125.29 74.4117L127.139 76.4701L129.784 76.0449C140.926 74.2544 152.26 75.6774 161.741 80.057C164.349 81.2619 168.461 83.6603 168.975 84.2766C169.138 84.473 169.439 85.7374 169.642 87.0866C170.347 91.7544 169.994 95.3323 168.567 98.0046C167.79 99.4588 167.746 99.9197 168.268 101.164C168.686 102.157 169.848 102.892 170.999 102.891C173.355 102.888 175.891 99.0791 177.066 93.78L177.532 91.6751L178.457 92.7224C183.528 98.4681 187.512 106.304 188.196 111.881L188.374 113.336L187.522 112.014C186.054 109.739 184.58 108.19 182.693 106.941C179.29 104.69 175.693 103.923 166.164 103.421C157.559 102.968 152.689 102.233 147.859 100.658C139.643 97.9792 135.501 94.4114 125.74 81.6059C121.405 75.918 118.726 72.7711 116.06 70.2368C110.004 64.4784 104.053 61.4584 96.4347 60.2768Z" fill="#FF007A"/>
|
||||
<path d="M170.916 72.9763C171.132 69.1649 171.649 66.651 172.688 64.3552C173.099 63.4465 173.485 62.7027 173.544 62.7027C173.604 62.7027 173.425 63.3735 173.147 64.1931C172.391 66.4212 172.267 69.4687 172.788 73.0144C173.448 77.5132 173.824 78.1623 178.579 83.022C180.809 85.3014 183.403 88.1762 184.344 89.4105L186.054 91.6547L184.344 90.0508C182.253 88.0895 177.444 84.2644 176.381 83.7176C175.669 83.3509 175.563 83.3573 175.124 83.7946C174.719 84.1975 174.634 84.803 174.577 87.6654C174.49 92.1267 173.882 94.9901 172.414 97.8533C171.621 99.4019 171.495 99.0714 172.214 97.3235C172.75 96.0184 172.805 95.4446 172.801 91.1259C172.792 82.4485 171.762 80.3624 165.721 76.7887C164.19 75.8834 161.668 74.5778 160.117 73.8872C158.565 73.1965 157.333 72.595 157.378 72.5501C157.549 72.3798 163.441 74.0995 165.812 75.0117C169.339 76.3686 169.922 76.5444 170.35 76.3807C170.637 76.271 170.776 75.4347 170.916 72.9763Z" fill="#FF007A"/>
|
||||
<path d="M100.497 87.8209C96.2514 81.9758 93.6246 73.0138 94.1933 66.3144L94.3691 64.2413L95.3355 64.4176C97.1504 64.7486 100.28 65.9133 101.745 66.8033C105.766 69.2453 107.506 72.4605 109.277 80.7164C109.796 83.1346 110.477 85.8712 110.79 86.7976C111.294 88.2889 113.199 91.7721 114.748 94.0343C115.864 95.6636 115.123 96.4356 112.657 96.213C108.89 95.873 103.788 92.3519 100.497 87.8209Z" fill="#FF007A"/>
|
||||
<path d="M165.766 131.323C145.925 123.335 138.937 116.4 138.937 104.7C138.937 102.979 138.996 101.57 139.068 101.57C139.14 101.57 139.908 102.138 140.774 102.833C144.797 106.06 149.303 107.438 161.776 109.258C169.115 110.328 173.245 111.193 177.056 112.457C189.166 116.473 196.658 124.624 198.445 135.725C198.964 138.951 198.66 145 197.818 148.188C197.153 150.706 195.125 155.245 194.588 155.419C194.439 155.468 194.292 154.896 194.254 154.118C194.05 149.95 191.943 145.891 188.406 142.851C184.383 139.395 178.979 136.643 165.766 131.323Z" fill="#FF007A"/>
|
||||
<path d="M151.837 134.642C151.588 133.163 151.157 131.273 150.879 130.444L150.372 128.935L151.313 129.991C152.614 131.451 153.642 133.32 154.514 135.81C155.179 137.71 155.254 138.275 155.249 141.362C155.244 144.393 155.161 145.029 154.546 146.739C153.578 149.436 152.376 151.348 150.359 153.4C146.735 157.089 142.075 159.131 135.351 159.978C134.182 160.125 130.775 160.373 127.78 160.529C120.232 160.922 115.264 161.733 110.801 163.3C110.159 163.525 109.586 163.662 109.528 163.604C109.347 163.425 112.386 161.613 114.897 160.404C118.436 158.699 121.96 157.768 129.854 156.454C133.754 155.804 137.781 155.016 138.804 154.702C148.461 151.741 153.426 144.1 151.837 134.642Z" fill="#FF007A"/>
|
||||
<path d="M160.932 150.795C158.296 145.128 157.691 139.657 159.135 134.554C159.289 134.009 159.538 133.562 159.687 133.562C159.837 133.562 160.459 133.899 161.07 134.31C162.284 135.127 164.721 136.505 171.212 140.044C179.311 144.46 183.929 147.879 187.07 151.786C189.82 155.208 191.522 159.104 192.341 163.856C192.805 166.548 192.533 173.024 191.843 175.735C189.665 184.281 184.604 190.993 177.385 194.911C176.327 195.484 175.377 195.955 175.275 195.958C175.172 195.96 175.557 194.98 176.131 193.78C178.56 188.703 178.836 183.765 177 178.269C175.876 174.904 173.584 170.797 168.956 163.857C163.575 155.788 162.256 153.641 160.932 150.795Z" fill="#FF007A"/>
|
||||
<path d="M86.4067 181.371C93.7696 175.154 102.931 170.738 111.276 169.382C114.872 168.798 120.864 169.03 124.194 169.882C129.532 171.248 134.307 174.308 136.791 177.954C139.218 181.517 140.259 184.622 141.343 191.53C141.771 194.255 142.236 196.992 142.377 197.611C143.191 201.192 144.775 204.054 146.739 205.491C149.857 207.773 155.227 207.915 160.509 205.855C161.405 205.505 162.184 205.263 162.238 205.318C162.43 205.508 159.77 207.288 157.894 208.226C155.369 209.487 153.361 209.975 150.693 209.975C145.855 209.975 141.839 207.514 138.487 202.495C137.828 201.508 136.346 198.55 135.193 195.922C131.655 187.851 129.907 185.393 125.799 182.702C122.223 180.361 117.612 179.941 114.143 181.642C109.586 183.876 108.315 189.699 111.579 193.39C112.876 194.856 115.295 196.121 117.273 196.367C120.973 196.828 124.153 194.013 124.153 190.277C124.153 187.851 123.221 186.467 120.873 185.407C117.668 183.961 114.222 185.652 114.238 188.663C114.245 189.947 114.805 190.754 116.092 191.336C116.918 191.71 116.937 191.74 116.263 191.6C113.322 190.99 112.633 187.444 114.998 185.09C117.837 182.264 123.709 183.511 125.725 187.368C126.572 188.988 126.67 192.215 125.932 194.164C124.279 198.525 119.46 200.819 114.571 199.571C111.243 198.721 109.887 197.801 105.874 193.667C98.9012 186.484 96.1941 185.092 86.141 183.523L84.2146 183.222L86.4067 181.371Z" fill="#FF007A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.8241 20.4645C57.1114 48.7285 93.0139 92.734 94.7963 95.1977C96.2679 97.232 95.7141 99.0609 93.1929 100.494C91.7909 101.291 88.9084 102.099 87.4652 102.099C85.8329 102.099 83.9939 101.313 82.6548 100.042C81.7084 99.1447 77.8882 93.4402 69.0694 79.7566C62.3216 69.2863 56.6747 60.6007 56.5206 60.4553C56.1644 60.119 56.1705 60.1303 68.3813 81.9787C76.0487 95.6979 78.6371 100.548 78.6371 101.197C78.6371 102.516 78.2771 103.21 76.6495 105.025C73.9363 108.052 72.7235 111.453 71.8479 118.492C70.8664 126.382 68.1064 131.955 60.4577 141.494C55.9803 147.078 55.2477 148.102 54.118 150.352C52.695 153.187 52.3037 154.774 52.1451 158.353C51.9775 162.137 52.3039 164.581 53.46 168.199C54.4721 171.366 55.5285 173.458 58.2292 177.641C60.5599 181.251 61.9019 183.933 61.9019 184.983C61.9019 185.818 62.0613 185.819 65.6729 185.003C74.316 183.052 81.3341 179.619 85.2812 175.412C87.724 172.808 88.2975 171.371 88.3161 167.802C88.3283 165.469 88.2462 164.98 87.6153 163.637C86.5884 161.452 84.7188 159.635 80.5983 156.818C75.1992 153.127 72.8932 150.156 72.2562 146.07C71.7337 142.717 72.3399 140.351 75.3267 134.091C78.4182 127.612 79.1843 124.85 79.7025 118.319C80.0372 114.1 80.5008 112.435 81.7131 111.1C82.9776 109.707 84.1158 109.235 87.245 108.808C92.3466 108.111 95.5951 106.791 98.2652 104.33C100.582 102.196 101.551 100.139 101.7 97.0427L101.813 94.6959L100.518 93.1867C95.8304 87.7209 30.6848 16.168 30.3963 16.168C30.3347 16.168 31.8773 18.1015 33.8241 20.4645ZM64.5333 162.634C65.5932 160.757 65.0301 158.344 63.2572 157.166C61.5821 156.052 58.9799 156.576 58.9799 158.028C58.9799 158.471 59.2248 158.793 59.7768 159.077C60.7063 159.555 60.7738 160.093 60.0425 161.192C59.3019 162.306 59.3616 163.284 60.2111 163.949C61.5802 165.021 63.5183 164.432 64.5333 162.634Z" fill="#FF007A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M105.032 110.039C102.637 110.774 100.309 113.312 99.5884 115.974C99.1487 117.598 99.3982 120.446 100.057 121.325C101.121 122.746 102.149 123.12 104.935 123.101C110.389 123.063 115.131 120.724 115.682 117.799C116.134 115.402 114.051 112.08 111.183 110.622C109.703 109.87 106.555 109.571 105.032 110.039ZM111.408 115.024C112.249 113.829 111.881 112.537 110.451 111.664C107.726 110 103.607 111.377 103.607 113.95C103.607 115.231 105.755 116.629 107.724 116.629C109.035 116.629 110.829 115.847 111.408 115.024Z" fill="#FF007A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.9 KiB |
@@ -63,7 +63,6 @@
|
||||
pointer-events: none;
|
||||
width: 200vw;
|
||||
height: 200vh;
|
||||
mix-blend-mode: color;
|
||||
background: radial-gradient(50% 50% at 50% 50%, #fc077d10 0%, rgba(255, 255, 255, 0) 100%);
|
||||
transform: translate(-50vw, -100vh);
|
||||
z-index: -1;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"background_color": "#fff",
|
||||
"display": "standalone",
|
||||
"homepage_url": "https://app.uniswap.org",
|
||||
"providedBy": { "name": "Uniswap", "url": "https://uniswap.org" },
|
||||
"icons": [
|
||||
{
|
||||
"src": "./images/192x192_App_Icon.png",
|
||||
@@ -18,6 +19,8 @@
|
||||
],
|
||||
"orientation": "portrait",
|
||||
"name": "Uniswap",
|
||||
"description": "Swap or provide liquidity on the Uniswap Protocol",
|
||||
"iconPath": "./images/256x256_App_Icon_Pink.svg",
|
||||
"short_name": "Uniswap",
|
||||
"start_url": ".",
|
||||
"theme_color": "#ff007a"
|
||||
|
||||
@@ -1,330 +0,0 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "callData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "aggregate",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "blockNumber",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes[]",
|
||||
"name": "returnData",
|
||||
"type": "bytes[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "callData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "blockAndAggregate",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "blockNumber",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "blockHash",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "returnData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "blockNumber",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getBlockHash",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "blockHash",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "returnData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getBlockNumber",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "blockNumber",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockCoinbase",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "coinbase",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockDifficulty",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "difficulty",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockGasLimit",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "gaslimit",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockTimestamp",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "timestamp",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "addr",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "getEthBalance",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getLastBlockHash",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "blockHash",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "requireSuccess",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "callData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "tryAggregate",
|
||||
"outputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "returnData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "requireSuccess",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "callData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "tryBlockAndAggregate",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "blockNumber",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "blockHash",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "returnData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
@@ -1,165 +0,0 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "target", "type": "address" },
|
||||
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "aggregate",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
|
||||
{ "internalType": "bytes[]", "name": "returnData", "type": "bytes[]" }
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "target", "type": "address" },
|
||||
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "blockAndAggregate",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
|
||||
{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" },
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "bool", "name": "success", "type": "bool" },
|
||||
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" }],
|
||||
"name": "getBlockHash",
|
||||
"outputs": [{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getBlockNumber",
|
||||
"outputs": [{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockCoinbase",
|
||||
"outputs": [{ "internalType": "address", "name": "coinbase", "type": "address" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockDifficulty",
|
||||
"outputs": [{ "internalType": "uint256", "name": "difficulty", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockGasLimit",
|
||||
"outputs": [{ "internalType": "uint256", "name": "gaslimit", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getCurrentBlockTimestamp",
|
||||
"outputs": [{ "internalType": "uint256", "name": "timestamp", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{ "internalType": "address", "name": "addr", "type": "address" }],
|
||||
"name": "getEthBalance",
|
||||
"outputs": [{ "internalType": "uint256", "name": "balance", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getLastBlockHash",
|
||||
"outputs": [{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "bool", "name": "requireSuccess", "type": "bool" },
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "target", "type": "address" },
|
||||
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "tryAggregate",
|
||||
"outputs": [
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "bool", "name": "success", "type": "bool" },
|
||||
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "bool", "name": "requireSuccess", "type": "bool" },
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "address", "name": "target", "type": "address" },
|
||||
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Call[]",
|
||||
"name": "calls",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "tryBlockAndAggregate",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
|
||||
{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" },
|
||||
{
|
||||
"components": [
|
||||
{ "internalType": "bool", "name": "success", "type": "bool" },
|
||||
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
|
||||
],
|
||||
"internalType": "struct Multicall2.Result[]",
|
||||
"name": "returnData",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
@@ -1,471 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "Transfer",
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_from",
|
||||
"indexed": true
|
||||
},
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_to",
|
||||
"indexed": true
|
||||
},
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_tokenId",
|
||||
"indexed": true
|
||||
}
|
||||
],
|
||||
"anonymous": false,
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"name": "Approval",
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_owner",
|
||||
"indexed": true
|
||||
},
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_approved",
|
||||
"indexed": true
|
||||
},
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_tokenId",
|
||||
"indexed": true
|
||||
}
|
||||
],
|
||||
"anonymous": false,
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"name": "ApprovalForAll",
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_owner",
|
||||
"indexed": true
|
||||
},
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_operator",
|
||||
"indexed": true
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "_approved",
|
||||
"indexed": false
|
||||
}
|
||||
],
|
||||
"anonymous": false,
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"outputs": [],
|
||||
"inputs": [],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"name": "tokenURI",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_tokenId"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "22405"
|
||||
},
|
||||
{
|
||||
"name": "tokenByIndex",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_index"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "631"
|
||||
},
|
||||
{
|
||||
"name": "tokenOfOwnerByIndex",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_owner"
|
||||
},
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_index"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "1248"
|
||||
},
|
||||
{
|
||||
"name": "transferFrom",
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_from"
|
||||
},
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_to"
|
||||
},
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_tokenId"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "259486"
|
||||
},
|
||||
{
|
||||
"name": "safeTransferFrom",
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_from"
|
||||
},
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_to"
|
||||
},
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_tokenId"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"name": "safeTransferFrom",
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_from"
|
||||
},
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_to"
|
||||
},
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_tokenId"
|
||||
},
|
||||
{
|
||||
"type": "bytes",
|
||||
"name": "_data"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"name": "approve",
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_approved"
|
||||
},
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "_tokenId"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "38422"
|
||||
},
|
||||
{
|
||||
"name": "setApprovalForAll",
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_operator"
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "_approved"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "38016"
|
||||
},
|
||||
{
|
||||
"name": "mint",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_to"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "182636"
|
||||
},
|
||||
{
|
||||
"name": "changeMinter",
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_minter"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "35897"
|
||||
},
|
||||
{
|
||||
"name": "changeURI",
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "_newURI"
|
||||
}
|
||||
],
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "35927"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "6612"
|
||||
},
|
||||
{
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "6642"
|
||||
},
|
||||
{
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "873"
|
||||
},
|
||||
{
|
||||
"name": "minter",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "903"
|
||||
},
|
||||
{
|
||||
"name": "socks",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "out",
|
||||
"unit": "Socks"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "933"
|
||||
},
|
||||
{
|
||||
"name": "newURI",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "963"
|
||||
},
|
||||
{
|
||||
"name": "ownerOf",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "arg0"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "1126"
|
||||
},
|
||||
{
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "arg0"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "1195"
|
||||
},
|
||||
{
|
||||
"name": "getApproved",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "uint256",
|
||||
"name": "arg0"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "1186"
|
||||
},
|
||||
{
|
||||
"name": "isApprovedForAll",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "address",
|
||||
"name": "arg0"
|
||||
},
|
||||
{
|
||||
"type": "address",
|
||||
"name": "arg1"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "1415"
|
||||
},
|
||||
{
|
||||
"name": "supportsInterface",
|
||||
"outputs": [
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "out"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"type": "bytes32",
|
||||
"name": "arg0"
|
||||
}
|
||||
],
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": "1246"
|
||||
}
|
||||
]
|
||||
181
src/assets/svg/arbitrum_logo.svg
Normal file
181
src/assets/svg/arbitrum_logo.svg
Normal file
@@ -0,0 +1,181 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 470.287 514.251" enable-background="new 0 0 470.287 514.251" xml:space="preserve">
|
||||
<g id="Background">
|
||||
</g>
|
||||
<g id="Logos_and_symbols">
|
||||
<g id="SYMBOL_VER_3">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_3_3_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_4">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_4_1_">
|
||||
<g id="SYMBOL_VER_4_3_">
|
||||
</g>
|
||||
</g>
|
||||
<g id="SYMBOL_VER_5_1_">
|
||||
</g>
|
||||
<g id="off_2_1_">
|
||||
</g>
|
||||
<g id="VER_3_1_">
|
||||
<g id="SYMBOL_VER_2_1_">
|
||||
</g>
|
||||
</g>
|
||||
<g id="VER_3">
|
||||
<g id="SYMBOL_VER_2">
|
||||
</g>
|
||||
</g>
|
||||
<g id="off_2">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_5">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_1">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_1_1_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_1-1_3_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_1-1_2_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_1-1">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_1-1_1_">
|
||||
<g id="_x31_-3">
|
||||
</g>
|
||||
<g id="Symbol_-_Original_14_">
|
||||
<path fill="#2D374B" d="M291.134,237.469l35.654-60.5l96.103,149.684l0.046,28.727l-0.313-197.672
|
||||
c-0.228-4.832-2.794-9.252-6.887-11.859L242.715,46.324c-4.045-1.99-9.18-1.967-13.22,0.063c-0.546,0.272-1.06,0.57-1.548,0.895
|
||||
l-0.604,0.379L59.399,144.983l-0.651,0.296c-0.838,0.385-1.686,0.875-2.48,1.444c-3.185,2.283-5.299,5.66-5.983,9.448
|
||||
c-0.103,0.574-0.179,1.158-0.214,1.749l0.264,161.083l89.515-138.745c11.271-18.397,35.825-24.323,58.62-24.001l26.753,0.706
|
||||
L67.588,409.765l18.582,10.697L245.692,157.22l70.51-0.256L157.091,426.849l66.306,38.138l7.922,4.556
|
||||
c3.351,1.362,7.302,1.431,10.681,0.21l175.453-101.678l-33.544,19.438L291.134,237.469z M304.736,433.395l-66.969-105.108
|
||||
l40.881-69.371l87.952,138.628L304.736,433.395z"/>
|
||||
<polygon fill="#28A0F0" points="237.768,328.286 304.736,433.395 366.601,397.543 278.648,258.915 "/>
|
||||
<path fill="#28A0F0" d="M422.937,355.379l-0.046-28.727l-96.103-149.684l-35.654,60.5l92.774,150.043l33.544-19.438
|
||||
c3.29-2.673,5.281-6.594,5.49-10.825L422.937,355.379z"/>
|
||||
<path fill="#FFFFFF" d="M20.219,382.469l47.369,27.296l157.634-252.801l-26.753-0.706c-22.795-0.322-47.35,5.604-58.62,24.001
|
||||
L50.334,319.004l-30.115,46.271V382.469z"/>
|
||||
<polygon fill="#FFFFFF" points="316.202,156.964 245.692,157.22 86.17,420.462 141.928,452.565 157.091,426.849 "/>
|
||||
<path fill="#96BEDC" d="M452.65,156.601c-0.59-14.746-8.574-28.245-21.08-36.104L256.28,19.692
|
||||
c-12.371-6.229-27.825-6.237-40.218-0.004c-1.465,0.739-170.465,98.752-170.465,98.752c-2.339,1.122-4.592,2.458-6.711,3.975
|
||||
c-11.164,8.001-17.969,20.435-18.668,34.095v208.765l30.115-46.271L50.07,157.921c0.035-0.589,0.109-1.169,0.214-1.741
|
||||
c0.681-3.79,2.797-7.171,5.983-9.456c0.795-0.569,172.682-100.064,173.228-100.337c4.04-2.029,9.175-2.053,13.22-0.063
|
||||
l173.022,99.523c4.093,2.607,6.659,7.027,6.887,11.859v199.542c-0.209,4.231-1.882,8.152-5.172,10.825l-33.544,19.438
|
||||
l-17.308,10.031l-61.864,35.852l-62.737,36.357c-3.379,1.221-7.33,1.152-10.681-0.21l-74.228-42.693l-15.163,25.717
|
||||
l66.706,38.406c2.206,1.255,4.171,2.367,5.784,3.272c2.497,1.4,4.199,2.337,4.8,2.629c4.741,2.303,11.563,3.643,17.71,3.643
|
||||
c5.636,0,11.132-1.035,16.332-3.072l182.225-105.531c10.459-8.104,16.612-20.325,17.166-33.564V156.601z"/>
|
||||
</g>
|
||||
<g id="Symbol_-_Original_13_">
|
||||
</g>
|
||||
<g id="Symbol_-_Original_6_">
|
||||
</g>
|
||||
<g id="Symbol_-_Original_4_">
|
||||
</g>
|
||||
<g id="One_color_version_-_White_3_">
|
||||
<g id="Symbol_-_Original_15_">
|
||||
</g>
|
||||
</g>
|
||||
<g id="One_color_version_-_White">
|
||||
<g id="Symbol_-_Original">
|
||||
</g>
|
||||
</g>
|
||||
<g id="Symbol_-_Monochromatic_3_">
|
||||
<g id="_x33__7_">
|
||||
</g>
|
||||
</g>
|
||||
<g id="Symbol_-_Monochromatic">
|
||||
<g id="_x33__3_">
|
||||
</g>
|
||||
</g>
|
||||
<g id="_x33__2_">
|
||||
</g>
|
||||
<g id="_x33__1_">
|
||||
</g>
|
||||
<g id="_x33_">
|
||||
</g>
|
||||
<g id="Symbol_-_Original_10_">
|
||||
</g>
|
||||
<g id="Symbol_-_Original_1_">
|
||||
</g>
|
||||
<g id="Symbol_-_Original_2_">
|
||||
</g>
|
||||
<g id="_x34__1_">
|
||||
</g>
|
||||
<g id="Symbol_-_Monochromatic_2_">
|
||||
<g id="_x33__6_">
|
||||
</g>
|
||||
</g>
|
||||
<g id="One_color_version_-_White_2_">
|
||||
<g id="Symbol_-_Original_11_">
|
||||
</g>
|
||||
</g>
|
||||
<g id="Symbol_-_Original_5_">
|
||||
<g id="Symbol_-_Original_12_">
|
||||
</g>
|
||||
</g>
|
||||
<g id="One_color_version_-_White_1_">
|
||||
<g id="Symbol_-_Original_9_">
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SYMBOL_VER_1_2_">
|
||||
<g id="SYMBOL_VER_2_4_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_2-1-1_1_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_2-2-1_1_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_2-3-1_4_">
|
||||
</g>
|
||||
<g id="New_Symbol_1_">
|
||||
<g id="SYMBOL_VER_2-3-1_3_">
|
||||
</g>
|
||||
</g>
|
||||
<g id="New_Symbol">
|
||||
<g id="SYMBOL_VER_2-3-1_1_">
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="SYMBOL_VER_2_2_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_4_2_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_3_2_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_3_1_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_1-1-1_1_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_1-1-1">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_1-1-1_2_2_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_1-1-1_2">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_1-1-1_2_1_">
|
||||
</g>
|
||||
<g id="Symbol_-_Original_7_">
|
||||
</g>
|
||||
<g id="Symbol_-_Original_8_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_2-1-1">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_2-2-1">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_2-3-1">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_5-1_1_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_5-1">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_5-2_1_">
|
||||
</g>
|
||||
<g id="SYMBOL_VER_5-2">
|
||||
</g>
|
||||
<g id="Symbol_-_Monochromatic_1_">
|
||||
<g id="_x33__4_">
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
5
src/assets/svg/optimism_logo.svg
Normal file
5
src/assets/svg/optimism_logo.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="250" cy="250" r="250" fill="#FF0420"/>
|
||||
<path d="M177.133 316.446C162.247 316.446 150.051 312.943 140.544 305.938C131.162 298.808 126.471 288.676 126.471 275.541C126.471 272.789 126.784 269.411 127.409 265.408C129.036 256.402 131.35 245.581 134.352 232.947C142.858 198.547 164.812 181.347 200.213 181.347C209.845 181.347 218.476 182.973 226.107 186.225C233.738 189.352 239.742 194.106 244.12 200.486C248.498 206.74 250.688 214.246 250.688 223.002C250.688 225.629 250.375 228.944 249.749 232.947C247.873 244.08 245.621 254.901 242.994 265.408C238.616 282.546 231.048 295.368 220.29 303.874C209.532 312.255 195.147 316.446 177.133 316.446ZM179.76 289.426C186.766 289.426 192.707 287.362 197.586 283.234C202.59 279.106 206.155 272.789 208.281 264.283C211.158 252.524 213.348 242.266 214.849 233.51C215.349 230.883 215.599 228.194 215.599 225.441C215.599 214.058 209.657 208.366 197.774 208.366C190.768 208.366 184.764 210.43 179.76 214.558C174.882 218.687 171.379 225.004 169.253 233.51C167.001 241.891 164.749 252.149 162.498 264.283C161.997 266.784 161.747 269.411 161.747 272.163C161.747 283.672 167.752 289.426 179.76 289.426Z" fill="white"/>
|
||||
<path d="M259.303 314.57C257.927 314.57 256.863 314.132 256.113 313.256C255.487 312.255 255.3 311.13 255.55 309.879L281.444 187.914C281.694 186.538 282.382 185.412 283.508 184.536C284.634 183.661 285.822 183.223 287.073 183.223H336.985C350.87 183.223 362.003 186.1 370.384 191.854C378.891 197.609 383.144 205.927 383.144 216.81C383.144 219.937 382.769 223.19 382.018 226.567C378.891 240.953 372.574 251.586 363.067 258.466C353.685 265.346 340.8 268.786 324.413 268.786H299.082L290.451 309.879C290.2 311.255 289.512 312.38 288.387 313.256C287.261 314.132 286.072 314.57 284.822 314.57H259.303ZM325.727 242.892C330.98 242.892 335.546 241.453 339.424 238.576C343.427 235.699 346.054 231.571 347.305 226.192C347.68 224.065 347.868 222.189 347.868 220.563C347.868 216.935 346.805 214.183 344.678 212.307C342.551 210.305 338.924 209.305 333.795 209.305H311.278L304.148 242.892H325.727Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { CheckCircle, Triangle } from 'react-feather'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useContext } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import { useCallback, useContext } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { SUPPORTED_WALLETS } from '../../constants/wallet'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { clearAllTransactions } from '../../state/transactions/actions'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useContext, useCallback } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { useContext, useCallback, ReactNode } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import useENS from '../../hooks/useENS'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { ExternalLink, TYPE } from '../../theme'
|
||||
@@ -40,7 +41,7 @@ const Input = styled.input<{ error?: boolean }>`
|
||||
width: 0;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
transition: color 300ms ${({ error }) => (error ? 'step-end' : 'step-start')};
|
||||
color: ${({ error, theme }) => (error ? theme.red1 : theme.primary1)};
|
||||
color: ${({ error, theme }) => (error ? theme.red1 : theme.text1)};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
@@ -67,10 +68,16 @@ const Input = styled.input<{ error?: boolean }>`
|
||||
|
||||
export default function AddressInputPanel({
|
||||
id,
|
||||
className = 'recipient-address-input',
|
||||
label,
|
||||
placeholder,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
id?: string
|
||||
className?: string
|
||||
label?: ReactNode
|
||||
placeholder?: string
|
||||
// the typed string value
|
||||
value: string
|
||||
// triggers whenever the typed value changes
|
||||
@@ -99,25 +106,25 @@ export default function AddressInputPanel({
|
||||
<AutoColumn gap="md">
|
||||
<RowBetween>
|
||||
<TYPE.black color={theme.text2} fontWeight={500} fontSize={14}>
|
||||
Recipient
|
||||
{label ?? <Trans>Recipient</Trans>}
|
||||
</TYPE.black>
|
||||
{address && chainId && (
|
||||
<ExternalLink
|
||||
href={getExplorerLink(chainId, name ?? address, ExplorerDataType.ADDRESS)}
|
||||
style={{ fontSize: '14px' }}
|
||||
>
|
||||
(View on Explorer)
|
||||
<Trans>(View on Explorer)</Trans>
|
||||
</ExternalLink>
|
||||
)}
|
||||
</RowBetween>
|
||||
<Input
|
||||
className="recipient-address-input"
|
||||
className={className}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
placeholder="Wallet Address or ENS name"
|
||||
placeholder={placeholder ?? t`Wallet Address or ENS name`}
|
||||
error={error}
|
||||
pattern="^(0x[a-fA-F0-9]{40})$"
|
||||
onChange={handleInput}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from 'react'
|
||||
|
||||
import Badge, { BadgeVariant } from 'components/Badge'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
@@ -26,13 +24,6 @@ const ActiveDot = styled.span`
|
||||
margin-right: 4px;
|
||||
`
|
||||
|
||||
export const DarkBadge = styled.div`
|
||||
width: fit-content;
|
||||
border-radius: 8px;
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
padding: 4px 6px;
|
||||
`
|
||||
|
||||
export default function RangeBadge({
|
||||
removed,
|
||||
inRange,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { readableColor } from 'polished'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import styled, { DefaultTheme } from 'styled-components'
|
||||
import styled, { DefaultTheme } from 'styled-components/macro'
|
||||
import { Color } from 'theme/styled'
|
||||
|
||||
export enum BadgeVariant {
|
||||
@@ -13,7 +13,7 @@ export enum BadgeVariant {
|
||||
WARNING_OUTLINE = 'WARNING_OUTLINE',
|
||||
}
|
||||
|
||||
export interface BadgeProps {
|
||||
interface BadgeProps {
|
||||
variant?: BadgeVariant
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { ReactNode, useMemo } from 'react'
|
||||
import { ReactNode, useMemo } from 'react'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { Trans } from '@lingui/macro'
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { darken } from 'polished'
|
||||
|
||||
@@ -13,16 +12,15 @@ const Base = styled(RebassButton)<
|
||||
{
|
||||
padding?: string
|
||||
width?: string
|
||||
borderRadius?: string
|
||||
$borderRadius?: string
|
||||
altDisabledStyle?: boolean
|
||||
} & ButtonProps
|
||||
>`
|
||||
padding: ${({ padding }) => (padding ? padding : '16px')};
|
||||
width: ${({ width }) => (width ? width : '100%')};
|
||||
padding: ${({ padding }) => padding ?? '16px'};
|
||||
width: ${({ width }) => width ?? '100%'};
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
border-radius: 20px;
|
||||
border-radius: ${({ borderRadius }) => borderRadius && borderRadius};
|
||||
border-radius: ${({ $borderRadius }) => $borderRadius ?? '20px'};
|
||||
outline: none;
|
||||
border: 1px solid transparent;
|
||||
color: white;
|
||||
@@ -43,10 +41,6 @@ const Base = styled(RebassButton)<
|
||||
transition: transform 450ms ease;
|
||||
transform: perspective(1px) translateZ(0);
|
||||
|
||||
&:hover {
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
> * {
|
||||
user-select: none;
|
||||
}
|
||||
@@ -72,14 +66,12 @@ export const ButtonPrimary = styled(Base)`
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme, altDisabledStyle, disabled }) =>
|
||||
altDisabledStyle ? (disabled ? theme.primary1 : theme.primary1) : theme.primary1};
|
||||
color: white;
|
||||
altDisabledStyle ? (disabled ? theme.primary1 : theme.bg2) : theme.bg2};
|
||||
color: ${({ theme }) => theme.text2};
|
||||
cursor: auto;
|
||||
box-shadow: none;
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
opacity: 0.4;
|
||||
opacity: ${({ altDisabledStyle }) => (altDisabledStyle ? '0.5' : '0.4')};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -116,9 +108,7 @@ export const ButtonGray = styled(Base)`
|
||||
color: ${({ theme }) => theme.text2};
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
&:focus {
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg2)};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg2)};
|
||||
}
|
||||
@@ -155,63 +145,41 @@ export const ButtonSecondary = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonPink = styled(Base)`
|
||||
background-color: ${({ theme }) => theme.primary1};
|
||||
color: white;
|
||||
|
||||
export const ButtonOutlined = styled(Base)`
|
||||
border: 1px solid ${({ theme }) => theme.bg2};
|
||||
background-color: transparent;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.primary1)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.primary1)};
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
|
||||
}
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => darken(0.05, theme.primary1)};
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.primary1)};
|
||||
background-color: ${({ theme }) => darken(0.1, theme.primary1)};
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme }) => theme.primary1};
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonUNIGradient = styled(ButtonPrimary)`
|
||||
export const ButtonYellow = styled(Base)`
|
||||
background-color: ${({ theme }) => theme.yellow3};
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
height: 36px;
|
||||
font-weight: 500;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
background: radial-gradient(174.47% 188.91% at 1.84% 0%, #ff007a 0%, #2172e5 100%), #edeef2;
|
||||
width: fit-content;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
white-space: no-wrap;
|
||||
:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
:active {
|
||||
opacity: 0.9;
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonOutlined = styled(Base)`
|
||||
border: 1px solid ${({ theme }) => theme.bg2};
|
||||
background-color: transparent;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.yellow3)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.yellow3)};
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.yellow3)};
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.yellow3)};
|
||||
background-color: ${({ theme }) => darken(0.1, theme.yellow3)};
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme }) => theme.yellow3};
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
}
|
||||
@@ -261,27 +229,6 @@ export const ButtonText = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonWhite = styled(Base)`
|
||||
border: 1px solid #edeef2;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
color: black;
|
||||
|
||||
&:focus {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
box-shadow: 0 0 0 1pt ${darken(0.05, '#edeef2')};
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1pt ${darken(0.1, '#edeef2')};
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1pt ${darken(0.1, '#edeef2')};
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
}
|
||||
`
|
||||
|
||||
const ButtonConfirmedStyle = styled(Base)`
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
color: ${({ theme }) => theme.text1};
|
||||
@@ -350,17 +297,6 @@ export function ButtonDropdown({ disabled = false, children, ...rest }: { disabl
|
||||
)
|
||||
}
|
||||
|
||||
export function ButtonDropdownGrey({ disabled = false, children, ...rest }: { disabled?: boolean } & ButtonProps) {
|
||||
return (
|
||||
<ButtonGray {...rest} disabled={disabled} style={{ borderRadius: '20px' }}>
|
||||
<RowBetween>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>{children}</div>
|
||||
<ChevronDown size={24} />
|
||||
</RowBetween>
|
||||
</ButtonGray>
|
||||
)
|
||||
}
|
||||
|
||||
export function ButtonDropdownLight({ disabled = false, children, ...rest }: { disabled?: boolean } & ButtonProps) {
|
||||
return (
|
||||
<ButtonOutlined {...rest} disabled={disabled}>
|
||||
@@ -372,14 +308,6 @@ export function ButtonDropdownLight({ disabled = false, children, ...rest }: { d
|
||||
)
|
||||
}
|
||||
|
||||
export function ButtonRadio({ active, ...rest }: { active?: boolean } & ButtonProps) {
|
||||
if (!active) {
|
||||
return <ButtonWhite {...rest} />
|
||||
} else {
|
||||
return <ButtonPrimary {...rest} />
|
||||
}
|
||||
}
|
||||
|
||||
const ActiveOutlined = styled(ButtonOutlined)`
|
||||
border: 1px solid;
|
||||
border-color: ${({ theme }) => theme.primary1};
|
||||
@@ -412,13 +340,13 @@ export function ButtonRadioChecked({ active = false, children, ...rest }: { acti
|
||||
|
||||
if (!active) {
|
||||
return (
|
||||
<ButtonOutlined borderRadius="12px" padding="12px 8px" {...rest}>
|
||||
<ButtonOutlined $borderRadius="12px" padding="12px 8px" {...rest}>
|
||||
{<RowBetween>{children}</RowBetween>}
|
||||
</ButtonOutlined>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ActiveOutlined {...rest} padding="12px 8px" borderRadius="12px">
|
||||
<ActiveOutlined {...rest} padding="12px 8px" $borderRadius="12px">
|
||||
{
|
||||
<RowBetween>
|
||||
{children}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import styled from 'styled-components/macro'
|
||||
import { Box } from 'rebass/styled-components'
|
||||
|
||||
const Card = styled(Box)<{ width?: string; padding?: string; border?: string; borderRadius?: string }>`
|
||||
const Card = styled(Box)<{ width?: string; padding?: string; border?: string; $borderRadius?: string }>`
|
||||
width: ${({ width }) => width ?? '100%'};
|
||||
border-radius: 16px;
|
||||
padding: 1rem;
|
||||
padding: ${({ padding }) => padding};
|
||||
padding: ${({ padding }) => padding ?? '1rem'};
|
||||
border-radius: ${({ $borderRadius }) => $borderRadius ?? '16px'};
|
||||
border: ${({ border }) => border};
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
`
|
||||
export default Card
|
||||
|
||||
@@ -42,12 +40,6 @@ export const YellowCard = styled(Card)`
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
export const PinkCard = styled(Card)`
|
||||
background-color: rgba(255, 0, 122, 0.03);
|
||||
color: ${({ theme }) => theme.primary1};
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
export const BlueCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.primary5};
|
||||
color: ${({ theme }) => theme.blue2};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import ReactConfetti from 'react-confetti'
|
||||
import { useWindowSize } from '../../hooks/useWindowSize'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import React, { useMemo } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import { TYPE } from '../../theme'
|
||||
import { warningSeverity } from '../../utils/prices'
|
||||
@@ -18,10 +18,10 @@ export function FiatValue({
|
||||
if (!priceImpact) return undefined
|
||||
if (priceImpact.lessThan('0')) return theme.green1
|
||||
const severity = warningSeverity(priceImpact)
|
||||
if (severity < 1) return theme.text4
|
||||
if (severity < 1) return theme.text3
|
||||
if (severity < 3) return theme.yellow1
|
||||
return theme.red1
|
||||
}, [priceImpact, theme.green1, theme.red1, theme.text4, theme.yellow1])
|
||||
}, [priceImpact, theme.green1, theme.red1, theme.text3, theme.yellow1])
|
||||
|
||||
return (
|
||||
<TYPE.body fontSize={14} color={fiatValue ? theme.text2 : theme.text4}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import React, { useState, useCallback, ReactNode } from 'react'
|
||||
import { useState, useCallback, ReactNode } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { darken } from 'polished'
|
||||
import { useCurrencyBalance } from '../../state/wallet/hooks'
|
||||
@@ -130,7 +130,7 @@ const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
color: ${({ theme }) => theme.primary1};
|
||||
color: ${({ theme }) => theme.primaryText1};
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
|
||||
margin-left: 0.25rem;
|
||||
@@ -160,6 +160,8 @@ interface CurrencyInputPanelProps {
|
||||
priceImpact?: Percent
|
||||
id: string
|
||||
showCommonBases?: boolean
|
||||
showCurrencyAmount?: boolean
|
||||
disableNonToken?: boolean
|
||||
renderBalance?: (amount: CurrencyAmount<Currency>) => ReactNode
|
||||
locked?: boolean
|
||||
}
|
||||
@@ -174,6 +176,8 @@ export default function CurrencyInputPanel({
|
||||
otherCurrency,
|
||||
id,
|
||||
showCommonBases,
|
||||
showCurrencyAmount,
|
||||
disableNonToken,
|
||||
renderBalance,
|
||||
fiatValue,
|
||||
priceImpact,
|
||||
@@ -298,6 +302,8 @@ export default function CurrencyInputPanel({
|
||||
selectedCurrency={currency}
|
||||
otherSelectedCurrency={otherCurrency}
|
||||
showCommonBases={showCommonBases}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
disableNonToken={disableNonToken}
|
||||
/>
|
||||
)}
|
||||
</InputPanel>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
|
||||
import Logo from '../Logo'
|
||||
|
||||
export const getTokenLogoURL = (address: string) =>
|
||||
`https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${address}/logo.png`
|
||||
`https://raw.githubusercontent.com/uniswap/assets/master/blockchains/ethereum/assets/${address}/logo.png`
|
||||
|
||||
const StyledEthereumLogo = styled.img<{ size: string }>`
|
||||
width: ${({ size }) => size};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
|
||||
@@ -10,7 +9,7 @@ const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>`
|
||||
margin-left: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'};
|
||||
`
|
||||
|
||||
export interface DoubleCurrencyLogoProps {
|
||||
interface DoubleCurrencyLogoProps {
|
||||
margin?: boolean
|
||||
size?: number
|
||||
currency0?: Currency
|
||||
|
||||
@@ -1,81 +1,228 @@
|
||||
import React from 'react'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { DynamicSection } from 'pages/AddLiquidity/styled'
|
||||
import { TYPE } from 'theme'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { ButtonRadioChecked } from 'components/Button'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ButtonGray, ButtonRadioChecked } from 'components/Button'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
import Badge from 'components/Badge'
|
||||
import Card from 'components/Card'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { useFeeTierDistribution } from 'hooks/useFeeTierDistribution'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Box } from 'rebass'
|
||||
|
||||
const pulse = (color: string) => keyframes`
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 ${color};
|
||||
}
|
||||
|
||||
70% {
|
||||
box-shadow: 0 0 0 2px ${color};
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 ${color};
|
||||
}
|
||||
`
|
||||
|
||||
const ResponsiveText = styled(TYPE.label)`
|
||||
line-height: 16px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
`};
|
||||
`
|
||||
|
||||
const FocusedOutlineCard = styled(Card)<{ pulsing: boolean }>`
|
||||
border: 1px solid ${({ theme }) => theme.bg2};
|
||||
animation: ${({ pulsing, theme }) => pulsing && pulse(theme.primary1)} 0.6s linear;
|
||||
`
|
||||
|
||||
const FeeAmountLabel = {
|
||||
[FeeAmount.LOW]: {
|
||||
label: '0.05',
|
||||
description: <Trans>Best for stable pairs.</Trans>,
|
||||
},
|
||||
[FeeAmount.MEDIUM]: {
|
||||
label: '0.3',
|
||||
description: <Trans>Best for most pairs.</Trans>,
|
||||
},
|
||||
[FeeAmount.HIGH]: {
|
||||
label: '1',
|
||||
description: <Trans>Best for exotic pairs.</Trans>,
|
||||
},
|
||||
}
|
||||
|
||||
const FeeTierPercentageBadge = ({ percentage }: { percentage: number | undefined }) => {
|
||||
return (
|
||||
<Badge>
|
||||
<TYPE.label fontSize={12}>
|
||||
{percentage !== undefined ? <Trans>{percentage?.toFixed(0)}% select</Trans> : <Trans>Not created</Trans>}
|
||||
</TYPE.label>
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
export default function FeeSelector({
|
||||
disabled = false,
|
||||
feeAmount,
|
||||
handleFeePoolSelect,
|
||||
currencyA,
|
||||
currencyB,
|
||||
}: {
|
||||
disabled?: boolean
|
||||
feeAmount?: FeeAmount
|
||||
handleFeePoolSelect: (feeAmount: FeeAmount) => void
|
||||
currencyA?: Currency | undefined
|
||||
currencyB?: Currency | undefined
|
||||
}) {
|
||||
const { isLoading, isError, largestUsageFeeTier, distributions } = useFeeTierDistribution(currencyA, currencyB)
|
||||
|
||||
const [showOptions, setShowOptions] = useState(false)
|
||||
const [pulsing, setPulsing] = useState(false)
|
||||
|
||||
const previousFeeAmount = usePrevious(feeAmount)
|
||||
|
||||
const recommended = useRef(false)
|
||||
|
||||
const handleFeePoolSelectWithEvent = useCallback(
|
||||
(fee) => {
|
||||
ReactGA.event({
|
||||
category: 'FeePoolSelect',
|
||||
action: 'Manual',
|
||||
})
|
||||
handleFeePoolSelect(fee)
|
||||
},
|
||||
[handleFeePoolSelect]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (feeAmount || isLoading || isError) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!largestUsageFeeTier) {
|
||||
// cannot recommend, open options
|
||||
setShowOptions(true)
|
||||
} else {
|
||||
setShowOptions(false)
|
||||
|
||||
recommended.current = true
|
||||
ReactGA.event({
|
||||
category: 'FeePoolSelect',
|
||||
action: ' Recommended',
|
||||
})
|
||||
|
||||
handleFeePoolSelect(largestUsageFeeTier)
|
||||
}
|
||||
}, [feeAmount, isLoading, isError, largestUsageFeeTier, handleFeePoolSelect])
|
||||
|
||||
useEffect(() => {
|
||||
setShowOptions(isError)
|
||||
}, [isError])
|
||||
|
||||
useEffect(() => {
|
||||
if (feeAmount && previousFeeAmount !== feeAmount) {
|
||||
setPulsing(true)
|
||||
}
|
||||
}, [previousFeeAmount, feeAmount])
|
||||
|
||||
return (
|
||||
<AutoColumn gap="16px">
|
||||
<DynamicSection gap="md" disabled={disabled}>
|
||||
<TYPE.label>
|
||||
<Trans>Select Pool</Trans>
|
||||
</TYPE.label>
|
||||
<TYPE.main fontSize={14} fontWeight={400} style={{ marginBottom: '.5rem', lineHeight: '125%' }}>
|
||||
<Trans>Select a pool type based on your preferred liquidity provider fee.</Trans>
|
||||
</TYPE.main>
|
||||
<RowBetween>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.LOW}
|
||||
onClick={() => handleFeePoolSelect(FeeAmount.LOW)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<ResponsiveText>
|
||||
<Trans>0.05% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for stable pairs.</Trans>
|
||||
</TYPE.main>
|
||||
<FocusedOutlineCard pulsing={pulsing} onAnimationEnd={() => setPulsing(false)}>
|
||||
<RowBetween>
|
||||
<AutoColumn id="add-liquidity-selected-fee">
|
||||
{!feeAmount ? (
|
||||
<>
|
||||
<TYPE.label>
|
||||
<Trans>Fee tier</Trans>
|
||||
</TYPE.label>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>The % you will earn in fees.</Trans>
|
||||
</TYPE.main>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<TYPE.label className="selected-fee-label">
|
||||
<Trans>{FeeAmountLabel[feeAmount].label}% fee tier</Trans>
|
||||
</TYPE.label>
|
||||
<Box style={{ width: 'fit-content', marginTop: '8px' }} className="selected-fee-percentage">
|
||||
{distributions && feeAmount && <FeeTierPercentageBadge percentage={distributions[feeAmount]} />}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.MEDIUM}
|
||||
onClick={() => handleFeePoolSelect(FeeAmount.MEDIUM)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<ResponsiveText>
|
||||
<Trans>0.3% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for most pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.HIGH}
|
||||
onClick={() => handleFeePoolSelect(FeeAmount.HIGH)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<ResponsiveText>
|
||||
<Trans>1% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for exotic pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
</RowBetween>
|
||||
|
||||
<ButtonGray onClick={() => setShowOptions(!showOptions)} width="auto" padding="4px" $borderRadius="6px">
|
||||
{showOptions ? <Trans>Hide</Trans> : <Trans>Edit</Trans>}
|
||||
</ButtonGray>
|
||||
</RowBetween>
|
||||
</FocusedOutlineCard>
|
||||
|
||||
{showOptions && (
|
||||
<RowBetween>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.LOW}
|
||||
onClick={() => handleFeePoolSelectWithEvent(FeeAmount.LOW)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<AutoColumn justify="flex-start" gap="6px">
|
||||
<ResponsiveText>
|
||||
<Trans>0.05% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for stable pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
|
||||
{distributions && <FeeTierPercentageBadge percentage={distributions[FeeAmount.LOW]} />}
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.MEDIUM}
|
||||
onClick={() => handleFeePoolSelectWithEvent(FeeAmount.MEDIUM)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<AutoColumn justify="flex-start" gap="4px">
|
||||
<ResponsiveText>
|
||||
<Trans>0.3% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for most pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
|
||||
{distributions && <FeeTierPercentageBadge percentage={distributions[FeeAmount.MEDIUM]} />}
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.HIGH}
|
||||
onClick={() => handleFeePoolSelectWithEvent(FeeAmount.HIGH)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<AutoColumn justify="flex-start" gap="4px">
|
||||
<ResponsiveText>
|
||||
<Trans>1% fee</Trans>
|
||||
</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
<Trans>Best for exotic pairs.</Trans>
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
|
||||
{distributions && <FeeTierPercentageBadge percentage={distributions[FeeAmount.HIGH]} />}
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
</RowBetween>
|
||||
)}
|
||||
</DynamicSection>
|
||||
</AutoColumn>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import JSBI from 'jsbi'
|
||||
import React from 'react'
|
||||
import { Currency, CurrencyAmount, Fraction } from '@uniswap/sdk-core'
|
||||
|
||||
const CURRENCY_AMOUNT_MIN = new Fraction(JSBI.BigInt(1), JSBI.BigInt(1000000))
|
||||
|
||||
234
src/components/Header/NetworkCard.tsx
Normal file
234
src/components/Header/NetworkCard.tsx
Normal file
@@ -0,0 +1,234 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { YellowCard } from 'components/Card'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { ArrowDownCircle, ChevronDown, ToggleLeft } from 'react-feather'
|
||||
import { ApplicationModal } from 'state/application/actions'
|
||||
import { useModalOpen, useToggleModal } from 'state/application/hooks'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { ExternalLink } from 'theme'
|
||||
import { switchToNetwork } from 'utils/switchToNetwork'
|
||||
import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from '../../constants/chains'
|
||||
|
||||
const BaseWrapper = css`
|
||||
position: relative;
|
||||
margin-right: 8px;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
justify-self: end;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
margin: 0 0.5rem 0 0;
|
||||
width: initial;
|
||||
text-overflow: ellipsis;
|
||||
flex-shrink: 1;
|
||||
`};
|
||||
`
|
||||
const L2Wrapper = styled.div`
|
||||
${BaseWrapper}
|
||||
`
|
||||
const BaseMenuItem = css`
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
justify-content: space-between;
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.text1};
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
const DisabledMenuItem = styled.div`
|
||||
${BaseMenuItem}
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
cursor: auto;
|
||||
display: flex;
|
||||
font-size: 10px;
|
||||
font-style: italic;
|
||||
justify-content: center;
|
||||
:hover,
|
||||
:active,
|
||||
:focus {
|
||||
color: ${({ theme }) => theme.text2};
|
||||
}
|
||||
`
|
||||
const FallbackWrapper = styled(YellowCard)`
|
||||
${BaseWrapper}
|
||||
width: auto;
|
||||
border-radius: 12px;
|
||||
padding: 8px 12px;
|
||||
width: 100%;
|
||||
`
|
||||
const Icon = styled.img`
|
||||
width: 16px;
|
||||
margin-right: 2px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
margin-right: 4px;
|
||||
|
||||
`};
|
||||
`
|
||||
|
||||
const MenuFlyout = styled.span`
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
border: 1px solid ${({ theme }) => theme.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: 12px;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 1rem;
|
||||
position: absolute;
|
||||
left: 0rem;
|
||||
top: 3rem;
|
||||
z-index: 100;
|
||||
width: 237px;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
|
||||
bottom: unset;
|
||||
top: 4.5em
|
||||
right: 0;
|
||||
|
||||
`};
|
||||
> {
|
||||
padding: 12px;
|
||||
}
|
||||
> :not(:first-child) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
> :not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
opacity: 0.6;
|
||||
`
|
||||
const MenuItem = styled(ExternalLink)`
|
||||
${BaseMenuItem}
|
||||
`
|
||||
const ButtonMenuItem = styled.button`
|
||||
${BaseMenuItem}
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
outline: none;
|
||||
padding: 0;
|
||||
`
|
||||
const NetworkInfo = styled.button<{ chainId: SupportedChainId }>`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
border-radius: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.bg0};
|
||||
color: ${({ theme }) => theme.text1};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
height: 38px;
|
||||
padding: 0.7rem;
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
border: 1px solid ${({ theme }) => theme.bg3};
|
||||
}
|
||||
`
|
||||
const NetworkName = styled.div<{ chainId: SupportedChainId }>`
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
padding: 0 2px 0.5px 4px;
|
||||
margin: 0 2px;
|
||||
white-space: pre;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
export default function NetworkCard() {
|
||||
const { chainId, library } = useActiveWeb3React()
|
||||
const node = useRef<HTMLDivElement>(null)
|
||||
const open = useModalOpen(ApplicationModal.ARBITRUM_OPTIONS)
|
||||
const toggle = useToggleModal(ApplicationModal.ARBITRUM_OPTIONS)
|
||||
useOnClickOutside(node, open ? toggle : undefined)
|
||||
|
||||
const [implements3085, setImplements3085] = useState(false)
|
||||
useEffect(() => {
|
||||
// metamask is currently the only known implementer of this EIP
|
||||
// here we proceed w/ a noop feature check to ensure the user's version of metamask supports network switching
|
||||
// if not, we hide the UI
|
||||
if (!library?.provider?.request || !chainId || !library?.provider?.isMetaMask) {
|
||||
return
|
||||
}
|
||||
switchToNetwork({ library, chainId })
|
||||
.then((x) => x ?? setImplements3085(true))
|
||||
.catch(() => setImplements3085(false))
|
||||
}, [library, chainId])
|
||||
|
||||
const info = chainId ? CHAIN_INFO[chainId] : undefined
|
||||
if (!chainId || chainId === SupportedChainId.MAINNET || !info || !library) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (L2_CHAIN_IDS.includes(chainId)) {
|
||||
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
|
||||
const isArbitrum = [SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_RINKEBY].includes(chainId)
|
||||
return (
|
||||
<L2Wrapper ref={node}>
|
||||
<NetworkInfo onClick={toggle} chainId={chainId}>
|
||||
<Icon src={info.logoUrl} />
|
||||
<NetworkName chainId={chainId}>{info.label}</NetworkName>
|
||||
<ChevronDown size={16} style={{ marginTop: '2px' }} strokeWidth={2.5} />
|
||||
</NetworkInfo>
|
||||
{open && (
|
||||
<MenuFlyout>
|
||||
<MenuItem href={info.bridge}>
|
||||
<div>{isArbitrum ? <Trans>{info.label} Bridge</Trans> : <Trans>Optimistic L2 Gateway</Trans>}</div>
|
||||
<LinkOutCircle />
|
||||
</MenuItem>
|
||||
<MenuItem href={info.explorer}>
|
||||
{isArbitrum ? <Trans>{info.label} Explorer</Trans> : <Trans>Optimistic Etherscan</Trans>}
|
||||
<LinkOutCircle />
|
||||
</MenuItem>
|
||||
<MenuItem href={info.docs}>
|
||||
<div>
|
||||
<Trans>Learn more</Trans>
|
||||
</div>
|
||||
<LinkOutCircle />
|
||||
</MenuItem>
|
||||
{implements3085 ? (
|
||||
<ButtonMenuItem onClick={() => switchToNetwork({ library, chainId: SupportedChainId.MAINNET })}>
|
||||
<div>
|
||||
<Trans>Switch to L1 (Mainnet)</Trans>
|
||||
</div>
|
||||
<ToggleLeft opacity={0.6} size={16} />
|
||||
</ButtonMenuItem>
|
||||
) : (
|
||||
<DisabledMenuItem>
|
||||
<Trans>Change your network to go back to L1</Trans>
|
||||
</DisabledMenuItem>
|
||||
)}
|
||||
</MenuFlyout>
|
||||
)}
|
||||
</L2Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
return <FallbackWrapper title={info.label}>{info.label}</FallbackWrapper>
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
import { useEffect, useState } from 'react'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
|
||||
import { useBlockNumber } from '../../state/application/hooks'
|
||||
@@ -21,7 +21,7 @@ const StyledPolling = styled.div`
|
||||
`
|
||||
const StyledPollingNumber = styled(TYPE.small)<{ breathe: boolean; hovering: boolean }>`
|
||||
transition: opacity 0.25s ease;
|
||||
opacity: ${({ breathe, hovering }) => (hovering ? 0.7 : breathe ? 1 : 0.2)};
|
||||
opacity: ${({ breathe, hovering }) => (hovering ? 0.7 : breathe ? 1 : 0.5)};
|
||||
:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { AlertTriangle, X } from 'react-feather'
|
||||
import { useURLWarningToggle, useURLWarningVisible } from '../../state/user/hooks'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
import { Trans } from '@lingui/macro'
|
||||
|
||||
const PhishAlert = styled.div<{ isActive: any }>`
|
||||
width: 100%;
|
||||
padding: 6px 6px;
|
||||
background-color: ${({ theme }) => theme.blue1};
|
||||
color: white;
|
||||
font-size: 11px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: ${({ isActive }) => (isActive ? 'flex' : 'none')};
|
||||
`
|
||||
|
||||
export const StyledClose = styled(X)`
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
export default function URLWarning() {
|
||||
const toggleURLWarning = useURLWarningToggle()
|
||||
const showURLWarning = useURLWarningVisible()
|
||||
|
||||
return isMobile ? (
|
||||
<PhishAlert isActive={showURLWarning}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<AlertTriangle style={{ marginRight: 6 }} size={12} />
|
||||
<Trans>
|
||||
Make sure the URL is
|
||||
<code style={{ padding: '0 4px', display: 'inline', fontWeight: 'bold' }}>app.uniswap.org</code>
|
||||
</Trans>
|
||||
</div>
|
||||
<StyledClose size={12} onClick={toggleURLWarning} />
|
||||
</PhishAlert>
|
||||
) : window.location.hostname === 'app.uniswap.org' ? (
|
||||
<PhishAlert isActive={showURLWarning}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<AlertTriangle style={{ marginRight: 6 }} size={12} />
|
||||
<Trans>
|
||||
Always make sure the URL is
|
||||
<code style={{ padding: '0 4px', display: 'inline', fontWeight: 'bold' }}>app.uniswap.org</code> - bookmark it
|
||||
to be safe.
|
||||
</Trans>
|
||||
</div>
|
||||
<StyledClose size={12} onClick={toggleURLWarning} />
|
||||
</PhishAlert>
|
||||
) : null
|
||||
}
|
||||
@@ -1,22 +1,23 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import React, { useMemo } from 'react'
|
||||
import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
|
||||
import { useMemo } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import tokenLogo from '../../assets/images/token-logo.png'
|
||||
import { UNI } from '../../constants/tokens'
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { useMerkleDistributorContract } from '../../hooks/useContract'
|
||||
import useCurrentBlockTimestamp from '../../hooks/useCurrentBlockTimestamp'
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
import useUSDCPrice from '../../hooks/useUSDCPrice'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { useTotalUniEarned } from '../../state/stake/hooks'
|
||||
import { useAggregateUniBalance, useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { ExternalLink, StyledInternalLink, TYPE, UniTokenAnimated } from '../../theme'
|
||||
import { computeUniCirculation } from '../../utils/computeUniCirculation'
|
||||
import useUSDCPrice from '../../hooks/useUSDCPrice'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { RowBetween } from '../Row'
|
||||
import { Break, CardBGImage, CardNoise, CardSection, DataCard } from '../earn/styled'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
const ContentWrapper = styled(AutoColumn)`
|
||||
width: 100%;
|
||||
@@ -59,6 +60,8 @@ export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowU
|
||||
[blockTimestamp, chainId, totalSupply, unclaimedUni, uni]
|
||||
)
|
||||
|
||||
const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
|
||||
|
||||
return (
|
||||
<ContentWrapper gap="lg">
|
||||
<ModalUpper>
|
||||
@@ -128,7 +131,7 @@ export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowU
|
||||
<TYPE.white color="white">{totalSupply?.toFixed(0, { groupSeparator: ',' })}</TYPE.white>
|
||||
</RowBetween>
|
||||
{uni && uni.chainId === 1 ? (
|
||||
<ExternalLink href={`https://info.uniswap.org/token/${uni.address}`}>
|
||||
<ExternalLink href={`${infoLink}/token/${uni.address}`}>
|
||||
<Trans>View UNI Analytics</Trans>
|
||||
</ExternalLink>
|
||||
) : null}
|
||||
|
||||
@@ -1,33 +1,28 @@
|
||||
import useScrollPosition from '@react-hook/window-scroll'
|
||||
import React, { useState } from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { darken } from 'polished'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Moon, Sun } from 'react-feather'
|
||||
import useScrollPosition from '@react-hook/window-scroll'
|
||||
import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
|
||||
import { darken } from 'polished'
|
||||
import { useState } from 'react'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import { useShowClaimPopup, useToggleSelfClaimModal } from 'state/application/hooks'
|
||||
import { useUserHasAvailableClaim } from 'state/claim/hooks'
|
||||
import { useUserHasSubmittedClaim } from 'state/transactions/hooks'
|
||||
import { useDarkModeManager } from 'state/user/hooks'
|
||||
import { useETHBalances } from 'state/wallet/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import Logo from '../../assets/svg/logo.svg'
|
||||
import LogoDark from '../../assets/svg/logo_white.svg'
|
||||
import { SupportedChainId } from '../../constants/chains'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { useDarkModeManager } from '../../state/user/hooks'
|
||||
import { useETHBalances } from '../../state/wallet/hooks'
|
||||
import { CardNoise } from '../earn/styled'
|
||||
import { TYPE, ExternalLink } from '../../theme'
|
||||
|
||||
import { YellowCard } from '../Card'
|
||||
import Menu from '../Menu'
|
||||
|
||||
import Row, { RowFixed } from '../Row'
|
||||
import Web3Status from '../Web3Status'
|
||||
import { ExternalLink, TYPE } from '../../theme'
|
||||
import ClaimModal from '../claim/ClaimModal'
|
||||
import { useToggleSelfClaimModal, useShowClaimPopup } from '../../state/application/hooks'
|
||||
import { useUserHasAvailableClaim } from '../../state/claim/hooks'
|
||||
import { useUserHasSubmittedClaim } from '../../state/transactions/hooks'
|
||||
import { Dots } from '../swap/styleds'
|
||||
import { CardNoise } from '../earn/styled'
|
||||
import Menu from '../Menu'
|
||||
import Modal from '../Modal'
|
||||
import Row from '../Row'
|
||||
import { Dots } from '../swap/styleds'
|
||||
import Web3Status from '../Web3Status'
|
||||
import NetworkCard from './NetworkCard'
|
||||
import UniBalanceContent from './UniBalanceContent'
|
||||
|
||||
const HeaderFrame = styled.div<{ showBackground: boolean }>`
|
||||
@@ -43,22 +38,27 @@ const HeaderFrame = styled.div<{ showBackground: boolean }>`
|
||||
padding: 1rem;
|
||||
z-index: 21;
|
||||
position: relative;
|
||||
|
||||
/* Background slide effect on scroll. */
|
||||
background-image: ${({ theme }) => `linear-gradient(to bottom, transparent 50%, ${theme.bg0} 50% )}}`}
|
||||
background-image: ${({ theme }) => `linear-gradient(to bottom, transparent 50%, ${theme.bg0} 50% )}}`};
|
||||
background-position: ${({ showBackground }) => (showBackground ? '0 -100%' : '0 0')};
|
||||
background-size: 100% 200%;
|
||||
box-shadow: 0px 0px 0px 1px ${({ theme, showBackground }) => (showBackground ? theme.bg2 : 'transparent;')};
|
||||
transition: background-position .1s, box-shadow .1s;
|
||||
transition: background-position 0.1s, box-shadow 0.1s;
|
||||
background-blend-mode: hard-light;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToLarge`
|
||||
grid-template-columns: 48px 1fr 1fr;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
padding: 1rem;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
padding: 1rem;
|
||||
`}
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
padding: 1rem;
|
||||
grid-template-columns: 36px 1fr;
|
||||
`};
|
||||
`
|
||||
|
||||
const HeaderControls = styled.div`
|
||||
@@ -66,23 +66,6 @@ const HeaderControls = styled.div`
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-self: flex-end;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
justify-self: center;
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
padding: 1rem;
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
z-index: 99;
|
||||
height: 72px;
|
||||
border-radius: 12px 12px 0 0;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
`};
|
||||
`
|
||||
|
||||
const HeaderElement = styled.div`
|
||||
@@ -95,22 +78,10 @@ const HeaderElement = styled.div`
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
`};
|
||||
`
|
||||
|
||||
const HeaderElementWrap = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const HeaderRow = styled(RowFixed)`
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
width: 100%;
|
||||
`};
|
||||
`
|
||||
|
||||
const HeaderLinks = styled(Row)`
|
||||
justify-self: center;
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
@@ -121,8 +92,25 @@ const HeaderLinks = styled(Row)`
|
||||
grid-auto-flow: column;
|
||||
grid-gap: 10px;
|
||||
overflow: auto;
|
||||
align-items: center;
|
||||
${({ theme }) => theme.mediaWidth.upToLarge`
|
||||
justify-self: start;
|
||||
`};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
justify-self: flex-end;
|
||||
justify-self: center;
|
||||
`};
|
||||
${({ theme }) => theme.mediaWidth.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.bg0};
|
||||
border: 1px solid ${({ theme }) => theme.bg2};
|
||||
box-shadow: 0px 6px 10px rgb(0 0 0 / 2%);
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -130,7 +118,7 @@ const AccountElement = styled.div<{ active: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background-color: ${({ theme, active }) => (!active ? theme.bg1 : theme.bg2)};
|
||||
background-color: ${({ theme, active }) => (!active ? theme.bg1 : theme.bg1)};
|
||||
border-radius: 12px;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
@@ -164,25 +152,6 @@ const UNIWrapper = styled.span`
|
||||
}
|
||||
`
|
||||
|
||||
const HideSmall = styled.span`
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
const NetworkCard = styled(YellowCard)`
|
||||
border-radius: 12px;
|
||||
padding: 8px 12px;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
margin: 0;
|
||||
margin-right: 0.5rem;
|
||||
width: initial;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-shrink: 1;
|
||||
`};
|
||||
`
|
||||
|
||||
const BalanceText = styled(Text)`
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
display: none;
|
||||
@@ -267,57 +236,13 @@ const StyledExternalLink = styled(ExternalLink).attrs({
|
||||
color: ${({ theme }) => darken(0.1, theme.text1)};
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
display: none;
|
||||
`}
|
||||
`
|
||||
|
||||
export const StyledMenuButton = styled.button`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 35px;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
margin-left: 8px;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
background-color: ${({ theme }) => theme.bg4};
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-top: 2px;
|
||||
}
|
||||
> * {
|
||||
stroke: ${({ theme }) => theme.text1};
|
||||
}
|
||||
`
|
||||
|
||||
const NETWORK_LABELS: { [chainId in SupportedChainId | number]: string } = {
|
||||
[SupportedChainId.MAINNET]: 'Mainnet',
|
||||
[SupportedChainId.RINKEBY]: 'Rinkeby',
|
||||
[SupportedChainId.ROPSTEN]: 'Ropsten',
|
||||
[SupportedChainId.GOERLI]: 'Görli',
|
||||
[SupportedChainId.KOVAN]: 'Kovan',
|
||||
[SupportedChainId.ARBITRUM_KOVAN]: 'kArbitrum',
|
||||
[SupportedChainId.ARBITRUM_ONE]: 'Arbitrum One',
|
||||
}
|
||||
|
||||
export default function Header() {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
|
||||
const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? '']
|
||||
// const [isDark] = useDarkModeManager()
|
||||
const [darkMode, toggleDarkMode] = useDarkModeManager()
|
||||
const [darkMode] = useDarkModeManager()
|
||||
|
||||
const toggleClaimModal = useToggleSelfClaimModal()
|
||||
|
||||
@@ -330,19 +255,18 @@ export default function Header() {
|
||||
|
||||
const scrollY = useScrollPosition()
|
||||
|
||||
const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
|
||||
return (
|
||||
<HeaderFrame showBackground={scrollY > 45}>
|
||||
<ClaimModal />
|
||||
<Modal isOpen={showUniBalanceModal} onDismiss={() => setShowUniBalanceModal(false)}>
|
||||
<UniBalanceContent setShowUniBalanceModal={setShowUniBalanceModal} />
|
||||
</Modal>
|
||||
<HeaderRow>
|
||||
<Title href=".">
|
||||
<UniIcon>
|
||||
<img width={'24px'} src={darkMode ? LogoDark : Logo} alt="logo" />
|
||||
</UniIcon>
|
||||
</Title>
|
||||
</HeaderRow>
|
||||
<Title href=".">
|
||||
<UniIcon>
|
||||
<img width={'24px'} src={darkMode ? LogoDark : Logo} alt="logo" />
|
||||
</UniIcon>
|
||||
</Title>
|
||||
<HeaderLinks>
|
||||
<StyledNavLink id={`swap-nav-link`} to={'/swap'}>
|
||||
<Trans>Swap</Trans>
|
||||
@@ -360,21 +284,20 @@ export default function Header() {
|
||||
>
|
||||
<Trans>Pool</Trans>
|
||||
</StyledNavLink>
|
||||
<StyledNavLink id={`stake-nav-link`} to={'/vote'}>
|
||||
<Trans>Vote</Trans>
|
||||
</StyledNavLink>
|
||||
<StyledExternalLink id={`stake-nav-link`} href={'https://info.uniswap.org'}>
|
||||
{chainId && chainId === SupportedChainId.MAINNET && (
|
||||
<StyledNavLink id={`stake-nav-link`} to={'/vote'}>
|
||||
<Trans>Vote</Trans>
|
||||
</StyledNavLink>
|
||||
)}
|
||||
<StyledExternalLink id={`stake-nav-link`} href={infoLink}>
|
||||
<Trans>Charts</Trans>
|
||||
<sup>↗</sup>
|
||||
</StyledExternalLink>
|
||||
</HeaderLinks>
|
||||
|
||||
<HeaderControls>
|
||||
<NetworkCard />
|
||||
<HeaderElement>
|
||||
<HideSmall>
|
||||
{chainId && chainId !== SupportedChainId.MAINNET && NETWORK_LABELS[chainId] && (
|
||||
<NetworkCard title={NETWORK_LABELS[chainId]}>{NETWORK_LABELS[chainId]}</NetworkCard>
|
||||
)}
|
||||
</HideSmall>
|
||||
{availableClaim && !showClaimPopup && (
|
||||
<UNIWrapper onClick={toggleClaimModal}>
|
||||
<UNIAmount active={!!account && !availableClaim} style={{ pointerEvents: 'auto' }}>
|
||||
@@ -394,18 +317,13 @@ export default function Header() {
|
||||
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
|
||||
{account && userEthBalance ? (
|
||||
<BalanceText style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
|
||||
<Trans>{userEthBalance?.toSignificant(4)} ETH</Trans>
|
||||
<Trans>{userEthBalance?.toSignificant(3)} ETH</Trans>
|
||||
</BalanceText>
|
||||
) : null}
|
||||
<Web3Status />
|
||||
</AccountElement>
|
||||
</HeaderElement>
|
||||
<HeaderElementWrap>
|
||||
<StyledMenuButton onClick={() => toggleDarkMode()}>
|
||||
{darkMode ? <Moon size={20} /> : <Sun size={20} />}
|
||||
</StyledMenuButton>
|
||||
<Menu />
|
||||
</HeaderElementWrap>
|
||||
</HeaderElement>
|
||||
</HeaderControls>
|
||||
</HeaderFrame>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import React, { useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const TextWrapper = styled.span<{ margin: boolean; link?: boolean; fontSize?: string; adjustSize?: boolean }>`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import React, { useState, useCallback, useEffect, ReactNode } from 'react'
|
||||
import { LightCard } from 'components/Card'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { useState, useCallback, useEffect, ReactNode } from 'react'
|
||||
import { OutlineCard } from 'components/Card'
|
||||
import { Input as NumericalInput } from '../NumericalInput'
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
import { TYPE } from 'theme'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import { ButtonGray } from 'components/Button'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { formattedFeeAmount } from 'utils'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Plus, Minus } from 'react-feather'
|
||||
|
||||
const pulse = (color: string) => keyframes`
|
||||
0% {
|
||||
@@ -24,25 +23,37 @@ const pulse = (color: string) => keyframes`
|
||||
}
|
||||
`
|
||||
|
||||
const SmallButton = styled(ButtonPrimary)`
|
||||
/* background-color: ${({ theme }) => theme.bg2}; */
|
||||
border-radius: 8px;
|
||||
padding: 4px 6px;
|
||||
width: 48%;
|
||||
const InputRow = styled.div`
|
||||
display: grid;
|
||||
|
||||
grid-template-columns: 30px 1fr 30px;
|
||||
`
|
||||
|
||||
const FocusedOutlineCard = styled(LightCard)<{ active?: boolean; pulsing?: boolean }>`
|
||||
const SmallButton = styled(ButtonGray)`
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
`
|
||||
|
||||
const FocusedOutlineCard = styled(OutlineCard)<{ active?: boolean; pulsing?: boolean }>`
|
||||
border-color: ${({ active, theme }) => active && theme.blue1};
|
||||
padding: 12px;
|
||||
animation: ${({ pulsing, theme }) => pulsing && pulse(theme.blue1)} 0.8s linear;
|
||||
`
|
||||
|
||||
const StyledInput = styled(NumericalInput)<{ usePercent?: boolean }>`
|
||||
/* background-color: ${({ theme }) => theme.bg0}; */
|
||||
background-color: transparent;
|
||||
text-align: center;
|
||||
margin-right: 12px;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
padding: 0 10px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
font-size: 16px;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
font-size: 12px;
|
||||
`};
|
||||
`
|
||||
|
||||
const InputTitle = styled(TYPE.small)`
|
||||
@@ -51,11 +62,17 @@ const InputTitle = styled(TYPE.small)`
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
const ButtonLabel = styled(TYPE.white)<{ disabled: boolean }>`
|
||||
color: ${({ theme, disabled }) => (disabled ? theme.text2 : theme.text1)} !important;
|
||||
`
|
||||
|
||||
interface StepCounterProps {
|
||||
value: string
|
||||
onUserInput: (value: string) => void
|
||||
decrement: () => string
|
||||
increment: () => string
|
||||
decrementDisabled?: boolean
|
||||
incrementDisabled?: boolean
|
||||
feeAmount?: FeeAmount
|
||||
label?: string
|
||||
width?: string
|
||||
@@ -69,7 +86,8 @@ const StepCounter = ({
|
||||
value,
|
||||
decrement,
|
||||
increment,
|
||||
feeAmount,
|
||||
decrementDisabled = false,
|
||||
incrementDisabled = false,
|
||||
width,
|
||||
locked,
|
||||
onUserInput,
|
||||
@@ -87,9 +105,6 @@ const StepCounter = ({
|
||||
// animation if parent value updates local value
|
||||
const [pulsing, setPulsing] = useState<boolean>(false)
|
||||
|
||||
// format fee amount
|
||||
const feeAmountFormatted = feeAmount ? formattedFeeAmount(feeAmount * 2) : ''
|
||||
|
||||
const handleOnFocus = () => {
|
||||
setUseLocalValue(true)
|
||||
setActive(true)
|
||||
@@ -126,39 +141,45 @@ const StepCounter = ({
|
||||
|
||||
return (
|
||||
<FocusedOutlineCard pulsing={pulsing} active={active} onFocus={handleOnFocus} onBlur={handleOnBlur} width={width}>
|
||||
<AutoColumn gap="6px" style={{ marginBottom: '12px' }}>
|
||||
<AutoColumn gap="6px">
|
||||
<InputTitle fontSize={12} textAlign="center">
|
||||
{title}
|
||||
</InputTitle>
|
||||
<StyledInput
|
||||
className="rate-input-0"
|
||||
value={localValue}
|
||||
fontSize="20px"
|
||||
disabled={locked}
|
||||
onUserInput={(val) => {
|
||||
setLocalValue(val)
|
||||
}}
|
||||
/>
|
||||
|
||||
<InputRow>
|
||||
{!locked && (
|
||||
<SmallButton onClick={handleDecrement} disabled={decrementDisabled}>
|
||||
<ButtonLabel disabled={decrementDisabled} fontSize="12px">
|
||||
<Minus size={18} />
|
||||
</ButtonLabel>
|
||||
</SmallButton>
|
||||
)}
|
||||
|
||||
<StyledInput
|
||||
className="rate-input-0"
|
||||
value={localValue}
|
||||
fontSize="20px"
|
||||
disabled={locked}
|
||||
onUserInput={(val) => {
|
||||
setLocalValue(val)
|
||||
}}
|
||||
/>
|
||||
|
||||
{!locked && (
|
||||
<SmallButton onClick={handleIncrement} disabled={incrementDisabled}>
|
||||
<ButtonLabel disabled={incrementDisabled} fontSize="12px">
|
||||
<Plus size={18} />
|
||||
</ButtonLabel>
|
||||
</SmallButton>
|
||||
)}
|
||||
</InputRow>
|
||||
|
||||
<InputTitle fontSize={12} textAlign="center">
|
||||
<Trans>
|
||||
{tokenB} per {tokenA}
|
||||
</Trans>
|
||||
</InputTitle>
|
||||
</AutoColumn>
|
||||
{!locked ? (
|
||||
<RowBetween>
|
||||
<SmallButton onClick={handleDecrement}>
|
||||
<TYPE.white fontSize="12px">
|
||||
<Trans>-{feeAmountFormatted}%</Trans>
|
||||
</TYPE.white>
|
||||
</SmallButton>
|
||||
<SmallButton onClick={handleIncrement}>
|
||||
<TYPE.white fontSize="12px">
|
||||
<Trans>+{feeAmountFormatted}%</Trans>
|
||||
</TYPE.white>
|
||||
</SmallButton>
|
||||
</RowBetween>
|
||||
) : null}
|
||||
</FocusedOutlineCard>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
export const dummyData = [
|
||||
{ time: '2018-10-19', value: 35.98 },
|
||||
{ time: '2018-10-22', value: 35.75 },
|
||||
{ time: '2018-10-23', value: 35.65 },
|
||||
{ time: '2018-10-24', value: 34.12 },
|
||||
{ time: '2018-10-25', value: 35.84 },
|
||||
{ time: '2018-10-26', value: 35.24 },
|
||||
{ time: '2018-10-29', value: 35.99 },
|
||||
{ time: '2018-10-30', value: 37.71 },
|
||||
{ time: '2018-10-31', value: 38.14 },
|
||||
{ time: '2018-11-01', value: 37.95 },
|
||||
{ time: '2018-11-02', value: 37.66 },
|
||||
{ time: '2018-11-05', value: 38.02 },
|
||||
{ time: '2018-11-06', value: 37.73 },
|
||||
{ time: '2018-11-07', value: 38.3 },
|
||||
{ time: '2018-11-08', value: 38.3 },
|
||||
{ time: '2018-11-09', value: 38.34 },
|
||||
{ time: '2018-11-12', value: 38.0 },
|
||||
{ time: '2018-11-13', value: 37.72 },
|
||||
{ time: '2018-11-14', value: 38.29 },
|
||||
{ time: '2018-11-15', value: 38.49 },
|
||||
{ time: '2018-11-16', value: 38.59 },
|
||||
{ time: '2018-11-19', value: 38.18 },
|
||||
{ time: '2018-11-20', value: 36.76 },
|
||||
{ time: '2018-11-21', value: 37.51 },
|
||||
{ time: '2018-11-23', value: 37.39 },
|
||||
{ time: '2018-11-26', value: 37.77 },
|
||||
{ time: '2018-11-27', value: 38.36 },
|
||||
{ time: '2018-11-28', value: 39.06 },
|
||||
{ time: '2018-11-29', value: 39.42 },
|
||||
{ time: '2018-11-30', value: 39.01 },
|
||||
{ time: '2018-12-03', value: 39.15 },
|
||||
{ time: '2018-12-04', value: 37.69 },
|
||||
{ time: '2018-12-06', value: 37.88 },
|
||||
{ time: '2018-12-07', value: 37.41 },
|
||||
{ time: '2018-12-10', value: 37.35 },
|
||||
{ time: '2018-12-11', value: 36.84 },
|
||||
{ time: '2018-12-12', value: 36.98 },
|
||||
{ time: '2018-12-13', value: 36.76 },
|
||||
{ time: '2018-12-14', value: 36.34 },
|
||||
{ time: '2018-12-17', value: 36.21 },
|
||||
{ time: '2018-12-18', value: 35.65 },
|
||||
{ time: '2018-12-19', value: 35.19 },
|
||||
{ time: '2018-12-20', value: 34.62 },
|
||||
{ time: '2018-12-21', value: 33.75 },
|
||||
{ time: '2018-12-24', value: 33.07 },
|
||||
{ time: '2018-12-26', value: 34.14 },
|
||||
{ time: '2018-12-27', value: 34.47 },
|
||||
{ time: '2018-12-28', value: 34.35 },
|
||||
{ time: '2018-12-31', value: 34.05 },
|
||||
{ time: '2019-01-02', value: 34.37 },
|
||||
{ time: '2019-01-03', value: 34.64 },
|
||||
{ time: '2019-01-04', value: 35.81 },
|
||||
{ time: '2019-01-07', value: 35.43 },
|
||||
{ time: '2019-01-08', value: 35.72 },
|
||||
{ time: '2019-01-09', value: 36.06 },
|
||||
{ time: '2019-01-10', value: 35.82 },
|
||||
{ time: '2019-01-11', value: 35.63 },
|
||||
{ time: '2019-01-14', value: 35.77 },
|
||||
{ time: '2019-01-15', value: 35.83 },
|
||||
{ time: '2019-01-16', value: 35.9 },
|
||||
{ time: '2019-01-17', value: 35.91 },
|
||||
{ time: '2019-01-18', value: 36.21 },
|
||||
{ time: '2019-01-22', value: 34.97 },
|
||||
{ time: '2019-01-23', value: 36.89 },
|
||||
{ time: '2019-01-24', value: 36.24 },
|
||||
{ time: '2019-01-25', value: 35.78 },
|
||||
{ time: '2019-01-28', value: 35.37 },
|
||||
{ time: '2019-01-29', value: 36.08 },
|
||||
{ time: '2019-01-30', value: 35.43 },
|
||||
{ time: '2019-01-31', value: 36.57 },
|
||||
{ time: '2019-02-01', value: 36.79 },
|
||||
{ time: '2019-02-04', value: 36.77 },
|
||||
{ time: '2019-02-05', value: 37.15 },
|
||||
{ time: '2019-02-06', value: 37.17 },
|
||||
{ time: '2019-02-07', value: 37.68 },
|
||||
{ time: '2019-02-08', value: 37.6 },
|
||||
{ time: '2019-02-11', value: 37.0 },
|
||||
{ time: '2019-02-12', value: 37.24 },
|
||||
{ time: '2019-02-13', value: 37.03 },
|
||||
{ time: '2019-02-14', value: 37.26 },
|
||||
{ time: '2019-02-15', value: 37.77 },
|
||||
{ time: '2019-02-19', value: 37.55 },
|
||||
{ time: '2019-02-20', value: 37.79 },
|
||||
{ time: '2019-02-21', value: 38.47 },
|
||||
{ time: '2019-02-22', value: 38.61 },
|
||||
{ time: '2019-02-25', value: 38.57 },
|
||||
{ time: '2019-02-26', value: 38.8 },
|
||||
{ time: '2019-02-27', value: 38.53 },
|
||||
{ time: '2019-02-28', value: 38.67 },
|
||||
{ time: '2019-03-01', value: 39.1 },
|
||||
{ time: '2019-03-04', value: 38.73 },
|
||||
{ time: '2019-03-05', value: 38.72 },
|
||||
{ time: '2019-03-06', value: 38.61 },
|
||||
{ time: '2019-03-07', value: 38.38 },
|
||||
{ time: '2019-03-08', value: 38.19 },
|
||||
{ time: '2019-03-11', value: 39.17 },
|
||||
{ time: '2019-03-12', value: 39.49 },
|
||||
{ time: '2019-03-13', value: 39.56 },
|
||||
{ time: '2019-03-14', value: 39.87 },
|
||||
{ time: '2019-03-15', value: 40.47 },
|
||||
{ time: '2019-03-18', value: 39.92 },
|
||||
{ time: '2019-03-19', value: 39.78 },
|
||||
{ time: '2019-03-20', value: 39.47 },
|
||||
{ time: '2019-03-21', value: 40.05 },
|
||||
{ time: '2019-03-22', value: 39.46 },
|
||||
{ time: '2019-03-25', value: 39.18 },
|
||||
{ time: '2019-03-26', value: 39.63 },
|
||||
{ time: '2019-03-27', value: 40.21 },
|
||||
{ time: '2019-03-28', value: 40.42 },
|
||||
{ time: '2019-03-29', value: 39.98 },
|
||||
{ time: '2019-04-01', value: 40.31 },
|
||||
{ time: '2019-04-02', value: 40.02 },
|
||||
{ time: '2019-04-03', value: 40.27 },
|
||||
{ time: '2019-04-04', value: 40.41 },
|
||||
{ time: '2019-04-05', value: 40.42 },
|
||||
{ time: '2019-04-08', value: 40.71 },
|
||||
{ time: '2019-04-09', value: 41.04 },
|
||||
{ time: '2019-04-10', value: 41.08 },
|
||||
{ time: '2019-04-11', value: 41.04 },
|
||||
{ time: '2019-04-12', value: 41.3 },
|
||||
{ time: '2019-04-15', value: 41.78 },
|
||||
{ time: '2019-04-16', value: 41.97 },
|
||||
{ time: '2019-04-17', value: 42.57 },
|
||||
{ time: '2019-04-18', value: 42.43 },
|
||||
{ time: '2019-04-22', value: 42.0 },
|
||||
{ time: '2019-04-23', value: 41.99 },
|
||||
{ time: '2019-04-24', value: 41.85 },
|
||||
{ time: '2019-04-25', value: 42.93 },
|
||||
{ time: '2019-04-26', value: 43.08 },
|
||||
{ time: '2019-04-29', value: 43.45 },
|
||||
{ time: '2019-04-30', value: 43.53 },
|
||||
{ time: '2019-05-01', value: 43.42 },
|
||||
{ time: '2019-05-02', value: 42.65 },
|
||||
{ time: '2019-05-03', value: 43.29 },
|
||||
{ time: '2019-05-06', value: 43.3 },
|
||||
{ time: '2019-05-07', value: 42.76 },
|
||||
{ time: '2019-05-08', value: 42.55 },
|
||||
{ time: '2019-05-09', value: 42.92 },
|
||||
{ time: '2019-05-10', value: 43.15 },
|
||||
{ time: '2019-05-13', value: 42.28 },
|
||||
{ time: '2019-05-14', value: 42.91 },
|
||||
{ time: '2019-05-15', value: 42.49 },
|
||||
{ time: '2019-05-16', value: 43.19 },
|
||||
{ time: '2019-05-17', value: 43.54 },
|
||||
{ time: '2019-05-20', value: 42.78 },
|
||||
{ time: '2019-05-21', value: 43.29 },
|
||||
{ time: '2019-05-22', value: 43.3 },
|
||||
{ time: '2019-05-23', value: 42.73 },
|
||||
{ time: '2019-05-24', value: 42.67 },
|
||||
{ time: '2019-05-28', value: 42.75 },
|
||||
]
|
||||
@@ -1,169 +0,0 @@
|
||||
import React, { useRef, useState, useEffect, useCallback, Dispatch, SetStateAction, ReactNode } from 'react'
|
||||
import { createChart, IChartApi } from 'lightweight-charts'
|
||||
import { darken } from 'polished'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import Card from '../Card'
|
||||
import styled from 'styled-components/macro'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
|
||||
const Wrapper = styled(Card)`
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
background-color: ${({ theme }) => theme.bg0}
|
||||
flex-direction: column;
|
||||
> * {
|
||||
font-size: 1rem;
|
||||
}
|
||||
`
|
||||
|
||||
const DEFAULT_HEIGHT = 300
|
||||
|
||||
export type LineChartProps = {
|
||||
data: any[]
|
||||
color?: string | undefined
|
||||
height?: number | undefined
|
||||
setValue?: Dispatch<SetStateAction<number | undefined>> // used for value on hover
|
||||
topLeft?: ReactNode | undefined
|
||||
topRight?: ReactNode | undefined
|
||||
bottomLeft?: ReactNode | undefined
|
||||
bottomRight?: ReactNode | undefined
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const LineChart = ({
|
||||
data,
|
||||
color = '#56B2A4',
|
||||
setValue,
|
||||
topLeft,
|
||||
topRight,
|
||||
bottomLeft,
|
||||
bottomRight,
|
||||
height = DEFAULT_HEIGHT,
|
||||
...rest
|
||||
}: LineChartProps) => {
|
||||
const theme = useTheme()
|
||||
|
||||
const chartRef = useRef<HTMLDivElement>(null)
|
||||
const [chartCreated, setChart] = useState<IChartApi | undefined>()
|
||||
|
||||
// for reseting value on hover exit
|
||||
const currenValue = data[data.length - 1].value
|
||||
|
||||
const handleResize = useCallback(() => {
|
||||
if (chartCreated && chartRef?.current?.parentElement) {
|
||||
chartCreated.resize(chartRef.current.parentElement.clientWidth - 32, height)
|
||||
chartCreated.timeScale().fitContent()
|
||||
chartCreated.timeScale().scrollToPosition(0, false)
|
||||
}
|
||||
}, [chartCreated, chartRef, height])
|
||||
|
||||
// add event listener for resize
|
||||
const isClient = typeof window === 'object'
|
||||
useEffect(() => {
|
||||
if (!isClient) {
|
||||
return
|
||||
}
|
||||
window.addEventListener('resize', handleResize)
|
||||
return () => window.removeEventListener('resize', handleResize)
|
||||
}, [isClient, chartRef, handleResize]) // Empty array ensures that effect is only run on mount and unmount
|
||||
|
||||
const textColor = theme.text2
|
||||
|
||||
// if chart not instantiated in canvas, create it
|
||||
useEffect(() => {
|
||||
if (!chartCreated && data && !!chartRef?.current?.parentElement) {
|
||||
const chart = createChart(chartRef.current, {
|
||||
height: height,
|
||||
width: chartRef.current.parentElement.clientWidth - 32,
|
||||
layout: {
|
||||
backgroundColor: 'transparent',
|
||||
textColor: textColor,
|
||||
fontFamily: 'Inter',
|
||||
},
|
||||
rightPriceScale: {
|
||||
scaleMargins: {
|
||||
top: 0.1,
|
||||
bottom: 0.1,
|
||||
},
|
||||
borderVisible: false,
|
||||
},
|
||||
timeScale: {
|
||||
borderVisible: false,
|
||||
},
|
||||
watermark: {
|
||||
color: 'rgba(0, 0, 0, 0)',
|
||||
},
|
||||
grid: {
|
||||
horzLines: {
|
||||
visible: false,
|
||||
},
|
||||
vertLines: {
|
||||
visible: false,
|
||||
},
|
||||
},
|
||||
crosshair: {
|
||||
horzLine: {
|
||||
visible: true,
|
||||
style: 3,
|
||||
width: 1,
|
||||
color: '#505050',
|
||||
labelBackgroundColor: color,
|
||||
},
|
||||
vertLine: {
|
||||
visible: true,
|
||||
style: 3,
|
||||
width: 1,
|
||||
color: '#505050',
|
||||
labelBackgroundColor: color,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const series = chart.addAreaSeries({
|
||||
lineColor: color,
|
||||
topColor: darken(0.4, color),
|
||||
bottomColor: theme.bg0,
|
||||
lineWidth: 2,
|
||||
priceLineVisible: false,
|
||||
})
|
||||
|
||||
series.setData(data)
|
||||
|
||||
// update the title when hovering on the chart
|
||||
chart.subscribeCrosshairMove(function (param) {
|
||||
if (
|
||||
chartRef?.current &&
|
||||
(param === undefined ||
|
||||
param.time === undefined ||
|
||||
(param && param.point && param.point.x < 0) ||
|
||||
(param && param.point && param.point.x > chartRef.current.clientWidth) ||
|
||||
(param && param.point && param.point.y < 0) ||
|
||||
(param && param.point && param.point.y > height))
|
||||
) {
|
||||
setValue && setValue(currenValue)
|
||||
} else {
|
||||
const price = parseFloat(param.seriesPrices.get(series)?.toString() ?? currenValue)
|
||||
setValue && setValue(price)
|
||||
}
|
||||
})
|
||||
chart.timeScale().fitContent()
|
||||
setChart(chart)
|
||||
}
|
||||
}, [color, chartCreated, currenValue, data, height, setValue, textColor, theme])
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<RowBetween>
|
||||
{topLeft ?? null}
|
||||
{topRight ?? null}
|
||||
</RowBetween>
|
||||
<div ref={chartRef} id={'line-chart'} {...rest} />
|
||||
<RowBetween>
|
||||
{bottomLeft ?? null}
|
||||
{bottomRight ?? null}
|
||||
</RowBetween>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default LineChart
|
||||
44
src/components/LiquidityChartRangeInput/Area.tsx
Normal file
44
src/components/LiquidityChartRangeInput/Area.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { area, curveStepAfter, ScaleLinear } from 'd3'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ChartEntry } from './types'
|
||||
import inRange from 'lodash/inRange'
|
||||
|
||||
const Path = styled.path<{ fill: string | undefined }>`
|
||||
opacity: 0.5;
|
||||
stroke: ${({ fill, theme }) => fill ?? theme.blue2};
|
||||
fill: ${({ fill, theme }) => fill ?? theme.blue2};
|
||||
`
|
||||
|
||||
export const Area = ({
|
||||
series,
|
||||
xScale,
|
||||
yScale,
|
||||
xValue,
|
||||
yValue,
|
||||
fill,
|
||||
}: {
|
||||
series: ChartEntry[]
|
||||
xScale: ScaleLinear<number, number>
|
||||
yScale: ScaleLinear<number, number>
|
||||
xValue: (d: ChartEntry) => number
|
||||
yValue: (d: ChartEntry) => number
|
||||
fill?: string | undefined
|
||||
}) =>
|
||||
useMemo(
|
||||
() => (
|
||||
<Path
|
||||
fill={fill}
|
||||
d={
|
||||
area()
|
||||
.curve(curveStepAfter)
|
||||
.x((d: unknown) => xScale(xValue(d as ChartEntry)))
|
||||
.y1((d: unknown) => yScale(yValue(d as ChartEntry)))
|
||||
.y0(yScale(0))(
|
||||
series.filter((d) => inRange(xScale(xValue(d)), 0, innerWidth)) as Iterable<[number, number]>
|
||||
) ?? undefined
|
||||
}
|
||||
/>
|
||||
),
|
||||
[fill, series, xScale, xValue, yScale, yValue]
|
||||
)
|
||||
43
src/components/LiquidityChartRangeInput/AxisBottom.tsx
Normal file
43
src/components/LiquidityChartRangeInput/AxisBottom.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { Axis as d3Axis, axisBottom, NumberValue, ScaleLinear, select } from 'd3'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const StyledGroup = styled.g`
|
||||
line {
|
||||
display: none;
|
||||
}
|
||||
|
||||
text {
|
||||
color: ${({ theme }) => theme.text2};
|
||||
transform: translateY(5px);
|
||||
}
|
||||
`
|
||||
|
||||
const Axis = ({ axisGenerator }: { axisGenerator: d3Axis<NumberValue> }) => {
|
||||
const axisRef = (axis: SVGGElement) => {
|
||||
axis &&
|
||||
select(axis)
|
||||
.call(axisGenerator)
|
||||
.call((g) => g.select('.domain').remove())
|
||||
}
|
||||
|
||||
return <g ref={axisRef} />
|
||||
}
|
||||
|
||||
export const AxisBottom = ({
|
||||
xScale,
|
||||
innerHeight,
|
||||
offset = 0,
|
||||
}: {
|
||||
xScale: ScaleLinear<number, number>
|
||||
innerHeight: number
|
||||
offset?: number
|
||||
}) =>
|
||||
useMemo(
|
||||
() => (
|
||||
<StyledGroup transform={`translate(0, ${innerHeight + offset})`}>
|
||||
<Axis axisGenerator={axisBottom(xScale).ticks(6)} />
|
||||
</StyledGroup>
|
||||
),
|
||||
[innerHeight, offset, xScale]
|
||||
)
|
||||
262
src/components/LiquidityChartRangeInput/Brush.tsx
Normal file
262
src/components/LiquidityChartRangeInput/Brush.tsx
Normal file
@@ -0,0 +1,262 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { BrushBehavior, brushX, D3BrushEvent, ScaleLinear, select } from 'd3'
|
||||
import styled from 'styled-components/macro'
|
||||
import { brushHandleAccentPath, brushHandlePath, OffScreenHandle } from 'components/LiquidityChartRangeInput/svg'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
|
||||
const Handle = styled.path<{ color: string }>`
|
||||
cursor: ew-resize;
|
||||
pointer-events: none;
|
||||
|
||||
stroke-width: 3;
|
||||
stroke: ${({ color }) => color};
|
||||
fill: ${({ color }) => color};
|
||||
`
|
||||
|
||||
const HandleAccent = styled.path`
|
||||
cursor: ew-resize;
|
||||
pointer-events: none;
|
||||
|
||||
stroke-width: 1.5;
|
||||
stroke: ${({ theme }) => theme.white};
|
||||
opacity: 0.6;
|
||||
`
|
||||
|
||||
const LabelGroup = styled.g<{ visible: boolean }>`
|
||||
opacity: ${({ visible }) => (visible ? '1' : '0')};
|
||||
transition: opacity 300ms;
|
||||
`
|
||||
|
||||
const TooltipBackground = styled.rect`
|
||||
fill: ${({ theme }) => theme.bg2};
|
||||
`
|
||||
|
||||
const Tooltip = styled.text`
|
||||
text-anchor: middle;
|
||||
font-size: 13px;
|
||||
fill: ${({ theme }) => theme.text1};
|
||||
`
|
||||
|
||||
// flips the handles draggers when close to the container edges
|
||||
const FLIP_HANDLE_THRESHOLD_PX = 20
|
||||
|
||||
// margin to prevent tick snapping from putting the brush off screen
|
||||
const BRUSH_EXTENT_MARGIN_PX = 2
|
||||
|
||||
const compare = (a1: [number, number], a2: [number, number]): boolean => a1[0] !== a2[0] || a1[1] !== a2[1]
|
||||
|
||||
export const Brush = ({
|
||||
id,
|
||||
xScale,
|
||||
interactive,
|
||||
brushLabelValue,
|
||||
brushExtent,
|
||||
setBrushExtent,
|
||||
innerWidth,
|
||||
innerHeight,
|
||||
westHandleColor,
|
||||
eastHandleColor,
|
||||
}: {
|
||||
id: string
|
||||
xScale: ScaleLinear<number, number>
|
||||
interactive: boolean
|
||||
brushLabelValue: (d: 'w' | 'e', x: number) => string
|
||||
brushExtent: [number, number]
|
||||
setBrushExtent: (extent: [number, number]) => void
|
||||
innerWidth: number
|
||||
innerHeight: number
|
||||
westHandleColor: string
|
||||
eastHandleColor: string
|
||||
}) => {
|
||||
const brushRef = useRef<SVGGElement | null>(null)
|
||||
const brushBehavior = useRef<BrushBehavior<SVGGElement> | null>(null)
|
||||
|
||||
// only used to drag the handles on brush for performance
|
||||
const [localBrushExtent, setLocalBrushExtent] = useState<[number, number] | null>(brushExtent)
|
||||
const [showLabels, setShowLabels] = useState(false)
|
||||
const [hovering, setHovering] = useState(false)
|
||||
|
||||
const previousBrushExtent = usePrevious(brushExtent)
|
||||
|
||||
const brushed = useCallback(
|
||||
({ type, selection }: D3BrushEvent<unknown>) => {
|
||||
if (!selection) {
|
||||
setLocalBrushExtent(null)
|
||||
return
|
||||
}
|
||||
|
||||
const scaled = (selection as [number, number]).map(xScale.invert) as [number, number]
|
||||
|
||||
// avoid infinite render loop by checking for change
|
||||
if (type === 'end' && compare(brushExtent, scaled)) {
|
||||
setBrushExtent(scaled)
|
||||
}
|
||||
|
||||
setLocalBrushExtent(scaled)
|
||||
},
|
||||
[xScale, brushExtent, setBrushExtent]
|
||||
)
|
||||
|
||||
// keep local and external brush extent in sync
|
||||
// i.e. snap to ticks on bruhs end
|
||||
useEffect(() => {
|
||||
setLocalBrushExtent(brushExtent)
|
||||
}, [brushExtent])
|
||||
|
||||
// initialize the brush
|
||||
useEffect(() => {
|
||||
if (!brushRef.current) return
|
||||
|
||||
brushBehavior.current = brushX<SVGGElement>()
|
||||
.extent([
|
||||
[Math.max(0 + BRUSH_EXTENT_MARGIN_PX, xScale(0)), 0],
|
||||
[innerWidth - BRUSH_EXTENT_MARGIN_PX, innerHeight],
|
||||
])
|
||||
.handleSize(30)
|
||||
.filter(() => interactive)
|
||||
.on('brush end', brushed)
|
||||
|
||||
brushBehavior.current(select(brushRef.current))
|
||||
|
||||
if (previousBrushExtent && compare(brushExtent, previousBrushExtent)) {
|
||||
select(brushRef.current)
|
||||
.transition()
|
||||
.call(brushBehavior.current.move as any, brushExtent.map(xScale))
|
||||
}
|
||||
|
||||
// brush linear gradient
|
||||
select(brushRef.current)
|
||||
.selectAll('.selection')
|
||||
.attr('stroke', 'none')
|
||||
.attr('fill-opacity', '0.1')
|
||||
.attr('fill', `url(#${id}-gradient-selection)`)
|
||||
}, [brushExtent, brushed, id, innerHeight, innerWidth, interactive, previousBrushExtent, xScale])
|
||||
|
||||
// respond to xScale changes only
|
||||
useEffect(() => {
|
||||
if (!brushRef.current || !brushBehavior.current) return
|
||||
|
||||
brushBehavior.current.move(select(brushRef.current) as any, brushExtent.map(xScale) as any)
|
||||
}, [brushExtent, xScale])
|
||||
|
||||
// show labels when local brush changes
|
||||
useEffect(() => {
|
||||
setShowLabels(true)
|
||||
const timeout = setTimeout(() => setShowLabels(false), 1500)
|
||||
return () => clearTimeout(timeout)
|
||||
}, [localBrushExtent])
|
||||
|
||||
// variables to help render the SVGs
|
||||
const flipWestHandle = localBrushExtent && xScale(localBrushExtent[0]) > FLIP_HANDLE_THRESHOLD_PX
|
||||
const flipEastHandle = localBrushExtent && xScale(localBrushExtent[1]) > innerWidth - FLIP_HANDLE_THRESHOLD_PX
|
||||
|
||||
const showWestArrow = localBrushExtent && (xScale(localBrushExtent[0]) < 0 || xScale(localBrushExtent[1]) < 0)
|
||||
const showEastArrow =
|
||||
localBrushExtent && (xScale(localBrushExtent[0]) > innerWidth || xScale(localBrushExtent[1]) > innerWidth)
|
||||
|
||||
const westHandleInView =
|
||||
localBrushExtent && xScale(localBrushExtent[0]) >= 0 && xScale(localBrushExtent[0]) <= innerWidth
|
||||
const eastHandleInView =
|
||||
localBrushExtent && xScale(localBrushExtent[1]) >= 0 && xScale(localBrushExtent[1]) <= innerWidth
|
||||
|
||||
return useMemo(
|
||||
() => (
|
||||
<>
|
||||
<defs>
|
||||
<linearGradient id={`${id}-gradient-selection`} x1="0%" y1="100%" x2="100%" y2="100%">
|
||||
<stop stopColor={westHandleColor} />
|
||||
<stop stopColor={eastHandleColor} offset="1" />
|
||||
</linearGradient>
|
||||
|
||||
{/* clips at exactly the svg area */}
|
||||
<clipPath id={`${id}-brush-clip`}>
|
||||
<rect x="0" y="0" width={innerWidth} height={innerHeight} />
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
{/* will host the d3 brush */}
|
||||
<g
|
||||
ref={brushRef}
|
||||
clipPath={`url(#${id}-brush-clip)`}
|
||||
onMouseEnter={() => setHovering(true)}
|
||||
onMouseLeave={() => setHovering(false)}
|
||||
/>
|
||||
|
||||
{/* custom brush handles */}
|
||||
{localBrushExtent && (
|
||||
<>
|
||||
{/* west handle */}
|
||||
{westHandleInView ? (
|
||||
<g
|
||||
transform={`translate(${Math.max(0, xScale(localBrushExtent[0]))}, 0), scale(${
|
||||
flipWestHandle ? '-1' : '1'
|
||||
}, 1)`}
|
||||
>
|
||||
<g>
|
||||
<Handle color={westHandleColor} d={brushHandlePath(innerHeight)} />
|
||||
<HandleAccent d={brushHandleAccentPath()} />
|
||||
</g>
|
||||
|
||||
<LabelGroup
|
||||
transform={`translate(50,0), scale(${flipWestHandle ? '1' : '-1'}, 1)`}
|
||||
visible={showLabels || hovering}
|
||||
>
|
||||
<TooltipBackground y="0" x="-30" height="30" width="60" rx="8" />
|
||||
<Tooltip transform={`scale(-1, 1)`} y="15" dominantBaseline="middle">
|
||||
{brushLabelValue('w', localBrushExtent[0])}
|
||||
</Tooltip>
|
||||
</LabelGroup>
|
||||
</g>
|
||||
) : null}
|
||||
|
||||
{/* east handle */}
|
||||
{eastHandleInView ? (
|
||||
<g transform={`translate(${xScale(localBrushExtent[1])}, 0), scale(${flipEastHandle ? '-1' : '1'}, 1)`}>
|
||||
<g>
|
||||
<Handle color={eastHandleColor} d={brushHandlePath(innerHeight)} />
|
||||
<HandleAccent d={brushHandleAccentPath()} />
|
||||
</g>
|
||||
|
||||
<LabelGroup
|
||||
transform={`translate(50,0), scale(${flipEastHandle ? '-1' : '1'}, 1)`}
|
||||
visible={showLabels || hovering}
|
||||
>
|
||||
<TooltipBackground y="0" x="-30" height="30" width="60" rx="8" />
|
||||
<Tooltip y="15" dominantBaseline="middle">
|
||||
{brushLabelValue('e', localBrushExtent[1])}
|
||||
</Tooltip>
|
||||
</LabelGroup>
|
||||
</g>
|
||||
) : null}
|
||||
|
||||
{showWestArrow && <OffScreenHandle color={westHandleColor} />}
|
||||
|
||||
{showEastArrow && (
|
||||
<g transform={`translate(${innerWidth}, 0) scale(-1, 1)`}>
|
||||
<OffScreenHandle color={eastHandleColor} />
|
||||
</g>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
[
|
||||
brushLabelValue,
|
||||
eastHandleColor,
|
||||
eastHandleInView,
|
||||
flipEastHandle,
|
||||
flipWestHandle,
|
||||
hovering,
|
||||
id,
|
||||
innerHeight,
|
||||
innerWidth,
|
||||
localBrushExtent,
|
||||
showEastArrow,
|
||||
showLabels,
|
||||
showWestArrow,
|
||||
westHandleColor,
|
||||
westHandleInView,
|
||||
xScale,
|
||||
]
|
||||
)
|
||||
}
|
||||
138
src/components/LiquidityChartRangeInput/Chart.tsx
Normal file
138
src/components/LiquidityChartRangeInput/Chart.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import { max, scaleLinear, ZoomTransform } from 'd3'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Area } from './Area'
|
||||
import { AxisBottom } from './AxisBottom'
|
||||
import { Brush } from './Brush'
|
||||
import { Line } from './Line'
|
||||
import { ChartEntry, LiquidityChartRangeInputProps } from './types'
|
||||
import Zoom, { ZoomOverlay } from './Zoom'
|
||||
|
||||
export const xAccessor = (d: ChartEntry) => d.price0
|
||||
export const yAccessor = (d: ChartEntry) => d.activeLiquidity
|
||||
|
||||
export function Chart({
|
||||
id = 'liquidityChartRangeInput',
|
||||
data: { series, current },
|
||||
styles,
|
||||
dimensions: { width, height },
|
||||
margins,
|
||||
interactive = true,
|
||||
brushDomain,
|
||||
brushLabels,
|
||||
onBrushDomainChange,
|
||||
zoomLevels,
|
||||
}: LiquidityChartRangeInputProps) {
|
||||
const zoomRef = useRef<SVGRectElement | null>(null)
|
||||
|
||||
const [zoom, setZoom] = useState<ZoomTransform | null>(null)
|
||||
|
||||
const [innerHeight, innerWidth] = useMemo(
|
||||
() => [height - margins.top - margins.bottom, width - margins.left - margins.right],
|
||||
[width, height, margins]
|
||||
)
|
||||
|
||||
const { xScale, yScale } = useMemo(() => {
|
||||
const scales = {
|
||||
xScale: scaleLinear()
|
||||
.domain([current * zoomLevels.initialMin, current * zoomLevels.initialMax] as number[])
|
||||
.range([0, innerWidth]),
|
||||
yScale: scaleLinear()
|
||||
.domain([0, max(series, yAccessor)] as number[])
|
||||
.range([innerHeight, 0]),
|
||||
}
|
||||
|
||||
if (zoom) {
|
||||
const newXscale = zoom.rescaleX(scales.xScale)
|
||||
scales.xScale.domain(newXscale.domain())
|
||||
}
|
||||
|
||||
return scales
|
||||
}, [current, zoomLevels.initialMin, zoomLevels.initialMax, innerWidth, series, innerHeight, zoom])
|
||||
|
||||
useEffect(() => {
|
||||
// reset zoom as necessary
|
||||
setZoom(null)
|
||||
}, [zoomLevels])
|
||||
|
||||
useEffect(() => {
|
||||
if (!brushDomain) {
|
||||
onBrushDomainChange(xScale.domain() as [number, number])
|
||||
}
|
||||
}, [brushDomain, onBrushDomainChange, xScale])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Zoom
|
||||
svg={zoomRef.current}
|
||||
xScale={xScale}
|
||||
setZoom={setZoom}
|
||||
width={innerWidth}
|
||||
height={
|
||||
// allow zooming inside the x-axis
|
||||
height
|
||||
}
|
||||
showClear={false}
|
||||
zoomLevels={zoomLevels}
|
||||
/>
|
||||
<svg width="100%" height="100%" viewBox={`0 0 ${width} ${height}`} style={{ overflow: 'visible' }}>
|
||||
<defs>
|
||||
<clipPath id={`${id}-chart-clip`}>
|
||||
<rect x="0" y="0" width={innerWidth} height={height} />
|
||||
</clipPath>
|
||||
|
||||
{brushDomain && (
|
||||
// mask to highlight selected area
|
||||
<mask id={`${id}-chart-area-mask`}>
|
||||
<rect
|
||||
fill="white"
|
||||
x={xScale(brushDomain[0])}
|
||||
y="0"
|
||||
width={xScale(brushDomain[1]) - xScale(brushDomain[0])}
|
||||
height={innerHeight}
|
||||
/>
|
||||
</mask>
|
||||
)}
|
||||
</defs>
|
||||
|
||||
<g transform={`translate(${margins.left},${margins.top})`}>
|
||||
<g clipPath={`url(#${id}-chart-clip)`}>
|
||||
<Area series={series} xScale={xScale} yScale={yScale} xValue={xAccessor} yValue={yAccessor} />
|
||||
|
||||
{brushDomain && (
|
||||
// duplicate area chart with mask for selected area
|
||||
<g mask={`url(#${id}-chart-area-mask)`}>
|
||||
<Area
|
||||
series={series}
|
||||
xScale={xScale}
|
||||
yScale={yScale}
|
||||
xValue={xAccessor}
|
||||
yValue={yAccessor}
|
||||
fill={styles.area.selection}
|
||||
/>
|
||||
</g>
|
||||
)}
|
||||
|
||||
<Line value={current} xScale={xScale} innerHeight={innerHeight} />
|
||||
|
||||
<AxisBottom xScale={xScale} innerHeight={innerHeight} />
|
||||
</g>
|
||||
|
||||
<ZoomOverlay width={innerWidth} height={height} ref={zoomRef} />
|
||||
|
||||
<Brush
|
||||
id={id}
|
||||
xScale={xScale}
|
||||
interactive={interactive}
|
||||
brushLabelValue={brushLabels}
|
||||
brushExtent={brushDomain ?? (xScale.domain() as [number, number])}
|
||||
innerWidth={innerWidth}
|
||||
innerHeight={innerHeight}
|
||||
setBrushExtent={onBrushDomainChange}
|
||||
westHandleColor={styles.brush.handle.west}
|
||||
eastHandleColor={styles.brush.handle.east}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</>
|
||||
)
|
||||
}
|
||||
24
src/components/LiquidityChartRangeInput/Line.tsx
Normal file
24
src/components/LiquidityChartRangeInput/Line.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { ScaleLinear } from 'd3'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const StyledLine = styled.line`
|
||||
opacity: 0.5;
|
||||
stroke-width: 2;
|
||||
stroke: ${({ theme }) => theme.text1};
|
||||
fill: none;
|
||||
`
|
||||
|
||||
export const Line = ({
|
||||
value,
|
||||
xScale,
|
||||
innerHeight,
|
||||
}: {
|
||||
value: number
|
||||
xScale: ScaleLinear<number, number>
|
||||
innerHeight: number
|
||||
}) =>
|
||||
useMemo(
|
||||
() => <StyledLine x1={xScale(value)} y1="0" x2={xScale(value)} y2={innerHeight} />,
|
||||
[value, xScale, innerHeight]
|
||||
)
|
||||
121
src/components/LiquidityChartRangeInput/Zoom.tsx
Normal file
121
src/components/LiquidityChartRangeInput/Zoom.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import React, { useEffect, useMemo, useRef } from 'react'
|
||||
import { ButtonGray } from 'components/Button'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ScaleLinear, select, ZoomBehavior, zoom, ZoomTransform } from 'd3'
|
||||
import { RefreshCcw, ZoomIn, ZoomOut } from 'react-feather'
|
||||
import { ZoomLevels } from './types'
|
||||
|
||||
const Wrapper = styled.div<{ count: number }>`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(${({ count }) => count.toString()}, 1fr);
|
||||
grid-gap: 6px;
|
||||
|
||||
position: absolute;
|
||||
top: -75px;
|
||||
right: 0;
|
||||
`
|
||||
|
||||
const Button = styled(ButtonGray)`
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
color: ${({ theme }) => theme.text1};
|
||||
}
|
||||
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 4px;
|
||||
`
|
||||
|
||||
export const ZoomOverlay = styled.rect`
|
||||
fill: transparent;
|
||||
cursor: grab;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
`
|
||||
|
||||
export default function Zoom({
|
||||
svg,
|
||||
xScale,
|
||||
setZoom,
|
||||
width,
|
||||
height,
|
||||
showClear,
|
||||
zoomLevels,
|
||||
}: {
|
||||
svg: SVGElement | null
|
||||
xScale: ScaleLinear<number, number>
|
||||
setZoom: (transform: ZoomTransform) => void
|
||||
width: number
|
||||
height: number
|
||||
showClear: boolean
|
||||
zoomLevels: ZoomLevels
|
||||
}) {
|
||||
const zoomBehavior = useRef<ZoomBehavior<Element, unknown>>()
|
||||
|
||||
const [zoomIn, zoomOut, reset, initial] = useMemo(
|
||||
() => [
|
||||
() =>
|
||||
svg &&
|
||||
zoomBehavior.current &&
|
||||
select(svg as Element)
|
||||
.transition()
|
||||
.call(zoomBehavior.current.scaleBy, 2),
|
||||
() =>
|
||||
svg &&
|
||||
zoomBehavior.current &&
|
||||
select(svg as Element)
|
||||
.transition()
|
||||
.call(zoomBehavior.current.scaleBy, 0.5),
|
||||
() =>
|
||||
svg &&
|
||||
zoomBehavior.current &&
|
||||
select(svg as Element)
|
||||
.transition()
|
||||
.call(zoomBehavior.current.scaleTo, 1),
|
||||
() =>
|
||||
svg &&
|
||||
zoomBehavior.current &&
|
||||
select(svg as Element)
|
||||
.transition()
|
||||
.call(zoomBehavior.current.scaleTo, 0.5),
|
||||
],
|
||||
[svg, zoomBehavior]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!svg) return
|
||||
|
||||
zoomBehavior.current = zoom()
|
||||
.scaleExtent([zoomLevels.min, zoomLevels.max])
|
||||
.extent([
|
||||
[0, 0],
|
||||
[width, height],
|
||||
])
|
||||
.on('zoom', ({ transform }: { transform: ZoomTransform }) => setZoom(transform))
|
||||
|
||||
select(svg as Element).call(zoomBehavior.current)
|
||||
}, [height, width, setZoom, svg, xScale, zoomBehavior, zoomLevels, zoomLevels.max, zoomLevels.min])
|
||||
|
||||
useEffect(() => {
|
||||
// reset zoom to initial on zoomLevel change
|
||||
initial()
|
||||
}, [initial, zoomLevels])
|
||||
|
||||
return (
|
||||
<Wrapper count={showClear ? 3 : 2}>
|
||||
{showClear && (
|
||||
<Button onClick={reset} disabled={false}>
|
||||
<RefreshCcw size={16} />
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={zoomIn} disabled={false}>
|
||||
<ZoomIn size={16} />
|
||||
</Button>
|
||||
<Button onClick={zoomOut} disabled={false}>
|
||||
<ZoomOut size={16} />
|
||||
</Button>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
56
src/components/LiquidityChartRangeInput/hooks.ts
Normal file
56
src/components/LiquidityChartRangeInput/hooks.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { usePoolActiveLiquidity } from 'hooks/usePoolTickData'
|
||||
import { ChartEntry } from './types'
|
||||
import JSBI from 'jsbi'
|
||||
|
||||
export interface TickProcessed {
|
||||
liquidityActive: JSBI
|
||||
price0: string
|
||||
}
|
||||
|
||||
export function useDensityChartData({
|
||||
currencyA,
|
||||
currencyB,
|
||||
feeAmount,
|
||||
}: {
|
||||
currencyA: Currency | undefined
|
||||
currencyB: Currency | undefined
|
||||
feeAmount: FeeAmount | undefined
|
||||
}) {
|
||||
const { isLoading, isUninitialized, isError, error, data } = usePoolActiveLiquidity(currencyA, currencyB, feeAmount)
|
||||
|
||||
const formatData = useCallback(() => {
|
||||
if (!data?.length) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const newData: ChartEntry[] = []
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const t: TickProcessed = data[i]
|
||||
|
||||
const chartEntry = {
|
||||
activeLiquidity: parseFloat(t.liquidityActive.toString()),
|
||||
price0: parseFloat(t.price0),
|
||||
}
|
||||
|
||||
if (chartEntry.activeLiquidity > 0) {
|
||||
newData.push(chartEntry)
|
||||
}
|
||||
}
|
||||
|
||||
return newData
|
||||
}, [data])
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
isLoading,
|
||||
isUninitialized,
|
||||
isError,
|
||||
error,
|
||||
formattedData: !isLoading && !isUninitialized ? formatData() : undefined,
|
||||
}
|
||||
}, [isLoading, isUninitialized, isError, error, formatData])
|
||||
}
|
||||
197
src/components/LiquidityChartRangeInput/index.tsx
Normal file
197
src/components/LiquidityChartRangeInput/index.tsx
Normal file
@@ -0,0 +1,197 @@
|
||||
import React, { ReactNode, useCallback, useMemo } from 'react'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, Price, Token } from '@uniswap/sdk-core'
|
||||
import { AutoColumn, ColumnCenter } from 'components/Column'
|
||||
import Loader from 'components/Loader'
|
||||
import { useColor } from 'hooks/useColor'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { saturate } from 'polished'
|
||||
import { BarChart2, Inbox, CloudOff } from 'react-feather'
|
||||
import { batch } from 'react-redux'
|
||||
import styled from 'styled-components/macro'
|
||||
import { TYPE } from '../../theme'
|
||||
import { Chart } from './Chart'
|
||||
import { useDensityChartData } from './hooks'
|
||||
import { format } from 'd3'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import ReactGA from 'react-ga'
|
||||
import { ZoomLevels } from './types'
|
||||
|
||||
const ZOOM_LEVELS: Record<FeeAmount, ZoomLevels> = {
|
||||
[FeeAmount.LOW]: {
|
||||
initialMin: 0.999,
|
||||
initialMax: 1.001,
|
||||
min: 0.00001,
|
||||
max: 1.5,
|
||||
},
|
||||
[FeeAmount.MEDIUM]: {
|
||||
initialMin: 0.5,
|
||||
initialMax: 2,
|
||||
min: 0.00001,
|
||||
max: 20,
|
||||
},
|
||||
[FeeAmount.HIGH]: {
|
||||
initialMin: 0.5,
|
||||
initialMax: 2,
|
||||
min: 0.00001,
|
||||
max: 20,
|
||||
},
|
||||
}
|
||||
|
||||
const ChartWrapper = styled.div`
|
||||
position: relative;
|
||||
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
`
|
||||
|
||||
function InfoBox({ message, icon }: { message?: ReactNode; icon: ReactNode }) {
|
||||
return (
|
||||
<ColumnCenter style={{ height: '100%', justifyContent: 'center' }}>
|
||||
{icon}
|
||||
{message && (
|
||||
<TYPE.mediumHeader padding={10} marginTop="20px" textAlign="center">
|
||||
{message}
|
||||
</TYPE.mediumHeader>
|
||||
)}
|
||||
</ColumnCenter>
|
||||
)
|
||||
}
|
||||
|
||||
export default function LiquidityChartRangeInput({
|
||||
currencyA,
|
||||
currencyB,
|
||||
feeAmount,
|
||||
ticksAtLimit,
|
||||
price,
|
||||
priceLower,
|
||||
priceUpper,
|
||||
onLeftRangeInput,
|
||||
onRightRangeInput,
|
||||
interactive,
|
||||
}: {
|
||||
currencyA: Currency | undefined
|
||||
currencyB: Currency | undefined
|
||||
feeAmount?: FeeAmount
|
||||
ticksAtLimit: { [bound in Bound]?: boolean | undefined }
|
||||
price: number | undefined
|
||||
priceLower?: Price<Token, Token>
|
||||
priceUpper?: Price<Token, Token>
|
||||
onLeftRangeInput: (typedValue: string) => void
|
||||
onRightRangeInput: (typedValue: string) => void
|
||||
interactive: boolean
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
|
||||
const tokenAColor = useColor(currencyA?.wrapped)
|
||||
const tokenBColor = useColor(currencyB?.wrapped)
|
||||
|
||||
const { isLoading, isUninitialized, isError, error, formattedData } = useDensityChartData({
|
||||
currencyA,
|
||||
currencyB,
|
||||
feeAmount,
|
||||
})
|
||||
|
||||
const onBrushDomainChangeEnded = useCallback(
|
||||
(domain) => {
|
||||
let leftRangeValue = Number(domain[0])
|
||||
const rightRangeValue = Number(domain[1])
|
||||
|
||||
if (leftRangeValue <= 0) {
|
||||
leftRangeValue = 1 / 10 ** 6
|
||||
}
|
||||
|
||||
batch(() => {
|
||||
// simulate user input for auto-formatting and other validations
|
||||
leftRangeValue > 0 && onLeftRangeInput(leftRangeValue.toFixed(6))
|
||||
rightRangeValue > 0 && onRightRangeInput(rightRangeValue.toFixed(6))
|
||||
})
|
||||
},
|
||||
[onLeftRangeInput, onRightRangeInput]
|
||||
)
|
||||
|
||||
interactive = interactive && Boolean(formattedData?.length)
|
||||
|
||||
const brushDomain: [number, number] | undefined = useMemo(() => {
|
||||
const isSorted = currencyA && currencyB && currencyA?.wrapped.sortsBefore(currencyB?.wrapped)
|
||||
|
||||
const leftPrice = isSorted ? priceLower : priceUpper?.invert()
|
||||
const rightPrice = isSorted ? priceUpper : priceLower?.invert()
|
||||
|
||||
return leftPrice && rightPrice
|
||||
? [parseFloat(leftPrice?.toSignificant(5)), parseFloat(rightPrice?.toSignificant(5))]
|
||||
: undefined
|
||||
}, [currencyA, currencyB, priceLower, priceUpper])
|
||||
|
||||
const brushLabelValue = useCallback(
|
||||
(d: 'w' | 'e', x: number) => {
|
||||
if (!price) return ''
|
||||
|
||||
if (d === 'w' && ticksAtLimit[Bound.LOWER]) return '0'
|
||||
if (d === 'e' && ticksAtLimit[Bound.UPPER]) return '∞'
|
||||
|
||||
//const percent = (((x < price ? -1 : 1) * (Math.max(x, price) - Math.min(x, price))) / Math.min(x, price)) * 100
|
||||
|
||||
const percent = (x < price ? -1 : 1) * ((Math.max(x, price) - Math.min(x, price)) / price) * 100
|
||||
|
||||
return price ? `${format(Math.abs(percent) > 1 ? '.2~s' : '.2~f')(percent)}%` : ''
|
||||
},
|
||||
[price, ticksAtLimit]
|
||||
)
|
||||
|
||||
if (isError) {
|
||||
ReactGA.exception({
|
||||
...error,
|
||||
category: 'Liquidity',
|
||||
fatal: false,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<AutoColumn gap="md" style={{ minHeight: '200px' }}>
|
||||
{isUninitialized ? (
|
||||
<InfoBox
|
||||
message={<Trans>Your position will appear here.</Trans>}
|
||||
icon={<Inbox size={56} stroke={theme.text1} />}
|
||||
/>
|
||||
) : isLoading ? (
|
||||
<InfoBox icon={<Loader size="40px" stroke={theme.text4} />} />
|
||||
) : isError ? (
|
||||
<InfoBox
|
||||
message={<Trans>Liquidity data not available.</Trans>}
|
||||
icon={<CloudOff size={56} stroke={theme.text4} />}
|
||||
/>
|
||||
) : !formattedData || formattedData === [] || !price ? (
|
||||
<InfoBox
|
||||
message={<Trans>There is no liquidity data.</Trans>}
|
||||
icon={<BarChart2 size={56} stroke={theme.text4} />}
|
||||
/>
|
||||
) : (
|
||||
<ChartWrapper>
|
||||
<Chart
|
||||
data={{ series: formattedData, current: price }}
|
||||
dimensions={{ width: 400, height: 200 }}
|
||||
margins={{ top: 10, right: 2, bottom: 20, left: 0 }}
|
||||
styles={{
|
||||
area: {
|
||||
selection: theme.blue1,
|
||||
},
|
||||
brush: {
|
||||
handle: {
|
||||
west: saturate(0.1, tokenAColor) ?? theme.red1,
|
||||
east: saturate(0.1, tokenBColor) ?? theme.blue1,
|
||||
},
|
||||
},
|
||||
}}
|
||||
interactive={interactive}
|
||||
brushLabels={brushLabelValue}
|
||||
brushDomain={brushDomain}
|
||||
onBrushDomainChange={onBrushDomainChangeEnded}
|
||||
zoomLevels={ZOOM_LEVELS[feeAmount ?? FeeAmount.MEDIUM]}
|
||||
/>
|
||||
</ChartWrapper>
|
||||
)}
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
61
src/components/LiquidityChartRangeInput/svg.tsx
Normal file
61
src/components/LiquidityChartRangeInput/svg.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Generates an SVG path for the east brush handle.
|
||||
* Apply `scale(-1, 1)` to generate west brush handle.
|
||||
*
|
||||
* |```````\
|
||||
* | | | |
|
||||
* |______/
|
||||
* |
|
||||
* |
|
||||
* |
|
||||
* |
|
||||
* |
|
||||
*
|
||||
* https://medium.com/@dennismphil/one-side-rounded-rectangle-using-svg-fb31cf318d90
|
||||
*/
|
||||
export const brushHandlePath = (height: number) =>
|
||||
[
|
||||
// handle
|
||||
`M 0 0`, // move to origin
|
||||
`v ${height}`, // vertical line
|
||||
'm 1 0', // move 1px to the right
|
||||
`V 0`, // second vertical line
|
||||
`M 0 1`, // move to origin
|
||||
|
||||
// head
|
||||
'h 12', // horizontal line
|
||||
'q 2 0, 2 2', // rounded corner
|
||||
'v 22', // vertical line
|
||||
'q 0 2 -2 2', // rounded corner
|
||||
'h -12', // horizontal line
|
||||
`z`, // close path
|
||||
].join(' ')
|
||||
|
||||
export const brushHandleAccentPath = () =>
|
||||
[
|
||||
'm 5 7', // move to first accent
|
||||
'v 14', // vertical line
|
||||
'M 0 0', // move to origin
|
||||
'm 9 7', // move to second accent
|
||||
'v 14', // vertical line
|
||||
'z',
|
||||
].join(' ')
|
||||
|
||||
export const OffScreenHandle = ({
|
||||
color,
|
||||
size = 10,
|
||||
margin = 10,
|
||||
}: {
|
||||
color: string
|
||||
size?: number
|
||||
margin?: number
|
||||
}) => (
|
||||
<polygon
|
||||
points={`0 0, ${size} ${size}, 0 ${size}`}
|
||||
transform={` translate(${size + margin}, ${margin}) rotate(45) `}
|
||||
fill={color}
|
||||
stroke={color}
|
||||
strokeWidth="4"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
)
|
||||
58
src/components/LiquidityChartRangeInput/types.ts
Normal file
58
src/components/LiquidityChartRangeInput/types.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
export interface ChartEntry {
|
||||
activeLiquidity: number
|
||||
price0: number
|
||||
}
|
||||
|
||||
export interface Dimensions {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export interface Margins {
|
||||
top: number
|
||||
right: number
|
||||
bottom: number
|
||||
left: number
|
||||
}
|
||||
|
||||
export interface ZoomLevels {
|
||||
initialMin: number
|
||||
initialMax: number
|
||||
min: number
|
||||
max: number
|
||||
}
|
||||
|
||||
export interface LiquidityChartRangeInputProps {
|
||||
// to distringuish between multiple charts in the DOM
|
||||
id?: string
|
||||
|
||||
data: {
|
||||
series: ChartEntry[]
|
||||
current: number
|
||||
}
|
||||
|
||||
styles: {
|
||||
area: {
|
||||
// color of the ticks in range
|
||||
selection: string
|
||||
}
|
||||
|
||||
brush: {
|
||||
handle: {
|
||||
west: string
|
||||
east: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dimensions: Dimensions
|
||||
margins: Margins
|
||||
|
||||
interactive?: boolean
|
||||
|
||||
brushLabels: (d: 'w' | 'e', x: number) => string
|
||||
brushDomain: [number, number] | undefined
|
||||
onBrushDomainChange: (domain: [number, number]) => void
|
||||
|
||||
zoomLevels: ZoomLevels
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
import React from 'react'
|
||||
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
|
||||
const rotate = keyframes`
|
||||
from {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { Slash } from 'react-feather'
|
||||
import { ImageProps } from 'rebass'
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
|
||||
const BAD_SRCS: { [tokenAddress: string]: true } = {}
|
||||
|
||||
export interface LogoProps extends Pick<ImageProps, 'style' | 'alt' | 'className'> {
|
||||
interface LogoProps extends Pick<ImageProps, 'style' | 'alt' | 'className'> {
|
||||
srcs: string[]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,32 @@
|
||||
import React, { useRef } from 'react'
|
||||
import { BookOpen, Code, Info, MessageCircle, PieChart } from 'react-feather'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
BookOpen,
|
||||
Code,
|
||||
Info,
|
||||
MessageCircle,
|
||||
PieChart,
|
||||
Moon,
|
||||
Sun,
|
||||
ChevronRight,
|
||||
ChevronLeft,
|
||||
Check,
|
||||
} from 'react-feather'
|
||||
import { Link } from 'react-router-dom'
|
||||
import styled, { css } from 'styled-components'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
import { ApplicationModal } from '../../state/application/actions'
|
||||
import { useModalOpen, useToggleModal } from '../../state/application/hooks'
|
||||
import { Trans } from '@lingui/macro'
|
||||
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
import { useDarkModeManager } from 'state/user/hooks'
|
||||
|
||||
import { L2_CHAIN_IDS, CHAIN_INFO, SupportedChainId } from 'constants/chains'
|
||||
import { LOCALE_LABEL, SupportedLocale, SUPPORTED_LOCALES } from 'constants/locales'
|
||||
import { useLocationLinkProps } from 'hooks/useLocationLinkProps'
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
|
||||
export enum FlyoutAlignment {
|
||||
LEFT = 'LEFT',
|
||||
@@ -30,17 +46,18 @@ const StyledMenuButton = styled.button`
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 35px;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
height: 38px;
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
border: 1px solid ${({ theme }) => theme.bg0};
|
||||
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
border-radius: 12px;
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
border: 1px solid ${({ theme }) => theme.bg3};
|
||||
}
|
||||
|
||||
svg {
|
||||
@@ -65,18 +82,22 @@ const StyledMenu = styled.div`
|
||||
`
|
||||
|
||||
const MenuFlyout = styled.span<{ flyoutAlignment?: FlyoutAlignment }>`
|
||||
min-width: 8.125rem;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
min-width: 196px;
|
||||
max-height: 350px;
|
||||
overflow: auto;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
|
||||
0px 24px 32px rgba(0, 0, 0, 0.01);
|
||||
border: 1px solid ${({ theme }) => theme.bg0};
|
||||
border-radius: 12px;
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 1rem;
|
||||
font-size: 16px;
|
||||
position: absolute;
|
||||
top: 3rem;
|
||||
z-index: 100;
|
||||
|
||||
${({ flyoutAlignment = FlyoutAlignment.RIGHT }) =>
|
||||
flyoutAlignment === FlyoutAlignment.RIGHT
|
||||
? css`
|
||||
@@ -86,7 +107,9 @@ const MenuFlyout = styled.span<{ flyoutAlignment?: FlyoutAlignment }>`
|
||||
left: 0rem;
|
||||
`};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
top: -17.25rem;
|
||||
bottom: unset;
|
||||
right: 0;
|
||||
left: unset;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -96,15 +119,13 @@ const MenuItem = styled(ExternalLink)`
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.5rem;
|
||||
justify-content: space-between;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.text1};
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
> svg {
|
||||
margin-right: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
const InternalMenuItem = styled(Link)`
|
||||
@@ -121,16 +142,89 @@ const InternalMenuItem = styled(Link)`
|
||||
}
|
||||
`
|
||||
|
||||
const InternalLinkMenuItem = styled(InternalMenuItem)`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.5rem;
|
||||
justify-content: space-between;
|
||||
text-decoration: none;
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.text1};
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
|
||||
const ToggleMenuItem = styled.button`
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.5rem;
|
||||
justify-content: space-between;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.text1};
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
|
||||
const CODE_LINK = 'https://github.com/Uniswap/uniswap-interface'
|
||||
|
||||
function LanguageMenuItem({ locale, active, key }: { locale: SupportedLocale; active: boolean; key: string }) {
|
||||
const { to, onClick } = useLocationLinkProps(locale)
|
||||
|
||||
if (!to) return null
|
||||
|
||||
return (
|
||||
<InternalLinkMenuItem onClick={onClick} key={key} to={to}>
|
||||
<div>{LOCALE_LABEL[locale]}</div>
|
||||
{active && <Check opacity={0.6} size={16} />}
|
||||
</InternalLinkMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
function LanguageMenu({ close }: { close: () => void }) {
|
||||
const activeLocale = useActiveLocale()
|
||||
|
||||
return (
|
||||
<MenuFlyout>
|
||||
<ToggleMenuItem onClick={close}>
|
||||
<ChevronLeft size={16} />
|
||||
</ToggleMenuItem>
|
||||
{SUPPORTED_LOCALES.map((locale) => (
|
||||
<LanguageMenuItem locale={locale} active={activeLocale === locale} key={locale} />
|
||||
))}
|
||||
</MenuFlyout>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Menu() {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const open = useModalOpen(ApplicationModal.MENU)
|
||||
const toggle = useToggleModal(ApplicationModal.MENU)
|
||||
useOnClickOutside(node, open ? toggle : undefined)
|
||||
const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
|
||||
const showUNIClaimOption = Boolean(!!account && !!chainId && !L2_CHAIN_IDS.includes(chainId))
|
||||
const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
|
||||
|
||||
const [darkMode, toggleDarkMode] = useDarkModeManager()
|
||||
|
||||
const [menu, setMenu] = useState<'main' | 'lang'>('main')
|
||||
|
||||
useEffect(() => {
|
||||
setMenu('main')
|
||||
}, [open])
|
||||
|
||||
return (
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451
|
||||
@@ -139,45 +233,70 @@ export default function Menu() {
|
||||
<StyledMenuIcon />
|
||||
</StyledMenuButton>
|
||||
|
||||
{open && (
|
||||
<MenuFlyout>
|
||||
<MenuItem href="https://uniswap.org/">
|
||||
<Info size={14} />
|
||||
<div>
|
||||
<Trans>About</Trans>
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem href="https://docs.uniswap.org/">
|
||||
<BookOpen size={14} />
|
||||
<div>
|
||||
<Trans>Docs</Trans>
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem href={CODE_LINK}>
|
||||
<Code size={14} />
|
||||
<div>
|
||||
<Trans>Code</Trans>
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem href="https://discord.gg/FCfyBSbCU5">
|
||||
<MessageCircle size={14} />
|
||||
<div>
|
||||
<Trans>Discord</Trans>
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem href="https://info.uniswap.org/">
|
||||
<PieChart size={14} />
|
||||
<div>
|
||||
<Trans>Analytics</Trans>
|
||||
</div>
|
||||
</MenuItem>
|
||||
{account && (
|
||||
<UNIbutton onClick={openClaimModal} padding="8px 16px" width="100%" borderRadius="12px" mt="0.5rem">
|
||||
<Trans>Claim UNI</Trans>
|
||||
</UNIbutton>
|
||||
)}
|
||||
</MenuFlyout>
|
||||
)}
|
||||
{open &&
|
||||
(() => {
|
||||
switch (menu) {
|
||||
case 'lang':
|
||||
return <LanguageMenu close={() => setMenu('main')} />
|
||||
case 'main':
|
||||
default:
|
||||
return (
|
||||
<MenuFlyout>
|
||||
<MenuItem href="https://uniswap.org/">
|
||||
<div>
|
||||
<Trans>About</Trans>
|
||||
</div>
|
||||
<Info opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
<MenuItem href="https://docs.uniswap.org/">
|
||||
<div>
|
||||
<Trans>Docs</Trans>
|
||||
</div>
|
||||
<BookOpen opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
<MenuItem href={CODE_LINK}>
|
||||
<div>
|
||||
<Trans>Code</Trans>
|
||||
</div>
|
||||
<Code opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
<MenuItem href="https://discord.gg/FCfyBSbCU5">
|
||||
<div>
|
||||
<Trans>Discord</Trans>
|
||||
</div>
|
||||
<MessageCircle opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
<MenuItem href={infoLink}>
|
||||
<div>
|
||||
<Trans>Analytics</Trans>
|
||||
</div>
|
||||
<PieChart opacity={0.6} size={16} />
|
||||
</MenuItem>
|
||||
<ToggleMenuItem onClick={() => setMenu('lang')}>
|
||||
<div>
|
||||
<Trans>Language</Trans>
|
||||
</div>
|
||||
<ChevronRight size={16} opacity={0.6} />
|
||||
</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} />}
|
||||
</ToggleMenuItem>
|
||||
{showUNIClaimOption && (
|
||||
<UNIbutton
|
||||
onClick={openClaimModal}
|
||||
padding="8px 16px"
|
||||
width="100%"
|
||||
$borderRadius="12px"
|
||||
mt="0.5rem"
|
||||
>
|
||||
<Trans>Claim UNI</Trans>
|
||||
</UNIbutton>
|
||||
)}
|
||||
</MenuFlyout>
|
||||
)
|
||||
}
|
||||
})()}
|
||||
</StyledMenu>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import styled, { css } from 'styled-components'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { animated, useTransition, useSpring } from 'react-spring'
|
||||
import { DialogOverlay, DialogContent } from '@reach/dialog'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
@@ -30,7 +30,7 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
|
||||
)).attrs({
|
||||
'aria-label': 'dialog',
|
||||
})`
|
||||
overflow-y: ${({ mobile }) => (mobile ? 'scroll' : 'hidden')};
|
||||
overflow-y: auto;
|
||||
|
||||
&[data-reach-dialog-content] {
|
||||
margin: 0 0 2rem 0;
|
||||
@@ -39,7 +39,7 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
|
||||
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadow1)};
|
||||
padding: 0px;
|
||||
width: 50vw;
|
||||
overflow-y: ${({ mobile }) => (mobile ? 'scroll' : 'hidden')};
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
align-self: ${({ mobile }) => (mobile ? 'flex-end' : 'center')};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { useContext } from 'react'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
|
||||
|
||||
import { AutoColumn, ColumnCenter } from '../Column'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { RowBetween } from '../Row'
|
||||
import { TYPE, CloseIcon, CustomLightSpinner } from '../../theme'
|
||||
import { ArrowUpCircle } from 'react-feather'
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { darken } from 'polished'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { NavLink, Link as HistoryLink } from 'react-router-dom'
|
||||
import { NavLink, Link as HistoryLink, useLocation } from 'react-router-dom'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import { RowBetween } from '../Row'
|
||||
import Row, { RowBetween } from '../Row'
|
||||
import SettingsTab from '../Settings'
|
||||
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
@@ -14,6 +13,8 @@ import { resetMintState } from 'state/mint/actions'
|
||||
import { resetMintState as resetMintV3State } from 'state/mint/v3/actions'
|
||||
import { TYPE } from 'theme'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { ReactNode } from 'react'
|
||||
import { Box } from 'rebass'
|
||||
|
||||
const Tabs = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
@@ -50,6 +51,15 @@ const StyledNavLink = styled(NavLink).attrs({
|
||||
}
|
||||
`
|
||||
|
||||
const StyledHistoryLink = styled(HistoryLink)<{ flex: string | undefined }>`
|
||||
flex: ${({ flex }) => flex ?? 'none'};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
flex: none;
|
||||
margin-right: 10px;
|
||||
`};
|
||||
`
|
||||
|
||||
const ActiveText = styled.div`
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
@@ -90,24 +100,32 @@ export function FindPoolTabs({ origin }: { origin: string }) {
|
||||
export function AddRemoveTabs({
|
||||
adding,
|
||||
creating,
|
||||
positionID,
|
||||
defaultSlippage,
|
||||
positionID,
|
||||
children,
|
||||
}: {
|
||||
adding: boolean
|
||||
creating: boolean
|
||||
positionID?: string | undefined
|
||||
defaultSlippage: Percent
|
||||
positionID?: string | undefined
|
||||
showBackLink?: boolean
|
||||
children?: ReactNode | undefined
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
|
||||
// reset states on back
|
||||
const dispatch = useAppDispatch()
|
||||
const location = useLocation()
|
||||
|
||||
// detect if back should redirect to v3 or v2 pool page
|
||||
const poolLink = location.pathname.includes('add/v2')
|
||||
? '/pool/v2'
|
||||
: '/pool' + (!!positionID ? `/${positionID.toString()}` : '')
|
||||
|
||||
return (
|
||||
<Tabs>
|
||||
<RowBetween style={{ padding: '1rem 1rem 0 1rem' }}>
|
||||
<HistoryLink
|
||||
to={'/pool' + (!!positionID ? `/${positionID.toString()}` : '')}
|
||||
<StyledHistoryLink
|
||||
to={poolLink}
|
||||
onClick={() => {
|
||||
if (adding) {
|
||||
// not 100% sure both of these are needed
|
||||
@@ -115,10 +133,15 @@ export function AddRemoveTabs({
|
||||
dispatch(resetMintV3State())
|
||||
}
|
||||
}}
|
||||
flex={children ? '1' : undefined}
|
||||
>
|
||||
<StyledArrowLeft stroke={theme.text2} />
|
||||
</HistoryLink>
|
||||
<TYPE.mediumHeader fontWeight={500} fontSize={20}>
|
||||
</StyledHistoryLink>
|
||||
<TYPE.mediumHeader
|
||||
fontWeight={500}
|
||||
fontSize={20}
|
||||
style={{ flex: '1', margin: 'auto', textAlign: children ? 'start' : 'center' }}
|
||||
>
|
||||
{creating ? (
|
||||
<Trans>Create a pair</Trans>
|
||||
) : adding ? (
|
||||
@@ -127,8 +150,22 @@ export function AddRemoveTabs({
|
||||
<Trans>Remove Liquidity</Trans>
|
||||
)}
|
||||
</TYPE.mediumHeader>
|
||||
<Box style={{ marginRight: '.5rem' }}>{children}</Box>
|
||||
<SettingsTab placeholderSlippage={defaultSlippage} />
|
||||
</RowBetween>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
export function CreateProposalTabs() {
|
||||
return (
|
||||
<Tabs>
|
||||
<Row style={{ padding: '1rem 1rem 0 1rem' }}>
|
||||
<HistoryLink to="/vote">
|
||||
<StyledArrowLeft />
|
||||
</HistoryLink>
|
||||
<ActiveText style={{ marginLeft: 'auto', marginRight: 'auto' }}>Create Proposal</ActiveText>
|
||||
</Row>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
128
src/components/NetworkAlert/AddLiquidityNetworkAlert.tsx
Normal file
128
src/components/NetworkAlert/AddLiquidityNetworkAlert.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import {
|
||||
ArbitrumWrapperBackgroundDarkMode,
|
||||
ArbitrumWrapperBackgroundLightMode,
|
||||
OptimismWrapperBackgroundDarkMode,
|
||||
OptimismWrapperBackgroundLightMode,
|
||||
} from 'components/NetworkAlert/NetworkAlert'
|
||||
import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { ArrowDownCircle } from 'react-feather'
|
||||
import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
|
||||
const L2Icon = styled.img`
|
||||
display: none;
|
||||
height: 40px;
|
||||
margin: auto 20px auto 4px;
|
||||
width: 40px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
const DesktopTextBreak = styled.div`
|
||||
display: none;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
const Wrapper = styled.div<{ chainId: SupportedL2ChainId; darkMode: boolean; logoUrl: string }>`
|
||||
${({ chainId, darkMode }) =>
|
||||
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? darkMode
|
||||
? OptimismWrapperBackgroundDarkMode
|
||||
: OptimismWrapperBackgroundLightMode
|
||||
: darkMode
|
||||
? ArbitrumWrapperBackgroundDarkMode
|
||||
: ArbitrumWrapperBackgroundLightMode};
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
:before {
|
||||
background-image: url(${({ logoUrl }) => logoUrl});
|
||||
background-repeat: no-repeat;
|
||||
background-size: 300px;
|
||||
content: '';
|
||||
height: 300px;
|
||||
opacity: 0.1;
|
||||
position: absolute;
|
||||
transform: rotate(25deg) translate(-90px, -40px);
|
||||
width: 300px;
|
||||
z-index: -1;
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
flex-direction: row;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
`
|
||||
const Body = styled.div`
|
||||
font-size: 12px;
|
||||
line-height: 143%;
|
||||
margin: 12px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
flex: 1 1 auto;
|
||||
margin: auto 0;
|
||||
}
|
||||
`
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-left: 12px;
|
||||
`
|
||||
const LinkOutToBridge = styled(ExternalLink)`
|
||||
align-items: center;
|
||||
background-color: black;
|
||||
border-radius: 16px;
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
justify-content: space-between;
|
||||
margin: 0;
|
||||
max-height: 47px;
|
||||
padding: 16px 12px;
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
:hover,
|
||||
:focus,
|
||||
:active {
|
||||
background-color: black;
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
margin: auto 0 auto auto;
|
||||
padding: 14px 16px;
|
||||
min-width: 226px;
|
||||
}
|
||||
`
|
||||
export function AddLiquidityNetworkAlert() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const [darkMode] = useDarkModeManager()
|
||||
const [arbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
|
||||
|
||||
if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged) {
|
||||
return null
|
||||
}
|
||||
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
|
||||
const depositUrl = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? `${info.bridge}?chainId=1`
|
||||
: info.bridge
|
||||
return (
|
||||
<Wrapper darkMode={darkMode} chainId={chainId} logoUrl={info.logoUrl}>
|
||||
<L2Icon src={info.logoUrl} />
|
||||
<Body>
|
||||
<Trans>This is an alpha release of Uniswap on the {info.label} network.</Trans>
|
||||
<DesktopTextBreak /> <Trans>You must bridge L1 assets to the network to use them.</Trans>
|
||||
</Body>
|
||||
<LinkOutToBridge href={depositUrl}>
|
||||
<Trans>Deposit to {info.label}</Trans>
|
||||
<LinkOutCircle />
|
||||
</LinkOutToBridge>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
128
src/components/NetworkAlert/MinimalNetworkAlert.tsx
Normal file
128
src/components/NetworkAlert/MinimalNetworkAlert.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import {
|
||||
ArbitrumWrapperBackgroundDarkMode,
|
||||
ArbitrumWrapperBackgroundLightMode,
|
||||
OptimismWrapperBackgroundDarkMode,
|
||||
OptimismWrapperBackgroundLightMode,
|
||||
} from 'components/NetworkAlert/NetworkAlert'
|
||||
import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { ArrowDownCircle } from 'react-feather'
|
||||
import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
|
||||
const L2Icon = styled.img`
|
||||
display: none;
|
||||
height: 40px;
|
||||
margin: auto 20px auto 4px;
|
||||
width: 40px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
const DesktopTextBreak = styled.div`
|
||||
display: none;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
const Wrapper = styled.div<{ chainId: SupportedL2ChainId; darkMode: boolean; logoUrl: string }>`
|
||||
${({ chainId, darkMode }) =>
|
||||
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? darkMode
|
||||
? OptimismWrapperBackgroundDarkMode
|
||||
: OptimismWrapperBackgroundLightMode
|
||||
: darkMode
|
||||
? ArbitrumWrapperBackgroundDarkMode
|
||||
: ArbitrumWrapperBackgroundLightMode};
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
:before {
|
||||
background-image: url(${({ logoUrl }) => logoUrl});
|
||||
background-repeat: no-repeat;
|
||||
background-size: 300px;
|
||||
content: '';
|
||||
height: 300px;
|
||||
opacity: 0.1;
|
||||
position: absolute;
|
||||
transform: rotate(25deg) translate(-90px, -40px);
|
||||
width: 300px;
|
||||
z-index: -1;
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
flex-direction: row;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
`
|
||||
const Body = styled.div`
|
||||
font-size: 12px;
|
||||
line-height: 143%;
|
||||
margin: 12px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
flex: 1 1 auto;
|
||||
margin: auto 0;
|
||||
}
|
||||
`
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-left: 12px;
|
||||
`
|
||||
const LinkOutToBridge = styled(ExternalLink)`
|
||||
align-items: center;
|
||||
background-color: black;
|
||||
border-radius: 16px;
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
justify-content: space-between;
|
||||
margin: 0;
|
||||
max-height: 47px;
|
||||
padding: 16px 8px;
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
:hover,
|
||||
:focus,
|
||||
:active {
|
||||
background-color: black;
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
margin: auto 0 auto auto;
|
||||
padding: 14px 17px;
|
||||
min-width: 226px;
|
||||
}
|
||||
`
|
||||
export function MinimalNetworkAlert() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const [darkMode] = useDarkModeManager()
|
||||
const [arbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
|
||||
|
||||
if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged) {
|
||||
return null
|
||||
}
|
||||
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
|
||||
const depositUrl = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? `${info.bridge}?chainId=1`
|
||||
: info.bridge
|
||||
return (
|
||||
<Wrapper darkMode={darkMode} chainId={chainId} logoUrl={info.logoUrl}>
|
||||
<L2Icon src={info.logoUrl} />
|
||||
<Body>
|
||||
<Trans>This is an alpha release of Uniswap on the {info.label} network.</Trans>
|
||||
<DesktopTextBreak /> <Trans>You must bridge L1 assets to the network to use them.</Trans>
|
||||
</Body>
|
||||
<LinkOutToBridge href={depositUrl}>
|
||||
<Trans>Deposit to {info.label}</Trans>
|
||||
<LinkOutCircle />
|
||||
</LinkOutToBridge>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
164
src/components/NetworkAlert/NetworkAlert.tsx
Normal file
164
src/components/NetworkAlert/NetworkAlert.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { ArrowDownCircle, X } from 'react-feather'
|
||||
import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
|
||||
import { useETHBalances } from 'state/wallet/hooks'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
import { CHAIN_INFO } from '../../constants/chains'
|
||||
|
||||
const L2Icon = styled.img`
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
justify-self: center;
|
||||
`
|
||||
const CloseIcon = styled(X)`
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
`
|
||||
const ContentWrapper = styled.div`
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-gap: 4px;
|
||||
grid-template-columns: 40px 4fr;
|
||||
grid-template-rows: auto auto;
|
||||
margin: 20px 16px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
grid-template-columns: 42px 4fr;
|
||||
grid-gap: 8px;
|
||||
}
|
||||
`
|
||||
export const ArbitrumWrapperBackgroundDarkMode = css`
|
||||
background: radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),
|
||||
radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.3) 0%, rgba(33, 114, 229, 0.3) 100%), hsla(0, 0%, 100%, 0.1);
|
||||
`
|
||||
export const ArbitrumWrapperBackgroundLightMode = css`
|
||||
background: 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);
|
||||
`
|
||||
export const OptimismWrapperBackgroundDarkMode = css`
|
||||
background: radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.2) 0%, rgba(255, 255, 255, 0.1) 100%),
|
||||
radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.5) 0%, rgba(235, 0, 255, 0.345) 96%);
|
||||
`
|
||||
export const OptimismWrapperBackgroundLightMode = css`
|
||||
background: 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.5);
|
||||
`
|
||||
const RootWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; logoUrl: string }>`
|
||||
${({ chainId, darkMode }) =>
|
||||
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? darkMode
|
||||
? OptimismWrapperBackgroundDarkMode
|
||||
: OptimismWrapperBackgroundLightMode
|
||||
: darkMode
|
||||
? ArbitrumWrapperBackgroundDarkMode
|
||||
: ArbitrumWrapperBackgroundLightMode};
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 480px;
|
||||
min-height: 174px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
:before {
|
||||
background-image: url(${({ logoUrl }) => logoUrl});
|
||||
background-repeat: no-repeat;
|
||||
background-size: 300px;
|
||||
content: '';
|
||||
height: 300px;
|
||||
opacity: 0.1;
|
||||
position: absolute;
|
||||
transform: rotate(25deg) translate(-90px, -40px);
|
||||
width: 300px;
|
||||
z-index: -1;
|
||||
}
|
||||
`
|
||||
const Header = styled.h2`
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
margin: 0;
|
||||
padding-right: 30px;
|
||||
`
|
||||
const Body = styled.p`
|
||||
font-size: 12px;
|
||||
grid-column: 1 / 3;
|
||||
line-height: 143%;
|
||||
margin: 0;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
grid-column: 2 / 3;
|
||||
}
|
||||
`
|
||||
const LinkOutCircle = styled(ArrowDownCircle)`
|
||||
transform: rotate(230deg);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
`
|
||||
const LinkOutToBridge = styled(ExternalLink)`
|
||||
align-items: center;
|
||||
background-color: black;
|
||||
border-radius: 16px;
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
height: 44px;
|
||||
justify-content: space-between;
|
||||
margin: 0 20px 20px 20px;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
width: auto;
|
||||
:hover,
|
||||
:focus,
|
||||
:active {
|
||||
background-color: black;
|
||||
}
|
||||
`
|
||||
export function NetworkAlert() {
|
||||
const { account, chainId } = useActiveWeb3React()
|
||||
const [darkMode] = useDarkModeManager()
|
||||
const [arbitrumAlphaAcknowledged, setArbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
|
||||
const [locallyDismissed, setLocallyDimissed] = useState(false)
|
||||
const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? '']
|
||||
|
||||
const dismiss = useCallback(() => {
|
||||
if (userEthBalance?.greaterThan(0)) {
|
||||
setArbitrumAlphaAcknowledged(true)
|
||||
} else {
|
||||
setLocallyDimissed(true)
|
||||
}
|
||||
}, [setArbitrumAlphaAcknowledged, userEthBalance])
|
||||
if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged || locallyDismissed) {
|
||||
return null
|
||||
}
|
||||
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
|
||||
const depositUrl = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
|
||||
? `${info.bridge}?chainId=1`
|
||||
: info.bridge
|
||||
|
||||
return (
|
||||
<RootWrapper chainId={chainId} darkMode={darkMode} logoUrl={info.logoUrl}>
|
||||
<CloseIcon onClick={dismiss} />
|
||||
<ContentWrapper>
|
||||
<L2Icon src={info.logoUrl} />
|
||||
<Header>
|
||||
<Trans>Uniswap on {info.label}</Trans>
|
||||
</Header>
|
||||
<Body>
|
||||
<Trans>
|
||||
This is an alpha release of Uniswap on the {info.label} network. You must bridge L1 assets to the network to
|
||||
swap them.
|
||||
</Trans>
|
||||
</Body>
|
||||
</ContentWrapper>
|
||||
<LinkOutToBridge href={depositUrl}>
|
||||
<Trans>Deposit to {info.label}</Trans>
|
||||
<LinkOutCircle />
|
||||
</LinkOutToBridge>
|
||||
</RootWrapper>
|
||||
)
|
||||
}
|
||||
63
src/components/OptimismDowntimeWarning/index.tsx
Normal file
63
src/components/OptimismDowntimeWarning/index.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const Root = styled.div`
|
||||
background-color: ${({ theme }) => theme.yellow3};
|
||||
border-radius: 18px;
|
||||
color: black;
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
`
|
||||
const WarningIcon = styled(AlertOctagon)`
|
||||
margin: 0 8px 0 0;
|
||||
`
|
||||
const TitleRow = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 25px;
|
||||
`
|
||||
const Body = styled.div`
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
margin: 8px 0 0 0;
|
||||
`
|
||||
const LinkOutToNotion = styled.a`
|
||||
color: black;
|
||||
`
|
||||
|
||||
export default function OptimismDowntimeWarning() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
if (!chainId || ![SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Root>
|
||||
<TitleRow>
|
||||
<WarningIcon />
|
||||
<Trans>{'Optimism'} Scheduled Downtimes</Trans>
|
||||
</TitleRow>
|
||||
<Body>
|
||||
<Trans>
|
||||
{'Optimism'} expects some scheduled downtime in the near future.
|
||||
<LinkOutToNotion
|
||||
href={`https://www.notion.so/Optimism-Regenesis-Schedule-8d14a34902ca4f5a8910762b3ec4b8da`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read more.
|
||||
</LinkOutToNotion>
|
||||
</Trans>
|
||||
</Body>
|
||||
</Root>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Heart, X } from 'react-feather'
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
import tokenLogo from '../../assets/images/token-logo.png'
|
||||
import { ButtonPrimary } from '../../components/Button'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
@@ -117,7 +117,7 @@ export default function ClaimPopup() {
|
||||
</TYPE.subHeader>
|
||||
</AutoColumn>
|
||||
<AutoColumn style={{ zIndex: 10 }} justify="center">
|
||||
<ButtonPrimary padding="8px" borderRadius="8px" width={'fit-content'} onClick={handleToggleSelfClaimModal}>
|
||||
<ButtonPrimary padding="8px" $borderRadius="8px" width={'fit-content'} onClick={handleToggleSelfClaimModal}>
|
||||
<Trans>Claim your UNI tokens</Trans>
|
||||
</ButtonPrimary>
|
||||
</AutoColumn>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useCallback, useContext, useEffect } from 'react'
|
||||
import { useCallback, useContext, useEffect } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { useSpring } from 'react-spring/web'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { animated } from 'react-spring'
|
||||
import { PopupContent } from '../../state/application/actions'
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
import TransactionPopup from './TransactionPopup'
|
||||
|
||||
export const StyledClose = styled(X)`
|
||||
const StyledClose = styled(X)`
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
@@ -16,7 +16,7 @@ export const StyledClose = styled(X)`
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
export const Popup = styled.div`
|
||||
const Popup = styled.div`
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 1em;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { useContext } from 'react'
|
||||
import { AlertCircle, CheckCircle } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { useActiveWeb3React } from '../../hooks/web3'
|
||||
import { TYPE } from '../../theme'
|
||||
import { ExternalLink } from '../../theme/components'
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useActivePopups } from '../../state/application/hooks'
|
||||
import { AutoColumn } from '../Column'
|
||||
import PopupItem from './PopupItem'
|
||||
import ClaimPopup from './ClaimPopup'
|
||||
import { useURLWarningVisible } from '../../state/user/hooks'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { MEDIA_WIDTHS } from 'theme'
|
||||
|
||||
const MobilePopupWrapper = styled.div<{ height: string | number }>`
|
||||
position: relative;
|
||||
@@ -31,9 +33,13 @@ const MobilePopupInner = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>`
|
||||
const StopOverflowQuery = `@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium + 1}px) and (max-width: ${
|
||||
MEDIA_WIDTHS.upToMedium + 500
|
||||
}px)`
|
||||
|
||||
const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean; xlPadding: boolean }>`
|
||||
position: fixed;
|
||||
top: ${({ extraPadding }) => (extraPadding ? '80px' : '88px')};
|
||||
top: ${({ extraPadding }) => (extraPadding ? '64px' : '56px')};
|
||||
right: 1rem;
|
||||
max-width: 355px !important;
|
||||
width: 100%;
|
||||
@@ -42,6 +48,10 @@ const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>`
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
|
||||
${StopOverflowQuery} {
|
||||
top: ${({ extraPadding, xlPadding }) => (xlPadding ? '64px' : extraPadding ? '64px' : '56px')};
|
||||
}
|
||||
`
|
||||
|
||||
export default function Popups() {
|
||||
@@ -50,9 +60,13 @@ export default function Popups() {
|
||||
|
||||
const urlWarningActive = useURLWarningVisible()
|
||||
|
||||
// need extra padding if network is not L1 Ethereum
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const isNotOnMainnet = Boolean(chainId && chainId !== SupportedChainId.MAINNET)
|
||||
|
||||
return (
|
||||
<>
|
||||
<FixedPopupColumn gap="20px" extraPadding={urlWarningActive}>
|
||||
<FixedPopupColumn gap="20px" extraPadding={urlWarningActive} xlPadding={isNotOnMainnet}>
|
||||
<ClaimPopup />
|
||||
{activePopups.map((item) => (
|
||||
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
@@ -63,7 +62,7 @@ export default function SushiPositionCard({ tokenA, tokenB, liquidityToken, bord
|
||||
<RowFixed gap="8px">
|
||||
<ButtonEmpty
|
||||
padding="0px 35px 0px 0px"
|
||||
borderRadius="12px"
|
||||
$borderRadius="12px"
|
||||
width="fit-content"
|
||||
as={Link}
|
||||
to={`/migrate/v2/${liquidityToken.address}`}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import JSBI from 'jsbi'
|
||||
import React, { useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { Percent, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { ChevronDown, ChevronUp } from 'react-feather'
|
||||
@@ -98,7 +98,7 @@ export default function V2PositionCard({ pair, border, stakedBalance }: Position
|
||||
<RowFixed gap="8px">
|
||||
<ButtonEmpty
|
||||
padding="6px 8px"
|
||||
borderRadius="12px"
|
||||
$borderRadius="12px"
|
||||
width="fit-content"
|
||||
onClick={() => setShowMore(!showMore)}
|
||||
>
|
||||
@@ -188,7 +188,7 @@ export default function V2PositionCard({ pair, border, stakedBalance }: Position
|
||||
<RowBetween marginTop="10px">
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
$borderRadius="8px"
|
||||
as={Link}
|
||||
to={`/migrate/v2/${pair.liquidityToken.address}`}
|
||||
width="64%"
|
||||
@@ -197,7 +197,7 @@ export default function V2PositionCard({ pair, border, stakedBalance }: Position
|
||||
</ButtonPrimary>
|
||||
<ButtonSecondary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
$borderRadius="8px"
|
||||
as={Link}
|
||||
width="32%"
|
||||
to={`/remove/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import JSBI from 'jsbi'
|
||||
import { Percent, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { darken } from 'polished'
|
||||
import React, { useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { ChevronDown, ChevronUp } from 'react-feather'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
@@ -21,7 +20,7 @@ import { CardNoise } from '../earn/styled'
|
||||
|
||||
import { useColor } from '../../hooks/useColor'
|
||||
|
||||
import Card, { GreyCard, LightCard } from '../Card'
|
||||
import { GreyCard, LightCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
@@ -33,12 +32,6 @@ export const FixedHeightRow = styled(RowBetween)`
|
||||
height: 24px;
|
||||
`
|
||||
|
||||
export const HoverCard = styled(Card)`
|
||||
border: 1px solid transparent;
|
||||
:hover {
|
||||
border: 1px solid ${({ theme }) => darken(0.06, theme.bg2)};
|
||||
}
|
||||
`
|
||||
const StyledPositionCard = styled(LightCard)<{ bgColor: any }>`
|
||||
border: none;
|
||||
background: ${({ theme, bgColor }) =>
|
||||
@@ -219,7 +212,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
</Text>
|
||||
</AutoRow>
|
||||
<RowFixed gap="8px" style={{ marginRight: '4px' }}>
|
||||
<ButtonEmpty padding="6px 8px" borderRadius="12px" width="100%" onClick={() => setShowMore(!showMore)}>
|
||||
<ButtonEmpty padding="6px 8px" $borderRadius="12px" width="100%" onClick={() => setShowMore(!showMore)}>
|
||||
{showMore ? (
|
||||
<>
|
||||
<Trans>Manage</Trans>
|
||||
@@ -306,7 +299,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
</Text>
|
||||
</FixedHeightRow>
|
||||
|
||||
<ButtonSecondary padding="8px" borderRadius="8px">
|
||||
<ButtonSecondary padding="8px" $borderRadius="8px">
|
||||
<ExternalLink
|
||||
style={{ width: '100%', textAlign: 'center' }}
|
||||
href={`https://v2.info.uniswap.org/account/${account}`}
|
||||
@@ -320,7 +313,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
<RowBetween marginTop="10px">
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
$borderRadius="8px"
|
||||
as={Link}
|
||||
to={`/migrate/v2/${pair.liquidityToken.address}`}
|
||||
width="32%"
|
||||
@@ -329,7 +322,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
</ButtonPrimary>
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
$borderRadius="8px"
|
||||
as={Link}
|
||||
to={`/add/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
width="32%"
|
||||
@@ -338,7 +331,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
</ButtonPrimary>
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
$borderRadius="8px"
|
||||
as={Link}
|
||||
width="32%"
|
||||
to={`/remove/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
@@ -350,7 +343,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
{stakedBalance && JSBI.greaterThan(stakedBalance.quotient, BIG_INT_ZERO) && (
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
$borderRadius="8px"
|
||||
as={Link}
|
||||
to={`/uni/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
width="100%"
|
||||
|
||||
@@ -34,7 +34,7 @@ const MobileHeader = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
export type PositionListProps = React.PropsWithChildren<{
|
||||
type PositionListProps = React.PropsWithChildren<{
|
||||
positions: PositionDetails[]
|
||||
}>
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function PositionList({ positions }: PositionListProps) {
|
||||
{positions && ' (' + positions.length + ')'}
|
||||
</div>
|
||||
<div>
|
||||
<Trans>Price range</Trans>
|
||||
<Trans>Status</Trans>
|
||||
</div>
|
||||
</DesktopHeader>
|
||||
<MobileHeader>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { Position } from '@uniswap/v3-sdk'
|
||||
import Badge from 'components/Badge'
|
||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
@@ -9,14 +9,16 @@ import styled from 'styled-components/macro'
|
||||
import { HideSmall, MEDIA_WIDTHS, SmallOnly } from 'theme'
|
||||
import { PositionDetails } from 'types/position'
|
||||
import { Price, Token, Percent } from '@uniswap/sdk-core'
|
||||
import { formatPrice } from 'utils/formatCurrencyAmount'
|
||||
import { formatTickPrice } from 'utils/formatTickPrice'
|
||||
import Loader from 'components/Loader'
|
||||
import { unwrappedToken } from 'utils/unwrappedToken'
|
||||
import RangeBadge from 'components/Badge/RangeBadge'
|
||||
import { RowFixed } from 'components/Row'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import HoverInlineText from 'components/HoverInlineText'
|
||||
import { DAI, USDC, USDT, WBTC, WETH9_EXTENDED } from '../../constants/tokens'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import useIsTickAtLimit from 'hooks/useIsTickAtLimit'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
|
||||
const LinkRow = styled(Link)`
|
||||
align-items: center;
|
||||
@@ -24,6 +26,9 @@ const LinkRow = styled(Link)`
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
justify-content: space-between;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
margin: 8px 0;
|
||||
@@ -32,25 +37,23 @@ const LinkRow = styled(Link)`
|
||||
font-weight: 500;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
|
||||
&:first-of-type {
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
&:last-of-type {
|
||||
margin: 8px 0 0 0;
|
||||
}
|
||||
& > div:not(:first-child) {
|
||||
text-align: right;
|
||||
text-align: center;
|
||||
}
|
||||
:hover {
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
}
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
flex-direction: row;
|
||||
/* flex-direction: row; */
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
flex-direction: column;
|
||||
row-gap: 24px;
|
||||
row-gap: 12px;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -70,11 +73,14 @@ const RangeLineItem = styled(DataLineItem)`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-self: flex-end;
|
||||
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
flex-direction: column;
|
||||
row-gap: 4px;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
border-radius: 12px;
|
||||
padding: 8px 0;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -97,6 +103,9 @@ const ExtentsText = styled.span`
|
||||
color: ${({ theme }) => theme.text3};
|
||||
font-size: 14px;
|
||||
margin-right: 4px;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
const PrimaryPositionIdData = styled.div`
|
||||
@@ -117,7 +126,7 @@ const DataText = styled.div`
|
||||
`};
|
||||
`
|
||||
|
||||
export interface PositionListItemProps {
|
||||
interface PositionListItemProps {
|
||||
positionDetails: PositionDetails
|
||||
}
|
||||
|
||||
@@ -201,6 +210,8 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
|
||||
return undefined
|
||||
}, [liquidity, pool, tickLower, tickUpper])
|
||||
|
||||
const tickAtLimit = useIsTickAtLimit(feeAmount, tickLower, tickUpper)
|
||||
|
||||
// prices
|
||||
const { priceLower, priceUpper, quote, base } = getPriceOrderingFromPositionForUI(position)
|
||||
|
||||
@@ -216,7 +227,7 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
|
||||
|
||||
return (
|
||||
<LinkRow to={positionSummaryLink}>
|
||||
<RowFixed>
|
||||
<RowBetween>
|
||||
<PrimaryPositionIdData>
|
||||
<DoubleCurrencyLogo currency0={currencyBase} currency1={currencyQuote} size={18} margin />
|
||||
<DataText>
|
||||
@@ -230,7 +241,7 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
|
||||
</Badge>
|
||||
</PrimaryPositionIdData>
|
||||
<RangeBadge removed={removed} inRange={!outOfRange} />
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
|
||||
{priceLower && priceUpper ? (
|
||||
<RangeLineItem>
|
||||
@@ -239,23 +250,23 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
|
||||
<Trans>Min: </Trans>
|
||||
</ExtentsText>
|
||||
<Trans>
|
||||
{formatPrice(priceLower, 5)} <HoverInlineText text={currencyQuote?.symbol} /> per{' '}
|
||||
<HoverInlineText text={currencyBase?.symbol ?? ''} />
|
||||
{formatTickPrice(priceLower, tickAtLimit, Bound.LOWER)} <HoverInlineText text={currencyQuote?.symbol} />{' '}
|
||||
per <HoverInlineText text={currencyBase?.symbol ?? ''} />
|
||||
</Trans>
|
||||
</RangeText>{' '}
|
||||
<HideSmall>
|
||||
<DoubleArrow>⟷</DoubleArrow>{' '}
|
||||
</HideSmall>
|
||||
<SmallOnly>
|
||||
<DoubleArrow>↕</DoubleArrow>{' '}
|
||||
<DoubleArrow>⟷</DoubleArrow>{' '}
|
||||
</SmallOnly>
|
||||
<RangeText>
|
||||
<ExtentsText>
|
||||
<Trans>Max:</Trans>
|
||||
</ExtentsText>
|
||||
<Trans>
|
||||
{formatPrice(priceUpper, 5)} <HoverInlineText text={currencyQuote?.symbol} /> per{' '}
|
||||
<HoverInlineText maxCharacters={10} text={currencyBase?.symbol} />
|
||||
{formatTickPrice(priceUpper, tickAtLimit, Bound.UPPER)} <HoverInlineText text={currencyQuote?.symbol} />{' '}
|
||||
per <HoverInlineText maxCharacters={10} text={currencyBase?.symbol} />
|
||||
</Trans>
|
||||
</RangeText>
|
||||
</RangeLineItem>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useCallback, useContext, ReactNode } from 'react'
|
||||
import { useState, useCallback, useContext, ReactNode } from 'react'
|
||||
import { Position } from '@uniswap/v3-sdk'
|
||||
import { LightCard } from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
@@ -12,19 +12,23 @@ import { Currency } from '@uniswap/sdk-core'
|
||||
import RateToggle from 'components/RateToggle'
|
||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
import RangeBadge from 'components/Badge/RangeBadge'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { ThemeContext } from 'styled-components/macro'
|
||||
import JSBI from 'jsbi'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
import { formatTickPrice } from 'utils/formatTickPrice'
|
||||
|
||||
export const PositionPreview = ({
|
||||
position,
|
||||
title,
|
||||
inRange,
|
||||
baseCurrencyDefault,
|
||||
ticksAtLimit,
|
||||
}: {
|
||||
position: Position
|
||||
title?: ReactNode
|
||||
inRange: boolean
|
||||
baseCurrencyDefault?: Currency | undefined
|
||||
ticksAtLimit: { [bound: string]: boolean | undefined }
|
||||
}) => {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
@@ -121,7 +125,11 @@ export const PositionPreview = ({
|
||||
<TYPE.main fontSize="12px">
|
||||
<Trans>Min Price</Trans>
|
||||
</TYPE.main>
|
||||
<TYPE.mediumHeader textAlign="center">{`${priceLower.toSignificant(5)}`}</TYPE.mediumHeader>
|
||||
<TYPE.mediumHeader textAlign="center">{`${formatTickPrice(
|
||||
priceLower,
|
||||
ticksAtLimit,
|
||||
Bound.LOWER
|
||||
)}`}</TYPE.mediumHeader>
|
||||
<TYPE.main textAlign="center" fontSize="12px">
|
||||
<Trans>
|
||||
{quoteCurrency.symbol} per {baseCurrency.symbol}
|
||||
@@ -138,7 +146,11 @@ export const PositionPreview = ({
|
||||
<TYPE.main fontSize="12px">
|
||||
<Trans>Max Price</Trans>
|
||||
</TYPE.main>
|
||||
<TYPE.mediumHeader textAlign="center">{`${priceUpper.toSignificant(5)}`}</TYPE.mediumHeader>
|
||||
<TYPE.mediumHeader textAlign="center">{`${formatTickPrice(
|
||||
priceUpper,
|
||||
ticksAtLimit,
|
||||
Bound.UPPER
|
||||
)}`}</TYPE.mediumHeader>
|
||||
<TYPE.main textAlign="center" fontSize="12px">
|
||||
<Trans>
|
||||
{quoteCurrency.symbol} per {baseCurrency.symbol}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { useContext } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { ThemeContext } from 'styled-components/macro'
|
||||
import { TYPE } from '../../theme'
|
||||
|
||||
const Wrapper = styled(AutoColumn)`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { ReactNode, useCallback, useState } from 'react'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import Tooltip from '../Tooltip'
|
||||
|
||||
|
||||
84
src/components/RangeSelector/PresetsButtons.tsx
Normal file
84
src/components/RangeSelector/PresetsButtons.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import React from 'react'
|
||||
import { ButtonOutlined } from 'components/Button'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { TYPE } from 'theme'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import ReactGA from 'react-ga'
|
||||
|
||||
const Button = styled(ButtonOutlined).attrs(() => ({
|
||||
padding: '4px',
|
||||
borderRadius: '8px',
|
||||
}))`
|
||||
color: ${({ theme }) => theme.text1};
|
||||
flex: 1;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
`
|
||||
|
||||
const RANGES = {
|
||||
[FeeAmount.LOW]: [
|
||||
{ label: '0.05', ticks: 5 },
|
||||
{ label: '0.1', ticks: 10 },
|
||||
{ label: '0.2', ticks: 20 },
|
||||
],
|
||||
[FeeAmount.MEDIUM]: [
|
||||
{ label: '1', ticks: 100 },
|
||||
{ label: '10', ticks: 953 },
|
||||
{ label: '50', ticks: 4055 },
|
||||
],
|
||||
[FeeAmount.HIGH]: [
|
||||
{ label: '2', ticks: 198 },
|
||||
{ label: '10', ticks: 953 },
|
||||
{ label: '80', ticks: 5878 },
|
||||
],
|
||||
}
|
||||
|
||||
interface PresetsButtonProps {
|
||||
feeAmount: FeeAmount | undefined
|
||||
setRange: (numTicks: number) => void
|
||||
setFullRange: () => void
|
||||
}
|
||||
|
||||
const PresetButton = ({
|
||||
values: { label, ticks },
|
||||
setRange,
|
||||
}: {
|
||||
values: {
|
||||
label: string
|
||||
ticks: number
|
||||
}
|
||||
setRange: (numTicks: number) => void
|
||||
}) => (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setRange(ticks)
|
||||
ReactGA.event({
|
||||
category: 'Liquidity',
|
||||
action: 'Preset clicked',
|
||||
label: label,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<TYPE.body fontSize={12}>
|
||||
<Trans>+/- {label}%</Trans>
|
||||
</TYPE.body>
|
||||
</Button>
|
||||
)
|
||||
|
||||
export default function PresetsButtons({ feeAmount, setRange, setFullRange }: PresetsButtonProps) {
|
||||
feeAmount = feeAmount ?? FeeAmount.LOW
|
||||
|
||||
return (
|
||||
<AutoRow gap="4px" width="auto">
|
||||
<PresetButton values={RANGES[feeAmount][0]} setRange={setRange} />
|
||||
<PresetButton values={RANGES[feeAmount][1]} setRange={setRange} />
|
||||
<PresetButton values={RANGES[feeAmount][2]} setRange={setRange} />
|
||||
<Button onClick={() => setFullRange()}>
|
||||
<TYPE.body fontSize={12}>
|
||||
<Trans>Full Range</Trans>
|
||||
</TYPE.body>
|
||||
</Button>
|
||||
</AutoRow>
|
||||
)
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import React from 'react'
|
||||
import { Currency, Price, Token } from '@uniswap/sdk-core'
|
||||
import StepCounter from 'components/InputStepCounter/InputStepCounter'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
|
||||
// currencyA is the base token
|
||||
export default function RangeSelector({
|
||||
@@ -17,6 +18,7 @@ export default function RangeSelector({
|
||||
currencyA,
|
||||
currencyB,
|
||||
feeAmount,
|
||||
ticksAtLimit,
|
||||
}: {
|
||||
priceLower?: Price<Token, Token>
|
||||
priceUpper?: Price<Token, Token>
|
||||
@@ -29,6 +31,7 @@ export default function RangeSelector({
|
||||
currencyA?: Currency | null
|
||||
currencyB?: Currency | null
|
||||
feeAmount?: number
|
||||
ticksAtLimit: { [bound in Bound]?: boolean | undefined }
|
||||
}) {
|
||||
const tokenA = (currencyA ?? undefined)?.wrapped
|
||||
const tokenB = (currencyB ?? undefined)?.wrapped
|
||||
@@ -38,31 +41,37 @@ export default function RangeSelector({
|
||||
const rightPrice = isSorted ? priceUpper : priceLower?.invert()
|
||||
|
||||
return (
|
||||
<RowBetween>
|
||||
<StepCounter
|
||||
value={leftPrice?.toSignificant(5) ?? ''}
|
||||
onUserInput={onLeftRangeInput}
|
||||
width="48%"
|
||||
decrement={isSorted ? getDecrementLower : getIncrementUpper}
|
||||
increment={isSorted ? getIncrementLower : getDecrementUpper}
|
||||
feeAmount={feeAmount}
|
||||
label={leftPrice ? `${currencyB?.symbol}` : '-'}
|
||||
title={<Trans>Min Price</Trans>}
|
||||
tokenA={currencyA?.symbol}
|
||||
tokenB={currencyB?.symbol}
|
||||
/>
|
||||
<StepCounter
|
||||
value={rightPrice?.toSignificant(5) ?? ''}
|
||||
onUserInput={onRightRangeInput}
|
||||
width="48%"
|
||||
decrement={isSorted ? getDecrementUpper : getIncrementLower}
|
||||
increment={isSorted ? getIncrementUpper : getDecrementLower}
|
||||
feeAmount={feeAmount}
|
||||
label={rightPrice ? `${currencyB?.symbol}` : '-'}
|
||||
tokenA={currencyA?.symbol}
|
||||
tokenB={currencyB?.symbol}
|
||||
title={<Trans>Max Price</Trans>}
|
||||
/>
|
||||
</RowBetween>
|
||||
<AutoColumn gap="md">
|
||||
<RowBetween>
|
||||
<StepCounter
|
||||
value={ticksAtLimit[Bound.LOWER] ? '0' : leftPrice?.toSignificant(5) ?? ''}
|
||||
onUserInput={onLeftRangeInput}
|
||||
width="48%"
|
||||
decrement={isSorted ? getDecrementLower : getIncrementUpper}
|
||||
increment={isSorted ? getIncrementLower : getDecrementUpper}
|
||||
decrementDisabled={ticksAtLimit[Bound.LOWER]}
|
||||
incrementDisabled={ticksAtLimit[Bound.LOWER]}
|
||||
feeAmount={feeAmount}
|
||||
label={leftPrice ? `${currencyB?.symbol}` : '-'}
|
||||
title={<Trans>Min Price</Trans>}
|
||||
tokenA={currencyA?.symbol}
|
||||
tokenB={currencyB?.symbol}
|
||||
/>
|
||||
<StepCounter
|
||||
value={ticksAtLimit[Bound.UPPER] ? '∞' : rightPrice?.toSignificant(5) ?? ''}
|
||||
onUserInput={onRightRangeInput}
|
||||
width="48%"
|
||||
decrement={isSorted ? getDecrementUpper : getIncrementLower}
|
||||
increment={isSorted ? getIncrementUpper : getDecrementLower}
|
||||
incrementDisabled={ticksAtLimit[Bound.UPPER]}
|
||||
decrementDisabled={ticksAtLimit[Bound.UPPER]}
|
||||
feeAmount={feeAmount}
|
||||
label={rightPrice ? `${currencyB?.symbol}` : '-'}
|
||||
tokenA={currencyA?.symbol}
|
||||
tokenB={currencyB?.symbol}
|
||||
title={<Trans>Max Price</Trans>}
|
||||
/>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import React from 'react'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { ToggleElement, ToggleWrapper } from 'components/Toggle/MultiToggle'
|
||||
|
||||
@@ -23,10 +22,10 @@ export default function RateToggle({
|
||||
<div style={{ width: 'fit-content', display: 'flex', alignItems: 'center' }} onClick={handleRateToggle}>
|
||||
<ToggleWrapper width="fit-content">
|
||||
<ToggleElement isActive={isSorted} fontSize="12px">
|
||||
<Trans>{isSorted ? currencyA.symbol : currencyB.symbol} price</Trans>
|
||||
<Trans>{isSorted ? currencyA.symbol : currencyB.symbol}</Trans>
|
||||
</ToggleElement>
|
||||
<ToggleElement isActive={!isSorted} fontSize="12px">
|
||||
<Trans>{isSorted ? currencyB.symbol : currencyA.symbol} price</Trans>
|
||||
<Trans>{isSorted ? currencyB.symbol : currencyA.symbol}</Trans>
|
||||
</ToggleElement>
|
||||
</ToggleWrapper>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import React from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -11,6 +10,12 @@ import QuestionHelper from '../QuestionHelper'
|
||||
import { AutoRow } from '../Row'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
|
||||
const MobileWrapper = styled(AutoColumn)`
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
const BaseWrapper = styled.div<{ disable?: boolean }>`
|
||||
border: 1px solid ${({ theme, disable }) => (disable ? 'transparent' : theme.bg3)};
|
||||
border-radius: 10px;
|
||||
@@ -23,8 +28,9 @@ const BaseWrapper = styled.div<{ disable?: boolean }>`
|
||||
background-color: ${({ theme, disable }) => !disable && theme.bg2};
|
||||
}
|
||||
|
||||
color: ${({ theme, disable }) => disable && theme.text3};
|
||||
background-color: ${({ theme, disable }) => disable && theme.bg3};
|
||||
opacity: ${({ disable }) => disable && '0.4'};
|
||||
filter: ${({ disable }) => disable && 'grayscale(1)'};
|
||||
`
|
||||
|
||||
export default function CommonBases({
|
||||
@@ -39,7 +45,7 @@ export default function CommonBases({
|
||||
const bases = typeof chainId !== 'undefined' ? COMMON_BASES[chainId] ?? [] : []
|
||||
|
||||
return bases.length > 0 ? (
|
||||
<AutoColumn gap="md">
|
||||
<MobileWrapper gap="md">
|
||||
<AutoRow>
|
||||
<Text fontWeight={500} fontSize={14}>
|
||||
<Trans>Common bases</Trans>
|
||||
@@ -63,6 +69,6 @@ export default function CommonBases({
|
||||
)
|
||||
})}
|
||||
</AutoRow>
|
||||
</AutoColumn>
|
||||
</MobileWrapper>
|
||||
) : null
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import React, { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'
|
||||
import { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -104,12 +104,14 @@ function CurrencyRow({
|
||||
isSelected,
|
||||
otherSelected,
|
||||
style,
|
||||
showCurrencyAmount,
|
||||
}: {
|
||||
currency: Currency
|
||||
onSelect: () => void
|
||||
isSelected: boolean
|
||||
otherSelected: boolean
|
||||
style: CSSProperties
|
||||
showCurrencyAmount?: boolean
|
||||
}) {
|
||||
const { account } = useActiveWeb3React()
|
||||
const key = currencyKey(currency)
|
||||
@@ -141,9 +143,11 @@ function CurrencyRow({
|
||||
</TYPE.darkGray>
|
||||
</Column>
|
||||
<TokenTags currency={currency} />
|
||||
<RowFixed style={{ justifySelf: 'flex-end' }}>
|
||||
{balance ? <Balance balance={balance} /> : account ? <Loader /> : null}
|
||||
</RowFixed>
|
||||
{showCurrencyAmount && (
|
||||
<RowFixed style={{ justifySelf: 'flex-end' }}>
|
||||
{balance ? <Balance balance={balance} /> : account ? <Loader /> : null}
|
||||
</RowFixed>
|
||||
)}
|
||||
</MenuItem>
|
||||
)
|
||||
}
|
||||
@@ -158,7 +162,7 @@ function BreakLineComponent({ style }: { style: CSSProperties }) {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<FixedContentRow style={style}>
|
||||
<LightGreyCard padding="8px 12px" borderRadius="8px">
|
||||
<LightGreyCard padding="8px 12px" $borderRadius="8px">
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<TokenListLogoWrapper src={TokenListLogo} />
|
||||
@@ -189,6 +193,7 @@ export default function CurrencyList({
|
||||
fixedListRef,
|
||||
showImportView,
|
||||
setImportToken,
|
||||
showCurrencyAmount,
|
||||
}: {
|
||||
height: number
|
||||
currencies: Currency[]
|
||||
@@ -199,6 +204,7 @@ export default function CurrencyList({
|
||||
fixedListRef?: MutableRefObject<FixedSizeList | undefined>
|
||||
showImportView: () => void
|
||||
setImportToken: (token: Token) => void
|
||||
showCurrencyAmount?: boolean
|
||||
}) {
|
||||
const itemData: (Currency | BreakLine)[] = useMemo(() => {
|
||||
if (otherListTokens && otherListTokens?.length > 0) {
|
||||
@@ -237,13 +243,22 @@ export default function CurrencyList({
|
||||
isSelected={isSelected}
|
||||
onSelect={handleSelect}
|
||||
otherSelected={otherSelected}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
[currencies.length, onCurrencySelect, otherCurrency, selectedCurrency, setImportToken, showImportView]
|
||||
[
|
||||
currencies.length,
|
||||
onCurrencySelect,
|
||||
otherCurrency,
|
||||
selectedCurrency,
|
||||
setImportToken,
|
||||
showImportView,
|
||||
showCurrencyAmount,
|
||||
]
|
||||
)
|
||||
|
||||
const itemKey = useCallback((index: number, data: typeof itemData) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import React, { KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
@@ -48,6 +48,8 @@ interface CurrencySearchProps {
|
||||
onCurrencySelect: (currency: Currency) => void
|
||||
otherSelectedCurrency?: Currency | null
|
||||
showCommonBases?: boolean
|
||||
showCurrencyAmount?: boolean
|
||||
disableNonToken?: boolean
|
||||
showManageView: () => void
|
||||
showImportView: () => void
|
||||
setImportToken: (token: Token) => void
|
||||
@@ -58,6 +60,8 @@ export function CurrencySearch({
|
||||
onCurrencySelect,
|
||||
otherSelectedCurrency,
|
||||
showCommonBases,
|
||||
showCurrencyAmount,
|
||||
disableNonToken,
|
||||
onDismiss,
|
||||
isOpen,
|
||||
showManageView,
|
||||
@@ -203,7 +207,7 @@ export function CurrencySearch({
|
||||
{({ height }) => (
|
||||
<CurrencyList
|
||||
height={height}
|
||||
currencies={filteredSortedTokensWithETH}
|
||||
currencies={disableNonToken ? filteredSortedTokens : filteredSortedTokensWithETH}
|
||||
otherListTokens={filteredInactiveTokens}
|
||||
onCurrencySelect={handleCurrencySelect}
|
||||
otherCurrency={otherSelectedCurrency}
|
||||
@@ -211,6 +215,7 @@ export function CurrencySearch({
|
||||
fixedListRef={fixedList}
|
||||
showImportView={showImportView}
|
||||
setImportToken={setImportToken}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
@@ -224,12 +229,12 @@ export function CurrencySearch({
|
||||
)}
|
||||
<Footer>
|
||||
<Row justify="center">
|
||||
<ButtonText onClick={showManageView} color={theme.blue1} className="list-token-manage-button">
|
||||
<ButtonText onClick={showManageView} color={theme.primary1} className="list-token-manage-button">
|
||||
<RowFixed>
|
||||
<IconWrapper size="16px" marginRight="6px">
|
||||
<IconWrapper size="16px" marginRight="6px" stroke={theme.primaryText1}>
|
||||
<Edit />
|
||||
</IconWrapper>
|
||||
<TYPE.main color={theme.blue1}>
|
||||
<TYPE.main color={theme.primaryText1}>
|
||||
<Trans>Manage Token Lists</Trans>
|
||||
</TYPE.main>
|
||||
</RowFixed>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import useLast from '../../hooks/useLast'
|
||||
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
|
||||
import Modal from '../Modal'
|
||||
@@ -17,6 +17,8 @@ interface CurrencySearchModalProps {
|
||||
onCurrencySelect: (currency: Currency) => void
|
||||
otherSelectedCurrency?: Currency | null
|
||||
showCommonBases?: boolean
|
||||
showCurrencyAmount?: boolean
|
||||
disableNonToken?: boolean
|
||||
}
|
||||
|
||||
export enum CurrencyModalView {
|
||||
@@ -33,6 +35,8 @@ export default function CurrencySearchModal({
|
||||
selectedCurrency,
|
||||
otherSelectedCurrency,
|
||||
showCommonBases = false,
|
||||
showCurrencyAmount = true,
|
||||
disableNonToken = false,
|
||||
}: CurrencySearchModalProps) {
|
||||
const [modalView, setModalView] = useState<CurrencyModalView>(CurrencyModalView.manage)
|
||||
const lastOpen = useLast(isOpen)
|
||||
@@ -74,6 +78,8 @@ export default function CurrencySearchModal({
|
||||
selectedCurrency={selectedCurrency}
|
||||
otherSelectedCurrency={otherSelectedCurrency}
|
||||
showCommonBases={showCommonBases}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
disableNonToken={disableNonToken}
|
||||
showImportView={() => setModalView(CurrencyModalView.importToken)}
|
||||
setImportToken={setImportToken}
|
||||
showManageView={() => setModalView(CurrencyModalView.manage)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import { useState, useCallback } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import ReactGA from 'react-ga'
|
||||
import { TYPE, CloseIcon } from 'theme'
|
||||
@@ -149,7 +149,7 @@ export function ImportList({ listURL, list, setModalView, onDismiss }: ImportPro
|
||||
<ButtonPrimary
|
||||
disabled={!confirmed}
|
||||
altDisabledStyle={true}
|
||||
borderRadius="20px"
|
||||
$borderRadius="20px"
|
||||
padding="10px 1rem"
|
||||
onClick={handleAddList}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { CSSProperties } from 'react'
|
||||
import { CSSProperties } from 'react'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { AutoRow, RowFixed } from 'components/Row'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { TokenList } from '@uniswap/token-lists/dist/types'
|
||||
import React from 'react'
|
||||
import { Token, Currency } from '@uniswap/sdk-core'
|
||||
import styled from 'styled-components/macro'
|
||||
import { TYPE, CloseIcon } from 'theme'
|
||||
@@ -34,10 +33,11 @@ const WarningWrapper = styled(Card)<{ highWarning: boolean }>`
|
||||
|
||||
const AddressText = styled(TYPE.blue)`
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
font-size: 10px;
|
||||
`}
|
||||
`}
|
||||
`
|
||||
|
||||
interface ImportProps {
|
||||
@@ -109,7 +109,7 @@ export function ImportToken({ tokens, list, onBack, onDismiss, handleCurrencySel
|
||||
</TYPE.small>
|
||||
</RowFixed>
|
||||
) : (
|
||||
<WarningWrapper borderRadius="4px" padding="4px" highWarning={true}>
|
||||
<WarningWrapper $borderRadius="4px" padding="4px" highWarning={true}>
|
||||
<RowFixed>
|
||||
<AlertCircle stroke={theme.red1} size="10px" />
|
||||
<TYPE.body color={theme.red1} ml="4px" fontSize="10px" fontWeight={500}>
|
||||
@@ -125,7 +125,7 @@ export function ImportToken({ tokens, list, onBack, onDismiss, handleCurrencySel
|
||||
|
||||
<ButtonPrimary
|
||||
altDisabledStyle={true}
|
||||
borderRadius="20px"
|
||||
$borderRadius="20px"
|
||||
padding="10px 1rem"
|
||||
onClick={() => {
|
||||
tokens.map((token) => addToken(token))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import React, { useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { PaddedColumn, Separator } from './styleds'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
|
||||
@@ -1,36 +1,34 @@
|
||||
import React, { memo, useCallback, useMemo, useRef, useState, useEffect } from 'react'
|
||||
import { Settings, CheckCircle } from 'react-feather'
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import Card from 'components/Card'
|
||||
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
|
||||
import { useListColor } from 'hooks/useColor'
|
||||
import { useActiveWeb3React } from 'hooks/web3'
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { CheckCircle, Settings } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import { usePopper } from 'react-popper'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useFetchListCallback } from '../../hooks/useFetchListCallback'
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
import { TokenList } from '@uniswap/token-lists'
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import useToggle from '../../hooks/useToggle'
|
||||
import { acceptListUpdate, removeList, disableList, enableList } from '../../state/lists/actions'
|
||||
import { useIsListActive, useAllLists, useActiveListUrls } from '../../state/lists/hooks'
|
||||
import { ExternalLink, LinkStyledButton, TYPE, IconWrapper } from '../../theme'
|
||||
import { acceptListUpdate, disableList, enableList, removeList } from '../../state/lists/actions'
|
||||
import { useActiveListUrls, useAllLists, useIsListActive } from '../../state/lists/hooks'
|
||||
import { ExternalLink, IconWrapper, LinkStyledButton, TYPE } from '../../theme'
|
||||
import listVersionLabel from '../../utils/listVersionLabel'
|
||||
import { parseENSAddress } from '../../utils/parseENSAddress'
|
||||
import uriToHttp from '../../utils/uriToHttp'
|
||||
import { ButtonEmpty, ButtonPrimary } from '../Button'
|
||||
|
||||
import Column, { AutoColumn } from '../Column'
|
||||
import ListLogo from '../ListLogo'
|
||||
import Row, { RowFixed, RowBetween } from '../Row'
|
||||
import { PaddedColumn, SearchInput, Separator, SeparatorDark } from './styleds'
|
||||
import { useListColor } from 'hooks/useColor'
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import Row, { RowBetween, RowFixed } from '../Row'
|
||||
import ListToggle from '../Toggle/ListToggle'
|
||||
import Card from 'components/Card'
|
||||
import { CurrencyModalView } from './CurrencySearchModal'
|
||||
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
|
||||
import { PaddedColumn, SearchInput, Separator, SeparatorDark } from './styleds'
|
||||
|
||||
const Wrapper = styled(Column)`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
@@ -80,8 +78,9 @@ const StyledListUrlText = styled(TYPE.main)<{ active: boolean }>`
|
||||
color: ${({ theme, active }) => (active ? theme.white : theme.text2)};
|
||||
`
|
||||
|
||||
const RowWrapper = styled(Row)<{ bgColor: string; active: boolean }>`
|
||||
const RowWrapper = styled(Row)<{ bgColor: string; active: boolean; hasActiveTokens: boolean }>`
|
||||
background-color: ${({ bgColor, active, theme }) => (active ? bgColor ?? 'transparent' : theme.bg2)};
|
||||
opacity: ${({ hasActiveTokens }) => (hasActiveTokens ? 1 : 0.4)};
|
||||
transition: 200ms;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
@@ -93,10 +92,18 @@ function listUrlRowHTMLId(listUrl: string) {
|
||||
}
|
||||
|
||||
const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
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)
|
||||
@@ -130,7 +137,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
action: 'Start Remove List',
|
||||
label: listUrl,
|
||||
})
|
||||
if (window.prompt(`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) {
|
||||
if (window.prompt(t`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) {
|
||||
ReactGA.event({
|
||||
category: 'Lists',
|
||||
action: 'Confirm Remove List',
|
||||
@@ -161,7 +168,13 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
if (!list) return null
|
||||
|
||||
return (
|
||||
<RowWrapper active={isActive} bgColor={listColor} key={listUrl} id={listUrlRowHTMLId(listUrl)}>
|
||||
<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`} />
|
||||
) : (
|
||||
@@ -173,7 +186,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
|
||||
</Row>
|
||||
<RowFixed mt="4px">
|
||||
<StyledListUrlText active={isActive} mr="6px">
|
||||
<Trans>{list.tokens.length} tokens</Trans>
|
||||
<Trans>{activeTokensOnThisChain} tokens</Trans>
|
||||
</StyledListUrlText>
|
||||
<StyledMenu ref={node as any}>
|
||||
<ButtonEmpty onClick={toggle} ref={setReferenceElement} padding="0">
|
||||
@@ -226,20 +239,29 @@ export function ManageLists({
|
||||
setImportList: (list: TokenList) => void
|
||||
setListUrl: (url: string) => void
|
||||
}) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
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 [activeCopy, setActiveCopy] = useState<string[] | undefined>()
|
||||
useEffect(() => {
|
||||
if (!activeCopy && activeListUrls) {
|
||||
setActiveCopy(activeListUrls)
|
||||
}
|
||||
}, [activeCopy, activeListUrls])
|
||||
|
||||
const handleInput = useCallback((e) => {
|
||||
setListUrlInput(e.target.value)
|
||||
@@ -258,30 +280,36 @@ export function ManageLists({
|
||||
// only show loaded lists, hide unsupported lists
|
||||
return Boolean(lists[listUrl].current) && !Boolean(UNSUPPORTED_LIST_URLS.includes(listUrl))
|
||||
})
|
||||
.sort((u1, u2) => {
|
||||
const { current: l1 } = lists[u1]
|
||||
const { current: l2 } = lists[u2]
|
||||
.sort((listUrlA, listUrlB) => {
|
||||
const { current: listA } = lists[listUrlA]
|
||||
const { current: listB } = lists[listUrlB]
|
||||
|
||||
// first filter on active lists
|
||||
if (activeCopy?.includes(u1) && !activeCopy?.includes(u2)) {
|
||||
if (activeListUrls?.includes(listUrlA) && !activeListUrls?.includes(listUrlB)) {
|
||||
return -1
|
||||
}
|
||||
if (!activeCopy?.includes(u1) && activeCopy?.includes(u2)) {
|
||||
if (!activeListUrls?.includes(listUrlA) && activeListUrls?.includes(listUrlB)) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (l1 && l2) {
|
||||
return l1.name.toLowerCase() < l2.name.toLowerCase()
|
||||
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
|
||||
: l1.name.toLowerCase() === l2.name.toLowerCase()
|
||||
: listA.name.toLowerCase() === listB.name.toLowerCase()
|
||||
? 0
|
||||
: 1
|
||||
}
|
||||
if (l1) return -1
|
||||
if (l2) return 1
|
||||
if (listA) return -1
|
||||
if (listB) return 1
|
||||
return 0
|
||||
})
|
||||
}, [lists, activeCopy])
|
||||
}, [lists, activeListUrls, tokenCountByListName])
|
||||
|
||||
// temporary fetched list for import flow
|
||||
const [tempList, setTempList] = useState<TokenList>()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useRef, RefObject, useCallback, useState, useMemo } from 'react'
|
||||
import { useRef, RefObject, useCallback, useState, useMemo } from 'react'
|
||||
import Column from 'components/Column'
|
||||
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
|
||||
import { PaddedColumn, Separator, SearchInput } from './styleds'
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { RowFixed } from '../Row'
|
||||
|
||||
export const FilterWrapper = styled(RowFixed)`
|
||||
padding: 8px;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
color: ${({ theme }) => theme.text1};
|
||||
border-radius: 8px;
|
||||
user-select: none;
|
||||
& > * {
|
||||
user-select: none;
|
||||
}
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
|
||||
export default function SortButton({
|
||||
toggleSortOrder,
|
||||
ascending,
|
||||
}: {
|
||||
toggleSortOrder: () => void
|
||||
ascending: boolean
|
||||
}) {
|
||||
return (
|
||||
<FilterWrapper onClick={toggleSortOrder}>
|
||||
<Text fontSize={14} fontWeight={500}>
|
||||
{ascending ? '↑' : '↓'}
|
||||
</Text>
|
||||
</FilterWrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,43 +1,6 @@
|
||||
import styled from 'styled-components/macro'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
|
||||
export const ModalInfo = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
padding: 1rem 1rem;
|
||||
margin: 0.25rem 0.5rem;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
user-select: none;
|
||||
`
|
||||
export const StyledMenu = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border: none;
|
||||
`
|
||||
|
||||
export 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.bg2};
|
||||
border: 1px solid ${({ theme }) => theme.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.text2};
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
grid-gap: 8px;
|
||||
font-size: 1rem;
|
||||
text-align: left;
|
||||
top: 80px;
|
||||
`
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
export const TextDot = styled.div`
|
||||
height: 3px;
|
||||
@@ -46,10 +9,6 @@ export const TextDot = styled.div`
|
||||
border-radius: 50%;
|
||||
`
|
||||
|
||||
export const FadedSpan = styled(RowFixed)`
|
||||
color: ${({ theme }) => theme.primary1};
|
||||
font-size: 14px;
|
||||
`
|
||||
export const Checkbox = styled.input`
|
||||
border: 1px solid ${({ theme }) => theme.red3};
|
||||
height: 20px;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import React, { useContext, useRef, useState } from 'react'
|
||||
import { useContext, useRef, useState } from 'react'
|
||||
import { Settings, X } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Text } from 'rebass'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
import { ApplicationModal } from '../../state/application/actions'
|
||||
import { useModalOpen, useToggleSettingsMenu } from '../../state/application/hooks'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const StyledRangeInput = styled.input<{ size: number }>`
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import React, { useMemo } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useLocation } from 'react-router'
|
||||
import { useMemo } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { DEFAULT_LOCALE, LOCALE_LABEL, SupportedLocale } from '../../constants/locales'
|
||||
import { navigatorLocale, useActiveLocale } from '../../hooks/useActiveLocale'
|
||||
import useParsedQueryString from '../../hooks/useParsedQueryString'
|
||||
import { StyledInternalLink, TYPE } from '../../theme'
|
||||
import { stringify } from 'qs'
|
||||
import { useLocationLinkProps } from 'hooks/useLocationLinkProps'
|
||||
|
||||
const Container = styled(TYPE.small)`
|
||||
opacity: 0.6;
|
||||
@@ -17,46 +14,35 @@ const Container = styled(TYPE.small)`
|
||||
margin-top: 1rem !important;
|
||||
`
|
||||
|
||||
export function SwitchLocaleLink() {
|
||||
const activeLocale = useActiveLocale()
|
||||
const useTargetLocale = (activeLocale: SupportedLocale) => {
|
||||
const browserLocale = useMemo(() => navigatorLocale(), [])
|
||||
const location = useLocation()
|
||||
const qs = useParsedQueryString()
|
||||
|
||||
if (browserLocale && (browserLocale !== DEFAULT_LOCALE || activeLocale !== DEFAULT_LOCALE)) {
|
||||
let targetLocale: SupportedLocale
|
||||
if (activeLocale === browserLocale) {
|
||||
targetLocale = DEFAULT_LOCALE
|
||||
return DEFAULT_LOCALE
|
||||
} else {
|
||||
targetLocale = browserLocale
|
||||
return browserLocale
|
||||
}
|
||||
|
||||
const target = {
|
||||
...location,
|
||||
search: stringify({ ...qs, lng: targetLocale }),
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Trans>
|
||||
Uniswap available in:{' '}
|
||||
{
|
||||
<StyledInternalLink
|
||||
onClick={() => {
|
||||
ReactGA.event({
|
||||
category: 'Localization',
|
||||
action: 'Switch Locale',
|
||||
label: `${activeLocale} -> ${targetLocale}`,
|
||||
})
|
||||
}}
|
||||
to={target}
|
||||
>
|
||||
{LOCALE_LABEL[targetLocale]}
|
||||
</StyledInternalLink>
|
||||
}
|
||||
</Trans>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function SwitchLocaleLink() {
|
||||
const activeLocale = useActiveLocale()
|
||||
const targetLocale = useTargetLocale(activeLocale)
|
||||
|
||||
const { to, onClick } = useLocationLinkProps(targetLocale)
|
||||
|
||||
if (!targetLocale || !to) return null
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Trans>
|
||||
Uniswap available in:{' '}
|
||||
<StyledInternalLink onClick={onClick} to={to}>
|
||||
{LOCALE_LABEL[targetLocale]}
|
||||
</StyledInternalLink>
|
||||
</Trans>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
146
src/components/TextInput/index.tsx
Normal file
146
src/components/TextInput/index.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import React, { memo, useCallback, useRef } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const Input = styled.input<{ error?: boolean; fontSize?: string }>`
|
||||
font-size: ${({ fontSize }) => fontSize || '1.25rem'};
|
||||
outline: none;
|
||||
border: none;
|
||||
flex: 1 1 auto;
|
||||
width: 0;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
transition: color 300ms ${({ error }) => (error ? 'step-end' : 'step-start')};
|
||||
color: ${({ error, theme }) => (error ? theme.red1 : theme.text1)};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
width: 100%;
|
||||
padding: 0px;
|
||||
-webkit-appearance: textfield;
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-outer-spin-button,
|
||||
::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: ${({ theme }) => theme.text4};
|
||||
}
|
||||
`
|
||||
|
||||
const TextAreaInput = styled.textarea<{ error?: boolean; fontSize?: string }>`
|
||||
font-size: ${({ fontSize }) => fontSize || '1.25rem'};
|
||||
outline: none;
|
||||
border: none;
|
||||
flex: 1 1 auto;
|
||||
width: 0;
|
||||
resize: none;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
transition: color 300ms ${({ error }) => (error ? 'step-end' : 'step-start')};
|
||||
color: ${({ error, theme }) => (error ? theme.red1 : theme.text1)};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
width: 100%;
|
||||
line-height: 1.2;
|
||||
padding: 0px;
|
||||
-webkit-appearance: textfield;
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-outer-spin-button,
|
||||
::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: ${({ theme }) => theme.text4};
|
||||
}
|
||||
`
|
||||
|
||||
export const TextInput = ({
|
||||
className,
|
||||
value,
|
||||
onUserInput,
|
||||
placeholder,
|
||||
fontSize,
|
||||
}: {
|
||||
className?: string
|
||||
value: string
|
||||
onUserInput: (value: string) => void
|
||||
placeholder: string
|
||||
fontSize: string
|
||||
}) => {
|
||||
const handleInput = useCallback(
|
||||
(event) => {
|
||||
onUserInput(event.target.value)
|
||||
},
|
||||
[onUserInput]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
placeholder={placeholder || ''}
|
||||
onChange={handleInput}
|
||||
value={value}
|
||||
fontSize={fontSize}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ResizingTextArea = memo(
|
||||
({
|
||||
className,
|
||||
value,
|
||||
onUserInput,
|
||||
placeholder,
|
||||
fontSize,
|
||||
}: {
|
||||
className?: string
|
||||
value: string
|
||||
onUserInput: (value: string) => void
|
||||
placeholder: string
|
||||
fontSize: string
|
||||
}) => {
|
||||
const inputRef = useRef<HTMLTextAreaElement>(document.createElement('textarea'))
|
||||
|
||||
const handleInput = useCallback(
|
||||
(event) => {
|
||||
inputRef.current.style.height = 'auto'
|
||||
inputRef.current.style.height = inputRef.current.scrollHeight + 'px'
|
||||
onUserInput(event.target.value)
|
||||
},
|
||||
[onUserInput]
|
||||
)
|
||||
|
||||
return (
|
||||
<TextAreaInput
|
||||
style={{ height: 'auto', minHeight: '500px' }}
|
||||
className={className}
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
placeholder={placeholder || ''}
|
||||
onChange={handleInput}
|
||||
value={value}
|
||||
fontSize={fontSize}
|
||||
ref={inputRef}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
ResizingTextArea.displayName = 'ResizingTextArea'
|
||||
@@ -1,37 +0,0 @@
|
||||
import { readableColor } from 'polished'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { colors } from 'theme'
|
||||
|
||||
const Swatch = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100px;
|
||||
justify-content: center;
|
||||
min-width: 200px;
|
||||
`
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
`
|
||||
|
||||
interface ThemePaletteProps {
|
||||
isDarkMode: boolean
|
||||
}
|
||||
|
||||
export default function ThemePalette({ isDarkMode }: ThemePaletteProps) {
|
||||
const data = colors(isDarkMode)
|
||||
return (
|
||||
<Wrapper>
|
||||
{Object.entries(data).map(([key, value]) => (
|
||||
<Swatch key={key} style={{ color: readableColor(value), backgroundColor: value }}>
|
||||
<div>{key}</div>
|
||||
<div>{value}</div>
|
||||
</Swatch>
|
||||
))}
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { TYPE } from '../../theme'
|
||||
|
||||
@@ -31,7 +30,7 @@ const StatusText = styled(TYPE.main)<{ isActive?: boolean }>`
|
||||
color: ${({ theme, isActive }) => (isActive ? theme.text1 : theme.text3)};
|
||||
`
|
||||
|
||||
export interface ToggleProps {
|
||||
interface ToggleProps {
|
||||
id?: string
|
||||
isActive: boolean
|
||||
bgColor: string
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user