Compare commits
207 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d3249e6bd | ||
|
|
f1c65afa98 | ||
|
|
80c1f0cdf9 | ||
|
|
ea0fe83d00 | ||
|
|
994836fba7 | ||
|
|
41aa1dcb0b | ||
|
|
3965d3fdd9 | ||
|
|
ff6fd8a6e9 | ||
|
|
0a6906b23e | ||
|
|
c38b5c0ce3 | ||
|
|
86f3b5a036 | ||
|
|
382a44f040 | ||
|
|
2d9604cd14 | ||
|
|
7930709bc3 | ||
|
|
6fe2c92cee | ||
|
|
884dee2db3 | ||
|
|
1f00c2a9c4 | ||
|
|
84070835df | ||
|
|
fb389137e7 | ||
|
|
c14b6a78ae | ||
|
|
a6c1c49f98 | ||
|
|
7848ad86bd | ||
|
|
882c15dada | ||
|
|
704ad222d9 | ||
|
|
cfee80ce3c | ||
|
|
eb95cedd72 | ||
|
|
99a7fb3383 | ||
|
|
7f4dbf9346 | ||
|
|
09b00c9974 | ||
|
|
b74fb8174d | ||
|
|
a7ec5a64b7 | ||
|
|
c619dcf65d | ||
|
|
1221d88e13 | ||
|
|
48d2ead71d | ||
|
|
ed7099bfd6 | ||
|
|
2604cdfdae | ||
|
|
94dc389812 | ||
|
|
4a8c621f46 | ||
|
|
477af8af4e | ||
|
|
a9a7d524aa | ||
|
|
1cdaff8ddf | ||
|
|
eeea3d2dcc | ||
|
|
f46b6a0697 | ||
|
|
622581ee0a | ||
|
|
eb725f51ce | ||
|
|
4d4462368b | ||
|
|
6fe5d4363d | ||
|
|
b46fa27084 | ||
|
|
ade2440613 | ||
|
|
4dc4620b60 | ||
|
|
202c2662f1 | ||
|
|
d2afd71c81 | ||
|
|
bad1ce2618 | ||
|
|
f194845b2b | ||
|
|
98d4e108e6 | ||
|
|
ab43ed1900 | ||
|
|
b147e047a5 | ||
|
|
bbb616f56c | ||
|
|
35a429ea65 | ||
|
|
bd16543c10 | ||
|
|
cbdeae276e | ||
|
|
e733113963 | ||
|
|
272b030b89 | ||
|
|
472a553d13 | ||
|
|
3a1bff146c | ||
|
|
b82b9acc54 | ||
|
|
fac3845756 | ||
|
|
9381a74f1d | ||
|
|
f6662a3208 | ||
|
|
134af82d90 | ||
|
|
75175b8e54 | ||
|
|
e3d8599dc7 | ||
|
|
77ee69ad52 | ||
|
|
4b82838f80 | ||
|
|
a177829976 | ||
|
|
b8b4f960dd | ||
|
|
2466414307 | ||
|
|
a3a32f0d68 | ||
|
|
ee001f86f0 | ||
|
|
87d6975bd8 | ||
|
|
4cab4e27ff | ||
|
|
f105f0995b | ||
|
|
18e89a7353 | ||
|
|
445f9a67a4 | ||
|
|
4c039c900c | ||
|
|
2c2e0a3419 | ||
|
|
1b43e0b28a | ||
|
|
a23f7782b2 | ||
|
|
723db9d0ea | ||
|
|
cbf165dc40 | ||
|
|
6f3579acf1 | ||
|
|
c9e2f86e57 | ||
|
|
8c0199119e | ||
|
|
5e6e6be888 | ||
|
|
79fb6485b1 | ||
|
|
29baaaf2ed | ||
|
|
f824fb25c2 | ||
|
|
28a6ea7e1a | ||
|
|
65566faf17 | ||
|
|
eb4f90e669 | ||
|
|
40308158ca | ||
|
|
d0d5240474 | ||
|
|
751ce8e6d6 | ||
|
|
748a5eadc0 | ||
|
|
5659fe21ea | ||
|
|
bc899b74a3 | ||
|
|
516c8b05a4 | ||
|
|
8502f9e303 | ||
|
|
bc90d416e6 | ||
|
|
2fd1cd72fd | ||
|
|
3a2276dcd1 | ||
|
|
c432c583f6 | ||
|
|
9b8f5ed8f4 | ||
|
|
0713a15028 | ||
|
|
62c502615f | ||
|
|
fdbe4b8f5e | ||
|
|
33c73f4dc8 | ||
|
|
c207a576e7 | ||
|
|
aa426514f3 | ||
|
|
ba9c28892e | ||
|
|
49c31ddfc8 | ||
|
|
4424205814 | ||
|
|
874f3fb737 | ||
|
|
ac27c89a44 | ||
|
|
0e530cf92e | ||
|
|
ed66b00b20 | ||
|
|
84fb05239b | ||
|
|
7500bbc0be | ||
|
|
c43c8de6cd | ||
|
|
9d40db5b21 | ||
|
|
a61eca36ae | ||
|
|
60479a442f | ||
|
|
5257188f70 | ||
|
|
7599239983 | ||
|
|
1561c0d000 | ||
|
|
1f740cf8c0 | ||
|
|
55c5f03004 | ||
|
|
a345cff614 | ||
|
|
85d8566cfa | ||
|
|
44d68e3ef0 | ||
|
|
04bd4900b0 | ||
|
|
81f277b36f | ||
|
|
575660d3e8 | ||
|
|
1e692491f1 | ||
|
|
b3639b3453 | ||
|
|
53ebf37b40 | ||
|
|
624ec33652 | ||
|
|
2890040118 | ||
|
|
68db8b3e23 | ||
|
|
9873491db1 | ||
|
|
5d64ab0146 | ||
|
|
568267ce07 | ||
|
|
69cdefe996 | ||
|
|
61758db589 | ||
|
|
9e9d98bb31 | ||
|
|
6b2b771dc4 | ||
|
|
031bea0f50 | ||
|
|
6f2e447ec3 | ||
|
|
cf831fbcea | ||
|
|
bcd4c1c182 | ||
|
|
d954026cea | ||
|
|
2c2dad1415 | ||
|
|
d42ed88845 | ||
|
|
e12c00e980 | ||
|
|
c25971e5d2 | ||
|
|
293e56758c | ||
|
|
a6b17f0437 | ||
|
|
140d59b898 | ||
|
|
85742c5785 | ||
|
|
9b07d8ce64 | ||
|
|
b1b9da1b17 | ||
|
|
ffe670923e | ||
|
|
21649967aa | ||
|
|
3f40f60c1c | ||
|
|
176c275a06 | ||
|
|
ae2b4b1668 | ||
|
|
a27f8e2937 | ||
|
|
818b1c84b0 | ||
|
|
75eceaa5e1 | ||
|
|
c6b4cc8e01 | ||
|
|
819302b51f | ||
|
|
c53d7fcc32 | ||
|
|
3de2e65530 | ||
|
|
c5319b6bea | ||
|
|
801ddc0886 | ||
|
|
dfd9196aa7 | ||
|
|
c4362297f5 | ||
|
|
96c23af99c | ||
|
|
6a29dacdeb | ||
|
|
9ddad80f2a | ||
|
|
1944fe4029 | ||
|
|
9921736102 | ||
|
|
11aa641dbc | ||
|
|
2f3290592b | ||
|
|
7b3fe73474 | ||
|
|
4237354bb7 | ||
|
|
f7354c9842 | ||
|
|
1636786af8 | ||
|
|
de0a716f41 | ||
|
|
9f108c406b | ||
|
|
5346d13674 | ||
|
|
d054079eeb | ||
|
|
fe6324f84d | ||
|
|
4a70eb5956 | ||
|
|
1a7b86d155 | ||
|
|
f66f8c4d59 | ||
|
|
1a9c3c3632 |
7
.env
@@ -1,3 +1,8 @@
|
||||
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
|
||||
REACT_APP_FORTMATIC_KEY="pk_live_357F77728B8EB880"
|
||||
REACT_APP_AMPLITUDE_TEST_KEY="add-the-real-test-key-if-you-need-to-test-amplitude-events"
|
||||
REACT_APP_AWS_API_REGION="us-east-2"
|
||||
REACT_APP_AWS_API_ACCESS_KEY="AKIAYJJWW6AQ47ODATHN"
|
||||
REACT_APP_AWS_API_ACCESS_SECRET="V9PoU0FhBP3cX760rPs9jMG/MIuDNLX6hYvVcaYO"
|
||||
REACT_APP_AWS_X_API_KEY="z9dReS5UtHu7iTrUsTuWRozLthi3AxOZlvobrIdr14"
|
||||
REACT_APP_AWS_API_ENDPOINT="https://beta.api.uniswap.org/v1/graphql"
|
||||
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
|
||||
|
||||
@@ -81,6 +81,18 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
{
|
||||
"name": "@ethersproject/providers",
|
||||
"message": "Please only use Providers instantiated in constants/providers to improve traceability.",
|
||||
"allowTypeImports": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
.gitignore
vendored
@@ -9,7 +9,6 @@
|
||||
/src/locales/**/pseudo.po
|
||||
|
||||
# generated graphql types
|
||||
/src/graphql/schema/
|
||||
__generated__/
|
||||
|
||||
# dependencies
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
overrideExisting: true
|
||||
schema: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'
|
||||
documents: 'src/**/!(*.d).{ts,tsx}'
|
||||
generates:
|
||||
./src/graphql/schema/schema.graphql:
|
||||
./src/graphql/thegraph/schema/schema.graphql:
|
||||
plugins:
|
||||
- schema-ast
|
||||
|
||||
50
cypress/e2e/wallet-dropdown.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
|
||||
import { getTestSelector } from '../utils'
|
||||
|
||||
describe('Wallet Dropdown', () => {
|
||||
before(() => {
|
||||
cy.visit('/', { featureFlags: [FeatureFlag.navBar, FeatureFlag.tokenSafety] })
|
||||
})
|
||||
|
||||
it('should change the theme', () => {
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).contains('Light theme').should('exist')
|
||||
})
|
||||
|
||||
it('should select a language', () => {
|
||||
cy.get(getTestSelector('wallet-select-language')).click()
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Taal')
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
})
|
||||
|
||||
it('should be able to view transactions', () => {
|
||||
cy.get(getTestSelector('wallet-transactions')).click()
|
||||
cy.get(getTestSelector('wallet-empty-transaction-text')).should('exist')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
})
|
||||
|
||||
it('should change the theme when not connected', () => {
|
||||
cy.get(getTestSelector('wallet-disconnect')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).click()
|
||||
cy.get(getTestSelector('wallet-select-theme')).contains('Dark theme').should('exist')
|
||||
})
|
||||
|
||||
it('should select a language when not connected', () => {
|
||||
cy.get(getTestSelector('wallet-select-language')).click()
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('Afrikaans').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Taal')
|
||||
cy.get(getTestSelector('wallet-language-item')).contains('English').click({ force: true })
|
||||
cy.get(getTestSelector('wallet-header')).should('contain', 'Language')
|
||||
cy.get(getTestSelector('wallet-back')).click()
|
||||
})
|
||||
|
||||
it('should open the wallet connect modal from the drop down when not connected', () => {
|
||||
cy.get(getTestSelector('wallet-connect-wallet')).click()
|
||||
cy.get(getTestSelector('wallet-modal')).should('exist')
|
||||
cy.get(getTestSelector('wallet-modal-close')).click()
|
||||
})
|
||||
})
|
||||
@@ -9,6 +9,8 @@
|
||||
import { injected } from './ethereum'
|
||||
import assert = require('assert')
|
||||
|
||||
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Cypress {
|
||||
@@ -17,6 +19,7 @@ declare global {
|
||||
}
|
||||
interface VisitOptions {
|
||||
serviceWorker?: true
|
||||
featureFlags?: Array<FeatureFlag>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +39,18 @@ Cypress.Commands.overwrite(
|
||||
options?.onBeforeLoad?.(win)
|
||||
win.localStorage.clear()
|
||||
win.localStorage.setItem('redux_localstorage_simple_user', '{"selectedWallet":"INJECTED"}')
|
||||
|
||||
if (options?.featureFlags) {
|
||||
const featureFlags = options.featureFlags.reduce(
|
||||
(flags, flag) => ({
|
||||
...flags,
|
||||
[flag]: 'enabled',
|
||||
}),
|
||||
{}
|
||||
)
|
||||
win.localStorage.setItem('featureFlags', JSON.stringify(featureFlags))
|
||||
}
|
||||
|
||||
win.ethereum = injected
|
||||
},
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
import { Wallet } from '@ethersproject/wallet'
|
||||
|
||||
|
||||
1
cypress/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const getTestSelector = (selectorId: string) => `[data-testid=${selectorId}]`
|
||||
18
fetch-schema.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/* eslint-disable */
|
||||
require('dotenv').config({ path: '.env.local' })
|
||||
const { exec } = require('child_process')
|
||||
const dataConfig = require('./relay.config')
|
||||
const thegraphConfig = require('./relay_thegraph.config')
|
||||
/* eslint-enable */
|
||||
|
||||
const THEGRAPH_API_URL = 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3'
|
||||
exec(`get-graphql-schema ${THEGRAPH_API_URL} > ${thegraphConfig.schema}`)
|
||||
|
||||
const API_URL = process.env.REACT_APP_GQL_API_URL
|
||||
const API_KEY = process.env.REACT_APP_GQL_API_KEY
|
||||
|
||||
if (API_URL && API_KEY) {
|
||||
exec(`get-graphql-schema ${API_URL} --h X-API-KEY=${API_KEY} > ${dataConfig.schema}`)
|
||||
} else {
|
||||
console.log('REACT_APP_GQL_API_URL or REACT_APP_GQL_API_KEY is missing from env.local')
|
||||
}
|
||||
36
package.json
@@ -8,8 +8,10 @@
|
||||
"contracts:compile:abi": "typechain --target ethers-v5 --out-dir src/abis/types \"./src/abis/**/*.json\"",
|
||||
"contracts:compile:v3": "typechain --target ethers-v5 --out-dir src/types/v3 \"./node_modules/@uniswap/**/artifacts/contracts/**/*[!dbg].json\"",
|
||||
"contracts:compile": "yarn contracts:compile:abi && yarn contracts:compile:v3",
|
||||
"relay": "relay-compiler",
|
||||
"graphql:generate": "graphql-codegen --config codegen.yml && yarn relay",
|
||||
"relay": "relay-compiler relay.config.js",
|
||||
"relay-thegraph": "relay-compiler relay_thegraph.config.js",
|
||||
"graphql:fetch": "node fetch-schema.js",
|
||||
"graphql:generate": "yarn relay && yarn relay-thegraph",
|
||||
"prei18n:extract": "node prei18n-extract.js",
|
||||
"i18n:extract": "lingui extract --locale en-US",
|
||||
"i18n:compile": "yarn i18n:extract && lingui compile",
|
||||
@@ -21,12 +23,8 @@
|
||||
"lint": "yarn eslint .",
|
||||
"test": "craco test --coverage",
|
||||
"cypress:open": "cypress open --browser chrome --e2e",
|
||||
"cypress:run": "cypress run --browser chrome --e2e"
|
||||
},
|
||||
"relay": {
|
||||
"src": "./src",
|
||||
"language": "typescript",
|
||||
"schema": "./src/graphql/schema/schema.graphql"
|
||||
"cypress:run": "cypress run --browser chrome --e2e",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
@@ -63,9 +61,6 @@
|
||||
"devDependencies": {
|
||||
"@craco/craco": "6.4.3",
|
||||
"@ethersproject/experimental": "^5.4.0",
|
||||
"@graphql-codegen/cli": "1.21.5",
|
||||
"@graphql-codegen/schema-ast": "^2.5.1",
|
||||
"@graphql-codegen/typescript-rtk-query": "^1.1.1",
|
||||
"@lingui/cli": "^3.9.0",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.1",
|
||||
@@ -95,9 +90,9 @@
|
||||
"@types/wcag-contrast": "^3.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4",
|
||||
"@typescript-eslint/parser": "^4",
|
||||
"babel-plugin-relay": "^14.1.0",
|
||||
"@vanilla-extract/babel-plugin": "^1.1.7",
|
||||
"@vanilla-extract/webpack-plugin": "^2.1.11",
|
||||
"babel-plugin-relay": "^14.1.0",
|
||||
"cypress": "^10.3.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"eslint": "^7.11.0",
|
||||
@@ -110,6 +105,8 @@
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"jest-styled-components": "^7.0.8",
|
||||
"ms.macro": "^2.0.0",
|
||||
"patch-package": "^6.4.7",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.7.1",
|
||||
"react-scripts": "^4.0.3",
|
||||
"relay-compiler": "^14.1.0",
|
||||
@@ -140,7 +137,7 @@
|
||||
"@uniswap/redux-multicall": "^1.1.5",
|
||||
"@uniswap/router-sdk": "^1.3.0",
|
||||
"@uniswap/sdk-core": "^3.0.1",
|
||||
"@uniswap/smart-order-router": "^2.9.2",
|
||||
"@uniswap/smart-order-router": "^2.10.0",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.30",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
@@ -148,7 +145,7 @@
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.9.0",
|
||||
"@uniswap/widgets": "^2.1.1",
|
||||
"@uniswap/widgets": "^2.8.1",
|
||||
"@vanilla-extract/css": "^1.7.2",
|
||||
"@vanilla-extract/css-utils": "^0.1.2",
|
||||
"@vanilla-extract/dynamic": "^2.0.2",
|
||||
@@ -173,15 +170,15 @@
|
||||
"ajv": "^6.12.3",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
"array.prototype.flatmap": "^1.2.4",
|
||||
"aws4fetch": "^1.0.13",
|
||||
"cids": "^1.0.0",
|
||||
"clsx": "^1.1.1",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"d3": "^7.6.1",
|
||||
"d3-curve-circlecorners": "^0.1.6",
|
||||
"ethers": "^5.1.4",
|
||||
"firebase": "^9.1.3",
|
||||
"focus-visible": "^5.2.0",
|
||||
"fortmatic": "^2.4.0",
|
||||
"get-graphql-schema": "^2.1.2",
|
||||
"graphql": "^16.5.0",
|
||||
"graphql-request": "^3.4.0",
|
||||
"immer": "^9.0.6",
|
||||
@@ -202,6 +199,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-feather": "^2.0.8",
|
||||
"react-ga4": "^1.4.1",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-is": "^17.0.2",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-popper": "^2.2.3",
|
||||
@@ -217,7 +215,6 @@
|
||||
"rebass": "^4.0.7",
|
||||
"redux": "^4.1.2",
|
||||
"redux-localstorage-simple": "^2.3.1",
|
||||
"relay-hooks": "^7.1.0",
|
||||
"setimmediate": "^1.0.5",
|
||||
"styled-components": "^5.3.5",
|
||||
"tiny-invariant": "^1.2.0",
|
||||
@@ -233,5 +230,10 @@
|
||||
"workbox-precaching": "^6.1.0",
|
||||
"workbox-routing": "^6.1.0",
|
||||
"zustand": "^4.0.0-rc.1"
|
||||
},
|
||||
"engines": {
|
||||
"npm": "please-use-yarn",
|
||||
"node": "14",
|
||||
"yarn": ">=1.22"
|
||||
}
|
||||
}
|
||||
|
||||
22
patches/@vanilla-extract+css+1.7.2.patch
Normal file
@@ -0,0 +1,22 @@
|
||||
diff --git a/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js b/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js
|
||||
index 6e40061..10283a2 100644
|
||||
--- a/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js
|
||||
+++ b/node_modules/@vanilla-extract/css/dist/vanilla-extract-css.cjs.dev.js
|
||||
@@ -177,11 +177,13 @@ function generateIdentifier(debugId) {
|
||||
var fileScopeHash = hash__default["default"](packageName ? "".concat(packageName).concat(filePath) : filePath);
|
||||
var identifier = "".concat(fileScopeHash).concat(refCount);
|
||||
|
||||
- if (adapter_dist_vanillaExtractCssAdapter.getIdentOption() === 'debug') {
|
||||
- var devPrefix = getDevPrefix(debugId);
|
||||
+ if (process.env.VANILLA_EXTRACT_DEV_PREFIX) {
|
||||
+ if (adapter_dist_vanillaExtractCssAdapter.getIdentOption() === 'debug') {
|
||||
+ var devPrefix = getDevPrefix(debugId);
|
||||
|
||||
- if (devPrefix) {
|
||||
- identifier = "".concat(devPrefix, "__").concat(identifier);
|
||||
+ if (devPrefix) {
|
||||
+ identifier = "".concat(devPrefix, "__").concat(identifier);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const exec = require('child_process').exec
|
||||
const { exec } = require('child_process')
|
||||
const isWindows = process.platform === 'win32' || /^(msys|cygwin)$/.test(process.env.OSTYPE)
|
||||
|
||||
if (isWindows) {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="theme-color" content="#ff007a" />
|
||||
<meta name="fortmatic-site-verification" content="j93LgcVZk79qcgyo" />
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when the app is installed as a PWA.
|
||||
|
||||
6
relay.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
src: './src',
|
||||
language: 'typescript',
|
||||
schema: './src/graphql/data/schema.graphql',
|
||||
exclude: ['**/node_modules/**', '**/__mocks__/**', '**/__generated__/**', '**/thegraph/**'],
|
||||
}
|
||||
9
relay_thegraph.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const defaultConfig = require('./relay.config')
|
||||
|
||||
module.exports = {
|
||||
src: defaultConfig.src,
|
||||
language: defaultConfig.language,
|
||||
schema: './src/graphql/thegraph/schema.graphql',
|
||||
exclude: ['**/node_modules/**', '**/__mocks__/**', '**/__generated__/**', '**/data/**'],
|
||||
}
|
||||
BIN
src/assets/images/arbitrumCircle.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
src/assets/images/celoCircle.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/images/nft-marketplaces.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
src/assets/images/optimismCircle.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/images/polygonCircle.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/assets/images/tokensPromoDark.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
src/assets/images/tokensPromoLight.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
3
src/assets/svg/socks.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="10" height="12" viewBox="0 0 10 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.75 11C4.75 11 5.22377 11.1391 5.80923 10.5828L7.64433 8.65908C8.35405 7.91508 8.74998 6.92678 8.74989 5.89856C8.7498 4.72716 8.74971 3.31706 8.74991 2.50009C8.74996 2.22391 8.77618 2 8.5 2H8.25M6.74898 5.75L6.74979 2L6.74991 1.50009C6.74996 1.22391 6.52609 1 6.24991 1H4.25167C3.97553 1 3.75167 1.22386 3.75167 1.5V4.75039C3.75167 5.29859 3.52665 5.82276 3.12922 6.20034L1.6891 7.56856C1.10364 8.12478 1.10363 9.0266 1.68909 9.58283C2.12197 9.99409 2.75372 10.1013 3.29025 9.90438C3.47937 9.83497 3.65665 9.72779 3.80923 9.58283L5.80923 7.6827M6.74898 5.75L6.7487 6.36119C6.74861 6.63517 6.63611 6.89711 6.43748 7.08582L5.80923 7.6827M6.74898 5.75H6.4384C5.67845 5.75 5.19623 6.56419 5.56146 7.23061L5.80923 7.6827" stroke="white" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 871 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#99A1BD" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 300 B |
@@ -1,13 +1,12 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsMetaMask } from 'connection/utils'
|
||||
import { Context, useCallback, useContext } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { ExternalLink as LinkIcon } from 'react-feather'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import { removeConnectedWallet } from 'state/wallets/reducer'
|
||||
import { DefaultTheme } from 'styled-components/macro'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
|
||||
import { ReactComponent as Close } from '../../assets/images/x.svg'
|
||||
@@ -25,7 +24,7 @@ const HeaderRow = styled.div`
|
||||
padding: 1rem 1rem;
|
||||
font-weight: 500;
|
||||
color: ${(props) => (props.color === 'blue' ? ({ theme }) => theme.deprecated_primary1 : 'inherit')};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
padding: 1rem;
|
||||
`};
|
||||
`
|
||||
@@ -75,7 +74,7 @@ const AccountGroupingRow = styled.div`
|
||||
|
||||
const AccountSection = styled.div`
|
||||
padding: 0rem 1rem;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`padding: 0rem 1rem 1.5rem 1rem;`};
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`padding: 0rem 1rem 1.5rem 1rem;`};
|
||||
`
|
||||
|
||||
const YourAccount = styled.div`
|
||||
@@ -146,7 +145,7 @@ const CloseIcon = styled.div`
|
||||
top: 14px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -207,7 +206,7 @@ export default function AccountDetails({
|
||||
const { chainId, account, connector } = useWeb3React()
|
||||
const connectionType = getConnection(connector).type
|
||||
|
||||
const theme = useContext(ThemeContext as Context<DefaultTheme>)
|
||||
const theme = useTheme()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const isMetaMask = getIsMetaMask()
|
||||
|
||||
88
src/components/AccountDetailsV2/LogoView.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { UNI_ADDRESS } from 'constants/addresses'
|
||||
import { TransactionInfo, TransactionType } from 'state/transactions/types'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
|
||||
import { nativeOnChain } from '../../constants/tokens'
|
||||
import { useCurrency } from '../../hooks/Tokens'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
|
||||
const CurrencyWrap = styled.div`
|
||||
position: relative;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
`
|
||||
|
||||
const CurrencyWrapStyles = css`
|
||||
position: absolute;
|
||||
height: 24px;
|
||||
`
|
||||
|
||||
const CurrencyLogoWrap = styled.span<{ isCentered: boolean }>`
|
||||
${CurrencyWrapStyles};
|
||||
left: ${({ isCentered }) => (isCentered ? '50%' : '0')};
|
||||
top: ${({ isCentered }) => (isCentered ? '50%' : '0')};
|
||||
transform: ${({ isCentered }) => isCentered && 'translate(-50%, -50%)'};
|
||||
`
|
||||
const CurrencyLogoWrapTwo = styled.span`
|
||||
${CurrencyWrapStyles};
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
`
|
||||
|
||||
interface CurrencyPair {
|
||||
currencyId0: string | undefined
|
||||
currencyId1: string | undefined
|
||||
}
|
||||
|
||||
const getCurrency = ({ info, chainId }: { info: TransactionInfo; chainId: number | undefined }): CurrencyPair => {
|
||||
switch (info.type) {
|
||||
case TransactionType.ADD_LIQUIDITY_V3_POOL:
|
||||
case TransactionType.REMOVE_LIQUIDITY_V3:
|
||||
case TransactionType.CREATE_V3_POOL:
|
||||
const { baseCurrencyId, quoteCurrencyId } = info
|
||||
return { currencyId0: baseCurrencyId, currencyId1: quoteCurrencyId }
|
||||
case TransactionType.SWAP:
|
||||
const { inputCurrencyId, outputCurrencyId } = info
|
||||
return { currencyId0: inputCurrencyId, currencyId1: outputCurrencyId }
|
||||
case TransactionType.WRAP:
|
||||
const { unwrapped } = info
|
||||
const native = info.chainId ? nativeOnChain(info.chainId) : undefined
|
||||
const base = 'ETH'
|
||||
const wrappedCurrency = native?.wrapped.address ?? 'WETH'
|
||||
return { currencyId0: unwrapped ? wrappedCurrency : base, currencyId1: unwrapped ? base : wrappedCurrency }
|
||||
case TransactionType.COLLECT_FEES:
|
||||
const { currencyId0, currencyId1 } = info
|
||||
return { currencyId0, currencyId1 }
|
||||
case TransactionType.APPROVAL:
|
||||
return { currencyId0: info.tokenAddress, currencyId1: undefined }
|
||||
case TransactionType.CLAIM:
|
||||
const uniAddress = chainId ? UNI_ADDRESS[chainId] : undefined
|
||||
return { currencyId0: uniAddress, currencyId1: undefined }
|
||||
default:
|
||||
return { currencyId0: undefined, currencyId1: undefined }
|
||||
}
|
||||
}
|
||||
|
||||
const LogoView = ({ info }: { info: TransactionInfo }) => {
|
||||
const { chainId } = useWeb3React()
|
||||
const { currencyId0, currencyId1 } = getCurrency({ info, chainId })
|
||||
const currency0 = useCurrency(currencyId0)
|
||||
const currency1 = useCurrency(currencyId1)
|
||||
const isCentered = !(currency0 && currency1)
|
||||
|
||||
return (
|
||||
<CurrencyWrap>
|
||||
<CurrencyLogoWrap isCentered={isCentered}>
|
||||
<CurrencyLogo size="24px" currency={currency0} />
|
||||
</CurrencyLogoWrap>
|
||||
{!isCentered && (
|
||||
<CurrencyLogoWrapTwo>
|
||||
<CurrencyLogo size="24px" currency={currency1} />
|
||||
</CurrencyLogoWrapTwo>
|
||||
)}
|
||||
</CurrencyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
export default LogoView
|
||||
337
src/components/AccountDetailsV2/TransactionBody.tsx
Normal file
@@ -0,0 +1,337 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Fraction, TradeType } from '@uniswap/sdk-core'
|
||||
import JSBI from 'jsbi'
|
||||
import {
|
||||
AddLiquidityV3PoolTransactionInfo,
|
||||
ApproveTransactionInfo,
|
||||
ClaimTransactionInfo,
|
||||
CollectFeesTransactionInfo,
|
||||
ExactInputSwapTransactionInfo,
|
||||
ExactOutputSwapTransactionInfo,
|
||||
RemoveLiquidityV3TransactionInfo,
|
||||
TransactionInfo,
|
||||
TransactionType,
|
||||
WrapTransactionInfo,
|
||||
} from 'state/transactions/types'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { nativeOnChain } from '../../constants/tokens'
|
||||
import { useCurrency, useToken } from '../../hooks/Tokens'
|
||||
import useENSName from '../../hooks/useENSName'
|
||||
import { shortenAddress } from '../../utils'
|
||||
import { TransactionState } from './index'
|
||||
|
||||
const HighlightText = styled.span`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-weight: 600;
|
||||
`
|
||||
|
||||
const BodyWrap = styled.div`
|
||||
line-height: 20px;
|
||||
`
|
||||
|
||||
interface ActionProps {
|
||||
pending: JSX.Element
|
||||
success: JSX.Element
|
||||
failed: JSX.Element
|
||||
transactionState: TransactionState
|
||||
}
|
||||
|
||||
const Action = ({ pending, success, failed, transactionState }: ActionProps) => {
|
||||
switch (transactionState) {
|
||||
case TransactionState.Failed:
|
||||
return failed
|
||||
case TransactionState.Success:
|
||||
return success
|
||||
default:
|
||||
return pending
|
||||
}
|
||||
}
|
||||
|
||||
const formatAmount = (amountRaw: string, decimals: number, sigFigs: number): string =>
|
||||
new Fraction(amountRaw, JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(decimals))).toSignificant(sigFigs)
|
||||
|
||||
const FailedText = ({ transactionState }: { transactionState: TransactionState }) =>
|
||||
transactionState === TransactionState.Failed ? <Trans>failed</Trans> : <span />
|
||||
|
||||
const FormattedCurrencyAmount = ({
|
||||
rawAmount,
|
||||
currencyId,
|
||||
sigFigs = 2,
|
||||
}: {
|
||||
rawAmount: string
|
||||
currencyId: string
|
||||
sigFigs: number
|
||||
}) => {
|
||||
const currency = useCurrency(currencyId)
|
||||
|
||||
return currency ? (
|
||||
<HighlightText>
|
||||
{formatAmount(rawAmount, currency.decimals, sigFigs)} {currency.symbol}
|
||||
</HighlightText>
|
||||
) : null
|
||||
}
|
||||
|
||||
const getRawAmounts = (
|
||||
info: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo
|
||||
): { rawAmountFrom: string; rawAmountTo: string } => {
|
||||
return info.tradeType === TradeType.EXACT_INPUT
|
||||
? { rawAmountFrom: info.inputCurrencyAmountRaw, rawAmountTo: info.expectedOutputCurrencyAmountRaw }
|
||||
: { rawAmountFrom: info.expectedInputCurrencyAmountRaw, rawAmountTo: info.outputCurrencyAmountRaw }
|
||||
}
|
||||
|
||||
const SwapSummary = ({
|
||||
info,
|
||||
transactionState,
|
||||
}: {
|
||||
info: ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Swapping</Trans>,
|
||||
success: <Trans>Swapped</Trans>,
|
||||
failed: <Trans>Swap</Trans>,
|
||||
}
|
||||
const { rawAmountFrom, rawAmountTo } = getRawAmounts(info)
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<FormattedCurrencyAmount rawAmount={rawAmountFrom} currencyId={info.inputCurrencyId} sigFigs={2} />{' '}
|
||||
<Trans>for </Trans>{' '}
|
||||
<FormattedCurrencyAmount rawAmount={rawAmountTo} currencyId={info.outputCurrencyId} sigFigs={2} />{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const AddLiquidityV3PoolSummary = ({
|
||||
info,
|
||||
transactionState,
|
||||
}: {
|
||||
info: AddLiquidityV3PoolTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const { createPool, quoteCurrencyId, baseCurrencyId } = info
|
||||
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Adding</Trans>,
|
||||
success: <Trans>Added</Trans>,
|
||||
failed: <Trans>Add</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
{createPool ? (
|
||||
<CreateV3PoolSummary info={info} transactionState={transactionState} />
|
||||
) : (
|
||||
<>
|
||||
<Action {...actionProps} />{' '}
|
||||
<FormattedCurrencyAmount rawAmount={info.expectedAmountBaseRaw} currencyId={baseCurrencyId} sigFigs={2} />{' '}
|
||||
<Trans>and</Trans>{' '}
|
||||
<FormattedCurrencyAmount rawAmount={info.expectedAmountQuoteRaw} currencyId={quoteCurrencyId} sigFigs={2} />
|
||||
</>
|
||||
)}{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const RemoveLiquidityV3Summary = ({
|
||||
info: { baseCurrencyId, quoteCurrencyId, expectedAmountBaseRaw, expectedAmountQuoteRaw },
|
||||
transactionState,
|
||||
}: {
|
||||
info: RemoveLiquidityV3TransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Removing</Trans>,
|
||||
success: <Trans>Removed</Trans>,
|
||||
failed: <Trans>Remove</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<FormattedCurrencyAmount rawAmount={expectedAmountBaseRaw} currencyId={baseCurrencyId} sigFigs={2} />{' '}
|
||||
<Trans>and</Trans>{' '}
|
||||
<FormattedCurrencyAmount rawAmount={expectedAmountQuoteRaw} currencyId={quoteCurrencyId} sigFigs={2} />{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const CreateV3PoolSummary = ({
|
||||
info: { baseCurrencyId, quoteCurrencyId },
|
||||
transactionState,
|
||||
}: {
|
||||
info: AddLiquidityV3PoolTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const baseCurrency = useCurrency(baseCurrencyId)
|
||||
const quoteCurrency = useCurrency(quoteCurrencyId)
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Creating</Trans>,
|
||||
success: <Trans>Created</Trans>,
|
||||
failed: <Trans>Create</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<HighlightText>
|
||||
{baseCurrency?.symbol}/{quoteCurrency?.symbol}{' '}
|
||||
</HighlightText>
|
||||
<Trans>Pool</Trans> <FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const CollectFeesSummary = ({
|
||||
info,
|
||||
transactionState,
|
||||
}: {
|
||||
info: CollectFeesTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const { currencyId0, expectedCurrencyOwed0 = '0', expectedCurrencyOwed1 = '0', currencyId1 } = info
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Collecting</Trans>,
|
||||
success: <Trans>Collected</Trans>,
|
||||
failed: <Trans>Collect</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<FormattedCurrencyAmount rawAmount={expectedCurrencyOwed0} currencyId={currencyId0} sigFigs={2} />{' '}
|
||||
<Trans>and</Trans>{' '}
|
||||
<FormattedCurrencyAmount rawAmount={expectedCurrencyOwed1} currencyId={currencyId1} sigFigs={2} />{' '}
|
||||
<Trans>fees</Trans> <FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const ApprovalSummary = ({
|
||||
info,
|
||||
transactionState,
|
||||
}: {
|
||||
info: ApproveTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const token = useToken(info.tokenAddress)
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Approving</Trans>,
|
||||
success: <Trans>Approved</Trans>,
|
||||
failed: <Trans>Approve</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} /> <HighlightText>{token?.symbol}</HighlightText>{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const ClaimSummary = ({
|
||||
info: { recipient, uniAmountRaw },
|
||||
transactionState,
|
||||
}: {
|
||||
info: ClaimTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const { ENSName } = useENSName()
|
||||
const actionProps = {
|
||||
transactionState,
|
||||
pending: <Trans>Claiming</Trans>,
|
||||
success: <Trans>Claimed</Trans>,
|
||||
failed: <Trans>Claim</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
{uniAmountRaw && (
|
||||
<>
|
||||
<Action {...actionProps} />{' '}
|
||||
<HighlightText>
|
||||
{formatAmount(uniAmountRaw, 18, 4)}
|
||||
UNI{' '}
|
||||
</HighlightText>{' '}
|
||||
<Trans>for</Trans> <HighlightText>{ENSName ?? shortenAddress(recipient)}</HighlightText>
|
||||
</>
|
||||
)}{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const WrapSummary = ({
|
||||
info: { chainId, currencyAmountRaw, unwrapped },
|
||||
transactionState,
|
||||
}: {
|
||||
info: WrapTransactionInfo
|
||||
transactionState: TransactionState
|
||||
}) => {
|
||||
const native = chainId ? nativeOnChain(chainId) : undefined
|
||||
const from = unwrapped ? native?.wrapped.symbol ?? 'WETH' : native?.symbol ?? 'ETH'
|
||||
const to = unwrapped ? native?.symbol ?? 'ETH' : native?.wrapped.symbol ?? 'WETH'
|
||||
|
||||
const amount = formatAmount(currencyAmountRaw, 18, 6)
|
||||
const actionProps = unwrapped
|
||||
? {
|
||||
transactionState,
|
||||
pending: <Trans>Unwrapping</Trans>,
|
||||
success: <Trans>Unwrapped</Trans>,
|
||||
failed: <Trans>Unwrap</Trans>,
|
||||
}
|
||||
: {
|
||||
transactionState,
|
||||
pending: <Trans>Wrapping</Trans>,
|
||||
success: <Trans>Wrapped</Trans>,
|
||||
failed: <Trans>Wrap</Trans>,
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyWrap>
|
||||
<Action {...actionProps} />{' '}
|
||||
<HighlightText>
|
||||
{amount} {from}
|
||||
</HighlightText>{' '}
|
||||
<Trans>to</Trans>{' '}
|
||||
<HighlightText>
|
||||
{amount} {to}
|
||||
</HighlightText>{' '}
|
||||
<FailedText transactionState={transactionState} />
|
||||
</BodyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
const TransactionBody = ({ info, transactionState }: { info: TransactionInfo; transactionState: TransactionState }) => {
|
||||
switch (info.type) {
|
||||
case TransactionType.SWAP:
|
||||
return <SwapSummary info={info} transactionState={transactionState} />
|
||||
case TransactionType.ADD_LIQUIDITY_V3_POOL:
|
||||
return <AddLiquidityV3PoolSummary info={info} transactionState={transactionState} />
|
||||
case TransactionType.REMOVE_LIQUIDITY_V3:
|
||||
return <RemoveLiquidityV3Summary info={info} transactionState={transactionState} />
|
||||
case TransactionType.WRAP:
|
||||
return <WrapSummary info={info} transactionState={transactionState} />
|
||||
case TransactionType.COLLECT_FEES:
|
||||
return <CollectFeesSummary info={info} transactionState={transactionState} />
|
||||
case TransactionType.APPROVAL:
|
||||
return <ApprovalSummary info={info} transactionState={transactionState} />
|
||||
case TransactionType.CLAIM:
|
||||
return <ClaimSummary info={info} transactionState={transactionState} />
|
||||
default:
|
||||
return <span />
|
||||
}
|
||||
}
|
||||
|
||||
export default TransactionBody
|
||||
90
src/components/AccountDetailsV2/index.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfoOrDefault } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useMemo } from 'react'
|
||||
import { AlertTriangle, CheckCircle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { colors } from 'theme/colors'
|
||||
|
||||
import { TransactionDetails } from '../../state/transactions/types'
|
||||
import Loader from '../Loader'
|
||||
import LogoView from './LogoView'
|
||||
import TransactionBody from './TransactionBody'
|
||||
|
||||
export enum TransactionState {
|
||||
Pending,
|
||||
Success,
|
||||
Failed,
|
||||
}
|
||||
|
||||
const Grid = styled.a`
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
grid-template-columns: 44px auto 24px;
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
border-bottom: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
|
||||
padding: 12px;
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
transition: 250ms background-color ease;
|
||||
}
|
||||
`
|
||||
|
||||
const TextContainer = styled.span`
|
||||
font-size: 14px;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
`
|
||||
|
||||
const IconStyleWrap = styled.span`
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-left: auto;
|
||||
height: 16px;
|
||||
`
|
||||
|
||||
export const TransactionSummary = ({ transactionDetails }: { transactionDetails: TransactionDetails }) => {
|
||||
const { chainId = 1 } = useWeb3React()
|
||||
const tx = transactionDetails
|
||||
const { explorer } = getChainInfoOrDefault(chainId ? chainId : SupportedChainId.MAINNET)
|
||||
const { info, receipt, hash } = tx
|
||||
|
||||
const transactionState = useMemo(() => {
|
||||
const pending = !receipt
|
||||
const success = !pending && tx && (receipt?.status === 1 || typeof receipt?.status === 'undefined')
|
||||
const transactionState = pending
|
||||
? TransactionState.Pending
|
||||
: success
|
||||
? TransactionState.Success
|
||||
: TransactionState.Failed
|
||||
|
||||
return transactionState
|
||||
}, [receipt, tx])
|
||||
|
||||
const link = `${explorer}tx/${hash}`
|
||||
|
||||
return chainId ? (
|
||||
<Grid href={link} target="_blank">
|
||||
<LogoView info={info} />
|
||||
<TextContainer as="span">
|
||||
<TransactionBody info={info} transactionState={transactionState} />
|
||||
</TextContainer>
|
||||
{transactionState === TransactionState.Pending ? (
|
||||
<IconStyleWrap>
|
||||
<Loader />
|
||||
</IconStyleWrap>
|
||||
) : transactionState === TransactionState.Success ? (
|
||||
<IconStyleWrap>
|
||||
<CheckCircle color={colors.green200} size="16px" />
|
||||
</IconStyleWrap>
|
||||
) : (
|
||||
<IconStyleWrap>
|
||||
<AlertTriangle color={colors.gold200} size="16px" />
|
||||
</IconStyleWrap>
|
||||
)}
|
||||
</Grid>
|
||||
) : null
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import { Trans } from '@lingui/macro'
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ChangeEvent, Context, ReactNode, useCallback, useContext } from 'react'
|
||||
import styled, { DefaultTheme, ThemeContext } from 'styled-components/macro'
|
||||
import { ChangeEvent, ReactNode, useCallback } from 'react'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import useENS from '../../hooks/useENS'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
@@ -87,7 +87,7 @@ export default function AddressInputPanel({
|
||||
onChange: (value: string) => void
|
||||
}) {
|
||||
const { chainId } = useWeb3React()
|
||||
const theme = useContext(ThemeContext as Context<DefaultTheme>)
|
||||
const theme = useTheme()
|
||||
|
||||
const { address, loading, name } = useENS(value)
|
||||
|
||||
|
||||
@@ -37,16 +37,9 @@ export enum CUSTOM_USER_PROPERTIES {
|
||||
SCREEN_RESOLUTION_HEIGHT = 'screen_resolution_height',
|
||||
SCREEN_RESOLUTION_WIDTH = 'screen_resolution_width',
|
||||
WALLET_ADDRESS = 'wallet_address',
|
||||
WALLET_NATIVE_CURRENCY_BALANCE_USD = 'wallet_native_currency_balance_usd',
|
||||
WALLET_TOKENS_ADDRESSES = 'wallet_tokens_addresses',
|
||||
WALLET_TOKENS_SYMBOLS = 'wallet_tokens_symbols',
|
||||
WALLET_TYPE = 'wallet_type',
|
||||
}
|
||||
|
||||
export enum CUSTOM_USER_PROPERTY_SUFFIXES {
|
||||
WALLET_TOKEN_AMOUNT_SUFFIX = '_token_amount',
|
||||
}
|
||||
|
||||
export enum BROWSER {
|
||||
FIREFOX = 'Mozilla Firefox',
|
||||
SAMSUNG = 'Samsung Internet',
|
||||
@@ -75,7 +68,7 @@ export enum SWAP_PRICE_UPDATE_USER_RESPONSE {
|
||||
* Known pages in the app. Highest order context.
|
||||
*/
|
||||
export enum PageName {
|
||||
EXPLORE_PAGE = 'explore-page',
|
||||
TOKENS_PAGE = 'tokens-page',
|
||||
POOL_PAGE = 'pool-page',
|
||||
SWAP_PAGE = 'swap-page',
|
||||
VOTE_PAGE = 'vote-page',
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { darken } from 'polished'
|
||||
import { Check, ChevronDown } from 'react-feather'
|
||||
import { Button as RebassButton, ButtonProps as ButtonPropsOriginal } from 'rebass/styled-components'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { RowBetween } from '../Row'
|
||||
|
||||
@@ -51,9 +50,12 @@ export const BaseButton = styled(RebassButton)<
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonPrimary = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.deprecated_primary1};
|
||||
color: white;
|
||||
export const ButtonPrimary = styled(BaseButton)<{ redesignFlag?: boolean }>`
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentAction : theme.deprecated_primary1)};
|
||||
font-size: ${({ redesignFlag }) => redesignFlag && '20px'};
|
||||
font-weight: ${({ redesignFlag }) => redesignFlag && '600'};
|
||||
padding: ${({ redesignFlag }) => redesignFlag && '16px'};
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentTextLightPrimary : 'white')};
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.deprecated_primary1)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.deprecated_primary1)};
|
||||
@@ -174,23 +176,28 @@ export const ButtonOutlined = styled(BaseButton)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonYellow = styled(BaseButton)`
|
||||
background-color: ${({ theme }) => theme.deprecated_yellow3};
|
||||
color: white;
|
||||
export const ButtonYellow = styled(BaseButton)<{ redesignFlag?: boolean }>`
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentWarningSoft : theme.deprecated_yellow3)};
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentWarning : 'white')};
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.05, theme.deprecated_yellow3)};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.deprecated_yellow3)};
|
||||
box-shadow: ${({ theme, redesignFlag }) => !redesignFlag && `0 0 0 1pt ${theme.deprecated_yellow3}`};
|
||||
background-color: ${({ theme, redesignFlag }) =>
|
||||
redesignFlag ? theme.accentWarningSoft : darken(0.05, theme.deprecated_yellow3)};
|
||||
}
|
||||
&:hover {
|
||||
background-color: ${({ theme }) => darken(0.05, theme.deprecated_yellow3)};
|
||||
background: ${({ theme, redesignFlag }) => redesignFlag && theme.stateOverlayHover};
|
||||
mix-blend-mode: ${({ redesignFlag }) => redesignFlag && 'normal'};
|
||||
background-color: ${({ theme, redesignFlag }) => !redesignFlag && darken(0.05, theme.deprecated_yellow3)};
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0 0 1pt ${({ theme }) => darken(0.1, theme.deprecated_yellow3)};
|
||||
background-color: ${({ theme }) => darken(0.1, theme.deprecated_yellow3)};
|
||||
box-shadow: ${({ theme, redesignFlag }) => !redesignFlag && `0 0 0 1pt ${darken(0.1, theme.deprecated_yellow3)}`};
|
||||
background-color: ${({ theme, redesignFlag }) =>
|
||||
redesignFlag ? theme.accentWarningSoft : darken(0.1, theme.deprecated_yellow3)};
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme }) => theme.deprecated_yellow3};
|
||||
opacity: 50%;
|
||||
background-color: ${({ theme, redesignFlag }) =>
|
||||
redesignFlag ? theme.accentWarningSoft : theme.deprecated_yellow3};
|
||||
opacity: ${({ redesignFlag }) => (redesignFlag ? '60%' : '50%')};
|
||||
cursor: auto;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Group } from '@visx/group'
|
||||
import { LinePath } from '@visx/shape'
|
||||
import { CurveFactory } from 'd3'
|
||||
import { radius } from 'd3-curve-circlecorners'
|
||||
import React from 'react'
|
||||
import { ReactNode } from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
@@ -12,7 +11,7 @@ interface LineChartProps<T> {
|
||||
getX: (t: T) => number
|
||||
getY: (t: T) => number
|
||||
marginTop?: number
|
||||
curve?: CurveFactory
|
||||
curve: CurveFactory
|
||||
color?: Color
|
||||
strokeWidth: number
|
||||
children?: ReactNode
|
||||
@@ -37,7 +36,7 @@ function LineChart<T>({
|
||||
<svg width={width} height={height}>
|
||||
<Group top={marginTop}>
|
||||
<LinePath
|
||||
curve={curve ?? radius(0.25)}
|
||||
curve={curve}
|
||||
stroke={color ?? theme.accentAction}
|
||||
strokeWidth={strokeWidth}
|
||||
data={data}
|
||||
|
||||
@@ -1,301 +0,0 @@
|
||||
import { AxisBottom, TickFormatter } from '@visx/axis'
|
||||
import { localPoint } from '@visx/event'
|
||||
import { EventType } from '@visx/event/lib/types'
|
||||
import { GlyphCircle } from '@visx/glyph'
|
||||
import { Line } from '@visx/shape'
|
||||
import { filterTimeAtom } from 'components/Explore/state'
|
||||
import { bisect, curveBasis, NumberValue, scaleLinear } from 'd3'
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
import { TimePeriod } from 'hooks/useExplorePageQuery'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { useAtom } from 'jotai'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { ArrowDownRight, ArrowUpRight } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { OPACITY_HOVER } from 'theme'
|
||||
import {
|
||||
dayHourFormatter,
|
||||
hourFormatter,
|
||||
monthDayFormatter,
|
||||
monthFormatter,
|
||||
monthYearDayFormatter,
|
||||
monthYearFormatter,
|
||||
weekFormatter,
|
||||
} from 'utils/formatChartTimes'
|
||||
|
||||
import data from './data.json'
|
||||
import LineChart from './LineChart'
|
||||
|
||||
// TODO: This should be combined with the logic in TimeSelector.
|
||||
const TIME_DISPLAYS: [TimePeriod, string][] = [
|
||||
[TimePeriod.hour, '1H'],
|
||||
[TimePeriod.day, '1D'],
|
||||
[TimePeriod.week, '1W'],
|
||||
[TimePeriod.month, '1M'],
|
||||
[TimePeriod.year, '1Y'],
|
||||
[TimePeriod.all, 'All'],
|
||||
]
|
||||
|
||||
type PricePoint = { value: number; timestamp: number }
|
||||
|
||||
function getPriceBounds(pricePoints: PricePoint[]): [number, number] {
|
||||
const prices = pricePoints.map((x) => x.value)
|
||||
const min = Math.min(...prices)
|
||||
const max = Math.max(...prices)
|
||||
return [min, max]
|
||||
}
|
||||
|
||||
const StyledUpArrow = styled(ArrowUpRight)`
|
||||
color: ${({ theme }) => theme.accentSuccess};
|
||||
`
|
||||
const StyledDownArrow = styled(ArrowDownRight)`
|
||||
color: ${({ theme }) => theme.accentFailure};
|
||||
`
|
||||
|
||||
function getDelta(start: number, current: number) {
|
||||
const delta = (current / start - 1) * 100
|
||||
const isPositive = Math.sign(delta) > 0
|
||||
|
||||
const formattedDelta = delta.toFixed(2) + '%'
|
||||
if (isPositive) {
|
||||
return ['+' + formattedDelta, <StyledUpArrow size={16} key="arrow-up" />]
|
||||
} else if (delta === 0) {
|
||||
return [formattedDelta, null]
|
||||
}
|
||||
return [formattedDelta, <StyledDownArrow size={16} key="arrow-down" />]
|
||||
}
|
||||
|
||||
export const ChartHeader = styled.div`
|
||||
position: absolute;
|
||||
`
|
||||
|
||||
export const TokenPrice = styled.span`
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
`
|
||||
export const DeltaContainer = styled.div`
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
const ArrowCell = styled.div`
|
||||
padding-left: 2px;
|
||||
display: flex;
|
||||
`
|
||||
export const TimeOptionsWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`
|
||||
export const TimeOptionsContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 4px;
|
||||
gap: 4px;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
border-radius: 16px;
|
||||
height: 40px;
|
||||
padding: 4px;
|
||||
width: fit-content;
|
||||
`
|
||||
const TimeButton = styled.button<{ active: boolean }>`
|
||||
background-color: ${({ theme, active }) => (active ? theme.backgroundInteractive : 'transparent')};
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 12px;
|
||||
line-height: 20px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: ${({ theme, active }) => (active ? theme.textPrimary : theme.textSecondary)};
|
||||
:hover {
|
||||
${({ active }) => !active && `opacity: ${OPACITY_HOVER};`}
|
||||
}
|
||||
`
|
||||
|
||||
function getTicks(startTimestamp: number, endTimestamp: number, numTicks = 5) {
|
||||
return Array.from(
|
||||
{ length: numTicks },
|
||||
(v, i) => endTimestamp - ((endTimestamp - startTimestamp) / (numTicks + 1)) * (i + 1)
|
||||
)
|
||||
}
|
||||
|
||||
function tickFormat(
|
||||
startTimestamp: number,
|
||||
endTimestamp: number,
|
||||
timePeriod: TimePeriod,
|
||||
locale: string
|
||||
): [TickFormatter<NumberValue>, (v: number) => string, number[]] {
|
||||
switch (timePeriod) {
|
||||
case TimePeriod.hour:
|
||||
return [hourFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
case TimePeriod.day:
|
||||
return [hourFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
case TimePeriod.week:
|
||||
return [weekFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp, 6)]
|
||||
case TimePeriod.month:
|
||||
return [monthDayFormatter(locale), dayHourFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
case TimePeriod.year:
|
||||
return [monthFormatter(locale), monthYearDayFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
case TimePeriod.all:
|
||||
return [monthYearFormatter(locale), monthYearDayFormatter(locale), getTicks(startTimestamp, endTimestamp)]
|
||||
}
|
||||
}
|
||||
|
||||
const margin = { top: 86, bottom: 48, crosshair: 72 }
|
||||
const timeOptionsHeight = 44
|
||||
const crosshairDateOverhang = 80
|
||||
|
||||
interface PriceChartProps {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export function PriceChart({ width, height }: PriceChartProps) {
|
||||
const [timePeriod, setTimePeriod] = useAtom(filterTimeAtom)
|
||||
const locale = useActiveLocale()
|
||||
const theme = useTheme()
|
||||
|
||||
/* TODO: Implement API calls & cache to use here */
|
||||
const pricePoints = data[timePeriod]
|
||||
const startingPrice = pricePoints[0]
|
||||
const endingPrice = pricePoints[pricePoints.length - 1]
|
||||
const initialState = { pricePoint: endingPrice, xCoordinate: null }
|
||||
const [selected, setSelected] = useState<{ pricePoint: PricePoint; xCoordinate: number | null }>(initialState)
|
||||
|
||||
const graphWidth = width + crosshairDateOverhang
|
||||
const graphHeight = height - timeOptionsHeight
|
||||
const graphInnerHeight = graphHeight - margin.top - margin.bottom
|
||||
|
||||
// Defining scales
|
||||
// x scale
|
||||
const timeScale = scaleLinear().domain([startingPrice.timestamp, endingPrice.timestamp]).range([0, width])
|
||||
// y scale
|
||||
const rdScale = scaleLinear().domain(getPriceBounds(pricePoints)).range([graphInnerHeight, 0])
|
||||
|
||||
const handleHover = useCallback(
|
||||
(event: Element | EventType) => {
|
||||
const { x } = localPoint(event) || { x: 0 }
|
||||
const x0 = timeScale.invert(x) // get timestamp from the scale
|
||||
const index = bisect(
|
||||
pricePoints.map((x) => x.timestamp),
|
||||
x0,
|
||||
1
|
||||
)
|
||||
|
||||
const d0 = pricePoints[index - 1]
|
||||
const d1 = pricePoints[index]
|
||||
let pricePoint = d0
|
||||
|
||||
const hasPreviousData = d1 && d1.timestamp
|
||||
if (hasPreviousData) {
|
||||
pricePoint = x0.valueOf() - d0.timestamp.valueOf() > d1.timestamp.valueOf() - x0.valueOf() ? d1 : d0
|
||||
}
|
||||
|
||||
setSelected({ pricePoint, xCoordinate: timeScale(pricePoint.timestamp) })
|
||||
},
|
||||
[timeScale, pricePoints]
|
||||
)
|
||||
|
||||
const [tickFormatter, crosshairDateFormatter, ticks] = tickFormat(
|
||||
startingPrice.timestamp,
|
||||
endingPrice.timestamp,
|
||||
timePeriod,
|
||||
locale
|
||||
)
|
||||
const [delta, arrow] = getDelta(startingPrice.value, selected.pricePoint.value)
|
||||
const crosshairEdgeMax = width * 0.97
|
||||
const crosshairAtEdge = !!selected.xCoordinate && selected.xCoordinate > crosshairEdgeMax
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChartHeader>
|
||||
<TokenPrice>${selected.pricePoint.value.toFixed(2)}</TokenPrice>
|
||||
<DeltaContainer>
|
||||
{delta}
|
||||
<ArrowCell>{arrow}</ArrowCell>
|
||||
</DeltaContainer>
|
||||
</ChartHeader>
|
||||
<LineChart
|
||||
data={pricePoints}
|
||||
getX={(p: PricePoint) => timeScale(p.timestamp)}
|
||||
getY={(p: PricePoint) => rdScale(p.value)}
|
||||
marginTop={margin.top}
|
||||
/* Default curve doesn't look good for the ALL chart */
|
||||
curve={timePeriod === TimePeriod.all ? curveBasis : undefined}
|
||||
strokeWidth={2}
|
||||
width={graphWidth}
|
||||
height={graphHeight}
|
||||
>
|
||||
{selected.xCoordinate !== null ? (
|
||||
<g>
|
||||
<AxisBottom
|
||||
scale={timeScale}
|
||||
stroke={theme.backgroundOutline}
|
||||
tickFormat={tickFormatter}
|
||||
tickStroke={theme.backgroundOutline}
|
||||
tickLength={4}
|
||||
tickTransform={'translate(0 -5)'}
|
||||
tickValues={ticks}
|
||||
top={graphHeight - 1}
|
||||
tickLabelProps={() => ({
|
||||
fill: theme.textSecondary,
|
||||
fontSize: 12,
|
||||
textAnchor: 'middle',
|
||||
transform: 'translate(0 -24)',
|
||||
})}
|
||||
/>
|
||||
<text
|
||||
x={selected.xCoordinate + (crosshairAtEdge ? -4 : 4)}
|
||||
y={margin.crosshair + 10}
|
||||
textAnchor={crosshairAtEdge ? 'end' : 'start'}
|
||||
fontSize={12}
|
||||
fill={theme.textSecondary}
|
||||
>
|
||||
{crosshairDateFormatter(selected.pricePoint.timestamp)}
|
||||
</text>
|
||||
<Line
|
||||
from={{ x: selected.xCoordinate, y: margin.crosshair }}
|
||||
to={{ x: selected.xCoordinate, y: graphHeight }}
|
||||
stroke={theme.backgroundOutline}
|
||||
strokeWidth={1}
|
||||
pointerEvents="none"
|
||||
strokeDasharray="4,4"
|
||||
/>
|
||||
<GlyphCircle
|
||||
left={selected.xCoordinate}
|
||||
top={rdScale(selected.pricePoint.value) + margin.top}
|
||||
size={50}
|
||||
fill={theme.accentActive}
|
||||
stroke={theme.backgroundOutline}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</g>
|
||||
) : (
|
||||
<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={() => setSelected(initialState)}
|
||||
/>
|
||||
</LineChart>
|
||||
<TimeOptionsWrapper>
|
||||
<TimeOptionsContainer>
|
||||
{TIME_DISPLAYS.map(([value, display]) => (
|
||||
<TimeButton key={display} active={timePeriod === value} onClick={() => setTimePeriod(value)}>
|
||||
{display}
|
||||
</TimeButton>
|
||||
))}
|
||||
</TimeOptionsContainer>
|
||||
</TimeOptionsWrapper>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default PriceChart
|
||||
@@ -1,6 +1,6 @@
|
||||
import { scaleLinear } from 'd3'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { curveCardinalOpen, scaleLinear } from 'd3'
|
||||
import React from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
|
||||
import data from './data.json'
|
||||
import LineChart from './LineChart'
|
||||
@@ -37,6 +37,7 @@ function SparklineChart({ width, height }: SparklineChartProps) {
|
||||
data={pricePoints}
|
||||
getX={(p: PricePoint) => timeScale(p.timestamp)}
|
||||
getY={(p: PricePoint) => rdScale(p.value)}
|
||||
curve={curveCardinalOpen.tension(0.9)}
|
||||
marginTop={0}
|
||||
color={isPositive ? theme.accentSuccess : theme.accentFailure}
|
||||
strokeWidth={1.5}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import Column from 'components/Column'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { AlertOctagon } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
|
||||
import { CopyHelper } from '../../theme'
|
||||
|
||||
@@ -4,8 +4,8 @@ import { t } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import HoverInlineText from 'components/HoverInlineText'
|
||||
import { useMemo } from 'react'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { warningSeverity } from '../../utils/prices'
|
||||
import { MouseoverTooltip } from '../Tooltip'
|
||||
|
||||
423
src/components/CurrencyInputPanel/SwapCurrencyInputPanel.tsx
Normal file
@@ -0,0 +1,423 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core'
|
||||
import { Pair } from '@uniswap/v2-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ElementName, Event, EventName } from 'components/AmplitudeAnalytics/constants'
|
||||
import { TraceEvent } from 'components/AmplitudeAnalytics/TraceEvent'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Lock } from 'react-feather'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
|
||||
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
import { useCurrencyBalance } from '../../state/connection/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { ButtonGray } from '../Button'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { Input as NumericalInput } from '../NumericalInput'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
|
||||
import { FiatValue } from './FiatValue'
|
||||
|
||||
const InputPanel = styled.div<{ hideInput?: boolean; redesignFlag: boolean }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
position: relative;
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
background-color: ${({ theme, redesignFlag, hideInput }) =>
|
||||
redesignFlag ? 'transparent' : hideInput ? 'transparent' : theme.deprecated_bg2};
|
||||
z-index: 1;
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
transition: height 1s ease;
|
||||
will-change: height;
|
||||
`
|
||||
|
||||
const FixedContainer = styled.div<{ redesignFlag: boolean }>`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
border-radius: 20px;
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg2)};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
`
|
||||
|
||||
const Container = styled.div<{ hideInput: boolean; disabled: boolean; redesignFlag: boolean }>`
|
||||
min-height: ${({ redesignFlag }) => redesignFlag && '69px'};
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
border: 1px solid ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg0)};
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg1)};
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
${({ theme, hideInput, disabled, redesignFlag }) =>
|
||||
!redesignFlag &&
|
||||
!disabled &&
|
||||
`
|
||||
:focus,
|
||||
:hover {
|
||||
border: 1px solid ${hideInput ? ' transparent' : theme.deprecated_bg3};
|
||||
}
|
||||
`}
|
||||
`
|
||||
|
||||
const CurrencySelect = styled(ButtonGray)<{
|
||||
visible: boolean
|
||||
selected: boolean
|
||||
hideInput?: boolean
|
||||
disabled?: boolean
|
||||
redesignFlag: boolean
|
||||
}>`
|
||||
align-items: center;
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
redesignFlag
|
||||
? selected
|
||||
? theme.backgroundSurface
|
||||
: theme.accentAction
|
||||
: selected
|
||||
? theme.deprecated_bg2
|
||||
: theme.deprecated_primary1};
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')};
|
||||
color: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
cursor: pointer;
|
||||
height: ${({ hideInput, redesignFlag }) => (redesignFlag ? 'unset' : hideInput ? '2.8rem' : '2.4rem')};
|
||||
border-radius: 16px;
|
||||
outline: none;
|
||||
user-select: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
padding: ${({ selected, redesignFlag }) =>
|
||||
redesignFlag ? (selected ? '4px 8px 4px 4px' : '6px 6px 6px 8px') : '0 8px'};
|
||||
gap: ${({ redesignFlag }) => (redesignFlag ? '8px' : '0px')};
|
||||
justify-content: space-between;
|
||||
margin-left: ${({ hideInput }) => (hideInput ? '0' : '12px')};
|
||||
:focus,
|
||||
:hover {
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
selected
|
||||
? redesignFlag
|
||||
? theme.backgroundSurface
|
||||
: theme.deprecated_bg3
|
||||
: darken(0.05, theme.deprecated_primary1)};
|
||||
}
|
||||
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}
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: ${({ selected, redesignFlag }) =>
|
||||
redesignFlag ? '0px' : selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 1rem 1rem'};
|
||||
`
|
||||
|
||||
const LabelRow = styled.div`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
padding: 0 1rem 1rem;
|
||||
|
||||
span:hover {
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => darken(0.2, theme.deprecated_text2)};
|
||||
}
|
||||
`
|
||||
|
||||
const FiatRow = styled(LabelRow)<{ redesignFlag: boolean }>`
|
||||
justify-content: flex-end;
|
||||
min-height: ${({ redesignFlag }) => redesignFlag && '32px'};
|
||||
padding: ${({ redesignFlag }) => redesignFlag && '8px 0px'};
|
||||
height: ${({ redesignFlag }) => !redesignFlag && '24px'};
|
||||
`
|
||||
|
||||
const 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;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledDropDown = styled(DropDown)<{ selected: boolean; redesignFlag: boolean }>`
|
||||
margin: 0 0.25rem 0 0.35rem;
|
||||
height: 35%;
|
||||
margin-left: ${({ redesignFlag }) => redesignFlag && '8px'};
|
||||
|
||||
path {
|
||||
stroke: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
stroke-width: ${({ redesignFlag }) => (redesignFlag ? '2px' : '1.5px')};
|
||||
}
|
||||
`
|
||||
|
||||
const StyledTokenName = styled.span<{ active?: boolean; redesignFlag: boolean }>`
|
||||
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
|
||||
font-size: ${({ active }) => (active ? '18px' : '18px')};
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
|
||||
`
|
||||
|
||||
const StyledBalanceMax = styled.button<{ disabled?: boolean; redesignFlag: boolean }>`
|
||||
background-color: transparent;
|
||||
background-color: ${({ theme, redesignFlag }) => !redesignFlag && theme.deprecated_primary5};
|
||||
border: none;
|
||||
text-transform: ${({ redesignFlag }) => !redesignFlag && 'uppercase'};
|
||||
border-radius: ${({ redesignFlag }) => !redesignFlag && '12px'};
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentAction : theme.deprecated_primary1)};
|
||||
cursor: pointer;
|
||||
font-size: ${({ redesignFlag }) => (redesignFlag ? '14px' : '11px')};
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
|
||||
margin-left: ${({ redesignFlag }) => (redesignFlag ? '0px' : '0.25rem')};
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
padding: 4px 6px;
|
||||
pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
|
||||
|
||||
:hover {
|
||||
opacity: ${({ disabled }) => (!disabled ? 0.8 : 0.4)};
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline: none;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean; redesignFlag: boolean }>`
|
||||
${loadingOpacityMixin};
|
||||
text-align: left;
|
||||
font-variant: ${({ redesignFlag }) => redesignFlag && 'small-caps'};
|
||||
font-feature-settings: ${({ redesignFlag }) => redesignFlag && 'pnum on, lnum on'};
|
||||
`
|
||||
|
||||
interface SwapCurrencyInputPanelProps {
|
||||
value: string
|
||||
onUserInput: (value: string) => void
|
||||
onMax?: () => void
|
||||
showMaxButton: boolean
|
||||
label?: ReactNode
|
||||
onCurrencySelect?: (currency: Currency) => void
|
||||
currency?: Currency | null
|
||||
hideBalance?: boolean
|
||||
pair?: Pair | null
|
||||
hideInput?: boolean
|
||||
otherCurrency?: Currency | null
|
||||
fiatValue?: CurrencyAmount<Token> | null
|
||||
priceImpact?: Percent
|
||||
id: string
|
||||
showCommonBases?: boolean
|
||||
showCurrencyAmount?: boolean
|
||||
disableNonToken?: boolean
|
||||
renderBalance?: (amount: CurrencyAmount<Currency>) => ReactNode
|
||||
locked?: boolean
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
export default function SwapCurrencyInputPanel({
|
||||
value,
|
||||
onUserInput,
|
||||
onMax,
|
||||
showMaxButton,
|
||||
onCurrencySelect,
|
||||
currency,
|
||||
otherCurrency,
|
||||
id,
|
||||
showCommonBases,
|
||||
showCurrencyAmount,
|
||||
disableNonToken,
|
||||
renderBalance,
|
||||
fiatValue,
|
||||
priceImpact,
|
||||
hideBalance = false,
|
||||
pair = null, // used for double token logo
|
||||
hideInput = false,
|
||||
locked = false,
|
||||
loading = false,
|
||||
...rest
|
||||
}: SwapCurrencyInputPanelProps) {
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const { account, chainId } = useWeb3React()
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
|
||||
const theme = useTheme()
|
||||
const { pathname } = useLocation()
|
||||
const isAddLiquidityPage = pathname.includes('/add') && !pathname.includes('/add/v2')
|
||||
|
||||
const handleDismissSearch = useCallback(() => {
|
||||
setModalOpen(false)
|
||||
}, [setModalOpen])
|
||||
|
||||
const chainAllowed = isSupportedChain(chainId)
|
||||
|
||||
return (
|
||||
<InputPanel id={id} hideInput={hideInput} {...rest} redesignFlag={redesignFlagEnabled}>
|
||||
{locked && (
|
||||
<FixedContainer redesignFlag={redesignFlagEnabled}>
|
||||
<AutoColumn gap="sm" justify="center">
|
||||
<Lock />
|
||||
<ThemedText.DeprecatedLabel fontSize="12px" textAlign="center" padding="0 12px">
|
||||
<Trans>The market price is outside your specified price range. Single-asset deposit only.</Trans>
|
||||
</ThemedText.DeprecatedLabel>
|
||||
</AutoColumn>
|
||||
</FixedContainer>
|
||||
)}
|
||||
<Container hideInput={hideInput} disabled={!chainAllowed} redesignFlag={redesignFlagEnabled}>
|
||||
<InputRow
|
||||
style={hideInput ? { padding: '0', borderRadius: '8px' } : {}}
|
||||
selected={!onCurrencySelect}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
>
|
||||
{!hideInput && (
|
||||
<StyledNumericalInput
|
||||
className="token-amount-input"
|
||||
value={value}
|
||||
onUserInput={onUserInput}
|
||||
disabled={!chainAllowed}
|
||||
$loading={loading}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
/>
|
||||
)}
|
||||
|
||||
<InputCurrencySelect
|
||||
disabled={!chainAllowed}
|
||||
visible={currency !== undefined}
|
||||
selected={!!currency}
|
||||
hideInput={hideInput}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
className="open-currency-select-button"
|
||||
onClick={() => {
|
||||
if (onCurrencySelect) {
|
||||
setModalOpen(true)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Aligner>
|
||||
<RowFixed>
|
||||
{pair ? (
|
||||
<span style={{ marginRight: '0.5rem' }}>
|
||||
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
|
||||
</span>
|
||||
) : currency ? (
|
||||
<CurrencyLogo style={{ marginRight: '2px' }} currency={currency} size={'24px'} />
|
||||
) : null}
|
||||
{pair ? (
|
||||
<StyledTokenName className="pair-name-container" redesignFlag={redesignFlagEnabled}>
|
||||
{pair?.token0.symbol}:{pair?.token1.symbol}
|
||||
</StyledTokenName>
|
||||
) : (
|
||||
<StyledTokenName
|
||||
className="token-symbol-container"
|
||||
active={Boolean(currency && currency.symbol)}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
>
|
||||
{(currency && currency.symbol && currency.symbol.length > 20
|
||||
? currency.symbol.slice(0, 4) +
|
||||
'...' +
|
||||
currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length)
|
||||
: currency?.symbol) || <Trans>Select token</Trans>}
|
||||
</StyledTokenName>
|
||||
)}
|
||||
</RowFixed>
|
||||
{onCurrencySelect && <StyledDropDown selected={!!currency} redesignFlag={redesignFlagEnabled} />}
|
||||
</Aligner>
|
||||
</InputCurrencySelect>
|
||||
</InputRow>
|
||||
{redesignFlagEnabled && !currency && !isAddLiquidityPage && (
|
||||
<NoBalanceState>
|
||||
<FiatRow redesignFlag={redesignFlagEnabled}>
|
||||
<RowBetween>
|
||||
<NoBalanceDash>-</NoBalanceDash>
|
||||
<NoBalanceDash>-</NoBalanceDash>
|
||||
</RowBetween>
|
||||
</FiatRow>
|
||||
</NoBalanceState>
|
||||
)}
|
||||
{!hideInput && !hideBalance && currency && (
|
||||
<FiatRow redesignFlag={redesignFlagEnabled}>
|
||||
<RowBetween>
|
||||
<LoadingOpacityContainer $loading={loading}>
|
||||
<FiatValue fiatValue={fiatValue} priceImpact={priceImpact} />
|
||||
</LoadingOpacityContainer>
|
||||
{account ? (
|
||||
<RowFixed style={{ height: '17px' }}>
|
||||
<ThemedText.DeprecatedBody
|
||||
onClick={onMax}
|
||||
color={theme.deprecated_text3}
|
||||
fontWeight={500}
|
||||
fontSize={14}
|
||||
style={{ display: 'inline', cursor: 'pointer' }}
|
||||
>
|
||||
{!hideBalance && currency && selectedCurrencyBalance ? (
|
||||
renderBalance ? (
|
||||
renderBalance(selectedCurrencyBalance)
|
||||
) : (
|
||||
<Trans>Balance: {formatCurrencyAmount(selectedCurrencyBalance, 4)}</Trans>
|
||||
)
|
||||
) : null}
|
||||
</ThemedText.DeprecatedBody>
|
||||
{showMaxButton && selectedCurrencyBalance ? (
|
||||
<TraceEvent
|
||||
events={[Event.onClick]}
|
||||
name={EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED}
|
||||
element={ElementName.MAX_TOKEN_AMOUNT_BUTTON}
|
||||
>
|
||||
<StyledBalanceMax onClick={onMax} redesignFlag={redesignFlagEnabled}>
|
||||
<Trans>Max</Trans>
|
||||
</StyledBalanceMax>
|
||||
</TraceEvent>
|
||||
) : null}
|
||||
</RowFixed>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
</RowBetween>
|
||||
</FiatRow>
|
||||
)}
|
||||
</Container>
|
||||
{onCurrencySelect && (
|
||||
<CurrencySearchModal
|
||||
isOpen={modalOpen}
|
||||
onDismiss={handleDismissSearch}
|
||||
onCurrencySelect={onCurrencySelect}
|
||||
selectedCurrency={currency}
|
||||
otherSelectedCurrency={otherCurrency}
|
||||
showCommonBases={showCommonBases}
|
||||
showCurrencyAmount={showCurrencyAmount}
|
||||
disableNonToken={disableNonToken}
|
||||
/>
|
||||
)}
|
||||
</InputPanel>
|
||||
)
|
||||
}
|
||||
@@ -11,11 +11,10 @@ import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import { darken } from 'polished'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Lock } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { formatCurrencyAmount } from 'utils/formatCurrencyAmount'
|
||||
|
||||
import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg'
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import { useCurrencyBalance } from '../../state/connection/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
import { ButtonGray } from '../Button'
|
||||
@@ -26,37 +25,35 @@ import { RowBetween, RowFixed } from '../Row'
|
||||
import CurrencySearchModal from '../SearchModal/CurrencySearchModal'
|
||||
import { FiatValue } from './FiatValue'
|
||||
|
||||
const InputPanel = styled.div<{ hideInput?: boolean; redesignFlag: boolean }>`
|
||||
const InputPanel = styled.div<{ hideInput?: boolean }>`
|
||||
${({ theme }) => theme.flexColumnNoWrap}
|
||||
position: relative;
|
||||
border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')};
|
||||
background-color: ${({ theme, redesignFlag, hideInput }) =>
|
||||
redesignFlag ? 'transparent' : hideInput ? 'transparent' : theme.deprecated_bg2};
|
||||
background-color: ${({ theme, hideInput }) => (hideInput ? 'transparent' : theme.deprecated_bg2)};
|
||||
z-index: 1;
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
transition: height 1s ease;
|
||||
will-change: height;
|
||||
`
|
||||
|
||||
const FixedContainer = styled.div<{ redesignFlag: boolean }>`
|
||||
const FixedContainer = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
border-radius: 20px;
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? 'transparent' : theme.deprecated_bg2)};
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
`
|
||||
|
||||
const Container = styled.div<{ hideInput: boolean; disabled: boolean; redesignFlag: boolean }>`
|
||||
const Container = styled.div<{ hideInput: boolean; disabled: boolean }>`
|
||||
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)};
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg0};
|
||||
background-color: ${({ theme }) => theme.deprecated_bg1};
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
${({ theme, hideInput, disabled, redesignFlag }) =>
|
||||
!redesignFlag &&
|
||||
${({ theme, hideInput, disabled }) =>
|
||||
!disabled &&
|
||||
`
|
||||
:focus,
|
||||
@@ -71,65 +68,38 @@ const CurrencySelect = styled(ButtonGray)<{
|
||||
selected: boolean
|
||||
hideInput?: boolean
|
||||
disabled?: boolean
|
||||
redesignFlag: boolean
|
||||
}>`
|
||||
align-items: center;
|
||||
background-color: ${({ selected, theme, redesignFlag }) =>
|
||||
redesignFlag
|
||||
? selected
|
||||
? theme.backgroundSurface
|
||||
: theme.accentAction
|
||||
: selected
|
||||
? theme.deprecated_bg2
|
||||
: theme.deprecated_primary1};
|
||||
background-color: ${({ selected, theme }) => (selected ? theme.deprecated_bg2 : theme.deprecated_primary1)};
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
box-shadow: ${({ selected }) => (selected ? 'none' : '0px 6px 10px rgba(0, 0, 0, 0.075)')};
|
||||
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
|
||||
color: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
cursor: pointer;
|
||||
height: ${({ hideInput, redesignFlag }) => (redesignFlag ? 'unset' : hideInput ? '2.8rem' : '2.4rem')};
|
||||
border-radius: 16px;
|
||||
outline: none;
|
||||
user-select: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
font-weight: 500;
|
||||
height: ${({ hideInput }) => (hideInput ? '2.8rem' : '2.4rem')};
|
||||
width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')};
|
||||
padding: ${({ selected, redesignFlag }) =>
|
||||
redesignFlag ? (selected ? '4px 8px 4px 4px' : '6px 6px 6px 8px') : '0 8px'};
|
||||
gap: ${({ redesignFlag }) => (redesignFlag ? '8px' : '0px')};
|
||||
padding: 0 8px;
|
||||
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)};
|
||||
background-color: ${({ selected, theme }) =>
|
||||
selected ? theme.deprecated_bg3 : darken(0.05, theme.deprecated_primary1)};
|
||||
}
|
||||
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 }>`
|
||||
const InputRow = styled.div<{ selected: boolean }>`
|
||||
${({ theme }) => theme.flexRowNoWrap}
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: ${({ selected, redesignFlag }) =>
|
||||
redesignFlag ? '0px' : selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 1rem 1rem'};
|
||||
padding: ${({ selected }) => (selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 1rem 1rem')};
|
||||
`
|
||||
|
||||
const LabelRow = styled.div`
|
||||
@@ -139,7 +109,6 @@ const LabelRow = styled.div`
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
padding: 0 1rem 1rem;
|
||||
|
||||
span:hover {
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => darken(0.2, theme.deprecated_text2)};
|
||||
@@ -148,20 +117,8 @@ const LabelRow = styled.div`
|
||||
|
||||
const FiatRow = styled(LabelRow)<{ redesignFlag: boolean }>`
|
||||
justify-content: flex-end;
|
||||
padding: ${({ redesignFlag }) => redesignFlag && '8px 0px'};
|
||||
height: ${({ redesignFlag }) => !redesignFlag && '24px'};
|
||||
`
|
||||
|
||||
const NoBalanceState = styled.div`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
font-weight: 400;
|
||||
justify-content: space-between;
|
||||
padding: 0px 4px;
|
||||
`
|
||||
const NoBalanceDash = styled.span`
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
font-variant: small-caps;
|
||||
font-feature-settings: 'pnum' on, 'lnum' on;
|
||||
padding: ${({ redesignFlag }) => redesignFlag && '0px 1rem 0.75rem'};
|
||||
height: ${({ redesignFlag }) => (redesignFlag ? '32px' : '16px')};
|
||||
`
|
||||
|
||||
const Aligner = styled.span`
|
||||
@@ -171,34 +128,31 @@ const Aligner = styled.span`
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledDropDown = styled(DropDown)<{ selected: boolean; redesignFlag: boolean }>`
|
||||
const StyledDropDown = styled(DropDown)<{ selected: boolean }>`
|
||||
margin: 0 0.25rem 0 0.35rem;
|
||||
height: 35%;
|
||||
margin-left: ${({ redesignFlag }) => redesignFlag && '8px'};
|
||||
|
||||
path {
|
||||
stroke: ${({ selected, theme }) => (selected ? theme.deprecated_text1 : theme.deprecated_white)};
|
||||
stroke-width: ${({ redesignFlag }) => (redesignFlag ? '2px' : '1.5px')};
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledTokenName = styled.span<{ active?: boolean; redesignFlag: boolean }>`
|
||||
const StyledTokenName = styled.span<{ active?: boolean }>`
|
||||
${({ active }) => (active ? ' margin: 0 0.25rem 0 0.25rem;' : ' margin: 0 0.25rem 0 0.25rem;')}
|
||||
font-size: ${({ active }) => (active ? '18px' : '18px')};
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
|
||||
`
|
||||
|
||||
const StyledBalanceMax = styled.button<{ disabled?: boolean; redesignFlag: boolean }>`
|
||||
const StyledBalanceMax = styled.button<{ disabled?: boolean }>`
|
||||
background-color: transparent;
|
||||
background-color: ${({ theme, redesignFlag }) => !redesignFlag && theme.deprecated_primary5};
|
||||
background-color: ${({ theme }) => theme.deprecated_primary5};
|
||||
border: none;
|
||||
text-transform: ${({ redesignFlag }) => !redesignFlag && 'uppercase'};
|
||||
border-radius: ${({ redesignFlag }) => !redesignFlag && '12px'};
|
||||
color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.accentAction : theme.deprecated_primary1)};
|
||||
border-radius: 12px;
|
||||
color: ${({ theme }) => theme.deprecated_primary1};
|
||||
cursor: pointer;
|
||||
font-size: ${({ redesignFlag }) => (redesignFlag ? '14px' : '11px')};
|
||||
font-weight: ${({ redesignFlag }) => (redesignFlag ? '600' : '500')};
|
||||
margin-left: ${({ redesignFlag }) => (redesignFlag ? '0px' : '0.25rem')};
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
margin-left: 0.25rem;
|
||||
opacity: ${({ disabled }) => (!disabled ? 1 : 0.4)};
|
||||
padding: 4px 6px;
|
||||
pointer-events: ${({ disabled }) => (!disabled ? 'initial' : 'none')};
|
||||
@@ -212,11 +166,9 @@ const StyledBalanceMax = styled.button<{ disabled?: boolean; redesignFlag: boole
|
||||
}
|
||||
`
|
||||
|
||||
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean; redesignFlag: boolean }>`
|
||||
const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean }>`
|
||||
${loadingOpacityMixin};
|
||||
text-align: left;
|
||||
font-variant: ${({ redesignFlag }) => redesignFlag && 'small-caps'};
|
||||
font-feature-settings: ${({ redesignFlag }) => redesignFlag && 'pnum on, lnum on'};
|
||||
`
|
||||
|
||||
interface CurrencyInputPanelProps {
|
||||
@@ -266,10 +218,10 @@ export default function CurrencyInputPanel({
|
||||
}: CurrencyInputPanelProps) {
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const { account, chainId } = useWeb3React()
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
const selectedCurrencyBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined)
|
||||
const theme = useTheme()
|
||||
const redesignFlag = useRedesignFlag()
|
||||
const redesignFlagEnabled = redesignFlag === RedesignVariant.Enabled
|
||||
|
||||
const handleDismissSearch = useCallback(() => {
|
||||
setModalOpen(false)
|
||||
@@ -278,9 +230,9 @@ export default function CurrencyInputPanel({
|
||||
const chainAllowed = isSupportedChain(chainId)
|
||||
|
||||
return (
|
||||
<InputPanel id={id} hideInput={hideInput} {...rest} redesignFlag={redesignFlagEnabled}>
|
||||
<InputPanel id={id} hideInput={hideInput} {...rest}>
|
||||
{locked && (
|
||||
<FixedContainer redesignFlag={redesignFlagEnabled}>
|
||||
<FixedContainer>
|
||||
<AutoColumn gap="sm" justify="center">
|
||||
<Lock />
|
||||
<ThemedText.DeprecatedLabel fontSize="12px" textAlign="center" padding="0 12px">
|
||||
@@ -289,12 +241,8 @@ export default function CurrencyInputPanel({
|
||||
</AutoColumn>
|
||||
</FixedContainer>
|
||||
)}
|
||||
<Container hideInput={hideInput} disabled={!chainAllowed} redesignFlag={redesignFlagEnabled}>
|
||||
<InputRow
|
||||
style={hideInput ? { padding: '0', borderRadius: '8px' } : {}}
|
||||
selected={!onCurrencySelect}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
>
|
||||
<Container hideInput={hideInput} disabled={!chainAllowed}>
|
||||
<InputRow style={hideInput ? { padding: '0', borderRadius: '8px' } : {}} selected={!onCurrencySelect}>
|
||||
{!hideInput && (
|
||||
<StyledNumericalInput
|
||||
className="token-amount-input"
|
||||
@@ -302,16 +250,14 @@ export default function CurrencyInputPanel({
|
||||
onUserInput={onUserInput}
|
||||
disabled={!chainAllowed}
|
||||
$loading={loading}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
/>
|
||||
)}
|
||||
|
||||
<InputCurrencySelect
|
||||
<CurrencySelect
|
||||
disabled={!chainAllowed}
|
||||
visible={currency !== undefined}
|
||||
selected={!!currency}
|
||||
hideInput={hideInput}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
className="open-currency-select-button"
|
||||
onClick={() => {
|
||||
if (onCurrencySelect) {
|
||||
@@ -326,40 +272,26 @@ export default function CurrencyInputPanel({
|
||||
<DoubleCurrencyLogo currency0={pair.token0} currency1={pair.token1} size={24} margin={true} />
|
||||
</span>
|
||||
) : currency ? (
|
||||
<CurrencyLogo style={{ marginRight: '2px' }} currency={currency} size={'24px'} />
|
||||
<CurrencyLogo style={{ marginRight: '0.5rem' }} currency={currency} size={'24px'} />
|
||||
) : null}
|
||||
{pair ? (
|
||||
<StyledTokenName className="pair-name-container" redesignFlag={redesignFlagEnabled}>
|
||||
<StyledTokenName className="pair-name-container">
|
||||
{pair?.token0.symbol}:{pair?.token1.symbol}
|
||||
</StyledTokenName>
|
||||
) : (
|
||||
<StyledTokenName
|
||||
className="token-symbol-container"
|
||||
active={Boolean(currency && currency.symbol)}
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
>
|
||||
<StyledTokenName className="token-symbol-container" active={Boolean(currency && currency.symbol)}>
|
||||
{(currency && currency.symbol && currency.symbol.length > 20
|
||||
? currency.symbol.slice(0, 4) +
|
||||
'...' +
|
||||
currency.symbol.slice(currency.symbol.length - 5, currency.symbol.length)
|
||||
: currency?.symbol) || <Trans>Select token</Trans>}
|
||||
: currency?.symbol) || <Trans>Select a token</Trans>}
|
||||
</StyledTokenName>
|
||||
)}
|
||||
</RowFixed>
|
||||
{onCurrencySelect && <StyledDropDown selected={!!currency} redesignFlag={redesignFlagEnabled} />}
|
||||
{onCurrencySelect && <StyledDropDown selected={!!currency} />}
|
||||
</Aligner>
|
||||
</InputCurrencySelect>
|
||||
</CurrencySelect>
|
||||
</InputRow>
|
||||
{redesignFlagEnabled && !currency && (
|
||||
<NoBalanceState>
|
||||
<FiatRow redesignFlag={redesignFlagEnabled}>
|
||||
<RowBetween>
|
||||
<NoBalanceDash>-</NoBalanceDash>
|
||||
<NoBalanceDash>-</NoBalanceDash>
|
||||
</RowBetween>
|
||||
</FiatRow>
|
||||
</NoBalanceState>
|
||||
)}
|
||||
{!hideInput && !hideBalance && currency && (
|
||||
<FiatRow redesignFlag={redesignFlagEnabled}>
|
||||
<RowBetween>
|
||||
@@ -389,8 +321,8 @@ export default function CurrencyInputPanel({
|
||||
name={EventName.SWAP_MAX_TOKEN_AMOUNT_SELECTED}
|
||||
element={ElementName.MAX_TOKEN_AMOUNT_BUTTON}
|
||||
>
|
||||
<StyledBalanceMax onClick={onMax} redesignFlag={redesignFlagEnabled}>
|
||||
<Trans>Max</Trans>
|
||||
<StyledBalanceMax onClick={onMax}>
|
||||
<Trans>MAX</Trans>
|
||||
</StyledBalanceMax>
|
||||
</TraceEvent>
|
||||
) : null}
|
||||
|
||||
@@ -24,11 +24,13 @@ const StyledNativeLogo = styled(StyledLogo)`
|
||||
|
||||
export default function CurrencyLogo({
|
||||
currency,
|
||||
symbol,
|
||||
size = '24px',
|
||||
style,
|
||||
...rest
|
||||
}: {
|
||||
currency?: Currency | null
|
||||
symbol?: string | null
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
}) {
|
||||
@@ -36,6 +38,7 @@ export default function CurrencyLogo({
|
||||
alt: `${currency?.symbol ?? 'token'} logo`,
|
||||
size,
|
||||
srcs: useCurrencyLogoURIs(currency),
|
||||
symbol: symbol ?? currency?.symbol,
|
||||
style,
|
||||
...rest,
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ interface DoubleCurrencyLogoProps {
|
||||
}
|
||||
|
||||
const HigherLogo = styled(CurrencyLogo)`
|
||||
z-index: 2;
|
||||
z-index: 1;
|
||||
`
|
||||
const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>`
|
||||
position: absolute;
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfoOrDefault } from 'constants/chainInfo'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { useNetworkTokenBalances } from 'hooks/useNetworkTokenBalances'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import NetworkBalance from './NetworkBalance'
|
||||
|
||||
const BalancesCard = styled.div`
|
||||
width: 284px;
|
||||
height: fit-content;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
padding: 20px;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
`
|
||||
const ErrorState = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
`
|
||||
const ErrorText = styled.span`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
const NetworkBalancesSection = styled.div`
|
||||
height: fit-content;
|
||||
`
|
||||
const TotalBalanceSection = styled.div`
|
||||
height: fit-content;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 20px;
|
||||
`
|
||||
const TotalBalance = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
margin-top: 12px;
|
||||
align-items: center;
|
||||
`
|
||||
const TotalBalanceItem = styled.div`
|
||||
display: flex;
|
||||
`
|
||||
|
||||
export default function BalanceSummary({
|
||||
address,
|
||||
networkBalances,
|
||||
totalBalance,
|
||||
}: {
|
||||
address: string
|
||||
networkBalances: (JSX.Element | null)[] | null
|
||||
totalBalance: number
|
||||
}) {
|
||||
const theme = useTheme()
|
||||
const tokenSymbol = useToken(address)?.symbol
|
||||
const { loading, error, data } = useNetworkTokenBalances({ address })
|
||||
|
||||
const { chainId: connectedChainId } = useWeb3React()
|
||||
|
||||
const { label: connectedLabel, logoUrl: connectedLogoUrl } = getChainInfoOrDefault(connectedChainId)
|
||||
const connectedFiatValue = 1
|
||||
const multipleBalances = true // for testing purposes
|
||||
|
||||
if (loading) return null
|
||||
return (
|
||||
<BalancesCard>
|
||||
{error ? (
|
||||
<ErrorState>
|
||||
<AlertTriangle size={24} />
|
||||
<ErrorText>
|
||||
<Trans>There was an error loading your {tokenSymbol} balance</Trans>
|
||||
</ErrorText>
|
||||
</ErrorState>
|
||||
) : multipleBalances ? (
|
||||
<>
|
||||
<TotalBalanceSection>
|
||||
Your balance across all networks
|
||||
<TotalBalance>
|
||||
<TotalBalanceItem>{`${totalBalance} ${tokenSymbol}`}</TotalBalanceItem>
|
||||
<TotalBalanceItem>$4,210.12</TotalBalanceItem>
|
||||
</TotalBalance>
|
||||
</TotalBalanceSection>
|
||||
<NetworkBalancesSection>Your balances by network</NetworkBalancesSection>
|
||||
{data && networkBalances}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Your balance on {connectedLabel}
|
||||
<NetworkBalance
|
||||
logoUrl={connectedLogoUrl}
|
||||
balance={'1'}
|
||||
tokenSymbol={tokenSymbol ?? 'XXX'}
|
||||
fiatValue={connectedFiatValue}
|
||||
label={connectedLabel}
|
||||
networkColor={theme.textPrimary}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</BalancesCard>
|
||||
)
|
||||
}
|
||||
@@ -1,254 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ParentSize } from '@visx/responsive'
|
||||
import PriceChart from 'components/Charts/PriceChart'
|
||||
import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import { VerifiedIcon } from 'components/TokenSafety/TokenSafetyIcon'
|
||||
import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { checkWarning } from 'constants/tokenSafety'
|
||||
import { useCurrency, useIsUserAddedToken, useToken } from 'hooks/Tokens'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { useCallback } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { ArrowLeft, Heart } from 'react-feather'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ClickableStyle, CopyContractAddress } from 'theme'
|
||||
|
||||
import { favoritesAtom, useToggleFavorite } from '../state'
|
||||
import { ClickFavorited } from '../TokenTable/TokenRow'
|
||||
import Resource from './Resource'
|
||||
import ShareButton from './ShareButton'
|
||||
|
||||
export const AboutSection = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 24px 0px;
|
||||
`
|
||||
export const AboutHeader = styled.span`
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
`
|
||||
export const BreadcrumbNavLink = styled(Link)`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
text-decoration: none;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
}
|
||||
`
|
||||
export const ChartHeader = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
gap: 4px;
|
||||
margin-bottom: 24px;
|
||||
`
|
||||
const ContractAddress = styled.button`
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0px;
|
||||
cursor: pointer;
|
||||
`
|
||||
export const ContractAddressSection = styled.div`
|
||||
padding: 24px 0px;
|
||||
`
|
||||
const Contract = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 14px;
|
||||
gap: 4px;
|
||||
`
|
||||
export const ChartContainer = styled.div`
|
||||
display: flex;
|
||||
height: 436px;
|
||||
align-items: center;
|
||||
`
|
||||
export const Stat = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 14px;
|
||||
min-width: 168px;
|
||||
flex: 1;
|
||||
gap: 4px;
|
||||
padding: 24px 0px;
|
||||
`
|
||||
const StatPrice = styled.span`
|
||||
font-size: 28px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
export const StatsSection = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
export const StatPair = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
`
|
||||
export const TokenNameCell = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
align-items: center;
|
||||
`
|
||||
const TokenActions = styled.div`
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
export const TokenInfoContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`
|
||||
const TokenSymbol = styled.span`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
export const TopArea = styled.div`
|
||||
max-width: 832px;
|
||||
`
|
||||
export const ResourcesContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
`
|
||||
const NetworkBadge = styled.div<{ networkColor?: string; backgroundColor?: string }>`
|
||||
border-radius: 5px;
|
||||
padding: 4px 8px;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
color: ${({ theme, networkColor }) => networkColor ?? theme.textPrimary};
|
||||
background-color: ${({ theme, backgroundColor }) => backgroundColor ?? theme.backgroundSurface};
|
||||
`
|
||||
const FavoriteIcon = styled(Heart)<{ isFavorited: boolean }>`
|
||||
${ClickableStyle}
|
||||
height: 22px;
|
||||
width: 24px;
|
||||
color: ${({ isFavorited, theme }) => (isFavorited ? theme.accentAction : theme.textSecondary)};
|
||||
fill: ${({ isFavorited, theme }) => (isFavorited ? theme.accentAction : 'transparent')};
|
||||
`
|
||||
|
||||
export default function LoadedTokenDetail({ address }: { address: string }) {
|
||||
const token = useToken(address)
|
||||
const currency = useCurrency(address)
|
||||
const favoriteTokens = useAtomValue<string[]>(favoritesAtom)
|
||||
const isFavorited = favoriteTokens.includes(address)
|
||||
const toggleFavorite = useToggleFavorite(address)
|
||||
const warning = checkWarning(address)
|
||||
const navigate = useNavigate()
|
||||
const isUserAddedToken = useIsUserAddedToken(token)
|
||||
const [warningModalOpen, setWarningModalOpen] = useState(!!warning && !isUserAddedToken)
|
||||
|
||||
const handleDismissWarning = useCallback(() => {
|
||||
setWarningModalOpen(false)
|
||||
}, [setWarningModalOpen])
|
||||
const chainInfo = getChainInfo(token?.chainId)
|
||||
const networkLabel = chainInfo?.label
|
||||
const networkBadgebackgroundColor = chainInfo?.backgroundColor
|
||||
|
||||
// catch token error and loading state
|
||||
if (!token || !token.name || !token.symbol) {
|
||||
return <div>No Token</div>
|
||||
}
|
||||
const tokenName = token.name
|
||||
const tokenSymbol = token.symbol
|
||||
|
||||
// TODO: format price, add sparkline
|
||||
const aboutToken =
|
||||
'Ethereum is a decentralized computing platform that uses ETH (Ether) to pay transaction fees (gas). Developers can use Ethereum to run decentralized applications (dApps) and issue new crypto assets, known as Ethereum tokens.'
|
||||
const tokenMarketCap = '23.02B'
|
||||
const tokenVolume = '1.6B'
|
||||
|
||||
return (
|
||||
<TopArea>
|
||||
<BreadcrumbNavLink to="/explore">
|
||||
<ArrowLeft size={14} /> Explore
|
||||
</BreadcrumbNavLink>
|
||||
<ChartHeader>
|
||||
<TokenInfoContainer>
|
||||
<TokenNameCell>
|
||||
<CurrencyLogo currency={currency} size={'32px'} />
|
||||
{tokenName} <TokenSymbol>{tokenSymbol}</TokenSymbol>
|
||||
{!warning && <VerifiedIcon size="20px" />}
|
||||
{networkBadgebackgroundColor && (
|
||||
<NetworkBadge networkColor={chainInfo?.color} backgroundColor={networkBadgebackgroundColor}>
|
||||
{networkLabel}
|
||||
</NetworkBadge>
|
||||
)}
|
||||
</TokenNameCell>
|
||||
<TokenActions>
|
||||
<ShareButton tokenName={tokenName} tokenSymbol={tokenSymbol} />
|
||||
<ClickFavorited onClick={toggleFavorite}>
|
||||
<FavoriteIcon isFavorited={isFavorited} />
|
||||
</ClickFavorited>
|
||||
</TokenActions>
|
||||
</TokenInfoContainer>
|
||||
<ChartContainer>
|
||||
<ParentSize>{({ width, height }) => <PriceChart width={width} height={height} />}</ParentSize>
|
||||
</ChartContainer>
|
||||
</ChartHeader>
|
||||
<AboutSection>
|
||||
<AboutHeader>
|
||||
<Trans>About</Trans>
|
||||
</AboutHeader>
|
||||
{aboutToken}
|
||||
<ResourcesContainer>
|
||||
<Resource name={'Etherscan'} link={'https://etherscan.io/'} />
|
||||
<Resource name={'Protocol Info'} link={`https://info.uniswap.org/#/tokens/${address}`} />
|
||||
</ResourcesContainer>
|
||||
</AboutSection>
|
||||
<StatsSection>
|
||||
<StatPair>
|
||||
<Stat>
|
||||
Market cap<StatPrice>${tokenMarketCap}</StatPrice>
|
||||
</Stat>
|
||||
<Stat>
|
||||
{/* TODO: connect to chart's selected time */}
|
||||
24H volume
|
||||
<StatPrice>${tokenVolume}</StatPrice>
|
||||
</Stat>
|
||||
</StatPair>
|
||||
<StatPair>
|
||||
<Stat>
|
||||
52W low
|
||||
<StatPrice>$1,790.01</StatPrice>
|
||||
</Stat>
|
||||
<Stat>
|
||||
52W high
|
||||
<StatPrice>$4,420.71</StatPrice>
|
||||
</Stat>
|
||||
</StatPair>
|
||||
</StatsSection>
|
||||
<ContractAddressSection>
|
||||
<Contract>
|
||||
Contract Address
|
||||
<ContractAddress>
|
||||
<CopyContractAddress address={address} />
|
||||
</ContractAddress>
|
||||
</Contract>
|
||||
</ContractAddressSection>
|
||||
<TokenSafetyModal
|
||||
isOpen={warningModalOpen}
|
||||
tokenAddress={address}
|
||||
onCancel={() => navigate(-1)}
|
||||
onContinue={handleDismissWarning}
|
||||
/>
|
||||
</TopArea>
|
||||
)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { useAtom } from 'jotai'
|
||||
import { Heart } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { SMALL_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { showFavoritesAtom } from '../state'
|
||||
|
||||
const FavoriteButtonContent = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
`
|
||||
const StyledFavoriteButton = styled.button<{ active: boolean }>`
|
||||
padding: 0px 16px;
|
||||
border-radius: 12px;
|
||||
background-color: ${({ theme, active }) => (active ? theme.accentAction : theme.backgroundInteractive)};
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
|
||||
:hover {
|
||||
background-color: ${({ theme, active }) => !active && theme.backgroundModule};
|
||||
}
|
||||
`
|
||||
const FavoriteText = styled.span`
|
||||
@media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
export default function FavoriteButton() {
|
||||
const theme = useTheme()
|
||||
const [showFavorites, setShowFavorites] = useAtom(showFavoritesAtom)
|
||||
return (
|
||||
<StyledFavoriteButton onClick={() => setShowFavorites(!showFavorites)} active={showFavorites}>
|
||||
<FavoriteButtonContent>
|
||||
<Heart size={17} color={theme.textPrimary} fill="transparent" />
|
||||
<FavoriteText>Favorites</FavoriteText>
|
||||
</FavoriteButtonContent>
|
||||
</StyledFavoriteButton>
|
||||
)
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import searchIcon from 'assets/svg/search.svg'
|
||||
import xIcon from 'assets/svg/x.svg'
|
||||
import { useAtom } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { MEDIUM_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { filterStringAtom } from '../state'
|
||||
const ICON_SIZE = '20px'
|
||||
|
||||
const SearchBarContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
`
|
||||
const SearchInput = styled.input<{ expanded: boolean }>`
|
||||
background: no-repeat scroll 7px 7px;
|
||||
background-image: ${({ expanded }) => !expanded && `url(${searchIcon})`};
|
||||
background-size: 20px 20px;
|
||||
background-position: 14px center;
|
||||
background-color: 'transparent';
|
||||
border-radius: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
height: 100%;
|
||||
width: ${({ expanded }) => (expanded ? '100%' : '52px')};
|
||||
font-size: 16px;
|
||||
padding-left: 35px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
transition: width 0.75s cubic-bezier(0, 0.795, 0, 1);
|
||||
|
||||
:hover {
|
||||
cursor: ${({ expanded }) => !expanded && 'pointer'};
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline: none;
|
||||
background-color: ${({ theme }) => theme.accentActionSoft};
|
||||
border: none;
|
||||
}
|
||||
::placeholder {
|
||||
color: ${({ expanded, theme }) => (expanded ? theme.textTertiary : 'transparent')};
|
||||
}
|
||||
::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: ${ICON_SIZE};
|
||||
width: ${ICON_SIZE};
|
||||
background-image: url(${xIcon});
|
||||
margin-right: 10px;
|
||||
background-size: ${ICON_SIZE} ${ICON_SIZE};
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) {
|
||||
width: 100%;
|
||||
}
|
||||
`
|
||||
|
||||
export default function SearchBar() {
|
||||
const [filterString, setFilterString] = useAtom(filterStringAtom)
|
||||
const [isExpanded, setExpanded] = useState(false)
|
||||
return (
|
||||
<SearchBarContainer>
|
||||
<SearchInput
|
||||
expanded={isExpanded}
|
||||
type="search"
|
||||
placeholder="Search by name or token address"
|
||||
id="searchBar"
|
||||
onBlur={() => isExpanded && filterString.length === 0 && setExpanded(false)}
|
||||
onFocus={() => setExpanded(true)}
|
||||
autoComplete="off"
|
||||
value={filterString}
|
||||
onChange={({ target: { value } }) => setFilterString(value)}
|
||||
/>
|
||||
</SearchBarContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
import {
|
||||
favoritesAtom,
|
||||
filterStringAtom,
|
||||
filterTimeAtom,
|
||||
showFavoritesAtom,
|
||||
sortCategoryAtom,
|
||||
sortDirectionAtom,
|
||||
} from 'components/Explore/state'
|
||||
import { useAllTokens } from 'hooks/Tokens'
|
||||
import { TimePeriod, TokenData } from 'hooks/useExplorePageQuery'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { ReactNode, useCallback, useMemo } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { MAX_WIDTH_MEDIA_BREAKPOINT } from '../constants'
|
||||
import { Category, SortDirection } from '../types'
|
||||
import LoadedRow, { HeaderRow, LoadingRow } from './TokenRow'
|
||||
|
||||
const GridContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT};
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
|
||||
0px 24px 32px rgba(0, 0, 0, 0.01);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
border-radius: 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
`
|
||||
const NoTokenDisplay = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
align-items: center;
|
||||
padding: 0px 28px;
|
||||
gap: 8px;
|
||||
`
|
||||
const TokenRowsContainer = styled.div`
|
||||
padding: 4px 0px;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
function useFilteredTokens(addresses: string[]) {
|
||||
const filterString = useAtomValue(filterStringAtom)
|
||||
const favoriteTokens = useAtomValue(favoritesAtom)
|
||||
const showFavorites = useAtomValue(showFavoritesAtom)
|
||||
const shownTokens = showFavorites ? favoriteTokens : addresses
|
||||
const allTokens = useAllTokens()
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
shownTokens.filter((tokenAddress) => {
|
||||
const token = allTokens[tokenAddress]
|
||||
const tokenName = token?.name ?? ''
|
||||
const tokenSymbol = token?.symbol ?? ''
|
||||
|
||||
if (!filterString) {
|
||||
return true
|
||||
}
|
||||
const lowercaseFilterString = filterString.toLowerCase()
|
||||
const addressIncludesFilterString = tokenAddress.toLowerCase().includes(lowercaseFilterString)
|
||||
const nameIncludesFilterString = tokenName.toLowerCase().includes(lowercaseFilterString)
|
||||
const symbolIncludesFilterString = tokenSymbol.toLowerCase().includes(lowercaseFilterString)
|
||||
return nameIncludesFilterString || symbolIncludesFilterString || addressIncludesFilterString
|
||||
}),
|
||||
[allTokens, shownTokens, filterString]
|
||||
)
|
||||
}
|
||||
|
||||
function useSortedTokens(addresses: string[], tokenData: TokenData | null) {
|
||||
const sortCategory = useAtomValue(sortCategoryAtom)
|
||||
const sortDirection = useAtomValue(sortDirectionAtom)
|
||||
const timePeriod = useAtomValue<TimePeriod>(filterTimeAtom)
|
||||
|
||||
const sortFn = useCallback(
|
||||
(a: any, b: any) => {
|
||||
if (a > b) {
|
||||
return sortDirection === SortDirection.decreasing ? -1 : 1
|
||||
} else if (a < b) {
|
||||
return sortDirection === SortDirection.decreasing ? 1 : -1
|
||||
}
|
||||
return 0
|
||||
},
|
||||
[sortDirection]
|
||||
)
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
addresses.sort((token1Address, token2Address) => {
|
||||
if (!tokenData) {
|
||||
return 0
|
||||
}
|
||||
const token1 = tokenData[token1Address] as any
|
||||
const token2 = tokenData[token2Address] as any
|
||||
|
||||
if (!token1 || !token2 || !sortDirection || !sortCategory) {
|
||||
return 0
|
||||
}
|
||||
let a: number
|
||||
let b: number
|
||||
switch (sortCategory) {
|
||||
case Category.marketCap:
|
||||
a = token1.marketCap
|
||||
b = token2.marketCap
|
||||
break
|
||||
case Category.percentChange:
|
||||
a = token1.delta
|
||||
b = token2.delta
|
||||
break
|
||||
case Category.price:
|
||||
a = token1.price
|
||||
b = token2.price
|
||||
break
|
||||
case Category.volume:
|
||||
a = token1.volume[timePeriod]
|
||||
b = token2.volume[timePeriod]
|
||||
break
|
||||
}
|
||||
return sortFn(a, b)
|
||||
}),
|
||||
[addresses, tokenData, sortDirection, sortCategory, sortFn, timePeriod]
|
||||
)
|
||||
}
|
||||
|
||||
function NoTokensState({ message }: { message: ReactNode }) {
|
||||
return (
|
||||
<GridContainer>
|
||||
<HeaderRow />
|
||||
<NoTokenDisplay>{message}</NoTokenDisplay>
|
||||
</GridContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const LOADING_ROWS = Array.from({ length: 100 })
|
||||
.fill(0)
|
||||
.map((_item, index) => <LoadingRow key={index} />)
|
||||
|
||||
function LoadingTokenTable() {
|
||||
return (
|
||||
<GridContainer>
|
||||
<HeaderRow />
|
||||
<TokenRowsContainer>{LOADING_ROWS}</TokenRowsContainer>
|
||||
</GridContainer>
|
||||
)
|
||||
}
|
||||
|
||||
interface TokenTableProps {
|
||||
data: TokenData | null
|
||||
error: string | null
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
export default function TokenTable({ data, error, loading }: TokenTableProps) {
|
||||
const showFavorites = useAtomValue<boolean>(showFavoritesAtom)
|
||||
const timePeriod = useAtomValue<TimePeriod>(filterTimeAtom)
|
||||
const topTokenAddresses = data ? Object.keys(data) : []
|
||||
const filteredTokens = useFilteredTokens(topTokenAddresses)
|
||||
const filteredAndSortedTokens = useSortedTokens(filteredTokens, data)
|
||||
|
||||
/* loading and error state */
|
||||
if (loading) {
|
||||
return <LoadingTokenTable />
|
||||
} else if (error || data === null) {
|
||||
return (
|
||||
<NoTokensState
|
||||
message={
|
||||
<>
|
||||
<AlertTriangle size={16} />
|
||||
An error occured loading tokens. Please try again.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (showFavorites && filteredAndSortedTokens.length === 0) {
|
||||
return <NoTokensState message="You have no favorited tokens" />
|
||||
}
|
||||
|
||||
if (!showFavorites && filteredAndSortedTokens.length === 0) {
|
||||
return <NoTokensState message="No tokens found" />
|
||||
}
|
||||
|
||||
return (
|
||||
<GridContainer>
|
||||
<HeaderRow />
|
||||
<TokenRowsContainer>
|
||||
{filteredAndSortedTokens.map((tokenAddress, index) => (
|
||||
<LoadedRow
|
||||
key={tokenAddress}
|
||||
tokenAddress={tokenAddress}
|
||||
tokenListIndex={index}
|
||||
tokenListLength={filteredAndSortedTokens.length}
|
||||
data={data}
|
||||
timePeriod={timePeriod}
|
||||
/>
|
||||
))}
|
||||
</TokenRowsContainer>
|
||||
</GridContainer>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import { FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||
import { ExploreVariant, useExploreFlag } from 'featureFlags/flags/explore'
|
||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
|
||||
import { Phase1Variant, usePhase1Flag } from 'featureFlags/flags/phase1'
|
||||
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 { useAtomValue } from 'jotai/utils'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import { TokensNetworkFilterVariant, useTokensNetworkFilterFlag } from 'featureFlags/flags/tokensNetworkFilter'
|
||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { useModalIsOpen, useToggleFeatureFlags } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
@@ -44,7 +46,14 @@ const Row = styled.div`
|
||||
|
||||
const CloseButton = styled.button`
|
||||
cursor: pointer;
|
||||
background: 'transparent';
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
|
||||
const ToggleButton = styled.button`
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
@@ -58,7 +67,6 @@ const Header = styled(Row)`
|
||||
const FlagName = styled.span`
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
padding-left: 8px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
`
|
||||
const FlagGroupName = styled.span`
|
||||
@@ -114,17 +122,53 @@ function Variant({ option }: { option: string }) {
|
||||
return <option value={option}>{option}</option>
|
||||
}
|
||||
|
||||
function FeatureFlagOption({
|
||||
variants,
|
||||
featureFlag,
|
||||
value,
|
||||
label,
|
||||
}: {
|
||||
variants: string[]
|
||||
interface FeatureFlagProps {
|
||||
variant: Record<string, string>
|
||||
featureFlag: FeatureFlag
|
||||
value: string
|
||||
label: string
|
||||
}) {
|
||||
}
|
||||
|
||||
function FeatureFlagGroup({ name, children }: PropsWithChildren<{ name: string }>) {
|
||||
// type FeatureFlagOption = { props: FeatureFlagProps }
|
||||
const togglableOptions = Children.toArray(children)
|
||||
.filter<ReactElement<FeatureFlagProps>>(
|
||||
(child): child is ReactElement<FeatureFlagProps> =>
|
||||
child instanceof Object && 'type' in child && child.type === FeatureFlagOption
|
||||
)
|
||||
.map(({ props }) => props)
|
||||
.filter(({ variant }) => {
|
||||
const values = Object.values(variant)
|
||||
return values.includes(BaseVariant.Control) && values.includes(BaseVariant.Enabled)
|
||||
})
|
||||
|
||||
const setFeatureFlags = useUpdateAtom(featureFlagSettings)
|
||||
const allEnabled = togglableOptions.every(({ value }) => value === BaseVariant.Enabled)
|
||||
const onToggle = useCallback(() => {
|
||||
setFeatureFlags((flags) => ({
|
||||
...flags,
|
||||
...togglableOptions.reduce(
|
||||
(flags, { featureFlag }) => ({
|
||||
...flags,
|
||||
[featureFlag]: allEnabled ? BaseVariant.Control : BaseVariant.Enabled,
|
||||
}),
|
||||
{}
|
||||
),
|
||||
}))
|
||||
}, [allEnabled, setFeatureFlags, togglableOptions])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row key={name}>
|
||||
<FlagGroupName>{name}</FlagGroupName>
|
||||
<ToggleButton onClick={onToggle}>{allEnabled ? 'Disable' : 'Enable'} group</ToggleButton>
|
||||
</Row>
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function FeatureFlagOption({ variant, featureFlag, value, label }: FeatureFlagProps) {
|
||||
const updateFlag = useUpdateFlag()
|
||||
const [count, setCount] = useState(0)
|
||||
const featureFlags = useAtomValue(featureFlagSettings)
|
||||
@@ -143,7 +187,7 @@ function FeatureFlagOption({
|
||||
}}
|
||||
value={featureFlags[featureFlag]}
|
||||
>
|
||||
{variants.map((variant) => (
|
||||
{Object.values(variant).map((variant) => (
|
||||
<Variant key={variant} option={variant} />
|
||||
))}
|
||||
</FlagVariantSelection>
|
||||
@@ -163,39 +207,50 @@ export default function FeatureFlagModal() {
|
||||
<X size={24} />
|
||||
</CloseButton>
|
||||
</Header>
|
||||
<FlagGroupName>Phase 1</FlagGroupName>
|
||||
<FeatureFlagOption
|
||||
variants={Object.values(Phase1Variant)}
|
||||
value={usePhase1Flag()}
|
||||
featureFlag={FeatureFlag.phase1}
|
||||
label="All Phase 1 changes (nft features)."
|
||||
/>
|
||||
<FlagGroupName>Phase 0</FlagGroupName>
|
||||
<FeatureFlagOption
|
||||
variants={Object.values(RedesignVariant)}
|
||||
value={useRedesignFlag()}
|
||||
featureFlag={FeatureFlag.redesign}
|
||||
label="Redesign"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variants={Object.values(NavBarVariant)}
|
||||
value={useNavBarFlag()}
|
||||
featureFlag={FeatureFlag.navBar}
|
||||
label="NavBar"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variants={Object.values(ExploreVariant)}
|
||||
value={useExploreFlag()}
|
||||
featureFlag={FeatureFlag.explore}
|
||||
label="Explore"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variants={Object.values(TokenSafetyVariant)}
|
||||
value={useTokenSafetyFlag()}
|
||||
featureFlag={FeatureFlag.tokenSafety}
|
||||
label="Token Safety"
|
||||
/>
|
||||
<SaveButton onClick={() => window.location.reload()}>Save Settings</SaveButton>
|
||||
<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={TokensNetworkFilterVariant}
|
||||
value={useTokensNetworkFilterFlag()}
|
||||
featureFlag={FeatureFlag.tokensNetworkFilter}
|
||||
label="Tokens Network Filter"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={TokenSafetyVariant}
|
||||
value={useTokenSafetyFlag()}
|
||||
featureFlag={FeatureFlag.tokenSafety}
|
||||
label="Token Safety"
|
||||
/>
|
||||
</FeatureFlagGroup>
|
||||
<FeatureFlagGroup name="Phase 1">
|
||||
<FeatureFlagOption variant={NftVariant} value={useNftFlag()} featureFlag={FeatureFlag.nft} label="NFTs" />
|
||||
</FeatureFlagGroup>
|
||||
<FeatureFlagGroup name="Debug">
|
||||
<FeatureFlagOption
|
||||
variant={TraceJsonRpcVariant}
|
||||
value={useTraceJsonRpcFlag()}
|
||||
featureFlag={FeatureFlag.traceJsonRpc}
|
||||
label="Enables JSON-RPC tracing"
|
||||
/>
|
||||
</FeatureFlagGroup>
|
||||
<SaveButton onClick={() => window.location.reload()}>Reload</SaveButton>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const ResponsiveText = styled(ThemedText.DeprecatedLabel)`
|
||||
line-height: 16px;
|
||||
font-size: 14px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
`};
|
||||
|
||||
@@ -39,7 +39,7 @@ const Wrapper = styled.div`
|
||||
padding: 16px 20px;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToMedium}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
|
||||
@@ -11,7 +11,6 @@ import { useCloseModal, useModalIsOpen, useOpenModal, useToggleModal } from 'sta
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
import { isChainAllowed } from 'utils/switchChain'
|
||||
import { isMobile } from 'utils/userAgent'
|
||||
|
||||
const ActiveRowLinkList = styled.div`
|
||||
@@ -53,7 +52,7 @@ const FlyoutMenu = styled.div`
|
||||
width: 272px;
|
||||
z-index: 99;
|
||||
padding-top: 10px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
top: 40px;
|
||||
}
|
||||
`
|
||||
@@ -112,7 +111,7 @@ const NetworkLabel = styled.div`
|
||||
`
|
||||
const SelectorLabel = styled(NetworkLabel)`
|
||||
display: none;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
display: block;
|
||||
margin-right: 8px;
|
||||
}
|
||||
@@ -126,7 +125,7 @@ const NetworkAlertLabel = styled(NetworkLabel)`
|
||||
font-size: 1rem;
|
||||
width: fit-content;
|
||||
font-weight: 500;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
@@ -153,12 +152,12 @@ const SelectorControls = styled.div<{ supportedChain: boolean }>`
|
||||
}
|
||||
`
|
||||
const SelectorLogo = styled(Logo)`
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
`
|
||||
const SelectorWrapper = styled.div`
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
position: relative;
|
||||
}
|
||||
`
|
||||
@@ -281,7 +280,7 @@ const NETWORK_SELECTOR_CHAINS = [
|
||||
]
|
||||
|
||||
export default function NetworkSelector() {
|
||||
const { chainId, provider, connector } = useWeb3React()
|
||||
const { chainId, provider } = useWeb3React()
|
||||
|
||||
const node = useRef<HTMLDivElement>(null)
|
||||
const isOpen = useModalIsOpen(ApplicationModal.NETWORK_SELECTOR)
|
||||
@@ -328,18 +327,16 @@ export default function NetworkSelector() {
|
||||
<FlyoutHeader>
|
||||
<Trans>Select a {!onSupportedChain ? ' supported ' : ''}network</Trans>
|
||||
</FlyoutHeader>
|
||||
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) =>
|
||||
isChainAllowed(connector, chainId) ? (
|
||||
<Row
|
||||
onSelectChain={async (targetChainId: SupportedChainId) => {
|
||||
await selectChain(targetChainId)
|
||||
closeModal()
|
||||
}}
|
||||
targetChain={chainId}
|
||||
key={chainId}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) => (
|
||||
<Row
|
||||
onSelectChain={async (targetChainId: SupportedChainId) => {
|
||||
await selectChain(targetChainId)
|
||||
closeModal()
|
||||
}}
|
||||
targetChain={chainId}
|
||||
key={chainId}
|
||||
/>
|
||||
))}
|
||||
</FlyoutMenuContents>
|
||||
</FlyoutMenu>
|
||||
)}
|
||||
|
||||
@@ -5,12 +5,11 @@ import { getChainInfo } from 'constants/chainInfo'
|
||||
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
|
||||
import useGasPrice from 'hooks/useGasPrice'
|
||||
import useMachineTimeMs from 'hooks/useMachineTime'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import JSBI from 'jsbi'
|
||||
import useBlockNumber from 'lib/hooks/useBlockNumber'
|
||||
import ms from 'ms.macro'
|
||||
import { useEffect, useState } from 'react'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
import styled, { keyframes, useTheme } from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
@@ -27,7 +26,7 @@ const StyledPolling = styled.div<{ warning: boolean }>`
|
||||
color: ${({ theme, warning }) => (warning ? theme.deprecated_yellow3 : theme.deprecated_green1)};
|
||||
transition: 250ms ease color;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
display: none;
|
||||
`}
|
||||
`
|
||||
|
||||
@@ -3,8 +3,7 @@ import useScrollPosition from '@react-hook/window-scroll'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfoOrDefault } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { ExploreVariant, useExploreFlag } from 'featureFlags/flags/explore'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { TokensVariant, useTokensFlag } from 'featureFlags/flags/tokens'
|
||||
import { darken } from 'polished'
|
||||
import { NavLink, useLocation } from 'react-router-dom'
|
||||
import { Text } from 'rebass'
|
||||
@@ -13,7 +12,7 @@ import { useUserHasAvailableClaim } from 'state/claim/hooks'
|
||||
import { useNativeCurrencyBalances } from 'state/connection/hooks'
|
||||
import { useUserHasSubmittedClaim } from 'state/transactions/hooks'
|
||||
import { useDarkModeManager } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { ReactComponent as Logo } from '../../assets/svg/logo.svg'
|
||||
import { ExternalLink, ThemedText } from '../../theme'
|
||||
@@ -47,16 +46,16 @@ const HeaderFrame = styled.div<{ showBackground: boolean }>`
|
||||
transition: background-position 0.1s, box-shadow 0.1s;
|
||||
background-blend-mode: hard-light;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToLarge`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToLarge`
|
||||
grid-template-columns: 48px 1fr 1fr;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
padding: 1rem;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
padding: 1rem;
|
||||
grid-template-columns: 36px 1fr;
|
||||
`};
|
||||
@@ -82,7 +81,7 @@ const HeaderElement = styled.div`
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
align-items: center;
|
||||
`};
|
||||
`
|
||||
@@ -98,13 +97,13 @@ const HeaderLinks = styled(Row)`
|
||||
grid-gap: 10px;
|
||||
overflow: auto;
|
||||
align-items: center;
|
||||
${({ theme }) => theme.mediaWidth.upToLarge`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToLarge`
|
||||
justify-self: start;
|
||||
`};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
justify-self: center;
|
||||
`};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
justify-self: center;
|
||||
@@ -158,7 +157,7 @@ const UNIWrapper = styled.span`
|
||||
`
|
||||
|
||||
const BalanceText = styled(Text)`
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToExtraSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
@@ -169,7 +168,7 @@ const Title = styled.a`
|
||||
pointer-events: auto;
|
||||
justify-self: flex-start;
|
||||
margin-right: 12px;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
justify-self: center;
|
||||
`};
|
||||
:hover {
|
||||
@@ -245,7 +244,7 @@ const StyledExternalLink = styled(ExternalLink)`
|
||||
`
|
||||
|
||||
export default function Header() {
|
||||
const exploreFlag = useExploreFlag()
|
||||
const tokensFlag = useTokensFlag()
|
||||
|
||||
const { account, chainId } = useWeb3React()
|
||||
|
||||
@@ -292,9 +291,9 @@ export default function Header() {
|
||||
<StyledNavLink id={`swap-nav-link`} to={'/swap'}>
|
||||
<Trans>Swap</Trans>
|
||||
</StyledNavLink>
|
||||
{exploreFlag === ExploreVariant.Enabled && (
|
||||
<StyledNavLink id={`explore-nav-link`} to={'/explore'}>
|
||||
<Trans>Explore</Trans>
|
||||
{tokensFlag === TokensVariant.Enabled && (
|
||||
<StyledNavLink id={`tokens-nav-link`} to={'/tokens'}>
|
||||
<Trans>Tokens</Trans>
|
||||
</StyledNavLink>
|
||||
)}
|
||||
<StyledNavLink
|
||||
|
||||
32
src/components/Icons/index.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { ChevronDown, ChevronUp } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
export const StyledChevronDown = styled(ChevronDown)<{ customColor?: string }>`
|
||||
color: ${({ theme, customColor }) => customColor ?? theme.textSecondary};
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.accentActionSoft};
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.fast} color ${timing.in}`};
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledChevronUp = styled(ChevronUp)<{ customColor?: string }>`
|
||||
color: ${({ theme, customColor }) => customColor ?? theme.textSecondary};
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.accentActionSoft};
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.fast} color ${timing.in}`};
|
||||
}
|
||||
`
|
||||
@@ -1,12 +1,18 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ConnectionType } from 'connection'
|
||||
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import styled from 'styled-components/macro'
|
||||
import { colors } from 'theme/colors'
|
||||
|
||||
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
|
||||
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
|
||||
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
|
||||
import sockImg from '../../assets/svg/socks.svg'
|
||||
import { useHasSocks } from '../../hooks/useSocksBalance'
|
||||
import Identicon from '../Identicon'
|
||||
|
||||
const IconWrapper = styled.div<{ size?: number }>`
|
||||
position: relative;
|
||||
${({ theme }) => theme.flexColumnNoWrap};
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -16,27 +22,62 @@ const IconWrapper = styled.div<{ size?: number }>`
|
||||
height: ${({ size }) => (size ? size + 'px' : '32px')};
|
||||
width: ${({ size }) => (size ? size + 'px' : '32px')};
|
||||
}
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
align-items: flex-end;
|
||||
`};
|
||||
`
|
||||
|
||||
export default function StatusIcon({ connectionType }: { connectionType: ConnectionType }) {
|
||||
let image
|
||||
switch (connectionType) {
|
||||
case ConnectionType.INJECTED:
|
||||
image = <Identicon />
|
||||
break
|
||||
case ConnectionType.WALLET_CONNECT:
|
||||
image = <img src={WalletConnectIcon} alt="WalletConnect" />
|
||||
break
|
||||
case ConnectionType.COINBASE_WALLET:
|
||||
image = <img src={CoinbaseWalletIcon} alt="Coinbase Wallet" />
|
||||
break
|
||||
case ConnectionType.FORTMATIC:
|
||||
image = <img src={FortmaticIcon} alt="Fortmatic" />
|
||||
break
|
||||
const SockContainer = styled.div`
|
||||
position: absolute;
|
||||
background-color: ${colors.pink400};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
bottom: -5px;
|
||||
right: -5px;
|
||||
`
|
||||
|
||||
const SockImg = styled.img`
|
||||
width: 7.5px;
|
||||
height: 10px;
|
||||
margin-top: 3px;
|
||||
`
|
||||
|
||||
const Socks = () => {
|
||||
return (
|
||||
<SockContainer>
|
||||
<SockImg src={sockImg} />
|
||||
</SockContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const useIcon = (connectionType: ConnectionType) => {
|
||||
const { account } = useWeb3React()
|
||||
const { avatar } = useENSAvatar(account ?? undefined)
|
||||
const isNavbarEnabled = useNavBarFlag() === NavBarVariant.Enabled
|
||||
|
||||
if ((isNavbarEnabled && avatar) || connectionType === ConnectionType.INJECTED) {
|
||||
return <Identicon />
|
||||
} else if (connectionType === ConnectionType.WALLET_CONNECT) {
|
||||
return <img src={WalletConnectIcon} alt="WalletConnect" />
|
||||
} else if (connectionType === ConnectionType.COINBASE_WALLET) {
|
||||
return <img src={CoinbaseWalletIcon} alt="Coinbase Wallet" />
|
||||
}
|
||||
|
||||
return <IconWrapper size={16}>{image}</IconWrapper>
|
||||
return undefined
|
||||
}
|
||||
|
||||
export default function StatusIcon({ connectionType, size }: { connectionType: ConnectionType; size?: number }) {
|
||||
const hasSocks = useHasSocks()
|
||||
const isNavbarEnabled = useNavBarFlag() === NavBarVariant.Enabled
|
||||
const icon = useIcon(connectionType)
|
||||
|
||||
return (
|
||||
<IconWrapper size={size ?? 16}>
|
||||
{isNavbarEnabled && hasSocks && <Socks />}
|
||||
{icon}
|
||||
</IconWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import jazzicon from '@metamask/jazzicon'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { NavBarVariant, useNavBarFlag } from 'featureFlags/flags/navBar'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import { useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const StyledIdenticon = styled.div`
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
const StyledIdenticon = styled.div<{ isNavbarEnabled: boolean }>`
|
||||
height: ${({ isNavbarEnabled }) => (isNavbarEnabled ? '24px' : '1rem')};
|
||||
width: ${({ isNavbarEnabled }) => (isNavbarEnabled ? '24px' : '1rem')};
|
||||
border-radius: 1.125rem;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg4};
|
||||
font-size: initial;
|
||||
@@ -22,8 +23,10 @@ export default function Identicon() {
|
||||
const { account } = useWeb3React()
|
||||
const { avatar } = useENSAvatar(account ?? undefined)
|
||||
const [fetchable, setFetchable] = useState(true)
|
||||
const isNavbarEnabled = useNavBarFlag() === NavBarVariant.Enabled
|
||||
const iconSize = isNavbarEnabled ? 24 : 16
|
||||
|
||||
const icon = useMemo(() => account && jazzicon(16, parseInt(account.slice(2, 10), 16)), [account])
|
||||
const icon = useMemo(() => account && jazzicon(iconSize, parseInt(account.slice(2, 10), 16)), [account, iconSize])
|
||||
const iconRef = useRef<HTMLDivElement>(null)
|
||||
useLayoutEffect(() => {
|
||||
const current = iconRef.current
|
||||
@@ -41,7 +44,7 @@ export default function Identicon() {
|
||||
}, [icon, iconRef])
|
||||
|
||||
return (
|
||||
<StyledIdenticon>
|
||||
<StyledIdenticon isNavbarEnabled={isNavbarEnabled}>
|
||||
{avatar && fetchable ? (
|
||||
<StyledAvatar alt="avatar" src={avatar} onError={() => setFetchable(false)}></StyledAvatar>
|
||||
) : (
|
||||
|
||||
@@ -48,11 +48,11 @@ const StyledInput = styled(NumericalInput)<{ usePercent?: boolean }>`
|
||||
font-weight: 500;
|
||||
padding: 0 10px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
font-size: 16px;
|
||||
`};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToExtraSmall`
|
||||
font-size: 12px;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -19,7 +19,7 @@ const HandleAccent = styled.path`
|
||||
|
||||
stroke-width: 1.5;
|
||||
stroke: ${({ theme }) => theme.deprecated_white};
|
||||
opacity: 0.6;
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
`
|
||||
|
||||
const LabelGroup = styled.g<{ visible: boolean }>`
|
||||
|
||||
@@ -6,13 +6,12 @@ import { AutoColumn, ColumnCenter } from 'components/Column'
|
||||
import Loader from 'components/Loader'
|
||||
import { format } from 'd3'
|
||||
import { useColor } from 'hooks/useColor'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { saturate } from 'polished'
|
||||
import React, { ReactNode, useCallback, useMemo } from 'react'
|
||||
import { BarChart2, CloudOff, Inbox } from 'react-feather'
|
||||
import { batch } from 'react-redux'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { ThemedText } from '../../theme'
|
||||
import { Chart } from './Chart'
|
||||
|
||||
@@ -14,13 +14,15 @@ export default function ListLogo({
|
||||
style,
|
||||
size = '24px',
|
||||
alt,
|
||||
symbol,
|
||||
}: {
|
||||
logoURI: string
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
alt?: string
|
||||
symbol?: string
|
||||
}) {
|
||||
const srcs: string[] = useHttpLocations(logoURI)
|
||||
|
||||
return <StyledListLogo alt={alt} size={size} srcs={srcs} style={style} />
|
||||
return <StyledListLogo alt={alt} size={size} symbol={symbol} srcs={srcs} style={style} />
|
||||
}
|
||||
|
||||
@@ -9,12 +9,13 @@ const rotate = keyframes`
|
||||
}
|
||||
`
|
||||
|
||||
const StyledSVG = styled.svg<{ size: string; stroke?: string }>`
|
||||
const StyledSVG = styled.svg<{ size: string; stroke?: string; redesignFlag?: boolean }>`
|
||||
animation: 2s ${rotate} linear infinite;
|
||||
height: ${({ size }) => size};
|
||||
width: ${({ size }) => size};
|
||||
path {
|
||||
stroke: ${({ stroke, theme }) => stroke ?? theme.deprecated_primary1};
|
||||
stroke: ${({ stroke, redesignFlag, theme }) =>
|
||||
redesignFlag ? theme.accentActive : stroke ?? theme.deprecated_primary1};
|
||||
}
|
||||
`
|
||||
|
||||
@@ -25,17 +26,29 @@ const StyledSVG = styled.svg<{ size: string; stroke?: string }>`
|
||||
export default function Loader({
|
||||
size = '16px',
|
||||
stroke,
|
||||
strokeWidth,
|
||||
redesignFlag,
|
||||
...rest
|
||||
}: {
|
||||
size?: string
|
||||
stroke?: string
|
||||
strokeWidth?: number
|
||||
redesignFlag?: boolean
|
||||
[k: string]: any
|
||||
}) {
|
||||
return (
|
||||
<StyledSVG viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" size={size} stroke={stroke} {...rest}>
|
||||
<StyledSVG
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
size={size}
|
||||
stroke={stroke}
|
||||
redesignFlag={redesignFlag}
|
||||
{...rest}
|
||||
>
|
||||
<path
|
||||
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 9.27455 20.9097 6.80375 19.1414 5"
|
||||
strokeWidth="2.5"
|
||||
strokeWidth={strokeWidth ?? '2.5'}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
import { useState } from 'react'
|
||||
import { Slash } from 'react-feather'
|
||||
import { ImageProps } from 'rebass'
|
||||
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
const BAD_SRCS: { [tokenAddress: string]: true } = {}
|
||||
|
||||
interface LogoProps extends Pick<ImageProps, 'style' | 'alt' | 'className'> {
|
||||
srcs: string[]
|
||||
symbol?: string
|
||||
size?: string
|
||||
}
|
||||
|
||||
const MissingImageLogo = styled.div<{ size?: string }>`
|
||||
--size: ${({ size }) => size};
|
||||
border-radius: 100px;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
background-color: ${({ theme }) => theme.backgroundInteractive};
|
||||
font-size: calc(var(--size) / 3);
|
||||
font-weight: 500;
|
||||
height: ${({ size }) => size ?? '24px'};
|
||||
line-height: ${({ size }) => size ?? '24px'};
|
||||
text-align: center;
|
||||
width: ${({ size }) => size ?? '24px'};
|
||||
`
|
||||
|
||||
/**
|
||||
* Renders an image by sequentially trying a list of URIs, and then eventually a fallback triangle alert
|
||||
*/
|
||||
export default function Logo({ srcs, alt, style, ...rest }: LogoProps) {
|
||||
export default function Logo({ srcs, alt, style, size, symbol, ...rest }: LogoProps) {
|
||||
const [, refresh] = useState<number>(0)
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
const src: string | undefined = srcs.find((src) => !BAD_SRCS[src])
|
||||
|
||||
if (src) {
|
||||
@@ -35,5 +46,10 @@ export default function Logo({ srcs, alt, style, ...rest }: LogoProps) {
|
||||
)
|
||||
}
|
||||
|
||||
return <Slash {...rest} style={{ ...style, color: theme.deprecated_bg4 }} />
|
||||
return (
|
||||
<MissingImageLogo size={size}>
|
||||
{/* use only first 3 characters of Symbol for design reasons */}
|
||||
{symbol?.toUpperCase().replace('$', '').replace(/\s+/g, '').slice(0, 3)}
|
||||
</MissingImageLogo>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ const MenuFlyout = styled.span<{ flyoutAlignment?: FlyoutAlignment }>`
|
||||
: css`
|
||||
left: 0rem;
|
||||
`};
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
bottom: unset;
|
||||
right: 0;
|
||||
left: unset;
|
||||
@@ -278,7 +278,7 @@ export default function Menu() {
|
||||
</ToggleMenuItem>
|
||||
<ToggleMenuItem onClick={() => toggleDarkMode()}>
|
||||
<div>{darkMode ? <Trans>Light Theme</Trans> : <Trans>Dark Theme</Trans>}</div>
|
||||
{darkMode ? <Moon opacity={0.6} size={16} /> : <Sun opacity={0.6} size={16} />}
|
||||
{darkMode ? <Sun opacity={0.6} size={16} /> : <Moon opacity={0.6} size={16} />}
|
||||
</ToggleMenuItem>
|
||||
<MenuItem href="https://docs.uniswap.org/">
|
||||
<div>
|
||||
|
||||
@@ -4,14 +4,15 @@ import React from 'react'
|
||||
import { animated, useSpring, useTransition } from 'react-spring'
|
||||
import { useGesture } from 'react-use-gesture'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
import { isMobile } from '../../utils/userAgent'
|
||||
|
||||
const AnimatedDialogOverlay = animated(DialogOverlay)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const StyledDialogOverlay = styled(AnimatedDialogOverlay)`
|
||||
const StyledDialogOverlay = styled(AnimatedDialogOverlay)<{ redesignFlag?: boolean }>`
|
||||
&[data-reach-dialog-overlay] {
|
||||
z-index: 2;
|
||||
z-index: ${Z_INDEX.modalBackdrop};
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -19,14 +20,14 @@ const StyledDialogOverlay = styled(AnimatedDialogOverlay)`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
background-color: ${({ theme }) => theme.deprecated_modalBG};
|
||||
background-color: ${({ theme, redesignFlag }) => (redesignFlag ? theme.backgroundScrim : theme.deprecated_modalBG)};
|
||||
}
|
||||
`
|
||||
|
||||
const AnimatedDialogContent = animated(DialogContent)
|
||||
// destructure to not pass custom props to Dialog DOM element
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...rest }) => (
|
||||
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, redesignFlag, ...rest }) => (
|
||||
<AnimatedDialogContent {...rest} />
|
||||
)).attrs({
|
||||
'aria-label': 'dialog',
|
||||
@@ -37,7 +38,8 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
|
||||
margin: 0 0 2rem 0;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg0};
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg1};
|
||||
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadow1)};
|
||||
box-shadow: ${({ theme, redesignFlag }) =>
|
||||
redesignFlag ? theme.deepShadow : `0 4px 8px 0 ${transparentize(0.95, theme.shadow1)}`};
|
||||
padding: 0px;
|
||||
width: 50vw;
|
||||
overflow-y: auto;
|
||||
@@ -58,11 +60,11 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
|
||||
`}
|
||||
display: flex;
|
||||
border-radius: 20px;
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
width: 65vw;
|
||||
margin: 0;
|
||||
`}
|
||||
${({ theme, mobile }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme, mobile }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
width: 85vw;
|
||||
${
|
||||
mobile &&
|
||||
@@ -84,6 +86,7 @@ interface ModalProps {
|
||||
maxHeight?: number
|
||||
initialFocusRef?: React.RefObject<any>
|
||||
children?: React.ReactNode
|
||||
redesignFlag?: boolean
|
||||
}
|
||||
|
||||
export default function Modal({
|
||||
@@ -93,6 +96,7 @@ export default function Modal({
|
||||
maxHeight = 90,
|
||||
initialFocusRef,
|
||||
children,
|
||||
redesignFlag,
|
||||
}: ModalProps) {
|
||||
const fadeTransition = useTransition(isOpen, null, {
|
||||
config: { duration: 200 },
|
||||
@@ -124,6 +128,7 @@ export default function Modal({
|
||||
onDismiss={onDismiss}
|
||||
initialFocusRef={initialFocusRef}
|
||||
unstable_lockFocusAcrossFrames={false}
|
||||
redesignFlag={redesignFlag}
|
||||
>
|
||||
<StyledDialogContent
|
||||
{...(isMobile
|
||||
@@ -136,6 +141,7 @@ export default function Modal({
|
||||
minHeight={minHeight}
|
||||
maxHeight={maxHeight}
|
||||
mobile={isMobile}
|
||||
redesignFlag={redesignFlag}
|
||||
>
|
||||
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
|
||||
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useContext } from 'react'
|
||||
import { ArrowUpCircle } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import Circle from '../../assets/images/blue-loader.svg'
|
||||
import { CloseIcon, CustomLightSpinner, ThemedText } from '../../theme'
|
||||
@@ -49,7 +48,7 @@ export function SubmittedView({
|
||||
onDismiss: () => void
|
||||
hash: string | undefined
|
||||
}) {
|
||||
const theme = useContext(ThemeContext)
|
||||
const theme = useTheme()
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
return (
|
||||
|
||||
53
src/components/NavBar/ChainSwitcher.css.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
import { lightGrayOverlayOnHover } from 'nft/css/common.css'
|
||||
|
||||
import { breakpoints, sprinkles } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const ChainSwitcher = style([
|
||||
lightGrayOverlayOnHover,
|
||||
sprinkles({
|
||||
borderRadius: '8',
|
||||
paddingY: '8',
|
||||
paddingX: '12',
|
||||
cursor: 'pointer',
|
||||
border: 'none',
|
||||
color: 'blackBlue',
|
||||
background: 'none',
|
||||
}),
|
||||
])
|
||||
|
||||
export const ChainSwitcherRow = style([
|
||||
lightGrayOverlayOnHover,
|
||||
sprinkles({
|
||||
border: 'none',
|
||||
justifyContent: 'space-between',
|
||||
paddingX: '8',
|
||||
paddingY: '8',
|
||||
cursor: 'pointer',
|
||||
color: 'blackBlue',
|
||||
borderRadius: '12',
|
||||
width: { sm: 'full' },
|
||||
}),
|
||||
{
|
||||
lineHeight: '24px',
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.sm}px)`]: {
|
||||
width: '204px',
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export const Image = style([
|
||||
sprinkles({
|
||||
width: '20',
|
||||
height: '20',
|
||||
}),
|
||||
])
|
||||
|
||||
export const Icon = style([
|
||||
Image,
|
||||
sprinkles({
|
||||
marginRight: '12',
|
||||
}),
|
||||
])
|
||||
129
src/components/NavBar/ChainSwitcher.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { StyledChevronDown, StyledChevronUp } from 'components/Icons'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import useSelectChain from 'hooks/useSelectChain'
|
||||
import useSyncChainQuery from 'hooks/useSyncChainQuery'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Portal } from 'nft/components/common/Portal'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { CheckMarkIcon, TokenWarningRedIcon } from 'nft/components/icons'
|
||||
import { subhead } from 'nft/css/common.css'
|
||||
import { themeVars, vars } from 'nft/css/sprinkles.css'
|
||||
import { useIsMobile } from 'nft/hooks'
|
||||
import { ReactNode, useReducer, useRef } from 'react'
|
||||
|
||||
import * as styles from './ChainSwitcher.css'
|
||||
import { NavDropdown } from './NavDropdown'
|
||||
|
||||
const ChainRow = ({
|
||||
targetChain,
|
||||
onSelectChain,
|
||||
}: {
|
||||
targetChain: SupportedChainId
|
||||
onSelectChain: (targetChain: number) => void
|
||||
}) => {
|
||||
const { chainId } = useWeb3React()
|
||||
const active = chainId === targetChain
|
||||
const { label, logoUrl } = getChainInfo(targetChain)
|
||||
|
||||
return (
|
||||
<Column borderRadius="12">
|
||||
<Row
|
||||
as="button"
|
||||
background="none"
|
||||
className={`${styles.ChainSwitcherRow} ${subhead}`}
|
||||
onClick={() => onSelectChain(targetChain)}
|
||||
>
|
||||
<ChainDetails>
|
||||
<img src={logoUrl} alt={label} className={styles.Icon} />
|
||||
{label}
|
||||
</ChainDetails>
|
||||
{active && <CheckMarkIcon width={20} height={20} color={vars.color.blue400} />}
|
||||
</Row>
|
||||
</Column>
|
||||
)
|
||||
}
|
||||
|
||||
const ChainDetails = ({ children }: { children: ReactNode }) => <Row>{children}</Row>
|
||||
|
||||
const NETWORK_SELECTOR_CHAINS = [
|
||||
SupportedChainId.MAINNET,
|
||||
SupportedChainId.POLYGON,
|
||||
SupportedChainId.OPTIMISM,
|
||||
SupportedChainId.ARBITRUM_ONE,
|
||||
SupportedChainId.CELO,
|
||||
]
|
||||
|
||||
interface ChainSwitcherProps {
|
||||
leftAlign?: boolean
|
||||
}
|
||||
|
||||
export const ChainSwitcher = ({ leftAlign }: ChainSwitcherProps) => {
|
||||
const { chainId } = useWeb3React()
|
||||
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const modalRef = useRef<HTMLDivElement>(null)
|
||||
useOnClickOutside(ref, isOpen ? toggleOpen : undefined, [modalRef])
|
||||
|
||||
const info = chainId ? getChainInfo(chainId) : undefined
|
||||
|
||||
const selectChain = useSelectChain()
|
||||
useSyncChainQuery()
|
||||
|
||||
if (!chainId) {
|
||||
return null
|
||||
}
|
||||
|
||||
const isSupported = !!info
|
||||
|
||||
const dropdown = (
|
||||
<NavDropdown top="56" left={leftAlign ? '0' : 'auto'} right={leftAlign ? 'auto' : '0'} ref={modalRef}>
|
||||
<Column marginX="8">
|
||||
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) => (
|
||||
<ChainRow
|
||||
onSelectChain={async (targetChainId: SupportedChainId) => {
|
||||
await selectChain(targetChainId)
|
||||
toggleOpen()
|
||||
}}
|
||||
targetChain={chainId}
|
||||
key={chainId}
|
||||
/>
|
||||
))}
|
||||
</Column>
|
||||
</NavDropdown>
|
||||
)
|
||||
|
||||
return (
|
||||
<Box position="relative" ref={ref}>
|
||||
<Row
|
||||
as="button"
|
||||
gap="8"
|
||||
className={styles.ChainSwitcher}
|
||||
background={isOpen ? 'accentActiveSoft' : 'none'}
|
||||
onClick={toggleOpen}
|
||||
>
|
||||
{!isSupported ? (
|
||||
<>
|
||||
<TokenWarningRedIcon fill={themeVars.colors.darkGray} width={24} height={24} />
|
||||
<Box as="span" className={subhead} display={{ sm: 'none', xxl: 'flex' }} style={{ lineHeight: '20px' }}>
|
||||
Unsupported
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<img src={info.logoUrl} alt={info.label} className={styles.Image} />
|
||||
<Box as="span" className={subhead} display={{ sm: 'none', xxl: 'flex' }} style={{ lineHeight: '20px' }}>
|
||||
{info.label}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
{isOpen ? <StyledChevronUp /> : <StyledChevronDown />}
|
||||
</Row>
|
||||
{isOpen && (isMobile ? <Portal>{dropdown}</Portal> : <>{dropdown}</>)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
68
src/components/NavBar/MenuDropdown.css.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
|
||||
import { sprinkles, themeVars, vars } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const hover = style([
|
||||
sprinkles({
|
||||
transition: '250',
|
||||
borderRadius: '12',
|
||||
}),
|
||||
{
|
||||
':hover': {
|
||||
background: vars.color.lightGrayOverlay,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export const MenuRow = style([
|
||||
hover,
|
||||
sprinkles({
|
||||
color: 'blackBlue',
|
||||
paddingY: '8',
|
||||
paddingX: '8',
|
||||
width: 'full',
|
||||
whiteSpace: 'nowrap',
|
||||
}),
|
||||
{
|
||||
lineHeight: '24px',
|
||||
textDecoration: 'none',
|
||||
},
|
||||
])
|
||||
|
||||
export const PrimaryText = style([
|
||||
{
|
||||
lineHeight: '24px',
|
||||
},
|
||||
])
|
||||
|
||||
export const SecondaryText = style([
|
||||
hover,
|
||||
sprinkles({
|
||||
paddingY: '8',
|
||||
paddingX: '8',
|
||||
color: 'darkGray',
|
||||
width: 'full',
|
||||
}),
|
||||
{
|
||||
lineHeight: '20px',
|
||||
},
|
||||
])
|
||||
|
||||
export const Separator = style([
|
||||
sprinkles({
|
||||
height: '0',
|
||||
marginX: '16',
|
||||
}),
|
||||
{
|
||||
borderTop: 'solid',
|
||||
borderColor: themeVars.colors.medGray,
|
||||
borderWidth: '1px',
|
||||
},
|
||||
])
|
||||
|
||||
export const IconRow = style([
|
||||
sprinkles({
|
||||
paddingX: '16',
|
||||
justifyContent: { sm: 'center', md: 'flex-start' },
|
||||
}),
|
||||
])
|
||||
210
src/components/NavBar/MenuDropdown.tsx
Normal file
@@ -0,0 +1,210 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
|
||||
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import {
|
||||
BarChartIcon,
|
||||
DiscordIconMenu,
|
||||
EllipsisIcon,
|
||||
GithubIconMenu,
|
||||
GovernanceIcon,
|
||||
ThinTagIcon,
|
||||
TwitterIconMenu,
|
||||
} from 'nft/components/icons'
|
||||
import { body, bodySmall } from 'nft/css/common.css'
|
||||
import { themeVars } from 'nft/css/sprinkles.css'
|
||||
import { ReactNode, useReducer, useRef } from 'react'
|
||||
import { NavLink, NavLinkProps } from 'react-router-dom'
|
||||
import { isDevelopmentEnv, isStagingEnv } from 'utils/env'
|
||||
|
||||
import { useToggleModal } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import * as styles from './MenuDropdown.css'
|
||||
import { NavDropdown } from './NavDropdown'
|
||||
import { NavIcon } from './NavIcon'
|
||||
|
||||
const PrimaryMenuRow = ({
|
||||
to,
|
||||
href,
|
||||
close,
|
||||
children,
|
||||
}: {
|
||||
to?: NavLinkProps['to']
|
||||
href?: string
|
||||
close?: () => void
|
||||
children: ReactNode
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{to ? (
|
||||
<NavLink to={to} className={styles.MenuRow}>
|
||||
<Row onClick={close}>{children}</Row>
|
||||
</NavLink>
|
||||
) : (
|
||||
<Row as="a" href={href} target={'_blank'} rel={'noopener noreferrer'} className={styles.MenuRow}>
|
||||
{children}
|
||||
</Row>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const PrimaryMenuRowText = ({ children }: { children: ReactNode }) => {
|
||||
return <Box className={`${styles.PrimaryText} ${body}`}>{children}</Box>
|
||||
}
|
||||
|
||||
PrimaryMenuRow.Text = PrimaryMenuRowText
|
||||
|
||||
const SecondaryLinkedText = ({
|
||||
href,
|
||||
onClick,
|
||||
children,
|
||||
}: {
|
||||
href?: string
|
||||
onClick?: () => void
|
||||
children: ReactNode
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
as={href ? 'a' : 'div'}
|
||||
href={href ?? undefined}
|
||||
target={href ? '_blank' : undefined}
|
||||
rel={href ? 'noopener noreferrer' : undefined}
|
||||
className={`${styles.SecondaryText} ${bodySmall}`}
|
||||
onClick={onClick}
|
||||
cursor="pointer"
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const Separator = () => {
|
||||
return <Box className={styles.Separator} />
|
||||
}
|
||||
|
||||
const IconRow = ({ children }: { children: ReactNode }) => {
|
||||
return <Row className={styles.IconRow}>{children}</Row>
|
||||
}
|
||||
|
||||
const Icon = ({ href, children }: { href?: string; children: ReactNode }) => {
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
as={href ? 'a' : 'div'}
|
||||
href={href ?? undefined}
|
||||
target={href ? '_blank' : undefined}
|
||||
rel={href ? 'noopener noreferrer' : undefined}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
color="blackBlue"
|
||||
background="none"
|
||||
border="none"
|
||||
justifyContent="center"
|
||||
textAlign="center"
|
||||
marginRight="12"
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const MenuDropdown = () => {
|
||||
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
|
||||
const togglePrivacyPolicy = useToggleModal(ApplicationModal.PRIVACY_POLICY)
|
||||
const openFeatureFlagsModal = useToggleModal(ApplicationModal.FEATURE_FLAGS)
|
||||
const nftFlag = useNftFlag()
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
useOnClickOutside(ref, isOpen ? toggleOpen : undefined)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box position="relative" ref={ref}>
|
||||
<NavIcon isActive={isOpen} onClick={toggleOpen}>
|
||||
<EllipsisIcon width={20} height={20} />
|
||||
</NavIcon>
|
||||
|
||||
{isOpen && (
|
||||
<NavDropdown top={{ sm: 'unset', lg: '56' }} bottom={{ sm: '56', lg: 'unset' }} right="0">
|
||||
<Column gap="16">
|
||||
<Column paddingX="8" gap="4">
|
||||
{nftFlag === NftVariant.Enabled && (
|
||||
<PrimaryMenuRow to="/nfts/sell" close={toggleOpen}>
|
||||
<Icon>
|
||||
<ThinTagIcon width={24} height={24} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Sell NFTs</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
)}
|
||||
<PrimaryMenuRow to="/vote" close={toggleOpen}>
|
||||
<Icon>
|
||||
<GovernanceIcon width={24} height={24} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Vote in governance</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
<PrimaryMenuRow href="https://info.uniswap.org/#/">
|
||||
<Icon>
|
||||
<BarChartIcon width={24} height={24} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>View token analytics</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
</Column>
|
||||
<Separator />
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection={{ sm: 'row', md: 'column' }}
|
||||
flexWrap="wrap"
|
||||
alignItems={{ sm: 'center', md: 'flex-start' }}
|
||||
paddingX="8"
|
||||
>
|
||||
<SecondaryLinkedText href="https://help.uniswap.org/en/">
|
||||
<Trans>Help center</Trans> ↗
|
||||
</SecondaryLinkedText>
|
||||
<SecondaryLinkedText href="https://docs.uniswap.org/">
|
||||
<Trans>Documentation</Trans> ↗
|
||||
</SecondaryLinkedText>
|
||||
<SecondaryLinkedText
|
||||
onClick={() => {
|
||||
toggleOpen()
|
||||
togglePrivacyPolicy()
|
||||
}}
|
||||
>
|
||||
<Trans>Legal & Privacy</Trans> ↗
|
||||
</SecondaryLinkedText>
|
||||
{(isDevelopmentEnv() || isStagingEnv()) && (
|
||||
<SecondaryLinkedText onClick={openFeatureFlagsModal}>
|
||||
<Trans>Feature Flags</Trans>
|
||||
</SecondaryLinkedText>
|
||||
)}
|
||||
</Box>
|
||||
<IconRow>
|
||||
<Icon href="https://discord.com/invite/FCfyBSbCU5">
|
||||
<DiscordIconMenu className={styles.hover} width={24} height={24} color={themeVars.colors.darkGray} />
|
||||
</Icon>
|
||||
<Icon href="https://twitter.com/Uniswap">
|
||||
<TwitterIconMenu className={styles.hover} width={24} height={24} color={themeVars.colors.darkGray} />
|
||||
</Icon>
|
||||
<Icon href="https://github.com/Uniswap">
|
||||
<GithubIconMenu className={styles.hover} width={24} height={24} color={themeVars.colors.darkGray} />
|
||||
</Icon>
|
||||
</IconRow>
|
||||
</Column>
|
||||
</NavDropdown>
|
||||
)}
|
||||
</Box>
|
||||
<PrivacyPolicyModal />
|
||||
<FeatureFlagModal />
|
||||
</>
|
||||
)
|
||||
}
|
||||
45
src/components/NavBar/NavDropdown.css.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
|
||||
import { sprinkles } from '../../nft/css/sprinkles.css'
|
||||
|
||||
const baseNavDropdown = style([
|
||||
sprinkles({
|
||||
background: 'lightGray',
|
||||
borderStyle: 'solid',
|
||||
borderColor: 'medGray',
|
||||
borderWidth: '1px',
|
||||
paddingBottom: '8',
|
||||
paddingTop: '8',
|
||||
zIndex: '2',
|
||||
}),
|
||||
{
|
||||
boxShadow: '0px 4px 12px 0px #00000026',
|
||||
},
|
||||
])
|
||||
|
||||
export const NavDropdown = style([
|
||||
baseNavDropdown,
|
||||
sprinkles({
|
||||
position: 'absolute',
|
||||
borderRadius: '12',
|
||||
}),
|
||||
{},
|
||||
])
|
||||
|
||||
export const mobileNavDropdown = style([
|
||||
baseNavDropdown,
|
||||
sprinkles({
|
||||
position: 'fixed',
|
||||
borderTopRightRadius: '12',
|
||||
borderTopLeftRadius: '12',
|
||||
top: 'unset',
|
||||
bottom: '56',
|
||||
left: '0',
|
||||
right: '0',
|
||||
width: 'full',
|
||||
}),
|
||||
{
|
||||
borderRightWidth: '0px',
|
||||
borderLeftWidth: '0px',
|
||||
},
|
||||
])
|
||||
12
src/components/NavBar/NavDropdown.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Box, BoxProps } from 'nft/components/Box'
|
||||
import { useIsMobile } from 'nft/hooks'
|
||||
import { ForwardedRef, forwardRef } from 'react'
|
||||
|
||||
import * as styles from './NavDropdown.css'
|
||||
|
||||
export const NavDropdown = forwardRef((props: BoxProps, ref: ForwardedRef<HTMLElement>) => {
|
||||
const isMobile = useIsMobile()
|
||||
return <Box ref={ref} className={isMobile ? styles.mobileNavDropdown : styles.NavDropdown} {...props} />
|
||||
})
|
||||
|
||||
NavDropdown.displayName = 'NavDropdown'
|
||||
24
src/components/NavBar/NavIcon.css.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
|
||||
import { sprinkles, vars } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const navIcon = style([
|
||||
sprinkles({
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
border: 'none',
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
padding: '10',
|
||||
borderRadius: '8',
|
||||
transition: '250',
|
||||
}),
|
||||
{
|
||||
':hover': {
|
||||
background: vars.color.lightGrayOverlay,
|
||||
},
|
||||
zIndex: 1,
|
||||
},
|
||||
])
|
||||
24
src/components/NavBar/NavIcon.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import * as styles from './NavIcon.css'
|
||||
|
||||
interface NavIconProps {
|
||||
children: ReactNode
|
||||
isActive?: boolean
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
export const NavIcon = ({ children, isActive, onClick }: NavIconProps) => {
|
||||
return (
|
||||
<Box
|
||||
as="button"
|
||||
className={styles.navIcon}
|
||||
background={isActive ? 'accentActiveSoft' : 'none'}
|
||||
color={isActive ? 'blackBlue' : 'darkGray'}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
|
||||
import { sprinkles } from '../../nft/css/sprinkles.css'
|
||||
import { subhead } from '../../nft/css/common.css'
|
||||
import { sprinkles, vars } from '../../nft/css/sprinkles.css'
|
||||
|
||||
export const nav = style([
|
||||
sprinkles({
|
||||
@@ -9,24 +10,115 @@ export const nav = style([
|
||||
width: 'full',
|
||||
height: '72',
|
||||
zIndex: '2',
|
||||
borderStyle: 'solid',
|
||||
background: 'white08',
|
||||
}),
|
||||
{
|
||||
borderWidth: '0.5px',
|
||||
backdropFilter: 'blur(24px)',
|
||||
},
|
||||
])
|
||||
|
||||
export const mobileWalletContainer = style([
|
||||
export const logoContainer = style([
|
||||
sprinkles({
|
||||
position: 'fixed',
|
||||
display: 'flex',
|
||||
bottom: '0',
|
||||
right: '1/2',
|
||||
marginY: '0',
|
||||
marginX: 'auto',
|
||||
marginRight: { sm: '12', xxl: '20' },
|
||||
alignItems: 'center',
|
||||
}),
|
||||
])
|
||||
|
||||
export const logo = style([
|
||||
sprinkles({
|
||||
display: 'block',
|
||||
color: 'blackBlue',
|
||||
}),
|
||||
])
|
||||
|
||||
export const baseContainer = style([
|
||||
sprinkles({
|
||||
alignItems: 'center',
|
||||
}),
|
||||
])
|
||||
|
||||
export const baseSideContainer = style([
|
||||
baseContainer,
|
||||
sprinkles({
|
||||
display: 'flex',
|
||||
width: 'full',
|
||||
flex: '1',
|
||||
flexShrink: '2',
|
||||
}),
|
||||
])
|
||||
|
||||
export const leftSideContainer = style([
|
||||
baseSideContainer,
|
||||
sprinkles({
|
||||
justifyContent: 'flex-start',
|
||||
}),
|
||||
])
|
||||
|
||||
export const middleContainer = style([
|
||||
baseContainer,
|
||||
sprinkles({
|
||||
flex: '1',
|
||||
flexShrink: '1',
|
||||
justifyContent: 'center',
|
||||
display: { sm: 'none', xl: 'flex' },
|
||||
}),
|
||||
])
|
||||
|
||||
export const rightSideContainer = style([
|
||||
baseSideContainer,
|
||||
sprinkles({
|
||||
justifyContent: 'flex-end',
|
||||
}),
|
||||
])
|
||||
|
||||
const baseMenuItem = style([
|
||||
subhead,
|
||||
sprinkles({
|
||||
paddingY: '8',
|
||||
paddingX: '16',
|
||||
marginY: '4',
|
||||
borderRadius: '12',
|
||||
transition: '250',
|
||||
height: 'min',
|
||||
width: 'full',
|
||||
textAlign: 'center',
|
||||
}),
|
||||
{
|
||||
transform: 'translate(50%,-50%)',
|
||||
lineHeight: '24px',
|
||||
textDecoration: 'none',
|
||||
':hover': {
|
||||
background: vars.color.lightGrayOverlay,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export const menuItem = style([
|
||||
baseMenuItem,
|
||||
sprinkles({
|
||||
color: 'darkGray',
|
||||
}),
|
||||
])
|
||||
|
||||
export const activeMenuItem = style([
|
||||
baseMenuItem,
|
||||
sprinkles({
|
||||
color: 'blackBlue',
|
||||
background: 'backgroundFloating',
|
||||
}),
|
||||
])
|
||||
|
||||
export const mobileBottomBar = style([
|
||||
sprinkles({
|
||||
position: 'fixed',
|
||||
display: { sm: 'flex', lg: 'none' },
|
||||
bottom: '0',
|
||||
right: '0',
|
||||
left: '0',
|
||||
justifyContent: 'space-between',
|
||||
paddingY: '4',
|
||||
paddingX: '8',
|
||||
height: '56',
|
||||
background: 'lightGray',
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -1,28 +1,116 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ChainSwitcher } from 'components/NavBar/ChainSwitcher'
|
||||
import { MenuDropdown } from 'components/NavBar/MenuDropdown'
|
||||
import * as styles from 'components/NavBar/Navbar.css'
|
||||
import { SearchBar } from 'components/NavBar/SearchBar'
|
||||
import { ShoppingBag } from 'components/NavBar/ShoppingBag'
|
||||
import Web3Status from 'components/Web3Status'
|
||||
import { useWindowSize } from 'hooks/useWindowSize'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Row } from 'nft/components/Flex'
|
||||
import { UniIcon } from 'nft/components/icons'
|
||||
import { ReactNode } from 'react'
|
||||
import { NavLink, NavLinkProps, useLocation } from 'react-router-dom'
|
||||
|
||||
import { breakpoints } from '../../nft/css/sprinkles.css'
|
||||
import * as styles from './Navbar.css'
|
||||
interface MenuItemProps {
|
||||
href: string
|
||||
id?: NavLinkProps['id']
|
||||
isActive?: boolean
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const MenuItem = ({ href, id, isActive, children }: MenuItemProps) => {
|
||||
return (
|
||||
<NavLink
|
||||
to={href}
|
||||
className={isActive ? styles.activeMenuItem : styles.menuItem}
|
||||
id={id}
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
{children}
|
||||
</NavLink>
|
||||
)
|
||||
}
|
||||
|
||||
const PageTabs = () => {
|
||||
const { pathname } = useLocation()
|
||||
const nftFlag = useNftFlag()
|
||||
|
||||
const isPoolActive =
|
||||
pathname.startsWith('/pool') ||
|
||||
pathname.startsWith('/add') ||
|
||||
pathname.startsWith('/remove') ||
|
||||
pathname.startsWith('/increase') ||
|
||||
pathname.startsWith('/find')
|
||||
|
||||
const MobileNavbar = () => {
|
||||
return (
|
||||
<>
|
||||
<nav className={styles.nav} />
|
||||
<div className={styles.mobileWalletContainer}>
|
||||
<Web3Status />
|
||||
</div>
|
||||
<MenuItem href="/swap" isActive={pathname.startsWith('/swap')}>
|
||||
<Trans>Swap</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem href="/tokens" isActive={pathname.startsWith('/tokens')}>
|
||||
<Trans>Tokens</Trans>
|
||||
</MenuItem>
|
||||
{nftFlag === NftVariant.Enabled && (
|
||||
<MenuItem href="/nfts" isActive={pathname.startsWith('/nfts')}>
|
||||
<Trans>NFTs</Trans>
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem href="/pool" id={'pool-nav-link'} isActive={isPoolActive}>
|
||||
<Trans>Pool</Trans>
|
||||
</MenuItem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const Navbar = () => {
|
||||
const { width: windowWidth } = useWindowSize()
|
||||
const { pathname } = useLocation()
|
||||
const isNftPage = pathname.startsWith('/nfts')
|
||||
|
||||
if (windowWidth && windowWidth < breakpoints.desktopXl) {
|
||||
return <MobileNavbar />
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<nav className={styles.nav}>
|
||||
<Box display="flex" height="full" flexWrap="nowrap" alignItems="stretch">
|
||||
<Box className={styles.leftSideContainer}>
|
||||
<Box as="a" href="#/swap" className={styles.logoContainer}>
|
||||
<UniIcon width="48" height="48" className={styles.logo} />
|
||||
</Box>
|
||||
<Box display={{ sm: 'flex', lg: 'none' }}>
|
||||
<ChainSwitcher leftAlign={true} />
|
||||
</Box>
|
||||
<Row gap="8" display={{ sm: 'none', lg: 'flex' }}>
|
||||
<PageTabs />
|
||||
</Row>
|
||||
</Box>
|
||||
<Box className={styles.middleContainer}>
|
||||
<SearchBar />
|
||||
</Box>
|
||||
<Box className={styles.rightSideContainer}>
|
||||
<Row gap="12">
|
||||
<Box display={{ sm: 'flex', xl: 'none' }}>
|
||||
<SearchBar />
|
||||
</Box>
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<MenuDropdown />
|
||||
</Box>
|
||||
{isNftPage && <ShoppingBag />}
|
||||
<Box display={{ sm: 'none', lg: 'flex' }}>
|
||||
<ChainSwitcher />
|
||||
</Box>
|
||||
|
||||
return <nav className={styles.nav} />
|
||||
<Web3Status />
|
||||
</Row>
|
||||
</Box>
|
||||
</Box>
|
||||
</nav>
|
||||
<Box className={styles.mobileBottomBar}>
|
||||
<PageTabs />
|
||||
<Box marginY="4">
|
||||
<MenuDropdown />
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Navbar
|
||||
|
||||
215
src/components/NavBar/SearchBar.css.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
import { buttonTextSmall, subhead, subheadSmall } from 'nft/css/common.css'
|
||||
|
||||
import { breakpoints, sprinkles, vars } from '../../nft/css/sprinkles.css'
|
||||
|
||||
const DESKTOP_NAVBAR_WIDTH = 360
|
||||
const MAGNIFYING_GLASS_ICON_WIDTH = 28
|
||||
|
||||
const baseSearchStyle = style([
|
||||
sprinkles({
|
||||
paddingY: '12',
|
||||
width: { sm: 'viewWidth' },
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '1px',
|
||||
borderColor: 'medGray',
|
||||
}),
|
||||
{
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.sm}px)`]: {
|
||||
width: `${DESKTOP_NAVBAR_WIDTH}px`,
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export const searchBarContainer = style([
|
||||
sprinkles({
|
||||
right: '0',
|
||||
top: '0',
|
||||
zIndex: '3',
|
||||
display: 'inline-block',
|
||||
}),
|
||||
{
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.lg}px)`]: {
|
||||
right: `-${DESKTOP_NAVBAR_WIDTH / 2 - MAGNIFYING_GLASS_ICON_WIDTH}px`,
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
export const searchBar = style([
|
||||
baseSearchStyle,
|
||||
sprinkles({
|
||||
color: 'placeholder',
|
||||
paddingX: '16',
|
||||
cursor: 'pointer',
|
||||
background: 'lightGray',
|
||||
}),
|
||||
])
|
||||
|
||||
export const searchBarInput = style([
|
||||
sprinkles({
|
||||
padding: '0',
|
||||
fontWeight: 'normal',
|
||||
fontSize: '16',
|
||||
color: { default: 'blackBlue', placeholder: 'placeholder' },
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
}),
|
||||
{
|
||||
lineHeight: '24px',
|
||||
},
|
||||
])
|
||||
|
||||
export const searchBarDropdown = style([
|
||||
baseSearchStyle,
|
||||
sprinkles({
|
||||
borderBottomLeftRadius: '12',
|
||||
borderBottomRightRadius: '12',
|
||||
background: 'lightGray',
|
||||
height: { sm: 'viewHeight', md: 'auto' },
|
||||
}),
|
||||
{
|
||||
borderTop: 'none',
|
||||
},
|
||||
])
|
||||
|
||||
export const suggestionRow = style([
|
||||
sprinkles({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingY: '8',
|
||||
paddingX: '16',
|
||||
}),
|
||||
{
|
||||
':hover': {
|
||||
cursor: 'pointer',
|
||||
background: vars.color.lightGrayOverlay,
|
||||
},
|
||||
textDecoration: 'none',
|
||||
},
|
||||
])
|
||||
|
||||
export const suggestionImage = sprinkles({
|
||||
width: '36',
|
||||
height: '36',
|
||||
borderRadius: 'round',
|
||||
marginRight: '8',
|
||||
})
|
||||
|
||||
export const suggestionPrimaryContainer = style([
|
||||
sprinkles({
|
||||
alignItems: 'flex-start',
|
||||
width: 'full',
|
||||
}),
|
||||
])
|
||||
|
||||
export const suggestionSecondaryContainer = sprinkles({
|
||||
textAlign: 'right',
|
||||
alignItems: 'flex-end',
|
||||
})
|
||||
|
||||
export const primaryText = style([
|
||||
subhead,
|
||||
sprinkles({
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
color: 'blackBlue',
|
||||
}),
|
||||
{
|
||||
lineHeight: '24px',
|
||||
},
|
||||
])
|
||||
|
||||
export const secondaryText = style([
|
||||
buttonTextSmall,
|
||||
sprinkles({
|
||||
color: 'darkGray',
|
||||
}),
|
||||
{
|
||||
lineHeight: '20px',
|
||||
},
|
||||
])
|
||||
|
||||
export const imageHolder = style([
|
||||
suggestionImage,
|
||||
sprinkles({
|
||||
background: 'loading',
|
||||
flexShrink: '0',
|
||||
}),
|
||||
])
|
||||
|
||||
export const suggestionIcon = sprinkles({
|
||||
display: 'flex',
|
||||
flexShrink: '0',
|
||||
})
|
||||
|
||||
export const sectionHeader = style([
|
||||
subheadSmall,
|
||||
sprinkles({
|
||||
color: 'darkGray',
|
||||
}),
|
||||
{
|
||||
lineHeight: '20px',
|
||||
},
|
||||
])
|
||||
|
||||
export const notFoundContainer = style([
|
||||
sectionHeader,
|
||||
sprinkles({
|
||||
paddingY: '4',
|
||||
paddingLeft: '16',
|
||||
}),
|
||||
])
|
||||
|
||||
const visibilityTransition = `visibility ${vars.time[125]}, opacity ${vars.time[125]}`
|
||||
const delayedTransitionProperties = `padding 0s ${vars.time[125]}, height 0s ${vars.time[125]}`
|
||||
|
||||
export const hidden = style([
|
||||
sprinkles({
|
||||
visibility: 'hidden',
|
||||
opacity: '0',
|
||||
padding: '0',
|
||||
height: '0',
|
||||
}),
|
||||
{
|
||||
transition: `${visibilityTransition}, ${delayedTransitionProperties}`,
|
||||
transitionTimingFunction: 'ease-in',
|
||||
},
|
||||
])
|
||||
export const visible = style([
|
||||
sprinkles({
|
||||
visibility: 'visible',
|
||||
opacity: '1',
|
||||
height: 'full',
|
||||
}),
|
||||
{
|
||||
transition: `${visibilityTransition}`,
|
||||
transitionTimingFunction: 'ease-out',
|
||||
},
|
||||
])
|
||||
|
||||
export const searchContentCentered = style({
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.lg}px)`]: {
|
||||
transform: `translateX(${DESKTOP_NAVBAR_WIDTH / 4}px)`,
|
||||
transition: `transform ${vars.time[125]}`,
|
||||
transitionTimingFunction: 'ease-out',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const searchContentLeftAlign = style({
|
||||
'@media': {
|
||||
[`screen and (min-width: ${breakpoints.lg}px)`]: {
|
||||
transform: 'translateX(0)',
|
||||
transition: `transform ${vars.time[125]}`,
|
||||
transitionTimingFunction: 'ease-in',
|
||||
},
|
||||
},
|
||||
})
|
||||
442
src/components/NavBar/SearchBar.tsx
Normal file
@@ -0,0 +1,442 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import clsx from 'clsx'
|
||||
import { NftVariant, useNftFlag } from 'featureFlags/flags/nft'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { organizeSearchResults } from 'lib/utils/searchBar'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { Overlay } from 'nft/components/modals/Overlay'
|
||||
import { magicalGradientOnHover, subheadSmall } from 'nft/css/common.css'
|
||||
import { useIsMobile, useIsTablet, useSearchHistory } from 'nft/hooks'
|
||||
import { fetchSearchCollections, fetchTrendingCollections } 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 { useQuery } from 'react-query'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ClockIcon,
|
||||
MagnifyingGlassIcon,
|
||||
NavMagnifyingGlassIcon,
|
||||
TrendingArrow,
|
||||
} 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
|
||||
).slice(0, 2)
|
||||
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(searchHistory.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">
|
||||
{searchHistory.length > 0 && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={0}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={searchHistory}
|
||||
header={<Trans>Recent searches</Trans>}
|
||||
headerIcon={<ClockIcon />}
|
||||
/>
|
||||
)}
|
||||
{!isNFTPage && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={searchHistory.length}
|
||||
setHoveredIndex={setHoveredIndex}
|
||||
toggleOpen={toggleOpen}
|
||||
suggestions={trendingTokens}
|
||||
header={<Trans>Popular tokens</Trans>}
|
||||
headerIcon={<TrendingArrow />}
|
||||
isLoading={trendingTokensAreLoading}
|
||||
/>
|
||||
)}
|
||||
{!isTokenPage && phase1Flag === NftVariant.Enabled && (
|
||||
<SearchBarDropdownSection
|
||||
hoveredIndex={hoveredIndex}
|
||||
startingIndex={searchHistory.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,
|
||||
searchHistory,
|
||||
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
|
||||
}
|
||||
|
||||
export const SearchBar = () => {
|
||||
const [isOpen, toggleOpen] = useReducer((state: boolean) => !state, false)
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
const debouncedSearchValue = useDebounce(searchValue, 300)
|
||||
const searchRef = useRef<HTMLDivElement>(null)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const { pathname } = useLocation()
|
||||
const phase1Flag = useNftFlag()
|
||||
const isMobile = useIsMobile()
|
||||
const isTablet = useIsTablet()
|
||||
|
||||
useOnClickOutside(searchRef, () => {
|
||||
isOpen && toggleOpen()
|
||||
})
|
||||
|
||||
const { data: collections, isLoading: collectionsAreLoading } = useQuery(
|
||||
['searchCollections', debouncedSearchValue],
|
||||
() => fetchSearchCollections(debouncedSearchValue),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
}
|
||||
)
|
||||
|
||||
const { data: tokens, isLoading: tokensAreLoading } = useQuery(
|
||||
['searchTokens', debouncedSearchValue],
|
||||
() => fetchSearchTokens(debouncedSearchValue),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
}
|
||||
)
|
||||
|
||||
const isNFTPage = pathname.includes('/nfts')
|
||||
|
||||
const [reducedTokens, reducedCollections] = organizeSearchResults(isNFTPage, tokens ?? [], collections ?? [])
|
||||
|
||||
// close dropdown on escape
|
||||
useEffect(() => {
|
||||
const escapeKeyDownHandler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape' && isOpen) {
|
||||
event.preventDefault()
|
||||
toggleOpen()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', escapeKeyDownHandler)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', escapeKeyDownHandler)
|
||||
}
|
||||
}, [isOpen, toggleOpen, collections])
|
||||
|
||||
// clear searchbar when changing pages
|
||||
useEffect(() => {
|
||||
setSearchValue('')
|
||||
}, [pathname])
|
||||
|
||||
// auto set cursor when searchbar is opened
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
inputRef.current?.focus()
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
const placeholderText = phase1Flag === NftVariant.Enabled ? t`Search tokens and NFT collections` : t`Search tokens`
|
||||
const isMobileOrTablet = isMobile || isTablet
|
||||
const showCenteredSearchContent = !isOpen && phase1Flag !== NftVariant.Enabled && !isMobileOrTablet
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
<Box
|
||||
position={{ sm: 'fixed', md: 'absolute' }}
|
||||
width={{ sm: isOpen ? 'viewWidth' : 'auto', md: 'auto' }}
|
||||
ref={searchRef}
|
||||
className={styles.searchBarContainer}
|
||||
display={{ sm: isOpen ? 'inline-block' : 'none', xl: 'inline-block' }}
|
||||
>
|
||||
<Row
|
||||
className={clsx(
|
||||
` ${styles.searchBar} ${!isOpen && !isMobile && magicalGradientOnHover} ${
|
||||
isMobileOrTablet && (isOpen ? styles.visible : styles.hidden)
|
||||
}`
|
||||
)}
|
||||
borderRadius={isOpen || isMobileOrTablet ? undefined : '12'}
|
||||
borderTopRightRadius={isOpen && !isMobile ? '12' : undefined}
|
||||
borderTopLeftRadius={isOpen && !isMobile ? '12' : undefined}
|
||||
borderBottomWidth={isOpen || isMobileOrTablet ? '0px' : '1px'}
|
||||
onClick={() => !isOpen && toggleOpen()}
|
||||
gap="12"
|
||||
>
|
||||
<Box className={showCenteredSearchContent ? styles.searchContentCentered : styles.searchContentLeftAlign}>
|
||||
<Box display={{ sm: 'none', md: 'flex' }}>
|
||||
<MagnifyingGlassIcon />
|
||||
</Box>
|
||||
<Box display={{ sm: 'flex', md: 'none' }} color="placeholder" onClick={toggleOpen}>
|
||||
<ChevronLeftIcon />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
as="input"
|
||||
placeholder={placeholderText}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
!isOpen && toggleOpen()
|
||||
setSearchValue(event.target.value)
|
||||
}}
|
||||
className={`${styles.searchBarInput} ${
|
||||
showCenteredSearchContent ? styles.searchContentCentered : styles.searchContentLeftAlign
|
||||
}`}
|
||||
value={searchValue}
|
||||
ref={inputRef}
|
||||
width={phase1Flag === NftVariant.Enabled || isOpen ? 'full' : '160'}
|
||||
/>
|
||||
</Row>
|
||||
<Box className={clsx(isOpen ? styles.visible : styles.hidden)}>
|
||||
{isOpen && (
|
||||
<SearchBarDropdown
|
||||
toggleOpen={toggleOpen}
|
||||
tokens={reducedTokens}
|
||||
collections={reducedCollections}
|
||||
hasInput={debouncedSearchValue.length > 0}
|
||||
isLoading={tokensAreLoading || (collectionsAreLoading && phase1Flag === NftVariant.Enabled)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<NavIcon onClick={toggleOpen}>
|
||||
<NavMagnifyingGlassIcon width={28} height={28} />
|
||||
</NavIcon>
|
||||
{isOpen && <Overlay />}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
22
src/components/NavBar/ShoppingBag.css.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { style } from '@vanilla-extract/css'
|
||||
import { sprinkles } from 'nft/css/sprinkles.css'
|
||||
|
||||
export const bagQuantity = style([
|
||||
sprinkles({
|
||||
position: 'absolute',
|
||||
top: '4',
|
||||
right: '4',
|
||||
backgroundColor: 'magicGradient',
|
||||
borderRadius: 'round',
|
||||
color: 'explicitWhite',
|
||||
textAlign: 'center',
|
||||
fontWeight: 'semibold',
|
||||
paddingY: '1',
|
||||
paddingX: '4',
|
||||
}),
|
||||
{
|
||||
fontSize: '8px',
|
||||
lineHeight: '12px',
|
||||
minWidth: '14px',
|
||||
},
|
||||
])
|
||||
47
src/components/NavBar/ShoppingBag.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { NavIcon } from 'components/NavBar/NavIcon'
|
||||
import * as styles from 'components/NavBar/ShoppingBag.css'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { BagIcon, HundredsOverflowIcon, TagIcon } from 'nft/components/icons'
|
||||
import { useBag, useSellAsset } from 'nft/hooks'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
export const ShoppingBag = () => {
|
||||
const itemsInBag = useBag((state) => state.itemsInBag)
|
||||
const sellAssets = useSellAsset((state) => state.sellAssets)
|
||||
const [bagQuantity, setBagQuantity] = useState(0)
|
||||
const [sellQuantity, setSellQuantity] = useState(0)
|
||||
const location = useLocation()
|
||||
|
||||
const toggleBag = useBag((s) => s.toggleBag)
|
||||
|
||||
useEffect(() => {
|
||||
setBagQuantity(itemsInBag.length)
|
||||
}, [itemsInBag])
|
||||
|
||||
useEffect(() => {
|
||||
setSellQuantity(sellAssets.length)
|
||||
}, [sellAssets])
|
||||
|
||||
const isSell = location.pathname === '/nfts/sell'
|
||||
|
||||
return (
|
||||
<NavIcon onClick={toggleBag}>
|
||||
{isSell ? (
|
||||
<>
|
||||
<TagIcon width={20} height={20} />
|
||||
{sellQuantity ? (
|
||||
<Box className={styles.bagQuantity}>{sellQuantity > 99 ? <HundredsOverflowIcon /> : sellQuantity}</Box>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<BagIcon width={20} height={20} />
|
||||
{bagQuantity ? (
|
||||
<Box className={styles.bagQuantity}>{bagQuantity > 99 ? <HundredsOverflowIcon /> : bagQuantity}</Box>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</NavIcon>
|
||||
)
|
||||
}
|
||||
195
src/components/NavBar/SuggestionRow.tsx
Normal file
@@ -0,0 +1,195 @@
|
||||
import clsx from 'clsx'
|
||||
import uriToHttp from 'lib/utils/uriToHttp'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { vars } from 'nft/css/sprinkles.css'
|
||||
import { useSearchHistory } from 'nft/hooks'
|
||||
import { FungibleToken, GenieCollection } from 'nft/types'
|
||||
import { ethNumberStandardFormatter } from 'nft/utils/currency'
|
||||
import { putCommas } from 'nft/utils/putCommas'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
|
||||
import { VerifiedIcon } from '../../nft/components/icons'
|
||||
import * as styles from './SearchBar.css'
|
||||
|
||||
interface CollectionRowProps {
|
||||
collection: GenieCollection
|
||||
isHovered: boolean
|
||||
setHoveredIndex: (index: number | undefined) => void
|
||||
toggleOpen: () => void
|
||||
index: number
|
||||
}
|
||||
|
||||
export const CollectionRow = ({ collection, isHovered, setHoveredIndex, toggleOpen, index }: CollectionRowProps) => {
|
||||
const [brokenImage, setBrokenImage] = useState(false)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const addToSearchHistory = useSearchHistory(
|
||||
(state: { addItem: (item: FungibleToken | GenieCollection) => void }) => state.addItem
|
||||
)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
addToSearchHistory(collection)
|
||||
toggleOpen()
|
||||
}, [addToSearchHistory, collection, toggleOpen])
|
||||
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter' && isHovered) {
|
||||
event.preventDefault()
|
||||
navigate(`/nfts/collection/${collection.address}`)
|
||||
handleClick()
|
||||
}
|
||||
}
|
||||
document.addEventListener('keydown', keyDownHandler)
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyDownHandler)
|
||||
}
|
||||
}, [toggleOpen, isHovered, collection, navigate, handleClick])
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={`/nfts/collection/${collection.address}`}
|
||||
onClick={handleClick}
|
||||
onMouseEnter={() => !isHovered && setHoveredIndex(index)}
|
||||
onMouseLeave={() => isHovered && setHoveredIndex(undefined)}
|
||||
className={styles.suggestionRow}
|
||||
style={{ background: isHovered ? vars.color.lightGrayOverlay : 'none' }}
|
||||
>
|
||||
<Row style={{ width: '60%' }}>
|
||||
{!brokenImage && collection.imageUrl ? (
|
||||
<Box
|
||||
as="img"
|
||||
src={collection.imageUrl}
|
||||
alt={collection.name}
|
||||
className={clsx(loaded ? styles.suggestionImage : styles.imageHolder)}
|
||||
onError={() => setBrokenImage(true)}
|
||||
onLoad={() => setLoaded(true)}
|
||||
/>
|
||||
) : (
|
||||
<Box className={styles.imageHolder} />
|
||||
)}
|
||||
<Column className={styles.suggestionPrimaryContainer}>
|
||||
<Row gap="4" width="full">
|
||||
<Box className={styles.primaryText}>{collection.name}</Box>
|
||||
{collection.isVerified && <VerifiedIcon className={styles.suggestionIcon} />}
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>{putCommas(collection.stats.total_supply)} items</Box>
|
||||
</Column>
|
||||
</Row>
|
||||
{collection.floorPrice ? (
|
||||
<Column className={styles.suggestionSecondaryContainer}>
|
||||
<Row gap="4">
|
||||
<Box className={styles.primaryText}>{ethNumberStandardFormatter(collection.floorPrice)} ETH</Box>
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>Floor</Box>
|
||||
</Column>
|
||||
) : null}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
interface TokenRowProps {
|
||||
token: FungibleToken
|
||||
isHovered: boolean
|
||||
setHoveredIndex: (index: number | undefined) => void
|
||||
toggleOpen: () => void
|
||||
index: number
|
||||
}
|
||||
|
||||
export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index }: TokenRowProps) => {
|
||||
const [brokenImage, setBrokenImage] = useState(false)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const addToSearchHistory = useSearchHistory(
|
||||
(state: { addItem: (item: FungibleToken | GenieCollection) => void }) => state.addItem
|
||||
)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
addToSearchHistory(token)
|
||||
toggleOpen()
|
||||
}, [addToSearchHistory, toggleOpen, token])
|
||||
|
||||
// Close the modal on escape
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter' && isHovered) {
|
||||
event.preventDefault()
|
||||
navigate(`/tokens/${token.address}`)
|
||||
handleClick()
|
||||
}
|
||||
}
|
||||
document.addEventListener('keydown', keyDownHandler)
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyDownHandler)
|
||||
}
|
||||
}, [toggleOpen, isHovered, token, navigate, handleClick])
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={`/tokens/${token.address}`}
|
||||
onClick={handleClick}
|
||||
onMouseEnter={() => !isHovered && setHoveredIndex(index)}
|
||||
onMouseLeave={() => isHovered && setHoveredIndex(undefined)}
|
||||
className={styles.suggestionRow}
|
||||
style={{ background: isHovered ? vars.color.lightGrayOverlay : 'none' }}
|
||||
>
|
||||
<Row style={{ width: '65%' }}>
|
||||
{!brokenImage && token.logoURI ? (
|
||||
<Box
|
||||
as="img"
|
||||
src={token.logoURI.includes('ipfs://') ? uriToHttp(token.logoURI)[0] : token.logoURI}
|
||||
alt={token.name}
|
||||
className={clsx(loaded ? styles.suggestionImage : styles.imageHolder)}
|
||||
onError={() => setBrokenImage(true)}
|
||||
onLoad={() => setLoaded(true)}
|
||||
/>
|
||||
) : (
|
||||
<Box className={styles.imageHolder} />
|
||||
)}
|
||||
<Column className={styles.suggestionPrimaryContainer}>
|
||||
<Row gap="4" width="full">
|
||||
<Box className={styles.primaryText}>{token.name}</Box>
|
||||
{token.onDefaultList && <VerifiedIcon className={styles.suggestionIcon} />}
|
||||
</Row>
|
||||
<Box className={styles.secondaryText}>{token.symbol}</Box>
|
||||
</Column>
|
||||
</Row>
|
||||
|
||||
<Column className={styles.suggestionSecondaryContainer}>
|
||||
{token.priceUsd && (
|
||||
<Row gap="4">
|
||||
<Box className={styles.primaryText}>{ethNumberStandardFormatter(token.priceUsd, true)}</Box>
|
||||
</Row>
|
||||
)}
|
||||
{token.price24hChange && (
|
||||
<Box className={styles.secondaryText} color={token.price24hChange >= 0 ? 'green400' : 'red400'}>
|
||||
{token.price24hChange.toFixed(2)}%
|
||||
</Box>
|
||||
)}
|
||||
</Column>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export const SkeletonRow = () => {
|
||||
return (
|
||||
<Row className={styles.suggestionRow}>
|
||||
<Row width="full">
|
||||
<Box className={styles.imageHolder} />
|
||||
<Column gap="4" width="full">
|
||||
<Row justifyContent="space-between">
|
||||
<Box borderRadius="round" height="20" background="loading" style={{ width: '180px' }} />
|
||||
<Box borderRadius="round" height="20" width="48" background="loading" />
|
||||
</Row>
|
||||
|
||||
<Row justifyContent="space-between">
|
||||
<Box borderRadius="round" height="16" width="120" background="loading" />
|
||||
<Box borderRadius="round" height="16" width="48" background="loading" />
|
||||
</Row>
|
||||
</Column>
|
||||
</Row>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { ReactNode } from 'react'
|
||||
import { ArrowLeft } from 'react-feather'
|
||||
import { Link as HistoryLink, useLocation } from 'react-router-dom'
|
||||
@@ -8,7 +7,7 @@ import { Box } from 'rebass'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { resetMintState } from 'state/mint/actions'
|
||||
import { resetMintState as resetMintV3State } from 'state/mint/v3/actions'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import Row, { RowBetween } from '../Row'
|
||||
@@ -24,7 +23,7 @@ const Tabs = styled.div`
|
||||
const StyledHistoryLink = styled(HistoryLink)<{ flex: string | undefined }>`
|
||||
flex: ${({ flex }) => flex ?? 'none'};
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToMedium`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToMedium`
|
||||
flex: none;
|
||||
margin-right: 10px;
|
||||
`};
|
||||
|
||||
@@ -4,9 +4,10 @@ import useInterval from 'lib/hooks/useInterval'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { usePopper } from 'react-popper'
|
||||
import styled from 'styled-components/macro'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
const PopoverContainer = styled.div<{ show: boolean }>`
|
||||
z-index: 9999;
|
||||
z-index: ${Z_INDEX.popover};
|
||||
visibility: ${(props) => (props.show ? 'visible' : 'hidden')};
|
||||
opacity: ${(props) => (props.show ? 1 : 0)};
|
||||
transition: visibility 150ms linear, opacity 150ms linear;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useContext } from 'react'
|
||||
import { AlertCircle } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { ThemedText } from '../../theme'
|
||||
import { AutoColumn } from '../Column'
|
||||
@@ -15,7 +14,7 @@ const RowNoFlex = styled(AutoRow)`
|
||||
|
||||
export default function FailedNetworkSwitchPopup({ chainId }: { chainId: SupportedChainId }) {
|
||||
const chainInfo = getChainInfo(chainId)
|
||||
const theme = useContext(ThemeContext)
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<RowNoFlex>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useCallback, useContext, useEffect } from 'react'
|
||||
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 styled, { ThemeContext } from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
import { PopupContent } from '../../state/application/reducer'
|
||||
@@ -29,7 +30,7 @@ const Popup = styled.div`
|
||||
padding-right: 35px;
|
||||
overflow: hidden;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
min-width: 290px;
|
||||
&:not(:last-of-type) {
|
||||
margin-right: 20px;
|
||||
@@ -57,6 +58,7 @@ export default function PopupItem({
|
||||
popKey: string
|
||||
}) {
|
||||
const removePopup = useRemovePopup()
|
||||
const navbarFlag = useNavBarFlag()
|
||||
const removeThisPopup = useCallback(() => removePopup(popKey), [popKey, removePopup])
|
||||
useEffect(() => {
|
||||
if (removeAfterMs === null) return undefined
|
||||
@@ -70,24 +72,25 @@ export default function PopupItem({
|
||||
}
|
||||
}, [removeAfterMs, removeThisPopup])
|
||||
|
||||
const theme = useContext(ThemeContext)
|
||||
const theme = useTheme()
|
||||
const faderStyle = useSpring({
|
||||
from: { width: '100%' },
|
||||
to: { width: '0%' },
|
||||
config: { duration: removeAfterMs ?? undefined },
|
||||
})
|
||||
|
||||
let popupContent
|
||||
if ('txn' in content) {
|
||||
const {
|
||||
txn: { hash },
|
||||
} = content
|
||||
if (navbarFlag === NavBarVariant.Enabled) return null
|
||||
|
||||
popupContent = <TransactionPopup hash={hash} />
|
||||
} else if ('failedSwitchNetwork' in content) {
|
||||
popupContent = <FailedNetworkSwitchPopup chainId={content.failedSwitchNetwork} />
|
||||
}
|
||||
|
||||
const faderStyle = useSpring({
|
||||
from: { width: '100%' },
|
||||
to: { width: '0%' },
|
||||
config: { duration: removeAfterMs ?? undefined },
|
||||
})
|
||||
|
||||
return (
|
||||
<Popup>
|
||||
<StyledClose color={theme.deprecated_text2} onClick={removeThisPopup} />
|
||||
|
||||
@@ -6,11 +6,11 @@ import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
|
||||
import { useEffect } from 'react'
|
||||
import { MessageCircle, X } from 'react-feather'
|
||||
import { useShowSurveyPopup } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText, Z_INDEX } from 'theme'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
import BGImage from '../../assets/images/survey-orb.svg'
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
|
||||
const Wrapper = styled(AutoColumn)`
|
||||
background: #edeef2;
|
||||
@@ -22,7 +22,7 @@ const Wrapper = styled(AutoColumn)`
|
||||
color: ${({ theme }) => theme.deprecated_text1};
|
||||
overflow: hidden;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
max-width: 100%;
|
||||
`}
|
||||
`
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useContext } from 'react'
|
||||
import { AlertCircle, CheckCircle } from 'react-feather'
|
||||
import styled, { ThemeContext } from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { useTransaction } from '../../state/transactions/hooks'
|
||||
import { ThemedText } from '../../theme'
|
||||
@@ -19,7 +18,7 @@ export default function TransactionPopup({ hash }: { hash: string }) {
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const tx = useTransaction(hash)
|
||||
const theme = useContext(ThemeContext)
|
||||
const theme = useTheme()
|
||||
|
||||
if (!tx) return null
|
||||
const success = Boolean(tx.receipt && tx.receipt.status === 1)
|
||||
|
||||
@@ -17,7 +17,7 @@ const MobilePopupWrapper = styled.div<{ height: string | number }>`
|
||||
margin-bottom: ${({ height }) => (height ? '20px' : 0)};
|
||||
|
||||
display: none;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
display: block;
|
||||
padding-top: 20px;
|
||||
`};
|
||||
@@ -35,8 +35,8 @@ const MobilePopupInner = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const StopOverflowQuery = `@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium + 1}px) and (max-width: ${
|
||||
MEDIA_WIDTHS.upToMedium + 500
|
||||
const StopOverflowQuery = `@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToMedium + 1}px) and (max-width: ${
|
||||
MEDIA_WIDTHS.deprecated_upToMedium + 500
|
||||
}px)`
|
||||
|
||||
const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean; xlPadding: boolean }>`
|
||||
@@ -47,7 +47,7 @@ const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean; xlPadding:
|
||||
width: 100%;
|
||||
z-index: 3;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ const DesktopHeader = styled.div`
|
||||
font-weight: 500;
|
||||
padding: 8px;
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -32,11 +32,11 @@ const MobileHeader = styled.div`
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.upToExtraSmall}px) {
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.deprecated_upToExtraSmall}px) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
@@ -50,12 +50,12 @@ const ToggleWrap = styled.div`
|
||||
`
|
||||
|
||||
const ToggleLabel = styled.div`
|
||||
opacity: 0.6;
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
margin-right: 10px;
|
||||
`
|
||||
|
||||
const MobileTogglePosition = styled.div`
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.upToExtraSmall}px) {
|
||||
@media screen and (max-width: ${MEDIA_WIDTHS.deprecated_upToExtraSmall}px) {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
@@ -48,11 +48,11 @@ const LinkRow = styled(Link)`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
}
|
||||
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToSmall}px) {
|
||||
/* flex-direction: row; */
|
||||
}
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
flex-direction: column;
|
||||
row-gap: 12px;
|
||||
`};
|
||||
@@ -61,7 +61,7 @@ const LinkRow = styled(Link)`
|
||||
const BadgeText = styled.div`
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
font-size: 12px;
|
||||
`};
|
||||
`
|
||||
@@ -78,7 +78,7 @@ const RangeLineItem = styled(DataLineItem)`
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
background-color: ${({ theme }) => theme.deprecated_bg2};
|
||||
border-radius: 12px;
|
||||
padding: 8px 0;
|
||||
@@ -88,7 +88,7 @@ const RangeLineItem = styled(DataLineItem)`
|
||||
const DoubleArrow = styled.span`
|
||||
margin: 0 2px;
|
||||
color: ${({ theme }) => theme.deprecated_text3};
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
margin: 4px;
|
||||
padding: 20px;
|
||||
`};
|
||||
@@ -104,7 +104,7 @@ const ExtentsText = styled.span`
|
||||
color: ${({ theme }) => theme.deprecated_text3};
|
||||
font-size: 14px;
|
||||
margin-right: 4px;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
@@ -122,7 +122,7 @@ const DataText = styled.div`
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
font-size: 14px;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -10,9 +10,9 @@ import { Break } from 'components/earn/styled'
|
||||
import RateToggle from 'components/RateToggle'
|
||||
import { RowBetween, RowFixed } from 'components/Row'
|
||||
import JSBI from 'jsbi'
|
||||
import { ReactNode, useCallback, useContext, useState } from 'react'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { Bound } from 'state/mint/v3/actions'
|
||||
import { ThemeContext } from 'styled-components/macro'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { formatTickPrice } from 'utils/formatTickPrice'
|
||||
import { unwrappedToken } from 'utils/unwrappedToken'
|
||||
@@ -30,7 +30,7 @@ export const PositionPreview = ({
|
||||
baseCurrencyDefault?: Currency | undefined
|
||||
ticksAtLimit: { [bound: string]: boolean | undefined }
|
||||
}) => {
|
||||
const theme = useContext(ThemeContext)
|
||||
const theme = useTheme()
|
||||
|
||||
const currency0 = unwrappedToken(position.pool.token0)
|
||||
const currency1 = unwrappedToken(position.pool.token1)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { useContext } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemeContext } from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { ThemedText } from '../../theme'
|
||||
import { AutoColumn } from '../Column'
|
||||
@@ -54,7 +52,7 @@ interface ProgressCirclesProps {
|
||||
* @param steps array of booleans where true means step is complete
|
||||
*/
|
||||
export default function ProgressCircles({ steps, disabled = false, ...rest }: ProgressCirclesProps) {
|
||||
const theme = useContext(ThemeContext)
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<Wrapper justify={'center'} {...rest}>
|
||||
|
||||
@@ -10,7 +10,8 @@ import { RoutingDiagramEntry } from 'components/swap/SwapRoute'
|
||||
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
|
||||
import { Box } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText, Z_INDEX } from 'theme'
|
||||
import { ThemedText } from 'theme'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
import { ReactComponent as DotLine } from '../../assets/svg/dot_line.svg'
|
||||
import { MouseoverTooltip } from '../Tooltip'
|
||||
|
||||
@@ -13,7 +13,7 @@ import styled from 'styled-components/macro'
|
||||
import { currencyId } from 'utils/currencyId'
|
||||
|
||||
const MobileWrapper = styled(AutoColumn)`
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
${({ theme }) => theme.deprecated_mediaWidth.deprecated_upToSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -9,12 +9,11 @@ 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 useTheme from 'hooks/useTheme'
|
||||
import { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'
|
||||
import { Check } from 'react-feather'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import TokenListLogo from '../../../assets/svg/tokenlist.svg'
|
||||
import { useIsUserAddedToken } from '../../../hooks/Tokens'
|
||||
@@ -129,7 +128,7 @@ function CurrencyRow({
|
||||
eventProperties,
|
||||
}: {
|
||||
currency: Currency
|
||||
onSelect: () => void
|
||||
onSelect: (hasWarning: boolean) => void
|
||||
isSelected: boolean
|
||||
otherSelected: boolean
|
||||
style: CSSProperties
|
||||
@@ -160,13 +159,13 @@ function CurrencyRow({
|
||||
redesignFlag={redesignFlagEnabled}
|
||||
style={style}
|
||||
className={`token-item-${key}`}
|
||||
onKeyPress={(e) => (!isSelected && e.key === 'Enter' ? onSelect() : null)}
|
||||
onClick={() => (isSelected ? null : onSelect())}
|
||||
onKeyPress={(e) => (!isSelected && e.key === 'Enter' ? onSelect(!!warning) : null)}
|
||||
onClick={() => (isSelected ? null : onSelect(!!warning))}
|
||||
disabled={isSelected}
|
||||
selected={otherSelected}
|
||||
>
|
||||
<Column>
|
||||
<CurrencyLogo currency={currency} size={'24px'} />
|
||||
<CurrencyLogo currency={currency} size={'36px'} />
|
||||
</Column>
|
||||
<AutoColumn>
|
||||
<Row>
|
||||
@@ -280,7 +279,7 @@ export default function CurrencyList({
|
||||
currencies: Currency[]
|
||||
otherListTokens?: WrappedTokenInfo[]
|
||||
selectedCurrency?: Currency | null
|
||||
onCurrencySelect: (currency: Currency) => void
|
||||
onCurrencySelect: (currency: Currency, hasWarning?: boolean) => void
|
||||
otherCurrency?: Currency | null
|
||||
fixedListRef?: MutableRefObject<FixedSizeList | undefined>
|
||||
showImportView: () => void
|
||||
@@ -309,7 +308,7 @@ export default function CurrencyList({
|
||||
|
||||
const isSelected = Boolean(currency && selectedCurrency && selectedCurrency.equals(currency))
|
||||
const otherSelected = Boolean(currency && otherCurrency && otherCurrency.equals(currency))
|
||||
const handleSelect = () => currency && onCurrencySelect(currency)
|
||||
const handleSelect = (hasWarning: boolean) => currency && onCurrencySelect(currency, hasWarning)
|
||||
|
||||
const token = currency?.wrapped
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import { sendEvent } from 'components/analytics'
|
||||
import { RedesignVariant, useRedesignFlag } from 'featureFlags/flags/redesign'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import useToggle from 'hooks/useToggle'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { getTokenFilter } from 'lib/hooks/useTokenList/filtering'
|
||||
@@ -19,7 +18,7 @@ import AutoSizer from 'react-virtualized-auto-sizer'
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { Text } from 'rebass'
|
||||
import { useAllTokenBalances } from 'state/connection/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { useAllTokens, useIsUserAddedToken, useSearchInactiveTokenLists, useToken } from '../../hooks/Tokens'
|
||||
import { ButtonText, CloseIcon, IconWrapper, ThemedText } from '../../theme'
|
||||
@@ -52,7 +51,7 @@ interface CurrencySearchProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
selectedCurrency?: Currency | null
|
||||
onCurrencySelect: (currency: Currency) => void
|
||||
onCurrencySelect: (currency: Currency, hasWarning?: boolean) => void
|
||||
otherSelectedCurrency?: Currency | null
|
||||
showCommonBases?: boolean
|
||||
showCurrencyAmount?: boolean
|
||||
@@ -137,9 +136,9 @@ export function CurrencySearch({
|
||||
}, [debouncedQuery, native, filteredSortedTokens])
|
||||
|
||||
const handleCurrencySelect = useCallback(
|
||||
(currency: Currency) => {
|
||||
onCurrencySelect(currency)
|
||||
onDismiss()
|
||||
(currency: Currency, hasWarning?: boolean) => {
|
||||
onCurrencySelect(currency, hasWarning)
|
||||
if (!hasWarning) onDismiss()
|
||||
},
|
||||
[onDismiss, onCurrencySelect]
|
||||
)
|
||||
|
||||
@@ -3,10 +3,12 @@ import { TokenList } from '@uniswap/token-lists'
|
||||
import TokenSafety from 'components/TokenSafety'
|
||||
import { TokenSafetyVariant, useTokenSafetyFlag } from 'featureFlags/flags/tokenSafety'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { memo, useCallback, useEffect, useState } from 'react'
|
||||
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
|
||||
import { useUserAddedTokens } from 'state/user/hooks'
|
||||
|
||||
import useLast from '../../hooks/useLast'
|
||||
import { useWindowSize } from '../../hooks/useWindowSize'
|
||||
import Modal from '../Modal'
|
||||
import { CurrencySearch } from './CurrencySearch'
|
||||
import { ImportList } from './ImportList'
|
||||
@@ -29,9 +31,10 @@ export enum CurrencyModalView {
|
||||
manage,
|
||||
importToken,
|
||||
importList,
|
||||
tokenSafety,
|
||||
}
|
||||
|
||||
export default function CurrencySearchModal({
|
||||
export default memo(function CurrencySearchModal({
|
||||
isOpen,
|
||||
onDismiss,
|
||||
onCurrencySelect,
|
||||
@@ -43,6 +46,7 @@ export default function CurrencySearchModal({
|
||||
}: CurrencySearchModalProps) {
|
||||
const [modalView, setModalView] = useState<CurrencyModalView>(CurrencyModalView.manage)
|
||||
const lastOpen = useLast(isOpen)
|
||||
const userAddedTokens = useUserAddedTokens()
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && !lastOpen) {
|
||||
@@ -50,12 +54,28 @@ export default function CurrencySearchModal({
|
||||
}
|
||||
}, [isOpen, lastOpen])
|
||||
|
||||
const showTokenSafetySpeedbump = (token: Token) => {
|
||||
setWarningToken(token)
|
||||
setModalView(CurrencyModalView.tokenSafety)
|
||||
}
|
||||
|
||||
const tokenSafetyFlag = useTokenSafetyFlag()
|
||||
|
||||
const handleCurrencySelect = useCallback(
|
||||
(currency: Currency) => {
|
||||
onCurrencySelect(currency)
|
||||
onDismiss()
|
||||
(currency: Currency, hasWarning?: boolean) => {
|
||||
if (
|
||||
tokenSafetyFlag === TokenSafetyVariant.Enabled &&
|
||||
hasWarning &&
|
||||
currency.isToken &&
|
||||
!userAddedTokens.find((token) => token.equals(currency))
|
||||
) {
|
||||
showTokenSafetySpeedbump(currency)
|
||||
} else {
|
||||
onCurrencySelect(currency)
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
[onDismiss, onCurrencySelect]
|
||||
[onDismiss, onCurrencySelect, tokenSafetyFlag, userAddedTokens]
|
||||
)
|
||||
|
||||
// for token import view
|
||||
@@ -68,6 +88,9 @@ export default function CurrencySearchModal({
|
||||
const [importList, setImportList] = useState<TokenList | undefined>()
|
||||
const [listURL, setListUrl] = useState<string | undefined>()
|
||||
|
||||
// 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(
|
||||
@@ -75,13 +98,16 @@ export default function CurrencySearchModal({
|
||||
[setModalView, prevView]
|
||||
)
|
||||
|
||||
const tokenSafetyFlag = useTokenSafetyFlag()
|
||||
|
||||
const { height: windowHeight } = useWindowSize()
|
||||
// change min height if not searching
|
||||
let minHeight: number | undefined = 80
|
||||
let modalHeight: number | undefined = 80
|
||||
let content = null
|
||||
switch (modalView) {
|
||||
case CurrencyModalView.search:
|
||||
if (windowHeight) {
|
||||
// Converts pixel units to vh for Modal component
|
||||
modalHeight = Math.min(Math.round((680 / windowHeight) * 100), 80)
|
||||
}
|
||||
content = (
|
||||
<CurrencySearch
|
||||
isOpen={isOpen}
|
||||
@@ -98,29 +124,38 @@ export default function CurrencySearchModal({
|
||||
/>
|
||||
)
|
||||
break
|
||||
case CurrencyModalView.tokenSafety:
|
||||
modalHeight = undefined
|
||||
if (tokenSafetyFlag === TokenSafetyVariant.Enabled && warningToken) {
|
||||
content = (
|
||||
<TokenSafety
|
||||
tokenAddress={warningToken.address}
|
||||
onContinue={() => handleCurrencySelect(warningToken)}
|
||||
onCancel={() => setModalView(CurrencyModalView.search)}
|
||||
showCancel={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
break
|
||||
case CurrencyModalView.importToken:
|
||||
if (importToken) {
|
||||
minHeight = undefined
|
||||
content =
|
||||
tokenSafetyFlag === TokenSafetyVariant.Enabled ? (
|
||||
<TokenSafety
|
||||
tokenAddress={importToken.address}
|
||||
onContinue={() => handleCurrencySelect(importToken)}
|
||||
onCancel={handleBackImport}
|
||||
/>
|
||||
) : (
|
||||
<ImportToken
|
||||
tokens={[importToken]}
|
||||
onDismiss={onDismiss}
|
||||
list={importToken instanceof WrappedTokenInfo ? importToken.list : undefined}
|
||||
onBack={handleBackImport}
|
||||
handleCurrencySelect={handleCurrencySelect}
|
||||
/>
|
||||
)
|
||||
modalHeight = undefined
|
||||
if (tokenSafetyFlag === TokenSafetyVariant.Enabled) {
|
||||
showTokenSafetySpeedbump(importToken)
|
||||
}
|
||||
content = (
|
||||
<ImportToken
|
||||
tokens={[importToken]}
|
||||
onDismiss={onDismiss}
|
||||
list={importToken instanceof WrappedTokenInfo ? importToken.list : undefined}
|
||||
onBack={handleBackImport}
|
||||
handleCurrencySelect={handleCurrencySelect}
|
||||
/>
|
||||
)
|
||||
}
|
||||
break
|
||||
case CurrencyModalView.importList:
|
||||
minHeight = 40
|
||||
modalHeight = 40
|
||||
if (importList && listURL) {
|
||||
content = <ImportList list={importList} listURL={listURL} onDismiss={onDismiss} setModalView={setModalView} />
|
||||
}
|
||||
@@ -138,8 +173,8 @@ export default function CurrencySearchModal({
|
||||
break
|
||||
}
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={80} minHeight={minHeight}>
|
||||
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={modalHeight} minHeight={modalHeight}>
|
||||
{content}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,14 +8,13 @@ import ListLogo from 'components/ListLogo'
|
||||
import { AutoRow, RowBetween, RowFixed } from 'components/Row'
|
||||
import { SectionBreak } from 'components/swap/styleds'
|
||||
import { useFetchListCallback } from 'hooks/useFetchListCallback'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { transparentize } from 'polished'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { AlertTriangle, ArrowLeft } from 'react-feather'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { enableList, removeList } from 'state/lists/actions'
|
||||
import { useAllLists } from 'state/lists/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { CloseIcon, ThemedText } from 'theme'
|
||||
|
||||
import { ExternalLink } from '../../theme'
|
||||
|
||||
@@ -6,10 +6,9 @@ import CurrencyLogo from 'components/CurrencyLogo'
|
||||
import ListLogo from 'components/ListLogo'
|
||||
import { AutoRow, RowFixed } from 'components/Row'
|
||||
import { useIsTokenActive, useIsUserAddedToken } from 'hooks/Tokens'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { CSSProperties } from 'react'
|
||||
import { CheckCircle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
|
||||
|
||||
@@ -8,10 +8,9 @@ import { AutoColumn } from 'components/Column'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { SectionBreak } from 'components/swap/styleds'
|
||||
import { useUnsupportedTokens } from 'hooks/Tokens'
|
||||
import useTheme from 'hooks/useTheme'
|
||||
import { AlertCircle, ArrowLeft } from 'react-feather'
|
||||
import { useAddUserToken } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { CloseIcon, ThemedText } from 'theme'
|
||||
|
||||
import BlockedToken from './BlockedToken'
|
||||
|
||||
@@ -12,11 +12,10 @@ import { ChangeEvent, memo, useCallback, useEffect, useMemo, useRef, useState }
|
||||
import { CheckCircle, Settings } from 'react-feather'
|
||||
import { usePopper } from 'react-popper'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { useFetchListCallback } from '../../hooks/useFetchListCallback'
|
||||
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import useToggle from '../../hooks/useToggle'
|
||||
import { acceptListUpdate, disableList, enableList, removeList } from '../../state/lists/actions'
|
||||
import { useActiveListUrls, useAllLists, useIsListActive } from '../../state/lists/hooks'
|
||||
|
||||
@@ -8,11 +8,10 @@ import Row, { RowBetween, RowFixed } from 'components/Row'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { ChangeEvent, RefObject, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { useRemoveUserAddedToken, useUserAddedTokens } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ButtonText, ExternalLink, ExternalLinkIcon, ThemedText, TrashIcon } from 'theme'
|
||||
import { isAddress } from 'utils'
|
||||
|
||||
import useTheme from '../../hooks/useTheme'
|
||||
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
|
||||
import { CurrencyModalView } from './CurrencySearchModal'
|
||||
import ImportRow from './ImportRow'
|
||||
|
||||