Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f431a1e26 | ||
|
|
bf13b4a917 | ||
|
|
c7ea77d292 | ||
|
|
00d674376e | ||
|
|
afaa52e5e7 | ||
|
|
443cfe7540 | ||
|
|
6768e4f4f7 | ||
|
|
a0e9211b71 | ||
|
|
aeeb3a248a | ||
|
|
3586a2884c | ||
|
|
5e2bdc4e4b | ||
|
|
f2a33b6f6b | ||
|
|
5462526f53 | ||
|
|
4388bbe0a2 | ||
|
|
6f2c09adea | ||
|
|
f9aadbbbdb | ||
|
|
f8c0525512 | ||
|
|
175ffade5e | ||
|
|
cba30fb0b1 | ||
|
|
604b854ef7 | ||
|
|
332843f428 | ||
|
|
cee32f9751 | ||
|
|
cb480706a2 | ||
|
|
4f74267144 | ||
|
|
f6b08e8ed1 | ||
|
|
0faaa3f0c4 | ||
|
|
6acc9300c0 | ||
|
|
70d33fb255 | ||
|
|
c0db592ab5 | ||
|
|
a5c5567936 | ||
|
|
cdd5b66d1b | ||
|
|
b65fffc5f7 | ||
|
|
a3a3e934a1 | ||
|
|
8a4e07e6b2 | ||
|
|
7f4413c79c | ||
|
|
55beaf65a2 | ||
|
|
20fe76ad29 | ||
|
|
0d5bc753ca | ||
|
|
79507a4b03 | ||
|
|
3a1be04a36 | ||
|
|
ec523e5235 | ||
|
|
c7b1aa2948 | ||
|
|
9370383f64 | ||
|
|
9856c03566 | ||
|
|
ec686bcaa5 | ||
|
|
06291a15a6 | ||
|
|
3e40a6f5c6 | ||
|
|
f91b48e214 | ||
|
|
e340f405b4 | ||
|
|
24fc39b016 | ||
|
|
2fc3f3c00e | ||
|
|
21e0faeb1e | ||
|
|
4b71a8d5f4 | ||
|
|
4075965252 | ||
|
|
3538312769 | ||
|
|
2924f36970 | ||
|
|
bf16dfa09c | ||
|
|
910e86d6a2 | ||
|
|
537fea103e | ||
|
|
87a6e2709b | ||
|
|
d704e78223 | ||
|
|
8ceabd513c |
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -5,8 +5,6 @@ on:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
# manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
@@ -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",
|
||||
|
||||
@@ -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> </div>
|
||||
</div>
|
||||
<div id="root">
|
||||
<!-- Triggers the font to load immediately and then is replaced by the app -->
|
||||
<div> </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>
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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 |
@@ -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}</>
|
||||
}
|
||||
@@ -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 }}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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} />
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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]}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}`
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 />}
|
||||
</>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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}
|
||||
</>
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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'}>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }) =>
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
export enum FeatureFlag {
|
||||
favoriteTokens = 'favoriteTokens',
|
||||
navBar = 'navBar',
|
||||
nft = 'nfts',
|
||||
redesign = 'redesign',
|
||||
tokens = 'tokens',
|
||||
tokenSafety = 'tokenSafety',
|
||||
traceJsonRpc = 'traceJsonRpc',
|
||||
multiNetworkBalances = 'multiNetworkBalances',
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useNavBarFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.navBar)
|
||||
}
|
||||
|
||||
export { BaseVariant as NavBarVariant }
|
||||
@@ -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 }
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useTokenSafetyFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.tokenSafety)
|
||||
}
|
||||
|
||||
export { BaseVariant as TokenSafetyVariant }
|
||||
@@ -1,7 +0,0 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useTokensFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.tokens)
|
||||
}
|
||||
|
||||
export { BaseVariant as TokensVariant }
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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>()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -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)}`)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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:
|
||||
{asset.lastPrice ? (
|
||||
<>
|
||||
{formatEth(asset.lastPrice)}
|
||||
ETH
|
||||
</>
|
||||
) : (
|
||||
<Box as="span" marginLeft="6">
|
||||
—
|
||||
<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:
|
||||
{asset.floorPrice ? (
|
||||
<>
|
||||
{formatEth(asset.floorPrice)}
|
||||
ETH
|
||||
</>
|
||||
) : (
|
||||
<Box as="span" marginLeft="8">
|
||||
—
|
||||
<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>
|
||||
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) => {
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
1
src/nft/queries/looksRare/constants.ts
Normal file
1
src/nft/queries/looksRare/constants.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const LOOKSRARE_MARKETPLACE_CONTRACT = '0x59728544B08AB483533076417FbBB2fD0B17CE3a'
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './constants'
|
||||
export * from './createLooksRareOrder'
|
||||
export * from './looksRareNonceFetcher'
|
||||
export * from './looksRareRewardsFetcher'
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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`
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user