Compare commits

...

210 Commits

Author SHA1 Message Date
Ian Lapham
311bdc19b3 add UniH to unsupported list (#2097) 2021-07-23 13:46:05 -04:00
Moody Salem
a6342d40f1 feat: support connecting to a gnosis safe (#2096)
* feat: support connecting to a gnosis safe

* add the manifest.json attributes required by gnosis safe

* remove background fill

* copy

* only try injected provider if not active after trying safe
2021-07-23 12:04:18 -05:00
Moody Salem
3686506f2a perf: remove extra spread operators from the combine maps function 2021-07-23 09:27:23 -05:00
Moody Salem
634d010d92 perf: list to token map function was doing too many reduces (#2095) 2021-07-23 09:16:56 -05:00
Moody Salem
979f29ad62 chore: copy edit on CONTRIBUTING.md 2021-07-22 16:14:55 -05:00
Justin Domingue
4d3ed5d6ba feat: routing api integration (#2080)
* initial routing api integration

* add routing api slice

* display route in dialog

* addressed pr feedback

* switch to `get`

* renamed useRouterTradeExactIn to useRouter

* moving few files to later iteration

* removed unnecessary `as`

* switch to polling

* add todo for blocknumber freshness

* remove account-slippage-deadline
2021-07-21 15:08:35 -07:00
Moody Salem
ea79fbc2e0 chore: add blurb about release process (#2061)
* chore: add blurb about release process

* additional standard

* how to collect feedback
2021-07-20 16:10:58 -05:00
Jordan Frankfurt
4d19122bd2 set 5 min deadline on swaps and hide UI on L2 (#2081) 2021-07-20 12:41:49 -04:00
Moody Salem
13d7d2c992 fix(swap): double max gas for quoting a v3 swap for large trades 2021-07-19 19:00:52 -05:00
Brendan Weinstein
02cf33e115 Update WalletLink and @web3-react/walletlink-connector (#2079) 2021-07-19 14:52:11 -07:00
Justin Domingue
1f62cdf7a2 fix: invert selected range on RateToggle instead of clearing inputs (#2078) 2021-07-19 11:22:46 -07:00
Crowdin Bot
701e8fe116 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-19 13:09:18 +00:00
Crowdin Bot
3207e6026e chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-19 10:05:27 +00:00
Justin Domingue
b0d93dbc0e increase zoom out range (#2076) 2021-07-16 19:14:39 -07:00
Justin Domingue
975a5e3434 remove unused dependencies (#2074) 2021-07-16 15:03:19 -07:00
Ian Lapham
07f52f02ff feat: only block on app url, mobile UI tweaks (#2073)
* start iframe ui updates

* replace hook with constant

* add origin check for blocked lists

* remove origin check for local unsupported list

* remove redundant retun

* remove iframe check

* undo local change
2021-07-16 15:28:19 -04:00
Crowdin Bot
5f112611c8 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-16 18:22:45 +00:00
Justin Domingue
1d82a4c71e fix: set language using url param in language dropdown (#2063)
* set language using url param in language dropdown

* refactor common code into a hook

* add hook

* address pr feedback
2021-07-16 10:56:33 -07:00
Justin Domingue
6004c4be3e fix: change panning cursor to col-resize (#2071)
* change panning cursor to col-resize

* revert to grabby, and support grabbing
2021-07-16 10:49:27 -07:00
Justin Domingue
a00432c1c3 fix: remove chart clamping and show indicator when handle is off screen (#2064)
* initial off screen indicator

* adjust offscreen indicator

* add off screen handle indicator

* hide reset until we get a better behavior

* add svg.tsx
2021-07-15 16:31:06 -07:00
Crowdin Bot
a184afa41e chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-15 22:05:15 +00:00
Crowdin Bot
49fd256e79 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-15 17:09:03 +00:00
Crowdin Bot
c006edf696 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-15 16:05:00 +00:00
Moody Salem
b16dd2a930 chore: add one more unit test for ALL_SUPPORTED_CHAIN_IDS 2021-07-15 10:04:43 -05:00
Moody Salem
08a8f669e8 fix: all supported chain ids should not include the string keys of the enum 2021-07-15 09:59:26 -05:00
Justin Domingue
95d91c8528 fix: add language selector in top-level menu (#2057)
* initial language selector

* set > icon opacity for consistency

* always reset menu to main on close

* always reset menu on menu open
2021-07-15 07:46:05 -07:00
Justin Domingue
03902fe5c1 remove duplicate set price range title when there is liquidity (#2058) 2021-07-14 17:43:45 -07:00
Ian Lapham
631052e6a5 fix: update balance check in derived swap state (#2054)
* update balance check in derived swap state

* replace balance check with correct version of trade
2021-07-14 19:23:46 -04:00
Moody Salem
c63bb84f09 fix(block number): link to the correct page in etherscan for optimism block number links 2021-07-14 11:56:37 -05:00
Moody Salem
f0d10ba0df fix: add DAI and WBTC and USDT to routing for Optimism
fixes https://github.com/Uniswap/uniswap-interface/issues/2016
2021-07-14 11:50:04 -05:00
Moody Salem
88600d3a78 refactor: clean up some constants 2021-07-14 11:20:37 -05:00
Crowdin Bot
a1060d5f75 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-14 12:05:09 +00:00
Crowdin Bot
83762b3c6a chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-14 07:04:47 +00:00
Crowdin Bot
7784f647e1 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-14 02:12:32 +00:00
Moody Salem
65ba56c5f0 fix(arbitrum): use infura endpoints 2021-07-13 20:44:37 -05:00
Crowdin Bot
963514cd51 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-14 01:25:14 +00:00
Crowdin Bot
6d0c8037a8 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-13 22:04:48 +00:00
Justin Domingue
d5ef7f9b24 fix: revert remove x-axis clamping behavior in liquidity chart (#2045)
* Revert "fix: remove x-axis clamping behavior in liquidity chart (#2042)"

This reverts commit f86db00464.

* removed extra curly braces around errorMessage
2021-07-13 14:33:13 -07:00
Justin Domingue
f86db00464 fix: remove x-axis clamping behavior in liquidity chart (#2042)
* remove x-axis clamping behavior

* remove extra curly braces around errorMessage
2021-07-13 14:17:52 -07:00
Justin Domingue
8884020fab chore: support stubbing subgraph in integration tests (#2039)
* upgrade to 7.0

* first iteration of stubbing subgraph in integration tests
2021-07-13 12:54:49 -07:00
Ian Lapham
db145658db update links (#2040) 2021-07-13 15:44:10 -04:00
Ian Lapham
55bbaffe15 fix: support for isolated pool creation (#2029)
* revert to remove ui polish changes

* left align blurb and fix bp issue

* fix translation wrapper

* remove preview screen for create, hide submitted txn UI for create, copy updates

* small fixes

* copy updates for create blurb

* add set price range title

* add translation

* fix some translations

Co-authored-by: Justin Domingue <judo@uniswap.org>
2021-07-13 15:18:09 -04:00
Callil Capuozzo
e1c3ad8f54 fix(L2): Design tweaks for L2 (Optimism) (#2038)
Style tweaks to header layout, network dropdown and pool pages

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
2021-07-13 15:08:23 -04:00
Ian Lapham
730af2eed4 fix: dismiss txn confirmation modal if on L2 (#2022)
* dismiss txn confirmation modal if on l2

* line up far right margin;

* remove submitted view on txns if on L2

* remove uneeded chainid

* wrap dismissal in useEffect

* update variable names
2021-07-13 14:37:35 -04:00
Jordan Frankfurt
c625d15feb fix(L2): add downtime warning to add liquidity and adjust styles of network notification (#2034)
* add liquidity warning and network notification

* remove unneeded width

* use externalLink in LinkOutToBridge component

Co-authored-by: Moody Salem <moody.salem@gmail.com>
2021-07-13 12:30:24 -05:00
Justin Domingue
f9de85251c fix overflow on mobile (#2037) 2021-07-13 10:11:50 -07:00
Moody Salem
a186833b7a fix(L2): enable optimism token list when switched to optimism (#2036)
* add title text and opacity variability to token lists

* add optimism token list

* show tokens from active lists

* sort up token lists
with tokens on the current chain

* fix up some type issues

prune out chainId changes

* clean up leftover any

* refactor token count mechanism

* handle plurals in title text string

* new combineMaps implementation

* remove custom plural

* address a couple nits

* show the number of tokens on current chain

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
Co-authored-by: Justin Domingue <judo@uniswap.org>
2021-07-13 11:51:59 -05:00
Luke Donato
aa06db7684 add optional chaining for stakingInfo (#1950) 2021-07-13 11:06:29 -05:00
Moody Salem
f5b601ee99 fix(L2): use 10 bips slippage for swaps by default on L2 (#2035)
* use 10bip slippage on L2

* update deps

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
2021-07-13 10:41:46 -05:00
Justin Domingue
bf30013b6c fix: add liquidity ux polish (#2024)
* zoom out on intiial

* adjust initial zoom ranges

* remove unneeded reactga event

* adjust full range warning copy

* update zoom

* adjust zoom ranges and label around current price
2021-07-13 08:23:24 -07:00
Justin Domingue
2bc2a2c76e compare with undefined rather than falsy (#2027) 2021-07-12 16:13:45 -07:00
Justin Domingue
28bf95123e load pool data for each tier to determine state (#2026) 2021-07-12 15:56:55 -07:00
Justin Domingue
b1d88317b9 fix: properly set upper/lower base/quote on manual inversion (#2023) 2021-07-12 14:58:44 -07:00
Moody Salem
f332315de9 fix(routing): updates routing constants for layer 2 networks to use ETH (#2018) 2021-07-12 15:30:28 -05:00
Ian Lapham
2fbb86ac44 fix: update CMC priority within lists (#2019) 2021-07-12 16:07:08 -04:00
Justin Domingue
8d567e4d4a fix: add liquidity flow polish (#2017)
* addressed feedback

* set initial, min and max zoom levels

* better handle 0

* avoid formatting range selector

* polish `not created` state

* remove unused import
2021-07-12 10:19:12 -07:00
Moody Salem
77fbccd3f1 fix: remove dollar sign from translation of eth amount
fixes https://github.com/Uniswap/uniswap-interface/issues/1986
2021-07-12 11:09:45 -05:00
Moody Salem
41330ab080 fix: price tokens on optimism mainnet using DAI 2021-07-12 11:03:10 -05:00
Jordan Frankfurt
4ba9d98e67 fix(L2): polish (#1988)
* polish

move getINfoLinkByChainId to chain constants

pr review - translations

optimism token list, typo, text color for L1 switch

use <Trans /> instead of t

undo unintentional tokenlist order change

use {'Optimism'} instead of {name}

switch deadline implementation

remove unused TYPE import

switch to generalized CHAIN_INFO instead of L2_INFO so we don't have to check in components

add target chain id param to optimism bridge

fix a minor breakpoint issue

reduce sigfigs for header balance

update network card dropdown text for optimism

remove list code

refactor SupportedChainId

* SupportedL1ChainId | SupportedL2ChainId -> SupportedChainId
2021-07-12 11:44:05 -04:00
Justin Domingue
52790ac92e support optimism and arbitrum subgraphs (#2001) 2021-07-12 08:26:11 -07:00
Crowdin Bot
380ca11dfd chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-10 23:05:01 +00:00
Crowdin Bot
e4261cc01b chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-10 22:05:10 +00:00
Crowdin Bot
99148dfd3f chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-09 18:05:05 +00:00
Moody Salem
8bf9a9ce8d chore(token lists): bump the token list spec version 2021-07-09 12:35:14 -05:00
Crowdin Bot
b6c0c1e607 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-09 16:05:04 +00:00
Crowdin Bot
0739613abe chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-09 12:05:06 +00:00
Crowdin Bot
3401dfdebe chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-09 06:05:09 +00:00
Justin Domingue
ab9c27edad fix: make liquidity chart and input step counter responsive (#2005)
* make chart responsive

* make input step counter text responsive

* clean up chart
2021-07-08 15:52:31 -07:00
Moody Salem
f2dfc57002 fix: retry calls that return header not found errors (#2004) 2021-07-08 17:12:55 -05:00
Justin Domingue
318252b6a9 fix: min/max price inverted in positions page (#2002)
* fix param ordering in inverter hook

* use object param
2021-07-08 17:06:24 -04:00
Noah Zinsmeister
440c50dd3a update multicall address 2021-07-08 15:49:06 -04:00
Crowdin Bot
eacf4e6049 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-08 19:04:32 +00:00
Justin Domingue
ffbd2d107a feat(pool): add liquidity distribution range slider and update add liquidity layout (#1829)
* first iteration of useTicks and useActiveLiquidity

* feat(pools): add liquidity depth chart (#1835)

* cleanup

* use area chart instead of bar chart

* check for undefined rather than falsy

* feat: range buttons based on fee amount (#1870)

* range buttons based on fee amount

* hardcode percentages as ticks

* increase blocksperfect

* feat: optimize add liquidity charts (#1880)

* ignore syncing state

* remove surrounding ticks

* avoid processing price1 as it is unused

* cleanup

* feat: add zoom buttons to liquidity depth chart (#1882)

* ignore syncing state

* remove surrounding ticks

* avoid processing price1 as it is unused

* cleanup

* first pass at +/- zoom buttons

* remove console.log, cleanup

* use real price for price line

* updated brush handles to latest spec

* added % labels to handles

* round tick to nearest usable tick

* first pass at brushable area chart with d3

* first pass at brushable area chart with d3

* rework

* address PR comments

* add brush handles

* address PR comments

* further improvements

* feat(pools): improve full range support + capital efficiency warning (#1903)

* handle min and max prices in add liquidity

* cleaned up

* use flag to denote full range

* reset inputs on fee select

* fixed merge conflict

* handle full range in positions preview

* fixed invalid range when tokens are reversed

* use formatTickData

* updated layout

* cleaned up layout

* fixed address

* avoid re-rendering deposit amounts

* added zoom behavior and more styling

* renamed chart

* renamed main chart file;

* add brush tooltips

* remove chart title

* added accents to brush handles

* more work

* moved to file

* modularize chart components

* fix maximum depth

* added brush labels

* cleanup

* cleanup

* set up zoom

* added new components

* improved brush and zoom integration

* cleaned up clip path

* fixed clip paths

* integrated with the graph changes

* adjust fee selector

* fix data error

* add bar chart

* polish

* merged

* clean up error

* cleaned up after merge

* visual improvements

* moved +/- buttons to the right/left

* removed margin bottom

* removed unsused

* fix brush labels % change

* use d3.axisBottom

* updated labels

* improve brush range

* fix one brush change only

* adjust zoom and clippath

* use bars

* use area

* adjust axis bottom to mocks;

* improved bars

* show bar colors

* better handle full range

* adjust colors for light mode

* updated to mocks

* adjusted handles for visibility

* switch to area

* add react ga events

* adjusted to mocks

* memo brush domain to avoid re-renders

* fix inputstepcounter color

* adjust handles

* rely on the graph sorting tickidx

* use curvestepafter

* updated polish

* merged main

* add clamping and other fixes

* highlight selected area using a mask

* use price instead of % for labels

* delete unused

* refine ux

* relayout

* improve hooks

* adjust layout for mobile

* fixed card color

* adjust padding

* preent tick overflow

* flip handles sooner

* delete bars.tsx
2021-07-08 11:30:43 -07:00
Justin Domingue
2db29f205b fix: simplify liqudity depth hooks and improve chart primitives (#1999)
* updated hooks and chart primitives

* add lodash.inrange to package.json
2021-07-08 11:12:36 -07:00
Crowdin Bot
3ae9e2af2a chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-08 17:12:35 +00:00
Noah Zinsmeister
5ff2cff841 feat: Add Optimism (#1923)
* preliminary support

* fix multicall abi

add explorer support

* Fix code style issues with ESLint

* fix gas estimation

* fix todo tests

* fix gas estimation comments

* anonymize links to kovan etherscan and poll more frequently on optimistic kovan

* separate pool creation

* remove supported chain id

* remove the blocktag

* do not use the blocktag on optimism only

* give more gas to tokenURI for optimism

* update sdk

* temp fix to the block tag thing (do not update the block number from the fetch block number)

* remove unused import

* gasRequired -> multi-network

* bump quoter gas limit to 6m on optimism

* move the gas required parameter by chain id one level up

* missed a hook

* retry fetching receipts for optimism as many times as arbitrum

* fix duplicate enum, add optimism as well to retry options config

* fix: state not getting updated after a transaction confirmation

* bump sdk version

* update for mainnet optimism (#1998)

* fix for calculateGasMargin on optimism

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
Co-authored-by: Moody Salem <moody.salem@gmail.com>
2021-07-08 12:46:21 -04:00
Crowdin Bot
0c0968864b chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-08 15:04:48 +00:00
Crowdin Bot
9230473c36 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-08 14:05:18 +00:00
ChrisFox.eth
b791afac59 Updating two public-facing URLs to docs (#1992) 2021-07-08 08:22:31 -05:00
Crowdin Bot
2899c8af45 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-08 06:04:59 +00:00
Moody Salem
5f13f7824a chore: update the contribution guidelines (#1987)
* chore: update the contribution guidelines

* move

* note about non-deterministic steps

* little bit of separation

* language nits, add accessibility

* add reasoning
2021-07-07 19:48:03 -05:00
Crowdin Bot
6690bc513d chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-08 00:06:45 +00:00
Justin Domingue
aea3c1f6e6 feat(pools): add liquidity chart range input primitives (#1990)
* first iteration of liquidity chart primitives

* add tickprocessed type

* clean up
2021-07-07 16:39:44 -07:00
Justin Domingue
e0c625671b improve liquidity depth hooks and data fetching (#1989) 2021-07-07 14:37:29 -07:00
Crowdin Bot
aa3c867222 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-07 16:06:58 +00:00
Justin Domingue
2182e676d4 fix: enforce styled-components/macro usage and upgrade react-scripts (#1981)
* upgrade react-scripts and enforce macro rule

* Fix code style issues with ESLint

Co-authored-by: Lint Action <lint-action@samuelmeuli.com>
2021-07-07 08:45:47 -07:00
Justin Domingue
f0fc11a3b5 stabilize fee distribution in hook (#1979) 2021-07-07 08:27:18 -07:00
Crowdin Bot
53d93afd78 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-07 15:05:04 +00:00
Jordan Frankfurt
b612b15e1e fix(L2): routing fixes (#1980)
* Revert "fix last commit"

This reverts commit 9f5764aab3.

* Revert "fix l2 routing bug"

This reverts commit f6dea47907.

* drops routing changes for L2

* pool v2 switch networks message
2021-07-07 10:59:31 -04:00
Noah Zinsmeister
9f5764aab3 fix last commit 2021-07-07 09:44:28 -04:00
Noah Zinsmeister
f6dea47907 fix l2 routing bug 2021-07-07 09:38:29 -04:00
Crowdin Bot
8e9981e186 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-07 08:05:21 +00:00
Crowdin Bot
1509c430fd chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-07 07:04:46 +00:00
Crowdin Bot
93073321db chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-07 06:05:06 +00:00
Crowdin Bot
618760ff1b chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-07 03:04:52 +00:00
Jordan Frankfurt
2d20fc939d feat(L2): network alert cards (optimism and arbitrum) (#1957)
* design review for arbitrum UI

* add optimism L2 UI

* better gradients, remove v2 links/routes, some minor padding and margin fixes
2021-07-06 22:38:47 -04:00
Crowdin Bot
8c1e9b394b chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-06 22:04:39 +00:00
Moody Salem
9b9f431e6f chore: update yarn.lock 2021-07-06 16:28:55 -05:00
Moody Salem
cfe590d810 refactor(devx): show a warning when a call fails due to insufficient gas allowed 2021-07-06 16:28:01 -05:00
Noah Zinsmeister
2ee9b16c49 remove a lot of dead code (#1970) 2021-07-06 17:08:40 -04:00
Moody Salem
c52cd2c38e fix(position image): increase the gas required constraint for the token URI 2021-07-06 16:08:20 -05:00
Noah Zinsmeister
43cefbdc6a no more borderRadius errors! (#1968) 2021-07-06 17:05:19 -04:00
Moody Salem
ae86fefe8b fix: do not reverse the proposals list in place 2021-07-06 10:17:08 -05:00
Crowdin Bot
8901605c32 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-05 12:05:15 +00:00
Justin Domingue
568829d3b5 fix: invalidate subgraph cache with provideTags (#1962)
* invalidate queries using tags

* enhance generated api with provide tags

* clean up
2021-07-03 12:56:38 -07:00
Justin Domingue
b0f4b4c735 remove loading indicator from fee tier selection (#1954) 2021-07-02 11:00:48 -07:00
Moody Salem
a73d3af4b2 fix: use an arbitrum multicall that returns a better block number 2021-07-02 12:56:12 -05:00
Moody Salem
2c7b431e60 fix: use a multicall that allows limiting gas of the individual calls (#1953)
* fix: use a multicall that allows limiting gas of the individual calls

* use the latest multicall addresses
2021-07-02 12:25:37 -05:00
Ian Lapham
0805b2d9b8 update defaults (#1955) 2021-07-02 13:24:04 -04:00
Moody Salem
6c8fc5a7f6 fix: reduce call retries by specifying a block tag in multicall 2021-07-02 09:25:11 -05:00
Crowdin Bot
702258574d chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-02 06:09:45 +00:00
Crowdin Bot
17bc229a36 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-01 23:07:38 +00:00
Justin Domingue
458a7d90a0 various edits 2021-07-01 15:13:36 -07:00
Crowdin Bot
72877fb0b6 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-01 20:09:52 +00:00
Moody Salem
27f756107e refactor: logs hook (#1941)
* feat(logs): add an infrastructure for fetching logs in a declarative way

* use the logs hook in the vote page, first pass

* fix comment

* bit of cleanup

* unused imports

* improve loading indicator on vote page

* some testnet behavior improvements

* fix loader state

* loading state nit

* show correct indexes

* remove the unnecessary retry code

* first pass at the slice

* no throws

* loading indicator should go away if not connected

* use the logs slice for the logs hook state

* style changes per cal's request
2021-07-01 14:44:02 -05:00
Justin Domingue
abfd87c517 feat(pools): fetch pool tick data from uniswap v3 subgraph (#1932)
* add hooks to fecth ticks data and active liquidity

* cleanup

* add polling interval

* moved ms.macro types to dev deps

* generate graphql schema on build

* added @types/ms.macro

* use clone deep
2021-07-01 11:08:53 -07:00
Crowdin Bot
b3d772bdb5 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-01 18:04:48 +00:00
Justin Domingue
d9c82ebf49 feat: add fee tier distribution badge (#1862)
* integrate with The Graph and auto-select fee tier

* restored

* addressed some design feedback

* add pulsing animation on feeAmount change

* simplify fee tier title

* adjust button radios

* addressed some design feedback

* log ReactGA events

* ignore data while fetching

* update to use new generated queries

* remove deleted file

* add usefeetierdistribution hook

* invalidate cache and standardize the outlined card

* added react ga

* fix show options logic

* address design feedback

* show % select in minified view

* updated merge error
2021-07-01 10:56:27 -07:00
Justin Domingue
5298a5ce29 feat: handle chain id in subgraph api (#1938)
* build dynamic subgraph url based on chain id

* reset api state (query cache) on chain id change

* removed dependency on rtk-query/graphql

* add error message
2021-07-01 09:50:03 -07:00
Moody Salem
4d3073d2d9 fix(position page): link to the token page of the explorer instead of the address page from the position page 2021-07-01 10:55:28 -05:00
Moody Salem
c732f407c6 fix: error messages from walletconnect provider (#1943) 2021-07-01 09:22:28 -05:00
Crowdin Bot
361e0ca8d7 chore(i18n): synchronize translations from crowdin [skip ci] 2021-07-01 10:04:56 +00:00
Justin Domingue
cc0a757ddd fix: run graphql:codegen on yarn build (#1933)
* run graphql codegen on build

* yarn.lock

* remove ms.macro
2021-06-29 17:22:41 -07:00
Justin Domingue
31632d3c5b ignore state/data/generated.ts 2021-06-29 16:27:12 -07:00
Justin Domingue
9cfbec4b8b generated graphql schema and en-US.po on start (#1930) 2021-06-29 12:03:47 -07:00
Crowdin Bot
b5ac5d882f chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-29 09:04:42 +00:00
Crowdin Bot
130606cab4 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-29 08:04:53 +00:00
Moody Salem
fa56919a06 fix(theme): always show the gradient background color 2021-06-28 19:47:22 -05:00
Moody Salem
633f65676e fix(theme): fix the missing background radial gradient
fixes https://github.com/Uniswap/uniswap-interface/issues/1925
2021-06-28 19:35:41 -05:00
Moody Salem
9bccb7ae3a fix(multicall): better use the block number returned from calls, and cancel all calls when a new block comes in 2021-06-28 19:26:47 -05:00
Moody Salem
8a2e4123c6 chore(yarn): update yarn.lock 2021-06-28 18:57:10 -05:00
Moody Salem
e93ddbedfa chore(ci): take a screenshot in the cypress test for an example 2021-06-28 18:55:42 -05:00
Justin Domingue
1667b56a04 feat(thegraph): auto-generate graphql types (#1926)
* auto-generate graphql types

* remove introspection

* generated .ts and add to prettierignore

* updated graph url
2021-06-28 16:43:24 -07:00
Justin Domingue
6e995d6c09 feat(pools): integrate with The Graph using RTK Query (#1924)
* integrate with the graph

* Update src/state/data/slice.ts

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2021-06-28 15:12:58 -07:00
Moody Salem
f096112716 fix: improve how we use blocks per fetch in the memoization of the listeners (#1920)
fixes https://github.com/Uniswap/uniswap-interface/issues/1877
2021-06-28 16:39:19 -05:00
Jordan Frankfurt
50c7d36164 replace trustwallet assets repo w/ our fork (#1922) 2021-06-28 16:35:31 -04:00
Moody Salem
8a7f1d51ce chore(ci): record integration tests 2021-06-28 15:12:24 -05:00
Crowdin Bot
8f59f501cc chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-28 16:04:43 +00:00
Jordan Frankfurt
197947835b remove unneeded log (#1917) 2021-06-27 22:23:46 -04:00
Crowdin Bot
94081b29a3 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-27 05:04:39 +00:00
Moody Salem
69183ed8c2 fix: translate claim modal inputs 2021-06-26 23:58:25 -05:00
Crowdin Bot
05e741b6c9 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-27 00:08:49 +00:00
Jordan Frankfurt
dfe550c43b add arbitrum-specific L2 designs (#1912) 2021-06-26 19:54:30 -04:00
Crowdin Bot
65dfb40e44 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-25 15:04:42 +00:00
Moody Salem
25f5ed9983 fix: scrollbars showing up in modal with mouse connected, use auto instead of scroll 2021-06-25 10:00:25 -05:00
Yuta Sugimura
9b1ef415c6 refactor: remove unneeded react import (#1801)
* Add rules

* Remove React import

* remove React import
2021-06-25 09:52:49 -05:00
Crowdin Bot
70f481430a chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-25 07:04:27 +00:00
Justin Domingue
942211eb00 fix: typos in modal css (#1911)
* allow modal scrolling on dekstop

* removed quote and fixed typo
2021-06-24 13:33:11 -07:00
Justin Domingue
6589c41800 allow modal scrolling on dekstop (#1910) 2021-06-24 10:04:07 -07:00
Moody Salem
14bac770b6 fix(arbitrum): explorer links and better anonymization of GA events 2021-06-24 11:38:08 -05:00
Crowdin Bot
c9dbe2f0ac chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-24 10:04:51 +00:00
Crowdin Bot
775cf57c30 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-24 09:04:32 +00:00
Moody Salem
f4935e9264 fix(arbitrum): miscellaneous arbitrum network changes
- adds the arbitrum rinkeby testnet deployment
- compute usdc prices on arbitrum one
2021-06-23 20:58:03 -05:00
Crowdin Bot
6b3914b7b9 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-23 08:05:03 +00:00
Crowdin Bot
41efb6f617 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-23 06:05:18 +00:00
Crowdin Bot
52df5f14cf chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-22 23:04:56 +00:00
Noah Zinsmeister
b48a7c529f fix: proposal sorting 2021-06-22 18:47:12 -04:00
Moody Salem
2eca50be93 chore: remove defunct arbitrum kovan testnet 2021-06-22 17:23:40 -05:00
Moody Salem
be2ce18bfa fix: change polling interval per network (#1898) 2021-06-22 17:21:01 -05:00
yj
fe13b29e5e Update README.md (#1897)
Simple signposting, for issue #1896
2021-06-22 15:03:45 -07:00
Crowdin Bot
02aac08489 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-22 21:05:04 +00:00
Crowdin Bot
8f29980b94 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-22 20:05:13 +00:00
Scott Moses Sunarto
bb542ef0fb feat: Add proposal creation interface (#1799)
* Make address input panel more generalized

* Add TextInput component

* Allow adjusting AppBody maxwidth

* Add create proposal layout page

* Fix style consistency

* create proposal ui goes brrr

* various fixes

* minor tweaks and bug fixes

* merge conflict clean up + bug fix

* i18n

* always show create proposal button

* adjust proposal title top margin and show ButtonError styling

* use button disable instead of button error when form is not filled

* revert mistaken css change

* default params for address input panel

* Refactor & fixes

- Add missing i18n
- Refactor how some styled-components is done
- ButtonError is now disabled when proposal is true in create proposal interface
- Edit copywriting
- Minor styling adjustments

* Fixed create proposal padding on medium screen

* refactor

* refactor pt. 2

* single column styling

* change AddressInputPanel font color

* Design adjustments

* change route for create proposal

* Add autonomous proposal CTA

* cta text edit

* Add link to docs for custom action

* small cleanup

* work with latest governor

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2021-06-22 15:51:32 -04:00
Crowdin Bot
c2fe17615f chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-22 13:12:10 +00:00
Crowdin Bot
a4cab75d09 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-22 08:04:47 +00:00
Crowdin Bot
882457cfef chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-21 16:05:32 +00:00
Justin Domingue
9a1bb5dbfb Add missing semicolon in English source 2021-06-21 08:10:00 -07:00
Crowdin Bot
767cc85b3e chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-21 07:07:47 +00:00
Crowdin Bot
47d726e544 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-21 06:04:54 +00:00
Crowdin Bot
32118520cd chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-20 19:05:02 +00:00
Crowdin Bot
ada8a3af92 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-19 07:06:22 +00:00
Crowdin Bot
63694b32c0 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-19 06:05:14 +00:00
Crowdin Bot
c9ab94d799 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-19 04:05:38 +00:00
Crowdin Bot
a990c4af70 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-18 21:04:57 +00:00
Crowdin Bot
b5cc33c1f5 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-18 20:05:12 +00:00
Justin Domingue
31da6cdb9d fix: new toggle theme (#1782)
* new toggle theme

* moved trans to default

* update radius

* fianlize styles
2021-06-18 12:44:14 -07:00
Crowdin Bot
4079d8a517 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-18 16:05:52 +00:00
Crowdin Bot
e93fbfd31b chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-18 15:05:19 +00:00
Crowdin Bot
7817846368 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-17 23:04:59 +00:00
Crowdin Bot
fc6a69a9af chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-17 09:04:49 +00:00
Crowdin Bot
93a774092f chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-17 08:05:12 +00:00
Crowdin Bot
55563e9bb2 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-17 07:07:40 +00:00
Crowdin Bot
f44aae2f53 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-17 06:04:53 +00:00
Crowdin Bot
7d674c33e7 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-17 04:05:17 +00:00
Noah Zinsmeister
79e6337629 fix governorIndex 2021-06-16 17:31:24 -04:00
Noah Zinsmeister
3c8e8604b8 Merge remote-tracking branch 'refs/remotes/origin/main' 2021-06-16 17:22:46 -04:00
Noah Zinsmeister
b763659788 restrict governance UI to mainnet only
fix governor name bug

revert useContract change

add governorIndex to vote page

only fetch latest useLatestProposalCount

fix useDataFromEventLogs

hardcode proposalIndexes for old governors
2021-06-16 17:22:37 -04:00
Crowdin Bot
1ac2581419 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-16 20:04:59 +00:00
Crowdin Bot
9f556680ed chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-16 19:06:17 +00:00
Justin Domingue
3f242f1b44 fix: memoize extendedEther to unnecessary rerenders (#1869) 2021-06-16 11:07:16 -07:00
Crowdin Bot
126688c753 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-16 17:16:00 +00:00
Ian Lapham
374cc361a6 small bg fixes (#1868) 2021-06-16 13:14:31 -04:00
Crowdin Bot
0198d0baa1 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-16 16:05:39 +00:00
Crowdin Bot
da2d7ba648 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-16 15:05:16 +00:00
Yuta Sugimura
37cf492dd5 fix: Add SwitchLocaleLink to a page where it does not exist (#1867) 2021-06-16 07:24:25 -07:00
Crowdin Bot
9e93f809a0 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-16 14:05:04 +00:00
Crowdin Bot
d9bd392e6d chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-16 12:05:13 +00:00
Crowdin Bot
c48adc519f chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-16 11:05:10 +00:00
Crowdin Bot
9f8983a92e chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-16 10:05:07 +00:00
Crowdin Bot
c9d3d81768 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-16 09:05:06 +00:00
Crowdin Bot
c6f73c35ee chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-16 08:05:22 +00:00
Crowdin Bot
7a2777b814 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-16 03:05:54 +00:00
Jordan Frankfurt
014595cdfb feat: add multi-contract governance support (#1860)
* Revert "feat: quick fix for new governor"
This reverts commit 5dd1249ddd.

* support multiple governance contracts
2021-06-15 22:36:27 -04:00
Jordan Frankfurt
1b27d8dab0 test integration key change (#1864) 2021-06-15 21:04:54 -04:00
Crowdin Bot
c4d12c86a8 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-15 23:04:42 +00:00
Crowdin Bot
8d2feeb89e chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-15 19:05:52 +00:00
Crowdin Bot
576b399768 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-15 18:06:23 +00:00
Crowdin Bot
eb8ce85872 chore(i18n): synchronize translations from crowdin [skip ci] 2021-06-15 15:04:56 +00:00
Justin Domingue
1644e4f9c6 fix(a11y): color contrast changes (#1803)
* fix: a11y changes

* more a11y fixes

* updated theme colors

* addressed rev4 comments

* address rev5 comments

* addressed rev6
2021-06-15 07:55:38 -07:00
Yuta Sugimura
574fab54d1 fix: props for ResponsiveButtonPrimary (#1839)
* fix: props for ResponsiveButtonPrimary

* fix ResponsiveButtonPrimary style
2021-06-15 07:55:11 -07:00
290 changed files with 23386 additions and 9689 deletions

View File

@@ -27,6 +27,19 @@
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/explicit-module-boundary-types": "off"
"@typescript-eslint/explicit-module-boundary-types": "off",
"react/react-in-jsx-scope": "off",
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "styled-components",
"message": "Please import from styled-components/macro."
}
],
"patterns": ["!styled-components/macro"]
}
]
}
}

View File

@@ -46,5 +46,6 @@ jobs:
- run: yarn integration-test
env:
CYPRESS_INTEGRATION_TEST_PRIVATE_KEY: ${{ secrets.CYPRESS_INTEGRATION_TEST_PRIVATE_KEY }}
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}

3
.gitignore vendored
View File

@@ -7,6 +7,7 @@
/src/locales/**/*.ts
/src/locales/**/*.json
/src/locales/**/en-US.po
/src/state/data/generated.ts
# dependencies
/node_modules
@@ -39,4 +40,4 @@ package-lock.json
cypress/videos
cypress/screenshots
cypress/fixtures/example.json
cypress/fixtures/example.json

1
.prettierignore Normal file
View File

@@ -0,0 +1 @@
/src/state/data/generated.ts

View File

@@ -1,46 +1,75 @@
# Development
## Install Dependencies
```bash
yarn install
```
## Generate locale files
```
yarn i18n:extract
```
## Run the interface
```bash
yarn start
```
# Contributing
Thank you for your interest in contributing to the Uniswap interface! 🦄
# Development
## Running the interface locally
1. `yarn install`
1. `yarn start`
## Creating a production build
1. `yarn install`
1. `yarn build`
## Engineering standards
Code merged into the `main` branch of this repository should adhere to high standards of correctness and maintainability.
Use your best judgment when applying these standards. If code is in the critical path, will be frequently visited, or
makes large architectural changes, consider following all the standards.
- Have at least one engineer approve of large code refactorings
- At least manually test small code changes, prefer automated tests
- Thoroughly unit test when code is not obviously correct
- If something breaks, add automated tests so it doesn't break again
- Add integration tests for new pages or flows
- Verify that all CI checks pass before merging
- Have at least one product manager or designer approve of significant UX changes
## Guidelines
The following points should help guide your development:
- Security: the interface is safe to use
- Avoid adding unnecessary dependencies due to [supply chain risk](https://github.com/LavaMoat/lavamoat#further-reading-on-software-supplychain-security)
- Reproducibility: anyone can build the interface
- Avoid adding steps to the development/build processes
- The build must be deterministic, i.e. a particular commit hash always produces the same build
- Decentralization: anyone can run the interface
- An Ethereum node should be the only critical dependency
- All other external dependencies should only enhance the UX ([graceful degradation](https://developer.mozilla.org/en-US/docs/Glossary/Graceful_degradation))
- Accessibility: anyone can use the interface
- The interface should be responsive, small and run well on low performance devices (majority of swaps on mobile!)
## Release process
Releases are cut automatically from the `main` branch Monday-Thursday in the morning according to the [release workflow](./.github/workflows/release.yaml).
Fix pull requests should be merged whenever ready and tested.
If a fix is urgently needed in production, releases can be manually triggered on [GitHub](https://github.com/Uniswap/uniswap-interface/actions/workflows/release.yaml)
after the fix is merged into `main`.
Features should not be merged into `main` until they are ready for users.
When building larger features or collaborating with other developers, create a new branch from `main` to track its development.
Use the automatic Vercel preview for sharing the feature to collect feedback.
When the feature is ready for review, create a new pull request from the feature branch into `main` and request reviews from
the appropriate UX reviewers (PMs or designers).
## Finding a first issue
Start with issues with the label
[`good first issue`](https://github.com/Uniswap/uniswap-interface/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
## Pull requests
**Please open all pull requests against the `main` branch.**
CI checks will run against all PRs.
# Translations
Help Uniswap reach a global audience!
Uniswap uses [Crowdin](https://crowdin.com/project/uniswap-interface) for managing translations.
[This workflow](./.github/workflows/crowdin.yaml) uploads new strings for translation to the Crowdin project whenever code using the [lingui translation macros](https://lingui.js.org/ref/macro.html) is merged into `main`.
Uniswap uses [Crowdin](https://crowdin.com/project/uniswap-interface)
for managing translations. Whenever a new string is added to the project,
it gets uploaded to Crowdin for translation by [this workflow](./.github/workflows/crowdin.yaml).
Every hour, translations are synced from Crowdin in [this other workflow](./.github/workflows/crowdin-sync.yaml).
Every hour, translations are synced back down from Crowdin to the repository in [this other workflow](./.github/workflows/crowdin-sync.yaml).
We sync to the repository on a schedule, rather than download translations at build time, so that builds are always reproducible.
You can contribute by joining Crowdin to proofread existing translations [here](https://crowdin.com/project/uniswap-interface/invite?d=93i5n413q403t4g473p443o4c3t2g3s21343u2c3n403l4b3v2735353i4g4k4l4g453j4g4o4j4e4k4b323l4a3h463s4g453q443m4e3t2b303s2a35353l403o443v293e303k4g4n4r4g483i4g4r4j4e4o473i5n4a3t463t4o4)

View File

@@ -28,7 +28,7 @@ or visit [app.uniswap.org](https://app.uniswap.org).
## Contributions
See [CONTRIBUTING](./CONTRIBUTING.md).
For steps on local deployment, development, and code contribution, please see [CONTRIBUTING](./CONTRIBUTING.md).
## Accessing Uniswap V2

11
codegen.yml Normal file
View File

@@ -0,0 +1,11 @@
overrideExisting: true
schema: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'
documents: 'src/**/!(*.d).{ts,tsx}'
generates:
./src/state/data/generated.ts:
plugins:
- typescript
- typescript-operations
- typescript-rtk-query:
importBaseApiFrom: './slice'
exportHooks: true

View File

@@ -1,7 +1,7 @@
{
"projectId": "yp82ef",
"baseUrl": "http://localhost:3000",
"pluginsFile": false,
"fixturesFolder": false,
"supportFile": "cypress/support/index.js",
"video": false,
"defaultCommandTimeout": 10000

View File

@@ -0,0 +1,25 @@
{
"_meta": {
"block": {
"number": 99999999
}
},
"asToken0": [
{
"feeTier": "500",
"totalValueLockedToken0": "0",
"totalValueLockedToken1": "1"
},
{
"feeTier": "3000",
"totalValueLockedToken0": "0",
"totalValueLockedToken1": "7"
},
{
"feeTier": "10000",
"totalValueLockedToken0": "0",
"totalValueLockedToken1": "2"
}
],
"asToken1": []
}

View File

@@ -1,4 +1,13 @@
import { CyHttpMessages } from 'cypress/types/net-stubbing'
import { aliasQuery, hasQuery } from '../utils/graphql-test-utils'
describe('Add Liquidity', () => {
beforeEach(() => {
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req) => {
aliasQuery(req, 'feeTierDistribution')
})
})
it('loads the two correct tokens', () => {
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab/500')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
@@ -23,4 +32,32 @@ describe('Add Liquidity', () => {
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
})
it('loads fee tier distribution', () => {
cy.fixture('feeTierDistribution.json').then((feeTierDistribution) => {
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req: CyHttpMessages.IncomingHttpRequest) => {
if (hasQuery(req, 'feeTierDistribution')) {
req.alias = 'feeTierDistributionQuery'
req.reply({
body: {
data: {
...feeTierDistribution,
},
},
headers: {
'access-control-allow-origin': '*',
},
})
}
})
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.wait('@feeTierDistributionQuery')
cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.3% fee tier')
cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '70%')
})
})
})

View File

@@ -4,6 +4,7 @@ describe('Landing Page', () => {
beforeEach(() => cy.visit('/'))
it('loads swap page', () => {
cy.get('#swap-page')
cy.screenshot()
})
it('redirects to url /swap', () => {

View File

@@ -8,7 +8,9 @@ import { JsonRpcProvider } from '@ethersproject/providers'
import { Wallet } from '@ethersproject/wallet'
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
const TEST_PRIVATE_KEY = Cypress.env('INTEGRATION_TEST_PRIVATE_KEY')
// todo: figure out how env vars actually work in CI
// const TEST_PRIVATE_KEY = Cypress.env('INTEGRATION_TEST_PRIVATE_KEY')
const TEST_PRIVATE_KEY = '0xe580410d7c37d26c6ad1a837bbae46bc27f9066a466fb3a66e770523b4666d19'
// address of the above key
export const TEST_ADDRESS_NEVER_USE = new Wallet(TEST_PRIVATE_KEY).address

View File

@@ -0,0 +1,12 @@
// Utility to match GraphQL mutation based on the query name
export const hasQuery = (req: any, queryName: string) => {
const { body } = req
return body.hasOwnProperty('query') && body.query.includes(queryName)
}
// Alias query if queryName matches
export const aliasQuery = (req: any, queryName: string) => {
if (hasQuery(req, queryName)) {
req.alias = `${queryName}Query`
}
}

View File

@@ -5,6 +5,11 @@
"private": true,
"devDependencies": {
"@ethersproject/experimental": "^5.2.0",
"@gnosis.pm/safe-apps-web3-react": "^0.6.0",
"@graphql-codegen/cli": "1.21.5",
"@graphql-codegen/typescript": "1.22.3",
"@graphql-codegen/typescript-operations": "^1.18.2",
"@graphql-codegen/typescript-rtk-query": "^1.1.1",
"@lingui/cli": "^3.9.0",
"@lingui/loader": "^3.9.0",
"@lingui/macro": "^3.9.0",
@@ -14,14 +19,16 @@
"@reach/dialog": "^0.10.3",
"@reach/portal": "^0.10.3",
"@react-hook/window-scroll": "^1.3.0",
"@reduxjs/toolkit": "^1.3.5",
"@reduxjs/toolkit": "^1.6.1",
"@typechain/ethers-v5": "^7.0.0",
"@types/d3": "^6.7.1",
"@types/jest": "^25.2.1",
"@types/lingui__core": "^2.7.1",
"@types/lingui__macro": "^2.7.4",
"@types/lingui__react": "^2.8.3",
"@types/lodash.flatmap": "^4.5.6",
"@types/luxon": "^1.24.4",
"@types/ms.macro": "^2.0.0",
"@types/multicodec": "^1.0.0",
"@types/node": "^13.13.5",
"@types/qs": "^6.9.2",
@@ -42,34 +49,37 @@
"@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "1.0.1",
"@uniswap/token-lists": "^1.0.0-beta.19",
"@uniswap/token-lists": "^1.0.0-beta.25",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@uniswap/v2-sdk": "^3.0.0-alpha.2",
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "1.0.0",
"@uniswap/v3-sdk": "^3.0.0-alpha.9",
"@uniswap/v3-periphery": "^1.1.1",
"@uniswap/v3-sdk": "^3.2.1",
"@web3-react/core": "^6.0.9",
"@web3-react/fortmatic-connector": "^6.0.9",
"@web3-react/injected-connector": "^6.0.7",
"@web3-react/portis-connector": "^6.0.9",
"@web3-react/walletconnect-connector": "^6.2.0",
"@web3-react/walletlink-connector": "^6.2.0",
"@web3-react/walletlink-connector": "^6.2.3",
"ajv": "^6.12.3",
"cids": "^1.0.0",
"copy-to-clipboard": "^3.2.0",
"cross-env": "^7.0.2",
"cypress": "^4.11.0",
"cypress": "^7.7.0",
"d3": "^7.0.0",
"eslint": "^7.11.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-react": "^7.19.0",
"eslint-plugin-react-hooks": "^4.0.0",
"ethers": "^5.2.0",
"graphql": "^15.5.0",
"graphql-request": "^3.4.0",
"inter-ui": "^3.13.1",
"lightweight-charts": "^3.3.0",
"lodash.flatmap": "^4.5.0",
"luxon": "^1.25.0",
"ms.macro": "^2.0.0",
"multicodec": "^3.0.1",
"multihashes": "^4.0.2",
"node-vibrant": "^3.1.5",
@@ -82,6 +92,7 @@
"react-dom": "^17.0.1",
"react-feather": "^2.0.8",
"react-ga": "^2.5.7",
"react-is": "^17.0.2",
"react-markdown": "^4.3.1",
"react-popper": "^2.2.3",
"react-redux": "^7.2.2",
@@ -92,10 +103,11 @@
"react-virtualized-auto-sizer": "^1.0.2",
"react-window": "^1.8.5",
"rebass": "^4.0.7",
"redux-devtools-extension": "^2.13.9",
"redux-localstorage-simple": "^2.3.1",
"serve": "^11.3.2",
"start-server-and-test": "^1.11.0",
"styled-components": "^4.2.0",
"styled-components": "^5.3.0",
"styled-system": "^5.1.5",
"typechain": "^5.0.0",
"typescript": "^4.2.3",
@@ -109,19 +121,21 @@
"workbox-strategies": "^6.1.0"
},
"resolutions": {
"@walletconnect/web3-provider": "1.4.2-rc.2"
"@walletconnect/web3-provider": "1.5.0-rc.5"
},
"scripts": {
"compile-contract-types": "yarn compile-external-abi-types && yarn compile-v3-contract-types",
"compile-external-abi-types": "typechain --target ethers-v5 --out-dir src/abis/types './src/abis/**/*.json'",
"compile-v3-contract-types": "typechain --target ethers-v5 --out-dir src/types/v3 './node_modules/@uniswap/?(v3-core|v3-periphery)/artifacts/contracts/**/*.json'",
"build": "yarn compile-contract-types && yarn i18n:extract && yarn i18n:compile && react-scripts build",
"build": "yarn compile-contract-types && yarn graphql:generate && yarn i18n:extract && yarn i18n:compile && react-scripts build",
"i18n:extract": "lingui extract --locale en-US",
"i18n:compile": "lingui compile",
"integration-test": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run'",
"integration-test": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run --record'",
"graphql:generate": "graphql-codegen --config codegen.yml",
"postinstall": "yarn compile-contract-types",
"start": "yarn compile-contract-types && react-scripts start",
"test": "react-scripts test --env=jsdom"
"test": "react-scripts test --env=jsdom",
"prestart": "yarn graphql:generate && touch src/locales/en-US.po"
},
"eslintConfig": {
"extends": "react-app",

View File

@@ -0,0 +1,11 @@
<svg width="257" height="256" viewBox="0 0 257 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M96.4347 60.2768C94.3434 59.9524 94.2552 59.9143 95.2394 59.7631C97.1254 59.473 101.579 59.8683 104.648 60.5981C111.812 62.3011 118.331 66.6637 125.29 74.4117L127.139 76.4701L129.784 76.0449C140.926 74.2544 152.26 75.6774 161.741 80.057C164.349 81.2619 168.461 83.6603 168.975 84.2766C169.138 84.473 169.439 85.7374 169.642 87.0866C170.347 91.7544 169.994 95.3323 168.567 98.0046C167.79 99.4588 167.746 99.9197 168.268 101.164C168.686 102.157 169.848 102.892 170.999 102.891C173.355 102.888 175.891 99.0791 177.066 93.78L177.532 91.6751L178.457 92.7224C183.528 98.4681 187.512 106.304 188.196 111.881L188.374 113.336L187.522 112.014C186.054 109.739 184.58 108.19 182.693 106.941C179.29 104.69 175.693 103.923 166.164 103.421C157.559 102.968 152.689 102.233 147.859 100.658C139.643 97.9792 135.501 94.4114 125.74 81.6059C121.405 75.918 118.726 72.7711 116.06 70.2368C110.004 64.4784 104.053 61.4584 96.4347 60.2768Z" fill="#FF007A"/>
<path d="M170.916 72.9763C171.132 69.1649 171.649 66.651 172.688 64.3552C173.099 63.4465 173.485 62.7027 173.544 62.7027C173.604 62.7027 173.425 63.3735 173.147 64.1931C172.391 66.4212 172.267 69.4687 172.788 73.0144C173.448 77.5132 173.824 78.1623 178.579 83.022C180.809 85.3014 183.403 88.1762 184.344 89.4105L186.054 91.6547L184.344 90.0508C182.253 88.0895 177.444 84.2644 176.381 83.7176C175.669 83.3509 175.563 83.3573 175.124 83.7946C174.719 84.1975 174.634 84.803 174.577 87.6654C174.49 92.1267 173.882 94.9901 172.414 97.8533C171.621 99.4019 171.495 99.0714 172.214 97.3235C172.75 96.0184 172.805 95.4446 172.801 91.1259C172.792 82.4485 171.762 80.3624 165.721 76.7887C164.19 75.8834 161.668 74.5778 160.117 73.8872C158.565 73.1965 157.333 72.595 157.378 72.5501C157.549 72.3798 163.441 74.0995 165.812 75.0117C169.339 76.3686 169.922 76.5444 170.35 76.3807C170.637 76.271 170.776 75.4347 170.916 72.9763Z" fill="#FF007A"/>
<path d="M100.497 87.8209C96.2514 81.9758 93.6246 73.0138 94.1933 66.3144L94.3691 64.2413L95.3355 64.4176C97.1504 64.7486 100.28 65.9133 101.745 66.8033C105.766 69.2453 107.506 72.4605 109.277 80.7164C109.796 83.1346 110.477 85.8712 110.79 86.7976C111.294 88.2889 113.199 91.7721 114.748 94.0343C115.864 95.6636 115.123 96.4356 112.657 96.213C108.89 95.873 103.788 92.3519 100.497 87.8209Z" fill="#FF007A"/>
<path d="M165.766 131.323C145.925 123.335 138.937 116.4 138.937 104.7C138.937 102.979 138.996 101.57 139.068 101.57C139.14 101.57 139.908 102.138 140.774 102.833C144.797 106.06 149.303 107.438 161.776 109.258C169.115 110.328 173.245 111.193 177.056 112.457C189.166 116.473 196.658 124.624 198.445 135.725C198.964 138.951 198.66 145 197.818 148.188C197.153 150.706 195.125 155.245 194.588 155.419C194.439 155.468 194.292 154.896 194.254 154.118C194.05 149.95 191.943 145.891 188.406 142.851C184.383 139.395 178.979 136.643 165.766 131.323Z" fill="#FF007A"/>
<path d="M151.837 134.642C151.588 133.163 151.157 131.273 150.879 130.444L150.372 128.935L151.313 129.991C152.614 131.451 153.642 133.32 154.514 135.81C155.179 137.71 155.254 138.275 155.249 141.362C155.244 144.393 155.161 145.029 154.546 146.739C153.578 149.436 152.376 151.348 150.359 153.4C146.735 157.089 142.075 159.131 135.351 159.978C134.182 160.125 130.775 160.373 127.78 160.529C120.232 160.922 115.264 161.733 110.801 163.3C110.159 163.525 109.586 163.662 109.528 163.604C109.347 163.425 112.386 161.613 114.897 160.404C118.436 158.699 121.96 157.768 129.854 156.454C133.754 155.804 137.781 155.016 138.804 154.702C148.461 151.741 153.426 144.1 151.837 134.642Z" fill="#FF007A"/>
<path d="M160.932 150.795C158.296 145.128 157.691 139.657 159.135 134.554C159.289 134.009 159.538 133.562 159.687 133.562C159.837 133.562 160.459 133.899 161.07 134.31C162.284 135.127 164.721 136.505 171.212 140.044C179.311 144.46 183.929 147.879 187.07 151.786C189.82 155.208 191.522 159.104 192.341 163.856C192.805 166.548 192.533 173.024 191.843 175.735C189.665 184.281 184.604 190.993 177.385 194.911C176.327 195.484 175.377 195.955 175.275 195.958C175.172 195.96 175.557 194.98 176.131 193.78C178.56 188.703 178.836 183.765 177 178.269C175.876 174.904 173.584 170.797 168.956 163.857C163.575 155.788 162.256 153.641 160.932 150.795Z" fill="#FF007A"/>
<path d="M86.4067 181.371C93.7696 175.154 102.931 170.738 111.276 169.382C114.872 168.798 120.864 169.03 124.194 169.882C129.532 171.248 134.307 174.308 136.791 177.954C139.218 181.517 140.259 184.622 141.343 191.53C141.771 194.255 142.236 196.992 142.377 197.611C143.191 201.192 144.775 204.054 146.739 205.491C149.857 207.773 155.227 207.915 160.509 205.855C161.405 205.505 162.184 205.263 162.238 205.318C162.43 205.508 159.77 207.288 157.894 208.226C155.369 209.487 153.361 209.975 150.693 209.975C145.855 209.975 141.839 207.514 138.487 202.495C137.828 201.508 136.346 198.55 135.193 195.922C131.655 187.851 129.907 185.393 125.799 182.702C122.223 180.361 117.612 179.941 114.143 181.642C109.586 183.876 108.315 189.699 111.579 193.39C112.876 194.856 115.295 196.121 117.273 196.367C120.973 196.828 124.153 194.013 124.153 190.277C124.153 187.851 123.221 186.467 120.873 185.407C117.668 183.961 114.222 185.652 114.238 188.663C114.245 189.947 114.805 190.754 116.092 191.336C116.918 191.71 116.937 191.74 116.263 191.6C113.322 190.99 112.633 187.444 114.998 185.09C117.837 182.264 123.709 183.511 125.725 187.368C126.572 188.988 126.67 192.215 125.932 194.164C124.279 198.525 119.46 200.819 114.571 199.571C111.243 198.721 109.887 197.801 105.874 193.667C98.9012 186.484 96.1941 185.092 86.141 183.523L84.2146 183.222L86.4067 181.371Z" fill="#FF007A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.8241 20.4645C57.1114 48.7285 93.0139 92.734 94.7963 95.1977C96.2679 97.232 95.7141 99.0609 93.1929 100.494C91.7909 101.291 88.9084 102.099 87.4652 102.099C85.8329 102.099 83.9939 101.313 82.6548 100.042C81.7084 99.1447 77.8882 93.4402 69.0694 79.7566C62.3216 69.2863 56.6747 60.6007 56.5206 60.4553C56.1644 60.119 56.1705 60.1303 68.3813 81.9787C76.0487 95.6979 78.6371 100.548 78.6371 101.197C78.6371 102.516 78.2771 103.21 76.6495 105.025C73.9363 108.052 72.7235 111.453 71.8479 118.492C70.8664 126.382 68.1064 131.955 60.4577 141.494C55.9803 147.078 55.2477 148.102 54.118 150.352C52.695 153.187 52.3037 154.774 52.1451 158.353C51.9775 162.137 52.3039 164.581 53.46 168.199C54.4721 171.366 55.5285 173.458 58.2292 177.641C60.5599 181.251 61.9019 183.933 61.9019 184.983C61.9019 185.818 62.0613 185.819 65.6729 185.003C74.316 183.052 81.3341 179.619 85.2812 175.412C87.724 172.808 88.2975 171.371 88.3161 167.802C88.3283 165.469 88.2462 164.98 87.6153 163.637C86.5884 161.452 84.7188 159.635 80.5983 156.818C75.1992 153.127 72.8932 150.156 72.2562 146.07C71.7337 142.717 72.3399 140.351 75.3267 134.091C78.4182 127.612 79.1843 124.85 79.7025 118.319C80.0372 114.1 80.5008 112.435 81.7131 111.1C82.9776 109.707 84.1158 109.235 87.245 108.808C92.3466 108.111 95.5951 106.791 98.2652 104.33C100.582 102.196 101.551 100.139 101.7 97.0427L101.813 94.6959L100.518 93.1867C95.8304 87.7209 30.6848 16.168 30.3963 16.168C30.3347 16.168 31.8773 18.1015 33.8241 20.4645ZM64.5333 162.634C65.5932 160.757 65.0301 158.344 63.2572 157.166C61.5821 156.052 58.9799 156.576 58.9799 158.028C58.9799 158.471 59.2248 158.793 59.7768 159.077C60.7063 159.555 60.7738 160.093 60.0425 161.192C59.3019 162.306 59.3616 163.284 60.2111 163.949C61.5802 165.021 63.5183 164.432 64.5333 162.634Z" fill="#FF007A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M105.032 110.039C102.637 110.774 100.309 113.312 99.5884 115.974C99.1487 117.598 99.3982 120.446 100.057 121.325C101.121 122.746 102.149 123.12 104.935 123.101C110.389 123.063 115.131 120.724 115.682 117.799C116.134 115.402 114.051 112.08 111.183 110.622C109.703 109.87 106.555 109.571 105.032 110.039ZM111.408 115.024C112.249 113.829 111.881 112.537 110.451 111.664C107.726 110 103.607 111.377 103.607 113.95C103.607 115.231 105.755 116.629 107.724 116.629C109.035 116.629 110.829 115.847 111.408 115.024Z" fill="#FF007A"/>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -63,7 +63,6 @@
pointer-events: none;
width: 200vw;
height: 200vh;
mix-blend-mode: color;
background: radial-gradient(50% 50% at 50% 50%, #fc077d10 0%, rgba(255, 255, 255, 0) 100%);
transform: translate(-50vw, -100vh);
z-index: -1;

View File

@@ -2,6 +2,7 @@
"background_color": "#fff",
"display": "standalone",
"homepage_url": "https://app.uniswap.org",
"providedBy": { "name": "Uniswap", "url": "https://uniswap.org" },
"icons": [
{
"src": "./images/192x192_App_Icon.png",
@@ -18,6 +19,8 @@
],
"orientation": "portrait",
"name": "Uniswap",
"description": "Swap or provide liquidity on the Uniswap Protocol",
"iconPath": "./images/256x256_App_Icon_Pink.svg",
"short_name": "Uniswap",
"start_url": ".",
"theme_color": "#ff007a"

View File

@@ -1,330 +0,0 @@
[
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "bytes",
"name": "callData",
"type": "bytes"
}
],
"internalType": "struct Multicall2.Call[]",
"name": "calls",
"type": "tuple[]"
}
],
"name": "aggregate",
"outputs": [
{
"internalType": "uint256",
"name": "blockNumber",
"type": "uint256"
},
{
"internalType": "bytes[]",
"name": "returnData",
"type": "bytes[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "bytes",
"name": "callData",
"type": "bytes"
}
],
"internalType": "struct Multicall2.Call[]",
"name": "calls",
"type": "tuple[]"
}
],
"name": "blockAndAggregate",
"outputs": [
{
"internalType": "uint256",
"name": "blockNumber",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "blockHash",
"type": "bytes32"
},
{
"components": [
{
"internalType": "bool",
"name": "success",
"type": "bool"
},
{
"internalType": "bytes",
"name": "returnData",
"type": "bytes"
}
],
"internalType": "struct Multicall2.Result[]",
"name": "returnData",
"type": "tuple[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "blockNumber",
"type": "uint256"
}
],
"name": "getBlockHash",
"outputs": [
{
"internalType": "bytes32",
"name": "blockHash",
"type": "bytes32"
},
{
"components": [
{
"internalType": "bool",
"name": "success",
"type": "bool"
},
{
"internalType": "bytes",
"name": "returnData",
"type": "bytes"
}
],
"internalType": "struct Multicall2.Result[]",
"name": "returnData",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getBlockNumber",
"outputs": [
{
"internalType": "uint256",
"name": "blockNumber",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCurrentBlockCoinbase",
"outputs": [
{
"internalType": "address",
"name": "coinbase",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCurrentBlockDifficulty",
"outputs": [
{
"internalType": "uint256",
"name": "difficulty",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCurrentBlockGasLimit",
"outputs": [
{
"internalType": "uint256",
"name": "gaslimit",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCurrentBlockTimestamp",
"outputs": [
{
"internalType": "uint256",
"name": "timestamp",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "addr",
"type": "address"
}
],
"name": "getEthBalance",
"outputs": [
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getLastBlockHash",
"outputs": [
{
"internalType": "bytes32",
"name": "blockHash",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "requireSuccess",
"type": "bool"
},
{
"components": [
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "bytes",
"name": "callData",
"type": "bytes"
}
],
"internalType": "struct Multicall2.Call[]",
"name": "calls",
"type": "tuple[]"
}
],
"name": "tryAggregate",
"outputs": [
{
"components": [
{
"internalType": "bool",
"name": "success",
"type": "bool"
},
{
"internalType": "bytes",
"name": "returnData",
"type": "bytes"
}
],
"internalType": "struct Multicall2.Result[]",
"name": "returnData",
"type": "tuple[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "requireSuccess",
"type": "bool"
},
{
"components": [
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "bytes",
"name": "callData",
"type": "bytes"
}
],
"internalType": "struct Multicall2.Call[]",
"name": "calls",
"type": "tuple[]"
}
],
"name": "tryBlockAndAggregate",
"outputs": [
{
"internalType": "uint256",
"name": "blockNumber",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "blockHash",
"type": "bytes32"
},
{
"components": [
{
"internalType": "bool",
"name": "success",
"type": "bool"
},
{
"internalType": "bytes",
"name": "returnData",
"type": "bytes"
}
],
"internalType": "struct Multicall2.Result[]",
"name": "returnData",
"type": "tuple[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]

View File

@@ -1,165 +0,0 @@
[
{
"inputs": [
{
"components": [
{ "internalType": "address", "name": "target", "type": "address" },
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
],
"internalType": "struct Multicall2.Call[]",
"name": "calls",
"type": "tuple[]"
}
],
"name": "aggregate",
"outputs": [
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
{ "internalType": "bytes[]", "name": "returnData", "type": "bytes[]" }
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{ "internalType": "address", "name": "target", "type": "address" },
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
],
"internalType": "struct Multicall2.Call[]",
"name": "calls",
"type": "tuple[]"
}
],
"name": "blockAndAggregate",
"outputs": [
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" },
{
"components": [
{ "internalType": "bool", "name": "success", "type": "bool" },
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
],
"internalType": "struct Multicall2.Result[]",
"name": "returnData",
"type": "tuple[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" }],
"name": "getBlockHash",
"outputs": [{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getBlockNumber",
"outputs": [{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCurrentBlockCoinbase",
"outputs": [{ "internalType": "address", "name": "coinbase", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCurrentBlockDifficulty",
"outputs": [{ "internalType": "uint256", "name": "difficulty", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCurrentBlockGasLimit",
"outputs": [{ "internalType": "uint256", "name": "gaslimit", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCurrentBlockTimestamp",
"outputs": [{ "internalType": "uint256", "name": "timestamp", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "addr", "type": "address" }],
"name": "getEthBalance",
"outputs": [{ "internalType": "uint256", "name": "balance", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getLastBlockHash",
"outputs": [{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bool", "name": "requireSuccess", "type": "bool" },
{
"components": [
{ "internalType": "address", "name": "target", "type": "address" },
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
],
"internalType": "struct Multicall2.Call[]",
"name": "calls",
"type": "tuple[]"
}
],
"name": "tryAggregate",
"outputs": [
{
"components": [
{ "internalType": "bool", "name": "success", "type": "bool" },
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
],
"internalType": "struct Multicall2.Result[]",
"name": "returnData",
"type": "tuple[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bool", "name": "requireSuccess", "type": "bool" },
{
"components": [
{ "internalType": "address", "name": "target", "type": "address" },
{ "internalType": "bytes", "name": "callData", "type": "bytes" }
],
"internalType": "struct Multicall2.Call[]",
"name": "calls",
"type": "tuple[]"
}
],
"name": "tryBlockAndAggregate",
"outputs": [
{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" },
{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" },
{
"components": [
{ "internalType": "bool", "name": "success", "type": "bool" },
{ "internalType": "bytes", "name": "returnData", "type": "bytes" }
],
"internalType": "struct Multicall2.Result[]",
"name": "returnData",
"type": "tuple[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]

View File

@@ -1,471 +0,0 @@
[
{
"name": "Transfer",
"inputs": [
{
"type": "address",
"name": "_from",
"indexed": true
},
{
"type": "address",
"name": "_to",
"indexed": true
},
{
"type": "uint256",
"name": "_tokenId",
"indexed": true
}
],
"anonymous": false,
"type": "event"
},
{
"name": "Approval",
"inputs": [
{
"type": "address",
"name": "_owner",
"indexed": true
},
{
"type": "address",
"name": "_approved",
"indexed": true
},
{
"type": "uint256",
"name": "_tokenId",
"indexed": true
}
],
"anonymous": false,
"type": "event"
},
{
"name": "ApprovalForAll",
"inputs": [
{
"type": "address",
"name": "_owner",
"indexed": true
},
{
"type": "address",
"name": "_operator",
"indexed": true
},
{
"type": "bool",
"name": "_approved",
"indexed": false
}
],
"anonymous": false,
"type": "event"
},
{
"outputs": [],
"inputs": [],
"constant": false,
"payable": false,
"type": "constructor"
},
{
"name": "tokenURI",
"outputs": [
{
"type": "string",
"name": "out"
}
],
"inputs": [
{
"type": "uint256",
"name": "_tokenId"
}
],
"constant": true,
"payable": false,
"type": "function",
"gas": "22405"
},
{
"name": "tokenByIndex",
"outputs": [
{
"type": "uint256",
"name": "out"
}
],
"inputs": [
{
"type": "uint256",
"name": "_index"
}
],
"constant": true,
"payable": false,
"type": "function",
"gas": "631"
},
{
"name": "tokenOfOwnerByIndex",
"outputs": [
{
"type": "uint256",
"name": "out"
}
],
"inputs": [
{
"type": "address",
"name": "_owner"
},
{
"type": "uint256",
"name": "_index"
}
],
"constant": true,
"payable": false,
"type": "function",
"gas": "1248"
},
{
"name": "transferFrom",
"outputs": [],
"inputs": [
{
"type": "address",
"name": "_from"
},
{
"type": "address",
"name": "_to"
},
{
"type": "uint256",
"name": "_tokenId"
}
],
"constant": false,
"payable": false,
"type": "function",
"gas": "259486"
},
{
"name": "safeTransferFrom",
"outputs": [],
"inputs": [
{
"type": "address",
"name": "_from"
},
{
"type": "address",
"name": "_to"
},
{
"type": "uint256",
"name": "_tokenId"
}
],
"constant": false,
"payable": false,
"type": "function"
},
{
"name": "safeTransferFrom",
"outputs": [],
"inputs": [
{
"type": "address",
"name": "_from"
},
{
"type": "address",
"name": "_to"
},
{
"type": "uint256",
"name": "_tokenId"
},
{
"type": "bytes",
"name": "_data"
}
],
"constant": false,
"payable": false,
"type": "function"
},
{
"name": "approve",
"outputs": [],
"inputs": [
{
"type": "address",
"name": "_approved"
},
{
"type": "uint256",
"name": "_tokenId"
}
],
"constant": false,
"payable": false,
"type": "function",
"gas": "38422"
},
{
"name": "setApprovalForAll",
"outputs": [],
"inputs": [
{
"type": "address",
"name": "_operator"
},
{
"type": "bool",
"name": "_approved"
}
],
"constant": false,
"payable": false,
"type": "function",
"gas": "38016"
},
{
"name": "mint",
"outputs": [
{
"type": "bool",
"name": "out"
}
],
"inputs": [
{
"type": "address",
"name": "_to"
}
],
"constant": false,
"payable": false,
"type": "function",
"gas": "182636"
},
{
"name": "changeMinter",
"outputs": [],
"inputs": [
{
"type": "address",
"name": "_minter"
}
],
"constant": false,
"payable": false,
"type": "function",
"gas": "35897"
},
{
"name": "changeURI",
"outputs": [],
"inputs": [
{
"type": "address",
"name": "_newURI"
}
],
"constant": false,
"payable": false,
"type": "function",
"gas": "35927"
},
{
"name": "name",
"outputs": [
{
"type": "string",
"name": "out"
}
],
"inputs": [],
"constant": true,
"payable": false,
"type": "function",
"gas": "6612"
},
{
"name": "symbol",
"outputs": [
{
"type": "string",
"name": "out"
}
],
"inputs": [],
"constant": true,
"payable": false,
"type": "function",
"gas": "6642"
},
{
"name": "totalSupply",
"outputs": [
{
"type": "uint256",
"name": "out"
}
],
"inputs": [],
"constant": true,
"payable": false,
"type": "function",
"gas": "873"
},
{
"name": "minter",
"outputs": [
{
"type": "address",
"name": "out"
}
],
"inputs": [],
"constant": true,
"payable": false,
"type": "function",
"gas": "903"
},
{
"name": "socks",
"outputs": [
{
"type": "address",
"name": "out",
"unit": "Socks"
}
],
"inputs": [],
"constant": true,
"payable": false,
"type": "function",
"gas": "933"
},
{
"name": "newURI",
"outputs": [
{
"type": "address",
"name": "out"
}
],
"inputs": [],
"constant": true,
"payable": false,
"type": "function",
"gas": "963"
},
{
"name": "ownerOf",
"outputs": [
{
"type": "address",
"name": "out"
}
],
"inputs": [
{
"type": "uint256",
"name": "arg0"
}
],
"constant": true,
"payable": false,
"type": "function",
"gas": "1126"
},
{
"name": "balanceOf",
"outputs": [
{
"type": "uint256",
"name": "out"
}
],
"inputs": [
{
"type": "address",
"name": "arg0"
}
],
"constant": true,
"payable": false,
"type": "function",
"gas": "1195"
},
{
"name": "getApproved",
"outputs": [
{
"type": "address",
"name": "out"
}
],
"inputs": [
{
"type": "uint256",
"name": "arg0"
}
],
"constant": true,
"payable": false,
"type": "function",
"gas": "1186"
},
{
"name": "isApprovedForAll",
"outputs": [
{
"type": "bool",
"name": "out"
}
],
"inputs": [
{
"type": "address",
"name": "arg0"
},
{
"type": "address",
"name": "arg1"
}
],
"constant": true,
"payable": false,
"type": "function",
"gas": "1415"
},
{
"name": "supportsInterface",
"outputs": [
{
"type": "bool",
"name": "out"
}
],
"inputs": [
{
"type": "bytes32",
"name": "arg0"
}
],
"constant": true,
"payable": false,
"type": "function",
"gas": "1246"
}
]

View File

@@ -0,0 +1,181 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 470.287 514.251" enable-background="new 0 0 470.287 514.251" xml:space="preserve">
<g id="Background">
</g>
<g id="Logos_and_symbols">
<g id="SYMBOL_VER_3">
</g>
<g id="SYMBOL_VER_3_3_">
</g>
<g id="SYMBOL_VER_4">
</g>
<g id="SYMBOL_VER_4_1_">
<g id="SYMBOL_VER_4_3_">
</g>
</g>
<g id="SYMBOL_VER_5_1_">
</g>
<g id="off_2_1_">
</g>
<g id="VER_3_1_">
<g id="SYMBOL_VER_2_1_">
</g>
</g>
<g id="VER_3">
<g id="SYMBOL_VER_2">
</g>
</g>
<g id="off_2">
</g>
<g id="SYMBOL_VER_5">
</g>
<g id="SYMBOL_VER_1">
</g>
<g id="SYMBOL_VER_1_1_">
</g>
<g id="SYMBOL_VER_1-1_3_">
</g>
<g id="SYMBOL_VER_1-1_2_">
</g>
<g id="SYMBOL_VER_1-1">
</g>
<g id="SYMBOL_VER_1-1_1_">
<g id="_x31_-3">
</g>
<g id="Symbol_-_Original_14_">
<path fill="#2D374B" d="M291.134,237.469l35.654-60.5l96.103,149.684l0.046,28.727l-0.313-197.672
c-0.228-4.832-2.794-9.252-6.887-11.859L242.715,46.324c-4.045-1.99-9.18-1.967-13.22,0.063c-0.546,0.272-1.06,0.57-1.548,0.895
l-0.604,0.379L59.399,144.983l-0.651,0.296c-0.838,0.385-1.686,0.875-2.48,1.444c-3.185,2.283-5.299,5.66-5.983,9.448
c-0.103,0.574-0.179,1.158-0.214,1.749l0.264,161.083l89.515-138.745c11.271-18.397,35.825-24.323,58.62-24.001l26.753,0.706
L67.588,409.765l18.582,10.697L245.692,157.22l70.51-0.256L157.091,426.849l66.306,38.138l7.922,4.556
c3.351,1.362,7.302,1.431,10.681,0.21l175.453-101.678l-33.544,19.438L291.134,237.469z M304.736,433.395l-66.969-105.108
l40.881-69.371l87.952,138.628L304.736,433.395z"/>
<polygon fill="#28A0F0" points="237.768,328.286 304.736,433.395 366.601,397.543 278.648,258.915 "/>
<path fill="#28A0F0" d="M422.937,355.379l-0.046-28.727l-96.103-149.684l-35.654,60.5l92.774,150.043l33.544-19.438
c3.29-2.673,5.281-6.594,5.49-10.825L422.937,355.379z"/>
<path fill="#FFFFFF" d="M20.219,382.469l47.369,27.296l157.634-252.801l-26.753-0.706c-22.795-0.322-47.35,5.604-58.62,24.001
L50.334,319.004l-30.115,46.271V382.469z"/>
<polygon fill="#FFFFFF" points="316.202,156.964 245.692,157.22 86.17,420.462 141.928,452.565 157.091,426.849 "/>
<path fill="#96BEDC" d="M452.65,156.601c-0.59-14.746-8.574-28.245-21.08-36.104L256.28,19.692
c-12.371-6.229-27.825-6.237-40.218-0.004c-1.465,0.739-170.465,98.752-170.465,98.752c-2.339,1.122-4.592,2.458-6.711,3.975
c-11.164,8.001-17.969,20.435-18.668,34.095v208.765l30.115-46.271L50.07,157.921c0.035-0.589,0.109-1.169,0.214-1.741
c0.681-3.79,2.797-7.171,5.983-9.456c0.795-0.569,172.682-100.064,173.228-100.337c4.04-2.029,9.175-2.053,13.22-0.063
l173.022,99.523c4.093,2.607,6.659,7.027,6.887,11.859v199.542c-0.209,4.231-1.882,8.152-5.172,10.825l-33.544,19.438
l-17.308,10.031l-61.864,35.852l-62.737,36.357c-3.379,1.221-7.33,1.152-10.681-0.21l-74.228-42.693l-15.163,25.717
l66.706,38.406c2.206,1.255,4.171,2.367,5.784,3.272c2.497,1.4,4.199,2.337,4.8,2.629c4.741,2.303,11.563,3.643,17.71,3.643
c5.636,0,11.132-1.035,16.332-3.072l182.225-105.531c10.459-8.104,16.612-20.325,17.166-33.564V156.601z"/>
</g>
<g id="Symbol_-_Original_13_">
</g>
<g id="Symbol_-_Original_6_">
</g>
<g id="Symbol_-_Original_4_">
</g>
<g id="One_color_version_-_White_3_">
<g id="Symbol_-_Original_15_">
</g>
</g>
<g id="One_color_version_-_White">
<g id="Symbol_-_Original">
</g>
</g>
<g id="Symbol_-_Monochromatic_3_">
<g id="_x33__7_">
</g>
</g>
<g id="Symbol_-_Monochromatic">
<g id="_x33__3_">
</g>
</g>
<g id="_x33__2_">
</g>
<g id="_x33__1_">
</g>
<g id="_x33_">
</g>
<g id="Symbol_-_Original_10_">
</g>
<g id="Symbol_-_Original_1_">
</g>
<g id="Symbol_-_Original_2_">
</g>
<g id="_x34__1_">
</g>
<g id="Symbol_-_Monochromatic_2_">
<g id="_x33__6_">
</g>
</g>
<g id="One_color_version_-_White_2_">
<g id="Symbol_-_Original_11_">
</g>
</g>
<g id="Symbol_-_Original_5_">
<g id="Symbol_-_Original_12_">
</g>
</g>
<g id="One_color_version_-_White_1_">
<g id="Symbol_-_Original_9_">
</g>
</g>
</g>
<g id="SYMBOL_VER_1_2_">
<g id="SYMBOL_VER_2_4_">
</g>
<g id="SYMBOL_VER_2-1-1_1_">
</g>
<g id="SYMBOL_VER_2-2-1_1_">
</g>
<g id="SYMBOL_VER_2-3-1_4_">
</g>
<g id="New_Symbol_1_">
<g id="SYMBOL_VER_2-3-1_3_">
</g>
</g>
<g id="New_Symbol">
<g id="SYMBOL_VER_2-3-1_1_">
</g>
</g>
</g>
<g id="SYMBOL_VER_2_2_">
</g>
<g id="SYMBOL_VER_4_2_">
</g>
<g id="SYMBOL_VER_3_2_">
</g>
<g id="SYMBOL_VER_3_1_">
</g>
<g id="SYMBOL_VER_1-1-1_1_">
</g>
<g id="SYMBOL_VER_1-1-1">
</g>
<g id="SYMBOL_VER_1-1-1_2_2_">
</g>
<g id="SYMBOL_VER_1-1-1_2">
</g>
<g id="SYMBOL_VER_1-1-1_2_1_">
</g>
<g id="Symbol_-_Original_7_">
</g>
<g id="Symbol_-_Original_8_">
</g>
<g id="SYMBOL_VER_2-1-1">
</g>
<g id="SYMBOL_VER_2-2-1">
</g>
<g id="SYMBOL_VER_2-3-1">
</g>
<g id="SYMBOL_VER_5-1_1_">
</g>
<g id="SYMBOL_VER_5-1">
</g>
<g id="SYMBOL_VER_5-2_1_">
</g>
<g id="SYMBOL_VER_5-2">
</g>
<g id="Symbol_-_Monochromatic_1_">
<g id="_x33__4_">
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -0,0 +1,5 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="250" cy="250" r="250" fill="#FF0420"/>
<path d="M177.133 316.446C162.247 316.446 150.051 312.943 140.544 305.938C131.162 298.808 126.471 288.676 126.471 275.541C126.471 272.789 126.784 269.411 127.409 265.408C129.036 256.402 131.35 245.581 134.352 232.947C142.858 198.547 164.812 181.347 200.213 181.347C209.845 181.347 218.476 182.973 226.107 186.225C233.738 189.352 239.742 194.106 244.12 200.486C248.498 206.74 250.688 214.246 250.688 223.002C250.688 225.629 250.375 228.944 249.749 232.947C247.873 244.08 245.621 254.901 242.994 265.408C238.616 282.546 231.048 295.368 220.29 303.874C209.532 312.255 195.147 316.446 177.133 316.446ZM179.76 289.426C186.766 289.426 192.707 287.362 197.586 283.234C202.59 279.106 206.155 272.789 208.281 264.283C211.158 252.524 213.348 242.266 214.849 233.51C215.349 230.883 215.599 228.194 215.599 225.441C215.599 214.058 209.657 208.366 197.774 208.366C190.768 208.366 184.764 210.43 179.76 214.558C174.882 218.687 171.379 225.004 169.253 233.51C167.001 241.891 164.749 252.149 162.498 264.283C161.997 266.784 161.747 269.411 161.747 272.163C161.747 283.672 167.752 289.426 179.76 289.426Z" fill="white"/>
<path d="M259.303 314.57C257.927 314.57 256.863 314.132 256.113 313.256C255.487 312.255 255.3 311.13 255.55 309.879L281.444 187.914C281.694 186.538 282.382 185.412 283.508 184.536C284.634 183.661 285.822 183.223 287.073 183.223H336.985C350.87 183.223 362.003 186.1 370.384 191.854C378.891 197.609 383.144 205.927 383.144 216.81C383.144 219.937 382.769 223.19 382.018 226.567C378.891 240.953 372.574 251.586 363.067 258.466C353.685 265.346 340.8 268.786 324.413 268.786H299.082L290.451 309.879C290.2 311.255 289.512 312.38 288.387 313.256C287.261 314.132 286.072 314.57 284.822 314.57H259.303ZM325.727 242.892C330.98 242.892 335.546 241.453 339.424 238.576C343.427 235.699 346.054 231.571 347.305 226.192C347.68 224.065 347.868 222.189 347.868 220.563C347.868 216.935 346.805 214.183 344.678 212.307C342.551 210.305 338.924 209.305 333.795 209.305H311.278L304.148 242.892H325.727Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,4 +1,3 @@
import React from 'react'
import styled from 'styled-components/macro'
import { CheckCircle, Triangle } from 'react-feather'

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useContext } from 'react'
import styled, { ThemeContext } from 'styled-components'
import { useCallback, useContext } from 'react'
import styled, { ThemeContext } from 'styled-components/macro'
import { SUPPORTED_WALLETS } from '../../constants/wallet'
import { useActiveWeb3React } from '../../hooks/web3'
import { clearAllTransactions } from '../../state/transactions/actions'

View File

@@ -1,5 +1,6 @@
import React, { useContext, useCallback } from 'react'
import styled, { ThemeContext } from 'styled-components'
import { t, Trans } from '@lingui/macro'
import { useContext, useCallback, ReactNode } from 'react'
import styled, { ThemeContext } from 'styled-components/macro'
import useENS from '../../hooks/useENS'
import { useActiveWeb3React } from '../../hooks/web3'
import { ExternalLink, TYPE } from '../../theme'
@@ -40,7 +41,7 @@ const Input = styled.input<{ error?: boolean }>`
width: 0;
background-color: ${({ theme }) => theme.bg1};
transition: color 300ms ${({ error }) => (error ? 'step-end' : 'step-start')};
color: ${({ error, theme }) => (error ? theme.red1 : theme.primary1)};
color: ${({ error, theme }) => (error ? theme.red1 : theme.text1)};
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
@@ -67,10 +68,16 @@ const Input = styled.input<{ error?: boolean }>`
export default function AddressInputPanel({
id,
className = 'recipient-address-input',
label,
placeholder,
value,
onChange,
}: {
id?: string
className?: string
label?: ReactNode
placeholder?: string
// the typed string value
value: string
// triggers whenever the typed value changes
@@ -99,25 +106,25 @@ export default function AddressInputPanel({
<AutoColumn gap="md">
<RowBetween>
<TYPE.black color={theme.text2} fontWeight={500} fontSize={14}>
Recipient
{label ?? <Trans>Recipient</Trans>}
</TYPE.black>
{address && chainId && (
<ExternalLink
href={getExplorerLink(chainId, name ?? address, ExplorerDataType.ADDRESS)}
style={{ fontSize: '14px' }}
>
(View on Explorer)
<Trans>(View on Explorer)</Trans>
</ExternalLink>
)}
</RowBetween>
<Input
className="recipient-address-input"
className={className}
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
placeholder="Wallet Address or ENS name"
placeholder={placeholder ?? t`Wallet Address or ENS name`}
error={error}
pattern="^(0x[a-fA-F0-9]{40})$"
onChange={handleInput}

View File

@@ -1,5 +1,3 @@
import React from 'react'
import Badge, { BadgeVariant } from 'components/Badge'
import styled from 'styled-components/macro'
@@ -26,13 +24,6 @@ const ActiveDot = styled.span`
margin-right: 4px;
`
export const DarkBadge = styled.div`
width: fit-content;
border-radius: 8px;
background-color: ${({ theme }) => theme.bg0};
padding: 4px 6px;
`
export default function RangeBadge({
removed,
inRange,

View File

@@ -1,6 +1,6 @@
import { readableColor } from 'polished'
import { PropsWithChildren } from 'react'
import styled, { DefaultTheme } from 'styled-components'
import styled, { DefaultTheme } from 'styled-components/macro'
import { Color } from 'theme/styled'
export enum BadgeVariant {
@@ -13,7 +13,7 @@ export enum BadgeVariant {
WARNING_OUTLINE = 'WARNING_OUTLINE',
}
export interface BadgeProps {
interface BadgeProps {
variant?: BadgeVariant
}

View File

@@ -1,4 +1,4 @@
import React, { ReactNode, useMemo } from 'react'
import { ReactNode, useMemo } from 'react'
import { useActiveWeb3React } from '../../hooks/web3'
import { Trans } from '@lingui/macro'

View File

@@ -1,4 +1,3 @@
import React from 'react'
import styled from 'styled-components/macro'
import { darken } from 'polished'
@@ -13,16 +12,15 @@ const Base = styled(RebassButton)<
{
padding?: string
width?: string
borderRadius?: string
$borderRadius?: string
altDisabledStyle?: boolean
} & ButtonProps
>`
padding: ${({ padding }) => (padding ? padding : '16px')};
width: ${({ width }) => (width ? width : '100%')};
padding: ${({ padding }) => padding ?? '16px'};
width: ${({ width }) => width ?? '100%'};
font-weight: 500;
text-align: center;
border-radius: 20px;
border-radius: ${({ borderRadius }) => borderRadius && borderRadius};
border-radius: ${({ $borderRadius }) => $borderRadius ?? '20px'};
outline: none;
border: 1px solid transparent;
color: white;
@@ -43,10 +41,6 @@ const Base = styled(RebassButton)<
transition: transform 450ms ease;
transform: perspective(1px) translateZ(0);
&:hover {
transform: scale(0.99);
}
> * {
user-select: none;
}
@@ -72,14 +66,12 @@ export const ButtonPrimary = styled(Base)`
}
&:disabled {
background-color: ${({ theme, altDisabledStyle, disabled }) =>
altDisabledStyle ? (disabled ? theme.primary1 : theme.primary1) : theme.primary1};
color: white;
altDisabledStyle ? (disabled ? theme.primary1 : theme.bg2) : theme.bg2};
color: ${({ theme }) => theme.text2};
cursor: auto;
box-shadow: none;
border: 1px solid transparent;
outline: none;
opacity: 0.4;
opacity: ${({ altDisabledStyle }) => (altDisabledStyle ? '0.5' : '0.4')};
}
`
@@ -116,9 +108,7 @@ export const ButtonGray = styled(Base)`
color: ${({ theme }) => theme.text2};
font-size: 16px;
font-weight: 500;
&:focus {
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg2)};
}
&:hover {
background-color: ${({ theme, disabled }) => !disabled && darken(0.05, theme.bg2)};
}
@@ -155,63 +145,41 @@ export const ButtonSecondary = styled(Base)`
}
`
export const ButtonPink = styled(Base)`
background-color: ${({ theme }) => theme.primary1};
color: white;
export const ButtonOutlined = styled(Base)`
border: 1px solid ${({ theme }) => theme.bg2};
background-color: transparent;
color: ${({ theme }) => theme.text1};
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.primary1)};
background-color: ${({ theme }) => darken(0.05, theme.primary1)};
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
}
&:hover {
background-color: ${({ theme }) => darken(0.05, theme.primary1)};
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.primary1)};
background-color: ${({ theme }) => darken(0.1, theme.primary1)};
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
}
&:disabled {
background-color: ${({ theme }) => theme.primary1};
opacity: 50%;
cursor: auto;
}
`
export const ButtonUNIGradient = styled(ButtonPrimary)`
export const ButtonYellow = styled(Base)`
background-color: ${({ theme }) => theme.yellow3};
color: white;
padding: 4px 8px;
height: 36px;
font-weight: 500;
background-color: ${({ theme }) => theme.bg3};
background: radial-gradient(174.47% 188.91% at 1.84% 0%, #ff007a 0%, #2172e5 100%), #edeef2;
width: fit-content;
position: relative;
cursor: pointer;
border: none;
white-space: no-wrap;
:hover {
opacity: 0.8;
}
:active {
opacity: 0.9;
}
`
export const ButtonOutlined = styled(Base)`
border: 1px solid ${({ theme }) => theme.bg2};
background-color: transparent;
color: ${({ theme }) => theme.text1};
&:focus {
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.yellow3)};
background-color: ${({ theme }) => darken(0.05, theme.yellow3)};
}
&:hover {
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
background-color: ${({ theme }) => darken(0.05, theme.yellow3)};
}
&:active {
box-shadow: 0 0 0 1px ${({ theme }) => theme.bg4};
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.yellow3)};
background-color: ${({ theme }) => darken(0.1, theme.yellow3)};
}
&:disabled {
background-color: ${({ theme }) => theme.yellow3};
opacity: 50%;
cursor: auto;
}
@@ -261,27 +229,6 @@ export const ButtonText = styled(Base)`
}
`
export const ButtonWhite = styled(Base)`
border: 1px solid #edeef2;
background-color: ${({ theme }) => theme.bg1};
color: black;
&:focus {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
box-shadow: 0 0 0 1pt ${darken(0.05, '#edeef2')};
}
&:hover {
box-shadow: 0 0 0 1pt ${darken(0.1, '#edeef2')};
}
&:active {
box-shadow: 0 0 0 1pt ${darken(0.1, '#edeef2')};
}
&:disabled {
opacity: 50%;
cursor: auto;
}
`
const ButtonConfirmedStyle = styled(Base)`
background-color: ${({ theme }) => theme.bg3};
color: ${({ theme }) => theme.text1};
@@ -350,17 +297,6 @@ export function ButtonDropdown({ disabled = false, children, ...rest }: { disabl
)
}
export function ButtonDropdownGrey({ disabled = false, children, ...rest }: { disabled?: boolean } & ButtonProps) {
return (
<ButtonGray {...rest} disabled={disabled} style={{ borderRadius: '20px' }}>
<RowBetween>
<div style={{ display: 'flex', alignItems: 'center' }}>{children}</div>
<ChevronDown size={24} />
</RowBetween>
</ButtonGray>
)
}
export function ButtonDropdownLight({ disabled = false, children, ...rest }: { disabled?: boolean } & ButtonProps) {
return (
<ButtonOutlined {...rest} disabled={disabled}>
@@ -372,14 +308,6 @@ export function ButtonDropdownLight({ disabled = false, children, ...rest }: { d
)
}
export function ButtonRadio({ active, ...rest }: { active?: boolean } & ButtonProps) {
if (!active) {
return <ButtonWhite {...rest} />
} else {
return <ButtonPrimary {...rest} />
}
}
const ActiveOutlined = styled(ButtonOutlined)`
border: 1px solid;
border-color: ${({ theme }) => theme.primary1};
@@ -412,13 +340,13 @@ export function ButtonRadioChecked({ active = false, children, ...rest }: { acti
if (!active) {
return (
<ButtonOutlined borderRadius="12px" padding="12px 8px" {...rest}>
<ButtonOutlined $borderRadius="12px" padding="12px 8px" {...rest}>
{<RowBetween>{children}</RowBetween>}
</ButtonOutlined>
)
} else {
return (
<ActiveOutlined {...rest} padding="12px 8px" borderRadius="12px">
<ActiveOutlined {...rest} padding="12px 8px" $borderRadius="12px">
{
<RowBetween>
{children}

View File

@@ -1,13 +1,11 @@
import styled from 'styled-components/macro'
import { Box } from 'rebass/styled-components'
const Card = styled(Box)<{ width?: string; padding?: string; border?: string; borderRadius?: string }>`
const Card = styled(Box)<{ width?: string; padding?: string; border?: string; $borderRadius?: string }>`
width: ${({ width }) => width ?? '100%'};
border-radius: 16px;
padding: 1rem;
padding: ${({ padding }) => padding};
padding: ${({ padding }) => padding ?? '1rem'};
border-radius: ${({ $borderRadius }) => $borderRadius ?? '16px'};
border: ${({ border }) => border};
border-radius: ${({ borderRadius }) => borderRadius};
`
export default Card
@@ -42,12 +40,6 @@ export const YellowCard = styled(Card)`
font-weight: 500;
`
export const PinkCard = styled(Card)`
background-color: rgba(255, 0, 122, 0.03);
color: ${({ theme }) => theme.primary1};
font-weight: 500;
`
export const BlueCard = styled(Card)`
background-color: ${({ theme }) => theme.primary5};
color: ${({ theme }) => theme.blue2};

View File

@@ -1,4 +1,3 @@
import React from 'react'
import ReactConfetti from 'react-confetti'
import { useWindowSize } from '../../hooks/useWindowSize'

View File

@@ -1,5 +1,5 @@
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import React, { useMemo } from 'react'
import { useMemo } from 'react'
import useTheme from '../../hooks/useTheme'
import { TYPE } from '../../theme'
import { warningSeverity } from '../../utils/prices'
@@ -18,10 +18,10 @@ export function FiatValue({
if (!priceImpact) return undefined
if (priceImpact.lessThan('0')) return theme.green1
const severity = warningSeverity(priceImpact)
if (severity < 1) return theme.text4
if (severity < 1) return theme.text3
if (severity < 3) return theme.yellow1
return theme.red1
}, [priceImpact, theme.green1, theme.red1, theme.text4, theme.yellow1])
}, [priceImpact, theme.green1, theme.red1, theme.text3, theme.yellow1])
return (
<TYPE.body fontSize={14} color={fiatValue ? theme.text2 : theme.text4}>

View File

@@ -1,6 +1,6 @@
import { Pair } from '@uniswap/v2-sdk'
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
import React, { useState, useCallback, ReactNode } from 'react'
import { useState, useCallback, ReactNode } from 'react'
import styled from 'styled-components/macro'
import { darken } from 'polished'
import { useCurrencyBalance } from '../../state/wallet/hooks'
@@ -130,7 +130,7 @@ const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
font-weight: 500;
cursor: pointer;
padding: 0;
color: ${({ theme }) => theme.primary1};
color: ${({ theme }) => theme.primaryText1};
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
margin-left: 0.25rem;
@@ -160,6 +160,8 @@ interface CurrencyInputPanelProps {
priceImpact?: Percent
id: string
showCommonBases?: boolean
showCurrencyAmount?: boolean
disableNonToken?: boolean
renderBalance?: (amount: CurrencyAmount<Currency>) => ReactNode
locked?: boolean
}
@@ -174,6 +176,8 @@ export default function CurrencyInputPanel({
otherCurrency,
id,
showCommonBases,
showCurrencyAmount,
disableNonToken,
renderBalance,
fiatValue,
priceImpact,
@@ -298,6 +302,8 @@ export default function CurrencyInputPanel({
selectedCurrency={currency}
otherSelectedCurrency={otherCurrency}
showCommonBases={showCommonBases}
showCurrencyAmount={showCurrencyAmount}
disableNonToken={disableNonToken}
/>
)}
</InputPanel>

View File

@@ -7,7 +7,7 @@ import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
import Logo from '../Logo'
export const getTokenLogoURL = (address: string) =>
`https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${address}/logo.png`
`https://raw.githubusercontent.com/uniswap/assets/master/blockchains/ethereum/assets/${address}/logo.png`
const StyledEthereumLogo = styled.img<{ size: string }>`
width: ${({ size }) => size};

View File

@@ -1,5 +1,4 @@
import { Currency } from '@uniswap/sdk-core'
import React from 'react'
import styled from 'styled-components/macro'
import CurrencyLogo from '../CurrencyLogo'
@@ -10,7 +9,7 @@ const Wrapper = styled.div<{ margin: boolean; sizeraw: number }>`
margin-left: ${({ sizeraw, margin }) => margin && (sizeraw / 3 + 8).toString() + 'px'};
`
export interface DoubleCurrencyLogoProps {
interface DoubleCurrencyLogoProps {
margin?: boolean
size?: number
currency0?: Currency

View File

@@ -1,81 +1,228 @@
import React from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { FeeAmount } from '@uniswap/v3-sdk'
import { Currency } from '@uniswap/sdk-core'
import { Trans } from '@lingui/macro'
import { AutoColumn } from 'components/Column'
import { DynamicSection } from 'pages/AddLiquidity/styled'
import { TYPE } from 'theme'
import { RowBetween } from 'components/Row'
import { ButtonRadioChecked } from 'components/Button'
import styled from 'styled-components/macro'
import { ButtonGray, ButtonRadioChecked } from 'components/Button'
import styled, { keyframes } from 'styled-components/macro'
import Badge from 'components/Badge'
import Card from 'components/Card'
import usePrevious from 'hooks/usePrevious'
import { useFeeTierDistribution } from 'hooks/useFeeTierDistribution'
import ReactGA from 'react-ga'
import { Box } from 'rebass'
const pulse = (color: string) => keyframes`
0% {
box-shadow: 0 0 0 0 ${color};
}
70% {
box-shadow: 0 0 0 2px ${color};
}
100% {
box-shadow: 0 0 0 0 ${color};
}
`
const ResponsiveText = styled(TYPE.label)`
line-height: 16px;
${({ theme }) => theme.mediaWidth.upToSmall`
font-size: 12px;
line-height: 12px;
`};
`
const FocusedOutlineCard = styled(Card)<{ pulsing: boolean }>`
border: 1px solid ${({ theme }) => theme.bg2};
animation: ${({ pulsing, theme }) => pulsing && pulse(theme.primary1)} 0.6s linear;
`
const FeeAmountLabel = {
[FeeAmount.LOW]: {
label: '0.05',
description: <Trans>Best for stable pairs.</Trans>,
},
[FeeAmount.MEDIUM]: {
label: '0.3',
description: <Trans>Best for most pairs.</Trans>,
},
[FeeAmount.HIGH]: {
label: '1',
description: <Trans>Best for exotic pairs.</Trans>,
},
}
const FeeTierPercentageBadge = ({ percentage }: { percentage: number | undefined }) => {
return (
<Badge>
<TYPE.label fontSize={12}>
{percentage !== undefined ? <Trans>{percentage?.toFixed(0)}% select</Trans> : <Trans>Not created</Trans>}
</TYPE.label>
</Badge>
)
}
export default function FeeSelector({
disabled = false,
feeAmount,
handleFeePoolSelect,
currencyA,
currencyB,
}: {
disabled?: boolean
feeAmount?: FeeAmount
handleFeePoolSelect: (feeAmount: FeeAmount) => void
currencyA?: Currency | undefined
currencyB?: Currency | undefined
}) {
const { isLoading, isError, largestUsageFeeTier, distributions } = useFeeTierDistribution(currencyA, currencyB)
const [showOptions, setShowOptions] = useState(false)
const [pulsing, setPulsing] = useState(false)
const previousFeeAmount = usePrevious(feeAmount)
const recommended = useRef(false)
const handleFeePoolSelectWithEvent = useCallback(
(fee) => {
ReactGA.event({
category: 'FeePoolSelect',
action: 'Manual',
})
handleFeePoolSelect(fee)
},
[handleFeePoolSelect]
)
useEffect(() => {
if (feeAmount || isLoading || isError) {
return
}
if (!largestUsageFeeTier) {
// cannot recommend, open options
setShowOptions(true)
} else {
setShowOptions(false)
recommended.current = true
ReactGA.event({
category: 'FeePoolSelect',
action: ' Recommended',
})
handleFeePoolSelect(largestUsageFeeTier)
}
}, [feeAmount, isLoading, isError, largestUsageFeeTier, handleFeePoolSelect])
useEffect(() => {
setShowOptions(isError)
}, [isError])
useEffect(() => {
if (feeAmount && previousFeeAmount !== feeAmount) {
setPulsing(true)
}
}, [previousFeeAmount, feeAmount])
return (
<AutoColumn gap="16px">
<DynamicSection gap="md" disabled={disabled}>
<TYPE.label>
<Trans>Select Pool</Trans>
</TYPE.label>
<TYPE.main fontSize={14} fontWeight={400} style={{ marginBottom: '.5rem', lineHeight: '125%' }}>
<Trans>Select a pool type based on your preferred liquidity provider fee.</Trans>
</TYPE.main>
<RowBetween>
<ButtonRadioChecked
width="32%"
active={feeAmount === FeeAmount.LOW}
onClick={() => handleFeePoolSelect(FeeAmount.LOW)}
>
<AutoColumn gap="sm" justify="flex-start">
<ResponsiveText>
<Trans>0.05% fee</Trans>
</ResponsiveText>
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
<Trans>Best for stable pairs.</Trans>
</TYPE.main>
<FocusedOutlineCard pulsing={pulsing} onAnimationEnd={() => setPulsing(false)}>
<RowBetween>
<AutoColumn id="add-liquidity-selected-fee">
{!feeAmount ? (
<>
<TYPE.label>
<Trans>Fee tier</Trans>
</TYPE.label>
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
<Trans>The % you will earn in fees.</Trans>
</TYPE.main>
</>
) : (
<>
<TYPE.label className="selected-fee-label">
<Trans>{FeeAmountLabel[feeAmount].label}% fee tier</Trans>
</TYPE.label>
<Box style={{ width: 'fit-content', marginTop: '8px' }} className="selected-fee-percentage">
{distributions && feeAmount && <FeeTierPercentageBadge percentage={distributions[feeAmount]} />}
</Box>
</>
)}
</AutoColumn>
</ButtonRadioChecked>
<ButtonRadioChecked
width="32%"
active={feeAmount === FeeAmount.MEDIUM}
onClick={() => handleFeePoolSelect(FeeAmount.MEDIUM)}
>
<AutoColumn gap="sm" justify="flex-start">
<ResponsiveText>
<Trans>0.3% fee</Trans>
</ResponsiveText>
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
<Trans>Best for most pairs.</Trans>
</TYPE.main>
</AutoColumn>
</ButtonRadioChecked>
<ButtonRadioChecked
width="32%"
active={feeAmount === FeeAmount.HIGH}
onClick={() => handleFeePoolSelect(FeeAmount.HIGH)}
>
<AutoColumn gap="sm" justify="flex-start">
<ResponsiveText>
<Trans>1% fee</Trans>
</ResponsiveText>
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
<Trans>Best for exotic pairs.</Trans>
</TYPE.main>
</AutoColumn>
</ButtonRadioChecked>
</RowBetween>
<ButtonGray onClick={() => setShowOptions(!showOptions)} width="auto" padding="4px" $borderRadius="6px">
{showOptions ? <Trans>Hide</Trans> : <Trans>Edit</Trans>}
</ButtonGray>
</RowBetween>
</FocusedOutlineCard>
{showOptions && (
<RowBetween>
<ButtonRadioChecked
width="32%"
active={feeAmount === FeeAmount.LOW}
onClick={() => handleFeePoolSelectWithEvent(FeeAmount.LOW)}
>
<AutoColumn gap="sm" justify="flex-start">
<AutoColumn justify="flex-start" gap="6px">
<ResponsiveText>
<Trans>0.05% fee</Trans>
</ResponsiveText>
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
<Trans>Best for stable pairs.</Trans>
</TYPE.main>
</AutoColumn>
{distributions && <FeeTierPercentageBadge percentage={distributions[FeeAmount.LOW]} />}
</AutoColumn>
</ButtonRadioChecked>
<ButtonRadioChecked
width="32%"
active={feeAmount === FeeAmount.MEDIUM}
onClick={() => handleFeePoolSelectWithEvent(FeeAmount.MEDIUM)}
>
<AutoColumn gap="sm" justify="flex-start">
<AutoColumn justify="flex-start" gap="4px">
<ResponsiveText>
<Trans>0.3% fee</Trans>
</ResponsiveText>
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
<Trans>Best for most pairs.</Trans>
</TYPE.main>
</AutoColumn>
{distributions && <FeeTierPercentageBadge percentage={distributions[FeeAmount.MEDIUM]} />}
</AutoColumn>
</ButtonRadioChecked>
<ButtonRadioChecked
width="32%"
active={feeAmount === FeeAmount.HIGH}
onClick={() => handleFeePoolSelectWithEvent(FeeAmount.HIGH)}
>
<AutoColumn gap="sm" justify="flex-start">
<AutoColumn justify="flex-start" gap="4px">
<ResponsiveText>
<Trans>1% fee</Trans>
</ResponsiveText>
<TYPE.main fontWeight={400} fontSize="12px" textAlign="left">
<Trans>Best for exotic pairs.</Trans>
</TYPE.main>
</AutoColumn>
{distributions && <FeeTierPercentageBadge percentage={distributions[FeeAmount.HIGH]} />}
</AutoColumn>
</ButtonRadioChecked>
</RowBetween>
)}
</DynamicSection>
</AutoColumn>
)

View File

@@ -1,5 +1,4 @@
import JSBI from 'jsbi'
import React from 'react'
import { Currency, CurrencyAmount, Fraction } from '@uniswap/sdk-core'
const CURRENCY_AMOUNT_MIN = new Fraction(JSBI.BigInt(1), JSBI.BigInt(1000000))

View File

@@ -0,0 +1,234 @@
import { Trans } from '@lingui/macro'
import { YellowCard } from 'components/Card'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { useActiveWeb3React } from 'hooks/web3'
import { useEffect, useRef, useState } from 'react'
import { ArrowDownCircle, ChevronDown, ToggleLeft } from 'react-feather'
import { ApplicationModal } from 'state/application/actions'
import { useModalOpen, useToggleModal } from 'state/application/hooks'
import styled, { css } from 'styled-components/macro'
import { ExternalLink } from 'theme'
import { switchToNetwork } from 'utils/switchToNetwork'
import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from '../../constants/chains'
const BaseWrapper = css`
position: relative;
margin-right: 8px;
${({ theme }) => theme.mediaWidth.upToMedium`
justify-self: end;
`};
${({ theme }) => theme.mediaWidth.upToSmall`
margin: 0 0.5rem 0 0;
width: initial;
text-overflow: ellipsis;
flex-shrink: 1;
`};
`
const L2Wrapper = styled.div`
${BaseWrapper}
`
const BaseMenuItem = css`
align-items: center;
background-color: transparent;
border-radius: 12px;
color: ${({ theme }) => theme.text2};
cursor: pointer;
display: flex;
flex: 1;
flex-direction: row;
font-size: 16px;
font-weight: 400;
justify-content: space-between;
:hover {
color: ${({ theme }) => theme.text1};
text-decoration: none;
}
`
const DisabledMenuItem = styled.div`
${BaseMenuItem}
align-items: center;
background-color: ${({ theme }) => theme.bg2};
cursor: auto;
display: flex;
font-size: 10px;
font-style: italic;
justify-content: center;
:hover,
:active,
:focus {
color: ${({ theme }) => theme.text2};
}
`
const FallbackWrapper = styled(YellowCard)`
${BaseWrapper}
width: auto;
border-radius: 12px;
padding: 8px 12px;
width: 100%;
`
const Icon = styled.img`
width: 16px;
margin-right: 2px;
${({ theme }) => theme.mediaWidth.upToSmall`
margin-right: 4px;
`};
`
const MenuFlyout = styled.span`
background-color: ${({ theme }) => theme.bg1};
border: 1px solid ${({ theme }) => theme.bg0};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 12px;
padding: 1rem;
display: flex;
flex-direction: column;
font-size: 1rem;
position: absolute;
left: 0rem;
top: 3rem;
z-index: 100;
width: 237px;
${({ theme }) => theme.mediaWidth.upToMedium`
bottom: unset;
top: 4.5em
right: 0;
`};
> {
padding: 12px;
}
> :not(:first-child) {
margin-top: 8px;
}
> :not(:last-child) {
margin-bottom: 8px;
}
`
const LinkOutCircle = styled(ArrowDownCircle)`
transform: rotate(230deg);
width: 16px;
height: 16px;
opacity: 0.6;
`
const MenuItem = styled(ExternalLink)`
${BaseMenuItem}
`
const ButtonMenuItem = styled.button`
${BaseMenuItem}
border: none;
box-shadow: none;
color: ${({ theme }) => theme.text2};
outline: none;
padding: 0;
`
const NetworkInfo = styled.button<{ chainId: SupportedChainId }>`
align-items: center;
background-color: ${({ theme }) => theme.bg0};
border-radius: 12px;
border: 1px solid ${({ theme }) => theme.bg0};
color: ${({ theme }) => theme.text1};
display: flex;
flex-direction: row;
font-weight: 500;
font-size: 12px;
height: 100%;
margin: 0;
height: 38px;
padding: 0.7rem;
:hover,
:focus {
cursor: pointer;
outline: none;
border: 1px solid ${({ theme }) => theme.bg3};
}
`
const NetworkName = styled.div<{ chainId: SupportedChainId }>`
border-radius: 6px;
font-size: 16px;
font-weight: 500;
padding: 0 2px 0.5px 4px;
margin: 0 2px;
white-space: pre;
${({ theme }) => theme.mediaWidth.upToSmall`
display: none;
`};
`
export default function NetworkCard() {
const { chainId, library } = useActiveWeb3React()
const node = useRef<HTMLDivElement>(null)
const open = useModalOpen(ApplicationModal.ARBITRUM_OPTIONS)
const toggle = useToggleModal(ApplicationModal.ARBITRUM_OPTIONS)
useOnClickOutside(node, open ? toggle : undefined)
const [implements3085, setImplements3085] = useState(false)
useEffect(() => {
// metamask is currently the only known implementer of this EIP
// here we proceed w/ a noop feature check to ensure the user's version of metamask supports network switching
// if not, we hide the UI
if (!library?.provider?.request || !chainId || !library?.provider?.isMetaMask) {
return
}
switchToNetwork({ library, chainId })
.then((x) => x ?? setImplements3085(true))
.catch(() => setImplements3085(false))
}, [library, chainId])
const info = chainId ? CHAIN_INFO[chainId] : undefined
if (!chainId || chainId === SupportedChainId.MAINNET || !info || !library) {
return null
}
if (L2_CHAIN_IDS.includes(chainId)) {
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
const isArbitrum = [SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_RINKEBY].includes(chainId)
return (
<L2Wrapper ref={node}>
<NetworkInfo onClick={toggle} chainId={chainId}>
<Icon src={info.logoUrl} />
<NetworkName chainId={chainId}>{info.label}</NetworkName>
<ChevronDown size={16} style={{ marginTop: '2px' }} strokeWidth={2.5} />
</NetworkInfo>
{open && (
<MenuFlyout>
<MenuItem href={info.bridge}>
<div>{isArbitrum ? <Trans>{info.label} Bridge</Trans> : <Trans>Optimistic L2 Gateway</Trans>}</div>
<LinkOutCircle />
</MenuItem>
<MenuItem href={info.explorer}>
{isArbitrum ? <Trans>{info.label} Explorer</Trans> : <Trans>Optimistic Etherscan</Trans>}
<LinkOutCircle />
</MenuItem>
<MenuItem href={info.docs}>
<div>
<Trans>Learn more</Trans>
</div>
<LinkOutCircle />
</MenuItem>
{implements3085 ? (
<ButtonMenuItem onClick={() => switchToNetwork({ library, chainId: SupportedChainId.MAINNET })}>
<div>
<Trans>Switch to L1 (Mainnet)</Trans>
</div>
<ToggleLeft opacity={0.6} size={16} />
</ButtonMenuItem>
) : (
<DisabledMenuItem>
<Trans>Change your network to go back to L1</Trans>
</DisabledMenuItem>
)}
</MenuFlyout>
)}
</L2Wrapper>
)
}
return <FallbackWrapper title={info.label}>{info.label}</FallbackWrapper>
}

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'
import styled, { keyframes } from 'styled-components'
import { useEffect, useState } from 'react'
import styled, { keyframes } from 'styled-components/macro'
import { useActiveWeb3React } from '../../hooks/web3'
import { useBlockNumber } from '../../state/application/hooks'
@@ -21,7 +21,7 @@ const StyledPolling = styled.div`
`
const StyledPollingNumber = styled(TYPE.small)<{ breathe: boolean; hovering: boolean }>`
transition: opacity 0.25s ease;
opacity: ${({ breathe, hovering }) => (hovering ? 0.7 : breathe ? 1 : 0.2)};
opacity: ${({ breathe, hovering }) => (hovering ? 0.7 : breathe ? 1 : 0.5)};
:hover {
opacity: 1;
}

View File

@@ -1,54 +0,0 @@
import React from 'react'
import styled from 'styled-components/macro'
import { AlertTriangle, X } from 'react-feather'
import { useURLWarningToggle, useURLWarningVisible } from '../../state/user/hooks'
import { isMobile } from 'react-device-detect'
import { Trans } from '@lingui/macro'
const PhishAlert = styled.div<{ isActive: any }>`
width: 100%;
padding: 6px 6px;
background-color: ${({ theme }) => theme.blue1};
color: white;
font-size: 11px;
justify-content: space-between;
align-items: center;
display: ${({ isActive }) => (isActive ? 'flex' : 'none')};
`
export const StyledClose = styled(X)`
:hover {
cursor: pointer;
}
`
export default function URLWarning() {
const toggleURLWarning = useURLWarningToggle()
const showURLWarning = useURLWarningVisible()
return isMobile ? (
<PhishAlert isActive={showURLWarning}>
<div style={{ display: 'flex' }}>
<AlertTriangle style={{ marginRight: 6 }} size={12} />
<Trans>
Make sure the URL is
<code style={{ padding: '0 4px', display: 'inline', fontWeight: 'bold' }}>app.uniswap.org</code>
</Trans>
</div>
<StyledClose size={12} onClick={toggleURLWarning} />
</PhishAlert>
) : window.location.hostname === 'app.uniswap.org' ? (
<PhishAlert isActive={showURLWarning}>
<div style={{ display: 'flex' }}>
<AlertTriangle style={{ marginRight: 6 }} size={12} />
<Trans>
Always make sure the URL is
<code style={{ padding: '0 4px', display: 'inline', fontWeight: 'bold' }}>app.uniswap.org</code> - bookmark it
to be safe.
</Trans>
</div>
<StyledClose size={12} onClick={toggleURLWarning} />
</PhishAlert>
) : null
}

View File

@@ -1,22 +1,23 @@
import { Trans } from '@lingui/macro'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import React, { useMemo } from 'react'
import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
import { useMemo } from 'react'
import { X } from 'react-feather'
import styled from 'styled-components/macro'
import tokenLogo from '../../assets/images/token-logo.png'
import { UNI } from '../../constants/tokens'
import { useTotalSupply } from '../../hooks/useTotalSupply'
import { useActiveWeb3React } from '../../hooks/web3'
import { useMerkleDistributorContract } from '../../hooks/useContract'
import useCurrentBlockTimestamp from '../../hooks/useCurrentBlockTimestamp'
import { useTotalSupply } from '../../hooks/useTotalSupply'
import useUSDCPrice from '../../hooks/useUSDCPrice'
import { useActiveWeb3React } from '../../hooks/web3'
import { useTotalUniEarned } from '../../state/stake/hooks'
import { useAggregateUniBalance, useTokenBalance } from '../../state/wallet/hooks'
import { ExternalLink, StyledInternalLink, TYPE, UniTokenAnimated } from '../../theme'
import { computeUniCirculation } from '../../utils/computeUniCirculation'
import useUSDCPrice from '../../hooks/useUSDCPrice'
import { AutoColumn } from '../Column'
import { RowBetween } from '../Row'
import { Break, CardBGImage, CardNoise, CardSection, DataCard } from '../earn/styled'
import { Trans } from '@lingui/macro'
import { RowBetween } from '../Row'
const ContentWrapper = styled(AutoColumn)`
width: 100%;
@@ -59,6 +60,8 @@ export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowU
[blockTimestamp, chainId, totalSupply, unclaimedUni, uni]
)
const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
return (
<ContentWrapper gap="lg">
<ModalUpper>
@@ -128,7 +131,7 @@ export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowU
<TYPE.white color="white">{totalSupply?.toFixed(0, { groupSeparator: ',' })}</TYPE.white>
</RowBetween>
{uni && uni.chainId === 1 ? (
<ExternalLink href={`https://info.uniswap.org/token/${uni.address}`}>
<ExternalLink href={`${infoLink}/token/${uni.address}`}>
<Trans>View UNI Analytics</Trans>
</ExternalLink>
) : null}

View File

@@ -1,33 +1,28 @@
import useScrollPosition from '@react-hook/window-scroll'
import React, { useState } from 'react'
import { Text } from 'rebass'
import { NavLink } from 'react-router-dom'
import { darken } from 'polished'
import { Trans } from '@lingui/macro'
import { Moon, Sun } from 'react-feather'
import useScrollPosition from '@react-hook/window-scroll'
import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
import { darken } from 'polished'
import { useState } from 'react'
import { NavLink } from 'react-router-dom'
import { Text } from 'rebass'
import { useShowClaimPopup, useToggleSelfClaimModal } from 'state/application/hooks'
import { useUserHasAvailableClaim } from 'state/claim/hooks'
import { useUserHasSubmittedClaim } from 'state/transactions/hooks'
import { useDarkModeManager } from 'state/user/hooks'
import { useETHBalances } from 'state/wallet/hooks'
import styled from 'styled-components/macro'
import Logo from '../../assets/svg/logo.svg'
import LogoDark from '../../assets/svg/logo_white.svg'
import { SupportedChainId } from '../../constants/chains'
import { useActiveWeb3React } from '../../hooks/web3'
import { useDarkModeManager } from '../../state/user/hooks'
import { useETHBalances } from '../../state/wallet/hooks'
import { CardNoise } from '../earn/styled'
import { TYPE, ExternalLink } from '../../theme'
import { YellowCard } from '../Card'
import Menu from '../Menu'
import Row, { RowFixed } from '../Row'
import Web3Status from '../Web3Status'
import { ExternalLink, TYPE } from '../../theme'
import ClaimModal from '../claim/ClaimModal'
import { useToggleSelfClaimModal, useShowClaimPopup } from '../../state/application/hooks'
import { useUserHasAvailableClaim } from '../../state/claim/hooks'
import { useUserHasSubmittedClaim } from '../../state/transactions/hooks'
import { Dots } from '../swap/styleds'
import { CardNoise } from '../earn/styled'
import Menu from '../Menu'
import Modal from '../Modal'
import Row from '../Row'
import { Dots } from '../swap/styleds'
import Web3Status from '../Web3Status'
import NetworkCard from './NetworkCard'
import UniBalanceContent from './UniBalanceContent'
const HeaderFrame = styled.div<{ showBackground: boolean }>`
@@ -43,22 +38,27 @@ const HeaderFrame = styled.div<{ showBackground: boolean }>`
padding: 1rem;
z-index: 21;
position: relative;
/* Background slide effect on scroll. */
background-image: ${({ theme }) => `linear-gradient(to bottom, transparent 50%, ${theme.bg0} 50% )}}`}
background-image: ${({ theme }) => `linear-gradient(to bottom, transparent 50%, ${theme.bg0} 50% )}}`};
background-position: ${({ showBackground }) => (showBackground ? '0 -100%' : '0 0')};
background-size: 100% 200%;
box-shadow: 0px 0px 0px 1px ${({ theme, showBackground }) => (showBackground ? theme.bg2 : 'transparent;')};
transition: background-position .1s, box-shadow .1s;
transition: background-position 0.1s, box-shadow 0.1s;
background-blend-mode: hard-light;
${({ theme }) => theme.mediaWidth.upToLarge`
grid-template-columns: 48px 1fr 1fr;
`};
${({ theme }) => theme.mediaWidth.upToMedium`
padding: 1rem;
grid-template-columns: auto 1fr;
grid-template-columns: 1fr 1fr;
`};
${({ theme }) => theme.mediaWidth.upToExtraSmall`
padding: 1rem;
`}
${({ theme }) => theme.mediaWidth.upToSmall`
padding: 1rem;
grid-template-columns: 36px 1fr;
`};
`
const HeaderControls = styled.div`
@@ -66,23 +66,6 @@ const HeaderControls = styled.div`
flex-direction: row;
align-items: center;
justify-self: flex-end;
${({ theme }) => theme.mediaWidth.upToMedium`
flex-direction: row;
justify-content: space-between;
justify-self: center;
width: 100%;
max-width: 960px;
padding: 1rem;
position: fixed;
bottom: 0px;
left: 0px;
width: 100%;
z-index: 99;
height: 72px;
border-radius: 12px 12px 0 0;
background-color: ${({ theme }) => theme.bg1};
`};
`
const HeaderElement = styled.div`
@@ -95,22 +78,10 @@ const HeaderElement = styled.div`
}
${({ theme }) => theme.mediaWidth.upToMedium`
flex-direction: row-reverse;
align-items: center;
`};
`
const HeaderElementWrap = styled.div`
display: flex;
align-items: center;
`
const HeaderRow = styled(RowFixed)`
${({ theme }) => theme.mediaWidth.upToMedium`
width: 100%;
`};
`
const HeaderLinks = styled(Row)`
justify-self: center;
background-color: ${({ theme }) => theme.bg0};
@@ -121,8 +92,25 @@ const HeaderLinks = styled(Row)`
grid-auto-flow: column;
grid-gap: 10px;
overflow: auto;
align-items: center;
${({ theme }) => theme.mediaWidth.upToLarge`
justify-self: start;
`};
${({ theme }) => theme.mediaWidth.upToMedium`
justify-self: flex-end;
justify-self: center;
`};
${({ theme }) => theme.mediaWidth.upToMedium`
flex-direction: row;
justify-content: space-between;
justify-self: center;
z-index: 99;
position: fixed;
bottom: 0; right: 50%;
transform: translate(50%,-50%);
margin: 0 auto;
background-color: ${({ theme }) => theme.bg0};
border: 1px solid ${({ theme }) => theme.bg2};
box-shadow: 0px 6px 10px rgb(0 0 0 / 2%);
`};
`
@@ -130,7 +118,7 @@ const AccountElement = styled.div<{ active: boolean }>`
display: flex;
flex-direction: row;
align-items: center;
background-color: ${({ theme, active }) => (!active ? theme.bg1 : theme.bg2)};
background-color: ${({ theme, active }) => (!active ? theme.bg1 : theme.bg1)};
border-radius: 12px;
white-space: nowrap;
width: 100%;
@@ -164,25 +152,6 @@ const UNIWrapper = styled.span`
}
`
const HideSmall = styled.span`
${({ theme }) => theme.mediaWidth.upToSmall`
display: none;
`};
`
const NetworkCard = styled(YellowCard)`
border-radius: 12px;
padding: 8px 12px;
${({ theme }) => theme.mediaWidth.upToSmall`
margin: 0;
margin-right: 0.5rem;
width: initial;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 1;
`};
`
const BalanceText = styled(Text)`
${({ theme }) => theme.mediaWidth.upToExtraSmall`
display: none;
@@ -267,57 +236,13 @@ const StyledExternalLink = styled(ExternalLink).attrs({
color: ${({ theme }) => darken(0.1, theme.text1)};
text-decoration: none;
}
${({ theme }) => theme.mediaWidth.upToExtraSmall`
display: none;
`}
`
export const StyledMenuButton = styled.button`
position: relative;
width: 100%;
height: 100%;
border: none;
background-color: transparent;
margin: 0;
padding: 0;
height: 35px;
background-color: ${({ theme }) => theme.bg2};
margin-left: 8px;
padding: 0.15rem 0.5rem;
border-radius: 0.5rem;
:hover,
:focus {
cursor: pointer;
outline: none;
background-color: ${({ theme }) => theme.bg4};
}
svg {
margin-top: 2px;
}
> * {
stroke: ${({ theme }) => theme.text1};
}
`
const NETWORK_LABELS: { [chainId in SupportedChainId | number]: string } = {
[SupportedChainId.MAINNET]: 'Mainnet',
[SupportedChainId.RINKEBY]: 'Rinkeby',
[SupportedChainId.ROPSTEN]: 'Ropsten',
[SupportedChainId.GOERLI]: 'Görli',
[SupportedChainId.KOVAN]: 'Kovan',
[SupportedChainId.ARBITRUM_KOVAN]: 'kArbitrum',
[SupportedChainId.ARBITRUM_ONE]: 'Arbitrum One',
}
export default function Header() {
const { account, chainId } = useActiveWeb3React()
const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? '']
// const [isDark] = useDarkModeManager()
const [darkMode, toggleDarkMode] = useDarkModeManager()
const [darkMode] = useDarkModeManager()
const toggleClaimModal = useToggleSelfClaimModal()
@@ -330,19 +255,18 @@ export default function Header() {
const scrollY = useScrollPosition()
const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
return (
<HeaderFrame showBackground={scrollY > 45}>
<ClaimModal />
<Modal isOpen={showUniBalanceModal} onDismiss={() => setShowUniBalanceModal(false)}>
<UniBalanceContent setShowUniBalanceModal={setShowUniBalanceModal} />
</Modal>
<HeaderRow>
<Title href=".">
<UniIcon>
<img width={'24px'} src={darkMode ? LogoDark : Logo} alt="logo" />
</UniIcon>
</Title>
</HeaderRow>
<Title href=".">
<UniIcon>
<img width={'24px'} src={darkMode ? LogoDark : Logo} alt="logo" />
</UniIcon>
</Title>
<HeaderLinks>
<StyledNavLink id={`swap-nav-link`} to={'/swap'}>
<Trans>Swap</Trans>
@@ -360,21 +284,20 @@ export default function Header() {
>
<Trans>Pool</Trans>
</StyledNavLink>
<StyledNavLink id={`stake-nav-link`} to={'/vote'}>
<Trans>Vote</Trans>
</StyledNavLink>
<StyledExternalLink id={`stake-nav-link`} href={'https://info.uniswap.org'}>
{chainId && chainId === SupportedChainId.MAINNET && (
<StyledNavLink id={`stake-nav-link`} to={'/vote'}>
<Trans>Vote</Trans>
</StyledNavLink>
)}
<StyledExternalLink id={`stake-nav-link`} href={infoLink}>
<Trans>Charts</Trans>
<sup></sup>
</StyledExternalLink>
</HeaderLinks>
<HeaderControls>
<NetworkCard />
<HeaderElement>
<HideSmall>
{chainId && chainId !== SupportedChainId.MAINNET && NETWORK_LABELS[chainId] && (
<NetworkCard title={NETWORK_LABELS[chainId]}>{NETWORK_LABELS[chainId]}</NetworkCard>
)}
</HideSmall>
{availableClaim && !showClaimPopup && (
<UNIWrapper onClick={toggleClaimModal}>
<UNIAmount active={!!account && !availableClaim} style={{ pointerEvents: 'auto' }}>
@@ -394,18 +317,13 @@ export default function Header() {
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
{account && userEthBalance ? (
<BalanceText style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
<Trans>{userEthBalance?.toSignificant(4)} ETH</Trans>
<Trans>{userEthBalance?.toSignificant(3)} ETH</Trans>
</BalanceText>
) : null}
<Web3Status />
</AccountElement>
</HeaderElement>
<HeaderElementWrap>
<StyledMenuButton onClick={() => toggleDarkMode()}>
{darkMode ? <Moon size={20} /> : <Sun size={20} />}
</StyledMenuButton>
<Menu />
</HeaderElementWrap>
</HeaderElement>
</HeaderControls>
</HeaderFrame>
)

View File

@@ -1,5 +1,5 @@
import Tooltip from 'components/Tooltip'
import React, { useState } from 'react'
import { useState } from 'react'
import styled from 'styled-components/macro'
const TextWrapper = styled.span<{ margin: boolean; link?: boolean; fontSize?: string; adjustSize?: boolean }>`

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useRef } from 'react'
import { useEffect, useRef } from 'react'
import styled from 'styled-components/macro'

View File

@@ -1,14 +1,13 @@
import React, { useState, useCallback, useEffect, ReactNode } from 'react'
import { LightCard } from 'components/Card'
import { RowBetween } from 'components/Row'
import { useState, useCallback, useEffect, ReactNode } from 'react'
import { OutlineCard } from 'components/Card'
import { Input as NumericalInput } from '../NumericalInput'
import styled, { keyframes } from 'styled-components'
import styled, { keyframes } from 'styled-components/macro'
import { TYPE } from 'theme'
import { AutoColumn } from 'components/Column'
import { ButtonPrimary } from 'components/Button'
import { ButtonGray } from 'components/Button'
import { FeeAmount } from '@uniswap/v3-sdk'
import { formattedFeeAmount } from 'utils'
import { Trans } from '@lingui/macro'
import { Plus, Minus } from 'react-feather'
const pulse = (color: string) => keyframes`
0% {
@@ -24,25 +23,37 @@ const pulse = (color: string) => keyframes`
}
`
const SmallButton = styled(ButtonPrimary)`
/* background-color: ${({ theme }) => theme.bg2}; */
border-radius: 8px;
padding: 4px 6px;
width: 48%;
const InputRow = styled.div`
display: grid;
grid-template-columns: 30px 1fr 30px;
`
const FocusedOutlineCard = styled(LightCard)<{ active?: boolean; pulsing?: boolean }>`
const SmallButton = styled(ButtonGray)`
border-radius: 8px;
padding: 4px;
`
const FocusedOutlineCard = styled(OutlineCard)<{ active?: boolean; pulsing?: boolean }>`
border-color: ${({ active, theme }) => active && theme.blue1};
padding: 12px;
animation: ${({ pulsing, theme }) => pulsing && pulse(theme.blue1)} 0.8s linear;
`
const StyledInput = styled(NumericalInput)<{ usePercent?: boolean }>`
/* background-color: ${({ theme }) => theme.bg0}; */
background-color: transparent;
text-align: center;
margin-right: 12px;
width: 100%;
font-weight: 500;
padding: 0 10px;
${({ theme }) => theme.mediaWidth.upToSmall`
font-size: 16px;
`};
${({ theme }) => theme.mediaWidth.upToExtraSmall`
font-size: 12px;
`};
`
const InputTitle = styled(TYPE.small)`
@@ -51,11 +62,17 @@ const InputTitle = styled(TYPE.small)`
font-weight: 500;
`
const ButtonLabel = styled(TYPE.white)<{ disabled: boolean }>`
color: ${({ theme, disabled }) => (disabled ? theme.text2 : theme.text1)} !important;
`
interface StepCounterProps {
value: string
onUserInput: (value: string) => void
decrement: () => string
increment: () => string
decrementDisabled?: boolean
incrementDisabled?: boolean
feeAmount?: FeeAmount
label?: string
width?: string
@@ -69,7 +86,8 @@ const StepCounter = ({
value,
decrement,
increment,
feeAmount,
decrementDisabled = false,
incrementDisabled = false,
width,
locked,
onUserInput,
@@ -87,9 +105,6 @@ const StepCounter = ({
// animation if parent value updates local value
const [pulsing, setPulsing] = useState<boolean>(false)
// format fee amount
const feeAmountFormatted = feeAmount ? formattedFeeAmount(feeAmount * 2) : ''
const handleOnFocus = () => {
setUseLocalValue(true)
setActive(true)
@@ -126,39 +141,45 @@ const StepCounter = ({
return (
<FocusedOutlineCard pulsing={pulsing} active={active} onFocus={handleOnFocus} onBlur={handleOnBlur} width={width}>
<AutoColumn gap="6px" style={{ marginBottom: '12px' }}>
<AutoColumn gap="6px">
<InputTitle fontSize={12} textAlign="center">
{title}
</InputTitle>
<StyledInput
className="rate-input-0"
value={localValue}
fontSize="20px"
disabled={locked}
onUserInput={(val) => {
setLocalValue(val)
}}
/>
<InputRow>
{!locked && (
<SmallButton onClick={handleDecrement} disabled={decrementDisabled}>
<ButtonLabel disabled={decrementDisabled} fontSize="12px">
<Minus size={18} />
</ButtonLabel>
</SmallButton>
)}
<StyledInput
className="rate-input-0"
value={localValue}
fontSize="20px"
disabled={locked}
onUserInput={(val) => {
setLocalValue(val)
}}
/>
{!locked && (
<SmallButton onClick={handleIncrement} disabled={incrementDisabled}>
<ButtonLabel disabled={incrementDisabled} fontSize="12px">
<Plus size={18} />
</ButtonLabel>
</SmallButton>
)}
</InputRow>
<InputTitle fontSize={12} textAlign="center">
<Trans>
{tokenB} per {tokenA}
</Trans>
</InputTitle>
</AutoColumn>
{!locked ? (
<RowBetween>
<SmallButton onClick={handleDecrement}>
<TYPE.white fontSize="12px">
<Trans>-{feeAmountFormatted}%</Trans>
</TYPE.white>
</SmallButton>
<SmallButton onClick={handleIncrement}>
<TYPE.white fontSize="12px">
<Trans>+{feeAmountFormatted}%</Trans>
</TYPE.white>
</SmallButton>
</RowBetween>
) : null}
</FocusedOutlineCard>
)
}

View File

@@ -1,152 +0,0 @@
export const dummyData = [
{ time: '2018-10-19', value: 35.98 },
{ time: '2018-10-22', value: 35.75 },
{ time: '2018-10-23', value: 35.65 },
{ time: '2018-10-24', value: 34.12 },
{ time: '2018-10-25', value: 35.84 },
{ time: '2018-10-26', value: 35.24 },
{ time: '2018-10-29', value: 35.99 },
{ time: '2018-10-30', value: 37.71 },
{ time: '2018-10-31', value: 38.14 },
{ time: '2018-11-01', value: 37.95 },
{ time: '2018-11-02', value: 37.66 },
{ time: '2018-11-05', value: 38.02 },
{ time: '2018-11-06', value: 37.73 },
{ time: '2018-11-07', value: 38.3 },
{ time: '2018-11-08', value: 38.3 },
{ time: '2018-11-09', value: 38.34 },
{ time: '2018-11-12', value: 38.0 },
{ time: '2018-11-13', value: 37.72 },
{ time: '2018-11-14', value: 38.29 },
{ time: '2018-11-15', value: 38.49 },
{ time: '2018-11-16', value: 38.59 },
{ time: '2018-11-19', value: 38.18 },
{ time: '2018-11-20', value: 36.76 },
{ time: '2018-11-21', value: 37.51 },
{ time: '2018-11-23', value: 37.39 },
{ time: '2018-11-26', value: 37.77 },
{ time: '2018-11-27', value: 38.36 },
{ time: '2018-11-28', value: 39.06 },
{ time: '2018-11-29', value: 39.42 },
{ time: '2018-11-30', value: 39.01 },
{ time: '2018-12-03', value: 39.15 },
{ time: '2018-12-04', value: 37.69 },
{ time: '2018-12-06', value: 37.88 },
{ time: '2018-12-07', value: 37.41 },
{ time: '2018-12-10', value: 37.35 },
{ time: '2018-12-11', value: 36.84 },
{ time: '2018-12-12', value: 36.98 },
{ time: '2018-12-13', value: 36.76 },
{ time: '2018-12-14', value: 36.34 },
{ time: '2018-12-17', value: 36.21 },
{ time: '2018-12-18', value: 35.65 },
{ time: '2018-12-19', value: 35.19 },
{ time: '2018-12-20', value: 34.62 },
{ time: '2018-12-21', value: 33.75 },
{ time: '2018-12-24', value: 33.07 },
{ time: '2018-12-26', value: 34.14 },
{ time: '2018-12-27', value: 34.47 },
{ time: '2018-12-28', value: 34.35 },
{ time: '2018-12-31', value: 34.05 },
{ time: '2019-01-02', value: 34.37 },
{ time: '2019-01-03', value: 34.64 },
{ time: '2019-01-04', value: 35.81 },
{ time: '2019-01-07', value: 35.43 },
{ time: '2019-01-08', value: 35.72 },
{ time: '2019-01-09', value: 36.06 },
{ time: '2019-01-10', value: 35.82 },
{ time: '2019-01-11', value: 35.63 },
{ time: '2019-01-14', value: 35.77 },
{ time: '2019-01-15', value: 35.83 },
{ time: '2019-01-16', value: 35.9 },
{ time: '2019-01-17', value: 35.91 },
{ time: '2019-01-18', value: 36.21 },
{ time: '2019-01-22', value: 34.97 },
{ time: '2019-01-23', value: 36.89 },
{ time: '2019-01-24', value: 36.24 },
{ time: '2019-01-25', value: 35.78 },
{ time: '2019-01-28', value: 35.37 },
{ time: '2019-01-29', value: 36.08 },
{ time: '2019-01-30', value: 35.43 },
{ time: '2019-01-31', value: 36.57 },
{ time: '2019-02-01', value: 36.79 },
{ time: '2019-02-04', value: 36.77 },
{ time: '2019-02-05', value: 37.15 },
{ time: '2019-02-06', value: 37.17 },
{ time: '2019-02-07', value: 37.68 },
{ time: '2019-02-08', value: 37.6 },
{ time: '2019-02-11', value: 37.0 },
{ time: '2019-02-12', value: 37.24 },
{ time: '2019-02-13', value: 37.03 },
{ time: '2019-02-14', value: 37.26 },
{ time: '2019-02-15', value: 37.77 },
{ time: '2019-02-19', value: 37.55 },
{ time: '2019-02-20', value: 37.79 },
{ time: '2019-02-21', value: 38.47 },
{ time: '2019-02-22', value: 38.61 },
{ time: '2019-02-25', value: 38.57 },
{ time: '2019-02-26', value: 38.8 },
{ time: '2019-02-27', value: 38.53 },
{ time: '2019-02-28', value: 38.67 },
{ time: '2019-03-01', value: 39.1 },
{ time: '2019-03-04', value: 38.73 },
{ time: '2019-03-05', value: 38.72 },
{ time: '2019-03-06', value: 38.61 },
{ time: '2019-03-07', value: 38.38 },
{ time: '2019-03-08', value: 38.19 },
{ time: '2019-03-11', value: 39.17 },
{ time: '2019-03-12', value: 39.49 },
{ time: '2019-03-13', value: 39.56 },
{ time: '2019-03-14', value: 39.87 },
{ time: '2019-03-15', value: 40.47 },
{ time: '2019-03-18', value: 39.92 },
{ time: '2019-03-19', value: 39.78 },
{ time: '2019-03-20', value: 39.47 },
{ time: '2019-03-21', value: 40.05 },
{ time: '2019-03-22', value: 39.46 },
{ time: '2019-03-25', value: 39.18 },
{ time: '2019-03-26', value: 39.63 },
{ time: '2019-03-27', value: 40.21 },
{ time: '2019-03-28', value: 40.42 },
{ time: '2019-03-29', value: 39.98 },
{ time: '2019-04-01', value: 40.31 },
{ time: '2019-04-02', value: 40.02 },
{ time: '2019-04-03', value: 40.27 },
{ time: '2019-04-04', value: 40.41 },
{ time: '2019-04-05', value: 40.42 },
{ time: '2019-04-08', value: 40.71 },
{ time: '2019-04-09', value: 41.04 },
{ time: '2019-04-10', value: 41.08 },
{ time: '2019-04-11', value: 41.04 },
{ time: '2019-04-12', value: 41.3 },
{ time: '2019-04-15', value: 41.78 },
{ time: '2019-04-16', value: 41.97 },
{ time: '2019-04-17', value: 42.57 },
{ time: '2019-04-18', value: 42.43 },
{ time: '2019-04-22', value: 42.0 },
{ time: '2019-04-23', value: 41.99 },
{ time: '2019-04-24', value: 41.85 },
{ time: '2019-04-25', value: 42.93 },
{ time: '2019-04-26', value: 43.08 },
{ time: '2019-04-29', value: 43.45 },
{ time: '2019-04-30', value: 43.53 },
{ time: '2019-05-01', value: 43.42 },
{ time: '2019-05-02', value: 42.65 },
{ time: '2019-05-03', value: 43.29 },
{ time: '2019-05-06', value: 43.3 },
{ time: '2019-05-07', value: 42.76 },
{ time: '2019-05-08', value: 42.55 },
{ time: '2019-05-09', value: 42.92 },
{ time: '2019-05-10', value: 43.15 },
{ time: '2019-05-13', value: 42.28 },
{ time: '2019-05-14', value: 42.91 },
{ time: '2019-05-15', value: 42.49 },
{ time: '2019-05-16', value: 43.19 },
{ time: '2019-05-17', value: 43.54 },
{ time: '2019-05-20', value: 42.78 },
{ time: '2019-05-21', value: 43.29 },
{ time: '2019-05-22', value: 43.3 },
{ time: '2019-05-23', value: 42.73 },
{ time: '2019-05-24', value: 42.67 },
{ time: '2019-05-28', value: 42.75 },
]

View File

@@ -1,169 +0,0 @@
import React, { useRef, useState, useEffect, useCallback, Dispatch, SetStateAction, ReactNode } from 'react'
import { createChart, IChartApi } from 'lightweight-charts'
import { darken } from 'polished'
import { RowBetween } from 'components/Row'
import Card from '../Card'
import styled from 'styled-components/macro'
import useTheme from 'hooks/useTheme'
const Wrapper = styled(Card)`
width: 100%;
padding: 1rem;
display: flex;
background-color: ${({ theme }) => theme.bg0}
flex-direction: column;
> * {
font-size: 1rem;
}
`
const DEFAULT_HEIGHT = 300
export type LineChartProps = {
data: any[]
color?: string | undefined
height?: number | undefined
setValue?: Dispatch<SetStateAction<number | undefined>> // used for value on hover
topLeft?: ReactNode | undefined
topRight?: ReactNode | undefined
bottomLeft?: ReactNode | undefined
bottomRight?: ReactNode | undefined
} & React.HTMLAttributes<HTMLDivElement>
const LineChart = ({
data,
color = '#56B2A4',
setValue,
topLeft,
topRight,
bottomLeft,
bottomRight,
height = DEFAULT_HEIGHT,
...rest
}: LineChartProps) => {
const theme = useTheme()
const chartRef = useRef<HTMLDivElement>(null)
const [chartCreated, setChart] = useState<IChartApi | undefined>()
// for reseting value on hover exit
const currenValue = data[data.length - 1].value
const handleResize = useCallback(() => {
if (chartCreated && chartRef?.current?.parentElement) {
chartCreated.resize(chartRef.current.parentElement.clientWidth - 32, height)
chartCreated.timeScale().fitContent()
chartCreated.timeScale().scrollToPosition(0, false)
}
}, [chartCreated, chartRef, height])
// add event listener for resize
const isClient = typeof window === 'object'
useEffect(() => {
if (!isClient) {
return
}
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [isClient, chartRef, handleResize]) // Empty array ensures that effect is only run on mount and unmount
const textColor = theme.text2
// if chart not instantiated in canvas, create it
useEffect(() => {
if (!chartCreated && data && !!chartRef?.current?.parentElement) {
const chart = createChart(chartRef.current, {
height: height,
width: chartRef.current.parentElement.clientWidth - 32,
layout: {
backgroundColor: 'transparent',
textColor: textColor,
fontFamily: 'Inter',
},
rightPriceScale: {
scaleMargins: {
top: 0.1,
bottom: 0.1,
},
borderVisible: false,
},
timeScale: {
borderVisible: false,
},
watermark: {
color: 'rgba(0, 0, 0, 0)',
},
grid: {
horzLines: {
visible: false,
},
vertLines: {
visible: false,
},
},
crosshair: {
horzLine: {
visible: true,
style: 3,
width: 1,
color: '#505050',
labelBackgroundColor: color,
},
vertLine: {
visible: true,
style: 3,
width: 1,
color: '#505050',
labelBackgroundColor: color,
},
},
})
const series = chart.addAreaSeries({
lineColor: color,
topColor: darken(0.4, color),
bottomColor: theme.bg0,
lineWidth: 2,
priceLineVisible: false,
})
series.setData(data)
// update the title when hovering on the chart
chart.subscribeCrosshairMove(function (param) {
if (
chartRef?.current &&
(param === undefined ||
param.time === undefined ||
(param && param.point && param.point.x < 0) ||
(param && param.point && param.point.x > chartRef.current.clientWidth) ||
(param && param.point && param.point.y < 0) ||
(param && param.point && param.point.y > height))
) {
setValue && setValue(currenValue)
} else {
const price = parseFloat(param.seriesPrices.get(series)?.toString() ?? currenValue)
setValue && setValue(price)
}
})
chart.timeScale().fitContent()
setChart(chart)
}
}, [color, chartCreated, currenValue, data, height, setValue, textColor, theme])
return (
<Wrapper>
<RowBetween>
{topLeft ?? null}
{topRight ?? null}
</RowBetween>
<div ref={chartRef} id={'line-chart'} {...rest} />
<RowBetween>
{bottomLeft ?? null}
{bottomRight ?? null}
</RowBetween>
</Wrapper>
)
}
export default LineChart

View File

@@ -0,0 +1,44 @@
import React, { useMemo } from 'react'
import { area, curveStepAfter, ScaleLinear } from 'd3'
import styled from 'styled-components/macro'
import { ChartEntry } from './types'
import inRange from 'lodash/inRange'
const Path = styled.path<{ fill: string | undefined }>`
opacity: 0.5;
stroke: ${({ fill, theme }) => fill ?? theme.blue2};
fill: ${({ fill, theme }) => fill ?? theme.blue2};
`
export const Area = ({
series,
xScale,
yScale,
xValue,
yValue,
fill,
}: {
series: ChartEntry[]
xScale: ScaleLinear<number, number>
yScale: ScaleLinear<number, number>
xValue: (d: ChartEntry) => number
yValue: (d: ChartEntry) => number
fill?: string | undefined
}) =>
useMemo(
() => (
<Path
fill={fill}
d={
area()
.curve(curveStepAfter)
.x((d: unknown) => xScale(xValue(d as ChartEntry)))
.y1((d: unknown) => yScale(yValue(d as ChartEntry)))
.y0(yScale(0))(
series.filter((d) => inRange(xScale(xValue(d)), 0, innerWidth)) as Iterable<[number, number]>
) ?? undefined
}
/>
),
[fill, series, xScale, xValue, yScale, yValue]
)

View File

@@ -0,0 +1,43 @@
import React, { useMemo } from 'react'
import { Axis as d3Axis, axisBottom, NumberValue, ScaleLinear, select } from 'd3'
import styled from 'styled-components/macro'
const StyledGroup = styled.g`
line {
display: none;
}
text {
color: ${({ theme }) => theme.text2};
transform: translateY(5px);
}
`
const Axis = ({ axisGenerator }: { axisGenerator: d3Axis<NumberValue> }) => {
const axisRef = (axis: SVGGElement) => {
axis &&
select(axis)
.call(axisGenerator)
.call((g) => g.select('.domain').remove())
}
return <g ref={axisRef} />
}
export const AxisBottom = ({
xScale,
innerHeight,
offset = 0,
}: {
xScale: ScaleLinear<number, number>
innerHeight: number
offset?: number
}) =>
useMemo(
() => (
<StyledGroup transform={`translate(0, ${innerHeight + offset})`}>
<Axis axisGenerator={axisBottom(xScale).ticks(6)} />
</StyledGroup>
),
[innerHeight, offset, xScale]
)

View File

@@ -0,0 +1,262 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { BrushBehavior, brushX, D3BrushEvent, ScaleLinear, select } from 'd3'
import styled from 'styled-components/macro'
import { brushHandleAccentPath, brushHandlePath, OffScreenHandle } from 'components/LiquidityChartRangeInput/svg'
import usePrevious from 'hooks/usePrevious'
const Handle = styled.path<{ color: string }>`
cursor: ew-resize;
pointer-events: none;
stroke-width: 3;
stroke: ${({ color }) => color};
fill: ${({ color }) => color};
`
const HandleAccent = styled.path`
cursor: ew-resize;
pointer-events: none;
stroke-width: 1.5;
stroke: ${({ theme }) => theme.white};
opacity: 0.6;
`
const LabelGroup = styled.g<{ visible: boolean }>`
opacity: ${({ visible }) => (visible ? '1' : '0')};
transition: opacity 300ms;
`
const TooltipBackground = styled.rect`
fill: ${({ theme }) => theme.bg2};
`
const Tooltip = styled.text`
text-anchor: middle;
font-size: 13px;
fill: ${({ theme }) => theme.text1};
`
// flips the handles draggers when close to the container edges
const FLIP_HANDLE_THRESHOLD_PX = 20
// margin to prevent tick snapping from putting the brush off screen
const BRUSH_EXTENT_MARGIN_PX = 2
const compare = (a1: [number, number], a2: [number, number]): boolean => a1[0] !== a2[0] || a1[1] !== a2[1]
export const Brush = ({
id,
xScale,
interactive,
brushLabelValue,
brushExtent,
setBrushExtent,
innerWidth,
innerHeight,
westHandleColor,
eastHandleColor,
}: {
id: string
xScale: ScaleLinear<number, number>
interactive: boolean
brushLabelValue: (d: 'w' | 'e', x: number) => string
brushExtent: [number, number]
setBrushExtent: (extent: [number, number]) => void
innerWidth: number
innerHeight: number
westHandleColor: string
eastHandleColor: string
}) => {
const brushRef = useRef<SVGGElement | null>(null)
const brushBehavior = useRef<BrushBehavior<SVGGElement> | null>(null)
// only used to drag the handles on brush for performance
const [localBrushExtent, setLocalBrushExtent] = useState<[number, number] | null>(brushExtent)
const [showLabels, setShowLabels] = useState(false)
const [hovering, setHovering] = useState(false)
const previousBrushExtent = usePrevious(brushExtent)
const brushed = useCallback(
({ type, selection }: D3BrushEvent<unknown>) => {
if (!selection) {
setLocalBrushExtent(null)
return
}
const scaled = (selection as [number, number]).map(xScale.invert) as [number, number]
// avoid infinite render loop by checking for change
if (type === 'end' && compare(brushExtent, scaled)) {
setBrushExtent(scaled)
}
setLocalBrushExtent(scaled)
},
[xScale, brushExtent, setBrushExtent]
)
// keep local and external brush extent in sync
// i.e. snap to ticks on bruhs end
useEffect(() => {
setLocalBrushExtent(brushExtent)
}, [brushExtent])
// initialize the brush
useEffect(() => {
if (!brushRef.current) return
brushBehavior.current = brushX<SVGGElement>()
.extent([
[Math.max(0 + BRUSH_EXTENT_MARGIN_PX, xScale(0)), 0],
[innerWidth - BRUSH_EXTENT_MARGIN_PX, innerHeight],
])
.handleSize(30)
.filter(() => interactive)
.on('brush end', brushed)
brushBehavior.current(select(brushRef.current))
if (previousBrushExtent && compare(brushExtent, previousBrushExtent)) {
select(brushRef.current)
.transition()
.call(brushBehavior.current.move as any, brushExtent.map(xScale))
}
// brush linear gradient
select(brushRef.current)
.selectAll('.selection')
.attr('stroke', 'none')
.attr('fill-opacity', '0.1')
.attr('fill', `url(#${id}-gradient-selection)`)
}, [brushExtent, brushed, id, innerHeight, innerWidth, interactive, previousBrushExtent, xScale])
// respond to xScale changes only
useEffect(() => {
if (!brushRef.current || !brushBehavior.current) return
brushBehavior.current.move(select(brushRef.current) as any, brushExtent.map(xScale) as any)
}, [brushExtent, xScale])
// show labels when local brush changes
useEffect(() => {
setShowLabels(true)
const timeout = setTimeout(() => setShowLabels(false), 1500)
return () => clearTimeout(timeout)
}, [localBrushExtent])
// variables to help render the SVGs
const flipWestHandle = localBrushExtent && xScale(localBrushExtent[0]) > FLIP_HANDLE_THRESHOLD_PX
const flipEastHandle = localBrushExtent && xScale(localBrushExtent[1]) > innerWidth - FLIP_HANDLE_THRESHOLD_PX
const showWestArrow = localBrushExtent && (xScale(localBrushExtent[0]) < 0 || xScale(localBrushExtent[1]) < 0)
const showEastArrow =
localBrushExtent && (xScale(localBrushExtent[0]) > innerWidth || xScale(localBrushExtent[1]) > innerWidth)
const westHandleInView =
localBrushExtent && xScale(localBrushExtent[0]) >= 0 && xScale(localBrushExtent[0]) <= innerWidth
const eastHandleInView =
localBrushExtent && xScale(localBrushExtent[1]) >= 0 && xScale(localBrushExtent[1]) <= innerWidth
return useMemo(
() => (
<>
<defs>
<linearGradient id={`${id}-gradient-selection`} x1="0%" y1="100%" x2="100%" y2="100%">
<stop stopColor={westHandleColor} />
<stop stopColor={eastHandleColor} offset="1" />
</linearGradient>
{/* clips at exactly the svg area */}
<clipPath id={`${id}-brush-clip`}>
<rect x="0" y="0" width={innerWidth} height={innerHeight} />
</clipPath>
</defs>
{/* will host the d3 brush */}
<g
ref={brushRef}
clipPath={`url(#${id}-brush-clip)`}
onMouseEnter={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
/>
{/* custom brush handles */}
{localBrushExtent && (
<>
{/* west handle */}
{westHandleInView ? (
<g
transform={`translate(${Math.max(0, xScale(localBrushExtent[0]))}, 0), scale(${
flipWestHandle ? '-1' : '1'
}, 1)`}
>
<g>
<Handle color={westHandleColor} d={brushHandlePath(innerHeight)} />
<HandleAccent d={brushHandleAccentPath()} />
</g>
<LabelGroup
transform={`translate(50,0), scale(${flipWestHandle ? '1' : '-1'}, 1)`}
visible={showLabels || hovering}
>
<TooltipBackground y="0" x="-30" height="30" width="60" rx="8" />
<Tooltip transform={`scale(-1, 1)`} y="15" dominantBaseline="middle">
{brushLabelValue('w', localBrushExtent[0])}
</Tooltip>
</LabelGroup>
</g>
) : null}
{/* east handle */}
{eastHandleInView ? (
<g transform={`translate(${xScale(localBrushExtent[1])}, 0), scale(${flipEastHandle ? '-1' : '1'}, 1)`}>
<g>
<Handle color={eastHandleColor} d={brushHandlePath(innerHeight)} />
<HandleAccent d={brushHandleAccentPath()} />
</g>
<LabelGroup
transform={`translate(50,0), scale(${flipEastHandle ? '-1' : '1'}, 1)`}
visible={showLabels || hovering}
>
<TooltipBackground y="0" x="-30" height="30" width="60" rx="8" />
<Tooltip y="15" dominantBaseline="middle">
{brushLabelValue('e', localBrushExtent[1])}
</Tooltip>
</LabelGroup>
</g>
) : null}
{showWestArrow && <OffScreenHandle color={westHandleColor} />}
{showEastArrow && (
<g transform={`translate(${innerWidth}, 0) scale(-1, 1)`}>
<OffScreenHandle color={eastHandleColor} />
</g>
)}
</>
)}
</>
),
[
brushLabelValue,
eastHandleColor,
eastHandleInView,
flipEastHandle,
flipWestHandle,
hovering,
id,
innerHeight,
innerWidth,
localBrushExtent,
showEastArrow,
showLabels,
showWestArrow,
westHandleColor,
westHandleInView,
xScale,
]
)
}

View File

@@ -0,0 +1,138 @@
import { max, scaleLinear, ZoomTransform } from 'd3'
import { useEffect, useMemo, useRef, useState } from 'react'
import { Area } from './Area'
import { AxisBottom } from './AxisBottom'
import { Brush } from './Brush'
import { Line } from './Line'
import { ChartEntry, LiquidityChartRangeInputProps } from './types'
import Zoom, { ZoomOverlay } from './Zoom'
export const xAccessor = (d: ChartEntry) => d.price0
export const yAccessor = (d: ChartEntry) => d.activeLiquidity
export function Chart({
id = 'liquidityChartRangeInput',
data: { series, current },
styles,
dimensions: { width, height },
margins,
interactive = true,
brushDomain,
brushLabels,
onBrushDomainChange,
zoomLevels,
}: LiquidityChartRangeInputProps) {
const zoomRef = useRef<SVGRectElement | null>(null)
const [zoom, setZoom] = useState<ZoomTransform | null>(null)
const [innerHeight, innerWidth] = useMemo(
() => [height - margins.top - margins.bottom, width - margins.left - margins.right],
[width, height, margins]
)
const { xScale, yScale } = useMemo(() => {
const scales = {
xScale: scaleLinear()
.domain([current * zoomLevels.initialMin, current * zoomLevels.initialMax] as number[])
.range([0, innerWidth]),
yScale: scaleLinear()
.domain([0, max(series, yAccessor)] as number[])
.range([innerHeight, 0]),
}
if (zoom) {
const newXscale = zoom.rescaleX(scales.xScale)
scales.xScale.domain(newXscale.domain())
}
return scales
}, [current, zoomLevels.initialMin, zoomLevels.initialMax, innerWidth, series, innerHeight, zoom])
useEffect(() => {
// reset zoom as necessary
setZoom(null)
}, [zoomLevels])
useEffect(() => {
if (!brushDomain) {
onBrushDomainChange(xScale.domain() as [number, number])
}
}, [brushDomain, onBrushDomainChange, xScale])
return (
<>
<Zoom
svg={zoomRef.current}
xScale={xScale}
setZoom={setZoom}
width={innerWidth}
height={
// allow zooming inside the x-axis
height
}
showClear={false}
zoomLevels={zoomLevels}
/>
<svg width="100%" height="100%" viewBox={`0 0 ${width} ${height}`} style={{ overflow: 'visible' }}>
<defs>
<clipPath id={`${id}-chart-clip`}>
<rect x="0" y="0" width={innerWidth} height={height} />
</clipPath>
{brushDomain && (
// mask to highlight selected area
<mask id={`${id}-chart-area-mask`}>
<rect
fill="white"
x={xScale(brushDomain[0])}
y="0"
width={xScale(brushDomain[1]) - xScale(brushDomain[0])}
height={innerHeight}
/>
</mask>
)}
</defs>
<g transform={`translate(${margins.left},${margins.top})`}>
<g clipPath={`url(#${id}-chart-clip)`}>
<Area series={series} xScale={xScale} yScale={yScale} xValue={xAccessor} yValue={yAccessor} />
{brushDomain && (
// duplicate area chart with mask for selected area
<g mask={`url(#${id}-chart-area-mask)`}>
<Area
series={series}
xScale={xScale}
yScale={yScale}
xValue={xAccessor}
yValue={yAccessor}
fill={styles.area.selection}
/>
</g>
)}
<Line value={current} xScale={xScale} innerHeight={innerHeight} />
<AxisBottom xScale={xScale} innerHeight={innerHeight} />
</g>
<ZoomOverlay width={innerWidth} height={height} ref={zoomRef} />
<Brush
id={id}
xScale={xScale}
interactive={interactive}
brushLabelValue={brushLabels}
brushExtent={brushDomain ?? (xScale.domain() as [number, number])}
innerWidth={innerWidth}
innerHeight={innerHeight}
setBrushExtent={onBrushDomainChange}
westHandleColor={styles.brush.handle.west}
eastHandleColor={styles.brush.handle.east}
/>
</g>
</svg>
</>
)
}

View File

@@ -0,0 +1,24 @@
import React, { useMemo } from 'react'
import { ScaleLinear } from 'd3'
import styled from 'styled-components/macro'
const StyledLine = styled.line`
opacity: 0.5;
stroke-width: 2;
stroke: ${({ theme }) => theme.text1};
fill: none;
`
export const Line = ({
value,
xScale,
innerHeight,
}: {
value: number
xScale: ScaleLinear<number, number>
innerHeight: number
}) =>
useMemo(
() => <StyledLine x1={xScale(value)} y1="0" x2={xScale(value)} y2={innerHeight} />,
[value, xScale, innerHeight]
)

View File

@@ -0,0 +1,121 @@
import React, { useEffect, useMemo, useRef } from 'react'
import { ButtonGray } from 'components/Button'
import styled from 'styled-components/macro'
import { ScaleLinear, select, ZoomBehavior, zoom, ZoomTransform } from 'd3'
import { RefreshCcw, ZoomIn, ZoomOut } from 'react-feather'
import { ZoomLevels } from './types'
const Wrapper = styled.div<{ count: number }>`
display: grid;
grid-template-columns: repeat(${({ count }) => count.toString()}, 1fr);
grid-gap: 6px;
position: absolute;
top: -75px;
right: 0;
`
const Button = styled(ButtonGray)`
&:hover {
background-color: ${({ theme }) => theme.bg2};
color: ${({ theme }) => theme.text1};
}
width: 32px;
height: 32px;
padding: 4px;
`
export const ZoomOverlay = styled.rect`
fill: transparent;
cursor: grab;
&:active {
cursor: grabbing;
}
`
export default function Zoom({
svg,
xScale,
setZoom,
width,
height,
showClear,
zoomLevels,
}: {
svg: SVGElement | null
xScale: ScaleLinear<number, number>
setZoom: (transform: ZoomTransform) => void
width: number
height: number
showClear: boolean
zoomLevels: ZoomLevels
}) {
const zoomBehavior = useRef<ZoomBehavior<Element, unknown>>()
const [zoomIn, zoomOut, reset, initial] = useMemo(
() => [
() =>
svg &&
zoomBehavior.current &&
select(svg as Element)
.transition()
.call(zoomBehavior.current.scaleBy, 2),
() =>
svg &&
zoomBehavior.current &&
select(svg as Element)
.transition()
.call(zoomBehavior.current.scaleBy, 0.5),
() =>
svg &&
zoomBehavior.current &&
select(svg as Element)
.transition()
.call(zoomBehavior.current.scaleTo, 1),
() =>
svg &&
zoomBehavior.current &&
select(svg as Element)
.transition()
.call(zoomBehavior.current.scaleTo, 0.5),
],
[svg, zoomBehavior]
)
useEffect(() => {
if (!svg) return
zoomBehavior.current = zoom()
.scaleExtent([zoomLevels.min, zoomLevels.max])
.extent([
[0, 0],
[width, height],
])
.on('zoom', ({ transform }: { transform: ZoomTransform }) => setZoom(transform))
select(svg as Element).call(zoomBehavior.current)
}, [height, width, setZoom, svg, xScale, zoomBehavior, zoomLevels, zoomLevels.max, zoomLevels.min])
useEffect(() => {
// reset zoom to initial on zoomLevel change
initial()
}, [initial, zoomLevels])
return (
<Wrapper count={showClear ? 3 : 2}>
{showClear && (
<Button onClick={reset} disabled={false}>
<RefreshCcw size={16} />
</Button>
)}
<Button onClick={zoomIn} disabled={false}>
<ZoomIn size={16} />
</Button>
<Button onClick={zoomOut} disabled={false}>
<ZoomOut size={16} />
</Button>
</Wrapper>
)
}

View File

@@ -0,0 +1,56 @@
import { useCallback, useMemo } from 'react'
import { Currency } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk'
import { usePoolActiveLiquidity } from 'hooks/usePoolTickData'
import { ChartEntry } from './types'
import JSBI from 'jsbi'
export interface TickProcessed {
liquidityActive: JSBI
price0: string
}
export function useDensityChartData({
currencyA,
currencyB,
feeAmount,
}: {
currencyA: Currency | undefined
currencyB: Currency | undefined
feeAmount: FeeAmount | undefined
}) {
const { isLoading, isUninitialized, isError, error, data } = usePoolActiveLiquidity(currencyA, currencyB, feeAmount)
const formatData = useCallback(() => {
if (!data?.length) {
return undefined
}
const newData: ChartEntry[] = []
for (let i = 0; i < data.length; i++) {
const t: TickProcessed = data[i]
const chartEntry = {
activeLiquidity: parseFloat(t.liquidityActive.toString()),
price0: parseFloat(t.price0),
}
if (chartEntry.activeLiquidity > 0) {
newData.push(chartEntry)
}
}
return newData
}, [data])
return useMemo(() => {
return {
isLoading,
isUninitialized,
isError,
error,
formattedData: !isLoading && !isUninitialized ? formatData() : undefined,
}
}, [isLoading, isUninitialized, isError, error, formatData])
}

View File

@@ -0,0 +1,197 @@
import React, { ReactNode, useCallback, useMemo } from 'react'
import { Trans } from '@lingui/macro'
import { Currency, Price, Token } from '@uniswap/sdk-core'
import { AutoColumn, ColumnCenter } from 'components/Column'
import Loader from 'components/Loader'
import { useColor } from 'hooks/useColor'
import useTheme from 'hooks/useTheme'
import { saturate } from 'polished'
import { BarChart2, Inbox, CloudOff } from 'react-feather'
import { batch } from 'react-redux'
import styled from 'styled-components/macro'
import { TYPE } from '../../theme'
import { Chart } from './Chart'
import { useDensityChartData } from './hooks'
import { format } from 'd3'
import { Bound } from 'state/mint/v3/actions'
import { FeeAmount } from '@uniswap/v3-sdk'
import ReactGA from 'react-ga'
import { ZoomLevels } from './types'
const ZOOM_LEVELS: Record<FeeAmount, ZoomLevels> = {
[FeeAmount.LOW]: {
initialMin: 0.999,
initialMax: 1.001,
min: 0.00001,
max: 1.5,
},
[FeeAmount.MEDIUM]: {
initialMin: 0.5,
initialMax: 2,
min: 0.00001,
max: 20,
},
[FeeAmount.HIGH]: {
initialMin: 0.5,
initialMax: 2,
min: 0.00001,
max: 20,
},
}
const ChartWrapper = styled.div`
position: relative;
justify-content: center;
align-content: center;
`
function InfoBox({ message, icon }: { message?: ReactNode; icon: ReactNode }) {
return (
<ColumnCenter style={{ height: '100%', justifyContent: 'center' }}>
{icon}
{message && (
<TYPE.mediumHeader padding={10} marginTop="20px" textAlign="center">
{message}
</TYPE.mediumHeader>
)}
</ColumnCenter>
)
}
export default function LiquidityChartRangeInput({
currencyA,
currencyB,
feeAmount,
ticksAtLimit,
price,
priceLower,
priceUpper,
onLeftRangeInput,
onRightRangeInput,
interactive,
}: {
currencyA: Currency | undefined
currencyB: Currency | undefined
feeAmount?: FeeAmount
ticksAtLimit: { [bound in Bound]?: boolean | undefined }
price: number | undefined
priceLower?: Price<Token, Token>
priceUpper?: Price<Token, Token>
onLeftRangeInput: (typedValue: string) => void
onRightRangeInput: (typedValue: string) => void
interactive: boolean
}) {
const theme = useTheme()
const tokenAColor = useColor(currencyA?.wrapped)
const tokenBColor = useColor(currencyB?.wrapped)
const { isLoading, isUninitialized, isError, error, formattedData } = useDensityChartData({
currencyA,
currencyB,
feeAmount,
})
const onBrushDomainChangeEnded = useCallback(
(domain) => {
let leftRangeValue = Number(domain[0])
const rightRangeValue = Number(domain[1])
if (leftRangeValue <= 0) {
leftRangeValue = 1 / 10 ** 6
}
batch(() => {
// simulate user input for auto-formatting and other validations
leftRangeValue > 0 && onLeftRangeInput(leftRangeValue.toFixed(6))
rightRangeValue > 0 && onRightRangeInput(rightRangeValue.toFixed(6))
})
},
[onLeftRangeInput, onRightRangeInput]
)
interactive = interactive && Boolean(formattedData?.length)
const brushDomain: [number, number] | undefined = useMemo(() => {
const isSorted = currencyA && currencyB && currencyA?.wrapped.sortsBefore(currencyB?.wrapped)
const leftPrice = isSorted ? priceLower : priceUpper?.invert()
const rightPrice = isSorted ? priceUpper : priceLower?.invert()
return leftPrice && rightPrice
? [parseFloat(leftPrice?.toSignificant(5)), parseFloat(rightPrice?.toSignificant(5))]
: undefined
}, [currencyA, currencyB, priceLower, priceUpper])
const brushLabelValue = useCallback(
(d: 'w' | 'e', x: number) => {
if (!price) return ''
if (d === 'w' && ticksAtLimit[Bound.LOWER]) return '0'
if (d === 'e' && ticksAtLimit[Bound.UPPER]) return '∞'
//const percent = (((x < price ? -1 : 1) * (Math.max(x, price) - Math.min(x, price))) / Math.min(x, price)) * 100
const percent = (x < price ? -1 : 1) * ((Math.max(x, price) - Math.min(x, price)) / price) * 100
return price ? `${format(Math.abs(percent) > 1 ? '.2~s' : '.2~f')(percent)}%` : ''
},
[price, ticksAtLimit]
)
if (isError) {
ReactGA.exception({
...error,
category: 'Liquidity',
fatal: false,
})
}
return (
<AutoColumn gap="md" style={{ minHeight: '200px' }}>
{isUninitialized ? (
<InfoBox
message={<Trans>Your position will appear here.</Trans>}
icon={<Inbox size={56} stroke={theme.text1} />}
/>
) : isLoading ? (
<InfoBox icon={<Loader size="40px" stroke={theme.text4} />} />
) : isError ? (
<InfoBox
message={<Trans>Liquidity data not available.</Trans>}
icon={<CloudOff size={56} stroke={theme.text4} />}
/>
) : !formattedData || formattedData === [] || !price ? (
<InfoBox
message={<Trans>There is no liquidity data.</Trans>}
icon={<BarChart2 size={56} stroke={theme.text4} />}
/>
) : (
<ChartWrapper>
<Chart
data={{ series: formattedData, current: price }}
dimensions={{ width: 400, height: 200 }}
margins={{ top: 10, right: 2, bottom: 20, left: 0 }}
styles={{
area: {
selection: theme.blue1,
},
brush: {
handle: {
west: saturate(0.1, tokenAColor) ?? theme.red1,
east: saturate(0.1, tokenBColor) ?? theme.blue1,
},
},
}}
interactive={interactive}
brushLabels={brushLabelValue}
brushDomain={brushDomain}
onBrushDomainChange={onBrushDomainChangeEnded}
zoomLevels={ZOOM_LEVELS[feeAmount ?? FeeAmount.MEDIUM]}
/>
</ChartWrapper>
)}
</AutoColumn>
)
}

View File

@@ -0,0 +1,61 @@
/*
* Generates an SVG path for the east brush handle.
* Apply `scale(-1, 1)` to generate west brush handle.
*
* |```````\
* | | | |
* |______/
* |
* |
* |
* |
* |
*
* https://medium.com/@dennismphil/one-side-rounded-rectangle-using-svg-fb31cf318d90
*/
export const brushHandlePath = (height: number) =>
[
// handle
`M 0 0`, // move to origin
`v ${height}`, // vertical line
'm 1 0', // move 1px to the right
`V 0`, // second vertical line
`M 0 1`, // move to origin
// head
'h 12', // horizontal line
'q 2 0, 2 2', // rounded corner
'v 22', // vertical line
'q 0 2 -2 2', // rounded corner
'h -12', // horizontal line
`z`, // close path
].join(' ')
export const brushHandleAccentPath = () =>
[
'm 5 7', // move to first accent
'v 14', // vertical line
'M 0 0', // move to origin
'm 9 7', // move to second accent
'v 14', // vertical line
'z',
].join(' ')
export const OffScreenHandle = ({
color,
size = 10,
margin = 10,
}: {
color: string
size?: number
margin?: number
}) => (
<polygon
points={`0 0, ${size} ${size}, 0 ${size}`}
transform={` translate(${size + margin}, ${margin}) rotate(45) `}
fill={color}
stroke={color}
strokeWidth="4"
strokeLinejoin="round"
/>
)

View File

@@ -0,0 +1,58 @@
export interface ChartEntry {
activeLiquidity: number
price0: number
}
export interface Dimensions {
width: number
height: number
}
export interface Margins {
top: number
right: number
bottom: number
left: number
}
export interface ZoomLevels {
initialMin: number
initialMax: number
min: number
max: number
}
export interface LiquidityChartRangeInputProps {
// to distringuish between multiple charts in the DOM
id?: string
data: {
series: ChartEntry[]
current: number
}
styles: {
area: {
// color of the ticks in range
selection: string
}
brush: {
handle: {
west: string
east: string
}
}
}
dimensions: Dimensions
margins: Margins
interactive?: boolean
brushLabels: (d: 'w' | 'e', x: number) => string
brushDomain: [number, number] | undefined
onBrushDomainChange: (domain: [number, number]) => void
zoomLevels: ZoomLevels
}

View File

@@ -1,6 +1,4 @@
import React from 'react'
import styled, { keyframes } from 'styled-components'
import styled, { keyframes } from 'styled-components/macro'
const rotate = keyframes`
from {

View File

@@ -1,11 +1,11 @@
import React, { useState } from 'react'
import { useState } from 'react'
import { Slash } from 'react-feather'
import { ImageProps } from 'rebass'
import useTheme from '../../hooks/useTheme'
const BAD_SRCS: { [tokenAddress: string]: true } = {}
export interface LogoProps extends Pick<ImageProps, 'style' | 'alt' | 'className'> {
interface LogoProps extends Pick<ImageProps, 'style' | 'alt' | 'className'> {
srcs: string[]
}

View File

@@ -1,16 +1,32 @@
import React, { useRef } from 'react'
import { BookOpen, Code, Info, MessageCircle, PieChart } from 'react-feather'
import React, { useEffect, useRef, useState } from 'react'
import {
BookOpen,
Code,
Info,
MessageCircle,
PieChart,
Moon,
Sun,
ChevronRight,
ChevronLeft,
Check,
} from 'react-feather'
import { Link } from 'react-router-dom'
import styled, { css } from 'styled-components'
import styled, { css } from 'styled-components/macro'
import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
import { useActiveWeb3React } from '../../hooks/web3'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import { ApplicationModal } from '../../state/application/actions'
import { useModalOpen, useToggleModal } from '../../state/application/hooks'
import { Trans } from '@lingui/macro'
import { ExternalLink } from '../../theme'
import { ButtonPrimary } from '../Button'
import { useDarkModeManager } from 'state/user/hooks'
import { L2_CHAIN_IDS, CHAIN_INFO, SupportedChainId } from 'constants/chains'
import { LOCALE_LABEL, SupportedLocale, SUPPORTED_LOCALES } from 'constants/locales'
import { useLocationLinkProps } from 'hooks/useLocationLinkProps'
import { useActiveLocale } from 'hooks/useActiveLocale'
export enum FlyoutAlignment {
LEFT = 'LEFT',
@@ -30,17 +46,18 @@ const StyledMenuButton = styled.button`
background-color: transparent;
margin: 0;
padding: 0;
height: 35px;
background-color: ${({ theme }) => theme.bg2};
height: 38px;
background-color: ${({ theme }) => theme.bg0};
border: 1px solid ${({ theme }) => theme.bg0};
padding: 0.15rem 0.5rem;
border-radius: 0.5rem;
border-radius: 12px;
:hover,
:focus {
cursor: pointer;
outline: none;
background-color: ${({ theme }) => theme.bg3};
border: 1px solid ${({ theme }) => theme.bg3};
}
svg {
@@ -65,18 +82,22 @@ const StyledMenu = styled.div`
`
const MenuFlyout = styled.span<{ flyoutAlignment?: FlyoutAlignment }>`
min-width: 8.125rem;
background-color: ${({ theme }) => theme.bg2};
min-width: 196px;
max-height: 350px;
overflow: auto;
background-color: ${({ theme }) => theme.bg1};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.01);
border: 1px solid ${({ theme }) => theme.bg0};
border-radius: 12px;
padding: 0.5rem;
display: flex;
flex-direction: column;
font-size: 1rem;
font-size: 16px;
position: absolute;
top: 3rem;
z-index: 100;
${({ flyoutAlignment = FlyoutAlignment.RIGHT }) =>
flyoutAlignment === FlyoutAlignment.RIGHT
? css`
@@ -86,7 +107,9 @@ const MenuFlyout = styled.span<{ flyoutAlignment?: FlyoutAlignment }>`
left: 0rem;
`};
${({ theme }) => theme.mediaWidth.upToMedium`
top: -17.25rem;
bottom: unset;
right: 0;
left: unset;
`};
`
@@ -96,15 +119,13 @@ const MenuItem = styled(ExternalLink)`
flex-direction: row;
align-items: center;
padding: 0.5rem 0.5rem;
justify-content: space-between;
color: ${({ theme }) => theme.text2};
:hover {
color: ${({ theme }) => theme.text1};
cursor: pointer;
text-decoration: none;
}
> svg {
margin-right: 8px;
}
`
const InternalMenuItem = styled(Link)`
@@ -121,16 +142,89 @@ const InternalMenuItem = styled(Link)`
}
`
const InternalLinkMenuItem = styled(InternalMenuItem)`
display: flex;
flex-direction: row;
align-items: center;
padding: 0.5rem 0.5rem;
justify-content: space-between;
text-decoration: none;
:hover {
color: ${({ theme }) => theme.text1};
cursor: pointer;
text-decoration: none;
}
`
const ToggleMenuItem = styled.button`
background-color: transparent;
margin: 0;
padding: 0;
border: none;
display: flex;
flex: 1;
flex-direction: row;
align-items: center;
padding: 0.5rem 0.5rem;
justify-content: space-between;
font-size: 1rem;
font-weight: 500;
color: ${({ theme }) => theme.text2};
:hover {
color: ${({ theme }) => theme.text1};
cursor: pointer;
text-decoration: none;
}
`
const CODE_LINK = 'https://github.com/Uniswap/uniswap-interface'
function LanguageMenuItem({ locale, active, key }: { locale: SupportedLocale; active: boolean; key: string }) {
const { to, onClick } = useLocationLinkProps(locale)
if (!to) return null
return (
<InternalLinkMenuItem onClick={onClick} key={key} to={to}>
<div>{LOCALE_LABEL[locale]}</div>
{active && <Check opacity={0.6} size={16} />}
</InternalLinkMenuItem>
)
}
function LanguageMenu({ close }: { close: () => void }) {
const activeLocale = useActiveLocale()
return (
<MenuFlyout>
<ToggleMenuItem onClick={close}>
<ChevronLeft size={16} />
</ToggleMenuItem>
{SUPPORTED_LOCALES.map((locale) => (
<LanguageMenuItem locale={locale} active={activeLocale === locale} key={locale} />
))}
</MenuFlyout>
)
}
export default function Menu() {
const { account } = useActiveWeb3React()
const { account, chainId } = useActiveWeb3React()
const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.MENU)
const toggle = useToggleModal(ApplicationModal.MENU)
useOnClickOutside(node, open ? toggle : undefined)
const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
const showUNIClaimOption = Boolean(!!account && !!chainId && !L2_CHAIN_IDS.includes(chainId))
const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
const [darkMode, toggleDarkMode] = useDarkModeManager()
const [menu, setMenu] = useState<'main' | 'lang'>('main')
useEffect(() => {
setMenu('main')
}, [open])
return (
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451
@@ -139,45 +233,70 @@ export default function Menu() {
<StyledMenuIcon />
</StyledMenuButton>
{open && (
<MenuFlyout>
<MenuItem href="https://uniswap.org/">
<Info size={14} />
<div>
<Trans>About</Trans>
</div>
</MenuItem>
<MenuItem href="https://docs.uniswap.org/">
<BookOpen size={14} />
<div>
<Trans>Docs</Trans>
</div>
</MenuItem>
<MenuItem href={CODE_LINK}>
<Code size={14} />
<div>
<Trans>Code</Trans>
</div>
</MenuItem>
<MenuItem href="https://discord.gg/FCfyBSbCU5">
<MessageCircle size={14} />
<div>
<Trans>Discord</Trans>
</div>
</MenuItem>
<MenuItem href="https://info.uniswap.org/">
<PieChart size={14} />
<div>
<Trans>Analytics</Trans>
</div>
</MenuItem>
{account && (
<UNIbutton onClick={openClaimModal} padding="8px 16px" width="100%" borderRadius="12px" mt="0.5rem">
<Trans>Claim UNI</Trans>
</UNIbutton>
)}
</MenuFlyout>
)}
{open &&
(() => {
switch (menu) {
case 'lang':
return <LanguageMenu close={() => setMenu('main')} />
case 'main':
default:
return (
<MenuFlyout>
<MenuItem href="https://uniswap.org/">
<div>
<Trans>About</Trans>
</div>
<Info opacity={0.6} size={16} />
</MenuItem>
<MenuItem href="https://docs.uniswap.org/">
<div>
<Trans>Docs</Trans>
</div>
<BookOpen opacity={0.6} size={16} />
</MenuItem>
<MenuItem href={CODE_LINK}>
<div>
<Trans>Code</Trans>
</div>
<Code opacity={0.6} size={16} />
</MenuItem>
<MenuItem href="https://discord.gg/FCfyBSbCU5">
<div>
<Trans>Discord</Trans>
</div>
<MessageCircle opacity={0.6} size={16} />
</MenuItem>
<MenuItem href={infoLink}>
<div>
<Trans>Analytics</Trans>
</div>
<PieChart opacity={0.6} size={16} />
</MenuItem>
<ToggleMenuItem onClick={() => setMenu('lang')}>
<div>
<Trans>Language</Trans>
</div>
<ChevronRight size={16} opacity={0.6} />
</ToggleMenuItem>
<ToggleMenuItem onClick={() => toggleDarkMode()}>
<div>{darkMode ? <Trans>Light Theme</Trans> : <Trans>Dark Theme</Trans>}</div>
{darkMode ? <Moon opacity={0.6} size={16} /> : <Sun opacity={0.6} size={16} />}
</ToggleMenuItem>
{showUNIClaimOption && (
<UNIbutton
onClick={openClaimModal}
padding="8px 16px"
width="100%"
$borderRadius="12px"
mt="0.5rem"
>
<Trans>Claim UNI</Trans>
</UNIbutton>
)}
</MenuFlyout>
)
}
})()}
</StyledMenu>
)
}

View File

@@ -1,5 +1,5 @@
import React from 'react'
import styled, { css } from 'styled-components'
import styled, { css } from 'styled-components/macro'
import { animated, useTransition, useSpring } from 'react-spring'
import { DialogOverlay, DialogContent } from '@reach/dialog'
import { isMobile } from 'react-device-detect'
@@ -30,7 +30,7 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
)).attrs({
'aria-label': 'dialog',
})`
overflow-y: ${({ mobile }) => (mobile ? 'scroll' : 'hidden')};
overflow-y: auto;
&[data-reach-dialog-content] {
margin: 0 0 2rem 0;
@@ -39,7 +39,7 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadow1)};
padding: 0px;
width: 50vw;
overflow-y: ${({ mobile }) => (mobile ? 'scroll' : 'hidden')};
overflow-y: auto;
overflow-x: hidden;
align-self: ${({ mobile }) => (mobile ? 'flex-end' : 'center')};

View File

@@ -1,9 +1,9 @@
import React, { useContext } from 'react'
import { useContext } from 'react'
import { useActiveWeb3React } from '../../hooks/web3'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
import { AutoColumn, ColumnCenter } from '../Column'
import styled, { ThemeContext } from 'styled-components'
import styled, { ThemeContext } from 'styled-components/macro'
import { RowBetween } from '../Row'
import { TYPE, CloseIcon, CustomLightSpinner } from '../../theme'
import { ArrowUpCircle } from 'react-feather'

View File

@@ -1,12 +1,11 @@
import React from 'react'
import styled from 'styled-components/macro'
import { darken } from 'polished'
import { Trans } from '@lingui/macro'
import { NavLink, Link as HistoryLink } from 'react-router-dom'
import { NavLink, Link as HistoryLink, useLocation } from 'react-router-dom'
import { Percent } from '@uniswap/sdk-core'
import { ArrowLeft } from 'react-feather'
import { RowBetween } from '../Row'
import Row, { RowBetween } from '../Row'
import SettingsTab from '../Settings'
import { useAppDispatch } from 'state/hooks'
@@ -14,6 +13,8 @@ import { resetMintState } from 'state/mint/actions'
import { resetMintState as resetMintV3State } from 'state/mint/v3/actions'
import { TYPE } from 'theme'
import useTheme from 'hooks/useTheme'
import { ReactNode } from 'react'
import { Box } from 'rebass'
const Tabs = styled.div`
${({ theme }) => theme.flexRowNoWrap}
@@ -50,6 +51,15 @@ const StyledNavLink = styled(NavLink).attrs({
}
`
const StyledHistoryLink = styled(HistoryLink)<{ flex: string | undefined }>`
flex: ${({ flex }) => flex ?? 'none'};
${({ theme }) => theme.mediaWidth.upToMedium`
flex: none;
margin-right: 10px;
`};
`
const ActiveText = styled.div`
font-weight: 500;
font-size: 20px;
@@ -90,24 +100,32 @@ export function FindPoolTabs({ origin }: { origin: string }) {
export function AddRemoveTabs({
adding,
creating,
positionID,
defaultSlippage,
positionID,
children,
}: {
adding: boolean
creating: boolean
positionID?: string | undefined
defaultSlippage: Percent
positionID?: string | undefined
showBackLink?: boolean
children?: ReactNode | undefined
}) {
const theme = useTheme()
// reset states on back
const dispatch = useAppDispatch()
const location = useLocation()
// detect if back should redirect to v3 or v2 pool page
const poolLink = location.pathname.includes('add/v2')
? '/pool/v2'
: '/pool' + (!!positionID ? `/${positionID.toString()}` : '')
return (
<Tabs>
<RowBetween style={{ padding: '1rem 1rem 0 1rem' }}>
<HistoryLink
to={'/pool' + (!!positionID ? `/${positionID.toString()}` : '')}
<StyledHistoryLink
to={poolLink}
onClick={() => {
if (adding) {
// not 100% sure both of these are needed
@@ -115,10 +133,15 @@ export function AddRemoveTabs({
dispatch(resetMintV3State())
}
}}
flex={children ? '1' : undefined}
>
<StyledArrowLeft stroke={theme.text2} />
</HistoryLink>
<TYPE.mediumHeader fontWeight={500} fontSize={20}>
</StyledHistoryLink>
<TYPE.mediumHeader
fontWeight={500}
fontSize={20}
style={{ flex: '1', margin: 'auto', textAlign: children ? 'start' : 'center' }}
>
{creating ? (
<Trans>Create a pair</Trans>
) : adding ? (
@@ -127,8 +150,22 @@ export function AddRemoveTabs({
<Trans>Remove Liquidity</Trans>
)}
</TYPE.mediumHeader>
<Box style={{ marginRight: '.5rem' }}>{children}</Box>
<SettingsTab placeholderSlippage={defaultSlippage} />
</RowBetween>
</Tabs>
)
}
export function CreateProposalTabs() {
return (
<Tabs>
<Row style={{ padding: '1rem 1rem 0 1rem' }}>
<HistoryLink to="/vote">
<StyledArrowLeft />
</HistoryLink>
<ActiveText style={{ marginLeft: 'auto', marginRight: 'auto' }}>Create Proposal</ActiveText>
</Row>
</Tabs>
)
}

View File

@@ -0,0 +1,128 @@
import { Trans } from '@lingui/macro'
import {
ArbitrumWrapperBackgroundDarkMode,
ArbitrumWrapperBackgroundLightMode,
OptimismWrapperBackgroundDarkMode,
OptimismWrapperBackgroundLightMode,
} from 'components/NetworkAlert/NetworkAlert'
import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
import { useActiveWeb3React } from 'hooks/web3'
import { ArrowDownCircle } from 'react-feather'
import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
const L2Icon = styled.img`
display: none;
height: 40px;
margin: auto 20px auto 4px;
width: 40px;
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
display: block;
}
`
const DesktopTextBreak = styled.div`
display: none;
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
display: block;
}
`
const Wrapper = styled.div<{ chainId: SupportedL2ChainId; darkMode: boolean; logoUrl: string }>`
${({ chainId, darkMode }) =>
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
? darkMode
? OptimismWrapperBackgroundDarkMode
: OptimismWrapperBackgroundLightMode
: darkMode
? ArbitrumWrapperBackgroundDarkMode
: ArbitrumWrapperBackgroundLightMode};
border-radius: 20px;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 12px;
position: relative;
width: 100%;
:before {
background-image: url(${({ logoUrl }) => logoUrl});
background-repeat: no-repeat;
background-size: 300px;
content: '';
height: 300px;
opacity: 0.1;
position: absolute;
transform: rotate(25deg) translate(-90px, -40px);
width: 300px;
z-index: -1;
}
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
flex-direction: row;
padding: 16px 20px;
}
`
const Body = styled.div`
font-size: 12px;
line-height: 143%;
margin: 12px;
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
flex: 1 1 auto;
margin: auto 0;
}
`
const LinkOutCircle = styled(ArrowDownCircle)`
transform: rotate(230deg);
width: 20px;
height: 20px;
margin-left: 12px;
`
const LinkOutToBridge = styled(ExternalLink)`
align-items: center;
background-color: black;
border-radius: 16px;
color: white;
display: flex;
font-size: 14px;
justify-content: space-between;
margin: 0;
max-height: 47px;
padding: 16px 12px;
text-decoration: none;
width: auto;
:hover,
:focus,
:active {
background-color: black;
}
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
margin: auto 0 auto auto;
padding: 14px 16px;
min-width: 226px;
}
`
export function AddLiquidityNetworkAlert() {
const { chainId } = useActiveWeb3React()
const [darkMode] = useDarkModeManager()
const [arbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged) {
return null
}
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
const depositUrl = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
? `${info.bridge}?chainId=1`
: info.bridge
return (
<Wrapper darkMode={darkMode} chainId={chainId} logoUrl={info.logoUrl}>
<L2Icon src={info.logoUrl} />
<Body>
<Trans>This is an alpha release of Uniswap on the {info.label} network.</Trans>
<DesktopTextBreak /> <Trans>You must bridge L1 assets to the network to use them.</Trans>
</Body>
<LinkOutToBridge href={depositUrl}>
<Trans>Deposit to {info.label}</Trans>
<LinkOutCircle />
</LinkOutToBridge>
</Wrapper>
)
}

View File

@@ -0,0 +1,128 @@
import { Trans } from '@lingui/macro'
import {
ArbitrumWrapperBackgroundDarkMode,
ArbitrumWrapperBackgroundLightMode,
OptimismWrapperBackgroundDarkMode,
OptimismWrapperBackgroundLightMode,
} from 'components/NetworkAlert/NetworkAlert'
import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
import { useActiveWeb3React } from 'hooks/web3'
import { ArrowDownCircle } from 'react-feather'
import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
import styled from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
const L2Icon = styled.img`
display: none;
height: 40px;
margin: auto 20px auto 4px;
width: 40px;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
display: block;
}
`
const DesktopTextBreak = styled.div`
display: none;
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
display: block;
}
`
const Wrapper = styled.div<{ chainId: SupportedL2ChainId; darkMode: boolean; logoUrl: string }>`
${({ chainId, darkMode }) =>
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
? darkMode
? OptimismWrapperBackgroundDarkMode
: OptimismWrapperBackgroundLightMode
: darkMode
? ArbitrumWrapperBackgroundDarkMode
: ArbitrumWrapperBackgroundLightMode};
border-radius: 20px;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 12px;
position: relative;
width: 100%;
:before {
background-image: url(${({ logoUrl }) => logoUrl});
background-repeat: no-repeat;
background-size: 300px;
content: '';
height: 300px;
opacity: 0.1;
position: absolute;
transform: rotate(25deg) translate(-90px, -40px);
width: 300px;
z-index: -1;
}
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
flex-direction: row;
padding: 16px 20px;
}
`
const Body = styled.div`
font-size: 12px;
line-height: 143%;
margin: 12px;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
flex: 1 1 auto;
margin: auto 0;
}
`
const LinkOutCircle = styled(ArrowDownCircle)`
transform: rotate(230deg);
width: 20px;
height: 20px;
margin-left: 12px;
`
const LinkOutToBridge = styled(ExternalLink)`
align-items: center;
background-color: black;
border-radius: 16px;
color: white;
display: flex;
font-size: 14px;
justify-content: space-between;
margin: 0;
max-height: 47px;
padding: 16px 8px;
text-decoration: none;
width: auto;
:hover,
:focus,
:active {
background-color: black;
}
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
margin: auto 0 auto auto;
padding: 14px 17px;
min-width: 226px;
}
`
export function MinimalNetworkAlert() {
const { chainId } = useActiveWeb3React()
const [darkMode] = useDarkModeManager()
const [arbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged) {
return null
}
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
const depositUrl = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
? `${info.bridge}?chainId=1`
: info.bridge
return (
<Wrapper darkMode={darkMode} chainId={chainId} logoUrl={info.logoUrl}>
<L2Icon src={info.logoUrl} />
<Body>
<Trans>This is an alpha release of Uniswap on the {info.label} network.</Trans>
<DesktopTextBreak /> <Trans>You must bridge L1 assets to the network to use them.</Trans>
</Body>
<LinkOutToBridge href={depositUrl}>
<Trans>Deposit to {info.label}</Trans>
<LinkOutCircle />
</LinkOutToBridge>
</Wrapper>
)
}

View File

@@ -0,0 +1,164 @@
import { Trans } from '@lingui/macro'
import { L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
import { useActiveWeb3React } from 'hooks/web3'
import { useCallback, useState } from 'react'
import { ArrowDownCircle, X } from 'react-feather'
import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
import { useETHBalances } from 'state/wallet/hooks'
import styled, { css } from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
import { CHAIN_INFO } from '../../constants/chains'
const L2Icon = styled.img`
width: 40px;
height: 40px;
justify-self: center;
`
const CloseIcon = styled(X)`
cursor: pointer;
position: absolute;
top: 16px;
right: 16px;
`
const ContentWrapper = styled.div`
align-items: center;
display: grid;
grid-gap: 4px;
grid-template-columns: 40px 4fr;
grid-template-rows: auto auto;
margin: 20px 16px;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
grid-template-columns: 42px 4fr;
grid-gap: 8px;
}
`
export const ArbitrumWrapperBackgroundDarkMode = css`
background: radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),
radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.3) 0%, rgba(33, 114, 229, 0.3) 100%), hsla(0, 0%, 100%, 0.1);
`
export const ArbitrumWrapperBackgroundLightMode = css`
background: radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),
radial-gradient(circle at top left, hsla(206, 50%, 75%, 0.01), hsla(215, 79%, 51%, 0.12)), hsla(0, 0%, 100%, 0.1);
`
export const OptimismWrapperBackgroundDarkMode = css`
background: radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.2) 0%, rgba(255, 255, 255, 0.1) 100%),
radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.5) 0%, rgba(235, 0, 255, 0.345) 96%);
`
export const OptimismWrapperBackgroundLightMode = css`
background: radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),
radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.5);
`
const RootWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; logoUrl: string }>`
${({ chainId, darkMode }) =>
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
? darkMode
? OptimismWrapperBackgroundDarkMode
: OptimismWrapperBackgroundLightMode
: darkMode
? ArbitrumWrapperBackgroundDarkMode
: ArbitrumWrapperBackgroundLightMode};
border-radius: 20px;
display: flex;
flex-direction: column;
max-width: 480px;
min-height: 174px;
overflow: hidden;
position: relative;
width: 100%;
:before {
background-image: url(${({ logoUrl }) => logoUrl});
background-repeat: no-repeat;
background-size: 300px;
content: '';
height: 300px;
opacity: 0.1;
position: absolute;
transform: rotate(25deg) translate(-90px, -40px);
width: 300px;
z-index: -1;
}
`
const Header = styled.h2`
font-weight: 600;
font-size: 20px;
margin: 0;
padding-right: 30px;
`
const Body = styled.p`
font-size: 12px;
grid-column: 1 / 3;
line-height: 143%;
margin: 0;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
grid-column: 2 / 3;
}
`
const LinkOutCircle = styled(ArrowDownCircle)`
transform: rotate(230deg);
width: 20px;
height: 20px;
`
const LinkOutToBridge = styled(ExternalLink)`
align-items: center;
background-color: black;
border-radius: 16px;
color: white;
display: flex;
font-size: 16px;
height: 44px;
justify-content: space-between;
margin: 0 20px 20px 20px;
padding: 12px 16px;
text-decoration: none;
width: auto;
:hover,
:focus,
:active {
background-color: black;
}
`
export function NetworkAlert() {
const { account, chainId } = useActiveWeb3React()
const [darkMode] = useDarkModeManager()
const [arbitrumAlphaAcknowledged, setArbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
const [locallyDismissed, setLocallyDimissed] = useState(false)
const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? '']
const dismiss = useCallback(() => {
if (userEthBalance?.greaterThan(0)) {
setArbitrumAlphaAcknowledged(true)
} else {
setLocallyDimissed(true)
}
}, [setArbitrumAlphaAcknowledged, userEthBalance])
if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged || locallyDismissed) {
return null
}
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
const depositUrl = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
? `${info.bridge}?chainId=1`
: info.bridge
return (
<RootWrapper chainId={chainId} darkMode={darkMode} logoUrl={info.logoUrl}>
<CloseIcon onClick={dismiss} />
<ContentWrapper>
<L2Icon src={info.logoUrl} />
<Header>
<Trans>Uniswap on {info.label}</Trans>
</Header>
<Body>
<Trans>
This is an alpha release of Uniswap on the {info.label} network. You must bridge L1 assets to the network to
swap them.
</Trans>
</Body>
</ContentWrapper>
<LinkOutToBridge href={depositUrl}>
<Trans>Deposit to {info.label}</Trans>
<LinkOutCircle />
</LinkOutToBridge>
</RootWrapper>
)
}

View File

@@ -0,0 +1,63 @@
import { Trans } from '@lingui/macro'
import { SupportedChainId } from 'constants/chains'
import { useActiveWeb3React } from 'hooks/web3'
import { AlertOctagon } from 'react-feather'
import styled from 'styled-components/macro'
const Root = styled.div`
background-color: ${({ theme }) => theme.yellow3};
border-radius: 18px;
color: black;
margin-top: 16px;
padding: 16px;
width: 100%;
`
const WarningIcon = styled(AlertOctagon)`
margin: 0 8px 0 0;
`
const TitleRow = styled.div`
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-start;
margin: 0;
font-size: 20px;
font-weight: 600;
line-height: 25px;
`
const Body = styled.div`
font-size: 12px;
line-height: 15px;
margin: 8px 0 0 0;
`
const LinkOutToNotion = styled.a`
color: black;
`
export default function OptimismDowntimeWarning() {
const { chainId } = useActiveWeb3React()
if (!chainId || ![SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)) {
return null
}
return (
<Root>
<TitleRow>
<WarningIcon />
<Trans>{'Optimism'} Scheduled Downtimes</Trans>
</TitleRow>
<Body>
<Trans>
{'Optimism'} expects some scheduled downtime in the near future.&nbsp;
<LinkOutToNotion
href={`https://www.notion.so/Optimism-Regenesis-Schedule-8d14a34902ca4f5a8910762b3ec4b8da`}
target="_blank"
rel="noopener noreferrer"
>
Read more.
</LinkOutToNotion>
</Trans>
</Body>
</Root>
)
}

View File

@@ -1,9 +1,9 @@
import { Trans } from '@lingui/macro'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import React, { useCallback, useEffect } from 'react'
import { useCallback, useEffect } from 'react'
import ReactGA from 'react-ga'
import { Heart, X } from 'react-feather'
import styled, { keyframes } from 'styled-components'
import styled, { keyframes } from 'styled-components/macro'
import tokenLogo from '../../assets/images/token-logo.png'
import { ButtonPrimary } from '../../components/Button'
import { useActiveWeb3React } from '../../hooks/web3'
@@ -117,7 +117,7 @@ export default function ClaimPopup() {
</TYPE.subHeader>
</AutoColumn>
<AutoColumn style={{ zIndex: 10 }} justify="center">
<ButtonPrimary padding="8px" borderRadius="8px" width={'fit-content'} onClick={handleToggleSelfClaimModal}>
<ButtonPrimary padding="8px" $borderRadius="8px" width={'fit-content'} onClick={handleToggleSelfClaimModal}>
<Trans>Claim your UNI tokens</Trans>
</ButtonPrimary>
</AutoColumn>

View File

@@ -1,13 +1,13 @@
import React, { useCallback, useContext, useEffect } from 'react'
import { useCallback, useContext, useEffect } from 'react'
import { X } from 'react-feather'
import { useSpring } from 'react-spring/web'
import styled, { ThemeContext } from 'styled-components'
import styled, { ThemeContext } from 'styled-components/macro'
import { animated } from 'react-spring'
import { PopupContent } from '../../state/application/actions'
import { useRemovePopup } from '../../state/application/hooks'
import TransactionPopup from './TransactionPopup'
export const StyledClose = styled(X)`
const StyledClose = styled(X)`
position: absolute;
right: 10px;
top: 10px;
@@ -16,7 +16,7 @@ export const StyledClose = styled(X)`
cursor: pointer;
}
`
export const Popup = styled.div`
const Popup = styled.div`
display: inline-block;
width: 100%;
padding: 1em;

View File

@@ -1,6 +1,6 @@
import React, { useContext } from 'react'
import { useContext } from 'react'
import { AlertCircle, CheckCircle } from 'react-feather'
import styled, { ThemeContext } from 'styled-components'
import styled, { ThemeContext } from 'styled-components/macro'
import { useActiveWeb3React } from '../../hooks/web3'
import { TYPE } from '../../theme'
import { ExternalLink } from '../../theme/components'

View File

@@ -1,10 +1,12 @@
import React from 'react'
import styled from 'styled-components/macro'
import { useActivePopups } from '../../state/application/hooks'
import { AutoColumn } from '../Column'
import PopupItem from './PopupItem'
import ClaimPopup from './ClaimPopup'
import { useURLWarningVisible } from '../../state/user/hooks'
import { useActiveWeb3React } from 'hooks/web3'
import { SupportedChainId } from 'constants/chains'
import { MEDIA_WIDTHS } from 'theme'
const MobilePopupWrapper = styled.div<{ height: string | number }>`
position: relative;
@@ -31,9 +33,13 @@ const MobilePopupInner = styled.div`
}
`
const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>`
const StopOverflowQuery = `@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium + 1}px) and (max-width: ${
MEDIA_WIDTHS.upToMedium + 500
}px)`
const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean; xlPadding: boolean }>`
position: fixed;
top: ${({ extraPadding }) => (extraPadding ? '80px' : '88px')};
top: ${({ extraPadding }) => (extraPadding ? '64px' : '56px')};
right: 1rem;
max-width: 355px !important;
width: 100%;
@@ -42,6 +48,10 @@ const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>`
${({ theme }) => theme.mediaWidth.upToSmall`
display: none;
`};
${StopOverflowQuery} {
top: ${({ extraPadding, xlPadding }) => (xlPadding ? '64px' : extraPadding ? '64px' : '56px')};
}
`
export default function Popups() {
@@ -50,9 +60,13 @@ export default function Popups() {
const urlWarningActive = useURLWarningVisible()
// need extra padding if network is not L1 Ethereum
const { chainId } = useActiveWeb3React()
const isNotOnMainnet = Boolean(chainId && chainId !== SupportedChainId.MAINNET)
return (
<>
<FixedPopupColumn gap="20px" extraPadding={urlWarningActive}>
<FixedPopupColumn gap="20px" extraPadding={urlWarningActive} xlPadding={isNotOnMainnet}>
<ClaimPopup />
{activePopups.map((item) => (
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />

View File

@@ -1,4 +1,3 @@
import React from 'react'
import { Token } from '@uniswap/sdk-core'
import { Link } from 'react-router-dom'
import { Text } from 'rebass'
@@ -63,7 +62,7 @@ export default function SushiPositionCard({ tokenA, tokenB, liquidityToken, bord
<RowFixed gap="8px">
<ButtonEmpty
padding="0px 35px 0px 0px"
borderRadius="12px"
$borderRadius="12px"
width="fit-content"
as={Link}
to={`/migrate/v2/${liquidityToken.address}`}

View File

@@ -1,5 +1,5 @@
import JSBI from 'jsbi'
import React, { useState } from 'react'
import { useState } from 'react'
import { Percent, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { ChevronDown, ChevronUp } from 'react-feather'
@@ -98,7 +98,7 @@ export default function V2PositionCard({ pair, border, stakedBalance }: Position
<RowFixed gap="8px">
<ButtonEmpty
padding="6px 8px"
borderRadius="12px"
$borderRadius="12px"
width="fit-content"
onClick={() => setShowMore(!showMore)}
>
@@ -188,7 +188,7 @@ export default function V2PositionCard({ pair, border, stakedBalance }: Position
<RowBetween marginTop="10px">
<ButtonPrimary
padding="8px"
borderRadius="8px"
$borderRadius="8px"
as={Link}
to={`/migrate/v2/${pair.liquidityToken.address}`}
width="64%"
@@ -197,7 +197,7 @@ export default function V2PositionCard({ pair, border, stakedBalance }: Position
</ButtonPrimary>
<ButtonSecondary
padding="8px"
borderRadius="8px"
$borderRadius="8px"
as={Link}
width="32%"
to={`/remove/v2/${currencyId(currency0)}/${currencyId(currency1)}`}

View File

@@ -1,8 +1,7 @@
import JSBI from 'jsbi'
import { Percent, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { darken } from 'polished'
import React, { useState } from 'react'
import { useState } from 'react'
import { ChevronDown, ChevronUp } from 'react-feather'
import { Link } from 'react-router-dom'
import { Text } from 'rebass'
@@ -21,7 +20,7 @@ import { CardNoise } from '../earn/styled'
import { useColor } from '../../hooks/useColor'
import Card, { GreyCard, LightCard } from '../Card'
import { GreyCard, LightCard } from '../Card'
import { AutoColumn } from '../Column'
import CurrencyLogo from '../CurrencyLogo'
import DoubleCurrencyLogo from '../DoubleLogo'
@@ -33,12 +32,6 @@ export const FixedHeightRow = styled(RowBetween)`
height: 24px;
`
export const HoverCard = styled(Card)`
border: 1px solid transparent;
:hover {
border: 1px solid ${({ theme }) => darken(0.06, theme.bg2)};
}
`
const StyledPositionCard = styled(LightCard)<{ bgColor: any }>`
border: none;
background: ${({ theme, bgColor }) =>
@@ -219,7 +212,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
</Text>
</AutoRow>
<RowFixed gap="8px" style={{ marginRight: '4px' }}>
<ButtonEmpty padding="6px 8px" borderRadius="12px" width="100%" onClick={() => setShowMore(!showMore)}>
<ButtonEmpty padding="6px 8px" $borderRadius="12px" width="100%" onClick={() => setShowMore(!showMore)}>
{showMore ? (
<>
<Trans>Manage</Trans>
@@ -306,7 +299,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
</Text>
</FixedHeightRow>
<ButtonSecondary padding="8px" borderRadius="8px">
<ButtonSecondary padding="8px" $borderRadius="8px">
<ExternalLink
style={{ width: '100%', textAlign: 'center' }}
href={`https://v2.info.uniswap.org/account/${account}`}
@@ -320,7 +313,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
<RowBetween marginTop="10px">
<ButtonPrimary
padding="8px"
borderRadius="8px"
$borderRadius="8px"
as={Link}
to={`/migrate/v2/${pair.liquidityToken.address}`}
width="32%"
@@ -329,7 +322,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
</ButtonPrimary>
<ButtonPrimary
padding="8px"
borderRadius="8px"
$borderRadius="8px"
as={Link}
to={`/add/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
width="32%"
@@ -338,7 +331,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
</ButtonPrimary>
<ButtonPrimary
padding="8px"
borderRadius="8px"
$borderRadius="8px"
as={Link}
width="32%"
to={`/remove/v2/${currencyId(currency0)}/${currencyId(currency1)}`}
@@ -350,7 +343,7 @@ export default function FullPositionCard({ pair, border, stakedBalance }: Positi
{stakedBalance && JSBI.greaterThan(stakedBalance.quotient, BIG_INT_ZERO) && (
<ButtonPrimary
padding="8px"
borderRadius="8px"
$borderRadius="8px"
as={Link}
to={`/uni/${currencyId(currency0)}/${currencyId(currency1)}`}
width="100%"

View File

@@ -34,7 +34,7 @@ const MobileHeader = styled.div`
}
`
export type PositionListProps = React.PropsWithChildren<{
type PositionListProps = React.PropsWithChildren<{
positions: PositionDetails[]
}>
@@ -47,7 +47,7 @@ export default function PositionList({ positions }: PositionListProps) {
{positions && ' (' + positions.length + ')'}
</div>
<div>
<Trans>Price range</Trans>
<Trans>Status</Trans>
</div>
</DesktopHeader>
<MobileHeader>

View File

@@ -1,4 +1,4 @@
import React, { useMemo } from 'react'
import { useMemo } from 'react'
import { Position } from '@uniswap/v3-sdk'
import Badge from 'components/Badge'
import DoubleCurrencyLogo from 'components/DoubleLogo'
@@ -9,14 +9,16 @@ import styled from 'styled-components/macro'
import { HideSmall, MEDIA_WIDTHS, SmallOnly } from 'theme'
import { PositionDetails } from 'types/position'
import { Price, Token, Percent } from '@uniswap/sdk-core'
import { formatPrice } from 'utils/formatCurrencyAmount'
import { formatTickPrice } from 'utils/formatTickPrice'
import Loader from 'components/Loader'
import { unwrappedToken } from 'utils/unwrappedToken'
import RangeBadge from 'components/Badge/RangeBadge'
import { RowFixed } from 'components/Row'
import { RowBetween } from 'components/Row'
import HoverInlineText from 'components/HoverInlineText'
import { DAI, USDC, USDT, WBTC, WETH9_EXTENDED } from '../../constants/tokens'
import { Trans } from '@lingui/macro'
import useIsTickAtLimit from 'hooks/useIsTickAtLimit'
import { Bound } from 'state/mint/v3/actions'
const LinkRow = styled(Link)`
align-items: center;
@@ -24,6 +26,9 @@ const LinkRow = styled(Link)`
display: flex;
cursor: pointer;
user-select: none;
display: flex;
flex-direction: column;
justify-content: space-between;
color: ${({ theme }) => theme.text1};
margin: 8px 0;
@@ -32,25 +37,23 @@ const LinkRow = styled(Link)`
font-weight: 500;
background-color: ${({ theme }) => theme.bg1};
&:first-of-type {
margin: 0 0 8px 0;
}
&:last-of-type {
margin: 8px 0 0 0;
}
& > div:not(:first-child) {
text-align: right;
text-align: center;
}
:hover {
background-color: ${({ theme }) => theme.bg2};
}
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
flex-direction: row;
/* flex-direction: row; */
}
${({ theme }) => theme.mediaWidth.upToSmall`
flex-direction: column;
row-gap: 24px;
row-gap: 12px;
`};
`
@@ -70,11 +73,14 @@ const RangeLineItem = styled(DataLineItem)`
display: flex;
flex-direction: row;
align-items: center;
justify-self: flex-end;
margin-top: 4px;
width: 100%;
${({ theme }) => theme.mediaWidth.upToSmall`
flex-direction: column;
row-gap: 4px;
background-color: ${({ theme }) => theme.bg2};
border-radius: 12px;
padding: 8px 0;
`};
`
@@ -97,6 +103,9 @@ const ExtentsText = styled.span`
color: ${({ theme }) => theme.text3};
font-size: 14px;
margin-right: 4px;
${({ theme }) => theme.mediaWidth.upToSmall`
display: none;
`};
`
const PrimaryPositionIdData = styled.div`
@@ -117,7 +126,7 @@ const DataText = styled.div`
`};
`
export interface PositionListItemProps {
interface PositionListItemProps {
positionDetails: PositionDetails
}
@@ -201,6 +210,8 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
return undefined
}, [liquidity, pool, tickLower, tickUpper])
const tickAtLimit = useIsTickAtLimit(feeAmount, tickLower, tickUpper)
// prices
const { priceLower, priceUpper, quote, base } = getPriceOrderingFromPositionForUI(position)
@@ -216,7 +227,7 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
return (
<LinkRow to={positionSummaryLink}>
<RowFixed>
<RowBetween>
<PrimaryPositionIdData>
<DoubleCurrencyLogo currency0={currencyBase} currency1={currencyQuote} size={18} margin />
<DataText>
@@ -230,7 +241,7 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
</Badge>
</PrimaryPositionIdData>
<RangeBadge removed={removed} inRange={!outOfRange} />
</RowFixed>
</RowBetween>
{priceLower && priceUpper ? (
<RangeLineItem>
@@ -239,23 +250,23 @@ export default function PositionListItem({ positionDetails }: PositionListItemPr
<Trans>Min: </Trans>
</ExtentsText>
<Trans>
{formatPrice(priceLower, 5)} <HoverInlineText text={currencyQuote?.symbol} /> per{' '}
<HoverInlineText text={currencyBase?.symbol ?? ''} />
{formatTickPrice(priceLower, tickAtLimit, Bound.LOWER)} <HoverInlineText text={currencyQuote?.symbol} />{' '}
per <HoverInlineText text={currencyBase?.symbol ?? ''} />
</Trans>
</RangeText>{' '}
<HideSmall>
<DoubleArrow></DoubleArrow>{' '}
</HideSmall>
<SmallOnly>
<DoubleArrow></DoubleArrow>{' '}
<DoubleArrow></DoubleArrow>{' '}
</SmallOnly>
<RangeText>
<ExtentsText>
<Trans>Max:</Trans>
</ExtentsText>
<Trans>
{formatPrice(priceUpper, 5)} <HoverInlineText text={currencyQuote?.symbol} /> per{' '}
<HoverInlineText maxCharacters={10} text={currencyBase?.symbol} />
{formatTickPrice(priceUpper, tickAtLimit, Bound.UPPER)} <HoverInlineText text={currencyQuote?.symbol} />{' '}
per <HoverInlineText maxCharacters={10} text={currencyBase?.symbol} />
</Trans>
</RangeText>
</RangeLineItem>

View File

@@ -1,4 +1,4 @@
import React, { useState, useCallback, useContext, ReactNode } from 'react'
import { useState, useCallback, useContext, ReactNode } from 'react'
import { Position } from '@uniswap/v3-sdk'
import { LightCard } from 'components/Card'
import { AutoColumn } from 'components/Column'
@@ -12,19 +12,23 @@ import { Currency } from '@uniswap/sdk-core'
import RateToggle from 'components/RateToggle'
import DoubleCurrencyLogo from 'components/DoubleLogo'
import RangeBadge from 'components/Badge/RangeBadge'
import { ThemeContext } from 'styled-components'
import { ThemeContext } from 'styled-components/macro'
import JSBI from 'jsbi'
import { Bound } from 'state/mint/v3/actions'
import { formatTickPrice } from 'utils/formatTickPrice'
export const PositionPreview = ({
position,
title,
inRange,
baseCurrencyDefault,
ticksAtLimit,
}: {
position: Position
title?: ReactNode
inRange: boolean
baseCurrencyDefault?: Currency | undefined
ticksAtLimit: { [bound: string]: boolean | undefined }
}) => {
const theme = useContext(ThemeContext)
@@ -121,7 +125,11 @@ export const PositionPreview = ({
<TYPE.main fontSize="12px">
<Trans>Min Price</Trans>
</TYPE.main>
<TYPE.mediumHeader textAlign="center">{`${priceLower.toSignificant(5)}`}</TYPE.mediumHeader>
<TYPE.mediumHeader textAlign="center">{`${formatTickPrice(
priceLower,
ticksAtLimit,
Bound.LOWER
)}`}</TYPE.mediumHeader>
<TYPE.main textAlign="center" fontSize="12px">
<Trans>
{quoteCurrency.symbol} per {baseCurrency.symbol}
@@ -138,7 +146,11 @@ export const PositionPreview = ({
<TYPE.main fontSize="12px">
<Trans>Max Price</Trans>
</TYPE.main>
<TYPE.mediumHeader textAlign="center">{`${priceUpper.toSignificant(5)}`}</TYPE.mediumHeader>
<TYPE.mediumHeader textAlign="center">{`${formatTickPrice(
priceUpper,
ticksAtLimit,
Bound.UPPER
)}`}</TYPE.mediumHeader>
<TYPE.main textAlign="center" fontSize="12px">
<Trans>
{quoteCurrency.symbol} per {baseCurrency.symbol}

View File

@@ -1,7 +1,7 @@
import React, { useContext } from 'react'
import { useContext } from 'react'
import styled from 'styled-components/macro'
import { AutoColumn } from '../Column'
import { ThemeContext } from 'styled-components'
import { ThemeContext } from 'styled-components/macro'
import { TYPE } from '../../theme'
const Wrapper = styled(AutoColumn)`

View File

@@ -1,4 +1,4 @@
import React, { ReactNode, useCallback, useState } from 'react'
import { ReactNode, useCallback, useState } from 'react'
import styled from 'styled-components/macro'
import Tooltip from '../Tooltip'

View File

@@ -0,0 +1,84 @@
import React from 'react'
import { ButtonOutlined } from 'components/Button'
import { AutoRow } from 'components/Row'
import { TYPE } from 'theme'
import styled from 'styled-components/macro'
import { Trans } from '@lingui/macro'
import { FeeAmount } from '@uniswap/v3-sdk'
import ReactGA from 'react-ga'
const Button = styled(ButtonOutlined).attrs(() => ({
padding: '4px',
borderRadius: '8px',
}))`
color: ${({ theme }) => theme.text1};
flex: 1;
background-color: ${({ theme }) => theme.bg2};
`
const RANGES = {
[FeeAmount.LOW]: [
{ label: '0.05', ticks: 5 },
{ label: '0.1', ticks: 10 },
{ label: '0.2', ticks: 20 },
],
[FeeAmount.MEDIUM]: [
{ label: '1', ticks: 100 },
{ label: '10', ticks: 953 },
{ label: '50', ticks: 4055 },
],
[FeeAmount.HIGH]: [
{ label: '2', ticks: 198 },
{ label: '10', ticks: 953 },
{ label: '80', ticks: 5878 },
],
}
interface PresetsButtonProps {
feeAmount: FeeAmount | undefined
setRange: (numTicks: number) => void
setFullRange: () => void
}
const PresetButton = ({
values: { label, ticks },
setRange,
}: {
values: {
label: string
ticks: number
}
setRange: (numTicks: number) => void
}) => (
<Button
onClick={() => {
setRange(ticks)
ReactGA.event({
category: 'Liquidity',
action: 'Preset clicked',
label: label,
})
}}
>
<TYPE.body fontSize={12}>
<Trans>+/- {label}%</Trans>
</TYPE.body>
</Button>
)
export default function PresetsButtons({ feeAmount, setRange, setFullRange }: PresetsButtonProps) {
feeAmount = feeAmount ?? FeeAmount.LOW
return (
<AutoRow gap="4px" width="auto">
<PresetButton values={RANGES[feeAmount][0]} setRange={setRange} />
<PresetButton values={RANGES[feeAmount][1]} setRange={setRange} />
<PresetButton values={RANGES[feeAmount][2]} setRange={setRange} />
<Button onClick={() => setFullRange()}>
<TYPE.body fontSize={12}>
<Trans>Full Range</Trans>
</TYPE.body>
</Button>
</AutoRow>
)
}

View File

@@ -1,8 +1,9 @@
import { Trans } from '@lingui/macro'
import React from 'react'
import { Currency, Price, Token } from '@uniswap/sdk-core'
import StepCounter from 'components/InputStepCounter/InputStepCounter'
import { RowBetween } from 'components/Row'
import { AutoColumn } from 'components/Column'
import { Bound } from 'state/mint/v3/actions'
// currencyA is the base token
export default function RangeSelector({
@@ -17,6 +18,7 @@ export default function RangeSelector({
currencyA,
currencyB,
feeAmount,
ticksAtLimit,
}: {
priceLower?: Price<Token, Token>
priceUpper?: Price<Token, Token>
@@ -29,6 +31,7 @@ export default function RangeSelector({
currencyA?: Currency | null
currencyB?: Currency | null
feeAmount?: number
ticksAtLimit: { [bound in Bound]?: boolean | undefined }
}) {
const tokenA = (currencyA ?? undefined)?.wrapped
const tokenB = (currencyB ?? undefined)?.wrapped
@@ -38,31 +41,37 @@ export default function RangeSelector({
const rightPrice = isSorted ? priceUpper : priceLower?.invert()
return (
<RowBetween>
<StepCounter
value={leftPrice?.toSignificant(5) ?? ''}
onUserInput={onLeftRangeInput}
width="48%"
decrement={isSorted ? getDecrementLower : getIncrementUpper}
increment={isSorted ? getIncrementLower : getDecrementUpper}
feeAmount={feeAmount}
label={leftPrice ? `${currencyB?.symbol}` : '-'}
title={<Trans>Min Price</Trans>}
tokenA={currencyA?.symbol}
tokenB={currencyB?.symbol}
/>
<StepCounter
value={rightPrice?.toSignificant(5) ?? ''}
onUserInput={onRightRangeInput}
width="48%"
decrement={isSorted ? getDecrementUpper : getIncrementLower}
increment={isSorted ? getIncrementUpper : getDecrementLower}
feeAmount={feeAmount}
label={rightPrice ? `${currencyB?.symbol}` : '-'}
tokenA={currencyA?.symbol}
tokenB={currencyB?.symbol}
title={<Trans>Max Price</Trans>}
/>
</RowBetween>
<AutoColumn gap="md">
<RowBetween>
<StepCounter
value={ticksAtLimit[Bound.LOWER] ? '0' : leftPrice?.toSignificant(5) ?? ''}
onUserInput={onLeftRangeInput}
width="48%"
decrement={isSorted ? getDecrementLower : getIncrementUpper}
increment={isSorted ? getIncrementLower : getDecrementUpper}
decrementDisabled={ticksAtLimit[Bound.LOWER]}
incrementDisabled={ticksAtLimit[Bound.LOWER]}
feeAmount={feeAmount}
label={leftPrice ? `${currencyB?.symbol}` : '-'}
title={<Trans>Min Price</Trans>}
tokenA={currencyA?.symbol}
tokenB={currencyB?.symbol}
/>
<StepCounter
value={ticksAtLimit[Bound.UPPER] ? '∞' : rightPrice?.toSignificant(5) ?? ''}
onUserInput={onRightRangeInput}
width="48%"
decrement={isSorted ? getDecrementUpper : getIncrementLower}
increment={isSorted ? getIncrementUpper : getDecrementLower}
incrementDisabled={ticksAtLimit[Bound.UPPER]}
decrementDisabled={ticksAtLimit[Bound.UPPER]}
feeAmount={feeAmount}
label={rightPrice ? `${currencyB?.symbol}` : '-'}
tokenA={currencyA?.symbol}
tokenB={currencyB?.symbol}
title={<Trans>Max Price</Trans>}
/>
</RowBetween>
</AutoColumn>
)
}

View File

@@ -1,5 +1,4 @@
import { Trans } from '@lingui/macro'
import React from 'react'
import { Currency } from '@uniswap/sdk-core'
import { ToggleElement, ToggleWrapper } from 'components/Toggle/MultiToggle'
@@ -23,10 +22,10 @@ export default function RateToggle({
<div style={{ width: 'fit-content', display: 'flex', alignItems: 'center' }} onClick={handleRateToggle}>
<ToggleWrapper width="fit-content">
<ToggleElement isActive={isSorted} fontSize="12px">
<Trans>{isSorted ? currencyA.symbol : currencyB.symbol} price</Trans>
<Trans>{isSorted ? currencyA.symbol : currencyB.symbol}</Trans>
</ToggleElement>
<ToggleElement isActive={!isSorted} fontSize="12px">
<Trans>{isSorted ? currencyB.symbol : currencyA.symbol} price</Trans>
<Trans>{isSorted ? currencyB.symbol : currencyA.symbol}</Trans>
</ToggleElement>
</ToggleWrapper>
</div>

View File

@@ -1,5 +1,4 @@
import { Trans } from '@lingui/macro'
import React from 'react'
import { Text } from 'rebass'
import { Currency } from '@uniswap/sdk-core'
import styled from 'styled-components/macro'
@@ -11,6 +10,12 @@ import QuestionHelper from '../QuestionHelper'
import { AutoRow } from '../Row'
import CurrencyLogo from '../CurrencyLogo'
const MobileWrapper = styled(AutoColumn)`
${({ theme }) => theme.mediaWidth.upToSmall`
display: none;
`};
`
const BaseWrapper = styled.div<{ disable?: boolean }>`
border: 1px solid ${({ theme, disable }) => (disable ? 'transparent' : theme.bg3)};
border-radius: 10px;
@@ -23,8 +28,9 @@ const BaseWrapper = styled.div<{ disable?: boolean }>`
background-color: ${({ theme, disable }) => !disable && theme.bg2};
}
color: ${({ theme, disable }) => disable && theme.text3};
background-color: ${({ theme, disable }) => disable && theme.bg3};
opacity: ${({ disable }) => disable && '0.4'};
filter: ${({ disable }) => disable && 'grayscale(1)'};
`
export default function CommonBases({
@@ -39,7 +45,7 @@ export default function CommonBases({
const bases = typeof chainId !== 'undefined' ? COMMON_BASES[chainId] ?? [] : []
return bases.length > 0 ? (
<AutoColumn gap="md">
<MobileWrapper gap="md">
<AutoRow>
<Text fontWeight={500} fontSize={14}>
<Trans>Common bases</Trans>
@@ -63,6 +69,6 @@ export default function CommonBases({
)
})}
</AutoRow>
</AutoColumn>
</MobileWrapper>
) : null
}

View File

@@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import React, { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'
import { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'
import { FixedSizeList } from 'react-window'
import { Text } from 'rebass'
import styled from 'styled-components/macro'
@@ -104,12 +104,14 @@ function CurrencyRow({
isSelected,
otherSelected,
style,
showCurrencyAmount,
}: {
currency: Currency
onSelect: () => void
isSelected: boolean
otherSelected: boolean
style: CSSProperties
showCurrencyAmount?: boolean
}) {
const { account } = useActiveWeb3React()
const key = currencyKey(currency)
@@ -141,9 +143,11 @@ function CurrencyRow({
</TYPE.darkGray>
</Column>
<TokenTags currency={currency} />
<RowFixed style={{ justifySelf: 'flex-end' }}>
{balance ? <Balance balance={balance} /> : account ? <Loader /> : null}
</RowFixed>
{showCurrencyAmount && (
<RowFixed style={{ justifySelf: 'flex-end' }}>
{balance ? <Balance balance={balance} /> : account ? <Loader /> : null}
</RowFixed>
)}
</MenuItem>
)
}
@@ -158,7 +162,7 @@ function BreakLineComponent({ style }: { style: CSSProperties }) {
const theme = useTheme()
return (
<FixedContentRow style={style}>
<LightGreyCard padding="8px 12px" borderRadius="8px">
<LightGreyCard padding="8px 12px" $borderRadius="8px">
<RowBetween>
<RowFixed>
<TokenListLogoWrapper src={TokenListLogo} />
@@ -189,6 +193,7 @@ export default function CurrencyList({
fixedListRef,
showImportView,
setImportToken,
showCurrencyAmount,
}: {
height: number
currencies: Currency[]
@@ -199,6 +204,7 @@ export default function CurrencyList({
fixedListRef?: MutableRefObject<FixedSizeList | undefined>
showImportView: () => void
setImportToken: (token: Token) => void
showCurrencyAmount?: boolean
}) {
const itemData: (Currency | BreakLine)[] = useMemo(() => {
if (otherListTokens && otherListTokens?.length > 0) {
@@ -237,13 +243,22 @@ export default function CurrencyList({
isSelected={isSelected}
onSelect={handleSelect}
otherSelected={otherSelected}
showCurrencyAmount={showCurrencyAmount}
/>
)
} else {
return null
}
},
[currencies.length, onCurrencySelect, otherCurrency, selectedCurrency, setImportToken, showImportView]
[
currencies.length,
onCurrencySelect,
otherCurrency,
selectedCurrency,
setImportToken,
showImportView,
showCurrencyAmount,
]
)
const itemKey = useCallback((index: number, data: typeof itemData) => {

View File

@@ -1,5 +1,5 @@
import { Currency, Token } from '@uniswap/sdk-core'
import React, { KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import ReactGA from 'react-ga'
import { t, Trans } from '@lingui/macro'
import { FixedSizeList } from 'react-window'
@@ -48,6 +48,8 @@ interface CurrencySearchProps {
onCurrencySelect: (currency: Currency) => void
otherSelectedCurrency?: Currency | null
showCommonBases?: boolean
showCurrencyAmount?: boolean
disableNonToken?: boolean
showManageView: () => void
showImportView: () => void
setImportToken: (token: Token) => void
@@ -58,6 +60,8 @@ export function CurrencySearch({
onCurrencySelect,
otherSelectedCurrency,
showCommonBases,
showCurrencyAmount,
disableNonToken,
onDismiss,
isOpen,
showManageView,
@@ -203,7 +207,7 @@ export function CurrencySearch({
{({ height }) => (
<CurrencyList
height={height}
currencies={filteredSortedTokensWithETH}
currencies={disableNonToken ? filteredSortedTokens : filteredSortedTokensWithETH}
otherListTokens={filteredInactiveTokens}
onCurrencySelect={handleCurrencySelect}
otherCurrency={otherSelectedCurrency}
@@ -211,6 +215,7 @@ export function CurrencySearch({
fixedListRef={fixedList}
showImportView={showImportView}
setImportToken={setImportToken}
showCurrencyAmount={showCurrencyAmount}
/>
)}
</AutoSizer>
@@ -224,12 +229,12 @@ export function CurrencySearch({
)}
<Footer>
<Row justify="center">
<ButtonText onClick={showManageView} color={theme.blue1} className="list-token-manage-button">
<ButtonText onClick={showManageView} color={theme.primary1} className="list-token-manage-button">
<RowFixed>
<IconWrapper size="16px" marginRight="6px">
<IconWrapper size="16px" marginRight="6px" stroke={theme.primaryText1}>
<Edit />
</IconWrapper>
<TYPE.main color={theme.blue1}>
<TYPE.main color={theme.primaryText1}>
<Trans>Manage Token Lists</Trans>
</TYPE.main>
</RowFixed>

View File

@@ -1,5 +1,5 @@
import { Currency, Token } from '@uniswap/sdk-core'
import React, { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import useLast from '../../hooks/useLast'
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
import Modal from '../Modal'
@@ -17,6 +17,8 @@ interface CurrencySearchModalProps {
onCurrencySelect: (currency: Currency) => void
otherSelectedCurrency?: Currency | null
showCommonBases?: boolean
showCurrencyAmount?: boolean
disableNonToken?: boolean
}
export enum CurrencyModalView {
@@ -33,6 +35,8 @@ export default function CurrencySearchModal({
selectedCurrency,
otherSelectedCurrency,
showCommonBases = false,
showCurrencyAmount = true,
disableNonToken = false,
}: CurrencySearchModalProps) {
const [modalView, setModalView] = useState<CurrencyModalView>(CurrencyModalView.manage)
const lastOpen = useLast(isOpen)
@@ -74,6 +78,8 @@ export default function CurrencySearchModal({
selectedCurrency={selectedCurrency}
otherSelectedCurrency={otherSelectedCurrency}
showCommonBases={showCommonBases}
showCurrencyAmount={showCurrencyAmount}
disableNonToken={disableNonToken}
showImportView={() => setModalView(CurrencyModalView.importToken)}
setImportToken={setImportToken}
showManageView={() => setModalView(CurrencyModalView.manage)}

View File

@@ -1,4 +1,4 @@
import React, { useState, useCallback } from 'react'
import { useState, useCallback } from 'react'
import styled from 'styled-components/macro'
import ReactGA from 'react-ga'
import { TYPE, CloseIcon } from 'theme'
@@ -149,7 +149,7 @@ export function ImportList({ listURL, list, setModalView, onDismiss }: ImportPro
<ButtonPrimary
disabled={!confirmed}
altDisabledStyle={true}
borderRadius="20px"
$borderRadius="20px"
padding="10px 1rem"
onClick={handleAddList}
>

View File

@@ -1,4 +1,4 @@
import React, { CSSProperties } from 'react'
import { CSSProperties } from 'react'
import { Token } from '@uniswap/sdk-core'
import { AutoRow, RowFixed } from 'components/Row'
import { AutoColumn } from 'components/Column'

View File

@@ -1,5 +1,4 @@
import { TokenList } from '@uniswap/token-lists/dist/types'
import React from 'react'
import { Token, Currency } from '@uniswap/sdk-core'
import styled from 'styled-components/macro'
import { TYPE, CloseIcon } from 'theme'
@@ -34,10 +33,11 @@ const WarningWrapper = styled(Card)<{ highWarning: boolean }>`
const AddressText = styled(TYPE.blue)`
font-size: 12px;
word-break: break-all;
${({ theme }) => theme.mediaWidth.upToSmall`
font-size: 10px;
`}
`}
`
interface ImportProps {
@@ -109,7 +109,7 @@ export function ImportToken({ tokens, list, onBack, onDismiss, handleCurrencySel
</TYPE.small>
</RowFixed>
) : (
<WarningWrapper borderRadius="4px" padding="4px" highWarning={true}>
<WarningWrapper $borderRadius="4px" padding="4px" highWarning={true}>
<RowFixed>
<AlertCircle stroke={theme.red1} size="10px" />
<TYPE.body color={theme.red1} ml="4px" fontSize="10px" fontWeight={500}>
@@ -125,7 +125,7 @@ export function ImportToken({ tokens, list, onBack, onDismiss, handleCurrencySel
<ButtonPrimary
altDisabledStyle={true}
borderRadius="20px"
$borderRadius="20px"
padding="10px 1rem"
onClick={() => {
tokens.map((token) => addToken(token))

View File

@@ -1,5 +1,5 @@
import { Trans } from '@lingui/macro'
import React, { useState } from 'react'
import { useState } from 'react'
import { PaddedColumn, Separator } from './styleds'
import { RowBetween } from 'components/Row'
import { ArrowLeft } from 'react-feather'

View File

@@ -1,36 +1,34 @@
import React, { memo, useCallback, useMemo, useRef, useState, useEffect } from 'react'
import { Settings, CheckCircle } from 'react-feather'
import { t, Trans } from '@lingui/macro'
import { TokenList } from '@uniswap/token-lists'
import Card from 'components/Card'
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
import { useListColor } from 'hooks/useColor'
import { useActiveWeb3React } from 'hooks/web3'
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { CheckCircle, Settings } from 'react-feather'
import ReactGA from 'react-ga'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { usePopper } from 'react-popper'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import styled from 'styled-components/macro'
import { useFetchListCallback } from '../../hooks/useFetchListCallback'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import { TokenList } from '@uniswap/token-lists'
import { t, Trans } from '@lingui/macro'
import useTheme from '../../hooks/useTheme'
import useToggle from '../../hooks/useToggle'
import { acceptListUpdate, removeList, disableList, enableList } from '../../state/lists/actions'
import { useIsListActive, useAllLists, useActiveListUrls } from '../../state/lists/hooks'
import { ExternalLink, LinkStyledButton, TYPE, IconWrapper } from '../../theme'
import { acceptListUpdate, disableList, enableList, removeList } from '../../state/lists/actions'
import { useActiveListUrls, useAllLists, useIsListActive } from '../../state/lists/hooks'
import { ExternalLink, IconWrapper, LinkStyledButton, TYPE } from '../../theme'
import listVersionLabel from '../../utils/listVersionLabel'
import { parseENSAddress } from '../../utils/parseENSAddress'
import uriToHttp from '../../utils/uriToHttp'
import { ButtonEmpty, ButtonPrimary } from '../Button'
import Column, { AutoColumn } from '../Column'
import ListLogo from '../ListLogo'
import Row, { RowFixed, RowBetween } from '../Row'
import { PaddedColumn, SearchInput, Separator, SeparatorDark } from './styleds'
import { useListColor } from 'hooks/useColor'
import useTheme from '../../hooks/useTheme'
import Row, { RowBetween, RowFixed } from '../Row'
import ListToggle from '../Toggle/ListToggle'
import Card from 'components/Card'
import { CurrencyModalView } from './CurrencySearchModal'
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
import { PaddedColumn, SearchInput, Separator, SeparatorDark } from './styleds'
const Wrapper = styled(Column)`
width: 100%;
height: 100%;
`
@@ -80,8 +78,9 @@ const StyledListUrlText = styled(TYPE.main)<{ active: boolean }>`
color: ${({ theme, active }) => (active ? theme.white : theme.text2)};
`
const RowWrapper = styled(Row)<{ bgColor: string; active: boolean }>`
const RowWrapper = styled(Row)<{ bgColor: string; active: boolean; hasActiveTokens: boolean }>`
background-color: ${({ bgColor, active, theme }) => (active ? bgColor ?? 'transparent' : theme.bg2)};
opacity: ${({ hasActiveTokens }) => (hasActiveTokens ? 1 : 0.4)};
transition: 200ms;
align-items: center;
padding: 1rem;
@@ -93,10 +92,18 @@ function listUrlRowHTMLId(listUrl: string) {
}
const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
const { chainId } = useActiveWeb3React()
const listsByUrl = useAppSelector((state) => state.lists.byUrl)
const dispatch = useAppDispatch()
const { current: list, pendingUpdate: pending } = listsByUrl[listUrl]
const activeTokensOnThisChain = useMemo(() => {
if (!list || !chainId) {
return 0
}
return list.tokens.reduce((acc, cur) => (cur.chainId === chainId ? acc + 1 : acc), 0)
}, [chainId, list])
const theme = useTheme()
const listColor = useListColor(list?.logoURI)
const isActive = useIsListActive(listUrl)
@@ -130,7 +137,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
action: 'Start Remove List',
label: listUrl,
})
if (window.prompt(`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) {
if (window.prompt(t`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) {
ReactGA.event({
category: 'Lists',
action: 'Confirm Remove List',
@@ -161,7 +168,13 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
if (!list) return null
return (
<RowWrapper active={isActive} bgColor={listColor} key={listUrl} id={listUrlRowHTMLId(listUrl)}>
<RowWrapper
active={isActive}
hasActiveTokens={activeTokensOnThisChain > 0}
bgColor={listColor}
key={listUrl}
id={listUrlRowHTMLId(listUrl)}
>
{list.logoURI ? (
<ListLogo size="40px" style={{ marginRight: '1rem' }} logoURI={list.logoURI} alt={`${list.name} list logo`} />
) : (
@@ -173,7 +186,7 @@ const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
</Row>
<RowFixed mt="4px">
<StyledListUrlText active={isActive} mr="6px">
<Trans>{list.tokens.length} tokens</Trans>
<Trans>{activeTokensOnThisChain} tokens</Trans>
</StyledListUrlText>
<StyledMenu ref={node as any}>
<ButtonEmpty onClick={toggle} ref={setReferenceElement} padding="0">
@@ -226,20 +239,29 @@ export function ManageLists({
setImportList: (list: TokenList) => void
setListUrl: (url: string) => void
}) {
const { chainId } = useActiveWeb3React()
const theme = useTheme()
const [listUrlInput, setListUrlInput] = useState<string>('')
const lists = useAllLists()
const tokenCountByListName = useMemo<Record<string, number>>(
() =>
Object.values(lists).reduce((acc, { current: list }) => {
if (!list) {
return acc
}
return {
...acc,
[list.name]: list.tokens.reduce((count: number, token) => (token.chainId === chainId ? count + 1 : count), 0),
}
}, {}),
[chainId, lists]
)
// sort by active but only if not visible
const activeListUrls = useActiveListUrls()
const [activeCopy, setActiveCopy] = useState<string[] | undefined>()
useEffect(() => {
if (!activeCopy && activeListUrls) {
setActiveCopy(activeListUrls)
}
}, [activeCopy, activeListUrls])
const handleInput = useCallback((e) => {
setListUrlInput(e.target.value)
@@ -258,30 +280,36 @@ export function ManageLists({
// only show loaded lists, hide unsupported lists
return Boolean(lists[listUrl].current) && !Boolean(UNSUPPORTED_LIST_URLS.includes(listUrl))
})
.sort((u1, u2) => {
const { current: l1 } = lists[u1]
const { current: l2 } = lists[u2]
.sort((listUrlA, listUrlB) => {
const { current: listA } = lists[listUrlA]
const { current: listB } = lists[listUrlB]
// first filter on active lists
if (activeCopy?.includes(u1) && !activeCopy?.includes(u2)) {
if (activeListUrls?.includes(listUrlA) && !activeListUrls?.includes(listUrlB)) {
return -1
}
if (!activeCopy?.includes(u1) && activeCopy?.includes(u2)) {
if (!activeListUrls?.includes(listUrlA) && activeListUrls?.includes(listUrlB)) {
return 1
}
if (l1 && l2) {
return l1.name.toLowerCase() < l2.name.toLowerCase()
if (listA && listB) {
if (tokenCountByListName[listA.name] > tokenCountByListName[listB.name]) {
return -1
}
if (tokenCountByListName[listA.name] < tokenCountByListName[listB.name]) {
return 1
}
return listA.name.toLowerCase() < listB.name.toLowerCase()
? -1
: l1.name.toLowerCase() === l2.name.toLowerCase()
: listA.name.toLowerCase() === listB.name.toLowerCase()
? 0
: 1
}
if (l1) return -1
if (l2) return 1
if (listA) return -1
if (listB) return 1
return 0
})
}, [lists, activeCopy])
}, [lists, activeListUrls, tokenCountByListName])
// temporary fetched list for import flow
const [tempList, setTempList] = useState<TokenList>()

View File

@@ -1,4 +1,4 @@
import React, { useRef, RefObject, useCallback, useState, useMemo } from 'react'
import { useRef, RefObject, useCallback, useState, useMemo } from 'react'
import Column from 'components/Column'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
import { PaddedColumn, Separator, SearchInput } from './styleds'

View File

@@ -1,34 +0,0 @@
import React from 'react'
import { Text } from 'rebass'
import styled from 'styled-components/macro'
import { RowFixed } from '../Row'
export const FilterWrapper = styled(RowFixed)`
padding: 8px;
background-color: ${({ theme }) => theme.bg2};
color: ${({ theme }) => theme.text1};
border-radius: 8px;
user-select: none;
& > * {
user-select: none;
}
:hover {
cursor: pointer;
}
`
export default function SortButton({
toggleSortOrder,
ascending,
}: {
toggleSortOrder: () => void
ascending: boolean
}) {
return (
<FilterWrapper onClick={toggleSortOrder}>
<Text fontSize={14} fontWeight={500}>
{ascending ? '↑' : '↓'}
</Text>
</FilterWrapper>
)
}

View File

@@ -1,43 +1,6 @@
import styled from 'styled-components/macro'
import { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row'
export const ModalInfo = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
padding: 1rem 1rem;
margin: 0.25rem 0.5rem;
justify-content: center;
flex: 1;
user-select: none;
`
export const StyledMenu = styled.div`
display: flex;
justify-content: center;
align-items: center;
position: relative;
border: none;
`
export const PopoverContainer = styled.div<{ show: boolean }>`
z-index: 100;
visibility: ${(props) => (props.show ? 'visible' : 'hidden')};
opacity: ${(props) => (props.show ? 1 : 0)};
transition: visibility 150ms linear, opacity 150ms linear;
background: ${({ theme }) => theme.bg2};
border: 1px solid ${({ theme }) => theme.bg3};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.01);
color: ${({ theme }) => theme.text2};
border-radius: 0.5rem;
padding: 1rem;
display: grid;
grid-template-rows: 1fr;
grid-gap: 8px;
font-size: 1rem;
text-align: left;
top: 80px;
`
import { RowBetween } from '../Row'
export const TextDot = styled.div`
height: 3px;
@@ -46,10 +9,6 @@ export const TextDot = styled.div`
border-radius: 50%;
`
export const FadedSpan = styled(RowFixed)`
color: ${({ theme }) => theme.primary1};
font-size: 14px;
`
export const Checkbox = styled.input`
border: 1px solid ${({ theme }) => theme.red3};
height: 20px;

View File

@@ -1,9 +1,9 @@
import { t, Trans } from '@lingui/macro'
import React, { useContext, useRef, useState } from 'react'
import { useContext, useRef, useState } from 'react'
import { Settings, X } from 'react-feather'
import ReactGA from 'react-ga'
import { Text } from 'rebass'
import styled, { ThemeContext } from 'styled-components'
import styled, { ThemeContext } from 'styled-components/macro'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import { ApplicationModal } from '../../state/application/actions'
import { useModalOpen, useToggleSettingsMenu } from '../../state/application/hooks'

View File

@@ -1,4 +1,4 @@
import React, { useCallback } from 'react'
import { useCallback } from 'react'
import styled from 'styled-components/macro'
const StyledRangeInput = styled.input<{ size: number }>`

View File

@@ -1,13 +1,10 @@
import { Trans } from '@lingui/macro'
import React, { useMemo } from 'react'
import ReactGA from 'react-ga'
import { useLocation } from 'react-router'
import { useMemo } from 'react'
import styled from 'styled-components/macro'
import { DEFAULT_LOCALE, LOCALE_LABEL, SupportedLocale } from '../../constants/locales'
import { navigatorLocale, useActiveLocale } from '../../hooks/useActiveLocale'
import useParsedQueryString from '../../hooks/useParsedQueryString'
import { StyledInternalLink, TYPE } from '../../theme'
import { stringify } from 'qs'
import { useLocationLinkProps } from 'hooks/useLocationLinkProps'
const Container = styled(TYPE.small)`
opacity: 0.6;
@@ -17,46 +14,35 @@ const Container = styled(TYPE.small)`
margin-top: 1rem !important;
`
export function SwitchLocaleLink() {
const activeLocale = useActiveLocale()
const useTargetLocale = (activeLocale: SupportedLocale) => {
const browserLocale = useMemo(() => navigatorLocale(), [])
const location = useLocation()
const qs = useParsedQueryString()
if (browserLocale && (browserLocale !== DEFAULT_LOCALE || activeLocale !== DEFAULT_LOCALE)) {
let targetLocale: SupportedLocale
if (activeLocale === browserLocale) {
targetLocale = DEFAULT_LOCALE
return DEFAULT_LOCALE
} else {
targetLocale = browserLocale
return browserLocale
}
const target = {
...location,
search: stringify({ ...qs, lng: targetLocale }),
}
return (
<Container>
<Trans>
Uniswap available in:{' '}
{
<StyledInternalLink
onClick={() => {
ReactGA.event({
category: 'Localization',
action: 'Switch Locale',
label: `${activeLocale} -> ${targetLocale}`,
})
}}
to={target}
>
{LOCALE_LABEL[targetLocale]}
</StyledInternalLink>
}
</Trans>
</Container>
)
}
return null
}
export function SwitchLocaleLink() {
const activeLocale = useActiveLocale()
const targetLocale = useTargetLocale(activeLocale)
const { to, onClick } = useLocationLinkProps(targetLocale)
if (!targetLocale || !to) return null
return (
<Container>
<Trans>
Uniswap available in:{' '}
<StyledInternalLink onClick={onClick} to={to}>
{LOCALE_LABEL[targetLocale]}
</StyledInternalLink>
</Trans>
</Container>
)
}

View File

@@ -0,0 +1,146 @@
import React, { memo, useCallback, useRef } from 'react'
import styled from 'styled-components/macro'
const Input = styled.input<{ error?: boolean; fontSize?: string }>`
font-size: ${({ fontSize }) => fontSize || '1.25rem'};
outline: none;
border: none;
flex: 1 1 auto;
width: 0;
background-color: ${({ theme }) => theme.bg1};
transition: color 300ms ${({ error }) => (error ? 'step-end' : 'step-start')};
color: ${({ error, theme }) => (error ? theme.red1 : theme.text1)};
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
width: 100%;
padding: 0px;
-webkit-appearance: textfield;
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-outer-spin-button,
::-webkit-inner-spin-button {
-webkit-appearance: none;
}
::placeholder {
color: ${({ theme }) => theme.text4};
}
`
const TextAreaInput = styled.textarea<{ error?: boolean; fontSize?: string }>`
font-size: ${({ fontSize }) => fontSize || '1.25rem'};
outline: none;
border: none;
flex: 1 1 auto;
width: 0;
resize: none;
background-color: ${({ theme }) => theme.bg1};
transition: color 300ms ${({ error }) => (error ? 'step-end' : 'step-start')};
color: ${({ error, theme }) => (error ? theme.red1 : theme.text1)};
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
width: 100%;
line-height: 1.2;
padding: 0px;
-webkit-appearance: textfield;
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-outer-spin-button,
::-webkit-inner-spin-button {
-webkit-appearance: none;
}
::placeholder {
color: ${({ theme }) => theme.text4};
}
`
export const TextInput = ({
className,
value,
onUserInput,
placeholder,
fontSize,
}: {
className?: string
value: string
onUserInput: (value: string) => void
placeholder: string
fontSize: string
}) => {
const handleInput = useCallback(
(event) => {
onUserInput(event.target.value)
},
[onUserInput]
)
return (
<div className={className}>
<Input
type="text"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
placeholder={placeholder || ''}
onChange={handleInput}
value={value}
fontSize={fontSize}
/>
</div>
)
}
export const ResizingTextArea = memo(
({
className,
value,
onUserInput,
placeholder,
fontSize,
}: {
className?: string
value: string
onUserInput: (value: string) => void
placeholder: string
fontSize: string
}) => {
const inputRef = useRef<HTMLTextAreaElement>(document.createElement('textarea'))
const handleInput = useCallback(
(event) => {
inputRef.current.style.height = 'auto'
inputRef.current.style.height = inputRef.current.scrollHeight + 'px'
onUserInput(event.target.value)
},
[onUserInput]
)
return (
<TextAreaInput
style={{ height: 'auto', minHeight: '500px' }}
className={className}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
placeholder={placeholder || ''}
onChange={handleInput}
value={value}
fontSize={fontSize}
ref={inputRef}
/>
)
}
)
ResizingTextArea.displayName = 'ResizingTextArea'

View File

@@ -1,37 +0,0 @@
import { readableColor } from 'polished'
import React from 'react'
import styled from 'styled-components/macro'
import { colors } from 'theme'
const Swatch = styled.div`
align-items: center;
display: flex;
flex-direction: column;
height: 100px;
justify-content: center;
min-width: 200px;
`
const Wrapper = styled.div`
display: flex;
flex-wrap: wrap;
flex-direction: row;
`
interface ThemePaletteProps {
isDarkMode: boolean
}
export default function ThemePalette({ isDarkMode }: ThemePaletteProps) {
const data = colors(isDarkMode)
return (
<Wrapper>
{Object.entries(data).map(([key, value]) => (
<Swatch key={key} style={{ color: readableColor(value), backgroundColor: value }}>
<div>{key}</div>
<div>{value}</div>
</Swatch>
))}
</Wrapper>
)
}

View File

@@ -1,5 +1,4 @@
import { Trans } from '@lingui/macro'
import React from 'react'
import styled from 'styled-components/macro'
import { TYPE } from '../../theme'
@@ -31,7 +30,7 @@ const StatusText = styled(TYPE.main)<{ isActive?: boolean }>`
color: ${({ theme, isActive }) => (isActive ? theme.text1 : theme.text3)};
`
export interface ToggleProps {
interface ToggleProps {
id?: string
isActive: boolean
bgColor: string

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