Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec686bcaa5 | ||
|
|
06291a15a6 | ||
|
|
3e40a6f5c6 | ||
|
|
f91b48e214 | ||
|
|
e340f405b4 | ||
|
|
24fc39b016 | ||
|
|
2fc3f3c00e | ||
|
|
21e0faeb1e | ||
|
|
4b71a8d5f4 | ||
|
|
4075965252 | ||
|
|
3538312769 | ||
|
|
2924f36970 | ||
|
|
bf16dfa09c | ||
|
|
910e86d6a2 | ||
|
|
537fea103e | ||
|
|
87a6e2709b | ||
|
|
d704e78223 | ||
|
|
8ceabd513c | ||
|
|
4806c69053 | ||
|
|
c9f333003b | ||
|
|
33fa32cb07 | ||
|
|
5d1377af80 | ||
|
|
a75e239fd2 | ||
|
|
a663482dc6 | ||
|
|
e8d6235529 | ||
|
|
d8677d8a6d | ||
|
|
351f66a83e | ||
|
|
79c7c01964 | ||
|
|
f51474b66d | ||
|
|
14f01905d7 | ||
|
|
23eb31e6a2 | ||
|
|
e8d4f00f49 | ||
|
|
978e3f945d | ||
|
|
072f394476 | ||
|
|
5926d7037d | ||
|
|
52b51ee7d0 | ||
|
|
54f831ede4 | ||
|
|
c9ead63ff6 | ||
|
|
9ca74cf8d2 | ||
|
|
2a0d455419 | ||
|
|
9e959ca455 | ||
|
|
d3f6796bb9 | ||
|
|
64d6eeabcb | ||
|
|
859258c25c | ||
|
|
2338255a54 | ||
|
|
843afa93c3 | ||
|
|
5441e63825 | ||
|
|
7bf741027e | ||
|
|
0017e2fcc8 | ||
|
|
5c9c8b4cb7 | ||
|
|
873d0ea2a3 | ||
|
|
db4987f557 | ||
|
|
b0b61f886d | ||
|
|
5d4b25f417 | ||
|
|
c88d7c880b | ||
|
|
a96d13978b | ||
|
|
22b26de78d | ||
|
|
53b57879a3 | ||
|
|
d794cef770 | ||
|
|
19f175ba89 | ||
|
|
aaf105ef51 | ||
|
|
974308f939 | ||
|
|
0ec738a48a | ||
|
|
904f6e22f4 | ||
|
|
66fad96e61 | ||
|
|
9037930e56 | ||
|
|
d62013177d | ||
|
|
fc08ede58a | ||
|
|
995a62985e | ||
|
|
67d5a00a0c | ||
|
|
84364c9df2 | ||
|
|
446eb9f9a4 | ||
|
|
a73e814167 | ||
|
|
7125562c9d | ||
|
|
1361f99639 | ||
|
|
d70a87a89a | ||
|
|
2cb0d9527e | ||
|
|
1839e145ec | ||
|
|
8c1e41a3a8 | ||
|
|
9859c0b4dd | ||
|
|
1138101dd0 | ||
|
|
106ac7ea35 | ||
|
|
19b4ee463b | ||
|
|
2aea96c3ba | ||
|
|
b1fb499e29 | ||
|
|
64207f29b0 | ||
|
|
7b6ac6cfaa | ||
|
|
8a9ade5f12 | ||
|
|
a3e567bc8a | ||
|
|
a887666bf5 | ||
|
|
ed8aa08255 | ||
|
|
53f4fb9ede | ||
|
|
bb1ccb7f1a | ||
|
|
03fe90ad53 | ||
|
|
1601962f03 | ||
|
|
ac8e59acba | ||
|
|
5f6d17bfe2 | ||
|
|
3c5fe00c30 | ||
|
|
91754848af | ||
|
|
d8eb4d188a | ||
|
|
25d64911d4 | ||
|
|
888f02dbaa | ||
|
|
728a5653be | ||
|
|
a5dc0fddb8 | ||
|
|
134f30e81f | ||
|
|
9b07ac2be4 | ||
|
|
571a49ba6f | ||
|
|
077437e1f1 | ||
|
|
ba9e509d67 | ||
|
|
181ab149e3 | ||
|
|
5ef64c7dd1 | ||
|
|
0f6a675d0c | ||
|
|
ec3552bbde | ||
|
|
5783602694 | ||
|
|
9ba76992e4 | ||
|
|
d075ab6a74 | ||
|
|
4cdfeaae34 | ||
|
|
e54b46910a | ||
|
|
9558406c90 | ||
|
|
f735c34841 | ||
|
|
1aa4afad5f | ||
|
|
58005d81d6 | ||
|
|
b0381c58e6 | ||
|
|
99a3cfafc9 | ||
|
|
6c908eb710 | ||
|
|
dc15144a29 | ||
|
|
34431bcb75 |
@@ -30,7 +30,7 @@ or visit [app.uniswap.org](https://app.uniswap.org).
|
||||
|
||||
Check out `useUnsupportedTokenList()` in [src/state/lists/hooks.ts](./src/state/lists/hooks.ts) for blocking tokens in your instance of the interface.
|
||||
|
||||
You can block an entire list of tokens by passing in a tokenlist like [here](./src/constants/lists.ts) or you can block specific tokens by adding them to [unsupported.tokenlist.json](./src/constants/tokenLists/unsupported.tokenlist.json).
|
||||
You can block an entire list of tokens by passing in a tokenlist like [here](./src/constants/lists.ts)
|
||||
|
||||
## Contributions
|
||||
|
||||
|
||||
@@ -21,20 +21,20 @@ describe('Add Liquidity', () => {
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('not.contain.text', 'ETH')
|
||||
})
|
||||
|
||||
it('token not in storage is loaded', () => {
|
||||
it.skip('token not in storage is loaded', () => {
|
||||
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
|
||||
cy.get('#add-liquidity-input-tokenb .token-symbol-container').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('single token can be selected', () => {
|
||||
it.skip('single token can be selected', () => {
|
||||
cy.visit('/add/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'SKL')
|
||||
cy.visit('/add/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#add-liquidity-input-tokena .token-symbol-container').should('contain.text', 'MKR')
|
||||
})
|
||||
|
||||
it('loads fee tier distribution', () => {
|
||||
it.skip('loads fee tier distribution', () => {
|
||||
cy.fixture('feeTierDistribution.json').then((feeTierDistribution) => {
|
||||
cy.intercept('POST', '/subgraphs/name/uniswap/uniswap-v3', (req: CyHttpMessages.IncomingHttpRequest) => {
|
||||
if (hasQuery(req, 'FeeTierDistributionQuery')) {
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -23,7 +23,7 @@ describe('Remove Liquidity', () => {
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'WETH')
|
||||
})
|
||||
|
||||
it('token not in storage is loaded', () => {
|
||||
it.skip('token not in storage is loaded', () => {
|
||||
cy.visit('/remove/v2/0xb290b2f9f8f108d03ff2af3ac5c8de6de31cdf6d/0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85')
|
||||
cy.get('#remove-liquidity-tokena-symbol').should('contain.text', 'SKL')
|
||||
cy.get('#remove-liquidity-tokenb-symbol').should('contain.text', 'MKR')
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
@@ -34,7 +34,7 @@ Cypress.Commands.overwrite(
|
||||
cy.intercept('/service-worker.js', options?.serviceWorker ? undefined : { statusCode: 404 }).then(() => {
|
||||
original({
|
||||
...options,
|
||||
url: (url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url) + '?chain=rinkeby',
|
||||
url: (url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url) + '?chain=goerli',
|
||||
onBeforeLoad(win) {
|
||||
options?.onBeforeLoad?.(win)
|
||||
win.localStorage.clear()
|
||||
|
||||
@@ -19,10 +19,10 @@ export const TEST_ADDRESS_NEVER_USE_SHORTENED = `${TEST_ADDRESS_NEVER_USE.substr
|
||||
6
|
||||
)}...${TEST_ADDRESS_NEVER_USE.substr(-4, 4)}`
|
||||
|
||||
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
|
||||
const provider = new JsonRpcProvider('https://goerli.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847', 4)
|
||||
const signer = new Wallet(TEST_PRIVATE_KEY, provider)
|
||||
export const injected = new (class extends Eip1193Bridge {
|
||||
chainId = 4
|
||||
chainId = /* GOERLI= */ 5
|
||||
|
||||
async sendAsync(...args: any[]) {
|
||||
console.debug('sendAsync called', ...args)
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-redux": "^7.1.24",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-table": "^7.7.12",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.0",
|
||||
"@types/react-window": "^1.8.2",
|
||||
"@types/rebass": "^4.0.7",
|
||||
@@ -134,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",
|
||||
@@ -145,7 +146,7 @@
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.9.0",
|
||||
"@uniswap/widgets": "^2.8.1",
|
||||
"@uniswap/widgets": "^2.14.1",
|
||||
"@vanilla-extract/css": "^1.7.2",
|
||||
"@vanilla-extract/css-utils": "^0.1.2",
|
||||
"@vanilla-extract/dynamic": "^2.0.2",
|
||||
@@ -154,6 +155,7 @@
|
||||
"@visx/event": "^2.6.0",
|
||||
"@visx/glyph": "^2.10.0",
|
||||
"@visx/group": "^2.10.0",
|
||||
"@visx/react-spring": "^2.12.2",
|
||||
"@visx/responsive": "^2.10.0",
|
||||
"@visx/shape": "^2.11.1",
|
||||
"@walletconnect/ethereum-provider": "1.7.1",
|
||||
@@ -206,7 +208,7 @@
|
||||
"react-redux": "^8.0.2",
|
||||
"react-relay": "^14.1.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-spring": "^9.5.5",
|
||||
"react-table": "^7.8.0",
|
||||
"react-use-gesture": "^6.0.14",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
|
||||
@@ -70,7 +70,6 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
font-feature-settings: 'ss01' on, 'ss02' on, 'cv01' on, 'cv03' on;
|
||||
}
|
||||
|
||||
#background-radial-gradient {
|
||||
|
||||
@@ -8,8 +8,11 @@ export enum EventName {
|
||||
APP_LOADED = 'Application Loaded',
|
||||
APPROVE_TOKEN_TXN_SUBMITTED = 'Approve Token Transaction Submitted',
|
||||
CONNECT_WALLET_BUTTON_CLICKED = 'Connect Wallet Button Clicked',
|
||||
EXPLORE_SEARCH_SELECTED = 'Explore Search Selected',
|
||||
EXPLORE_TOKEN_ROW_CLICKED = 'Explore Token Row Clicked',
|
||||
PAGE_VIEWED = 'Page Viewed',
|
||||
NAVBAR_SEARCH_SELECTED = 'Navbar Search Selected',
|
||||
NAVBAR_SEARCH_EXITED = 'Navbar Search Exited',
|
||||
SWAP_AUTOROUTER_VISUALIZATION_EXPANDED = 'Swap Autorouter Visualization Expanded',
|
||||
SWAP_DETAILS_EXPANDED = 'Swap Details Expanded',
|
||||
SWAP_MAX_TOKEN_AMOUNT_SELECTED = 'Swap Max Token Amount Selected',
|
||||
@@ -61,8 +64,6 @@ export enum WALLET_CONNECTION_RESULT {
|
||||
FAILED = 'Failed',
|
||||
}
|
||||
|
||||
export const NATIVE_CHAIN_ID = 'NATIVE'
|
||||
|
||||
export enum SWAP_PRICE_UPDATE_USER_RESPONSE {
|
||||
ACCEPTED = 'Accepted',
|
||||
REJECTED = 'Rejected',
|
||||
@@ -106,8 +107,10 @@ export enum ElementName {
|
||||
COMMON_BASES_CURRENCY_BUTTON = 'common-bases-currency-button',
|
||||
CONFIRM_SWAP_BUTTON = 'confirm-swap-or-send',
|
||||
CONNECT_WALLET_BUTTON = 'connect-wallet-button',
|
||||
EXPLORE_SEARCH_INPUT = 'explore_search_input',
|
||||
IMPORT_TOKEN_BUTTON = 'import-token-button',
|
||||
MAX_TOKEN_AMOUNT_BUTTON = 'max-token-amount-button',
|
||||
NAVBAR_SEARCH_INPUT = 'navbar-search-input',
|
||||
PRICE_UPDATE_ACCEPT_BUTTON = 'price-update-accept-button',
|
||||
SWAP_BUTTON = 'swap-button',
|
||||
SWAP_DETAILS_DROPDOWN = 'swap-details-dropdown',
|
||||
@@ -124,6 +127,7 @@ export enum ElementName {
|
||||
*/
|
||||
export enum Event {
|
||||
onClick = 'onClick',
|
||||
onFocus = 'onFocus',
|
||||
onKeyPress = 'onKeyPress',
|
||||
onSelect = 'onSelect',
|
||||
// alphabetize additional events.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
|
||||
import { NATIVE_CHAIN_ID } from './constants'
|
||||
import { NATIVE_CHAIN_ID } from 'constants/tokens'
|
||||
|
||||
export const getDurationUntilTimestampSeconds = (futureTimestampInSecondsSinceEpoch?: number): number | undefined => {
|
||||
if (!futureTimestampInSecondsSinceEpoch) return undefined
|
||||
|
||||
BIN
src/assets/images/sizingImage.png
Normal file
BIN
src/assets/images/sizingImage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
3
src/assets/svg/swap-arrows.svg
Normal file
3
src/assets/svg/swap-arrows.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.80333 4.8863C7.51044 5.17919 7.51044 5.65406 7.80333 5.94696C8.09622 6.23985 8.5711 6.23985 8.86399 5.94696L7.80333 4.8863ZM12.0837 1.66663L12.614 1.1363C12.3211 0.843403 11.8462 0.843403 11.5533 1.1363L12.0837 1.66663ZM15.3033 5.94696C15.5962 6.23985 16.0711 6.23985 16.364 5.94696C16.6569 5.65406 16.6569 5.17919 16.364 4.8863L15.3033 5.94696ZM11.3337 9.99996C11.3337 10.4142 11.6694 10.75 12.0837 10.75C12.4979 10.75 12.8337 10.4142 12.8337 9.99996H11.3337ZM12.1973 15.1136C12.4902 14.8207 12.4902 14.3459 12.1973 14.053C11.9044 13.7601 11.4296 13.7601 11.1367 14.053L12.1973 15.1136ZM7.91699 18.3333L7.38666 18.8636C7.52731 19.0043 7.71808 19.0833 7.91699 19.0833C8.1159 19.0833 8.30667 19.0043 8.44732 18.8636L7.91699 18.3333ZM4.69732 14.053C4.40443 13.7601 3.92956 13.7601 3.63666 14.053C3.34377 14.3459 3.34377 14.8207 3.63666 15.1136L4.69732 14.053ZM8.66699 10.8333C8.66699 10.4191 8.33121 10.0833 7.91699 10.0833C7.50278 10.0833 7.16699 10.4191 7.16699 10.8333H8.66699ZM8.86399 5.94696L12.614 2.19696L11.5533 1.1363L7.80333 4.8863L8.86399 5.94696ZM11.5533 2.19696L15.3033 5.94696L16.364 4.8863L12.614 1.1363L11.5533 2.19696ZM11.3337 1.66663V9.99996H12.8337V1.66663H11.3337ZM11.1367 14.053L7.38666 17.803L8.44732 18.8636L12.1973 15.1136L11.1367 14.053ZM8.44732 17.803L4.69732 14.053L3.63666 15.1136L7.38666 18.8636L8.44732 17.803ZM8.66699 18.3333L8.66699 10.8333H7.16699L7.16699 18.3333H8.66699Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.24453 18.0887C3.24331 19.0467 3.47372 19.7558 3.93576 20.2158C4.39658 20.6771 5.09574 20.904 6.03326 20.8967H8.11975C8.20693 20.8934 8.29386 20.9079 8.37521 20.9395C8.45656 20.9711 8.53062 21.019 8.5928 21.0802L10.0779 22.5484C10.7527 23.2226 11.4139 23.5578 12.0617 23.5541C12.7096 23.5504 13.3709 23.2152 14.0456 22.5484L15.5124 21.0802C15.5767 21.0182 15.6529 20.97 15.7365 20.9385C15.82 20.9069 15.9091 20.8927 15.9982 20.8967H18.0719C19.0192 20.8979 19.7251 20.6673 20.1896 20.2048C20.6541 19.7423 20.8864 19.0333 20.8864 18.0777V16.0021C20.8816 15.8222 20.9474 15.6476 21.0697 15.5157L22.5365 14.0475C23.2198 13.3758 23.559 12.7145 23.5541 12.0636C23.5492 11.4127 23.21 10.7508 22.5365 10.0779L21.0697 8.6097C20.9471 8.47802 20.8812 8.30329 20.8864 8.12336V6.04769C20.8851 5.09092 20.6547 4.3819 20.1951 3.92064C19.7355 3.45939 19.0278 3.22875 18.0719 3.22875H15.9982C15.9091 3.23242 15.8201 3.21807 15.7366 3.18653C15.6532 3.155 15.5769 3.10694 15.5124 3.04523L14.0456 1.57703C13.3709 0.902883 12.7096 0.567648 12.0617 0.571319C11.4139 0.574989 10.7527 0.910224 10.0779 1.57703L8.5928 3.04523C8.53043 3.10622 8.45638 3.15393 8.37508 3.18547C8.29377 3.21701 8.20689 3.23173 8.11975 3.22875H6.03326C5.08718 3.22998 4.38373 3.45877 3.92291 3.91513C3.4621 4.3715 3.23168 5.08235 3.23168 6.04769V8.12887C3.23683 8.3088 3.17096 8.48352 3.04833 8.6152L1.58154 10.0834C0.908042 10.7551 0.571289 11.417 0.571289 12.0691C0.571289 12.7213 0.912332 13.3844 1.59439 14.0585L3.06118 15.5267C3.18346 15.6586 3.24928 15.8332 3.24453 16.0131V18.0887Z" fill="#4C82FB"/>
|
||||
<path d="M3.24453 18.0887C3.24331 19.0467 3.47372 19.7558 3.93576 20.2158C4.39658 20.6771 5.09574 20.904 6.03326 20.8967H8.11975C8.20693 20.8934 8.29386 20.9079 8.37521 20.9395C8.45656 20.9711 8.53062 21.019 8.5928 21.0802L10.0779 22.5484C10.7527 23.2226 11.4139 23.5578 12.0617 23.5541C12.7096 23.5504 13.3709 23.2152 14.0456 22.5484L15.5124 21.0802C15.5767 21.0182 15.6529 20.97 15.7365 20.9385C15.82 20.9069 15.9091 20.8927 15.9982 20.8967H18.0719C19.0192 20.8979 19.7251 20.6673 20.1896 20.2048C20.6541 19.7423 20.8864 19.0333 20.8864 18.0777V16.0021C20.8816 15.8222 20.9474 15.6476 21.0697 15.5157L22.5365 14.0475C23.2198 13.3758 23.559 12.7145 23.5541 12.0636C23.5492 11.4127 23.21 10.7508 22.5365 10.0779L21.0697 8.6097C20.9471 8.47802 20.8812 8.30329 20.8864 8.12336V6.04769C20.8851 5.09092 20.6547 4.3819 20.1951 3.92064C19.7355 3.45939 19.0278 3.22875 18.0719 3.22875H15.9982C15.9091 3.23242 15.8201 3.21807 15.7366 3.18653C15.6532 3.155 15.5769 3.10694 15.5124 3.04523L14.0456 1.57703C13.3709 0.902883 12.7096 0.567648 12.0617 0.571319C11.4139 0.574989 10.7527 0.910224 10.0779 1.57703L8.5928 3.04523C8.53043 3.10622 8.45638 3.15393 8.37508 3.18547C8.29377 3.21701 8.20689 3.23173 8.11975 3.22875H6.03326C5.08718 3.22998 4.38373 3.45877 3.92291 3.91513C3.4621 4.3715 3.23168 5.08235 3.23168 6.04769V8.12887C3.23683 8.3088 3.17096 8.48352 3.04833 8.6152L1.58154 10.0834C0.908042 10.7551 0.571289 11.417 0.571289 12.0691C0.571289 12.7213 0.912332 13.3844 1.59439 14.0585L3.06118 15.5267C3.18346 15.6586 3.24928 15.8332 3.24453 16.0131V18.0887Z" fill="currentColor"/>
|
||||
<path d="M11.996 15.9909C11.7795 16.3208 11.4599 16.5064 11.0887 16.5064C10.7072 16.5064 10.4083 16.3517 10.1299 15.9909L7.69677 13.0216C7.5215 12.8051 7.42871 12.5783 7.42871 12.3309C7.42871 11.8154 7.82049 11.4133 8.32567 11.4133C8.63497 11.4133 8.8824 11.5267 9.12984 11.8463L11.0475 14.2897L15.1199 7.75329C15.3364 7.40275 15.6147 7.23779 15.924 7.23779C16.4086 7.23779 16.8622 7.57802 16.8622 8.0832C16.8622 8.32033 16.7385 8.56777 16.6045 8.78427L11.996 15.9909Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
94
src/components/Charts/AnimatedInLineChart.tsx
Normal file
94
src/components/Charts/AnimatedInLineChart.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Group } from '@visx/group'
|
||||
import { LinePath } from '@visx/shape'
|
||||
import { easeCubicInOut } from 'd3'
|
||||
import React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { animated, useSpring } from 'react-spring'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { LineChartProps } from './LineChart'
|
||||
|
||||
type AnimatedInLineChartProps<T> = Omit<LineChartProps<T>, 'height' | 'width' | 'children'>
|
||||
|
||||
const config = {
|
||||
duration: 800,
|
||||
easing: easeCubicInOut,
|
||||
}
|
||||
|
||||
// code reference: https://airbnb.io/visx/lineradial
|
||||
|
||||
function AnimatedInLineChart<T>({
|
||||
data,
|
||||
getX,
|
||||
getY,
|
||||
marginTop,
|
||||
curve,
|
||||
color,
|
||||
strokeWidth,
|
||||
}: AnimatedInLineChartProps<T>) {
|
||||
const lineRef = useRef<SVGPathElement>(null)
|
||||
const [lineLength, setLineLength] = useState(0)
|
||||
const [shouldAnimate, setShouldAnimate] = useState(false)
|
||||
const [hasAnimatedIn, setHasAnimatedIn] = useState(false)
|
||||
|
||||
const spring = useSpring({
|
||||
frame: shouldAnimate ? 0 : 1,
|
||||
config,
|
||||
onRest: () => {
|
||||
setShouldAnimate(false)
|
||||
setHasAnimatedIn(true)
|
||||
},
|
||||
})
|
||||
|
||||
// We need to check to see after the "invisble" line has been drawn
|
||||
// what the length is to be able to animate in the line for the first time
|
||||
// This will run on each render to see if there is a new line length
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(() => {
|
||||
if (lineRef.current) {
|
||||
const length = lineRef.current.getTotalLength()
|
||||
if (length !== lineLength) {
|
||||
setLineLength(length)
|
||||
}
|
||||
if (length > 0 && !shouldAnimate && !hasAnimatedIn) {
|
||||
setShouldAnimate(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
const theme = useTheme()
|
||||
const lineColor = color ?? theme.accentAction
|
||||
|
||||
return (
|
||||
<Group top={marginTop}>
|
||||
<LinePath curve={curve} x={getX} y={getY}>
|
||||
{({ path }) => {
|
||||
const d = path(data) || ''
|
||||
return (
|
||||
<>
|
||||
<animated.path
|
||||
d={d}
|
||||
ref={lineRef}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeOpacity={hasAnimatedIn ? 1 : 0}
|
||||
fill="none"
|
||||
stroke={lineColor}
|
||||
/>
|
||||
{shouldAnimate && lineLength !== 0 && (
|
||||
<animated.path
|
||||
d={d}
|
||||
strokeWidth={strokeWidth}
|
||||
fill="none"
|
||||
stroke={lineColor}
|
||||
strokeDashoffset={spring.frame.to((v) => v * lineLength)}
|
||||
strokeDasharray={lineLength}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</LinePath>
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
|
||||
export default AnimatedInLineChart
|
||||
@@ -6,7 +6,7 @@ import { ReactNode } from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
import { Color } from 'theme/styled'
|
||||
|
||||
interface LineChartProps<T> {
|
||||
export interface LineChartProps<T> {
|
||||
data: T[]
|
||||
getX: (t: T) => number
|
||||
getY: (t: T) => number
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { SparkLineLoadingBubble } from 'components/Tokens/TokenTable/TokenRow'
|
||||
import { curveCardinal, scaleLinear } from 'd3'
|
||||
import { filterPrices } from 'graphql/data/Token'
|
||||
import { TopToken } from 'graphql/data/TopTokens'
|
||||
import { PricePoint } from 'graphql/data/Token'
|
||||
import { SparklineMap, TopToken } from 'graphql/data/TopTokens'
|
||||
import { TimePeriod } from 'graphql/data/util'
|
||||
import React from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
import { memo } from 'react'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { DATA_EMPTY, getPriceBounds } from '../Tokens/TokenDetails/PriceChart'
|
||||
import { getPriceBounds } from '../Tokens/TokenDetails/PriceChart'
|
||||
import LineChart from './LineChart'
|
||||
|
||||
type PricePoint = { value: number; timestamp: number }
|
||||
const LoadingContainer = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
interface SparklineChartProps {
|
||||
width: number
|
||||
@@ -16,20 +22,43 @@ interface SparklineChartProps {
|
||||
tokenData: TopToken
|
||||
pricePercentChange: number | undefined | null
|
||||
timePeriod: TimePeriod
|
||||
sparklineMap: SparklineMap
|
||||
}
|
||||
|
||||
function SparklineChart({ width, height, tokenData, pricePercentChange, timePeriod }: SparklineChartProps) {
|
||||
function _SparklineChart({
|
||||
width,
|
||||
height,
|
||||
tokenData,
|
||||
pricePercentChange,
|
||||
timePeriod,
|
||||
sparklineMap,
|
||||
}: SparklineChartProps) {
|
||||
const theme = useTheme()
|
||||
// for sparkline
|
||||
const pricePoints = filterPrices(tokenData?.market?.priceHistory) ?? []
|
||||
const hasData = pricePoints.length !== 0
|
||||
const startingPrice = hasData ? pricePoints[0] : DATA_EMPTY
|
||||
const endingPrice = hasData ? pricePoints[pricePoints.length - 1] : DATA_EMPTY
|
||||
const widthScale = scaleLinear().domain([startingPrice.timestamp, endingPrice.timestamp]).range([0, 124])
|
||||
const rdScale = scaleLinear().domain(getPriceBounds(pricePoints)).range([42, 0])
|
||||
const pricePoints = tokenData?.address ? sparklineMap[tokenData.address] : null
|
||||
|
||||
/* Default curve doesn't look good for the ALL chart */
|
||||
const curveTension = timePeriod === TimePeriod.ALL ? 0.75 : 0.9
|
||||
// Don't display if there's one or less pricepoints
|
||||
if (!pricePoints || pricePoints.length <= 1) {
|
||||
return (
|
||||
<LoadingContainer>
|
||||
<SparkLineLoadingBubble />
|
||||
</LoadingContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const startingPrice = pricePoints[0]
|
||||
const endingPrice = pricePoints[pricePoints.length - 1]
|
||||
const widthScale = scaleLinear()
|
||||
.domain(
|
||||
// the range of possible input values
|
||||
[startingPrice.timestamp, endingPrice.timestamp]
|
||||
)
|
||||
.range(
|
||||
// the range of possible output values that the inputs should be transformed to (see https://www.d3indepth.com/scales/ for details)
|
||||
[0, 110]
|
||||
)
|
||||
const rdScale = scaleLinear().domain(getPriceBounds(pricePoints)).range([30, 0])
|
||||
const curveTension = 0.9
|
||||
|
||||
return (
|
||||
<LineChart
|
||||
@@ -37,6 +66,7 @@ function SparklineChart({ width, height, tokenData, pricePercentChange, timePeri
|
||||
getX={(p: PricePoint) => widthScale(p.timestamp)}
|
||||
getY={(p: PricePoint) => rdScale(p.value)}
|
||||
curve={curveCardinal.tension(curveTension)}
|
||||
marginTop={5}
|
||||
color={pricePercentChange && pricePercentChange < 0 ? theme.accentFailure : theme.accentSuccess}
|
||||
strokeWidth={1.5}
|
||||
width={width}
|
||||
@@ -45,4 +75,4 @@ function SparklineChart({ width, height, tokenData, pricePercentChange, timePeri
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(SparklineChart)
|
||||
export default memo(_SparklineChart)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro'
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import HoverInlineText from 'components/HoverInlineText'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { useMemo } from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
|
||||
@@ -18,6 +18,7 @@ export function FiatValue({
|
||||
priceImpact?: Percent
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const redesignFlagEnabled = useRedesignFlag() === RedesignVariant.Enabled
|
||||
const priceImpactColor = useMemo(() => {
|
||||
if (!priceImpact) return undefined
|
||||
if (priceImpact.lessThan('0')) return theme.deprecated_green1
|
||||
@@ -30,19 +31,15 @@ export function FiatValue({
|
||||
const p = Number(fiatValue?.toFixed())
|
||||
const visibleDecimalPlaces = p < 1.05 ? 4 : 2
|
||||
|
||||
const textColor = redesignFlagEnabled
|
||||
? theme.textSecondary
|
||||
: fiatValue
|
||||
? theme.deprecated_text3
|
||||
: theme.deprecated_text4
|
||||
|
||||
return (
|
||||
<ThemedText.DeprecatedBody fontSize={14} color={fiatValue ? theme.deprecated_text3 : theme.deprecated_text4}>
|
||||
{fiatValue ? (
|
||||
<Trans>
|
||||
$
|
||||
<HoverInlineText
|
||||
text={fiatValue?.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })}
|
||||
textColor={fiatValue ? theme.deprecated_text3 : theme.deprecated_text4}
|
||||
/>
|
||||
</Trans>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<ThemedText.DeprecatedBody fontSize={14} color={textColor}>
|
||||
{fiatValue && <>${fiatValue?.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })}</>}
|
||||
{priceImpact ? (
|
||||
<span style={{ color: priceImpactColor }}>
|
||||
{' '}
|
||||
|
||||
@@ -11,8 +11,7 @@ import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Lock } from 'react-feather'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import styled, { css, useTheme } from 'styled-components/macro'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
|
||||
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
@@ -51,7 +50,7 @@ const FixedContainer = styled.div<{ redesignFlag: boolean }>`
|
||||
`
|
||||
|
||||
const Container = styled.div<{ hideInput: boolean; disabled: boolean; redesignFlag: boolean }>`
|
||||
min-height: ${({ redesignFlag }) => redesignFlag && '69px'};
|
||||
min-height: ${({ redesignFlag }) => redesignFlag && '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)};
|
||||
@@ -78,7 +77,7 @@ const CurrencySelect = styled(ButtonGray)<{
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
redesignFlag
|
||||
? selected
|
||||
? theme.backgroundSurface
|
||||
? theme.backgroundInteractive
|
||||
: theme.accentAction
|
||||
: selected
|
||||
? theme.deprecated_bg2
|
||||
@@ -100,30 +99,51 @@ const CurrencySelect = styled(ButtonGray)<{
|
||||
gap: ${({ redesignFlag }) => (redesignFlag ? '8px' : '0px')};
|
||||
justify-content: space-between;
|
||||
margin-left: ${({ hideInput }) => (hideInput ? '0' : '12px')};
|
||||
:focus,
|
||||
:hover {
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
selected
|
||||
? redesignFlag
|
||||
? theme.backgroundSurface
|
||||
: theme.deprecated_bg3
|
||||
: darken(0.05, theme.deprecated_primary1)};
|
||||
}
|
||||
|
||||
${({ redesignFlag, selected }) =>
|
||||
!redesignFlag &&
|
||||
css`
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => (selected ? theme.deprecated_bg3 : darken(0.05, theme.deprecated_primary1))};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: ${({ theme }) => (selected ? theme.deprecated_bg3 : darken(0.05, theme.deprecated_primary1))};
|
||||
}
|
||||
`}
|
||||
|
||||
${({ redesignFlag, selected }) =>
|
||||
redesignFlag &&
|
||||
css`
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: ${({ theme }) => (selected ? theme.backgroundInteractive : theme.accentAction)};
|
||||
}
|
||||
|
||||
&:before {
|
||||
background-size: 100%;
|
||||
border-radius: inherit;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:hover:before {
|
||||
background-color: ${({ theme }) => theme.stateOverlayHover};
|
||||
}
|
||||
|
||||
&:active:before {
|
||||
background-color: ${({ theme }) => theme.stateOverlayPressed};
|
||||
}
|
||||
`}
|
||||
|
||||
visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')};
|
||||
`
|
||||
const InputCurrencySelect = styled(CurrencySelect)<{ redesignFlag: boolean }>`
|
||||
background-color: ${({ theme, selected, redesignFlag }) =>
|
||||
redesignFlag && (selected ? theme.backgroundModule : theme.accentAction)};
|
||||
:focus,
|
||||
:hover {
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
selected
|
||||
? redesignFlag
|
||||
? theme.backgroundInteractive
|
||||
: theme.deprecated_bg3
|
||||
: darken(0.05, theme.deprecated_primary1)};
|
||||
}
|
||||
`
|
||||
|
||||
const InputRow = styled.div<{ selected: boolean; redesignFlag: boolean }>`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
@@ -133,13 +153,13 @@ const InputRow = styled.div<{ selected: boolean; redesignFlag: boolean }>`
|
||||
redesignFlag ? '0px' : selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 1rem 1rem'};
|
||||
`
|
||||
|
||||
const LabelRow = styled.div`
|
||||
const LabelRow = styled.div<{ redesignFlag: boolean }>`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textSecondary : theme.deprecated_text1)};
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
padding: 0 1rem 1rem;
|
||||
padding: ${({ redesignFlag }) => (redesignFlag ? '0px' : '0 1rem 1rem')};
|
||||
|
||||
span:hover {
|
||||
cursor: pointer;
|
||||
@@ -149,23 +169,11 @@ const LabelRow = styled.div`
|
||||
|
||||
const FiatRow = styled(LabelRow)<{ redesignFlag: boolean }>`
|
||||
justify-content: flex-end;
|
||||
min-height: ${({ redesignFlag }) => redesignFlag && '32px'};
|
||||
padding: ${({ redesignFlag }) => redesignFlag && '8px 0px'};
|
||||
min-height: ${({ redesignFlag }) => redesignFlag && '20px'};
|
||||
padding: ${({ redesignFlag }) => redesignFlag && '8px 0px 0px 0px'};
|
||||
height: ${({ redesignFlag }) => !redesignFlag && '24px'};
|
||||
`
|
||||
|
||||
const NoBalanceState = styled.div`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
font-weight: 400;
|
||||
justify-content: space-between;
|
||||
padding: 0px 4px 1px 4px;
|
||||
`
|
||||
const NoBalanceDash = styled.span`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
font-variant: small-caps;
|
||||
font-feature-settings: 'pnum' on, 'lnum' on;
|
||||
`
|
||||
|
||||
const Aligner = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -186,7 +194,7 @@ const StyledDropDown = styled(DropDown)<{ selected: boolean; redesignFlag: boole
|
||||
|
||||
const StyledTokenName = styled.span<{ active?: boolean; redesignFlag: boolean }>`
|
||||
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
|
||||
font-size: ${({ active }) => (active ? '18px' : '18px')};
|
||||
font-size: ${({ redesignFlag }) => (redesignFlag ? '20px' : '18px')};
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
|
||||
`
|
||||
|
||||
@@ -217,8 +225,9 @@ const StyledBalanceMax = styled.button<{ disabled?: boolean; redesignFlag: boole
|
||||
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean; redesignFlag: boolean }>`
|
||||
${loadingOpacityMixin};
|
||||
text-align: left;
|
||||
font-size: ${({ redesignFlag }) => redesignFlag && '36px'};
|
||||
line-height: ${({ redesignFlag }) => redesignFlag && '44px'};
|
||||
font-variant: ${({ redesignFlag }) => redesignFlag && 'small-caps'};
|
||||
font-feature-settings: ${({ redesignFlag }) => redesignFlag && 'pnum on, lnum on'};
|
||||
`
|
||||
|
||||
interface SwapCurrencyInputPanelProps {
|
||||
@@ -272,8 +281,6 @@ export default function SwapCurrencyInputPanel({
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
|
||||
const theme = useTheme()
|
||||
const { pathname } = useLocation()
|
||||
const isAddLiquidityPage = pathname.includes('/add') && !pathname.includes('/add/v2')
|
||||
|
||||
const handleDismissSearch = useCallback(() => {
|
||||
setModalOpen(false)
|
||||
@@ -310,7 +317,7 @@ export default function SwapCurrencyInputPanel({
|
||||
/>
|
||||
)}
|
||||
|
||||
<InputCurrencySelect
|
||||
<CurrencySelect
|
||||
disabled={!chainAllowed}
|
||||
visible={currency !== undefined}
|
||||
selected={!!currency}
|
||||
@@ -352,18 +359,8 @@ export default function SwapCurrencyInputPanel({
|
||||
</RowFixed>
|
||||
{onCurrencySelect && <StyledDropDown selected={!!currency} redesignFlag={redesignFlagEnabled} />}
|
||||
</Aligner>
|
||||
</InputCurrencySelect>
|
||||
</CurrencySelect>
|
||||
</InputRow>
|
||||
{redesignFlagEnabled && !currency && !isAddLiquidityPage && (
|
||||
<NoBalanceState>
|
||||
<FiatRow redesignFlag={redesignFlagEnabled}>
|
||||
<RowBetween>
|
||||
<NoBalanceDash>-</NoBalanceDash>
|
||||
<NoBalanceDash>-</NoBalanceDash>
|
||||
</RowBetween>
|
||||
</FiatRow>
|
||||
</NoBalanceState>
|
||||
)}
|
||||
{!hideInput && !hideBalance && currency && (
|
||||
<FiatRow redesignFlag={redesignFlagEnabled}>
|
||||
<RowBetween>
|
||||
@@ -373,11 +370,10 @@ export default function SwapCurrencyInputPanel({
|
||||
{account ? (
|
||||
<RowFixed style={{ height: '17px' }}>
|
||||
<ThemedText.DeprecatedBody
|
||||
onClick={onMax}
|
||||
color={theme.deprecated_text3}
|
||||
fontWeight={500}
|
||||
color={redesignFlag ? theme.textSecondary : theme.deprecated_text3}
|
||||
fontWeight={redesignFlag ? 400 : 500}
|
||||
fontSize={14}
|
||||
style={{ display: 'inline', cursor: 'pointer' }}
|
||||
style={{ display: 'inline' }}
|
||||
>
|
||||
{!hideBalance && currency && selectedCurrencyBalance ? (
|
||||
renderBalance ? (
|
||||
|
||||
@@ -140,7 +140,7 @@ const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
|
||||
|
||||
const StyledTokenName = styled.span<{ active?: boolean }>`
|
||||
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
|
||||
font-size: ${({ active }) => (active ? '18px' : '18px')};
|
||||
font-size: 20px;
|
||||
`
|
||||
|
||||
const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
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,32 +203,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}
|
||||
|
||||
@@ -2,17 +2,25 @@ import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfoOrDefault, L2ChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { AlertOctagon, AlertTriangle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
|
||||
const BodyRow = styled.div`
|
||||
color: ${({ theme }) => theme.deprecated_black};
|
||||
const BodyRow = styled.div<{ $redesignFlag?: boolean }>`
|
||||
color: ${({ theme, $redesignFlag }) => ($redesignFlag ? theme.textPrimary : theme.black)};
|
||||
font-size: 12px;
|
||||
font-weight: ${({ $redesignFlag }) => $redesignFlag && '400'};
|
||||
font-size: ${({ $redesignFlag }) => ($redesignFlag ? '14px' : '12px')};
|
||||
line-height: ${({ $redesignFlag }) => $redesignFlag && '20px'};
|
||||
`
|
||||
const CautionIcon = styled(AlertOctagon)`
|
||||
const CautionOctagon = styled(AlertOctagon)`
|
||||
color: ${({ theme }) => theme.deprecated_black};
|
||||
`
|
||||
|
||||
const CautionTriangle = styled(AlertTriangle)`
|
||||
color: ${({ theme }) => theme.accentWarning};
|
||||
`
|
||||
const Link = styled(ExternalLink)`
|
||||
color: ${({ theme }) => theme.deprecated_black};
|
||||
text-decoration: underline;
|
||||
@@ -23,21 +31,22 @@ const TitleRow = styled.div`
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
const TitleText = styled.div`
|
||||
color: black;
|
||||
font-weight: 600;
|
||||
const TitleText = styled.div<{ redesignFlag?: boolean }>`
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textPrimary : theme.black)};
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? '500' : '600')};
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
line-height: ${({ redesignFlag }) => (redesignFlag ? '24px' : '20px')};
|
||||
margin: 0px 12px;
|
||||
`
|
||||
const Wrapper = styled.div`
|
||||
background-color: ${({ theme }) => theme.deprecated_yellow3};
|
||||
const Wrapper = styled.div<{ redesignFlag?: boolean }>`
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundSurface : theme.deprecated_yellow3)};
|
||||
border-radius: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
bottom: 60px;
|
||||
display: none;
|
||||
max-width: 348px;
|
||||
padding: 16px 20px;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToMedium}px) {
|
||||
display: block;
|
||||
@@ -48,20 +57,21 @@ export function ChainConnectivityWarning() {
|
||||
const { chainId } = useWeb3React()
|
||||
const info = getChainInfoOrDefault(chainId)
|
||||
const label = info?.label
|
||||
const redesignFlag = useRedesignFlag() === RedesignVariant.Enabled
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Wrapper redesignFlag={redesignFlag}>
|
||||
<TitleRow>
|
||||
<CautionIcon />
|
||||
<TitleText>
|
||||
{redesignFlag ? <CautionTriangle /> : <CautionOctagon />}
|
||||
<TitleText redesignFlag={redesignFlag}>
|
||||
<Trans>Network Warning</Trans>
|
||||
</TitleText>
|
||||
</TitleRow>
|
||||
<BodyRow>
|
||||
<BodyRow $redesignFlag={redesignFlag}>
|
||||
{chainId === SupportedChainId.MAINNET ? (
|
||||
<Trans>You may have lost your network connection.</Trans>
|
||||
) : (
|
||||
<Trans>You may have lost your network connection, or {label} might be down right now.</Trans>
|
||||
<Trans>{label} might be down right now, or you may have lost your network connection.</Trans>
|
||||
)}{' '}
|
||||
{(info as L2ChainInfo).statusPage !== undefined && (
|
||||
<span>
|
||||
|
||||
@@ -10,7 +10,6 @@ const TextWrapper = styled.span<{
|
||||
textColor?: string
|
||||
}>`
|
||||
margin-left: ${({ margin }) => margin && '4px'};
|
||||
color: ${({ theme, link, textColor }) => (link ? theme.deprecated_blue1 : textColor ?? theme.deprecated_text1)};
|
||||
font-size: ${({ fontSize }) => fontSize ?? 'inherit'};
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
|
||||
@@ -5,9 +5,9 @@ import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const StyledIdenticon = styled.div<{ isNavbarEnabled: boolean }>`
|
||||
height: ${({ isNavbarEnabled }) => (isNavbarEnabled ? '24px' : '1rem')};
|
||||
width: ${({ isNavbarEnabled }) => (isNavbarEnabled ? '24px' : '1rem')};
|
||||
const StyledIdenticon = styled.div<{ iconSize: number }>`
|
||||
height: ${({ iconSize }) => `${iconSize}px`};
|
||||
width: ${({ iconSize }) => `${iconSize}px`};
|
||||
border-radius: 1.125rem;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg4};
|
||||
font-size: initial;
|
||||
@@ -19,12 +19,12 @@ const StyledAvatar = styled.img`
|
||||
border-radius: inherit;
|
||||
`
|
||||
|
||||
export default function Identicon() {
|
||||
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 = isNavbarEnabled ? 24 : 16
|
||||
const iconSize = size ? size : isNavbarEnabled ? 24 : 16
|
||||
|
||||
const icon = useMemo(() => account && jazzicon(iconSize, parseInt(account.slice(2, 10), 16)), [account, iconSize])
|
||||
const iconRef = useRef<HTMLDivElement>(null)
|
||||
@@ -44,7 +44,7 @@ export default function Identicon() {
|
||||
}, [icon, iconRef])
|
||||
|
||||
return (
|
||||
<StyledIdenticon isNavbarEnabled={isNavbarEnabled}>
|
||||
<StyledIdenticon iconSize={iconSize}>
|
||||
{avatar && fetchable ? (
|
||||
<StyledAvatar alt="avatar" src={avatar} onError={() => setFetchable(false)}></StyledAvatar>
|
||||
) : (
|
||||
|
||||
@@ -176,7 +176,7 @@ export default function LiquidityChartRangeInput({
|
||||
message={<Trans>Liquidity data not available.</Trans>}
|
||||
icon={<CloudOff size={56} stroke={theme.deprecated_text4} />}
|
||||
/>
|
||||
) : !formattedData || formattedData === [] || !price ? (
|
||||
) : !formattedData || formattedData.length === 0 || !price ? (
|
||||
<InfoBox
|
||||
message={<Trans>There is no liquidity data.</Trans>}
|
||||
icon={<BarChart2 size={56} stroke={theme.deprecated_text4} />}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { isMobile } from '../../utils/userAgent'
|
||||
|
||||
const AnimatedDialogOverlay = animated(DialogOverlay)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ redesignFlag?: boolean }>`
|
||||
const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ redesignFlag?: boolean; scrollOverlay?: boolean }>`
|
||||
&[data-reach-dialog-overlay] {
|
||||
z-index: ${Z_INDEX.modalBackdrop};
|
||||
background-color: transparent;
|
||||
@@ -18,6 +18,7 @@ const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ redesignFlag?: boole
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow-y: ${({ scrollOverlay }) => scrollOverlay && 'scroll'};
|
||||
justify-content: center;
|
||||
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundScrim : theme.deprecated_modalBG)};
|
||||
@@ -27,7 +28,7 @@ const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ redesignFlag?: boole
|
||||
const AnimatedDialogContent = animated(DialogContent)
|
||||
// destructure to not pass custom props to Dialog DOM element
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, redesignFlag, ...rest }) => (
|
||||
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, redesignFlag, scrollOverlay, ...rest }) => (
|
||||
<AnimatedDialogContent {...rest} />
|
||||
)).attrs({
|
||||
'aria-label': 'dialog',
|
||||
@@ -35,7 +36,7 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, rede
|
||||
overflow-y: auto;
|
||||
|
||||
&[data-reach-dialog-content] {
|
||||
margin: 0 0 2rem 0;
|
||||
margin: ${({ redesignFlag }) => (redesignFlag ? 'auto' : '0 0 2rem 0')};
|
||||
background-color: ${({ theme }) => theme.deprecated_bg0};
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg1};
|
||||
box-shadow: ${({ theme, redesignFlag }) =>
|
||||
@@ -45,7 +46,7 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, rede
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
align-self: ${({ mobile }) => (mobile ? 'flex-end' : 'center')};
|
||||
align-self: ${({ mobile }) => mobile && 'flex-end'};
|
||||
|
||||
max-width: 420px;
|
||||
${({ maxHeight }) =>
|
||||
@@ -58,11 +59,11 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, rede
|
||||
css`
|
||||
min-height: ${minHeight}vh;
|
||||
`}
|
||||
display: flex;
|
||||
display: ${({ scrollOverlay }) => (scrollOverlay ? 'inline-table' : 'flex')};
|
||||
border-radius: 20px;
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
${({ theme, redesignFlag }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
width: 65vw;
|
||||
margin: 0;
|
||||
margin: ${redesignFlag ? 'auto' : '0'};
|
||||
`}
|
||||
${({ theme, mobile }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
width: 85vw;
|
||||
@@ -87,6 +88,7 @@ interface ModalProps {
|
||||
initialFocusRef?: React.RefObject<any>
|
||||
children?: React.ReactNode
|
||||
redesignFlag?: boolean
|
||||
scrollOverlay?: boolean
|
||||
}
|
||||
|
||||
export default function Modal({
|
||||
@@ -97,8 +99,9 @@ export default function Modal({
|
||||
initialFocusRef,
|
||||
children,
|
||||
redesignFlag,
|
||||
scrollOverlay,
|
||||
}: ModalProps) {
|
||||
const fadeTransition = useTransition(isOpen, null, {
|
||||
const fadeTransition = useTransition(isOpen, {
|
||||
config: { duration: 200 },
|
||||
from: { opacity: 0 },
|
||||
enter: { opacity: 1 },
|
||||
@@ -119,16 +122,17 @@ export default function Modal({
|
||||
|
||||
return (
|
||||
<>
|
||||
{fadeTransition.map(
|
||||
({ item, key, props }) =>
|
||||
{fadeTransition(
|
||||
({ opacity }, item) =>
|
||||
item && (
|
||||
<StyledDialogOverlay
|
||||
key={key}
|
||||
style={props}
|
||||
as={AnimatedDialogOverlay}
|
||||
style={{ opacity: opacity.to({ range: [0.0, 1.0], output: [0, 1] }) }}
|
||||
onDismiss={onDismiss}
|
||||
initialFocusRef={initialFocusRef}
|
||||
unstable_lockFocusAcrossFrames={false}
|
||||
redesignFlag={redesignFlag}
|
||||
scrollOverlay={scrollOverlay}
|
||||
>
|
||||
<StyledDialogContent
|
||||
{...(isMobile
|
||||
@@ -142,6 +146,7 @@ export default function Modal({
|
||||
maxHeight={maxHeight}
|
||||
mobile={isMobile}
|
||||
redesignFlag={redesignFlag}
|
||||
scrollOverlay={scrollOverlay}
|
||||
>
|
||||
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
|
||||
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
|
||||
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
@@ -11,7 +10,6 @@ import {
|
||||
EllipsisIcon,
|
||||
GithubIconMenu,
|
||||
GovernanceIcon,
|
||||
ThinTagIcon,
|
||||
TwitterIconMenu,
|
||||
} from 'nft/components/icons'
|
||||
import { body, bodySmall } from 'nft/css/common.css'
|
||||
@@ -117,7 +115,6 @@ export const MenuDropdown = () => {
|
||||
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
|
||||
const togglePrivacyPolicy = useToggleModal(ApplicationModal.PRIVACY_POLICY)
|
||||
const openFeatureFlagsModal = useToggleModal(ApplicationModal.FEATURE_FLAGS)
|
||||
const nftFlag = useNftFlag()
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
useOnClickOutside(ref, isOpen ? toggleOpen : undefined)
|
||||
@@ -133,16 +130,6 @@ export const MenuDropdown = () => {
|
||||
<NavDropdown top={{ sm: 'unset', lg: '56' }} bottom={{ sm: '56', lg: 'unset' }} right="0">
|
||||
<Column gap="16">
|
||||
<Column paddingX="8" gap="4">
|
||||
{nftFlag === NftVariant.Enabled && (
|
||||
<PrimaryMenuRow to="/nfts/sell" close={toggleOpen}>
|
||||
<Icon>
|
||||
<ThinTagIcon width={24} height={24} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Sell NFTs</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
)}
|
||||
<PrimaryMenuRow to="/vote" close={toggleOpen}>
|
||||
<Icon>
|
||||
<GovernanceIcon width={24} height={24} />
|
||||
@@ -156,7 +143,7 @@ export const MenuDropdown = () => {
|
||||
<BarChartIcon width={24} height={24} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>View token analytics</Trans>
|
||||
<Trans>View more analytics</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
</Column>
|
||||
|
||||
@@ -34,7 +34,7 @@ export const searchBarContainer = style([
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.lg}px)`]: {
|
||||
right: `-${DESKTOP_NAVBAR_WIDTH / 2 - MAGNIFYING_GLASS_ICON_WIDTH}px`,
|
||||
top: '-5px',
|
||||
top: '-3px',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -45,7 +45,6 @@ export const searchBar = style([
|
||||
sprinkles({
|
||||
color: 'textTertiary',
|
||||
paddingX: '16',
|
||||
cursor: 'pointer',
|
||||
background: 'backgroundSurface',
|
||||
}),
|
||||
])
|
||||
@@ -58,10 +57,9 @@ export const searchBarInput = style([
|
||||
color: { default: 'textPrimary', placeholder: 'textTertiary' },
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
lineHeight: '24',
|
||||
height: 'full',
|
||||
}),
|
||||
{
|
||||
lineHeight: '24px',
|
||||
},
|
||||
])
|
||||
|
||||
export const searchBarDropdown = style([
|
||||
@@ -85,10 +83,10 @@ export const suggestionRow = style([
|
||||
justifyContent: 'space-between',
|
||||
paddingY: '8',
|
||||
paddingX: '16',
|
||||
cursor: 'pointer',
|
||||
}),
|
||||
{
|
||||
':hover': {
|
||||
cursor: 'pointer',
|
||||
background: vars.color.lightGrayOverlay,
|
||||
},
|
||||
textDecoration: 'none',
|
||||
|
||||
@@ -1,307 +1,27 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { t } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { ElementName, Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import clsx from 'clsx'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { organizeSearchResults } from 'lib/utils/searchBar'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { magicalGradientOnHover, subheadSmall } from 'nft/css/common.css'
|
||||
import { useIsMobile, useIsTablet, useSearchHistory } from 'nft/hooks'
|
||||
import { fetchSearchCollections, fetchTrendingCollections } from 'nft/queries'
|
||||
import { Row } from 'nft/components/Flex'
|
||||
import { magicalGradientOnHover } from 'nft/css/common.css'
|
||||
import { useIsMobile, useIsTablet } from 'nft/hooks'
|
||||
import { fetchSearchCollections } from 'nft/queries'
|
||||
import { fetchSearchTokens } from 'nft/queries/genie/SearchTokensFetcher'
|
||||
import { fetchTrendingTokens } from 'nft/queries/genie/TrendingTokensFetcher'
|
||||
import { FungibleToken, GenieCollection, TimePeriod, TrendingCollection } from 'nft/types'
|
||||
import { formatEthPrice } from 'nft/utils/currency'
|
||||
import { ChangeEvent, ReactNode, useEffect, useMemo, useReducer, useRef, useState } from 'react'
|
||||
import { ChangeEvent, useEffect, useReducer, useRef, useState } from 'react'
|
||||
import { useQuery } from 'react-query'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ClockIcon,
|
||||
MagnifyingGlassIcon,
|
||||
NavMagnifyingGlassIcon,
|
||||
TrendingArrow,
|
||||
} from '../../nft/components/icons'
|
||||
import { ChevronLeftIcon, MagnifyingGlassIcon, NavMagnifyingGlassIcon } from '../../nft/components/icons'
|
||||
import { NavIcon } from './NavIcon'
|
||||
import * as styles from './SearchBar.css'
|
||||
import { CollectionRow, SkeletonRow, TokenRow } from './SuggestionRow'
|
||||
|
||||
interface SearchBarDropdownSectionProps {
|
||||
toggleOpen: () => void
|
||||
suggestions: (GenieCollection | FungibleToken)[]
|
||||
header: JSX.Element
|
||||
headerIcon?: JSX.Element
|
||||
hoveredIndex: number | undefined
|
||||
startingIndex: number
|
||||
setHoveredIndex: (index: number | undefined) => void
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
export const SearchBarDropdownSection = ({
|
||||
toggleOpen,
|
||||
suggestions,
|
||||
header,
|
||||
headerIcon = undefined,
|
||||
hoveredIndex,
|
||||
startingIndex,
|
||||
setHoveredIndex,
|
||||
isLoading,
|
||||
}: SearchBarDropdownSectionProps) => {
|
||||
return (
|
||||
<Column gap="12">
|
||||
<Row paddingX="16" paddingY="4" gap="8" color="grey300" className={subheadSmall} style={{ lineHeight: '20px' }}>
|
||||
{headerIcon ? headerIcon : null}
|
||||
<Box>{header}</Box>
|
||||
</Row>
|
||||
<Column gap="12">
|
||||
{suggestions.map((suggestion, index) =>
|
||||
isLoading ? (
|
||||
<SkeletonRow key={index} />
|
||||
) : isCollection(suggestion) ? (
|
||||
<CollectionRow
|
||||
key={suggestion.address}
|
||||
collection={suggestion as GenieCollection}
|
||||
isHovered={hoveredIndex === index + startingIndex}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
index={index + startingIndex}
|
||||
/>
|
||||
) : (
|
||||
<TokenRow
|
||||
key={suggestion.address}
|
||||
token={suggestion as FungibleToken}
|
||||
isHovered={hoveredIndex === index + startingIndex}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
index={index + startingIndex}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Column>
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
|
||||
interface SearchBarDropdownProps {
|
||||
toggleOpen: () => void
|
||||
tokens: FungibleToken[]
|
||||
collections: GenieCollection[]
|
||||
hasInput: boolean
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput, isLoading }: SearchBarDropdownProps) => {
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(0)
|
||||
const searchHistory = useSearchHistory((state: { history: (FungibleToken | GenieCollection)[] }) => state.history)
|
||||
const shortenedHistory = useMemo(() => searchHistory.slice(0, 2), [searchHistory])
|
||||
const { pathname } = useLocation()
|
||||
const isNFTPage = pathname.includes('/nfts')
|
||||
const isTokenPage = pathname.includes('/tokens')
|
||||
const phase1Flag = useNftFlag()
|
||||
const [resultsState, setResultsState] = useState<ReactNode>()
|
||||
|
||||
const tokenSearchResults =
|
||||
tokens.length > 0 ? (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={isNFTPage ? collections.length : 0}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={tokens}
|
||||
header={<Trans>Tokens</Trans>}
|
||||
/>
|
||||
) : (
|
||||
<Box className={styles.notFoundContainer}>
|
||||
<Trans>No tokens found.</Trans>
|
||||
</Box>
|
||||
)
|
||||
|
||||
const collectionSearchResults =
|
||||
phase1Flag === NftVariant.Enabled ? (
|
||||
collections.length > 0 ? (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={isNFTPage ? 0 : tokens.length}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={collections}
|
||||
header={<Trans>NFT Collections</Trans>}
|
||||
/>
|
||||
) : (
|
||||
<Box className={styles.notFoundContainer}>No NFT collections found.</Box>
|
||||
)
|
||||
) : null
|
||||
|
||||
const { data: trendingCollectionResults, isLoading: trendingCollectionsAreLoading } = useQuery(
|
||||
['trendingCollections', 'eth', 'twenty_four_hours'],
|
||||
() => fetchTrendingCollections({ volumeType: 'eth', timePeriod: 'ONE_DAY' as TimePeriod, size: 3 })
|
||||
)
|
||||
|
||||
const trendingCollections = useMemo(
|
||||
() =>
|
||||
trendingCollectionResults
|
||||
? trendingCollectionResults
|
||||
.map((collection) => ({
|
||||
...collection,
|
||||
collectionAddress: collection.address,
|
||||
floorPrice: formatEthPrice(collection.floor?.toString()),
|
||||
stats: {
|
||||
total_supply: collection.totalSupply,
|
||||
one_day_change: collection.floorChange,
|
||||
},
|
||||
}))
|
||||
.slice(0, isNFTPage ? 3 : 2)
|
||||
: [...Array<GenieCollection>(isNFTPage ? 3 : 2)],
|
||||
[isNFTPage, trendingCollectionResults]
|
||||
)
|
||||
|
||||
const { data: trendingTokenResults, isLoading: trendingTokensAreLoading } = useQuery(
|
||||
['trendingTokens'],
|
||||
() => fetchTrendingTokens(4),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
}
|
||||
)
|
||||
|
||||
const trendingTokensLength = phase1Flag === NftVariant.Enabled ? (isTokenPage ? 3 : 2) : 4
|
||||
|
||||
const trendingTokens = useMemo(
|
||||
() =>
|
||||
trendingTokenResults
|
||||
? trendingTokenResults.slice(0, trendingTokensLength)
|
||||
: [...Array<FungibleToken>(trendingTokensLength)],
|
||||
[trendingTokenResults, trendingTokensLength]
|
||||
)
|
||||
|
||||
const totalSuggestions = hasInput
|
||||
? tokens.length + collections.length
|
||||
: Math.min(shortenedHistory.length, 2) +
|
||||
(isNFTPage || !isTokenPage ? trendingCollections?.length ?? 0 : 0) +
|
||||
(isTokenPage || !isNFTPage ? trendingTokens?.length ?? 0 : 0)
|
||||
|
||||
// Navigate search results via arrow keys
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'ArrowUp') {
|
||||
event.preventDefault()
|
||||
if (!hoveredIndex) {
|
||||
setHoveredIndex(totalSuggestions - 1)
|
||||
} else {
|
||||
setHoveredIndex(hoveredIndex - 1)
|
||||
}
|
||||
} else if (event.key === 'ArrowDown') {
|
||||
event.preventDefault()
|
||||
if (hoveredIndex && hoveredIndex === totalSuggestions - 1) {
|
||||
setHoveredIndex(0)
|
||||
} else {
|
||||
setHoveredIndex((hoveredIndex ?? -1) + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', keyDownHandler)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyDownHandler)
|
||||
}
|
||||
}, [toggleOpen, hoveredIndex, totalSuggestions])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading) {
|
||||
const currentState = () =>
|
||||
hasInput ? (
|
||||
// Empty or Up to 8 combined tokens and nfts
|
||||
<Column gap="20">
|
||||
{isNFTPage ? (
|
||||
<>
|
||||
{collectionSearchResults}
|
||||
{tokenSearchResults}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{tokenSearchResults}
|
||||
{collectionSearchResults}
|
||||
</>
|
||||
)}
|
||||
</Column>
|
||||
) : (
|
||||
// Recent Searches, Trending Tokens, Trending Collections
|
||||
<Column gap="20">
|
||||
{shortenedHistory.length > 0 && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={0}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={shortenedHistory}
|
||||
header={<Trans>Recent searches</Trans>}
|
||||
headerIcon={<ClockIcon />}
|
||||
/>
|
||||
)}
|
||||
{!isNFTPage && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={shortenedHistory.length}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={trendingTokens}
|
||||
header={<Trans>Popular tokens</Trans>}
|
||||
headerIcon={<TrendingArrow />}
|
||||
isLoading={trendingTokensAreLoading}
|
||||
/>
|
||||
)}
|
||||
{!isTokenPage && phase1Flag === NftVariant.Enabled && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={shortenedHistory.length + (isNFTPage ? 0 : trendingTokens?.length ?? 0)}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={trendingCollections as unknown as GenieCollection[]}
|
||||
header={<Trans>Popular NFT collections</Trans>}
|
||||
headerIcon={<TrendingArrow />}
|
||||
isLoading={trendingCollectionsAreLoading}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
)
|
||||
|
||||
setResultsState(currentState)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
isLoading,
|
||||
tokens,
|
||||
collections,
|
||||
trendingCollections,
|
||||
trendingCollectionsAreLoading,
|
||||
trendingTokens,
|
||||
trendingTokensAreLoading,
|
||||
hoveredIndex,
|
||||
phase1Flag,
|
||||
toggleOpen,
|
||||
shortenedHistory,
|
||||
hasInput,
|
||||
isNFTPage,
|
||||
isTokenPage,
|
||||
])
|
||||
|
||||
return (
|
||||
<Box className={styles.searchBarDropdown}>
|
||||
<Box opacity={isLoading ? '0.3' : '1'} transition="125">
|
||||
{resultsState}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function isCollection(suggestion: GenieCollection | FungibleToken | TrendingCollection) {
|
||||
return (suggestion as FungibleToken).decimals === undefined
|
||||
}
|
||||
import { SearchBarDropdown } from './SearchBarDropdown'
|
||||
|
||||
export const SearchBar = () => {
|
||||
const [isOpen, toggleOpen] = useReducer((state: boolean) => !state, false)
|
||||
@@ -372,7 +92,12 @@ export const SearchBar = () => {
|
||||
|
||||
const placeholderText = phase1Flag === NftVariant.Enabled ? t`Search tokens and NFT collections` : t`Search tokens`
|
||||
const isMobileOrTablet = isMobile || isTablet
|
||||
const showCenteredSearchContent = !isOpen && phase1Flag !== NftVariant.Enabled && !isMobileOrTablet
|
||||
const showCenteredSearchContent =
|
||||
!isOpen && phase1Flag !== NftVariant.Enabled && !isMobileOrTablet && searchValue.length === 0
|
||||
|
||||
const navbarSearchEventProperties = {
|
||||
navbar_search_input_text: debouncedSearchValue,
|
||||
}
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
@@ -404,20 +129,27 @@ export const SearchBar = () => {
|
||||
<ChevronLeftIcon />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
as="input"
|
||||
placeholder={placeholderText}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
!isOpen && toggleOpen()
|
||||
setSearchValue(event.target.value)
|
||||
}}
|
||||
className={`${styles.searchBarInput} ${
|
||||
showCenteredSearchContent ? styles.searchContentCentered : styles.searchContentLeftAlign
|
||||
}`}
|
||||
value={searchValue}
|
||||
ref={inputRef}
|
||||
width={phase1Flag === NftVariant.Enabled || isOpen ? 'full' : '160'}
|
||||
/>
|
||||
<TraceEvent
|
||||
events={[Event.onFocus]}
|
||||
name={EventName.NAVBAR_SEARCH_SELECTED}
|
||||
element={ElementName.NAVBAR_SEARCH_INPUT}
|
||||
>
|
||||
<Box
|
||||
as="input"
|
||||
placeholder={placeholderText}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
!isOpen && toggleOpen()
|
||||
setSearchValue(event.target.value)
|
||||
}}
|
||||
onBlur={() => sendAnalyticsEvent(EventName.NAVBAR_SEARCH_EXITED, navbarSearchEventProperties)}
|
||||
className={`${styles.searchBarInput} ${
|
||||
showCenteredSearchContent ? styles.searchContentCentered : styles.searchContentLeftAlign
|
||||
}`}
|
||||
value={searchValue}
|
||||
ref={inputRef}
|
||||
width={phase1Flag === NftVariant.Enabled || isOpen ? 'full' : '160'}
|
||||
/>
|
||||
</TraceEvent>
|
||||
</Row>
|
||||
<Box className={clsx(isOpen ? styles.visible : styles.hidden)}>
|
||||
{isOpen && (
|
||||
|
||||
312
src/components/NavBar/SearchBarDropdown.tsx
Normal file
312
src/components/NavBar/SearchBarDropdown.tsx
Normal file
@@ -0,0 +1,312 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from 'analytics'
|
||||
import { EventName } from 'analytics/constants'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { subheadSmall } from 'nft/css/common.css'
|
||||
import { useSearchHistory } from 'nft/hooks'
|
||||
import { fetchTrendingCollections } from 'nft/queries'
|
||||
import { fetchTrendingTokens } from 'nft/queries/genie/TrendingTokensFetcher'
|
||||
import { FungibleToken, GenieCollection, TimePeriod, TrendingCollection } from 'nft/types'
|
||||
import { formatEthPrice } from 'nft/utils/currency'
|
||||
import { ReactNode, useEffect, useMemo, useState } from 'react'
|
||||
import { useQuery } from 'react-query'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { ClockIcon, TrendingArrow } from '../../nft/components/icons'
|
||||
import * as styles from './SearchBar.css'
|
||||
import { CollectionRow, SkeletonRow, TokenRow } from './SuggestionRow'
|
||||
|
||||
function isCollection(suggestion: GenieCollection | FungibleToken | TrendingCollection) {
|
||||
return (suggestion as FungibleToken).decimals === undefined
|
||||
}
|
||||
|
||||
interface SearchBarDropdownSectionProps {
|
||||
toggleOpen: () => void
|
||||
suggestions: (GenieCollection | FungibleToken)[]
|
||||
header: JSX.Element
|
||||
headerIcon?: JSX.Element
|
||||
hoveredIndex: number | undefined
|
||||
startingIndex: number
|
||||
setHoveredIndex: (index: number | undefined) => void
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
export const SearchBarDropdownSection = ({
|
||||
toggleOpen,
|
||||
suggestions,
|
||||
header,
|
||||
headerIcon = undefined,
|
||||
hoveredIndex,
|
||||
startingIndex,
|
||||
setHoveredIndex,
|
||||
isLoading,
|
||||
}: SearchBarDropdownSectionProps) => {
|
||||
return (
|
||||
<Column gap="12">
|
||||
<Row paddingX="16" paddingY="4" gap="8" color="grey300" className={subheadSmall} style={{ lineHeight: '20px' }}>
|
||||
{headerIcon ? headerIcon : null}
|
||||
<Box>{header}</Box>
|
||||
</Row>
|
||||
<Column gap="12">
|
||||
{suggestions.map((suggestion, index) =>
|
||||
isLoading ? (
|
||||
<SkeletonRow key={index} />
|
||||
) : isCollection(suggestion) ? (
|
||||
<CollectionRow
|
||||
key={suggestion.address}
|
||||
collection={suggestion as GenieCollection}
|
||||
isHovered={hoveredIndex === index + startingIndex}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
traceEvent={() =>
|
||||
sendAnalyticsEvent(EventName.NAVBAR_SEARCH_EXITED, {
|
||||
position: index,
|
||||
selected_type: 'collection',
|
||||
suggestion_count: suggestions.length,
|
||||
selected_name: suggestion.name,
|
||||
selected_address: suggestion.address,
|
||||
})
|
||||
}
|
||||
index={index + startingIndex}
|
||||
/>
|
||||
) : (
|
||||
<TokenRow
|
||||
key={suggestion.address}
|
||||
token={suggestion as FungibleToken}
|
||||
isHovered={hoveredIndex === index + startingIndex}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
traceEvent={() =>
|
||||
sendAnalyticsEvent(EventName.NAVBAR_SEARCH_EXITED, {
|
||||
position: index,
|
||||
selected_type: 'token',
|
||||
suggestion_count: suggestions.length,
|
||||
selected_name: suggestion.name,
|
||||
selected_address: suggestion.address,
|
||||
})
|
||||
}
|
||||
index={index + startingIndex}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Column>
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
|
||||
interface SearchBarDropdownProps {
|
||||
toggleOpen: () => void
|
||||
tokens: FungibleToken[]
|
||||
collections: GenieCollection[]
|
||||
hasInput: boolean
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export const SearchBarDropdown = ({ toggleOpen, tokens, collections, hasInput, isLoading }: SearchBarDropdownProps) => {
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(0)
|
||||
const { history: searchHistory, updateItem: updateSearchHistory } = useSearchHistory()
|
||||
const shortenedHistory = useMemo(() => searchHistory.slice(0, 2), [searchHistory])
|
||||
const { pathname } = useLocation()
|
||||
const isNFTPage = pathname.includes('/nfts')
|
||||
const isTokenPage = pathname.includes('/tokens')
|
||||
const phase1Flag = useNftFlag()
|
||||
const [resultsState, setResultsState] = useState<ReactNode>()
|
||||
|
||||
const { data: trendingCollectionResults, isLoading: trendingCollectionsAreLoading } = useQuery(
|
||||
['trendingCollections', 'eth', 'twenty_four_hours'],
|
||||
() => fetchTrendingCollections({ volumeType: 'eth', timePeriod: 'ONE_DAY' as TimePeriod, size: 3 })
|
||||
)
|
||||
|
||||
const trendingCollections = useMemo(
|
||||
() =>
|
||||
trendingCollectionResults
|
||||
? trendingCollectionResults
|
||||
.map((collection) => ({
|
||||
...collection,
|
||||
collectionAddress: collection.address,
|
||||
floorPrice: formatEthPrice(collection.floor?.toString()),
|
||||
stats: {
|
||||
total_supply: collection.totalSupply,
|
||||
one_day_change: collection.floorChange,
|
||||
},
|
||||
}))
|
||||
.slice(0, isNFTPage ? 3 : 2)
|
||||
: [...Array<GenieCollection>(isNFTPage ? 3 : 2)],
|
||||
[isNFTPage, trendingCollectionResults]
|
||||
)
|
||||
|
||||
const { data: trendingTokenResults, isLoading: trendingTokensAreLoading } = useQuery(
|
||||
['trendingTokens'],
|
||||
() => fetchTrendingTokens(4),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
}
|
||||
)
|
||||
useEffect(() => {
|
||||
trendingTokenResults?.forEach(updateSearchHistory)
|
||||
}, [trendingTokenResults, updateSearchHistory])
|
||||
|
||||
const trendingTokensLength = phase1Flag === NftVariant.Enabled ? (isTokenPage ? 3 : 2) : 4
|
||||
const trendingTokens = useMemo(
|
||||
() =>
|
||||
trendingTokenResults
|
||||
? trendingTokenResults.slice(0, trendingTokensLength)
|
||||
: [...Array<FungibleToken>(trendingTokensLength)],
|
||||
[trendingTokenResults, trendingTokensLength]
|
||||
)
|
||||
|
||||
const totalSuggestions = hasInput
|
||||
? tokens.length + collections.length
|
||||
: Math.min(shortenedHistory.length, 2) +
|
||||
(isNFTPage || !isTokenPage ? trendingCollections?.length ?? 0 : 0) +
|
||||
(isTokenPage || !isNFTPage ? trendingTokens?.length ?? 0 : 0)
|
||||
|
||||
// Navigate search results via arrow keys
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'ArrowUp') {
|
||||
event.preventDefault()
|
||||
if (!hoveredIndex) {
|
||||
setHoveredIndex(totalSuggestions - 1)
|
||||
} else {
|
||||
setHoveredIndex(hoveredIndex - 1)
|
||||
}
|
||||
} else if (event.key === 'ArrowDown') {
|
||||
event.preventDefault()
|
||||
if (hoveredIndex && hoveredIndex === totalSuggestions - 1) {
|
||||
setHoveredIndex(0)
|
||||
} else {
|
||||
setHoveredIndex((hoveredIndex ?? -1) + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', keyDownHandler)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyDownHandler)
|
||||
}
|
||||
}, [toggleOpen, hoveredIndex, totalSuggestions])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading) {
|
||||
const tokenSearchResults =
|
||||
tokens.length > 0 ? (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={isNFTPage ? collections.length : 0}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={tokens}
|
||||
header={<Trans>Tokens</Trans>}
|
||||
/>
|
||||
) : (
|
||||
<Box className={styles.notFoundContainer}>
|
||||
<Trans>No tokens found.</Trans>
|
||||
</Box>
|
||||
)
|
||||
|
||||
const collectionSearchResults =
|
||||
phase1Flag === NftVariant.Enabled ? (
|
||||
collections.length > 0 ? (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={isNFTPage ? 0 : tokens.length}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={collections}
|
||||
header={<Trans>NFT Collections</Trans>}
|
||||
/>
|
||||
) : (
|
||||
<Box className={styles.notFoundContainer}>No NFT collections found.</Box>
|
||||
)
|
||||
) : null
|
||||
|
||||
const currentState = () =>
|
||||
hasInput ? (
|
||||
// Empty or Up to 8 combined tokens and nfts
|
||||
<Column gap="20">
|
||||
{isNFTPage ? (
|
||||
<>
|
||||
{collectionSearchResults}
|
||||
{tokenSearchResults}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{tokenSearchResults}
|
||||
{collectionSearchResults}
|
||||
</>
|
||||
)}
|
||||
</Column>
|
||||
) : (
|
||||
// Recent Searches, Trending Tokens, Trending Collections
|
||||
<Column gap="20">
|
||||
{shortenedHistory.length > 0 && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={0}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={shortenedHistory}
|
||||
header={<Trans>Recent searches</Trans>}
|
||||
headerIcon={<ClockIcon />}
|
||||
/>
|
||||
)}
|
||||
{!isNFTPage && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={shortenedHistory.length}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={trendingTokens}
|
||||
header={<Trans>Popular tokens</Trans>}
|
||||
headerIcon={<TrendingArrow />}
|
||||
isLoading={trendingTokensAreLoading}
|
||||
/>
|
||||
)}
|
||||
{!isTokenPage && phase1Flag === NftVariant.Enabled && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={shortenedHistory.length + (isNFTPage ? 0 : trendingTokens?.length ?? 0)}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={trendingCollections as unknown as GenieCollection[]}
|
||||
header={<Trans>Popular NFT collections</Trans>}
|
||||
headerIcon={<TrendingArrow />}
|
||||
isLoading={trendingCollectionsAreLoading}
|
||||
/>
|
||||
)}
|
||||
</Column>
|
||||
)
|
||||
|
||||
setResultsState(currentState)
|
||||
}
|
||||
}, [
|
||||
isLoading,
|
||||
tokens,
|
||||
collections,
|
||||
trendingCollections,
|
||||
trendingCollectionsAreLoading,
|
||||
trendingTokens,
|
||||
trendingTokensAreLoading,
|
||||
hoveredIndex,
|
||||
phase1Flag,
|
||||
toggleOpen,
|
||||
shortenedHistory,
|
||||
hasInput,
|
||||
isNFTPage,
|
||||
isTokenPage,
|
||||
])
|
||||
|
||||
return (
|
||||
<Box className={styles.searchBarDropdown}>
|
||||
<Box opacity={isLoading ? '0.3' : '1'} transition="125">
|
||||
{resultsState}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -6,7 +6,7 @@ export const bagQuantity = style([
|
||||
position: 'absolute',
|
||||
top: '4',
|
||||
right: '4',
|
||||
backgroundColor: 'backgroundAction',
|
||||
backgroundColor: 'accentAction',
|
||||
borderRadius: 'round',
|
||||
color: 'explicitWhite',
|
||||
textAlign: 'center',
|
||||
|
||||
@@ -23,11 +23,11 @@ export const ShoppingBag = () => {
|
||||
setSellQuantity(sellAssets.length)
|
||||
}, [sellAssets])
|
||||
|
||||
const isSell = location.pathname === '/nfts/sell'
|
||||
const isProfilePage = location.pathname === '/profile'
|
||||
|
||||
return (
|
||||
<NavIcon onClick={toggleBag}>
|
||||
{isSell ? (
|
||||
{isProfilePage ? (
|
||||
<>
|
||||
<TagIcon width={20} height={20} />
|
||||
{sellQuantity ? (
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import clsx from 'clsx'
|
||||
import { L2NetworkLogo, LogoContainer } from 'components/Tokens/TokenTable/TokenRow'
|
||||
import { VerifiedIcon } from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { getTokenDetailsURL } from 'graphql/data/util'
|
||||
import uriToHttp from 'lib/utils/uriToHttp'
|
||||
import { Box } from 'nft/components/Box'
|
||||
@@ -10,8 +14,8 @@ import { ethNumberStandardFormatter } from 'nft/utils/currency'
|
||||
import { putCommas } from 'nft/utils/putCommas'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { formatDollar } from 'utils/formatNumbers'
|
||||
|
||||
import { VerifiedIcon } from '../../nft/components/icons'
|
||||
import * as styles from './SearchBar.css'
|
||||
|
||||
interface CollectionRowProps {
|
||||
@@ -19,10 +23,18 @@ interface CollectionRowProps {
|
||||
isHovered: boolean
|
||||
setHoveredIndex: (index: number | undefined) => void
|
||||
toggleOpen: () => void
|
||||
traceEvent: () => void
|
||||
index: number
|
||||
}
|
||||
|
||||
export const CollectionRow = ({ collection, isHovered, setHoveredIndex, toggleOpen, index }: CollectionRowProps) => {
|
||||
export const CollectionRow = ({
|
||||
collection,
|
||||
isHovered,
|
||||
setHoveredIndex,
|
||||
toggleOpen,
|
||||
traceEvent,
|
||||
index,
|
||||
}: CollectionRowProps) => {
|
||||
const [brokenImage, setBrokenImage] = useState(false)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const addToSearchHistory = useSearchHistory(
|
||||
@@ -33,7 +45,8 @@ export const CollectionRow = ({ collection, isHovered, setHoveredIndex, toggleOp
|
||||
const handleClick = useCallback(() => {
|
||||
addToSearchHistory(collection)
|
||||
toggleOpen()
|
||||
}, [addToSearchHistory, collection, toggleOpen])
|
||||
traceEvent()
|
||||
}, [addToSearchHistory, collection, toggleOpen, traceEvent])
|
||||
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
@@ -91,15 +104,25 @@ export const CollectionRow = ({ collection, isHovered, setHoveredIndex, toggleOp
|
||||
)
|
||||
}
|
||||
|
||||
function useBridgedAddress(token: FungibleToken): [string | undefined, number | undefined, string | undefined] {
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
const bridgedAddress = connectedChainId ? token.extensions?.bridgeInfo?.[connectedChainId]?.tokenAddress : undefined
|
||||
if (bridgedAddress && connectedChainId) {
|
||||
return [bridgedAddress, connectedChainId, getChainInfo(connectedChainId)?.circleLogoUrl]
|
||||
}
|
||||
return [undefined, undefined, undefined]
|
||||
}
|
||||
|
||||
interface TokenRowProps {
|
||||
token: FungibleToken
|
||||
isHovered: boolean
|
||||
setHoveredIndex: (index: number | undefined) => void
|
||||
toggleOpen: () => void
|
||||
traceEvent: () => void
|
||||
index: number
|
||||
}
|
||||
|
||||
export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index }: TokenRowProps) => {
|
||||
export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, traceEvent, index }: TokenRowProps) => {
|
||||
const [brokenImage, setBrokenImage] = useState(false)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const addToSearchHistory = useSearchHistory(
|
||||
@@ -110,9 +133,11 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index
|
||||
const handleClick = useCallback(() => {
|
||||
addToSearchHistory(token)
|
||||
toggleOpen()
|
||||
}, [addToSearchHistory, toggleOpen, token])
|
||||
traceEvent()
|
||||
}, [addToSearchHistory, toggleOpen, token, traceEvent])
|
||||
|
||||
const tokenDetailsPath = getTokenDetailsURL(token.address, undefined, token.chainId)
|
||||
const [bridgedAddress, bridgedChain, L2Icon] = useBridgedAddress(token)
|
||||
const tokenDetailsPath = getTokenDetailsURL(bridgedAddress ?? token.address, undefined, bridgedChain ?? token.chainId)
|
||||
// Close the modal on escape
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
@@ -139,14 +164,17 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index
|
||||
>
|
||||
<Row style={{ width: '65%' }}>
|
||||
{!brokenImage && token.logoURI ? (
|
||||
<Box
|
||||
as="img"
|
||||
src={token.logoURI.includes('ipfs://') ? uriToHttp(token.logoURI)[0] : token.logoURI}
|
||||
alt={token.name}
|
||||
className={clsx(loaded ? styles.suggestionImage : styles.imageHolder)}
|
||||
onError={() => setBrokenImage(true)}
|
||||
onLoad={() => setLoaded(true)}
|
||||
/>
|
||||
<LogoContainer>
|
||||
<Box
|
||||
as="img"
|
||||
src={token.logoURI.includes('ipfs://') ? uriToHttp(token.logoURI)[0] : token.logoURI}
|
||||
alt={token.name}
|
||||
className={clsx(loaded ? styles.suggestionImage : styles.imageHolder)}
|
||||
onError={() => setBrokenImage(true)}
|
||||
onLoad={() => setLoaded(true)}
|
||||
/>
|
||||
<L2NetworkLogo networkUrl={L2Icon} size="16px" />
|
||||
</LogoContainer>
|
||||
) : (
|
||||
<Box className={styles.imageHolder} />
|
||||
)}
|
||||
@@ -162,7 +190,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index
|
||||
<Column className={styles.suggestionSecondaryContainer}>
|
||||
{token.priceUsd && (
|
||||
<Row gap="4">
|
||||
<Box className={styles.primaryText}>{ethNumberStandardFormatter(token.priceUsd, true)}</Box>
|
||||
<Box className={styles.primaryText}>{formatDollar({ num: token.priceUsd, isPrice: true })}</Box>
|
||||
</Row>
|
||||
)}
|
||||
{token.price24hChange && (
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Web3Status from 'components/Web3Status'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { useGlobalChainName } from 'graphql/data/util'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Row } from 'nft/components/Flex'
|
||||
import { UniIcon } from 'nft/components/icons'
|
||||
@@ -37,7 +38,8 @@ const MenuItem = ({ href, id, isActive, children }: MenuItemProps) => {
|
||||
const PageTabs = () => {
|
||||
const { pathname } = useLocation()
|
||||
const nftFlag = useNftFlag()
|
||||
const chainName = useGlobalChainName()
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
const chainName = chainIdToBackendName(connectedChainId)
|
||||
|
||||
const isPoolActive =
|
||||
pathname.startsWith('/pool') ||
|
||||
@@ -68,7 +70,7 @@ const PageTabs = () => {
|
||||
|
||||
const Navbar = () => {
|
||||
const { pathname } = useLocation()
|
||||
const isNftPage = pathname.startsWith('/nfts')
|
||||
const showShoppingBag = pathname.startsWith('/nfts') || pathname.startsWith('/profile')
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -96,7 +98,7 @@ const Navbar = () => {
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<MenuDropdown />
|
||||
</Box>
|
||||
{isNftPage && <ShoppingBag />}
|
||||
{showShoppingBag && <ShoppingBag />}
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<ChainSelector />
|
||||
</Box>
|
||||
|
||||
@@ -36,7 +36,7 @@ const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: s
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textSecondary : theme.deprecated_text4)};
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.textTertiary : theme.deprecated_text4)};
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ const PopoverContainer = styled.div<{ show: boolean }>`
|
||||
|
||||
const ReferenceElement = styled.div`
|
||||
display: inline-block;
|
||||
height: inherit;
|
||||
`
|
||||
|
||||
const Arrow = styled.div`
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { animated } from 'react-spring'
|
||||
import { useSpring } from 'react-spring/web'
|
||||
import { useSpring } from 'react-spring'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
|
||||
@@ -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,13 +3,7 @@
|
||||
exports[`renders currency rows correctly when currencies list is non-empty 1`] = `
|
||||
<DocumentFragment>
|
||||
.c7 {
|
||||
color: #6E727D;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
color: #99A1BD;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
@@ -31,6 +25,12 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
grid-auto-rows: auto;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
@@ -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 {
|
||||
@@ -100,6 +100,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
@@ -138,6 +139,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
@@ -176,6 +178,7 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
@@ -209,12 +212,74 @@ exports[`renders currency rows correctly when currencies list is non-empty 1`] =
|
||||
|
||||
exports[`renders loading rows when isLoading is true 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
.c0 {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.c0 > div {
|
||||
-webkit-animation: fAQEyV 1.5s infinite;
|
||||
animation: fAQEyV 1.5s infinite;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
background: linear-gradient( to left,#F5F6FC 25%,#E8ECFB 50%,#F5F6FC 75% );
|
||||
background-size: 400%;
|
||||
border-radius: 12px;
|
||||
height: 2.4em;
|
||||
will-change: background-position;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
grid-column-gap: 0.5em;
|
||||
grid-template-columns: repeat(12,1fr);
|
||||
max-width: 960px;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
|
||||
.c1 > div:nth-child(4n + 1) {
|
||||
grid-column: 1 / 8;
|
||||
height: 1em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.c1 > div:nth-child(4n + 2) {
|
||||
grid-column: 12;
|
||||
height: 1em;
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.c1 > div:nth-child(4n + 3) {
|
||||
grid-column: 1 / 4;
|
||||
height: 0.75em;
|
||||
}
|
||||
|
||||
<div
|
||||
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||
>
|
||||
<div
|
||||
style="height: 0px; width: 100%;"
|
||||
/>
|
||||
style="height: 560px; width: 100%;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
@@ -3,19 +3,17 @@ import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ElementName, Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import { LightGreyCard } from 'components/Card'
|
||||
import QuestionHelper from 'components/QuestionHelper'
|
||||
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'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import TokenListLogo from '../../../assets/svg/tokenlist.svg'
|
||||
import { useIsUserAddedToken } from '../../../hooks/Tokens'
|
||||
import { useCurrencyBalance } from '../../../state/connection/hooks'
|
||||
import { useCombinedActiveList } from '../../../state/lists/hooks'
|
||||
@@ -25,9 +23,8 @@ import { isTokenOnList } from '../../../utils'
|
||||
import Column, { AutoColumn } from '../../Column'
|
||||
import CurrencyLogo from '../../CurrencyLogo'
|
||||
import Loader from '../../Loader'
|
||||
import Row, { RowBetween, RowFixed } from '../../Row'
|
||||
import Row, { RowFixed } from '../../Row'
|
||||
import { MouseoverTooltip } from '../../Tooltip'
|
||||
import ImportRow from '../ImportRow'
|
||||
import { LoadingRows, MenuItem } from '../styleds'
|
||||
|
||||
function currencyKey(currency: Currency): string {
|
||||
@@ -69,13 +66,12 @@ const Tag = styled.div`
|
||||
margin-right: 4px;
|
||||
`
|
||||
|
||||
const FixedContentRow = styled.div`
|
||||
padding: 4px 20px;
|
||||
height: 56px;
|
||||
display: grid;
|
||||
grid-gap: 16px;
|
||||
align-items: center;
|
||||
export const BlockedTokenIcon = styled(XOctagon)<{ size?: string }>`
|
||||
margin-left: 0.3em;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
`
|
||||
|
||||
function Balance({ balance }: { balance: CurrencyAmount<Currency> }) {
|
||||
return <StyledBalanceText title={balance.toExact()}>{balance.toSignificant(4)}</StyledBalanceText>
|
||||
}
|
||||
@@ -85,10 +81,6 @@ const TagContainer = styled.div`
|
||||
justify-content: flex-end;
|
||||
`
|
||||
|
||||
const TokenListLogoWrapper = styled.img`
|
||||
height: 20px;
|
||||
`
|
||||
|
||||
function TokenTags({ currency }: { currency: Currency }) {
|
||||
if (!(currency instanceof WrappedTokenInfo)) {
|
||||
return null
|
||||
@@ -118,7 +110,7 @@ function TokenTags({ currency }: { currency: Currency }) {
|
||||
)
|
||||
}
|
||||
|
||||
function CurrencyRow({
|
||||
export function CurrencyRow({
|
||||
currency,
|
||||
onSelect,
|
||||
isSelected,
|
||||
@@ -131,7 +123,7 @@ function CurrencyRow({
|
||||
onSelect: (hasWarning: boolean) => void
|
||||
isSelected: boolean
|
||||
otherSelected: boolean
|
||||
style: CSSProperties
|
||||
style?: CSSProperties
|
||||
showCurrencyAmount?: boolean
|
||||
eventProperties: Record<string, unknown>
|
||||
}) {
|
||||
@@ -142,9 +134,10 @@ function CurrencyRow({
|
||||
const customAdded = useIsUserAddedToken(currency)
|
||||
const balance = useCurrencyBalance(account ?? undefined, currency)
|
||||
const warning = currency.isNative ? null : checkWarning(currency.address)
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
const tokenSafetyFlag = useTokenSafetyFlag()
|
||||
const redesignFlagEnabled = useRedesignFlag() === RedesignVariant.Enabled
|
||||
const tokenSafetyFlagEnabled = useTokenSafetyFlag() === TokenSafetyVariant.Enabled
|
||||
const isBlockedToken = !!warning && !warning.canProceed
|
||||
const blockedTokenOpacity = '0.6'
|
||||
|
||||
// only show add or remove buttons if not on selected list
|
||||
return (
|
||||
@@ -163,15 +156,20 @@ function CurrencyRow({
|
||||
onClick={() => (isSelected ? null : onSelect(!!warning))}
|
||||
disabled={isSelected}
|
||||
selected={otherSelected}
|
||||
dim={isBlockedToken}
|
||||
>
|
||||
<Column>
|
||||
<CurrencyLogo currency={currency} size={'36px'} />
|
||||
<CurrencyLogo
|
||||
currency={currency}
|
||||
size={'36px'}
|
||||
style={{ opacity: isBlockedToken ? blockedTokenOpacity : '1' }}
|
||||
/>
|
||||
</Column>
|
||||
<AutoColumn>
|
||||
<AutoColumn style={{ opacity: isBlockedToken ? blockedTokenOpacity : '1' }}>
|
||||
<Row>
|
||||
<CurrencyName title={currency.name}>{currency.name}</CurrencyName>
|
||||
|
||||
{tokenSafetyFlag === TokenSafetyVariant.Enabled && <TokenSafetyIcon warning={warning} />}
|
||||
{tokenSafetyFlagEnabled && <TokenSafetyIcon warning={warning} />}
|
||||
{isBlockedToken && <BlockedTokenIcon />}
|
||||
</Row>
|
||||
<ThemedText.DeprecatedDarkGray ml="0px" fontSize={'12px'} fontWeight={300}>
|
||||
{!currency.isNative && !isOnSelectedList && customAdded ? (
|
||||
@@ -204,44 +202,13 @@ function CurrencyRow({
|
||||
)
|
||||
}
|
||||
|
||||
const BREAK_LINE = 'BREAK'
|
||||
type BreakLine = typeof BREAK_LINE
|
||||
function isBreakLine(x: unknown): x is BreakLine {
|
||||
return x === BREAK_LINE
|
||||
}
|
||||
|
||||
function BreakLineComponent({ style }: { style: CSSProperties }) {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<FixedContentRow style={style}>
|
||||
<LightGreyCard padding="8px 12px" $borderRadius="8px">
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<TokenListLogoWrapper src={TokenListLogo} />
|
||||
<ThemedText.DeprecatedMain ml="6px" fontSize="12px" color={theme.deprecated_text1}>
|
||||
<Trans>Expanded results from inactive Token Lists</Trans>
|
||||
</ThemedText.DeprecatedMain>
|
||||
</RowFixed>
|
||||
<QuestionHelper
|
||||
text={
|
||||
<Trans>
|
||||
Tokens from inactive lists. Import specific tokens below or click Manage to activate more lists.
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
</RowBetween>
|
||||
</LightGreyCard>
|
||||
</FixedContentRow>
|
||||
)
|
||||
}
|
||||
|
||||
interface TokenRowProps {
|
||||
data: Array<Currency | BreakLine>
|
||||
data: Array<Currency>
|
||||
index: number
|
||||
style: CSSProperties
|
||||
}
|
||||
|
||||
const formatAnalyticsEventProperties = (
|
||||
export const formatAnalyticsEventProperties = (
|
||||
token: Token,
|
||||
index: number,
|
||||
data: any[],
|
||||
@@ -260,6 +227,14 @@ const formatAnalyticsEventProperties = (
|
||||
: { search_token_address_input: isAddressSearch }),
|
||||
})
|
||||
|
||||
const LoadingRow = () => (
|
||||
<LoadingRows>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</LoadingRows>
|
||||
)
|
||||
|
||||
export default function CurrencyList({
|
||||
height,
|
||||
currencies,
|
||||
@@ -268,8 +243,6 @@ export default function CurrencyList({
|
||||
onCurrencySelect,
|
||||
otherCurrency,
|
||||
fixedListRef,
|
||||
showImportView,
|
||||
setImportToken,
|
||||
showCurrencyAmount,
|
||||
isLoading,
|
||||
searchQuery,
|
||||
@@ -282,27 +255,21 @@ export default function CurrencyList({
|
||||
onCurrencySelect: (currency: Currency, hasWarning?: boolean) => void
|
||||
otherCurrency?: Currency | null
|
||||
fixedListRef?: MutableRefObject<FixedSizeList | undefined>
|
||||
showImportView: () => void
|
||||
setImportToken: (token: Token) => void
|
||||
showCurrencyAmount?: boolean
|
||||
isLoading: boolean
|
||||
searchQuery: string
|
||||
isAddressSearch: string | false
|
||||
}) {
|
||||
const itemData: (Currency | BreakLine)[] = useMemo(() => {
|
||||
const itemData: Currency[] = useMemo(() => {
|
||||
if (otherListTokens && otherListTokens?.length > 0) {
|
||||
return [...currencies, BREAK_LINE, ...otherListTokens]
|
||||
return [...currencies, ...otherListTokens]
|
||||
}
|
||||
return currencies
|
||||
}, [currencies, otherListTokens])
|
||||
|
||||
const Row = useCallback(
|
||||
function TokenRow({ data, index, style }: TokenRowProps) {
|
||||
const row: Currency | BreakLine = data[index]
|
||||
|
||||
if (isBreakLine(row)) {
|
||||
return <BreakLineComponent style={style} />
|
||||
}
|
||||
const row: Currency = data[index]
|
||||
|
||||
const currency = row
|
||||
|
||||
@@ -312,20 +279,8 @@ export default function CurrencyList({
|
||||
|
||||
const token = currency?.wrapped
|
||||
|
||||
const showImport = index > currencies.length
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingRows>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</LoadingRows>
|
||||
)
|
||||
} else if (showImport && token) {
|
||||
return (
|
||||
<ImportRow style={style} token={token} showImportView={showImportView} setImportToken={setImportToken} dim />
|
||||
)
|
||||
return LoadingRow()
|
||||
} else if (currency) {
|
||||
return (
|
||||
<CurrencyRow
|
||||
@@ -342,27 +297,19 @@ export default function CurrencyList({
|
||||
return null
|
||||
}
|
||||
},
|
||||
[
|
||||
currencies.length,
|
||||
onCurrencySelect,
|
||||
otherCurrency,
|
||||
selectedCurrency,
|
||||
setImportToken,
|
||||
showImportView,
|
||||
showCurrencyAmount,
|
||||
isLoading,
|
||||
isAddressSearch,
|
||||
searchQuery,
|
||||
]
|
||||
[onCurrencySelect, otherCurrency, selectedCurrency, showCurrencyAmount, isLoading, isAddressSearch, searchQuery]
|
||||
)
|
||||
|
||||
const itemKey = useCallback((index: number, data: typeof itemData) => {
|
||||
const currency = data[index]
|
||||
if (isBreakLine(currency)) return BREAK_LINE
|
||||
return currencyKey(currency)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
return isLoading ? (
|
||||
<FixedSizeList height={height} ref={fixedListRef as any} width="100%" itemData={[]} itemCount={10} itemSize={56}>
|
||||
{LoadingRow}
|
||||
</FixedSizeList>
|
||||
) : (
|
||||
<FixedSizeList
|
||||
height={height}
|
||||
ref={fixedListRef as any}
|
||||
|
||||
@@ -26,8 +26,8 @@ import { isAddress } from '../../utils'
|
||||
import Column from '../Column'
|
||||
import Row, { RowBetween, RowFixed } from '../Row'
|
||||
import CommonBases from './CommonBases'
|
||||
import { CurrencyRow, formatAnalyticsEventProperties } from './CurrencyList'
|
||||
import CurrencyList from './CurrencyList'
|
||||
import ImportRow from './ImportRow'
|
||||
import { PaddedColumn, SearchInput, Separator } from './styleds'
|
||||
|
||||
const ContentWrapper = styled(Column)<{ redesignFlag?: boolean }>`
|
||||
@@ -57,8 +57,6 @@ interface CurrencySearchProps {
|
||||
showCurrencyAmount?: boolean
|
||||
disableNonToken?: boolean
|
||||
showManageView: () => void
|
||||
showImportView: () => void
|
||||
setImportToken: (token: Token) => void
|
||||
}
|
||||
|
||||
export function CurrencySearch({
|
||||
@@ -71,8 +69,6 @@ export function CurrencySearch({
|
||||
onDismiss,
|
||||
isOpen,
|
||||
showManageView,
|
||||
showImportView,
|
||||
setImportToken,
|
||||
}: CurrencySearchProps) {
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
@@ -111,11 +107,12 @@ export function CurrencySearch({
|
||||
return Object.values(allTokens).filter(getTokenFilter(debouncedQuery))
|
||||
}, [allTokens, debouncedQuery])
|
||||
|
||||
const [balances, balancesIsLoading] = useAllTokenBalances()
|
||||
const [balances, balancesAreLoading] = useAllTokenBalances()
|
||||
const sortedTokens: Token[] = useMemo(
|
||||
() => (!balancesIsLoading ? [...filteredTokens].sort(tokenComparator.bind(null, balances)) : []),
|
||||
[balances, filteredTokens, balancesIsLoading]
|
||||
() => (!balancesAreLoading ? [...filteredTokens].sort(tokenComparator.bind(null, balances)) : []),
|
||||
[balances, filteredTokens, balancesAreLoading]
|
||||
)
|
||||
const isLoading = Boolean(balancesAreLoading && !tokenLoaderTimerElapsed)
|
||||
|
||||
const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens)
|
||||
|
||||
@@ -230,9 +227,22 @@ export function CurrencySearch({
|
||||
<Separator redesignFlag={redesignFlagEnabled} />
|
||||
{searchToken && !searchTokenIsAdded ? (
|
||||
<Column style={{ padding: '20px 0', height: '100%' }}>
|
||||
<ImportRow token={searchToken} showImportView={showImportView} setImportToken={setImportToken} />
|
||||
<CurrencyRow
|
||||
currency={searchToken}
|
||||
isSelected={Boolean(searchToken && selectedCurrency && selectedCurrency.equals(searchToken))}
|
||||
onSelect={(hasWarning: boolean) => searchToken && handleCurrencySelect(searchToken, hasWarning)}
|
||||
otherSelected={Boolean(searchToken && otherSelectedCurrency && otherSelectedCurrency.equals(searchToken))}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
eventProperties={formatAnalyticsEventProperties(
|
||||
searchToken,
|
||||
0,
|
||||
[searchToken],
|
||||
searchQuery,
|
||||
isAddressSearch
|
||||
)}
|
||||
/>
|
||||
</Column>
|
||||
) : filteredSortedTokens?.length > 0 || filteredInactiveTokens?.length > 0 ? (
|
||||
) : filteredSortedTokens?.length > 0 || filteredInactiveTokens?.length > 0 || isLoading ? (
|
||||
<div style={{ flex: '1' }}>
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
@@ -244,10 +254,8 @@ export function CurrencySearch({
|
||||
otherCurrency={otherSelectedCurrency}
|
||||
selectedCurrency={selectedCurrency}
|
||||
fixedListRef={fixedList}
|
||||
showImportView={showImportView}
|
||||
setImportToken={setImportToken}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
isLoading={balancesIsLoading && !tokenLoaderTimerElapsed}
|
||||
isLoading={isLoading}
|
||||
searchQuery={searchQuery}
|
||||
isAddressSearch={isAddressSearch}
|
||||
/>
|
||||
|
||||
@@ -91,7 +91,6 @@ export default memo(function CurrencySearchModal({
|
||||
// used for token safety
|
||||
const [warningToken, setWarningToken] = useState<Token | undefined>()
|
||||
|
||||
const showImportView = useCallback(() => setModalView(CurrencyModalView.importToken), [setModalView])
|
||||
const showManageView = useCallback(() => setModalView(CurrencyModalView.manage), [setModalView])
|
||||
const handleBackImport = useCallback(
|
||||
() => setModalView(prevView && prevView !== CurrencyModalView.importToken ? prevView : CurrencyModalView.search),
|
||||
@@ -118,8 +117,6 @@ export default memo(function CurrencySearchModal({
|
||||
showCommonBases={showCommonBases}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
disableNonToken={disableNonToken}
|
||||
showImportView={showImportView}
|
||||
setImportToken={setImportToken}
|
||||
showManageView={showManageView}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@ export const PaddedColumn = styled(AutoColumn)`
|
||||
padding: 20px;
|
||||
`
|
||||
|
||||
export const MenuItem = styled(RowBetween)<{ redesignFlag?: boolean }>`
|
||||
export const MenuItem = styled(RowBetween)<{ redesignFlag?: boolean; dim?: boolean }>`
|
||||
padding: 4px 20px;
|
||||
height: 56px;
|
||||
display: grid;
|
||||
@@ -34,7 +34,7 @@ export const MenuItem = styled(RowBetween)<{ redesignFlag?: boolean }>`
|
||||
background-color: ${({ theme, disabled, redesignFlag }) =>
|
||||
(redesignFlag && theme.hoverDefault) || (!disabled && theme.deprecated_bg2)};
|
||||
}
|
||||
opacity: ${({ disabled, selected }) => (disabled || selected ? 0.5 : 1)};
|
||||
opacity: ${({ disabled, selected, dim }) => (dim || disabled || selected ? 0.4 : 1)};
|
||||
`
|
||||
|
||||
export const SearchInput = styled.input<{ redesignFlag?: boolean }>`
|
||||
|
||||
@@ -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,18 +1,6 @@
|
||||
import { ReactComponent as Verified } from 'assets/svg/verified.svg'
|
||||
import { Warning, WARNING_LEVEL } from 'constants/tokenSafety'
|
||||
import { useTokenWarningColor } from 'hooks/useTokenWarningColor'
|
||||
import { AlertOctagon, AlertTriangle } from 'react-feather'
|
||||
import { Warning } from 'constants/tokenSafety'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Color } from 'theme/styled'
|
||||
|
||||
const Container = styled.div<{ color: Color }>`
|
||||
width: 0.9rem;
|
||||
height: 0.9rem;
|
||||
margin-left: 4px;
|
||||
color: ${({ color }) => color};
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const VerifiedContainer = styled.div`
|
||||
margin-left: 4px;
|
||||
@@ -23,16 +11,14 @@ const VerifiedContainer = styled.div`
|
||||
export const VerifiedIcon = styled(Verified)<{ size?: string }>`
|
||||
width: ${({ size }) => size ?? '1em'};
|
||||
height: ${({ size }) => size ?? '1em'};
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
`
|
||||
|
||||
export default function TokenSafetyIcon({ warning }: { warning: Warning | null }) {
|
||||
const color = useTokenWarningColor(warning ? warning.level : WARNING_LEVEL.UNKNOWN)
|
||||
if (!warning) {
|
||||
return (
|
||||
<VerifiedContainer>
|
||||
<VerifiedIcon />
|
||||
</VerifiedContainer>
|
||||
)
|
||||
}
|
||||
return <Container color={color}>{warning.canProceed ? <AlertTriangle /> : <AlertOctagon />}</Container>
|
||||
if (warning) return null
|
||||
return (
|
||||
<VerifiedContainer>
|
||||
<VerifiedIcon />
|
||||
</VerifiedContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { Trans } from '@lingui/macro'
|
||||
import { darken } from 'polished'
|
||||
import { useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { textFadeIn } from 'theme/animations'
|
||||
|
||||
import Resource from './Resource'
|
||||
|
||||
@@ -23,8 +25,8 @@ const TokenDescriptionContainer = styled.div`
|
||||
const TruncateDescriptionButton = styled.div`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
padding-top: 14px;
|
||||
font-size: 0.85em;
|
||||
padding-top: 0.5em;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
@@ -49,10 +51,10 @@ const TRUNCATE_CHARACTER_COUNT = 400
|
||||
export const AboutContainer = styled.div`
|
||||
gap: 16px;
|
||||
padding: 24px 0px;
|
||||
${textFadeIn}
|
||||
`
|
||||
export const AboutHeader = styled.span`
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
export const AboutHeader = styled(ThemedText.MediumHeader)`
|
||||
font-size: 28px !important;
|
||||
`
|
||||
|
||||
export const ResourcesContainer = styled.div`
|
||||
@@ -88,13 +90,17 @@ export function AboutSection({ address, description, homepageUrl, twitterName }:
|
||||
{tokenDescription}
|
||||
{shouldTruncate && (
|
||||
<TruncateDescriptionButton onClick={() => setIsDescriptionTruncated(!isDescriptionTruncated)}>
|
||||
{isDescriptionTruncated ? <Trans>Read more</Trans> : <Trans>Hide</Trans>}
|
||||
{isDescriptionTruncated ? <Trans>Show more</Trans> : <Trans>Hide</Trans>}
|
||||
</TruncateDescriptionButton>
|
||||
)}
|
||||
</TokenDescriptionContainer>
|
||||
<br />
|
||||
<ThemedText.SubHeaderSmall>
|
||||
<Trans>Links</Trans>
|
||||
</ThemedText.SubHeaderSmall>
|
||||
<ResourcesContainer>
|
||||
<Resource name={'Etherscan'} link={`https://etherscan.io/address/${address}`} />
|
||||
<Resource name={'Protocol info'} link={`https://info.uniswap.org/#/tokens/${address}`} />
|
||||
<Resource name={'More analytics'} link={`https://info.uniswap.org/#/tokens/${address}`} />
|
||||
{homepageUrl && <Resource name={'Website'} link={homepageUrl} />}
|
||||
{twitterName && <Resource name={'Twitter'} link={`https://twitter.com/${twitterName}`} />}
|
||||
</ResourcesContainer>
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import styled from 'styled-components/macro'
|
||||
import { CopyContractAddress } from 'theme'
|
||||
import { CopyContractAddress, ThemedText } from 'theme'
|
||||
|
||||
export const ContractAddressSection = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
font-size: 0.9em;
|
||||
gap: 4px;
|
||||
padding: 36px 0px;
|
||||
padding: 4px 0px;
|
||||
`
|
||||
|
||||
const ContractAddress = styled.button`
|
||||
@@ -21,13 +20,14 @@ const ContractAddress = styled.button`
|
||||
border: none;
|
||||
min-height: 38px;
|
||||
padding: 0px;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
export default function AddressSection({ address }: { address: string }) {
|
||||
return (
|
||||
<ContractAddressSection>
|
||||
<Trans>Contract address</Trans>
|
||||
<ThemedText.SubHeaderSmall>
|
||||
<Trans>Contract address</Trans>
|
||||
</ThemedText.SubHeaderSmall>
|
||||
<ContractAddress>
|
||||
<CopyContractAddress address={address} />
|
||||
</ContractAddress>
|
||||
|
||||
@@ -1,83 +1,140 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { useNetworkTokenBalances } from 'hooks/useNetworkTokenBalances'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { formatToDecimal } from 'analytics/utils'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import { validateUrlChainParam } from 'graphql/data/util'
|
||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { StyledInternalLink } from 'theme'
|
||||
import { currencyAmountToPreciseFloat, formatDollar } from 'utils/formatNumbers'
|
||||
|
||||
const BalancesCard = styled.div`
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
padding: 20px;
|
||||
box-shadow: ${({ theme }) => theme.shallowShadow};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
|
||||
border-radius: 16px;
|
||||
`
|
||||
const ErrorState = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
`
|
||||
const ErrorText = styled.span`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
height: fit-content;
|
||||
line-height: 16px;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
|
||||
// 768 hardcoded to match NFT-redesign navbar breakpoints
|
||||
// src/nft/css/sprinkles.css.ts
|
||||
// change to match theme breakpoints when this navbar is updated
|
||||
@media screen and (min-width: 768px) {
|
||||
display: flex;
|
||||
}
|
||||
`
|
||||
const TotalBalanceSection = styled.div`
|
||||
height: fit-content;
|
||||
width: 100%;
|
||||
`
|
||||
const TotalBalance = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
font-size: 20px;
|
||||
justify-content: space-between;
|
||||
line-height: 28px;
|
||||
margin-top: 12px;
|
||||
align-items: center;
|
||||
`
|
||||
const TotalBalanceItem = styled.div`
|
||||
display: flex;
|
||||
`
|
||||
|
||||
export default function BalanceSummary({
|
||||
address,
|
||||
balance,
|
||||
balanceUsd,
|
||||
}: {
|
||||
address: string
|
||||
balance?: number
|
||||
balanceUsd?: number
|
||||
}) {
|
||||
const token = useToken(address)
|
||||
const { loading, error } = useNetworkTokenBalances({ address })
|
||||
const BalanceRowLink = styled(StyledInternalLink)`
|
||||
color: unset;
|
||||
`
|
||||
|
||||
function BalanceRow({ currency, formattedBalance, usdValue, href }: BalanceRowData) {
|
||||
const content = (
|
||||
<TotalBalance key={currency.wrapped.address}>
|
||||
<TotalBalanceItem>
|
||||
<CurrencyLogo currency={currency} />
|
||||
{formattedBalance} {currency?.symbol}
|
||||
</TotalBalanceItem>
|
||||
<TotalBalanceItem>{formatDollar({ num: usdValue === 0 ? undefined : usdValue, isPrice: true })}</TotalBalanceItem>
|
||||
</TotalBalance>
|
||||
)
|
||||
if (href) {
|
||||
return <BalanceRowLink to={href}>{content}</BalanceRowLink>
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
interface BalanceRowData {
|
||||
currency: Currency
|
||||
formattedBalance: number
|
||||
usdValue: number | undefined
|
||||
href?: string
|
||||
}
|
||||
export interface BalanceSummaryProps {
|
||||
tokenAmount: CurrencyAmount<Token> | undefined
|
||||
nativeCurrencyAmount: CurrencyAmount<Currency> | undefined
|
||||
isNative: boolean
|
||||
}
|
||||
|
||||
export default function BalanceSummary({ tokenAmount, nativeCurrencyAmount, isNative }: BalanceSummaryProps) {
|
||||
const balanceUsdValue = useStablecoinValue(tokenAmount)
|
||||
const nativeBalanceUsdValue = useStablecoinValue(nativeCurrencyAmount)
|
||||
|
||||
const { chainName } = useParams<{ chainName?: string }>()
|
||||
const pageChainName = validateUrlChainParam(chainName).toLowerCase()
|
||||
|
||||
const tokenIsWrappedNative =
|
||||
tokenAmount &&
|
||||
nativeCurrencyAmount &&
|
||||
tokenAmount.currency.address.toLowerCase() === nativeCurrencyAmount.currency.wrapped.address.toLowerCase()
|
||||
|
||||
if (
|
||||
(!tokenAmount && !nativeCurrencyAmount) ||
|
||||
(!tokenAmount && !tokenIsWrappedNative && !isNative) ||
|
||||
(!isNative && !tokenIsWrappedNative && tokenAmount?.equalTo(0)) ||
|
||||
(isNative && tokenAmount?.equalTo(0) && nativeCurrencyAmount?.equalTo(0))
|
||||
) {
|
||||
return null
|
||||
}
|
||||
const showNative = tokenIsWrappedNative || isNative
|
||||
|
||||
const currencies = []
|
||||
|
||||
if (tokenAmount) {
|
||||
const tokenData: BalanceRowData = {
|
||||
currency: tokenAmount.currency,
|
||||
formattedBalance: formatToDecimal(tokenAmount, Math.min(tokenAmount.currency.decimals, 2)),
|
||||
usdValue: balanceUsdValue ? currencyAmountToPreciseFloat(balanceUsdValue) : undefined,
|
||||
}
|
||||
if (isNative) {
|
||||
tokenData.href = `/tokens/${pageChainName}/${tokenAmount.currency.address}`
|
||||
}
|
||||
currencies.push(tokenData)
|
||||
}
|
||||
if (showNative && nativeCurrencyAmount) {
|
||||
const nativeData: BalanceRowData = {
|
||||
currency: nativeCurrencyAmount.currency,
|
||||
formattedBalance: formatToDecimal(nativeCurrencyAmount, Math.min(nativeCurrencyAmount.currency.decimals, 2)),
|
||||
usdValue: nativeBalanceUsdValue ? currencyAmountToPreciseFloat(nativeBalanceUsdValue) : undefined,
|
||||
}
|
||||
if (isNative) {
|
||||
currencies.unshift(nativeData)
|
||||
} else {
|
||||
nativeData.href = `/tokens/${pageChainName}/NATIVE`
|
||||
currencies.push(nativeData)
|
||||
}
|
||||
}
|
||||
|
||||
if (loading || (!error && !balance && !balanceUsd)) return null
|
||||
return (
|
||||
<BalancesCard>
|
||||
{error ? (
|
||||
<ErrorState>
|
||||
<AlertTriangle size={24} />
|
||||
<ErrorText>
|
||||
<Trans>There was an error loading your {token?.symbol} balance</Trans>
|
||||
</ErrorText>
|
||||
</ErrorState>
|
||||
) : (
|
||||
<>
|
||||
<TotalBalanceSection>
|
||||
Your balance
|
||||
<TotalBalance>
|
||||
<TotalBalanceItem>{`${balance} ${token?.symbol}`}</TotalBalanceItem>
|
||||
<TotalBalanceItem>{`$${balanceUsd}`}</TotalBalanceItem>
|
||||
</TotalBalance>
|
||||
</TotalBalanceSection>
|
||||
</>
|
||||
)}
|
||||
<TotalBalanceSection>
|
||||
<Trans>Your balance</Trans>
|
||||
{currencies.map((props, i) => (
|
||||
<BalanceRow {...props} key={props.currency.wrapped.address + i} />
|
||||
))}
|
||||
</TotalBalanceSection>
|
||||
</BalancesCard>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,14 +6,16 @@ import { VerifiedIcon } from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { checkWarning } from 'constants/tokenSafety'
|
||||
import { FavoriteTokensVariant, useFavoriteTokensFlag } from 'featureFlags/flags/favoriteTokens'
|
||||
import { SingleTokenData, useTokenPricesCached } from 'graphql/data/Token'
|
||||
import { PriceDurations, PricePoint, SingleTokenData } from 'graphql/data/Token'
|
||||
import { TopToken } from 'graphql/data/TopTokens'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID } from 'graphql/data/util'
|
||||
import useCurrencyLogoURIs, { getTokenLogoURI } from 'lib/hooks/useCurrencyLogoURIs'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID, TimePeriod } from 'graphql/data/util'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
|
||||
import { useMemo } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { isAddress } from 'utils'
|
||||
import { textFadeIn } from 'theme/animations'
|
||||
|
||||
import { useIsFavorited, useToggleFavorite } from '../state'
|
||||
import { filterTimeAtom, useIsFavorited, useToggleFavorite } from '../state'
|
||||
import { ClickFavorited, FavoriteIcon, L2NetworkLogo, LogoContainer } from '../TokenTable/TokenRow'
|
||||
import PriceChart from './PriceChart'
|
||||
import ShareButton from './ShareButton'
|
||||
@@ -42,6 +44,7 @@ export const TokenNameCell = styled.div`
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
align-items: center;
|
||||
${textFadeIn}
|
||||
`
|
||||
const TokenSymbol = styled.span`
|
||||
text-transform: uppercase;
|
||||
@@ -57,32 +60,52 @@ export function useTokenLogoURI(
|
||||
token: NonNullable<SingleTokenData> | NonNullable<TopToken>,
|
||||
nativeCurrency?: Token | NativeCurrency
|
||||
) {
|
||||
const checksummedAddress = isAddress(token.address)
|
||||
const chainId = CHAIN_NAME_TO_CHAIN_ID[token.chain]
|
||||
return (
|
||||
useCurrencyLogoURIs(nativeCurrency)[0] ??
|
||||
(checksummedAddress && getTokenLogoURI(checksummedAddress, chainId)) ??
|
||||
token.project?.logoUrl
|
||||
)
|
||||
return [
|
||||
...useCurrencyLogoURIs(nativeCurrency),
|
||||
...useCurrencyLogoURIs({ ...token, chainId }),
|
||||
token.project?.logoUrl,
|
||||
][0]
|
||||
}
|
||||
|
||||
export default function ChartSection({
|
||||
token,
|
||||
nativeCurrency,
|
||||
prices,
|
||||
}: {
|
||||
token: NonNullable<SingleTokenData>
|
||||
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 warning = checkWarning(token.address ?? '')
|
||||
|
||||
const { prices } = useTokenPricesCached(token)
|
||||
const timePeriod = useAtomValue(filterTimeAtom)
|
||||
|
||||
const logoSrc = useTokenLogoURI(token, nativeCurrency)
|
||||
|
||||
// Backend doesn't always return latest price point for every duration.
|
||||
// Thus we need to manually determine latest price point available, and
|
||||
// append it to the prices list for every duration.
|
||||
useMemo(() => {
|
||||
let latestPricePoint: PricePoint = { value: 0, timestamp: 0 }
|
||||
let latestPricePointTimePeriod: TimePeriod
|
||||
Object.keys(prices).forEach((key) => {
|
||||
const latestPricePointForTimePeriod = prices[key as unknown as TimePeriod]?.slice(-1)[0]
|
||||
if (latestPricePointForTimePeriod && latestPricePointForTimePeriod.timestamp > latestPricePoint.timestamp) {
|
||||
latestPricePoint = latestPricePointForTimePeriod
|
||||
latestPricePointTimePeriod = key as unknown as TimePeriod
|
||||
}
|
||||
})
|
||||
Object.keys(prices).forEach((key) => {
|
||||
if ((key as unknown as TimePeriod) !== latestPricePointTimePeriod) {
|
||||
prices[key as unknown as TimePeriod]?.push(latestPricePoint)
|
||||
}
|
||||
})
|
||||
}, [prices])
|
||||
|
||||
return (
|
||||
<ChartHeader>
|
||||
<TokenInfoContainer>
|
||||
@@ -93,12 +116,10 @@ export default function ChartSection({
|
||||
</LogoContainer>
|
||||
{nativeCurrency?.name ?? token.name ?? <Trans>Name not found</Trans>}
|
||||
<TokenSymbol>{nativeCurrency?.symbol ?? token.symbol ?? <Trans>Symbol not found</Trans>}</TokenSymbol>
|
||||
{!warning && <VerifiedIcon size="20px" />}
|
||||
{!warning && <VerifiedIcon size="16px" />}
|
||||
</TokenNameCell>
|
||||
<TokenActions>
|
||||
{token.name && token.symbol && token.address && (
|
||||
<ShareButton tokenName={token.name} tokenSymbol={token.symbol} tokenAddress={token.address} />
|
||||
)}
|
||||
{token.name && token.symbol && token.address && <ShareButton token={token} isNative={!!nativeCurrency} />}
|
||||
{useFavoriteTokensFlag() === FavoriteTokensVariant.Enabled && (
|
||||
<ClickFavorited onClick={toggleFavorite}>
|
||||
<FavoriteIcon isFavorited={isFavorited} />
|
||||
@@ -108,7 +129,7 @@ export default function ChartSection({
|
||||
</TokenInfoContainer>
|
||||
<ChartContainer>
|
||||
<ParentSize>
|
||||
{({ width, height }) => prices && <PriceChart prices={prices} width={width} height={height} />}
|
||||
{({ width, height }) => prices && <PriceChart prices={prices[timePeriod]} width={width} height={height} />}
|
||||
</ParentSize>
|
||||
</ChartContainer>
|
||||
</ChartHeader>
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { useNetworkTokenBalances } from 'hooks/useNetworkTokenBalances'
|
||||
import { useState } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { Link } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { SMALLEST_MOBILE_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { LoadingBubble } from '../loading'
|
||||
|
||||
const PLACEHOLDER_NAV_FOOTER_HEIGHT = '56px'
|
||||
const BalanceFooter = styled.div`
|
||||
height: fit-content;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 20px 20px 0px 0px;
|
||||
padding: 12px 16px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
width: 100%;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: ${PLACEHOLDER_NAV_FOOTER_HEIGHT};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: center;
|
||||
`
|
||||
const BalanceValue = styled.div`
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
`
|
||||
const BalanceTotal = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
const BalanceInfo = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-direction: column;
|
||||
`
|
||||
const FakeFooterNavBar = styled.div`
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
background-color: ${({ theme }) => theme.backgroundBackdrop};
|
||||
height: ${PLACEHOLDER_NAV_FOOTER_HEIGHT};
|
||||
width: 100%;
|
||||
align-items: flex-end;
|
||||
padding: 20px 8px;
|
||||
font-size: 10px;
|
||||
`
|
||||
const FiatValue = styled.span`
|
||||
display: flex;
|
||||
align-self: flex-end;
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
|
||||
@media only screen and (max-width: ${SMALLEST_MOBILE_MEDIA_BREAKPOINT}) {
|
||||
line-height: 16px;
|
||||
}
|
||||
`
|
||||
const NetworkBalancesSection = styled.div`
|
||||
height: fit-content;
|
||||
border-top: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 16px 0px 8px 0px;
|
||||
margin-top: 16px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
const NetworkBalancesLabel = styled.span`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
const SwapButton = styled.button`
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.accentTextLightPrimary};
|
||||
padding: 12px 16px;
|
||||
width: 120px;
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
justify-content: center;
|
||||
`
|
||||
const TotalBalancesSection = styled.div`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`
|
||||
const ViewAll = styled.span`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
`
|
||||
const ErrorState = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding-right: 8px;
|
||||
`
|
||||
const LoadingState = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
`
|
||||
const TopBalanceLoadBubble = styled(LoadingBubble)`
|
||||
height: 12px;
|
||||
width: 172px;
|
||||
`
|
||||
const BottomBalanceLoadBubble = styled(LoadingBubble)`
|
||||
height: 16px;
|
||||
width: 188px;
|
||||
`
|
||||
const ErrorText = styled.span`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
|
||||
export default function FooterBalanceSummary({
|
||||
address,
|
||||
networkBalances,
|
||||
balance,
|
||||
balanceUsd,
|
||||
}: {
|
||||
address: string
|
||||
networkBalances: (JSX.Element | null)[] | null
|
||||
balance?: number
|
||||
balanceUsd?: number
|
||||
}) {
|
||||
const tokenSymbol = useToken(address)?.symbol
|
||||
const [showMultipleBalances, setShowMultipleBalances] = useState(false)
|
||||
const multipleBalances = false // for testing purposes
|
||||
const networkNameIfOneBalance = 'Ethereum' // for testing purposes
|
||||
const { loading, error } = useNetworkTokenBalances({ address })
|
||||
return (
|
||||
<BalanceFooter>
|
||||
<TotalBalancesSection>
|
||||
{loading ? (
|
||||
<LoadingState>
|
||||
<TopBalanceLoadBubble></TopBalanceLoadBubble>
|
||||
<BottomBalanceLoadBubble></BottomBalanceLoadBubble>
|
||||
</LoadingState>
|
||||
) : error ? (
|
||||
<ErrorState>
|
||||
<AlertTriangle size={17} />
|
||||
<ErrorText>
|
||||
<Trans>There was an error fetching your balance</Trans>
|
||||
</ErrorText>
|
||||
</ErrorState>
|
||||
) : (
|
||||
!!balance &&
|
||||
!!balanceUsd && (
|
||||
<BalanceInfo>
|
||||
{multipleBalances ? 'Balance on all networks' : `Your balance on ${networkNameIfOneBalance}`}
|
||||
<BalanceTotal>
|
||||
<BalanceValue>
|
||||
{balance} {tokenSymbol}
|
||||
</BalanceValue>
|
||||
<FiatValue>{`$${balanceUsd}`}</FiatValue>
|
||||
</BalanceTotal>
|
||||
{multipleBalances && (
|
||||
<ViewAll onClick={() => setShowMultipleBalances(!showMultipleBalances)}>
|
||||
<Trans>{showMultipleBalances ? 'Hide' : 'View'} all balances</Trans>
|
||||
</ViewAll>
|
||||
)}
|
||||
</BalanceInfo>
|
||||
)
|
||||
)}
|
||||
<Link to={`/swap?outputCurrency=${address}`}>
|
||||
<SwapButton>
|
||||
<Trans>Swap</Trans>
|
||||
</SwapButton>
|
||||
</Link>
|
||||
</TotalBalancesSection>
|
||||
{showMultipleBalances && (
|
||||
<NetworkBalancesSection>
|
||||
<NetworkBalancesLabel>
|
||||
<Trans>Your balances by network</Trans>
|
||||
</NetworkBalancesLabel>
|
||||
{networkBalances}
|
||||
</NetworkBalancesSection>
|
||||
)}
|
||||
<FakeFooterNavBar>**leaving space for updated nav footer**</FakeFooterNavBar>
|
||||
</BalanceFooter>
|
||||
)
|
||||
}
|
||||
40
src/components/Tokens/TokenDetails/InfoTip.tsx
Normal file
40
src/components/Tokens/TokenDetails/InfoTip.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Info } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const InfoTipContainer = styled.div`
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
cursor: help;
|
||||
`
|
||||
|
||||
const InfoTipBody = styled.div`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
`
|
||||
|
||||
const InfoTipWrapper = styled.div`
|
||||
margin-left: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
export default function InfoTip({ text }: { text: ReactNode; size?: number }) {
|
||||
const [show, setShow] = useState<boolean>(false)
|
||||
|
||||
const open = useCallback(() => setShow(true), [setShow])
|
||||
const close = useCallback(() => setShow(false), [setShow])
|
||||
return (
|
||||
<InfoTipWrapper>
|
||||
<Tooltip text={<InfoTipBody>{text}</InfoTipBody>} show={show} placement="right">
|
||||
<InfoTipContainer onClick={open} onMouseEnter={open} onMouseLeave={close}>
|
||||
<Info size={14} />
|
||||
</InfoTipContainer>
|
||||
</Tooltip>
|
||||
</InfoTipWrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Footer, LeftPanel, RightPanel, TokenDetailsLayout } from 'pages/TokenDetails'
|
||||
import { WidgetSkeleton } from 'components/Widget'
|
||||
import { LeftPanel, RightPanel, TokenDetailsLayout } from 'pages/TokenDetails'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { LoadingBubble } from '../loading'
|
||||
@@ -154,8 +155,9 @@ export function LoadingTokenDetails() {
|
||||
return (
|
||||
<TokenDetailsLayout>
|
||||
<LoadingTokenDetail />
|
||||
<RightPanel />
|
||||
<Footer />
|
||||
<RightPanel>
|
||||
<WidgetSkeleton />
|
||||
</RightPanel>
|
||||
</TokenDetailsLayout>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { formatToDecimal } from 'analytics/utils'
|
||||
import { useStablecoinValue } from 'hooks/useStablecoinPrice'
|
||||
import styled from 'styled-components/macro'
|
||||
import { StyledInternalLink } from 'theme'
|
||||
import { currencyAmountToPreciseFloat, formatDollar } from 'utils/formatNumbers'
|
||||
|
||||
import { BalanceSummaryProps } from './BalanceSummary'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 20px 20px 0px 0px;
|
||||
bottom: 56px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
height: fit-content;
|
||||
justify-content: space-between;
|
||||
left: 0;
|
||||
line-height: 20px;
|
||||
padding: 12px 16px;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
|
||||
@media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) {
|
||||
bottom: 0px;
|
||||
}
|
||||
@media screen and (min-width: ${({ theme }) => theme.breakpoint.lg}px) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
const BalanceValue = styled.div`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
`
|
||||
const BalanceTotal = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
`
|
||||
const BalanceInfo = styled.div`
|
||||
display: flex;
|
||||
flex: 10 1 auto;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
`
|
||||
const FiatValue = styled.span`
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
|
||||
@media screen and (min-width: ${({ theme }) => theme.breakpoint.sm}px) {
|
||||
line-height: 24px;
|
||||
}
|
||||
`
|
||||
const SwapButton = styled(StyledInternalLink)`
|
||||
background-color: ${({ theme }) => theme.accentAction};
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.accentTextLightPrimary};
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
padding: 12px 16px;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
height: 44px;
|
||||
justify-content: center;
|
||||
margin: auto;
|
||||
max-width: 100vw;
|
||||
`
|
||||
|
||||
export default function MobileBalanceSummaryFooter({
|
||||
tokenAmount,
|
||||
nativeCurrencyAmount,
|
||||
isNative,
|
||||
}: BalanceSummaryProps) {
|
||||
const balanceUsdValue = useStablecoinValue(tokenAmount)
|
||||
const nativeBalanceUsdValue = useStablecoinValue(nativeCurrencyAmount)
|
||||
|
||||
const formattedBalance = tokenAmount
|
||||
? formatToDecimal(tokenAmount, Math.min(tokenAmount.currency.decimals, 2))
|
||||
: undefined
|
||||
|
||||
const balanceUsd = balanceUsdValue ? currencyAmountToPreciseFloat(balanceUsdValue) : undefined
|
||||
|
||||
const formattedNativeBalance = nativeCurrencyAmount
|
||||
? formatToDecimal(nativeCurrencyAmount, Math.min(nativeCurrencyAmount.currency.decimals, 2))
|
||||
: 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)) && (
|
||||
<BalanceInfo>
|
||||
<Trans>Your {tokenAmount?.currency?.symbol} balance</Trans>
|
||||
<BalanceTotal>
|
||||
<BalanceValue>
|
||||
{formattedBalance} {tokenAmount?.currency?.symbol}
|
||||
</BalanceValue>
|
||||
<FiatValue>{formatDollar({ num: balanceUsd, isPrice: true })}</FiatValue>
|
||||
</BalanceTotal>
|
||||
</BalanceInfo>
|
||||
)}
|
||||
{Boolean(isNative && nativeCurrencyAmount?.greaterThan(0)) && (
|
||||
<BalanceInfo>
|
||||
<Trans>Your {nativeCurrencyAmount?.currency?.symbol} balance</Trans>
|
||||
<BalanceTotal>
|
||||
<BalanceValue>
|
||||
{formattedNativeBalance} {nativeCurrencyAmount?.currency?.symbol}
|
||||
</BalanceValue>
|
||||
<FiatValue>{formatDollar({ num: nativeBalanceUsd, isPrice: true })}</FiatValue>
|
||||
</BalanceTotal>
|
||||
</BalanceInfo>
|
||||
)}
|
||||
<SwapButton to={`/swap?outputCurrency=${outputTokenAddress}`}>
|
||||
<Trans>Swap</Trans>
|
||||
</SwapButton>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
const Balance = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
`
|
||||
const BalanceItem = styled.div`
|
||||
display: flex;
|
||||
`
|
||||
const BalanceRow = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
`
|
||||
const Logo = styled.img`
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
margin-right: 8px;
|
||||
`
|
||||
const Network = styled.span<{ color: string }>`
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 500;
|
||||
color: ${({ color }) => color};
|
||||
`
|
||||
const NetworkBalanceContainer = styled.div`
|
||||
display: flex;
|
||||
padding-top: 16px;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
export default function NetworkBalance({
|
||||
logoUrl,
|
||||
balance,
|
||||
tokenSymbol,
|
||||
fiatValue,
|
||||
label,
|
||||
networkColor,
|
||||
}: {
|
||||
logoUrl: string
|
||||
balance: string
|
||||
tokenSymbol: string
|
||||
fiatValue: string | number
|
||||
label: string
|
||||
networkColor: string | undefined
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<NetworkBalanceContainer>
|
||||
<Logo src={logoUrl} />
|
||||
<Balance>
|
||||
<BalanceRow>
|
||||
<BalanceItem>
|
||||
{balance} {tokenSymbol}
|
||||
</BalanceItem>
|
||||
<BalanceItem>${fiatValue}</BalanceItem>
|
||||
</BalanceRow>
|
||||
<Network color={networkColor ?? theme.textPrimary}>{label}</Network>
|
||||
</Balance>
|
||||
</NetworkBalanceContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,26 +1,18 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { AxisBottom, TickFormatter } from '@visx/axis'
|
||||
import { localPoint } from '@visx/event'
|
||||
import { EventType } from '@visx/event/lib/types'
|
||||
import { GlyphCircle } from '@visx/glyph'
|
||||
import { Line } from '@visx/shape'
|
||||
import AnimatedInLineChart from 'components/Charts/AnimatedInLineChart'
|
||||
import { filterTimeAtom } from 'components/Tokens/state'
|
||||
import {
|
||||
bisect,
|
||||
curveCardinal,
|
||||
NumberValue,
|
||||
scaleLinear,
|
||||
timeDay,
|
||||
timeHour,
|
||||
timeMinute,
|
||||
timeMonth,
|
||||
timeTicks,
|
||||
} from 'd3'
|
||||
import { bisect, curveCardinal, NumberValue, scaleLinear, timeDay, timeHour, timeMinute, timeMonth } from 'd3'
|
||||
import { PricePoint } from 'graphql/data/Token'
|
||||
import { TimePeriod } from 'graphql/data/util'
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
import { useAtom } from 'jotai'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { ArrowDownRight, ArrowUpRight } from 'react-feather'
|
||||
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { ArrowDownRight, ArrowUpRight, TrendingUp } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import {
|
||||
dayHourFormatter,
|
||||
@@ -28,11 +20,11 @@ import {
|
||||
monthDayFormatter,
|
||||
monthTickFormatter,
|
||||
monthYearDayFormatter,
|
||||
monthYearFormatter,
|
||||
weekFormatter,
|
||||
} from 'utils/formatChartTimes'
|
||||
import { formatDollar } from 'utils/formatNumbers'
|
||||
|
||||
import LineChart from '../../Charts/LineChart'
|
||||
import { MEDIUM_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { DISPLAYS, ORDERED_TIMES } from '../TokenTable/TimeSelector'
|
||||
|
||||
export const DATA_EMPTY = { value: 0, timestamp: 0 }
|
||||
@@ -55,17 +47,21 @@ export function calculateDelta(start: number, current: number) {
|
||||
return (current / start - 1) * 100
|
||||
}
|
||||
|
||||
export function getDeltaArrow(delta: number) {
|
||||
if (Math.sign(delta) > 0) {
|
||||
return <StyledUpArrow size={16} key="arrow-up" />
|
||||
} else if (delta === 0) {
|
||||
export function getDeltaArrow(delta: number | null | undefined) {
|
||||
// Null-check not including zero
|
||||
if (delta === null || delta === undefined) {
|
||||
return null
|
||||
} else {
|
||||
} else if (Math.sign(delta) < 0) {
|
||||
return <StyledDownArrow size={16} key="arrow-down" />
|
||||
}
|
||||
return <StyledUpArrow size={16} key="arrow-up" />
|
||||
}
|
||||
|
||||
export function formatDelta(delta: number) {
|
||||
export function formatDelta(delta: number | null | undefined) {
|
||||
// Null-check not including zero
|
||||
if (delta === null || delta === undefined) {
|
||||
return '-'
|
||||
}
|
||||
let formattedDelta = delta.toFixed(2) + '%'
|
||||
if (Math.sign(delta) > 0) {
|
||||
formattedDelta = '+' + formattedDelta
|
||||
@@ -104,8 +100,18 @@ export const TimeOptionsContainer = styled.div`
|
||||
height: 40px;
|
||||
padding: 4px;
|
||||
width: fit-content;
|
||||
|
||||
@media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
border: none;
|
||||
}
|
||||
`
|
||||
const TimeButton = styled.button<{ active: boolean }>`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: ${({ theme, active }) => (active ? theme.backgroundInteractive : 'transparent')};
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
@@ -168,8 +174,6 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
|
||||
)
|
||||
|
||||
function tickFormat(
|
||||
startTimestamp: number,
|
||||
endTimestamp: number,
|
||||
timePeriod: TimePeriod,
|
||||
locale: string
|
||||
): [TickFormatter<NumberValue>, (v: number) => string, NumberValue[]] {
|
||||
@@ -209,12 +213,6 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
|
||||
monthYearDayFormatter(locale),
|
||||
timeMonth.range(startDateWithOffset, endDateWithOffset, 2).map((x) => x.valueOf() / 1000),
|
||||
]
|
||||
case TimePeriod.ALL:
|
||||
return [
|
||||
monthYearFormatter(locale),
|
||||
monthYearDayFormatter(locale),
|
||||
timeTicks(startDateWithOffset, endDateWithOffset, 6).map((x) => x.valueOf() / 1000),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,8 +237,10 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
|
||||
pricePoint = x0.valueOf() - d0.timestamp.valueOf() > d1.timestamp.valueOf() - x0.valueOf() ? d1 : d0
|
||||
}
|
||||
|
||||
setCrosshair(timeScale(pricePoint.timestamp))
|
||||
setDisplayPrice(pricePoint)
|
||||
if (pricePoint) {
|
||||
setCrosshair(timeScale(pricePoint.timestamp))
|
||||
setDisplayPrice(pricePoint)
|
||||
}
|
||||
},
|
||||
[timeScale, prices]
|
||||
)
|
||||
@@ -250,105 +250,110 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
|
||||
setDisplayPrice(endingPrice)
|
||||
}, [setCrosshair, setDisplayPrice, endingPrice])
|
||||
|
||||
// TODO: Display no data available error
|
||||
if (!prices) {
|
||||
return null
|
||||
}
|
||||
|
||||
const [tickFormatter, crosshairDateFormatter, ticks] = tickFormat(
|
||||
startingPrice.timestamp,
|
||||
endingPrice.timestamp,
|
||||
timePeriod,
|
||||
locale
|
||||
)
|
||||
const [tickFormatter, crosshairDateFormatter, ticks] = tickFormat(timePeriod, locale)
|
||||
const delta = calculateDelta(startingPrice.value, displayPrice.value)
|
||||
const formattedDelta = formatDelta(delta)
|
||||
const arrow = getDeltaArrow(delta)
|
||||
const crosshairEdgeMax = width * 0.85
|
||||
const crosshairAtEdge = !!crosshair && crosshair > crosshairEdgeMax
|
||||
const hasData = prices && prices.length > 0
|
||||
|
||||
/* Default curve doesn't look good for the HOUR/ALL chart */
|
||||
const curveTension = timePeriod === TimePeriod.ALL ? 0.75 : timePeriod === TimePeriod.HOUR ? 1 : 0.9
|
||||
/*
|
||||
* Default curve doesn't look good for the HOUR chart.
|
||||
* Higher values make the curve more rigid, lower values smooth the curve but make it less "sticky" to real data points,
|
||||
* making it unacceptable for shorter durations / smaller variances.
|
||||
*/
|
||||
const curveTension = timePeriod === TimePeriod.HOUR ? 1 : 0.9
|
||||
|
||||
const getX = useMemo(() => (p: PricePoint) => timeScale(p.timestamp), [timeScale])
|
||||
const getY = useMemo(() => (p: PricePoint) => rdScale(p.value), [rdScale])
|
||||
const curve = useMemo(() => curveCardinal.tension(curveTension), [curveTension])
|
||||
return (
|
||||
<>
|
||||
<ChartHeader>
|
||||
<TokenPrice>${displayPrice.value < 0.000001 ? '<0.000001' : displayPrice.value.toFixed(6)}</TokenPrice>
|
||||
<TokenPrice>{formatDollar({ num: displayPrice.value, isPrice: true })}</TokenPrice>
|
||||
<DeltaContainer>
|
||||
{formattedDelta}
|
||||
<ArrowCell>{arrow}</ArrowCell>
|
||||
</DeltaContainer>
|
||||
</ChartHeader>
|
||||
<LineChart
|
||||
data={prices}
|
||||
getX={(p: PricePoint) => timeScale(p.timestamp)}
|
||||
getY={(p: PricePoint) => rdScale(p.value)}
|
||||
marginTop={margin.top}
|
||||
curve={curveCardinal.tension(curveTension)}
|
||||
strokeWidth={2}
|
||||
width={width}
|
||||
height={graphHeight}
|
||||
>
|
||||
{crosshair !== null ? (
|
||||
<g>
|
||||
<AxisBottom
|
||||
scale={timeScale}
|
||||
stroke={theme.backgroundOutline}
|
||||
tickFormat={tickFormatter}
|
||||
tickStroke={theme.backgroundOutline}
|
||||
tickLength={4}
|
||||
hideTicks={true}
|
||||
tickTransform={'translate(0 -5)'}
|
||||
tickValues={ticks}
|
||||
top={graphHeight - 1}
|
||||
tickLabelProps={() => ({
|
||||
fill: theme.textSecondary,
|
||||
fontSize: 12,
|
||||
textAnchor: 'middle',
|
||||
transform: 'translate(0 -24)',
|
||||
})}
|
||||
/>
|
||||
<text
|
||||
x={crosshair + (crosshairAtEdge ? -4 : 4)}
|
||||
y={margin.crosshair + 10}
|
||||
textAnchor={crosshairAtEdge ? 'end' : 'start'}
|
||||
fontSize={12}
|
||||
fill={theme.textSecondary}
|
||||
>
|
||||
{crosshairDateFormatter(displayPrice.timestamp)}
|
||||
</text>
|
||||
<Line
|
||||
from={{ x: crosshair, y: margin.crosshair }}
|
||||
to={{ x: crosshair, y: graphHeight }}
|
||||
stroke={theme.backgroundOutline}
|
||||
strokeWidth={1}
|
||||
pointerEvents="none"
|
||||
strokeDasharray="4,4"
|
||||
/>
|
||||
<GlyphCircle
|
||||
left={crosshair}
|
||||
top={rdScale(displayPrice.value) + margin.top}
|
||||
size={50}
|
||||
fill={theme.accentActive}
|
||||
stroke={theme.backgroundOutline}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</g>
|
||||
) : (
|
||||
<AxisBottom scale={timeScale} stroke={theme.backgroundOutline} top={graphHeight - 1} hideTicks />
|
||||
)}
|
||||
<rect
|
||||
x={0}
|
||||
y={0}
|
||||
{!hasData ? (
|
||||
<MissingPriceChart
|
||||
width={width}
|
||||
height={graphHeight}
|
||||
fill={'transparent'}
|
||||
onTouchStart={handleHover}
|
||||
onTouchMove={handleHover}
|
||||
onMouseMove={handleHover}
|
||||
onMouseLeave={resetDisplay}
|
||||
message={prices && prices.length === 0 ? <NoV3DataMessage /> : <MissingDataMessage />}
|
||||
/>
|
||||
</LineChart>
|
||||
) : (
|
||||
<svg width={width} height={graphHeight}>
|
||||
<AnimatedInLineChart
|
||||
data={prices}
|
||||
getX={getX}
|
||||
getY={getY}
|
||||
marginTop={margin.top}
|
||||
curve={curve}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
{crosshair !== null ? (
|
||||
<g>
|
||||
<AxisBottom
|
||||
scale={timeScale}
|
||||
stroke={theme.backgroundOutline}
|
||||
tickFormat={tickFormatter}
|
||||
tickStroke={theme.backgroundOutline}
|
||||
tickLength={4}
|
||||
hideTicks={true}
|
||||
tickTransform={'translate(0 -5)'}
|
||||
tickValues={ticks}
|
||||
top={graphHeight - 1}
|
||||
tickLabelProps={() => ({
|
||||
fill: theme.textSecondary,
|
||||
fontSize: 12,
|
||||
textAnchor: 'middle',
|
||||
transform: 'translate(0 -24)',
|
||||
})}
|
||||
/>
|
||||
<text
|
||||
x={crosshair + (crosshairAtEdge ? -4 : 4)}
|
||||
y={margin.crosshair + 10}
|
||||
textAnchor={crosshairAtEdge ? 'end' : 'start'}
|
||||
fontSize={12}
|
||||
fill={theme.textSecondary}
|
||||
>
|
||||
{crosshairDateFormatter(displayPrice.timestamp)}
|
||||
</text>
|
||||
<Line
|
||||
from={{ x: crosshair, y: margin.crosshair }}
|
||||
to={{ x: crosshair, y: graphHeight }}
|
||||
stroke={theme.backgroundOutline}
|
||||
strokeWidth={1}
|
||||
pointerEvents="none"
|
||||
strokeDasharray="4,4"
|
||||
/>
|
||||
<GlyphCircle
|
||||
left={crosshair}
|
||||
top={rdScale(displayPrice.value) + margin.top}
|
||||
size={50}
|
||||
fill={theme.accentAction}
|
||||
stroke={theme.backgroundOutline}
|
||||
strokeWidth={0.5}
|
||||
/>
|
||||
</g>
|
||||
) : (
|
||||
<AxisBottom scale={timeScale} stroke={theme.backgroundOutline} top={graphHeight - 1} hideTicks />
|
||||
)}
|
||||
<rect
|
||||
x={0}
|
||||
y={0}
|
||||
width={width}
|
||||
height={graphHeight}
|
||||
fill={'transparent'}
|
||||
onTouchStart={handleHover}
|
||||
onTouchMove={handleHover}
|
||||
onMouseMove={handleHover}
|
||||
onMouseLeave={resetDisplay}
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
<TimeOptionsWrapper>
|
||||
<TimeOptionsContainer>
|
||||
{ORDERED_TIMES.map((time) => (
|
||||
@@ -368,4 +373,44 @@ export function PriceChart({ width, height, prices }: PriceChartProps) {
|
||||
)
|
||||
}
|
||||
|
||||
const StyledMissingChart = styled.svg`
|
||||
text {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
`
|
||||
|
||||
const chartBottomPadding = 15
|
||||
|
||||
const NoV3DataMessage = () => (
|
||||
<Trans>This token doesn't have chart data because it hasn't been traded on Uniswap v3</Trans>
|
||||
)
|
||||
const MissingDataMessage = () => <Trans>Missing chart data</Trans>
|
||||
|
||||
function MissingPriceChart({ width, height, message }: { width: number; height: number; message: ReactNode }) {
|
||||
const theme = useTheme()
|
||||
const midPoint = height / 2 + 45
|
||||
return (
|
||||
<StyledMissingChart width={width} height={height}>
|
||||
<path
|
||||
d={`M 0 ${midPoint} Q 104 ${midPoint - 70}, 208 ${midPoint} T 416 ${midPoint}
|
||||
M 416 ${midPoint} Q 520 ${midPoint - 70}, 624 ${midPoint} T 832 ${midPoint}`}
|
||||
stroke={theme.backgroundOutline}
|
||||
fill="transparent"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
<TrendingUp stroke={theme.textTertiary} x={0} size={12} y={height - chartBottomPadding - 10} />
|
||||
<text y={height - chartBottomPadding} x="20" fill={theme.textTertiary}>
|
||||
{message || <Trans>Missing chart data</Trans>}
|
||||
</text>
|
||||
<path
|
||||
d={`M 0 ${height - 1}, ${width} ${height - 1}`}
|
||||
stroke={theme.backgroundOutline}
|
||||
fill="transparent"
|
||||
strokeWidth="1"
|
||||
/>
|
||||
</StyledMissingChart>
|
||||
)
|
||||
}
|
||||
|
||||
export default PriceChart
|
||||
|
||||
@@ -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,7 +1,13 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ReactNode } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { formatDollarAmount } from 'utils/formatDollarAmt'
|
||||
import { ThemedText } from 'theme'
|
||||
import { textFadeIn } from 'theme/animations'
|
||||
import { formatDollar } from 'utils/formatNumbers'
|
||||
|
||||
import { TokenSortMethod } from '../state'
|
||||
import { HEADER_DESCRIPTIONS } from '../TokenTable/TokenRow'
|
||||
import InfoTip from './InfoTip'
|
||||
|
||||
export const StatWrapper = styled.div`
|
||||
display: flex;
|
||||
@@ -22,6 +28,15 @@ export const StatPair = styled.div`
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
|
||||
const Header = styled(ThemedText.MediumHeader)`
|
||||
font-size: 28px !important;
|
||||
`
|
||||
const StatTitle = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
`
|
||||
const StatPrice = styled.span`
|
||||
font-size: 28px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
@@ -29,14 +44,32 @@ const StatPrice = styled.span`
|
||||
const NoData = styled.div`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
`
|
||||
const Wrapper = styled.div`
|
||||
gap: 16px;
|
||||
${textFadeIn}
|
||||
`
|
||||
|
||||
type NumericStat = number | undefined | null
|
||||
|
||||
function Stat({ value, title }: { value: NumericStat; title: ReactNode }) {
|
||||
function Stat({
|
||||
value,
|
||||
title,
|
||||
description,
|
||||
isPrice = false,
|
||||
}: {
|
||||
value: NumericStat
|
||||
title: ReactNode
|
||||
description?: ReactNode
|
||||
isPrice?: boolean
|
||||
}) {
|
||||
return (
|
||||
<StatWrapper>
|
||||
{title}
|
||||
<StatPrice>{value ? formatDollarAmount(value) : '-'}</StatPrice>
|
||||
<StatTitle>
|
||||
{title}
|
||||
{description && <InfoTip text={description}></InfoTip>}
|
||||
</StatTitle>
|
||||
|
||||
<StatPrice>{formatDollar({ num: value, isPrice })}</StatPrice>
|
||||
</StatWrapper>
|
||||
)
|
||||
}
|
||||
@@ -51,16 +84,33 @@ export default function StatsSection(props: StatsSectionProps) {
|
||||
const { priceLow52W, priceHigh52W, TVL, volume24H } = props
|
||||
if (TVL || volume24H || priceLow52W || priceHigh52W) {
|
||||
return (
|
||||
<TokenStatsSection>
|
||||
<StatPair>
|
||||
<Stat value={TVL} title={<Trans>Total Value Locked</Trans>} />
|
||||
<Stat value={volume24H} title={<Trans>24H volume</Trans>} />
|
||||
</StatPair>
|
||||
<StatPair>
|
||||
<Stat value={priceLow52W} title={<Trans>52W low</Trans>} />
|
||||
<Stat value={priceHigh52W} title={<Trans>52W high</Trans>} />
|
||||
</StatPair>
|
||||
</TokenStatsSection>
|
||||
<Wrapper>
|
||||
<Header>
|
||||
<Trans>Stats</Trans>
|
||||
</Header>
|
||||
<TokenStatsSection>
|
||||
<StatPair>
|
||||
<Stat
|
||||
value={TVL}
|
||||
description={HEADER_DESCRIPTIONS[TokenSortMethod.TOTAL_VALUE_LOCKED]}
|
||||
title={<Trans>TVL</Trans>}
|
||||
/>
|
||||
<Stat
|
||||
value={volume24H}
|
||||
description={
|
||||
<Trans>
|
||||
24H volume is the amount of the asset that has been traded on Uniswap v3 during the past 24 hours.
|
||||
</Trans>
|
||||
}
|
||||
title={<Trans>24H volume</Trans>}
|
||||
/>
|
||||
</StatPair>
|
||||
<StatPair>
|
||||
<Stat value={priceLow52W} title={<Trans>52W low</Trans>} isPrice={true} />
|
||||
<Stat value={priceHigh52W} title={<Trans>52W high</Trans>} isPrice={true} />
|
||||
</StatPair>
|
||||
</TokenStatsSection>
|
||||
</Wrapper>
|
||||
)
|
||||
} else {
|
||||
return <NoData>No stats available</NoData>
|
||||
|
||||
@@ -8,7 +8,6 @@ import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { MEDIUM_MEDIA_BREAKPOINT } from '../constants'
|
||||
import FilterOption from './FilterOption'
|
||||
|
||||
const InternalMenuItem = styled.div`
|
||||
@@ -58,10 +57,6 @@ const StyledMenu = styled.div`
|
||||
position: relative;
|
||||
border: none;
|
||||
text-align: left;
|
||||
|
||||
@media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) {
|
||||
flex: 1;
|
||||
}
|
||||
`
|
||||
const StyledMenuContent = styled.div`
|
||||
display: flex;
|
||||
@@ -69,7 +64,6 @@ const StyledMenuContent = styled.div`
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
border: none;
|
||||
width: 100%;
|
||||
font-weight: 600;
|
||||
vertical-align: middle;
|
||||
`
|
||||
@@ -90,6 +84,9 @@ const CheckContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: flex-end;
|
||||
`
|
||||
const NetworkFilterOption = styled(FilterOption)`
|
||||
width: 156px;
|
||||
`
|
||||
|
||||
export default function NetworkFilter() {
|
||||
const theme = useTheme()
|
||||
@@ -106,7 +103,7 @@ export default function NetworkFilter() {
|
||||
|
||||
return (
|
||||
<StyledMenu ref={node}>
|
||||
<FilterOption onClick={toggleMenu} aria-label={`networkFilter`} active={open}>
|
||||
<NetworkFilterOption onClick={toggleMenu} aria-label={`networkFilter`} active={open}>
|
||||
<StyledMenuContent>
|
||||
<NetworkLabel>
|
||||
<Logo src={circleLogoUrl ?? logoUrl} /> {label}
|
||||
@@ -119,7 +116,7 @@ export default function NetworkFilter() {
|
||||
)}
|
||||
</Chevron>
|
||||
</StyledMenuContent>
|
||||
</FilterOption>
|
||||
</NetworkFilterOption>
|
||||
{open && (
|
||||
<MenuTimeFlyout>
|
||||
{BACKEND_CHAIN_NAMES.map((network) => {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ElementName, Event, EventName } from 'analytics/constants'
|
||||
import { TraceEvent } from 'analytics/TraceEvent'
|
||||
import searchIcon from 'assets/svg/search.svg'
|
||||
import xIcon from 'assets/svg/x.svg'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useResetAtom, useUpdateAtom } from 'jotai/utils'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { useEffect, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
@@ -21,10 +23,10 @@ const SearchInput = styled.input`
|
||||
background-position: 12px center;
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
border-radius: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
border: 1.5px solid ${({ theme }) => theme.backgroundOutline};
|
||||
height: 100%;
|
||||
width: min(200px, 100%);
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
padding-left: 40px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
transition-duration: ${({ theme }) => theme.transition.duration.fast};
|
||||
@@ -59,30 +61,37 @@ const SearchInput = styled.input`
|
||||
`
|
||||
|
||||
export default function SearchBar() {
|
||||
const [localFilterString, setLocalFilterString] = useState('')
|
||||
const currentString = useAtomValue(filterStringAtom)
|
||||
const [localFilterString, setLocalFilterString] = useState(currentString)
|
||||
const setFilterString = useUpdateAtom(filterStringAtom)
|
||||
const resetFilterString = useResetAtom(filterStringAtom)
|
||||
const debouncedLocalFilterString = useDebounce(localFilterString, 300)
|
||||
|
||||
useEffect(() => {
|
||||
setLocalFilterString(currentString)
|
||||
}, [currentString])
|
||||
|
||||
useEffect(() => {
|
||||
setFilterString(debouncedLocalFilterString)
|
||||
return () => {
|
||||
resetFilterString()
|
||||
}
|
||||
}, [debouncedLocalFilterString, setFilterString, resetFilterString])
|
||||
}, [debouncedLocalFilterString, setFilterString])
|
||||
|
||||
return (
|
||||
<SearchBarContainer>
|
||||
<Trans
|
||||
render={({ translation }) => (
|
||||
<SearchInput
|
||||
type="search"
|
||||
placeholder={`${translation}`}
|
||||
id="searchBar"
|
||||
autoComplete="off"
|
||||
value={localFilterString}
|
||||
onChange={({ target: { value } }) => setLocalFilterString(value)}
|
||||
/>
|
||||
<TraceEvent
|
||||
events={[Event.onFocus]}
|
||||
name={EventName.EXPLORE_SEARCH_SELECTED}
|
||||
element={ElementName.EXPLORE_SEARCH_INPUT}
|
||||
>
|
||||
<SearchInput
|
||||
type="search"
|
||||
placeholder={`${translation}`}
|
||||
id="searchBar"
|
||||
autoComplete="off"
|
||||
value={localFilterString}
|
||||
onChange={({ target: { value } }) => setLocalFilterString(value)}
|
||||
/>
|
||||
</TraceEvent>
|
||||
)}
|
||||
>
|
||||
Filter tokens
|
||||
|
||||
@@ -17,16 +17,14 @@ export const DISPLAYS: Record<TimePeriod, string> = {
|
||||
[TimePeriod.WEEK]: '1W',
|
||||
[TimePeriod.MONTH]: '1M',
|
||||
[TimePeriod.YEAR]: '1Y',
|
||||
[TimePeriod.ALL]: 'All',
|
||||
}
|
||||
|
||||
export const ORDERED_TIMES = [
|
||||
export const ORDERED_TIMES: TimePeriod[] = [
|
||||
TimePeriod.HOUR,
|
||||
TimePeriod.DAY,
|
||||
TimePeriod.WEEK,
|
||||
TimePeriod.MONTH,
|
||||
TimePeriod.YEAR,
|
||||
TimePeriod.ALL,
|
||||
]
|
||||
|
||||
const InternalMenuItem = styled.div`
|
||||
|
||||
@@ -6,16 +6,17 @@ import SparklineChart from 'components/Charts/SparklineChart'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { FavoriteTokensVariant, useFavoriteTokensFlag } from 'featureFlags/flags/favoriteTokens'
|
||||
import { TokenSortMethod, TopToken } from 'graphql/data/TopTokens'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID, getTokenDetailsURL, TimePeriod } from 'graphql/data/util'
|
||||
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 { Link, useParams } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import styled, { css, useTheme } from 'styled-components/macro'
|
||||
import { ClickableStyle } from 'theme'
|
||||
import { formatDollarAmount } from 'utils/formatDollarAmt'
|
||||
import { formatDollar } from 'utils/formatNumbers'
|
||||
|
||||
import {
|
||||
LARGE_MEDIA_BREAKPOINT,
|
||||
@@ -29,13 +30,14 @@ import {
|
||||
filterTimeAtom,
|
||||
sortAscendingAtom,
|
||||
sortMethodAtom,
|
||||
TokenSortMethod,
|
||||
useIsFavorited,
|
||||
useSetSortMethod,
|
||||
useToggleFavorite,
|
||||
} from '../state'
|
||||
import { useTokenLogoURI } from '../TokenDetails/ChartSection'
|
||||
import InfoTip from '../TokenDetails/InfoTip'
|
||||
import { formatDelta, getDeltaArrow } from '../TokenDetails/PriceChart'
|
||||
import { DISPLAYS } from './TimeSelector'
|
||||
|
||||
const Cell = styled.div`
|
||||
display: flex;
|
||||
@@ -50,15 +52,17 @@ const StyledTokenRow = styled.div<{
|
||||
}>`
|
||||
background-color: transparent;
|
||||
display: grid;
|
||||
font-size: 15px;
|
||||
font-size: 16px;
|
||||
grid-template-columns: ${({ favoriteTokensEnabled }) =>
|
||||
favoriteTokensEnabled ? '1fr 7fr 4fr 4fr 4fr 4fr 5fr 1.2fr' : '1fr 7fr 4fr 4fr 4fr 4fr 5fr'};
|
||||
height: 60px;
|
||||
line-height: 24px;
|
||||
max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT};
|
||||
min-width: 390px;
|
||||
padding-top: ${({ first }) => (first ? '4px' : '0px')};
|
||||
padding-bottom: ${({ last }) => (last ? '4px' : '0px')};
|
||||
${({ first, last }) => css`
|
||||
height: ${first || last ? '72px' : '64px'};
|
||||
padding-top: ${first ? '8px' : '0px'};
|
||||
padding-bottom: ${last ? '8px' : '0px'};
|
||||
`}
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
transition: ${({
|
||||
@@ -150,7 +154,7 @@ const StyledHeaderRow = styled(StyledTokenRow)`
|
||||
border-color: ${({ theme }) => theme.backgroundOutline};
|
||||
border-radius: 8px 8px 0px 0px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 12px;
|
||||
font-size: 14px;
|
||||
height: 48px;
|
||||
line-height: 16px;
|
||||
padding: 0px 12px;
|
||||
@@ -169,7 +173,7 @@ const StyledHeaderRow = styled(StyledTokenRow)`
|
||||
const ListNumberCell = styled(Cell)<{ header: boolean }>`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
min-width: 32px;
|
||||
height: ${({ header }) => (header ? '48px' : '60px')};
|
||||
font-size: 14px;
|
||||
|
||||
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
|
||||
display: none;
|
||||
@@ -195,7 +199,7 @@ const MarketCapCell = styled(DataCell)`
|
||||
const NameCell = styled(Cell)`
|
||||
justify-content: flex-start;
|
||||
padding: 0px 8px;
|
||||
min-width: 200px;
|
||||
min-width: 240px;
|
||||
gap: 8px;
|
||||
`
|
||||
const PriceCell = styled(DataCell)`
|
||||
@@ -227,18 +231,18 @@ const PriceInfoCell = styled(Cell)`
|
||||
align-items: flex-end;
|
||||
}
|
||||
`
|
||||
const SortArrowCell = styled(Cell)`
|
||||
padding-right: 2px;
|
||||
`
|
||||
const HeaderCellWrapper = styled.span<{ onClick?: () => void }>`
|
||||
align-items: center;
|
||||
${ClickableStyle}
|
||||
cursor: ${({ onClick }) => (onClick ? 'pointer' : 'unset')};
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
height: 100%;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
`
|
||||
const HeaderCellText = styled(Text)`
|
||||
${ClickableStyle}
|
||||
`
|
||||
const SparkLineCell = styled(Cell)`
|
||||
padding: 0px 24px;
|
||||
min-width: 120px;
|
||||
@@ -307,7 +311,7 @@ const IconLoadingBubble = styled(LoadingBubble)`
|
||||
border-radius: 50%;
|
||||
width: 24px;
|
||||
`
|
||||
const SparkLineLoadingBubble = styled(LongLoadingBubble)`
|
||||
export const SparkLineLoadingBubble = styled(LongLoadingBubble)`
|
||||
height: 4px;
|
||||
`
|
||||
|
||||
@@ -316,7 +320,7 @@ export const L2NetworkLogo = styled.div<{ networkUrl?: string; size?: string }>`
|
||||
width: ${({ size }) => size ?? '12px'};
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
bottom: 0;
|
||||
background: url(${({ networkUrl }) => networkUrl});
|
||||
background-repeat: no-repeat;
|
||||
background-size: ${({ size }) => (size ? `${size} ${size}` : '12px 12px')};
|
||||
@@ -328,11 +332,15 @@ export const LogoContainer = styled.div`
|
||||
display: flex;
|
||||
`
|
||||
|
||||
/* formatting for volume with timeframe header display */
|
||||
function getHeaderDisplay(method: string, timeframe: TimePeriod): string {
|
||||
if (method === TokenSortMethod.VOLUME || method === TokenSortMethod.PERCENT_CHANGE)
|
||||
return `${DISPLAYS[timeframe]} ${method}`
|
||||
return method
|
||||
export const HEADER_DESCRIPTIONS: Record<TokenSortMethod, ReactNode | undefined> = {
|
||||
[TokenSortMethod.PRICE]: undefined,
|
||||
[TokenSortMethod.PERCENT_CHANGE]: undefined,
|
||||
[TokenSortMethod.TOTAL_VALUE_LOCKED]: (
|
||||
<Trans>Total value locked (TVL) is the amount of the asset that’s currently in a Uniswap v3 liquidity pool.</Trans>
|
||||
),
|
||||
[TokenSortMethod.VOLUME]: (
|
||||
<Trans>Volume is the amount of the asset that has been traded on Uniswap v3 during the selected time frame.</Trans>
|
||||
),
|
||||
}
|
||||
|
||||
/* Get singular header cell for header row */
|
||||
@@ -347,33 +355,24 @@ function HeaderCell({
|
||||
const sortAscending = useAtomValue(sortAscendingAtom)
|
||||
const handleSortCategory = useSetSortMethod(category)
|
||||
const sortMethod = useAtomValue(sortMethodAtom)
|
||||
const timeframe = useAtomValue(filterTimeAtom)
|
||||
|
||||
if (sortMethod === category) {
|
||||
return (
|
||||
<HeaderCellWrapper onClick={handleSortCategory}>
|
||||
<SortArrowCell>
|
||||
const description = HEADER_DESCRIPTIONS[category]
|
||||
|
||||
return (
|
||||
<HeaderCellWrapper onClick={handleSortCategory}>
|
||||
{sortMethod === category && (
|
||||
<>
|
||||
{sortAscending ? (
|
||||
<ArrowUp size={14} color={theme.accentActive} />
|
||||
<ArrowUp size={20} strokeWidth={1.8} color={theme.accentActive} />
|
||||
) : (
|
||||
<ArrowDown size={14} color={theme.accentActive} />
|
||||
<ArrowDown size={20} strokeWidth={1.8} color={theme.accentActive} />
|
||||
)}
|
||||
</SortArrowCell>
|
||||
{getHeaderDisplay(category, timeframe)}
|
||||
</HeaderCellWrapper>
|
||||
)
|
||||
}
|
||||
if (sortable) {
|
||||
return (
|
||||
<HeaderCellWrapper onClick={handleSortCategory}>
|
||||
<SortArrowCell>
|
||||
<ArrowUp size={14} visibility="hidden" />
|
||||
</SortArrowCell>
|
||||
{getHeaderDisplay(category, timeframe)}
|
||||
</HeaderCellWrapper>
|
||||
)
|
||||
}
|
||||
return <HeaderCellWrapper>{getHeaderDisplay(category, timeframe)}</HeaderCellWrapper>
|
||||
</>
|
||||
)}
|
||||
<HeaderCellText>{category}</HeaderCellText>
|
||||
{description && <InfoTip text={description}></InfoTip>}
|
||||
</HeaderCellWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
/* Token Row: skeleton row component */
|
||||
@@ -397,7 +396,7 @@ export function TokenRow({
|
||||
marketCap: ReactNode
|
||||
price: ReactNode
|
||||
percentChange: ReactNode
|
||||
sparkLine: ReactNode
|
||||
sparkLine?: ReactNode
|
||||
tokenInfo: ReactNode
|
||||
volume: ReactNode
|
||||
last?: boolean
|
||||
@@ -468,6 +467,7 @@ interface LoadedRowProps {
|
||||
tokenListIndex: number
|
||||
tokenListLength: number
|
||||
token: NonNullable<TopToken>
|
||||
sparklineMap: SparklineMap
|
||||
}
|
||||
|
||||
/* Loaded State: row component with token information */
|
||||
@@ -479,21 +479,23 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
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 timePeriod = useAtomValue(filterTimeAtom)
|
||||
const delta = token.market?.pricePercentChange?.value
|
||||
const arrow = delta ? getDeltaArrow(delta) : null
|
||||
const formattedDelta = delta ? formatDelta(delta) : null
|
||||
const sortAscending = useAtomValue(sortAscendingAtom)
|
||||
const arrow = getDeltaArrow(delta)
|
||||
const formattedDelta = formatDelta(delta)
|
||||
const rank = sortAscending ? tokenListLength - tokenListIndex : tokenListIndex + 1
|
||||
|
||||
const exploreTokenSelectedEventProperties = {
|
||||
chain_id: filterNetwork,
|
||||
token_address: tokenAddress,
|
||||
token_symbol: tokenSymbol,
|
||||
token_list_index: tokenListIndex,
|
||||
token_list_rank: rank,
|
||||
token_list_length: tokenListLength,
|
||||
time_frame: timePeriod,
|
||||
search_token_address_input: filterString,
|
||||
@@ -518,7 +520,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
<FavoriteIcon isFavorited={isFavorited} />
|
||||
</ClickFavorited>
|
||||
}
|
||||
listNumber={sortAscending ? tokenListLength - tokenListIndex : tokenListIndex + 1}
|
||||
listNumber={rank}
|
||||
tokenInfo={
|
||||
<ClickableName>
|
||||
<LogoContainer>
|
||||
@@ -534,7 +536,9 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
price={
|
||||
<ClickableContent>
|
||||
<PriceInfoCell>
|
||||
{token.market?.price?.value ? formatDollarAmount(token.market.price.value) : '-'}
|
||||
{token.market?.price?.value
|
||||
? formatDollar({ num: token.market.price.value, isPrice: true, lessPreciseStablecoinValues: true })
|
||||
: '-'}
|
||||
<PercentChangeInfoCell>
|
||||
{formattedDelta}
|
||||
{arrow}
|
||||
@@ -544,32 +548,35 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
|
||||
}
|
||||
percentChange={
|
||||
<ClickableContent>
|
||||
{formattedDelta ?? '-'}
|
||||
{formattedDelta}
|
||||
{arrow}
|
||||
</ClickableContent>
|
||||
}
|
||||
marketCap={
|
||||
<ClickableContent>
|
||||
{token.market?.totalValueLocked?.value ? formatDollarAmount(token.market.totalValueLocked.value) : '-'}
|
||||
{token.market?.totalValueLocked?.value ? formatDollar({ num: token.market.totalValueLocked.value }) : '-'}
|
||||
</ClickableContent>
|
||||
}
|
||||
volume={
|
||||
<ClickableContent>
|
||||
{token.market?.volume?.value ? formatDollarAmount(token.market.volume.value) : '-'}
|
||||
{token.market?.volume?.value ? formatDollar({ num: token.market.volume.value }) : '-'}
|
||||
</ClickableContent>
|
||||
}
|
||||
sparkLine={
|
||||
<SparkLine>
|
||||
<ParentSize>
|
||||
{({ width, height }) => (
|
||||
<SparklineChart
|
||||
width={width}
|
||||
height={height}
|
||||
tokenData={token}
|
||||
pricePercentChange={token.market?.pricePercentChange?.value}
|
||||
timePeriod={timePeriod}
|
||||
/>
|
||||
)}
|
||||
{({ width, height }) =>
|
||||
props.sparklineMap && (
|
||||
<SparklineChart
|
||||
width={width}
|
||||
height={height}
|
||||
tokenData={token}
|
||||
pricePercentChange={token.market?.pricePercentChange?.value}
|
||||
timePeriod={timePeriod}
|
||||
sparklineMap={props.sparklineMap}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</ParentSize>
|
||||
</SparkLine>
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ 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, useCallback, useRef } from 'react'
|
||||
import { ReactNode } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
@@ -11,8 +11,6 @@ import styled from 'styled-components/macro'
|
||||
import { MAX_WIDTH_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { HeaderRow, LoadedRow, LoadingRow } from './TokenRow'
|
||||
|
||||
const LOADING_ROWS_COUNT = 3
|
||||
|
||||
const GridContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -22,13 +20,16 @@ const GridContainer = styled.div`
|
||||
0px 24px 32px rgba(0, 0, 0, 0.01);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 8px;
|
||||
border-radius: 12px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
`
|
||||
|
||||
const TokenDataContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`
|
||||
@@ -55,8 +56,19 @@ function NoTokensState({ message }: { message: ReactNode }) {
|
||||
)
|
||||
}
|
||||
|
||||
const LoadingMoreRows = Array(LOADING_ROWS_COUNT).fill(<LoadingRow />)
|
||||
const LoadingRows = (rowCount?: number) => Array(rowCount ?? PAGE_SIZE).fill(<LoadingRow />)
|
||||
const LoadingRowsWrapper = styled.div`
|
||||
margin-top: 8px;
|
||||
`
|
||||
|
||||
const LoadingRows = (rowCount?: number) => (
|
||||
<LoadingRowsWrapper>
|
||||
{Array(rowCount ?? PAGE_SIZE)
|
||||
.fill(null)
|
||||
.map((_, index) => {
|
||||
return <LoadingRow key={index} />
|
||||
})}
|
||||
</LoadingRowsWrapper>
|
||||
)
|
||||
|
||||
export function LoadingTokenTable({ rowCount }: { rowCount?: number }) {
|
||||
return (
|
||||
@@ -67,74 +79,51 @@ export function LoadingTokenTable({ rowCount }: { rowCount?: number }) {
|
||||
)
|
||||
}
|
||||
|
||||
export default function TokenTable() {
|
||||
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 { loading, tokens, tokensWithoutPriceHistoryCount, hasMore, loadMoreTokens, maxFetchable } =
|
||||
useTopTokens(chainName)
|
||||
const showMoreLoadingRows = Boolean(loading && hasMore)
|
||||
|
||||
const observer = useRef<IntersectionObserver>()
|
||||
const lastTokenRef = useCallback(
|
||||
(node: HTMLDivElement) => {
|
||||
if (loading) return
|
||||
if (observer.current) observer.current.disconnect()
|
||||
observer.current = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && hasMore) {
|
||||
loadMoreTokens()
|
||||
}
|
||||
})
|
||||
if (node) observer.current.observe(node)
|
||||
},
|
||||
[loading, hasMore, loadMoreTokens]
|
||||
)
|
||||
const { tokens, sparklines } = useTopTokens(chainName)
|
||||
setRowCount(tokens?.length ?? PAGE_SIZE)
|
||||
|
||||
/* loading and error state */
|
||||
if (loading && (!tokens || tokens?.length === 0)) {
|
||||
return <LoadingTokenTable rowCount={Math.min(tokensWithoutPriceHistoryCount, PAGE_SIZE)} />
|
||||
if (!tokens) {
|
||||
return (
|
||||
<NoTokensState
|
||||
message={
|
||||
<>
|
||||
<AlertTriangle size={16} />
|
||||
<Trans>An error occurred loading tokens. Please try again.</Trans>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
} else if (tokens?.length === 0) {
|
||||
return showFavorites ? (
|
||||
<NoTokensState message={<Trans>You have no favorited tokens</Trans>} />
|
||||
) : (
|
||||
<NoTokensState message={<Trans>No tokens found</Trans>} />
|
||||
)
|
||||
} else {
|
||||
if (!tokens) {
|
||||
return (
|
||||
<NoTokensState
|
||||
message={
|
||||
<>
|
||||
<AlertTriangle size={16} />
|
||||
<Trans>An error occured loading tokens. Please try again.</Trans>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
} else if (tokens?.length === 0) {
|
||||
return showFavorites ? (
|
||||
<NoTokensState message={<Trans>You have no favorited tokens</Trans>} />
|
||||
) : (
|
||||
<NoTokensState message={<Trans>No tokens found</Trans>} />
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<GridContainer>
|
||||
<HeaderRow />
|
||||
<TokenDataContainer>
|
||||
{tokens.map(
|
||||
(token, index) =>
|
||||
token && (
|
||||
<LoadedRow
|
||||
key={token?.address}
|
||||
tokenListIndex={index}
|
||||
tokenListLength={maxFetchable}
|
||||
token={token}
|
||||
ref={index + 1 === tokens.length ? lastTokenRef : undefined}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{showMoreLoadingRows && LoadingMoreRows}
|
||||
</TokenDataContainer>
|
||||
</GridContainer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<GridContainer>
|
||||
<HeaderRow />
|
||||
<TokenDataContainer>
|
||||
{tokens.map(
|
||||
(token, index) =>
|
||||
token && (
|
||||
<LoadedRow
|
||||
key={token?.address}
|
||||
tokenListIndex={index}
|
||||
tokenListLength={tokens.length}
|
||||
token={token}
|
||||
sparklineMap={sparklines}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</TokenDataContainer>
|
||||
</GridContainer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { X } from 'react-feather'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { useShowTokensPromoBanner } from 'state/user/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { opacify } from 'theme/utils'
|
||||
@@ -59,22 +62,34 @@ const Description = styled(Link)`
|
||||
export default function TokensBanner() {
|
||||
const theme = useTheme()
|
||||
const [showTokensPromoBanner, setShowTokensPromoBanner] = useShowTokensPromoBanner()
|
||||
const navigate = useNavigate()
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
const chainName = chainIdToBackendName(connectedChainId).toLowerCase()
|
||||
|
||||
const closeBanner = () => {
|
||||
setShowTokensPromoBanner(false)
|
||||
const navigateToExplorePage = () => {
|
||||
navigate(`/tokens/${chainName}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<PopupContainer show={showTokensPromoBanner}>
|
||||
<PopupContainer show={showTokensPromoBanner} onClick={navigateToExplorePage}>
|
||||
<Header>
|
||||
<HeaderText to={'/tokens'} onClick={closeBanner}>
|
||||
Explore Top Tokens
|
||||
<HeaderText to={'/tokens'}>
|
||||
<Trans>Explore Top Tokens on Uniswap</Trans>
|
||||
</HeaderText>
|
||||
<X size={20} color={theme.textSecondary} onClick={closeBanner} style={{ cursor: 'pointer' }} />
|
||||
<X
|
||||
size={20}
|
||||
color={theme.textSecondary}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setShowTokensPromoBanner(false)
|
||||
}}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</Header>
|
||||
|
||||
<Description to={'/tokens'} onClick={closeBanner}>
|
||||
Check out the new explore tab to discover and learn more
|
||||
<Description to={'/tokens'}>
|
||||
<Trans>Sort and filter assets across networks on the new Tokens page.</Trans>
|
||||
</Description>
|
||||
</PopupContainer>
|
||||
)
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { TokenSortMethod } from 'graphql/data/TopTokens'
|
||||
import { TimePeriod } from 'graphql/data/util'
|
||||
import { atom, useAtom } from 'jotai'
|
||||
import { atomWithReset, atomWithStorage, useAtomValue } from 'jotai/utils'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
|
||||
export enum TokenSortMethod {
|
||||
PRICE = 'Price',
|
||||
PERCENT_CHANGE = 'Change',
|
||||
TOTAL_VALUE_LOCKED = 'TVL',
|
||||
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.TOTAL_VALUE_LOCKED)
|
||||
export const sortMethodAtom = atom<TokenSortMethod>(TokenSortMethod.VOLUME)
|
||||
export const sortAscendingAtom = atom<boolean>(false)
|
||||
|
||||
/* for favoriting tokens */
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useWeb3React } from '@web3-react/core'
|
||||
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'
|
||||
@@ -9,8 +10,8 @@ import { useLocation } from 'react-router-dom'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
|
||||
const Cart = lazy(() => import('nft/components/sell/modal/ListingTag'))
|
||||
const Bag = lazy(() => import('nft/components/bag/Bag'))
|
||||
const TransactionCompleteModal = lazy(() => import('nft/components/collection/TransactionCompleteModal'))
|
||||
|
||||
export default function TopLevelModals() {
|
||||
const addressClaimOpen = useModalIsOpen(ApplicationModal.ADDRESS_CLAIM)
|
||||
@@ -28,8 +29,8 @@ export default function TopLevelModals() {
|
||||
<ConnectedAccountBlocked account={account} isOpen={open} />
|
||||
{useTokensFlag() === TokensVariant.Enabled &&
|
||||
(location.pathname.includes('/pool') || location.pathname.includes('/swap')) && <TokensBanner />}
|
||||
<Cart />
|
||||
<Bag />
|
||||
{useNftFlag() === NftVariant.Enabled && <TransactionCompleteModal />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -454,7 +454,7 @@ export default function TransactionConfirmationModal({
|
||||
|
||||
// confirmation screen
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90} redesignFlag={redesignFlagEnabled}>
|
||||
<Modal isOpen={isOpen} scrollOverlay={true} onDismiss={onDismiss} maxHeight={90} redesignFlag={redesignFlagEnabled}>
|
||||
{isL2ChainId(chainId) && (hash || attemptingTxn) ? (
|
||||
<L2Content chainId={chainId} hash={hash} onDismiss={onDismiss} pendingText={pendingText} />
|
||||
) : attemptingTxn ? (
|
||||
|
||||
@@ -4,11 +4,13 @@ import { useWeb3React } from '@web3-react/core'
|
||||
import { getConnection } from 'connection/utils'
|
||||
import { getChainInfoOrDefault } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import useCopyClipboard from 'hooks/useCopyClipboard'
|
||||
import useStablecoinPrice from 'hooks/useStablecoinPrice'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { Copy, ExternalLink, Power } from 'react-feather'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
import { useCurrencyBalanceString } from 'state/connection/hooks'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
@@ -16,15 +18,14 @@ import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { shortenAddress } from '../../nft/utils/address'
|
||||
import { useToggleModal } from '../../state/application/hooks'
|
||||
import { useCloseModal, useToggleModal } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/claim/hooks'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
import StatusIcon from '../Identicon/StatusIcon'
|
||||
import IconButton, { IconHoverText } from './IconButton'
|
||||
|
||||
const UNIbutton = styled(ButtonPrimary)`
|
||||
background: linear-gradient(to right, #9139b0 0%, #4261d6 100%);
|
||||
const WalletButton = styled(ButtonPrimary)`
|
||||
border-radius: 12px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
@@ -33,6 +34,16 @@ const UNIbutton = styled(ButtonPrimary)`
|
||||
border: none;
|
||||
`
|
||||
|
||||
const ProfileButton = styled(WalletButton)`
|
||||
background: ${({ theme }) => theme.backgroundInteractive};
|
||||
transition: ${({ theme }) => theme.transition.duration.fast} ${({ theme }) => theme.transition.timing.ease}
|
||||
background-color;
|
||||
`
|
||||
|
||||
const UNIButton = styled(WalletButton)`
|
||||
background: linear-gradient(to right, #9139b0 0%, #4261d6 100%);
|
||||
`
|
||||
|
||||
const Column = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -97,6 +108,9 @@ const AuthenticatedHeader = () => {
|
||||
nativeCurrency: { symbol: nativeCurrencySymbol },
|
||||
explorer,
|
||||
} = getChainInfoOrDefault(chainId ? chainId : SupportedChainId.MAINNET)
|
||||
const nftFlag = useNftFlag()
|
||||
const navigate = useNavigate()
|
||||
const closeModal = useCloseModal(ApplicationModal.WALLET_DROPDOWN)
|
||||
|
||||
const unclaimedAmount: CurrencyAmount<Token> | undefined = useUserUnclaimedAmount(account)
|
||||
const isUnclaimed = useUserHasAvailableClaim(account)
|
||||
@@ -118,6 +132,11 @@ const AuthenticatedHeader = () => {
|
||||
return price * balance
|
||||
}, [balanceString, nativeCurrencyPrice])
|
||||
|
||||
const navigateToProfile = () => {
|
||||
navigate('/profile')
|
||||
closeModal()
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthenticatedHeaderWrapper>
|
||||
<HeaderWrapper>
|
||||
@@ -130,14 +149,15 @@ const AuthenticatedHeader = () => {
|
||||
</FlexContainer>
|
||||
</StatusWrapper>
|
||||
<IconContainer>
|
||||
<IconButton onClick={copy} Icon={Copy} text={isCopied ? <Trans>Copied!</Trans> : <Trans>Copy</Trans>} />
|
||||
<IconButton href={`${explorer}address/${account}`} Icon={ExternalLink} text={<Trans>Explore</Trans>} />
|
||||
<IconButton
|
||||
dataTestId="wallet-disconnect"
|
||||
onClick={disconnect}
|
||||
Icon={Power}
|
||||
text={<Trans>Disconnect</Trans>}
|
||||
/>
|
||||
<IconButton onClick={copy} Icon={Copy}>
|
||||
{isCopied ? <Trans>Copied!</Trans> : <Trans>Copy</Trans>}
|
||||
</IconButton>
|
||||
<IconButton href={`${explorer}address/${account}`} target="_blank" Icon={ExternalLink}>
|
||||
<Trans>Explore</Trans>
|
||||
</IconButton>
|
||||
<IconButton data-testid="wallet-disconnect" onClick={disconnect} Icon={Power}>
|
||||
<Trans>Disconnect</Trans>
|
||||
</IconButton>
|
||||
</IconContainer>
|
||||
</HeaderWrapper>
|
||||
<Column>
|
||||
@@ -147,10 +167,15 @@ const AuthenticatedHeader = () => {
|
||||
</Text>
|
||||
<USDText>${amountUSD.toFixed(2)} USD</USDText>
|
||||
</BalanceWrapper>
|
||||
{nftFlag === NftVariant.Enabled && (
|
||||
<ProfileButton onClick={navigateToProfile}>
|
||||
<Trans>View and sell NFTs</Trans>
|
||||
</ProfileButton>
|
||||
)}
|
||||
{isUnclaimed && (
|
||||
<UNIbutton onClick={openClaimModal}>
|
||||
<UNIButton onClick={openClaimModal}>
|
||||
<Trans>Claim</Trans> {unclaimedAmount?.toFixed(0, { groupSeparator: ',' } ?? '-')} <Trans>reward</Trans>
|
||||
</UNIbutton>
|
||||
</UNIButton>
|
||||
)}
|
||||
</Column>
|
||||
</AuthenticatedHeaderWrapper>
|
||||
|
||||
@@ -58,31 +58,28 @@ const IconWrapper = styled.span`
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
`
|
||||
|
||||
interface IconButtonProps {
|
||||
text: React.ReactNode
|
||||
interface BaseProps {
|
||||
Icon: Icon
|
||||
onClick?: () => void
|
||||
href?: string
|
||||
dataTestId?: string
|
||||
}
|
||||
interface IconLinkProps extends React.ComponentPropsWithoutRef<'a'>, BaseProps {}
|
||||
interface IconButtonProps extends React.ComponentPropsWithoutRef<'button'>, BaseProps {}
|
||||
|
||||
const IconBlock = (props: React.ComponentPropsWithoutRef<'a' | 'button'>) => {
|
||||
if ('href' in props) {
|
||||
return <IconBlockLink {...props} />
|
||||
}
|
||||
// ignoring 'button' 'type' conflict between React and styled-components
|
||||
// @ts-ignore
|
||||
return <IconBlockButton {...props} />
|
||||
}
|
||||
|
||||
const IconButton = ({ Icon, onClick, text, href, dataTestId }: IconButtonProps) => {
|
||||
return href ? (
|
||||
<IconBlockLink data-testId={dataTestId} href={href} target="_blank">
|
||||
<IconWrapper>
|
||||
<Icon strokeWidth={1.5} size={16} />
|
||||
<IconHoverText>{text}</IconHoverText>
|
||||
</IconWrapper>
|
||||
</IconBlockLink>
|
||||
) : (
|
||||
<IconBlockButton data-testId={dataTestId} onClick={onClick}>
|
||||
<IconWrapper>
|
||||
<Icon strokeWidth={1.5} size={16} />
|
||||
<IconHoverText>{text}</IconHoverText>
|
||||
</IconWrapper>
|
||||
</IconBlockButton>
|
||||
)
|
||||
}
|
||||
const IconButton = ({ Icon, children, ...rest }: IconButtonProps | IconLinkProps) => (
|
||||
<IconBlock {...rest}>
|
||||
<IconWrapper>
|
||||
<Icon strokeWidth={1.5} size={16} />
|
||||
<IconHoverText>{children}</IconHoverText>
|
||||
</IconWrapper>
|
||||
</IconBlock>
|
||||
)
|
||||
|
||||
export default IconButton
|
||||
|
||||
@@ -11,7 +11,6 @@ import { TransactionHistoryMenu } from './TransactionMenu'
|
||||
const WalletWrapper = styled.div`
|
||||
border-radius: 12px;
|
||||
width: 320px;
|
||||
max-height: 376px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 16px;
|
||||
|
||||
@@ -116,9 +116,9 @@ export default function PendingView({
|
||||
<ThemedText.MediumHeader>
|
||||
<Trans>Waiting to connect</Trans>
|
||||
</ThemedText.MediumHeader>
|
||||
<ThemedText.SubHeader style={{ paddingTop: '8px' }}>
|
||||
<ThemedText.BodyPrimary style={{ paddingTop: '8px' }}>
|
||||
<Trans>Confirm this connection in your wallet</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</ThemedText.BodyPrimary>
|
||||
</WaitingToConnectSection>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsInjected, getIsMetaMask } from 'connection/utils'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
@@ -156,6 +157,7 @@ export default function WalletModal({
|
||||
|
||||
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)
|
||||
|
||||
@@ -307,15 +309,15 @@ export default function WalletModal({
|
||||
)
|
||||
}
|
||||
|
||||
function getTermsOfService(redesignFlagEnabled: boolean) {
|
||||
return redesignFlagEnabled ? (
|
||||
function getTermsOfService(nftFlagEnabled: boolean, walletView: string) {
|
||||
if (nftFlagEnabled && walletView === WALLET_VIEWS.PENDING) return null
|
||||
return nftFlagEnabled ? (
|
||||
<AutoRow style={{ flexWrap: 'nowrap', padding: '4px 16px' }}>
|
||||
<ThemedText.BodySecondary fontSize={12}>
|
||||
<ThemedText.BodySecondary fontSize={16} lineHeight={'24px'}>
|
||||
<Trans>
|
||||
By connecting a wallet, you agree to Uniswap Labs’{' '}
|
||||
<ExternalLink href="https://uniswap.org/terms-of-service/">Terms of Service</ExternalLink> and acknowledge
|
||||
that you have read and understand the Uniswap{' '}
|
||||
<ExternalLink href="https://uniswap.org/disclaimer/">Protocol Disclaimer</ExternalLink>.
|
||||
<ExternalLink href="https://uniswap.org/terms-of-service/">Terms of Service</ExternalLink> and consent to
|
||||
its <ExternalLink href="https://uniswap.org/privacy-policy">Privacy Policy</ExternalLink>.
|
||||
</Trans>
|
||||
</ThemedText.BodySecondary>
|
||||
</AutoRow>
|
||||
@@ -357,7 +359,7 @@ export default function WalletModal({
|
||||
/>
|
||||
)}
|
||||
{walletView !== WALLET_VIEWS.PENDING && <OptionGrid data-testid="option-grid">{getOptions()}</OptionGrid>}
|
||||
{!pendingError && getTermsOfService(redesignFlagEnabled)}
|
||||
{!pendingError && getTermsOfService(nftFlagEnabled, walletView)}
|
||||
</AutoColumn>
|
||||
</ContentWrapper>
|
||||
</UpperSection>
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { Currency, OnReviewSwapClick, SwapWidget } from '@uniswap/widgets'
|
||||
// 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 { useWeb3React } from '@web3-react/core'
|
||||
import { networkConnection } from 'connection'
|
||||
import { RPC_PROVIDERS } from 'constants/providers'
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
import { useMemo } from 'react'
|
||||
import { useIsDarkMode } from 'state/user/hooks'
|
||||
import { DARK_THEME, LIGHT_THEME } from 'theme/widget'
|
||||
|
||||
@@ -10,7 +14,7 @@ import { useSyncWidgetInputs } from './inputs'
|
||||
import { useSyncWidgetSettings } from './settings'
|
||||
import { useSyncWidgetTransactions } from './transactions'
|
||||
|
||||
export const WIDGET_WIDTH = 320
|
||||
export const WIDGET_WIDTH = 360
|
||||
|
||||
const WIDGET_ROUTER_URL = 'https://api.uniswap.org/v1/'
|
||||
|
||||
@@ -21,9 +25,8 @@ export interface WidgetProps {
|
||||
|
||||
export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps) {
|
||||
const locale = useActiveLocale()
|
||||
const darkMode = useIsDarkMode()
|
||||
const theme = useMemo(() => (darkMode ? DARK_THEME : LIGHT_THEME), [darkMode])
|
||||
const { provider } = useWeb3React()
|
||||
const theme = useIsDarkMode() ? DARK_THEME : LIGHT_THEME
|
||||
const { connector, provider } = useWeb3React()
|
||||
|
||||
const { inputs, tokenSelector } = useSyncWidgetInputs(defaultToken)
|
||||
const { settings } = useSyncWidgetSettings()
|
||||
@@ -41,7 +44,8 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps)
|
||||
theme={theme}
|
||||
onReviewSwapClick={onReviewSwapClick}
|
||||
// defaultChainId is excluded - it is always inferred from the passed provider
|
||||
provider={provider}
|
||||
provider={connector === networkConnection.connector ? null : provider} // use jsonRpcUrlMap for network providers
|
||||
tokenList={EMPTY_TOKEN_LIST} // prevents loading the default token list, as we use our own token selector UI
|
||||
{...inputs}
|
||||
{...settings}
|
||||
{...transactions}
|
||||
@@ -50,3 +54,7 @@ export default function Widget({ defaultToken, onReviewSwapClick }: WidgetProps)
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function WidgetSkeleton() {
|
||||
return <SwapWidgetSkeleton theme={useIsDarkMode() ? DARK_THEME : LIGHT_THEME} width={WIDGET_WIDTH} />
|
||||
}
|
||||
|
||||
@@ -68,7 +68,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])
|
||||
|
||||
@@ -23,9 +23,13 @@ import { ResponsiveTooltipContainer } from './styleds'
|
||||
import SwapRoute from './SwapRoute'
|
||||
import TradePrice from './TradePrice'
|
||||
|
||||
const Wrapper = styled(Row)`
|
||||
const Wrapper = styled(Row)<{ redesignFlag: boolean }>`
|
||||
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'};
|
||||
`
|
||||
|
||||
const StyledInfoIcon = styled(Info)`
|
||||
@@ -41,14 +45,11 @@ const StyledCard = styled(OutlineCard)<{ redesignFlag: boolean }>`
|
||||
`
|
||||
|
||||
const StyledHeaderRow = styled(RowBetween)<{ disabled: boolean; open: boolean; redesignFlag: boolean }>`
|
||||
padding: ${({ redesignFlag }) => (redesignFlag ? '8px 0px 0px 0px' : '4px 8px')};
|
||||
padding: ${({ redesignFlag }) => (redesignFlag ? '0' : '4px 8px')};
|
||||
background-color: ${({ open, theme, redesignFlag }) =>
|
||||
open && !redesignFlag ? theme.deprecated_bg1 : 'transparent'};
|
||||
align-items: center;
|
||||
border-top: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundOutline : 'transparent')};
|
||||
margin-top: ${({ redesignFlag }) => redesignFlag && '8px'};
|
||||
cursor: ${({ disabled }) => (disabled ? 'initial' : 'pointer')};
|
||||
min-height: 40px;
|
||||
border-radius: ${({ redesignFlag }) => !redesignFlag && '12px'};
|
||||
`
|
||||
|
||||
@@ -133,7 +134,7 @@ export default function SwapDetailsDropdown({
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
|
||||
return (
|
||||
<Wrapper style={{ marginTop: '8px' }}>
|
||||
<Wrapper style={{ marginTop: redesignFlagEnabled ? '0' : '8px' }} redesignFlag={redesignFlagEnabled}>
|
||||
<AutoColumn gap={'8px'} style={{ width: '100%', marginBottom: '-8px' }}>
|
||||
<TraceEvent
|
||||
events={[Event.onClick]}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useCallback } from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { formatDollar, formatTransactionAmount, priceToPreciseFloat } from 'utils/formatNumbers'
|
||||
|
||||
interface TradePriceProps {
|
||||
price: Price<Currency, Currency>
|
||||
@@ -33,15 +34,12 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra
|
||||
const theme = useTheme()
|
||||
|
||||
const usdcPrice = useStablecoinPrice(showInverted ? price.baseCurrency : price.quoteCurrency)
|
||||
/*
|
||||
* calculate needed amount of decimal prices, for prices between 0.95-1.05 use 4 decimal places
|
||||
*/
|
||||
const p = Number(usdcPrice?.toFixed())
|
||||
const visibleDecimalPlaces = p < 1.05 ? 4 : 2
|
||||
|
||||
let formattedPrice: string
|
||||
try {
|
||||
formattedPrice = showInverted ? price.toSignificant(4) : price.invert()?.toSignificant(4)
|
||||
formattedPrice = showInverted
|
||||
? formatTransactionAmount(priceToPreciseFloat(price))
|
||||
: formatTransactionAmount(priceToPreciseFloat(price.invert()))
|
||||
} catch (error) {
|
||||
formattedPrice = '0'
|
||||
}
|
||||
@@ -65,7 +63,7 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra
|
||||
</Text>{' '}
|
||||
{usdcPrice && (
|
||||
<ThemedText.DeprecatedDarkGray>
|
||||
<Trans>(${usdcPrice.toFixed(visibleDecimalPlaces, { groupSeparator: ',' })})</Trans>
|
||||
<Trans>({formatDollar({ num: priceToPreciseFloat(usdcPrice) })})</Trans>
|
||||
</ThemedText.DeprecatedDarkGray>
|
||||
)}
|
||||
</StyledPriceContainer>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { AutoColumn } from '../Column'
|
||||
|
||||
export const PageWrapper = styled.div<{ redesignFlag: boolean; navBarFlag: boolean }>`
|
||||
padding: ${({ navBarFlag }) => (navBarFlag ? '68px 8px 0px' : '0px 8px')};
|
||||
max-width: ${({ redesignFlag }) => (redesignFlag ? '420px' : '480px')};
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
|
||||
@@ -30,25 +30,23 @@ export const SwapWrapper = styled.main<{ margin?: string; maxWidth?: string; red
|
||||
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundOutline : 'transparent')};
|
||||
padding: 8px;
|
||||
z-index: ${Z_INDEX.deprecated_content};
|
||||
font-feature-settings: ${({ redesignFlag }) => redesignFlag && "'ss02' off"};
|
||||
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 }>`
|
||||
padding: 4px;
|
||||
border-radius: 12px;
|
||||
height: ${({ redesignFlag }) => (redesignFlag ? '40px' : '32px')};
|
||||
width: ${({ redesignFlag }) => (redesignFlag ? '40px' : '32px')};
|
||||
position: relative;
|
||||
margin-top: -14px;
|
||||
margin-top: ${({ redesignFlag }) => (redesignFlag ? '-18px' : '-14px')};
|
||||
margin-bottom: ${({ redesignFlag }) => (redesignFlag ? '-18px' : '-14px')};
|
||||
left: calc(50% - 16px);
|
||||
/* transform: rotate(90deg); */
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundSurface : theme.deprecated_bg1)};
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundInteractive : theme.deprecated_bg1)};
|
||||
border: 4px solid;
|
||||
border-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundModule : theme.deprecated_bg0)};
|
||||
border-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundSurface : theme.deprecated_bg0)};
|
||||
|
||||
z-index: 2;
|
||||
${({ clickable }) =>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { TokenInfo } from '@uniswap/token-lists'
|
||||
|
||||
import store from '../state'
|
||||
import { UNI_EXTENDED_LIST, UNI_LIST } from './lists'
|
||||
import { UNI_EXTENDED_LIST, UNI_LIST, UNSUPPORTED_LIST_URLS } from './lists'
|
||||
import brokenTokenList from './tokenLists/broken.tokenlist.json'
|
||||
import unsupportedTokenList from './tokenLists/unsupported.tokenlist.json'
|
||||
import { NATIVE_CHAIN_ID } from './tokens'
|
||||
|
||||
export enum TOKEN_LIST_TYPES {
|
||||
UNI_DEFAULT = 1,
|
||||
@@ -16,30 +18,29 @@ class TokenSafetyLookupTable {
|
||||
|
||||
createMap() {
|
||||
const dict: { [key: string]: TOKEN_LIST_TYPES } = {}
|
||||
let uniDefaultTokens = store.getState().lists.byUrl[UNI_LIST].current?.tokens
|
||||
let uniExtendedTokens = store.getState().lists.byUrl[UNI_EXTENDED_LIST].current?.tokens
|
||||
const brokenTokens = brokenTokenList.tokens
|
||||
const unsupportTokens = unsupportedTokenList.tokens
|
||||
|
||||
if (!uniDefaultTokens) {
|
||||
uniDefaultTokens = []
|
||||
}
|
||||
if (!uniExtendedTokens) {
|
||||
uniExtendedTokens = []
|
||||
}
|
||||
brokenTokens.forEach((token) => {
|
||||
dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.BROKEN
|
||||
})
|
||||
unsupportTokens.forEach((token) => {
|
||||
dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.BLOCKED
|
||||
})
|
||||
uniExtendedTokens.forEach((token) => {
|
||||
// Initialize extended tokens first
|
||||
store.getState().lists.byUrl[UNI_EXTENDED_LIST].current?.tokens.forEach((token) => {
|
||||
dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.UNI_EXTENDED
|
||||
})
|
||||
uniDefaultTokens.forEach((token) => {
|
||||
|
||||
// Initialize default tokens second, so that any tokens on both default and extended will display as default (no warning)
|
||||
store.getState().lists.byUrl[UNI_LIST].current?.tokens.forEach((token) => {
|
||||
dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.UNI_DEFAULT
|
||||
})
|
||||
|
||||
// TODO: Figure out if this list is still relevant
|
||||
brokenTokenList.tokens.forEach((token) => {
|
||||
dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.BROKEN
|
||||
})
|
||||
|
||||
// Initialize blocked tokens from all urls included
|
||||
UNSUPPORTED_LIST_URLS.map((url) => store.getState().lists.byUrl[url].current?.tokens)
|
||||
.filter((x): x is TokenInfo[] => !!x)
|
||||
.flat(1)
|
||||
.forEach((token) => {
|
||||
dict[token.address.toLowerCase()] = TOKEN_LIST_TYPES.BLOCKED
|
||||
})
|
||||
return dict
|
||||
}
|
||||
|
||||
@@ -47,6 +48,9 @@ class TokenSafetyLookupTable {
|
||||
if (!this.dict) {
|
||||
this.dict = this.createMap()
|
||||
}
|
||||
if (address === NATIVE_CHAIN_ID.toLowerCase()) {
|
||||
return TOKEN_LIST_TYPES.UNI_DEFAULT
|
||||
}
|
||||
return this.dict[address] ?? TOKEN_LIST_TYPES.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const UNI_LIST = 'https://tokens.uniswap.org'
|
||||
export const UNI_EXTENDED_LIST = 'https://gateway.pinata.cloud/ipfs/QmaQvV3pWKKaWJcHvSBuvQMrpckV3KKtGJ6p3HZjakwFtX'
|
||||
export const UNI_EXTENDED_LIST = 'https://extendedtokens.uniswap.org/'
|
||||
const UNI_UNSUPPORTED_LISTS = 'https://unsupportedtokens.uniswap.org/'
|
||||
const AAVE_LIST = 'tokenlist.aave.eth'
|
||||
const BA_LIST = 'https://raw.githubusercontent.com/The-Blockchain-Association/sec-notice-list/master/ba-sec-list.json'
|
||||
const CMC_ALL_LIST = 'https://api.coinmarketcap.com/data-api/v3/uniswap/all.json'
|
||||
@@ -15,7 +16,7 @@ export const OPTIMISM_LIST = 'https://static.optimism.io/optimism.tokenlist.json
|
||||
export const ARBITRUM_LIST = 'https://bridge.arbitrum.io/token-list-42161.json'
|
||||
export const CELO_LIST = 'https://celo-org.github.io/celo-token-list/celo.tokenlist.json'
|
||||
|
||||
export const UNSUPPORTED_LIST_URLS: string[] = [BA_LIST]
|
||||
export const UNSUPPORTED_LIST_URLS: string[] = [BA_LIST, UNI_UNSUPPORTED_LISTS]
|
||||
|
||||
// this is the default list of lists that are exposed to users
|
||||
// lower index == higher priority for token import
|
||||
|
||||
@@ -4,6 +4,8 @@ import invariant from 'tiny-invariant'
|
||||
import { UNI_ADDRESS } from './addresses'
|
||||
import { SupportedChainId } from './chains'
|
||||
|
||||
export const NATIVE_CHAIN_ID = 'NATIVE'
|
||||
|
||||
export const USDC_MAINNET = new Token(
|
||||
SupportedChainId.MAINNET,
|
||||
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
||||
@@ -388,6 +390,20 @@ export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token | undefined } =
|
||||
'WMATIC',
|
||||
'Wrapped MATIC'
|
||||
),
|
||||
[SupportedChainId.CELO]: new Token(
|
||||
SupportedChainId.CELO,
|
||||
'0x471ece3750da237f93b8e339c536989b8978a438',
|
||||
18,
|
||||
'CELO',
|
||||
'Celo native asset'
|
||||
),
|
||||
[SupportedChainId.CELO_ALFAJORES]: new Token(
|
||||
SupportedChainId.CELO_ALFAJORES,
|
||||
'0xf194afdf50b03e69bd7d057c1aa9e10c9954e4c9',
|
||||
18,
|
||||
'CELO',
|
||||
'Celo native asset'
|
||||
),
|
||||
}
|
||||
|
||||
export function isCelo(chainId: number): chainId is SupportedChainId.CELO | SupportedChainId.CELO_ALFAJORES {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
export enum FeatureFlag {
|
||||
favoriteTokens = 'favoriteTokens',
|
||||
navBar = 'navBar',
|
||||
nft = 'nfts',
|
||||
redesign = 'redesign',
|
||||
tokens = 'tokens',
|
||||
tokenSafety = 'tokenSafety',
|
||||
traceJsonRpc = 'traceJsonRpc',
|
||||
multiNetworkBalances = 'multiNetworkBalances',
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
import { BaseVariant } from '../index'
|
||||
|
||||
export function useNavBarFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.navBar)
|
||||
return BaseVariant.Enabled
|
||||
}
|
||||
|
||||
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 +1,7 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
import { BaseVariant } from '../index'
|
||||
|
||||
export function useTokenSafetyFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.tokenSafety)
|
||||
return BaseVariant.Enabled
|
||||
}
|
||||
|
||||
export { BaseVariant as TokenSafetyVariant }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
import { BaseVariant } from '../index'
|
||||
|
||||
export function useTokensFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.tokens)
|
||||
return BaseVariant.Enabled
|
||||
}
|
||||
|
||||
export { BaseVariant as TokensVariant }
|
||||
|
||||
@@ -1,32 +1,12 @@
|
||||
import graphql from 'babel-plugin-relay/macro'
|
||||
import { filterTimeAtom } from 'components/Tokens/state'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { fetchQuery, useFragment, useLazyLoadQuery, useRelayEnvironment } from 'react-relay'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { fetchQuery, useLazyLoadQuery } from 'react-relay'
|
||||
|
||||
import { Chain, TokenPriceQuery } from './__generated__/TokenPriceQuery.graphql'
|
||||
import { TokenPrices$data, TokenPrices$key } from './__generated__/TokenPrices.graphql'
|
||||
import { TokenQuery, TokenQuery$data } from './__generated__/TokenQuery.graphql'
|
||||
import { ContractInput, HistoryDuration, TokenQuery, TokenQuery$data } from './__generated__/TokenQuery.graphql'
|
||||
import environment from './RelayEnvironment'
|
||||
import { TimePeriod, toHistoryDuration } from './util'
|
||||
|
||||
export type PricePoint = { value: number; timestamp: number }
|
||||
|
||||
export const projectMetaDataFragment = graphql`
|
||||
fragment Token_TokenProject_Metadata on TokenProject {
|
||||
description
|
||||
homepageUrl
|
||||
twitterName
|
||||
name
|
||||
}
|
||||
`
|
||||
const tokenPricesFragment = graphql`
|
||||
fragment TokenPrices on TokenProjectMarket {
|
||||
priceHistory(duration: $duration) {
|
||||
timestamp
|
||||
value
|
||||
}
|
||||
}
|
||||
`
|
||||
/*
|
||||
The difference between Token and TokenProject:
|
||||
Token: an on-chain entity referring to a contract (e.g. uni token on ethereum 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984)
|
||||
@@ -60,6 +40,12 @@ const tokenQuery = graphql`
|
||||
value
|
||||
currency
|
||||
}
|
||||
priceHigh52W: priceHighLow(duration: YEAR, highLow: HIGH) {
|
||||
value
|
||||
}
|
||||
priceLow52W: priceHighLow(duration: YEAR, highLow: LOW) {
|
||||
value
|
||||
}
|
||||
}
|
||||
project {
|
||||
description
|
||||
@@ -75,13 +61,79 @@ const tokenQuery = graphql`
|
||||
}
|
||||
`
|
||||
|
||||
export function useTokenQuery(address: string, chain: Chain, timePeriod: TimePeriod) {
|
||||
const data = useLazyLoadQuery<TokenQuery>(tokenQuery, {
|
||||
contract: { address: address.toLowerCase(), chain },
|
||||
duration: toHistoryDuration(timePeriod),
|
||||
export type PricePoint = { value: number; timestamp: number }
|
||||
export function filterPrices(prices: NonNullable<NonNullable<SingleTokenData>['market']>['priceHistory'] | undefined) {
|
||||
return prices?.filter((p): p is PricePoint => Boolean(p && p.value))
|
||||
}
|
||||
|
||||
export type PriceDurations = Record<TimePeriod, PricePoint[] | undefined>
|
||||
function fetchAllPriceDurations(contract: ContractInput, originalDuration: HistoryDuration) {
|
||||
return fetchQuery<TokenPriceQuery>(environment, tokenPriceQuery, {
|
||||
contract,
|
||||
skip1H: originalDuration === 'HOUR',
|
||||
skip1D: originalDuration === 'DAY',
|
||||
skip1W: originalDuration === 'WEEK',
|
||||
skip1M: originalDuration === 'MONTH',
|
||||
skip1Y: originalDuration === 'YEAR',
|
||||
})
|
||||
}
|
||||
|
||||
export type SingleTokenData = NonNullable<TokenQuery$data['tokens']>[number]
|
||||
export function useTokenQuery(
|
||||
address: string,
|
||||
chain: Chain,
|
||||
timePeriod: TimePeriod
|
||||
): [SingleTokenData | undefined, PriceDurations] {
|
||||
const [prices, setPrices] = useState<PriceDurations>({
|
||||
[TimePeriod.HOUR]: undefined,
|
||||
[TimePeriod.DAY]: undefined,
|
||||
[TimePeriod.WEEK]: undefined,
|
||||
[TimePeriod.MONTH]: undefined,
|
||||
[TimePeriod.YEAR]: undefined,
|
||||
})
|
||||
|
||||
return data
|
||||
const contract = useMemo(() => {
|
||||
return { address: address.toLowerCase(), chain }
|
||||
}, [address, chain])
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const originalTimePeriod = useMemo(() => timePeriod, [contract])
|
||||
|
||||
const updatePrices = (response: TokenPriceQuery['response']) => {
|
||||
const priceData = response.tokens?.[0]?.market
|
||||
if (priceData) {
|
||||
setPrices((current) => {
|
||||
return {
|
||||
[TimePeriod.HOUR]: filterPrices(priceData.priceHistory1H) ?? current[TimePeriod.HOUR],
|
||||
[TimePeriod.DAY]: filterPrices(priceData.priceHistory1D) ?? current[TimePeriod.DAY],
|
||||
[TimePeriod.WEEK]: filterPrices(priceData.priceHistory1W) ?? current[TimePeriod.WEEK],
|
||||
[TimePeriod.MONTH]: filterPrices(priceData.priceHistory1M) ?? current[TimePeriod.MONTH],
|
||||
[TimePeriod.YEAR]: filterPrices(priceData.priceHistory1Y) ?? current[TimePeriod.YEAR],
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch prices & token info in tandem so we can render faster
|
||||
useMemo(
|
||||
() => fetchAllPriceDurations(contract, toHistoryDuration(originalTimePeriod)).subscribe({ next: updatePrices }),
|
||||
[contract, originalTimePeriod]
|
||||
)
|
||||
const token = useLazyLoadQuery<TokenQuery>(tokenQuery, {
|
||||
contract,
|
||||
duration: toHistoryDuration(originalTimePeriod),
|
||||
}).tokens?.[0]
|
||||
|
||||
useMemo(
|
||||
() =>
|
||||
setPrices((current) => {
|
||||
current[originalTimePeriod] = filterPrices(token?.market?.priceHistory)
|
||||
return current
|
||||
}),
|
||||
[token, originalTimePeriod]
|
||||
)
|
||||
|
||||
return [token, prices]
|
||||
}
|
||||
|
||||
const tokenPriceQuery = graphql`
|
||||
@@ -92,7 +144,6 @@ const tokenPriceQuery = graphql`
|
||||
$skip1W: Boolean!
|
||||
$skip1M: Boolean!
|
||||
$skip1Y: Boolean!
|
||||
$skipMax: Boolean!
|
||||
) {
|
||||
tokens(contracts: [$contract]) {
|
||||
market(currency: USD) {
|
||||
@@ -116,70 +167,7 @@ const tokenPriceQuery = graphql`
|
||||
timestamp
|
||||
value
|
||||
}
|
||||
priceHistoryMAX: priceHistory(duration: MAX) @skip(if: $skipMax) {
|
||||
timestamp
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export function filterPrices(prices: TokenPrices$data['priceHistory'] | undefined) {
|
||||
return prices?.filter((p): p is PricePoint => Boolean(p && p.value))
|
||||
}
|
||||
|
||||
export function useTokenPricesFromFragment(key: TokenPrices$key | null | undefined) {
|
||||
const fetchedTokenPrices = useFragment(tokenPricesFragment, key ?? null)?.priceHistory
|
||||
return filterPrices(fetchedTokenPrices)
|
||||
}
|
||||
|
||||
export function useTokenPricesCached(token: SingleTokenData) {
|
||||
// Attempt to use token prices already provided by TokenDetails / TopToken queries
|
||||
const environment = useRelayEnvironment()
|
||||
const timePeriod = useAtomValue(filterTimeAtom)
|
||||
|
||||
const [priceMap, setPriceMap] = useState<Map<TimePeriod, PricePoint[] | undefined>>(new Map())
|
||||
|
||||
const updatePrices = useCallback(
|
||||
(key: TimePeriod, data?: PricePoint[]) => {
|
||||
setPriceMap(new Map(priceMap.set(key, data)))
|
||||
},
|
||||
[priceMap]
|
||||
)
|
||||
|
||||
// Fetch the other timePeriods after first render
|
||||
useEffect(() => {
|
||||
const fetchedTokenPrices = token?.market?.priceHistory
|
||||
updatePrices(timePeriod, filterPrices(fetchedTokenPrices))
|
||||
// Fetch all time periods except the one already populated
|
||||
if (token?.chain && token?.address) {
|
||||
fetchQuery<TokenPriceQuery>(environment, tokenPriceQuery, {
|
||||
contract: { address: token.address, chain: token.chain },
|
||||
skip1H: timePeriod === TimePeriod.HOUR && !!fetchedTokenPrices,
|
||||
skip1D: timePeriod === TimePeriod.DAY && !!fetchedTokenPrices,
|
||||
skip1W: timePeriod === TimePeriod.WEEK && !!fetchedTokenPrices,
|
||||
skip1M: timePeriod === TimePeriod.MONTH && !!fetchedTokenPrices,
|
||||
skip1Y: timePeriod === TimePeriod.YEAR && !!fetchedTokenPrices,
|
||||
skipMax: timePeriod === TimePeriod.ALL && !!fetchedTokenPrices,
|
||||
}).subscribe({
|
||||
next: (data) => {
|
||||
const market = data.tokens?.[0]?.market
|
||||
if (market) {
|
||||
market.priceHistory1H && updatePrices(TimePeriod.HOUR, filterPrices(market.priceHistory1H))
|
||||
market.priceHistory1D && updatePrices(TimePeriod.DAY, filterPrices(market.priceHistory1D))
|
||||
market.priceHistory1W && updatePrices(TimePeriod.WEEK, filterPrices(market.priceHistory1W))
|
||||
market.priceHistory1M && updatePrices(TimePeriod.MONTH, filterPrices(market.priceHistory1M))
|
||||
market.priceHistory1Y && updatePrices(TimePeriod.YEAR, filterPrices(market.priceHistory1Y))
|
||||
market.priceHistoryMAX && updatePrices(TimePeriod.ALL, filterPrices(market.priceHistoryMAX))
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [token?.chain, token?.address])
|
||||
|
||||
return { prices: priceMap.get(timePeriod) }
|
||||
}
|
||||
|
||||
export type SingleTokenData = NonNullable<TokenQuery$data['tokens']>[number]
|
||||
|
||||
@@ -6,23 +6,16 @@ import {
|
||||
showFavoritesAtom,
|
||||
sortAscendingAtom,
|
||||
sortMethodAtom,
|
||||
TokenSortMethod,
|
||||
} from 'components/Tokens/state'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { fetchQuery, useLazyLoadQuery, useRelayEnvironment } from 'react-relay'
|
||||
|
||||
import {
|
||||
Chain,
|
||||
ContractInput,
|
||||
HistoryDuration,
|
||||
TopTokens_TokensQuery,
|
||||
} from './__generated__/TopTokens_TokensQuery.graphql'
|
||||
import type { TopTokens100Query } from './__generated__/TopTokens100Query.graphql'
|
||||
import { toHistoryDuration } from './util'
|
||||
|
||||
export function usePrefetchTopTokens(duration: HistoryDuration, chain: Chain) {
|
||||
return useLazyLoadQuery<TopTokens100Query>(topTokens100Query, { duration, chain })
|
||||
}
|
||||
import type { Chain, TopTokens100Query } from './__generated__/TopTokens100Query.graphql'
|
||||
import { TopTokensSparklineQuery } from './__generated__/TopTokensSparklineQuery.graphql'
|
||||
import { filterPrices, PricePoint } from './Token'
|
||||
import { CHAIN_NAME_TO_CHAIN_ID, toHistoryDuration, unwrapToken } from './util'
|
||||
|
||||
const topTokens100Query = graphql`
|
||||
query TopTokens100Query($duration: HistoryDuration!, $chain: Chain!) {
|
||||
@@ -50,26 +43,34 @@ const topTokens100Query = graphql`
|
||||
currency
|
||||
}
|
||||
}
|
||||
project {
|
||||
logoUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export enum TokenSortMethod {
|
||||
PRICE = 'Price',
|
||||
PERCENT_CHANGE = 'Change',
|
||||
TOTAL_VALUE_LOCKED = 'TVL',
|
||||
VOLUME = 'Volume',
|
||||
}
|
||||
const tokenSparklineQuery = graphql`
|
||||
query TopTokensSparklineQuery($duration: HistoryDuration!, $chain: Chain!) {
|
||||
topTokens(pageSize: 100, page: 1, chain: $chain) {
|
||||
address
|
||||
market(currency: USD) {
|
||||
priceHistory(duration: $duration) {
|
||||
timestamp
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export type PrefetchedTopToken = NonNullable<TopTokens100Query['response']['topTokens']>[number]
|
||||
|
||||
function useSortedTokens(tokens: TopTokens100Query['response']['topTokens']) {
|
||||
function useSortedTokens(tokens: NonNullable<TopTokens100Query['response']['topTokens']>) {
|
||||
const sortMethod = useAtomValue(sortMethodAtom)
|
||||
const sortAscending = useAtomValue(sortAscendingAtom)
|
||||
|
||||
return useMemo(() => {
|
||||
if (!tokens) return []
|
||||
|
||||
let tokenArray = Array.from(tokens)
|
||||
switch (sortMethod) {
|
||||
case TokenSortMethod.PRICE:
|
||||
@@ -94,7 +95,7 @@ function useSortedTokens(tokens: TopTokens100Query['response']['topTokens']) {
|
||||
}, [tokens, sortMethod, sortAscending])
|
||||
}
|
||||
|
||||
function useFilteredTokens(tokens: PrefetchedTopToken[]) {
|
||||
function useFilteredTokens(tokens: NonNullable<TopTokens100Query['response']['topTokens']>) {
|
||||
const filterString = useAtomValue(filterStringAtom)
|
||||
const favorites = useAtomValue(favoritesAtom)
|
||||
const showFavorites = useAtomValue(showFavoritesAtom)
|
||||
@@ -102,10 +103,6 @@ function useFilteredTokens(tokens: PrefetchedTopToken[]) {
|
||||
const lowercaseFilterString = useMemo(() => filterString.toLowerCase(), [filterString])
|
||||
|
||||
return useMemo(() => {
|
||||
if (!tokens) {
|
||||
return []
|
||||
}
|
||||
|
||||
let returnTokens = tokens
|
||||
if (showFavorites) {
|
||||
returnTokens = returnTokens?.filter((token) => token?.address && favorites.includes(token.address))
|
||||
@@ -125,162 +122,43 @@ function useFilteredTokens(tokens: PrefetchedTopToken[]) {
|
||||
// Number of items to render in each fetch in infinite scroll.
|
||||
export const PAGE_SIZE = 20
|
||||
|
||||
function toContractInput(token: PrefetchedTopToken) {
|
||||
return {
|
||||
address: token?.address ?? '',
|
||||
chain: token?.chain ?? 'ETHEREUM',
|
||||
}
|
||||
}
|
||||
|
||||
// Map of key: ${chain} + ${address} and value: TopToken object.
|
||||
// Acts as a local cache.
|
||||
const tokensWithPriceHistoryCache: Record<string, TopToken> = {}
|
||||
|
||||
const checkIfAllTokensCached = (tokens: PrefetchedTopToken[]) => {
|
||||
let everyTokenInCache = true
|
||||
const cachedTokens: TopToken[] = []
|
||||
|
||||
const checkCache = (token: PrefetchedTopToken) => {
|
||||
const tokenCacheKey = !!token ? `${token.chain}${token.address}` : ''
|
||||
if (tokenCacheKey in tokensWithPriceHistoryCache) {
|
||||
cachedTokens.push(tokensWithPriceHistoryCache[tokenCacheKey])
|
||||
return true
|
||||
} else {
|
||||
everyTokenInCache = false
|
||||
cachedTokens.length = 0
|
||||
return false
|
||||
}
|
||||
}
|
||||
tokens.every((token) => checkCache(token))
|
||||
return { everyTokenInCache, cachedTokens }
|
||||
}
|
||||
|
||||
export type TopToken = NonNullable<TopTokens_TokensQuery['response']['tokens']>[number]
|
||||
export type TopToken = NonNullable<NonNullable<TopTokens100Query['response']>['topTokens']>[number]
|
||||
export type SparklineMap = { [key: string]: PricePoint[] | undefined }
|
||||
interface UseTopTokensReturnValue {
|
||||
loading: boolean
|
||||
tokens: TopToken[] | undefined
|
||||
tokensWithoutPriceHistoryCount: number
|
||||
hasMore: boolean
|
||||
loadMoreTokens: () => void
|
||||
maxFetchable: number
|
||||
sparklines: SparklineMap
|
||||
}
|
||||
export function useTopTokens(chain: Chain): UseTopTokensReturnValue {
|
||||
const duration = toHistoryDuration(useAtomValue(filterTimeAtom))
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [tokens, setTokens] = useState<TopToken[]>()
|
||||
const [page, setPage] = useState(0)
|
||||
const prefetchedData = usePrefetchTopTokens(duration, chain)
|
||||
const prefetchedSelectedTokensWithoutPriceHistory = useFilteredTokens(useSortedTokens(prefetchedData.topTokens))
|
||||
const maxFetchable = useMemo(
|
||||
() => prefetchedSelectedTokensWithoutPriceHistory.length,
|
||||
[prefetchedSelectedTokensWithoutPriceHistory]
|
||||
)
|
||||
|
||||
const hasMore = !tokens || tokens.length < prefetchedSelectedTokensWithoutPriceHistory.length
|
||||
export function useTopTokens(chain: Chain): UseTopTokensReturnValue {
|
||||
const chainId = CHAIN_NAME_TO_CHAIN_ID[chain]
|
||||
const duration = toHistoryDuration(useAtomValue(filterTimeAtom))
|
||||
|
||||
const environment = useRelayEnvironment()
|
||||
const [sparklines, setSparklines] = useState<SparklineMap>({})
|
||||
useEffect(() => {
|
||||
const subscription = fetchQuery<TopTokensSparklineQuery>(environment, tokenSparklineQuery, { duration, chain })
|
||||
.map((data) => ({
|
||||
topTokens: data.topTokens?.map((token) => unwrapToken(chainId, token)),
|
||||
}))
|
||||
.subscribe({
|
||||
next(data) {
|
||||
const map: SparklineMap = {}
|
||||
data.topTokens?.forEach(
|
||||
(current) => current?.address && (map[current.address] = filterPrices(current?.market?.priceHistory))
|
||||
)
|
||||
setSparklines(map)
|
||||
},
|
||||
})
|
||||
return () => subscription.unsubscribe()
|
||||
}, [chain, chainId, duration, environment])
|
||||
|
||||
// TopTokens should ideally be fetched with usePaginationFragment. The backend does not current support graphql cursors;
|
||||
// in the meantime, fetchQuery is used, as other relay hooks do not allow the refreshing and lazy loading we need
|
||||
const loadTokensWithPriceHistory = useCallback(
|
||||
({
|
||||
contracts,
|
||||
appendingTokens,
|
||||
page,
|
||||
tokens,
|
||||
}: {
|
||||
contracts: ContractInput[]
|
||||
appendingTokens: boolean
|
||||
page: number
|
||||
tokens?: TopToken[]
|
||||
}) => {
|
||||
fetchQuery<TopTokens_TokensQuery>(
|
||||
environment,
|
||||
tokensQuery,
|
||||
{ contracts, duration },
|
||||
{ fetchPolicy: 'store-or-network' }
|
||||
)
|
||||
.toPromise()
|
||||
.then((data) => {
|
||||
if (data?.tokens) {
|
||||
data.tokens.map((token) =>
|
||||
!!token ? (tokensWithPriceHistoryCache[`${token.chain}${token.address}`] = token) : null
|
||||
)
|
||||
appendingTokens ? setTokens([...(tokens ?? []), ...data.tokens]) : setTokens([...data.tokens])
|
||||
setLoading(false)
|
||||
setPage(page + 1)
|
||||
}
|
||||
})
|
||||
},
|
||||
[duration, environment]
|
||||
)
|
||||
useEffect(() => {
|
||||
setSparklines({})
|
||||
}, [duration])
|
||||
|
||||
const loadMoreTokens = useCallback(() => {
|
||||
setLoading(true)
|
||||
const contracts = prefetchedSelectedTokensWithoutPriceHistory
|
||||
.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE)
|
||||
.map(toContractInput)
|
||||
loadTokensWithPriceHistory({ contracts, appendingTokens: true, page, tokens })
|
||||
}, [prefetchedSelectedTokensWithoutPriceHistory, page, loadTokensWithPriceHistory, tokens])
|
||||
|
||||
// Reset count when filters are changed
|
||||
useLayoutEffect(() => {
|
||||
const { everyTokenInCache, cachedTokens } = checkIfAllTokensCached(prefetchedSelectedTokensWithoutPriceHistory)
|
||||
if (everyTokenInCache) {
|
||||
setTokens(cachedTokens)
|
||||
setLoading(false)
|
||||
return
|
||||
} else {
|
||||
setLoading(true)
|
||||
setTokens([])
|
||||
const contracts = prefetchedSelectedTokensWithoutPriceHistory.slice(0, PAGE_SIZE).map(toContractInput)
|
||||
loadTokensWithPriceHistory({ contracts, appendingTokens: false, page: 0 })
|
||||
}
|
||||
}, [loadTokensWithPriceHistory, prefetchedSelectedTokensWithoutPriceHistory])
|
||||
|
||||
return {
|
||||
loading,
|
||||
tokens,
|
||||
hasMore,
|
||||
tokensWithoutPriceHistoryCount: prefetchedSelectedTokensWithoutPriceHistory.length,
|
||||
loadMoreTokens,
|
||||
maxFetchable,
|
||||
}
|
||||
const { topTokens } = useLazyLoadQuery<TopTokens100Query>(topTokens100Query, { duration, chain })
|
||||
const mappedTokens = useMemo(() => topTokens?.map((token) => unwrapToken(chainId, token)) ?? [], [chainId, topTokens])
|
||||
const filteredTokens = useFilteredTokens(mappedTokens)
|
||||
const sortedTokens = useSortedTokens(filteredTokens)
|
||||
return useMemo(() => ({ tokens: sortedTokens, sparklines }), [sortedTokens, sparklines])
|
||||
}
|
||||
|
||||
export const tokensQuery = graphql`
|
||||
query TopTokens_TokensQuery($contracts: [ContractInput!]!, $duration: HistoryDuration!) {
|
||||
tokens(contracts: $contracts) {
|
||||
id @required(action: LOG)
|
||||
name
|
||||
chain @required(action: LOG)
|
||||
address @required(action: LOG)
|
||||
symbol
|
||||
market(currency: USD) {
|
||||
totalValueLocked {
|
||||
value
|
||||
currency
|
||||
}
|
||||
priceHistory(duration: $duration) {
|
||||
timestamp
|
||||
value
|
||||
}
|
||||
price {
|
||||
value
|
||||
currency
|
||||
}
|
||||
volume(duration: $duration) {
|
||||
value
|
||||
currency
|
||||
}
|
||||
pricePercentChange(duration: $duration) {
|
||||
currency
|
||||
value
|
||||
}
|
||||
}
|
||||
project {
|
||||
logoUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import { ZERO_ADDRESS } from 'constants/misc'
|
||||
import { NATIVE_CHAIN_ID, nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
|
||||
|
||||
import { Chain, HistoryDuration } from './__generated__/TokenQuery.graphql'
|
||||
|
||||
@@ -9,7 +10,6 @@ export enum TimePeriod {
|
||||
WEEK,
|
||||
MONTH,
|
||||
YEAR,
|
||||
ALL,
|
||||
}
|
||||
|
||||
export function toHistoryDuration(timePeriod: TimePeriod): HistoryDuration {
|
||||
@@ -24,8 +24,6 @@ export function toHistoryDuration(timePeriod: TimePeriod): HistoryDuration {
|
||||
return 'MONTH'
|
||||
case TimePeriod.YEAR:
|
||||
return 'YEAR'
|
||||
case TimePeriod.ALL:
|
||||
return 'MAX'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,9 +40,10 @@ export const CHAIN_ID_TO_BACKEND_NAME: { [key: number]: Chain } = {
|
||||
[SupportedChainId.OPTIMISM_GOERLI]: 'OPTIMISM',
|
||||
}
|
||||
|
||||
export function useGlobalChainName() {
|
||||
const chainId = useAppSelector((state) => state.application.chainId)
|
||||
return chainId && CHAIN_ID_TO_BACKEND_NAME[chainId] ? CHAIN_ID_TO_BACKEND_NAME[chainId] : 'ETHEREUM'
|
||||
export function chainIdToBackendName(chainId: number | undefined) {
|
||||
return chainId && CHAIN_ID_TO_BACKEND_NAME[chainId]
|
||||
? CHAIN_ID_TO_BACKEND_NAME[chainId]
|
||||
: CHAIN_ID_TO_BACKEND_NAME[SupportedChainId.MAINNET]
|
||||
}
|
||||
|
||||
export const URL_CHAIN_PARAM_TO_BACKEND: { [key: string]: Chain } = {
|
||||
@@ -78,7 +77,9 @@ export function isValidBackendChainName(chainName: string | undefined): chainNam
|
||||
}
|
||||
|
||||
export function getTokenDetailsURL(address: string, chainName?: Chain, chainId?: number) {
|
||||
if (chainName) {
|
||||
if (address === ZERO_ADDRESS && chainId && chainId === SupportedChainId.MAINNET) {
|
||||
return `/tokens/${CHAIN_ID_TO_BACKEND_NAME[chainId].toLowerCase()}/${NATIVE_CHAIN_ID}`
|
||||
} else if (chainName) {
|
||||
return `/tokens/${chainName.toLowerCase()}/${address}`
|
||||
} else if (chainId) {
|
||||
const chainName = CHAIN_ID_TO_BACKEND_NAME[chainId]
|
||||
@@ -87,3 +88,14 @@ export function getTokenDetailsURL(address: string, chainName?: Chain, chainId?:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export function unwrapToken<T extends { address: string | null } | null>(chainId: number, token: T): T {
|
||||
if (!token?.address) return token
|
||||
|
||||
const address = token.address.toLowerCase()
|
||||
const nativeAddress = WRAPPED_NATIVE_CURRENCY[chainId]?.address.toLowerCase()
|
||||
if (address !== nativeAddress) return token
|
||||
|
||||
const nativeToken = nativeOnChain(chainId)
|
||||
return { ...token, ...nativeToken, address: NATIVE_CHAIN_ID }
|
||||
}
|
||||
|
||||
@@ -9,16 +9,22 @@ import { useAppDispatch } from 'state/hooks'
|
||||
|
||||
import { fetchTokenList } from '../state/lists/actions'
|
||||
|
||||
export function useFetchListCallback(): (listUrl: string, sendDispatch?: boolean) => Promise<TokenList> {
|
||||
export function useFetchListCallback(): (
|
||||
listUrl: string,
|
||||
sendDispatch?: boolean,
|
||||
skipValidation?: boolean
|
||||
) => Promise<TokenList> {
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
// note: prevent dispatch if using for list search or unsupported list
|
||||
return useCallback(
|
||||
async (listUrl: string, sendDispatch = true) => {
|
||||
async (listUrl: string, sendDispatch = true, skipValidation?: boolean) => {
|
||||
const requestId = nanoid()
|
||||
sendDispatch && dispatch(fetchTokenList.pending({ requestId, url: listUrl }))
|
||||
return getTokenList(listUrl, (ensName: string) =>
|
||||
resolveENSContentHash(ensName, RPC_PROVIDERS[SupportedChainId.MAINNET])
|
||||
return getTokenList(
|
||||
listUrl,
|
||||
(ensName: string) => resolveENSContentHash(ensName, RPC_PROVIDERS[SupportedChainId.MAINNET]),
|
||||
skipValidation
|
||||
)
|
||||
.then((tokenList) => {
|
||||
sendDispatch && dispatch(fetchTokenList.fulfilled({ url: listUrl, tokenList, requestId }))
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { Chain } from 'graphql/data/__generated__/TokenQuery.graphql'
|
||||
import { useGlobalChainName } from 'graphql/data/util'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
export const useOnGlobalChainSwitch = (callback: (chain: Chain) => void) => {
|
||||
const globalChainName = useGlobalChainName()
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
const globalChainName = chainIdToBackendName(connectedChainId)
|
||||
const prevGlobalChainRef = useRef(globalChainName)
|
||||
useEffect(() => {
|
||||
if (prevGlobalChainRef.current !== globalChainName) {
|
||||
|
||||
123
src/hooks/useMultiNetworkAddressBalances.ts
Normal file
123
src/hooks/useMultiNetworkAddressBalances.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { Currency, CurrencyAmount, NativeCurrency, Token } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { Weth } from 'abis/types'
|
||||
import WETH_ABI from 'abis/weth.json'
|
||||
import { ALL_SUPPORTED_CHAIN_IDS, isSupportedChain, SupportedChainId, TESTNET_CHAIN_IDS } from 'constants/chains'
|
||||
import { RPC_PROVIDERS } from 'constants/providers'
|
||||
import { NATIVE_CHAIN_ID, nativeOnChain, WRAPPED_NATIVE_CURRENCY } from 'constants/tokens'
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from 'featureFlags'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { getContract } from 'utils'
|
||||
|
||||
interface useMultiNetworkAddressBalancesArgs {
|
||||
ownerAddress: string | undefined
|
||||
tokenAddress: 'NATIVE' | string | undefined
|
||||
}
|
||||
|
||||
type AddressNetworkBalanceData = Partial<
|
||||
Record<
|
||||
SupportedChainId,
|
||||
Record<string | 'NATIVE', CurrencyAmount<Token> | CurrencyAmount<NativeCurrency> | undefined>
|
||||
>
|
||||
>
|
||||
|
||||
interface handleBalanceArg {
|
||||
amount: CurrencyAmount<Currency>
|
||||
chainId: SupportedChainId
|
||||
tokenAddress: string | 'NATIVE'
|
||||
}
|
||||
|
||||
const testnetSet = new Set(TESTNET_CHAIN_IDS) as Set<SupportedChainId>
|
||||
|
||||
export function useMultiNetworkAddressBalances({ ownerAddress, tokenAddress }: useMultiNetworkAddressBalancesArgs) {
|
||||
const [data, setData] = useState<AddressNetworkBalanceData>({})
|
||||
const [error] = useState<string | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
const feature_flag_multi_network_balances = useBaseFlag(FeatureFlag.multiNetworkBalances)
|
||||
|
||||
const handleBalance = useCallback(({ amount, chainId, tokenAddress }: handleBalanceArg) => {
|
||||
if (!amount.greaterThan(0) || !tokenAddress) {
|
||||
return
|
||||
}
|
||||
setData((data) => ({
|
||||
...data,
|
||||
[chainId]: {
|
||||
...(data[chainId] ?? {}),
|
||||
[tokenAddress]: amount,
|
||||
},
|
||||
}))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!ownerAddress || !tokenAddress) {
|
||||
return
|
||||
}
|
||||
const isConnecteToTestnet = connectedChainId ? TESTNET_CHAIN_IDS.includes(connectedChainId) : false
|
||||
setLoading(true)
|
||||
const isNative = tokenAddress === NATIVE_CHAIN_ID
|
||||
const promises: Promise<any>[] = []
|
||||
|
||||
const isWrappedNative = ALL_SUPPORTED_CHAIN_IDS.some(
|
||||
(chainId) => WRAPPED_NATIVE_CURRENCY[chainId]?.address.toLowerCase() === tokenAddress.toLowerCase()
|
||||
)
|
||||
|
||||
const chainsToCheck: SupportedChainId[] =
|
||||
feature_flag_multi_network_balances === BaseVariant.Enabled
|
||||
? ALL_SUPPORTED_CHAIN_IDS
|
||||
: isSupportedChain(connectedChainId)
|
||||
? [SupportedChainId.MAINNET, connectedChainId]
|
||||
: [SupportedChainId.MAINNET]
|
||||
|
||||
chainsToCheck.forEach((chainId) => {
|
||||
const isTestnet = testnetSet.has(chainId)
|
||||
if ((isConnecteToTestnet && isTestnet) || !isTestnet) {
|
||||
const provider = RPC_PROVIDERS[chainId]
|
||||
if (isWrappedNative || isNative) {
|
||||
const wrappedNative = WRAPPED_NATIVE_CURRENCY[chainId]
|
||||
if (wrappedNative) {
|
||||
promises.push(
|
||||
new Promise(async (resolve) => {
|
||||
try {
|
||||
const wrappedNativeContract = getContract(wrappedNative.address, WETH_ABI, provider) as Weth
|
||||
const balance = await wrappedNativeContract.balanceOf(ownerAddress, { blockTag: 'latest' })
|
||||
const amount = CurrencyAmount.fromRawAmount(wrappedNative, balance.toString())
|
||||
resolve(handleBalance({ amount, chainId, tokenAddress: wrappedNative.address.toLowerCase() }))
|
||||
} catch (e) {}
|
||||
})
|
||||
)
|
||||
}
|
||||
promises.push(
|
||||
new Promise(async (resolve) => {
|
||||
try {
|
||||
const balance = await provider.getBalance(ownerAddress, 'latest')
|
||||
const nativeCurrency = nativeOnChain(chainId)
|
||||
const amount = CurrencyAmount.fromRawAmount(nativeCurrency, balance.toString())
|
||||
resolve(handleBalance({ amount, chainId, tokenAddress: 'NATIVE' }))
|
||||
} catch (e) {}
|
||||
})
|
||||
)
|
||||
// todo (jordan): support multi-network ERC20 balances
|
||||
// } else {
|
||||
// promises.push(
|
||||
// new Promise(async (resolve) => {
|
||||
// try {
|
||||
// const ERC20Contract = getContract(tokenAddress, ERC20_ABI, provider) as Erc20
|
||||
// const balance = await ERC20Contract.balanceOf(ownerAddress, { blockTag: 'latest' })
|
||||
// const amount = //
|
||||
// resolve(handleBalance({ amount, chainId, tokenAddress }))
|
||||
// } catch (e) {}
|
||||
// })
|
||||
// )
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Promise.all(promises)
|
||||
.catch(() => ({}))
|
||||
.finally(() => setLoading(false))
|
||||
}, [connectedChainId, feature_flag_multi_network_balances, handleBalance, ownerAddress, tokenAddress])
|
||||
|
||||
return { data, error, loading }
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { gql } from 'graphql-request'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
type NetworkTokenBalancesMap = Partial<Record<SupportedChainId, CurrencyAmount<Token>>>
|
||||
|
||||
interface useNetworkTokenBalancesResult {
|
||||
data: NetworkTokenBalancesMap | null
|
||||
error: null | string
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
interface useNetworkTokenBalancesArgs {
|
||||
address: string | undefined
|
||||
}
|
||||
|
||||
export function useNetworkTokenBalances({ address }: useNetworkTokenBalancesArgs): useNetworkTokenBalancesResult {
|
||||
const [data, setData] = useState<NetworkTokenBalancesMap | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const query = gql``
|
||||
|
||||
useEffect(() => {
|
||||
if (address) {
|
||||
const FAKE_TOKEN_NETWORK_BALANCES = {
|
||||
[SupportedChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(
|
||||
new Token(SupportedChainId.ARBITRUM_ONE, address, 18),
|
||||
10e18
|
||||
),
|
||||
[SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(
|
||||
new Token(SupportedChainId.MAINNET, address, 18),
|
||||
1e18
|
||||
),
|
||||
[SupportedChainId.RINKEBY]: CurrencyAmount.fromRawAmount(
|
||||
new Token(SupportedChainId.RINKEBY, address, 9),
|
||||
10e18
|
||||
),
|
||||
}
|
||||
|
||||
const fetchNetworkTokenBalances = async (address: string): Promise<NetworkTokenBalancesMap | void> => {
|
||||
const waitRandom = (min: number, max: number): Promise<void> =>
|
||||
new Promise((resolve) => setTimeout(resolve, min + Math.round(Math.random() * Math.max(0, max - min))))
|
||||
try {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
await waitRandom(250, 2000)
|
||||
if (Math.random() < 0.05) {
|
||||
throw new Error('fake error')
|
||||
}
|
||||
return FAKE_TOKEN_NETWORK_BALANCES
|
||||
} catch (e) {
|
||||
setError('something went wrong')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
fetchNetworkTokenBalances(address)
|
||||
.then((data) => {
|
||||
if (data) setData(data)
|
||||
})
|
||||
.catch((e) => setError(e))
|
||||
.finally(() => setLoading(false))
|
||||
} else {
|
||||
setData(null)
|
||||
}
|
||||
}, [address, query])
|
||||
|
||||
return {
|
||||
data,
|
||||
error,
|
||||
loading,
|
||||
}
|
||||
}
|
||||
@@ -29,48 +29,47 @@ 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 useTokenFromNetwork(
|
||||
tokenAddress: string | null | undefined,
|
||||
tokenChainId?: number
|
||||
): 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 (tokenChainId !== undefined && tokenChainId !== chainId) return undefined
|
||||
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)
|
||||
}, [tokenChainId, chainId, tokenAddress, formattedAddress, isLoading, parsedDecimals, parsedSymbol, parsedName])
|
||||
}
|
||||
|
||||
type TokenMap = { [address: string]: Token }
|
||||
@@ -105,8 +104,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
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import useHttpLocations from 'hooks/useHttpLocations'
|
||||
import { useMemo } from 'react'
|
||||
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
|
||||
import { isAddress } from 'utils'
|
||||
|
||||
import EthereumLogo from '../../assets/images/ethereum-logo.png'
|
||||
import CeloLogo from '../../assets/svg/celo_logo.svg'
|
||||
import MaticLogo from '../../assets/svg/matic-token-icon.svg'
|
||||
import { isCelo, nativeOnChain } from '../../constants/tokens'
|
||||
import { isCelo, NATIVE_CHAIN_ID, nativeOnChain } from '../../constants/tokens'
|
||||
|
||||
type Network = 'ethereum' | 'arbitrum' | 'optimism' | 'polygon'
|
||||
|
||||
@@ -54,15 +53,27 @@ export function getTokenLogoURI(address: string, chainId: SupportedChainId = Sup
|
||||
}
|
||||
}
|
||||
|
||||
export default function useCurrencyLogoURIs(currency?: Currency | null): string[] {
|
||||
const locations = useHttpLocations(currency instanceof WrappedTokenInfo ? currency.logoURI : undefined)
|
||||
export default function useCurrencyLogoURIs(
|
||||
currency:
|
||||
| {
|
||||
isNative?: boolean
|
||||
isToken?: boolean
|
||||
address?: string
|
||||
chainId: number
|
||||
logoURI?: string
|
||||
}
|
||||
| null
|
||||
| undefined
|
||||
): string[] {
|
||||
const locations = useHttpLocations(currency?.logoURI)
|
||||
return useMemo(() => {
|
||||
const logoURIs = [...locations]
|
||||
if (currency) {
|
||||
if (currency.isNative) {
|
||||
if (currency.isNative || currency.address === NATIVE_CHAIN_ID) {
|
||||
logoURIs.push(getNativeLogoURI(currency.chainId))
|
||||
} else if (currency.isToken) {
|
||||
const logoURI = getTokenLogoURI(currency.address, currency.chainId)
|
||||
} else if (currency.isToken || currency.address) {
|
||||
const checksummedAddress = isAddress(currency.address)
|
||||
const logoURI = checksummedAddress && getTokenLogoURI(checksummedAddress, currency.chainId)
|
||||
if (logoURI) {
|
||||
logoURIs.push(logoURI)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ const listCache = new Map<string, TokenList>()
|
||||
/** Fetches and validates a token list. */
|
||||
export default async function fetchTokenList(
|
||||
listUrl: string,
|
||||
resolveENSContentHash: (ensName: string) => Promise<string>
|
||||
resolveENSContentHash: (ensName: string) => Promise<string>,
|
||||
skipValidation?: boolean
|
||||
): Promise<TokenList> {
|
||||
const cached = listCache?.get(listUrl) // avoid spurious re-fetches
|
||||
if (cached) {
|
||||
@@ -64,7 +65,7 @@ export default async function fetchTokenList(
|
||||
}
|
||||
|
||||
const json = await response.json()
|
||||
const list = await validateTokenList(json)
|
||||
const list = skipValidation ? json : await validateTokenList(json)
|
||||
listCache?.set(listUrl, list)
|
||||
return list
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: uniswap-interface\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: 2022-09-21 21:05\n"
|
||||
"PO-Revision-Date: 2022-10-11 02:57\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: af_ZA\n"
|
||||
"Language-Team: Afrikaans\n"
|
||||
@@ -21,10 +21,6 @@ msgstr ""
|
||||
msgid "$-"
|
||||
msgstr "$ -"
|
||||
|
||||
#: src/components/CurrencyInputPanel/FiatValue.tsx
|
||||
msgid "$<0/>"
|
||||
msgstr "$<0/>"
|
||||
|
||||
#: src/components/earn/PoolCard.tsx
|
||||
#: src/components/swap/GasEstimateBadge.tsx
|
||||
#: src/pages/Pool/PositionPage.tsx
|
||||
@@ -63,6 +59,10 @@ msgstr "0 UNI / week"
|
||||
msgid "24H volume"
|
||||
msgstr "24H volume"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "24H volume is the amount of the asset that has been traded on Uniswap v3 during the past 24 hours."
|
||||
msgstr "24H-volume is die bedrag van die bate wat gedurende die afgelope 24 uur op Uniswap v3 verhandel is."
|
||||
|
||||
#: src/pages/RemoveLiquidity/V3.tsx
|
||||
msgid "25%"
|
||||
msgstr "25%"
|
||||
@@ -240,7 +240,7 @@ msgid "Amount"
|
||||
msgstr "Bedrag"
|
||||
|
||||
#: src/components/Tokens/TokenTable/TokenTable.tsx
|
||||
msgid "An error occured loading tokens. Please try again."
|
||||
msgid "An error occurred loading tokens. Please try again."
|
||||
msgstr "Kon nie tokens laai nie. Probeer asseblief weer."
|
||||
|
||||
#: src/utils/swapErrorToUserReadableMessage.tsx
|
||||
@@ -373,11 +373,14 @@ msgstr "Deur likiditeit by te voeg, verdien u 0,3% van alle transaksies op hierd
|
||||
msgid "By adding this list you are implicitly trusting that the data is correct. Anyone can create a list, including creating fake versions of existing lists and lists that claim to represent projects that do not have one."
|
||||
msgstr "Deur hierdie lys by te voeg, vertrou u implisiet dat die data korrek is. Almal kan 'n lys opstel, insluitend die skep van vals weergawes van bestaande lyste en lyste wat beweer dat hulle projekte verteenwoordig wat nie een het nie."
|
||||
|
||||
#: src/components/WalletModal/index.tsx
|
||||
#: src/components/WalletModal/index.tsx
|
||||
msgid "By connecting a wallet, you agree to Uniswap Labs’ <0>Terms of Service</0> and acknowledge that you have read and understand the Uniswap <1>Protocol Disclaimer</1>."
|
||||
msgstr "Deur 'n beursie te koppel, stem jy in tot Uniswap Labs se <0>Diensbepalings</0> en erken dat jy die Uniswap <1>Protokol Disclaimer</1>gelees en verstaan het."
|
||||
|
||||
#: src/components/WalletModal/index.tsx
|
||||
msgid "By connecting a wallet, you agree to Uniswap Labs’ <0>Terms of Service</0> and consent to its <1>Privacy Policy</1>."
|
||||
msgstr "Deur 'n beursie te koppel, stem jy in tot Uniswap Labs se <0>Diensbepalings</0> en stem in tot sy <1>Privaatheidsbeleid</1>."
|
||||
|
||||
#: src/pages/Vote/styled.tsx
|
||||
msgid "Canceled"
|
||||
msgstr "Gekanselleer"
|
||||
@@ -605,11 +608,13 @@ msgid "Contract address"
|
||||
msgstr "Kontrak adres"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
#: src/nft/components/profile/view/ProfileAccountDetails.tsx
|
||||
#: src/theme/components.tsx
|
||||
msgid "Copied!"
|
||||
msgstr "Gekopieer!"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
#: src/nft/components/profile/view/ProfileAccountDetails.tsx
|
||||
msgid "Copy"
|
||||
msgstr "Kopieer"
|
||||
|
||||
@@ -889,10 +894,6 @@ msgstr "Die uitvoering van hierdie voorstel sal die oproepdata in die ketting in
|
||||
msgid "Execution Submitted"
|
||||
msgstr "Uitvoering ingedien"
|
||||
|
||||
#: src/components/SearchModal/CurrencyList/index.tsx
|
||||
msgid "Expanded results from inactive Token Lists"
|
||||
msgstr "Uitgebreide resultate van onaktiewe tekenlyste"
|
||||
|
||||
#: src/components/swap/AdvancedSwapDetails.tsx
|
||||
msgid "Expected Output"
|
||||
msgstr "Verwagte uitset"
|
||||
@@ -913,10 +914,9 @@ msgstr "Verstreke"
|
||||
msgid "Explore"
|
||||
msgstr "Verken"
|
||||
|
||||
#: src/pages/Tokens/index.tsx
|
||||
#: src/pages/Tokens/index.tsx
|
||||
msgid "Explore Tokens"
|
||||
msgstr "Verken Tokens"
|
||||
#: src/components/Tokens/TokensBanner.tsx
|
||||
msgid "Explore Top Tokens on Uniswap"
|
||||
msgstr "Verken Top Tokens op Uniswap"
|
||||
|
||||
#: src/pages/Pool/CTACards.tsx
|
||||
msgid "Explore Uniswap Analytics."
|
||||
@@ -1130,6 +1130,10 @@ msgstr "Ligte tema"
|
||||
msgid "Light theme"
|
||||
msgstr "Ligte tema"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/About.tsx
|
||||
msgid "Links"
|
||||
msgstr "Skakels"
|
||||
|
||||
#: src/components/claim/ClaimModal.tsx
|
||||
#: src/pages/Pool/PositionPage.tsx
|
||||
msgid "Liquidity"
|
||||
@@ -1262,6 +1266,11 @@ msgstr "Min:"
|
||||
msgid "Minimum received"
|
||||
msgstr "Minimum ontvang"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/PriceChart.tsx
|
||||
#: src/components/Tokens/TokenDetails/PriceChart.tsx
|
||||
msgid "Missing chart data"
|
||||
msgstr "Grafiekdata ontbreek"
|
||||
|
||||
#: src/lib/hooks/swap/useSwapCallback.tsx
|
||||
msgid "Missing dependencies"
|
||||
msgstr "Ontbrekings ontbreek"
|
||||
@@ -1270,7 +1279,7 @@ msgstr "Ontbrekings ontbreek"
|
||||
msgid "More"
|
||||
msgstr "Meer"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "NFT Collections"
|
||||
msgstr "NFT-versamelings"
|
||||
|
||||
@@ -1298,6 +1307,10 @@ msgstr "Netwerkfooie oorskry 50% van die ruilbedrag!"
|
||||
msgid "New Position"
|
||||
msgstr "Nuwe posisie"
|
||||
|
||||
#: src/nft/components/profile/view/EmptyWalletContent.tsx
|
||||
msgid "No NFTs in"
|
||||
msgstr "Geen NFT's in nie"
|
||||
|
||||
#: src/pages/MigrateV2/index.tsx
|
||||
msgid "No V2 Liquidity found."
|
||||
msgstr "Geen V2-likiditeit gevind nie."
|
||||
@@ -1339,7 +1352,7 @@ msgstr "Geen tekeninligting beskikbaar nie"
|
||||
msgid "No tokens found"
|
||||
msgstr "Geen tokens gevind nie"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "No tokens found."
|
||||
msgstr "Geen tokens gevind nie."
|
||||
|
||||
@@ -1468,11 +1481,11 @@ msgstr "Gepoel {0}:"
|
||||
msgid "Pools"
|
||||
msgstr "Swembaddens"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "Popular NFT collections"
|
||||
msgstr "Gewilde NFT-versamelings"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "Popular tokens"
|
||||
msgstr "Gewilde tekens"
|
||||
|
||||
@@ -1577,10 +1590,6 @@ msgstr "Toustaan"
|
||||
msgid "Rates"
|
||||
msgstr "Tariewe"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/About.tsx
|
||||
msgid "Read more"
|
||||
msgstr "Lees meer"
|
||||
|
||||
#: src/pages/Earn/index.tsx
|
||||
msgid "Read more about UNI"
|
||||
msgstr "Lees meer oor UNI"
|
||||
@@ -1601,7 +1610,7 @@ msgstr "Lees meer oor onondersteunde bates"
|
||||
msgid "Recent Transactions"
|
||||
msgstr "Onlangse transaksies"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "Recent searches"
|
||||
msgstr "Onlangse soektogte"
|
||||
|
||||
@@ -1719,10 +1728,6 @@ msgstr "Self"
|
||||
msgid "Self Delegate"
|
||||
msgstr "Self Afgevaardigde"
|
||||
|
||||
#: src/components/NavBar/MenuDropdown.tsx
|
||||
msgid "Sell NFTs"
|
||||
msgstr "Verkoop NFT's"
|
||||
|
||||
#: src/pages/AddLiquidity/index.tsx
|
||||
#: src/pages/AddLiquidity/index.tsx
|
||||
#: src/pages/MigrateV2/MigrateV2Pair.tsx
|
||||
@@ -1755,6 +1760,10 @@ msgstr "Vertoning is gekanselleer"
|
||||
msgid "Show closed positions"
|
||||
msgstr "Toon geslote posisies"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/About.tsx
|
||||
msgid "Show more"
|
||||
msgstr "Wys meer"
|
||||
|
||||
#: src/pages/RemoveLiquidity/index.tsx
|
||||
msgid "Simple"
|
||||
msgstr "Eenvoudig"
|
||||
@@ -1771,6 +1780,14 @@ msgstr "Sommige bates is nie beskikbaar via hierdie koppelvlak nie, omdat dit mo
|
||||
msgid "Something went wrong"
|
||||
msgstr "Iets het verkeerd geloop"
|
||||
|
||||
#: src/components/Tokens/TokensBanner.tsx
|
||||
msgid "Sort and filter assets across networks on the new Tokens page."
|
||||
msgstr "Sorteer en filtreer bates oor netwerke op die nuwe Tokens-bladsy."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "Stats"
|
||||
msgstr "Statistieke"
|
||||
|
||||
#: src/pages/Earn/Manage.tsx
|
||||
msgid "Step 1. Get UNI-V2 Liquidity tokens"
|
||||
msgstr "Stap 1. Kry UNI-V2 likiditeitstekens"
|
||||
@@ -1806,7 +1823,7 @@ msgstr "Verskaf {0} {1} en {2} {3}"
|
||||
#: src/components/AccountDetailsV2/TransactionBody.tsx
|
||||
#: src/components/Header/index.tsx
|
||||
#: src/components/NavBar/index.tsx
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
#: src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx
|
||||
#: src/components/swap/SwapHeader.tsx
|
||||
#: src/pages/Swap/index.tsx
|
||||
#: src/pages/Swap/index.tsx
|
||||
@@ -1848,6 +1865,10 @@ msgstr "Ruil {0} {1} vir {2} {3}"
|
||||
msgid "Symbol not found"
|
||||
msgstr "Simbool nie gevind nie"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "TVL"
|
||||
msgstr "TVL"
|
||||
|
||||
#: src/components/Popups/SurveyPopup.tsx
|
||||
msgid "Take a 10 minute survey to help us improve your experience in the Uniswap app."
|
||||
msgstr "Neem 'n 10 minute opname om ons te help om jou ervaring in die Uniswap-toepassing te verbeter."
|
||||
@@ -1962,14 +1983,6 @@ msgstr "Die transaksie kon nie gestuur word nie omdat die sperdatum verstryk het
|
||||
msgid "There is no liquidity data."
|
||||
msgstr "Daar is geen likiditeitsdata nie."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
msgid "There was an error fetching your balance"
|
||||
msgstr "Kon nie jou saldo haal nie"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/BalanceSummary.tsx
|
||||
msgid "There was an error loading your {0} balance"
|
||||
msgstr "Kon nie jou {0} -saldo laai nie"
|
||||
|
||||
#: src/components/ConnectedAccountBlocked/index.tsx
|
||||
msgid "This address is blocked on the Uniswap Labs interface because it is associated with one or more"
|
||||
msgstr "Hierdie adres is geblokkeer op die Uniswap Labs-koppelvlak omdat dit met een of meer geassosieer word"
|
||||
@@ -1994,6 +2007,10 @@ msgstr "Hierdie roete optimaliseer jou totale uitset deur gesplete roetes, veelv
|
||||
msgid "This token doesn't appear on the active token list(s). Make sure this is the token that you want to trade."
|
||||
msgstr "Hierdie teken verskyn nie op die aktiewe tekenlys(te) nie. Maak seker dat dit die teken is wat u wil verhandel."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/PriceChart.tsx
|
||||
msgid "This token doesn't have chart data because it hasn't been traded on Uniswap v3"
|
||||
msgstr "Hierdie teken het nie grafiekdata nie omdat dit nie op Uniswap v3 verhandel is nie"
|
||||
|
||||
#: src/components/SearchModal/BlockedToken.tsx
|
||||
msgid "This token is not supported in the Uniswap Labs app"
|
||||
msgstr "Hierdie teken word nie in die Uniswap Labs-toepassing ondersteun nie"
|
||||
@@ -2033,23 +2050,20 @@ msgid "Token not supported"
|
||||
msgstr "Token word nie ondersteun nie"
|
||||
|
||||
#: src/components/Header/index.tsx
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
#: src/components/NavBar/index.tsx
|
||||
#: src/components/SearchModal/Manage.tsx
|
||||
msgid "Tokens"
|
||||
msgstr "Tekens"
|
||||
|
||||
#: src/components/SearchModal/CurrencyList/index.tsx
|
||||
msgid "Tokens from inactive lists. Import specific tokens below or click Manage to activate more lists."
|
||||
msgstr "Tekens uit onaktiewe lyste. Voer spesifieke tekens hieronder in of klik op Bestuur om meer lyste te aktiveer."
|
||||
|
||||
#: src/pages/Pool/CTACards.tsx
|
||||
msgid "Top pools"
|
||||
msgstr "Top poele"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "Total Value Locked"
|
||||
msgstr "Totale waarde gesluit"
|
||||
#: src/pages/Tokens/index.tsx
|
||||
#: src/pages/Tokens/index.tsx
|
||||
msgid "Top tokens on Uniswap"
|
||||
msgstr "Top tokens op Uniswap"
|
||||
|
||||
#: src/components/earn/PoolCard.tsx
|
||||
msgid "Total deposited"
|
||||
@@ -2059,6 +2073,10 @@ msgstr "Totale deposito"
|
||||
msgid "Total deposits"
|
||||
msgstr "Totale deposito's"
|
||||
|
||||
#: src/components/Tokens/TokenTable/TokenRow.tsx
|
||||
msgid "Total value locked (TVL) is the amount of the asset that’s currently in a Uniswap v3 liquidity pool."
|
||||
msgstr "Totale waarde gesluit (TVL) is die bedrag van die bate wat tans in 'n Uniswap v3-likiditeitspoel is."
|
||||
|
||||
#: src/components/swap/RouterLabel.tsx
|
||||
msgid "Trade Route"
|
||||
msgstr "Handelsroete"
|
||||
@@ -2263,10 +2281,18 @@ msgstr "V3 {0} prys:"
|
||||
msgid "View accrued fees and analytics<0>↗</0>"
|
||||
msgstr "Kyk na opgelope fooie en analise <0> ↗</0>"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
msgid "View and sell NFTs"
|
||||
msgstr "Bekyk en verkoop NFT's"
|
||||
|
||||
#: src/components/SearchModal/ManageLists.tsx
|
||||
msgid "View list"
|
||||
msgstr "Kyk na lys"
|
||||
|
||||
#: src/components/NavBar/MenuDropdown.tsx
|
||||
msgid "View more analytics"
|
||||
msgstr "Bekyk meer ontledings"
|
||||
|
||||
#: src/components/TransactionConfirmationModal/index.tsx
|
||||
#: src/pages/CreateProposal/ProposalSubmissionModal.tsx
|
||||
msgid "View on Etherscan"
|
||||
@@ -2278,10 +2304,6 @@ msgstr "Uitsig op Etherscan"
|
||||
msgid "View on Explorer"
|
||||
msgstr "Bekyk op Explorer"
|
||||
|
||||
#: src/components/NavBar/MenuDropdown.tsx
|
||||
msgid "View token analytics"
|
||||
msgstr "Bekyk token-analise"
|
||||
|
||||
#: src/components/ModalViews/index.tsx
|
||||
#: src/components/claim/AddressClaimModal.tsx
|
||||
#: src/components/claim/ClaimModal.tsx
|
||||
@@ -2291,6 +2313,10 @@ msgstr "Bekyk token-analise"
|
||||
msgid "View transaction on Explorer"
|
||||
msgstr "Bekyk transaksie op Explorer"
|
||||
|
||||
#: src/components/Tokens/TokenTable/TokenRow.tsx
|
||||
msgid "Volume is the amount of the asset that has been traded on Uniswap v3 during the selected time frame."
|
||||
msgstr "Volume is die bedrag van die bate wat gedurende die geselekteerde tydraamwerk op Uniswap v3 verhandel is."
|
||||
|
||||
#: src/components/Header/index.tsx
|
||||
msgid "Vote"
|
||||
msgstr "Stem"
|
||||
@@ -2472,10 +2498,6 @@ msgstr "U het nog nie likiditeit in hierdie poel nie."
|
||||
msgid "You have no favorited tokens"
|
||||
msgstr "Jy het geen gunsteling-tokens nie"
|
||||
|
||||
#: src/components/Header/ChainConnectivityWarning.tsx
|
||||
msgid "You may have lost your network connection, or {label} might be down right now."
|
||||
msgstr "Miskien het u u netwerkverbinding verloor, of {label} is tans af."
|
||||
|
||||
#: src/components/Header/ChainConnectivityWarning.tsx
|
||||
msgid "You may have lost your network connection."
|
||||
msgstr "U het moontlik u netwerkverbinding verloor."
|
||||
@@ -2517,9 +2539,9 @@ msgstr "U V2-likiditeit"
|
||||
msgid "Your active V3 liquidity positions will appear here."
|
||||
msgstr "Jou aktiewe V3-likiditeitsposisies sal hier verskyn."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
msgid "Your balances by network"
|
||||
msgstr "Jou saldo's per netwerk"
|
||||
#: src/components/Tokens/TokenDetails/BalanceSummary.tsx
|
||||
msgid "Your balance"
|
||||
msgstr "Jou balans"
|
||||
|
||||
#: src/pages/Pool/index.tsx
|
||||
msgid "Your connected network is unsupported."
|
||||
@@ -2608,6 +2630,11 @@ msgstr "U transaksies sal hier verskyn ..."
|
||||
msgid "Your unclaimed UNI"
|
||||
msgstr "U onopgeëiste UNI"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx
|
||||
#: src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx
|
||||
msgid "Your {0} balance"
|
||||
msgstr "Jou {0} -saldo"
|
||||
|
||||
#: src/components/swap/AdvancedSwapDetails.tsx
|
||||
msgid "after slippage"
|
||||
msgstr "na gly"
|
||||
@@ -2684,8 +2711,8 @@ msgid "{0, plural, =1 {Import token} other {Import tokens}}"
|
||||
msgstr "{0, plural, =1 {Voer teken in} other {Voer tekens in}}"
|
||||
|
||||
#: src/constants/tokenSafety.tsx
|
||||
msgid "{0, plural, =1 {This token isn't verified} other {These tokens aren't verified}}"
|
||||
msgstr "{0, plural, =1 {Hierdie teken is nie geverifieer nie} other {Hierdie tekens is nie geverifieer nie}}"
|
||||
msgid "{0, plural, =1 {This token isn't verified.} other {These tokens aren't verified.}}"
|
||||
msgstr "{0, plural, =1 {Hierdie teken is nie geverifieer nie.} other {Hierdie tekens is nie geverifieer nie.}}"
|
||||
|
||||
#: src/constants/tokenSafety.tsx
|
||||
msgid "{0, plural, =1 {You can't trade this token using the Uniswap App.} other {You can't trade these tokens using the Uniswap App.}}"
|
||||
@@ -2758,10 +2785,6 @@ msgstr "{0} UNI-V2 LP tekens beskikbaar"
|
||||
msgid "{0} Votes"
|
||||
msgstr "{0} Stemme"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
msgid "{0} all balances"
|
||||
msgstr "{0} alle saldo's"
|
||||
|
||||
#: src/components/PositionPreview/index.tsx
|
||||
#: src/components/PositionPreview/index.tsx
|
||||
#: src/components/PositionPreview/index.tsx
|
||||
@@ -2839,6 +2862,10 @@ msgstr "{USER_AMOUNT} UNI"
|
||||
msgid "{activeTokensOnThisChain} tokens"
|
||||
msgstr "{activeTokensOnThisChain} tokens"
|
||||
|
||||
#: src/components/Header/ChainConnectivityWarning.tsx
|
||||
msgid "{label} might be down right now, or you may have lost your network connection."
|
||||
msgstr "{label} is dalk op die oomblik af, of jy het dalk jou netwerkverbinding verloor."
|
||||
|
||||
#: src/components/NetworkAlert/NetworkAlert.tsx
|
||||
msgid "{label} token bridge"
|
||||
msgstr "{label} teken brug"
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: uniswap-interface\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: 2022-09-21 21:05\n"
|
||||
"PO-Revision-Date: 2022-10-11 02:57\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: ar_SA\n"
|
||||
"Language-Team: Arabic\n"
|
||||
@@ -21,10 +21,6 @@ msgstr ""
|
||||
msgid "$-"
|
||||
msgstr "$-"
|
||||
|
||||
#: src/components/CurrencyInputPanel/FiatValue.tsx
|
||||
msgid "$<0/>"
|
||||
msgstr "<0/>دولار"
|
||||
|
||||
#: src/components/earn/PoolCard.tsx
|
||||
#: src/components/swap/GasEstimateBadge.tsx
|
||||
#: src/pages/Pool/PositionPage.tsx
|
||||
@@ -63,6 +59,10 @@ msgstr "0 UNI / أسبوع"
|
||||
msgid "24H volume"
|
||||
msgstr "حجم 24H"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "24H volume is the amount of the asset that has been traded on Uniswap v3 during the past 24 hours."
|
||||
msgstr "حجم 24 ساعة هو مقدار الأصل الذي تم تداوله على Uniswap v3 خلال الـ 24 ساعة الماضية."
|
||||
|
||||
#: src/pages/RemoveLiquidity/V3.tsx
|
||||
msgid "25%"
|
||||
msgstr "25%"
|
||||
@@ -240,8 +240,8 @@ msgid "Amount"
|
||||
msgstr "المبلغ"
|
||||
|
||||
#: src/components/Tokens/TokenTable/TokenTable.tsx
|
||||
msgid "An error occured loading tokens. Please try again."
|
||||
msgstr "حدث خطأ أثناء تحميل الرموز. حاول مرة اخرى."
|
||||
msgid "An error occurred loading tokens. Please try again."
|
||||
msgstr "حدث خطأ أثناء تحميل الرموز المميزة. حاول مرة اخرى."
|
||||
|
||||
#: src/utils/swapErrorToUserReadableMessage.tsx
|
||||
msgid "An error occurred when trying to execute this swap. You may need to increase your slippage tolerance. If that does not work, there may be an incompatibility with the token you are trading. Note: fee on transfer and rebase tokens are incompatible with Uniswap V3."
|
||||
@@ -373,11 +373,14 @@ msgstr "من خلال إضافة السيولة ستكسب 0.3٪ من جميع
|
||||
msgid "By adding this list you are implicitly trusting that the data is correct. Anyone can create a list, including creating fake versions of existing lists and lists that claim to represent projects that do not have one."
|
||||
msgstr "بإضافة هذه القائمة فإنك تثق ضمنًا بأن البيانات صحيحة. ويمكن لأي شخص أن ينشئ قائمة، بما في ذلك إنشاء صيغ مزيفة من القوائم القائمة والقوائم التي تزعم أنها تمثل مشاريع لا تملك قائمة."
|
||||
|
||||
#: src/components/WalletModal/index.tsx
|
||||
#: src/components/WalletModal/index.tsx
|
||||
msgid "By connecting a wallet, you agree to Uniswap Labs’ <0>Terms of Service</0> and acknowledge that you have read and understand the Uniswap <1>Protocol Disclaimer</1>."
|
||||
msgstr "من خلال ربط المحفظة ، فإنك توافق على شروط الخدمة</0> <0>وتقر بأنك قد قرأت وفهمت إخلاء مسؤولية بروتوكول <1></1>."
|
||||
|
||||
#: src/components/WalletModal/index.tsx
|
||||
msgid "By connecting a wallet, you agree to Uniswap Labs’ <0>Terms of Service</0> and consent to its <1>Privacy Policy</1>."
|
||||
msgstr "من خلال ربط المحفظة ، فإنك توافق على شروط الخدمة <0>الخاصة بـ</1>Labs وتوافق على</0> سياسة الخصوصية <1>."
|
||||
|
||||
#: src/pages/Vote/styled.tsx
|
||||
msgid "Canceled"
|
||||
msgstr "ألغيت"
|
||||
@@ -605,11 +608,13 @@ msgid "Contract address"
|
||||
msgstr "عنوان العقد"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
#: src/nft/components/profile/view/ProfileAccountDetails.tsx
|
||||
#: src/theme/components.tsx
|
||||
msgid "Copied!"
|
||||
msgstr "نسخ!"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
#: src/nft/components/profile/view/ProfileAccountDetails.tsx
|
||||
msgid "Copy"
|
||||
msgstr "ينسخ"
|
||||
|
||||
@@ -889,10 +894,6 @@ msgstr "سيؤدي تنفيذ هذا الاقتراح إلى تفعيل Calldata
|
||||
msgid "Execution Submitted"
|
||||
msgstr "تم تقديم التنفيذ"
|
||||
|
||||
#: src/components/SearchModal/CurrencyList/index.tsx
|
||||
msgid "Expanded results from inactive Token Lists"
|
||||
msgstr "النتائج الموسعة من قوائم الرموز غير النشطة"
|
||||
|
||||
#: src/components/swap/AdvancedSwapDetails.tsx
|
||||
msgid "Expected Output"
|
||||
msgstr "الناتج المتوقع"
|
||||
@@ -913,10 +914,9 @@ msgstr "منتهي الصلاحية"
|
||||
msgid "Explore"
|
||||
msgstr "يكتشف"
|
||||
|
||||
#: src/pages/Tokens/index.tsx
|
||||
#: src/pages/Tokens/index.tsx
|
||||
msgid "Explore Tokens"
|
||||
msgstr "اكتشف الرموز"
|
||||
#: src/components/Tokens/TokensBanner.tsx
|
||||
msgid "Explore Top Tokens on Uniswap"
|
||||
msgstr "استكشف أفضل الرموز على Uniswap"
|
||||
|
||||
#: src/pages/Pool/CTACards.tsx
|
||||
msgid "Explore Uniswap Analytics."
|
||||
@@ -1130,6 +1130,10 @@ msgstr "مظهر خفيف"
|
||||
msgid "Light theme"
|
||||
msgstr "مظهر خفيف"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/About.tsx
|
||||
msgid "Links"
|
||||
msgstr "الروابط"
|
||||
|
||||
#: src/components/claim/ClaimModal.tsx
|
||||
#: src/pages/Pool/PositionPage.tsx
|
||||
msgid "Liquidity"
|
||||
@@ -1262,6 +1266,11 @@ msgstr "دقيقة:"
|
||||
msgid "Minimum received"
|
||||
msgstr "تلقى الحد الأدنى"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/PriceChart.tsx
|
||||
#: src/components/Tokens/TokenDetails/PriceChart.tsx
|
||||
msgid "Missing chart data"
|
||||
msgstr "بيانات الرسم البياني مفقودة"
|
||||
|
||||
#: src/lib/hooks/swap/useSwapCallback.tsx
|
||||
msgid "Missing dependencies"
|
||||
msgstr "التبعيات المفقودة"
|
||||
@@ -1270,7 +1279,7 @@ msgstr "التبعيات المفقودة"
|
||||
msgid "More"
|
||||
msgstr "المزيد"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "NFT Collections"
|
||||
msgstr "مجموعات NFT"
|
||||
|
||||
@@ -1298,6 +1307,10 @@ msgstr "رسوم الشبكة تتجاوز 50٪ من مبلغ المبادلة!"
|
||||
msgid "New Position"
|
||||
msgstr "موضع جديد"
|
||||
|
||||
#: src/nft/components/profile/view/EmptyWalletContent.tsx
|
||||
msgid "No NFTs in"
|
||||
msgstr "لا توجد NFTs في"
|
||||
|
||||
#: src/pages/MigrateV2/index.tsx
|
||||
msgid "No V2 Liquidity found."
|
||||
msgstr "لم يتم العثور على سيولة V2."
|
||||
@@ -1339,7 +1352,7 @@ msgstr "لا توجد معلومات رمزية متاحة"
|
||||
msgid "No tokens found"
|
||||
msgstr "لم يتم العثور على الرموز المميزة"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "No tokens found."
|
||||
msgstr "لم يتم العثور على الرموز المميزة."
|
||||
|
||||
@@ -1468,11 +1481,11 @@ msgstr "مجمّع {0}:"
|
||||
msgid "Pools"
|
||||
msgstr "حمامات السباحة"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "Popular NFT collections"
|
||||
msgstr "مجموعات NFT الشعبية"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "Popular tokens"
|
||||
msgstr "الرموز الشعبية"
|
||||
|
||||
@@ -1577,10 +1590,6 @@ msgstr "قائمة الانتظار"
|
||||
msgid "Rates"
|
||||
msgstr "الأسعار"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/About.tsx
|
||||
msgid "Read more"
|
||||
msgstr "اقرأ أكثر"
|
||||
|
||||
#: src/pages/Earn/index.tsx
|
||||
msgid "Read more about UNI"
|
||||
msgstr "اقرأ المزيد عن UNI"
|
||||
@@ -1601,7 +1610,7 @@ msgstr "اقرأ المزيد عن الأصول غير المدعومة"
|
||||
msgid "Recent Transactions"
|
||||
msgstr "المعاملات الأخيرة"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "Recent searches"
|
||||
msgstr "عمليات البحث الأخيرة"
|
||||
|
||||
@@ -1719,10 +1728,6 @@ msgstr "نفسه"
|
||||
msgid "Self Delegate"
|
||||
msgstr "تفويض ذاتي"
|
||||
|
||||
#: src/components/NavBar/MenuDropdown.tsx
|
||||
msgid "Sell NFTs"
|
||||
msgstr "بيع NFTs"
|
||||
|
||||
#: src/pages/AddLiquidity/index.tsx
|
||||
#: src/pages/AddLiquidity/index.tsx
|
||||
#: src/pages/MigrateV2/MigrateV2Pair.tsx
|
||||
@@ -1755,6 +1760,10 @@ msgstr "إظهار الملغاة"
|
||||
msgid "Show closed positions"
|
||||
msgstr "إظهار المراكز المغلقة"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/About.tsx
|
||||
msgid "Show more"
|
||||
msgstr "أظهر المزيد"
|
||||
|
||||
#: src/pages/RemoveLiquidity/index.tsx
|
||||
msgid "Simple"
|
||||
msgstr "بسيط"
|
||||
@@ -1771,6 +1780,14 @@ msgstr "بعض الأصول غير متوفرة من خلال هذه الواج
|
||||
msgid "Something went wrong"
|
||||
msgstr "حدث خطأ ما"
|
||||
|
||||
#: src/components/Tokens/TokensBanner.tsx
|
||||
msgid "Sort and filter assets across networks on the new Tokens page."
|
||||
msgstr "قم بفرز الأصول وتصفيتها عبر الشبكات في صفحة الرموز الجديدة."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "Stats"
|
||||
msgstr "احصائيات"
|
||||
|
||||
#: src/pages/Earn/Manage.tsx
|
||||
msgid "Step 1. Get UNI-V2 Liquidity tokens"
|
||||
msgstr "الخطوة 1. احصل على رمز سيولة UNI-V2"
|
||||
@@ -1806,7 +1823,7 @@ msgstr "إمداد {0} {1} و {2} {3}"
|
||||
#: src/components/AccountDetailsV2/TransactionBody.tsx
|
||||
#: src/components/Header/index.tsx
|
||||
#: src/components/NavBar/index.tsx
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
#: src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx
|
||||
#: src/components/swap/SwapHeader.tsx
|
||||
#: src/pages/Swap/index.tsx
|
||||
#: src/pages/Swap/index.tsx
|
||||
@@ -1848,6 +1865,10 @@ msgstr "مبادلة {0} {1} مقابل {2} {3}"
|
||||
msgid "Symbol not found"
|
||||
msgstr "لم يتم العثور على الرمز"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "TVL"
|
||||
msgstr "TVL"
|
||||
|
||||
#: src/components/Popups/SurveyPopup.tsx
|
||||
msgid "Take a 10 minute survey to help us improve your experience in the Uniswap app."
|
||||
msgstr "شارك في استبيان مدته 10 دقائق لمساعدتنا على تحسين تجربتك في تطبيق Uniswap."
|
||||
@@ -1962,14 +1983,6 @@ msgstr "تعذر إرسال المعاملة لانتهاء الموعد الم
|
||||
msgid "There is no liquidity data."
|
||||
msgstr "لا توجد بيانات سيولة."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
msgid "There was an error fetching your balance"
|
||||
msgstr "حدث خطأ في جلب رصيدك"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/BalanceSummary.tsx
|
||||
msgid "There was an error loading your {0} balance"
|
||||
msgstr "حدث خطأ أثناء تحميل رصيدك {0}"
|
||||
|
||||
#: src/components/ConnectedAccountBlocked/index.tsx
|
||||
msgid "This address is blocked on the Uniswap Labs interface because it is associated with one or more"
|
||||
msgstr "هذا العنوان محظور في واجهة Uniswap Labs لأنه مرتبط بواحد أو أكثر"
|
||||
@@ -1994,6 +2007,10 @@ msgstr "يعمل هذا المسار على تحسين إنتاجك الإجما
|
||||
msgid "This token doesn't appear on the active token list(s). Make sure this is the token that you want to trade."
|
||||
msgstr "لا يظهر هذا الرمز في قائمة (قوائم) الرموز النشطة. تأكد من أن هذا هو الرمز الذي تريد تداوله."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/PriceChart.tsx
|
||||
msgid "This token doesn't have chart data because it hasn't been traded on Uniswap v3"
|
||||
msgstr "لا يحتوي هذا الرمز المميز على بيانات مخطط لأنه لم يتم تداوله على Uniswap v3"
|
||||
|
||||
#: src/components/SearchModal/BlockedToken.tsx
|
||||
msgid "This token is not supported in the Uniswap Labs app"
|
||||
msgstr "هذا الرمز المميز غير مدعوم في تطبيق Uniswap Labs"
|
||||
@@ -2033,23 +2050,20 @@ msgid "Token not supported"
|
||||
msgstr "رمز غير مدعوم"
|
||||
|
||||
#: src/components/Header/index.tsx
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
#: src/components/NavBar/index.tsx
|
||||
#: src/components/SearchModal/Manage.tsx
|
||||
msgid "Tokens"
|
||||
msgstr "الرموز"
|
||||
|
||||
#: src/components/SearchModal/CurrencyList/index.tsx
|
||||
msgid "Tokens from inactive lists. Import specific tokens below or click Manage to activate more lists."
|
||||
msgstr "الرموز من القوائم غير النشطة. قم باستيراد الرموز المميزة المحددة أدناه أو انقر فوق \"إدارة\" لتنشيط المزيد من القوائم."
|
||||
|
||||
#: src/pages/Pool/CTACards.tsx
|
||||
msgid "Top pools"
|
||||
msgstr "أفضل المجموعات"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "Total Value Locked"
|
||||
msgstr "إجمالي القيمة مؤمن"
|
||||
#: src/pages/Tokens/index.tsx
|
||||
#: src/pages/Tokens/index.tsx
|
||||
msgid "Top tokens on Uniswap"
|
||||
msgstr "أعلى الرموز على Uniswap"
|
||||
|
||||
#: src/components/earn/PoolCard.tsx
|
||||
msgid "Total deposited"
|
||||
@@ -2059,6 +2073,10 @@ msgstr "الإجمالي المودَع"
|
||||
msgid "Total deposits"
|
||||
msgstr "مجموع الودائع"
|
||||
|
||||
#: src/components/Tokens/TokenTable/TokenRow.tsx
|
||||
msgid "Total value locked (TVL) is the amount of the asset that’s currently in a Uniswap v3 liquidity pool."
|
||||
msgstr "إجمالي القيمة المقفلة (TVL) هو مقدار الأصل الموجود حاليًا في تجمع سيولة Uniswap v3."
|
||||
|
||||
#: src/components/swap/RouterLabel.tsx
|
||||
msgid "Trade Route"
|
||||
msgstr "طريق التجارة"
|
||||
@@ -2263,10 +2281,18 @@ msgstr "سعر V3 {0}:"
|
||||
msgid "View accrued fees and analytics<0>↗</0>"
|
||||
msgstr "عرض الرسوم المتراكمة والتحليلات <0>↗</0>"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
msgid "View and sell NFTs"
|
||||
msgstr "عرض وبيع NFTs"
|
||||
|
||||
#: src/components/SearchModal/ManageLists.tsx
|
||||
msgid "View list"
|
||||
msgstr "عرض القائمة"
|
||||
|
||||
#: src/components/NavBar/MenuDropdown.tsx
|
||||
msgid "View more analytics"
|
||||
msgstr "عرض المزيد من التحليلات"
|
||||
|
||||
#: src/components/TransactionConfirmationModal/index.tsx
|
||||
#: src/pages/CreateProposal/ProposalSubmissionModal.tsx
|
||||
msgid "View on Etherscan"
|
||||
@@ -2278,10 +2304,6 @@ msgstr "عرض على Etherscan"
|
||||
msgid "View on Explorer"
|
||||
msgstr "عرض على Explorer"
|
||||
|
||||
#: src/components/NavBar/MenuDropdown.tsx
|
||||
msgid "View token analytics"
|
||||
msgstr "عرض تحليلات الرمز المميز"
|
||||
|
||||
#: src/components/ModalViews/index.tsx
|
||||
#: src/components/claim/AddressClaimModal.tsx
|
||||
#: src/components/claim/ClaimModal.tsx
|
||||
@@ -2291,6 +2313,10 @@ msgstr "عرض تحليلات الرمز المميز"
|
||||
msgid "View transaction on Explorer"
|
||||
msgstr "عرض المعاملة على Explorer"
|
||||
|
||||
#: src/components/Tokens/TokenTable/TokenRow.tsx
|
||||
msgid "Volume is the amount of the asset that has been traded on Uniswap v3 during the selected time frame."
|
||||
msgstr "الحجم هو مقدار الأصل الذي تم تداوله على Uniswap v3 خلال الإطار الزمني المحدد."
|
||||
|
||||
#: src/components/Header/index.tsx
|
||||
msgid "Vote"
|
||||
msgstr "التصويت"
|
||||
@@ -2472,10 +2498,6 @@ msgstr "ليس لديك سيولة في هذه المجموعة حتى الآن.
|
||||
msgid "You have no favorited tokens"
|
||||
msgstr "ليس لديك رموز مفضلة"
|
||||
|
||||
#: src/components/Header/ChainConnectivityWarning.tsx
|
||||
msgid "You may have lost your network connection, or {label} might be down right now."
|
||||
msgstr "ربما تكون قد فقدت اتصالك بالشبكة ، أو ربما يكون الرقم {label}"
|
||||
|
||||
#: src/components/Header/ChainConnectivityWarning.tsx
|
||||
msgid "You may have lost your network connection."
|
||||
msgstr "ربما تكون قد فقدت اتصالك بالشبكة."
|
||||
@@ -2517,9 +2539,9 @@ msgstr "سيولة V2 الخاصة بك"
|
||||
msgid "Your active V3 liquidity positions will appear here."
|
||||
msgstr "ستظهر مراكز سيولة V3 النشطة الخاصة بك هنا."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
msgid "Your balances by network"
|
||||
msgstr "الأرصدة الخاصة بك عن طريق الشبكة"
|
||||
#: src/components/Tokens/TokenDetails/BalanceSummary.tsx
|
||||
msgid "Your balance"
|
||||
msgstr "رصيدك"
|
||||
|
||||
#: src/pages/Pool/index.tsx
|
||||
msgid "Your connected network is unsupported."
|
||||
@@ -2608,6 +2630,11 @@ msgstr "المعاملات الخاصة بك ستظهر هنا..."
|
||||
msgid "Your unclaimed UNI"
|
||||
msgstr "UNI الخاص بك بدون مطالبة"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx
|
||||
#: src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx
|
||||
msgid "Your {0} balance"
|
||||
msgstr "رصيدك {0}"
|
||||
|
||||
#: src/components/swap/AdvancedSwapDetails.tsx
|
||||
msgid "after slippage"
|
||||
msgstr "بعد الانزلاق السعري"
|
||||
@@ -2684,8 +2711,8 @@ msgid "{0, plural, =1 {Import token} other {Import tokens}}"
|
||||
msgstr "{0, plural, =1 {رمز الاستيراد} other {استيراد الرموز}}"
|
||||
|
||||
#: src/constants/tokenSafety.tsx
|
||||
msgid "{0, plural, =1 {This token isn't verified} other {These tokens aren't verified}}"
|
||||
msgstr "{0, plural, =1 {لم يتم التحقق من هذا الرمز} other {لم يتم التحقق من هذه الرموز}}"
|
||||
msgid "{0, plural, =1 {This token isn't verified.} other {These tokens aren't verified.}}"
|
||||
msgstr "{0, plural, =1 {لم يتم التحقق من هذا الرمز المميز.} other {لم يتم التحقق من هذه الرموز المميزة.}}"
|
||||
|
||||
#: src/constants/tokenSafety.tsx
|
||||
msgid "{0, plural, =1 {You can't trade this token using the Uniswap App.} other {You can't trade these tokens using the Uniswap App.}}"
|
||||
@@ -2758,10 +2785,6 @@ msgstr "{0} رموز UNI-V2 LP متاحة"
|
||||
msgid "{0} Votes"
|
||||
msgstr "{0} أصوات"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
msgid "{0} all balances"
|
||||
msgstr "{0} جميع الأرصدة"
|
||||
|
||||
#: src/components/PositionPreview/index.tsx
|
||||
#: src/components/PositionPreview/index.tsx
|
||||
#: src/components/PositionPreview/index.tsx
|
||||
@@ -2839,6 +2862,10 @@ msgstr "{USER_AMOUNT} UNI"
|
||||
msgid "{activeTokensOnThisChain} tokens"
|
||||
msgstr "{activeTokensOnThisChain} توكينز"
|
||||
|
||||
#: src/components/Header/ChainConnectivityWarning.tsx
|
||||
msgid "{label} might be down right now, or you may have lost your network connection."
|
||||
msgstr "قد يكون الرقم {label} معطلاً الآن ، أو ربما فقدت اتصالك بالشبكة."
|
||||
|
||||
#: src/components/NetworkAlert/NetworkAlert.tsx
|
||||
msgid "{label} token bridge"
|
||||
msgstr "{label} جسر رمزي"
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: uniswap-interface\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: 2022-09-21 21:05\n"
|
||||
"PO-Revision-Date: 2022-10-11 02:57\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: ca_ES\n"
|
||||
"Language-Team: Catalan\n"
|
||||
@@ -21,10 +21,6 @@ msgstr ""
|
||||
msgid "$-"
|
||||
msgstr "$-"
|
||||
|
||||
#: src/components/CurrencyInputPanel/FiatValue.tsx
|
||||
msgid "$<0/>"
|
||||
msgstr "<0/> $"
|
||||
|
||||
#: src/components/earn/PoolCard.tsx
|
||||
#: src/components/swap/GasEstimateBadge.tsx
|
||||
#: src/pages/Pool/PositionPage.tsx
|
||||
@@ -63,6 +59,10 @@ msgstr "0 UNI / setmana"
|
||||
msgid "24H volume"
|
||||
msgstr "Volum 24 h"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "24H volume is the amount of the asset that has been traded on Uniswap v3 during the past 24 hours."
|
||||
msgstr "El volum de 24 hores és la quantitat de l'actiu que s'ha negociat a Uniswap v3 durant les últimes 24 hores."
|
||||
|
||||
#: src/pages/RemoveLiquidity/V3.tsx
|
||||
msgid "25%"
|
||||
msgstr "25%"
|
||||
@@ -240,7 +240,7 @@ msgid "Amount"
|
||||
msgstr "Import"
|
||||
|
||||
#: src/components/Tokens/TokenTable/TokenTable.tsx
|
||||
msgid "An error occured loading tokens. Please try again."
|
||||
msgid "An error occurred loading tokens. Please try again."
|
||||
msgstr "S'ha produït un error en carregar fitxes. Siusplau torna-ho a provar."
|
||||
|
||||
#: src/utils/swapErrorToUserReadableMessage.tsx
|
||||
@@ -373,11 +373,14 @@ msgstr "Si afegiu liquiditat, guanyareu el 0,3% de totes les operacions d’aque
|
||||
msgid "By adding this list you are implicitly trusting that the data is correct. Anyone can create a list, including creating fake versions of existing lists and lists that claim to represent projects that do not have one."
|
||||
msgstr "En afegir aquesta llista, confieu implícitament que les dades són correctes. Qualsevol persona pot crear una llista, inclosa la creació de versions falses de llistes existents i llistes que afirmen representar projectes que no en tenen cap."
|
||||
|
||||
#: src/components/WalletModal/index.tsx
|
||||
#: src/components/WalletModal/index.tsx
|
||||
msgid "By connecting a wallet, you agree to Uniswap Labs’ <0>Terms of Service</0> and acknowledge that you have read and understand the Uniswap <1>Protocol Disclaimer</1>."
|
||||
msgstr "En connectar una cartera, acceptes les Condicions del servei <0></0> i reconeixes que has llegit i entén l'exempció <1>responsabilitat del protocol d'</1>."
|
||||
|
||||
#: src/components/WalletModal/index.tsx
|
||||
msgid "By connecting a wallet, you agree to Uniswap Labs’ <0>Terms of Service</0> and consent to its <1>Privacy Policy</1>."
|
||||
msgstr "En connectar una cartera, accepteu les <0>Condicions del servei</0> d'Uniswap Labs i accepteu la seva <1>Política de privadesa</1>."
|
||||
|
||||
#: src/pages/Vote/styled.tsx
|
||||
msgid "Canceled"
|
||||
msgstr "Cancel·lat"
|
||||
@@ -605,11 +608,13 @@ msgid "Contract address"
|
||||
msgstr "Adreça del contracte"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
#: src/nft/components/profile/view/ProfileAccountDetails.tsx
|
||||
#: src/theme/components.tsx
|
||||
msgid "Copied!"
|
||||
msgstr "Copiat!"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
#: src/nft/components/profile/view/ProfileAccountDetails.tsx
|
||||
msgid "Copy"
|
||||
msgstr "Còpia"
|
||||
|
||||
@@ -889,10 +894,6 @@ msgstr "L'execució d'aquesta proposta promulgarà les dades de la convocatòria
|
||||
msgid "Execution Submitted"
|
||||
msgstr "Execució presentada"
|
||||
|
||||
#: src/components/SearchModal/CurrencyList/index.tsx
|
||||
msgid "Expanded results from inactive Token Lists"
|
||||
msgstr "Resultats ampliats de llistes de fitxes inactives"
|
||||
|
||||
#: src/components/swap/AdvancedSwapDetails.tsx
|
||||
msgid "Expected Output"
|
||||
msgstr "Sortida esperada"
|
||||
@@ -913,10 +914,9 @@ msgstr "Caducat"
|
||||
msgid "Explore"
|
||||
msgstr "Explora"
|
||||
|
||||
#: src/pages/Tokens/index.tsx
|
||||
#: src/pages/Tokens/index.tsx
|
||||
msgid "Explore Tokens"
|
||||
msgstr "Exploreu fitxes"
|
||||
#: src/components/Tokens/TokensBanner.tsx
|
||||
msgid "Explore Top Tokens on Uniswap"
|
||||
msgstr "Exploreu les fitxes principals a Uniswap"
|
||||
|
||||
#: src/pages/Pool/CTACards.tsx
|
||||
msgid "Explore Uniswap Analytics."
|
||||
@@ -1130,6 +1130,10 @@ msgstr "Tema de la llum"
|
||||
msgid "Light theme"
|
||||
msgstr "Tema lleuger"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/About.tsx
|
||||
msgid "Links"
|
||||
msgstr "Enllaços"
|
||||
|
||||
#: src/components/claim/ClaimModal.tsx
|
||||
#: src/pages/Pool/PositionPage.tsx
|
||||
msgid "Liquidity"
|
||||
@@ -1262,6 +1266,11 @@ msgstr "Min:"
|
||||
msgid "Minimum received"
|
||||
msgstr "Mínim rebut"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/PriceChart.tsx
|
||||
#: src/components/Tokens/TokenDetails/PriceChart.tsx
|
||||
msgid "Missing chart data"
|
||||
msgstr "Falten dades del gràfic"
|
||||
|
||||
#: src/lib/hooks/swap/useSwapCallback.tsx
|
||||
msgid "Missing dependencies"
|
||||
msgstr "Falten dependències"
|
||||
@@ -1270,7 +1279,7 @@ msgstr "Falten dependències"
|
||||
msgid "More"
|
||||
msgstr "Més"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "NFT Collections"
|
||||
msgstr "Col·leccions NFT"
|
||||
|
||||
@@ -1298,6 +1307,10 @@ msgstr "Les tarifes de la xarxa superen el 50% de l'import de l'intercanvi!"
|
||||
msgid "New Position"
|
||||
msgstr "Nova posició"
|
||||
|
||||
#: src/nft/components/profile/view/EmptyWalletContent.tsx
|
||||
msgid "No NFTs in"
|
||||
msgstr "No hi ha NFT"
|
||||
|
||||
#: src/pages/MigrateV2/index.tsx
|
||||
msgid "No V2 Liquidity found."
|
||||
msgstr "No s'ha trobat cap liquiditat V2."
|
||||
@@ -1339,7 +1352,7 @@ msgstr "No hi ha informació de testimoni disponible"
|
||||
msgid "No tokens found"
|
||||
msgstr "No s'han trobat fitxes"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "No tokens found."
|
||||
msgstr "No s'han trobat fitxes."
|
||||
|
||||
@@ -1468,11 +1481,11 @@ msgstr "Agrupat {0}:"
|
||||
msgid "Pools"
|
||||
msgstr "Piscines"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "Popular NFT collections"
|
||||
msgstr "Col·leccions populars de NFT"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "Popular tokens"
|
||||
msgstr "Fitxes populars"
|
||||
|
||||
@@ -1577,10 +1590,6 @@ msgstr "Fent cua"
|
||||
msgid "Rates"
|
||||
msgstr "Tarifes"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/About.tsx
|
||||
msgid "Read more"
|
||||
msgstr "Llegeix més"
|
||||
|
||||
#: src/pages/Earn/index.tsx
|
||||
msgid "Read more about UNI"
|
||||
msgstr "Llegiu més sobre UNI"
|
||||
@@ -1601,7 +1610,7 @@ msgstr "Obteniu més informació sobre els recursos no compatibles"
|
||||
msgid "Recent Transactions"
|
||||
msgstr "Transaccions recents"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "Recent searches"
|
||||
msgstr "Recerques recents"
|
||||
|
||||
@@ -1719,10 +1728,6 @@ msgstr "Auto"
|
||||
msgid "Self Delegate"
|
||||
msgstr "Autodelegat"
|
||||
|
||||
#: src/components/NavBar/MenuDropdown.tsx
|
||||
msgid "Sell NFTs"
|
||||
msgstr "Vendre NFT"
|
||||
|
||||
#: src/pages/AddLiquidity/index.tsx
|
||||
#: src/pages/AddLiquidity/index.tsx
|
||||
#: src/pages/MigrateV2/MigrateV2Pair.tsx
|
||||
@@ -1755,6 +1760,10 @@ msgstr "Mostra cancel·lada"
|
||||
msgid "Show closed positions"
|
||||
msgstr "Mostra posicions tancades"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/About.tsx
|
||||
msgid "Show more"
|
||||
msgstr "Mostra més"
|
||||
|
||||
#: src/pages/RemoveLiquidity/index.tsx
|
||||
msgid "Simple"
|
||||
msgstr "Senzill"
|
||||
@@ -1771,6 +1780,14 @@ msgstr "Alguns recursos no estan disponibles a través d’aquesta interfície p
|
||||
msgid "Something went wrong"
|
||||
msgstr "Alguna cosa ha anat malament"
|
||||
|
||||
#: src/components/Tokens/TokensBanner.tsx
|
||||
msgid "Sort and filter assets across networks on the new Tokens page."
|
||||
msgstr "Ordena i filtra els actius a través de les xarxes a la nova pàgina de fitxes."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "Stats"
|
||||
msgstr "Estadístiques"
|
||||
|
||||
#: src/pages/Earn/Manage.tsx
|
||||
msgid "Step 1. Get UNI-V2 Liquidity tokens"
|
||||
msgstr "Pas 1. Obteniu fitxes de liquiditat UNI-V2"
|
||||
@@ -1806,7 +1823,7 @@ msgstr "Subministrant {0} {1} i {2} {3}"
|
||||
#: src/components/AccountDetailsV2/TransactionBody.tsx
|
||||
#: src/components/Header/index.tsx
|
||||
#: src/components/NavBar/index.tsx
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
#: src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx
|
||||
#: src/components/swap/SwapHeader.tsx
|
||||
#: src/pages/Swap/index.tsx
|
||||
#: src/pages/Swap/index.tsx
|
||||
@@ -1848,6 +1865,10 @@ msgstr "Intercanvi de {0} {1} per {2} {3}"
|
||||
msgid "Symbol not found"
|
||||
msgstr "No s'ha trobat el símbol"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "TVL"
|
||||
msgstr "TVL"
|
||||
|
||||
#: src/components/Popups/SurveyPopup.tsx
|
||||
msgid "Take a 10 minute survey to help us improve your experience in the Uniswap app."
|
||||
msgstr "Fes una enquesta de 10 minuts per ajudar-nos a millorar la teva experiència a l'aplicació Uniswap."
|
||||
@@ -1962,14 +1983,6 @@ msgstr "No s'ha pogut enviar la transacció perquè s'ha acabat el termini. Comp
|
||||
msgid "There is no liquidity data."
|
||||
msgstr "No hi ha dades de liquiditat."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
msgid "There was an error fetching your balance"
|
||||
msgstr "S'ha produït un error en recuperar el vostre saldo"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/BalanceSummary.tsx
|
||||
msgid "There was an error loading your {0} balance"
|
||||
msgstr "S'ha produït un error en carregar el vostre saldo {0}"
|
||||
|
||||
#: src/components/ConnectedAccountBlocked/index.tsx
|
||||
msgid "This address is blocked on the Uniswap Labs interface because it is associated with one or more"
|
||||
msgstr "Aquesta adreça està bloquejada a la interfície d'Uniswap Labs perquè està associada amb una o més"
|
||||
@@ -1994,6 +2007,10 @@ msgstr "Aquesta ruta optimitza la producció total tenint en compte les rutes di
|
||||
msgid "This token doesn't appear on the active token list(s). Make sure this is the token that you want to trade."
|
||||
msgstr "Aquest testimoni no apareix a la llista de fitxes actives. Assegureu-vos que aquest sigui el testimoni que voleu canviar."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/PriceChart.tsx
|
||||
msgid "This token doesn't have chart data because it hasn't been traded on Uniswap v3"
|
||||
msgstr "Aquest testimoni no té dades de gràfics perquè no s'ha negociat a Uniswap v3"
|
||||
|
||||
#: src/components/SearchModal/BlockedToken.tsx
|
||||
msgid "This token is not supported in the Uniswap Labs app"
|
||||
msgstr "Aquest testimoni no és compatible amb l'aplicació Uniswap Labs"
|
||||
@@ -2033,23 +2050,20 @@ msgid "Token not supported"
|
||||
msgstr "El testimoni no és compatible"
|
||||
|
||||
#: src/components/Header/index.tsx
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
#: src/components/NavBar/index.tsx
|
||||
#: src/components/SearchModal/Manage.tsx
|
||||
msgid "Tokens"
|
||||
msgstr "Fitxes"
|
||||
|
||||
#: src/components/SearchModal/CurrencyList/index.tsx
|
||||
msgid "Tokens from inactive lists. Import specific tokens below or click Manage to activate more lists."
|
||||
msgstr "Fitxes de llistes inactives. Importeu fitxes específiques a continuació o feu clic a Gestiona per activar més llistes."
|
||||
|
||||
#: src/pages/Pool/CTACards.tsx
|
||||
msgid "Top pools"
|
||||
msgstr "Grups principals"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "Total Value Locked"
|
||||
msgstr "Valor total bloquejat"
|
||||
#: src/pages/Tokens/index.tsx
|
||||
#: src/pages/Tokens/index.tsx
|
||||
msgid "Top tokens on Uniswap"
|
||||
msgstr "Les fitxes principals d'Uniswap"
|
||||
|
||||
#: src/components/earn/PoolCard.tsx
|
||||
msgid "Total deposited"
|
||||
@@ -2059,6 +2073,10 @@ msgstr "Total dipositat"
|
||||
msgid "Total deposits"
|
||||
msgstr "Dipòsits totals"
|
||||
|
||||
#: src/components/Tokens/TokenTable/TokenRow.tsx
|
||||
msgid "Total value locked (TVL) is the amount of the asset that’s currently in a Uniswap v3 liquidity pool."
|
||||
msgstr "El valor total bloquejat (TVL) és la quantitat de l'actiu que es troba actualment en un conjunt de liquiditat Uniswap v3."
|
||||
|
||||
#: src/components/swap/RouterLabel.tsx
|
||||
msgid "Trade Route"
|
||||
msgstr "Ruta del comerç"
|
||||
@@ -2263,10 +2281,18 @@ msgstr "V3 {0} Preu:"
|
||||
msgid "View accrued fees and analytics<0>↗</0>"
|
||||
msgstr "Consulteu els honoraris i les taxes acumulades <0> ↗</0>"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
msgid "View and sell NFTs"
|
||||
msgstr "Veure i vendre NFT"
|
||||
|
||||
#: src/components/SearchModal/ManageLists.tsx
|
||||
msgid "View list"
|
||||
msgstr "Veure llista"
|
||||
|
||||
#: src/components/NavBar/MenuDropdown.tsx
|
||||
msgid "View more analytics"
|
||||
msgstr "Veure més analítiques"
|
||||
|
||||
#: src/components/TransactionConfirmationModal/index.tsx
|
||||
#: src/pages/CreateProposal/ProposalSubmissionModal.tsx
|
||||
msgid "View on Etherscan"
|
||||
@@ -2278,10 +2304,6 @@ msgstr "Veure a Etherscan"
|
||||
msgid "View on Explorer"
|
||||
msgstr "Veure a Explorer"
|
||||
|
||||
#: src/components/NavBar/MenuDropdown.tsx
|
||||
msgid "View token analytics"
|
||||
msgstr "Veure l'anàlisi de testimonis"
|
||||
|
||||
#: src/components/ModalViews/index.tsx
|
||||
#: src/components/claim/AddressClaimModal.tsx
|
||||
#: src/components/claim/ClaimModal.tsx
|
||||
@@ -2291,6 +2313,10 @@ msgstr "Veure l'anàlisi de testimonis"
|
||||
msgid "View transaction on Explorer"
|
||||
msgstr "Veure la transacció a Explorer"
|
||||
|
||||
#: src/components/Tokens/TokenTable/TokenRow.tsx
|
||||
msgid "Volume is the amount of the asset that has been traded on Uniswap v3 during the selected time frame."
|
||||
msgstr "El volum és la quantitat de l'actiu que s'ha negociat a Uniswap v3 durant el període de temps seleccionat."
|
||||
|
||||
#: src/components/Header/index.tsx
|
||||
msgid "Vote"
|
||||
msgstr "Vota"
|
||||
@@ -2472,10 +2498,6 @@ msgstr "Encara no teniu liquiditat en aquest grup."
|
||||
msgid "You have no favorited tokens"
|
||||
msgstr "No tens fitxes preferides"
|
||||
|
||||
#: src/components/Header/ChainConnectivityWarning.tsx
|
||||
msgid "You may have lost your network connection, or {label} might be down right now."
|
||||
msgstr "És possible que hàgiu perdut la connexió de xarxa o que {label} estigui inactiu en aquest moment."
|
||||
|
||||
#: src/components/Header/ChainConnectivityWarning.tsx
|
||||
msgid "You may have lost your network connection."
|
||||
msgstr "És possible que hàgiu perdut la connexió de xarxa."
|
||||
@@ -2517,9 +2539,9 @@ msgstr "La vostra liquiditat V2"
|
||||
msgid "Your active V3 liquidity positions will appear here."
|
||||
msgstr "Les vostres posicions de liquiditat V3 actives apareixeran aquí."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
msgid "Your balances by network"
|
||||
msgstr "Els teus saldos per xarxa"
|
||||
#: src/components/Tokens/TokenDetails/BalanceSummary.tsx
|
||||
msgid "Your balance"
|
||||
msgstr "El teu saldo"
|
||||
|
||||
#: src/pages/Pool/index.tsx
|
||||
msgid "Your connected network is unsupported."
|
||||
@@ -2608,6 +2630,11 @@ msgstr "Les vostres transaccions apareixeran aquí..."
|
||||
msgid "Your unclaimed UNI"
|
||||
msgstr "La vostra UNI no reclamada"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx
|
||||
#: src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx
|
||||
msgid "Your {0} balance"
|
||||
msgstr "El teu saldo {0}"
|
||||
|
||||
#: src/components/swap/AdvancedSwapDetails.tsx
|
||||
msgid "after slippage"
|
||||
msgstr "després del lliscament"
|
||||
@@ -2684,8 +2711,8 @@ msgid "{0, plural, =1 {Import token} other {Import tokens}}"
|
||||
msgstr "{0, plural, =1 {Fitxa d'importació} other {Importa fitxes}}"
|
||||
|
||||
#: src/constants/tokenSafety.tsx
|
||||
msgid "{0, plural, =1 {This token isn't verified} other {These tokens aren't verified}}"
|
||||
msgstr "{0, plural, =1 {Aquest testimoni no està verificat} other {Aquestes fitxes no estan verificades}}"
|
||||
msgid "{0, plural, =1 {This token isn't verified.} other {These tokens aren't verified.}}"
|
||||
msgstr "{0, plural, =1 {Aquest testimoni no està verificat.} other {Aquestes fitxes no estan verificades.}}"
|
||||
|
||||
#: src/constants/tokenSafety.tsx
|
||||
msgid "{0, plural, =1 {You can't trade this token using the Uniswap App.} other {You can't trade these tokens using the Uniswap App.}}"
|
||||
@@ -2758,10 +2785,6 @@ msgstr "{0} fitxes UNI-V2 LP disponibles"
|
||||
msgid "{0} Votes"
|
||||
msgstr "{0} vVots"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
msgid "{0} all balances"
|
||||
msgstr "{0} tots els saldos"
|
||||
|
||||
#: src/components/PositionPreview/index.tsx
|
||||
#: src/components/PositionPreview/index.tsx
|
||||
#: src/components/PositionPreview/index.tsx
|
||||
@@ -2839,6 +2862,10 @@ msgstr "{USER_AMOUNT} UNI"
|
||||
msgid "{activeTokensOnThisChain} tokens"
|
||||
msgstr "{activeTokensOnThisChain} fitxes"
|
||||
|
||||
#: src/components/Header/ChainConnectivityWarning.tsx
|
||||
msgid "{label} might be down right now, or you may have lost your network connection."
|
||||
msgstr "És possible que {label} estigui inactiva en aquest moment o que hagis perdut la connexió a la xarxa."
|
||||
|
||||
#: src/components/NetworkAlert/NetworkAlert.tsx
|
||||
msgid "{label} token bridge"
|
||||
msgstr "{label} pont de fitxes"
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: uniswap-interface\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: 2022-09-21 21:05\n"
|
||||
"PO-Revision-Date: 2022-10-11 02:57\n"
|
||||
"Last-Translator: \n"
|
||||
"Language: cs_CZ\n"
|
||||
"Language-Team: Czech\n"
|
||||
@@ -21,10 +21,6 @@ msgstr ""
|
||||
msgid "$-"
|
||||
msgstr "$-"
|
||||
|
||||
#: src/components/CurrencyInputPanel/FiatValue.tsx
|
||||
msgid "$<0/>"
|
||||
msgstr "<0/>$"
|
||||
|
||||
#: src/components/earn/PoolCard.tsx
|
||||
#: src/components/swap/GasEstimateBadge.tsx
|
||||
#: src/pages/Pool/PositionPage.tsx
|
||||
@@ -63,6 +59,10 @@ msgstr "0 UNI / týden"
|
||||
msgid "24H volume"
|
||||
msgstr "Hlasitost 24h"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "24H volume is the amount of the asset that has been traded on Uniswap v3 during the past 24 hours."
|
||||
msgstr "Objem za 24 hodin je množství aktiv, které bylo zobchodováno na Uniswap v3 během posledních 24 hodin."
|
||||
|
||||
#: src/pages/RemoveLiquidity/V3.tsx
|
||||
msgid "25%"
|
||||
msgstr "25 %"
|
||||
@@ -240,7 +240,7 @@ msgid "Amount"
|
||||
msgstr "Částka"
|
||||
|
||||
#: src/components/Tokens/TokenTable/TokenTable.tsx
|
||||
msgid "An error occured loading tokens. Please try again."
|
||||
msgid "An error occurred loading tokens. Please try again."
|
||||
msgstr "Při načítání tokenů došlo k chybě. Prosím zkuste to znovu."
|
||||
|
||||
#: src/utils/swapErrorToUserReadableMessage.tsx
|
||||
@@ -373,11 +373,14 @@ msgstr "Přidáním likvidity získáte 0,3 % všech obchodů na tomto páru úm
|
||||
msgid "By adding this list you are implicitly trusting that the data is correct. Anyone can create a list, including creating fake versions of existing lists and lists that claim to represent projects that do not have one."
|
||||
msgstr "Přidáním tohoto seznamu implicitně vyjadřujete důvěru v to, že data jsou správná. Seznam může vytvořit kdokoli, včetně vytváření falešných verzí existujících seznamů a seznamů, které prohlašují, že reprezentují projekty, které žádný seznam nemají."
|
||||
|
||||
#: src/components/WalletModal/index.tsx
|
||||
#: src/components/WalletModal/index.tsx
|
||||
msgid "By connecting a wallet, you agree to Uniswap Labs’ <0>Terms of Service</0> and acknowledge that you have read and understand the Uniswap <1>Protocol Disclaimer</1>."
|
||||
msgstr "Připojením peněženky souhlasíte s <0>podmínkami služby</0> a potvrzujete, že jste si přečetli a rozumíte prohlášení o vyloučení odpovědnosti za protokol <1></1>."
|
||||
|
||||
#: src/components/WalletModal/index.tsx
|
||||
msgid "By connecting a wallet, you agree to Uniswap Labs’ <0>Terms of Service</0> and consent to its <1>Privacy Policy</1>."
|
||||
msgstr "Připojením peněženky souhlasíte s <0>Smluvními podmínkami</0> společnosti Uniswap Labs a souhlasíte s jejími <1>Zásadami ochrany osobních údajů</1>."
|
||||
|
||||
#: src/pages/Vote/styled.tsx
|
||||
msgid "Canceled"
|
||||
msgstr "Zrušeno"
|
||||
@@ -605,11 +608,13 @@ msgid "Contract address"
|
||||
msgstr "Adresa smlouvy"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
#: src/nft/components/profile/view/ProfileAccountDetails.tsx
|
||||
#: src/theme/components.tsx
|
||||
msgid "Copied!"
|
||||
msgstr "Zkopírováno!"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
#: src/nft/components/profile/view/ProfileAccountDetails.tsx
|
||||
msgid "Copy"
|
||||
msgstr "kopírovat"
|
||||
|
||||
@@ -889,10 +894,6 @@ msgstr "Provedení tohoto návrhu uzákoní data volání v řetězci."
|
||||
msgid "Execution Submitted"
|
||||
msgstr "Provedení předloženo"
|
||||
|
||||
#: src/components/SearchModal/CurrencyList/index.tsx
|
||||
msgid "Expanded results from inactive Token Lists"
|
||||
msgstr "Rozšířené výsledky z neaktivních seznamů žetonů"
|
||||
|
||||
#: src/components/swap/AdvancedSwapDetails.tsx
|
||||
msgid "Expected Output"
|
||||
msgstr "Očekávaný výstup"
|
||||
@@ -913,10 +914,9 @@ msgstr "Vypršela"
|
||||
msgid "Explore"
|
||||
msgstr "Prozkoumat"
|
||||
|
||||
#: src/pages/Tokens/index.tsx
|
||||
#: src/pages/Tokens/index.tsx
|
||||
msgid "Explore Tokens"
|
||||
msgstr "Prozkoumejte tokeny"
|
||||
#: src/components/Tokens/TokensBanner.tsx
|
||||
msgid "Explore Top Tokens on Uniswap"
|
||||
msgstr "Prozkoumejte nejlepší tokeny na Uniswapu"
|
||||
|
||||
#: src/pages/Pool/CTACards.tsx
|
||||
msgid "Explore Uniswap Analytics."
|
||||
@@ -1130,6 +1130,10 @@ msgstr "Lehké téma"
|
||||
msgid "Light theme"
|
||||
msgstr "Světlé téma"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/About.tsx
|
||||
msgid "Links"
|
||||
msgstr "Odkazy"
|
||||
|
||||
#: src/components/claim/ClaimModal.tsx
|
||||
#: src/pages/Pool/PositionPage.tsx
|
||||
msgid "Liquidity"
|
||||
@@ -1262,6 +1266,11 @@ msgstr "Minimum:"
|
||||
msgid "Minimum received"
|
||||
msgstr "Minimum přijato"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/PriceChart.tsx
|
||||
#: src/components/Tokens/TokenDetails/PriceChart.tsx
|
||||
msgid "Missing chart data"
|
||||
msgstr "Chybí data grafu"
|
||||
|
||||
#: src/lib/hooks/swap/useSwapCallback.tsx
|
||||
msgid "Missing dependencies"
|
||||
msgstr "Chybějící závislosti"
|
||||
@@ -1270,7 +1279,7 @@ msgstr "Chybějící závislosti"
|
||||
msgid "More"
|
||||
msgstr "Více"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "NFT Collections"
|
||||
msgstr "Sbírky NFT"
|
||||
|
||||
@@ -1298,6 +1307,10 @@ msgstr "Síťové poplatky přesahují 50 % swapové částky!"
|
||||
msgid "New Position"
|
||||
msgstr "Nová pozice"
|
||||
|
||||
#: src/nft/components/profile/view/EmptyWalletContent.tsx
|
||||
msgid "No NFTs in"
|
||||
msgstr "Žádné NFT"
|
||||
|
||||
#: src/pages/MigrateV2/index.tsx
|
||||
msgid "No V2 Liquidity found."
|
||||
msgstr "Nebyla nalezena likvidita V2."
|
||||
@@ -1339,7 +1352,7 @@ msgstr "Nejsou k dispozici žádné informace o tokenu"
|
||||
msgid "No tokens found"
|
||||
msgstr "Nebyly nalezeny žádné tokeny"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "No tokens found."
|
||||
msgstr "Nebyly nalezeny žádné tokeny."
|
||||
|
||||
@@ -1468,11 +1481,11 @@ msgstr "Sestaveno do fondu {0}:"
|
||||
msgid "Pools"
|
||||
msgstr "Bazény"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "Popular NFT collections"
|
||||
msgstr "Populární kolekce NFT"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "Popular tokens"
|
||||
msgstr "Populární tokeny"
|
||||
|
||||
@@ -1577,10 +1590,6 @@ msgstr "Zařazení do fronty"
|
||||
msgid "Rates"
|
||||
msgstr "Sazby"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/About.tsx
|
||||
msgid "Read more"
|
||||
msgstr "Přečtěte si více"
|
||||
|
||||
#: src/pages/Earn/index.tsx
|
||||
msgid "Read more about UNI"
|
||||
msgstr "Přečtěte si více o UNI"
|
||||
@@ -1601,7 +1610,7 @@ msgstr "Přečtěte si více o nepodporovaných aktivech"
|
||||
msgid "Recent Transactions"
|
||||
msgstr "Nedávné transakce"
|
||||
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
msgid "Recent searches"
|
||||
msgstr "Nedávná vyhledávání"
|
||||
|
||||
@@ -1719,10 +1728,6 @@ msgstr "Sám"
|
||||
msgid "Self Delegate"
|
||||
msgstr "Sobě delegovat"
|
||||
|
||||
#: src/components/NavBar/MenuDropdown.tsx
|
||||
msgid "Sell NFTs"
|
||||
msgstr "Prodám NFT"
|
||||
|
||||
#: src/pages/AddLiquidity/index.tsx
|
||||
#: src/pages/AddLiquidity/index.tsx
|
||||
#: src/pages/MigrateV2/MigrateV2Pair.tsx
|
||||
@@ -1755,6 +1760,10 @@ msgstr "Zobrazit zrušeno"
|
||||
msgid "Show closed positions"
|
||||
msgstr "Zobrazit uzavřené pozice"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/About.tsx
|
||||
msgid "Show more"
|
||||
msgstr "Zobrazit více"
|
||||
|
||||
#: src/pages/RemoveLiquidity/index.tsx
|
||||
msgid "Simple"
|
||||
msgstr "Jednoduché"
|
||||
@@ -1771,6 +1780,14 @@ msgstr "Některá aktiva nejsou přes toto rozhraní dostupná, protože nemusí
|
||||
msgid "Something went wrong"
|
||||
msgstr "Něco je špatně"
|
||||
|
||||
#: src/components/Tokens/TokensBanner.tsx
|
||||
msgid "Sort and filter assets across networks on the new Tokens page."
|
||||
msgstr "Seřaďte a filtrujte aktiva napříč sítěmi na nové stránce Tokeny."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "Stats"
|
||||
msgstr "Statistiky"
|
||||
|
||||
#: src/pages/Earn/Manage.tsx
|
||||
msgid "Step 1. Get UNI-V2 Liquidity tokens"
|
||||
msgstr "Krok 1. Získejte žetony likvidity UNI-V2"
|
||||
@@ -1806,7 +1823,7 @@ msgstr "Dodávání {0} {1} a {2} {3}"
|
||||
#: src/components/AccountDetailsV2/TransactionBody.tsx
|
||||
#: src/components/Header/index.tsx
|
||||
#: src/components/NavBar/index.tsx
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
#: src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx
|
||||
#: src/components/swap/SwapHeader.tsx
|
||||
#: src/pages/Swap/index.tsx
|
||||
#: src/pages/Swap/index.tsx
|
||||
@@ -1848,6 +1865,10 @@ msgstr "Výměna {0} {1} za {2} {3}"
|
||||
msgid "Symbol not found"
|
||||
msgstr "Symbol nenalezen"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "TVL"
|
||||
msgstr "TVL"
|
||||
|
||||
#: src/components/Popups/SurveyPopup.tsx
|
||||
msgid "Take a 10 minute survey to help us improve your experience in the Uniswap app."
|
||||
msgstr "Udělejte si 10minutový průzkum, který nám pomůže zlepšit vaši zkušenost s aplikací Uniswap."
|
||||
@@ -1962,14 +1983,6 @@ msgstr "Transakci nebylo možno odeslat, protože uplynula lhůta. Zkontrolujte,
|
||||
msgid "There is no liquidity data."
|
||||
msgstr "Neexistují žádné údaje o likviditě."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
msgid "There was an error fetching your balance"
|
||||
msgstr "Při načítání vašeho zůstatku došlo k chybě"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/BalanceSummary.tsx
|
||||
msgid "There was an error loading your {0} balance"
|
||||
msgstr "Při načítání vašeho zůstatku {0} došlo k chybě"
|
||||
|
||||
#: src/components/ConnectedAccountBlocked/index.tsx
|
||||
msgid "This address is blocked on the Uniswap Labs interface because it is associated with one or more"
|
||||
msgstr "Tato adresa je v rozhraní Uniswap Labs blokována, protože je přidružena k jedné nebo více"
|
||||
@@ -1994,6 +2007,10 @@ msgstr "Tato trasa optimalizuje váš celkový výkon zohledněním rozdělenýc
|
||||
msgid "This token doesn't appear on the active token list(s). Make sure this is the token that you want to trade."
|
||||
msgstr "Tento žeton není na seznamech aktivních žetonů. Ujistěte se, že toto je ten žeton, který chcete obchodovat."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/PriceChart.tsx
|
||||
msgid "This token doesn't have chart data because it hasn't been traded on Uniswap v3"
|
||||
msgstr "Tento token nemá data grafu, protože nebyl obchodován na Uniswap v3"
|
||||
|
||||
#: src/components/SearchModal/BlockedToken.tsx
|
||||
msgid "This token is not supported in the Uniswap Labs app"
|
||||
msgstr "Tento token není podporován v aplikaci Uniswap Labs"
|
||||
@@ -2033,23 +2050,20 @@ msgid "Token not supported"
|
||||
msgstr "Token není podporován"
|
||||
|
||||
#: src/components/Header/index.tsx
|
||||
#: src/components/NavBar/SearchBar.tsx
|
||||
#: src/components/NavBar/SearchBarDropdown.tsx
|
||||
#: src/components/NavBar/index.tsx
|
||||
#: src/components/SearchModal/Manage.tsx
|
||||
msgid "Tokens"
|
||||
msgstr "Žetony"
|
||||
|
||||
#: src/components/SearchModal/CurrencyList/index.tsx
|
||||
msgid "Tokens from inactive lists. Import specific tokens below or click Manage to activate more lists."
|
||||
msgstr "Žetony z neaktivních seznamů. Buď importujte konkrétní žetony níže nebo kliknutím na Správa aktivujte další seznamy."
|
||||
|
||||
#: src/pages/Pool/CTACards.tsx
|
||||
msgid "Top pools"
|
||||
msgstr "Nejvýše umístěné fondy"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/StatsSection.tsx
|
||||
msgid "Total Value Locked"
|
||||
msgstr "Celková hodnota uzamčena"
|
||||
#: src/pages/Tokens/index.tsx
|
||||
#: src/pages/Tokens/index.tsx
|
||||
msgid "Top tokens on Uniswap"
|
||||
msgstr "Nejlepší tokeny na Uniswapu"
|
||||
|
||||
#: src/components/earn/PoolCard.tsx
|
||||
msgid "Total deposited"
|
||||
@@ -2059,6 +2073,10 @@ msgstr "Celkem uloženo"
|
||||
msgid "Total deposits"
|
||||
msgstr "Vklady celkem"
|
||||
|
||||
#: src/components/Tokens/TokenTable/TokenRow.tsx
|
||||
msgid "Total value locked (TVL) is the amount of the asset that’s currently in a Uniswap v3 liquidity pool."
|
||||
msgstr "Uzamčená celková hodnota (TVL) je množství aktiva, které je aktuálně v fondu likvidity Uniswap v3."
|
||||
|
||||
#: src/components/swap/RouterLabel.tsx
|
||||
msgid "Trade Route"
|
||||
msgstr "Obchodní cesta"
|
||||
@@ -2263,10 +2281,18 @@ msgstr "V3 {0} cena:"
|
||||
msgid "View accrued fees and analytics<0>↗</0>"
|
||||
msgstr "Zobrazit naběhlé poplatky a analýzy<0>↗</0>"
|
||||
|
||||
#: src/components/WalletDropdown/AuthenticatedHeader.tsx
|
||||
msgid "View and sell NFTs"
|
||||
msgstr "Prohlížejte a prodávejte NFT"
|
||||
|
||||
#: src/components/SearchModal/ManageLists.tsx
|
||||
msgid "View list"
|
||||
msgstr "Zobrazit seznam"
|
||||
|
||||
#: src/components/NavBar/MenuDropdown.tsx
|
||||
msgid "View more analytics"
|
||||
msgstr "Zobrazit další analýzy"
|
||||
|
||||
#: src/components/TransactionConfirmationModal/index.tsx
|
||||
#: src/pages/CreateProposal/ProposalSubmissionModal.tsx
|
||||
msgid "View on Etherscan"
|
||||
@@ -2278,10 +2304,6 @@ msgstr "Pohled na Etherscan"
|
||||
msgid "View on Explorer"
|
||||
msgstr "Zobrazit v Průzkumníku"
|
||||
|
||||
#: src/components/NavBar/MenuDropdown.tsx
|
||||
msgid "View token analytics"
|
||||
msgstr "Zobrazit analýzu tokenů"
|
||||
|
||||
#: src/components/ModalViews/index.tsx
|
||||
#: src/components/claim/AddressClaimModal.tsx
|
||||
#: src/components/claim/ClaimModal.tsx
|
||||
@@ -2291,6 +2313,10 @@ msgstr "Zobrazit analýzu tokenů"
|
||||
msgid "View transaction on Explorer"
|
||||
msgstr "Zobrazit transakci v Průzkumníku"
|
||||
|
||||
#: src/components/Tokens/TokenTable/TokenRow.tsx
|
||||
msgid "Volume is the amount of the asset that has been traded on Uniswap v3 during the selected time frame."
|
||||
msgstr "Objem je množství aktiva, které bylo obchodováno na Uniswap v3 během zvoleného časového rámce."
|
||||
|
||||
#: src/components/Header/index.tsx
|
||||
msgid "Vote"
|
||||
msgstr "Hlasovat"
|
||||
@@ -2472,10 +2498,6 @@ msgstr "V tomto fondu zatím nemáte likviditu."
|
||||
msgid "You have no favorited tokens"
|
||||
msgstr "Nemáte žádné oblíbené žetony"
|
||||
|
||||
#: src/components/Header/ChainConnectivityWarning.tsx
|
||||
msgid "You may have lost your network connection, or {label} might be down right now."
|
||||
msgstr "Možná jste ztratili připojení k síti nebo {label} může být právě mimo provoz."
|
||||
|
||||
#: src/components/Header/ChainConnectivityWarning.tsx
|
||||
msgid "You may have lost your network connection."
|
||||
msgstr "Možná jste ztratili připojení k síti."
|
||||
@@ -2517,9 +2539,9 @@ msgstr "Vaše likvidita V2"
|
||||
msgid "Your active V3 liquidity positions will appear here."
|
||||
msgstr "Zde se zobrazí vaše aktivní pozice likvidity V3."
|
||||
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
msgid "Your balances by network"
|
||||
msgstr "Vaše zůstatky podle sítě"
|
||||
#: src/components/Tokens/TokenDetails/BalanceSummary.tsx
|
||||
msgid "Your balance"
|
||||
msgstr "Tvůj zůstatek"
|
||||
|
||||
#: src/pages/Pool/index.tsx
|
||||
msgid "Your connected network is unsupported."
|
||||
@@ -2608,6 +2630,11 @@ msgstr "Tady se budou zobrazovat vaše transakce..."
|
||||
msgid "Your unclaimed UNI"
|
||||
msgstr "Vaše nenárokované UNI"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx
|
||||
#: src/components/Tokens/TokenDetails/MobileBalanceSummaryFooter.tsx
|
||||
msgid "Your {0} balance"
|
||||
msgstr "Váš zůstatek {0}"
|
||||
|
||||
#: src/components/swap/AdvancedSwapDetails.tsx
|
||||
msgid "after slippage"
|
||||
msgstr "po uklouznutí"
|
||||
@@ -2684,7 +2711,7 @@ msgid "{0, plural, =1 {Import token} other {Import tokens}}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/tokenSafety.tsx
|
||||
msgid "{0, plural, =1 {This token isn't verified} other {These tokens aren't verified}}"
|
||||
msgid "{0, plural, =1 {This token isn't verified.} other {These tokens aren't verified.}}"
|
||||
msgstr ""
|
||||
|
||||
#: src/constants/tokenSafety.tsx
|
||||
@@ -2758,10 +2785,6 @@ msgstr "{0} dostupných žetonů UNI-V2 LP"
|
||||
msgid "{0} Votes"
|
||||
msgstr "{0} hlasů"
|
||||
|
||||
#: src/components/Tokens/TokenDetails/FooterBalanceSummary.tsx
|
||||
msgid "{0} all balances"
|
||||
msgstr "{0} všechny zůstatky"
|
||||
|
||||
#: src/components/PositionPreview/index.tsx
|
||||
#: src/components/PositionPreview/index.tsx
|
||||
#: src/components/PositionPreview/index.tsx
|
||||
@@ -2839,6 +2862,10 @@ msgstr "{USER_AMOUNT} UNI"
|
||||
msgid "{activeTokensOnThisChain} tokens"
|
||||
msgstr "{activeTokensOnThisChain} žetonů"
|
||||
|
||||
#: src/components/Header/ChainConnectivityWarning.tsx
|
||||
msgid "{label} might be down right now, or you may have lost your network connection."
|
||||
msgstr "{label} může být právě teď mimo provoz, nebo jste možná ztratili připojení k síti."
|
||||
|
||||
#: src/components/NetworkAlert/NetworkAlert.tsx
|
||||
msgid "{label} token bridge"
|
||||
msgstr "{label} token bridge"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user