Compare commits

...

288 Commits

Author SHA1 Message Date
Moody Salem
54f59e02fd add page url to the issue template 2021-05-12 16:17:46 -05:00
Moody Salem
7950e5c083 fixes https://github.com/Uniswap/uniswap-interface/issues/1548 2021-05-12 16:16:00 -05:00
Ian Lapham
dd33205bf6 update string in token amount formatting (#1539) 2021-05-12 14:19:01 -04:00
Moody Salem
397a20b9ec fix the issue title 2021-05-12 13:14:02 -05:00
Moody Salem
0aac0b43aa clean up the error boundary 2021-05-12 13:11:10 -05:00
Ian Lapham
702500794d truncate and format large numbers (#1518)
* truncate and format large numbers

* update truncation shorthand

* update logic for detecting small amount
2021-05-12 14:08:05 -04:00
Moody Salem
8bea95fab2 fix the transaction deadline errors 2021-05-12 13:01:31 -05:00
Moody Salem
27094c87f2 clean up price impact formatting 2021-05-12 12:39:21 -05:00
Justin Domingue
bede9171c3 add header background on scroll body overlap (#1531)
* add header background-color on scroll overlap

* slide background in/out on scroll

* remove unused import

Co-authored-by: Justin Domingue <domingue.justin@gmail.com>
2021-05-12 13:19:43 -04:00
Justin Domingue
251d7c0bc2 Fix NFT SVG performance issue in browser (#1509)
* only animate NFT SVG on hover by using a canvas

* handle high dpis

* animation transition between canvas and img

* set start state to not animated

* removed animations that were causing issues on Firefox

* simplify code

* remove debugger statement

* remove useEffect in favor of an event handler

* hide canvas without unmounting to avoid blinking

* fix lint error

* fix flicker on hover by leaving canvas always visible

* add comment about z-index

Co-authored-by: Justin Domingue <domingue.justin@gmail.com>
2021-05-12 13:02:52 -04:00
Moody Salem
285e4f28f5 fix bug in fiat value price impact display 2021-05-12 11:56:18 -05:00
Moody Salem
3aa045303a remove a few unused isToken calls 2021-05-12 11:43:30 -05:00
Noah Zinsmeister
e0a7c3794e bump gas margin to 20% 2021-05-12 12:35:46 -04:00
Noah Zinsmeister
f5fc5da341 fix erroneous addition of tokensOwned{0,1} (#1533)
only pass tokenId to useV3PositionFees
2021-05-12 12:20:20 -04:00
Moody Salem
bea5c0484b fix lint errors, show source list in extra tokens 2021-05-12 09:32:24 -05:00
Moody Salem
27960532ca refactor: use new sdk version (#1472)
* do not construct tokens for wrapped token info

* some cleanup of the wrapped token info

* back to extends, bump sdk core version via v2/v3 sdk updates

* Revert "back to extends, bump sdk core version via v2/v3 sdk updates"

This reverts commit 92cc5073

* update the sdk version

* fix some more uses of instanceof

* finish the refactor

* mess with the currency list performance

* start replacing with the latest v3/v2 sdks

* raw -> quotient

* more cleanup

* finish the refactor

* clean up currency list refactor

* fix list rendering

* perf(token lists): improve app performance when there are large inactive token lists (#1510)

* improve inactive token lists performance

* cleanup before larger changes to combine inactive lists

* only do the search if the query does not match any active lists, limit the number of results

* more performance improvements

* search inactive lists more aggressively
2021-05-12 08:52:17 -05:00
Ian Lapham
37a4e2f6e3 More UI bug fixes (#1515)
* fix for error token map parsings

* update varios UI styles

* update padding on & amounts
2021-05-11 22:29:25 -04:00
Moody Salem
19a3b12ca8 bump typechain for faster/less noisy type generation 2021-05-11 16:43:08 -05:00
Noah Zinsmeister
22c1ddf393 swaps to .5% slippage 2021-05-11 15:00:51 -04:00
Noah Zinsmeister
b44ae1a267 clean up parseCurrencyFromURLParameter 2021-05-11 14:19:56 -04:00
Ian Lapham
418dcf0cb2 Various bug fixes (#1501)
* fix for error token map parsings

* fix error on formatting sig figs

* fix various bugs

* no hover cursor

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2021-05-11 14:03:02 -04:00
Noah Zinsmeister
58a508c9d6 .25% -> .30% slippage for v3 swaps 2021-05-11 13:53:15 -04:00
Jorropo
3198129af2 feat(routing): support mirror protocol routing as additional bases (#1375)
* feat(routing): support mirror protocol routing as additional bases

* Fix code style issues with ESLint

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
2021-05-11 13:28:50 -04:00
John Shutt
89d484d882 feat(uma): uma call option routing (#1385)
* feat(uma): uma call option routing

Signed-off-by: John Shutt <john.d.shutt@gmail.com>

* Fix code style issues with ESLint

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
Co-authored-by: Moody Salem <moodysalem@users.noreply.github.com>
2021-05-11 13:25:51 -04:00
Callil Capuozzo
fa4688d96c UI improvements (#1505)
* Change price ratio using slash to "per"

* Fix header, toggle copy and increase copy

* Add clearer V2 and migrate buttons

* Fix link

* fix account modal background color

* tweak sig figs

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2021-05-11 13:25:04 -04:00
Moody Salem
7ee761a59e feat: automatic slippage tolerance (#1463)
* automatic slippage tolerance start

* get it compiling

* out of range/in range behavior of slippage tolerance in add

* small useDerivedSwapInfo refactor

* improve useSwapSlippageTolerance

* fix unit test

* thread placeholder slippage through

* small improvement to slippage input behavior

* fix the display bug

* fix tx settings modal ux

* don't pass props unnecessarily

* switch back to static swap slippage for now

bump migrate slippage to .75%

* fix font size

* add flag for auto slippage migration

validate version updates even more

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2021-05-11 13:00:42 -04:00
jochenboesmans
78e95f6073 Add App-level error boundary, referring users to GitHub issue creation (#1464)
* Add App-level error boundary, referring users to GitHub issue creation on page crashes. (#1452)

* Class component is used as boundary since catching errors is apparently not yet possible with hooks.

* EventListener in window was removed and replaced by error boundary's error catch, which now fires a GA exception. The fields it passes are slightly different because React uses slightly different error types.

* Pre-filling issues with dynamic data is possible with POST requests to GitHub's API, but the GH web client seems to only support pre-fill based on templates. Therefore users still need to copy error info themselves.

* Prefill GitHub issues with crash data.

* Added package 'react-device-detect' to include device data such as OS, browser etc. in crash report.
* Included error stack in issue body.
* Used <code> html tag for displaying stack to user.

* Slightly reduce vertical padding on code block.

* Add ua-parser-js for parsing user agent.

* Revert react-device-detect to ^1.6.2 (which is used for mobile detection etc. in components)
2021-05-11 12:09:01 -04:00
Noah Zinsmeister
c67e57505a make price sig figs more consistent 2021-05-11 10:17:06 -04:00
Justin Domingue
30f7385db7 optimize sandtexture.png with .webp (#1502)
Co-authored-by: Justin Domingue <domingue.justin@gmail.com>
2021-05-10 21:32:53 -04:00
Noah Zinsmeister
c0f58ae810 don't use a signer for callStatic contract 2021-05-10 16:59:11 -04:00
Noah Zinsmeister
54dd5476ca fetch fees directly from collect via callStatic (#1500)
* fetch fees directly from collect via callStatic

* don't clear state
2021-05-10 16:30:02 -04:00
Moody Salem
57786335df fix calculateSlippageAmount (#1497) 2021-05-10 14:22:26 -05:00
Joe Butler
948e01a196 Fix typo (#1454) 2021-05-10 14:01:35 -04:00
Noah Zinsmeister
abf127c596 sdk bugfix bump (#1492) 2021-05-10 13:04:07 -04:00
Moody Salem
4d3f870b93 add a test for calculating slippage amounts 2021-05-09 12:31:45 -05:00
Moody Salem
452f2dc3c0 fix slippage amount bug https://github.com/Uniswap/uniswap-interface/issues/1473 2021-05-09 11:30:56 -05:00
Ian Lapham
b6bd59f2b1 Fix bug on formatted token amounts when decimals < sig fig (#1479)
* fix for error token map parsings

* fix error on formatting sig figs
2021-05-07 16:52:00 -04:00
Noah Zinsmeister
0190b5a408 bump sdk to fix add/remove slippage 2021-05-06 18:20:36 -04:00
Noah Zinsmeister
d6030dcd45 add settings tab to migrate 2021-05-06 17:44:05 -04:00
Moody Salem
f0e2a491dc fix(slippage settings): improve slippage tolerance warnings 2021-05-06 11:19:36 -04:00
Moody Salem
021aab6547 fix(wallet): workaround the ethers bug to fix other wallets 2021-05-06 11:10:45 -04:00
Noah Zinsmeister
81af31eec1 estimate gas in migrate v2 2021-05-06 10:40:19 -04:00
Moody Salem
d3898cf900 fix(wallet): workaround for coinbase wallet / fortmatic 2021-05-06 10:09:41 -04:00
Moody Salem
b8f61d5f90 fix(positions list): base/currency ordering 2021-05-06 09:57:12 -04:00
Moody Salem
6c880d29a6 fix(position fee computation): incorrect sub underflow 2021-05-05 22:54:30 -04:00
Lint Action
a8baa6d6a5 Fix code style issues with ESLint 2021-05-05 21:56:49 +00:00
Callil Capuozzo
00a1dee073 Merge branch 'main' of https://github.com/Uniswap/uniswap-interface into main 2021-05-05 17:54:47 -04:00
Callil Capuozzo
a6de7d7846 add links 2021-05-05 17:54:33 -04:00
Moody Salem
e1a81a9996 uniswap protocol disclaimer 2021-05-05 17:40:22 -04:00
Noah Zinsmeister
0770bab032 try manual approve if erc712 doesn't work (#1397)
* initial impl

* fix async logic
2021-05-05 17:36:07 -04:00
Callil Capuozzo
e5404dbf97 Fix some links 2021-05-05 16:04:40 -04:00
Noah Zinsmeister
78e41848f2 another copy fix 2021-05-05 14:26:53 -04:00
Noah Zinsmeister
77c090534b copy hotfix 2021-05-05 14:26:02 -04:00
Moody Salem
05acbfee88 fix(swap): do not throw an error in the routing if we cannot construct a pool 2021-05-05 14:22:57 -04:00
Callil Capuozzo
64dd2f9ed1 update button styles 2021-05-05 13:58:37 -04:00
Callil Capuozzo
09f30ce0f7 Merge branch 'main' of https://github.com/Uniswap/v3-interface into main 2021-05-05 13:41:47 -04:00
Callil Capuozzo
a49d5382db UI tweaks 2021-05-05 13:41:46 -04:00
Ian Lapham
3affcb8d32 small ux style updates (#160) 2021-05-05 13:18:47 -04:00
Moody Salem
56e759ff78 fix unit test 2021-05-05 13:10:19 -04:00
Callil Capuozzo
f73a166d92 Merge branch 'main' of https://github.com/Uniswap/v3-interface into main 2021-05-05 13:07:26 -04:00
Callil Capuozzo
e1e52c06db update copy 2021-05-05 13:06:26 -04:00
Moody Salem
ca4d915903 fix it everywhere 2021-05-05 13:04:50 -04:00
Moody Salem
bdb6327c13 set initial allowed slippage to 10 bips instead of 50 bips 2021-05-05 13:03:39 -04:00
Noah Zinsmeister
ce81dd5a79 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-05-05 12:47:47 -04:00
Noah Zinsmeister
73f29eea2c don't show approve for both assets...
...in the single-sided liquidity provision case
2021-05-05 12:47:41 -04:00
Moody Salem
92193076c5 fixes https://github.com/Uniswap/v3-interface/issues/161 2021-05-05 12:45:29 -04:00
Moody Salem
1aab086693 Swap interface tweaks (#159)
* fix some nits in the swap interface

* remove some unused css

* tweak the info icon a bit
2021-05-05 12:29:33 -04:00
Noah Zinsmeister
8057cb9fbe fix rate toggle clearing add liquidity state 2021-05-05 12:19:21 -04:00
Ian Lapham
1b798889af add default increment behavior (#157)
Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2021-05-05 12:12:23 -04:00
Connor Martin
1e54b97693 changed to v3 docs links (#138) 2021-05-05 12:05:13 -04:00
Noah Zinsmeister
02c21ef720 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-05-05 12:03:29 -04:00
Noah Zinsmeister
b39aeeb805 fix for inactive positions 2021-05-05 12:03:20 -04:00
Callil Capuozzo
8401a4b9b4 Update styles, change warning modals 2021-05-05 11:43:47 -04:00
Callil Capuozzo
3b27ee94d7 Merge branch 'main' of https://github.com/Uniswap/v3-interface into main 2021-05-05 11:42:04 -04:00
Noah Zinsmeister
660c355273 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-05-05 11:19:02 -04:00
Noah Zinsmeister
cd3c48462d separate v2 + v3 mint reducers
reset v3 mint state on migrate mount/unmount
2021-05-05 11:18:54 -04:00
Moody Salem
acfd5c2720 fixes https://github.com/Uniswap/v3-interface/issues/123 2021-05-05 10:23:45 -04:00
Callil Capuozzo
a5ed12bfc7 Merge branch 'main' of https://github.com/Uniswap/v3-interface into main 2021-05-05 00:08:15 -04:00
Noah Zinsmeister
40f0e619cc fix additional base currency toggle bugs 2021-05-04 22:43:36 -04:00
Noah Zinsmeister
bd817083c9 actually fix isNotUniswap logic 2021-05-04 22:32:01 -04:00
Callil Capuozzo
699bcc25b6 Merge branch 'main' of https://github.com/Uniswap/v3-interface into main 2021-05-04 20:52:27 -04:00
Callil Capuozzo
e88a8effef style tweaks
- bring back gradient background
- improve header
- improve light mode
2021-05-04 20:52:25 -04:00
Ian Lapham
2339817170 multiple bug fixes (#141)
* multiple bug fixes

* remove unused local

* disable currency select on bottom liquidity
2021-05-04 20:17:19 -04:00
Moody Salem
4220cafbd3 fix the usdc permit 2021-05-04 18:22:45 -05:00
Noah Zinsmeister
a61df58599 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-05-04 19:15:46 -04:00
Noah Zinsmeister
1b35128035 clarify migrate ui 2021-05-04 19:15:29 -04:00
Moody Salem
ab5114c5f5 move the compute price impact function out to a utility file 2021-05-04 17:55:51 -05:00
Moody Salem
28c7cfa1f1 formatted price impact 2021-05-04 17:52:23 -05:00
Moody Salem
509b307b67 show some price impact in the interface 2021-05-04 17:51:01 -05:00
Ian Lapham
1e5519de3f add dark mdoe toggle, update some styles (#134) 2021-05-04 18:25:20 -04:00
Noah Zinsmeister
e8587396d3 hide collect fees if not owner
link to owner
2021-05-04 17:48:54 -04:00
Noah Zinsmeister
d69b194ffb close #127 2021-05-04 17:32:24 -04:00
Noah Zinsmeister
1325443025 fix ratio bugs 2021-05-04 17:03:17 -04:00
Moody Salem
1607f8919a code in the mainnet addresses 2021-05-04 14:44:55 -05:00
Moody Salem
873cf9760e fix addresses 2021-05-04 14:39:02 -05:00
Noah Zinsmeister
b39f2fe055 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-05-04 14:58:53 -04:00
Noah Zinsmeister
516e783be6 hot fix for asset composition formula 2021-05-04 14:58:29 -04:00
Callil Capuozzo
9a326fa023 Add liquidity styles (#98)
* Start styles

* Add liquidity styles & consistency

* clean up styles

* remove un-used element

* New styles

* fix errors
2021-05-04 14:57:57 -04:00
Noah Zinsmeister
06d6c711dd closes #94 2021-05-04 14:17:28 -04:00
Noah Zinsmeister
7178746023 highlight 100% position composition 2021-05-04 14:04:30 -04:00
Moody Salem
2efe8250ae rename gorli to goerli 2021-05-04 12:31:51 -05:00
Moody Salem
24d8b4abc9 latest deploy 2021-05-04 12:23:07 -05:00
Noah Zinsmeister
aec18b7eb1 closes #107 2021-05-04 13:22:26 -04:00
Noah Zinsmeister
0c37e81d97 closes #108 2021-05-04 13:20:52 -04:00
Noah Zinsmeister
624c3678c7 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-05-04 13:18:27 -04:00
Noah Zinsmeister
cdae20f2ed fix weth/eth bug
closes #47
2021-05-04 13:18:21 -04:00
Ian Lapham
042967502e recreate bug fixes (#109) 2021-05-04 12:43:43 -04:00
Ian Lapham
eedba9795e Mobile styles (#100)
* mobile styles

* remove unused local

* update react imports

* fix imports

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
2021-05-04 11:05:26 -04:00
Noah Zinsmeister
eaec4a33fc bump v3 sdk 2021-05-04 10:31:23 -04:00
Moody Salem
300dd70804 show currency values and price impact in the confirmation modal (fixes https://github.com/Uniswap/v3-interface/issues/92) 2021-05-04 08:38:47 -05:00
Lint Action
f4e994867e Fix code style issues with ESLint 2021-05-04 01:40:43 +00:00
Moody Salem
7ca79ff12b fix unit tests 2021-05-03 20:39:03 -05:00
Moody Salem
4903258b7c Merge branch 'main' into v3-main
# Conflicts:
#	src/pages/Vote/VotePage.tsx
#	src/pages/Vote/index.tsx
2021-05-03 20:37:44 -05:00
jochenboesmans
fe35ca9db8 use enum for enumerateProposalState #1166 (#1381)
* Use ts enum for ProposalState in state/governance/hooks.ts (#1166)

* Use ProposalState enum in Vote/styled.tsx for determining colors of state text etc. (#1166)
2021-05-03 20:33:34 -05:00
Moody Salem
4bc1b8eb5c Merge branch 'main' into v3-main 2021-05-03 20:30:46 -05:00
AmagiDDmxh
7801695180 fix(block number): Update polling block number breathing behavior (#1379) 2021-05-03 20:30:32 -05:00
Moody Salem
ee69357305 some minor refactoring with price impact functions 2021-05-03 20:25:23 -05:00
Moody Salem
ce7f94f16a hide max button appropriately 2021-05-03 20:17:15 -05:00
Moody Salem
124b83ae56 some clean up in showing the route 2021-05-03 20:13:52 -05:00
Moody Salem
0dcd5743c8 fix price impact handling/coloring 2021-05-03 20:05:20 -05:00
Moody Salem
9fec8dbe93 show percentage difference in price on swap 2021-05-03 17:13:05 -05:00
Noah Zinsmeister
549d4e38c6 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-05-03 17:43:49 -04:00
Noah Zinsmeister
3af6781821 add % breakdown for assets in range
closes #64
2021-05-03 17:43:39 -04:00
Moody Salem
fbccb83edb Merge branch 'main' into v3-main 2021-05-03 16:30:04 -05:00
Moody Salem
ff12b7be10 refactor the fiat values 2021-05-03 16:29:45 -05:00
Callil Capuozzo
4c2cb5b0c1 Swap polish 02 (#93)
* rework swap UI

* Add v2/v3 route toggle UX

* improve button and progress styles

* put optional route in swap header

* Further refinements

* Alt swap layout

* clean up

* Tweak route ui

* merge main

* update swap header

* adjust currency select ui
2021-05-03 16:32:40 -04:00
Callil Capuozzo
d7785942b1 Update migration styles (#78)
* re-work migrate page

* Layout Tweaks

* cleanup

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2021-05-03 15:55:30 -04:00
Noah Zinsmeister
ed801deb15 add fiat value of liquidity + fees 2021-05-03 15:19:59 -04:00
Noah Zinsmeister
c34727641d add basic sushi support (#87) 2021-05-03 14:48:30 -04:00
Ian Lapham
807860aac6 refactor review modals (#60) 2021-05-03 13:37:41 -04:00
Ian Lapham
5a9034fe95 fix for error token map parsings (#1384) 2021-05-03 13:34:01 -04:00
Ian Lapham
ee0db4f2aa fix list parsing on broken tokens (#85)
* fix list parsing on broken tokens

* revert to most recent changes
2021-05-03 13:28:55 -04:00
Ian Lapham
6d5625a1f8 Hot fix for token error parsing (#1382)
* hot fix for token list parsing

* revert tsconfig
2021-05-03 12:11:27 -04:00
Callil Capuozzo
1619386ab4 Position styles (#55)
* Better position list layout WIP

* Position list updates

* add badge data and current price hover

* merge cleanup

* fix missing library

* position page improvements

* Clean up position page and overview

* layout and color updates

* Clean up page

* Clean up position page

* clean up errors

* Add icons

* Merge main

* Position styles tweaks

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2021-05-03 11:50:54 -04:00
Moody Salem
93d33947da fix bug in remove 2021-04-30 22:57:55 -05:00
Moody Salem
91f3e21bd4 some list code cleanup 2021-04-30 22:56:19 -05:00
Moody Salem
e9a432b58e fix a bug in v3 trade routing returning the wrong trade type for output trade 2021-04-30 22:43:29 -05:00
Moody Salem
1f41587ba9 fix the swap notification 2021-04-30 22:38:24 -05:00
Moody Salem
9b639bee65 invalidate signature data for old nonce 2021-04-30 21:23:16 -05:00
Moody Salem
eaddc9e6f8 dai style permit 2021-04-30 21:20:39 -05:00
Noah Zinsmeister
e83a1ec923 undupe migrate menu 2021-04-30 15:55:53 -04:00
Noah Zinsmeister
8021315f87 fix rates in add liquidity 2021-04-30 15:40:39 -04:00
Noah Zinsmeister
0540012bb9 WETH -> ETH in migrate
fix rates in migrate

fix migrate link
2021-04-30 15:12:00 -04:00
Moody Salem
3f6bf607dd replace uniswap.info links 2021-04-30 13:14:31 -05:00
Moody Salem
828f7ee446 Use permit in swap (#65)
* add a usePermit hook

* semi working

* Fix code style issues with ESLint

* don't override gas, some cleanup in the permit function

* fix permit validity

* some more permit fixes

* nits

* another nit

* use the erc20 permit hook everywhere

* unused exports

* handle missing version

* replace everywhere

* add DAI and todos

* woopsie bug

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
2021-04-30 12:50:57 -05:00
Noah Zinsmeister
432d17bdfd only show buttons if nft is owned 2021-04-29 15:55:39 -04:00
Noah Zinsmeister
9743b211e7 change migration threshold to 2
fix starting price bug
2021-04-29 15:39:44 -04:00
Noah Zinsmeister
dce187e433 fix price bug 2021-04-29 14:18:15 -04:00
Moody Salem
8859ff6979 fix gas estimate bug 2021-04-29 11:56:02 -05:00
Moody Salem
f566a72b06 fix a bug in the logo not passing style down 2021-04-29 11:31:34 -05:00
Moody Salem
e39e5908e7 drop the card in the position page 2021-04-29 11:18:56 -05:00
Moody Salem
c5424d2dcc some tweaks to the token logo 2021-04-29 10:26:12 -05:00
Noah Zinsmeister
8b4bc167c5 add multicall mainnet address 2021-04-29 11:01:53 -04:00
Moody Salem
43bceb232e label nit 2021-04-28 21:23:22 -05:00
Moody Salem
c3909bc1d0 improve the better trade link logic 2021-04-28 20:03:38 -05:00
Moody Salem
b25287923c return fake price data for non mainnet and fill it into the currency input panel 2021-04-28 19:44:10 -05:00
Noah Zinsmeister
65c51ea4aa update to latest sdk 2021-04-28 20:17:22 -04:00
Moody Salem
b0fa08e9b0 show a loading indicator 2021-04-28 19:11:27 -05:00
Moody Salem
b09eb8fb52 mess with the gas estimation code a bit to allow swapping 2021-04-28 18:58:53 -05:00
Moody Salem
5b49cedebb bump addresses 2021-04-28 18:16:49 -05:00
Noah Zinsmeister
ae76f26501 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-04-28 17:59:10 -04:00
Noah Zinsmeister
1cb1ffe2f6 update to latest formatting logic
nits
2021-04-28 17:58:59 -04:00
Moody Salem
d83bc3097d fix some linting errors and some better trade link cleanup 2021-04-28 16:47:03 -05:00
Moody Salem
323edc0fcd consistently use the worst price and amounts in the swap view 2021-04-28 16:42:56 -05:00
Moody Salem
fc258fdf5c don't throw if the trades are not comparable 2021-04-28 13:34:22 -05:00
Moody Salem
2e599dc00e handle loading v3 trade state better 2021-04-28 13:31:30 -05:00
Moody Salem
464a682fcc show the better trade link for v2/v3 2021-04-28 13:27:14 -05:00
Moody Salem
6ace3c211c fix linting errors 2021-04-28 12:55:56 -05:00
Moody Salem
2d0b0c70bf drop the show details stuff 2021-04-28 12:55:28 -05:00
Moody Salem
3c3be3c61a fix some unit tests 2021-04-28 12:17:40 -05:00
Noah Zinsmeister
7660943e97 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-04-27 22:25:48 -04:00
Noah Zinsmeister
d342ccdb78 use sdk for more calldata 2021-04-27 22:24:29 -04:00
Moody Salem
0ea9dc046b bumped v3 sdk version 2021-04-27 19:42:39 -05:00
Moody Salem
3a34c2ec25 eth swaps kinda working 2021-04-27 19:20:05 -05:00
Moody Salem
83b0ef94f9 show the worst amounts/prices instead of current price 2021-04-27 13:07:07 -05:00
Moody Salem
3680b93fb3 make the way we get the v2 router consistent, start working through changes in the swap callback 2021-04-27 11:00:34 -05:00
Moody Salem
8e86ded09b temporarily hude the usdc value, use the disable multihop setting in the v3 routing 2021-04-27 10:35:09 -05:00
Moody Salem
37a50372f6 improve responsiveness of header 2021-04-27 10:16:02 -05:00
Noah Zinsmeister
ab99cc612b add current price 2021-04-27 10:51:12 -04:00
Moody Salem
11a4fa23c1 fix the approve callback from trade bug 2021-04-26 21:26:57 -05:00
Callil Capuozzo
5358b4dc15 New swap layout (#53)
* re-work header

* swap tweaks

* re-work swap in progress

- inline slippage
- hidey details
- better invert UI
- better flip rate ui

* Swap improvements

- new layout order

* New swap layout

* merge main

* get all the tests running

* skip the swap test as well

Co-authored-by: Moody Salem <moody.salem@gmail.com>
2021-04-26 20:59:56 -05:00
Moody Salem
ccbd5dfcf7 show v3 quotes 2021-04-26 19:35:32 -05:00
Noah Zinsmeister
bb17c57a84 nits 2021-04-26 15:51:31 -04:00
Noah Zinsmeister
3b6213f411 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-04-26 14:28:45 -04:00
Noah Zinsmeister
a5251f55de improve stepping behavior 2021-04-26 14:28:36 -04:00
Moody Salem
d846c83afa handle errored calls better 2021-04-26 13:23:53 -05:00
Noah Zinsmeister
00438bea12 fix tick bug 2021-04-26 14:21:42 -04:00
Noah Zinsmeister
0dd5e6b33f Merge remote-tracking branch 'refs/remotes/origin/main' 2021-04-26 14:15:40 -04:00
Noah Zinsmeister
2d7642ed9b simplify range ui 2021-04-26 14:15:32 -04:00
Moody Salem
740e18454f fix linting errors for the preview 2021-04-26 13:01:13 -05:00
Moody Salem
7c17cfc642 bump and use the unchecked trade creation method 2021-04-26 12:29:20 -05:00
Moody Salem
7f61b67947 first pass v3 routing code 2021-04-26 12:17:37 -05:00
Moody Salem
466c0b0142 remove the anonymous credentials from the image (it causes a CORS preflight) 2021-04-26 10:53:55 -05:00
Moody Salem
1c6d9d810e keep list asset requests anonymous 2021-04-26 10:50:34 -05:00
Noah Zinsmeister
c3ad129658 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-04-26 11:20:49 -04:00
Moody Salem
a25a72d0d9 use path encoding from v3-sdk 2021-04-26 10:14:21 -05:00
Noah Zinsmeister
dbc9e85b90 bug fixes 2021-04-26 11:13:02 -04:00
Ian Lapham
7dafb0cfba Remove UI (#52)
* use token0 as base in all calculations

* refactor

* fix price order

* fix existing position ticks

* remove empty space

* basic remove page

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2021-04-25 18:46:53 -04:00
Ian Lapham
da33ec9d2f use token0 as base in all calculations (#51)
* use token0 as base in all calculations

* refactor

* fix price order

* fix existing position ticks

* remove empty space

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2021-04-25 15:45:37 -04:00
Moody Salem
605368629f surface the best v3 route to the swap page 2021-04-24 20:27:56 -05:00
Moody Salem
ca2b84ec0b get the best route in v3 for an exact in swap 2021-04-24 18:52:13 -05:00
Moody Salem
ac7cf35bb7 missed a spot 2021-04-24 17:43:57 -05:00
Moody Salem
bd346030f0 useAllV3Routes, some cleanup 2021-04-24 17:43:14 -05:00
Moody Salem
aa742f415d create some hooks for using in v3 swap routing 2021-04-23 21:32:31 -05:00
Moody Salem
77fa61495f fix loading state flashing in pool page 2021-04-23 21:12:34 -05:00
Moody Salem
bff3811faf use multicall2 2021-04-23 18:26:24 -05:00
Moody Salem
60d1f8743f just a bit more refactoring to remove the data directory 2021-04-23 17:57:48 -05:00
Moody Salem
2d118a904a refactor some hooks into the hooks directory 2021-04-23 17:54:24 -05:00
Moody Salem
b69f08cbe1 allow fetching multiple pools via usePools.ts 2021-04-23 17:31:36 -05:00
Noah Zinsmeister
644ecdbd32 extra / 2021-04-23 13:04:12 -04:00
Noah Zinsmeister
4b47ae6da3 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-04-23 13:03:25 -04:00
Noah Zinsmeister
fe6a46fc0f css bug fix 2021-04-23 13:03:06 -04:00
Moody Salem
539a6e05e5 don't fetch pool tick data 2021-04-23 11:58:07 -05:00
Noah Zinsmeister
3693c83b6e remove liquidity sends ETH now
can collect fees from position page now
2021-04-23 12:08:21 -04:00
Ian Lapham
18408c9c75 Increase Liquidity, component cleanup, preview components (#50)
* basic +/- buttons

* start increase liquidity

* fix v3 remove
2021-04-22 22:58:03 -04:00
Noah Zinsmeister
39f018bae0 small formatting fix 2021-04-22 18:45:36 -04:00
Noah Zinsmeister
c00eb2451f fix small merge bug 2021-04-22 18:43:07 -04:00
Noah Zinsmeister
6b99309fab Merge remote-tracking branch 'refs/remotes/origin/main' 2021-04-22 18:36:20 -04:00
Noah Zinsmeister
976b15986f add fee calculations 2021-04-22 18:36:11 -04:00
Moody Salem
df4c18c8d4 remove v1 swaps and migrate (#46)
* remove v1 swaps and migrate

* simplify

* more delete

* revert route changes

* fix v2 add

* fix integration tests

* try to fix https://github.com/Uniswap/v3-interface/issues/41
2021-04-22 17:10:18 -05:00
Moody Salem
62d3aa4b76 update sdk and addresses 2021-04-22 15:54:34 -05:00
Noah Zinsmeister
361d17c925 add permit support to migration 2021-04-22 15:23:13 -04:00
Noah Zinsmeister
9269f15ffc Merge remote-tracking branch 'refs/remotes/origin/main' 2021-04-21 17:46:33 -04:00
Noah Zinsmeister
925668c7a7 get skeleton remove working 2021-04-21 17:46:25 -04:00
Moody Salem
a64d526206 bump v3-sdk version again (fixing core/periphery dependencies) 2021-04-21 14:19:43 -05:00
Moody Salem
69234eb708 bump v3-sdk version 2021-04-21 14:14:18 -05:00
Moody Salem
f833133689 add margin bottom to text in confirm transaction modal 2021-04-21 14:11:04 -05:00
Moody Salem
c73a2655cb Merge branch 'main' into v3-main 2021-04-21 13:07:12 -05:00
Moody Salem
c55061873c update the v2-sdk version 2021-04-21 13:06:35 -05:00
Moody Salem
d4562a2373 uncomment release workflow lines (will be disabled manually) 2021-04-21 12:48:43 -05:00
Moody Salem
e13369154d Merge branch 'main' into v3-main
# Conflicts:
#	.github/workflows/tests.yaml
2021-04-21 12:37:20 -05:00
Moody Salem
6be54afd12 fix the integration test errors from upgrading ethers 2021-04-21 11:19:34 -05:00
Noah Zinsmeister
d1fad3c4ea update to latest deploy
refactor useV3Positions to work with remove

axe old redirects
2021-04-20 15:31:31 -04:00
Noah Zinsmeister
a1c4b97a18 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-04-19 17:56:00 -04:00
Noah Zinsmeister
f00adc755a refactor range selection and use in migrate
improve migrate
2021-04-19 17:55:52 -04:00
Ian Lapham
e97939c545 update position details page (#43) 2021-04-19 15:49:43 -04:00
Moody Salem
63907b7bc5 fix icon urls 2021-04-19 13:02:04 -05:00
Moody Salem
48ad8e529f more flexible detection of network 2021-04-19 12:55:54 -05:00
Moody Salem
b669ec6976 workaround for detectNetwork on our custom provider 2021-04-19 12:51:16 -05:00
Moody Salem
2d4b60b9dd Merge branch 'main' into v3-main
# Conflicts:
#	.env
#	README.md
#	package.json
#	src/components/TokenWarningModal/index.tsx
#	src/connectors/index.ts
#	src/constants/index.ts
#	src/hooks/Trades.ts
#	src/index.tsx
#	src/state/claim/hooks.ts
#	src/state/user/hooks.tsx
#	yarn.lock
2021-04-19 11:37:33 -05:00
Noah Zinsmeister
9fc096d091 Finish migration (#42)
* start migration (wip)

abstract some add liquidity components

bump deploy version

* add slippage params
2021-04-16 17:35:50 -04:00
Jordan Frankfurt
9f5584c37d Start Position management, bug fix on add amounts (#40)
* very rough positions/pools data fetching and position list rendering

* fix formatting

* fix loading

* position page routing, bug on add page

Co-authored-by: ianlapham <ianlapham@gmail.com>
2021-04-16 15:33:26 -04:00
Ian Lapham
0c0305a53d Add page MVP UI + v3 pool hooks and state (#35)
* WIP start usePool and useDerivedMint hooks

* naming updates

* Use real tick and pool math

* rate updates

* WIP start usePool and useDerivedMint hooks

* naming updates

* Use real tick and pool math

* rate updates

* fix warnings

* fix incorrect import

* clean up state, fix preview

* same token check

* amoutn parse update

* update hard coded chain id

* fix price creation in util

* update 1 amount in price calculation

* update comments

* update tick spacing input

* fix label on counter

* update rate label on range select

* update labels

* fixing pool hook

* clean pool hook

* preserve working rate switching

* reset values on rate switch

* clean up derived hook - setup for testnet

* format slippage amounts and support ETH

* fix import error

* fix package.json dependencies

* silence warnings

* silence more warnings

* bump multicodec and multihashes

* update migrator constants

* update txn to use sdk calldata

* fix txn formatting, update summary

* Squashed commit of the following:

commit b81ff7ca9e57bb8c3823be4c54827e5955fb3d15
Author: ianlapham <ianlapham@gmail.com>
Date:   Mon Apr 12 23:46:09 2021 -0400

    fix txn formatting, update summary

commit b9f91b0746c546602d336c8fd6f614ec9b4f3f19
Author: ianlapham <ianlapham@gmail.com>
Date:   Mon Apr 12 19:29:12 2021 -0400

    update txn to use sdk calldata

commit 20acf704c67cfd4f597494c8cb9c672c6270ae02
Merge: 4431914 2462901
Author: ianlapham <ianlapham@gmail.com>
Date:   Sun Apr 11 20:33:39 2021 -0400

    Merge branch 'minting' of https://github.com/Uniswap/v3-interface into minting

commit 44319146372e1c373b025741ae896fa2476e5765
Author: ianlapham <ianlapham@gmail.com>
Date:   Sun Apr 11 20:32:35 2021 -0400

    update migrator constants

commit 35e0618de06ba316d3a3f327075625760414ab83
Merge: 8927882 c3f65e3
Author: ianlapham <ianlapham@gmail.com>
Date:   Sun Apr 11 20:13:36 2021 -0400

    Merge branch 'main' of https://github.com/Uniswap/v3-interface into minting

commit 24629019e80c368c337a2679a51d4acb1097171c
Author: Noah Zinsmeister <noahwz@gmail.com>
Date:   Fri Apr 9 15:56:25 2021 -0400

    bump multicodec and multihashes

commit 9b5dd1876a64acbf6694d208b608bb0b429e317f
Author: Noah Zinsmeister <noahwz@gmail.com>
Date:   Fri Apr 9 14:59:09 2021 -0400

    silence more warnings

commit 140ddc1b54c7fbdd7ead2fa64bcc302f201d69f5
Author: Noah Zinsmeister <noahwz@gmail.com>
Date:   Fri Apr 9 14:57:58 2021 -0400

    silence warnings

commit 5a2726ebdd4ffaacfb3d8ec7903a944042c1bd9d
Author: Noah Zinsmeister <noahwz@gmail.com>
Date:   Fri Apr 9 14:35:01 2021 -0400

    fix package.json dependencies

commit 7c4d0a40931338de9a6197652b82fdab773483e3
Author: Noah Zinsmeister <noahwz@gmail.com>
Date:   Fri Apr 9 14:21:46 2021 -0400

    fix import error

commit e49ef19cbef7fbdf1737787a439e7cb78ba295b4
Merge: 8927882 c3f65e3
Author: Noah Zinsmeister <noahwz@gmail.com>
Date:   Fri Apr 9 14:08:34 2021 -0400

    Merge branch 'main' into minting

commit 89278825bd798a87d6010a74f8fc1d2b34a8ece1
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 8 15:18:40 2021 -0400

    format slippage amounts and support ETH

commit 9a90b19e9a759cbc0c3e903a983660730c8833ad
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Apr 7 19:43:43 2021 -0400

    clean up derived hook - setup for testnet

commit dc034bc78a147f95f47b077d28a7d6e3165cedd7
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Apr 7 00:48:24 2021 -0400

    reset values on rate switch

commit bb5ccb2c853f7b2c27ec8d2f34f42a1b06f845b9
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Apr 7 00:38:39 2021 -0400

    preserve working rate switching

commit 5312d0ae7015150da48ba304de8c7a02b7d8925c
Author: ianlapham <ianlapham@gmail.com>
Date:   Mon Apr 5 13:52:46 2021 -0400

    clean pool hook

commit 5222de14834e76c37755225be17214a6e798d872
Merge: b2ba466 24521f0
Author: ianlapham <ianlapham@gmail.com>
Date:   Mon Apr 5 12:20:34 2021 -0400

    Merge branch 'main' of https://github.com/Uniswap/v3-interface into minting

commit b2ba46684a7b0bd8a8362f5990f4a208bfeff2dd
Author: ianlapham <ianlapham@gmail.com>
Date:   Mon Apr 5 12:19:20 2021 -0400

    fixing pool hook

commit b10742af99a725e04c1b756aa20f99e995f8cfeb
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 16:53:52 2021 -0400

    update labels

commit 05abd395949245596c95090a9d5d77c7c272dbd3
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 15:34:17 2021 -0400

    update rate label on range select

commit f098d01b6f4dc1dcb99e0fa314dde93647a19bb6
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 15:26:30 2021 -0400

    fix label on counter

commit 16ffe61e8ee2b677adf5d468efa9d7aa8d7e092e
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 15:06:50 2021 -0400

    update tick spacing input

commit 0fa2c8a15821dd32ec978750991a962ecb8f7344
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 14:53:18 2021 -0400

    update comments

commit 1fccf57a1ef081ef6ba9790dc20e0ed604ac2b09
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 14:52:37 2021 -0400

    update 1 amount in price calculation

commit b0e5d22bf8c57b3eacd75f077f68aaca4a9f975a
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 14:46:41 2021 -0400

    fix price creation in util

commit 1ce246e85372e4f120f983ca18a1eb3d16e8647e
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 13:55:14 2021 -0400

    update hard coded chain id

commit 2360b2d0a3233b604956e89de4bd7b09c0506875
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 13:09:21 2021 -0400

    amoutn parse update

commit 6a99a7b71fe446fe77cb2741adce4c067862ca4a
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 13:05:41 2021 -0400

    same token check

commit 83a1fd5a9ff02c6a49532cb54a57770b52fc052e
Author: ianlapham <ianlapham@gmail.com>
Date:   Thu Apr 1 12:31:21 2021 -0400

    clean up state, fix preview

commit 8592383b8386d7adbbaeaa2c6f9c36bb121d1c65
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Mar 31 22:47:56 2021 -0400

    fix incorrect import

commit ce526fd545e52142f847dbf3caec1ca37bb0650b
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Mar 31 22:36:10 2021 -0400

    fix warnings

commit 572770fd3e000ce31cd3a6c5c5c91eac92cc8c5c
Merge: a9e5b6c 2677491
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Mar 31 22:16:30 2021 -0400

    Merge branch 'minting' of https://github.com/Uniswap/v3-interface into minting

commit a9e5b6c5e5983e279a640886783f97c33b713125
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Mar 31 22:12:43 2021 -0400

    rate updates

commit b88cab6c06176eefe5cf71f7cc3e3664d9f514ab
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Mar 31 16:15:08 2021 -0400

    Use real tick and pool math

commit ed933cfd17141174c03b0bcac5f41cf75ff9b258
Author: ianlapham <ianlapham@gmail.com>
Date:   Sun Mar 28 22:42:05 2021 -0400

    naming updates

commit 50c0a0ece5c6c66a603508529c5e7a28f45db632
Author: ianlapham <ianlapham@gmail.com>
Date:   Sun Mar 28 22:36:08 2021 -0400

    WIP start usePool and useDerivedMint hooks

commit 2677491e2128e1318a0dd4307e63069e0f8e1dfe
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Mar 31 22:12:43 2021 -0400

    rate updates

commit c2f59d6c61068a2bf4d34d102d5d28c9863ce982
Author: ianlapham <ianlapham@gmail.com>
Date:   Wed Mar 31 16:15:08 2021 -0400

    Use real tick and pool math

commit 7d53e5c7e979be19fc5c63eb52307f302328c4eb
Author: ianlapham <ianlapham@gmail.com>
Date:   Sun Mar 28 22:42:05 2021 -0400

    naming updates

commit 9022650d391682f97e71d336021c2db2e5ea5455
Author: ianlapham <ianlapham@gmail.com>
Date:   Sun Mar 28 22:36:08 2021 -0400

    WIP start usePool and useDerivedMint hooks

* remove 1337 references

* clean up multicall

* clean up redirects/router

* cleanup

* improve useAllV3Ticks

* fix multicall

* typo

* Fix code style issues with ESLint

* preserve sticky

* reset to non fixed scroll

* fix inputs at 1

* update tests

* fix routes

* sticky sidebar

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
2021-04-14 10:12:35 -04:00
Noah Zinsmeister
c3f65e3abd migration v2 (#38)
* wip

* add some v2 paths

* start to refactor migrate page

update deploy addresses

* fix tests

* stub out v3 remove

* silence some lint warnings
2021-04-09 14:01:56 -04:00
Lint Action
306aaa3a20 Fix code style issues with ESLint 2021-04-08 15:28:38 +00:00
Noah Zinsmeister
d08cae175a update to latest deploy + multicall 2 2021-04-08 11:27:19 -04:00
Noah Zinsmeister
f0f4110b4b update some multicall constants 2021-04-06 10:34:28 -04:00
Noah Zinsmeister
24521f0c92 deploy new tick lens, fix hook 2021-04-02 21:21:47 -04:00
Noah Zinsmeister
a037595e6e chunk multicall by gas cost
add useAllV3Ticks
2021-04-02 20:00:28 -04:00
Jordan Frankfurt
edf4c47451 add external abi types (#37) 2021-03-31 03:40:13 -04:00
Jordan Frankfurt
c0ce6a55c4 add typechain gen scripts (#36)
add typechain generation for core/periphery contracts
2021-03-30 03:51:37 -04:00
Ian Lapham
878fc9cf4d Refactor with core (#33)
* Update index.ts

* feature(service worker): add offline support (#1319)

* Revert "feature(service worker): add offline support (#1319)" (#1320)

This reverts commit 34dfb41a1e.

* Revert "Revert "feature(service worker): add offline support (#1319)" (#1320)" (#1321)

This reverts commit db3328c8d9.

* unregisters all installed service workers (#1322)

* fix: modals stealing focus across frames (#1326)

* improvement(lists): add BA SEC tokens to unsupported list  (#1327)

* show hidden search results by default

* update break styles

* optimize filter, use debounce on input

* increase debounce time

* add ba association list

* handle dismiss (#1328)

* split up sdk use between core and v2

* Fix code style issues with ESLint

* remove service worker

Co-authored-by: Moody Salem <moodysalem@users.noreply.github.com>
Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
Co-authored-by: Hyperion <72735936+moontools-hyperion@users.noreply.github.com>
Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
2021-03-23 21:45:58 -04:00
Jordan Frankfurt
32e679c62e prototype position data hook (#32)
Co-authored-by: Jordan Frankfurt <layup-entropy@protonmail.com>
2021-03-23 20:18:52 -04:00
Jordan Frankfurt
59164f876f dont destructure response 2021-03-18 11:36:27 -04:00
Jordan Frankfurt
5d952661d7 fix(merkle drop): make claim a post request 2021-03-18 01:46:51 -04:00
Ian Lapham
9509737811 Improvement(add): Style tweaks on add page, remove timeout (#30)
* start add liquidity

* update add state, add input UI

* basic add skeleton with dummy state

* refactor with preview steps on add

* small style tweaks, timeout update

* remove merge conflict
2021-03-09 11:58:41 -05:00
Callil Capuozzo
e42a26c3dc Tweak position list styles 2021-03-09 11:01:26 -05:00
Ian Lapham
eeb258ebd5 improvement(Add): skeleton UI with dummy state (#29)
* start add liquidity

* update add state, add input UI

* basic add skeleton with dummy state

* refactor with preview steps on add
2021-03-08 20:52:36 -05:00
Jordan Frankfurt
ab1538b196 feature(position-list): add summary page for positions with mock data (#28)
* feature(position-list): add summary page for positions with mock data

* add loading states

* mobile layout
2021-03-03 17:21:14 -05:00
ianlapham
7e5a230a33 update chart styles 2021-02-16 21:54:51 -05:00
ianlapham
20adf82c79 add dynamic toggle 2021-02-16 21:51:46 -05:00
Jordan Frankfurt
0ec6cad6d1 Revert "fix(storybook): remove it for now" (#9)
fix rebass types to work with storybook
2021-02-16 17:33:55 -05:00
Jordan Frankfurt
eb850cff0b remove husky/pretty-quick (#8)
Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@github.com>
2021-02-16 13:45:36 -05:00
Lint Action
9d9b57dd4c Fix code style issues with ESLint 2021-02-16 08:46:17 +00:00
Jordan Frankfurt
37b0e2fa28 Merge pull request #7 from Uniswap/prettier-upgrade
upgrade prettier
2021-02-16 03:44:49 -05:00
Lint Action
b6d8512316 Fix code style issues with ESLint 2021-02-16 08:36:54 +00:00
Jordan Frankfurt
4e17107ac5 upgrade prettier 2021-02-16 03:34:45 -05:00
Lint Action
64c97f7e30 Fix code style issues with ESLint 2021-02-16 08:28:56 +00:00
Jordan Frankfurt
f3c513573d Merge pull request #6 from Uniswap/prettier-upgrade
Revert "upgrade prettier"
2021-02-16 03:27:31 -05:00
Jordan Frankfurt
6965707d45 Revert "upgrade prettier"
This reverts commit 5fabe438e5.
2021-02-16 03:26:53 -05:00
Ian Lapham
d762836eb9 Merge pull request #2 from Uniswap/nuke-storybook
fix(storybook): remove it for now
2021-02-15 21:48:18 -05:00
ianlapham
88f8f804d9 remove chart on swap page 2021-02-15 21:43:29 -05:00
ianlapham
c1042c6b7a update chart font 2021-02-15 21:42:28 -05:00
ianlapham
3cb382376a format chart component 2021-02-15 21:27:42 -05:00
ianlapham
b44c7e7d7e nuke it 2021-02-15 17:20:37 -05:00
ianlapham
333c907e63 remove stories 2021-02-15 17:19:21 -05:00
Lint Action
b630d59437 Fix code style issues with ESLint 2021-02-15 17:01:46 +00:00
Ian Lapham
ba647d9d6c Merge pull request #4 from Uniswap/upgrade-react
bump react
2021-02-15 11:59:59 -05:00
Ian Lapham
449ed0185e Merge pull request #3 from Uniswap/prettier-upgrade
upgrade prettier
2021-02-15 11:59:42 -05:00
Lint Action
52e3ad0703 Fix code style issues with ESLint 2021-02-14 10:44:11 +00:00
Jordan Frankfurt
bf9dad2550 bump react 2021-02-14 05:41:46 -05:00
Lint Action
adcecdeefc Fix code style issues with ESLint 2021-02-14 08:11:05 +00:00
Jordan Frankfurt
5fabe438e5 upgrade prettier 2021-02-14 03:07:58 -05:00
Jordan Frankfurt
98e77cb01b fix(storybook): remove it for now 2021-02-13 03:28:56 -05:00
ianlapham
b05c4c111c add start of chart component 2021-02-12 16:15:02 -05:00
Ian Lapham
4d612dc2a2 Merge pull request #1 from Uniswap/add-storybook
feature(storybook): first pass at storybook integration
2021-02-12 15:41:27 -05:00
Jordan Frankfurt
619c7c2d95 feature(storybook): first pass at storybook integration 2021-02-12 15:32:25 -05:00
Moody Salem
db886930a0 Update release.yaml 2021-02-12 10:51:31 -06:00
325 changed files with 21598 additions and 11939 deletions

2
.env
View File

@@ -1,3 +1,5 @@
REACT_APP_CHAIN_ID="1" REACT_APP_CHAIN_ID="1"
REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847" REACT_APP_NETWORK_URL="https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
REACT_APP_WALLETCONNECT_BRIDGE_URL="https://uniswap.bridge.walletconnect.org" 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

View File

@@ -8,9 +8,7 @@
"jsx": true "jsx": true
} }
}, },
"ignorePatterns": [ "ignorePatterns": ["node_modules/**/*"],
"node_modules/**/*"
],
"settings": { "settings": {
"react": { "react": {
"version": "detect" "version": "detect"
@@ -26,6 +24,9 @@
"rules": { "rules": {
"@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-function-return-type": "off",
"prettier/prettier": "error", "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"
} }
} }

View File

@@ -10,9 +10,10 @@ assignees: ''
A clear and concise description of the bug. A clear and concise description of the bug.
**Steps to Reproduce** **Steps to Reproduce**
1. Go to ... 1. Go to ...
2. Click on ... 2. Click on ...
... ...
**Expected Behavior** **Expected Behavior**
A clear and concise description of what you expected to happen. A clear and concise description of what you expected to happen.

View File

@@ -1,4 +1,4 @@
blank_issues_enabled: false blank_issues_enabled: true
contact_links: contact_links:
- name: Support - name: Support
url: https://discord.gg/FCfyBSbCU5 url: https://discord.gg/FCfyBSbCU5

4
.gitignore vendored
View File

@@ -1,5 +1,9 @@
# See https://help.github.com/ignore-files/ for more about ignoring files. # See https://help.github.com/ignore-files/ for more about ignoring files.
# generated contract types
/src/types/v3
/src/abis/types
# dependencies # dependencies
/node_modules /node_modules

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
lts/*

16
.storybook/main.ts Normal file
View 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
View File

@@ -0,0 +1,4 @@
import { addons } from '@storybook/addons'
import { light } from './theme'
addons.setConfig({ theme: light })

91
.storybook/preview.tsx Normal file
View 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
View 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,
// })

View File

@@ -1,18 +1,18 @@
describe('Add Liquidity', () => { describe('Add Liquidity', () => {
it('loads the two correct tokens', () => { 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-tokena .token-symbol-container').should('contain.text', 'MKR')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'ETH') cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'ETH')
}) })
it('does not crash if ETH is duplicated', () => { 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-tokena .token-symbol-container').should('contain.text', 'ETH')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('not.contain.text', 'ETH') cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('not.contain.text', 'ETH')
}) })
it('token not in storage is loaded', () => { 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-tokena .token-symbol-container').should('contain.text', 'SKL')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'MKR') 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.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR') 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'
)
})
}) })

View File

@@ -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')
})
})
})

View File

@@ -4,9 +4,4 @@ describe('Pool', () => {
cy.get('#join-pool-button').click() cy.get('#join-pool-button').click()
cy.url().should('contain', '/add/ETH') cy.url().should('contain', '/add/ETH')
}) })
it('import pool links to /import', () => {
cy.get('#import-pool-link').click()
cy.url().should('contain', '/find')
})
}) })

View File

@@ -1,38 +1,30 @@
describe('Remove Liquidity', () => { describe('Remove Liquidity', () => {
it('redirects', () => {
cy.visit('/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab-0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.url().should(
'contain',
'/remove/0xc778417E063141139Fce010982780140Aa0cD5Ab/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'
)
})
it('eth remove', () => { 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-tokena-symbol').should('contain.text', 'ETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR') cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
}) })
it('eth remove swap order', () => { 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-tokena-symbol').should('contain.text', 'MKR')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'ETH') cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'ETH')
}) })
it('loads the two correct tokens', () => { 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-tokena-symbol').should('contain.text', 'WETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR') cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
}) })
it('does not crash if ETH is duplicated', () => { 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-tokena-symbol').should('contain.text', 'WETH')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH') cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH')
}) })
it('token not in storage is loaded', () => { 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-tokena-symbol').should('contain.text', 'SKL')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR') cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
}) })

View File

@@ -3,36 +3,26 @@ describe('Swap', () => {
cy.visit('/swap') cy.visit('/swap')
}) })
it('can enter an amount into input', () => { it('can enter an amount into input', () => {
cy.get('#swap-currency-input .token-amount-input') cy.get('#swap-currency-input .token-amount-input').type('0.001', { delay: 200 }).should('have.value', '0.001')
.type('0.001', { delay: 200 })
.should('have.value', '0.001')
}) })
it('zero swap amount', () => { it('zero swap amount', () => {
cy.get('#swap-currency-input .token-amount-input') cy.get('#swap-currency-input .token-amount-input').type('0.0', { delay: 200 }).should('have.value', '0.0')
.type('0.0', { delay: 200 })
.should('have.value', '0.0')
}) })
it('invalid swap amount', () => { it('invalid swap amount', () => {
cy.get('#swap-currency-input .token-amount-input') cy.get('#swap-currency-input .token-amount-input').type('\\', { delay: 200 }).should('have.value', '')
.type('\\', { delay: 200 })
.should('have.value', '')
}) })
it('can enter an amount into output', () => { it('can enter an amount into output', () => {
cy.get('#swap-currency-output .token-amount-input') cy.get('#swap-currency-output .token-amount-input').type('0.001', { delay: 200 }).should('have.value', '0.001')
.type('0.001', { delay: 200 })
.should('have.value', '0.001')
}) })
it('zero output amount', () => { it('zero output amount', () => {
cy.get('#swap-currency-output .token-amount-input') cy.get('#swap-currency-output .token-amount-input').type('0.0', { delay: 200 }).should('have.value', '0.0')
.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('#swap-currency-output .open-currency-select-button').click()
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').should('be.visible') cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').should('be.visible')
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').click({ force: true }) cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').click({ force: true })
@@ -43,13 +33,13 @@ describe('Swap', () => {
cy.get('#confirm-swap-or-send').should('contain', 'Confirm 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') cy.get('#add-recipient-button').should('not.exist')
}) })
describe('expert mode', () => { describe('expert mode', () => {
beforeEach(() => { beforeEach(() => {
cy.window().then(win => { cy.window().then((win) => {
cy.stub(win, 'prompt').returns('confirm') cy.stub(win, 'prompt').returns('confirm')
}) })
cy.get('#open-settings-dialog-button').click() cy.get('#open-settings-dialog-button').click()
@@ -57,16 +47,16 @@ describe('Swap', () => {
cy.get('#confirm-expert-mode').click() 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') 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('#add-recipient-button').click()
cy.get('#recipient').should('exist') cy.get('#recipient').should('exist')
}) })
it('remove recipient', () => { it.skip('remove recipient', () => {
cy.get('#add-recipient-button').click() cy.get('#add-recipient-button').click()
cy.get('#remove-recipient-button').click() cy.get('#remove-recipient-button').click()
cy.get('#recipient').should('not.exist') cy.get('#recipient').should('not.exist')

View File

@@ -6,7 +6,7 @@
import { JsonRpcProvider } from '@ethersproject/providers' import { JsonRpcProvider } from '@ethersproject/providers'
import { Wallet } from '@ethersproject/wallet' 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') 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 6
)}...${TEST_ADDRESS_NEVER_USE.substr(-4, 4)}` )}...${TEST_ADDRESS_NEVER_USE.substr(-4, 4)}`
class CustomizedBridge extends _Eip1193Bridge { class CustomizedBridge extends Eip1193Bridge {
chainId = 4
async sendAsync(...args) { async sendAsync(...args) {
console.debug('sendAsync called', ...args) console.debug('sendAsync called', ...args)
return this.send(...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 provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
const signer = new Wallet(TEST_PRIVATE_KEY, provider) const signer = new Wallet(TEST_PRIVATE_KEY, provider)
win.ethereum = new CustomizedBridge(signer, provider) win.ethereum = new CustomizedBridge(signer, provider)
} },
}) })
}) })

View File

@@ -4,36 +4,54 @@
"homepage": ".", "homepage": ".",
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"@emotion/core": "^11.0.0",
"@ethersproject/experimental": "^5.0.1", "@ethersproject/experimental": "^5.0.1",
"@popperjs/core": "^2.4.4", "@popperjs/core": "^2.4.4",
"@reach/dialog": "^0.10.3", "@reach/dialog": "^0.10.3",
"@reach/portal": "^0.10.3", "@reach/portal": "^0.10.3",
"@react-hook/window-scroll": "^1.3.0",
"@reduxjs/toolkit": "^1.3.5", "@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/jest": "^25.2.1",
"@types/lodash.flatmap": "^4.5.6", "@types/lodash.flatmap": "^4.5.6",
"@types/luxon": "^1.24.4", "@types/luxon": "^1.24.4",
"@types/multicodec": "^1.0.0", "@types/multicodec": "^1.0.0",
"@types/node": "^13.13.5", "@types/node": "^13.13.5",
"@types/qs": "^6.9.2", "@types/qs": "^6.9.2",
"@types/react": "^16.9.34", "@types/react": "^17.0.2",
"@types/react-dom": "^16.9.7", "@types/react-dom": "^17.0.1",
"@types/react-redux": "^7.1.8", "@types/react-redux": "^7.1.16",
"@types/react-router-dom": "^5.0.0", "@types/react-router-dom": "^5.0.0",
"@types/react-virtualized-auto-sizer": "^1.0.0", "@types/react-virtualized-auto-sizer": "^1.0.0",
"@types/react-window": "^1.8.2", "@types/react-window": "^1.8.2",
"@types/rebass": "^4.0.5", "@types/rebass": "^4.0.7",
"@types/styled-components": "^5.1.0", "@types/styled-components": "^5.1.0",
"@types/testing-library__cypress": "^5.0.5", "@types/testing-library__cypress": "^5.0.5",
"@types/ua-parser-js": "^0.7.35",
"@types/wcag-contrast": "^3.0.0", "@types/wcag-contrast": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^2.31.0", "@typescript-eslint/eslint-plugin": "^4.1.0",
"@typescript-eslint/parser": "^2.31.0", "@typescript-eslint/parser": "^4.1.0",
"@uniswap/default-token-list": "^2.0.0",
"@uniswap/governance": "^1.0.2", "@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2", "@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "1.0.1", "@uniswap/merkle-distributor": "1.0.1",
"@uniswap/sdk": "3.0.3",
"@uniswap/token-lists": "^1.0.0-beta.19", "@uniswap/token-lists": "^1.0.0-beta.19",
"@uniswap/v2-core": "1.0.0", "@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.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.1",
"@web3-react/core": "^6.0.9", "@web3-react/core": "^6.0.9",
"@web3-react/fortmatic-connector": "^6.0.9", "@web3-react/fortmatic-connector": "^6.0.9",
"@web3-react/injected-connector": "^6.0.7", "@web3-react/injected-connector": "^6.0.7",
@@ -45,7 +63,7 @@
"copy-to-clipboard": "^3.2.0", "copy-to-clipboard": "^3.2.0",
"cross-env": "^7.0.2", "cross-env": "^7.0.2",
"cypress": "^4.11.0", "cypress": "^4.11.0",
"eslint": "^6.8.0", "eslint": "^7.11.0",
"eslint-config-prettier": "^6.11.0", "eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3", "eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-react": "^7.19.0", "eslint-plugin-react": "^7.19.0",
@@ -56,26 +74,27 @@
"i18next-xhr-backend": "^2.0.1", "i18next-xhr-backend": "^2.0.1",
"inter-ui": "^3.13.1", "inter-ui": "^3.13.1",
"jazzicon": "^1.5.0", "jazzicon": "^1.5.0",
"lightweight-charts": "^3.3.0",
"lodash.flatmap": "^4.5.0", "lodash.flatmap": "^4.5.0",
"luxon": "^1.25.0", "luxon": "^1.25.0",
"multicodec": "^2.0.0", "multicodec": "^3.0.1",
"multihashes": "^3.0.1", "multihashes": "^4.0.2",
"node-vibrant": "^3.1.5", "node-vibrant": "^3.1.5",
"polished": "^3.3.2", "polished": "^3.3.2",
"prettier": "^1.17.0", "prettier": "^2.2.1",
"qs": "^6.9.4", "qs": "^6.9.4",
"react": "^16.13.1", "react": "^17.0.1",
"react-confetti": "^6.0.0", "react-confetti": "^6.0.0",
"react-device-detect": "^1.6.2", "react-device-detect": "^1.6.2",
"react-dom": "^16.13.1", "react-dom": "^17.0.1",
"react-feather": "^2.0.8", "react-feather": "^2.0.8",
"react-ga": "^2.5.7", "react-ga": "^2.5.7",
"react-i18next": "^10.7.0", "react-i18next": "^10.7.0",
"react-markdown": "^4.3.1", "react-markdown": "^4.3.1",
"react-popper": "^2.2.3", "react-popper": "^2.2.3",
"react-redux": "^7.2.0", "react-redux": "^7.2.2",
"react-router-dom": "^5.0.0", "react-router-dom": "^5.0.0",
"react-scripts": "^3.4.1", "react-scripts": "^4.0.3",
"react-spring": "^8.0.27", "react-spring": "^8.0.27",
"react-use-gesture": "^6.0.14", "react-use-gesture": "^6.0.14",
"react-virtualized-auto-sizer": "^1.0.2", "react-virtualized-auto-sizer": "^1.0.2",
@@ -85,7 +104,10 @@
"serve": "^11.3.2", "serve": "^11.3.2",
"start-server-and-test": "^1.11.0", "start-server-and-test": "^1.11.0",
"styled-components": "^4.2.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", "use-count-up": "^2.2.5",
"wcag-contrast": "^3.0.0", "wcag-contrast": "^3.0.0",
"workbox-core": "^6.1.0", "workbox-core": "^6.1.0",
@@ -95,12 +117,15 @@
"workbox-strategies": "^6.1.0" "workbox-strategies": "^6.1.0"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "compile-contract-types": "yarn compile-external-abi-types && yarn compile-v3-contract-types",
"start:service-worker": "yarn build && yarn serve -s build", "compile-external-abi-types": "npx typechain --target ethers-v5 --out-dir src/abis/types './src/abis/**/*.json'",
"build": "react-scripts build", "compile-v3-contract-types": "npx typechain --target ethers-v5 --out-dir src/types/v3 './node_modules/@uniswap/?(v3-core|v3-periphery)/artifacts/contracts/**/*.json'",
"test": "react-scripts test --env=jsdom", "build": "yarn compile-contract-types && react-scripts build",
"eject": "react-scripts eject", "integration-test": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run'",
"integration-test": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run'" "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": { "eslintConfig": {
"extends": "react-app", "extends": "react-app",
@@ -121,7 +146,5 @@
] ]
}, },
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {}
"@uniswap/default-token-list": "^2.0.0"
}
} }

View File

@@ -87,5 +87,30 @@
"forAtLeast": "for at least ", "forAtLeast": "for at least ",
"brokenToken": "The selected token is not compatible with Uniswap V1. Adding liquidity will result in locked funds.", "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.", "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 dont 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
View 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
View 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
View 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"
}
]

View 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 }

View File

@@ -89,7 +89,7 @@
"constant": true, "constant": true,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 22405 "gas": "22405"
}, },
{ {
"name": "tokenByIndex", "name": "tokenByIndex",
@@ -108,7 +108,7 @@
"constant": true, "constant": true,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 631 "gas": "631"
}, },
{ {
"name": "tokenOfOwnerByIndex", "name": "tokenOfOwnerByIndex",
@@ -131,7 +131,7 @@
"constant": true, "constant": true,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 1248 "gas": "1248"
}, },
{ {
"name": "transferFrom", "name": "transferFrom",
@@ -153,7 +153,7 @@
"constant": false, "constant": false,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 259486 "gas": "259486"
}, },
{ {
"name": "safeTransferFrom", "name": "safeTransferFrom",
@@ -217,7 +217,7 @@
"constant": false, "constant": false,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 38422 "gas": "38422"
}, },
{ {
"name": "setApprovalForAll", "name": "setApprovalForAll",
@@ -235,7 +235,7 @@
"constant": false, "constant": false,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 38016 "gas": "38016"
}, },
{ {
"name": "mint", "name": "mint",
@@ -254,7 +254,7 @@
"constant": false, "constant": false,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 182636 "gas": "182636"
}, },
{ {
"name": "changeMinter", "name": "changeMinter",
@@ -268,7 +268,7 @@
"constant": false, "constant": false,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 35897 "gas": "35897"
}, },
{ {
"name": "changeURI", "name": "changeURI",
@@ -282,7 +282,7 @@
"constant": false, "constant": false,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 35927 "gas": "35927"
}, },
{ {
"name": "name", "name": "name",
@@ -296,7 +296,7 @@
"constant": true, "constant": true,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 6612 "gas": "6612"
}, },
{ {
"name": "symbol", "name": "symbol",
@@ -310,7 +310,7 @@
"constant": true, "constant": true,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 6642 "gas": "6642"
}, },
{ {
"name": "totalSupply", "name": "totalSupply",
@@ -324,7 +324,7 @@
"constant": true, "constant": true,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 873 "gas": "873"
}, },
{ {
"name": "minter", "name": "minter",
@@ -338,7 +338,7 @@
"constant": true, "constant": true,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 903 "gas": "903"
}, },
{ {
"name": "socks", "name": "socks",
@@ -353,7 +353,7 @@
"constant": true, "constant": true,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 933 "gas": "933"
}, },
{ {
"name": "newURI", "name": "newURI",
@@ -367,7 +367,7 @@
"constant": true, "constant": true,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 963 "gas": "963"
}, },
{ {
"name": "ownerOf", "name": "ownerOf",
@@ -386,7 +386,7 @@
"constant": true, "constant": true,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 1126 "gas": "1126"
}, },
{ {
"name": "balanceOf", "name": "balanceOf",
@@ -405,7 +405,7 @@
"constant": true, "constant": true,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 1195 "gas": "1195"
}, },
{ {
"name": "getApproved", "name": "getApproved",
@@ -424,7 +424,7 @@
"constant": true, "constant": true,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 1186 "gas": "1186"
}, },
{ {
"name": "isApprovedForAll", "name": "isApprovedForAll",
@@ -447,7 +447,7 @@
"constant": true, "constant": true,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 1415 "gas": "1415"
}, },
{ {
"name": "supportsInterface", "name": "supportsInterface",
@@ -466,6 +466,6 @@
"constant": true, "constant": true,
"payable": false, "payable": false,
"type": "function", "type": "function",
"gas": 1246 "gas": "1246"
} }
] ]

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

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

View 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

View File

@@ -26,7 +26,7 @@ const HeaderRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}; ${({ theme }) => theme.flexRowNoWrap};
padding: 1rem 1rem; padding: 1rem 1rem;
font-weight: 500; 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` ${({ theme }) => theme.mediaWidth.upToMedium`
padding: 1rem; padding: 1rem;
`}; `};
@@ -76,7 +76,6 @@ const AccountGroupingRow = styled.div`
` `
const AccountSection = styled.div` const AccountSection = styled.div`
background-color: ${({ theme }) => theme.bg1};
padding: 0rem 1rem; padding: 0rem 1rem;
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0rem 1rem 1.5rem 1rem;`}; ${({ theme }) => theme.mediaWidth.upToMedium`padding: 0rem 1rem 1.5rem 1rem;`};
` `
@@ -223,7 +222,7 @@ export default function AccountDetails({
pendingTransactions, pendingTransactions,
confirmedTransactions, confirmedTransactions,
ENSName, ENSName,
openOptions openOptions,
}: AccountDetailsProps) { }: AccountDetailsProps) {
const { chainId, account, connector } = useActiveWeb3React() const { chainId, account, connector } = useActiveWeb3React()
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
@@ -234,10 +233,10 @@ export default function AccountDetails({
const isMetaMask = !!(ethereum && ethereum.isMetaMask) const isMetaMask = !!(ethereum && ethereum.isMetaMask)
const name = Object.keys(SUPPORTED_WALLETS) const name = Object.keys(SUPPORTED_WALLETS)
.filter( .filter(
k => (k) =>
SUPPORTED_WALLETS[k].connector === connector && (connector !== injected || isMetaMask === (k === 'METAMASK')) 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> return <WalletName>Connected with {name}</WalletName>
} }

View File

@@ -68,7 +68,7 @@ const Input = styled.input<{ error?: boolean }>`
export default function AddressInputPanel({ export default function AddressInputPanel({
id, id,
value, value,
onChange onChange,
}: { }: {
id?: string id?: string
// the typed string value // the typed string value
@@ -82,7 +82,7 @@ export default function AddressInputPanel({
const { address, loading, name } = useENS(value) const { address, loading, name } = useENS(value)
const handleInput = useCallback( const handleInput = useCallback(
event => { (event) => {
const input = event.target.value const input = event.target.value
const withoutSpaces = input.replace(/\s+/g, '') const withoutSpaces = input.replace(/\s+/g, '')
onChange(withoutSpaces) onChange(withoutSpaces)

View 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,
}

View File

@@ -0,0 +1,77 @@
import React from 'react'
import Badge, { BadgeVariant } from 'components/Badge'
import styled from 'styled-components'
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} />
&nbsp;
<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 /> &nbsp;
<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} />
&nbsp;
<BadgeText>{t('Out of range')}</BadgeText>
</Badge>
</MouseoverTooltip>
)}
</BadgeWrapper>
)
}

View 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

View File

@@ -0,0 +1,153 @@
import { Story } from '@storybook/react/types-6-0'
import styled from 'styled-components'
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 />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonRadio>
)
export const DropdownLight = () => (
<ButtonDropdownLight>
<Unicorn />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonDropdownLight>
)
export const DropdownGrey = () => (
<ButtonDropdownGrey>
<Unicorn />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonDropdownGrey>
)
export const Dropdown = () => (
<ButtonDropdown>
<Unicorn />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonDropdown>
)
export const Error = () => (
<ButtonError>
<Unicorn />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonError>
)
export const Confirmed = () => (
<ButtonConfirmed>
<Unicorn />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonConfirmed>
)
export const White = () => (
<ButtonWhite>
<Unicorn />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonWhite>
)
export const Empty = () => (
<ButtonEmpty>
<Unicorn />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonEmpty>
)
export const Outlined = () => (
<ButtonOutlined>
<Unicorn />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonOutlined>
)
export const UNIGradient = () => (
<ButtonUNIGradient>
<Unicorn />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonUNIGradient>
)
export const Pink = () => (
<ButtonPink>
<Unicorn />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonPink>
)
export const Secondary = () => (
<ButtonSecondary>
<Unicorn />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonSecondary>
)
export const Gray = () => (
<ButtonGray>
<Unicorn />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonGray>
)
export const Light = () => (
<ButtonLight>
<Unicorn />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonLight>
)
export const Primary = () => (
<ButtonPrimary>
<Unicorn />
&nbsp;UNISWAP&nbsp;
<Unicorn />
</ButtonPrimary>
)

View File

@@ -1,10 +1,11 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { darken, lighten } from 'polished' import { darken } from 'polished'
import { RowBetween } from '../Row' 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 { Button as RebassButton, ButtonProps } from 'rebass/styled-components'
import useTheme from 'hooks/useTheme'
const Base = styled(RebassButton)<{ const Base = styled(RebassButton)<{
padding?: string padding?: string
@@ -12,7 +13,7 @@ const Base = styled(RebassButton)<{
borderRadius?: string borderRadius?: string
altDisabledStyle?: boolean altDisabledStyle?: boolean
}>` }>`
padding: ${({ padding }) => (padding ? padding : '18px')}; padding: ${({ padding }) => (padding ? padding : '16px')};
width: ${({ width }) => (width ? width : '100%')}; width: ${({ width }) => (width ? width : '100%')};
font-weight: 500; font-weight: 500;
text-align: center; text-align: center;
@@ -31,11 +32,24 @@ const Base = styled(RebassButton)<{
z-index: 1; z-index: 1;
&:disabled { &:disabled {
cursor: auto; 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; user-select: none;
} }
> a {
text-decoration: none;
}
` `
export const ButtonPrimary = styled(Base)` export const ButtonPrimary = styled(Base)`
@@ -54,14 +68,14 @@ export const ButtonPrimary = styled(Base)`
} }
&:disabled { &:disabled {
background-color: ${({ theme, altDisabledStyle, disabled }) => background-color: ${({ theme, altDisabledStyle, disabled }) =>
altDisabledStyle ? (disabled ? theme.bg3 : theme.primary1) : theme.bg3}; altDisabledStyle ? (disabled ? theme.primary1 : theme.primary1) : theme.primary1};
color: ${({ theme, altDisabledStyle, disabled }) => color: white;
altDisabledStyle ? (disabled ? theme.text3 : 'white') : theme.text3};
cursor: auto; cursor: auto;
box-shadow: none; box-shadow: none;
border: 1px solid transparent; border: 1px solid transparent;
outline: none; 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)` export const ButtonGray = styled(Base)`
background-color: ${({ theme }) => theme.bg3}; background-color: ${({ theme }) => theme.bg1};
color: ${({ theme }) => theme.text2}; color: ${({ theme }) => theme.text2};
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
&:focus { &:focus {
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg4)}; background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg2)};
} }
&:hover { &:hover {
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg4)}; background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg2)};
} }
&:active { &: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)` export const ButtonWhite = styled(Base)`
border: 1px solid #edeef2; border: 1px solid #edeef2;
background-color: ${({ theme }) => theme.bg1}; background-color: ${({ theme }) => theme.bg1};
@@ -243,12 +279,14 @@ export const ButtonWhite = styled(Base)`
` `
const ButtonConfirmedStyle = styled(Base)` const ButtonConfirmedStyle = styled(Base)`
background-color: ${({ theme }) => lighten(0.5, theme.green1)}; background-color: ${({ theme }) => theme.bg3};
color: ${({ theme }) => theme.green1}; color: ${({ theme }) => theme.text1};
border: 1px solid ${({ theme }) => theme.green1}; /* border: 1px solid ${({ theme }) => theme.green1}; */
&:disabled { &:disabled {
opacity: 50%; /* opacity: 50%; */
background-color: ${({ theme }) => theme.bg2};
color: ${({ theme }) => theme.text2};
cursor: auto; cursor: auto;
} }
` `
@@ -337,3 +375,57 @@ export function ButtonRadio({ active, ...rest }: { active?: boolean } & ButtonPr
return <ButtonPrimary {...rest} /> 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>
)
}
}

View File

@@ -1,12 +1,10 @@
import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { CardProps, Text } from 'rebass'
import { Box } from 'rebass/styled-components' import { Box } from 'rebass/styled-components'
const Card = styled(Box)<{ width?: string; padding?: string; border?: string; borderRadius?: string }>` const Card = styled(Box)<{ width?: string; padding?: string; border?: string; borderRadius?: string }>`
width: ${({ width }) => width ?? '100%'}; width: ${({ width }) => width ?? '100%'};
border-radius: 16px; border-radius: 16px;
padding: 1.25rem; padding: 1rem;
padding: ${({ padding }) => padding}; padding: ${({ padding }) => padding};
border: ${({ border }) => border}; border: ${({ border }) => border};
border-radius: ${({ borderRadius }) => borderRadius}; border-radius: ${({ borderRadius }) => borderRadius};
@@ -26,13 +24,21 @@ export const GreyCard = styled(Card)`
background-color: ${({ theme }) => theme.bg3}; 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)` export const OutlineCard = styled(Card)`
border: 1px solid ${({ theme }) => theme.bg3}; border: 1px solid ${({ theme }) => theme.bg3};
` `
export const YellowCard = styled(Card)` export const YellowCard = styled(Card)`
background-color: rgba(243, 132, 30, 0.05); background-color: rgba(243, 132, 30, 0.05);
color: ${({ theme }) => theme.yellow2}; color: ${({ theme }) => theme.yellow3};
font-weight: 500; font-weight: 500;
` `
@@ -42,19 +48,8 @@ export const PinkCard = styled(Card)`
font-weight: 500; font-weight: 500;
` `
const BlueCardStyled = styled(Card)` export const BlueCard = styled(Card)`
background-color: ${({ theme }) => theme.primary5}; background-color: ${({ theme }) => theme.primary5};
color: ${({ theme }) => theme.primary1}; color: ${({ theme }) => theme.blue2};
border-radius: 12px; border-radius: 12px;
width: fit-content;
` `
export const BlueCard = ({ children, ...rest }: CardProps) => {
return (
<BlueCardStyled {...rest}>
<Text fontWeight={500} color="#2172E5">
{children}
</Text>
</BlueCardStyled>
)
}

View File

@@ -20,7 +20,7 @@ export default function Confetti({ start, variant }: { start: boolean; variant?:
h: height, h: height,
w: width, w: width,
x: 0, 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} initialVelocityX={15}
initialVelocityY={30} initialVelocityY={30}

View 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 ? Number(fiatValue?.toSignificant(6)).toLocaleString('en') : '-'} />{' '}
{priceImpact ? (
<span style={{ color: priceImpactColor }}> ({priceImpact.multiply(-1).toSignificant(3)}%)</span>
) : null}
</TYPE.body>
)
}

View File

@@ -1,4 +1,5 @@
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 React, { useState, useCallback } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { darken } from 'polished' import { darken } from 'polished'
@@ -6,63 +7,108 @@ import { useCurrencyBalance } from '../../state/wallet/hooks'
import CurrencySearchModal from '../SearchModal/CurrencySearchModal' import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
import CurrencyLogo from '../CurrencyLogo' import CurrencyLogo from '../CurrencyLogo'
import DoubleCurrencyLogo from '../DoubleLogo' import DoubleCurrencyLogo from '../DoubleLogo'
import { RowBetween } from '../Row' import { ButtonGray } from '../Button'
import { RowBetween, RowFixed } from '../Row'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { Input as NumericalInput } from '../NumericalInput' import { Input as NumericalInput } from '../NumericalInput'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg' import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useTheme from '../../hooks/useTheme' 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 }>` const InputPanel = styled.div<{ hideInput?: boolean }>`
${({ theme }) => theme.flexRowNoWrap} ${({ theme }) => theme.flexColumnNoWrap}
align-items: center; position: relative;
padding: ${({ selected }) => (selected ? '0.75rem 0.5rem 0.75rem 1rem' : '0.75rem 0.75rem 0.75rem 1rem')}; 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; align-items: center;
height: 2.2rem; justify-content: center;
font-size: 20px; 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; 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)}; 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: ${({ 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; outline: none;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
border: 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, :focus,
:hover { :hover {
background-color: ${({ selected, theme }) => (selected ? theme.bg2 : darken(0.05, theme.primary1))}; 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` const LabelRow = styled.div`
${({ theme }) => theme.flexRowNoWrap} ${({ theme }) => theme.flexRowNoWrap}
align-items: center; align-items: center;
color: ${({ theme }) => theme.text1}; color: ${({ theme }) => theme.text1};
font-size: 0.75rem; font-size: 0.75rem;
line-height: 1rem; line-height: 1rem;
padding: 0.75rem 1rem 0 1rem; padding: 0 1rem 1rem;
span:hover { span:hover {
cursor: pointer; cursor: pointer;
color: ${({ theme }) => darken(0.2, theme.text2)}; color: ${({ theme }) => darken(0.2, theme.text2)};
} }
` `
const FiatRow = styled(LabelRow)`
justify-content: flex-end;
`
const Aligner = styled.span` const Aligner = styled.span`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
width: 100%;
` `
const StyledDropDown = styled(DropDown)<{ selected: boolean }>` const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
margin: 0 0.25rem 0 0.5rem; margin: 0 0.25rem 0 0.35rem;
height: 35%; height: 35%;
path { 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 }>` const StyledTokenName = styled.span<{ active?: boolean }>`
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.75rem;' : ' margin: 0 0.25rem 0 0.25rem;')} ${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
font-size: ${({ active }) => (active ? '20px' : '16px')}; font-size: ${({ active }) => (active ? '18px' : '18px')};
` `
const StyledBalanceMax = styled.button` const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
height: 28px; background-color: transparent;
background-color: ${({ theme }) => theme.primary5}; border: none;
border: 1px solid ${({ theme }) => theme.primary5}; border-radius: 12px;
border-radius: 0.5rem; font-size: 14px;
font-size: 0.875rem;
font-weight: 500; font-weight: 500;
cursor: pointer; cursor: pointer;
margin-right: 0.5rem; padding: 0;
color: ${({ theme }) => theme.primaryText1}; color: ${({ theme }) => theme.primary1};
:hover { opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
border: 1px solid ${({ theme }) => theme.primary1}; pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
} margin-left: 0.25rem;
:focus { :focus {
border: 1px solid ${({ theme }) => theme.primary1};
outline: none; outline: none;
} }
@@ -123,14 +152,16 @@ interface CurrencyInputPanelProps {
label?: string label?: string
onCurrencySelect?: (currency: Currency) => void onCurrencySelect?: (currency: Currency) => void
currency?: Currency | null currency?: Currency | null
disableCurrencySelect?: boolean
hideBalance?: boolean hideBalance?: boolean
pair?: Pair | null pair?: Pair | null
hideInput?: boolean hideInput?: boolean
otherCurrency?: Currency | null otherCurrency?: Currency | null
fiatValue?: CurrencyAmount<Token> | null
priceImpact?: Percent
id: string id: string
showCommonBases?: boolean showCommonBases?: boolean
customBalanceText?: string customBalanceText?: string
locked?: boolean
} }
export default function CurrencyInputPanel({ export default function CurrencyInputPanel({
@@ -138,17 +169,19 @@ export default function CurrencyInputPanel({
onUserInput, onUserInput,
onMax, onMax,
showMaxButton, showMaxButton,
label = 'Input',
onCurrencySelect, onCurrencySelect,
currency, currency,
disableCurrencySelect = false,
hideBalance = false,
pair = null, // used for double token logo
hideInput = false,
otherCurrency, otherCurrency,
id, id,
showCommonBases, showCommonBases,
customBalanceText customBalanceText,
fiatValue,
priceImpact,
hideBalance = false,
pair = null, // used for double token logo
hideInput = false,
locked = false,
...rest
}: CurrencyInputPanelProps) { }: CurrencyInputPanelProps) {
const { t } = useTranslation() const { t } = useTranslation()
@@ -162,59 +195,37 @@ export default function CurrencyInputPanel({
}, [setModalOpen]) }, [setModalOpen])
return ( 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}> <Container hideInput={hideInput}>
{!hideInput && ( <InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={!onCurrencySelect}>
<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>
)}
</>
)}
<CurrencySelect <CurrencySelect
selected={!!currency} selected={!!currency}
hideInput={hideInput}
className="open-currency-select-button" className="open-currency-select-button"
onClick={() => { onClick={() => {
if (!disableCurrencySelect) { if (onCurrencySelect) {
setModalOpen(true) setModalOpen(true)
} }
}} }}
> >
<Aligner> <Aligner>
<RowFixed>
{pair ? ( {pair ? (
<span style={{ marginRight: '0.5rem' }}>
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} /> <DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
</span>
) : currency ? ( ) : currency ? (
<CurrencyLogo currency={currency} size={'24px'} /> <CurrencyLogo style={{ marginRight: '0.5rem' }} currency={currency} size={'24px'} />
) : null} ) : null}
{pair ? ( {pair ? (
<StyledTokenName className="pair-name-container"> <StyledTokenName className="pair-name-container">
@@ -229,12 +240,54 @@ export default function CurrencyInputPanel({
: currency?.symbol) || t('selectToken')} : currency?.symbol) || t('selectToken')}
</StyledTokenName> </StyledTokenName>
)} )}
{!disableCurrencySelect && <StyledDropDown selected={!!currency} />} </RowFixed>
{onCurrencySelect && <StyledDropDown selected={!!currency} />}
</Aligner> </Aligner>
</CurrencySelect> </CurrencySelect>
{!hideInput && (
<>
<NumericalInput
className="token-amount-input"
value={value}
onUserInput={(val) => {
onUserInput(val)
}}
/>
</>
)}
</InputRow> </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> </Container>
{!disableCurrencySelect && onCurrencySelect && ( {onCurrencySelect && (
<CurrencySearchModal <CurrencySearchModal
isOpen={modalOpen} isOpen={modalOpen}
onDismiss={handleDismissSearch} onDismiss={handleDismissSearch}

View File

@@ -1,10 +1,9 @@
import { Currency, ETHER, Token } from '@uniswap/sdk' import { ChainId, Currency } from '@uniswap/sdk-core'
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import EthereumLogo from '../../assets/images/ethereum-logo.png' import EthereumLogo from '../../assets/images/ethereum-logo.png'
import useHttpLocations from '../../hooks/useHttpLocations' import useHttpLocations from '../../hooks/useHttpLocations'
import { WrappedTokenInfo } from '../../state/lists/hooks' import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
import Logo from '../Logo' import Logo from '../Logo'
export const getTokenLogoURL = (address: string) => export const getTokenLogoURL = (address: string) =>
@@ -28,7 +27,8 @@ const StyledLogo = styled(Logo)<{ size: string }>`
export default function CurrencyLogo({ export default function CurrencyLogo({
currency, currency,
size = '24px', size = '24px',
style style,
...rest
}: { }: {
currency?: Currency currency?: Currency
size?: string size?: string
@@ -37,20 +37,21 @@ export default function CurrencyLogo({
const uriLocations = useHttpLocations(currency instanceof WrappedTokenInfo ? currency.logoURI : undefined) const uriLocations = useHttpLocations(currency instanceof WrappedTokenInfo ? currency.logoURI : undefined)
const srcs: string[] = useMemo(() => { 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) { if (currency instanceof WrappedTokenInfo) {
return [...uriLocations, getTokenLogoURL(currency.address)] return [...uriLocations, ...defaultUrls]
} }
return [getTokenLogoURL(currency.address)] return defaultUrls
} }
return [] return []
}, [currency, uriLocations]) }, [currency, uriLocations])
if (currency === ETHER) { if (currency?.isEther) {
return <StyledEthereumLogo src={EthereumLogo} size={size} style={style} /> 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} />
} }

View 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,
}

View File

@@ -1,4 +1,4 @@
import { Currency } from '@uniswap/sdk' import { Currency } from '@uniswap/sdk-core'
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import CurrencyLogo from '../CurrencyLogo' import CurrencyLogo from '../CurrencyLogo'
@@ -7,10 +7,10 @@ const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>`
position: relative; position: relative;
display: flex; display: flex;
flex-direction: row; 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 margin?: boolean
size?: number size?: number
currency0?: Currency currency0?: Currency
@@ -29,7 +29,7 @@ export default function DoubleCurrencyLogo({
currency0, currency0,
currency1, currency1,
size = 16, size = 16,
margin = false margin = false,
}: DoubleCurrencyLogoProps) { }: DoubleCurrencyLogoProps) {
return ( return (
<Wrapper sizeraw={size} margin={margin}> <Wrapper sizeraw={size} margin={margin}>

View File

@@ -0,0 +1,162 @@
import React, { ErrorInfo } from 'react'
import { ExternalLink, ThemedBackground, TYPE } from '../../theme'
import { AutoColumn } from '../Column'
import styled from 'styled-components'
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 issueBody(error: Error): string {
if (!error) throw new Error('no error to report')
const deviceData = getUserAgent()
return `**Bug Description**
App crashed
**Steps to Reproduce**
1. Go to ...
2. Click on ...
...
**URL**
${window.location.href}
${
error.name &&
`**Error**
\`\`\`
${error.name}${error.message && `: ${error.message}`}
\`\`\`
`
}
${
error.stack &&
`**Stacktrace**
\`\`\`
${error.stack}
\`\`\`
`
}
${
deviceData &&
`**Device data**
\`\`\`json5
${JSON.stringify(deviceData, null, 2)}
\`\`\`
`
}
`
}

View 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'
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>
)
}

View File

@@ -1,13 +1,14 @@
import JSBI from 'jsbi'
import React from 'react' 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)) const CURRENCY_AMOUNT_MIN = new Fraction(JSBI.BigInt(1), JSBI.BigInt(1000000))
export default function FormattedCurrencyAmount({ export default function FormattedCurrencyAmount({
currencyAmount, currencyAmount,
significantDigits = 4 significantDigits = 4,
}: { }: {
currencyAmount: CurrencyAmount currencyAmount: CurrencyAmount<Currency>
significantDigits?: number significantDigits?: number
}) { }) {
return ( return (

View File

@@ -9,27 +9,29 @@ import { useActiveWeb3React } from '../../hooks'
const StyledPolling = styled.div` const StyledPolling = styled.div`
position: fixed; position: fixed;
display: flex; display: flex;
align-items: center;
right: 0; right: 0;
bottom: 0; bottom: 0;
padding: 1rem; padding: 1rem;
color: white;
transition: opacity 0.25s ease;
color: ${({ theme }) => theme.green1}; color: ${({ theme }) => theme.green1};
:hover {
opacity: 1;
}
${({ theme }) => theme.mediaWidth.upToMedium` ${({ theme }) => theme.mediaWidth.upToMedium`
display: none; display: none;
`} `}
` `
const StyledPollingNumber = styled(TYPE.small)<{ breathe: boolean; hovering: boolean }>`
transition: opacity 0.25s ease;
opacity: ${({ breathe, hovering }) => (hovering ? 0.7 : breathe ? 1 : 0.2)};
:hover {
opacity: 1;
}
`
const StyledPollingDot = styled.div` const StyledPollingDot = styled.div`
width: 8px; width: 8px;
height: 8px; height: 8px;
min-height: 8px; min-height: 8px;
min-width: 8px; min-width: 8px;
margin-left: 0.5rem; margin-left: 0.5rem;
margin-top: 3px;
border-radius: 50%; border-radius: 50%;
position: relative; position: relative;
background-color: ${({ theme }) => theme.green1}; background-color: ${({ theme }) => theme.green1};
@@ -67,16 +69,21 @@ export default function Polling() {
const blockNumber = useBlockNumber() const blockNumber = useBlockNumber()
const [isMounted, setIsMounted] = useState(true) const [isMounting, setIsMounting] = useState(false)
const [isHover, setIsHover] = useState(false)
useEffect( useEffect(
() => { () => {
const timer1 = setTimeout(() => setIsMounted(true), 1000) if (!blockNumber) {
return
}
setIsMounting(true)
const mountingTimer = setTimeout(() => setIsMounting(false), 1000)
// this will clear Timeout when component unmount like in willComponentUnmount // this will clear Timeout when component unmount like in willComponentUnmount
return () => { return () => {
setIsMounted(false) clearTimeout(mountingTimer)
clearTimeout(timer1)
} }
}, },
[blockNumber] //useEffect will run only one time [blockNumber] //useEffect will run only one time
@@ -85,9 +92,11 @@ export default function Polling() {
return ( return (
<ExternalLink href={chainId && blockNumber ? getEtherscanLink(chainId, blockNumber.toString(), 'block') : ''}> <ExternalLink href={chainId && blockNumber ? getEtherscanLink(chainId, blockNumber.toString(), 'block') : ''}>
<StyledPolling> <StyledPolling onMouseEnter={() => setIsHover(true)} onMouseLeave={() => setIsHover(false)}>
<TYPE.small style={{ opacity: isMounted ? '0.2' : '0.6' }}>{blockNumber}</TYPE.small> <StyledPollingNumber breathe={isMounting} hovering={isHover}>
<StyledPollingDot>{!isMounted && <Spinner />}</StyledPollingDot> {blockNumber}
</StyledPollingNumber>
<StyledPollingDot>{isMounting && <Spinner />}</StyledPollingDot>
</StyledPolling> </StyledPolling>
</ExternalLink> </ExternalLink>
) )

View File

@@ -1,10 +1,10 @@
import { ChainId, TokenAmount } from '@uniswap/sdk' import { ChainId, CurrencyAmount, Token } from '@uniswap/sdk-core'
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import { X } from 'react-feather' import { X } from 'react-feather'
import styled from 'styled-components' import styled from 'styled-components'
import tokenLogo from '../../assets/images/token-logo.png' import tokenLogo from '../../assets/images/token-logo.png'
import { UNI } from '../../constants' import { UNI } from '../../constants'
import { useTotalSupply } from '../../data/TotalSupply' import { useTotalSupply } from '../../hooks/useTotalSupply'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useMerkleDistributorContract } from '../../hooks/useContract' import { useMerkleDistributorContract } from '../../hooks/useContract'
import useCurrentBlockTimestamp from '../../hooks/useCurrentBlockTimestamp' import useCurrentBlockTimestamp from '../../hooks/useCurrentBlockTimestamp'
@@ -12,7 +12,7 @@ import { useTotalUniEarned } from '../../state/stake/hooks'
import { useAggregateUniBalance, useTokenBalance } from '../../state/wallet/hooks' import { useAggregateUniBalance, useTokenBalance } from '../../state/wallet/hooks'
import { ExternalLink, StyledInternalLink, TYPE, UniTokenAnimated } from '../../theme' import { ExternalLink, StyledInternalLink, TYPE, UniTokenAnimated } from '../../theme'
import { computeUniCirculation } from '../../utils/computeUniCirculation' import { computeUniCirculation } from '../../utils/computeUniCirculation'
import useUSDCPrice from '../../utils/useUSDCPrice' import useUSDCPrice from '../../hooks/useUSDCPrice'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import { RowBetween } from '../Row' import { RowBetween } from '../Row'
import { Break, CardBGImage, CardNoise, CardSection, DataCard } from '../earn/styled' 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 uni = chainId ? UNI[chainId] : undefined
const total = useAggregateUniBalance() const total = useAggregateUniBalance()
const uniBalance: TokenAmount | undefined = useTokenBalance(account ?? undefined, uni) const uniBalance: CurrencyAmount<Token> | undefined = useTokenBalance(account ?? undefined, uni)
const uniToClaim: TokenAmount | undefined = useTotalUniEarned() const uniToClaim: CurrencyAmount<Token> | undefined = useTotalUniEarned()
const totalSupply: TokenAmount | undefined = useTotalSupply(uni) const totalSupply: CurrencyAmount<Token> | undefined = useTotalSupply(uni)
const uniPrice = useUSDCPrice(uni) const uniPrice = useUSDCPrice(uni)
const blockTimestamp = useCurrentBlockTimestamp() const blockTimestamp = useCurrentBlockTimestamp()
const unclaimedUni = useTokenBalance(useMerkleDistributorContract()?.address, uni) const unclaimedUni = useTokenBalance(useMerkleDistributorContract()?.address, uni)
const circulation: TokenAmount | undefined = useMemo( const circulation: CurrencyAmount<Token> | undefined = useMemo(
() => () =>
blockTimestamp && uni && chainId === ChainId.MAINNET blockTimestamp && uni && chainId === ChainId.MAINNET
? computeUniCirculation(uni, blockTimestamp, unclaimedUni) ? computeUniCirculation(uni, blockTimestamp, unclaimedUni)
@@ -117,7 +117,7 @@ export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowU
<TYPE.white color="white">{totalSupply?.toFixed(0, { groupSeparator: ',' })}</TYPE.white> <TYPE.white color="white">{totalSupply?.toFixed(0, { groupSeparator: ',' })}</TYPE.white>
</RowBetween> </RowBetween>
{uni && uni.chainId === ChainId.MAINNET ? ( {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} ) : null}
</AutoColumn> </AutoColumn>
</CardSection> </CardSection>

View File

@@ -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 React, { useState } from 'react'
import { Text } from 'rebass' import { Text } from 'rebass'
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import { darken } from 'polished' import { darken } from 'polished'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Moon, Sun } from 'react-feather'
import styled from 'styled-components' import styled from 'styled-components'
import Logo from '../../assets/svg/logo.svg' import Logo from '../../assets/svg/logo.svg'
import LogoDark from '../../assets/svg/logo_white.svg' import LogoDark from '../../assets/svg/logo_white.svg'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useDarkModeManager } from '../../state/user/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 { CardNoise } from '../earn/styled'
import { CountUp } from 'use-count-up'
import { TYPE, ExternalLink } from '../../theme' import { TYPE, ExternalLink } from '../../theme'
import { YellowCard } from '../Card' import { YellowCard } from '../Card'
import { Moon, Sun } from 'react-feather'
import Menu from '../Menu' import Menu from '../Menu'
import Row, { RowFixed } from '../Row' import Row, { RowFixed } from '../Row'
@@ -29,11 +29,10 @@ import { useUserHasSubmittedClaim } from '../../state/transactions/hooks'
import { Dots } from '../swap/styleds' import { Dots } from '../swap/styleds'
import Modal from '../Modal' import Modal from '../Modal'
import UniBalanceContent from './UniBalanceContent' import UniBalanceContent from './UniBalanceContent'
import usePrevious from '../../hooks/usePrevious'
const HeaderFrame = styled.div` const HeaderFrame = styled.div<{ showBackground: boolean }>`
display: grid; display: grid;
grid-template-columns: 1fr 120px; grid-template-columns: 120px 1fr 120px;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@@ -41,18 +40,25 @@ const HeaderFrame = styled.div`
width: 100%; width: 100%;
top: 0; top: 0;
position: relative; position: relative;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
padding: 1rem; padding: 1rem;
z-index: 2; z-index: 21;
${({ theme }) => theme.mediaWidth.upToMedium`
grid-template-columns: 1fr;
padding: 0 1rem;
width: calc(100%);
position: relative; 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`
padding: 1rem;
grid-template-columns: 120px 1fr;
`}; `};
${({ theme }) => theme.mediaWidth.upToExtraSmall` ${({ theme }) => theme.mediaWidth.upToExtraSmall`
padding: 0.5rem 1rem; padding: 1rem;
`} `}
` `
@@ -107,18 +113,25 @@ const HeaderRow = styled(RowFixed)`
` `
const HeaderLinks = styled(Row)` 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` ${({ theme }) => theme.mediaWidth.upToMedium`
padding: 1rem 0 1rem 1rem; justify-self: flex-end;
justify-content: flex-end; `};
`};
` `
const AccountElement = styled.div<{ active: boolean }>` const AccountElement = styled.div<{ active: boolean }>`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
background-color: ${({ theme, active }) => (!active ? theme.bg1 : theme.bg3)}; background-color: ${({ theme, active }) => (!active ? theme.bg1 : theme.bg2)};
border-radius: 12px; border-radius: 12px;
white-space: nowrap; white-space: nowrap;
width: 100%; width: 100%;
@@ -201,7 +214,7 @@ const UniIcon = styled.div`
const activeClassName = 'ACTIVE' const activeClassName = 'ACTIVE'
const StyledNavLink = styled(NavLink).attrs({ const StyledNavLink = styled(NavLink).attrs({
activeClassName activeClassName,
})` })`
${({ theme }) => theme.flexRowNoWrap} ${({ theme }) => theme.flexRowNoWrap}
align-items: left; align-items: left;
@@ -212,13 +225,14 @@ const StyledNavLink = styled(NavLink).attrs({
color: ${({ theme }) => theme.text2}; color: ${({ theme }) => theme.text2};
font-size: 1rem; font-size: 1rem;
width: fit-content; width: fit-content;
margin: 0 12px;
font-weight: 500; font-weight: 500;
padding: 8px 12px;
&.${activeClassName} { &.${activeClassName} {
border-radius: 12px; border-radius: 12px;
font-weight: 600; font-weight: 600;
color: ${({ theme }) => theme.text1}; color: ${({ theme }) => theme.text1};
background-color: ${({ theme }) => theme.bg2};
} }
:hover, :hover,
@@ -228,7 +242,7 @@ const StyledNavLink = styled(NavLink).attrs({
` `
const StyledExternalLink = styled(ExternalLink).attrs({ const StyledExternalLink = styled(ExternalLink).attrs({
activeClassName activeClassName,
})<{ isActive?: boolean }>` })<{ isActive?: boolean }>`
${({ theme }) => theme.flexRowNoWrap} ${({ theme }) => theme.flexRowNoWrap}
align-items: left; align-items: left;
@@ -267,7 +281,7 @@ export const StyledMenuButton = styled.button`
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 35px; height: 35px;
background-color: ${({ theme }) => theme.bg3}; background-color: ${({ theme }) => theme.bg2};
margin-left: 8px; margin-left: 8px;
padding: 0.15rem 0.5rem; padding: 0.15rem 0.5rem;
border-radius: 0.5rem; border-radius: 0.5rem;
@@ -291,7 +305,7 @@ const NETWORK_LABELS: { [chainId in ChainId]?: string } = {
[ChainId.RINKEBY]: 'Rinkeby', [ChainId.RINKEBY]: 'Rinkeby',
[ChainId.ROPSTEN]: 'Ropsten', [ChainId.ROPSTEN]: 'Ropsten',
[ChainId.GÖRLI]: 'Görli', [ChainId.GÖRLI]: 'Görli',
[ChainId.KOVAN]: 'Kovan' [ChainId.KOVAN]: 'Kovan',
} }
export default function Header() { export default function Header() {
@@ -308,16 +322,13 @@ export default function Header() {
const { claimTxn } = useUserHasSubmittedClaim(account ?? undefined) const { claimTxn } = useUserHasSubmittedClaim(account ?? undefined)
const aggregateBalance: TokenAmount | undefined = useAggregateUniBalance()
const [showUniBalanceModal, setShowUniBalanceModal] = useState(false) const [showUniBalanceModal, setShowUniBalanceModal] = useState(false)
const showClaimPopup = useShowClaimPopup() const showClaimPopup = useShowClaimPopup()
const countUpValue = aggregateBalance?.toFixed(0) ?? '0' const scrollY = useScrollPosition()
const countUpValuePrevious = usePrevious(countUpValue) ?? '0'
return ( return (
<HeaderFrame> <HeaderFrame showBackground={scrollY > 45}>
<ClaimModal /> <ClaimModal />
<Modal isOpen={showUniBalanceModal} onDismiss={() => setShowUniBalanceModal(false)}> <Modal isOpen={showUniBalanceModal} onDismiss={() => setShowUniBalanceModal(false)}>
<UniBalanceContent setShowUniBalanceModal={setShowUniBalanceModal} /> <UniBalanceContent setShowUniBalanceModal={setShowUniBalanceModal} />
@@ -328,6 +339,7 @@ export default function Header() {
<img width={'24px'} src={darkMode ? LogoDark : Logo} alt="logo" /> <img width={'24px'} src={darkMode ? LogoDark : Logo} alt="logo" />
</UniIcon> </UniIcon>
</Title> </Title>
</HeaderRow>
<HeaderLinks> <HeaderLinks>
<StyledNavLink id={`swap-nav-link`} to={'/swap'}> <StyledNavLink id={`swap-nav-link`} to={'/swap'}>
{t('swap')} {t('swap')}
@@ -339,23 +351,19 @@ export default function Header() {
Boolean(match) || Boolean(match) ||
pathname.startsWith('/add') || pathname.startsWith('/add') ||
pathname.startsWith('/remove') || pathname.startsWith('/remove') ||
pathname.startsWith('/create') || pathname.startsWith('/increase') ||
pathname.startsWith('/find') pathname.startsWith('/find')
} }
> >
{t('pool')} {t('pool')}
</StyledNavLink> </StyledNavLink>
<StyledNavLink id={`stake-nav-link`} to={'/uni'}>
UNI
</StyledNavLink>
<StyledNavLink id={`stake-nav-link`} to={'/vote'}> <StyledNavLink id={`stake-nav-link`} to={'/vote'}>
Vote Vote
</StyledNavLink> </StyledNavLink>
<StyledExternalLink id={`stake-nav-link`} href={'https://uniswap.info'}> <StyledExternalLink id={`stake-nav-link`} href={'https://info.uniswap.org'}>
Charts <span style={{ fontSize: '11px' }}></span> Charts <span style={{ fontSize: '11px', textDecoration: 'none !important' }}></span>
</StyledExternalLink> </StyledExternalLink>
</HeaderLinks> </HeaderLinks>
</HeaderRow>
<HeaderControls> <HeaderControls>
<HeaderElement> <HeaderElement>
<HideSmall> <HideSmall>
@@ -373,32 +381,6 @@ export default function Header() {
<CardNoise /> <CardNoise />
</UNIWrapper> </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' }}> <AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
{account && userEthBalance ? ( {account && userEthBalance ? (
<BalanceText style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}> <BalanceText style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>

View File

@@ -0,0 +1,63 @@
import Tooltip from 'components/Tooltip'
import React, { useState } from 'react'
import styled from 'styled-components'
const TextWrapper = styled.span<{ margin: boolean; link: boolean; fontSize?: string; adjustSize?: boolean }>`
position: relative;
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></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

View 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

View 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'
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>
// )
// }
// ]

View 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 },
]

View 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'
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

View File

@@ -13,7 +13,7 @@ export default function ListLogo({
logoURI, logoURI,
style, style,
size = '24px', size = '24px',
alt alt,
}: { }: {
logoURI: string logoURI: string
size?: string size?: string

View File

@@ -1,6 +1,7 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import { HelpCircle } from 'react-feather' import { Slash } from 'react-feather'
import { ImageProps } from 'rebass' import { ImageProps } from 'rebass'
import useTheme from '../../hooks/useTheme'
const BAD_SRCS: { [tokenAddress: string]: true } = {} 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 * 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 [, 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) { if (src) {
return ( return (
@@ -22,13 +25,14 @@ export default function Logo({ srcs, alt, ...rest }: LogoProps) {
{...rest} {...rest}
alt={alt} alt={alt}
src={src} src={src}
style={style}
onError={() => { onError={() => {
if (src) BAD_SRCS[src] = true 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 }} />
} }

View File

@@ -0,0 +1,23 @@
import { Story } from '@storybook/react/types-6-0'
import React from 'react'
import styled from 'styled-components'
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 = {}

View File

@@ -1,6 +1,7 @@
import React, { useRef } from 'react' import React, { useRef } from 'react'
import { BookOpen, Code, Info, MessageCircle, PieChart } from 'react-feather' 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 { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useOnClickOutside } from '../../hooks/useOnClickOutside' import { useOnClickOutside } from '../../hooks/useOnClickOutside'
@@ -10,6 +11,11 @@ import { useModalOpen, useToggleModal } from '../../state/application/hooks'
import { ExternalLink } from '../../theme' import { ExternalLink } from '../../theme'
import { ButtonPrimary } from '../Button' import { ButtonPrimary } from '../Button'
export enum FlyoutAlignment {
LEFT = 'LEFT',
RIGHT = 'RIGHT',
}
const StyledMenuIcon = styled(MenuIcon)` const StyledMenuIcon = styled(MenuIcon)`
path { path {
stroke: ${({ theme }) => theme.text1}; stroke: ${({ theme }) => theme.text1};
@@ -24,7 +30,7 @@ const StyledMenuButton = styled.button`
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 35px; height: 35px;
background-color: ${({ theme }) => theme.bg3}; background-color: ${({ theme }) => theme.bg2};
padding: 0.15rem 0.5rem; padding: 0.15rem 0.5rem;
border-radius: 0.5rem; border-radius: 0.5rem;
@@ -33,7 +39,7 @@ const StyledMenuButton = styled.button`
:focus { :focus {
cursor: pointer; cursor: pointer;
outline: none; outline: none;
background-color: ${({ theme }) => theme.bg4}; background-color: ${({ theme }) => theme.bg3};
} }
svg { 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` const StyledMenu = styled.div`
margin-left: 0.5rem; margin-left: 0.5rem;
display: flex; display: flex;
@@ -51,9 +63,9 @@ const StyledMenu = styled.div`
text-align: left; text-align: left;
` `
const MenuFlyout = styled.span` const MenuFlyout = styled.span<{ flyoutAlignment?: FlyoutAlignment }>`
min-width: 8.125rem; 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), 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); 0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 12px; border-radius: 12px;
@@ -62,16 +74,39 @@ const MenuFlyout = styled.span`
flex-direction: column; flex-direction: column;
font-size: 1rem; font-size: 1rem;
position: absolute; position: absolute;
top: 4rem; top: 3rem;
right: 0rem;
z-index: 100; z-index: 100;
${({ flyoutAlignment = FlyoutAlignment.RIGHT }) =>
flyoutAlignment === FlyoutAlignment.RIGHT
? css`
right: 0rem;
`
: css`
left: 0rem;
`};
${({ theme }) => theme.mediaWidth.upToMedium` ${({ theme }) => theme.mediaWidth.upToMedium`
top: -17.25rem; top: -17.25rem;
`}; `};
` `
const MenuItem = styled(ExternalLink)` 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; flex: 1;
padding: 0.5rem 0.5rem; padding: 0.5rem 0.5rem;
color: ${({ theme }) => theme.text2}; color: ${({ theme }) => theme.text2};
@@ -105,33 +140,84 @@ export default function Menu() {
{open && ( {open && (
<MenuFlyout> <MenuFlyout>
<MenuItem id="link" href="https://uniswap.org/"> <MenuItem href="https://uniswap.org/">
<Info size={14} /> <Info size={14} />
About <div>About</div>
</MenuItem> </MenuItem>
<MenuItem id="link" href="https://uniswap.org/docs/v2"> <MenuItem href="https://docs.uniswap.org/">
<BookOpen size={14} /> <BookOpen size={14} />
Docs <div>Docs</div>
</MenuItem> </MenuItem>
<MenuItem id="link" href={CODE_LINK}> <MenuItem href={CODE_LINK}>
<Code size={14} /> <Code size={14} />
Code <div>Code</div>
</MenuItem> </MenuItem>
<MenuItem id="link" href="https://discord.gg/FCfyBSbCU5"> <MenuItem href="https://discord.gg/FCfyBSbCU5">
<MessageCircle size={14} /> <MessageCircle size={14} />
Discord <div>Discord</div>
</MenuItem> </MenuItem>
<MenuItem id="link" href="https://uniswap.info/"> <MenuItem href="https://info.uniswap.org/">
<PieChart size={14} /> <PieChart size={14} />
Analytics <div>Analytics</div>
</MenuItem> </MenuItem>
{account && ( {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 Claim UNI
</ButtonPrimary> </UNIbutton>
)} )}
</MenuFlyout> </MenuFlyout>
)} )}
</StyledMenu> </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>
)
}

View File

@@ -29,13 +29,14 @@ const AnimatedDialogContent = animated(DialogContent)
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...rest }) => ( const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...rest }) => (
<AnimatedDialogContent {...rest} /> <AnimatedDialogContent {...rest} />
)).attrs({ )).attrs({
'aria-label': 'dialog' 'aria-label': 'dialog',
})` })`
overflow-y: ${({ mobile }) => (mobile ? 'scroll' : 'hidden')}; overflow-y: ${({ mobile }) => (mobile ? 'scroll' : 'hidden')};
&[data-reach-dialog-content] { &[data-reach-dialog-content] {
margin: 0 0 2rem 0; 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)}; box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadow1)};
padding: 0px; padding: 0px;
width: 50vw; width: 50vw;
@@ -63,13 +64,15 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
`} `}
${({ theme, mobile }) => theme.mediaWidth.upToSmall` ${({ theme, mobile }) => theme.mediaWidth.upToSmall`
width: 85vw; width: 85vw;
${mobile && ${
mobile &&
css` css`
width: 100vw; width: 100vw;
border-radius: 20px; border-radius: 20px;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
`} `
}
`} `}
} }
` `
@@ -89,25 +92,25 @@ export default function Modal({
minHeight = false, minHeight = false,
maxHeight = 90, maxHeight = 90,
initialFocusRef, initialFocusRef,
children children,
}: ModalProps) { }: ModalProps) {
const fadeTransition = useTransition(isOpen, null, { const fadeTransition = useTransition(isOpen, null, {
config: { duration: 200 }, config: { duration: 200 },
from: { opacity: 0 }, from: { opacity: 0 },
enter: { opacity: 1 }, enter: { opacity: 1 },
leave: { opacity: 0 } leave: { opacity: 0 },
}) })
const [{ y }, set] = useSpring(() => ({ y: 0, config: { mass: 1, tension: 210, friction: 20 } })) const [{ y }, set] = useSpring(() => ({ y: 0, config: { mass: 1, tension: 210, friction: 20 } }))
const bind = useGesture({ const bind = useGesture({
onDrag: state => { onDrag: (state) => {
set({ 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)) { if (state.movement[1] > 300 || (state.velocity > 3 && state.direction[1] > 0)) {
onDismiss() onDismiss()
} }
} },
}) })
return ( return (
@@ -126,7 +129,7 @@ export default function Modal({
{...(isMobile {...(isMobile
? { ? {
...bind(), ...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" aria-label="dialog content"

View File

@@ -41,7 +41,7 @@ export function LoadingView({ children, onDismiss }: { children: any; onDismiss:
export function SubmittedView({ export function SubmittedView({
children, children,
onDismiss, onDismiss,
hash hash,
}: { }: {
children: any children: any
onDismiss: () => void onDismiss: () => void

View File

@@ -3,14 +3,17 @@ import styled from 'styled-components'
import { darken } from 'polished' import { darken } from 'polished'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { NavLink, Link as HistoryLink } from 'react-router-dom' import { NavLink, Link as HistoryLink } from 'react-router-dom'
import { Percent } from '@uniswap/sdk-core'
import { ArrowLeft } from 'react-feather' import { ArrowLeft } from 'react-feather'
import { RowBetween } from '../Row' import { RowBetween } from '../Row'
// import QuestionHelper from '../QuestionHelper' import SettingsTab from '../Settings'
import Settings from '../Settings'
import { useDispatch } from 'react-redux' import { useDispatch } from 'react-redux'
import { AppDispatch } from 'state' import { AppDispatch } from 'state'
import { resetMintState } from 'state/mint/actions' 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` const Tabs = styled.div`
${({ theme }) => theme.flexRowNoWrap} ${({ theme }) => theme.flexRowNoWrap}
@@ -22,7 +25,7 @@ const Tabs = styled.div`
const activeClassName = 'ACTIVE' const activeClassName = 'ACTIVE'
const StyledNavLink = styled(NavLink).attrs({ const StyledNavLink = styled(NavLink).attrs({
activeClassName activeClassName,
})` })`
${({ theme }) => theme.flexRowNoWrap} ${({ theme }) => theme.flexRowNoWrap}
align-items: center; align-items: center;
@@ -59,7 +62,7 @@ const StyledArrowLeft = styled(ArrowLeft)`
export function SwapPoolTabs({ active }: { active: 'swap' | 'pool' }) { export function SwapPoolTabs({ active }: { active: 'swap' | 'pool' }) {
const { t } = useTranslation() const { t } = useTranslation()
return ( 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'}> <StyledNavLink id={`swap-nav-link`} to={'/swap'} isActive={() => active === 'swap'}>
{t('swap')} {t('swap')}
</StyledNavLink> </StyledNavLink>
@@ -70,21 +73,32 @@ export function SwapPoolTabs({ active }: { active: 'swap' | 'pool' }) {
) )
} }
export function FindPoolTabs() { export function FindPoolTabs({ origin }: { origin: string }) {
return ( return (
<Tabs> <Tabs>
<RowBetween style={{ padding: '1rem 1rem 0 1rem' }}> <RowBetween style={{ padding: '1rem 1rem 0 1rem' }}>
<HistoryLink to="/pool"> <HistoryLink to={origin}>
<StyledArrowLeft /> <StyledArrowLeft />
</HistoryLink> </HistoryLink>
<ActiveText>Import Pool</ActiveText> <ActiveText>Import Pool</ActiveText>
<Settings />
</RowBetween> </RowBetween>
</Tabs> </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 // reset states on back
const dispatch = useDispatch<AppDispatch>() const dispatch = useDispatch<AppDispatch>()
@@ -92,15 +106,21 @@ export function AddRemoveTabs({ adding, creating }: { adding: boolean; creating:
<Tabs> <Tabs>
<RowBetween style={{ padding: '1rem 1rem 0 1rem' }}> <RowBetween style={{ padding: '1rem 1rem 0 1rem' }}>
<HistoryLink <HistoryLink
to="/pool" to={'/pool' + (!!positionID ? `/${positionID.toString()}` : '')}
onClick={() => { onClick={() => {
adding && dispatch(resetMintState()) if (adding) {
// not 100% sure both of these are needed
dispatch(resetMintState())
dispatch(resetMintV3State())
}
}} }}
> >
<StyledArrowLeft /> <StyledArrowLeft stroke={theme.text2} />
</HistoryLink> </HistoryLink>
<ActiveText>{creating ? 'Create a pair' : adding ? 'Add Liquidity' : 'Remove Liquidity'}</ActiveText> <TYPE.mediumHeader fontWeight={500} fontSize={20}>
<Settings /> {creating ? 'Create a pair' : adding ? 'Add Liquidity' : 'Remove Liquidity'}
</TYPE.mediumHeader>
<SettingsTab placeholderSlippage={defaultSlippage} />
</RowBetween> </RowBetween>
</Tabs> </Tabs>
) )

View File

@@ -18,6 +18,7 @@ const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: s
text-overflow: ellipsis; text-overflow: ellipsis;
padding: 0px; padding: 0px;
-webkit-appearance: textfield; -webkit-appearance: textfield;
text-align: right;
::-webkit-search-decoration { ::-webkit-search-decoration {
-webkit-appearance: none; -webkit-appearance: none;
@@ -43,6 +44,7 @@ export const Input = React.memo(function InnerInput({
value, value,
onUserInput, onUserInput,
placeholder, placeholder,
prependSymbol,
...rest ...rest
}: { }: {
value: string | number value: string | number
@@ -50,6 +52,7 @@ export const Input = React.memo(function InnerInput({
error?: boolean error?: boolean
fontSize?: string fontSize?: string
align?: 'right' | 'left' align?: 'right' | 'left'
prependSymbol?: string | undefined
} & Omit<React.HTMLProps<HTMLInputElement>, 'ref' | 'onChange' | 'as'>) { } & Omit<React.HTMLProps<HTMLInputElement>, 'ref' | 'onChange' | 'as'>) {
const enforcer = (nextUserInput: string) => { const enforcer = (nextUserInput: string) => {
if (nextUserInput === '' || inputRegex.test(escapeRegExp(nextUserInput))) { if (nextUserInput === '' || inputRegex.test(escapeRegExp(nextUserInput))) {
@@ -60,14 +63,24 @@ export const Input = React.memo(function InnerInput({
return ( return (
<StyledInput <StyledInput
{...rest} {...rest}
value={value} value={prependSymbol && value ? prependSymbol + value : value}
onChange={event => { 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 // replace commas with periods, because uniswap exclusively uses period as the decimal separator
enforcer(formattedValue.replace(/,/g, '.'))
} else {
enforcer(event.target.value.replace(/,/g, '.')) enforcer(event.target.value.replace(/,/g, '.'))
}
}} }}
// universal input options // universal input options
inputMode="decimal" inputMode="decimal"
title="Token Amount"
autoComplete="off" autoComplete="off"
autoCorrect="off" autoCorrect="off"
// text-specific options // text-specific options

View File

@@ -8,11 +8,9 @@ import Portal from '@reach/portal'
const PopoverContainer = styled.div<{ show: boolean }>` const PopoverContainer = styled.div<{ show: boolean }>`
z-index: 9999; z-index: 9999;
visibility: ${(props) => (props.show ? 'visible' : 'hidden')};
visibility: ${props => (props.show ? 'visible' : 'hidden')}; opacity: ${(props) => (props.show ? 1 : 0)};
opacity: ${props => (props.show ? 1 : 0)};
transition: visibility 150ms linear, opacity 150ms linear; transition: visibility 150ms linear, opacity 150ms linear;
background: ${({ theme }) => theme.bg2}; background: ${({ theme }) => theme.bg2};
border: 1px solid ${({ theme }) => theme.bg3}; border: 1px solid ${({ theme }) => theme.bg3};
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.9, theme.shadow1)}; 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', strategy: 'fixed',
modifiers: [ modifiers: [
{ name: 'offset', options: { offset: [8, 8] } }, { name: 'offset', options: { offset: [8, 8] } },
{ name: 'arrow', options: { element: arrowElement } } { name: 'arrow', options: { element: arrowElement } },
] ],
}) })
const updateCallback = useCallback(() => { const updateCallback = useCallback(() => {
update && update() update && update()

View File

@@ -1,4 +1,4 @@
import { TokenAmount } from '@uniswap/sdk' import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { X } from 'react-feather' import { X } from 'react-feather'
import styled, { keyframes } from 'styled-components' import styled, { keyframes } from 'styled-components'
@@ -10,7 +10,7 @@ import {
useModalOpen, useModalOpen,
useShowClaimPopup, useShowClaimPopup,
useToggleSelfClaimModal, useToggleSelfClaimModal,
useToggleShowClaimPopup useToggleShowClaimPopup,
} from '../../state/application/hooks' } from '../../state/application/hooks'
import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/claim/hooks' import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/claim/hooks'
@@ -65,7 +65,7 @@ export default function ClaimPopup() {
// const userHasAvailableclaim = useUserHasAvailableClaim() // const userHasAvailableclaim = useUserHasAvailableClaim()
const userHasAvailableclaim: boolean = useUserHasAvailableClaim(account) 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 // listen for available claim and show popup if needed
useEffect(() => { useEffect(() => {

View File

@@ -23,7 +23,7 @@ export default function ListUpdatePopup({
listUrl, listUrl,
oldList, oldList,
newList, newList,
auto auto,
}: { }: {
popKey: string popKey: string
listUrl: string listUrl: string
@@ -40,7 +40,7 @@ export default function ListUpdatePopup({
ReactGA.event({ ReactGA.event({
category: 'Lists', category: 'Lists',
action: 'Update List from Popup', action: 'Update List from Popup',
label: listUrl label: listUrl,
}) })
dispatch(acceptListUpdate(listUrl)) dispatch(acceptListUpdate(listUrl))
removeThisPopup() removeThisPopup()

View File

@@ -21,7 +21,7 @@ export const Popup = styled.div`
display: inline-block; display: inline-block;
width: 100%; width: 100%;
padding: 1em; padding: 1em;
background-color: ${({ theme }) => theme.bg1}; background-color: ${({ theme }) => theme.bg0};
position: relative; position: relative;
border-radius: 10px; border-radius: 10px;
padding: 20px; padding: 20px;
@@ -49,7 +49,7 @@ const AnimatedFader = animated(Fader)
export default function PopupItem({ export default function PopupItem({
removeAfterMs, removeAfterMs,
content, content,
popKey popKey,
}: { }: {
removeAfterMs: number | null removeAfterMs: number | null
content: PopupContent content: PopupContent
@@ -74,12 +74,12 @@ export default function PopupItem({
let popupContent let popupContent
if ('txn' in content) { if ('txn' in content) {
const { const {
txn: { hash, success, summary } txn: { hash, success, summary },
} = content } = content
popupContent = <TransactionPopup hash={hash} success={success} summary={summary} /> popupContent = <TransactionPopup hash={hash} success={success} summary={summary} />
} else if ('listUpdate' in content) { } else if ('listUpdate' in content) {
const { const {
listUpdate: { listUrl, oldList, newList, auto } listUpdate: { listUrl, oldList, newList, auto },
} = content } = content
popupContent = <ListUpdatePopup popKey={popKey} listUrl={listUrl} oldList={oldList} newList={newList} auto={auto} /> popupContent = <ListUpdatePopup popKey={popKey} listUrl={listUrl} oldList={oldList} newList={newList} auto={auto} />
} }
@@ -87,7 +87,7 @@ export default function PopupItem({
const faderStyle = useSpring({ const faderStyle = useSpring({
from: { width: '100%' }, from: { width: '100%' },
to: { width: '0%' }, to: { width: '0%' },
config: { duration: removeAfterMs ?? undefined } config: { duration: removeAfterMs ?? undefined },
}) })
return ( return (

View File

@@ -15,7 +15,7 @@ const RowNoFlex = styled(AutoRow)`
export default function TransactionPopup({ export default function TransactionPopup({
hash, hash,
success, success,
summary summary,
}: { }: {
hash: string hash: string
success?: boolean success?: boolean

View File

@@ -33,7 +33,7 @@ const MobilePopupInner = styled.div`
const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>` const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>`
position: fixed; position: fixed;
top: ${({ extraPadding }) => (extraPadding ? '108px' : '88px')}; top: ${({ extraPadding }) => (extraPadding ? '72px' : '88px')};
right: 1rem; right: 1rem;
max-width: 355px !important; max-width: 355px !important;
width: 100%; width: 100%;
@@ -54,7 +54,7 @@ export default function Popups() {
<> <>
<FixedPopupColumn gap="20px" extraPadding={urlWarningActive}> <FixedPopupColumn gap="20px" extraPadding={urlWarningActive}>
<ClaimPopup /> <ClaimPopup />
{activePopups.map(item => ( {activePopups.map((item) => (
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} /> <PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
))} ))}
</FixedPopupColumn> </FixedPopupColumn>
@@ -63,7 +63,7 @@ export default function Popups() {
{activePopups // reverse so new items up front {activePopups // reverse so new items up front
.slice(0) .slice(0)
.reverse() .reverse()
.map(item => ( .map((item) => (
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} /> <PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
))} ))}
</MobilePopupInner> </MobilePopupInner>

View 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'
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>
)
}

View File

@@ -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)

View 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'
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>
)
}

View File

@@ -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 { darken } from 'polished'
import React, { useState } from 'react' import React, { useState } from 'react'
import { ChevronDown, ChevronUp } from 'react-feather' import { ChevronDown, ChevronUp } from 'react-feather'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { Text } from 'rebass' import { Text } from 'rebass'
import styled from 'styled-components' import styled from 'styled-components'
import { useTotalSupply } from '../../data/TotalSupply' import { useTotalSupply } from '../../hooks/useTotalSupply'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useTokenBalance } from '../../state/wallet/hooks' import { useTokenBalance } from '../../state/wallet/hooks'
@@ -48,7 +50,7 @@ interface PositionCardProps {
pair: Pair pair: Pair
showUnwrapped?: boolean showUnwrapped?: boolean
border?: string 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) { 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 totalPoolTokens = useTotalSupply(pair.liquidityToken)
const poolTokenPercentage = const poolTokenPercentage =
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw) !!userPoolBalance &&
? new Percent(userPoolBalance.raw, totalPoolTokens.raw) !!totalPoolTokens &&
JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userPoolBalance.quotient)
? new Percent(userPoolBalance.quotient, totalPoolTokens.quotient)
: undefined : undefined
const [token0Deposited, token1Deposited] = const [token0Deposited, token1Deposited] =
@@ -72,16 +76,16 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
!!totalPoolTokens && !!totalPoolTokens &&
!!userPoolBalance && !!userPoolBalance &&
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply // 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.token0, totalPoolTokens, userPoolBalance, false),
pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false) pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false),
] ]
: [undefined, undefined] : [undefined, undefined]
return ( return (
<> <>
{userPoolBalance && JSBI.greaterThan(userPoolBalance.raw, JSBI.BigInt(0)) ? ( {userPoolBalance && JSBI.greaterThan(userPoolBalance.quotient, JSBI.BigInt(0)) ? (
<GreyCard border={border}> <GreyCard border={border}>
<AutoColumn gap="12px"> <AutoColumn gap="12px">
<FixedHeightRow> <FixedHeightRow>
@@ -174,8 +178,10 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
const userPoolBalance = stakedBalance ? userDefaultPoolBalance?.add(stakedBalance) : userDefaultPoolBalance const userPoolBalance = stakedBalance ? userDefaultPoolBalance?.add(stakedBalance) : userDefaultPoolBalance
const poolTokenPercentage = const poolTokenPercentage =
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw) !!userPoolBalance &&
? new Percent(userPoolBalance.raw, totalPoolTokens.raw) !!totalPoolTokens &&
JSBI.greaterThanOrEqual(totalPoolTokens.quotient, userPoolBalance.quotient)
? new Percent(userPoolBalance.quotient, totalPoolTokens.quotient)
: undefined : undefined
const [token0Deposited, token1Deposited] = const [token0Deposited, token1Deposited] =
@@ -183,10 +189,10 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
!!totalPoolTokens && !!totalPoolTokens &&
!!userPoolBalance && !!userPoolBalance &&
// this condition is a short-circuit in the case where useTokenBalance updates sooner than useTotalSupply // 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.token0, totalPoolTokens, userPoolBalance, false),
pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false) pair.getLiquidityValue(pair.token1, totalPoolTokens, userPoolBalance, false),
] ]
: [undefined, undefined] : [undefined, undefined]
@@ -197,28 +203,23 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
<CardNoise /> <CardNoise />
<AutoColumn gap="12px"> <AutoColumn gap="12px">
<FixedHeightRow> <FixedHeightRow>
<AutoRow gap="8px"> <AutoRow gap="8px" style={{ marginLeft: '8px' }}>
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} size={20} /> <DoubleCurrencyLogo currency0={currency0} currency1={currency1} size={20} />
<Text fontWeight={500} fontSize={20}> <Text fontWeight={500} fontSize={20}>
{!currency0 || !currency1 ? <Dots>Loading</Dots> : `${currency0.symbol}/${currency1.symbol}`} {!currency0 || !currency1 ? <Dots>Loading</Dots> : `${currency0.symbol}/${currency1.symbol}`}
</Text> </Text>
</AutoRow> </AutoRow>
<RowFixed gap="8px"> <RowFixed gap="8px" style={{ marginRight: '4px' }}>
<ButtonEmpty <ButtonEmpty padding="6px 8px" borderRadius="12px" width="100%" onClick={() => setShowMore(!showMore)}>
padding="6px 8px"
borderRadius="12px"
width="fit-content"
onClick={() => setShowMore(!showMore)}
>
{showMore ? ( {showMore ? (
<> <>
Manage Manage
<ChevronUp size="20" style={{ marginLeft: '10px' }} /> <ChevronUp size="20" style={{ marginLeft: '8px', height: '20px', minWidth: '20px' }} />
</> </>
) : ( ) : (
<> <>
Manage Manage
<ChevronDown size="20" style={{ marginLeft: '10px' }} /> <ChevronDown size="20" style={{ marginLeft: '8px', height: '20px', minWidth: '20px' }} />
</> </>
)} )}
</ButtonEmpty> </ButtonEmpty>
@@ -295,19 +296,28 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
<ButtonSecondary padding="8px" borderRadius="8px"> <ButtonSecondary padding="8px" borderRadius="8px">
<ExternalLink <ExternalLink
style={{ width: '100%', textAlign: 'center' }} 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> View accrued fees and analytics<span style={{ fontSize: '11px' }}></span>
</ExternalLink> </ExternalLink>
</ButtonSecondary> </ButtonSecondary>
{userDefaultPoolBalance && JSBI.greaterThan(userDefaultPoolBalance.raw, BIG_INT_ZERO) && ( {userDefaultPoolBalance && JSBI.greaterThan(userDefaultPoolBalance.quotient, BIG_INT_ZERO) && (
<RowBetween marginTop="10px"> <RowBetween marginTop="10px">
<ButtonPrimary <ButtonPrimary
padding="8px" padding="8px"
borderRadius="8px" borderRadius="8px"
as={Link} as={Link}
to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`} to={`/migrate/v2/${pair.liquidityToken.address}`}
width="48%" width="32%"
>
Migrate
</ButtonPrimary>
<ButtonPrimary
padding="8px"
borderRadius="8px"
as={Link}
to={`/add/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
width="32%"
> >
Add Add
</ButtonPrimary> </ButtonPrimary>
@@ -315,14 +325,14 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
padding="8px" padding="8px"
borderRadius="8px" borderRadius="8px"
as={Link} as={Link}
width="48%" width="32%"
to={`/remove/${currencyId(currency0)}/${currencyId(currency1)}`} to={`/remove/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
> >
Remove Remove
</ButtonPrimary> </ButtonPrimary>
</RowBetween> </RowBetween>
)} )}
{stakedBalance && JSBI.greaterThan(stakedBalance.raw, BIG_INT_ZERO) && ( {stakedBalance && JSBI.greaterThan(stakedBalance.quotient, BIG_INT_ZERO) && (
<ButtonPrimary <ButtonPrimary
padding="8px" padding="8px"
borderRadius="8px" borderRadius="8px"

View 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,
// }

View File

@@ -0,0 +1,59 @@
import PositionListItem from 'components/PositionListItem'
import React from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
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} />
})}
</>
)
}

View 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}

View 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'
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>
&nbsp;{currencyQuote?.symbol}&nbsp;/&nbsp;{currencyBase?.symbol}
</DataText>
&nbsp;
<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>
)
}

View 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>
)
}

View File

@@ -1,49 +1,42 @@
import React from 'react' import React, { useContext } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { RowBetween } from '../Row'
import { AutoColumn } from '../Column' 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)` const Grouping = styled(AutoColumn)`
width: 50%; width: fit-content;
padding: 4px;
/* background-color: ${({ theme }) => theme.bg2}; */
border-radius: 16px;
` `
const Circle = styled.div<{ confirmed?: boolean; disabled?: boolean }>` const Circle = styled.div<{ confirmed?: boolean; disabled?: boolean }>`
min-width: 20px; width: 48px;
min-height: 20px; height: 48px;
background-color: ${({ theme, confirmed, disabled }) => background-color: ${({ theme, confirmed, disabled }) =>
disabled ? theme.bg4 : confirmed ? theme.green1 : theme.primary1}; disabled ? theme.bg3 : confirmed ? theme.green1 : theme.primary1};
border-radius: 50%; border-radius: 50%;
color: ${({ theme }) => theme.white}; color: ${({ theme, disabled }) => (disabled ? theme.text3 : theme.text1)};
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
line-height: 8px; line-height: 8px;
font-size: 12px; font-size: 16px;
padding: 1rem;
` `
const CircleRow = styled.div` const CircleRow = styled.div`
width: calc(100% - 20px);
display: flex; display: flex;
flex-direction: column;
align-items: center; 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 { interface ProgressCirclesProps {
steps: boolean[] steps: boolean[]
disabled?: boolean disabled?: boolean
@@ -60,6 +53,8 @@ interface ProgressCirclesProps {
* @param steps array of booleans where true means step is complete * @param steps array of booleans where true means step is complete
*/ */
export default function ProgressCircles({ steps, disabled = false, ...rest }: ProgressCirclesProps) { export default function ProgressCircles({ steps, disabled = false, ...rest }: ProgressCirclesProps) {
const theme = useContext(ThemeContext)
return ( return (
<Wrapper justify={'center'} {...rest}> <Wrapper justify={'center'} {...rest}>
<Grouping> <Grouping>
@@ -67,13 +62,13 @@ export default function ProgressCircles({ steps, disabled = false, ...rest }: Pr
return ( return (
<CircleRow key={i}> <CircleRow key={i}>
<Circle confirmed={step} disabled={disabled || (!steps[i - 1] && i !== 0)}> <Circle confirmed={step} disabled={disabled || (!steps[i - 1] && i !== 0)}>
{step ? '✓' : i + 1} {step ? '✓' : i + 1 + '.'}
</Circle> </Circle>
<Connector prevConfirmed={step} disabled={disabled} /> <TYPE.main color={theme.text4}>|</TYPE.main>
</CircleRow> </CircleRow>
) )
})} })}
<Circle disabled={disabled || !steps[steps.length - 1]}>{steps.length + 1}</Circle> <Circle disabled={disabled || !steps[steps.length - 1]}>{steps.length + 1 + '.'}</Circle>
</Grouping> </Grouping>
</Wrapper> </Wrapper>
) )

View File

@@ -1,5 +1,4 @@
import React, { useCallback, useState } from 'react' import React, { useCallback, useState } from 'react'
import { HelpCircle as Question } from 'react-feather'
import styled from 'styled-components' import styled from 'styled-components'
import Tooltip from '../Tooltip' import Tooltip from '../Tooltip'
@@ -7,12 +6,15 @@ const QuestionWrapper = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0.2rem; padding: 0px;
width: 18px;
height: 18px;
border: none; border: none;
background: none; background: none;
outline: none; outline: none;
cursor: default; cursor: default;
border-radius: 36px; border-radius: 36px;
font-size: 12px;
background-color: ${({ theme }) => theme.bg2}; background-color: ${({ theme }) => theme.bg2};
color: ${({ theme }) => theme.text2}; color: ${({ theme }) => theme.text2};
@@ -44,20 +46,20 @@ const LightQuestionWrapper = styled.div`
` `
const QuestionMark = styled.span` 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 [show, setShow] = useState<boolean>(false)
const open = useCallback(() => setShow(true), [setShow]) const open = useCallback(() => setShow(true), [setShow])
const close = useCallback(() => setShow(false), [setShow]) const close = useCallback(() => setShow(false), [setShow])
return ( return (
<span style={{ marginLeft: 4 }}> <span style={{ marginLeft: 4, display: 'flex', alignItems: 'center' }}>
<Tooltip text={text} show={show}> <Tooltip text={text} show={show}>
<QuestionWrapper onClick={open} onMouseEnter={open} onMouseLeave={close}> <QuestionWrapper onClick={open} onMouseEnter={open} onMouseLeave={close}>
<Question size={16} /> <QuestionMark>?</QuestionMark>
</QuestionWrapper> </QuestionWrapper>
</Tooltip> </Tooltip>
</span> </span>

View 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>
)
}

View 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
}

View File

@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ChainId, Currency, currencyEquals, ETHER, Token } from '@uniswap/sdk' import { ChainId, Currency, currencyEquals, Token, ETHER } from '@uniswap/sdk-core'
import styled from 'styled-components' import styled from 'styled-components'
import { SUGGESTED_BASES } from '../../constants' import { SUGGESTED_BASES } from '../../constants'
@@ -28,7 +28,7 @@ const BaseWrapper = styled.div<{ disable?: boolean }>`
export default function CommonBases({ export default function CommonBases({
chainId, chainId,
onSelect, onSelect,
selectedCurrency selectedCurrency,
}: { }: {
chainId?: ChainId chainId?: ChainId
selectedCurrency?: Currency | null selectedCurrency?: Currency | null
@@ -49,15 +49,15 @@ export default function CommonBases({
onSelect(ETHER) onSelect(ETHER)
} }
}} }}
disable={selectedCurrency === ETHER} disable={selectedCurrency?.isEther}
> >
<CurrencyLogo currency={ETHER} style={{ marginRight: 8 }} /> <CurrencyLogo currency={ETHER} style={{ marginRight: 8 }} />
<Text fontWeight={500} fontSize={16}> <Text fontWeight={500} fontSize={16}>
ETH ETH
</Text> </Text>
</BaseWrapper> </BaseWrapper>
{(chainId ? SUGGESTED_BASES[chainId] : []).map((token: Token) => { {(typeof chainId === 'number' ? SUGGESTED_BASES[chainId] ?? [] : []).map((token: Token) => {
const selected = selectedCurrency instanceof Token && selectedCurrency.address === token.address const selected = selectedCurrency?.isToken && selectedCurrency.address === token.address
return ( return (
<BaseWrapper onClick={() => !selected && onSelect(token)} disable={selected} key={token.address}> <BaseWrapper onClick={() => !selected && onSelect(token)} disable={selected} key={token.address}>
<CurrencyLogo currency={token} style={{ marginRight: 8 }} /> <CurrencyLogo currency={token} style={{ marginRight: 8 }} />

View File

@@ -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 React, { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'
import { FixedSizeList } from 'react-window' import { FixedSizeList } from 'react-window'
import { Text } from 'rebass' import { Text } from 'rebass'
import styled from 'styled-components' import styled from 'styled-components'
import { useActiveWeb3React } from '../../hooks' 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 { useCurrencyBalance } from '../../state/wallet/hooks'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { useIsUserAddedToken, useAllInactiveTokens } from '../../hooks/Tokens' import { useIsUserAddedToken } from '../../hooks/Tokens'
import Column from '../Column' import Column from '../Column'
import { RowFixed, RowBetween } from '../Row' import { RowFixed, RowBetween } from '../Row'
import CurrencyLogo from '../CurrencyLogo' import CurrencyLogo from '../CurrencyLogo'
@@ -23,7 +24,7 @@ import QuestionHelper from 'components/QuestionHelper'
import useTheme from 'hooks/useTheme' import useTheme from 'hooks/useTheme'
function currencyKey(currency: Currency): string { function currencyKey(currency: Currency): string {
return currency instanceof Token ? currency.address : currency === ETHER ? 'ETHER' : '' return currency.isToken ? currency.address : 'ETHER'
} }
const StyledBalanceText = styled(Text)` const StyledBalanceText = styled(Text)`
@@ -55,7 +56,7 @@ const FixedContentRow = styled.div`
align-items: center; align-items: center;
` `
function Balance({ balance }: { balance: CurrencyAmount }) { function Balance({ balance }: { balance: CurrencyAmount<Currency> }) {
return <StyledBalanceText title={balance.toExact()}>{balance.toSignificant(4)}</StyledBalanceText> return <StyledBalanceText title={balance.toExact()}>{balance.toSignificant(4)}</StyledBalanceText>
} }
@@ -102,7 +103,7 @@ function CurrencyRow({
onSelect, onSelect,
isSelected, isSelected,
otherSelected, otherSelected,
style style,
}: { }: {
currency: Currency currency: Currency
onSelect: () => void onSelect: () => void
@@ -113,7 +114,7 @@ function CurrencyRow({
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const key = currencyKey(currency) const key = currencyKey(currency)
const selectedTokenList = useCombinedActiveList() const selectedTokenList = useCombinedActiveList()
const isOnSelectedList = isTokenOnList(selectedTokenList, currency) const isOnSelectedList = isTokenOnList(selectedTokenList, currency.isToken ? currency : undefined)
const customAdded = useIsUserAddedToken(currency) const customAdded = useIsUserAddedToken(currency)
const balance = useCurrencyBalance(account ?? undefined, currency) const balance = useCurrencyBalance(account ?? undefined, currency)
@@ -132,7 +133,7 @@ function CurrencyRow({
{currency.symbol} {currency.symbol}
</Text> </Text>
<TYPE.darkGray ml="0px" fontSize={'12px'} fontWeight={300}> <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> </TYPE.darkGray>
</Column> </Column>
<TokenTags currency={currency} /> <TokenTags currency={currency} />
@@ -143,56 +144,14 @@ function CurrencyRow({
) )
} }
export default function CurrencyList({ const BREAK_LINE = 'BREAK'
height, type BreakLine = typeof BREAK_LINE
currencies, function isBreakLine(x: unknown): x is BreakLine {
selectedCurrency, return x === BREAK_LINE
onCurrencySelect, }
otherCurrency,
fixedListRef,
showETH,
showImportView,
setImportToken,
breakIndex
}: {
height: number
currencies: Currency[]
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)]
}
return formatted
}, [breakIndex, currencies, showETH])
const { chainId } = useActiveWeb3React() function BreakLineComponent({ style }: { style: CSSProperties }) {
const theme = useTheme() 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)
const token = wrappedCurrency(currency, chainId)
const showImport = inactiveTokens && token && Object.keys(inactiveTokens).includes(token.address)
if (index === breakIndex || !data) {
return ( return (
<FixedContentRow style={style}> <FixedContentRow style={style}>
<LightGreyCard padding="8px 12px" borderRadius="8px"> <LightGreyCard padding="8px 12px" borderRadius="8px">
@@ -208,19 +167,61 @@ export default function CurrencyList({
</LightGreyCard> </LightGreyCard>
</FixedContentRow> </FixedContentRow>
) )
}
export default function CurrencyList({
height,
currencies,
otherListTokens,
selectedCurrency,
onCurrencySelect,
otherCurrency,
fixedListRef,
showImportView,
setImportToken,
}: {
height: number
currencies: Currency[]
otherListTokens?: WrappedTokenInfo[]
selectedCurrency?: Currency | null
onCurrencySelect: (currency: Currency) => void
otherCurrency?: Currency | null
fixedListRef?: MutableRefObject<FixedSizeList | undefined>
showImportView: () => void
setImportToken: (token: Token) => void
}) {
const itemData: (Currency | BreakLine)[] = useMemo(() => {
if (otherListTokens && otherListTokens?.length > 0) {
return [...currencies, BREAK_LINE, ...otherListTokens]
} }
return currencies
}, [currencies, otherListTokens])
const { chainId } = useActiveWeb3React()
const Row = useCallback(
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 = index > currencies.length
if (showImport && token) { if (showImport && token) {
return ( return (
<ImportRow <ImportRow style={style} token={token} showImportView={showImportView} setImportToken={setImportToken} dim />
style={style}
token={token}
showImportView={showImportView}
setImportToken={setImportToken}
dim={true}
/>
) )
} else { } else if (currency) {
return ( return (
<CurrencyRow <CurrencyRow
style={style} style={style}
@@ -230,22 +231,18 @@ export default function CurrencyList({
otherSelected={otherSelected} otherSelected={otherSelected}
/> />
) )
} else {
return null
} }
}, },
[ [chainId, currencies.length, onCurrencySelect, otherCurrency, selectedCurrency, setImportToken, showImportView]
chainId,
inactiveTokens,
onCurrencySelect,
otherCurrency,
selectedCurrency,
setImportToken,
showImportView,
breakIndex,
theme.text1
]
) )
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 ( return (
<FixedSizeList <FixedSizeList

View File

@@ -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 React, { KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { FixedSizeList } from 'react-window' import { FixedSizeList } from 'react-window'
import { Text } from 'rebass' import { Text } from 'rebass'
import { useActiveWeb3React } from '../../hooks' 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 { CloseIcon, TYPE, ButtonText, IconWrapper } from '../../theme'
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
import Column from '../Column' import Column from '../Column'
@@ -61,7 +61,7 @@ export function CurrencySearch({
isOpen, isOpen,
showManageView, showManageView,
showImportView, showImportView,
setImportToken setImportToken,
}: CurrencySearchProps) { }: CurrencySearchProps) {
const { t } = useTranslation() const { t } = useTranslation()
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
@@ -79,7 +79,9 @@ export function CurrencySearch({
// if they input an address, use it // if they input an address, use it
const isAddressSearch = isAddress(debouncedQuery) const isAddressSearch = isAddress(debouncedQuery)
const searchToken = useToken(debouncedQuery) const searchToken = useToken(debouncedQuery)
const searchTokenIsAdded = useIsUserAddedToken(searchToken) const searchTokenIsAdded = useIsUserAddedToken(searchToken)
useEffect(() => { useEffect(() => {
@@ -87,16 +89,11 @@ export function CurrencySearch({
ReactGA.event({ ReactGA.event({
category: 'Currency Select', category: 'Currency Select',
action: 'Search by address', action: 'Search by address',
label: isAddressSearch label: isAddressSearch,
}) })
} }
}, [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 tokenComparator = useTokenComparator(invertSearchOrder)
const filteredTokens: Token[] = useMemo(() => { const filteredTokens: Token[] = useMemo(() => {
@@ -109,6 +106,14 @@ export function CurrencySearch({
const filteredSortedTokens = useSortedTokensByQuery(sortedTokens, debouncedQuery) 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( const handleCurrencySelect = useCallback(
(currency: Currency) => { (currency: Currency) => {
onCurrencySelect(currency) onCurrencySelect(currency)
@@ -124,7 +129,7 @@ export function CurrencySearch({
// manage focus on modal show // manage focus on modal show
const inputRef = useRef<HTMLInputElement>() const inputRef = useRef<HTMLInputElement>()
const handleInput = useCallback(event => { const handleInput = useCallback((event) => {
const input = event.target.value const input = event.target.value
const checksummedInput = isAddress(input) const checksummedInput = isAddress(input)
setSearchQuery(checksummedInput || input) setSearchQuery(checksummedInput || input)
@@ -137,17 +142,17 @@ export function CurrencySearch({
const s = debouncedQuery.toLowerCase().trim() const s = debouncedQuery.toLowerCase().trim()
if (s === 'eth') { if (s === 'eth') {
handleCurrencySelect(ETHER) handleCurrencySelect(ETHER)
} else if (filteredSortedTokens.length > 0) { } else if (filteredSortedTokensWithETH.length > 0) {
if ( if (
filteredSortedTokens[0].symbol?.toLowerCase() === debouncedQuery.trim().toLowerCase() || filteredSortedTokensWithETH[0].symbol?.toLowerCase() === debouncedQuery.trim().toLowerCase() ||
filteredSortedTokens.length === 1 filteredSortedTokensWithETH.length === 1
) { ) {
handleCurrencySelect(filteredSortedTokens[0]) handleCurrencySelect(filteredSortedTokensWithETH[0])
} }
} }
} }
}, },
[filteredSortedTokens, handleCurrencySelect, debouncedQuery] [filteredSortedTokensWithETH, handleCurrencySelect, debouncedQuery]
) )
// menu ui // menu ui
@@ -156,8 +161,9 @@ export function CurrencySearch({
useOnClickOutside(node, open ? toggle : undefined) useOnClickOutside(node, open ? toggle : undefined)
// if no results on main list, show option to expand into inactive // if no results on main list, show option to expand into inactive
const inactiveTokens = useFoundOnInactiveList(debouncedQuery) const filteredInactiveTokens = useSearchInactiveTokenLists(
const filteredInactiveTokens: Token[] = useSortedTokensByQuery(inactiveTokens, debouncedQuery) filteredTokens.length === 0 || (debouncedQuery.length > 2 && !isAddressSearch) ? debouncedQuery : undefined
)
return ( return (
<ContentWrapper> <ContentWrapper>
@@ -195,11 +201,8 @@ export function CurrencySearch({
{({ height }) => ( {({ height }) => (
<CurrencyList <CurrencyList
height={height} height={height}
showETH={showETH} currencies={filteredSortedTokensWithETH}
currencies={ otherListTokens={filteredInactiveTokens}
filteredInactiveTokens ? filteredSortedTokens.concat(filteredInactiveTokens) : filteredSortedTokens
}
breakIndex={inactiveTokens && filteredSortedTokens ? filteredSortedTokens.length : undefined}
onCurrencySelect={handleCurrencySelect} onCurrencySelect={handleCurrencySelect}
otherCurrency={otherSelectedCurrency} otherCurrency={otherSelectedCurrency}
selectedCurrency={selectedCurrency} selectedCurrency={selectedCurrency}
@@ -224,7 +227,7 @@ export function CurrencySearch({
<IconWrapper size="16px" marginRight="6px"> <IconWrapper size="16px" marginRight="6px">
<Edit /> <Edit />
</IconWrapper> </IconWrapper>
<TYPE.main color={theme.blue1}>Manage</TYPE.main> <TYPE.main color={theme.blue1}>Manage Token Lists</TYPE.main>
</RowFixed> </RowFixed>
</ButtonText> </ButtonText>
</Row> </Row>

View File

@@ -1,4 +1,4 @@
import { Currency, Token } from '@uniswap/sdk' import { Currency, Token } from '@uniswap/sdk-core'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import useLast from '../../hooks/useLast' import useLast from '../../hooks/useLast'
import Modal from '../Modal' import Modal from '../Modal'
@@ -22,7 +22,7 @@ export enum CurrencyModalView {
search, search,
manage, manage,
importToken, importToken,
importList importList,
} }
export default function CurrencySearchModal({ export default function CurrencySearchModal({
@@ -31,7 +31,7 @@ export default function CurrencySearchModal({
onCurrencySelect, onCurrencySelect,
selectedCurrency, selectedCurrency,
otherSelectedCurrency, otherSelectedCurrency,
showCommonBases = false showCommonBases = false,
}: CurrencySearchModalProps) { }: CurrencySearchModalProps) {
const [modalView, setModalView] = useState<CurrencyModalView>(CurrencyModalView.manage) const [modalView, setModalView] = useState<CurrencyModalView>(CurrencyModalView.manage)
const lastOpen = useLast(isOpen) const lastOpen = useLast(isOpen)

View File

@@ -56,7 +56,7 @@ export function ImportList({ listURL, list, setModalView, onDismiss }: ImportPro
ReactGA.event({ ReactGA.event({
category: 'Lists', category: 'Lists',
action: 'Add List', action: 'Add List',
label: listURL label: listURL,
}) })
// turn list on // turn list on
@@ -64,11 +64,11 @@ export function ImportList({ listURL, list, setModalView, onDismiss }: ImportPro
// go back to lists // go back to lists
setModalView(CurrencyModalView.manage) setModalView(CurrencyModalView.manage)
}) })
.catch(error => { .catch((error) => {
ReactGA.event({ ReactGA.event({
category: 'Lists', category: 'Lists',
action: 'Add List Failed', action: 'Add List Failed',
label: listURL label: listURL,
}) })
setAddError(error.message) setAddError(error.message)
dispatch(removeList(listURL)) dispatch(removeList(listURL))

View File

@@ -1,17 +1,16 @@
import React, { CSSProperties } from 'react' import React, { CSSProperties } from 'react'
import { Token } from '@uniswap/sdk' import { Token } from '@uniswap/sdk-core'
import { AutoRow, RowFixed } from 'components/Row' import { AutoRow, RowFixed } from 'components/Row'
import { AutoColumn } from 'components/Column' import { AutoColumn } from 'components/Column'
import CurrencyLogo from 'components/CurrencyLogo' import CurrencyLogo from 'components/CurrencyLogo'
import { TYPE } from 'theme' import { TYPE } from 'theme'
import ListLogo from 'components/ListLogo' import ListLogo from 'components/ListLogo'
import { useActiveWeb3React } from 'hooks'
import { useCombinedInactiveList } from 'state/lists/hooks'
import useTheme from 'hooks/useTheme' import useTheme from 'hooks/useTheme'
import { ButtonPrimary } from 'components/Button' import { ButtonPrimary } from 'components/Button'
import styled from 'styled-components' import styled from 'styled-components'
import { useIsUserAddedToken, useIsTokenActive } from 'hooks/Tokens' import { useIsUserAddedToken, useIsTokenActive } from 'hooks/Tokens'
import { CheckCircle } from 'react-feather' import { CheckCircle } from 'react-feather'
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
const TokenSection = styled.div<{ dim?: boolean }>` const TokenSection = styled.div<{ dim?: boolean }>`
padding: 4px 20px; padding: 4px 20px;
@@ -45,7 +44,7 @@ export default function ImportRow({
style, style,
dim, dim,
showImportView, showImportView,
setImportToken setImportToken,
}: { }: {
token: Token token: Token
style?: CSSProperties style?: CSSProperties
@@ -53,18 +52,14 @@ export default function ImportRow({
showImportView: () => void showImportView: () => void
setImportToken: (token: Token) => void setImportToken: (token: Token) => void
}) { }) {
// gloabls
const { chainId } = useActiveWeb3React()
const theme = useTheme() const theme = useTheme()
// check if token comes from list
const inactiveTokenList = useCombinedInactiveList()
const list = chainId && inactiveTokenList?.[chainId]?.[token.address]?.list
// check if already active on list or local storage tokens // check if already active on list or local storage tokens
const isAdded = useIsUserAddedToken(token) const isAdded = useIsUserAddedToken(token)
const isActive = useIsTokenActive(token) const isActive = useIsTokenActive(token)
const list = token instanceof WrappedTokenInfo ? token.list : undefined
return ( return (
<TokenSection style={style}> <TokenSection style={style}>
<CurrencyLogo currency={token} size={'24px'} style={{ opacity: dim ? '0.6' : '1' }} /> <CurrencyLogo currency={token} size={'24px'} style={{ opacity: dim ? '0.6' : '1' }} />

View File

@@ -1,12 +1,13 @@
import React, { useState } from 'react' import { TokenList } from '@uniswap/token-lists/dist/types'
import { Token, Currency } from '@uniswap/sdk' import React from 'react'
import { Token, Currency } from '@uniswap/sdk-core'
import styled from 'styled-components' import styled from 'styled-components'
import { TYPE, CloseIcon } from 'theme' import { TYPE, CloseIcon } from 'theme'
import Card from 'components/Card' import Card from 'components/Card'
import { AutoColumn } from 'components/Column' import { AutoColumn } from 'components/Column'
import { RowBetween, RowFixed, AutoRow } from 'components/Row' import { RowBetween, RowFixed } from 'components/Row'
import CurrencyLogo from 'components/CurrencyLogo' import CurrencyLogo from 'components/CurrencyLogo'
import { ArrowLeft, AlertTriangle } from 'react-feather' import { ArrowLeft, AlertCircle } from 'react-feather'
import { transparentize } from 'polished' import { transparentize } from 'polished'
import useTheme from 'hooks/useTheme' import useTheme from 'hooks/useTheme'
import { ButtonPrimary } from 'components/Button' import { ButtonPrimary } from 'components/Button'
@@ -15,9 +16,8 @@ import { useAddUserToken } from 'state/user/hooks'
import { getEtherscanLink } from 'utils' import { getEtherscanLink } from 'utils'
import { useActiveWeb3React } from 'hooks' import { useActiveWeb3React } from 'hooks'
import { ExternalLink } from '../../theme/components' import { ExternalLink } from '../../theme/components'
import { useCombinedInactiveList } from 'state/lists/hooks'
import ListLogo from 'components/ListLogo' import ListLogo from 'components/ListLogo'
import { PaddedColumn, Checkbox } from './styleds' import { PaddedColumn } from './styleds'
const Wrapper = styled.div` const Wrapper = styled.div`
position: relative; position: relative;
@@ -41,67 +41,73 @@ const AddressText = styled(TYPE.blue)`
interface ImportProps { interface ImportProps {
tokens: Token[] tokens: Token[]
list?: TokenList
onBack?: () => void onBack?: () => void
onDismiss?: () => void onDismiss?: () => void
handleCurrencySelect?: (currency: Currency) => void handleCurrencySelect?: (currency: Currency) => void
} }
export function ImportToken({ tokens, onBack, onDismiss, handleCurrencySelect }: ImportProps) { export function ImportToken({ tokens, list, onBack, onDismiss, handleCurrencySelect }: ImportProps) {
const theme = useTheme() const theme = useTheme()
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const [confirmed, setConfirmed] = useState(false)
const addToken = useAddUserToken() const addToken = useAddUserToken()
// use for showing import source on inactive tokens
const inactiveTokenList = useCombinedInactiveList()
// higher warning severity if either is not on a list
const fromLists =
(chainId && inactiveTokenList?.[chainId]?.[tokens[0]?.address]?.list) ||
(chainId && inactiveTokenList?.[chainId]?.[tokens[1]?.address]?.list)
return ( return (
<Wrapper> <Wrapper>
<PaddedColumn gap="14px" style={{ width: '100%', flex: '1 1' }}> <PaddedColumn gap="14px" style={{ width: '100%', flex: '1 1' }}>
<RowBetween> <RowBetween>
{onBack ? <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} /> : <div></div>} {onBack ? <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} /> : <div />}
<TYPE.mediumHeader>Import {tokens.length > 1 ? 'Tokens' : 'Token'}</TYPE.mediumHeader> <TYPE.mediumHeader>Import {tokens.length > 1 ? 'Tokens' : 'Token'}</TYPE.mediumHeader>
{onDismiss ? <CloseIcon onClick={onDismiss} /> : <div></div>} {onDismiss ? <CloseIcon onClick={onDismiss} /> : <div />}
</RowBetween> </RowBetween>
</PaddedColumn> </PaddedColumn>
<SectionBreak /> <SectionBreak />
<PaddedColumn gap="md"> <AutoColumn gap="md" style={{ marginBottom: '32px', padding: '1rem' }}>
{tokens.map(token => { <AutoColumn justify="center" style={{ textAlign: 'center', gap: '16px', padding: '1rem' }}>
const list = chainId && inactiveTokenList?.[chainId]?.[token.address]?.list <AlertCircle size={48} stroke={theme.text2} strokeWidth={1} />
<TYPE.body fontWeight={400} fontSize={16}>
{
"This token doesn't appear on the active token list(s). Make sure this is the token that you want to trade."
}
</TYPE.body>
</AutoColumn>
{tokens.map((token) => {
return ( return (
<Card backgroundColor={theme.bg2} key={'import' + token.address} className=".token-warning-container"> <Card
<AutoColumn gap="10px"> backgroundColor={theme.bg2}
<AutoRow align="center"> key={'import' + token.address}
<CurrencyLogo currency={token} size={'24px'} /> className=".token-warning-container"
<TYPE.body ml="8px" mr="8px" fontWeight={500}> padding="2rem"
>
<AutoColumn gap="10px" justify="center">
<CurrencyLogo currency={token} size={'32px'} />
<AutoColumn gap="4px" justify="center">
<TYPE.body ml="8px" mr="8px" fontWeight={500} fontSize={20}>
{token.symbol} {token.symbol}
</TYPE.body> </TYPE.body>
<TYPE.darkGray fontWeight={300}>{token.name}</TYPE.darkGray> <TYPE.darkGray fontWeight={400} fontSize={14}>
</AutoRow> {token.name}
</TYPE.darkGray>
</AutoColumn>
{chainId && ( {chainId && (
<ExternalLink href={getEtherscanLink(chainId, token.address, 'address')}> <ExternalLink href={getEtherscanLink(chainId, token.address, 'address')}>
<AddressText>{token.address}</AddressText> <AddressText fontSize={12}>{token.address}</AddressText>
</ExternalLink> </ExternalLink>
)} )}
{list !== undefined ? ( {list !== undefined ? (
<RowFixed> <RowFixed>
{list.logoURI && <ListLogo logoURI={list.logoURI} size="12px" />} {list.logoURI && <ListLogo logoURI={list.logoURI} size="16px" />}
<TYPE.small ml="6px" color={theme.text3}> <TYPE.small ml="6px" fontSize={14} color={theme.text3}>
via {list.name} via {list.name} token list
</TYPE.small> </TYPE.small>
</RowFixed> </RowFixed>
) : ( ) : (
<WarningWrapper borderRadius="4px" padding="4px" highWarning={true}> <WarningWrapper borderRadius="4px" padding="4px" highWarning={true}>
<RowFixed> <RowFixed>
<AlertTriangle stroke={theme.red1} size="10px" /> <AlertCircle stroke={theme.red1} size="10px" />
<TYPE.body color={theme.red1} ml="4px" fontSize="10px" fontWeight={500}> <TYPE.body color={theme.red1} ml="4px" fontSize="10px" fontWeight={500}>
Unknown Source Unknown Source
</TYPE.body> </TYPE.body>
@@ -113,52 +119,19 @@ export function ImportToken({ tokens, onBack, onDismiss, handleCurrencySelect }:
) )
})} })}
<Card
style={{ backgroundColor: fromLists ? transparentize(0.8, theme.yellow2) : transparentize(0.8, theme.red1) }}
>
<AutoColumn justify="center" style={{ textAlign: 'center', gap: '16px', marginBottom: '12px' }}>
<AlertTriangle stroke={fromLists ? theme.yellow2 : theme.red1} size={32} />
<TYPE.body fontWeight={600} fontSize={20} color={fromLists ? theme.yellow2 : theme.red1}>
Trade at your own risk!
</TYPE.body>
</AutoColumn>
<AutoColumn style={{ textAlign: 'center', gap: '16px', marginBottom: '12px' }}>
<TYPE.body fontWeight={400} color={fromLists ? theme.yellow2 : theme.red1}>
Anyone can create a token, including creating fake versions of existing tokens that claim to represent
projects.
</TYPE.body>
<TYPE.body fontWeight={600} color={fromLists ? theme.yellow2 : theme.red1}>
If you purchase this token, you may not be able to sell it back.
</TYPE.body>
</AutoColumn>
<AutoRow justify="center" style={{ cursor: 'pointer' }} onClick={() => setConfirmed(!confirmed)}>
<Checkbox
className=".understand-checkbox"
name="confirmed"
type="checkbox"
checked={confirmed}
onChange={() => setConfirmed(!confirmed)}
/>
<TYPE.body ml="10px" fontSize="16px" color={fromLists ? theme.yellow2 : theme.red1} fontWeight={500}>
I understand
</TYPE.body>
</AutoRow>
</Card>
<ButtonPrimary <ButtonPrimary
disabled={!confirmed}
altDisabledStyle={true} altDisabledStyle={true}
borderRadius="20px" borderRadius="20px"
padding="10px 1rem" padding="10px 1rem"
onClick={() => { onClick={() => {
tokens.map(token => addToken(token)) tokens.map((token) => addToken(token))
handleCurrencySelect && handleCurrencySelect(tokens[0]) handleCurrencySelect && handleCurrencySelect(tokens[0])
}} }}
className=".token-dismiss-button" className=".token-dismiss-button"
> >
Import Import
</ButtonPrimary> </ButtonPrimary>
</PaddedColumn> </AutoColumn>
</Wrapper> </Wrapper>
) )
} }

View File

@@ -5,7 +5,7 @@ import { ArrowLeft } from 'react-feather'
import { Text } from 'rebass' import { Text } from 'rebass'
import { CloseIcon } from 'theme' import { CloseIcon } from 'theme'
import styled from 'styled-components' import styled from 'styled-components'
import { Token } from '@uniswap/sdk' import { Token } from '@uniswap/sdk-core'
import { ManageLists } from './ManageLists' import { ManageLists } from './ManageLists'
import ManageTokens from './ManageTokens' import ManageTokens from './ManageTokens'
import { TokenList } from '@uniswap/token-lists' import { TokenList } from '@uniswap/token-lists'
@@ -46,7 +46,7 @@ export default function Manage({
setModalView, setModalView,
setImportList, setImportList,
setImportToken, setImportToken,
setListUrl setListUrl,
}: { }: {
onDismiss: () => void onDismiss: () => void
setModalView: (view: CurrencyModalView) => void setModalView: (view: CurrencyModalView) => void

View File

@@ -42,8 +42,8 @@ const UnpaddedLinkStyledButton = styled(LinkStyledButton)`
const PopoverContainer = styled.div<{ show: boolean }>` const PopoverContainer = styled.div<{ show: boolean }>`
z-index: 100; z-index: 100;
visibility: ${props => (props.show ? 'visible' : 'hidden')}; visibility: ${(props) => (props.show ? 'visible' : 'hidden')};
opacity: ${props => (props.show ? 1 : 0)}; opacity: ${(props) => (props.show ? 1 : 0)};
transition: visibility 150ms linear, opacity 150ms linear; transition: visibility 150ms linear, opacity 150ms linear;
background: ${({ theme }) => theme.bg2}; background: ${({ theme }) => theme.bg2};
border: 1px solid ${({ theme }) => theme.bg3}; border: 1px solid ${({ theme }) => theme.bg3};
@@ -93,7 +93,7 @@ function listUrlRowHTMLId(listUrl: string) {
} }
const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) { const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
const listsByUrl = useSelector<AppState, AppState['lists']['byUrl']>(state => state.lists.byUrl) const listsByUrl = useSelector<AppState, AppState['lists']['byUrl']>((state) => state.lists.byUrl)
const dispatch = useDispatch<AppDispatch>() const dispatch = useDispatch<AppDispatch>()
const { current: list, pendingUpdate: pending } = listsByUrl[listUrl] const { current: list, pendingUpdate: pending } = listsByUrl[listUrl]
@@ -109,7 +109,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
const { styles, attributes } = usePopper(referenceElement, popperElement, { const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: 'auto', placement: 'auto',
strategy: 'fixed', strategy: 'fixed',
modifiers: [{ name: 'offset', options: { offset: [8, 8] } }] modifiers: [{ name: 'offset', options: { offset: [8, 8] } }],
}) })
useOnClickOutside(node, open ? toggle : undefined) useOnClickOutside(node, open ? toggle : undefined)
@@ -119,7 +119,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
ReactGA.event({ ReactGA.event({
category: 'Lists', category: 'Lists',
action: 'Update List from List Select', action: 'Update List from List Select',
label: listUrl label: listUrl,
}) })
dispatch(acceptListUpdate(listUrl)) dispatch(acceptListUpdate(listUrl))
}, [dispatch, listUrl, pending]) }, [dispatch, listUrl, pending])
@@ -128,13 +128,13 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
ReactGA.event({ ReactGA.event({
category: 'Lists', category: 'Lists',
action: 'Start Remove List', action: 'Start Remove List',
label: listUrl label: listUrl,
}) })
if (window.prompt(`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) { if (window.prompt(`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) {
ReactGA.event({ ReactGA.event({
category: 'Lists', category: 'Lists',
action: 'Confirm Remove List', action: 'Confirm Remove List',
label: listUrl label: listUrl,
}) })
dispatch(removeList(listUrl)) dispatch(removeList(listUrl))
} }
@@ -144,7 +144,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
ReactGA.event({ ReactGA.event({
category: 'Lists', category: 'Lists',
action: 'Enable List', action: 'Enable List',
label: listUrl label: listUrl,
}) })
dispatch(enableList(listUrl)) dispatch(enableList(listUrl))
}, [dispatch, listUrl]) }, [dispatch, listUrl])
@@ -153,7 +153,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
ReactGA.event({ ReactGA.event({
category: 'Lists', category: 'Lists',
action: 'Disable List', action: 'Disable List',
label: listUrl label: listUrl,
}) })
dispatch(disableList(listUrl)) dispatch(disableList(listUrl))
}, [dispatch, listUrl]) }, [dispatch, listUrl])
@@ -216,7 +216,7 @@ const ListContainer = styled.div`
export function ManageLists({ export function ManageLists({
setModalView, setModalView,
setImportList, setImportList,
setListUrl setListUrl,
}: { }: {
setModalView: (view: CurrencyModalView) => void setModalView: (view: CurrencyModalView) => void
setImportList: (list: TokenList) => void setImportList: (list: TokenList) => void
@@ -237,7 +237,7 @@ export function ManageLists({
} }
}, [activeCopy, activeListUrls]) }, [activeCopy, activeListUrls])
const handleInput = useCallback(e => { const handleInput = useCallback((e) => {
setListUrlInput(e.target.value) setListUrlInput(e.target.value)
}, []) }, [])
@@ -250,7 +250,7 @@ export function ManageLists({
const sortedLists = useMemo(() => { const sortedLists = useMemo(() => {
const listUrls = Object.keys(lists) const listUrls = Object.keys(lists)
return listUrls return listUrls
.filter(listUrl => { .filter((listUrl) => {
// only show loaded lists, hide unsupported lists // only show loaded lists, hide unsupported lists
return Boolean(lists[listUrl].current) && !Boolean(UNSUPPORTED_LIST_URLS.includes(listUrl)) return Boolean(lists[listUrl].current) && !Boolean(UNSUPPORTED_LIST_URLS.includes(listUrl))
}) })
@@ -286,7 +286,7 @@ export function ManageLists({
useEffect(() => { useEffect(() => {
async function fetchTempList() { async function fetchTempList() {
fetchList(listUrlInput, false) fetchList(listUrlInput, false)
.then(list => setTempList(list)) .then((list) => setTempList(list))
.catch(() => setAddError('Error importing list')) .catch(() => setAddError('Error importing list'))
} }
// if valid url, fetch details for card // if valid url, fetch details for card
@@ -367,7 +367,7 @@ export function ManageLists({
<Separator /> <Separator />
<ListContainer> <ListContainer>
<AutoColumn gap="md"> <AutoColumn gap="md">
{sortedLists.map(listUrl => ( {sortedLists.map((listUrl) => (
<ListRow key={listUrl} listUrl={listUrl} /> <ListRow key={listUrl} listUrl={listUrl} />
))} ))}
</AutoColumn> </AutoColumn>

View File

@@ -6,7 +6,7 @@ import { TYPE, ExternalLinkIcon, TrashIcon, ButtonText, ExternalLink } from 'the
import { useToken } from 'hooks/Tokens' import { useToken } from 'hooks/Tokens'
import styled from 'styled-components' import styled from 'styled-components'
import { useUserAddedTokens, useRemoveUserAddedToken } from 'state/user/hooks' import { useUserAddedTokens, useRemoveUserAddedToken } from 'state/user/hooks'
import { Token } from '@uniswap/sdk' import { Token } from '@uniswap/sdk-core'
import CurrencyLogo from 'components/CurrencyLogo' import CurrencyLogo from 'components/CurrencyLogo'
import { getEtherscanLink, isAddress } from 'utils' import { getEtherscanLink, isAddress } from 'utils'
import { useActiveWeb3React } from 'hooks' import { useActiveWeb3React } from 'hooks'
@@ -37,7 +37,7 @@ const Footer = styled.div`
export default function ManageTokens({ export default function ManageTokens({
setModalView, setModalView,
setImportToken setImportToken,
}: { }: {
setModalView: (view: CurrencyModalView) => void setModalView: (view: CurrencyModalView) => void
setImportToken: (token: Token) => void setImportToken: (token: Token) => void
@@ -49,7 +49,7 @@ export default function ManageTokens({
// manage focus on modal show // manage focus on modal show
const inputRef = useRef<HTMLInputElement>() const inputRef = useRef<HTMLInputElement>()
const handleInput = useCallback(event => { const handleInput = useCallback((event) => {
const input = event.target.value const input = event.target.value
const checksummedInput = isAddress(input) const checksummedInput = isAddress(input)
setSearchQuery(checksummedInput || input) setSearchQuery(checksummedInput || input)
@@ -65,7 +65,7 @@ export default function ManageTokens({
const handleRemoveAll = useCallback(() => { const handleRemoveAll = useCallback(() => {
if (chainId && userAddedTokens) { if (chainId && userAddedTokens) {
userAddedTokens.map(token => { userAddedTokens.map((token) => {
return removeToken(chainId, token.address) return removeToken(chainId, token.address)
}) })
} }
@@ -74,7 +74,7 @@ export default function ManageTokens({
const tokenList = useMemo(() => { const tokenList = useMemo(() => {
return ( return (
chainId && chainId &&
userAddedTokens.map(token => ( userAddedTokens.map((token) => (
<RowBetween key={token.address} width="100%"> <RowBetween key={token.address} width="100%">
<RowFixed> <RowFixed>
<CurrencyLogo currency={token} size={'20px'} /> <CurrencyLogo currency={token} size={'20px'} />

View File

@@ -19,7 +19,7 @@ export const FilterWrapper = styled(RowFixed)`
export default function SortButton({ export default function SortButton({
toggleSortOrder, toggleSortOrder,
ascending ascending,
}: { }: {
toggleSortOrder: () => void toggleSortOrder: () => void
ascending: boolean ascending: boolean

View File

@@ -1,20 +1,21 @@
import { TokenInfo } from '@uniswap/token-lists'
import { useMemo } from 'react' import { useMemo } from 'react'
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
import { Token } from '@uniswap/sdk' import { Token } from '@uniswap/sdk-core'
export function filterTokens(tokens: Token[], search: string): Token[] { export function filterTokens<T extends Token | TokenInfo>(tokens: T[], search: string): T[] {
if (search.length === 0) return tokens if (search.length === 0) return tokens
const searchingAddress = isAddress(search) const searchingAddress = isAddress(search)
if (searchingAddress) { if (searchingAddress) {
return tokens.filter(token => token.address === searchingAddress) return tokens.filter((token) => token.address === searchingAddress)
} }
const lowerSearchParts = search const lowerSearchParts = search
.toLowerCase() .toLowerCase()
.split(/\s+/) .split(/\s+/)
.filter(s => s.length > 0) .filter((s) => s.length > 0)
if (lowerSearchParts.length === 0) { if (lowerSearchParts.length === 0) {
return tokens return tokens
@@ -24,12 +25,12 @@ export function filterTokens(tokens: Token[], search: string): Token[] {
const sParts = s const sParts = s
.toLowerCase() .toLowerCase()
.split(/\s+/) .split(/\s+/)
.filter(s => s.length > 0) .filter((s) => s.length > 0)
return lowerSearchParts.every(p => p.length === 0 || sParts.some(sp => sp.startsWith(p) || sp.endsWith(p))) return lowerSearchParts.every((p) => p.length === 0 || sParts.some((sp) => sp.startsWith(p) || sp.endsWith(p)))
} }
return tokens.filter(token => { return tokens.filter((token) => {
const { symbol, name } = token const { symbol, name } = token
return (symbol && matchesSearch(symbol)) || (name && matchesSearch(name)) return (symbol && matchesSearch(symbol)) || (name && matchesSearch(name))
}) })
@@ -44,7 +45,7 @@ export function useSortedTokensByQuery(tokens: Token[] | undefined, searchQuery:
const symbolMatch = searchQuery const symbolMatch = searchQuery
.toLowerCase() .toLowerCase()
.split(/\s+/) .split(/\s+/)
.filter(s => s.length > 0) .filter((s) => s.length > 0)
if (symbolMatch.length > 1) { if (symbolMatch.length > 1) {
return tokens return tokens
@@ -55,7 +56,7 @@ export function useSortedTokensByQuery(tokens: Token[] | undefined, searchQuery:
const rest: Token[] = [] const rest: Token[] = []
// sort tokens by exact match -> subtring on symbol match -> rest // sort tokens by exact match -> subtring on symbol match -> rest
tokens.map(token => { tokens.map((token) => {
if (token.symbol?.toLowerCase() === symbolMatch[0]) { if (token.symbol?.toLowerCase() === symbolMatch[0]) {
return exactMatches.push(token) return exactMatches.push(token)
} else if (token.symbol?.toLowerCase().startsWith(searchQuery.toLowerCase().trim())) { } else if (token.symbol?.toLowerCase().startsWith(searchQuery.toLowerCase().trim())) {

View File

@@ -1,9 +1,9 @@
import { Token, TokenAmount } from '@uniswap/sdk' import { Token, CurrencyAmount, Currency } from '@uniswap/sdk-core'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useAllTokenBalances } from '../../state/wallet/hooks' import { useAllTokenBalances } from '../../state/wallet/hooks'
// compare two token amounts with highest one coming first // compare two token amounts with highest one coming first
function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount) { function balanceComparator(balanceA?: CurrencyAmount<Currency>, balanceB?: CurrencyAmount<Currency>) {
if (balanceA && balanceB) { if (balanceA && balanceB) {
return balanceA.greaterThan(balanceB) ? -1 : balanceA.equalTo(balanceB) ? 0 : 1 return balanceA.greaterThan(balanceB) ? -1 : balanceA.equalTo(balanceB) ? 0 : 1
} else if (balanceA && balanceA.greaterThan('0')) { } else if (balanceA && balanceA.greaterThan('0')) {
@@ -15,7 +15,7 @@ function balanceComparator(balanceA?: TokenAmount, balanceB?: TokenAmount) {
} }
function getTokenComparator(balances: { function getTokenComparator(balances: {
[tokenAddress: string]: TokenAmount | undefined [tokenAddress: string]: CurrencyAmount<Currency> | undefined
}): (tokenA: Token, tokenB: Token) => number { }): (tokenA: Token, tokenB: Token) => number {
return function sortTokens(tokenA: Token, tokenB: Token): number { return function sortTokens(tokenA: Token, tokenB: Token): number {
// -1 = a is first // -1 = a is first

View File

@@ -21,8 +21,8 @@ export const StyledMenu = styled.div`
export const PopoverContainer = styled.div<{ show: boolean }>` export const PopoverContainer = styled.div<{ show: boolean }>`
z-index: 100; z-index: 100;
visibility: ${props => (props.show ? 'visible' : 'hidden')}; visibility: ${(props) => (props.show ? 'visible' : 'hidden')};
opacity: ${props => (props.show ? 1 : 0)}; opacity: ${(props) => (props.show ? 1 : 0)};
transition: visibility 150ms linear, opacity 150ms linear; transition: visibility 150ms linear, opacity 150ms linear;
background: ${({ theme }) => theme.bg2}; background: ${({ theme }) => theme.bg2};
border: 1px solid ${({ theme }) => theme.bg3}; border: 1px solid ${({ theme }) => theme.bg3};

Some files were not shown because too many files have changed in this diff Show More