Compare commits

...

287 Commits

Author SHA1 Message Date
Zach Pomerantz
cbefbba02c feat: send analytics for swaps modified in-wallet (#5212)
* build: upgrade analytics-events

* feat: better swap analytics
2022-11-14 15:25:05 -08:00
Zach Pomerantz
3acbcbc690 fix: err and warn if user modifies tx (#5210)
* fix: err and warn if user modifies tx

* fix: update message

* fix: clarify swap error name
2022-11-14 15:09:12 -08:00
Charles Bachmeier
7b086848bf refactor: lazy load asset details and profile page (#5203)
* properly load NFtBalance data

* lazy load asset details

* extend useLoadNftBalance

* handle different balance query formats

* correct load params
2022-11-14 14:38:02 -08:00
Zach Pomerantz
77a6d158ea fix: imperatively send SwapSigned (#5207) 2022-11-14 14:27:50 -08:00
aballerr
28be15ef9f fix: fixing color of buttons (#5179)
* fixing color states, price slider and navbar
2022-11-14 16:04:29 -05:00
Jordan Frankfurt
96dc7e0998 fix(bag): simplify header (#5185)
* fix(bag): simplify header

* pr feedback: adjust margins

* add margin-auto instead of flex-basis for space-filling to get a more accurate click area

* pr feedback from lynn

* rename numberdot to counterdot
2022-11-14 12:34:16 -06:00
vignesh mohankumar
c8f1c98639 fix: resolves infinite load for WalletConnect (#5156)
* fix: resolves infinite load for WalletConnect

* actually, only 1.7.8 exists

* dedupe

* 1.8.0
2022-11-14 12:53:46 -05:00
aballerr
8c3ba8bac3 fix: Simplifying opacity hover and fixing a link (#5200)
* Simplifying Opacity hover state and fixing a link styling
2022-11-14 12:29:38 -05:00
Charles Bachmeier
c563dd5a39 feat: add market logos to filters and chips (#5199)
* add market logos to filters and chips

* remove comment
2022-11-14 11:34:23 -05:00
unipadmini
bd8cd71452 style: Update banner text, font size, color. (#5195)
* chore: Update banner text, font size, color.

* fix css

* Update line height to 88.

Co-authored-by: Padmini Pyapali <padminipyapali@Padminis-MacBook-Pro.local>
Co-authored-by: Vignesh Mohankumar <me@vig.xyz>
2022-11-14 10:52:38 -05:00
unipadmini
61d0fd9062 fix: Add ETH to the floor price in trending collections banner. (#5194)
Co-authored-by: Padmini Pyapali <padminipyapali@Padminis-MacBook-Pro.local>
2022-11-14 10:22:36 -05:00
aballerr
68709ae65b feat: Details loading (#5162)
* Merging in details loading state
2022-11-14 09:33:50 -05:00
unipadmini
ac1e83ea9f fix: Collection name should link to collection page. (#5198)
Co-authored-by: Padmini Pyapali <padminipyapali@Padminis-MacBook-Pro.local>
2022-11-14 08:34:18 -05:00
unipadmini
6215911719 style: Reduce radius and increase padding of trait chips on collection/profile pages (#5196)
* style: Reduce border radius and increase padding of trait chips on collection pages.

* Update trait chips on profile/view my NFTs page.

Co-authored-by: Padmini Pyapali <padminipyapali@Padminis-MacBook-Pro.local>
2022-11-14 07:50:11 -05:00
unipadmini
fc81c6e37d chore: Change font weights for headlines from normal -> medium. (#5193)
Co-authored-by: Padmini Pyapali <padminipyapali@Padminis-MacBook-Pro.local>
2022-11-13 19:37:21 -05:00
Charles Bachmeier
9d5e0701e7 fix: disable polling after scroll (#5191)
disable polling after scroll
2022-11-13 13:33:38 -05:00
lynn
66bdfd8a94 fix: only disable erc 1155 in sell mode (#5183)
* init

* make height into variable

* respond charlie

* cleaner code

* dont include erc 1155 when selecting all

* fix

* disable select all for sus flag nfts too

* oops
2022-11-12 00:03:19 -05:00
Zach Pomerantz
935694630b fix: do not poll when loading more assets (#5184) 2022-11-11 14:54:26 -08:00
Zach Pomerantz
f8399fd03c fix: enforce uniform asset height (#5181)
* fix: show loading assets at uniform height

* fix: enforce the uniform asset height

* fix: memoize assets

* fix: be more lenient with uniformHeight

* fix: simplify mapping
2022-11-11 17:29:57 -05:00
Jordan Frankfurt
429ade5b20 fix: add back the border-radius for swap tx conf (#5164)
add back the border-radius for swap tx conf
2022-11-11 16:13:49 -05:00
Zach Pomerantz
5c21dd9852 fix: defer useInterval until callback resolves (#5096)
* fix: defer useInterval until callback resolves

* fix: avoid refs in useInterval
2022-11-11 13:08:29 -08:00
aballerr
2ce5990f60 fix: fixing header on light mode (#5178)
* fixing header on light mode
2022-11-11 15:29:31 -05:00
Charles Bachmeier
d56851561b feat: mobile filters menu (#5163)
* header and scroll

* allow sweep buy now off

* generic filter row header

* begin building sort dropdown

* add file

* working checkmark

* remove icons

* updating scroll to work with mobile

* prevent scorlling behind menu

* hover styles

* remove console.log

* respond to comments

* revert null

* styled component header

* filter item styled component

* padding for items, slider, and inputs

* fixed scroll on mobile

Co-authored-by: Alex Ball <alex.ball@uniswap.org>
2022-11-11 15:01:39 -05:00
Zach Pomerantz
5325b5f8b4 fix: nft waterfalls requests (#5168)
* fix: request all sweep data in parallel

* fix: trigger collection query from a wrapping screen

* load sweep for correct markets

* add preload logic for assets query

* add load query to explore table

* fix: cleanup AssetFetcherParams

* fix: preload trending collections

* fix: graphql array argument

* fix: actually use preloaded asset query

* fix: use network and suspense to actually parallelize

Co-authored-by: Charlie <charles@bachmeier.io>
2022-11-11 14:55:09 -05:00
lynn
27936cf3f5 feat: image cropping / sizing on view my nfts (#5177)
init
2022-11-11 14:54:37 -05:00
Jordan Frankfurt
ff6f43d7aa fix(bag-footer): remove double border top and excess margin (#5171) 2022-11-11 13:34:32 -05:00
lynn
f1443671ef feat: Web 1854 listed card (#5160)
* init

* it's working with jack's card.tsx components

* add nft details on cards for view my nfts

* listed cards ready for review

* remove unnecessary code

* updated radius

* first round charlie comments

* respond all comments

* init

* fix

* color

* remove floor price when not on sell mode

* remove floor when nft is listed

* feat: Web 1858 disabled card 1155 when sell mode is on (#5169)

* disabled states + tooltips

* remove collection asset changes

* popover offset changes

* respond to padmini comment

* respond to charlie
2022-11-11 13:22:19 -05:00
Zach Pomerantz
a95697daf8 fix: initialize analytics outside of react lifecycle (#5173) 2022-11-11 09:47:37 -08:00
cartcrom
0835744006 feat: replaced protocol disclaimer with privacy policy link (#5170)
copy updates
2022-11-11 11:28:27 -05:00
Danny Daniil
f5df2fed09 feat: de-148- Analytics package integration (#5093)
* integrate analytics sdk

* roll back vscode

* remove dup imports

* use dummy api key and url from env

* update analytics sdk for optional init

* yarn deduped

* update documentation

* add analytics events package

* getBrowser from analytics events

* remove token banner

* add origin app

* replace local analytics

* upgrade events version

* events 1.0.4

* remove importToken
2022-11-11 11:05:57 -05:00
aballerr
5e7f6333b1 fix: Activity updates (#5165)
* listing and styling updates
2022-11-11 09:34:55 -05:00
Zach Pomerantz
2aa4ec6a38 fix: clear or retain amounts on token select (#5161)
* fix: clear or retain amounts on token select

* lint: add curlies
2022-11-10 16:01:37 -08:00
lynn
a70ef4326d feat: unlisted view my nft cards (#5129)
* init

* it's working with jack's card.tsx components

* add nft details on cards for view my nfts

* listed cards ready for review

* remove unnecessary code

* updated radius

* first round charlie comments

* respond all comments

* fix

* remove floor price when not on sell mode
2022-11-10 15:28:17 -05:00
Zach Pomerantz
6edc73784c fix: show 6 sigFigs for tx history (#5135) 2022-11-10 11:23:03 -08:00
aballerr
da6e13130b fix: Several Listing Cleanup Items (#5119)
* Improving view my nfts and listing
2022-11-10 12:39:37 -05:00
Jordan Frankfurt
4ef4ea8f58 fix: give a bunch of list renders keys (#5158)
* fix: give a bunch of list renders keys

* pr feedback

* pr feedback
2022-11-10 11:51:04 -05:00
vignesh mohankumar
4438818f38 fix: don't fetch tokens on empty search (#5155) 2022-11-10 11:35:35 -05:00
cartcrom
12eb337444 fix: one point price charts + added suspense (#5030)
* Used suspense for graph queries
* cleaned up unused code
* updated skeleton
* fixed zach's pr comments
* removed console.log
* throw error on missing token details address
2022-11-10 11:00:32 -05:00
cartcrom
44163f54b1 refactor: remove unused selector list/import components (#5145)
* removed unnused components
2022-11-10 10:57:18 -05:00
vignesh mohankumar
4b282d7813 fix: don't fetch collections without flag (#5154)
* better way

* check if value
2022-11-10 09:21:21 -05:00
vignesh mohankumar
f862a3f975 chore: add accentAction to sprinkles (#5146)
* fix: update Checkbox to accentActive

* revert checkbox

* one definition
2022-11-09 20:10:38 -05:00
Greg Bugyis
48d5955185 feat: Log NFT Sell events (#5106)
* Log profile page view

* Log sell flow started

* Add Start Listing event

* Add constant for list modal + useTrace

* Log sell item added

* Log listing completed

* Fix usd_value property

* Move log to startListingFlow

* Use Set to remove duplicate marketplaces

* Move listing completed event
2022-11-10 01:57:30 +02:00
Charles Bachmeier
dbf5c63ece refactor: remove graphql flag and default to gql endpoints (#5151)
* remove graphql flag and old endpoints

* remove unused queries

* deprecate old sell order type

* better null checks

* merge conflict
2022-11-09 18:15:40 -05:00
vignesh mohankumar
37d2603406 fix: accentAction for FilterButton (#5147)
* fix: accentAction for FilterButton

* update
2022-11-09 17:15:36 -05:00
aballerr
9bb1ca2970 fix: Hiding block on nft (#5148)
* hiding block # and gwei estimate on nft pages since it often is behind other react components
2022-11-09 16:15:52 -05:00
Charles Bachmeier
2abae0ee4c refactor: replace sweep query with gql call (#5143)
replace sweep query with gql call
2022-11-09 16:05:57 -05:00
vignesh mohankumar
9f8355ed7b refactor: useIsNftPage hooks (#5142) 2022-11-09 16:01:34 -05:00
Greg Bugyis
c5bed1c6fb style: Font weight on Trending Collections table (#5121)
* Drop classnames on Trending Table TD cells (no longer used)

* Fix font-weight in Trending Collections table collection name to match Figma

* There was a newer Figma

* Fixes

* Extend maxWidth on collection name (mobile)

* Mobile/desktop tweak

* Fix mobile size and add constants

* Make truncatedText a styled component
2022-11-09 22:42:52 +02:00
Jordan Frankfurt
1411a92146 feat(sell-bag): closing link sell bag open/close state with sell mode activation (#5128)
* stash resolve

* toggling bag open/close also toggles sell mode
2022-11-09 15:18:52 -05:00
Jordan Frankfurt
d016bdd87c fix(identicon): fix wrapper border-radius (#5144)
* fix(identicon): fix wrapper border-radius

* pr feedback
2022-11-09 14:56:25 -05:00
aballerr
491ae578ab feat: Mobile status bar (#5141)
* Updating mobile status bar to shrink (nft enabled)
2022-11-09 14:48:23 -05:00
vignesh mohankumar
1df685f31e fix: default to buy now disabled (#5140) 2022-11-09 11:52:56 -05:00
Charles Bachmeier
02aeb43e62 fix: don't pluralize Filter Button with 1 result (#5139)
* don't pluralize results with 1 result

* pass in count for profile page
2022-11-09 11:15:34 -05:00
aballerr
1d849927ef fix: fixing loader (#5138)
Fixing bug in loader that would not use correct stroke color
2022-11-09 11:11:18 -05:00
Charles Bachmeier
1893d258b5 feat: add marketplace and trait counts to assets query (#5137)
* working total count

* trait counts

* marketplace counts

* carousel card

* undo count refactor

* Filter styles

* remove any cast and handle 0
2022-11-09 10:44:42 -05:00
Charles Bachmeier
ed7f126bd0 fix: updates to wallet asset schemas (#5132)
* updates to wallet asset schemas

* update map type and market check

* much better syntax
2022-11-09 10:31:30 -05:00
Jordan Frankfurt
9a38c4e58d fix(bag): don't close when the user switches pages (#5133) 2022-11-08 18:47:01 -06:00
aballerr
99f3998941 fix: Several bugs on nft, see comments for clarity (#5116)
* a number of minor fixes related to nfts
2022-11-08 17:39:49 -05:00
vignesh mohankumar
30fa88e3af fix: show transparent box on empty banner (#5131)
* fix: show transparent box on empty banner

* div
2022-11-08 16:20:12 -05:00
vignesh mohankumar
d951172a81 chore: update nft grays (#5124)
* chore: update nft grays

* forgot to change some out

* prettier

* add 700
2022-11-08 16:15:05 -05:00
aballerr
eb35d3a2a0 fix: price range and details screen sizing (#5094)
* adjustments to screen sizing and updating to work better with graphql
2022-11-08 15:36:52 -05:00
Charles Bachmeier
87455fc096 fix: sort explore table null values (#5130) 2022-11-08 14:41:21 -05:00
Greg Bugyis
054d92cb9c style: Fix Trending Table filter borders on Safari (mobile & desktop) (#5122) 2022-11-08 20:13:34 +02:00
Charles Bachmeier
36109a1fe7 refactor: update nft gql fetcher endpoint (#5126)
update gql fetcher for prod endpoint
2022-11-08 13:11:25 -05:00
Charles Bachmeier
8f8fe9ddad fix: lowercase markets for gql profile cards (#5127)
lowercase markets for gql profile cards
2022-11-08 13:11:00 -05:00
Charles Bachmeier
2b279e00f9 feat: virtual containers for collection pages (#5125)
* use fixedsizeList only

* add autosizer comments

* undo asset testing change

* init

* cleanup

* scrollbar styles

* scrollbar styles

Co-authored-by: Lynn Yu <lynn.yu@uniswap.org>
2022-11-08 12:15:21 -05:00
Jordan Frankfurt
9f5c588bdd feat(sell-bag): patch open/close behavior (#5107)
keep bag link in nav bar at all times
2022-11-08 10:14:00 -06:00
Charles Bachmeier
415b3a1548 fix: add more null checks for Asset gql query (#5123)
add more null checks for Asset gql query
2022-11-08 10:54:11 -05:00
Charles Bachmeier
4e7b8264c3 refactor: Wallet Assets GraphQL Query (#5053)
* begin nft balance integration

* inf scroll works

* working list

* update comment

* connect collection filters

* use lazyload

* update schema

* working with new schema

* details for nfs assets

* cleanup

* more null checks

* unique index for loading cards

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-11-08 09:02:39 -05:00
lynn
0ef6d1625a fix: collection stat line fixes (#5112)
fix
2022-11-07 19:45:23 -05:00
yyip-dev
0258460821 refactor: remove token promo banner (#5114)
* Remove token promo banner

* Remove unused variable and import

Co-authored-by: Yannie Yip <yannie@UNISWAP-MAC-072.local>
2022-11-07 19:42:03 -05:00
Zach Pomerantz
2246afcefb fix: only poll every 12s (#5110)
* fix: only poll every 12s

* refactor: clarify polling interval

* doc: polling interval

* docs: further clarification

* fix: ctor super first
2022-11-07 15:35:31 -08:00
vignesh mohankumar
e0767b1cb7 build: Revert "build: deploy on friday AMs" (#5113)
Revert "build: deploy on friday AMs (#5085)"

This reverts commit a5cb1f05dc.
2022-11-07 18:34:03 -05:00
lynn
15dd02fe6a refactor: filter panel (#5103)
* init

* remove unnecessary chagnes

* fix comments
2022-11-07 17:13:08 -05:00
lynn
562a386de7 fix: release phase 1 nav bar (#5111)
* init

* just release phase 1 nav bar

* oops
2022-11-07 16:16:06 -05:00
vignesh mohankumar
99bea34f14 chore: add new background to theme (#5109)
* chore: add new background to theme

* update snapshot
2022-11-07 16:05:57 -05:00
Greg Bugyis
58f1c6ff84 fix: Event property fixes (#5108)
* Fix usd value on NFT buy bag success

* Fix bag quantity property on nft_buy_bag_changed
2022-11-07 19:35:48 +02:00
Jordan Frankfurt
b2481d6ba8 fix(asset-details): if an asset has no listings, don't try to render them (#5088)
* fix(asset-details): if an asset has no listings, don't try to render them

* add todo
2022-11-07 10:22:03 -06:00
Zach Pomerantz
eaa9b51913 feat: nft polling (#5083)
* feat: nft polling

* docs: document the poll

* chore: add todo for cursor tracking

* fix: poll all pages

* 5s polling

Co-authored-by: Charlie <charles@bachmeier.io>
2022-11-07 07:28:38 -08:00
vignesh mohankumar
6f68980d86 chore: update design-system grays (#5097)
* chore: update design-system grays

* update snapshots
2022-11-04 17:40:42 -04:00
vignesh mohankumar
9e05c178b4 fix: move /profile to /nfts/profile (#5084)
* fix: move /profile to /nfts/profile

* add back variable

* fix

* rm shouldShowBag
2022-11-04 17:13:38 -04:00
Jack Short
d98808dae9 fix: for cutoff border radius (#5095) 2022-11-04 16:29:43 -04:00
Jack Short
01a749a755 fix: wrong types for set min (#5092) 2022-11-04 16:13:31 -04:00
Jack Short
c233ba1175 style: updating collection page for mobile (#5078)
* style: updating collection page for mobile

* addressing comments

* responding to comments
2022-11-04 16:13:17 -04:00
vignesh mohankumar
b1dc415fb5 fix: replaceState instead of pushState for collection filters (#5089)
* fix: replaceState instead of pushState for collection filters

* comment
2022-11-04 15:59:18 -04:00
Charles Bachmeier
1242aef466 fix: marketplace icons on details page (#5091) 2022-11-04 12:32:30 -07:00
Charles Bachmeier
7f2bb6c6ae fix: add back floor to search results (#5087)
* add back floor to search results

* fix search by address

* slice const
2022-11-04 12:16:35 -07:00
aballerr
4b1b6098f3 feat: Details implementation (#5059)
* Details Page Update
2022-11-04 12:06:39 -06:00
Jordan Frankfurt
53a6acc199 feat(bag): add check if the user has manually closed the bag (#5074)
* feat(bag): add check if the user has manually closed the bag, and don't reopen on asset add until bag is cleared

* pr feedback
2022-11-04 12:19:42 -05:00
Charles Bachmeier
bd0a32b07c fix: gql traits for collection (#5080)
fix gql traits for collection
2022-11-04 10:07:59 -07:00
lynn
7d480494ba fix: enlarge pfp icon (#5076)
* init

* init

* fix
2022-11-04 12:25:40 -04:00
Greg Bugyis
5caed66b39 fix: Remove rank on trending collection table (mobile) (#5086) 2022-11-04 18:04:22 +02:00
lynn
136c16bbae fix: fix sell bag empty state (#5079)
* init

* fix comments
2022-11-04 11:23:29 -04:00
lynn
691dcd269c fix: activity page polish (#5081)
* fix

* remove env omg
2022-11-04 10:37:38 -04:00
vignesh mohankumar
a5cb1f05dc build: deploy on friday AMs (#5085) 2022-11-04 08:45:24 -04:00
Callil Capuozzo
4a14db2d4c fix: Update pools page to match token table design (#4861)
* Update pool pages design

* Fix small text

* Mobile tweaks

* remove redesign flag

Co-authored-by: Vignesh Mohankumar <me@vig.xyz>
2022-11-04 02:10:19 -04:00
Greg Bugyis
a57c19bb45 feat: Collection Activity mobile fixes (#5073) 2022-11-03 21:35:24 +02:00
Charles Bachmeier
9fd6ab01de refactor: Asset Details GQL Query (#5037)
* add details query

* add new query file

* connect details gql query to page

* remove comment

* type refactoring

* working pooled asset logic

* useLazyload

* remove any cast

* Deprecated_SellOrder

* extranneous cast

* return markets

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-11-03 10:52:20 -07:00
lynn
48aa11403f fix: polish controller bar in collection and my nft page (#5069)
* fix

* remove console logs

* remove unused theme

* fixes
2022-11-03 13:21:36 -04:00
Jordan Frankfurt
31b0c3dc04 fix: remove border-top-x-radius (#5071) 2022-11-02 17:36:34 -05:00
Jordan Frankfurt
d575c72127 feat(nft-bag): wrong-network-cta (#5056)
* pull warning out into its own component

* add wrong network cta

* simplify sufficient balance logic

* consolidate warning and button text logic
2022-11-02 16:56:37 -05:00
Greg Bugyis
d400b9094d feat: Buy Bag events (#5062)
* Add activity boolean to Collection page event

* Log add-to-bag events

* WIP

* Bag update events

* Log pay event

* Bag success event

* Add bag signed event

* Move formatting function out to util

* Format event properties with utility function

* Remove console log and fix event on details page

* Remove commented code

* Fix event names to follow convention

* Move priceChangedAssets logging to useEffect

* Add modal constant and useTrace

* Fix typo
2022-11-02 23:21:02 +02:00
Charles Bachmeier
fda9d29d5e fix: prices passed in scientific notation (#5070)
fix prices passed in scientific notation
2022-11-02 13:48:02 -07:00
Charles Bachmeier
d74c05008b refactor: Add Collection Stats GraphQL query (#5022)
* add demo Asset Fetcher

* new file

* update fetcher

* update query name

* beginning integration type

* uncomment

* working mutant apes

* comment out debug logging

* pass in inputs to query

* update collections to handle inf scroll

* paginated query first attempt

* wrapped assetQuery

* building pagination, needs spread

* working pagination

* working sort

* use cacheconfig

* change query source in Collection page

* passed in filters

* fetch schema from main endpoint

* delete unused relayenv

* rename token_url

* easy GenieAsset refactoring

* add rarity

* update price info

* remove logging

* remove redundancy

* refactor usd price fetching for assets

* update standard and address

* remove unused cacheconfig

* add gql collection query

* dont repeat ethprice calc

* unmemo bools

* reduce duplicated usd price logic

* cleanup imports

* useUsd price hook

* restructure Traits datatype

* working traits

* add new markets

* resolve merge conflict

* totalVolume workaround

* update comment

* fix for totalVolume bug

* add sudoswap icon

* deprecate unused vars in GenieCollection

* interim rarity verified

* cleanup

* use forEach

* add comment

* undefined division

* cleanup

* usememo marketplace select

* update % formatting

* use updated prod schema

* useLazyLoad

* remove any cast

* re-add null checks

* respond to comments

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-11-02 13:33:08 -07:00
lynn
c8f365ca31 fix: adjust mobile view bag z-index (#5067)
init
2022-11-02 15:48:55 -04:00
lynn
66ab3e21c8 fix: dynamic padding based on screen sizes for collections + my nft (#5064)
* init

* my nft pages
2022-11-02 15:48:20 -04:00
vignesh mohankumar
d4b15a6de4 fix: show <.001 ETH in collection floors (#5065) 2022-11-02 15:32:24 -04:00
lynn
fe61365a11 fix: event filter styling adjustment (#5063)
* init

* jack comment
2022-11-02 14:10:23 -04:00
github-actions[bot]
c5047a9301 chore(i18n): new Crowdin translations (#4859)
chore(i18n): synchronize translations from crowdin [skip ci]

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-11-02 10:44:06 -07:00
lynn
d4f8f2a600 fix: remove blur transition as requested by bryan (#5060)
* init

* init
2022-11-02 12:12:19 -04:00
lynn
d41a5a4874 fix: nav bar icon sizes (#5051)
* init

* fix
2022-11-02 10:07:10 -04:00
lynn
2fe444f903 fix: make addresses clickable (#5040)
* init

* fix

* change color to primary

* fix

* respond to comments
2022-11-01 22:38:25 -04:00
Jack Short
155bf2e873 style: updated collection cards (#5047) 2022-11-01 19:15:50 -04:00
Jack Short
6e282a6d13 style: updating explore table (#5043)
* style: updating explore table

* pr comments

* addressing pr comments

* moved loading table to its own component

* removing clsx

* updating key for row

* updating spacing
2022-11-01 16:09:03 -04:00
lynn
d3a2e14d4d fix: stop nft id tag from getting cut off (#5049)
* init

* respond to comments

* fix
2022-11-01 15:46:50 -04:00
vignesh mohankumar
619c0a0f46 fix: sometimes show verified nft collections first (#5055)
* fix: sometimes show verified nft collections first

* simplify

* use showNfts logic

* rename

* use collections not trending
2022-11-01 14:55:07 -04:00
Jack Short
d4fb0913a4 feat: explore carousel (#5042)
* feat: explore carousel

* removing peeking cards

* mobile carousel and pr changes

* collectionStats fetching

* total listings calc
2022-11-01 14:37:00 -04:00
Charles Bachmeier
734a15e350 fix: Adds some null checks to Asset gql query (#5058)
* add some null checks

* undo susflag change

* fix not for sale logic

* fix rarity

* fix rarity format

* remove logging

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-11-01 11:06:53 -07:00
lynn
d3cbcc769c fix: remove radial gradient background effect from nft pages (#5057)
init
2022-11-01 14:05:06 -04:00
Charles Bachmeier
ab43800d8e refactor: add suspense loading states for Relay on Collection Page (#5021)
* add suspense loading states for Relay

* remove duplication

* cleanup

* add back in suspense wrapper

* rename to skeleton

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-11-01 09:48:04 -07:00
Greg Bugyis
74accb2b7e feat: Search result events (#5025) 2022-11-01 17:28:27 +02:00
lynn
289119833a fix: button color (#5052)
init
2022-10-31 17:28:02 -04:00
lynn
8445d61df0 fix: stat line polish (#5050)
init
2022-10-31 14:26:10 -04:00
lynn
ffb6318475 fix: collection banner sizing (#5039)
* fix

* fix in response to vm comments
2022-10-31 13:32:51 -04:00
lynn
b1a0c3d6a9 fix: Web 1616 make time stamp body bodysmall (#5046)
* copywriting change

* init

* remove prev change
2022-10-31 11:47:23 -04:00
lynn
42edf9750e fix: copywriting change (#5045)
copywriting change
2022-10-31 11:37:59 -04:00
Greg Bugyis
5398826400 feat: Log NFT explore events (#4997)
* Add page event and constant for NFT Explore

* Log trending row event

* Add chainID to table row log properties
2022-10-28 23:19:05 +03:00
lynn
d4884716e2 fix: when scrolled, remove blur on nav bar (#5035)
* init

* add transition animation as per fred rec

* add border transition rec by fred

* respond to comments
2022-10-28 16:12:02 -04:00
Zach Pomerantz
6712eafefe fix: display updated widget token immediately (#5020)
* fix: display updated widget token immediately

* fix: disable if undefaulted

* docs: add comment
2022-10-28 12:41:50 -07:00
aballerr
aba6c1a1f4 docs: adding comment to env (#5038)
* adding comment to env for clarity on exposed variables
2022-10-28 14:30:09 -05:00
Charles Bachmeier
964001fe78 refactor: update all nft fetchers to use v3 endpoint (#4894)
update all fetchers to use v3 endpoint

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-10-28 10:12:48 -07:00
lynn
449c323fbb fix: modify hyperlink styling in nft collection descriptions (#5012)
* init

* this isn't really correct but halfway there

* modify opacity as per slack discussion
2022-10-28 12:10:18 -04:00
Jordan Frankfurt
d290adb0b8 feat: add medium-button and use it nft bag for removal (#5014)
* add medium-button and use it nft bag for removal

* close icon hover states

* update bag row to use correct hover background color

* update x to opacity effect

* pr feedback
2022-10-28 10:04:13 -05:00
lynn
ac4e64db6c fix: Web 1544 filter bar remove transparent bg on nft collection pages (#5034)
* remove blur effect

* init
2022-10-28 10:31:10 -04:00
lynn
3a0c930113 fix: adjust padding on collection page from 32 to 48 (#5036)
fix
2022-10-28 10:30:58 -04:00
blairmason
58c94b7279 feat: amplitude proxy (#5026)
* configure amplitude sdk to send events to Uniswap reverse proxy
2022-10-27 13:33:10 -07:00
Zach Pomerantz
02d25daa4c build: upgrade widget to 2.18 (#5032) 2022-10-27 13:22:17 -07:00
Charles Bachmeier
ef57ff7611 refactor: NFT Assets GraphQL Integration (#4928)
* add demo Asset Fetcher

* new file

* update fetcher

* update query name

* beginning integration type

* uncomment

* working mutant apes

* comment out debug logging

* pass in inputs to query

* update collections to handle inf scroll

* paginated query first attempt

* wrapped assetQuery

* building pagination, needs spread

* working pagination

* working sort

* use cacheconfig

* change query source in Collection page

* passed in filters

* fetch schema from main endpoint

* delete unused relayenv

* rename token_url

* easy GenieAsset refactoring

* add rarity

* update price info

* remove logging

* remove redundancy

* refactor usd price fetching for assets

* update standard and address

* remove unused cacheconfig

* dont repeat ethprice calc

* unmemo bools

* reduce duplicated usd price logic

* cleanup imports

* useUsd price hook

* resolve merge conflict

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-10-27 10:08:25 -07:00
Charles Bachmeier
bed0b3ab1c fix: re-add verification icon for collections in search (#5028)
re-add verification icon for collections in search

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-10-27 09:38:54 -07:00
Zach Pomerantz
751ba3c62d fix: token price (#5018)
* feat: add decimals, wrapper to token query

* fix: construct token in details page

* fix: clean widget default behavior

* fix: actual defaulting again

* fix: split token from price queries

* fix: reimplement TokenPrice query

* chore: rm old code

* fix: keep loading chart while loading

* fix: mv loader down

* fix: loading chart
2022-10-26 18:53:44 -07:00
Zach Pomerantz
83bc6db74e fix: clean up widget token lock (#5017)
* feat: add decimals, wrapper to token query

* fix: construct token in details page

* fix: clean widget default behavior

* fix: actual defaulting again
2022-10-26 18:16:04 -07:00
Zach Pomerantz
058aa52faf fix: fetch decimals from token query (#5016)
* feat: add decimals, wrapper to token query

* fix: construct token in details page
2022-10-26 18:00:53 -07:00
Jordan Frankfurt
2e3950018a fix: update "item" to "nft" in closed bag mobile copy (#5015)
https://uniswaplabs.atlassian.net/browse/WEB-1567
2022-10-26 18:53:24 -05:00
Greg Bugyis
d86120a257 feat: Collection page log events (#4971)
* Log collection page view

* Make property names/intention more clear

* Remove console log

* Add event for Activity tab

* Filter events (buy now, marketplaces, and price range)

* Handle trait items

* Bump collection stats mobile padding

* Use shouldLogImpression to conditionally fire event

* Adding back trace on Collection page, still necessary

* Add back address property

* Drop Buy Now log, not part of first set

* Update filter properties to match spreadsheet

* Only trigger price range log if inputs contain a value

* Fix ordering on Page Names

* Capitalize text

* Add constant for filter types

* Add chainId as property
2022-10-27 00:44:46 +03:00
Zach Pomerantz
e2fea4a5fb fix: do not flicker when navigating tokens (#5013) 2022-10-26 10:27:50 -07:00
lynn
1f810f84be fix: remove chain selector for nft pages (#5002)
remove chain selector for nft pages
2022-10-26 12:56:26 -04:00
Jordan Frankfurt
f6ffc68ef7 fix: update bag header layout (#5005)
* fix: update-bag-header

* pr feedback
2022-10-26 10:49:41 -05:00
Jordan Frankfurt
b3c44f20d7 fix: bag footer layout (#5003)
* fix: bag footer layout

* pr feedback

* remove balance from footer
2022-10-26 10:37:35 -05:00
Zach Pomerantz
8a5045f635 fix: do not test rpc urls before walletconnecting (#5008) 2022-10-26 08:15:06 -07:00
Zach Pomerantz
c1607bbd52 fix: token details layout gap (#5004) 2022-10-26 08:14:38 -07:00
Zach Pomerantz
dfbed6b89d fix: rm connect wallet border (#5009) 2022-10-25 17:44:50 -07:00
Zach Pomerantz
c871e55d82 fix: clean up balance summaries (#4984)
* fix: clean up balance summaries

* fix: nits

* fix: off-chain balances

* fix: only show token under details

* fix: consolidate formatting
2022-10-25 13:46:25 -07:00
Jordan Frankfurt
f15ac091b4 fix: hover flash (#4998) 2022-10-25 15:21:19 -05:00
Greg Bugyis
6037d74cfb feat: page view event on NFT Details (#4995)
* Add details page to page constants

* Add page view event to details page
2022-10-25 22:46:41 +03:00
Zach Pomerantz
d0e4659d32 feat: transition between tokens details (#4981)
* fix: navigate to widget-selected token

* fix: leave tokens if default is already set

* refactor: clean up widget skeleton

* fix: clean widget skeleton

* feat: transition between tokens

* fix: flicker on chart draw

* fix: nits

* fix: pixel-match loader

* fix: rm debug clause

* fix: hr color
2022-10-25 12:16:01 -07:00
lynn
2d87e692e6 fix: change suspicious activity warning txt (#5001)
change text
2022-10-25 15:12:00 -04:00
lynn
f65fb5bc2b fix: fixes show more / show less (#5000)
* fixes font

* oops merge again
2022-10-25 15:11:25 -04:00
lynn
83597c0efe fix: enlarge stat line font (#4999)
fix font
2022-10-25 14:56:43 -04:00
Jordan Frankfurt
21ee680d3a fix: bag row item usd price alignment (#4996) 2022-10-25 11:14:03 -05:00
Jordan Frankfurt
627af50841 fix: total and 'my bag' text style update (#4994) 2022-10-25 11:04:12 -05:00
Zach Pomerantz
a955b3730e feat: navigate to widget-selected token (#4975)
* fix: navigate to widget-selected token

* fix: leave tokens if default is already set

* refactor: clean up widget skeleton

* fix: clean widget skeleton

* fix: nits
2022-10-25 08:56:15 -07:00
vignesh mohankumar
2f7c5b1df4 chore: remove remaining redesign flag usage (#4958)
* chore: remove redesign flag in base shared components

* remove more flags

* rm pending view

* chore: remove liquidity redesign flags

* rm searchmodal

* rm searchmodal

* rm searchmodal

* numericalinput

* rm remaining

* rm flag

* fix

* fixes

* !isactive fix

* fix appbody

* hoverdefault fix
2022-10-25 09:08:14 -04:00
lynn
8fca286099 fix: resolve some tokens not searchable by contract address in token selector cx bug (#4988)
* init

* ask zzmp question

* fixes omg

* remove comment

* comments

* respond to zzmp

* fix
2022-10-24 23:53:42 -04:00
cartcrom
8be9701700 fix: token selector contract address search (#4987)
* fixed contract address search not working after importing token
* fixed unsupported asset bug
2022-10-24 16:22:26 -04:00
Jack Short
d66002dc75 feat: nft sweep (#4972)
* adding slider

* removing test css

* initial sweep pass

* cleaning up sweep functionality

* adding eth amount for sweep

* handling input

* sweep does not add duplicates

* updating fetcher to handle traits + price

* locking sweeped items on contract addr change/ filter change

* final touches to sweep for desktop

* desktop sweep finalizations

* keeping state after close

* added mobile sweep

* loading state

* dedup

* addressing comments
2022-10-24 14:55:55 -04:00
Zach Pomerantz
b12e5270fa fix: sync network connector to wallet (#4985)
* fix: sync network connector to wallet

* fix: clean todos
2022-10-24 11:51:19 -07:00
lynn
a717818920 fix: truncate search token name earlier (#4956)
* truncate search token name earlier

* fixes in response to comments
2022-10-24 12:57:01 -04:00
cartcrom
d9434a1a9c feat: update token safety / lists / verification (#4968)
* removed selected list logic and state
* updated copy
* updated warning color
* updated lists and fixed native currency bug
* removed no-longer-relevant active list tests
* removed leftover list code
* copy and color changes
2022-10-24 11:53:00 -04:00
Jack Short
a920a93b3d feat: erc1155 specific functionality to details page (#4867)
* adding erc1155 specific functionality to details page

* style update
2022-10-24 11:34:39 -04:00
Jordan Frankfurt
0987a311cf fix(nft): empty bag state render (#4947)
* fix empty bag state render

* add tests
2022-10-21 12:38:30 -05:00
Jack Short
a97a6b7fa8 chore: update price range slider to rc-slider (#4980)
* chore: update price range slider to rc-slider

* removing styles

* decreasing stepsize
2022-10-21 13:37:42 -04:00
vignesh mohankumar
414b221727 fix: no margin-top for LoadingRows (#4979) 2022-10-21 13:33:15 -04:00
aballerr
bab2f47ac9 style: filters update (#4749)
* styles filter update
2022-10-21 09:43:30 -05:00
vignesh mohankumar
0323725543 fix: add padding to LoadingRow (#4931)
* fix: add padding to LoadingRow

* remove sortable on header
2022-10-20 16:20:39 -04:00
aballerr
f6a7c8568e fix: overflow and empty popup (#4973)
* fixing a popup bug and wallet overflow
2022-10-20 11:28:03 -05:00
Charles Bachmeier
a21bbfd5a7 feat: NFT GraphQL Feature Flag (#4969)
* add nft graphQl feature flag

* connect flag to env provider

* attempting metadata relay env switch

* working config

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-10-20 08:51:00 -07:00
cartcrom
5f431a1e26 feat: show wrapped native asset at top of token selector (#4967)
* finished feature

* simplified logic from pr comments

* added checking for disableNonToken back in
2022-10-19 18:41:12 -04:00
vignesh mohankumar
bf13b4a917 chore: remove redesign flag in swap (#4964) 2022-10-19 17:42:34 -04:00
vignesh mohankumar
c7ea77d292 chore: rm redesign flags from CurrencyInputPanel (#4966)
* chore: remove flags from CurrencyInputPanel

* one more rm

* unused

* unused
2022-10-19 16:06:37 -04:00
Connor McEwen
00d674376e chore: use the same font as the main app in the widget (#4853)
* chore: use the same font as the main app

* match css file

* use string since values are the same
2022-10-19 14:34:20 -04:00
Connor McEwen
afaa52e5e7 feat: use widget validate function (#4909)
* feat: use widget validate function

* load GA scripts
2022-10-19 13:30:42 -04:00
vignesh mohankumar
443cfe7540 chore: remove favoriteTokens flag (#4955)
* chore: remove favoriteTokens flag

* remove more favorites stuff

* unused imports
2022-10-19 12:55:45 -04:00
Jordan Frankfurt
6768e4f4f7 chore: clean up unneeded code (#4426) 2022-10-19 11:47:38 -05:00
vignesh mohankumar
a0e9211b71 chore: remove redesign flag in settings (#4959)
chore: remove redesign flag in base settings
2022-10-19 10:12:28 -04:00
cartcrom
aeeb3a248a fix: show blocked tokens warning (#4954)
shows blocked warning
2022-10-18 17:10:11 -04:00
lynn
3586a2884c fix: swap quote event (#4923)
* init

* testing

* remove console line

* remove todos
2022-10-18 15:24:50 -04:00
Greg Bugyis
5e2bdc4e4b feat: Update Collection Stats and number formatting (#4937) 2022-10-18 19:58:27 +03:00
vignesh mohankumar
f2a33b6f6b chore: rm redesign flag in WalletModal (#4957) 2022-10-18 12:38:57 -04:00
vignesh mohankumar
5462526f53 fix: hover state shows for full header cell (#4940) 2022-10-18 12:10:02 -04:00
vignesh mohankumar
4388bbe0a2 fix: don't crash on unexpected chain (#4952)
* fix: don't crash on unexpected chain

* skip if undefined
2022-10-18 11:30:12 -04:00
Greg Bugyis
6f2c09adea feat: NFT Collections: add 24-hour price change and delta arrow (#4913)
* Add 24-hour volume and delta arrow to Collection Stats

* Move 24-hour floor change after Floor stat in row

* Position percent change arrow with styled div

* Use math.round instead of toFixed()

* PR Feedback

* Remove console log
2022-10-18 00:46:03 +03:00
vignesh mohankumar
f9aadbbbdb chore: remove tokenSafety flag (#4944) 2022-10-17 17:30:44 -04:00
vignesh mohankumar
f8c0525512 chore: remove deprecated colors (#4945) 2022-10-17 17:28:33 -04:00
Yadong Zhang
175ffade5e fix: handled undefined circleLogoUrl and failed fetchQuery (#4916)
* fix: handled circleLogoUrl undefined.

* fix: catch error and return empty data.

* fix: added optional chaining for circleLogoUrl in TokenRow file.
2022-10-17 14:26:30 -07:00
vignesh mohankumar
cba30fb0b1 chore: remove navBarFlag (#4941)
* chore: remove navBarFlag

* move Polling
2022-10-17 16:44:11 -04:00
vignesh mohankumar
604b854ef7 chore: remove tokens flag (#4943) 2022-10-17 12:28:16 -04:00
lynn
332843f428 fix: add logging for token details (#4925)
logging for token details
2022-10-13 17:27:30 -04:00
Zach Pomerantz
cee32f9751 fix: display loaded logo on token details (#4922) 2022-10-13 12:15:09 -07:00
cartcrom
cb480706a2 fix: display 0 instead of '-' on explore table (#4892)
* fixed table display
* updated test
2022-10-13 14:00:22 -05:00
Zach Pomerantz
4f74267144 fix: prevent marking x-chain wrapped as native (#4921) 2022-10-13 11:34:27 -07:00
Jordan Frankfurt
f6b08e8ed1 fix: make background opaque (#4895)
* fix: remove old background-color flash (#4890)

remove old background-color

* fix: make background opaque

* refactor to fix dismissal bug and improve code quality

* fix merge errors
2022-10-13 13:33:40 -05:00
Zach Pomerantz
0faaa3f0c4 fix: disallow duplicate currencies in widget (#4919)
fix: currency equality check
2022-10-13 11:20:53 -07:00
lynn
6acc9300c0 fix: add origin to every event (#4918)
* add origin to every event

* remove console log
2022-10-13 13:38:52 -04:00
Zach Pomerantz
70d33fb255 build: upgrade widget (#4917)
* build: upgrade widget

* build: upgrade widget
2022-10-13 10:37:06 -07:00
Charles Bachmeier
c0db592ab5 feat: revised profile card designs (#4901)
* add default style

* update select button styles

* add styles for listed cards

* update index for profile skeleton

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-10-13 10:13:35 -07:00
lynn
a5c5567936 feat: widget analytics (#4869)
* chore: todos for analytics

* example

* another skeleton logging event

* feat: onSwapApprove

* feat: widget tracing

* feat: better useTrace

* feat: max

* feat: switch

* add trace amd remove onreviewswapclick

* onExpandSwapDetails

* feat: initial quote

* add event properties for wrap

* feat: update ack

* SWAP_SIGNED

* feat: submit

* fix: wrap type

* chore: tracing

* move format fn to utils

* fix: remove old background-color flash (#4890)

remove old background-color

* revert: add back phase0 bug fixes (#4888)

* Revert "revert: removing phase0 bug fixes temporarily (#4886)"

This reverts commit 06291a15a6.

* use token amount

* Revert "use token amount"

This reverts commit f47c00358b.

* dont render if empty

* fix: upgrade pkg to eliminate compile error (#4898)

* upgrade pkg

* dedup

* fix: Remove token selector flash of old ui  (#4896)

remove token selector flash of old view

* fix: Web 1561 logging event for clicking on explore banner toast +  WEB-1543  [Explore Banner] String should be sentence case (#4899)

* explore banner changes

* remove console log

* oops

* test: run tests on all PRs (#4905)

* fix: use correct optimism icon in explore (#4893)

* fix: click area should match button effect (#4887)

* fix: update font-weight values to match spec (#4863)

* fixes. working now verified on console.

* fix: mobile tweaks (#4910)

* update manifest theme colors to magenta

* text spacing and right positioning on very small screens

* feat: load token from query data (#4904)

* fix: do not fetch balances cross-chain

* build: updgrade widget

* feat: cleanly load and switch chains from widget

* fix: load token from query data

* build: trigger checks

* fix: do not override native token from query

* fix: catch error on switch chain

* refactor: useTokenFromActiveNetwork

* refactor: defaultToken behavior clarification

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
Co-authored-by: vignesh mohankumar <vignesh@vigneshmohankumar.com>
Co-authored-by: cartcrom <39385577+cartcrom@users.noreply.github.com>
2022-10-13 12:20:59 -04:00
Connor McEwen
cdd5b66d1b fix: merge conflict 2022-10-13 11:38:26 -04:00
Zach Pomerantz
b65fffc5f7 feat: load token from query data (#4904)
* fix: do not fetch balances cross-chain

* build: updgrade widget

* feat: cleanly load and switch chains from widget

* fix: load token from query data

* build: trigger checks

* fix: do not override native token from query

* fix: catch error on switch chain

* refactor: useTokenFromActiveNetwork

* refactor: defaultToken behavior clarification
2022-10-13 11:38:26 -04:00
Jordan Frankfurt
a3a3e934a1 fix: mobile tweaks (#4910)
* update manifest theme colors to magenta

* text spacing and right positioning on very small screens
2022-10-13 11:38:26 -04:00
Jordan Frankfurt
8a4e07e6b2 fix: update font-weight values to match spec (#4863) 2022-10-13 11:38:26 -04:00
Jordan Frankfurt
7f4413c79c fix: click area should match button effect (#4887) 2022-10-13 11:38:26 -04:00
cartcrom
55beaf65a2 fix: use correct optimism icon in explore (#4893) 2022-10-13 11:38:26 -04:00
Zach Pomerantz
20fe76ad29 test: run tests on all PRs (#4905) 2022-10-13 11:38:26 -04:00
lynn
0d5bc753ca fix: Web 1561 logging event for clicking on explore banner toast + WEB-1543 [Explore Banner] String should be sentence case (#4899)
* explore banner changes

* remove console log

* oops
2022-10-13 11:38:26 -04:00
lynn
79507a4b03 fix: Remove token selector flash of old ui (#4896)
remove token selector flash of old view
2022-10-13 11:38:26 -04:00
lynn
3a1be04a36 fix: upgrade pkg to eliminate compile error (#4898)
* upgrade pkg

* dedup
2022-10-13 11:38:26 -04:00
vignesh mohankumar
ec523e5235 revert: add back phase0 bug fixes (#4888)
* Revert "revert: removing phase0 bug fixes temporarily (#4886)"

This reverts commit 06291a15a6.

* use token amount

* Revert "use token amount"

This reverts commit f47c00358b.

* dont render if empty
2022-10-13 11:38:26 -04:00
Jordan Frankfurt
c7b1aa2948 fix: remove old background-color flash (#4890)
remove old background-color
2022-10-13 11:38:26 -04:00
Greg Bugyis
9370383f64 feat: Remove value prop from NFT Explore (#4914) 2022-10-13 17:35:56 +03:00
Charles Bachmeier
9856c03566 fix: issues with NFT listing (#4828)
* can approve marketplaces for listing

* add consts

* fix issue with expiration time

* adding api key to env + changing variable

* updating env name to req

* removing console

* use v3 endpoint

* remove unussed util

* uneeded null check

* remove console.log

Co-authored-by: Jack Short <john.short.tj@gmail.com>
Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-10-12 15:24:55 -07:00
vignesh mohankumar
ec686bcaa5 chore: remove phase0 flags (#4833)
* chore: remove phase0 flags

* unused imports

* update snapshots

* fix link test

* use the data cy

* delete lists test

* rm wallet.test

* update snapshot

* fix: update default html background-color (#4881)

* Revert "fix: update default html background-color (#4881)"

This reverts commit 043f844067.

Co-authored-by: Connor McEwen <connor.mcewen@gmail.com>
2022-10-12 10:56:20 -04:00
vignesh mohankumar
06291a15a6 revert: removing phase0 bug fixes temporarily (#4886)
* Revert "fix: handle backspace out of /tokens (#4879)"

This reverts commit 3e40a6f5c6.

* Revert "fix: add padding-bottom to TokenDetailsLayout (#4882)"

This reverts commit f91b48e214.

* Revert "fix: updates outputCurrency link in mobile balance footer (#4885)"

This reverts commit e340f405b4.
2022-10-12 09:34:03 -05:00
vignesh mohankumar
3e40a6f5c6 fix: handle backspace out of /tokens (#4879)
* fix: handle backspace out of /tokens

* simplify banner

* simplify banner more
2022-10-12 09:05:53 -05:00
vignesh mohankumar
f91b48e214 fix: add padding-bottom to TokenDetailsLayout (#4882) 2022-10-12 09:03:14 -05:00
vignesh mohankumar
e340f405b4 fix: updates outputCurrency link in mobile balance footer (#4885) 2022-10-12 09:02:54 -05:00
Connor McEwen
24fc39b016 fix: upgrade widget to fix token selection field (#4878) 2022-10-12 00:41:55 -04:00
Zach Pomerantz
2fc3f3c00e fix: network token memoization (#4877)
* build: upgrade redux-multicall

* fix: memoize network token

* docs: invalid token
2022-10-11 21:41:20 -07:00
Connor McEwen
21e0faeb1e fix: actually fetch token object on page (#4875)
* fix: actually fetch token object on page

* syntax
2022-10-11 20:46:59 -04:00
Zach Pomerantz
4b71a8d5f4 build: upgrade widget (#4873) 2022-10-11 17:00:11 -07:00
Zach Pomerantz
4075965252 revert: "revert: "fix: use widget network connection"" (#4871)
Revert "revert: "fix: use widget network connection" (#4870)"

This reverts commit 3538312769.
2022-10-11 16:21:51 -07:00
Zach Pomerantz
3538312769 revert: "fix: use widget network connection" (#4870)
Revert "fix: use widget network connection (#4866)"

This reverts commit 2924f36970.
2022-10-11 16:06:17 -07:00
Zach Pomerantz
2924f36970 fix: use widget network connection (#4866)
* fix: switch network connector on other network token details

* fix: use jsonRpcUrlMap for widget

* fix: use widget network provider

* build: upgrade widget

* build: upgrade widget

* fix: SwapController typing
2022-10-11 15:45:43 -07:00
lynn
bf16dfa09c fix: fix twitter links (#4868)
* fix twitter links

* fix
2022-10-11 16:42:19 -04:00
Charles Bachmeier
910e86d6a2 feat: Add loading skeleton for profile page (#4823)
* Add loading skeleton for profile page

* add consts for sidebar width and padding

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-10-11 12:37:55 -07:00
Connor McEwen
537fea103e fix: revert max input formatting change (#4865)
revert max input formatting change
2022-10-11 14:16:52 -04:00
lynn
87a6e2709b fix: remove double $ render (#4864)
remove double $ render
2022-10-11 13:43:11 -04:00
Jack Short
d704e78223 feat: details owner actions (#4851)
* feat: initial pass at price details container

* feat: nft details owners actions

* adding check for bad types
2022-10-11 12:53:33 -04:00
Connor McEwen
8ceabd513c feat: upgrade widget build (#4862) 2022-10-11 11:48:33 -04:00
Zach Pomerantz
4806c69053 fix: map wrapped tokens to native tokens for top/trending (#4855)
* fix: map wrapped to native for trending tokens

* fix: close top tokens subscription

* fix: map wrapped to native for top tokens

* fix: link to native from native

* refactor: unwrapToken.ts

* fix: mv query to effect

* fix: native token logos

* fix: use NATIVE_CHAIN_ID

* fix: rm todo

* fix: include NATIVE_CHAIN_ID

* fix: NATIVE_CHAIN_ID import
2022-10-10 22:34:11 -07:00
github-actions[bot]
c9f333003b chore(i18n): new Crowdin translations (#4713)
* 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-10-10 23:23:39 -05:00
vignesh mohankumar
33fa32cb07 chore: remove unused TokensLoading (#4858) 2022-10-10 23:16:06 -05:00
vignesh mohankumar
5d1377af80 fix: track token_list_rank (#4857) 2022-10-10 23:15:47 -05:00
Zach Pomerantz
a75e239fd2 fix: keep search history synced with trending (#4854) 2022-10-10 19:34:27 -07:00
lynn
a663482dc6 fix: second part of price fixes (#4852)
* init

* use named args

* transaction price format

* respond to comments

* 1.00 rounding edge case
2022-10-10 22:09:02 -04:00
lynn
e8d6235529 fix: revise strings on token details + change privacy disclaimer edits to be enabled by phase 1 flag (#4856)
* init

* nft flag name change
2022-10-10 21:58:42 -04:00
cartcrom
d8677d8a6d feat: lazy load sparklines (#4827)
* testing removing sparklines

* fixed build

* filter working

* added lazy loading of sparklines

* fixed bugs

* removed comments

* add back memo

Co-authored-by: Connor McEwen <connor.mcewen@gmail.com>
2022-10-10 16:51:45 -04:00
Callil Capuozzo
351f66a83e fix: Update input placeholder text color (#4848)
Update input placeholder text color
2022-10-10 16:50:35 -04:00
omahs
79c7c01964 fix: minor typo (#4845)
Fix: typo

Fix: typo
2022-10-10 13:25:14 -07:00
Greg Bugyis
f51474b66d feat: [Collection Stats] update listings copy and change value to percentage (#4718) 2022-10-10 21:28:57 +03:00
lynn
14f01905d7 fix: price formatting spot check fixes (#4850)
fix price formatting
2022-10-10 13:49:32 -04:00
vignesh mohankumar
23eb31e6a2 fix: aligns token ranking column (#4849) 2022-10-10 13:28:19 -04:00
lynn
e8d4f00f49 fix: remove no tokens state and replace w loading (#4847)
* remove no tokens state and replace w loading

* update snapshot
2022-10-10 12:23:49 -04:00
aballerr
978e3f945d feat: adding in trait chips (#4807)
* adding in traitchips
2022-10-10 11:23:45 -04:00
Jordan Frankfurt
072f394476 feat(WEB-1457): update banner text (#4841) 2022-10-07 14:06:55 -05:00
lynn
5926d7037d fix: handle tiny numbers (#4842)
* handle tiny numbers

* remove console
2022-10-07 14:43:52 -04:00
lynn
52b51ee7d0 fix: price calculations are wrong (#4840)
* make every duration have latest price point

* simplify

* fix info tip icon regression

* remove unecessary line

* use memo

* fix MASSIVE ERROR in number cal

* bump widget v

* delete unit test failing for some obscure reason, added todo

* fix unit tests
2022-10-07 13:51:31 -04:00
Jordan Frankfurt
54f831ede4 fix(token-details): make breakpoints more consistent and use only theme.breakpoint values (#4836)
* fix(token-details): make breakpoints more consistent and use only theme.breakpoint values

* feat(token-details): design patch for mobile swap button footer

* render mobile balance footer w/ only swap button when user is disconnected

* don't render BalanceSummary if no balances are provided

* full width swap button footer until breakpoints.lg
2022-10-07 12:35:27 -05:00
Fred Zaw
c9ead63ff6 style: increase name column width in token table (#4835)
increase name column width in token table
2022-10-07 13:27:51 -04:00
Fred Zaw
9ca74cf8d2 style: updated crosshair light/dark color and stroke width (#4837)
updated crosshair light/dark color and stroke width
2022-10-07 13:24:21 -04:00
Connor McEwen
2a0d455419 fix: animation on mount wasn't happening (#4839) 2022-10-07 13:20:29 -04:00
Connor McEwen
9e959ca455 fix: import widget font (#4813)
* import widget font

* update comment
2022-10-07 12:00:42 -04:00
lynn
d3f6796bb9 fix: make sure every duration has latest price point (#4832)
* make every duration have latest price point

* simplify

* fix info tip icon regression

* remove unecessary line

* use memo
2022-10-07 11:44:36 -04:00
cartcrom
64d6eeabcb fix: info tip light theme issue (#4825)
fixed light theme issue

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
2022-10-07 09:59:58 -04:00
lynn
859258c25c fix: standardize decimals for token prices in explore, token details, search (#4821)
* token prices in explore, token details, search

* use correct dollar format function

* additional price corrections

* remove oopsies

* changes in notion from andy

* use currencyAmountToPreciseFloat everywhere
2022-10-06 20:27:23 -04:00
Jordan Frankfurt
2338255a54 fix(token-details): nits from design re: headers, spacing, layout (#4829)
* fix(token-details): nits from design re: headers, spacing, layout

* pr feedback
2022-10-06 18:26:51 -05:00
Zach Pomerantz
843afa93c3 fix: do not load token list (#4820)
* build: upgrade widgets

* fix: use empty token list

* build: trigger checks
2022-10-06 17:50:33 -05:00
Zach Pomerantz
5441e63825 build: upgrade widgets (#4824) 2022-10-06 14:35:34 -07:00
Charles Bachmeier
7bf741027e feat: profile listing sidebar (#4809)
* Add sell header to Bag

* split bag content to its own file

* empty tag state

* continue button

* file re-arranging and add profile select row content

* update padding

* better null check

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-10-06 14:23:57 -07:00
Zach Pomerantz
0017e2fcc8 test: use goerli (#4826)
* test: use goerli

* test: skip rinkeby-specific tests

* revert: retain arbitrum rinkeby
2022-10-06 15:16:58 -05:00
cartcrom
5c9c8b4cb7 feat: explore category info icons (#4787)
* added tooltips
* switched to premade tooltip component
* added 'help' cursor
2022-10-06 14:39:41 -04:00
Jack Short
873d0ea2a3 chore: breaking details into two containers (#4822)
* holding

* breaking up code into two areas for development purposes
2022-10-06 13:56:41 -04:00
Charles Bachmeier
db4987f557 feat: Sell Mode Button (#4803)
* remove sort and search functionality

* add sell mode button toggle

* update cards to sell mode

* split wallet asset display file

* remove unused style

* address comments

Co-authored-by: Charles Bachmeier <charlie@genie.xyz>
2022-10-06 10:44:14 -07:00
Jordan Frankfurt
b0b61f886d fix: refactors the way chainId is accessed in some places (#4818)
fix: refactors the way some data is accessed
2022-10-06 11:55:46 -05:00
cartcrom
5d4b25f417 feat: chart states polish (#4819)
* init
* finished missing data component
* finished feature
2022-10-06 12:51:37 -04:00
Callil Capuozzo
c88d7c880b fix: Adjust verified icons to accentAction (#4804)
* Adjust verified icons to accentAction

* Adjust to match designs

* Apply verified icons in all places

* use theme.accentAction

Co-authored-by: Jordan Frankfurt <jordanwfrankfurt@gmail.com>
2022-10-06 11:37:48 -05:00
Jordan Frankfurt
a96d13978b feat(token-details): link between native:wrapped assets in the balance summary (#4817)
* feat(token-details): link between native:wrapped assets in the balance summary

* update pageChainName access method
2022-10-06 11:36:52 -05:00
Jordan Frankfurt
22b26de78d feat(token-details): native/wrapped-native balance UI (#4814)
* feat(token-details): balance hook

* mobile balance UI

* feat(token-details): sidebar balance summary

* pr feedback
2022-10-05 22:46:24 -05:00
lynn
53b57879a3 fix: link to explore table when explore tokens banner clicked (#4811)
* token banner

* responses to jordan
2022-10-05 22:59:35 -04:00
lynn
d794cef770 fix: more explore table fixes (#4806)
* testing

* remove dev dep

* fixed

* fixes

* add developer comments

* link to eth token page when eth clicked in search bar

* undo commit to wrong pr / branch smh

* fix ascending sorted tokens index
2022-10-05 22:51:22 -04:00
lynn
19f175ba89 fix: make eth in search bar navigate to eth token details (#4812)
* make eth in search bar navigate to eth token details

* use local resolving functions
2022-10-05 22:44:27 -04:00
vignesh mohankumar
aaf105ef51 fix: add redirect from /tokens to /tokens/ethereum (#4816) 2022-10-05 20:14:32 -04:00
vignesh mohankumar
974308f939 fix: center TimeButton in PriceChart horizontally (#4795) 2022-10-05 19:28:33 -04:00
vignesh mohankumar
0ec738a48a fix: update hover states in control CurrencyInputSelector (#4815) 2022-10-05 19:27:12 -04:00
cartcrom
904f6e22f4 feat: search attempts route to current chain (#4785)
* finished feature

* added L2 icons

* removed console.log
2022-10-05 15:50:33 -04:00
cartcrom
66fad96e61 perf: improve token details query performance (#4808)
* updated hook
* fixed build error
2022-10-05 15:50:08 -04:00
cartcrom
9037930e56 feat: added widget skeleton to loading state (#4793)
* added widget skeleton to loading state

* removed memo

* made theme inline
2022-10-05 11:19:46 -04:00
cartcrom
d62013177d feat: add 52 w highlow (#4791)
updated query
2022-10-05 10:44:09 -04:00
404 changed files with 17035 additions and 19759 deletions

5
.env
View File

@@ -1,8 +1,9 @@
# These API keys are intentionally public. Please do not report them - thank you for your concern.
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
REACT_APP_AMPLITUDE_TEST_KEY="add-the-real-test-key-if-you-need-to-test-amplitude-events"
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
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"
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"

View File

@@ -1,5 +1,4 @@
REACT_APP_AMPLITUDE_KEY="b8f7dabddb1c3b03b394619767972160"
REACT_APP_AMPLITUDE_TEST_KEY="1c694b28cd089acc2c386d518f93a775"
REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
REACT_APP_INFURA_KEY="099fc58e0de9451d80b18d7c74caa7c1"
REACT_APP_FORTMATIC_KEY="pk_live_F937DF033A1666BF"
REACT_APP_GOOGLE_ANALYTICS_ID="G-KDP9B6W4H8"

View File

@@ -5,8 +5,6 @@ on:
branches:
- main
pull_request:
branches:
- main
# manual trigger
workflow_dispatch:

View File

@@ -16,4 +16,4 @@
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
}

View File

@@ -21,20 +21,20 @@ describe('Add Liquidity', () => {
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('not.contain.text', 'ETH')
})
it('token not in storage is loaded', () => {
it.skip('token not in storage is loaded', () => {
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'MKR')
})
it('single token can be selected', () => {
it.skip('single token can be selected', () => {
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
})
it('loads fee tier distribution', () => {
it.skip('loads fee tier distribution', () => {
cy.fixture('feeTierDistribution.json').then((feeTierDistribution) => {
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req: CyHttpMessages.IncomingHttpRequest) => {
if (hasQuery(req, 'FeeTierDistributionQuery')) {

View File

@@ -2,7 +2,7 @@
describe('Link', () => {
it('should update route', () => {
cy.visit('/')
cy.get('[data-cy="pool-nav-link"]').click()
cy.contains('Pool').click()
cy.get('[data-cy="join-pool-button"]').should('exist')
})
})

View File

@@ -1,11 +0,0 @@
describe('Lists', () => {
beforeEach(() => {
cy.visit('/swap')
})
// @TODO check if default lists are active when we have them
it('change list', () => {
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('.list-token-manage-button').click()
})
})

View File

@@ -23,7 +23,7 @@ describe('Remove Liquidity', () => {
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH')
})
it('token not in storage is loaded', () => {
it.skip('token not in storage is loaded', () => {
cy.visit('/remove/v2/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'SKL')
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')

View File

@@ -1,31 +0,0 @@
import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../support/ethereum'
describe('Wallet', () => {
before(() => {
cy.visit('/swap')
})
it('displays account details', () => {
cy.get('[data-testid=web3-status-connected]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED).click()
})
it('displays account view in wallet modal', () => {
cy.get('[data-testid=web3-account-identifier-row]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
})
it('changes back to the options grid', () => {
cy.contains('Change').click()
cy.get('[data-testid=option-grid]').should('exist')
})
it('selects injected wallet option', () => {
cy.contains('Injected').click()
cy.get('[data-testid=web3-account-identifier-row]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED)
})
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

@@ -34,7 +34,7 @@ Cypress.Commands.overwrite(
cy.intercept('/service-worker.js', options?.serviceWorker ? undefined : { statusCode: 404 }).then(() => {
original({
...options,
url: (url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url) + '?chain=rinkeby',
url: (url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url) + '?chain=goerli',
onBeforeLoad(win) {
options?.onBeforeLoad?.(win)
win.localStorage.clear()

View File

@@ -19,10 +19,10 @@ export const TEST_ADDRESS_NEVER_USE_SHORTENED = `${TEST_ADDRESS_NEVER_USE.substr
6
)}...${TEST_ADDRESS_NEVER_USE.substr(-4, 4)}`
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
const provider = new JsonRpcProvider('https://goerli.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
const signer = new Wallet(TEST_PRIVATE_KEY, provider)
export const injected = new (class extends Eip1193Bridge {
chainId = 4
chainId = /* GOERLI= */ 5
async sendAsync(...args: any[]) {
console.debug('sendAsync called', ...args)

View File

@@ -100,8 +100,8 @@
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-better-styled-components": "^1.1.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-react": "^7.19.0",
"eslint-plugin-react-hooks": "^4.0.0",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"jest-styled-components": "^7.0.8",
@@ -116,7 +116,6 @@
"typescript": "^4.4.3"
},
"dependencies": {
"@amplitude/analytics-browser": "^1.1.4",
"@coinbase/wallet-sdk": "^3.3.0",
"@fontsource/ibm-plex-mono": "^4.5.1",
"@fontsource/inter": "^4.5.1",
@@ -132,10 +131,12 @@
"@react-hook/window-scroll": "^1.3.0",
"@reduxjs/toolkit": "^1.6.1",
"@types/react-relay": "^13.0.2",
"@uniswap/analytics": "1.0.3",
"@uniswap/analytics-events": "1.1.0",
"@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "1.0.1",
"@uniswap/redux-multicall": "^1.1.5",
"@uniswap/redux-multicall": "^1.1.6",
"@uniswap/router-sdk": "^1.3.0",
"@uniswap/sdk-core": "^3.0.1",
"@uniswap/smart-order-router": "^2.10.0",
@@ -146,7 +147,7 @@
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "^1.1.1",
"@uniswap/v3-sdk": "^3.9.0",
"@uniswap/widgets": "^2.9.2",
"@uniswap/widgets": "^2.18.0",
"@vanilla-extract/css": "^1.7.2",
"@vanilla-extract/css-utils": "^0.1.2",
"@vanilla-extract/dynamic": "^2.0.2",
@@ -158,7 +159,7 @@
"@visx/react-spring": "^2.12.2",
"@visx/responsive": "^2.10.0",
"@visx/shape": "^2.11.1",
"@walletconnect/ethereum-provider": "1.7.1",
"@walletconnect/ethereum-provider": "^1.8.0",
"@web3-react/coinbase-wallet": "^8.0.34-beta.0",
"@web3-react/core": "^8.0.35-beta.0",
"@web3-react/eip1193": "^8.0.26-beta.0",
@@ -169,7 +170,6 @@
"@web3-react/types": "^8.0.20-beta.0",
"@web3-react/url": "^8.0.25-beta.0",
"@web3-react/walletconnect": "^8.0.35-beta.0",
"ajv": "^6.12.3",
"array.prototype.flat": "^1.2.4",
"array.prototype.flatmap": "^1.2.4",
"cids": "^1.0.0",
@@ -195,6 +195,7 @@
"polyfill-object.fromentries": "^1.0.1",
"popper-max-size-modifier": "^0.2.0",
"qs": "^6.9.4",
"rc-slider": "^10.0.1",
"react": "^18.2.0",
"react-confetti": "^6.0.0",
"react-dom": "^18.2.0",

View File

@@ -15,7 +15,11 @@
<link rel="apple-touch-icon" sizes="512x512" href="%PUBLIC_URL%/images/512x512_App_Icon.png" />
<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="theme-color" content="#FC72FF" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com 'unsafe-inline'"
/>
<!--
manifest.json provides metadata used when the app is installed as a PWA.
@@ -73,6 +77,7 @@
}
#background-radial-gradient {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
position: fixed;
top: 0;
left: 0;
@@ -80,7 +85,6 @@
pointer-events: none;
width: 200vw;
height: 200vh;
background: radial-gradient(50% 50% at 50% 50%, #fc077d10 0%, rgba(255, 255, 255, 0) 100%);
transform: translate(-50vw, -100vh);
z-index: -1;
}
@@ -94,6 +98,7 @@
background-color: #212429;
}
}
@media (prefers-color-scheme: light) {
html {
background-color: #f7f8fa;
@@ -101,26 +106,15 @@
}
</style>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<!-- The root is the container of the app -->
<div id="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@@ -2,7 +2,10 @@
"background_color": "#fff",
"display": "standalone",
"homepage_url": "https://app.uniswap.org",
"providedBy": { "name": "Uniswap", "url": "https://uniswap.org" },
"providedBy": {
"name": "Uniswap",
"url": "https://uniswap.org"
},
"icons": [
{
"src": "./images/192x192_App_Icon.png",
@@ -23,5 +26,5 @@
"iconPath": "./images/256x256_App_Icon_Pink.svg",
"short_name": "Uniswap",
"start_url": ".",
"theme_color": "#ff007a"
"theme_color": "#FC72FFs"
}

View File

@@ -0,0 +1,6 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 14.4C0 6.4471 6.4471 0 14.4 0H33.6C41.5529 0 48 6.4471 48 14.4V33.6C48 41.5529 41.5529 48 33.6 48H14.4C6.4471 48 0 41.5529 0 33.6V14.4Z" fill="#B9B9FF"/>
<path d="M9.29999 15H12L15.3 24L12 33H9.29999L12.6 24L9.29999 15Z" fill="#121212"/>
<path d="M23.5727 33.192C22.1807 33.192 21.0367 32.872 20.1407 32.232C19.2607 31.592 18.8207 30.72 18.8207 29.616H21.2447C21.2447 30.112 21.4607 30.496 21.8927 30.768C22.3407 31.024 22.9167 31.152 23.6207 31.152H24.6287C25.4607 31.152 26.0767 30.992 26.4767 30.672C26.8767 30.336 27.0767 29.896 27.0767 29.352C27.0767 28.808 26.8847 28.384 26.5007 28.08C26.1167 27.776 25.5727 27.56 24.8687 27.432L23.0687 27.168C20.4447 26.752 19.1327 25.48 19.1327 23.352C19.1327 22.136 19.5327 21.208 20.3327 20.568C21.1327 19.928 22.2767 19.608 23.7647 19.608H24.6767C26.0527 19.608 27.1567 19.92 27.9887 20.544C28.8207 21.152 29.2367 21.96 29.2367 22.968H26.8127C26.8127 22.568 26.6127 22.248 26.2127 22.008C25.8287 21.768 25.3007 21.648 24.6287 21.648H23.7167C22.9807 21.648 22.4207 21.8 22.0367 22.104C21.6527 22.392 21.4607 22.808 21.4607 23.352C21.4607 24.28 22.1167 24.848 23.4287 25.056L25.2287 25.344C26.6687 25.568 27.7247 25.992 28.3967 26.616C29.0687 27.224 29.4047 28.104 29.4047 29.256C29.4047 30.488 28.9967 31.456 28.1807 32.16C27.3807 32.848 26.1807 33.192 24.5807 33.192H23.5727Z" fill="#121212"/>
<path d="M38.7 15H36L32.7 24L36 33H38.7L35.4 24L38.7 15Z" fill="#121212"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,64 +0,0 @@
import { createContext, memo, PropsWithChildren, useContext, useEffect, useMemo } from 'react'
import { sendAnalyticsEvent } from '.'
import { ElementName, EventName, ModalName, PageName, SectionName } from './constants'
export interface ITraceContext {
// Highest order context: eg Swap or Explore.
page?: PageName
// Enclosed section name. For contexts with modals, refers to the
// section of the page from which the user triggered the modal.
section?: SectionName
modal?: ModalName
// Element name mostly used to identify events sources
// Does not need to be unique given the higher order page and section.
element?: ElementName
}
export const TraceContext = createContext<ITraceContext>({})
type TraceProps = {
shouldLogImpression?: boolean // whether to log impression on mount
name?: EventName
properties?: Record<string, unknown>
} & ITraceContext
/**
* Sends an analytics event on mount (if shouldLogImpression is set),
* and propagates the context to child traces.
*/
export const Trace = memo(
({ shouldLogImpression, name, children, page, section, element, properties }: PropsWithChildren<TraceProps>) => {
const parentTrace = useContext(TraceContext)
const combinedProps = useMemo(
() => ({
...parentTrace,
...Object.fromEntries(Object.entries({ page, section, element }).filter(([_, v]) => v !== undefined)),
}),
[element, parentTrace, page, section]
)
useEffect(() => {
if (shouldLogImpression) {
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
}, [])
return <TraceContext.Provider value={combinedProps}>{children}</TraceContext.Provider>
}
)
Trace.displayName = 'Trace'

View File

@@ -1,75 +0,0 @@
import { Children, cloneElement, isValidElement, memo, PropsWithChildren, SyntheticEvent } from 'react'
import { sendAnalyticsEvent } from '.'
import { Event, EventName } from './constants'
import { ITraceContext, Trace, TraceContext } from './Trace'
type TraceEventProps = {
events: Event[]
name: EventName
properties?: Record<string, unknown>
shouldLogImpression?: boolean
} & ITraceContext
/**
* Analytics instrumentation component that wraps event callbacks with logging logic.
*
* @example
* <TraceEvent events={[Event.onClick]} element={ElementName.SWAP_BUTTON}>
* <Button onClick={() => console.log('clicked')}>Click me</Button>
* </TraceEvent>
*/
export const TraceEvent = memo((props: PropsWithChildren<TraceEventProps>) => {
const { shouldLogImpression, name, properties, events, children, ...traceProps } = props
return (
<Trace {...traceProps}>
<TraceContext.Consumer>
{(traceContext) =>
Children.map(children, (child) => {
if (!isValidElement(child)) {
return child
}
// For each child, augment event handlers defined in `events` with event tracing.
return cloneElement(
child,
getEventHandlers(child, traceContext, events, name, properties, shouldLogImpression)
)
})
}
</TraceContext.Consumer>
</Trace>
)
})
TraceEvent.displayName = 'TraceEvent'
/**
* Given a set of child element and event props, returns a spreadable
* object of the event handlers augmented with analytics logging.
*/
function getEventHandlers(
child: React.ReactElement,
traceContext: ITraceContext,
events: Event[],
name: EventName,
properties?: Record<string, unknown>,
shouldLogImpression = true
) {
const eventHandlers: Partial<Record<Event, (e: SyntheticEvent<Element, Event>) => void>> = {}
for (const event of events) {
eventHandlers[event] = (eventHandlerArgs: unknown) => {
// call child event handler with original arguments, must be in array
const args = Array.isArray(eventHandlerArgs) ? eventHandlerArgs : [eventHandlerArgs]
child.props[event]?.apply(child, args)
// augment handler with analytics logging
if (shouldLogImpression) sendAnalyticsEvent(name, { ...traceContext, ...properties, action: event })
}
}
// return a spreadable event handler object
return eventHandlers
}

View File

@@ -1,136 +0,0 @@
/**
* Event names that can occur in this application.
*
* Subject to change as new features are added and new events are defined
* and logged.
*/
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',
NAVBAR_SEARCH_SELECTED = 'Navbar Search Selected',
NAVBAR_SEARCH_EXITED = 'Navbar Search Exited',
SWAP_AUTOROUTER_VISUALIZATION_EXPANDED = 'Swap Autorouter Visualization Expanded',
SWAP_DETAILS_EXPANDED = 'Swap Details Expanded',
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_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',
TOKEN_SELECTED = 'Token Selected',
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.
}
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_TYPE = 'wallet_type',
}
export enum BROWSER {
FIREFOX = 'Mozilla Firefox',
SAMSUNG = 'Samsung Internet',
OPERA = 'Opera',
INTERNET_EXPLORER = 'Microsoft Internet Explorer',
EDGE = 'Microsoft Edge (Legacy)',
EDGE_CHROMIUM = 'Microsoft Edge (Chromium)',
CHROME = 'Google Chrome or Chromium',
SAFARI = 'Apple Safari',
BRAVE = 'Brave',
UNKNOWN = 'unknown',
}
export enum WALLET_CONNECTION_RESULT {
SUCCEEDED = 'Succeeded',
FAILED = 'Failed',
}
export const NATIVE_CHAIN_ID = 'NATIVE'
export enum SWAP_PRICE_UPDATE_USER_RESPONSE {
ACCEPTED = 'Accepted',
REJECTED = 'Rejected',
}
/**
* Known pages in the app. Highest order context.
*/
export enum PageName {
TOKENS_PAGE = 'tokens-page',
POOL_PAGE = 'pool-page',
SWAP_PAGE = 'swap-page',
VOTE_PAGE = 'vote-page',
// alphabetize additional page names.
}
/**
* Sections. Disambiguates low-level elements that may share a name.
* eg a `back` button in a modal will have the same `element`,
* but a different `section`.
*/
export enum SectionName {
CURRENCY_INPUT_PANEL = 'swap-currency-input',
CURRENCY_OUTPUT_PANEL = 'swap-currency-output',
// alphabetize additional section names.
}
/** Known modals for analytics purposes. */
export enum ModalName {
CONFIRM_SWAP = 'confirm-swap-modal',
TOKEN_SELECTOR = 'token-selector-modal',
// alphabetize additional modal names.
}
/**
* Known element names for analytics purposes.
* Use to identify low-level components given a TraceContext
*/
export enum ElementName {
AUTOROUTER_VISUALIZATION_ROW = 'expandable-autorouter-visualization-row',
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',
NAVBAR_SEARCH_INPUT = 'navbar-search-input',
PRICE_UPDATE_ACCEPT_BUTTON = 'price-update-accept-button',
SWAP_BUTTON = 'swap-button',
SWAP_DETAILS_DROPDOWN = 'swap-details-dropdown',
SWAP_TOKENS_REVERSE_ARROW_BUTTON = 'swap-tokens-reverse-arrow-button',
TOKEN_SELECTOR_ROW = 'token-selector-row',
WALLET_TYPE_OPTION = 'wallet-type-option',
// alphabetize additional element names.
}
/**
* Known events that trigger callbacks.
* @example
* <TraceEvent events={[Event.onClick]} element={name}>
*/
export enum Event {
onClick = 'onClick',
onFocus = 'onFocus',
onKeyPress = 'onKeyPress',
onSelect = 'onSelect',
// alphabetize additional events.
}

View File

@@ -1,90 +0,0 @@
import { Identify, identify, init, track } from '@amplitude/analytics-browser'
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.
*
* Uniswap has two Amplitude projects: test and production. You must be a
* member of the organization on Amplitude to view details.
*/
export function initializeAnalytics() {
if (typeof API_KEY === 'undefined') {
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= */
{
// Disable tracking of private user information by Amplitude
trackingOptions: {
// 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,
dma: false, // designated market area
},
}
)
}
/** Sends an event to Amplitude. */
export function sendAnalyticsEvent(eventName: string, eventProperties?: Record<string, unknown>) {
if (!API_KEY) {
console.log(`[analytics(${eventName})]: ${JSON.stringify(eventProperties)}`)
return
}
track(eventName, eventProperties)
}
type Value = string | number | boolean | string[] | number[]
/**
* Class that exposes methods to mutate the User Model's properties in
* Amplitude that represents the current session's user.
*
* See https://help.amplitude.com/hc/en-us/articles/115002380567-User-properties-and-event-properties
* for details.
*/
class UserModel {
private log(method: string, ...parameters: unknown[]) {
console.debug(`[amplitude(Identify)]: ${method}(${parameters})`)
}
private call(mutate: (event: Identify) => Identify) {
if (!isProductionEnv()) {
const log = (_: Identify, method: string) => this.log.bind(this, method)
mutate(new Proxy(new Identify(), { get: log }))
return
}
identify(mutate(new Identify()))
}
set(key: string, value: Value) {
this.call((event) => event.set(key, value))
}
setOnce(key: string, value: Value) {
this.call((event) => event.setOnce(key, value))
}
add(key: string, value: number) {
this.call((event) => event.add(key, value))
}
postInsert(key: string, value: string | number) {
this.call((event) => event.postInsert(key, value))
}
remove(key: string, value: string | number) {
this.call((event) => event.remove(key, value))
}
}
export const user = new UserModel()

View File

@@ -1,23 +0,0 @@
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
import { NATIVE_CHAIN_ID } from './constants'
export const getDurationUntilTimestampSeconds = (futureTimestampInSecondsSinceEpoch?: number): number | undefined => {
if (!futureTimestampInSecondsSinceEpoch) return undefined
return futureTimestampInSecondsSinceEpoch - new Date().getTime() / 1000
}
export const getDurationFromDateMilliseconds = (start: Date): number => {
return new Date().getTime() - start.getTime()
}
export const formatToDecimal = (
intialNumberObject: Percent | CurrencyAmount<Token | Currency>,
decimalPlace: number
): number => parseFloat(intialNumberObject.toFixed(decimalPlace))
export const getTokenAddress = (currency: Currency) => (currency.isNative ? NATIVE_CHAIN_ID : currency.address)
export const formatPercentInBasisPointsNumber = (percent: Percent): number => parseFloat(percent.toFixed(2)) * 100
export const formatPercentNumber = (percent: Percent): number => parseFloat(percent.toFixed(2))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -1,4 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.24453 18.0887C3.24331 19.0467 3.47372 19.7558 3.93576 20.2158C4.39658 20.6771 5.09574 20.904 6.03326 20.8967H8.11975C8.20693 20.8934 8.29386 20.9079 8.37521 20.9395C8.45656 20.9711 8.53062 21.019 8.5928 21.0802L10.0779 22.5484C10.7527 23.2226 11.4139 23.5578 12.0617 23.5541C12.7096 23.5504 13.3709 23.2152 14.0456 22.5484L15.5124 21.0802C15.5767 21.0182 15.6529 20.97 15.7365 20.9385C15.82 20.9069 15.9091 20.8927 15.9982 20.8967H18.0719C19.0192 20.8979 19.7251 20.6673 20.1896 20.2048C20.6541 19.7423 20.8864 19.0333 20.8864 18.0777V16.0021C20.8816 15.8222 20.9474 15.6476 21.0697 15.5157L22.5365 14.0475C23.2198 13.3758 23.559 12.7145 23.5541 12.0636C23.5492 11.4127 23.21 10.7508 22.5365 10.0779L21.0697 8.6097C20.9471 8.47802 20.8812 8.30329 20.8864 8.12336V6.04769C20.8851 5.09092 20.6547 4.3819 20.1951 3.92064C19.7355 3.45939 19.0278 3.22875 18.0719 3.22875H15.9982C15.9091 3.23242 15.8201 3.21807 15.7366 3.18653C15.6532 3.155 15.5769 3.10694 15.5124 3.04523L14.0456 1.57703C13.3709 0.902883 12.7096 0.567648 12.0617 0.571319C11.4139 0.574989 10.7527 0.910224 10.0779 1.57703L8.5928 3.04523C8.53043 3.10622 8.45638 3.15393 8.37508 3.18547C8.29377 3.21701 8.20689 3.23173 8.11975 3.22875H6.03326C5.08718 3.22998 4.38373 3.45877 3.92291 3.91513C3.4621 4.3715 3.23168 5.08235 3.23168 6.04769V8.12887C3.23683 8.3088 3.17096 8.48352 3.04833 8.6152L1.58154 10.0834C0.908042 10.7551 0.571289 11.417 0.571289 12.0691C0.571289 12.7213 0.912332 13.3844 1.59439 14.0585L3.06118 15.5267C3.18346 15.6586 3.24928 15.8332 3.24453 16.0131V18.0887Z" fill="#4C82FB"/>
<path d="M3.24453 18.0887C3.24331 19.0467 3.47372 19.7558 3.93576 20.2158C4.39658 20.6771 5.09574 20.904 6.03326 20.8967H8.11975C8.20693 20.8934 8.29386 20.9079 8.37521 20.9395C8.45656 20.9711 8.53062 21.019 8.5928 21.0802L10.0779 22.5484C10.7527 23.2226 11.4139 23.5578 12.0617 23.5541C12.7096 23.5504 13.3709 23.2152 14.0456 22.5484L15.5124 21.0802C15.5767 21.0182 15.6529 20.97 15.7365 20.9385C15.82 20.9069 15.9091 20.8927 15.9982 20.8967H18.0719C19.0192 20.8979 19.7251 20.6673 20.1896 20.2048C20.6541 19.7423 20.8864 19.0333 20.8864 18.0777V16.0021C20.8816 15.8222 20.9474 15.6476 21.0697 15.5157L22.5365 14.0475C23.2198 13.3758 23.559 12.7145 23.5541 12.0636C23.5492 11.4127 23.21 10.7508 22.5365 10.0779L21.0697 8.6097C20.9471 8.47802 20.8812 8.30329 20.8864 8.12336V6.04769C20.8851 5.09092 20.6547 4.3819 20.1951 3.92064C19.7355 3.45939 19.0278 3.22875 18.0719 3.22875H15.9982C15.9091 3.23242 15.8201 3.21807 15.7366 3.18653C15.6532 3.155 15.5769 3.10694 15.5124 3.04523L14.0456 1.57703C13.3709 0.902883 12.7096 0.567648 12.0617 0.571319C11.4139 0.574989 10.7527 0.910224 10.0779 1.57703L8.5928 3.04523C8.53043 3.10622 8.45638 3.15393 8.37508 3.18547C8.29377 3.21701 8.20689 3.23173 8.11975 3.22875H6.03326C5.08718 3.22998 4.38373 3.45877 3.92291 3.91513C3.4621 4.3715 3.23168 5.08235 3.23168 6.04769V8.12887C3.23683 8.3088 3.17096 8.48352 3.04833 8.6152L1.58154 10.0834C0.908042 10.7551 0.571289 11.417 0.571289 12.0691C0.571289 12.7213 0.912332 13.3844 1.59439 14.0585L3.06118 15.5267C3.18346 15.6586 3.24928 15.8332 3.24453 16.0131V18.0887Z" fill="currentColor"/>
<path d="M11.996 15.9909C11.7795 16.3208 11.4599 16.5064 11.0887 16.5064C10.7072 16.5064 10.4083 16.3517 10.1299 15.9909L7.69677 13.0216C7.5215 12.8051 7.42871 12.5783 7.42871 12.3309C7.42871 11.8154 7.82049 11.4133 8.32567 11.4133C8.63497 11.4133 8.8824 11.5267 9.12984 11.8463L11.0475 14.2897L15.1199 7.75329C15.3364 7.40275 15.6147 7.23779 15.924 7.23779C16.4086 7.23779 16.8622 7.57802 16.8622 8.0832C16.8622 8.32033 16.7385 8.56777 16.6045 8.78427L11.996 15.9909Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -57,7 +57,6 @@ const FailedText = ({ transactionState }: { transactionState: TransactionState }
const FormattedCurrencyAmount = ({
rawAmount,
currencyId,
sigFigs = 2,
}: {
rawAmount: string
currencyId: string
@@ -67,7 +66,7 @@ const FormattedCurrencyAmount = ({
return currency ? (
<HighlightText>
{formatAmount(rawAmount, currency.decimals, sigFigs)} {currency.symbol}
{formatAmount(rawAmount, currency.decimals, /* sigFigs= */ 6)} {currency.symbol}
</HighlightText>
) : null
}

View File

@@ -1,51 +0,0 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { ReactNode, useMemo } from 'react'
const BLOCKED_ADDRESSES: string[] = [
'0x7Db418b5D567A4e0E8c59Ad71BE1FcE48f3E6107',
'0x72a5843cc08275C8171E582972Aa4fDa8C397B2A',
'0x7F19720A857F834887FC9A7bC0a0fBe7Fc7f8102',
'0xA7e5d5A720f06526557c513402f2e6B5fA20b008',
'0x1da5821544e25c636c1417Ba96Ade4Cf6D2f9B5A',
'0x9F4cda013E354b8fC285BF4b9A60460cEe7f7Ea9',
'0x19Aa5Fe80D33a56D56c78e82eA5E50E5d80b4Dff',
'0x2f389cE8bD8ff92De3402FFCe4691d17fC4f6535',
'0xe7aa314c77F4233C18C6CC84384A9247c0cf367B',
'0x7F367cC41522cE07553e823bf3be79A889DEbe1B',
'0xd882cFc20F52f2599D84b8e8D58C7FB62cfE344b',
'0x901bb9583b24D97e995513C6778dc6888AB6870e',
'0x8576aCC5C05D6Ce88f4e49bf65BdF0C62F91353C',
'0xC8a65Fadf0e0dDAf421F28FEAb69Bf6E2E589963',
'0x308eD4B7b49797e1A98D3818bFF6fe5385410370',
'0x67d40EE1A85bf4a4Bb7Ffae16De985e8427B',
'0x6f1ca141a28907f78ebaa64fb83a9088b02a83',
'0x6acdfba02d390b97ac2b2d42a63e85293bcc1',
'0x48549a34ae37b12f6a30566245176994e17c6',
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121',
'0xC455f7fd3e0e12afd51fba5c106909934D8A0e4a',
'0x3CBdeD43EFdAf0FC77b9C55F6fC9988fCC9b757d',
'0x67d40EE1A85bf4a4Bb7Ffae16De985e8427B6b45',
'0x6F1cA141A28907F78Ebaa64fb83A9088b02A8352',
'0x6aCDFBA02D390b97Ac2b2d42A63E85293BCc160e',
'0x48549a34ae37b12f6a30566245176994e17c6b4a',
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0',
'0xC455f7fd3e0e12afd51fba5c106909934D8A0e4a',
'0x629e7Da20197a5429d30da36E77d06CdF796b71A',
'0x7FF9cFad3877F21d41Da833E2F775dB0569eE3D9',
'0x098B716B8Aaf21512996dC57EB0615e2383E2f96',
'0xfEC8A60023265364D066a1212fDE3930F6Ae8da7',
]
export default function Blocklist({ children }: { children: ReactNode }) {
const { account } = useWeb3React()
const blocked: boolean = useMemo(() => Boolean(account && BLOCKED_ADDRESSES.indexOf(account) !== -1), [account])
if (blocked) {
return (
<div>
<Trans>Blocked address</Trans>
</div>
)
}
return <>{children}</>
}

View File

@@ -50,12 +50,12 @@ export const BaseButton = styled(RebassButton)<
}
`
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')};
export const ButtonPrimary = styled(BaseButton)`
background-color: ${({ theme }) => theme.accentAction};
font-size: 20px;
font-weight: 600;
padding: 16px;
color: ${({ theme }) => theme.accentTextLightPrimary};
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.deprecated_primary1)};
background-color: ${({ theme }) => darken(0.05, theme.deprecated_primary1)};
@@ -79,35 +79,28 @@ export const ButtonPrimary = styled(BaseButton)<{ redesignFlag?: boolean }>`
}
`
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')};
export const ButtonLight = styled(BaseButton)`
background-color: ${({ theme }) => theme.accentActionSoft};
color: ${({ theme }) => theme.accentAction};
font-size: 20px;
font-weight: 600;
&:focus {
box-shadow: 0 0 0 1pt
${({ 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))};
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
background-color: ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
}
&:hover {
background-color: ${({ theme, disabled, redesignFlag }) =>
!disabled && (redesignFlag ? theme.accentActionSoft : darken(0.03, theme.deprecated_primary5))};
background-color: ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
}
&:active {
box-shadow: 0 0 0 1pt
${({ 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))};
box-shadow: 0 0 0 1pt ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
background-color: ${({ theme, disabled }) => !disabled && theme.accentActionSoft};
}
:disabled {
opacity: 0.4;
:hover {
cursor: auto;
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_primary5)};
background-color: transparent;
box-shadow: none;
border: 1px solid transparent;
outline: none;
@@ -176,28 +169,22 @@ export const ButtonOutlined = styled(BaseButton)`
}
`
export const ButtonYellow = styled(BaseButton)<{ redesignFlag?: boolean }>`
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentWarningSoft : theme.deprecated_yellow3)};
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentWarning : 'white')};
export const ButtonYellow = styled(BaseButton)`
background-color: ${({ theme }) => theme.accentWarningSoft};
color: ${({ theme }) => theme.accentWarning};
&:focus {
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)};
background-color: ${({ theme }) => theme.accentWarningSoft};
}
&:hover {
background: ${({ theme, redesignFlag }) => redesignFlag && theme.stateOverlayHover};
mix-blend-mode: ${({ redesignFlag }) => redesignFlag && 'normal'};
background-color: ${({ theme, redesignFlag }) => !redesignFlag && darken(0.05, theme.deprecated_yellow3)};
background: ${({ theme }) => theme.stateOverlayHover};
mix-blend-mode: normal;
}
&:active {
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)};
background-color: ${({ theme }) => theme.accentWarningSoft};
}
&:disabled {
background-color: ${({ theme, redesignFlag }) =>
redesignFlag ? theme.accentWarningSoft : theme.deprecated_yellow3};
opacity: ${({ redesignFlag }) => (redesignFlag ? '60%' : '50%')};
background-color: ${({ theme }) => theme.accentWarningSoft};
opacity: 60%;
cursor: auto;
}
`
@@ -378,3 +365,34 @@ export function ButtonRadioChecked({ active = false, children, ...rest }: { acti
)
}
}
export const MediumButton = styled.button`
align-items: center;
background-color: ${({ theme }) => theme.backgroundInteractive};
border-radius: 16px;
border: 0;
color: ${({ theme }) => theme.textPrimary};
cursor: pointer;
display: flex;
flex-direction: row;
font-weight: 600;
gap: 12px;
justify-content: center;
padding: 16px;
transition: 150ms ease background-color opacity;
:disabled {
background-color: ${({ theme }) => theme.backgroundInteractive};
cursor: default;
opacity: 0.6;
}
:hover {
background-color: ${({ theme }) => theme.stateOverlayHover};
}
:active {
background-color: ${({ theme }) => theme.stateOverlayPressed};
}
:focus {
background-color: ${({ theme }) => theme.stateOverlayPressed};
}
`

View File

@@ -14,15 +14,15 @@ export const LightCard = styled(Card)`
background-color: ${({ theme }) => theme.deprecated_bg1};
`
export const LightGreyCard = styled(Card)`
export const LightGrayCard = styled(Card)`
background-color: ${({ theme }) => theme.deprecated_bg2};
`
export const GreyCard = styled(Card)`
export const GrayCard = styled(Card)`
background-color: ${({ theme }) => theme.deprecated_bg3};
`
export const DarkGreyCard = styled(Card)`
export const DarkGrayCard = styled(Card)`
background-color: ${({ theme }) => theme.deprecated_bg2};
`

View File

@@ -8,6 +8,8 @@ import { useTheme } from 'styled-components/macro'
import { LineChartProps } from './LineChart'
type AnimatedInLineChartProps<T> = Omit<LineChartProps<T>, 'height' | 'width' | 'children'>
const config = {
duration: 800,
easing: easeCubicInOut,
@@ -23,10 +25,7 @@ function AnimatedInLineChart<T>({
curve,
color,
strokeWidth,
width,
height,
children,
}: LineChartProps<T>) {
}: AnimatedInLineChartProps<T>) {
const lineRef = useRef<SVGPathElement>(null)
const [lineLength, setLineLength] = useState(0)
const [shouldAnimate, setShouldAnimate] = useState(false)
@@ -41,49 +40,54 @@ function AnimatedInLineChart<T>({
},
})
const effectDependency = lineRef.current
// We need to check to see after the "invisble" line has been drawn
// what the length is to be able to animate in the line for the first time
// This will run on each render to see if there is a new line length
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
if (lineRef.current) {
setLineLength(lineRef.current.getTotalLength())
setShouldAnimate(true)
const length = lineRef.current.getTotalLength()
if (length !== lineLength) {
setLineLength(length)
}
if (length > 0 && !shouldAnimate && !hasAnimatedIn) {
setShouldAnimate(true)
}
}
}, [effectDependency])
})
const theme = useTheme()
const lineColor = color ?? theme.accentAction
return (
<svg width={width} height={height}>
<Group top={marginTop}>
<LinePath curve={curve} x={getX} y={getY}>
{({ path }) => {
const d = path(data) || ''
return (
<>
<Group top={marginTop}>
<LinePath curve={curve} x={getX} y={getY}>
{({ path }) => {
const d = path(data) || ''
return (
<>
<animated.path
d={d}
ref={lineRef}
strokeWidth={strokeWidth}
strokeOpacity={hasAnimatedIn ? 1 : 0}
fill="none"
stroke={lineColor}
/>
{shouldAnimate && lineLength !== 0 && (
<animated.path
d={d}
ref={lineRef}
strokeWidth={strokeWidth}
strokeOpacity={hasAnimatedIn ? 1 : 0}
fill="none"
stroke={lineColor}
strokeDashoffset={spring.frame.to((v) => v * lineLength)}
strokeDasharray={lineLength}
/>
{shouldAnimate && lineLength !== 0 && (
<animated.path
d={d}
strokeWidth={strokeWidth}
fill="none"
stroke={lineColor}
strokeDashoffset={spring.frame.to((v) => v * lineLength)}
strokeDasharray={lineLength}
/>
)}
</>
)
}}
</LinePath>
</Group>
{children}
</svg>
)}
</>
)
}}
</LinePath>
</Group>
)
}

View File

@@ -1,14 +1,20 @@
import { SparkLineLoadingBubble } from 'components/Tokens/TokenTable/TokenRow'
import { curveCardinal, scaleLinear } from 'd3'
import { filterPrices } from 'graphql/data/Token'
import { TopToken } from 'graphql/data/TopTokens'
import { SparklineMap, TopToken } from 'graphql/data/TopTokens'
import { PricePoint } from 'graphql/data/util'
import { TimePeriod } from 'graphql/data/util'
import React from 'react'
import { useTheme } from 'styled-components/macro'
import { memo } from 'react'
import styled, { useTheme } from 'styled-components/macro'
import { DATA_EMPTY, getPriceBounds } from '../Tokens/TokenDetails/PriceChart'
import { getPriceBounds } from '../Tokens/TokenDetails/PriceChart'
import LineChart from './LineChart'
type PricePoint = { value: number; timestamp: number }
const LoadingContainer = styled.div`
height: 100%;
display: flex;
justify-content: center;
align-items: center;
`
interface SparklineChartProps {
width: number
@@ -16,15 +22,32 @@ interface SparklineChartProps {
tokenData: TopToken
pricePercentChange: number | undefined | null
timePeriod: TimePeriod
sparklineMap: SparklineMap
}
function SparklineChart({ width, height, tokenData, pricePercentChange, timePeriod }: SparklineChartProps) {
function _SparklineChart({
width,
height,
tokenData,
pricePercentChange,
timePeriod,
sparklineMap,
}: 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 pricePoints = tokenData?.address ? sparklineMap[tokenData.address] : null
// Don't display if there's one or less pricepoints
if (!pricePoints || pricePoints.length <= 1) {
return (
<LoadingContainer>
<SparkLineLoadingBubble />
</LoadingContainer>
)
}
const startingPrice = pricePoints[0]
const endingPrice = pricePoints[pricePoints.length - 1]
const widthScale = scaleLinear()
.domain(
// the range of possible input values
@@ -52,4 +75,4 @@ function SparklineChart({ width, height, tokenData, pricePercentChange, timePeri
)
}
export default React.memo(SparklineChart)
export default memo(_SparklineChart)

View File

@@ -0,0 +1,47 @@
import { css } from 'styled-components/macro'
export const ScrollBarStyles = css<{ $isHorizontalScroll?: boolean }>`
// Firefox scrollbar styling
scrollbar-width: thin;
scrollbar-color: ${({ theme }) => `${theme.backgroundOutline} transparent`};
height: 100%;
// safari and chrome scrollbar styling
::-webkit-scrollbar {
background: transparent;
// Set height for horizontal scrolls
${({ $isHorizontalScroll }) => {
return $isHorizontalScroll
? css`
height: 4px;
overflow-x: scroll;
`
: css`
width: 4px;
overflow-y: scroll;
`
}}
}
::-webkit-scrollbar-thumb {
background: ${({ theme }) => theme.backgroundOutline};
border-radius: 8px;
}
`
export const OpacityHoverState = css`
&:hover {
opacity: ${({ theme }) => theme.opacity.hover};
}
&:active {
opacity: ${({ theme }) => theme.opacity.click};
}
transition: ${({
theme: {
transition: { duration, timing },
},
}) => `opacity ${duration.medium} ${timing.ease}`};
`

View File

@@ -2,7 +2,6 @@ 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 { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { useMemo } from 'react'
import { useTheme } from 'styled-components/macro'
@@ -18,7 +17,6 @@ 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
@@ -31,14 +29,8 @@ 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={textColor}>
<ThemedText.DeprecatedBody fontSize={14} color={theme.textSecondary}>
{fiatValue && <>${fiatValue?.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })}</>}
{priceImpact ? (
<span style={{ color: priceImpactColor }}>

View File

@@ -1,17 +1,16 @@
import { Trans } from '@lingui/macro'
import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
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, { css, useTheme } 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'
@@ -25,45 +24,31 @@ import { RowBetween, RowFixed } from '../Row'
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
import { FiatValue } from './FiatValue'
const InputPanel = styled.div<{ hideInput?: boolean; redesignFlag: boolean }>`
const InputPanel = styled.div<{ hideInput?: boolean }>`
${({ theme }) => theme.flexColumnNoWrap}
position: relative;
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
background-color: ${({ theme, redesignFlag, hideInput }) =>
redesignFlag ? 'transparent' : hideInput ? 'transparent' : theme.deprecated_bg2};
z-index: 1;
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
transition: height 1s ease;
will-change: height;
`
const FixedContainer = styled.div<{ redesignFlag: boolean }>`
const FixedContainer = styled.div`
width: 100%;
height: 100%;
position: absolute;
border-radius: 20px;
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg2)};
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 && '44px'};
const Container = styled.div<{ hideInput: boolean }>`
min-height: 44px;
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)<{
@@ -71,22 +56,14 @@ const CurrencySelect = styled(ButtonGray)<{
selected: boolean
hideInput?: boolean
disabled?: boolean
redesignFlag: boolean
}>`
align-items: center;
background-color: ${({ selected, theme, redesignFlag }) =>
redesignFlag
? selected
? theme.backgroundInteractive
: theme.accentAction
: selected
? theme.deprecated_bg2
: theme.deprecated_primary1};
background-color: ${({ selected, theme }) => (selected ? theme.backgroundInteractive : theme.accentAction)};
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')};
height: unset;
border-radius: 16px;
outline: none;
user-select: none;
@@ -94,72 +71,52 @@ const CurrencySelect = styled(ButtonGray)<{
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')};
padding: ${({ selected }) => (selected ? '4px 8px 4px 4px' : '6px 6px 6px 8px')};
gap: 8px;
justify-content: space-between;
margin-left: ${({ hideInput }) => (hideInput ? '0' : '12px')};
${({ redesignFlag, selected }) =>
!redesignFlag &&
css`
&:hover {
background-color: ${({ theme }) => (selected ? darken(0.05, theme.deprecated_primary1) : theme.deprecated_bg3)};
}
&:hover,
&:active {
background-color: ${({ theme, selected }) => (selected ? theme.backgroundInteractive : theme.accentAction)};
}
&:active {
background-color: ${({ theme }) => (selected ? darken(0.05, theme.deprecated_primary1) : theme.deprecated_bg3)};
}
`}
&:before {
background-size: 100%;
border-radius: inherit;
${({ redesignFlag, selected }) =>
redesignFlag &&
css`
&:hover,
&:active {
background-color: ${({ theme }) => (selected ? theme.backgroundInteractive : theme.accentAction)};
}
position: absolute;
top: 0;
left: 0;
&:before {
background-size: 100%;
border-radius: inherit;
width: 100%;
height: 100%;
content: '';
}
position: absolute;
top: 0;
left: 0;
&:hover:before {
background-color: ${({ theme }) => theme.stateOverlayHover};
}
width: 100%;
height: 100%;
content: '';
}
&:hover:before {
background-color: ${({ theme }) => theme.stateOverlayHover};
}
&:active:before {
background-color: ${({ theme }) => theme.stateOverlayPressed};
}
`}
&:active:before {
background-color: ${({ theme }) => theme.stateOverlayPressed};
}
visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
`
const InputRow = styled.div<{ selected: boolean; redesignFlag: boolean }>`
const InputRow = styled.div`
${({ 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 }>`
const LabelRow = styled.div`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textSecondary : theme.deprecated_text1)};
color: ${({ theme }) => theme.textSecondary};
font-size: 0.75rem;
line-height: 1rem;
padding: ${({ redesignFlag }) => (redesignFlag ? '0px' : '0 1rem 1rem')};
span:hover {
cursor: pointer;
@@ -167,11 +124,10 @@ const LabelRow = styled.div<{ redesignFlag: boolean }>`
}
`
const FiatRow = styled(LabelRow)<{ redesignFlag: boolean }>`
const FiatRow = styled(LabelRow)`
justify-content: flex-end;
min-height: ${({ redesignFlag }) => redesignFlag && '20px'};
padding: ${({ redesignFlag }) => redesignFlag && '8px 0px 0px 0px'};
height: ${({ redesignFlag }) => !redesignFlag && '24px'};
min-height: 20px;
padding: 8px 0px 0px 0px;
`
const Aligner = styled.span`
@@ -181,34 +137,30 @@ const Aligner = styled.span`
width: 100%;
`
const StyledDropDown = styled(DropDown)<{ selected: boolean; redesignFlag: boolean }>`
const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
margin: 0 0.25rem 0 0.35rem;
height: 35%;
margin-left: ${({ redesignFlag }) => redesignFlag && '8px'};
margin-left: 8px;
path {
stroke: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
stroke-width: ${({ redesignFlag }) => (redesignFlag ? '2px' : '1.5px')};
stroke-width: 2px;
}
`
const StyledTokenName = styled.span<{ active?: boolean; redesignFlag: boolean }>`
const StyledTokenName = styled.span<{ active?: boolean }>`
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
font-size: ${({ redesignFlag }) => (redesignFlag ? '20px' : '18px')};
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
font-size: 20px;
font-weight: 600;
`
const StyledBalanceMax = styled.button<{ disabled?: boolean; redesignFlag: boolean }>`
const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
background-color: transparent;
background-color: ${({ theme, redesignFlag }) => !redesignFlag && theme.deprecated_primary5};
border: none;
text-transform: ${({ redesignFlag }) => !redesignFlag && 'uppercase'};
border-radius: ${({ redesignFlag }) => !redesignFlag && '12px'};
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentAction : theme.deprecated_primary1)};
color: ${({ theme }) => theme.accentAction};
cursor: pointer;
font-size: ${({ redesignFlag }) => (redesignFlag ? '14px' : '11px')};
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
margin-left: ${({ redesignFlag }) => (redesignFlag ? '0px' : '0.25rem')};
font-size: 14px;
font-weight: 600;
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
padding: 4px 6px;
pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
@@ -222,12 +174,12 @@ const StyledBalanceMax = styled.button<{ disabled?: boolean; redesignFlag: boole
}
`
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean; redesignFlag: boolean }>`
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean }>`
${loadingOpacityMixin};
text-align: left;
font-size: ${({ redesignFlag }) => redesignFlag && '36px'};
line-height: ${({ redesignFlag }) => redesignFlag && '44px'};
font-variant: ${({ redesignFlag }) => redesignFlag && 'small-caps'};
font-size: 36px;
line-height: 44px;
font-variant: small-caps;
`
interface SwapCurrencyInputPanelProps {
@@ -277,8 +229,6 @@ export default function SwapCurrencyInputPanel({
}: 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()
@@ -289,9 +239,9 @@ export default function SwapCurrencyInputPanel({
const chainAllowed = isSupportedChain(chainId)
return (
<InputPanel id={id} hideInput={hideInput} {...rest} redesignFlag={redesignFlagEnabled}>
<InputPanel id={id} hideInput={hideInput} {...rest}>
{locked && (
<FixedContainer redesignFlag={redesignFlagEnabled}>
<FixedContainer>
<AutoColumn gap="sm" justify="center">
<Lock />
<ThemedText.DeprecatedLabel fontSize="12px" textAlign="center" padding="0 12px">
@@ -300,12 +250,8 @@ export default function SwapCurrencyInputPanel({
</AutoColumn>
</FixedContainer>
)}
<Container hideInput={hideInput} disabled={!chainAllowed} redesignFlag={redesignFlagEnabled}>
<InputRow
style={hideInput ? { padding: '0', borderRadius: '8px' } : {}}
selected={!onCurrencySelect}
redesignFlag={redesignFlagEnabled}
>
<Container hideInput={hideInput}>
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}}>
{!hideInput && (
<StyledNumericalInput
className="token-amount-input"
@@ -313,7 +259,6 @@ export default function SwapCurrencyInputPanel({
onUserInput={onUserInput}
disabled={!chainAllowed}
$loading={loading}
redesignFlag={redesignFlagEnabled}
/>
)}
@@ -322,7 +267,6 @@ export default function SwapCurrencyInputPanel({
visible={currency !== undefined}
selected={!!currency}
hideInput={hideInput}
redesignFlag={redesignFlagEnabled}
className="open-currency-select-button"
onClick={() => {
if (onCurrencySelect) {
@@ -340,15 +284,11 @@ export default function SwapCurrencyInputPanel({
<CurrencyLogo style={{ marginRight: '2px' }} currency={currency} size={'24px'} />
) : null}
{pair ? (
<StyledTokenName className="pair-name-container" redesignFlag={redesignFlagEnabled}>
<StyledTokenName className="pair-name-container">
{pair?.token0.symbol}:{pair?.token1.symbol}
</StyledTokenName>
) : (
<StyledTokenName
className="token-symbol-container"
active={Boolean(currency && currency.symbol)}
redesignFlag={redesignFlagEnabled}
>
<StyledTokenName className="token-symbol-container" active={Boolean(currency && currency.symbol)}>
{(currency && currency.symbol && currency.symbol.length > 20
? currency.symbol.slice(0, 4) +
'...' +
@@ -357,12 +297,12 @@ export default function SwapCurrencyInputPanel({
</StyledTokenName>
)}
</RowFixed>
{onCurrencySelect && <StyledDropDown selected={!!currency} redesignFlag={redesignFlagEnabled} />}
{onCurrencySelect && <StyledDropDown selected={!!currency} />}
</Aligner>
</CurrencySelect>
</InputRow>
{!hideInput && !hideBalance && currency && (
<FiatRow redesignFlag={redesignFlagEnabled}>
<FiatRow>
<RowBetween>
<LoadingOpacityContainer $loading={loading}>
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
@@ -370,8 +310,8 @@ export default function SwapCurrencyInputPanel({
{account ? (
<RowFixed style={{ height: '17px' }}>
<ThemedText.DeprecatedBody
color={redesignFlag ? theme.textSecondary : theme.deprecated_text3}
fontWeight={redesignFlag ? 400 : 500}
color={theme.textSecondary}
fontWeight={400}
fontSize={14}
style={{ display: 'inline' }}
>
@@ -385,11 +325,11 @@ export default function SwapCurrencyInputPanel({
</ThemedText.DeprecatedBody>
{showMaxButton && selectedCurrencyBalance ? (
<TraceEvent
events={[Event.onClick]}
events={[BrowserEvent.onClick]}
name={EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED}
element={ElementName.MAX_TOKEN_AMOUNT_BUTTON}
>
<StyledBalanceMax onClick={onMax} redesignFlag={redesignFlagEnabled}>
<StyledBalanceMax onClick={onMax}>
<Trans>Max</Trans>
</StyledBalanceMax>
</TraceEvent>

View File

@@ -1,13 +1,12 @@
import { Trans } from '@lingui/macro'
import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
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'
@@ -115,10 +114,10 @@ const LabelRow = styled.div`
}
`
const FiatRow = styled(LabelRow)<{ redesignFlag: boolean }>`
const FiatRow = styled(LabelRow)`
justify-content: flex-end;
padding: ${({ redesignFlag }) => redesignFlag && '0px 1rem 0.75rem'};
height: ${({ redesignFlag }) => (redesignFlag ? '32px' : '16px')};
padding: 0px 1rem 0.75rem;
height: 32px;
`
const Aligner = styled.span`
@@ -220,8 +219,6 @@ export default function CurrencyInputPanel({
const { account, chainId } = useWeb3React()
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
const theme = useTheme()
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const handleDismissSearch = useCallback(() => {
setModalOpen(false)
@@ -293,7 +290,7 @@ export default function CurrencyInputPanel({
</CurrencySelect>
</InputRow>
{!hideInput && !hideBalance && currency && (
<FiatRow redesignFlag={redesignFlagEnabled}>
<FiatRow>
<RowBetween>
<LoadingOpacityContainer $loading={loading}>
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
@@ -317,7 +314,7 @@ export default function CurrencyInputPanel({
</ThemedText.DeprecatedBody>
{showMaxButton && selectedCurrencyBalance ? (
<TraceEvent
events={[Event.onClick]}
events={[BrowserEvent.onClick]}
name={EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED}
element={ElementName.MAX_TOKEN_AMOUNT_BUTTON}
>

View File

@@ -37,7 +37,7 @@ export default function CurrencyLogo({
src?: string | null
}) {
const logoURIs = useCurrencyLogoURIs(currency)
const srcs = useMemo(() => (src ? [src] : logoURIs), [src, logoURIs])
const srcs = useMemo(() => (src ? [src, ...logoURIs] : logoURIs), [src, logoURIs])
const props = {
alt: `${currency?.symbol ?? 'token'} logo`,
size,

View File

@@ -1,10 +1,5 @@
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'
@@ -207,40 +202,6 @@ export default function FeatureFlagModal() {
<X size={24} />
</CloseButton>
</Header>
<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>

View File

@@ -1,26 +0,0 @@
import { ReactElement } from 'react'
import styled from 'styled-components/macro'
import SantaHat from '../../assets/images/santa-hat.png'
const SantaHatImage = styled.img`
position: absolute;
top: -4px;
right: -4px;
height: 18px;
`
const Christmas = <SantaHatImage src={SantaHat} alt="Santa hat" />
const DATE_TO_ORNAMENT: { [date: string]: ReactElement } = {
'12-24': Christmas,
'12-25': Christmas,
}
const HolidayOrnament = () => {
// months in javascript are 0 indexed...
const today = `${new Date().getMonth() + 1}-${new Date().getDate()}`
return DATE_TO_ORNAMENT[today] || null
}
export default HolidayOrnament

View File

@@ -1,345 +0,0 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { getChainInfo } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import useSelectChain from 'hooks/useSelectChain'
import useSyncChainQuery from 'hooks/useSyncChainQuery'
import { darken } from 'polished'
import { useRef } from 'react'
import { AlertTriangle, ArrowDownCircle, ChevronDown } from 'react-feather'
import { useCloseModal, useModalIsOpen, useOpenModal, useToggleModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import styled from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
import { isMobile } from 'utils/userAgent'
const ActiveRowLinkList = styled.div`
display: flex;
flex-direction: column;
padding: 0 8px;
& > a {
align-items: center;
color: ${({ theme }) => theme.deprecated_text2};
display: flex;
flex-direction: row;
font-size: 14px;
font-weight: 500;
justify-content: space-between;
padding: 8px 0 4px;
text-decoration: none;
}
& > a:first-child {
margin: 0;
margin-top: 0px;
padding-top: 10px;
}
`
const ActiveRowWrapper = styled.div`
background-color: ${({ theme }) => theme.deprecated_bg1};
border-radius: 8px;
cursor: pointer;
padding: 8px;
width: 100%;
`
const FlyoutHeader = styled.div`
color: ${({ theme }) => theme.deprecated_text2};
cursor: default;
font-weight: 400;
`
const FlyoutMenu = styled.div`
position: absolute;
top: 54px;
width: 272px;
z-index: 99;
padding-top: 10px;
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
top: 40px;
}
`
const FlyoutMenuContents = styled.div`
align-items: flex-start;
background-color: ${({ theme }) => theme.deprecated_bg0};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 20px;
display: flex;
flex-direction: column;
font-size: 16px;
overflow: auto;
padding: 16px;
& > *:not(:last-child) {
margin-bottom: 12px;
}
`
const FlyoutRow = styled.div<{ active: boolean }>`
align-items: center;
background-color: ${({ active, theme }) => (active ? theme.deprecated_bg1 : 'transparent')};
border-radius: 8px;
cursor: pointer;
display: flex;
font-weight: 500;
justify-content: space-between;
padding: 6px 8px;
text-align: left;
width: 100%;
`
const FlyoutRowActiveIndicator = styled.div`
background-color: ${({ theme }) => theme.deprecated_green1};
border-radius: 50%;
height: 9px;
width: 9px;
`
const CircleContainer = styled.div`
width: 20px;
display: flex;
justify-content: center;
`
const LinkOutCircle = styled(ArrowDownCircle)`
transform: rotate(230deg);
width: 16px;
height: 16px;
`
const Logo = styled.img`
height: 20px;
width: 20px;
margin-right: 8px;
`
const NetworkLabel = styled.div`
flex: 1 1 auto;
`
const SelectorLabel = styled(NetworkLabel)`
display: none;
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
display: block;
margin-right: 8px;
}
`
const NetworkAlertLabel = styled(NetworkLabel)`
display: none;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin: 0 0.5rem 0 0.4rem;
font-size: 1rem;
width: fit-content;
font-weight: 500;
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
display: block;
}
`
const SelectorControls = styled.div<{ supportedChain: boolean }>`
align-items: center;
background-color: ${({ theme }) => theme.deprecated_bg0};
border: 2px solid ${({ theme }) => theme.deprecated_bg0};
border-radius: 16px;
color: ${({ theme }) => theme.deprecated_text1};
display: flex;
font-weight: 500;
justify-content: space-between;
padding: 6px 8px;
${({ supportedChain, theme }) =>
!supportedChain &&
`
color: ${theme.deprecated_white};
background-color: ${theme.deprecated_red1};
border: 2px solid ${theme.deprecated_red1};
`}
cursor: default;
:focus {
background-color: ${({ theme }) => darken(0.1, theme.deprecated_red1)};
}
`
const SelectorLogo = styled(Logo)`
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
margin-right: 8px;
}
`
const SelectorWrapper = styled.div`
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
position: relative;
}
`
const StyledChevronDown = styled(ChevronDown)`
width: 16px;
`
const NetworkIcon = styled(AlertTriangle)`
margin-left: 0.25rem;
margin-right: 0.25rem;
width: 16px;
height: 16px;
`
const BridgeLabel = ({ chainId }: { chainId: SupportedChainId }) => {
switch (chainId) {
case SupportedChainId.ARBITRUM_ONE:
case SupportedChainId.ARBITRUM_RINKEBY:
return <Trans>Arbitrum Bridge</Trans>
case SupportedChainId.OPTIMISM:
case SupportedChainId.OPTIMISM_GOERLI:
return <Trans>Optimism Bridge</Trans>
case SupportedChainId.POLYGON:
case SupportedChainId.POLYGON_MUMBAI:
return <Trans>Polygon Bridge</Trans>
case SupportedChainId.CELO:
case SupportedChainId.CELO_ALFAJORES:
return <Trans>Portal Bridge</Trans>
default:
return <Trans>Bridge</Trans>
}
}
const ExplorerLabel = ({ chainId }: { chainId: SupportedChainId }) => {
switch (chainId) {
case SupportedChainId.ARBITRUM_ONE:
case SupportedChainId.ARBITRUM_RINKEBY:
return <Trans>Arbiscan</Trans>
case SupportedChainId.OPTIMISM:
case SupportedChainId.OPTIMISM_GOERLI:
return <Trans>Optimistic Etherscan</Trans>
case SupportedChainId.POLYGON:
case SupportedChainId.POLYGON_MUMBAI:
return <Trans>Polygonscan</Trans>
case SupportedChainId.CELO:
case SupportedChainId.CELO_ALFAJORES:
return <Trans>Blockscout</Trans>
default:
return <Trans>Etherscan</Trans>
}
}
function Row({
targetChain,
onSelectChain,
}: {
targetChain: SupportedChainId
onSelectChain: (targetChain: number) => void
}) {
const { provider, chainId } = useWeb3React()
if (!provider || !chainId) {
return null
}
const active = chainId === targetChain
const { helpCenterUrl, explorer, bridge, label, logoUrl } = getChainInfo(targetChain)
const rowContent = (
<FlyoutRow onClick={() => onSelectChain(targetChain)} active={active}>
<Logo src={logoUrl} />
<NetworkLabel>{label}</NetworkLabel>
{chainId === targetChain && (
<CircleContainer>
<FlyoutRowActiveIndicator />
</CircleContainer>
)}
</FlyoutRow>
)
if (active) {
return (
<ActiveRowWrapper>
{rowContent}
<ActiveRowLinkList>
{bridge && (
<ExternalLink href={bridge}>
<BridgeLabel chainId={chainId} />
<CircleContainer>
<LinkOutCircle />
</CircleContainer>
</ExternalLink>
)}
{explorer && (
<ExternalLink href={explorer}>
<ExplorerLabel chainId={chainId} />
<CircleContainer>
<LinkOutCircle />
</CircleContainer>
</ExternalLink>
)}
{helpCenterUrl && (
<ExternalLink href={helpCenterUrl}>
<Trans>Help Center</Trans>
<CircleContainer>
<LinkOutCircle />
</CircleContainer>
</ExternalLink>
)}
</ActiveRowLinkList>
</ActiveRowWrapper>
)
}
return rowContent
}
const NETWORK_SELECTOR_CHAINS = [
SupportedChainId.MAINNET,
SupportedChainId.POLYGON,
SupportedChainId.OPTIMISM,
SupportedChainId.ARBITRUM_ONE,
SupportedChainId.CELO,
]
export default function NetworkSelector() {
const { chainId, provider } = useWeb3React()
const node = useRef<HTMLDivElement>(null)
const isOpen = useModalIsOpen(ApplicationModal.NETWORK_SELECTOR)
const openModal = useOpenModal(ApplicationModal.NETWORK_SELECTOR)
const closeModal = useCloseModal(ApplicationModal.NETWORK_SELECTOR)
const toggleModal = useToggleModal(ApplicationModal.NETWORK_SELECTOR)
const info = getChainInfo(chainId)
const selectChain = useSelectChain()
useSyncChainQuery()
if (!chainId || !provider) {
return null
}
const onSupportedChain = info !== undefined
return (
<SelectorWrapper
ref={node}
onMouseEnter={openModal}
onMouseLeave={closeModal}
onClick={isMobile ? toggleModal : undefined}
>
<SelectorControls supportedChain={onSupportedChain}>
{onSupportedChain ? (
<>
<SelectorLogo src={info.logoUrl} />
<SelectorLabel>{info.label}</SelectorLabel>
<StyledChevronDown />
</>
) : (
<>
<NetworkIcon />
<NetworkAlertLabel>Switch Network</NetworkAlertLabel>
<StyledChevronDown />
</>
)}
</SelectorControls>
{isOpen && (
<FlyoutMenu>
<FlyoutMenuContents>
<FlyoutHeader>
<Trans>Select a {!onSupportedChain ? ' supported ' : ''}network</Trans>
</FlyoutHeader>
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) => (
<Row
onSelectChain={async (targetChainId: SupportedChainId) => {
await selectChain(targetChainId)
closeModal()
}}
targetChain={chainId}
key={chainId}
/>
))}
</FlyoutMenuContents>
</FlyoutMenu>
)}
</SelectorWrapper>
)
}

View File

@@ -1,356 +0,0 @@
import { Trans } from '@lingui/macro'
import useScrollPosition from '@react-hook/window-scroll'
import { useWeb3React } from '@web3-react/core'
import { getChainInfoOrDefault } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import { TokensVariant, useTokensFlag } from 'featureFlags/flags/tokens'
import { darken } from 'polished'
import { NavLink, useLocation } from 'react-router-dom'
import { Text } from 'rebass'
import { useShowClaimPopup, useToggleSelfClaimModal } from 'state/application/hooks'
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, { useTheme } from 'styled-components/macro'
import { ReactComponent as Logo } from '../../assets/svg/logo.svg'
import { ExternalLink, ThemedText } from '../../theme'
import ClaimModal from '../claim/ClaimModal'
import { CardNoise } from '../earn/styled'
import Menu from '../Menu'
import Row from '../Row'
import { Dots } from '../swap/styleds'
import Web3Status from '../Web3Status'
import HolidayOrnament from './HolidayOrnament'
import NetworkSelector from './NetworkSelector'
const HeaderFrame = styled.div<{ showBackground: boolean }>`
display: grid;
grid-template-columns: 120px 1fr 120px;
align-items: center;
justify-content: space-between;
align-items: center;
flex-direction: row;
width: 100%;
top: 0;
position: relative;
padding: 1rem;
z-index: 21;
position: relative;
/* Background slide effect on scroll. */
background-image: ${({ theme }) => `linear-gradient(to bottom, transparent 50%, ${theme.deprecated_bg0} 50% )}}`};
background-position: ${({ showBackground }) => (showBackground ? '0 -100%' : '0 0')};
background-size: 100% 200%;
box-shadow: 0px 0px 0px 1px ${({ theme, showBackground }) => (showBackground ? theme.deprecated_bg2 : 'transparent;')};
transition: background-position 0.1s, box-shadow 0.1s;
background-blend-mode: hard-light;
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToLarge`
grid-template-columns: 48px 1fr 1fr;
`};
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
padding: 1rem;
grid-template-columns: 1fr 1fr;
`};
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
padding: 1rem;
grid-template-columns: 36px 1fr;
`};
`
const HeaderControls = styled.div`
display: flex;
flex-direction: row;
align-items: center;
justify-self: flex-end;
`
const HeaderElement = styled.div`
display: flex;
align-items: center;
&:not(:first-child) {
margin-left: 0.5em;
}
/* addresses safaris lack of support for "gap" */
& > *:not(:first-child) {
margin-left: 8px;
}
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
align-items: center;
`};
`
const HeaderLinks = styled(Row)`
justify-self: center;
background-color: ${({ theme }) => theme.deprecated_bg0};
width: max-content;
padding: 2px;
border-radius: 16px;
display: grid;
grid-auto-flow: column;
grid-gap: 10px;
overflow: auto;
align-items: center;
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToLarge`
justify-self: start;
`};
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
justify-self: center;
`};
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
flex-direction: row;
justify-content: space-between;
justify-self: center;
z-index: 99;
position: fixed;
bottom: 0; right: 50%;
transform: translate(50%,-50%);
margin: 0 auto;
background-color: ${({ theme }) => theme.deprecated_bg0};
border: 1px solid ${({ theme }) => theme.deprecated_bg2};
box-shadow: 0px 6px 10px rgb(0 0 0 / 2%);
`};
`
const AccountElement = styled.div<{ active: boolean }>`
display: flex;
flex-direction: row;
align-items: center;
background-color: ${({ theme, active }) => (!active ? theme.deprecated_bg0 : theme.deprecated_bg0)};
border-radius: 16px;
white-space: nowrap;
width: 100%;
height: 40px;
:focus {
border: 1px solid blue;
}
`
const UNIAmount = styled(AccountElement)`
color: white;
padding: 4px 8px;
height: 36px;
font-weight: 500;
background-color: ${({ theme }) => theme.deprecated_bg3};
background: radial-gradient(174.47% 188.91% at 1.84% 0%, #ff007a 0%, #2172e5 100%), #edeef2;
`
const UNIWrapper = styled.span`
width: fit-content;
position: relative;
cursor: pointer;
:hover {
opacity: 0.8;
}
:active {
opacity: 0.9;
}
`
const BalanceText = styled(Text)`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToExtraSmall`
display: none;
`};
`
const Title = styled.a`
display: flex;
align-items: center;
pointer-events: auto;
justify-self: flex-start;
margin-right: 12px;
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
justify-self: center;
`};
:hover {
cursor: pointer;
}
`
const UniIcon = styled.div`
transition: transform 0.3s ease;
:hover {
transform: rotate(-5deg);
}
position: relative;
`
// can't be customized under react-router-dom v6
// so we have to persist to the default one, i.e., .active
const activeClassName = 'active'
const StyledNavLink = styled(NavLink)`
${({ theme }) => theme.flexRowNoWrap}
align-items: left;
border-radius: 3rem;
outline: none;
cursor: pointer;
text-decoration: none;
color: ${({ theme }) => theme.deprecated_text2};
font-size: 1rem;
font-weight: 500;
padding: 8px 12px;
word-break: break-word;
overflow: hidden;
white-space: nowrap;
&.${activeClassName} {
border-radius: 14px;
font-weight: 600;
justify-content: center;
color: ${({ theme }) => theme.deprecated_text1};
background-color: ${({ theme }) => theme.deprecated_bg1};
}
:hover,
:focus {
color: ${({ theme }) => darken(0.1, theme.deprecated_text1)};
}
`
const StyledExternalLink = styled(ExternalLink)`
${({ theme }) => theme.flexRowNoWrap}
align-items: left;
border-radius: 3rem;
outline: none;
cursor: pointer;
text-decoration: none;
color: ${({ theme }) => theme.deprecated_text2};
font-size: 1rem;
width: fit-content;
margin: 0 12px;
font-weight: 500;
&.${activeClassName} {
border-radius: 14px;
font-weight: 600;
color: ${({ theme }) => theme.deprecated_text1};
}
:hover,
:focus {
color: ${({ theme }) => darken(0.1, theme.deprecated_text1)};
text-decoration: none;
}
`
export default function Header() {
const tokensFlag = useTokensFlag()
const { account, chainId } = useWeb3React()
const userEthBalance = useNativeCurrencyBalances(account ? [account] : [])?.[account ?? '']
const [darkMode] = useDarkModeManager()
const { deprecated_white, deprecated_black } = useTheme()
const toggleClaimModal = useToggleSelfClaimModal()
const availableClaim: boolean = useUserHasAvailableClaim(account)
const { claimTxn } = useUserHasSubmittedClaim(account ?? undefined)
const showClaimPopup = useShowClaimPopup()
const scrollY = useScrollPosition()
const { pathname } = useLocation()
const {
infoLink,
nativeCurrency: { symbol: nativeCurrencySymbol },
} = getChainInfoOrDefault(chainId)
// work around https://github.com/remix-run/react-router/issues/8161
// as we can't pass function `({isActive}) => ''` to className with styled-components
const isPoolActive =
pathname.startsWith('/pool') ||
pathname.startsWith('/add') ||
pathname.startsWith('/remove') ||
pathname.startsWith('/increase') ||
pathname.startsWith('/find')
return (
<HeaderFrame showBackground={scrollY > 45}>
<ClaimModal />
<Title href=".">
<UniIcon>
<Logo fill={darkMode ? deprecated_white : deprecated_black} width="24px" height="100%" title="logo" />
<HolidayOrnament />
</UniIcon>
</Title>
<HeaderLinks>
<StyledNavLink id={`swap-nav-link`} to={'/swap'}>
<Trans>Swap</Trans>
</StyledNavLink>
{tokensFlag === TokensVariant.Enabled && (
<StyledNavLink id={`tokens-nav-link`} to={'/tokens'}>
<Trans>Tokens</Trans>
</StyledNavLink>
)}
<StyledNavLink
data-cy="pool-nav-link"
id={`pool-nav-link`}
to={'/pool'}
className={isPoolActive ? activeClassName : undefined}
>
<Trans>Pool</Trans>
</StyledNavLink>
{(!chainId || chainId === SupportedChainId.MAINNET) && (
<StyledNavLink id={`vote-nav-link`} to={'/vote'}>
<Trans>Vote</Trans>
</StyledNavLink>
)}
<StyledExternalLink id={`charts-nav-link`} href={infoLink}>
<Trans>Charts</Trans>
<sup></sup>
</StyledExternalLink>
</HeaderLinks>
<HeaderControls>
<HeaderElement>
<NetworkSelector />
</HeaderElement>
<HeaderElement>
{availableClaim && !showClaimPopup && (
<UNIWrapper onClick={toggleClaimModal}>
<UNIAmount active={!!account && !availableClaim} style={{ pointerEvents: 'auto' }}>
<ThemedText.DeprecatedWhite padding="0 2px">
{claimTxn && !claimTxn?.receipt ? (
<Dots>
<Trans>Claiming UNI</Trans>
</Dots>
) : (
<Trans>Claim UNI</Trans>
)}
</ThemedText.DeprecatedWhite>
</UNIAmount>
<CardNoise />
</UNIWrapper>
)}
<AccountElement active={!!account}>
{account && userEthBalance ? (
<BalanceText style={{ flexShrink: 0, userSelect: 'none' }} pl="0.75rem" pr=".4rem" fontWeight={500}>
<Trans>
{userEthBalance?.toSignificant(3)} {nativeCurrencySymbol}
</Trans>
</BalanceText>
) : null}
<Web3Status />
</AccountElement>
</HeaderElement>
<HeaderElement>
<Menu />
</HeaderElement>
</HeaderControls>
</HeaderFrame>
)
}

View File

@@ -1,6 +1,5 @@
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'
@@ -10,7 +9,7 @@ import sockImg from '../../assets/svg/socks.svg'
import { useHasSocks } from '../../hooks/useSocksBalance'
import Identicon from '../Identicon'
const IconWrapper = styled.div<{ size?: number }>`
export const IconWrapper = styled.div<{ size?: number }>`
position: relative;
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
@@ -53,9 +52,8 @@ const Socks = () => {
const useIcon = (connectionType: ConnectionType) => {
const { account } = useWeb3React()
const { avatar } = useENSAvatar(account ?? undefined)
const isNavbarEnabled = useNavBarFlag() === NavBarVariant.Enabled
if ((isNavbarEnabled && avatar) || connectionType === ConnectionType.INJECTED) {
if (avatar || connectionType === ConnectionType.INJECTED) {
return <Identicon />
} else if (connectionType === ConnectionType.WALLET_CONNECT) {
return <img src={WalletConnectIcon} alt="WalletConnect" />
@@ -68,12 +66,11 @@ const useIcon = (connectionType: ConnectionType) => {
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 />}
{hasSocks && <Socks />}
{icon}
</IconWrapper>
)

View File

@@ -1,14 +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 { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'
import styled from 'styled-components/macro'
const StyledIdenticon = styled.div<{ iconSize: number }>`
height: ${({ iconSize }) => `${iconSize}px`};
width: ${({ iconSize }) => `${iconSize}px`};
border-radius: 1.125rem;
border-radius: 50%;
background-color: ${({ theme }) => theme.deprecated_bg4};
font-size: initial;
`
@@ -23,8 +22,7 @@ export default function Identicon({ size }: { size?: number }) {
const { account } = useWeb3React()
const { avatar } = useENSAvatar(account ?? undefined)
const [fetchable, setFetchable] = useState(true)
const isNavbarEnabled = useNavBarFlag() === NavBarVariant.Enabled
const iconSize = size ? size : isNavbarEnabled ? 24 : 16
const iconSize = size ?? 24
const icon = useMemo(() => account && jazzicon(iconSize, parseInt(account.slice(2, 10), 16)), [account, iconSize])
const iconRef = useRef<HTMLDivElement>(null)
@@ -43,10 +41,12 @@ export default function Identicon({ size }: { size?: number }) {
return
}, [icon, iconRef])
const handleError = useCallback(() => setFetchable(false), [])
return (
<StyledIdenticon iconSize={iconSize}>
{avatar && fetchable ? (
<StyledAvatar alt="avatar" src={avatar} onError={() => setFetchable(false)}></StyledAvatar>
<StyledAvatar alt="avatar" src={avatar} onError={handleError}></StyledAvatar>
) : (
<span ref={iconRef} />
)}

View File

@@ -1,28 +0,0 @@
import React from 'react'
import styled from 'styled-components/macro'
import useHttpLocations from '../../hooks/useHttpLocations'
import Logo from '../Logo'
const StyledListLogo = styled(Logo)<{ size: string }>`
width: ${({ size }) => size};
height: ${({ size }) => size};
`
export default function ListLogo({
logoURI,
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} symbol={symbol} srcs={srcs} style={style} />
}

View File

@@ -9,13 +9,12 @@ const rotate = keyframes`
}
`
const StyledSVG = styled.svg<{ size: string; stroke?: string; redesignFlag?: boolean }>`
const StyledSVG = styled.svg<{ size: string; stroke?: string }>`
animation: 2s ${rotate} linear infinite;
height: ${({ size }) => size};
width: ${({ size }) => size};
path {
stroke: ${({ stroke, redesignFlag, theme }) =>
redesignFlag ? theme.accentActive : stroke ?? theme.deprecated_primary1};
stroke: ${({ stroke, theme }) => stroke ?? theme.accentActive};
}
`
@@ -27,25 +26,15 @@ 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}
redesignFlag={redesignFlag}
{...rest}
>
<StyledSVG viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" size={size} stroke={stroke} {...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={strokeWidth ?? '2.5'}

View File

@@ -1,5 +1,4 @@
import { DialogContent, DialogOverlay } from '@reach/dialog'
import { transparentize } from 'polished'
import React from 'react'
import { animated, useSpring, useTransition } from 'react-spring'
import { useGesture } from 'react-use-gesture'
@@ -10,7 +9,7 @@ import { isMobile } from '../../utils/userAgent'
const AnimatedDialogOverlay = animated(DialogOverlay)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ redesignFlag?: boolean; scrollOverlay?: boolean }>`
const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ scrollOverlay?: boolean }>`
&[data-reach-dialog-overlay] {
z-index: ${Z_INDEX.modalBackdrop};
background-color: transparent;
@@ -21,14 +20,14 @@ const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ redesignFlag?: boole
overflow-y: ${({ scrollOverlay }) => scrollOverlay && 'scroll'};
justify-content: center;
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundScrim : theme.deprecated_modalBG)};
background-color: ${({ theme }) => theme.backgroundScrim};
}
`
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, redesignFlag, scrollOverlay, ...rest }) => (
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, scrollOverlay, ...rest }) => (
<AnimatedDialogContent {...rest} />
)).attrs({
'aria-label': 'dialog',
@@ -36,11 +35,10 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, rede
overflow-y: auto;
&[data-reach-dialog-content] {
margin: ${({ redesignFlag }) => (redesignFlag ? 'auto' : '0 0 2rem 0')};
margin: auto;
background-color: ${({ theme }) => theme.deprecated_bg0};
border: 1px solid ${({ theme }) => theme.deprecated_bg1};
box-shadow: ${({ theme, redesignFlag }) =>
redesignFlag ? theme.deepShadow : `0 4px 8px 0 ${transparentize(0.95, theme.shadow1)}`};
box-shadow: ${({ theme }) => theme.deepShadow};
padding: 0px;
width: 50vw;
overflow-y: auto;
@@ -61,9 +59,9 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, rede
`}
display: ${({ scrollOverlay }) => (scrollOverlay ? 'inline-table' : 'flex')};
border-radius: 20px;
${({ theme, redesignFlag }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
width: 65vw;
margin: ${redesignFlag ? 'auto' : '0'};
margin: auto;
`}
${({ theme, mobile }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
width: 85vw;
@@ -87,7 +85,6 @@ interface ModalProps {
maxHeight?: number
initialFocusRef?: React.RefObject<any>
children?: React.ReactNode
redesignFlag?: boolean
scrollOverlay?: boolean
}
@@ -98,7 +95,6 @@ export default function Modal({
maxHeight = 90,
initialFocusRef,
children,
redesignFlag,
scrollOverlay,
}: ModalProps) {
const fadeTransition = useTransition(isOpen, {
@@ -131,7 +127,6 @@ export default function Modal({
onDismiss={onDismiss}
initialFocusRef={initialFocusRef}
unstable_lockFocusAcrossFrames={false}
redesignFlag={redesignFlag}
scrollOverlay={scrollOverlay}
>
<StyledDialogContent
@@ -145,7 +140,6 @@ export default function Modal({
minHeight={minHeight}
maxHeight={maxHeight}
mobile={isMobile}
redesignFlag={redesignFlag}
scrollOverlay={scrollOverlay}
>
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}

View File

@@ -123,7 +123,7 @@ export const MenuDropdown = () => {
<>
<Box position="relative" ref={ref}>
<NavIcon isActive={isOpen} onClick={toggleOpen}>
<EllipsisIcon width={20} height={20} />
<EllipsisIcon viewBox="0 0 20 20" width={24} height={24} />
</NavIcon>
{isOpen && (
@@ -143,7 +143,7 @@ export const MenuDropdown = () => {
<BarChartIcon width={24} height={24} />
</Icon>
<PrimaryMenuRow.Text>
<Trans>View token analytics</Trans>
<Trans>View more analytics</Trans>
</PrimaryMenuRow.Text>
</PrimaryMenuRow>
</Column>

View File

@@ -4,6 +4,7 @@ import { sprinkles, vars } from '../../nft/css/sprinkles.css'
export const navIcon = style([
sprinkles({
alignItems: 'center',
position: 'relative',
display: 'flex',
flexDirection: 'column',
@@ -11,7 +12,6 @@ export const navIcon = style([
justifyContent: 'center',
textAlign: 'center',
cursor: 'pointer',
padding: '10',
borderRadius: '8',
transition: '250',
}),

View File

@@ -18,6 +18,7 @@ export const NavIcon = ({ children, isActive, onClick }: NavIconProps) => {
color={isActive ? 'textPrimary' : 'textSecondary'}
onClick={onClick}
height="40"
width="40"
>
{children}
</Box>

View File

@@ -103,8 +103,10 @@ export const suggestionImage = sprinkles({
export const suggestionPrimaryContainer = style([
sprinkles({
alignItems: 'flex-start',
width: 'full',
}),
{
width: '90%',
},
])
export const suggestionSecondaryContainer = sprinkles({

View File

@@ -1,11 +1,11 @@
// eslint-disable-next-line no-restricted-imports
import { t } from '@lingui/macro'
import { sendAnalyticsEvent } from 'analytics'
import { ElementName, Event, EventName } from 'analytics/constants'
import { TraceEvent } from 'analytics/TraceEvent'
import { sendAnalyticsEvent, Trace, TraceEvent, useTrace } from '@uniswap/analytics'
import { BrowserEvent, ElementName, EventName, SectionName } from '@uniswap/analytics-events'
import clsx from 'clsx'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
import useDebounce from 'hooks/useDebounce'
import { useIsNftPage } from 'hooks/useIsNftPage'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { organizeSearchResults } from 'lib/utils/searchBar'
import { Box } from 'nft/components/Box'
@@ -45,6 +45,7 @@ export const SearchBar = () => {
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
enabled: !!debouncedSearchValue.length && phase1Flag === NftVariant.Enabled,
}
)
@@ -55,10 +56,11 @@ export const SearchBar = () => {
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
enabled: !!debouncedSearchValue.length,
}
)
const isNFTPage = pathname.includes('/nfts')
const isNFTPage = useIsNftPage()
const [reducedTokens, reducedCollections] = organizeSearchResults(isNFTPage, tokens ?? [], collections ?? [])
@@ -95,77 +97,85 @@ export const SearchBar = () => {
const showCenteredSearchContent =
!isOpen && phase1Flag !== NftVariant.Enabled && !isMobileOrTablet && searchValue.length === 0
const trace = useTrace({ section: SectionName.NAVBAR_SEARCH })
const navbarSearchEventProperties = {
navbar_search_input_text: debouncedSearchValue,
hasInput: debouncedSearchValue && debouncedSearchValue.length > 0,
...trace,
}
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"
<Trace section={SectionName.NAVBAR_SEARCH}>
<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' }}
>
<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>
<TraceEvent
events={[Event.onFocus]}
name={EventName.NAVBAR_SEARCH_SELECTED}
element={ElementName.NAVBAR_SEARCH_INPUT}
<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
as="input"
placeholder={placeholderText}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
!isOpen && toggleOpen()
setSearchValue(event.target.value)
}}
onBlur={() => sendAnalyticsEvent(EventName.NAVBAR_SEARCH_EXITED, navbarSearchEventProperties)}
className={`${styles.searchBarInput} ${
showCenteredSearchContent ? styles.searchContentCentered : styles.searchContentLeftAlign
}`}
value={searchValue}
ref={inputRef}
width={phase1Flag === NftVariant.Enabled || isOpen ? 'full' : '160'}
/>
</TraceEvent>
</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 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>
<TraceEvent
events={[BrowserEvent.onFocus]}
name={EventName.NAVBAR_SEARCH_SELECTED}
element={ElementName.NAVBAR_SEARCH_INPUT}
properties={{ ...trace }}
>
<Box
as="input"
placeholder={placeholderText}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
!isOpen && toggleOpen()
setSearchValue(event.target.value)
}}
onBlur={() => sendAnalyticsEvent(EventName.NAVBAR_SEARCH_EXITED, navbarSearchEventProperties)}
className={`${styles.searchBarInput} ${
showCenteredSearchContent ? styles.searchContentCentered : styles.searchContentLeftAlign
}`}
value={searchValue}
ref={inputRef}
width={phase1Flag === NftVariant.Enabled || isOpen ? 'full' : '160'}
/>
</TraceEvent>
</Row>
<Box className={clsx(isOpen ? styles.visible : styles.hidden)}>
{isOpen && (
<SearchBarDropdown
toggleOpen={toggleOpen}
tokens={reducedTokens}
collections={reducedCollections}
queryText={debouncedSearchValue}
hasInput={debouncedSearchValue.length > 0}
isLoading={tokensAreLoading || (collectionsAreLoading && phase1Flag === NftVariant.Enabled)}
/>
)}
</Box>
</Box>
</Box>
<NavIcon onClick={toggleOpen}>
<NavMagnifyingGlassIcon />
</NavIcon>
<NavIcon onClick={toggleOpen}>
<NavMagnifyingGlassIcon />
</NavIcon>
</Trace>
</Box>
)
}

View File

@@ -1,7 +1,8 @@
import { Trans } from '@lingui/macro'
import { sendAnalyticsEvent } from 'analytics'
import { EventName } from 'analytics/constants'
import { useTrace } from '@uniswap/analytics'
import { NavBarSearchTypes, SectionName } from '@uniswap/analytics-events'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
import { useIsNftPage } from 'hooks/useIsNftPage'
import { Box } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex'
import { subheadSmall } from 'nft/css/common.css'
@@ -31,6 +32,7 @@ interface SearchBarDropdownSectionProps {
startingIndex: number
setHoveredIndex: (index: number | undefined) => void
isLoading?: boolean
eventProperties: Record<string, unknown>
}
export const SearchBarDropdownSection = ({
@@ -42,10 +44,11 @@ export const SearchBarDropdownSection = ({
startingIndex,
setHoveredIndex,
isLoading,
eventProperties,
}: SearchBarDropdownSectionProps) => {
return (
<Column gap="12">
<Row paddingX="16" paddingY="4" gap="8" color="grey300" className={subheadSmall} style={{ lineHeight: '20px' }}>
<Row paddingX="16" paddingY="4" gap="8" color="gray300" className={subheadSmall} style={{ lineHeight: '20px' }}>
{headerIcon ? headerIcon : null}
<Box>{header}</Box>
</Row>
@@ -60,16 +63,13 @@ export const SearchBarDropdownSection = ({
isHovered={hoveredIndex === index + startingIndex}
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
traceEvent={() =>
sendAnalyticsEvent(EventName.NAVBAR_SEARCH_EXITED, {
position: index,
selected_type: 'collection',
suggestion_count: suggestions.length,
selected_name: suggestion.name,
selected_address: suggestion.address,
})
}
index={index + startingIndex}
eventProperties={{
position: index + startingIndex,
selected_search_result_name: suggestion.name,
selected_search_result_address: suggestion.address,
...eventProperties,
}}
/>
) : (
<TokenRow
@@ -78,16 +78,13 @@ export const SearchBarDropdownSection = ({
isHovered={hoveredIndex === index + startingIndex}
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
traceEvent={() =>
sendAnalyticsEvent(EventName.NAVBAR_SEARCH_EXITED, {
position: index,
selected_type: 'token',
suggestion_count: suggestions.length,
selected_name: suggestion.name,
selected_address: suggestion.address,
})
}
index={index + startingIndex}
eventProperties={{
position: index + startingIndex,
selected_search_result_name: suggestion.name,
selected_search_result_address: suggestion.address,
...eventProperties,
}}
/>
)
)}
@@ -100,16 +97,24 @@ interface SearchBarDropdownProps {
toggleOpen: () => void
tokens: FungibleToken[]
collections: GenieCollection[]
queryText: string
hasInput: boolean
isLoading: boolean
}
export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput, isLoading }: SearchBarDropdownProps) => {
export const SearchBarDropdown = ({
toggleOpen,
tokens,
collections,
queryText,
hasInput,
isLoading,
}: SearchBarDropdownProps) => {
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(0)
const searchHistory = useSearchHistory((state: { history: (FungibleToken | GenieCollection)[] }) => state.history)
const { history: searchHistory, updateItem: updateSearchHistory } = useSearchHistory()
const shortenedHistory = useMemo(() => searchHistory.slice(0, 2), [searchHistory])
const { pathname } = useLocation()
const isNFTPage = pathname.includes('/nfts')
const isNFTPage = useIsNftPage()
const isTokenPage = pathname.includes('/tokens')
const phase1Flag = useNftFlag()
const [resultsState, setResultsState] = useState<ReactNode>()
@@ -130,6 +135,7 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput, i
stats: {
total_supply: collection.totalSupply,
one_day_change: collection.floorChange,
floor_price: formatEthPrice(collection.floor?.toString()),
},
}))
.slice(0, isNFTPage ? 3 : 2)
@@ -146,9 +152,11 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput, i
refetchOnReconnect: false,
}
)
useEffect(() => {
trendingTokenResults?.forEach(updateSearchHistory)
}, [trendingTokenResults, updateSearchHistory])
const trendingTokensLength = phase1Flag === NftVariant.Enabled ? (isTokenPage ? 3 : 2) : 4
const trendingTokens = useMemo(
() =>
trendingTokenResults
@@ -190,16 +198,29 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput, i
}
}, [toggleOpen, hoveredIndex, totalSuggestions])
const hasVerifiedCollection = collections.some((collection) => collection.isVerified)
const hasVerifiedToken = tokens.some((token) => token.onDefaultList)
const showCollectionsFirst =
(isNFTPage && (hasVerifiedCollection || !hasVerifiedToken)) ||
(!isNFTPage && !hasVerifiedToken && hasVerifiedCollection)
const trace = useTrace({ section: SectionName.NAVBAR_SEARCH })
useEffect(() => {
const eventProperties = { total_suggestions: totalSuggestions, query_text: queryText, ...trace }
if (!isLoading) {
const tokenSearchResults =
tokens.length > 0 ? (
<SearchBarDropdownSection
hoveredIndex={hoveredIndex}
startingIndex={isNFTPage ? collections.length : 0}
startingIndex={showCollectionsFirst ? collections.length : 0}
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={tokens}
eventProperties={{
suggestion_type: NavBarSearchTypes.TOKEN_SUGGESTION,
...eventProperties,
}}
header={<Trans>Tokens</Trans>}
/>
) : (
@@ -213,10 +234,14 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput, i
collections.length > 0 ? (
<SearchBarDropdownSection
hoveredIndex={hoveredIndex}
startingIndex={isNFTPage ? 0 : tokens.length}
startingIndex={showCollectionsFirst ? 0 : tokens.length}
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={collections}
eventProperties={{
suggestion_type: NavBarSearchTypes.COLLECTION_SUGGESTION,
...eventProperties,
}}
header={<Trans>NFT Collections</Trans>}
/>
) : (
@@ -228,7 +253,7 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput, i
hasInput ? (
// Empty or Up to 8 combined tokens and nfts
<Column gap="20">
{isNFTPage ? (
{showCollectionsFirst ? (
<>
{collectionSearchResults}
{tokenSearchResults}
@@ -250,6 +275,10 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput, i
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={shortenedHistory}
eventProperties={{
suggestion_type: NavBarSearchTypes.RECENT_SEARCH,
...eventProperties,
}}
header={<Trans>Recent searches</Trans>}
headerIcon={<ClockIcon />}
/>
@@ -261,6 +290,10 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput, i
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={trendingTokens}
eventProperties={{
suggestion_type: NavBarSearchTypes.TOKEN_TRENDING,
...eventProperties,
}}
header={<Trans>Popular tokens</Trans>}
headerIcon={<TrendingArrow />}
isLoading={trendingTokensAreLoading}
@@ -273,6 +306,10 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput, i
setHoveredIndex={setHoveredIndex}
toggleOpen={toggleOpen}
suggestions={trendingCollections as unknown as GenieCollection[]}
eventProperties={{
suggestion_type: NavBarSearchTypes.COLLECTION_TRENDING,
...eventProperties,
}}
header={<Trans>Popular NFT collections</Trans>}
headerIcon={<TrendingArrow />}
isLoading={trendingCollectionsAreLoading}
@@ -298,6 +335,10 @@ export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput, i
hasInput,
isNFTPage,
isTokenPage,
showCollectionsFirst,
queryText,
totalSuggestions,
trace,
])
return (

View File

@@ -1,46 +1,46 @@
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 { BagIcon, HundredsOverflowIcon } from 'nft/components/icons'
import { useBag, useSellAsset } from 'nft/hooks'
import { useEffect, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { useCallback, useEffect, useState } from 'react'
import shallow from 'zustand/shallow'
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)
const { bagExpanded, setBagExpanded } = useBag(
({ bagExpanded, setBagExpanded }) => ({ bagExpanded, setBagExpanded }),
shallow
)
const { isSellMode, resetSellAssets, setIsSellMode } = useSellAsset(
({ isSellMode, reset, setIsSellMode }) => ({
isSellMode,
resetSellAssets: reset,
setIsSellMode,
}),
shallow
)
const handleIconClick = useCallback(() => {
if (isSellMode && bagExpanded) {
resetSellAssets()
setIsSellMode(false)
}
setBagExpanded({ bagExpanded: !bagExpanded })
}, [bagExpanded, isSellMode, resetSellAssets, setBagExpanded, setIsSellMode])
useEffect(() => {
setBagQuantity(itemsInBag.length)
}, [itemsInBag])
useEffect(() => {
setSellQuantity(sellAssets.length)
}, [sellAssets])
const isProfilePage = location.pathname === '/profile'
const bagHasItems = bagQuantity > 0
return (
<NavIcon onClick={toggleBag}>
{isProfilePage ? (
<>
<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 onClick={handleIconClick}>
<BagIcon viewBox="0 0 20 20" width={24} height={24} />
{bagHasItems && (
<Box className={styles.bagQuantity}>{bagQuantity > 99 ? <HundredsOverflowIcon /> : bagQuantity}</Box>
)}
</NavIcon>
)

View File

@@ -1,8 +1,16 @@
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { EventName } from '@uniswap/analytics-events'
import { useWeb3React } from '@web3-react/core'
import clsx from 'clsx'
import { L2NetworkLogo, LogoContainer } from 'components/Tokens/TokenTable/TokenRow'
import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon'
import { getChainInfo } from 'constants/chainInfo'
import { checkWarning } from 'constants/tokenSafety'
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 { VerifiedIcon } from 'nft/components/icons'
import { vars } from 'nft/css/sprinkles.css'
import { useSearchHistory } from 'nft/hooks'
import { FungibleToken, GenieCollection } from 'nft/types'
@@ -10,8 +18,8 @@ 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 { formatDollar } from 'utils/formatNumbers'
import { VerifiedIcon } from '../../nft/components/icons'
import * as styles from './SearchBar.css'
interface CollectionRowProps {
@@ -19,8 +27,8 @@ interface CollectionRowProps {
isHovered: boolean
setHoveredIndex: (index: number | undefined) => void
toggleOpen: () => void
traceEvent: () => void
index: number
eventProperties: Record<string, unknown>
}
export const CollectionRow = ({
@@ -28,8 +36,8 @@ export const CollectionRow = ({
isHovered,
setHoveredIndex,
toggleOpen,
traceEvent,
index,
eventProperties,
}: CollectionRowProps) => {
const [brokenImage, setBrokenImage] = useState(false)
const [loaded, setLoaded] = useState(false)
@@ -41,8 +49,8 @@ export const CollectionRow = ({
const handleClick = useCallback(() => {
addToSearchHistory(collection)
toggleOpen()
traceEvent()
}, [addToSearchHistory, collection, toggleOpen, traceEvent])
sendAnalyticsEvent(EventName.NAVBAR_RESULT_SELECTED, { ...eventProperties })
}, [addToSearchHistory, collection, toggleOpen, eventProperties])
useEffect(() => {
const keyDownHandler = (event: KeyboardEvent) => {
@@ -85,13 +93,13 @@ export const CollectionRow = ({
<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>
<Box className={styles.secondaryText}>{putCommas(collection?.stats?.total_supply ?? 0)} items</Box>
</Column>
</Row>
{collection.floorPrice ? (
{collection.stats?.floor_price ? (
<Column className={styles.suggestionSecondaryContainer}>
<Row gap="4">
<Box className={styles.primaryText}>{ethNumberStandardFormatter(collection.floorPrice)} ETH</Box>
<Box className={styles.primaryText}>{ethNumberStandardFormatter(collection.stats?.floor_price)} ETH</Box>
</Row>
<Box className={styles.secondaryText}>Floor</Box>
</Column>
@@ -100,16 +108,25 @@ export const CollectionRow = ({
)
}
function useBridgedAddress(token: FungibleToken): [string | undefined, number | undefined, string | undefined] {
const { chainId: connectedChainId } = useWeb3React()
const bridgedAddress = connectedChainId ? token.extensions?.bridgeInfo?.[connectedChainId]?.tokenAddress : undefined
if (bridgedAddress && connectedChainId) {
return [bridgedAddress, connectedChainId, getChainInfo(connectedChainId)?.circleLogoUrl]
}
return [undefined, undefined, undefined]
}
interface TokenRowProps {
token: FungibleToken
isHovered: boolean
setHoveredIndex: (index: number | undefined) => void
toggleOpen: () => void
traceEvent: () => void
index: number
eventProperties: Record<string, unknown>
}
export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, traceEvent, index }: TokenRowProps) => {
export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index, eventProperties }: TokenRowProps) => {
const [brokenImage, setBrokenImage] = useState(false)
const [loaded, setLoaded] = useState(false)
const addToSearchHistory = useSearchHistory(
@@ -120,10 +137,11 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, traceE
const handleClick = useCallback(() => {
addToSearchHistory(token)
toggleOpen()
traceEvent()
}, [addToSearchHistory, toggleOpen, token, traceEvent])
sendAnalyticsEvent(EventName.NAVBAR_RESULT_SELECTED, { ...eventProperties })
}, [addToSearchHistory, toggleOpen, token, eventProperties])
const tokenDetailsPath = getTokenDetailsURL(token.address, undefined, token.chainId)
const [bridgedAddress, bridgedChain, L2Icon] = useBridgedAddress(token)
const tokenDetailsPath = getTokenDetailsURL(bridgedAddress ?? token.address, undefined, bridgedChain ?? token.chainId)
// Close the modal on escape
useEffect(() => {
const keyDownHandler = (event: KeyboardEvent) => {
@@ -150,21 +168,24 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, traceE
>
<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)}
/>
<LogoContainer>
<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)}
/>
<L2NetworkLogo networkUrl={L2Icon} size="16px" />
</LogoContainer>
) : (
<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} />}
<TokenSafetyIcon warning={checkWarning(token.address)} />
</Row>
<Box className={styles.secondaryText}>{token.symbol}</Box>
</Column>
@@ -173,7 +194,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, traceE
<Column className={styles.suggestionSecondaryContainer}>
{token.priceUsd && (
<Row gap="4">
<Box className={styles.primaryText}>{ethNumberStandardFormatter(token.priceUsd, true)}</Box>
<Box className={styles.primaryText}>{formatDollar({ num: token.priceUsd, isPrice: true })}</Box>
</Row>
)}
{token.price24hChange && (

View File

@@ -1,11 +1,14 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import Web3Status from 'components/Web3Status'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
import { useGlobalChainName } from 'graphql/data/util'
import { chainIdToBackendName } from 'graphql/data/util'
import { useIsNftPage } from 'hooks/useIsNftPage'
import { Box } from 'nft/components/Box'
import { Row } from 'nft/components/Flex'
import { UniIcon } from 'nft/components/icons'
import { ReactNode } from 'react'
import { useIsMobile } from 'nft/hooks'
import { ReactNode, useMemo } from 'react'
import { NavLink, NavLinkProps, useLocation } from 'react-router-dom'
import { ChainSelector } from './ChainSelector'
@@ -37,7 +40,8 @@ const MenuItem = ({ href, id, isActive, children }: MenuItemProps) => {
const PageTabs = () => {
const { pathname } = useLocation()
const nftFlag = useNftFlag()
const chainName = useGlobalChainName()
const { chainId: connectedChainId } = useWeb3React()
const chainName = chainIdToBackendName(connectedChainId)
const isPoolActive =
pathname.startsWith('/pool') ||
@@ -46,6 +50,8 @@ const PageTabs = () => {
pathname.startsWith('/increase') ||
pathname.startsWith('/find')
const isNftPage = useIsNftPage()
return (
<>
<MenuItem href="/swap" isActive={pathname.startsWith('/swap')}>
@@ -55,7 +61,7 @@ const PageTabs = () => {
<Trans>Tokens</Trans>
</MenuItem>
{nftFlag === NftVariant.Enabled && (
<MenuItem href="/nfts" isActive={pathname.startsWith('/nfts')}>
<MenuItem href="/nfts" isActive={isNftPage}>
<Trans>NFTs</Trans>
</MenuItem>
)}
@@ -66,9 +72,29 @@ const PageTabs = () => {
)
}
const Navbar = () => {
const useShouldHideNavbar = () => {
const { pathname } = useLocation()
const showShoppingBag = pathname.startsWith('/nfts') || pathname.startsWith('/profile')
const isMobile = useIsMobile()
const shouldHideNavbar = useMemo(() => {
const paths = ['/nfts/profile']
if (!isMobile) return false
for (const path of paths) {
if (pathname.includes(path)) return true
}
return false
}, [isMobile, pathname])
return shouldHideNavbar
}
const Navbar = () => {
const shouldHideNavbar = useShouldHideNavbar()
const isNftPage = useIsNftPage()
if (shouldHideNavbar) return null
return (
<>
@@ -78,9 +104,11 @@ const Navbar = () => {
<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>
{!isNftPage && (
<Box display={{ sm: 'flex', lg: 'none' }}>
<ChainSelector leftAlign={true} />
</Box>
)}
<Row gap="8" display={{ sm: 'none', lg: 'flex' }}>
<PageTabs />
</Row>
@@ -96,10 +124,12 @@ const Navbar = () => {
<Box display={{ sm: 'none', lg: 'flex' }}>
<MenuDropdown />
</Box>
{showShoppingBag && <ShoppingBag />}
<Box display={{ sm: 'none', lg: 'flex' }}>
<ChainSelector />
</Box>
{isNftPage && <ShoppingBag />}
{!isNftPage && (
<Box display={{ sm: 'none', lg: 'flex' }}>
<ChainSelector />
</Box>
)}
<Web3Status />
</Row>

View File

@@ -12,9 +12,6 @@ export const nav = style([
zIndex: '2',
background: 'backgroundFloating',
}),
{
backdropFilter: 'blur(24px)',
},
])
export const logoContainer = style([

View File

@@ -1,18 +1,17 @@
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import React from 'react'
import styled from 'styled-components/macro'
import { escapeRegExp } from '../../utils'
const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: string; redesignFlag: boolean }>`
const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: string }>`
color: ${({ error, theme }) => (error ? theme.deprecated_red1 : theme.deprecated_text1)};
width: 0;
position: relative;
font-weight: ${({ redesignFlag }) => (redesignFlag ? 400 : 500)};
font-weight: 400;
outline: none;
border: none;
flex: 1 1 auto;
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg1)};
background-color: transparent;
font-size: ${({ fontSize }) => fontSize ?? '28px'};
text-align: ${({ align }) => align && align};
white-space: nowrap;
@@ -36,7 +35,7 @@ const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: s
}
::placeholder {
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textSecondary : theme.deprecated_text4)};
color: ${({ theme }) => theme.textTertiary};
}
`
@@ -56,8 +55,6 @@ export const Input = React.memo(function InnerInput({
align?: 'right' | 'left'
prependSymbol?: string | undefined
} & Omit<React.HTMLProps<HTMLInputElement>, 'ref' | 'onChange' | 'as'>) {
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const enforcer = (nextUserInput: string) => {
if (nextUserInput === '' || inputRegex.test(escapeRegExp(nextUserInput))) {
onUserInput(nextUserInput)
@@ -68,7 +65,6 @@ export const Input = React.memo(function InnerInput({
<StyledInput
{...rest}
value={prependSymbol && value ? prependSymbol + value : value}
redesignFlag={redesignFlagEnabled}
onChange={(event) => {
if (prependSymbol) {
const value = event.target.value
@@ -91,7 +87,7 @@ export const Input = React.memo(function InnerInput({
// text-specific options
type="text"
pattern="^[0-9]*[.,]?[0-9]*$"
placeholder={placeholder || (redesignFlagEnabled ? '0' : '0.0')}
placeholder={placeholder || '0'}
minLength={1}
maxLength={79}
spellCheck="false"

View File

@@ -2,22 +2,17 @@ import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { getChainInfoOrDefault, L2ChainInfo } from 'constants/chainInfo'
import { SupportedChainId } from 'constants/chains'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { AlertOctagon, AlertTriangle } from 'react-feather'
import { AlertTriangle } from 'react-feather'
import styled from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
const BodyRow = styled.div<{ $redesignFlag?: boolean }>`
color: ${({ theme, $redesignFlag }) => ($redesignFlag ? theme.textPrimary : theme.black)};
const BodyRow = styled.div`
color: ${({ theme }) => theme.textPrimary};
font-size: 12px;
font-weight: ${({ $redesignFlag }) => $redesignFlag && '400'};
font-size: ${({ $redesignFlag }) => ($redesignFlag ? '14px' : '12px')};
line-height: ${({ $redesignFlag }) => $redesignFlag && '20px'};
font-weight: 400;
font-size: 14px;
line-height: 20px;
`
const CautionOctagon = styled(AlertOctagon)`
color: ${({ theme }) => theme.deprecated_black};
`
const CautionTriangle = styled(AlertTriangle)`
color: ${({ theme }) => theme.accentWarning};
`
@@ -31,15 +26,15 @@ const TitleRow = styled.div`
justify-content: flex-start;
margin-bottom: 8px;
`
const TitleText = styled.div<{ redesignFlag?: boolean }>`
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textPrimary : theme.black)};
font-weight: ${({ redesignFlag }) => (redesignFlag ? '500' : '600')};
const TitleText = styled.div`
color: ${({ theme }) => theme.textPrimary};
font-weight: 500;
font-size: 16px;
line-height: ${({ redesignFlag }) => (redesignFlag ? '24px' : '20px')};
line-height: 24px;
margin: 0px 12px;
`
const Wrapper = styled.div<{ redesignFlag?: boolean }>`
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundSurface : theme.deprecated_yellow3)};
const Wrapper = styled.div`
background-color: ${({ theme }) => theme.backgroundSurface};
border-radius: 12px;
border: 1px solid ${({ theme }) => theme.backgroundOutline};
bottom: 60px;
@@ -57,17 +52,16 @@ export function ChainConnectivityWarning() {
const { chainId } = useWeb3React()
const info = getChainInfoOrDefault(chainId)
const label = info?.label
const redesignFlag = useRedesignFlag() === RedesignVariant.Enabled
return (
<Wrapper redesignFlag={redesignFlag}>
<Wrapper>
<TitleRow>
{redesignFlag ? <CautionTriangle /> : <CautionOctagon />}
<TitleText redesignFlag={redesignFlag}>
<CautionTriangle />
<TitleText>
<Trans>Network Warning</Trans>
</TitleText>
</TitleRow>
<BodyRow $redesignFlag={redesignFlag}>
<BodyRow>
{chainId === SupportedChainId.MAINNET ? (
<Trans>You may have lost your network connection.</Trans>
) : (

View File

@@ -4,6 +4,7 @@ import { RowFixed } from 'components/Row'
import { getChainInfo } from 'constants/chainInfo'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import useGasPrice from 'hooks/useGasPrice'
import { useIsNftPage } from 'hooks/useIsNftPage'
import useMachineTimeMs from 'hooks/useMachineTime'
import JSBI from 'jsbi'
import useBlockNumber from 'lib/hooks/useBlockNumber'
@@ -106,6 +107,7 @@ export default function Polling() {
const machineTime = useMachineTimeMs(NETWORK_HEALTH_CHECK_MS)
const blockTime = useCurrentBlockTimestamp()
const theme = useTheme()
const isNftPage = useIsNftPage()
const ethGasPrice = useGasPrice()
const priceGwei = ethGasPrice ? JSBI.divide(ethGasPrice, JSBI.BigInt(1000000000)) : undefined
@@ -135,7 +137,7 @@ export default function Polling() {
//TODO - chainlink gas oracle is really slow. Can we get a better data source?
return (
return isNftPage ? null : (
<>
<RowFixed>
<StyledPolling onMouseEnter={() => setIsHover(true)} onMouseLeave={() => setIsHover(false)} warning={warning}>

View File

@@ -1,7 +1,7 @@
import { Options, Placement } from '@popperjs/core'
import Portal from '@reach/portal'
import useInterval from 'lib/hooks/useInterval'
import React, { useCallback, useMemo, useState } from 'react'
import React, { CSSProperties, useCallback, useMemo, useState } from 'react'
import { usePopper } from 'react-popper'
import styled from 'styled-components/macro'
import { Z_INDEX } from 'theme/zIndex'
@@ -76,9 +76,20 @@ export interface PopoverProps {
show: boolean
children: React.ReactNode
placement?: Placement
offsetX?: number
offsetY?: number
style?: CSSProperties
}
export default function Popover({ content, show, children, placement = 'auto' }: PopoverProps) {
export default function Popover({
content,
show,
children,
placement = 'auto',
offsetX = 8,
offsetY = 8,
style,
}: PopoverProps) {
const [referenceElement, setReferenceElement] = useState<HTMLDivElement | null>(null)
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null)
const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null)
@@ -88,12 +99,12 @@ export default function Popover({ content, show, children, placement = 'auto' }:
placement,
strategy: 'fixed',
modifiers: [
{ name: 'offset', options: { offset: [8, 8] } },
{ name: 'offset', options: { offset: [offsetX, offsetY] } },
{ name: 'arrow', options: { element: arrowElement } },
{ name: 'preventOverflow', options: { padding: 8 } },
],
}),
[arrowElement, placement]
[arrowElement, offsetX, offsetY, placement]
)
const { styles, update, attributes } = usePopper(referenceElement, popperElement, options)
@@ -105,7 +116,9 @@ export default function Popover({ content, show, children, placement = 'auto' }:
return (
<>
<ReferenceElement ref={setReferenceElement as any}>{children}</ReferenceElement>
<ReferenceElement style={style} ref={setReferenceElement as any}>
{children}
</ReferenceElement>
<Portal>
<PopoverContainer show={show} ref={setPopperElement as any} style={styles.popper} {...attributes.popper}>
{content}

View File

@@ -1,4 +1,3 @@
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
import { useCallback, useEffect } from 'react'
import { X } from 'react-feather'
import { animated } from 'react-spring'
@@ -8,7 +7,6 @@ import styled, { useTheme } from 'styled-components/macro'
import { useRemovePopup } from '../../state/application/hooks'
import { PopupContent } from '../../state/application/reducer'
import FailedNetworkSwitchPopup from './FailedNetworkSwitchPopup'
import TransactionPopup from './TransactionPopup'
const StyledClose = styled(X)`
position: absolute;
@@ -58,7 +56,6 @@ export default function PopupItem({
popKey: string
}) {
const removePopup = useRemovePopup()
const navbarFlag = useNavBarFlag()
const removeThisPopup = useCallback(() => removePopup(popKey), [popKey, removePopup])
useEffect(() => {
if (removeAfterMs === null) return undefined
@@ -80,22 +77,15 @@ export default function PopupItem({
})
let popupContent
if ('txn' in content) {
const {
txn: { hash },
} = content
if (navbarFlag === NavBarVariant.Enabled) return null
popupContent = <TransactionPopup hash={hash} />
} else if ('failedSwitchNetwork' in content) {
if ('failedSwitchNetwork' in content) {
popupContent = <FailedNetworkSwitchPopup chainId={content.failedSwitchNetwork} />
}
return (
return popupContent ? (
<Popup>
<StyledClose color={theme.deprecated_text2} onClick={removeThisPopup} />
{popupContent}
{removeAfterMs !== null ? <AnimatedFader style={faderStyle} /> : null}
</Popup>
)
) : null
}

View File

@@ -18,7 +18,7 @@ import { ExternalLink, ThemedText } from '../../theme'
import { currencyId } from '../../utils/currencyId'
import { unwrappedToken } from '../../utils/unwrappedToken'
import { ButtonEmpty, ButtonPrimary, ButtonSecondary } from '../Button'
import { GreyCard, LightCard } from '../Card'
import { GrayCard, LightCard } from '../Card'
import { AutoColumn } from '../Column'
import CurrencyLogo from '../CurrencyLogo'
import DoubleCurrencyLogo from '../DoubleLogo'
@@ -78,7 +78,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
return (
<>
{userPoolBalance && JSBI.greaterThan(userPoolBalance.quotient, JSBI.BigInt(0)) ? (
<GreyCard border={border}>
<GrayCard border={border}>
<AutoColumn gap="12px">
<FixedHeightRow>
<RowFixed>
@@ -139,7 +139,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
</FixedHeightRow>
</AutoColumn>
</AutoColumn>
</GreyCard>
</GrayCard>
) : (
<LightCard>
<ThemedText.DeprecatedSubHeader style={{ textAlign: 'center' }}>

View File

@@ -1,6 +1,5 @@
import { Trans } from '@lingui/macro'
import PositionListItem from 'components/PositionListItem'
import Toggle from 'components/Toggle'
import React from 'react'
import styled from 'styled-components/macro'
import { MEDIA_WIDTHS } from 'theme'
@@ -10,7 +9,8 @@ const DesktopHeader = styled.div`
display: none;
font-size: 14px;
font-weight: 500;
padding: 8px;
padding: 16px;
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
align-items: center;
@@ -25,12 +25,14 @@ const DesktopHeader = styled.div`
const MobileHeader = styled.div`
font-weight: medium;
font-size: 16px;
font-weight: 500;
padding: 8px;
font-weight: 500;
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
display: none;
@@ -38,8 +40,8 @@ const MobileHeader = styled.div`
@media screen and (max-width: ${MEDIA_WIDTHS.deprecated_upToExtraSmall}px) {
display: flex;
flex-direction: column;
align-items: start;
flex-direction: row;
justify-content: space-between;
}
`
@@ -49,16 +51,12 @@ const ToggleWrap = styled.div`
align-items: center;
`
const ToggleLabel = styled.div`
opacity: ${({ theme }) => theme.opacity.hover};
margin-right: 10px;
`
const MobileTogglePosition = styled.div`
@media screen and (max-width: ${MEDIA_WIDTHS.deprecated_upToExtraSmall}px) {
position: absolute;
right: 20px;
}
const ToggleLabel = styled.button`
cursor: pointer;
background-color: transparent;
border: none;
color: ${({ theme }) => theme.accentAction};
font-size: 1rem;
`
type PositionListProps = React.PropsWithChildren<{
@@ -79,34 +77,26 @@ export default function PositionList({
<Trans>Your positions</Trans>
{positions && ' (' + positions.length + ')'}
</div>
<ToggleWrap>
<ToggleLabel>
<Trans>Show closed positions</Trans>
</ToggleLabel>
<Toggle
id="desktop-hide-closed-positions"
isActive={!userHideClosedPositions}
toggle={() => {
setUserHideClosedPositions(!userHideClosedPositions)
}}
/>
</ToggleWrap>
<ToggleLabel
id="desktop-hide-closed-positions"
onClick={() => {
setUserHideClosedPositions(!userHideClosedPositions)
}}
>
{userHideClosedPositions ? <Trans>Show closed positions</Trans> : <Trans>Hide closed positions</Trans>}
</ToggleLabel>
</DesktopHeader>
<MobileHeader>
<Trans>Your positions</Trans>
<ToggleWrap>
<ToggleLabel>
<Trans>Show closed positions</Trans>
<ToggleLabel
onClick={() => {
setUserHideClosedPositions(!userHideClosedPositions)
}}
>
{userHideClosedPositions ? <Trans>Show closed positions</Trans> : <Trans>Hide closed positions</Trans>}
</ToggleLabel>
<MobileTogglePosition>
<Toggle
id="mobile-hide-closed-positions"
isActive={!userHideClosedPositions}
toggle={() => {
setUserHideClosedPositions(!userHideClosedPositions)
}}
/>
</MobileTogglePosition>
</ToggleWrap>
</MobileHeader>
{positions.map((p) => {

View File

@@ -23,29 +23,22 @@ import { DAI, USDC_MAINNET, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../co
const LinkRow = styled(Link)`
align-items: center;
border-radius: 20px;
display: flex;
cursor: pointer;
user-select: none;
display: flex;
flex-direction: column;
justify-content: space-between;
color: ${({ theme }) => theme.deprecated_text1};
margin: 8px 0;
padding: 16px;
text-decoration: none;
font-weight: 500;
background-color: ${({ theme }) => theme.deprecated_bg1};
&:last-of-type {
margin: 8px 0 0 0;
}
& > div:not(:first-child) {
text-align: center;
}
:hover {
background-color: ${({ theme }) => theme.deprecated_bg2};
background-color: ${({ theme }) => theme.hoverDefault};
}
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
@@ -54,7 +47,7 @@ const LinkRow = styled(Link)`
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
flex-direction: column;
row-gap: 12px;
row-gap: 8px;
`};
`
@@ -74,29 +67,17 @@ const RangeLineItem = styled(DataLineItem)`
display: flex;
flex-direction: row;
align-items: center;
margin-top: 4px;
width: 100%;
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
background-color: ${({ theme }) => theme.deprecated_bg2};
border-radius: 12px;
padding: 8px 0;
`};
`
const DoubleArrow = styled.span`
margin: 0 2px;
color: ${({ theme }) => theme.deprecated_text3};
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
margin: 4px;
padding: 20px;
`};
`
const RangeText = styled.span`
/* background-color: ${({ theme }) => theme.deprecated_bg2}; */
padding: 0.25rem 0.5rem;
padding: 0.25rem 0.25rem;
border-radius: 8px;
`
@@ -123,7 +104,7 @@ const DataText = styled.div`
font-size: 18px;
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
font-size: 14px;
font-size: 18px;
`};
`

View File

@@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro'
import { sendEvent } from 'components/analytics'
import Card, { DarkGreyCard } from 'components/Card'
import Card, { DarkGrayCard } from 'components/Card'
import Row, { AutoRow, RowBetween } from 'components/Row'
import { useEffect, useRef } from 'react'
import { ArrowDown, Info, X } from 'react-feather'
@@ -137,12 +137,12 @@ export function PrivacyPolicy() {
</ExternalLink>
</StyledExternalCard>
<StyledExternalCard>
<ExternalLink href={'https://uniswap.org/disclaimer/'}>
<ExternalLink href={'https://uniswap.org/privacy-policy/'}>
<RowBetween>
<AutoRow gap="4px">
<Info size={20} />
<ThemedText.DeprecatedMain fontSize={14} color={'deprecated_primaryText1'}>
<Trans>Protocol Disclaimer</Trans>
<Trans>Privacy Policy</Trans>
</ThemedText.DeprecatedMain>
</AutoRow>
<StyledLinkOut size={20} />
@@ -155,7 +155,7 @@ export function PrivacyPolicy() {
</ThemedText.DeprecatedMain>
<AutoColumn gap="12px">
{EXTERNAL_APIS.map(({ name, description }, i) => (
<DarkGreyCard key={i}>
<DarkGrayCard key={i}>
<AutoColumn gap="8px">
<AutoRow gap="4px">
<Info size={18} />
@@ -165,7 +165,7 @@ export function PrivacyPolicy() {
</AutoRow>
<ThemedText.DeprecatedMain fontSize={14}>{description}</ThemedText.DeprecatedMain>
</AutoColumn>
</DarkGreyCard>
</DarkGrayCard>
))}
<ThemedText.DeprecatedBody fontSize={12}>
<Row justify="center" marginBottom="1rem">

View File

@@ -1,11 +1,10 @@
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { ReactNode, useCallback, useState } from 'react'
import { HelpCircle } from 'react-feather'
import styled from 'styled-components/macro'
import Tooltip from '../Tooltip'
const QuestionWrapper = styled.div<{ redesignFlag: boolean }>`
const QuestionWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
@@ -18,8 +17,7 @@ const QuestionWrapper = styled.div<{ redesignFlag: boolean }>`
cursor: default;
border-radius: 36px;
font-size: 12px;
border-radius: ${({ redesignFlag }) => redesignFlag && '12px'};
color: ${({ theme, redesignFlag }) => !redesignFlag && theme.deprecated_text2};
border-radius: 12px;
:hover,
:focus {
@@ -27,12 +25,12 @@ const QuestionWrapper = styled.div<{ redesignFlag: boolean }>`
}
`
const QuestionMark = styled.span<{ redesignFlag?: boolean }>`
const QuestionMark = styled.span`
font-size: 14px;
margin-left: ${({ redesignFlag }) => redesignFlag && '8px'};
align-items: ${({ redesignFlag }) => redesignFlag && 'center'};
color: ${({ theme, redesignFlag }) => redesignFlag && theme.textSecondary};
margin-top: ${({ redesignFlag }) => redesignFlag && '2.5px'};
margin-left: 8px;
align-items: center;
color: ${({ theme }) => theme.textSecondary};
margin-top: 2.5px;
`
export default function QuestionHelper({ text }: { text: ReactNode; size?: number }) {
@@ -40,14 +38,12 @@ export default function QuestionHelper({ text }: { text: ReactNode; size?: numbe
const open = useCallback(() => setShow(true), [setShow])
const close = useCallback(() => setShow(false), [setShow])
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
return (
<span style={{ marginLeft: 4, display: 'flex', alignItems: 'center' }}>
<Tooltip text={text} show={show}>
<QuestionWrapper onClick={open} onMouseEnter={open} onMouseLeave={close} redesignFlag={redesignFlagEnabled}>
<QuestionMark redesignFlag={redesignFlagEnabled}>
{redesignFlagEnabled ? <HelpCircle size={16}></HelpCircle> : '?'}
<QuestionWrapper onClick={open} onMouseEnter={open} onMouseLeave={close}>
<QuestionMark>
<HelpCircle size={16} />
</QuestionMark>
</QuestionWrapper>
</Tooltip>

View File

@@ -7,7 +7,7 @@ exports[`renders multi route 1`] = `
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
background-color: #EDEEF2;
background-color: #E8ECFB;
border: unset;
border-radius: 0.5rem;
color: #000;
@@ -110,11 +110,11 @@ exports[`renders multi route 1`] = `
}
.c6 path {
stroke: #888D9B;
stroke: #98A1C0;
}
.c8 {
background-color: #EDEEF2;
background-color: #E8ECFB;
border-radius: 8px;
display: grid;
font-size: 12px;
@@ -129,9 +129,9 @@ exports[`renders multi route 1`] = `
}
.c9 {
background-color: #CED0D9;
background-color: #B8C0DC;
border-radius: 4px;
color: #565A69;
color: #7780A0;
font-size: 10px;
padding: 2px 4px;
z-index: 1021;
@@ -245,7 +245,7 @@ exports[`renders single route 1`] = `
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
background-color: #EDEEF2;
background-color: #E8ECFB;
border: unset;
border-radius: 0.5rem;
color: #000;
@@ -348,11 +348,11 @@ exports[`renders single route 1`] = `
}
.c6 path {
stroke: #888D9B;
stroke: #98A1C0;
}
.c8 {
background-color: #EDEEF2;
background-color: #E8ECFB;
border-radius: 8px;
display: grid;
font-size: 12px;
@@ -367,9 +367,9 @@ exports[`renders single route 1`] = `
}
.c9 {
background-color: #CED0D9;
background-color: #B8C0DC;
border-radius: 4px;
color: #565A69;
color: #7780A0;
font-size: 10px;
padding: 2px 4px;
z-index: 1021;

View File

@@ -1,71 +0,0 @@
import { Trans } from '@lingui/macro'
import { Token } from '@uniswap/sdk-core'
import { ButtonPrimary } from 'components/Button'
import { AlertCircle, ArrowLeft } from 'react-feather'
import styled from 'styled-components/macro'
import { CloseIcon, ThemedText } from 'theme'
import TokenImportCard from './TokenImportCard'
const Wrapper = styled.div`
align-items: center;
display: flex;
flex-direction: column;
flex: 1 1 auto;
height: 100%;
width: 100%;
`
const Button = styled(ButtonPrimary)`
margin-top: 1em;
padding: 10px 1em;
`
const Content = styled.div`
padding: 1em;
`
const Copy = styled(ThemedText.DeprecatedBody)`
text-align: center;
margin: 0 2em 1em !important;
font-weight: 400;
font-size: 16px;
`
const Header = styled.div`
align-items: center;
display: flex;
gap: 14px;
justify-content: space-between;
padding: 20px;
width: 100%;
`
const Icon = styled(AlertCircle)`
stroke: ${({ theme }) => theme.deprecated_text2};
width: 48px;
height: 48px;
`
interface BlockedTokenProps {
onBack: (() => void) | undefined
onDismiss: (() => void) | undefined
blockedTokens: Token[]
}
const BlockedToken = ({ onBack, onDismiss, blockedTokens }: BlockedTokenProps) => (
<Wrapper>
<Header>
{onBack ? <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} /> : <div />}
<ThemedText.DeprecatedMediumHeader>
<Trans>Token not supported</Trans>
</ThemedText.DeprecatedMediumHeader>
{onDismiss ? <CloseIcon onClick={onDismiss} /> : <div />}
</Header>
<Icon />
<Content>
<Copy>
<Trans>This token is not supported in the Uniswap Labs app</Trans>
</Copy>
<TokenImportCard token={blockedTokens[0]} />
<Button disabled>
<Trans>Import</Trans>
</Button>
</Content>
</Wrapper>
)
export default BlockedToken

View File

@@ -1,13 +1,12 @@
import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
import { Currency } from '@uniswap/sdk-core'
import { ElementName, Event, EventName } from 'analytics/constants'
import { TraceEvent } from 'analytics/TraceEvent'
import { getTokenAddress } from 'analytics/utils'
import { AutoColumn } from 'components/Column'
import CurrencyLogo from 'components/CurrencyLogo'
import { AutoRow } from 'components/Row'
import { COMMON_BASES } from 'constants/routing'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
import { getTokenAddress } from 'lib/utils/analytics'
import { Text } from 'rebass'
import styled from 'styled-components/macro'
import { currencyId } from 'utils/currencyId'
@@ -18,17 +17,9 @@ const MobileWrapper = styled(AutoColumn)`
`};
`
const BaseWrapper = styled.div<{ disable?: boolean; redesignFlag?: boolean }>`
border: 1px solid
${({ theme, disable, redesignFlag }) =>
disable
? redesignFlag
? theme.accentAction
: 'transparent'
: redesignFlag
? theme.backgroundOutline
: theme.deprecated_bg3};
border-radius: ${({ redesignFlag }) => (redesignFlag ? '16px' : '10px')};
const BaseWrapper = styled.div<{ disable?: boolean }>`
border: 1px solid ${({ theme, disable }) => (disable ? theme.accentAction : theme.backgroundOutline)};
border-radius: 16px;
display: flex;
padding: 6px;
padding-right: 12px;
@@ -36,15 +27,11 @@ const BaseWrapper = styled.div<{ disable?: boolean; redesignFlag?: boolean }>`
align-items: center;
:hover {
cursor: ${({ disable }) => !disable && 'pointer'};
background-color: ${({ theme, disable, redesignFlag }) =>
(redesignFlag && theme.hoverDefault) || (!disable && theme.deprecated_bg2)};
background-color: ${({ theme }) => theme.hoverDefault};
}
color: ${({ theme, disable, redesignFlag }) =>
disable && (redesignFlag ? theme.accentAction : theme.deprecated_text3)};
background-color: ${({ theme, disable, redesignFlag }) =>
disable && (redesignFlag ? theme.accentActionSoft : theme.deprecated_bg3)};
filter: ${({ disable, redesignFlag }) => disable && !redesignFlag && 'grayscale(1)'};
color: ${({ theme, disable }) => disable && theme.accentAction};
background-color: ${({ theme, disable }) => disable && theme.accentActionSoft};
`
const formatAnalyticsEventProperties = (currency: Currency, searchQuery: string, isAddressSearch: string | false) => ({
@@ -73,8 +60,6 @@ export default function CommonBases({
isAddressSearch: string | false
}) {
const bases = typeof chainId !== 'undefined' ? COMMON_BASES[chainId] ?? [] : []
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
return bases.length > 0 ? (
<MobileWrapper gap="md">
@@ -84,7 +69,7 @@ export default function CommonBases({
return (
<TraceEvent
events={[Event.onClick, Event.onKeyPress]}
events={[BrowserEvent.onClick, BrowserEvent.onKeyPress]}
name={EventName.TOKEN_SELECTED}
properties={formatAnalyticsEventProperties(currency, searchQuery, isAddressSearch)}
element={ElementName.COMMON_BASES_CURRENCY_BUTTON}
@@ -95,7 +80,6 @@ export default function CommonBases({
onKeyPress={(e) => !isSelected && e.key === 'Enter' && onSelect(currency)}
onClick={() => !isSelected && onSelect(currency)}
disable={isSelected}
redesignFlag={redesignFlagEnabled}
key={currencyId(currency)}
>
<CurrencyLogoFromList currency={currency} />

View File

@@ -2,8 +2,26 @@
exports[`renders currency rows correctly when currencies list is non-empty 1`] = `
<DocumentFragment>
.c7 {
color: #6E727D;
.c9 {
color: #98A1C0;
}
.c7 {
margin-left: 4px;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
}
.c8 {
width: 1em;
height: 1em;
color: #98A1C0;
}
.c4 {
@@ -55,7 +73,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
justify-content: space-between;
}
.c8 {
.c10 {
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
@@ -72,7 +90,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
}
.c3:hover {
background-color: #EDEEF2;
background-color: #B8C0DC14;
}
.c6 {
@@ -111,9 +129,41 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
>
Dai Stablecoin
</div>
<div
class="c7"
>
<svg
class="c8"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
/>
<line
x1="12"
x2="12"
y1="9"
y2="13"
/>
<line
x1="12"
x2="12.01"
y1="17"
y2="17"
/>
</svg>
</div>
</div>
<div
class="c7 css-1j6a53a"
class="c9 css-1j6a53a"
>
DAI
</div>
@@ -122,7 +172,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
class="c4"
>
<div
class="c0 c1 c8"
class="c0 c1 c10"
style="justify-self: flex-end;"
/>
</div>
@@ -150,9 +200,41 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
>
USD//C
</div>
<div
class="c7"
>
<svg
class="c8"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
/>
<line
x1="12"
x2="12"
y1="9"
y2="13"
/>
<line
x1="12"
x2="12.01"
y1="17"
y2="17"
/>
</svg>
</div>
</div>
<div
class="c7 css-1j6a53a"
class="c9 css-1j6a53a"
>
USDC
</div>
@@ -161,7 +243,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
class="c4"
>
<div
class="c0 c1 c8"
class="c0 c1 c10"
style="justify-self: flex-end;"
/>
</div>
@@ -189,9 +271,41 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
>
Wrapped BTC
</div>
<div
class="c7"
>
<svg
class="c8"
fill="none"
height="24"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
/>
<line
x1="12"
x2="12"
y1="9"
y2="13"
/>
<line
x1="12"
x2="12.01"
y1="17"
y2="17"
/>
</svg>
</div>
</div>
<div
class="c7 css-1j6a53a"
class="c9 css-1j6a53a"
>
WBTC
</div>
@@ -200,7 +314,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
class="c4"
>
<div
class="c0 c1 c8"
class="c0 c1 c10"
style="justify-self: flex-end;"
/>
</div>
@@ -212,12 +326,74 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
exports[`renders loading rows when isLoading is true 1`] = `
<DocumentFragment>
<div
.c0 {
display: grid;
}
.c0 > div {
-webkit-animation: fAQEyV 1.5s infinite;
animation: fAQEyV 1.5s infinite;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
background: linear-gradient( to left,#F5F6FC 25%,#E8ECFB 50%,#F5F6FC 75% );
background-size: 400%;
border-radius: 12px;
height: 2.4em;
will-change: background-position;
}
.c1 {
grid-column-gap: 0.5em;
grid-template-columns: repeat(12,1fr);
max-width: 960px;
padding: 12px 20px;
}
.c1 > div:nth-child(4n + 1) {
grid-column: 1 / 8;
height: 1em;
margin-bottom: 0.25em;
}
.c1 > div:nth-child(4n + 2) {
grid-column: 12;
height: 1em;
margin-top: 0.25em;
}
.c1 > div:nth-child(4n + 3) {
grid-column: 1 / 4;
height: 0.75em;
}
<div
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
>
<div
style="height: 0px; width: 100%;"
/>
style="height: 560px; width: 100%;"
>
<div
class="c0 c1"
>
<div />
<div />
<div />
</div>
<div
class="c0 c1"
>
<div />
<div />
<div />
</div>
<div
class="c0 c1"
>
<div />
<div />
<div />
</div>
</div>
</div>
</DocumentFragment>
`;

View File

@@ -1,12 +1,9 @@
import { Trans } from '@lingui/macro'
import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, ElementName, EventName } from '@uniswap/analytics-events'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'analytics/constants'
import { TraceEvent } from 'analytics/TraceEvent'
import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon'
import { checkWarning } from 'constants/tokenSafety'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { TokenSafetyVariant, useTokenSafetyFlag } from 'featureFlags/flags/tokenSafety'
import { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'
import { XOctagon } from 'react-feather'
import { Check } from 'react-feather'
@@ -16,10 +13,8 @@ import styled from 'styled-components/macro'
import { useIsUserAddedToken } from '../../../hooks/Tokens'
import { useCurrencyBalance } from '../../../state/connection/hooks'
import { useCombinedActiveList } from '../../../state/lists/hooks'
import { WrappedTokenInfo } from '../../../state/lists/wrappedTokenInfo'
import { ThemedText } from '../../../theme'
import { isTokenOnList } from '../../../utils'
import Column, { AutoColumn } from '../../Column'
import CurrencyLogo from '../../CurrencyLogo'
import Loader from '../../Loader'
@@ -129,27 +124,22 @@ export function CurrencyRow({
}) {
const { account } = useWeb3React()
const key = currencyKey(currency)
const selectedTokenList = useCombinedActiveList()
const isOnSelectedList = isTokenOnList(selectedTokenList, currency.isToken ? currency : undefined)
const customAdded = useIsUserAddedToken(currency)
const balance = useCurrencyBalance(account ?? undefined, currency)
const warning = currency.isNative ? null : checkWarning(currency.address)
const redesignFlagEnabled = useRedesignFlag() === RedesignVariant.Enabled
const tokenSafetyFlagEnabled = useTokenSafetyFlag() === TokenSafetyVariant.Enabled
const isBlockedToken = !!warning && !warning.canProceed
const blockedTokenOpacity = '0.6'
// only show add or remove buttons if not on selected list
return (
<TraceEvent
events={[Event.onClick, Event.onKeyPress]}
events={[BrowserEvent.onClick, BrowserEvent.onKeyPress]}
name={EventName.TOKEN_SELECTED}
properties={{ is_imported_by_user: customAdded, ...eventProperties }}
element={ElementName.TOKEN_SELECTOR_ROW}
>
<MenuItem
tabIndex={0}
redesignFlag={redesignFlagEnabled}
style={style}
className={`token-item-${key}`}
onKeyPress={(e) => (!isSelected && e.key === 'Enter' ? onSelect(!!warning) : null)}
@@ -168,15 +158,11 @@ export function CurrencyRow({
<AutoColumn style={{ opacity: isBlockedToken ? blockedTokenOpacity : '1' }}>
<Row>
<CurrencyName title={currency.name}>{currency.name}</CurrencyName>
{tokenSafetyFlagEnabled && <TokenSafetyIcon warning={warning} />}
<TokenSafetyIcon warning={warning} />
{isBlockedToken && <BlockedTokenIcon />}
</Row>
<ThemedText.DeprecatedDarkGray ml="0px" fontSize={'12px'} fontWeight={300}>
{!currency.isNative && !isOnSelectedList && customAdded ? (
<Trans>{currency.symbol} Added by user</Trans>
) : (
currency.symbol
)}
{currency.symbol}
</ThemedText.DeprecatedDarkGray>
</AutoColumn>
<Column>
@@ -187,10 +173,9 @@ export function CurrencyRow({
{showCurrencyAmount ? (
<RowFixed style={{ justifySelf: 'flex-end' }}>
{balance ? <Balance balance={balance} /> : account ? <Loader /> : null}
{redesignFlagEnabled && isSelected && <CheckIcon />}
{isSelected && <CheckIcon />}
</RowFixed>
) : (
redesignFlagEnabled &&
isSelected && (
<RowFixed style={{ justifySelf: 'flex-end' }}>
<CheckIcon />
@@ -227,6 +212,14 @@ export const formatAnalyticsEventProperties = (
: { search_token_address_input: isAddressSearch }),
})
const LoadingRow = () => (
<LoadingRows>
<div />
<div />
<div />
</LoadingRows>
)
export default function CurrencyList({
height,
currencies,
@@ -272,13 +265,7 @@ export default function CurrencyList({
const token = currency?.wrapped
if (isLoading) {
return (
<LoadingRows>
<div />
<div />
<div />
</LoadingRows>
)
return LoadingRow()
} else if (currency) {
return (
<CurrencyRow
@@ -303,7 +290,11 @@ export default function CurrencyList({
return currencyKey(currency)
}, [])
return (
return isLoading ? (
<FixedSizeList height={height} ref={fixedListRef as any} width="100%" itemData={[]} itemCount={10} itemSize={56}>
{LoadingRow}
</FixedSizeList>
) : (
<FixedSizeList
height={height}
ref={fixedListRef as any}

View File

@@ -1,11 +1,10 @@
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
import { Trace } from '@uniswap/analytics'
import { EventName, ModalName } from '@uniswap/analytics-events'
import { Currency, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { EventName, ModalName } from 'analytics/constants'
import { Trace } from 'analytics/Trace'
import { sendEvent } from 'components/analytics'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import useDebounce from 'hooks/useDebounce'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import useToggle from 'hooks/useToggle'
@@ -13,40 +12,29 @@ import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { getTokenFilter } from 'lib/hooks/useTokenList/filtering'
import { tokenComparator, useSortTokensByQuery } from 'lib/hooks/useTokenList/sorting'
import { ChangeEvent, KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Edit } from 'react-feather'
import AutoSizer from 'react-virtualized-auto-sizer'
import { FixedSizeList } from 'react-window'
import { Text } from 'rebass'
import { useAllTokenBalances } from 'state/connection/hooks'
import styled, { useTheme } from 'styled-components/macro'
import { useAllTokens, useIsUserAddedToken, useSearchInactiveTokenLists, useToken } from '../../hooks/Tokens'
import { ButtonText, CloseIcon, IconWrapper, ThemedText } from '../../theme'
import { useActiveTokens, useIsUserAddedToken, useSearchInactiveTokenLists, useToken } from '../../hooks/Tokens'
import { CloseIcon, ThemedText } from '../../theme'
import { isAddress } from '../../utils'
import Column from '../Column'
import Row, { RowBetween, RowFixed } from '../Row'
import Row, { RowBetween } from '../Row'
import CommonBases from './CommonBases'
import { CurrencyRow, formatAnalyticsEventProperties } from './CurrencyList'
import CurrencyList from './CurrencyList'
import { PaddedColumn, SearchInput, Separator } from './styleds'
const ContentWrapper = styled(Column)<{ redesignFlag?: boolean }>`
background-color: ${({ theme, redesignFlag }) => redesignFlag && theme.backgroundSurface};
const ContentWrapper = styled(Column)`
background-color: ${({ theme }) => theme.backgroundSurface};
width: 100%;
flex: 1 1;
position: relative;
`
const Footer = styled.div`
width: 100%;
border-radius: 20px;
padding: 20px;
border-top-left-radius: 0;
border-top-right-radius: 0;
background-color: ${({ theme }) => theme.deprecated_bg1};
border-top: 1px solid ${({ theme }) => theme.deprecated_bg2};
`
interface CurrencySearchProps {
isOpen: boolean
onDismiss: () => void
@@ -56,7 +44,6 @@ interface CurrencySearchProps {
showCommonBases?: boolean
showCurrencyAmount?: boolean
disableNonToken?: boolean
showManageView: () => void
}
export function CurrencySearch({
@@ -68,11 +55,7 @@ export function CurrencySearch({
disableNonToken,
onDismiss,
isOpen,
showManageView,
}: CurrencySearchProps) {
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const { chainId } = useWeb3React()
const theme = useTheme()
@@ -84,7 +67,8 @@ export function CurrencySearch({
const [searchQuery, setSearchQuery] = useState<string>('')
const debouncedQuery = useDebounce(searchQuery, 200)
const allTokens = useAllTokens()
// Only display 'imported' tokens when the search filter has input
const defaultTokens = useActiveTokens(debouncedQuery.length > 0)
// if they input an address, use it
const isAddressSearch = isAddress(debouncedQuery)
@@ -104,32 +88,30 @@ export function CurrencySearch({
}, [isAddressSearch])
const filteredTokens: Token[] = useMemo(() => {
return Object.values(allTokens).filter(getTokenFilter(debouncedQuery))
}, [allTokens, debouncedQuery])
return Object.values(defaultTokens).filter(getTokenFilter(debouncedQuery))
}, [defaultTokens, debouncedQuery])
const [balances, balancesIsLoading] = useAllTokenBalances()
const [balances, balancesAreLoading] = useAllTokenBalances()
const sortedTokens: Token[] = useMemo(
() => (!balancesIsLoading ? [...filteredTokens].sort(tokenComparator.bind(null, balances)) : []),
[balances, filteredTokens, balancesIsLoading]
() => (!balancesAreLoading ? [...filteredTokens].sort(tokenComparator.bind(null, balances)) : []),
[balances, filteredTokens, balancesAreLoading]
)
const isLoading = Boolean(balancesAreLoading && !tokenLoaderTimerElapsed)
const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens)
const native = useNativeCurrency()
const wrapped = native.wrapped
const filteredSortedTokensWithETH: Currency[] = useMemo(() => {
// Use Celo ERC20 Implementation and exclude the native asset
if (!native) {
return filteredSortedTokens
}
const searchCurrencies: Currency[] = useMemo(() => {
const s = debouncedQuery.toLowerCase().trim()
if (native.symbol?.toLowerCase()?.indexOf(s) !== -1) {
// Always bump the native token to the top of the list.
return [native, ...filteredSortedTokens.filter((t) => !t.equals(native))]
}
return filteredSortedTokens
}, [debouncedQuery, native, filteredSortedTokens])
const tokens = filteredSortedTokens.filter((t) => !(t.equals(wrapped) || (disableNonToken && t.isNative)))
const natives = (disableNonToken || native.equals(wrapped) ? [wrapped] : [native, wrapped]).filter(
(n) => n.symbol?.toLowerCase()?.indexOf(s) !== -1 || n.name?.toLowerCase()?.indexOf(s) !== -1
)
return [...natives, ...tokens]
}, [debouncedQuery, filteredSortedTokens, wrapped, disableNonToken, native])
const handleCurrencySelect = useCallback(
(currency: Currency, hasWarning?: boolean) => {
@@ -159,17 +141,17 @@ export function CurrencySearch({
const s = debouncedQuery.toLowerCase().trim()
if (s === native?.symbol?.toLowerCase()) {
handleCurrencySelect(native)
} else if (filteredSortedTokensWithETH.length > 0) {
} else if (searchCurrencies.length > 0) {
if (
filteredSortedTokensWithETH[0].symbol?.toLowerCase() === debouncedQuery.trim().toLowerCase() ||
filteredSortedTokensWithETH.length === 1
searchCurrencies[0].symbol?.toLowerCase() === debouncedQuery.trim().toLowerCase() ||
searchCurrencies.length === 1
) {
handleCurrencySelect(filteredSortedTokensWithETH[0])
handleCurrencySelect(searchCurrencies[0])
}
}
}
},
[debouncedQuery, native, filteredSortedTokensWithETH, handleCurrencySelect]
[debouncedQuery, native, searchCurrencies, handleCurrencySelect]
)
// menu ui
@@ -191,7 +173,7 @@ export function CurrencySearch({
}, [])
return (
<ContentWrapper redesignFlag={redesignFlagEnabled}>
<ContentWrapper>
<Trace name={EventName.TOKEN_SELECTOR_OPENED} modal={ModalName.TOKEN_SELECTOR} shouldLogImpression>
<PaddedColumn gap="16px">
<RowBetween>
@@ -206,7 +188,6 @@ export function CurrencySearch({
id="token-search-input"
placeholder={t`Search name or paste address`}
autoComplete="off"
redesignFlag={redesignFlagEnabled}
value={searchQuery}
ref={inputRef as RefObject<HTMLInputElement>}
onChange={handleInput}
@@ -223,7 +204,7 @@ export function CurrencySearch({
/>
)}
</PaddedColumn>
<Separator redesignFlag={redesignFlagEnabled} />
<Separator />
{searchToken && !searchTokenIsAdded ? (
<Column style={{ padding: '20px 0', height: '100%' }}>
<CurrencyRow
@@ -241,20 +222,20 @@ export function CurrencySearch({
)}
/>
</Column>
) : filteredSortedTokens?.length > 0 || filteredInactiveTokens?.length > 0 ? (
) : searchCurrencies?.length > 0 || filteredInactiveTokens?.length > 0 || isLoading ? (
<div style={{ flex: '1' }}>
<AutoSizer disableWidth>
{({ height }) => (
<CurrencyList
height={height}
currencies={disableNonToken ? filteredSortedTokens : filteredSortedTokensWithETH}
currencies={searchCurrencies}
otherListTokens={filteredInactiveTokens}
onCurrencySelect={handleCurrencySelect}
otherCurrency={otherSelectedCurrency}
selectedCurrency={selectedCurrency}
fixedListRef={fixedList}
showCurrencyAmount={showCurrencyAmount}
isLoading={balancesIsLoading && !tokenLoaderTimerElapsed}
isLoading={isLoading}
searchQuery={searchQuery}
isAddressSearch={isAddressSearch}
/>
@@ -268,26 +249,6 @@ export function CurrencySearch({
</ThemedText.DeprecatedMain>
</Column>
)}
{!redesignFlagEnabled && (
<Footer>
<Row justify="center">
<ButtonText
onClick={showManageView}
color={theme.deprecated_primary1}
className="list-token-manage-button"
>
<RowFixed>
<IconWrapper size="16px" marginRight="6px" stroke={theme.deprecated_primaryText1}>
<Edit />
</IconWrapper>
<ThemedText.DeprecatedMain color={theme.deprecated_primaryText1}>
<Trans>Manage Token Lists</Trans>
</ThemedText.DeprecatedMain>
</RowFixed>
</ButtonText>
</Row>
</Footer>
)}
</Trace>
</ContentWrapper>
)

View File

@@ -1,19 +1,12 @@
import { Currency, Token } from '@uniswap/sdk-core'
import { TokenList } from '@uniswap/token-lists'
import TokenSafety from 'components/TokenSafety'
import { TokenSafetyVariant, useTokenSafetyFlag } from 'featureFlags/flags/tokenSafety'
import usePrevious from 'hooks/usePrevious'
import { memo, useCallback, useEffect, useState } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import { useUserAddedTokens } from 'state/user/hooks'
import useLast from '../../hooks/useLast'
import { useWindowSize } from '../../hooks/useWindowSize'
import Modal from '../Modal'
import { CurrencySearch } from './CurrencySearch'
import { ImportList } from './ImportList'
import { ImportToken } from './ImportToken'
import Manage from './Manage'
interface CurrencySearchModalProps {
isOpen: boolean
@@ -28,9 +21,7 @@ interface CurrencySearchModalProps {
export enum CurrencyModalView {
search,
manage,
importToken,
importList,
tokenSafety,
}
@@ -44,7 +35,7 @@ export default memo(function CurrencySearchModal({
showCurrencyAmount = true,
disableNonToken = false,
}: CurrencySearchModalProps) {
const [modalView, setModalView] = useState<CurrencyModalView>(CurrencyModalView.manage)
const [modalView, setModalView] = useState<CurrencyModalView>(CurrencyModalView.search)
const lastOpen = useLast(isOpen)
const userAddedTokens = useUserAddedTokens()
@@ -59,44 +50,20 @@ export default memo(function CurrencySearchModal({
setModalView(CurrencyModalView.tokenSafety)
}
const tokenSafetyFlag = useTokenSafetyFlag()
const handleCurrencySelect = useCallback(
(currency: Currency, hasWarning?: boolean) => {
if (
tokenSafetyFlag === TokenSafetyVariant.Enabled &&
hasWarning &&
currency.isToken &&
!userAddedTokens.find((token) => token.equals(currency))
) {
if (hasWarning && currency.isToken && !userAddedTokens.find((token) => token.equals(currency))) {
showTokenSafetySpeedbump(currency)
} else {
onCurrencySelect(currency)
onDismiss()
}
},
[onDismiss, onCurrencySelect, tokenSafetyFlag, userAddedTokens]
[onDismiss, onCurrencySelect, userAddedTokens]
)
// for token import view
const prevView = usePrevious(modalView)
// used for import token flow
const [importToken, setImportToken] = useState<Token | undefined>()
// used for import list
const [importList, setImportList] = useState<TokenList | undefined>()
const [listURL, setListUrl] = useState<string | undefined>()
// used for token safety
const [warningToken, setWarningToken] = useState<Token | undefined>()
const showManageView = useCallback(() => setModalView(CurrencyModalView.manage), [setModalView])
const handleBackImport = useCallback(
() => setModalView(prevView && prevView !== CurrencyModalView.importToken ? prevView : CurrencyModalView.search),
[setModalView, prevView]
)
const { height: windowHeight } = useWindowSize()
// change min height if not searching
let modalHeight: number | undefined = 80
@@ -117,13 +84,12 @@ export default memo(function CurrencySearchModal({
showCommonBases={showCommonBases}
showCurrencyAmount={showCurrencyAmount}
disableNonToken={disableNonToken}
showManageView={showManageView}
/>
)
break
case CurrencyModalView.tokenSafety:
modalHeight = undefined
if (tokenSafetyFlag === TokenSafetyVariant.Enabled && warningToken) {
if (warningToken) {
content = (
<TokenSafety
tokenAddress={warningToken.address}
@@ -134,40 +100,6 @@ export default memo(function CurrencySearchModal({
)
}
break
case CurrencyModalView.importToken:
if (importToken) {
modalHeight = undefined
if (tokenSafetyFlag === TokenSafetyVariant.Enabled) {
showTokenSafetySpeedbump(importToken)
}
content = (
<ImportToken
tokens={[importToken]}
onDismiss={onDismiss}
list={importToken instanceof WrappedTokenInfo ? importToken.list : undefined}
onBack={handleBackImport}
handleCurrencySelect={handleCurrencySelect}
/>
)
}
break
case CurrencyModalView.importList:
modalHeight = 40
if (importList && listURL) {
content = <ImportList list={importList} listURL={listURL} onDismiss={onDismiss} setModalView={setModalView} />
}
break
case CurrencyModalView.manage:
content = (
<Manage
onDismiss={onDismiss}
setModalView={setModalView}
setImportToken={setImportToken}
setImportList={setImportList}
setListUrl={setListUrl}
/>
)
break
}
return (
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={modalHeight} minHeight={modalHeight}>

View File

@@ -1,167 +0,0 @@
import { Trans } from '@lingui/macro'
import { TokenList } from '@uniswap/token-lists'
import { sendEvent } from 'components/analytics'
import { ButtonPrimary } from 'components/Button'
import Card from 'components/Card'
import { AutoColumn } from 'components/Column'
import ListLogo from 'components/ListLogo'
import { AutoRow, RowBetween, RowFixed } from 'components/Row'
import { SectionBreak } from 'components/swap/styleds'
import { useFetchListCallback } from 'hooks/useFetchListCallback'
import { transparentize } from 'polished'
import { useCallback, useState } from 'react'
import { AlertTriangle, ArrowLeft } from 'react-feather'
import { useAppDispatch } from 'state/hooks'
import { enableList, removeList } from 'state/lists/actions'
import { useAllLists } from 'state/lists/hooks'
import styled, { useTheme } from 'styled-components/macro'
import { CloseIcon, ThemedText } from 'theme'
import { ExternalLink } from '../../theme'
import { CurrencyModalView } from './CurrencySearchModal'
import { Checkbox, PaddedColumn, TextDot } from './styleds'
const Wrapper = styled.div`
position: relative;
width: 100%;
overflow: auto;
`
interface ImportProps {
listURL: string
list: TokenList
onDismiss: () => void
setModalView: (view: CurrencyModalView) => void
}
export function ImportList({ listURL, list, setModalView, onDismiss }: ImportProps) {
const theme = useTheme()
const dispatch = useAppDispatch()
// user must accept
const [confirmed, setConfirmed] = useState(false)
const lists = useAllLists()
const fetchList = useFetchListCallback()
// monitor is list is loading
const adding = Boolean(lists[listURL]?.loadingRequestId)
const [addError, setAddError] = useState<string | null>(null)
const handleAddList = useCallback(() => {
if (adding) return
setAddError(null)
fetchList(listURL)
.then(() => {
sendEvent({
category: 'Lists',
action: 'Add List',
label: listURL,
})
// turn list on
dispatch(enableList(listURL))
// go back to lists
setModalView(CurrencyModalView.manage)
})
.catch((error) => {
sendEvent({
category: 'Lists',
action: 'Add List Failed',
label: listURL,
})
setAddError(error.message)
dispatch(removeList(listURL))
})
}, [adding, dispatch, fetchList, listURL, setModalView])
return (
<Wrapper>
<PaddedColumn gap="14px" style={{ width: '100%', flex: '1 1' }}>
<RowBetween>
<ArrowLeft style={{ cursor: 'pointer' }} onClick={() => setModalView(CurrencyModalView.manage)} />
<ThemedText.DeprecatedMediumHeader>
<Trans>Import List</Trans>
</ThemedText.DeprecatedMediumHeader>
<CloseIcon onClick={onDismiss} />
</RowBetween>
</PaddedColumn>
<SectionBreak />
<PaddedColumn gap="md">
<AutoColumn gap="md">
<Card backgroundColor={theme.deprecated_bg2} padding="12px 20px">
<RowBetween>
<RowFixed>
{list.logoURI && <ListLogo logoURI={list.logoURI} size="40px" />}
<AutoColumn gap="sm" style={{ marginLeft: '20px' }}>
<RowFixed>
<ThemedText.DeprecatedBody fontWeight={600} mr="6px">
{list.name}
</ThemedText.DeprecatedBody>
<TextDot />
<ThemedText.DeprecatedMain fontSize={'16px'} ml="6px">
<Trans>{list.tokens.length} tokens</Trans>
</ThemedText.DeprecatedMain>
</RowFixed>
<ExternalLink href={`https://tokenlists.org/token-list?url=${listURL}`}>
<ThemedText.DeprecatedMain fontSize={'12px'} color={theme.deprecated_blue1}>
{listURL}
</ThemedText.DeprecatedMain>
</ExternalLink>
</AutoColumn>
</RowFixed>
</RowBetween>
</Card>
<Card style={{ backgroundColor: transparentize(0.8, theme.deprecated_red1) }}>
<AutoColumn justify="center" style={{ textAlign: 'center', gap: '16px', marginBottom: '12px' }}>
<AlertTriangle stroke={theme.deprecated_red1} size={32} />
<ThemedText.DeprecatedBody fontWeight={500} fontSize={20} color={theme.deprecated_red1}>
<Trans>Import at your own risk</Trans>
</ThemedText.DeprecatedBody>
</AutoColumn>
<AutoColumn style={{ textAlign: 'center', gap: '16px', marginBottom: '12px' }}>
<ThemedText.DeprecatedBody fontWeight={500} color={theme.deprecated_red1}>
<Trans>
By adding this list you are implicitly trusting that the data is correct. Anyone can create a list,
including creating fake versions of existing lists and lists that claim to represent projects that do
not have one.
</Trans>
</ThemedText.DeprecatedBody>
<ThemedText.DeprecatedBody fontWeight={600} color={theme.deprecated_red1}>
<Trans>If you purchase a token from this list, you may not be able to sell it back.</Trans>
</ThemedText.DeprecatedBody>
</AutoColumn>
<AutoRow justify="center" style={{ cursor: 'pointer' }} onClick={() => setConfirmed(!confirmed)}>
<Checkbox
name="confirmed"
type="checkbox"
checked={confirmed}
onChange={() => setConfirmed(!confirmed)}
/>
<ThemedText.DeprecatedBody ml="10px" fontSize="16px" color={theme.deprecated_red1} fontWeight={500}>
<Trans>I understand</Trans>
</ThemedText.DeprecatedBody>
</AutoRow>
</Card>
<ButtonPrimary
disabled={!confirmed}
altDisabledStyle={true}
$borderRadius="20px"
padding="10px 1rem"
onClick={handleAddList}
>
<Trans>Import</Trans>
</ButtonPrimary>
{addError ? (
<ThemedText.DeprecatedError title={addError} style={{ textOverflow: 'ellipsis', overflow: 'hidden' }} error>
{addError}
</ThemedText.DeprecatedError>
) : null}
</AutoColumn>
{/* </Card> */}
</PaddedColumn>
</Wrapper>
)
}

View File

@@ -1,106 +0,0 @@
import { Trans } from '@lingui/macro'
import { Token } from '@uniswap/sdk-core'
import { ButtonPrimary } from 'components/Button'
import { AutoColumn } from 'components/Column'
import CurrencyLogo from 'components/CurrencyLogo'
import ListLogo from 'components/ListLogo'
import { AutoRow, RowFixed } from 'components/Row'
import { useIsTokenActive, useIsUserAddedToken } from 'hooks/Tokens'
import { CSSProperties } from 'react'
import { CheckCircle } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from 'theme'
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
const TokenSection = styled.div<{ dim?: boolean }>`
padding: 4px 20px;
height: 56px;
display: grid;
grid-template-columns: auto minmax(auto, 1fr) auto;
grid-gap: 16px;
align-items: center;
opacity: ${({ dim }) => (dim ? '0.4' : '1')};
`
const CheckIcon = styled(CheckCircle)`
height: 16px;
width: 16px;
margin-right: 6px;
stroke: ${({ theme }) => theme.deprecated_green1};
`
const NameOverflow = styled.div`
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
text-overflow: ellipsis;
max-width: 140px;
font-size: 12px;
`
export default function ImportRow({
token,
style,
dim,
showImportView,
setImportToken,
}: {
token: Token
style?: CSSProperties
dim?: boolean
showImportView: () => void
setImportToken: (token: Token) => void
}) {
const theme = useTheme()
// check if already active on list or local storage tokens
const isAdded = useIsUserAddedToken(token)
const isActive = useIsTokenActive(token)
const list = token instanceof WrappedTokenInfo ? token.list : undefined
return (
<TokenSection tabIndex={0} style={style}>
<CurrencyLogo currency={token} size={'24px'} style={{ opacity: dim ? '0.6' : '1' }} />
<AutoColumn gap="4px" style={{ opacity: dim ? '0.6' : '1' }}>
<AutoRow>
<ThemedText.DeprecatedBody fontWeight={500}>{token.symbol}</ThemedText.DeprecatedBody>
<ThemedText.DeprecatedDarkGray ml="8px" fontWeight={300}>
<NameOverflow title={token.name}>{token.name}</NameOverflow>
</ThemedText.DeprecatedDarkGray>
</AutoRow>
{list && list.logoURI && (
<RowFixed>
<ThemedText.DeprecatedSmall mr="4px" color={theme.deprecated_text3}>
<Trans>via {list.name} </Trans>
</ThemedText.DeprecatedSmall>
<ListLogo logoURI={list.logoURI} size="12px" />
</RowFixed>
)}
</AutoColumn>
{!isActive && !isAdded ? (
<ButtonPrimary
width="fit-content"
padding="6px 12px"
fontWeight={500}
fontSize="14px"
onClick={() => {
setImportToken && setImportToken(token)
showImportView()
}}
>
<Trans>Import</Trans>
</ButtonPrimary>
) : (
<RowFixed style={{ minWidth: 'fit-content' }}>
<CheckIcon />
<ThemedText.DeprecatedMain color={theme.deprecated_green1}>
<Trans>Active</Trans>
</ThemedText.DeprecatedMain>
</RowFixed>
)}
</TokenSection>
)
}

View File

@@ -1,100 +0,0 @@
import { Plural, Trans } from '@lingui/macro'
import { Currency, Token } from '@uniswap/sdk-core'
import { TokenList } from '@uniswap/token-lists'
import { ElementName, Event, EventName } from 'analytics/constants'
import { TraceEvent } from 'analytics/TraceEvent'
import { ButtonPrimary } from 'components/Button'
import { AutoColumn } from 'components/Column'
import { RowBetween } from 'components/Row'
import { SectionBreak } from 'components/swap/styleds'
import { useUnsupportedTokens } from 'hooks/Tokens'
import { AlertCircle, ArrowLeft } from 'react-feather'
import { useAddUserToken } from 'state/user/hooks'
import styled, { useTheme } from 'styled-components/macro'
import { CloseIcon, ThemedText } from 'theme'
import BlockedToken from './BlockedToken'
import { PaddedColumn } from './styleds'
import TokenImportCard from './TokenImportCard'
const Wrapper = styled.div`
position: relative;
width: 100%;
overflow: auto;
`
interface ImportProps {
tokens: Token[]
list?: TokenList
onBack?: () => void
onDismiss?: () => void
handleCurrencySelect?: (currency: Currency) => void
}
const formatAnalyticsEventProperties = (tokens: Token[]) => ({
token_symbols: tokens.map((token) => token?.symbol),
token_addresses: tokens.map((token) => token?.address),
token_chain_ids: tokens.map((token) => token?.chainId),
})
export function ImportToken(props: ImportProps) {
const { tokens, list, onBack, onDismiss, handleCurrencySelect } = props
const theme = useTheme()
const addToken = useAddUserToken()
const unsupportedTokens = useUnsupportedTokens()
const unsupportedSet = new Set(Object.keys(unsupportedTokens))
const intersection = new Set(tokens.filter((token) => unsupportedSet.has(token.address)))
if (intersection.size > 0) {
return <BlockedToken onBack={onBack} onDismiss={onDismiss} blockedTokens={Array.from(intersection)} />
}
return (
<Wrapper>
<PaddedColumn gap="14px" style={{ width: '100%', flex: '1 1' }}>
<RowBetween>
{onBack ? <ArrowLeft style={{ cursor: 'pointer' }} onClick={onBack} /> : <div />}
<ThemedText.DeprecatedMediumHeader>
<Plural value={tokens.length} _1="Import token" other="Import tokens" />
</ThemedText.DeprecatedMediumHeader>
{onDismiss ? <CloseIcon onClick={onDismiss} /> : <div />}
</RowBetween>
</PaddedColumn>
<SectionBreak />
<AutoColumn gap="md" style={{ marginBottom: '32px', padding: '1rem' }}>
<AutoColumn justify="center" style={{ textAlign: 'center', gap: '16px', padding: '1rem' }}>
<AlertCircle size={48} stroke={theme.deprecated_text2} strokeWidth={1} />
<ThemedText.DeprecatedBody fontWeight={400} fontSize={16}>
<Trans>
This token doesn&apos;t appear on the active token list(s). Make sure this is the token that you want to
trade.
</Trans>
</ThemedText.DeprecatedBody>
</AutoColumn>
{tokens.map((token) => (
<TokenImportCard token={token} list={list} key={'import' + token.address} />
))}
<TraceEvent
events={[Event.onClick]}
name={EventName.TOKEN_IMPORTED}
properties={formatAnalyticsEventProperties(tokens)}
element={ElementName.IMPORT_TOKEN_BUTTON}
>
<ButtonPrimary
altDisabledStyle={true}
$borderRadius="20px"
padding="10px 1rem"
onClick={() => {
tokens.map((token) => addToken(token))
handleCurrencySelect && handleCurrencySelect(tokens[0])
}}
className=".token-dismiss-button"
>
<Trans>Import</Trans>
</ButtonPrimary>
</TraceEvent>
</AutoColumn>
</Wrapper>
)
}

View File

@@ -1,92 +0,0 @@
import { Trans } from '@lingui/macro'
import { Token } from '@uniswap/sdk-core'
import { TokenList } from '@uniswap/token-lists'
import { RowBetween } from 'components/Row'
import { useState } from 'react'
import { ArrowLeft } from 'react-feather'
import { Text } from 'rebass'
import styled from 'styled-components/macro'
import { CloseIcon } from 'theme'
import { CurrencyModalView } from './CurrencySearchModal'
import { ManageLists } from './ManageLists'
import ManageTokens from './ManageTokens'
import { PaddedColumn, Separator } from './styleds'
const Wrapper = styled.div`
width: 100%;
position: relative;
display: flex;
flex-flow: column;
`
const ToggleWrapper = styled(RowBetween)`
background-color: ${({ theme }) => theme.deprecated_bg3};
border-radius: 12px;
padding: 6px;
`
const ToggleOption = styled.div<{ active?: boolean }>`
width: 48%;
padding: 10px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 12px;
font-weight: 600;
background-color: ${({ theme, active }) => (active ? theme.deprecated_bg1 : theme.deprecated_bg3)};
color: ${({ theme, active }) => (active ? theme.deprecated_text1 : theme.deprecated_text2)};
user-select: none;
:hover {
cursor: pointer;
opacity: 0.7;
}
`
export default function Manage({
onDismiss,
setModalView,
setImportList,
setImportToken,
setListUrl,
}: {
onDismiss: () => void
setModalView: (view: CurrencyModalView) => void
setImportToken: (token: Token) => void
setImportList: (list: TokenList) => void
setListUrl: (url: string) => void
}) {
// toggle between tokens and lists
const [showLists, setShowLists] = useState(true)
return (
<Wrapper>
<PaddedColumn>
<RowBetween>
<ArrowLeft style={{ cursor: 'pointer' }} onClick={() => setModalView(CurrencyModalView.search)} />
<Text fontWeight={500} fontSize={20}>
<Trans>Manage</Trans>
</Text>
<CloseIcon onClick={onDismiss} />
</RowBetween>
</PaddedColumn>
<Separator />
<PaddedColumn style={{ paddingBottom: 0 }}>
<ToggleWrapper>
<ToggleOption onClick={() => setShowLists(!showLists)} active={showLists}>
<Trans>Lists</Trans>
</ToggleOption>
<ToggleOption onClick={() => setShowLists(!showLists)} active={!showLists}>
<Trans>Tokens</Trans>
</ToggleOption>
</ToggleWrapper>
</PaddedColumn>
{showLists ? (
<ManageLists setModalView={setModalView} setImportList={setImportList} setListUrl={setListUrl} />
) : (
<ManageTokens setModalView={setModalView} setImportToken={setImportToken} />
)}
</Wrapper>
)
}

View File

@@ -1,415 +0,0 @@
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
import { TokenList } from '@uniswap/token-lists'
import { useWeb3React } from '@web3-react/core'
import { sendEvent } from 'components/analytics'
import Card from 'components/Card'
import { UNSUPPORTED_LIST_URLS } from 'constants/lists'
import { useListColor } from 'hooks/useColor'
import parseENSAddress from 'lib/utils/parseENSAddress'
import uriToHttp from 'lib/utils/uriToHttp'
import { ChangeEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { CheckCircle, Settings } from 'react-feather'
import { usePopper } from 'react-popper'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import styled, { useTheme } from 'styled-components/macro'
import { useFetchListCallback } from '../../hooks/useFetchListCallback'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import useToggle from '../../hooks/useToggle'
import { acceptListUpdate, disableList, enableList, removeList } from '../../state/lists/actions'
import { useActiveListUrls, useAllLists, useIsListActive } from '../../state/lists/hooks'
import { ExternalLink, IconWrapper, LinkStyledButton, ThemedText } from '../../theme'
import listVersionLabel from '../../utils/listVersionLabel'
import { ButtonEmpty, ButtonPrimary } from '../Button'
import Column, { AutoColumn } from '../Column'
import ListLogo from '../ListLogo'
import Row, { RowBetween, RowFixed } from '../Row'
import Toggle from '../Toggle'
import { CurrencyModalView } from './CurrencySearchModal'
import { PaddedColumn, SearchInput, Separator, SeparatorDark } from './styleds'
const Wrapper = styled(Column)`
flex: 1;
overflow-y: hidden;
`
const UnpaddedLinkStyledButton = styled(LinkStyledButton)`
padding: 0;
font-size: 1rem;
opacity: ${({ disabled }) => (disabled ? '0.4' : '1')};
`
const PopoverContainer = styled.div<{ show: boolean }>`
z-index: 100;
visibility: ${(props) => (props.show ? 'visible' : 'hidden')};
opacity: ${(props) => (props.show ? 1 : 0)};
transition: visibility 150ms linear, opacity 150ms linear;
background: ${({ theme }) => theme.deprecated_bg2};
border: 1px solid ${({ theme }) => theme.deprecated_bg3};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.01);
color: ${({ theme }) => theme.deprecated_text2};
border-radius: 0.5rem;
padding: 1rem;
display: grid;
grid-template-rows: 1fr;
grid-gap: 8px;
font-size: 1rem;
text-align: left;
`
const StyledMenu = styled.div`
display: flex;
justify-content: center;
align-items: center;
position: relative;
border: none;
`
const StyledTitleText = styled.div<{ active: boolean }>`
font-size: 16px;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 600;
color: ${({ theme, active }) => (active ? theme.deprecated_white : theme.deprecated_text2)};
`
const StyledListUrlText = styled(ThemedText.DeprecatedMain)<{ active: boolean }>`
font-size: 12px;
color: ${({ theme, active }) => (active ? theme.deprecated_white : theme.deprecated_text2)};
`
const RowWrapper = styled(Row)<{ bgColor: string; active: boolean; hasActiveTokens: boolean }>`
background-color: ${({ bgColor, active, theme }) => (active ? bgColor ?? 'transparent' : theme.deprecated_bg2)};
opacity: ${({ hasActiveTokens }) => (hasActiveTokens ? 1 : 0.4)};
transition: 200ms;
align-items: center;
padding: 1rem;
border-radius: 20px;
`
function listUrlRowHTMLId(listUrl: string) {
return `list-row-${listUrl.replace(/\./g, '-')}`
}
const ListRow = memo(function ListRow({ listUrl }: { listUrl: string }) {
const { chainId } = useWeb3React()
const listsByUrl = useAppSelector((state) => state.lists.byUrl)
const dispatch = useAppDispatch()
const { current: list, pendingUpdate: pending } = listsByUrl[listUrl]
const activeTokensOnThisChain = useMemo(() => {
if (!list || !chainId) {
return 0
}
return list.tokens.reduce((acc, cur) => (cur.chainId === chainId ? acc + 1 : acc), 0)
}, [chainId, list])
const theme = useTheme()
const listColor = useListColor(list?.logoURI)
const isActive = useIsListActive(listUrl)
const [open, toggle] = useToggle(false)
const node = useRef<HTMLDivElement>()
const [referenceElement, setReferenceElement] = useState<HTMLDivElement>()
const [popperElement, setPopperElement] = useState<HTMLDivElement>()
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: 'auto',
strategy: 'fixed',
modifiers: [{ name: 'offset', options: { offset: [8, 8] } }],
})
useOnClickOutside(node, open ? toggle : undefined)
const handleAcceptListUpdate = useCallback(() => {
if (!pending) return
sendEvent({
category: 'Lists',
action: 'Update List from List Select',
label: listUrl,
})
dispatch(acceptListUpdate(listUrl))
}, [dispatch, listUrl, pending])
const handleRemoveList = useCallback(() => {
sendEvent({
category: 'Lists',
action: 'Start Remove List',
label: listUrl,
})
if (window.prompt(t`Please confirm you would like to remove this list by typing REMOVE`) === `REMOVE`) {
sendEvent({
category: 'Lists',
action: 'Confirm Remove List',
label: listUrl,
})
dispatch(removeList(listUrl))
}
}, [dispatch, listUrl])
const handleEnableList = useCallback(() => {
sendEvent({
category: 'Lists',
action: 'Enable List',
label: listUrl,
})
dispatch(enableList(listUrl))
}, [dispatch, listUrl])
const handleDisableList = useCallback(() => {
sendEvent({
category: 'Lists',
action: 'Disable List',
label: listUrl,
})
dispatch(disableList(listUrl))
}, [dispatch, listUrl])
if (!list) return null
return (
<RowWrapper
active={isActive}
hasActiveTokens={activeTokensOnThisChain > 0}
bgColor={listColor}
key={listUrl}
id={listUrlRowHTMLId(listUrl)}
>
{list.logoURI ? (
<ListLogo size="40px" style={{ marginRight: '1rem' }} logoURI={list.logoURI} alt={`${list.name} list logo`} />
) : (
<div style={{ width: '24px', height: '24px', marginRight: '1rem' }} />
)}
<Column style={{ flex: '1' }}>
<Row>
<StyledTitleText active={isActive}>{list.name}</StyledTitleText>
</Row>
<RowFixed mt="4px">
<StyledListUrlText active={isActive} mr="6px">
<Trans>{activeTokensOnThisChain} tokens</Trans>
</StyledListUrlText>
<StyledMenu ref={node as any}>
<ButtonEmpty onClick={toggle} ref={setReferenceElement} padding="0">
<Settings stroke={isActive ? theme.deprecated_bg1 : theme.deprecated_text1} size={12} />
</ButtonEmpty>
{open && (
<PopoverContainer show={true} ref={setPopperElement as any} style={styles.popper} {...attributes.popper}>
<div>{list && listVersionLabel(list.version)}</div>
<SeparatorDark />
<ExternalLink href={`https://tokenlists.org/token-list?url=${listUrl}`}>
<Trans>View list</Trans>
</ExternalLink>
<UnpaddedLinkStyledButton onClick={handleRemoveList} disabled={Object.keys(listsByUrl).length === 1}>
<Trans>Remove list</Trans>
</UnpaddedLinkStyledButton>
{pending && (
<UnpaddedLinkStyledButton onClick={handleAcceptListUpdate}>
<Trans>Update list</Trans>
</UnpaddedLinkStyledButton>
)}
</PopoverContainer>
)}
</StyledMenu>
</RowFixed>
</Column>
<Toggle
isActive={isActive}
bgColor={listColor}
toggle={() => {
isActive ? handleDisableList() : handleEnableList()
}}
/>
</RowWrapper>
)
})
const ListContainer = styled.div`
padding: 1rem;
height: 100%;
overflow: auto;
flex: 1;
`
export function ManageLists({
setModalView,
setImportList,
setListUrl,
}: {
setModalView: (view: CurrencyModalView) => void
setImportList: (list: TokenList) => void
setListUrl: (url: string) => void
}) {
const { chainId } = useWeb3React()
const theme = useTheme()
const [listUrlInput, setListUrlInput] = useState<string>('')
const lists = useAllLists()
const tokenCountByListName = useMemo<Record<string, number>>(
() =>
Object.values(lists).reduce((acc, { current: list }) => {
if (!list) {
return acc
}
return {
...acc,
[list.name]: list.tokens.reduce((count: number, token) => (token.chainId === chainId ? count + 1 : count), 0),
}
}, {}),
[chainId, lists]
)
// sort by active but only if not visible
const activeListUrls = useActiveListUrls()
const handleInput = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setListUrlInput(e.target.value)
}, [])
const fetchList = useFetchListCallback()
const validUrl: boolean = useMemo(() => {
return uriToHttp(listUrlInput).length > 0 || Boolean(parseENSAddress(listUrlInput))
}, [listUrlInput])
const sortedLists = useMemo(() => {
const listUrls = Object.keys(lists)
return listUrls
.filter((listUrl) => {
// only show loaded lists, hide unsupported lists
return Boolean(lists[listUrl].current) && !Boolean(UNSUPPORTED_LIST_URLS.includes(listUrl))
})
.sort((listUrlA, listUrlB) => {
const { current: listA } = lists[listUrlA]
const { current: listB } = lists[listUrlB]
// first filter on active lists
if (activeListUrls?.includes(listUrlA) && !activeListUrls?.includes(listUrlB)) {
return -1
}
if (!activeListUrls?.includes(listUrlA) && activeListUrls?.includes(listUrlB)) {
return 1
}
if (listA && listB) {
if (tokenCountByListName[listA.name] > tokenCountByListName[listB.name]) {
return -1
}
if (tokenCountByListName[listA.name] < tokenCountByListName[listB.name]) {
return 1
}
return listA.name.toLowerCase() < listB.name.toLowerCase()
? -1
: listA.name.toLowerCase() === listB.name.toLowerCase()
? 0
: 1
}
if (listA) return -1
if (listB) return 1
return 0
})
}, [lists, activeListUrls, tokenCountByListName])
// temporary fetched list for import flow
const [tempList, setTempList] = useState<TokenList>()
const [addError, setAddError] = useState<string | undefined>()
useEffect(() => {
async function fetchTempList() {
fetchList(listUrlInput, false)
.then((list) => setTempList(list))
.catch(() => setAddError(t`Error importing list`))
}
// if valid url, fetch details for card
if (validUrl) {
fetchTempList()
} else {
setTempList(undefined)
listUrlInput !== '' && setAddError(t`Enter valid list location`)
}
// reset error
if (listUrlInput === '') {
setAddError(undefined)
}
}, [fetchList, listUrlInput, validUrl])
// check if list is already imported
const isImported = Object.keys(lists).includes(listUrlInput)
// set list values and have parent modal switch to import list view
const handleImport = useCallback(() => {
if (!tempList) return
setImportList(tempList)
setModalView(CurrencyModalView.importList)
setListUrl(listUrlInput)
}, [listUrlInput, setImportList, setListUrl, setModalView, tempList])
return (
<Wrapper>
<PaddedColumn gap="14px">
<Row>
<SearchInput
type="text"
id="list-add-input"
placeholder={t`https:// or ipfs:// or ENS name`}
value={listUrlInput}
onChange={handleInput}
/>
</Row>
{addError ? (
<ThemedText.DeprecatedError title={addError} style={{ textOverflow: 'ellipsis', overflow: 'hidden' }} error>
{addError}
</ThemedText.DeprecatedError>
) : null}
</PaddedColumn>
{tempList && (
<PaddedColumn style={{ paddingTop: 0 }}>
<Card backgroundColor={theme.deprecated_bg2} padding="12px 20px">
<RowBetween>
<RowFixed>
{tempList.logoURI && <ListLogo logoURI={tempList.logoURI} size="40px" />}
<AutoColumn gap="4px" style={{ marginLeft: '20px' }}>
<ThemedText.DeprecatedBody fontWeight={600}>{tempList.name}</ThemedText.DeprecatedBody>
<ThemedText.DeprecatedMain fontSize={'12px'}>
<Trans>{tempList.tokens.length} tokens</Trans>
</ThemedText.DeprecatedMain>
</AutoColumn>
</RowFixed>
{isImported ? (
<RowFixed>
<IconWrapper stroke={theme.deprecated_text2} size="16px" marginRight={'10px'}>
<CheckCircle />
</IconWrapper>
<ThemedText.DeprecatedBody color={theme.deprecated_text2}>
<Trans>Loaded</Trans>
</ThemedText.DeprecatedBody>
</RowFixed>
) : (
<ButtonPrimary
style={{ fontSize: '14px' }}
padding="6px 8px"
width="fit-content"
onClick={handleImport}
>
<Trans>Import</Trans>
</ButtonPrimary>
)}
</RowBetween>
</Card>
</PaddedColumn>
)}
<Separator />
<ListContainer>
<AutoColumn gap="md">
{sortedLists.map((listUrl) => (
<ListRow key={listUrl} listUrl={listUrl} />
))}
</AutoColumn>
</ListContainer>
</Wrapper>
)
}

View File

@@ -1,152 +0,0 @@
import { Trans } from '@lingui/macro'
import { Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import Card from 'components/Card'
import Column from 'components/Column'
import CurrencyLogo from 'components/CurrencyLogo'
import Row, { RowBetween, RowFixed } from 'components/Row'
import { useToken } from 'hooks/Tokens'
import { ChangeEvent, RefObject, useCallback, useMemo, useRef, useState } from 'react'
import { useRemoveUserAddedToken, useUserAddedTokens } from 'state/user/hooks'
import styled, { useTheme } from 'styled-components/macro'
import { ButtonText, ExternalLink, ExternalLinkIcon, ThemedText, TrashIcon } from 'theme'
import { isAddress } from 'utils'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
import { CurrencyModalView } from './CurrencySearchModal'
import ImportRow from './ImportRow'
import { PaddedColumn, SearchInput, Separator } from './styleds'
const Wrapper = styled.div`
width: 100%;
height: calc(100% - 60px);
position: relative;
padding-bottom: 80px;
`
const Footer = styled.div`
position: absolute;
bottom: 0;
width: 100%;
border-radius: 20px;
border-top-right-radius: 0;
border-top-left-radius: 0;
border-top: 1px solid ${({ theme }) => theme.deprecated_bg3};
padding: 20px;
text-align: center;
`
export default function ManageTokens({
setModalView,
setImportToken,
}: {
setModalView: (view: CurrencyModalView) => void
setImportToken: (token: Token) => void
}) {
const { chainId } = useWeb3React()
const [searchQuery, setSearchQuery] = useState<string>('')
const theme = useTheme()
// manage focus on modal show
const inputRef = useRef<HTMLInputElement>()
const handleInput = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const input = event.target.value
const checksummedInput = isAddress(input)
setSearchQuery(checksummedInput || input)
}, [])
// if they input an address, use it
const isAddressSearch = isAddress(searchQuery)
const searchToken = useToken(searchQuery)
// all tokens for local lisr
const userAddedTokens: Token[] = useUserAddedTokens()
const removeToken = useRemoveUserAddedToken()
const handleRemoveAll = useCallback(() => {
if (chainId && userAddedTokens) {
userAddedTokens.map((token) => {
return removeToken(chainId, token.address)
})
}
}, [removeToken, userAddedTokens, chainId])
const tokenList = useMemo(() => {
return (
chainId &&
userAddedTokens.map((token) => (
<RowBetween key={token.address} width="100%">
<RowFixed>
<CurrencyLogo currency={token} size={'20px'} />
<ExternalLink href={getExplorerLink(chainId, token.address, ExplorerDataType.ADDRESS)}>
<ThemedText.DeprecatedMain ml={'10px'} fontWeight={600}>
{token.symbol}
</ThemedText.DeprecatedMain>
</ExternalLink>
</RowFixed>
<RowFixed>
<TrashIcon onClick={() => removeToken(chainId, token.address)} />
<ExternalLinkIcon href={getExplorerLink(chainId, token.address, ExplorerDataType.ADDRESS)} />
</RowFixed>
</RowBetween>
))
)
}, [userAddedTokens, chainId, removeToken])
return (
<Wrapper>
<Column style={{ width: '100%', height: '100%', flex: '1 1' }}>
<PaddedColumn gap="14px">
<Row>
<SearchInput
type="text"
id="token-search-input"
placeholder={'0x0000'}
value={searchQuery}
autoComplete="off"
ref={inputRef as RefObject<HTMLInputElement>}
onChange={handleInput}
/>
</Row>
{searchQuery !== '' && !isAddressSearch && (
<ThemedText.DeprecatedError error={true}>
<Trans>Enter valid token address</Trans>
</ThemedText.DeprecatedError>
)}
{searchToken && (
<Card backgroundColor={theme.deprecated_bg2} padding="10px 0">
<ImportRow
token={searchToken}
showImportView={() => setModalView(CurrencyModalView.importToken)}
setImportToken={setImportToken}
style={{ height: 'fit-content' }}
/>
</Card>
)}
</PaddedColumn>
<Separator />
<PaddedColumn gap="lg" style={{ overflow: 'auto', marginBottom: '10px' }}>
<RowBetween>
<ThemedText.DeprecatedMain fontWeight={600}>
<Trans>{userAddedTokens?.length} Custom Tokens</Trans>
</ThemedText.DeprecatedMain>
{userAddedTokens.length > 0 && (
<ButtonText onClick={handleRemoveAll}>
<ThemedText.DeprecatedBlue>
<Trans>Clear all</Trans>
</ThemedText.DeprecatedBlue>
</ButtonText>
)}
</RowBetween>
{tokenList}
</PaddedColumn>
</Column>
<Footer>
<ThemedText.DeprecatedDarkGray>
<Trans>Tip: Custom tokens are stored locally in your browser</Trans>
</ThemedText.DeprecatedDarkGray>
</Footer>
</Wrapper>
)
}

View File

@@ -1,76 +0,0 @@
import { Trans } from '@lingui/macro'
import { Token } from '@uniswap/sdk-core'
import { TokenList } from '@uniswap/token-lists'
import { useWeb3React } from '@web3-react/core'
import Card from 'components/Card'
import { AutoColumn } from 'components/Column'
import CurrencyLogo from 'components/CurrencyLogo'
import ListLogo from 'components/ListLogo'
import { RowFixed } from 'components/Row'
import { transparentize } from 'polished'
import { AlertCircle } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
import { ExternalLink, ThemedText } from 'theme'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
const WarningWrapper = styled(Card)<{ highWarning: boolean }>`
background-color: ${({ theme, highWarning }) =>
highWarning ? transparentize(0.8, theme.deprecated_red1) : transparentize(0.8, theme.deprecated_yellow2)};
width: fit-content;
`
const AddressText = styled(ThemedText.DeprecatedBlue)`
font-size: 12px;
word-break: break-all;
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
font-size: 10px;
`}
`
interface TokenImportCardProps {
list?: TokenList
token: Token
}
const TokenImportCard = ({ list, token }: TokenImportCardProps) => {
const theme = useTheme()
const { chainId } = useWeb3React()
return (
<Card backgroundColor={theme.deprecated_bg2} padding="2rem">
<AutoColumn gap="10px" justify="center">
<CurrencyLogo currency={token} size={'32px'} />
<AutoColumn gap="4px" justify="center">
<ThemedText.DeprecatedBody ml="8px" mr="8px" fontWeight={500} fontSize={20}>
{token.symbol}
</ThemedText.DeprecatedBody>
<ThemedText.DeprecatedDarkGray fontWeight={400} fontSize={14}>
{token.name}
</ThemedText.DeprecatedDarkGray>
</AutoColumn>
{chainId && (
<ExternalLink href={getExplorerLink(chainId, token.address, ExplorerDataType.ADDRESS)}>
<AddressText fontSize={12}>{token.address}</AddressText>
</ExternalLink>
)}
{list !== undefined ? (
<RowFixed>
{list.logoURI && <ListLogo logoURI={list.logoURI} size="16px" />}
<ThemedText.DeprecatedSmall ml="6px" fontSize={14} color={theme.deprecated_text3}>
<Trans>via {list.name} token list</Trans>
</ThemedText.DeprecatedSmall>
</RowFixed>
) : (
<WarningWrapper $borderRadius="4px" padding="4px" highWarning={true}>
<RowFixed>
<AlertCircle stroke={theme.deprecated_red1} size="10px" />
<ThemedText.DeprecatedBody color={theme.deprecated_red1} ml="4px" fontSize="10px" fontWeight={500}>
<Trans>Unknown Source</Trans>
</ThemedText.DeprecatedBody>
</RowFixed>
</WarningWrapper>
)}
</AutoColumn>
</Card>
)
}
export default TokenImportCard

View File

@@ -22,7 +22,7 @@ export const PaddedColumn = styled(AutoColumn)`
padding: 20px;
`
export const MenuItem = styled(RowBetween)<{ redesignFlag?: boolean; dim?: boolean }>`
export const MenuItem = styled(RowBetween)<{ dim?: boolean }>`
padding: 4px 20px;
height: 56px;
display: grid;
@@ -31,13 +31,12 @@ export const MenuItem = styled(RowBetween)<{ redesignFlag?: boolean; dim?: boole
cursor: ${({ disabled }) => !disabled && 'pointer'};
pointer-events: ${({ disabled }) => disabled && 'none'};
:hover {
background-color: ${({ theme, disabled, redesignFlag }) =>
(redesignFlag && theme.hoverDefault) || (!disabled && theme.deprecated_bg2)};
background-color: ${({ theme }) => theme.hoverDefault};
}
opacity: ${({ disabled, selected, dim }) => (dim || disabled || selected ? 0.4 : 1)};
`
export const SearchInput = styled.input<{ redesignFlag?: boolean }>`
export const SearchInput = styled.input`
background: no-repeat scroll 7px 7px;
background-image: url(${searchIcon});
background-size: 20px 20px;
@@ -46,37 +45,36 @@ export const SearchInput = styled.input<{ redesignFlag?: boolean }>`
display: flex;
padding: 16px;
padding-left: 40px;
height: ${({ redesignFlag }) => redesignFlag && '40px'};
height: 40px;
align-items: center;
width: 100%;
white-space: nowrap;
background-color: ${({ theme, redesignFlag }) => redesignFlag && theme.backgroundModule};
background-color: ${({ theme }) => theme.backgroundModule};
border: none;
outline: none;
border-radius: ${({ redesignFlag }) => (redesignFlag ? '12px' : '20px')};
border-radius: 12px;
color: ${({ theme }) => theme.deprecated_text1};
border-style: solid;
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundOutline : theme.deprecated_bg3)};
border: 1px solid ${({ theme }) => theme.backgroundOutline};
-webkit-appearance: none;
font-size: ${({ redesignFlag }) => (redesignFlag ? '16px' : '18px')};
font-size: 16px;
::placeholder {
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textTertiary : theme.deprecated_text3)};
font-size: ${({ redesignFlag }) => redesignFlag && '16px'};
color: ${({ theme }) => theme.textTertiary};
font-size: 16px;
}
transition: border 100ms;
:focus {
border: 1px solid
${({ theme, redesignFlag }) => (redesignFlag ? theme.accentActiveSoft : theme.deprecated_primary1)};
background-color: ${({ theme, redesignFlag }) => redesignFlag && theme.backgroundSurface};
border: 1px solid ${({ theme }) => theme.accentActiveSoft};
background-color: ${({ theme }) => theme.backgroundSurface};
outline: none;
}
`
export const Separator = styled.div<{ redesignFlag?: boolean }>`
export const Separator = styled.div`
width: 100%;
height: 1px;
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundOutline : theme.deprecated_bg2)};
background-color: ${({ theme }) => theme.backgroundOutline};
`
export const SeparatorDark = styled.div`

View File

@@ -3,7 +3,6 @@ import { t, Trans } from '@lingui/macro'
import { Percent } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { sendEvent } from 'components/analytics'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { isSupportedChainId } from 'lib/hooks/routing/clientSideSmartOrderRouter'
import { useRef, useState } from 'react'
import { Settings, X } from 'react-feather'
@@ -23,16 +22,16 @@ import { RowBetween, RowFixed } from '../Row'
import Toggle from '../Toggle'
import TransactionSettings from '../TransactionSettings'
const StyledMenuIcon = styled(Settings)<{ redesignFlag: boolean }>`
const StyledMenuIcon = styled(Settings)`
height: 20px;
width: 20px;
> * {
stroke: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textSecondary : theme.deprecated_text1)};
stroke: ${({ theme }) => theme.textSecondary};
}
`
const StyledCloseIcon = styled(X)<{ redesignFlag: boolean }>`
const StyledCloseIcon = styled(X)`
height: 20px;
width: 20px;
:hover {
@@ -40,7 +39,7 @@ const StyledCloseIcon = styled(X)<{ redesignFlag: boolean }>`
}
> * {
stroke: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textSecondary : theme.deprecated_text1)};
stroke: ${({ theme }) => theme.textSecondary};
}
`
@@ -83,10 +82,10 @@ const StyledMenu = styled.div`
text-align: left;
`
const MenuFlyout = styled.span<{ redesignFlag: boolean }>`
const MenuFlyout = styled.span`
min-width: 20.125rem;
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundSurface : theme.deprecated_bg2)};
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundOutline : theme.deprecated_bg3)};
background-color: ${({ theme }) => theme.backgroundSurface};
border: 1px solid ${({ theme }) => theme.backgroundOutline};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 12px;
@@ -97,7 +96,7 @@ const MenuFlyout = styled.span<{ redesignFlag: boolean }>`
top: 2rem;
right: 0rem;
z-index: 100;
color: ${({ theme, redesignFlag }) => redesignFlag && theme.textPrimary};
color: ${({ theme }) => theme.textPrimary};
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
min-width: 18.125rem;
@@ -123,8 +122,6 @@ const ModalContentWrapper = styled.div`
export default function SettingsTab({ placeholderSlippage }: { placeholderSlippage: Percent }) {
const { chainId } = useWeb3React()
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const node = useRef<HTMLDivElement>()
const open = useModalIsOpen(ApplicationModal.SETTINGS)
@@ -152,7 +149,7 @@ export default function SettingsTab({ placeholderSlippage }: { placeholderSlippa
<Text fontWeight={500} fontSize={20}>
<Trans>Are you sure?</Trans>
</Text>
<StyledCloseIcon onClick={() => setShowConfirmation(false)} redesignFlag={redesignFlagEnabled} />
<StyledCloseIcon onClick={() => setShowConfirmation(false)} />
</RowBetween>
<Break />
<AutoColumn gap="lg" style={{ padding: '0 2rem' }}>
@@ -190,7 +187,7 @@ export default function SettingsTab({ placeholderSlippage }: { placeholderSlippa
id="open-settings-dialog-button"
aria-label={t`Transaction Settings`}
>
<StyledMenuIcon redesignFlag={redesignFlagEnabled} />
<StyledMenuIcon />
{expertMode ? (
<EmojiWrapper>
<span role="img" aria-label="wizard-icon">
@@ -200,10 +197,10 @@ export default function SettingsTab({ placeholderSlippage }: { placeholderSlippa
) : null}
</StyledMenuButton>
{open && (
<MenuFlyout redesignFlag={redesignFlagEnabled}>
<MenuFlyout>
<AutoColumn gap="md" style={{ padding: '1rem' }}>
<Text fontWeight={600} fontSize={14}>
<Trans>{redesignFlagEnabled ? 'Settings' : 'Transaction Settings'}</Trans>
<Trans>Settings</Trans>
</Text>
<TransactionSettings placeholderSlippage={placeholderSlippage} />
<Text fontWeight={600} fontSize={14}>

View File

@@ -11,10 +11,10 @@ exports[`ResizableTextArea renders correctly 1`] = `
flex: 1 1 auto;
width: 0;
resize: none;
background-color: #F7F8FA;
background-color: #F5F6FC;
-webkit-transition: color 300ms step-start;
transition: color 300ms step-start;
color: #000000;
color: #0D111C;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
@@ -34,19 +34,19 @@ exports[`ResizableTextArea renders correctly 1`] = `
}
.c0::-webkit-input-placeholder {
color: #C3C5CB;
color: #98A1C0;
}
.c0::-moz-placeholder {
color: #C3C5CB;
color: #98A1C0;
}
.c0:-ms-input-placeholder {
color: #C3C5CB;
color: #98A1C0;
}
.c0::placeholder {
color: #C3C5CB;
color: #98A1C0;
}
<textarea
@@ -74,10 +74,10 @@ exports[`TextInput renders correctly 1`] = `
-ms-flex: 1 1 auto;
flex: 1 1 auto;
width: 0;
background-color: #F7F8FA;
background-color: #F5F6FC;
-webkit-transition: color 300ms step-start;
transition: color 300ms step-start;
color: #000000;
color: #0D111C;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
@@ -96,19 +96,19 @@ exports[`TextInput renders correctly 1`] = `
}
.c0::-webkit-input-placeholder {
color: #C3C5CB;
color: #98A1C0;
}
.c0::-moz-placeholder {
color: #C3C5CB;
color: #98A1C0;
}
.c0:-ms-input-placeholder {
color: #C3C5CB;
color: #98A1C0;
}
.c0::placeholder {
color: #C3C5CB;
color: #98A1C0;
}
<div

View File

@@ -1,23 +1,16 @@
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { darken } from 'polished'
import { useState } from 'react'
import styled, { keyframes } from 'styled-components/macro'
const Wrapper = styled.button<{ isActive?: boolean; activeElement?: boolean; redesignFlag: boolean }>`
const Wrapper = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
align-items: center;
background: ${({ isActive, theme, redesignFlag }) =>
redesignFlag && isActive
? theme.accentActionSoft
: redesignFlag && !isActive
? 'transparent'
: theme.deprecated_bg1};
border: ${({ redesignFlag, theme, isActive }) =>
redesignFlag && !isActive ? `1px solid ${theme.backgroundOutline}` : 'none'};
background: ${({ isActive, theme }) => (isActive ? theme.accentActionSoft : 'transparent')};
border: ${({ theme, isActive }) => (isActive ? 'none' : `1px solid ${theme.backgroundOutline}`)};
border-radius: 20px;
cursor: pointer;
display: flex;
outline: none;
padding: ${({ redesignFlag }) => (redesignFlag ? '4px' : '0.4rem 0.4rem')};
padding: 4px;
width: fit-content;
`
@@ -64,8 +57,8 @@ const ToggleElement = styled.span<{ isActive?: boolean; bgColor?: string; isInit
:hover {
${({ bgColor, theme, isActive }) => ToggleElementHoverStyle(!!bgColor, theme, isActive)}
}
margin-left: ${({ isActive }) => (isActive ? '2.2em' : '0em')};
margin-right: ${({ isActive }) => (!isActive ? '2.2em' : '0em')};
margin-left: ${({ isActive }) => isActive && '2.2em'};
margin-right: ${({ isActive }) => !isActive && '2.2em'};
width: 24px;
`
@@ -78,8 +71,6 @@ interface ToggleProps {
export default function Toggle({ id, bgColor, isActive, toggle }: ToggleProps) {
const [isInitialToggleLoad, setIsInitialToggleLoad] = useState(true)
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const switchToggle = () => {
toggle()
@@ -87,7 +78,7 @@ export default function Toggle({ id, bgColor, isActive, toggle }: ToggleProps) {
}
return (
<Wrapper id={id} isActive={isActive} onClick={switchToggle} redesignFlag={redesignFlagEnabled}>
<Wrapper id={id} isActive={isActive} onClick={switchToggle}>
<ToggleElement isActive={isActive} bgColor={bgColor} isInitialToggleLoad={isInitialToggleLoad} />
</Wrapper>
)

View File

@@ -1,5 +1,5 @@
import { ReactComponent as Verified } from 'assets/svg/verified.svg'
import { Warning } from 'constants/tokenSafety'
import { Warning, WARNING_LEVEL } from 'constants/tokenSafety'
import { AlertTriangle } from 'react-feather'
import styled from 'styled-components/macro'
const VerifiedContainer = styled.div`
@@ -8,16 +8,17 @@ const VerifiedContainer = styled.div`
justify-content: center;
`
export const VerifiedIcon = styled(Verified)<{ size?: string }>`
export const WarningIcon = styled(AlertTriangle)<{ size?: string }>`
width: ${({ size }) => size ?? '1em'};
height: ${({ size }) => size ?? '1em'};
color: ${({ theme }) => theme.textTertiary};
`
export default function TokenSafetyIcon({ warning }: { warning: Warning | null }) {
if (warning) return null
if (warning?.level !== WARNING_LEVEL.UNKNOWN) return null
return (
<VerifiedContainer>
<VerifiedIcon />
<WarningIcon />
</VerifiedContainer>
)
}

View File

@@ -9,7 +9,7 @@ import { Color } from 'theme/styled'
const Label = styled.div<{ color: Color }>`
width: 100%;
padding: 12px 20px;
padding: 12px 20px 16px;
background-color: ${({ color }) => color + '1F'};
border-radius: 16px;
color: ${({ color }) => color};
@@ -31,9 +31,15 @@ const Title = styled(Text)`
const DetailsRow = styled.div`
margin-top: 8px;
font-size: 12px;
line-height: 16px;
color: ${({ theme }) => theme.textSecondary};
`
const StyledLink = styled(ExternalLink)`
color: ${({ theme }) => theme.textSecondary};
font-weight: 700;
`
type TokenWarningMessageProps = {
warning: Warning
tokenAddress: string
@@ -56,9 +62,9 @@ export default function TokenWarningMessage({ warning, tokenAddress }: TokenWarn
{description}
{Boolean(description) && ' '}
{tokenAddress && (
<ExternalLink href={TOKEN_SAFETY_ARTICLE}>
<StyledLink href={TOKEN_SAFETY_ARTICLE}>
<Trans>Learn more</Trans>
</ExternalLink>
</StyledLink>
)}
</DetailsRow>
</Label>

View File

@@ -244,6 +244,11 @@ export default function TokenSafety({
}
const { heading, description } = getWarningCopy(displayWarning, plural)
const learnMoreUrl = (
<StyledExternalLink href={TOKEN_SAFETY_ARTICLE}>
<Trans>Learn more</Trans>
</StyledExternalLink>
)
return (
displayWarning && (
@@ -255,13 +260,9 @@ export default function TokenSafety({
<ShortColumn>
<SafetyLabel warning={displayWarning} />
</ShortColumn>
<ShortColumn>{heading && <InfoText fontSize="20px">{heading}</InfoText>}</ShortColumn>
<ShortColumn>
<InfoText>
{description}{' '}
<StyledExternalLink href={TOKEN_SAFETY_ARTICLE}>
<Trans>Learn more</Trans>
</StyledExternalLink>
{heading} {description} {learnMoreUrl}
</InfoText>
</ShortColumn>
<LinkColumn>{urls}</LinkColumn>

View File

@@ -1,22 +0,0 @@
import { Token } from '@uniswap/sdk-core'
import { ImportToken } from 'components/SearchModal/ImportToken'
import Modal from '../Modal'
export default function TokenWarningModal({
isOpen,
tokens,
onConfirm,
onDismiss,
}: {
isOpen: boolean
tokens: Token[]
onConfirm: () => void
onDismiss: () => void
}) {
return (
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={100}>
<ImportToken tokens={tokens} handleCurrencySelect={onConfirm} />
</Modal>
)
}

View File

@@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'
import { darken } from 'polished'
import { useState } from 'react'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { textFadeIn } from 'theme/animations'
import Resource from './Resource'
@@ -24,8 +25,8 @@ const TokenDescriptionContainer = styled.div`
const TruncateDescriptionButton = styled.div`
color: ${({ theme }) => theme.textSecondary};
font-weight: 400;
font-size: 14px;
padding-top: 14px;
font-size: 0.85em;
padding-top: 0.5em;
&:hover,
&:focus {
@@ -52,9 +53,8 @@ export const AboutContainer = styled.div`
padding: 24px 0px;
${textFadeIn}
`
export const AboutHeader = styled.span`
font-size: 28px;
line-height: 36px;
export const AboutHeader = styled(ThemedText.MediumHeader)`
font-size: 28px !important;
`
export const ResourcesContainer = styled.div`
@@ -90,13 +90,17 @@ export function AboutSection({ address, description, homepageUrl, twitterName }:
{tokenDescription}
{shouldTruncate && (
<TruncateDescriptionButton onClick={() => setIsDescriptionTruncated(!isDescriptionTruncated)}>
{isDescriptionTruncated ? <Trans>Read more</Trans> : <Trans>Hide</Trans>}
{isDescriptionTruncated ? <Trans>Show more</Trans> : <Trans>Hide</Trans>}
</TruncateDescriptionButton>
)}
</TokenDescriptionContainer>
<br />
<ThemedText.SubHeaderSmall>
<Trans>Links</Trans>
</ThemedText.SubHeaderSmall>
<ResourcesContainer>
<Resource name={'Etherscan'} link={`https://etherscan.io/address/${address}`} />
<Resource name={'Protocol info'} link={`https://info.uniswap.org/#/tokens/${address}`} />
<Resource name={'More analytics'} link={`https://info.uniswap.org/#/tokens/${address}`} />
{homepageUrl && <Resource name={'Website'} link={homepageUrl} />}
{twitterName && <Resource name={'Twitter'} link={`https://twitter.com/${twitterName}`} />}
</ResourcesContainer>

View File

@@ -1,15 +1,14 @@
import { Trans } from '@lingui/macro'
import styled from 'styled-components/macro'
import { CopyContractAddress } from 'theme'
import { CopyContractAddress, ThemedText } from 'theme'
export const ContractAddressSection = styled.div`
display: flex;
flex-direction: column;
color: ${({ theme }) => theme.textSecondary};
font-weight: 600;
font-size: 14px;
font-size: 0.9em;
gap: 4px;
padding: 36px 0px;
padding: 4px 0px;
`
const ContractAddress = styled.button`
@@ -21,13 +20,14 @@ const ContractAddress = styled.button`
border: none;
min-height: 38px;
padding: 0px;
cursor: pointer;
`
export default function AddressSection({ address }: { address: string }) {
return (
<ContractAddressSection>
<Trans>Contract address</Trans>
<ThemedText.SubHeaderSmall>
<Trans>Contract address</Trans>
</ThemedText.SubHeaderSmall>
<ContractAddress>
<CopyContractAddress address={address} />
</ContractAddress>

View File

@@ -1,83 +1,96 @@
import { Trans } from '@lingui/macro'
import { useToken } from 'hooks/Tokens'
import { useNetworkTokenBalances } from 'hooks/useNetworkTokenBalances'
import { AlertTriangle } from 'react-feather'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import CurrencyLogo from 'components/CurrencyLogo'
import { NATIVE_CHAIN_ID } from 'constants/tokens'
import { CHAIN_ID_TO_BACKEND_NAME } from 'graphql/data/util'
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
import { formatToDecimal } from 'lib/utils/analytics'
import { useMemo } from 'react'
import styled from 'styled-components/macro'
import { StyledInternalLink } from 'theme'
import { currencyAmountToPreciseFloat, formatDollar } from 'utils/formatNumbers'
const BalancesCard = styled.div`
width: 100%;
height: fit-content;
color: ${({ theme }) => theme.textPrimary};
font-size: 12px;
line-height: 16px;
padding: 20px;
box-shadow: ${({ theme }) => theme.shallowShadow};
background-color: ${({ theme }) => theme.backgroundSurface};
border: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
border-radius: 16px;
`
const ErrorState = styled.div`
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 TotalBalanceSection = styled.div`
color: ${({ theme }) => theme.textPrimary};
display: none;
font-size: 12px;
height: fit-content;
line-height: 16px;
padding: 20px;
width: 100%;
// 768 hardcoded to match NFT-redesign navbar breakpoints
// src/nft/css/sprinkles.css.ts
// change to match theme breakpoints when this navbar is updated
@media screen and (min-width: 768px) {
display: flex;
}
`
const TotalBalance = styled.div`
const BalanceSection = styled.div`
height: fit-content;
width: 100%;
`
const BalanceRow = styled.div`
align-items: center;
display: flex;
justify-content: space-between;
flex-direction: row;
font-size: 20px;
justify-content: space-between;
line-height: 28px;
margin-top: 12px;
align-items: center;
`
const TotalBalanceItem = styled.div`
const BalanceItem = styled.div`
display: flex;
`
export default function BalanceSummary({
address,
balance,
balanceUsd,
}: {
address: string
balance?: number
balanceUsd?: number
}) {
const token = useToken(address)
const { loading, error } = useNetworkTokenBalances({ address })
const BalanceLink = styled(StyledInternalLink)`
color: unset;
`
if (loading || (!error && !balance && !balanceUsd)) return null
export function useFormatBalance(balance: CurrencyAmount<Currency> | undefined) {
return useMemo(
() => (balance ? formatToDecimal(balance, Math.min(balance.currency.decimals, 2)) : undefined),
[balance]
)
}
export function useFormatUsdValue(usdValue: CurrencyAmount<Token> | null) {
return useMemo(() => {
const float = usdValue ? currencyAmountToPreciseFloat(usdValue) : undefined
if (!float) return undefined
return formatDollar({ num: float, isPrice: true })
}, [usdValue])
}
export default function BalanceSummary({ token }: { token: Currency }) {
const { account } = useWeb3React()
const balance = useCurrencyBalance(account, token)
const formattedBalance = useFormatBalance(balance)
const usdValue = useStablecoinValue(balance)
const formattedUsdValue = useFormatUsdValue(usdValue)
const chain = CHAIN_ID_TO_BACKEND_NAME[token.chainId].toLowerCase()
if (!account || !balance) return null
return (
<BalancesCard>
{error ? (
<ErrorState>
<AlertTriangle size={24} />
<ErrorText>
<Trans>There was an error loading your {token?.symbol} balance</Trans>
</ErrorText>
</ErrorState>
) : (
<>
<TotalBalanceSection>
Your balance
<TotalBalance>
<TotalBalanceItem>{`${balance} ${token?.symbol}`}</TotalBalanceItem>
<TotalBalanceItem>{`$${balanceUsd}`}</TotalBalanceItem>
</TotalBalance>
</TotalBalanceSection>
</>
)}
<BalanceSection>
<Trans>Your balance</Trans>
<BalanceLink to={`/tokens/${chain}/${token.isNative ? NATIVE_CHAIN_ID : token.address}`}>
<BalanceRow>
<BalanceItem>
<CurrencyLogo currency={token} />
&nbsp;{formattedBalance} {token.symbol}
</BalanceItem>
<BalanceItem>{formattedUsdValue}</BalanceItem>
</BalanceRow>
</BalanceLink>
</BalanceSection>
</BalancesCard>
)
}

View File

@@ -1,118 +1,77 @@
import { Trans } from '@lingui/macro'
import { NativeCurrency, Token } from '@uniswap/sdk-core'
import { ParentSize } from '@visx/responsive'
import CurrencyLogo from 'components/CurrencyLogo'
import { VerifiedIcon } from 'components/TokenSafety/TokenSafetyIcon'
import { getChainInfo } from 'constants/chainInfo'
import { checkWarning } from 'constants/tokenSafety'
import { FavoriteTokensVariant, useFavoriteTokensFlag } from 'featureFlags/flags/favoriteTokens'
import { SingleTokenData, useTokenPricesCached } from 'graphql/data/Token'
import { TopToken } from 'graphql/data/TopTokens'
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
import useCurrencyLogoURIs, { getTokenLogoURI } from 'lib/hooks/useCurrencyLogoURIs'
import styled from 'styled-components/macro'
import { textFadeIn } from 'theme/animations'
import { isAddress } from 'utils'
import { ChartContainer, LoadingChart } from 'components/Tokens/TokenDetails/Skeleton'
import { TokenPriceQuery, tokenPriceQuery } from 'graphql/data/TokenPrice'
import { isPricePoint, PricePoint } from 'graphql/data/util'
import { TimePeriod } from 'graphql/data/util'
import { useAtomValue } from 'jotai/utils'
import { startTransition, Suspense, useMemo, useState } from 'react'
import { PreloadedQuery, usePreloadedQuery } from 'react-relay'
import { useIsFavorited, useToggleFavorite } from '../state'
import { ClickFavorited, FavoriteIcon, L2NetworkLogo, LogoContainer } from '../TokenTable/TokenRow'
import { filterTimeAtom } from '../state'
import PriceChart from './PriceChart'
import ShareButton from './ShareButton'
import TimePeriodSelector from './TimeSelector'
export const ChartHeader = styled.div`
width: 100%;
display: flex;
flex-direction: column;
color: ${({ theme }) => theme.textPrimary};
gap: 4px;
margin-bottom: 24px;
`
export const TokenInfoContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
`
export const ChartContainer = styled.div`
display: flex;
height: 436px;
align-items: center;
`
export const TokenNameCell = styled.div`
display: flex;
gap: 8px;
font-size: 20px;
line-height: 28px;
align-items: center;
${textFadeIn}
`
const TokenSymbol = styled.span`
text-transform: uppercase;
color: ${({ theme }) => theme.textSecondary};
`
const TokenActions = styled.div`
display: flex;
gap: 16px;
color: ${({ theme }) => theme.textSecondary};
`
function usePreloadedTokenPriceQuery(priceQueryReference: PreloadedQuery<TokenPriceQuery>): PricePoint[] | undefined {
const queryData = usePreloadedQuery(tokenPriceQuery, priceQueryReference)
export function useTokenLogoURI(
token: NonNullable<SingleTokenData> | NonNullable<TopToken>,
nativeCurrency?: Token | NativeCurrency
) {
const checksummedAddress = isAddress(token.address)
const chainId = CHAIN_NAME_TO_CHAIN_ID[token.chain]
return (
useCurrencyLogoURIs(nativeCurrency)[0] ??
(checksummedAddress && getTokenLogoURI(checksummedAddress, chainId)) ??
token.project?.logoUrl
)
// Appends the current price to the end of the priceHistory array
const priceHistory = useMemo(() => {
const market = queryData.tokens?.[0]?.market
const priceHistory = market?.priceHistory?.filter(isPricePoint)
const currentPrice = market?.price?.value
if (Array.isArray(priceHistory) && currentPrice !== undefined) {
const timestamp = Date.now() / 1000
return [...priceHistory, { timestamp, value: currentPrice }]
}
return priceHistory
}, [queryData])
return priceHistory
}
export default function ChartSection({
token,
nativeCurrency,
priceQueryReference,
refetchTokenPrices,
}: {
token: NonNullable<SingleTokenData>
nativeCurrency?: Token | NativeCurrency
priceQueryReference: PreloadedQuery<TokenPriceQuery> | null | undefined
refetchTokenPrices: RefetchPricesFunction
}) {
const isFavorited = useIsFavorited(token.address)
const toggleFavorite = useToggleFavorite(token.address)
const chainId = CHAIN_NAME_TO_CHAIN_ID[token.chain]
const L2Icon = getChainInfo(chainId).circleLogoUrl
const warning = checkWarning(token.address ?? '')
const { prices } = useTokenPricesCached(token)
const logoSrc = useTokenLogoURI(token, nativeCurrency)
if (!priceQueryReference) {
return <LoadingChart />
}
return (
<ChartHeader>
<TokenInfoContainer>
<TokenNameCell>
<LogoContainer>
<CurrencyLogo src={logoSrc} size={'32px'} symbol={nativeCurrency?.symbol ?? token.symbol} />
<L2NetworkLogo networkUrl={L2Icon} size={'16px'} />
</LogoContainer>
{nativeCurrency?.name ?? token.name ?? <Trans>Name not found</Trans>}
<TokenSymbol>{nativeCurrency?.symbol ?? token.symbol ?? <Trans>Symbol not found</Trans>}</TokenSymbol>
{!warning && <VerifiedIcon size="20px" />}
</TokenNameCell>
<TokenActions>
{token.name && token.symbol && token.address && (
<ShareButton tokenName={token.name} tokenSymbol={token.symbol} tokenAddress={token.address} />
)}
{useFavoriteTokensFlag() === FavoriteTokensVariant.Enabled && (
<ClickFavorited onClick={toggleFavorite}>
<FavoriteIcon isFavorited={isFavorited} />
</ClickFavorited>
)}
</TokenActions>
</TokenInfoContainer>
<Suspense fallback={<LoadingChart />}>
<ChartContainer>
<ParentSize>
{({ width, height }) => prices && <PriceChart prices={prices} width={width} height={height} />}
</ParentSize>
<Chart priceQueryReference={priceQueryReference} refetchTokenPrices={refetchTokenPrices} />
</ChartContainer>
</ChartHeader>
</Suspense>
)
}
export type RefetchPricesFunction = (t: TimePeriod) => void
function Chart({
priceQueryReference,
refetchTokenPrices,
}: {
priceQueryReference: PreloadedQuery<TokenPriceQuery>
refetchTokenPrices: RefetchPricesFunction
}) {
const prices = usePreloadedTokenPriceQuery(priceQueryReference)
// Initializes time period to global & maintain separate time period for subsequent changes
const [timePeriod, setTimePeriod] = useState(useAtomValue(filterTimeAtom))
return (
<ChartContainer>
<ParentSize>
{({ width }) => <PriceChart prices={prices ?? null} width={width} height={436} timePeriod={timePeriod} />}
</ParentSize>
<TimePeriodSelector
currentTimePeriod={timePeriod}
onTimeChange={(t: TimePeriod) => {
startTransition(() => refetchTokenPrices(t))
setTimePeriod(t)
}}
/>
</ChartContainer>
)
}

View File

@@ -1,199 +0,0 @@
import { Trans } from '@lingui/macro'
import { useToken } from 'hooks/Tokens'
import { useNetworkTokenBalances } from 'hooks/useNetworkTokenBalances'
import { useState } from 'react'
import { AlertTriangle } from 'react-feather'
import { Link } from 'react-router-dom'
import styled from 'styled-components/macro'
import { SMALLEST_MOBILE_MEDIA_BREAKPOINT } from '../constants'
import { LoadingBubble } from '../loading'
const PLACEHOLDER_NAV_FOOTER_HEIGHT = '56px'
const BalanceFooter = styled.div`
height: fit-content;
border: 1px solid ${({ theme }) => theme.backgroundOutline};
background-color: ${({ theme }) => theme.backgroundSurface};
border-radius: 20px 20px 0px 0px;
padding: 12px 16px;
font-weight: 500;
font-size: 14px;
line-height: 20px;
width: 100%;
color: ${({ theme }) => theme.textSecondary};
position: fixed;
left: 0;
bottom: ${PLACEHOLDER_NAV_FOOTER_HEIGHT};
display: flex;
flex-direction: column;
align-content: center;
`
const BalanceValue = styled.div`
font-size: 20px;
line-height: 28px;
display: flex;
gap: 8px;
`
const BalanceTotal = styled.div`
display: flex;
flex-wrap: wrap;
gap: 8px;
color: ${({ theme }) => theme.textPrimary};
`
const BalanceInfo = styled.div`
display: flex;
justify-content: flex-start;
flex-direction: column;
`
const FakeFooterNavBar = styled.div`
position: fixed;
bottom: 0px;
left: 0px;
background-color: ${({ theme }) => theme.backgroundBackdrop};
height: ${PLACEHOLDER_NAV_FOOTER_HEIGHT};
width: 100%;
align-items: flex-end;
padding: 20px 8px;
font-size: 10px;
`
const FiatValue = styled.span`
display: flex;
align-self: flex-end;
font-size: 12px;
line-height: 24px;
@media only screen and (max-width: ${SMALLEST_MOBILE_MEDIA_BREAKPOINT}) {
line-height: 16px;
}
`
const NetworkBalancesSection = styled.div`
height: fit-content;
border-top: 1px solid ${({ theme }) => theme.backgroundOutline};
display: flex;
flex-direction: column;
padding: 16px 0px 8px 0px;
margin-top: 16px;
color: ${({ theme }) => theme.textPrimary};
`
const NetworkBalancesLabel = styled.span`
color: ${({ theme }) => theme.textSecondary};
`
const SwapButton = styled.button`
background-color: ${({ theme }) => theme.accentAction};
border-radius: 12px;
display: flex;
align-items: center;
border: none;
color: ${({ theme }) => theme.accentTextLightPrimary};
padding: 12px 16px;
width: 120px;
height: 44px;
font-size: 16px;
font-weight: 600;
justify-content: center;
`
const TotalBalancesSection = styled.div`
display: flex;
color: ${({ theme }) => theme.textSecondary};
justify-content: space-between;
align-items: center;
`
const ViewAll = styled.span`
display: flex;
color: ${({ theme }) => theme.accentAction};
font-size: 14px;
line-height: 20px;
cursor: pointer;
`
const ErrorState = styled.div`
display: flex;
align-items: center;
gap: 8px;
padding-right: 8px;
`
const LoadingState = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
`
const TopBalanceLoadBubble = styled(LoadingBubble)`
height: 12px;
width: 172px;
`
const BottomBalanceLoadBubble = styled(LoadingBubble)`
height: 16px;
width: 188px;
`
const ErrorText = styled.span`
display: flex;
flex-wrap: wrap;
`
export default function FooterBalanceSummary({
address,
networkBalances,
balance,
balanceUsd,
}: {
address: string
networkBalances: (JSX.Element | null)[] | null
balance?: number
balanceUsd?: number
}) {
const tokenSymbol = useToken(address)?.symbol
const [showMultipleBalances, setShowMultipleBalances] = useState(false)
const multipleBalances = false // for testing purposes
const networkNameIfOneBalance = 'Ethereum' // for testing purposes
const { loading, error } = useNetworkTokenBalances({ address })
return (
<BalanceFooter>
<TotalBalancesSection>
{loading ? (
<LoadingState>
<TopBalanceLoadBubble></TopBalanceLoadBubble>
<BottomBalanceLoadBubble></BottomBalanceLoadBubble>
</LoadingState>
) : error ? (
<ErrorState>
<AlertTriangle size={17} />
<ErrorText>
<Trans>There was an error fetching your balance</Trans>
</ErrorText>
</ErrorState>
) : (
!!balance &&
!!balanceUsd && (
<BalanceInfo>
{multipleBalances ? 'Balance on all networks' : `Your balance on ${networkNameIfOneBalance}`}
<BalanceTotal>
<BalanceValue>
{balance} {tokenSymbol}
</BalanceValue>
<FiatValue>{`$${balanceUsd}`}</FiatValue>
</BalanceTotal>
{multipleBalances && (
<ViewAll onClick={() => setShowMultipleBalances(!showMultipleBalances)}>
<Trans>{showMultipleBalances ? 'Hide' : 'View'} all balances</Trans>
</ViewAll>
)}
</BalanceInfo>
)
)}
<Link to={`/swap?outputCurrency=${address}`}>
<SwapButton>
<Trans>Swap</Trans>
</SwapButton>
</Link>
</TotalBalancesSection>
{showMultipleBalances && (
<NetworkBalancesSection>
<NetworkBalancesLabel>
<Trans>Your balances by network</Trans>
</NetworkBalancesLabel>
{networkBalances}
</NetworkBalancesSection>
)}
<FakeFooterNavBar>**leaving space for updated nav footer**</FakeFooterNavBar>
</BalanceFooter>
)
}

View File

@@ -0,0 +1,40 @@
import Tooltip from 'components/Tooltip'
import { ReactNode, useCallback, useState } from 'react'
import { Info } from 'react-feather'
import styled from 'styled-components/macro'
const InfoTipContainer = styled.div`
display: flex;
position: relative;
align-items: center;
cursor: help;
`
const InfoTipBody = styled.div`
color: ${({ theme }) => theme.textPrimary};
font-weight: 400;
font-size: 12px;
line-height: 16px;
`
const InfoTipWrapper = styled.div`
margin-left: 4px;
display: flex;
align-items: center;
`
export default function InfoTip({ text }: { text: ReactNode; size?: number }) {
const [show, setShow] = useState<boolean>(false)
const open = useCallback(() => setShow(true), [setShow])
const close = useCallback(() => setShow(false), [setShow])
return (
<InfoTipWrapper>
<Tooltip text={<InfoTipBody>{text}</InfoTipBody>} show={show} placement="right">
<InfoTipContainer onClick={open} onMouseEnter={open} onMouseLeave={close}>
<Info size={14} />
</InfoTipContainer>
</Tooltip>
</InfoTipWrapper>
)
}

View File

@@ -1,161 +0,0 @@
import { Footer, LeftPanel, RightPanel, TokenDetailsLayout } from 'pages/TokenDetails'
import styled, { useTheme } from 'styled-components/macro'
import { LoadingBubble } from '../loading'
import { AboutContainer, AboutHeader, ResourcesContainer } from './About'
import { ContractAddressSection } from './AddressSection'
import { BreadcrumbNavLink } from './BreadcrumbNavLink'
import { ChartContainer, ChartHeader, TokenInfoContainer, TokenNameCell } from './ChartSection'
import { DeltaContainer, TokenPrice } from './PriceChart'
import { StatPair, StatWrapper, TokenStatsSection } from './StatsSection'
const LoadingChartContainer = styled(ChartContainer)`
height: 336px;
overflow: hidden;
`
/* Loading state bubbles */
const LoadingDetailBubble = styled(LoadingBubble)`
height: 16px;
width: 180px;
`
const TitleLoadingBubble = styled(LoadingDetailBubble)`
width: 140px;
`
const SquareLoadingBubble = styled(LoadingDetailBubble)`
height: 32px;
border-radius: 8px;
margin-bottom: 10px;
`
const PriceLoadingBubble = styled(SquareLoadingBubble)`
height: 40px;
`
const LongLoadingBubble = styled(LoadingDetailBubble)`
margin-top: 6px;
width: 100%;
`
const HalfLoadingBubble = styled(LoadingDetailBubble)`
margin-top: 6px;
width: 50%;
`
const IconLoadingBubble = styled(LoadingDetailBubble)`
width: 32px;
height: 32px;
border-radius: 50%;
`
const StatLoadingBubble = styled(SquareLoadingBubble)`
width: 116px;
`
const StatsLoadingContainer = styled.div`
display: flex;
gap: 24px;
flex-wrap: wrap;
`
const ChartAnimation = styled.div`
display: flex;
animation: wave 8s cubic-bezier(0.36, 0.45, 0.63, 0.53) infinite;
overflow: hidden;
@keyframes wave {
0% {
margin-left: 0;
}
100% {
margin-left: -800px;
}
}
`
const Space = styled.div<{ heightSize: number }>`
height: ${({ heightSize }) => `${heightSize}px`};
`
export function Wave() {
const theme = useTheme()
return (
<svg width="416" height="160" xmlns="http://www.w3.org/2000/svg">
<path d="M 0 80 Q 104 10, 208 80 T 416 80" stroke={theme.backgroundOutline} fill="transparent" strokeWidth="2" />
</svg>
)
}
/* Loading State: row component with loading bubbles */
export default function LoadingTokenDetail() {
return (
<LeftPanel>
<BreadcrumbNavLink to="/explore">
<Space heightSize={20} />
</BreadcrumbNavLink>
<ChartHeader>
<TokenInfoContainer>
<TokenNameCell>
<IconLoadingBubble />
<TitleLoadingBubble />
</TokenNameCell>
</TokenInfoContainer>
<TokenPrice>
<PriceLoadingBubble />
</TokenPrice>
<DeltaContainer>
<Space heightSize={20} />
</DeltaContainer>
<LoadingChartContainer>
<div>
<ChartAnimation>
<Wave />
<Wave />
<Wave />
<Wave />
<Wave />
</ChartAnimation>
</div>
</LoadingChartContainer>
<Space heightSize={32} />
</ChartHeader>
<TokenStatsSection>
<StatsLoadingContainer>
<StatPair>
<StatWrapper>
<HalfLoadingBubble />
<StatLoadingBubble />
</StatWrapper>
<StatWrapper>
<HalfLoadingBubble />
<StatLoadingBubble />
</StatWrapper>
</StatPair>
<StatPair>
<StatWrapper>
<HalfLoadingBubble />
<StatLoadingBubble />
</StatWrapper>
<StatWrapper>
<HalfLoadingBubble />
<StatLoadingBubble />
</StatWrapper>
</StatPair>
</StatsLoadingContainer>
</TokenStatsSection>
<AboutContainer>
<AboutHeader>
<SquareLoadingBubble />
</AboutHeader>
<LongLoadingBubble />
<LongLoadingBubble />
<HalfLoadingBubble />
<ResourcesContainer>{null}</ResourcesContainer>
</AboutContainer>
<ContractAddressSection>{null}</ContractAddressSection>
</LeftPanel>
)
}
export function LoadingTokenDetails() {
return (
<TokenDetailsLayout>
<LoadingTokenDetail />
<RightPanel />
<Footer />
</TokenDetailsLayout>
)
}

View File

@@ -0,0 +1,110 @@
import { Trans } from '@lingui/macro'
import { Currency } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { NATIVE_CHAIN_ID } from 'constants/tokens'
import { CHAIN_ID_TO_BACKEND_NAME } from 'graphql/data/util'
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
import styled from 'styled-components/macro'
import { StyledInternalLink } from 'theme'
import { useFormatBalance, useFormatUsdValue } from './BalanceSummary'
const Wrapper = styled.div`
align-content: center;
align-items: center;
border: 1px solid ${({ theme }) => theme.backgroundOutline};
background-color: ${({ theme }) => theme.backgroundSurface};
border-radius: 20px 20px 0px 0px;
bottom: 56px;
color: ${({ theme }) => theme.textSecondary};
display: flex;
flex-direction: row;
font-weight: 500;
font-size: 14px;
height: fit-content;
justify-content: space-between;
left: 0;
line-height: 20px;
padding: 12px 16px;
position: fixed;
width: 100%;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) {
bottom: 0px;
}
@media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) {
display: none;
}
`
const BalanceValue = styled.div`
color: ${({ theme }) => theme.textPrimary};
font-size: 20px;
line-height: 28px;
display: flex;
gap: 8px;
`
const Balance = styled.div`
align-items: center;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 8px;
`
const BalanceInfo = styled.div`
display: flex;
flex: 10 1 auto;
flex-direction: column;
justify-content: flex-start;
`
const FiatValue = styled.span`
font-size: 12px;
line-height: 16px;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.sm}px) {
line-height: 24px;
}
`
const SwapButton = styled(StyledInternalLink)`
background-color: ${({ theme }) => theme.accentAction};
border: none;
border-radius: 12px;
color: ${({ theme }) => theme.accentTextLightPrimary};
display: flex;
flex: 1 1 auto;
padding: 12px 16px;
font-size: 1em;
font-weight: 600;
height: 44px;
justify-content: center;
margin: auto;
max-width: 100vw;
`
export default function MobileBalanceSummaryFooter({ token }: { token: Currency }) {
const { account } = useWeb3React()
const balance = useCurrencyBalance(account, token)
const formattedBalance = useFormatBalance(balance)
const usdValue = useStablecoinValue(balance)
const formattedUsdValue = useFormatUsdValue(usdValue)
const chain = CHAIN_ID_TO_BACKEND_NAME[token.chainId].toLowerCase()
return (
<Wrapper>
{Boolean(account && balance) && (
<BalanceInfo>
<Trans>Your {token.symbol} balance</Trans>
<Balance>
<BalanceValue>
{formattedBalance} {token.symbol}
</BalanceValue>
<FiatValue>{formattedUsdValue}</FiatValue>
</Balance>
</BalanceInfo>
)}
<SwapButton to={`/swap?chainName=${chain}&outputCurrency=${token.isNative ? NATIVE_CHAIN_ID : token.address}`}>
<Trans>Swap</Trans>
</SwapButton>
</Wrapper>
)
}

View File

@@ -1,64 +0,0 @@
import styled, { useTheme } from 'styled-components/macro'
const Balance = styled.div`
width: 100%;
display: flex;
flex-direction: column;
font-size: 16px;
line-height: 20px;
`
const BalanceItem = styled.div`
display: flex;
`
const BalanceRow = styled.div`
display: flex;
justify-content: space-between;
`
const Logo = styled.img`
height: 32px;
width: 32px;
margin-right: 8px;
`
const Network = styled.span<{ color: string }>`
font-size: 12px;
line-height: 16px;
font-weight: 500;
color: ${({ color }) => color};
`
const NetworkBalanceContainer = styled.div`
display: flex;
padding-top: 16px;
align-items: center;
`
export default function NetworkBalance({
logoUrl,
balance,
tokenSymbol,
fiatValue,
label,
networkColor,
}: {
logoUrl: string
balance: string
tokenSymbol: string
fiatValue: string | number
label: string
networkColor: string | undefined
}) {
const theme = useTheme()
return (
<NetworkBalanceContainer>
<Logo src={logoUrl} />
<Balance>
<BalanceRow>
<BalanceItem>
{balance}&nbsp;{tokenSymbol}
</BalanceItem>
<BalanceItem>${fiatValue}</BalanceItem>
</BalanceRow>
<Network color={networkColor ?? theme.textPrimary}>{label}</Network>
</Balance>
</NetworkBalanceContainer>
)
}

View File

@@ -1,17 +1,16 @@
import { Trans } from '@lingui/macro'
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 AnimatedInLineChart from 'components/Charts/AnimatedInLineChart'
import { filterTimeAtom } from 'components/Tokens/state'
import { bisect, curveCardinal, NumberValue, scaleLinear, timeDay, timeHour, timeMinute, timeMonth } from 'd3'
import { PricePoint } from 'graphql/data/Token'
import { PricePoint } from 'graphql/data/util'
import { TimePeriod } from 'graphql/data/util'
import { useActiveLocale } from 'hooks/useActiveLocale'
import { useAtom } from 'jotai'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { ArrowDownRight, ArrowUpRight } from 'react-feather'
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
import { ArrowDownRight, ArrowUpRight, TrendingUp } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
import {
dayHourFormatter,
@@ -21,9 +20,7 @@ import {
monthYearDayFormatter,
weekFormatter,
} from 'utils/formatChartTimes'
import { MEDIUM_MEDIA_BREAKPOINT } from '../constants'
import { DISPLAYS, ORDERED_TIMES } from '../TokenTable/TimeSelector'
import { formatDollar } from 'utils/formatNumbers'
export const DATA_EMPTY = { value: 0, timestamp: 0 }
@@ -50,14 +47,14 @@ export function getDeltaArrow(delta: number | null | undefined) {
if (delta === null || delta === undefined) {
return null
} else if (Math.sign(delta) < 0) {
return <StyledDownArrow size={16} key="arrow-down" />
return <StyledDownArrow size={24} key="arrow-down" />
}
return <StyledUpArrow size={16} key="arrow-up" />
return <StyledUpArrow size={24} key="arrow-up" />
}
export function formatDelta(delta: number | null | undefined) {
// Null-check not including zero
if (delta === null || delta === undefined) {
if (delta === null || delta === undefined || delta === Infinity || isNaN(delta)) {
return '-'
}
let formattedDelta = delta.toFixed(2) + '%'
@@ -84,45 +81,6 @@ const ArrowCell = styled.div`
padding-left: 2px;
display: flex;
`
export const TimeOptionsWrapper = styled.div`
display: flex;
justify-content: flex-end;
`
export const TimeOptionsContainer = styled.div`
display: flex;
justify-content: flex-end;
margin-top: 4px;
gap: 4px;
border: 1px solid ${({ theme }) => theme.backgroundOutline};
border-radius: 16px;
height: 40px;
padding: 4px;
width: fit-content;
@media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) {
width: 100%;
justify-content: space-between;
border: none;
}
`
const TimeButton = styled.button<{ active: boolean }>`
flex: 1;
display: flex;
align-items: center;
background-color: ${({ theme, active }) => (active ? theme.backgroundInteractive : 'transparent')};
font-weight: 600;
font-size: 16px;
padding: 6px 12px;
border-radius: 12px;
line-height: 20px;
border: none;
cursor: pointer;
color: ${({ theme, active }) => (active ? theme.textPrimary : theme.textSecondary)};
transition-duration: ${({ theme }) => theme.transition.duration.fast};
:hover {
${({ active, theme }) => !active && `opacity: ${theme.opacity.hover};`}
}
`
const margin = { top: 100, bottom: 48, crosshair: 72 }
const timeOptionsHeight = 44
@@ -130,11 +88,11 @@ const timeOptionsHeight = 44
interface PriceChartProps {
width: number
height: number
prices: PricePoint[] | undefined
prices: PricePoint[] | undefined | null
timePeriod: TimePeriod
}
export function PriceChart({ width, height, prices }: PriceChartProps) {
const [timePeriod, setTimePeriod] = useAtom(filterTimeAtom)
export function PriceChart({ width, height, prices, timePeriod }: PriceChartProps) {
const locale = useActiveLocale()
const theme = useTheme()
@@ -247,17 +205,13 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
setDisplayPrice(endingPrice)
}, [setCrosshair, setDisplayPrice, endingPrice])
// TODO: Display no data available error
if (!prices) {
return null
}
const [tickFormatter, crosshairDateFormatter, ticks] = tickFormat(timePeriod, locale)
const delta = calculateDelta(startingPrice.value, displayPrice.value)
const formattedDelta = formatDelta(delta)
const arrow = getDeltaArrow(delta)
const crosshairEdgeMax = width * 0.85
const crosshairAtEdge = !!crosshair && crosshair > crosshairEdgeMax
const hasData = prices && prices.length > 0
/*
* Default curve doesn't look good for the HOUR chart.
@@ -266,102 +220,151 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
*/
const curveTension = timePeriod === TimePeriod.HOUR ? 1 : 0.9
const getX = useMemo(() => (p: PricePoint) => timeScale(p.timestamp), [timeScale])
const getY = useMemo(() => (p: PricePoint) => rdScale(p.value), [rdScale])
const curve = useMemo(() => curveCardinal.tension(curveTension), [curveTension])
return (
<>
<ChartHeader>
<TokenPrice>${displayPrice.value < 0.000001 ? '<0.000001' : displayPrice.value.toFixed(6)}</TokenPrice>
<TokenPrice>{formatDollar({ num: displayPrice.value, isPrice: true })}</TokenPrice>
<DeltaContainer>
{formattedDelta}
<ArrowCell>{arrow}</ArrowCell>
</DeltaContainer>
</ChartHeader>
<AnimatedInLineChart
data={prices}
getX={(p: PricePoint) => timeScale(p.timestamp)}
getY={(p: PricePoint) => rdScale(p.value)}
marginTop={margin.top}
curve={curveCardinal.tension(curveTension)}
strokeWidth={2}
width={width}
height={graphHeight}
>
{crosshair !== null ? (
<g>
<AxisBottom
scale={timeScale}
stroke={theme.backgroundOutline}
tickFormat={tickFormatter}
tickStroke={theme.backgroundOutline}
tickLength={4}
hideTicks={true}
tickTransform={'translate(0 -5)'}
tickValues={ticks}
top={graphHeight - 1}
tickLabelProps={() => ({
fill: theme.textSecondary,
fontSize: 12,
textAnchor: 'middle',
transform: 'translate(0 -24)',
})}
/>
<text
x={crosshair + (crosshairAtEdge ? -4 : 4)}
y={margin.crosshair + 10}
textAnchor={crosshairAtEdge ? 'end' : 'start'}
fontSize={12}
fill={theme.textSecondary}
>
{crosshairDateFormatter(displayPrice.timestamp)}
</text>
<Line
from={{ x: crosshair, y: margin.crosshair }}
to={{ x: crosshair, y: graphHeight }}
stroke={theme.backgroundOutline}
strokeWidth={1}
pointerEvents="none"
strokeDasharray="4,4"
/>
<GlyphCircle
left={crosshair}
top={rdScale(displayPrice.value) + margin.top}
size={50}
fill={theme.accentActive}
stroke={theme.backgroundOutline}
strokeWidth={2}
/>
</g>
) : (
<AxisBottom scale={timeScale} stroke={theme.backgroundOutline} top={graphHeight - 1} hideTicks />
)}
<rect
x={0}
y={0}
{!hasData ? (
<MissingPriceChart
width={width}
height={graphHeight}
fill={'transparent'}
onTouchStart={handleHover}
onTouchMove={handleHover}
onMouseMove={handleHover}
onMouseLeave={resetDisplay}
message={
prices?.length === 0 ? (
<Trans>This token doesn&apos;t have chart data because it hasn&apos;t been traded on Uniswap v3</Trans>
) : (
<Trans>Missing chart data</Trans>
)
}
/>
</AnimatedInLineChart>
<TimeOptionsWrapper>
<TimeOptionsContainer>
{ORDERED_TIMES.map((time) => (
<TimeButton
key={DISPLAYS[time]}
active={timePeriod === time}
onClick={() => {
setTimePeriod(time)
}}
>
{DISPLAYS[time]}
</TimeButton>
))}
</TimeOptionsContainer>
</TimeOptionsWrapper>
) : (
<svg width={width} height={graphHeight} style={{ minWidth: '100%' }}>
<AnimatedInLineChart
data={prices}
getX={getX}
getY={getY}
marginTop={margin.top}
curve={curve}
strokeWidth={2}
/>
{crosshair !== null ? (
<g>
<AxisBottom
scale={timeScale}
stroke={theme.backgroundOutline}
tickFormat={tickFormatter}
tickStroke={theme.backgroundOutline}
tickLength={4}
hideTicks={true}
tickTransform={'translate(0 -5)'}
tickValues={ticks}
top={graphHeight - 1}
tickLabelProps={() => ({
fill: theme.textSecondary,
fontSize: 12,
textAnchor: 'middle',
transform: 'translate(0 -24)',
})}
/>
<text
x={crosshair + (crosshairAtEdge ? -4 : 4)}
y={margin.crosshair + 10}
textAnchor={crosshairAtEdge ? 'end' : 'start'}
fontSize={12}
fill={theme.textSecondary}
>
{crosshairDateFormatter(displayPrice.timestamp)}
</text>
<Line
from={{ x: crosshair, y: margin.crosshair }}
to={{ x: crosshair, y: graphHeight }}
stroke={theme.backgroundOutline}
strokeWidth={1}
pointerEvents="none"
strokeDasharray="4,4"
/>
<GlyphCircle
left={crosshair}
top={rdScale(displayPrice.value) + margin.top}
size={50}
fill={theme.accentAction}
stroke={theme.backgroundOutline}
strokeWidth={0.5}
/>
</g>
) : (
<AxisBottom scale={timeScale} stroke={theme.backgroundOutline} top={graphHeight - 1} hideTicks />
)}
{!width && (
// Ensures an axis is drawn even if the width is not yet initialized.
<line
x1={0}
y1={graphHeight - 1}
x2="100%"
y2={graphHeight - 1}
fill="transparent"
shapeRendering="crispEdges"
stroke={theme.backgroundOutline}
strokeWidth={1}
/>
)}
<rect
x={0}
y={0}
width={width}
height={graphHeight}
fill={'transparent'}
onTouchStart={handleHover}
onTouchMove={handleHover}
onMouseMove={handleHover}
onMouseLeave={resetDisplay}
/>
</svg>
)}
</>
)
}
const StyledMissingChart = styled.svg`
text {
font-size: 12px;
font-weight: 400;
}
`
const chartBottomPadding = 15
function MissingPriceChart({ width, height, message }: { width: number; height: number; message: ReactNode }) {
const theme = useTheme()
const midPoint = height / 2 + 45
return (
<StyledMissingChart width={width} height={height} style={{ minWidth: '100%' }}>
<path
d={`M 0 ${midPoint} Q 104 ${midPoint - 70}, 208 ${midPoint} T 416 ${midPoint}
M 416 ${midPoint} Q 520 ${midPoint - 70}, 624 ${midPoint} T 832 ${midPoint}`}
stroke={theme.backgroundOutline}
fill="transparent"
strokeWidth="2"
/>
<TrendingUp stroke={theme.textTertiary} x={0} size={12} y={height - chartBottomPadding - 10} />
<text y={height - chartBottomPadding} x="20" fill={theme.textTertiary}>
{message || <Trans>Missing chart data</Trans>}
</text>
<path
d={`M 0 ${height - 1}, ${width} ${height - 1}`}
stroke={theme.backgroundOutline}
fill="transparent"
strokeWidth="1"
/>
</StyledMissingChart>
)
}
export default PriceChart

View File

@@ -1,4 +1,6 @@
import { Trans } from '@lingui/macro'
import { NATIVE_CHAIN_ID } from 'constants/tokens'
import { TokenQueryData } from 'graphql/data/Token'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { useRef } from 'react'
import { Twitter } from 'react-feather'
@@ -62,9 +64,8 @@ const ShareAction = styled.div`
`
interface TokenInfo {
tokenName: string
tokenSymbol: string
tokenAddress: string
token: NonNullable<TokenQueryData>
isNative: boolean
}
export default function ShareButton(tokenInfo: TokenInfo) {
@@ -75,11 +76,12 @@ export default function ShareButton(tokenInfo: TokenInfo) {
useOnClickOutside(node, open ? toggleShare : undefined)
const positionX = (window.screen.width - TWITTER_WIDTH) / 2
const positionY = (window.screen.height - TWITTER_HEIGHT) / 2
const tokenAddress = tokenInfo.isNative ? NATIVE_CHAIN_ID : tokenInfo.token.address
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.tokenAddress}%20via%20@uniswap`,
`https://twitter.com/intent/tweet?text=Check%20out%20${tokenInfo.token.name}%20(${tokenInfo.token.symbol})%20https://app.uniswap.org/%23/tokens/${tokenInfo.token.chain}/${tokenAddress}%20via%20@uniswap`,
'newwindow',
`left=${positionX}, top=${positionY}, width=${TWITTER_WIDTH}, height=${TWITTER_HEIGHT}`
)

View File

@@ -0,0 +1,259 @@
import { WidgetSkeleton } from 'components/Widget'
import { WIDGET_WIDTH } from 'components/Widget'
import { ArrowLeft } from 'react-feather'
import { useParams } from 'react-router-dom'
import styled, { useTheme } from 'styled-components/macro'
import { textFadeIn } from 'theme/animations'
import { LoadingBubble } from '../loading'
import { LogoContainer } from '../TokenTable/TokenRow'
import { AboutContainer, AboutHeader } from './About'
import { BreadcrumbNavLink } from './BreadcrumbNavLink'
import { DeltaContainer, TokenPrice } from './PriceChart'
import { StatPair, StatsWrapper, StatWrapper } from './StatsSection'
export const Hr = styled.hr`
background-color: ${({ theme }) => theme.backgroundOutline};
border: none;
height: 0.5px;
`
export const TokenDetailsLayout = styled.div`
display: flex;
padding: 0 8px 52px;
justify-content: center;
width: 100%;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.sm}px) {
gap: 16px;
padding: 0 16px;
}
@media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) {
gap: 40px;
padding: 48px 20px;
}
@media screen and (min-width: ${({ theme }) => theme.breakpoint.xl}px) {
gap: 60px;
}
`
export const LeftPanel = styled.div`
flex: 1;
max-width: 780px;
overflow: hidden;
`
export const RightPanel = styled.div`
display: none;
flex-direction: column;
gap: 20px;
width: ${WIDGET_WIDTH}px;
@media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) {
display: flex;
}
`
export const ChartContainer = styled.div`
display: flex;
flex-direction: column;
height: 436px;
margin-bottom: 24px;
align-items: flex-start;
width: 100%;
`
const LoadingChartContainer = styled.div`
display: flex;
flex-direction: row;
align-items: flex-end;
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
height: 100%;
margin-bottom: 44px;
padding-bottom: 66px;
overflow: hidden;
`
export const TokenInfoContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
`
export const TokenNameCell = styled.div`
display: flex;
gap: 8px;
font-size: 20px;
line-height: 28px;
align-items: center;
${textFadeIn}
`
/* Loading state bubbles */
const DetailBubble = styled(LoadingBubble)`
height: 16px;
width: 180px;
`
const SquaredBubble = styled(DetailBubble)`
height: 32px;
border-radius: 8px;
`
const TokenLogoBubble = styled(DetailBubble)`
width: 32px;
height: 32px;
border-radius: 50%;
`
const TitleBubble = styled(DetailBubble)`
width: 140px;
`
const PriceBubble = styled(SquaredBubble)`
margin-top: 2px;
height: 38px;
`
const DeltaBubble = styled(DetailBubble)`
margin-top: 6px;
width: 96px;
height: 20px;
`
const SectionBubble = styled(SquaredBubble)`
width: 96px;
`
const StatTitleBubble = styled(DetailBubble)`
width: 25%;
margin-bottom: 4px;
`
const StatBubble = styled(SquaredBubble)`
width: 50%;
`
const WideBubble = styled(DetailBubble)`
margin-bottom: 6px;
width: 100%;
`
const HalfWideBubble = styled(WideBubble)`
width: 50%;
`
const StatsLoadingContainer = styled.div`
width: 100%;
display: flex;
flex-wrap: wrap;
`
const ChartAnimation = styled.div`
animation: wave 8s cubic-bezier(0.36, 0.45, 0.63, 0.53) infinite;
display: flex;
overflow: hidden;
margin-top: 90px;
@keyframes wave {
0% {
margin-left: 0;
}
100% {
margin-left: -800px;
}
}
`
const Space = styled.div<{ heightSize: number }>`
height: ${({ heightSize }) => `${heightSize}px`};
`
function Wave() {
const theme = useTheme()
return (
<svg width="416" height="160" xmlns="http://www.w3.org/2000/svg">
<path d="M 0 80 Q 104 10, 208 80 T 416 80" stroke={theme.backgroundOutline} fill="transparent" strokeWidth="2" />
</svg>
)
}
export function LoadingChart() {
return (
<ChartContainer>
<TokenPrice>
<PriceBubble />
</TokenPrice>
<DeltaContainer>
<DeltaBubble />
</DeltaContainer>
<Space heightSize={6} />
<LoadingChartContainer>
<div>
<ChartAnimation>
<Wave />
<Wave />
<Wave />
<Wave />
<Wave />
</ChartAnimation>
</div>
</LoadingChartContainer>
</ChartContainer>
)
}
function LoadingStats() {
return (
<StatsWrapper>
<SectionBubble />
<StatsLoadingContainer>
<StatPair>
<StatWrapper>
<StatTitleBubble />
<StatBubble />
</StatWrapper>
<StatWrapper>
<StatTitleBubble />
<StatBubble />
</StatWrapper>
</StatPair>
<StatPair>
<StatWrapper>
<StatTitleBubble />
<StatBubble />
</StatWrapper>
<StatWrapper>
<StatTitleBubble />
<StatBubble />
</StatWrapper>
</StatPair>
</StatsLoadingContainer>
</StatsWrapper>
)
}
/* Loading State: row component with loading bubbles */
export default function TokenDetailsSkeleton() {
const { chainName } = useParams<{ chainName?: string }>()
return (
<LeftPanel>
<BreadcrumbNavLink to={{ chainName } ? `/tokens/${chainName}` : `/explore`}>
<ArrowLeft size={14} /> Tokens
</BreadcrumbNavLink>
<TokenInfoContainer>
<TokenNameCell>
<LogoContainer>
<TokenLogoBubble />
</LogoContainer>
<TitleBubble />
</TokenNameCell>
</TokenInfoContainer>
<LoadingChart />
<Space heightSize={4} />
<LoadingStats />
<Hr />
<AboutContainer>
<AboutHeader>
<SectionBubble />
</AboutHeader>
</AboutContainer>
<WideBubble />
<WideBubble />
<HalfWideBubble />
</LeftPanel>
)
}
export function TokenDetailsPageSkeleton() {
return (
<TokenDetailsLayout>
<TokenDetailsSkeleton />
<RightPanel>
<WidgetSkeleton />
</RightPanel>
</TokenDetailsLayout>
)
}

View File

@@ -1,8 +1,13 @@
import { Trans } from '@lingui/macro'
import { ReactNode } from 'react'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { textFadeIn } from 'theme/animations'
import { formatDollarAmount } from 'utils/formatDollarAmt'
import { formatDollar } from 'utils/formatNumbers'
import { TokenSortMethod } from '../state'
import { HEADER_DESCRIPTIONS } from '../TokenTable/TokenRow'
import InfoTip from './InfoTip'
export const StatWrapper = styled.div`
display: flex;
@@ -17,13 +22,21 @@ export const StatWrapper = styled.div`
export const TokenStatsSection = styled.div`
display: flex;
flex-wrap: wrap;
${textFadeIn}
`
export const StatPair = styled.div`
display: flex;
flex: 1;
flex-wrap: wrap;
`
const Header = styled(ThemedText.MediumHeader)`
font-size: 28px !important;
`
const StatTitle = styled.div`
display: flex;
flex-direction: row;
gap: 4px;
`
const StatPrice = styled.span`
font-size: 28px;
color: ${({ theme }) => theme.textPrimary};
@@ -31,14 +44,32 @@ const StatPrice = styled.span`
const NoData = styled.div`
color: ${({ theme }) => theme.textTertiary};
`
export const StatsWrapper = styled.div`
gap: 16px;
${textFadeIn}
`
type NumericStat = number | undefined | null
function Stat({ value, title }: { value: NumericStat; title: ReactNode }) {
function Stat({
value,
title,
description,
isPrice = false,
}: {
value: NumericStat
title: ReactNode
description?: ReactNode
isPrice?: boolean
}) {
return (
<StatWrapper>
{title}
<StatPrice>{value ? formatDollarAmount(value) : '-'}</StatPrice>
<StatTitle>
{title}
{description && <InfoTip text={description}></InfoTip>}
</StatTitle>
<StatPrice>{formatDollar({ num: value, isPrice })}</StatPrice>
</StatWrapper>
)
}
@@ -53,16 +84,33 @@ export default function StatsSection(props: StatsSectionProps) {
const { priceLow52W, priceHigh52W, TVL, volume24H } = props
if (TVL || volume24H || priceLow52W || priceHigh52W) {
return (
<TokenStatsSection>
<StatPair>
<Stat value={TVL} title={<Trans>Total Value Locked</Trans>} />
<Stat value={volume24H} title={<Trans>24H volume</Trans>} />
</StatPair>
<StatPair>
<Stat value={priceLow52W} title={<Trans>52W low</Trans>} />
<Stat value={priceHigh52W} title={<Trans>52W high</Trans>} />
</StatPair>
</TokenStatsSection>
<StatsWrapper>
<Header>
<Trans>Stats</Trans>
</Header>
<TokenStatsSection>
<StatPair>
<Stat
value={TVL}
description={HEADER_DESCRIPTIONS[TokenSortMethod.TOTAL_VALUE_LOCKED]}
title={<Trans>TVL</Trans>}
/>
<Stat
value={volume24H}
description={
<Trans>
24H volume is the amount of the asset that has been traded on Uniswap v3 during the past 24 hours.
</Trans>
}
title={<Trans>24H volume</Trans>}
/>
</StatPair>
<StatPair>
<Stat value={priceLow52W} title={<Trans>52W low</Trans>} isPrice={true} />
<Stat value={priceHigh52W} title={<Trans>52W high</Trans>} isPrice={true} />
</StatPair>
</TokenStatsSection>
</StatsWrapper>
)
} else {
return <NoData>No stats available</NoData>

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