Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ad13c96a8 | ||
|
|
4e99cc4d93 | ||
|
|
6d29815f59 | ||
|
|
4888fe23df | ||
|
|
ef9ecd9ce2 | ||
|
|
f5d0804c46 | ||
|
|
0bac257254 | ||
|
|
a77752ab83 | ||
|
|
bf31ca4f06 | ||
|
|
ed8afbd851 | ||
|
|
47b6a7c4d5 | ||
|
|
086fc65457 | ||
|
|
7df53f30a0 | ||
|
|
66497a0108 | ||
|
|
e0eb701bc0 | ||
|
|
36cb0668a3 | ||
|
|
810f42136e | ||
|
|
07b7d7f268 | ||
|
|
39b5bb37cd | ||
|
|
feed63b1b3 | ||
|
|
ee56382956 | ||
|
|
64e396d9e0 | ||
|
|
2ffc8a0bdf | ||
|
|
5ec9cdc5c4 | ||
|
|
4d85775d90 | ||
|
|
c1c59ca692 | ||
|
|
f29d97413e | ||
|
|
a078d94a38 | ||
|
|
c9c3329bc3 | ||
|
|
13d0b70fa8 | ||
|
|
b852e4e64a | ||
|
|
55bd3555be | ||
|
|
972a65066c | ||
|
|
39a212f762 | ||
|
|
c362f4fe39 | ||
|
|
271ef580e1 | ||
|
|
81ced4cb8b | ||
|
|
ab214a8133 | ||
|
|
1b2d86ae3a | ||
|
|
40cac44e07 | ||
|
|
4e6d28cff4 | ||
|
|
709fad0804 | ||
|
|
573f4c873a | ||
|
|
d300db669f | ||
|
|
fb8217ddea | ||
|
|
7b9a23d920 | ||
|
|
120ad935fa |
@@ -5,6 +5,14 @@ require('@uniswap/eslint-config/load')
|
||||
module.exports = {
|
||||
extends: '@uniswap/eslint-config/react',
|
||||
overrides: [
|
||||
{
|
||||
// Configuration/typings typically export objects/definitions that are used outside of the transpiled package
|
||||
// (eg not captured by the tsconfig). Because it's typical and not exceptional, this is turned off entirely.
|
||||
files: ['**/*.config.*', '**/*.d.ts'],
|
||||
rules: {
|
||||
'import/no-unused-modules': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
rules: {
|
||||
|
||||
53
.github/pull_request_template.md
vendored
53
.github/pull_request_template.md
vendored
@@ -1,22 +1,45 @@
|
||||
<!-- Your PR title must follow conventional commits: https://github.com/Uniswap/interface#pr-title -->
|
||||
|
||||
## Description
|
||||
|
||||
_[Summary of change, motivation, and context.]_
|
||||
|
||||
- _Link to JIRA ticket, slack thread, or relevant docs helpful for providing context to reviewers._
|
||||
|
||||
- _Note: Your PR title must follow conventions [outlined here](https://github.com/Uniswap/interface#contributions)._
|
||||
|
||||
## Screen Capture
|
||||
| Before | After |
|
||||
| ---------------- |-----------------|
|
||||
| _insert_before_ | _insert_after_ |
|
||||
<!-- Summary of change, including motivation and context. -->
|
||||
<!-- Use verb-driven language: "Fixes XYZ" instead of "This change fixes XYZ" -->
|
||||
|
||||
|
||||
## Test Plan
|
||||
#### Manual
|
||||
<!-- Delete inapplicable lines: -->
|
||||
_JIRA ticket:_
|
||||
_Slack thread:_
|
||||
_Relevant docs:_
|
||||
|
||||
_[Steps of how you are testing the change and ensuring no regression.]_
|
||||
|
||||
#### Automated
|
||||
<!-- Delete this section if your change does not affect UI. -->
|
||||
## Screen capture
|
||||
|
||||
| Before | After (Desktop) | After (Mobile) |
|
||||
| ------------ |---------------- | -------------- |
|
||||
| paste_before | past_after | paste_after |
|
||||
|
||||
|
||||
## Test plan
|
||||
|
||||
<!-- Delete this section if your change is not a bug fix. -->
|
||||
### Reproducing the error
|
||||
|
||||
<!-- Include steps to reproduce the bug. -->
|
||||
1.
|
||||
|
||||
### QA (ie manual testing)
|
||||
|
||||
<!-- Include steps to test the change, ensuring no regression. -->
|
||||
- [ ] N/A
|
||||
|
||||
|
||||
#### Devices
|
||||
<!-- If applicable, include different devices and screen sizes that may be affected, and how you've tested them. -->
|
||||
|
||||
|
||||
### Automated testing
|
||||
|
||||
<!-- If N/A, check and note so it is obvious to your reviewers and does not show up as an incomplete task. -->
|
||||
<!-- eg - [x] Unit test N/A -->
|
||||
- [ ] Unit test
|
||||
- [ ] Integration/E2E test
|
||||
|
||||
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@@ -15,6 +15,14 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn lint
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
- run: yarn prepare
|
||||
- run: yarn typecheck
|
||||
|
||||
deps-tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-env node */
|
||||
const { VanillaExtractPlugin } = require('@vanilla-extract/webpack-plugin')
|
||||
const { execSync } = require('child_process')
|
||||
const EsLintWebpackPlugin = require('eslint-webpack-plugin')
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const { DefinePlugin } = require('webpack')
|
||||
|
||||
const commitHash = require('child_process').execSync('git rev-parse HEAD')
|
||||
const isProduction = process.env.NODE_ENV === 'production'
|
||||
const commitHash = execSync('git rev-parse HEAD').toString().trim()
|
||||
|
||||
module.exports = {
|
||||
babel: {
|
||||
@@ -29,25 +33,40 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
webpack: {
|
||||
plugins: [
|
||||
new VanillaExtractPlugin({ identifiers: 'short' }),
|
||||
new DefinePlugin({
|
||||
'process.env.REACT_APP_GIT_COMMIT_HASH': JSON.stringify(commitHash.toString()),
|
||||
}),
|
||||
],
|
||||
plugins: [new VanillaExtractPlugin({ identifiers: 'short' })],
|
||||
configure: (webpackConfig) => {
|
||||
const instanceOfMiniCssExtractPlugin = webpackConfig.plugins.find(
|
||||
(plugin) => plugin instanceof MiniCssExtractPlugin
|
||||
)
|
||||
if (instanceOfMiniCssExtractPlugin !== undefined) instanceOfMiniCssExtractPlugin.options.ignoreOrder = true
|
||||
webpackConfig.plugins = webpackConfig.plugins
|
||||
.filter((plugin) => {
|
||||
// Type checking and linting are only necessary as part of development and testing.
|
||||
// Omit them from production builds, as they slow down the feedback loop.
|
||||
if (isProduction) {
|
||||
if (plugin instanceof ForkTsCheckerWebpackPlugin) return false
|
||||
if (plugin instanceof EsLintWebpackPlugin) return false
|
||||
}
|
||||
|
||||
// We're currently on Webpack 4.x that doesn't support the `exports` field in package.json.
|
||||
return true
|
||||
})
|
||||
.map((plugin) => {
|
||||
// Extend process.env with dynamic values (eg commit hash).
|
||||
// This will make dynamic values available to JavaScript only, not to interpolated HTML (ie index.html).
|
||||
if (plugin instanceof DefinePlugin) {
|
||||
Object.assign(plugin.definitions['process.env'], {
|
||||
REACT_APP_GIT_COMMIT_HASH: JSON.stringify(commitHash),
|
||||
})
|
||||
}
|
||||
|
||||
// CSS ordering is mitigated through scoping / naming conventions, so we can ignore order warnings.
|
||||
// See https://webpack.js.org/plugins/mini-css-extract-plugin/#remove-order-warnings.
|
||||
if (plugin instanceof MiniCssExtractPlugin) {
|
||||
plugin.options.ignoreOrder = true
|
||||
}
|
||||
|
||||
return plugin
|
||||
})
|
||||
|
||||
// We're currently on Webpack 4.x which doesn't support the `exports` field in package.json.
|
||||
// Instead, we need to manually map the import path to the correct exports path (eg dist or build folder).
|
||||
// See https://github.com/webpack/webpack/issues/9509.
|
||||
//
|
||||
// In case you need to add more modules, make sure to remap them to the correct path.
|
||||
//
|
||||
// Map @uniswap/conedison to its dist folder.
|
||||
// This is required because conedison uses * to redirect all imports to its dist.
|
||||
webpackConfig.resolve.alias['@uniswap/conedison'] = '@uniswap/conedison/dist'
|
||||
|
||||
return webpackConfig
|
||||
|
||||
@@ -6,6 +6,7 @@ export default defineConfig({
|
||||
videoUploadOnPasses: false,
|
||||
defaultCommandTimeout: 24000, // 2x average block time
|
||||
chromeWebSecurity: false,
|
||||
retries: { runMode: 2 },
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
codeCoverageTask(on, config)
|
||||
|
||||
@@ -26,7 +26,15 @@ describe('Landing Page', () => {
|
||||
|
||||
it('allows navigation to pool', () => {
|
||||
cy.viewport(2000, 1600)
|
||||
cy.visit('/swap')
|
||||
cy.get(getTestSelector('pool-nav-link')).first().click()
|
||||
cy.url().should('include', '/pools')
|
||||
})
|
||||
|
||||
it('allows navigation to pool on mobile', () => {
|
||||
cy.viewport('iphone-6')
|
||||
cy.visit('/swap')
|
||||
cy.get(getTestSelector('pool-nav-link')).last().click()
|
||||
cy.url().should('include', '/pools')
|
||||
})
|
||||
})
|
||||
|
||||
11
cypress/e2e/position.test.ts
Normal file
11
cypress/e2e/position.test.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
describe('Position', () => {
|
||||
it('shows an valid state on a supported network', () => {
|
||||
cy.visit('/pools/1')
|
||||
cy.contains('UNI / ETH')
|
||||
})
|
||||
|
||||
it('shows an invalid state on a supported network', () => {
|
||||
cy.visit('/pools/788893')
|
||||
cy.contains('To view a position, you must be connected to the network it belongs to.')
|
||||
})
|
||||
})
|
||||
@@ -77,4 +77,20 @@ describe('Wallet Dropdown', () => {
|
||||
cy.get(getTestSelector('theme-auto')).click()
|
||||
cy.get(getTestSelector('wallet-header')).should('have.css', 'color', 'rgb(119, 128, 160)')
|
||||
})
|
||||
|
||||
it('should dismiss the wallet bottom sheet when clicking buy crypto', () => {
|
||||
visit(false)
|
||||
cy.viewport('iphone-6')
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('wallet-buy-crypto')).click()
|
||||
cy.contains('Buy crypto').should('not.be.visible')
|
||||
})
|
||||
|
||||
it('should use a bottom sheet and dismiss when on a mobile screen size', () => {
|
||||
visit(true)
|
||||
cy.viewport('iphone-6')
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.root().click(15, 40)
|
||||
cy.get(getTestSelector('wallet-settings')).should('not.be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
14
package.json
14
package.json
@@ -17,16 +17,17 @@
|
||||
"i18n:compile": "yarn i18n:extract && lingui compile",
|
||||
"i18n:pseudo": "lingui extract --locale pseudo && lingui compile",
|
||||
"prepare": "yarn contracts:compile && yarn graphql:fetch && yarn graphql:generate && yarn i18n:compile",
|
||||
"postinstall": "patch-package",
|
||||
"start": "craco start",
|
||||
"build": "craco build",
|
||||
"serve": "serve build -l 3000",
|
||||
"deduplicate": "yarn-deduplicate --strategy=highest",
|
||||
"lint": "yarn eslint .",
|
||||
"lint": "yarn eslint --ignore-path .gitignore .",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "craco test --coverage",
|
||||
"test:size": "node scripts/test-size.js",
|
||||
"cypress:open": "cypress open --browser chrome --e2e",
|
||||
"cypress:run": "cypress run --browser chrome --e2e",
|
||||
"postinstall": "patch-package"
|
||||
"deduplicate": "yarn-deduplicate --strategy=highest"
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
@@ -37,8 +38,12 @@
|
||||
"src/lib/utils/**/*.ts*",
|
||||
"src/pages/**/*.ts*",
|
||||
"src/state/**/*.ts*",
|
||||
"src/tracing/**/*.ts*",
|
||||
"src/utils/**/*.ts*"
|
||||
],
|
||||
"coveragePathIgnorePatterns": [
|
||||
".snap"
|
||||
],
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"branches": 4,
|
||||
@@ -66,6 +71,7 @@
|
||||
"@lingui/cli": "^3.9.0",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.1",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@typechain/ethers-v5": "^7.0.0",
|
||||
"@types/array.prototype.flat": "^1.2.1",
|
||||
"@types/array.prototype.flatmap": "^1.2.2",
|
||||
@@ -136,7 +142,7 @@
|
||||
"@sentry/tracing": "^7.45.0",
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@uniswap/analytics": "^1.3.1",
|
||||
"@uniswap/analytics-events": "^2.8.0",
|
||||
"@uniswap/analytics-events": "^2.10.0",
|
||||
"@uniswap/conedison": "^1.4.0",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 558 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 306 KiB |
19
src/assets/svg/wallet_banner_phone_image.svg
Normal file
19
src/assets/svg/wallet_banner_phone_image.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 990 KiB |
@@ -30,7 +30,7 @@ import { useCloseModal, useFiatOnrampAvailability, useOpenModal, useToggleModal
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/claim/hooks'
|
||||
import StatusIcon from '../Identicon/StatusIcon'
|
||||
import { useToggleWalletDrawer } from '.'
|
||||
import { useToggleAccountDrawer } from '.'
|
||||
import IconButton, { IconHoverText } from './IconButton'
|
||||
import MiniPortfolio from './MiniPortfolio'
|
||||
import { portfolioFadeInAnimation } from './MiniPortfolio/PortfolioRow'
|
||||
@@ -184,7 +184,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
|
||||
dispatch(updateSelectedWallet({ wallet: undefined }))
|
||||
}, [connector, dispatch])
|
||||
|
||||
const toggleWalletDrawer = useToggleWalletDrawer()
|
||||
const toggleWalletDrawer = useToggleAccountDrawer()
|
||||
|
||||
const navigateToProfile = useCallback(() => {
|
||||
toggleWalletDrawer()
|
||||
@@ -197,9 +197,10 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
|
||||
|
||||
const openFiatOnrampModal = useOpenModal(ApplicationModal.FIAT_ONRAMP)
|
||||
const openFoRModalWithAnalytics = useCallback(() => {
|
||||
toggleWalletDrawer()
|
||||
sendAnalyticsEvent(InterfaceEventName.FIAT_ONRAMP_WIDGET_OPENED)
|
||||
openFiatOnrampModal()
|
||||
}, [openFiatOnrampModal])
|
||||
}, [openFiatOnrampModal, toggleWalletDrawer])
|
||||
|
||||
const [shouldCheck, setShouldCheck] = useState(false)
|
||||
const {
|
||||
@@ -287,11 +288,22 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
|
||||
<LoadingBubble height="16px" width="100px" margin="4px 0 20px 0" />
|
||||
</Column>
|
||||
)}
|
||||
{!shouldDisableNFTRoutes && (
|
||||
<HeaderButton
|
||||
data-testid="nft-view-self-nfts"
|
||||
onClick={navigateToProfile}
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
>
|
||||
<Trans>View and sell NFTs</Trans>
|
||||
</HeaderButton>
|
||||
)}
|
||||
<HeaderButton
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
onClick={handleBuyCryptoClick}
|
||||
disabled={disableBuyCryptoButton}
|
||||
data-testid="wallet-buy-crypto"
|
||||
>
|
||||
{error ? (
|
||||
<ThemedText.BodyPrimary>{error}</ThemedText.BodyPrimary>
|
||||
@@ -306,16 +318,6 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
|
||||
</>
|
||||
)}
|
||||
</HeaderButton>
|
||||
{!shouldDisableNFTRoutes && (
|
||||
<HeaderButton
|
||||
data-testid="nft-view-self-nfts"
|
||||
onClick={navigateToProfile}
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
>
|
||||
<Trans>View and sell NFTs</Trans>
|
||||
</HeaderButton>
|
||||
)}
|
||||
{Boolean(!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) && (
|
||||
<FiatOnrampNotAvailableText marginTop="8px">
|
||||
<Trans>Not available in your region</Trans>
|
||||
@@ -1,7 +1,6 @@
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { useMGTMMicrositeEnabled } from 'featureFlags/flags/mgtm'
|
||||
import { InterfaceElementName, InterfaceEventName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import { PropsWithChildren, useCallback } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ClickableStyle } from 'theme'
|
||||
import { isIOS } from 'utils/userAgent'
|
||||
@@ -33,22 +32,38 @@ function BaseButton({ onClick, branded, children }: PropsWithChildren<{ onClick?
|
||||
)
|
||||
}
|
||||
|
||||
const APP_STORE_LINK = 'https://apps.apple.com/us/app/uniswap-wallet-defi-nfts/id6443944476'
|
||||
const APP_STORE_LINK = 'https://apps.apple.com/us/app/uniswap-wallet/id6443944476'
|
||||
const MICROSITE_LINK = 'https://wallet.uniswap.org/'
|
||||
|
||||
const openAppStore = () => {
|
||||
window.open(APP_STORE_LINK, /* target = */ 'uniswap_wallet_appstore')
|
||||
}
|
||||
export const openWalletMicrosite = () => {
|
||||
sendAnalyticsEvent(InterfaceEventName.UNISWAP_WALLET_MICROSITE_OPENED)
|
||||
window.open(MICROSITE_LINK, /* target = */ 'uniswap_wallet_microsite')
|
||||
}
|
||||
|
||||
export function openDownloadApp(element: InterfaceElementName) {
|
||||
sendAnalyticsEvent(SharedEventName.ELEMENT_CLICKED, { element })
|
||||
if (isIOS) openAppStore()
|
||||
else openWalletMicrosite()
|
||||
}
|
||||
|
||||
// Launches App Store if on an iOS device, else navigates to Uniswap Wallet microsite
|
||||
export function DownloadButton({ onClick, text = 'Download' }: { onClick?: () => void; text?: string }) {
|
||||
const navigate = useNavigate()
|
||||
const micrositeEnabled = useMGTMMicrositeEnabled()
|
||||
|
||||
export function DownloadButton({
|
||||
onClick,
|
||||
text = 'Download',
|
||||
element,
|
||||
}: {
|
||||
onClick?: () => void
|
||||
text?: string
|
||||
element: InterfaceElementName
|
||||
}) {
|
||||
const onButtonClick = useCallback(() => {
|
||||
// handles any actions required by the parent, i.e. cancelling wallet connection attempt or dismissing an ad
|
||||
onClick?.()
|
||||
|
||||
if (isIOS || !micrositeEnabled) {
|
||||
sendAnalyticsEvent('Uniswap wallet download clicked')
|
||||
window.open(APP_STORE_LINK)
|
||||
} else navigate('/wallet')
|
||||
}, [onClick, micrositeEnabled, navigate])
|
||||
openDownloadApp(element)
|
||||
}, [element, onClick])
|
||||
|
||||
return (
|
||||
<BaseButton branded onClick={onButtonClick}>
|
||||
@@ -56,8 +71,3 @@ export function DownloadButton({ onClick, text = 'Download' }: { onClick?: () =>
|
||||
</BaseButton>
|
||||
)
|
||||
}
|
||||
|
||||
export function LearnMoreButton() {
|
||||
const navigate = useNavigate()
|
||||
return <BaseButton onClick={() => navigate('/wallet')}>Learn More</BaseButton>
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t } from '@lingui/macro'
|
||||
import { useAccountDrawer } from 'components/AccountDrawer'
|
||||
import Column from 'components/Column'
|
||||
import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { useWalletDrawer } from 'components/WalletDropdown'
|
||||
import { getYear, isSameDay, isSameMonth, isSameWeek, isSameYear } from 'date-fns'
|
||||
import { TransactionStatus, useTransactionListQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { PollingInterval } from 'graphql/data/util'
|
||||
@@ -98,10 +98,10 @@ function combineActivities(localMap: ActivityMap = {}, remoteMap: ActivityMap =
|
||||
const lastFetchedAtom = atom<number | undefined>(0)
|
||||
|
||||
export function ActivityTab({ account }: { account: string }) {
|
||||
const [drawerOpen, toggleWalletDrawer] = useWalletDrawer()
|
||||
const [drawerOpen, toggleWalletDrawer] = useAccountDrawer()
|
||||
const [lastFetched, setLastFetched] = useAtom(lastFetchedAtom)
|
||||
|
||||
const localMap = useLocalActivities()
|
||||
const localMap = useLocalActivities(account)
|
||||
|
||||
const { data, loading, refetch } = useTransactionListQuery({
|
||||
variables: { account },
|
||||
@@ -1,7 +1,7 @@
|
||||
// jest unit tests for the parseLocalActivity function
|
||||
|
||||
import { SupportedChainId, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { DAI, USDC_MAINNET } from 'constants/tokens'
|
||||
import { SupportedChainId, Token, TradeType as MockTradeType } from '@uniswap/sdk-core'
|
||||
import { DAI as MockDAI, USDC_MAINNET as MockUSDC_MAINNET } from 'constants/tokens'
|
||||
import { TokenAddressMap } from 'state/lists/hooks'
|
||||
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
|
||||
import {
|
||||
@@ -10,23 +10,24 @@ import {
|
||||
TransactionDetails,
|
||||
TransactionType,
|
||||
} from 'state/transactions/types'
|
||||
import { renderHook } from 'test-utils/render'
|
||||
|
||||
import { parseLocalActivity } from './parseLocal'
|
||||
import { parseLocalActivity, useLocalActivities } from './parseLocal'
|
||||
|
||||
const oneUSDCRaw = '1000000'
|
||||
const oneDAIRaw = '1000000000000000000'
|
||||
|
||||
function buildSwapInfo(
|
||||
type: TradeType,
|
||||
function mockSwapInfo(
|
||||
type: MockTradeType,
|
||||
inputCurrency: Token,
|
||||
inputCurrencyAmountRaw: string,
|
||||
outputCurrency: Token,
|
||||
outputCurrencyAmountRaw: string
|
||||
): ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo {
|
||||
if (type === TradeType.EXACT_INPUT) {
|
||||
if (type === MockTradeType.EXACT_INPUT) {
|
||||
return {
|
||||
type: TransactionType.SWAP,
|
||||
tradeType: TradeType.EXACT_INPUT,
|
||||
tradeType: MockTradeType.EXACT_INPUT,
|
||||
inputCurrencyId: inputCurrency.address,
|
||||
inputCurrencyAmountRaw,
|
||||
outputCurrencyId: outputCurrency.address,
|
||||
@@ -36,7 +37,7 @@ function buildSwapInfo(
|
||||
} else {
|
||||
return {
|
||||
type: TransactionType.SWAP,
|
||||
tradeType: TradeType.EXACT_OUTPUT,
|
||||
tradeType: MockTradeType.EXACT_OUTPUT,
|
||||
inputCurrencyId: inputCurrency.address,
|
||||
expectedInputCurrencyAmountRaw: inputCurrencyAmountRaw,
|
||||
maximumInputCurrencyAmountRaw: inputCurrencyAmountRaw,
|
||||
@@ -46,7 +47,44 @@ function buildSwapInfo(
|
||||
}
|
||||
}
|
||||
|
||||
function buildTokenAddressMap(...tokens: WrappedTokenInfo[]): TokenAddressMap {
|
||||
const mockAccount1 = '0x000000000000000000000000000000000000000001'
|
||||
const mockAccount2 = '0x000000000000000000000000000000000000000002'
|
||||
const mockChainId = SupportedChainId.MAINNET
|
||||
|
||||
jest.mock('../../../../state/transactions/hooks', () => {
|
||||
return {
|
||||
useMultichainTransactions: () => {
|
||||
return [
|
||||
[
|
||||
{
|
||||
info: mockSwapInfo(MockTradeType.EXACT_INPUT, MockUSDC_MAINNET, oneUSDCRaw, MockDAI, oneDAIRaw),
|
||||
hash: '0x123',
|
||||
from: mockAccount1,
|
||||
} as TransactionDetails,
|
||||
mockChainId,
|
||||
],
|
||||
[
|
||||
{
|
||||
info: mockSwapInfo(MockTradeType.EXACT_INPUT, MockUSDC_MAINNET, oneUSDCRaw, MockDAI, oneDAIRaw),
|
||||
hash: '0x456',
|
||||
from: mockAccount2,
|
||||
} as TransactionDetails,
|
||||
mockChainId,
|
||||
],
|
||||
[
|
||||
{
|
||||
info: mockSwapInfo(MockTradeType.EXACT_INPUT, MockUSDC_MAINNET, oneUSDCRaw, MockDAI, oneDAIRaw),
|
||||
hash: '0x789',
|
||||
from: mockAccount2,
|
||||
} as TransactionDetails,
|
||||
mockChainId,
|
||||
],
|
||||
]
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
function mockTokenAddressMap(...tokens: WrappedTokenInfo[]): TokenAddressMap {
|
||||
return {
|
||||
[SupportedChainId.MAINNET]: Object.fromEntries(tokens.map((token) => [token.address, { token }])),
|
||||
}
|
||||
@@ -55,27 +93,27 @@ function buildTokenAddressMap(...tokens: WrappedTokenInfo[]): TokenAddressMap {
|
||||
describe('parseLocalActivity', () => {
|
||||
it('returns swap activity fields with known tokens, exact input', () => {
|
||||
const details = {
|
||||
info: buildSwapInfo(TradeType.EXACT_INPUT, USDC_MAINNET, oneUSDCRaw, DAI, oneDAIRaw),
|
||||
info: mockSwapInfo(MockTradeType.EXACT_INPUT, MockUSDC_MAINNET, oneUSDCRaw, MockDAI, oneDAIRaw),
|
||||
receipt: {
|
||||
transactionHash: '0x123',
|
||||
status: 1,
|
||||
},
|
||||
} as TransactionDetails
|
||||
const chainId = SupportedChainId.MAINNET
|
||||
const tokens = buildTokenAddressMap(USDC_MAINNET as WrappedTokenInfo, DAI as WrappedTokenInfo)
|
||||
const tokens = mockTokenAddressMap(MockUSDC_MAINNET as WrappedTokenInfo, MockDAI as WrappedTokenInfo)
|
||||
expect(parseLocalActivity(details, chainId, tokens)).toEqual({
|
||||
chainId: 1,
|
||||
currencies: [USDC_MAINNET, DAI],
|
||||
currencies: [MockUSDC_MAINNET, MockDAI],
|
||||
descriptor: '1.00 USDC for 1.00 DAI',
|
||||
hash: undefined,
|
||||
receipt: {
|
||||
id: '0x123',
|
||||
info: {
|
||||
type: 1,
|
||||
tradeType: TradeType.EXACT_INPUT,
|
||||
inputCurrencyId: USDC_MAINNET.address,
|
||||
tradeType: MockTradeType.EXACT_INPUT,
|
||||
inputCurrencyId: MockUSDC_MAINNET.address,
|
||||
inputCurrencyAmountRaw: oneUSDCRaw,
|
||||
outputCurrencyId: DAI.address,
|
||||
outputCurrencyId: MockDAI.address,
|
||||
expectedOutputCurrencyAmountRaw: oneDAIRaw,
|
||||
minimumOutputCurrencyAmountRaw: oneDAIRaw,
|
||||
},
|
||||
@@ -91,28 +129,28 @@ describe('parseLocalActivity', () => {
|
||||
|
||||
it('returns swap activity fields with known tokens, exact output', () => {
|
||||
const details = {
|
||||
info: buildSwapInfo(TradeType.EXACT_OUTPUT, USDC_MAINNET, oneUSDCRaw, DAI, oneDAIRaw),
|
||||
info: mockSwapInfo(MockTradeType.EXACT_OUTPUT, MockUSDC_MAINNET, oneUSDCRaw, MockDAI, oneDAIRaw),
|
||||
receipt: {
|
||||
transactionHash: '0x123',
|
||||
status: 1,
|
||||
},
|
||||
} as TransactionDetails
|
||||
const chainId = SupportedChainId.MAINNET
|
||||
const tokens = buildTokenAddressMap(USDC_MAINNET as WrappedTokenInfo, DAI as WrappedTokenInfo)
|
||||
const tokens = mockTokenAddressMap(MockUSDC_MAINNET as WrappedTokenInfo, MockDAI as WrappedTokenInfo)
|
||||
expect(parseLocalActivity(details, chainId, tokens)).toEqual({
|
||||
chainId: 1,
|
||||
currencies: [USDC_MAINNET, DAI],
|
||||
currencies: [MockUSDC_MAINNET, MockDAI],
|
||||
descriptor: '1.00 USDC for 1.00 DAI',
|
||||
hash: undefined,
|
||||
receipt: {
|
||||
id: '0x123',
|
||||
info: {
|
||||
type: 1,
|
||||
tradeType: TradeType.EXACT_OUTPUT,
|
||||
inputCurrencyId: USDC_MAINNET.address,
|
||||
tradeType: MockTradeType.EXACT_OUTPUT,
|
||||
inputCurrencyId: MockUSDC_MAINNET.address,
|
||||
expectedInputCurrencyAmountRaw: oneUSDCRaw,
|
||||
maximumInputCurrencyAmountRaw: oneUSDCRaw,
|
||||
outputCurrencyId: DAI.address,
|
||||
outputCurrencyId: MockDAI.address,
|
||||
outputCurrencyAmountRaw: oneDAIRaw,
|
||||
},
|
||||
receipt: { status: 1, transactionHash: '0x123' },
|
||||
@@ -127,7 +165,7 @@ describe('parseLocalActivity', () => {
|
||||
|
||||
it('returns swap activity fields with unknown tokens', () => {
|
||||
const details = {
|
||||
info: buildSwapInfo(TradeType.EXACT_INPUT, USDC_MAINNET, oneUSDCRaw, DAI, oneDAIRaw),
|
||||
info: mockSwapInfo(MockTradeType.EXACT_INPUT, MockUSDC_MAINNET, oneUSDCRaw, MockDAI, oneDAIRaw),
|
||||
receipt: {
|
||||
transactionHash: '0x123',
|
||||
status: 1,
|
||||
@@ -144,10 +182,10 @@ describe('parseLocalActivity', () => {
|
||||
id: '0x123',
|
||||
info: {
|
||||
type: 1,
|
||||
tradeType: TradeType.EXACT_INPUT,
|
||||
inputCurrencyId: USDC_MAINNET.address,
|
||||
tradeType: MockTradeType.EXACT_INPUT,
|
||||
inputCurrencyId: MockUSDC_MAINNET.address,
|
||||
inputCurrencyAmountRaw: oneUSDCRaw,
|
||||
outputCurrencyId: DAI.address,
|
||||
outputCurrencyId: MockDAI.address,
|
||||
expectedOutputCurrencyAmountRaw: oneDAIRaw,
|
||||
minimumOutputCurrencyAmountRaw: oneDAIRaw,
|
||||
},
|
||||
@@ -160,4 +198,12 @@ describe('parseLocalActivity', () => {
|
||||
title: 'Swapped',
|
||||
})
|
||||
})
|
||||
|
||||
it('only returns activity for the current account', () => {
|
||||
const account1Activites = renderHook(() => useLocalActivities(mockAccount1)).result.current
|
||||
const account2Activites = renderHook(() => useLocalActivities(mockAccount2)).result.current
|
||||
|
||||
expect(Object.values(account1Activites)).toHaveLength(1)
|
||||
expect(Object.values(account2Activites)).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
@@ -2,7 +2,6 @@ import { t } from '@lingui/macro'
|
||||
import { formatCurrencyAmount } from '@uniswap/conedison/format'
|
||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { nativeOnChain } from '@uniswap/smart-order-router'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { TransactionPartsFragment, TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { useMemo } from 'react'
|
||||
@@ -135,71 +134,69 @@ export function parseLocalActivity(
|
||||
chainId: SupportedChainId,
|
||||
tokens: TokenAddressMap
|
||||
): Activity | undefined {
|
||||
const status = !details.receipt
|
||||
? TransactionStatus.Pending
|
||||
: details.receipt.status === 1 || details.receipt?.status === undefined
|
||||
? TransactionStatus.Confirmed
|
||||
: TransactionStatus.Failed
|
||||
try {
|
||||
const status = !details.receipt
|
||||
? TransactionStatus.Pending
|
||||
: details.receipt.status === 1 || details.receipt?.status === undefined
|
||||
? TransactionStatus.Confirmed
|
||||
: TransactionStatus.Failed
|
||||
|
||||
const receipt: TransactionPartsFragment | undefined = details.receipt
|
||||
? {
|
||||
id: details.receipt.transactionHash,
|
||||
...details.receipt,
|
||||
...details,
|
||||
status,
|
||||
}
|
||||
: undefined
|
||||
const receipt: TransactionPartsFragment | undefined = details.receipt
|
||||
? {
|
||||
id: details.receipt.transactionHash,
|
||||
...details.receipt,
|
||||
...details,
|
||||
status,
|
||||
}
|
||||
: undefined
|
||||
|
||||
const defaultFields = {
|
||||
hash: details.hash,
|
||||
chainId,
|
||||
title: getActivityTitle(details.info.type, status),
|
||||
status,
|
||||
timestamp: (details.confirmedTime ?? details.addedTime) / 1000,
|
||||
receipt,
|
||||
const defaultFields = {
|
||||
hash: details.hash,
|
||||
chainId,
|
||||
title: getActivityTitle(details.info.type, status),
|
||||
status,
|
||||
timestamp: (details.confirmedTime ?? details.addedTime) / 1000,
|
||||
receipt,
|
||||
}
|
||||
|
||||
let additionalFields: Partial<Activity> = {}
|
||||
const info = details.info
|
||||
if (info.type === TransactionType.SWAP) {
|
||||
additionalFields = parseSwap(info, chainId, tokens)
|
||||
} else if (info.type === TransactionType.APPROVAL) {
|
||||
additionalFields = parseApproval(info, chainId, tokens)
|
||||
} else if (info.type === TransactionType.WRAP) {
|
||||
additionalFields = parseWrap(info, chainId, status)
|
||||
} else if (
|
||||
info.type === TransactionType.ADD_LIQUIDITY_V3_POOL ||
|
||||
info.type === TransactionType.REMOVE_LIQUIDITY_V3 ||
|
||||
info.type === TransactionType.ADD_LIQUIDITY_V2_POOL
|
||||
) {
|
||||
additionalFields = parseLP(info, chainId, tokens)
|
||||
} else if (info.type === TransactionType.COLLECT_FEES) {
|
||||
additionalFields = parseCollectFees(info, chainId, tokens)
|
||||
} else if (info.type === TransactionType.MIGRATE_LIQUIDITY_V3 || info.type === TransactionType.CREATE_V3_POOL) {
|
||||
additionalFields = parseMigrateCreateV3(info, chainId, tokens)
|
||||
}
|
||||
|
||||
return { ...defaultFields, ...additionalFields }
|
||||
} catch (error) {
|
||||
console.debug(`Failed to parse transaction ${details.hash}`, error)
|
||||
return undefined
|
||||
}
|
||||
|
||||
let additionalFields: Partial<Activity> = {}
|
||||
const info = details.info
|
||||
if (info.type === TransactionType.SWAP) {
|
||||
additionalFields = parseSwap(info, chainId, tokens)
|
||||
} else if (info.type === TransactionType.APPROVAL) {
|
||||
additionalFields = parseApproval(info, chainId, tokens)
|
||||
} else if (info.type === TransactionType.WRAP) {
|
||||
additionalFields = parseWrap(info, chainId, status)
|
||||
} else if (
|
||||
info.type === TransactionType.ADD_LIQUIDITY_V3_POOL ||
|
||||
info.type === TransactionType.REMOVE_LIQUIDITY_V3 ||
|
||||
info.type === TransactionType.ADD_LIQUIDITY_V2_POOL
|
||||
) {
|
||||
additionalFields = parseLP(info, chainId, tokens)
|
||||
} else if (info.type === TransactionType.COLLECT_FEES) {
|
||||
additionalFields = parseCollectFees(info, chainId, tokens)
|
||||
} else if (info.type === TransactionType.MIGRATE_LIQUIDITY_V3 || info.type === TransactionType.CREATE_V3_POOL) {
|
||||
additionalFields = parseMigrateCreateV3(info, chainId, tokens)
|
||||
}
|
||||
|
||||
return { ...defaultFields, ...additionalFields }
|
||||
}
|
||||
|
||||
export function useLocalActivities(): ActivityMap | undefined {
|
||||
export function useLocalActivities(account: string): ActivityMap {
|
||||
const allTransactions = useMultichainTransactions()
|
||||
const { chainId } = useWeb3React()
|
||||
const tokens = useCombinedActiveList()
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
chainId
|
||||
? allTransactions.reduce((acc: { [hash: string]: Activity }, [transaction, chainId]) => {
|
||||
try {
|
||||
const localActivity = parseLocalActivity(transaction, chainId, tokens)
|
||||
if (localActivity) acc[localActivity.hash] = localActivity
|
||||
} catch (error) {
|
||||
console.error('Failed to parse local activity', transaction)
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
: undefined,
|
||||
[allTransactions, chainId, tokens]
|
||||
)
|
||||
return useMemo(() => {
|
||||
const activityByHash: ActivityMap = {}
|
||||
for (const [transaction, chainId] of allTransactions) {
|
||||
if (transaction.from !== account) continue
|
||||
|
||||
activityByHash[transaction.hash] = parseLocalActivity(transaction, chainId, tokens)
|
||||
}
|
||||
return activityByHash
|
||||
}, [account, allTransactions, tokens])
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics'
|
||||
import { InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import { useToggleAccountDrawer } from 'components/AccountDrawer'
|
||||
import Column from 'components/Column'
|
||||
import Row from 'components/Row'
|
||||
import { useToggleWalletDrawer } from 'components/WalletDropdown'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { NftCard } from 'nft/components/card'
|
||||
import { detailsHref } from 'nft/components/card/utils'
|
||||
@@ -46,7 +46,7 @@ export function NFT({
|
||||
mediaShouldBePlaying: boolean
|
||||
setCurrentTokenPlayingMedia: (tokenId: string | undefined) => void
|
||||
}) {
|
||||
const toggleWalletDrawer = useToggleWalletDrawer()
|
||||
const toggleWalletDrawer = useToggleAccountDrawer()
|
||||
const navigate = useNavigate()
|
||||
const trace = useTrace()
|
||||
|
||||
@@ -5,13 +5,22 @@ import { useState } from 'react'
|
||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { useToggleWalletDrawer } from '..'
|
||||
import { DEFAULT_NFT_QUERY_AMOUNT } from './constants'
|
||||
import { NFT } from './NFT'
|
||||
import { useAccountDrawer } from '../..'
|
||||
import { DEFAULT_NFT_QUERY_AMOUNT } from '../constants'
|
||||
import { NFT } from './NFTItem'
|
||||
|
||||
export default function NFTs({ account }: { account: string }) {
|
||||
const { walletAssets, loading, hasNext, loadMore } = useNftBalance(account, [], [], DEFAULT_NFT_QUERY_AMOUNT)
|
||||
const toggleWalletDrawer = useToggleWalletDrawer()
|
||||
const [walletDrawerOpen, toggleWalletDrawer] = useAccountDrawer()
|
||||
const { walletAssets, loading, hasNext, loadMore } = useNftBalance(
|
||||
account,
|
||||
[],
|
||||
[],
|
||||
DEFAULT_NFT_QUERY_AMOUNT,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
!walletDrawerOpen
|
||||
)
|
||||
|
||||
const [currentTokenPlayingMedia, setCurrentTokenPlayingMedia] = useState<string | undefined>()
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { SupportedChainId, WETH9 } from '@uniswap/sdk-core'
|
||||
import { FeeAmount, Pool, Position } from '@uniswap/v3-sdk'
|
||||
import { USDC_MAINNET } from 'constants/tokens'
|
||||
import { mocked } from 'test-utils/mocked'
|
||||
import { render } from 'test-utils/render'
|
||||
|
||||
import Pools from '.'
|
||||
import useMultiChainPositions from './useMultiChainPositions'
|
||||
|
||||
jest.mock('./useMultiChainPositions')
|
||||
|
||||
const owner = '0xf5b6bb25f5beaea03dd014c6ef9fa9f3926bf36c'
|
||||
|
||||
const pool = new Pool(
|
||||
USDC_MAINNET,
|
||||
WETH9[SupportedChainId.MAINNET],
|
||||
FeeAmount.MEDIUM,
|
||||
'1851127709498178402383049949138810',
|
||||
'7076437181775065414',
|
||||
201189
|
||||
)
|
||||
|
||||
const position = new Position({
|
||||
pool,
|
||||
liquidity: 1341008833950736,
|
||||
tickLower: 200040,
|
||||
tickUpper: 202560,
|
||||
})
|
||||
const details = {
|
||||
nonce: BigNumber.from('0'),
|
||||
tokenId: BigNumber.from('0'),
|
||||
operator: '0x0',
|
||||
token0: USDC_MAINNET.address,
|
||||
token1: WETH9[SupportedChainId.MAINNET].address,
|
||||
fee: FeeAmount.MEDIUM,
|
||||
tickLower: -100,
|
||||
tickUpper: 100,
|
||||
liquidity: BigNumber.from('9000'),
|
||||
feeGrowthInside0LastX128: BigNumber.from('0'),
|
||||
feeGrowthInside1LastX128: BigNumber.from('0'),
|
||||
tokensOwed0: BigNumber.from('0'),
|
||||
tokensOwed1: BigNumber.from('0'),
|
||||
}
|
||||
const useMultiChainPositionsReturnValue = {
|
||||
positions: [
|
||||
{
|
||||
owner,
|
||||
chainId: SupportedChainId.MAINNET,
|
||||
position,
|
||||
pool,
|
||||
details,
|
||||
inRange: true,
|
||||
closed: false,
|
||||
},
|
||||
],
|
||||
loading: false,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mocked(useMultiChainPositions).mockReturnValue(useMultiChainPositionsReturnValue)
|
||||
})
|
||||
test('Pools should render LP positions', () => {
|
||||
const props = { account: owner }
|
||||
const { container } = render(<Pools {...props} />)
|
||||
expect(container).not.toBeEmptyDOMElement()
|
||||
})
|
||||
@@ -4,9 +4,9 @@ import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/an
|
||||
import { formatNumber, NumberType } from '@uniswap/conedison/format'
|
||||
import { Position } from '@uniswap/v3-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useToggleAccountDrawer } from 'components/AccountDrawer'
|
||||
import Row from 'components/Row'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import { useToggleWalletDrawer } from 'components/WalletDropdown'
|
||||
import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
|
||||
import { useCallback, useMemo, useReducer } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
@@ -33,7 +33,7 @@ export default function Pools({ account }: { account: string }) {
|
||||
return [openPositions, closedPositions]
|
||||
}, [positions])
|
||||
|
||||
const toggleWalletDrawer = useToggleWalletDrawer()
|
||||
const toggleWalletDrawer = useToggleAccountDrawer()
|
||||
|
||||
if (!positions || loading) {
|
||||
return <PortfolioSkeleton />
|
||||
@@ -93,7 +93,7 @@ function PositionListItem({ positionInfo }: { positionInfo: PositionInfo }) {
|
||||
const liquidityValue = calculcateLiquidityValue(priceA, priceB, position)
|
||||
|
||||
const navigate = useNavigate()
|
||||
const toggleWalletDrawer = useToggleWalletDrawer()
|
||||
const toggleWalletDrawer = useToggleAccountDrawer()
|
||||
const { chainId: walletChainId, connector } = useWeb3React()
|
||||
const onClick = useCallback(async () => {
|
||||
if (walletChainId !== chainId) await switchChain(connector, chainId)
|
||||
@@ -111,15 +111,9 @@ function PositionListItem({ positionInfo }: { positionInfo: PositionInfo }) {
|
||||
[chainId, pool.token0.address, pool.token0.symbol, pool.token1.address, pool.token1.symbol]
|
||||
)
|
||||
|
||||
const containsURL = useMemo(
|
||||
() =>
|
||||
[pool.token0.name, pool.token0.symbol, pool.token1.name, pool.token1.symbol].some((testString) =>
|
||||
hasURL(testString)
|
||||
),
|
||||
[pool]
|
||||
)
|
||||
const shouldHidePosition = hasURL(pool.token0.symbol) || hasURL(pool.token1.symbol)
|
||||
|
||||
if (containsURL) {
|
||||
if (shouldHidePosition) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SupportedChainId } from '@uniswap/sdk-core'
|
||||
import { DAI_ARBITRUM } from '@uniswap/smart-order-router'
|
||||
import { DAI, USDC_ARBITRUM, USDC_MAINNET } from 'constants/tokens'
|
||||
import { render } from 'test-utils'
|
||||
import { render } from 'test-utils/render'
|
||||
|
||||
import { PortfolioLogo } from './PortfolioLogo'
|
||||
|
||||
@@ -12,12 +12,12 @@ import { useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { EllipsisStyle, ThemedText } from 'theme'
|
||||
|
||||
import { useToggleWalletDrawer } from '..'
|
||||
import { PortfolioArrow } from '../AuthenticatedHeader'
|
||||
import { hideSmallBalancesAtom } from '../SmallBalanceToggle'
|
||||
import { ExpandoRow } from './ExpandoRow'
|
||||
import { PortfolioLogo } from './PortfolioLogo'
|
||||
import PortfolioRow, { PortfolioSkeleton, PortfolioTabWrapper } from './PortfolioRow'
|
||||
import { useToggleAccountDrawer } from '../..'
|
||||
import { PortfolioArrow } from '../../AuthenticatedHeader'
|
||||
import { hideSmallBalancesAtom } from '../../SmallBalanceToggle'
|
||||
import { ExpandoRow } from '../ExpandoRow'
|
||||
import { PortfolioLogo } from '../PortfolioLogo'
|
||||
import PortfolioRow, { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow'
|
||||
|
||||
const HIDE_SMALL_USD_BALANCES_THRESHOLD = 1
|
||||
|
||||
@@ -26,7 +26,7 @@ function meetsThreshold(tokenBalance: TokenBalance, hideSmallBalances: boolean)
|
||||
}
|
||||
|
||||
export default function Tokens({ account }: { account: string }) {
|
||||
const toggleWalletDrawer = useToggleWalletDrawer()
|
||||
const toggleWalletDrawer = useToggleAccountDrawer()
|
||||
const hideSmallBalances = useAtomValue(hideSmallBalancesAtom)
|
||||
const [showHiddenTokens, setShowHiddenTokens] = useState(false)
|
||||
|
||||
@@ -96,7 +96,7 @@ function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: Tok
|
||||
const percentChange = tokenProjectMarket?.pricePercentChange?.value ?? 0
|
||||
|
||||
const navigate = useNavigate()
|
||||
const toggleWalletDrawer = useToggleWalletDrawer()
|
||||
const toggleWalletDrawer = useToggleAccountDrawer()
|
||||
const navigateToTokenDetails = useCallback(async () => {
|
||||
navigate(getTokenDetailsURL(token))
|
||||
toggleWalletDrawer()
|
||||
@@ -11,7 +11,7 @@ import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import { ActivityTab } from './Activity/ActivityTab'
|
||||
import { ActivityTab } from './Activity'
|
||||
import NFTs from './NFTs'
|
||||
import Pools from './Pools'
|
||||
import { PortfolioRowWrapper } from './PortfolioRow'
|
||||
@@ -5,7 +5,7 @@ import { PropsWithChildren, useCallback, useEffect, useMemo, useState } from 're
|
||||
import { useAllTransactions } from 'state/transactions/hooks'
|
||||
import { TransactionDetails } from 'state/transactions/types'
|
||||
|
||||
import { useWalletDrawer } from '.'
|
||||
import { useAccountDrawer } from '.'
|
||||
|
||||
const isTxPending = (tx: TransactionDetails) => !tx.receipt
|
||||
function wasPending(previousTxs: { [hash: string]: TransactionDetails | undefined }, current: TransactionDetails) {
|
||||
@@ -39,7 +39,7 @@ function useHasUpdatedTx() {
|
||||
export default function PrefetchBalancesWrapper({ children }: PropsWithChildren) {
|
||||
const { account } = useWeb3React()
|
||||
const [prefetchPortfolioBalances] = usePortfolioBalancesLazyQuery()
|
||||
const [drawerOpen] = useWalletDrawer()
|
||||
const [drawerOpen] = useAccountDrawer()
|
||||
|
||||
const [hasUnfetchedBalances, setHasUnfetchedBalances] = useState(true)
|
||||
const fetchBalances = useCallback(() => {
|
||||
@@ -3,7 +3,7 @@ import { LOCALE_LABEL, SUPPORTED_LOCALES, SupportedLocale } from 'constants/loca
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
import { useLocationLinkProps } from 'hooks/useLocationLinkProps'
|
||||
import { Check } from 'react-feather'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { Link } from 'react-router-dom'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { ClickableStyle, ThemedText } from 'theme'
|
||||
import ThemeToggle from 'theme/components/ThemeToggle'
|
||||
@@ -56,16 +56,13 @@ const BalanceToggleContainer = styled.div`
|
||||
export default function SettingsMenu({ onClose }: { onClose: () => void }) {
|
||||
const activeLocale = useActiveLocale()
|
||||
|
||||
const { pathname } = useLocation()
|
||||
const isWalletPage = pathname.includes('/wallet')
|
||||
|
||||
return (
|
||||
<SlideOutMenu title={<Trans>Settings</Trans>} onClose={onClose}>
|
||||
<SectionTitle>
|
||||
<Trans>Preferences</Trans>
|
||||
</SectionTitle>
|
||||
<ThemeToggleContainer>
|
||||
<ThemeToggle disabled={isWalletPage} />
|
||||
<ThemeToggle />
|
||||
</ThemeToggleContainer>
|
||||
<BalanceToggleContainer>
|
||||
<SmallBalanceToggle />
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { InterfaceElementName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { WalletConnect } from '@web3-react/walletconnect'
|
||||
import Column, { AutoColumn } from 'components/Column'
|
||||
@@ -95,7 +96,7 @@ export default function UniwalletModal() {
|
||||
)}
|
||||
</QRCodeWrapper>
|
||||
<Divider />
|
||||
<InfoSection onClose={onClose} />
|
||||
<InfoSection />
|
||||
</UniwalletConnectWrapper>
|
||||
</Modal>
|
||||
)
|
||||
@@ -108,7 +109,7 @@ const InfoSectionWrapper = styled(RowBetween)`
|
||||
gap: 20px;
|
||||
`
|
||||
|
||||
function InfoSection({ onClose }: { onClose: () => void }) {
|
||||
function InfoSection() {
|
||||
return (
|
||||
<InfoSectionWrapper>
|
||||
<AutoColumn gap="4px">
|
||||
@@ -117,12 +118,12 @@ function InfoSection({ onClose }: { onClose: () => void }) {
|
||||
</ThemedText.SubHeaderSmall>
|
||||
<ThemedText.Caption color="textSecondary">
|
||||
<Trans>
|
||||
Download in the App Store to safely store and send tokens and NFTs, swap tokens, and connect to crypto apps.
|
||||
Download in the App Store to safely store your tokens and NFTs, swap tokens, and connect to crypto apps.
|
||||
</Trans>
|
||||
</ThemedText.Caption>
|
||||
</AutoColumn>
|
||||
<Column>
|
||||
<DownloadButton onClick={onClose} />
|
||||
<DownloadButton element={InterfaceElementName.UNISWAP_WALLET_MODAL_DOWNLOAD_BUTTON} />
|
||||
</Column>
|
||||
</InfoSectionWrapper>
|
||||
)
|
||||
@@ -18,18 +18,18 @@ const DRAWER_MARGIN = '8px'
|
||||
const DRAWER_OFFSET = '10px'
|
||||
const DRAWER_TOP_MARGIN_MOBILE_WEB = '72px'
|
||||
|
||||
const walletDrawerOpenAtom = atom(false)
|
||||
const accountDrawerOpenAtom = atom(false)
|
||||
|
||||
export function useToggleWalletDrawer() {
|
||||
const updateWalletDrawerOpen = useUpdateAtom(walletDrawerOpenAtom)
|
||||
export function useToggleAccountDrawer() {
|
||||
const updateAccountDrawerOpen = useUpdateAtom(accountDrawerOpenAtom)
|
||||
return useCallback(() => {
|
||||
updateWalletDrawerOpen((open) => !open)
|
||||
}, [updateWalletDrawerOpen])
|
||||
updateAccountDrawerOpen((open) => !open)
|
||||
}, [updateAccountDrawerOpen])
|
||||
}
|
||||
|
||||
export function useWalletDrawer(): [boolean, () => void] {
|
||||
const walletDrawerOpen = useAtomValue(walletDrawerOpenAtom)
|
||||
return [walletDrawerOpen, useToggleWalletDrawer()]
|
||||
export function useAccountDrawer(): [boolean, () => void] {
|
||||
const accountDrawerOpen = useAtomValue(accountDrawerOpenAtom)
|
||||
return [accountDrawerOpen, useToggleAccountDrawer()]
|
||||
}
|
||||
|
||||
const ScrimBackground = styled.div<{ open: boolean }>`
|
||||
@@ -63,7 +63,7 @@ const Scrim = ({ onClick, open }: { onClick: () => void; open: boolean }) => {
|
||||
return <ScrimBackground onClick={onClick} open={open} />
|
||||
}
|
||||
|
||||
const WalletDropdownScrollWrapper = styled.div`
|
||||
const AccountDrawerScrollWrapper = styled.div`
|
||||
overflow: hidden;
|
||||
&:hover {
|
||||
overflow-y: auto;
|
||||
@@ -76,7 +76,7 @@ const WalletDropdownScrollWrapper = styled.div`
|
||||
border-radius: 12px;
|
||||
`
|
||||
|
||||
const Wrapper = styled.div`
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: calc(100% - 2 * ${DRAWER_MARGIN});
|
||||
@@ -85,29 +85,36 @@ const Wrapper = styled.div`
|
||||
right: ${DRAWER_MARGIN};
|
||||
top: ${DRAWER_MARGIN};
|
||||
z-index: ${Z_INDEX.fixed};
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
`
|
||||
|
||||
const WalletDropdownWrapper = styled.div<{ open: boolean }>`
|
||||
const AccountDrawerWrapper = styled.div<{ open: boolean }>`
|
||||
margin-right: ${({ open }) => (open ? 0 : '-' + DRAWER_WIDTH)};
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
|
||||
z-index: ${Z_INDEX.modal};
|
||||
top: unset;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: ${({ open }) => (open ? 0 : `calc(-1 * (100% - ${DRAWER_TOP_MARGIN_MOBILE_WEB}))`)};
|
||||
position: absolute;
|
||||
margin-right: 0;
|
||||
top: ${({ open }) => (open ? `calc(-1 * (100% - ${DRAWER_TOP_MARGIN_MOBILE_WEB}))` : 0)};
|
||||
|
||||
width: 100%;
|
||||
height: calc(100% - ${DRAWER_TOP_MARGIN_MOBILE_WEB});
|
||||
border-bottom-right-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
box-shadow: unset;
|
||||
transition: top ${({ theme }) => theme.transition.duration.medium};
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1440px) {
|
||||
margin-right: ${({ open }) => (open ? 0 : '-' + DRAWER_WIDTH_XL)};
|
||||
margin-right: ${({ open }) => (open ? 0 : `-${DRAWER_WIDTH_XL}`)};
|
||||
width: ${DRAWER_WIDTH_XL};
|
||||
}
|
||||
|
||||
@@ -118,8 +125,7 @@ const WalletDropdownWrapper = styled.div<{ open: boolean }>`
|
||||
border: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
|
||||
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
transition: margin-right ${({ theme }) => theme.transition.duration.medium},
|
||||
bottom ${({ theme }) => theme.transition.duration.medium};
|
||||
transition: margin-right ${({ theme }) => theme.transition.duration.medium};
|
||||
`
|
||||
|
||||
const CloseIcon = styled(ChevronsRight).attrs({ size: 24 })`
|
||||
@@ -136,7 +142,8 @@ const CloseDrawer = styled.div`
|
||||
transition: ${({ theme }) =>
|
||||
`${theme.transition.duration.medium} ${theme.transition.timing.ease} background-color, ${theme.transition.duration.medium} ${theme.transition.timing.ease} margin`};
|
||||
&:hover {
|
||||
margin: 0 -4px 0 0;
|
||||
z-index: -1;
|
||||
margin: 0 -8px 0 0;
|
||||
background-color: ${({ theme }) => theme.stateOverlayHover};
|
||||
}
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
|
||||
@@ -144,8 +151,8 @@ const CloseDrawer = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
function WalletDropdown() {
|
||||
const [walletDrawerOpen, toggleWalletDrawer] = useWalletDrawer()
|
||||
function AccountDrawer() {
|
||||
const [walletDrawerOpen, toggleWalletDrawer] = useAccountDrawer()
|
||||
const scrollRef = useRef<HTMLDivElement>(null)
|
||||
useEffect(() => {
|
||||
if (!walletDrawerOpen) {
|
||||
@@ -186,7 +193,7 @@ function WalletDropdown() {
|
||||
}, [walletDrawerOpen, toggleWalletDrawer])
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Container>
|
||||
{walletDrawerOpen && (
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
@@ -199,14 +206,14 @@ function WalletDropdown() {
|
||||
</TraceEvent>
|
||||
)}
|
||||
<Scrim onClick={toggleWalletDrawer} open={walletDrawerOpen} />
|
||||
<WalletDropdownWrapper open={walletDrawerOpen}>
|
||||
<AccountDrawerWrapper open={walletDrawerOpen}>
|
||||
{/* id used for child InfiniteScrolls to reference when it has reached the bottom of the component */}
|
||||
<WalletDropdownScrollWrapper ref={scrollRef} id="wallet-dropdown-scroll-wrapper">
|
||||
<AccountDrawerScrollWrapper ref={scrollRef} id="wallet-dropdown-scroll-wrapper">
|
||||
<DefaultMenu />
|
||||
</WalletDropdownScrollWrapper>
|
||||
</WalletDropdownWrapper>
|
||||
</Wrapper>
|
||||
</AccountDrawerScrollWrapper>
|
||||
</AccountDrawerWrapper>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default WalletDropdown
|
||||
export default AccountDrawer
|
||||
@@ -1,111 +1,147 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { InterfaceElementName } from '@uniswap/analytics-events'
|
||||
import { openDownloadApp, openWalletMicrosite } from 'components/AccountDrawer/DownloadButton'
|
||||
import { BaseButton } from 'components/Button'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import Row, { RowBetween } from 'components/Row'
|
||||
import { useWalletDrawer } from 'components/WalletDropdown'
|
||||
import { DownloadButton, LearnMoreButton } from 'components/WalletDropdown/DownloadButton'
|
||||
import { OpacityHoverState } from 'components/Common'
|
||||
import Row from 'components/Row'
|
||||
import { useMgtmEnabled } from 'featureFlags/flags/mgtm'
|
||||
import { useScreenSize } from 'hooks/useScreenSize'
|
||||
import { X } from 'react-feather'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { useHideUniswapWalletBanner } from 'state/user/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
import { isIOS } from 'utils/userAgent'
|
||||
|
||||
import bannerImageDark from '../../assets/images/uniswapWalletBannerDark.png'
|
||||
import bannerImageLight from '../../assets/images/uniswapWalletBannerLight.png'
|
||||
import { ReactComponent as AppleLogo } from '../../assets/svg/apple_logo.svg'
|
||||
import walletBannerPhoneImageSrc from '../../assets/svg/wallet_banner_phone_image.svg'
|
||||
|
||||
const PopupContainer = styled.div<{ show: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
${({ show }) => !show && 'display: none'};
|
||||
|
||||
box-shadow: ${({ theme }) =>
|
||||
theme.darkMode
|
||||
? '0px -16px 24px rgba(0, 0, 0, 0.4), 0px -8px 12px rgba(0, 0, 0, 0.4), 0px -4px 8px rgba(0, 0, 0, 0.32)'
|
||||
: '0px -12px 20px rgba(51, 53, 72, 0.04), 0px -6px 12px rgba(51, 53, 72, 0.02), 0px -4px 8px rgba(51, 53, 72, 0.04)'};
|
||||
|
||||
background-image: ${({ theme }) => (theme.darkMode ? `url(${bannerImageDark})` : `url(${bannerImageLight})`)};
|
||||
background: url(${walletBannerPhoneImageSrc});
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: bottom -1px right 15px;
|
||||
background-size: 166px;
|
||||
|
||||
cursor: pointer;
|
||||
:hover {
|
||||
background-size: 170px;
|
||||
}
|
||||
transition: background-size ${({ theme }) => theme.transition.duration.medium}
|
||||
${({ theme }) => theme.transition.timing.inOut};
|
||||
|
||||
background-color: ${({ theme }) => theme.promotional};
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
position: fixed;
|
||||
z-index: ${Z_INDEX.sticky};
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.slow} opacity ${timing.in}`};
|
||||
width: 100%;
|
||||
bottom: 56px;
|
||||
height: 20%;
|
||||
`
|
||||
|
||||
const InnerContainer = styled.div`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
padding: 24px 16px;
|
||||
padding: 24px 16px 16px;
|
||||
|
||||
border-radius: 20px;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 390px;
|
||||
height: 164px;
|
||||
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) {
|
||||
bottom: 62px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
|
||||
width: unset;
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
user-select: none;
|
||||
`
|
||||
|
||||
const ButtonRow = styled(Row)`
|
||||
gap: 16px;
|
||||
`
|
||||
|
||||
const StyledXButton = styled(X)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
&:hover {
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
}
|
||||
&:active {
|
||||
opacity: ${({ theme }) => theme.opacity.click};
|
||||
}
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 21px;
|
||||
right: 17px;
|
||||
|
||||
color: ${({ theme }) => theme.white};
|
||||
${OpacityHoverState};
|
||||
`
|
||||
|
||||
const BannerButton = styled(BaseButton)`
|
||||
height: 40px;
|
||||
border-radius: 16px;
|
||||
padding: 10px;
|
||||
${OpacityHoverState};
|
||||
`
|
||||
|
||||
export default function UniswapWalletBanner() {
|
||||
const [hideUniswapWalletBanner, toggleHideUniswapWalletBanner] = useHideUniswapWalletBanner()
|
||||
const [walletDrawerOpen] = useWalletDrawer()
|
||||
const mgtmEnabled = useMgtmEnabled()
|
||||
const location = useLocation()
|
||||
const isLandingScreen = location.search === '?intro=true' || location.pathname === '/'
|
||||
|
||||
const theme = useTheme()
|
||||
const shouldDisplay = Boolean(mgtmEnabled && !hideUniswapWalletBanner && !isLandingScreen)
|
||||
|
||||
const { pathname } = useLocation()
|
||||
// hardcodeToFalse hardcodes the banner to never display, temporarily:
|
||||
const hardcodeToFalse = false
|
||||
const shouldDisplay = Boolean(
|
||||
!walletDrawerOpen && !hideUniswapWalletBanner && isIOS && !pathname.startsWith('/wallet') && hardcodeToFalse
|
||||
)
|
||||
const screenSize = useScreenSize()
|
||||
|
||||
return (
|
||||
<PopupContainer show={shouldDisplay}>
|
||||
<InnerContainer>
|
||||
<AutoColumn gap="8px">
|
||||
<RowBetween>
|
||||
<ThemedText.SubHeader>
|
||||
<Trans>Get the power of Uniswap in your pocket</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
<StyledXButton
|
||||
data-testid="uniswap-wallet-banner"
|
||||
color={theme.textSecondary}
|
||||
size={20}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
toggleHideUniswapWalletBanner()
|
||||
}}
|
||||
/>
|
||||
</RowBetween>
|
||||
<ThemedText.BodySmall>
|
||||
<Trans>Download in the App Store today.</Trans>{' '}
|
||||
</ThemedText.BodySmall>
|
||||
</AutoColumn>
|
||||
<StyledXButton
|
||||
data-testid="uniswap-wallet-banner"
|
||||
size={20}
|
||||
onClick={(e) => {
|
||||
// prevent click from bubbling to UI on the page underneath, i.e. clicking a token row
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
toggleHideUniswapWalletBanner()
|
||||
}}
|
||||
/>
|
||||
|
||||
<ButtonRow>
|
||||
<LearnMoreButton />
|
||||
<DownloadButton onClick={() => toggleHideUniswapWalletBanner()} />
|
||||
</ButtonRow>
|
||||
</InnerContainer>
|
||||
<AutoColumn gap="8px">
|
||||
<ThemedText.HeadlineMedium fontSize="24px" lineHeight="28px" color="white" maxWidth="60%">
|
||||
<Trans>Uniswap in your pocket</Trans>
|
||||
</ThemedText.HeadlineMedium>
|
||||
</AutoColumn>
|
||||
|
||||
<ButtonRow>
|
||||
{isIOS ? (
|
||||
<>
|
||||
<BannerButton
|
||||
backgroundColor="white"
|
||||
onClick={() => openDownloadApp(InterfaceElementName.UNISWAP_WALLET_BANNER_DOWNLOAD_BUTTON)}
|
||||
>
|
||||
<AppleLogo width={14} height={14} />
|
||||
<ThemedText.LabelSmall color="black" marginLeft="5px">
|
||||
{!screenSize['xs'] ? <Trans>Download</Trans> : <Trans>Download app</Trans>}
|
||||
</ThemedText.LabelSmall>
|
||||
</BannerButton>
|
||||
|
||||
<BannerButton backgroundColor="black" onClick={openWalletMicrosite}>
|
||||
<ThemedText.LabelSmall color="white">
|
||||
<Trans>Learn more</Trans>
|
||||
</ThemedText.LabelSmall>
|
||||
</BannerButton>
|
||||
</>
|
||||
) : (
|
||||
<BannerButton backgroundColor="white" width="125px" onClick={openWalletMicrosite}>
|
||||
<ThemedText.LabelSmall color="black">
|
||||
<Trans>Learn more</Trans>
|
||||
</ThemedText.LabelSmall>
|
||||
</BannerButton>
|
||||
)}
|
||||
</ButtonRow>
|
||||
</PopupContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import * as Sentry from '@sentry/react'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { SwapEventName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { ButtonLight, SmallButtonPrimary } from 'components/Button'
|
||||
import { ChevronUpIcon } from 'nft/components/icons'
|
||||
import { useIsMobile } from 'nft/hooks'
|
||||
import React, { PropsWithChildren, useState } from 'react'
|
||||
import { Copy } from 'react-feather'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { isSentryEnabled } from 'utils/env'
|
||||
|
||||
@@ -220,18 +218,16 @@ const updateServiceWorkerInBackground = async () => {
|
||||
}
|
||||
|
||||
export default function ErrorBoundary({ children }: PropsWithChildren): JSX.Element {
|
||||
const { pathname } = useLocation()
|
||||
const { chainId } = useWeb3React()
|
||||
return (
|
||||
<Sentry.ErrorBoundary
|
||||
fallback={({ error, eventId }) => <Fallback error={error} eventId={eventId} />}
|
||||
beforeCapture={(scope) => {
|
||||
scope.setLevel('fatal')
|
||||
scope.setTag('chain_id', chainId)
|
||||
}}
|
||||
onError={(error) => {
|
||||
onError={() => {
|
||||
updateServiceWorkerInBackground()
|
||||
if (pathname === '/swap') {
|
||||
sendAnalyticsEvent(SwapEventName.SWAP_ERROR, { error })
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useBaseFlag, useUpdateFlag } from 'featureFlags'
|
||||
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
|
||||
import { MgtmVariant, useMgtmFlag } from 'featureFlags/flags/mgtm'
|
||||
import { useMiniPortfolioFlag } from 'featureFlags/flags/miniPortfolio'
|
||||
import { DetailsV2Variant, useDetailsV2Flag } from 'featureFlags/flags/nftDetails'
|
||||
import { NftGraphqlVariant, useNftGraphqlFlag } from 'featureFlags/flags/nftlGraphql'
|
||||
import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken'
|
||||
import { SwapWidgetVariant, useSwapWidgetFlag } from 'featureFlags/flags/swapWidget'
|
||||
@@ -211,12 +212,6 @@ export default function FeatureFlagModal() {
|
||||
featureFlag={FeatureFlag.mgtm}
|
||||
label="Mobile Wallet go-to-market assets"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useBaseFlag(FeatureFlag.walletMicrosite)}
|
||||
featureFlag={FeatureFlag.walletMicrosite}
|
||||
label="Mobile Wallet microsite (requires mgtm to also be enabled)"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={BaseVariant}
|
||||
value={useMiniPortfolioFlag()}
|
||||
@@ -241,6 +236,12 @@ export default function FeatureFlagModal() {
|
||||
featureFlag={FeatureFlag.nftGraphql}
|
||||
label="Migrate NFT read endpoints to GQL"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={DetailsV2Variant}
|
||||
value={useDetailsV2Flag()}
|
||||
featureFlag={FeatureFlag.detailsV2}
|
||||
label="Use the new details page for nfts"
|
||||
/>
|
||||
<FeatureFlagGroup name="Debug">
|
||||
<FeatureFlagOption
|
||||
variant={TraceJsonRpcVariant}
|
||||
|
||||
24
src/components/Identicon/StatusIcon.test.tsx
Normal file
24
src/components/Identicon/StatusIcon.test.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { getConnections } from 'connection'
|
||||
import { render } from 'test-utils/render'
|
||||
|
||||
import StatusIcon from './StatusIcon'
|
||||
|
||||
jest.mock('../../hooks/useSocksBalance', () => ({
|
||||
useHasSocks: () => true,
|
||||
}))
|
||||
|
||||
describe('StatusIcon', () => {
|
||||
it('renders children in correct order, with no account and with socks', () => {
|
||||
const supportedConnections = getConnections()
|
||||
const injectedConnection = supportedConnections[1]
|
||||
const component = render(<StatusIcon connection={injectedConnection} />)
|
||||
expect(component.getByTestId('StatusIconRoot')).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders with no account and showMiniIcons=false', () => {
|
||||
const supportedConnections = getConnections()
|
||||
const injectedConnection = supportedConnections[1]
|
||||
const component = render(<StatusIcon connection={injectedConnection} showMiniIcons={false} />)
|
||||
expect(component.getByTestId('StatusIconRoot').children.length).toEqual(0)
|
||||
})
|
||||
})
|
||||
@@ -3,6 +3,7 @@ import { Unicon } from 'components/Unicon'
|
||||
import { Connection, ConnectionType } from 'connection'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useIsDarkMode } from 'theme/components/ThemeToggle'
|
||||
import { flexColumnNoWrap } from 'theme/styles'
|
||||
|
||||
import sockImg from '../../assets/svg/socks.svg'
|
||||
@@ -58,9 +59,10 @@ const Socks = () => {
|
||||
}
|
||||
|
||||
const MiniWalletIcon = ({ connection, side }: { connection: Connection; side: 'left' | 'right' }) => {
|
||||
const isDarkMode = useIsDarkMode()
|
||||
return (
|
||||
<MiniIconContainer side={side}>
|
||||
<MiniImg src={connection.getIcon?.()} alt={`${connection.getName()} icon`} />
|
||||
<MiniImg src={connection.getIcon?.(isDarkMode)} alt={`${connection.getName()} icon`} />
|
||||
</MiniIconContainer>
|
||||
)
|
||||
}
|
||||
@@ -90,10 +92,10 @@ export default function StatusIcon({
|
||||
const hasSocks = useHasSocks()
|
||||
|
||||
return (
|
||||
<IconWrapper size={size}>
|
||||
{hasSocks && showMiniIcons && <Socks />}
|
||||
<IconWrapper size={size} data-testid="StatusIconRoot">
|
||||
<MainWalletIcon connection={connection} size={size} />
|
||||
{showMiniIcons && <MiniWalletIcon connection={connection} side="right" />}
|
||||
{hasSocks && showMiniIcons && <Socks />}
|
||||
</IconWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
129
src/components/Identicon/__snapshots__/StatusIcon.test.tsx.snap
Normal file
129
src/components/Identicon/__snapshots__/StatusIcon.test.tsx.snap
Normal file
@@ -0,0 +1,129 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`StatusIcon renders children in correct order, with no account and with socks 1`] = `
|
||||
.c0 {
|
||||
position: relative;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-flow: column nowrap;
|
||||
-ms-flex-flow: column nowrap;
|
||||
flex-flow: column nowrap;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.c0 > img,
|
||||
.c0 span {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
position: absolute;
|
||||
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;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
bottom: -4px;
|
||||
right: -4px;
|
||||
border-radius: 50%;
|
||||
outline: 2px solid #FFFFFF;
|
||||
outline-offset: -0.1px;
|
||||
background-color: #FFFFFF;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
position: absolute;
|
||||
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;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
bottom: -4px;
|
||||
left: -4px;
|
||||
border-radius: 50%;
|
||||
outline: 2px solid #FFFFFF;
|
||||
outline-offset: -0.1px;
|
||||
background-color: #FFFFFF;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
@media (max-width:960px) {
|
||||
.c0 {
|
||||
-webkit-align-items: flex-end;
|
||||
-webkit-box-align: flex-end;
|
||||
-ms-flex-align: flex-end;
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
@supports (overflow:clip) {
|
||||
.c1 {
|
||||
overflow: clip;
|
||||
}
|
||||
}
|
||||
|
||||
@supports (overflow:clip) {
|
||||
.c3 {
|
||||
overflow: clip;
|
||||
}
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
data-testid="StatusIconRoot"
|
||||
size="16"
|
||||
>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<img
|
||||
alt="MetaMask icon"
|
||||
class="c2"
|
||||
src="metamask.svg"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="c3"
|
||||
>
|
||||
<img
|
||||
class="c2"
|
||||
src="socks.svg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,6 +1,9 @@
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { InterfaceElementName } from '@uniswap/analytics-events'
|
||||
import { openDownloadApp } from 'components/AccountDrawer/DownloadButton'
|
||||
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
|
||||
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
|
||||
import { useMgtmEnabled } from 'featureFlags/flags/mgtm'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
@@ -21,6 +24,7 @@ import { useToggleModal } from 'state/application/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { isDevelopmentEnv, isStagingEnv } from 'utils/env'
|
||||
|
||||
import { ReactComponent as AppleLogo } from '../../assets/svg/apple_logo.svg'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import * as styles from './MenuDropdown.css'
|
||||
import { NavDropdown } from './NavDropdown'
|
||||
@@ -126,6 +130,8 @@ export const MenuDropdown = () => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
useOnClickOutside(ref, isOpen ? toggleOpen : undefined)
|
||||
|
||||
const mgtmEnabled = useMgtmEnabled()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box position="relative" ref={ref}>
|
||||
@@ -140,16 +146,29 @@ export const MenuDropdown = () => {
|
||||
<Box display={{ sm: 'none', lg: 'flex', xxl: 'none' }}>
|
||||
<PrimaryMenuRow to="/pool" close={toggleOpen}>
|
||||
<Icon>
|
||||
<PoolIcon width={24} height={24} color={theme.textSecondary} />
|
||||
<PoolIcon width={24} height={24} fill={theme.textPrimary} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Pool</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
</Box>
|
||||
<Box
|
||||
display={mgtmEnabled ? 'flex' : 'none'}
|
||||
onClick={() => openDownloadApp(InterfaceElementName.UNISWAP_WALLET_MODAL_DOWNLOAD_BUTTON)}
|
||||
>
|
||||
<PrimaryMenuRow close={toggleOpen}>
|
||||
<Icon>
|
||||
<AppleLogo width="24px" height="24px" fill={theme.textPrimary} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Download Uniswap Wallet</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
</Box>
|
||||
<PrimaryMenuRow to="/vote" close={toggleOpen}>
|
||||
<Icon>
|
||||
<GovernanceIcon width={24} height={24} color={theme.textSecondary} />
|
||||
<GovernanceIcon width={24} height={24} color={theme.textPrimary} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Vote in governance</Trans>
|
||||
@@ -157,7 +176,7 @@ export const MenuDropdown = () => {
|
||||
</PrimaryMenuRow>
|
||||
<PrimaryMenuRow href="https://info.uniswap.org/#/">
|
||||
<Icon>
|
||||
<BarChartIcon width={24} height={24} color={theme.textSecondary} />
|
||||
<BarChartIcon width={24} height={24} color={theme.textPrimary} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>View more analytics</Trans>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import NewBadge from 'components/WalletModal/NewBadge'
|
||||
import Web3Status from 'components/Web3Status'
|
||||
import { useMGTMMicrositeEnabled } from 'featureFlags/flags/mgtm'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { useIsPoolsPage } from 'hooks/useIsPoolsPage'
|
||||
@@ -60,7 +58,6 @@ export const PageTabs = () => {
|
||||
|
||||
const isPoolActive = useIsPoolsPage()
|
||||
const isNftPage = useIsNftPage()
|
||||
const micrositeEnabled = useMGTMMicrositeEnabled()
|
||||
|
||||
const shouldDisableNFTRoutes = useAtomValue(shouldDisableNFTRoutesAtom)
|
||||
|
||||
@@ -82,14 +79,6 @@ export const PageTabs = () => {
|
||||
<Trans>Pools</Trans>
|
||||
</MenuItem>
|
||||
</Box>
|
||||
{micrositeEnabled && (
|
||||
<Box display={{ sm: 'none', xxxl: 'flex' }}>
|
||||
<MenuItem href="/wallet" isActive={pathname.startsWith('/wallet')}>
|
||||
<Trans>Wallet</Trans>
|
||||
<NewBadge />
|
||||
</MenuItem>
|
||||
</Box>
|
||||
)}
|
||||
<Box marginY={{ sm: '4', md: 'unset' }}>
|
||||
<MenuDropdown />
|
||||
</Box>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { parseLocalActivity } from 'components/AccountDrawer/MiniPortfolio/Activity/parseLocal'
|
||||
import { PortfolioLogo } from 'components/AccountDrawer/MiniPortfolio/PortfolioLogo'
|
||||
import PortfolioRow from 'components/AccountDrawer/MiniPortfolio/PortfolioRow'
|
||||
import Column from 'components/Column'
|
||||
import { parseLocalActivity } from 'components/WalletDropdown/MiniPortfolio/Activity/parseLocal'
|
||||
import { PortfolioLogo } from 'components/WalletDropdown/MiniPortfolio/PortfolioLogo'
|
||||
import PortfolioRow from 'components/WalletDropdown/MiniPortfolio/PortfolioRow'
|
||||
import useENSName from 'hooks/useENSName'
|
||||
import { useCombinedActiveList } from 'state/lists/hooks'
|
||||
import { useTransaction } from 'state/transactions/hooks'
|
||||
|
||||
@@ -1,38 +1,89 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { render, screen } from 'test-utils'
|
||||
import { SupportedChainId, Token, WETH9 } from '@uniswap/sdk-core'
|
||||
import { FeeAmount, Pool } from '@uniswap/v3-sdk'
|
||||
import { USDC_MAINNET } from 'constants/tokens'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { usePool } from 'hooks/usePools'
|
||||
import { PoolState } from 'hooks/usePools'
|
||||
import { mocked } from 'test-utils/mocked'
|
||||
import { render } from 'test-utils/render'
|
||||
import { unwrappedToken } from 'utils/unwrappedToken'
|
||||
|
||||
import PositionListItem from '.'
|
||||
|
||||
jest.mock('hooks/Tokens', () => {
|
||||
const originalModule = jest.requireActual('hooks/Tokens')
|
||||
const uniSDK = jest.requireActual('@uniswap/sdk-core')
|
||||
jest.mock('utils/unwrappedToken')
|
||||
|
||||
jest.mock('hooks/usePools')
|
||||
|
||||
jest.mock('hooks/Tokens')
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
jest.mock('components/DoubleLogo', () => () => <div />)
|
||||
|
||||
jest.mock('@web3-react/core', () => {
|
||||
const web3React = jest.requireActual('@web3-react/core')
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
useToken: jest.fn(
|
||||
() =>
|
||||
new uniSDK.Token(
|
||||
1,
|
||||
'0x39AA39c021dfbaE8faC545936693aC917d5E7563',
|
||||
8,
|
||||
'https://www.example.com',
|
||||
'example.com coin'
|
||||
)
|
||||
),
|
||||
...web3React,
|
||||
useWeb3React: () => ({
|
||||
chainId: 1,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
test('PositionListItem should not render when the name contains a url', () => {
|
||||
const susToken0Address = '0x39AA39c021dfbaE8faC545936693aC917d5E7563'
|
||||
|
||||
beforeEach(() => {
|
||||
const susToken0 = new Token(1, susToken0Address, 8, 'https://www.example.com', 'example.com coin')
|
||||
mocked(useToken).mockImplementation((tokenAddress?: string | null | undefined) => {
|
||||
if (!tokenAddress) return null
|
||||
if (tokenAddress === susToken0.address) return susToken0
|
||||
return new Token(1, tokenAddress, 8, 'symbol', 'name')
|
||||
})
|
||||
mocked(usePool).mockReturnValue([
|
||||
PoolState.EXISTS,
|
||||
new Pool(susToken0, USDC_MAINNET, FeeAmount.HIGH, '2437312313659959819381354528', '10272714736694327408', -69633),
|
||||
])
|
||||
mocked(unwrappedToken).mockReturnValue(susToken0)
|
||||
})
|
||||
|
||||
test('PositionListItem should not render when token0 symbol contains a url', () => {
|
||||
const positionDetails = {
|
||||
token0: '0x39AA39c021dfbaE8faC545936693aC917d5E7563',
|
||||
token1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||
token0: susToken0Address,
|
||||
token1: USDC_MAINNET.address,
|
||||
tokenId: BigNumber.from(436148),
|
||||
fee: 100,
|
||||
liquidity: BigNumber.from('0x5c985aff8059be04'),
|
||||
tickLower: -800,
|
||||
tickUpper: 1600,
|
||||
}
|
||||
render(<PositionListItem {...positionDetails} />)
|
||||
screen.debug()
|
||||
expect(screen.queryByText('.com', { exact: false })).toBe(null)
|
||||
const { container } = render(<PositionListItem {...positionDetails} />)
|
||||
expect(container).toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
test('PositionListItem should not render when token1 symbol contains a url', () => {
|
||||
const positionDetails = {
|
||||
token0: USDC_MAINNET.address,
|
||||
token1: susToken0Address,
|
||||
tokenId: BigNumber.from(436148),
|
||||
fee: 100,
|
||||
liquidity: BigNumber.from('0x5c985aff8059be04'),
|
||||
tickLower: -800,
|
||||
tickUpper: 1600,
|
||||
}
|
||||
const { container } = render(<PositionListItem {...positionDetails} />)
|
||||
expect(container).toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
test('PositionListItem should render a position', () => {
|
||||
const positionDetails = {
|
||||
token0: USDC_MAINNET.address,
|
||||
token1: WETH9[SupportedChainId.MAINNET].address,
|
||||
tokenId: BigNumber.from(436148),
|
||||
fee: 100,
|
||||
liquidity: BigNumber.from('0x5c985aff8059be04'),
|
||||
tickLower: -800,
|
||||
tickUpper: 1600,
|
||||
}
|
||||
const { container } = render(<PositionListItem {...positionDetails} />)
|
||||
expect(container).not.toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
@@ -203,12 +203,9 @@ export default function PositionListItem({
|
||||
|
||||
const removed = liquidity?.eq(0)
|
||||
|
||||
const containsURL = useMemo(
|
||||
() => [token0?.name, token0?.symbol, token1?.name, token1?.symbol].some((testString) => hasURL(testString)),
|
||||
[token0?.name, token0?.symbol, token1?.name, token1?.symbol]
|
||||
)
|
||||
const shouldHidePosition = hasURL(token0?.symbol) || hasURL(token1?.symbol)
|
||||
|
||||
if (containsURL) {
|
||||
if (shouldHidePosition) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Currency, Percent } from '@uniswap/sdk-core'
|
||||
import { FeeAmount } from '@uniswap/v3-sdk'
|
||||
import { RoutingDiagramEntry } from 'components/swap/SwapRoute'
|
||||
import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens'
|
||||
import { render } from 'test-utils'
|
||||
import { render } from 'test-utils/render'
|
||||
|
||||
import RoutingDiagram from './RoutingDiagram'
|
||||
|
||||
|
||||
@@ -1,425 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders currency rows correctly when currencies list is non-empty 1`] = `
|
||||
<DocumentFragment>
|
||||
.c0 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
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;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.c11 {
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
margin-left: 4px;
|
||||
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;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
color: #98A1C0;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
display: grid;
|
||||
grid-auto-rows: auto;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
padding: 4px 20px;
|
||||
height: 56px;
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(auto,1fr) auto minmax(0,72px);
|
||||
grid-gap: 16px;
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c3:hover {
|
||||
background-color: #98A1C014;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
margin-left: 0.3em;
|
||||
}
|
||||
|
||||
<div
|
||||
style="padding-right: 4px;"
|
||||
>
|
||||
<div
|
||||
class="CurrencyList_scrollbarStyle__1pi21y70"
|
||||
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||
>
|
||||
<div
|
||||
style="height: 168px; width: 100%;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c2 c3 token-item-0x6B175474E89094C44Da98b954EedeAC495271d0F"
|
||||
style="position: absolute; left: 0px; top: 0px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
CurrencyLogo currency=DAI
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div
|
||||
class="c6 css-vurnku"
|
||||
title="Dai Stablecoin"
|
||||
>
|
||||
Dai Stablecoin
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div
|
||||
class="c8"
|
||||
>
|
||||
<svg
|
||||
class="c9"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c10 css-yfjwjl"
|
||||
>
|
||||
DAI
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c11"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1 c2 c3 token-item-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
||||
style="position: absolute; left: 0px; top: 56px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
CurrencyLogo currency=USDC
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div
|
||||
class="c6 css-vurnku"
|
||||
title="USD//C"
|
||||
>
|
||||
USD//C
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div
|
||||
class="c8"
|
||||
>
|
||||
<svg
|
||||
class="c9"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c10 css-yfjwjl"
|
||||
>
|
||||
USDC
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c11"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1 c2 c3 token-item-0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
||||
style="position: absolute; left: 0px; top: 112px; height: 56px; width: 100%;"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
CurrencyLogo currency=WBTC
|
||||
</div>
|
||||
<div
|
||||
class="c5"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div
|
||||
class="c6 css-vurnku"
|
||||
title="Wrapped BTC"
|
||||
>
|
||||
Wrapped BTC
|
||||
</div>
|
||||
<div
|
||||
class="c7"
|
||||
>
|
||||
<div
|
||||
class="c8"
|
||||
>
|
||||
<svg
|
||||
class="c9"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12"
|
||||
y1="9"
|
||||
y2="13"
|
||||
/>
|
||||
<line
|
||||
x1="12"
|
||||
x2="12.01"
|
||||
y1="17"
|
||||
y2="17"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c10 css-yfjwjl"
|
||||
>
|
||||
WBTC
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<div
|
||||
class="c0 c1 c11"
|
||||
style="justify-self: flex-end;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`renders loading rows when isLoading is true 1`] = `
|
||||
<DocumentFragment>
|
||||
.c0 {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.c0 > div {
|
||||
-webkit-animation: fAQEyV 1.5s infinite;
|
||||
animation: fAQEyV 1.5s infinite;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
background: linear-gradient( to left,#F5F6FC 25%,#E8ECFB 50%,#F5F6FC 75% );
|
||||
background-size: 400%;
|
||||
border-radius: 12px;
|
||||
height: 2.4em;
|
||||
will-change: background-position;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
grid-column-gap: 0.5em;
|
||||
grid-template-columns: repeat(12,1fr);
|
||||
max-width: 960px;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
|
||||
.c1 > div:nth-child(4n + 1) {
|
||||
grid-column: 1 / 8;
|
||||
height: 1em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.c1 > div:nth-child(4n + 2) {
|
||||
grid-column: 12;
|
||||
height: 1em;
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.c1 > div:nth-child(4n + 3) {
|
||||
grid-column: 1 / 4;
|
||||
height: 0.75em;
|
||||
}
|
||||
|
||||
<div
|
||||
style="padding-right: 4px;"
|
||||
>
|
||||
<div
|
||||
class="CurrencyList_scrollbarStyle__1pi21y70"
|
||||
style="position: relative; height: 10px; width: 100%; overflow: auto; will-change: transform; direction: ltr;"
|
||||
>
|
||||
<div
|
||||
style="height: 560px; width: 100%;"
|
||||
>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="c0 c1"
|
||||
>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -1,7 +1,8 @@
|
||||
import { screen } from '@testing-library/react'
|
||||
import { Currency, CurrencyAmount as mockCurrencyAmount, Token as mockToken } from '@uniswap/sdk-core'
|
||||
import { DAI, USDC_MAINNET, WBTC } from 'constants/tokens'
|
||||
import * as mockJSBI from 'jsbi'
|
||||
import { render } from 'test-utils'
|
||||
import { render } from 'test-utils/render'
|
||||
|
||||
import CurrencyList from '.'
|
||||
|
||||
@@ -25,11 +26,11 @@ jest.mock(
|
||||
jest.mock('@web3-react/core', () => {
|
||||
const web3React = jest.requireActual('@web3-react/core')
|
||||
return {
|
||||
...web3React,
|
||||
useWeb3React: () => ({
|
||||
account: '123',
|
||||
isActive: true,
|
||||
}),
|
||||
...web3React,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -42,37 +43,38 @@ jest.mock('../../../state/connection/hooks', () => {
|
||||
})
|
||||
|
||||
it('renders loading rows when isLoading is true', () => {
|
||||
const { asFragment } = render(
|
||||
const component = render(
|
||||
<CurrencyList
|
||||
height={10}
|
||||
currencies={[]}
|
||||
otherListTokens={[]}
|
||||
selectedCurrency={null}
|
||||
onCurrencySelect={noOp}
|
||||
showImportView={noOp}
|
||||
setImportToken={noOp}
|
||||
isLoading={true}
|
||||
searchQuery=""
|
||||
isAddressSearch=""
|
||||
/>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
expect(component.findByTestId('loading-rows')).toBeTruthy()
|
||||
expect(screen.queryByText('Wrapped BTC')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('DAI')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('USDC')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders currency rows correctly when currencies list is non-empty', () => {
|
||||
const { asFragment } = render(
|
||||
render(
|
||||
<CurrencyList
|
||||
height={10}
|
||||
currencies={[DAI, USDC_MAINNET, WBTC]}
|
||||
otherListTokens={[]}
|
||||
selectedCurrency={null}
|
||||
onCurrencySelect={noOp}
|
||||
showImportView={noOp}
|
||||
setImportToken={noOp}
|
||||
isLoading={false}
|
||||
searchQuery=""
|
||||
isAddressSearch=""
|
||||
/>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
expect(screen.getByText('Wrapped BTC')).toBeInTheDocument()
|
||||
expect(screen.getByText('DAI')).toBeInTheDocument()
|
||||
expect(screen.getByText('USDC')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -20,7 +20,7 @@ import CurrencyLogo from '../../Logo/CurrencyLogo'
|
||||
import Row, { RowFixed } from '../../Row'
|
||||
import { MouseoverTooltip } from '../../Tooltip'
|
||||
import { LoadingRows, MenuItem } from '../styleds'
|
||||
import * as styles from './index.css'
|
||||
import { scrollbarStyle } from './index.css'
|
||||
|
||||
function currencyKey(currency: Currency): string {
|
||||
return currency.isToken ? currency.address : 'ETHER'
|
||||
@@ -65,6 +65,10 @@ const WarningContainer = styled.div`
|
||||
margin-left: 0.3em;
|
||||
`
|
||||
|
||||
const ListWrapper = styled.div`
|
||||
padding-right: 0.25rem;
|
||||
`
|
||||
|
||||
function Balance({ balance }: { balance: CurrencyAmount<Currency> }) {
|
||||
return <StyledBalanceText title={balance.toExact()}>{balance.toSignificant(4)}</StyledBalanceText>
|
||||
}
|
||||
@@ -212,7 +216,7 @@ export const formatAnalyticsEventProperties = (
|
||||
})
|
||||
|
||||
const LoadingRow = () => (
|
||||
<LoadingRows>
|
||||
<LoadingRows data-testid="loading-rows">
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
@@ -290,10 +294,10 @@ export default function CurrencyList({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div style={{ paddingRight: '4px' }}>
|
||||
<ListWrapper>
|
||||
{isLoading ? (
|
||||
<FixedSizeList
|
||||
className={styles.scrollbarStyle}
|
||||
className={scrollbarStyle}
|
||||
height={height}
|
||||
ref={fixedListRef as any}
|
||||
width="100%"
|
||||
@@ -305,7 +309,7 @@ export default function CurrencyList({
|
||||
</FixedSizeList>
|
||||
) : (
|
||||
<FixedSizeList
|
||||
className={styles.scrollbarStyle}
|
||||
className={scrollbarStyle}
|
||||
height={height}
|
||||
ref={fixedListRef as any}
|
||||
width="100%"
|
||||
@@ -317,6 +321,6 @@ export default function CurrencyList({
|
||||
{Row}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</div>
|
||||
</ListWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen } from 'test-utils'
|
||||
import { fireEvent, render, screen } from 'test-utils/render'
|
||||
|
||||
import { ResizingTextArea, TextInput } from './'
|
||||
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { transparentize } from 'polished'
|
||||
import { ReactNode, useCallback, useEffect, useState } from 'react'
|
||||
import { ReactNode, useEffect, useState } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import Popover, { PopoverProps } from '../Popover'
|
||||
|
||||
// TODO(WEB-3163): migrate noops throughout web to a shared util file.
|
||||
const noop = () => null
|
||||
|
||||
export const TooltipContainer = styled.div`
|
||||
max-width: 256px;
|
||||
cursor: default;
|
||||
padding: 0.6rem 1rem;
|
||||
pointer-events: auto;
|
||||
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-weight: 400;
|
||||
@@ -25,7 +29,6 @@ interface TooltipProps extends Omit<PopoverProps, 'content'> {
|
||||
text: ReactNode
|
||||
open?: () => void
|
||||
close?: () => void
|
||||
noOp?: () => void
|
||||
disableHover?: boolean // disable the hover and content display
|
||||
timeout?: number
|
||||
}
|
||||
@@ -33,17 +36,19 @@ interface TooltipProps extends Omit<PopoverProps, 'content'> {
|
||||
interface TooltipContentProps extends Omit<PopoverProps, 'content'> {
|
||||
content: ReactNode
|
||||
onOpen?: () => void
|
||||
open?: () => void
|
||||
close?: () => void
|
||||
// whether to wrap the content in a `TooltipContainer`
|
||||
wrap?: boolean
|
||||
disableHover?: boolean // disable the hover and content display
|
||||
}
|
||||
|
||||
export default function Tooltip({ text, open, close, noOp, disableHover, ...rest }: TooltipProps) {
|
||||
export default function Tooltip({ text, open, close, disableHover, ...rest }: TooltipProps) {
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
text && (
|
||||
<TooltipContainer onMouseEnter={disableHover ? noOp : open} onMouseLeave={disableHover ? noOp : close}>
|
||||
<TooltipContainer onMouseEnter={disableHover ? noop : open} onMouseLeave={disableHover ? noop : close}>
|
||||
{text}
|
||||
</TooltipContainer>
|
||||
)
|
||||
@@ -53,15 +58,28 @@ export default function Tooltip({ text, open, close, noOp, disableHover, ...rest
|
||||
)
|
||||
}
|
||||
|
||||
function TooltipContent({ content, wrap = false, ...rest }: TooltipContentProps) {
|
||||
return <Popover content={wrap ? <TooltipContainer>{content}</TooltipContainer> : content} {...rest} />
|
||||
function TooltipContent({ content, wrap = false, open, close, disableHover, ...rest }: TooltipContentProps) {
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
wrap ? (
|
||||
<TooltipContainer onMouseEnter={disableHover ? noop : open} onMouseLeave={disableHover ? noop : close}>
|
||||
{content}
|
||||
</TooltipContainer>
|
||||
) : (
|
||||
content
|
||||
)
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/** Standard text tooltip. */
|
||||
export function MouseoverTooltip({ text, disableHover, children, timeout, ...rest }: Omit<TooltipProps, 'show'>) {
|
||||
const [show, setShow] = useState(false)
|
||||
const open = useCallback(() => text && setShow(true), [text, setShow])
|
||||
const close = useCallback(() => setShow(false), [setShow])
|
||||
const open = () => text && setShow(true)
|
||||
const close = () => setShow(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (show && timeout) {
|
||||
@@ -76,18 +94,16 @@ export function MouseoverTooltip({ text, disableHover, children, timeout, ...res
|
||||
return
|
||||
}, [timeout, show])
|
||||
|
||||
const noOp = () => null
|
||||
return (
|
||||
<Tooltip
|
||||
{...rest}
|
||||
open={open}
|
||||
close={close}
|
||||
noOp={noOp}
|
||||
disableHover={disableHover}
|
||||
show={show}
|
||||
text={disableHover ? null : text}
|
||||
>
|
||||
<div onMouseEnter={disableHover ? noOp : open} onMouseLeave={disableHover || timeout ? noOp : close}>
|
||||
<div onMouseEnter={disableHover ? noop : open} onMouseLeave={disableHover || timeout ? noop : close}>
|
||||
{children}
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -103,18 +119,23 @@ export function MouseoverTooltipContent({
|
||||
...rest
|
||||
}: Omit<TooltipContentProps, 'show'>) {
|
||||
const [show, setShow] = useState(false)
|
||||
const open = useCallback(() => {
|
||||
const open = () => {
|
||||
setShow(true)
|
||||
openCallback?.()
|
||||
}, [openCallback])
|
||||
const close = useCallback(() => setShow(false), [setShow])
|
||||
}
|
||||
const close = () => {
|
||||
setShow(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<TooltipContent {...rest} show={!disableHover && show} content={disableHover ? null : content}>
|
||||
<div
|
||||
style={{ display: 'inline-block', lineHeight: 0, padding: '0.25rem' }}
|
||||
onMouseEnter={open}
|
||||
onMouseLeave={close}
|
||||
>
|
||||
<TooltipContent
|
||||
{...rest}
|
||||
open={open}
|
||||
close={close}
|
||||
show={!disableHover && show}
|
||||
content={disableHover ? null : content}
|
||||
>
|
||||
<div onMouseEnter={open} onMouseLeave={close}>
|
||||
{children}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import UniwalletModal from 'components/AccountDrawer/UniwalletModal'
|
||||
import UniswapWalletBanner from 'components/Banner/UniswapWalletBanner'
|
||||
import AddressClaimModal from 'components/claim/AddressClaimModal'
|
||||
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
|
||||
import FiatOnrampModal from 'components/FiatOnrampModal'
|
||||
import UniwalletModal from 'components/WalletDropdown/UniwalletModal'
|
||||
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
|
||||
import { lazy } from 'react'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BrowserEvent, InterfaceElementName, InterfaceEventName } from '@uniswap
|
||||
import Loader from 'components/Icons/LoadingSpinner'
|
||||
import { Connection, ConnectionType } from 'connection'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useIsDarkMode } from 'theme/components/ThemeToggle'
|
||||
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
|
||||
|
||||
import NewBadge from './NewBadge'
|
||||
@@ -68,6 +69,7 @@ type OptionProps = {
|
||||
}
|
||||
export default function Option({ connection, pendingConnectionType, activate }: OptionProps) {
|
||||
const isPending = pendingConnectionType === connection.type
|
||||
const isDarkMode = useIsDarkMode()
|
||||
const content = (
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
@@ -83,7 +85,7 @@ export default function Option({ connection, pendingConnectionType, activate }:
|
||||
>
|
||||
<OptionCardLeft>
|
||||
<IconWrapper>
|
||||
<img src={connection.getIcon?.()} alt="Icon" />
|
||||
<img src={connection.getIcon?.(isDarkMode)} alt="Icon" />
|
||||
</IconWrapper>
|
||||
<HeaderText>{connection.getName()}</HeaderText>
|
||||
{connection.isNew && <NewBadge />}
|
||||
|
||||
@@ -2,11 +2,11 @@ import { sendAnalyticsEvent, user } from '@uniswap/analytics'
|
||||
import { CustomUserProperties, InterfaceEventName, WalletConnectionResult } from '@uniswap/analytics-events'
|
||||
import { getWalletMeta } from '@uniswap/conedison/provider/meta'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useAccountDrawer } from 'components/AccountDrawer'
|
||||
import IconButton from 'components/AccountDrawer/IconButton'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { useWalletDrawer } from 'components/WalletDropdown'
|
||||
import IconButton from 'components/WalletDropdown/IconButton'
|
||||
import { Connection, ConnectionType, getConnections, networkConnection } from 'connection'
|
||||
import { useGetConnection } from 'connection'
|
||||
import { ErrorCode } from 'connection/utils'
|
||||
@@ -84,7 +84,7 @@ function didUserReject(connection: Connection, error: any): boolean {
|
||||
export default function WalletModal({ openSettings }: { openSettings: () => void }) {
|
||||
const dispatch = useAppDispatch()
|
||||
const { connector, account, chainId, provider } = useWeb3React()
|
||||
const [drawerOpen, toggleWalletDrawer] = useWalletDrawer()
|
||||
const [drawerOpen, toggleWalletDrawer] = useAccountDrawer()
|
||||
|
||||
const [connectedWallets, addWalletToConnectedWallets] = useConnectedWallets()
|
||||
const [lastActiveWalletAddress, setLastActiveWalletAddress] = useState<string | undefined>(account)
|
||||
@@ -156,15 +156,16 @@ export default function WalletModal({ openSettings }: { openSettings: () => void
|
||||
setPendingError(undefined)
|
||||
|
||||
await connection.connector.activate()
|
||||
console.debug(`connection activated: ${connection.getName()}`)
|
||||
dispatch(updateSelectedWallet({ wallet: connection.type }))
|
||||
if (drawerOpenRef.current) toggleWalletDrawer()
|
||||
} catch (error) {
|
||||
console.debug(`web3-react connection error: ${error}`)
|
||||
// TODO(WEB-3162): re-add special treatment for already-pending injected errors
|
||||
if (didUserReject(connection, error)) {
|
||||
setPendingConnection(undefined)
|
||||
} // Prevents showing error caused by MetaMask being prompted twice
|
||||
else if (error?.code !== ErrorCode.MM_ALREADY_PENDING) {
|
||||
console.debug(`web3-react connection error: ${error}`)
|
||||
setPendingError(error.message)
|
||||
} else {
|
||||
setPendingError(error)
|
||||
|
||||
sendAnalyticsEvent(InterfaceEventName.WALLET_CONNECT_TXN_COMPLETED, {
|
||||
result: WalletConnectionResult.FAILED,
|
||||
|
||||
@@ -2,19 +2,17 @@ import { Trans } from '@lingui/macro'
|
||||
import { sendAnalyticsEvent, TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import PortfolioDrawer, { useAccountDrawer } from 'components/AccountDrawer'
|
||||
import PrefetchBalancesWrapper from 'components/AccountDrawer/PrefetchBalancesWrapper'
|
||||
import Loader from 'components/Icons/LoadingSpinner'
|
||||
import { IconWrapper } from 'components/Identicon/StatusIcon'
|
||||
import WalletDropdown, { useWalletDrawer } from 'components/WalletDropdown'
|
||||
import PrefetchBalancesWrapper from 'components/WalletDropdown/PrefetchBalancesWrapper'
|
||||
import { useGetConnection } from 'connection'
|
||||
import { Portal } from 'nft/components/common/Portal'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { getIsValidSwapQuote } from 'pages/Swap'
|
||||
import { darken } from 'polished'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import { useDerivedSwapInfo } from 'state/swap/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { colors } from 'theme/colors'
|
||||
import { flexRowNoWrap } from 'theme/styles'
|
||||
@@ -153,16 +151,11 @@ function Web3StatusInner() {
|
||||
const { account, connector, chainId, ENSName } = useWeb3React()
|
||||
const getConnection = useGetConnection()
|
||||
const connection = getConnection(connector)
|
||||
const {
|
||||
trade: { state: tradeState, trade },
|
||||
inputError: swapInputError,
|
||||
} = useDerivedSwapInfo()
|
||||
const validSwapQuote = getIsValidSwapQuote(trade, tradeState, swapInputError)
|
||||
const [, toggleWalletDrawer] = useWalletDrawer()
|
||||
const [, toggleAccountDrawer] = useAccountDrawer()
|
||||
const handleWalletDropdownClick = useCallback(() => {
|
||||
sendAnalyticsEvent(InterfaceEventName.ACCOUNT_DROPDOWN_BUTTON_CLICKED)
|
||||
toggleWalletDrawer()
|
||||
}, [toggleWalletDrawer])
|
||||
toggleAccountDrawer()
|
||||
}, [toggleAccountDrawer])
|
||||
const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable)
|
||||
|
||||
const error = useAppSelector((state) => state.connection.errorByConnectionType[getConnection(connector).type])
|
||||
@@ -223,7 +216,6 @@ function Web3StatusInner() {
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={InterfaceEventName.CONNECT_WALLET_BUTTON_CLICKED}
|
||||
properties={{ received_swap_quote: validSwapQuote }}
|
||||
element={InterfaceElementName.CONNECT_WALLET_BUTTON}
|
||||
>
|
||||
<Web3StatusConnectWrapper
|
||||
@@ -241,13 +233,12 @@ function Web3StatusInner() {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-unused-modules
|
||||
export default function Web3Status() {
|
||||
return (
|
||||
<PrefetchBalancesWrapper>
|
||||
<Web3StatusInner />
|
||||
<Portal>
|
||||
<WalletDropdown />
|
||||
<PortfolioDrawer />
|
||||
</Portal>
|
||||
</PrefetchBalancesWrapper>
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
SwapWidgetSkeleton,
|
||||
} from '@uniswap/widgets'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useToggleWalletDrawer } from 'components/WalletDropdown'
|
||||
import { useToggleAccountDrawer } from 'components/AccountDrawer'
|
||||
import { useActiveLocale } from 'hooks/useActiveLocale'
|
||||
import {
|
||||
formatPercentInBasisPointsNumber,
|
||||
@@ -67,7 +67,7 @@ export default function Widget({
|
||||
const { settings } = useSyncWidgetSettings()
|
||||
const { transactions } = useSyncWidgetTransactions()
|
||||
|
||||
const toggleWalletDrawer = useToggleWalletDrawer()
|
||||
const toggleWalletDrawer = useToggleAccountDrawer()
|
||||
const onConnectWalletClick = useCallback(() => {
|
||||
toggleWalletDrawer()
|
||||
return false // prevents the in-widget wallet modal from opening
|
||||
|
||||
111
src/components/swap/SwapBuyFiatButton.test.tsx
Normal file
111
src/components/swap/SwapBuyFiatButton.test.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useAccountDrawer } from 'components/AccountDrawer'
|
||||
import { fireEvent, render, screen } from 'test-utils/render'
|
||||
|
||||
import { useFiatOnrampAvailability, useOpenModal } from '../../state/application/hooks'
|
||||
import SwapBuyFiatButton, { MOONPAY_REGION_AVAILABILITY_ARTICLE } from './SwapBuyFiatButton'
|
||||
|
||||
jest.mock('@web3-react/core', () => {
|
||||
const web3React = jest.requireActual('@web3-react/core')
|
||||
return {
|
||||
...web3React,
|
||||
useWeb3React: jest.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock('../../state/application/hooks')
|
||||
const mockUseFiatOnrampAvailability = useFiatOnrampAvailability as jest.MockedFunction<typeof useFiatOnrampAvailability>
|
||||
const mockUseOpenModal = useOpenModal as jest.MockedFunction<typeof useOpenModal>
|
||||
|
||||
jest.mock('components/AccountDrawer')
|
||||
const mockuseAccountDrawer = useAccountDrawer as jest.MockedFunction<typeof useAccountDrawer>
|
||||
|
||||
const mockUseFiatOnRampsUnavailable = (shouldCheck: boolean) => {
|
||||
return {
|
||||
available: false,
|
||||
availabilityChecked: shouldCheck,
|
||||
error: null,
|
||||
loading: false,
|
||||
}
|
||||
}
|
||||
|
||||
const mockUseFiatOnRampsAvailable = (shouldCheck: boolean) => {
|
||||
if (shouldCheck) {
|
||||
return {
|
||||
available: true,
|
||||
availabilityChecked: true,
|
||||
error: null,
|
||||
loading: false,
|
||||
}
|
||||
}
|
||||
return {
|
||||
available: false,
|
||||
availabilityChecked: false,
|
||||
error: null,
|
||||
loading: false,
|
||||
}
|
||||
}
|
||||
|
||||
describe('SwapBuyFiatButton.tsx', () => {
|
||||
let toggleWalletDrawer: jest.Mock<any, any>
|
||||
let useOpenModal: jest.Mock<any, any>
|
||||
|
||||
beforeAll(() => {
|
||||
toggleWalletDrawer = jest.fn()
|
||||
useOpenModal = jest.fn()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks()
|
||||
;(useWeb3React as jest.Mock).mockReturnValue({
|
||||
account: undefined,
|
||||
isActive: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('matches base snapshot', () => {
|
||||
mockUseFiatOnrampAvailability.mockImplementation(mockUseFiatOnRampsUnavailable)
|
||||
mockuseAccountDrawer.mockImplementation(() => [false, toggleWalletDrawer])
|
||||
const { asFragment } = render(<SwapBuyFiatButton />)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('fiat on ramps available in region, account unconnected', async () => {
|
||||
mockUseFiatOnrampAvailability.mockImplementation(mockUseFiatOnRampsAvailable)
|
||||
mockuseAccountDrawer.mockImplementation(() => [false, toggleWalletDrawer])
|
||||
mockUseOpenModal.mockImplementation(() => useOpenModal)
|
||||
render(<SwapBuyFiatButton />)
|
||||
await userEvent.click(screen.getByTestId('buy-fiat-button'))
|
||||
expect(toggleWalletDrawer).toHaveBeenCalledTimes(1)
|
||||
expect(screen.queryByTestId('fiat-on-ramp-unavailable-tooltip')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('fiat on ramps available in region, account connected', async () => {
|
||||
;(useWeb3React as jest.Mock).mockReturnValue({
|
||||
account: '0x52270d8234b864dcAC9947f510CE9275A8a116Db',
|
||||
isActive: true,
|
||||
})
|
||||
mockUseFiatOnrampAvailability.mockImplementation(mockUseFiatOnRampsAvailable)
|
||||
mockuseAccountDrawer.mockImplementation(() => [false, toggleWalletDrawer])
|
||||
mockUseOpenModal.mockImplementation(() => useOpenModal)
|
||||
render(<SwapBuyFiatButton />)
|
||||
expect(screen.getByTestId('buy-fiat-flow-incomplete-indicator')).toBeInTheDocument()
|
||||
await userEvent.click(screen.getByTestId('buy-fiat-button'))
|
||||
expect(toggleWalletDrawer).toHaveBeenCalledTimes(0)
|
||||
expect(useOpenModal).toHaveBeenCalledTimes(1)
|
||||
expect(screen.queryByTestId('fiat-on-ramp-unavailable-tooltip')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('buy-fiat-flow-incomplete-indicator')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('fiat on ramps unavailable in region', async () => {
|
||||
mockUseFiatOnrampAvailability.mockImplementation(mockUseFiatOnRampsUnavailable)
|
||||
mockuseAccountDrawer.mockImplementation(() => [false, toggleWalletDrawer])
|
||||
render(<SwapBuyFiatButton />)
|
||||
await userEvent.click(screen.getByTestId('buy-fiat-button'))
|
||||
fireEvent.mouseOver(screen.getByTestId('buy-fiat-button'))
|
||||
expect(await screen.findByTestId('fiat-on-ramp-unavailable-tooltip')).toBeInTheDocument()
|
||||
expect(await screen.findByText(/Learn more/i)).toHaveAttribute('href', MOONPAY_REGION_AVAILABILITY_ARTICLE)
|
||||
expect(await screen.findByTestId('buy-fiat-button')).toBeDisabled()
|
||||
})
|
||||
})
|
||||
146
src/components/swap/SwapBuyFiatButton.tsx
Normal file
146
src/components/swap/SwapBuyFiatButton.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useAccountDrawer } from 'components/AccountDrawer'
|
||||
import { ButtonText } from 'components/Button'
|
||||
import { MouseoverTooltipContent } from 'components/Tooltip'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useBuyFiatFlowCompleted } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink } from 'theme'
|
||||
|
||||
import { useFiatOnrampAvailability, useOpenModal } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
|
||||
const Dot = styled.div`
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
background-color: ${({ theme }) => theme.accentActive};
|
||||
border-radius: 50%;
|
||||
`
|
||||
|
||||
export const MOONPAY_REGION_AVAILABILITY_ARTICLE =
|
||||
'https://support.uniswap.org/hc/en-us/articles/11306664890381-Why-isn-t-MoonPay-available-in-my-region-'
|
||||
|
||||
enum BuyFiatFlowState {
|
||||
// Default initial state. User is not actively trying to buy fiat.
|
||||
INACTIVE,
|
||||
// Buy fiat flow is active and region availability has been checked.
|
||||
ACTIVE_CHECKING_REGION,
|
||||
// Buy fiat flow is active, feature is available in user's region & needs wallet connection.
|
||||
ACTIVE_NEEDS_ACCOUNT,
|
||||
}
|
||||
|
||||
const StyledTextButton = styled(ButtonText)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
gap: 4px;
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
|
||||
export default function SwapBuyFiatButton() {
|
||||
const { account } = useWeb3React()
|
||||
const openFiatOnRampModal = useOpenModal(ApplicationModal.FIAT_ONRAMP)
|
||||
const [buyFiatFlowCompleted, setBuyFiatFlowCompleted] = useBuyFiatFlowCompleted()
|
||||
const [checkFiatRegionAvailability, setCheckFiatRegionAvailability] = useState(false)
|
||||
const {
|
||||
available: fiatOnrampAvailable,
|
||||
availabilityChecked: fiatOnrampAvailabilityChecked,
|
||||
loading: fiatOnrampAvailabilityLoading,
|
||||
} = useFiatOnrampAvailability(checkFiatRegionAvailability)
|
||||
const [buyFiatFlowState, setBuyFiatFlowState] = useState(BuyFiatFlowState.INACTIVE)
|
||||
const [walletDrawerOpen, toggleWalletDrawer] = useAccountDrawer()
|
||||
|
||||
/*
|
||||
* Depending on the current state of the buy fiat flow the user is in (buyFiatFlowState),
|
||||
* the desired behavior of clicking the 'Buy' button is different.
|
||||
* 1) Initially upon first click, need to check the availability of the feature in the user's
|
||||
* region, and continue the flow.
|
||||
* 2) If the feature is available in the user's region, need to connect a wallet, and continue
|
||||
* the flow.
|
||||
* 3) If the feature is available and a wallet account is connected, show fiat on ramp modal.
|
||||
* 4) If the feature is unavailable, show feature unavailable tooltip.
|
||||
*/
|
||||
const handleBuyCrypto = useCallback(() => {
|
||||
if (!fiatOnrampAvailabilityChecked) {
|
||||
setCheckFiatRegionAvailability(true)
|
||||
setBuyFiatFlowState(BuyFiatFlowState.ACTIVE_CHECKING_REGION)
|
||||
} else if (fiatOnrampAvailable && !account && !walletDrawerOpen) {
|
||||
toggleWalletDrawer()
|
||||
setBuyFiatFlowState(BuyFiatFlowState.ACTIVE_NEEDS_ACCOUNT)
|
||||
} else if (fiatOnrampAvailable && account) {
|
||||
openFiatOnRampModal()
|
||||
setBuyFiatFlowCompleted(true)
|
||||
setBuyFiatFlowState(BuyFiatFlowState.INACTIVE)
|
||||
} else if (!fiatOnrampAvailable) {
|
||||
setBuyFiatFlowCompleted(true)
|
||||
setBuyFiatFlowState(BuyFiatFlowState.INACTIVE)
|
||||
}
|
||||
}, [
|
||||
fiatOnrampAvailabilityChecked,
|
||||
fiatOnrampAvailable,
|
||||
account,
|
||||
walletDrawerOpen,
|
||||
toggleWalletDrawer,
|
||||
openFiatOnRampModal,
|
||||
setBuyFiatFlowCompleted,
|
||||
])
|
||||
|
||||
// Continue buy fiat flow automatically when requisite state changes have occured.
|
||||
useEffect(() => {
|
||||
if (
|
||||
(buyFiatFlowState === BuyFiatFlowState.ACTIVE_CHECKING_REGION && fiatOnrampAvailabilityChecked) ||
|
||||
(account && buyFiatFlowState === BuyFiatFlowState.ACTIVE_NEEDS_ACCOUNT)
|
||||
) {
|
||||
handleBuyCrypto()
|
||||
}
|
||||
}, [account, handleBuyCrypto, buyFiatFlowState, fiatOnrampAvailabilityChecked])
|
||||
|
||||
const buyCryptoButtonDisabled =
|
||||
(!fiatOnrampAvailable && fiatOnrampAvailabilityChecked) ||
|
||||
fiatOnrampAvailabilityLoading ||
|
||||
// When wallet drawer is open AND user is in the connect wallet step of the buy fiat flow, disable buy fiat button.
|
||||
(walletDrawerOpen && buyFiatFlowState === BuyFiatFlowState.ACTIVE_NEEDS_ACCOUNT)
|
||||
|
||||
const fiatOnRampsUnavailableTooltipDisabled =
|
||||
!fiatOnrampAvailabilityChecked || (fiatOnrampAvailabilityChecked && fiatOnrampAvailable)
|
||||
|
||||
return (
|
||||
<MouseoverTooltipContent
|
||||
wrap
|
||||
content={
|
||||
<div data-testid="fiat-on-ramp-unavailable-tooltip">
|
||||
<Trans>Crypto purchases are not available in your region. </Trans>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={InterfaceElementName.FIAT_ON_RAMP_LEARN_MORE_LINK}
|
||||
>
|
||||
<ExternalLink href={MOONPAY_REGION_AVAILABILITY_ARTICLE} style={{ paddingLeft: '4px' }}>
|
||||
<Trans>Learn more</Trans>
|
||||
</ExternalLink>
|
||||
</TraceEvent>
|
||||
</div>
|
||||
}
|
||||
placement="bottom"
|
||||
disableHover={fiatOnRampsUnavailableTooltipDisabled}
|
||||
>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={InterfaceElementName.FIAT_ON_RAMP_BUY_BUTTON}
|
||||
properties={{ account_connected: !!account }}
|
||||
>
|
||||
<StyledTextButton onClick={handleBuyCrypto} disabled={buyCryptoButtonDisabled} data-testid="buy-fiat-button">
|
||||
<Trans>Buy</Trans>
|
||||
{!buyFiatFlowCompleted && <Dot data-testid="buy-fiat-flow-incomplete-indicator" />}
|
||||
</StyledTextButton>
|
||||
</TraceEvent>
|
||||
</MouseoverTooltipContent>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { useFiatOnRampButtonEnabled } from 'featureFlags/flags/fiatOnRampButton'
|
||||
import { subhead } from 'nft/css/common.css'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { ThemedText } from '../../theme'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import SettingsTab from '../Settings'
|
||||
import SwapBuyFiatButton from './SwapBuyFiatButton'
|
||||
|
||||
const StyledSwapHeader = styled.div`
|
||||
padding: 8px 12px;
|
||||
@@ -13,14 +15,27 @@ const StyledSwapHeader = styled.div`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
|
||||
const TextHeader = styled.div`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
margin-right: 8px;
|
||||
display: flex;
|
||||
line-height: 20px;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
export default function SwapHeader({ allowedSlippage }: { allowedSlippage: Percent }) {
|
||||
const fiatOnRampButtonEnabled = useFiatOnRampButtonEnabled()
|
||||
|
||||
return (
|
||||
<StyledSwapHeader>
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<ThemedText.DeprecatedBlack fontWeight={500} fontSize={16} style={{ marginRight: '8px' }}>
|
||||
<RowFixed style={{ gap: '8px' }}>
|
||||
<TextHeader className={subhead}>
|
||||
<Trans>Swap</Trans>
|
||||
</ThemedText.DeprecatedBlack>
|
||||
</TextHeader>
|
||||
{fiatOnRampButtonEnabled && <SwapBuyFiatButton />}
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
<SettingsTab placeholderSlippage={allowedSlippage} />
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SwapBuyFiatButton.tsx matches base snapshot 1`] = `
|
||||
<DocumentFragment>
|
||||
.c1 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
line-height: inherit;
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
color: white;
|
||||
background-color: primary;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
border-radius: 20px;
|
||||
outline: none;
|
||||
border: 1px solid transparent;
|
||||
color: #0D111C;
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
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;
|
||||
-webkit-flex-wrap: nowrap;
|
||||
-ms-flex-wrap: nowrap;
|
||||
flex-wrap: nowrap;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
will-change: transform;
|
||||
-webkit-transition: -webkit-transform 450ms ease;
|
||||
-webkit-transition: transform 450ms ease;
|
||||
transition: transform 450ms ease;
|
||||
-webkit-transform: perspective(1px) translateZ(0);
|
||||
-ms-transform: perspective(1px) translateZ(0);
|
||||
transform: perspective(1px) translateZ(0);
|
||||
}
|
||||
|
||||
.c2:disabled {
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.c2 > * {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.c2 > a {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
padding: 0;
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
background: none;
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.c3:focus {
|
||||
-webkit-text-decoration: underline;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.c3:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.c3:active {
|
||||
-webkit-text-decoration: underline;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.c3:disabled {
|
||||
opacity: 50%;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
display: inline-block;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
background-color: #4C82FB;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
color: #7780A0;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.c4:focus {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.c4:active {
|
||||
-webkit-text-decoration: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<div>
|
||||
<button
|
||||
class="c1 c2 c3 c4"
|
||||
data-testid="buy-fiat-button"
|
||||
>
|
||||
Buy
|
||||
<div
|
||||
class="c5"
|
||||
data-testid="buy-fiat-flow-incomplete-indicator"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -1,5 +1,7 @@
|
||||
import INJECTED_DARK_ICON from 'assets/svg/browser-wallet-dark.svg'
|
||||
import INJECTED_LIGHT_ICON from 'assets/svg/browser-wallet-light.svg'
|
||||
import { ConnectionType, getConnections, useGetConnection } from 'connection'
|
||||
import { renderHook } from 'test-utils'
|
||||
import { renderHook } from 'test-utils/render'
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules()
|
||||
@@ -108,6 +110,9 @@ describe('connection utility/metadata tests', () => {
|
||||
expect(injected.getName()).toBe('Browser Wallet')
|
||||
expect(injected.overrideActivate?.()).toBeFalsy()
|
||||
|
||||
expect(injected.getIcon?.(/* isDarkMode */ false)).toBe(INJECTED_LIGHT_ICON)
|
||||
expect(injected.getIcon?.(/* isDarkMode */ true)).toBe(INJECTED_DARK_ICON)
|
||||
|
||||
// Ensures we provide multiple connection options if in an unknown injected browser
|
||||
expect(displayed.length).toEqual(4)
|
||||
})
|
||||
|
||||
@@ -4,13 +4,14 @@ import { GnosisSafe } from '@web3-react/gnosis-safe'
|
||||
import { MetaMask } from '@web3-react/metamask'
|
||||
import { Network } from '@web3-react/network'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import COINBASE_ICON_URL from 'assets/images/coinbaseWalletIcon.svg'
|
||||
import GNOSIS_ICON_URL from 'assets/images/gnosis.png'
|
||||
import METAMASK_ICON_URL from 'assets/images/metamask.svg'
|
||||
import UNIWALLET_ICON_URL from 'assets/images/uniwallet.svg'
|
||||
import WALLET_CONNECT_ICON_URL from 'assets/images/walletConnectIcon.svg'
|
||||
import INJECTED_LIGHT_ICON_URL from 'assets/svg/browser-wallet-light.svg'
|
||||
import UNISWAP_LOGO_URL from 'assets/svg/logo.svg'
|
||||
import COINBASE_ICON from 'assets/images/coinbaseWalletIcon.svg'
|
||||
import GNOSIS_ICON from 'assets/images/gnosis.png'
|
||||
import METAMASK_ICON from 'assets/images/metamask.svg'
|
||||
import UNIWALLET_ICON from 'assets/images/uniwallet.svg'
|
||||
import WALLET_CONNECT_ICON from 'assets/images/walletConnectIcon.svg'
|
||||
import INJECTED_DARK_ICON from 'assets/svg/browser-wallet-dark.svg'
|
||||
import INJECTED_LIGHT_ICON from 'assets/svg/browser-wallet-light.svg'
|
||||
import UNISWAP_LOGO from 'assets/svg/logo.svg'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useCallback } from 'react'
|
||||
import { isMobile, isNonIOSPhone } from 'utils/userAgent'
|
||||
@@ -34,8 +35,7 @@ export interface Connection {
|
||||
connector: Connector
|
||||
hooks: Web3ReactHooks
|
||||
type: ConnectionType
|
||||
// TODO(WEB-3130): add darkmode check for icons
|
||||
getIcon?(): string
|
||||
getIcon?(isDarkMode: boolean): string
|
||||
shouldDisplay(): boolean
|
||||
overrideActivate?: () => boolean
|
||||
isNew?: boolean
|
||||
@@ -65,13 +65,15 @@ const getShouldAdvertiseMetaMask = () =>
|
||||
const getIsGenericInjector = () => getIsInjected() && !getIsMetaMaskWallet() && !getIsCoinbaseWallet()
|
||||
|
||||
const [web3Injected, web3InjectedHooks] = initializeConnector<MetaMask>((actions) => new MetaMask({ actions, onError }))
|
||||
|
||||
const injectedConnection: Connection = {
|
||||
// TODO(WEB-3131) re-add "Install MetaMask" string when no injector is present
|
||||
getName: () => (getIsGenericInjector() ? 'Browser Wallet' : 'MetaMask'),
|
||||
connector: web3Injected,
|
||||
hooks: web3InjectedHooks,
|
||||
type: ConnectionType.INJECTED,
|
||||
getIcon: () => (getIsGenericInjector() ? INJECTED_LIGHT_ICON_URL : METAMASK_ICON_URL),
|
||||
getIcon: (isDarkMode: boolean) =>
|
||||
getIsGenericInjector() ? (isDarkMode ? INJECTED_DARK_ICON : INJECTED_LIGHT_ICON) : METAMASK_ICON,
|
||||
shouldDisplay: () => getIsMetaMaskWallet() || getShouldAdvertiseMetaMask() || getIsGenericInjector(),
|
||||
// If on non-injected, non-mobile browser, prompt user to install Metamask
|
||||
overrideActivate: () => {
|
||||
@@ -82,14 +84,13 @@ const injectedConnection: Connection = {
|
||||
return false
|
||||
},
|
||||
}
|
||||
|
||||
const [web3GnosisSafe, web3GnosisSafeHooks] = initializeConnector<GnosisSafe>((actions) => new GnosisSafe({ actions }))
|
||||
export const gnosisSafeConnection: Connection = {
|
||||
getName: () => 'Gnosis Safe',
|
||||
connector: web3GnosisSafe,
|
||||
hooks: web3GnosisSafeHooks,
|
||||
type: ConnectionType.GNOSIS_SAFE,
|
||||
getIcon: () => GNOSIS_ICON_URL,
|
||||
getIcon: () => GNOSIS_ICON,
|
||||
shouldDisplay: () => false,
|
||||
}
|
||||
|
||||
@@ -101,7 +102,7 @@ export const walletConnectConnection: Connection = {
|
||||
connector: web3WalletConnect,
|
||||
hooks: web3WalletConnectHooks,
|
||||
type: ConnectionType.WALLET_CONNECT,
|
||||
getIcon: () => WALLET_CONNECT_ICON_URL,
|
||||
getIcon: () => WALLET_CONNECT_ICON,
|
||||
shouldDisplay: () => !getIsInjectedMobileBrowser(),
|
||||
}
|
||||
|
||||
@@ -113,7 +114,7 @@ export const uniwalletConnectConnection: Connection = {
|
||||
connector: web3UniwalletConnect,
|
||||
hooks: web3UniwalletConnectHooks,
|
||||
type: ConnectionType.UNIWALLET,
|
||||
getIcon: () => UNIWALLET_ICON_URL,
|
||||
getIcon: () => UNIWALLET_ICON,
|
||||
shouldDisplay: () => Boolean(!getIsInjectedMobileBrowser() && !isNonIOSPhone),
|
||||
isNew: true,
|
||||
}
|
||||
@@ -125,7 +126,7 @@ const [web3CoinbaseWallet, web3CoinbaseWalletHooks] = initializeConnector<Coinba
|
||||
options: {
|
||||
url: RPC_URLS[SupportedChainId.MAINNET][0],
|
||||
appName: 'Uniswap',
|
||||
appLogoUrl: UNISWAP_LOGO_URL,
|
||||
appLogoUrl: UNISWAP_LOGO,
|
||||
reloadOnDisconnect: false,
|
||||
},
|
||||
onError,
|
||||
@@ -137,7 +138,7 @@ const coinbaseWalletConnection: Connection = {
|
||||
connector: web3CoinbaseWallet,
|
||||
hooks: web3CoinbaseWalletHooks,
|
||||
type: ConnectionType.COINBASE_WALLET,
|
||||
getIcon: () => COINBASE_ICON_URL,
|
||||
getIcon: () => COINBASE_ICON,
|
||||
shouldDisplay: () =>
|
||||
Boolean((isMobile && !getIsInjectedMobileBrowser()) || !isMobile || getIsCoinbaseWalletBrowser()),
|
||||
// If on a mobile browser that isn't the coinbase wallet browser, deeplink to the coinbase wallet app
|
||||
|
||||
@@ -5,10 +5,11 @@ export enum FeatureFlag {
|
||||
traceJsonRpc = 'traceJsonRpc',
|
||||
permit2 = 'permit2',
|
||||
payWithAnyToken = 'payWithAnyToken',
|
||||
fiatOnRampButtonOnSwap = 'fiat_on_ramp_button_on_swap_page',
|
||||
swapWidget = 'swap_widget_replacement_enabled',
|
||||
statsigDummy = 'web_dummy_gate_amplitude_id',
|
||||
nftGraphql = 'nft_graphql_migration',
|
||||
mgtm = 'web_mobile_go_to_market_enabled',
|
||||
walletMicrosite = 'walletMicrosite',
|
||||
miniPortfolio = 'miniPortfolio',
|
||||
detailsV2 = 'details_v2',
|
||||
}
|
||||
|
||||
9
src/featureFlags/flags/fiatOnRampButton.ts
Normal file
9
src/featureFlags/flags/fiatOnRampButton.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
function useFiatOnRampButtonFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.fiatOnRampButtonOnSwap)
|
||||
}
|
||||
|
||||
export function useFiatOnRampButtonEnabled(): boolean {
|
||||
return useFiatOnRampButtonFlag() === BaseVariant.Enabled
|
||||
}
|
||||
@@ -10,9 +10,4 @@ export function useMgtmEnabled(): boolean {
|
||||
return useMgtmFlag() === BaseVariant.Enabled
|
||||
}
|
||||
|
||||
export function useMGTMMicrositeEnabled() {
|
||||
const mgtmEnabled = useMgtmEnabled()
|
||||
return useBaseFlag(FeatureFlag.walletMicrosite) === BaseVariant.Enabled && mgtmEnabled
|
||||
}
|
||||
|
||||
export { BaseVariant as MgtmVariant }
|
||||
|
||||
11
src/featureFlags/flags/nftDetails.ts
Normal file
11
src/featureFlags/flags/nftDetails.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useDetailsV2Flag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.detailsV2)
|
||||
}
|
||||
|
||||
export function useDetailsV2Enabled(): boolean {
|
||||
return useDetailsV2Flag() === BaseVariant.Enabled
|
||||
}
|
||||
|
||||
export { BaseVariant as DetailsV2Variant }
|
||||
@@ -39,33 +39,18 @@ gql`
|
||||
node {
|
||||
id
|
||||
name
|
||||
ownerAddress
|
||||
image {
|
||||
url
|
||||
}
|
||||
smallImage {
|
||||
url
|
||||
}
|
||||
originalImage {
|
||||
url
|
||||
}
|
||||
tokenId
|
||||
description
|
||||
animationUrl
|
||||
suspiciousFlag
|
||||
collection {
|
||||
name
|
||||
isVerified
|
||||
image {
|
||||
url
|
||||
}
|
||||
creator {
|
||||
address
|
||||
profileImage {
|
||||
url
|
||||
}
|
||||
isVerified
|
||||
}
|
||||
nftContracts {
|
||||
address
|
||||
standard
|
||||
@@ -98,11 +83,8 @@ gql`
|
||||
}
|
||||
}
|
||||
rarities {
|
||||
provider
|
||||
rank
|
||||
score
|
||||
}
|
||||
metadataUrl
|
||||
}
|
||||
cursor
|
||||
}
|
||||
|
||||
@@ -122,7 +122,8 @@ export function useNftBalance(
|
||||
first?: number,
|
||||
after?: string,
|
||||
last?: number,
|
||||
before?: string
|
||||
before?: string,
|
||||
skip = false
|
||||
) {
|
||||
const { data, loading, fetchMore } = useNftBalanceQuery({
|
||||
variables: {
|
||||
@@ -140,6 +141,7 @@ export function useNftBalance(
|
||||
last,
|
||||
before,
|
||||
},
|
||||
skip,
|
||||
})
|
||||
|
||||
const hasNext = data?.nftBalances?.pageInfo?.hasNextPage
|
||||
|
||||
@@ -4,6 +4,7 @@ import { DAI, USDC_MAINNET } from 'constants/tokens'
|
||||
import { RouterPreference } from 'state/routing/slice'
|
||||
import { TradeState } from 'state/routing/types'
|
||||
import { useClientSideRouter } from 'state/user/hooks'
|
||||
import { mocked } from 'test-utils/mocked'
|
||||
|
||||
import { useRoutingAPITrade } from '../state/routing/useRoutingAPITrade'
|
||||
import useAutoRouterSupported from './useAutoRouterSupported'
|
||||
@@ -29,53 +30,45 @@ jest.mock('./useIsWindowVisible')
|
||||
jest.mock('state/routing/useRoutingAPITrade')
|
||||
jest.mock('state/user/hooks')
|
||||
|
||||
const mockUseDebounce = useDebounce as jest.MockedFunction<typeof useDebounce>
|
||||
const mockUseAutoRouterSupported = useAutoRouterSupported as jest.MockedFunction<typeof useAutoRouterSupported>
|
||||
const mockUseIsWindowVisible = useIsWindowVisible as jest.MockedFunction<typeof useIsWindowVisible>
|
||||
|
||||
const mockUseRoutingAPITrade = useRoutingAPITrade as jest.MockedFunction<typeof useRoutingAPITrade>
|
||||
const mockUseClientSideRouter = useClientSideRouter as jest.MockedFunction<typeof useClientSideRouter>
|
||||
const mockUseClientSideV3Trade = useClientSideV3Trade as jest.MockedFunction<typeof useClientSideV3Trade>
|
||||
|
||||
// helpers to set mock expectations
|
||||
const expectRouterMock = (state: TradeState) => {
|
||||
mockUseRoutingAPITrade.mockReturnValue({ state, trade: undefined })
|
||||
mocked(useRoutingAPITrade).mockReturnValue({ state, trade: undefined })
|
||||
}
|
||||
|
||||
const expectClientSideMock = (state: TradeState) => {
|
||||
mockUseClientSideV3Trade.mockReturnValue({ state, trade: undefined })
|
||||
mocked(useClientSideV3Trade).mockReturnValue({ state, trade: undefined })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
// ignore debounced value
|
||||
mockUseDebounce.mockImplementation((value) => value)
|
||||
mocked(useDebounce).mockImplementation((value) => value)
|
||||
|
||||
mockUseIsWindowVisible.mockReturnValue(true)
|
||||
mockUseAutoRouterSupported.mockReturnValue(true)
|
||||
mockUseClientSideRouter.mockReturnValue([true, () => undefined])
|
||||
mocked(useIsWindowVisible).mockReturnValue(true)
|
||||
mocked(useAutoRouterSupported).mockReturnValue(true)
|
||||
mocked(useClientSideRouter).mockReturnValue([true, () => undefined])
|
||||
})
|
||||
|
||||
describe('#useBestV3Trade ExactIn', () => {
|
||||
it('does not compute routing api trade when routing API is not supported', async () => {
|
||||
mockUseAutoRouterSupported.mockReturnValue(false)
|
||||
mocked(useAutoRouterSupported).mockReturnValue(false)
|
||||
expectRouterMock(TradeState.INVALID)
|
||||
expectClientSideMock(TradeState.VALID)
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
|
||||
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, DAI, RouterPreference.CLIENT)
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, USDCAmount, DAI)
|
||||
expect(useRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, DAI, RouterPreference.CLIENT)
|
||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, USDCAmount, DAI)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
})
|
||||
|
||||
it('does not compute routing api trade when window is not focused', async () => {
|
||||
mockUseIsWindowVisible.mockReturnValue(false)
|
||||
mocked(useIsWindowVisible).mockReturnValue(false)
|
||||
expectRouterMock(TradeState.NO_ROUTE_FOUND)
|
||||
expectClientSideMock(TradeState.VALID)
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
|
||||
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, DAI, RouterPreference.CLIENT)
|
||||
expect(useRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, DAI, RouterPreference.CLIENT)
|
||||
expect(result.current).toEqual({ state: TradeState.NO_ROUTE_FOUND, trade: undefined })
|
||||
})
|
||||
|
||||
@@ -85,7 +78,7 @@ describe('#useBestV3Trade ExactIn', () => {
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
|
||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
|
||||
expect(result.current).toEqual({ state: TradeState.LOADING, trade: undefined })
|
||||
})
|
||||
|
||||
@@ -94,7 +87,7 @@ describe('#useBestV3Trade ExactIn', () => {
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
|
||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
})
|
||||
|
||||
@@ -103,7 +96,7 @@ describe('#useBestV3Trade ExactIn', () => {
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
|
||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
|
||||
expect(result.current).toEqual({ state: TradeState.SYNCING, trade: undefined })
|
||||
})
|
||||
})
|
||||
@@ -115,7 +108,7 @@ describe('#useBestV3Trade ExactIn', () => {
|
||||
|
||||
renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
|
||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
|
||||
})
|
||||
|
||||
it('computes client side v3 trade if routing api is NO_ROUTE_FOUND', () => {
|
||||
@@ -124,7 +117,7 @@ describe('#useBestV3Trade ExactIn', () => {
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, USDCAmount, DAI)
|
||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, USDCAmount, DAI)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
})
|
||||
})
|
||||
@@ -132,30 +125,30 @@ describe('#useBestV3Trade ExactIn', () => {
|
||||
|
||||
describe('#useBestV3Trade ExactOut', () => {
|
||||
it('does not compute routing api trade when routing API is not supported', () => {
|
||||
mockUseAutoRouterSupported.mockReturnValue(false)
|
||||
mocked(useAutoRouterSupported).mockReturnValue(false)
|
||||
expectRouterMock(TradeState.INVALID)
|
||||
expectClientSideMock(TradeState.VALID)
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(
|
||||
expect(useRoutingAPITrade).toHaveBeenCalledWith(
|
||||
TradeType.EXACT_OUTPUT,
|
||||
undefined,
|
||||
USDC_MAINNET,
|
||||
RouterPreference.CLIENT
|
||||
)
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
|
||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
})
|
||||
|
||||
it('does not compute routing api trade when window is not focused', () => {
|
||||
mockUseIsWindowVisible.mockReturnValue(false)
|
||||
mocked(useIsWindowVisible).mockReturnValue(false)
|
||||
expectRouterMock(TradeState.NO_ROUTE_FOUND)
|
||||
expectClientSideMock(TradeState.VALID)
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(
|
||||
expect(useRoutingAPITrade).toHaveBeenCalledWith(
|
||||
TradeType.EXACT_OUTPUT,
|
||||
undefined,
|
||||
USDC_MAINNET,
|
||||
@@ -169,7 +162,7 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
||||
expect(result.current).toEqual({ state: TradeState.LOADING, trade: undefined })
|
||||
})
|
||||
|
||||
@@ -178,7 +171,7 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
})
|
||||
|
||||
@@ -187,7 +180,7 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
||||
expect(result.current).toEqual({ state: TradeState.SYNCING, trade: undefined })
|
||||
})
|
||||
})
|
||||
@@ -199,7 +192,7 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
|
||||
renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
|
||||
})
|
||||
|
||||
it('computes client side v3 trade if routing api is NO_ROUTE_FOUND', () => {
|
||||
@@ -208,7 +201,7 @@ describe('#useBestV3Trade ExactOut', () => {
|
||||
|
||||
const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET))
|
||||
|
||||
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
|
||||
expect(useClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET)
|
||||
expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WARNING_LEVEL } from 'constants/tokenSafety'
|
||||
import { renderHook } from 'test-utils'
|
||||
import { renderHook } from 'test-utils/render'
|
||||
import { lightTheme } from 'theme/colors'
|
||||
|
||||
import { useTokenWarningColor, useTokenWarningTextColor } from './useTokenWarningColor'
|
||||
|
||||
@@ -18,7 +18,9 @@ describe('fetchTokenList', () => {
|
||||
fetch.mockOnceIf(url, () => {
|
||||
throw new Error()
|
||||
})
|
||||
await expect(fetchTokenList(url, resolver)).rejects.toThrow(`failed to fetch list: ${url}`)
|
||||
await expect(fetchTokenList(url, resolver)).rejects.toThrow(
|
||||
`No valid token list found at any URLs derived from ${url}.`
|
||||
)
|
||||
expect(console.debug).toHaveBeenCalled()
|
||||
expect(resolver).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -33,9 +35,63 @@ describe('fetchTokenList', () => {
|
||||
expect(resolver).toHaveBeenCalledWith(url)
|
||||
})
|
||||
|
||||
it('throws an error when the ENS resolver throws', async () => {
|
||||
const url = 'example.eth'
|
||||
const error = new Error('ENS resolver error')
|
||||
resolver.mockRejectedValue(error)
|
||||
await expect(fetchTokenList(url, resolver)).rejects.toThrow(`failed to resolve ENS name: ${url}`)
|
||||
expect(resolver).toHaveBeenCalledWith(url)
|
||||
})
|
||||
|
||||
it('fetches and validates a list from an ENS address', async () => {
|
||||
jest.mock('../../utils/contenthashToUri', () =>
|
||||
jest.fn().mockImplementation(() => 'ipfs://QmPgEqyV3m8SB52BS2j2mJpu9zGprhj2BGCHtRiiw2fdM1')
|
||||
)
|
||||
const url = 'example.eth'
|
||||
const contenthash = '0xe3010170122013e051d1cfff20606de36845d4fe28deb9861a319a5bc8596fa4e610e8803918'
|
||||
const translatedUri = 'https://cloudflare-ipfs.com/ipfs/QmPgEqyV3m8SB52BS2j2mJpu9zGprhj2BGCHtRiiw2fdM1/'
|
||||
resolver.mockResolvedValue(contenthash)
|
||||
fetch.mockOnceIf(translatedUri, () => Promise.resolve(JSON.stringify(defaultTokenList)))
|
||||
await expect(fetchTokenList(url, resolver)).resolves.toStrictEqual(defaultTokenList)
|
||||
})
|
||||
|
||||
it('throws for an unrecognized list URL protocol', async () => {
|
||||
const url = 'unknown://example.com/invalid-tokenlist.json'
|
||||
fetch.mockOnceIf(url, () => Promise.resolve(''))
|
||||
await expect(fetchTokenList(url, resolver)).rejects.toThrow(`Unrecognized list URL protocol.`)
|
||||
})
|
||||
|
||||
it('logs a debug statement if the response is not successful', async () => {
|
||||
const url = 'https://example.com/invalid-tokenlist.json'
|
||||
fetch.mockOnceIf(url, () => Promise.resolve({ status: 404 }))
|
||||
await expect(fetchTokenList(url, resolver)).rejects.toThrow(
|
||||
`No valid token list found at any URLs derived from ${url}.`
|
||||
)
|
||||
expect(console.debug).toHaveBeenCalled()
|
||||
expect(resolver).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('fetches and validates the default token list', async () => {
|
||||
fetch.mockOnceIf(DEFAULT_TOKEN_LIST, () => Promise.resolve(JSON.stringify(defaultTokenList)))
|
||||
await expect(fetchTokenList(DEFAULT_TOKEN_LIST, resolver)).resolves.toStrictEqual(defaultTokenList)
|
||||
expect(resolver).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('throws for a list with invalid json response', async () => {
|
||||
const url = 'https://example.com/invalid-tokenlist.json'
|
||||
fetch.mockOnceIf(url, () => Promise.resolve('invalid json'))
|
||||
await expect(fetchTokenList(url, resolver)).rejects.toThrow(
|
||||
`No valid token list found at any URLs derived from ${url}.`
|
||||
)
|
||||
expect(console.debug).toHaveBeenCalled()
|
||||
expect(resolver).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('uses cached value the second time', async () => {
|
||||
const url = 'https://example.com/invalid-tokenlist.json'
|
||||
fetch.mockOnceIf(url, () => Promise.resolve(JSON.stringify(defaultTokenList)))
|
||||
await expect(fetchTokenList(url, resolver)).resolves.toStrictEqual(defaultTokenList)
|
||||
await expect(fetchTokenList(url, resolver)).resolves.toStrictEqual(defaultTokenList)
|
||||
expect(fetch).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -8,7 +8,11 @@ export const DEFAULT_TOKEN_LIST = 'https://gateway.ipfs.io/ipns/tokens.uniswap.o
|
||||
|
||||
const listCache = new Map<string, TokenList>()
|
||||
|
||||
/** Fetches and validates a token list. */
|
||||
/**
|
||||
* Fetches and validates a token list.
|
||||
* For a given token list URL, we try to fetch the list from all the possible HTTP URLs.
|
||||
* For example, IPFS URLs can be fetched through multiple gateways.
|
||||
*/
|
||||
export default async function fetchTokenList(
|
||||
listUrl: string,
|
||||
resolveENSContentHash: (ensName: string) => Promise<string>,
|
||||
@@ -43,31 +47,38 @@ export default async function fetchTokenList(
|
||||
urls = uriToHttp(listUrl)
|
||||
}
|
||||
|
||||
if (urls.length === 0) {
|
||||
throw new Error('Unrecognized list URL protocol.')
|
||||
}
|
||||
|
||||
// Try each of the derived URLs until one succeeds.
|
||||
for (let i = 0; i < urls.length; i++) {
|
||||
const url = urls[i]
|
||||
const isLast = i === urls.length - 1
|
||||
let response
|
||||
try {
|
||||
response = await fetch(url, { credentials: 'omit' })
|
||||
} catch (error) {
|
||||
const message = `failed to fetch list: ${listUrl}`
|
||||
console.debug(message, error)
|
||||
if (isLast) throw new Error(message)
|
||||
console.debug(`failed to fetch list: ${listUrl} (${url})`, error)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const message = `failed to fetch list: ${listUrl}`
|
||||
console.debug(message, response.statusText)
|
||||
if (isLast) throw new Error(message)
|
||||
console.debug(`failed to fetch list ${listUrl} (${url})`, response.statusText)
|
||||
continue
|
||||
}
|
||||
|
||||
const json = await response.json()
|
||||
const list = skipValidation ? json : await validateTokenList(json)
|
||||
listCache?.set(listUrl, list)
|
||||
return list
|
||||
try {
|
||||
// The content of the result is sometimes invalid even with a 200 status code.
|
||||
// A response can be invalid if it's not a valid JSON or if it doesn't match the TokenList schema.
|
||||
const json = await response.json()
|
||||
const list = skipValidation ? json : await validateTokenList(json)
|
||||
listCache?.set(listUrl, list)
|
||||
return list
|
||||
} catch (error) {
|
||||
console.debug(`failed to parse and validate list response: ${listUrl} (${url})`, error)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Unrecognized list URL protocol.')
|
||||
throw new Error(`No valid token list found at any URLs derived from ${listUrl}.`)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { i18n } from '@lingui/core'
|
||||
import { I18nProvider } from '@lingui/react'
|
||||
import * as Sentry from '@sentry/react'
|
||||
import { DEFAULT_LOCALE, SupportedLocale } from 'constants/locales'
|
||||
import {
|
||||
af,
|
||||
@@ -83,9 +82,8 @@ export async function dynamicActivate(locale: SupportedLocale) {
|
||||
const catalog = await import(`locales/${locale}.js`)
|
||||
// Bundlers will either export it as default or as a named export named default.
|
||||
i18n.load(locale, catalog.messages || catalog.default.messages)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
Sentry.captureException(new Error(`Unable to load locale (${locale})`))
|
||||
} catch (error: unknown) {
|
||||
console.error(new Error(`Unable to load locale (${locale}): ${error}`))
|
||||
}
|
||||
i18n.activate(locale)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { sendAnalyticsEvent, TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, NFTEventName } from '@uniswap/analytics-events'
|
||||
import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useToggleAccountDrawer } from 'components/AccountDrawer'
|
||||
import Column from 'components/Column'
|
||||
import Loader from 'components/Icons/LoadingSpinner'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
@@ -12,7 +13,6 @@ import Row from 'components/Row'
|
||||
import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal'
|
||||
import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import { useToggleWalletDrawer } from 'components/WalletDropdown'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { usePayWithAnyTokenEnabled } from 'featureFlags/flags/payWithAnyToken'
|
||||
import { useCurrency } from 'hooks/Tokens'
|
||||
@@ -287,7 +287,7 @@ const PENDING_BAG_STATUSES = [
|
||||
]
|
||||
|
||||
export const BagFooter = ({ setModalIsOpen, eventProperties }: BagFooterProps) => {
|
||||
const toggleWalletDrawer = useToggleWalletDrawer()
|
||||
const toggleWalletDrawer = useToggleAccountDrawer()
|
||||
const theme = useTheme()
|
||||
const { account, chainId, connector } = useWeb3React()
|
||||
const connected = Boolean(account && chainId)
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { render } from 'test-utils'
|
||||
import { render } from 'test-utils/render'
|
||||
|
||||
import Bag from './Bag'
|
||||
|
||||
jest.mock('@web3-react/core', () => {
|
||||
const web3React = jest.requireActual('@web3-react/core')
|
||||
return {
|
||||
...web3React,
|
||||
useWeb3React: () => ({
|
||||
account: '0x52270d8234b864dcAC9947f510CE9275A8a116Db',
|
||||
isActive: true,
|
||||
}),
|
||||
...web3React,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Markets } from 'nft/types'
|
||||
import { render } from 'test-utils'
|
||||
import { render } from 'test-utils/render'
|
||||
|
||||
import { MarketplaceContainer } from './icons'
|
||||
|
||||
|
||||
@@ -313,18 +313,16 @@ export const AssetDetails = ({ asset, collection }: AssetDetailsProps) => {
|
||||
'no-cache'
|
||||
)
|
||||
|
||||
// TODO simplify typecasting when removing graphql flag
|
||||
const lastSalePrice = isNftGraphqlEnabled ? gqlPriceData?.[0]?.price : priceData?.events[0]?.price
|
||||
const formattedEthprice = isNftGraphqlEnabled
|
||||
? formatEth(parseFloat(lastSalePrice ?? ''))
|
||||
: formatEthPrice(lastSalePrice) || 0
|
||||
const formattedPrice = isNftGraphqlEnabled
|
||||
? formattedEthprice
|
||||
: lastSalePrice
|
||||
? putCommas(parseFloat(formattedEthprice.toString())).toString()
|
||||
: null
|
||||
const [activeFilters, filtersDispatch] = useReducer(reduceFilters, initialFilterState)
|
||||
let formattedPrice
|
||||
if (isNftGraphqlEnabled) {
|
||||
const weiPrice = gqlPriceData?.[0]?.price
|
||||
formattedPrice = weiPrice ? formatEth(parseFloat(weiPrice)) : undefined
|
||||
} else {
|
||||
const ethPrice = priceData?.events[0]?.price
|
||||
formattedPrice = ethPrice ? putCommas(formatEthPrice(priceData?.events[0]?.price)).toString() : undefined
|
||||
}
|
||||
|
||||
const [activeFilters, filtersDispatch] = useReducer(reduceFilters, initialFilterState)
|
||||
const Filter = useCallback(
|
||||
function ActivityFilter({ eventType }: { eventType: ActivityEventType }) {
|
||||
const isActive = activeFilters[eventType]
|
||||
|
||||
14
src/nft/components/details/NftDetails.tsx
Normal file
14
src/nft/components/details/NftDetails.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { CollectionInfoForAsset, GenieAsset } from 'nft/types'
|
||||
|
||||
interface NftDetailsProps {
|
||||
asset: GenieAsset
|
||||
collection: CollectionInfoForAsset
|
||||
}
|
||||
|
||||
export const NftDetails = ({ asset, collection }: NftDetailsProps) => {
|
||||
return (
|
||||
<div>
|
||||
Details page for {asset.name} from {collection.collectionName}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -114,8 +114,8 @@ export const EthCell = ({
|
||||
denomination: Denomination
|
||||
usdPrice?: number
|
||||
}) => {
|
||||
const denominatedValue = getDenominatedValue(denomination, true, value, usdPrice)
|
||||
const isNftGraphqlEnabled = useNftGraphqlEnabled()
|
||||
const denominatedValue = getDenominatedValue(denomination, !isNftGraphqlEnabled, value, usdPrice)
|
||||
const formattedValue = denominatedValue
|
||||
? denomination === Denomination.ETH
|
||||
? isNftGraphqlEnabled
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { useNftGraphqlEnabled } from 'featureFlags/flags/nftlGraphql'
|
||||
import { CollectionTableColumn, TimePeriod } from 'nft/types'
|
||||
import { useMemo } from 'react'
|
||||
import { CellProps, Column, Row } from 'react-table'
|
||||
@@ -26,14 +27,19 @@ const compareFloats = (a?: number, b?: number): 1 | -1 => {
|
||||
}
|
||||
|
||||
const CollectionTable = ({ data, timePeriod }: { data: CollectionTableColumn[]; timePeriod: TimePeriod }) => {
|
||||
const isNftGraphqlEnabled = useNftGraphqlEnabled()
|
||||
const floorSort = useMemo(() => {
|
||||
return (rowA: Row<CollectionTableColumn>, rowB: Row<CollectionTableColumn>) => {
|
||||
const aFloor = BigNumber.from(rowA.original.floor.value ?? 0)
|
||||
const bFloor = BigNumber.from(rowB.original.floor.value ?? 0)
|
||||
if (isNftGraphqlEnabled) {
|
||||
return compareFloats(rowA.original.floor.value, rowB.original.floor.value)
|
||||
} else {
|
||||
const aFloor = BigNumber.from(rowA.original.floor.value ?? 0)
|
||||
const bFloor = BigNumber.from(rowB.original.floor.value ?? 0)
|
||||
|
||||
return aFloor.gte(bFloor) ? 1 : -1
|
||||
return aFloor.gte(bFloor) ? 1 : -1
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
}, [isNftGraphqlEnabled])
|
||||
|
||||
const floorChangeSort = useMemo(() => {
|
||||
return (rowA: Row<CollectionTableColumn>, rowB: Row<CollectionTableColumn>) => {
|
||||
|
||||
17
src/nft/components/profile/view/EmptyWalletContent.test.tsx
Normal file
17
src/nft/components/profile/view/EmptyWalletContent.test.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { render } from 'test-utils/render'
|
||||
|
||||
import { EmptyWalletModule } from './EmptyWalletContent'
|
||||
|
||||
describe('EmptyWalletContent.tsx', () => {
|
||||
it('matches base snapshot', () => {
|
||||
const { asFragment } = render(
|
||||
<div>
|
||||
<EmptyWalletModule type="nft" />
|
||||
<EmptyWalletModule type="token" />
|
||||
<EmptyWalletModule type="activity" />
|
||||
<EmptyWalletModule type="pool" />
|
||||
</div>
|
||||
)
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
@@ -5,7 +5,7 @@ import { useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import { EmptyActivityIcon, EmptyNftsIcon, EmptyTokensIcon } from './icons'
|
||||
import { EmptyActivityIcon, EmptyNftsIcon, EmptyPoolsIcon, EmptyTokensIcon } from './icons'
|
||||
|
||||
const EmptyWalletContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -74,11 +74,11 @@ const EMPTY_WALLET_CONTENT: { [key in EmptyWalletContentType]: EmptyWalletConten
|
||||
icon: <EmptyActivityIcon />,
|
||||
},
|
||||
pool: {
|
||||
title: <Trans>No positions yet</Trans>,
|
||||
title: <Trans>No pools yet</Trans>,
|
||||
subtitle: <Trans>Open a new position or create a pool to get started.</Trans>,
|
||||
actionText: <Trans>+ New position</Trans>,
|
||||
urlPath: '/pool',
|
||||
icon: <EmptyTokensIcon />,
|
||||
icon: <EmptyPoolsIcon />,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EmptyWalletContent.tsx matches base snapshot 1`] = `
|
||||
<DocumentFragment>
|
||||
.c1 {
|
||||
color: #0D111C;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
color: #7780A0;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
-webkit-flex-wrap: wrap;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
white-space: normal;
|
||||
margin-top: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
background-color: #FB118E;
|
||||
padding: 10px 24px;
|
||||
color: #FFFFFF;
|
||||
width: -webkit-min-content;
|
||||
width: -moz-min-content;
|
||||
width: min-content;
|
||||
border: none;
|
||||
outline: none;
|
||||
border-radius: 12px;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="116"
|
||||
viewBox="0 0 116 116"
|
||||
width="116"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M106.673 12.4027C110.616 13.5333 112.895 17.6462 111.765 21.5891L97.7533 70.4529C96.8931 73.4525 94.307 75.4896 91.3828 75.7948C91.4046 75.5034 91.4157 75.2091 91.4157 74.9121V27.1674C91.4157 20.7217 86.1904 15.4965 79.7447 15.4965H56.1167L58.7303 6.38172C59.8609 2.43883 63.9738 0.159015 67.9167 1.28962L106.673 12.4027Z"
|
||||
fill="#404A67"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M32 27.7402C32 23.322 35.5817 19.7402 40 19.7402H79.1717C83.59 19.7402 87.1717 23.322 87.1717 27.7402V74.3389C87.1717 78.7572 83.59 82.3389 79.1717 82.3389H40C35.5817 82.3389 32 78.7572 32 74.3389V27.7402ZM57.1717 42.7402C57.1717 46.6062 53.8138 49.7402 49.6717 49.7402C45.5296 49.7402 42.1717 46.6062 42.1717 42.7402C42.1717 38.8742 45.5296 35.7402 49.6717 35.7402C53.8138 35.7402 57.1717 38.8742 57.1717 42.7402ZM36.1717 60.8153C37.2808 58.3975 40.7688 54.8201 45.7381 54.3677C51.977 53.7997 55.3044 57.8295 56.5522 60.0094C59.8797 55.4423 67.0336 46.8724 72.3575 45.9053C77.6814 44.9381 81.7853 48.4574 83.1717 50.338V72.6975C83.1717 75.4825 80.914 77.7402 78.1289 77.7402H41.2144C38.4294 77.7402 36.1717 75.4825 36.1717 72.6975V60.8153Z"
|
||||
fill="#404A67"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="c1 c2 common__127l8hd1 sprinkles_fontWeight_semibold_sm__rgw6ezen sprinkles_fontSize_28_sm__rgw6ezcw sprinkles_lineHeight_36_sm__rgw6ezgl css-6ms77s"
|
||||
>
|
||||
No NFTs yet
|
||||
</div>
|
||||
<div
|
||||
class="c3 c4 css-zhpkf8"
|
||||
>
|
||||
Buy or transfer NFTs to this wallet to get started.
|
||||
</div>
|
||||
<button
|
||||
class="c5"
|
||||
data-testid="nft-explore-nfts-button"
|
||||
>
|
||||
Explore NFTs
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="80"
|
||||
viewBox="0 0 91 80"
|
||||
width="91"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M3 0C1.61929 0 0.5 1.11929 0.5 2.5C0.5 3.88071 1.61929 5 3 5L57 5C58.3807 5.00001 59.5 3.88072 59.5 2.50001C59.5 1.11929 58.3807 5.06009e-06 57 4.93939e-06L3 0ZM7.51953 11.1055H10.5143C13.5091 11.1055 15.0065 12.6029 15.0065 15.5977V18.5924C15.0065 21.5872 13.5091 23.0846 10.5143 23.0846H7.51953C4.52474 23.0846 3.02734 21.5872 3.02734 18.5924V15.5977C3.02734 12.6029 4.52474 11.1055 7.51953 11.1055ZM31.4779 11.1055H28.4831C25.4883 11.1055 23.9909 12.6029 23.9909 15.5977V18.5924C23.9909 21.5872 25.4883 23.0846 28.4831 23.0846H31.4779C34.4726 23.0846 35.97 21.5872 35.97 18.5924V15.5977C35.97 12.6029 34.4726 11.1055 31.4779 11.1055ZM49.4466 11.1055H52.4414C55.4362 11.1055 56.9336 12.6029 56.9336 15.5977V18.5924C56.9336 21.5872 55.4362 23.0846 52.4414 23.0846H49.4466C46.4518 23.0846 44.9544 21.5872 44.9544 18.5924V15.5977C44.9544 12.6029 46.4518 11.1055 49.4466 11.1055ZM10.5143 31.47H7.51953C4.52474 31.47 3.02734 32.9674 3.02734 35.9622V38.957C3.02734 41.9518 4.52474 43.4492 7.51953 43.4492H10.5143C13.5091 43.4492 15.0065 41.9518 15.0065 38.957V35.9622C15.0065 32.9674 13.5091 31.47 10.5143 31.47ZM28.4831 31.47H31.4779C34.4726 31.47 35.97 32.9674 35.97 35.9622V38.957C35.97 41.9518 34.4726 43.4492 31.4779 43.4492H28.4831C25.4883 43.4492 23.9909 41.9518 23.9909 38.957V35.9622C23.9909 32.9674 25.4883 31.47 28.4831 31.47ZM52.4414 31.47H49.4466C46.4518 31.47 44.9544 32.9674 44.9544 35.9622V38.957C44.9544 41.1067 45.726 42.4849 47.2691 43.0915C49.7015 39.5566 52.9858 36.6532 56.8257 34.6779C56.4335 32.5393 54.9721 31.47 52.4414 31.47Z"
|
||||
fill="#98A1C0"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M68.7031 79.8125C80.8534 79.8125 90.7031 69.9628 90.7031 57.8125C90.7031 45.6622 80.8534 35.8125 68.7031 35.8125C56.5529 35.8125 46.7031 45.6622 46.7031 57.8125C46.7031 69.9628 56.5529 79.8125 68.7031 79.8125ZM76.9524 57.8122L68.7027 49.5625L60.4531 57.8122L68.7027 66.0619L76.9524 57.8122Z"
|
||||
fill="#404A67"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="c1 c2 common__127l8hd1 sprinkles_fontWeight_semibold_sm__rgw6ezen sprinkles_fontSize_28_sm__rgw6ezcw sprinkles_lineHeight_36_sm__rgw6ezgl css-6ms77s"
|
||||
>
|
||||
No tokens yet
|
||||
</div>
|
||||
<div
|
||||
class="c3 c4 css-zhpkf8"
|
||||
>
|
||||
Buy or transfer tokens to this wallet to get started.
|
||||
</div>
|
||||
<button
|
||||
class="c5"
|
||||
data-testid="nft-explore-nfts-button"
|
||||
>
|
||||
Explore tokens
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="94"
|
||||
viewBox="0 0 102 94"
|
||||
width="102"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M20.4998 9.00098L5.22859 13.3799C1.51236 14.4455 -0.636389 18.322 0.429224 22.0382L13.6352 68.093C14.7008 71.8092 18.5773 73.958 22.2935 72.8924L56.7921 63H31.4998C25.4246 63 20.4998 58.0752 20.4998 52V9.00098Z"
|
||||
fill="#98A1C0"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M31.5 0C27.634 0 24.5 3.13401 24.5 7V52C24.5 55.866 27.634 59 31.5 59H56.7364C60.5936 51.6192 67.8907 46.3207 76.5 45.2321V7C76.5 3.13401 73.366 0 69.5 0H31.5ZM37 19C38.3807 19 39.5 17.8807 39.5 16.5C39.5 15.1193 38.3807 14 37 14C35.6193 14 34.5 15.1193 34.5 16.5C34.5 17.8807 35.6193 19 37 19ZM39.5 29.5C39.5 30.8807 38.3807 32 37 32C35.6193 32 34.5 30.8807 34.5 29.5C34.5 28.1193 35.6193 27 37 27C38.3807 27 39.5 28.1193 39.5 29.5ZM37 45C38.3807 45 39.5 43.8807 39.5 42.5C39.5 41.1193 38.3807 40 37 40C35.6193 40 34.5 41.1193 34.5 42.5C34.5 43.8807 35.6193 45 37 45ZM44.5 16.5C44.5 15.1193 45.6193 14 47 14H64C65.3807 14 66.5 15.1193 66.5 16.5C66.5 17.8807 65.3807 19 64 19H47C45.6193 19 44.5 17.8807 44.5 16.5ZM47 27C45.6193 27 44.5 28.1193 44.5 29.5C44.5 30.8807 45.6193 32 47 32H64C65.3807 32 66.5 30.8807 66.5 29.5C66.5 28.1193 65.3807 27 64 27H47ZM44.5 42.5C44.5 41.1193 45.6193 40 47 40H64C65.3807 40 66.5 41.1193 66.5 42.5C66.5 43.8807 65.3807 45 64 45H47C45.6193 45 44.5 43.8807 44.5 42.5Z"
|
||||
fill="#98A1C0"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M79.7939 93.0254C91.9442 93.0254 101.794 83.1757 101.794 71.0254C101.794 58.8751 91.9442 49.0254 79.7939 49.0254C67.6437 49.0254 57.7939 58.8751 57.7939 71.0254C57.7939 83.1757 67.6437 93.0254 79.7939 93.0254ZM88.0433 71.0251L79.7936 62.7754L71.544 71.0251L79.7936 79.2748L88.0433 71.0251Z"
|
||||
fill="#404A67"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="c1 c2 common__127l8hd1 sprinkles_fontWeight_semibold_sm__rgw6ezen sprinkles_fontSize_28_sm__rgw6ezcw sprinkles_lineHeight_36_sm__rgw6ezgl css-6ms77s"
|
||||
>
|
||||
No activity yet
|
||||
</div>
|
||||
<div
|
||||
class="c3 c4 css-zhpkf8"
|
||||
>
|
||||
Your onchain transactions and crypto purchases will appear here.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="85"
|
||||
viewBox="0 0 81 85"
|
||||
width="81"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M40.98 44C53.1302 44 62.98 34.1503 62.98 22C62.98 9.84974 53.1302 0 40.98 0C28.8297 0 18.98 9.84974 18.98 22C18.98 34.1503 28.8297 44 40.98 44ZM49.23 22L40.98 13.75L32.73 22L40.98 30.25L49.23 22Z"
|
||||
fill="#404A67"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M2.5 63.1986C12.9105 63.1986 20.7173 53.0581 20.7173 53.0581C20.7173 53.0581 28.5241 63.1986 38.9346 63.1986C49.3409 63.1986 59.7514 53.0581 59.7514 53.0581C59.7514 53.0581 70.1619 63.1986 77.9687 63.1986M2.5 82.2504C12.9105 82.2504 20.7173 72.1099 20.7173 72.1099C20.7173 72.1099 28.5241 82.2504 38.9346 82.2504C49.3409 82.2504 59.7514 72.1099 59.7514 72.1099C59.7514 72.1099 70.1619 82.2504 77.9687 82.2504"
|
||||
stroke="#98A1C0"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="5"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="c1 c2 common__127l8hd1 sprinkles_fontWeight_semibold_sm__rgw6ezen sprinkles_fontSize_28_sm__rgw6ezcw sprinkles_lineHeight_36_sm__rgw6ezgl css-6ms77s"
|
||||
>
|
||||
No pools yet
|
||||
</div>
|
||||
<div
|
||||
class="c3 c4 css-zhpkf8"
|
||||
>
|
||||
Open a new position or create a pool to get started.
|
||||
</div>
|
||||
<button
|
||||
class="c5"
|
||||
data-testid="nft-explore-nfts-button"
|
||||
>
|
||||
+ New position
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -13,25 +13,23 @@ const useEmptyStateIconColors = () => {
|
||||
export const EmptyActivityIcon = (props: SVGProps) => {
|
||||
const { primary, secondary } = useEmptyStateIconColors()
|
||||
return (
|
||||
<svg {...props} width="96" height="66" viewBox="0 0 96 66" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect
|
||||
x="89.2"
|
||||
y="8.3"
|
||||
width="30.4"
|
||||
height="82.4"
|
||||
rx="5.2"
|
||||
transform="rotate(90 89.2 8.3)"
|
||||
stroke={secondary}
|
||||
strokeWidth="2.4"
|
||||
/>
|
||||
<line x1="17.7" y1="19.3" x2="23.3" y2="19.3" stroke={secondary} strokeWidth="2.4" strokeLinecap="round" />
|
||||
<line x1="17.7" y1="24.3" x2="23.3" y2="24.3" stroke={secondary} strokeWidth="2.4" strokeLinecap="round" />
|
||||
<line x1="28.7" y1="19.3" x2="78.3" y2="19.3" stroke={secondary} strokeWidth="2.4" strokeLinecap="round" />
|
||||
<line x1="28.7" y1="24.3" x2="78.3" y2="24.3" stroke={secondary} strokeWidth="2.4" strokeLinecap="round" />
|
||||
<svg {...props} width="102" height="94" viewBox="0 0 102 94" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M23 56.5C29.6274 56.5 35 51.1274 35 44.5C35 37.8726 29.6274 32.5 23 32.5C16.3726 32.5 11 37.8726 11 44.5C11 51.1274 16.3726 56.5 23 56.5ZM27.5 44.5L23 40L18.5 44.5L23 49L27.5 44.5Z"
|
||||
d="M20.4998 9.00098L5.22859 13.3799C1.51236 14.4455 -0.636389 18.322 0.429224 22.0382L13.6352 68.093C14.7008 71.8092 18.5773 73.958 22.2935 72.8924L56.7921 63H31.4998C25.4246 63 20.4998 58.0752 20.4998 52V9.00098Z"
|
||||
fill={secondary}
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M31.5 0C27.634 0 24.5 3.13401 24.5 7V52C24.5 55.866 27.634 59 31.5 59H56.7364C60.5936 51.6192 67.8907 46.3207 76.5 45.2321V7C76.5 3.13401 73.366 0 69.5 0H31.5ZM37 19C38.3807 19 39.5 17.8807 39.5 16.5C39.5 15.1193 38.3807 14 37 14C35.6193 14 34.5 15.1193 34.5 16.5C34.5 17.8807 35.6193 19 37 19ZM39.5 29.5C39.5 30.8807 38.3807 32 37 32C35.6193 32 34.5 30.8807 34.5 29.5C34.5 28.1193 35.6193 27 37 27C38.3807 27 39.5 28.1193 39.5 29.5ZM37 45C38.3807 45 39.5 43.8807 39.5 42.5C39.5 41.1193 38.3807 40 37 40C35.6193 40 34.5 41.1193 34.5 42.5C34.5 43.8807 35.6193 45 37 45ZM44.5 16.5C44.5 15.1193 45.6193 14 47 14H64C65.3807 14 66.5 15.1193 66.5 16.5C66.5 17.8807 65.3807 19 64 19H47C45.6193 19 44.5 17.8807 44.5 16.5ZM47 27C45.6193 27 44.5 28.1193 44.5 29.5C44.5 30.8807 45.6193 32 47 32H64C65.3807 32 66.5 30.8807 66.5 29.5C66.5 28.1193 65.3807 27 64 27H47ZM44.5 42.5C44.5 41.1193 45.6193 40 47 40H64C65.3807 40 66.5 41.1193 66.5 42.5C66.5 43.8807 65.3807 45 64 45H47C45.6193 45 44.5 43.8807 44.5 42.5Z"
|
||||
fill={secondary}
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M79.7939 93.0254C91.9442 93.0254 101.794 83.1757 101.794 71.0254C101.794 58.8751 91.9442 49.0254 79.7939 49.0254C67.6437 49.0254 57.7939 58.8751 57.7939 71.0254C57.7939 83.1757 67.6437 93.0254 79.7939 93.0254ZM88.0433 71.0251L79.7936 62.7754L71.544 71.0251L79.7936 79.2748L88.0433 71.0251Z"
|
||||
fill={primary}
|
||||
/>
|
||||
</svg>
|
||||
@@ -39,36 +37,42 @@ export const EmptyActivityIcon = (props: SVGProps) => {
|
||||
}
|
||||
|
||||
export const EmptyNftsIcon = (props: SVGProps) => {
|
||||
const { primary } = useEmptyStateIconColors()
|
||||
return (
|
||||
<svg {...props} width="116" height="116" viewBox="0 0 116 116" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M106.673 12.4027C110.616 13.5333 112.895 17.6462 111.765 21.5891L97.7533 70.4529C96.8931 73.4525 94.307 75.4896 91.3828 75.7948C91.4046 75.5034 91.4157 75.2091 91.4157 74.9121V27.1674C91.4157 20.7217 86.1904 15.4965 79.7447 15.4965H56.1167L58.7303 6.38172C59.8609 2.43883 63.9738 0.159015 67.9167 1.28962L106.673 12.4027Z"
|
||||
fill={primary}
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M32 27.7402C32 23.322 35.5817 19.7402 40 19.7402H79.1717C83.59 19.7402 87.1717 23.322 87.1717 27.7402V74.3389C87.1717 78.7572 83.59 82.3389 79.1717 82.3389H40C35.5817 82.3389 32 78.7572 32 74.3389V27.7402ZM57.1717 42.7402C57.1717 46.6062 53.8138 49.7402 49.6717 49.7402C45.5296 49.7402 42.1717 46.6062 42.1717 42.7402C42.1717 38.8742 45.5296 35.7402 49.6717 35.7402C53.8138 35.7402 57.1717 38.8742 57.1717 42.7402ZM36.1717 60.8153C37.2808 58.3975 40.7688 54.8201 45.7381 54.3677C51.977 53.7997 55.3044 57.8295 56.5522 60.0094C59.8797 55.4423 67.0336 46.8724 72.3575 45.9053C77.6814 44.9381 81.7853 48.4574 83.1717 50.338V72.6975C83.1717 75.4825 80.914 77.7402 78.1289 77.7402H41.2144C38.4294 77.7402 36.1717 75.4825 36.1717 72.6975V60.8153Z"
|
||||
fill={primary}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const EmptyPoolsIcon = (props: SVGProps) => {
|
||||
const { primary, secondary } = useEmptyStateIconColors()
|
||||
return (
|
||||
<svg {...props} width="88" height="66" viewBox="0 0 88 66" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_963_210087)">
|
||||
<rect
|
||||
x="21.1665"
|
||||
y="12.6367"
|
||||
width="39.1249"
|
||||
height="52.1665"
|
||||
rx="5.61681"
|
||||
stroke={secondary}
|
||||
strokeWidth="2.4"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M43.3569 0.982394C39.6744 0.199643 36.0545 2.55041 35.2718 6.23295L33.9106 12.6366H36.3643L37.6193 6.73194C38.1265 4.34591 40.4719 2.82278 42.8579 3.32995L70.1397 9.12887C72.5258 9.63604 74.0489 11.9814 73.5417 14.3675L65.0313 54.4059C64.5595 56.6254 62.4972 58.0982 60.2912 57.8848V59.1863C60.2912 59.5622 60.2543 59.9295 60.1839 60.2848C63.5428 60.5495 66.6594 58.2898 67.3788 54.9049L75.8893 14.8665C76.672 11.1839 74.3213 7.56407 70.6387 6.78132L43.3569 0.982394Z"
|
||||
fill={secondary}
|
||||
/>
|
||||
<circle cx="31.3553" cy="27.7161" r="5.29816" fill={primary} />
|
||||
<path
|
||||
d="M29.6131 38.9281C25.6488 39.4121 22.8663 43.2392 21.9814 45.8259V58.3157C21.9814 61.4178 24.4962 63.9325 27.5983 63.9325H53.8593C56.9614 63.9325 59.4761 61.4178 59.4761 58.3157V34.617C58.3701 32.6052 55.0962 28.8401 50.849 29.8748C46.6019 30.9095 40.8947 40.0778 38.2402 44.9637C37.2448 42.6316 34.5903 38.3205 29.6131 38.9281Z"
|
||||
fill={primary}
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_963_210087">
|
||||
<rect width="86.6667" height="65" fill="white" transform="translate(0.666504 0.5)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<svg {...props} width="81" height="85" viewBox="0 0 81 85" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M40.98 44C53.1302 44 62.98 34.1503 62.98 22C62.98 9.84974 53.1302 0 40.98 0C28.8297 0 18.98 9.84974 18.98 22C18.98 34.1503 28.8297 44 40.98 44ZM49.23 22L40.98 13.75L32.73 22L40.98 30.25L49.23 22Z"
|
||||
fill={primary}
|
||||
/>
|
||||
<path
|
||||
d="M2.5 63.1986C12.9105 63.1986 20.7173 53.0581 20.7173 53.0581C20.7173 53.0581 28.5241 63.1986 38.9346 63.1986C49.3409 63.1986 59.7514 53.0581 59.7514 53.0581C59.7514 53.0581 70.1619 63.1986 77.9687 63.1986M2.5 82.2504C12.9105 82.2504 20.7173 72.1099 20.7173 72.1099C20.7173 72.1099 28.5241 82.2504 38.9346 82.2504C49.3409 82.2504 59.7514 72.1099 59.7514 72.1099C59.7514 72.1099 70.1619 82.2504 77.9687 82.2504"
|
||||
stroke={secondary}
|
||||
strokeWidth="5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -76,65 +80,19 @@ export const EmptyNftsIcon = (props: SVGProps) => {
|
||||
export const EmptyTokensIcon = (props: SVGProps) => {
|
||||
const { primary, secondary } = useEmptyStateIconColors()
|
||||
return (
|
||||
<svg {...props} width="66" height="66" viewBox="0 0 66 66" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_963_209164)">
|
||||
<rect
|
||||
x="35.5728"
|
||||
y="23.4258"
|
||||
width="8.46281"
|
||||
height="36.0727"
|
||||
rx="2.32727"
|
||||
transform="rotate(90 35.5728 23.4258)"
|
||||
fill={secondary}
|
||||
/>
|
||||
<rect
|
||||
x="35.5728"
|
||||
y="11.9629"
|
||||
width="8.46281"
|
||||
height="36.0727"
|
||||
rx="2.32727"
|
||||
transform="rotate(90 35.5728 11.9629)"
|
||||
fill={secondary}
|
||||
/>
|
||||
<rect
|
||||
x="40.2271"
|
||||
y="0.5"
|
||||
width="8.46281"
|
||||
height="36.0727"
|
||||
rx="2.32727"
|
||||
transform="rotate(90 40.2271 0.5)"
|
||||
fill={secondary}
|
||||
/>
|
||||
<rect
|
||||
x="39.0635"
|
||||
y="34.8887"
|
||||
width="8.46281"
|
||||
height="36.0727"
|
||||
rx="2.32727"
|
||||
transform="rotate(90 39.0635 34.8887)"
|
||||
fill={secondary}
|
||||
/>
|
||||
<rect
|
||||
x="35.5728"
|
||||
y="46.3511"
|
||||
width="8.46281"
|
||||
height="36.0727"
|
||||
rx="2.32727"
|
||||
transform="rotate(90 35.5728 46.3511)"
|
||||
fill={secondary}
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M49.4092 65.5C58.2457 65.5 65.4092 58.3366 65.4092 49.5C65.4092 40.6634 58.2457 33.5 49.4092 33.5C40.5726 33.5 33.4092 40.6634 33.4092 49.5C33.4092 58.3366 40.5726 65.5 49.4092 65.5ZM55.4092 49.5L49.4092 43.5L43.4092 49.5L49.4092 55.5L55.4092 49.5Z"
|
||||
fill={primary}
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_963_209164">
|
||||
<rect width="66" height="65" fill="white" transform="translate(0 0.5)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<svg {...props} width="91" height="80" viewBox="0 0 91 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M3 0C1.61929 0 0.5 1.11929 0.5 2.5C0.5 3.88071 1.61929 5 3 5L57 5C58.3807 5.00001 59.5 3.88072 59.5 2.50001C59.5 1.11929 58.3807 5.06009e-06 57 4.93939e-06L3 0ZM7.51953 11.1055H10.5143C13.5091 11.1055 15.0065 12.6029 15.0065 15.5977V18.5924C15.0065 21.5872 13.5091 23.0846 10.5143 23.0846H7.51953C4.52474 23.0846 3.02734 21.5872 3.02734 18.5924V15.5977C3.02734 12.6029 4.52474 11.1055 7.51953 11.1055ZM31.4779 11.1055H28.4831C25.4883 11.1055 23.9909 12.6029 23.9909 15.5977V18.5924C23.9909 21.5872 25.4883 23.0846 28.4831 23.0846H31.4779C34.4726 23.0846 35.97 21.5872 35.97 18.5924V15.5977C35.97 12.6029 34.4726 11.1055 31.4779 11.1055ZM49.4466 11.1055H52.4414C55.4362 11.1055 56.9336 12.6029 56.9336 15.5977V18.5924C56.9336 21.5872 55.4362 23.0846 52.4414 23.0846H49.4466C46.4518 23.0846 44.9544 21.5872 44.9544 18.5924V15.5977C44.9544 12.6029 46.4518 11.1055 49.4466 11.1055ZM10.5143 31.47H7.51953C4.52474 31.47 3.02734 32.9674 3.02734 35.9622V38.957C3.02734 41.9518 4.52474 43.4492 7.51953 43.4492H10.5143C13.5091 43.4492 15.0065 41.9518 15.0065 38.957V35.9622C15.0065 32.9674 13.5091 31.47 10.5143 31.47ZM28.4831 31.47H31.4779C34.4726 31.47 35.97 32.9674 35.97 35.9622V38.957C35.97 41.9518 34.4726 43.4492 31.4779 43.4492H28.4831C25.4883 43.4492 23.9909 41.9518 23.9909 38.957V35.9622C23.9909 32.9674 25.4883 31.47 28.4831 31.47ZM52.4414 31.47H49.4466C46.4518 31.47 44.9544 32.9674 44.9544 35.9622V38.957C44.9544 41.1067 45.726 42.4849 47.2691 43.0915C49.7015 39.5566 52.9858 36.6532 56.8257 34.6779C56.4335 32.5393 54.9721 31.47 52.4414 31.47Z"
|
||||
fill={secondary}
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M68.7031 79.8125C80.8534 79.8125 90.7031 69.9628 90.7031 57.8125C90.7031 45.6622 80.8534 35.8125 68.7031 35.8125C56.5529 35.8125 46.7031 45.6622 46.7031 57.8125C46.7031 69.9628 56.5529 79.8125 68.7031 79.8125ZM76.9524 57.8122L68.7027 49.5625L60.4531 57.8122L68.7027 66.0619L76.9524 57.8122Z"
|
||||
fill={primary}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Trace } from '@uniswap/analytics'
|
||||
import { InterfacePageName } from '@uniswap/analytics-events'
|
||||
import { useDetailsV2Enabled } from 'featureFlags/flags/nftDetails'
|
||||
import { useNftAssetDetails } from 'graphql/data/nft/Details'
|
||||
import { AssetDetails } from 'nft/components/details/AssetDetails'
|
||||
import { AssetDetailsLoading } from 'nft/components/details/AssetDetailsLoading'
|
||||
import { AssetPriceDetails } from 'nft/components/details/AssetPriceDetails'
|
||||
import { NftDetails } from 'nft/components/details/NftDetails'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
@@ -37,11 +39,11 @@ const AssetPriceDetailsContainer = styled.div`
|
||||
const AssetPage = () => {
|
||||
const { tokenId = '', contractAddress = '' } = useParams()
|
||||
const { data, loading } = useNftAssetDetails(contractAddress, tokenId)
|
||||
const detailsV2Enabled = useDetailsV2Enabled()
|
||||
|
||||
const [asset, collection] = data
|
||||
|
||||
if (loading) return <AssetDetailsLoading />
|
||||
|
||||
if (loading && !detailsV2Enabled) return <AssetDetailsLoading />
|
||||
return (
|
||||
<>
|
||||
<Trace
|
||||
@@ -49,14 +51,18 @@ const AssetPage = () => {
|
||||
properties={{ collection_address: contractAddress, token_id: tokenId }}
|
||||
shouldLogImpression
|
||||
>
|
||||
{!!asset && !!collection && (
|
||||
<AssetContainer>
|
||||
<AssetDetails collection={collection} asset={asset} />
|
||||
<AssetPriceDetailsContainer>
|
||||
<AssetPriceDetails collection={collection} asset={asset} />
|
||||
</AssetPriceDetailsContainer>
|
||||
</AssetContainer>
|
||||
)}
|
||||
{!!asset && !!collection ? (
|
||||
detailsV2Enabled ? (
|
||||
<NftDetails asset={asset} collection={collection} />
|
||||
) : (
|
||||
<AssetContainer>
|
||||
<AssetDetails collection={collection} asset={asset} />
|
||||
<AssetPriceDetailsContainer>
|
||||
<AssetPriceDetails collection={collection} asset={asset} />
|
||||
</AssetPriceDetailsContainer>
|
||||
</AssetContainer>
|
||||
)
|
||||
) : null}
|
||||
</Trace>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Trans } from '@lingui/macro'
|
||||
import { Trace } from '@uniswap/analytics'
|
||||
import { InterfacePageName } from '@uniswap/analytics-events'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useToggleAccountDrawer } from 'components/AccountDrawer'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import { useToggleWalletDrawer } from 'components/WalletDropdown'
|
||||
import { XXXL_BAG_WIDTH } from 'nft/components/bag/Bag'
|
||||
import { ListPage } from 'nft/components/profile/list/ListPage'
|
||||
import { ProfilePage } from 'nft/components/profile/view/ProfilePage'
|
||||
@@ -67,7 +67,7 @@ const ProfileContent = () => {
|
||||
|
||||
const { account } = useWeb3React()
|
||||
const accountRef = useRef(account)
|
||||
const toggleWalletDrawer = useToggleWalletDrawer()
|
||||
const toggleWalletDrawer = useToggleAccountDrawer()
|
||||
|
||||
useEffect(() => {
|
||||
if (accountRef.current !== account) {
|
||||
|
||||
@@ -37,11 +37,6 @@ export const ListingMarkets: ListingMarket[] = [
|
||||
fee: 0.5,
|
||||
icon: '/nft/svgs/marketplaces/x2y2.svg',
|
||||
},
|
||||
{
|
||||
name: 'LooksRare',
|
||||
fee: 1.5,
|
||||
icon: '/nft/svgs/marketplaces/looksrare.svg',
|
||||
},
|
||||
{
|
||||
name: 'OpenSea',
|
||||
fee: 0,
|
||||
|
||||
@@ -6,10 +6,10 @@ import { BrowserEvent, InterfaceElementName, InterfaceEventName } from '@uniswap
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import { FeeAmount, NonfungiblePositionManager } from '@uniswap/v3-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useToggleAccountDrawer } from 'components/AccountDrawer'
|
||||
import OwnershipWarning from 'components/addLiquidity/OwnershipWarning'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
|
||||
import { useToggleWalletDrawer } from 'components/WalletDropdown'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { useSingleCallResult } from 'lib/hooks/multicall'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
@@ -91,7 +91,7 @@ export default function AddLiquidity() {
|
||||
const { account, chainId, provider } = useWeb3React()
|
||||
const theme = useTheme()
|
||||
|
||||
const toggleWalletDrawer = useToggleWalletDrawer() // toggle wallet when disconnected
|
||||
const toggleWalletDrawer = useToggleAccountDrawer() // toggle wallet when disconnected
|
||||
const expertMode = useIsExpertMode()
|
||||
const addTransaction = useTransactionAdder()
|
||||
const positionManager = useV3NFTPositionManagerContract()
|
||||
|
||||
@@ -5,10 +5,10 @@ import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
|
||||
import { Currency, CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { useToggleAccountDrawer } from 'components/AccountDrawer'
|
||||
import { sendEvent } from 'components/analytics'
|
||||
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
|
||||
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
|
||||
import { useToggleWalletDrawer } from 'components/WalletDropdown'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { Plus } from 'react-feather'
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom'
|
||||
@@ -67,7 +67,7 @@ export default function AddLiquidity() {
|
||||
((currencyA && currencyA.equals(wrappedNativeCurrency)) || (currencyB && currencyB.equals(wrappedNativeCurrency)))
|
||||
)
|
||||
|
||||
const toggleWalletDrawer = useToggleWalletDrawer() // toggle wallet when disconnected
|
||||
const toggleWalletDrawer = useToggleAccountDrawer() // toggle wallet when disconnected
|
||||
|
||||
const expertMode = useIsExpertMode()
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user