Compare commits

...

105 Commits

Author SHA1 Message Date
Yadong Zhang
eb725f51ce fix: handled liquidity minPrice and maxPrice are empty case. (#4569)
thanks!
2022-09-04 18:37:42 -05:00
lynn
4d4462368b fix: add back usd volume to swap submitted from ui instead of infura hook (#4563)
* init

* typo
2022-09-02 14:56:05 -04:00
lynn
6fe5d4363d fix: remove warning icon on search (#4564)
init
2022-09-02 14:55:50 -04:00
Jordan Frankfurt
b46fa27084 feat: disable branding on swap widget (#4561)
* feat: disable branding on swap widget:

* pr feedback
2022-09-02 10:03:06 -05:00
lynn
ade2440613 fix: missing logo redesign (#4535)
* fix: logos

* different font sizings for diff token sizes from fred

* remove missing symbol

* fix

* fixes to comments

* fix

* use calc instead of fn

* add comment
2022-09-01 17:51:43 -04:00
Jordan Frankfurt
4dc4620b60 feat: integrate widget tx states (#4553)
* feat(widget): sync transaction states

* s

* waiting on type release

* slippage is all that remains

* finalize tx integration

* pr feedback

* pr feedback - else if

* update @uniswap/widgets to 2.7

* add slippage tolerance from transaction.info
2022-09-01 13:17:39 -05:00
aballerr
202c2662f1 fix: Adding socks icon to users profile icon (#4545)
* Adding icon for socks owner for p0 redesign


Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-09-01 14:08:49 -04:00
Charles Bachmeier
d2afd71c81 fix: change from flex to inline to fix safari bugs (#4559)
change from flex to inline to fix safari bugs

Co-authored-by: Charlie <charlie@uniswap.org>
2022-09-01 08:29:46 -07:00
aballerr
bad1ce2618 fix: Prod has chevron (#4558)
Remove chevron from prod

Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-09-01 10:20:09 -04:00
Vignesh Mohankumar
f194845b2b chore: remove theme.blue200 (#4533)
* chore: remove theme.blue200

* favorite button changes
2022-08-31 20:13:21 -04:00
Jack Short
98d4e108e6 fix: search icon bug (#4556) 2022-08-31 19:35:06 -04:00
lynn
ab43ed1900 fix: connect wallet button chevron styling fixes (#4554)
* fix

* simplify
2022-08-31 18:16:42 -04:00
Greg Bugyis
b147e047a5 fix: Bump height on LineNumberCell and pass header boolean (#4548) 2022-08-31 20:36:23 +03:00
Charles Bachmeier
bbb616f56c feat: auto set cursor on searchbar opening (#4552)
* auto set cursor on searchbar opening

* comment update

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-31 10:33:51 -07:00
Charles Bachmeier
35a429ea65 feat: highlight first result by default (#4551)
highlight first result by default

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-31 10:33:33 -07:00
Zach Pomerantz
bd16543c10 build: upgrade @uniswap/widgets to 2.5.0 (#4546)
* build: rm d3-curve-circlecorners entry

* build: upgrade @uniswap/widgets package

* fix: widget input typing
2022-08-31 10:29:50 -07:00
Charles Bachmeier
cbdeae276e feat: adjust bottom navbar padding (#4550)
adjust bottom navbar padding

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-31 09:57:42 -07:00
lynn
e733113963 fix: modify chart axis to to match crosshairs in year view (#4547)
* fix

* simplify

* refactor

* move logic to monthTickFormatter

* time to date string
2022-08-31 11:31:33 -04:00
aballerr
272b030b89 fix: Fixing border overlap and reducing button size (#4537)
* fixing styling on wallet border

Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-08-31 11:11:37 -04:00
Jack Short
472a553d13 fix: catch invalid address token details (#4529) 2022-08-30 17:25:59 -04:00
cartcrom
3a1bff146c feat: adding token safety article link (#4532)
updated article link
2022-08-30 17:24:23 -04:00
lynn
b82b9acc54 fix: remove stablecoin usd val fetch in logging to reduce infura spend (#4543)
remove stablecoin usd val fetch reduce infura
2022-08-30 15:15:49 -04:00
cartcrom
fac3845756 fix: missing segments of price chart (#4541)
fixed missing segments of line
2022-08-30 12:28:52 -04:00
Charles Bachmeier
9381a74f1d feat: nav update to new responsive designs (#4542)
* uppdated mobile nav

* adjust searchbar for new styles

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-30 09:08:10 -07:00
Charles Bachmeier
f6662a3208 fix: hide right and left border on mobile dropdown (#4540)
hide right and left border on mobile

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-30 07:15:50 -07:00
pp-hh-ii-ll
134af82d90 fix: updated color definitions for backgroundModule, add scrolledSurface (#4538)
Updated color definitions for BackgroundModule and tokens list in Explore page

Update Dark Mode BackgroundModule to Grey800 to match new design spec

Update Widgets theme to use BackgroundModule for Module

Update Token List in Explore page to use Surface instead of Module

Add Scrolled Surface color definition
2022-08-29 22:00:34 -04:00
Vignesh Mohankumar
75175b8e54 fix: don't track balances/values (#4539) 2022-08-29 18:46:37 -04:00
Greg Bugyis
e3d8599dc7 fix: [TokenDetails] Glyph placement on line chart (#4525)
* Modify line curve on token price chart to fix inconsistency on steep drops/increases and glyph placement

* Make curve required on LineChart

* Add curve to SparkLine chart

* Remove dependency: d3-curve-circlecornders - no longer used

* Drop d3-curve-circlecorner from react-app-env

Co-authored-by: gbugyis <greg@bugyis.com>
2022-08-29 21:40:05 +03:00
Charles Bachmeier
77ee69ad52 fix: search flash of no tokens found when typing (#4531)
* add null check to collection floor price

* don't show 0 if floor is null

* use debouncedSearchValue

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-29 10:47:42 -07:00
lynn
4b82838f80 fix: add back cancel button in token safety for swap (#4527)
* add back cancel button in token safety for swap

* also add to swap flow
2022-08-29 13:08:30 -04:00
Jack Short
a177829976 style: switching hovered and idle colors (#4526) 2022-08-29 12:49:36 -04:00
Kaylee George
b8b4f960dd fix: tokens banner link works (#4522)
* maybe fixed?

* fix

* fix link
2022-08-29 12:48:13 -04:00
Charles Bachmeier
2466414307 feat: search when no results there is too much top padding (#4530)
* add null check to collection floor price

* don't show 0 if floor is null

* remove top margin for no results

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-29 09:43:50 -07:00
Charles Bachmeier
a3a32f0d68 fix: add null check to collection floor price (#4528)
* add null check to collection floor price

* don't show 0 if floor is null

* formatEthPrice accepts udnefined

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-29 09:38:22 -07:00
Vignesh Mohankumar
ee001f86f0 refactor: remove isChainAllowed (#4494) 2022-08-29 10:21:18 -04:00
lynn
87d6975bd8 fix: stop swap layout shifts when reverse token button clicked (#4523)
fix
2022-08-26 17:22:58 -04:00
Kaylee George
4cab4e27ff fix: fix favorites button responsiveness on tokens explore (#4520)
fav button responsiveness
2022-08-26 16:47:41 -04:00
Kaylee George
f105f0995b fix: formatted delta on mobile view of token explore (#4519)
fix delta
2022-08-26 13:42:38 -07:00
Kaylee George
18e89a7353 fix: rm right padding on token details (#4518)
rm padding on mobile
2022-08-26 16:32:23 -04:00
Charles Bachmeier
445f9a67a4 fix: mobile network switcher (#4516)
* fix: mobile network switcher

* fix zindex

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-26 13:01:20 -07:00
Jack Short
4c039c900c fix: wrapped native asset resolve to their appropriate native asset o… (#4497)
* fix: wrapped native asset resolve to their appropriate native asset on respective chains

* addressed comments
2022-08-26 14:42:34 -04:00
Kaylee George
2c2e0a3419 fix: vertically center the explore table header text (#4514)
center
2022-08-26 14:41:34 -04:00
aballerr
1b43e0b28a fix: wallet mobile ui changes (#4515)
fix wallet mobile

Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-08-26 14:41:02 -04:00
Connor McEwen
a23f7782b2 fix: widget module color (#4513) 2022-08-26 14:40:33 -04:00
Zach Pomerantz
723db9d0ea feat: integrate widget settings/value (#4499)
* widgets: v2.2.0

* refactor: wrap Widget

* feat: integrate widget settings

* refactor: import from widgets

* fix: better settings integration

* fix: include types

* chore: formatting

* build: bump widgets version

* feat: integrate widget value

* feat: integrate widget token selection

* feat: clean up widget integration

* build: bump widgets version

* refactor: mv widget wrapper to components

* refactor: split widget integrations

* refactor: value -> inputs

* fix: consolidate slippage hooks

* fix: memoize currency search modal

* refactor: clarify widget code

* fix: allow loading token in widget

* fix: add TradeType helpers
2022-08-26 11:19:51 -07:00
lynn
cbf165dc40 fix: oops (#4511)
oops
2022-08-26 11:12:48 -07:00
Kaylee George
6f3579acf1 feat: add token explore promo banner (#4481)
* initial design

* progress

* add background image

* add logic

* update

* delete unneed state

* more

* redux

* redux

* more redux

* rebase main

* rm unused state

* rm unused export

* relative link
2022-08-26 11:00:50 -07:00
lynn
c9e2f86e57 feat: add i18n to token detail and token explore (#4510)
init
2022-08-26 13:51:29 -04:00
aballerr
8c0199119e fix: Fix to mobile wallet experience (#4508)
* fixing wallet on mobile and making useClickoutside more extensible


Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-08-26 13:35:38 -04:00
Kaylee George
5e6e6be888 fix: fix decimal formatting of token price on details page (#4470)
* init

* 6 decimals

* dollar dollar bill yo
2022-08-26 10:19:29 -07:00
github-actions[bot]
79fb6485b1 chore(i18n): new Crowdin translations (#4495)
chore(i18n): synchronize translations from crowdin [skip ci]

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-08-26 13:16:06 -04:00
Greg Bugyis
29baaaf2ed fix: Token Warning dismiss/redirect (#4482)
* Remove cancel option from TokenSafety modal

* Only redirect if token is blocked

* Remove unused variables

Co-authored-by: gbugyis <greg@bugyis.com>
2022-08-26 20:10:54 +03:00
Kaylee George
f824fb25c2 fix: explore table hover state colors (#4479)
* fix hover state colors

* colors

* fixed
2022-08-26 09:41:05 -07:00
Charles Bachmeier
28a6ea7e1a feat: NavBar I18N (#4507)
* wrap tabs and searchbar

* wrap chain switcher and fix right align bug

* undo translate chains

* replace t with i18n

* revert i18n

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-26 09:22:05 -07:00
lynn
65566faf17 fix: yellow button redesign, improve add liquidity page readability (#4504)
* init

* add background overlay for hover states
2022-08-26 12:02:03 -04:00
lynn
eb4f90e669 feat: add volume trade info to swap completed event (#4506)
add volume trade info to swap completed event
2022-08-26 10:56:53 -04:00
aballerr
40308158ca fix: Updating search token width and adding margin between token delta and price (#4500)
Updating search token width and adding margin between token delta and price

Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-08-26 09:46:39 -04:00
Greg Bugyis
d0d5240474 fix: update Token table headers to match latest Figma (#4505)
TokenTable: update table headers to match latest Figma

Co-authored-by: gbugyis <greg@bugyis.com>
2022-08-26 15:21:35 +03:00
Charles Bachmeier
751ce8e6d6 feat: Mobile Nav (#4501)
* working and cleaned up mobile nav

* delete old files

* fix wallet position

* update searchbar breakpoint

* update full screen search

* delete old comments

* cleanup eslint

* Update MenuDropdown.tsx

* Update SearchBar.tsx

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-26 04:39:58 -07:00
Greg Bugyis
748a5eadc0 fix: Token Details - move About section below Market Stats (#4483) 2022-08-26 10:50:55 +03:00
lynn
5659fe21ea fix: fix swap small layout shifts when clicking reverse tokens button (#4503)
fix
2022-08-25 23:57:14 -04:00
lynn
bc899b74a3 feat: make token safety speedbumps appear for tokens with warnings in token selector (#4496)
* checkpoint: token modal safety warning working, showing speedbump

* fix styling

* dont show token safety once user has already ack'd that token

* fix cancel button on token safety - always navigate back to search
2022-08-25 21:20:04 -04:00
Zach Pomerantz
516c8b05a4 feat: add group flag toggles (#4502) 2022-08-25 16:54:15 -07:00
aballerr
8502f9e303 fix: Wallet updates (#4498)
* Wallet Polishing

Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-08-25 17:50:38 -04:00
Jordan Frankfurt
bc90d416e6 feat: add background-color transitions to table color changes (#4491)
* feat: add background-color transitions to table color changes:

* pr review--use theme transition info
2022-08-25 16:34:05 -05:00
Charles Bachmeier
2fd1cd72fd fix: bug with network with no info (#4489)
* fix bug with network with no info

* remove unneeded check

* add null checks

* check info for supported case

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-25 11:32:26 -07:00
github-actions[bot]
3a2276dcd1 chore(i18n): new Crowdin translations (#4478)
* chore(i18n): synchronize translations from crowdin [skip ci]

* empty

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Vignesh Mohankumar <me@vig.xyz>
2022-08-25 14:30:25 -04:00
Vignesh Mohankumar
c432c583f6 fix: rename search to filter tokens (#4485)
* fix: rename search to filter tokens

* translate
2022-08-25 14:04:35 -04:00
Connor McEwen
9b8f5ed8f4 fix: update widget color scheme (#4475)
* fix: update widget color scheme

* Map Uniswap Design System colors to Widget color styles

Co-authored-by: pp-hh-ii-ll <111304124+pp-hh-ii-ll@users.noreply.github.com>
2022-08-25 13:50:46 -04:00
Jack Short
0713a15028 fix: adding explore buttons on loading (#4490) 2022-08-25 13:17:45 -04:00
Charles Bachmeier
62c502615f feat: show searchbar on tablet size (#4484)
show searchbar on tablet size

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-25 10:00:41 -07:00
Charles Bachmeier
fdbe4b8f5e feat: mobile network switcher (#4486)
* feat: mobile network switcher

* adjust bottom border radius on mobile

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-25 09:31:47 -07:00
lynn
33c73f4dc8 fix: network balances summary styling (#4487)
init
2022-08-25 09:15:36 -07:00
aballerr
c207a576e7 feat: Sample transitions pr (#4477)
* Updating theming to include transitions


Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-08-25 11:25:49 -04:00
Jack Short
aa426514f3 chore: updating navbar padding for token pages (#4474)
* chore: updating navbar padding for token pages

* removing margin on pools page
2022-08-25 11:20:19 -04:00
aballerr
ba9c28892e fix: Fixing token modal warning to match figma design (#4476)
* fixing token modal warning to match figma design


Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-08-25 10:06:12 -04:00
Charles Bachmeier
49c31ddfc8 feat: update sprinkles breakpoints (#4480)
update breakpoints

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-24 19:01:11 -07:00
github-actions[bot]
4424205814 chore(i18n): new Crowdin translations (#4460)
* chore(i18n): synchronize translations from crowdin [skip ci]

* empty

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Vignesh Mohankumar <me@vig.xyz>
2022-08-24 18:04:37 -04:00
lynn
874f3fb737 fix: fix copy button styling (#4473)
* fix copy button styling

* fix positionig

* preserve original css for token safety warning use case
2022-08-24 17:57:25 -04:00
Kaylee George
ac27c89a44 fix: change top tokens query return type (#4453)
* query return

* fix return

* rm default empty string

* rm ?

* undef

* undefined

* null

* fix

* handle search

* .

* rebase

* top tokens

* rm blank

* rm console log

* make 100
2022-08-24 12:38:22 -07:00
aballerr
0e530cf92e fix: added in correct wallet breakpoints (#4472)
* adding in wallet breakpoints

Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-08-24 15:16:51 -04:00
Charles Bachmeier
ed66b00b20 feat: update Chain Switcher Modal and Active State (#4471)
* Add unsupported network and active state to chain switcher

* update switcher dropdown modal

* undo

* undo new styles, adjust padding

* update padding

* use supported chain helper fn

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-24 11:51:38 -07:00
cartcrom
84fb05239b fix: data api loading states and repetitive calls (#4461)
* diagnosing issues
* fixed loading states
* addressed PR comments
* fixed missing symbol issue
* fixed merge conflcit
* fixed uppercase token symbol issue
2022-08-24 14:13:21 -04:00
Vignesh Mohankumar
7500bbc0be fix: don't translate UNI (#4467) 2022-08-24 14:00:20 -04:00
Jack Short
c43c8de6cd chore: updating breakpoints (#4465)
* chore: updating breakpoints

* deprecating old breakpoints

* using breakpoints in theme instead of media queries
2022-08-24 13:43:46 -04:00
Connor McEwen
9d40db5b21 fix: update swap CTA button color (#4464) 2022-08-24 10:42:56 -07:00
Kaylee George
a61eca36ae fix: change toggle and input placeholder colors on swap settings flyout (#4466)
change colors
2022-08-24 10:41:24 -07:00
Kaylee George
60479a442f fix: remove UNI currency logo from swap widget approval button (#4468)
take away logo
2022-08-24 10:41:11 -07:00
Kaylee George
5257188f70 fix: fix token details 'share to twitter' information (#4469)
add twitter correct link
2022-08-24 10:33:19 -07:00
lynn
7599239983 fix: Web 897 redesign general token selector from swap focusing text input has incorrect background color (#4463)
* init

* add search icon
2022-08-24 12:42:17 -04:00
lynn
1561c0d000 feat: fix visual bugs on truncate token description (#4462)
init
2022-08-24 12:28:07 -04:00
Charles Bachmeier
1f740cf8c0 feat: update search icon (#4459)
* feat: update search icon

* re add chevron on mobile

* icon uses currentColor

* icon uses currentColor

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-24 07:07:12 -07:00
aballerr
55c5f03004 feat: Adding in remaining transactions to transaction history (#4455)
* adding in remaining transactions: Wrap, Collect Fees, Approval and claim rewards

Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-08-24 09:59:11 -04:00
Charles Bachmeier
a345cff614 fix: cleanup styles for overflow modal (#4458)
* improvements to overflow modal

* add active state to overflow modal

* fix right aligned models

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-24 06:32:32 -07:00
lynn
85d8566cfa feat: make currencyInputPanel redesign changes apply only to swap (#4456)
* init: revert to pre redesign swap changes

* differentiate between swapCurrencyInputPanel and regular

* it was inverted whoops

* add padding to fiat row from cal
2022-08-24 02:00:48 -04:00
Charles Bachmeier
44d68e3ef0 fix: /pool position second icon zindex issue (#4457)
fix /pool zindex issue

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-23 21:05:58 -07:00
Greg Bugyis
04bd4900b0 feat: adds banner section (carousel only) for NFT Explore (#4383)
* NFT Explore: Banner section and carousel

* Fixes from PR feedback

* PR feedback and slight refactor of Carousel Progress indicators

* Only render current collection, and simplify fullWidth class

* Add colors to sprinkles and drop zIndex on bannerContent

* Simplify component structure

* Separate out CarouselIndicator and other cleanup

* Restore CarouselProgress component

* Position carousel progress over bg overlay

Co-authored-by: gbugyis <greg@bugyis.com>
2022-08-24 01:06:54 +03:00
github-actions[bot]
81f277b36f chore(i18n): new Crowdin translations (#4454)
* chore(i18n): synchronize translations from crowdin [skip ci]

* empty

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Vignesh Mohankumar <me@vig.xyz>
2022-08-23 16:47:51 -04:00
Connor McEwen
575660d3e8 fix: respect padding in cells with proper hover (#4451) 2022-08-23 16:41:42 -04:00
lynn
1e692491f1 fix: truncates to token details descriptions (#4450)
* inti

* respond to vm comments

* add paragraphs as per fred, respond vm comments

* respond last vm comment

* simplify

* move fn out of component

* fix messed up styling

* simplify

* simplify

* fred nits

* remove xtra token detail props

* fix sentence casing
2022-08-23 15:25:37 -04:00
Vignesh Mohankumar
b3639b3453 fix: makes SearchBar focus border-width consistent (#4452) 2022-08-23 11:44:01 -07:00
Kaylee George
53ebf37b40 feat: add top tokens query for Explore tokens table (#4448)
* progress

* map??

* idk

* maybe progress?

* more

* hook

* rm favs

* add query

* comment

* get rid of page query

* fix autoimports

* Update TokenRow.tsx
2022-08-23 11:13:35 -07:00
lynn
624ec33652 build: patch vanilla extract to speed up build time (#4447)
patch ve to build

Co-authored-by: Connor McEwen <connor.mcewen@gmail.com>
2022-08-23 12:02:27 -04:00
Charles Bachmeier
2890040118 feat: Fixed light mode styles and added hover states to navbar (#4449)
* cleanup navbar lightmode

* lightmode and hover states

* move magical gradient to common stlyes

* inherit border radius

* hover transition

* further split common gradient style

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-23 08:30:37 -07:00
aballerr
68db8b3e23 feat: wallet hover styling and moving wallet to correct navbar (#4444)
* updating wallet to have correct hover highlighting and have correct scrolling

Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-08-23 11:00:36 -04:00
191 changed files with 7923 additions and 2859 deletions

View File

@@ -23,7 +23,8 @@
"lint": "yarn eslint .",
"test": "craco test --coverage",
"cypress:open": "cypress open --browser chrome --e2e",
"cypress:run": "cypress run --browser chrome --e2e"
"cypress:run": "cypress run --browser chrome --e2e",
"postinstall": "patch-package"
},
"jest": {
"collectCoverageFrom": [
@@ -104,6 +105,8 @@
"eslint-plugin-unused-imports": "^2.0.0",
"jest-styled-components": "^7.0.8",
"ms.macro": "^2.0.0",
"patch-package": "^6.4.7",
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.7.1",
"react-scripts": "^4.0.3",
"relay-compiler": "^14.1.0",
@@ -142,7 +145,7 @@
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "^1.1.1",
"@uniswap/v3-sdk": "^3.9.0",
"@uniswap/widgets": "^2.1.1",
"@uniswap/widgets": "^2.7.0",
"@vanilla-extract/css": "^1.7.2",
"@vanilla-extract/css-utils": "^0.1.2",
"@vanilla-extract/dynamic": "^2.0.2",
@@ -172,7 +175,6 @@
"clsx": "^1.1.1",
"copy-to-clipboard": "^3.2.0",
"d3": "^7.6.1",
"d3-curve-circlecorners": "^0.1.6",
"ethers": "^5.1.4",
"firebase": "^9.1.3",
"focus-visible": "^5.2.0",

View File

@@ -0,0 +1,22 @@
diff --git a/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js b/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js
index 6e40061..10283a2 100644
--- a/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js
+++ b/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js
@@ -177,11 +177,13 @@ function generateIdentifier(debugId) {
var fileScopeHash = hash__default["default"](packageName ? "".concat(packageName).concat(filePath) : filePath);
var identifier = "".concat(fileScopeHash).concat(refCount);
- if (adapter_dist_vanillaExtractCssAdapter.getIdentOption() === 'debug') {
- var devPrefix = getDevPrefix(debugId);
+ if (process.env.VANILLA_EXTRACT_DEV_PREFIX) {
+ if (adapter_dist_vanillaExtractCssAdapter.getIdentOption() === 'debug') {
+ var devPrefix = getDevPrefix(debugId);
- if (devPrefix) {
- identifier = "".concat(devPrefix, "__").concat(identifier);
+ if (devPrefix) {
+ identifier = "".concat(devPrefix, "__").concat(identifier);
+ }
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

3
src/assets/svg/socks.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="10" height="12" viewBox="0 0 10 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.75 11C4.75 11 5.22377 11.1391 5.80923 10.5828L7.64433 8.65908C8.35405 7.91508 8.74998 6.92678 8.74989 5.89856C8.7498 4.72716 8.74971 3.31706 8.74991 2.50009C8.74996 2.22391 8.77618 2 8.5 2H8.25M6.74898 5.75L6.74979 2L6.74991 1.50009C6.74996 1.22391 6.52609 1 6.24991 1H4.25167C3.97553 1 3.75167 1.22386 3.75167 1.5V4.75039C3.75167 5.29859 3.52665 5.82276 3.12922 6.20034L1.6891 7.56856C1.10364 8.12478 1.10363 9.0266 1.68909 9.58283C2.12197 9.99409 2.75372 10.1013 3.29025 9.90438C3.47937 9.83497 3.65665 9.72779 3.80923 9.58283L5.80923 7.6827M6.74898 5.75L6.7487 6.36119C6.74861 6.63517 6.63611 6.89711 6.43748 7.08582L5.80923 7.6827M6.74898 5.75H6.4384C5.67845 5.75 5.19623 6.56419 5.56146 7.23061L5.80923 7.6827" stroke="white" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 871 B

View File

@@ -24,7 +24,7 @@ const HeaderRow = styled.div`
padding: 1rem 1rem;
font-weight: 500;
color: ${(props) => (props.color === 'blue' ? ({ theme }) => theme.deprecated_primary1 : 'inherit')};
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
padding: 1rem;
`};
`
@@ -74,7 +74,7 @@ const AccountGroupingRow = styled.div`
const AccountSection = styled.div`
padding: 0rem 1rem;
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0rem 1rem 1.5rem 1rem;`};
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`padding: 0rem 1rem 1.5rem 1rem;`};
`
const YourAccount = styled.div`

View File

@@ -1,6 +1,9 @@
import { useWeb3React } from '@web3-react/core'
import { UNI_ADDRESS } from 'constants/addresses'
import { TransactionInfo, TransactionType } from 'state/transactions/types'
import styled, { css } from 'styled-components/macro'
import { nativeOnChain } from '../../constants/tokens'
import { useCurrency } from '../../hooks/Tokens'
import CurrencyLogo from '../CurrencyLogo'
@@ -32,22 +35,38 @@ interface CurrencyPair {
currencyId1: string | undefined
}
const getCurrency = ({ info }: { info: TransactionInfo }): CurrencyPair => {
const getCurrency = ({ info, chainId }: { info: TransactionInfo; chainId: number | undefined }): CurrencyPair => {
switch (info.type) {
case TransactionType.ADD_LIQUIDITY_V3_POOL:
case TransactionType.REMOVE_LIQUIDITY_V3:
case TransactionType.CREATE_V3_POOL:
const { baseCurrencyId, quoteCurrencyId } = info
return { currencyId0: baseCurrencyId, currencyId1: quoteCurrencyId }
case TransactionType.SWAP:
const { inputCurrencyId, outputCurrencyId } = info
return { currencyId0: inputCurrencyId, currencyId1: outputCurrencyId }
case TransactionType.WRAP:
const { unwrapped } = info
const native = info.chainId ? nativeOnChain(info.chainId) : undefined
const base = 'ETH'
const wrappedCurrency = native?.wrapped.address ?? 'WETH'
return { currencyId0: unwrapped ? wrappedCurrency : base, currencyId1: unwrapped ? base : wrappedCurrency }
case TransactionType.COLLECT_FEES:
const { currencyId0, currencyId1 } = info
return { currencyId0, currencyId1 }
case TransactionType.APPROVAL:
return { currencyId0: info.tokenAddress, currencyId1: undefined }
case TransactionType.CLAIM:
const uniAddress = chainId ? UNI_ADDRESS[chainId] : undefined
return { currencyId0: uniAddress, currencyId1: undefined }
default:
return { currencyId0: undefined, currencyId1: undefined }
}
}
const LogoView = ({ info }: { info: TransactionInfo }) => {
const { currencyId0, currencyId1 } = getCurrency({ info })
const { chainId } = useWeb3React()
const { currencyId0, currencyId1 } = getCurrency({ info, chainId })
const currency0 = useCurrency(currencyId0)
const currency1 = useCurrency(currencyId1)
const isCentered = !(currency0 && currency1)

View File

@@ -3,15 +3,22 @@ import { Fraction, TradeType } from '@uniswap/sdk-core'
import JSBI from 'jsbi'
import {
AddLiquidityV3PoolTransactionInfo,
ApproveTransactionInfo,
ClaimTransactionInfo,
CollectFeesTransactionInfo,
ExactInputSwapTransactionInfo,
ExactOutputSwapTransactionInfo,
RemoveLiquidityV3TransactionInfo,
TransactionInfo,
TransactionType,
WrapTransactionInfo,
} from 'state/transactions/types'
import styled from 'styled-components/macro'
import { useCurrency } from '../../hooks/Tokens'
import { nativeOnChain } from '../../constants/tokens'
import { useCurrency, useToken } from '../../hooks/Tokens'
import useENSName from '../../hooks/useENSName'
import { shortenAddress } from '../../utils'
import { TransactionState } from './index'
const HighlightText = styled.span`
@@ -19,6 +26,10 @@ const HighlightText = styled.span`
font-weight: 600;
`
const BodyWrap = styled.div`
line-height: 20px;
`
interface ActionProps {
pending: JSX.Element
success: JSX.Element
@@ -85,13 +96,13 @@ const SwapSummary = ({
const { rawAmountFrom, rawAmountTo } = getRawAmounts(info)
return (
<>
<BodyWrap>
<Action {...actionProps} />{' '}
<FormattedCurrencyAmount rawAmount={rawAmountFrom} currencyId={info.inputCurrencyId} sigFigs={2} />{' '}
<Trans>for </Trans>{' '}
<FormattedCurrencyAmount rawAmount={rawAmountTo} currencyId={info.outputCurrencyId} sigFigs={2} />{' '}
<FailedText transactionState={transactionState} />
</>
</BodyWrap>
)
}
@@ -112,7 +123,7 @@ const AddLiquidityV3PoolSummary = ({
}
return (
<>
<BodyWrap>
{createPool ? (
<CreateV3PoolSummary info={info} transactionState={transactionState} />
) : (
@@ -124,7 +135,7 @@ const AddLiquidityV3PoolSummary = ({
</>
)}{' '}
<FailedText transactionState={transactionState} />
</>
</BodyWrap>
)
}
@@ -143,13 +154,13 @@ const RemoveLiquidityV3Summary = ({
}
return (
<>
<BodyWrap>
<Action {...actionProps} />{' '}
<FormattedCurrencyAmount rawAmount={expectedAmountBaseRaw} currencyId={baseCurrencyId} sigFigs={2} />{' '}
<Trans>and</Trans>{' '}
<FormattedCurrencyAmount rawAmount={expectedAmountQuoteRaw} currencyId={quoteCurrencyId} sigFigs={2} />{' '}
<FailedText transactionState={transactionState} />
</>
</BodyWrap>
)
}
@@ -170,13 +181,135 @@ const CreateV3PoolSummary = ({
}
return (
<>
<BodyWrap>
<Action {...actionProps} />{' '}
<HighlightText>
{baseCurrency?.symbol}/{quoteCurrency?.symbol}{' '}
</HighlightText>
<Trans>Pool</Trans> <FailedText transactionState={transactionState} />
</>
</BodyWrap>
)
}
const CollectFeesSummary = ({
info,
transactionState,
}: {
info: CollectFeesTransactionInfo
transactionState: TransactionState
}) => {
const { currencyId0, expectedCurrencyOwed0 = '0', expectedCurrencyOwed1 = '0', currencyId1 } = info
const actionProps = {
transactionState,
pending: <Trans>Collecting</Trans>,
success: <Trans>Collected</Trans>,
failed: <Trans>Collect</Trans>,
}
return (
<BodyWrap>
<Action {...actionProps} />{' '}
<FormattedCurrencyAmount rawAmount={expectedCurrencyOwed0} currencyId={currencyId0} sigFigs={2} />{' '}
<Trans>and</Trans>{' '}
<FormattedCurrencyAmount rawAmount={expectedCurrencyOwed1} currencyId={currencyId1} sigFigs={2} />{' '}
<Trans>fees</Trans> <FailedText transactionState={transactionState} />
</BodyWrap>
)
}
const ApprovalSummary = ({
info,
transactionState,
}: {
info: ApproveTransactionInfo
transactionState: TransactionState
}) => {
const token = useToken(info.tokenAddress)
const actionProps = {
transactionState,
pending: <Trans>Approving</Trans>,
success: <Trans>Approved</Trans>,
failed: <Trans>Approve</Trans>,
}
return (
<BodyWrap>
<Action {...actionProps} /> <HighlightText>{token?.symbol}</HighlightText>{' '}
<FailedText transactionState={transactionState} />
</BodyWrap>
)
}
const ClaimSummary = ({
info: { recipient, uniAmountRaw },
transactionState,
}: {
info: ClaimTransactionInfo
transactionState: TransactionState
}) => {
const { ENSName } = useENSName()
const actionProps = {
transactionState,
pending: <Trans>Claiming</Trans>,
success: <Trans>Claimed</Trans>,
failed: <Trans>Claim</Trans>,
}
return (
<BodyWrap>
{uniAmountRaw && (
<>
<Action {...actionProps} />{' '}
<HighlightText>
{formatAmount(uniAmountRaw, 18, 4)}
UNI{' '}
</HighlightText>{' '}
<Trans>for</Trans> <HighlightText>{ENSName ?? shortenAddress(recipient)}</HighlightText>
</>
)}{' '}
<FailedText transactionState={transactionState} />
</BodyWrap>
)
}
const WrapSummary = ({
info: { chainId, currencyAmountRaw, unwrapped },
transactionState,
}: {
info: WrapTransactionInfo
transactionState: TransactionState
}) => {
const native = chainId ? nativeOnChain(chainId) : undefined
const from = unwrapped ? native?.wrapped.symbol ?? 'WETH' : native?.symbol ?? 'ETH'
const to = unwrapped ? native?.symbol ?? 'ETH' : native?.wrapped.symbol ?? 'WETH'
const amount = formatAmount(currencyAmountRaw, 18, 6)
const actionProps = unwrapped
? {
transactionState,
pending: <Trans>Unwrapping</Trans>,
success: <Trans>Unwrapped</Trans>,
failed: <Trans>Unwrap</Trans>,
}
: {
transactionState,
pending: <Trans>Wrapping</Trans>,
success: <Trans>Wrapped</Trans>,
failed: <Trans>Wrap</Trans>,
}
return (
<BodyWrap>
<Action {...actionProps} />{' '}
<HighlightText>
{amount} {from}
</HighlightText>{' '}
<Trans>to</Trans>{' '}
<HighlightText>
{amount} {to}
</HighlightText>{' '}
<FailedText transactionState={transactionState} />
</BodyWrap>
)
}
@@ -188,6 +321,14 @@ const TransactionBody = ({ info, transactionState }: { info: TransactionInfo; tr
return <AddLiquidityV3PoolSummary info={info} transactionState={transactionState} />
case TransactionType.REMOVE_LIQUIDITY_V3:
return <RemoveLiquidityV3Summary info={info} transactionState={transactionState} />
case TransactionType.WRAP:
return <WrapSummary info={info} transactionState={transactionState} />
case TransactionType.COLLECT_FEES:
return <CollectFeesSummary info={info} transactionState={transactionState} />
case TransactionType.APPROVAL:
return <ApprovalSummary info={info} transactionState={transactionState} />
case TransactionType.CLAIM:
return <ClaimSummary info={info} transactionState={transactionState} />
default:
return <span />
}

View File

@@ -25,6 +25,11 @@ const Grid = styled.a`
text-decoration: none;
border-bottom: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
padding: 12px;
&:hover {
background-color: ${({ theme }) => theme.backgroundModule};
transition: 250ms background-color ease;
}
`
const TextContainer = styled.span`

View File

@@ -1,7 +1,3 @@
import { Token } from '@uniswap/sdk-core'
import { nativeOnChain } from '../../constants/tokens'
/**
* Event names that can occur in this application.
*
@@ -41,21 +37,7 @@ export enum CUSTOM_USER_PROPERTIES {
SCREEN_RESOLUTION_HEIGHT = 'screen_resolution_height',
SCREEN_RESOLUTION_WIDTH = 'screen_resolution_width',
WALLET_ADDRESS = 'wallet_address',
WALLET_NATIVE_CURRENCY_AMOUNT = 'wallet_native_currency_amount',
WALLET_NATIVE_CURRENCY_BALANCE_USD = 'wallet_native_currency_balance_usd',
WALLET_TYPE = 'wallet_type',
WALLET_USDC_AMOUNT = 'wallet_usdc_amount',
WALLET_USDC_BALANCE_USD = 'wallet_usdc_balance_usd',
WALLET_WETH_AMOUNT = 'wallet_weth_amount',
WALLET_WETH_BALANCE_USD = 'wallet_weth_balance_usd',
}
const ETH = nativeOnChain(1)
const USDC = new Token(1, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC')
export const TOKENS_TO_TRACK = {
USDC,
WETH: ETH.wrapped,
}
export enum BROWSER {

View File

@@ -176,23 +176,28 @@ export const ButtonOutlined = styled(BaseButton)`
}
`
export const ButtonYellow = styled(BaseButton)`
background-color: ${({ theme }) => theme.deprecated_yellow3};
color: white;
export const ButtonYellow = styled(BaseButton)<{ redesignFlag?: boolean }>`
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentWarningSoft : theme.deprecated_yellow3)};
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentWarning : 'white')};
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.deprecated_yellow3)};
background-color: ${({ theme }) => darken(0.05, theme.deprecated_yellow3)};
box-shadow: ${({ theme, redesignFlag }) => !redesignFlag && `0 0 0 1pt ${theme.deprecated_yellow3}`};
background-color: ${({ theme, redesignFlag }) =>
redesignFlag ? theme.accentWarningSoft : darken(0.05, theme.deprecated_yellow3)};
}
&:hover {
background-color: ${({ theme }) => darken(0.05, theme.deprecated_yellow3)};
background: ${({ theme, redesignFlag }) => redesignFlag && theme.stateOverlayHover};
mix-blend-mode: ${({ redesignFlag }) => redesignFlag && 'normal'};
background-color: ${({ theme, redesignFlag }) => !redesignFlag && darken(0.05, theme.deprecated_yellow3)};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.deprecated_yellow3)};
background-color: ${({ theme }) => darken(0.1, theme.deprecated_yellow3)};
box-shadow: ${({ theme, redesignFlag }) => !redesignFlag && `0 0 0 1pt ${darken(0.1, theme.deprecated_yellow3)}`};
background-color: ${({ theme, redesignFlag }) =>
redesignFlag ? theme.accentWarningSoft : darken(0.1, theme.deprecated_yellow3)};
}
&:disabled {
background-color: ${({ theme }) => theme.deprecated_yellow3};
opacity: 50%;
background-color: ${({ theme, redesignFlag }) =>
redesignFlag ? theme.accentWarningSoft : theme.deprecated_yellow3};
opacity: ${({ redesignFlag }) => (redesignFlag ? '60%' : '50%')};
cursor: auto;
}
`

View File

@@ -1,7 +1,6 @@
import { Group } from '@visx/group'
import { LinePath } from '@visx/shape'
import { CurveFactory } from 'd3'
import { radius } from 'd3-curve-circlecorners'
import React from 'react'
import { ReactNode } from 'react'
import { useTheme } from 'styled-components/macro'
@@ -12,7 +11,7 @@ interface LineChartProps<T> {
getX: (t: T) => number
getY: (t: T) => number
marginTop?: number
curve?: CurveFactory
curve: CurveFactory
color?: Color
strokeWidth: number
children?: ReactNode
@@ -37,7 +36,7 @@ function LineChart<T>({
<svg width={width} height={height}>
<Group top={marginTop}>
<LinePath
curve={curve ?? radius(0.25)}
curve={curve}
stroke={color ?? theme.accentAction}
strokeWidth={strokeWidth}
data={data}

View File

@@ -1,4 +1,4 @@
import { scaleLinear } from 'd3'
import { curveCardinalOpen, scaleLinear } from 'd3'
import React from 'react'
import { useTheme } from 'styled-components/macro'
@@ -37,6 +37,7 @@ function SparklineChart({ width, height }: SparklineChartProps) {
data={pricePoints}
getX={(p: PricePoint) => timeScale(p.timestamp)}
getY={(p: PricePoint) => rdScale(p.value)}
curve={curveCardinalOpen.tension(0.9)}
marginTop={0}
color={isPositive ? theme.accentSuccess : theme.accentFailure}
strokeWidth={1.5}

View File

@@ -0,0 +1,423 @@
import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import { AutoColumn } from 'components/Column'
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
import { isSupportedChain } from 'constants/chains'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { darken } from 'polished'
import { ReactNode, useCallback, useState } from 'react'
import { Lock } from 'react-feather'
import { useLocation } from 'react-router-dom'
import styled, { useTheme } from 'styled-components/macro'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import { useCurrencyBalance } from '../../state/connection/hooks'
import { ThemedText } from '../../theme'
import { ButtonGray } from '../Button'
import CurrencyLogo from '../CurrencyLogo'
import DoubleCurrencyLogo from '../DoubleLogo'
import { Input as NumericalInput } from '../NumericalInput'
import { RowBetween, RowFixed } from '../Row'
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
import { FiatValue } from './FiatValue'
const InputPanel = styled.div<{ hideInput?: boolean; redesignFlag: boolean }>`
${({ theme }) => theme.flexColumnNoWrap}
position: relative;
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
background-color: ${({ theme, redesignFlag, hideInput }) =>
redesignFlag ? 'transparent' : hideInput ? 'transparent' : theme.deprecated_bg2};
z-index: 1;
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
transition: height 1s ease;
will-change: height;
`
const FixedContainer = styled.div<{ redesignFlag: boolean }>`
width: 100%;
height: 100%;
position: absolute;
border-radius: 20px;
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg2)};
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
`
const Container = styled.div<{ hideInput: boolean; disabled: boolean; redesignFlag: boolean }>`
min-height: ${({ redesignFlag }) => redesignFlag && '69px'};
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg0)};
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg1)};
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
${({ theme, hideInput, disabled, redesignFlag }) =>
!redesignFlag &&
!disabled &&
`
:focus,
:hover {
border: 1px solid ${hideInput ? ' transparent' : theme.deprecated_bg3};
}
`}
`
const CurrencySelect = styled(ButtonGray)<{
visible: boolean
selected: boolean
hideInput?: boolean
disabled?: boolean
redesignFlag: boolean
}>`
align-items: center;
background-color: ${({ selected, theme, redesignFlag }) =>
redesignFlag
? selected
? theme.backgroundSurface
: theme.accentAction
: selected
? theme.deprecated_bg2
: theme.deprecated_primary1};
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')};
color: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
cursor: pointer;
height: ${({ hideInput, redesignFlag }) => (redesignFlag ? 'unset' : hideInput ? '2.8rem' : '2.4rem')};
border-radius: 16px;
outline: none;
user-select: none;
border: none;
font-size: 24px;
font-weight: 400;
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
padding: ${({ selected, redesignFlag }) =>
redesignFlag ? (selected ? '4px 8px 4px 4px' : '6px 6px 6px 8px') : '0 8px'};
gap: ${({ redesignFlag }) => (redesignFlag ? '8px' : '0px')};
justify-content: space-between;
margin-left: ${({ hideInput }) => (hideInput ? '0' : '12px')};
:focus,
:hover {
background-color: ${({ selected, theme, redesignFlag }) =>
selected
? redesignFlag
? theme.backgroundSurface
: theme.deprecated_bg3
: darken(0.05, theme.deprecated_primary1)};
}
visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
`
const InputCurrencySelect = styled(CurrencySelect)<{ redesignFlag: boolean }>`
background-color: ${({ theme, selected, redesignFlag }) =>
redesignFlag && (selected ? theme.backgroundModule : theme.accentAction)};
:focus,
:hover {
background-color: ${({ selected, theme, redesignFlag }) =>
selected
? redesignFlag
? theme.backgroundInteractive
: theme.deprecated_bg3
: darken(0.05, theme.deprecated_primary1)};
}
`
const InputRow = styled.div<{ selected: boolean; redesignFlag: boolean }>`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
justify-content: space-between;
padding: ${({ selected, redesignFlag }) =>
redesignFlag ? '0px' : selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 1rem 1rem'};
`
const LabelRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
color: ${({ theme }) => theme.deprecated_text1};
font-size: 0.75rem;
line-height: 1rem;
padding: 0 1rem 1rem;
span:hover {
cursor: pointer;
color: ${({ theme }) => darken(0.2, theme.deprecated_text2)};
}
`
const FiatRow = styled(LabelRow)<{ redesignFlag: boolean }>`
justify-content: flex-end;
min-height: ${({ redesignFlag }) => redesignFlag && '32px'};
padding: ${({ redesignFlag }) => redesignFlag && '8px 0px'};
height: ${({ redesignFlag }) => !redesignFlag && '24px'};
`
const NoBalanceState = styled.div`
color: ${({ theme }) => theme.textTertiary};
font-weight: 400;
justify-content: space-between;
padding: 0px 4px 1px 4px;
`
const NoBalanceDash = styled.span`
color: ${({ theme }) => theme.textTertiary};
font-variant: small-caps;
font-feature-settings: 'pnum' on, 'lnum' on;
`
const Aligner = styled.span`
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
`
const StyledDropDown = styled(DropDown)<{ selected: boolean; redesignFlag: boolean }>`
margin: 0 0.25rem 0 0.35rem;
height: 35%;
margin-left: ${({ redesignFlag }) => redesignFlag && '8px'};
path {
stroke: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
stroke-width: ${({ redesignFlag }) => (redesignFlag ? '2px' : '1.5px')};
}
`
const StyledTokenName = styled.span<{ active?: boolean; redesignFlag: boolean }>`
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
font-size: ${({ active }) => (active ? '18px' : '18px')};
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
`
const StyledBalanceMax = styled.button<{ disabled?: boolean; redesignFlag: boolean }>`
background-color: transparent;
background-color: ${({ theme, redesignFlag }) => !redesignFlag && theme.deprecated_primary5};
border: none;
text-transform: ${({ redesignFlag }) => !redesignFlag && 'uppercase'};
border-radius: ${({ redesignFlag }) => !redesignFlag && '12px'};
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentAction : theme.deprecated_primary1)};
cursor: pointer;
font-size: ${({ redesignFlag }) => (redesignFlag ? '14px' : '11px')};
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
margin-left: ${({ redesignFlag }) => (redesignFlag ? '0px' : '0.25rem')};
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
padding: 4px 6px;
pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
:hover {
opacity: ${({ disabled }) => (!disabled ? 0.8 : 0.4)};
}
:focus {
outline: none;
}
`
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean; redesignFlag: boolean }>`
${loadingOpacityMixin};
text-align: left;
font-variant: ${({ redesignFlag }) => redesignFlag && 'small-caps'};
font-feature-settings: ${({ redesignFlag }) => redesignFlag && 'pnum on, lnum on'};
`
interface SwapCurrencyInputPanelProps {
value: string
onUserInput: (value: string) => void
onMax?: () => void
showMaxButton: boolean
label?: ReactNode
onCurrencySelect?: (currency: Currency) => void
currency?: Currency | null
hideBalance?: boolean
pair?: Pair | null
hideInput?: boolean
otherCurrency?: Currency | null
fiatValue?: CurrencyAmount<Token> | null
priceImpact?: Percent
id: string
showCommonBases?: boolean
showCurrencyAmount?: boolean
disableNonToken?: boolean
renderBalance?: (amount: CurrencyAmount<Currency>) => ReactNode
locked?: boolean
loading?: boolean
}
export default function SwapCurrencyInputPanel({
value,
onUserInput,
onMax,
showMaxButton,
onCurrencySelect,
currency,
otherCurrency,
id,
showCommonBases,
showCurrencyAmount,
disableNonToken,
renderBalance,
fiatValue,
priceImpact,
hideBalance = false,
pair = null, // used for double token logo
hideInput = false,
locked = false,
loading = false,
...rest
}: SwapCurrencyInputPanelProps) {
const [modalOpen, setModalOpen] = useState(false)
const { account, chainId } = useWeb3React()
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
const theme = useTheme()
const { pathname } = useLocation()
const isAddLiquidityPage = pathname.includes('/add') && !pathname.includes('/add/v2')
const handleDismissSearch = useCallback(() => {
setModalOpen(false)
}, [setModalOpen])
const chainAllowed = isSupportedChain(chainId)
return (
<InputPanel id={id} hideInput={hideInput} {...rest} redesignFlag={redesignFlagEnabled}>
{locked && (
<FixedContainer redesignFlag={redesignFlagEnabled}>
<AutoColumn gap="sm" justify="center">
<Lock />
<ThemedText.DeprecatedLabel fontSize="12px" textAlign="center" padding="0 12px">
<Trans>The market price is outside your specified price range. Single-asset deposit only.</Trans>
</ThemedText.DeprecatedLabel>
</AutoColumn>
</FixedContainer>
)}
<Container hideInput={hideInput} disabled={!chainAllowed} redesignFlag={redesignFlagEnabled}>
<InputRow
style={hideInput ? { padding: '0', borderRadius: '8px' } : {}}
selected={!onCurrencySelect}
redesignFlag={redesignFlagEnabled}
>
{!hideInput && (
<StyledNumericalInput
className="token-amount-input"
value={value}
onUserInput={onUserInput}
disabled={!chainAllowed}
$loading={loading}
redesignFlag={redesignFlagEnabled}
/>
)}
<InputCurrencySelect
disabled={!chainAllowed}
visible={currency !== undefined}
selected={!!currency}
hideInput={hideInput}
redesignFlag={redesignFlagEnabled}
className="open-currency-select-button"
onClick={() => {
if (onCurrencySelect) {
setModalOpen(true)
}
}}
>
<Aligner>
<RowFixed>
{pair ? (
<span style={{ marginRight: '0.5rem' }}>
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
</span>
) : currency ? (
<CurrencyLogo style={{ marginRight: '2px' }} currency={currency} size={'24px'} />
) : null}
{pair ? (
<StyledTokenName className="pair-name-container" redesignFlag={redesignFlagEnabled}>
{pair?.token0.symbol}:{pair?.token1.symbol}
</StyledTokenName>
) : (
<StyledTokenName
className="token-symbol-container"
active={Boolean(currency && currency.symbol)}
redesignFlag={redesignFlagEnabled}
>
{(currency && currency.symbol && currency.symbol.length > 20
? currency.symbol.slice(0, 4) +
'...' +
currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length)
: currency?.symbol) || <Trans>Select token</Trans>}
</StyledTokenName>
)}
</RowFixed>
{onCurrencySelect && <StyledDropDown selected={!!currency} redesignFlag={redesignFlagEnabled} />}
</Aligner>
</InputCurrencySelect>
</InputRow>
{redesignFlagEnabled && !currency && !isAddLiquidityPage && (
<NoBalanceState>
<FiatRow redesignFlag={redesignFlagEnabled}>
<RowBetween>
<NoBalanceDash>-</NoBalanceDash>
<NoBalanceDash>-</NoBalanceDash>
</RowBetween>
</FiatRow>
</NoBalanceState>
)}
{!hideInput && !hideBalance && currency && (
<FiatRow redesignFlag={redesignFlagEnabled}>
<RowBetween>
<LoadingOpacityContainer $loading={loading}>
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
</LoadingOpacityContainer>
{account ? (
<RowFixed style={{ height: '17px' }}>
<ThemedText.DeprecatedBody
onClick={onMax}
color={theme.deprecated_text3}
fontWeight={500}
fontSize={14}
style={{ display: 'inline', cursor: 'pointer' }}
>
{!hideBalance && currency && selectedCurrencyBalance ? (
renderBalance ? (
renderBalance(selectedCurrencyBalance)
) : (
<Trans>Balance: {formatCurrencyAmount(selectedCurrencyBalance, 4)}</Trans>
)
) : null}
</ThemedText.DeprecatedBody>
{showMaxButton && selectedCurrencyBalance ? (
<TraceEvent
events={[Event.onClick]}
name={EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED}
element={ElementName.MAX_TOKEN_AMOUNT_BUTTON}
>
<StyledBalanceMax onClick={onMax} redesignFlag={redesignFlagEnabled}>
<Trans>Max</Trans>
</StyledBalanceMax>
</TraceEvent>
) : null}
</RowFixed>
) : (
<span />
)}
</RowBetween>
</FiatRow>
)}
</Container>
{onCurrencySelect && (
<CurrencySearchModal
isOpen={modalOpen}
onDismiss={handleDismissSearch}
onCurrencySelect={onCurrencySelect}
selectedCurrency={currency}
otherSelectedCurrency={otherCurrency}
showCommonBases={showCommonBases}
showCurrencyAmount={showCurrencyAmount}
disableNonToken={disableNonToken}
/>
)}
</InputPanel>
)
}

View File

@@ -11,7 +11,6 @@ import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { darken } from 'polished'
import { ReactNode, useCallback, useState } from 'react'
import { Lock } from 'react-feather'
import { useLocation } from 'react-router-dom'
import styled, { useTheme } from 'styled-components/macro'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
@@ -26,37 +25,35 @@ import { RowBetween, RowFixed } from '../Row'
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
import { FiatValue } from './FiatValue'
const InputPanel = styled.div<{ hideInput?: boolean; redesignFlag: boolean }>`
const InputPanel = styled.div<{ hideInput?: boolean }>`
${({ theme }) => theme.flexColumnNoWrap}
position: relative;
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
background-color: ${({ theme, redesignFlag, hideInput }) =>
redesignFlag ? 'transparent' : hideInput ? 'transparent' : theme.deprecated_bg2};
background-color: ${({ theme, hideInput }) => (hideInput ? 'transparent' : theme.deprecated_bg2)};
z-index: 1;
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
transition: height 1s ease;
will-change: height;
`
const FixedContainer = styled.div<{ redesignFlag: boolean }>`
const FixedContainer = styled.div`
width: 100%;
height: 100%;
position: absolute;
border-radius: 20px;
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg2)};
background-color: ${({ theme }) => theme.deprecated_bg2};
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
`
const Container = styled.div<{ hideInput: boolean; disabled: boolean; redesignFlag: boolean }>`
const Container = styled.div<{ hideInput: boolean; disabled: boolean }>`
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg0)};
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg1)};
border: 1px solid ${({ theme }) => theme.deprecated_bg0};
background-color: ${({ theme }) => theme.deprecated_bg1};
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
${({ theme, hideInput, disabled, redesignFlag }) =>
!redesignFlag &&
${({ theme, hideInput, disabled }) =>
!disabled &&
`
:focus,
@@ -71,65 +68,38 @@ const CurrencySelect = styled(ButtonGray)<{
selected: boolean
hideInput?: boolean
disabled?: boolean
redesignFlag: boolean
}>`
align-items: center;
background-color: ${({ selected, theme, redesignFlag }) =>
redesignFlag
? selected
? theme.backgroundSurface
: theme.accentAction
: selected
? theme.deprecated_bg2
: theme.deprecated_primary1};
background-color: ${({ selected, theme }) => (selected ? theme.deprecated_bg2 : theme.deprecated_primary1)};
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')};
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
color: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
cursor: pointer;
height: ${({ hideInput, redesignFlag }) => (redesignFlag ? 'unset' : hideInput ? '2.8rem' : '2.4rem')};
border-radius: 16px;
outline: none;
user-select: none;
border: none;
font-size: 24px;
font-weight: 400;
font-weight: 500;
height: ${({ hideInput }) => (hideInput ? '2.8rem' : '2.4rem')};
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
padding: ${({ selected, redesignFlag }) =>
redesignFlag ? (selected ? '4px 8px 4px 4px' : '6px 6px 6px 8px') : '0 8px'};
gap: ${({ redesignFlag }) => (redesignFlag ? '8px' : '0px')};
padding: 0 8px;
justify-content: space-between;
margin-left: ${({ hideInput }) => (hideInput ? '0' : '12px')};
:focus,
:hover {
background-color: ${({ selected, theme, redesignFlag }) =>
selected
? redesignFlag
? theme.backgroundSurface
: theme.deprecated_bg3
: darken(0.05, theme.deprecated_primary1)};
background-color: ${({ selected, theme }) =>
selected ? theme.deprecated_bg3 : darken(0.05, theme.deprecated_primary1)};
}
visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
`
const InputCurrencySelect = styled(CurrencySelect)<{ redesignFlag: boolean }>`
background-color: ${({ theme, selected, redesignFlag }) =>
redesignFlag && (selected ? theme.backgroundModule : theme.accentAction)};
:focus,
:hover {
background-color: ${({ selected, theme, redesignFlag }) =>
selected
? redesignFlag
? theme.backgroundInteractive
: theme.deprecated_bg3
: darken(0.05, theme.deprecated_primary1)};
}
`
const InputRow = styled.div<{ selected: boolean; redesignFlag: boolean }>`
const InputRow = styled.div<{ selected: boolean }>`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
justify-content: space-between;
padding: ${({ selected, redesignFlag }) =>
redesignFlag ? '0px' : selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 1rem 1rem'};
padding: ${({ selected }) => (selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 1rem 1rem')};
`
const LabelRow = styled.div`
@@ -139,7 +109,6 @@ const LabelRow = styled.div`
font-size: 0.75rem;
line-height: 1rem;
padding: 0 1rem 1rem;
span:hover {
cursor: pointer;
color: ${({ theme }) => darken(0.2, theme.deprecated_text2)};
@@ -148,20 +117,8 @@ const LabelRow = styled.div`
const FiatRow = styled(LabelRow)<{ redesignFlag: boolean }>`
justify-content: flex-end;
padding: ${({ redesignFlag }) => redesignFlag && '8px 0px'};
height: ${({ redesignFlag }) => !redesignFlag && '24px'};
`
const NoBalanceState = styled.div`
color: ${({ theme }) => theme.textTertiary};
font-weight: 400;
justify-content: space-between;
padding: 0px 4px;
`
const NoBalanceDash = styled.span`
color: ${({ theme }) => theme.textTertiary};
font-variant: small-caps;
font-feature-settings: 'pnum' on, 'lnum' on;
padding: ${({ redesignFlag }) => redesignFlag && '0px 1rem 0.75rem'};
height: ${({ redesignFlag }) => (redesignFlag ? '32px' : '16px')};
`
const Aligner = styled.span`
@@ -171,34 +128,31 @@ const Aligner = styled.span`
width: 100%;
`
const StyledDropDown = styled(DropDown)<{ selected: boolean; redesignFlag: boolean }>`
const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
margin: 0 0.25rem 0 0.35rem;
height: 35%;
margin-left: ${({ redesignFlag }) => redesignFlag && '8px'};
path {
stroke: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
stroke-width: ${({ redesignFlag }) => (redesignFlag ? '2px' : '1.5px')};
stroke-width: 1.5px;
}
`
const StyledTokenName = styled.span<{ active?: boolean; redesignFlag: boolean }>`
const StyledTokenName = styled.span<{ active?: boolean }>`
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
font-size: ${({ active }) => (active ? '18px' : '18px')};
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
`
const StyledBalanceMax = styled.button<{ disabled?: boolean; redesignFlag: boolean }>`
const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
background-color: transparent;
background-color: ${({ theme, redesignFlag }) => !redesignFlag && theme.deprecated_primary5};
background-color: ${({ theme }) => theme.deprecated_primary5};
border: none;
text-transform: ${({ redesignFlag }) => !redesignFlag && 'uppercase'};
border-radius: ${({ redesignFlag }) => !redesignFlag && '12px'};
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentAction : theme.deprecated_primary1)};
border-radius: 12px;
color: ${({ theme }) => theme.deprecated_primary1};
cursor: pointer;
font-size: ${({ redesignFlag }) => (redesignFlag ? '14px' : '11px')};
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
margin-left: ${({ redesignFlag }) => (redesignFlag ? '0px' : '0.25rem')};
font-size: 11px;
font-weight: 500;
margin-left: 0.25rem;
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
padding: 4px 6px;
pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
@@ -212,11 +166,9 @@ const StyledBalanceMax = styled.button<{ disabled?: boolean; redesignFlag: boole
}
`
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean; redesignFlag: boolean }>`
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean }>`
${loadingOpacityMixin};
text-align: left;
font-variant: ${({ redesignFlag }) => redesignFlag && 'small-caps'};
font-feature-settings: ${({ redesignFlag }) => redesignFlag && 'pnum on, lnum on'};
`
interface CurrencyInputPanelProps {
@@ -266,12 +218,10 @@ export default function CurrencyInputPanel({
}: CurrencyInputPanelProps) {
const [modalOpen, setModalOpen] = useState(false)
const { account, chainId } = useWeb3React()
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
const theme = useTheme()
const { pathname } = useLocation()
const isAddLiquidityPage = pathname.includes('/add') && !pathname.includes('/add/v2')
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const handleDismissSearch = useCallback(() => {
setModalOpen(false)
@@ -280,9 +230,9 @@ export default function CurrencyInputPanel({
const chainAllowed = isSupportedChain(chainId)
return (
<InputPanel id={id} hideInput={hideInput} {...rest} redesignFlag={redesignFlagEnabled}>
<InputPanel id={id} hideInput={hideInput} {...rest}>
{locked && (
<FixedContainer redesignFlag={redesignFlagEnabled}>
<FixedContainer>
<AutoColumn gap="sm" justify="center">
<Lock />
<ThemedText.DeprecatedLabel fontSize="12px" textAlign="center" padding="0 12px">
@@ -291,12 +241,8 @@ export default function CurrencyInputPanel({
</AutoColumn>
</FixedContainer>
)}
<Container hideInput={hideInput} disabled={!chainAllowed} redesignFlag={redesignFlagEnabled}>
<InputRow
style={hideInput ? { padding: '0', borderRadius: '8px' } : {}}
selected={!onCurrencySelect}
redesignFlag={redesignFlagEnabled}
>
<Container hideInput={hideInput} disabled={!chainAllowed}>
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={!onCurrencySelect}>
{!hideInput && (
<StyledNumericalInput
className="token-amount-input"
@@ -304,16 +250,14 @@ export default function CurrencyInputPanel({
onUserInput={onUserInput}
disabled={!chainAllowed}
$loading={loading}
redesignFlag={redesignFlagEnabled}
/>
)}
<InputCurrencySelect
<CurrencySelect
disabled={!chainAllowed}
visible={currency !== undefined}
selected={!!currency}
hideInput={hideInput}
redesignFlag={redesignFlagEnabled}
className="open-currency-select-button"
onClick={() => {
if (onCurrencySelect) {
@@ -328,40 +272,26 @@ export default function CurrencyInputPanel({
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
</span>
) : currency ? (
<CurrencyLogo style={{ marginRight: '2px' }} currency={currency} size={'24px'} />
<CurrencyLogo style={{ marginRight: '0.5rem' }} currency={currency} size={'24px'} />
) : null}
{pair ? (
<StyledTokenName className="pair-name-container" redesignFlag={redesignFlagEnabled}>
<StyledTokenName className="pair-name-container">
{pair?.token0.symbol}:{pair?.token1.symbol}
</StyledTokenName>
) : (
<StyledTokenName
className="token-symbol-container"
active={Boolean(currency && currency.symbol)}
redesignFlag={redesignFlagEnabled}
>
<StyledTokenName className="token-symbol-container" active={Boolean(currency && currency.symbol)}>
{(currency && currency.symbol && currency.symbol.length > 20
? currency.symbol.slice(0, 4) +
'...' +
currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length)
: currency?.symbol) || <Trans>Select token</Trans>}
: currency?.symbol) || <Trans>Select a token</Trans>}
</StyledTokenName>
)}
</RowFixed>
{onCurrencySelect && <StyledDropDown selected={!!currency} redesignFlag={redesignFlagEnabled} />}
{onCurrencySelect && <StyledDropDown selected={!!currency} />}
</Aligner>
</InputCurrencySelect>
</CurrencySelect>
</InputRow>
{redesignFlagEnabled && !currency && !isAddLiquidityPage && (
<NoBalanceState>
<FiatRow redesignFlag={redesignFlagEnabled}>
<RowBetween>
<NoBalanceDash>-</NoBalanceDash>
<NoBalanceDash>-</NoBalanceDash>
</RowBetween>
</FiatRow>
</NoBalanceState>
)}
{!hideInput && !hideBalance && currency && (
<FiatRow redesignFlag={redesignFlagEnabled}>
<RowBetween>
@@ -391,8 +321,8 @@ export default function CurrencyInputPanel({
name={EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED}
element={ElementName.MAX_TOKEN_AMOUNT_BUTTON}
>
<StyledBalanceMax onClick={onMax} redesignFlag={redesignFlagEnabled}>
<Trans>Max</Trans>
<StyledBalanceMax onClick={onMax}>
<Trans>MAX</Trans>
</StyledBalanceMax>
</TraceEvent>
) : null}

View File

@@ -24,11 +24,13 @@ const StyledNativeLogo = styled(StyledLogo)`
export default function CurrencyLogo({
currency,
symbol,
size = '24px',
style,
...rest
}: {
currency?: Currency | null
symbol?: string | null
size?: string
style?: React.CSSProperties
}) {
@@ -36,6 +38,7 @@ export default function CurrencyLogo({
alt: `${currency?.symbol ?? 'token'} logo`,
size,
srcs: useCurrencyLogoURIs(currency),
symbol: symbol ?? currency?.symbol,
style,
...rest,
}

View File

@@ -18,7 +18,7 @@ interface DoubleCurrencyLogoProps {
}
const HigherLogo = styled(CurrencyLogo)`
z-index: 2;
z-index: 1;
`
const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>`
position: absolute;

View File

@@ -1,13 +1,12 @@
import { FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { TokensVariant, useTokensFlag } from 'featureFlags/flags/tokens'
import { TokenSafetyVariant, useTokenSafetyFlag } from 'featureFlags/flags/tokenSafety'
import { TokensNetworkFilterVariant, useTokensNetworkFilterFlag } from 'featureFlags/flags/tokensNetworkFilter'
import { useWalletFlag, WalletVariant } from 'featureFlags/flags/wallet'
import { useAtomValue } from 'jotai/utils'
import { ReactNode, useState } from 'react'
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
import { X } from 'react-feather'
import { useModalIsOpen, useToggleFeatureFlags } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
@@ -46,7 +45,14 @@ const Row = styled.div`
const CloseButton = styled.button`
cursor: pointer;
background: 'transparent';
background: transparent;
border: none;
color: ${({ theme }) => theme.textPrimary};
`
const ToggleButton = styled.button`
cursor: pointer;
background: transparent;
border: none;
color: ${({ theme }) => theme.textPrimary};
`
@@ -60,7 +66,6 @@ const Header = styled(Row)`
const FlagName = styled.span`
font-size: 16px;
line-height: 20px;
padding-left: 8px;
color: ${({ theme }) => theme.textPrimary};
`
const FlagGroupName = styled.span`
@@ -116,17 +121,53 @@ function Variant({ option }: { option: string }) {
return <option value={option}>{option}</option>
}
function FeatureFlagOption({
variants,
featureFlag,
value,
label,
}: {
variants: string[]
interface FeatureFlagProps {
variant: Record<string, string>
featureFlag: FeatureFlag
value: string
label: string
}) {
}
function FeatureFlagGroup({ name, children }: PropsWithChildren<{ name: string }>) {
// type FeatureFlagOption = { props: FeatureFlagProps }
const togglableOptions = Children.toArray(children)
.filter<ReactElement<FeatureFlagProps>>(
(child): child is ReactElement<FeatureFlagProps> =>
child instanceof Object && 'type' in child && child.type === FeatureFlagOption
)
.map(({ props }) => props)
.filter(({ variant }) => {
const values = Object.values(variant)
return values.includes(BaseVariant.Control) && values.includes(BaseVariant.Enabled)
})
const setFeatureFlags = useUpdateAtom(featureFlagSettings)
const allEnabled = togglableOptions.every(({ value }) => value === BaseVariant.Enabled)
const onToggle = useCallback(() => {
setFeatureFlags((flags) => ({
...flags,
...togglableOptions.reduce(
(flags, { featureFlag }) => ({
...flags,
[featureFlag]: allEnabled ? BaseVariant.Control : BaseVariant.Enabled,
}),
{}
),
}))
}, [allEnabled, setFeatureFlags, togglableOptions])
return (
<>
<Row key={name}>
<FlagGroupName>{name}</FlagGroupName>
<ToggleButton onClick={onToggle}>{allEnabled ? 'Disable' : 'Enable'} group</ToggleButton>
</Row>
{children}
</>
)
}
function FeatureFlagOption({ variant, featureFlag, value, label }: FeatureFlagProps) {
const updateFlag = useUpdateFlag()
const [count, setCount] = useState(0)
const featureFlags = useAtomValue(featureFlagSettings)
@@ -145,7 +186,7 @@ function FeatureFlagOption({
}}
value={featureFlags[featureFlag]}
>
{variants.map((variant) => (
{Object.values(variant).map((variant) => (
<Variant key={variant} option={variant} />
))}
</FlagVariantSelection>
@@ -165,51 +206,42 @@ export default function FeatureFlagModal() {
<X size={24} />
</CloseButton>
</Header>
<FlagGroupName>Phase 1</FlagGroupName>
<FeatureFlagOption
variants={Object.values(NftVariant)}
value={useNftFlag()}
featureFlag={FeatureFlag.nft}
label="NFTs"
/>
<FlagGroupName>Phase 0</FlagGroupName>
<FeatureFlagOption
variants={Object.values(RedesignVariant)}
value={useRedesignFlag()}
featureFlag={FeatureFlag.redesign}
label="Redesign"
/>
<FeatureFlagOption
variants={Object.values(NavBarVariant)}
value={useNavBarFlag()}
featureFlag={FeatureFlag.navBar}
label="NavBar"
/>
<FeatureFlagOption
variants={Object.values(TokensVariant)}
value={useTokensFlag()}
featureFlag={FeatureFlag.tokens}
label="Tokens"
/>
<FeatureFlagOption
variants={Object.values(TokensNetworkFilterVariant)}
value={useTokensNetworkFilterFlag()}
featureFlag={FeatureFlag.tokensNetworkFilter}
label="Tokens Network Filter"
/>
<FeatureFlagOption
variants={Object.values(TokenSafetyVariant)}
value={useTokenSafetyFlag()}
featureFlag={FeatureFlag.tokenSafety}
label="Token Safety"
/>
<FeatureFlagOption
variants={Object.values(WalletVariant)}
value={useWalletFlag()}
featureFlag={FeatureFlag.wallet}
label="Wallet Flag"
/>
<SaveButton onClick={() => window.location.reload()}>Save Settings</SaveButton>
<FeatureFlagGroup name="Phase 0">
<FeatureFlagOption
variant={RedesignVariant}
value={useRedesignFlag()}
featureFlag={FeatureFlag.redesign}
label="Redesign"
/>
<FeatureFlagOption
variant={NavBarVariant}
value={useNavBarFlag()}
featureFlag={FeatureFlag.navBar}
label="NavBar"
/>
<FeatureFlagOption
variant={TokensVariant}
value={useTokensFlag()}
featureFlag={FeatureFlag.tokens}
label="Tokens"
/>
<FeatureFlagOption
variant={TokensNetworkFilterVariant}
value={useTokensNetworkFilterFlag()}
featureFlag={FeatureFlag.tokensNetworkFilter}
label="Tokens Network Filter"
/>
<FeatureFlagOption
variant={TokenSafetyVariant}
value={useTokenSafetyFlag()}
featureFlag={FeatureFlag.tokenSafety}
label="Token Safety"
/>
</FeatureFlagGroup>
<FeatureFlagGroup name="Phase 1">
<FeatureFlagOption variant={NftVariant} value={useNftFlag()} featureFlag={FeatureFlag.nft} label="NFTs" />
</FeatureFlagGroup>
<SaveButton onClick={() => window.location.reload()}>Reload</SaveButton>
</Modal>
)
}

View File

@@ -15,7 +15,7 @@ const ResponsiveText = styled(ThemedText.DeprecatedLabel)`
line-height: 16px;
font-size: 14px;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
font-size: 12px;
line-height: 12px;
`};

View File

@@ -39,7 +39,7 @@ const Wrapper = styled.div`
padding: 16px 20px;
position: absolute;
right: 16px;
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToMedium}px) {
display: block;
}
`

View File

@@ -11,7 +11,6 @@ import { useCloseModal, useModalIsOpen, useOpenModal, useToggleModal } from 'sta
import { ApplicationModal } from 'state/application/reducer'
import styled from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
import { isChainAllowed } from 'utils/switchChain'
import { isMobile } from 'utils/userAgent'
const ActiveRowLinkList = styled.div`
@@ -53,7 +52,7 @@ const FlyoutMenu = styled.div`
width: 272px;
z-index: 99;
padding-top: 10px;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
top: 40px;
}
`
@@ -112,7 +111,7 @@ const NetworkLabel = styled.div`
`
const SelectorLabel = styled(NetworkLabel)`
display: none;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
display: block;
margin-right: 8px;
}
@@ -126,7 +125,7 @@ const NetworkAlertLabel = styled(NetworkLabel)`
font-size: 1rem;
width: fit-content;
font-weight: 500;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
display: block;
}
`
@@ -153,12 +152,12 @@ const SelectorControls = styled.div<{ supportedChain: boolean }>`
}
`
const SelectorLogo = styled(Logo)`
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
margin-right: 8px;
}
`
const SelectorWrapper = styled.div`
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
position: relative;
}
`
@@ -328,18 +327,16 @@ export default function NetworkSelector() {
<FlyoutHeader>
<Trans>Select a {!onSupportedChain ? ' supported ' : ''}network</Trans>
</FlyoutHeader>
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) =>
isChainAllowed(chainId) ? (
<Row
onSelectChain={async (targetChainId: SupportedChainId) => {
await selectChain(targetChainId)
closeModal()
}}
targetChain={chainId}
key={chainId}
/>
) : null
)}
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) => (
<Row
onSelectChain={async (targetChainId: SupportedChainId) => {
await selectChain(targetChainId)
closeModal()
}}
targetChain={chainId}
key={chainId}
/>
))}
</FlyoutMenuContents>
</FlyoutMenu>
)}

View File

@@ -26,7 +26,7 @@ const StyledPolling = styled.div<{ warning: boolean }>`
color: ${({ theme, warning }) => (warning ? theme.deprecated_yellow3 : theme.deprecated_green1)};
transition: 250ms ease color;
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
display: none;
`}
`

View File

@@ -1,11 +1,9 @@
import { Trans } from '@lingui/macro'
import useScrollPosition from '@react-hook/window-scroll'
import { useWeb3React } from '@web3-react/core'
import WalletDropdown from 'components/WalletDropdown'
import { getChainInfoOrDefault } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import { TokensVariant, useTokensFlag } from 'featureFlags/flags/tokens'
import { useWalletFlag, WalletVariant } from 'featureFlags/flags/wallet'
import { darken } from 'polished'
import { NavLink, useLocation } from 'react-router-dom'
import { Text } from 'rebass'
@@ -48,16 +46,16 @@ const HeaderFrame = styled.div<{ showBackground: boolean }>`
transition: background-position 0.1s, box-shadow 0.1s;
background-blend-mode: hard-light;
${({ theme }) => theme.mediaWidth.upToLarge`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToLarge`
grid-template-columns: 48px 1fr 1fr;
`};
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
padding: 1rem;
grid-template-columns: 1fr 1fr;
`};
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
padding: 1rem;
grid-template-columns: 36px 1fr;
`};
@@ -83,7 +81,7 @@ const HeaderElement = styled.div`
margin-left: 8px;
}
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
align-items: center;
`};
`
@@ -99,13 +97,13 @@ const HeaderLinks = styled(Row)`
grid-gap: 10px;
overflow: auto;
align-items: center;
${({ theme }) => theme.mediaWidth.upToLarge`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToLarge`
justify-self: start;
`};
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
justify-self: center;
`};
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
flex-direction: row;
justify-content: space-between;
justify-self: center;
@@ -159,7 +157,7 @@ const UNIWrapper = styled.span`
`
const BalanceText = styled(Text)`
${({ theme }) => theme.mediaWidth.upToExtraSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToExtraSmall`
display: none;
`};
`
@@ -170,7 +168,7 @@ const Title = styled.a`
pointer-events: auto;
justify-self: flex-start;
margin-right: 12px;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
justify-self: center;
`};
:hover {
@@ -219,12 +217,6 @@ const StyledNavLink = styled(NavLink)`
}
`
const WalletDropdownWrapper = styled.div`
position: absolute;
top: 75px;
right: 20px;
`
const StyledExternalLink = styled(ExternalLink)`
${({ theme }) => theme.flexRowNoWrap}
align-items: left;
@@ -252,7 +244,6 @@ const StyledExternalLink = styled(ExternalLink)`
`
export default function Header() {
const walletFlag = useWalletFlag()
const tokensFlag = useTokensFlag()
const { account, chainId } = useWeb3React()
@@ -355,11 +346,6 @@ export default function Header() {
) : null}
<Web3Status />
</AccountElement>
{walletFlag === WalletVariant.Enabled && (
<WalletDropdownWrapper>
<WalletDropdown />
</WalletDropdownWrapper>
)}
</HeaderElement>
<HeaderElement>
<Menu />

View File

@@ -0,0 +1,32 @@
import { ChevronDown, ChevronUp } from 'react-feather'
import styled from 'styled-components/macro'
export const StyledChevronDown = styled(ChevronDown)<{ customColor?: string }>`
color: ${({ theme, customColor }) => customColor ?? theme.textSecondary};
height: 20px;
width: 20px;
&:hover {
color: ${({ theme }) => theme.accentActionSoft};
transition: ${({
theme: {
transition: { duration, timing },
},
}) => `${duration.fast}ms color ${timing.in}`};
}
`
export const StyledChevronUp = styled(ChevronUp)<{ customColor?: string }>`
color: ${({ theme, customColor }) => customColor ?? theme.textSecondary};
height: 20px;
width: 20px;
&:hover {
color: ${({ theme }) => theme.accentActionSoft};
transition: ${({
theme: {
transition: { duration, timing },
},
}) => `${duration.fast}ms color ${timing.in}`};
}
`

View File

@@ -1,11 +1,18 @@
import { useWeb3React } from '@web3-react/core'
import { ConnectionType } from 'connection'
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
import useENSAvatar from 'hooks/useENSAvatar'
import styled from 'styled-components/macro'
import { colors } from 'theme/colors'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import sockImg from '../../assets/svg/socks.svg'
import { useHasSocks } from '../../hooks/useSocksBalance'
import Identicon from '../Identicon'
const IconWrapper = styled.div<{ size?: number }>`
position: relative;
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
@@ -15,24 +22,62 @@ const IconWrapper = styled.div<{ size?: number }>`
height: ${({ size }) => (size ? size + 'px' : '32px')};
width: ${({ size }) => (size ? size + 'px' : '32px')};
}
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
align-items: flex-end;
`};
`
export default function StatusIcon({ connectionType, size }: { connectionType: ConnectionType; size?: number }) {
let image
switch (connectionType) {
case ConnectionType.INJECTED:
image = <Identicon />
break
case ConnectionType.WALLET_CONNECT:
image = <img src={WalletConnectIcon} alt="WalletConnect" />
break
case ConnectionType.COINBASE_WALLET:
image = <img src={CoinbaseWalletIcon} alt="Coinbase Wallet" />
break
const SockContainer = styled.div`
position: absolute;
background-color: ${colors.pink400};
display: flex;
justify-content: center;
border-radius: 50%;
width: 16px;
height: 16px;
bottom: -5px;
right: -5px;
`
const SockImg = styled.img`
width: 7.5px;
height: 10px;
margin-top: 3px;
`
const Socks = () => {
return (
<SockContainer>
<SockImg src={sockImg} />
</SockContainer>
)
}
const useIcon = (connectionType: ConnectionType) => {
const { account } = useWeb3React()
const { avatar } = useENSAvatar(account ?? undefined)
const isNavbarEnabled = useNavBarFlag() === NavBarVariant.Enabled
if ((isNavbarEnabled && avatar) || connectionType === ConnectionType.INJECTED) {
return <Identicon />
} else if (connectionType === ConnectionType.WALLET_CONNECT) {
return <img src={WalletConnectIcon} alt="WalletConnect" />
} else if (connectionType === ConnectionType.COINBASE_WALLET) {
return <img src={CoinbaseWalletIcon} alt="Coinbase Wallet" />
}
return <IconWrapper size={size ?? 16}>{image}</IconWrapper>
return undefined
}
export default function StatusIcon({ connectionType, size }: { connectionType: ConnectionType; size?: number }) {
const hasSocks = useHasSocks()
const isNavbarEnabled = useNavBarFlag() === NavBarVariant.Enabled
const icon = useIcon(connectionType)
return (
<IconWrapper size={size ?? 16}>
{isNavbarEnabled && hasSocks && <Socks />}
{icon}
</IconWrapper>
)
}

View File

@@ -1,12 +1,13 @@
import jazzicon from '@metamask/jazzicon'
import { useWeb3React } from '@web3-react/core'
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
import useENSAvatar from 'hooks/useENSAvatar'
import { useLayoutEffect, useMemo, useRef, useState } from 'react'
import styled from 'styled-components/macro'
const StyledIdenticon = styled.div`
height: 1rem;
width: 1rem;
const StyledIdenticon = styled.div<{ isNavbarEnabled: boolean }>`
height: ${({ isNavbarEnabled }) => (isNavbarEnabled ? '24px' : '1rem')};
width: ${({ isNavbarEnabled }) => (isNavbarEnabled ? '24px' : '1rem')};
border-radius: 1.125rem;
background-color: ${({ theme }) => theme.deprecated_bg4};
font-size: initial;
@@ -22,8 +23,10 @@ export default function Identicon() {
const { account } = useWeb3React()
const { avatar } = useENSAvatar(account ?? undefined)
const [fetchable, setFetchable] = useState(true)
const isNavbarEnabled = useNavBarFlag() === NavBarVariant.Enabled
const iconSize = isNavbarEnabled ? 24 : 16
const icon = useMemo(() => account && jazzicon(16, parseInt(account.slice(2, 10), 16)), [account])
const icon = useMemo(() => account && jazzicon(iconSize, parseInt(account.slice(2, 10), 16)), [account, iconSize])
const iconRef = useRef<HTMLDivElement>(null)
useLayoutEffect(() => {
const current = iconRef.current
@@ -41,7 +44,7 @@ export default function Identicon() {
}, [icon, iconRef])
return (
<StyledIdenticon>
<StyledIdenticon isNavbarEnabled={isNavbarEnabled}>
{avatar && fetchable ? (
<StyledAvatar alt="avatar" src={avatar} onError={() => setFetchable(false)}></StyledAvatar>
) : (

View File

@@ -48,11 +48,11 @@ const StyledInput = styled(NumericalInput)<{ usePercent?: boolean }>`
font-weight: 500;
padding: 0 10px;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
font-size: 16px;
`};
${({ theme }) => theme.mediaWidth.upToExtraSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToExtraSmall`
font-size: 12px;
`};
`

View File

@@ -14,13 +14,15 @@ export default function ListLogo({
style,
size = '24px',
alt,
symbol,
}: {
logoURI: string
size?: string
style?: React.CSSProperties
alt?: string
symbol?: string
}) {
const srcs: string[] = useHttpLocations(logoURI)
return <StyledListLogo alt={alt} size={size} srcs={srcs} style={style} />
return <StyledListLogo alt={alt} size={size} symbol={symbol} srcs={srcs} style={style} />
}

View File

@@ -1,22 +1,34 @@
import { useState } from 'react'
import { Slash } from 'react-feather'
import { ImageProps } from 'rebass'
import { useTheme } from 'styled-components/macro'
import styled from 'styled-components/macro'
const BAD_SRCS: { [tokenAddress: string]: true } = {}
interface LogoProps extends Pick<ImageProps, 'style' | 'alt' | 'className'> {
srcs: string[]
symbol?: string
size?: string
}
const MissingImageLogo = styled.div<{ size?: string }>`
--size: ${({ size }) => size};
border-radius: 100px;
color: ${({ theme }) => theme.textPrimary};
background-color: ${({ theme }) => theme.backgroundInteractive};
font-size: calc(var(--size) / 3);
font-weight: 500;
height: ${({ size }) => size ?? '24px'};
line-height: ${({ size }) => size ?? '24px'};
text-align: center;
width: ${({ size }) => size ?? '24px'};
`
/**
* Renders an image by sequentially trying a list of URIs, and then eventually a fallback triangle alert
*/
export default function Logo({ srcs, alt, style, ...rest }: LogoProps) {
export default function Logo({ srcs, alt, style, size, symbol, ...rest }: LogoProps) {
const [, refresh] = useState<number>(0)
const theme = useTheme()
const src: string | undefined = srcs.find((src) => !BAD_SRCS[src])
if (src) {
@@ -34,5 +46,10 @@ export default function Logo({ srcs, alt, style, ...rest }: LogoProps) {
)
}
return <Slash {...rest} style={{ ...style, color: theme.deprecated_bg4 }} />
return (
<MissingImageLogo size={size}>
{/* use only first 3 characters of Symbol for design reasons */}
{symbol?.toUpperCase().replace('$', '').replace(/\s+/g, '').slice(0, 3)}
</MissingImageLogo>
)
}

View File

@@ -110,7 +110,7 @@ const MenuFlyout = styled.span<{ flyoutAlignment?: FlyoutAlignment }>`
: css`
left: 0rem;
`};
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
bottom: unset;
right: 0;
left: unset;

View File

@@ -59,11 +59,11 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, rede
`}
display: flex;
border-radius: 20px;
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
width: 65vw;
margin: 0;
`}
${({ theme, mobile }) => theme.mediaWidth.upToSmall`
${({ theme, mobile }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
width: 85vw;
${
mobile &&

View File

@@ -1,37 +1,47 @@
import { style } from '@vanilla-extract/css'
import { lightGrayOverlayOnHover } from 'nft/css/common.css'
import { sprinkles } from '../../nft/css/sprinkles.css'
import { breakpoints, sprinkles } from '../../nft/css/sprinkles.css'
export const ChainSwitcher = style([
lightGrayOverlayOnHover,
sprinkles({
background: 'lightGrayContainer',
borderRadius: '8',
paddingY: '8',
paddingX: '12',
cursor: 'pointer',
border: 'none',
color: 'blackBlue',
background: 'none',
}),
])
export const ChainSwitcherRow = style([
lightGrayOverlayOnHover,
sprinkles({
border: 'none',
color: 'blackBlue',
justifyContent: 'space-between',
paddingX: '16',
paddingY: '12',
paddingX: '8',
paddingY: '8',
cursor: 'pointer',
color: 'blackBlue',
borderRadius: '12',
width: { sm: 'full' },
}),
{
lineHeight: '24px',
width: '308px',
'@media': {
[`screen and (min-width: ${breakpoints.sm}px)`]: {
width: '204px',
},
},
},
])
export const Image = style([
sprinkles({
width: '28',
height: '28',
width: '20',
height: '20',
}),
])
@@ -41,9 +51,3 @@ export const Icon = style([
marginRight: '12',
}),
])
export const Indicator = style([
sprinkles({
marginLeft: '8',
}),
])

View File

@@ -1,16 +1,18 @@
import { useWeb3React } from '@web3-react/core'
import { StyledChevronDown, StyledChevronUp } from 'components/Icons'
import { getChainInfo } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import useSelectChain from 'hooks/useSelectChain'
import useSyncChainQuery from 'hooks/useSyncChainQuery'
import { Box } from 'nft/components/Box'
import { Portal } from 'nft/components/common/Portal'
import { Column, Row } from 'nft/components/Flex'
import { NewChevronDownIcon, NewChevronUpIcon } from 'nft/components/icons'
import { CheckMarkIcon } from 'nft/components/icons'
import { CheckMarkIcon, TokenWarningRedIcon } from 'nft/components/icons'
import { subhead } from 'nft/css/common.css'
import { themeVars, vars } from 'nft/css/sprinkles.css'
import { useIsMobile } from 'nft/hooks'
import { ReactNode, useReducer, useRef } from 'react'
import { isChainAllowed } from 'utils/switchChain'
import * as styles from './ChainSwitcher.css'
import { NavDropdown } from './NavDropdown'
@@ -27,18 +29,20 @@ const ChainRow = ({
const { label, logoUrl } = getChainInfo(targetChain)
return (
<Row
as="button"
background={active ? 'lightGrayContainer' : 'none'}
className={`${styles.ChainSwitcherRow} ${subhead}`}
onClick={() => onSelectChain(targetChain)}
>
<ChainDetails>
<img src={logoUrl} alt={label} className={styles.Icon} />
{label}
</ChainDetails>
{active && <CheckMarkIcon width={20} height={20} />}
</Row>
<Column borderRadius="12">
<Row
as="button"
background="none"
className={`${styles.ChainSwitcherRow} ${subhead}`}
onClick={() => onSelectChain(targetChain)}
>
<ChainDetails>
<img src={logoUrl} alt={label} className={styles.Icon} />
{label}
</ChainDetails>
{active && <CheckMarkIcon width={20} height={20} color={vars.color.blue400} />}
</Row>
</Column>
)
}
@@ -53,56 +57,73 @@ const NETWORK_SELECTOR_CHAINS = [
]
interface ChainSwitcherProps {
isMobile?: boolean
leftAlign?: boolean
}
export const ChainSwitcher = ({ isMobile }: ChainSwitcherProps) => {
export const ChainSwitcher = ({ leftAlign }: ChainSwitcherProps) => {
const { chainId } = useWeb3React()
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
const isMobile = useIsMobile()
const ref = useRef<HTMLDivElement>(null)
useOnClickOutside(ref, isOpen ? toggleOpen : undefined)
const modalRef = useRef<HTMLDivElement>(null)
useOnClickOutside(ref, isOpen ? toggleOpen : undefined, [modalRef])
const info = chainId ? getChainInfo(chainId) : undefined
const selectChain = useSelectChain()
useSyncChainQuery()
if (!chainId || !info) {
if (!chainId) {
return null
}
const isSupported = !!info
const dropdown = (
<NavDropdown top="56" left={leftAlign ? '0' : 'auto'} right={leftAlign ? 'auto' : '0'} ref={modalRef}>
<Column marginX="8">
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) => (
<ChainRow
onSelectChain={async (targetChainId: SupportedChainId) => {
await selectChain(targetChainId)
toggleOpen()
}}
targetChain={chainId}
key={chainId}
/>
))}
</Column>
</NavDropdown>
)
return (
<Box position="relative" ref={ref}>
<Row as="button" gap="8" className={styles.ChainSwitcher} onClick={toggleOpen}>
<img src={info.logoUrl} alt={info.label} className={styles.Image} />
<Box as="span" className={subhead} color="explicitWhite" style={{ lineHeight: '20px' }}>
{info.label}
</Box>
{isOpen ? (
<NewChevronUpIcon width={16} height={16} color="darkGray" />
<Row
as="button"
gap="8"
className={styles.ChainSwitcher}
background={isOpen ? 'accentActiveSoft' : 'none'}
onClick={toggleOpen}
>
{!isSupported ? (
<>
<TokenWarningRedIcon fill={themeVars.colors.darkGray} width={24} height={24} />
<Box as="span" className={subhead} display={{ sm: 'none', xxl: 'flex' }} style={{ lineHeight: '20px' }}>
Unsupported
</Box>
</>
) : (
<NewChevronDownIcon width={16} height={16} color="darkGray" />
<>
<img src={info.logoUrl} alt={info.label} className={styles.Image} />
<Box as="span" className={subhead} display={{ sm: 'none', xxl: 'flex' }} style={{ lineHeight: '20px' }}>
{info.label}
</Box>
</>
)}
{isOpen ? <StyledChevronUp /> : <StyledChevronDown />}
</Row>
{isOpen && (
<NavDropdown top={60} leftAligned={isMobile}>
<Column gap="4">
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) =>
isChainAllowed(chainId) ? (
<ChainRow
onSelectChain={async (targetChainId: SupportedChainId) => {
await selectChain(targetChainId)
toggleOpen()
}}
targetChain={chainId}
key={chainId}
/>
) : null
)}
</Column>
</NavDropdown>
)}
{isOpen && (isMobile ? <Portal>{dropdown}</Portal> : <>{dropdown}</>)}
</Box>
)
}

View File

@@ -1,13 +1,27 @@
import { style } from '@vanilla-extract/css'
import { sprinkles, themeVars } from '../../nft/css/sprinkles.css'
import { sprinkles, themeVars, vars } from '../../nft/css/sprinkles.css'
export const hover = style([
sprinkles({
transition: '250',
borderRadius: '12',
}),
{
':hover': {
background: vars.color.lightGrayOverlay,
},
},
])
export const MenuRow = style([
hover,
sprinkles({
color: 'blackBlue',
paddingY: '12',
width: 'max',
marginRight: '52',
paddingY: '8',
paddingX: '8',
width: 'full',
whiteSpace: 'nowrap',
}),
{
lineHeight: '24px',
@@ -22,8 +36,10 @@ export const PrimaryText = style([
])
export const SecondaryText = style([
hover,
sprinkles({
paddingY: '8',
paddingX: '8',
color: 'darkGray',
}),
{
@@ -34,6 +50,7 @@ export const SecondaryText = style([
export const Separator = style([
sprinkles({
height: '0',
marginX: '16',
}),
{
borderTop: 'solid',
@@ -45,6 +62,6 @@ export const Separator = style([
export const IconRow = style([
sprinkles({
paddingX: '16',
paddingY: '8',
justifyContent: { sm: 'center', md: 'flex-start' },
}),
])

View File

@@ -1,3 +1,4 @@
import { Trans } from '@lingui/macro'
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
@@ -124,58 +125,78 @@ export const MenuDropdown = () => {
return (
<>
<Box position="relative" ref={ref}>
<NavIcon onClick={toggleOpen}>
<EllipsisIcon width={28} height={28} />
<NavIcon isActive={isOpen} onClick={toggleOpen}>
<EllipsisIcon />
</NavIcon>
{isOpen && (
<NavDropdown top={60}>
<Column gap="12">
<Column paddingX="16" gap="4">
<NavDropdown top={{ sm: 'unset', lg: '56' }} bottom={{ sm: '56', lg: 'unset' }} right="0">
<Column gap="16">
<Column paddingX="8" gap="4">
{nftFlag === NftVariant.Enabled && (
<PrimaryMenuRow to="/nft/sell" close={toggleOpen}>
<Icon>
<ThinTagIcon width={24} height={24} />
</Icon>
<PrimaryMenuRow.Text>Sell NFTs</PrimaryMenuRow.Text>
<PrimaryMenuRow.Text>
<Trans>Sell NFTs</Trans>
</PrimaryMenuRow.Text>
</PrimaryMenuRow>
)}
<PrimaryMenuRow to="/vote" close={toggleOpen}>
<Icon>
<GovernanceIcon width={24} height={24} />
</Icon>
<PrimaryMenuRow.Text>Vote in governance</PrimaryMenuRow.Text>
<PrimaryMenuRow.Text>
<Trans>Vote in governance</Trans>
</PrimaryMenuRow.Text>
</PrimaryMenuRow>
<PrimaryMenuRow href="https://info.uniswap.org/#/">
<Icon>
<BarChartIcon width={24} height={24} />
</Icon>
<PrimaryMenuRow.Text>View token analytics </PrimaryMenuRow.Text>
<PrimaryMenuRow.Text>
<Trans>View token analytics</Trans>
</PrimaryMenuRow.Text>
</PrimaryMenuRow>
</Column>
<Separator />
<Column paddingX="16" gap="4">
<SecondaryLinkedText href="https://help.uniswap.org/en/">Help center </SecondaryLinkedText>
<SecondaryLinkedText href="https://docs.uniswap.org/">Documentation </SecondaryLinkedText>
<Box
display="flex"
flexDirection={{ sm: 'row', md: 'column' }}
flexWrap="wrap"
alignItems={{ sm: 'center', md: 'flex-start' }}
paddingX="8"
>
<SecondaryLinkedText href="https://help.uniswap.org/en/">
<Trans>Help center</Trans>
</SecondaryLinkedText>
<SecondaryLinkedText href="https://docs.uniswap.org/">
<Trans>Documentation</Trans>
</SecondaryLinkedText>
<SecondaryLinkedText
onClick={() => {
toggleOpen()
togglePrivacyPolicy()
}}
>{`Legal & Privacy`}</SecondaryLinkedText>
>
<Trans>Legal & Privacy</Trans>
</SecondaryLinkedText>
{(isDevelopmentEnv() || isStagingEnv()) && (
<SecondaryLinkedText onClick={openFeatureFlagsModal}>{`Feature Flags`}</SecondaryLinkedText>
<SecondaryLinkedText onClick={openFeatureFlagsModal}>
<Trans>Feature Flags</Trans>
</SecondaryLinkedText>
)}
</Column>
</Box>
<IconRow>
<Icon href="https://discord.com/invite/FCfyBSbCU5">
<DiscordIconMenu width={24} height={24} color={themeVars.colors.darkGray} />
<DiscordIconMenu className={styles.hover} width={24} height={24} color={themeVars.colors.darkGray} />
</Icon>
<Icon href="https://twitter.com/Uniswap">
<TwitterIconMenu width={24} height={24} color={themeVars.colors.darkGray} />
<TwitterIconMenu className={styles.hover} width={24} height={24} color={themeVars.colors.darkGray} />
</Icon>
<Icon href="https://github.com/Uniswap">
<GithubIconMenu width={24} height={24} color={themeVars.colors.darkGray} />
<GithubIconMenu className={styles.hover} width={24} height={24} color={themeVars.colors.darkGray} />
</Icon>
</IconRow>
</Column>

View File

@@ -1,121 +0,0 @@
import { style } from '@vanilla-extract/css'
import { subhead } from 'nft/css/common.css'
import { sprinkles } from '../../nft/css/sprinkles.css'
export const sidebar = style([
sprinkles({
display: 'flex',
position: 'fixed',
background: 'white',
height: 'full',
top: '0',
left: '0',
right: '0',
bottom: '0',
paddingBottom: '16',
justifyContent: 'space-between',
}),
{
zIndex: 20,
},
])
export const icon = style([
sprinkles({
width: '32',
height: '32',
}),
])
export const iconContainer = style([
sprinkles({
position: 'relative',
display: 'flex',
flexDirection: 'column',
color: 'darkGray',
background: 'none',
border: 'none',
justifyContent: 'flex-end',
textAlign: 'center',
cursor: 'pointer',
padding: '6',
}),
])
export const linkRow = style([
subhead,
sprinkles({
color: 'blackBlue',
width: 'full',
paddingLeft: '16',
paddingY: '12',
cursor: 'pointer',
}),
{
lineHeight: '24px',
textDecoration: 'none',
},
])
export const activeLinkRow = style([
linkRow,
sprinkles({
background: 'lightGrayButton',
}),
])
export const separator = style([
sprinkles({
height: '0',
borderStyle: 'solid',
borderColor: 'medGray',
borderWidth: '1px',
marginY: '8',
marginX: '16',
}),
])
export const extraLinkRow = style([
subhead,
sprinkles({
width: 'full',
color: 'blackBlue',
paddingY: '12',
paddingLeft: '16',
cursor: 'pointer',
}),
{
lineHeight: '24px',
textDecoration: 'none',
},
])
export const bottomExternalLinks = style([
sprinkles({
gap: '4',
paddingX: '4',
width: 'max',
flexWrap: 'wrap',
}),
])
export const bottomJointExternalLinksContainer = style([
sprinkles({
paddingX: '8',
paddingY: '4',
color: 'darkGray',
fontWeight: 'medium',
fontSize: '12',
}),
{
lineHeight: '20px',
},
])
export const IconRow = style([
sprinkles({
gap: '12',
width: 'max',
}),
])

View File

@@ -1,249 +0,0 @@
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
import { Box } from 'nft/components/Box'
import { Portal } from 'nft/components/common/Portal'
import { Column, Row } from 'nft/components/Flex'
import {
BarChartIconMobile,
BulletIcon,
CloseIcon,
DiscordIconMenuMobile,
GithubIconMenuMobile,
GovernanceIconMobile,
HamburgerIcon,
ThinTagIconMobile,
TwitterIconMenuMobile,
} from 'nft/components/icons'
import { themeVars } from 'nft/css/sprinkles.css'
import { ReactNode, useReducer } from 'react'
import { NavLink, NavLinkProps, useLocation } from 'react-router-dom'
import { useToggleModal, useTogglePrivacyPolicy } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import { isDevelopmentEnv, isStagingEnv } from 'utils/env'
import * as styles from './MobileSidebar.css'
import { NavIcon } from './NavIcon'
interface NavLinkRowProps {
href: string
id?: NavLinkProps['id']
isActive?: boolean
close: () => void
children: ReactNode
}
const NavLinkRow = ({ href, id, isActive, close, children }: NavLinkRowProps) => {
return (
<NavLink to={href} className={isActive ? styles.activeLinkRow : styles.linkRow} id={id} onClick={close}>
{children}
</NavLink>
)
}
const ExtraLinkRow = ({
to,
href,
close,
children,
}: {
to?: NavLinkProps['to']
href?: string
close: () => void
children: ReactNode
}) => {
return (
<>
{to ? (
<NavLink to={to} className={styles.extraLinkRow}>
<Row gap="12" onClick={close}>
{children}
</Row>
</NavLink>
) : (
<Row
as="a"
href={href}
target={'_blank'}
rel={'noopener noreferrer'}
gap="12"
onClick={close}
className={styles.extraLinkRow}
>
{children}
</Row>
)}
</>
)
}
const BottomExternalLink = ({
href,
onClick,
children,
}: {
href?: string
onClick?: () => void
children: ReactNode
}) => {
return (
<Box
as={href ? 'a' : 'div'}
href={href ?? undefined}
target={href ? '_blank' : undefined}
rel={href ? 'noopener noreferrer' : undefined}
className={`${styles.bottomJointExternalLinksContainer}`}
onClick={onClick}
cursor="pointer"
>
{children}
</Box>
)
}
const Icon = ({ href, children }: { href?: string; children: ReactNode }) => {
return (
<>
<Box
as={href ? 'a' : 'div'}
href={href ?? undefined}
target={href ? '_blank' : undefined}
rel={href ? 'noopener noreferrer' : undefined}
display="flex"
flexDirection="column"
color="blackBlue"
background="none"
border="none"
justifyContent="center"
textAlign="center"
>
{children}
</Box>
</>
)
}
const IconRow = ({ children }: { children: ReactNode }) => {
return <Row className={styles.IconRow}>{children}</Row>
}
const Seperator = () => {
return <Box className={styles.separator} />
}
export const MobileSideBar = () => {
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
const togglePrivacyPolicy = useTogglePrivacyPolicy()
const openFeatureFlagsModal = useToggleModal(ApplicationModal.FEATURE_FLAGS)
const { pathname } = useLocation()
const nftFlag = useNftFlag()
const isPoolActive =
pathname.startsWith('/pool') ||
pathname.startsWith('/add') ||
pathname.startsWith('/remove') ||
pathname.startsWith('/increase') ||
pathname.startsWith('/find')
return (
<>
<NavIcon onClick={toggleOpen}>
<HamburgerIcon width={28} height={28} />
</NavIcon>
{isOpen && (
<Portal>
<Column className={styles.sidebar}>
<Column>
<Row justifyContent="flex-end" marginTop="14" marginBottom="20" marginRight="8">
<Box as="button" onClick={toggleOpen} className={styles.iconContainer}>
<CloseIcon className={styles.icon} />
</Box>
</Row>
<Column gap="4">
<NavLinkRow href="/swap" close={toggleOpen} isActive={pathname.startsWith('/swap')}>
Swap
</NavLinkRow>
<NavLinkRow href="/tokens" close={toggleOpen} isActive={pathname.startsWith('/tokens')}>
Tokens
</NavLinkRow>
{nftFlag === NftVariant.Enabled && (
<NavLinkRow href="/nfts" close={toggleOpen} isActive={pathname.startsWith('/nfts')}>
NFTs
</NavLinkRow>
)}
<NavLinkRow href="/pool" id={'pool-nav-link'} isActive={isPoolActive} close={toggleOpen}>
Pool
</NavLinkRow>
</Column>
<Seperator />
<Column gap="4">
{nftFlag === NftVariant.Enabled && (
<ExtraLinkRow to="/nft/sell" close={toggleOpen}>
<Icon>
<ThinTagIconMobile width={24} height={24} />
</Icon>
Sell NFTs
</ExtraLinkRow>
)}
<ExtraLinkRow to="/vote" close={toggleOpen}>
<Icon>
<GovernanceIconMobile width={24} height={24} />
</Icon>
Vote in governance
</ExtraLinkRow>
<ExtraLinkRow href="https://info.uniswap.org/#/" close={toggleOpen}>
<Icon>
<BarChartIconMobile width={24} height={24} />
</Icon>
View token analytics
</ExtraLinkRow>
</Column>
</Column>
<Column>
<Row justifyContent="center" marginBottom="12" flexWrap="wrap">
<Row className={styles.bottomExternalLinks}>
<BottomExternalLink href="https://help.uniswap.org/en/" onClick={toggleOpen}>
Help center
</BottomExternalLink>
<BulletIcon />
<BottomExternalLink href="https://docs.uniswap.org/" onClick={toggleOpen}>
Documentation
</BottomExternalLink>
<BulletIcon />
<BottomExternalLink
onClick={() => {
toggleOpen()
togglePrivacyPolicy()
}}
>
{`Legal & Privacy`}
</BottomExternalLink>
</Row>
{(isDevelopmentEnv() || isStagingEnv()) && (
<>
<BulletIcon />
<BottomExternalLink onClick={openFeatureFlagsModal}>{`Feature Flags`}</BottomExternalLink>
</>
)}
</Row>
<Row justifyContent="center">
<IconRow>
<Icon href="https://discord.com/invite/FCfyBSbCU5">
<DiscordIconMenuMobile width={32} height={32} color={themeVars.colors.darkGray} />
</Icon>
<Icon href="https://twitter.com/Uniswap">
<TwitterIconMenuMobile width={32} height={32} color={themeVars.colors.darkGray} />
</Icon>
<Icon href="https://github.com/Uniswap">
<GithubIconMenuMobile width={32} height={32} color={themeVars.colors.darkGray} />
</Icon>
</IconRow>
</Row>
</Column>
</Column>
</Portal>
)}
<PrivacyPolicyModal />
<FeatureFlagModal />
</>
)
}

View File

@@ -2,18 +2,44 @@ import { style } from '@vanilla-extract/css'
import { sprinkles } from '../../nft/css/sprinkles.css'
export const NavDropdown = style([
const baseNavDropdown = style([
sprinkles({
position: 'absolute',
background: 'white95',
borderRadius: '12',
background: 'lightGray',
borderStyle: 'solid',
borderColor: 'medGray',
paddingY: '20',
borderWidth: '1px',
paddingBottom: '8',
paddingTop: '8',
zIndex: '2',
}),
{
boxShadow: '0px 4px 12px 0px #00000026',
zIndex: 10,
},
])
export const NavDropdown = style([
baseNavDropdown,
sprinkles({
position: 'absolute',
borderRadius: '12',
}),
{},
])
export const mobileNavDropdown = style([
baseNavDropdown,
sprinkles({
position: 'fixed',
borderTopRightRadius: '12',
borderTopLeftRadius: '12',
top: 'unset',
bottom: '56',
left: '0',
right: '0',
width: 'full',
}),
{
borderRightWidth: '0px',
borderLeftWidth: '0px',
},
])

View File

@@ -1,37 +1,12 @@
import { Box } from 'nft/components/Box'
import { ReactNode } from 'react'
import { Box, BoxProps } from 'nft/components/Box'
import { useIsMobile } from 'nft/hooks'
import { ForwardedRef, forwardRef } from 'react'
import * as styles from './NavDropdown.css'
interface NavDropdownProps {
top: number
right?: number
leftAligned?: boolean
horizontalPadding?: boolean
centerHorizontally?: boolean
children: ReactNode
}
export const NavDropdown = forwardRef((props: BoxProps, ref: ForwardedRef<HTMLElement>) => {
const isMobile = useIsMobile()
return <Box ref={ref} className={isMobile ? styles.mobileNavDropdown : styles.NavDropdown} {...props} />
})
export const NavDropdown = ({
top,
centerHorizontally,
leftAligned,
horizontalPadding,
children,
}: NavDropdownProps) => {
return (
<Box
paddingX={horizontalPadding ? '16' : undefined}
style={{
top: `${top}px`,
left: centerHorizontally ? '50%' : leftAligned ? '0px' : 'auto',
right: centerHorizontally || leftAligned ? 'auto' : '10px',
transform: centerHorizontally ? 'translateX(-50%)' : 'unset',
zIndex: 3,
}}
className={styles.NavDropdown}
>
{children}
</Box>
)
}
NavDropdown.displayName = 'NavDropdown'

View File

@@ -1,25 +1,24 @@
import { style } from '@vanilla-extract/css'
import { sprinkles, themeVars } from '../../nft/css/sprinkles.css'
import { sprinkles, vars } from '../../nft/css/sprinkles.css'
export const navIcon = style([
sprinkles({
position: 'relative',
display: 'flex',
flexDirection: 'column',
color: 'blackBlue',
background: 'none',
border: 'none',
justifyContent: 'center',
textAlign: 'center',
cursor: 'pointer',
padding: '8',
borderRadius: '8',
transition: '250',
}),
{
':hover': {
background: themeVars.colors.lightGrayContainer,
background: vars.color.lightGrayOverlay,
},
zIndex: 2,
zIndex: 1,
},
])

View File

@@ -5,12 +5,19 @@ import * as styles from './NavIcon.css'
interface NavIconProps {
children: ReactNode
isActive?: boolean
onClick: () => void
}
export const NavIcon = ({ children, onClick }: NavIconProps) => {
export const NavIcon = ({ children, isActive, onClick }: NavIconProps) => {
return (
<Box as="button" className={styles.navIcon} onClick={onClick}>
<Box
as="button"
className={styles.navIcon}
background={isActive ? 'accentActiveSoft' : 'none'}
color={isActive ? 'blackBlue' : 'darkGray'}
onClick={onClick}
>
{children}
</Box>
)

View File

@@ -1,7 +1,7 @@
import { style } from '@vanilla-extract/css'
import { subhead } from '../../nft/css/common.css'
import { sprinkles } from '../../nft/css/sprinkles.css'
import { sprinkles, vars } from '../../nft/css/sprinkles.css'
export const nav = style([
sprinkles({
@@ -20,7 +20,7 @@ export const nav = style([
export const logoContainer = style([
sprinkles({
display: 'flex',
marginRight: { mobile: '12', desktopXl: '20' },
marginRight: { sm: '12', xxl: '20' },
alignItems: 'center',
}),
])
@@ -34,23 +34,14 @@ export const logo = style([
export const baseContainer = style([
sprinkles({
display: 'flex',
alignItems: 'center',
}),
])
export const baseMobileContainer = style([
sprinkles({
display: 'flex',
width: 'full',
alignItems: 'center',
marginY: '2',
}),
])
export const baseSideContainer = style([
baseContainer,
sprinkles({
display: 'flex',
width: 'full',
flex: '1',
flexShrink: '2',
@@ -64,19 +55,13 @@ export const leftSideContainer = style([
}),
])
export const leftSideMobileContainer = style([
baseMobileContainer,
sprinkles({
justifyContent: 'flex-start',
}),
])
export const middleContainer = style([
baseContainer,
sprinkles({
flex: '1',
flexShrink: '1',
justifyContent: 'center',
display: { sm: 'none', xl: 'flex' },
}),
])
@@ -94,10 +79,17 @@ const baseMenuItem = style([
paddingX: '16',
marginY: '4',
borderRadius: '12',
transition: '250',
height: 'min',
width: 'full',
textAlign: 'center',
}),
{
lineHeight: '24px',
textDecoration: 'none',
':hover': {
background: vars.color.lightGrayOverlay,
},
},
])
@@ -108,30 +100,25 @@ export const menuItem = style([
}),
])
export const rightSideMobileContainer = style([
baseMobileContainer,
sprinkles({
justifyContent: 'flex-end',
}),
])
export const activeMenuItem = style([
baseMenuItem,
sprinkles({
color: 'blackBlue',
background: 'backgroundFloating',
}),
])
export const mobileWalletContainer = style([
export const mobileBottomBar = style([
sprinkles({
position: 'fixed',
display: 'flex',
display: { sm: 'flex', lg: 'none' },
bottom: '0',
right: '1/2',
marginY: '0',
marginX: 'auto',
right: '0',
left: '0',
justifyContent: 'space-between',
paddingY: '4',
paddingX: '8',
height: '56',
background: 'lightGray',
}),
{
transform: 'translate(50%,-50%)',
},
])

View File

@@ -1,16 +1,14 @@
import { Trans } from '@lingui/macro'
import Web3Status from 'components/Web3Status'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
import { useWindowSize } from 'hooks/useWindowSize'
import { ReactNode } from 'react'
import { NavLink, NavLinkProps, useLocation } from 'react-router-dom'
import { Box } from '../../nft/components/Box'
import { Row } from '../../nft/components/Flex'
import { UniIcon, UniIconMobile } from '../../nft/components/icons'
import { breakpoints } from '../../nft/css/sprinkles.css'
import { UniIcon } from '../../nft/components/icons'
import { ChainSwitcher } from './ChainSwitcher'
import { MenuDropdown } from './MenuDropdown'
import { MobileSideBar } from './MobileSidebar'
import * as styles from './Navbar.css'
import { SearchBar } from './SearchBar'
@@ -34,41 +32,10 @@ const MenuItem = ({ href, id, isActive, children }: MenuItemProps) => {
)
}
const MobileNavbar = () => {
return (
<>
<nav className={styles.nav}>
<Box display="flex" height="full" flexWrap="nowrap" alignItems="stretch">
<Box className={styles.leftSideMobileContainer}>
<Box as="a" href="#/swap" className={styles.logoContainer}>
<UniIconMobile width="44" height="44" className={styles.logo} />
</Box>
<ChainSwitcher isMobile={true} />
</Box>
<Box className={styles.rightSideMobileContainer}>
<Row gap="16">
<SearchBar />
<MobileSideBar />
</Row>
</Box>
</Box>
</nav>
<Box className={styles.mobileWalletContainer}>
<Web3Status />
</Box>
</>
)
}
const Navbar = () => {
const { width: windowWidth } = useWindowSize()
const PageTabs = () => {
const { pathname } = useLocation()
const nftFlag = useNftFlag()
if (windowWidth && windowWidth < breakpoints.desktopXl) {
return <MobileNavbar />
}
const isPoolActive =
pathname.startsWith('/pool') ||
pathname.startsWith('/add') ||
@@ -77,41 +44,68 @@ const Navbar = () => {
pathname.startsWith('/find')
return (
<nav className={styles.nav}>
<Box display="flex" height="full" flexWrap="nowrap" alignItems="stretch">
<Box className={styles.leftSideContainer}>
<Box as="a" href="#/swap" className={styles.logoContainer}>
<UniIcon width="48" height="48" className={styles.logo} />
<>
<MenuItem href="/swap" isActive={pathname.startsWith('/swap')}>
<Trans>Swap</Trans>
</MenuItem>
<MenuItem href="/tokens" isActive={pathname.startsWith('/tokens')}>
<Trans>Tokens</Trans>
</MenuItem>
{nftFlag === NftVariant.Enabled && (
<MenuItem href="/nfts" isActive={pathname.startsWith('/nfts')}>
<Trans>NFTs</Trans>
</MenuItem>
)}
<MenuItem href="/pool" id={'pool-nav-link'} isActive={isPoolActive}>
<Trans>Pool</Trans>
</MenuItem>
</>
)
}
const Navbar = () => {
return (
<>
<nav className={styles.nav}>
<Box display="flex" height="full" flexWrap="nowrap" alignItems="stretch">
<Box className={styles.leftSideContainer}>
<Box as="a" href="#/swap" className={styles.logoContainer}>
<UniIcon width="48" height="48" className={styles.logo} />
</Box>
<Box display={{ sm: 'flex', lg: 'none' }}>
<ChainSwitcher leftAlign={true} />
</Box>
<Row gap="8" display={{ sm: 'none', lg: 'flex' }}>
<PageTabs />
</Row>
</Box>
<Box className={styles.middleContainer}>
<SearchBar />
</Box>
<Box className={styles.rightSideContainer}>
<Row gap="12">
<Box display={{ sm: 'flex', xl: 'none' }}>
<SearchBar />
</Box>
<Box display={{ sm: 'none', lg: 'flex' }}>
<MenuDropdown />
</Box>
<Box display={{ sm: 'none', lg: 'flex' }}>
<ChainSwitcher />
</Box>
<Web3Status />
</Row>
</Box>
<Row gap="8">
<MenuItem href="/swap" isActive={pathname.startsWith('/swap')}>
Swap
</MenuItem>
<MenuItem href="/tokens" isActive={pathname.startsWith('/tokens')}>
Tokens
</MenuItem>
{nftFlag === NftVariant.Enabled && (
<MenuItem href="/nfts" isActive={pathname.startsWith('/nfts')}>
NFTs
</MenuItem>
)}
<MenuItem href="/pool" id={'pool-nav-link'} isActive={isPoolActive}>
Pool
</MenuItem>
</Row>
</Box>
<Box className={styles.middleContainer}>
<SearchBar />
</Box>
<Box className={styles.rightSideContainer}>
<Row gap="12">
<MenuDropdown />
<ChainSwitcher />
<Web3Status />
</Row>
</nav>
<Box className={styles.mobileBottomBar}>
<PageTabs />
<Box marginY="4">
<MenuDropdown />
</Box>
</Box>
</nav>
</>
)
}

View File

@@ -3,20 +3,39 @@ import { buttonTextSmall, subhead, subheadSmall } from 'nft/css/common.css'
import { breakpoints, sprinkles, vars } from '../../nft/css/sprinkles.css'
const DESKTOP_NAVBAR_WIDTH = '360px'
const DESKTOP_NAVBAR_WIDTH = 360
const baseSearchStyle = style([
sprinkles({
borderStyle: 'solid',
borderColor: 'lightGrayButton',
borderWidth: '1px',
paddingY: '12',
width: { mobile: 'viewWidth' },
width: { sm: 'viewWidth' },
borderStyle: 'solid',
borderWidth: '1px',
borderColor: 'medGray',
}),
{
'@media': {
[`screen and (min-width: ${breakpoints.tabletSm}px)`]: {
width: DESKTOP_NAVBAR_WIDTH,
[`screen and (min-width: ${breakpoints.sm}px)`]: {
width: `${DESKTOP_NAVBAR_WIDTH}px`,
},
},
},
])
export const searchBarContainer = style([
sprinkles({
right: '0',
top: '0',
zIndex: '3',
display: 'inline-block',
}),
{
'@media': {
[`screen and (min-width: ${breakpoints.sm}px)`]: {
top: '-24px',
},
[`screen and (min-width: ${breakpoints.lg}px)`]: {
right: `-${DESKTOP_NAVBAR_WIDTH / 2}px`,
},
},
},
@@ -29,6 +48,7 @@ export const searchBar = style([
color: 'placeholder',
paddingX: '16',
cursor: 'pointer',
background: 'lightGray',
}),
])
@@ -47,12 +67,9 @@ export const searchBarInput = style([
export const searchBarDropdown = style([
baseSearchStyle,
sprinkles({
position: 'absolute',
left: '0',
top: '48',
borderBottomLeftRadius: '12',
borderBottomRightRadius: '12',
background: 'white',
background: 'lightGray',
}),
{
borderTop: 'none',
@@ -72,7 +89,7 @@ export const suggestionRow = style([
{
':hover': {
cursor: 'pointer',
background: vars.color.lightGrayButton,
background: vars.color.lightGrayOverlay,
},
textDecoration: 'none',
},
@@ -133,14 +150,6 @@ export const suggestionIcon = sprinkles({
flexShrink: '0',
})
export const magnifyingGlassIcon = style([
sprinkles({
width: '20',
height: '20',
marginRight: '12',
}),
])
export const sectionHeader = style([
subheadSmall,
sprinkles({
@@ -156,6 +165,5 @@ export const notFoundContainer = style([
sprinkles({
paddingY: '4',
paddingLeft: '16',
marginTop: '20',
}),
])

View File

@@ -1,14 +1,15 @@
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
import clsx from 'clsx'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
import useDebounce from 'hooks/useDebounce'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { useWindowSize } from 'hooks/useWindowSize'
import { organizeSearchResults } from 'lib/utils/searchBar'
import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex'
import { Overlay } from 'nft/components/modals/Overlay'
import { subheadSmall } from 'nft/css/common.css'
import { breakpoints } from 'nft/css/sprinkles.css'
import { useSearchHistory } from 'nft/hooks'
import { magicalGradientOnHover, subheadSmall } from 'nft/css/common.css'
import { useIsMobile, useSearchHistory } from 'nft/hooks'
import { fetchSearchCollections, fetchTrendingCollections } from 'nft/queries'
import { fetchSearchTokens } from 'nft/queries/genie/SearchTokensFetcher'
import { fetchTrendingTokens } from 'nft/queries/genie/TrendingTokensFetcher'
@@ -32,7 +33,7 @@ import { CollectionRow, SkeletonRow, TokenRow } from './SuggestionRow'
interface SearchBarDropdownSectionProps {
toggleOpen: () => void
suggestions: (GenieCollection | FungibleToken)[]
header: string
header: JSX.Element
headerIcon?: JSX.Element
hoveredIndex: number | undefined
startingIndex: number
@@ -89,7 +90,7 @@ interface SearchBarDropdownProps {
}
export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }: SearchBarDropdownProps) => {
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(undefined)
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(0)
const searchHistory = useSearchHistory(
(state: { history: (FungibleToken | GenieCollection)[] }) => state.history
).slice(0, 2)
@@ -106,10 +107,12 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }:
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={tokens}
header={'Tokens'}
header={<Trans>Tokens</Trans>}
/>
) : (
<Box className={styles.notFoundContainer}>No tokens found.</Box>
<Box className={styles.notFoundContainer}>
<Trans>No tokens found.</Trans>
</Box>
)
const collectionSearchResults =
@@ -121,7 +124,7 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }:
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={collections}
header={'NFT Collections'}
header={<Trans>NFT Collections</Trans>}
/>
) : (
<Box className={styles.notFoundContainer}>No NFT collections found.</Box>
@@ -138,7 +141,7 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }:
return {
...collection,
collectionAddress: collection.address,
floorPrice: formatEthPrice(collection.floor.toString()),
floorPrice: formatEthPrice(collection.floor?.toString()),
stats: {
total_supply: collection.totalSupply,
one_day_change: collection.floorChange,
@@ -182,6 +185,7 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }:
setHoveredIndex(hoveredIndex - 1)
}
} else if (event.key === 'ArrowDown') {
event.preventDefault()
if (hoveredIndex && hoveredIndex === totalSuggestions - 1) {
setHoveredIndex(0)
} else {
@@ -224,7 +228,7 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }:
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={searchHistory}
header={'Recent searches'}
header={<Trans>Recent searches</Trans>}
headerIcon={<ClockIcon />}
/>
)}
@@ -235,7 +239,7 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }:
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={trendingTokens ?? []}
header={'Popular tokens'}
header={<Trans>Popular tokens</Trans>}
headerIcon={<TrendingArrow />}
/>
)}
@@ -246,7 +250,7 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput }:
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={trendingCollections as unknown as GenieCollection[]}
header={'Popular NFT collections'}
header={<Trans>Popular NFT collections</Trans>}
headerIcon={<TrendingArrow />}
/>
)}
@@ -265,9 +269,10 @@ export const SearchBar = () => {
const [searchValue, setSearchValue] = useState('')
const debouncedSearchValue = useDebounce(searchValue, 300)
const searchRef = useRef<HTMLDivElement>(null)
const inputRef = useRef<HTMLInputElement>(null)
const { pathname } = useLocation()
const { width: windowWidth } = useWindowSize()
const phase1Flag = useNftFlag()
const isMobile = useIsMobile()
useOnClickOutside(searchRef, () => {
isOpen && toggleOpen()
@@ -317,38 +322,44 @@ export const SearchBar = () => {
setSearchValue('')
}, [pathname])
const isMobile = useMemo(() => windowWidth && windowWidth <= breakpoints.tabletSm, [windowWidth])
// auto set cursor when searchbar is opened
useEffect(() => {
if (isOpen) {
inputRef.current?.focus()
}
}, [isOpen])
const placeholderText = phase1Flag === NftVariant.Enabled ? t`Search tokens and NFT collections` : t`Search tokens`
return (
<>
<Box position="relative">
<Box
position={{ mobile: isOpen ? 'absolute' : 'relative', tabletSm: 'relative' }}
top={{ mobile: '0', tabletSm: 'unset' }}
left={{ mobile: '0', tabletSm: 'unset' }}
width={{ mobile: isOpen ? 'viewWidth' : 'auto', tabletSm: 'auto' }}
position={isOpen ? { sm: 'fixed', md: 'absolute' } : 'static'}
width={{ sm: isOpen ? 'viewWidth' : 'auto', md: 'auto' }}
ref={searchRef}
style={{ zIndex: '1000' }}
className={styles.searchBarContainer}
>
<Row
className={styles.searchBar}
className={clsx(`${styles.searchBar} ${!isOpen && magicalGradientOnHover}`)}
borderRadius={isOpen ? undefined : '12'}
borderTopRightRadius={isOpen && !isMobile ? '12' : undefined}
borderTopLeftRadius={isOpen && !isMobile ? '12' : undefined}
display={{ mobile: isOpen ? 'flex' : 'none', desktopXl: 'flex' }}
borderBottomWidth={isOpen ? '0px' : '1px'}
display={{ sm: isOpen ? 'flex' : 'none', xl: 'flex' }}
justifyContent={isOpen || phase1Flag === NftVariant.Enabled ? 'flex-start' : 'center'}
background={isOpen ? 'white' : 'lightGrayContainer'}
onFocus={() => !isOpen && toggleOpen()}
onClick={() => !isOpen && toggleOpen()}
gap="12"
>
<Box display={{ mobile: 'none', tabletSm: 'flex' }}>
<MagnifyingGlassIcon className={styles.magnifyingGlassIcon} />
<Box display={{ sm: 'none', md: 'flex' }}>
<MagnifyingGlassIcon />
</Box>
<Box display={{ mobile: 'flex', tabletSm: 'none' }} color="blackBlue" onClick={toggleOpen}>
<ChevronLeftIcon className={styles.magnifyingGlassIcon} />
<Box display={{ sm: 'flex', md: 'none' }} color="placeholder" onClick={toggleOpen}>
<ChevronLeftIcon />
</Box>
<Box
as="input"
placeholder={`Search tokens${phase1Flag === NftVariant.Enabled ? ' and NFT collections' : ''}`}
placeholder={placeholderText}
width={isOpen || phase1Flag === NftVariant.Enabled ? 'full' : '120'}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
!isOpen && toggleOpen()
@@ -356,26 +367,27 @@ export const SearchBar = () => {
}}
className={styles.searchBarInput}
value={searchValue}
ref={inputRef}
/>
</Row>
<Box display={{ mobile: isOpen ? 'none' : 'flex', desktopXl: 'none' }}>
<Box display={{ sm: isOpen ? 'none' : 'flex', xl: 'none' }}>
<NavIcon onClick={toggleOpen}>
<NavMagnifyingGlassIcon width={28} height={28} />
</NavIcon>
</Box>
{isOpen &&
(searchValue.length > 0 && (tokensAreLoading || collectionsAreLoading) ? (
(debouncedSearchValue.length > 0 && (tokensAreLoading || collectionsAreLoading) ? (
<SkeletonRow />
) : (
<SearchBarDropdown
toggleOpen={toggleOpen}
tokens={reducedTokens}
collections={reducedCollections}
hasInput={searchValue.length > 0}
hasInput={debouncedSearchValue.length > 0}
/>
))}
</Box>
{isOpen && <Overlay />}
</>
</Box>
)
}

View File

@@ -4,14 +4,13 @@ import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex'
import { vars } from 'nft/css/sprinkles.css'
import { useSearchHistory } from 'nft/hooks'
// import { fetchSearchCollections, fetchTrendingCollections } from 'nft/queries'
import { FungibleToken, GenieCollection } from 'nft/types'
import { ethNumberStandardFormatter } from 'nft/utils/currency'
import { putCommas } from 'nft/utils/putCommas'
import { useCallback, useEffect, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { TokenWarningRedIcon, VerifiedIcon } from '../../nft/components/icons'
import { VerifiedIcon } from '../../nft/components/icons'
import * as styles from './SearchBar.css'
interface CollectionRowProps {
@@ -56,7 +55,7 @@ export const CollectionRow = ({ collection, isHovered, setHoveredIndex, toggleOp
onMouseEnter={() => !isHovered && setHoveredIndex(index)}
onMouseLeave={() => isHovered && setHoveredIndex(undefined)}
className={styles.suggestionRow}
style={{ background: isHovered ? vars.color.lightGrayButton : 'none' }}
style={{ background: isHovered ? vars.color.lightGrayOverlay : 'none' }}
>
<Row style={{ width: '60%' }}>
{!brokenImage && collection.imageUrl ? (
@@ -79,14 +78,14 @@ export const CollectionRow = ({ collection, isHovered, setHoveredIndex, toggleOp
<Box className={styles.secondaryText}>{putCommas(collection.stats.total_supply)} items</Box>
</Column>
</Row>
{collection.floorPrice && (
{collection.floorPrice ? (
<Column className={styles.suggestionSecondaryContainer}>
<Row gap="4">
<Box className={styles.primaryText}>{ethNumberStandardFormatter(collection.floorPrice)} ETH</Box>
</Row>
<Box className={styles.secondaryText}>Floor</Box>
</Column>
)}
) : null}
</Link>
)
}
@@ -129,13 +128,12 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index
return (
<Link
// TODO connect with explore token URI
to={`/tokens/${token.address}`}
onClick={handleClick}
onMouseEnter={() => !isHovered && setHoveredIndex(index)}
onMouseLeave={() => isHovered && setHoveredIndex(undefined)}
className={styles.suggestionRow}
style={{ background: isHovered ? vars.color.lightGrayButton : 'none' }}
style={{ background: isHovered ? vars.color.lightGrayOverlay : 'none' }}
>
<Row style={{ width: '65%' }}>
{!brokenImage && token.logoURI ? (
@@ -153,11 +151,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index
<Column className={styles.suggestionPrimaryContainer}>
<Row gap="4" width="full">
<Box className={styles.primaryText}>{token.name}</Box>
{token.onDefaultList ? (
<VerifiedIcon className={styles.suggestionIcon} />
) : (
<TokenWarningRedIcon className={styles.suggestionIcon} />
)}
{token.onDefaultList && <VerifiedIcon className={styles.suggestionIcon} />}
</Row>
<Box className={styles.secondaryText}>{token.symbol}</Box>
</Column>

View File

@@ -23,7 +23,7 @@ const Tabs = styled.div`
const StyledHistoryLink = styled(HistoryLink)<{ flex: string | undefined }>`
flex: ${({ flex }) => flex ?? 'none'};
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
flex: none;
margin-right: 10px;
`};

View File

@@ -1,3 +1,4 @@
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
import { useCallback, useEffect } from 'react'
import { X } from 'react-feather'
import { animated } from 'react-spring'
@@ -29,7 +30,7 @@ const Popup = styled.div`
padding-right: 35px;
overflow: hidden;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
min-width: 290px;
&:not(:last-of-type) {
margin-right: 20px;
@@ -57,6 +58,7 @@ export default function PopupItem({
popKey: string
}) {
const removePopup = useRemovePopup()
const navbarFlag = useNavBarFlag()
const removeThisPopup = useCallback(() => removePopup(popKey), [popKey, removePopup])
useEffect(() => {
if (removeAfterMs === null) return undefined
@@ -71,23 +73,24 @@ export default function PopupItem({
}, [removeAfterMs, removeThisPopup])
const theme = useTheme()
const faderStyle = useSpring({
from: { width: '100%' },
to: { width: '0%' },
config: { duration: removeAfterMs ?? undefined },
})
let popupContent
if ('txn' in content) {
const {
txn: { hash },
} = content
if (navbarFlag === NavBarVariant.Enabled) return null
popupContent = <TransactionPopup hash={hash} />
} else if ('failedSwitchNetwork' in content) {
popupContent = <FailedNetworkSwitchPopup chainId={content.failedSwitchNetwork} />
}
const faderStyle = useSpring({
from: { width: '100%' },
to: { width: '0%' },
config: { duration: removeAfterMs ?? undefined },
})
return (
<Popup>
<StyledClose color={theme.deprecated_text2} onClick={removeThisPopup} />

View File

@@ -21,7 +21,7 @@ const Wrapper = styled(AutoColumn)`
color: ${({ theme }) => theme.deprecated_text1};
overflow: hidden;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
max-width: 100%;
`}
`

View File

@@ -17,7 +17,7 @@ const MobilePopupWrapper = styled.div<{ height: string | number }>`
margin-bottom: ${({ height }) => (height ? '20px' : 0)};
display: none;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
display: block;
padding-top: 20px;
`};
@@ -35,8 +35,8 @@ const MobilePopupInner = styled.div`
}
`
const StopOverflowQuery = `@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium + 1}px) and (max-width: ${
MEDIA_WIDTHS.upToMedium + 500
const StopOverflowQuery = `@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToMedium + 1}px) and (max-width: ${
MEDIA_WIDTHS.deprecated_upToMedium + 500
}px)`
const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean; xlPadding: boolean }>`
@@ -47,7 +47,7 @@ const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean; xlPadding:
width: 100%;
z-index: 3;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
display: none;
`};

View File

@@ -12,7 +12,7 @@ const DesktopHeader = styled.div`
font-weight: 500;
padding: 8px;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
align-items: center;
display: flex;
justify-content: space-between;
@@ -32,11 +32,11 @@ const MobileHeader = styled.div`
justify-content: space-between;
align-items: center;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
display: none;
}
@media screen and (max-width: ${MEDIA_WIDTHS.upToExtraSmall}px) {
@media screen and (max-width: ${MEDIA_WIDTHS.deprecated_upToExtraSmall}px) {
display: flex;
flex-direction: column;
align-items: start;
@@ -55,7 +55,7 @@ const ToggleLabel = styled.div`
`
const MobileTogglePosition = styled.div`
@media screen and (max-width: ${MEDIA_WIDTHS.upToExtraSmall}px) {
@media screen and (max-width: ${MEDIA_WIDTHS.deprecated_upToExtraSmall}px) {
position: absolute;
right: 20px;
}

View File

@@ -48,11 +48,11 @@ const LinkRow = styled(Link)`
background-color: ${({ theme }) => theme.deprecated_bg2};
}
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
/* flex-direction: row; */
}
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
flex-direction: column;
row-gap: 12px;
`};
@@ -61,7 +61,7 @@ const LinkRow = styled(Link)`
const BadgeText = styled.div`
font-weight: 500;
font-size: 14px;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
font-size: 12px;
`};
`
@@ -78,7 +78,7 @@ const RangeLineItem = styled(DataLineItem)`
margin-top: 4px;
width: 100%;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
background-color: ${({ theme }) => theme.deprecated_bg2};
border-radius: 12px;
padding: 8px 0;
@@ -88,7 +88,7 @@ const RangeLineItem = styled(DataLineItem)`
const DoubleArrow = styled.span`
margin: 0 2px;
color: ${({ theme }) => theme.deprecated_text3};
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
margin: 4px;
padding: 20px;
`};
@@ -104,7 +104,7 @@ const ExtentsText = styled.span`
color: ${({ theme }) => theme.deprecated_text3};
font-size: 14px;
margin-right: 4px;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
display: none;
`};
`
@@ -122,7 +122,7 @@ const DataText = styled.div`
font-weight: 600;
font-size: 18px;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
font-size: 14px;
`};
`

View File

@@ -13,7 +13,7 @@ import styled from 'styled-components/macro'
import { currencyId } from 'utils/currencyId'
const MobileWrapper = styled(AutoColumn)`
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
display: none;
`};
`

View File

@@ -128,7 +128,7 @@ function CurrencyRow({
eventProperties,
}: {
currency: Currency
onSelect: () => void
onSelect: (hasWarning: boolean) => void
isSelected: boolean
otherSelected: boolean
style: CSSProperties
@@ -159,13 +159,13 @@ function CurrencyRow({
redesignFlag={redesignFlagEnabled}
style={style}
className={`token-item-${key}`}
onKeyPress={(e) => (!isSelected && e.key === 'Enter' ? onSelect() : null)}
onClick={() => (isSelected ? null : onSelect())}
onKeyPress={(e) => (!isSelected && e.key === 'Enter' ? onSelect(!!warning) : null)}
onClick={() => (isSelected ? null : onSelect(!!warning))}
disabled={isSelected}
selected={otherSelected}
>
<Column>
<CurrencyLogo currency={currency} size={'24px'} />
<CurrencyLogo currency={currency} size={'36px'} />
</Column>
<AutoColumn>
<Row>
@@ -279,7 +279,7 @@ export default function CurrencyList({
currencies: Currency[]
otherListTokens?: WrappedTokenInfo[]
selectedCurrency?: Currency | null
onCurrencySelect: (currency: Currency) => void
onCurrencySelect: (currency: Currency, hasWarning?: boolean) => void
otherCurrency?: Currency | null
fixedListRef?: MutableRefObject<FixedSizeList | undefined>
showImportView: () => void
@@ -308,7 +308,7 @@ export default function CurrencyList({
const isSelected = Boolean(currency && selectedCurrency && selectedCurrency.equals(currency))
const otherSelected = Boolean(currency && otherCurrency && otherCurrency.equals(currency))
const handleSelect = () => currency && onCurrencySelect(currency)
const handleSelect = (hasWarning: boolean) => currency && onCurrencySelect(currency, hasWarning)
const token = currency?.wrapped

View File

@@ -51,7 +51,7 @@ interface CurrencySearchProps {
isOpen: boolean
onDismiss: () => void
selectedCurrency?: Currency | null
onCurrencySelect: (currency: Currency) => void
onCurrencySelect: (currency: Currency, hasWarning?: boolean) => void
otherSelectedCurrency?: Currency | null
showCommonBases?: boolean
showCurrencyAmount?: boolean
@@ -136,9 +136,9 @@ export function CurrencySearch({
}, [debouncedQuery, native, filteredSortedTokens])
const handleCurrencySelect = useCallback(
(currency: Currency) => {
onCurrencySelect(currency)
onDismiss()
(currency: Currency, hasWarning?: boolean) => {
onCurrencySelect(currency, hasWarning)
if (!hasWarning) onDismiss()
},
[onDismiss, onCurrencySelect]
)

View File

@@ -3,8 +3,9 @@ import { TokenList } from '@uniswap/token-lists'
import TokenSafety from 'components/TokenSafety'
import { TokenSafetyVariant, useTokenSafetyFlag } from 'featureFlags/flags/tokenSafety'
import usePrevious from 'hooks/usePrevious'
import { useCallback, useEffect, useState } from 'react'
import { memo, useCallback, useEffect, useState } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import { useUserAddedTokens } from 'state/user/hooks'
import useLast from '../../hooks/useLast'
import Modal from '../Modal'
@@ -29,9 +30,10 @@ export enum CurrencyModalView {
manage,
importToken,
importList,
tokenSafety,
}
export default function CurrencySearchModal({
export default memo(function CurrencySearchModal({
isOpen,
onDismiss,
onCurrencySelect,
@@ -43,6 +45,7 @@ export default function CurrencySearchModal({
}: CurrencySearchModalProps) {
const [modalView, setModalView] = useState<CurrencyModalView>(CurrencyModalView.manage)
const lastOpen = useLast(isOpen)
const userAddedTokens = useUserAddedTokens()
useEffect(() => {
if (isOpen && !lastOpen) {
@@ -50,12 +53,28 @@ export default function CurrencySearchModal({
}
}, [isOpen, lastOpen])
const showTokenSafetySpeedbump = (token: Token) => {
setWarningToken(token)
setModalView(CurrencyModalView.tokenSafety)
}
const tokenSafetyFlag = useTokenSafetyFlag()
const handleCurrencySelect = useCallback(
(currency: Currency) => {
onCurrencySelect(currency)
onDismiss()
(currency: Currency, hasWarning?: boolean) => {
if (
tokenSafetyFlag === TokenSafetyVariant.Enabled &&
hasWarning &&
currency.isToken &&
!userAddedTokens.find((token) => token.equals(currency))
) {
showTokenSafetySpeedbump(currency)
} else {
onCurrencySelect(currency)
onDismiss()
}
},
[onDismiss, onCurrencySelect]
[onDismiss, onCurrencySelect, tokenSafetyFlag, userAddedTokens]
)
// for token import view
@@ -68,6 +87,9 @@ export default function CurrencySearchModal({
const [importList, setImportList] = useState<TokenList | undefined>()
const [listURL, setListUrl] = useState<string | undefined>()
// used for token safety
const [warningToken, setWarningToken] = useState<Token | undefined>()
const showImportView = useCallback(() => setModalView(CurrencyModalView.importToken), [setModalView])
const showManageView = useCallback(() => setModalView(CurrencyModalView.manage), [setModalView])
const handleBackImport = useCallback(
@@ -75,8 +97,6 @@ export default function CurrencySearchModal({
[setModalView, prevView]
)
const tokenSafetyFlag = useTokenSafetyFlag()
// change min height if not searching
let minHeight: number | undefined = 80
let content = null
@@ -98,25 +118,34 @@ export default function CurrencySearchModal({
/>
)
break
case CurrencyModalView.tokenSafety:
minHeight = undefined
if (tokenSafetyFlag === TokenSafetyVariant.Enabled && warningToken) {
content = (
<TokenSafety
tokenAddress={warningToken.address}
onContinue={() => handleCurrencySelect(warningToken)}
onCancel={() => setModalView(CurrencyModalView.search)}
showCancel={true}
/>
)
}
break
case CurrencyModalView.importToken:
if (importToken) {
minHeight = undefined
content =
tokenSafetyFlag === TokenSafetyVariant.Enabled ? (
<TokenSafety
tokenAddress={importToken.address}
onContinue={() => handleCurrencySelect(importToken)}
onCancel={handleBackImport}
/>
) : (
<ImportToken
tokens={[importToken]}
onDismiss={onDismiss}
list={importToken instanceof WrappedTokenInfo ? importToken.list : undefined}
onBack={handleBackImport}
handleCurrencySelect={handleCurrencySelect}
/>
)
if (tokenSafetyFlag === TokenSafetyVariant.Enabled) {
showTokenSafetySpeedbump(importToken)
}
content = (
<ImportToken
tokens={[importToken]}
onDismiss={onDismiss}
list={importToken instanceof WrappedTokenInfo ? importToken.list : undefined}
onBack={handleBackImport}
handleCurrencySelect={handleCurrencySelect}
/>
)
}
break
case CurrencyModalView.importList:
@@ -142,4 +171,4 @@ export default function CurrencySearchModal({
{content}
</Modal>
)
}
})

View File

@@ -23,7 +23,7 @@ const AddressText = styled(ThemedText.DeprecatedBlue)`
font-size: 12px;
word-break: break-all;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
font-size: 10px;
`}
`

View File

@@ -1,3 +1,4 @@
import searchIcon from 'assets/svg/search.svg'
import { LoadingRows as BaseLoadingRows } from 'components/Loader/styled'
import styled from 'styled-components/macro'
@@ -37,14 +38,18 @@ export const MenuItem = styled(RowBetween)<{ redesignFlag?: boolean }>`
`
export const SearchInput = styled.input<{ redesignFlag?: boolean }>`
background: no-repeat scroll 7px 7px;
background-image: url(${searchIcon});
background-size: 20px 20px;
background-position: 12px center;
position: relative;
display: flex;
padding: 16px;
padding-left: 40px;
height: ${({ redesignFlag }) => redesignFlag && '40px'};
align-items: center;
width: 100%;
white-space: nowrap;
background: none;
background-color: ${({ theme, redesignFlag }) => redesignFlag && theme.backgroundModule};
border: none;
outline: none;
@@ -62,8 +67,9 @@ export const SearchInput = styled.input<{ redesignFlag?: boolean }>`
}
transition: border 100ms;
:focus {
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_primary1)};
background-color: ${({ theme, redesignFlag }) => redesignFlag && theme.accentActionSoft};
border: 1px solid
${({ theme, redesignFlag }) => (redesignFlag ? theme.accentActiveSoft : theme.deprecated_primary1)};
background-color: ${({ theme, redesignFlag }) => redesignFlag && theme.backgroundSurface};
outline: none;
}
`

View File

@@ -99,7 +99,7 @@ const MenuFlyout = styled.span<{ redesignFlag: boolean }>`
z-index: 100;
color: ${({ theme, redesignFlag }) => redesignFlag && theme.textPrimary};
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
min-width: 18.125rem;
`};

View File

@@ -6,13 +6,18 @@ import styled, { keyframes } from 'styled-components/macro'
const Wrapper = styled.button<{ isActive?: boolean; activeElement?: boolean; redesignFlag: boolean }>`
align-items: center;
background: ${({ isActive, theme, redesignFlag }) =>
redesignFlag && isActive ? theme.accentActionSoft : theme.deprecated_bg1};
border: none;
redesignFlag && isActive
? theme.accentActionSoft
: redesignFlag && !isActive
? 'transparent'
: theme.deprecated_bg1};
border: ${({ redesignFlag, theme, isActive }) =>
redesignFlag && !isActive ? `1px solid ${theme.backgroundOutline}` : 'none'};
border-radius: 20px;
cursor: pointer;
display: flex;
outline: none;
padding: 0.4rem 0.4rem;
padding: ${({ redesignFlag }) => (redesignFlag ? '4px' : '0.4rem 0.4rem')};
width: fit-content;
`

View File

@@ -7,6 +7,7 @@ interface TokenSafetyModalProps {
secondTokenAddress?: string
onContinue: () => void
onCancel: () => void
showCancel?: boolean
}
export default function TokenSafetyModal({
@@ -15,6 +16,7 @@ export default function TokenSafetyModal({
secondTokenAddress,
onContinue,
onCancel,
showCancel,
}: TokenSafetyModalProps) {
return (
<Modal isOpen={isOpen} onDismiss={onCancel}>
@@ -23,6 +25,7 @@ export default function TokenSafetyModal({
secondTokenAddress={secondTokenAddress}
onCancel={onCancel}
onContinue={onContinue}
showCancel={showCancel}
/>
</Modal>
)

View File

@@ -4,14 +4,13 @@ import { ButtonPrimary } from 'components/Button'
import { AutoColumn } from 'components/Column'
import CurrencyLogo from 'components/CurrencyLogo'
import TokenSafetyLabel from 'components/TokenSafety/TokenSafetyLabel'
import { checkWarning, getWarningCopy, TOKEN_SAFETY_ARTICLE, Warning, WARNING_LEVEL } from 'constants/tokenSafety'
import { checkWarning, getWarningCopy, TOKEN_SAFETY_ARTICLE, Warning } from 'constants/tokenSafety'
import { useToken } from 'hooks/Tokens'
import { ExternalLink as LinkIconFeather } from 'react-feather'
import { Text } from 'rebass'
import { useAddUserToken } from 'state/user/hooks'
import styled, { useTheme } from 'styled-components/macro'
import styled from 'styled-components/macro'
import { ButtonText, CopyLinkIcon, ExternalLink } from 'theme'
import { Color } from 'theme/styled'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
const Wrapper = styled.div`
@@ -46,61 +45,52 @@ const InfoText = styled(Text)`
text-align: center;
`
const StyledButton = styled(ButtonPrimary)<{ buttonColor: Color; textColor: Color }>`
color: ${({ textColor }) => textColor};
background-color: ${({ buttonColor }) => buttonColor};
const StyledButton = styled(ButtonPrimary)`
margin-top: 24px;
width: 100%;
:hover {
background-color: ${({ buttonColor, theme }) => buttonColor ?? theme.accentAction};
}
font-weight: 600;
`
const StyledCancelButton = styled(ButtonText)<{ color?: Color }>`
const StyledCancelButton = styled(ButtonText)`
margin-top: 16px;
color: ${({ color, theme }) => color ?? theme.accentAction};
color: ${({ theme }) => theme.textSecondary};
font-weight: 600;
font-size: 14px;
`
const StyledCloseButton = styled(StyledButton)`
background-color: ${({ theme }) => theme.backgroundInteractive};
color: ${({ theme }) => theme.textPrimary};
&:hover {
background-color: ${({ theme }) => theme.backgroundInteractive};
opacity: 0.6;
transition: opacity 250ms ease;
}
`
const Buttons = ({
warning,
onContinue,
onCancel,
showCancel,
}: {
warning: Warning
onContinue: () => void
onCancel: () => void
showCancel?: boolean
}) => {
const theme = useTheme()
let textColor, buttonColor, cancelColor
switch (warning.level) {
case WARNING_LEVEL.MEDIUM:
textColor = theme.white
buttonColor = theme.accentAction
cancelColor = theme.accentAction
break
case WARNING_LEVEL.UNKNOWN:
textColor = theme.accentFailure
buttonColor = theme.accentFailureSoft
cancelColor = theme.textPrimary
break
case WARNING_LEVEL.BLOCKED:
textColor = theme.textPrimary
buttonColor = theme.backgroundInteractive
break
}
return warning.canProceed ? (
<>
<StyledButton buttonColor={buttonColor} textColor={textColor} onClick={onContinue}>
<StyledButton onClick={onContinue}>
<Trans>I understand</Trans>
</StyledButton>
<StyledCancelButton color={cancelColor} onClick={onCancel}>
Cancel
</StyledCancelButton>
{showCancel && <StyledCancelButton onClick={onCancel}>Cancel</StyledCancelButton>}
</>
) : (
<StyledButton buttonColor={buttonColor} textColor={textColor} onClick={onCancel}>
<StyledCloseButton onClick={onCancel}>
<Trans>Close</Trans>
</StyledButton>
</StyledCloseButton>
)
}
@@ -124,8 +114,8 @@ const ExplorerContainer = styled.div`
height: 32px;
margin-top: 10px;
font-size: 20px;
background-color: ${({ theme }) => theme.accentActiveSoft};
color: ${({ theme }) => theme.accentActive};
background-color: ${({ theme }) => theme.accentActionSoft};
color: ${({ theme }) => theme.accentAction};
border-radius: 8px;
padding: 2px 12px;
display: flex;
@@ -190,14 +180,25 @@ function ExplorerView({ token }: { token: Token }) {
}
}
const StyledExternalLink = styled(ExternalLink)`
font-weight: 600;
`
interface TokenSafetyProps {
tokenAddress: string | null
secondTokenAddress?: string
onContinue: () => void
onCancel: () => void
showCancel?: boolean
}
export default function TokenSafety({ tokenAddress, secondTokenAddress, onContinue, onCancel }: TokenSafetyProps) {
export default function TokenSafety({
tokenAddress,
secondTokenAddress,
onContinue,
onCancel,
showCancel,
}: TokenSafetyProps) {
const logos = []
const urls = []
@@ -254,13 +255,13 @@ export default function TokenSafety({ tokenAddress, secondTokenAddress, onContin
<ShortColumn>
<InfoText>
{description}{' '}
<ExternalLink href={TOKEN_SAFETY_ARTICLE}>
<StyledExternalLink href={TOKEN_SAFETY_ARTICLE}>
<Trans>Learn more</Trans>
</ExternalLink>
</StyledExternalLink>
</InfoText>
</ShortColumn>
<LinkColumn>{urls}</LinkColumn>
<Buttons warning={displayWarning} onContinue={acknowledge} onCancel={onCancel} />
<Buttons warning={displayWarning} onContinue={acknowledge} onCancel={onCancel} showCancel={showCancel} />
</Container>
</Wrapper>
)

View File

@@ -15,7 +15,9 @@ const BalancesCard = styled.div`
font-size: 12px;
line-height: 16px;
padding: 20px;
box-shadow: ${({ theme }) => theme.shallowShadow};
background-color: ${({ theme }) => theme.backgroundSurface};
border: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
border-radius: 16px;
`
const ErrorState = styled.div`

View File

@@ -1,3 +1,4 @@
import { Trans } from '@lingui/macro'
import { useToken } from 'hooks/Tokens'
import { useNetworkTokenBalances } from 'hooks/useNetworkTokenBalances'
import { useState } from 'react'
@@ -82,7 +83,7 @@ const SwapButton = styled.button`
display: flex;
align-items: center;
border: none;
color: ${({ theme }) => theme.textPrimary};
color: ${({ theme }) => theme.accentTextLightPrimary};
padding: 12px 16px;
width: 120px;
height: 44px;
@@ -152,7 +153,9 @@ export default function FooterBalanceSummary({
) : error ? (
<ErrorState>
<AlertTriangle size={17} />
<ErrorText>There was an error fetching your balance</ErrorText>
<ErrorText>
<Trans>There was an error fetching your balance</Trans>
</ErrorText>
</ErrorState>
) : (
<BalanceInfo>
@@ -165,16 +168,21 @@ export default function FooterBalanceSummary({
</BalanceTotal>
{multipleBalances && (
<ViewAll onClick={() => setShowMultipleBalances(!showMultipleBalances)}>
{showMultipleBalances ? 'Hide' : 'View'} all balances
<Trans>{showMultipleBalances ? 'Hide' : 'View'} all balances</Trans>
</ViewAll>
)}
</BalanceInfo>
)}
<SwapButton onClick={() => (window.location.href = 'https://app.uniswap.org/#/swap')}>Swap</SwapButton>
<SwapButton onClick={() => (window.location.href = 'https://app.uniswap.org/#/swap')}>
<Trans>Swap</Trans>
</SwapButton>
</TotalBalancesSection>
{showMultipleBalances && (
<NetworkBalancesSection>
<NetworkBalancesLabel>Your balances by network</NetworkBalancesLabel> {networkBalances}
<NetworkBalancesLabel>
<Trans>Your balances by network</Trans>
</NetworkBalancesLabel>
{networkBalances}
</NetworkBalancesSection>
)}
<FakeFooterNavBar>**leaving space for updated nav footer**</FakeFooterNavBar>

View File

@@ -3,8 +3,8 @@ import styled, { useTheme } from 'styled-components/macro'
import { LoadingBubble } from '../loading'
import { DeltaContainer, TokenPrice } from './PriceChart'
import {
AboutContainer,
AboutHeader,
AboutSection,
BreadcrumbNavLink,
ChartContainer,
ChartHeader,
@@ -16,7 +16,7 @@ import {
TokenInfoContainer,
TokenNameCell,
TopArea,
} from './TokenDetail'
} from './TokenDetailContainers'
const LoadingChartContainer = styled(ChartContainer)`
height: 336px;
@@ -34,15 +34,17 @@ const TitleLoadingBubble = styled(LoadingDetailBubble)`
const SquareLoadingBubble = styled(LoadingDetailBubble)`
height: 32px;
border-radius: 8px;
margin-top: 4px;
margin-bottom: 10px;
`
const PriceLoadingBubble = styled(SquareLoadingBubble)`
height: 40px;
`
const LongLoadingBubble = styled(LoadingDetailBubble)`
margin-top: 6px;
width: 100%;
`
const HalfLoadingBubble = styled(LoadingDetailBubble)`
margin-top: 6px;
width: 50%;
`
const IconLoadingBubble = styled(LoadingDetailBubble)`
@@ -118,16 +120,6 @@ export default function LoadingTokenDetail() {
</LoadingChartContainer>
<Space heightSize={32} />
</ChartHeader>
<AboutSection>
<AboutHeader>
<SquareLoadingBubble />
</AboutHeader>
<LongLoadingBubble />
<LongLoadingBubble />
<HalfLoadingBubble />
<ResourcesContainer>{null}</ResourcesContainer>
</AboutSection>
<StatsSection>
<StatsLoadingContainer>
<StatPair>
@@ -152,6 +144,16 @@ export default function LoadingTokenDetail() {
</StatPair>
</StatsLoadingContainer>
</StatsSection>
<AboutContainer>
<AboutHeader>
<SquareLoadingBubble />
</AboutHeader>
<LongLoadingBubble />
<LongLoadingBubble />
<HalfLoadingBubble />
<ResourcesContainer>{null}</ResourcesContainer>
</AboutContainer>
<ContractAddressSection>{null}</ContractAddressSection>
</TopArea>
)

View File

@@ -5,10 +5,10 @@ import { EventType } from '@visx/event/lib/types'
import { GlyphCircle } from '@visx/glyph'
import { Line } from '@visx/shape'
import { filterTimeAtom } from 'components/Tokens/state'
import { bisect, curveBasis, NumberValue, scaleLinear } from 'd3'
import { bisect, curveCardinal, NumberValue, scaleLinear } from 'd3'
import { useTokenPriceQuery } from 'graphql/data/TokenPriceQuery'
import { TimePeriod } from 'graphql/data/TopTokenQuery'
import { useActiveLocale } from 'hooks/useActiveLocale'
import { TimePeriod } from 'hooks/useExplorePageQuery'
import { useAtom } from 'jotai'
import { useCallback, useState } from 'react'
import { ArrowDownRight, ArrowUpRight } from 'react-feather'
@@ -18,7 +18,7 @@ import {
dayHourFormatter,
hourFormatter,
monthDayFormatter,
monthFormatter,
monthTickFormatter,
monthYearDayFormatter,
monthYearFormatter,
weekFormatter,
@@ -47,17 +47,26 @@ const StyledDownArrow = styled(ArrowDownRight)`
color: ${({ theme }) => theme.accentFailure};
`
export function getDelta(start: number, current: number) {
const delta = (current / start - 1) * 100
const isPositive = Math.sign(delta) > 0
export function calculateDelta(start: number, current: number) {
return (current / start - 1) * 100
}
const formattedDelta = delta.toFixed(2) + '%'
if (isPositive) {
return ['+' + formattedDelta, <StyledUpArrow size={16} key="arrow-up" />]
export function getDeltaArrow(delta: number) {
if (Math.sign(delta) > 0) {
return <StyledUpArrow size={16} key="arrow-up" />
} else if (delta === 0) {
return [formattedDelta, null]
return null
} else {
return <StyledDownArrow size={16} key="arrow-down" />
}
return [formattedDelta, <StyledDownArrow size={16} key="arrow-down" />]
}
export function formatDelta(delta: number) {
let formattedDelta = delta.toFixed(2) + '%'
if (Math.sign(delta) > 0) {
formattedDelta = '+' + formattedDelta
}
return formattedDelta
}
export const ChartHeader = styled.div`
@@ -72,6 +81,7 @@ export const DeltaContainer = styled.div`
height: 16px;
display: flex;
align-items: center;
margin-top: 4px;
`
const ArrowCell = styled.div`
padding-left: 2px;
@@ -130,7 +140,7 @@ function tickFormat(
case TimePeriod.MONTH:
return [monthDayFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp)]
case TimePeriod.YEAR:
return [monthFormatter(locale), monthYearDayFormatter(locale), getTicks(startTimestamp, endTimestamp)]
return [monthTickFormatter(locale), monthYearDayFormatter(locale), getTicks(startTimestamp, endTimestamp)]
case TimePeriod.ALL:
return [monthYearFormatter(locale), monthYearDayFormatter(locale), getTicks(startTimestamp, endTimestamp)]
}
@@ -165,8 +175,9 @@ export function PriceChart({ width, height, token }: PriceChartProps) {
const [crosshair, setCrosshair] = useState<number | null>(null)
const graphWidth = width + crosshairDateOverhang
const graphHeight = height - timeOptionsHeight
const graphInnerHeight = graphHeight - margin.top - margin.bottom
// TODO: remove this logic after suspense is properly added
const graphHeight = height - timeOptionsHeight > 0 ? height - timeOptionsHeight : 0
const graphInnerHeight = graphHeight - margin.top - margin.bottom > 0 ? graphHeight - margin.top - margin.bottom : 0
// Defining scales
// x scale
@@ -177,7 +188,7 @@ export function PriceChart({ width, height, token }: PriceChartProps) {
const handleHover = useCallback(
(event: Element | EventType) => {
const { x } = localPoint(event) || { x: 0 }
const x0 = timeScale.invert(x) // get timestamp from the scale
const x0 = timeScale.invert(x) // get timestamp from the scalexw
const index = bisect(
pricePoints.map((x) => x.timestamp),
x0,
@@ -215,16 +226,21 @@ export function PriceChart({ width, height, token }: PriceChartProps) {
timePeriod,
locale
)
const [delta, arrow] = getDelta(startingPrice.value, displayPrice.value)
const delta = calculateDelta(startingPrice.value, displayPrice.value)
const formattedDelta = formatDelta(delta)
const arrow = getDeltaArrow(delta)
const crosshairEdgeMax = width * 0.85
const crosshairAtEdge = !!crosshair && crosshair > crosshairEdgeMax
/* Default curve doesn't look good for the ALL chart */
const curveTension = timePeriod === TimePeriod.ALL ? 0.75 : 0.9
return (
<>
<ChartHeader>
<TokenPrice>${displayPrice.value.toFixed(2)}</TokenPrice>
<TokenPrice>${displayPrice.value < 0.000001 ? '<0.000001' : displayPrice.value.toFixed(6)}</TokenPrice>
<DeltaContainer>
{delta}
{formattedDelta}
<ArrowCell>{arrow}</ArrowCell>
</DeltaContainer>
</ChartHeader>
@@ -233,8 +249,7 @@ export function PriceChart({ width, height, token }: PriceChartProps) {
getX={(p: PricePoint) => timeScale(p.timestamp)}
getY={(p: PricePoint) => rdScale(p.value)}
marginTop={margin.top}
/* Default curve doesn't look good for the ALL chart */
curve={timePeriod === TimePeriod.ALL ? curveBasis : undefined}
curve={curveCardinal.tension(curveTension)}
strokeWidth={2}
width={graphWidth}
height={graphHeight}

View File

@@ -1,3 +1,4 @@
import { Trans } from '@lingui/macro'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { useRef } from 'react'
import { Twitter } from 'react-feather'
@@ -62,6 +63,7 @@ const ShareAction = styled.div`
interface TokenInfo {
tokenName: string
tokenSymbol: string
tokenAddress: string
}
export default function ShareButton(tokenInfo: TokenInfo) {
@@ -76,7 +78,7 @@ export default function ShareButton(tokenInfo: TokenInfo) {
const shareTweet = () => {
toggleShare()
window.open(
`https://twitter.com/intent/tweet?text=Check%20out%20${tokenInfo.tokenName}%20(${tokenInfo.tokenSymbol})%20https://app.uniswap.org/%23/tokens/${tokenInfo.tokenSymbol}%20via%20@uniswap`,
`https://twitter.com/intent/tweet?text=Check%20out%20${tokenInfo.tokenName}%20(${tokenInfo.tokenSymbol})%20https://app.uniswap.org/%23/tokens/${tokenInfo.tokenAddress}%20via%20@uniswap`,
'newwindow',
`left=${positionX}, top=${positionY}, width=${TWITTER_WIDTH}, height=${TWITTER_HEIGHT}`
)
@@ -97,13 +99,13 @@ export default function ShareButton(tokenInfo: TokenInfo) {
toCopy={window.location.href}
ref={copyHelperRef}
>
Copy Link
<Trans>Copy Link</Trans>
</CopyHelper>
</ShareAction>
<ShareAction onClick={shareTweet}>
<Twitter color={theme.textPrimary} size={20} strokeWidth={1.5} />
Share to Twitter
<Trans>Share to Twitter</Trans>
</ShareAction>
</ShareActions>
)}

View File

@@ -1,60 +1,46 @@
import { Trans } from '@lingui/macro'
import { ParentSize } from '@visx/responsive'
import { useWeb3React } from '@web3-react/core'
import CurrencyLogo from 'components/CurrencyLogo'
import PriceChart from 'components/Tokens/TokenDetails/PriceChart'
import { VerifiedIcon } from 'components/TokenSafety/TokenSafetyIcon'
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
import { getChainInfo } from 'constants/chainInfo'
import { checkWarning } from 'constants/tokenSafety'
import { nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
import { checkWarning, WARNING_LEVEL } from 'constants/tokenSafety'
import { chainIdToChainName, useTokenDetailQuery } from 'graphql/data/TokenDetailQuery'
import { useCurrency, useIsUserAddedToken, useToken } from 'hooks/Tokens'
import { useAtomValue } from 'jotai/utils'
import { useCallback } from 'react'
import { darken } from 'polished'
import { Suspense, useCallback } from 'react'
import { useState } from 'react'
import { ArrowLeft, Heart, TrendingUp } from 'react-feather'
import { Link, useNavigate } from 'react-router-dom'
import { ArrowLeft, Heart } from 'react-feather'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components/macro'
import { ClickableStyle, CopyContractAddress } from 'theme'
import { formatDollarAmount } from 'utils/formatDollarAmt'
import { favoritesAtom, filterNetworkAtom, useToggleFavorite } from '../state'
import { ClickFavorited } from '../TokenTable/TokenRow'
import { Wave } from './LoadingTokenDetail'
import LoadingTokenDetail from './LoadingTokenDetail'
import Resource from './Resource'
import ShareButton from './ShareButton'
import {
AboutContainer,
AboutHeader,
BreadcrumbNavLink,
ChartContainer,
ChartHeader,
ContractAddressSection,
ResourcesContainer,
Stat,
StatPair,
StatsSection,
TokenInfoContainer,
TokenNameCell,
TopArea,
} from './TokenDetailContainers'
export const AboutSection = styled.div`
display: flex;
flex-direction: column;
gap: 16px;
padding: 24px 0px;
`
export const AboutHeader = styled.span`
font-size: 28px;
line-height: 36px;
`
export const BreadcrumbNavLink = styled(Link)`
display: flex;
color: ${({ theme }) => theme.textSecondary};
font-size: 14px;
line-height: 20px;
align-items: center;
gap: 4px;
text-decoration: none;
margin-bottom: 16px;
&:hover {
color: ${({ theme }) => theme.textTertiary};
}
`
export const ChartHeader = styled.div`
width: 100%;
display: flex;
flex-direction: column;
color: ${({ theme }) => theme.textPrimary};
gap: 4px;
margin-bottom: 24px;
`
const ContractAddress = styled.button`
display: flex;
color: ${({ theme }) => theme.textPrimary};
@@ -62,12 +48,10 @@ const ContractAddress = styled.button`
align-items: center;
background: transparent;
border: none;
min-height: 38px;
padding: 0px;
cursor: pointer;
`
export const ContractAddressSection = styled.div`
padding: 24px 0px;
`
const Contract = styled.div`
display: flex;
flex-direction: column;
@@ -75,62 +59,19 @@ const Contract = styled.div`
font-size: 14px;
gap: 4px;
`
export const ChartContainer = styled.div`
display: flex;
height: 436px;
align-items: center;
`
export const Stat = styled.div`
display: flex;
flex-direction: column;
color: ${({ theme }) => theme.textSecondary};
font-size: 14px;
min-width: 168px;
flex: 1;
gap: 4px;
padding: 24px 0px;
`
const StatPrice = styled.span`
font-size: 28px;
color: ${({ theme }) => theme.textPrimary};
`
export const StatsSection = styled.div`
display: flex;
flex-wrap: wrap;
`
export const StatPair = styled.div`
display: flex;
flex: 1;
flex-wrap: wrap;
`
export const TokenNameCell = styled.div`
display: flex;
gap: 8px;
font-size: 20px;
line-height: 28px;
align-items: center;
`
const TokenActions = styled.div`
display: flex;
gap: 16px;
color: ${({ theme }) => theme.textSecondary};
`
export const TokenInfoContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
`
const TokenSymbol = styled.span`
text-transform: uppercase;
color: ${({ theme }) => theme.textSecondary};
`
export const TopArea = styled.div`
max-width: 832px;
overflow: hidden;
`
export const ResourcesContainer = styled.div`
display: flex;
gap: 14px;
`
const NetworkBadge = styled.div<{ networkColor?: string; backgroundColor?: string }>`
border-radius: 5px;
padding: 4px 8px;
@@ -147,36 +88,99 @@ const FavoriteIcon = styled(Heart)<{ isFavorited: boolean }>`
color: ${({ isFavorited, theme }) => (isFavorited ? theme.accentAction : theme.textSecondary)};
fill: ${({ isFavorited, theme }) => (isFavorited ? theme.accentAction : 'transparent')};
`
const ChartEmpty = styled.div`
display: flex;
height: 400px;
align-items: center;
`
const NoInfoAvailable = styled.span`
color: ${({ theme }) => theme.textTertiary};
font-weight: 400;
font-size: 16px;
`
const MissingChartData = styled.div`
color: ${({ theme }) => theme.textTertiary};
display: flex;
font-weight: 400;
font-size: 12px;
gap: 4px;
align-items: center;
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
padding: 8px 0px;
margin-top: -40px;
const TokenDescriptionContainer = styled.div`
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
max-height: fit-content;
padding-top: 16px;
line-height: 24px;
white-space: pre-wrap;
`
const MissingData = styled.div`
display: flex;
flex-direction: column;
gap: 20px;
const TruncateDescriptionButton = styled.div`
color: ${({ theme }) => theme.textSecondary};
font-weight: 400;
font-size: 14px;
padding-top: 14px;
&:hover,
&:focus {
color: ${({ theme }) => darken(0.1, theme.textSecondary)};
cursor: pointer;
}
`
const TRUNCATE_CHARACTER_COUNT = 400
type TokenDetailData = {
description: string | null | undefined
homepageUrl: string | null | undefined
twitterName: string | null | undefined
}
const truncateDescription = (desc: string) => {
//trim the string to the maximum length
let tokenDescriptionTruncated = desc.slice(0, TRUNCATE_CHARACTER_COUNT)
//re-trim if we are in the middle of a word
tokenDescriptionTruncated = `${tokenDescriptionTruncated.slice(
0,
Math.min(tokenDescriptionTruncated.length, tokenDescriptionTruncated.lastIndexOf(' '))
)}...`
return tokenDescriptionTruncated
}
export function AboutSection({ address, tokenDetailData }: { address: string; tokenDetailData: TokenDetailData }) {
const [isDescriptionTruncated, setIsDescriptionTruncated] = useState(true)
const shouldTruncate =
tokenDetailData && tokenDetailData.description
? tokenDetailData.description.length > TRUNCATE_CHARACTER_COUNT
: false
const tokenDescription =
tokenDetailData && tokenDetailData.description && shouldTruncate && isDescriptionTruncated
? truncateDescription(tokenDetailData.description)
: tokenDetailData.description
return (
<AboutContainer>
<AboutHeader>
<Trans>About</Trans>
</AboutHeader>
<TokenDescriptionContainer>
{(!tokenDetailData || !tokenDetailData.description) && (
<NoInfoAvailable>
<Trans>No token information available</Trans>
</NoInfoAvailable>
)}
{tokenDescription}
{shouldTruncate && (
<TruncateDescriptionButton onClick={() => setIsDescriptionTruncated(!isDescriptionTruncated)}>
{isDescriptionTruncated ? <Trans>Read more</Trans> : <Trans>Hide</Trans>}
</TruncateDescriptionButton>
)}
</TokenDescriptionContainer>
<ResourcesContainer>
<Resource name={'Etherscan'} link={`https://etherscan.io/address/${address}`} />
<Resource name={'Protocol info'} link={`https://info.uniswap.org/#/tokens/${address}`} />
{tokenDetailData?.homepageUrl && <Resource name={'Website'} link={tokenDetailData.homepageUrl} />}
{tokenDetailData?.twitterName && (
<Resource name={'Twitter'} link={`https://twitter.com/${tokenDetailData.twitterName}`} />
)}
</ResourcesContainer>
</AboutContainer>
)
}
export default function LoadedTokenDetail({ address }: { address: string }) {
const { chainId: connectedChainId } = useWeb3React()
const token = useToken(address)
const currency = useCurrency(address)
let currency = useCurrency(address)
const favoriteTokens = useAtomValue<string[]>(favoritesAtom)
const isFavorited = favoriteTokens.includes(address)
const toggleFavorite = useToggleFavorite(address)
@@ -188,15 +192,38 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
const handleDismissWarning = useCallback(() => {
setWarningModalOpen(false)
}, [setWarningModalOpen])
const handleCancel = useCallback(() => {
setWarningModalOpen(false)
warning && warning.level === WARNING_LEVEL.BLOCKED && navigate(-1)
}, [setWarningModalOpen, navigate, warning])
const chainInfo = getChainInfo(token?.chainId)
const networkLabel = chainInfo?.label
const networkBadgebackgroundColor = chainInfo?.backgroundColor
const filterNetwork = useAtomValue(filterNetworkAtom)
const tokenDetailData = useTokenDetailQuery(address, chainIdToChainName(filterNetwork))
const relevantTokenDetailData = (({ description, homepageUrl, twitterName }) => ({
description,
homepageUrl,
twitterName,
}))(tokenDetailData)
// catch token error and loading state
if (!token || !token.name || !token.symbol) {
return (
if (!token || !token.name || !token.symbol || !connectedChainId) {
return <LoadingTokenDetail />
}
const wrappedNativeCurrency = WRAPPED_NATIVE_CURRENCY[connectedChainId]
const isWrappedNativeToken = wrappedNativeCurrency?.address === token.address
if (isWrappedNativeToken) {
currency = nativeOnChain(connectedChainId)
}
const tokenName = isWrappedNativeToken && currency ? currency.name : tokenDetailData.name
const defaultTokenSymbol = tokenDetailData.tokens?.[0]?.symbol ?? token.symbol
const tokenSymbol = isWrappedNativeToken && currency ? currency.symbol : defaultTokenSymbol
return (
<Suspense fallback={<LoadingTokenDetail />}>
<TopArea>
<BreadcrumbNavLink to="/tokens">
<ArrowLeft size={14} /> Tokens
@@ -204,9 +231,9 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
<ChartHeader>
<TokenInfoContainer>
<TokenNameCell>
<CurrencyLogo currency={currency} size={'32px'} />
<Trans>{!token ? 'Name not found' : token.name}</Trans>
<TokenSymbol>{token && token.symbol}</TokenSymbol>
<CurrencyLogo currency={currency} size={'32px'} symbol={tokenSymbol} />
{tokenName ?? <Trans>Name not found</Trans>}
<TokenSymbol>{tokenSymbol ?? <Trans>Symbol not found</Trans>}</TokenSymbol>
{!warning && <VerifiedIcon size="20px" />}
{networkBadgebackgroundColor && (
<NetworkBadge networkColor={chainInfo?.color} backgroundColor={networkBadgebackgroundColor}>
@@ -214,143 +241,65 @@ export default function LoadedTokenDetail({ address }: { address: string }) {
</NetworkBadge>
)}
</TokenNameCell>
<TokenActions>
{tokenName && tokenSymbol && (
<ShareButton tokenName={tokenName} tokenSymbol={tokenSymbol} tokenAddress={address} />
)}
<ClickFavorited onClick={toggleFavorite}>
<FavoriteIcon isFavorited={isFavorited} />
</ClickFavorited>
</TokenActions>
</TokenInfoContainer>
<ChartEmpty>
<Wave />
<Wave />
</ChartEmpty>
<MissingChartData>
<TrendingUp size={12} />
Missing chart data
</MissingChartData>
<ChartContainer>
<ParentSize>{({ width, height }) => <PriceChart token={token} width={width} height={height} />}</ParentSize>
</ChartContainer>
</ChartHeader>
<MissingData>
<AboutSection>
<AboutHeader>
<Trans>About</Trans>
</AboutHeader>
<NoInfoAvailable>
<Trans>No token information available</Trans>
</NoInfoAvailable>
<ResourcesContainer>
<Resource name={'Etherscan'} link={`https://etherscan.io/address/${address}`} />
<Resource name={'Protocol Info'} link={`https://info.uniswap.org/#/tokens/${address}`} />
</ResourcesContainer>
</AboutSection>
<StatsSection>
<NoInfoAvailable>
<Trans>No stats available</Trans>
</NoInfoAvailable>
</StatsSection>
<ContractAddressSection>
<Contract>
Contract Address
<ContractAddress>
<CopyContractAddress address={address} />
</ContractAddress>
</Contract>
</ContractAddressSection>
</MissingData>
<StatsSection>
<StatPair>
<Stat>
<Trans>Market cap</Trans>
<StatPrice>
{tokenDetailData.marketCap?.value ? formatDollarAmount(tokenDetailData.marketCap?.value) : '-'}
</StatPrice>
</Stat>
<Stat>
24H volume
<StatPrice>
{tokenDetailData.volume24h?.value ? formatDollarAmount(tokenDetailData.volume24h?.value) : '-'}
</StatPrice>
</Stat>
</StatPair>
<StatPair>
<Stat>
52W low
<StatPrice>
{tokenDetailData.priceLow52W?.value ? formatDollarAmount(tokenDetailData.priceLow52W?.value) : '-'}
</StatPrice>
</Stat>
<Stat>
52W high
<StatPrice>
{tokenDetailData.priceHigh52W?.value ? formatDollarAmount(tokenDetailData.priceHigh52W?.value) : '-'}
</StatPrice>
</Stat>
</StatPair>
</StatsSection>
<AboutSection address={address} tokenDetailData={relevantTokenDetailData} />
<ContractAddressSection>
<Contract>
<Trans>Contract address</Trans>
<ContractAddress>
<CopyContractAddress address={address} />
</ContractAddress>
</Contract>
</ContractAddressSection>
<TokenSafetyModal
isOpen={warningModalOpen}
tokenAddress={address}
onCancel={() => navigate(-1)}
onCancel={handleCancel}
onContinue={handleDismissWarning}
/>
</TopArea>
)
}
const tokenName = tokenDetailData.name
const tokenSymbol = tokenDetailData.tokens?.[0].symbol?.toUpperCase()
return (
<TopArea>
<BreadcrumbNavLink to="/tokens">
<ArrowLeft size={14} /> Tokens
</BreadcrumbNavLink>
<ChartHeader>
<TokenInfoContainer>
<TokenNameCell>
<CurrencyLogo currency={currency} size={'32px'} />
{tokenName ?? <Trans>Name not found</Trans>}
<TokenSymbol>{tokenSymbol ?? <Trans>Symbol not found</Trans>}</TokenSymbol>
{!warning && <VerifiedIcon size="20px" />}
{networkBadgebackgroundColor && (
<NetworkBadge networkColor={chainInfo?.color} backgroundColor={networkBadgebackgroundColor}>
{networkLabel}
</NetworkBadge>
)}
</TokenNameCell>
<TokenActions>
{tokenName && tokenSymbol && <ShareButton tokenName={tokenName} tokenSymbol={tokenSymbol} />}
<ClickFavorited onClick={toggleFavorite}>
<FavoriteIcon isFavorited={isFavorited} />
</ClickFavorited>
</TokenActions>
</TokenInfoContainer>
<ChartContainer>
<ParentSize>{({ width, height }) => <PriceChart token={token} width={width} height={height} />}</ParentSize>
</ChartContainer>
</ChartHeader>
<AboutSection>
<AboutHeader>
<Trans>About</Trans>
</AboutHeader>
{tokenDetailData.description}
<ResourcesContainer>
<Resource name={'Etherscan'} link={`https://etherscan.io/address/${address}`} />
<Resource name={'Protocol Info'} link={`https://info.uniswap.org/#/tokens/${address}`} />
{tokenDetailData.homepageUrl && <Resource name={'Website'} link={tokenDetailData.homepageUrl} />}
{tokenDetailData.twitterName && (
<Resource name={'Twitter'} link={`https://twitter.com/${tokenDetailData.twitterName}`} />
)}
</ResourcesContainer>
</AboutSection>
<StatsSection>
<StatPair>
<Stat>
Market cap
<StatPrice>
{tokenDetailData.marketCap?.value ? formatDollarAmount(tokenDetailData.marketCap?.value) : '-'}
</StatPrice>
</Stat>
<Stat>
24H volume
<StatPrice>
{tokenDetailData.volume24h?.value ? formatDollarAmount(tokenDetailData.volume24h?.value) : '-'}
</StatPrice>
</Stat>
</StatPair>
<StatPair>
<Stat>
52W low
<StatPrice>
{tokenDetailData.priceLow52W?.value ? formatDollarAmount(tokenDetailData.priceLow52W?.value) : '-'}
</StatPrice>
</Stat>
<Stat>
52W high
<StatPrice>
{tokenDetailData.priceHigh52W?.value ? formatDollarAmount(tokenDetailData.priceHigh52W?.value) : '-'}
</StatPrice>
</Stat>
</StatPair>
</StatsSection>
<ContractAddressSection>
<Contract>
Contract Address
<ContractAddress>
<CopyContractAddress address={address} />
</ContractAddress>
</Contract>
</ContractAddressSection>
<TokenSafetyModal
isOpen={warningModalOpen}
tokenAddress={address}
onCancel={() => navigate(-1)}
onContinue={handleDismissWarning}
/>
</TopArea>
</Suspense>
)
}

View File

@@ -0,0 +1,81 @@
import { Link } from 'react-router-dom'
import styled from 'styled-components/macro'
export const AboutContainer = styled.div`
gap: 16px;
padding: 24px 0px;
`
export const AboutHeader = styled.span`
font-size: 28px;
line-height: 36px;
`
export const BreadcrumbNavLink = styled(Link)`
display: flex;
color: ${({ theme }) => theme.textSecondary};
font-size: 14px;
line-height: 20px;
align-items: center;
gap: 4px;
text-decoration: none;
margin-bottom: 16px;
&:hover {
color: ${({ theme }) => theme.textTertiary};
}
`
export const ChartHeader = styled.div`
width: 100%;
display: flex;
flex-direction: column;
color: ${({ theme }) => theme.textPrimary};
gap: 4px;
margin-bottom: 24px;
`
export const ContractAddressSection = styled.div`
padding: 36px 0px;
`
export const ChartContainer = styled.div`
display: flex;
height: 436px;
align-items: center;
`
export const Stat = styled.div`
display: flex;
flex-direction: column;
color: ${({ theme }) => theme.textSecondary};
font-size: 14px;
min-width: 168px;
flex: 1;
gap: 4px;
padding: 24px 0px;
`
export const StatsSection = styled.div`
display: flex;
flex-wrap: wrap;
`
export const StatPair = styled.div`
display: flex;
flex: 1;
flex-wrap: wrap;
`
export const TokenNameCell = styled.div`
display: flex;
gap: 8px;
font-size: 20px;
line-height: 28px;
align-items: center;
`
export const TokenInfoContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
`
export const TopArea = styled.div`
max-width: 832px;
overflow: hidden;
`
export const ResourcesContainer = styled.div`
display: flex;
padding-top: 12px;
gap: 14px;
`

View File

@@ -1,8 +1,9 @@
import { Trans } from '@lingui/macro'
import { useAtom } from 'jotai'
import { Heart } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
import { SMALL_MEDIA_BREAKPOINT } from '../constants'
import { SMALLEST_MOBILE_MEDIA_BREAKPOINT } from '../constants'
import { showFavoritesAtom } from '../state'
const FavoriteButtonContent = styled.div`
@@ -14,9 +15,9 @@ const FavoriteButtonContent = styled.div`
const StyledFavoriteButton = styled.button<{ active: boolean }>`
padding: 0px 16px;
border-radius: 12px;
background-color: ${({ theme, active }) => (active ? theme.accentAction : theme.backgroundInteractive)};
background-color: ${({ theme, active }) => (active ? theme.accentActiveSoft : theme.backgroundInteractive)};
border: none;
color: ${({ theme, active }) => (active ? theme.white : theme.textPrimary)};
color: ${({ theme, active }) => (active ? theme.accentActive : theme.textPrimary)};
font-size: 16px;
font-weight: 600;
cursor: pointer;
@@ -26,7 +27,7 @@ const StyledFavoriteButton = styled.button<{ active: boolean }>`
}
`
const FavoriteText = styled.span`
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
@media only screen and (max-width: ${SMALLEST_MOBILE_MEDIA_BREAKPOINT}) {
display: none;
}
`
@@ -37,8 +38,14 @@ export default function FavoriteButton() {
return (
<StyledFavoriteButton onClick={() => setShowFavorites(!showFavorites)} active={showFavorites}>
<FavoriteButtonContent>
<Heart size={17} color={showFavorites ? theme.white : theme.textPrimary} fill="transparent" />
<FavoriteText>Favorites</FavoriteText>
<Heart
size={17}
color={showFavorites ? theme.accentActive : theme.textPrimary}
fill={showFavorites ? theme.accentActive : 'transparent'}
/>
<FavoriteText>
<Trans>Favorites</Trans>
</FavoriteText>
</FavoriteButtonContent>
</StyledFavoriteButton>
)

View File

@@ -63,7 +63,7 @@ const MenuTimeFlyout = styled.span`
const StyledMenuButton = styled.button<{ open: boolean }>`
width: 100%;
height: 100%;
color: ${({ theme, open }) => (open ? theme.blue200 : theme.textPrimary)};
color: ${({ theme, open }) => (open ? theme.accentActive : theme.textPrimary)};
border: none;
background-color: ${({ theme, open }) => (open ? theme.accentActiveSoft : theme.backgroundInteractive)};
margin: 0;
@@ -115,7 +115,7 @@ const StyledMenuContent = styled.div`
const Chevron = styled.span<{ open: boolean }>`
padding-top: 1px;
color: ${({ open, theme }) => (open ? theme.blue200 : theme.textSecondary)};
color: ${({ open, theme }) => (open ? theme.accentActive : theme.textSecondary)};
`
const NetworkLabel = styled.div`
display: flex;

View File

@@ -1,3 +1,4 @@
import { Trans } from '@lingui/macro'
import searchIcon from 'assets/svg/search.svg'
import xIcon from 'assets/svg/x.svg'
import { useAtom } from 'jotai'
@@ -16,24 +17,25 @@ const SearchInput = styled.input`
background-image: url(${searchIcon});
background-size: 20px 20px;
background-position: 12px center;
background-color: ${({ theme }) => theme.backgroundSurface};
background-color: ${({ theme }) => theme.backgroundModule};
border-radius: 12px;
border: 1px solid ${({ theme }) => theme.backgroundOutline};
height: 100%;
width: min(300px, 100%);
width: min(200px, 100%);
font-size: 16px;
padding-left: 40px;
color: ${({ theme }) => theme.textSecondary};
:hover {
background-color: ${({ theme }) => theme.backgroundModule};
background-color: ${({ theme }) => theme.backgroundSurface};
}
:focus {
outline: none;
background-color: ${({ theme }) => theme.backgroundSurface};
border: 1.5px solid ${({ theme }) => theme.accentActionSoft};
border-color: ${({ theme }) => theme.accentActionSoft};
}
::placeholder {
color: ${({ theme }) => theme.textTertiary};
}
@@ -57,14 +59,20 @@ export default function SearchBar() {
const [filterString, setFilterString] = useAtom(filterStringAtom)
return (
<SearchBarContainer>
<SearchInput
type="search"
placeholder="Search tokens"
id="searchBar"
autoComplete="off"
value={filterString}
onChange={({ target: { value } }) => setFilterString(value)}
/>
<Trans
render={({ translation }) => (
<SearchInput
type="search"
placeholder={`${translation}`}
id="searchBar"
autoComplete="off"
value={filterString}
onChange={({ target: { value } }) => setFilterString(value)}
/>
)}
>
Filter tokens
</Trans>
</SearchBarContainer>
)
}

View File

@@ -1,4 +1,4 @@
import { TimePeriod } from 'hooks/useExplorePageQuery'
import { TimePeriod } from 'graphql/data/TopTokenQuery'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { useAtom } from 'jotai'
import { useRef } from 'react'
@@ -81,7 +81,7 @@ const StyledMenuButton = styled.button<{ open: boolean }>`
width: 100%;
height: 100%;
border: none;
color: ${({ theme, open }) => (open ? theme.blue200 : theme.textPrimary)};
color: ${({ theme, open }) => (open ? theme.accentActive : theme.textPrimary)};
margin: 0;
background-color: ${({ theme, open }) => (open ? theme.accentActiveSoft : theme.backgroundInteractive)};
padding: 6px 12px 6px 12px;
@@ -131,7 +131,7 @@ const StyledMenuContent = styled.div`
const Chevron = styled.span<{ open: boolean }>`
padding-top: 1px;
color: ${({ open, theme }) => (open ? theme.blue200 : theme.textSecondary)};
color: ${({ open, theme }) => (open ? theme.accentActive : theme.textSecondary)};
`
// TODO: change this to reflect data pipeline

View File

@@ -5,17 +5,14 @@ import { EventName } from 'components/AmplitudeAnalytics/constants'
import SparklineChart from 'components/Charts/SparklineChart'
import CurrencyLogo from 'components/CurrencyLogo'
import { getChainInfo } from 'constants/chainInfo'
import { chainIdToChainName } from 'graphql/data/TokenDetailQuery'
import { useTokenPriceQuery } from 'graphql/data/TokenPriceQuery'
import { useTokenRowQuery } from 'graphql/data/TokenRowQuery'
import { useCurrency, useToken } from 'hooks/Tokens'
import { TimePeriod, TokenData } from 'hooks/useExplorePageQuery'
import { TimePeriod, TokenData } from 'graphql/data/TopTokenQuery'
import { useCurrency } from 'hooks/Tokens'
import { useAtom } from 'jotai'
import { useAtomValue } from 'jotai/utils'
import { ReactNode } from 'react'
import { ArrowDown, ArrowUp, Heart } from 'react-feather'
import { Link } from 'react-router-dom'
import styled, { useTheme } from 'styled-components/macro'
import styled, { css, useTheme } from 'styled-components/macro'
import { formatDollarAmount } from 'utils/formatDollarAmt'
import {
@@ -35,7 +32,7 @@ import {
useSetSortCategory,
useToggleFavorite,
} from '../state'
import { DATA_EMPTY, getDelta, PricePoint } from '../TokenDetails/PriceChart'
import { formatDelta, getDeltaArrow } from '../TokenDetails/PriceChart'
import { Category, SortDirection } from '../types'
import { DISPLAYS } from './TimeSelector'
@@ -44,20 +41,37 @@ const Cell = styled.div`
align-items: center;
justify-content: center;
`
const StyledTokenRow = styled.div`
width: 100%;
height: 60px;
const StyledTokenRow = styled.div<{ first?: boolean; last?: boolean; loading?: boolean }>`
background-color: transparent;
display: grid;
grid-template-columns: 1fr 7fr 4fr 4fr 4fr 4fr 5fr 1.2fr;
font-size: 15px;
grid-template-columns: 1fr 7fr 4fr 4fr 4fr 4fr 5fr 1.2fr;
height: 60px;
line-height: 24px;
max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT};
min-width: 390px;
padding: 0px 12px;
padding-top: ${({ first }) => (first ? '4px' : '0px')};
padding-bottom: ${({ last }) => (last ? '4px' : '0px')};
padding-left: 12px;
padding-right: 12px;
transition: ${({
theme: {
transition: { duration, timing },
},
}) => css`background-color ${duration.medium} ${timing.ease}`};
width: 100%;
&:hover {
background-color: ${({ theme }) => theme.accentActionSoft};
${({ loading, theme }) =>
!loading &&
css`
background-color: ${theme.hoverDefault};
`}
${({ last }) =>
last &&
css`
border-radius: 0px 0px 8px 8px;
`}
}
@media only screen and (max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT}) {
@@ -125,6 +139,7 @@ const StyledHeaderRow = styled(StyledTokenRow)`
line-height: 16px;
padding: 0px 12px;
width: 100%;
justify-content: center;
&:hover {
background-color: transparent;
@@ -134,9 +149,11 @@ const StyledHeaderRow = styled(StyledTokenRow)`
justify-content: space-between;
}
`
const ListNumberCell = styled(Cell)`
const ListNumberCell = styled(Cell)<{ header: boolean }>`
color: ${({ theme }) => theme.textSecondary};
min-width: 32px;
height: ${({ header }) => (header ? '48px' : '60px')};
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
display: none;
@@ -147,10 +164,11 @@ const DataCell = styled(Cell)<{ sortable: boolean }>`
min-width: 80px;
user-select: ${({ sortable }) => (sortable ? 'none' : 'unset')};
&:hover {
color: ${({ theme, sortable }) => sortable && theme.white};
background-color: ${({ theme, sortable }) => sortable && theme.accentActionSoft};
}
transition: ${({
theme: {
transition: { duration, timing },
},
}) => css`background-color ${duration.medium} ${timing.ease}`};
`
const MarketCapCell = styled(DataCell)`
padding-right: 8px;
@@ -203,6 +221,10 @@ const HeaderCellWrapper = styled.span<{ onClick?: () => void }>`
height: 100%;
justify-content: flex-end;
width: 100%;
&:hover {
opacity: 60%;
}
`
const SparkLineCell = styled(Cell)`
padding: 0px 24px;
@@ -244,6 +266,7 @@ const TokenName = styled.div`
`
const TokenSymbol = styled(Cell)`
color: ${({ theme }) => theme.textTertiary};
text-transform: uppercase;
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
font-size: 12px;
@@ -294,7 +317,7 @@ const LogoContainer = styled.div`
/* formatting for volume with timeframe header display */
function getHeaderDisplay(category: string, timeframe: TimePeriod): string {
if (category === Category.volume) return `${DISPLAYS[timeframe]} ${category}`
if (category === Category.volume || category === Category.percentChange) return `${DISPLAYS[timeframe]} ${category}`
return category
}
@@ -341,9 +364,8 @@ function HeaderCell({
/* Token Row: skeleton row component */
export function TokenRow({
address,
header,
favorited,
header,
listNumber,
tokenInfo,
price,
@@ -351,21 +373,24 @@ export function TokenRow({
marketCap,
volume,
sparkLine,
...rest
}: {
address: ReactNode
header: boolean
favorited: ReactNode
first?: boolean
header: boolean
listNumber: ReactNode
tokenInfo: ReactNode
loading?: boolean
marketCap: ReactNode
price: ReactNode
percentChange: ReactNode
marketCap: ReactNode
volume: ReactNode
sparkLine: ReactNode
tokenInfo: ReactNode
volume: ReactNode
last?: boolean
}) {
const rowCells = (
<>
<ListNumberCell>{listNumber}</ListNumberCell>
<ListNumberCell header={header}>{listNumber}</ListNumberCell>
<NameCell>{tokenInfo}</NameCell>
<PriceCell sortable={header}>{price}</PriceCell>
<PercentChangeCell sortable={header}>{percentChange}</PercentChangeCell>
@@ -376,14 +401,13 @@ export function TokenRow({
</>
)
if (header) return <StyledHeaderRow>{rowCells}</StyledHeaderRow>
return <StyledTokenRow>{rowCells}</StyledTokenRow>
return <StyledTokenRow {...rest}>{rowCells}</StyledTokenRow>
}
/* Header Row: top header row component for table */
export function HeaderRow() {
return (
<TokenRow
address={null}
header={true}
favorited={null}
listNumber="#"
@@ -401,10 +425,10 @@ export function HeaderRow() {
export function LoadingRow() {
return (
<TokenRow
address={null}
header={false}
favorited={null}
header={false}
listNumber={<SmallLoadingBubble />}
loading
tokenInfo={
<>
<IconLoadingBubble />
@@ -425,19 +449,18 @@ export default function LoadedRow({
tokenAddress,
tokenListIndex,
tokenListLength,
data,
tokenData,
timePeriod,
}: {
tokenAddress: string
tokenListIndex: number
tokenListLength: number
data: TokenData
tokenData: TokenData
timePeriod: TimePeriod
}) {
const token = useToken(tokenAddress)
const currency = useCurrency(tokenAddress)
const tokenName = token?.name ?? ''
const tokenSymbol = token?.symbol ?? ''
const tokenName = tokenData.name
const tokenSymbol = tokenData.symbol
const theme = useTheme()
const [favoriteTokens] = useAtom(favoritesAtom)
const isFavorited = favoriteTokens.includes(tokenAddress)
@@ -445,22 +468,14 @@ export default function LoadedRow({
const filterString = useAtomValue(filterStringAtom)
const filterNetwork = useAtomValue(filterNetworkAtom)
const L2Icon = getChainInfo(filterNetwork).circleLogoUrl
// TODO: make delta shareable and fix based on future changes
const pricePoints: PricePoint[] = useTokenPriceQuery(tokenAddress, timePeriod, 'ETHEREUM').filter(
(p): p is PricePoint => Boolean(p && p.value)
)
const hasData = pricePoints.length !== 0
/* TODO: Implement API calls & cache to use here */
const startingPrice = hasData ? pricePoints[0] : DATA_EMPTY
const endingPrice = hasData ? pricePoints[pricePoints.length - 1] : DATA_EMPTY
const [delta, arrow] = getDelta(startingPrice.value, endingPrice.value)
const delta = tokenData.percentChange?.[timePeriod]?.value
const arrow = delta ? getDeltaArrow(delta) : null
const formattedDelta = delta ? formatDelta(delta) : null
const exploreTokenSelectedEventProperties = {
chain_id: filterNetwork,
token_address: tokenAddress,
token_symbol: token?.symbol,
token_symbol: tokenSymbol,
token_list_index: tokenListIndex,
token_list_length: tokenListLength,
time_frame: timePeriod,
@@ -468,8 +483,6 @@ export default function LoadedRow({
}
const heartColor = isFavorited ? theme.accentActive : undefined
// TODO: consider using backend network?
const tokenRowData = useTokenRowQuery(tokenAddress, timePeriod, chainIdToChainName(filterNetwork))
// TODO: currency logo sizing mobile (32px) vs. desktop (24px)
return (
<StyledLink
@@ -477,7 +490,6 @@ export default function LoadedRow({
onClick={() => sendAnalyticsEvent(EventName.EXPLORE_TOKEN_ROW_CLICKED, exploreTokenSelectedEventProperties)}
>
<TokenRow
address={tokenAddress}
header={false}
favorited={
<ClickFavorited
@@ -493,7 +505,7 @@ export default function LoadedRow({
tokenInfo={
<ClickableName>
<LogoContainer>
<CurrencyLogo currency={currency} />
<CurrencyLogo currency={currency} symbol={tokenSymbol} />
<L2NetworkLogo networkUrl={L2Icon} />
</LogoContainer>
<TokenInfoCell>
@@ -505,9 +517,9 @@ export default function LoadedRow({
price={
<ClickableContent>
<PriceInfoCell>
{tokenRowData.price?.value ? formatDollarAmount(tokenRowData.price?.value) : '-'}
{tokenData.price?.value ? formatDollarAmount(tokenData.price?.value) : '-'}
<PercentChangeInfoCell>
{delta}
{formattedDelta}
{arrow}
</PercentChangeInfoCell>
</PriceInfoCell>
@@ -515,18 +527,20 @@ export default function LoadedRow({
}
percentChange={
<ClickableContent>
{delta}
{formattedDelta}
{arrow}
</ClickableContent>
}
marketCap={
<ClickableContent>
{tokenRowData.marketCap?.value ? formatDollarAmount(tokenRowData.marketCap?.value) : '-'}
{tokenData.marketCap?.value ? formatDollarAmount(tokenData.marketCap?.value) : '-'}
</ClickableContent>
}
volume={
<ClickableContent>
{tokenRowData.volume?.value ? formatDollarAmount(tokenRowData.volume?.value) : '-'}
{tokenData.volume?.[timePeriod]?.value
? formatDollarAmount(tokenData.volume?.[timePeriod]?.value ?? undefined)
: '-'}
</ClickableContent>
}
sparkLine={
@@ -534,6 +548,8 @@ export default function LoadedRow({
<ParentSize>{({ width, height }) => <SparklineChart width={width} height={height} />}</ParentSize>
</SparkLine>
}
first={tokenListIndex === 0}
last={tokenListIndex === tokenListLength - 1}
/>
</StyledLink>
)

View File

@@ -1,3 +1,4 @@
import { Trans } from '@lingui/macro'
import {
favoritesAtom,
filterStringAtom,
@@ -6,10 +7,9 @@ import {
sortCategoryAtom,
sortDirectionAtom,
} from 'components/Tokens/state'
import { useAllTokens } from 'hooks/Tokens'
import { TimePeriod, TokenData } from 'hooks/useExplorePageQuery'
import { TimePeriod, TokenData } from 'graphql/data/TopTokenQuery'
import { useAtomValue } from 'jotai/utils'
import { ReactNode, useCallback, useMemo } from 'react'
import { ReactNode, Suspense, useCallback, useMemo } from 'react'
import { AlertTriangle } from 'react-feather'
import styled from 'styled-components/macro'
@@ -21,7 +21,7 @@ const GridContainer = styled.div`
display: flex;
flex-direction: column;
max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT};
background-color: ${({ theme }) => theme.backgroundModule};
background-color: ${({ theme }) => theme.backgroundSurface};
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);
margin-left: auto;
@@ -44,38 +44,36 @@ const NoTokenDisplay = styled.div`
gap: 8px;
`
const TokenRowsContainer = styled.div`
padding: 4px 0px;
width: 100%;
`
function useFilteredTokens(addresses: string[]) {
function useFilteredTokens(tokens: TokenData[] | undefined) {
const filterString = useAtomValue(filterStringAtom)
const favoriteTokens = useAtomValue(favoritesAtom)
const favoriteTokenAddresses = useAtomValue(favoritesAtom)
const showFavorites = useAtomValue(showFavoritesAtom)
const shownTokens = showFavorites ? favoriteTokens : addresses
const allTokens = useAllTokens()
const shownTokens =
showFavorites && tokens ? tokens.filter((token) => favoriteTokenAddresses.includes(token.address)) : tokens
return useMemo(
() =>
shownTokens.filter((tokenAddress) => {
const token = allTokens[tokenAddress]
const tokenName = token?.name ?? ''
const tokenSymbol = token?.symbol ?? ''
(shownTokens ?? []).filter((token) => {
if (!token.address) {
return false
}
if (!filterString) {
return true
}
const lowercaseFilterString = filterString.toLowerCase()
const addressIncludesFilterString = tokenAddress.toLowerCase().includes(lowercaseFilterString)
const nameIncludesFilterString = tokenName.toLowerCase().includes(lowercaseFilterString)
const symbolIncludesFilterString = tokenSymbol.toLowerCase().includes(lowercaseFilterString)
const addressIncludesFilterString = token?.address?.toLowerCase().includes(lowercaseFilterString)
const nameIncludesFilterString = token?.name?.toLowerCase().includes(lowercaseFilterString)
const symbolIncludesFilterString = token?.symbol?.toLowerCase().includes(lowercaseFilterString)
return nameIncludesFilterString || symbolIncludesFilterString || addressIncludesFilterString
}),
[allTokens, shownTokens, filterString]
[shownTokens, filterString]
)
}
function useSortedTokens(addresses: string[], tokenData: TokenData | null) {
function useSortedTokens(tokenData: TokenData[] | null) {
const sortCategory = useAtomValue(sortCategoryAtom)
const sortDirection = useAtomValue(sortDirectionAtom)
const timePeriod = useAtomValue<TimePeriod>(filterTimeAtom)
@@ -94,39 +92,38 @@ function useSortedTokens(addresses: string[], tokenData: TokenData | null) {
return useMemo(
() =>
addresses.sort((token1Address, token2Address) => {
tokenData &&
tokenData.sort((token1, token2) => {
if (!tokenData) {
return 0
}
const token1 = tokenData[token1Address] as any
const token2 = tokenData[token2Address] as any
// fix delta/percent change property
if (!token1 || !token2 || !sortDirection || !sortCategory) {
return 0
}
let a: number
let b: number
let a: number | null | undefined
let b: number | null | undefined
switch (sortCategory) {
case Category.marketCap:
a = token1.marketCap
b = token2.marketCap
break
case Category.percentChange:
a = token1.delta
b = token2.delta
a = token1.marketCap?.value
b = token2.marketCap?.value
break
case Category.price:
a = token1.price
b = token2.price
a = token1.price?.value
b = token2.price?.value
break
case Category.volume:
a = token1.volume[timePeriod]
b = token2.volume[timePeriod]
a = token1.volume?.[timePeriod]?.value
b = token2.volume?.[timePeriod]?.value
break
case Category.percentChange:
a = token1.percentChange?.[timePeriod]?.value
b = token2.percentChange?.[timePeriod]?.value
break
}
return sortFn(a, b)
}),
[addresses, tokenData, sortDirection, sortCategory, sortFn, timePeriod]
[tokenData, sortDirection, sortCategory, sortFn, timePeriod]
)
}
@@ -143,7 +140,7 @@ const LOADING_ROWS = Array.from({ length: 100 })
.fill(0)
.map((_item, index) => <LoadingRow key={index} />)
function LoadingTokenTable() {
export function LoadingTokenTable() {
return (
<GridContainer>
<HeaderRow />
@@ -152,58 +149,51 @@ function LoadingTokenTable() {
)
}
interface TokenTableProps {
data: TokenData | null
error: string | null
loading: boolean
}
export default function TokenTable({ data, error, loading }: TokenTableProps) {
export default function TokenTable({ data }: { data: TokenData[] | undefined }) {
const showFavorites = useAtomValue<boolean>(showFavoritesAtom)
const timePeriod = useAtomValue<TimePeriod>(filterTimeAtom)
const topTokenAddresses = data ? Object.keys(data) : []
const filteredTokens = useFilteredTokens(topTokenAddresses)
const filteredAndSortedTokens = useSortedTokens(filteredTokens, data)
const filteredTokens = useFilteredTokens(data)
const sortedFilteredTokens = useSortedTokens(filteredTokens)
/* loading and error state */
if (loading) {
return <LoadingTokenTable />
} else if (error || data === null) {
if (data === null) {
return (
<NoTokensState
message={
<>
<AlertTriangle size={16} />
An error occured loading tokens. Please try again.
<Trans>An error occured loading tokens. Please try again.</Trans>
</>
}
/>
)
}
if (showFavorites && filteredAndSortedTokens.length === 0) {
return <NoTokensState message="You have no favorited tokens" />
if (showFavorites && sortedFilteredTokens?.length === 0) {
return <NoTokensState message={<Trans>You have no favorited tokens</Trans>} />
}
if (!showFavorites && filteredAndSortedTokens.length === 0) {
return <NoTokensState message="No tokens found" />
if (!showFavorites && sortedFilteredTokens?.length === 0) {
return <NoTokensState message={<Trans>No tokens found</Trans>} />
}
return (
<GridContainer>
<HeaderRow />
<TokenRowsContainer>
{filteredAndSortedTokens.map((tokenAddress, index) => (
<LoadedRow
key={tokenAddress}
tokenAddress={tokenAddress}
tokenListIndex={index}
tokenListLength={filteredAndSortedTokens.length}
data={data}
timePeriod={timePeriod}
/>
))}
</TokenRowsContainer>
</GridContainer>
<Suspense fallback={<LoadingTokenTable />}>
<GridContainer>
<HeaderRow />
<TokenRowsContainer>
{sortedFilteredTokens?.map((token, index) => (
<LoadedRow
key={token.address}
tokenAddress={token.address}
tokenListIndex={index}
tokenListLength={sortedFilteredTokens.length}
tokenData={token}
timePeriod={timePeriod}
/>
))}
</TokenRowsContainer>
</GridContainer>
</Suspense>
)
}

View File

@@ -0,0 +1,80 @@
import { X } from 'react-feather'
import { Link } from 'react-router-dom'
import { useShowTokensPromoBanner } from 'state/user/hooks'
import styled, { useTheme } from 'styled-components/macro'
import { opacify } from 'theme/utils'
import tokensPromoDark from '../../assets/images/tokensPromoDark.png'
import tokensPromoLight from '../../assets/images/tokensPromoLight.png'
const PopupContainer = styled.div<{ show: boolean }>`
position: absolute;
display: ${({ show }) => (show ? 'flex' : 'none')};
flex-direction: column;
padding: 12px 16px 12px 20px;
gap: 8px;
bottom: 48px;
right: 16px;
width: 320px;
height: 88px;
z-index: 5;
background-color: ${({ theme }) => (theme.darkMode ? theme.backgroundScrim : opacify(60, '#FDF0F8'))};
color: ${({ theme }) => theme.textPrimary};
border: 1px solid ${({ theme }) => theme.backgroundOutline};
border-radius: 12px;
box-shadow: ${({ theme }) => theme.deepShadow};
background-image: url(${({ theme }) => (theme.darkMode ? `${tokensPromoDark}` : `${tokensPromoLight}`)});
background-size: cover;
background-blend-mode: overlay;
transition: ${({
theme: {
transition: { duration, timing },
},
}) => `${duration.slow}ms opacity ${timing.in}`};
`
const Header = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
`
const HeaderText = styled(Link)`
font-weight: 600;
font-size: 14px;
line-height: 20px;
text-decoration: none;
color: ${({ theme }) => theme.textPrimary};
`
const Description = styled(Link)`
font-weight: 400;
font-size: 12px;
line-height: 16px;
width: 75%;
text-decoration: none;
color: ${({ theme }) => theme.textPrimary};
`
export default function TokensBanner() {
const theme = useTheme()
const [showTokensPromoBanner, setShowTokensPromoBanner] = useShowTokensPromoBanner()
const closeBanner = () => {
setShowTokensPromoBanner(false)
}
return (
<PopupContainer show={showTokensPromoBanner}>
<Header>
<HeaderText to={'/tokens'} onClick={closeBanner}>
Explore Top Tokens
</HeaderText>
<X size={20} color={theme.textSecondary} onClick={closeBanner} style={{ cursor: 'pointer' }} />
</Header>
<Description to={'/tokens'} onClick={closeBanner}>
Check out the new explore tab to discover and learn more
</Description>
</PopupContainer>
)
}

View File

@@ -1,5 +1,5 @@
import { SupportedChainId } from 'constants/chains'
import { TimePeriod } from 'hooks/useExplorePageQuery'
import { TimePeriod } from 'graphql/data/TopTokenQuery'
import { atom, useAtom } from 'jotai'
import { atomWithReset, atomWithStorage } from 'jotai/utils'
import { useCallback } from 'react'

View File

@@ -1,5 +1,5 @@
export enum Category {
percentChange = '% Change',
percentChange = 'Change',
marketCap = 'Market Cap',
price = 'Price',
volume = 'Volume',

View File

@@ -7,7 +7,7 @@ import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import ms from 'ms.macro'
import { darken } from 'polished'
import { useState } from 'react'
import { useSetUserSlippageTolerance, useUserSlippageTolerance, useUserTransactionTTL } from 'state/user/hooks'
import { useUserSlippageTolerance, useUserTransactionTTL } from 'state/user/hooks'
import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from '../../theme'
@@ -64,6 +64,10 @@ const Input = styled.input<{ redesignFlag: boolean }>`
}
color: ${({ theme, color }) => (color === 'red' ? theme.deprecated_red1 : theme.deprecated_text1)};
text-align: right;
::placeholder {
color: ${({ theme, redesignFlag }) => redesignFlag && theme.textTertiary};
}
`
const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean; redesignFlag: boolean }>`
@@ -91,7 +95,7 @@ const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean;
const SlippageEmojiContainer = styled.span`
color: #f3841e;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
display: none;
`}
`
@@ -108,8 +112,7 @@ export default function TransactionSettings({ placeholderSlippage }: Transaction
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const userSlippageTolerance = useUserSlippageTolerance()
const setUserSlippageTolerance = useSetUserSlippageTolerance()
const [userSlippageTolerance, setUserSlippageTolerance] = useUserSlippageTolerance()
const [deadline, setDeadline] = useUserTransactionTTL()

View File

@@ -81,6 +81,10 @@ const HeaderWrapper = styled.div`
justify-content: space-between;
`
const AuthenticatedHeaderWrapper = styled.div`
padding: 0 16px;
`
const AuthenticatedHeader = () => {
const { account, chainId, connector } = useWeb3React()
const [isCopied, setCopied] = useCopyClipboard()
@@ -115,7 +119,7 @@ const AuthenticatedHeader = () => {
}, [balanceString, nativeCurrencyPrice])
return (
<>
<AuthenticatedHeaderWrapper>
<HeaderWrapper>
<StatusWrapper>
<FlexContainer>
@@ -144,7 +148,7 @@ const AuthenticatedHeader = () => {
</UNIbutton>
)}
</Column>
</>
</AuthenticatedHeaderWrapper>
)
}

View File

@@ -18,10 +18,16 @@ const ConnectButton = styled(ButtonPrimary)`
width: 288px;
font-weight: 600;
font-size: 16px;
margin-left: auto;
margin-right: auto;
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
width: 100%;
}
`
const Divider = styled.div`
border: 1px solid ${({ theme }) => theme.backgroundOutline};
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
margin-top: 16px;
margin-bottom: 16px;
`
@@ -33,18 +39,23 @@ const ToggleMenuItem = styled.button`
cursor: pointer;
display: flex;
flex: 1;
border-radius: 12px;
flex-direction: row;
align-items: center;
padding: 8px 0px;
justify-content: space-between;
font-size: 14px;
font-weight: 400;
width: 100%;
margin-bottom: 8px;
padding: 12px 8px;
color: ${({ theme }) => theme.textSecondary};
:hover {
color: ${({ theme }) => theme.textPrimary};
transition: 250ms color ease;
background-color: ${({ theme }) => theme.backgroundModule};
transition: ${({
theme: {
transition: { duration, timing },
},
}) => `${duration.fast}ms all ${timing.in}`};
}
`
@@ -69,9 +80,9 @@ const IconWrap = styled.span`
`
const DefaultMenuWrap = styled.div`
padding: 0 16px;
width: 100%;
height: 100%;
padding: 0 8px;
`
const DefaultText = styled.span`

View File

@@ -24,7 +24,11 @@ const IconStyles = css`
color: ${({ theme }) => theme.textPrimary};
:hover {
background-color: ${({ theme }) => theme.hoverState};
transition: background-color 200ms linear;
transition: ${({
theme: {
transition: { duration, timing },
},
}) => `${duration.fast}ms background-color ${timing.in}`};
${IconHoverText} {
opacity: 1;

View File

@@ -18,24 +18,25 @@ const InternalMenuItem = styled(Link)`
}
`
const InternalLinkMenuItem = styled(InternalMenuItem)<{ isActive: boolean }>`
const InternalLinkMenuItem = styled(InternalMenuItem)`
display: flex;
flex-direction: row;
align-items: center;
padding: 12px 16px;
justify-content: space-between;
text-decoration: none;
background-color: ${({ isActive, theme }) => isActive && theme.accentActionSoft};
color: ${({ theme }) => theme.textPrimary};
:hover {
cursor: pointer;
background-color: ${({ theme }) => theme.backgroundModule};
transition: ${({
theme: {
transition: { duration, timing },
},
}) => `${duration.fast}ms background-color ${timing.in}`};
}
`
const LanguageWrap = styled.div`
margin-top: 16px;
`
function LanguageMenuItem({ locale, isActive }: { locale: SupportedLocale; isActive: boolean }) {
const { to, onClick } = useLocationLinkProps(locale)
const theme = useTheme()
@@ -43,11 +44,11 @@ function LanguageMenuItem({ locale, isActive }: { locale: SupportedLocale; isAct
if (!to) return null
return (
<InternalLinkMenuItem isActive={isActive} onClick={onClick} to={to}>
<InternalLinkMenuItem onClick={onClick} to={to}>
<Text fontSize={16} fontWeight={400} lineHeight="24px">
{LOCALE_LABEL[locale]}
</Text>
{isActive && <Check color={theme.accentAction} opacity={1} size={20} />}
{isActive && <Check color={theme.accentActive} opacity={1} size={20} />}
</InternalLinkMenuItem>
)
}
@@ -57,11 +58,9 @@ const LanguageMenu = ({ onClose }: { onClose: () => void }) => {
return (
<SlideOutMenu title={<Trans>Language</Trans>} onClose={onClose}>
<LanguageWrap>
{SUPPORTED_LOCALES.map((locale) => (
<LanguageMenuItem locale={locale} isActive={activeLocale === locale} key={locale} />
))}
</LanguageWrap>
{SUPPORTED_LOCALES.map((locale) => (
<LanguageMenuItem locale={locale} isActive={activeLocale === locale} key={locale} />
))}
</SlideOutMenu>
)
}

View File

@@ -1,22 +1,28 @@
import { ChevronLeft } from 'react-feather'
import styled from 'styled-components/macro'
const BackSection = styled.div`
position: relative;
display: flex;
padding: 0 16px;
color: ${({ theme }) => theme.textSecondary};
cursor: default;
:hover {
text-decoration: none;
}
`
const Menu = styled.div`
width: 100%;
height: 100%;
font-size: 16px;
overflow-y: scroll;
overflow: auto;
// Firefox scrollbar styling
scrollbar-width: thin;
scrollbar-color: ${({ theme }) => `${theme.backgroundOutline} transparent`};
// safari and chrome scrollbar styling
::-webkit-scrollbar {
background: transparent;
width: 4px;
}
::-webkit-scrollbar-track {
margin-top: 40px;
}
::-webkit-scrollbar-thumb {
background: ${({ theme }) => theme.backgroundOutline};
border-radius: 8px;
}
`
const Header = styled.span`
@@ -29,12 +35,20 @@ const Header = styled.span`
const ClearAll = styled.div`
display: inline-block;
cursor: pointer;
margin-left: auto;
color: ${({ theme }) => theme.accentAction};
font-weight: 600;
font-size: 14px;
margin-top: auto;
margin-bottom: auto;
:hover {
opacity: 0.6;
transition: ${({
theme: {
transition: { duration, timing },
},
}) => `${duration.fast}ms opacity ${timing.in}`};
}
`
const StyledChevron = styled(ChevronLeft)`
@@ -42,10 +56,37 @@ const StyledChevron = styled(ChevronLeft)`
&:hover {
color: ${({ theme }) => theme.textPrimary};
transition: 250ms color ease;
transition: ${({
theme: {
transition: { duration, timing },
},
}) => `${duration.fast}ms color ${timing.in}`};
}
`
const BackSection = styled.div`
position: absolute;
background-color: ${({ theme }) => theme.backgroundSurface};
width: 99%;
padding: 0 16px 16px 16px;
color: ${({ theme }) => theme.textSecondary};
cursor: default;
display: flex;
justify-content: space-between;
z-index: 1;
`
const BackSectionContainer = styled.div`
display: flex;
justify-content: space-between;
position: relative;
width: 100%;
`
const ChildrenContainer = styled.div`
margin-top: 40px;
`
export const SlideOutMenu = ({
children,
onClose,
@@ -59,10 +100,13 @@ export const SlideOutMenu = ({
}) => (
<Menu>
<BackSection>
<StyledChevron onClick={onClose} size={24} />
<Header>{title}</Header>
{onClear && <ClearAll onClick={onClear}>Clear All</ClearAll>}
<BackSectionContainer>
<StyledChevron onClick={onClose} size={24} />
<Header>{title}</Header>
{onClear && <ClearAll onClick={onClear}>Clear All</ClearAll>}
</BackSectionContainer>
</BackSection>
{children}
<ChildrenContainer>{children}</ChildrenContainer>
</Menu>
)

View File

@@ -1,6 +1,9 @@
import { useState } from 'react'
import styled from 'styled-components/macro'
import { Z_INDEX } from 'theme'
import { useModalIsOpen } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer'
import DefaultMenu from './DefaultMenu'
import LanguageMenu from './LanguageMenu'
import { TransactionHistoryMenu } from './TransactionMenu'
@@ -9,14 +12,22 @@ const WalletWrapper = styled.div`
border-radius: 12px;
width: 320px;
max-height: 376px;
display: flex;
flex-direction: column;
font-size: 16px;
top: 60px;
right: 70px;
background-color: ${({ theme }) => theme.backgroundSurface};
border: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
box-shadow: ${({ theme }) => theme.deepShadow};
padding: 16px 0;
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
width: 100%;
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
box-shadow: unset;
}
`
export enum MenuState {
@@ -25,15 +36,36 @@ export enum MenuState {
TRANSACTIONS = 'TRANSACTIONS',
}
const WalletDropdownWrapper = styled.div`
position: absolute;
top: 65px;
right: 20px;
z-index: ${Z_INDEX.dropdown};
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
top: unset;
left: 0;
right: 0;
bottom: 56px;
}
`
const WalletDropdown = () => {
const [menu, setMenu] = useState<MenuState>(MenuState.DEFAULT)
const walletDropdownOpen = useModalIsOpen(ApplicationModal.WALLET_DROPDOWN)
return (
<WalletWrapper>
{menu === MenuState.TRANSACTIONS && <TransactionHistoryMenu onClose={() => setMenu(MenuState.DEFAULT)} />}
{menu === MenuState.LANGUAGE && <LanguageMenu onClose={() => setMenu(MenuState.DEFAULT)} />}
{menu === MenuState.DEFAULT && <DefaultMenu setMenu={setMenu} />}
</WalletWrapper>
<>
{walletDropdownOpen && (
<WalletDropdownWrapper>
<WalletWrapper>
{menu === MenuState.TRANSACTIONS && <TransactionHistoryMenu onClose={() => setMenu(MenuState.DEFAULT)} />}
{menu === MenuState.LANGUAGE && <LanguageMenu onClose={() => setMenu(MenuState.DEFAULT)} />}
{menu === MenuState.DEFAULT && <DefaultMenu setMenu={setMenu} />}
</WalletWrapper>
</WalletDropdownWrapper>
)}
</>
)
}

View File

@@ -30,7 +30,7 @@ const CheckIcon = styled(Check)`
align-items: center;
justify-content: center;
color: ${({ theme }) => theme.accentAction};
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
align-items: flex-end;
`};
`
@@ -112,7 +112,7 @@ const IconWrapperDeprecated = styled.div<{ size?: number | null }>`
height: ${({ size }) => (size ? size + 'px' : '24px')};
width: ${({ size }) => (size ? size + 'px' : '24px')};
}
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
align-items: flex-end;
`};
`
@@ -127,7 +127,7 @@ const IconWrapper = styled.div<{ size?: number | null }>`
height: ${({ size }) => (size ? size + 'px' : '28px')};
width: ${({ size }) => (size ? size + 'px' : '28px')};
}
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
align-items: flex-end;
`};
`

View File

@@ -1,23 +1,13 @@
import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { Connector } from '@web3-react/types'
import { sendAnalyticsEvent, user } from 'components/AmplitudeAnalytics'
import {
CUSTOM_USER_PROPERTIES,
EventName,
TOKENS_TO_TRACK,
WALLET_CONNECTION_RESULT,
} from 'components/AmplitudeAnalytics/constants'
import { formatToDecimal } from 'components/AmplitudeAnalytics/utils'
import { CUSTOM_USER_PROPERTIES, EventName, WALLET_CONNECTION_RESULT } from 'components/AmplitudeAnalytics/constants'
import { sendEvent } from 'components/analytics'
import { AutoColumn } from 'components/Column'
import { AutoRow } from 'components/Row'
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsInjected, getIsMetaMask } from 'connection/utils'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useCurrencyBalance, { useTokenBalance } from 'lib/hooks/useCurrencyBalance'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useCallback, useEffect, useState } from 'react'
import { ArrowLeft } from 'react-feather'
import { updateConnectionError } from 'state/connection/reducer'
@@ -71,7 +61,7 @@ const HeaderRow = styled.div<{ redesignFlag?: boolean }>`
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
size: ${({ redesignFlag }) => redesignFlag && '16px'};
color: ${(props) => (props.color === 'blue' ? ({ theme }) => theme.deprecated_primary1 : 'inherit')};
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
padding: 1rem;
`};
`
@@ -82,7 +72,7 @@ const ContentWrapper = styled.div<{ redesignFlag?: boolean }>`
padding: 0 1rem 1rem 1rem;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0 1rem 1rem 1rem`};
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`padding: 0 1rem 1rem 1rem`};
`
const UpperSection = styled.div`
@@ -105,7 +95,7 @@ const UpperSection = styled.div`
const OptionGrid = styled.div`
display: grid;
grid-gap: 10px;
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
grid-template-columns: 1fr;
grid-gap: 10px;
`};
@@ -148,32 +138,6 @@ const sendAnalyticsEventAndUserInfo = (
user.postInsert(CUSTOM_USER_PROPERTIES.ALL_WALLET_ADDRESSES_CONNECTED, account)
}
function useLogToken(
tokenBalanceUsdValue: string | undefined,
tokenBalance: CurrencyAmount<Token | Currency> | undefined,
shouldLogTokenBalance: boolean,
setShouldLogTokenBalance: (shouldLog: boolean) => void,
tokenAmountProperty: string,
tokenUsdBalanceProperty: string
) {
useEffect(() => {
if (shouldLogTokenBalance && tokenBalance && tokenBalanceUsdValue) {
const tokenBalanceUsd = tokenBalanceUsdValue ? parseFloat(tokenBalanceUsdValue) : 0
const tokenBalanceAmount = formatToDecimal(tokenBalance, tokenBalance.currency.decimals)
user.set(tokenAmountProperty, tokenBalanceAmount)
user.set(tokenUsdBalanceProperty, tokenBalanceUsd)
setShouldLogTokenBalance(false)
}
}, [
tokenBalanceUsdValue,
tokenBalance,
shouldLogTokenBalance,
setShouldLogTokenBalance,
tokenAmountProperty,
tokenUsdBalanceProperty,
])
}
export default function WalletModal({
pendingTransactions,
confirmedTransactions,
@@ -191,9 +155,6 @@ export default function WalletModal({
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT)
const [lastActiveWalletAddress, setLastActiveWalletAddress] = useState<string | undefined>(account)
const [shouldLogUsdcBalance, setShouldLogUsdcBalance] = useState(false)
const [shouldLogWethBalance, setShouldLogWethBalance] = useState(false)
const [shouldLogNativeBalance, setShouldLogNativeBalance] = useState(false)
const [pendingConnector, setPendingConnector] = useState<Connector | undefined>()
const pendingError = useAppSelector((state) =>
@@ -203,15 +164,6 @@ export default function WalletModal({
const walletModalOpen = useModalIsOpen(ApplicationModal.WALLET)
const toggleWalletModal = useToggleWalletModal()
const native = useNativeCurrency()
const usdcBalance = useTokenBalance(account, TOKENS_TO_TRACK.USDC)
const wethBalance = useTokenBalance(account, TOKENS_TO_TRACK.WETH)
const nativeCurrencyBalance = useCurrencyBalance(account, native)
const usdcBalanceUsdValue = useStablecoinValue(usdcBalance)?.toFixed(2)
const wethBalanceUsdValue = useStablecoinValue(wethBalance)?.toFixed(2)
const nativeCurrencyBalanceUsdValue = useStablecoinValue(nativeCurrencyBalance)?.toFixed(2)
const openOptions = useCallback(() => {
setWalletView(WALLET_VIEWS.OPTIONS)
}, [setWalletView])
@@ -236,40 +188,11 @@ export default function WalletModal({
const isReconnect =
connectedWallets.filter((wallet) => wallet.account === account && wallet.walletType === walletType).length > 0
sendAnalyticsEventAndUserInfo(account, walletType, chainId, isReconnect)
setShouldLogNativeBalance(true)
setShouldLogUsdcBalance(true)
setShouldLogWethBalance(true)
if (!isReconnect) addWalletToConnectedWallets({ account, walletType })
}
setLastActiveWalletAddress(account)
}, [connectedWallets, addWalletToConnectedWallets, lastActiveWalletAddress, account, connector, chainId])
// Send wallet balances info once it becomes available.
useLogToken(
nativeCurrencyBalanceUsdValue,
nativeCurrencyBalance,
shouldLogNativeBalance,
setShouldLogNativeBalance,
CUSTOM_USER_PROPERTIES.WALLET_NATIVE_CURRENCY_AMOUNT,
CUSTOM_USER_PROPERTIES.WALLET_NATIVE_CURRENCY_BALANCE_USD
)
useLogToken(
usdcBalanceUsdValue,
usdcBalance,
shouldLogUsdcBalance,
setShouldLogUsdcBalance,
CUSTOM_USER_PROPERTIES.WALLET_USDC_AMOUNT,
CUSTOM_USER_PROPERTIES.WALLET_USDC_BALANCE_USD
)
useLogToken(
wethBalanceUsdValue,
wethBalance,
shouldLogWethBalance,
setShouldLogWethBalance,
CUSTOM_USER_PROPERTIES.WALLET_WETH_AMOUNT,
CUSTOM_USER_PROPERTIES.WALLET_WETH_BALANCE_USD
)
const tryActivation = useCallback(
async (connector: Connector) => {
const connectionType = getConnection(connector).type

View File

@@ -3,17 +3,28 @@ import { t, Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import { StyledChevronDown, StyledChevronUp } from 'components/Icons'
import WalletDropdown from 'components/WalletDropdown'
import { getConnection } from 'connection/utils'
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
import { Portal } from 'nft/components/common/Portal'
import { getIsValidSwapQuote } from 'pages/Swap'
import { darken } from 'polished'
import { useMemo } from 'react'
import { useMemo, useRef } from 'react'
import { AlertTriangle } from 'react-feather'
import { useAppSelector } from 'state/hooks'
import { useDerivedSwapInfo } from 'state/swap/hooks'
import styled, { css } from 'styled-components/macro'
import styled, { css, useTheme } from 'styled-components/macro'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import { useHasSocks } from '../../hooks/useSocksBalance'
import { useToggleWalletModal } from '../../state/application/hooks'
import {
useCloseModal,
useModalIsOpen,
useToggleWalletDropdown,
useToggleWalletModal,
} from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer'
import { isTransactionRecent, useAllTransactions } from '../../state/transactions/hooks'
import { TransactionDetails } from '../../state/transactions/types'
import { shortenAddress } from '../../utils'
@@ -49,10 +60,26 @@ const Web3StatusError = styled(Web3StatusGeneric)`
}
`
const Web3StatusConnectNavbar = styled.button<{ faded?: boolean }>`
dispay: flex;
align-items: center;
${({ theme }) => theme.flexRowNoWrap}
background-color: ${({ theme }) => theme.accentActionSoft};
border-radius: 12px;
border: none;
cursor: pointer;
padding: 8px 12px;
:hover,
:active,
:focus {
border: none;
}
`
const Web3StatusConnect = styled(Web3StatusGeneric)<{ faded?: boolean }>`
background-color: ${({ theme }) => theme.deprecated_primary4};
border: none;
color: ${({ theme }) => theme.deprecated_primaryText1};
font-weight: 500;
@@ -125,6 +152,29 @@ function Sock() {
)
}
const VerticalDivider = styled.div`
height: 20px;
margin: 0px 4px;
width: 1px;
background-color: ${({ theme }) => theme.accentAction};
`
const StyledConnect = styled.div`
color: ${({ theme }) => theme.accentAction};
font-weight: 600;
font-size: 16px;
margin-right: 8px;
&:hover {
color: ${({ theme }) => theme.accentActionSoft};
transition: ${({
theme: {
transition: { duration, timing },
},
}) => `${duration.fast}ms color ${timing.in}`};
}
`
function Web3StatusInner() {
const { account, connector, chainId, ENSName } = useWeb3React()
const connectionType = getConnection(connector).type
@@ -133,6 +183,11 @@ function Web3StatusInner() {
inputError: swapInputError,
} = useDerivedSwapInfo()
const validSwapQuote = getIsValidSwapQuote(trade, tradeState, swapInputError)
const navbarFlagEnabled = useNavBarFlag() === NavBarVariant.Enabled
const theme = useTheme()
const toggleWalletDropdown = useToggleWalletDropdown()
const toggleWalletModal = useToggleWalletModal()
const walletIsOpen = useIsOpen()
const error = useAppSelector((state) => state.connection.errorByConnectionType[getConnection(connector).type])
@@ -147,13 +202,13 @@ function Web3StatusInner() {
const hasPendingTransactions = !!pending.length
const hasSocks = useHasSocks()
const toggleWalletModal = useToggleWalletModal()
const toggleWallet = navbarFlagEnabled ? toggleWalletDropdown : toggleWalletModal
if (!chainId) {
return null
} else if (error) {
return (
<Web3StatusError onClick={toggleWalletModal}>
<Web3StatusError onClick={toggleWallet}>
<NetworkIcon />
<Text>
<Trans>Error</Trans>
@@ -162,11 +217,8 @@ function Web3StatusInner() {
)
} else if (account) {
return (
<Web3StatusConnected
data-testid="web3-status-connected"
onClick={toggleWalletModal}
pending={hasPendingTransactions}
>
<Web3StatusConnected data-testid="web3-status-connected" onClick={toggleWallet} pending={hasPendingTransactions}>
{navbarFlagEnabled && !hasPendingTransactions && <StatusIcon connectionType={connectionType} />}
{hasPendingTransactions ? (
<RowBetween>
<Text>
@@ -176,11 +228,18 @@ function Web3StatusInner() {
</RowBetween>
) : (
<>
{hasSocks ? <Sock /> : null}
{hasSocks && !navbarFlagEnabled ? <Sock /> : null}
<Text>{ENSName || shortenAddress(account)}</Text>
{navbarFlagEnabled ? (
walletIsOpen ? (
<StyledChevronUp onClick={toggleWalletDropdown} />
) : (
<StyledChevronDown onClick={toggleWalletDropdown} />
)
) : null}
</>
)}
{!hasPendingTransactions && <StatusIcon connectionType={connectionType} />}
{!navbarFlagEnabled && !hasPendingTransactions && <StatusIcon connectionType={connectionType} />}
</Web3StatusConnected>
)
} else {
@@ -191,20 +250,47 @@ function Web3StatusInner() {
properties={{ received_swap_quote: validSwapQuote }}
element={ElementName.CONNECT_WALLET_BUTTON}
>
<Web3StatusConnect onClick={toggleWalletModal} faded={!account}>
<Text>
<Trans>Connect Wallet</Trans>
</Text>
</Web3StatusConnect>
{navbarFlagEnabled ? (
<Web3StatusConnectNavbar faded={!account}>
<StyledConnect onClick={toggleWalletModal}>
<Trans>Connect</Trans>
</StyledConnect>
<VerticalDivider />
{walletIsOpen ? (
<StyledChevronUp customColor={theme.accentAction} onClick={toggleWalletDropdown} />
) : (
<StyledChevronDown customColor={theme.accentAction} onClick={toggleWalletDropdown} />
)}
</Web3StatusConnectNavbar>
) : (
<Web3StatusConnect onClick={toggleWallet} faded={!account}>
<Text>
<Trans>Connect Wallet</Trans>
</Text>
</Web3StatusConnect>
)}
</TraceEvent>
)
}
}
const useIsOpen = () => {
const walletDropdownOpen = useModalIsOpen(ApplicationModal.WALLET_DROPDOWN)
const navbarFlag = useNavBarFlag()
return useMemo(() => navbarFlag === NavBarVariant.Enabled && walletDropdownOpen, [navbarFlag, walletDropdownOpen])
}
export default function Web3Status() {
const { ENSName } = useWeb3React()
const allTransactions = useAllTransactions()
const ref = useRef<HTMLDivElement>(null)
const walletRef = useRef<HTMLDivElement>(null)
const closeModal = useCloseModal(ApplicationModal.WALLET_DROPDOWN)
const isOpen = useIsOpen()
useOnClickOutside(ref, isOpen ? closeModal : undefined, [walletRef])
const sortedRecentTransactions = useMemo(() => {
const txs = Object.values(allTransactions)
@@ -215,9 +301,14 @@ export default function Web3Status() {
const confirmed = sortedRecentTransactions.filter((tx) => tx.receipt).map((tx) => tx.hash)
return (
<>
<span ref={ref}>
<Web3StatusInner />
<WalletModal ENSName={ENSName ?? undefined} pendingTransactions={pending} confirmedTransactions={confirmed} />
</>
<Portal>
<span ref={walletRef}>
<WalletDropdown />
</span>
</Portal>
</span>
)
}

View File

@@ -0,0 +1,50 @@
import { Currency, SwapWidget } from '@uniswap/widgets'
import { useWeb3React } from '@web3-react/core'
import { RPC_URLS } from 'constants/networks'
import { useActiveLocale } from 'hooks/useActiveLocale'
import { useMemo } from 'react'
import { useIsDarkMode } from 'state/user/hooks'
import { DARK_THEME, LIGHT_THEME } from 'theme/widget'
import { useSyncWidgetInputs } from './inputs'
import { useSyncWidgetSettings } from './settings'
import { useSyncWidgetTransactions } from './transactions'
export const WIDGET_WIDTH = 320
const WIDGET_ROUTER_URL = 'https://api.uniswap.org/v1/'
export interface WidgetProps {
defaultToken?: Currency
}
export default function Widget({ defaultToken }: WidgetProps) {
const locale = useActiveLocale()
const darkMode = useIsDarkMode()
const theme = useMemo(() => (darkMode ? DARK_THEME : LIGHT_THEME), [darkMode])
const { provider } = useWeb3React()
const { inputs, tokenSelector } = useSyncWidgetInputs(defaultToken)
const { settings } = useSyncWidgetSettings()
const { transactions } = useSyncWidgetTransactions()
return (
<>
<SwapWidget
disableBranding
hideConnectionUI
jsonRpcUrlMap={RPC_URLS}
routerUrl={WIDGET_ROUTER_URL}
width={WIDGET_WIDTH}
locale={locale}
theme={theme}
// defaultChainId is excluded - it is always inferred from the passed provider
provider={provider}
{...inputs}
{...settings}
{...transactions}
/>
{tokenSelector}
</>
)
}

View File

@@ -0,0 +1,88 @@
import { Currency, Field, SwapController, SwapEventHandlers, TradeType } from '@uniswap/widgets'
import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal'
import { useCallback, useMemo, useState } from 'react'
/**
* Integrates the Widget's inputs.
* Treats the Widget as a controlled component, using the app's own token selector for selection.
*/
export function useSyncWidgetInputs(defaultToken?: Currency) {
const [type, setType] = useState(TradeType.EXACT_INPUT)
const [amount, setAmount] = useState('')
const onAmountChange = useCallback((field: Field, amount: string) => {
setType(toTradeType(field))
setAmount(amount)
}, [])
const [tokens, setTokens] = useState<{ [Field.INPUT]?: Currency; [Field.OUTPUT]?: Currency }>({
[Field.OUTPUT]: defaultToken,
})
const onSwitchTokens = useCallback(() => {
setType((type) => invertTradeType(type))
setTokens((tokens) => ({
[Field.INPUT]: tokens[Field.OUTPUT],
[Field.OUTPUT]: tokens[Field.INPUT],
}))
}, [])
const [selectingField, setSelectingField] = useState<Field>()
const otherField = useMemo(() => (selectingField === Field.INPUT ? Field.OUTPUT : Field.INPUT), [selectingField])
const [selectingToken, otherToken] = useMemo(() => {
if (selectingField === undefined) return [undefined, undefined]
return [tokens[selectingField], tokens[otherField]]
}, [otherField, selectingField, tokens])
const onTokenSelectorClick = useCallback((field: Field) => {
setSelectingField(field)
return false
}, [])
const onTokenSelect = useCallback(
(token: Currency) => {
if (selectingField === undefined) return
setType(TradeType.EXACT_INPUT)
setTokens(() => {
return {
[otherField]: token === otherToken ? selectingToken : otherToken,
[selectingField]: token,
}
})
},
[otherField, otherToken, selectingField, selectingToken]
)
const tokenSelector = (
<CurrencySearchModal
isOpen={selectingField !== undefined}
onDismiss={() => setSelectingField(undefined)}
selectedCurrency={selectingToken}
otherSelectedCurrency={otherToken}
onCurrencySelect={onTokenSelect}
/>
)
const value: SwapController = useMemo(() => ({ type, amount, ...tokens }), [amount, tokens, type])
const valueHandlers: SwapEventHandlers = useMemo(
() => ({ onAmountChange, onSwitchTokens, onTokenSelectorClick }),
[onAmountChange, onSwitchTokens, onTokenSelectorClick]
)
return { inputs: { value, ...valueHandlers }, tokenSelector }
}
// TODO(zzmp): Move to @uniswap/widgets.
function toTradeType(modifiedField: Field) {
switch (modifiedField) {
case Field.INPUT:
return TradeType.EXACT_INPUT
case Field.OUTPUT:
return TradeType.EXACT_OUTPUT
}
}
// TODO(zzmp): Include in @uniswap/sdk-core (on TradeType, if possible).
function invertTradeType(tradeType: TradeType) {
switch (tradeType) {
case TradeType.EXACT_INPUT:
return TradeType.EXACT_OUTPUT
case TradeType.EXACT_OUTPUT:
return TradeType.EXACT_INPUT
}
}

View File

@@ -0,0 +1,57 @@
import { Percent } from '@uniswap/sdk-core'
import { Slippage, SwapEventHandlers, SwapSettingsController } from '@uniswap/widgets'
import { DEFAULT_DEADLINE_FROM_NOW } from 'constants/misc'
import { useCallback, useMemo, useState } from 'react'
import { useUserSlippageTolerance, useUserTransactionTTL } from 'state/user/hooks'
/**
* Integrates the Widget's settings, keeping the widget and app settings in sync.
* NB: This acts as an integration layer, so certain values are duplicated in order to translate
* between app and widget representations.
*/
export function useSyncWidgetSettings() {
const [appTtl, setAppTtl] = useUserTransactionTTL()
const [widgetTtl, setWidgetTtl] = useState<number | undefined>(appTtl / 60)
const onTransactionDeadlineChange = useCallback(
(widgetTtl: number | undefined) => {
setWidgetTtl(widgetTtl)
const appTtl = widgetTtl === undefined ? widgetTtl : widgetTtl * 60
setAppTtl(appTtl ?? DEFAULT_DEADLINE_FROM_NOW)
},
[setAppTtl]
)
const [appSlippage, setAppSlippage] = useUserSlippageTolerance()
const [widgetSlippage, setWidgetSlippage] = useState<string | undefined>(
appSlippage === 'auto' ? undefined : appSlippage.toFixed(2)
)
const onSlippageChange = useCallback(
(widgetSlippage: Slippage) => {
setWidgetSlippage(widgetSlippage.max)
if (widgetSlippage.auto || !widgetSlippage.max) {
setAppSlippage('auto')
} else {
setAppSlippage(new Percent(Math.floor(Number(widgetSlippage.max) * 100), 10_000))
}
},
[setAppSlippage]
)
const onSettingsReset = useCallback(() => {
setWidgetTtl(undefined)
setAppTtl(DEFAULT_DEADLINE_FROM_NOW)
setWidgetSlippage(undefined)
setAppSlippage('auto')
}, [setAppSlippage, setAppTtl])
const settings: SwapSettingsController = useMemo(() => {
const auto = appSlippage === 'auto'
return { slippage: { auto, max: widgetSlippage }, transactionTtl: widgetTtl }
}, [widgetSlippage, widgetTtl, appSlippage])
const settingsHandlers: SwapEventHandlers = useMemo(
() => ({ onSettingsReset, onSlippageChange, onTransactionDeadlineChange }),
[onSettingsReset, onSlippageChange, onTransactionDeadlineChange]
)
return { settings: { settings, ...settingsHandlers } }
}

View File

@@ -0,0 +1,70 @@
import {
TradeType,
Transaction,
TransactionEventHandlers,
TransactionInfo,
TransactionType as WidgetTransactionType,
} from '@uniswap/widgets'
import { useWeb3React } from '@web3-react/core'
import { useCallback, useMemo } from 'react'
import { useTransactionAdder } from 'state/transactions/hooks'
import {
ExactInputSwapTransactionInfo,
ExactOutputSwapTransactionInfo,
TransactionType as AppTransactionType,
WrapTransactionInfo,
} from 'state/transactions/types'
import { currencyId } from 'utils/currencyId'
/** Integrates the Widget's transactions, showing the widget's transactions in the app. */
export function useSyncWidgetTransactions() {
const { chainId } = useWeb3React()
const addTransaction = useTransactionAdder()
const onTxSubmit = useCallback(
(_hash: string, transaction: Transaction<TransactionInfo>) => {
const { type, response } = transaction.info
if (!type || !response) {
return
} else if (type === WidgetTransactionType.WRAP || type === WidgetTransactionType.UNWRAP) {
const { amount } = transaction.info
addTransaction(response, {
type: AppTransactionType.WRAP,
unwrapped: type === WidgetTransactionType.UNWRAP,
currencyAmountRaw: amount.quotient.toString(),
chainId,
} as WrapTransactionInfo)
} else if (type === WidgetTransactionType.SWAP) {
const { slippageTolerance, trade, tradeType } = transaction.info
const baseTxInfo = {
type: AppTransactionType.SWAP,
tradeType,
inputCurrencyId: currencyId(trade.inputAmount.currency),
outputCurrencyId: currencyId(trade.outputAmount.currency),
}
if (tradeType === TradeType.EXACT_OUTPUT) {
addTransaction(response, {
...baseTxInfo,
maximumInputCurrencyAmountRaw: trade.maximumAmountIn(slippageTolerance).quotient.toString(),
outputCurrencyAmountRaw: trade.outputAmount.quotient.toString(),
expectedInputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
} as ExactOutputSwapTransactionInfo)
} else {
addTransaction(response, {
...baseTxInfo,
inputCurrencyAmountRaw: trade.inputAmount.quotient.toString(),
expectedOutputCurrencyAmountRaw: trade.outputAmount.quotient.toString(),
minimumOutputCurrencyAmountRaw: trade.minimumAmountOut(slippageTolerance).quotient.toString(),
} as ExactInputSwapTransactionInfo)
}
}
},
[addTransaction, chainId]
)
const txHandlers: TransactionEventHandlers = useMemo(() => ({ onTxSubmit }), [onTxSubmit])
return { transactions: { ...txHandlers } }
}

View File

@@ -26,7 +26,7 @@ const StatContainer = styled.div`
margin-bottom: 1rem;
margin-right: 1rem;
margin-left: 1rem;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
display: none;
`};
`
@@ -57,7 +57,7 @@ const TopSection = styled.div`
align-items: center;
padding: 1rem;
z-index: 1;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
grid-template-columns: 48px 1fr 96px;
`};
`

View File

@@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro'
import { Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
import { ModalName } from 'components/AmplitudeAnalytics/constants'
import { Trace } from 'components/AmplitudeAnalytics/Trace'
import { ReactNode, useCallback, useMemo, useState } from 'react'
@@ -27,6 +27,8 @@ export default function ConfirmSwapModal({
attemptingTxn,
txHash,
swapQuoteReceivedDate,
fiatValueInput,
fiatValueOutput,
}: {
isOpen: boolean
trade: InterfaceTrade<Currency, Currency, TradeType> | undefined
@@ -40,6 +42,8 @@ export default function ConfirmSwapModal({
swapErrorMessage: ReactNode | undefined
onDismiss: () => void
swapQuoteReceivedDate: Date | undefined
fiatValueInput?: CurrencyAmount<Token> | null
fiatValueOutput?: CurrencyAmount<Token> | null
}) {
// shouldLogModalCloseEvent lets the child SwapModalHeader component know when modal has been closed
// and an event triggered by modal closing should be logged.
@@ -73,14 +77,26 @@ export default function ConfirmSwapModal({
<SwapModalFooter
onConfirm={onConfirm}
trade={trade}
txHash={txHash}
hash={txHash}
allowedSlippage={allowedSlippage}
disabledConfirm={showAcceptChanges}
swapErrorMessage={swapErrorMessage}
swapQuoteReceivedDate={swapQuoteReceivedDate}
fiatValueInput={fiatValueInput}
fiatValueOutput={fiatValueOutput}
/>
) : null
}, [onConfirm, showAcceptChanges, swapErrorMessage, trade, allowedSlippage, txHash, swapQuoteReceivedDate])
}, [
onConfirm,
showAcceptChanges,
swapErrorMessage,
trade,
allowedSlippage,
txHash,
swapQuoteReceivedDate,
fiatValueInput,
fiatValueOutput,
])
// text to show while loading
const pendingText = (

View File

@@ -67,7 +67,7 @@ const StyledPolling = styled.div`
color: ${({ theme }) => theme.deprecated_text1};
transition: 250ms ease color;
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
display: none;
`}
`

View File

@@ -1,5 +1,5 @@
import { Trans } from '@lingui/macro'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
import {
@@ -10,7 +10,6 @@ import {
getDurationUntilTimestampSeconds,
getTokenAddress,
} from 'components/AmplitudeAnalytics/utils'
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useTransactionDeadline from 'hooks/useTransactionDeadline'
import { ReactNode } from 'react'
import { Text } from 'rebass'
@@ -25,15 +24,15 @@ import { getTokenPath, RoutingDiagramEntry } from './SwapRoute'
interface AnalyticsEventProps {
trade: InterfaceTrade<Currency, Currency, TradeType>
txHash: string | undefined
hash: string | undefined
allowedSlippage: Percent
transactionDeadlineSecondsSinceEpoch: number | undefined
isAutoSlippage: boolean
isAutoRouterApi: boolean
tokenInAmountUsd: string | undefined
tokenOutAmountUsd: string | undefined
swapQuoteReceivedDate: Date | undefined
routes: RoutingDiagramEntry[]
fiatValueInput?: CurrencyAmount<Token> | null
fiatValueOutput?: CurrencyAmount<Token> | null
}
const formatRoutesEventProperties = (routes: RoutingDiagramEntry[]) => {
@@ -65,27 +64,27 @@ const formatRoutesEventProperties = (routes: RoutingDiagramEntry[]) => {
const formatAnalyticsEventProperties = ({
trade,
txHash,
hash,
allowedSlippage,
transactionDeadlineSecondsSinceEpoch,
isAutoSlippage,
isAutoRouterApi,
tokenInAmountUsd,
tokenOutAmountUsd,
swapQuoteReceivedDate,
routes,
fiatValueInput,
fiatValueOutput,
}: AnalyticsEventProps) => ({
estimated_network_fee_usd: trade.gasUseEstimateUSD ? formatToDecimal(trade.gasUseEstimateUSD, 2) : undefined,
transaction_hash: txHash,
transaction_hash: hash,
transaction_deadline_seconds: getDurationUntilTimestampSeconds(transactionDeadlineSecondsSinceEpoch),
token_in_amount_usd: tokenInAmountUsd ? parseFloat(tokenInAmountUsd) : undefined,
token_out_amount_usd: tokenOutAmountUsd ? parseFloat(tokenOutAmountUsd) : undefined,
token_in_address: getTokenAddress(trade.inputAmount.currency),
token_out_address: getTokenAddress(trade.outputAmount.currency),
token_in_symbol: trade.inputAmount.currency.symbol,
token_out_symbol: trade.outputAmount.currency.symbol,
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
token_in_amount_usd: fiatValueInput ? parseFloat(fiatValueInput.toFixed(2)) : undefined,
token_out_amount_usd: fiatValueOutput ? parseFloat(fiatValueOutput.toFixed(2)) : undefined,
price_impact_basis_points: formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)),
allowed_slippage_basis_points: formatPercentInBasisPointsNumber(allowedSlippage),
is_auto_router_api: isAutoRouterApi,
@@ -104,25 +103,27 @@ const formatAnalyticsEventProperties = ({
export default function SwapModalFooter({
trade,
allowedSlippage,
txHash,
hash,
onConfirm,
swapErrorMessage,
disabledConfirm,
swapQuoteReceivedDate,
fiatValueInput,
fiatValueOutput,
}: {
trade: InterfaceTrade<Currency, Currency, TradeType>
txHash: string | undefined
hash: string | undefined
allowedSlippage: Percent
onConfirm: () => void
swapErrorMessage: ReactNode | undefined
disabledConfirm: boolean
swapQuoteReceivedDate: Date | undefined
fiatValueInput?: CurrencyAmount<Token> | null
fiatValueOutput?: CurrencyAmount<Token> | null
}) {
const transactionDeadlineSecondsSinceEpoch = useTransactionDeadline()?.toNumber() // in seconds since epoch
const isAutoSlippage = useUserSlippageTolerance() === 'auto'
const isAutoSlippage = useUserSlippageTolerance()[0] === 'auto'
const [clientSideRouter] = useClientSideRouter()
const tokenInAmountUsd = useStablecoinValue(trade.inputAmount)?.toFixed(2)
const tokenOutAmountUsd = useStablecoinValue(trade.outputAmount)?.toFixed(2)
const routes = getTokenPath(trade)
return (
@@ -134,15 +135,15 @@ export default function SwapModalFooter({
name={EventName.SWAP_SUBMITTED}
properties={formatAnalyticsEventProperties({
trade,
txHash,
hash,
allowedSlippage,
transactionDeadlineSecondsSinceEpoch,
isAutoSlippage,
isAutoRouterApi: !clientSideRouter,
tokenInAmountUsd,
tokenOutAmountUsd,
swapQuoteReceivedDate,
routes,
fiatValueInput,
fiatValueOutput,
})}
>
<ButtonError

View File

@@ -40,7 +40,7 @@ const StyledButtonEmpty = styled(ButtonEmpty)`
const AddressText = styled(ThemedText.DeprecatedBlue)`
font-size: 12px;
${({ theme }) => theme.mediaWidth.upToSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
font-size: 10px;
`}
`

View File

@@ -8,10 +8,18 @@ import { Z_INDEX } from 'theme'
import { AutoColumn } from '../Column'
export const PageWrapper = styled.div<{ redesignFlag: boolean }>`
padding: 0 8px;
export const PageWrapper = styled.div<{ redesignFlag: boolean; navBarFlag: boolean }>`
padding: ${({ navBarFlag }) => (navBarFlag ? '68px 8px 0px' : '0px 8px')};
max-width: ${({ redesignFlag }) => (redesignFlag ? '420px' : '480px')};
width: 100%;
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
padding-top: ${({ navBarFlag }) => (navBarFlag ? '48px' : '0px')};
}
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
padding-top: ${({ navBarFlag }) => (navBarFlag ? '20px' : '0px')};
}
`
// Mostly copied from `AppBody` but it was getting too hard to maintain backwards compatibility.
@@ -154,7 +162,7 @@ export const ResponsiveTooltipContainer = styled(TooltipContainer)<{ origin?: st
padding: 1rem;
width: ${({ width }) => width ?? 'auto'};
${({ theme, origin }) => theme.mediaWidth.upToExtraSmall`
${({ theme, origin }) => theme.deprecated_mediaWidth.deprecated_upToExtraSmall`
transform: scale(0.8);
transform-origin: ${origin ?? 'top left'};
`}

View File

@@ -2,8 +2,7 @@ import celoCircleLogoUrl from 'assets/images/celoCircle.png'
import ethereumLogoUrl from 'assets/images/ethereum-logo.png'
import optimismCircleLogoUrl from 'assets/images/optimismCircle.png'
import polygonCircleLogoUrl from 'assets/images/polygonCircle.png'
import arbitrumLogoUrl from 'assets/svg/arbitrum_logo.svg'
import arbitrumCircleLogoUrl from 'assets/svg/arbitrum_logo.svg'
import { default as arbitrumCircleLogoUrl, default as arbitrumLogoUrl } from 'assets/svg/arbitrum_logo.svg'
import celoLogo from 'assets/svg/celo_logo.svg'
import optimismLogoUrl from 'assets/svg/optimistic_ethereum.svg'
import polygonMaticLogo from 'assets/svg/polygon-matic-logo.svg'

View File

@@ -2,8 +2,7 @@ import { Plural, Trans } from '@lingui/macro'
import WarningCache, { TOKEN_LIST_TYPES } from './TokenSafetyLookupTable'
// TODO: Replace this with Steph's article when it is available.
export const TOKEN_SAFETY_ARTICLE = 'https://help.uniswap.org/en/'
export const TOKEN_SAFETY_ARTICLE = 'https://support.uniswap.org/hc/en-us/articles/8723118437133'
export enum WARNING_LEVEL {
MEDIUM,

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