Compare commits

...

337 Commits

Author SHA1 Message Date
vignesh mohankumar
1601962f03 fix: use font-feature-settings in AppWrapper (#4759)
* font-feature-settings

* fix: use font-feature-settings across app

* move to appwrapper

* oops

* maybe better?

* fix
2022-09-28 18:14:00 -04:00
ICONation
ac8e59acba fix: Comparing formattedData reference with [] doesn't work as expected (#4717)
Fix comparing formattedData reference with [] 

This condition will always return 'false' since JavaScript compares objects by reference, not value. ts(2839)
2022-09-28 15:03:28 -07:00
vignesh mohankumar
5f6d17bfe2 fix: grow left panel in token details (#4754) 2022-09-28 17:50:05 -04:00
lynn
3c5fe00c30 fix: restores styling for verified and blocked tokens (#4753)
* fix

* update snapshot

* add blocked token icon

* repsond to vm
2022-09-28 17:30:11 -04:00
Jordan Frankfurt
91754848af fix(styles): revert global bold on links (#4755) 2022-09-28 16:12:08 -05:00
cartcrom
d8eb4d188a fix: display 0 percent change on explore (#4747)
* fixed issue
2022-09-28 16:49:04 -04:00
vignesh mohankumar
25d64911d4 fix: left align SearchBar when text present (#4752) 2022-09-28 15:45:52 -04:00
Zach Pomerantz
888f02dbaa fix: approval pending ux (#4693)
* fix: approval button colors

* feat: show spinner while pending wallet interaction

* fix: constant allow button height
2022-09-28 12:18:32 -07:00
vignesh mohankumar
728a5653be fix: update TokenDetails layout sizing (#4750) 2022-09-28 15:12:01 -04:00
vignesh mohankumar
a5dc0fddb8 fix: remove ignore exhaustive deps in SearchBarDropdown (#4746)
* fix: remove ignore exhaustive deps in SearchBarDropdown

* move into the effect
2022-09-28 13:48:56 -04:00
lynn
134f30e81f fix: resolve bug where changing duration when filter string is applied does not change data (original duration is still applied) (#4737)
* init middle of change

* init

* fix

* respond to jordan
2022-09-28 13:22:52 -04:00
Greg Bugyis
9b07ac2be4 feat: log token explore search event (#4695)
* Log token explore search event

* Move event directly above target element
2022-09-28 20:07:18 +03:00
vignesh mohankumar
571a49ba6f fix: no cursor pointer on SearchBar input (#4743) 2022-09-28 12:37:19 -04:00
vignesh mohankumar
077437e1f1 fix: update swap box styles (#4687)
* fix: update swap box styles

* fix balance text color/weight

* fix padding

* text color
2022-09-28 12:29:58 -04:00
vignesh mohankumar
ba9e509d67 refactor: split out SearchBarDropdown (#4744)
refactor: split out SearchBarDropdown.tsx
2022-09-28 12:26:01 -04:00
vignesh mohankumar
181ab149e3 fix: left align TimeOptions until small breakpoint (#4739)
* fix: left align TimeOptions until small breakpoint

* mobile fixes
2022-09-28 11:44:43 -04:00
vignesh mohankumar
5ef64c7dd1 fix: network filter container shouldn't expand on mobile (#4736) 2022-09-28 11:33:42 -04:00
vignesh mohankumar
0f6a675d0c fix: center TimeButton in PriceChart (#4738) 2022-09-28 11:31:19 -04:00
Jack Short
ec3552bbde style: updating bag styles (#4724)
* updating footer

* bag optimizations

* correct margins for price change rows

* pay button color

* adding fiat values

* using bodySmall
2022-09-28 10:25:18 -04:00
lynn
5783602694 fix: Web 1254 explore sparkline chart quality tweaks (#4732)
* init

* init

* undo random height padding

* revert weird merge mistakes

* fixes sparkline sizing

* respond to jordan comments

* add comments
2022-09-27 21:13:07 -04:00
vignesh mohankumar
9ba76992e4 fix: no hover on price estimate in swap (#4734) 2022-09-27 17:26:05 -04:00
vignesh mohankumar
d075ab6a74 fix: no hover on balance text in swap (#4735) 2022-09-27 17:22:02 -04:00
cartcrom
4cdfeaae34 feat: use new token lists (#4733)
* initial commit

* updates

* prevent unsupported from being validated

* removed unused export

* removed unecessary in

* removed unecessary brackets
2022-09-27 16:57:29 -04:00
Charles Bachmeier
e54b46910a feat: add deep shadow on card hover (#4730)
add deep shadow on card hover

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-09-27 13:36:06 -07:00
lynn
9558406c90 fix: revert simplify search bar filter string handling and reduce # of state changes" (#4731)
Revert "fix: simplify search bar filter string handling and reduce # of state changes (#4716)"

This reverts commit 86785c726a.
2022-09-27 15:15:10 -04:00
Greg Bugyis
f735c34841 feat: Trending Collections Table (#4694)
* Migrate Trending Collections: first pass

* Adding types for react-table

* Forgot to add yarn.lock

* Update sprinkles colors and add accentSuccess to match Figma

* Style cleanup

* Fix overlap on activity items and text wrapping on Value Prop

* Update header to new typography name

* Make entire table row link to collection

* Remove duplicated navigate() on table row

* Use borderStyle: none (sprinkle) instead of hidden

* Use common typography style for table header row

* Sprinkles for rank styles

* Sprinkles for TrendingOptions border styles

* Update color on trendingOption active state

* Restore useEffect to hide certain columns on mobile

* forgot to save one file

* Update accent color

* Use isMobile instead of breakpoint check
2022-09-27 20:33:15 +03:00
vignesh mohankumar
1aa4afad5f chore(theme): add stateOverlayPressed (#4690) 2022-09-27 13:08:15 -04:00
Charles Bachmeier
58005d81d6 fix: broken Details links (#4729)
fix broken Details links

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-09-27 10:00:02 -07:00
lynn
b0381c58e6 fix: Web 1258 token selector remove import from token list functionality (#4726)
* testing

* init

* respond to vm comments and remove dead code

* remove dead code

* update snapshot
2022-09-27 12:07:19 -04:00
Jordan Frankfurt
99a3cfafc9 feat: support NATIVE page for all supported chains (#4722)
support NATIVE page for all supported chains
2022-09-27 11:05:38 -05:00
Charles Bachmeier
6c908eb710 refactor: update card colors and rename backgroundAction (#4725)
update card colors and rename backgroundAction

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-09-27 08:48:13 -07:00
aballerr
dc15144a29 chore: merging loading states part 1 (#4626)
* Part 1 of merging in loading states


Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-09-27 11:07:46 -04:00
Jack Short
34431bcb75 feat: porting over transaction screens (#4720)
* feat: porting over transaction screens

* cannot trigger unless flag enabled

* inital comment addressings

* adjusting zIndex

* changing zIndex when modal is open
2022-09-27 10:31:08 -04:00
cartcrom
0041b787ec refactor: remove unnecessary auth (#4723)
* removed aws auth
* updated fetching and package scripts
* updated url usage
2022-09-26 18:01:50 -04:00
cartcrom
868edc6028 fix: error when hitting enter on search (#4721)
* fixed issue
* fixed comment
2022-09-26 16:49:49 -04:00
Annie Ke
d8c84a91f4 chore: switch optimism testnet from kovan to goerli (#4719)
* chore: switch optimism testnet from kovan to goerli

* update supported chain id check

Co-authored-by: Vignesh Mohankumar <me@vig.xyz>
2022-09-26 14:42:47 -04:00
Jack Short
68282af457 feat: activity port (#4702)
* adding activitySwither

* in the middle of porting activity

* ported over activity leaving - working on breakpoints

* updating responsive design

* updating responsive design

* addressed comments
2022-09-26 14:34:46 -04:00
Charles Bachmeier
0ecb732331 refactor: update common typography (#4714)
update common typography

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-09-26 08:42:25 -07:00
lynn
86785c726a fix: simplify search bar filter string handling and reduce # of state changes (#4716)
fix search bar filter handling
2022-09-26 11:31:46 -04:00
Jack Short
10fe7f5213 feat: purchasing through bag (#4696)
* feat: purchasing assets from bag

* better state management for bag

* fix: comineItemsWithTxRoute.ts

* fixed purchasing assets in review
2022-09-26 10:17:59 -04:00
cartcrom
4deab7554c fix: search crash and explore row numbering (#4715)
fixed
2022-09-23 15:28:54 -04:00
cartcrom
b92c8007e4 feat: explore chain switching (#4710)
* initial commit
* replaced isUserAddedToken with chain-switching friendly version of hook
* reverted useTopTokens()
* addressed first round of PR comments
2022-09-23 15:15:59 -04:00
lynn
029f3acbd5 fix: fix explore table filtering and sorting bugs (#4705)
* fix explore table bugs

* temp

* fix search bar incongruency when navigating to other tab + remove flickering data

* add local cache

* more clear names

* add back useTopTokens return type interface

* respond to comments and dedup repeated code

* respond to cmcewen comments
2022-09-23 14:45:28 -04:00
Jordan Frankfurt
0b9fda5b25 feat(token-details): fix crash on undefined object access (#4712)
feat(token-details): fix crash on undefined object access +some small errors
2022-09-23 12:07:03 -05:00
github-actions[bot]
47816f2530 chore(i18n): new Crowdin translations (#4625)
chore(i18n): synchronize translations from crowdin [skip ci]

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-09-23 09:11:26 -07:00
Charles Bachmeier
73023499aa refactor: Sprinkles Design System Update Color (#4704)
* text primary and secondary

* backgroundOutline

* lightGray

* placeholder

* error and disconnect

* deprecate

* loading

* white opacities

* white

* border cleanup

* organize

* bagQuantity color fix

* fix nav border colors

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-09-23 09:00:53 -07:00
lynn
241043b616 fix: remove number from name in balances (#4711)
remove number
2022-09-23 11:41:29 -04:00
lynn
e97e117298 fix: show real balance in footer summary instead of dummy data (#4707)
real balances to footer
2022-09-23 11:06:55 -04:00
aballerr
b63e95388c chore: Merging in search, sort (#4675)
* Porting over search and porting over sort
2022-09-22 17:52:44 -04:00
vignesh mohankumar
ef8fba1d49 feat: add git commit hash on build (#4701)
* feat: add git commit hash on build

* process.env.

* works

* log

* import

* log full
2022-09-22 15:29:29 -04:00
Zach Pomerantz
be64c03d06 fix: track origin (#4700)
fix: add origin
2022-09-22 12:02:57 -07:00
Zach Pomerantz
45682ca59e feat: track Brave and UA (#4699)
* fix: track Brave from UA

* feat: track UA
2022-09-22 11:42:33 -07:00
Jordan Frankfurt
397b9d423e feat(explore): rewrite top tokens hook (#4697)
rewrite top tokens hook
2022-09-22 13:06:31 -05:00
Jack Short
d9113fb6d4 feat: adding suspicious and pooled asset icons to cards + rarity (#4686)
* adding pooled assets and suspicious assets icons

* adding rarity

* better way to get states
2022-09-22 12:44:43 -04:00
Jack Short
5dc0df2132 feat: initial bag port (#4665)
* initial bag port

* adding add to bag

* adding remove

* addressing comments

* reenable bag on disconnect when reviewing price changes
2022-09-21 16:44:46 -04:00
Jordan Frankfurt
7f2cc9a3e6 feat: add tvl to token details page (#4692)
* add tvl to token details page

* remove mcap, add explanation of market entities
2022-09-21 15:35:17 -05:00
Charles Bachmeier
d3c4ca6e09 feat: add Sell listing page (#4664)
* add listing modal

* add new files

* Add listing page

* remove useeffect

* re-add useeffect and includes array

* position relative

* add listing datatype

* use pluralize

* readable const

* clsx

* parseFloat 0 default

* don't use any

* cant use months for ms

* remove unused input style

* border sprinkles

* clsx

* duration enum

* remove unused index

* pluralize

* clsx

* pluralize

* type refactoring

* move format to utils

* remove uneeded check

* border sprinkles

* change input based on ref

* remove console.log

* correct warning check

* better clsx

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-09-21 13:30:28 -07:00
lynn
28538214d2 feat: new lazy load that scrolls whole window and not inside fixed size container (#4684)
* init

* messy but working omfg

* dont set initial to 500 set to just 1 for testing purposes

* it looks pretty now and works well

* sorting filtering and suspense loading are working

* fix comments

* handle token rows lacking addresS

* start working with new data schema

* new gql schema

* initial commit

* improved performance, added filtering

* lint

* removed comments and accidental settings.json changes

* refactor: switch explore over to new queries (#4657)

* initial commit
* improved performance, added filtering
* addressed pr comments
* fixed typescript issue

* merges

* fix

* fix oopsies

* fix accidental changes

* its working

* drop leftover comment

* clean up loaded row props

* respond to comments

* respond to jordan comments

* init

* remove unnecessary pkgs

* undo yarn lock changes

* loading rows fix

* change loading rows to 3 as per fred instruction

* remove anys

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
Co-authored-by: cartcrom <cartergcromer@gmail.com>
Co-authored-by: cartcrom <39385577+cartcrom@users.noreply.github.com>
2022-09-21 14:03:54 -04:00
Zach Pomerantz
4f48f3372c feat: weth check (#4685)
* feat: throw on invalid deposits

* feat: add new bug link

* chore: note the usage of error state
2022-09-21 11:39:35 -05:00
Jordan Frankfurt
9e070107a2 feat: new gql schema (#4654)
* new gql schema

* refactor: switch explore over to new queries (#4657)

* initial commit
* improved performance, added filtering
* addressed pr comments
* fixed typescript issue

* drop leftover comment

* clean up loaded row props

Co-authored-by: cartcrom <39385577+cartcrom@users.noreply.github.com>
2022-09-21 09:44:14 -05:00
vignesh mohankumar
2a92b2992f fix: remove hover state for chevrons in nav (#4681)
* fix

* fix
2022-09-21 10:28:22 -04:00
aballerr
e180153c3a chore: merge marketplace and traits (#4645)
* porting of filters

Co-authored-by: Jack Short <john.short.tj@gmail.com>
2022-09-21 07:22:05 -04:00
Greg Bugyis
e35f9e16a1 feat: NFT Explore Activity Feed (#4635)
* NFT Explore: Add Activity Feed to Banner section

* Renamed separate style file

* Fix positioning to not squish details section

* Add back activeRow state

* Hide Activity on smaller screens

* Fix for uneven widths between collections

* Addressing PR feedback

Co-authored-by: gbugyis <greg@bugyis.com>
2022-09-21 02:08:25 +03:00
vignesh mohankumar
8e955e9257 fix: fully round corner on account dropdown (#4682)
* fix: fully rounder corner on account dropdown

* comment
2022-09-20 18:38:32 -04:00
lynn
9ca44652b3 fix: make token detail chart full width and x axis fixes (#4669)
* full chart width

* add offsets to x axis start and end times so they dont get cut off

* hide ticks

* reduce offsets

* undo
2022-09-20 17:20:43 -04:00
vignesh mohankumar
3d89d72426 chore: feature flags token favorites (#4680)
* fix: fixes TokenTable header ordering

* flag favorite tokens

* imports

* remove fav col

* hide on token details
2022-09-20 16:36:44 -04:00
vignesh mohankumar
c2ffab3273 fix: center SearchBar vertically (#4677)
* fix: center SearchBar vertically

* oops
2022-09-20 16:36:23 -04:00
vignesh mohankumar
dbb62b613c chore: remove tokensNetworkFilter feature flag (#4679) 2022-09-20 15:57:22 -04:00
vignesh mohankumar
6f2d6e31c9 feat: add "approve in wallet" prompt on chain selector (#4673)
* logic

* working

* grid

* some file renaming

* Move to ChainSelectorRow.tsx

* remove flex

* more renames

* more styles

* fixes

* local imports

* more styling changes

* style

* fix mobile

* toggle open on open

* setIsOpen
2022-09-20 15:34:34 -04:00
vignesh mohankumar
944939a2e9 fix: makes sure wallet error is visible (#4676) 2022-09-20 15:32:11 -04:00
vignesh mohankumar
04164a550d fix: close WalletModal on connection (#4670)
* fix: close WalletModal on connection

* fix test
2022-09-20 14:08:09 -04:00
vignesh mohankumar
16a5e15070 fix: center Web3StatusConnectButton (#4671) 2022-09-20 14:02:59 -04:00
Charles Bachmeier
ee97d8d902 feat: add listing modal (#4663)
* add listing modal

* add new files

* remove useeffect

* re-add useeffect and includes array

* position relative

* add listing datatype

* use pluralize

* readable const

* clsx

* parseFloat 0 default

* don't use any

* cant use months for ms

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-09-20 10:45:22 -07:00
a6Ce6Bs
3d4b077b89 fix: networkProvider error (#4674) 2022-09-20 11:34:40 -05:00
pp-hh-ii-ll
0f4a89d938 fix: update Unisocks status icon and positioning (#4582)
* fix: update Unisocks status icon and positioning

Updates design of Unisocks icon and adjusts positioning to suit new design

* Update StatusIcon.tsx
2022-09-19 15:17:41 -04:00
vignesh mohankumar
0d0ec12dbf fix: normalize NavBar button heights (#4656)
* fix: normalize NavBar button heights

* chainswitcher and web3status are not using this component

* update nav icon
2022-09-19 14:50:30 -04:00
vignesh mohankumar
afe30a2c02 fix: reset Widget amount on URL change (#4668)
* fix: reset Widget amount on URL change

* empty
2022-09-19 14:50:16 -04:00
vignesh mohankumar
2f9289a2c5 fix: updates Widget defaultToken on URL change (#4666)
* fix: updates defaultToken on URL change

* rm usePrevious
2022-09-19 13:52:36 -04:00
vignesh mohankumar
7a9d2e80d0 refactor: move components/AmplitudeAnalytics to analytics (#4655)
* move components/AmplitudeAnalytics to analytics/amplitude

* move to analytics

* fix imports
2022-09-19 12:54:54 -04:00
vignesh mohankumar
d4b8735c04 fix: remove sendTestAnalyticsEvent (#4653)
* fix: remove sendTestAnalyticsEvent

* comments
2022-09-19 12:44:29 -04:00
vignesh mohankumar
d31687d0bf style: remove broken flex property (#4650) 2022-09-19 11:53:42 -04:00
vignesh mohankumar
470535dd33 fix: pass outputCurrency not inputCurrency (#4649) 2022-09-19 11:53:31 -04:00
lynn
27f53f1e99 fix: load correct initial display price and % delta w/o need for scrubbing on token detail chart (#4651)
* remove console logs

* fix in response to comments
2022-09-19 11:30:09 -04:00
aballerr
e7d498c95e chore: Merging details part 3 (#4637)
* Merging details part 3



Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-09-19 09:32:11 -04:00
vignesh mohankumar
02b617d297 fix: move token banner logic into Modals (#4638)
* move banner logic into Modals

* fixed

* z-index

* fix

* fix
2022-09-16 16:25:25 -04:00
vignesh mohankumar
96d04e1a7d fix: remove search overlay (#4648) 2022-09-16 16:24:13 -04:00
vignesh mohankumar
fe9d805d7c fix: link to swap with inputCurrency from mobile token detail (#4647) 2022-09-16 16:00:58 -04:00
vignesh mohankumar
b6c136839e fix: show copied text where address was clicked (#4646)
* pos x

* working

* tooltip width
2022-09-16 15:14:55 -04:00
Charles Bachmeier
8c947a0e0d feat: Add Select Page Modal (#4643)
* add select nfts shopping bag modal

* Add shopping bag to top lovel modal wrapper

* addressing comments

* rename to listingTag

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-09-16 11:31:25 -07:00
Charles Bachmeier
49c5cbbf3b feat: Add sell page filters sidebar (#4630)
* working sell filters

* split filters into own file

* include new file

* fix eslint warnings

* update filter button param and fix rerender bug

* de morgon's law

* usecallback

* move max_padding

* extend htmlinputelement for checkbox

* styles cleanup

* simplify checkbox sprinkles

* add null check to collectionfilteritem

* remove x axis scrollbar on collections

* update fitlerbutton logic

* scrollbar width

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-09-16 10:55:26 -07:00
Greg Bugyis
efaefe2e44 fix: Token Details chart: adjust curveTension on 1H chart timeframe (#4632)
Adjust curveTension on 1H chart timeframe
2022-09-16 20:19:20 +03:00
lynn
ed95f1b966 fix: add IP to test project for amplitude (#4644)
* init

* fix lint

* add comment

* lint fix

Co-authored-by: Lynn Yu <lynn.yu@ContangoITs-MacBook-Pro.local>
2022-09-16 13:13:30 -04:00
Jack Short
7ecbc552aa feat: cards resize to uniform height (#4639)
* feat: cards resize to uniform height if differenting heights in collection

* setting uniform height if card is video
2022-09-16 10:45:05 -04:00
Jack Short
dadc997398 chore: updating sprinkles z indices (#4641)
chore: updating sprinkles zindices
2022-09-15 19:10:43 -04:00
lynn
b90d6b5ab0 feat: amplitude swap fixes (#4627)
* init

* add back txn completed event
2022-09-15 17:53:45 -04:00
Jack Short
db5c6f82fd fix: search rerender (#4640) 2022-09-15 17:28:19 -04:00
lynn
f161f9617b feat: Web 924 explore use real data for the sparkline price charts (#4634)
* init

* test

* working w new hook

* simsplify

* fixes to comments

* extra comment remove
2022-09-15 17:27:33 -04:00
Connor McEwen
9d3249e6bd fix: rendering issues with modal/header (#4636)
* fix overlay issue with modal

* fix z index
2022-09-15 15:32:06 -04:00
Jack Short
f1c65afa98 feat: audio video nft collection cards (#4628)
* feat: video asset cards

* feat: audio cards

* square aspect ratio for videos

* adding playsinline
2022-09-15 15:12:23 -04:00
Jack Short
80c1f0cdf9 feat: nft filter bar (#4617)
* initial filter window

* filters

* filter button

* adding all filters to filter check

* sorting exports

* reviewing old css

* change to const

* responding to comments

* removing isMobile

* fixing radio input

* refactoring radio

* refactoring radio

* reusing the same class

* removing unused props

* removing useless clsx

* removing scrollToTop
2022-09-15 13:01:04 -04:00
aballerr
ea0fe83d00 chore: Merging details part 2 (#4594)
* Merging details part 2


Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-09-15 12:28:04 -04:00
Greg Bugyis
994836fba7 style: Extend hover on secondary dropdown menu links (#4633)
* Full width hover on secondary menu links

* Remove commented line
2022-09-15 19:27:03 +03:00
Greg Bugyis
41aa1dcb0b feat: NFT Explore Value prop row (#4603)
* NFT Explore: value prop row

* Changes from PR feedback

* Tweak opacity values

* Style cleanup

* Adjust bg image on smaller screen sizes

* Remove unnecessary media query

Co-authored-by: gbugyis <greg@bugyis.com>
2022-09-15 11:48:15 +03:00
Zach Pomerantz
3965d3fdd9 feat: upgrade widget (#4629)
* feat: upgrade widget and use static connectors

* build: upgrade lockfile
2022-09-14 16:29:18 -07:00
cartcrom
ff6fd8a6e9 style: added transition ease to explore (#4616)
* initial commit
* updated transition css
* added opacity constants to theme, switched to using transition duration constants
2022-09-14 17:03:19 -04:00
taycaldwell
0a6906b23e fix: Swaps icons for light theme and dark theme (#4571)
Fix icons for light/dark mode
2022-09-14 14:17:36 -04:00
Charles Bachmeier
c38b5c0ce3 feat: migrate select page assets (#4618)
* add main nft sell page

* remove background

* more precise naming

* Add wallet assets for select page

* update styles while without filter bar

* remove unnecessary useeffect

* deprecate old stlye

* move to common props

* add round helper fn

* use react router link

Co-authored-by: Charlie <charlie@uniswap.org>
Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-09-14 11:07:58 -07:00
Zach Pomerantz
86f3b5a036 feat: caching provider (#4615)
* feat: caching providers

* feat: clear cache per block
2022-09-14 10:05:29 -07:00
Jack Short
382a44f040 feat: adding bag icon to navbar (#4619)
* feat: adding bag icon to navbar

* updating sell icon
2022-09-14 12:40:42 -04:00
Greg Bugyis
2d9604cd14 fix: Tick collisions on token detail price chart (#4623) 2022-09-14 17:19:38 +03:00
cartcrom
7930709bc3 refactor: unnesting token details into sectioned components (#4621)
finished refactoring
2022-09-14 10:19:18 -04:00
Zach Pomerantz
6fe2c92cee fix: reduce price fetching to every 2m (#4622)
* fix: reduce price fetching to every 2m

* fix: grammar
2022-09-13 15:16:01 -07:00
Jack Short
884dee2db3 fix: pointing nft links to the right url hash (#4620) 2022-09-13 17:03:08 -04:00
Zach Pomerantz
1f00c2a9c4 fix: fetch the usd price from parsed amount (#4612) 2022-09-13 10:31:30 -07:00
cartcrom
84070835df feat: token data cache (#4534)
* initial cache construction
* switched to relay cache, updated hooks
* fixed comments
2022-09-13 13:09:12 -04:00
Zach Pomerantz
fb389137e7 feat: adds ethers provider tracing (#4614) 2022-09-13 09:07:47 -07:00
Charles Bachmeier
c14b6a78ae feat: add main nft sell page (#4609)
* add main nft sell page

* remove background

* more precise naming

Co-authored-by: Charlie <charlie@uniswap.org>
Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-09-13 09:07:06 -07:00
Zach Pomerantz
a6c1c49f98 fix: update feature flags on update (#4613) 2022-09-13 09:03:59 -07:00
Charles Bachmeier
7848ad86bd fix: adjust search input width (#4610)
adjust search input width

Co-authored-by: Charlie <charlie@uniswap.org>
2022-09-13 08:39:57 -07:00
Charles Bachmeier
882c15dada fix: bug where changing tabs can open the searchbar (#4607)
* remove onFocus trigger

* typo on comment

Co-authored-by: Charlie <charlie@uniswap.org>
2022-09-12 11:39:07 -07:00
Charles Bachmeier
704ad222d9 feat: add animation to search skeleton and typing state (#4598)
* fade when loading

* fade results between searches

* trending search loading skeleton

* adjust skeleton and cleanup

* remove unused style change

* eslint

* add trendingTokens to query and remove unnecessary returns

* move array map compatibility higher in the call hierarchy

* add feature flag to isLoading param

Co-authored-by: Charlie <charlie@uniswap.org>
2022-09-12 10:47:53 -07:00
Charles Bachmeier
cfee80ce3c feat: Add animations for desktop, tablet, and mobile searchbar (#4584)
* animations working pre cleanup

* all sprinkles

* fix bug with phase1 search content

* new isTablet hook

* add conditional vars

* typeof window

* remove unneeded clsx usage

Co-authored-by: Charlie <charlie@uniswap.org>
2022-09-12 10:09:59 -07:00
github-actions[bot]
eb95cedd72 chore(i18n): new Crowdin translations (#4512)
chore(i18n): synchronize translations from crowdin [skip ci]

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-09-12 11:21:30 -04:00
vignesh mohankumar
99a7fb3383 fix: remove refetchOnFocus for routing-api (#4601) 2022-09-09 18:35:12 -04:00
vignesh mohankumar
7f4dbf9346 chore: update smart-order-router (#4600)
* bump SOR

* dedupe
2022-09-09 18:31:41 -04:00
Zach Pomerantz
09b00c9974 fix: use static rpc urls (#4599) 2022-09-09 14:58:13 -07:00
Zach Pomerantz
b74fb8174d feat: optimize AlphaRouter usage (#4596)
* feat: add RouterPreference.PRICE

* docs: add reference to quote params

* fix: cache routers between usages

* fix: tune price inquiries

* fix: note price queries tuning

* fix: clean up params - nix mixed

* fix: defer PRICE tuning
2022-09-09 13:07:53 -07:00
Jordan Frankfurt
a7ec5a64b7 feat(explore): add a simple search debounce (#4595)
add a simple search debounce
2022-09-09 14:09:08 -05:00
Greg Bugyis
c619dcf65d fix: Token chart - inconsistent x-axis time intervals (#4579)
* Use timeScale for x-axis

* Use d3 time intervals for ticks

* Drop slice for now, will handle differently

* scaleTime turned out to be unnecessary

* Use .nice() to help with tick spacing at start/end

Co-authored-by: gbugyis <greg@bugyis.com>
2022-09-09 22:08:57 +03:00
aballerr
1221d88e13 chore: Cypress utility function for selecting feature flags and walletdrop down cypress tests (#4536)
* Adding feature flag utility to cypress and adding wallet cypress tests


Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-09-09 14:54:54 -04:00
Charles Bachmeier
48d2ead71d feat: update universal token search and trending tokens endpoint (#4593)
update universal token search and trending tokens endpoint

Co-authored-by: Charlie <charlie@uniswap.org>
2022-09-09 10:28:54 -07:00
aballerr
ed7099bfd6 chore: Merging details page (#4585)
* Initial merge of details page


Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-09-09 13:23:41 -04:00
vignesh mohankumar
2604cdfdae fix: only initialize using chain query (#4567) 2022-09-08 12:16:29 -04:00
cartcrom
94dc389812 feat: widget speedbumps on swap review (#4544)
* initial commit
* finished feature
* addressed PR comments
2022-09-07 16:12:35 -04:00
Greg Bugyis
4a8c621f46 feat: add maxHeight to CurrencySearchModal (#4557)
* Add maxHeight to CurrencySearchModal (search only)

* Combine min and maxHeight into single modalHeight value

* Use clearer variable name to distinguish window height value

Co-authored-by: gbugyis <greg@bugyis.com>
2022-09-07 23:08:29 +03:00
aballerr
477af8af4e fix: Making sure all icons are 24px (#4580)
Making all icons size 24px on web status

Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-09-07 15:47:24 -04:00
aballerr
a9a7d524aa fix: fixing token colors and token select persistence (#4575)
* fixing token colors and token select persistence

Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-09-07 13:35:00 -04:00
aballerr
1cdaff8ddf fix: fixing match design (#4577)
* fixing select token favorite icon to match design



Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-09-07 12:44:19 -04:00
aballerr
eeea3d2dcc fix: fixed wallet scrolling issue (#4574)
* fixed scrolling issue for wallet


Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-09-07 11:44:33 -04:00
lynn
f46b6a0697 fix: ensure nav bar goes above all other components when scrolling (#4576)
* fix nav bar below other components issue

* respond to comments
2022-09-06 17:18:54 -04:00
lynn
622581ee0a feat: show real values for current network balance (#4565)
* init

* remove card when balance is zero

* remove commented code

* remove commented
2022-09-06 11:50:02 -04:00
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
Vignesh Mohankumar
9873491db1 fix: increases tokens search background contrast (#4443) 2022-08-22 15:17:40 -07:00
Vignesh Mohankumar
5d64ab0146 fix: flag NetworkFilter (#4442)
feat: flag token table network filter
2022-08-22 18:11:22 -04:00
github-actions[bot]
568267ce07 chore(i18n): new Crowdin translations (#4410)
* 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-22 17:29:40 -04:00
Vignesh Mohankumar
69cdefe996 fix: update Swap container padding (#4440)
* fix: update Swap container padding

* rm AlertWrapper

* empty
2022-08-22 17:28:17 -04:00
Charles Bachmeier
61758db589 fix: active state for Token Explore (#4439)
fix active state for Token Explore

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-22 14:23:34 -07:00
cartcrom
9e9d98bb31 fix: chart size responsiveness issue (#4441)
fixed responsiveness issue
2022-08-22 17:15:24 -04:00
lynn
6b2b771dc4 fix: correct mistaken usdc user property that should be weth (#4438)
fix
2022-08-22 16:33:00 -04:00
pp-hh-ii-ll
031bea0f50 fix: Update outdated colors (#4420)
* Update outdated colors and add shallowShadow

* Update colors.ts

Co-authored-by: Kaylee George <62825936+kayleegeorge@users.noreply.github.com>
2022-08-22 13:20:59 -07:00
Vignesh Mohankumar
6f2e447ec3 chore: remove fortmatic key (#4435)
chore: remove unused fortmatic key
2022-08-22 16:12:55 -04:00
Kaylee George
cf831fbcea refactor: removes flyoutDropShadow, adds shallowShadow (#4437)
flyout drop shadow
2022-08-22 12:58:58 -07:00
lynn
bcd4c1c182 feat: only track usdc, weth, native token balances (#4424)
* init

* fix unit test

* extract log token logic into custom hook
2022-08-22 15:31:28 -04:00
Kaylee George
d954026cea feat: add token details and token row table query (#4419)
* add query

* revert styles

* more style

* rename

* restructure

* fix

* more

* network

* uppercase symbol

* rm unused

* fix

* check nan

* add token row query

* small change

* nan?
2022-08-22 11:52:24 -07:00
Vignesh Mohankumar
2c2dad1415 build: enforce node/npm versions (#4434)
* build: enforce node/npm versions

* enforce yarn usage
2022-08-22 13:55:48 -04:00
Kaylee George
d42ed88845 fix: fix various selected states on Explore filters and update theme colors (#4433)
* fixed

* update more
2022-08-22 10:51:59 -07:00
Kaylee George
e12c00e980 fix: time selector updated to align with price chart (#4425)
* fix time

* displays
2022-08-22 09:37:49 -07:00
Jack Short
c25971e5d2 feat: collection asset cards (#4422)
* removing differentiating mobile and desktop collections

* feat: asset cards

* changed card to module + addressed other comments

* todo
2022-08-22 12:15:17 -04:00
Charles Bachmeier
293e56758c feat: Placeholder NFT Details and NFT Sell Pages (#4431)
* add placeholder pages for nft sell and nft details

* feature flag link in navbar dropdown

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-22 07:53:11 -07:00
tainguyen-kms
a6b17f0437 fix: Fix unresponsive image in vote page (#4416)
* Fix unresponsive image scale in vote page

* Create renderer function for Image
2022-08-19 23:19:05 -04:00
cartcrom
140d59b898 fix: updated schema (#4423)
updated schema
2022-08-19 17:40:58 -04:00
cartcrom
85742c5785 feat: data api connection (#4396)
* split relay into two environments, refactored schema grabbing, implemented api call for token price graph
2022-08-19 15:31:10 -04:00
aballerr
9b07d8ce64 feat: Wallet v1 transactions part 1 (#4398)
* Adding in tx history for 3 transactions: Swap, Add Liquidity, Remove Liquidty




Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.fios-router.home>
Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.local>
2022-08-19 14:01:34 -04:00
Charles Bachmeier
b1b9da1b17 feat: enable NFT search results (#4413)
* enable NFT search results

* update usememo deps

* simplify flag trending token logic

* respond to comments

* lowercase bool

* rename flag

* improve truncation

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-19 09:58:26 -07:00
Vignesh Mohankumar
ffe670923e feat: remove fortmatic (#4167)
* rm fortmatic (wip for test)

* remove isChainAllowed

* handle unexpected connector

* clear on catch

* Revert "remove isChainAllowed"

This reverts commit d505cac39a446cb2c273016d03896469ba39eb60.

* fix to actually use ALLOWED_CHAIN_IDS

* fix
2022-08-19 12:06:14 -04:00
Charles Bachmeier
21649967aa refactor: rename phase1flag to nftflag (#4414)
* rename phase1flag to nftflag

* add new flag file

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-19 08:41:54 -07:00
lynn
3f40f60c1c fix: fix add liquidity select tokens alignment (#4403)
* init

* only remove this component in add liquidity screen
2022-08-18 23:30:15 -04:00
lynn
176c275a06 feat: Web 640 eng transaction states redesign (#4389)
* init

* pending and failed states

* simplify

* init

* modals

* fix drop shadow logic

* fixes for kaylee comments
2022-08-18 22:49:39 -04:00
Charles Bachmeier
ae2b4b1668 fix: fix skeleton hover state bug (#4409)
* fix skeleton hover state bug

* corretc hover color

* add transition

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-18 16:10:14 -07:00
Kaylee George
a27f8e2937 feat: add no information available state for token details (#4407)
* skeleton

* fix

* fix layout

* gap
2022-08-18 15:02:44 -07:00
Charles Bachmeier
818b1c84b0 feat: add NFTs tab to navbar (#4408)
add NFT tab to navbar

Co-authored-by: Charlie <charlie@uniswap.org>
2022-08-18 14:40:52 -07:00
Vignesh Mohankumar
75eceaa5e1 fix: Revert "feat: fix padding" (#4406)
Revert "feat: fix padding (#4400)"

This reverts commit 96c23af99c.
2022-08-18 17:36:32 -04:00
Charles Bachmeier
c6b4cc8e01 feat: add phase0 searchbar (#4377)
* feat: add phase0 searchbar

* exhaustive deps

* use router Link'

* use correct navigate for tokens

* useLocation

* add util function for organizing search results

* fix mobile navbar link

* remove exhausted depedencies

* split suggestion rows to their own file

* add new file

* use pathname instead of hash

* use imageholder classname

* fallback update

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-08-18 14:19:03 -07:00
Jack Short
819302b51f feat: adding collection stats to collection page (#4391)
* feat: adding collection stats

* removing debounced callback

* addressing comments

* updating marquee and updating isMobile hook

* adding bool to useIsMobile
2022-08-18 16:10:22 -04:00
Kaylee George
c53d7fcc32 fix: fix Explore search bar styling (#4405)
* fix search

* fix responsive

* fix width
2022-08-18 13:06:09 -07:00
lynn
3de2e65530 feat: fix hover color on vote page items (#4404)
init
2022-08-18 15:05:27 -04:00
Kaylee George
c5319b6bea fix: move favorites to right and fix header highlight (#4402)
* change favorite style

* more

* fix header

* fix styling

* small fix
2022-08-18 11:14:43 -07:00
github-actions[bot]
801ddc0886 chore(i18n): new Crowdin translations (#4395)
chore(i18n): synchronize translations from crowdin [skip ci]

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-08-18 11:06:14 -07:00
aballerr
dfd9196aa7 feat: Wallet p0 (#4368)
* P0 Wallet

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.fios-router.home>
2022-08-18 13:56:15 -04:00
lynn
c4362297f5 feat: Safety label speedbump: fix copy (#4401)
init
2022-08-18 13:17:22 -04:00
lynn
96c23af99c feat: fix padding (#4400)
fix padding
2022-08-18 12:13:20 -04:00
lynn
6a29dacdeb feat: fix explore and pools titles to match designs. (#4399)
init
2022-08-18 11:46:39 -04:00
lynn
9ddad80f2a feat: fix favorite tokens design (#4397)
fix favorites
2022-08-18 08:24:07 -07:00
Kaylee George
1944fe4029 fix: width of widget and network balances on token details (#4392)
* fix width

* nits

* width var

* nit

* px fix

Co-authored-by: Vignesh Mohankumar <me@vig.xyz>
2022-08-17 19:02:24 -04:00
lynn
9921736102 fix: add missing colon to end of drop shadow color (#4394)
init
2022-08-17 18:43:14 -04:00
lynn
11aa641dbc feat: wallet connect redesign (#4381)
* init

* pending and failed states

* simplify

* fixes in respond to fred nits

* update overlay
2022-08-17 18:15:53 -04:00
Kaylee George
2f3290592b feat: add L2 network label to Explore token row (#4361)
* initial

* add L2 badge

* meh

* png updates

* fix icons

* naming

* nits

* case

* rename

* rm
2022-08-17 17:35:25 -04:00
github-actions[bot]
7b3fe73474 chore(i18n): new Crowdin translations (#4378)
chore(i18n): synchronize translations from crowdin [skip ci]

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-08-17 13:27:37 -07:00
Kaylee George
4237354bb7 chore: rename Explore directory to Tokens (#4388)
* rename

* move file
2022-08-17 15:18:26 -04:00
Jack Short
f7354c9842 refactor: removes BodyWrapper padding (#4375)
* fix: removing bodywrapper padding for phase1

* reverting to previous version and adjusting padding across pages appropriately

* adjusting padding on add liquidity page

* prettier changes

* missed semicolon
2022-08-17 14:35:47 -04:00
Charles Bachmeier
1636786af8 feat: add the phase0 chain switcher (#4376)
* feat: add phase0 chain switcher

* update styles

* add chain switcher files

* remove unneeded eslint disable

* add Celo and remove unneeded null check

* remove old comment

* fix mobile routing

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-08-17 11:24:56 -07:00
aballerr
de0a716f41 chore: Usetheme hook fix (#4387)
* fixing hook


Co-authored-by: Alex Ball <alexball@UNISWAP-MAC-038.fios-router.home>
2022-08-17 17:53:46 +00:00
Zach Pomerantz
9f108c406b chore: clean up theme usage (#4335)
* chore: clean up DefaultTheme usage

* chore: simplify useTheme usage

* chore: consolidate multi-line imports
2022-08-17 16:55:58 +00:00
Vignesh Mohankumar
5346d13674 chore: rename flag explore -> tokens (#4386) 2022-08-17 16:43:21 +00:00
Charles Bachmeier
d054079eeb feat: add routing functionality to navbar (#4374)
* feat: add routingfunctionality to navbar

* update colors

* rename and pass strings directly

* rename symbol

* rename props

* better symbol name for mobile link

* more isPoolActive

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-08-17 16:31:30 +00:00
Vignesh Mohankumar
fe6324f84d feat: remove v2, v3 swap router (#4372)
* remove v3 router

* rm v2

* unused

* remove anytrade

* unused

* rm shared

* remove optimized trade

* celo fix
2022-08-17 16:05:16 +00:00
Vignesh Mohankumar
4a70eb5956 fix: rename /explore to /tokens (#4385)
* fix: rename /explore to /tokens

* name

* tokens

* oops
2022-08-17 16:05:01 +00:00
Vignesh Mohankumar
1a7b86d155 chore: remove unused exports (#4380) 2022-08-17 11:45:45 -04:00
Vignesh Mohankumar
f66f8c4d59 fix: undo polygon auto-slippage changes (#4379) 2022-08-17 11:37:54 -04:00
Jack Short
1a9c3c3632 feat: add a feature flagged blank collections page (#4371) 2022-08-17 10:42:27 -04:00
cartcrom
91f4892b0c feat: integrate relay (#4320)
* setup relay compiler
* refactored to use polling interval, fixed PR comments
* fixes, readded uninitialized state for liquidity chart
* updated cypress test
* reorganized graphql files into src/graphql
2022-08-16 20:01:12 -04:00
cartcrom
d6d0a98afe fix: chart design fixes & style updates (#4341)
* removed ticks outside of hover
* simplifying copyhelper
* finished implementing fred's feedback
* addressed PR comments
* fixed more of fred's feedback
2022-08-16 19:59:02 -04:00
Kaylee George
8efc5af2bc fix: update feature flag modal design & add headers (#4370)
* initial

* progress

* sike no progress

* design updates

* fix header

* remove colon

* add flag
2022-08-16 19:10:56 +00:00
Zach Pomerantz
104b62f4d8 feat: send web vitals (#4366) 2022-08-16 11:26:15 -07:00
Charles Bachmeier
8e9dbe31fa feat: add a feature flagged blank navbar (#4365)
* add some initial infra for blank navbar

* add blank navbar

* add new files

* use web3status

* remove unused mobile flag

* remove colors

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-08-16 17:46:50 +00:00
Jordan Frankfurt
cf0afa01c8 feat(widget): light and dark themes (#4367)
feat(widget): light and dark themes:
2022-08-16 12:20:23 -04:00
Vignesh Mohankumar
c5b67ac60b refactor: deprecate theme.none (#4362) 2022-08-16 09:05:43 -07:00
Charles Bachmeier
110c6fc08f feat: add in common nft components (#4363)
* add in common genie components

* update import and run linter

* better event type handling

* rotate 360

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-08-16 05:38:27 -07:00
Vignesh Mohankumar
f349ecdd3c chore: remove selectedWalletBackfilled (#4283)
* remove selectedWalletBackfilled

* FIX
2022-08-15 15:39:16 -07:00
lynn
57fb481da9 chore: update amplitude version (#4358)
* update amplitude version

* fix amplitude analytics index file to work with new version

* remove extraneous comment
2022-08-15 18:02:02 -04:00
Charles Bachmeier
5871e0afe1 feat: add navbar feature flag and small style update (#4364)
add navbar feature flag and small style update

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-08-15 21:34:25 +00:00
Jordan Frankfurt
281879ea98 chore: dont-send-test-data-to-test-amplitude (#4360)
* chore: dont-send-test-data-to-test-amplitude

* chore: dont-send-test-data-to-test-amplitude
2022-08-15 15:37:05 -05:00
Zach Pomerantz
b623380dd0 feat: show widget in token details (#4337)
* build: install widgets

* feat: show widget in token details

* basic integration props

* ts fix and update widget to 2.1.1

* url map

* yarn-deduplicate --strategy=highest

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
2022-08-15 15:12:59 -05:00
Vignesh Mohankumar
7645094df6 refactor: move useSyncChainQuery to NetworkSelector (#4353) 2022-08-15 12:06:18 -07:00
Vignesh Mohankumar
733b1885ff fix: await selectChain before closing modal (#4354) 2022-08-15 12:05:55 -07:00
Vignesh Mohankumar
7adbb1e0af fix: use tokenSafety flag in Modal (#4359) 2022-08-15 18:59:30 +00:00
Jordan Frankfurt
6d5967c981 chore: add test key to .env (#4357)
add test key to .env
2022-08-15 14:29:30 -04:00
lynn
b15f2a2bb6 feat: fail gracefully when amplitude api key is not present (#4355)
init
2022-08-15 18:00:37 +00:00
Vignesh Mohankumar
3cc0a41e50 refactor: split ChainId useEffects (#4348)
* split out useOnSelectChain, useSyncChainQuery

* use chain query sync

* lint

* rename

* move variables
2022-08-15 10:20:26 -07:00
Noah Zinsmeister
4abdca9fdd fix: improve calldata formatting (#4352)
improve calldata formatting
2022-08-15 12:33:23 -04:00
lynn
99ef9366d6 feat: amplitude production sdk changes (#4312)
* init

* error change

* use isProduction vs isDevelopment to include vercel
2022-08-15 12:24:31 -04:00
github-actions[bot]
235ee5dff9 chore(i18n): new Crowdin translations (#4321)
chore(i18n): synchronize translations from crowdin [skip ci]

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-08-15 11:20:31 -04:00
lynn
20f25803d4 feat: Add favorite tokens from localstorage to /explore top tokens data query (#4351)
* init

* move to explore page level

* remove extraneous useTopTokens hook replaced by useExplorePageQuery
2022-08-15 11:13:36 -04:00
Vignesh Mohankumar
09380698fa refactor: split phase0 flag into redesign, explore, tokenSafety (#4347)
* changes so far

* more changes

* lint

* remove Phase0:
2022-08-15 10:09:26 -04:00
lynn
1d96961f25 feat: token selector design update (#4333)
* init

* selected base currency button

* add check icon

* theme background module change

* hover changes

* theme none

* additional padding

* hover default color from phil + feature flag

* checkmark style fixes
2022-08-12 17:47:36 +00:00
Vignesh Mohankumar
b38241c675 chore: remove @amplitude/experiment-js-client (#4338) 2022-08-11 18:55:16 -04:00
Eric Zhong
1dbd3240a3 feat: Add protcol.MIXED to protocols (#4334)
Add protcol.MIXED to protocols
2022-08-11 17:13:49 -04:00
Eric Zhong
47e6c0891e feat: Add support for mixed routes in the interface (#4181)
* Expand typing to match new QuoteResponse from routing api

* lint autofix

* Expand InterfaceTrade class to match router-sdk and allow mixedroutes

* Add new routing-api poolInRoute logic for client side trade

* eslint fix ==

* Add custom mxiedProtocolBadge and add into badgeDiagram

* Bump router-sdk to published version, and install latest local SOR

* git checkout main yarn.lock && yarn && npx yarn-deduplicate

* Bump SOR to 2.9.2

* bump SOR to 2.9.2

* Update yarn.lock

* revert slice.ts

* Add guesstimate logic for mixedRoutes

* Oops wrong pool import lol

* Add beta url + expand protocols array

* Add forceMixedRoutes for testing

* Fix cyrpress build issue: no-loop-func

* Change to prod url and remove testing param

* remove protocol flag

* Revisions: getRouteProtocol helper, fix gas acc for mixed route

* revert protocols array abck

* Remove :Protocol
2022-08-11 16:40:55 -04:00
Kaylee George
a6e35ed70f fix: fix naming in pricechart (#4332)
fix
2022-08-11 19:50:19 +00:00
Kaylee George
f570c3fa06 fix: update time option menu for token details and colors to match theme file (#4322)
* fix time options and colors

* fix line height

* color change
2022-08-11 19:35:34 +00:00
Vignesh Mohankumar
50556a295f test: skip fetchTokenList tests (#4330)
skip token list tests
2022-08-11 19:00:44 +00:00
cartcrom
acdca350a6 refactor: removing duplicate AccountDetails code (#4329)
fixed redundant code
2022-08-11 14:03:46 -04:00
Vignesh Mohankumar
fe55c7ae7a fix: increase Polygon auto-slippage min (#4327)
* fix: increase Polygon auto-slippage min

* comment

* update percent
2022-08-11 16:15:02 +00:00
515 changed files with 36695 additions and 9339 deletions

8
.env
View File

@@ -1,2 +1,8 @@
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
REACT_APP_FORTMATIC_KEY="pk_live_357F77728B8EB880"
REACT_APP_AMPLITUDE_TEST_KEY="add-the-real-test-key-if-you-need-to-test-amplitude-events"
REACT_APP_AWS_API_REGION="us-east-2"
REACT_APP_AWS_API_ACCESS_KEY="AKIAYJJWW6AQ47ODATHN"
REACT_APP_AWS_API_ACCESS_SECRET="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"
REACT_APP_AWS_X_API_KEY="z9dReS5UtHu7iTrUsTuWRozLthi3AxOZlvobrIdr14"
REACT_APP_AWS_API_ENDPOINT="https://beta.api.uniswap.org/v1/graphql"
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"

View File

@@ -1,5 +1,7 @@
REACT_APP_AMPLITUDE_KEY="1c694b28cd089acc2c386d518f93a775"
REACT_APP_AMPLITUDE_KEY="b8f7dabddb1c3b03b394619767972160"
REACT_APP_AMPLITUDE_TEST_KEY="1c694b28cd089acc2c386d518f93a775"
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"
REACT_APP_FIREBASE_KEY="AIzaSyBcZWwTcTJHj_R6ipZcrJkXdq05PuX0Rs0"
REACT_APP_AWS_API_ENDPOINT="https://api.uniswap.org/v1/graphql"

View File

@@ -18,7 +18,6 @@
"src/abis/types",
"src/locales/**/*.js",
"src/locales/**/en-US.po",
"src/state/data/generated.ts",
"node_modules",
"coverage",
"build",
@@ -82,6 +81,18 @@
}
]
}
],
"@typescript-eslint/no-restricted-imports": [
"error",
{
"paths": [
{
"name": "@ethersproject/providers",
"message": "Please only use Providers instantiated in constants/providers to improve traceability.",
"allowTypeImports": true
}
]
}
]
}
}

6
.gitignore vendored
View File

@@ -1,12 +1,16 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# generated contract types
/src/types/v3
/src/abis/types
/src/locales/**/*.js
/src/locales/**/en-US.po
/src/locales/**/pseudo.po
/src/state/data/generated.ts
# generated graphql types
__generated__/
schema.graphql
# dependencies
/node_modules

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
engine-strict = true

View File

@@ -1 +1 @@
/src/state/data/generated.ts
/src/schema/schema.graphql

View File

@@ -30,7 +30,7 @@ or visit [app.uniswap.org](https://app.uniswap.org).
Check out `useUnsupportedTokenList()` in [src/state/lists/hooks.ts](./src/state/lists/hooks.ts) for blocking tokens in your instance of the interface.
You can block an entire list of tokens by passing in a tokenlist like [here](./src/constants/lists.ts) or you can block specific tokens by adding them to [unsupported.tokenlist.json](./src/constants/tokenLists/unsupported.tokenlist.json).
You can block an entire list of tokens by passing in a tokenlist like [here](./src/constants/lists.ts)
## Contributions

View File

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

View File

@@ -1,13 +1,21 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const { VanillaExtractPlugin } = require('@vanilla-extract/webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { DefinePlugin } = require('webpack')
const commitHash = require('child_process').execSync('git rev-parse HEAD')
module.exports = {
babel: {
plugins: ['@vanilla-extract/babel-plugin'],
},
webpack: {
plugins: [new VanillaExtractPlugin()],
plugins: [
new VanillaExtractPlugin(),
new DefinePlugin({
'process.env.REACT_APP_GIT_COMMIT_HASH': JSON.stringify(commitHash.toString()),
}),
],
configure: (webpackConfig) => {
const instanceOfMiniCssExtractPlugin = webpackConfig.plugins.find(
(plugin) => plugin instanceof MiniCssExtractPlugin

View File

@@ -37,8 +37,8 @@ describe('Add Liquidity', () => {
it('loads fee tier distribution', () => {
cy.fixture('feeTierDistribution.json').then((feeTierDistribution) => {
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req: CyHttpMessages.IncomingHttpRequest) => {
if (hasQuery(req, 'feeTierDistribution')) {
req.alias = 'feeTierDistributionQuery'
if (hasQuery(req, 'FeeTierDistributionQuery')) {
req.alias = 'FeeTierDistributionQuery'
req.reply({
body: {
@@ -55,7 +55,7 @@ describe('Add Liquidity', () => {
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85/0xc778417E063141139Fce010982780140Aa0cD5Ab')
cy.wait('@feeTierDistributionQuery')
cy.wait('@FeeTierDistributionQuery')
cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.3% fee tier')
cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '40%')

View File

@@ -0,0 +1,50 @@
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
import { getTestSelector } from '../utils'
describe('Wallet Dropdown', () => {
before(() => {
cy.visit('/', { featureFlags: [FeatureFlag.navBar, FeatureFlag.tokenSafety] })
})
it('should change the theme', () => {
cy.get(getTestSelector('web3-status-connected')).click()
cy.get(getTestSelector('wallet-select-theme')).click()
cy.get(getTestSelector('wallet-select-theme')).contains('Light theme').should('exist')
})
it('should select a language', () => {
cy.get(getTestSelector('wallet-select-language')).click()
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
cy.get(getTestSelector('wallet-header')).should('contain', 'Taal')
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
cy.get(getTestSelector('wallet-back')).click()
})
it('should be able to view transactions', () => {
cy.get(getTestSelector('wallet-transactions')).click()
cy.get(getTestSelector('wallet-empty-transaction-text')).should('exist')
cy.get(getTestSelector('wallet-back')).click()
})
it('should change the theme when not connected', () => {
cy.get(getTestSelector('wallet-disconnect')).click()
cy.get(getTestSelector('wallet-select-theme')).click()
cy.get(getTestSelector('wallet-select-theme')).contains('Dark theme').should('exist')
})
it('should select a language when not connected', () => {
cy.get(getTestSelector('wallet-select-language')).click()
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
cy.get(getTestSelector('wallet-header')).should('contain', 'Taal')
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
cy.get(getTestSelector('wallet-back')).click()
})
it('should open the wallet connect modal from the drop down when not connected', () => {
cy.get(getTestSelector('wallet-connect-wallet')).click()
cy.get(getTestSelector('wallet-modal')).should('exist')
cy.get(getTestSelector('wallet-modal-close')).click()
})
})

View File

@@ -24,6 +24,7 @@ describe('Wallet', () => {
})
it('shows connect buttons after disconnect', () => {
cy.get('[data-testid=web3-status-connected]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED).click()
cy.contains('Disconnect').click()
cy.get('[data-testid=option-grid]').should('exist')
})

View File

@@ -9,6 +9,8 @@
import { injected } from './ethereum'
import assert = require('assert')
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
@@ -17,6 +19,7 @@ declare global {
}
interface VisitOptions {
serviceWorker?: true
featureFlags?: Array<FeatureFlag>
}
}
}
@@ -35,6 +38,19 @@ Cypress.Commands.overwrite(
onBeforeLoad(win) {
options?.onBeforeLoad?.(win)
win.localStorage.clear()
win.localStorage.setItem('redux_localstorage_simple_user', '{"selectedWallet":"INJECTED"}')
if (options?.featureFlags) {
const featureFlags = options.featureFlags.reduce(
(flags, flag) => ({
...flags,
[flag]: 'enabled',
}),
{}
)
win.localStorage.setItem('featureFlags', JSON.stringify(featureFlags))
}
win.ethereum = injected
},
})

View File

@@ -3,6 +3,7 @@
*/
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { JsonRpcProvider } from '@ethersproject/providers'
import { Wallet } from '@ethersproject/wallet'

1
cypress/utils/index.ts Normal file
View File

@@ -0,0 +1 @@
export const getTestSelector = (selectorId: string) => `[data-testid=${selectorId}]`

14
fetch-schema.js Normal file
View File

@@ -0,0 +1,14 @@
/* eslint-disable */
require('dotenv').config({ path: '.env.production' })
const { exec } = require('child_process')
const dataConfig = require('./relay.config')
const thegraphConfig = require('./relay_thegraph.config')
/* eslint-enable */
const THEGRAPH_API_URL = 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'
exec(`get-graphql-schema ${THEGRAPH_API_URL} > ${thegraphConfig.schema}`)
console.log(process.env.REACT_APP_AWS_API_ENDPOINT)
exec(
`get-graphql-schema --h Origin=https://app.uniswap.org ${process.env.REACT_APP_AWS_API_ENDPOINT} > ${dataConfig.schema}`
)

View File

@@ -8,19 +8,23 @@
"contracts:compile:abi": "typechain --target ethers-v5 --out-dir src/abis/types \"./src/abis/**/*.json\"",
"contracts:compile:v3": "typechain --target ethers-v5 --out-dir src/types/v3 \"./node_modules/@uniswap/**/artifacts/contracts/**/*[!dbg].json\"",
"contracts:compile": "yarn contracts:compile:abi && yarn contracts:compile:v3",
"graphql:generate": "graphql-codegen --config codegen.yml",
"relay": "relay-compiler relay.config.js",
"relay-thegraph": "relay-compiler relay_thegraph.config.js",
"graphql:fetch": "node fetch-schema.js",
"graphql:generate": "yarn relay && yarn relay-thegraph",
"prei18n:extract": "node prei18n-extract.js",
"i18n:extract": "lingui extract --locale en-US",
"i18n:compile": "yarn i18n:extract && lingui compile",
"i18n:pseudo": "lingui extract --locale pseudo && lingui compile",
"prepare": "yarn contracts:compile && yarn graphql:generate && yarn i18n:compile",
"prepare": "yarn contracts:compile && yarn graphql:fetch && yarn graphql:generate && yarn i18n:compile",
"start": "craco start",
"build": "craco build",
"serve": "serve build -l 3000",
"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": [
@@ -57,10 +61,6 @@
"devDependencies": {
"@craco/craco": "6.4.3",
"@ethersproject/experimental": "^5.4.0",
"@graphql-codegen/cli": "1.21.5",
"@graphql-codegen/typescript": "1.22.3",
"@graphql-codegen/typescript-operations": "^1.18.2",
"@graphql-codegen/typescript-rtk-query": "^1.1.1",
"@lingui/cli": "^3.9.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.1",
@@ -80,6 +80,7 @@
"@types/react-dom": "^18.0.6",
"@types/react-redux": "^7.1.24",
"@types/react-router-dom": "^5.3.3",
"@types/react-table": "^7.7.12",
"@types/react-virtualized-auto-sizer": "^1.0.0",
"@types/react-window": "^1.8.2",
"@types/rebass": "^4.0.7",
@@ -90,6 +91,9 @@
"@types/wcag-contrast": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^4",
"@typescript-eslint/parser": "^4",
"@vanilla-extract/babel-plugin": "^1.1.7",
"@vanilla-extract/webpack-plugin": "^2.1.11",
"babel-plugin-relay": "^14.1.0",
"cypress": "^10.3.1",
"env-cmd": "^10.1.0",
"eslint": "^7.11.0",
@@ -102,17 +106,17 @@
"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",
"serve": "^11.3.2",
"typechain": "^5.0.0",
"typescript": "^4.4.3",
"@vanilla-extract/babel-plugin": "^1.1.7",
"@vanilla-extract/webpack-plugin": "^2.1.11"
"typescript": "^4.4.3"
},
"dependencies": {
"@amplitude/analytics-browser": "^0.5.1",
"@amplitude/experiment-js-client": "^1.5.4",
"@amplitude/analytics-browser": "^1.1.4",
"@coinbase/wallet-sdk": "^3.3.0",
"@fontsource/ibm-plex-mono": "^4.5.1",
"@fontsource/inter": "^4.5.1",
@@ -127,13 +131,14 @@
"@reach/portal": "^0.10.3",
"@react-hook/window-scroll": "^1.3.0",
"@reduxjs/toolkit": "^1.6.1",
"@types/react-relay": "^13.0.2",
"@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "1.0.1",
"@uniswap/redux-multicall": "^1.1.5",
"@uniswap/router-sdk": "^1.0.6",
"@uniswap/router-sdk": "^1.3.0",
"@uniswap/sdk-core": "^3.0.1",
"@uniswap/smart-order-router": "^2.8.2",
"@uniswap/smart-order-router": "^2.10.0",
"@uniswap/token-lists": "^1.0.0-beta.30",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
@@ -141,6 +146,7 @@
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "^1.1.1",
"@uniswap/v3-sdk": "^3.9.0",
"@uniswap/widgets": "^2.8.1",
"@vanilla-extract/css": "^1.7.2",
"@vanilla-extract/css-utils": "^0.1.2",
"@vanilla-extract/dynamic": "^2.0.2",
@@ -169,12 +175,11 @@
"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",
"fortmatic": "^2.4.0",
"graphql": "^15.5.0",
"get-graphql-schema": "^2.1.2",
"graphql": "^16.5.0",
"graphql-request": "^3.4.0",
"immer": "^9.0.6",
"inter-ui": "^3.13.1",
@@ -194,11 +199,13 @@
"react-dom": "^18.2.0",
"react-feather": "^2.0.8",
"react-ga4": "^1.4.1",
"react-infinite-scroll-component": "^6.1.0",
"react-is": "^17.0.2",
"react-markdown": "^4.3.1",
"react-popper": "^2.2.3",
"react-query": "^3.39.1",
"react-redux": "^8.0.2",
"react-relay": "^14.1.0",
"react-router-dom": "^6.3.0",
"react-spring": "^8.0.27",
"react-table": "^7.8.0",
@@ -223,5 +230,10 @@
"workbox-precaching": "^6.1.0",
"workbox-routing": "^6.1.0",
"zustand": "^4.0.0-rc.1"
},
"engines": {
"npm": "please-use-yarn",
"node": "14",
"yarn": ">=1.22"
}
}

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);
+ }
}
}

View File

@@ -1,5 +1,5 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const exec = require('child_process').exec
const { exec } = require('child_process')
const isWindows = process.platform === 'win32' || /^(msys|cygwin)$/.test(process.env.OSTYPE)
if (isWindows) {

View File

@@ -16,7 +16,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#ff007a" />
<meta name="fortmatic-site-verification" content="j93LgcVZk79qcgyo" />
<!--
manifest.json provides metadata used when the app is installed as a PWA.
@@ -71,7 +70,6 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
font-feature-settings: 'ss01' on, 'ss02' on, 'cv01' on, 'cv03' on;
}
#background-radial-gradient {

6
relay.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
src: './src',
language: 'typescript',
schema: './src/graphql/data/schema.graphql',
exclude: ['**/node_modules/**', '**/__mocks__/**', '**/__generated__/**', '**/thegraph/**'],
}

9
relay_thegraph.config.js Normal file
View File

@@ -0,0 +1,9 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const defaultConfig = require('./relay.config')
module.exports = {
src: defaultConfig.src,
language: defaultConfig.language,
schema: './src/graphql/thegraph/schema.graphql',
exclude: ['**/node_modules/**', '**/__mocks__/**', '**/__generated__/**', '**/data/**'],
}

View File

@@ -44,7 +44,14 @@ export const Trace = memo(
useEffect(() => {
if (shouldLogImpression) {
sendAnalyticsEvent(name ?? EventName.PAGE_VIEWED, { ...combinedProps, ...properties })
const origin = window.location.origin
const commitHash = process.env.REACT_APP_GIT_COMMIT_HASH
sendAnalyticsEvent(name ?? EventName.PAGE_VIEWED, {
...combinedProps,
...properties,
origin,
git_commit_hash: commitHash,
})
}
// Impressions should only be logged on mount.
// eslint-disable-next-line react-hooks/exhaustive-deps

View File

@@ -8,6 +8,7 @@ export enum EventName {
APP_LOADED = 'Application Loaded',
APPROVE_TOKEN_TXN_SUBMITTED = 'Approve Token Transaction Submitted',
CONNECT_WALLET_BUTTON_CLICKED = 'Connect Wallet Button Clicked',
EXPLORE_SEARCH_SELECTED = 'Explore Search Selected',
EXPLORE_TOKEN_ROW_CLICKED = 'Explore Token Row Clicked',
PAGE_VIEWED = 'Page Viewed',
SWAP_AUTOROUTER_VISUALIZATION_EXPANDED = 'Swap Autorouter Visualization Expanded',
@@ -15,7 +16,8 @@ export enum EventName {
SWAP_MAX_TOKEN_AMOUNT_SELECTED = 'Swap Max Token Amount Selected',
SWAP_PRICE_UPDATE_ACKNOWLEDGED = 'Swap Price Update Acknowledged',
SWAP_QUOTE_RECEIVED = 'Swap Quote Received',
SWAP_SUBMITTED = 'Swap Submitted',
SWAP_SIGNED = 'Swap Signed',
SWAP_SUBMITTED_BUTTON_CLICKED = 'Swap Submit Button Clicked',
SWAP_TOKENS_REVERSED = 'Swap Tokens Reversed',
SWAP_TRANSACTION_COMPLETED = 'Swap Transaction Completed',
TOKEN_IMPORTED = 'Token Imported',
@@ -23,6 +25,8 @@ export enum EventName {
TOKEN_SELECTOR_OPENED = 'Token Selector Opened',
WALLET_CONNECT_TXN_COMPLETED = 'Wallet Connect Transaction Completed',
WALLET_SELECTED = 'Wallet Selected',
WEB_VITALS = 'Web Vitals',
WRAP_TOKEN_TXN_INVALIDATED = 'Wrap Token Transaction Invalidated',
WRAP_TOKEN_TXN_SUBMITTED = 'Wrap Token Transaction Submitted',
// alphabetize additional event names.
}
@@ -30,22 +34,16 @@ export enum EventName {
export enum CUSTOM_USER_PROPERTIES {
ALL_WALLET_ADDRESSES_CONNECTED = 'all_wallet_addresses_connected',
ALL_WALLET_CHAIN_IDS = 'all_wallet_chain_ids',
USER_AGENT = 'user_agent',
BROWSER = 'browser',
DARK_MODE = 'is_dark_mode',
EXPERT_MODE = 'is_expert_mode',
SCREEN_RESOLUTION_HEIGHT = 'screen_resolution_height',
SCREEN_RESOLUTION_WIDTH = 'screen_resolution_width',
WALLET_ADDRESS = 'wallet_address',
WALLET_NATIVE_CURRENCY_BALANCE_USD = 'wallet_native_currency_balance_usd',
WALLET_TOKENS_ADDRESSES = 'wallet_tokens_addresses',
WALLET_TOKENS_SYMBOLS = 'wallet_tokens_symbols',
WALLET_TYPE = 'wallet_type',
}
export enum CUSTOM_USER_PROPERTY_SUFFIXES {
WALLET_TOKEN_AMOUNT_SUFFIX = '_token_amount',
}
export enum BROWSER {
FIREFOX = 'Mozilla Firefox',
SAMSUNG = 'Samsung Internet',
@@ -55,6 +53,7 @@ export enum BROWSER {
EDGE_CHROMIUM = 'Microsoft Edge (Chromium)',
CHROME = 'Google Chrome or Chromium',
SAFARI = 'Apple Safari',
BRAVE = 'Brave',
UNKNOWN = 'unknown',
}
@@ -74,7 +73,7 @@ export enum SWAP_PRICE_UPDATE_USER_RESPONSE {
* Known pages in the app. Highest order context.
*/
export enum PageName {
EXPLORE_PAGE = 'explore-page',
TOKENS_PAGE = 'tokens-page',
POOL_PAGE = 'pool-page',
SWAP_PAGE = 'swap-page',
VOTE_PAGE = 'vote-page',
@@ -108,6 +107,7 @@ export enum ElementName {
COMMON_BASES_CURRENCY_BUTTON = 'common-bases-currency-button',
CONFIRM_SWAP_BUTTON = 'confirm-swap-or-send',
CONNECT_WALLET_BUTTON = 'connect-wallet-button',
EXPLORE_SEARCH_INPUT = 'explore_search_input',
IMPORT_TOKEN_BUTTON = 'import-token-button',
MAX_TOKEN_AMOUNT_BUTTON = 'max-token-amount-button',
PRICE_UPDATE_ACCEPT_BUTTON = 'price-update-accept-button',

View File

@@ -1,5 +1,7 @@
import { Identify, identify, init, track } from '@amplitude/analytics-browser'
import { isDevelopmentEnv } from 'utils/env'
import { isProductionEnv } from 'utils/env'
const API_KEY = isProductionEnv() ? process.env.REACT_APP_AMPLITUDE_KEY : process.env.REACT_APP_AMPLITUDE_TEST_KEY
/**
* Initializes Amplitude with API key for project.
@@ -8,24 +10,20 @@ import { isDevelopmentEnv } from 'utils/env'
* member of the organization on Amplitude to view details.
*/
export function initializeAnalytics() {
if (isDevelopmentEnv()) return
const API_KEY = process.env.REACT_APP_AMPLITUDE_KEY
if (typeof API_KEY === 'undefined') {
throw new Error(`REACT_APP_AMPLITUDE_KEY must be a defined environment variable`)
const keyName = isProductionEnv() ? 'REACT_APP_AMPLITUDE_KEY' : 'REACT_APP_AMPLITUDE_TEST_KEY'
console.error(`${keyName} is undefined, Amplitude analytics will not run.`)
return
}
init(
API_KEY,
/* userId= */ undefined, // User ID should be undefined to let Amplitude default to Device ID
/* options= */ {
// See documentation: https://www.docs.developers.amplitude.com/data/sdks/javascript/#track-referrers
includeReferrer: true,
// See documentation: https://www.docs.developers.amplitude.com/data/sdks/javascript/#track-utm-parameters
includeUtm: true,
/* options= */
{
// Disable tracking of private user information by Amplitude
trackingOptions: {
ipAddress: false,
// IP is being dropped before ingestion on Amplitude side, only being used to determine country.
ipAddress: isProductionEnv() ? false : true,
carrier: false,
city: false,
region: false,
@@ -37,8 +35,8 @@ export function initializeAnalytics() {
/** Sends an event to Amplitude. */
export function sendAnalyticsEvent(eventName: string, eventProperties?: Record<string, unknown>) {
if (isDevelopmentEnv()) {
console.log(`[amplitude(${eventName})]: ${JSON.stringify(eventProperties)}`)
if (!API_KEY) {
console.log(`[analytics(${eventName})]: ${JSON.stringify(eventProperties)}`)
return
}
@@ -60,7 +58,7 @@ class UserModel {
}
private call(mutate: (event: Identify) => Identify) {
if (isDevelopmentEnv()) {
if (!isProductionEnv()) {
const log = (_: Identify, method: string) => this.log.bind(this, method)
mutate(new Proxy(new Identify(), { get: log }))
return

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

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

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="#fff">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4m14-7-5-5-5 5m5-5v12"/>
</svg>

After

Width:  |  Height:  |  Size: 257 B

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

@@ -0,0 +1,186 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20">
<g filter="url(#a)">
<path fill="url(#b)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
<path fill="url(#c)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
<path fill="url(#d)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
<path fill="url(#e)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
<path fill="url(#f)" d="M17.654 4.868c0-.69-.56-1.25-1.25-1.25h-2.932c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.741 1.873c-.898.999-1.115 2.687-.058 3.748 1.013 1.017 2.918.988 3.87 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.158v-7.55Z"/>
</g>
<g filter="url(#g)">
<path fill="url(#h)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
<path fill="url(#i)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
<path fill="url(#j)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
<path fill="url(#k)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
<path fill="url(#l)" d="M10.954 2.602c0-.69-.56-1.25-1.25-1.25H6.773c-.69 0-1.25.56-1.25 1.25v5.704a2.5 2.5 0 0 1-.67 1.703l-1.742 1.873c-.898 1-1.115 2.687-.058 3.748 1.014 1.018 2.919.988 3.871 0l3.167-3.32a3.125 3.125 0 0 0 .864-2.157V2.602Z"/>
</g>
<path fill="url(#m)" d="M17.654 5.776h-5.431v.673h5.431v-.673Z"/>
<path fill="url(#n)" d="M17.654 5.776h-5.431v.673h5.431v-.673Z"/>
<path fill="url(#o)" d="M10.955 3.51H5.523v.674h5.432V3.51Z"/>
<path fill="url(#p)" d="M10.955 3.51H5.523v.674h5.432V3.51Z"/>
<path fill="url(#q)" d="M17.606 4.523c.031.11.048.225.048.345v.328h-5.431v-.328c0-.12.016-.236.048-.345h5.335Z"/>
<path fill="url(#r)" d="M17.606 4.523c.031.11.048.225.048.345v.328h-5.431v-.328c0-.12.016-.236.048-.345h5.335Z"/>
<path fill="url(#s)" d="M10.907 2.257c.031.11.048.225.048.345v.329H5.523v-.329c0-.12.017-.235.049-.345h5.335Z"/>
<path fill="url(#t)" d="M10.907 2.257c.031.11.048.225.048.345v.329H5.523v-.329c0-.12.017-.235.049-.345h5.335Z"/>
<g filter="url(#u)">
<path fill="url(#v)" d="M17.654 12.236h-3.116a.173.173 0 0 0-.176.17c0 1.255.904 2.3 2.096 2.518l.332-.349a3.125 3.125 0 0 0 .864-2.157v-.182Z"/>
</g>
<g filter="url(#w)">
<path fill="url(#x)" d="M10.955 9.97H7.839a.173.173 0 0 0-.176.17c0 1.255.903 2.3 2.096 2.518l.332-.349a3.125 3.125 0 0 0 .864-2.156V9.97Z"/>
</g>
<g filter="url(#y)">
<path fill="url(#z)" d="M13.243 18.21v-.05a3.424 3.424 0 0 0-3.856-3.397c-.511.986-.496 2.268.366 3.133.892.895 2.474.98 3.49.314Z"/>
</g>
<g filter="url(#A)">
<path fill="url(#B)" d="M6.544 15.944v-.05a3.424 3.424 0 0 0-3.857-3.396c-.51.985-.495 2.267.366 3.132.892.896 2.475.98 3.49.314Z"/>
</g>
<defs>
<linearGradient id="b" x1="17.248" x2="13.193" y1="4.4" y2="16.827" gradientUnits="userSpaceOnUse">
<stop stop-color="#FDF2FF"/>
<stop offset="1" stop-color="#FFECFB"/>
</linearGradient>
<linearGradient id="c" x1="17.654" x2="15.615" y1="11.854" y2="11.854" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFCCF1"/>
<stop offset="1" stop-color="#FFC5EF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="f" x1="14.99" x2="13.818" y1="16.932" y2="15.721" gradientUnits="userSpaceOnUse">
<stop offset=".209" stop-color="#EDB0DF"/>
<stop offset="1" stop-color="#ECAAED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="h" x1="10.549" x2="6.493" y1="2.134" y2="14.562" gradientUnits="userSpaceOnUse">
<stop stop-color="#FDF2FF"/>
<stop offset="1" stop-color="#FFECFB"/>
</linearGradient>
<linearGradient id="i" x1="10.954" x2="8.915" y1="9.588" y2="9.588" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFCCF1"/>
<stop offset="1" stop-color="#FFC5EF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="l" x1="8.29" x2="7.119" y1="14.666" y2="13.456" gradientUnits="userSpaceOnUse">
<stop offset=".209" stop-color="#EDB0DF"/>
<stop offset="1" stop-color="#ECAAED" stop-opacity="0"/>
</linearGradient>
<linearGradient id="m" x1="12.773" x2="16.915" y1="6.449" y2="6.449" gradientUnits="userSpaceOnUse">
<stop stop-color="#E95FDB"/>
<stop offset="1" stop-color="#FF46CB"/>
</linearGradient>
<linearGradient id="n" x1="12.223" x2="12.793" y1="6.449" y2="6.449" gradientUnits="userSpaceOnUse">
<stop stop-color="#9F4977"/>
<stop offset="1" stop-color="#CA5284" stop-opacity="0"/>
</linearGradient>
<linearGradient id="o" x1="6.074" x2="10.216" y1="4.184" y2="4.184" gradientUnits="userSpaceOnUse">
<stop stop-color="#E95FDB"/>
<stop offset="1" stop-color="#FF46CB"/>
</linearGradient>
<linearGradient id="p" x1="5.523" x2="6.094" y1="4.184" y2="4.184" gradientUnits="userSpaceOnUse">
<stop stop-color="#9F4977"/>
<stop offset="1" stop-color="#CA5284" stop-opacity="0"/>
</linearGradient>
<linearGradient id="q" x1="12.773" x2="16.915" y1="5.196" y2="5.196" gradientUnits="userSpaceOnUse">
<stop stop-color="#E95FDB"/>
<stop offset="1" stop-color="#FF46CB"/>
</linearGradient>
<linearGradient id="r" x1="12.223" x2="12.793" y1="5.196" y2="5.196" gradientUnits="userSpaceOnUse">
<stop stop-color="#9F4977"/>
<stop offset="1" stop-color="#CA5284" stop-opacity="0"/>
</linearGradient>
<linearGradient id="s" x1="6.074" x2="10.216" y1="2.931" y2="2.931" gradientUnits="userSpaceOnUse">
<stop stop-color="#E95FDB"/>
<stop offset="1" stop-color="#FF46CB"/>
</linearGradient>
<linearGradient id="t" x1="5.523" x2="6.094" y1="2.931" y2="2.931" gradientUnits="userSpaceOnUse">
<stop stop-color="#9F4977"/>
<stop offset="1" stop-color="#CA5284" stop-opacity="0"/>
</linearGradient>
<radialGradient id="d" cx="0" cy="0" r="1" gradientTransform="matrix(2.8125 0 0 6.01563 12.02 8.026)" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFD1F5"/>
<stop offset="1" stop-color="#FECAFF" stop-opacity="0"/>
</radialGradient>
<radialGradient id="e" cx="0" cy="0" r="1" gradientTransform="matrix(1.54348 -1.53472 2.30642 2.31958 11.806 16.12)" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFC0FC"/>
<stop offset="1" stop-color="#FFBCFC" stop-opacity="0"/>
</radialGradient>
<radialGradient id="j" cx="0" cy="0" r="1" gradientTransform="matrix(2.8125 0 0 6.01563 5.322 5.76)" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFD1F5"/>
<stop offset="1" stop-color="#FECAFF" stop-opacity="0"/>
</radialGradient>
<radialGradient id="k" cx="0" cy="0" r="1" gradientTransform="matrix(1.54347 -1.53473 2.30643 2.31957 5.107 13.854)" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFC0FC"/>
<stop offset="1" stop-color="#FFBCFC" stop-opacity="0"/>
</radialGradient>
<radialGradient id="v" cx="0" cy="0" r="1" gradientTransform="matrix(-.42911 2.50572 -2.20218 -.37713 16.437 12.236)" gradientUnits="userSpaceOnUse">
<stop offset=".147" stop-color="#FF52CF"/>
<stop offset="1" stop-color="#FB3FFF"/>
</radialGradient>
<radialGradient id="x" cx="0" cy="0" r="1" gradientTransform="matrix(-.42911 2.50572 -2.20218 -.37713 9.738 9.97)" gradientUnits="userSpaceOnUse">
<stop offset=".147" stop-color="#FF52CF"/>
<stop offset="1" stop-color="#FB3FFF"/>
</radialGradient>
<radialGradient id="z" cx="0" cy="0" r="1" gradientTransform="rotate(167.471 5.464 9.437) scale(4.54024 4.83245)" gradientUnits="userSpaceOnUse">
<stop offset=".268" stop-color="#FF4EE3"/>
<stop offset=".92" stop-color="#D12396"/>
</radialGradient>
<radialGradient id="B" cx="0" cy="0" r="1" gradientTransform="rotate(167.471 2.239 7.937) scale(4.54024 4.83245)" gradientUnits="userSpaceOnUse">
<stop offset=".268" stop-color="#FF4EE3"/>
<stop offset=".92" stop-color="#D12396"/>
</radialGradient>
<filter id="a" width="8.808" height="15.23" x="9.045" y="3.418" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx=".2" dy="-.2"/>
<feGaussianBlur stdDeviation=".3"/>
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
<feColorMatrix values="0 0 0 0 0.522043 0 0 0 0 0.119948 0 0 0 0 0.5875 0 0 0 1 0"/>
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
</filter>
<filter id="g" width="8.808" height="15.23" x="2.346" y="1.152" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx=".2" dy="-.2"/>
<feGaussianBlur stdDeviation=".3"/>
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
<feColorMatrix values="0 0 0 0 0.545098 0 0 0 0 0.219608 0 0 0 0 0.512549 0 0 0 1 0"/>
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
</filter>
<filter id="u" width="3.541" height="2.688" x="14.112" y="12.236" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx="-.25"/>
<feGaussianBlur stdDeviation=".25"/>
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
<feColorMatrix values="0 0 0 0 0.976471 0 0 0 0 0.145098 0 0 0 0 0.743686 0 0 0 1 0"/>
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
</filter>
<filter id="w" width="3.541" height="2.688" x="7.413" y="9.97" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx="-.25"/>
<feGaussianBlur stdDeviation=".25"/>
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
<feColorMatrix values="0 0 0 0 0.976471 0 0 0 0 0.145098 0 0 0 0 0.743686 0 0 0 1 0"/>
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
</filter>
<filter id="y" width="4.448" height="4.112" x="9.045" y="14.536" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx=".25" dy="-.2"/>
<feGaussianBlur stdDeviation=".25"/>
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
<feColorMatrix values="0 0 0 0 0.906118 0 0 0 0 0.329412 0 0 0 0 1 0 0 0 1 0"/>
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
</filter>
<filter id="A" width="4.448" height="4.112" x="2.346" y="12.27" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dx=".25" dy="-.2"/>
<feGaussianBlur stdDeviation=".25"/>
<feComposite in2="hardAlpha" k2="-1" k3="1" operator="arithmetic"/>
<feColorMatrix values="0 0 0 0 0.906118 0 0 0 0 0.329412 0 0 0 0 1 0 0 0 1 0"/>
<feBlend in2="shape" result="effect1_innerShadow_6126_86420"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#99A1BD" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>

Before

Width:  |  Height:  |  Size: 294 B

After

Width:  |  Height:  |  Size: 300 B

View File

@@ -1,64 +0,0 @@
import { Trans } from '@lingui/macro'
import useCopyClipboard from 'hooks/useCopyClipboard'
import React, { useCallback } from 'react'
import { CheckCircle, Copy } from 'react-feather'
import styled from 'styled-components/macro'
import { LinkStyledButton } from 'theme'
const CopyIcon = styled(LinkStyledButton)`
color: ${({ color, theme }) => color || theme.accentAction};
flex-shrink: 0;
display: flex;
text-decoration: none;
:hover,
:active,
:focus {
text-decoration: none;
color: ${({ color, theme }) => color || theme.accentAction};
}
`
const StyledText = styled.span`
margin-left: 0.25rem;
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
`
const Copied = ({ iconSize }: { iconSize?: number }) => (
<StyledText>
<CheckCircle size={iconSize ?? '16'} />
<StyledText>
<Trans>Copied</Trans>
</StyledText>
</StyledText>
)
const Icon = ({ iconSize }: { iconSize?: number }) => (
<StyledText>
<Copy size={iconSize ?? '16'} />
</StyledText>
)
interface BaseProps {
toCopy: string
color?: string
iconSize?: number
iconPosition?: 'left' | 'right'
}
export type CopyHelperProps = BaseProps & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, keyof BaseProps>
export default function CopyHelper({ color, toCopy, children, iconSize, iconPosition }: CopyHelperProps) {
const [isCopied, setCopied] = useCopyClipboard()
const copy = useCallback(() => {
setCopied(toCopy)
}, [toCopy, setCopied])
return (
<CopyIcon onClick={copy} color={color}>
{iconPosition === 'left' ? isCopied ? <Copied iconSize={iconSize} /> : <Icon iconSize={iconSize} /> : null}
{iconPosition === 'left' && <>&nbsp;</>}
{isCopied ? '' : children}
{iconPosition === 'right' && <>&nbsp;</>}
{iconPosition === 'right' ? isCopied ? <Copied iconSize={iconSize} /> : <Icon iconSize={iconSize} /> : null}
</CopyIcon>
)
}

View File

@@ -1,19 +1,17 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import CopyHelper from 'components/AccountDetails/Copy'
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsMetaMask } from 'connection/utils'
import { Context, useCallback, useContext } from 'react'
import { useCallback } from 'react'
import { ExternalLink as LinkIcon } from 'react-feather'
import { useAppDispatch } from 'state/hooks'
import { updateSelectedWallet } from 'state/user/reducer'
import { removeConnectedWallet } from 'state/wallets/reducer'
import { DefaultTheme } from 'styled-components/macro'
import styled, { ThemeContext } from 'styled-components/macro'
import styled, { useTheme } from 'styled-components/macro'
import { isMobile } from 'utils/userAgent'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { clearAllTransactions } from '../../state/transactions/reducer'
import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
import { CopyHelper, ExternalLink, LinkStyledButton, ThemedText } from '../../theme'
import { shortenAddress } from '../../utils'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
import { ButtonSecondary } from '../Button'
@@ -26,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;
`};
`
@@ -76,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`
@@ -129,12 +127,13 @@ const AccountControl = styled.div`
}
`
const AddressLink = styled(ExternalLink)<{ hasENS: boolean; isENS: boolean }>`
font-size: 0.825rem;
const AddressLink = styled(ExternalLink)`
color: ${({ theme }) => theme.deprecated_text3};
margin-left: 1rem;
font-size: 0.825rem;
display: flex;
gap: 6px;
text-decoration: none !important;
:hover {
color: ${({ theme }) => theme.deprecated_text2};
}
@@ -146,7 +145,7 @@ const CloseIcon = styled.div`
top: 14px;
&:hover {
cursor: pointer;
opacity: 0.6;
opacity: ${({ theme }) => theme.opacity.hover};
}
`
@@ -207,7 +206,7 @@ export default function AccountDetails({
const { chainId, account, connector } = useWeb3React()
const connectionType = getConnection(connector).type
const theme = useContext(ThemeContext as Context<DefaultTheme>)
const theme = useTheme()
const dispatch = useAppDispatch()
const isMetaMask = getIsMetaMask()
@@ -281,59 +280,21 @@ export default function AccountDetails({
</AccountControl>
</AccountGroupingRow>
<AccountGroupingRow>
{ENSName ? (
<>
<AccountControl>
<div>
{account && (
<CopyHelper toCopy={account} iconPosition="left">
<span style={{ marginLeft: '4px' }}>
<Trans>Copy Address</Trans>
</span>
</CopyHelper>
)}
{chainId && account && (
<AddressLink
hasENS={!!ENSName}
isENS={true}
href={getExplorerLink(chainId, ENSName, ExplorerDataType.ADDRESS)}
>
<LinkIcon size={16} />
<span style={{ marginLeft: '4px' }}>
<Trans>View on Explorer</Trans>
</span>
</AddressLink>
)}
</div>
</AccountControl>
</>
) : (
<>
<AccountControl>
<div>
{account && (
<CopyHelper toCopy={account} iconPosition="left">
<span style={{ marginLeft: '4px' }}>
<Trans>Copy Address</Trans>
</span>
</CopyHelper>
)}
{chainId && account && (
<AddressLink
hasENS={!!ENSName}
isENS={false}
href={getExplorerLink(chainId, account, ExplorerDataType.ADDRESS)}
>
<LinkIcon size={16} />
<span style={{ marginLeft: '4px' }}>
<Trans>View on Explorer</Trans>
</span>
</AddressLink>
)}
</div>
</AccountControl>
</>
)}
<AccountControl>
<div>
{account && (
<CopyHelper toCopy={account} gap={6} iconSize={16} fontSize={14}>
<Trans>Copy Address</Trans>
</CopyHelper>
)}
{chainId && account && (
<AddressLink href={getExplorerLink(chainId, ENSName ?? account, ExplorerDataType.ADDRESS)}>
<LinkIcon size={16} />
<Trans>View on Explorer</Trans>
</AddressLink>
)}
</div>
</AccountControl>
</AccountGroupingRow>
</InfoCard>
</YourAccount>

View File

@@ -0,0 +1,88 @@
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'
const CurrencyWrap = styled.div`
position: relative;
width: 36px;
height: 36px;
`
const CurrencyWrapStyles = css`
position: absolute;
height: 24px;
`
const CurrencyLogoWrap = styled.span<{ isCentered: boolean }>`
${CurrencyWrapStyles};
left: ${({ isCentered }) => (isCentered ? '50%' : '0')};
top: ${({ isCentered }) => (isCentered ? '50%' : '0')};
transform: ${({ isCentered }) => isCentered && 'translate(-50%, -50%)'};
`
const CurrencyLogoWrapTwo = styled.span`
${CurrencyWrapStyles};
bottom: 0px;
right: 0px;
`
interface CurrencyPair {
currencyId0: string | undefined
currencyId1: string | undefined
}
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 { chainId } = useWeb3React()
const { currencyId0, currencyId1 } = getCurrency({ info, chainId })
const currency0 = useCurrency(currencyId0)
const currency1 = useCurrency(currencyId1)
const isCentered = !(currency0 && currency1)
return (
<CurrencyWrap>
<CurrencyLogoWrap isCentered={isCentered}>
<CurrencyLogo size="24px" currency={currency0} />
</CurrencyLogoWrap>
{!isCentered && (
<CurrencyLogoWrapTwo>
<CurrencyLogo size="24px" currency={currency1} />
</CurrencyLogoWrapTwo>
)}
</CurrencyWrap>
)
}
export default LogoView

View File

@@ -0,0 +1,337 @@
import { Trans } from '@lingui/macro'
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 { 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`
color: ${({ theme }) => theme.textPrimary};
font-weight: 600;
`
const BodyWrap = styled.div`
line-height: 20px;
`
interface ActionProps {
pending: JSX.Element
success: JSX.Element
failed: JSX.Element
transactionState: TransactionState
}
const Action = ({ pending, success, failed, transactionState }: ActionProps) => {
switch (transactionState) {
case TransactionState.Failed:
return failed
case TransactionState.Success:
return success
default:
return pending
}
}
const formatAmount = (amountRaw: string, decimals: number, sigFigs: number): string =>
new Fraction(amountRaw, JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(decimals))).toSignificant(sigFigs)
const FailedText = ({ transactionState }: { transactionState: TransactionState }) =>
transactionState === TransactionState.Failed ? <Trans>failed</Trans> : <span />
const FormattedCurrencyAmount = ({
rawAmount,
currencyId,
sigFigs = 2,
}: {
rawAmount: string
currencyId: string
sigFigs: number
}) => {
const currency = useCurrency(currencyId)
return currency ? (
<HighlightText>
{formatAmount(rawAmount, currency.decimals, sigFigs)} {currency.symbol}
</HighlightText>
) : null
}
const getRawAmounts = (
info: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo
): { rawAmountFrom: string; rawAmountTo: string } => {
return info.tradeType === TradeType.EXACT_INPUT
? { rawAmountFrom: info.inputCurrencyAmountRaw, rawAmountTo: info.expectedOutputCurrencyAmountRaw }
: { rawAmountFrom: info.expectedInputCurrencyAmountRaw, rawAmountTo: info.outputCurrencyAmountRaw }
}
const SwapSummary = ({
info,
transactionState,
}: {
info: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo
transactionState: TransactionState
}) => {
const actionProps = {
transactionState,
pending: <Trans>Swapping</Trans>,
success: <Trans>Swapped</Trans>,
failed: <Trans>Swap</Trans>,
}
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>
)
}
const AddLiquidityV3PoolSummary = ({
info,
transactionState,
}: {
info: AddLiquidityV3PoolTransactionInfo
transactionState: TransactionState
}) => {
const { createPool, quoteCurrencyId, baseCurrencyId } = info
const actionProps = {
transactionState,
pending: <Trans>Adding</Trans>,
success: <Trans>Added</Trans>,
failed: <Trans>Add</Trans>,
}
return (
<BodyWrap>
{createPool ? (
<CreateV3PoolSummary info={info} transactionState={transactionState} />
) : (
<>
<Action {...actionProps} />{' '}
<FormattedCurrencyAmount rawAmount={info.expectedAmountBaseRaw} currencyId={baseCurrencyId} sigFigs={2} />{' '}
<Trans>and</Trans>{' '}
<FormattedCurrencyAmount rawAmount={info.expectedAmountQuoteRaw} currencyId={quoteCurrencyId} sigFigs={2} />
</>
)}{' '}
<FailedText transactionState={transactionState} />
</BodyWrap>
)
}
const RemoveLiquidityV3Summary = ({
info: { baseCurrencyId, quoteCurrencyId, expectedAmountBaseRaw, expectedAmountQuoteRaw },
transactionState,
}: {
info: RemoveLiquidityV3TransactionInfo
transactionState: TransactionState
}) => {
const actionProps = {
transactionState,
pending: <Trans>Removing</Trans>,
success: <Trans>Removed</Trans>,
failed: <Trans>Remove</Trans>,
}
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>
)
}
const CreateV3PoolSummary = ({
info: { baseCurrencyId, quoteCurrencyId },
transactionState,
}: {
info: AddLiquidityV3PoolTransactionInfo
transactionState: TransactionState
}) => {
const baseCurrency = useCurrency(baseCurrencyId)
const quoteCurrency = useCurrency(quoteCurrencyId)
const actionProps = {
transactionState,
pending: <Trans>Creating</Trans>,
success: <Trans>Created</Trans>,
failed: <Trans>Create</Trans>,
}
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>
)
}
const TransactionBody = ({ info, transactionState }: { info: TransactionInfo; transactionState: TransactionState }) => {
switch (info.type) {
case TransactionType.SWAP:
return <SwapSummary info={info} transactionState={transactionState} />
case TransactionType.ADD_LIQUIDITY_V3_POOL:
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 />
}
}
export default TransactionBody

View File

@@ -0,0 +1,90 @@
import { useWeb3React } from '@web3-react/core'
import { getChainInfoOrDefault } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import { useMemo } from 'react'
import { AlertTriangle, CheckCircle } from 'react-feather'
import styled from 'styled-components/macro'
import { colors } from 'theme/colors'
import { TransactionDetails } from '../../state/transactions/types'
import Loader from '../Loader'
import LogoView from './LogoView'
import TransactionBody from './TransactionBody'
export enum TransactionState {
Pending,
Success,
Failed,
}
const Grid = styled.a`
cursor: pointer;
display: grid;
grid-template-columns: 44px auto 24px;
width: 100%;
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`
font-size: 14px;
margin-top: auto;
margin-bottom: auto;
color: ${({ theme }) => theme.textTertiary};
`
const IconStyleWrap = styled.span`
margin-top: auto;
margin-bottom: auto;
margin-left: auto;
height: 16px;
`
export const TransactionSummary = ({ transactionDetails }: { transactionDetails: TransactionDetails }) => {
const { chainId = 1 } = useWeb3React()
const tx = transactionDetails
const { explorer } = getChainInfoOrDefault(chainId ? chainId : SupportedChainId.MAINNET)
const { info, receipt, hash } = tx
const transactionState = useMemo(() => {
const pending = !receipt
const success = !pending && tx && (receipt?.status === 1 || typeof receipt?.status === 'undefined')
const transactionState = pending
? TransactionState.Pending
: success
? TransactionState.Success
: TransactionState.Failed
return transactionState
}, [receipt, tx])
const link = `${explorer}tx/${hash}`
return chainId ? (
<Grid href={link} target="_blank">
<LogoView info={info} />
<TextContainer as="span">
<TransactionBody info={info} transactionState={transactionState} />
</TextContainer>
{transactionState === TransactionState.Pending ? (
<IconStyleWrap>
<Loader />
</IconStyleWrap>
) : transactionState === TransactionState.Success ? (
<IconStyleWrap>
<CheckCircle color={colors.green200} size="16px" />
</IconStyleWrap>
) : (
<IconStyleWrap>
<AlertTriangle color={colors.gold200} size="16px" />
</IconStyleWrap>
)}
</Grid>
) : null
}

View File

@@ -2,8 +2,8 @@ import { Trans } from '@lingui/macro'
// eslint-disable-next-line no-restricted-imports
import { t } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { ChangeEvent, Context, ReactNode, useCallback, useContext } from 'react'
import styled, { DefaultTheme, ThemeContext } from 'styled-components/macro'
import { ChangeEvent, ReactNode, useCallback } from 'react'
import styled, { useTheme } from 'styled-components/macro'
import useENS from '../../hooks/useENS'
import { ExternalLink, ThemedText } from '../../theme'
@@ -87,7 +87,7 @@ export default function AddressInputPanel({
onChange: (value: string) => void
}) {
const { chainId } = useWeb3React()
const theme = useContext(ThemeContext as Context<DefaultTheme>)
const theme = useTheme()
const { address, loading, name } = useENS(value)

View File

@@ -1,8 +1,7 @@
import useTheme from 'hooks/useTheme'
import { darken } from 'polished'
import { Check, ChevronDown } from 'react-feather'
import { Button as RebassButton, ButtonProps as ButtonPropsOriginal } from 'rebass/styled-components'
import styled from 'styled-components/macro'
import styled, { useTheme } from 'styled-components/macro'
import { RowBetween } from '../Row'
@@ -51,9 +50,12 @@ export const BaseButton = styled(RebassButton)<
}
`
export const ButtonPrimary = styled(BaseButton)`
background-color: ${({ theme }) => theme.deprecated_primary1};
color: white;
export const ButtonPrimary = styled(BaseButton)<{ redesignFlag?: boolean }>`
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentAction : theme.deprecated_primary1)};
font-size: ${({ redesignFlag }) => redesignFlag && '20px'};
font-weight: ${({ redesignFlag }) => redesignFlag && '600'};
padding: ${({ redesignFlag }) => redesignFlag && '16px'};
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentTextLightPrimary : 'white')};
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.deprecated_primary1)};
background-color: ${({ theme }) => darken(0.05, theme.deprecated_primary1)};
@@ -77,35 +79,35 @@ export const ButtonPrimary = styled(BaseButton)`
}
`
export const ButtonLight = styled(BaseButton)<{ phase0Flag?: boolean }>`
background-color: ${({ theme, phase0Flag }) => (phase0Flag ? theme.accentActionSoft : theme.deprecated_primary5)};
color: ${({ theme, phase0Flag }) => (phase0Flag ? theme.accentAction : theme.deprecated_primaryText1)};
font-size: ${({ phase0Flag }) => (phase0Flag ? '20px' : '16px')};
font-weight: ${({ phase0Flag }) => (phase0Flag ? '600' : '500')};
export const ButtonLight = styled(BaseButton)<{ redesignFlag?: boolean }>`
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentActionSoft : theme.deprecated_primary5)};
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentAction : theme.deprecated_primaryText1)};
font-size: ${({ redesignFlag }) => (redesignFlag ? '20px' : '16px')};
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
&:focus {
box-shadow: 0 0 0 1pt
${({ theme, disabled, phase0Flag }) =>
!disabled && (phase0Flag ? theme.accentActionSoft : darken(0.03, theme.deprecated_primary5))};
background-color: ${({ theme, disabled, phase0Flag }) =>
!disabled && (phase0Flag ? theme.accentActionSoft : darken(0.03, theme.deprecated_primary5))};
${({ theme, disabled, redesignFlag }) =>
!disabled && (redesignFlag ? theme.accentActionSoft : darken(0.03, theme.deprecated_primary5))};
background-color: ${({ theme, disabled, redesignFlag }) =>
!disabled && (redesignFlag ? theme.accentActionSoft : darken(0.03, theme.deprecated_primary5))};
}
&:hover {
background-color: ${({ theme, disabled, phase0Flag }) =>
!disabled && (phase0Flag ? theme.accentActionSoft : darken(0.03, theme.deprecated_primary5))};
background-color: ${({ theme, disabled, redesignFlag }) =>
!disabled && (redesignFlag ? theme.accentActionSoft : darken(0.03, theme.deprecated_primary5))};
}
&:active {
box-shadow: 0 0 0 1pt
${({ theme, disabled, phase0Flag }) =>
!disabled && (phase0Flag ? theme.accentActionSoft : darken(0.05, theme.deprecated_primary5))};
background-color: ${({ theme, disabled, phase0Flag }) =>
!disabled && (phase0Flag ? theme.accentActionSoft : darken(0.05, theme.deprecated_primary5))};
${({ theme, disabled, redesignFlag }) =>
!disabled && (redesignFlag ? theme.accentActionSoft : darken(0.05, theme.deprecated_primary5))};
background-color: ${({ theme, disabled, redesignFlag }) =>
!disabled && (redesignFlag ? theme.accentActionSoft : darken(0.05, theme.deprecated_primary5))};
}
:disabled {
opacity: 0.4;
:hover {
cursor: auto;
background-color: ${({ theme, phase0Flag }) => (phase0Flag ? theme.none : theme.deprecated_primary5)};
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_primary5)};
box-shadow: none;
border: 1px solid transparent;
outline: none;
@@ -174,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'
@@ -11,8 +10,8 @@ interface LineChartProps<T> {
data: T[]
getX: (t: T) => number
getY: (t: T) => number
marginTop: number
curve?: CurveFactory
marginTop?: number
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,288 +0,0 @@
import { AxisBottom, TickFormatter } from '@visx/axis'
import { localPoint } from '@visx/event'
import { EventType } from '@visx/event/lib/types'
import { GlyphCircle } from '@visx/glyph'
import { Line } from '@visx/shape'
import { filterTimeAtom } from 'components/Explore/state'
import { bisect, curveBasis, NumberValue, scaleLinear } from 'd3'
import { useActiveLocale } from 'hooks/useActiveLocale'
import useTheme from 'hooks/useTheme'
import { TimePeriod } from 'hooks/useTopTokens'
import { useAtom } from 'jotai'
import { useCallback, useState } from 'react'
import { ArrowDownRight, ArrowUpRight } from 'react-feather'
import styled from 'styled-components/macro'
import {
dayHourFormatter,
hourFormatter,
monthDayFormatter,
monthFormatter,
monthYearDayFormatter,
monthYearFormatter,
weekFormatter,
} from 'utils/formatChartTimes'
import data from './data.json'
import LineChart from './LineChart'
// TODO: This should be combined with the logic in TimeSelector.
const TIME_DISPLAYS: [TimePeriod, string][] = [
[TimePeriod.hour, '1H'],
[TimePeriod.day, '1D'],
[TimePeriod.week, '1W'],
[TimePeriod.month, '1M'],
[TimePeriod.year, '1Y'],
[TimePeriod.all, 'ALL'],
]
type PricePoint = { value: number; timestamp: number }
function getPriceBounds(pricePoints: PricePoint[]): [number, number] {
const prices = pricePoints.map((x) => x.value)
const min = Math.min(...prices)
const max = Math.max(...prices)
return [min, max]
}
const StyledUpArrow = styled(ArrowUpRight)`
color: ${({ theme }) => theme.accentSuccess};
`
const StyledDownArrow = styled(ArrowDownRight)`
color: ${({ theme }) => theme.accentFailure};
`
function getDelta(start: number, current: number) {
const delta = (current / start - 1) * 100
const isPositive = Math.sign(delta) > 0
const formattedDelta = delta.toFixed(2) + '%'
if (isPositive) {
return ['+' + formattedDelta, <StyledUpArrow size={16} key="arrow-up" />]
} else if (delta === 0) {
return [formattedDelta, null]
}
return [formattedDelta, <StyledDownArrow size={16} key="arrow-down" />]
}
export const ChartWrapper = styled.div`
position: relative;
overflow: visible;
`
export const ChartHeader = styled.div`
position: absolute;
`
export const TokenPrice = styled.span`
font-size: 36px;
line-height: 44px;
`
export const DeltaContainer = styled.div`
height: 16px;
display: flex;
align-items: center;
`
const ArrowCell = styled.div`
padding-left: 2px;
display: flex;
`
export const TimeOptionsContainer = styled.div`
display: flex;
justify-content: flex-end;
margin-top: 4px;
gap: 4px;
`
const TimeButton = styled.button<{ active: boolean }>`
background-color: ${({ theme, active }) => (active ? theme.accentActive : 'transparent')};
font-size: 14px;
width: 36px;
height: 36px;
border-radius: 12px;
border: none;
cursor: pointer;
color: ${({ theme }) => theme.textPrimary};
`
function getTicks(startTimestamp: number, endTimestamp: number, numTicks = 5) {
return Array.from(
{ length: numTicks },
(v, i) => endTimestamp - ((endTimestamp - startTimestamp) / (numTicks + 1)) * (i + 1)
)
}
function tickFormat(
startTimestamp: number,
endTimestamp: number,
timePeriod: TimePeriod,
locale: string
): [TickFormatter<NumberValue>, (v: number) => string, number[]] {
switch (timePeriod) {
case TimePeriod.hour:
return [hourFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp)]
case TimePeriod.day:
return [hourFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp)]
case TimePeriod.week:
return [weekFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp, 6)]
case TimePeriod.month:
return [monthDayFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp)]
case TimePeriod.year:
return [monthFormatter(locale), monthYearDayFormatter(locale), getTicks(startTimestamp, endTimestamp)]
case TimePeriod.all:
return [monthYearFormatter(locale), monthYearDayFormatter(locale), getTicks(startTimestamp, endTimestamp)]
}
}
const margin = { top: 86, bottom: 32, crosshair: 72 }
const timeOptionsHeight = 44
const crosshairDateOverhang = 80
interface PriceChartProps {
width: number
height: number
}
export function PriceChart({ width, height }: PriceChartProps) {
const [timePeriod, setTimePeriod] = useAtom(filterTimeAtom)
const locale = useActiveLocale()
const theme = useTheme()
/* TODO: Implement API calls & cache to use here */
const pricePoints = data[timePeriod]
const startingPrice = pricePoints[0]
const endingPrice = pricePoints[pricePoints.length - 1]
const initialState = { pricePoint: endingPrice, xCoordinate: null }
const [selected, setSelected] = useState<{ pricePoint: PricePoint; xCoordinate: number | null }>(initialState)
const graphWidth = width + crosshairDateOverhang
const graphHeight = height - timeOptionsHeight
const graphInnerHeight = graphHeight - margin.top - margin.bottom
// Defining scales
// x scale
const timeScale = scaleLinear().domain([startingPrice.timestamp, endingPrice.timestamp]).range([0, width])
// y scale
const rdScale = scaleLinear().domain(getPriceBounds(pricePoints)).range([graphInnerHeight, 0])
const handleHover = useCallback(
(event: Element | EventType) => {
const { x } = localPoint(event) || { x: 0 }
const x0 = timeScale.invert(x) // get timestamp from the scale
const index = bisect(
pricePoints.map((x) => x.timestamp),
x0,
1
)
const d0 = pricePoints[index - 1]
const d1 = pricePoints[index]
let pricePoint = d0
const hasPreviousData = d1 && d1.timestamp
if (hasPreviousData) {
pricePoint = x0.valueOf() - d0.timestamp.valueOf() > d1.timestamp.valueOf() - x0.valueOf() ? d1 : d0
}
setSelected({ pricePoint, xCoordinate: timeScale(pricePoint.timestamp) })
},
[timeScale, pricePoints]
)
const [tickFormatter, crosshairDateFormatter, ticks] = tickFormat(
startingPrice.timestamp,
endingPrice.timestamp,
timePeriod,
locale
)
const [delta, arrow] = getDelta(startingPrice.value, selected.pricePoint.value)
const crosshairEdgeMax = width * 0.97
const crosshairAtEdge = !!selected.xCoordinate && selected.xCoordinate > crosshairEdgeMax
return (
<ChartWrapper>
<ChartHeader>
<TokenPrice>${selected.pricePoint.value.toFixed(2)}</TokenPrice>
<DeltaContainer>
{delta}
<ArrowCell>{arrow}</ArrowCell>
</DeltaContainer>
</ChartHeader>
<LineChart
data={pricePoints}
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}
strokeWidth={2}
width={graphWidth}
height={graphHeight}
>
<AxisBottom
scale={timeScale}
stroke={theme.backgroundOutline}
tickFormat={tickFormatter}
tickStroke={theme.backgroundOutline}
tickLength={4}
tickTransform={'translate(0 -5)'}
tickValues={ticks}
top={graphHeight - 1}
tickLabelProps={() => ({
fill: theme.textSecondary,
fontSize: 12,
textAnchor: 'middle',
transform: 'translate(0 -24)',
})}
/>
{selected.xCoordinate !== null && (
<g>
<text
x={selected.xCoordinate + (crosshairAtEdge ? -4 : 4)}
y={margin.crosshair + 10}
textAnchor={crosshairAtEdge ? 'end' : 'start'}
fontSize={12}
fill={theme.textSecondary}
>
{crosshairDateFormatter(selected.pricePoint.timestamp)}
</text>
<Line
from={{ x: selected.xCoordinate, y: margin.crosshair }}
to={{ x: selected.xCoordinate, y: graphHeight }}
stroke={theme.backgroundOutline}
strokeWidth={1}
pointerEvents="none"
strokeDasharray="4,4"
/>
<GlyphCircle
left={selected.xCoordinate}
top={rdScale(selected.pricePoint.value) + margin.top}
size={50}
fill={theme.accentActive}
stroke={theme.backgroundOutline}
strokeWidth={2}
/>
</g>
)}
<rect
x={0}
y={0}
width={width}
height={graphHeight}
fill={'transparent'}
onTouchStart={handleHover}
onTouchMove={handleHover}
onMouseMove={handleHover}
onMouseLeave={() => setSelected(initialState)}
/>
</LineChart>
<TimeOptionsContainer>
{TIME_DISPLAYS.map(([value, display]) => (
<TimeButton key={display} active={timePeriod === value} onClick={() => setTimePeriod(value)}>
{display}
</TimeButton>
))}
</TimeOptionsContainer>
</ChartWrapper>
)
}
export default PriceChart

View File

@@ -1,48 +1,56 @@
import { scaleLinear } from 'd3'
import useTheme from 'hooks/useTheme'
import { curveCardinal, scaleLinear } from 'd3'
import { filterPrices } from 'graphql/data/Token'
import { TopToken } from 'graphql/data/TopTokens'
import { TimePeriod } from 'graphql/data/util'
import React from 'react'
import { useTheme } from 'styled-components/macro'
import data from './data.json'
import { DATA_EMPTY, getPriceBounds } from '../Tokens/TokenDetails/PriceChart'
import LineChart from './LineChart'
type PricePoint = { value: number; timestamp: number }
function getPriceBounds(pricePoints: PricePoint[]): [number, number] {
const prices = pricePoints.map((x) => x.value)
const min = Math.min(...prices)
const max = Math.max(...prices)
return [min, max]
}
interface SparklineChartProps {
width: number
height: number
tokenData: TopToken
pricePercentChange: number | undefined | null
timePeriod: TimePeriod
}
function SparklineChart({ width, height }: SparklineChartProps) {
function SparklineChart({ width, height, tokenData, pricePercentChange, timePeriod }: SparklineChartProps) {
const theme = useTheme()
// for sparkline
const pricePoints = filterPrices(tokenData?.market?.priceHistory) ?? []
const hasData = pricePoints.length !== 0
const startingPrice = hasData ? pricePoints[0] : DATA_EMPTY
const endingPrice = hasData ? pricePoints[pricePoints.length - 1] : DATA_EMPTY
const widthScale = scaleLinear()
.domain(
// the range of possible input values
[startingPrice.timestamp, endingPrice.timestamp]
)
.range(
// the range of possible output values that the inputs should be transformed to (see https://www.d3indepth.com/scales/ for details)
[0, 110]
)
const rdScale = scaleLinear().domain(getPriceBounds(pricePoints)).range([30, 0])
/* TODO: Implement API calls & cache to use here */
const pricePoints = data.day
const startingPrice = pricePoints[0]
const endingPrice = pricePoints[pricePoints.length - 1]
const timeScale = scaleLinear().domain([startingPrice.timestamp, endingPrice.timestamp]).range([0, width])
const rdScale = scaleLinear().domain(getPriceBounds(pricePoints)).range([height, 0])
const isPositive = endingPrice.value >= startingPrice.value
/* Default curve doesn't look good for the ALL chart */
const curveTension = timePeriod === TimePeriod.ALL ? 0.75 : 0.9
return (
<LineChart
data={pricePoints}
getX={(p: PricePoint) => timeScale(p.timestamp)}
getX={(p: PricePoint) => widthScale(p.timestamp)}
getY={(p: PricePoint) => rdScale(p.value)}
marginTop={0}
color={isPositive ? theme.accentSuccess : theme.accentFailure}
curve={curveCardinal.tension(curveTension)}
marginTop={5}
color={pricePercentChange && pricePercentChange < 0 ? theme.accentFailure : theme.accentSuccess}
strokeWidth={1.5}
width={width}
height={height}
></LineChart>
/>
)
}

View File

@@ -1,17 +1,17 @@
import { Trans } from '@lingui/macro'
import CopyHelper from 'components/AccountDetails/Copy'
import Column from 'components/Column'
import useTheme from 'hooks/useTheme'
import { AlertOctagon } from 'react-feather'
import styled from 'styled-components/macro'
import styled, { useTheme } from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme'
import { CopyHelper } from '../../theme'
import Modal from '../Modal'
const ContentWrapper = styled(Column)`
align-items: center;
margin: 32px;
text-align: center;
font-size: 12px;
`
const WarningIcon = styled(AlertOctagon)`
min-height: 22px;
@@ -49,7 +49,14 @@ export default function ConnectedAccountBlocked(props: ConnectedAccountBlockedPr
<ThemedText.DeprecatedMain fontSize={12}>
<Trans>If you believe this is an error, please send an email including your address to </Trans>{' '}
</ThemedText.DeprecatedMain>
<Copy iconSize={12} toCopy="compliance@uniswap.org" color={theme.deprecated_primary1} iconPosition="right">
<Copy
toCopy="compliance@uniswap.org"
fontSize={14}
iconSize={16}
gap={6}
color={theme.deprecated_primary1}
iconPosition="right"
>
compliance@uniswap.org
</Copy>
</ContentWrapper>

View File

@@ -2,10 +2,10 @@ import { Trans } from '@lingui/macro'
// eslint-disable-next-line no-restricted-imports
import { t } from '@lingui/macro'
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
import HoverInlineText from 'components/HoverInlineText'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { useMemo } from 'react'
import { useTheme } from 'styled-components/macro'
import useTheme from '../../hooks/useTheme'
import { ThemedText } from '../../theme'
import { warningSeverity } from '../../utils/prices'
import { MouseoverTooltip } from '../Tooltip'
@@ -18,6 +18,7 @@ export function FiatValue({
priceImpact?: Percent
}) {
const theme = useTheme()
const redesignFlagEnabled = useRedesignFlag() === RedesignVariant.Enabled
const priceImpactColor = useMemo(() => {
if (!priceImpact) return undefined
if (priceImpact.lessThan('0')) return theme.deprecated_green1
@@ -30,19 +31,15 @@ export function FiatValue({
const p = Number(fiatValue?.toFixed())
const visibleDecimalPlaces = p < 1.05 ? 4 : 2
const textColor = redesignFlagEnabled
? theme.textSecondary
: fiatValue
? theme.deprecated_text3
: theme.deprecated_text4
return (
<ThemedText.DeprecatedBody fontSize={14} color={fiatValue ? theme.deprecated_text3 : theme.deprecated_text4}>
{fiatValue ? (
<Trans>
$
<HoverInlineText
text={fiatValue?.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })}
textColor={fiatValue ? theme.deprecated_text3 : theme.deprecated_text4}
/>
</Trans>
) : (
''
)}
<ThemedText.DeprecatedBody fontSize={14} color={textColor}>
{fiatValue && <>${fiatValue?.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })}</>}
{priceImpact ? (
<span style={{ color: priceImpactColor }}>
{' '}

View File

@@ -0,0 +1,395 @@
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 'analytics/constants'
import { TraceEvent } from 'analytics/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 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.stateOverlayPressed
: 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')};
&:hover {
background-color: ${({ selected, theme, redesignFlag }) =>
redesignFlag
? theme.stateOverlayHover
: selected
? darken(0.05, theme.deprecated_primary1)
: theme.deprecated_bg3};
}
&:active {
background-color: ${({ selected, theme, redesignFlag }) =>
redesignFlag
? theme.stateOverlayPressed
: selected
? darken(0.05, theme.deprecated_primary1)
: theme.deprecated_bg3};
}
visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
`
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<{ redesignFlag: boolean }>`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textSecondary : 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 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-size: ${({ redesignFlag }) => redesignFlag && '36px'};
line-height: ${({ redesignFlag }) => redesignFlag && '44px'};
font-variant: ${({ redesignFlag }) => redesignFlag && 'small-caps'};
`
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 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}
/>
)}
<CurrencySelect
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>
</CurrencySelect>
</InputRow>
{!hideInput && !hideBalance && currency && (
<FiatRow redesignFlag={redesignFlagEnabled}>
<RowBetween>
<LoadingOpacityContainer $loading={loading}>
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
</LoadingOpacityContainer>
{account ? (
<RowFixed style={{ height: '17px' }}>
<ThemedText.DeprecatedBody
color={redesignFlag ? theme.textSecondary : theme.deprecated_text3}
fontWeight={redesignFlag ? 400 : 500}
fontSize={14}
style={{ display: 'inline' }}
>
{!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

@@ -2,20 +2,19 @@ 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 { ElementName, Event, EventName } from 'analytics/constants'
import { TraceEvent } from 'analytics/TraceEvent'
import { AutoColumn } from 'components/Column'
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
import { isSupportedChain } from 'constants/chains'
import { Phase0Variant, usePhase0Flag } from 'featureFlags/flags/phase0'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { darken } from 'polished'
import { ReactNode, useCallback, useState } from 'react'
import { Lock } from 'react-feather'
import styled from 'styled-components/macro'
import styled, { useTheme } from 'styled-components/macro'
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
import useTheme from '../../hooks/useTheme'
import { useCurrencyBalance } from '../../state/connection/hooks'
import { ThemedText } from '../../theme'
import { ButtonGray } from '../Button'
@@ -26,37 +25,35 @@ import { RowBetween, RowFixed } from '../Row'
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
import { FiatValue } from './FiatValue'
const InputPanel = styled.div<{ hideInput?: boolean; phase0Flag: boolean }>`
const InputPanel = styled.div<{ hideInput?: boolean }>`
${({ theme }) => theme.flexColumnNoWrap}
position: relative;
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
background-color: ${({ theme, phase0Flag, hideInput }) =>
phase0Flag ? theme.none : 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<{ phase0Flag: boolean }>`
const FixedContainer = styled.div`
width: 100%;
height: 100%;
position: absolute;
border-radius: 20px;
background-color: ${({ theme, phase0Flag }) => (phase0Flag ? theme.none : 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; phase0Flag: boolean }>`
const Container = styled.div<{ hideInput: boolean; disabled: boolean }>`
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
border: 1px solid ${({ theme, phase0Flag }) => (phase0Flag ? theme.none : theme.deprecated_bg0)};
background-color: ${({ theme, phase0Flag }) => (phase0Flag ? theme.none : theme.deprecated_bg1)};
border: 1px solid ${({ theme }) => theme.deprecated_bg0};
background-color: ${({ theme }) => theme.deprecated_bg1};
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
${({ theme, hideInput, disabled, phase0Flag }) =>
!phase0Flag &&
${({ theme, hideInput, disabled }) =>
!disabled &&
`
:focus,
@@ -71,64 +68,38 @@ const CurrencySelect = styled(ButtonGray)<{
selected: boolean
hideInput?: boolean
disabled?: boolean
phase0Flag: boolean
}>`
align-items: center;
background-color: ${({ selected, theme, phase0Flag }) =>
phase0Flag
? 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, phase0Flag }) => (phase0Flag ? '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, phase0Flag }) => (phase0Flag ? (selected ? '4px 8px 4px 4px' : '6px 6px 6px 8px') : '0 8px')};
gap: ${({ phase0Flag }) => (phase0Flag ? '8px' : '0px')};
padding: 0 8px;
justify-content: space-between;
margin-left: ${({ hideInput }) => (hideInput ? '0' : '12px')};
:focus,
:hover {
background-color: ${({ selected, theme, phase0Flag }) =>
selected
? phase0Flag
? 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)<{ phase0Flag: boolean }>`
background-color: ${({ theme, selected, phase0Flag }) =>
phase0Flag && (selected ? theme.backgroundContainer : theme.accentAction)};
:focus,
:hover {
background-color: ${({ selected, theme, phase0Flag }) =>
selected
? phase0Flag
? theme.backgroundAction
: theme.deprecated_bg3
: darken(0.05, theme.deprecated_primary1)};
}
`
const InputRow = styled.div<{ selected: boolean; phase0Flag: boolean }>`
const InputRow = styled.div<{ selected: boolean }>`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
justify-content: space-between;
padding: ${({ selected, phase0Flag }) =>
phase0Flag ? '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`
@@ -138,29 +109,16 @@ 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)};
}
`
const FiatRow = styled(LabelRow)<{ phase0Flag: boolean }>`
const FiatRow = styled(LabelRow)<{ redesignFlag: boolean }>`
justify-content: flex-end;
padding: ${({ phase0Flag }) => phase0Flag && '8px 0px'};
height: ${({ phase0Flag }) => !phase0Flag && '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`
@@ -170,34 +128,31 @@ const Aligner = styled.span`
width: 100%;
`
const StyledDropDown = styled(DropDown)<{ selected: boolean; phase0Flag: boolean }>`
const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
margin: 0 0.25rem 0 0.35rem;
height: 35%;
margin-left: ${({ phase0Flag }) => phase0Flag && '8px'};
path {
stroke: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
stroke-width: ${({ phase0Flag }) => (phase0Flag ? '2px' : '1.5px')};
stroke-width: 1.5px;
}
`
const StyledTokenName = styled.span<{ active?: boolean; phase0Flag: 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: ${({ phase0Flag }) => (phase0Flag ? '600' : '500')};
`
const StyledBalanceMax = styled.button<{ disabled?: boolean; phase0Flag: boolean }>`
const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
background-color: transparent;
background-color: ${({ theme, phase0Flag }) => !phase0Flag && theme.deprecated_primary5};
background-color: ${({ theme }) => theme.deprecated_primary5};
border: none;
text-transform: ${({ phase0Flag }) => !phase0Flag && 'uppercase'};
border-radius: ${({ phase0Flag }) => !phase0Flag && '12px'};
color: ${({ theme, phase0Flag }) => (phase0Flag ? theme.accentAction : theme.deprecated_primary1)};
border-radius: 12px;
color: ${({ theme }) => theme.deprecated_primary1};
cursor: pointer;
font-size: ${({ phase0Flag }) => (phase0Flag ? '14px' : '11px')};
font-weight: ${({ phase0Flag }) => (phase0Flag ? '600' : '500')};
margin-left: ${({ phase0Flag }) => (phase0Flag ? '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')};
@@ -211,11 +166,9 @@ const StyledBalanceMax = styled.button<{ disabled?: boolean; phase0Flag: boolean
}
`
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean; phase0Flag: boolean }>`
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean }>`
${loadingOpacityMixin};
text-align: left;
font-variant: ${({ phase0Flag }) => phase0Flag && 'small-caps'};
font-feature-settings: ${({ phase0Flag }) => phase0Flag && 'pnum on, lnum on'};
`
interface CurrencyInputPanelProps {
@@ -265,10 +218,10 @@ export default function CurrencyInputPanel({
}: CurrencyInputPanelProps) {
const [modalOpen, setModalOpen] = useState(false)
const { account, chainId } = useWeb3React()
const phase0Flag = usePhase0Flag()
const phase0FlagEnabled = phase0Flag === Phase0Variant.Enabled
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
const theme = useTheme()
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const handleDismissSearch = useCallback(() => {
setModalOpen(false)
@@ -277,9 +230,9 @@ export default function CurrencyInputPanel({
const chainAllowed = isSupportedChain(chainId)
return (
<InputPanel id={id} hideInput={hideInput} {...rest} phase0Flag={phase0FlagEnabled}>
<InputPanel id={id} hideInput={hideInput} {...rest}>
{locked && (
<FixedContainer phase0Flag={phase0FlagEnabled}>
<FixedContainer>
<AutoColumn gap="sm" justify="center">
<Lock />
<ThemedText.DeprecatedLabel fontSize="12px" textAlign="center" padding="0 12px">
@@ -288,12 +241,8 @@ export default function CurrencyInputPanel({
</AutoColumn>
</FixedContainer>
)}
<Container hideInput={hideInput} disabled={!chainAllowed} phase0Flag={phase0FlagEnabled}>
<InputRow
style={hideInput ? { padding: '0', borderRadius: '8px' } : {}}
selected={!onCurrencySelect}
phase0Flag={phase0FlagEnabled}
>
<Container hideInput={hideInput} disabled={!chainAllowed}>
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={!onCurrencySelect}>
{!hideInput && (
<StyledNumericalInput
className="token-amount-input"
@@ -301,16 +250,14 @@ export default function CurrencyInputPanel({
onUserInput={onUserInput}
disabled={!chainAllowed}
$loading={loading}
phase0Flag={phase0FlagEnabled}
/>
)}
<InputCurrencySelect
<CurrencySelect
disabled={!chainAllowed}
visible={currency !== undefined}
selected={!!currency}
hideInput={hideInput}
phase0Flag={phase0FlagEnabled}
className="open-currency-select-button"
onClick={() => {
if (onCurrencySelect) {
@@ -325,42 +272,28 @@ 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" phase0Flag={phase0FlagEnabled}>
<StyledTokenName className="pair-name-container">
{pair?.token0.symbol}:{pair?.token1.symbol}
</StyledTokenName>
) : (
<StyledTokenName
className="token-symbol-container"
active={Boolean(currency && currency.symbol)}
phase0Flag={phase0FlagEnabled}
>
<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} phase0Flag={phase0FlagEnabled} />}
{onCurrencySelect && <StyledDropDown selected={!!currency} />}
</Aligner>
</InputCurrencySelect>
</CurrencySelect>
</InputRow>
{phase0FlagEnabled && !currency && (
<NoBalanceState>
<FiatRow phase0Flag={phase0FlagEnabled}>
<RowBetween>
<NoBalanceDash>-</NoBalanceDash>
<NoBalanceDash>-</NoBalanceDash>
</RowBetween>
</FiatRow>
</NoBalanceState>
)}
{!hideInput && !hideBalance && currency && (
<FiatRow phase0Flag={phase0FlagEnabled}>
<FiatRow redesignFlag={redesignFlagEnabled}>
<RowBetween>
<LoadingOpacityContainer $loading={loading}>
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
@@ -388,8 +321,8 @@ export default function CurrencyInputPanel({
name={EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED}
element={ElementName.MAX_TOKEN_AMOUNT_BUTTON}
>
<StyledBalanceMax onClick={onMax} phase0Flag={phase0FlagEnabled}>
<Trans>Max</Trans>
<StyledBalanceMax onClick={onMax}>
<Trans>MAX</Trans>
</StyledBalanceMax>
</TraceEvent>
) : null}

View File

@@ -1,6 +1,6 @@
import { Currency } from '@uniswap/sdk-core'
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
import React from 'react'
import React, { useMemo } from 'react'
import styled from 'styled-components/macro'
import Logo from '../Logo'
@@ -24,18 +24,25 @@ const StyledNativeLogo = styled(StyledLogo)`
export default function CurrencyLogo({
currency,
symbol,
size = '24px',
style,
src,
...rest
}: {
currency?: Currency | null
symbol?: string | null
size?: string
style?: React.CSSProperties
src?: string | null
}) {
const logoURIs = useCurrencyLogoURIs(currency)
const srcs = useMemo(() => (src ? [src] : logoURIs), [src, logoURIs])
const props = {
alt: `${currency?.symbol ?? 'token'} logo`,
size,
srcs: useCurrencyLogoURIs(currency),
srcs,
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

@@ -49,7 +49,7 @@ export default function DowntimeWarning() {
switch (chainId) {
case SupportedChainId.OPTIMISM:
case SupportedChainId.OPTIMISTIC_KOVAN:
case SupportedChainId.OPTIMISM_GOERLI:
return (
<Wrapper>
<Trans>

View File

@@ -1,112 +0,0 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { getChainInfoOrDefault } from 'constants/chainInfo'
import { useToken } from 'hooks/Tokens'
import { useNetworkTokenBalances } from 'hooks/useNetworkTokenBalances'
import { AlertTriangle } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
import NetworkBalance from './NetworkBalance'
const BalancesCard = styled.div`
width: 284px;
height: fit-content;
color: ${({ theme }) => theme.textPrimary};
font-size: 12px;
line-height: 20px;
padding: 20px;
background-color: ${({ theme }) => theme.backgroundSurface};
border-radius: 12px;
border: 1px solid ${({ theme }) => theme.backgroundOutline};
`
const ErrorState = styled.div`
display: flex;
align-items: center;
gap: 12px;
color: ${({ theme }) => theme.textSecondary};
font-weight: 500;
font-size: 14px;
line-height: 20px;
`
const ErrorText = styled.span`
display: flex;
flex-wrap: wrap;
`
const NetworkBalancesSection = styled.div`
height: fit-content;
`
const TotalBalanceSection = styled.div`
height: fit-content;
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
margin-bottom: 20px;
padding-bottom: 20px;
`
const TotalBalance = styled.div`
display: flex;
justify-content: space-between;
font-size: 20px;
line-height: 28px;
margin-top: 12px;
align-items: center;
`
const TotalBalanceItem = styled.div`
display: flex;
`
export default function BalanceSummary({
address,
networkBalances,
totalBalance,
}: {
address: string
networkBalances: (JSX.Element | null)[] | null
totalBalance: number
}) {
const theme = useTheme()
const tokenSymbol = useToken(address)?.symbol
const { loading, error, data } = useNetworkTokenBalances({ address })
const { chainId: connectedChainId } = useWeb3React()
const { label: connectedLabel, logoUrl: connectedLogoUrl } = getChainInfoOrDefault(connectedChainId)
const connectedFiatValue = 1
const multipleBalances = true // for testing purposes
if (loading) return null
return (
<BalancesCard>
{error ? (
<ErrorState>
<AlertTriangle size={24} />
<ErrorText>
<Trans>There was an error loading your {tokenSymbol} balance</Trans>
</ErrorText>
</ErrorState>
) : multipleBalances ? (
<>
<TotalBalanceSection>
Your balance across all networks
<TotalBalance>
<TotalBalanceItem>{`${totalBalance} ${tokenSymbol}`}</TotalBalanceItem>
<TotalBalanceItem>$4,210.12</TotalBalanceItem>
</TotalBalance>
</TotalBalanceSection>
<NetworkBalancesSection>Your balances by network</NetworkBalancesSection>
{data && networkBalances}
</>
) : (
<>
Your balance on {connectedLabel}
<NetworkBalance
logoUrl={connectedLogoUrl}
balance={'1'}
tokenSymbol={tokenSymbol ?? 'XXX'}
fiatValue={connectedFiatValue}
label={connectedLabel}
networkColor={theme.textPrimary}
/>
</>
)}
</BalancesCard>
)
}

View File

@@ -1,141 +0,0 @@
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { darken } from 'polished'
import { useRef, useState } from 'react'
import { Check, Link, Share, Twitter } from 'react-feather'
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import styled, { useTheme } from 'styled-components/macro'
import { Z_INDEX } from 'theme'
const TWITTER_WIDTH = 560
const TWITTER_HEIGHT = 480
const ShareButtonDisplay = styled.div`
display: flex;
cursor: pointer;
position: relative;
z-index: ${Z_INDEX.dropdown};
&:hover {
color: ${({ theme }) => darken(0.1, theme.textSecondary)};
}
`
const ShareActions = styled.div`
position: absolute;
top: 28px;
right: 0px;
padding: 8px 0px;
display: flex;
flex-direction: column;
width: fit-content;
overflow: auto;
background-color: ${({ theme }) => theme.backgroundSurface};
border: 1px solid ${({ theme }) => theme.backgroundOutline};
box-shadow: ${({ theme }) => theme.flyoutDropShadow};
border-radius: 12px;
`
const ShareAction = styled.div`
display: flex;
align-items: center;
padding: 12px 16px;
font-size: 16px;
gap: 8px;
width: 200px;
height: 48px;
color: ${({ theme }) => theme.textPrimary};
cursor: pointer;
&:hover {
background-color: ${({ theme }) => theme.backgroundContainer};
}
`
const LinkCopied = styled.div<{ show: boolean }>`
display: ${({ show }) => (show ? 'flex' : 'none')};
width: 328px;
height: 72px;
color: ${({ theme }) => theme.textPrimary};
background-color: ${({ theme }) => theme.backgroundBackdrop};
justify-content: flex-start;
align-items: center;
padding: 24px 16px;
position: absolute;
right: 32px;
bottom: 32px;
font-size: 14px;
gap: 8px;
border: 1px solid rgba(153, 161, 189, 0.08);
box-shadow: ${({ theme }) => theme.flyoutDropShadow};
border-radius: 20px;
animation: floatIn 0s ease-in 3s forwards;
@keyframes floatIn {
to {
width: 0;
height: 0;
overflow: hidden;
display: none;
}
}
`
interface TokenInfo {
tokenName: string
tokenSymbol: string
}
export default function ShareButton(tokenInfo: TokenInfo) {
const theme = useTheme()
const node = useRef<HTMLDivElement | null>(null)
const open = useModalIsOpen(ApplicationModal.SHARE)
const toggleShare = useToggleModal(ApplicationModal.SHARE)
useOnClickOutside(node, open ? toggleShare : undefined)
const [showCopied, setShowCopied] = useState(false)
const positionX = (window.screen.width - TWITTER_WIDTH) / 2
const positionY = (window.screen.height - TWITTER_HEIGHT) / 2
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`,
'newwindow',
`left=${positionX}, top=${positionY}, width=${TWITTER_WIDTH}, height=${TWITTER_HEIGHT}`
)
}
const copyLink = () => {
navigator.clipboard.writeText(window.location.href).then(
function handleClipboardWriteSuccess() {
setShowCopied(true)
toggleShare()
setTimeout(() => setShowCopied(false), 3000)
},
function error() {
console.error('Clipboard copy failed.')
}
)
}
return (
<>
<ShareButtonDisplay ref={node}>
<Share size={18} onClick={toggleShare} aria-label={`ShareOptions`} />
{open && (
<ShareActions>
<ShareAction onClick={copyLink}>
<Link color={theme.textSecondary} size={18} />
Copy link
</ShareAction>
<ShareAction onClick={shareTweet}>
<Twitter color={theme.textSecondary} size={18} />
Share to Twitter
</ShareAction>
</ShareActions>
)}
</ShareButtonDisplay>
<LinkCopied show={showCopied}>
<Check color={theme.accentSuccess} />
Link Copied
</LinkCopied>
</>
)
}

View File

@@ -1,271 +0,0 @@
import { Trans } from '@lingui/macro'
import { ParentSize } from '@visx/responsive'
import PriceChart from 'components/Charts/PriceChart'
import CurrencyLogo from 'components/CurrencyLogo'
import { VerifiedIcon } from 'components/TokenSafety/TokenSafetyIcon'
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
import { getChainInfo } from 'constants/chainInfo'
import { checkWarning } from 'constants/tokenSafety'
import { useCurrency, useIsUserAddedToken, useToken } from 'hooks/Tokens'
import { useAtomValue } from 'jotai/utils'
import { darken } from 'polished'
import { useCallback } from 'react'
import { useState } from 'react'
import { ArrowLeft, Copy, Heart } from 'react-feather'
import { Link, useNavigate } from 'react-router-dom'
import styled, { useTheme } from 'styled-components/macro'
import { MOBILE_MEDIA_BREAKPOINT } from '../constants'
import { favoritesAtom, useToggleFavorite } from '../state'
import { ClickFavorited } from '../TokenTable/TokenRow'
import Resource from './Resource'
import ShareButton from './ShareButton'
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};
gap: 10px;
align-items: center;
background: transparent;
border: none;
padding: 0px;
cursor: pointer;
&:hover {
color: ${({ theme }) => darken(0.1, theme.textPrimary)};
}
`
export const ContractAddressSection = styled.div`
padding: 24px 0px;
`
const Contract = styled.div`
display: flex;
flex-direction: column;
color: ${({ theme }) => theme.textSecondary};
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: 24px;
color: ${({ theme }) => theme.textSecondary};
`
export const TokenInfoContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
`
const TokenSymbol = styled.span`
color: ${({ theme }) => theme.textSecondary};
`
export const TopArea = styled.div`
max-width: 832px;
`
export const ResourcesContainer = styled.div`
display: flex;
gap: 14px;
`
const FullAddress = styled.span`
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
display: none;
}
`
const TruncatedAddress = styled.span`
display: none;
@media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) {
display: flex;
}
`
const NetworkBadge = styled.div<{ networkColor?: string; backgroundColor?: string }>`
border-radius: 5px;
padding: 4px 8px;
font-weight: 600;
font-size: 12px;
line-height: 12px;
color: ${({ theme, networkColor }) => networkColor ?? theme.textPrimary};
background-color: ${({ theme, backgroundColor }) => backgroundColor ?? theme.backgroundSurface};
`
export default function LoadedTokenDetail({ address }: { address: string }) {
const theme = useTheme()
const token = useToken(address)
const currency = useCurrency(address)
const favoriteTokens = useAtomValue<string[]>(favoritesAtom)
const isFavorited = favoriteTokens.includes(address)
const toggleFavorite = useToggleFavorite(address)
const warning = checkWarning(address)
const navigate = useNavigate()
const isUserAddedToken = useIsUserAddedToken(token)
const [warningModalOpen, setWarningModalOpen] = useState(!!warning && !isUserAddedToken)
const handleDismissWarning = useCallback(() => {
setWarningModalOpen(false)
}, [setWarningModalOpen])
const chainInfo = getChainInfo(token?.chainId)
const networkLabel = chainInfo?.label
const networkBadgebackgroundColor = chainInfo?.backgroundColor
// catch token error and loading state
if (!token || !token.name || !token.symbol) {
return <div>No Token</div>
}
const tokenName = token.name
const tokenSymbol = token.symbol
// TODO: format price, add sparkline
const aboutToken =
'Ethereum is a decentralized computing platform that uses ETH (Ether) to pay transaction fees (gas). Developers can use Ethereum to run decentralized applications (dApps) and issue new crypto assets, known as Ethereum tokens.'
const tokenMarketCap = '23.02B'
const tokenVolume = '1.6B'
const truncatedTokenAddress = `${address.slice(0, 4)}...${address.slice(-3)}`
return (
<TopArea>
<BreadcrumbNavLink to="/explore">
<ArrowLeft size={14} /> Explore
</BreadcrumbNavLink>
<ChartHeader>
<TokenInfoContainer>
<TokenNameCell>
<CurrencyLogo currency={currency} size={'32px'} />
{tokenName} <TokenSymbol>{tokenSymbol}</TokenSymbol>
{!warning && <VerifiedIcon size="24px" />}
{networkBadgebackgroundColor && (
<NetworkBadge networkColor={chainInfo?.color} backgroundColor={networkBadgebackgroundColor}>
{networkLabel}
</NetworkBadge>
)}
</TokenNameCell>
<TokenActions>
<ShareButton tokenName={tokenName} tokenSymbol={tokenSymbol} />
<ClickFavorited onClick={toggleFavorite}>
<Heart
size={15}
color={isFavorited ? theme.accentAction : theme.textSecondary}
fill={isFavorited ? theme.accentAction : theme.none}
/>
</ClickFavorited>
</TokenActions>
</TokenInfoContainer>
<ChartContainer>
<ParentSize>{({ width, height }) => <PriceChart width={width} height={height} />}</ParentSize>
</ChartContainer>
</ChartHeader>
<AboutSection>
<AboutHeader>
<Trans>About</Trans>
</AboutHeader>
{aboutToken}
<ResourcesContainer>
<Resource name={'Etherscan'} link={'https://etherscan.io/'} />
<Resource name={'Protocol Info'} link={`https://info.uniswap.org/#/tokens/${address}`} />
</ResourcesContainer>
</AboutSection>
<StatsSection>
<StatPair>
<Stat>
Market cap<StatPrice>${tokenMarketCap}</StatPrice>
</Stat>
<Stat>
{/* TODO: connect to chart's selected time */}
1h volume
<StatPrice>${tokenVolume}</StatPrice>
</Stat>
</StatPair>
<StatPair>
<Stat>
52W low
<StatPrice>$1,790.01</StatPrice>
</Stat>
<Stat>
52W high
<StatPrice>$4,420.71</StatPrice>
</Stat>
</StatPair>
</StatsSection>
<ContractAddressSection>
<Contract>
Contract Address
<ContractAddress onClick={() => navigator.clipboard.writeText(address)}>
<FullAddress>{address}</FullAddress>
<TruncatedAddress>{truncatedTokenAddress}</TruncatedAddress>
<Copy size={13} color={theme.textSecondary} />
</ContractAddress>
</Contract>
</ContractAddressSection>
<TokenSafetyModal
isOpen={warningModalOpen}
tokenAddress={address}
onCancel={() => navigate(-1)}
onContinue={handleDismissWarning}
/>
</TopArea>
)
}

View File

@@ -1,46 +0,0 @@
import useTheme from 'hooks/useTheme'
import { useAtom } from 'jotai'
import { Heart } from 'react-feather'
import styled from 'styled-components/macro'
import { SMALL_MEDIA_BREAKPOINT } from '../constants'
import { showFavoritesAtom } from '../state'
const FavoriteButtonContent = styled.div`
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
`
const StyledFavoriteButton = styled.button<{ active: boolean }>`
padding: 0px 16px;
border-radius: 12px;
background-color: ${({ theme, active }) => (active ? theme.accentAction : theme.backgroundAction)};
border: none;
color: ${({ theme }) => theme.textPrimary};
font-size: 16px;
font-weight: 600;
cursor: pointer;
:hover {
background-color: ${({ theme, active }) => !active && theme.backgroundContainer};
}
`
const FavoriteText = styled.span`
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
display: none;
}
`
export default function FavoriteButton() {
const theme = useTheme()
const [showFavorites, setShowFavorites] = useAtom(showFavoritesAtom)
return (
<StyledFavoriteButton onClick={() => setShowFavorites(!showFavorites)} active={showFavorites}>
<FavoriteButtonContent>
<Heart size={17} color={theme.textPrimary} fill={theme.none} />
<FavoriteText>Favorites</FavoriteText>
</FavoriteButtonContent>
</StyledFavoriteButton>
)
}

View File

@@ -1,77 +0,0 @@
import searchIcon from 'assets/svg/search.svg'
import xIcon from 'assets/svg/x.svg'
import { useAtom } from 'jotai'
import { useState } from 'react'
import styled from 'styled-components/macro'
import { MEDIUM_MEDIA_BREAKPOINT } from '../constants'
import { filterStringAtom } from '../state'
const ICON_SIZE = '20px'
const SearchBarContainer = styled.div`
display: flex;
flex: 1;
`
const SearchInput = styled.input<{ expanded: boolean }>`
background: no-repeat scroll 7px 7px;
background-image: ${({ expanded }) => !expanded && `url(${searchIcon})`};
background-size: 20px 20px;
background-position: 14px center;
background-color: ${({ theme }) => theme.none};
border-radius: 12px;
border: 1px solid ${({ theme }) => theme.backgroundOutline};
height: 100%;
width: ${({ expanded }) => (expanded ? '100%' : '52px')};
font-size: 16px;
padding-left: 35px;
color: ${({ theme }) => theme.textPrimary};
transition: width 0.75s cubic-bezier(0, 0.795, 0, 1);
:hover {
cursor: ${({ expanded }) => !expanded && 'pointer'};
background-color: ${({ theme }) => theme.backgroundContainer};
}
:focus {
outline: none;
background-color: ${({ theme }) => theme.accentActionSoft};
border: none;
}
::placeholder {
color: ${({ expanded, theme }) => (expanded ? theme.textTertiary : theme.none)};
}
::-webkit-search-cancel-button {
-webkit-appearance: none;
appearance: none;
height: ${ICON_SIZE};
width: ${ICON_SIZE};
background-image: url(${xIcon});
margin-right: 10px;
background-size: ${ICON_SIZE} ${ICON_SIZE};
cursor: pointer;
}
@media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) {
width: 100%;
}
`
export default function SearchBar() {
const [filterString, setFilterString] = useAtom(filterStringAtom)
const [isExpanded, setExpanded] = useState(false)
return (
<SearchBarContainer>
<SearchInput
expanded={isExpanded}
type="search"
placeholder="Search by name or token address"
id="searchBar"
onBlur={() => isExpanded && filterString.length === 0 && setExpanded(false)}
onFocus={() => setExpanded(true)}
autoComplete="off"
value={filterString}
onChange={({ target: { value } }) => setFilterString(value)}
/>
</SearchBarContainer>
)
}

View File

@@ -1,512 +0,0 @@
import { Trans } from '@lingui/macro'
import { ParentSize } from '@visx/responsive'
import { sendAnalyticsEvent } from 'components/AmplitudeAnalytics'
import { EventName } from 'components/AmplitudeAnalytics/constants'
import SparklineChart from 'components/Charts/SparklineChart'
import CurrencyLogo from 'components/CurrencyLogo'
import { useCurrency, useToken } from 'hooks/Tokens'
import useTheme from 'hooks/useTheme'
import { TimePeriod, TokenData } from 'hooks/useTopTokens'
import { useAtom } from 'jotai'
import { useAtomValue } from 'jotai/utils'
import { ReactNode } from 'react'
import { ArrowDown, ArrowDownRight, ArrowUp, ArrowUpRight, Heart } from 'react-feather'
import { Link } from 'react-router-dom'
import styled from 'styled-components/macro'
import { formatAmount, formatDollarAmount } from 'utils/formatDollarAmt'
import {
LARGE_MEDIA_BREAKPOINT,
MAX_WIDTH_MEDIA_BREAKPOINT,
MEDIUM_MEDIA_BREAKPOINT,
SMALL_MEDIA_BREAKPOINT,
} from '../constants'
import { LoadingBubble } from '../loading'
import {
favoritesAtom,
filterNetworkAtom,
filterStringAtom,
filterTimeAtom,
sortCategoryAtom,
sortDirectionAtom,
useSetSortCategory,
useToggleFavorite,
} from '../state'
import { Category, SortDirection } from '../types'
import { TIME_DISPLAYS } from './TimeSelector'
const ArrowCell = styled.div`
padding-left: 2px;
display: flex;
`
const Cell = styled.div`
display: flex;
align-items: center;
justify-content: center;
`
const StyledTokenRow = styled.div`
width: 100%;
height: 60px;
display: grid;
grid-template-columns: 1.2fr 1fr 7fr 4fr 4fr 4fr 4fr 5fr;
font-size: 15px;
line-height: 24px;
max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT};
min-width: 390px;
padding: 0px 12px;
&:hover {
background-color: ${({ theme }) => theme.accentActionSoft};
}
@media only screen and (max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT}) {
grid-template-columns: 1.7fr 1fr 6.5fr 4.5fr 4.5fr 4.5fr 4.5fr;
width: fit-content;
padding-right: 24px;
}
@media only screen and (max-width: ${LARGE_MEDIA_BREAKPOINT}) {
grid-template-columns: 1.7fr 1fr 7.5fr 4.5fr 4.5fr 4.5fr;
width: fit-content;
}
@media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) {
grid-template-columns: 1.2fr 1fr 10fr 5fr 3fr;
width: fit-content;
}
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
grid-template-columns: 4fr 2fr;
min-width: unset;
border-bottom: 0.5px solid ${({ theme }) => theme.backgroundContainer};
padding: 0px 12px;
:last-of-type {
border-bottom: none;
}
}
`
export const ClickFavorited = styled.span`
display: flex;
align-items: center;
cursor: pointer;
&:hover {
color: ${({ theme }) => theme.textPrimary};
}
`
const ClickableContent = styled.div`
display: flex;
text-decoration: none;
color: ${({ theme }) => theme.textPrimary};
align-items: center;
cursor: pointer;
`
const ClickableName = styled(ClickableContent)`
gap: 8px;
max-width: 100%;
`
const FavoriteCell = styled(Cell)`
min-width: 40px;
color: ${({ theme }) => theme.textSecondary};
fill: none;
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
display: none;
}
`
const StyledHeaderRow = styled(StyledTokenRow)`
border-bottom: 1px solid;
border-color: ${({ theme }) => theme.backgroundOutline};
border-radius: 8px 8px 0px 0px;
color: ${({ theme }) => theme.textSecondary};
font-size: 12px;
height: 48px;
line-height: 16px;
padding: 0px 12px;
width: 100%;
&:hover {
background-color: ${({ theme }) => theme.none};
}
@media only screen and (max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT}) {
padding-right: 24px;
}
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
justify-content: space-between;
padding: 0px 12px;
}
`
const ListNumberCell = styled(Cell)`
color: ${({ theme }) => theme.textSecondary};
min-width: 32px;
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
display: none;
}
`
const DataCell = styled(Cell)<{ sortable: boolean }>`
justify-content: flex-end;
min-width: 80px;
user-select: ${({ sortable }) => (sortable ? 'none' : 'unset')};
&:hover {
color: ${({ theme, sortable }) => sortable && theme.white};
background-color: ${({ theme, sortable }) => sortable && theme.accentActionSoft};
}
`
const MarketCapCell = styled(DataCell)`
padding-right: 8px;
@media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) {
display: none;
}
`
const NameCell = styled(Cell)`
justify-content: flex-start;
padding: 0px 8px;
min-width: 200px;
gap: 8px;
`
const PriceCell = styled(DataCell)`
padding-right: 8px;
`
const PercentChangeCell = styled(DataCell)`
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
display: none;
}
`
const PercentChangeInfoCell = styled(Cell)`
display: none;
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
display: flex;
justify-content: flex-end;
color: ${({ theme }) => theme.textSecondary};
font-size: 12px;
line-height: 16px;
}
`
const PriceInfoCell = styled(Cell)`
justify-content: flex-end;
flex: 1;
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
flex-direction: column;
align-items: flex-end;
}
`
const SortArrowCell = styled(Cell)`
padding-right: 2px;
`
const HeaderCellWrapper = styled.span<{ onClick?: () => void }>`
align-items: center;
cursor: ${({ onClick }) => (onClick ? 'pointer' : 'unset')};
display: flex;
height: 100%;
justify-content: flex-end;
width: 100%;
`
const SparkLineCell = styled(Cell)`
padding: 0px 24px;
min-width: 120px;
@media only screen and (max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT}) {
display: none;
}
`
const SparkLine = styled(Cell)`
width: 124px;
height: 42px;
`
const StyledLink = styled(Link)`
text-decoration: none;
`
const TokenInfoCell = styled(Cell)`
gap: 8px;
line-height: 24px;
font-size: 16px;
max-width: inherit;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
justify-content: flex-start;
flex-direction: column;
gap: 0px;
width: max-content;
font-weight: 500;
}
`
const TokenName = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
`
const TokenSymbol = styled(Cell)`
color: ${({ theme }) => theme.textTertiary};
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
font-size: 12px;
height: 16px;
justify-content: flex-start;
width: 100%;
}
`
const VolumeCell = styled(DataCell)`
padding-right: 8px;
@media only screen and (max-width: ${LARGE_MEDIA_BREAKPOINT}) {
display: none;
}
`
const SmallLoadingBubble = styled(LoadingBubble)`
width: 25%;
`
const MediumLoadingBubble = styled(LoadingBubble)`
width: 65%;
`
const LongLoadingBubble = styled(LoadingBubble)`
width: 90%;
`
const IconLoadingBubble = styled(LoadingBubble)`
border-radius: 50%;
width: 24px;
`
const SparkLineLoadingBubble = styled(LongLoadingBubble)`
height: 4px;
`
/* formatting for volume with timeframe header display */
function getHeaderDisplay(category: string, timeframe: string): string {
if (category === Category.volume) return `${TIME_DISPLAYS[timeframe]} ${category}`
return category
}
/* Get singular header cell for header row */
function HeaderCell({
category,
sortable,
}: {
category: Category // TODO: change this to make it work for trans
sortable: boolean
}) {
const theme = useTheme()
const sortDirection = useAtomValue<SortDirection>(sortDirectionAtom)
const handleSortCategory = useSetSortCategory(category)
const sortCategory = useAtomValue<Category>(sortCategoryAtom)
const timeframe = useAtomValue<TimePeriod>(filterTimeAtom)
if (sortCategory === category) {
return (
<HeaderCellWrapper onClick={handleSortCategory}>
<SortArrowCell>
{sortDirection === SortDirection.increasing ? (
<ArrowUp size={14} color={theme.accentActive} />
) : (
<ArrowDown size={14} color={theme.accentActive} />
)}
</SortArrowCell>
{getHeaderDisplay(category, timeframe)}
</HeaderCellWrapper>
)
}
if (sortable) {
return (
<HeaderCellWrapper onClick={handleSortCategory}>
<SortArrowCell>
<ArrowUp size={14} visibility="hidden" />
</SortArrowCell>
{getHeaderDisplay(category, timeframe)}
</HeaderCellWrapper>
)
}
return <HeaderCellWrapper>{getHeaderDisplay(category, timeframe)}</HeaderCellWrapper>
}
/* Token Row: skeleton row component */
export function TokenRow({
address,
header,
favorited,
listNumber,
tokenInfo,
price,
percentChange,
marketCap,
volume,
sparkLine,
}: {
address: ReactNode
header: boolean
favorited: ReactNode
listNumber: ReactNode
tokenInfo: ReactNode
price: ReactNode
percentChange: ReactNode
marketCap: ReactNode
volume: ReactNode
sparkLine: ReactNode
}) {
const rowCells = (
<>
<FavoriteCell>{favorited}</FavoriteCell>
<ListNumberCell>{listNumber}</ListNumberCell>
<NameCell>{tokenInfo}</NameCell>
<PriceCell sortable={header}>{price}</PriceCell>
<PercentChangeCell sortable={header}>{percentChange}</PercentChangeCell>
<MarketCapCell sortable={header}>{marketCap}</MarketCapCell>
<VolumeCell sortable={header}>{volume}</VolumeCell>
<SparkLineCell>{sparkLine}</SparkLineCell>
</>
)
if (header) return <StyledHeaderRow>{rowCells}</StyledHeaderRow>
return <StyledTokenRow>{rowCells}</StyledTokenRow>
}
/* Header Row: top header row component for table */
export function HeaderRow() {
return (
<TokenRow
address={null}
header={true}
favorited={null}
listNumber="#"
tokenInfo={<Trans>Token Name</Trans>}
price={<HeaderCell category={Category.price} sortable />}
percentChange={<HeaderCell category={Category.percentChange} sortable />}
marketCap={<HeaderCell category={Category.marketCap} sortable />}
volume={<HeaderCell category={Category.volume} sortable />}
sparkLine={null}
/>
)
}
/* Loading State: row component with loading bubbles */
export function LoadingRow() {
return (
<TokenRow
address={null}
header={false}
favorited={null}
listNumber={<SmallLoadingBubble />}
tokenInfo={
<>
<IconLoadingBubble />
<MediumLoadingBubble />
</>
}
price={<MediumLoadingBubble />}
percentChange={<LoadingBubble />}
marketCap={<LoadingBubble />}
volume={<LoadingBubble />}
sparkLine={<SparkLineLoadingBubble />}
/>
)
}
/* Loaded State: row component with token information */
export default function LoadedRow({
tokenAddress,
tokenListIndex,
tokenListLength,
data,
timePeriod,
}: {
tokenAddress: string
tokenListIndex: number
tokenListLength: number
data: TokenData
timePeriod: TimePeriod
}) {
const token = useToken(tokenAddress)
const currency = useCurrency(tokenAddress)
const tokenName = token?.name ?? ''
const tokenSymbol = token?.symbol ?? ''
const tokenData = data[tokenAddress]
const theme = useTheme()
const [favoriteTokens] = useAtom(favoritesAtom)
const isFavorited = favoriteTokens.includes(tokenAddress)
const toggleFavorite = useToggleFavorite(tokenAddress)
const isPositive = Math.sign(tokenData.delta) > 0
const filterString = useAtomValue(filterStringAtom)
const filterNetwork = useAtomValue(filterNetworkAtom)
const filterTime = useAtomValue(filterTimeAtom) // filter time period for top tokens table
const tokenPercentChangeInfo = (
<>
{tokenData.delta}%
<ArrowCell>
{isPositive ? (
<ArrowUpRight size={16} color={theme.accentSuccess} />
) : (
<ArrowDownRight size={16} color={theme.accentFailure} />
)}
</ArrowCell>
</>
)
const exploreTokenSelectedEventProperties = {
chain_id: filterNetwork,
token_address: tokenAddress,
token_symbol: token?.symbol,
token_list_index: tokenListIndex,
token_list_length: tokenListLength,
time_frame: filterTime,
search_token_address_input: filterString,
}
const heartColor = isFavorited ? theme.accentActive : undefined
// TODO: currency logo sizing mobile (32px) vs. desktop (24px)
return (
<StyledLink
to={`/tokens/${tokenAddress}`}
onClick={() => sendAnalyticsEvent(EventName.EXPLORE_TOKEN_ROW_CLICKED, exploreTokenSelectedEventProperties)}
>
<TokenRow
address={tokenAddress}
header={false}
favorited={
<ClickFavorited
onClick={(e) => {
e.preventDefault()
toggleFavorite()
}}
>
<Heart size={18} color={heartColor} fill={heartColor} />
</ClickFavorited>
}
listNumber={tokenListIndex + 1}
tokenInfo={
<ClickableName>
<CurrencyLogo currency={currency} />
<TokenInfoCell>
<TokenName>{tokenName}</TokenName>
<TokenSymbol>{tokenSymbol}</TokenSymbol>
</TokenInfoCell>
</ClickableName>
}
price={
<ClickableContent>
<PriceInfoCell>
{formatDollarAmount(tokenData.price)}
<PercentChangeInfoCell>{tokenPercentChangeInfo}</PercentChangeInfoCell>
</PriceInfoCell>
</ClickableContent>
}
percentChange={<ClickableContent>{tokenPercentChangeInfo}</ClickableContent>}
marketCap={<ClickableContent>{formatAmount(tokenData.marketCap).toUpperCase()}</ClickableContent>}
volume={<ClickableContent>{formatAmount(tokenData.volume[timePeriod]).toUpperCase()}</ClickableContent>}
sparkLine={
<SparkLine>
<ParentSize>{({ width, height }) => <SparklineChart width={width} height={height} />}</ParentSize>
</SparkLine>
}
/>
</StyledLink>
)
}

View File

@@ -1,204 +0,0 @@
import {
favoritesAtom,
filterStringAtom,
filterTimeAtom,
showFavoritesAtom,
sortCategoryAtom,
sortDirectionAtom,
} from 'components/Explore/state'
import { useAllTokens } from 'hooks/Tokens'
import useTopTokens, { TimePeriod, TokenData } from 'hooks/useTopTokens'
import { useAtomValue } from 'jotai/utils'
import { ReactNode, useCallback, useMemo } from 'react'
import { AlertTriangle } from 'react-feather'
import styled from 'styled-components/macro'
import { MAX_WIDTH_MEDIA_BREAKPOINT } from '../constants'
import { Category, SortDirection } from '../types'
import LoadedRow, { HeaderRow, LoadingRow } from './TokenRow'
const GridContainer = styled.div`
display: flex;
flex-direction: column;
max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT};
background-color: ${({ theme }) => theme.backgroundContainer};
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;
margin-right: auto;
border-radius: 8px;
justify-content: center;
align-items: center;
border: 1px solid ${({ theme }) => theme.backgroundOutline};
`
const NoTokenDisplay = styled.div`
display: flex;
justify-content: center;
width: 100%;
height: 60px;
color: ${({ theme }) => theme.textSecondary};
font-size: 16px;
font-weight: 500;
align-items: center;
padding: 0px 28px;
gap: 8px;
`
const TokenRowsContainer = styled.div`
padding: 4px 0px;
width: 100%;
`
function useFilteredTokens(addresses: string[]) {
const filterString = useAtomValue(filterStringAtom)
const favoriteTokens = useAtomValue(favoritesAtom)
const showFavorites = useAtomValue(showFavoritesAtom)
const shownTokens = showFavorites ? favoriteTokens : addresses
const allTokens = useAllTokens()
return useMemo(
() =>
shownTokens.filter((tokenAddress) => {
const token = allTokens[tokenAddress]
const tokenName = token?.name ?? ''
const tokenSymbol = token?.symbol ?? ''
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)
return nameIncludesFilterString || symbolIncludesFilterString || addressIncludesFilterString
}),
[allTokens, shownTokens, filterString]
)
}
function useSortedTokens(addresses: string[], tokenData: TokenData | null) {
const sortCategory = useAtomValue(sortCategoryAtom)
const sortDirection = useAtomValue(sortDirectionAtom)
const timePeriod = useAtomValue<TimePeriod>(filterTimeAtom)
const sortFn = useCallback(
(a: any, b: any) => {
if (a > b) {
return sortDirection === SortDirection.decreasing ? -1 : 1
} else if (a < b) {
return sortDirection === SortDirection.decreasing ? 1 : -1
}
return 0
},
[sortDirection]
)
return useMemo(
() =>
addresses.sort((token1Address, token2Address) => {
if (!tokenData) {
return 0
}
const token1 = tokenData[token1Address] as any
const token2 = tokenData[token2Address] as any
if (!token1 || !token2 || !sortDirection || !sortCategory) {
return 0
}
let a: number
let b: number
switch (sortCategory) {
case Category.marketCap:
a = token1.marketCap
b = token2.marketCap
break
case Category.percentChange:
a = token1.delta
b = token2.delta
break
case Category.price:
a = token1.price
b = token2.price
break
case Category.volume:
a = token1.volume[timePeriod]
b = token2.volume[timePeriod]
break
}
return sortFn(a, b)
}),
[addresses, tokenData, sortDirection, sortCategory, sortFn, timePeriod]
)
}
function NoTokensState({ message }: { message: ReactNode }) {
return (
<GridContainer>
<HeaderRow />
<NoTokenDisplay>{message}</NoTokenDisplay>
</GridContainer>
)
}
const LOADING_ROWS = Array.from({ length: 100 })
.fill(0)
.map((_item, index) => <LoadingRow key={index} />)
function LoadingTokenTable() {
return (
<GridContainer>
<HeaderRow />
<TokenRowsContainer>{LOADING_ROWS}</TokenRowsContainer>
</GridContainer>
)
}
export default function TokenTable() {
const { data, error, loading } = useTopTokens()
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)
/* loading and error state */
if (loading) {
return <LoadingTokenTable />
} else if (error || data === null) {
return (
<NoTokensState
message={
<>
<AlertTriangle size={16} />
An error occured loading tokens. Please try again.
</>
}
/>
)
}
if (showFavorites && filteredAndSortedTokens.length === 0) {
return <NoTokensState message="You have no favorited tokens" />
}
if (!showFavorites && filteredAndSortedTokens.length === 0) {
return <NoTokensState message="No tokens found" />
}
return (
<GridContainer>
<HeaderRow />
<TokenRowsContainer>
{filteredAndSortedTokens.map((tokenAddress, index) => (
<LoadedRow
key={tokenAddress}
tokenAddress={tokenAddress}
tokenListIndex={index}
tokenListLength={filteredAndSortedTokens.length}
data={data}
timePeriod={timePeriod}
/>
))}
</TokenRowsContainer>
</GridContainer>
)
}

View File

@@ -1,49 +0,0 @@
import { SupportedChainId } from 'constants/chains'
import { TimePeriod } from 'hooks/useTopTokens'
import { atom, useAtom } from 'jotai'
import { atomWithReset, atomWithStorage } from 'jotai/utils'
import { useCallback } from 'react'
import { Category, SortDirection } from './types'
export const favoritesAtom = atomWithStorage<string[]>('favorites', [])
export const showFavoritesAtom = atomWithStorage<boolean>('showFavorites', false)
export const filterStringAtom = atomWithReset<string>('')
export const filterNetworkAtom = atom<SupportedChainId>(SupportedChainId.MAINNET)
export const filterTimeAtom = atom<TimePeriod>(TimePeriod.day)
export const sortCategoryAtom = atom<Category>(Category.marketCap)
export const sortDirectionAtom = atom<SortDirection>(SortDirection.decreasing)
/* for favoriting tokens */
export function useToggleFavorite(tokenAddress: string) {
const [favoriteTokens, updateFavoriteTokens] = useAtom(favoritesAtom)
return useCallback(() => {
let updatedFavoriteTokens
if (favoriteTokens.includes(tokenAddress)) {
updatedFavoriteTokens = favoriteTokens.filter((address: string) => {
return address !== tokenAddress
})
} else {
updatedFavoriteTokens = [...favoriteTokens, tokenAddress]
}
updateFavoriteTokens(updatedFavoriteTokens)
}, [favoriteTokens, tokenAddress, updateFavoriteTokens])
}
/* keep track of sort category for token table */
export function useSetSortCategory(category: Category) {
const [sortCategory, setSortCategory] = useAtom(sortCategoryAtom)
const [sortDirection, setDirectionCategory] = useAtom(sortDirectionAtom)
return useCallback(() => {
if (category === sortCategory) {
const oppositeDirection =
sortDirection === SortDirection.increasing ? SortDirection.decreasing : SortDirection.increasing
setDirectionCategory(oppositeDirection)
} else {
setSortCategory(category)
setDirectionCategory(SortDirection.decreasing)
}
}, [category, sortCategory, setSortCategory, sortDirection, setDirectionCategory])
}

View File

@@ -1,10 +0,0 @@
export enum Category {
percentChange = '% Change',
marketCap = 'Market Cap',
price = 'Price',
volume = 'Volume',
}
export enum SortDirection {
increasing = 'Increasing',
decreasing = 'Decreasing',
}

View File

@@ -1,7 +1,13 @@
import { FeatureFlag, useUpdateFlag } from 'featureFlags'
import { Phase0Variant, usePhase0Flag } from 'featureFlags/flags/phase0'
import { Phase1Variant, usePhase1Flag } from 'featureFlags/flags/phase1'
import { ReactNode } from 'react'
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
import { FavoriteTokensVariant, useFavoriteTokensFlag } from 'featureFlags/flags/favoriteTokens'
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 { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
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'
@@ -11,7 +17,7 @@ const StyledModal = styled.div`
position: fixed;
display: flex;
left: 50%;
top: 50%;
top: 50vh;
transform: translate(-50%, -50%);
width: 400px;
height: fit-content;
@@ -40,49 +46,151 @@ const Row = styled.div`
const CloseButton = styled.button`
cursor: pointer;
background: ${({ theme }) => theme.none};
background: transparent;
border: none;
color: ${({ theme }) => theme.textPrimary};
`
const ToggleButton = styled.button`
cursor: pointer;
background: transparent;
border: none;
color: ${({ theme }) => theme.textPrimary};
`
const Header = styled(Row)`
font-weight: 600;
font-size: 20px;
font-size: 16px;
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
margin-bottom: 8px;
`
const FlagName = styled.span`
font-size: 16px;
line-height: 20px;
color: ${({ theme }) => theme.textPrimary};
`
const FlagGroupName = styled.span`
font-size: 20px;
line-height: 24px;
color: ${({ theme }) => theme.textPrimary};
font-weight: 600;
`
const FlagDescription = styled.span`
font-size: 12px;
line-height: 16px;
color: ${({ theme }) => theme.textSecondary};
display: flex;
align-items: center;
`
const FlagVariantSelection = styled.select`
border-radius: 12px;
padding: 8px;
background: ${({ theme }) => theme.backgroundInteractive};
font-weight: 600;
font-size: 16px;
border: none;
color: ${({ theme }) => theme.textPrimary};
cursor: pointer;
:hover {
background: ${({ theme }) => theme.backgroundOutline};
}
`
const FlagInfo = styled.div`
display: flex;
flex-direction: column;
padding-left: 8px;
`
const SaveButton = styled.button`
border-radius: 12px;
padding: 8px;
background: ${({ theme }) => theme.backgroundInteractive};
font-weight: 600;
font-size: 16px;
border: none;
color: ${({ theme }) => theme.textPrimary};
cursor: pointer;
:hover {
background: ${({ theme }) => theme.backgroundOutline};
}
`
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)
return (
<Row key={featureFlag}>
{featureFlag}: {label}
<select
<FlagInfo>
<FlagName>{featureFlag}</FlagName>
<FlagDescription>{label}</FlagDescription>
</FlagInfo>
<FlagVariantSelection
id={featureFlag}
value={value}
onChange={(e) => {
updateFlag(featureFlag, e.target.value)
window.location.reload()
setCount(count + 1)
}}
value={featureFlags[featureFlag]}
>
{variants.map((variant) => (
{Object.values(variant).map((variant) => (
<Variant key={variant} option={variant} />
))}
</select>
</FlagVariantSelection>
</Row>
)
}
@@ -99,19 +207,52 @@ export default function FeatureFlagModal() {
<X size={24} />
</CloseButton>
</Header>
<FeatureFlagOption
variants={Object.values(Phase0Variant)}
value={usePhase0Flag()}
featureFlag={FeatureFlag.phase0}
label="All Phase 0 changes (redesign, explore, header)."
/>
<FeatureFlagOption
variants={Object.values(Phase1Variant)}
value={usePhase1Flag()}
featureFlag={FeatureFlag.phase1}
label="All Phase 1 changes (nft features)."
/>
<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={TokenSafetyVariant}
value={useTokenSafetyFlag()}
featureFlag={FeatureFlag.tokenSafety}
label="Token Safety"
/>
</FeatureFlagGroup>
<FeatureFlagGroup name="Phase 0 Follow-ups">
<FeatureFlagOption
variant={FavoriteTokensVariant}
value={useFavoriteTokensFlag()}
featureFlag={FeatureFlag.favoriteTokens}
label="Favorite Tokens"
/>
</FeatureFlagGroup>
<FeatureFlagGroup name="Phase 1">
<FeatureFlagOption variant={NftVariant} value={useNftFlag()} featureFlag={FeatureFlag.nft} label="NFTs" />
</FeatureFlagGroup>
<FeatureFlagGroup name="Debug">
<FeatureFlagOption
variant={TraceJsonRpcVariant}
value={useTraceJsonRpcFlag()}
featureFlag={FeatureFlag.traceJsonRpc}
label="Enables JSON-RPC tracing"
/>
</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

@@ -1,23 +1,16 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { getConnection } from 'connection/utils'
import { getChainInfo } from 'constants/chainInfo'
import { CHAIN_IDS_TO_NAMES, SupportedChainId } from 'constants/chains'
import useParsedQueryString from 'hooks/useParsedQueryString'
import usePrevious from 'hooks/usePrevious'
import { SupportedChainId } from 'constants/chains'
import useSelectChain from 'hooks/useSelectChain'
import useSyncChainQuery from 'hooks/useSyncChainQuery'
import { darken } from 'polished'
import { ParsedQs } from 'qs'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useRef } from 'react'
import { AlertTriangle, ArrowDownCircle, ChevronDown } from 'react-feather'
import { useLocation, useNavigate } from 'react-router-dom'
import { useCloseModal, useModalIsOpen, useOpenModal, useToggleModal } from 'state/application/hooks'
import { addPopup, ApplicationModal } from 'state/application/reducer'
import { updateConnectionError } from 'state/connection/reducer'
import { useAppDispatch } from 'state/hooks'
import { ApplicationModal } from 'state/application/reducer'
import styled from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
import { replaceURLParam } from 'utils/routes'
import { isChainAllowed, switchChain } from 'utils/switchChain'
import { isMobile } from 'utils/userAgent'
const ActiveRowLinkList = styled.div`
@@ -59,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;
}
`
@@ -118,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;
}
@@ -132,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;
}
`
@@ -159,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;
}
`
@@ -185,7 +178,7 @@ const BridgeLabel = ({ chainId }: { chainId: SupportedChainId }) => {
case SupportedChainId.ARBITRUM_RINKEBY:
return <Trans>Arbitrum Bridge</Trans>
case SupportedChainId.OPTIMISM:
case SupportedChainId.OPTIMISTIC_KOVAN:
case SupportedChainId.OPTIMISM_GOERLI:
return <Trans>Optimism Bridge</Trans>
case SupportedChainId.POLYGON:
case SupportedChainId.POLYGON_MUMBAI:
@@ -203,7 +196,7 @@ const ExplorerLabel = ({ chainId }: { chainId: SupportedChainId }) => {
case SupportedChainId.ARBITRUM_RINKEBY:
return <Trans>Arbiscan</Trans>
case SupportedChainId.OPTIMISM:
case SupportedChainId.OPTIMISTIC_KOVAN:
case SupportedChainId.OPTIMISM_GOERLI:
return <Trans>Optimistic Etherscan</Trans>
case SupportedChainId.POLYGON:
case SupportedChainId.POLYGON_MUMBAI:
@@ -278,24 +271,6 @@ function Row({
return rowContent
}
const getParsedChainId = (parsedQs?: ParsedQs) => {
const chain = parsedQs?.chain
if (!chain || typeof chain !== 'string') return
return getChainIdFromName(chain)
}
const getChainIdFromName = (name: string) => {
const entry = Object.entries(CHAIN_IDS_TO_NAMES).find(([_, n]) => n === name)
const chainId = entry?.[0]
return chainId ? parseInt(chainId) : undefined
}
const getChainNameFromId = (id: string | number) => {
// casting here may not be right but fine to return undefined if it's not a supported chain ID
return CHAIN_IDS_TO_NAMES[id as SupportedChainId] || ''
}
const NETWORK_SELECTOR_CHAINS = [
SupportedChainId.MAINNET,
SupportedChainId.POLYGON,
@@ -305,24 +280,7 @@ const NETWORK_SELECTOR_CHAINS = [
]
export default function NetworkSelector() {
const dispatch = useAppDispatch()
const { chainId, provider, connector, isActive } = useWeb3React()
const [previousChainId, setPreviousChainId] = useState<number | undefined>(undefined)
// Can't use `usePrevious` because `chainId` can be undefined while activating.
useEffect(() => {
if (chainId && chainId !== previousChainId) {
setPreviousChainId(chainId)
}
}, [chainId, previousChainId])
const parsedQs = useParsedQueryString()
const urlChainId = getParsedChainId(parsedQs)
const previousUrlChainId = usePrevious(urlChainId)
const navigate = useNavigate()
const { search } = useLocation()
const { chainId, provider } = useWeb3React()
const node = useRef<HTMLDivElement>(null)
const isOpen = useModalIsOpen(ApplicationModal.NETWORK_SELECTOR)
@@ -332,62 +290,8 @@ export default function NetworkSelector() {
const info = getChainInfo(chainId)
const replaceURLChainParam = useCallback(() => {
if (chainId) {
navigate({ search: replaceURLParam(search, 'chain', getChainNameFromId(chainId)) }, { replace: true })
}
}, [chainId, search, navigate])
const onSelectChain = useCallback(
async (targetChain: SupportedChainId, skipClose?: boolean) => {
if (!connector) return
const connectionType = getConnection(connector).type
try {
dispatch(updateConnectionError({ connectionType, error: undefined }))
await switchChain(connector, targetChain)
} catch (error) {
console.error('Failed to switch networks', error)
dispatch(updateConnectionError({ connectionType, error: error.message }))
dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: `failed-network-switch` }))
// If we activate a chain and it fails, reset the query param to the current chainId
replaceURLChainParam()
}
if (!skipClose) {
closeModal()
}
},
[connector, closeModal, dispatch, replaceURLChainParam]
)
// If there is no chain query param, set it to the current chain
useEffect(() => {
const chainQueryUnpopulated = !urlChainId
if (chainQueryUnpopulated && chainId) {
replaceURLChainParam()
}
}, [chainId, urlChainId, replaceURLChainParam])
// If the chain changed but the query param is stale, update to the current chain
useEffect(() => {
const chainChanged = chainId !== previousChainId
const chainQueryStale = urlChainId !== chainId
if (chainChanged && chainQueryStale) {
replaceURLChainParam()
}
}, [chainId, previousChainId, replaceURLChainParam, urlChainId])
// If the query param changed, and the chain didn't change, then activate the new chain
useEffect(() => {
const chainQueryManuallyUpdated = urlChainId && urlChainId !== previousUrlChainId
if (chainQueryManuallyUpdated && isActive) {
onSelectChain(urlChainId, true)
}
}, [onSelectChain, urlChainId, previousUrlChainId, isActive])
const selectChain = useSelectChain()
useSyncChainQuery()
if (!chainId || !provider) {
return null
@@ -423,11 +327,16 @@ export default function NetworkSelector() {
<FlyoutHeader>
<Trans>Select a {!onSupportedChain ? ' supported ' : ''}network</Trans>
</FlyoutHeader>
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) =>
isChainAllowed(connector, chainId) ? (
<Row onSelectChain={onSelectChain} 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

@@ -5,12 +5,11 @@ import { getChainInfo } from 'constants/chainInfo'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import useGasPrice from 'hooks/useGasPrice'
import useMachineTimeMs from 'hooks/useMachineTime'
import useTheme from 'hooks/useTheme'
import JSBI from 'jsbi'
import useBlockNumber from 'lib/hooks/useBlockNumber'
import ms from 'ms.macro'
import { useEffect, useState } from 'react'
import styled, { keyframes } from 'styled-components/macro'
import styled, { keyframes, useTheme } from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
@@ -27,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

@@ -3,8 +3,7 @@ import useScrollPosition from '@react-hook/window-scroll'
import { useWeb3React } from '@web3-react/core'
import { getChainInfoOrDefault } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import { Phase0Variant, usePhase0Flag } from 'featureFlags/flags/phase0'
import useTheme from 'hooks/useTheme'
import { TokensVariant, useTokensFlag } from 'featureFlags/flags/tokens'
import { darken } from 'polished'
import { NavLink, useLocation } from 'react-router-dom'
import { Text } from 'rebass'
@@ -13,7 +12,7 @@ import { useUserHasAvailableClaim } from 'state/claim/hooks'
import { useNativeCurrencyBalances } from 'state/connection/hooks'
import { useUserHasSubmittedClaim } from 'state/transactions/hooks'
import { useDarkModeManager } from 'state/user/hooks'
import styled from 'styled-components/macro'
import styled, { useTheme } from 'styled-components/macro'
import { ReactComponent as Logo } from '../../assets/svg/logo.svg'
import { ExternalLink, ThemedText } from '../../theme'
@@ -47,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;
`};
@@ -82,7 +81,7 @@ const HeaderElement = styled.div`
margin-left: 8px;
}
${({ theme }) => theme.mediaWidth.upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
align-items: center;
`};
`
@@ -98,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;
@@ -158,7 +157,7 @@ const UNIWrapper = styled.span`
`
const BalanceText = styled(Text)`
${({ theme }) => theme.mediaWidth.upToExtraSmall`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToExtraSmall`
display: none;
`};
`
@@ -169,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 {
@@ -245,7 +244,7 @@ const StyledExternalLink = styled(ExternalLink)`
`
export default function Header() {
const phase0Flag = usePhase0Flag()
const tokensFlag = useTokensFlag()
const { account, chainId } = useWeb3React()
@@ -292,9 +291,9 @@ export default function Header() {
<StyledNavLink id={`swap-nav-link`} to={'/swap'}>
<Trans>Swap</Trans>
</StyledNavLink>
{phase0Flag === Phase0Variant.Enabled && (
<StyledNavLink id={`explore-nav-link`} to={'/explore'}>
<Trans>Explore</Trans>
{tokensFlag === TokensVariant.Enabled && (
<StyledNavLink id={`tokens-nav-link`} to={'/tokens'}>
<Trans>Tokens</Trans>
</StyledNavLink>
)}
<StyledNavLink

View File

@@ -10,7 +10,6 @@ const TextWrapper = styled.span<{
textColor?: string
}>`
margin-left: ${({ margin }) => margin && '4px'};
color: ${({ theme, link, textColor }) => (link ? theme.deprecated_blue1 : textColor ?? theme.deprecated_text1)};
font-size: ${({ fontSize }) => fontSize ?? 'inherit'};
@media screen and (max-width: 600px) {

View File

@@ -1,12 +1,17 @@
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 CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
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;
@@ -16,27 +21,60 @@ 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 }: { connectionType: ConnectionType }) {
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
case ConnectionType.FORTMATIC:
image = <img src={FortmaticIcon} alt="Fortmatic" />
break
const SockContainer = styled.div`
position: absolute;
display: flex;
justify-content: center;
border-radius: 50%;
width: 16px;
height: 16px;
bottom: -4px;
right: -4px;
`
const SockImg = styled.img`
width: 16px;
height: 16px;
`
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={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

@@ -19,7 +19,7 @@ const HandleAccent = styled.path`
stroke-width: 1.5;
stroke: ${({ theme }) => theme.deprecated_white};
opacity: 0.6;
opacity: ${({ theme }) => theme.opacity.hover};
`
const LabelGroup = styled.g<{ visible: boolean }>`

View File

@@ -14,7 +14,7 @@ export function useDensityChartData({
currencyB: Currency | undefined
feeAmount: FeeAmount | undefined
}) {
const { isLoading, isUninitialized, isError, error, data } = usePoolActiveLiquidity(currencyA, currencyB, feeAmount)
const { isLoading, error, data } = usePoolActiveLiquidity(currencyA, currencyB, feeAmount)
const formatData = useCallback(() => {
if (!data?.length) {
@@ -42,10 +42,8 @@ export function useDensityChartData({
return useMemo(() => {
return {
isLoading,
isUninitialized,
isError,
error,
formattedData: !isLoading && !isUninitialized ? formatData() : undefined,
formattedData: !isLoading ? formatData() : undefined,
}
}, [isLoading, isUninitialized, isError, error, formatData])
}, [isLoading, error, formatData])
}

View File

@@ -6,13 +6,12 @@ import { AutoColumn, ColumnCenter } from 'components/Column'
import Loader from 'components/Loader'
import { format } from 'd3'
import { useColor } from 'hooks/useColor'
import useTheme from 'hooks/useTheme'
import { saturate } from 'polished'
import React, { ReactNode, useCallback, useMemo } from 'react'
import { BarChart2, CloudOff, Inbox } from 'react-feather'
import { batch } from 'react-redux'
import { Bound } from 'state/mint/v3/actions'
import styled from 'styled-components/macro'
import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from '../../theme'
import { Chart } from './Chart'
@@ -96,7 +95,7 @@ export default function LiquidityChartRangeInput({
const isSorted = currencyA && currencyB && currencyA?.wrapped.sortsBefore(currencyB?.wrapped)
const { isLoading, isUninitialized, isError, error, formattedData } = useDensityChartData({
const { isLoading, error, formattedData } = useDensityChartData({
currencyA,
currencyB,
feeAmount,
@@ -157,10 +156,12 @@ export default function LiquidityChartRangeInput({
[isSorted, price, ticksAtLimit]
)
if (isError) {
if (error) {
sendEvent('exception', { description: error.toString(), fatal: false })
}
const isUninitialized = !currencyA || !currencyB || (formattedData === undefined && !isLoading)
return (
<AutoColumn gap="md" style={{ minHeight: '200px' }}>
{isUninitialized ? (
@@ -170,12 +171,12 @@ export default function LiquidityChartRangeInput({
/>
) : isLoading ? (
<InfoBox icon={<Loader size="40px" stroke={theme.deprecated_text4} />} />
) : isError ? (
) : error ? (
<InfoBox
message={<Trans>Liquidity data not available.</Trans>}
icon={<CloudOff size={56} stroke={theme.deprecated_text4} />}
/>
) : !formattedData || formattedData === [] || !price ? (
) : !formattedData || formattedData.length === 0 || !price ? (
<InfoBox
message={<Trans>There is no liquidity data.</Trans>}
icon={<BarChart2 size={56} stroke={theme.deprecated_text4} />}

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

@@ -9,12 +9,13 @@ const rotate = keyframes`
}
`
const StyledSVG = styled.svg<{ size: string; stroke?: string }>`
const StyledSVG = styled.svg<{ size: string; stroke?: string; redesignFlag?: boolean }>`
animation: 2s ${rotate} linear infinite;
height: ${({ size }) => size};
width: ${({ size }) => size};
path {
stroke: ${({ stroke, theme }) => stroke ?? theme.deprecated_primary1};
stroke: ${({ stroke, redesignFlag, theme }) =>
redesignFlag ? theme.accentActive : stroke ?? theme.deprecated_primary1};
}
`
@@ -25,17 +26,29 @@ const StyledSVG = styled.svg<{ size: string; stroke?: string }>`
export default function Loader({
size = '16px',
stroke,
strokeWidth,
redesignFlag,
...rest
}: {
size?: string
stroke?: string
strokeWidth?: number
redesignFlag?: boolean
[k: string]: any
}) {
return (
<StyledSVG viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" size={size} stroke={stroke} {...rest}>
<StyledSVG
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
size={size}
stroke={stroke}
redesignFlag={redesignFlag}
{...rest}
>
<path
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 9.27455 20.9097 6.80375 19.1414 5"
strokeWidth="2.5"
strokeWidth={strokeWidth ?? '2.5'}
strokeLinecap="round"
strokeLinejoin="round"
/>

View File

@@ -1,23 +1,34 @@
import { useState } from 'react'
import { Slash } from 'react-feather'
import { ImageProps } from 'rebass'
import useTheme from '../../hooks/useTheme'
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) {
@@ -35,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

@@ -14,6 +14,7 @@ import {
ChevronLeft,
Coffee,
FileText,
Flag,
Globe,
HelpCircle,
Info,
@@ -109,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;
@@ -277,7 +278,7 @@ export default function Menu() {
</ToggleMenuItem>
<ToggleMenuItem onClick={() => toggleDarkMode()}>
<div>{darkMode ? <Trans>Light Theme</Trans> : <Trans>Dark Theme</Trans>}</div>
{darkMode ? <Moon opacity={0.6} size={16} /> : <Sun opacity={0.6} size={16} />}
{darkMode ? <Sun opacity={0.6} size={16} /> : <Moon opacity={0.6} size={16} />}
</ToggleMenuItem>
<MenuItem href="https://docs.uniswap.org/">
<div>
@@ -291,6 +292,11 @@ export default function Menu() {
</div>
<FileText opacity={0.6} size={16} />
</ToggleMenuItem>
{(isDevelopmentEnv() || isStagingEnv()) && (
<ToggleMenuItem onClick={openFeatureFlagsModal}>
Feature Flags <Flag opacity={0.6} size={16} />
</ToggleMenuItem>
)}
{showUNIClaimOption && (
<UNIbutton
onClick={openClaimModal}
@@ -302,9 +308,6 @@ export default function Menu() {
<Trans>Claim UNI</Trans>
</UNIbutton>
)}
{(isDevelopmentEnv() || isStagingEnv()) && (
<ToggleMenuItem onClick={openFeatureFlagsModal}>Feature Flags</ToggleMenuItem>
)}
</MenuFlyout>
)
}

View File

@@ -4,14 +4,15 @@ import React from 'react'
import { animated, useSpring, useTransition } from 'react-spring'
import { useGesture } from 'react-use-gesture'
import styled, { css } from 'styled-components/macro'
import { Z_INDEX } from 'theme/zIndex'
import { isMobile } from '../../utils/userAgent'
const AnimatedDialogOverlay = animated(DialogOverlay)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const StyledDialogOverlay = styled(AnimatedDialogOverlay)`
const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ redesignFlag?: boolean }>`
&[data-reach-dialog-overlay] {
z-index: 2;
z-index: ${Z_INDEX.modalBackdrop};
background-color: transparent;
overflow: hidden;
@@ -19,14 +20,14 @@ const StyledDialogOverlay = styled(AnimatedDialogOverlay)`
align-items: center;
justify-content: center;
background-color: ${({ theme }) => theme.deprecated_modalBG};
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundScrim : theme.deprecated_modalBG)};
}
`
const AnimatedDialogContent = animated(DialogContent)
// destructure to not pass custom props to Dialog DOM element
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...rest }) => (
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, redesignFlag, ...rest }) => (
<AnimatedDialogContent {...rest} />
)).attrs({
'aria-label': 'dialog',
@@ -37,7 +38,8 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
margin: 0 0 2rem 0;
background-color: ${({ theme }) => theme.deprecated_bg0};
border: 1px solid ${({ theme }) => theme.deprecated_bg1};
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadow1)};
box-shadow: ${({ theme, redesignFlag }) =>
redesignFlag ? theme.deepShadow : `0 4px 8px 0 ${transparentize(0.95, theme.shadow1)}`};
padding: 0px;
width: 50vw;
overflow-y: auto;
@@ -58,11 +60,11 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
`}
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 &&
@@ -84,6 +86,7 @@ interface ModalProps {
maxHeight?: number
initialFocusRef?: React.RefObject<any>
children?: React.ReactNode
redesignFlag?: boolean
}
export default function Modal({
@@ -93,6 +96,7 @@ export default function Modal({
maxHeight = 90,
initialFocusRef,
children,
redesignFlag,
}: ModalProps) {
const fadeTransition = useTransition(isOpen, null, {
config: { duration: 200 },
@@ -124,6 +128,7 @@ export default function Modal({
onDismiss={onDismiss}
initialFocusRef={initialFocusRef}
unstable_lockFocusAcrossFrames={false}
redesignFlag={redesignFlag}
>
<StyledDialogContent
{...(isMobile
@@ -136,6 +141,7 @@ export default function Modal({
minHeight={minHeight}
maxHeight={maxHeight}
mobile={isMobile}
redesignFlag={redesignFlag}
>
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}

View File

@@ -1,8 +1,7 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { useContext } from 'react'
import { ArrowUpCircle } from 'react-feather'
import styled, { ThemeContext } from 'styled-components/macro'
import styled, { useTheme } from 'styled-components/macro'
import Circle from '../../assets/images/blue-loader.svg'
import { CloseIcon, CustomLightSpinner, ThemedText } from '../../theme'
@@ -49,7 +48,7 @@ export function SubmittedView({
onDismiss: () => void
hash: string | undefined
}) {
const theme = useContext(ThemeContext)
const theme = useTheme()
const { chainId } = useWeb3React()
return (

View File

@@ -0,0 +1,23 @@
import { style } from '@vanilla-extract/css'
import { lightGrayOverlayOnHover } from 'nft/css/common.css'
import { sprinkles } from '../../nft/css/sprinkles.css'
export const ChainSelector = style([
lightGrayOverlayOnHover,
sprinkles({
borderRadius: '8',
height: '40',
cursor: 'pointer',
border: 'none',
color: 'textPrimary',
background: 'none',
}),
])
export const Image = style([
sprinkles({
width: '20',
height: '20',
}),
])

View File

@@ -0,0 +1,118 @@
import { useWeb3React } from '@web3-react/core'
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 { TokenWarningRedIcon } from 'nft/components/icons'
import { subhead } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css'
import { useIsMobile } from 'nft/hooks'
import { useCallback, useRef, useState } from 'react'
import { ChevronDown, ChevronUp } from 'react-feather'
import { useTheme } from 'styled-components/macro'
import * as styles from './ChainSelector.css'
import ChainSelectorRow from './ChainSelectorRow'
import { NavDropdown } from './NavDropdown'
const NETWORK_SELECTOR_CHAINS = [
SupportedChainId.MAINNET,
SupportedChainId.POLYGON,
SupportedChainId.OPTIMISM,
SupportedChainId.ARBITRUM_ONE,
SupportedChainId.CELO,
]
interface ChainSelectorProps {
leftAlign?: boolean
}
export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
const { chainId } = useWeb3React()
const [isOpen, setIsOpen] = useState<boolean>(false)
const isMobile = useIsMobile()
const theme = useTheme()
const ref = useRef<HTMLDivElement>(null)
const modalRef = useRef<HTMLDivElement>(null)
useOnClickOutside(ref, () => setIsOpen(false), [modalRef])
const info = chainId ? getChainInfo(chainId) : undefined
const selectChain = useSelectChain()
useSyncChainQuery()
const [pendingChainId, setPendingChainId] = useState<SupportedChainId | undefined>(undefined)
const onSelectChain = useCallback(
async (targetChainId: SupportedChainId) => {
setPendingChainId(targetChainId)
await selectChain(targetChainId)
setPendingChainId(undefined)
setIsOpen(false)
},
[selectChain, setIsOpen]
)
if (!chainId) {
return null
}
const isSupported = !!info
const dropdown = (
<NavDropdown top="56" left={leftAlign ? '0' : 'auto'} right={leftAlign ? 'auto' : '0'} ref={modalRef}>
<Column paddingX="8">
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) => (
<ChainSelectorRow
onSelectChain={onSelectChain}
targetChain={chainId}
key={chainId}
isPending={chainId === pendingChainId}
/>
))}
</Column>
</NavDropdown>
)
const chevronProps = {
height: 20,
width: 20,
color: theme.textSecondary,
}
return (
<Box position="relative" ref={ref}>
<Row
as="button"
gap="8"
className={styles.ChainSelector}
background={isOpen ? 'accentActiveSoft' : 'none'}
onClick={() => setIsOpen(!isOpen)}
>
{!isSupported ? (
<>
<TokenWarningRedIcon fill={themeVars.colors.textSecondary} width={24} height={24} />
<Box as="span" className={subhead} display={{ sm: 'none', xxl: 'flex' }} style={{ lineHeight: '20px' }}>
Unsupported
</Box>
</>
) : (
<>
<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 ? <ChevronUp {...chevronProps} /> : <ChevronDown {...chevronProps} />}
</Row>
{isOpen && (isMobile ? <Portal>{dropdown}</Portal> : <>{dropdown}</>)}
</Box>
)
}

View File

@@ -0,0 +1,89 @@
import { useWeb3React } from '@web3-react/core'
import Loader from 'components/Loader'
import { getChainInfo } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import { CheckMarkIcon } from 'nft/components/icons'
import styled, { useTheme } from 'styled-components/macro'
const LOGO_SIZE = 20
const Container = styled.button`
display: grid;
background: none;
grid-template-columns: min-content 1fr min-content;
align-items: center;
text-align: left;
line-height: 24px;
border: none;
justify-content: space-between;
padding: 10px 8px;
cursor: pointer;
border-radius: 12px;
color: ${({ theme }) => theme.textPrimary};
width: 240px;
transition: ${({ theme }) => theme.transition.duration.medium} ${({ theme }) => theme.transition.timing.ease}
background-color;
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
width: 100%;
}
&:hover {
background-color: ${({ theme }) => theme.backgroundOutline};
}
`
const Label = styled.div`
grid-column: 2;
grid-row: 1;
font-size: 16px;
`
const Status = styled.div`
grid-column: 3;
grid-row: 1;
display: flex;
align-items: center;
width: ${LOGO_SIZE}px;
`
const ApproveText = styled.div`
color: ${({ theme }) => theme.textSecondary};
font-size: 12px;
grid-column: 2;
grid-row: 2;
`
const Logo = styled.img`
height: ${LOGO_SIZE}px;
width: ${LOGO_SIZE}px;
margin-right: 12px;
`
export default function ChainSelectorRow({
targetChain,
onSelectChain,
isPending,
}: {
targetChain: SupportedChainId
onSelectChain: (targetChain: number) => void
isPending: boolean
}) {
const { chainId } = useWeb3React()
const active = chainId === targetChain
const { label, logoUrl } = getChainInfo(targetChain)
const theme = useTheme()
return (
<Container onClick={() => onSelectChain(targetChain)}>
<Logo src={logoUrl} alt={label} />
<Label>{label}</Label>
{isPending && <ApproveText>Approve in wallet</ApproveText>}
<Status>
{active && <CheckMarkIcon width={LOGO_SIZE} height={LOGO_SIZE} color={theme.accentActive} />}
{isPending && <Loader width={LOGO_SIZE} height={LOGO_SIZE} />}
</Status>
</Container>
)
}

View File

@@ -0,0 +1,68 @@
import { style } from '@vanilla-extract/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: 'textPrimary',
paddingY: '8',
paddingX: '8',
width: 'full',
whiteSpace: 'nowrap',
}),
{
lineHeight: '24px',
textDecoration: 'none',
},
])
export const PrimaryText = style([
{
lineHeight: '24px',
},
])
export const SecondaryText = style([
hover,
sprinkles({
paddingY: '8',
paddingX: '8',
color: 'textSecondary',
width: 'full',
}),
{
lineHeight: '20px',
},
])
export const Separator = style([
sprinkles({
height: '0',
marginX: '16',
}),
{
borderTop: 'solid',
borderColor: themeVars.colors.backgroundOutline,
borderWidth: '1px',
},
])
export const IconRow = style([
sprinkles({
paddingX: '16',
justifyContent: { sm: 'center', md: 'flex-start' },
}),
])

View File

@@ -0,0 +1,225 @@
import { Trans } from '@lingui/macro'
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex'
import {
BarChartIcon,
DiscordIconMenu,
EllipsisIcon,
GithubIconMenu,
GovernanceIcon,
ThinTagIcon,
TwitterIconMenu,
} from 'nft/components/icons'
import { body, bodySmall } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css'
import { ReactNode, useReducer, useRef } from 'react'
import { NavLink, NavLinkProps } from 'react-router-dom'
import { isDevelopmentEnv, isStagingEnv } from 'utils/env'
import { useToggleModal } from '../../state/application/hooks'
import { ApplicationModal } from '../../state/application/reducer'
import * as styles from './MenuDropdown.css'
import { NavDropdown } from './NavDropdown'
import { NavIcon } from './NavIcon'
const PrimaryMenuRow = ({
to,
href,
close,
children,
}: {
to?: NavLinkProps['to']
href?: string
close?: () => void
children: ReactNode
}) => {
return (
<>
{to ? (
<NavLink to={to} className={styles.MenuRow}>
<Row onClick={close}>{children}</Row>
</NavLink>
) : (
<Row as="a" href={href} target={'_blank'} rel={'noopener noreferrer'} className={styles.MenuRow}>
{children}
</Row>
)}
</>
)
}
const PrimaryMenuRowText = ({ children }: { children: ReactNode }) => {
return <Box className={`${styles.PrimaryText} ${body}`}>{children}</Box>
}
PrimaryMenuRow.Text = PrimaryMenuRowText
const SecondaryLinkedText = ({
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.SecondaryText} ${bodySmall}`}
onClick={onClick}
cursor="pointer"
>
{children}
</Box>
)
}
const Separator = () => {
return <Box className={styles.Separator} />
}
const IconRow = ({ children }: { children: ReactNode }) => {
return <Row className={styles.IconRow}>{children}</Row>
}
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="textPrimary"
background="none"
border="none"
justifyContent="center"
textAlign="center"
marginRight="12"
>
{children}
</Box>
</>
)
}
export const MenuDropdown = () => {
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
const togglePrivacyPolicy = useToggleModal(ApplicationModal.PRIVACY_POLICY)
const openFeatureFlagsModal = useToggleModal(ApplicationModal.FEATURE_FLAGS)
const nftFlag = useNftFlag()
const ref = useRef<HTMLDivElement>(null)
useOnClickOutside(ref, isOpen ? toggleOpen : undefined)
return (
<>
<Box position="relative" ref={ref}>
<NavIcon isActive={isOpen} onClick={toggleOpen}>
<EllipsisIcon width={20} height={20} />
</NavIcon>
{isOpen && (
<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="/nfts/sell" close={toggleOpen}>
<Icon>
<ThinTagIcon width={24} height={24} />
</Icon>
<PrimaryMenuRow.Text>
<Trans>Sell NFTs</Trans>
</PrimaryMenuRow.Text>
</PrimaryMenuRow>
)}
<PrimaryMenuRow to="/vote" close={toggleOpen}>
<Icon>
<GovernanceIcon width={24} height={24} />
</Icon>
<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>
<Trans>View token analytics</Trans>
</PrimaryMenuRow.Text>
</PrimaryMenuRow>
</Column>
<Separator />
<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()
}}
>
<Trans>Legal & Privacy</Trans>
</SecondaryLinkedText>
{(isDevelopmentEnv() || isStagingEnv()) && (
<SecondaryLinkedText onClick={openFeatureFlagsModal}>
<Trans>Feature Flags</Trans>
</SecondaryLinkedText>
)}
</Box>
<IconRow>
<Icon href="https://discord.com/invite/FCfyBSbCU5">
<DiscordIconMenu
className={styles.hover}
width={24}
height={24}
color={themeVars.colors.textSecondary}
/>
</Icon>
<Icon href="https://twitter.com/Uniswap">
<TwitterIconMenu
className={styles.hover}
width={24}
height={24}
color={themeVars.colors.textSecondary}
/>
</Icon>
<Icon href="https://github.com/Uniswap">
<GithubIconMenu
className={styles.hover}
width={24}
height={24}
color={themeVars.colors.textSecondary}
/>
</Icon>
</IconRow>
</Column>
</NavDropdown>
)}
</Box>
<PrivacyPolicyModal />
<FeatureFlagModal />
</>
)
}

View File

@@ -0,0 +1,45 @@
import { style } from '@vanilla-extract/css'
import { sprinkles } from '../../nft/css/sprinkles.css'
const baseNavDropdown = style([
sprinkles({
background: 'backgroundSurface',
borderStyle: 'solid',
borderColor: 'backgroundOutline',
borderWidth: '1px',
paddingBottom: '8',
paddingTop: '8',
zIndex: '2',
}),
{
boxShadow: '0px 4px 12px 0px #00000026',
},
])
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

@@ -0,0 +1,12 @@
import { Box, BoxProps } from 'nft/components/Box'
import { useIsMobile } from 'nft/hooks'
import { ForwardedRef, forwardRef } from 'react'
import * as styles from './NavDropdown.css'
export const NavDropdown = forwardRef((props: BoxProps, ref: ForwardedRef<HTMLElement>) => {
const isMobile = useIsMobile()
return <Box ref={ref} className={isMobile ? styles.mobileNavDropdown : styles.NavDropdown} {...props} />
})
NavDropdown.displayName = 'NavDropdown'

View File

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

View File

@@ -0,0 +1,25 @@
import { Box } from 'nft/components/Box'
import { ReactNode } from 'react'
import * as styles from './NavIcon.css'
interface NavIconProps {
children: ReactNode
isActive?: boolean
onClick: () => void
}
export const NavIcon = ({ children, isActive, onClick }: NavIconProps) => {
return (
<Box
as="button"
className={styles.navIcon}
background={isActive ? 'accentActiveSoft' : 'none'}
color={isActive ? 'textPrimary' : 'textSecondary'}
onClick={onClick}
height="40"
>
{children}
</Box>
)
}

View File

@@ -0,0 +1,215 @@
import { style } from '@vanilla-extract/css'
import { buttonTextSmall, subhead, subheadSmall } from 'nft/css/common.css'
import { breakpoints, sprinkles, vars } from '../../nft/css/sprinkles.css'
const DESKTOP_NAVBAR_WIDTH = 360
const MAGNIFYING_GLASS_ICON_WIDTH = 28
const baseSearchStyle = style([
sprinkles({
paddingY: '12',
width: { sm: 'viewWidth' },
borderStyle: 'solid',
borderWidth: '1px',
borderColor: 'backgroundOutline',
}),
{
'@media': {
[`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.lg}px)`]: {
right: `-${DESKTOP_NAVBAR_WIDTH / 2 - MAGNIFYING_GLASS_ICON_WIDTH}px`,
top: '-5px',
},
},
},
])
export const searchBar = style([
baseSearchStyle,
sprinkles({
color: 'textTertiary',
paddingX: '16',
background: 'backgroundSurface',
}),
])
export const searchBarInput = style([
sprinkles({
padding: '0',
fontWeight: 'normal',
fontSize: '16',
color: { default: 'textPrimary', placeholder: 'textTertiary' },
border: 'none',
background: 'none',
}),
{
lineHeight: '24px',
},
])
export const searchBarDropdown = style([
baseSearchStyle,
sprinkles({
borderBottomLeftRadius: '12',
borderBottomRightRadius: '12',
background: 'backgroundSurface',
height: { sm: 'viewHeight', md: 'auto' },
}),
{
borderTop: 'none',
},
])
export const suggestionRow = style([
sprinkles({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingY: '8',
paddingX: '16',
cursor: 'pointer',
}),
{
':hover': {
background: vars.color.lightGrayOverlay,
},
textDecoration: 'none',
},
])
export const suggestionImage = sprinkles({
width: '36',
height: '36',
borderRadius: 'round',
marginRight: '8',
})
export const suggestionPrimaryContainer = style([
sprinkles({
alignItems: 'flex-start',
width: 'full',
}),
])
export const suggestionSecondaryContainer = sprinkles({
textAlign: 'right',
alignItems: 'flex-end',
})
export const primaryText = style([
subhead,
sprinkles({
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
color: 'textPrimary',
}),
{
lineHeight: '24px',
},
])
export const secondaryText = style([
buttonTextSmall,
sprinkles({
color: 'textSecondary',
}),
{
lineHeight: '20px',
},
])
export const imageHolder = style([
suggestionImage,
sprinkles({
background: 'backgroundModule',
flexShrink: '0',
}),
])
export const suggestionIcon = sprinkles({
display: 'flex',
flexShrink: '0',
})
export const sectionHeader = style([
subheadSmall,
sprinkles({
color: 'textSecondary',
}),
{
lineHeight: '20px',
},
])
export const notFoundContainer = style([
sectionHeader,
sprinkles({
paddingY: '4',
paddingLeft: '16',
}),
])
const visibilityTransition = `visibility ${vars.time[125]}, opacity ${vars.time[125]}`
const delayedTransitionProperties = `padding 0s ${vars.time[125]}, height 0s ${vars.time[125]}`
export const hidden = style([
sprinkles({
visibility: 'hidden',
opacity: '0',
padding: '0',
height: '0',
}),
{
transition: `${visibilityTransition}, ${delayedTransitionProperties}`,
transitionTimingFunction: 'ease-in',
},
])
export const visible = style([
sprinkles({
visibility: 'visible',
opacity: '1',
height: 'full',
}),
{
transition: `${visibilityTransition}`,
transitionTimingFunction: 'ease-out',
},
])
export const searchContentCentered = style({
'@media': {
[`screen and (min-width: ${breakpoints.lg}px)`]: {
transform: `translateX(${DESKTOP_NAVBAR_WIDTH / 4}px)`,
transition: `transform ${vars.time[125]}`,
transitionTimingFunction: 'ease-out',
},
},
})
export const searchContentLeftAlign = style({
'@media': {
[`screen and (min-width: ${breakpoints.lg}px)`]: {
transform: 'translateX(0)',
transition: `transform ${vars.time[125]}`,
transitionTimingFunction: 'ease-in',
},
},
})

View File

@@ -0,0 +1,157 @@
// eslint-disable-next-line no-restricted-imports
import { t } 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 { organizeSearchResults } from 'lib/utils/searchBar'
import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex'
import { magicalGradientOnHover } from 'nft/css/common.css'
import { useIsMobile, useIsTablet } from 'nft/hooks'
import { fetchSearchCollections } from 'nft/queries'
import { fetchSearchTokens } from 'nft/queries/genie/SearchTokensFetcher'
import { ChangeEvent, useEffect, useReducer, useRef, useState } from 'react'
import { useQuery } from 'react-query'
import { useLocation } from 'react-router-dom'
import { ChevronLeftIcon, MagnifyingGlassIcon, NavMagnifyingGlassIcon } from '../../nft/components/icons'
import { NavIcon } from './NavIcon'
import * as styles from './SearchBar.css'
import { SearchBarDropdown } from './SearchBarDropdown'
export const SearchBar = () => {
const [isOpen, toggleOpen] = useReducer((state: boolean) => !state, false)
const [searchValue, setSearchValue] = useState('')
const debouncedSearchValue = useDebounce(searchValue, 300)
const searchRef = useRef<HTMLDivElement>(null)
const inputRef = useRef<HTMLInputElement>(null)
const { pathname } = useLocation()
const phase1Flag = useNftFlag()
const isMobile = useIsMobile()
const isTablet = useIsTablet()
useOnClickOutside(searchRef, () => {
isOpen && toggleOpen()
})
const { data: collections, isLoading: collectionsAreLoading } = useQuery(
['searchCollections', debouncedSearchValue],
() => fetchSearchCollections(debouncedSearchValue),
{
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
}
)
const { data: tokens, isLoading: tokensAreLoading } = useQuery(
['searchTokens', debouncedSearchValue],
() => fetchSearchTokens(debouncedSearchValue),
{
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
}
)
const isNFTPage = pathname.includes('/nfts')
const [reducedTokens, reducedCollections] = organizeSearchResults(isNFTPage, tokens ?? [], collections ?? [])
// close dropdown on escape
useEffect(() => {
const escapeKeyDownHandler = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isOpen) {
event.preventDefault()
toggleOpen()
}
}
document.addEventListener('keydown', escapeKeyDownHandler)
return () => {
document.removeEventListener('keydown', escapeKeyDownHandler)
}
}, [isOpen, toggleOpen, collections])
// clear searchbar when changing pages
useEffect(() => {
setSearchValue('')
}, [pathname])
// 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`
const isMobileOrTablet = isMobile || isTablet
const showCenteredSearchContent =
!isOpen && phase1Flag !== NftVariant.Enabled && !isMobileOrTablet && searchValue.length === 0
return (
<Box position="relative">
<Box
position={{ sm: 'fixed', md: 'absolute' }}
width={{ sm: isOpen ? 'viewWidth' : 'auto', md: 'auto' }}
ref={searchRef}
className={styles.searchBarContainer}
display={{ sm: isOpen ? 'inline-block' : 'none', xl: 'inline-block' }}
>
<Row
className={clsx(
` ${styles.searchBar} ${!isOpen && !isMobile && magicalGradientOnHover} ${
isMobileOrTablet && (isOpen ? styles.visible : styles.hidden)
}`
)}
borderRadius={isOpen || isMobileOrTablet ? undefined : '12'}
borderTopRightRadius={isOpen && !isMobile ? '12' : undefined}
borderTopLeftRadius={isOpen && !isMobile ? '12' : undefined}
borderBottomWidth={isOpen || isMobileOrTablet ? '0px' : '1px'}
onClick={() => !isOpen && toggleOpen()}
gap="12"
>
<Box className={showCenteredSearchContent ? styles.searchContentCentered : styles.searchContentLeftAlign}>
<Box display={{ sm: 'none', md: 'flex' }}>
<MagnifyingGlassIcon />
</Box>
<Box display={{ sm: 'flex', md: 'none' }} color="textTertiary" onClick={toggleOpen}>
<ChevronLeftIcon />
</Box>
</Box>
<Box
as="input"
placeholder={placeholderText}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
!isOpen && toggleOpen()
setSearchValue(event.target.value)
}}
className={`${styles.searchBarInput} ${
showCenteredSearchContent ? styles.searchContentCentered : styles.searchContentLeftAlign
}`}
value={searchValue}
ref={inputRef}
width={phase1Flag === NftVariant.Enabled || isOpen ? 'full' : '160'}
/>
</Row>
<Box className={clsx(isOpen ? styles.visible : styles.hidden)}>
{isOpen && (
<SearchBarDropdown
toggleOpen={toggleOpen}
tokens={reducedTokens}
collections={reducedCollections}
hasInput={debouncedSearchValue.length > 0}
isLoading={tokensAreLoading || (collectionsAreLoading && phase1Flag === NftVariant.Enabled)}
/>
)}
</Box>
</Box>
<NavIcon onClick={toggleOpen}>
<NavMagnifyingGlassIcon />
</NavIcon>
</Box>
)
}

View File

@@ -0,0 +1,290 @@
import { Trans } from '@lingui/macro'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex'
import { subheadSmall } from 'nft/css/common.css'
import { useSearchHistory } from 'nft/hooks'
import { fetchTrendingCollections } from 'nft/queries'
import { fetchTrendingTokens } from 'nft/queries/genie/TrendingTokensFetcher'
import { FungibleToken, GenieCollection, TimePeriod, TrendingCollection } from 'nft/types'
import { formatEthPrice } from 'nft/utils/currency'
import { ReactNode, useEffect, useMemo, useState } from 'react'
import { useQuery } from 'react-query'
import { useLocation } from 'react-router-dom'
import { ClockIcon, TrendingArrow } from '../../nft/components/icons'
import * as styles from './SearchBar.css'
import { CollectionRow, SkeletonRow, TokenRow } from './SuggestionRow'
function isCollection(suggestion: GenieCollection | FungibleToken | TrendingCollection) {
return (suggestion as FungibleToken).decimals === undefined
}
interface SearchBarDropdownSectionProps {
toggleOpen: () => void
suggestions: (GenieCollection | FungibleToken)[]
header: JSX.Element
headerIcon?: JSX.Element
hoveredIndex: number | undefined
startingIndex: number
setHoveredIndex: (index: number | undefined) => void
isLoading?: boolean
}
export const SearchBarDropdownSection = ({
toggleOpen,
suggestions,
header,
headerIcon = undefined,
hoveredIndex,
startingIndex,
setHoveredIndex,
isLoading,
}: SearchBarDropdownSectionProps) => {
return (
<Column gap="12">
<Row paddingX="16" paddingY="4" gap="8" color="grey300" className={subheadSmall} style={{ lineHeight: '20px' }}>
{headerIcon ? headerIcon : null}
<Box>{header}</Box>
</Row>
<Column gap="12">
{suggestions.map((suggestion, index) =>
isLoading ? (
<SkeletonRow key={index} />
) : isCollection(suggestion) ? (
<CollectionRow
key={suggestion.address}
collection={suggestion as GenieCollection}
isHovered={hoveredIndex === index + startingIndex}
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
index={index + startingIndex}
/>
) : (
<TokenRow
key={suggestion.address}
token={suggestion as FungibleToken}
isHovered={hoveredIndex === index + startingIndex}
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
index={index + startingIndex}
/>
)
)}
</Column>
</Column>
)
}
interface SearchBarDropdownProps {
toggleOpen: () => void
tokens: FungibleToken[]
collections: GenieCollection[]
hasInput: boolean
isLoading: boolean
}
export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput, isLoading }: SearchBarDropdownProps) => {
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(0)
const searchHistory = useSearchHistory((state: { history: (FungibleToken | GenieCollection)[] }) => state.history)
const shortenedHistory = useMemo(() => searchHistory.slice(0, 2), [searchHistory])
const { pathname } = useLocation()
const isNFTPage = pathname.includes('/nfts')
const isTokenPage = pathname.includes('/tokens')
const phase1Flag = useNftFlag()
const [resultsState, setResultsState] = useState<ReactNode>()
const { data: trendingCollectionResults, isLoading: trendingCollectionsAreLoading } = useQuery(
['trendingCollections', 'eth', 'twenty_four_hours'],
() => fetchTrendingCollections({ volumeType: 'eth', timePeriod: 'ONE_DAY' as TimePeriod, size: 3 })
)
const trendingCollections = useMemo(
() =>
trendingCollectionResults
? trendingCollectionResults
.map((collection) => ({
...collection,
collectionAddress: collection.address,
floorPrice: formatEthPrice(collection.floor?.toString()),
stats: {
total_supply: collection.totalSupply,
one_day_change: collection.floorChange,
},
}))
.slice(0, isNFTPage ? 3 : 2)
: [...Array<GenieCollection>(isNFTPage ? 3 : 2)],
[isNFTPage, trendingCollectionResults]
)
const { data: trendingTokenResults, isLoading: trendingTokensAreLoading } = useQuery(
['trendingTokens'],
() => fetchTrendingTokens(4),
{
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
}
)
const trendingTokensLength = phase1Flag === NftVariant.Enabled ? (isTokenPage ? 3 : 2) : 4
const trendingTokens = useMemo(
() =>
trendingTokenResults
? trendingTokenResults.slice(0, trendingTokensLength)
: [...Array<FungibleToken>(trendingTokensLength)],
[trendingTokenResults, trendingTokensLength]
)
const totalSuggestions = hasInput
? tokens.length + collections.length
: Math.min(shortenedHistory.length, 2) +
(isNFTPage || !isTokenPage ? trendingCollections?.length ?? 0 : 0) +
(isTokenPage || !isNFTPage ? trendingTokens?.length ?? 0 : 0)
// Navigate search results via arrow keys
useEffect(() => {
const keyDownHandler = (event: KeyboardEvent) => {
if (event.key === 'ArrowUp') {
event.preventDefault()
if (!hoveredIndex) {
setHoveredIndex(totalSuggestions - 1)
} else {
setHoveredIndex(hoveredIndex - 1)
}
} else if (event.key === 'ArrowDown') {
event.preventDefault()
if (hoveredIndex && hoveredIndex === totalSuggestions - 1) {
setHoveredIndex(0)
} else {
setHoveredIndex((hoveredIndex ?? -1) + 1)
}
}
}
document.addEventListener('keydown', keyDownHandler)
return () => {
document.removeEventListener('keydown', keyDownHandler)
}
}, [toggleOpen, hoveredIndex, totalSuggestions])
useEffect(() => {
if (!isLoading) {
const tokenSearchResults =
tokens.length > 0 ? (
<SearchBarDropdownSection
hoveredIndex={hoveredIndex}
startingIndex={isNFTPage ? collections.length : 0}
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={tokens}
header={<Trans>Tokens</Trans>}
/>
) : (
<Box className={styles.notFoundContainer}>
<Trans>No tokens found.</Trans>
</Box>
)
const collectionSearchResults =
phase1Flag === NftVariant.Enabled ? (
collections.length > 0 ? (
<SearchBarDropdownSection
hoveredIndex={hoveredIndex}
startingIndex={isNFTPage ? 0 : tokens.length}
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={collections}
header={<Trans>NFT Collections</Trans>}
/>
) : (
<Box className={styles.notFoundContainer}>No NFT collections found.</Box>
)
) : null
const currentState = () =>
hasInput ? (
// Empty or Up to 8 combined tokens and nfts
<Column gap="20">
{isNFTPage ? (
<>
{collectionSearchResults}
{tokenSearchResults}
</>
) : (
<>
{tokenSearchResults}
{collectionSearchResults}
</>
)}
</Column>
) : (
// Recent Searches, Trending Tokens, Trending Collections
<Column gap="20">
{shortenedHistory.length > 0 && (
<SearchBarDropdownSection
hoveredIndex={hoveredIndex}
startingIndex={0}
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={shortenedHistory}
header={<Trans>Recent searches</Trans>}
headerIcon={<ClockIcon />}
/>
)}
{!isNFTPage && (
<SearchBarDropdownSection
hoveredIndex={hoveredIndex}
startingIndex={shortenedHistory.length}
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={trendingTokens}
header={<Trans>Popular tokens</Trans>}
headerIcon={<TrendingArrow />}
isLoading={trendingTokensAreLoading}
/>
)}
{!isTokenPage && phase1Flag === NftVariant.Enabled && (
<SearchBarDropdownSection
hoveredIndex={hoveredIndex}
startingIndex={shortenedHistory.length + (isNFTPage ? 0 : trendingTokens?.length ?? 0)}
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={trendingCollections as unknown as GenieCollection[]}
header={<Trans>Popular NFT collections</Trans>}
headerIcon={<TrendingArrow />}
isLoading={trendingCollectionsAreLoading}
/>
)}
</Column>
)
setResultsState(currentState)
}
}, [
isLoading,
tokens,
collections,
trendingCollections,
trendingCollectionsAreLoading,
trendingTokens,
trendingTokensAreLoading,
hoveredIndex,
phase1Flag,
toggleOpen,
shortenedHistory,
hasInput,
isNFTPage,
isTokenPage,
])
return (
<Box className={styles.searchBarDropdown}>
<Box opacity={isLoading ? '0.3' : '1'} transition="125">
{resultsState}
</Box>
</Box>
)
}

View File

@@ -0,0 +1,22 @@
import { style } from '@vanilla-extract/css'
import { sprinkles } from 'nft/css/sprinkles.css'
export const bagQuantity = style([
sprinkles({
position: 'absolute',
top: '4',
right: '4',
backgroundColor: 'accentAction',
borderRadius: 'round',
color: 'explicitWhite',
textAlign: 'center',
fontWeight: 'semibold',
paddingY: '1',
paddingX: '4',
}),
{
fontSize: '8px',
lineHeight: '12px',
minWidth: '14px',
},
])

View File

@@ -0,0 +1,47 @@
import { NavIcon } from 'components/NavBar/NavIcon'
import * as styles from 'components/NavBar/ShoppingBag.css'
import { Box } from 'nft/components/Box'
import { BagIcon, HundredsOverflowIcon, TagIcon } from 'nft/components/icons'
import { useBag, useSellAsset } from 'nft/hooks'
import { useEffect, useState } from 'react'
import { useLocation } from 'react-router-dom'
export const ShoppingBag = () => {
const itemsInBag = useBag((state) => state.itemsInBag)
const sellAssets = useSellAsset((state) => state.sellAssets)
const [bagQuantity, setBagQuantity] = useState(0)
const [sellQuantity, setSellQuantity] = useState(0)
const location = useLocation()
const toggleBag = useBag((s) => s.toggleBag)
useEffect(() => {
setBagQuantity(itemsInBag.length)
}, [itemsInBag])
useEffect(() => {
setSellQuantity(sellAssets.length)
}, [sellAssets])
const isSell = location.pathname === '/nfts/sell'
return (
<NavIcon onClick={toggleBag}>
{isSell ? (
<>
<TagIcon width={20} height={20} />
{sellQuantity ? (
<Box className={styles.bagQuantity}>{sellQuantity > 99 ? <HundredsOverflowIcon /> : sellQuantity}</Box>
) : null}
</>
) : (
<>
<BagIcon width={20} height={20} />
{bagQuantity ? (
<Box className={styles.bagQuantity}>{bagQuantity > 99 ? <HundredsOverflowIcon /> : bagQuantity}</Box>
) : null}
</>
)}
</NavIcon>
)
}

View File

@@ -0,0 +1,197 @@
import clsx from 'clsx'
import { getTokenDetailsURL } from 'graphql/data/util'
import uriToHttp from 'lib/utils/uriToHttp'
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 { 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 { VerifiedIcon } from '../../nft/components/icons'
import * as styles from './SearchBar.css'
interface CollectionRowProps {
collection: GenieCollection
isHovered: boolean
setHoveredIndex: (index: number | undefined) => void
toggleOpen: () => void
index: number
}
export const CollectionRow = ({ collection, isHovered, setHoveredIndex, toggleOpen, index }: CollectionRowProps) => {
const [brokenImage, setBrokenImage] = useState(false)
const [loaded, setLoaded] = useState(false)
const addToSearchHistory = useSearchHistory(
(state: { addItem: (item: FungibleToken | GenieCollection) => void }) => state.addItem
)
const navigate = useNavigate()
const handleClick = useCallback(() => {
addToSearchHistory(collection)
toggleOpen()
}, [addToSearchHistory, collection, toggleOpen])
useEffect(() => {
const keyDownHandler = (event: KeyboardEvent) => {
if (event.key === 'Enter' && isHovered) {
event.preventDefault()
navigate(`/nfts/collection/${collection.address}`)
handleClick()
}
}
document.addEventListener('keydown', keyDownHandler)
return () => {
document.removeEventListener('keydown', keyDownHandler)
}
}, [toggleOpen, isHovered, collection, navigate, handleClick])
return (
<Link
to={`/nfts/collection/${collection.address}`}
onClick={handleClick}
onMouseEnter={() => !isHovered && setHoveredIndex(index)}
onMouseLeave={() => isHovered && setHoveredIndex(undefined)}
className={styles.suggestionRow}
style={{ background: isHovered ? vars.color.lightGrayOverlay : 'none' }}
>
<Row style={{ width: '60%' }}>
{!brokenImage && collection.imageUrl ? (
<Box
as="img"
src={collection.imageUrl}
alt={collection.name}
className={clsx(loaded ? styles.suggestionImage : styles.imageHolder)}
onError={() => setBrokenImage(true)}
onLoad={() => setLoaded(true)}
/>
) : (
<Box className={styles.imageHolder} />
)}
<Column className={styles.suggestionPrimaryContainer}>
<Row gap="4" width="full">
<Box className={styles.primaryText}>{collection.name}</Box>
{collection.isVerified && <VerifiedIcon className={styles.suggestionIcon} />}
</Row>
<Box className={styles.secondaryText}>{putCommas(collection.stats.total_supply)} items</Box>
</Column>
</Row>
{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>
)
}
interface TokenRowProps {
token: FungibleToken
isHovered: boolean
setHoveredIndex: (index: number | undefined) => void
toggleOpen: () => void
index: number
}
export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index }: TokenRowProps) => {
const [brokenImage, setBrokenImage] = useState(false)
const [loaded, setLoaded] = useState(false)
const addToSearchHistory = useSearchHistory(
(state: { addItem: (item: FungibleToken | GenieCollection) => void }) => state.addItem
)
const navigate = useNavigate()
const handleClick = useCallback(() => {
addToSearchHistory(token)
toggleOpen()
}, [addToSearchHistory, toggleOpen, token])
const tokenDetailsPath = getTokenDetailsURL(token.address, undefined, token.chainId)
// Close the modal on escape
useEffect(() => {
const keyDownHandler = (event: KeyboardEvent) => {
if (event.key === 'Enter' && isHovered) {
event.preventDefault()
navigate(tokenDetailsPath)
handleClick()
}
}
document.addEventListener('keydown', keyDownHandler)
return () => {
document.removeEventListener('keydown', keyDownHandler)
}
}, [toggleOpen, isHovered, token, navigate, handleClick, tokenDetailsPath])
return (
<Link
to={tokenDetailsPath}
onClick={handleClick}
onMouseEnter={() => !isHovered && setHoveredIndex(index)}
onMouseLeave={() => isHovered && setHoveredIndex(undefined)}
className={styles.suggestionRow}
style={{ background: isHovered ? vars.color.lightGrayOverlay : 'none' }}
>
<Row style={{ width: '65%' }}>
{!brokenImage && token.logoURI ? (
<Box
as="img"
src={token.logoURI.includes('ipfs://') ? uriToHttp(token.logoURI)[0] : token.logoURI}
alt={token.name}
className={clsx(loaded ? styles.suggestionImage : styles.imageHolder)}
onError={() => setBrokenImage(true)}
onLoad={() => setLoaded(true)}
/>
) : (
<Box className={styles.imageHolder} />
)}
<Column className={styles.suggestionPrimaryContainer}>
<Row gap="4" width="full">
<Box className={styles.primaryText}>{token.name}</Box>
{token.onDefaultList && <VerifiedIcon className={styles.suggestionIcon} />}
</Row>
<Box className={styles.secondaryText}>{token.symbol}</Box>
</Column>
</Row>
<Column className={styles.suggestionSecondaryContainer}>
{token.priceUsd && (
<Row gap="4">
<Box className={styles.primaryText}>{ethNumberStandardFormatter(token.priceUsd, true)}</Box>
</Row>
)}
{token.price24hChange && (
<Box className={styles.secondaryText} color={token.price24hChange >= 0 ? 'green400' : 'red400'}>
{token.price24hChange.toFixed(2)}%
</Box>
)}
</Column>
</Link>
)
}
export const SkeletonRow = () => {
return (
<Row className={styles.suggestionRow}>
<Row width="full">
<Box className={styles.imageHolder} />
<Column gap="4" width="full">
<Row justifyContent="space-between">
<Box borderRadius="round" height="20" background="backgroundModule" style={{ width: '180px' }} />
<Box borderRadius="round" height="20" width="48" background="backgroundModule" />
</Row>
<Row justifyContent="space-between">
<Box borderRadius="round" height="16" width="120" background="backgroundModule" />
<Box borderRadius="round" height="16" width="48" background="backgroundModule" />
</Row>
</Column>
</Row>
</Row>
)
}

View File

@@ -0,0 +1,119 @@
import { Trans } from '@lingui/macro'
import Web3Status from 'components/Web3Status'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
import { useGlobalChainName } from 'graphql/data/util'
import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex'
import { UniIcon } from 'nft/components/icons'
import { ReactNode } from 'react'
import { NavLink, NavLinkProps, useLocation } from 'react-router-dom'
import { ChainSelector } from './ChainSelector'
import { MenuDropdown } from './MenuDropdown'
import { SearchBar } from './SearchBar'
import { ShoppingBag } from './ShoppingBag'
import * as styles from './style.css'
interface MenuItemProps {
href: string
id?: NavLinkProps['id']
isActive?: boolean
children: ReactNode
}
const MenuItem = ({ href, id, isActive, children }: MenuItemProps) => {
return (
<NavLink
to={href}
className={isActive ? styles.activeMenuItem : styles.menuItem}
id={id}
style={{ textDecoration: 'none' }}
>
{children}
</NavLink>
)
}
const PageTabs = () => {
const { pathname } = useLocation()
const nftFlag = useNftFlag()
const chainName = useGlobalChainName()
const isPoolActive =
pathname.startsWith('/pool') ||
pathname.startsWith('/add') ||
pathname.startsWith('/remove') ||
pathname.startsWith('/increase') ||
pathname.startsWith('/find')
return (
<>
<MenuItem href="/swap" isActive={pathname.startsWith('/swap')}>
<Trans>Swap</Trans>
</MenuItem>
<MenuItem href={`/tokens/${chainName.toLowerCase()}`} 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 = () => {
const { pathname } = useLocation()
const isNftPage = pathname.startsWith('/nfts')
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' }}>
<ChainSelector 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>
{isNftPage && <ShoppingBag />}
<Box display={{ sm: 'none', lg: 'flex' }}>
<ChainSelector />
</Box>
<Web3Status />
</Row>
</Box>
</Box>
</nav>
<Box className={styles.mobileBottomBar}>
<PageTabs />
<Box marginY="4">
<MenuDropdown />
</Box>
</Box>
</>
)
}
export default Navbar

View File

@@ -0,0 +1,124 @@
import { style } from '@vanilla-extract/css'
import { subhead } from '../../nft/css/common.css'
import { sprinkles, vars } from '../../nft/css/sprinkles.css'
export const nav = style([
sprinkles({
paddingX: '20',
paddingY: '12',
width: 'full',
height: '72',
zIndex: '2',
background: 'backgroundFloating',
}),
{
backdropFilter: 'blur(24px)',
},
])
export const logoContainer = style([
sprinkles({
display: 'flex',
marginRight: { sm: '12', xxl: '20' },
alignItems: 'center',
}),
])
export const logo = style([
sprinkles({
display: 'block',
color: 'textPrimary',
}),
])
export const baseContainer = style([
sprinkles({
alignItems: 'center',
}),
])
export const baseSideContainer = style([
baseContainer,
sprinkles({
display: 'flex',
width: 'full',
flex: '1',
flexShrink: '2',
}),
])
export const leftSideContainer = style([
baseSideContainer,
sprinkles({
justifyContent: 'flex-start',
}),
])
export const middleContainer = style([
baseContainer,
sprinkles({
flex: '1',
flexShrink: '1',
justifyContent: 'center',
display: { sm: 'none', xl: 'flex' },
}),
])
export const rightSideContainer = style([
baseSideContainer,
sprinkles({
justifyContent: 'flex-end',
}),
])
const baseMenuItem = style([
subhead,
sprinkles({
paddingY: '8',
paddingX: '16',
marginY: '4',
borderRadius: '12',
transition: '250',
height: 'min',
width: 'full',
textAlign: 'center',
}),
{
lineHeight: '24px',
textDecoration: 'none',
':hover': {
background: vars.color.lightGrayOverlay,
},
},
])
export const menuItem = style([
baseMenuItem,
sprinkles({
color: 'textSecondary',
}),
])
export const activeMenuItem = style([
baseMenuItem,
sprinkles({
color: 'textPrimary',
background: 'backgroundFloating',
}),
])
export const mobileBottomBar = style([
sprinkles({
position: 'fixed',
display: { sm: 'flex', lg: 'none' },
bottom: '0',
right: '0',
left: '0',
justifyContent: 'space-between',
paddingY: '4',
paddingX: '8',
height: '56',
background: 'backgroundSurface',
}),
])

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