diff --git a/.env b/.env index fcf4b9f4cd..0b93647203 100644 --- a/.env +++ b/.env @@ -72,3 +72,6 @@ REACT_APP_PATH_REGEX_ENS="/ipfs" # Enables mock mode (default = true) REACT_APP_MOCK=true + +# Locales +REACT_APP_LOCALES="locales" \ No newline at end of file diff --git a/.env.production b/.env.production index 777ebee0ac..9ee1d04bcd 100644 --- a/.env.production +++ b/.env.production @@ -73,3 +73,6 @@ REACT_APP_PATH_REGEX_ENS="/ipfs" # Enables mock mode (default = false) REACT_APP_MOCK=false + +# Locales +REACT_APP_LOCALES="locales" \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index d3f6e1448a..2fada84279 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,21 +8,50 @@ "jsx": true } }, - "ignorePatterns": ["node_modules/**/*", ".github/*", "src/components/RateToggle/index.tsx"], "settings": { "react": { "version": "detect" } }, + "ignorePatterns": [ + "src/lib/", + "__fixtures__", + ".github/*", + "src/components/RateToggle/index.tsx", + "src/types/v3", + "src/abis/types", + "src/locales/**/*.js", + "src/locales/**/en-US.po", + "src/state/data/generated.ts", + "node_modules", + "coverage", + "build", + "dist", + ".DS_Store", + ".env.local", + ".env.development.local", + ".env.test.local", + ".env.production.local", + ".idea/", + ".vscode/", + "package-lock.json", + "yarn.lock" + ], "extends": [ + "react-app", "plugin:react/recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended", "prettier/@typescript-eslint", + "plugin:cypress/recommended", "plugin:prettier/recommended", "plugin:jsx-a11y/recommended" ], + "plugins": ["simple-import-sort", "unused-imports"], "rules": { + "unused-imports/no-unused-imports": "error", + // "simple-import-sort/imports": "error", + // "simple-import-sort/exports": "error", "@typescript-eslint/explicit-function-return-type": "off", "prettier/prettier": "error", "@typescript-eslint/no-explicit-any": "off", @@ -35,10 +64,6 @@ "error", { "paths": [ - { - "name": "lodash", - "message": "Please import from 'lodash/module' directly to support tree-shaking." - }, { "name": "ethers", "message": "Please import from '@ethersproject/module' directly to support tree-shaking." diff --git a/.github/uniswap-original/workflows/integration-tests.yaml b/.github/uniswap-original/workflows/integration-tests.yaml index 5a02fdb51e..18cd058e8a 100644 --- a/.github/uniswap-original/workflows/integration-tests.yaml +++ b/.github/uniswap-original/workflows/integration-tests.yaml @@ -11,7 +11,7 @@ on: jobs: integration-tests: name: Cypress - runs-on: ubuntu-16.04 + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 @@ -38,15 +38,14 @@ jobs: run: yarn install --frozen-lockfile - run: yarn cypress install + - run: yarn build env: CI: false - REACT_APP_NETWORK_URL: "https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847" + REACT_APP_NETWORK_URL: 'https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847' REACT_APP_SERVICE_WORKER: false - run: yarn integration-test env: CYPRESS_INTEGRATION_TEST_PRIVATE_KEY: ${{ secrets.CYPRESS_INTEGRATION_TEST_PRIVATE_KEY }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - - diff --git a/.github/uniswap-original/workflows/lint.yml b/.github/uniswap-original/workflows/lint.yml index 5249124008..930da1f53c 100644 --- a/.github/uniswap-original/workflows/lint.yml +++ b/.github/uniswap-original/workflows/lint.yml @@ -3,15 +3,14 @@ name: Lint on: push: branches: - - master - pull_request_target: + - main + pull_request: branches: - - master + - main jobs: run-linters: name: Run linters - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.repository_owner }} runs-on: ubuntu-latest steps: @@ -35,14 +34,18 @@ jobs: key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn- - - name: Install dependencies run: yarn install --frozen-lockfile - - name: Run linters - uses: wearerequired/lint-action@b98b0918aa71490373d2eca9e8e39a9bc1cc2517 + - name: Run eslint w/ autofix + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.repository_owner }} + uses: wearerequired/lint-action@36c7e6689e80d785d27a22f71d970f3a3b4fcb70 with: github_token: ${{ secrets.github_token }} eslint: true - eslint_extensions: js,jsx,ts,tsx,json + eslint_args: "-c .eslintrc.json" auto_fix: true + + - name: Run eslint + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.owner.login != github.repository_owner }} + run: yarn eslint . \ No newline at end of file diff --git a/.github/workflows/bundle.yaml b/.github/workflows/bundle.yaml new file mode 100644 index 0000000000..1f99c34841 --- /dev/null +++ b/.github/workflows/bundle.yaml @@ -0,0 +1,40 @@ +name: Widgets +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up node + uses: actions/setup-node@v2 + with: + node-version: 14 + registry-url: https://registry.npmjs.org + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v2 + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Build + run: yarn widgets:build \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3bd915954..812cbfc362 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,10 +145,15 @@ jobs: - name: Run linters uses: wearerequired/lint-action@v1 with: - eslint: true - prettier: true github_token: ${{ secrets.github_token }} + eslint: true + eslint_args: "-c .eslintrc.json ." auto_fix: ${{ github.event_name == 'pull_request' }} + prettier: true + + - name: Run eslint + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.owner.login != github.repository_owner }} + run: yarn eslint . build: name: Build apps diff --git a/.gitignore b/.gitignore index e62a5052cf..64a26db1e6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,14 @@ # generated contract types /src/types/v3 /src/abis/types +/src/lib/locales/**/*.js +/src/lib/locales/**/en-US.po +/src/lib/locales/**/pseudo.po /src/locales/**/*.js /src/locales/**/*.ts /src/locales/**/*.json /src/locales/**/en-US.po +/src/locales/**/pseudo.po /src/state/data/generated.ts # dependencies @@ -15,11 +19,10 @@ # testing /coverage -# production +# builds /build - -# bundle /dist +/dts # misc .DS_Store diff --git a/babel-plugin-macros.config.js b/babel-plugin-macros.config.cjs similarity index 100% rename from babel-plugin-macros.config.js rename to babel-plugin-macros.config.cjs diff --git a/cosmos.config.json b/cosmos.config.json index 7608c2d445..c78607b74d 100644 --- a/cosmos.config.json +++ b/cosmos.config.json @@ -1,7 +1,8 @@ { - "staticPath": "public", "watchDirs": ["src"], "webpack": { - "configPath": "./cosmos.webpack.config" - } + "configPath": "react-scripts/config/webpack.config", + "overridePath": "cosmos.override.js" + }, + "port": 5001 } diff --git a/cosmos.override.js b/cosmos.override.js new file mode 100644 index 0000000000..10132a4903 --- /dev/null +++ b/cosmos.override.js @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const HtmlWebpackPlugin = require('html-webpack-plugin') +const { DefinePlugin } = require('webpack') + +// Renders the cosmos fixtures in isolation, instead of using public/index.html. +module.exports = (webpackConfig) => ({ + ...webpackConfig, + plugins: webpackConfig.plugins.map((plugin) => { + if (plugin instanceof HtmlWebpackPlugin) { + return new HtmlWebpackPlugin({ + templateContent: '', + }) + } + if (plugin instanceof DefinePlugin) { + return new DefinePlugin({ + ...plugin.definitions, + 'process.env': { + ...plugin.definitions['process.env'], + REACT_APP_IS_WIDGET: true, + REACT_APP_LOCALES: '"../locales"', + }, + }) + } + return plugin + }), +}) diff --git a/cypress-custom/integration/swapMod.ts b/cypress-custom/integration/swapMod.ts index 0191bf1d5f..e29956d00f 100644 --- a/cypress-custom/integration/swapMod.ts +++ b/cypress-custom/integration/swapMod.ts @@ -2,35 +2,47 @@ describe('Swap (mod)', () => { beforeEach(() => { cy.visit('/swap') }) + + it('starts with an Native/USDC swap and quotes it', () => { + cy.get('#swap-currency-input .token-amount-input').should('have.value', '1') + cy.get('#swap-currency-input .token-symbol-container').should('contain.text', 'WETH') + cy.get('#swap-currency-output .token-amount-input').should('not.have.value', '') + cy.get('#swap-currency-output .token-symbol-container').should('contain.text', 'USDC') + }) + it('can enter an amount into input', () => { cy.get('#swap-currency-input .token-amount-input') + .clear() .type('0.001', { delay: 400, force: true }) .should('have.value', '0.001') }) it('zero swap amount', () => { cy.get('#swap-currency-input .token-amount-input') + .clear() .type('0.0', { delay: 400, force: true }) .should('have.value', '0.0') }) it('invalid swap amount', () => { - cy.get('#swap-currency-input .token-amount-input').type('\\', { delay: 400, force: true }).should('have.value', '') + cy.get('#swap-currency-input .token-amount-input') + .clear() + .type('\\', { delay: 400, force: true }) + .should('have.value', '') }) - it.skip('can enter an amount into output', () => { + it('can enter an amount into output', () => { cy.get('#swap-currency-output .token-amount-input') + .clear() .type('0.001', { delay: 400, force: true }) .should('have.value', '0.001') }) it('zero output amount', () => { - cy.get('#swap-currency-output .token-amount-input') - .type('0.0', { delay: 400, force: true }) - .should('have.value', '0.0') + cy.get('#swap-currency-output .token-amount-input').clear().type('0.0', { delay: 400 }).should('have.value', '0.0') }) - it.skip('can swap ETH for DAI', () => { + it('can swap Native for DAI', () => { cy.get('#swap-currency-output .open-currency-select-button').click() cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').should('be.visible') cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').click({ force: true }) @@ -55,16 +67,16 @@ describe('Swap (mod)', () => { cy.get('#confirm-expert-mode').click() }) - it.skip('add a recipient is visible', () => { + it('add a recipient is visible', () => { cy.get('#add-recipient-button').should('be.visible') }) - it.skip('add a recipient', () => { + it('add a recipient', () => { cy.get('#add-recipient-button').click() cy.get('#recipient').should('exist') }) - it.skip('remove recipient', () => { + it('remove recipient', () => { cy.get('#add-recipient-button').click() cy.get('#remove-recipient-button').click() cy.get('#recipient').should('not.exist') diff --git a/cypress-custom/support/commands.d.ts b/cypress-custom/support/commands.d.ts index 50e1d7c2d6..a8805a9ab7 100644 --- a/cypress-custom/support/commands.d.ts +++ b/cypress-custom/support/commands.d.ts @@ -1,4 +1,4 @@ -/// +/// declare namespace Cypress { interface Chainable { diff --git a/cypress/fixtures/feeTierDistribution.json b/cypress/fixtures/feeTierDistribution.json index 20d8f28a77..4a01d75b84 100644 --- a/cypress/fixtures/feeTierDistribution.json +++ b/cypress/fixtures/feeTierDistribution.json @@ -5,6 +5,11 @@ } }, "asToken0": [ + { + "feeTier": "100", + "totalValueLockedToken0": "0", + "totalValueLockedToken1": "3" + }, { "feeTier": "500", "totalValueLockedToken0": "0", @@ -13,7 +18,7 @@ { "feeTier": "3000", "totalValueLockedToken0": "0", - "totalValueLockedToken1": "7" + "totalValueLockedToken1": "4" }, { "feeTier": "10000", diff --git a/cypress/integration/add-liquidity.test.ts b/cypress/integration/add-liquidity.test.ts index 0266739f6d..510f507af0 100644 --- a/cypress/integration/add-liquidity.test.ts +++ b/cypress/integration/add-liquidity.test.ts @@ -57,7 +57,7 @@ describe('Add Liquidity', () => { cy.wait('@feeTierDistributionQuery') cy.get('#add-liquidity-selected-fee .selected-fee-label').should('contain.text', '0.3% fee tier') - cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '70%') + cy.get('#add-liquidity-selected-fee .selected-fee-percentage').should('contain.text', '40%') }) }) }) diff --git a/cypress/integration/swap.test.ts b/cypress/integration/swap.test.ts index df4dfba437..6c223c0f2b 100644 --- a/cypress/integration/swap.test.ts +++ b/cypress/integration/swap.test.ts @@ -2,23 +2,34 @@ describe('Swap', () => { beforeEach(() => { cy.visit('/swap') }) - it('can enter an amount into input', () => { - cy.get('#swap-currency-input .token-amount-input').type('0.001', { delay: 200 }).should('have.value', '0.001') + + it.skip('starts with an ETH/USDC swap and quotes it', () => { + cy.get('#swap-currency-input .token-amount-input').should('have.value', '1') + cy.get('#swap-currency-input .token-symbol-container').should('contain.text', 'ETH') + cy.get('#swap-currency-output .token-amount-input').should('not.have.value', '') + cy.get('#swap-currency-output .token-symbol-container').should('contain.text', 'USDC') + }) + + it.skip('can enter an amount into input', () => { + cy.get('#swap-currency-input .token-amount-input') + .clear() + .type('0.001', { delay: 200 }) + .should('have.value', '0.001') }) it.skip('zero swap amount', () => { - cy.get('#swap-currency-input .token-amount-input').type('0.0', { delay: 200 }).should('have.value', '0.0') + cy.get('#swap-currency-input .token-amount-input').clear().type('0.0', { delay: 200 }).should('have.value', '0.0') }) - it('invalid swap amount', () => { - cy.get('#swap-currency-input .token-amount-input').type('\\', { delay: 200 }).should('have.value', '') + it.skip('invalid swap amount', () => { + cy.get('#swap-currency-input .token-amount-input').clear().type('\\', { delay: 200 }).should('have.value', '') }) it.skip('can enter an amount into output', () => { cy.get('#swap-currency-output .token-amount-input').type('0.001', { delay: 200 }).should('have.value', '0.001') }) - it('zero output amount', () => { + it.skip('zero output amount', () => { cy.get('#swap-currency-output .token-amount-input').type('0.0', { delay: 200 }).should('have.value', '0.0') }) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 3957854dc7..9d0d0b21ec 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -75,7 +75,7 @@ class CustomizedBridge extends Eip1193Bridge { // If from is present on eth_sendTransaction it errors, removing it makes the library set // from as the connected wallet which works fine delete params[0].from - const req = ethers.providers.JsonRpcProvider.hexlifyTransaction(params[0]) + const req = JsonRpcProvider.hexlifyTransaction(params[0]) // Hexlify sets the gasLimit property to be gas again and send transaction requires gasLimit req.gasLimit = req.gas delete req.gas @@ -104,6 +104,7 @@ class CustomizedBridge extends Eip1193Bridge { } // sets up the injected provider to be a mock ethereum provider with the given mnemonic/index +// eslint-disable-next-line no-undef Cypress.Commands.overwrite('visit', (original, url, options) => { return original(url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url, { ...options, diff --git a/lingui.config.ts b/lingui.config.ts index 3aea3ad5e7..8bc943c737 100644 --- a/lingui.config.ts +++ b/lingui.config.ts @@ -1,4 +1,4 @@ -export default { +const linguiConfig = { catalogs: [ { path: '/src/locales/{locale}', @@ -14,40 +14,45 @@ export default { lineNumbers: false, }, locales: [ - // 'af-ZA', - // 'ar-SA', - // 'ca-ES', - // 'cs-CZ', - // 'da-DK', - // 'de-DE', - // 'el-GR', + /* 'af-ZA', + 'ar-SA', + 'ca-ES', + 'cs-CZ', + 'da-DK', + 'de-DE', + 'el-GR', */ 'en-US', - // 'es-ES', - // 'fi-FI', - // 'fr-FR', - // 'he-IL', - // 'hu-HU', - // 'id-ID', - // 'it-IT', - // 'ja-JP', - // 'ko-KR', - // 'nl-NL', - // 'no-NO', - // 'pl-PL', - // 'pt-BR', - // 'pt-PT', - // 'ro-RO', - // 'ru-RU', - // 'sr-SP', - // 'sv-SE', - // 'tr-TR', - // 'uk-UA', - // 'vi-VN', - // 'zh-CN', - // 'zh-TW', + /* 'es-ES', + 'fi-FI', + 'fr-FR', + 'he-IL', + 'hu-HU', + 'id-ID', + 'it-IT', + 'ja-JP', + 'ko-KR', + 'nl-NL', + 'no-NO', + 'pl-PL', + 'pt-BR', + 'pt-PT', + 'ro-RO', + 'ru-RU', + 'sr-SP', + 'sv-SE', + 'sw-TZ', + 'tr-TR', + 'uk-UA', + 'vi-VN', + 'zh-CN', + 'zh-TW', */ + 'pseudo', ], orderBy: 'messageId', rootDir: '.', runtimeConfigModule: ['@lingui/core', 'i18n'], sourceLocale: 'en-US', + pseudoLocale: 'pseudo', } + +export default linguiConfig diff --git a/package.json b/package.json index 7be233923b..48cf7f01ca 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,22 @@ "name": "@gnosis/cowswap", "description": "CowSwap - CoW Protocol", "homepage": ".", - "main": "dist/interface.js", - "module": "dist/interface.esm.js", - "types": "dist/index.d.ts", "files": [ "lib", "dist" ], + "exports": { + ".": { + "types": "./dist/widgets.d.ts", + "import": "./dist/widgets.cjs", + "require": "./dist/widgets.cjs" + }, + "./fonts.css": { + "import": "./dist/fonts.css", + "require": "./dist/fonts.css" + } + }, + "types": "./dist/widgets.d.ts", "private": true, "version": "1.13.1", "engines": { @@ -16,26 +25,27 @@ }, "devDependencies": { "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", + "@babel/plugin-transform-runtime": "^7.17.0", + "@babel/preset-env": "^7.16.11", + "@babel/preset-react": "^7.16.7", + "@babel/preset-typescript": "^7.16.7", "@craco/craco": "^5.7.0", - "@davatar/react": "1.8.1", - "@ethersproject/experimental": "^5.4.0", - "@gnosis.pm/safe-apps-web3-react": "^0.6.0", "@graphql-codegen/cli": "1.21.5", "@graphql-codegen/typescript": "1.22.3", "@graphql-codegen/typescript-operations": "^1.18.2", "@graphql-codegen/typescript-rtk-query": "^1.1.1", - "@lingui/cli": "^3.9.0", - "@lingui/loader": "^3.9.0", - "@lingui/macro": "^3.9.0", - "@lingui/react": "^3.9.0", - "@popperjs/core": "^2.4.4", - "@reach/dialog": "^0.10.3", - "@reach/portal": "^0.10.3", - "@react-hook/window-scroll": "^1.3.0", - "@reduxjs/toolkit": "^1.6.1", - "@sentry/webpack-plugin": "^1.17.1", + "@rollup/plugin-alias": "^3.1.9", + "@rollup/plugin-babel": "^5.3.0", + "@rollup/plugin-commonjs": "^21.0.1", + "@rollup/plugin-eslint": "^8.0.1", + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^13.1.3", + "@rollup/plugin-replace": "^3.0.1", + "@rollup/plugin-typescript": "^8.3.0", + "@rollup/plugin-url": "^6.1.0", "@simbathesailor/babel-plugin-use-what-changed": "^2.1.0", "@simbathesailor/use-what-changed": "^2.0.0", + "@svgr/rollup": "^6.2.0", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.0.0", "@testing-library/react-hooks": "^7.0.2", @@ -47,8 +57,6 @@ "@types/lingui__core": "^2.7.1", "@types/lingui__macro": "^2.7.4", "@types/lingui__react": "^2.8.3", - "@types/lodash.flatmap": "^4.5.6", - "@types/luxon": "^1.24.4", "@types/ms.macro": "^2.0.0", "@types/multicodec": "^1.0.0", "@types/node": "^13.13.5", @@ -67,92 +75,48 @@ "@types/wcag-contrast": "^3.0.0", "@typescript-eslint/eslint-plugin": "^4.1.0", "@typescript-eslint/parser": "^4.1.0", - "@uniswap/default-token-list": "^2.0.0", - "@uniswap/governance": "^1.0.2", - "@uniswap/liquidity-staker": "^1.0.2", - "@uniswap/merkle-distributor": "1.0.1", - "@uniswap/sdk": "npm:@anxolin/uniswap-sdk#3.0.3-rc.3", - "@uniswap/sdk-core": "^3.0.1", - "@uniswap/token-lists": "^1.0.0-beta.27", - "@uniswap/v2-core": "1.0.0", - "@uniswap/v2-periphery": "^1.1.0-beta.0", - "@uniswap/v2-sdk": "^3.0.0-alpha.2", - "@uniswap/v3-core": "1.0.0", - "@uniswap/v3-periphery": "^1.1.1", - "@uniswap/v3-sdk": "^3.4.1", - "@web3-react/core": "^6.1.9", - "@web3-react/fortmatic-connector": "^6.1.6", - "@web3-react/injected-connector": "^6.0.7", - "@web3-react/portis-connector": "^6.1.9", - "@web3-react/walletconnect-connector": "6.2.4", - "@web3-react/walletlink-connector": "^6.2.12", - "ajv": "^6.12.3", "array.prototype.flat": "^1.2.4", "array.prototype.flatmap": "^1.2.4", - "cids": "^1.0.0", - "copy-to-clipboard": "^3.2.0", + "babel-plugin-macros": "^3.1.0", "cross-env": "^7.0.3", "cypress": "^7.7.0", "d3": "^7.0.0", - "eslint": "^7.11.0", "eslint-config-prettier": "^6.11.0", + "eslint-plugin-better-styled-components": "^1.1.2", + "eslint-plugin-cypress": "^2.12.1", "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-react": "^7.19.0", "eslint-plugin-react-hooks": "^4.0.0", "eslint-plugin-simple-import-sort": "^7.0.0", - "ethers": "^5.4.6", + "eslint-plugin-unused-imports": "^2.0.0", "graphql": "^15.5.0", "graphql-request": "^3.4.0", "inter-ui": "^3.13.1", - "ipfs-deploy": "^8.0.1", - "jazzicon": "^1.5.0", "jest-styled-components": "^7.0.5", - "lightweight-charts": "^3.3.0", - "lodash.flatmap": "^4.5.0", - "luxon": "^1.25.0", "microbundle": "^0.13.3", - "ms.macro": "^2.0.0", - "multicodec": "^3.0.1", - "multihashes": "^4.0.2", - "node-vibrant": "^3.2.1-alpha.1", - "polished": "^3.3.2", "polyfill-object.fromentries": "^1.0.1", "prettier": "^2.2.1", - "qs": "^6.9.4", - "react": "^17.0.1", - "react-confetti": "^6.0.0", - "react-cosmos": "^5.6.3", - "react-device-detect": "^1.6.2", - "react-dom": "^17.0.1", - "react-feather": "^2.0.8", - "react-ga": "^2.5.7", - "react-i18next": "^10.7.0", - "react-is": "^17.0.2", - "react-markdown": "^5.0.3", - "react-popper": "^2.2.3", - "react-redux": "^7.2.2", - "react-router-dom": "^5.0.0", - "react-scripts": "^4.0.3", - "react-spring": "^8.0.27", - "react-use-gesture": "^6.0.14", - "react-virtualized-auto-sizer": "^1.0.2", - "react-window": "^1.8.5", - "rebass": "^4.0.7", - "redux-localstorage-simple": "^2.3.1", + "rollup": "^2.63.0", + "rollup-plugin-copy": "^3.4.0", + "rollup-plugin-delete": "^2.0.0", + "rollup-plugin-dts": "^4.1.0", + "rollup-plugin-node-externals": "^3.1.2", + "rollup-plugin-scss": "^3.0.0", + "rollup-plugin-typescript2": "^0.31.1", + "sass": "^1.45.1", "serve": "^11.3.2", "start-server-and-test": "^1.11.0", - "styled-components": "^5.3.0", - "styled-system": "^5.1.5", - "swr": "^1.0.1", + "text-encoding-polyfill": "^0.6.7", "typechain": "^5.0.0", - "typescript": "^4.4.2", - "ua-parser-js": "^0.7.28", - "use-count-up": "^2.2.5", - "wcag-contrast": "^3.0.0", - "web-vitals": "^2.1.0", - "web3": "^1.7.0", - "web3-providers-http": "^1.7.0", - "web3-providers-ws": "^1.7.0", + "typescript": "^4.4.3", + "web3-react-abstract-connector": "npm:@web3-react/abstract-connector@^6.0.7", + "web3-react-core": "npm:@web3-react/core@^6.0.9", + "web3-react-fortmatic-connector": "npm:@web3-react/fortmatic-connector@^6.0.9", + "web3-react-injected-connector": "npm:@web3-react/injected-connector@^6.0.7", + "web3-react-portis-connector": "npm:@web3-react/portis-connector@^6.0.9", + "web3-react-types": "npm:@web3-react/types@^6.0.7", + "web3-react-walletconnect-connector": "npm:@web3-react/walletconnect-connector@^7.0.2-alpha.0", + "web3-react-walletlink-connector": "npm:@web3-react/walletlink-connector@^6.2.13", "webpack-bundle-analyzer": "^4.5.0", "workbox-core": "^6.1.0", "workbox-precaching": "^6.1.0", @@ -160,7 +124,7 @@ "yarn-audit-fix": "^9.2.1" }, "resolutions": { - "@walletconnect/ethereum-provider": "1.6.4", + "@walletconnect/ethereum-provider": "1.7.1", "react-error-overlay": "6.0.9" }, "scripts": { @@ -182,16 +146,21 @@ "cypress": "cypress open", "contracts:add:export": "node ./scripts/contracts_add_export.js", "contracts:compile:abi": "typechain --target ethers-v5 --out-dir src/custom/abis/types './src/custom/abis/**/*.json'", - "contracts:compile:abi-uniswap": "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/?(v3-core|v3-periphery)/artifacts/contracts/**/*.json'", + "contracts:compile:abi-uniswap": "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:add:export && yarn contracts:compile:abi-uniswap && yarn contracts:compile:v3", "graphql:generate": "graphql-codegen --config codegen.yml", + "prei18n:extract": "touch src/locales/en-US.po", "postinstall": "yarn contracts:compile && yarn graphql:generate && yarn i18n:compile", "i18n:extract": "lingui extract --locale en-US", "i18n:compile": "yarn i18n:extract && lingui compile", - "prei18n:extract": "touch src/locales/en-US.po", + "i18n:pseudo": "lingui extract --locale pseudo && lingui compile", + "prepare": "yarn contracts:compile && yarn graphql:generate && yarn i18n:compile", + "prepublishOnly": "yarn widgets:build", + "test:e2e": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run --record'", + "widgets:start": "cosmos", + "widgets:build": "rollup --config --failAfterWarnings --configPlugin typescript2", "bundle": "microbundle --tsconfig tsconfig.lib.json src/lib/index.tsx --format esm,cjs", - "cosmos": "cross-env FAST_REFRESH=false cosmos", "sitemap": "node src/custom/sitemap", "writeVersion": "node src/custom/writeVersion" }, @@ -220,24 +189,144 @@ }, "license": "GPL-3.0-or-later", "dependencies": { + "@babel/runtime": "^7.17.0", + "@davatar/react": "1.8.1", + "@ethersproject/abi": "^5.4.1", + "@ethersproject/abstract-provider": "^5.4.1", + "@ethersproject/abstract-signer": "^5.4.1", + "@ethersproject/address": "^5.4.0", + "@ethersproject/bignumber": "^5.4.2", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/contracts": "^5.4.1", + "@ethersproject/experimental": "^5.4.0", + "@ethersproject/hash": "^5.4.0", + "@ethersproject/providers": "^5.4.0", + "@ethersproject/solidity": "^5.4.0", + "@ethersproject/strings": "^5.4.0", + "@ethersproject/units": "^5.4.0", + "@ethersproject/wallet": "^5.4.0", + "@fontsource/ibm-plex-mono": "^4.5.1", + "@fontsource/inter": "^4.5.1", "@gnosis.pm/cow-runner-game": "^0.2.9", "@gnosis.pm/dex-js": "^0.14.0", "@gnosis.pm/gp-v2-contracts": "^1.1.2", + "@gnosis.pm/safe-apps-web3-react": "^0.6.0", "@gnosis.pm/safe-service-client": "^0.1.1", + "@lingui/cli": "^3.9.0", + "@lingui/core": "^3.13.2", + "@lingui/loader": "^3.13.2", + "@lingui/macro": "^3.13.2", + "@lingui/react": "^3.13.2", + "@metamask/jazzicon": "^2.0.0", + "@popperjs/core": "^2.4.4", + "@reach/dialog": "^0.10.3", + "@reach/portal": "^0.10.3", + "@react-hook/window-scroll": "^1.3.0", + "@reduxjs/toolkit": "^1.6.1", "@sentry/react": "^6.11.0", "@sentry/tracing": "^6.11.0", - "@uniswap/default-token-list": "^2.0.0", + "@sentry/webpack-plugin": "^1.17.1", + "@uniswap/default-token-list": "^3.0.0", + "@uniswap/governance": "^1.0.2", + "@uniswap/liquidity-staker": "^1.0.2", + "@uniswap/merkle-distributor": "1.0.1", + "@uniswap/redux-multicall": "^1.0.0", + "@uniswap/router-sdk": "^1.0.3", + "@uniswap/sdk": "npm:@anxolin/uniswap-sdk#3.0.3-rc.3", + "@uniswap/sdk-core": "^3.0.1", + "@uniswap/smart-order-router": "^2.5.10", + "@uniswap/token-lists": "^1.0.0-beta.27", + "@uniswap/v2-core": "1.0.0", + "@uniswap/v2-periphery": "^1.1.0-beta.0", + "@uniswap/v2-sdk": "^3.0.1", + "@uniswap/v3-core": "1.0.0", + "@uniswap/v3-periphery": "^1.1.1", + "@uniswap/v3-sdk": "^3.8.2", "@walletconnect/web3-provider": "^1.6.6", + "@web3-react/core": "8.0.14-beta.0", + "@web3-react/eip1193": "8.0.9-beta.0", + "@web3-react/empty": "8.0.7-beta.0", + "@web3-react/fortmatic-connector": "^6.1.6", + "@web3-react/injected-connector": "^6.0.7", + "@web3-react/metamask": "8.0.10-beta.0", + "@web3-react/portis-connector": "^6.1.9", + "@web3-react/types": "8.0.7-beta.0", + "@web3-react/url": "8.0.9-beta.0", + "@web3-react/walletconnect": "8.0.15-beta.0", + "@web3-react/walletconnect-connector": "6.2.4", + "@web3-react/walletlink-connector": "^6.2.5", + "ajv": "^6.12.3", "bnc-sdk": "^3.5.0", + "cids": "^1.0.0", + "copy-to-clipboard": "^3.2.0", + "ethers": "^5.4.6", "fast-safe-stringify": "^2.0.8", "firebase": "^9.1.3", "fortmatic": "^2.2.1", + "immer": "^9.0.6", + "ipfs-deploy": "^8.0.1", "ipfs-http-client": "^52.0.3", + "jazzicon": "^1.5.0", + "jotai": "^1.3.7", + "jsbi": "^3.1.4", + "lightweight-charts": "^3.3.0", + "make-plural": "^7.0.0", + "ms.macro": "^2.0.0", + "multicodec": "^3.0.1", + "multihashes": "^4.0.2", + "node-vibrant": "^3.2.1-alpha.1", "paraswap": "npm:@nenad91/paraswap#5.1.0", + "polished": "^3.3.2", + "popper-max-size-modifier": "^0.2.0", + "qs": "^6.9.4", + "react": "^17.0.1", "react-appzi": "^1.0.4", + "react-confetti": "^6.0.0", + "react-cosmos": "^5.6.6", + "react-device-detect": "^1.6.2", + "react-dom": "^17.0.1", + "react-feather": "^2.0.8", + "react-ga": "^2.5.7", + "react-i18next": "^10.7.0", "react-inlinesvg": "^2.3.0", + "react-markdown": "^5.0.3", + "react-popper": "^2.2.3", + "react-redux": "^7.2.2", + "react-router-dom": "^5.0.0", "react-router-hash-link": "^2.4.0", + "react-scripts": "^4.0.3", + "react-spring": "^8.0.27", + "react-use-gesture": "^6.0.14", + "react-virtualized-auto-sizer": "^1.0.2", + "react-window": "^1.8.5", + "rebass": "^4.0.7", + "redux": "^4.1.2", + "redux-localstorage-simple": "^2.3.1", + "setimmediate": "^1.0.5", "simple-sitemap-renderer": "^1.1.0", - "timeago.js": "^4.0.2" + "styled-components": "^5.3.0", + "swr": "^1.0.1", + "timeago.js": "^4.0.2", + "tiny-invariant": "^1.2.0", + "ua-parser-js": "^0.7.28", + "use-count-up": "^2.2.5", + "use-resize-observer": "^8.0.0", + "wcag-contrast": "^3.0.0", + "web-vitals": "^2.1.0", + "web3": "^1.7.0", + "wicg-inert": "^3.1.1" + }, + "peerDependencies": { + "@babel/runtime": "^7.17.0", + "react": "^17.0.1", + "react-dom": "^17.0.1", + "react-redux": "^7.2.2", + "redux": "^4.1.2" + }, + "optionalDependencies": { + "bufferutil": "^4.0.6", + "encoding": "^0.1.13", + "utf-8-validate": "^5.0.8" } } diff --git a/rollup.config.ts b/rollup.config.ts new file mode 100644 index 0000000000..233e3dea6d --- /dev/null +++ b/rollup.config.ts @@ -0,0 +1,122 @@ +/** + * Bundles the widgets library, which is released independently of the interface application. + * This library lives in src/lib, but shares code with the interface application. + */ + +import alias from '@rollup/plugin-alias' +import babel from '@rollup/plugin-babel' +import commonjs from '@rollup/plugin-commonjs' +import json from '@rollup/plugin-json' +import resolve from '@rollup/plugin-node-resolve' +import replace from '@rollup/plugin-replace' +import typescript from '@rollup/plugin-typescript' +import url from '@rollup/plugin-url' +import svgr from '@svgr/rollup' +import path from 'path' +import { RollupWarning } from 'rollup' +import copy from 'rollup-plugin-copy' +import del from 'rollup-plugin-delete' +import dts from 'rollup-plugin-dts' +import externals from 'rollup-plugin-node-externals' +import sass from 'rollup-plugin-scss' +import { CompilerOptions } from 'typescript' + +const REPLACEMENTS = { + 'process.env.REACT_APP_IS_WIDGET': true, + 'process.env.REACT_APP_LOCALES': '"./locales"', +} + +const EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx'] +const ASSET_EXTENSIONS = ['.png', '.svg'] +function isAsset(source: string) { + const extname = path.extname(source) + return extname && [...ASSET_EXTENSIONS, '.css', '.scss'].includes(extname) +} + +const TS_CONFIG = './tsconfig.lib.json' +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { baseUrl, paths }: CompilerOptions = require(TS_CONFIG).compilerOptions +const aliases = Object.entries({ ...paths }).flatMap(([find, replacements]) => { + return replacements.map((replacement) => ({ + find: path.dirname(find), + replacement: path.join(__dirname, baseUrl || '.', path.dirname(replacement)), + })) +}) + +const plugins = [ + // Dependency resolution + externals({ exclude: ['constants'], deps: true, peerDeps: true }), // marks builtins, dependencies, and peerDependencies external + resolve({ extensions: EXTENSIONS }), // resolves third-party modules within node_modules/ + alias({ entries: aliases }), // resolves paths aliased through the tsconfig (babel does not use tsconfig path resolution) + + // Source code transformation + replace({ ...REPLACEMENTS, preventAssignment: true }), + json(), // imports json as ES6; doing so enables type-checking and module resolution +] + +const check = { + input: 'src/lib/index.tsx', + output: { file: 'dist/widgets.tsc' }, + external: isAsset, + plugins: [...plugins, typescript({ tsconfig: TS_CONFIG })], + onwarn: squelchTranspilationWarnings, // this pipeline is only for typechecking and generating definitions +} + +const type = { + input: 'dist/dts/lib/index.d.ts', + output: { file: 'dist/widgets.d.ts' }, + external: isAsset, + plugins: [ + dts({ compilerOptions: { baseUrl: 'dist/dts' } }), + process.env.ROLLUP_WATCH ? undefined : del({ hook: 'buildEnd', targets: ['dist/widgets.tsc', 'dist/dts'] }), + ], +} + +const transpile = { + input: 'src/lib/index.tsx', + output: [ + { + file: 'dist/widgets.cjs', + format: 'cjs', + sourcemap: true, + }, + ], + plugins: [ + ...plugins, + + // Source code transformation + url({ include: ASSET_EXTENSIONS.map((extname) => '**/*' + extname), limit: Infinity }), // imports assets as data URIs + svgr({ exportType: 'named', svgo: false }), // imports svgs as React components + sass({ output: 'dist/fonts.css' }), // generates fonts.css + commonjs(), // transforms cjs dependencies into tree-shakeable ES modules + + babel({ + babelHelpers: 'runtime', + presets: ['@babel/preset-env', ['@babel/preset-react', { runtime: 'automatic' }], '@babel/preset-typescript'], + extensions: EXTENSIONS, + plugins: [ + 'macros', // enables @lingui and styled-components macros + '@babel/plugin-transform-runtime', // embeds the babel runtime for library distribution + ], + }), + + copy({ + copyOnce: true, + targets: [{ src: 'src/locales/*.js', dest: 'dist/locales' }], + }), + ], + onwarn: squelchTypeWarnings, // this pipeline is only for transpilation +} + +const config = [check, type, transpile] +export default config + +function squelchTranspilationWarnings(warning: RollupWarning, warn: (warning: RollupWarning) => void) { + if (warning.pluginCode === 'TS5055') return + warn(warning) +} + +function squelchTypeWarnings(warning: RollupWarning, warn: (warning: RollupWarning) => void) { + if (warning.code === 'UNUSED_EXTERNAL_IMPORT') return + warn(warning) +} diff --git a/src/abis/erc1155.json b/src/abis/erc1155.json new file mode 100644 index 0000000000..d14e0e18bc --- /dev/null +++ b/src/abis/erc1155.json @@ -0,0 +1,49 @@ +[ + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_id", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "_id", + "type": "uint256" + } + ], + "name": "uri", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abis/erc721.json b/src/abis/erc721.json new file mode 100644 index 0000000000..76339c6a0b --- /dev/null +++ b/src/abis/erc721.json @@ -0,0 +1,40 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/assets/images/gas-icon.svg b/src/assets/images/gas-icon.svg new file mode 100644 index 0000000000..38d202909f --- /dev/null +++ b/src/assets/images/gas-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/images/router-icon-grey.svg b/src/assets/images/router-icon-grey.svg new file mode 100644 index 0000000000..46a752b701 --- /dev/null +++ b/src/assets/images/router-icon-grey.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/images/santa-hat.png b/src/assets/images/santa-hat.png new file mode 100644 index 0000000000..0615c0dd36 Binary files /dev/null and b/src/assets/images/santa-hat.png differ diff --git a/src/assets/images/survey-orb.svg b/src/assets/images/survey-orb.svg new file mode 100644 index 0000000000..953202dc6a --- /dev/null +++ b/src/assets/images/survey-orb.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/svg/matic-token-icon.svg b/src/assets/svg/matic-token-icon.svg new file mode 100644 index 0000000000..aa6da8b3bb --- /dev/null +++ b/src/assets/svg/matic-token-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/polygon-matic-logo.svg b/src/assets/svg/polygon-matic-logo.svg new file mode 100644 index 0000000000..a5bb6124fc --- /dev/null +++ b/src/assets/svg/polygon-matic-logo.svg @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/src/components/AccountDetails/Transaction.tsx b/src/components/AccountDetails/Transaction.tsx index 4f1c4cdf8d..1e2b56848b 100644 --- a/src/components/AccountDetails/Transaction.tsx +++ b/src/components/AccountDetails/Transaction.tsx @@ -1,7 +1,7 @@ +import useActiveWeb3React from 'hooks/useActiveWeb3React' import { CheckCircle, Triangle } from 'react-feather' import styled from 'styled-components/macro' -import { useActiveWeb3React } from '../../hooks/web3' import { useAllTransactions } from '../../state/transactions/hooks' import { ExternalLink } from '../../theme' import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink' diff --git a/src/components/AccountDetails/TransactionSummary.tsx b/src/components/AccountDetails/TransactionSummary.tsx index 59d814e818..6c444c4eda 100644 --- a/src/components/AccountDetails/TransactionSummary.tsx +++ b/src/components/AccountDetails/TransactionSummary.tsx @@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro' import { Fraction, TradeType } from '@uniswap/sdk-core' import JSBI from 'jsbi' +import { nativeOnChain } from '../../constants/tokens' import { useCurrency, useToken } from '../../hooks/Tokens' import useENSName from '../../hooks/useENSName' import { VoteOption } from '../../state/governance/types' @@ -80,7 +81,7 @@ function ClaimSummary({ info: { recipient, uniAmountRaw } }: { info: ClaimTransa ) } -function SubmitProposalTransactionSummary({}: { info: SubmitProposalTransactionInfo }) { +function SubmitProposalTransactionSummary(_: { info: SubmitProposalTransactionInfo }) { return Submit new proposal } @@ -130,30 +131,45 @@ function DelegateSummary({ info: { delegatee } }: { info: DelegateTransactionInf return Delegate voting power to {ENSName ?? delegatee} } -function WrapSummary({ info: { currencyAmountRaw, unwrapped } }: { info: WrapTransactionInfo }) { +function WrapSummary({ info: { chainId, currencyAmountRaw, unwrapped } }: { info: WrapTransactionInfo }) { + const native = chainId ? nativeOnChain(chainId) : undefined + if (unwrapped) { return ( - Unwrap to - ETH + Unwrap{' '} + {' '} + to {native?.symbol ?? 'ETH'} ) } else { return ( - Wrap to WETH + Wrap{' '} + {' '} + to {native?.wrapped?.symbol ?? 'WETH'} ) } } -function DepositLiquidityStakingSummary({}: { info: DepositLiquidityStakingTransactionInfo }) { +function DepositLiquidityStakingSummary(_: { info: DepositLiquidityStakingTransactionInfo }) { // not worth rendering the tokens since you can should no longer deposit liquidity in the staking contracts // todo: deprecate and delete the code paths that allow this, show user more information return Deposit liquidity } -function WithdrawLiquidityStakingSummary({}: { info: WithdrawLiquidityStakingTransactionInfo }) { +function WithdrawLiquidityStakingSummary(_: { info: WithdrawLiquidityStakingTransactionInfo }) { return Withdraw deposited liquidity } diff --git a/src/components/AccountDetails/index.tsx b/src/components/AccountDetails/index.tsx index c4a393373f..e367793d5e 100644 --- a/src/components/AccountDetails/index.tsx +++ b/src/components/AccountDetails/index.tsx @@ -1,24 +1,22 @@ import { Trans } from '@lingui/macro' +import { Connector } from '@web3-react/types' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import Transaction from '@src/components/AccountDetails/Transaction' -import { fortmatic, injected, portis, walletconnect, walletlink } from 'connectors' +import { injected, portis, walletlink } from 'connectors' import { SUPPORTED_WALLETS } from 'constants/wallet' import { useCallback, useContext } from 'react' import { ExternalLink as LinkIcon } from 'react-feather' import { useAppDispatch } from 'state/hooks' import styled, { ThemeContext } from 'styled-components/macro' +import { AbstractConnector } from 'web3-react-abstract-connector' import { shortenAddress } from 'utils' import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink' -import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg' -import FortmaticIcon from '../../assets/images/fortmaticIcon.png' -import PortisIcon from '../../assets/images/portisIcon.png' -import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg' import { ReactComponent as Close } from '../../assets/images/x.svg' -import { useActiveWeb3React } from '../../hooks/web3' import { clearAllTransactions } from '../../state/transactions/actions' -import { ExternalLink, LinkStyledButton, TYPE } from '../../theme' +import { ExternalLink, LinkStyledButton, ThemedText } from '../../theme' import { ButtonSecondary } from '../Button' -import Identicon from '../Identicon' +import StatusIcon from '../Identicon/StatusIcon' import { AutoRow } from '../Row' import Copy from './Copy' @@ -178,6 +176,23 @@ const IconWrapper = styled.div<{ size?: number }>` `}; ` +function WrappedStatusIcon({ connector }: { connector: AbstractConnector | Connector }) { + return ( + + + {connector === portis && ( + { + portis.portis.showPortis() + }} + > + Show Portis + + )} + + ) +} + const TransactionListWrapper = styled.div` ${({ theme }) => theme.flexColumnNoWrap}; ` @@ -243,50 +258,6 @@ export default function AccountDetails({ ) } - function getStatusIcon() { - if (connector === injected) { - return ( - - - - ) - } else if (connector === walletconnect) { - return ( - - {'WalletConnect - - ) - } else if (connector === walletlink) { - return ( - - {'Coinbase - - ) - } else if (connector === fortmatic) { - return ( - - {'Fortmatic - - ) - } else if (connector === portis) { - return ( - <> - - {'Portis - { - portis.portis.showPortis() - }} - > - Show Portis - - - - ) - } - return null - } - const clearAllTransactionsCallback = useCallback(() => { if (chainId) dispatch(clearAllTransactions({ chainId })) }, [dispatch, chainId]) @@ -331,14 +302,14 @@ export default function AccountDetails({ {ENSName ? ( <>
- {getStatusIcon()} + {connector && }

{ENSName}

) : ( <>
- {getStatusIcon()} + {connector && }

{account && shortenAddress(account)}

@@ -407,9 +378,9 @@ export default function AccountDetails({ {!!pendingTransactions.length || !!confirmedTransactions.length ? ( - + Recent Transactions - + (clear all) @@ -419,9 +390,9 @@ export default function AccountDetails({ ) : ( - + Your transactions will appear here... - + )} diff --git a/src/components/AddressInputPanel/index.tsx b/src/components/AddressInputPanel/index.tsx index c749aff9ab..95432b6df0 100644 --- a/src/components/AddressInputPanel/index.tsx +++ b/src/components/AddressInputPanel/index.tsx @@ -1,11 +1,12 @@ +import { Trans } from '@lingui/macro' // eslint-disable-next-line no-restricted-imports -import { t, Trans } from '@lingui/macro' +import { t } from '@lingui/macro' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import { ReactNode, useCallback, useContext } from 'react' import styled, { ThemeContext } from 'styled-components/macro' import useENS from '../../hooks/useENS' -import { useActiveWeb3React } from '../../hooks/web3' -import { ExternalLink, TYPE } from 'theme' +import { ExternalLink, ThemedText } from 'theme' import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink' import { AutoColumn } from '../Column' import { RowBetween } from '../Row' @@ -107,9 +108,9 @@ export default function AddressInputPanel({ - + {label ?? Recipient} - + {address && chainId && ( ) { + const { ref, height } = useResizeObserver() + + const props = useSpring({ + height: open ? height ?? 0 : 0, + config: { + mass: 1.2, + tension: 300, + friction: 20, + clamp: true, + velocity: 0.01, + }, + }) + + return ( + +
{children}
+
+ ) +} diff --git a/src/components/Blocklist/index.tsx b/src/components/Blocklist/index.tsx index 2659f2d681..d0d353282a 100644 --- a/src/components/Blocklist/index.tsx +++ b/src/components/Blocklist/index.tsx @@ -1,8 +1,7 @@ import { Trans } from '@lingui/macro' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import { ReactNode, useMemo } from 'react' -import { useActiveWeb3React } from '../../hooks/web3' - // SDN OFAC addresses const BLOCKED_ADDRESSES: string[] = [ '0x7Db418b5D567A4e0E8c59Ad71BE1FcE48f3E6107', @@ -20,6 +19,20 @@ const BLOCKED_ADDRESSES: string[] = [ '0x8576aCC5C05D6Ce88f4e49bf65BdF0C62F91353C', '0xC8a65Fadf0e0dDAf421F28FEAb69Bf6E2E589963', '0x308eD4B7b49797e1A98D3818bFF6fe5385410370', + '0x67d40EE1A85bf4a4Bb7Ffae16De985e8427B', + '0x6f1ca141a28907f78ebaa64fb83a9088b02a83', + '0x6acdfba02d390b97ac2b2d42a63e85293bcc1', + '0x48549a34ae37b12f6a30566245176994e17c6', + '0x5512d943ed1f7c8a43f3435c85f7ab68b30121', + '0xc455f7fd3e0e12afd51fba5c106909934d8a0e', + '0x3cbded43efdaf0fc77b9c55f6fc9988fcc9b757d', + '0x67d40EE1A85bf4a4Bb7Ffae16De985e8427B6b45', + '0x6f1ca141a28907f78ebaa64fb83a9088b02a8352', + '0x6acdfba02d390b97ac2b2d42a63e85293bcc160e', + '0x48549a34ae37b12f6a30566245176994e17c6b4a', + '0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0', + '0xc455f7fd3e0e12afd51fba5c106909934d8a0e4a', + '0x629e7Da20197a5429d30da36E77d06CdF796b71A', ] export default function Blocklist({ children }: { children: ReactNode }) { diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 2ea8c795cb..b850da7200 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -23,7 +23,7 @@ export const BaseButton = styled(RebassButton)< border-radius: ${({ $borderRadius }) => $borderRadius ?? '20px'}; outline: none; border: 1px solid transparent; - color: white; + color: ${({ theme }) => theme.text1}; text-decoration: none; display: flex; justify-content: center; @@ -33,6 +33,7 @@ export const BaseButton = styled(RebassButton)< position: relative; z-index: 1; &:disabled { + opacity: 50%; cursor: auto; pointer-events: none; } @@ -235,7 +236,7 @@ const ButtonConfirmedStyle = styled(BaseButton)` /* border: 1px solid ${({ theme }) => theme.green1}; */ &:disabled { - /* opacity: 50%; */ + opacity: 50%; background-color: ${({ theme }) => theme.bg2}; color: ${({ theme }) => theme.text2}; cursor: auto; @@ -314,8 +315,8 @@ const ActiveOutlined = styled(ButtonOutlined)` ` const Circle = styled.div` - height: 20px; - width: 20px; + height: 17px; + width: 17px; border-radius: 50%; background-color: ${({ theme }) => theme.primary1}; display: flex; @@ -324,11 +325,11 @@ const Circle = styled.div` ` const CheckboxWrapper = styled.div` - width: 30px; + width: 20px; padding: 0 10px; position: absolute; - top: 10px; - right: 10px; + top: 11px; + right: 15px; ` const ResponsiveCheck = styled(Check)` diff --git a/src/components/CurrencyInputPanel/FiatValue.tsx b/src/components/CurrencyInputPanel/FiatValue.tsx index 2e63a21c7c..c09fa9c399 100644 --- a/src/components/CurrencyInputPanel/FiatValue.tsx +++ b/src/components/CurrencyInputPanel/FiatValue.tsx @@ -1,11 +1,14 @@ import { Trans } from '@lingui/macro' +// eslint-disable-next-line no-restricted-imports +import { t } from '@lingui/macro' import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core' import HoverInlineText from 'components/HoverInlineText' import { useMemo } from 'react' import useTheme from '../../hooks/useTheme' -import { TYPE } from '../../theme' +import { ThemedText } from '../../theme' import { warningSeverity } from '../../utils/prices' +import { MouseoverTooltip } from '../Tooltip' export function FiatValue({ fiatValue, @@ -25,10 +28,14 @@ export function FiatValue({ }, [priceImpact, theme.green1, theme.red1, theme.text3, theme.yellow1]) return ( - + {fiatValue ? ( - ~$ + $ + ) : ( '' @@ -36,9 +43,11 @@ export function FiatValue({ {priceImpact ? ( {' '} - ({priceImpact.multiply(-1).toSignificant(3)}%) + + ({priceImpact.multiply(-1).toSignificant(3)}%) + ) : null} - + ) } diff --git a/src/components/CurrencyInputPanel/index.tsx b/src/components/CurrencyInputPanel/index.tsx index dc64046353..951ee95eda 100644 --- a/src/components/CurrencyInputPanel/index.tsx +++ b/src/components/CurrencyInputPanel/index.tsx @@ -3,6 +3,7 @@ import { Currency, CurrencyAmount, Percent, Token } from '@uniswap/sdk-core' import { Pair } from '@uniswap/v2-sdk' import { AutoColumn } from 'components/Column' import { LoadingOpacityContainer, loadingOpacityMixin } from 'components/Loader/styled' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import { darken } from 'polished' import { ReactNode, useCallback, useState } from 'react' import { Lock } from 'react-feather' @@ -11,9 +12,8 @@ import { formatCurrencyAmount } from 'utils/formatCurrencyAmount' import { ReactComponent as DropDown } from '../../assets/images/dropdown.svg' import useTheme from '../../hooks/useTheme' -import { useActiveWeb3React } from '../../hooks/web3' import { useCurrencyBalance } from '../../state/wallet/hooks' -import { TYPE } from '../../theme' +import { ThemedText } from '../../theme' import { ButtonGray } from '../Button' import CurrencyLogo from 'components/CurrencyLogo' import DoubleCurrencyLogo from '../DoubleLogo' @@ -29,6 +29,8 @@ const InputPanel = styled.div<{ hideInput?: boolean }>` background-color: ${({ theme, hideInput }) => (hideInput ? 'transparent' : theme.bg2)}; z-index: 1; width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')}; + transition: height 1s ease; + will-change: height; ` const FixedContainer = styled.div` @@ -36,8 +38,7 @@ const FixedContainer = styled.div` height: 100%; position: absolute; border-radius: 20px; - background-color: ${({ theme }) => theme.bg1}; - opacity: 0.95; + background-color: ${({ theme }) => theme.bg2}; display: flex; align-items: center; justify-content: center; @@ -46,7 +47,7 @@ const FixedContainer = styled.div` const Container = styled.div<{ hideInput: boolean }>` border-radius: ${({ hideInput }) => (hideInput ? '16px' : '20px')}; - border: 1px solid ${({ theme, hideInput }) => (hideInput ? ' transparent' : theme.bg2)}; + border: 1px solid ${({ theme }) => theme.bg0}; background-color: ${({ theme }) => theme.bg1}; width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')}; :focus, @@ -56,35 +57,35 @@ const Container = styled.div<{ hideInput: boolean }>` ` const CurrencySelect = styled(ButtonGray)<{ visible: boolean; selected: boolean; hideInput?: boolean }>` - visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')}; align-items: center; - font-size: 24px; - font-weight: 500; - background-color: ${({ selected, theme }) => (selected ? theme.bg0 : theme.primary1)}; - color: ${({ selected, theme }) => (selected ? theme.text1 : theme.white)}; - border-radius: 16px; + background-color: ${({ selected, theme }) => (selected ? theme.bg2 : theme.primary1)}; 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); - outline: none; + color: ${({ selected, theme }) => (selected ? theme.text1 : theme.white)}; cursor: pointer; + border-radius: 16px; + outline: none; user-select: none; border: none; + font-size: 24px; + font-weight: 500; height: ${({ hideInput }) => (hideInput ? '2.8rem' : '2.4rem')}; width: ${({ hideInput }) => (hideInput ? '100%' : 'initial')}; padding: 0 8px; justify-content: space-between; - margin-right: ${({ hideInput }) => (hideInput ? '0' : '12px')}; + margin-left: ${({ hideInput }) => (hideInput ? '0' : '12px')}; :focus, :hover { - background-color: ${({ selected, theme }) => (selected ? theme.bg2 : darken(0.05, theme.primary1))}; + background-color: ${({ selected, theme }) => (selected ? theme.bg3 : darken(0.05, theme.primary1))}; } + visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')}; ` const InputRow = styled.div<{ selected: boolean }>` ${({ theme }) => theme.flexRowNoWrap} align-items: center; justify-content: space-between; - padding: ${({ selected }) => (selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 0.75rem 1rem')}; + padding: ${({ selected }) => (selected ? ' 1rem 1rem 0.75rem 1rem' : '1rem 1rem 1rem 1rem')}; ` const LabelRow = styled.div` @@ -128,28 +129,30 @@ const StyledTokenName = styled.span<{ active?: boolean }>` const StyledBalanceMax = styled.button<{ disabled?: boolean }>` background-color: transparent; + background-color: ${({ theme }) => theme.primary5}; border: none; border-radius: 12px; - font-size: 14px; - font-weight: 500; + color: ${({ theme }) => theme.primary1}; cursor: pointer; - padding: 0; - color: ${({ theme }) => theme.primaryText1}; + 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')}; - margin-left: 0.25rem; + + :hover { + opacity: ${({ disabled }) => (!disabled ? 0.8 : 0.4)}; + } :focus { outline: none; } - - ${({ theme }) => theme.mediaWidth.upToExtraSmall` - margin-right: 0.5rem; - `}; ` const StyledNumericalInput = styled(NumericalInput)<{ $loading: boolean }>` - ${loadingOpacityMixin} + ${loadingOpacityMixin}; + text-align: left; ` interface CurrencyInputPanelProps { @@ -212,14 +215,23 @@ export default function CurrencyInputPanel({ - + The market price is outside your specified price range. Single-asset deposit only. - + )} + {!hideInput && ( + + )} + } - {!hideInput && ( - - )} - {!hideInput && !hideBalance && ( + {!hideInput && !hideBalance && currency && ( + + + {account ? ( - @@ -282,24 +289,19 @@ export default function CurrencyInputPanel({ renderBalance ? ( renderBalance(selectedCurrencyBalance) ) : ( - - Balance: {formatCurrencyAmount(selectedCurrencyBalance, 4)} {currency.symbol} - + Balance: {formatCurrencyAmount(selectedCurrencyBalance, 4)} ) ) : null} - + {showMaxButton && selectedCurrencyBalance ? ( - (Max) + MAX ) : null} ) : ( )} - - - )} diff --git a/src/components/CurrencyLogo/index.tsx b/src/components/CurrencyLogo/index.tsx index 979711c4fa..1f031c1522 100644 --- a/src/components/CurrencyLogo/index.tsx +++ b/src/components/CurrencyLogo/index.tsx @@ -1,52 +1,19 @@ import { Currency } from '@uniswap/sdk-core' -import { SupportedChainId } from '@src/constants/chains' -import React, { useMemo } from 'react' +import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs' +import React from 'react' import styled from 'styled-components/macro' -import EthereumLogo from '../../assets/images/ethereum-logo.png' -import useHttpLocations from '../../hooks/useHttpLocations' -import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo' import Logo from '../Logo' -type Network = 'ethereum' | 'arbitrum' | 'optimism' - -function chainIdToNetworkName(networkId: SupportedChainId): Network { - switch (networkId) { - case SupportedChainId.MAINNET: - return 'ethereum' - case SupportedChainId.ARBITRUM_ONE: - return 'arbitrum' - case SupportedChainId.OPTIMISM: - return 'optimism' - default: - return 'ethereum' - } -} - -export const getTokenLogoURL = ( - address: string, - chainId: SupportedChainId = SupportedChainId.MAINNET -): string | void => { - const networkName = chainIdToNetworkName(chainId) - const networksWithUrls = [SupportedChainId.ARBITRUM_ONE, SupportedChainId.MAINNET, SupportedChainId.OPTIMISM] - if (networksWithUrls.includes(chainId)) { - return `https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/${networkName}/assets/${address}/logo.png` - } -} - -const StyledEthereumLogo = styled.img<{ size: string }>` +export const StyledLogo = styled(Logo)<{ size: string; native: boolean }>` width: ${({ size }) => size}; height: ${({ size }) => size}; - box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075); - border-radius: 24px; -` - -export const StyledLogo = styled(Logo)<{ size: string }>` - width: ${({ size }) => size}; - height: ${({ size }) => size}; - border-radius: ${({ size }) => size}; - box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075); - background-color: ${({ theme }) => theme.white}; + background: radial-gradient(white 50%, #ffffff00 calc(75% + 1px), #ffffff00 100%); + border-radius: 50%; + -mox-box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')}; + -webkit-box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')}; + box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')}; + border: 0px solid rgba(255, 255, 255, 0); ` export default function CurrencyLogo({ @@ -59,28 +26,16 @@ export default function CurrencyLogo({ size?: string style?: React.CSSProperties }) { - const uriLocations = useHttpLocations(currency instanceof WrappedTokenInfo ? currency.logoURI : undefined) - - const srcs: string[] = useMemo(() => { - if (!currency || currency.isNative) return [] - - if (currency.isToken) { - const defaultUrls = [] - const url = getTokenLogoURL(currency.address, currency.chainId) - if (url) { - defaultUrls.push(url) - } - if (currency instanceof WrappedTokenInfo) { - return [...uriLocations, ...defaultUrls] - } - return defaultUrls - } - return [] - }, [currency, uriLocations]) - - if (currency?.isNative) { - return - } - - return + const logoURIs = useCurrencyLogoURIs(currency) + + return ( + + ) } diff --git a/src/components/DowntimeWarning/index.tsx b/src/components/DowntimeWarning/index.tsx index 8e31046e58..9a55b01beb 100644 --- a/src/components/DowntimeWarning/index.tsx +++ b/src/components/DowntimeWarning/index.tsx @@ -1,10 +1,12 @@ import { Trans } from '@lingui/macro' -import { L2_CHAIN_IDS, SupportedChainId } from '@src/constants/chains' -import { useActiveWeb3React } from 'hooks/web3' +import { SupportedChainId } from '@src/constants/chains' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import { AlertOctagon } from 'react-feather' import styled from 'styled-components/macro' import { ExternalLink } from 'theme' +import { isL2ChainId } from '../../utils/chains' + const Root = styled.div` background-color: ${({ theme }) => (theme.darkMode ? '#888D9B' : '#CED0D9')}; border-radius: 18px; @@ -18,7 +20,6 @@ const Root = styled.div` max-width: 880px; ` const WarningIcon = styled(AlertOctagon)` - display: block; margin: auto 16px auto 0; min-height: 22px; min-width: 22px; @@ -28,50 +29,54 @@ const ReadMoreLink = styled(ExternalLink)` text-decoration: underline; ` +function Wrapper({ children }: { children: React.ReactNode }) { + return ( + + +
{children}
+
+ ) +} + +/** + * Shows a downtime warning for the network if it's relevant + */ export default function DowntimeWarning() { const { chainId } = useActiveWeb3React() - if (!chainId || !L2_CHAIN_IDS.includes(chainId)) { + if (!isL2ChainId(chainId)) { return null } - const Content = () => { - switch (chainId) { - case SupportedChainId.OPTIMISM: - case SupportedChainId.OPTIMISTIC_KOVAN: - return ( -
- - Optimistic Ethereum is in Beta and may experience downtime. Optimism expects planned downtime to upgrade - the network in the near future. During downtime, your position will not earn fees and you will be unable - to remove liquidity.{' '} - - Read more. - - -
- ) - case SupportedChainId.ARBITRUM_ONE: - case SupportedChainId.ARBITRUM_RINKEBY: - return ( -
- - Arbitrum is in Beta and may experience downtime. During downtime, your position will not earn fees and you - will be unable to remove liquidity.{' '} - - Read more. - - -
- ) - default: - return null - } - } + switch (chainId) { + case SupportedChainId.OPTIMISM: + case SupportedChainId.OPTIMISTIC_KOVAN: + return ( + + + Optimism is in Beta and may experience downtime. Optimism expects planned downtime to upgrade the network in + the near future. During downtime, your position will not earn fees and you will be unable to remove + liquidity.{' '} + + Read more. + + + + ) + case SupportedChainId.ARBITRUM_ONE: + case SupportedChainId.ARBITRUM_RINKEBY: + return ( + + + Arbitrum is in Beta and may experience downtime. During downtime, your position will not earn fees and you + will be unable to remove liquidity.{' '} + + Read more. + + + + ) - return ( - - - - - ) + default: + return null + } } diff --git a/src/components/ErrorBoundary/index.tsx b/src/components/ErrorBoundary/index.tsx index ebcf87590f..294e6228b8 100644 --- a/src/components/ErrorBoundary/index.tsx +++ b/src/components/ErrorBoundary/index.tsx @@ -4,7 +4,7 @@ import ReactGA from 'react-ga' import styled from 'styled-components/macro' import store, { AppState } from '../../state' -import { ExternalLink, TYPE } from '../../theme' +import { ExternalLink, ThemedText } from '../../theme' import { userAgent } from '../../utils/userAgent' import { AutoColumn } from '../Column' import { AutoRow } from '../Row' @@ -47,6 +47,8 @@ type ErrorBoundaryState = { error: Error | null } +const IS_UNISWAP = window.location.hostname === 'app.uniswap.org' + export default class ErrorBoundary extends React.Component { constructor(props: unknown) { super(props) @@ -67,6 +69,7 @@ export default class ErrorBoundary extends React.Component - + Something went wrong - + - {error.stack} + {error.stack} - - - - - Create an issue on GitHub - - - - - - - - Get support on Discord - - - - - + {IS_UNISWAP ? ( + + + + + Create an issue on GitHub + + + + + + + + Get support on Discord + + + + + + ) : null} @@ -121,7 +126,7 @@ function getRelevantState(): null | keyof AppState { if (!path.startsWith('#/')) { return null } - const pieces = path.substring(2).split(/[\/\\?]/) + const pieces = path.substring(2).split(/[/\\?]/) switch (pieces[0]) { case 'swap': return 'swap' diff --git a/src/components/FeeSelector/FeeOption.tsx b/src/components/FeeSelector/FeeOption.tsx new file mode 100644 index 0000000000..8193fffeab --- /dev/null +++ b/src/components/FeeSelector/FeeOption.tsx @@ -0,0 +1,51 @@ +import { Trans } from '@lingui/macro' +import { FeeAmount } from '@uniswap/v3-sdk' +import { ButtonRadioChecked } from 'components/Button' +import { AutoColumn } from 'components/Column' +import { useFeeTierDistribution } from 'hooks/useFeeTierDistribution' +import { PoolState } from 'hooks/usePools' +import React from 'react' +import styled from 'styled-components/macro' +import { ThemedText } from 'theme' + +import { FeeTierPercentageBadge } from './FeeTierPercentageBadge' +import { FEE_AMOUNT_DETAIL } from './shared' + +const ResponsiveText = styled(ThemedText.Label)` + line-height: 16px; + font-size: 14px; + + ${({ theme }) => theme.mediaWidth.upToSmall` + font-size: 12px; + line-height: 12px; + `}; +` + +interface FeeOptionProps { + feeAmount: FeeAmount + active: boolean + distributions: ReturnType['distributions'] + poolState: PoolState + onClick: () => void +} + +export function FeeOption({ feeAmount, active, poolState, distributions, onClick }: FeeOptionProps) { + return ( + + + + + {FEE_AMOUNT_DETAIL[feeAmount].label}% + + + {FEE_AMOUNT_DETAIL[feeAmount].description} + + + + {distributions && ( + + )} + + + ) +} diff --git a/src/components/FeeSelector/FeeTierPercentageBadge.tsx b/src/components/FeeSelector/FeeTierPercentageBadge.tsx new file mode 100644 index 0000000000..814d7bd9b3 --- /dev/null +++ b/src/components/FeeSelector/FeeTierPercentageBadge.tsx @@ -0,0 +1,31 @@ +import { Trans } from '@lingui/macro' +import { FeeAmount } from '@uniswap/v3-sdk' +import Badge from 'components/Badge' +import { useFeeTierDistribution } from 'hooks/useFeeTierDistribution' +import { PoolState } from 'hooks/usePools' +import React from 'react' +import { ThemedText } from 'theme' + +export function FeeTierPercentageBadge({ + feeAmount, + distributions, + poolState, +}: { + feeAmount: FeeAmount + distributions: ReturnType['distributions'] + poolState: PoolState +}) { + return ( + + + {!distributions || poolState === PoolState.NOT_EXISTS || poolState === PoolState.INVALID ? ( + Not created + ) : distributions[feeAmount] !== undefined ? ( + {distributions[feeAmount]?.toFixed(0)}% select + ) : ( + No data + )} + + + ) +} diff --git a/src/components/FeeSelector/index.tsx b/src/components/FeeSelector/index.tsx index 545a9ceba7..7b55a219c5 100644 --- a/src/components/FeeSelector/index.tsx +++ b/src/components/FeeSelector/index.tsx @@ -1,11 +1,11 @@ import { Trans } from '@lingui/macro' import { Currency } from '@uniswap/sdk-core' import { FeeAmount } from '@uniswap/v3-sdk' -import Badge from 'components/Badge' -import { ButtonGray, ButtonRadioChecked } from 'components/Button' +import { ButtonGray } from 'components/Button' import Card from 'components/Card' import { AutoColumn } from 'components/Column' import { RowBetween } from 'components/Row' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useFeeTierDistribution } from 'hooks/useFeeTierDistribution' import { PoolState, usePools } from 'hooks/usePools' import usePrevious from 'hooks/usePrevious' @@ -14,7 +14,11 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import ReactGA from 'react-ga' import { Box } from 'rebass' import styled, { keyframes } from 'styled-components/macro' -import { TYPE } from 'theme' +import { ThemedText } from 'theme' + +import { FeeOption } from './FeeOption' +import { FeeTierPercentageBadge } from './FeeTierPercentageBadge' +import { FEE_AMOUNT_DETAIL } from './shared' const pulse = (color: string) => keyframes` 0% { @@ -29,60 +33,18 @@ const pulse = (color: string) => keyframes` box-shadow: 0 0 0 0 ${color}; } ` - -const ResponsiveText = styled(TYPE.label)` - line-height: 16px; - - ${({ theme }) => theme.mediaWidth.upToSmall` - font-size: 12px; - line-height: 12px; - `}; -` - const FocusedOutlineCard = styled(Card)<{ pulsing: boolean }>` border: 1px solid ${({ theme }) => theme.bg2}; animation: ${({ pulsing, theme }) => pulsing && pulse(theme.primary1)} 0.6s linear; align-self: center; ` -const FeeAmountLabel = { - [FeeAmount.LOW]: { - label: '0.05', - description: Best for stable pairs., - }, - [FeeAmount.MEDIUM]: { - label: '0.3', - description: Best for most pairs., - }, - [FeeAmount.HIGH]: { - label: '1', - description: Best for exotic pairs., - }, -} - -function FeeTierPercentageBadge({ - feeAmount, - distributions, - poolState, -}: { - feeAmount: FeeAmount - distributions: ReturnType['distributions'] - poolState: PoolState -}) { - return ( - - - {!distributions || poolState === PoolState.NOT_EXISTS || poolState === PoolState.INVALID ? ( - Not created - ) : distributions[feeAmount] !== undefined ? ( - {distributions[feeAmount]?.toFixed(0)}% select - ) : ( - No data - )} - - - ) -} +const Select = styled.div` + align-items: flex-start; + display: grid; + grid-auto-flow: column; + grid-gap: 8px; +` export default function FeeSelector({ disabled = false, @@ -97,16 +59,19 @@ export default function FeeSelector({ currencyA?: Currency | undefined currencyB?: Currency | undefined }) { + const { chainId } = useActiveWeb3React() + const { isLoading, isError, largestUsageFeeTier, distributions } = useFeeTierDistribution(currencyA, currencyB) // get pool data on-chain for latest states const pools = usePools([ + [currencyA, currencyB, FeeAmount.LOWEST], [currencyA, currencyB, FeeAmount.LOW], [currencyA, currencyB, FeeAmount.MEDIUM], [currencyA, currencyB, FeeAmount.HIGH], ]) - const poolsByFeeTier = useMemo( + const poolsByFeeTier: Record = useMemo( () => pools.reduce( (acc, [curPoolState, curPool]) => { @@ -118,6 +83,7 @@ export default function FeeSelector({ }, { // default all states to NOT_EXISTS + [FeeAmount.LOWEST]: PoolState.NOT_EXISTS, [FeeAmount.LOW]: PoolState.NOT_EXISTS, [FeeAmount.MEDIUM]: PoolState.NOT_EXISTS, [FeeAmount.HIGH]: PoolState.NOT_EXISTS, @@ -134,7 +100,7 @@ export default function FeeSelector({ const recommended = useRef(false) const handleFeePoolSelectWithEvent = useCallback( - (fee) => { + (fee: FeeAmount) => { ReactGA.event({ category: 'FeePoolSelect', action: 'Manual', @@ -183,18 +149,18 @@ export default function FeeSelector({ {!feeAmount ? ( <> - + Fee tier - - + + The % you will earn in fees. - + ) : ( <> - - {FeeAmountLabel[feeAmount].label}% fee tier - + + {FEE_AMOUNT_DETAIL[feeAmount].label}% fee tier + {distributions && ( - {showOptions && ( - - handleFeePoolSelectWithEvent(FeeAmount.LOW)} - > - - - - 0.05% fee - - - Best for stable pairs. - - - - {distributions && ( - - )} - - - handleFeePoolSelectWithEvent(FeeAmount.MEDIUM)} - > - - - - 0.3% fee - - - Best for most pairs. - - - - {distributions && ( - - )} - - - handleFeePoolSelectWithEvent(FeeAmount.HIGH)} - > - - - - 1% fee - - - Best for exotic pairs. - - - - {distributions && ( - + {[FeeAmount.LOWEST, FeeAmount.LOW, FeeAmount.MEDIUM, FeeAmount.HIGH].map((_feeAmount, i) => { + const { supportedChains } = FEE_AMOUNT_DETAIL[_feeAmount] + if (supportedChains.includes(chainId)) { + return ( + handleFeePoolSelectWithEvent(_feeAmount)} distributions={distributions} - feeAmount={FeeAmount.HIGH} - poolState={poolsByFeeTier[FeeAmount.HIGH]} + poolState={poolsByFeeTier[_feeAmount]} + key={i} /> - )} - - - + ) + } + return null + })} + )} diff --git a/src/components/FeeSelector/shared.tsx b/src/components/FeeSelector/shared.tsx new file mode 100644 index 0000000000..4b93e68d9f --- /dev/null +++ b/src/components/FeeSelector/shared.tsx @@ -0,0 +1,30 @@ +import { Trans } from '@lingui/macro' +import { FeeAmount } from '@uniswap/v3-sdk' +import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from 'constants/chains' +import { ReactNode } from 'react' + +export const FEE_AMOUNT_DETAIL: Record< + FeeAmount, + { label: string; description: ReactNode; supportedChains: SupportedChainId[] } +> = { + [FeeAmount.LOWEST]: { + label: '0.01', + description: Best for very stable pairs., + supportedChains: [SupportedChainId.MAINNET], + }, + [FeeAmount.LOW]: { + label: '0.05', + description: Best for stable pairs., + supportedChains: ALL_SUPPORTED_CHAIN_IDS, + }, + [FeeAmount.MEDIUM]: { + label: '0.3', + description: Best for most pairs., + supportedChains: ALL_SUPPORTED_CHAIN_IDS, + }, + [FeeAmount.HIGH]: { + label: '1', + description: Best for exotic pairs., + supportedChains: ALL_SUPPORTED_CHAIN_IDS, + }, +} diff --git a/src/components/Header/ChainConnectivityWarning.tsx b/src/components/Header/ChainConnectivityWarning.tsx index 87c992c88b..c52e68cbea 100644 --- a/src/components/Header/ChainConnectivityWarning.tsx +++ b/src/components/Header/ChainConnectivityWarning.tsx @@ -1,6 +1,7 @@ import { Trans } from '@lingui/macro' -import { CHAIN_INFO, L2ChainInfo, SupportedChainId } from 'constants/chains' -import { useActiveWeb3React } from 'hooks/web3' +import { CHAIN_INFO, L2ChainInfo } from 'constants/chainInfo' +import { SupportedChainId } from 'constants/chains' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import { AlertOctagon } from 'react-feather' import styled from 'styled-components/macro' import { ExternalLink, MEDIA_WIDTHS } from 'theme' diff --git a/src/components/Header/HolidayOrnament.tsx b/src/components/Header/HolidayOrnament.tsx new file mode 100644 index 0000000000..028e66c422 --- /dev/null +++ b/src/components/Header/HolidayOrnament.tsx @@ -0,0 +1,26 @@ +import { ReactElement } from 'react' +import styled from 'styled-components/macro' + +import SantaHat from '../../assets/images/santa-hat.png' + +const SantaHatImage = styled.img` + position: absolute; + top: -4px; + right: -4px; + height: 18px; +` + +const Christmas = + +const DATE_TO_ORNAMENT: { [date: string]: ReactElement } = { + '12-24': Christmas, + '12-25': Christmas, +} + +const HolidayOrnament = () => { + // months in javascript are 0 indexed... + const today = `${new Date().getMonth() + 1}-${new Date().getDate()}` + return DATE_TO_ORNAMENT[today] || null +} + +export default HolidayOrnament diff --git a/src/components/Header/NetworkCard.tsx b/src/components/Header/NetworkCard.tsx deleted file mode 100644 index f7b8e1a692..0000000000 --- a/src/components/Header/NetworkCard.tsx +++ /dev/null @@ -1,235 +0,0 @@ -import { Trans } from '@lingui/macro' -import { YellowCard } from 'components/Card' -import { useOnClickOutside } from 'hooks/useOnClickOutside' -import { useActiveWeb3React } from 'hooks/web3' -import { useEffect, useRef, useState } from 'react' -import { ArrowDownCircle, ChevronDown, ToggleLeft } from 'react-feather' -import { ApplicationModal } from 'state/application/reducer' -import { useModalOpen, useToggleModal } from 'state/application/hooks' -import styled, { css } from 'styled-components/macro' -import { ExternalLink } from 'theme' -import { switchToNetwork } from 'utils/switchToNetwork' -import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from '@src/constants/chains' - -const BaseWrapper = css` - position: relative; - margin-right: 8px; - ${({ theme }) => theme.mediaWidth.upToMedium` - justify-self: end; - `}; - - ${({ theme }) => theme.mediaWidth.upToSmall` - margin: 0 0.5rem 0 0; - width: initial; - text-overflow: ellipsis; - flex-shrink: 1; - `}; -` -const L2Wrapper = styled.div` - ${BaseWrapper} -` -const BaseMenuItem = css` - align-items: center; - background-color: transparent; - border-radius: 12px; - color: ${({ theme }) => theme.text2}; - cursor: pointer; - display: flex; - flex: 1; - flex-direction: row; - font-size: 16px; - font-weight: 400; - justify-content: space-between; - :hover { - color: ${({ theme }) => theme.text1}; - text-decoration: none; - } -` -const DisabledMenuItem = styled.div` - ${BaseMenuItem} - align-items: center; - background-color: ${({ theme }) => theme.bg2}; - cursor: auto; - display: flex; - font-size: 10px; - font-style: italic; - justify-content: center; - :hover, - :active, - :focus { - color: ${({ theme }) => theme.text2}; - } -` -const FallbackWrapper = styled(YellowCard)` - ${BaseWrapper} - width: auto; - border-radius: 12px; - padding: 8px 12px; - width: 100%; - user-select: none; -` -const Icon = styled.img` - width: 16px; - margin-right: 2px; - - ${({ theme }) => theme.mediaWidth.upToSmall` - margin-right: 4px; - - `}; -` - -const MenuFlyout = styled.span` - background-color: ${({ theme }) => theme.bg1}; - border: 1px solid ${({ theme }) => theme.bg0}; - - box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), - 0px 24px 32px rgba(0, 0, 0, 0.01); - border-radius: 12px; - padding: 1rem; - display: flex; - flex-direction: column; - font-size: 1rem; - position: absolute; - left: 0rem; - top: 3rem; - z-index: 100; - width: 237px; - ${({ theme }) => theme.mediaWidth.upToMedium` - - bottom: unset; - top: 4.5em - right: 0; - - `}; - > { - padding: 12px; - } - > :not(:first-child) { - margin-top: 8px; - } - > :not(:last-child) { - margin-bottom: 8px; - } -` -const LinkOutCircle = styled(ArrowDownCircle)` - transform: rotate(230deg); - width: 16px; - height: 16px; - opacity: 0.6; -` -const MenuItem = styled(ExternalLink)` - ${BaseMenuItem} -` -const ButtonMenuItem = styled.button` - ${BaseMenuItem} - border: none; - box-shadow: none; - color: ${({ theme }) => theme.text2}; - outline: none; - padding: 0; -` -const NetworkInfo = styled.button<{ chainId: SupportedChainId }>` - align-items: center; - background-color: ${({ theme }) => theme.bg0}; - border-radius: 12px; - border: 1px solid ${({ theme }) => theme.bg0}; - color: ${({ theme }) => theme.text1}; - display: flex; - flex-direction: row; - font-weight: 500; - font-size: 12px; - height: 100%; - margin: 0; - height: 38px; - padding: 0.7rem; - - :hover, - :focus { - cursor: pointer; - outline: none; - border: 1px solid ${({ theme }) => theme.bg3}; - } -` -const NetworkName = styled.div<{ chainId: SupportedChainId }>` - border-radius: 6px; - font-size: 16px; - font-weight: 500; - padding: 0 2px 0.5px 4px; - margin: 0 2px; - white-space: pre; - ${({ theme }) => theme.mediaWidth.upToSmall` - display: none; - `}; -` - -export default function NetworkCard() { - const { chainId, library } = useActiveWeb3React() - const node = useRef(null) - const open = useModalOpen(ApplicationModal.ARBITRUM_OPTIONS) - const toggle = useToggleModal(ApplicationModal.ARBITRUM_OPTIONS) - useOnClickOutside(node, open ? toggle : undefined) - - const [implements3085, setImplements3085] = useState(false) - useEffect(() => { - // metamask is currently the only known implementer of this EIP - // here we proceed w/ a noop feature check to ensure the user's version of metamask supports network switching - // if not, we hide the UI - if (!library?.provider?.request || !chainId || !library?.provider?.isMetaMask) { - return - } - switchToNetwork({ library, chainId }) - .then((x) => x ?? setImplements3085(true)) - .catch(() => setImplements3085(false)) - }, [library, chainId]) - - const info = chainId ? CHAIN_INFO[chainId] : undefined - if (!chainId || chainId === SupportedChainId.MAINNET || !info || !library) { - return null - } - - if (L2_CHAIN_IDS.includes(chainId)) { - const info = CHAIN_INFO[chainId as SupportedL2ChainId] - const isArbitrum = [SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_RINKEBY].includes(chainId) - return ( - - - - {info.label} - - - {open && ( - - -
{isArbitrum ? {info.label} Bridge : Optimistic L2 Gateway}
- -
- - {isArbitrum ? {info.label} Explorer : Optimistic Etherscan} - - - -
- Learn more -
- -
- {implements3085 ? ( - switchToNetwork({ library, chainId: 1 })}> -
- Switch to L1 (Mainnet) -
- -
- ) : ( - - Change your network to go back to L1 - - )} -
- )} -
- ) - } - - return {info.label} -} diff --git a/src/components/Header/NetworkSelector.tsx b/src/components/Header/NetworkSelector.tsx index 49389e2c4b..9312e76b8e 100644 --- a/src/components/Header/NetworkSelector.tsx +++ b/src/components/Header/NetworkSelector.tsx @@ -1,24 +1,24 @@ import { Trans } from '@lingui/macro' -import { - ARBITRUM_HELP_CENTER_LINK, - CHAIN_INFO, - L2_CHAIN_IDS, - OPTIMISM_HELP_CENTER_LINK, - SupportedChainId, - SupportedL2ChainId, -} from '@src/constants/chains' +import { CHAIN_INFO } from 'constants/chainInfo' +import { CHAIN_IDS_TO_NAMES, SupportedChainId } from '@src/constants/chains' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useOnClickOutside } from 'hooks/useOnClickOutside' -import { useActiveWeb3React } from 'hooks/web3' -import { useCallback, useRef } from 'react' +import useParsedQueryString from 'hooks/useParsedQueryString' +import usePrevious from 'hooks/usePrevious' +import { ParsedQs } from 'qs' +import { useCallback, useEffect, useRef } from 'react' import { ArrowDownCircle, ChevronDown } from 'react-feather' +import { useHistory } from 'react-router-dom' import { useModalOpen, useToggleModal } from 'state/application/hooks' -import { ApplicationModal } from 'state/application/reducer' -import { useAppSelector } from 'state/hooks' +import { addPopup, ApplicationModal } from 'state/application/reducer' import styled from 'styled-components/macro' import { ExternalLink, MEDIA_WIDTHS } from 'theme' -import { switchToNetwork } from 'utils/switchToNetwork' +import { replaceURLParam } from 'utils/routes' -const ActiveRowLinkList = styled.div` +import { useAppDispatch } from '../../state/hooks' +import { switchToNetwork } from '../../utils/switchToNetwork' + +export const ActiveRowLinkList = styled.div` display: flex; flex-direction: column; padding: 0 8px; @@ -34,26 +34,25 @@ const ActiveRowLinkList = styled.div` text-decoration: none; } & > a:first-child { - border-top: 1px solid ${({ theme }) => theme.text2}; margin: 0; - margin-top: 6px; + margin-top: 0px; padding-top: 10px; } ` -const ActiveRowWrapper = styled.div` - background-color: ${({ theme }) => theme.bg2}; +export const ActiveRowWrapper = styled.div` + background-color: ${({ theme }) => theme.bg1}; border-radius: 8px; cursor: pointer; - padding: 8px 0 8px 0; + padding: 8px; width: 100%; ` -const FlyoutHeader = styled.div` +export const FlyoutHeader = styled.div` color: ${({ theme }) => theme.text2}; font-weight: 400; ` const FlyoutMenu = styled.div` align-items: flex-start; - background-color: ${({ theme }) => theme.bg1}; + background-color: ${({ theme }) => theme.bg0}; box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), 0px 24px 32px rgba(0, 0, 0, 0.01); border-radius: 20px; @@ -75,7 +74,7 @@ const FlyoutMenu = styled.div` ` const FlyoutRow = styled.div<{ active: boolean }>` align-items: center; - background-color: ${({ active, theme }) => (active ? theme.bg2 : 'transparent')}; + background-color: ${({ active, theme }) => (active ? theme.bg1 : 'transparent')}; border-radius: 8px; cursor: pointer; display: flex; @@ -91,7 +90,7 @@ const FlyoutRowActiveIndicator = styled.div` height: 9px; width: 9px; ` -const LinkOutCircle = styled(ArrowDownCircle)` +export const LinkOutCircle = styled(ArrowDownCircle)` transform: rotate(230deg); width: 16px; height: 16px; @@ -113,9 +112,9 @@ const SelectorLabel = styled(NetworkLabel)` ` const SelectorControls = styled.div<{ interactive: boolean }>` align-items: center; - background-color: ${({ theme }) => theme.bg1}; - border: 2px solid ${({ theme }) => theme.bg1}; - border-radius: 12px; + background-color: ${({ theme }) => theme.bg0}; + border: 2px solid ${({ theme }) => theme.bg0}; + border-radius: 16px; color: ${({ theme }) => theme.text1}; cursor: ${({ interactive }) => (interactive ? 'pointer' : 'auto')}; display: flex; @@ -135,9 +134,9 @@ const SelectorWrapper = styled.div` } ` const StyledChevronDown = styled(ChevronDown)` - width: 12px; + width: 16px; ` -const BridgeText = ({ chainId }: { chainId: SupportedL2ChainId }) => { +const BridgeLabel = ({ chainId }: { chainId: SupportedChainId }) => { switch (chainId) { case SupportedChainId.ARBITRUM_ONE: case SupportedChainId.ARBITRUM_RINKEBY: @@ -145,11 +144,14 @@ const BridgeText = ({ chainId }: { chainId: SupportedL2ChainId }) => { case SupportedChainId.OPTIMISM: case SupportedChainId.OPTIMISTIC_KOVAN: return Optimism Gateway + case SupportedChainId.POLYGON: + case SupportedChainId.POLYGON_MUMBAI: + return Polygon Bridge default: return Bridge } } -const ExplorerText = ({ chainId }: { chainId: SupportedL2ChainId }) => { +const ExplorerLabel = ({ chainId }: { chainId: SupportedChainId }) => { switch (chainId) { case SupportedChainId.ARBITRUM_ONE: case SupportedChainId.ARBITRUM_RINKEBY: @@ -157,91 +159,167 @@ const ExplorerText = ({ chainId }: { chainId: SupportedL2ChainId }) => { case SupportedChainId.OPTIMISM: case SupportedChainId.OPTIMISTIC_KOVAN: return Optimistic Etherscan + case SupportedChainId.POLYGON: + case SupportedChainId.POLYGON_MUMBAI: + return Polygonscan default: - return Explorer + return Etherscan + } +} + +function Row({ + targetChain, + onSelectChain, +}: { + targetChain: SupportedChainId + onSelectChain: (targetChain: number) => void +}) { + const { library, chainId } = useActiveWeb3React() + if (!library || !chainId) { + return null } + const active = chainId === targetChain + const { helpCenterUrl, explorer, bridge, label, logoUrl } = CHAIN_INFO[targetChain] + + const rowContent = ( + onSelectChain(targetChain)} active={active}> + + {label} + {chainId === targetChain && } + + ) + + if (active) { + return ( + + {rowContent} + + {bridge ? ( + + + + ) : null} + {explorer ? ( + + + + ) : null} + {helpCenterUrl ? ( + + Help Center + + ) : null} + + + ) + } + return rowContent +} + +const getParsedChainId = (parsedQs?: ParsedQs) => { + const chain = parsedQs?.chain + if (!chain || typeof chain !== 'string') return { urlChain: undefined, urlChainId: undefined } + + return { urlChain: chain.toLowerCase(), urlChainId: getChainIdFromName(chain) } +} + +const getChainIdFromName = (name: string) => { + const entry = Object.entries(CHAIN_IDS_TO_NAMES).find(([_, n]) => n === name) + const chainId = entry?.[0] + return chainId ? parseInt(chainId) : undefined +} + +const getChainNameFromId = (id: string | number) => { + // casting here may not be right but fine to return undefined if it's not a supported chain ID + return CHAIN_IDS_TO_NAMES[id as SupportedChainId] || '' } export default function NetworkSelector() { const { chainId, library } = useActiveWeb3React() + const parsedQs = useParsedQueryString() + const { urlChain, urlChainId } = getParsedChainId(parsedQs) + const prevChainId = usePrevious(chainId) const node = useRef() const open = useModalOpen(ApplicationModal.NETWORK_SELECTOR) const toggle = useToggleModal(ApplicationModal.NETWORK_SELECTOR) useOnClickOutside(node, open ? toggle : undefined) - const implements3085 = useAppSelector((state) => state.application.implements3085) + + const history = useHistory() const info = chainId ? CHAIN_INFO[chainId] : undefined - const isOnL2 = chainId ? L2_CHAIN_IDS.includes(chainId) : false - const showSelector = Boolean(implements3085 || isOnL2) - const mainnetInfo = CHAIN_INFO[SupportedChainId.MAINNET] + const dispatch = useAppDispatch() - const conditionalToggle = useCallback(() => { - if (showSelector) { - toggle() - } - }, [showSelector, toggle]) + const handleChainSwitch = useCallback( + (targetChain: number, skipToggle?: boolean) => { + if (!library) return + switchToNetwork({ library, chainId: targetChain }) + .then(() => { + if (!skipToggle) { + toggle() + } + history.replace({ + search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(targetChain)), + }) + }) + .catch((error) => { + console.error('Failed to switch networks', error) - if (!chainId || !info || !library) { - return null - } + // we want app network <-> chainId param to be in sync, so if user changes the network by changing the URL + // but the request fails, revert the URL back to current chainId + if (chainId) { + history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) }) + } - function Row({ targetChain }: { targetChain: number }) { - if (!library || !chainId || (!implements3085 && targetChain !== chainId)) { - return null - } - const handleRowClick = () => { - switchToNetwork({ library, chainId: targetChain }) - toggle() + if (!skipToggle) { + toggle() + } + + dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: `failed-network-switch` })) + }) + }, + [dispatch, library, toggle, history, chainId] + ) + + useEffect(() => { + if (!chainId || !prevChainId) return + + // when network change originates from wallet or dropdown selector, just update URL + if (chainId !== prevChainId) { + history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) }) + // otherwise assume network change originates from URL + } else if (urlChainId && urlChainId !== chainId) { + handleChainSwitch(urlChainId, true) } - const active = chainId === targetChain - const hasExtendedInfo = L2_CHAIN_IDS.includes(targetChain) - const isOptimism = targetChain === SupportedChainId.OPTIMISM - const rowText = `${CHAIN_INFO[targetChain].label}${isOptimism ? ' (Optimism)' : ''}` - const RowContent = () => ( - - - {rowText} - {chainId === targetChain && } - - ) - const helpCenterLink = isOptimism ? OPTIMISM_HELP_CENTER_LINK : ARBITRUM_HELP_CENTER_LINK - if (active && hasExtendedInfo) { - return ( - - - - - - - - - - - Help Center - - - - ) + }, [chainId, urlChainId, prevChainId, handleChainSwitch, history]) + + // set chain parameter on initial load if not there + useEffect(() => { + if (chainId && !urlChainId) { + history.replace({ search: replaceURLParam(history.location.search, 'chain', getChainNameFromId(chainId)) }) } - return + }, [chainId, history, urlChainId, urlChain]) + + if (!chainId || !info || !library) { + return null } return ( - - + + {info.label} - {showSelector && } + {open && ( - + Select a network - - - + + + + )} diff --git a/src/components/Header/Polling.tsx b/src/components/Header/Polling.tsx index bc79a01520..f20b74e1ed 100644 --- a/src/components/Header/Polling.tsx +++ b/src/components/Header/Polling.tsx @@ -1,11 +1,20 @@ +import { Trans } from '@lingui/macro' +import { RowFixed } from 'components/Row' +import { CHAIN_INFO } from 'constants/chainInfo' +import useActiveWeb3React from 'hooks/useActiveWeb3React' +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 { useAppSelector } from 'state/hooks' import styled, { keyframes } from 'styled-components/macro' +import { ExternalLink, ThemedText } from 'theme' +import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink' -import { useActiveWeb3React } from '../../hooks/web3' -import { useBlockNumber } from '../../state/application/hooks' -import { ExternalLink, TYPE } from '../../theme' -import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink' +import { MouseoverTooltip } from '../Tooltip' import { ChainConnectivityWarning } from './ChainConnectivityWarning' export const StyledPolling = styled.div<{ warning: boolean }>` @@ -22,12 +31,20 @@ export const StyledPolling = styled.div<{ warning: boolean }>` display: none; `} ` -export const StyledPollingNumber = styled(TYPE.small)<{ breathe: boolean; hovering: boolean }>` +export const StyledPollingNumber = styled(ThemedText.Small)<{ breathe: boolean; hovering: boolean }>` transition: opacity 0.25s ease; opacity: ${({ breathe, hovering }) => (hovering ? 0.7 : breathe ? 1 : 0.5)}; :hover { opacity: 1; } + + a { + color: unset; + } + a:hover { + text-decoration: none; + color: unset; + } ` export const StyledPollingDot = styled.div<{ warning: boolean }>` width: 8px; @@ -40,6 +57,17 @@ export const StyledPollingDot = styled.div<{ warning: boolean }>` transition: 250ms ease background-color; ` +export const StyledGasDot = styled.div` + background-color: ${({ theme }) => theme.text3}; + border-radius: 50%; + height: 4px; + min-height: 4px; + min-width: 4px; + position: relative; + transition: 250ms ease background-color; + width: 4px; +` + const rotate360 = keyframes` from { transform: rotate(0deg); @@ -49,7 +77,7 @@ const rotate360 = keyframes` } ` -const Spinner = styled.div<{ warning: boolean }>` +export const Spinner = styled.div<{ warning: boolean }>` animation: ${rotate360} 1s cubic-bezier(0.83, 0, 0.17, 1) infinite; transform: translateZ(0); @@ -68,12 +96,25 @@ const Spinner = styled.div<{ warning: boolean }>` top: -3px; ` +const DEFAULT_MS_BEFORE_WARNING = ms`10m` +const NETWORK_HEALTH_CHECK_MS = ms`10s` + export default function Polling() { const { chainId } = useActiveWeb3React() const blockNumber = useBlockNumber() const [isMounting, setIsMounting] = useState(false) const [isHover, setIsHover] = useState(false) - const chainConnectivityWarning = useAppSelector((state) => state.application.chainConnectivityWarning) + const machineTime = useMachineTimeMs(NETWORK_HEALTH_CHECK_MS) + const blockTime = useCurrentBlockTimestamp() + const theme = useTheme() + + const ethGasPrice = useGasPrice() + const priceGwei = ethGasPrice ? JSBI.divide(ethGasPrice, JSBI.BigInt(1000000000)) : undefined + + const waitMsBeforeWarning = + (chainId ? CHAIN_INFO[chainId]?.blockWaitMsBeforeWarning : DEFAULT_MS_BEFORE_WARNING) ?? DEFAULT_MS_BEFORE_WARNING + + const warning = Boolean(!!blockTime && machineTime - blockTime.mul(1000).toNumber() > waitMsBeforeWarning) useEffect( () => { @@ -93,25 +134,48 @@ export default function Polling() { //if you pass a value to array, like this [data] than clearTimeout will run every time this value changes (useEffect re-run) ) + //TODO - chainlink gas oracle is really slow. Can we get a better data source? + return ( <> - - setIsHover(true)} - onMouseLeave={() => setIsHover(false)} - warning={chainConnectivityWarning} - > + + setIsHover(true)} onMouseLeave={() => setIsHover(false)} warning={warning}> + + {priceGwei ? ( + + + + The current fast gas amount for sending a transaction on L1. Gas fees are paid in + Ethereum's native currency Ether (ETH) and denominated in GWEI. + + } + > + {priceGwei.toString()} gwei + + + + + ) : null} + - {blockNumber}  + + The most recent block number on this network. Prices update on every block.} + > + {blockNumber}  + + - - {isMounting && } - {' '} + {isMounting && }{' '} - - {chainConnectivityWarning && } + {warning && } + ) } diff --git a/src/components/Header/UniBalanceContent.tsx b/src/components/Header/UniBalanceContent.tsx deleted file mode 100644 index bb77c40127..0000000000 --- a/src/components/Header/UniBalanceContent.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import { Trans } from '@lingui/macro' -import { CurrencyAmount, Token } from '@uniswap/sdk-core' -import { CHAIN_INFO, SupportedChainId } from '@src/constants/chains' -import { useMemo } from 'react' -import { X } from 'react-feather' -import styled from 'styled-components/macro' - -import tokenLogo from '../../assets/images/token-logo.png' -import { UNI } from 'constants/tokens' -import { useMerkleDistributorContract } from '../../hooks/useContract' -import useCurrentBlockTimestamp from '../../hooks/useCurrentBlockTimestamp' -import { useTotalSupply } from '../../hooks/useTotalSupply' -import useUSDCPrice from '../../hooks/useUSDCPrice' -import { useActiveWeb3React } from '../../hooks/web3' -import { useTotalUniEarned } from '../../state/stake/hooks' -import { useAggregateUniBalance, useTokenBalance } from '../../state/wallet/hooks' -import { ExternalLink, StyledInternalLink, TYPE, UniTokenAnimated } from 'theme' -import { computeUniCirculation } from '../../utils/computeUniCirculation' -import { AutoColumn } from '../Column' -import { Break, CardBGImage, CardNoise, CardSection, DataCard } from '../earn/styled' -import { RowBetween } from '../Row' - -const ContentWrapper = styled(AutoColumn)` - width: 100%; -` - -const ModalUpper = styled(DataCard)` - box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); - background: radial-gradient(76.02% 75.41% at 1.84% 0%, #ff007a 0%, #021d43 100%); - padding: 0.5rem; -` - -const StyledClose = styled(X)` - position: absolute; - right: 16px; - top: 16px; - - :hover { - cursor: pointer; - } -` - -/** - * Content for balance stats modal - */ -export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowUniBalanceModal: any }) { - const { account, chainId } = useActiveWeb3React() - const uni = chainId ? UNI[chainId] : undefined - - const total = useAggregateUniBalance() - const uniBalance: CurrencyAmount | undefined = useTokenBalance(account ?? undefined, uni) - const uniToClaim: CurrencyAmount | undefined = useTotalUniEarned() - - const totalSupply: CurrencyAmount | undefined = useTotalSupply(uni) - const uniPrice = useUSDCPrice(uni) - const blockTimestamp = useCurrentBlockTimestamp() - const unclaimedUni = useTokenBalance(useMerkleDistributorContract()?.address, uni) - const circulation: CurrencyAmount | undefined = useMemo( - () => - blockTimestamp && uni && chainId === 1 ? computeUniCirculation(uni, blockTimestamp, unclaimedUni) : totalSupply, - [blockTimestamp, chainId, totalSupply, unclaimedUni, uni] - ) - - const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET] - - return ( - - - - - - - - Your UNI Breakdown - - setShowUniBalanceModal(false)} /> - - - - {account && ( - <> - - - {' '} - - {total?.toFixed(2, { groupSeparator: ',' })} - - - - - - Balance: - - {uniBalance?.toFixed(2, { groupSeparator: ',' })} - - - - Unclaimed: - - - {uniToClaim?.toFixed(4, { groupSeparator: ',' })}{' '} - {uniToClaim && uniToClaim.greaterThan('0') && ( - setShowUniBalanceModal(false)} to="/uni"> - (claim) - - )} - - - - - - - )} - - - - - UNI price: - - ${uniPrice?.toFixed(2) ?? '-'} - - - - UNI in circulation: - - {circulation?.toFixed(0, { groupSeparator: ',' })} - - - - Total Supply - - {totalSupply?.toFixed(0, { groupSeparator: ',' })} - - {uni && uni.chainId === 1 ? ( - - View UNI Analytics - - ) : null} - - - - - ) -} diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index c813ff7ab3..4224b97d83 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -1,30 +1,29 @@ import { Trans } from '@lingui/macro' import useScrollPosition from '@react-hook/window-scroll' -import { CHAIN_INFO, SupportedChainId } from '@src/constants/chains' +import { CHAIN_INFO } from 'constants/chainInfo' +import { SupportedChainId } from '@src/constants/chains' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import useTheme from 'hooks/useTheme' import { darken } from 'polished' -import { useState } from 'react' import { NavLink } from 'react-router-dom' import { Text } from 'rebass' import { useShowClaimPopup, useToggleSelfClaimModal } from 'state/application/hooks' import { useUserHasAvailableClaim } from 'state/claim/hooks' import { useUserHasSubmittedClaim } from 'state/transactions/hooks' import { useDarkModeManager } from 'state/user/hooks' -import { useETHBalances } from 'state/wallet/hooks' +import { useNativeCurrencyBalances } from 'state/wallet/hooks' import styled from 'styled-components/macro' import { ReactComponent as Logo } from '../../assets/svg/logo.svg' -import { useActiveWeb3React } from '../../hooks/web3' -import { ExternalLink, TYPE } from '../../theme' +import { ExternalLink, ThemedText } from '../../theme' import ClaimModal from '../claim/ClaimModal' import { CardNoise } from '../earn/styled' import Menu from '../Menu' -import Modal from '../Modal' import Row from '../Row' import { Dots } from '../swap/styleds' import Web3Status from '../Web3Status' +import HolidayOrnament from './HolidayOrnament' import NetworkSelector from './NetworkSelector' -import UniBalanceContent from './UniBalanceContent' const HeaderFrame = styled.div<{ showBackground: boolean }>` display: grid; @@ -91,7 +90,7 @@ const HeaderLinks = styled(Row)` justify-self: center; background-color: ${({ theme }) => theme.bg0}; width: fit-content; - padding: 4px; + padding: 2px; border-radius: 16px; display: grid; grid-auto-flow: column; @@ -123,10 +122,11 @@ const AccountElement = styled.div<{ active: boolean }>` display: flex; flex-direction: row; align-items: center; - background-color: ${({ theme, active }) => (!active ? theme.bg1 : theme.bg1)}; - border-radius: 12px; + background-color: ${({ theme, active }) => (!active ? theme.bg0 : theme.bg0)}; + border-radius: 16px; white-space: nowrap; width: 100%; + height: 40px; :focus { border: 1px solid blue; @@ -181,6 +181,8 @@ const UniIcon = styled.div` :hover { transform: rotate(-5deg); } + + position: relative; ` const activeClassName = 'ACTIVE' @@ -202,11 +204,11 @@ const StyledNavLink = styled(NavLink).attrs({ overflow: hidden; white-space: nowrap; &.${activeClassName} { - border-radius: 12px; + border-radius: 14px; font-weight: 600; justify-content: center; color: ${({ theme }) => theme.text1}; - background-color: ${({ theme }) => theme.bg2}; + background-color: ${({ theme }) => theme.bg1}; } :hover, @@ -231,7 +233,7 @@ const StyledExternalLink = styled(ExternalLink).attrs({ font-weight: 500; &.${activeClassName} { - border-radius: 12px; + border-radius: 14px; font-weight: 600; color: ${({ theme }) => theme.text1}; } @@ -246,7 +248,7 @@ const StyledExternalLink = styled(ExternalLink).attrs({ export default function Header() { const { account, chainId } = useActiveWeb3React() - const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? ''] + const userEthBalance = useNativeCurrencyBalances(account ? [account] : [])?.[account ?? ''] const [darkMode] = useDarkModeManager() const { white, black } = useTheme() @@ -256,21 +258,22 @@ export default function Header() { const { claimTxn } = useUserHasSubmittedClaim(account ?? undefined) - const [showUniBalanceModal, setShowUniBalanceModal] = useState(false) const showClaimPopup = useShowClaimPopup() const scrollY = useScrollPosition() - const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET] + const { + infoLink, + nativeCurrency: { symbol: nativeCurrencySymbol }, + } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET] + return ( 45}> - setShowUniBalanceModal(false)}> - - <UniIcon> <Logo fill={darkMode ? white : black} width="24px" height="100%" title="logo" /> + <HolidayOrnament /> </UniIcon> @@ -300,6 +303,7 @@ export default function Header() { + @@ -308,7 +312,7 @@ export default function Header() { {availableClaim && !showClaimPopup && ( - + {claimTxn && !claimTxn?.receipt ? ( Claiming UNI @@ -316,7 +320,7 @@ export default function Header() { ) : ( Claim UNI )} - + @@ -324,7 +328,9 @@ export default function Header() { {account && userEthBalance ? ( - {userEthBalance?.toSignificant(3)} ETH + + {userEthBalance?.toSignificant(3)} {nativeCurrencySymbol} + ) : null} diff --git a/src/components/HoverInlineText/index.tsx b/src/components/HoverInlineText/index.tsx index 0efbea8173..ec32694653 100644 --- a/src/components/HoverInlineText/index.tsx +++ b/src/components/HoverInlineText/index.tsx @@ -2,9 +2,15 @@ import Tooltip from 'components/Tooltip' import { useState } from 'react' import styled from 'styled-components/macro' -export const TextWrapper = styled.span<{ margin: boolean; link?: boolean; fontSize?: string; adjustSize?: boolean }>` +export const TextWrapper = styled.span<{ + margin: boolean + link?: boolean + fontSize?: string + adjustSize?: boolean + textColor?: string +}>` margin-left: ${({ margin }) => margin && '4px'}; - color: ${({ theme, link }) => (link ? theme.blue1 : theme.text1)}; + color: ${({ theme, link, textColor }) => (link ? theme.blue1 : textColor ?? theme.text1)}; font-size: ${({ fontSize }) => fontSize ?? 'inherit'}; @media screen and (max-width: 600px) { @@ -18,6 +24,7 @@ const HoverInlineText = ({ margin = false, adjustSize = false, fontSize, + textColor, link, ...rest }: { @@ -26,6 +33,7 @@ const HoverInlineText = ({ margin?: boolean adjustSize?: boolean fontSize?: string + textColor?: string link?: boolean }) => { const [showHover, setShowHover] = useState(false) @@ -42,6 +50,7 @@ const HoverInlineText = ({ onMouseLeave={() => setShowHover(false)} margin={margin} adjustSize={adjustSize} + textColor={textColor} link={link} fontSize={fontSize} {...rest} @@ -53,7 +62,14 @@ const HoverInlineText = ({ } return ( - + {text} ) diff --git a/src/components/Identicon/StatusIcon.tsx b/src/components/Identicon/StatusIcon.tsx new file mode 100644 index 0000000000..4e87424926 --- /dev/null +++ b/src/components/Identicon/StatusIcon.tsx @@ -0,0 +1,26 @@ +import { Connector } from '@web3-react/types' +import { AbstractConnector } from 'web3-react-abstract-connector' + +import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg' +import FortmaticIcon from '../../assets/images/fortmaticIcon.png' +import PortisIcon from '../../assets/images/portisIcon.png' +import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg' +import { fortmatic, injected, portis, walletconnect, walletlink } from 'connectors' +import Identicon from 'components/Identicon' + +export default function StatusIcon({ connector }: { connector: AbstractConnector | Connector }) { + switch (connector) { + case injected: + return + case walletconnect: + return {'WalletConnect'} + case walletlink: + return {'Coinbase + case fortmatic: + return {'Fortmatic'} + case portis: + return {'Portis'} + default: + return null + } +} diff --git a/src/components/Identicon/index.tsx b/src/components/Identicon/index.tsx index f0015537af..aee4626d7f 100644 --- a/src/components/Identicon/index.tsx +++ b/src/components/Identicon/index.tsx @@ -1,32 +1,52 @@ -import Davatar, { Image } from '@davatar/react' -import { useMemo } from 'react' +import jazzicon from '@metamask/jazzicon' +import useActiveWeb3React from 'hooks/useActiveWeb3React' +import useENSAvatar from 'hooks/useENSAvatar' +import { useLayoutEffect, useMemo, useRef, useState } from 'react' import styled from 'styled-components/macro' -import { useActiveWeb3React } from '../../hooks/web3' - -const StyledIdenticonContainer = styled.div` +const StyledIdenticon = styled.div` height: 1rem; width: 1rem; border-radius: 1.125rem; background-color: ${({ theme }) => theme.bg4}; + font-size: initial; +` + +const StyledAvatar = styled.img` + height: inherit; + width: inherit; + border-radius: inherit; ` export default function Identicon() { - const { account, library } = useActiveWeb3React() + const { account } = useActiveWeb3React() + const { avatar } = useENSAvatar(account ?? undefined) + const [fetchable, setFetchable] = useState(true) - // restrict usage of Davatar until it stops sending 3p requests - // see https://github.com/metaphor-xyz/davatar-helpers/issues/18 - const supportsENS = useMemo(() => { - return ([1, 3, 4, 5] as Array).includes(library?.network?.chainId) - }, [library]) + const icon = useMemo(() => account && jazzicon(16, parseInt(account.slice(2, 10), 16)), [account]) + const iconRef = useRef(null) + useLayoutEffect(() => { + const current = iconRef.current + if (icon) { + current?.appendChild(icon) + return () => { + try { + current?.removeChild(icon) + } catch (e) { + console.error('Avatar icon not found') + } + } + } + return + }, [icon, iconRef]) return ( - - {account && supportsENS ? ( - + + {avatar && fetchable ? ( + setFetchable(false)}> ) : ( - + )} - + ) } diff --git a/src/components/InputStepCounter/InputStepCounter.tsx b/src/components/InputStepCounter/InputStepCounter.tsx index b9a2e74762..c3db459e0d 100644 --- a/src/components/InputStepCounter/InputStepCounter.tsx +++ b/src/components/InputStepCounter/InputStepCounter.tsx @@ -6,7 +6,7 @@ import { AutoColumn } from 'components/Column' import { ReactNode, SetStateAction, useCallback, useEffect, useState } from 'react' import { Minus, Plus } from 'react-feather' import styled, { keyframes } from 'styled-components/macro' -import { TYPE } from 'theme' +import { ThemedText } from 'theme' import { Input as NumericalInput } from '../NumericalInput' @@ -57,13 +57,13 @@ const StyledInput = styled(NumericalInput)<{ usePercent?: boolean }>` `}; ` -const InputTitle = styled(TYPE.small)` +const InputTitle = styled(ThemedText.Small)` color: ${({ theme }) => theme.text2}; font-size: 12px; font-weight: 500; ` -const ButtonLabel = styled(TYPE.white)<{ disabled: boolean }>` +const ButtonLabel = styled(ThemedText.White)<{ disabled: boolean }>` color: ${({ theme, disabled }) => (disabled ? theme.text2 : theme.text1)} !important; ` diff --git a/src/components/LiquidityChartRangeInput/Area.tsx b/src/components/LiquidityChartRangeInput/Area.tsx index 7a0e2401ef..ea7953e5e1 100644 --- a/src/components/LiquidityChartRangeInput/Area.tsx +++ b/src/components/LiquidityChartRangeInput/Area.tsx @@ -37,7 +37,7 @@ export const Area = ({ .y0(yScale(0))( series.filter((d) => { const value = xScale(xValue(d)) - return value > 0 && value <= innerWidth + return value > 0 && value <= window.innerWidth }) as Iterable<[number, number]> ) ?? undefined } diff --git a/src/components/LiquidityChartRangeInput/hooks.ts b/src/components/LiquidityChartRangeInput/hooks.ts index 4996b2ea7d..c8d6a204c2 100644 --- a/src/components/LiquidityChartRangeInput/hooks.ts +++ b/src/components/LiquidityChartRangeInput/hooks.ts @@ -1,16 +1,10 @@ import { Currency } from '@uniswap/sdk-core' import { FeeAmount } from '@uniswap/v3-sdk' -import { usePoolActiveLiquidity } from 'hooks/usePoolTickData' -import JSBI from 'jsbi' +import { TickProcessed, usePoolActiveLiquidity } from 'hooks/usePoolTickData' import { useCallback, useMemo } from 'react' import { ChartEntry } from './types' -export interface TickProcessed { - liquidityActive: JSBI - price0: string -} - export function useDensityChartData({ currencyA, currencyB, diff --git a/src/components/LiquidityChartRangeInput/index.tsx b/src/components/LiquidityChartRangeInput/index.tsx index f9133d4aa5..acbb0a3433 100644 --- a/src/components/LiquidityChartRangeInput/index.tsx +++ b/src/components/LiquidityChartRangeInput/index.tsx @@ -14,12 +14,18 @@ import { batch } from 'react-redux' import { Bound } from 'state/mint/v3/actions' import styled from 'styled-components/macro' -import { TYPE } from '../../theme' +import { ThemedText } from '../../theme' import { Chart } from './Chart' import { useDensityChartData } from './hooks' import { ZoomLevels } from './types' const ZOOM_LEVELS: Record = { + [FeeAmount.LOWEST]: { + initialMin: 0.999, + initialMax: 1.001, + min: 0.00001, + max: 1.5, + }, [FeeAmount.LOW]: { initialMin: 0.999, initialMax: 1.001, @@ -52,9 +58,9 @@ function InfoBox({ message, icon }: { message?: ReactNode; icon: ReactNode }) { {icon} {message && ( - + {message} - + )} ) diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx index 0987909b49..238558a440 100644 --- a/src/components/Menu/index.tsx +++ b/src/components/Menu/index.tsx @@ -1,22 +1,23 @@ // eslint-disable-next-line no-restricted-imports import { t, Trans } from '@lingui/macro' import { PrivacyPolicyModal } from 'components/PrivacyPolicy' -import { L2_CHAIN_IDS, CHAIN_INFO, SupportedChainId } from '@src/constants/chains' +import { L2_CHAIN_IDS } from '@src/constants/chains' import { LOCALE_LABEL, SUPPORTED_LOCALES, SupportedLocale } from 'constants/locales' import { useActiveLocale } from 'hooks/useActiveLocale' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useLocationLinkProps } from 'hooks/useLocationLinkProps' import React, { useEffect, useRef, useState } from 'react' import { BookOpen, Check, ChevronLeft, - Code, + Coffee, FileText, Globe, + HelpCircle, Info, MessageCircle, Moon, - PieChart, Sun, } from 'react-feather' import { Link } from 'react-router-dom' @@ -25,7 +26,6 @@ import styled, { css } from 'styled-components/macro' import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg' import { useOnClickOutside } from '../../hooks/useOnClickOutside' -import { useActiveWeb3React } from '../../hooks/web3' import { useModalOpen, useToggleModal } from '../../state/application/hooks' import { ApplicationModal } from '../../state/application/reducer' import { ExternalLink } from '../../theme' @@ -49,12 +49,11 @@ const StyledMenuButton = styled.button` background-color: transparent; margin: 0; padding: 0; - height: 38px; + height: 40px; background-color: ${({ theme }) => theme.bg0}; border: 1px solid ${({ theme }) => theme.bg0}; - padding: 0.15rem 0.5rem; - border-radius: 12px; + border-radius: 16px; :hover, :focus { @@ -99,6 +98,7 @@ const MenuFlyout = styled.span<{ flyoutAlignment?: FlyoutAlignment }>` position: absolute; top: 3rem; z-index: 100; + ${({ flyoutAlignment = FlyoutAlignment.RIGHT }) => flyoutAlignment === FlyoutAlignment.RIGHT ? css` @@ -178,8 +178,6 @@ const ToggleMenuItem = styled.button` } ` -const CODE_LINK = 'https://github.com/Uniswap/uniswap-interface' - function LanguageMenuItem({ locale, active, key }: { locale: SupportedLocale; active: boolean; key: string }) { const { to, onClick } = useLocationLinkProps(locale) @@ -218,7 +216,6 @@ export default function Menu() { const togglePrivacyPolicy = useToggleModal(ApplicationModal.PRIVACY_POLICY) const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM) const showUNIClaimOption = Boolean(!!account && !!chainId && !L2_CHAIN_IDS.includes(chainId)) - const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET] const [darkMode, toggleDarkMode] = useDarkModeManager() @@ -251,17 +248,17 @@ export default function Menu() { - +
- Docs + Help Center
- +
- +
- Code + Request Features
- +
@@ -269,12 +266,6 @@ export default function Menu() {
- -
- Analytics -
- -
setMenu('lang')}>
Language @@ -285,6 +276,12 @@ export default function Menu() {
{darkMode ? Light Theme : Dark Theme}
{darkMode ? : } + +
+ Docs +
+ +
togglePrivacyPolicy()}>
Legal & Privacy diff --git a/src/components/ModalViews/index.tsx b/src/components/ModalViews/index.tsx index 2ed6899e5e..98bd623d4d 100644 --- a/src/components/ModalViews/index.tsx +++ b/src/components/ModalViews/index.tsx @@ -1,11 +1,11 @@ import { Trans } from '@lingui/macro' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useContext } from 'react' import { ArrowUpCircle } from 'react-feather' import styled, { ThemeContext } from 'styled-components/macro' import Circle from '../../assets/images/blue-loader.svg' -import { useActiveWeb3React } from '../../hooks/web3' -import { CloseIcon, CustomLightSpinner, TYPE } from '../../theme' +import { CloseIcon, CustomLightSpinner, ThemedText } from '../../theme' import { ExternalLink } from '../../theme/components' import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink' import { AutoColumn, ColumnCenter } from '../Column' @@ -32,9 +32,9 @@ export function LoadingView({ children, onDismiss }: { children: any; onDismiss: {children} - + Confirm this transaction in your wallet - + ) @@ -68,9 +68,9 @@ export function SubmittedView({ href={getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)} style={{ marginLeft: '4px' }} > - + View transaction on Explorer - + )} diff --git a/src/components/NavigationTabs/index.tsx b/src/components/NavigationTabs/index.tsx index 4d8de801a2..1a09322322 100644 --- a/src/components/NavigationTabs/index.tsx +++ b/src/components/NavigationTabs/index.tsx @@ -10,7 +10,7 @@ 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 { TYPE } from 'theme' +import { ThemedText } from 'theme' import Row, { RowBetween } from '../Row' import SettingsTab from '../Settings' @@ -136,7 +136,7 @@ export function AddRemoveTabs({ > - Remove Liquidity )} - + {children} diff --git a/src/components/NetworkAlert/AddLiquidityNetworkAlert.tsx b/src/components/NetworkAlert/AddLiquidityNetworkAlert.tsx deleted file mode 100644 index 02c3854fd5..0000000000 --- a/src/components/NetworkAlert/AddLiquidityNetworkAlert.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { Trans } from '@lingui/macro' -import { - ArbitrumWrapperBackgroundDarkMode, - ArbitrumWrapperBackgroundLightMode, - OptimismWrapperBackgroundDarkMode, - OptimismWrapperBackgroundLightMode, -} from 'components/NetworkAlert/NetworkAlert' -import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from '@src/constants/chains' -import { useActiveWeb3React } from 'hooks/web3' -import { ArrowDownCircle } from 'react-feather' -import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks' -import styled from 'styled-components/macro' -import { ExternalLink, MEDIA_WIDTHS } from 'theme' -import { ReadMoreLink } from './styles' - -const L2Icon = styled.img` - display: none; - height: 40px; - margin: auto 20px auto 4px; - width: 40px; - @media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) { - display: block; - } -` -const DesktopTextBreak = styled.div` - display: none; - @media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) { - display: block; - } -` -const Wrapper = styled.div<{ chainId: SupportedL2ChainId; darkMode: boolean; logoUrl: string }>` - ${({ chainId, darkMode }) => - [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId) - ? darkMode - ? OptimismWrapperBackgroundDarkMode - : OptimismWrapperBackgroundLightMode - : darkMode - ? ArbitrumWrapperBackgroundDarkMode - : ArbitrumWrapperBackgroundLightMode}; - border-radius: 20px; - display: flex; - flex-direction: column; - overflow: hidden; - padding: 12px; - position: relative; - width: 100%; - - :before { - background-image: url(${({ logoUrl }) => logoUrl}); - background-repeat: no-repeat; - background-size: 300px; - content: ''; - height: 300px; - opacity: 0.1; - position: absolute; - transform: rotate(25deg) translate(-90px, -40px); - width: 300px; - z-index: -1; - } - @media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) { - flex-direction: row; - padding: 16px 20px; - } -` -const Body = styled.div` - font-size: 12px; - line-height: 143%; - margin: 12px; - @media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) { - flex: 1 1 auto; - margin: auto 0; - } -` -const LinkOutCircle = styled(ArrowDownCircle)` - transform: rotate(230deg); - width: 20px; - height: 20px; - margin-left: 12px; -` -const LinkOutToBridge = styled(ExternalLink)` - align-items: center; - background-color: black; - border-radius: 16px; - color: white; - display: flex; - font-size: 14px; - justify-content: space-between; - margin: 0; - max-height: 47px; - padding: 16px 12px; - text-decoration: none; - width: auto; - :hover, - :focus, - :active { - background-color: black; - } - @media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) { - margin: auto 0 auto auto; - padding: 14px 16px; - min-width: 226px; - } -` -export function AddLiquidityNetworkAlert() { - const { chainId } = useActiveWeb3React() - const [darkMode] = useDarkModeManager() - const [arbitrumAlphaAcknowledged] = useArbitrumAlphaAlert() - - if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged) { - return null - } - const info = CHAIN_INFO[chainId as SupportedL2ChainId] - const isOptimism = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId) - const depositUrl = isOptimism ? `${info.bridge}?chainId=1` : info.bridge - const readMoreLink = isOptimism - ? 'https://help.uniswap.org/en/articles/5392809-how-to-deposit-tokens-to-optimism' - : 'https://help.uniswap.org/en/articles/5538618-how-to-deposit-tokens-to-arbitrum' - return ( - - - - This is an alpha release of Uniswap on the {info.label} network. - You must bridge L1 assets to the network to use them.{' '} - - Read more - - - - Deposit to {info.label} - - - - ) -} diff --git a/src/components/NetworkAlert/MinimalNetworkAlert.tsx b/src/components/NetworkAlert/MinimalNetworkAlert.tsx deleted file mode 100644 index 443ff2a7c9..0000000000 --- a/src/components/NetworkAlert/MinimalNetworkAlert.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { Trans } from '@lingui/macro' -import { - ArbitrumWrapperBackgroundDarkMode, - ArbitrumWrapperBackgroundLightMode, - OptimismWrapperBackgroundDarkMode, - OptimismWrapperBackgroundLightMode, -} from 'components/NetworkAlert/NetworkAlert' -import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from '@src/constants/chains' -import { useActiveWeb3React } from 'hooks/web3' -import { ArrowDownCircle } from 'react-feather' -import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks' -import styled from 'styled-components/macro' -import { ExternalLink, MEDIA_WIDTHS } from 'theme' -import { ReadMoreLink } from './styles' - -const L2Icon = styled.img` - display: none; - height: 40px; - margin: auto 20px auto 4px; - width: 40px; - @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) { - display: block; - } -` -const DesktopTextBreak = styled.div` - display: none; - @media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) { - display: block; - } -` -const Wrapper = styled.div<{ chainId: SupportedL2ChainId; darkMode: boolean; logoUrl: string }>` - ${({ chainId, darkMode }) => - [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId) - ? darkMode - ? OptimismWrapperBackgroundDarkMode - : OptimismWrapperBackgroundLightMode - : darkMode - ? ArbitrumWrapperBackgroundDarkMode - : ArbitrumWrapperBackgroundLightMode}; - border-radius: 20px; - display: flex; - flex-direction: column; - overflow: hidden; - padding: 12px; - position: relative; - width: 100%; - - :before { - background-image: url(${({ logoUrl }) => logoUrl}); - background-repeat: no-repeat; - background-size: 300px; - content: ''; - height: 300px; - opacity: 0.1; - position: absolute; - transform: rotate(25deg) translate(-90px, -40px); - width: 300px; - z-index: -1; - } - @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) { - flex-direction: row; - padding: 16px 20px; - } -` -const Body = styled.div` - font-size: 12px; - line-height: 143%; - margin: 12px; - @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) { - flex: 1 1 auto; - margin: auto 0; - } -` -const LinkOutCircle = styled(ArrowDownCircle)` - transform: rotate(230deg); - width: 20px; - height: 20px; - margin-left: 12px; -` -const LinkOutToBridge = styled(ExternalLink)` - align-items: center; - background-color: black; - border-radius: 16px; - color: white; - display: flex; - font-size: 14px; - justify-content: space-between; - margin: 0; - max-height: 47px; - padding: 16px 8px; - text-decoration: none; - width: auto; - :hover, - :focus, - :active { - background-color: black; - } - @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) { - margin: auto 0 auto auto; - padding: 14px 17px; - min-width: 226px; - } -` -export function MinimalNetworkAlert() { - const { chainId } = useActiveWeb3React() - const [darkMode] = useDarkModeManager() - const [arbitrumAlphaAcknowledged] = useArbitrumAlphaAlert() - - if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged) { - return null - } - const info = CHAIN_INFO[chainId as SupportedL2ChainId] - const isOptimism = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId) - const depositUrl = isOptimism ? `${info.bridge}?chainId=1` : info.bridge - const readMoreLink = isOptimism - ? 'https://help.uniswap.org/en/articles/5392809-how-to-deposit-tokens-to-optimism' - : 'https://help.uniswap.org/en/articles/5538618-how-to-deposit-tokens-to-arbitrum' - return ( - - - - This is an alpha release of Uniswap on the {info.label} network. - You must bridge L1 assets to the network to use them.{' '} - - Read more - - - - Deposit to {info.label} - - - - ) -} diff --git a/src/components/NetworkAlert/NetworkAlert.tsx b/src/components/NetworkAlert/NetworkAlert.tsx index cbc3517a13..2536f0fd88 100644 --- a/src/components/NetworkAlert/NetworkAlert.tsx +++ b/src/components/NetworkAlert/NetworkAlert.tsx @@ -1,157 +1,93 @@ import { Trans } from '@lingui/macro' -import { - ARBITRUM_HELP_CENTER_LINK, - L2_CHAIN_IDS, - OPTIMISM_HELP_CENTER_LINK, - SupportedChainId, - SupportedL2ChainId, -} from '@src/constants/chains' -import { useActiveWeb3React } from 'hooks/web3' -import { useCallback, useState } from 'react' -import { ArrowDownCircle, X } from 'react-feather' -import { useArbitrumAlphaAlert, useDarkModeManager, useOptimismAlphaAlert } from '@src/state/user/hooks' -import { useETHBalances } from 'state/wallet/hooks' -import styled, { css } from 'styled-components/macro' -import { ExternalLink, MEDIA_WIDTHS } from 'theme' +import { CHAIN_INFO } from 'constants/chainInfo' +import { SupportedChainId } from '@src/constants/chains' +import useActiveWeb3React from 'hooks/useActiveWeb3React' +import { ArrowUpRight } from 'react-feather' +import { useDarkModeManager } from '@src/state/user/hooks' +import styled from 'styled-components/macro' +import { ExternalLink, HideSmall } from 'theme' -import { CHAIN_INFO } from '../../constants/chains' - -export const DesktopTextBreak = styled.div` - display: none; - @media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) { - display: block; - } -` +import { AutoRow } from '../Row' const L2Icon = styled.img` - width: 36px; - height: 36px; - justify-self: center; -` -const BetaTag = styled.span<{ color: string }>` - align-items: center; - background-color: ${({ color }) => color}; - border-radius: 6px; - color: ${({ theme }) => theme.white}; - display: flex; - font-size: 14px; - height: 28px; - justify-content: center; - left: -16px; - position: absolute; - transform: rotate(-15deg); - top: -16px; - width: 60px; - z-index: 1; + width: 24px; + height: 24px; + margin-right: 16px; ` -const Body = styled.p` - font-size: 12px; - grid-column: 1 / 3; - line-height: 143%; - margin: 0; - @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) { - grid-column: 2 / 3; - } -` -export const Controls = styled.div<{ thin?: boolean }>` + +export const Controls = styled.div` align-items: center; display: flex; justify-content: flex-start; - ${({ thin }) => - thin && - css` - margin: auto 32px auto 0; - `} -` -const CloseIcon = styled(X)` - cursor: pointer; - position: absolute; - top: 16px; - right: 16px; + padding: 0 20px 20px 20px; ` + const BodyText = styled.div` - align-items: center; - display: grid; - grid-gap: 4px; - grid-template-columns: 40px 4fr; - grid-template-rows: auto auto; - margin: 20px 16px; - @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) { - grid-template-columns: 42px 4fr; - grid-gap: 8px; - } -` -const LearnMoreLink = styled(ExternalLink)<{ thin?: boolean }>` - align-items: center; - background-color: transparent; - border: 1px solid rgba(255, 255, 255, 0.4); - border-radius: 8px; - color: ${({ theme }) => theme.text1}; + color: ${({ color }) => color}; display: flex; - font-size: 16px; - height: 44px; - justify-content: space-between; - margin: 0 0 20px 0; - padding: 12px 16px; - text-decoration: none; - width: auto; - :hover, - :focus, - :active { - background-color: rgba(255, 255, 255, 0.05); - } - transition: background-color 150ms ease-in-out; - ${({ thin }) => - thin && - css` - font-size: 14px; - margin: auto; - width: 112px; - `} + align-items: center; + justify-content: flex-start; + margin: 8px; + font-size: 14px; ` const RootWrapper = styled.div` position: relative; + margin-top: 16px; ` -export const ArbitrumWrapperBackgroundDarkMode = css` - background: radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%), - radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.3) 0%, rgba(33, 114, 229, 0.3) 100%), hsla(0, 0%, 100%, 0.1); -` -export const ArbitrumWrapperBackgroundLightMode = css` - background: radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%), - radial-gradient(circle at top left, hsla(206, 50%, 75%, 0.01), hsla(215, 79%, 51%, 0.12)), hsla(0, 0%, 100%, 0.1); -` -export const OptimismWrapperBackgroundDarkMode = css` - background: radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.2) 0%, rgba(255, 255, 255, 0.1) 100%), - radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.5) 0%, rgba(235, 0, 255, 0.345) 96%); -` -export const OptimismWrapperBackgroundLightMode = css` - background: radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%), - radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.5); -` -const ContentWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; logoUrl: string; thin?: boolean }>` - ${({ chainId, darkMode }) => - [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId) - ? darkMode - ? OptimismWrapperBackgroundDarkMode - : OptimismWrapperBackgroundLightMode - : darkMode - ? ArbitrumWrapperBackgroundDarkMode - : ArbitrumWrapperBackgroundLightMode}; + +const SHOULD_SHOW_ALERT = { + [SupportedChainId.OPTIMISM]: true, + [SupportedChainId.OPTIMISTIC_KOVAN]: true, + [SupportedChainId.ARBITRUM_ONE]: true, + [SupportedChainId.ARBITRUM_RINKEBY]: true, + [SupportedChainId.POLYGON]: true, + [SupportedChainId.POLYGON_MUMBAI]: true, +} + +type NetworkAlertChains = keyof typeof SHOULD_SHOW_ALERT + +const BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID: { + [darkMode in 'dark' | 'light']: { [chainId in NetworkAlertChains]: string } +} = { + dark: { + [SupportedChainId.POLYGON]: + 'radial-gradient(100% 93.36% at 0% 6.64%, rgba(160, 108, 247, 0.1) 0%, rgba(82, 32, 166, 0.1) 100%)', + [SupportedChainId.POLYGON_MUMBAI]: + 'radial-gradient(100% 93.36% at 0% 6.64%, rgba(160, 108, 247, 0.1) 0%, rgba(82, 32, 166, 0.1) 100%)', + [SupportedChainId.OPTIMISM]: + 'radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.01) 0%, rgba(255, 255, 255, 0.04) 100%),radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.01) 0%, rgba(235, 0, 255, 0.01) 96%)', + [SupportedChainId.OPTIMISTIC_KOVAN]: + 'radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.04) 100%),radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.04) 0%, rgba(235, 0, 255, 0.01 96%)', + [SupportedChainId.ARBITRUM_ONE]: + 'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.01) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.05) 0%, rgba(33, 114, 229, 0.05) 100%), hsla(0, 0%, 100%, 0.05)', + [SupportedChainId.ARBITRUM_RINKEBY]: + 'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.05) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.05) 0%, rgba(33, 114, 229, 0.1) 100%), hsla(0, 0%, 100%, 0.05)', + }, + light: { + [SupportedChainId.POLYGON]: + 'radial-gradient(182.71% 205.59% at 2.81% 7.69%, rgba(130, 71, 229, 0.2) 0%, rgba(167, 202, 255, 0.2) 100%)', + [SupportedChainId.POLYGON_MUMBAI]: + 'radial-gradient(182.71% 205.59% at 2.81% 7.69%, rgba(130, 71, 229, 0.2) 0%, rgba(167, 202, 255, 0.2) 100%)', + [SupportedChainId.OPTIMISM]: + 'radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.1)', + [SupportedChainId.OPTIMISTIC_KOVAN]: + 'radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.1)', + [SupportedChainId.ARBITRUM_ONE]: + 'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(circle at top left, hsla(206, 50%, 75%, 0.01), hsla(215, 79%, 51%, 0.12)), hsla(0, 0%, 100%, 0.1)', + [SupportedChainId.ARBITRUM_RINKEBY]: + 'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(circle at top left, hsla(206, 50%, 75%, 0.01), hsla(215, 79%, 51%, 0.12)), hsla(0, 0%, 100%, 0.1)', + }, +} + +const ContentWrapper = styled.div<{ chainId: NetworkAlertChains; darkMode: boolean; logoUrl: string }>` + background: ${({ chainId, darkMode }) => BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID[darkMode ? 'dark' : 'light'][chainId]}; border-radius: 20px; display: flex; - flex-direction: column; - max-width: 480px; - min-height: 174px; + flex-direction: row; overflow: hidden; position: relative; width: 100%; - ${({ thin }) => - thin && - css` - flex-direction: row; - max-width: max-content; - min-height: min-content; - `} + :before { background-image: url(${({ logoUrl }) => logoUrl}); background-repeat: no-repeat; @@ -165,116 +101,73 @@ const ContentWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean z-index: -1; } ` -const Header = styled.h2<{ thin?: boolean }>` +const Header = styled.h2` font-weight: 600; - font-size: 20px; + font-size: 16px; margin: 0; - padding-right: 30px; - display: ${({ thin }) => (thin ? 'none' : 'block')}; -` -const LinkOutCircle = styled(ArrowDownCircle)` - margin-left: 12px; - transform: rotate(230deg); - width: 20px; - height: 20px; ` -const LinkOutToBridge = styled(ExternalLink)<{ thin?: boolean }>` + +const LinkOutToBridge = styled(ExternalLink)` align-items: center; - background-color: black; border-radius: 8px; color: white; display: flex; font-size: 16px; - height: 44px; justify-content: space-between; - margin: 0 12px 20px 18px; - padding: 12px 16px; - text-decoration: none; - width: auto; - :hover, - :focus, - :active { - background-color: black; - } - ${({ thin }) => - thin && - css` - font-size: 14px; - margin: auto 10px; - width: 168px; - `} + padding: 6px 8px; + margin-right: 12px; + text-decoration: none !important; + width: 100%; +` + +const StyledArrowUpRight = styled(ArrowUpRight)` + margin-left: 12px; + width: 24px; + height: 24px; ` -interface NetworkAlertProps { - thin?: boolean +const TEXT_COLORS: { [chainId in NetworkAlertChains]: string } = { + [SupportedChainId.POLYGON]: 'rgba(130, 71, 229)', + [SupportedChainId.POLYGON_MUMBAI]: 'rgba(130, 71, 229)', + [SupportedChainId.OPTIMISM]: '#ff3856', + [SupportedChainId.OPTIMISTIC_KOVAN]: '#ff3856', + [SupportedChainId.ARBITRUM_ONE]: '#0490ed', + [SupportedChainId.ARBITRUM_RINKEBY]: '#0490ed', } -export function NetworkAlert(props: NetworkAlertProps) { - const { account, chainId } = useActiveWeb3React() - const [darkMode] = useDarkModeManager() - const [arbitrumAlphaAcknowledged, setArbitrumAlphaAcknowledged] = useArbitrumAlphaAlert() - const [optimismAlphaAcknowledged, setOptimismAlphaAcknowledged] = useOptimismAlphaAlert() - const [locallyDismissed, setLocallyDimissed] = useState(false) - const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? ''] +function shouldShowAlert(chainId: number | undefined): chainId is NetworkAlertChains { + return Boolean(chainId && SHOULD_SHOW_ALERT[chainId as unknown as NetworkAlertChains]) +} - const dismiss = useCallback(() => { - if (userEthBalance?.greaterThan(0)) { - switch (chainId) { - case SupportedChainId.OPTIMISM: - setOptimismAlphaAcknowledged(true) - break - case SupportedChainId.ARBITRUM_ONE: - setArbitrumAlphaAcknowledged(true) - break - } - } else { - setLocallyDimissed(true) - } - }, [chainId, setArbitrumAlphaAcknowledged, setOptimismAlphaAcknowledged, userEthBalance]) +export function NetworkAlert() { + const { chainId } = useActiveWeb3React() + const [darkMode] = useDarkModeManager() - const onOptimismAndOptimismAcknowledged = SupportedChainId.OPTIMISM === chainId && optimismAlphaAcknowledged - const onArbitrumAndArbitrumAcknowledged = SupportedChainId.ARBITRUM_ONE === chainId && arbitrumAlphaAcknowledged - if ( - !chainId || - !L2_CHAIN_IDS.includes(chainId) || - onArbitrumAndArbitrumAcknowledged || - onOptimismAndOptimismAcknowledged || - locallyDismissed - ) { + if (!shouldShowAlert(chainId)) { return null } - const info = CHAIN_INFO[chainId as SupportedL2ChainId] - const isOptimism = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId) - const depositUrl = isOptimism ? `${info.bridge}?chainId=1` : info.bridge - const helpCenterLink = isOptimism ? OPTIMISM_HELP_CENTER_LINK : ARBITRUM_HELP_CENTER_LINK - const showCloseIcon = Boolean(userEthBalance?.greaterThan(0) && !props.thin) - return ( + + const { label, logoUrl, bridge } = CHAIN_INFO[chainId] + const textColor = TEXT_COLORS[chainId] + + return bridge ? ( - Beta - - {showCloseIcon && } - - -
- Uniswap on {info.label} -
- - - To starting trading on {info.label}, first bridge your assets from L1 to L2. Please treat this as a beta - release and learn about the risks before using {info.label}. - - -
- - - Deposit Assets - - - - Learn More - - + + + + + +
+ {label} token bridge +
+ + Deposit tokens to the {label} network. + +
+
+ +
- ) + ) : null } diff --git a/src/components/NumericalInput/index.tsx b/src/components/NumericalInput/index.tsx index e51d154945..ae2613824b 100644 --- a/src/components/NumericalInput/index.tsx +++ b/src/components/NumericalInput/index.tsx @@ -12,7 +12,7 @@ const StyledInput = styled.input<{ error?: boolean; fontSize?: string; align?: s border: none; flex: 1 1 auto; background-color: ${({ theme }) => theme.bg1}; - font-size: ${({ fontSize }) => fontSize ?? '24px'}; + font-size: ${({ fontSize }) => fontSize ?? '28px'}; text-align: ${({ align }) => align && align}; white-space: nowrap; overflow: hidden; diff --git a/src/components/OptimismDowntimeWarning/index.tsx b/src/components/OptimismDowntimeWarning/index.tsx deleted file mode 100644 index a537a5e79b..0000000000 --- a/src/components/OptimismDowntimeWarning/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Trans } from '@lingui/macro' -import { SupportedChainId } from '@src/constants/chains' -import { useActiveWeb3React } from 'hooks/web3' -import { AlertOctagon } from 'react-feather' -import styled from 'styled-components/macro' -import { ExternalLink } from 'theme' - -const Root = styled.div` - background-color: ${({ theme }) => theme.yellow3}; - border-radius: 18px; - color: black; - margin-top: 16px; - padding: 16px; - width: 100%; - max-width: 880px; -` -const WarningIcon = styled(AlertOctagon)` - margin: 0 8px 0 0; -` -const TitleRow = styled.div` - align-items: center; - display: flex; - flex-direction: row; - justify-content: flex-start; - margin: 0; - font-size: 20px; - font-weight: 600; - line-height: 25px; -` -const Body = styled.div` - font-size: 12px; - line-height: 15px; - margin: 8px 0 0 0; -` -const ReadMoreLink = styled(ExternalLink)` - color: black; - text-decoration: underline; -` - -export default function OptimismDowntimeWarning() { - const { chainId } = useActiveWeb3React() - if (!chainId || ![SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)) { - return null - } - - return ( - - - - Optimism Planned Downtime - - - - Optimism expects planned downtime in the near future. Unplanned downtime may also occur. While the network is - down, fees will not be generated and you will be unable to remove liquidity.{' '} - - Read more. - - - - - ) -} diff --git a/src/components/Popover/index.tsx b/src/components/Popover/index.tsx index 20cd620d90..7effb0012d 100644 --- a/src/components/Popover/index.tsx +++ b/src/components/Popover/index.tsx @@ -1,11 +1,10 @@ import { Options, Placement } from '@popperjs/core' import Portal from '@reach/portal' +import useInterval from 'lib/hooks/useInterval' import React, { useCallback, useMemo, useState } from 'react' import { usePopper } from 'react-popper' import styled from 'styled-components/macro' -import useInterval from '../../hooks/useInterval' - const PopoverContainer = styled.div<{ show: boolean }>` z-index: 9999; visibility: ${(props) => (props.show ? 'visible' : 'hidden')}; diff --git a/src/components/Popups/ClaimPopup.tsx b/src/components/Popups/ClaimPopup.tsx index dffda549f0..958b11f7cf 100644 --- a/src/components/Popups/ClaimPopup.tsx +++ b/src/components/Popups/ClaimPopup.tsx @@ -1,13 +1,12 @@ import { Trans } from '@lingui/macro' import { CurrencyAmount, Token } from '@uniswap/sdk-core' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useCallback, useEffect } from 'react' import { Heart, X } from 'react-feather' import ReactGA from 'react-ga' import styled, { keyframes } from 'styled-components/macro' import tokenLogo from '../../assets/images/token-logo.png' -import { ButtonPrimary } from '../../components/Button' -import { useActiveWeb3React } from '../../hooks/web3' import { useModalOpen, useShowClaimPopup, @@ -16,7 +15,8 @@ import { } from '../../state/application/hooks' import { ApplicationModal } from '../../state/application/reducer' import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/claim/hooks' -import { TYPE } from 'theme' +import { ThemedText } from 'theme' +import { ButtonPrimary } from '../Button' import { AutoColumn } from '../Column' import { CardBGImage, CardNoise } from '../earn/styled' @@ -98,10 +98,10 @@ export default function ClaimPopup() { {' '} - + {unclaimedAmount?.toFixed(0, { groupSeparator: ',' } ?? '-')} UNI - - + + 🎉 {' '} @@ -109,12 +109,12 @@ export default function ClaimPopup() { 🎉 - - + + Thanks for being part of the Uniswap community - + diff --git a/src/components/Popups/FailedNetworkSwitchPopup.tsx b/src/components/Popups/FailedNetworkSwitchPopup.tsx new file mode 100644 index 0000000000..91d94a2812 --- /dev/null +++ b/src/components/Popups/FailedNetworkSwitchPopup.tsx @@ -0,0 +1,35 @@ +import { Trans } from '@lingui/macro' +import { CHAIN_INFO } 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 { ThemedText } from '../../theme' +import { AutoColumn } from '../Column' +import { AutoRow } from '../Row' + +const RowNoFlex = styled(AutoRow)` + flex-wrap: nowrap; +` + +export default function FailedNetworkSwitchPopup({ chainId }: { chainId: SupportedChainId }) { + const chainInfo = CHAIN_INFO[chainId] + const theme = useContext(ThemeContext) + + return ( + +
+ +
+ + + + Failed to switch networks from the Uniswap Interface. In order to use Uniswap on {chainInfo.label}, you must + change the network in your wallet. + + + +
+ ) +} diff --git a/src/components/Popups/PopupItem.tsx b/src/components/Popups/PopupItem.tsx index 3d64df72b8..5403786c32 100644 --- a/src/components/Popups/PopupItem.tsx +++ b/src/components/Popups/PopupItem.tsx @@ -5,7 +5,8 @@ import { useSpring } from 'react-spring/web' import styled, { ThemeContext } from 'styled-components/macro' import { useRemovePopup } from '../../state/application/hooks' -import { PopupContent } from 'state/application/reducer' +import { PopupContent } from '@src/state/application/reducer' +import FailedNetworkSwitchPopup from './FailedNetworkSwitchPopup' import TransactionPopup from './TransactionPopup' export const StyledClose = styled(X)` @@ -77,6 +78,8 @@ export default function PopupItem({ txn: { hash }, } = content popupContent = + } else if ('failedSwitchNetwork' in content) { + popupContent = } const faderStyle = useSpring({ diff --git a/src/components/Popups/SurveyPopup.tsx b/src/components/Popups/SurveyPopup.tsx new file mode 100644 index 0000000000..15a88aaee6 --- /dev/null +++ b/src/components/Popups/SurveyPopup.tsx @@ -0,0 +1,106 @@ +import { Trans } from '@lingui/macro' +import { AutoColumn } from 'components/Column' +import { RowFixed } from 'components/Row' +import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp' +import { useEffect } from 'react' +import { MessageCircle, X } from 'react-feather' +import ReactGA from 'react-ga' +import { useShowSurveyPopup } from 'state/user/hooks' +import styled from 'styled-components/macro' +import { ExternalLink, ThemedText, Z_INDEX } from 'theme' + +import BGImage from '../../assets/images/survey-orb.svg' +import useTheme from '../../hooks/useTheme' + +const Wrapper = styled(AutoColumn)` + background: #edeef2; + position: relative; + border-radius: 12px; + padding: 18px; + max-width: 360px; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); + color: ${({ theme }) => theme.text1}; + overflow: hidden; + + ${({ theme }) => theme.mediaWidth.upToSmall` + max-width: 100%; + `} +` + +const BGOrb = styled.img` + position: absolute; + right: -64px; + top: -64px; + width: 180px; + z-index: ${Z_INDEX.sticky}; +` + +const WrappedCloseIcon = styled(X)` + position: absolute; + top: 10px; + right: 10px; + width: 20px; + height: 20px; + stroke: #7c7c80; + z-index: ${Z_INDEX.fixed}; + :hover { + cursor: pointer; + opacity: 0.8; + } +` + +const END_TIMESTAMP = 1642272346 // Jan 15th + +export default function SurveyPopup() { + const theme = useTheme() + const [showPopup, setShowSurveyPopup] = useShowSurveyPopup() + + // show popup to 1% of users + useEffect(() => { + // has not visited page during A/B testing if undefined + if (showPopup === undefined) { + if (Math.random() < 0.01) { + setShowSurveyPopup(true) + // log a case of succesful view + ReactGA.event({ + category: 'Survey', + action: 'Saw Survey', + }) + } + } + }, [setShowSurveyPopup, showPopup]) + + // limit survey to 24 hours based on timestamps + const timestamp = useCurrentBlockTimestamp() + const durationOver = timestamp ? timestamp.toNumber() > END_TIMESTAMP : false + + return ( + <> + {!showPopup || durationOver ? null : ( + + { + ReactGA.event({ + category: 'Survey', + action: 'Clicked Survey Link', + }) + setShowSurveyPopup(false) + }} + /> + + + + + + Tell us what you think ↗ + + + + + Take a 10 minute survey to help us improve your experience in the Uniswap app. + + + )} + + ) +} diff --git a/src/components/Popups/TransactionPopup.tsx b/src/components/Popups/TransactionPopup.tsx index 45ca2c6104..d33b20b5e7 100644 --- a/src/components/Popups/TransactionPopup.tsx +++ b/src/components/Popups/TransactionPopup.tsx @@ -1,11 +1,11 @@ +import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useContext } from 'react' import { AlertCircle, CheckCircle } from 'react-feather' import styled, { ThemeContext } from 'styled-components/macro' -import { useActiveWeb3React } from '../../hooks/web3' import { useTransaction } from '../../state/transactions/hooks' -import { TYPE } from '../../theme' -import { ExternalLink } from '../../theme/components' +import { ThemedText } from '../../theme' +import { ExternalLink } from '../../theme' import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink' import { TransactionSummary } from '../AccountDetails/TransactionSummary' import { AutoColumn } from '../Column' @@ -30,9 +30,9 @@ export default function TransactionPopup({ hash }: { hash: string }) { {success ? : }
- + - + {chainId && ( View on Explorer diff --git a/src/components/Popups/index.tsx b/src/components/Popups/index.tsx index d4050e3479..33a2fec12b 100644 --- a/src/components/Popups/index.tsx +++ b/src/components/Popups/index.tsx @@ -1,5 +1,5 @@ import { SupportedChainId } from 'constants/chains' -import { useActiveWeb3React } from 'hooks/web3' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import styled from 'styled-components/macro' import { MEDIA_WIDTHS } from 'theme' diff --git a/src/components/PositionCard/V2.tsx b/src/components/PositionCard/V2.tsx index faaf5faad3..f8dfea56e4 100644 --- a/src/components/PositionCard/V2.tsx +++ b/src/components/PositionCard/V2.tsx @@ -1,6 +1,7 @@ import { Trans } from '@lingui/macro' import { CurrencyAmount, Percent, Token } from '@uniswap/sdk-core' import { Pair } from '@uniswap/v2-sdk' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import JSBI from 'jsbi' import { transparentize } from 'polished' import { useState } from 'react' @@ -12,7 +13,6 @@ import styled from 'styled-components/macro' import { BIG_INT_ZERO } from '../../constants/misc' import { useColor } from '../../hooks/useColor' import { useTotalSupply } from '../../hooks/useTotalSupply' -import { useActiveWeb3React } from '../../hooks/web3' import { useTokenBalance } from '../../state/wallet/hooks' import { currencyId } from '../../utils/currencyId' import { unwrappedToken } from '../../utils/unwrappedToken' diff --git a/src/components/PositionCard/index.tsx b/src/components/PositionCard/index.tsx index c6822f6e12..e3edbdc8fc 100644 --- a/src/components/PositionCard/index.tsx +++ b/src/components/PositionCard/index.tsx @@ -1,6 +1,7 @@ import { Trans } from '@lingui/macro' import { CurrencyAmount, Percent, Token } from '@uniswap/sdk-core' import { Pair } from '@uniswap/v2-sdk' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import JSBI from 'jsbi' import { transparentize } from 'polished' import { useState } from 'react' @@ -12,9 +13,8 @@ import styled from 'styled-components/macro' import { BIG_INT_ZERO } from '../../constants/misc' import { useColor } from '../../hooks/useColor' import { useTotalSupply } from '../../hooks/useTotalSupply' -import { useActiveWeb3React } from '../../hooks/web3' import { useTokenBalance } from '../../state/wallet/hooks' -import { ExternalLink, TYPE } from 'theme' +import { ExternalLink, ThemedText } from 'theme' import { currencyId } from '../../utils/currencyId' import { unwrappedToken } from '../../utils/unwrappedToken' import { ButtonEmpty, ButtonPrimary, ButtonSecondary } from '../Button' @@ -142,7 +142,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos ) : ( - + ⭐️ {' '} @@ -150,7 +150,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos By adding liquidity you'll earn 0.3% of all trades on this pair proportional to your share of the pool. Fees are added to the pool, accrue in real time and can be claimed by withdrawing your liquidity. {' '} - + )} diff --git a/src/components/PositionList/index.tsx b/src/components/PositionList/index.tsx index ebb2400275..82639b1d5e 100644 --- a/src/components/PositionList/index.tsx +++ b/src/components/PositionList/index.tsx @@ -1,4 +1,5 @@ import { Trans } from '@lingui/macro' +import { ButtonText } from 'components/Button' import PositionListItem from 'components/PositionListItem' import React from 'react' import styled from 'styled-components/macro' @@ -14,9 +15,7 @@ const DesktopHeader = styled.div` @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) { align-items: center; display: flex; - - display: grid; - grid-template-columns: 1fr 1fr; + justify-content: space-between; & > div:last-child { text-align: right; margin-right: 12px; @@ -36,9 +35,15 @@ const MobileHeader = styled.div` export type PositionListProps = React.PropsWithChildren<{ positions: PositionDetails[] + setUserHideClosedPositions: any + userHideClosedPositions: boolean }> -export default function PositionList({ positions }: PositionListProps) { +export default function PositionList({ + positions, + setUserHideClosedPositions, + userHideClosedPositions, +}: PositionListProps) { return ( <> @@ -46,9 +51,9 @@ export default function PositionList({ positions }: PositionListProps) { Your positions {positions && ' (' + positions.length + ')'}
-
- Status -
+ setUserHideClosedPositions(!userHideClosedPositions)}> + Hide closed positions + Your positions diff --git a/src/components/PositionListItem/index.tsx b/src/components/PositionListItem/index.tsx index da04b47d4d..d35e71b915 100644 --- a/src/components/PositionListItem/index.tsx +++ b/src/components/PositionListItem/index.tsx @@ -19,7 +19,7 @@ import { PositionDetails } from 'types/position' import { formatTickPrice } from 'utils/formatTickPrice' import { unwrappedToken } from 'utils/unwrappedToken' -import { DAI, USDC, USDT, WBTC, WETH9_EXTENDED } from '../../constants/tokens' +import { DAI, USDC_MAINNET, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens' const LinkRow = styled(Link)` align-items: center; @@ -145,7 +145,7 @@ export function getPriceOrderingFromPositionForUI(position?: Position): { const token1 = position.amount1.currency // if token0 is a dollar-stable asset, set it as the quote token - const stables = [DAI, USDC, USDT] + const stables = [DAI, USDC_MAINNET, USDT] if (stables.some((stable) => stable.equals(token0))) { return { priceLower: position.token0PriceUpper.invert(), @@ -156,7 +156,7 @@ export function getPriceOrderingFromPositionForUI(position?: Position): { } // if token1 is an ETH-/BTC-stable asset, set it as the base token - const bases = [...Object.values(WETH9_EXTENDED), WBTC] + const bases = [...Object.values(WRAPPED_NATIVE_CURRENCY), WBTC] if (bases.some((base) => base.equals(token1))) { return { priceLower: position.token0PriceUpper.invert(), diff --git a/src/components/PositionPreview/index.tsx b/src/components/PositionPreview/index.tsx index f45bfe576c..23b6778182 100644 --- a/src/components/PositionPreview/index.tsx +++ b/src/components/PositionPreview/index.tsx @@ -13,7 +13,7 @@ import JSBI from 'jsbi' import { ReactNode, useCallback, useContext, useState } from 'react' import { Bound } from 'state/mint/v3/actions' import { ThemeContext } from 'styled-components/macro' -import { TYPE } from 'theme' +import { ThemedText } from 'theme' import { formatTickPrice } from 'utils/formatTickPrice' import { unwrappedToken } from 'utils/unwrappedToken' @@ -70,9 +70,9 @@ export const PositionPreview = ({ size={24} margin={true} /> - + {currency0?.symbol} / {currency1?.symbol} - +
@@ -82,36 +82,36 @@ export const PositionPreview = ({ - {currency0?.symbol} + {currency0?.symbol} - {position.amount0.toSignificant(4)} + {position.amount0.toSignificant(4)} - {currency1?.symbol} + {currency1?.symbol} - {position.amount1.toSignificant(4)} + {position.amount1.toSignificant(4)} - + Fee Tier - - + + {position?.pool?.fee / 10000}% - +
- {title ? {title} :
} + {title ? {title} :
} - + Min Price - - {`${formatTickPrice( + + {`${formatTickPrice( priceLower, ticksAtLimit, Bound.LOWER - )}`} - + )}`} + {quoteCurrency.symbol} per {baseCurrency.symbol} - - + + Your position will be 100% composed of {baseCurrency?.symbol} at this price - + - + Max Price - - {`${formatTickPrice( + + {`${formatTickPrice( priceUpper, ticksAtLimit, Bound.UPPER - )}`} - + )}`} + {quoteCurrency.symbol} per {baseCurrency.symbol} - - + + Your position will be 100% composed of {quoteCurrency?.symbol} at this price - + - + Current price - - {`${price.toSignificant(5)} `} - + + {`${price.toSignificant(5)} `} + {quoteCurrency.symbol} per {baseCurrency.symbol} - + diff --git a/src/components/PrivacyPolicy/index.tsx b/src/components/PrivacyPolicy/index.tsx index 9e4690fe07..e3e909e3e8 100644 --- a/src/components/PrivacyPolicy/index.tsx +++ b/src/components/PrivacyPolicy/index.tsx @@ -5,7 +5,7 @@ import { useEffect, useRef } from 'react' import { ArrowDown, Info, X } from 'react-feather' import ReactGA from 'react-ga' import styled from 'styled-components/macro' -import { ExternalLink, TYPE } from 'theme' +import { ExternalLink, ThemedText } from 'theme' import { isMobile } from 'utils/userAgent' import { useModalOpen, useTogglePrivacyPolicy } from '../../state/application/hooks' @@ -58,9 +58,15 @@ const EXTERNAL_APIS = [ { name: 'TRM Labs', description: ( - - The app securely collects your wallet address and shares it with TRM Labs Inc. for risk and compliance reasons. - + <> + + The app securely collects your wallet address and shares it with TRM Labs Inc. for risk and compliance + reasons. + {' '} + + Learn more + + ), }, { @@ -91,9 +97,9 @@ export function PrivacyPolicyModal() { toggle()}> - + Legal & Privacy - + toggle()}> @@ -122,9 +128,9 @@ export function PrivacyPolicy() { - + Uniswap Labs' Terms of Service - + @@ -135,29 +141,29 @@ export function PrivacyPolicy() { - + Protocol Disclaimer - + - + This app uses the following third-party APIs: - + {EXTERNAL_APIS.map(({ name, description }, i) => ( - + {name} - + - {description} + {description} ))} diff --git a/src/components/ProgressSteps/index.tsx b/src/components/ProgressSteps/index.tsx index c5cba0035e..33f6bcbb96 100644 --- a/src/components/ProgressSteps/index.tsx +++ b/src/components/ProgressSteps/index.tsx @@ -2,7 +2,7 @@ import { useContext } from 'react' import styled from 'styled-components/macro' import { ThemeContext } from 'styled-components/macro' -import { TYPE } from '../../theme' +import { ThemedText } from '../../theme' import { AutoColumn } from '../Column' const Wrapper = styled(AutoColumn)` @@ -65,7 +65,7 @@ export default function ProgressCircles({ steps, disabled = false, ...rest }: Pr {step ? '✓' : i + 1 + '.'} - | + | ) })} diff --git a/src/components/RangeSelector/PresetsButtons.tsx b/src/components/RangeSelector/PresetsButtons.tsx index b4092b9669..0b4e0e0ab5 100644 --- a/src/components/RangeSelector/PresetsButtons.tsx +++ b/src/components/RangeSelector/PresetsButtons.tsx @@ -4,7 +4,7 @@ import { AutoRow } from 'components/Row' import React from 'react' import ReactGA from 'react-ga' import styled from 'styled-components/macro' -import { TYPE } from 'theme' +import { ThemedText } from 'theme' const Button = styled(ButtonOutlined).attrs(() => ({ padding: '8px', @@ -26,9 +26,9 @@ export default function PresetsButtons({ setFullRange }: { setFullRange: () => v }) }} > - + Full Range - + ) diff --git a/src/components/RoutingDiagram/RoutingDiagram.test.tsx b/src/components/RoutingDiagram/RoutingDiagram.test.tsx index 6df0a929a6..c3666b21f0 100644 --- a/src/components/RoutingDiagram/RoutingDiagram.test.tsx +++ b/src/components/RoutingDiagram/RoutingDiagram.test.tsx @@ -1,26 +1,32 @@ /** * @jest-environment ./custom-test-env.js */ - +import { Protocol } from '@uniswap/router-sdk' import { Currency, Percent } from '@uniswap/sdk-core' import { FeeAmount } from '@uniswap/v3-sdk' -import { DAI, USDC, WBTC } from 'constants/tokens' +import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens' import { render } from 'test-utils' -import RoutingDiagram, { RoutingDiagramEntry } from './RoutingDiagram' +import RoutingDiagram from './RoutingDiagram' +import { RoutingDiagramEntry } from 'lib/components/Swap/RoutingDiagram/utils' const percent = (strings: TemplateStringsArray) => new Percent(parseInt(strings[0]), 100) -const singleRoute: RoutingDiagramEntry = { percent: percent`100`, path: [[USDC, DAI, FeeAmount.LOW]] } +const singleRoute: RoutingDiagramEntry = { + percent: percent`100`, + path: [[USDC_MAINNET, DAI, FeeAmount.LOW]], + protocol: Protocol.V3, +} const multiRoute: RoutingDiagramEntry[] = [ - { percent: percent`75`, path: [[USDC, DAI, FeeAmount.LOW]] }, + { percent: percent`75`, path: [[USDC_MAINNET, DAI, FeeAmount.LOWEST]], protocol: Protocol.V2 }, { percent: percent`25`, path: [ - [USDC, WBTC, FeeAmount.MEDIUM], + [USDC_MAINNET, WBTC, FeeAmount.MEDIUM], [WBTC, DAI, FeeAmount.HIGH], ], + protocol: Protocol.V3, }, ] @@ -44,17 +50,17 @@ jest.mock('hooks/useTokenInfoFromActiveList', () => ({ useTokenInfoFromActiveList: (currency: Currency) => currency, })) -it('renders when no routes are provided', () => { - const { asFragment } = render() +it.skip('renders when no routes are provided', () => { + const { asFragment } = render() expect(asFragment()).toMatchSnapshot() }) -it('renders single route', () => { - const { asFragment } = render() +it.skip('renders single route', () => { + const { asFragment } = render() expect(asFragment()).toMatchSnapshot() }) -it('renders multi route', () => { - const { asFragment } = render() +it.skip('renders multi route', () => { + const { asFragment } = render() expect(asFragment()).toMatchSnapshot() }) diff --git a/src/components/RoutingDiagram/RoutingDiagram.tsx b/src/components/RoutingDiagram/RoutingDiagram.tsx index 077a827b2f..f1b2150aac 100644 --- a/src/components/RoutingDiagram/RoutingDiagram.tsx +++ b/src/components/RoutingDiagram/RoutingDiagram.tsx @@ -1,30 +1,26 @@ -import { Currency, Percent } from '@uniswap/sdk-core' +import { Trans } from '@lingui/macro' +import { Currency } from '@uniswap/sdk-core' import { FeeAmount } from '@uniswap/v3-sdk' import Badge from 'components/Badge' import CurrencyLogo from 'components/CurrencyLogo' import DoubleCurrencyLogo from 'components/DoubleLogo' import Row, { AutoRow } from 'components/Row' import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList' +import { RoutingDiagramEntry } from 'lib/components/Swap/RoutingDiagram/utils' import { Box } from 'rebass' import styled from 'styled-components/macro' -import { TYPE } from 'theme' +import { ThemedText, Z_INDEX } from 'theme' import { ReactComponent as DotLine } from '../../assets/svg/dot_line.svg' - -export interface RoutingDiagramEntry { - percent: Percent - path: [Currency, Currency, FeeAmount][] -} +import { MouseoverTooltip } from '../Tooltip' const Wrapper = styled(Box)` align-items: center; - background-color: ${({ theme }) => theme.bg0}; - width: 400px; + width: 100%; ` const RouteContainerRow = styled(Row)` display: grid; - grid-gap: 8px; grid-template-columns: 24px 1fr 24px; ` @@ -38,7 +34,7 @@ const RouteRow = styled(Row)` const PoolBadge = styled(Badge)` display: flex; - padding: 0.25rem 0.5rem; + padding: 4px 4px; ` const DottedLine = styled.div` @@ -58,7 +54,27 @@ const DotColor = styled(DotLine)` const OpaqueBadge = styled(Badge)` background-color: ${({ theme }) => theme.bg2}; - z-index: 2; + border-radius: 8px; + display: grid; + font-size: 12px; + grid-gap: 4px; + grid-auto-flow: column; + justify-content: start; + padding: 4px 6px 4px 4px; + z-index: ${Z_INDEX.sticky}; +` + +const ProtocolBadge = styled(Badge)` + background-color: ${({ theme }) => theme.bg3}; + border-radius: 4px; + color: ${({ theme }) => theme.text2}; + font-size: 10px; + padding: 2px 4px; + z-index: ${Z_INDEX.sticky + 1}; +` + +const BadgeText = styled(ThemedText.Small)` + word-break: normal; ` export default function RoutingDiagram({ @@ -75,29 +91,31 @@ export default function RoutingDiagram({ return ( - {routes.map(({ percent, path }, index) => ( + {routes.map((entry, index) => ( - - - + + + ))} ) } -function Route({ percent, path }: { percent: RoutingDiagramEntry['percent']; path: RoutingDiagramEntry['path'] }) { +function Route({ entry: { percent, path, protocol } }: { entry: RoutingDiagramEntry }) { return ( - + + {protocol.toUpperCase()} + + {percent.toSignificant(2)}% - + - {path.map(([currency0, currency1, feeAmount], index) => ( @@ -111,12 +129,17 @@ function Pool({ currency0, currency1, feeAmount }: { currency0: Currency; curren const tokenInfo0 = useTokenInfoFromActiveList(currency0) const tokenInfo1 = useTokenInfoFromActiveList(currency1) + // TODO - link pool icon to info.uniswap.org via query params return ( - - - - - {feeAmount / 10000}% - + {tokenInfo0?.symbol + '/' + tokenInfo1?.symbol + ' ' + feeAmount / 10000}% pool} + > + + + + + {feeAmount / 10000}% + + ) } diff --git a/src/components/RoutingDiagram/__snapshots__/RoutingDiagram.test.tsx.snap b/src/components/RoutingDiagram/__snapshots__/RoutingDiagram.test.tsx.snap index 781bb049e6..81e81dce55 100644 --- a/src/components/RoutingDiagram/__snapshots__/RoutingDiagram.test.tsx.snap +++ b/src/components/RoutingDiagram/__snapshots__/RoutingDiagram.test.tsx.snap @@ -2,244 +2,96 @@ exports[`renders multi route 1`] = ` - .c1 { - box-sizing: border-box; - margin: 0; - min-width: 0; -} - -.c9 { - box-sizing: border-box; - margin: 0; - min-width: 0; - width: 100%; -} - -.c2 { - width: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - padding: 0; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: start; - -webkit-justify-content: flex-start; - -ms-flex-pack: start; - justify-content: flex-start; -} - -.c10 { - -webkit-flex-wrap: wrap; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - margin: -1px; -} - -.c10 > * { - margin: 1px !important; -} - -.c7 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - background-color: #ffffff; - border: unset; - border-radius: 0.5rem; - color: #000; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - padding: 4px 6px; - -webkit-box-pack: center; - -webkit-justify-content: center; - -ms-flex-pack: center; - justify-content: center; - font-weight: 500; -} - -.c0 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - background-color: #FFF; - width: 400px; -} - -.c3 { - display: grid; - grid-gap: 8px; - grid-template-columns: 24px 1fr 24px; -} - -.c4 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-pack: center; - -webkit-justify-content: center; - -ms-flex-pack: center; - justify-content: center; - padding: 0.1rem 0.5rem; - position: relative; -} - -.c11 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - padding: 0.25rem 0.5rem; -} - -.c5 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: absolute; - width: calc(100%); - z-index: 1; - opacity: 0.5; -} - -.c6 path { - stroke: #ffffff; -} - -.c8 { - background-color: #ffffff; - z-index: 2; -} - -
CurrencyLogo currency=USDC
dot_line.svg
+
+ V2 +
+
+
75%
-
-
- DoubleCurrencyLogo currency0=DAI currency1=USDC -
-
- 0.05% -
-
+ Popover
CurrencyLogo currency=DAI
CurrencyLogo currency=USDC
dot_line.svg
-
- 25% -
-
-
- DoubleCurrencyLogo currency0=WBTC currency1=USDC -
-
- 0.3% + V3
-
- DoubleCurrencyLogo currency0=DAI currency1=WBTC -
-
- 1% -
+ 25%
+
+ PopoverPopover +
CurrencyLogo currency=DAI
@@ -249,180 +101,50 @@ exports[`renders multi route 1`] = ` exports[`renders single route 1`] = ` - .c1 { - box-sizing: border-box; - margin: 0; - min-width: 0; -} - -.c9 { - box-sizing: border-box; - margin: 0; - min-width: 0; - width: 100%; -} - -.c2 { - width: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - padding: 0; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: start; - -webkit-justify-content: flex-start; - -ms-flex-pack: start; - justify-content: flex-start; -} - -.c10 { - -webkit-flex-wrap: wrap; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - margin: -1px; -} - -.c10 > * { - margin: 1px !important; -} - -.c7 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - background-color: #ffffff; - border: unset; - border-radius: 0.5rem; - color: #000; - display: -webkit-inline-box; - display: -webkit-inline-flex; - display: -ms-inline-flexbox; - display: inline-flex; - padding: 4px 6px; - -webkit-box-pack: center; - -webkit-justify-content: center; - -ms-flex-pack: center; - justify-content: center; - font-weight: 500; -} - -.c0 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - background-color: #FFF; - width: 400px; -} - -.c3 { - display: grid; - grid-gap: 8px; - grid-template-columns: 24px 1fr 24px; -} - -.c4 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-pack: center; - -webkit-justify-content: center; - -ms-flex-pack: center; - justify-content: center; - padding: 0.1rem 0.5rem; - position: relative; -} - -.c11 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - padding: 0.25rem 0.5rem; -} - -.c5 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - position: absolute; - width: calc(100%); - z-index: 1; - opacity: 0.5; -} - -.c6 path { - stroke: #ffffff; -} - -.c8 { - background-color: #ffffff; - z-index: 2; -} - -
CurrencyLogo currency=USDC
dot_line.svg
+
+ V3 +
+
+
100%
-
-
- DoubleCurrencyLogo currency0=DAI currency1=USDC -
-
- 0.05% -
-
+ Popover
CurrencyLogo currency=DAI @@ -433,17 +155,8 @@ exports[`renders single route 1`] = ` exports[`renders when no routes are provided 1`] = ` - .c0 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - background-color: #FFF; - width: 400px; -} - -
`; diff --git a/src/components/SearchModal/BlockedToken.tsx b/src/components/SearchModal/BlockedToken.tsx index ce98d4f05d..3d5f25cabb 100644 --- a/src/components/SearchModal/BlockedToken.tsx +++ b/src/components/SearchModal/BlockedToken.tsx @@ -3,7 +3,7 @@ import { Token } from '@uniswap/sdk-core' import { ButtonPrimary } from 'components/Button' import { AlertCircle, ArrowLeft } from 'react-feather' import styled from 'styled-components/macro' -import { CloseIcon, TYPE } from 'theme' +import { CloseIcon, ThemedText } from 'theme' import TokenImportCard from './TokenImportCard' @@ -22,7 +22,7 @@ const Button = styled(ButtonPrimary)` const Content = styled.div` padding: 1em; ` -const Copy = styled(TYPE.body)` +const Copy = styled(ThemedText.Body)` text-align: center; margin: 0 2em 1em !important; font-weight: 400; @@ -51,9 +51,9 @@ const BlockedToken = ({ onBack, onDismiss, blockedTokens }: BlockedTokenProps) =
{onBack ? :
} - + Token not supported - + {onDismiss ? :
}
diff --git a/src/components/SearchModal/CommonBases.tsx b/src/components/SearchModal/CommonBases.tsx index 00e759aa07..208f321595 100644 --- a/src/components/SearchModal/CommonBases.tsx +++ b/src/components/SearchModal/CommonBases.tsx @@ -1,14 +1,12 @@ -import { Trans } from '@lingui/macro' import { Currency } from '@uniswap/sdk-core' -import { AutoColumn } from '../Column' +import { AutoColumn } from 'components/Column' import CurrencyLogo from 'components/CurrencyLogo' -import QuestionHelper from '../QuestionHelper' -import { AutoRow } from '../Row' +import { AutoRow } from 'components/Row' import { COMMON_BASES } from 'constants/routing' import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList' import { Text } from 'rebass' import styled from 'styled-components/macro' -import { currencyId } from '../../utils/currencyId' +import { currencyId } from 'utils/currencyId' const MobileWrapper = styled(AutoColumn)` ${({ theme }) => theme.mediaWidth.upToSmall` @@ -46,12 +44,6 @@ export default function CommonBases({ return bases.length > 0 ? ( - - - Common bases - - These tokens are commonly paired with other tokens.} /> - {bases.map((currency: Currency) => { const isSelected = selectedCurrency?.equals(currency) @@ -74,7 +66,7 @@ export default function CommonBases({ } /** helper component to retrieve a base currency from the active token lists */ -function CurrencyLogoFromList({ currency }: { currency: Currency }) { +export function CurrencyLogoFromList({ currency }: { currency: Currency }) { const token = useTokenInfoFromActiveList(currency) return diff --git a/src/components/SearchModal/CurrencyList.tsx b/src/components/SearchModal/CurrencyList.tsx index 8ef8f4e073..4b3e7b2986 100644 --- a/src/components/SearchModal/CurrencyList.tsx +++ b/src/components/SearchModal/CurrencyList.tsx @@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro' import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' import { LightGreyCard } from 'components/Card' import QuestionHelper from 'components/QuestionHelper' +import useActiveWeb3React from 'hooks/useActiveWeb3React' import useTheme from 'hooks/useTheme' import { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react' import { FixedSizeList } from 'react-window' @@ -10,11 +11,10 @@ import styled from 'styled-components/macro' import TokenListLogo from '../../assets/svg/tokenlist.svg' import { useIsUserAddedToken } from '../../hooks/Tokens' -import { useActiveWeb3React } from '../../hooks/web3' import { useCombinedActiveList } from '../../state/lists/hooks' import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo' import { useCurrencyBalance } from '../../state/wallet/hooks' -import { TYPE } from '../../theme' +import { ThemedText } from '../../theme' import { isTokenOnList } from '../../utils' import Column from '../Column' import CurrencyLogo from 'components/CurrencyLogo' @@ -135,13 +135,13 @@ function CurrencyRow({ {currency.symbol} - + {!currency.isNative && !isOnSelectedList && customAdded ? ( {currency.name} • Added by user ) : ( currency.name )} - + {showCurrencyAmount && ( @@ -167,9 +167,9 @@ function BreakLineComponent({ style }: { style: CSSProperties }) { - + Expanded results from inactive Token Lists - + ('') const debouncedQuery = useDebounce(searchQuery, 200) - const [invertSearchOrder] = useState(false) - const allTokens = useAllTokens() // if they input an address, use it @@ -100,27 +99,28 @@ export function CurrencySearch({ } }, [isAddressSearch]) - const tokenComparator = useTokenComparator(invertSearchOrder) - const filteredTokens: Token[] = useMemo(() => { - return filterTokens(Object.values(allTokens), debouncedQuery) + return Object.values(allTokens).filter(getTokenFilter(debouncedQuery)) }, [allTokens, debouncedQuery]) + const balances = useAllTokenBalances() const sortedTokens: Token[] = useMemo(() => { - return filteredTokens.sort(tokenComparator) - }, [filteredTokens, tokenComparator]) + return filteredTokens.sort(tokenComparator.bind(null, balances)) + }, [balances, filteredTokens]) - const filteredSortedTokens = useSortedTokensByQuery(sortedTokens, debouncedQuery) + const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens) - const ether = useMemo(() => chainId && ExtendedEther.onChain(chainId), [chainId]) + const native = useNativeCurrency() const filteredSortedTokensWithETH: Currency[] = useMemo(() => { + if (!native) return filteredSortedTokens + const s = debouncedQuery.toLowerCase().trim() - if (s === '' || s === 'e' || s === 'et' || s === 'eth') { - return ether ? [ether, ...filteredSortedTokens] : filteredSortedTokens + if (native.symbol?.toLowerCase()?.indexOf(s) !== -1) { + return native ? [native, ...filteredSortedTokens] : filteredSortedTokens } return filteredSortedTokens - }, [debouncedQuery, ether, filteredSortedTokens]) + }, [debouncedQuery, native, filteredSortedTokens]) const handleCurrencySelect = useCallback( (currency: Currency) => { @@ -148,8 +148,8 @@ export function CurrencySearch({ (e: KeyboardEvent) => { if (e.key === 'Enter') { const s = debouncedQuery.toLowerCase().trim() - if (s === 'eth' && ether) { - handleCurrencySelect(ether) + if (s === native?.symbol?.toLowerCase()) { + handleCurrencySelect(native) } else if (filteredSortedTokensWithETH.length > 0) { if ( filteredSortedTokensWithETH[0].symbol?.toLowerCase() === debouncedQuery.trim().toLowerCase() || @@ -160,7 +160,7 @@ export function CurrencySearch({ } } }, - [debouncedQuery, ether, filteredSortedTokensWithETH, handleCurrencySelect] + [debouncedQuery, native, filteredSortedTokensWithETH, handleCurrencySelect] ) // menu ui @@ -224,9 +224,9 @@ export function CurrencySearch({
) : ( - + No results found. - + )}