Compare commits

...

62 Commits

Author SHA1 Message Date
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
122 changed files with 1750 additions and 2699 deletions

View File

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

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

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

@@ -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",
@@ -135,7 +135,7 @@
"@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 +146,7 @@
"@uniswap/v3-core": "1.0.0",
"@uniswap/v3-periphery": "^1.1.1",
"@uniswap/v3-sdk": "^3.9.0",
"@uniswap/widgets": "^2.11.0",
"@uniswap/widgets": "^2.16.2",
"@vanilla-extract/css": "^1.7.2",
"@vanilla-extract/css-utils": "^0.1.2",
"@vanilla-extract/dynamic": "^2.0.2",
@@ -169,7 +169,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",

View File

@@ -1,126 +1,119 @@
<!DOCTYPE html>
<html translate="no">
<head>
<meta charset="utf-8" />
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<head>
<meta charset="utf-8" />
<!--
<title>Uniswap Interface</title>
<meta name="description" content="Swap or provide liquidity on the Uniswap Protocol" />
<!--
%PUBLIC_URL% will be replaced with the URL of the `public` folder during build.
Only files inside the `public` folder can be referenced from the HTML.
-->
<link rel="shortcut icon" type="image/png" href="%PUBLIC_URL%/favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="%PUBLIC_URL%/images/192x192_App_Icon.png" />
<link rel="apple-touch-icon" sizes="512x512" href="%PUBLIC_URL%/images/512x512_App_Icon.png" />
<link rel="shortcut icon" type="image/png" href="%PUBLIC_URL%/favicon.png" />
<link rel="apple-touch-icon" sizes="192x192" href="%PUBLIC_URL%/images/192x192_App_Icon.png" />
<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="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<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.
See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="preconnect" href="https://www.google-analytics.com/" />
<link rel="preconnect" href="https://www.google-analytics.com/" />
<link rel="preload" href="%PUBLIC_URL%/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="%PUBLIC_URL%/fonts/Inter-roman.var.woff2" as="font" type="font/woff2" crossorigin />
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
<style>
* {
font-family: 'Inter', sans-serif;
box-sizing: border-box;
}
/**
/**
Explicitly load Inter var from public/ so it does not block LCP's critical path.
*/
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2');
}
@font-face {
font-family: 'Inter custom';
font-weight: 100 900;
font-style: normal;
font-display: block;
font-named-instance: 'Regular';
src: url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2 supports variations(gvar)'),
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2-variations'),
url(%PUBLIC_URL%/fonts/Inter-roman.var.woff2) format('woff2');
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
@supports (font-variation-settings: normal) {
* {
font-family: 'Inter custom', sans-serif;
}
}
html,
body {
margin: 0;
padding: 0;
}
html,
body {
margin: 0;
padding: 0;
}
button {
user-select: none;
}
button {
user-select: none;
}
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
#background-radial-gradient {
background: linear-gradient(180deg, #202738 0%, #070816 100%);
position: fixed;
top: 0;
left: 0;
right: 0;
pointer-events: none;
width: 200vw;
height: 200vh;
transform: translate(-50vw, -100vh);
z-index: -1;
}
html {
min-height: 100%;
}
@media (prefers-color-scheme: dark) {
html {
font-size: 16px;
font-variant: none;
font-smooth: always;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
#background-radial-gradient {
position: fixed;
top: 0;
left: 0;
right: 0;
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;
background-color: #212429;
}
}
@media (prefers-color-scheme: light) {
html {
min-height: 100%;
background-color: #f7f8fa;
}
}
</style>
</head>
@media (prefers-color-scheme: dark) {
html {
background-color: #212429;
}
}
@media (prefers-color-scheme: light) {
html {
background-color: #f7f8fa;
}
}
</style>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<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="root">
<!-- Triggers the font to load immediately and then is replaced by the app -->
<div>&emsp;</div>
</div>
<div id="background-radial-gradient"></div>
<div id="background-radial-gradient"></div>
</body>
<!--
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>
</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

@@ -20,6 +20,11 @@ export interface ITraceContext {
export const TraceContext = createContext<ITraceContext>({})
export function useTrace(trace?: ITraceContext): ITraceContext {
const parentTrace = useContext(TraceContext)
return useMemo(() => ({ ...parentTrace, ...trace }), [parentTrace, trace])
}
type TraceProps = {
shouldLogImpression?: boolean // whether to log impression on mount
name?: EventName
@@ -31,25 +36,32 @@ type TraceProps = {
* and propagates the context to child traces.
*/
export const Trace = memo(
({ shouldLogImpression, name, children, page, section, element, properties }: PropsWithChildren<TraceProps>) => {
const parentTrace = useContext(TraceContext)
({
shouldLogImpression,
name,
children,
page,
section,
modal,
element,
properties,
}: PropsWithChildren<TraceProps>) => {
const parentTrace = useTrace()
const combinedProps = useMemo(
() => ({
...parentTrace,
...Object.fromEntries(Object.entries({ page, section, element }).filter(([_, v]) => v !== undefined)),
...Object.fromEntries(Object.entries({ page, section, modal, element }).filter(([_, v]) => v !== undefined)),
}),
[element, parentTrace, page, section]
[element, parentTrace, page, modal, 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,
})
}

View File

@@ -8,6 +8,7 @@ export enum EventName {
APP_LOADED = 'Application Loaded',
APPROVE_TOKEN_TXN_SUBMITTED = 'Approve Token Transaction Submitted',
CONNECT_WALLET_BUTTON_CLICKED = 'Connect Wallet Button Clicked',
EXPLORE_BANNER_CLICKED = 'Explore Banner Clicked',
EXPLORE_SEARCH_SELECTED = 'Explore Search Selected',
EXPLORE_TOKEN_ROW_CLICKED = 'Explore Token Row Clicked',
PAGE_VIEWED = 'Page Viewed',
@@ -73,6 +74,7 @@ export enum SWAP_PRICE_UPDATE_USER_RESPONSE {
* Known pages in the app. Highest order context.
*/
export enum PageName {
TOKEN_DETAILS_PAGE = 'token-details',
TOKENS_PAGE = 'tokens-page',
POOL_PAGE = 'pool-page',
SWAP_PAGE = 'swap-page',
@@ -88,6 +90,7 @@ export enum PageName {
export enum SectionName {
CURRENCY_INPUT_PANEL = 'swap-currency-input',
CURRENCY_OUTPUT_PANEL = 'swap-currency-output',
WIDGET = 'widget',
// alphabetize additional section names.
}
@@ -107,6 +110,7 @@ export enum ElementName {
COMMON_BASES_CURRENCY_BUTTON = 'common-bases-currency-button',
CONFIRM_SWAP_BUTTON = 'confirm-swap-or-send',
CONNECT_WALLET_BUTTON = 'connect-wallet-button',
EXPLORE_BANNER = 'explore-banner',
EXPLORE_SEARCH_INPUT = 'explore_search_input',
IMPORT_TOKEN_BUTTON = 'import-token-button',
MAX_TOKEN_AMOUNT_BUTTON = 'max-token-amount-button',

View File

@@ -35,12 +35,13 @@ export function initializeAnalytics() {
/** Sends an event to Amplitude. */
export function sendAnalyticsEvent(eventName: string, eventProperties?: Record<string, unknown>) {
const origin = window.location.origin
if (!API_KEY) {
console.log(`[analytics(${eventName})]: ${JSON.stringify(eventProperties)}`)
return
}
track(eventName, eventProperties)
track(eventName, { ...eventProperties, origin })
}
type Value = string | number | boolean | string[] | number[]

View File

@@ -1,12 +1,16 @@
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
import { Trade } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Percent, Price, Token, TradeType } from '@uniswap/sdk-core'
import { NATIVE_CHAIN_ID } from 'constants/tokens'
import { InterfaceTrade } from 'state/routing/types'
import { computeRealizedPriceImpact } from 'utils/prices'
export const getDurationUntilTimestampSeconds = (futureTimestampInSecondsSinceEpoch?: number): number | undefined => {
if (!futureTimestampInSecondsSinceEpoch) return undefined
return futureTimestampInSecondsSinceEpoch - new Date().getTime() / 1000
}
export const getDurationFromDateMilliseconds = (start: Date): number => {
export const getDurationFromDateMilliseconds = (start?: Date): number | undefined => {
if (!start) return undefined
return new Date().getTime() - start.getTime()
}
@@ -20,3 +24,57 @@ export const getTokenAddress = (currency: Currency) => (currency.isNative ? NATI
export const formatPercentInBasisPointsNumber = (percent: Percent): number => parseFloat(percent.toFixed(2)) * 100
export const formatPercentNumber = (percent: Percent): number => parseFloat(percent.toFixed(2))
export const getPriceUpdateBasisPoints = (
prevPrice: Price<Currency, Currency>,
newPrice: Price<Currency, Currency>
): number => {
const changeFraction = newPrice.subtract(prevPrice).divide(prevPrice)
const changePercentage = new Percent(changeFraction.numerator, changeFraction.denominator)
return formatPercentInBasisPointsNumber(changePercentage)
}
export const formatSwapSignedAnalyticsEventProperties = ({
trade,
txHash,
}: {
trade: InterfaceTrade<Currency, Currency, TradeType> | Trade<Currency, Currency, TradeType>
txHash: string
}) => ({
transaction_hash: txHash,
token_in_address: getTokenAddress(trade.inputAmount.currency),
token_out_address: getTokenAddress(trade.outputAmount.currency),
token_in_symbol: trade.inputAmount.currency.symbol,
token_out_symbol: trade.outputAmount.currency.symbol,
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
price_impact_basis_points: formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)),
chain_id:
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
? trade.inputAmount.currency.chainId
: undefined,
})
export const formatSwapQuoteReceivedEventProperties = (
trade: Trade<Currency, Currency, TradeType>,
gasUseEstimateUSD?: CurrencyAmount<Token>,
fetchingSwapQuoteStartTime?: Date
) => {
return {
token_in_symbol: trade.inputAmount.currency.symbol,
token_out_symbol: trade.outputAmount.currency.symbol,
token_in_address: getTokenAddress(trade.inputAmount.currency),
token_out_address: getTokenAddress(trade.outputAmount.currency),
price_impact_basis_points: trade ? formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)) : undefined,
estimated_network_fee_usd: gasUseEstimateUSD ? formatToDecimal(gasUseEstimateUSD, 2) : undefined,
chain_id:
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
? trade.inputAmount.currency.chainId
: undefined,
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
quote_latency_milliseconds: fetchingSwapQuoteStartTime
? getDurationFromDateMilliseconds(fetchingSwapQuoteStartTime)
: undefined,
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

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

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

@@ -7,11 +7,10 @@ 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 ? theme.deprecated_bg3 : darken(0.05, theme.deprecated_primary1))};
}
&:hover,
&:active {
background-color: ${({ theme, selected }) => (selected ? theme.backgroundInteractive : theme.accentAction)};
}
&:active {
background-color: ${({ theme }) => (selected ? theme.deprecated_bg3 : darken(0.05, theme.deprecated_primary1))};
}
`}
&: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' }}
>
@@ -389,7 +329,7 @@ export default function SwapCurrencyInputPanel({
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

@@ -7,7 +7,6 @@ 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} />

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'
@@ -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,6 +1,5 @@
import jazzicon from '@metamask/jazzicon'
import { useWeb3React } from '@web3-react/core'
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
import useENSAvatar from 'hooks/useENSAvatar'
import { useLayoutEffect, useMemo, useRef, useState } from 'react'
import styled from 'styled-components/macro'
@@ -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)

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,14 +77,7 @@ 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} />
}

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: #99A1BD;
}
.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: #C9D0E7;
border-radius: 4px;
color: #565A69;
color: #5E6887;
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: #99A1BD;
}
.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: #C9D0E7;
border-radius: 4px;
color: #565A69;
color: #5E6887;
font-size: 10px;
padding: 2px 4px;
z-index: 1021;

View File

@@ -3,7 +3,7 @@
exports[`renders currency rows correctly when currencies list is non-empty 1`] = `
<DocumentFragment>
.c7 {
color: #6E727D;
color: #99A1BD;
}
.c4 {
@@ -72,7 +72,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
}
.c3:hover {
background-color: #EDEEF2;
background-color: #C9D0E714;
}
.c6 {
@@ -221,7 +221,7 @@ exports[`renders loading rows when isLoading is true 1`] = `
animation: fAQEyV 1.5s infinite;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
background: linear-gradient( to left,#F7F8FA 25%,#EDEEF2 50%,#F7F8FA 75% );
background: linear-gradient( to left,#F5F6FC 25%,#E8ECFB 50%,#F5F6FC 75% );
background-size: 400%;
border-radius: 12px;
height: 2.4em;

View File

@@ -6,7 +6,6 @@ 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'
@@ -135,7 +134,6 @@ export function CurrencyRow({
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'
@@ -168,7 +166,7 @@ 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}>

View File

@@ -13,7 +13,6 @@ 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'
@@ -21,10 +20,10 @@ 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 { 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'
@@ -37,16 +36,6 @@ const ContentWrapper = styled(Column)<{ redesignFlag?: boolean }>`
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 +45,6 @@ interface CurrencySearchProps {
showCommonBases?: boolean
showCurrencyAmount?: boolean
disableNonToken?: boolean
showManageView: () => void
}
export function CurrencySearch({
@@ -68,7 +56,6 @@ export function CurrencySearch({
disableNonToken,
onDismiss,
isOpen,
showManageView,
}: CurrencySearchProps) {
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
@@ -117,20 +104,17 @@ export function CurrencySearch({
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) => {
@@ -160,17 +144,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
@@ -242,13 +226,13 @@ export function CurrencySearch({
)}
/>
</Column>
) : filteredSortedTokens?.length > 0 || filteredInactiveTokens?.length > 0 || isLoading ? (
) : 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}
@@ -269,26 +253,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,7 +1,6 @@
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'
@@ -44,7 +43,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,23 +58,16 @@ 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
@@ -91,7 +83,6 @@ export default memo(function CurrencySearchModal({
// 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]
@@ -117,13 +108,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}
@@ -137,9 +127,7 @@ export default memo(function CurrencySearchModal({
case CurrencyModalView.importToken:
if (importToken) {
modalHeight = undefined
if (tokenSafetyFlag === TokenSafetyVariant.Enabled) {
showTokenSafetySpeedbump(importToken)
}
showTokenSafetySpeedbump(importToken)
content = (
<ImportToken
tokens={[importToken]}

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: #0E111A;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
@@ -34,19 +34,19 @@ exports[`ResizableTextArea renders correctly 1`] = `
}
.c0::-webkit-input-placeholder {
color: #C3C5CB;
color: #99A1BD;
}
.c0::-moz-placeholder {
color: #C3C5CB;
color: #99A1BD;
}
.c0:-ms-input-placeholder {
color: #C3C5CB;
color: #99A1BD;
}
.c0::placeholder {
color: #C3C5CB;
color: #99A1BD;
}
<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: #0E111A;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
@@ -96,19 +96,19 @@ exports[`TextInput renders correctly 1`] = `
}
.c0::-webkit-input-placeholder {
color: #C3C5CB;
color: #99A1BD;
}
.c0::-moz-placeholder {
color: #C3C5CB;
color: #99A1BD;
}
.c0:-ms-input-placeholder {
color: #C3C5CB;
color: #99A1BD;
}
.c0::placeholder {
color: #C3C5CB;
color: #99A1BD;
}
<div

View File

@@ -1,11 +1,10 @@
import { Trans } from '@lingui/macro'
import { NativeCurrency, Token } from '@uniswap/sdk-core'
import { Currency, 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 { PriceDurations, PricePoint, SingleTokenData } from 'graphql/data/Token'
import { TopToken } from 'graphql/data/TopTokens'
import { CHAIN_NAME_TO_CHAIN_ID, TimePeriod } from 'graphql/data/util'
@@ -15,8 +14,8 @@ import { useMemo } from 'react'
import styled from 'styled-components/macro'
import { textFadeIn } from 'theme/animations'
import { filterTimeAtom, useIsFavorited, useToggleFavorite } from '../state'
import { ClickFavorited, FavoriteIcon, L2NetworkLogo, LogoContainer } from '../TokenTable/TokenRow'
import { filterTimeAtom } from '../state'
import { L2NetworkLogo, LogoContainer } from '../TokenTable/TokenRow'
import PriceChart from './PriceChart'
import ShareButton from './ShareButton'
@@ -70,17 +69,17 @@ export function useTokenLogoURI(
export default function ChartSection({
token,
currency,
nativeCurrency,
prices,
}: {
token: NonNullable<SingleTokenData>
currency?: Currency | null
nativeCurrency?: Token | NativeCurrency
prices: PriceDurations
}) {
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 L2Icon = getChainInfo(chainId)?.circleLogoUrl
const warning = checkWarning(token.address ?? '')
const timePeriod = useAtomValue(filterTimeAtom)
@@ -111,7 +110,12 @@ export default function ChartSection({
<TokenInfoContainer>
<TokenNameCell>
<LogoContainer>
<CurrencyLogo src={logoSrc} size={'32px'} symbol={nativeCurrency?.symbol ?? token.symbol} />
<CurrencyLogo
src={logoSrc}
size={'32px'}
symbol={nativeCurrency?.symbol ?? token.symbol}
currency={nativeCurrency ? undefined : currency}
/>
<L2NetworkLogo networkUrl={L2Icon} size={'16px'} />
</LogoContainer>
{nativeCurrency?.name ?? token.name ?? <Trans>Name not found</Trans>}
@@ -119,14 +123,7 @@ export default function ChartSection({
{!warning && <VerifiedIcon size="16px" />}
</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>
)}
{token.name && token.symbol && token.address && <ShareButton token={token} isNative={!!nativeCurrency} />}
</TokenActions>
</TokenInfoContainer>
<ChartContainer>

View File

@@ -82,7 +82,8 @@ export default function MobileBalanceSummaryFooter({
tokenAmount,
nativeCurrencyAmount,
isNative,
}: BalanceSummaryProps) {
tokenAddress,
}: BalanceSummaryProps & { tokenAddress: string }) {
const balanceUsdValue = useStablecoinValue(tokenAmount)
const nativeBalanceUsdValue = useStablecoinValue(nativeCurrencyAmount)
@@ -97,8 +98,6 @@ export default function MobileBalanceSummaryFooter({
: undefined
const nativeBalanceUsd = nativeBalanceUsdValue ? currencyAmountToPreciseFloat(nativeBalanceUsdValue) : undefined
const outputTokenAddress = tokenAmount?.currency.address ?? nativeCurrencyAmount?.wrapped.currency.address
return (
<Wrapper>
{Boolean(formattedBalance !== undefined && !isNative && tokenAmount?.greaterThan(0)) && (
@@ -123,7 +122,7 @@ export default function MobileBalanceSummaryFooter({
</BalanceTotal>
</BalanceInfo>
)}
<SwapButton to={`/swap?outputCurrency=${outputTokenAddress}`}>
<SwapButton to={`/swap?outputCurrency=${tokenAddress}`}>
<Trans>Swap</Trans>
</SwapButton>
</Wrapper>

View File

@@ -1,4 +1,6 @@
import { Trans } from '@lingui/macro'
import { NATIVE_CHAIN_ID } from 'constants/tokens'
import { SingleTokenData } 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<SingleTokenData>
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

@@ -1,35 +0,0 @@
import { Trans } from '@lingui/macro'
import { useAtom } from 'jotai'
import { Heart } from 'react-feather'
import styled, { useTheme } from 'styled-components/macro'
import { SMALLEST_MOBILE_MEDIA_BREAKPOINT } from '../constants'
import { showFavoritesAtom } from '../state'
import FilterOption from './FilterOption'
const FavoriteButtonContent = styled.div`
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
`
const FavoriteText = styled.span`
@media only screen and (max-width: ${SMALLEST_MOBILE_MEDIA_BREAKPOINT}) {
display: none;
}
`
export default function FavoriteButton() {
const theme = useTheme()
const [showFavorites, setShowFavorites] = useAtom(showFavoritesAtom)
return (
<FilterOption onClick={() => setShowFavorites(!showFavorites)} active={showFavorites} highlight>
<FavoriteButtonContent>
<Heart size={20} color={showFavorites ? theme.accentActive : theme.textPrimary} />
<FavoriteText>
<Trans>Favorites</Trans>
</FavoriteText>
</FavoriteButtonContent>
</FilterOption>
)
}

View File

@@ -99,14 +99,14 @@ export default function NetworkFilter() {
const { chainName } = useParams<{ chainName?: string }>()
const currentChainName = validateUrlChainParam(chainName)
const { label, circleLogoUrl, logoUrl } = getChainInfo(CHAIN_NAME_TO_CHAIN_ID[currentChainName])
const chainInfo = getChainInfo(CHAIN_NAME_TO_CHAIN_ID[currentChainName])
return (
<StyledMenu ref={node}>
<NetworkFilterOption onClick={toggleMenu} aria-label={`networkFilter`} active={open}>
<StyledMenuContent>
<NetworkLabel>
<Logo src={circleLogoUrl ?? logoUrl} /> {label}
<Logo src={chainInfo?.logoUrl} /> {chainInfo?.label}
</NetworkLabel>
<Chevron open={open}>
{open ? (
@@ -121,6 +121,7 @@ export default function NetworkFilter() {
<MenuTimeFlyout>
{BACKEND_CHAIN_NAMES.map((network) => {
const chainInfo = getChainInfo(CHAIN_NAME_TO_CHAIN_ID[network])
if (!chainInfo) return null
return (
<InternalLinkMenuItem
key={network}
@@ -130,7 +131,7 @@ export default function NetworkFilter() {
}}
>
<NetworkLabel>
<Logo src={chainInfo.circleLogoUrl ?? chainInfo.logoUrl} />
<Logo src={chainInfo.logoUrl} />
{chainInfo.label}
</NetworkLabel>
{network === currentChainName && (

View File

@@ -5,15 +5,13 @@ import { EventName } from 'analytics/constants'
import SparklineChart from 'components/Charts/SparklineChart'
import CurrencyLogo from 'components/CurrencyLogo'
import { getChainInfo } from 'constants/chainInfo'
import { FavoriteTokensVariant, useFavoriteTokensFlag } from 'featureFlags/flags/favoriteTokens'
import { SparklineMap, TopToken } from 'graphql/data/TopTokens'
import { CHAIN_NAME_TO_CHAIN_ID, getTokenDetailsURL } from 'graphql/data/util'
import { useAtomValue } from 'jotai/utils'
import { ForwardedRef, forwardRef } from 'react'
import { CSSProperties, ReactNode } from 'react'
import { ArrowDown, ArrowUp, Heart } from 'react-feather'
import { ArrowDown, ArrowUp } from 'react-feather'
import { Link, useParams } from 'react-router-dom'
import { Text } from 'rebass'
import styled, { css, useTheme } from 'styled-components/macro'
import { ClickableStyle } from 'theme'
import { formatDollar } from 'utils/formatNumbers'
@@ -31,9 +29,7 @@ import {
sortAscendingAtom,
sortMethodAtom,
TokenSortMethod,
useIsFavorited,
useSetSortMethod,
useToggleFavorite,
} from '../state'
import { useTokenLogoURI } from '../TokenDetails/ChartSection'
import InfoTip from '../TokenDetails/InfoTip'
@@ -48,13 +44,11 @@ const StyledTokenRow = styled.div<{
first?: boolean
last?: boolean
loading?: boolean
favoriteTokensEnabled?: boolean
}>`
background-color: transparent;
display: grid;
font-size: 16px;
grid-template-columns: ${({ favoriteTokensEnabled }) =>
favoriteTokensEnabled ? '1fr 7fr 4fr 4fr 4fr 4fr 5fr 1.2fr' : '1fr 7fr 4fr 4fr 4fr 4fr 5fr'};
grid-template-columns: 1fr 7fr 4fr 4fr 4fr 4fr 5fr;
line-height: 24px;
max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT};
min-width: 390px;
@@ -111,23 +105,6 @@ const StyledTokenRow = styled.div<{
}
}
`
export const ClickFavorited = styled.span`
display: flex;
align-items: center;
cursor: pointer;
&:hover {
opacity: 60%;
}
`
export const FavoriteIcon = styled(Heart)<{ isFavorited: boolean }>`
${ClickableStyle}
height: 22px;
width: 24px;
color: ${({ isFavorited, theme }) => (isFavorited ? theme.accentAction : theme.textSecondary)};
fill: ${({ isFavorited, theme }) => (isFavorited ? theme.accentAction : 'transparent')};
`
const ClickableContent = styled.div`
display: flex;
@@ -140,15 +117,6 @@ const ClickableName = styled(ClickableContent)`
gap: 8px;
max-width: 100%;
`
const FavoriteCell = styled(Cell)`
min-width: 40px;
color: ${({ theme }) => theme.textSecondary};
fill: none;
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
display: none;
}
`
const StyledHeaderRow = styled(StyledTokenRow)`
border-bottom: 1px solid;
border-color: ${({ theme }) => theme.backgroundOutline};
@@ -183,14 +151,13 @@ const DataCell = styled(Cell)<{ sortable: boolean }>`
justify-content: flex-end;
min-width: 80px;
user-select: ${({ sortable }) => (sortable ? 'none' : 'unset')};
transition: ${({
theme: {
transition: { duration, timing },
},
}) => css`background-color ${duration.medium} ${timing.ease}`};
`
const MarketCapCell = styled(DataCell)`
const TvlCell = styled(DataCell)`
padding-right: 8px;
@media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) {
display: none;
@@ -239,9 +206,10 @@ const HeaderCellWrapper = styled.span<{ onClick?: () => void }>`
height: 100%;
justify-content: flex-end;
width: 100%;
`
const HeaderCellText = styled(Text)`
${ClickableStyle}
&:hover {
${ClickableStyle}
}
`
const SparkLineCell = styled(Cell)`
padding: 0px 24px;
@@ -369,7 +337,7 @@ function HeaderCell({
)}
</>
)}
<HeaderCellText>{category}</HeaderCellText>
{category}
{description && <InfoTip text={description}></InfoTip>}
</HeaderCellWrapper>
)
@@ -377,23 +345,21 @@ function HeaderCell({
/* Token Row: skeleton row component */
export function TokenRow({
favorited,
header,
listNumber,
tokenInfo,
price,
percentChange,
marketCap,
tvl,
volume,
sparkLine,
...rest
}: {
favorited: ReactNode
first?: boolean
header: boolean
listNumber: ReactNode
loading?: boolean
marketCap: ReactNode
tvl: ReactNode
price: ReactNode
percentChange: ReactNode
sparkLine?: ReactNode
@@ -402,25 +368,19 @@ export function TokenRow({
last?: boolean
style?: CSSProperties
}) {
const favoriteTokensEnabled = useFavoriteTokensFlag() === FavoriteTokensVariant.Enabled
const rowCells = (
<>
<ListNumberCell header={header}>{listNumber}</ListNumberCell>
<NameCell>{tokenInfo}</NameCell>
<PriceCell sortable={header}>{price}</PriceCell>
<PercentChangeCell sortable={header}>{percentChange}</PercentChangeCell>
<MarketCapCell sortable={header}>{marketCap}</MarketCapCell>
<TvlCell sortable={header}>{tvl}</TvlCell>
<VolumeCell sortable={header}>{volume}</VolumeCell>
<SparkLineCell>{sparkLine}</SparkLineCell>
{favoriteTokensEnabled && <FavoriteCell>{favorited}</FavoriteCell>}
</>
)
if (header) return <StyledHeaderRow favoriteTokensEnabled={favoriteTokensEnabled}>{rowCells}</StyledHeaderRow>
return (
<StyledTokenRow favoriteTokensEnabled={favoriteTokensEnabled} {...rest}>
{rowCells}
</StyledTokenRow>
)
if (header) return <StyledHeaderRow>{rowCells}</StyledHeaderRow>
return <StyledTokenRow {...rest}>{rowCells}</StyledTokenRow>
}
/* Header Row: top header row component for table */
@@ -428,12 +388,11 @@ export function HeaderRow() {
return (
<TokenRow
header={true}
favorited={null}
listNumber="#"
tokenInfo={<Trans>Token Name</Trans>}
tokenInfo={<Trans>Token name</Trans>}
price={<HeaderCell category={TokenSortMethod.PRICE} sortable />}
percentChange={<HeaderCell category={TokenSortMethod.PERCENT_CHANGE} sortable />}
marketCap={<HeaderCell category={TokenSortMethod.TOTAL_VALUE_LOCKED} sortable />}
tvl={<HeaderCell category={TokenSortMethod.TOTAL_VALUE_LOCKED} sortable />}
volume={<HeaderCell category={TokenSortMethod.VOLUME} sortable />}
sparkLine={null}
/>
@@ -444,7 +403,6 @@ export function HeaderRow() {
export function LoadingRow() {
return (
<TokenRow
favorited={null}
header={false}
listNumber={<SmallLoadingBubble />}
loading
@@ -456,7 +414,7 @@ export function LoadingRow() {
}
price={<MediumLoadingBubble />}
percentChange={<LoadingBubble />}
marketCap={<LoadingBubble />}
tvl={<LoadingBubble />}
volume={<LoadingBubble />}
sparkLine={<SparkLineLoadingBubble />}
/>
@@ -476,14 +434,12 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
const tokenAddress = token.address
const tokenName = token.name
const tokenSymbol = token.symbol
const isFavorited = useIsFavorited(tokenAddress)
const toggleFavorite = useToggleFavorite(tokenAddress)
const filterString = useAtomValue(filterStringAtom)
const sortAscending = useAtomValue(sortAscendingAtom)
const lowercaseChainName = useParams<{ chainName?: string }>().chainName?.toUpperCase() ?? 'ethereum'
const filterNetwork = lowercaseChainName.toUpperCase()
const L2Icon = getChainInfo(CHAIN_NAME_TO_CHAIN_ID[filterNetwork]).circleLogoUrl
const L2Icon = getChainInfo(CHAIN_NAME_TO_CHAIN_ID[filterNetwork])?.circleLogoUrl
const timePeriod = useAtomValue(filterTimeAtom)
const delta = token.market?.pricePercentChange?.value
const arrow = getDeltaArrow(delta)
@@ -510,16 +466,6 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
>
<TokenRow
header={false}
favorited={
<ClickFavorited
onClick={(e) => {
e.preventDefault()
toggleFavorite()
}}
>
<FavoriteIcon isFavorited={isFavorited} />
</ClickFavorited>
}
listNumber={rank}
tokenInfo={
<ClickableName>
@@ -536,9 +482,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
price={
<ClickableContent>
<PriceInfoCell>
{token.market?.price?.value
? formatDollar({ num: token.market.price.value, isPrice: true, lessPreciseStablecoinValues: true })
: '-'}
{formatDollar({ num: token.market?.price?.value, isPrice: true, lessPreciseStablecoinValues: true })}
<PercentChangeInfoCell>
{formattedDelta}
{arrow}
@@ -552,16 +496,8 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
{arrow}
</ClickableContent>
}
marketCap={
<ClickableContent>
{token.market?.totalValueLocked?.value ? formatDollar({ num: token.market.totalValueLocked.value }) : '-'}
</ClickableContent>
}
volume={
<ClickableContent>
{token.market?.volume?.value ? formatDollar({ num: token.market.volume.value }) : '-'}
</ClickableContent>
}
tvl={<ClickableContent>{formatDollar({ num: token.market?.totalValueLocked?.value })}</ClickableContent>}
volume={<ClickableContent>{formatDollar({ num: token.market?.volume?.value })}</ClickableContent>}
sparkLine={
<SparkLine>
<ParentSize>

View File

@@ -1,8 +1,6 @@
import { Trans } from '@lingui/macro'
import { showFavoritesAtom } from 'components/Tokens/state'
import { PAGE_SIZE, useTopTokens } from 'graphql/data/TopTokens'
import { validateUrlChainParam } from 'graphql/data/util'
import { useAtomValue } from 'jotai/utils'
import { ReactNode } from 'react'
import { AlertTriangle } from 'react-feather'
import { useParams } from 'react-router-dom'
@@ -80,8 +78,6 @@ export function LoadingTokenTable({ rowCount }: { rowCount?: number }) {
}
export default function TokenTable({ setRowCount }: { setRowCount: (c: number) => void }) {
const showFavorites = useAtomValue<boolean>(showFavoritesAtom)
// TODO: consider moving prefetched call into app.tsx and passing it here, use a preloaded call & updated on interval every 60s
const chainName = validateUrlChainParam(useParams<{ chainName?: string }>().chainName)
const { tokens, sparklines } = useTopTokens(chainName)
@@ -100,11 +96,7 @@ export default function TokenTable({ setRowCount }: { setRowCount: (c: number) =
/>
)
} else if (tokens?.length === 0) {
return showFavorites ? (
<NoTokensState message={<Trans>You have no favorited tokens</Trans>} />
) : (
<NoTokensState message={<Trans>No tokens found</Trans>} />
)
return <NoTokensState message={<Trans>No tokens found</Trans>} />
} else {
return (
<GridContainer>

View File

@@ -1,8 +1,10 @@
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'analytics/constants'
import { TraceEvent } from 'analytics/TraceEvent'
import { chainIdToBackendName } from 'graphql/data/util'
import { X } from 'react-feather'
import { Link, useNavigate } from 'react-router-dom'
import { Link } from 'react-router-dom'
import { useShowTokensPromoBanner } from 'state/user/hooks'
import styled, { useTheme } from 'styled-components/macro'
import { opacify } from 'theme/utils'
@@ -11,26 +13,32 @@ import { Z_INDEX } from 'theme/zIndex'
import tokensPromoDark from '../../assets/images/tokensPromoDark.png'
import tokensPromoLight from '../../assets/images/tokensPromoLight.png'
const PopupContainer = styled.div<{ show: boolean }>`
position: fixed;
display: ${({ show }) => (show ? 'flex' : 'none')};
flex-direction: column;
padding: 12px 16px 12px 20px;
gap: 8px;
bottom: 48px;
right: 16px;
width: 320px;
height: 88px;
z-index: ${Z_INDEX.sticky};
background-color: ${({ theme }) => (theme.darkMode ? theme.backgroundScrim : opacify(60, '#FDF0F8'))};
color: ${({ theme }) => theme.textPrimary};
const BackgroundColor = styled(Link)<{ show: boolean }>`
background-color: ${({ theme }) => (theme.darkMode ? theme.backgroundScrim : '#FDF0F8')};
border: 1px solid ${({ theme }) => theme.backgroundOutline};
border-radius: 12px;
bottom: 48px;
box-shadow: ${({ theme }) => theme.deepShadow};
display: ${({ show }) => (show ? 'block' : 'none')};
height: 88px;
position: fixed;
right: clamp(0px, 1vw, 16px);
text-decoration: none;
width: 320px;
z-index: ${Z_INDEX.sticky};
`
const PopupContainer = styled.div`
background-color: ${({ theme }) => (theme.darkMode ? theme.backgroundScrim : opacify(60, '#FDF0F8'))};
background-image: url(${({ theme }) => (theme.darkMode ? `${tokensPromoDark}` : `${tokensPromoLight}`)});
background-size: cover;
background-blend-mode: overlay;
border-radius: 12px;
color: ${({ theme }) => theme.textPrimary};
display: flex;
flex-direction: column;
gap: 8px;
height: 100%;
padding: 12px 16px 12px 20px;
transition: ${({
theme: {
@@ -39,58 +47,54 @@ const PopupContainer = styled.div<{ show: boolean }>`
}) => `${duration.slow} opacity ${timing.in}`};
`
const Header = styled.div`
display: flex;
align-items: center;
display: flex;
justify-content: space-between;
`
const HeaderText = styled(Link)`
const HeaderText = styled.span`
font-weight: 600;
font-size: 14px;
line-height: 20px;
text-decoration: none;
color: ${({ theme }) => theme.textPrimary};
`
const Description = styled(Link)`
const Description = styled.span`
font-weight: 400;
font-size: 12px;
line-height: 16px;
width: 75%;
text-decoration: none;
color: ${({ theme }) => theme.textPrimary};
width: max(212px, calc(100% - 36px));
`
export default function TokensBanner() {
const theme = useTheme()
const [showTokensPromoBanner, setShowTokensPromoBanner] = useShowTokensPromoBanner()
const navigate = useNavigate()
const { chainId: connectedChainId } = useWeb3React()
const chainName = chainIdToBackendName(connectedChainId).toLowerCase()
const navigateToExplorePage = () => {
navigate(`/tokens/${chainName}`)
}
return (
<PopupContainer show={showTokensPromoBanner} onClick={navigateToExplorePage}>
<Header>
<HeaderText to={'/tokens'}>
<Trans>Explore Top Tokens on Uniswap</Trans>
</HeaderText>
<X
size={20}
color={theme.textSecondary}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setShowTokensPromoBanner(false)
}}
style={{ cursor: 'pointer' }}
/>
</Header>
<BackgroundColor show={showTokensPromoBanner} to={`/tokens/${chainName}`}>
<TraceEvent events={[Event.onClick]} name={EventName.EXPLORE_BANNER_CLICKED} element={ElementName.EXPLORE_BANNER}>
<PopupContainer>
<Header>
<HeaderText>
<Trans>Explore Top Tokens on Uniswap</Trans>
</HeaderText>
<X
size={20}
color={theme.textSecondary}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setShowTokensPromoBanner(false)
}}
style={{ cursor: 'pointer' }}
/>
</Header>
<Description to={'/tokens'}>
<Trans>Sort and filter assets across networks on the new Tokens page.</Trans>
</Description>
</PopupContainer>
<Description>
<Trans>Sort and filter assets across networks on the new Tokens page.</Trans>
</Description>
</PopupContainer>
</TraceEvent>
</BackgroundColor>
)
}

View File

@@ -1,7 +1,7 @@
import { TimePeriod } from 'graphql/data/util'
import { atom, useAtom } from 'jotai'
import { atomWithReset, atomWithStorage, useAtomValue } from 'jotai/utils'
import { useCallback, useMemo } from 'react'
import { atomWithReset } from 'jotai/utils'
import { useCallback } from 'react'
export enum TokenSortMethod {
PRICE = 'Price',
@@ -10,31 +10,11 @@ export enum TokenSortMethod {
VOLUME = 'Volume',
}
export const favoritesAtom = atomWithStorage<string[]>('favorites', [])
export const showFavoritesAtom = atomWithStorage<boolean>('showFavorites', false)
export const filterStringAtom = atomWithReset<string>('')
export const filterTimeAtom = atom<TimePeriod>(TimePeriod.DAY)
export const sortMethodAtom = atom<TokenSortMethod>(TokenSortMethod.VOLUME)
export const sortAscendingAtom = atom<boolean>(false)
/* for favoriting tokens */
export function useToggleFavorite(tokenAddress: string | undefined | null) {
const [favoriteTokens, updateFavoriteTokens] = useAtom(favoritesAtom)
return useCallback(() => {
if (!tokenAddress) return
let updatedFavoriteTokens
if (favoriteTokens.includes(tokenAddress.toLocaleLowerCase())) {
updatedFavoriteTokens = favoriteTokens.filter((address: string) => {
return address !== tokenAddress.toLocaleLowerCase()
})
} else {
updatedFavoriteTokens = [...favoriteTokens, tokenAddress.toLocaleLowerCase()]
}
updateFavoriteTokens(updatedFavoriteTokens)
}, [favoriteTokens, tokenAddress, updateFavoriteTokens])
}
/* keep track of sort category for token table */
export function useSetSortMethod(newSortMethod: TokenSortMethod) {
const [sortMethod, setSortMethod] = useAtom(sortMethodAtom)
@@ -49,12 +29,3 @@ export function useSetSortMethod(newSortMethod: TokenSortMethod) {
}
}, [sortMethod, setSortMethod, setSortAscending, sortAscending, newSortMethod])
}
export function useIsFavorited(tokenAddress: string | null | undefined) {
const favoritedTokens = useAtomValue<string[]>(favoritesAtom)
return useMemo(
() => (tokenAddress ? favoritedTokens.includes(tokenAddress.toLocaleLowerCase()) : false),
[favoritedTokens, tokenAddress]
)
}

View File

@@ -3,7 +3,6 @@ import AddressClaimModal from 'components/claim/AddressClaimModal'
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
import TokensBanner from 'components/Tokens/TokensBanner'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
import { TokensVariant, useTokensFlag } from 'featureFlags/flags/tokens'
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
import { lazy } from 'react'
import { useLocation } from 'react-router-dom'
@@ -27,8 +26,7 @@ export default function TopLevelModals() {
<>
<AddressClaimModal isOpen={addressClaimOpen} onDismiss={addressClaimToggle} />
<ConnectedAccountBlocked account={account} isOpen={open} />
{useTokensFlag() === TokensVariant.Enabled &&
(location.pathname.includes('/pool') || location.pathname.includes('/swap')) && <TokensBanner />}
{(location.pathname.includes('/pool') || location.pathname.includes('/swap')) && <TokensBanner />}
<Bag />
{useNftFlag() === NftVariant.Enabled && <TransactionCompleteModal />}
</>

View File

@@ -3,7 +3,6 @@ import { Percent } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { L2_CHAIN_IDS } from 'constants/chains'
import { DEFAULT_DEADLINE_FROM_NOW } from 'constants/misc'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import ms from 'ms.macro'
import { darken } from 'polished'
import { useState } from 'react'
@@ -42,9 +41,9 @@ const FancyButton = styled.button`
}
`
const Option = styled(FancyButton)<{ active: boolean; redesignFlag: boolean }>`
const Option = styled(FancyButton)<{ active: boolean }>`
margin-right: 8px;
border-radius: ${({ redesignFlag }) => redesignFlag && '12px'};
border-radius: 12px;
:hover {
cursor: pointer;
}
@@ -52,10 +51,10 @@ const Option = styled(FancyButton)<{ active: boolean; redesignFlag: boolean }>`
color: ${({ active, theme }) => (active ? theme.deprecated_white : theme.deprecated_text1)};
`
const Input = styled.input<{ redesignFlag: boolean }>`
const Input = styled.input`
background: ${({ theme }) => theme.deprecated_bg1};
font-size: 16px;
border-radius: ${({ redesignFlag }) => redesignFlag && '12px'};
border-radius: 12px;
width: auto;
outline: none;
&::-webkit-outer-spin-button,
@@ -66,15 +65,15 @@ const Input = styled.input<{ redesignFlag: boolean }>`
text-align: right;
::placeholder {
color: ${({ theme, redesignFlag }) => redesignFlag && theme.textTertiary};
color: ${({ theme }) => theme.textTertiary};
}
`
const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean; redesignFlag: boolean }>`
const OptionCustom = styled(FancyButton)<{ active?: boolean; warning?: boolean }>`
height: 2rem;
position: relative;
padding: 0 0.75rem;
border-radius: ${({ redesignFlag }) => redesignFlag && '12px'};
border-radius: 12px;
flex: 1;
border: ${({ theme, active, warning }) =>
active
@@ -109,8 +108,6 @@ const THREE_DAYS_IN_SECONDS = ms`3 days` / 1000
export default function TransactionSettings({ placeholderSlippage }: TransactionSettingsProps) {
const { chainId } = useWeb3React()
const theme = useTheme()
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const [userSlippageTolerance, setUserSlippageTolerance] = useUserSlippageTolerance()
@@ -185,7 +182,6 @@ export default function TransactionSettings({ placeholderSlippage }: Transaction
</RowFixed>
<RowBetween>
<Option
redesignFlag={redesignFlagEnabled}
onClick={() => {
parseSlippageInput('')
}}
@@ -193,12 +189,7 @@ export default function TransactionSettings({ placeholderSlippage }: Transaction
>
<Trans>Auto</Trans>
</Option>
<OptionCustom
redesignFlag={redesignFlagEnabled}
active={userSlippageTolerance !== 'auto'}
warning={!!slippageError}
tabIndex={-1}
>
<OptionCustom active={userSlippageTolerance !== 'auto'} warning={!!slippageError} tabIndex={-1}>
<RowBetween>
{tooLow || tooHigh ? (
<SlippageEmojiContainer>
@@ -208,7 +199,6 @@ export default function TransactionSettings({ placeholderSlippage }: Transaction
</SlippageEmojiContainer>
) : null}
<Input
redesignFlag={redesignFlagEnabled}
placeholder={placeholderSlippage.toFixed(2)}
value={
slippageInput.length > 0
@@ -258,14 +248,8 @@ export default function TransactionSettings({ placeholderSlippage }: Transaction
/>
</RowFixed>
<RowFixed>
<OptionCustom
style={{ width: '80px' }}
warning={!!deadlineError}
tabIndex={-1}
redesignFlag={redesignFlagEnabled}
>
<OptionCustom style={{ width: '80px' }} warning={!!deadlineError} tabIndex={-1}>
<Input
redesignFlag={redesignFlagEnabled}
placeholder={(DEFAULT_DEADLINE_FROM_NOW / 60).toString()}
value={
deadlineInput.length > 0

View File

@@ -1,26 +1,22 @@
import { ElementName, Event, EventName } from 'analytics/constants'
import { TraceEvent } from 'analytics/TraceEvent'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import React from 'react'
import { Check } from 'react-feather'
import styled from 'styled-components/macro'
import { ExternalLink } from '../../theme'
const InfoCard = styled.button<{ isActive?: boolean; redesignFlag?: boolean }>`
background-color: ${({ theme, isActive, redesignFlag }) =>
redesignFlag ? theme.backgroundInteractive : isActive ? theme.deprecated_bg3 : theme.deprecated_bg2};
const InfoCard = styled.button<{ isActive?: boolean }>`
background-color: ${({ theme }) => theme.backgroundInteractive};
padding: 1rem;
outline: none;
border: 1px solid;
border-radius: 12px;
width: 100% !important;
&:focus {
box-shadow: ${({ theme, redesignFlag }) => !redesignFlag && `0 0 0 1px ${theme.deprecated_primary1}`};
background-color: ${({ theme, redesignFlag }) => redesignFlag && theme.hoverState};
background-color: ${({ theme }) => theme.hoverState};
}
border-color: ${({ theme, isActive, redesignFlag }) =>
redesignFlag ? (isActive ? theme.accentActive : 'transparent') : isActive ? 'transparent' : theme.deprecated_bg3};
border-color: ${({ theme, isActive }) => (isActive ? theme.accentActive : 'transparent')};
`
const CheckIcon = styled(Check)`
@@ -53,48 +49,24 @@ const OptionCardLeft = styled.div`
const OptionCardClickable = styled(OptionCard as any)<{
active?: boolean
clickable?: boolean
redesignFlag?: boolean
}>`
margin-top: 0;
border: ${({ active, theme }) => active && `1px solid ${theme.accentActive}`};
&:hover {
cursor: ${({ clickable }) => clickable && 'pointer'};
background-color: ${({ theme, redesignFlag }) => redesignFlag && theme.hoverState};
border: ${({ clickable, redesignFlag, theme }) =>
clickable && !redesignFlag && `1px solid ${theme.deprecated_primary1}`};
background-color: ${({ theme }) => theme.hoverState};
}
opacity: ${({ disabled }) => (disabled ? '0.5' : '1')};
`
const GreenCircle = styled.div`
${({ theme }) => theme.flexRowNoWrap}
justify-content: center;
align-items: center;
&:first-child {
height: 8px;
width: 8px;
margin-right: 8px;
background-color: ${({ theme }) => theme.deprecated_green1};
border-radius: 50%;
}
`
const CircleWrapper = styled.div`
color: ${({ theme }) => theme.deprecated_green1};
display: flex;
justify-content: center;
align-items: center;
`
const HeaderText = styled.div<{ redesignFlag?: boolean }>`
const HeaderText = styled.div`
${({ theme }) => theme.flexRowNoWrap};
align-items: center;
justify-content: center;
color: ${(props) =>
props.color === 'blue' ? ({ theme }) => theme.deprecated_primary1 : ({ theme }) => theme.deprecated_text1};
font-size: ${({ redesignFlag }) => (redesignFlag ? '16px' : '1rem')};
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
font-size: 16px;
font-weight: 600;
`
const SubHeader = styled.div`
@@ -103,20 +75,6 @@ const SubHeader = styled.div`
font-size: 12px;
`
const IconWrapperDeprecated = styled.div<{ size?: number | null }>`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
justify-content: center;
& > img,
span {
height: ${({ size }) => (size ? size + 'px' : '24px')};
width: ${({ size }) => (size ? size + 'px' : '24px')};
}
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
align-items: flex-end;
`};
`
const IconWrapper = styled.div<{ size?: number | null }>`
${({ theme }) => theme.flexColumnNoWrap};
align-items: center;
@@ -155,9 +113,6 @@ export default function Option({
isActive?: boolean
id: string
}) {
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const content = (
<TraceEvent
events={[Event.onClick]}
@@ -165,53 +120,24 @@ export default function Option({
properties={{ wallet_type: header }}
element={ElementName.WALLET_TYPE_OPTION}
>
{redesignFlagEnabled ? (
<OptionCardClickable
id={id}
onClick={onClick}
clickable={clickable && !isActive}
active={isActive}
redesignFlag={true}
data-testid="wallet-modal-option"
>
<OptionCardLeft>
<HeaderText color={color} redesignFlag={true}>
<IconWrapper size={size}>
<img src={icon} alt={'Icon'} />
</IconWrapper>
{header}
</HeaderText>
{subheader && <SubHeader>{subheader}</SubHeader>}
</OptionCardLeft>
{isActive && <CheckIcon />}
</OptionCardClickable>
) : (
<OptionCardClickable
id={id}
onClick={onClick}
clickable={clickable && !isActive}
active={isActive}
redesignFlag={false}
data-testid="wallet-modal-option"
>
<OptionCardLeft>
<HeaderText color={color} redesignFlag={false}>
{isActive && (
<CircleWrapper>
<GreenCircle>
<div />
</GreenCircle>
</CircleWrapper>
)}
{header}
</HeaderText>
{subheader && <SubHeader>{subheader}</SubHeader>}
</OptionCardLeft>
<IconWrapperDeprecated size={size}>
<img src={icon} alt={'Icon'} />
</IconWrapperDeprecated>
</OptionCardClickable>
)}
<OptionCardClickable
id={id}
onClick={onClick}
clickable={clickable && !isActive}
active={isActive}
data-testid="wallet-modal-option"
>
<OptionCardLeft>
<HeaderText color={color}>
<IconWrapper size={size}>
<img src={icon} alt={'Icon'} />
</IconWrapper>
{header}
</HeaderText>
{subheader && <SubHeader>{subheader}</SubHeader>}
</OptionCardLeft>
{isActive && <CheckIcon />}
</OptionCardClickable>
</TraceEvent>
)
if (link) {

View File

@@ -8,7 +8,6 @@ import { AutoColumn } from 'components/Column'
import { AutoRow } from 'components/Row'
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsInjected, getIsMetaMask } from 'connection/utils'
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import usePrevious from 'hooks/usePrevious'
import { useCallback, useEffect, useState } from 'react'
import { ArrowLeft } from 'react-feather'
@@ -47,30 +46,30 @@ const CloseColor = styled(Close)`
}
`
const Wrapper = styled.div<{ redesignFlag?: boolean }>`
const Wrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap}
background-color: ${({ redesignFlag, theme }) => redesignFlag && theme.backgroundSurface};
outline: ${({ theme, redesignFlag }) => redesignFlag && `1px solid ${theme.backgroundOutline}`};
box-shadow: ${({ redesignFlag, theme }) => redesignFlag && theme.deepShadow};
background-color: ${({ theme }) => theme.backgroundSurface};
outline: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
box-shadow: ${({ theme }) => theme.deepShadow};
margin: 0;
padding: 0;
width: 100%;
`
const HeaderRow = styled.div<{ redesignFlag?: boolean }>`
const HeaderRow = styled.div`
${({ theme }) => theme.flexRowNoWrap};
padding: 1rem 1rem;
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
size: ${({ redesignFlag }) => redesignFlag && '16px'};
font-weight: 600;
size: 16px;
color: ${(props) => (props.color === 'blue' ? ({ theme }) => theme.deprecated_primary1 : 'inherit')};
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
padding: 1rem;
`};
`
const ContentWrapper = styled.div<{ redesignFlag?: boolean }>`
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundSurface : theme.deprecated_bg0)};
border: ${({ theme, redesignFlag }) => redesignFlag && `1px solid ${theme.backgroundOutline}`};
const ContentWrapper = styled.div`
background-color: ${({ theme }) => theme.backgroundSurface};
border: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
padding: 0 1rem 1rem 1rem;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
@@ -155,8 +154,6 @@ export default function WalletModal({
const [connectedWallets, addWalletToConnectedWallets] = useConnectedWallets()
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const nftFlagEnabled = useNftFlag() === NftVariant.Enabled
const [walletView, setWalletView] = useState(WALLET_VIEWS.ACCOUNT)
const [lastActiveWalletAddress, setLastActiveWalletAddress] = useState<string | undefined>(account)
@@ -301,7 +298,7 @@ export default function WalletModal({
)
} else {
headerRow = (
<HeaderRow redesignFlag={redesignFlagEnabled}>
<HeaderRow>
<HoverText>
<Trans>Connect a wallet</Trans>
</HoverText>
@@ -367,16 +364,8 @@ export default function WalletModal({
}
return (
<Modal
isOpen={walletModalOpen}
onDismiss={toggleWalletModal}
minHeight={false}
maxHeight={90}
redesignFlag={redesignFlagEnabled}
>
<Wrapper data-testid="wallet-modal" redesignFlag={redesignFlagEnabled}>
{getModalContent()}
</Wrapper>
<Modal isOpen={walletModalOpen} onDismiss={toggleWalletModal} minHeight={false} maxHeight={90}>
<Wrapper data-testid="wallet-modal">{getModalContent()}</Wrapper>
</Modal>
)
}

View File

@@ -1,11 +1,9 @@
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { ElementName, Event, EventName } from 'analytics/constants'
import { TraceEvent } from 'analytics/TraceEvent'
import WalletDropdown from 'components/WalletDropdown'
import { getConnection } from 'connection/utils'
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
import { Portal } from 'nft/components/common/Portal'
import { getIsValidSwapQuote } from 'pages/Swap'
import { darken } from 'polished'
@@ -13,10 +11,9 @@ import { useMemo, useRef } from 'react'
import { AlertTriangle, ChevronDown, ChevronUp } from 'react-feather'
import { useAppSelector } from 'state/hooks'
import { useDerivedSwapInfo } from 'state/swap/hooks'
import styled, { css, useTheme } from 'styled-components/macro'
import styled, { useTheme } from 'styled-components/macro'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import { useHasSocks } from '../../hooks/useSocksBalance'
import {
useCloseModal,
useModalIsOpen,
@@ -36,6 +33,23 @@ import WalletModal from '../WalletModal'
// https://stackoverflow.com/a/31617326
const FULL_BORDER_RADIUS = 9999
const ChevronWrapper = styled.button`
background-color: transparent;
border: none;
cursor: pointer;
display: flex;
padding: 10px 16px 10px 4px;
:hover {
color: ${({ theme }) => theme.accentActionSoft};
}
:hover,
:active,
:focus {
border: none;
}
`
const Web3StatusGeneric = styled(ButtonSecondary)`
${({ theme }) => theme.flexRowNoWrap}
width: 100%;
@@ -62,14 +76,13 @@ const Web3StatusError = styled(Web3StatusGeneric)`
}
`
const Web3StatusConnectButton = styled.button<{ faded?: boolean }>`
const Web3StatusConnectWrapper = styled.div<{ faded?: boolean }>`
${({ theme }) => theme.flexRowNoWrap}
align-items: center;
background-color: ${({ theme }) => theme.accentActionSoft};
border-radius: ${FULL_BORDER_RADIUS}px;
border: none;
cursor: pointer;
padding: 0 12px;
padding: 0;
height: 40px;
:hover,
@@ -79,33 +92,6 @@ const Web3StatusConnectButton = styled.button<{ faded?: boolean }>`
}
`
const Web3StatusConnect = styled(Web3StatusGeneric)<{ faded?: boolean }>`
background-color: ${({ theme }) => theme.deprecated_primary4};
border: none;
color: ${({ theme }) => theme.deprecated_primaryText1};
font-weight: 500;
:hover,
:focus {
border: 1px solid ${({ theme }) => darken(0.05, theme.deprecated_primary4)};
color: ${({ theme }) => theme.deprecated_primaryText1};
}
${({ faded }) =>
faded &&
css`
background-color: ${({ theme }) => theme.deprecated_primary5};
border: 1px solid ${({ theme }) => theme.deprecated_primary5};
color: ${({ theme }) => theme.deprecated_primaryText1};
:hover,
:focus {
border: 1px solid ${({ theme }) => darken(0.05, theme.deprecated_primary4)};
color: ${({ theme }) => darken(0.05, theme.deprecated_primaryText1)};
}
`}
`
const Web3StatusConnected = styled(Web3StatusGeneric)<{ pending?: boolean }>`
background-color: ${({ pending, theme }) => (pending ? theme.deprecated_primary1 : theme.deprecated_bg1)};
border: 1px solid ${({ pending, theme }) => (pending ? theme.deprecated_primary1 : theme.deprecated_bg1)};
@@ -146,34 +132,37 @@ function newTransactionsFirst(a: TransactionDetails, b: TransactionDetails) {
return b.addedTime - a.addedTime
}
function Sock() {
return (
<span role="img" aria-label={t`has socks emoji`} style={{ marginTop: -4, marginBottom: -4 }}>
🧦
</span>
)
}
const VerticalDivider = styled.div`
height: 20px;
margin: 0px 4px;
margin: 0px;
width: 1px;
background-color: ${({ theme }) => theme.accentAction};
`
const StyledConnect = styled.div`
const StyledConnectButton = styled.button`
background-color: transparent;
border: none;
border-top-left-radius: ${FULL_BORDER_RADIUS}px;
border-bottom-left-radius: ${FULL_BORDER_RADIUS}px;
color: ${({ theme }) => theme.accentAction};
cursor: pointer;
font-weight: 600;
font-size: 16px;
margin-right: 8px;
padding: 10px 8px 10px 12px;
&:hover {
transition: ${({
theme: {
transition: { duration, timing },
},
}) => `${duration.fast} color ${timing.in}`};
:hover,
:active,
:focus {
border: none;
}
:hover {
color: ${({ theme }) => theme.accentActionSoft};
transition: ${({
theme: {
transition: { duration, timing },
},
}) => `${duration.fast} color ${timing.in}`};
}
`
@@ -190,11 +179,10 @@ function Web3StatusInner() {
inputError: swapInputError,
} = useDerivedSwapInfo()
const validSwapQuote = getIsValidSwapQuote(trade, tradeState, swapInputError)
const navbarFlagEnabled = useNavBarFlag() === NavBarVariant.Enabled
const theme = useTheme()
const toggleWalletDropdown = useToggleWalletDropdown()
const toggleWalletModal = useToggleWalletModal()
const walletIsOpen = useIsOpen()
const walletIsOpen = useModalIsOpen(ApplicationModal.WALLET_DROPDOWN)
const error = useAppSelector((state) => state.connection.errorByConnectionType[getConnection(connector).type])
@@ -208,8 +196,7 @@ function Web3StatusInner() {
const pending = sortedRecentTransactions.filter((tx) => !tx.receipt).map((tx) => tx.hash)
const hasPendingTransactions = !!pending.length
const hasSocks = useHasSocks()
const toggleWallet = navbarFlagEnabled ? toggleWalletDropdown : toggleWalletModal
const toggleWallet = toggleWalletDropdown
if (!chainId) {
return null
@@ -229,7 +216,7 @@ function Web3StatusInner() {
}
return (
<Web3StatusConnected data-testid="web3-status-connected" onClick={toggleWallet} pending={hasPendingTransactions}>
{navbarFlagEnabled && !hasPendingTransactions && <StatusIcon size={24} connectionType={connectionType} />}
{!hasPendingTransactions && <StatusIcon size={24} connectionType={connectionType} />}
{hasPendingTransactions ? (
<RowBetween>
<Text>
@@ -239,18 +226,10 @@ function Web3StatusInner() {
</RowBetween>
) : (
<>
{hasSocks && !navbarFlagEnabled ? <Sock /> : null}
<Text>{ENSName || shortenAddress(account)}</Text>
{navbarFlagEnabled ? (
walletIsOpen ? (
<ChevronUp {...chevronProps} />
) : (
<ChevronDown {...chevronProps} />
)
) : null}
{walletIsOpen ? <ChevronUp {...chevronProps} /> : <ChevronDown {...chevronProps} />}
</>
)}
{!navbarFlagEnabled && !hasPendingTransactions && <StatusIcon connectionType={connectionType} />}
</Web3StatusConnected>
)
} else {
@@ -258,7 +237,6 @@ function Web3StatusInner() {
...CHEVRON_PROPS,
color: theme.accentAction,
'data-testid': 'navbar-wallet-dropdown',
onClick: toggleWalletDropdown,
}
return (
<TraceEvent
@@ -267,33 +245,20 @@ function Web3StatusInner() {
properties={{ received_swap_quote: validSwapQuote }}
element={ElementName.CONNECT_WALLET_BUTTON}
>
{navbarFlagEnabled ? (
<Web3StatusConnectButton faded={!account}>
<StyledConnect data-testid="navbar-connect-wallet" onClick={toggleWalletModal}>
<Trans>Connect</Trans>
</StyledConnect>
<VerticalDivider />
<Web3StatusConnectWrapper faded={!account}>
<StyledConnectButton data-testid="navbar-connect-wallet" onClick={toggleWalletModal}>
<Trans>Connect</Trans>
</StyledConnectButton>
<VerticalDivider />
<ChevronWrapper onClick={toggleWalletDropdown}>
{walletIsOpen ? <ChevronUp {...chevronProps} /> : <ChevronDown {...chevronProps} />}
</Web3StatusConnectButton>
) : (
<Web3StatusConnect onClick={toggleWallet} faded={!account}>
<Text>
<Trans>Connect Wallet</Trans>
</Text>
</Web3StatusConnect>
)}
</ChevronWrapper>
</Web3StatusConnectWrapper>
</TraceEvent>
)
}
}
const useIsOpen = () => {
const walletDropdownOpen = useModalIsOpen(ApplicationModal.WALLET_DROPDOWN)
const navbarFlag = useNavBarFlag()
return useMemo(() => navbarFlag === NavBarVariant.Enabled && walletDropdownOpen, [navbarFlag, walletDropdownOpen])
}
export default function Web3Status() {
const { ENSName } = useWeb3React()
@@ -301,7 +266,7 @@ export default function Web3Status() {
const ref = useRef<HTMLDivElement>(null)
const walletRef = useRef<HTMLDivElement>(null)
const closeModal = useCloseModal(ApplicationModal.WALLET_DROPDOWN)
const isOpen = useIsOpen()
const isOpen = useModalIsOpen(ApplicationModal.WALLET_DROPDOWN)
useOnClickOutside(ref, isOpen ? closeModal : undefined, [walletRef])

View File

@@ -1,12 +1,30 @@
// Import fonts.css for the side-effect of loading fonts for @uniswap/widgets.
// eslint-disable-next-line no-restricted-imports
import '@uniswap/widgets/dist/fonts.css'
import { Currency, EMPTY_TOKEN_LIST, OnReviewSwapClick, SwapWidget, SwapWidgetSkeleton } from '@uniswap/widgets'
import { Trade } from '@uniswap/router-sdk'
import { Currency, TradeType } from '@uniswap/sdk-core'
import {
AddEthereumChainParameter,
EMPTY_TOKEN_LIST,
OnReviewSwapClick,
SwapWidget,
SwapWidgetSkeleton,
} from '@uniswap/widgets'
import { useWeb3React } from '@web3-react/core'
import { sendAnalyticsEvent } from 'analytics'
import { EventName, SectionName, SWAP_PRICE_UPDATE_USER_RESPONSE } from 'analytics/constants'
import { useTrace } from 'analytics/Trace'
import {
formatPercentInBasisPointsNumber,
formatSwapQuoteReceivedEventProperties,
formatToDecimal,
getDurationFromDateMilliseconds,
getPriceUpdateBasisPoints,
getTokenAddress,
} from 'analytics/utils'
import { useActiveLocale } from 'hooks/useActiveLocale'
import { useCallback, useState } from 'react'
import { useIsDarkMode } from 'state/user/hooks'
import { DARK_THEME, LIGHT_THEME } from 'theme/widget'
import { computeRealizedPriceImpact } from 'utils/prices'
import { switchChain } from 'utils/switchChain'
import { useSyncWidgetInputs } from './inputs'
import { useSyncWidgetSettings } from './settings'
@@ -24,29 +42,123 @@ export interface WidgetProps {
export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps) {
const locale = useActiveLocale()
const theme = useIsDarkMode() ? DARK_THEME : LIGHT_THEME
const { provider } = useWeb3React()
const { connector, provider } = useWeb3React()
const { inputs, tokenSelector } = useSyncWidgetInputs(defaultToken)
const { settings } = useSyncWidgetSettings()
const { transactions } = useSyncWidgetTransactions()
const trace = useTrace({ section: SectionName.WIDGET })
const [initialQuoteDate, setInitialQuoteDate] = useState<Date>()
const onInitialSwapQuote = useCallback(
(trade: Trade<Currency, Currency, TradeType>) => {
setInitialQuoteDate(new Date())
const eventProperties = {
// TODO(1416): Include undefined values.
...formatSwapQuoteReceivedEventProperties(
trade,
/* gasUseEstimateUSD= */ undefined,
/* fetchingSwapQuoteStartTime= */ undefined
),
...trace,
}
sendAnalyticsEvent(EventName.SWAP_QUOTE_RECEIVED, eventProperties)
},
[trace]
)
const onApproveToken = useCallback(() => {
const input = inputs.value.INPUT
if (!input) return
const eventProperties = {
chain_id: input.chainId,
token_symbol: input.symbol,
token_address: getTokenAddress(input),
...trace,
}
sendAnalyticsEvent(EventName.APPROVE_TOKEN_TXN_SUBMITTED, eventProperties)
}, [inputs.value.INPUT, trace])
const onExpandSwapDetails = useCallback(() => {
sendAnalyticsEvent(EventName.SWAP_DETAILS_EXPANDED, { ...trace })
}, [trace])
const onSwapPriceUpdateAck = useCallback(
(stale: Trade<Currency, Currency, TradeType>, update: Trade<Currency, Currency, TradeType>) => {
const eventProperties = {
chain_id: update.inputAmount.currency.chainId,
response: SWAP_PRICE_UPDATE_USER_RESPONSE.ACCEPTED,
token_in_symbol: update.inputAmount.currency.symbol,
token_out_symbol: update.outputAmount.currency.symbol,
price_update_basis_points: getPriceUpdateBasisPoints(stale.executionPrice, update.executionPrice),
...trace,
}
sendAnalyticsEvent(EventName.SWAP_PRICE_UPDATE_ACKNOWLEDGED, eventProperties)
},
[trace]
)
const onSubmitSwapClick = useCallback(
(trade: Trade<Currency, Currency, TradeType>) => {
const eventProperties = {
// TODO(1416): Include undefined values.
estimated_network_fee_usd: undefined,
transaction_deadline_seconds: undefined,
token_in_address: getTokenAddress(trade.inputAmount.currency),
token_out_address: getTokenAddress(trade.outputAmount.currency),
token_in_symbol: trade.inputAmount.currency.symbol,
token_out_symbol: trade.outputAmount.currency.symbol,
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
token_in_amount_usd: undefined,
token_out_amount_usd: undefined,
price_impact_basis_points: formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)),
allowed_slippage_basis_points: undefined,
is_auto_router_api: undefined,
is_auto_slippage: undefined,
chain_id: trade.inputAmount.currency.chainId,
duration_from_first_quote_to_swap_submission_milliseconds: getDurationFromDateMilliseconds(initialQuoteDate),
swap_quote_block_number: undefined,
...trace,
}
sendAnalyticsEvent(EventName.SWAP_SUBMITTED_BUTTON_CLICKED, eventProperties)
},
[initialQuoteDate, trace]
)
const onSwitchChain = useCallback(
// TODO: Widget should not break if this rejects - upstream the catch to ignore it.
({ chainId }: AddEthereumChainParameter) => switchChain(connector, Number(chainId)).catch(() => undefined),
[connector]
)
if (!inputs.value.INPUT && !inputs.value.OUTPUT) {
return <WidgetSkeleton />
}
return (
<>
<SwapWidget
disableBranding
hideConnectionUI
// jsonRpcUrlMap is excluded - network providers are always passed directly
routerUrl={WIDGET_ROUTER_URL}
width={WIDGET_WIDTH}
locale={locale}
theme={theme}
onReviewSwapClick={onReviewSwapClick}
// defaultChainId is excluded - it is always inferred from the passed provider
provider={provider}
onSwitchChain={onSwitchChain}
tokenList={EMPTY_TOKEN_LIST} // prevents loading the default token list, as we use our own token selector UI
{...inputs}
{...settings}
{...transactions}
onExpandSwapDetails={onExpandSwapDetails}
onReviewSwapClick={onReviewSwapClick}
onSubmitSwapClick={onSubmitSwapClick}
onSwapApprove={onApproveToken}
onInitialSwapQuote={onInitialSwapQuote}
onSwapPriceUpdateAck={onSwapPriceUpdateAck}
/>
{tokenSelector}
</>

View File

@@ -1,4 +1,7 @@
import { Currency, Field, SwapController, SwapEventHandlers, TradeType } from '@uniswap/widgets'
import { sendAnalyticsEvent } from 'analytics'
import { EventName, SectionName } from 'analytics/constants'
import { useTrace } from 'analytics/Trace'
import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal'
import { useCallback, useEffect, useMemo, useState } from 'react'
@@ -9,18 +12,28 @@ const EMPTY_AMOUNT = ''
* Treats the Widget as a controlled component, using the app's own token selector for selection.
*/
export function useSyncWidgetInputs(defaultToken?: Currency) {
const trace = useTrace({ section: SectionName.WIDGET })
const [type, setType] = useState(TradeType.EXACT_INPUT)
const [amount, setAmount] = useState(EMPTY_AMOUNT)
const onAmountChange = useCallback((field: Field, amount: string) => {
setType(toTradeType(field))
setAmount(amount)
}, [])
const onAmountChange = useCallback(
(field: Field, amount: string, origin?: 'max') => {
if (origin === 'max') {
sendAnalyticsEvent(EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED, { ...trace })
}
setType(toTradeType(field))
setAmount(amount)
},
[trace]
)
const [tokens, setTokens] = useState<{ [Field.INPUT]?: Currency; [Field.OUTPUT]?: Currency }>({
[Field.OUTPUT]: defaultToken,
})
useEffect(() => {
// Avoid overwriting tokens if none are specified, so that a loading token does not cause layout flashing.
if (!defaultToken) return
setTokens({
[Field.OUTPUT]: defaultToken,
})
@@ -28,12 +41,13 @@ export function useSyncWidgetInputs(defaultToken?: Currency) {
}, [defaultToken])
const onSwitchTokens = useCallback(() => {
sendAnalyticsEvent(EventName.SWAP_TOKENS_REVERSED, { ...trace })
setType((type) => invertTradeType(type))
setTokens((tokens) => ({
[Field.INPUT]: tokens[Field.OUTPUT],
[Field.OUTPUT]: tokens[Field.INPUT],
}))
}, [])
}, [trace])
const [selectingField, setSelectingField] = useState<Field>()
const otherField = useMemo(() => (selectingField === Field.INPUT ? Field.OUTPUT : Field.INPUT), [selectingField])
@@ -51,7 +65,7 @@ export function useSyncWidgetInputs(defaultToken?: Currency) {
setType(TradeType.EXACT_INPUT)
setTokens(() => {
return {
[otherField]: token === otherToken ? selectingToken : otherToken,
[otherField]: otherToken?.equals(token) ? selectingToken : otherToken,
[selectingField]: token,
}
})
@@ -68,7 +82,7 @@ export function useSyncWidgetInputs(defaultToken?: Currency) {
/>
)
const value: SwapController = useMemo(() => ({ type, amount, ...tokens }), [amount, tokens, type])
const value: SwapController['value'] = useMemo(() => ({ type, amount, ...tokens }), [amount, tokens, type])
const valueHandlers: SwapEventHandlers = useMemo(
() => ({ onAmountChange, onSwitchTokens, onTokenSelectorClick }),
[onAmountChange, onSwitchTokens, onTokenSelectorClick]

View File

@@ -1,5 +1,5 @@
import { Percent } from '@uniswap/sdk-core'
import { Slippage, SwapEventHandlers, SwapSettingsController } from '@uniswap/widgets'
import { Slippage, SwapController, SwapEventHandlers } from '@uniswap/widgets'
import { DEFAULT_DEADLINE_FROM_NOW } from 'constants/misc'
import { useCallback, useMemo, useState } from 'react'
import { useUserSlippageTolerance, useUserTransactionTTL } from 'state/user/hooks'
@@ -44,7 +44,7 @@ export function useSyncWidgetSettings() {
setAppSlippage('auto')
}, [setAppSlippage, setAppTtl])
const settings: SwapSettingsController = useMemo(() => {
const settings: SwapController['settings'] = useMemo(() => {
const auto = appSlippage === 'auto'
return { slippage: { auto, max: widgetSlippage }, transactionTtl: widgetTtl }
}, [widgetSlippage, widgetTtl, appSlippage])

View File

@@ -6,6 +6,12 @@ import {
TransactionType as WidgetTransactionType,
} from '@uniswap/widgets'
import { useWeb3React } from '@web3-react/core'
import { sendAnalyticsEvent } from 'analytics'
import { EventName, SectionName } from 'analytics/constants'
import { useTrace } from 'analytics/Trace'
import { formatToDecimal, getTokenAddress } from 'analytics/utils'
import { formatSwapSignedAnalyticsEventProperties } from 'analytics/utils'
import { WrapType } from 'hooks/useWrapCallback'
import { useCallback, useMemo } from 'react'
import { useTransactionAdder } from 'state/transactions/hooks'
import {
@@ -18,6 +24,8 @@ import { currencyId } from 'utils/currencyId'
/** Integrates the Widget's transactions, showing the widget's transactions in the app. */
export function useSyncWidgetTransactions() {
const trace = useTrace({ section: SectionName.WIDGET })
const { chainId } = useWeb3React()
const addTransaction = useTransactionAdder()
@@ -28,8 +36,23 @@ export function useSyncWidgetTransactions() {
if (!type || !response) {
return
} else if (type === WidgetTransactionType.WRAP || type === WidgetTransactionType.UNWRAP) {
const { amount } = transaction.info
const { type, amount: transactionAmount } = transaction.info
const eventProperties = {
// get this info from widget handlers
token_in_address: getTokenAddress(transactionAmount.currency),
token_out_address: getTokenAddress(transactionAmount.currency.wrapped),
token_in_symbol: transactionAmount.currency.symbol,
token_out_symbol: transactionAmount.currency.wrapped.symbol,
chain_id: transactionAmount.currency.chainId,
amount: transactionAmount
? formatToDecimal(transactionAmount, transactionAmount?.currency.decimals)
: undefined,
type: type === WidgetTransactionType.WRAP ? WrapType.WRAP : WrapType.UNWRAP,
...trace,
}
sendAnalyticsEvent(EventName.WRAP_TOKEN_TXN_SUBMITTED, eventProperties)
const { amount } = transaction.info
addTransaction(response, {
type: AppTransactionType.WRAP,
unwrapped: type === WidgetTransactionType.UNWRAP,
@@ -38,6 +61,15 @@ export function useSyncWidgetTransactions() {
} as WrapTransactionInfo)
} else if (type === WidgetTransactionType.SWAP) {
const { slippageTolerance, trade, tradeType } = transaction.info
const eventProperties = {
...formatSwapSignedAnalyticsEventProperties({
trade,
txHash: transaction.receipt?.transactionHash ?? '',
}),
...trace,
}
sendAnalyticsEvent(EventName.SWAP_SIGNED, eventProperties)
const baseTxInfo = {
type: AppTransactionType.SWAP,
tradeType,
@@ -61,7 +93,7 @@ export function useSyncWidgetTransactions() {
}
}
},
[addTransaction, chainId]
[addTransaction, chainId, trace]
)
const txHandlers: TransactionEventHandlers = useMemo(() => ({ onTxSubmit }), [onTxSubmit])

View File

@@ -4,7 +4,6 @@ import { useWeb3React } from '@web3-react/core'
import Card from 'components/Card'
import { LoadingRows } from 'components/Loader/styled'
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useMemo } from 'react'
import { InterfaceTrade } from 'state/routing/types'
@@ -55,8 +54,6 @@ export function AdvancedSwapDetails({
const theme = useTheme()
const { chainId } = useWeb3React()
const nativeCurrency = useNativeCurrency()
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const { expectedOutputAmount, priceImpact } = useMemo(() => {
return {
@@ -109,7 +106,7 @@ export function AdvancedSwapDetails({
</ThemedText.DeprecatedBlack>
</TextWithLoadingPlaceholder>
</RowBetween>
<Separator redesignFlag={redesignFlagEnabled} />
<Separator />
<RowBetween>
<RowFixed style={{ marginRight: '20px' }}>
<MouseoverTooltip

View File

@@ -5,10 +5,9 @@ import { sendAnalyticsEvent } from 'analytics'
import { ModalName } from 'analytics/constants'
import { EventName } from 'analytics/constants'
import { Trace } from 'analytics/Trace'
import { formatPercentInBasisPointsNumber, formatToDecimal, getTokenAddress } from 'analytics/utils'
import { formatSwapSignedAnalyticsEventProperties } from 'analytics/utils'
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
import { InterfaceTrade } from 'state/routing/types'
import { computeRealizedPriceImpact } from 'utils/prices'
import { tradeMeaningfullyDiffers } from 'utils/tradeMeaningFullyDiffer'
import TransactionConfirmationModal, {
@@ -18,27 +17,6 @@ import TransactionConfirmationModal, {
import SwapModalFooter from './SwapModalFooter'
import SwapModalHeader from './SwapModalHeader'
const formatAnalyticsEventProperties = ({
trade,
txHash,
}: {
trade: InterfaceTrade<Currency, Currency, TradeType>
txHash: string
}) => ({
transaction_hash: txHash,
token_in_address: getTokenAddress(trade.inputAmount.currency),
token_out_address: getTokenAddress(trade.outputAmount.currency),
token_in_symbol: trade.inputAmount.currency.symbol,
token_out_symbol: trade.outputAmount.currency.symbol,
token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals),
token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals),
price_impact_basis_points: formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)),
chain_id:
trade.inputAmount.currency.chainId === trade.outputAmount.currency.chainId
? trade.inputAmount.currency.chainId
: undefined,
})
export default function ConfirmSwapModal({
trade,
originalTrade,
@@ -149,7 +127,7 @@ export default function ConfirmSwapModal({
useEffect(() => {
if (!attemptingTxn && isOpen && txHash && trade && txHash !== lastTxnHashLogged) {
sendAnalyticsEvent(EventName.SWAP_SIGNED, formatAnalyticsEventProperties({ trade, txHash }))
sendAnalyticsEvent(EventName.SWAP_SIGNED, formatSwapSignedAnalyticsEventProperties({ trade, txHash }))
setLastTxnHashLogged(txHash)
}
}, [attemptingTxn, isOpen, txHash, trade, lastTxnHashLogged])

View File

@@ -10,7 +10,6 @@ import { LoadingOpacityContainer } from 'components/Loader/styled'
import Row, { RowBetween, RowFixed } from 'components/Row'
import { MouseoverTooltipContent } from 'components/Tooltip'
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { useState } from 'react'
import { ChevronDown, Info } from 'react-feather'
import { InterfaceTrade } from 'state/routing/types'
@@ -23,13 +22,13 @@ import { ResponsiveTooltipContainer } from './styleds'
import SwapRoute from './SwapRoute'
import TradePrice from './TradePrice'
const Wrapper = styled(Row)<{ redesignFlag: boolean }>`
const Wrapper = styled(Row)`
width: 100%;
justify-content: center;
border-radius: ${({ redesignFlag }) => redesignFlag && 'inherit'};
padding: ${({ redesignFlag }) => redesignFlag && '8px 12px'};
margin-top: ${({ redesignFlag }) => (redesignFlag ? '0px' : '4px')};
min-height: ${({ redesignFlag }) => redesignFlag && '32px'};
border-radius: inherit;
padding: 8px 12px;
margin-top: 0;
min-height: 32px;
`
const StyledInfoIcon = styled(Info)`
@@ -39,18 +38,15 @@ const StyledInfoIcon = styled(Info)`
color: ${({ theme }) => theme.deprecated_text3};
`
const StyledCard = styled(OutlineCard)<{ redesignFlag: boolean }>`
const StyledCard = styled(OutlineCard)`
padding: 12px;
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundOutline : theme.deprecated_bg3)};
border: 1px solid ${({ theme }) => theme.backgroundOutline};
`
const StyledHeaderRow = styled(RowBetween)<{ disabled: boolean; open: boolean; redesignFlag: boolean }>`
padding: ${({ redesignFlag }) => (redesignFlag ? '0' : '4px 8px')};
background-color: ${({ open, theme, redesignFlag }) =>
open && !redesignFlag ? theme.deprecated_bg1 : 'transparent'};
const StyledHeaderRow = styled(RowBetween)<{ disabled: boolean; open: boolean }>`
padding: 0;
align-items: center;
cursor: ${({ disabled }) => (disabled ? 'initial' : 'pointer')};
border-radius: ${({ redesignFlag }) => !redesignFlag && '12px'};
`
const RotatingArrow = styled(ChevronDown)<{ open?: boolean }>`
@@ -130,11 +126,9 @@ export default function SwapDetailsDropdown({
const theme = useTheme()
const { chainId } = useWeb3React()
const [showDetails, setShowDetails] = useState(false)
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
return (
<Wrapper style={{ marginTop: redesignFlagEnabled ? '0' : '8px' }} redesignFlag={redesignFlagEnabled}>
<Wrapper style={{ marginTop: '0' }}>
<AutoColumn gap={'8px'} style={{ width: '100%', marginBottom: '-8px' }}>
<TraceEvent
events={[Event.onClick]}
@@ -142,12 +136,7 @@ export default function SwapDetailsDropdown({
element={ElementName.SWAP_DETAILS_DROPDOWN}
shouldLogImpression={!showDetails}
>
<StyledHeaderRow
redesignFlag={redesignFlagEnabled}
onClick={() => setShowDetails(!showDetails)}
disabled={!trade}
open={showDetails}
>
<StyledHeaderRow onClick={() => setShowDetails(!showDetails)} disabled={!trade} open={showDetails}>
<RowFixed style={{ position: 'relative' }}>
{loading || syncing ? (
<StyledPolling>
@@ -214,7 +203,7 @@ export default function SwapDetailsDropdown({
<AnimatedDropdown open={showDetails}>
<AutoColumn gap={'8px'} style={{ padding: '0', paddingBottom: '8px' }}>
{trade ? (
<StyledCard redesignFlag={redesignFlagEnabled}>
<StyledCard>
<AdvancedSwapDetails trade={trade} allowedSlippage={allowedSlippage} syncing={syncing} />
</StyledCard>
) : null}

View File

@@ -1,10 +1,8 @@
import { Trans } from '@lingui/macro'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Price } from '@uniswap/sdk-core'
import { sendAnalyticsEvent } from 'analytics'
import { EventName, SWAP_PRICE_UPDATE_USER_RESPONSE } from 'analytics/constants'
import { formatPercentInBasisPointsNumber } from 'analytics/utils'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import { getPriceUpdateBasisPoints } from 'analytics/utils'
import { useEffect, useState } from 'react'
import { AlertTriangle, ArrowDown } from 'react-feather'
import { Text } from 'rebass'
@@ -25,11 +23,11 @@ import TradePrice from '../swap/TradePrice'
import { AdvancedSwapDetails } from './AdvancedSwapDetails'
import { SwapShowAcceptChanges, TruncatedText } from './styleds'
const ArrowWrapper = styled.div<{ redesignFlag: boolean }>`
const ArrowWrapper = styled.div`
padding: 4px;
border-radius: 12px;
height: ${({ redesignFlag }) => (redesignFlag ? '40px' : '32px')};
width: ${({ redesignFlag }) => (redesignFlag ? '40px' : '32px')};
height: 40px;
width: 40px;
position: relative;
margin-top: -18px;
margin-bottom: -18px;
@@ -37,9 +35,9 @@ const ArrowWrapper = styled.div<{ redesignFlag: boolean }>`
display: flex;
justify-content: center;
align-items: center;
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundSurface : theme.deprecated_bg1)};
background-color: ${({ theme }) => theme.backgroundSurface};
border: 4px solid;
border-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundModule : theme.deprecated_bg0)};
border-color: ${({ theme }) => theme.backgroundModule};
z-index: 2;
`
@@ -58,15 +56,6 @@ const formatAnalyticsEventProperties = (
price_update_basis_points: priceUpdate,
})
const getPriceUpdateBasisPoints = (
prevPrice: Price<Currency, Currency>,
newPrice: Price<Currency, Currency>
): number => {
const changeFraction = newPrice.subtract(prevPrice).divide(prevPrice)
const changePercentage = new Percent(changeFraction.numerator, changeFraction.denominator)
return formatPercentInBasisPointsNumber(changePercentage)
}
export default function SwapModalHeader({
trade,
shouldLogModalCloseEvent,
@@ -85,8 +74,6 @@ export default function SwapModalHeader({
onAcceptChanges: () => void
}) {
const theme = useTheme()
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const [showInverted, setShowInverted] = useState<boolean>(false)
const [lastExecutionPrice, setLastExecutionPrice] = useState(trade.executionPrice)
@@ -137,8 +124,8 @@ export default function SwapModalHeader({
</RowBetween>
</AutoColumn>
</LightCard>
<ArrowWrapper redesignFlag={redesignFlagEnabled}>
<ArrowDown size="16" color={redesignFlagEnabled ? theme.textPrimary : theme.deprecated_text2} />
<ArrowWrapper>
<ArrowDown size="16" color={theme.textPrimary} />
</ArrowWrapper>
<LightCard padding="0.75rem 1rem" style={{ marginBottom: '0.25rem' }}>
<AutoColumn gap={'8px'}>

View File

@@ -12,7 +12,6 @@ import { LoadingRows } from 'components/Loader/styled'
import RoutingDiagram from 'components/RoutingDiagram/RoutingDiagram'
import { AutoRow, RowBetween } from 'components/Row'
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
import useAutoRouterSupported from 'hooks/useAutoRouterSupported'
import { memo, useState } from 'react'
import { Plus } from 'react-feather'
@@ -23,12 +22,10 @@ import { Separator, ThemedText } from 'theme'
import { AutoRouterLabel, AutoRouterLogo } from './RouterLabel'
const Wrapper = styled(AutoColumn)<{ darkMode?: boolean; fixedOpen?: boolean; redesignFlag: boolean }>`
const Wrapper = styled(AutoColumn)<{ darkMode?: boolean; fixedOpen?: boolean }>`
padding: ${({ fixedOpen }) => (fixedOpen ? '12px' : '12px 8px 12px 12px')};
border-radius: 16px;
border: 1px solid
${({ theme, fixedOpen, redesignFlag }) =>
fixedOpen ? 'transparent' : redesignFlag ? theme.backgroundOutline : theme.deprecated_bg2};
border: 1px solid ${({ theme, fixedOpen }) => (fixedOpen ? 'transparent' : theme.backgroundOutline)};
cursor: pointer;
`
@@ -56,8 +53,6 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r
const routes = getTokenPath(trade)
const [open, setOpen] = useState(false)
const { chainId } = useWeb3React()
const redesignFlag = useRedesignFlag()
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
const [darkMode] = useDarkModeManager()
@@ -68,7 +63,7 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r
: undefined
return (
<Wrapper {...rest} darkMode={darkMode} fixedOpen={fixedOpen} redesignFlag={redesignFlagEnabled}>
<Wrapper {...rest} darkMode={darkMode} fixedOpen={fixedOpen}>
<TraceEvent
events={[Event.onClick]}
name={EventName.SWAP_AUTOROUTER_VISUALIZATION_EXPANDED}

View File

@@ -63,7 +63,7 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra
</Text>{' '}
{usdcPrice && (
<ThemedText.DeprecatedDarkGray>
<Trans>(${formatDollar({ num: priceToPreciseFloat(usdcPrice) })})</Trans>
<Trans>({formatDollar({ num: priceToPreciseFloat(usdcPrice) })})</Trans>
</ThemedText.DeprecatedDarkGray>
)}
</StyledPriceContainer>

View File

@@ -8,45 +8,42 @@ import { Z_INDEX } from 'theme/zIndex'
import { AutoColumn } from '../Column'
export const PageWrapper = styled.div<{ redesignFlag: boolean; navBarFlag: boolean }>`
padding: ${({ navBarFlag }) => (navBarFlag ? '68px 8px 0px' : '0px 8px')};
export const PageWrapper = styled.div`
padding: 68px 8px 0px;
max-width: 480px;
width: 100%;
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
padding-top: ${({ navBarFlag }) => (navBarFlag ? '48px' : '0px')};
padding-top: 48px;
}
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
padding-top: ${({ navBarFlag }) => (navBarFlag ? '20px' : '0px')};
padding-top: 20px;
}
`
// Mostly copied from `AppBody` but it was getting too hard to maintain backwards compatibility.
export const SwapWrapper = styled.main<{ margin?: string; maxWidth?: string; redesignFlag: boolean }>`
export const SwapWrapper = styled.main<{ margin?: string; maxWidth?: string }>`
position: relative;
background: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundSurface : theme.deprecated_bg0)};
border-radius: ${({ redesignFlag }) => (redesignFlag ? '16px' : '24px')};
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundOutline : 'transparent')};
background: ${({ theme }) => theme.backgroundSurface};
border-radius: 16px;
border: 1px solid ${({ theme }) => theme.backgroundOutline};
padding: 8px;
z-index: ${Z_INDEX.deprecated_content};
box-shadow: ${({ redesignFlag }) =>
!redesignFlag &&
'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)'};
`
export const ArrowWrapper = styled.div<{ clickable: boolean; redesignFlag: boolean }>`
export const ArrowWrapper = styled.div<{ clickable: boolean }>`
border-radius: 12px;
height: ${({ redesignFlag }) => (redesignFlag ? '40px' : '32px')};
width: ${({ redesignFlag }) => (redesignFlag ? '40px' : '32px')};
height: 40px;
width: 40px;
position: relative;
margin-top: ${({ redesignFlag }) => (redesignFlag ? '-18px' : '-14px')};
margin-bottom: ${({ redesignFlag }) => (redesignFlag ? '-18px' : '-14px')};
margin-top: -18px;
margin-bottom: -18px;
margin-left: auto;
margin-right: auto;
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundInteractive : theme.deprecated_bg1)};
background-color: ${({ theme }) => theme.backgroundInteractive};
border: 4px solid;
border-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundSurface : theme.deprecated_bg0)};
border-color: ${({ theme }) => theme.backgroundSurface};
z-index: 2;
${({ clickable }) =>

View File

@@ -1,6 +1,5 @@
import celoCircleLogoUrl from 'assets/images/celoCircle.png'
import ethereumLogoUrl from 'assets/images/ethereum-logo.png'
import optimismCircleLogoUrl from 'assets/images/optimismCircle.png'
import polygonCircleLogoUrl from 'assets/images/polygonCircle.png'
import { default as arbitrumCircleLogoUrl, default as arbitrumLogoUrl } from 'assets/svg/arbitrum_logo.svg'
import celoLogo from 'assets/svg/celo_logo.svg'
@@ -114,7 +113,8 @@ const CHAIN_INFO: ChainInfoMap = {
infoLink: 'https://info.uniswap.org/#/optimism/',
label: 'Optimism',
logoUrl: optimismLogoUrl,
circleLogoUrl: optimismCircleLogoUrl,
// Optimism perfers same icon for both
circleLogoUrl: optimismLogoUrl,
statusPage: 'https://optimism.io/status',
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ',
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },

View File

@@ -1,10 +1,6 @@
export enum FeatureFlag {
favoriteTokens = 'favoriteTokens',
navBar = 'navBar',
nft = 'nfts',
redesign = 'redesign',
tokens = 'tokens',
tokenSafety = 'tokenSafety',
traceJsonRpc = 'traceJsonRpc',
multiNetworkBalances = 'multiNetworkBalances',
}

View File

@@ -1,7 +0,0 @@
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useNavBarFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.navBar)
}
export { BaseVariant as NavBarVariant }

View File

@@ -1,7 +1,7 @@
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
import { BaseVariant } from '../index'
export function useRedesignFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.redesign)
return BaseVariant.Enabled
}
export { BaseVariant as RedesignVariant }

View File

@@ -1,7 +0,0 @@
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useTokenSafetyFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.tokenSafety)
}
export { BaseVariant as TokenSafetyVariant }

View File

@@ -1,7 +0,0 @@
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useTokensFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.tokens)
}
export { BaseVariant as TokensVariant }

View File

@@ -1,9 +1,7 @@
import graphql from 'babel-plugin-relay/macro'
import {
favoritesAtom,
filterStringAtom,
filterTimeAtom,
showFavoritesAtom,
sortAscendingAtom,
sortMethodAtom,
TokenSortMethod,
@@ -97,16 +95,11 @@ function useSortedTokens(tokens: NonNullable<TopTokens100Query['response']['topT
function useFilteredTokens(tokens: NonNullable<TopTokens100Query['response']['topTokens']>) {
const filterString = useAtomValue(filterStringAtom)
const favorites = useAtomValue(favoritesAtom)
const showFavorites = useAtomValue(showFavoritesAtom)
const lowercaseFilterString = useMemo(() => filterString.toLowerCase(), [filterString])
return useMemo(() => {
let returnTokens = tokens
if (showFavorites) {
returnTokens = returnTokens?.filter((token) => token?.address && favorites.includes(token.address))
}
if (lowercaseFilterString) {
returnTokens = returnTokens?.filter((token) => {
const addressIncludesFilterString = token?.address?.toLowerCase().includes(lowercaseFilterString)
@@ -116,7 +109,7 @@ function useFilteredTokens(tokens: NonNullable<TopTokens100Query['response']['to
})
}
return returnTokens
}, [tokens, showFavorites, lowercaseFilterString, favorites])
}, [tokens, lowercaseFilterString])
}
// Number of items to render in each fetch in infinite scroll.

View File

@@ -16,7 +16,12 @@ const fetchQuery = (params: RequestParameters, variables: Variables): Promise<Gr
variables,
})
return fetch(URL, { method: 'POST', body, headers }).then((res) => res.json())
return fetch(URL, { method: 'POST', body, headers })
.then((res) => res.json())
.catch((e) => {
console.error(e)
return { data: [] }
})
}
export default fetchQuery

View File

@@ -66,7 +66,7 @@ export const CHAIN_NAME_TO_CHAIN_ID: { [key: string]: SupportedChainId } = {
OPTIMISM: SupportedChainId.OPTIMISM,
}
export const BACKEND_CHAIN_NAMES: Chain[] = ['ARBITRUM', 'CELO', 'ETHEREUM', 'OPTIMISM', 'POLYGON']
export const BACKEND_CHAIN_NAMES: Chain[] = ['ETHEREUM', 'POLYGON', 'OPTIMISM', 'ARBITRUM', 'CELO']
export function isValidBackendChainName(chainName: string | undefined): chainName is Chain {
if (!chainName) return false
@@ -97,5 +97,10 @@ export function unwrapToken<T extends { address: string | null } | null>(chainId
if (address !== nativeAddress) return token
const nativeToken = nativeOnChain(chainId)
return { ...token, ...nativeToken, address: NATIVE_CHAIN_ID }
return {
...token,
...nativeToken,
address: NATIVE_CHAIN_ID,
extensions: undefined, // prevents marking cross-chain wrapped tokens as native
}
}

View File

@@ -14,7 +14,6 @@ import { Provider } from 'react-redux'
import { RelayEnvironmentProvider } from 'react-relay'
import { HashRouter } from 'react-router-dom'
import Blocklist from './components/Blocklist'
import Web3Provider from './components/Web3Provider'
import { LanguageProvider } from './i18n'
import App from './pages/App'
@@ -59,15 +58,13 @@ createRoot(container).render(
<LanguageProvider>
<Web3Provider>
<RelayEnvironmentProvider environment={RelayEnvironment}>
<Blocklist>
<BlockNumberProvider>
<Updaters />
<ThemeProvider>
<ThemedGlobalStyle />
<App />
</ThemeProvider>
</BlockNumberProvider>
</Blocklist>
<BlockNumberProvider>
<Updaters />
<ThemeProvider>
<ThemedGlobalStyle />
<App />
</ThemeProvider>
</BlockNumberProvider>
</RelayEnvironmentProvider>
</Web3Provider>
</LanguageProvider>

View File

@@ -2,16 +2,90 @@ import { arrayify } from '@ethersproject/bytes'
import { parseBytes32String } from '@ethersproject/strings'
import { Currency, Token } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { isSupportedChain } from 'constants/chains'
import ERC20_ABI from 'abis/erc20.json'
import { Erc20 } from 'abis/types'
import { isSupportedChain, SupportedChainId } from 'constants/chains'
import { RPC_PROVIDERS } from 'constants/providers'
import { useBytes32TokenContract, useTokenContract } from 'hooks/useContract'
import { NEVER_RELOAD, useSingleCallResult } from 'lib/hooks/multicall'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useMemo } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import { TOKEN_SHORTHANDS } from '../../constants/tokens'
import { isAddress } from '../../utils'
import { getContract, isAddress } from '../../utils'
import { supportedChainId } from '../../utils/supportedChainId'
/**
* Returns a Token from query data.
* Data should already include all fields except decimals, or it will be considered invalid.
* Returns null if the token is loading or null was passed.
* Returns undefined if invalid or the token does not exist.
*/
export function useTokenFromQuery({
address: tokenAddress,
chainId,
symbol,
name,
project,
}: {
address?: string
chainId?: SupportedChainId
symbol?: string | null
name?: string | null
project?: { logoUrl?: string | null } | null
} = {}): Token | null | undefined {
const { chainId: activeChainId } = useWeb3React()
const address = isAddress(tokenAddress)
const [decimals, setDecimals] = useState<number | null | undefined>(null)
const tokenContract = useTokenContract(chainId === activeChainId ? (address ? address : undefined) : undefined, false)
const { loading, result: [decimalsResult] = [] } = useSingleCallResult(
tokenContract,
'decimals',
undefined,
NEVER_RELOAD
)
useEffect(() => {
if (loading) {
setDecimals(null)
} else if (decimalsResult) {
setDecimals(decimalsResult)
} else if (!address || !chainId || chainId === activeChainId) {
setDecimals(undefined)
} else {
setDecimals(null)
// Load decimals from a cross-chain RPC provider.
const provider = RPC_PROVIDERS[chainId]
const contract = getContract(address, ERC20_ABI, provider) as Erc20
contract
.decimals()
.then((value) => {
if (!stale) setDecimals(value)
})
.catch(() => undefined)
}
let stale = false
return () => {
stale = true
}
}, [activeChainId, address, chainId, decimalsResult, loading])
return useMemo(() => {
if (!chainId || !address) return undefined
if (decimals === null || decimals === undefined) return decimals
if (!symbol || !name) {
return new Token(chainId, address, decimals, symbol ?? undefined, name ?? undefined)
} else {
const logoURI = project?.logoUrl ?? undefined
return new WrappedTokenInfo({ chainId, address, decimals, symbol, name, logoURI })
}
}, [address, chainId, decimals, name, project?.logoUrl, symbol])
}
// parse a name or symbol from a token response
const BYTES32_REGEX = /^0x[a-fA-F0-9]{64}$/
@@ -29,48 +103,43 @@ function parseStringOrBytes32(str: string | undefined, bytes32: string | undefin
* Returns null if token is loading or null was passed.
* Returns undefined if tokenAddress is invalid or token does not exist.
*/
export function useTokenFromNetwork(tokenAddress: string | null | undefined): Token | null | undefined {
export function useTokenFromActiveNetwork(tokenAddress: string | undefined): Token | null | undefined {
const { chainId } = useWeb3React()
const supportedChain = isSupportedChain(chainId)
const formattedAddress = isAddress(tokenAddress)
const tokenContract = useTokenContract(formattedAddress ? formattedAddress : undefined, false)
const tokenContractBytes32 = useBytes32TokenContract(formattedAddress ? formattedAddress : undefined, false)
// TODO: Fix redux-multicall so that these values do not reload.
const tokenName = useSingleCallResult(tokenContract, 'name', undefined, NEVER_RELOAD)
const tokenNameBytes32 = useSingleCallResult(tokenContractBytes32, 'name', undefined, NEVER_RELOAD)
const symbol = useSingleCallResult(tokenContract, 'symbol', undefined, NEVER_RELOAD)
const symbolBytes32 = useSingleCallResult(tokenContractBytes32, 'symbol', undefined, NEVER_RELOAD)
const decimals = useSingleCallResult(tokenContract, 'decimals', undefined, NEVER_RELOAD)
const isLoading = useMemo(
() => decimals.loading || symbol.loading || tokenName.loading,
[decimals.loading, symbol.loading, tokenName.loading]
)
const parsedDecimals = useMemo(() => decimals.result?.[0], [decimals.result])
const parsedSymbol = useMemo(
() => parseStringOrBytes32(symbol.result?.[0], symbolBytes32.result?.[0], 'UNKNOWN'),
[symbol.result, symbolBytes32.result]
)
const parsedName = useMemo(
() => parseStringOrBytes32(tokenName.result?.[0], tokenNameBytes32.result?.[0], 'Unknown Token'),
[tokenName.result, tokenNameBytes32.result]
)
return useMemo(() => {
if (typeof tokenAddress !== 'string' || !supportedChain || !formattedAddress) return undefined
if (decimals.loading || symbol.loading || tokenName.loading) return null
if (decimals.result) {
return new Token(
chainId,
formattedAddress,
decimals.result[0],
parseStringOrBytes32(symbol.result?.[0], symbolBytes32.result?.[0], 'UNKNOWN'),
parseStringOrBytes32(tokenName.result?.[0], tokenNameBytes32.result?.[0], 'Unknown Token')
)
}
return undefined
}, [
formattedAddress,
chainId,
supportedChain,
decimals.loading,
decimals.result,
symbol.loading,
symbol.result,
symbolBytes32.result,
tokenAddress,
tokenName.loading,
tokenName.result,
tokenNameBytes32.result,
])
// If the token is on another chain, we cannot fetch it on-chain, and it is invalid.
if (typeof tokenAddress !== 'string' || !isSupportedChain(chainId) || !formattedAddress) return undefined
if (isLoading || !chainId) return null
if (!parsedDecimals) return undefined
return new Token(chainId, formattedAddress, parsedDecimals, parsedSymbol, parsedName)
}, [chainId, tokenAddress, formattedAddress, isLoading, parsedDecimals, parsedSymbol, parsedName])
}
type TokenMap = { [address: string]: Token }
@@ -84,7 +153,7 @@ export function useTokenFromMapOrNetwork(tokens: TokenMap, tokenAddress?: string
const address = isAddress(tokenAddress)
const token: Token | undefined = address ? tokens[address] : undefined
const tokenFromNetwork = useTokenFromNetwork(token ? undefined : address ? address : undefined)
const tokenFromNetwork = useTokenFromActiveNetwork(token ? undefined : address ? address : undefined)
return tokenFromNetwork ?? token
}
@@ -105,8 +174,7 @@ export function useCurrencyFromMap(tokens: TokenMap, currencyId?: string | null)
const token = useTokenFromMapOrNetwork(tokens, isNative ? undefined : shorthandMatchAddress ?? currencyId)
const supportedChain = isSupportedChain(chainId)
if (currencyId === null || currencyId === undefined || !supportedChain) return null
if (currencyId === null || currencyId === undefined || !isSupportedChain(chainId)) return null
// this case so we use our builtin wrapped token instead of wrapped tokens on token lists
const wrappedNative = nativeCurrency?.wrapped

View File

@@ -56,9 +56,10 @@ export function useTokenBalancesWithLoadingIndicator(
address?: string,
tokens?: (Token | undefined)[]
): [{ [tokenAddress: string]: CurrencyAmount<Token> | undefined }, boolean] {
const { chainId } = useWeb3React() // we cannot fetch balances cross-chain
const validatedTokens: Token[] = useMemo(
() => tokens?.filter((t?: Token): t is Token => isAddress(t?.address) !== false) ?? [],
[tokens]
() => tokens?.filter((t?: Token): t is Token => isAddress(t?.address) !== false && t?.chainId === chainId) ?? [],
[chainId, tokens]
)
const validatedTokenAddresses = useMemo(() => validatedTokens.map((vt) => vt.address), [validatedTokens])

View File

@@ -1,10 +1,9 @@
import type { TokenList } from '@uniswap/token-lists'
import { validateTokenList } from '@uniswap/widgets'
import contenthashToUri from 'lib/utils/contenthashToUri'
import parseENSAddress from 'lib/utils/parseENSAddress'
import uriToHttp from 'lib/utils/uriToHttp'
import validateTokenList from './validateTokenList'
export const DEFAULT_TOKEN_LIST = 'https://gateway.ipfs.io/ipns/tokens.uniswap.org'
const listCache = new Map<string, TokenList>()

View File

@@ -51,10 +51,12 @@ export function useSortTokensByQuery<T extends Token | TokenInfo>(query: string,
const rest: T[] = []
// sort tokens by exact match -> subtring on symbol match -> rest
const trimmedQuery = query.toLowerCase().trim()
tokens.map((token) => {
if (token.symbol?.toLowerCase() === matches[0]) {
const symbol = token.symbol?.toLowerCase()
if (symbol === matches[0]) {
return exactMatches.push(token)
} else if (token.symbol?.toLowerCase().startsWith(query.toLowerCase().trim())) {
} else if (symbol?.startsWith(trimmedQuery)) {
return symbolSubtrings.push(token)
} else {
return rest.push(token)

View File

@@ -1,42 +0,0 @@
import { TokenInfo } from '@uniswap/token-lists'
import { validateTokens } from './validateTokenList'
const INVALID_TOKEN: TokenInfo = {
name: 'Dai Stablecoin',
address: '0xD3ADB33F',
symbol: 'DAI',
decimals: 18,
chainId: 1,
}
const INLINE_TOKEN_LIST = [
{
name: 'Dai Stablecoin',
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
symbol: 'DAI',
decimals: 18,
chainId: 1,
logoURI:
'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x6B175474E89094C44Da98b954EedeAC495271d0F/logo.png',
},
{
name: 'USDCoin',
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
symbol: 'USDC',
decimals: 6,
chainId: 1,
logoURI:
'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png',
},
]
describe('validateTokens', () => {
it('throws on invalid tokens', async () => {
await expect(validateTokens([INVALID_TOKEN])).rejects.toThrowError(/^Token list failed validation:.*address/)
})
it('validates the passed token info', async () => {
await expect(validateTokens(INLINE_TOKEN_LIST)).resolves.toBe(INLINE_TOKEN_LIST)
})
})

View File

@@ -1,54 +0,0 @@
import type { TokenInfo, TokenList } from '@uniswap/token-lists'
import type { Ajv, ValidateFunction } from 'ajv'
enum ValidationSchema {
LIST = 'list',
TOKENS = 'tokens',
}
const validator = new Promise<Ajv>(async (resolve) => {
const [ajv, schema] = await Promise.all([import('ajv'), import('@uniswap/token-lists/src/tokenlist.schema.json')])
const validator = new ajv.default({ allErrors: true })
.addSchema(schema, ValidationSchema.LIST)
// Adds a meta scheme of Pick<TokenList, 'tokens'>
.addSchema(
{
...schema,
$id: schema.$id + '#tokens',
required: ['tokens'],
},
ValidationSchema.TOKENS
)
resolve(validator)
})
function getValidationErrors(validate: ValidateFunction | undefined): string {
return (
validate?.errors?.map((error) => [error.dataPath, error.message].filter(Boolean).join(' ')).join('; ') ??
'unknown error'
)
}
/**
* Validates an array of tokens.
* @param json the TokenInfo[] to validate
*/
export async function validateTokens(json: TokenInfo[]): Promise<TokenInfo[]> {
const validate = (await validator).getSchema(ValidationSchema.TOKENS)
if (validate?.({ tokens: json })) {
return json
}
throw new Error(`Token list failed validation: ${getValidationErrors(validate)}`)
}
/**
* Validates a token list.
* @param json the TokenList to validate
*/
export default async function validateTokenList(json: TokenList): Promise<TokenList> {
const validate = (await validator).getSchema(ValidationSchema.LIST)
if (validate?.(json)) {
return json
}
throw new Error(`Token list failed validation: ${getValidationErrors(validate)}`)
}

View File

@@ -55,7 +55,7 @@ export const ListingButton = ({ onClick, buttonText, showWarningOverride = false
(asset: WalletAsset) => asset.marketplaces === undefined || asset.marketplaces.length === 0
)
const missingExpiration = sellAssets.some((asset) => {
return asset.expirationTime != null && asset.expirationTime - Date.now() < ms`60 seconds`
return asset.expirationTime != null && asset.expirationTime * 1000 - Date.now() < ms`60 seconds`
})
const invalidExpiration = sellAssets.some((asset) => {
return asset.expirationTime != null && isNaN(asset.expirationTime)

View File

@@ -1,12 +1,6 @@
import { BigNumber } from '@ethersproject/bignumber'
import type { JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
import { ConsiderationInputItem } from '@opensea/seaport-js/lib/types'
import {
INVERSE_BASIS_POINTS,
OPENSEA_CROSS_CHAIN_CONDUIT,
OPENSEA_DEFAULT_FEE,
OPENSEA_FEE_ADDRESS,
} from 'nft/queries/openSea'
import { LOOKSRARE_MARKETPLACE_CONTRACT, X2Y2_TRANSFER_CONTRACT } from 'nft/queries'
import { OPENSEA_CROSS_CHAIN_CONDUIT } from 'nft/queries/openSea'
import { AssetRow, CollectionRow, ListingMarket, ListingRow, ListingStatus, WalletAsset } from 'nft/types'
import { approveCollection, signListing } from 'nft/utils/listNfts'
import { Dispatch } from 'react'
@@ -60,9 +54,9 @@ export async function approveCollectionRow(
marketplace.name === 'OpenSea'
? OPENSEA_CROSS_CHAIN_CONDUIT
: marketplace.name === 'Rarible'
? process.env.REACT_APP_LOOKSRARE_MARKETPLACE_CONTRACT
? LOOKSRARE_MARKETPLACE_CONTRACT
: marketplace.name === 'X2Y2'
? process.env.REACT_APP_X2Y2_TRANSFER_CONTRACT
? X2Y2_TRANSFER_CONTRACT
: looksRareAddress
await approveCollection(spender ?? '', collectionAddress, signer, (newStatus: ListingStatus) =>
updateStatus({
@@ -266,38 +260,3 @@ export const resetRow = (row: AssetRow, rows: AssetRow[], setRows: Dispatch<Asse
setRows,
})
}
const createConsiderationItem = (basisPoints: string, recipient: string): ConsiderationInputItem => {
return {
amount: basisPoints,
recipient,
}
}
export const getConsiderationItems = (
asset: WalletAsset,
price: BigNumber,
signerAddress: string
): {
sellerFee: ConsiderationInputItem
openseaFee: ConsiderationInputItem
creatorFee?: ConsiderationInputItem
} => {
const openSeaBasisPoints = OPENSEA_DEFAULT_FEE * INVERSE_BASIS_POINTS
const creatorFeeBasisPoints = asset.creatorPercentage * INVERSE_BASIS_POINTS
const sellerBasisPoints = INVERSE_BASIS_POINTS - openSeaBasisPoints - creatorFeeBasisPoints
const openseaFee = price.mul(BigNumber.from(openSeaBasisPoints)).div(BigNumber.from(INVERSE_BASIS_POINTS)).toString()
const creatorFee = price
.mul(BigNumber.from(creatorFeeBasisPoints))
.div(BigNumber.from(INVERSE_BASIS_POINTS))
.toString()
const sellerFee = price.mul(BigNumber.from(sellerBasisPoints)).div(BigNumber.from(INVERSE_BASIS_POINTS)).toString()
return {
sellerFee: createConsiderationItem(sellerFee, signerAddress),
openseaFee: createConsiderationItem(openseaFee, OPENSEA_FEE_ADDRESS),
creatorFee:
creatorFeeBasisPoints > 0 ? createConsiderationItem(creatorFee, asset.asset_contract.payout_address) : undefined,
}
}

View File

@@ -1,4 +1,5 @@
import clsx from 'clsx'
import { getDeltaArrow } from 'components/Tokens/TokenDetails/PriceChart'
import { Box, BoxProps } from 'nft/components/Box'
import { Column, Row } from 'nft/components/Flex'
import { Marquee } from 'nft/components/layout/Marquee'
@@ -6,14 +7,20 @@ import { headlineMedium } from 'nft/css/common.css'
import { themeVars } from 'nft/css/sprinkles.css'
import { useIsCollectionLoading } from 'nft/hooks/useIsCollectionLoading'
import { GenieCollection } from 'nft/types'
import { ethNumberStandardFormatter } from 'nft/utils/currency'
import { putCommas } from 'nft/utils/putCommas'
import { floorFormatter, quantityFormatter, roundWholePercentage, volumeFormatter } from 'nft/utils/numbers'
import { ReactNode, useEffect, useReducer, useRef, useState } from 'react'
import ReactMarkdown from 'react-markdown'
import styled from 'styled-components/macro'
import { DiscordIcon, EllipsisIcon, ExternalIcon, InstagramIcon, TwitterIcon, VerifiedIcon, XMarkIcon } from '../icons'
import * as styles from './CollectionStats.css'
const PercentChange = styled.div`
display: flex;
align-items: center;
justify-content: center;
`
const MobileSocialsIcon = ({ children, href }: { children: ReactNode; href: string }) => {
return (
<Box
@@ -233,27 +240,32 @@ const CollectionDescription = ({ description }: { description: string }) => {
const StatsItem = ({ children, label, isMobile }: { children: ReactNode; label: string; isMobile: boolean }) => {
return (
<Box display="flex" flexDirection={isMobile ? 'row' : 'column'} alignItems="baseline" gap="2" height="min">
<Box as="span" className={styles.statsLabel}>
{`${label}${isMobile ? ': ' : ''}`}
</Box>
<Box display="flex" flexDirection={'column'} alignItems="baseline" gap="2" height="min">
<span className={styles.statsValue}>{children}</span>
<Box as="span" className={styles.statsLabel}>
{label}
</Box>
</Box>
)
}
const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMobile?: boolean } & BoxProps) => {
const numOwnersStr = stats.stats ? putCommas(stats.stats.num_owners) : 0
const totalSupplyStr = stats.stats ? putCommas(stats.stats.total_supply) : 0
const uniqueOwnersPercentage = stats.stats
? roundWholePercentage((stats.stats.num_owners / stats.stats.total_supply) * 100)
: 0
const totalSupplyStr = stats.stats ? quantityFormatter(stats.stats.total_supply) : 0
const listedPercentageStr =
stats.stats && stats.stats.total_listings > 0
? ((stats.stats.total_listings / stats.stats.total_supply) * 100).toFixed(0)
? roundWholePercentage((stats.stats.total_listings / stats.stats.total_supply) * 100)
: 0
const isCollectionStatsLoading = useIsCollectionLoading((state) => state.isCollectionStatsLoading)
// round daily volume & floorPrice to 3 decimals or less
const totalVolumeStr = ethNumberStandardFormatter(stats.stats?.total_volume)
const floorPriceStr = ethNumberStandardFormatter(stats.floorPrice)
const totalVolumeStr = volumeFormatter(stats.stats?.total_volume)
const floorPriceStr = floorFormatter(stats.floorPrice)
const floorChangeStr =
stats.stats && stats.stats.one_day_floor_change ? Math.round(Math.abs(stats.stats.one_day_floor_change) * 100) : 0
const arrow = stats.stats && stats.stats.one_day_change ? getDeltaArrow(stats.stats.one_day_floor_change) : null
const statsLoadingSkeleton = new Array(5).fill(
<>
@@ -267,19 +279,31 @@ const StatsRow = ({ stats, isMobile, ...props }: { stats: GenieCollection; isMob
return (
<Row gap={{ sm: '20', md: '60' }} {...props}>
{isCollectionStatsLoading && statsLoadingSkeleton}
{stats.floorPrice ? (
<StatsItem label="Global floor" isMobile={isMobile ?? false}>
{floorPriceStr} ETH
</StatsItem>
) : null}
{stats.stats?.one_day_floor_change ? (
<StatsItem label="24-Hour Floor" isMobile={isMobile ?? false}>
<PercentChange>
{floorChangeStr}% {arrow}
</PercentChange>
</StatsItem>
) : null}
{stats.stats?.total_volume ? (
<StatsItem label="Total volume" isMobile={isMobile ?? false}>
{totalVolumeStr} ETH
</StatsItem>
) : null}
{totalSupplyStr ? (
<StatsItem label="Items" isMobile={isMobile ?? false}>
{totalSupplyStr}
</StatsItem>
) : null}
{numOwnersStr ? (
<StatsItem label="Owners" isMobile={isMobile ?? false}>
{numOwnersStr}
</StatsItem>
) : null}
{stats.floorPrice ? (
<StatsItem label="Floor Price" isMobile={isMobile ?? false}>
{floorPriceStr} ETH
{uniqueOwnersPercentage ? (
<StatsItem label="Unique owners" isMobile={isMobile ?? false}>
{uniqueOwnersPercentage}%
</StatsItem>
) : null}
{stats.stats?.total_volume ? (

View File

@@ -13,7 +13,6 @@ import { badge, bodySmall, caption, headlineMedium, subhead } from 'nft/css/comm
import { themeVars } from 'nft/css/sprinkles.css'
import { useBag } from 'nft/hooks'
import { useTimeout } from 'nft/hooks/useTimeout'
import { fetchSingleAsset } from 'nft/queries'
import { CollectionInfoForAsset, GenieAsset, SellOrder } from 'nft/types'
import { shortenAddress } from 'nft/utils/address'
import { formatEthPrice } from 'nft/utils/currency'
@@ -25,7 +24,6 @@ import { toSignificant } from 'nft/utils/toSignificant'
import qs from 'query-string'
import { useEffect, useMemo, useState } from 'react'
import ReactMarkdown from 'react-markdown'
import { useQuery } from 'react-query'
import { Link, useLocation, useNavigate } from 'react-router-dom'
import { useSpring } from 'react-spring'
@@ -107,14 +105,11 @@ enum MediaType {
}
interface AssetDetailsProps {
tokenId: string
contractAddress: string
asset: GenieAsset
collection: CollectionInfoForAsset
}
export const AssetDetails = ({ tokenId, contractAddress }: AssetDetailsProps) => {
const { data } = useQuery(['assetDetail', contractAddress, tokenId], () =>
fetchSingleAsset({ contractAddress, tokenId })
)
export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
const { pathname, search } = useLocation()
const navigate = useNavigate()
const addAssetToBag = useBag((state) => state.addAssetToBag)
@@ -127,8 +122,6 @@ export const AssetDetails = ({ tokenId, contractAddress }: AssetDetailsProps) =>
const creatorEnsName = useENSName(creatorAddress)
const ownerEnsName = useENSName(ownerAddress)
const parsed = qs.parse(search)
const asset = useMemo(() => (data ? data[0] : ({} as GenieAsset)), [data])
const collection = useMemo(() => (data ? data[1] : ({} as CollectionInfoForAsset)), [data])
const { gridWidthOffset } = useSpring({
gridWidthOffset: bagExpanded ? 324 : 0,
})
@@ -415,8 +408,8 @@ export const AssetDetails = ({ tokenId, contractAddress }: AssetDetailsProps) =>
<Traits collectionAddress={asset.address} traits={asset.traits ?? []} />
) : (
<Details
contractAddress={contractAddress}
tokenId={tokenId}
contractAddress={asset.address}
tokenId={asset.tokenId}
tokenType={asset.tokenType}
blockchain="Ethereum"
metadataUrl={asset.externalLink}

View File

@@ -1,3 +1,206 @@
export const AssetPriceDetails = () => {
return <div>Holder for price details</div>
import { useWeb3React } from '@web3-react/core'
import { CancelListingIcon } from 'nft/components/icons'
import { useBag } from 'nft/hooks'
import { CollectionInfoForAsset, GenieAsset } from 'nft/types'
import { ethNumberStandardFormatter, formatEthPrice, getMarketplaceIcon, timeLeft } from 'nft/utils'
import { useMemo } from 'react'
import { useNavigate } from 'react-router-dom'
import styled, { useTheme } from 'styled-components/macro'
import { ThemedText } from 'theme'
interface AssetPriceDetailsProps {
asset: GenieAsset
collection: CollectionInfoForAsset
}
const Container = styled.div`
margin-left: 86px;
`
const BestPriceContainer = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px;
background-color: ${({ theme }) => theme.backgroundSurface};
border: 1px solid ${({ theme }) => theme.backgroundOutline};
border-radius: 16px;
width: 320px;
`
const HeaderRow = styled.div`
display: flex;
justify-content: space-between;
`
const PriceRow = styled.div`
display: flex;
gap: 12px;
align-items: flex-end;
`
const MarketplaceIcon = styled.img`
width: 20px;
height: 20px;
border-radius: 4px;
margin-top: auto;
margin-bottom: auto;
`
const BuyNowButton = styled.div<{ assetInBag: boolean; margin: boolean; useAccentColor: boolean }>`
width: 100%;
background-color: ${({ theme, assetInBag, useAccentColor }) =>
assetInBag ? theme.accentFailure : useAccentColor ? theme.accentAction : theme.backgroundInteractive};
border-radius: 12px;
padding: 10px 12px;
margin-top: ${({ margin }) => (margin ? '12px' : '0px')};
text-align: center;
cursor: pointer;
`
const NotForSaleContainer = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
padding: 48px 18px;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
`
const DiscoveryContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
`
export const OwnerContainer = ({ asset }: { asset: GenieAsset }) => {
const listing = asset.sellorders && asset.sellorders.length > 0 ? asset.sellorders[0] : undefined
const expirationDate = listing ? new Date(listing.orderClosingDate) : undefined
const navigate = useNavigate()
return (
<Container>
<BestPriceContainer>
<HeaderRow>
<ThemedText.SubHeader fontWeight={500} lineHeight={'24px'}>
{listing ? 'Your Price' : 'List for Sale'}
</ThemedText.SubHeader>
{listing && <MarketplaceIcon alt={listing.marketplace} src={getMarketplaceIcon(listing.marketplace)} />}
</HeaderRow>
<PriceRow>
{listing ? (
<>
<ThemedText.MediumHeader fontSize={'28px'} lineHeight={'36px'}>
{formatEthPrice(asset.priceInfo.ETHPrice)}
</ThemedText.MediumHeader>
<ThemedText.BodySecondary lineHeight={'24px'}>
{ethNumberStandardFormatter(asset.priceInfo.USDPrice, true, true)}
</ThemedText.BodySecondary>
</>
) : (
<ThemedText.BodySecondary fontSize="14px" lineHeight={'20px'}>
Get the best price for your NFT by selling with Uniswap.
</ThemedText.BodySecondary>
)}
</PriceRow>
{expirationDate && (
<ThemedText.BodySecondary fontSize={'14px'}>Sale ends: {timeLeft(expirationDate)}</ThemedText.BodySecondary>
)}
{!listing ? (
<BuyNowButton assetInBag={false} margin={true} useAccentColor={true} onClick={() => navigate('/profile')}>
<ThemedText.SubHeader lineHeight={'20px'}>List</ThemedText.SubHeader>
</BuyNowButton>
) : (
<>
<BuyNowButton assetInBag={false} margin={true} useAccentColor={false} onClick={() => navigate('/profile')}>
<ThemedText.SubHeader lineHeight={'20px'}>Adjust listing</ThemedText.SubHeader>
</BuyNowButton>
<BuyNowButton assetInBag={true} margin={false} useAccentColor={false} onClick={() => navigate('/profile')}>
<ThemedText.SubHeader lineHeight={'20px'}>Cancel listing</ThemedText.SubHeader>
</BuyNowButton>
</>
)}
</BestPriceContainer>
</Container>
)
}
export const NotForSale = ({ collection }: { collection: CollectionInfoForAsset }) => {
const theme = useTheme()
return (
<BestPriceContainer>
<NotForSaleContainer>
<CancelListingIcon width="79px" height="79px" color={theme.textTertiary} />
<ThemedText.SubHeader fontWeight={500} lineHeight="24px">
Not for sale
</ThemedText.SubHeader>
<DiscoveryContainer>
<ThemedText.BodySecondary fontSize="14px" lineHeight="20px">
Discover similar NFTs for sale in
</ThemedText.BodySecondary>
<ThemedText.Link lineHeight="20px">{collection.collectionName}</ThemedText.Link>
</DiscoveryContainer>
</NotForSaleContainer>
</BestPriceContainer>
)
}
export const AssetPriceDetails = ({ asset, collection }: AssetPriceDetailsProps) => {
const { account } = useWeb3React()
const cheapestOrder = asset.sellorders && asset.sellorders.length > 0 ? asset.sellorders[0] : undefined
const expirationDate = cheapestOrder ? new Date(cheapestOrder.orderClosingDate) : undefined
const itemsInBag = useBag((s) => s.itemsInBag)
const addAssetToBag = useBag((s) => s.addAssetToBag)
const removeAssetFromBag = useBag((s) => s.removeAssetFromBag)
const assetInBag = useMemo(() => {
return itemsInBag.some((item) => item.asset.tokenId === asset.tokenId && item.asset.address === asset.address)
}, [itemsInBag, asset])
const isOwner =
asset.owner && typeof asset.owner === 'string' ? account?.toLowerCase() === asset.owner.toLowerCase() : false
if (isOwner) {
return <OwnerContainer asset={asset} />
}
return (
<Container>
{cheapestOrder && asset.priceInfo ? (
<BestPriceContainer>
<HeaderRow>
<ThemedText.SubHeader fontWeight={500} lineHeight={'24px'}>
Best Price
</ThemedText.SubHeader>
<MarketplaceIcon alt={cheapestOrder.marketplace} src={getMarketplaceIcon(cheapestOrder.marketplace)} />
</HeaderRow>
<PriceRow>
<ThemedText.MediumHeader fontSize={'28px'} lineHeight={'36px'}>
{formatEthPrice(asset.priceInfo.ETHPrice)}
</ThemedText.MediumHeader>
<ThemedText.BodySecondary lineHeight={'24px'}>
{ethNumberStandardFormatter(asset.priceInfo.USDPrice, true, true)}
</ThemedText.BodySecondary>
</PriceRow>
{expirationDate && (
<ThemedText.BodySecondary fontSize={'14px'}>Sale ends: {timeLeft(expirationDate)}</ThemedText.BodySecondary>
)}
<BuyNowButton
assetInBag={assetInBag}
margin={true}
useAccentColor={true}
onClick={() => (assetInBag ? removeAssetFromBag(asset) : addAssetToBag(asset))}
>
<ThemedText.SubHeader lineHeight={'20px'}>{assetInBag ? 'Remove' : 'Buy Now'}</ThemedText.SubHeader>
</BuyNowButton>
</BestPriceContainer>
) : (
<NotForSale collection={collection} />
)}
</Container>
)
}

View File

@@ -146,70 +146,6 @@ export const marketplaceIcon = style([
},
])
/* Value Prop Styles */
export const valuePropWrap = style([
{
backgroundRepeat: 'no-repeat',
backgroundSize: 'auto',
'@media': {
[`(max-width: ${breakpoints.sm}px)`]: {
backgroundPosition: 'top 0 left 100px',
},
[`(min-width: ${breakpoints.sm}px)`]: {
backgroundPosition: 'top 0 right 0',
},
},
},
sprinkles({
width: 'full',
borderWidth: '1px',
borderStyle: 'solid',
borderColor: 'outline',
borderRadius: '12',
paddingLeft: '16',
paddingRight: '16',
marginTop: '60',
position: 'relative',
}),
])
export const valuePropOverlay = style([
{
height: '135px',
},
sprinkles({
position: 'absolute',
width: 'full',
backgroundColor: 'grey900',
left: '0',
top: '0',
opacity: { sm: '0.7', xl: '0.1' },
}),
])
export const valuePropContent = style([
sprinkles({
position: 'relative',
zIndex: '1',
paddingLeft: { sm: '20', md: '28', lg: '36' },
paddingBottom: '18',
fontSize: { sm: '20', md: '28' },
paddingTop: { sm: '28', md: '32' },
}),
{
lineHeight: '28px',
'@media': {
[`(max-width: 400px)`]: { width: '88%' },
[`(min-width: 400px)`]: { width: '67%' },
[`(min-width: ${breakpoints.md}px)`]: {
width: '58%',
lineHeight: '36px',
},
[`(min-width: ${breakpoints.lg - 1}px)`]: { width: '50%' },
},
},
])
/* Base Table Styles */
export const table = style([

View File

@@ -1,33 +0,0 @@
import clsx from 'clsx'
import { Box } from 'nft/components/Box'
import { Column } from 'nft/components/Flex'
import MarketplacesImage from '../../../assets/images/nft-marketplaces.png'
import * as styles from './Explore.css'
const ValueProp = () => {
return (
<Box width="full">
<Column as="section" className={styles.section}>
<Box
className={clsx(styles.bannerWrap, styles.valuePropWrap)}
style={{
height: '135px',
backgroundImage: `url(${MarketplacesImage})`,
}}
>
<Box className={styles.valuePropOverlay} width="full" />
<Box className={styles.valuePropContent}>
Discover, buy, and{' '}
<Box as="span" color="pink400">
sell NFTs
</Box>{' '}
across all NFT marketplaces
</Box>
</Box>
</Column>
</Box>
)
}
export default ValueProp

View File

@@ -1511,3 +1511,15 @@ export const EmptyNFTWalletIcon = (props: SVGProps) => (
/>
</svg>
)
export const CancelListingIcon = (props: SVGProps) => (
<svg fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
d="M71 31L75.36 35.36C76.85 36.8589 77.6863 38.8865 77.6863 41C77.6863 43.1135 76.85 45.1411 75.36 46.64L46.68 75.32C45.937 76.0638 45.0547 76.6539 44.0835 77.0565C43.1123 77.4591 42.0713 77.6663 41.02 77.6663C39.9687 77.6663 38.9277 77.4591 37.9565 77.0565C36.9853 76.6539 36.103 76.0638 35.36 75.32L31 71M47.8 7.8L41 1H1V41L7.8 47.8M77.6863 1L1.62987 77.0565M21 21H21.0333"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)

View File

@@ -236,7 +236,7 @@ const SetDurationModal = () => {
const convertDurationToExpiration = (amount: number, duration: Duration) => {
const durationFactor =
duration === Duration.hour ? 1 : duration === Duration.day ? 24 : duration === Duration.week ? 24 * 7 : 24 * 30
return Math.round(Date.now() + ms`1 hour` * durationFactor * amount)
return Math.round((Date.now() + ms`1 hour` * durationFactor * amount) / 1000)
}
interface GlobalDurationButtonProps {

View File

@@ -26,6 +26,7 @@ import styled from 'styled-components/macro'
import { EmptyWalletContent } from './EmptyWalletContent'
import { ProfileAccountDetails } from './ProfileAccountDetails'
import * as styles from './ProfilePage.css'
import { ProfilePageLoadingSkeleton } from './ProfilePageLoadingSkeleton'
import { WalletAssetDisplay } from './WalletAssetDisplay'
const SellModeButton = styled.button<{ active: boolean }>`
@@ -46,6 +47,9 @@ const SellModeButton = styled.button<{ active: boolean }>`
}
`
const FILTER_SIDEBAR_WIDTH = 300
const PADDING = 16
function roundFloorPrice(price?: number, n?: number) {
return price ? Math.round(price * Math.pow(10, n ?? 3) + Number.EPSILON) / Math.pow(10, n ?? 3) : 0
}
@@ -76,7 +80,7 @@ export const ProfilePage = () => {
toggleSellMode()
}
const { data: ownerCollections } = useQuery(
const { data: ownerCollections, isLoading: collectionsAreLoading } = useQuery(
['ownerCollections', address],
() => OSCollectionsFetcher({ params: { asset_owner: address, offset: '0', limit: '300' } }),
{
@@ -85,7 +89,7 @@ export const ProfilePage = () => {
)
const ownerCollectionsAddresses = useMemo(() => ownerCollections?.map(({ address }) => address), [ownerCollections])
const { data: collectionStats } = useQuery(
const { data: collectionStats, isLoading: collectionStatsAreLoading } = useQuery(
['ownerCollectionStats', ownerCollectionsAddresses],
() => fetchMultipleCollectionStats({ addresses: ownerCollectionsAddresses ?? [] }),
{
@@ -98,6 +102,7 @@ export const ProfilePage = () => {
fetchNextPage,
hasNextPage,
isSuccess,
isLoading: assetsAreLoading,
} = useInfiniteQuery(
['ownerAssets', address, collectionFilters],
async ({ pageParam = 0 }) => {
@@ -116,6 +121,8 @@ export const ProfilePage = () => {
}
)
const anyQueryIsLoading = collectionsAreLoading || collectionStatsAreLoading || assetsAreLoading
const ownerAssets = useMemo(() => (isSuccess ? ownerAssetsData?.pages.flat() : null), [isSuccess, ownerAssetsData])
useEffect(() => {
@@ -153,17 +160,19 @@ export const ProfilePage = () => {
}, [collectionStats, ownerCollections, setWalletCollections])
const { gridX } = useSpring({
gridX: isFiltersExpanded ? 300 : -16,
gridX: isFiltersExpanded ? FILTER_SIDEBAR_WIDTH : -PADDING,
})
return (
<Column
width="full"
paddingLeft={{ sm: '16', md: '52' }}
paddingRight={{ sm: '0', md: isBagExpanded ? '0' : '72' }}
paddingTop={{ sm: '16', md: '40' }}
paddingLeft={{ sm: `${PADDING}`, md: '52' }}
paddingRight={{ sm: `${PADDING}`, md: isBagExpanded ? '0' : '72' }}
paddingTop={{ sm: `${PADDING}`, md: '40' }}
>
{walletAssets.length === 0 ? (
{anyQueryIsLoading ? (
<ProfilePageLoadingSkeleton />
) : walletAssets.length === 0 ? (
<EmptyWalletContent />
) : (
<Row alignItems="flex-start" position="relative">
@@ -173,10 +182,12 @@ export const ProfilePage = () => {
<Column width="full">
<ProfileAccountDetails />
<AnimatedBox
paddingLeft={isFiltersExpanded ? '24' : '16'}
flexShrink="0"
style={{
transform: gridX.to((x) => `translate(${Number(x) - (!isMobile && isFiltersExpanded ? 300 : 0)}px)`),
transform: gridX.to(
(x) =>
`translate(${Number(x) - (!isMobile && isFiltersExpanded ? FILTER_SIDEBAR_WIDTH : -PADDING)}px)`
),
}}
>
<Row gap="8" flexWrap="nowrap" justifyContent="space-between">

View File

@@ -0,0 +1,85 @@
import { assetList } from 'nft/components/collection/CollectionNfts.css'
import styled from 'styled-components/macro'
const SkeletonPageWrapper = styled.div`
display: flex;
flex-direction: column;
width: 100%;
gap: 18px;
`
const SkeletonRowWrapper = styled.div`
display: flex;
flex-direct: row;
width: 100%;
`
const AccountDetailsSkeletonWrapper = styled(SkeletonRowWrapper)`
gap: 12px;
margin-bottom: 30px;
`
const ProfilePictureSkeleton = styled.div`
height: 44px;
width: 44px;
background: ${({ theme }) => theme.backgroundModule};
border-radius: 100px;
`
const ProfileDetailsSkeleton = styled.div`
width: 180px;
height: 36px;
background: ${({ theme }) => theme.backgroundModule};
border-radius: 12px;
`
const FilterBarSkeletonWrapper = styled(SkeletonRowWrapper)`
justify-content: space-between;
`
const FilterButtonSkeleton = styled.div`
width: 92px;
height: 44px;
background: ${({ theme }) => theme.backgroundModule};
border-radius: 12px;
`
const SellButtonSkeleton = styled.div`
width: 80px;
height: 44px;
background: ${({ theme }) => theme.backgroundModule};
border-radius: 12px;
`
export const ProfileAssetsWrapperSkeleton = styled(SkeletonRowWrapper)`
flex-wrap: wrap;
gap: 26px;
margin-bottom: 20px;
`
export const ProfileAssetCardSkeleton = styled.div`
width: 100%;
height: 330px;
background: ${({ theme }) => theme.backgroundModule};
border-radius: 20px;
`
export const ProfilePageLoadingSkeleton = () => {
return (
<SkeletonPageWrapper>
<AccountDetailsSkeletonWrapper>
<ProfilePictureSkeleton />
<ProfileDetailsSkeleton />
</AccountDetailsSkeletonWrapper>
<FilterBarSkeletonWrapper>
<FilterButtonSkeleton />
<SellButtonSkeleton />
</FilterBarSkeletonWrapper>
<div className={assetList}>
{new Array(25).map((_, index) => (
<ProfileAssetCardSkeleton key={index} />
))}
</div>
</SkeletonPageWrapper>
)
}

View File

@@ -1,7 +1,7 @@
import { Box } from 'nft/components/Box'
import { Column } from 'nft/components/Flex'
import { Column, Row } from 'nft/components/Flex'
import { VerifiedIcon } from 'nft/components/icons'
import { subheadSmall } from 'nft/css/common.css'
import { bodySmall, buttonTextSmall, subhead } from 'nft/css/common.css'
import { useBag, useIsMobile, useSellAsset } from 'nft/hooks'
import { DetailsOrigin, WalletAsset } from 'nft/types'
import { formatEth, getAssetHref } from 'nft/utils'
@@ -41,11 +41,19 @@ export const WalletAssetDisplay = ({ asset, isSellMode }: { asset: WalletAsset;
toggleCart()
}
const uniqueSellOrdersMarketplaces = useMemo(
() => [...new Set(asset.sellOrders.map((order) => order.marketplace))],
[asset.sellOrders]
)
return (
<Link to={getAssetHref(asset, DetailsOrigin.PROFILE)} style={{ textDecoration: 'none' }}>
<Column
color={'textPrimary'}
className={subheadSmall}
borderBottomLeftRadius="20"
borderBottomRightRadius="20"
paddingBottom="20"
transition="250"
backgroundColor={boxHovered ? 'backgroundOutline' : 'backgroundSurface'}
onMouseEnter={toggleBoxHovered}
onMouseLeave={toggleBoxHovered}
>
@@ -58,48 +66,47 @@ export const WalletAssetDisplay = ({ asset, isSellMode }: { asset: WalletAsset;
src={asset.image_url ?? '/nft/svgs/image-placeholder.svg'}
style={{ aspectRatio: '1' }}
/>
<Column
position="relative"
borderBottomLeftRadius="20"
borderBottomRightRadius="20"
transition="250"
backgroundColor={boxHovered ? 'backgroundOutline' : 'backgroundSurface'}
paddingY="12"
paddingX="12"
>
<Box className={subheadSmall} overflow="hidden" textOverflow="ellipsis" marginTop="4" lineHeight="20">
{asset.name ? asset.name : `#${asset.tokenId}`}
</Box>
<Box fontSize="12" marginTop="4" lineHeight="16" overflow="hidden" textOverflow="ellipsis">
{asset.collection?.name}
{asset.collectionIsVerified ? <VerifiedIcon className={styles.verifiedBadge} /> : null}
</Box>
<Box as="span" fontSize="12" lineHeight="16" color="textSecondary" marginTop="8">
Last:&nbsp;
{asset.lastPrice ? (
<>
{formatEth(asset.lastPrice)}
&nbsp;ETH
</>
) : (
<Box as="span" marginLeft="6">
&mdash;
<Column paddingTop="12" paddingX="12">
<Row>
<Column flex="2" gap="4" whiteSpace="nowrap" style={{ maxWidth: '67%' }}>
<Box className={subhead} color="textPrimary" overflow="hidden" textOverflow="ellipsis">
{asset.name ? asset.name : `#${asset.tokenId}`}
</Box>
)}
</Box>
<Box as="span" fontSize="12" lineHeight="16" color="textSecondary" marginTop="4">
Floor:&nbsp;
{asset.floorPrice ? (
<>
{formatEth(asset.floorPrice)}
&nbsp;ETH
</>
) : (
<Box as="span" marginLeft="8">
&mdash;
<Box className={bodySmall} color="textSecondary" overflow="hidden" textOverflow="ellipsis">
{asset.collection?.name}
{asset.collectionIsVerified ? <VerifiedIcon className={styles.verifiedBadge} /> : null}
</Box>
</Column>
{asset.sellOrders.length > 0 && (
<Column gap="6" flex="1" justifyContent="flex-end" whiteSpace="nowrap" style={{ maxWidth: '33%' }}>
<>
<Row className={subhead} color="textPrimary">
<Box width="full" textAlign="right" overflow="hidden" textOverflow="ellipsis">
{formatEth(asset.floor_sell_order_price)}
</Box>
&nbsp;ETH
</Row>
<Row justifyContent="flex-end">
{uniqueSellOrdersMarketplaces.map((market, index) => {
return (
<Box
as="img"
key={index}
alt={market}
width="16"
height="16"
borderRadius="4"
marginLeft="4"
objectFit="cover"
src={`/nft/svgs/marketplaces/${market}.svg`}
/>
)
})}
</Row>
</>
</Column>
)}
</Box>
</Row>
{isSellMode && (
<Box
marginTop="12"
@@ -108,9 +115,17 @@ export const WalletAssetDisplay = ({ asset, isSellMode }: { asset: WalletAsset;
borderRadius="12"
paddingY="8"
transition="250"
color={buttonHovered ? 'textPrimary' : isSelected ? 'red400' : 'genieBlue'}
backgroundColor={buttonHovered ? (isSelected ? 'red400' : 'genieBlue') : 'backgroundSurface'}
className={subheadSmall}
color={buttonHovered || isSelected ? 'textPrimary' : 'accentAction'}
backgroundColor={
buttonHovered
? isSelected
? 'accentFailure'
: 'accentAction'
: isSelected
? 'backgroundInteractive'
: 'accentActionSoft'
}
className={buttonTextSmall}
onMouseEnter={toggleButtonHovered}
onMouseLeave={toggleButtonHovered}
onClick={(e) => {

View File

@@ -1,5 +1,8 @@
import { AssetDetails } from 'nft/components/details/AssetDetails'
import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails'
import { fetchSingleAsset } from 'nft/queries'
import { useMemo } from 'react'
import { useQuery } from 'react-query'
import { useParams } from 'react-router-dom'
import styled from 'styled-components/macro'
@@ -11,12 +14,30 @@ const AssetContainer = styled.div`
const Asset = () => {
const { tokenId = '', contractAddress = '' } = useParams()
const { data } = useQuery(
['assetDetail', contractAddress, tokenId],
() => fetchSingleAsset({ contractAddress, tokenId }),
{
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
}
)
const asset = useMemo(() => (data ? data[0] : undefined), [data])
const collection = useMemo(() => (data ? data[1] : undefined), [data])
return (
<AssetContainer>
<AssetDetails tokenId={tokenId} contractAddress={contractAddress} />
<AssetPriceDetails />
</AssetContainer>
<>
{asset && collection ? (
<AssetContainer>
<AssetDetails collection={collection} asset={asset} />
<AssetPriceDetails collection={collection} asset={asset} />
</AssetContainer>
) : (
<div>Holder for loading ...</div>
)}
</>
)
}

View File

@@ -1,12 +1,10 @@
import Banner from 'nft/components/explore/Banner'
import TrendingCollections from 'nft/components/explore/TrendingCollections'
import ValueProp from 'nft/components/explore/ValueProp'
const NftExplore = () => {
return (
<>
<Banner />
<ValueProp />
<TrendingCollections />
</>
)

View File

@@ -14,7 +14,7 @@ import { useToggleWalletModal } from 'state/application/hooks'
import * as styles from './sell.css'
const SHOPPING_BAG_WIDTH = 324
const SHOPPING_BAG_WIDTH = 360
const Profile = () => {
const sellPageState = useProfilePageState((state) => state.state)

View File

@@ -24,7 +24,7 @@ export const fetchWalletAssets = async ({
? collectionAddresses.reduce((str, collectionAddress) => str + `&assetContractAddresses=${collectionAddress}`, '')
: ''
const url = `${
process.env.REACT_APP_GENIE_API_URL
process.env.REACT_APP_GENIE_V3_API_URL
}/walletAssets?address=${ownerAddress}${collectionAddressesString}&limit=25&offset=${pageParam * 25}`
const r = await fetch(url, {
@@ -38,7 +38,7 @@ export const fetchWalletAssets = async ({
return {
...asset,
collectionIsVerified: asset.asset_contract.isVerified,
lastPrice: asset.last_sale && formatEther(asset.last_sale.total_price),
lastPrice: asset.last_sale.total_price && formatEther(asset.last_sale.total_price),
floorPrice: asset.collection?.floorPrice,
creatorPercentage: parseFloat(asset.asset_contract.dev_seller_fee_basis_points) / 10000,
date_acquired: asset.last_sale ? asset.last_sale.event_timestamp : asset.asset_contract.created_date,

View File

@@ -0,0 +1 @@
export const LOOKSRARE_MARKETPLACE_CONTRACT = '0x59728544B08AB483533076417FbBB2fD0B17CE3a'

View File

@@ -1,3 +1,4 @@
export * from './constants'
export * from './createLooksRareOrder'
export * from './looksRareNonceFetcher'
export * from './looksRareRewardsFetcher'

View File

@@ -11,6 +11,7 @@ export async function PostOpenSeaSellOrder<T>(
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-API-KEY': process.env.REACT_APP_OPENSEA_API_KEY ?? '',
},
...opts,
}

View File

@@ -1,5 +1,5 @@
export const OPENSEA_BASE_API_PATH = 'https://api.opensea.io'
export const OPENSEA_FEE_ADDRESS = '0x8de9c5a032463c561423387a9648c5c7bcc5bc90'
export const OPENSEA_FEE_ADDRESS = '0x0000a26b00c1F0DF003000390027140000fAa719'
export const OPENSEA_DEFAULT_ZONE = '0x004c00500000ad104d7dbd00e3ae0a5c00560c00'
export const OPENSEA_LISTINGS_API_PATH = '/v2/orders/ethereum/seaport/listings'
export const OPENSEA_DEFAULT_CROSS_CHAIN_CONDUIT_KEY =

View File

@@ -1,5 +1,7 @@
import { OrderPayload } from '../../utils/x2y2'
export const X2Y2_TRANSFER_CONTRACT = '0xf849de01b080adc3a814fabe1e2087475cf2e354'
export const newX2Y2Order = async (payload: OrderPayload): Promise<boolean> => {
const body = JSON.stringify(payload)
const url = `${process.env.REACT_APP_GENIE_API_URL}/postX2Y2SellOrderWithApiKey`

View File

@@ -5,7 +5,7 @@ export const darkTheme: Theme = {
accentFailure: vars.color.red300,
accentFailureSoft: 'rgba(253, 118, 107, 0.12)',
accentAction: vars.color.blue400,
accentActionSoft: '#000000E5',
accentActionSoft: 'rgba(76, 130, 251, 0.24)',
explicitWhite: '#FFFFFF',
green: vars.color.green200,

View File

@@ -24,3 +24,7 @@ export const getAssetHref = (asset: GenieAsset | WalletAsset, origin?: DetailsOr
: (asset as WalletAsset).asset_contract.address
return `/nfts/asset/${address}/${asset.tokenId}${origin ? `?origin=${origin}` : ''}`
}
export const getMarketplaceIcon = (marketplace: string) => {
return `/nft/svgs/marketplaces/${marketplace}.svg`
}

View File

@@ -10,5 +10,6 @@ export * from './listNfts'
export * from './putCommas'
export * from './rarity'
export * from './roundAndPluralize'
export * from './timeSince'
export * from './transactionResponse'
export * from './updatedAssets'

View File

@@ -15,7 +15,7 @@ import {
} from 'nft/queries/openSea'
import ERC721 from '../../abis/erc721.json'
import { createLooksRareOrder, newX2Y2Order, PostOpenSeaSellOrder } from '../queries'
import { createLooksRareOrder, LOOKSRARE_MARKETPLACE_CONTRACT, newX2Y2Order, PostOpenSeaSellOrder } from '../queries'
import { INVERSE_BASIS_POINTS, OPENSEA_DEFAULT_FEE, OPENSEA_FEE_ADDRESS } from '../queries/openSea'
import { ListingMarket, ListingStatus, WalletAsset } from '../types'
import { createSellOrder, encodeOrder, OfferItem, OrderPayload, signOrderData } from './x2y2'
@@ -198,7 +198,7 @@ export async function signListing(
signer,
SupportedChainId.MAINNET,
makerOrder,
process.env.REACT_APP_LOOKSRARE_MARKETPLACE_CONTRACT || ''
LOOKSRARE_MARKETPLACE_CONTRACT
)
setStatus(ListingStatus.PENDING)
const payload = {

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