Compare commits
305 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af6098bfe5 | ||
|
|
fce29bb36f | ||
|
|
4517af39ba | ||
|
|
b40163ce05 | ||
|
|
809902efec | ||
|
|
70be9894fa | ||
|
|
698ad5bac9 | ||
|
|
cd37b7533d | ||
|
|
c0cd6a1c8d | ||
|
|
f6245d1093 | ||
|
|
83c784f7c0 | ||
|
|
3d95b1a33b | ||
|
|
5c96942922 | ||
|
|
d27c83b382 | ||
|
|
b2f88965a9 | ||
|
|
95db44e0fc | ||
|
|
7d45ff5ca8 | ||
|
|
8964cf86aa | ||
|
|
0e9f23ed56 | ||
|
|
e08e597655 | ||
|
|
744db49803 | ||
|
|
54f59e02fd | ||
|
|
7950e5c083 | ||
|
|
dd33205bf6 | ||
|
|
397a20b9ec | ||
|
|
0aac0b43aa | ||
|
|
702500794d | ||
|
|
8bea95fab2 | ||
|
|
27094c87f2 | ||
|
|
bede9171c3 | ||
|
|
251d7c0bc2 | ||
|
|
285e4f28f5 | ||
|
|
3aa045303a | ||
|
|
e0a7c3794e | ||
|
|
f5fc5da341 | ||
|
|
bea5c0484b | ||
|
|
27960532ca | ||
|
|
37a4e2f6e3 | ||
|
|
19a3b12ca8 | ||
|
|
22c1ddf393 | ||
|
|
b44ae1a267 | ||
|
|
418dcf0cb2 | ||
|
|
58a508c9d6 | ||
|
|
3198129af2 | ||
|
|
89d484d882 | ||
|
|
fa4688d96c | ||
|
|
7ee761a59e | ||
|
|
78e95f6073 | ||
|
|
c67e57505a | ||
|
|
30f7385db7 | ||
|
|
c0f58ae810 | ||
|
|
54dd5476ca | ||
|
|
57786335df | ||
|
|
948e01a196 | ||
|
|
abf127c596 | ||
|
|
4d3f870b93 | ||
|
|
452f2dc3c0 | ||
|
|
b6bd59f2b1 | ||
|
|
0190b5a408 | ||
|
|
d6030dcd45 | ||
|
|
f0e2a491dc | ||
|
|
021aab6547 | ||
|
|
81af31eec1 | ||
|
|
d3898cf900 | ||
|
|
b8f61d5f90 | ||
|
|
6c880d29a6 | ||
|
|
a8baa6d6a5 | ||
|
|
00a1dee073 | ||
|
|
a6de7d7846 | ||
|
|
e1a81a9996 | ||
|
|
0770bab032 | ||
|
|
e5404dbf97 | ||
|
|
78e41848f2 | ||
|
|
77c090534b | ||
|
|
05acbfee88 | ||
|
|
64dd2f9ed1 | ||
|
|
09f30ce0f7 | ||
|
|
a49d5382db | ||
|
|
3affcb8d32 | ||
|
|
56e759ff78 | ||
|
|
f73a166d92 | ||
|
|
e1e52c06db | ||
|
|
ca4d915903 | ||
|
|
bdb6327c13 | ||
|
|
ce81dd5a79 | ||
|
|
73f29eea2c | ||
|
|
92193076c5 | ||
|
|
1aab086693 | ||
|
|
8057cb9fbe | ||
|
|
1b798889af | ||
|
|
1e54b97693 | ||
|
|
02c21ef720 | ||
|
|
b39aeeb805 | ||
|
|
8401a4b9b4 | ||
|
|
3b27ee94d7 | ||
|
|
660c355273 | ||
|
|
cd3c48462d | ||
|
|
acfd5c2720 | ||
|
|
a5ed12bfc7 | ||
|
|
40f0e619cc | ||
|
|
bd817083c9 | ||
|
|
699bcc25b6 | ||
|
|
e88a8effef | ||
|
|
2339817170 | ||
|
|
4220cafbd3 | ||
|
|
a61df58599 | ||
|
|
1b35128035 | ||
|
|
ab5114c5f5 | ||
|
|
28c7cfa1f1 | ||
|
|
509b307b67 | ||
|
|
1e5519de3f | ||
|
|
e8587396d3 | ||
|
|
d69b194ffb | ||
|
|
1325443025 | ||
|
|
1607f8919a | ||
|
|
873cf9760e | ||
|
|
b39f2fe055 | ||
|
|
516e783be6 | ||
|
|
9a326fa023 | ||
|
|
06d6c711dd | ||
|
|
7178746023 | ||
|
|
2efe8250ae | ||
|
|
24d8b4abc9 | ||
|
|
aec18b7eb1 | ||
|
|
0c37e81d97 | ||
|
|
624c3678c7 | ||
|
|
cdae20f2ed | ||
|
|
042967502e | ||
|
|
eedba9795e | ||
|
|
eaec4a33fc | ||
|
|
300dd70804 | ||
|
|
f4e994867e | ||
|
|
7ca79ff12b | ||
|
|
4903258b7c | ||
|
|
4bc1b8eb5c | ||
|
|
ee69357305 | ||
|
|
ce7f94f16a | ||
|
|
124b83ae56 | ||
|
|
0dcd5743c8 | ||
|
|
9fec8dbe93 | ||
|
|
549d4e38c6 | ||
|
|
3af6781821 | ||
|
|
fbccb83edb | ||
|
|
ff12b7be10 | ||
|
|
4c2cb5b0c1 | ||
|
|
d7785942b1 | ||
|
|
ed801deb15 | ||
|
|
c34727641d | ||
|
|
807860aac6 | ||
|
|
ee0db4f2aa | ||
|
|
1619386ab4 | ||
|
|
93d33947da | ||
|
|
91f3e21bd4 | ||
|
|
e9a432b58e | ||
|
|
1f41587ba9 | ||
|
|
9b639bee65 | ||
|
|
eaddc9e6f8 | ||
|
|
e83a1ec923 | ||
|
|
8021315f87 | ||
|
|
0540012bb9 | ||
|
|
3f6bf607dd | ||
|
|
828f7ee446 | ||
|
|
432d17bdfd | ||
|
|
9743b211e7 | ||
|
|
dce187e433 | ||
|
|
8859ff6979 | ||
|
|
f566a72b06 | ||
|
|
e39e5908e7 | ||
|
|
c5424d2dcc | ||
|
|
8b4bc167c5 | ||
|
|
43bceb232e | ||
|
|
c3909bc1d0 | ||
|
|
b25287923c | ||
|
|
65c51ea4aa | ||
|
|
b0fa08e9b0 | ||
|
|
b09eb8fb52 | ||
|
|
5b49cedebb | ||
|
|
ae76f26501 | ||
|
|
1cb1ffe2f6 | ||
|
|
d83bc3097d | ||
|
|
323edc0fcd | ||
|
|
fc258fdf5c | ||
|
|
2e599dc00e | ||
|
|
464a682fcc | ||
|
|
6ace3c211c | ||
|
|
2d0b0c70bf | ||
|
|
3c3be3c61a | ||
|
|
7660943e97 | ||
|
|
d342ccdb78 | ||
|
|
0ea9dc046b | ||
|
|
3a34c2ec25 | ||
|
|
83b0ef94f9 | ||
|
|
3680b93fb3 | ||
|
|
8e86ded09b | ||
|
|
37a50372f6 | ||
|
|
ab99cc612b | ||
|
|
11a4fa23c1 | ||
|
|
5358b4dc15 | ||
|
|
ccbd5dfcf7 | ||
|
|
bb17c57a84 | ||
|
|
3b6213f411 | ||
|
|
a5251f55de | ||
|
|
d846c83afa | ||
|
|
00438bea12 | ||
|
|
0dd5e6b33f | ||
|
|
2d7642ed9b | ||
|
|
740e18454f | ||
|
|
7c17cfc642 | ||
|
|
7f61b67947 | ||
|
|
466c0b0142 | ||
|
|
1c6d9d810e | ||
|
|
c3ad129658 | ||
|
|
a25a72d0d9 | ||
|
|
dbc9e85b90 | ||
|
|
7dafb0cfba | ||
|
|
da33ec9d2f | ||
|
|
605368629f | ||
|
|
ca2b84ec0b | ||
|
|
ac7cf35bb7 | ||
|
|
bd346030f0 | ||
|
|
aa742f415d | ||
|
|
77fa61495f | ||
|
|
bff3811faf | ||
|
|
60d1f8743f | ||
|
|
2d118a904a | ||
|
|
b69f08cbe1 | ||
|
|
644ecdbd32 | ||
|
|
4b47ae6da3 | ||
|
|
fe6a46fc0f | ||
|
|
539a6e05e5 | ||
|
|
3693c83b6e | ||
|
|
18408c9c75 | ||
|
|
39f018bae0 | ||
|
|
c00eb2451f | ||
|
|
6b99309fab | ||
|
|
976b15986f | ||
|
|
df4c18c8d4 | ||
|
|
62d3aa4b76 | ||
|
|
361d17c925 | ||
|
|
9269f15ffc | ||
|
|
925668c7a7 | ||
|
|
a64d526206 | ||
|
|
69234eb708 | ||
|
|
f833133689 | ||
|
|
c73a2655cb | ||
|
|
c55061873c | ||
|
|
d4562a2373 | ||
|
|
e13369154d | ||
|
|
6be54afd12 | ||
|
|
d1fad3c4ea | ||
|
|
a1c4b97a18 | ||
|
|
f00adc755a | ||
|
|
e97939c545 | ||
|
|
63907b7bc5 | ||
|
|
48ad8e529f | ||
|
|
b669ec6976 | ||
|
|
2d4b60b9dd | ||
|
|
9fc096d091 | ||
|
|
9f5584c37d | ||
|
|
0c0305a53d | ||
|
|
c3f65e3abd | ||
|
|
306aaa3a20 | ||
|
|
d08cae175a | ||
|
|
f0f4110b4b | ||
|
|
24521f0c92 | ||
|
|
a037595e6e | ||
|
|
edf4c47451 | ||
|
|
c0ce6a55c4 | ||
|
|
878fc9cf4d | ||
|
|
32e679c62e | ||
|
|
59164f876f | ||
|
|
5d952661d7 | ||
|
|
9509737811 | ||
|
|
e42a26c3dc | ||
|
|
eeb258ebd5 | ||
|
|
ab1538b196 | ||
|
|
7e5a230a33 | ||
|
|
20adf82c79 | ||
|
|
0ec6cad6d1 | ||
|
|
eb850cff0b | ||
|
|
9d9b57dd4c | ||
|
|
37b0e2fa28 | ||
|
|
b6d8512316 | ||
|
|
4e17107ac5 | ||
|
|
64c97f7e30 | ||
|
|
f3c513573d | ||
|
|
6965707d45 | ||
|
|
d762836eb9 | ||
|
|
88f8f804d9 | ||
|
|
c1042c6b7a | ||
|
|
3cb382376a | ||
|
|
b44c7e7d7e | ||
|
|
333c907e63 | ||
|
|
b630d59437 | ||
|
|
ba647d9d6c | ||
|
|
449ed0185e | ||
|
|
52e3ad0703 | ||
|
|
bf9dad2550 | ||
|
|
adcecdeefc | ||
|
|
5fabe438e5 | ||
|
|
98e77cb01b | ||
|
|
b05c4c111c | ||
|
|
4d612dc2a2 | ||
|
|
619c7c2d95 | ||
|
|
db886930a0 |
7
.env
7
.env
@@ -1,3 +1,4 @@
|
||||
REACT_APP_CHAIN_ID="1"
|
||||
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
|
||||
REACT_APP_WALLETCONNECT_BRIDGE_URL="https://uniswap.bridge.walletconnect.org"
|
||||
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||
REACT_APP_WALLETCONNECT_BRIDGE_URL="https://uniswap.bridge.walletconnect.org"
|
||||
# Because we use storybook which has its own babel-loader dependency @ 8.2.2, where react-scripts uses 8.1.0
|
||||
SKIP_PREFLIGHT_CHECK=true
|
||||
@@ -1,5 +1,4 @@
|
||||
REACT_APP_CHAIN_ID="1"
|
||||
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/099fc58e0de9451d80b18d7c74caa7c1"
|
||||
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
|
||||
REACT_APP_PORTIS_ID="c0e2bf01-4b08-4fd5-ac7b-8e26b58cd236"
|
||||
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
|
||||
REACT_APP_GOOGLE_ANALYTICS_ID="UA-128182339-4"
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"node_modules/**/*"
|
||||
],
|
||||
"ignorePatterns": ["node_modules/**/*"],
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
@@ -26,6 +24,9 @@
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"prettier/prettier": "error",
|
||||
"@typescript-eslint/no-explicit-any": "off"
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/bug-report.md
vendored
3
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -10,9 +10,10 @@ assignees: ''
|
||||
A clear and concise description of the bug.
|
||||
|
||||
**Steps to Reproduce**
|
||||
|
||||
1. Go to ...
|
||||
2. Click on ...
|
||||
...
|
||||
...
|
||||
|
||||
**Expected Behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,4 +1,4 @@
|
||||
blank_issues_enabled: false
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Support
|
||||
url: https://discord.gg/FCfyBSbCU5
|
||||
|
||||
14
.github/workflows/release.yaml
vendored
14
.github/workflows/release.yaml
vendored
@@ -94,19 +94,13 @@ jobs:
|
||||
The latest release is always accessible via our alias to the Cloudflare IPFS gateway at [app.uniswap.org](https://app.uniswap.org).
|
||||
|
||||
You can also access the Uniswap Interface directly from an IPFS gateway.
|
||||
The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to store your settings.
|
||||
**Beware** that other sites you access via the _same_ IPFS gateway can read and modify your settings on the Uniswap interface without your permission.
|
||||
You can avoid this issue by using a subdomain IPFS gateway, or our alias to the latest release at [app.uniswap.org](https://app.uniswap.org).
|
||||
The preferred URLs below are safe to use to access this specific release.
|
||||
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported.
|
||||
**You should always use an IPFS gateway that enforces origin separation**, or our alias to the latest release at [app.uniswap.org](https://app.uniswap.org).
|
||||
Your Uniswap settings are never remembered across different URLs.
|
||||
|
||||
Preferred URLs:
|
||||
IPFS gateways:
|
||||
- https://${{ steps.convert_cidv0.outputs.cidv1 }}.ipfs.dweb.link/
|
||||
- https://${{ steps.convert_cidv0.outputs.cidv1 }}.ipfs.cf-ipfs.com/
|
||||
- [ipfs://${{ steps.upload.outputs.hash }}/](ipfs://${{ steps.upload.outputs.hash }}/)
|
||||
|
||||
Other IPFS gateways:
|
||||
- https://cloudflare-ipfs.com/ipfs/${{ steps.upload.outputs.hash }}/
|
||||
- https://ipfs.infura.io/ipfs/${{ steps.upload.outputs.hash }}/
|
||||
- https://ipfs.io/ipfs/${{ steps.upload.outputs.hash }}/
|
||||
|
||||
${{ needs.bump_version.outputs.changelog }}
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,5 +1,9 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# generated contract types
|
||||
/src/types/v3
|
||||
/src/abis/types
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
|
||||
16
.storybook/main.ts
Normal file
16
.storybook/main.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
const { dirname, join, parse, resolve } = require('path')
|
||||
const { existsSync } = require('fs')
|
||||
|
||||
module.exports = {
|
||||
stories: ['../src/**/*.stories.@(ts|tsx)'],
|
||||
addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/preset-create-react-app'],
|
||||
typescript: {
|
||||
check: true,
|
||||
checkOptions: {},
|
||||
reactDocgen: 'react-docgen-typescript',
|
||||
reactDocgenTypescriptOptions: {
|
||||
shouldExtractLiteralValuesFromEnum: true,
|
||||
propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
|
||||
},
|
||||
},
|
||||
}
|
||||
4
.storybook/manager.ts
Normal file
4
.storybook/manager.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { addons } from '@storybook/addons'
|
||||
import { light } from './theme'
|
||||
|
||||
addons.setConfig({ theme: light })
|
||||
91
.storybook/preview.tsx
Normal file
91
.storybook/preview.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import 'inter-ui'
|
||||
import { Story } from '@storybook/react/types-6-0'
|
||||
import { createWeb3ReactRoot, Web3ReactProvider } from '@web3-react/core'
|
||||
import React from 'react'
|
||||
import { Provider as StoreProvider } from 'react-redux'
|
||||
import { ThemeProvider as SCThemeProvider } from 'styled-components'
|
||||
import { NetworkContextName } from '../src/constants'
|
||||
import store from '../src/state'
|
||||
import { FixedGlobalStyle, theme, ThemedGlobalStyle } from '../src/theme'
|
||||
import getLibrary from '../src/utils/getLibrary'
|
||||
import * as storybookThemes from './theme'
|
||||
|
||||
export const parameters = {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
dependencies: {
|
||||
withStoriesOnly: true,
|
||||
hideEmpty: true,
|
||||
},
|
||||
docs: {
|
||||
theme: storybookThemes.light,
|
||||
},
|
||||
viewport: {
|
||||
viewports: {
|
||||
mobile: {
|
||||
name: 'iPhone X',
|
||||
styles: {
|
||||
width: '375px',
|
||||
height: '812px',
|
||||
},
|
||||
},
|
||||
tablet: {
|
||||
name: 'iPad',
|
||||
styles: {
|
||||
width: '768px',
|
||||
height: '1024px',
|
||||
},
|
||||
},
|
||||
laptop: {
|
||||
name: 'Laptop',
|
||||
styles: {
|
||||
width: '1024px',
|
||||
height: '768px',
|
||||
},
|
||||
},
|
||||
desktop: {
|
||||
name: 'Desktop',
|
||||
styles: {
|
||||
width: '1440px',
|
||||
height: '1024px',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const globalTypes = {
|
||||
theme: {
|
||||
name: 'Theme',
|
||||
description: 'Global theme for components',
|
||||
defaultValue: 'light',
|
||||
toolbar: {
|
||||
icon: 'circlehollow',
|
||||
items: ['light', 'dark'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const Web3ProviderNetwork = createWeb3ReactRoot(NetworkContextName)
|
||||
|
||||
const withProviders = (Component: Story, context: Record<string, any>) => {
|
||||
const THEME = theme(context.globals.theme === 'dark')
|
||||
return (
|
||||
<>
|
||||
<Web3ReactProvider getLibrary={getLibrary}>
|
||||
<Web3ProviderNetwork getLibrary={getLibrary}>
|
||||
<StoreProvider store={store}>
|
||||
<SCThemeProvider theme={THEME}>
|
||||
<FixedGlobalStyle />
|
||||
<ThemedGlobalStyle />
|
||||
<main>
|
||||
<Component />
|
||||
</main>
|
||||
</SCThemeProvider>
|
||||
</StoreProvider>
|
||||
</Web3ProviderNetwork>
|
||||
</Web3ReactProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const decorators = [withProviders]
|
||||
17
.storybook/theme.ts
Normal file
17
.storybook/theme.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { create } from '@storybook/theming'
|
||||
|
||||
// this themes the storybook UI
|
||||
const uniswapBaseTheme = {
|
||||
brandTitle: 'Uniswap Design',
|
||||
brandUrl: 'https://uniswap.org',
|
||||
brandImage: 'https://ipfs.io/ipfs/QmNa8mQkrNKp1WEEeGjFezDmDeodkWRevGFN8JCV7b4Xir',
|
||||
}
|
||||
export const light = create({
|
||||
base: 'light',
|
||||
...uniswapBaseTheme,
|
||||
})
|
||||
|
||||
// export const dark = create({
|
||||
// base: 'dark',
|
||||
// ...uniswapBaseTheme,
|
||||
// })
|
||||
27
README.md
27
README.md
@@ -19,21 +19,15 @@ An open source interface for Uniswap -- a protocol for decentralized exchange of
|
||||
## Accessing the Uniswap Interface
|
||||
|
||||
To access the Uniswap Interface, use an IPFS gateway link from the
|
||||
[latest release](https://github.com/Uniswap/uniswap-interface/releases/latest),
|
||||
[latest release](https://github.com/Uniswap/uniswap-interface/releases/latest),
|
||||
or visit [app.uniswap.org](https://app.uniswap.org).
|
||||
|
||||
## Listing a token
|
||||
|
||||
Please see the
|
||||
[@uniswap/default-token-list](https://github.com/uniswap/default-token-list)
|
||||
repository.
|
||||
|
||||
## Development
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
yarn
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Run
|
||||
@@ -42,26 +36,13 @@ yarn
|
||||
yarn start
|
||||
```
|
||||
|
||||
### Configuring the environment (optional)
|
||||
|
||||
To have the interface default to a different network when a wallet is not connected:
|
||||
|
||||
1. Make a copy of `.env` named `.env.local`
|
||||
2. Change `REACT_APP_NETWORK_ID` to `"{YOUR_NETWORK_ID}"`
|
||||
3. Change `REACT_APP_NETWORK_URL` to e.g. `"https://{YOUR_NETWORK_ID}.infura.io/v3/{YOUR_INFURA_KEY}"`
|
||||
|
||||
Note that the interface only works on testnets where both
|
||||
[Uniswap V2](https://uniswap.org/docs/v2/smart-contracts/factory/) and
|
||||
[multicall](https://github.com/makerdao/multicall) are deployed.
|
||||
The interface will not work on other networks.
|
||||
|
||||
## Contributions
|
||||
|
||||
**Please open all pull requests against the `main` branch.**
|
||||
**Please open all pull requests against the `main` branch.**
|
||||
CI checks will run against all PRs.
|
||||
|
||||
## Accessing Uniswap Interface V1
|
||||
|
||||
The Uniswap Interface supports swapping against, and migrating or removing liquidity from Uniswap V1. However,
|
||||
if you would like to use Uniswap V1, the Uniswap V1 interface for mainnet and testnets is accessible via IPFS gateways
|
||||
if you would like to use Uniswap V1, the Uniswap V1 interface for mainnet and testnets is accessible via IPFS gateways
|
||||
linked from the [v1.0.0 release](https://github.com/Uniswap/uniswap-interface/releases/tag/v1.0.0).
|
||||
|
||||
8
babel-plugin-macros.config.js
Normal file
8
babel-plugin-macros.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const isDev = process.env.NODE_ENV !== 'production'
|
||||
|
||||
module.exports = {
|
||||
styledComponents: {
|
||||
fileName: isDev,
|
||||
displayName: isDev,
|
||||
},
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
describe('Add Liquidity', () => {
|
||||
it('loads the two correct tokens', () => {
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85-0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab/500')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'ETH')
|
||||
})
|
||||
|
||||
it('does not crash if ETH is duplicated', () => {
|
||||
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'ETH')
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('not.contain.text', 'ETH')
|
||||
})
|
||||
|
||||
it('token not in storage is loaded', () => {
|
||||
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'MKR')
|
||||
})
|
||||
@@ -23,28 +23,4 @@ describe('Add Liquidity', () => {
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('redirects /add/token-token to add/token/token', () => {
|
||||
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.url().should(
|
||||
'contain',
|
||||
'/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
|
||||
)
|
||||
})
|
||||
|
||||
it('redirects /add/WETH-token to /add/WETH-address/token', () => {
|
||||
cy.visit('/add/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.url().should(
|
||||
'contain',
|
||||
'/add/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
|
||||
)
|
||||
})
|
||||
|
||||
it('redirects /add/token-WETH to /add/token/WETH-address', () => {
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85-0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
cy.url().should(
|
||||
'contain',
|
||||
'/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
describe('Migrate V1 Liquidity', () => {
|
||||
describe('Remove V1 liquidity', () => {
|
||||
it('renders the correct page', () => {
|
||||
cy.visit('/remove/v1/0x93bB63aFe1E0180d0eF100D774B473034fd60C36')
|
||||
cy.get('#remove-v1-exchange').should('contain', 'MKR/ETH')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -4,9 +4,4 @@ describe('Pool', () => {
|
||||
cy.get('#join-pool-button').click()
|
||||
cy.url().should('contain', '/add/ETH')
|
||||
})
|
||||
|
||||
it('import pool links to /import', () => {
|
||||
cy.get('#import-pool-link').click()
|
||||
cy.url().should('contain', '/find')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,38 +1,30 @@
|
||||
describe('Remove Liquidity', () => {
|
||||
it('redirects', () => {
|
||||
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.url().should(
|
||||
'contain',
|
||||
'/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
|
||||
)
|
||||
})
|
||||
|
||||
it('eth remove', () => {
|
||||
cy.visit('/remove/ETH/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.visit('/remove/v2/ETH/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'ETH')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('eth remove swap order', () => {
|
||||
cy.visit('/remove/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/ETH')
|
||||
cy.visit('/remove/v2/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/ETH')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'MKR')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'ETH')
|
||||
})
|
||||
|
||||
it('loads the two correct tokens', () => {
|
||||
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.visit('/remove/v2/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('does not crash if ETH is duplicated', () => {
|
||||
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
cy.visit('/remove/v2/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xc778417E063141139Fce010982780140Aa0cD5Ab')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'WETH')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH')
|
||||
})
|
||||
|
||||
it('token not in storage is loaded', () => {
|
||||
cy.visit('/remove/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.visit('/remove/v2/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'SKL')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
@@ -3,36 +3,26 @@ describe('Swap', () => {
|
||||
cy.visit('/swap')
|
||||
})
|
||||
it('can enter an amount into input', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input')
|
||||
.type('0.001', { delay: 200 })
|
||||
.should('have.value', '0.001')
|
||||
cy.get('#swap-currency-input .token-amount-input').type('0.001', { delay: 200 }).should('have.value', '0.001')
|
||||
})
|
||||
|
||||
it('zero swap amount', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input')
|
||||
.type('0.0', { delay: 200 })
|
||||
.should('have.value', '0.0')
|
||||
cy.get('#swap-currency-input .token-amount-input').type('0.0', { delay: 200 }).should('have.value', '0.0')
|
||||
})
|
||||
|
||||
it('invalid swap amount', () => {
|
||||
cy.get('#swap-currency-input .token-amount-input')
|
||||
.type('\\', { delay: 200 })
|
||||
.should('have.value', '')
|
||||
cy.get('#swap-currency-input .token-amount-input').type('\\', { delay: 200 }).should('have.value', '')
|
||||
})
|
||||
|
||||
it('can enter an amount into output', () => {
|
||||
cy.get('#swap-currency-output .token-amount-input')
|
||||
.type('0.001', { delay: 200 })
|
||||
.should('have.value', '0.001')
|
||||
cy.get('#swap-currency-output .token-amount-input').type('0.001', { delay: 200 }).should('have.value', '0.001')
|
||||
})
|
||||
|
||||
it('zero output amount', () => {
|
||||
cy.get('#swap-currency-output .token-amount-input')
|
||||
.type('0.0', { delay: 200 })
|
||||
.should('have.value', '0.0')
|
||||
cy.get('#swap-currency-output .token-amount-input').type('0.0', { delay: 200 }).should('have.value', '0.0')
|
||||
})
|
||||
|
||||
it('can swap ETH for DAI', () => {
|
||||
it.skip('can swap ETH for DAI', () => {
|
||||
cy.get('#swap-currency-output .open-currency-select-button').click()
|
||||
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').should('be.visible')
|
||||
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').click({ force: true })
|
||||
@@ -43,13 +33,13 @@ describe('Swap', () => {
|
||||
cy.get('#confirm-swap-or-send').should('contain', 'Confirm Swap')
|
||||
})
|
||||
|
||||
it('add a recipient does not exist unless in expert mode', () => {
|
||||
it.skip('add a recipient does not exist unless in expert mode', () => {
|
||||
cy.get('#add-recipient-button').should('not.exist')
|
||||
})
|
||||
|
||||
describe('expert mode', () => {
|
||||
beforeEach(() => {
|
||||
cy.window().then(win => {
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'prompt').returns('confirm')
|
||||
})
|
||||
cy.get('#open-settings-dialog-button').click()
|
||||
@@ -57,16 +47,16 @@ describe('Swap', () => {
|
||||
cy.get('#confirm-expert-mode').click()
|
||||
})
|
||||
|
||||
it('add a recipient is visible', () => {
|
||||
it.skip('add a recipient is visible', () => {
|
||||
cy.get('#add-recipient-button').should('be.visible')
|
||||
})
|
||||
|
||||
it('add a recipient', () => {
|
||||
it.skip('add a recipient', () => {
|
||||
cy.get('#add-recipient-button').click()
|
||||
cy.get('#recipient').should('exist')
|
||||
})
|
||||
|
||||
it('remove recipient', () => {
|
||||
it.skip('remove recipient', () => {
|
||||
cy.get('#add-recipient-button').click()
|
||||
cy.get('#remove-recipient-button').click()
|
||||
cy.get('#recipient').should('not.exist')
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
import { Wallet } from '@ethersproject/wallet'
|
||||
import { _Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||
|
||||
const TEST_PRIVATE_KEY = Cypress.env('INTEGRATION_TEST_PRIVATE_KEY')
|
||||
|
||||
@@ -18,7 +18,9 @@ export const TEST_ADDRESS_NEVER_USE_SHORTENED = `${TEST_ADDRESS_NEVER_USE.substr
|
||||
6
|
||||
)}...${TEST_ADDRESS_NEVER_USE.substr(-4, 4)}`
|
||||
|
||||
class CustomizedBridge extends _Eip1193Bridge {
|
||||
class CustomizedBridge extends Eip1193Bridge {
|
||||
chainId = 4
|
||||
|
||||
async sendAsync(...args) {
|
||||
console.debug('sendAsync called', ...args)
|
||||
return this.send(...args)
|
||||
@@ -79,6 +81,6 @@ Cypress.Commands.overwrite('visit', (original, url, options) => {
|
||||
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
|
||||
const signer = new Wallet(TEST_PRIVATE_KEY, provider)
|
||||
win.ethereum = new CustomizedBridge(signer, provider)
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
77
package.json
77
package.json
@@ -4,48 +4,66 @@
|
||||
"homepage": ".",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@emotion/core": "^11.0.0",
|
||||
"@ethersproject/experimental": "^5.0.1",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"@reach/dialog": "^0.10.3",
|
||||
"@reach/portal": "^0.10.3",
|
||||
"@react-hook/window-scroll": "^1.3.0",
|
||||
"@reduxjs/toolkit": "^1.3.5",
|
||||
"@storybook/addon-actions": "^6.1.17",
|
||||
"@storybook/addon-essentials": "^6.1.17",
|
||||
"@storybook/addon-links": "^6.1.17",
|
||||
"@storybook/addons": "^6.1.17",
|
||||
"@storybook/components": "^6.1.17",
|
||||
"@storybook/preset-create-react-app": "^3.1.5",
|
||||
"@storybook/preset-typescript": "^3.0.0",
|
||||
"@storybook/react": "^6.1.17",
|
||||
"@storybook/theming": "^6.1.17",
|
||||
"@styled-system/css": "^5.1.5",
|
||||
"@typechain/ethers-v5": "^7.0.0",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/lodash.flatmap": "^4.5.6",
|
||||
"@types/luxon": "^1.24.4",
|
||||
"@types/multicodec": "^1.0.0",
|
||||
"@types/node": "^13.13.5",
|
||||
"@types/qs": "^6.9.2",
|
||||
"@types/react": "^16.9.34",
|
||||
"@types/react-dom": "^16.9.7",
|
||||
"@types/react-redux": "^7.1.8",
|
||||
"@types/react": "^17.0.2",
|
||||
"@types/react-dom": "^17.0.1",
|
||||
"@types/react-redux": "^7.1.16",
|
||||
"@types/react-router-dom": "^5.0.0",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.0",
|
||||
"@types/react-window": "^1.8.2",
|
||||
"@types/rebass": "^4.0.5",
|
||||
"@types/rebass": "^4.0.7",
|
||||
"@types/styled-components": "^5.1.0",
|
||||
"@types/testing-library__cypress": "^5.0.5",
|
||||
"@types/ua-parser-js": "^0.7.35",
|
||||
"@types/wcag-contrast": "^3.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.31.0",
|
||||
"@typescript-eslint/parser": "^2.31.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.1.0",
|
||||
"@typescript-eslint/parser": "^4.1.0",
|
||||
"@uniswap/default-token-list": "^2.0.0",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
"@uniswap/sdk": "3.0.3",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.19",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v2-sdk": "^3.0.0-alpha.0",
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "1.0.0",
|
||||
"@uniswap/v3-sdk": "^3.0.0-alpha.2",
|
||||
"@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.1.1",
|
||||
"@web3-react/walletlink-connector": "^6.0.9",
|
||||
"@web3-react/walletconnect-connector": "^6.2.0",
|
||||
"@web3-react/walletlink-connector": "^6.2.0",
|
||||
"ajv": "^6.12.3",
|
||||
"cids": "^1.0.0",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"cypress": "^4.11.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-react": "^7.19.0",
|
||||
@@ -56,26 +74,27 @@
|
||||
"i18next-xhr-backend": "^2.0.1",
|
||||
"inter-ui": "^3.13.1",
|
||||
"jazzicon": "^1.5.0",
|
||||
"lightweight-charts": "^3.3.0",
|
||||
"lodash.flatmap": "^4.5.0",
|
||||
"luxon": "^1.25.0",
|
||||
"multicodec": "^2.0.0",
|
||||
"multihashes": "^3.0.1",
|
||||
"multicodec": "^3.0.1",
|
||||
"multihashes": "^4.0.2",
|
||||
"node-vibrant": "^3.1.5",
|
||||
"polished": "^3.3.2",
|
||||
"prettier": "^1.17.0",
|
||||
"prettier": "^2.2.1",
|
||||
"qs": "^6.9.4",
|
||||
"react": "^16.13.1",
|
||||
"react": "^17.0.1",
|
||||
"react-confetti": "^6.0.0",
|
||||
"react-device-detect": "^1.6.2",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-feather": "^2.0.8",
|
||||
"react-ga": "^2.5.7",
|
||||
"react-i18next": "^10.7.0",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-popper": "^2.2.3",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.0.0",
|
||||
"react-scripts": "^3.4.1",
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-use-gesture": "^6.0.14",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
@@ -85,7 +104,10 @@
|
||||
"serve": "^11.3.2",
|
||||
"start-server-and-test": "^1.11.0",
|
||||
"styled-components": "^4.2.0",
|
||||
"typescript": "^3.8.3",
|
||||
"styled-system": "^5.1.5",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.2.3",
|
||||
"ua-parser-js": "^0.7.28",
|
||||
"use-count-up": "^2.2.5",
|
||||
"wcag-contrast": "^3.0.0",
|
||||
"workbox-core": "^6.1.0",
|
||||
@@ -95,12 +117,15 @@
|
||||
"workbox-strategies": "^6.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"start:service-worker": "yarn build && yarn serve -s build",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject",
|
||||
"integration-test": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run'"
|
||||
"compile-contract-types": "yarn compile-external-abi-types && yarn compile-v3-contract-types",
|
||||
"compile-external-abi-types": "npx typechain --target ethers-v5 --out-dir src/abis/types './src/abis/**/*.json'",
|
||||
"compile-v3-contract-types": "npx typechain --target ethers-v5 --out-dir src/types/v3 './node_modules/@uniswap/?(v3-core|v3-periphery)/artifacts/contracts/**/*.json'",
|
||||
"build": "yarn compile-contract-types && react-scripts build",
|
||||
"integration-test": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run'",
|
||||
"postinstall": "yarn compile-contract-types",
|
||||
"start": "yarn compile-contract-types && react-scripts start",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"test": "react-scripts test --env=jsdom"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app",
|
||||
@@ -121,7 +146,5 @@
|
||||
]
|
||||
},
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@uniswap/default-token-list": "^2.0.0"
|
||||
}
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
@@ -87,5 +87,30 @@
|
||||
"forAtLeast": "for at least ",
|
||||
"brokenToken": "The selected token is not compatible with Uniswap V1. Adding liquidity will result in locked funds.",
|
||||
"toleranceExplanation": "Lowering this limit decreases your risk of frontrunning. However, this makes more likely that your transaction will fail due to normal price movements.",
|
||||
"tokenSearchPlaceholder": "Search name or paste address"
|
||||
"tokenSearchPlaceholder": "Search name or paste address",
|
||||
"selectFee": "Select Fee",
|
||||
"selectLiquidityRange": "Set Price Range",
|
||||
"selectPool": "Select Fee Tier",
|
||||
"depositAmounts": "Deposit Amounts",
|
||||
"fee": "fee",
|
||||
"setLimits": "Set Limits",
|
||||
"percent": "Percent",
|
||||
"rate": "Rate",
|
||||
"currentRate": "Current {{label}} Price:",
|
||||
"inactiveRangeWarning": "Your position will not earn fees or be used in trades until the market price moves into your range.",
|
||||
"invalidRangeWarning": "Invalid range selected. The min price must be lower than the max price.",
|
||||
"connectWallet": "Connect Wallet",
|
||||
"unsupportedAsset": "Unsupported Asset",
|
||||
"feePool": "Fee Pool",
|
||||
"feeTier": "Fee Tier",
|
||||
"rebalanceMessage": "Your underlying tokens will be automatically rebalanced when the rate of the pool changes and may be different when you withdraw the position.",
|
||||
"addEarnHelper": "You will earn fees from trades proportional to your share of the pool.",
|
||||
"learnMoreAboutFess": " Learn more about earning fees.",
|
||||
"selectAPool": "Choose the best fee tier for your selected pair.",
|
||||
"poolType": "Select a fee tier based on your preferred liquidity provider fee.",
|
||||
"rangeWarning": "Your liquidity will only be active and earning fees when the rate of the pool is within this price range.",
|
||||
"chooseLiquidityAmount": "Choose an amount of tokens to open this liquidity position. If you don’t have enough tokens you can trade for them with a Swap.",
|
||||
"inputTokenDynamic": "Input {{label}}",
|
||||
"selectStartingPrice": "Set Starting Price",
|
||||
"newPoolPrice": "Select the market rate for the tokens being added."
|
||||
}
|
||||
|
||||
20
src/abis/eip_2612.json
Normal file
20
src/abis/eip_2612.json
Normal file
@@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [{ "name": "owner", "type": "address" }],
|
||||
"name": "nonces",
|
||||
"outputs": [{ "name": "", "type": "uint256" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "DOMAIN_SEPARATOR",
|
||||
"outputs": [{ "name": "", "type": "bytes32" }],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
330
src/abis/multicall.json
Normal file
330
src/abis/multicall.json
Normal file
@@ -0,0 +1,330 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
165
src/abis/multicall2.json
Normal file
165
src/abis/multicall2.json
Normal file
@@ -0,0 +1,165 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
6
src/abis/staking-rewards.ts
Normal file
6
src/abis/staking-rewards.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Interface } from '@ethersproject/abi'
|
||||
import { abi as STAKING_REWARDS_ABI } from '@uniswap/liquidity-staker/build/StakingRewards.json'
|
||||
|
||||
const STAKING_REWARDS_INTERFACE = new Interface(STAKING_REWARDS_ABI)
|
||||
|
||||
export { STAKING_REWARDS_INTERFACE }
|
||||
@@ -89,7 +89,7 @@
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 22405
|
||||
"gas": "22405"
|
||||
},
|
||||
{
|
||||
"name": "tokenByIndex",
|
||||
@@ -108,7 +108,7 @@
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 631
|
||||
"gas": "631"
|
||||
},
|
||||
{
|
||||
"name": "tokenOfOwnerByIndex",
|
||||
@@ -131,7 +131,7 @@
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 1248
|
||||
"gas": "1248"
|
||||
},
|
||||
{
|
||||
"name": "transferFrom",
|
||||
@@ -153,7 +153,7 @@
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 259486
|
||||
"gas": "259486"
|
||||
},
|
||||
{
|
||||
"name": "safeTransferFrom",
|
||||
@@ -217,7 +217,7 @@
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 38422
|
||||
"gas": "38422"
|
||||
},
|
||||
{
|
||||
"name": "setApprovalForAll",
|
||||
@@ -235,7 +235,7 @@
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 38016
|
||||
"gas": "38016"
|
||||
},
|
||||
{
|
||||
"name": "mint",
|
||||
@@ -254,7 +254,7 @@
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 182636
|
||||
"gas": "182636"
|
||||
},
|
||||
{
|
||||
"name": "changeMinter",
|
||||
@@ -268,7 +268,7 @@
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 35897
|
||||
"gas": "35897"
|
||||
},
|
||||
{
|
||||
"name": "changeURI",
|
||||
@@ -282,7 +282,7 @@
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 35927
|
||||
"gas": "35927"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
@@ -296,7 +296,7 @@
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 6612
|
||||
"gas": "6612"
|
||||
},
|
||||
{
|
||||
"name": "symbol",
|
||||
@@ -310,7 +310,7 @@
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 6642
|
||||
"gas": "6642"
|
||||
},
|
||||
{
|
||||
"name": "totalSupply",
|
||||
@@ -324,7 +324,7 @@
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 873
|
||||
"gas": "873"
|
||||
},
|
||||
{
|
||||
"name": "minter",
|
||||
@@ -338,7 +338,7 @@
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 903
|
||||
"gas": "903"
|
||||
},
|
||||
{
|
||||
"name": "socks",
|
||||
@@ -353,7 +353,7 @@
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 933
|
||||
"gas": "933"
|
||||
},
|
||||
{
|
||||
"name": "newURI",
|
||||
@@ -367,7 +367,7 @@
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 963
|
||||
"gas": "963"
|
||||
},
|
||||
{
|
||||
"name": "ownerOf",
|
||||
@@ -386,7 +386,7 @@
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 1126
|
||||
"gas": "1126"
|
||||
},
|
||||
{
|
||||
"name": "balanceOf",
|
||||
@@ -405,7 +405,7 @@
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 1195
|
||||
"gas": "1195"
|
||||
},
|
||||
{
|
||||
"name": "getApproved",
|
||||
@@ -424,7 +424,7 @@
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 1186
|
||||
"gas": "1186"
|
||||
},
|
||||
{
|
||||
"name": "isApprovedForAll",
|
||||
@@ -447,7 +447,7 @@
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 1415
|
||||
"gas": "1415"
|
||||
},
|
||||
{
|
||||
"name": "supportsInterface",
|
||||
@@ -466,6 +466,6 @@
|
||||
"constant": true,
|
||||
"payable": false,
|
||||
"type": "function",
|
||||
"gas": 1246
|
||||
"gas": "1246"
|
||||
}
|
||||
]
|
||||
BIN
src/assets/images/sandtexture.webp
Normal file
BIN
src/assets/images/sandtexture.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 235 KiB |
BIN
src/assets/images/squiggle.png
Normal file
BIN
src/assets/images/squiggle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 7.9 KiB |
6
src/assets/svg/switch.svg
Normal file
6
src/assets/svg/switch.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 2.5L12.5 5L10 7.5" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 5L12.3333 5" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.5 13.5L3 11L5.5 8.5" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.3333 11L3 11" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 476 B |
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import useCopyClipboard from '../../hooks/useCopyClipboard'
|
||||
|
||||
import { LinkStyledButton } from '../../theme'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import { CheckCircle, Triangle } from 'react-feather'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
|
||||
@@ -26,7 +26,7 @@ const HeaderRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap};
|
||||
padding: 1rem 1rem;
|
||||
font-weight: 500;
|
||||
color: ${props => (props.color === 'blue' ? ({ theme }) => theme.primary1 : 'inherit')};
|
||||
color: ${(props) => (props.color === 'blue' ? ({ theme }) => theme.primary1 : 'inherit')};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
padding: 1rem;
|
||||
`};
|
||||
@@ -76,7 +76,6 @@ const AccountGroupingRow = styled.div`
|
||||
`
|
||||
|
||||
const AccountSection = styled.div`
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
padding: 0rem 1rem;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0rem 1rem 1.5rem 1rem;`};
|
||||
`
|
||||
@@ -223,7 +222,7 @@ export default function AccountDetails({
|
||||
pendingTransactions,
|
||||
confirmedTransactions,
|
||||
ENSName,
|
||||
openOptions
|
||||
openOptions,
|
||||
}: AccountDetailsProps) {
|
||||
const { chainId, account, connector } = useActiveWeb3React()
|
||||
const theme = useContext(ThemeContext)
|
||||
@@ -234,10 +233,10 @@ export default function AccountDetails({
|
||||
const isMetaMask = !!(ethereum && ethereum.isMetaMask)
|
||||
const name = Object.keys(SUPPORTED_WALLETS)
|
||||
.filter(
|
||||
k =>
|
||||
(k) =>
|
||||
SUPPORTED_WALLETS[k].connector === connector && (connector !== injected || isMetaMask === (k === 'METAMASK'))
|
||||
)
|
||||
.map(k => SUPPORTED_WALLETS[k].name)[0]
|
||||
.map((k) => SUPPORTED_WALLETS[k].name)[0]
|
||||
return <WalletName>Connected with {name}</WalletName>
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ const Input = styled.input<{ error?: boolean }>`
|
||||
export default function AddressInputPanel({
|
||||
id,
|
||||
value,
|
||||
onChange
|
||||
onChange,
|
||||
}: {
|
||||
id?: string
|
||||
// the typed string value
|
||||
@@ -82,7 +82,7 @@ export default function AddressInputPanel({
|
||||
const { address, loading, name } = useENS(value)
|
||||
|
||||
const handleInput = useCallback(
|
||||
event => {
|
||||
(event) => {
|
||||
const input = event.target.value
|
||||
const withoutSpaces = input.replace(/\s+/g, '')
|
||||
onChange(withoutSpaces)
|
||||
|
||||
44
src/components/Badge/Badge.stories.tsx
Normal file
44
src/components/Badge/Badge.stories.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Story } from '@storybook/react/types-6-0'
|
||||
import React, { PropsWithChildren } from 'react'
|
||||
import Component, { BadgeProps, BadgeVariant } from './index'
|
||||
|
||||
export default {
|
||||
title: 'Badge',
|
||||
argTypes: {
|
||||
variant: {
|
||||
name: 'variant',
|
||||
type: { name: 'string', require: false },
|
||||
defaultValue: BadgeVariant.DEFAULT,
|
||||
description: 'badge variant',
|
||||
control: {
|
||||
type: 'select',
|
||||
options: Object.values(BadgeVariant),
|
||||
},
|
||||
},
|
||||
},
|
||||
args: {
|
||||
children: '🦄 UNISWAP 🦄',
|
||||
},
|
||||
}
|
||||
|
||||
const Template: Story<PropsWithChildren<BadgeProps>> = (args) => <Component {...args}>{args.children}</Component>
|
||||
|
||||
export const DefaultBadge = Template.bind({})
|
||||
DefaultBadge.args = {
|
||||
variant: BadgeVariant.DEFAULT,
|
||||
}
|
||||
|
||||
export const WarningBadge = Template.bind({})
|
||||
WarningBadge.args = {
|
||||
variant: BadgeVariant.WARNING,
|
||||
}
|
||||
|
||||
export const NegativeBadge = Template.bind({})
|
||||
NegativeBadge.args = {
|
||||
variant: BadgeVariant.NEGATIVE,
|
||||
}
|
||||
|
||||
export const PositiveBadge = Template.bind({})
|
||||
PositiveBadge.args = {
|
||||
variant: BadgeVariant.POSITIVE,
|
||||
}
|
||||
77
src/components/Badge/RangeBadge.tsx
Normal file
77
src/components/Badge/RangeBadge.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React from 'react'
|
||||
|
||||
import Badge, { BadgeVariant } from 'components/Badge'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { MouseoverTooltip } from '../../components/Tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AlertCircle } from 'react-feather'
|
||||
|
||||
const BadgeWrapper = styled.div`
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`
|
||||
|
||||
const BadgeText = styled.div`
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
`
|
||||
|
||||
const ActiveDot = styled.span`
|
||||
background-color: ${({ theme }) => theme.success};
|
||||
border-radius: 50%;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
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,
|
||||
}: {
|
||||
removed: boolean | undefined
|
||||
inRange: boolean | undefined
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<BadgeWrapper>
|
||||
{removed ? (
|
||||
<MouseoverTooltip text={`Your position has 0 liquidity, and is not earning fees.`}>
|
||||
<Badge variant={BadgeVariant.DEFAULT}>
|
||||
<AlertCircle width={14} height={14} />
|
||||
|
||||
<BadgeText>{t('Inactive')}</BadgeText>
|
||||
</Badge>
|
||||
</MouseoverTooltip>
|
||||
) : inRange ? (
|
||||
<MouseoverTooltip
|
||||
text={`The price of this pool is within your selected range. Your position is currently earning fees.`}
|
||||
>
|
||||
<Badge variant={BadgeVariant.DEFAULT}>
|
||||
<ActiveDot />
|
||||
<BadgeText>{t('In range')}</BadgeText>
|
||||
</Badge>
|
||||
</MouseoverTooltip>
|
||||
) : (
|
||||
<MouseoverTooltip
|
||||
text={`The price of this pool is outside of your selected range. Your position is not currently earning fees.`}
|
||||
>
|
||||
<Badge variant={BadgeVariant.WARNING}>
|
||||
<AlertCircle width={14} height={14} />
|
||||
|
||||
<BadgeText>{t('Out of range')}</BadgeText>
|
||||
</Badge>
|
||||
</MouseoverTooltip>
|
||||
)}
|
||||
</BadgeWrapper>
|
||||
)
|
||||
}
|
||||
73
src/components/Badge/index.tsx
Normal file
73
src/components/Badge/index.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import { readableColor } from 'polished'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import styled, { DefaultTheme } from 'styled-components'
|
||||
import { Color } from 'theme/styled'
|
||||
|
||||
export enum BadgeVariant {
|
||||
DEFAULT = 'DEFAULT',
|
||||
NEGATIVE = 'NEGATIVE',
|
||||
POSITIVE = 'POSITIVE',
|
||||
PRIMARY = 'PRIMARY',
|
||||
WARNING = 'WARNING',
|
||||
|
||||
WARNING_OUTLINE = 'WARNING_OUTLINE',
|
||||
}
|
||||
|
||||
export interface BadgeProps {
|
||||
variant?: BadgeVariant
|
||||
}
|
||||
|
||||
function pickBackgroundColor(variant: BadgeVariant | undefined, theme: DefaultTheme): Color {
|
||||
switch (variant) {
|
||||
case BadgeVariant.NEGATIVE:
|
||||
return theme.error
|
||||
case BadgeVariant.POSITIVE:
|
||||
return theme.success
|
||||
case BadgeVariant.PRIMARY:
|
||||
return theme.primary1
|
||||
case BadgeVariant.WARNING:
|
||||
return theme.warning
|
||||
case BadgeVariant.WARNING_OUTLINE:
|
||||
return 'transparent'
|
||||
default:
|
||||
return theme.bg2
|
||||
}
|
||||
}
|
||||
|
||||
function pickBorder(variant: BadgeVariant | undefined, theme: DefaultTheme): string {
|
||||
switch (variant) {
|
||||
case BadgeVariant.WARNING_OUTLINE:
|
||||
return `1px solid ${theme.warning}`
|
||||
default:
|
||||
return 'unset'
|
||||
}
|
||||
}
|
||||
|
||||
function pickFontColor(variant: BadgeVariant | undefined, theme: DefaultTheme): string {
|
||||
switch (variant) {
|
||||
case BadgeVariant.NEGATIVE:
|
||||
return readableColor(theme.error)
|
||||
case BadgeVariant.POSITIVE:
|
||||
return readableColor(theme.success)
|
||||
case BadgeVariant.WARNING:
|
||||
return readableColor(theme.warning)
|
||||
case BadgeVariant.WARNING_OUTLINE:
|
||||
return theme.warning
|
||||
default:
|
||||
return readableColor(theme.bg2)
|
||||
}
|
||||
}
|
||||
|
||||
const Badge = styled.div<PropsWithChildren<BadgeProps>>`
|
||||
align-items: center;
|
||||
background-color: ${({ theme, variant }) => pickBackgroundColor(variant, theme)};
|
||||
border: ${({ theme, variant }) => pickBorder(variant, theme)};
|
||||
border-radius: 0.5rem;
|
||||
color: ${({ theme, variant }) => pickFontColor(variant, theme)};
|
||||
display: inline-flex;
|
||||
padding: 4px 6px;
|
||||
justify-content: center;
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
export default Badge
|
||||
153
src/components/Button/Button.stories.tsx
Normal file
153
src/components/Button/Button.stories.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import { Story } from '@storybook/react/types-6-0'
|
||||
import styled from 'styled-components/macro'
|
||||
import React from 'react'
|
||||
import {
|
||||
ButtonConfirmed,
|
||||
ButtonDropdown,
|
||||
ButtonDropdownGrey,
|
||||
ButtonDropdownLight,
|
||||
ButtonEmpty,
|
||||
ButtonError,
|
||||
ButtonGray,
|
||||
ButtonLight,
|
||||
ButtonOutlined,
|
||||
ButtonPink,
|
||||
ButtonPrimary,
|
||||
ButtonRadio,
|
||||
ButtonSecondary,
|
||||
ButtonUNIGradient,
|
||||
ButtonWhite,
|
||||
} from './index'
|
||||
|
||||
const wrapperCss = styled.main`
|
||||
font-size: 2em;
|
||||
margin: 3em;
|
||||
max-width: 300px;
|
||||
`
|
||||
|
||||
export default {
|
||||
title: 'Buttons',
|
||||
argTypes: {
|
||||
disabled: { control: { type: 'boolean' } },
|
||||
onClick: { action: 'clicked' },
|
||||
},
|
||||
decorators: [
|
||||
(Component: Story) => (
|
||||
<div css={wrapperCss}>
|
||||
<Component />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
const Unicorn = () => (
|
||||
<span role="img" aria-label="unicorn">
|
||||
🦄
|
||||
</span>
|
||||
)
|
||||
|
||||
export const Radio = () => (
|
||||
<ButtonRadio>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonRadio>
|
||||
)
|
||||
export const DropdownLight = () => (
|
||||
<ButtonDropdownLight>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonDropdownLight>
|
||||
)
|
||||
export const DropdownGrey = () => (
|
||||
<ButtonDropdownGrey>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonDropdownGrey>
|
||||
)
|
||||
export const Dropdown = () => (
|
||||
<ButtonDropdown>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonDropdown>
|
||||
)
|
||||
export const Error = () => (
|
||||
<ButtonError>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonError>
|
||||
)
|
||||
export const Confirmed = () => (
|
||||
<ButtonConfirmed>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonConfirmed>
|
||||
)
|
||||
export const White = () => (
|
||||
<ButtonWhite>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonWhite>
|
||||
)
|
||||
export const Empty = () => (
|
||||
<ButtonEmpty>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonEmpty>
|
||||
)
|
||||
export const Outlined = () => (
|
||||
<ButtonOutlined>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonOutlined>
|
||||
)
|
||||
export const UNIGradient = () => (
|
||||
<ButtonUNIGradient>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonUNIGradient>
|
||||
)
|
||||
export const Pink = () => (
|
||||
<ButtonPink>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonPink>
|
||||
)
|
||||
export const Secondary = () => (
|
||||
<ButtonSecondary>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonSecondary>
|
||||
)
|
||||
export const Gray = () => (
|
||||
<ButtonGray>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonGray>
|
||||
)
|
||||
export const Light = () => (
|
||||
<ButtonLight>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonLight>
|
||||
)
|
||||
export const Primary = () => (
|
||||
<ButtonPrimary>
|
||||
<Unicorn />
|
||||
UNISWAP
|
||||
<Unicorn />
|
||||
</ButtonPrimary>
|
||||
)
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { darken, lighten } from 'polished'
|
||||
import styled from 'styled-components/macro'
|
||||
import { darken } from 'polished'
|
||||
|
||||
import { RowBetween } from '../Row'
|
||||
import { ChevronDown } from 'react-feather'
|
||||
import { ChevronDown, Check } from 'react-feather'
|
||||
import { Button as RebassButton, ButtonProps } from 'rebass/styled-components'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
|
||||
const Base = styled(RebassButton)<{
|
||||
padding?: string
|
||||
@@ -12,7 +13,7 @@ const Base = styled(RebassButton)<{
|
||||
borderRadius?: string
|
||||
altDisabledStyle?: boolean
|
||||
}>`
|
||||
padding: ${({ padding }) => (padding ? padding : '18px')};
|
||||
padding: ${({ padding }) => (padding ? padding : '16px')};
|
||||
width: ${({ width }) => (width ? width : '100%')};
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
@@ -31,11 +32,24 @@ const Base = styled(RebassButton)<{
|
||||
z-index: 1;
|
||||
&:disabled {
|
||||
cursor: auto;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
will-change: transform;
|
||||
transition: transform 450ms ease;
|
||||
transform: perspective(1px) translateZ(0);
|
||||
|
||||
&:hover {
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
> * {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
> a {
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonPrimary = styled(Base)`
|
||||
@@ -54,14 +68,14 @@ export const ButtonPrimary = styled(Base)`
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme, altDisabledStyle, disabled }) =>
|
||||
altDisabledStyle ? (disabled ? theme.bg3 : theme.primary1) : theme.bg3};
|
||||
color: ${({ theme, altDisabledStyle, disabled }) =>
|
||||
altDisabledStyle ? (disabled ? theme.text3 : 'white') : theme.text3};
|
||||
altDisabledStyle ? (disabled ? theme.primary1 : theme.primary1) : theme.primary1};
|
||||
color: white;
|
||||
cursor: auto;
|
||||
box-shadow: none;
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
opacity: ${({ altDisabledStyle }) => (altDisabledStyle ? '0.5' : '1')};
|
||||
opacity: 0.4;
|
||||
opacity: ${({ altDisabledStyle }) => (altDisabledStyle ? '0.5' : '0.4')};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -94,18 +108,18 @@ export const ButtonLight = styled(Base)`
|
||||
`
|
||||
|
||||
export const ButtonGray = styled(Base)`
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
color: ${({ theme }) => theme.text2};
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
&:focus {
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg4)};
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg2)};
|
||||
}
|
||||
&:hover {
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg4)};
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg2)};
|
||||
}
|
||||
&:active {
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.1, theme.bg4)};
|
||||
background-color: ${({ theme, disabled }) => !disabled && darken(0.1, theme.bg2)};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -221,6 +235,28 @@ export const ButtonEmpty = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonText = styled(Base)`
|
||||
padding: 0;
|
||||
width: fit-content;
|
||||
background: none;
|
||||
text-decoration: none;
|
||||
&:focus {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
text-decoration: underline;
|
||||
}
|
||||
&:hover {
|
||||
// text-decoration: underline;
|
||||
opacity: 0.9;
|
||||
}
|
||||
&:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
&:disabled {
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonWhite = styled(Base)`
|
||||
border: 1px solid #edeef2;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
@@ -243,12 +279,14 @@ export const ButtonWhite = styled(Base)`
|
||||
`
|
||||
|
||||
const ButtonConfirmedStyle = styled(Base)`
|
||||
background-color: ${({ theme }) => lighten(0.5, theme.green1)};
|
||||
color: ${({ theme }) => theme.green1};
|
||||
border: 1px solid ${({ theme }) => theme.green1};
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
color: ${({ theme }) => theme.text1};
|
||||
/* border: 1px solid ${({ theme }) => theme.green1}; */
|
||||
|
||||
&:disabled {
|
||||
opacity: 50%;
|
||||
/* opacity: 50%; */
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
color: ${({ theme }) => theme.text2};
|
||||
cursor: auto;
|
||||
}
|
||||
`
|
||||
@@ -337,3 +375,57 @@ export function ButtonRadio({ active, ...rest }: { active?: boolean } & ButtonPr
|
||||
return <ButtonPrimary {...rest} />
|
||||
}
|
||||
}
|
||||
|
||||
const ActiveOutlined = styled(ButtonOutlined)`
|
||||
border: 1px solid;
|
||||
border-color: ${({ theme }) => theme.primary1};
|
||||
`
|
||||
|
||||
const Circle = styled.div`
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 50%;
|
||||
background-color: ${({ theme }) => theme.primary1};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const CheckboxWrapper = styled.div`
|
||||
width: 30px;
|
||||
padding: 0 10px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
`
|
||||
|
||||
const ResponsiveCheck = styled(Check)`
|
||||
size: 13px;
|
||||
`
|
||||
|
||||
export function ButtonRadioChecked({ active = false, children, ...rest }: { active?: boolean } & ButtonProps) {
|
||||
const theme = useTheme()
|
||||
|
||||
if (!active) {
|
||||
return (
|
||||
<ButtonOutlined borderRadius="12px" padding="12px 8px" {...rest}>
|
||||
{<RowBetween>{children}</RowBetween>}
|
||||
</ButtonOutlined>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ActiveOutlined {...rest} padding="12px 8px" borderRadius="12px">
|
||||
{
|
||||
<RowBetween>
|
||||
{children}
|
||||
<CheckboxWrapper>
|
||||
<Circle>
|
||||
<ResponsiveCheck size={13} stroke={theme.white} />
|
||||
</Circle>
|
||||
</CheckboxWrapper>
|
||||
</RowBetween>
|
||||
}
|
||||
</ActiveOutlined>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { CardProps, Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Box } from 'rebass/styled-components'
|
||||
|
||||
const Card = styled(Box)<{ width?: string; padding?: string; border?: string; borderRadius?: string }>`
|
||||
width: ${({ width }) => width ?? '100%'};
|
||||
border-radius: 16px;
|
||||
padding: 1.25rem;
|
||||
padding: 1rem;
|
||||
padding: ${({ padding }) => padding};
|
||||
border: ${({ border }) => border};
|
||||
border-radius: ${({ borderRadius }) => borderRadius};
|
||||
@@ -26,13 +24,21 @@ export const GreyCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
`
|
||||
|
||||
export const DarkGreyCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
`
|
||||
|
||||
export const DarkCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
`
|
||||
|
||||
export const OutlineCard = styled(Card)`
|
||||
border: 1px solid ${({ theme }) => theme.bg3};
|
||||
`
|
||||
|
||||
export const YellowCard = styled(Card)`
|
||||
background-color: rgba(243, 132, 30, 0.05);
|
||||
color: ${({ theme }) => theme.yellow2};
|
||||
color: ${({ theme }) => theme.yellow3};
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
@@ -42,19 +48,8 @@ export const PinkCard = styled(Card)`
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
const BlueCardStyled = styled(Card)`
|
||||
export const BlueCard = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.primary5};
|
||||
color: ${({ theme }) => theme.primary1};
|
||||
color: ${({ theme }) => theme.blue2};
|
||||
border-radius: 12px;
|
||||
width: fit-content;
|
||||
`
|
||||
|
||||
export const BlueCard = ({ children, ...rest }: CardProps) => {
|
||||
return (
|
||||
<BlueCardStyled {...rest}>
|
||||
<Text fontWeight={500} color="#2172E5">
|
||||
{children}
|
||||
</Text>
|
||||
</BlueCardStyled>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const Column = styled.div`
|
||||
display: flex;
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function Confetti({ start, variant }: { start: boolean; variant?:
|
||||
h: height,
|
||||
w: width,
|
||||
x: 0,
|
||||
y: _variant === 'top' ? height * 0.25 : _variant === 'bottom' ? height * 0.75 : height * 0.5
|
||||
y: _variant === 'top' ? height * 0.25 : _variant === 'bottom' ? height * 0.75 : height * 0.5,
|
||||
}}
|
||||
initialVelocityX={15}
|
||||
initialVelocityY={30}
|
||||
|
||||
34
src/components/CurrencyInputPanel/FiatValue.tsx
Normal file
34
src/components/CurrencyInputPanel/FiatValue.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import React, { useMemo } from 'react'
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import { TYPE } from '../../theme'
|
||||
import { warningSeverity } from '../../utils/prices'
|
||||
import HoverInlineText from 'components/HoverInlineText'
|
||||
|
||||
export function FiatValue({
|
||||
fiatValue,
|
||||
priceImpact,
|
||||
}: {
|
||||
fiatValue: CurrencyAmount<Currency> | null | undefined
|
||||
priceImpact?: Percent
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const priceImpactColor = useMemo(() => {
|
||||
if (!priceImpact) return undefined
|
||||
if (priceImpact.lessThan('0')) return theme.green1
|
||||
const severity = warningSeverity(priceImpact)
|
||||
if (severity < 1) return theme.text4
|
||||
if (severity < 3) return theme.yellow1
|
||||
return theme.red1
|
||||
}, [priceImpact, theme.green1, theme.red1, theme.text4, theme.yellow1])
|
||||
|
||||
return (
|
||||
<TYPE.body fontSize={14} color={fiatValue ? theme.text2 : theme.text4}>
|
||||
{fiatValue ? '~' : ''}$
|
||||
<HoverInlineText text={fiatValue ? fiatValue?.toSignificant(6, { groupSeparator: ',' }) : '-'} />{' '}
|
||||
{priceImpact ? (
|
||||
<span style={{ color: priceImpactColor }}> ({priceImpact.multiply(-1).toSignificant(3)}%)</span>
|
||||
) : null}
|
||||
</TYPE.body>
|
||||
)
|
||||
}
|
||||
@@ -1,68 +1,114 @@
|
||||
import { Currency, Pair } from '@uniswap/sdk'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import { darken } from 'polished'
|
||||
import { useCurrencyBalance } from '../../state/wallet/hooks'
|
||||
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { RowBetween } from '../Row'
|
||||
import { ButtonGray } from '../Button'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import { TYPE } from '../../theme'
|
||||
import { Input as NumericalInput } from '../NumericalInput'
|
||||
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import { Lock } from 'react-feather'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { FiatValue } from './FiatValue'
|
||||
import { formatTokenAmount } from 'utils/formatTokenAmount'
|
||||
|
||||
const InputRow = styled.div<{ selected: boolean }>`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
padding: ${({ selected }) => (selected ? '0.75rem 0.5rem 0.75rem 1rem' : '0.75rem 0.75rem 0.75rem 1rem')};
|
||||
const InputPanel = styled.div<{ hideInput?: boolean }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
position: relative;
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
background-color: ${({ theme, hideInput }) => (hideInput ? 'transparent' : theme.bg2)};
|
||||
z-index: 1;
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
`
|
||||
|
||||
const CurrencySelect = styled.button<{ selected: boolean }>`
|
||||
const FixedContainer = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
border-radius: 20px;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
opacity: 0.95;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 2.2rem;
|
||||
font-size: 20px;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
`
|
||||
|
||||
const Container = styled.div<{ hideInput: boolean }>`
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
border: 1px solid ${({ theme, hideInput }) => (hideInput ? ' transparent' : theme.bg2)};
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
:focus,
|
||||
:hover {
|
||||
border: 1px solid ${({ theme, hideInput }) => (hideInput ? ' transparent' : theme.bg3)};
|
||||
}
|
||||
`
|
||||
|
||||
const CurrencySelect = styled(ButtonGray)<{ selected: boolean; hideInput?: boolean }>`
|
||||
align-items: center;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
background-color: ${({ selected, theme }) => (selected ? theme.bg1 : theme.primary1)};
|
||||
background-color: ${({ selected, theme }) => (selected ? theme.bg0 : theme.primary1)};
|
||||
color: ${({ selected, theme }) => (selected ? theme.text1 : theme.white)};
|
||||
border-radius: 12px;
|
||||
border-radius: 16px;
|
||||
box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')};
|
||||
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border: none;
|
||||
padding: 0 0.5rem;
|
||||
|
||||
height: ${({ hideInput }) => (hideInput ? '2.8rem' : '2.4rem')};
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
padding: 0 8px;
|
||||
justify-content: space-between;
|
||||
margin-right: ${({ hideInput }) => (hideInput ? '0' : '12px')};
|
||||
:focus,
|
||||
:hover {
|
||||
background-color: ${({ selected, theme }) => (selected ? theme.bg2 : darken(0.05, theme.primary1))};
|
||||
}
|
||||
`
|
||||
|
||||
const InputRow = styled.div<{ selected: boolean }>`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
padding: ${({ selected }) => (selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 0.75rem 1rem')};
|
||||
`
|
||||
|
||||
const LabelRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
padding: 0.75rem 1rem 0 1rem;
|
||||
padding: 0 1rem 1rem;
|
||||
span:hover {
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => darken(0.2, theme.text2)};
|
||||
}
|
||||
`
|
||||
|
||||
const FiatRow = styled(LabelRow)`
|
||||
justify-content: flex-end;
|
||||
`
|
||||
|
||||
const Aligner = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
|
||||
margin: 0 0.25rem 0 0.5rem;
|
||||
margin: 0 0.25rem 0 0.35rem;
|
||||
height: 35%;
|
||||
|
||||
path {
|
||||
@@ -71,42 +117,25 @@ const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
|
||||
}
|
||||
`
|
||||
|
||||
const InputPanel = styled.div<{ hideInput?: boolean }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
position: relative;
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')};
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
z-index: 1;
|
||||
`
|
||||
|
||||
const Container = styled.div<{ hideInput: boolean }>`
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')};
|
||||
border: 1px solid ${({ theme }) => theme.bg2};
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
`
|
||||
|
||||
const StyledTokenName = styled.span<{ active?: boolean }>`
|
||||
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.75rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
|
||||
font-size: ${({ active }) => (active ? '20px' : '16px')};
|
||||
|
||||
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
|
||||
font-size: ${({ active }) => (active ? '18px' : '18px')};
|
||||
`
|
||||
|
||||
const StyledBalanceMax = styled.button`
|
||||
height: 28px;
|
||||
background-color: ${({ theme }) => theme.primary5};
|
||||
border: 1px solid ${({ theme }) => theme.primary5};
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
|
||||
const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
margin-right: 0.5rem;
|
||||
color: ${({ theme }) => theme.primaryText1};
|
||||
:hover {
|
||||
border: 1px solid ${({ theme }) => theme.primary1};
|
||||
}
|
||||
padding: 0;
|
||||
color: ${({ theme }) => theme.primary1};
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
|
||||
margin-left: 0.25rem;
|
||||
|
||||
:focus {
|
||||
border: 1px solid ${({ theme }) => theme.primary1};
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -123,14 +152,16 @@ interface CurrencyInputPanelProps {
|
||||
label?: string
|
||||
onCurrencySelect?: (currency: Currency) => void
|
||||
currency?: Currency | null
|
||||
disableCurrencySelect?: boolean
|
||||
hideBalance?: boolean
|
||||
pair?: Pair | null
|
||||
hideInput?: boolean
|
||||
otherCurrency?: Currency | null
|
||||
fiatValue?: CurrencyAmount<Token> | null
|
||||
priceImpact?: Percent
|
||||
id: string
|
||||
showCommonBases?: boolean
|
||||
customBalanceText?: string
|
||||
locked?: boolean
|
||||
}
|
||||
|
||||
export default function CurrencyInputPanel({
|
||||
@@ -138,17 +169,19 @@ export default function CurrencyInputPanel({
|
||||
onUserInput,
|
||||
onMax,
|
||||
showMaxButton,
|
||||
label = 'Input',
|
||||
onCurrencySelect,
|
||||
currency,
|
||||
disableCurrencySelect = false,
|
||||
hideBalance = false,
|
||||
pair = null, // used for double token logo
|
||||
hideInput = false,
|
||||
otherCurrency,
|
||||
id,
|
||||
showCommonBases,
|
||||
customBalanceText
|
||||
customBalanceText,
|
||||
fiatValue,
|
||||
priceImpact,
|
||||
hideBalance = false,
|
||||
pair = null, // used for double token logo
|
||||
hideInput = false,
|
||||
locked = false,
|
||||
...rest
|
||||
}: CurrencyInputPanelProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -162,79 +195,99 @@ export default function CurrencyInputPanel({
|
||||
}, [setModalOpen])
|
||||
|
||||
return (
|
||||
<InputPanel id={id}>
|
||||
<InputPanel id={id} hideInput={hideInput} {...rest}>
|
||||
{locked && (
|
||||
<FixedContainer>
|
||||
<AutoColumn gap="sm" justify="center">
|
||||
<Lock />
|
||||
<TYPE.label fontSize="12px" textAlign="center">
|
||||
The market price is outside your specified price range. Single-asset deposit only.
|
||||
</TYPE.label>
|
||||
</AutoColumn>
|
||||
</FixedContainer>
|
||||
)}
|
||||
<Container hideInput={hideInput}>
|
||||
{!hideInput && (
|
||||
<LabelRow>
|
||||
<RowBetween>
|
||||
<TYPE.body color={theme.text2} fontWeight={500} fontSize={14}>
|
||||
{label}
|
||||
</TYPE.body>
|
||||
{account && (
|
||||
<TYPE.body
|
||||
onClick={onMax}
|
||||
color={theme.text2}
|
||||
fontWeight={500}
|
||||
fontSize={14}
|
||||
style={{ display: 'inline', cursor: 'pointer' }}
|
||||
>
|
||||
{!hideBalance && !!currency && selectedCurrencyBalance
|
||||
? (customBalanceText ?? 'Balance: ') + selectedCurrencyBalance?.toSignificant(6)
|
||||
: ' -'}
|
||||
</TYPE.body>
|
||||
)}
|
||||
</RowBetween>
|
||||
</LabelRow>
|
||||
)}
|
||||
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={disableCurrencySelect}>
|
||||
{!hideInput && (
|
||||
<>
|
||||
<NumericalInput
|
||||
className="token-amount-input"
|
||||
value={value}
|
||||
onUserInput={val => {
|
||||
onUserInput(val)
|
||||
}}
|
||||
/>
|
||||
{account && currency && showMaxButton && label !== 'To' && (
|
||||
<StyledBalanceMax onClick={onMax}>MAX</StyledBalanceMax>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={!onCurrencySelect}>
|
||||
<CurrencySelect
|
||||
selected={!!currency}
|
||||
hideInput={hideInput}
|
||||
className="open-currency-select-button"
|
||||
onClick={() => {
|
||||
if (!disableCurrencySelect) {
|
||||
if (onCurrencySelect) {
|
||||
setModalOpen(true)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Aligner>
|
||||
{pair ? (
|
||||
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
|
||||
) : currency ? (
|
||||
<CurrencyLogo currency={currency} size={'24px'} />
|
||||
) : null}
|
||||
{pair ? (
|
||||
<StyledTokenName className="pair-name-container">
|
||||
{pair?.token0.symbol}:{pair?.token1.symbol}
|
||||
</StyledTokenName>
|
||||
) : (
|
||||
<StyledTokenName className="token-symbol-container" active={Boolean(currency && currency.symbol)}>
|
||||
{(currency && currency.symbol && currency.symbol.length > 20
|
||||
? currency.symbol.slice(0, 4) +
|
||||
'...' +
|
||||
currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length)
|
||||
: currency?.symbol) || t('selectToken')}
|
||||
</StyledTokenName>
|
||||
)}
|
||||
{!disableCurrencySelect && <StyledDropDown selected={!!currency} />}
|
||||
<RowFixed>
|
||||
{pair ? (
|
||||
<span style={{ marginRight: '0.5rem' }}>
|
||||
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
|
||||
</span>
|
||||
) : currency ? (
|
||||
<CurrencyLogo style={{ marginRight: '0.5rem' }} currency={currency} size={'24px'} />
|
||||
) : null}
|
||||
{pair ? (
|
||||
<StyledTokenName className="pair-name-container">
|
||||
{pair?.token0.symbol}:{pair?.token1.symbol}
|
||||
</StyledTokenName>
|
||||
) : (
|
||||
<StyledTokenName className="token-symbol-container" active={Boolean(currency && currency.symbol)}>
|
||||
{(currency && currency.symbol && currency.symbol.length > 20
|
||||
? currency.symbol.slice(0, 4) +
|
||||
'...' +
|
||||
currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length)
|
||||
: currency?.symbol) || t('selectToken')}
|
||||
</StyledTokenName>
|
||||
)}
|
||||
</RowFixed>
|
||||
{onCurrencySelect && <StyledDropDown selected={!!currency} />}
|
||||
</Aligner>
|
||||
</CurrencySelect>
|
||||
{!hideInput && (
|
||||
<>
|
||||
<NumericalInput
|
||||
className="token-amount-input"
|
||||
value={value}
|
||||
onUserInput={(val) => {
|
||||
onUserInput(val)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</InputRow>
|
||||
{!hideInput && !hideBalance && (
|
||||
<FiatRow>
|
||||
<RowBetween>
|
||||
{account ? (
|
||||
<RowFixed style={{ height: '17px' }}>
|
||||
<TYPE.body
|
||||
onClick={onMax}
|
||||
color={theme.text2}
|
||||
fontWeight={400}
|
||||
fontSize={14}
|
||||
style={{ display: 'inline', cursor: 'pointer' }}
|
||||
>
|
||||
{!hideBalance && !!currency && selectedCurrencyBalance
|
||||
? (customBalanceText ?? 'Balance: ') +
|
||||
formatTokenAmount(selectedCurrencyBalance, 4) +
|
||||
' ' +
|
||||
currency.symbol
|
||||
: '-'}
|
||||
</TYPE.body>
|
||||
{showMaxButton && selectedCurrencyBalance ? (
|
||||
<StyledBalanceMax onClick={onMax}>(Max)</StyledBalanceMax>
|
||||
) : null}
|
||||
</RowFixed>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
|
||||
</RowBetween>
|
||||
</FiatRow>
|
||||
)}
|
||||
</Container>
|
||||
{!disableCurrencySelect && onCurrencySelect && (
|
||||
{onCurrencySelect && (
|
||||
<CurrencySearchModal
|
||||
isOpen={modalOpen}
|
||||
onDismiss={handleDismissSearch}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Currency, ETHER, Token } from '@uniswap/sdk'
|
||||
import { ChainId, Currency } from '@uniswap/sdk-core'
|
||||
import React, { useMemo } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import styled from 'styled-components/macro'
|
||||
import EthereumLogo from '../../assets/images/ethereum-logo.png'
|
||||
import useHttpLocations from '../../hooks/useHttpLocations'
|
||||
import { WrappedTokenInfo } from '../../state/lists/hooks'
|
||||
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
|
||||
import Logo from '../Logo'
|
||||
|
||||
export const getTokenLogoURL = (address: string) =>
|
||||
@@ -28,7 +27,8 @@ const StyledLogo = styled(Logo)<{ size: string }>`
|
||||
export default function CurrencyLogo({
|
||||
currency,
|
||||
size = '24px',
|
||||
style
|
||||
style,
|
||||
...rest
|
||||
}: {
|
||||
currency?: Currency
|
||||
size?: string
|
||||
@@ -37,20 +37,21 @@ export default function CurrencyLogo({
|
||||
const uriLocations = useHttpLocations(currency instanceof WrappedTokenInfo ? currency.logoURI : undefined)
|
||||
|
||||
const srcs: string[] = useMemo(() => {
|
||||
if (currency === ETHER) return []
|
||||
if (!currency || currency.isEther) return []
|
||||
|
||||
if (currency instanceof Token) {
|
||||
if (currency.isToken) {
|
||||
const defaultUrls = currency.chainId === ChainId.MAINNET ? [getTokenLogoURL(currency.address)] : []
|
||||
if (currency instanceof WrappedTokenInfo) {
|
||||
return [...uriLocations, getTokenLogoURL(currency.address)]
|
||||
return [...uriLocations, ...defaultUrls]
|
||||
}
|
||||
return [getTokenLogoURL(currency.address)]
|
||||
return defaultUrls
|
||||
}
|
||||
return []
|
||||
}, [currency, uriLocations])
|
||||
|
||||
if (currency === ETHER) {
|
||||
return <StyledEthereumLogo src={EthereumLogo} size={size} style={style} />
|
||||
if (currency?.isEther) {
|
||||
return <StyledEthereumLogo src={EthereumLogo} size={size} style={style} {...rest} />
|
||||
}
|
||||
|
||||
return <StyledLogo size={size} srcs={srcs} alt={`${currency?.symbol ?? 'token'} logo`} style={style} />
|
||||
return <StyledLogo size={size} srcs={srcs} alt={`${currency?.symbol ?? 'token'} logo`} style={style} {...rest} />
|
||||
}
|
||||
|
||||
18
src/components/DoubleLogo/DoubleCurrencyLogo.stories.tsx
Normal file
18
src/components/DoubleLogo/DoubleCurrencyLogo.stories.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Story } from '@storybook/react/types-6-0'
|
||||
import React from 'react'
|
||||
import { DAI, WBTC } from '../../constants'
|
||||
import Component, { DoubleCurrencyLogoProps } from './index'
|
||||
|
||||
export default {
|
||||
title: 'DoubleCurrencyLogo',
|
||||
decorators: [],
|
||||
}
|
||||
|
||||
const Template: Story<DoubleCurrencyLogoProps> = (args) => <Component {...args} />
|
||||
|
||||
export const DoubleCurrencyLogo = Template.bind({})
|
||||
DoubleCurrencyLogo.args = {
|
||||
currency0: DAI,
|
||||
currency1: WBTC,
|
||||
size: 220,
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Currency } from '@uniswap/sdk'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
|
||||
const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>`
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-right: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'};
|
||||
margin-left: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'};
|
||||
`
|
||||
|
||||
interface DoubleCurrencyLogoProps {
|
||||
export interface DoubleCurrencyLogoProps {
|
||||
margin?: boolean
|
||||
size?: number
|
||||
currency0?: Currency
|
||||
@@ -29,7 +29,7 @@ export default function DoubleCurrencyLogo({
|
||||
currency0,
|
||||
currency1,
|
||||
size = 16,
|
||||
margin = false
|
||||
margin = false,
|
||||
}: DoubleCurrencyLogoProps) {
|
||||
return (
|
||||
<Wrapper sizeraw={size} margin={margin}>
|
||||
|
||||
182
src/components/ErrorBoundary/index.tsx
Normal file
182
src/components/ErrorBoundary/index.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import React, { ErrorInfo } from 'react'
|
||||
import store, { AppState } from '../../state'
|
||||
import { ExternalLink, ThemedBackground, TYPE } from '../../theme'
|
||||
import { AutoColumn } from '../Column'
|
||||
import styled from 'styled-components/macro'
|
||||
import ReactGA from 'react-ga'
|
||||
import { getUserAgent } from '../../utils/getUserAgent'
|
||||
import { AutoRow } from '../Row'
|
||||
|
||||
const FallbackWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
`
|
||||
|
||||
const BodyWrapper = styled.div<{ margin?: string }>`
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
white-space: ;
|
||||
`
|
||||
|
||||
const CodeBlockWrapper = styled.div`
|
||||
background: ${({ theme }) => theme.bg0};
|
||||
overflow: auto;
|
||||
white-space: pre;
|
||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
|
||||
0px 24px 32px rgba(0, 0, 0, 0.01);
|
||||
border-radius: 24px;
|
||||
padding: 18px 24px;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
`
|
||||
|
||||
const LinkWrapper = styled.div`
|
||||
color: ${({ theme }) => theme.blue1};
|
||||
padding: 6px 24px;
|
||||
`
|
||||
|
||||
const SomethingWentWrongWrapper = styled.div`
|
||||
padding: 6px 24px;
|
||||
`
|
||||
|
||||
type ErrorBoundaryState = {
|
||||
error: Error | null
|
||||
}
|
||||
|
||||
export default class ErrorBoundary extends React.Component<unknown, ErrorBoundaryState> {
|
||||
constructor(props: unknown) {
|
||||
super(props)
|
||||
this.state = { error: null }
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
return { error }
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
ReactGA.exception({
|
||||
...error,
|
||||
...errorInfo,
|
||||
fatal: true,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { error } = this.state
|
||||
if (error !== null) {
|
||||
const encodedBody = encodeURIComponent(issueBody(error))
|
||||
return (
|
||||
<FallbackWrapper>
|
||||
<ThemedBackground />
|
||||
<BodyWrapper>
|
||||
<AutoColumn gap={'md'}>
|
||||
<SomethingWentWrongWrapper>
|
||||
<TYPE.label fontSize={24} fontWeight={600}>
|
||||
Something went wrong
|
||||
</TYPE.label>
|
||||
</SomethingWentWrongWrapper>
|
||||
<CodeBlockWrapper>
|
||||
<code>
|
||||
<TYPE.main fontSize={10}>{error.stack}</TYPE.main>
|
||||
</code>
|
||||
</CodeBlockWrapper>
|
||||
<AutoRow>
|
||||
<LinkWrapper>
|
||||
<ExternalLink
|
||||
id="create-github-issue-link"
|
||||
href={`https://github.com/Uniswap/uniswap-interface/issues/new?assignees=&labels=bug&body=${encodedBody}&title=${encodeURIComponent(
|
||||
`Crash report: \`${error.name}${error.message && `: ${error.message}`}\``
|
||||
)}`}
|
||||
target="_blank"
|
||||
>
|
||||
<TYPE.link fontSize={16}>
|
||||
Create an issue on GitHub
|
||||
<span>↗</span>
|
||||
</TYPE.link>
|
||||
</ExternalLink>
|
||||
</LinkWrapper>
|
||||
<LinkWrapper>
|
||||
<ExternalLink id="get-support-on-discord" href="https://discord.gg/FCfyBSbCU5" target="_blank">
|
||||
<TYPE.link fontSize={16}>
|
||||
Get support on Discord
|
||||
<span>↗</span>
|
||||
</TYPE.link>
|
||||
</ExternalLink>
|
||||
</LinkWrapper>
|
||||
</AutoRow>
|
||||
</AutoColumn>
|
||||
</BodyWrapper>
|
||||
</FallbackWrapper>
|
||||
)
|
||||
}
|
||||
return this.props.children
|
||||
}
|
||||
}
|
||||
|
||||
function getRelevantState(): null | keyof AppState {
|
||||
const path = window.location.hash
|
||||
if (!path.startsWith('#/')) {
|
||||
return null
|
||||
}
|
||||
const pieces = path.substring(2).split(/[\/\\?]/)
|
||||
switch (pieces[0]) {
|
||||
case 'swap':
|
||||
return 'swap'
|
||||
case 'add':
|
||||
if (pieces[1] === 'v2') return 'mint'
|
||||
else return 'mintV3'
|
||||
case 'remove':
|
||||
if (pieces[1] === 'v2') return 'burn'
|
||||
else return 'burnV3'
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function issueBody(error: Error): string {
|
||||
const relevantState = getRelevantState()
|
||||
const deviceData = getUserAgent()
|
||||
return `## URL
|
||||
|
||||
${window.location.href}
|
||||
|
||||
${
|
||||
relevantState
|
||||
? `## \`${relevantState}\` state
|
||||
|
||||
\`\`\`json
|
||||
${JSON.stringify(store.getState()[relevantState], null, 2)}
|
||||
\`\`\`
|
||||
`
|
||||
: ''
|
||||
}
|
||||
${
|
||||
error.name &&
|
||||
`## Error
|
||||
|
||||
\`\`\`
|
||||
${error.name}${error.message && `: ${error.message}`}
|
||||
\`\`\`
|
||||
`
|
||||
}
|
||||
${
|
||||
error.stack &&
|
||||
`## Stacktrace
|
||||
|
||||
\`\`\`
|
||||
${error.stack}
|
||||
\`\`\`
|
||||
`
|
||||
}
|
||||
${
|
||||
deviceData &&
|
||||
`## Device data
|
||||
|
||||
\`\`\`json
|
||||
${JSON.stringify(deviceData, null, 2)}
|
||||
\`\`\`
|
||||
`
|
||||
}
|
||||
`
|
||||
}
|
||||
76
src/components/FeeSelector/index.tsx
Normal file
76
src/components/FeeSelector/index.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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'
|
||||
|
||||
const ResponsiveText = styled(TYPE.label)`
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
font-size: 12px;
|
||||
`};
|
||||
`
|
||||
|
||||
export default function FeeSelector({
|
||||
disabled = false,
|
||||
feeAmount,
|
||||
handleFeePoolSelect,
|
||||
}: {
|
||||
disabled?: boolean
|
||||
feeAmount?: FeeAmount
|
||||
handleFeePoolSelect: (feeAmount: FeeAmount) => void
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<AutoColumn gap="16px">
|
||||
<DynamicSection gap="md" disabled={disabled}>
|
||||
<TYPE.label>{t('selectPool')}</TYPE.label>
|
||||
<TYPE.main fontSize={14} fontWeight={400} style={{ marginBottom: '.5rem', lineHeight: '125%' }}>
|
||||
Select a pool type based on your preferred liquidity provider fee.
|
||||
</TYPE.main>
|
||||
<RowBetween>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.LOW}
|
||||
onClick={() => handleFeePoolSelect(FeeAmount.LOW)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<ResponsiveText>0.05% {t('fee')}</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
Best for stable pairs.
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.MEDIUM}
|
||||
onClick={() => handleFeePoolSelect(FeeAmount.MEDIUM)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<ResponsiveText>0.3% {t('fee')}</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
Best for most pairs.
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
<ButtonRadioChecked
|
||||
width="32%"
|
||||
active={feeAmount === FeeAmount.HIGH}
|
||||
onClick={() => handleFeePoolSelect(FeeAmount.HIGH)}
|
||||
>
|
||||
<AutoColumn gap="sm" justify="flex-start">
|
||||
<ResponsiveText>1% {t('fee')}</ResponsiveText>
|
||||
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
|
||||
Best for exotic pairs.
|
||||
</TYPE.main>
|
||||
</AutoColumn>
|
||||
</ButtonRadioChecked>
|
||||
</RowBetween>
|
||||
</DynamicSection>
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import JSBI from 'jsbi'
|
||||
import React from 'react'
|
||||
import { CurrencyAmount, Fraction, JSBI } from '@uniswap/sdk'
|
||||
import { Currency, CurrencyAmount, Fraction } from '@uniswap/sdk-core'
|
||||
|
||||
const CURRENCY_AMOUNT_MIN = new Fraction(JSBI.BigInt(1), JSBI.BigInt(1000000))
|
||||
|
||||
export default function FormattedCurrencyAmount({
|
||||
currencyAmount,
|
||||
significantDigits = 4
|
||||
significantDigits = 4,
|
||||
}: {
|
||||
currencyAmount: CurrencyAmount
|
||||
currencyAmount: CurrencyAmount<Currency>
|
||||
significantDigits?: number
|
||||
}) {
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { AlertTriangle, X } from 'react-feather'
|
||||
import { useURLWarningToggle, useURLWarningVisible } from '../../state/user/hooks'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { ChainId, TokenAmount } from '@uniswap/sdk'
|
||||
import { ChainId, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import React, { useMemo } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import tokenLogo from '../../assets/images/token-logo.png'
|
||||
import { UNI } from '../../constants'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useMerkleDistributorContract } from '../../hooks/useContract'
|
||||
import useCurrentBlockTimestamp from '../../hooks/useCurrentBlockTimestamp'
|
||||
@@ -12,7 +12,7 @@ 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 '../../utils/useUSDCPrice'
|
||||
import useUSDCPrice from '../../hooks/useUSDCPrice'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { RowBetween } from '../Row'
|
||||
import { Break, CardBGImage, CardNoise, CardSection, DataCard } from '../earn/styled'
|
||||
@@ -45,14 +45,14 @@ export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowU
|
||||
const uni = chainId ? UNI[chainId] : undefined
|
||||
|
||||
const total = useAggregateUniBalance()
|
||||
const uniBalance: TokenAmount | undefined = useTokenBalance(account ?? undefined, uni)
|
||||
const uniToClaim: TokenAmount | undefined = useTotalUniEarned()
|
||||
const uniBalance: CurrencyAmount<Token> | undefined = useTokenBalance(account ?? undefined, uni)
|
||||
const uniToClaim: CurrencyAmount<Token> | undefined = useTotalUniEarned()
|
||||
|
||||
const totalSupply: TokenAmount | undefined = useTotalSupply(uni)
|
||||
const totalSupply: CurrencyAmount<Token> | undefined = useTotalSupply(uni)
|
||||
const uniPrice = useUSDCPrice(uni)
|
||||
const blockTimestamp = useCurrentBlockTimestamp()
|
||||
const unclaimedUni = useTokenBalance(useMerkleDistributorContract()?.address, uni)
|
||||
const circulation: TokenAmount | undefined = useMemo(
|
||||
const circulation: CurrencyAmount<Token> | undefined = useMemo(
|
||||
() =>
|
||||
blockTimestamp && uni && chainId === ChainId.MAINNET
|
||||
? computeUniCirculation(uni, blockTimestamp, unclaimedUni)
|
||||
@@ -117,7 +117,7 @@ export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowU
|
||||
<TYPE.white color="white">{totalSupply?.toFixed(0, { groupSeparator: ',' })}</TYPE.white>
|
||||
</RowBetween>
|
||||
{uni && uni.chainId === ChainId.MAINNET ? (
|
||||
<ExternalLink href={`https://uniswap.info/token/${uni.address}`}>View UNI Analytics</ExternalLink>
|
||||
<ExternalLink href={`https://info.uniswap.org/token/${uni.address}`}>View UNI Analytics</ExternalLink>
|
||||
) : null}
|
||||
</AutoColumn>
|
||||
</CardSection>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { ChainId, TokenAmount } from '@uniswap/sdk'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
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 { useTranslation } from 'react-i18next'
|
||||
|
||||
import styled from 'styled-components'
|
||||
import { Moon, Sun } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import Logo from '../../assets/svg/logo.svg'
|
||||
import LogoDark from '../../assets/svg/logo_white.svg'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useDarkModeManager } from '../../state/user/hooks'
|
||||
import { useETHBalances, useAggregateUniBalance } from '../../state/wallet/hooks'
|
||||
import { useETHBalances } from '../../state/wallet/hooks'
|
||||
import { CardNoise } from '../earn/styled'
|
||||
import { CountUp } from 'use-count-up'
|
||||
import { TYPE, ExternalLink } from '../../theme'
|
||||
|
||||
import { YellowCard } from '../Card'
|
||||
import { Moon, Sun } from 'react-feather'
|
||||
import Menu from '../Menu'
|
||||
|
||||
import Row, { RowFixed } from '../Row'
|
||||
@@ -29,11 +29,10 @@ import { useUserHasSubmittedClaim } from '../../state/transactions/hooks'
|
||||
import { Dots } from '../swap/styleds'
|
||||
import Modal from '../Modal'
|
||||
import UniBalanceContent from './UniBalanceContent'
|
||||
import usePrevious from '../../hooks/usePrevious'
|
||||
|
||||
const HeaderFrame = styled.div`
|
||||
const HeaderFrame = styled.div<{ showBackground: boolean }>`
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 120px;
|
||||
grid-template-columns: 120px 1fr 120px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@@ -41,18 +40,25 @@ const HeaderFrame = styled.div`
|
||||
width: 100%;
|
||||
top: 0;
|
||||
position: relative;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding: 1rem;
|
||||
z-index: 2;
|
||||
z-index: 21;
|
||||
position: relative;
|
||||
|
||||
/* Background slide effect on scroll. */
|
||||
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;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
grid-template-columns: 1fr;
|
||||
padding: 0 1rem;
|
||||
width: calc(100%);
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
grid-template-columns: 120px 1fr;
|
||||
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
padding: 0.5rem 1rem;
|
||||
padding: 1rem;
|
||||
`}
|
||||
`
|
||||
|
||||
@@ -90,7 +96,7 @@ const HeaderElement = styled.div`
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
flex-direction: row-reverse;
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
`};
|
||||
`
|
||||
@@ -107,18 +113,25 @@ const HeaderRow = styled(RowFixed)`
|
||||
`
|
||||
|
||||
const HeaderLinks = styled(Row)`
|
||||
justify-content: center;
|
||||
justify-self: center;
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
width: fit-content;
|
||||
padding: 4px;
|
||||
border-radius: 16px;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-gap: 10px;
|
||||
overflow: auto;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
padding: 1rem 0 1rem 1rem;
|
||||
justify-content: flex-end;
|
||||
`};
|
||||
justify-self: flex-end;
|
||||
`};
|
||||
`
|
||||
|
||||
const AccountElement = styled.div<{ active: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background-color: ${({ theme, active }) => (!active ? theme.bg1 : theme.bg3)};
|
||||
background-color: ${({ theme, active }) => (!active ? theme.bg1 : theme.bg2)};
|
||||
border-radius: 12px;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
@@ -201,7 +214,7 @@ const UniIcon = styled.div`
|
||||
const activeClassName = 'ACTIVE'
|
||||
|
||||
const StyledNavLink = styled(NavLink).attrs({
|
||||
activeClassName
|
||||
activeClassName,
|
||||
})`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: left;
|
||||
@@ -212,13 +225,14 @@ const StyledNavLink = styled(NavLink).attrs({
|
||||
color: ${({ theme }) => theme.text2};
|
||||
font-size: 1rem;
|
||||
width: fit-content;
|
||||
margin: 0 12px;
|
||||
font-weight: 500;
|
||||
padding: 8px 12px;
|
||||
|
||||
&.${activeClassName} {
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
}
|
||||
|
||||
:hover,
|
||||
@@ -228,7 +242,7 @@ const StyledNavLink = styled(NavLink).attrs({
|
||||
`
|
||||
|
||||
const StyledExternalLink = styled(ExternalLink).attrs({
|
||||
activeClassName
|
||||
activeClassName,
|
||||
})<{ isActive?: boolean }>`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: left;
|
||||
@@ -267,7 +281,7 @@ export const StyledMenuButton = styled.button`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 35px;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
margin-left: 8px;
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
@@ -291,7 +305,7 @@ const NETWORK_LABELS: { [chainId in ChainId]?: string } = {
|
||||
[ChainId.RINKEBY]: 'Rinkeby',
|
||||
[ChainId.ROPSTEN]: 'Ropsten',
|
||||
[ChainId.GÖRLI]: 'Görli',
|
||||
[ChainId.KOVAN]: 'Kovan'
|
||||
[ChainId.KOVAN]: 'Kovan',
|
||||
}
|
||||
|
||||
export default function Header() {
|
||||
@@ -308,16 +322,13 @@ export default function Header() {
|
||||
|
||||
const { claimTxn } = useUserHasSubmittedClaim(account ?? undefined)
|
||||
|
||||
const aggregateBalance: TokenAmount | undefined = useAggregateUniBalance()
|
||||
|
||||
const [showUniBalanceModal, setShowUniBalanceModal] = useState(false)
|
||||
const showClaimPopup = useShowClaimPopup()
|
||||
|
||||
const countUpValue = aggregateBalance?.toFixed(0) ?? '0'
|
||||
const countUpValuePrevious = usePrevious(countUpValue) ?? '0'
|
||||
const scrollY = useScrollPosition()
|
||||
|
||||
return (
|
||||
<HeaderFrame>
|
||||
<HeaderFrame showBackground={scrollY > 45}>
|
||||
<ClaimModal />
|
||||
<Modal isOpen={showUniBalanceModal} onDismiss={() => setShowUniBalanceModal(false)}>
|
||||
<UniBalanceContent setShowUniBalanceModal={setShowUniBalanceModal} />
|
||||
@@ -328,34 +339,31 @@ export default function Header() {
|
||||
<img width={'24px'} src={darkMode ? LogoDark : Logo} alt="logo" />
|
||||
</UniIcon>
|
||||
</Title>
|
||||
<HeaderLinks>
|
||||
<StyledNavLink id={`swap-nav-link`} to={'/swap'}>
|
||||
{t('swap')}
|
||||
</StyledNavLink>
|
||||
<StyledNavLink
|
||||
id={`pool-nav-link`}
|
||||
to={'/pool'}
|
||||
isActive={(match, { pathname }) =>
|
||||
Boolean(match) ||
|
||||
pathname.startsWith('/add') ||
|
||||
pathname.startsWith('/remove') ||
|
||||
pathname.startsWith('/create') ||
|
||||
pathname.startsWith('/find')
|
||||
}
|
||||
>
|
||||
{t('pool')}
|
||||
</StyledNavLink>
|
||||
<StyledNavLink id={`stake-nav-link`} to={'/uni'}>
|
||||
UNI
|
||||
</StyledNavLink>
|
||||
<StyledNavLink id={`stake-nav-link`} to={'/vote'}>
|
||||
Vote
|
||||
</StyledNavLink>
|
||||
<StyledExternalLink id={`stake-nav-link`} href={'https://uniswap.info'}>
|
||||
Charts <span style={{ fontSize: '11px' }}>↗</span>
|
||||
</StyledExternalLink>
|
||||
</HeaderLinks>
|
||||
</HeaderRow>
|
||||
<HeaderLinks>
|
||||
<StyledNavLink id={`swap-nav-link`} to={'/swap'}>
|
||||
{t('swap')}
|
||||
</StyledNavLink>
|
||||
<StyledNavLink
|
||||
id={`pool-nav-link`}
|
||||
to={'/pool'}
|
||||
isActive={(match, { pathname }) =>
|
||||
Boolean(match) ||
|
||||
pathname.startsWith('/add') ||
|
||||
pathname.startsWith('/remove') ||
|
||||
pathname.startsWith('/increase') ||
|
||||
pathname.startsWith('/find')
|
||||
}
|
||||
>
|
||||
{t('pool')}
|
||||
</StyledNavLink>
|
||||
<StyledNavLink id={`stake-nav-link`} to={'/vote'}>
|
||||
Vote
|
||||
</StyledNavLink>
|
||||
<StyledExternalLink id={`stake-nav-link`} href={'https://info.uniswap.org'}>
|
||||
Charts <span style={{ fontSize: '11px', textDecoration: 'none !important' }}>↗</span>
|
||||
</StyledExternalLink>
|
||||
</HeaderLinks>
|
||||
<HeaderControls>
|
||||
<HeaderElement>
|
||||
<HideSmall>
|
||||
@@ -373,32 +381,6 @@ export default function Header() {
|
||||
<CardNoise />
|
||||
</UNIWrapper>
|
||||
)}
|
||||
{!availableClaim && aggregateBalance && (
|
||||
<UNIWrapper onClick={() => setShowUniBalanceModal(true)}>
|
||||
<UNIAmount active={!!account && !availableClaim} style={{ pointerEvents: 'auto' }}>
|
||||
{account && (
|
||||
<HideSmall>
|
||||
<TYPE.white
|
||||
style={{
|
||||
paddingRight: '.4rem'
|
||||
}}
|
||||
>
|
||||
<CountUp
|
||||
key={countUpValue}
|
||||
isCounting
|
||||
start={parseFloat(countUpValuePrevious)}
|
||||
end={parseFloat(countUpValue)}
|
||||
thousandsSeparator={','}
|
||||
duration={1}
|
||||
/>
|
||||
</TYPE.white>
|
||||
</HideSmall>
|
||||
)}
|
||||
UNI
|
||||
</UNIAmount>
|
||||
<CardNoise />
|
||||
</UNIWrapper>
|
||||
)}
|
||||
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
|
||||
{account && userEthBalance ? (
|
||||
<BalanceText style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
|
||||
|
||||
63
src/components/HoverInlineText/index.tsx
Normal file
63
src/components/HoverInlineText/index.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import React, { useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const TextWrapper = styled.span<{ margin: boolean; link?: boolean; fontSize?: string; adjustSize?: boolean }>`
|
||||
cursor: auto;
|
||||
margin-left: ${({ margin }) => margin && '4px'};
|
||||
color: ${({ theme, link }) => (link ? theme.blue1 : theme.text1)};
|
||||
font-size: ${({ fontSize }) => fontSize ?? 'inherit'};
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
font-size: ${({ adjustSize }) => adjustSize && '12px'};
|
||||
}
|
||||
`
|
||||
|
||||
const HoverInlineText = ({
|
||||
text,
|
||||
maxCharacters = 20,
|
||||
margin = false,
|
||||
adjustSize = false,
|
||||
fontSize,
|
||||
link,
|
||||
...rest
|
||||
}: {
|
||||
text: string
|
||||
maxCharacters?: number
|
||||
margin?: boolean
|
||||
adjustSize?: boolean
|
||||
fontSize?: string
|
||||
link?: boolean
|
||||
}) => {
|
||||
const [showHover, setShowHover] = useState(false)
|
||||
|
||||
if (!text) {
|
||||
return <span />
|
||||
}
|
||||
|
||||
if (text.length > maxCharacters) {
|
||||
return (
|
||||
<Tooltip text={text} show={showHover}>
|
||||
<TextWrapper
|
||||
onMouseEnter={() => setShowHover(true)}
|
||||
onMouseLeave={() => setShowHover(false)}
|
||||
margin={margin}
|
||||
adjustSize={adjustSize}
|
||||
link={link}
|
||||
fontSize={fontSize}
|
||||
{...rest}
|
||||
>
|
||||
{' ' + text.slice(0, maxCharacters - 1) + '...'}
|
||||
</TextWrapper>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TextWrapper margin={margin} adjustSize={adjustSize} link={link} fontSize={fontSize} {...rest}>
|
||||
{text}
|
||||
</TextWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default HoverInlineText
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import Jazzicon from 'jazzicon'
|
||||
|
||||
159
src/components/InputStepCounter/InputStepCounter.tsx
Normal file
159
src/components/InputStepCounter/InputStepCounter.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import React, { useState, useCallback, useEffect } from 'react'
|
||||
import { LightCard } from 'components/Card'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { Input as NumericalInput } from '../NumericalInput'
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
import { TYPE } from 'theme'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { formattedFeeAmount } from 'utils'
|
||||
|
||||
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 SmallButton = styled(ButtonPrimary)`
|
||||
/* background-color: ${({ theme }) => theme.bg2}; */
|
||||
border-radius: 8px;
|
||||
padding: 4px 6px;
|
||||
width: 48%;
|
||||
`
|
||||
|
||||
const FocusedOutlineCard = styled(LightCard)<{ 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}; */
|
||||
text-align: center;
|
||||
margin-right: 12px;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
const InputTitle = styled(TYPE.small)`
|
||||
color: ${({ theme }) => theme.text2};
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
interface StepCounterProps {
|
||||
value: string
|
||||
onUserInput: (value: string) => void
|
||||
decrement: () => string
|
||||
increment: () => string
|
||||
feeAmount?: FeeAmount
|
||||
label?: string
|
||||
width?: string
|
||||
locked?: boolean // disable input
|
||||
title: string
|
||||
tokenA: string | undefined
|
||||
tokenB: string | undefined
|
||||
}
|
||||
|
||||
const StepCounter = ({
|
||||
value,
|
||||
decrement,
|
||||
increment,
|
||||
feeAmount,
|
||||
width,
|
||||
locked,
|
||||
onUserInput,
|
||||
title,
|
||||
tokenA,
|
||||
tokenB,
|
||||
}: StepCounterProps) => {
|
||||
// for focus state, styled components doesnt let you select input parent container
|
||||
const [active, setActive] = useState(false)
|
||||
|
||||
// let user type value and only update parent value on blur
|
||||
const [localValue, setLocalValue] = useState('')
|
||||
const [useLocalValue, setUseLocalValue] = useState(false)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
const handleOnBlur = useCallback(() => {
|
||||
setUseLocalValue(false)
|
||||
setActive(false)
|
||||
onUserInput(localValue) // trigger update on parent value
|
||||
}, [localValue, onUserInput])
|
||||
|
||||
// for button clicks
|
||||
const handleDecrement = useCallback(() => {
|
||||
setUseLocalValue(false)
|
||||
onUserInput(decrement())
|
||||
}, [decrement, onUserInput])
|
||||
|
||||
const handleIncrement = useCallback(() => {
|
||||
setUseLocalValue(false)
|
||||
onUserInput(increment())
|
||||
}, [increment, onUserInput])
|
||||
|
||||
useEffect(() => {
|
||||
if (localValue !== value && !useLocalValue) {
|
||||
setTimeout(() => {
|
||||
setLocalValue(value) // reset local value to match parent
|
||||
setPulsing(true) // trigger animation
|
||||
setTimeout(function () {
|
||||
setPulsing(false)
|
||||
}, 1800)
|
||||
}, 0)
|
||||
}
|
||||
}, [localValue, useLocalValue, value])
|
||||
|
||||
return (
|
||||
<FocusedOutlineCard pulsing={pulsing} active={active} onFocus={handleOnFocus} onBlur={handleOnBlur} width={width}>
|
||||
<AutoColumn gap="6px" style={{ marginBottom: '12px' }}>
|
||||
<InputTitle fontSize={12} textAlign="center">
|
||||
{title}
|
||||
</InputTitle>
|
||||
<StyledInput
|
||||
className="rate-input-0"
|
||||
value={localValue}
|
||||
fontSize="20px"
|
||||
disabled={locked}
|
||||
onUserInput={(val) => {
|
||||
setLocalValue(val)
|
||||
}}
|
||||
/>
|
||||
<InputTitle fontSize={12} textAlign="center">
|
||||
{tokenB + ' per ' + tokenA}
|
||||
</InputTitle>
|
||||
</AutoColumn>
|
||||
{!locked ? (
|
||||
<RowBetween>
|
||||
<SmallButton onClick={handleDecrement}>
|
||||
<TYPE.white fontSize="12px">-{feeAmountFormatted}%</TYPE.white>
|
||||
</SmallButton>
|
||||
<SmallButton onClick={handleIncrement}>
|
||||
<TYPE.white fontSize="12px">+{feeAmountFormatted}%</TYPE.white>
|
||||
</SmallButton>
|
||||
</RowBetween>
|
||||
) : null}
|
||||
</FocusedOutlineCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default StepCounter
|
||||
90
src/components/LineChart/LineChart.stories.tsx
Normal file
90
src/components/LineChart/LineChart.stories.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Story } from '@storybook/react/types-6-0'
|
||||
import React from 'react'
|
||||
// import Row, { RowFixed } from 'components/Row'
|
||||
import styled from 'styled-components/macro'
|
||||
import Component, { LineChartProps } from './'
|
||||
import { dummyData } from './data'
|
||||
// import { AutoColumn } from 'components/Column'
|
||||
// import { TYPE } from 'theme'
|
||||
// import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
// import { MKR } from 'constants'
|
||||
// import { ETHER } from '@uniswap/sdk'
|
||||
// import LineChart from '.'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
margin: 1em 2em;
|
||||
max-width: 500px;
|
||||
& > * {
|
||||
font-size: 1em;
|
||||
}
|
||||
`
|
||||
|
||||
export default {
|
||||
title: 'Charts',
|
||||
argTypes: {
|
||||
disabled: { control: { type: 'boolean' } },
|
||||
onClick: { action: 'clicked' },
|
||||
},
|
||||
decorators: [
|
||||
(Component: Story) => (
|
||||
<Wrapper>
|
||||
<Component />
|
||||
</Wrapper>
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
const Template: Story<LineChartProps> = (args) => <Component {...args}>{args.children}</Component>
|
||||
|
||||
export const Basic = Template.bind({})
|
||||
Basic.args = { data: dummyData }
|
||||
|
||||
// const Full = () => {
|
||||
// const [value, setValue] = useState<number | undefined>(dummyData[dummyData.length - 1].value)
|
||||
|
||||
// const dummyUSDPrice = 410 // used for conversion
|
||||
|
||||
// const TopLeftContent = () => (
|
||||
// <AutoColumn gap="md">
|
||||
// <RowFixed align="center">
|
||||
// <DoubleCurrencyLogo currency0={MKR} currency1={ETHER} size={20} />{' '}
|
||||
// <TYPE.main fontSize="20px" color="white" ml="10px">
|
||||
// ETH / MKR
|
||||
// </TYPE.main>
|
||||
// </RowFixed>
|
||||
// <Row>
|
||||
// <TYPE.main fontSize="20px" color="white">
|
||||
// {value} MKR
|
||||
// </TYPE.main>
|
||||
// <TYPE.main color="#565A69" fontSize="20px" ml="10px">
|
||||
// ($
|
||||
// {value
|
||||
// ? (value * dummyUSDPrice).toLocaleString('USD', {
|
||||
// currency: 'USD',
|
||||
// minimumFractionDigits: 2
|
||||
// })
|
||||
// : null}{' '}
|
||||
// )
|
||||
// </TYPE.main>
|
||||
// </Row>
|
||||
// </AutoColumn>
|
||||
// )
|
||||
|
||||
// return (
|
||||
// <Wrapper>
|
||||
// <LineChart data={dummyData} setValue={setValue} topLeft={<TopLeftContent />} />
|
||||
// </Wrapper>
|
||||
// )
|
||||
// }
|
||||
|
||||
// export const FullVersion = Template.bind(() => <LineChart data={dummyData} />)
|
||||
// Full.args = { data: dummyData }
|
||||
// Full.decorators = [
|
||||
// (Story: any) => {
|
||||
// return (
|
||||
// <Wrapper>
|
||||
// <LineChart data={dummyData} />
|
||||
// </Wrapper>
|
||||
// )
|
||||
// }
|
||||
// ]
|
||||
152
src/components/LineChart/data.ts
Normal file
152
src/components/LineChart/data.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
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 },
|
||||
]
|
||||
169
src/components/LineChart/index.tsx
Normal file
169
src/components/LineChart/index.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
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
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import useHttpLocations from '../../hooks/useHttpLocations'
|
||||
|
||||
import Logo from '../Logo'
|
||||
@@ -13,7 +13,7 @@ export default function ListLogo({
|
||||
logoURI,
|
||||
style,
|
||||
size = '24px',
|
||||
alt
|
||||
alt,
|
||||
}: {
|
||||
logoURI: string
|
||||
size?: string
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react'
|
||||
import { HelpCircle } from 'react-feather'
|
||||
import { Slash } from 'react-feather'
|
||||
import { ImageProps } from 'rebass'
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
|
||||
const BAD_SRCS: { [tokenAddress: string]: true } = {}
|
||||
|
||||
@@ -11,10 +12,12 @@ export interface LogoProps extends Pick<ImageProps, 'style' | 'alt' | 'className
|
||||
/**
|
||||
* Renders an image by sequentially trying a list of URIs, and then eventually a fallback triangle alert
|
||||
*/
|
||||
export default function Logo({ srcs, alt, ...rest }: LogoProps) {
|
||||
export default function Logo({ srcs, alt, style, ...rest }: LogoProps) {
|
||||
const [, refresh] = useState<number>(0)
|
||||
|
||||
const src: string | undefined = srcs.find(src => !BAD_SRCS[src])
|
||||
const theme = useTheme()
|
||||
|
||||
const src: string | undefined = srcs.find((src) => !BAD_SRCS[src])
|
||||
|
||||
if (src) {
|
||||
return (
|
||||
@@ -22,13 +25,14 @@ export default function Logo({ srcs, alt, ...rest }: LogoProps) {
|
||||
{...rest}
|
||||
alt={alt}
|
||||
src={src}
|
||||
style={style}
|
||||
onError={() => {
|
||||
if (src) BAD_SRCS[src] = true
|
||||
refresh(i => i + 1)
|
||||
refresh((i) => i + 1)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return <HelpCircle {...rest} />
|
||||
return <Slash {...rest} style={{ ...style, color: theme.bg4 }} />
|
||||
}
|
||||
|
||||
23
src/components/Menu/Menu.stories.tsx
Normal file
23
src/components/Menu/Menu.stories.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Story } from '@storybook/react/types-6-0'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import Component from './index'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
max-width: 150px;
|
||||
`
|
||||
export default {
|
||||
title: 'Menu',
|
||||
decorators: [
|
||||
() => (
|
||||
<Wrapper>
|
||||
<Component />
|
||||
</Wrapper>
|
||||
),
|
||||
],
|
||||
}
|
||||
|
||||
const Template: Story<any> = (args) => <Component {...args} />
|
||||
|
||||
export const Menu = Template.bind({})
|
||||
Menu.args = {}
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useRef } from 'react'
|
||||
import { BookOpen, Code, Info, MessageCircle, PieChart } from 'react-feather'
|
||||
import styled from 'styled-components'
|
||||
import { Link } from 'react-router-dom'
|
||||
import styled, { css } from 'styled-components'
|
||||
import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
@@ -10,6 +11,11 @@ import { useModalOpen, useToggleModal } from '../../state/application/hooks'
|
||||
import { ExternalLink } from '../../theme'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
|
||||
export enum FlyoutAlignment {
|
||||
LEFT = 'LEFT',
|
||||
RIGHT = 'RIGHT',
|
||||
}
|
||||
|
||||
const StyledMenuIcon = styled(MenuIcon)`
|
||||
path {
|
||||
stroke: ${({ theme }) => theme.text1};
|
||||
@@ -24,7 +30,7 @@ const StyledMenuButton = styled.button`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 35px;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
@@ -33,7 +39,7 @@ const StyledMenuButton = styled.button`
|
||||
:focus {
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
background-color: ${({ theme }) => theme.bg4};
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
}
|
||||
|
||||
svg {
|
||||
@@ -41,6 +47,12 @@ const StyledMenuButton = styled.button`
|
||||
}
|
||||
`
|
||||
|
||||
const UNIbutton = styled(ButtonPrimary)`
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
background: radial-gradient(174.47% 188.91% at 1.84% 0%, #ff007a 0%, #2172e5 100%), #edeef2;
|
||||
border: none;
|
||||
`
|
||||
|
||||
const StyledMenu = styled.div`
|
||||
margin-left: 0.5rem;
|
||||
display: flex;
|
||||
@@ -51,9 +63,9 @@ const StyledMenu = styled.div`
|
||||
text-align: left;
|
||||
`
|
||||
|
||||
const MenuFlyout = styled.span`
|
||||
const MenuFlyout = styled.span<{ flyoutAlignment?: FlyoutAlignment }>`
|
||||
min-width: 8.125rem;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
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;
|
||||
@@ -62,16 +74,39 @@ const MenuFlyout = styled.span`
|
||||
flex-direction: column;
|
||||
font-size: 1rem;
|
||||
position: absolute;
|
||||
top: 4rem;
|
||||
right: 0rem;
|
||||
top: 3rem;
|
||||
z-index: 100;
|
||||
|
||||
${({ flyoutAlignment = FlyoutAlignment.RIGHT }) =>
|
||||
flyoutAlignment === FlyoutAlignment.RIGHT
|
||||
? css`
|
||||
right: 0rem;
|
||||
`
|
||||
: css`
|
||||
left: 0rem;
|
||||
`};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
top: -17.25rem;
|
||||
`};
|
||||
`
|
||||
|
||||
const MenuItem = styled(ExternalLink)`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.5rem;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.text1};
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
> svg {
|
||||
margin-right: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
const InternalMenuItem = styled(Link)`
|
||||
flex: 1;
|
||||
padding: 0.5rem 0.5rem;
|
||||
color: ${({ theme }) => theme.text2};
|
||||
@@ -105,33 +140,84 @@ export default function Menu() {
|
||||
|
||||
{open && (
|
||||
<MenuFlyout>
|
||||
<MenuItem id="link" href="https://uniswap.org/">
|
||||
<MenuItem href="https://uniswap.org/">
|
||||
<Info size={14} />
|
||||
About
|
||||
<div>About</div>
|
||||
</MenuItem>
|
||||
<MenuItem id="link" href="https://uniswap.org/docs/v2">
|
||||
<MenuItem href="https://docs.uniswap.org/">
|
||||
<BookOpen size={14} />
|
||||
Docs
|
||||
<div>Docs</div>
|
||||
</MenuItem>
|
||||
<MenuItem id="link" href={CODE_LINK}>
|
||||
<MenuItem href={CODE_LINK}>
|
||||
<Code size={14} />
|
||||
Code
|
||||
<div>Code</div>
|
||||
</MenuItem>
|
||||
<MenuItem id="link" href="https://discord.gg/FCfyBSbCU5">
|
||||
<MenuItem href="https://discord.gg/FCfyBSbCU5">
|
||||
<MessageCircle size={14} />
|
||||
Discord
|
||||
<div>Discord</div>
|
||||
</MenuItem>
|
||||
<MenuItem id="link" href="https://uniswap.info/">
|
||||
<MenuItem href="https://info.uniswap.org/">
|
||||
<PieChart size={14} />
|
||||
Analytics
|
||||
<div>Analytics</div>
|
||||
</MenuItem>
|
||||
{account && (
|
||||
<ButtonPrimary onClick={openClaimModal} padding="8px 16px" width="100%" borderRadius="12px" mt="0.5rem">
|
||||
<UNIbutton onClick={openClaimModal} padding="8px 16px" width="100%" borderRadius="12px" mt="0.5rem">
|
||||
Claim UNI
|
||||
</ButtonPrimary>
|
||||
</UNIbutton>
|
||||
)}
|
||||
</MenuFlyout>
|
||||
)}
|
||||
</StyledMenu>
|
||||
)
|
||||
}
|
||||
|
||||
interface NewMenuProps {
|
||||
flyoutAlignment?: FlyoutAlignment
|
||||
ToggleUI?: React.FunctionComponent
|
||||
menuItems: {
|
||||
content: any
|
||||
link: string
|
||||
external: boolean
|
||||
}[]
|
||||
}
|
||||
|
||||
const NewMenuFlyout = styled(MenuFlyout)`
|
||||
top: 3rem !important;
|
||||
`
|
||||
const NewMenuItem = styled(InternalMenuItem)`
|
||||
width: max-content;
|
||||
text-decoration: none;
|
||||
`
|
||||
|
||||
const ExternalMenuItem = styled(MenuItem)`
|
||||
width: max-content;
|
||||
text-decoration: none;
|
||||
`
|
||||
|
||||
export const NewMenu = ({ flyoutAlignment = FlyoutAlignment.RIGHT, ToggleUI, menuItems, ...rest }: NewMenuProps) => {
|
||||
const node = useRef<HTMLDivElement>()
|
||||
const open = useModalOpen(ApplicationModal.POOL_OVERVIEW_OPTIONS)
|
||||
const toggle = useToggleModal(ApplicationModal.POOL_OVERVIEW_OPTIONS)
|
||||
useOnClickOutside(node, open ? toggle : undefined)
|
||||
const ToggleElement = ToggleUI || StyledMenuIcon
|
||||
return (
|
||||
<StyledMenu ref={node as any} {...rest}>
|
||||
<ToggleElement onClick={toggle} />
|
||||
{open && (
|
||||
<NewMenuFlyout flyoutAlignment={flyoutAlignment}>
|
||||
{menuItems.map(({ content, link, external }, i) =>
|
||||
external ? (
|
||||
<ExternalMenuItem id="link" href={link} key={link + i}>
|
||||
{content}
|
||||
</ExternalMenuItem>
|
||||
) : (
|
||||
<NewMenuItem id="link" to={link} key={link + i}>
|
||||
{content}
|
||||
</NewMenuItem>
|
||||
)
|
||||
)}
|
||||
</NewMenuFlyout>
|
||||
)}
|
||||
</StyledMenu>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,13 +29,14 @@ const AnimatedDialogContent = animated(DialogContent)
|
||||
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...rest }) => (
|
||||
<AnimatedDialogContent {...rest} />
|
||||
)).attrs({
|
||||
'aria-label': 'dialog'
|
||||
'aria-label': 'dialog',
|
||||
})`
|
||||
overflow-y: ${({ mobile }) => (mobile ? 'scroll' : 'hidden')};
|
||||
|
||||
&[data-reach-dialog-content] {
|
||||
margin: 0 0 2rem 0;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
border: 1px solid ${({ theme }) => theme.bg1};
|
||||
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadow1)};
|
||||
padding: 0px;
|
||||
width: 50vw;
|
||||
@@ -63,13 +64,15 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
|
||||
`}
|
||||
${({ theme, mobile }) => theme.mediaWidth.upToSmall`
|
||||
width: 85vw;
|
||||
${mobile &&
|
||||
${
|
||||
mobile &&
|
||||
css`
|
||||
width: 100vw;
|
||||
border-radius: 20px;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
`}
|
||||
`
|
||||
}
|
||||
`}
|
||||
}
|
||||
`
|
||||
@@ -89,25 +92,25 @@ export default function Modal({
|
||||
minHeight = false,
|
||||
maxHeight = 90,
|
||||
initialFocusRef,
|
||||
children
|
||||
children,
|
||||
}: ModalProps) {
|
||||
const fadeTransition = useTransition(isOpen, null, {
|
||||
config: { duration: 200 },
|
||||
from: { opacity: 0 },
|
||||
enter: { opacity: 1 },
|
||||
leave: { opacity: 0 }
|
||||
leave: { opacity: 0 },
|
||||
})
|
||||
|
||||
const [{ y }, set] = useSpring(() => ({ y: 0, config: { mass: 1, tension: 210, friction: 20 } }))
|
||||
const bind = useGesture({
|
||||
onDrag: state => {
|
||||
onDrag: (state) => {
|
||||
set({
|
||||
y: state.down ? state.movement[1] : 0
|
||||
y: state.down ? state.movement[1] : 0,
|
||||
})
|
||||
if (state.movement[1] > 300 || (state.velocity > 3 && state.direction[1] > 0)) {
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -126,7 +129,7 @@ export default function Modal({
|
||||
{...(isMobile
|
||||
? {
|
||||
...bind(),
|
||||
style: { transform: y.interpolate(y => `translateY(${y > 0 ? y : 0}px)`) }
|
||||
style: { transform: y.interpolate((y) => `translateY(${(y as number) > 0 ? y : 0}px)`) },
|
||||
}
|
||||
: {})}
|
||||
aria-label="dialog content"
|
||||
|
||||
@@ -41,7 +41,7 @@ export function LoadingView({ children, onDismiss }: { children: any; onDismiss:
|
||||
export function SubmittedView({
|
||||
children,
|
||||
onDismiss,
|
||||
hash
|
||||
hash,
|
||||
}: {
|
||||
children: any
|
||||
onDismiss: () => void
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import { darken } from 'polished'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { NavLink, Link as HistoryLink } from 'react-router-dom'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import { RowBetween } from '../Row'
|
||||
// import QuestionHelper from '../QuestionHelper'
|
||||
import Settings from '../Settings'
|
||||
import SettingsTab from '../Settings'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { AppDispatch } from 'state'
|
||||
import { resetMintState } from 'state/mint/actions'
|
||||
import { resetMintState as resetMintV3State } from 'state/mint/v3/actions'
|
||||
import { TYPE } from 'theme'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
|
||||
const Tabs = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
@@ -22,7 +25,7 @@ const Tabs = styled.div`
|
||||
const activeClassName = 'ACTIVE'
|
||||
|
||||
const StyledNavLink = styled(NavLink).attrs({
|
||||
activeClassName
|
||||
activeClassName,
|
||||
})`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
@@ -59,7 +62,7 @@ const StyledArrowLeft = styled(ArrowLeft)`
|
||||
export function SwapPoolTabs({ active }: { active: 'swap' | 'pool' }) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Tabs style={{ marginBottom: '20px', display: 'none' }}>
|
||||
<Tabs style={{ marginBottom: '20px', display: 'none', padding: '1rem 1rem 0 1rem' }}>
|
||||
<StyledNavLink id={`swap-nav-link`} to={'/swap'} isActive={() => active === 'swap'}>
|
||||
{t('swap')}
|
||||
</StyledNavLink>
|
||||
@@ -70,21 +73,32 @@ export function SwapPoolTabs({ active }: { active: 'swap' | 'pool' }) {
|
||||
)
|
||||
}
|
||||
|
||||
export function FindPoolTabs() {
|
||||
export function FindPoolTabs({ origin }: { origin: string }) {
|
||||
return (
|
||||
<Tabs>
|
||||
<RowBetween style={{ padding: '1rem 1rem 0 1rem' }}>
|
||||
<HistoryLink to="/pool">
|
||||
<HistoryLink to={origin}>
|
||||
<StyledArrowLeft />
|
||||
</HistoryLink>
|
||||
<ActiveText>Import Pool</ActiveText>
|
||||
<Settings />
|
||||
</RowBetween>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
export function AddRemoveTabs({ adding, creating }: { adding: boolean; creating: boolean }) {
|
||||
export function AddRemoveTabs({
|
||||
adding,
|
||||
creating,
|
||||
positionID,
|
||||
defaultSlippage,
|
||||
}: {
|
||||
adding: boolean
|
||||
creating: boolean
|
||||
positionID?: string | undefined
|
||||
defaultSlippage: Percent
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
|
||||
// reset states on back
|
||||
const dispatch = useDispatch<AppDispatch>()
|
||||
|
||||
@@ -92,15 +106,21 @@ export function AddRemoveTabs({ adding, creating }: { adding: boolean; creating:
|
||||
<Tabs>
|
||||
<RowBetween style={{ padding: '1rem 1rem 0 1rem' }}>
|
||||
<HistoryLink
|
||||
to="/pool"
|
||||
to={'/pool' + (!!positionID ? `/${positionID.toString()}` : '')}
|
||||
onClick={() => {
|
||||
adding && dispatch(resetMintState())
|
||||
if (adding) {
|
||||
// not 100% sure both of these are needed
|
||||
dispatch(resetMintState())
|
||||
dispatch(resetMintV3State())
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StyledArrowLeft />
|
||||
<StyledArrowLeft stroke={theme.text2} />
|
||||
</HistoryLink>
|
||||
<ActiveText>{creating ? 'Create a pair' : adding ? 'Add Liquidity' : 'Remove Liquidity'}</ActiveText>
|
||||
<Settings />
|
||||
<TYPE.mediumHeader fontWeight={500} fontSize={20}>
|
||||
{creating ? 'Create a pair' : adding ? 'Add Liquidity' : 'Remove Liquidity'}
|
||||
</TYPE.mediumHeader>
|
||||
<SettingsTab placeholderSlippage={defaultSlippage} />
|
||||
</RowBetween>
|
||||
</Tabs>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import { escapeRegExp } from '../../utils'
|
||||
|
||||
const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: string }>`
|
||||
@@ -18,6 +18,7 @@ const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: s
|
||||
text-overflow: ellipsis;
|
||||
padding: 0px;
|
||||
-webkit-appearance: textfield;
|
||||
text-align: right;
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
@@ -43,6 +44,7 @@ export const Input = React.memo(function InnerInput({
|
||||
value,
|
||||
onUserInput,
|
||||
placeholder,
|
||||
prependSymbol,
|
||||
...rest
|
||||
}: {
|
||||
value: string | number
|
||||
@@ -50,6 +52,7 @@ export const Input = React.memo(function InnerInput({
|
||||
error?: boolean
|
||||
fontSize?: string
|
||||
align?: 'right' | 'left'
|
||||
prependSymbol?: string | undefined
|
||||
} & Omit<React.HTMLProps<HTMLInputElement>, 'ref' | 'onChange' | 'as'>) {
|
||||
const enforcer = (nextUserInput: string) => {
|
||||
if (nextUserInput === '' || inputRegex.test(escapeRegExp(nextUserInput))) {
|
||||
@@ -60,14 +63,24 @@ export const Input = React.memo(function InnerInput({
|
||||
return (
|
||||
<StyledInput
|
||||
{...rest}
|
||||
value={value}
|
||||
onChange={event => {
|
||||
// replace commas with periods, because uniswap exclusively uses period as the decimal separator
|
||||
enforcer(event.target.value.replace(/,/g, '.'))
|
||||
value={prependSymbol && value ? prependSymbol + value : value}
|
||||
onChange={(event) => {
|
||||
if (prependSymbol) {
|
||||
const value = event.target.value
|
||||
|
||||
// cut off prepended symbol
|
||||
const formattedValue = value.toString().includes(prependSymbol)
|
||||
? value.toString().slice(1, value.toString().length + 1)
|
||||
: value
|
||||
|
||||
// replace commas with periods, because uniswap exclusively uses period as the decimal separator
|
||||
enforcer(formattedValue.replace(/,/g, '.'))
|
||||
} else {
|
||||
enforcer(event.target.value.replace(/,/g, '.'))
|
||||
}
|
||||
}}
|
||||
// universal input options
|
||||
inputMode="decimal"
|
||||
title="Token Amount"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
// text-specific options
|
||||
|
||||
@@ -2,17 +2,15 @@ import { Placement } from '@popperjs/core'
|
||||
import { transparentize } from 'polished'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { usePopper } from 'react-popper'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import useInterval from '../../hooks/useInterval'
|
||||
import Portal from '@reach/portal'
|
||||
|
||||
const PopoverContainer = styled.div<{ show: boolean }>`
|
||||
z-index: 9999;
|
||||
|
||||
visibility: ${props => (props.show ? 'visible' : 'hidden')};
|
||||
opacity: ${props => (props.show ? 1 : 0)};
|
||||
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: 0 4px 8px 0 ${({ theme }) => transparentize(0.9, theme.shadow1)};
|
||||
@@ -91,8 +89,8 @@ export default function Popover({ content, show, children, placement = 'auto' }:
|
||||
strategy: 'fixed',
|
||||
modifiers: [
|
||||
{ name: 'offset', options: { offset: [8, 8] } },
|
||||
{ name: 'arrow', options: { element: arrowElement } }
|
||||
]
|
||||
{ name: 'arrow', options: { element: arrowElement } },
|
||||
],
|
||||
})
|
||||
const updateCallback = useCallback(() => {
|
||||
update && update()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TokenAmount } from '@uniswap/sdk'
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import React, { useEffect } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
useModalOpen,
|
||||
useShowClaimPopup,
|
||||
useToggleSelfClaimModal,
|
||||
useToggleShowClaimPopup
|
||||
useToggleShowClaimPopup,
|
||||
} from '../../state/application/hooks'
|
||||
|
||||
import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/claim/hooks'
|
||||
@@ -65,7 +65,7 @@ export default function ClaimPopup() {
|
||||
|
||||
// const userHasAvailableclaim = useUserHasAvailableClaim()
|
||||
const userHasAvailableclaim: boolean = useUserHasAvailableClaim(account)
|
||||
const unclaimedAmount: TokenAmount | undefined = useUserUnclaimedAmount(account)
|
||||
const unclaimedAmount: CurrencyAmount<Token> | undefined = useUserUnclaimedAmount(account)
|
||||
|
||||
// listen for available claim and show popup if needed
|
||||
useEffect(() => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { useCallback, useMemo } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import { AppDispatch } from '../../state'
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
import { acceptListUpdate } from '../../state/lists/actions'
|
||||
@@ -23,7 +23,7 @@ export default function ListUpdatePopup({
|
||||
listUrl,
|
||||
oldList,
|
||||
newList,
|
||||
auto
|
||||
auto,
|
||||
}: {
|
||||
popKey: string
|
||||
listUrl: string
|
||||
@@ -40,7 +40,7 @@ export default function ListUpdatePopup({
|
||||
ReactGA.event({
|
||||
category: 'Lists',
|
||||
action: 'Update List from Popup',
|
||||
label: listUrl
|
||||
label: listUrl,
|
||||
})
|
||||
dispatch(acceptListUpdate(listUrl))
|
||||
removeThisPopup()
|
||||
|
||||
@@ -21,7 +21,7 @@ export const Popup = styled.div`
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 1em;
|
||||
background-color: ${({ theme }) => theme.bg1};
|
||||
background-color: ${({ theme }) => theme.bg0};
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
@@ -49,7 +49,7 @@ const AnimatedFader = animated(Fader)
|
||||
export default function PopupItem({
|
||||
removeAfterMs,
|
||||
content,
|
||||
popKey
|
||||
popKey,
|
||||
}: {
|
||||
removeAfterMs: number | null
|
||||
content: PopupContent
|
||||
@@ -74,12 +74,12 @@ export default function PopupItem({
|
||||
let popupContent
|
||||
if ('txn' in content) {
|
||||
const {
|
||||
txn: { hash, success, summary }
|
||||
txn: { hash, success, summary },
|
||||
} = content
|
||||
popupContent = <TransactionPopup hash={hash} success={success} summary={summary} />
|
||||
} else if ('listUpdate' in content) {
|
||||
const {
|
||||
listUpdate: { listUrl, oldList, newList, auto }
|
||||
listUpdate: { listUrl, oldList, newList, auto },
|
||||
} = content
|
||||
popupContent = <ListUpdatePopup popKey={popKey} listUrl={listUrl} oldList={oldList} newList={newList} auto={auto} />
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export default function PopupItem({
|
||||
const faderStyle = useSpring({
|
||||
from: { width: '100%' },
|
||||
to: { width: '0%' },
|
||||
config: { duration: removeAfterMs ?? undefined }
|
||||
config: { duration: removeAfterMs ?? undefined },
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -15,7 +15,7 @@ const RowNoFlex = styled(AutoRow)`
|
||||
export default function TransactionPopup({
|
||||
hash,
|
||||
success,
|
||||
summary
|
||||
summary,
|
||||
}: {
|
||||
hash: string
|
||||
success?: boolean
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useActivePopups } from '../../state/application/hooks'
|
||||
import { AutoColumn } from '../Column'
|
||||
import PopupItem from './PopupItem'
|
||||
@@ -33,7 +33,7 @@ const MobilePopupInner = styled.div`
|
||||
|
||||
const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>`
|
||||
position: fixed;
|
||||
top: ${({ extraPadding }) => (extraPadding ? '108px' : '88px')};
|
||||
top: ${({ extraPadding }) => (extraPadding ? '72px' : '88px')};
|
||||
right: 1rem;
|
||||
max-width: 355px !important;
|
||||
width: 100%;
|
||||
@@ -54,7 +54,7 @@ export default function Popups() {
|
||||
<>
|
||||
<FixedPopupColumn gap="20px" extraPadding={urlWarningActive}>
|
||||
<ClaimPopup />
|
||||
{activePopups.map(item => (
|
||||
{activePopups.map((item) => (
|
||||
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
|
||||
))}
|
||||
</FixedPopupColumn>
|
||||
@@ -63,7 +63,7 @@ export default function Popups() {
|
||||
{activePopups // reverse so new items up front
|
||||
.slice(0)
|
||||
.reverse()
|
||||
.map(item => (
|
||||
.map((item) => (
|
||||
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
|
||||
))}
|
||||
</MobilePopupInner>
|
||||
|
||||
71
src/components/PositionCard/Sushi.tsx
Normal file
71
src/components/PositionCard/Sushi.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import React from 'react'
|
||||
import { Token } from '@uniswap/sdk-core'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { unwrappedToken } from '../../utils/wrappedCurrency'
|
||||
import { ButtonEmpty } from '../Button'
|
||||
import { transparentize } from 'polished'
|
||||
import { CardNoise } from '../earn/styled'
|
||||
|
||||
import { useColor } from '../../hooks/useColor'
|
||||
|
||||
import { LightCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { RowFixed, AutoRow } from '../Row'
|
||||
import { Dots } from '../swap/styleds'
|
||||
import { FixedHeightRow } from '.'
|
||||
import Badge, { BadgeVariant } from 'components/Badge'
|
||||
|
||||
const StyledPositionCard = styled(LightCard)<{ bgColor: any }>`
|
||||
border: none;
|
||||
background: ${({ theme, bgColor }) =>
|
||||
`radial-gradient(91.85% 100% at 1.84% 0%, ${transparentize(0.8, bgColor)} 0%, ${theme.bg3} 100%) `};
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
interface PositionCardProps {
|
||||
tokenA: Token
|
||||
tokenB: Token
|
||||
liquidityToken: Token
|
||||
border?: string
|
||||
}
|
||||
|
||||
export default function SushiPositionCard({ tokenA, tokenB, liquidityToken, border }: PositionCardProps) {
|
||||
const currency0 = unwrappedToken(tokenA)
|
||||
const currency1 = unwrappedToken(tokenB)
|
||||
|
||||
const backgroundColor = useColor(tokenA)
|
||||
|
||||
return (
|
||||
<StyledPositionCard border={border} bgColor={backgroundColor}>
|
||||
<CardNoise />
|
||||
<AutoColumn gap="12px">
|
||||
<FixedHeightRow>
|
||||
<AutoRow gap="8px">
|
||||
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} size={20} />
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
{!currency0 || !currency1 ? <Dots>Loading</Dots> : `${currency0.symbol}/${currency1.symbol}`}
|
||||
</Text>
|
||||
|
||||
<Badge variant={BadgeVariant.WARNING}>Sushi</Badge>
|
||||
</AutoRow>
|
||||
<RowFixed gap="8px">
|
||||
<ButtonEmpty
|
||||
padding="0px 35px 0px 0px"
|
||||
borderRadius="12px"
|
||||
width="fit-content"
|
||||
as={Link}
|
||||
to={`/migrate/v2/${liquidityToken.address}`}
|
||||
>
|
||||
Migrate
|
||||
</ButtonEmpty>
|
||||
</RowFixed>
|
||||
</FixedHeightRow>
|
||||
</AutoColumn>
|
||||
</StyledPositionCard>
|
||||
)
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { Link, RouteComponentProps, withRouter } from 'react-router-dom'
|
||||
import { Token, TokenAmount, WETH } from '@uniswap/sdk'
|
||||
|
||||
import { Text } from 'rebass'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { ButtonSecondary } from '../Button'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import { FixedHeightRow, HoverCard } from './index'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
|
||||
interface PositionCardProps extends RouteComponentProps<{}> {
|
||||
token: Token
|
||||
V1LiquidityBalance: TokenAmount
|
||||
}
|
||||
|
||||
function V1PositionCard({ token, V1LiquidityBalance }: PositionCardProps) {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
return (
|
||||
<HoverCard>
|
||||
<AutoColumn gap="12px">
|
||||
<FixedHeightRow>
|
||||
<RowFixed>
|
||||
<DoubleCurrencyLogo currency0={token} margin={true} size={20} />
|
||||
<Text fontWeight={500} fontSize={20} style={{ marginLeft: '' }}>
|
||||
{`${chainId && token.equals(WETH[chainId]) ? 'WETH' : token.symbol}/ETH`}
|
||||
</Text>
|
||||
<Text
|
||||
fontSize={12}
|
||||
fontWeight={500}
|
||||
ml="0.5rem"
|
||||
px="0.75rem"
|
||||
py="0.25rem"
|
||||
style={{ borderRadius: '1rem' }}
|
||||
backgroundColor={theme.yellow1}
|
||||
color={'black'}
|
||||
>
|
||||
V1
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</FixedHeightRow>
|
||||
|
||||
<AutoColumn gap="8px">
|
||||
<RowBetween marginTop="10px">
|
||||
<ButtonSecondary width="68%" as={Link} to={`/migrate/v1/${V1LiquidityBalance.token.address}`}>
|
||||
Migrate
|
||||
</ButtonSecondary>
|
||||
|
||||
<ButtonSecondary
|
||||
style={{ backgroundColor: 'transparent' }}
|
||||
width="28%"
|
||||
as={Link}
|
||||
to={`/remove/v1/${V1LiquidityBalance.token.address}`}
|
||||
>
|
||||
Remove
|
||||
</ButtonSecondary>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
</AutoColumn>
|
||||
</HoverCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(V1PositionCard)
|
||||
207
src/components/PositionCard/V2.tsx
Normal file
207
src/components/PositionCard/V2.tsx
Normal file
@@ -0,0 +1,207 @@
|
||||
import JSBI from 'jsbi'
|
||||
import React, { useState } from 'react'
|
||||
import { Percent, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { ChevronDown, ChevronUp } from 'react-feather'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
import { unwrappedToken } from '../../utils/wrappedCurrency'
|
||||
import { ButtonPrimary, ButtonSecondary, ButtonEmpty } from '../Button'
|
||||
import { transparentize } from 'polished'
|
||||
import { CardNoise } from '../earn/styled'
|
||||
|
||||
import { useColor } from '../../hooks/useColor'
|
||||
|
||||
import { LightCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { RowBetween, RowFixed, AutoRow } from '../Row'
|
||||
import { Dots } from '../swap/styleds'
|
||||
import { BIG_INT_ZERO } from '../../constants'
|
||||
import { FixedHeightRow } from '.'
|
||||
|
||||
const StyledPositionCard = styled(LightCard)<{ bgColor: any }>`
|
||||
border: none;
|
||||
background: ${({ theme, bgColor }) =>
|
||||
`radial-gradient(91.85% 100% at 1.84% 0%, ${transparentize(0.8, bgColor)} 0%, ${theme.bg3} 100%) `};
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
interface PositionCardProps {
|
||||
pair: Pair
|
||||
showUnwrapped?: boolean
|
||||
border?: string
|
||||
stakedBalance?: CurrencyAmount<Token> // optional balance to indicate that liquidity is deposited in mining pool
|
||||
}
|
||||
|
||||
export default function V2PositionCard({ pair, border, stakedBalance }: PositionCardProps) {
|
||||
const { account } = useActiveWeb3React()
|
||||
|
||||
const currency0 = unwrappedToken(pair.token0)
|
||||
const currency1 = unwrappedToken(pair.token1)
|
||||
|
||||
const [showMore, setShowMore] = useState(false)
|
||||
|
||||
const userDefaultPoolBalance = useTokenBalance(account ?? undefined, pair.liquidityToken)
|
||||
const totalPoolTokens = useTotalSupply(pair.liquidityToken)
|
||||
|
||||
// if staked balance balance provided, add to standard liquidity amount
|
||||
const userPoolBalance = stakedBalance ? userDefaultPoolBalance?.add(stakedBalance) : userDefaultPoolBalance
|
||||
|
||||
const poolTokenPercentage =
|
||||
!!userPoolBalance &&
|
||||
!!totalPoolTokens &&
|
||||
JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userPoolBalance.quotient)
|
||||
? new Percent(userPoolBalance.quotient, totalPoolTokens.quotient)
|
||||
: undefined
|
||||
|
||||
const [token0Deposited, token1Deposited] =
|
||||
!!pair &&
|
||||
!!totalPoolTokens &&
|
||||
!!userPoolBalance &&
|
||||
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||
JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userPoolBalance.quotient)
|
||||
? [
|
||||
pair.getLiquidityValue(pair.token0, totalPoolTokens, userPoolBalance, false),
|
||||
pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false),
|
||||
]
|
||||
: [undefined, undefined]
|
||||
|
||||
const backgroundColor = useColor(pair?.token0)
|
||||
|
||||
return (
|
||||
<StyledPositionCard border={border} bgColor={backgroundColor}>
|
||||
<CardNoise />
|
||||
<AutoColumn gap="12px">
|
||||
<FixedHeightRow>
|
||||
<AutoRow gap="8px">
|
||||
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} size={20} />
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
{!currency0 || !currency1 ? <Dots>Loading</Dots> : `${currency0.symbol}/${currency1.symbol}`}
|
||||
</Text>
|
||||
</AutoRow>
|
||||
<RowFixed gap="8px">
|
||||
<ButtonEmpty
|
||||
padding="6px 8px"
|
||||
borderRadius="12px"
|
||||
width="fit-content"
|
||||
onClick={() => setShowMore(!showMore)}
|
||||
>
|
||||
{showMore ? (
|
||||
<>
|
||||
Manage
|
||||
<ChevronUp size="20" style={{ marginLeft: '10px' }} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Manage
|
||||
<ChevronDown size="20" style={{ marginLeft: '10px' }} />
|
||||
</>
|
||||
)}
|
||||
</ButtonEmpty>
|
||||
</RowFixed>
|
||||
</FixedHeightRow>
|
||||
|
||||
{showMore && (
|
||||
<AutoColumn gap="8px">
|
||||
<FixedHeightRow>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Your total pool tokens:
|
||||
</Text>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
{userPoolBalance ? userPoolBalance.toSignificant(4) : '-'}
|
||||
</Text>
|
||||
</FixedHeightRow>
|
||||
{stakedBalance && (
|
||||
<FixedHeightRow>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Pool tokens in rewards pool:
|
||||
</Text>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
{stakedBalance.toSignificant(4)}
|
||||
</Text>
|
||||
</FixedHeightRow>
|
||||
)}
|
||||
<FixedHeightRow>
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Pooled {currency0.symbol}:
|
||||
</Text>
|
||||
</RowFixed>
|
||||
{token0Deposited ? (
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{token0Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency0} />
|
||||
</RowFixed>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</FixedHeightRow>
|
||||
|
||||
<FixedHeightRow>
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Pooled {currency1.symbol}:
|
||||
</Text>
|
||||
</RowFixed>
|
||||
{token1Deposited ? (
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
|
||||
{token1Deposited?.toSignificant(6)}
|
||||
</Text>
|
||||
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={currency1} />
|
||||
</RowFixed>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</FixedHeightRow>
|
||||
|
||||
<FixedHeightRow>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Your pool share:
|
||||
</Text>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
{poolTokenPercentage
|
||||
? (poolTokenPercentage.toFixed(2) === '0.00' ? '<0.01' : poolTokenPercentage.toFixed(2)) + '%'
|
||||
: '-'}
|
||||
</Text>
|
||||
</FixedHeightRow>
|
||||
|
||||
{userDefaultPoolBalance && JSBI.greaterThan(userDefaultPoolBalance.quotient, BIG_INT_ZERO) && (
|
||||
<RowBetween marginTop="10px">
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
as={Link}
|
||||
to={`/migrate/v2/${pair.liquidityToken.address}`}
|
||||
width="64%"
|
||||
>
|
||||
Migrate
|
||||
</ButtonPrimary>
|
||||
<ButtonSecondary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
as={Link}
|
||||
width="32%"
|
||||
to={`/remove/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
>
|
||||
Remove
|
||||
</ButtonSecondary>
|
||||
</RowBetween>
|
||||
)}
|
||||
</AutoColumn>
|
||||
)}
|
||||
</AutoColumn>
|
||||
</StyledPositionCard>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import { JSBI, Pair, Percent, TokenAmount } from '@uniswap/sdk'
|
||||
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 { ChevronDown, ChevronUp } from 'react-feather'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useTokenBalance } from '../../state/wallet/hooks'
|
||||
@@ -48,7 +50,7 @@ interface PositionCardProps {
|
||||
pair: Pair
|
||||
showUnwrapped?: boolean
|
||||
border?: string
|
||||
stakedBalance?: TokenAmount // optional balance to indicate that liquidity is deposited in mining pool
|
||||
stakedBalance?: CurrencyAmount<Token> // optional balance to indicate that liquidity is deposited in mining pool
|
||||
}
|
||||
|
||||
export function MinimalPositionCard({ pair, showUnwrapped = false, border }: PositionCardProps) {
|
||||
@@ -63,8 +65,10 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
|
||||
const totalPoolTokens = useTotalSupply(pair.liquidityToken)
|
||||
|
||||
const poolTokenPercentage =
|
||||
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
|
||||
? new Percent(userPoolBalance.raw, totalPoolTokens.raw)
|
||||
!!userPoolBalance &&
|
||||
!!totalPoolTokens &&
|
||||
JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userPoolBalance.quotient)
|
||||
? new Percent(userPoolBalance.quotient, totalPoolTokens.quotient)
|
||||
: undefined
|
||||
|
||||
const [token0Deposited, token1Deposited] =
|
||||
@@ -72,16 +76,16 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
|
||||
!!totalPoolTokens &&
|
||||
!!userPoolBalance &&
|
||||
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
|
||||
JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userPoolBalance.quotient)
|
||||
? [
|
||||
pair.getLiquidityValue(pair.token0, totalPoolTokens, userPoolBalance, false),
|
||||
pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false)
|
||||
pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false),
|
||||
]
|
||||
: [undefined, undefined]
|
||||
|
||||
return (
|
||||
<>
|
||||
{userPoolBalance && JSBI.greaterThan(userPoolBalance.raw, JSBI.BigInt(0)) ? (
|
||||
{userPoolBalance && JSBI.greaterThan(userPoolBalance.quotient, JSBI.BigInt(0)) ? (
|
||||
<GreyCard border={border}>
|
||||
<AutoColumn gap="12px">
|
||||
<FixedHeightRow>
|
||||
@@ -174,8 +178,10 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
const userPoolBalance = stakedBalance ? userDefaultPoolBalance?.add(stakedBalance) : userDefaultPoolBalance
|
||||
|
||||
const poolTokenPercentage =
|
||||
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
|
||||
? new Percent(userPoolBalance.raw, totalPoolTokens.raw)
|
||||
!!userPoolBalance &&
|
||||
!!totalPoolTokens &&
|
||||
JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userPoolBalance.quotient)
|
||||
? new Percent(userPoolBalance.quotient, totalPoolTokens.quotient)
|
||||
: undefined
|
||||
|
||||
const [token0Deposited, token1Deposited] =
|
||||
@@ -183,10 +189,10 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
!!totalPoolTokens &&
|
||||
!!userPoolBalance &&
|
||||
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply
|
||||
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
|
||||
JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userPoolBalance.quotient)
|
||||
? [
|
||||
pair.getLiquidityValue(pair.token0, totalPoolTokens, userPoolBalance, false),
|
||||
pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false)
|
||||
pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false),
|
||||
]
|
||||
: [undefined, undefined]
|
||||
|
||||
@@ -197,28 +203,23 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
<CardNoise />
|
||||
<AutoColumn gap="12px">
|
||||
<FixedHeightRow>
|
||||
<AutoRow gap="8px">
|
||||
<AutoRow gap="8px" style={{ marginLeft: '8px' }}>
|
||||
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} size={20} />
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
{!currency0 || !currency1 ? <Dots>Loading</Dots> : `${currency0.symbol}/${currency1.symbol}`}
|
||||
</Text>
|
||||
</AutoRow>
|
||||
<RowFixed gap="8px">
|
||||
<ButtonEmpty
|
||||
padding="6px 8px"
|
||||
borderRadius="12px"
|
||||
width="fit-content"
|
||||
onClick={() => setShowMore(!showMore)}
|
||||
>
|
||||
<RowFixed gap="8px" style={{ marginRight: '4px' }}>
|
||||
<ButtonEmpty padding="6px 8px" borderRadius="12px" width="100%" onClick={() => setShowMore(!showMore)}>
|
||||
{showMore ? (
|
||||
<>
|
||||
Manage
|
||||
<ChevronUp size="20" style={{ marginLeft: '10px' }} />
|
||||
<ChevronUp size="20" style={{ marginLeft: '8px', height: '20px', minWidth: '20px' }} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Manage
|
||||
<ChevronDown size="20" style={{ marginLeft: '10px' }} />
|
||||
<ChevronDown size="20" style={{ marginLeft: '8px', height: '20px', minWidth: '20px' }} />
|
||||
</>
|
||||
)}
|
||||
</ButtonEmpty>
|
||||
@@ -295,19 +296,28 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
<ButtonSecondary padding="8px" borderRadius="8px">
|
||||
<ExternalLink
|
||||
style={{ width: '100%', textAlign: 'center' }}
|
||||
href={`https://uniswap.info/account/${account}`}
|
||||
href={`https://v2.info.uniswap.org/account/${account}`}
|
||||
>
|
||||
View accrued fees and analytics<span style={{ fontSize: '11px' }}>↗</span>
|
||||
</ExternalLink>
|
||||
</ButtonSecondary>
|
||||
{userDefaultPoolBalance && JSBI.greaterThan(userDefaultPoolBalance.raw, BIG_INT_ZERO) && (
|
||||
{userDefaultPoolBalance && JSBI.greaterThan(userDefaultPoolBalance.quotient, BIG_INT_ZERO) && (
|
||||
<RowBetween marginTop="10px">
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
as={Link}
|
||||
to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
width="48%"
|
||||
to={`/migrate/v2/${pair.liquidityToken.address}`}
|
||||
width="32%"
|
||||
>
|
||||
Migrate
|
||||
</ButtonPrimary>
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
as={Link}
|
||||
to={`/add/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
width="32%"
|
||||
>
|
||||
Add
|
||||
</ButtonPrimary>
|
||||
@@ -315,14 +325,14 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
as={Link}
|
||||
width="48%"
|
||||
to={`/remove/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
width="32%"
|
||||
to={`/remove/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
>
|
||||
Remove
|
||||
</ButtonPrimary>
|
||||
</RowBetween>
|
||||
)}
|
||||
{stakedBalance && JSBI.greaterThan(stakedBalance.raw, BIG_INT_ZERO) && (
|
||||
{stakedBalance && JSBI.greaterThan(stakedBalance.quotient, BIG_INT_ZERO) && (
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
|
||||
53
src/components/PositionList/PositionList.stories.tsx
Normal file
53
src/components/PositionList/PositionList.stories.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
// import { Story } from '@storybook/react/types-6-0'
|
||||
// import React from 'react'
|
||||
// import { Position } from 'types/position'
|
||||
// import { basisPointsToPercent } from 'utils'
|
||||
// import { DAI, WBTC } from '../../constants'
|
||||
// import Component, { PositionListProps } from './index'
|
||||
// import { CurrencyAmount } from '@uniswap/sdk-core'
|
||||
// import JSBI from 'jsbi'
|
||||
|
||||
// const FEE_BIPS = {
|
||||
// FIVE: basisPointsToPercent(5),
|
||||
// THIRTY: basisPointsToPercent(30),
|
||||
// ONE_HUNDRED: basisPointsToPercent(100),
|
||||
// }
|
||||
// const daiAmount = CurrencyAmount.fromRawAmount(DAI, JSBI.BigInt(500 * 10 ** 18))
|
||||
// const wbtcAmount = CurrencyAmount.fromRawAmount(WBTC, JSBI.BigInt(10 ** 7))
|
||||
// const positions = [
|
||||
// {
|
||||
// feesEarned: {
|
||||
// DAI: 1000,
|
||||
// WBTC: 0.005,
|
||||
// },
|
||||
// feeLevel: FEE_BIPS.FIVE,
|
||||
// tokenAmount0: daiAmount,
|
||||
// tokenAmount1: wbtcAmount,
|
||||
// tickLower: 40000,
|
||||
// tickUpper: 60000,
|
||||
// },
|
||||
// {
|
||||
// feesEarned: {
|
||||
// DAI: 1000,
|
||||
// WBTC: 0.005,
|
||||
// },
|
||||
// feeLevel: FEE_BIPS.THIRTY,
|
||||
// tokenAmount0: daiAmount,
|
||||
// tokenAmount1: wbtcAmount,
|
||||
// tickLower: 45000,
|
||||
// tickUpper: 55000,
|
||||
// },
|
||||
// ]
|
||||
// const positions: Position[] = []
|
||||
|
||||
export default {
|
||||
title: 'PositionList',
|
||||
}
|
||||
|
||||
// const Template: Story<PositionListProps> = (args) => <Component {...args} />
|
||||
|
||||
// export const PositionList = Template.bind({})
|
||||
// PositionList.args = {
|
||||
// positions,
|
||||
// showUnwrapped: true,
|
||||
// }
|
||||
59
src/components/PositionList/index.tsx
Normal file
59
src/components/PositionList/index.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import PositionListItem from 'components/PositionListItem'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components/macro'
|
||||
import { MEDIA_WIDTHS } from 'theme'
|
||||
import { PositionDetails } from 'types/position'
|
||||
|
||||
const DesktopHeader = styled.div`
|
||||
display: none;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
padding: 8px;
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
& > div:last-child {
|
||||
text-align: right;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const MobileHeader = styled.div`
|
||||
font-weight: medium;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
padding: 8px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
export type PositionListProps = React.PropsWithChildren<{
|
||||
positions: PositionDetails[]
|
||||
}>
|
||||
|
||||
export default function PositionList({ positions }: PositionListProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<>
|
||||
<DesktopHeader>
|
||||
<div>
|
||||
{t('Your positions')}
|
||||
{positions && ' (' + positions.length + ')'}
|
||||
</div>
|
||||
<div>{t('Price range')}</div>
|
||||
</DesktopHeader>
|
||||
<MobileHeader>Your positions</MobileHeader>
|
||||
{positions.map((p) => {
|
||||
return <PositionListItem key={p.tokenId.toString()} positionDetails={p} />
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
31
src/components/PositionListItem/PositionListItem.stories.tsx
Normal file
31
src/components/PositionListItem/PositionListItem.stories.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
// import { Story } from '@storybook/react/types-6-0'
|
||||
// import { FeeAmount, MAX_TICK, MIN_TICK, TICK_SPACINGS } from '@uniswap/v3-sdk'
|
||||
// import { BigNumber } from 'ethers'
|
||||
// import React from 'react'
|
||||
// import { Position } from 'types/position'
|
||||
// import Component, { PositionListItemProps } from './index'
|
||||
|
||||
// const position: Position = {
|
||||
// nonce: BigNumber.from(0),
|
||||
// operator: '',
|
||||
// token0: '',
|
||||
// token1: '',
|
||||
// fee: FeeAmount.LOW,
|
||||
// tickLower: MIN_TICK(TICK_SPACINGS[FeeAmount.LOW]),
|
||||
// tickUpper: MAX_TICK(TICK_SPACINGS[FeeAmount.LOW]),
|
||||
// liquidity,
|
||||
// feeGrowthInside0LastX128fee
|
||||
// feeGrowthInside0LastX128
|
||||
// feeGrowthInside1LastX128
|
||||
// tokensOwed0
|
||||
// tokensOwed1
|
||||
// }
|
||||
|
||||
export default {
|
||||
title: 'PositionListItem',
|
||||
}
|
||||
|
||||
// const Template: Story<PositionListItemProps> = (args) => <Component {...args} />
|
||||
|
||||
// export const PositionListItem = Template.bind({})
|
||||
// PositionListItem.args = {position}
|
||||
276
src/components/PositionListItem/index.tsx
Normal file
276
src/components/PositionListItem/index.tsx
Normal file
@@ -0,0 +1,276 @@
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { Position } from '@uniswap/v3-sdk'
|
||||
import Badge from 'components/Badge'
|
||||
import DoubleCurrencyLogo from 'components/DoubleLogo'
|
||||
import { usePool } from 'hooks/usePools'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { Link } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { HideSmall, MEDIA_WIDTHS, SmallOnly } from 'theme'
|
||||
import { PositionDetails } from 'types/position'
|
||||
import { WETH9, Price, Token, Percent } from '@uniswap/sdk-core'
|
||||
import { formatPrice } from 'utils/formatTokenAmount'
|
||||
import Loader from 'components/Loader'
|
||||
import { unwrappedToken } from 'utils/wrappedCurrency'
|
||||
import { DAI, USDC, USDT, WBTC } from '../../constants'
|
||||
import RangeBadge from 'components/Badge/RangeBadge'
|
||||
import { RowFixed } from 'components/Row'
|
||||
import HoverInlineText from 'components/HoverInlineText'
|
||||
|
||||
const Row = styled(Link)`
|
||||
align-items: center;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: ${({ theme }) => theme.text1};
|
||||
margin: 8px 0;
|
||||
padding: 16px;
|
||||
text-decoration: none;
|
||||
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;
|
||||
}
|
||||
:hover {
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
}
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
flex-direction: column;
|
||||
row-gap: 24px;
|
||||
`};
|
||||
`
|
||||
const BadgeText = styled.div`
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
font-size: 12px;
|
||||
`};
|
||||
`
|
||||
|
||||
const DataLineItem = styled.div`
|
||||
font-size: 14px;
|
||||
`
|
||||
|
||||
const RangeLineItem = styled(DataLineItem)`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-self: flex-end;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
flex-direction: column;
|
||||
row-gap: 4px;
|
||||
`};
|
||||
`
|
||||
|
||||
const DoubleArrow = styled.span`
|
||||
margin: 0 2px;
|
||||
color: ${({ theme }) => theme.text3};
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
margin: 4px;
|
||||
padding: 20px;
|
||||
`};
|
||||
`
|
||||
|
||||
const RangeText = styled.span`
|
||||
/* background-color: ${({ theme }) => theme.bg2}; */
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 8px;
|
||||
`
|
||||
|
||||
const ExtentsText = styled.span`
|
||||
color: ${({ theme }) => theme.text3};
|
||||
font-size: 14px;
|
||||
margin-right: 4px;
|
||||
`
|
||||
|
||||
const PrimaryPositionIdData = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
> * {
|
||||
margin-right: 8px;
|
||||
}
|
||||
`
|
||||
|
||||
const DataText = styled.div`
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
font-size: 14px;
|
||||
`};
|
||||
`
|
||||
|
||||
export interface PositionListItemProps {
|
||||
positionDetails: PositionDetails
|
||||
}
|
||||
|
||||
export function getPriceOrderingFromPositionForUI(
|
||||
position?: Position
|
||||
): {
|
||||
priceLower?: Price<Token, Token>
|
||||
priceUpper?: Price<Token, Token>
|
||||
quote?: Token
|
||||
base?: Token
|
||||
} {
|
||||
if (!position) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const token0 = position.amount0.currency
|
||||
const token1 = position.amount1.currency
|
||||
|
||||
// if token0 is a dollar-stable asset, set it as the quote token
|
||||
const stables = [DAI, USDC, USDT]
|
||||
if (stables.some((stable) => stable.equals(token0))) {
|
||||
return {
|
||||
priceLower: position.token0PriceUpper.invert(),
|
||||
priceUpper: position.token0PriceLower.invert(),
|
||||
quote: token0,
|
||||
base: token1,
|
||||
}
|
||||
}
|
||||
|
||||
// if token1 is an ETH-/BTC-stable asset, set it as the base token
|
||||
const bases = [...Object.values(WETH9), WBTC]
|
||||
if (bases.some((base) => base.equals(token1))) {
|
||||
return {
|
||||
priceLower: position.token0PriceUpper.invert(),
|
||||
priceUpper: position.token0PriceLower.invert(),
|
||||
quote: token0,
|
||||
base: token1,
|
||||
}
|
||||
}
|
||||
|
||||
// if both prices are below 1, invert
|
||||
if (position.token0PriceUpper.lessThan(1)) {
|
||||
return {
|
||||
priceLower: position.token0PriceUpper.invert(),
|
||||
priceUpper: position.token0PriceLower.invert(),
|
||||
quote: token0,
|
||||
base: token1,
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, just return the default
|
||||
return {
|
||||
priceLower: position.token0PriceLower,
|
||||
priceUpper: position.token0PriceUpper,
|
||||
quote: token1,
|
||||
base: token0,
|
||||
}
|
||||
}
|
||||
|
||||
export default function PositionListItem({ positionDetails }: PositionListItemProps) {
|
||||
const {
|
||||
token0: token0Address,
|
||||
token1: token1Address,
|
||||
fee: feeAmount,
|
||||
liquidity,
|
||||
tickLower,
|
||||
tickUpper,
|
||||
} = positionDetails
|
||||
|
||||
const token0 = useToken(token0Address)
|
||||
const token1 = useToken(token1Address)
|
||||
|
||||
const currency0 = token0 ? unwrappedToken(token0) : undefined
|
||||
const currency1 = token1 ? unwrappedToken(token1) : undefined
|
||||
|
||||
// construct Position from details returned
|
||||
const [, pool] = usePool(currency0 ?? undefined, currency1 ?? undefined, feeAmount)
|
||||
|
||||
const position = useMemo(() => {
|
||||
if (pool) {
|
||||
return new Position({ pool, liquidity: liquidity.toString(), tickLower, tickUpper })
|
||||
}
|
||||
return undefined
|
||||
}, [liquidity, pool, tickLower, tickUpper])
|
||||
|
||||
// prices
|
||||
let { priceLower, priceUpper, base, quote } = getPriceOrderingFromPositionForUI(position)
|
||||
const inverted = token1 ? base?.equals(token1) : undefined
|
||||
const currencyQuote = inverted ? currency1 : currency0
|
||||
const currencyBase = inverted ? currency0 : currency1
|
||||
|
||||
// check if price is within range
|
||||
const outOfRange: boolean = pool ? pool.tickCurrent < tickLower || pool.tickCurrent >= tickUpper : false
|
||||
|
||||
const positionSummaryLink = '/pool/' + positionDetails.tokenId
|
||||
|
||||
const [manuallyInverted, setManuallyInverted] = useState(true)
|
||||
if (manuallyInverted) {
|
||||
;[priceLower, priceUpper, base, quote] = [priceUpper?.invert(), priceLower?.invert(), quote, base]
|
||||
}
|
||||
|
||||
const removed = liquidity?.eq(0)
|
||||
|
||||
return (
|
||||
<Row to={positionSummaryLink}>
|
||||
<RowFixed>
|
||||
<PrimaryPositionIdData>
|
||||
<DoubleCurrencyLogo currency0={currencyBase} currency1={currencyQuote} size={18} margin />
|
||||
<DataText>
|
||||
{currencyQuote?.symbol} / {currencyBase?.symbol}
|
||||
</DataText>
|
||||
|
||||
<Badge>
|
||||
<BadgeText>{new Percent(feeAmount, 1_000_000).toSignificant()}%</BadgeText>
|
||||
</Badge>
|
||||
</PrimaryPositionIdData>
|
||||
<RangeBadge removed={removed} inRange={!outOfRange} />
|
||||
</RowFixed>
|
||||
|
||||
{priceLower && priceUpper ? (
|
||||
<>
|
||||
<RangeLineItem
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setManuallyInverted(!manuallyInverted)
|
||||
}}
|
||||
>
|
||||
<RangeText>
|
||||
<ExtentsText>Min: </ExtentsText>
|
||||
{formatPrice(priceLower, 5)}{' '}
|
||||
<HoverInlineText text={manuallyInverted ? currencyQuote?.symbol ?? '' : currencyBase?.symbol ?? ''} />{' '}
|
||||
{' per '}{' '}
|
||||
<HoverInlineText text={manuallyInverted ? currencyBase?.symbol ?? '' : currencyQuote?.symbol ?? ''} />
|
||||
</RangeText>{' '}
|
||||
<HideSmall>
|
||||
<DoubleArrow>⟷</DoubleArrow>{' '}
|
||||
</HideSmall>
|
||||
<SmallOnly>
|
||||
<DoubleArrow>↕</DoubleArrow>{' '}
|
||||
</SmallOnly>
|
||||
<RangeText>
|
||||
<ExtentsText>Max:</ExtentsText>
|
||||
{formatPrice(priceUpper, 5)}{' '}
|
||||
<HoverInlineText text={manuallyInverted ? currencyQuote?.symbol ?? '' : currencyBase?.symbol ?? ''} />{' '}
|
||||
{' per '}{' '}
|
||||
<HoverInlineText
|
||||
maxCharacters={10}
|
||||
text={manuallyInverted ? currencyBase?.symbol ?? '' : currencyQuote?.symbol ?? ''}
|
||||
/>
|
||||
</RangeText>{' '}
|
||||
</RangeLineItem>
|
||||
</>
|
||||
) : (
|
||||
<Loader />
|
||||
)}
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
158
src/components/PositionPreview/index.tsx
Normal file
158
src/components/PositionPreview/index.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
import React, { useState, useCallback, useContext } from 'react'
|
||||
import { Position } from '@uniswap/v3-sdk'
|
||||
import { LightCard } from 'components/Card'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { TYPE } from 'theme'
|
||||
import { RowBetween, RowFixed } from 'components/Row'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import { unwrappedToken } from 'utils/wrappedCurrency'
|
||||
import { Break } from 'components/earn/styled'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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 JSBI from 'jsbi'
|
||||
|
||||
export const PositionPreview = ({
|
||||
position,
|
||||
title,
|
||||
inRange,
|
||||
baseCurrencyDefault,
|
||||
}: {
|
||||
position: Position
|
||||
title?: string
|
||||
inRange: boolean
|
||||
baseCurrencyDefault?: Currency | undefined
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const currency0 = unwrappedToken(position.pool.token0)
|
||||
const currency1 = unwrappedToken(position.pool.token1)
|
||||
|
||||
// track which currency should be base
|
||||
const [baseCurrency, setBaseCurrency] = useState(
|
||||
baseCurrencyDefault
|
||||
? baseCurrencyDefault === currency0
|
||||
? currency0
|
||||
: baseCurrencyDefault === currency1
|
||||
? currency1
|
||||
: currency0
|
||||
: currency0
|
||||
)
|
||||
|
||||
const sorted = baseCurrency === currency0
|
||||
const quoteCurrency = sorted ? currency1 : currency0
|
||||
|
||||
const price = sorted ? position.pool.priceOf(position.pool.token0) : position.pool.priceOf(position.pool.token1)
|
||||
|
||||
const priceLower = sorted ? position.token0PriceLower : position.token0PriceUpper.invert()
|
||||
const priceUpper = sorted ? position.token0PriceUpper : position.token0PriceLower.invert()
|
||||
|
||||
const handleRateChange = useCallback(() => {
|
||||
setBaseCurrency(quoteCurrency)
|
||||
}, [quoteCurrency])
|
||||
|
||||
const removed = position?.liquidity && JSBI.equal(position?.liquidity, JSBI.BigInt(0))
|
||||
|
||||
return (
|
||||
<AutoColumn gap="md" style={{ marginTop: '0.5rem' }}>
|
||||
<RowBetween style={{ marginBottom: '0.5rem' }}>
|
||||
<RowFixed>
|
||||
<DoubleCurrencyLogo
|
||||
currency0={currency0 ?? undefined}
|
||||
currency1={currency1 ?? undefined}
|
||||
size={24}
|
||||
margin={true}
|
||||
/>
|
||||
<TYPE.label ml="10px" fontSize="24px">
|
||||
{currency0?.symbol} / {currency1?.symbol}
|
||||
</TYPE.label>
|
||||
</RowFixed>
|
||||
<RangeBadge removed={removed} inRange={inRange} />
|
||||
</RowBetween>
|
||||
|
||||
<LightCard>
|
||||
<AutoColumn gap="md">
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<CurrencyLogo currency={currency0} />
|
||||
<TYPE.label ml="8px">{currency0?.symbol}</TYPE.label>
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
<TYPE.label mr="8px">{position.amount0.toSignificant(4)}</TYPE.label>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<CurrencyLogo currency={currency1} />
|
||||
<TYPE.label ml="8px">{currency1?.symbol}</TYPE.label>
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
<TYPE.label mr="8px">{position.amount1.toSignificant(4)}</TYPE.label>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<Break />
|
||||
<RowBetween>
|
||||
<TYPE.label>{t('feeTier')}</TYPE.label>
|
||||
<TYPE.label>{position?.pool?.fee / 10000}%</TYPE.label>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
</LightCard>
|
||||
|
||||
<AutoColumn gap="md">
|
||||
<RowBetween>
|
||||
{title ? <TYPE.main>{title}</TYPE.main> : <div />}
|
||||
<RateToggle
|
||||
currencyA={sorted ? currency0 : currency1}
|
||||
currencyB={sorted ? currency1 : currency0}
|
||||
handleRateToggle={handleRateChange}
|
||||
/>
|
||||
</RowBetween>
|
||||
|
||||
<RowBetween>
|
||||
<LightCard width="48%" padding="8px">
|
||||
<AutoColumn gap="4px" justify="center">
|
||||
<TYPE.main fontSize="12px">Min Price</TYPE.main>
|
||||
<TYPE.mediumHeader textAlign="center">{`${priceLower.toSignificant(5)}`}</TYPE.mediumHeader>
|
||||
<TYPE.main
|
||||
textAlign="center"
|
||||
fontSize="12px"
|
||||
>{` ${quoteCurrency.symbol}/${baseCurrency.symbol}`}</TYPE.main>
|
||||
<TYPE.small textAlign="center" color={theme.text3} style={{ marginTop: '4px' }}>
|
||||
Your position will be 100% composed of {baseCurrency?.symbol} at this price
|
||||
</TYPE.small>
|
||||
</AutoColumn>
|
||||
</LightCard>
|
||||
|
||||
<LightCard width="48%" padding="8px">
|
||||
<AutoColumn gap="4px" justify="center">
|
||||
<TYPE.main fontSize="12px">Max Price</TYPE.main>
|
||||
<TYPE.mediumHeader textAlign="center">{`${priceUpper.toSignificant(5)}`}</TYPE.mediumHeader>
|
||||
<TYPE.main
|
||||
textAlign="center"
|
||||
fontSize="12px"
|
||||
>{` ${quoteCurrency.symbol} per ${baseCurrency.symbol}`}</TYPE.main>
|
||||
<TYPE.small textAlign="center" color={theme.text3} style={{ marginTop: '4px' }}>
|
||||
Your position will be 100% composed of {quoteCurrency?.symbol} at this price
|
||||
</TYPE.small>
|
||||
</AutoColumn>
|
||||
</LightCard>
|
||||
</RowBetween>
|
||||
<LightCard padding="12px ">
|
||||
<AutoColumn gap="4px" justify="center">
|
||||
<TYPE.main fontSize="12px">Current price</TYPE.main>
|
||||
<TYPE.mediumHeader>{`${price.toSignificant(5)} `}</TYPE.mediumHeader>
|
||||
<TYPE.main
|
||||
textAlign="center"
|
||||
fontSize="12px"
|
||||
>{` ${quoteCurrency.symbol} per ${baseCurrency.symbol}`}</TYPE.main>
|
||||
</AutoColumn>
|
||||
</LightCard>
|
||||
</AutoColumn>
|
||||
</AutoColumn>
|
||||
)
|
||||
}
|
||||
@@ -1,49 +1,42 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { RowBetween } from '../Row'
|
||||
import React, { useContext } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { transparentize } from 'polished'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { TYPE } from '../../theme'
|
||||
|
||||
const Wrapper = styled(AutoColumn)``
|
||||
const Wrapper = styled(AutoColumn)`
|
||||
margin-right: 8px;
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
const Grouping = styled(RowBetween)`
|
||||
width: 50%;
|
||||
const Grouping = styled(AutoColumn)`
|
||||
width: fit-content;
|
||||
padding: 4px;
|
||||
/* background-color: ${({ theme }) => theme.bg2}; */
|
||||
border-radius: 16px;
|
||||
`
|
||||
|
||||
const Circle = styled.div<{ confirmed?: boolean; disabled?: boolean }>`
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background-color: ${({ theme, confirmed, disabled }) =>
|
||||
disabled ? theme.bg4 : confirmed ? theme.green1 : theme.primary1};
|
||||
disabled ? theme.bg3 : confirmed ? theme.green1 : theme.primary1};
|
||||
border-radius: 50%;
|
||||
color: ${({ theme }) => theme.white};
|
||||
color: ${({ theme, disabled }) => (disabled ? theme.text3 : theme.text1)};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 8px;
|
||||
font-size: 12px;
|
||||
font-size: 16px;
|
||||
padding: 1rem;
|
||||
`
|
||||
|
||||
const CircleRow = styled.div`
|
||||
width: calc(100% - 20px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Connector = styled.div<{ prevConfirmed?: boolean; disabled?: boolean }>`
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: ;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
${({ theme, prevConfirmed, disabled }) =>
|
||||
disabled ? theme.bg4 : transparentize(0.5, prevConfirmed ? theme.green1 : theme.primary1)}
|
||||
0%,
|
||||
${({ theme, prevConfirmed, disabled }) => (disabled ? theme.bg4 : prevConfirmed ? theme.primary1 : theme.bg4)} 80%
|
||||
);
|
||||
opacity: 0.6;
|
||||
`
|
||||
|
||||
interface ProgressCirclesProps {
|
||||
steps: boolean[]
|
||||
disabled?: boolean
|
||||
@@ -60,6 +53,8 @@ interface ProgressCirclesProps {
|
||||
* @param steps array of booleans where true means step is complete
|
||||
*/
|
||||
export default function ProgressCircles({ steps, disabled = false, ...rest }: ProgressCirclesProps) {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
return (
|
||||
<Wrapper justify={'center'} {...rest}>
|
||||
<Grouping>
|
||||
@@ -67,13 +62,13 @@ export default function ProgressCircles({ steps, disabled = false, ...rest }: Pr
|
||||
return (
|
||||
<CircleRow key={i}>
|
||||
<Circle confirmed={step} disabled={disabled || (!steps[i - 1] && i !== 0)}>
|
||||
{step ? '✓' : i + 1}
|
||||
{step ? '✓' : i + 1 + '.'}
|
||||
</Circle>
|
||||
<Connector prevConfirmed={step} disabled={disabled} />
|
||||
<TYPE.main color={theme.text4}>|</TYPE.main>
|
||||
</CircleRow>
|
||||
)
|
||||
})}
|
||||
<Circle disabled={disabled || !steps[steps.length - 1]}>{steps.length + 1}</Circle>
|
||||
<Circle disabled={disabled || !steps[steps.length - 1]}>{steps.length + 1 + '.'}</Circle>
|
||||
</Grouping>
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { HelpCircle as Question } from 'react-feather'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import Tooltip from '../Tooltip'
|
||||
|
||||
const QuestionWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.2rem;
|
||||
padding: 0px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
cursor: default;
|
||||
border-radius: 36px;
|
||||
font-size: 12px;
|
||||
background-color: ${({ theme }) => theme.bg2};
|
||||
color: ${({ theme }) => theme.text2};
|
||||
|
||||
@@ -44,20 +46,20 @@ const LightQuestionWrapper = styled.div`
|
||||
`
|
||||
|
||||
const QuestionMark = styled.span`
|
||||
font-size: 1rem;
|
||||
font-size: 14px;
|
||||
`
|
||||
|
||||
export default function QuestionHelper({ text }: { text: string }) {
|
||||
export default function QuestionHelper({ text }: { text: string; size?: number }) {
|
||||
const [show, setShow] = useState<boolean>(false)
|
||||
|
||||
const open = useCallback(() => setShow(true), [setShow])
|
||||
const close = useCallback(() => setShow(false), [setShow])
|
||||
|
||||
return (
|
||||
<span style={{ marginLeft: 4 }}>
|
||||
<span style={{ marginLeft: 4, display: 'flex', alignItems: 'center' }}>
|
||||
<Tooltip text={text} show={show}>
|
||||
<QuestionWrapper onClick={open} onMouseEnter={open} onMouseLeave={close}>
|
||||
<Question size={16} />
|
||||
<QuestionMark>?</QuestionMark>
|
||||
</QuestionWrapper>
|
||||
</Tooltip>
|
||||
</span>
|
||||
|
||||
70
src/components/RangeSelector/index.tsx
Normal file
70
src/components/RangeSelector/index.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React from 'react'
|
||||
import { Currency, Price, Token } from '@uniswap/sdk-core'
|
||||
import StepCounter from 'components/InputStepCounter/InputStepCounter'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { useActiveWeb3React } from 'hooks'
|
||||
import { wrappedCurrency } from 'utils/wrappedCurrency'
|
||||
|
||||
// currencyA is the base token
|
||||
export default function RangeSelector({
|
||||
priceLower,
|
||||
priceUpper,
|
||||
onLeftRangeInput,
|
||||
onRightRangeInput,
|
||||
getDecrementLower,
|
||||
getIncrementLower,
|
||||
getDecrementUpper,
|
||||
getIncrementUpper,
|
||||
currencyA,
|
||||
currencyB,
|
||||
feeAmount,
|
||||
}: {
|
||||
priceLower?: Price<Token, Token>
|
||||
priceUpper?: Price<Token, Token>
|
||||
getDecrementLower: () => string
|
||||
getIncrementLower: () => string
|
||||
getDecrementUpper: () => string
|
||||
getIncrementUpper: () => string
|
||||
onLeftRangeInput: (typedValue: string) => void
|
||||
onRightRangeInput: (typedValue: string) => void
|
||||
currencyA?: Currency | null
|
||||
currencyB?: Currency | null
|
||||
feeAmount?: number
|
||||
}) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const tokenA = wrappedCurrency(currencyA ?? undefined, chainId)
|
||||
const tokenB = wrappedCurrency(currencyB ?? undefined, chainId)
|
||||
const isSorted = tokenA && tokenB && tokenA.sortsBefore(tokenB)
|
||||
|
||||
const leftPrice = isSorted ? priceLower : priceUpper?.invert()
|
||||
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={'Min Price'}
|
||||
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={'Max Price'}
|
||||
/>
|
||||
</RowBetween>
|
||||
)
|
||||
}
|
||||
37
src/components/RateToggle/index.tsx
Normal file
37
src/components/RateToggle/index.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { ToggleElement, ToggleWrapper } from 'components/Toggle/MultiToggle'
|
||||
import { useActiveWeb3React } from 'hooks'
|
||||
import { wrappedCurrency } from 'utils/wrappedCurrency'
|
||||
|
||||
// the order of displayed base currencies from left to right is always in sort order
|
||||
// currencyA is treated as the preferred base currency
|
||||
export default function RateToggle({
|
||||
currencyA,
|
||||
currencyB,
|
||||
handleRateToggle,
|
||||
}: {
|
||||
currencyA: Currency
|
||||
currencyB: Currency
|
||||
handleRateToggle: () => void
|
||||
}) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
const tokenA = wrappedCurrency(currencyA, chainId)
|
||||
const tokenB = wrappedCurrency(currencyB, chainId)
|
||||
|
||||
const isSorted = tokenA && tokenB && tokenA.sortsBefore(tokenB)
|
||||
|
||||
return tokenA && tokenB ? (
|
||||
<div style={{ width: 'fit-content', display: 'flex', alignItems: 'center' }}>
|
||||
<ToggleWrapper width="fit-content">
|
||||
<ToggleElement isActive={isSorted} fontSize="12px" onClick={handleRateToggle}>
|
||||
{isSorted ? currencyA.symbol + ' price ' : currencyB.symbol + ' price '}
|
||||
</ToggleElement>
|
||||
<ToggleElement isActive={!isSorted} fontSize="12px" onClick={handleRateToggle}>
|
||||
{isSorted ? currencyB.symbol + ' price ' : currencyA.symbol + ' price '}
|
||||
</ToggleElement>
|
||||
</ToggleWrapper>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Box } from 'rebass/styled-components'
|
||||
|
||||
const Row = styled(Box)<{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import { ChainId, Currency, currencyEquals, ETHER, Token } from '@uniswap/sdk'
|
||||
import styled from 'styled-components'
|
||||
import { ChainId, Currency, currencyEquals, Token, ETHER } from '@uniswap/sdk-core'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { SUGGESTED_BASES } from '../../constants'
|
||||
import { AutoColumn } from '../Column'
|
||||
@@ -28,7 +28,7 @@ const BaseWrapper = styled.div<{ disable?: boolean }>`
|
||||
export default function CommonBases({
|
||||
chainId,
|
||||
onSelect,
|
||||
selectedCurrency
|
||||
selectedCurrency,
|
||||
}: {
|
||||
chainId?: ChainId
|
||||
selectedCurrency?: Currency | null
|
||||
@@ -49,15 +49,15 @@ export default function CommonBases({
|
||||
onSelect(ETHER)
|
||||
}
|
||||
}}
|
||||
disable={selectedCurrency === ETHER}
|
||||
disable={selectedCurrency?.isEther}
|
||||
>
|
||||
<CurrencyLogo currency={ETHER} style={{ marginRight: 8 }} />
|
||||
<Text fontWeight={500} fontSize={16}>
|
||||
ETH
|
||||
</Text>
|
||||
</BaseWrapper>
|
||||
{(chainId ? SUGGESTED_BASES[chainId] : []).map((token: Token) => {
|
||||
const selected = selectedCurrency instanceof Token && selectedCurrency.address === token.address
|
||||
{(typeof chainId === 'number' ? SUGGESTED_BASES[chainId] ?? [] : []).map((token: Token) => {
|
||||
const selected = selectedCurrency?.isToken && selectedCurrency.address === token.address
|
||||
return (
|
||||
<BaseWrapper onClick={() => !selected && onSelect(token)} disable={selected} key={token.address}>
|
||||
<CurrencyLogo currency={token} style={{ marginRight: 8 }} />
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Currency, CurrencyAmount, currencyEquals, ETHER, Token } from '@uniswap/sdk'
|
||||
import { Currency, CurrencyAmount, currencyEquals, Token } from '@uniswap/sdk-core'
|
||||
import React, { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { WrappedTokenInfo, useCombinedActiveList } from '../../state/lists/hooks'
|
||||
import { useCombinedActiveList } from '../../state/lists/hooks'
|
||||
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
|
||||
import { useCurrencyBalance } from '../../state/wallet/hooks'
|
||||
import { TYPE } from '../../theme'
|
||||
import { useIsUserAddedToken, useAllInactiveTokens } from '../../hooks/Tokens'
|
||||
import { useIsUserAddedToken } from '../../hooks/Tokens'
|
||||
import Column from '../Column'
|
||||
import { RowFixed, RowBetween } from '../Row'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
@@ -23,7 +24,7 @@ import QuestionHelper from 'components/QuestionHelper'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
|
||||
function currencyKey(currency: Currency): string {
|
||||
return currency instanceof Token ? currency.address : currency === ETHER ? 'ETHER' : ''
|
||||
return currency.isToken ? currency.address : 'ETHER'
|
||||
}
|
||||
|
||||
const StyledBalanceText = styled(Text)`
|
||||
@@ -55,7 +56,7 @@ const FixedContentRow = styled.div`
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
function Balance({ balance }: { balance: CurrencyAmount }) {
|
||||
function Balance({ balance }: { balance: CurrencyAmount<Currency> }) {
|
||||
return <StyledBalanceText title={balance.toExact()}>{balance.toSignificant(4)}</StyledBalanceText>
|
||||
}
|
||||
|
||||
@@ -102,7 +103,7 @@ function CurrencyRow({
|
||||
onSelect,
|
||||
isSelected,
|
||||
otherSelected,
|
||||
style
|
||||
style,
|
||||
}: {
|
||||
currency: Currency
|
||||
onSelect: () => void
|
||||
@@ -113,7 +114,7 @@ function CurrencyRow({
|
||||
const { account } = useActiveWeb3React()
|
||||
const key = currencyKey(currency)
|
||||
const selectedTokenList = useCombinedActiveList()
|
||||
const isOnSelectedList = isTokenOnList(selectedTokenList, currency)
|
||||
const isOnSelectedList = isTokenOnList(selectedTokenList, currency.isToken ? currency : undefined)
|
||||
const customAdded = useIsUserAddedToken(currency)
|
||||
const balance = useCurrencyBalance(account ?? undefined, currency)
|
||||
|
||||
@@ -132,7 +133,7 @@ function CurrencyRow({
|
||||
{currency.symbol}
|
||||
</Text>
|
||||
<TYPE.darkGray ml="0px" fontSize={'12px'} fontWeight={300}>
|
||||
{currency.name} {!isOnSelectedList && customAdded && '• Added by user'}
|
||||
{currency.name} {!currency.isEther && !isOnSelectedList && customAdded && '• Added by user'}
|
||||
</TYPE.darkGray>
|
||||
</Column>
|
||||
<TokenTags currency={currency} />
|
||||
@@ -143,84 +144,84 @@ function CurrencyRow({
|
||||
)
|
||||
}
|
||||
|
||||
const BREAK_LINE = 'BREAK'
|
||||
type BreakLine = typeof BREAK_LINE
|
||||
function isBreakLine(x: unknown): x is BreakLine {
|
||||
return x === BREAK_LINE
|
||||
}
|
||||
|
||||
function BreakLineComponent({ style }: { style: CSSProperties }) {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<FixedContentRow style={style}>
|
||||
<LightGreyCard padding="8px 12px" borderRadius="8px">
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<TokenListLogoWrapper src={TokenListLogo} />
|
||||
<TYPE.main ml="6px" fontSize="12px" color={theme.text1}>
|
||||
Expanded results from inactive Token Lists
|
||||
</TYPE.main>
|
||||
</RowFixed>
|
||||
<QuestionHelper text="Tokens from inactive lists. Import specific tokens below or click 'Manage' to activate more lists." />
|
||||
</RowBetween>
|
||||
</LightGreyCard>
|
||||
</FixedContentRow>
|
||||
)
|
||||
}
|
||||
|
||||
export default function CurrencyList({
|
||||
height,
|
||||
currencies,
|
||||
otherListTokens,
|
||||
selectedCurrency,
|
||||
onCurrencySelect,
|
||||
otherCurrency,
|
||||
fixedListRef,
|
||||
showETH,
|
||||
showImportView,
|
||||
setImportToken,
|
||||
breakIndex
|
||||
}: {
|
||||
height: number
|
||||
currencies: Currency[]
|
||||
otherListTokens?: WrappedTokenInfo[]
|
||||
selectedCurrency?: Currency | null
|
||||
onCurrencySelect: (currency: Currency) => void
|
||||
otherCurrency?: Currency | null
|
||||
fixedListRef?: MutableRefObject<FixedSizeList | undefined>
|
||||
showETH: boolean
|
||||
showImportView: () => void
|
||||
setImportToken: (token: Token) => void
|
||||
breakIndex: number | undefined
|
||||
}) {
|
||||
const itemData: (Currency | undefined)[] = useMemo(() => {
|
||||
let formatted: (Currency | undefined)[] = showETH ? [Currency.ETHER, ...currencies] : currencies
|
||||
if (breakIndex !== undefined) {
|
||||
formatted = [...formatted.slice(0, breakIndex), undefined, ...formatted.slice(breakIndex, formatted.length)]
|
||||
const itemData: (Currency | BreakLine)[] = useMemo(() => {
|
||||
if (otherListTokens && otherListTokens?.length > 0) {
|
||||
return [...currencies, BREAK_LINE, ...otherListTokens]
|
||||
}
|
||||
return formatted
|
||||
}, [breakIndex, currencies, showETH])
|
||||
return currencies
|
||||
}, [currencies, otherListTokens])
|
||||
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const theme = useTheme()
|
||||
|
||||
const inactiveTokens: {
|
||||
[address: string]: Token
|
||||
} = useAllInactiveTokens()
|
||||
|
||||
const Row = useCallback(
|
||||
({ data, index, style }) => {
|
||||
const currency: Currency = data[index]
|
||||
const isSelected = Boolean(selectedCurrency && currencyEquals(selectedCurrency, currency))
|
||||
const otherSelected = Boolean(otherCurrency && currencyEquals(otherCurrency, currency))
|
||||
const handleSelect = () => onCurrencySelect(currency)
|
||||
function TokenRow({ data, index, style }) {
|
||||
const row: Currency | BreakLine = data[index]
|
||||
|
||||
if (isBreakLine(row)) {
|
||||
return <BreakLineComponent style={style} />
|
||||
}
|
||||
|
||||
const currency = row
|
||||
|
||||
const isSelected = Boolean(currency && selectedCurrency && currencyEquals(selectedCurrency, currency))
|
||||
const otherSelected = Boolean(currency && otherCurrency && currencyEquals(otherCurrency, currency))
|
||||
const handleSelect = () => currency && onCurrencySelect(currency)
|
||||
|
||||
const token = wrappedCurrency(currency, chainId)
|
||||
|
||||
const showImport = inactiveTokens && token && Object.keys(inactiveTokens).includes(token.address)
|
||||
|
||||
if (index === breakIndex || !data) {
|
||||
return (
|
||||
<FixedContentRow style={style}>
|
||||
<LightGreyCard padding="8px 12px" borderRadius="8px">
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<TokenListLogoWrapper src={TokenListLogo} />
|
||||
<TYPE.main ml="6px" fontSize="12px" color={theme.text1}>
|
||||
Expanded results from inactive Token Lists
|
||||
</TYPE.main>
|
||||
</RowFixed>
|
||||
<QuestionHelper text="Tokens from inactive lists. Import specific tokens below or click 'Manage' to activate more lists." />
|
||||
</RowBetween>
|
||||
</LightGreyCard>
|
||||
</FixedContentRow>
|
||||
)
|
||||
}
|
||||
const showImport = index > currencies.length
|
||||
|
||||
if (showImport && token) {
|
||||
return (
|
||||
<ImportRow
|
||||
style={style}
|
||||
token={token}
|
||||
showImportView={showImportView}
|
||||
setImportToken={setImportToken}
|
||||
dim={true}
|
||||
/>
|
||||
<ImportRow style={style} token={token} showImportView={showImportView} setImportToken={setImportToken} dim />
|
||||
)
|
||||
} else {
|
||||
} else if (currency) {
|
||||
return (
|
||||
<CurrencyRow
|
||||
style={style}
|
||||
@@ -230,22 +231,18 @@ export default function CurrencyList({
|
||||
otherSelected={otherSelected}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
[
|
||||
chainId,
|
||||
inactiveTokens,
|
||||
onCurrencySelect,
|
||||
otherCurrency,
|
||||
selectedCurrency,
|
||||
setImportToken,
|
||||
showImportView,
|
||||
breakIndex,
|
||||
theme.text1
|
||||
]
|
||||
[chainId, currencies.length, onCurrencySelect, otherCurrency, selectedCurrency, setImportToken, showImportView]
|
||||
)
|
||||
|
||||
const itemKey = useCallback((index: number, data: any) => currencyKey(data[index]), [])
|
||||
const itemKey = useCallback((index: number, data: typeof itemData) => {
|
||||
const currency = data[index]
|
||||
if (isBreakLine(currency)) return BREAK_LINE
|
||||
return currencyKey(currency)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<FixedSizeList
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Currency, ETHER, Token } from '@uniswap/sdk'
|
||||
import { Currency, ETHER, Token } from '@uniswap/sdk-core'
|
||||
import React, { KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useAllTokens, useToken, useIsUserAddedToken, useFoundOnInactiveList } from '../../hooks/Tokens'
|
||||
import { useAllTokens, useToken, useIsUserAddedToken, useSearchInactiveTokenLists } from '../../hooks/Tokens'
|
||||
import { CloseIcon, TYPE, ButtonText, IconWrapper } from '../../theme'
|
||||
import { isAddress } from '../../utils'
|
||||
import Column from '../Column'
|
||||
@@ -16,7 +16,7 @@ import { filterTokens, useSortedTokensByQuery } from './filtering'
|
||||
import { useTokenComparator } from './sorting'
|
||||
import { PaddedColumn, SearchInput, Separator } from './styleds'
|
||||
import AutoSizer from 'react-virtualized-auto-sizer'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import useToggle from 'hooks/useToggle'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
@@ -61,7 +61,7 @@ export function CurrencySearch({
|
||||
isOpen,
|
||||
showManageView,
|
||||
showImportView,
|
||||
setImportToken
|
||||
setImportToken,
|
||||
}: CurrencySearchProps) {
|
||||
const { t } = useTranslation()
|
||||
const { chainId } = useActiveWeb3React()
|
||||
@@ -79,7 +79,9 @@ export function CurrencySearch({
|
||||
|
||||
// if they input an address, use it
|
||||
const isAddressSearch = isAddress(debouncedQuery)
|
||||
|
||||
const searchToken = useToken(debouncedQuery)
|
||||
|
||||
const searchTokenIsAdded = useIsUserAddedToken(searchToken)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -87,16 +89,11 @@ export function CurrencySearch({
|
||||
ReactGA.event({
|
||||
category: 'Currency Select',
|
||||
action: 'Search by address',
|
||||
label: isAddressSearch
|
||||
label: isAddressSearch,
|
||||
})
|
||||
}
|
||||
}, [isAddressSearch])
|
||||
|
||||
const showETH: boolean = useMemo(() => {
|
||||
const s = debouncedQuery.toLowerCase().trim()
|
||||
return s === '' || s === 'e' || s === 'et' || s === 'eth'
|
||||
}, [debouncedQuery])
|
||||
|
||||
const tokenComparator = useTokenComparator(invertSearchOrder)
|
||||
|
||||
const filteredTokens: Token[] = useMemo(() => {
|
||||
@@ -109,6 +106,14 @@ export function CurrencySearch({
|
||||
|
||||
const filteredSortedTokens = useSortedTokensByQuery(sortedTokens, debouncedQuery)
|
||||
|
||||
const filteredSortedTokensWithETH: Currency[] = useMemo(() => {
|
||||
const s = debouncedQuery.toLowerCase().trim()
|
||||
if (s === '' || s === 'e' || s === 'et' || s === 'eth') {
|
||||
return [ETHER, ...filteredSortedTokens]
|
||||
}
|
||||
return filteredSortedTokens
|
||||
}, [debouncedQuery, filteredSortedTokens])
|
||||
|
||||
const handleCurrencySelect = useCallback(
|
||||
(currency: Currency) => {
|
||||
onCurrencySelect(currency)
|
||||
@@ -124,7 +129,7 @@ export function CurrencySearch({
|
||||
|
||||
// manage focus on modal show
|
||||
const inputRef = useRef<HTMLInputElement>()
|
||||
const handleInput = useCallback(event => {
|
||||
const handleInput = useCallback((event) => {
|
||||
const input = event.target.value
|
||||
const checksummedInput = isAddress(input)
|
||||
setSearchQuery(checksummedInput || input)
|
||||
@@ -137,17 +142,17 @@ export function CurrencySearch({
|
||||
const s = debouncedQuery.toLowerCase().trim()
|
||||
if (s === 'eth') {
|
||||
handleCurrencySelect(ETHER)
|
||||
} else if (filteredSortedTokens.length > 0) {
|
||||
} else if (filteredSortedTokensWithETH.length > 0) {
|
||||
if (
|
||||
filteredSortedTokens[0].symbol?.toLowerCase() === debouncedQuery.trim().toLowerCase() ||
|
||||
filteredSortedTokens.length === 1
|
||||
filteredSortedTokensWithETH[0].symbol?.toLowerCase() === debouncedQuery.trim().toLowerCase() ||
|
||||
filteredSortedTokensWithETH.length === 1
|
||||
) {
|
||||
handleCurrencySelect(filteredSortedTokens[0])
|
||||
handleCurrencySelect(filteredSortedTokensWithETH[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[filteredSortedTokens, handleCurrencySelect, debouncedQuery]
|
||||
[filteredSortedTokensWithETH, handleCurrencySelect, debouncedQuery]
|
||||
)
|
||||
|
||||
// menu ui
|
||||
@@ -156,8 +161,9 @@ export function CurrencySearch({
|
||||
useOnClickOutside(node, open ? toggle : undefined)
|
||||
|
||||
// if no results on main list, show option to expand into inactive
|
||||
const inactiveTokens = useFoundOnInactiveList(debouncedQuery)
|
||||
const filteredInactiveTokens: Token[] = useSortedTokensByQuery(inactiveTokens, debouncedQuery)
|
||||
const filteredInactiveTokens = useSearchInactiveTokenLists(
|
||||
filteredTokens.length === 0 || (debouncedQuery.length > 2 && !isAddressSearch) ? debouncedQuery : undefined
|
||||
)
|
||||
|
||||
return (
|
||||
<ContentWrapper>
|
||||
@@ -195,11 +201,8 @@ export function CurrencySearch({
|
||||
{({ height }) => (
|
||||
<CurrencyList
|
||||
height={height}
|
||||
showETH={showETH}
|
||||
currencies={
|
||||
filteredInactiveTokens ? filteredSortedTokens.concat(filteredInactiveTokens) : filteredSortedTokens
|
||||
}
|
||||
breakIndex={inactiveTokens && filteredSortedTokens ? filteredSortedTokens.length : undefined}
|
||||
currencies={filteredSortedTokensWithETH}
|
||||
otherListTokens={filteredInactiveTokens}
|
||||
onCurrencySelect={handleCurrencySelect}
|
||||
otherCurrency={otherSelectedCurrency}
|
||||
selectedCurrency={selectedCurrency}
|
||||
@@ -224,7 +227,7 @@ export function CurrencySearch({
|
||||
<IconWrapper size="16px" marginRight="6px">
|
||||
<Edit />
|
||||
</IconWrapper>
|
||||
<TYPE.main color={theme.blue1}>Manage</TYPE.main>
|
||||
<TYPE.main color={theme.blue1}>Manage Token Lists</TYPE.main>
|
||||
</RowFixed>
|
||||
</ButtonText>
|
||||
</Row>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Currency, Token } from '@uniswap/sdk'
|
||||
import { Currency, Token } from '@uniswap/sdk-core'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import useLast from '../../hooks/useLast'
|
||||
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
|
||||
import Modal from '../Modal'
|
||||
import { CurrencySearch } from './CurrencySearch'
|
||||
import { ImportToken } from './ImportToken'
|
||||
@@ -22,7 +23,7 @@ export enum CurrencyModalView {
|
||||
search,
|
||||
manage,
|
||||
importToken,
|
||||
importList
|
||||
importList,
|
||||
}
|
||||
|
||||
export default function CurrencySearchModal({
|
||||
@@ -31,7 +32,7 @@ export default function CurrencySearchModal({
|
||||
onCurrencySelect,
|
||||
selectedCurrency,
|
||||
otherSelectedCurrency,
|
||||
showCommonBases = false
|
||||
showCommonBases = false,
|
||||
}: CurrencySearchModalProps) {
|
||||
const [modalView, setModalView] = useState<CurrencyModalView>(CurrencyModalView.manage)
|
||||
const lastOpen = useLast(isOpen)
|
||||
@@ -81,6 +82,7 @@ export default function CurrencySearchModal({
|
||||
<ImportToken
|
||||
tokens={[importToken]}
|
||||
onDismiss={onDismiss}
|
||||
list={importToken instanceof WrappedTokenInfo ? importToken.list : undefined}
|
||||
onBack={() =>
|
||||
setModalView(prevView && prevView !== CurrencyModalView.importToken ? prevView : CurrencyModalView.search)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import ReactGA from 'react-ga'
|
||||
import { TYPE, CloseIcon } from 'theme'
|
||||
import Card from 'components/Card'
|
||||
@@ -56,7 +56,7 @@ export function ImportList({ listURL, list, setModalView, onDismiss }: ImportPro
|
||||
ReactGA.event({
|
||||
category: 'Lists',
|
||||
action: 'Add List',
|
||||
label: listURL
|
||||
label: listURL,
|
||||
})
|
||||
|
||||
// turn list on
|
||||
@@ -64,11 +64,11 @@ export function ImportList({ listURL, list, setModalView, onDismiss }: ImportPro
|
||||
// go back to lists
|
||||
setModalView(CurrencyModalView.manage)
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
ReactGA.event({
|
||||
category: 'Lists',
|
||||
action: 'Add List Failed',
|
||||
label: listURL
|
||||
label: listURL,
|
||||
})
|
||||
setAddError(error.message)
|
||||
dispatch(removeList(listURL))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user