Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
120ad935fa | ||
|
|
4eaf16b624 | ||
|
|
857e2915ab | ||
|
|
7410c81b42 | ||
|
|
fb05439d32 | ||
|
|
fb7eade70b | ||
|
|
bd2b2c487a | ||
|
|
2f004ed1d9 | ||
|
|
db257c73f2 | ||
|
|
7c37b9d00e | ||
|
|
7688c527f0 | ||
|
|
06dd41a9cd | ||
|
|
850fec40a9 | ||
|
|
3c7eabc3d8 | ||
|
|
048607080c | ||
|
|
a0f20c54d8 | ||
|
|
da79abbc0d | ||
|
|
5ac08e1142 | ||
|
|
d330eea375 | ||
|
|
35dace7bfe | ||
|
|
8ce8e17f62 | ||
|
|
eb105b6ec7 | ||
|
|
267e7de2b6 | ||
|
|
ab9f2af054 | ||
|
|
67b405dd42 | ||
|
|
281dbf4305 | ||
|
|
369f2d7dfa | ||
|
|
803c749b13 | ||
|
|
8818dadf24 | ||
|
|
d179fc6b84 | ||
|
|
18ec675c52 | ||
|
|
1a79bac893 |
19
.eslintrc.js
19
.eslintrc.js
@@ -4,4 +4,23 @@ require('@uniswap/eslint-config/load')
|
||||
|
||||
module.exports = {
|
||||
extends: '@uniswap/eslint-config/react',
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: 'zustand',
|
||||
importNames: ['default'],
|
||||
message: 'Default import from zustand is deprecated. Import `{ create }` instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1 +1 @@
|
||||
@uniswap/web
|
||||
@uniswap/web-reviewers
|
||||
36
.github/pull_request_template.md
vendored
36
.github/pull_request_template.md
vendored
@@ -1,24 +1,22 @@
|
||||
Your PR title must follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary), and should start with one of the following [types](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#type):
|
||||
## Description
|
||||
|
||||
- build: Changes that affect the build system or external dependencies (example scopes: yarn, eslint, typescript)
|
||||
- ci: Changes to our CI configuration files and scripts (example scopes: vercel, github, cypress)
|
||||
- docs: Documentation only changes
|
||||
- feat: A new feature
|
||||
- fix: A bug fix
|
||||
- perf: A code change that improves performance
|
||||
- refactor: A code change that neither fixes a bug nor adds a feature
|
||||
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
||||
- test: Adding missing tests or correcting existing tests
|
||||
_[Summary of change, motivation, and context.]_
|
||||
|
||||
Example commit messages:
|
||||
- _Link to JIRA ticket, slack thread, or relevant docs helpful for providing context to reviewers._
|
||||
|
||||
- feat: adds support for gnosis safe wallet
|
||||
- fix: removes a polling memory leak
|
||||
- chore: bumps redux version
|
||||
- _Note: Your PR title must follow conventions [outlined here](https://github.com/Uniswap/interface#contributions)._
|
||||
|
||||
Other things to note:
|
||||
## Screen Capture
|
||||
| Before | After |
|
||||
| ---------------- |-----------------|
|
||||
| _insert_before_ | _insert_after_ |
|
||||
|
||||
- Please describe the change using verb statements (ex: Removes X from Y)
|
||||
- PRs with multiple changes should use a list of verb statements
|
||||
- Add any relevant unit / integration tests
|
||||
- Changes will be previewable via vercel. Non-obvious changes should include instructions for how to reproduce them
|
||||
|
||||
## Test Plan
|
||||
#### Manual
|
||||
|
||||
_[Steps of how you are testing the change and ensuring no regression.]_
|
||||
|
||||
#### Automated
|
||||
- [ ] Unit test
|
||||
- [ ] Integration/E2E test
|
||||
|
||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -42,6 +42,8 @@ jobs:
|
||||
needs: tag
|
||||
if: ${{ needs.tag.outputs.new_tag != null }}
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: release
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup
|
||||
|
||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -113,6 +113,12 @@ jobs:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
verbose: true
|
||||
|
||||
# Included as a single job to check for cypress-test-matrix success, as a matrix cannot be checked.
|
||||
cypress-tests:
|
||||
if: ${{ always() }}
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -29,6 +29,10 @@ schema.graphql
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
instrumented
|
||||
.nyc_output
|
||||
.nyc_output/**/*
|
||||
|
||||
/.netlify
|
||||
|
||||
npm-debug.log*
|
||||
|
||||
7
.nycrc
Normal file
7
.nycrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "@istanbuljs/nyc-config-typescript",
|
||||
"all": true,
|
||||
"report-dir": "coverage",
|
||||
"soureMap": false,
|
||||
"instrument": false
|
||||
}
|
||||
27
README.md
27
README.md
@@ -38,6 +38,33 @@ You can block an entire list of tokens by passing in a tokenlist like [here](./s
|
||||
|
||||
For steps on local deployment, development, and code contribution, please see [CONTRIBUTING](./CONTRIBUTING.md).
|
||||
|
||||
#### PR Title
|
||||
Your PR title must follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary), and should start with one of the following [types](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#type):
|
||||
|
||||
- build: Changes that affect the build system or external dependencies (example scopes: yarn, eslint, typescript)
|
||||
- ci: Changes to our CI configuration files and scripts (example scopes: vercel, github, cypress)
|
||||
- docs: Documentation only changes
|
||||
- feat: A new feature
|
||||
- fix: A bug fix
|
||||
- perf: A code change that improves performance
|
||||
- refactor: A code change that neither fixes a bug nor adds a feature
|
||||
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
||||
- test: Adding missing tests or correcting existing tests
|
||||
|
||||
Example commit messages:
|
||||
|
||||
- feat: adds support for gnosis safe wallet
|
||||
- fix: removes a polling memory leak
|
||||
- chore: bumps redux version
|
||||
|
||||
Other things to note:
|
||||
|
||||
- Please describe the change using verb statements (ex: Removes X from Y)
|
||||
- PRs with multiple changes should use a list of verb statements
|
||||
- Add any relevant unit / integration tests
|
||||
- Changes will be previewable via vercel. Non-obvious changes should include instructions for how to reproduce them
|
||||
|
||||
|
||||
## Accessing Uniswap V2
|
||||
|
||||
The Uniswap Interface supports swapping, adding liquidity, removing liquidity and migrating liquidity for Uniswap protocol V2.
|
||||
|
||||
21
codecov.yml
Normal file
21
codecov.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
ignore:
|
||||
- "**/generated/**/*"
|
||||
- "**/generated/*"
|
||||
- "**/cypress/**/*"
|
||||
- "cypress/**/*"
|
||||
- "**/instrumented/**/*"
|
||||
- "**/styles/**/*"
|
||||
- "styles/**/*"
|
||||
- "**/constants/**/*"
|
||||
- "constants/**/*"
|
||||
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 1%
|
||||
if_ci_failed: error
|
||||
patch:
|
||||
default:
|
||||
target: 80%
|
||||
@@ -8,6 +8,14 @@ const commitHash = require('child_process').execSync('git rev-parse HEAD')
|
||||
module.exports = {
|
||||
babel: {
|
||||
plugins: ['@vanilla-extract/babel-plugin'],
|
||||
env: {
|
||||
test: {
|
||||
plugins: ['istanbul'],
|
||||
},
|
||||
development: {
|
||||
plugins: ['istanbul'],
|
||||
},
|
||||
},
|
||||
},
|
||||
jest: {
|
||||
configure(jestConfig) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import codeCoverageTask from '@cypress/code-coverage/task'
|
||||
import { defineConfig } from 'cypress'
|
||||
|
||||
export default defineConfig({
|
||||
@@ -7,6 +8,7 @@ export default defineConfig({
|
||||
chromeWebSecurity: false,
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
codeCoverageTask(on, config)
|
||||
return {
|
||||
...config,
|
||||
// Only enable Chrome.
|
||||
|
||||
@@ -53,4 +53,11 @@ describe('Testing nfts', () => {
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('nft-view-self-nfts')).click()
|
||||
})
|
||||
|
||||
it('should close the sidebar when navigating to NFT details', () => {
|
||||
cy.get(getTestSelector('web3-status-connected')).click()
|
||||
cy.get(getTestSelector('mini-portfolio-nav-nfts')).click()
|
||||
cy.get(getTestSelector('mini-portfolio-nft')).first().click()
|
||||
cy.contains('Buy crypto').should('not.be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ describe('Token explore', () => {
|
||||
|
||||
it('should load token leaderboard', () => {
|
||||
cy.visit('/tokens/ethereum')
|
||||
cy.get(getTestSelectorStartsWith('token-table')).its('length').should('be.eq', 100)
|
||||
cy.get(getTestSelectorStartsWith('token-table')).its('length').should('be.greaterThan', 0)
|
||||
// check sorted svg icon is present in volume cell, since tokens are sorted by volume by default
|
||||
cy.get(getTestSelector('header-row')).find(getTestSelector('volume-cell')).find('svg').should('exist')
|
||||
cy.get(getTestSelector('token-table-row-ETH')).find(getTestSelector('name-cell')).should('include.text', 'Ether')
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
// Import commands.ts using ES2015 syntax:
|
||||
import { injected } from './ethereum'
|
||||
import assert = require('assert')
|
||||
import '@cypress/code-coverage/support'
|
||||
|
||||
import { FeatureFlag } from '../../src/featureFlags/flags/featureFlags'
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.2",
|
||||
"@coinbase/wallet-sdk": "^3.6.4",
|
||||
"@cypress/code-coverage": "^3.10.0",
|
||||
"@fontsource/ibm-plex-mono": "^4.5.1",
|
||||
"@fontsource/inter": "^4.5.1",
|
||||
"@graphql-codegen/cli": "^2.15.0",
|
||||
@@ -131,11 +132,11 @@
|
||||
"@reach/dialog": "^0.10.3",
|
||||
"@reach/portal": "^0.10.3",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@sentry/react": "^7.40.0",
|
||||
"@sentry/tracing": "^7.40.0",
|
||||
"@sentry/react": "^7.45.0",
|
||||
"@sentry/tracing": "^7.45.0",
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@uniswap/analytics": "^1.3.1",
|
||||
"@uniswap/analytics-events": "^2.7.0",
|
||||
"@uniswap/analytics-events": "^2.8.0",
|
||||
"@uniswap/conedison": "^1.4.0",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
@@ -178,6 +179,7 @@
|
||||
"@web3-react/walletconnect": "8.1.2-beta.0",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
"array.prototype.flatmap": "^1.2.4",
|
||||
"babel-plugin-istanbul": "^6.1.1",
|
||||
"cids": "^1.0.0",
|
||||
"clsx": "^1.1.1",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useMiniPortfolioFlag } from 'featureFlags/flags/miniPortfolio'
|
||||
import { NftGraphqlVariant, useNftGraphqlFlag } from 'featureFlags/flags/nftlGraphql'
|
||||
import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken'
|
||||
import { SwapWidgetVariant, useSwapWidgetFlag } from 'featureFlags/flags/swapWidget'
|
||||
import { TaxServiceVariant, useTaxServiceBannerFlag } from 'featureFlags/flags/taxServiceBanner'
|
||||
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
|
||||
import { useUpdateAtom } from 'jotai/utils'
|
||||
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
|
||||
@@ -242,12 +241,6 @@ export default function FeatureFlagModal() {
|
||||
featureFlag={FeatureFlag.nftGraphql}
|
||||
label="Migrate NFT read endpoints to GQL"
|
||||
/>
|
||||
<FeatureFlagOption
|
||||
variant={TaxServiceVariant}
|
||||
value={useTaxServiceBannerFlag()}
|
||||
featureFlag={FeatureFlag.taxService}
|
||||
label="Tax Service Banner"
|
||||
/>
|
||||
<FeatureFlagGroup name="Debug">
|
||||
<FeatureFlagOption
|
||||
variant={TraceJsonRpcVariant}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { MouseoverTooltip } from 'components/Tooltip'
|
||||
import { Unicon } from 'components/Unicon'
|
||||
import { Connection, ConnectionType } from 'connection'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import { useIsMobile } from 'nft/hooks'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
import { flexColumnNoWrap } from 'theme/styles'
|
||||
|
||||
import sockImg from '../../assets/svg/socks.svg'
|
||||
@@ -64,77 +60,31 @@ const Socks = () => {
|
||||
const MiniWalletIcon = ({ connection, side }: { connection: Connection; side: 'left' | 'right' }) => {
|
||||
return (
|
||||
<MiniIconContainer side={side}>
|
||||
<MiniImg src={connection.icon} alt={`${connection.name} icon`} />
|
||||
<MiniImg src={connection.getIcon?.()} alt={`${connection.getName()} icon`} />
|
||||
</MiniIconContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const Divider = styled.div`
|
||||
border-bottom: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
margin: 12px 0;
|
||||
`
|
||||
|
||||
function UniconTooltip({ children, enabled }: PropsWithChildren<{ enabled?: boolean }>) {
|
||||
return (
|
||||
<MouseoverTooltip
|
||||
offsetY={8}
|
||||
disableHover={!enabled}
|
||||
text={
|
||||
// TODO(cartcrom): add Learn More link when unicon microsite is polished
|
||||
<>
|
||||
<ThemedText.SubHeaderSmall color="textPrimary" paddingTop="4px">
|
||||
This is your Unicon
|
||||
</ThemedText.SubHeaderSmall>
|
||||
<Divider />
|
||||
<ThemedText.Caption paddingBottom="4px">
|
||||
Unicons are avatars for your wallet, generated from your address.
|
||||
</ThemedText.Caption>
|
||||
</>
|
||||
}
|
||||
placement="bottom"
|
||||
>
|
||||
<div>{children}</div>
|
||||
</MouseoverTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const MainWalletIcon = ({
|
||||
connection,
|
||||
size,
|
||||
enableInfotips,
|
||||
}: {
|
||||
connection: Connection
|
||||
size: number
|
||||
enableInfotips?: boolean
|
||||
}) => {
|
||||
const MainWalletIcon = ({ connection, size }: { connection: Connection; size: number }) => {
|
||||
const { account } = useWeb3React()
|
||||
const { avatar } = useENSAvatar(account ?? undefined)
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
if (!account) {
|
||||
return null
|
||||
} else if (avatar || (connection.type === ConnectionType.INJECTED && connection.name === 'MetaMask')) {
|
||||
} else if (avatar || (connection.type === ConnectionType.INJECTED && connection.getName() === 'MetaMask')) {
|
||||
return <Identicon size={size} />
|
||||
} else {
|
||||
return isMobile ? (
|
||||
<Unicon address={account} size={size} />
|
||||
) : (
|
||||
<UniconTooltip enabled={enableInfotips}>
|
||||
<Unicon address={account} size={size} />
|
||||
</UniconTooltip>
|
||||
)
|
||||
return <Unicon address={account} size={size} />
|
||||
}
|
||||
}
|
||||
|
||||
export default function StatusIcon({
|
||||
connection,
|
||||
size = 16,
|
||||
enableInfotips,
|
||||
showMiniIcons = true,
|
||||
}: {
|
||||
connection: Connection
|
||||
size?: number
|
||||
enableInfotips?: boolean
|
||||
showMiniIcons?: boolean
|
||||
}) {
|
||||
const hasSocks = useHasSocks()
|
||||
@@ -142,7 +92,7 @@ export default function StatusIcon({
|
||||
return (
|
||||
<IconWrapper size={size}>
|
||||
{hasSocks && showMiniIcons && <Socks />}
|
||||
<MainWalletIcon connection={connection} size={size} enableInfotips={enableInfotips} />
|
||||
<MainWalletIcon connection={connection} size={size} />
|
||||
{showMiniIcons && <MiniWalletIcon connection={connection} side="right" />}
|
||||
</IconWrapper>
|
||||
)
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { ReactComponent as AppleLogo } from 'assets/svg/apple_logo.svg'
|
||||
import FeatureFlagModal from 'components/FeatureFlagModal/FeatureFlagModal'
|
||||
import { PrivacyPolicyModal } from 'components/PrivacyPolicy'
|
||||
import { APP_STORE_LINK } from 'components/WalletDropdown/DownloadButton'
|
||||
import NewBadge from 'components/WalletModal/NewBadge'
|
||||
import { useMgtmEnabled, useMGTMMicrositeEnabled } from 'featureFlags/flags/mgtm'
|
||||
import { useOnClickOutside } from 'hooks/useOnClickOutside'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
import { BarChartIcon, EllipsisIcon, GovernanceIcon, PoolIcon } from 'nft/components/icons'
|
||||
import {
|
||||
BarChartIcon,
|
||||
DiscordIconMenu,
|
||||
EllipsisIcon,
|
||||
GithubIconMenu,
|
||||
GovernanceIcon,
|
||||
PoolIcon,
|
||||
TwitterIconMenu,
|
||||
} from 'nft/components/icons'
|
||||
import { body, bodySmall } from 'nft/css/common.css'
|
||||
import { themeVars } from 'nft/css/sprinkles.css'
|
||||
import { ReactNode, useReducer, useRef } from 'react'
|
||||
import { DollarSign, HelpCircle, Shield, Terminal } from 'react-feather'
|
||||
import { NavLink, NavLinkProps } from 'react-router-dom'
|
||||
import { useToggleModal } from 'state/application/hooks'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import { isDevelopmentEnv, isStagingEnv } from 'utils/env'
|
||||
|
||||
import { useToggleModal, useToggleTaxServiceModal } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
import * as styles from './MenuDropdown.css'
|
||||
import { NavDropdown } from './NavDropdown'
|
||||
@@ -27,30 +31,20 @@ const PrimaryMenuRow = ({
|
||||
href,
|
||||
close,
|
||||
children,
|
||||
onClick,
|
||||
}: {
|
||||
to?: NavLinkProps['to']
|
||||
href?: string
|
||||
close?: () => void
|
||||
children: ReactNode
|
||||
onClick?: () => void
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{to ? (
|
||||
<NavLink to={to} className={styles.MenuRow} onClick={onClick}>
|
||||
<NavLink to={to} className={styles.MenuRow}>
|
||||
<Row onClick={close}>{children}</Row>
|
||||
</NavLink>
|
||||
) : (
|
||||
<Row
|
||||
as="a"
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.MenuRow}
|
||||
onClick={onClick}
|
||||
cursor="pointer"
|
||||
>
|
||||
<Row as="a" href={href} target="_blank" rel="noopener noreferrer" className={styles.MenuRow}>
|
||||
{children}
|
||||
</Row>
|
||||
)}
|
||||
@@ -97,6 +91,10 @@ const Separator = () => {
|
||||
return <Box className={styles.Separator} />
|
||||
}
|
||||
|
||||
const IconRow = ({ children }: { children: ReactNode }) => {
|
||||
return <Row className={styles.IconRow}>{children}</Row>
|
||||
}
|
||||
|
||||
const Icon = ({ href, children }: { href?: string; children: ReactNode }) => {
|
||||
return (
|
||||
<>
|
||||
@@ -107,7 +105,7 @@ const Icon = ({ href, children }: { href?: string; children: ReactNode }) => {
|
||||
rel={href ? 'noopener noreferrer' : undefined}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
color="textSecondary"
|
||||
color="textPrimary"
|
||||
background="none"
|
||||
border="none"
|
||||
justifyContent="center"
|
||||
@@ -120,47 +118,19 @@ const Icon = ({ href, children }: { href?: string; children: ReactNode }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const StyledAppleLogo = styled(AppleLogo)`
|
||||
fill: ${({ theme }) => theme.textSecondary};
|
||||
padding: 2px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
`
|
||||
|
||||
const BadgeWrapper = styled.div`
|
||||
margin-left: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
export const MenuDropdown = () => {
|
||||
const theme = useTheme()
|
||||
const [isOpen, toggleOpen] = useReducer((s) => !s, false)
|
||||
const togglePrivacyPolicy = useToggleModal(ApplicationModal.PRIVACY_POLICY)
|
||||
const openFeatureFlagsModal = useToggleModal(ApplicationModal.FEATURE_FLAGS)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
useOnClickOutside(ref, isOpen ? toggleOpen : undefined)
|
||||
const toggleTaxServiceModal = useToggleTaxServiceModal()
|
||||
const theme = useTheme()
|
||||
|
||||
const mgtmEnabled = useMgtmEnabled()
|
||||
const micrositeEnabled = useMGTMMicrositeEnabled()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box position="relative" ref={ref}>
|
||||
<NavIcon
|
||||
isActive={isOpen}
|
||||
onClick={toggleOpen}
|
||||
label={isOpen ? t`Show resources` : t`Hide resources`}
|
||||
activeBackground={isOpen}
|
||||
>
|
||||
<EllipsisIcon
|
||||
viewBox="0 0 20 20"
|
||||
width={24}
|
||||
height={24}
|
||||
color={isOpen ? theme.accentActive : theme.textSecondary}
|
||||
/>
|
||||
<NavIcon isActive={isOpen} onClick={toggleOpen} label={isOpen ? t`Show resources` : t`Hide resources`}>
|
||||
<EllipsisIcon viewBox="0 0 20 20" width={24} height={24} />
|
||||
</NavIcon>
|
||||
|
||||
{isOpen && (
|
||||
@@ -179,90 +149,78 @@ export const MenuDropdown = () => {
|
||||
</Box>
|
||||
<PrimaryMenuRow to="/vote" close={toggleOpen}>
|
||||
<Icon>
|
||||
<GovernanceIcon width={24} height={24} />
|
||||
<GovernanceIcon width={24} height={24} color={theme.textSecondary} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Governance</Trans>
|
||||
<Trans>Vote in governance</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
<PrimaryMenuRow href="https://info.uniswap.org/#/">
|
||||
<Icon>
|
||||
<BarChartIcon width={24} height={24} />
|
||||
<BarChartIcon width={24} height={24} color={theme.textSecondary} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Token analytics</Trans>
|
||||
<Trans>View more analytics</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
<PrimaryMenuRow href="https://help.uniswap.org/en/">
|
||||
<Icon>
|
||||
<HelpCircle color={theme.textSecondary} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Help center</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
<PrimaryMenuRow href="https://docs.uniswap.org/">
|
||||
<Icon>
|
||||
<Terminal color={theme.textSecondary} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Documentation</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
<PrimaryMenuRow
|
||||
</Column>
|
||||
<Separator />
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection={{ sm: 'row', md: 'column' }}
|
||||
flexWrap="wrap"
|
||||
alignItems={{ sm: 'center', md: 'flex-start' }}
|
||||
paddingX="8"
|
||||
>
|
||||
<SecondaryLinkedText href="https://help.uniswap.org/en/">
|
||||
<Trans>Help center</Trans> ↗
|
||||
</SecondaryLinkedText>
|
||||
<SecondaryLinkedText href="https://docs.uniswap.org/">
|
||||
<Trans>Documentation</Trans> ↗
|
||||
</SecondaryLinkedText>
|
||||
<SecondaryLinkedText href="https://uniswap.canny.io/feature-requests">
|
||||
<Trans>Feedback</Trans> ↗
|
||||
</SecondaryLinkedText>
|
||||
<SecondaryLinkedText
|
||||
onClick={() => {
|
||||
toggleOpen()
|
||||
togglePrivacyPolicy()
|
||||
}}
|
||||
>
|
||||
<Icon>
|
||||
<Shield color={theme.textSecondary} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Legal & Privacy</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
<PrimaryMenuRow
|
||||
onClick={() => {
|
||||
toggleTaxServiceModal()
|
||||
toggleOpen()
|
||||
}}
|
||||
>
|
||||
<Icon>
|
||||
<DollarSign size="24px" color={theme.textSecondary} />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Save on Tax Services</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
</PrimaryMenuRow>
|
||||
{mgtmEnabled && (
|
||||
<Box display={micrositeEnabled ? { xxl: 'flex', xxxl: 'none' } : 'flex'}>
|
||||
<PrimaryMenuRow
|
||||
to={micrositeEnabled ? '/wallet' : undefined}
|
||||
href={micrositeEnabled ? undefined : APP_STORE_LINK}
|
||||
close={toggleOpen}
|
||||
>
|
||||
<Icon>
|
||||
<StyledAppleLogo />
|
||||
</Icon>
|
||||
<PrimaryMenuRow.Text>
|
||||
<Trans>Uniswap Wallet</Trans>
|
||||
</PrimaryMenuRow.Text>
|
||||
<BadgeWrapper>
|
||||
<NewBadge />
|
||||
</BadgeWrapper>
|
||||
</PrimaryMenuRow>
|
||||
</Box>
|
||||
)}
|
||||
<Trans>Legal & Privacy</Trans> ↗
|
||||
</SecondaryLinkedText>
|
||||
{(isDevelopmentEnv() || isStagingEnv()) && (
|
||||
<>
|
||||
<Separator />
|
||||
<SecondaryLinkedText onClick={openFeatureFlagsModal}>
|
||||
<Trans>Feature Flags</Trans>
|
||||
</SecondaryLinkedText>
|
||||
</>
|
||||
<SecondaryLinkedText onClick={openFeatureFlagsModal}>
|
||||
<Trans>Feature Flags</Trans>
|
||||
</SecondaryLinkedText>
|
||||
)}
|
||||
</Column>
|
||||
</Box>
|
||||
<IconRow>
|
||||
<Icon href="https://discord.com/invite/FCfyBSbCU5">
|
||||
<DiscordIconMenu
|
||||
className={styles.hover}
|
||||
width={24}
|
||||
height={24}
|
||||
color={themeVars.colors.textSecondary}
|
||||
/>
|
||||
</Icon>
|
||||
<Icon href="https://twitter.com/Uniswap">
|
||||
<TwitterIconMenu
|
||||
className={styles.hover}
|
||||
width={24}
|
||||
height={24}
|
||||
color={themeVars.colors.textSecondary}
|
||||
/>
|
||||
</Icon>
|
||||
<Icon href="https://github.com/Uniswap">
|
||||
<GithubIconMenu
|
||||
className={styles.hover}
|
||||
width={24}
|
||||
height={24}
|
||||
color={themeVars.colors.textSecondary}
|
||||
/>
|
||||
</Icon>
|
||||
</IconRow>
|
||||
</Column>
|
||||
</NavDropdown>
|
||||
)}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { HistoryDuration, SafetyLevel } from 'graphql/data/__generated__/types-a
|
||||
import { useTrendingCollections } from 'graphql/data/nft/TrendingCollections'
|
||||
import { SearchToken } from 'graphql/data/SearchTokens'
|
||||
import useTrendingTokens from 'graphql/data/TrendingTokens'
|
||||
import { CHAIN_ID_TO_BACKEND_NAME } from 'graphql/data/util'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Column, Row } from 'nft/components/Flex'
|
||||
@@ -361,9 +360,7 @@ export const SearchBarDropdown = ({
|
||||
searchHistory,
|
||||
])
|
||||
|
||||
const showBNBComingSoonBadge = Boolean(
|
||||
chainId !== undefined && chainId === SupportedChainId.BNB && !isLoading && !CHAIN_ID_TO_BACKEND_NAME[chainId]
|
||||
)
|
||||
const showBNBComingSoonBadge = chainId === SupportedChainId.BNB && !isLoading
|
||||
|
||||
return (
|
||||
<Box className={styles.searchBarDropdownNft}>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useMGTMMicrositeEnabled } from 'featureFlags/flags/mgtm'
|
||||
import { chainIdToBackendName } from 'graphql/data/util'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { useIsPoolsPage } from 'hooks/useIsPoolsPage'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { Row } from 'nft/components/Flex'
|
||||
import { UniIcon } from 'nft/components/icons'
|
||||
@@ -13,6 +14,7 @@ import { useProfilePageState } from 'nft/hooks'
|
||||
import { ProfilePageStateType } from 'nft/types'
|
||||
import { ReactNode } from 'react'
|
||||
import { NavLink, NavLinkProps, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { Bag } from './Bag'
|
||||
@@ -60,6 +62,8 @@ export const PageTabs = () => {
|
||||
const isNftPage = useIsNftPage()
|
||||
const micrositeEnabled = useMGTMMicrositeEnabled()
|
||||
|
||||
const shouldDisableNFTRoutes = useAtomValue(shouldDisableNFTRoutesAtom)
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem href="/swap" isActive={pathname.startsWith('/swap')}>
|
||||
@@ -68,9 +72,11 @@ export const PageTabs = () => {
|
||||
<MenuItem href={`/tokens/${chainName.toLowerCase()}`} isActive={pathname.startsWith('/tokens')}>
|
||||
<Trans>Tokens</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem dataTestId="nft-nav" href="/nfts" isActive={isNftPage}>
|
||||
<Trans>NFTs</Trans>
|
||||
</MenuItem>
|
||||
{!shouldDisableNFTRoutes && (
|
||||
<MenuItem dataTestId="nft-nav" href="/nfts" isActive={isNftPage}>
|
||||
<Trans>NFTs</Trans>
|
||||
</MenuItem>
|
||||
)}
|
||||
<Box display={{ sm: 'flex', lg: 'none', xxl: 'flex' }} width="full">
|
||||
<MenuItem href="/pools" dataTestId="pool-nav-link" isActive={isPoolActive}>
|
||||
<Trans>Pools</Trans>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import AlertTriangleFilled from 'components/Icons/AlertTriangleFilled'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import { ThemedText } from '../../theme'
|
||||
import { AutoColumn } from '../Column'
|
||||
@@ -12,26 +12,31 @@ const RowNoFlex = styled(AutoRow)`
|
||||
flex-wrap: nowrap;
|
||||
`
|
||||
|
||||
const ColumnContainer = styled(AutoColumn)`
|
||||
margin: 0 12px;
|
||||
`
|
||||
|
||||
export const PopupAlertTriangle = styled(AlertTriangleFilled)`
|
||||
flex-shrink: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
`
|
||||
|
||||
export default function FailedNetworkSwitchPopup({ chainId }: { chainId: SupportedChainId }) {
|
||||
const chainInfo = getChainInfo(chainId)
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<RowNoFlex>
|
||||
<AutoColumn gap="sm">
|
||||
<RowNoFlex style={{ alignItems: 'center' }}>
|
||||
<div style={{ paddingRight: 13 }}>
|
||||
<AlertTriangle color={theme.accentWarning} size={24} display="flex" />
|
||||
</div>
|
||||
<ThemedText.SubHeader>
|
||||
<Trans>Failed to switch networks</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</RowNoFlex>
|
||||
<RowNoFlex gap="12px">
|
||||
<PopupAlertTriangle />
|
||||
<ColumnContainer gap="sm">
|
||||
<ThemedText.SubHeader color="textSecondary">
|
||||
<Trans>Failed to switch networks</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
|
||||
<ThemedText.BodySmall>
|
||||
<ThemedText.BodySmall color="textSecondary">
|
||||
<Trans>To use Uniswap on {chainInfo.label}, switch the network in your wallet’s settings.</Trans>
|
||||
</ThemedText.BodySmall>
|
||||
</AutoColumn>
|
||||
</ColumnContainer>
|
||||
</RowNoFlex>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { animated } from 'react-spring'
|
||||
import { useSpring } from 'react-spring'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
import { PopupContent } from '../../state/application/reducer'
|
||||
import FailedNetworkSwitchPopup from './FailedNetworkSwitchPopup'
|
||||
import TransactionPopup from './TransactionPopup'
|
||||
|
||||
const StyledClose = styled(X)`
|
||||
position: absolute;
|
||||
@@ -23,7 +22,7 @@ const Popup = styled.div`
|
||||
padding: 1em;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
padding-right: 35px;
|
||||
overflow: hidden;
|
||||
@@ -35,16 +34,6 @@ const Popup = styled.div`
|
||||
}
|
||||
`}
|
||||
`
|
||||
const Fader = styled.div`
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: ${({ theme }) => theme.deprecated_bg3};
|
||||
`
|
||||
|
||||
const AnimatedFader = animated(Fader)
|
||||
|
||||
export default function PopupItem({
|
||||
removeAfterMs,
|
||||
@@ -70,14 +59,11 @@ export default function PopupItem({
|
||||
}, [removeAfterMs, removeThisPopup])
|
||||
|
||||
const theme = useTheme()
|
||||
const faderStyle = useSpring({
|
||||
from: { width: '100%' },
|
||||
to: { width: '0%' },
|
||||
config: { duration: removeAfterMs ?? undefined },
|
||||
})
|
||||
|
||||
let popupContent
|
||||
if ('failedSwitchNetwork' in content) {
|
||||
if ('txn' in content) {
|
||||
popupContent = <TransactionPopup hash={content.txn.hash} />
|
||||
} else if ('failedSwitchNetwork' in content) {
|
||||
popupContent = <FailedNetworkSwitchPopup chainId={content.failedSwitchNetwork} />
|
||||
}
|
||||
|
||||
@@ -85,7 +71,6 @@ export default function PopupItem({
|
||||
<Popup>
|
||||
<StyledClose color={theme.textSecondary} onClick={removeThisPopup} />
|
||||
{popupContent}
|
||||
{removeAfterMs !== null ? <AnimatedFader style={faderStyle} /> : null}
|
||||
</Popup>
|
||||
) : null
|
||||
}
|
||||
|
||||
66
src/components/Popups/TransactionPopup.tsx
Normal file
66
src/components/Popups/TransactionPopup.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
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'
|
||||
import { TransactionDetails } from 'state/transactions/types'
|
||||
import styled from 'styled-components/macro'
|
||||
import { EllipsisStyle, ThemedText } from 'theme'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
import { PopupAlertTriangle } from './FailedNetworkSwitchPopup'
|
||||
|
||||
const Descriptor = styled(ThemedText.BodySmall)`
|
||||
${EllipsisStyle}
|
||||
`
|
||||
|
||||
function TransactionPopupContent({ tx, chainId }: { tx: TransactionDetails; chainId: number }) {
|
||||
const success = tx.receipt?.status === 1
|
||||
const tokens = useCombinedActiveList()
|
||||
const activity = parseLocalActivity(tx, chainId, tokens)
|
||||
const { ENSName } = useENSName(activity?.otherAccount)
|
||||
|
||||
if (!activity) return null
|
||||
|
||||
const explorerUrl = getExplorerLink(chainId, tx.hash, ExplorerDataType.TRANSACTION)
|
||||
|
||||
return (
|
||||
<PortfolioRow
|
||||
left={
|
||||
success ? (
|
||||
<Column>
|
||||
<PortfolioLogo
|
||||
chainId={chainId}
|
||||
currencies={activity.currencies}
|
||||
images={activity.logos}
|
||||
accountAddress={activity.otherAccount}
|
||||
/>
|
||||
</Column>
|
||||
) : (
|
||||
<PopupAlertTriangle />
|
||||
)
|
||||
}
|
||||
title={<ThemedText.SubHeader fontWeight={500}>{activity.title}</ThemedText.SubHeader>}
|
||||
descriptor={
|
||||
<Descriptor color="textSecondary">
|
||||
{activity.descriptor}
|
||||
{ENSName ?? activity.otherAccount}
|
||||
</Descriptor>
|
||||
}
|
||||
onClick={() => window.open(explorerUrl, '_blank')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default function TransactionPopup({ hash }: { hash: string }) {
|
||||
const { chainId } = useWeb3React()
|
||||
|
||||
const tx = useTransaction(hash)
|
||||
|
||||
if (!chainId || !tx) return null
|
||||
|
||||
return <TransactionPopupContent tx={tx} chainId={chainId} />
|
||||
}
|
||||
@@ -41,7 +41,7 @@ const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean; xlPadding:
|
||||
position: fixed;
|
||||
top: ${({ extraPadding }) => (extraPadding ? '72px' : '64px')};
|
||||
right: 1rem;
|
||||
max-width: 355px !important;
|
||||
max-width: 376px !important;
|
||||
width: 100%;
|
||||
z-index: 3;
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB |
@@ -1,185 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
|
||||
import { bodySmall, subhead } from 'nft/css/common.css'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { X } from 'react-feather'
|
||||
import { useModalIsOpen, useToggleTaxServiceModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
import { useTaxServiceDismissal } from 'state/user/hooks'
|
||||
import styled from 'styled-components/macro'
|
||||
import { useIsDarkMode } from 'theme/components/ThemeToggle'
|
||||
import { opacify } from 'theme/utils'
|
||||
import { Z_INDEX } from 'theme/zIndex'
|
||||
|
||||
import TaxServiceModal from '.'
|
||||
import CointrackerLogo from './CointrackerLogo.png'
|
||||
import TokenTaxLogo from './TokenTaxLogo.png'
|
||||
|
||||
const PopupContainer = styled.div<{ show: boolean; isDarkMode: boolean }>`
|
||||
box-shadow: ${({ theme }) => theme.deepShadow};
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 13px;
|
||||
cursor: pointer;
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
display: ${({ show }) => (show ? 'flex' : 'none')};
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
right: clamp(0px, 1vw, 16px);
|
||||
z-index: ${Z_INDEX.sticky};
|
||||
transition: ${({
|
||||
theme: {
|
||||
transition: { duration, timing },
|
||||
},
|
||||
}) => `${duration.slow} opacity ${timing.in}`};
|
||||
width: 320px;
|
||||
height: 156px;
|
||||
bottom: 50px;
|
||||
@media screen and (max-width: ${({ theme }) => theme.breakpoint.sm}px) {
|
||||
border-style: solid none;
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
|
||||
background-image: url(${CointrackerLogo}), url(${TokenTaxLogo});
|
||||
background-size: 15%, 20%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: top right 75px, bottom 5px right 7px;
|
||||
@media screen and (max-width: ${({ theme }) => theme.breakpoint.sm}px) {
|
||||
background-size: 48px, 64px;
|
||||
background-position: top right 75px, bottom 20px right 7px;
|
||||
}
|
||||
|
||||
opacity: ${({ isDarkMode }) => (isDarkMode ? '0.9' : '0.25')};
|
||||
}
|
||||
`
|
||||
|
||||
const InnerContainer = styled.div<{ isDarkMode: boolean }>`
|
||||
border-radius: 12px;
|
||||
cursor: auto;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
background-color: ${({ isDarkMode, theme }) =>
|
||||
isDarkMode ? opacify(10, theme.accentAction) : opacify(4, theme.accentAction)};
|
||||
@media screen and (max-width: ${({ theme }) => theme.breakpoint.sm}px) {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
`
|
||||
|
||||
const Button = styled(ThemeButton)`
|
||||
margin-top: auto;
|
||||
margin-right: auto;
|
||||
padding: 8px 24px;
|
||||
gap: 8px;
|
||||
border-radius: 12px;
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
user-select: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 90%;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
export const StyledXButton = styled(X)`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: ${({ theme }) => theme.opacity.hover};
|
||||
}
|
||||
&:active {
|
||||
opacity: ${({ theme }) => theme.opacity.click};
|
||||
}
|
||||
`
|
||||
|
||||
const TAX_SERVICE_DISMISSED = 'TaxServiceToast-dismissed'
|
||||
|
||||
// TODO(lynnshaoyu): remove this count and change taxServiceDismissals in UserState to be a boolean
|
||||
// flag instead after upgrading to redux-persist.
|
||||
const MAX_RENDER_COUNT = 1
|
||||
|
||||
export default function TaxServiceBanner() {
|
||||
const isDarkMode = useIsDarkMode()
|
||||
const [dismissals, addTaxServiceDismissal] = useTaxServiceDismissal()
|
||||
const modalOpen = useModalIsOpen(ApplicationModal.TAX_SERVICE)
|
||||
const toggleTaxServiceModal = useToggleTaxServiceModal()
|
||||
|
||||
const sessionStorageTaxServiceDismissed = sessionStorage.getItem(TAX_SERVICE_DISMISSED)
|
||||
|
||||
if (!sessionStorageTaxServiceDismissed) {
|
||||
sessionStorage.setItem(TAX_SERVICE_DISMISSED, 'false')
|
||||
}
|
||||
const [bannerOpen, setBannerOpen] = useState(
|
||||
sessionStorageTaxServiceDismissed !== 'true' && (dismissals === undefined || dismissals < MAX_RENDER_COUNT)
|
||||
)
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
sessionStorage.setItem(TAX_SERVICE_DISMISSED, 'true')
|
||||
setBannerOpen(false)
|
||||
dismissals === undefined ? addTaxServiceDismissal(1) : addTaxServiceDismissal(dismissals + 1)
|
||||
}, [addTaxServiceDismissal, dismissals])
|
||||
|
||||
const handleLearnMoreClick = useCallback(
|
||||
(e: any) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
toggleTaxServiceModal()
|
||||
},
|
||||
[toggleTaxServiceModal]
|
||||
)
|
||||
|
||||
return (
|
||||
<PopupContainer show={bannerOpen} isDarkMode={isDarkMode}>
|
||||
<InnerContainer isDarkMode={isDarkMode} tabIndex={0}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<TextContainer data-testid="tax-service-description">
|
||||
<div className={subhead} style={{ paddingBottom: '12px' }}>
|
||||
<Trans>Save on your crypto taxes</Trans>
|
||||
</div>
|
||||
<div className={bodySmall} style={{ paddingBottom: '12px' }}>
|
||||
<Trans>Uniswap Labs can save you up to 20% on CoinTracker and TokenTax</Trans>{' '}
|
||||
</div>
|
||||
</TextContainer>
|
||||
<StyledXButton size={20} onClick={handleClose} />
|
||||
</div>
|
||||
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={InterfaceElementName.TAX_SERVICE_BANNER_CTA_BUTTON}
|
||||
>
|
||||
<Button
|
||||
size={ButtonSize.small}
|
||||
emphasis={ButtonEmphasis.promotional}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault()
|
||||
}}
|
||||
onClick={handleLearnMoreClick}
|
||||
data-testid="learn-more-button"
|
||||
>
|
||||
<Trans>Learn more</Trans>
|
||||
</Button>
|
||||
</TraceEvent>
|
||||
</InnerContainer>
|
||||
<TaxServiceModal isOpen={modalOpen} onDismiss={toggleTaxServiceModal} />
|
||||
</PopupContainer>
|
||||
)
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 33 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
@@ -1,17 +0,0 @@
|
||||
import { render, screen } from '../../test-utils'
|
||||
import TaxServiceModal from './'
|
||||
import TaxServiceBanner from './TaxServiceBanner'
|
||||
|
||||
it('renders Tax Service Modal content', async () => {
|
||||
render(<TaxServiceModal isOpen={true} onDismiss={() => null} />)
|
||||
expect(screen.getByText('Save 10% on all plans')).toBeInTheDocument()
|
||||
expect(screen.getByText('New and existing users save up to 20%')).toBeInTheDocument()
|
||||
expect(screen.getAllByTestId('tax-service-option-button')).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('renders Tax Service Banner', async () => {
|
||||
render(<TaxServiceBanner />)
|
||||
expect(screen.getByText('Save on your crypto taxes')).toBeInTheDocument()
|
||||
expect(screen.getAllByTestId('learn-more-button')).toHaveLength(1)
|
||||
expect(screen.getByText('Uniswap Labs can save you up to 20% on CoinTracker and TokenTax')).toBeInTheDocument()
|
||||
})
|
||||
@@ -1,141 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import { ButtonEmphasis } from 'components/Button'
|
||||
import { ButtonSize, ThemeButton } from 'components/Button'
|
||||
import { Box } from 'nft/components/Box'
|
||||
import { bodySmall, subhead } from 'nft/css/common.css'
|
||||
import { memo } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
|
||||
import Modal from '../Modal'
|
||||
import CointrackerFullLogo from './CointrackerFullLogo.png'
|
||||
import { StyledXButton } from './TaxServiceBanner'
|
||||
import TokenTaxFullLogo from './TokenTaxFullLogo.png'
|
||||
|
||||
interface TaxServiceModalProps {
|
||||
isOpen: boolean
|
||||
onDismiss: () => void
|
||||
}
|
||||
|
||||
interface TaxServiceOptionProps {
|
||||
logo: any
|
||||
description: string
|
||||
url: string
|
||||
}
|
||||
|
||||
const InnerContainer = styled.div`
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
width: 420px;
|
||||
height: 268px;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
gap: 20px;
|
||||
padding: 16px;
|
||||
`
|
||||
|
||||
const TaxOptionContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const TaxOptionDescription = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
text-align: center;
|
||||
`
|
||||
|
||||
const TaxOption = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.backgroundModule};
|
||||
border-radius: 12px;
|
||||
cursor: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
justify-content: space-between;
|
||||
padding: 12px;
|
||||
gap: 16px;
|
||||
`
|
||||
|
||||
const StyledImageContainer = styled(Box)`
|
||||
width: 75%;
|
||||
height: 80%;
|
||||
cursor: auto;
|
||||
object-fit: contain;
|
||||
`
|
||||
|
||||
const Button = styled(ThemeButton)`
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
`
|
||||
|
||||
const TOKEN_TAX_URL = 'https://tokentax.co/uniswap?via=uniswap'
|
||||
const COINTRACKER_URL = 'https://www.cointracker.io/partner/uniswap?utm_source=uniswap'
|
||||
|
||||
const TOKEN_TAX_DESCRIPTION = 'Save 10% on all plans'
|
||||
const COINTRACKER_DESCRIPTION = 'New and existing users save up to 20%'
|
||||
|
||||
function TaxServiceOption({ description, logo, url }: TaxServiceOptionProps) {
|
||||
return (
|
||||
<TaxOption tabIndex={0}>
|
||||
<StyledImageContainer as="img" src={logo} draggable={false} />
|
||||
<TaxOptionDescription className={bodySmall}>{description}</TaxOptionDescription>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={
|
||||
url.includes('tokentax')
|
||||
? InterfaceElementName.TAX_SERVICE_TOKENTAX_BUTTON
|
||||
: InterfaceElementName.TAX_SERVICE_COINTRACKER_BUTTON
|
||||
}
|
||||
>
|
||||
<a href={url} target="_blank" rel="noreferrer" style={{ textDecoration: 'none' }}>
|
||||
<Button
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault()
|
||||
}}
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
data-testid="tax-service-option-button"
|
||||
>
|
||||
Get started
|
||||
</Button>
|
||||
</a>
|
||||
</TraceEvent>
|
||||
</TaxOption>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(function TaxServiceModal({ isOpen, onDismiss }: TaxServiceModalProps) {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90} minHeight={false}>
|
||||
<InnerContainer>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', userSelect: 'none' }}>
|
||||
<div className={subhead}>
|
||||
<Trans>Save on your crypto taxes</Trans>
|
||||
</div>
|
||||
<StyledXButton
|
||||
size={20}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
onDismiss()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<TaxOptionContainer>
|
||||
<TaxServiceOption description={COINTRACKER_DESCRIPTION} logo={CointrackerFullLogo} url={COINTRACKER_URL} />
|
||||
<TaxServiceOption description={TOKEN_TAX_DESCRIPTION} logo={TokenTaxFullLogo} url={TOKEN_TAX_URL} />
|
||||
</TaxOptionContainer>
|
||||
</InnerContainer>
|
||||
</Modal>
|
||||
)
|
||||
})
|
||||
@@ -3,14 +3,9 @@ import UniswapWalletBanner from 'components/Banner/UniswapWalletBanner'
|
||||
import AddressClaimModal from 'components/claim/AddressClaimModal'
|
||||
import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked'
|
||||
import FiatOnrampModal from 'components/FiatOnrampModal'
|
||||
import TaxServiceBanner from 'components/TaxServiceModal/TaxServiceBanner'
|
||||
import UniwalletModal from 'components/WalletDropdown/UniwalletModal'
|
||||
import { useTaxServiceBannerEnabled } from 'featureFlags/flags/taxServiceBanner'
|
||||
import useAccountRiskCheck from 'hooks/useAccountRiskCheck'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { useIsPoolsPage } from 'hooks/useIsPoolsPage'
|
||||
import { lazy } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { useModalIsOpen, useToggleModal } from 'state/application/hooks'
|
||||
import { ApplicationModal } from 'state/application/reducer'
|
||||
|
||||
@@ -25,13 +20,6 @@ export default function TopLevelModals() {
|
||||
const { account } = useWeb3React()
|
||||
useAccountRiskCheck(account)
|
||||
const accountBlocked = Boolean(blockedAccountModalOpen && account)
|
||||
const taxServiceEnabled = useTaxServiceBannerEnabled()
|
||||
|
||||
const { pathname } = useLocation()
|
||||
const isNftPage = useIsNftPage()
|
||||
const isPoolPage = useIsPoolsPage()
|
||||
|
||||
const isTaxModalServicePage = isNftPage || isPoolPage || pathname.startsWith('/swap')
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -43,7 +31,6 @@ export default function TopLevelModals() {
|
||||
<TransactionCompleteModal />
|
||||
<AirdropModal />
|
||||
<FiatOnrampModal />
|
||||
{taxServiceEnabled && isTaxModalServicePage && <TaxServiceBanner />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,12 +12,14 @@ import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { useGetConnection } from 'connection'
|
||||
import { usePortfolioBalancesQuery } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { useProfilePageState, useSellAsset, useWalletCollections } from 'nft/hooks'
|
||||
import { useIsNftClaimAvailable } from 'nft/hooks/useIsNftClaimAvailable'
|
||||
import { ProfilePageStateType } from 'nft/types'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { ArrowDownRight, ArrowUpRight, Copy, CreditCard, IconProps, Info, Power, Settings } from 'react-feather'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
@@ -166,6 +168,8 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
|
||||
const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters)
|
||||
const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable)
|
||||
|
||||
const shouldDisableNFTRoutes = useAtomValue(shouldDisableNFTRoutesAtom)
|
||||
|
||||
const unclaimedAmount: CurrencyAmount<Token> | undefined = useUserUnclaimedAmount(account)
|
||||
const isUnclaimed = useUserHasAvailableClaim(account)
|
||||
const getConnection = useGetConnection()
|
||||
@@ -302,14 +306,16 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
|
||||
</>
|
||||
)}
|
||||
</HeaderButton>
|
||||
<HeaderButton
|
||||
data-testid="nft-view-self-nfts"
|
||||
onClick={navigateToProfile}
|
||||
size={ButtonSize.medium}
|
||||
emphasis={ButtonEmphasis.medium}
|
||||
>
|
||||
<Trans>View and sell NFTs</Trans>
|
||||
</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>
|
||||
|
||||
@@ -33,7 +33,7 @@ function BaseButton({ onClick, branded, children }: PropsWithChildren<{ onClick?
|
||||
)
|
||||
}
|
||||
|
||||
export 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-defi-nfts/id6443944476'
|
||||
|
||||
// Launches App Store if on an iOS device, else navigates to Uniswap Wallet microsite
|
||||
export function DownloadButton({ onClick, text = 'Download' }: { onClick?: () => void; text?: string }) {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import Column from 'components/Column'
|
||||
import AlertTriangleFilled from 'components/Icons/AlertTriangleFilled'
|
||||
import { LoaderV2 } from 'components/Icons/LoadingSpinner'
|
||||
import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import useENSName from 'hooks/useENSName'
|
||||
import styled from 'styled-components/macro'
|
||||
import { EllipsisStyle, ThemedText } from 'theme'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
import { PortfolioLogo } from '../PortfolioLogo'
|
||||
import PortfolioRow from '../PortfolioRow'
|
||||
import { useTimeSince } from './parseRemote'
|
||||
import { Activity } from './types'
|
||||
|
||||
const ActivityRowDescriptor = styled(ThemedText.BodySmall)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
${EllipsisStyle}
|
||||
`
|
||||
|
||||
const StyledTimestamp = styled(ThemedText.Caption)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-variant: small;
|
||||
font-feature-settings: 'tnum' on, 'lnum' on, 'ss02' on;
|
||||
`
|
||||
|
||||
export function ActivityRow({
|
||||
activity: { chainId, status, title, descriptor, logos, otherAccount, currencies, timestamp, hash },
|
||||
}: {
|
||||
activity: Activity
|
||||
}) {
|
||||
const { ENSName } = useENSName(otherAccount)
|
||||
const timeSince = useTimeSince(timestamp)
|
||||
|
||||
const explorerUrl = getExplorerLink(chainId, hash, ExplorerDataType.TRANSACTION)
|
||||
|
||||
return (
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={InterfaceElementName.MINI_PORTFOLIO_ACTIVITY_ROW}
|
||||
properties={{ hash, chain_id: chainId, explorer_url: explorerUrl }}
|
||||
>
|
||||
<PortfolioRow
|
||||
left={
|
||||
<Column>
|
||||
<PortfolioLogo chainId={chainId} currencies={currencies} images={logos} accountAddress={otherAccount} />
|
||||
</Column>
|
||||
}
|
||||
title={<ThemedText.SubHeader fontWeight={500}>{title}</ThemedText.SubHeader>}
|
||||
descriptor={
|
||||
<ActivityRowDescriptor color="textSecondary">
|
||||
{descriptor}
|
||||
{ENSName ?? otherAccount}
|
||||
</ActivityRowDescriptor>
|
||||
}
|
||||
right={
|
||||
status === TransactionStatus.Pending ? (
|
||||
<LoaderV2 />
|
||||
) : status === TransactionStatus.Confirmed ? (
|
||||
<StyledTimestamp>{timeSince}</StyledTimestamp>
|
||||
) : (
|
||||
<AlertTriangleFilled />
|
||||
)
|
||||
}
|
||||
onClick={() => window.open(explorerUrl, '_blank')}
|
||||
/>
|
||||
</TraceEvent>
|
||||
)
|
||||
}
|
||||
@@ -1,24 +1,20 @@
|
||||
import { t } from '@lingui/macro'
|
||||
import Column from 'components/Column'
|
||||
import AlertTriangleFilled from 'components/Icons/AlertTriangleFilled'
|
||||
import { LoaderV2 } from 'components/Icons/LoadingSpinner'
|
||||
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'
|
||||
import useENSName from 'hooks/useENSName'
|
||||
import { atom, useAtom } from 'jotai'
|
||||
import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { EllipsisStyle, ThemedText } from 'theme'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import { PortfolioLogo } from '../PortfolioLogo'
|
||||
import PortfolioRow, { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow'
|
||||
import { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow'
|
||||
import { ActivityRow } from './ActivityRow'
|
||||
import { useLocalActivities } from './parseLocal'
|
||||
import { parseRemoteActivities, useTimeSince } from './parseRemote'
|
||||
import { parseRemoteActivities } from './parseRemote'
|
||||
import { Activity, ActivityMap } from './types'
|
||||
|
||||
interface ActivityGroup {
|
||||
@@ -101,7 +97,7 @@ function combineActivities(localMap: ActivityMap = {}, remoteMap: ActivityMap =
|
||||
|
||||
const lastFetchedAtom = atom<number | undefined>(0)
|
||||
|
||||
export default function ActivityTab({ account }: { account: string }) {
|
||||
export function ActivityTab({ account }: { account: string }) {
|
||||
const [drawerOpen, toggleWalletDrawer] = useWalletDrawer()
|
||||
const [lastFetched, setLastFetched] = useAtom(lastFetchedAtom)
|
||||
|
||||
@@ -158,49 +154,3 @@ export default function ActivityTab({ account }: { account: string }) {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const StyledDescriptor = styled(ThemedText.BodySmall)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
${EllipsisStyle}
|
||||
`
|
||||
|
||||
const StyledTimestamp = styled(ThemedText.Caption)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
font-variant: small;
|
||||
font-feature-settings: 'tnum' on, 'lnum' on, 'ss02' on;
|
||||
`
|
||||
|
||||
function ActivityRow({ activity }: { activity: Activity }) {
|
||||
const { chainId, status, title, descriptor, logos, otherAccount, currencies } = activity
|
||||
const { ENSName } = useENSName(otherAccount)
|
||||
|
||||
const explorerUrl = getExplorerLink(activity.chainId, activity.hash, ExplorerDataType.TRANSACTION)
|
||||
const timeSince = useTimeSince(activity.timestamp)
|
||||
|
||||
return (
|
||||
<PortfolioRow
|
||||
left={
|
||||
<Column>
|
||||
<PortfolioLogo chainId={chainId} currencies={currencies} images={logos} accountAddress={otherAccount} />
|
||||
</Column>
|
||||
}
|
||||
title={<ThemedText.SubHeader fontWeight={500}>{title}</ThemedText.SubHeader>}
|
||||
descriptor={
|
||||
<StyledDescriptor color="textSecondary">
|
||||
{descriptor}
|
||||
{ENSName ?? otherAccount}
|
||||
</StyledDescriptor>
|
||||
}
|
||||
right={
|
||||
status === TransactionStatus.Pending ? (
|
||||
<LoaderV2 />
|
||||
) : status === TransactionStatus.Confirmed ? (
|
||||
<StyledTimestamp>{timeSince}</StyledTimestamp>
|
||||
) : (
|
||||
<AlertTriangleFilled />
|
||||
)
|
||||
}
|
||||
onClick={() => window.open(explorerUrl, '_blank')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// jest unit tests for the parseLocalActivity function
|
||||
|
||||
import { SupportedChainId, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import { DAI, USDC_MAINNET } from 'constants/tokens'
|
||||
import { TokenAddressMap } from 'state/lists/hooks'
|
||||
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
|
||||
import {
|
||||
ExactInputSwapTransactionInfo,
|
||||
ExactOutputSwapTransactionInfo,
|
||||
TransactionDetails,
|
||||
TransactionType,
|
||||
} from 'state/transactions/types'
|
||||
|
||||
import { parseLocalActivity } from './parseLocal'
|
||||
|
||||
const oneUSDCRaw = '1000000'
|
||||
const oneDAIRaw = '1000000000000000000'
|
||||
|
||||
function buildSwapInfo(
|
||||
type: TradeType,
|
||||
inputCurrency: Token,
|
||||
inputCurrencyAmountRaw: string,
|
||||
outputCurrency: Token,
|
||||
outputCurrencyAmountRaw: string
|
||||
): ExactInputSwapTransactionInfo | ExactOutputSwapTransactionInfo {
|
||||
if (type === TradeType.EXACT_INPUT) {
|
||||
return {
|
||||
type: TransactionType.SWAP,
|
||||
tradeType: TradeType.EXACT_INPUT,
|
||||
inputCurrencyId: inputCurrency.address,
|
||||
inputCurrencyAmountRaw,
|
||||
outputCurrencyId: outputCurrency.address,
|
||||
expectedOutputCurrencyAmountRaw: outputCurrencyAmountRaw,
|
||||
minimumOutputCurrencyAmountRaw: outputCurrencyAmountRaw,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
type: TransactionType.SWAP,
|
||||
tradeType: TradeType.EXACT_OUTPUT,
|
||||
inputCurrencyId: inputCurrency.address,
|
||||
expectedInputCurrencyAmountRaw: inputCurrencyAmountRaw,
|
||||
maximumInputCurrencyAmountRaw: inputCurrencyAmountRaw,
|
||||
outputCurrencyId: outputCurrency.address,
|
||||
outputCurrencyAmountRaw,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildTokenAddressMap(...tokens: WrappedTokenInfo[]): TokenAddressMap {
|
||||
return {
|
||||
[SupportedChainId.MAINNET]: Object.fromEntries(tokens.map((token) => [token.address, { token }])),
|
||||
}
|
||||
}
|
||||
|
||||
describe('parseLocalActivity', () => {
|
||||
it('returns swap activity fields with known tokens, exact input', () => {
|
||||
const details = {
|
||||
info: buildSwapInfo(TradeType.EXACT_INPUT, USDC_MAINNET, oneUSDCRaw, DAI, oneDAIRaw),
|
||||
receipt: {
|
||||
transactionHash: '0x123',
|
||||
status: 1,
|
||||
},
|
||||
} as TransactionDetails
|
||||
const chainId = SupportedChainId.MAINNET
|
||||
const tokens = buildTokenAddressMap(USDC_MAINNET as WrappedTokenInfo, DAI as WrappedTokenInfo)
|
||||
expect(parseLocalActivity(details, chainId, tokens)).toEqual({
|
||||
chainId: 1,
|
||||
currencies: [USDC_MAINNET, DAI],
|
||||
descriptor: '1.00 USDC for 1.00 DAI',
|
||||
hash: undefined,
|
||||
receipt: {
|
||||
id: '0x123',
|
||||
info: {
|
||||
type: 1,
|
||||
tradeType: TradeType.EXACT_INPUT,
|
||||
inputCurrencyId: USDC_MAINNET.address,
|
||||
inputCurrencyAmountRaw: oneUSDCRaw,
|
||||
outputCurrencyId: DAI.address,
|
||||
expectedOutputCurrencyAmountRaw: oneDAIRaw,
|
||||
minimumOutputCurrencyAmountRaw: oneDAIRaw,
|
||||
},
|
||||
receipt: { status: 1, transactionHash: '0x123' },
|
||||
status: 'CONFIRMED',
|
||||
transactionHash: '0x123',
|
||||
},
|
||||
status: 'CONFIRMED',
|
||||
timestamp: NaN,
|
||||
title: 'Swapped',
|
||||
})
|
||||
})
|
||||
|
||||
it('returns swap activity fields with known tokens, exact output', () => {
|
||||
const details = {
|
||||
info: buildSwapInfo(TradeType.EXACT_OUTPUT, USDC_MAINNET, oneUSDCRaw, DAI, oneDAIRaw),
|
||||
receipt: {
|
||||
transactionHash: '0x123',
|
||||
status: 1,
|
||||
},
|
||||
} as TransactionDetails
|
||||
const chainId = SupportedChainId.MAINNET
|
||||
const tokens = buildTokenAddressMap(USDC_MAINNET as WrappedTokenInfo, DAI as WrappedTokenInfo)
|
||||
expect(parseLocalActivity(details, chainId, tokens)).toEqual({
|
||||
chainId: 1,
|
||||
currencies: [USDC_MAINNET, DAI],
|
||||
descriptor: '1.00 USDC for 1.00 DAI',
|
||||
hash: undefined,
|
||||
receipt: {
|
||||
id: '0x123',
|
||||
info: {
|
||||
type: 1,
|
||||
tradeType: TradeType.EXACT_OUTPUT,
|
||||
inputCurrencyId: USDC_MAINNET.address,
|
||||
expectedInputCurrencyAmountRaw: oneUSDCRaw,
|
||||
maximumInputCurrencyAmountRaw: oneUSDCRaw,
|
||||
outputCurrencyId: DAI.address,
|
||||
outputCurrencyAmountRaw: oneDAIRaw,
|
||||
},
|
||||
receipt: { status: 1, transactionHash: '0x123' },
|
||||
status: 'CONFIRMED',
|
||||
transactionHash: '0x123',
|
||||
},
|
||||
status: 'CONFIRMED',
|
||||
timestamp: NaN,
|
||||
title: 'Swapped',
|
||||
})
|
||||
})
|
||||
|
||||
it('returns swap activity fields with unknown tokens', () => {
|
||||
const details = {
|
||||
info: buildSwapInfo(TradeType.EXACT_INPUT, USDC_MAINNET, oneUSDCRaw, DAI, oneDAIRaw),
|
||||
receipt: {
|
||||
transactionHash: '0x123',
|
||||
status: 1,
|
||||
},
|
||||
} as TransactionDetails
|
||||
const chainId = SupportedChainId.MAINNET
|
||||
const tokens = {} as TokenAddressMap
|
||||
expect(parseLocalActivity(details, chainId, tokens)).toEqual({
|
||||
chainId: 1,
|
||||
currencies: [undefined, undefined],
|
||||
descriptor: 'Unknown for Unknown',
|
||||
hash: undefined,
|
||||
receipt: {
|
||||
id: '0x123',
|
||||
info: {
|
||||
type: 1,
|
||||
tradeType: TradeType.EXACT_INPUT,
|
||||
inputCurrencyId: USDC_MAINNET.address,
|
||||
inputCurrencyAmountRaw: oneUSDCRaw,
|
||||
outputCurrencyId: DAI.address,
|
||||
expectedOutputCurrencyAmountRaw: oneDAIRaw,
|
||||
minimumOutputCurrencyAmountRaw: oneDAIRaw,
|
||||
},
|
||||
receipt: { status: 1, transactionHash: '0x123' },
|
||||
status: 'CONFIRMED',
|
||||
transactionHash: '0x123',
|
||||
},
|
||||
status: 'CONFIRMED',
|
||||
timestamp: NaN,
|
||||
title: 'Swapped',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -26,20 +26,22 @@ import {
|
||||
import { getActivityTitle } from '../constants'
|
||||
import { Activity, ActivityMap } from './types'
|
||||
|
||||
function getCurrency(currencyId: string, chainId: SupportedChainId, tokens: TokenAddressMap) {
|
||||
return currencyId === 'ETH' ? nativeOnChain(chainId) : tokens[chainId][currencyId].token
|
||||
function getCurrency(currencyId: string, chainId: SupportedChainId, tokens: TokenAddressMap): Currency | undefined {
|
||||
return currencyId === 'ETH' ? nativeOnChain(chainId) : tokens[chainId]?.[currencyId]?.token
|
||||
}
|
||||
|
||||
function buildCurrencyDescriptor(
|
||||
currencyA: Currency,
|
||||
currencyA: Currency | undefined,
|
||||
amtA: string,
|
||||
currencyB: Currency,
|
||||
currencyB: Currency | undefined,
|
||||
amtB: string,
|
||||
delimiter = t`for`
|
||||
) {
|
||||
const formattedA = formatCurrencyAmount(CurrencyAmount.fromRawAmount(currencyA, amtA))
|
||||
const formattedB = formatCurrencyAmount(CurrencyAmount.fromRawAmount(currencyB, amtB))
|
||||
return `${formattedA} ${currencyA.symbol} ${delimiter} ${formattedB} ${currencyB.symbol}`
|
||||
const formattedA = currencyA ? formatCurrencyAmount(CurrencyAmount.fromRawAmount(currencyA, amtA)) : t`Unknown`
|
||||
const symbolA = currencyA?.symbol ?? ''
|
||||
const formattedB = currencyB ? formatCurrencyAmount(CurrencyAmount.fromRawAmount(currencyB, amtB)) : t`Unknown`
|
||||
const symbolB = currencyB?.symbol ?? ''
|
||||
return [formattedA, symbolA, delimiter, formattedB, symbolB].filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
function parseSwap(
|
||||
@@ -79,7 +81,7 @@ function parseApproval(
|
||||
): Partial<Activity> {
|
||||
// TODO: Add 'amount' approved to ApproveTransactionInfo so we can distinguish between revoke and approve
|
||||
const currency = getCurrency(approval.tokenAddress, chainId, tokens)
|
||||
const descriptor = t`${currency.symbol ?? currency.name}`
|
||||
const descriptor = currency?.symbol ?? currency?.name ?? t`Unknown`
|
||||
return {
|
||||
descriptor,
|
||||
currencies: [currency],
|
||||
@@ -120,13 +122,15 @@ function parseMigrateCreateV3(
|
||||
tokens: TokenAddressMap
|
||||
): Partial<Activity> {
|
||||
const baseCurrency = getCurrency(lp.baseCurrencyId, chainId, tokens)
|
||||
const baseSymbol = baseCurrency?.symbol ?? t`Unknown`
|
||||
const quoteCurrency = getCurrency(lp.baseCurrencyId, chainId, tokens)
|
||||
const descriptor = t`${baseCurrency.symbol} and ${quoteCurrency.symbol}`
|
||||
const quoteSymbol = quoteCurrency?.symbol ?? t`Unknown`
|
||||
const descriptor = t`${baseSymbol} and ${quoteSymbol}`
|
||||
|
||||
return { descriptor, currencies: [baseCurrency, quoteCurrency] }
|
||||
}
|
||||
|
||||
function parseLocalActivity(
|
||||
export function parseLocalActivity(
|
||||
details: TransactionDetails,
|
||||
chainId: SupportedChainId,
|
||||
tokens: TokenAddressMap
|
||||
@@ -12,7 +12,7 @@ export type Activity = {
|
||||
title: string
|
||||
descriptor?: string
|
||||
logos?: Array<string | undefined>
|
||||
currencies?: Array<Currency>
|
||||
currencies?: Array<Currency | undefined>
|
||||
otherAccount?: string
|
||||
receipt?: Receipt
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { sendAnalyticsEvent, useTrace } from '@uniswap/analytics'
|
||||
import { InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
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'
|
||||
import { VerifiedIcon } from 'nft/components/icons'
|
||||
import { WalletAsset } from 'nft/types'
|
||||
import { floorFormatter } from 'nft/utils'
|
||||
@@ -45,10 +48,11 @@ export function NFT({
|
||||
}) {
|
||||
const toggleWalletDrawer = useToggleWalletDrawer()
|
||||
const navigate = useNavigate()
|
||||
const trace = useTrace()
|
||||
|
||||
const navigateToNFTDetails = () => {
|
||||
navigate(`/nfts/asset/${asset.asset_contract.address}/${asset.tokenId}`)
|
||||
toggleWalletDrawer()
|
||||
navigate(detailsHref(asset))
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -59,12 +63,19 @@ export function NFT({
|
||||
display={{ disabledInfo: true }}
|
||||
isSelected={false}
|
||||
isDisabled={false}
|
||||
selectAsset={navigateToNFTDetails}
|
||||
unselectAsset={() => {
|
||||
/* */
|
||||
}}
|
||||
onCardClick={navigateToNFTDetails}
|
||||
sendAnalyticsEvent={() =>
|
||||
sendAnalyticsEvent(SharedEventName.ELEMENT_CLICKED, {
|
||||
element: InterfaceElementName.MINI_PORTFOLIO_NFT_ITEM,
|
||||
collection_name: asset.collection?.name,
|
||||
collection_address: asset.collection?.address,
|
||||
token_id: asset.tokenId,
|
||||
...trace,
|
||||
})
|
||||
}
|
||||
mediaShouldBePlaying={mediaShouldBePlaying}
|
||||
setCurrentTokenPlayingMedia={setCurrentTokenPlayingMedia}
|
||||
testId="mini-portfolio-nft"
|
||||
/>
|
||||
<NFTDetails asset={asset} />
|
||||
</NFTContainer>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { t } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import { formatNumber, NumberType } from '@uniswap/conedison/format'
|
||||
import { Position } from '@uniswap/v3-sdk'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
@@ -98,6 +100,16 @@ function PositionListItem({ positionInfo }: { positionInfo: PositionInfo }) {
|
||||
toggleWalletDrawer()
|
||||
navigate('/pool/' + details.tokenId)
|
||||
}, [walletChainId, chainId, connector, toggleWalletDrawer, navigate, details.tokenId])
|
||||
const analyticsEventProperties = useMemo(
|
||||
() => ({
|
||||
chain_id: chainId,
|
||||
pool_token_0_symbol: pool.token0.symbol,
|
||||
pool_token_1_symbol: pool.token1.symbol,
|
||||
pool_token_0_address: pool.token0.address,
|
||||
pool_token_1_address: pool.token1.address,
|
||||
}),
|
||||
[chainId, pool.token0.address, pool.token0.symbol, pool.token1.address, pool.token1.symbol]
|
||||
)
|
||||
|
||||
const containsURL = useMemo(
|
||||
() =>
|
||||
@@ -112,43 +124,50 @@ function PositionListItem({ positionInfo }: { positionInfo: PositionInfo }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<PortfolioRow
|
||||
onClick={onClick}
|
||||
left={<PortfolioLogo chainId={chainId} currencies={[pool.token0, pool.token1]} />}
|
||||
title={
|
||||
<Row>
|
||||
<ThemedText.SubHeader fontWeight={500}>
|
||||
{pool.token0.symbol} / {pool.token1?.symbol}
|
||||
</ThemedText.SubHeader>
|
||||
</Row>
|
||||
}
|
||||
descriptor={<ThemedText.Caption>{`${pool.fee / 10000}%`}</ThemedText.Caption>}
|
||||
right={
|
||||
<>
|
||||
<MouseoverTooltip
|
||||
placement="left"
|
||||
text={
|
||||
<div style={{ padding: '4px 0px' }}>
|
||||
<ThemedText.Caption>{`${formatNumber(
|
||||
liquidityValue,
|
||||
NumberType.PortfolioBalance
|
||||
)} (liquidity) + ${formatNumber(feeValue, NumberType.PortfolioBalance)} (fees)`}</ThemedText.Caption>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={InterfaceElementName.MINI_PORTFOLIO_POOLS_ROW}
|
||||
properties={analyticsEventProperties}
|
||||
>
|
||||
<PortfolioRow
|
||||
onClick={onClick}
|
||||
left={<PortfolioLogo chainId={chainId} currencies={[pool.token0, pool.token1]} />}
|
||||
title={
|
||||
<Row>
|
||||
<ThemedText.SubHeader fontWeight={500}>
|
||||
{formatNumber((liquidityValue ?? 0) + (feeValue ?? 0), NumberType.PortfolioBalance)}
|
||||
{pool.token0.symbol} / {pool.token1?.symbol}
|
||||
</ThemedText.SubHeader>
|
||||
</MouseoverTooltip>
|
||||
|
||||
<Row justify="flex-end">
|
||||
<ThemedText.Caption color="textSecondary">
|
||||
{closed ? t`Closed` : inRange ? t`In range` : t`Out of range`}
|
||||
</ThemedText.Caption>
|
||||
<ActiveDot closed={closed} outOfRange={!inRange} />
|
||||
</Row>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
}
|
||||
descriptor={<ThemedText.Caption>{`${pool.fee / 10000}%`}</ThemedText.Caption>}
|
||||
right={
|
||||
<>
|
||||
<MouseoverTooltip
|
||||
placement="left"
|
||||
text={
|
||||
<div style={{ padding: '4px 0px' }}>
|
||||
<ThemedText.Caption>{`${formatNumber(
|
||||
liquidityValue,
|
||||
NumberType.PortfolioBalance
|
||||
)} (liquidity) + ${formatNumber(feeValue, NumberType.PortfolioBalance)} (fees)`}</ThemedText.Caption>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<ThemedText.SubHeader fontWeight={500}>
|
||||
{formatNumber((liquidityValue ?? 0) + (feeValue ?? 0), NumberType.PortfolioBalance)}
|
||||
</ThemedText.SubHeader>
|
||||
</MouseoverTooltip>
|
||||
|
||||
<Row justify="flex-end">
|
||||
<ThemedText.Caption color="textSecondary">
|
||||
{closed ? t`Closed` : inRange ? t`In range` : t`Out of range`}
|
||||
</ThemedText.Caption>
|
||||
<ActiveDot closed={closed} outOfRange={!inRange} />
|
||||
</Row>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</TraceEvent>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
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 { PortfolioLogo } from './PortfolioLogo'
|
||||
|
||||
describe('PortfolioLogo', () => {
|
||||
it('renders without L2 icon', () => {
|
||||
const { container } = render(<PortfolioLogo chainId={SupportedChainId.MAINNET} currencies={[DAI, USDC_MAINNET]} />)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders with L2 icon', () => {
|
||||
const { container } = render(
|
||||
<PortfolioLogo chainId={SupportedChainId.ARBITRUM_ONE} currencies={[DAI_ARBITRUM, USDC_ARBITRUM]} />
|
||||
)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
@@ -9,7 +9,7 @@ import useTokenLogoSource from 'hooks/useAssetLogoSource'
|
||||
import useENSAvatar from 'hooks/useENSAvatar'
|
||||
import React from 'react'
|
||||
import { Loader } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import styled, { useTheme } from 'styled-components/macro'
|
||||
const UnknownContract = styled(UnknownStatus)`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
@@ -39,7 +39,7 @@ const DoubleLogoContainer = styled.div`
|
||||
type MultiLogoProps = {
|
||||
chainId: SupportedChainId
|
||||
accountAddress?: string
|
||||
currencies?: Currency[]
|
||||
currencies?: Array<Currency | undefined>
|
||||
images?: (string | undefined)[]
|
||||
size?: string
|
||||
style?: React.CSSProperties
|
||||
@@ -57,34 +57,28 @@ const ENSAvatarImg = styled.img`
|
||||
width: 40px;
|
||||
`
|
||||
|
||||
const StyledChainLogo = styled.img<{ isSquare: boolean }>`
|
||||
height: ${({ isSquare }) => (isSquare ? '16px' : '14px')};
|
||||
width: ${({ isSquare }) => (isSquare ? '16px' : '14px')};
|
||||
margin-top: ${({ isSquare }) => (isSquare ? '0px' : '1px')};
|
||||
margin-left: ${({ isSquare }) => (isSquare ? '0px' : '1px')};
|
||||
position: absolute;
|
||||
top: 68%;
|
||||
left: 68%;
|
||||
const StyledChainLogo = styled.img`
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
`
|
||||
|
||||
const ChainLogoSquareBackground = styled.div`
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-radius: 4px;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
const SquareChainLogo = styled.img`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const L2LogoContainer = styled.div<{ $backgroundColor?: string }>`
|
||||
background-color: ${({ $backgroundColor }) => $backgroundColor};
|
||||
border-radius: 2px;
|
||||
height: 16px;
|
||||
left: 60%;
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
left: 60%;
|
||||
`
|
||||
|
||||
const SquareBackgroundForNonSquareLogo = styled.div`
|
||||
height: 16px;
|
||||
outline: 2px solid ${({ theme }) => theme.backgroundSurface};
|
||||
width: 16px;
|
||||
border-radius: 2px;
|
||||
background-color: ${({ theme }) => theme.textPrimary};
|
||||
position: absolute;
|
||||
top: 68%;
|
||||
left: 68%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
/**
|
||||
@@ -101,6 +95,7 @@ export function PortfolioLogo({
|
||||
const { squareLogoUrl, logoUrl } = getChainInfo(chainId)
|
||||
const chainLogo = squareLogoUrl ?? logoUrl
|
||||
const { avatar, loading } = useENSAvatar(accountAddress, false)
|
||||
const theme = useTheme()
|
||||
|
||||
const [src, nextSrc] = useTokenLogoSource(currencies?.[0]?.wrapped.address, chainId, currencies?.[0]?.isNative)
|
||||
const [src2, nextSrc2] = useTokenLogoSource(currencies?.[1]?.wrapped.address, chainId, currencies?.[1]?.isNative)
|
||||
@@ -147,13 +142,15 @@ export function PortfolioLogo({
|
||||
}
|
||||
|
||||
const L2Logo =
|
||||
chainId === SupportedChainId.MAINNET ? null : (
|
||||
<div>
|
||||
{chainLogo && <ChainLogoSquareBackground />}
|
||||
{!squareLogoUrl && logoUrl && <SquareBackgroundForNonSquareLogo />}
|
||||
{chainLogo && <StyledChainLogo isSquare={!!squareLogoUrl} src={chainLogo} alt="chainLogo" />}
|
||||
</div>
|
||||
)
|
||||
chainId !== SupportedChainId.MAINNET && chainLogo ? (
|
||||
<L2LogoContainer $backgroundColor={squareLogoUrl ? theme.backgroundSurface : theme.textPrimary}>
|
||||
{squareLogoUrl ? (
|
||||
<SquareChainLogo src={chainLogo} alt="chainLogo" />
|
||||
) : (
|
||||
<StyledChainLogo src={chainLogo} alt="chainLogo" />
|
||||
)}
|
||||
</L2LogoContainer>
|
||||
) : null
|
||||
|
||||
return (
|
||||
<StyledLogoParentContainer>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import Column, { AutoColumn } from 'components/Column'
|
||||
import Row from 'components/Row'
|
||||
import { LoadingBubble } from 'components/Tokens/loading'
|
||||
import { useMemo } from 'react'
|
||||
import styled, { css, keyframes } from 'styled-components/macro'
|
||||
|
||||
const RowWrapper = styled(Row)<{ onClick?: any }>`
|
||||
export const PortfolioRowWrapper = styled(Row)<{ onClick?: any }>`
|
||||
gap: 12px;
|
||||
height: 68px;
|
||||
padding: 0 16px;
|
||||
@@ -14,7 +13,6 @@ const RowWrapper = styled(Row)<{ onClick?: any }>`
|
||||
${({ onClick }) => onClick && 'cursor: pointer'};
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.hoverDefault};
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
@@ -28,39 +26,30 @@ export default function PortfolioRow({
|
||||
title,
|
||||
descriptor,
|
||||
right,
|
||||
setIsHover,
|
||||
onClick,
|
||||
}: {
|
||||
left: React.ReactNode
|
||||
title: React.ReactNode
|
||||
descriptor?: React.ReactNode
|
||||
right: React.ReactNode
|
||||
right?: React.ReactNode
|
||||
setIsHover?: (b: boolean) => void
|
||||
onClick?: () => void
|
||||
}) {
|
||||
const onHover = useMemo(
|
||||
() =>
|
||||
setIsHover && {
|
||||
onMouseEnter: () => setIsHover?.(true),
|
||||
onMouseLeave: () => setIsHover?.(false),
|
||||
},
|
||||
[setIsHover]
|
||||
)
|
||||
return (
|
||||
<RowWrapper {...onHover} onClick={onClick}>
|
||||
<PortfolioRowWrapper onClick={onClick}>
|
||||
{left}
|
||||
<AutoColumn grow>
|
||||
{title}
|
||||
{descriptor}
|
||||
</AutoColumn>
|
||||
<EndColumn>{right}</EndColumn>
|
||||
</RowWrapper>
|
||||
{right && <EndColumn>{right}</EndColumn>}
|
||||
</PortfolioRowWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
function PortfolioSkeletonRow({ shrinkRight }: { shrinkRight?: boolean }) {
|
||||
return (
|
||||
<RowWrapper>
|
||||
<PortfolioRowWrapper>
|
||||
<LoadingBubble height="40px" width="40px" round />
|
||||
<AutoColumn grow gap="4px">
|
||||
<LoadingBubble height="16px" width="60px" delay="300ms" />
|
||||
@@ -76,7 +65,7 @@ function PortfolioSkeletonRow({ shrinkRight }: { shrinkRight?: boolean }) {
|
||||
</>
|
||||
)}
|
||||
</EndColumn>
|
||||
</RowWrapper>
|
||||
</PortfolioRowWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
|
||||
import { formatNumber, NumberType } from '@uniswap/conedison/format'
|
||||
import Row from 'components/Row'
|
||||
import { formatDelta } from 'components/Tokens/TokenDetails/PriceChart'
|
||||
@@ -102,28 +104,35 @@ function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: Tok
|
||||
|
||||
const currency = gqlToCurrency(token)
|
||||
return (
|
||||
<PortfolioRow
|
||||
left={<PortfolioLogo chainId={currency.chainId} currencies={[currency]} size="40px" />}
|
||||
title={<ThemedText.SubHeader fontWeight={500}>{token?.name}</ThemedText.SubHeader>}
|
||||
descriptor={
|
||||
<TokenBalanceText>
|
||||
{formatNumber(quantity, NumberType.TokenNonTx)} {token?.symbol}
|
||||
</TokenBalanceText>
|
||||
}
|
||||
onClick={navigateToTokenDetails}
|
||||
right={
|
||||
denominatedValue && (
|
||||
<>
|
||||
<ThemedText.SubHeader fontWeight={500}>
|
||||
{formatNumber(denominatedValue?.value, NumberType.PortfolioBalance)}
|
||||
</ThemedText.SubHeader>
|
||||
<Row justify="flex-end">
|
||||
<PortfolioArrow change={percentChange} size={20} strokeWidth={1.75} />
|
||||
<ThemedText.BodySecondary>{formatDelta(percentChange)}</ThemedText.BodySecondary>
|
||||
</Row>
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.ELEMENT_CLICKED}
|
||||
element={InterfaceElementName.MINI_PORTFOLIO_TOKEN_ROW}
|
||||
properties={{ chain_id: currency.chainId, token_name: token?.name, address: token?.address }}
|
||||
>
|
||||
<PortfolioRow
|
||||
left={<PortfolioLogo chainId={currency.chainId} currencies={[currency]} size="40px" />}
|
||||
title={<ThemedText.SubHeader fontWeight={500}>{token?.name}</ThemedText.SubHeader>}
|
||||
descriptor={
|
||||
<TokenBalanceText>
|
||||
{formatNumber(quantity, NumberType.TokenNonTx)} {token?.symbol}
|
||||
</TokenBalanceText>
|
||||
}
|
||||
onClick={navigateToTokenDetails}
|
||||
right={
|
||||
denominatedValue && (
|
||||
<>
|
||||
<ThemedText.SubHeader fontWeight={500}>
|
||||
{formatNumber(denominatedValue?.value, NumberType.PortfolioBalance)}
|
||||
</ThemedText.SubHeader>
|
||||
<Row justify="flex-end">
|
||||
<PortfolioArrow change={percentChange} size={20} strokeWidth={1.75} />
|
||||
<ThemedText.BodySecondary>{formatDelta(percentChange)}</ThemedText.BodySecondary>
|
||||
</Row>
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</TraceEvent>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PortfolioLogo renders with L2 icon 1`] = `
|
||||
.c3 {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
gap: 2px;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.c1 .c2:nth-child(n) {
|
||||
width: 19px;
|
||||
height: 40px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.c1 .c2:nth-child(1) {
|
||||
border-radius: 20px 0 0 20px;
|
||||
object-position: 0 0;
|
||||
}
|
||||
|
||||
.c1 .c2:nth-child(2) {
|
||||
border-radius: 0 20px 20px 0;
|
||||
object-position: 100% 0;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
background-color: #0D111C;
|
||||
border-radius: 2px;
|
||||
height: 16px;
|
||||
left: 60%;
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
outline: 2px solid #FFFFFF;
|
||||
width: 16px;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<img
|
||||
class="c2 c3"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/arbitrum/assets/0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1/logo.png"
|
||||
/>
|
||||
<img
|
||||
class="c2 c3"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/arbitrum/assets/0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8/logo.png"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="c4"
|
||||
>
|
||||
<img
|
||||
alt="chainLogo"
|
||||
class="c5"
|
||||
src="arbitrum_logo.svg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`PortfolioLogo renders without L2 icon 1`] = `
|
||||
.c3 {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
gap: 2px;
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.c1 .c2:nth-child(n) {
|
||||
width: 19px;
|
||||
height: 40px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.c1 .c2:nth-child(1) {
|
||||
border-radius: 20px 0 0 20px;
|
||||
object-position: 0 0;
|
||||
}
|
||||
|
||||
.c1 .c2:nth-child(2) {
|
||||
border-radius: 0 20px 20px 0;
|
||||
object-position: 100% 0;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<div
|
||||
class="c1"
|
||||
>
|
||||
<img
|
||||
class="c2 c3"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0x6B175474E89094C44Da98b954EedeAC495271d0F/logo.png"
|
||||
/>
|
||||
<img
|
||||
class="c2 c3"
|
||||
src="https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -5,13 +5,16 @@ import Column from 'components/Column'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { useMiniPortfolioEnabled } from 'featureFlags/flags/miniPortfolio'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { useState } from 'react'
|
||||
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
import Activity from './Activity'
|
||||
import { ActivityTab } from './Activity/ActivityTab'
|
||||
import NFTs from './NFTs'
|
||||
import Pools from './Pools'
|
||||
import { PortfolioRowWrapper } from './PortfolioRow'
|
||||
import Tokens from './Tokens'
|
||||
|
||||
const Wrapper = styled(Column)`
|
||||
@@ -20,6 +23,12 @@ const Wrapper = styled(Column)`
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
gap: 12px;
|
||||
|
||||
${PortfolioRowWrapper} {
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.hoverDefault};
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const Nav = styled(AutoRow)`
|
||||
@@ -46,46 +55,67 @@ const PageWrapper = styled.div`
|
||||
|
||||
interface Page {
|
||||
title: React.ReactNode
|
||||
key: string
|
||||
component: ({ account }: { account: string }) => JSX.Element
|
||||
loggingElementName?: string
|
||||
loggingElementName: string
|
||||
}
|
||||
|
||||
const Pages: Array<Page> = [
|
||||
{
|
||||
title: <Trans>Tokens</Trans>,
|
||||
key: 'tokens',
|
||||
component: Tokens,
|
||||
loggingElementName: InterfaceElementName.MINI_PORTFOLIO_TOKENS_TAB,
|
||||
},
|
||||
{ title: <Trans>NFTs</Trans>, component: NFTs, loggingElementName: InterfaceElementName.MINI_PORTFOLIO_NFT_TAB },
|
||||
{ title: <Trans>Pools</Trans>, component: Pools, loggingElementName: InterfaceElementName.MINI_PORTFOLIO_POOLS_TAB },
|
||||
{ title: <Trans>Activity</Trans>, component: Activity },
|
||||
{
|
||||
title: <Trans>NFTs</Trans>,
|
||||
key: 'nfts',
|
||||
component: NFTs,
|
||||
loggingElementName: InterfaceElementName.MINI_PORTFOLIO_NFT_TAB,
|
||||
},
|
||||
{
|
||||
title: <Trans>Pools</Trans>,
|
||||
key: 'pools',
|
||||
component: Pools,
|
||||
loggingElementName: InterfaceElementName.MINI_PORTFOLIO_POOLS_TAB,
|
||||
},
|
||||
{
|
||||
title: <Trans>Activity</Trans>,
|
||||
key: 'activity',
|
||||
component: ActivityTab,
|
||||
loggingElementName: InterfaceElementName.MINI_PORTFOLIO_ACTIVITY_TAB,
|
||||
},
|
||||
]
|
||||
|
||||
function MiniPortfolio({ account }: { account: string }) {
|
||||
const isNftPage = useIsNftPage()
|
||||
const [currentPage, setCurrentPage] = useState(isNftPage ? 1 : 0)
|
||||
const shouldDisableNFTRoutes = useAtomValue(shouldDisableNFTRoutesAtom)
|
||||
|
||||
const Page = Pages[currentPage].component
|
||||
return (
|
||||
<Wrapper>
|
||||
<Nav>
|
||||
{Pages.map(({ title }, index) => (
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.NAVBAR_CLICKED}
|
||||
element={Pages[index].loggingElementName}
|
||||
shouldLogImpression={!!Pages[index].loggingElementName}
|
||||
key={index}
|
||||
>
|
||||
<NavItem
|
||||
onClick={() => setCurrentPage(index)}
|
||||
active={currentPage === index}
|
||||
key={`Mini Portfolio page ${index}`}
|
||||
{Pages.map(({ title, loggingElementName, key }, index) => {
|
||||
if (shouldDisableNFTRoutes && loggingElementName.includes('nft')) return null
|
||||
return (
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={SharedEventName.NAVBAR_CLICKED}
|
||||
element={loggingElementName}
|
||||
key={index}
|
||||
>
|
||||
{title}
|
||||
</NavItem>
|
||||
</TraceEvent>
|
||||
))}
|
||||
<NavItem
|
||||
data-testid={`mini-portfolio-nav-${key}`}
|
||||
onClick={() => setCurrentPage(index)}
|
||||
active={currentPage === index}
|
||||
key={`Mini Portfolio page ${index}`}
|
||||
>
|
||||
{title}
|
||||
</NavItem>
|
||||
</TraceEvent>
|
||||
)
|
||||
})}
|
||||
</Nav>
|
||||
<PageWrapper>
|
||||
<Page account={account} />
|
||||
|
||||
@@ -72,7 +72,7 @@ export default function Option({ connection, pendingConnectionType, activate }:
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={InterfaceEventName.WALLET_SELECTED}
|
||||
properties={{ wallet_type: connection.name }}
|
||||
properties={{ wallet_type: connection.getName() }}
|
||||
element={InterfaceElementName.WALLET_TYPE_OPTION}
|
||||
>
|
||||
<OptionCardClickable
|
||||
@@ -83,9 +83,9 @@ export default function Option({ connection, pendingConnectionType, activate }:
|
||||
>
|
||||
<OptionCardLeft>
|
||||
<IconWrapper>
|
||||
<img src={connection.icon} alt="Icon" />
|
||||
<img src={connection.getIcon?.()} alt="Icon" />
|
||||
</IconWrapper>
|
||||
<HeaderText>{connection.name}</HeaderText>
|
||||
<HeaderText>{connection.getName()}</HeaderText>
|
||||
{connection.isNew && <NewBadge />}
|
||||
</OptionCardLeft>
|
||||
{isPending && <Loader />}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { AutoColumn } from 'components/Column'
|
||||
import { AutoRow } from 'components/Row'
|
||||
import { useWalletDrawer } from 'components/WalletDropdown'
|
||||
import IconButton from 'components/WalletDropdown/IconButton'
|
||||
import { Connection, ConnectionType, networkConnection, useConnections } from 'connection'
|
||||
import { Connection, ConnectionType, getConnections, networkConnection } from 'connection'
|
||||
import { useGetConnection } from 'connection'
|
||||
import { ErrorCode } from 'connection/utils'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
@@ -91,7 +91,7 @@ export default function WalletModal({ openSettings }: { openSettings: () => void
|
||||
const [pendingConnection, setPendingConnection] = useState<Connection | undefined>()
|
||||
const [pendingError, setPendingError] = useState<any>()
|
||||
|
||||
const connections = useConnections()
|
||||
const connections = getConnections()
|
||||
const getConnection = useGetConnection()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -116,7 +116,7 @@ export default function WalletModal({ openSettings }: { openSettings: () => void
|
||||
// When new wallet is successfully set by the user, trigger logging of Amplitude analytics event.
|
||||
useEffect(() => {
|
||||
if (account && account !== lastActiveWalletAddress) {
|
||||
const walletName = getConnection(connector).name
|
||||
const walletName = getConnection(connector).getName()
|
||||
const peerWalletAgent = provider ? getWalletMeta(provider)?.agent : undefined
|
||||
const isReconnect =
|
||||
connectedWallets.filter((wallet) => wallet.account === account && wallet.walletType === walletName).length > 0
|
||||
@@ -141,6 +141,9 @@ export default function WalletModal({ openSettings }: { openSettings: () => void
|
||||
|
||||
const tryActivation = useCallback(
|
||||
async (connection: Connection) => {
|
||||
// Skips wallet connection if the connection should override the default behavior, i.e. install metamask or launch coinbase app
|
||||
if (connection.overrideActivate?.()) return
|
||||
|
||||
// log selected wallet
|
||||
sendEvent({
|
||||
category: 'Wallet',
|
||||
@@ -165,7 +168,7 @@ export default function WalletModal({ openSettings }: { openSettings: () => void
|
||||
|
||||
sendAnalyticsEvent(InterfaceEventName.WALLET_CONNECT_TXN_COMPLETED, {
|
||||
result: WalletConnectionResult.FAILED,
|
||||
wallet_type: connection.name,
|
||||
wallet_type: connection.getName(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -190,11 +193,11 @@ export default function WalletModal({ openSettings }: { openSettings: () => void
|
||||
<OptionGrid data-testid="option-grid">
|
||||
{connections.map((connection) =>
|
||||
// Hides Uniswap Wallet if mgtm is disabled
|
||||
connection.shouldDisplay && !(connection.type === ConnectionType.UNIWALLET && !mgtmEnabled) ? (
|
||||
connection.shouldDisplay() && !(connection.type === ConnectionType.UNIWALLET && !mgtmEnabled) ? (
|
||||
<Option
|
||||
key={connection.name}
|
||||
key={connection.getName()}
|
||||
connection={connection}
|
||||
activate={connection.overrideActivate ?? (() => tryActivation(connection))}
|
||||
activate={() => tryActivation(connection)}
|
||||
pendingConnectionType={pendingConnection?.type}
|
||||
/>
|
||||
) : null
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function Web3Provider({ children }: { children: ReactNode }) {
|
||||
const connections = useOrderedConnections()
|
||||
const connectors: [Connector, Web3ReactHooks][] = connections.map(({ hooks, connector }) => [connector, hooks])
|
||||
|
||||
const key = useMemo(() => connections.map((connection) => connection.name).join('-'), [connections])
|
||||
const key = useMemo(() => connections.map((connection) => connection.getName()).join('-'), [connections])
|
||||
|
||||
return (
|
||||
<Web3ReactProvider connectors={connectors} key={key}>
|
||||
|
||||
@@ -202,9 +202,7 @@ function Web3StatusInner() {
|
||||
pending={hasPendingTransactions}
|
||||
isClaimAvailable={isClaimAvailable}
|
||||
>
|
||||
{!hasPendingTransactions && (
|
||||
<StatusIcon enableInfotips={true} size={24} connection={connection} showMiniIcons={false} />
|
||||
)}
|
||||
{!hasPendingTransactions && <StatusIcon size={24} connection={connection} showMiniIcons={false} />}
|
||||
{hasPendingTransactions ? (
|
||||
<RowBetween>
|
||||
<Text>
|
||||
|
||||
46
src/components/addLiquidity/OwnershipWarning.tsx
Normal file
46
src/components/addLiquidity/OwnershipWarning.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
const ExplainerText = styled.div`
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
`
|
||||
const TitleRow = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
color: ${({ theme }) => theme.accentWarning};
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
const Wrapper = styled.div`
|
||||
background-color: ${({ theme }) => theme.accentWarningSoft};
|
||||
border-radius: 16px;
|
||||
margin-top: 12px;
|
||||
max-width: 480px;
|
||||
padding: 12px 20px;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
interface OwnershipWarningProps {
|
||||
ownerAddress: string
|
||||
}
|
||||
|
||||
const OwnershipWarning = ({ ownerAddress }: OwnershipWarningProps) => (
|
||||
<Wrapper>
|
||||
<TitleRow>
|
||||
<AlertTriangle style={{ marginRight: '8px' }} />
|
||||
<ThemedText.SubHeader color="accentWarning">
|
||||
<Trans>Warning</Trans>
|
||||
</ThemedText.SubHeader>
|
||||
</TitleRow>
|
||||
<ExplainerText>
|
||||
<Trans>
|
||||
You are not the owner of this LP position. You will not be able to withdraw the liquidity from this position
|
||||
unless you own the following address: {ownerAddress}
|
||||
</Trans>
|
||||
</ExplainerText>
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
export default OwnershipWarning
|
||||
@@ -1,111 +1,145 @@
|
||||
// eslint-disable-next-line jest/no-export
|
||||
export {}
|
||||
import { ConnectionType, getConnections, useGetConnection } from 'connection'
|
||||
import { renderHook } from 'test-utils'
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules()
|
||||
jest.resetAllMocks()
|
||||
})
|
||||
|
||||
it('Non-injected Desktop', async () => {
|
||||
jest.mock('connection/utils', () => ({ isInjected: false, isMetaMaskWallet: false, isCoinbaseWallet: false }))
|
||||
jest.mock('utils/userAgent', () => ({ isMobile: false }))
|
||||
const connection = await import('connection')
|
||||
expect(connection.darkInjectedConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.darkInjectedConnection.name).toBe('MetaMask')
|
||||
expect(connection.darkInjectedConnection.overrideActivate).toBeDefined()
|
||||
expect(connection.coinbaseWalletConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.uniwalletConnectConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.walletConnectConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.getConnections(true).filter((c) => c.shouldDisplay).length).toEqual(4)
|
||||
})
|
||||
const UserAgentMock = jest.requireMock('utils/userAgent')
|
||||
jest.mock('utils/userAgent', () => ({
|
||||
isMobile: false,
|
||||
}))
|
||||
|
||||
it('MetaMask Injected Desktop', async () => {
|
||||
jest.mock('connection/utils', () => ({ isInjected: true, isMetaMaskWallet: true, isCoinbaseWallet: false }))
|
||||
jest.mock('utils/userAgent', () => ({ isMobile: false }))
|
||||
const connection = await import('connection')
|
||||
expect(connection.darkInjectedConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.darkInjectedConnection.name).toBe('MetaMask')
|
||||
expect(connection.darkInjectedConnection.overrideActivate).toBeUndefined()
|
||||
expect(connection.coinbaseWalletConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.uniwalletConnectConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.walletConnectConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.getConnections(true).filter((c) => c.shouldDisplay).length).toEqual(4)
|
||||
})
|
||||
describe('connection utility/metadata tests', () => {
|
||||
const createWalletEnvironment = (ethereum: Window['window']['ethereum'], isMobile = false) => {
|
||||
UserAgentMock.isMobile = isMobile
|
||||
global.window.ethereum = ethereum
|
||||
|
||||
it('Coinbase Injected Desktop', async () => {
|
||||
jest.mock('connection/utils', () => ({ isInjected: true, isMetaMaskWallet: false, isCoinbaseWallet: true }))
|
||||
jest.mock('utils/userAgent', () => ({ isMobile: false }))
|
||||
const connection = await import('connection')
|
||||
expect(connection.darkInjectedConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.darkInjectedConnection.name).toBe('MetaMask')
|
||||
expect(connection.darkInjectedConnection.overrideActivate).toBeDefined()
|
||||
expect(connection.coinbaseWalletConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.uniwalletConnectConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.walletConnectConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.getConnections(true).filter((c) => c.shouldDisplay).length).toEqual(4)
|
||||
})
|
||||
const displayed = getConnections().filter((c) => c.shouldDisplay())
|
||||
const getConnection = renderHook(() => useGetConnection()).result.current
|
||||
const injected = getConnection(ConnectionType.INJECTED)
|
||||
const coinbase = getConnection(ConnectionType.COINBASE_WALLET)
|
||||
const uniswap = getConnection(ConnectionType.UNIWALLET)
|
||||
const walletconnect = getConnection(ConnectionType.WALLET_CONNECT)
|
||||
|
||||
it('Coinbase and MetaMask Injected Desktop', async () => {
|
||||
jest.mock('connection/utils', () => ({ isInjected: true, isMetaMaskWallet: true, isCoinbaseWallet: true }))
|
||||
jest.mock('utils/userAgent', () => ({ isMobile: false }))
|
||||
const connection = await import('connection')
|
||||
expect(connection.darkInjectedConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.darkInjectedConnection.name).toBe('MetaMask')
|
||||
expect(connection.darkInjectedConnection.overrideActivate).toBeUndefined()
|
||||
expect(connection.coinbaseWalletConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.uniwalletConnectConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.walletConnectConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.getConnections(true).filter((c) => c.shouldDisplay).length).toEqual(4)
|
||||
})
|
||||
return { displayed, injected, coinbase, uniswap, walletconnect }
|
||||
}
|
||||
|
||||
it('Generic Injected Desktop', async () => {
|
||||
jest.mock('connection/utils', () => ({ isInjected: true, isMetaMaskWallet: false, isCoinbaseWallet: false }))
|
||||
jest.mock('utils/userAgent', () => ({ isMobile: false }))
|
||||
const connection = await import('connection')
|
||||
expect(connection.darkInjectedConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.darkInjectedConnection.name).toBe('Browser Wallet')
|
||||
expect(connection.darkInjectedConnection.overrideActivate).toBeUndefined()
|
||||
expect(connection.coinbaseWalletConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.uniwalletConnectConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.walletConnectConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.getConnections(true).filter((c) => c.shouldDisplay).length).toEqual(4)
|
||||
})
|
||||
it('Non-injected Desktop', async () => {
|
||||
const { displayed, injected } = createWalletEnvironment(undefined)
|
||||
|
||||
it('Generic Injected Mobile Browser', async () => {
|
||||
jest.mock('connection/utils', () => ({ isInjected: true, isMetaMaskWallet: false, isCoinbaseWallet: false }))
|
||||
jest.mock('utils/userAgent', () => ({ isMobile: true }))
|
||||
const connection = await import('connection')
|
||||
expect(connection.darkInjectedConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.darkInjectedConnection.name).toBe('Browser Wallet')
|
||||
})
|
||||
expect(displayed.includes(injected)).toBe(true)
|
||||
expect(injected.getName()).toBe('MetaMask')
|
||||
expect(injected.overrideActivate?.()).toBeTruthy()
|
||||
|
||||
it('MetaMask Mobile Browser', async () => {
|
||||
jest.mock('connection/utils', () => ({ isInjected: true, isMetaMaskWallet: true, isCoinbaseWallet: false }))
|
||||
jest.mock('utils/userAgent', () => ({ isMobile: true }))
|
||||
const connection = await import('connection')
|
||||
expect(connection.darkInjectedConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.darkInjectedConnection.name).toBe('MetaMask')
|
||||
expect(connection.getConnections(true).filter((c) => c.shouldDisplay).length).toEqual(1)
|
||||
})
|
||||
expect(displayed.length).toEqual(4)
|
||||
})
|
||||
|
||||
it('Coinbase Mobile Browser', async () => {
|
||||
jest.mock('connection/utils', () => ({ isInjected: true, isMetaMaskWallet: false, isCoinbaseWallet: true }))
|
||||
jest.mock('utils/userAgent', () => ({ isMobile: true }))
|
||||
const connection = await import('connection')
|
||||
it('MetaMask-Injected Desktop', async () => {
|
||||
const { displayed, injected } = createWalletEnvironment({ isMetaMask: true })
|
||||
|
||||
expect(connection.coinbaseWalletConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.coinbaseWalletConnection.overrideActivate).toBeUndefined()
|
||||
expect(connection.getConnections(true).filter((c) => c.shouldDisplay).length).toEqual(1)
|
||||
})
|
||||
expect(displayed.includes(injected)).toBe(true)
|
||||
expect(injected.getName()).toBe('MetaMask')
|
||||
expect(injected.overrideActivate?.()).toBeFalsy()
|
||||
|
||||
it('mWeb Browser', async () => {
|
||||
jest.mock('connection/utils', () => ({ isInjected: false, isMetaMaskWallet: false, isCoinbaseWallet: false }))
|
||||
jest.mock('utils/userAgent', () => ({ isMobile: true }))
|
||||
const connection = await import('connection')
|
||||
expect(connection.darkInjectedConnection.shouldDisplay).toBe(false)
|
||||
expect(connection.coinbaseWalletConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.coinbaseWalletConnection.overrideActivate).toBeDefined()
|
||||
expect(connection.uniwalletConnectConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.walletConnectConnection.shouldDisplay).toBe(true)
|
||||
expect(connection.getConnections(true).filter((c) => c.shouldDisplay).length).toEqual(3)
|
||||
expect(displayed.length).toEqual(4)
|
||||
})
|
||||
|
||||
it('Coinbase-Injected Desktop', async () => {
|
||||
const { displayed, injected, coinbase } = createWalletEnvironment({ isCoinbaseWallet: true })
|
||||
|
||||
expect(displayed.includes(coinbase)).toBe(true)
|
||||
expect(displayed.includes(injected)).toBe(true)
|
||||
expect(injected.getName()).toBe('MetaMask')
|
||||
expect(injected.overrideActivate?.()).toBeTruthy()
|
||||
|
||||
expect(displayed.length).toEqual(4)
|
||||
})
|
||||
|
||||
it('Coinbase and MetaMask Injected Desktop', async () => {
|
||||
const { displayed, injected, coinbase } = createWalletEnvironment({ isCoinbaseWallet: true, isMetaMask: true })
|
||||
|
||||
expect(displayed.includes(coinbase)).toBe(true)
|
||||
expect(displayed.includes(injected)).toBe(true)
|
||||
expect(injected.getName()).toBe('MetaMask')
|
||||
expect(injected.overrideActivate?.()).toBeFalsy()
|
||||
|
||||
expect(displayed.length).toEqual(4)
|
||||
})
|
||||
|
||||
it('Generic Injected Desktop', async () => {
|
||||
const { displayed, injected } = createWalletEnvironment({ isTrustWallet: true })
|
||||
|
||||
expect(displayed.includes(injected)).toBe(true)
|
||||
expect(injected.getName()).toBe('Browser Wallet')
|
||||
expect(injected.overrideActivate?.()).toBeFalsy()
|
||||
|
||||
expect(displayed.length).toEqual(4)
|
||||
})
|
||||
|
||||
it('Generic Browser Wallet that injects as MetaMask', async () => {
|
||||
const { displayed, injected } = createWalletEnvironment({ isRabby: true, isMetaMask: true })
|
||||
|
||||
expect(displayed.includes(injected)).toBe(true)
|
||||
expect(injected.getName()).toBe('Browser Wallet')
|
||||
expect(injected.overrideActivate?.()).toBeFalsy()
|
||||
|
||||
expect(displayed.length).toEqual(4)
|
||||
})
|
||||
|
||||
it('Generic Wallet Browser with delayed injection', async () => {
|
||||
const { injected } = createWalletEnvironment(undefined)
|
||||
|
||||
expect(injected.getName()).toBe('MetaMask')
|
||||
expect(injected.overrideActivate?.()).toBeTruthy()
|
||||
|
||||
createWalletEnvironment({ isTrustWallet: true })
|
||||
|
||||
expect(injected.getName()).toBe('Browser Wallet')
|
||||
expect(injected.overrideActivate?.()).toBeFalsy()
|
||||
})
|
||||
|
||||
const UNKNOWN_INJECTOR = { isRandomWallet: true } as Window['window']['ethereum']
|
||||
it('Generic Unknown Injected Wallet Browser', async () => {
|
||||
const { displayed, injected } = createWalletEnvironment(UNKNOWN_INJECTOR, true)
|
||||
|
||||
expect(displayed.includes(injected)).toBe(true)
|
||||
expect(injected.getName()).toBe('Browser Wallet')
|
||||
expect(injected.overrideActivate?.()).toBeFalsy()
|
||||
|
||||
// Ensures we provide multiple connection options if in an unknown injected browser
|
||||
expect(displayed.length).toEqual(4)
|
||||
})
|
||||
|
||||
it('MetaMask Mobile Browser', async () => {
|
||||
const { displayed, injected } = createWalletEnvironment({ isMetaMask: true }, true)
|
||||
|
||||
expect(displayed.includes(injected)).toBe(true)
|
||||
expect(injected.getName()).toBe('MetaMask')
|
||||
expect(injected.overrideActivate?.()).toBeFalsy()
|
||||
expect(displayed.length).toEqual(1)
|
||||
})
|
||||
|
||||
it('Coinbase Mobile Browser', async () => {
|
||||
const { displayed, coinbase } = createWalletEnvironment({ isCoinbaseWallet: true }, true)
|
||||
|
||||
expect(displayed.includes(coinbase)).toBe(true)
|
||||
// Expect coinbase option to not override activation in a the cb mobile browser
|
||||
expect(coinbase.overrideActivate?.()).toBeFalsy()
|
||||
expect(displayed.length).toEqual(1)
|
||||
})
|
||||
|
||||
it('Uninjected mWeb Browser', async () => {
|
||||
const { displayed, injected, coinbase, walletconnect } = createWalletEnvironment(undefined, true)
|
||||
|
||||
expect(displayed.includes(coinbase)).toBe(true)
|
||||
expect(displayed.includes(walletconnect)).toBe(true)
|
||||
// Don't show injected connection on plain mWeb browser
|
||||
expect(displayed.includes(injected)).toBe(false)
|
||||
// Expect coinbase option to launch coinbase app in a regular mobile browser
|
||||
expect(coinbase.overrideActivate?.()).toBeTruthy()
|
||||
|
||||
expect(displayed.length).toEqual(3)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,17 +9,15 @@ 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_DARK_ICON_URL from 'assets/svg/browser-wallet-dark.svg'
|
||||
import INJECTED_LIGHT_ICON_URL from 'assets/svg/browser-wallet-light.svg'
|
||||
import UNISWAP_LOGO_URL from 'assets/svg/logo.svg'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { useCallback } from 'react'
|
||||
import { useIsDarkMode } from 'theme/components/ThemeToggle'
|
||||
import { isMobile, isNonIOSPhone } from 'utils/userAgent'
|
||||
|
||||
import { RPC_URLS } from '../constants/networks'
|
||||
import { RPC_PROVIDERS } from '../constants/providers'
|
||||
import { isCoinbaseWallet, isInjected, isMetaMaskWallet } from './utils'
|
||||
import { getIsCoinbaseWallet, getIsInjected, getIsMetaMaskWallet } from './utils'
|
||||
import { UniwalletConnect, WalletConnectPopup } from './WalletConnect'
|
||||
|
||||
export enum ConnectionType {
|
||||
@@ -32,13 +30,14 @@ export enum ConnectionType {
|
||||
}
|
||||
|
||||
export interface Connection {
|
||||
name: string
|
||||
getName(): string
|
||||
connector: Connector
|
||||
hooks: Web3ReactHooks
|
||||
type: ConnectionType
|
||||
icon?: string
|
||||
shouldDisplay?: boolean
|
||||
overrideActivate?: () => void
|
||||
// TODO(WEB-3130): add darkmode check for icons
|
||||
getIcon?(): string
|
||||
shouldDisplay(): boolean
|
||||
overrideActivate?: () => boolean
|
||||
isNew?: boolean
|
||||
}
|
||||
|
||||
@@ -50,73 +49,72 @@ const [web3Network, web3NetworkHooks] = initializeConnector<Network>(
|
||||
(actions) => new Network({ actions, urlMap: RPC_PROVIDERS, defaultChainId: 1 })
|
||||
)
|
||||
export const networkConnection: Connection = {
|
||||
name: 'Network',
|
||||
getName: () => 'Network',
|
||||
connector: web3Network,
|
||||
hooks: web3NetworkHooks,
|
||||
type: ConnectionType.NETWORK,
|
||||
shouldDisplay: false,
|
||||
shouldDisplay: () => false,
|
||||
}
|
||||
|
||||
const isCoinbaseWalletBrowser = isMobile && isCoinbaseWallet
|
||||
const isMetaMaskBrowser = isMobile && isMetaMaskWallet
|
||||
const getIsInjectedMobileBrowser = isCoinbaseWalletBrowser || isMetaMaskBrowser
|
||||
const getIsCoinbaseWalletBrowser = () => isMobile && getIsCoinbaseWallet()
|
||||
const getIsMetaMaskBrowser = () => isMobile && getIsMetaMaskWallet()
|
||||
const getIsInjectedMobileBrowser = () => getIsCoinbaseWalletBrowser() || getIsMetaMaskBrowser()
|
||||
|
||||
const getShouldAdvertiseMetaMask = !isMetaMaskWallet && !isMobile && (!isInjected || isCoinbaseWallet)
|
||||
const isGenericInjector = isInjected && !isMetaMaskWallet && !isCoinbaseWallet
|
||||
const getShouldAdvertiseMetaMask = () =>
|
||||
!getIsMetaMaskWallet() && !isMobile && (!getIsInjected() || getIsCoinbaseWallet())
|
||||
const getIsGenericInjector = () => getIsInjected() && !getIsMetaMaskWallet() && !getIsCoinbaseWallet()
|
||||
|
||||
const [web3Injected, web3InjectedHooks] = initializeConnector<MetaMask>((actions) => new MetaMask({ actions, onError }))
|
||||
const baseInjectedConnection: Omit<Connection, 'icon'> = {
|
||||
name: isGenericInjector ? 'Browser Wallet' : 'MetaMask',
|
||||
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,
|
||||
shouldDisplay: isMetaMaskWallet || getShouldAdvertiseMetaMask || isGenericInjector,
|
||||
getIcon: () => (getIsGenericInjector() ? INJECTED_LIGHT_ICON_URL : METAMASK_ICON_URL),
|
||||
shouldDisplay: () => getIsMetaMaskWallet() || getShouldAdvertiseMetaMask() || getIsGenericInjector(),
|
||||
// If on non-injected, non-mobile browser, prompt user to install Metamask
|
||||
overrideActivate: getShouldAdvertiseMetaMask ? () => window.open('https://metamask.io/', 'inst_metamask') : undefined,
|
||||
}
|
||||
|
||||
export const darkInjectedConnection: Connection = {
|
||||
...baseInjectedConnection,
|
||||
icon: isGenericInjector ? INJECTED_DARK_ICON_URL : METAMASK_ICON_URL,
|
||||
}
|
||||
|
||||
export const lightInjectedConnection: Connection = {
|
||||
...baseInjectedConnection,
|
||||
icon: isGenericInjector ? INJECTED_LIGHT_ICON_URL : METAMASK_ICON_URL,
|
||||
overrideActivate: () => {
|
||||
if (getShouldAdvertiseMetaMask()) {
|
||||
window.open('https://metamask.io/', 'inst_metamask')
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
}
|
||||
|
||||
const [web3GnosisSafe, web3GnosisSafeHooks] = initializeConnector<GnosisSafe>((actions) => new GnosisSafe({ actions }))
|
||||
export const gnosisSafeConnection: Connection = {
|
||||
name: 'Gnosis Safe',
|
||||
getName: () => 'Gnosis Safe',
|
||||
connector: web3GnosisSafe,
|
||||
hooks: web3GnosisSafeHooks,
|
||||
type: ConnectionType.GNOSIS_SAFE,
|
||||
icon: GNOSIS_ICON_URL,
|
||||
shouldDisplay: false,
|
||||
getIcon: () => GNOSIS_ICON_URL,
|
||||
shouldDisplay: () => false,
|
||||
}
|
||||
|
||||
const [web3WalletConnect, web3WalletConnectHooks] = initializeConnector<WalletConnectPopup>(
|
||||
(actions) => new WalletConnectPopup({ actions, onError })
|
||||
)
|
||||
export const walletConnectConnection: Connection = {
|
||||
name: 'WalletConnect',
|
||||
getName: () => 'WalletConnect',
|
||||
connector: web3WalletConnect,
|
||||
hooks: web3WalletConnectHooks,
|
||||
type: ConnectionType.WALLET_CONNECT,
|
||||
icon: WALLET_CONNECT_ICON_URL,
|
||||
shouldDisplay: !getIsInjectedMobileBrowser,
|
||||
getIcon: () => WALLET_CONNECT_ICON_URL,
|
||||
shouldDisplay: () => !getIsInjectedMobileBrowser(),
|
||||
}
|
||||
|
||||
const [web3UniwalletConnect, web3UniwalletConnectHooks] = initializeConnector<UniwalletConnect>(
|
||||
(actions) => new UniwalletConnect({ actions, onError })
|
||||
)
|
||||
export const uniwalletConnectConnection: Connection = {
|
||||
name: 'Uniswap Wallet',
|
||||
getName: () => 'Uniswap Wallet',
|
||||
connector: web3UniwalletConnect,
|
||||
hooks: web3UniwalletConnectHooks,
|
||||
type: ConnectionType.UNIWALLET,
|
||||
icon: UNIWALLET_ICON_URL,
|
||||
shouldDisplay: Boolean(!getIsInjectedMobileBrowser && !isNonIOSPhone),
|
||||
getIcon: () => UNIWALLET_ICON_URL,
|
||||
shouldDisplay: () => Boolean(!getIsInjectedMobileBrowser() && !isNonIOSPhone),
|
||||
isNew: true,
|
||||
}
|
||||
|
||||
@@ -134,24 +132,28 @@ const [web3CoinbaseWallet, web3CoinbaseWalletHooks] = initializeConnector<Coinba
|
||||
})
|
||||
)
|
||||
|
||||
export const coinbaseWalletConnection: Connection = {
|
||||
name: 'Coinbase Wallet',
|
||||
const coinbaseWalletConnection: Connection = {
|
||||
getName: () => 'Coinbase Wallet',
|
||||
connector: web3CoinbaseWallet,
|
||||
hooks: web3CoinbaseWalletHooks,
|
||||
type: ConnectionType.COINBASE_WALLET,
|
||||
icon: COINBASE_ICON_URL,
|
||||
shouldDisplay: Boolean((isMobile && !getIsInjectedMobileBrowser) || !isMobile || isCoinbaseWalletBrowser),
|
||||
getIcon: () => COINBASE_ICON_URL,
|
||||
shouldDisplay: () =>
|
||||
Boolean((isMobile && !getIsInjectedMobileBrowser()) || !isMobile || getIsCoinbaseWalletBrowser()),
|
||||
// If on a mobile browser that isn't the coinbase wallet browser, deeplink to the coinbase wallet app
|
||||
overrideActivate:
|
||||
isMobile && !getIsInjectedMobileBrowser
|
||||
? () => window.open('https://go.cb-w.com/mtUDhEZPy1', 'cbwallet')
|
||||
: undefined,
|
||||
overrideActivate: () => {
|
||||
if (isMobile && !getIsInjectedMobileBrowser()) {
|
||||
window.open('https://go.cb-w.com/mtUDhEZPy1', 'cbwallet')
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
}
|
||||
|
||||
export function getConnections(isDarkMode: boolean) {
|
||||
export function getConnections() {
|
||||
return [
|
||||
uniwalletConnectConnection,
|
||||
isDarkMode ? darkInjectedConnection : lightInjectedConnection,
|
||||
injectedConnection,
|
||||
walletConnectConnection,
|
||||
coinbaseWalletConnection,
|
||||
gnosisSafeConnection,
|
||||
@@ -159,38 +161,29 @@ export function getConnections(isDarkMode: boolean) {
|
||||
]
|
||||
}
|
||||
|
||||
export function useConnections() {
|
||||
const isDarkMode = useIsDarkMode()
|
||||
return getConnections(isDarkMode)
|
||||
}
|
||||
|
||||
export function useGetConnection() {
|
||||
const isDarkMode = useIsDarkMode()
|
||||
return useCallback(
|
||||
(c: Connector | ConnectionType) => {
|
||||
if (c instanceof Connector) {
|
||||
const connection = getConnections(isDarkMode).find((connection) => connection.connector === c)
|
||||
if (!connection) {
|
||||
throw Error('unsupported connector')
|
||||
}
|
||||
return connection
|
||||
} else {
|
||||
switch (c) {
|
||||
case ConnectionType.INJECTED:
|
||||
return isDarkMode ? darkInjectedConnection : lightInjectedConnection
|
||||
case ConnectionType.COINBASE_WALLET:
|
||||
return coinbaseWalletConnection
|
||||
case ConnectionType.WALLET_CONNECT:
|
||||
return walletConnectConnection
|
||||
case ConnectionType.UNIWALLET:
|
||||
return uniwalletConnectConnection
|
||||
case ConnectionType.NETWORK:
|
||||
return networkConnection
|
||||
case ConnectionType.GNOSIS_SAFE:
|
||||
return gnosisSafeConnection
|
||||
}
|
||||
return useCallback((c: Connector | ConnectionType) => {
|
||||
if (c instanceof Connector) {
|
||||
const connection = getConnections().find((connection) => connection.connector === c)
|
||||
if (!connection) {
|
||||
throw Error('unsupported connector')
|
||||
}
|
||||
},
|
||||
[isDarkMode]
|
||||
)
|
||||
return connection
|
||||
} else {
|
||||
switch (c) {
|
||||
case ConnectionType.INJECTED:
|
||||
return injectedConnection
|
||||
case ConnectionType.COINBASE_WALLET:
|
||||
return coinbaseWalletConnection
|
||||
case ConnectionType.WALLET_CONNECT:
|
||||
return walletConnectConnection
|
||||
case ConnectionType.UNIWALLET:
|
||||
return uniwalletConnectConnection
|
||||
case ConnectionType.NETWORK:
|
||||
return networkConnection
|
||||
case ConnectionType.GNOSIS_SAFE:
|
||||
return gnosisSafeConnection
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
export const isInjected = Boolean(window.ethereum)
|
||||
export const getIsInjected = () => Boolean(window.ethereum)
|
||||
|
||||
// When using Brave browser, `isMetaMask` is set to true when using the built-in wallet
|
||||
// This variable should be true only when using the MetaMask extension
|
||||
// https://wallet-docs.brave.com/ethereum/wallet-detection#compatability-with-metamask
|
||||
type NonMetaMaskFlag = 'isRabby' | 'isBraveWallet' | 'isTrustWallet'
|
||||
const allNonMetamaskFlags: NonMetaMaskFlag[] = ['isRabby', 'isBraveWallet', 'isTrustWallet']
|
||||
export const isMetaMaskWallet = Boolean(
|
||||
window.ethereum?.isMetaMask && !allNonMetamaskFlags.some((flag) => window.ethereum?.[flag])
|
||||
)
|
||||
type NonMetaMaskFlag = 'isRabby' | 'isBraveWallet' | 'isTrustWallet' | 'isLedgerConnect'
|
||||
const allNonMetamaskFlags: NonMetaMaskFlag[] = ['isRabby', 'isBraveWallet', 'isTrustWallet', 'isLedgerConnect']
|
||||
export const getIsMetaMaskWallet = () =>
|
||||
Boolean(window.ethereum?.isMetaMask && !allNonMetamaskFlags.some((flag) => window.ethereum?.[flag]))
|
||||
|
||||
export const isCoinbaseWallet = Boolean(window.ethereum?.isCoinbaseWallet)
|
||||
export const getIsCoinbaseWallet = () => Boolean(window.ethereum?.isCoinbaseWallet)
|
||||
|
||||
// https://eips.ethereum.org/EIPS/eip-1193#provider-errors
|
||||
export enum ErrorCode {
|
||||
|
||||
@@ -489,7 +489,7 @@ class ExtendedEther extends Ether {
|
||||
public get wrapped(): Token {
|
||||
const wrapped = WRAPPED_NATIVE_CURRENCY[this.chainId]
|
||||
if (wrapped) return wrapped
|
||||
throw new Error('Unsupported chain ID')
|
||||
throw new Error(`Unsupported chain ID: ${this.chainId}`)
|
||||
}
|
||||
|
||||
private static _cachedExtendedEther: { [chainId: number]: NativeCurrency } = {}
|
||||
|
||||
@@ -8,7 +8,6 @@ export enum FeatureFlag {
|
||||
swapWidget = 'swap_widget_replacement_enabled',
|
||||
statsigDummy = 'web_dummy_gate_amplitude_id',
|
||||
nftGraphql = 'nft_graphql_migration',
|
||||
taxService = 'tax_service_banner',
|
||||
mgtm = 'web_mobile_go_to_market_enabled',
|
||||
walletMicrosite = 'walletMicrosite',
|
||||
miniPortfolio = 'miniPortfolio',
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
|
||||
|
||||
export function useTaxServiceBannerFlag(): BaseVariant {
|
||||
return useBaseFlag(FeatureFlag.taxService, BaseVariant.Control)
|
||||
}
|
||||
|
||||
export function useTaxServiceBannerEnabled(): boolean {
|
||||
return useTaxServiceBannerFlag() === BaseVariant.Enabled
|
||||
}
|
||||
|
||||
export { BaseVariant as TaxServiceVariant }
|
||||
@@ -25,7 +25,7 @@ export default function useSelectChain() {
|
||||
console.error('Failed to switch networks', error)
|
||||
|
||||
dispatch(updateConnectionError({ connectionType, error: error.message }))
|
||||
dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: `failed-network-switch` }))
|
||||
dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: 'failed-network-switch' }))
|
||||
}
|
||||
},
|
||||
[connector, dispatch, getConnection]
|
||||
|
||||
24
src/nft/components/card/MarketplaceContainer.test.tsx
Normal file
24
src/nft/components/card/MarketplaceContainer.test.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Markets } from 'nft/types'
|
||||
import { render } from 'test-utils'
|
||||
|
||||
import { MarketplaceContainer } from './icons'
|
||||
|
||||
describe('MarketplaceContainer', () => {
|
||||
it('should render with list price', () => {
|
||||
const result = render(<MarketplaceContainer isSelected={false} listedPrice="10" />)
|
||||
expect(result.queryByText('10 ETH')).toBeTruthy()
|
||||
expect(result.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('should render null without list price or marketplace', () => {
|
||||
const result = render(<MarketplaceContainer isSelected={false} listedPrice="10" hidePrice={true} />)
|
||||
expect(result.queryByText('10 ETH')).toBeFalsy()
|
||||
expect(result.container.children.length).toEqual(0)
|
||||
expect(result.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('should render with marketplace', () => {
|
||||
const result = render(<MarketplaceContainer isSelected={false} marketplace={Markets.Opensea} />)
|
||||
expect(result.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,144 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MarketplaceContainer should render null without list price or marketplace 1`] = `<div />`;
|
||||
|
||||
exports[`MarketplaceContainer should render with list price 1`] = `
|
||||
.c1 {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
width: 100%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
position: absolute;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
height: 32px;
|
||||
width: auto;
|
||||
padding: 0px 8px;
|
||||
background: rgba(93,103,133,0.24);
|
||||
color: #F5F6FC;
|
||||
-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;
|
||||
border-radius: 32px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
gap: 6px;
|
||||
color: #F5F6FC;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
text-shadow: 1px 1px 3px rgba(51,53,72,0.54);
|
||||
}
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<div
|
||||
class="c1 c2 c3"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"
|
||||
/>
|
||||
<line
|
||||
x1="7"
|
||||
x2="7.01"
|
||||
y1="7"
|
||||
y2="7"
|
||||
/>
|
||||
</svg>
|
||||
10
|
||||
ETH
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`MarketplaceContainer should render with marketplace 1`] = `
|
||||
.c0 {
|
||||
position: absolute;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
padding: 0px;
|
||||
background: rgba(93,103,133,0.24);
|
||||
color: #F5F6FC;
|
||||
-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;
|
||||
border-radius: 32px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="c0"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M24 16.06V14.806C24 14.69 23.888 14.606 23.778 14.638L17.856 16.35C17.824 16.358 17.796 16.376 17.774 16.4C17.1396 17.1008 16.6005 17.571 16.4578 17.6955L16.448 17.704C16.08 18.016 15.624 18.186 15.144 18.186H14.093C13.4894 18.186 13 17.6966 13 17.093C13 16.4894 13.4894 16 14.093 16H14.704C14.748 16 14.79 15.984 14.822 15.956L15.042 15.754C15.136 15.668 15.248 15.564 15.382 15.43C15.3933 15.4187 15.4047 15.4073 15.4163 15.3958C15.4868 15.3256 15.5621 15.2505 15.636 15.168C15.724 15.082 15.81 14.986 15.89 14.892C16.024 14.748 16.152 14.598 16.286 14.44C16.382 14.336 16.47 14.218 16.556 14.1C16.652 13.988 16.746 13.862 16.834 13.742C16.8666 13.6941 16.9013 13.6457 16.9367 13.5963C16.9708 13.5486 17.0057 13.5 17.04 13.45C17.104 13.354 17.168 13.252 17.222 13.156C17.39 12.896 17.532 12.618 17.652 12.34C17.707 12.2211 17.751 12.096 17.7937 11.9743C17.7992 11.9588 17.8046 11.9434 17.81 11.928C17.858 11.786 17.898 11.652 17.928 11.51C18 11.176 18.016 10.844 17.984 10.512C17.9764 10.4136 17.9688 10.317 17.9477 10.2255C17.9454 10.2152 17.944 10.2046 17.944 10.194C17.936 10.126 17.92 10.05 17.898 9.98001C17.826 9.65601 17.714 9.332 17.572 9.014C17.524 8.89599 17.468 8.77599 17.414 8.66598C17.286 8.42802 17.152 8.19001 17 7.96C16.9695 7.91136 16.9357 7.86209 16.902 7.81289C16.8762 7.77511 16.8503 7.73737 16.826 7.70002C16.7297 7.5514 16.6213 7.40815 16.5163 7.26916C16.4926 7.2379 16.4692 7.20686 16.446 7.17602C16.384 7.09458 16.3161 7.01314 16.2477 6.93116C16.2103 6.88629 16.1728 6.84127 16.136 6.79599C16.032 6.66998 15.93 6.54998 15.826 6.43201C15.454 6.01201 15.064 5.63198 14.716 5.30802C14.652 5.24399 14.582 5.18001 14.51 5.11798C14.24 4.87201 13.994 4.65801 13.788 4.49198C13.726 4.44425 13.6703 4.39722 13.6185 4.35345C13.5835 4.32387 13.5503 4.29579 13.518 4.26998C13.4545 4.22272 13.3996 4.18086 13.3537 4.14585C13.3258 4.12459 13.3012 4.10585 13.28 4.08998C13.264 4.078 13.246 4.06999 13.228 4.06398C13.0932 4.02615 13 3.90323 13 3.76322V2.11201C13 1.80401 12.876 1.52802 12.678 1.326C12.48 1.12398 12.204 1 11.9 1C11.292 1 10.8 1.498 10.8 2.11201V3.2656C10.8 3.32504 10.7432 3.36806 10.686 3.35198L10.376 3.26399L10.102 3.18821C10.1004 3.18775 10.0987 3.18716 10.097 3.18657C10.0934 3.18529 10.0898 3.18401 10.086 3.18401C10.082 3.18401 10.078 3.18348 10.0742 3.18244L7.93999 2.60399C7.84603 2.578 7.766 2.68 7.81402 2.766L8.15602 3.39801C8.17546 3.44665 8.2001 3.4953 8.22543 3.54529C8.24175 3.57751 8.25835 3.61028 8.27403 3.64398C8.33001 3.75602 8.38603 3.87399 8.44002 3.992C8.48799 4.09599 8.53601 4.198 8.59203 4.30999C8.61561 4.36275 8.63965 4.41614 8.66403 4.4703C8.75336 4.6687 8.8473 4.87732 8.94 5.10202L8.94079 5.10389C9.02051 5.29326 9.10024 5.48265 9.17001 5.68C9.36199 6.178 9.54402 6.70999 9.70201 7.25601C9.7413 7.37805 9.7727 7.49617 9.80452 7.61587C9.81806 7.66682 9.83168 7.71806 9.84601 7.77001L9.86799 7.866C9.93201 8.12001 9.98799 8.372 10.028 8.62601C10.06 8.8 10.09 8.96598 10.106 9.134L10.106 9.13407C10.13 9.32404 10.154 9.51401 10.162 9.70398C10.178 9.87801 10.186 10.06 10.186 10.234C10.186 10.678 10.146 11.106 10.052 11.51C10.0462 11.5316 10.0403 11.5534 10.0344 11.5755C10.008 11.6739 9.98068 11.776 9.94802 11.874C9.91838 11.9792 9.87997 12.0844 9.84008 12.1937C9.82613 12.2319 9.812 12.2706 9.798 12.31C9.7957 12.3162 9.7934 12.3224 9.7911 12.3286C9.76138 12.4087 9.73114 12.4902 9.694 12.57C9.49601 13.046 9.24999 13.52 8.99602 13.964C8.624 14.622 8.25002 15.2 7.988 15.572C7.97207 15.5959 7.95652 15.6186 7.94154 15.6405C7.92269 15.6681 7.90474 15.6944 7.88803 15.72C7.80601 15.836 7.89002 16 8.032 16H9.707C10.3106 16 10.8 16.4894 10.8 17.093C10.8 17.6966 10.3106 18.186 9.707 18.186H8.00003C7.24802 18.186 6.55203 17.76 6.21599 17.078C6.042 16.736 5.974 16.36 6.01402 15.992C6.02401 15.882 5.94199 15.778 5.82999 15.778H0.17403C0.0779956 15.778 0 15.856 0 15.952V16.068C0 19.676 2.914 22.6 6.51002 22.6H16.656C18.5579 22.6 19.6378 20.8669 20.6993 19.1634C20.9951 18.6886 21.2896 18.216 21.6 17.784C22.158 17.008 23.5 16.392 23.892 16.224C23.956 16.196 24 16.132 24 16.06ZM1.51195 13.202L1.42794 13.334C1.35397 13.448 1.43594 13.6 1.57593 13.6H6.78395C6.84196 13.6 6.89594 13.572 6.92796 13.524C6.99596 13.4201 7.05994 13.312 7.11795 13.202C7.56797 12.446 7.96794 11.628 8.11394 11.024C8.45594 9.55604 7.72595 7.19805 6.87994 5.30201C6.82396 5.17604 6.64993 5.16401 6.57596 5.28004L1.51195 13.202Z"
|
||||
fill="white"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -186,22 +186,20 @@ const Container = ({
|
||||
isSelected,
|
||||
isDisabled,
|
||||
detailsHref,
|
||||
doNotLinkToDetails = false,
|
||||
testId,
|
||||
onClick,
|
||||
children,
|
||||
}: {
|
||||
isSelected: boolean
|
||||
isDisabled: boolean
|
||||
detailsHref: string
|
||||
doNotLinkToDetails: boolean
|
||||
detailsHref?: string
|
||||
testId?: string
|
||||
children: ReactNode
|
||||
onClick?: (e: React.MouseEvent) => void
|
||||
}) => {
|
||||
return (
|
||||
<CardContainer isSelected={isSelected} isDisabled={isDisabled} testId={testId} onClick={onClick}>
|
||||
<StyledLink to={doNotLinkToDetails ? '' : detailsHref}>{children}</StyledLink>
|
||||
{detailsHref ? <StyledLink to={detailsHref}>{children}</StyledLink> : children}
|
||||
</CardContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -40,11 +40,13 @@ export const MarketplaceContainer = ({
|
||||
marketplace,
|
||||
tokenType,
|
||||
listedPrice,
|
||||
hidePrice,
|
||||
}: {
|
||||
isSelected: boolean
|
||||
marketplace?: Markets
|
||||
tokenType?: NftStandard
|
||||
listedPrice?: string
|
||||
hidePrice?: boolean
|
||||
}) => {
|
||||
if (isSelected) {
|
||||
if (!marketplace) {
|
||||
@@ -62,7 +64,7 @@ export const MarketplaceContainer = ({
|
||||
)
|
||||
}
|
||||
|
||||
if (listedPrice) {
|
||||
if (listedPrice && !hidePrice) {
|
||||
return (
|
||||
<StyledMarketplaceContainer isText={true}>
|
||||
<ListPriceRowContainer>
|
||||
|
||||
@@ -13,10 +13,11 @@ interface NftCardProps {
|
||||
display: NftCardDisplayProps
|
||||
isSelected: boolean
|
||||
isDisabled: boolean
|
||||
selectAsset: () => void
|
||||
unselectAsset: () => void
|
||||
onClick?: () => void
|
||||
doNotLinkToDetails?: boolean
|
||||
selectAsset?: () => void
|
||||
unselectAsset?: () => void
|
||||
onButtonClick?: () => void
|
||||
onCardClick?: () => void
|
||||
sendAnalyticsEvent?: () => void
|
||||
mediaShouldBePlaying: boolean
|
||||
uniformAspectRatio?: UniformAspectRatio
|
||||
setUniformAspectRatio?: (uniformAspectRatio: UniformAspectRatio) => void
|
||||
@@ -37,6 +38,12 @@ export interface NftCardDisplayProps {
|
||||
disabledInfo?: ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* NftCard is a component that displays an NFT asset.
|
||||
*
|
||||
* By default, clicking on the card will navigate to the details page.
|
||||
* If you wish to override this behavior, pass a value for the onCardClick prop.
|
||||
*/
|
||||
export const NftCard = ({
|
||||
asset,
|
||||
display,
|
||||
@@ -44,8 +51,9 @@ export const NftCard = ({
|
||||
selectAsset,
|
||||
unselectAsset,
|
||||
isDisabled,
|
||||
onClick,
|
||||
doNotLinkToDetails = false,
|
||||
onButtonClick,
|
||||
onCardClick,
|
||||
sendAnalyticsEvent,
|
||||
mediaShouldBePlaying,
|
||||
uniformAspectRatio = UniformAspectRatios.square,
|
||||
setUniformAspectRatio,
|
||||
@@ -55,7 +63,13 @@ export const NftCard = ({
|
||||
testId,
|
||||
hideDetails = false,
|
||||
}: NftCardProps) => {
|
||||
const clickActionButton = useSelectAsset(selectAsset, unselectAsset, isSelected, isDisabled, onClick)
|
||||
const clickActionButton = useSelectAsset({
|
||||
selectAsset,
|
||||
unselectAsset,
|
||||
isSelected,
|
||||
isDisabled,
|
||||
onClick: onButtonClick,
|
||||
})
|
||||
const { bagExpanded, setBagExpanded } = useBag(
|
||||
(state) => ({
|
||||
bagExpanded: state.bagExpanded,
|
||||
@@ -75,15 +89,17 @@ export const NftCard = ({
|
||||
<Card.Container
|
||||
isSelected={isSelected}
|
||||
isDisabled={isDisabled}
|
||||
detailsHref={detailsHref(asset)}
|
||||
doNotLinkToDetails={doNotLinkToDetails}
|
||||
detailsHref={onCardClick ? undefined : detailsHref(asset)}
|
||||
testId={testId}
|
||||
onClick={() => {
|
||||
if (bagExpanded) setBagExpanded({ bagExpanded: false })
|
||||
onCardClick?.()
|
||||
sendAnalyticsEvent?.()
|
||||
}}
|
||||
>
|
||||
<MediaContainer isDisabled={isDisabled}>
|
||||
<MarketplaceContainer
|
||||
hidePrice={hideDetails}
|
||||
isSelected={isSelected}
|
||||
marketplace={marketplace}
|
||||
tokenType={tokenType}
|
||||
|
||||
@@ -96,13 +96,19 @@ export function getNftDisplayComponent(
|
||||
}
|
||||
}
|
||||
|
||||
export function useSelectAsset(
|
||||
selectAsset: () => void,
|
||||
unselectAsset: () => void,
|
||||
isSelected: boolean,
|
||||
isDisabled: boolean,
|
||||
export function useSelectAsset({
|
||||
selectAsset,
|
||||
unselectAsset,
|
||||
isSelected,
|
||||
isDisabled,
|
||||
onClick,
|
||||
}: {
|
||||
selectAsset?: () => void
|
||||
unselectAsset?: () => void
|
||||
isSelected: boolean
|
||||
isDisabled: boolean
|
||||
onClick?: () => void
|
||||
) {
|
||||
}) {
|
||||
return useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
@@ -117,7 +123,7 @@ export function useSelectAsset(
|
||||
return
|
||||
}
|
||||
|
||||
return isSelected ? unselectAsset() : selectAsset()
|
||||
return isSelected ? unselectAsset?.() : selectAsset?.()
|
||||
},
|
||||
[selectAsset, isDisabled, onClick, unselectAsset, isSelected]
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import styled, { useTheme } from 'styled-components/macro'
|
||||
import { themeVars, vars } from '../css/sprinkles.css'
|
||||
|
||||
// ESLint reports `fill` is missing, whereas it exists on an SVGProps type
|
||||
type SVGProps = React.SVGProps<SVGSVGElement> & { fill?: string }
|
||||
type SVGProps = React.SVGProps<SVGSVGElement> & { fill?: string; height?: string | number; width?: string | number }
|
||||
|
||||
export const UniIcon = (props: SVGProps) => (
|
||||
<svg {...props} fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -67,10 +67,17 @@ export const VerifiedIcon = (props: SVGProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
export const PoolIcon = (props: SVGProps) => (
|
||||
<svg {...props} fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
export const PoolIcon = ({ width, height, ...props }: SVGProps) => (
|
||||
<svg
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={`0 0 ${width} ${height}`}
|
||||
{...props}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3.13184 15.1834C3.13184 15.5098 3.34528 15.7233 3.67174 15.7233H16.2464C16.5854 15.7233 16.8679 15.4596 16.8679 15.108C16.8679 14.7565 16.5854 14.4928 16.2464 14.4928H4.53809C4.40625 14.4928 4.3623 14.4489 4.3623 14.317V10.9019L7.02414 9.35749C7.23758 9.55838 7.52009 9.68394 7.84026 9.68394C8.11021 9.68394 8.36761 9.58977 8.5685 9.43283L9.74874 10.4373C9.7048 10.5628 9.67969 10.701 9.67969 10.8391C9.67969 11.5045 10.2133 12.0382 10.8725 12.0382C11.5317 12.0382 12.0716 11.5045 12.0716 10.8391C12.0716 10.7889 12.0653 10.7324 12.059 10.6821L14.9343 8.96198C15.1226 9.09382 15.3549 9.16915 15.5998 9.16915C16.2589 9.16915 16.7988 8.62925 16.7988 7.97008C16.7988 7.3109 16.2589 6.771 15.5998 6.771C14.9406 6.771 14.4007 7.3109 14.4007 7.97008C14.4007 8.05169 14.4132 8.1333 14.4258 8.20864L11.607 9.89739C11.4061 9.74044 11.1487 9.64627 10.8725 9.64627C10.6779 9.64627 10.4895 9.69022 10.32 9.77811L9.02051 8.6732C9.02679 8.61042 9.03306 8.54764 9.03306 8.48486C9.03306 7.82568 8.49944 7.28578 7.84026 7.28578C7.17481 7.28578 6.64118 7.82568 6.64118 8.48486C6.64118 8.5037 6.64118 8.52253 6.64118 8.54136L4.3623 9.85972V4.44817C4.3623 4.10289 4.09863 3.82666 3.74707 3.82666C3.39551 3.82666 3.13184 4.10289 3.13184 4.44817V15.1834Z"
|
||||
d="M5.19993 7.5915H12.3999V3.59138C12.3999 1.82406 13.8326 0.39143 15.5999 0.39143C17.3672 0.39143 18.8 1.82406 18.8 3.59138H17.1999C17.1999 2.70774 16.4835 1.99148 15.5999 1.99148C14.7162 1.99148 14 2.70774 14 3.59138V12.3915H12.3999V9.19155H5.19993V10.7915H3.59988V3.59145C3.59988 1.82413 5.03265 0.391498 6.79998 0.391498C8.5673 0.391498 9.99993 1.82413 9.99993 3.59145H8.39988C8.39988 2.70781 7.68362 1.99154 6.79998 1.99154C5.91633 1.99154 5.19993 2.70781 5.19993 3.59145V7.5915ZM1.49707 14.7342L0.902832 13.2487C3.75352 12.1084 6.88055 12.1084 10.2529 13.2325C13.2806 14.2417 16.0201 14.2417 18.5027 13.2487L19.097 14.7342C16.2463 15.8745 13.1192 15.8745 9.7469 14.7503C6.71916 13.7411 3.9797 13.7411 1.49707 14.7342ZM1.49707 18.734L0.902832 17.2485C3.75352 16.1082 6.88055 16.1082 10.2529 17.2324C13.2806 18.2416 16.0201 18.2416 18.5027 17.2485L19.097 18.734C16.2463 19.8743 13.1192 19.8743 9.7469 18.7502C6.71916 17.7411 3.9797 17.7411 1.49707 18.734Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
@@ -411,6 +418,35 @@ export const TwitterIcon = (props: SVGProps) => (
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const DiscordIconMenu = (props: SVGProps) => (
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M17.0325 6.95914C16.0748 6.51107 15.0508 6.18542 13.9802 6C13.8487 6.23771 13.6951 6.55743 13.5892 6.81177C12.4512 6.64062 11.3236 6.64062 10.2065 6.81177C10.1006 6.55743 9.94354 6.23771 9.81089 6C8.73916 6.18542 7.71399 6.51226 6.7563 6.96152C4.82465 9.8805 4.30101 12.727 4.56283 15.5331C5.844 16.4898 7.08562 17.071 8.30628 17.4513C8.60767 17.0365 8.87646 16.5956 9.10803 16.1309C8.66701 15.9633 8.24461 15.7565 7.84549 15.5164C7.95137 15.438 8.05494 15.356 8.15501 15.2716C10.5894 16.4102 13.2343 16.4102 15.6396 15.2716C15.7408 15.356 15.8444 15.438 15.9491 15.5164C15.5488 15.7577 15.1253 15.9645 14.6842 16.1321C14.9158 16.5956 15.1834 17.0377 15.486 17.4525C16.7078 17.0722 17.9506 16.491 19.2318 15.5331C19.539 12.2801 18.707 9.45977 17.0325 6.95914ZM9.43967 13.8074C8.70891 13.8074 8.10962 13.1251 8.10962 12.2944C8.10962 11.4636 8.69611 10.7802 9.43967 10.7802C10.1833 10.7802 10.7825 11.4624 10.7697 12.2944C10.7709 13.1251 10.1833 13.8074 9.43967 13.8074ZM14.3549 13.8074C13.6242 13.8074 13.0249 13.1251 13.0249 12.2944C13.0249 11.4636 13.6113 10.7802 14.3549 10.7802C15.0985 10.7802 15.6978 11.4624 15.685 12.2944C15.685 13.1251 15.0985 13.8074 14.3549 13.8074Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const TwitterIconMenu = (props: SVGProps) => (
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
d="M18.955 7.34849C18.4393 7.57725 17.885 7.73179 17.3033 7.80132C17.897 7.44542 18.3531 6.88184 18.5678 6.21026C18.012 6.53989 17.3966 6.77916 16.7415 6.90812C16.217 6.34918 15.4695 6 14.6424 6C13.0542 6 11.7665 7.28758 11.7665 8.87576C11.7665 9.10117 11.7919 9.32068 11.841 9.53116C9.45084 9.41124 7.33181 8.26628 5.91338 6.52636C5.66583 6.95109 5.52397 7.44507 5.52397 7.97213C5.52397 8.96989 6.03168 9.85012 6.80335 10.3658C6.33194 10.3509 5.8885 10.2215 5.50077 10.0061C5.50044 10.0181 5.50044 10.0302 5.50044 10.0423C5.50044 11.4356 6.49176 12.5979 7.80738 12.8623C7.56607 12.928 7.312 12.9631 7.04973 12.9631C6.86442 12.9631 6.6843 12.9451 6.50866 12.9116C6.87465 14.054 7.93669 14.8855 9.19518 14.9086C8.21094 15.68 6.97095 16.1398 5.62352 16.1398C5.39139 16.1398 5.16248 16.1261 4.9375 16.0996C6.21022 16.9156 7.72187 17.3917 9.34594 17.3917C14.6357 17.3917 17.5284 13.0096 17.5284 9.20917C17.5284 9.08449 17.5256 8.9605 17.52 8.83708C18.0819 8.43169 18.5694 7.92524 18.955 7.34849H18.955Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const GithubIconMenu = (props: SVGProps) => (
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12 5.25C8.27062 5.25 5.25 8.34703 5.25 12.1707C5.25 15.2332 7.18219 17.8198 9.86531 18.7368C10.2028 18.7973 10.3294 18.5897 10.3294 18.408C10.3294 18.2437 10.3209 17.6987 10.3209 17.1191C8.625 17.4391 8.18625 16.6952 8.05125 16.3059C7.97531 16.1069 7.64625 15.4927 7.35938 15.3283C7.12312 15.1986 6.78562 14.8785 7.35094 14.8698C7.8825 14.8612 8.26219 15.3716 8.38875 15.5792C8.99625 16.626 9.96656 16.3318 10.3547 16.1502C10.4138 15.7003 10.5909 15.3975 10.785 15.2245C9.28312 15.0515 7.71375 14.4546 7.71375 11.8074C7.71375 11.0548 7.97531 10.4319 8.40563 9.94745C8.33812 9.77443 8.10187 9.06505 8.47312 8.11345C8.47312 8.11345 9.03844 7.93178 10.3294 8.82283C10.8694 8.66711 11.4431 8.58925 12.0169 8.58925C12.5906 8.58925 13.1644 8.66711 13.7044 8.82283C14.9953 7.92313 15.5606 8.11345 15.5606 8.11345C15.9319 9.06505 15.6956 9.77443 15.6281 9.94745C16.0584 10.4319 16.32 11.0461 16.32 11.8074C16.32 14.4632 14.7422 15.0515 13.2403 15.2245C13.485 15.4408 13.6959 15.856 13.6959 16.5048C13.6959 17.4305 13.6875 18.1745 13.6875 18.408C13.6875 18.5897 13.8141 18.806 14.1516 18.7368C16.8178 17.8198 18.75 15.2245 18.75 12.1707C18.75 8.34703 15.7294 5.25 12 5.25Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const CheckMarkIcon = (props: SVGProps) => (
|
||||
<svg {...props} fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
|
||||
@@ -3,10 +3,12 @@ import { useTrace } from '@uniswap/analytics'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { NFTEventName } from '@uniswap/analytics-events'
|
||||
import { NftCard, NftCardDisplayProps } from 'nft/components/card'
|
||||
import { detailsHref } from 'nft/components/card/utils'
|
||||
import { VerifiedIcon } from 'nft/components/icons'
|
||||
import { useBag, useIsMobile, useSellAsset } from 'nft/hooks'
|
||||
import { WalletAsset } from 'nft/types'
|
||||
import { useMemo } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
interface ViewMyNftsAssetProps {
|
||||
asset: WalletAsset
|
||||
@@ -27,6 +29,7 @@ export const ViewMyNftsAsset = ({
|
||||
const cartExpanded = useBag((state) => state.bagExpanded)
|
||||
const toggleCart = useBag((state) => state.toggleBag)
|
||||
const isMobile = useIsMobile()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const isSelected = useMemo(() => {
|
||||
return sellAssets.some(
|
||||
@@ -35,7 +38,7 @@ export const ViewMyNftsAsset = ({
|
||||
}, [asset, sellAssets])
|
||||
|
||||
const trace = useTrace()
|
||||
const onCardClick = () => handleSelect(isSelected)
|
||||
const toggleSelect = () => handleSelect(isSelected)
|
||||
|
||||
const handleSelect = (removeAsset: boolean) => {
|
||||
if (removeAsset) {
|
||||
@@ -79,11 +82,13 @@ export const ViewMyNftsAsset = ({
|
||||
isDisabled={Boolean(isDisabled)}
|
||||
selectAsset={() => handleSelect(false)}
|
||||
unselectAsset={() => handleSelect(true)}
|
||||
onClick={onCardClick}
|
||||
onButtonClick={toggleSelect}
|
||||
onCardClick={() => {
|
||||
if (!hideDetails) navigate(detailsHref(asset))
|
||||
}}
|
||||
mediaShouldBePlaying={mediaShouldBePlaying}
|
||||
setCurrentTokenPlayingMedia={setCurrentTokenPlayingMedia}
|
||||
testId="nft-profile-asset"
|
||||
doNotLinkToDetails={hideDetails}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import { BagItem, BagItemStatus, BagStatus, UpdatedGenieAsset } from 'nft/types'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
interface BagState {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NftAssetSortableField } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
export enum SortBy {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools, persist } from 'zustand/middleware'
|
||||
|
||||
interface State {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
interface State {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
interface NFTClaim {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
export type MarketplaceOption = { name: string; icon: string }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CollectionRow, ListingRow, ListingStatus } from 'nft/types'
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
interface NFTListState {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
import { OpenSeaAsset } from '../types'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
interface PriceRangeProps {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
import { ProfilePageStateType } from '../types'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
import { GenieAsset } from '../types'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
import { ListingMarket, WalletAsset } from '../types'
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ContractReceipt } from '@ethersproject/contracts'
|
||||
import type { JsonRpcSigner } from '@ethersproject/providers'
|
||||
import { sendAnalyticsEvent } from '@uniswap/analytics'
|
||||
import { NFTEventName } from '@uniswap/analytics-events'
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
import ERC721 from '../../abis/erc721.json'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools, persist } from 'zustand/middleware'
|
||||
|
||||
import { GenieAsset } from '../types'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { TokenTradeInput } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
interface TokenInputState {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
interface traitOpen {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TxResponse } from 'nft/types'
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
type TransactionResponseValue = TxResponse | undefined
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NftStandard } from 'graphql/data/__generated__/types-and-hooks'
|
||||
import create from 'zustand'
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
import { WalletAsset, WalletCollection } from '../types'
|
||||
|
||||
@@ -6,10 +6,12 @@ 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 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'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
|
||||
@@ -21,6 +23,7 @@ import {
|
||||
useV3MintState,
|
||||
} from 'state/mint/v3/hooks'
|
||||
import { useTheme } from 'styled-components/macro'
|
||||
import { addressesAreEquivalent } from 'utils/addressesAreEquivalent'
|
||||
|
||||
import { ButtonError, ButtonLight, ButtonPrimary, ButtonText } from '../../components/Button'
|
||||
import { BlueCard, OutlineCard, YellowCard } from '../../components/Card'
|
||||
@@ -548,6 +551,12 @@ export default function AddLiquidity() {
|
||||
}),
|
||||
[usdcValueCurrencyB]
|
||||
)
|
||||
|
||||
const owner = useSingleCallResult(tokenId ? positionManager : null, 'ownerOf', [tokenId]).result?.[0]
|
||||
const ownsNFT =
|
||||
addressesAreEquivalent(owner, account) || addressesAreEquivalent(existingPositionDetails?.operator, account)
|
||||
const showOwnershipWarning = Boolean(hasExistingPosition && account && !ownsNFT)
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollablePage>
|
||||
@@ -912,6 +921,7 @@ export default function AddLiquidity() {
|
||||
</ResponsiveTwoColumns>
|
||||
</Wrapper>
|
||||
</PageWrapper>
|
||||
{showOwnershipWarning && <OwnershipWarning ownerAddress={owner} />}
|
||||
{addIsUnsupported && (
|
||||
<UnsupportedCurrencyFooter
|
||||
show={addIsUnsupported}
|
||||
|
||||
@@ -6,9 +6,11 @@ import TopLevelModals from 'components/TopLevelModals'
|
||||
import { useFeatureFlagsIsLoaded } from 'featureFlags'
|
||||
import { useMGTMMicrositeEnabled } from 'featureFlags/flags/mgtm'
|
||||
import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader'
|
||||
import { useAtom } from 'jotai'
|
||||
import { useBag } from 'nft/hooks/useBag'
|
||||
import { lazy, Suspense, useEffect, useMemo, useState } from 'react'
|
||||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom'
|
||||
import { Navigate, Route, Routes, useLocation, useSearchParams } from 'react-router-dom'
|
||||
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
|
||||
import { StatsigProvider, StatsigUser } from 'statsig-react'
|
||||
import styled from 'styled-components/macro'
|
||||
import { SpinnerSVG } from 'theme/components'
|
||||
@@ -132,6 +134,7 @@ const LazyLoadSpinner = () => (
|
||||
|
||||
export default function App() {
|
||||
const isLoaded = useFeatureFlagsIsLoaded()
|
||||
const [shouldDisableNFTRoutes, setShouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom)
|
||||
|
||||
const { pathname } = useLocation()
|
||||
const currentPage = getCurrentPageFromLocation(pathname)
|
||||
@@ -146,6 +149,15 @@ export default function App() {
|
||||
setScrolledState(false)
|
||||
}, [pathname])
|
||||
|
||||
const [searchParams] = useSearchParams()
|
||||
useEffect(() => {
|
||||
if (searchParams.get('disableNFTs') === 'true') {
|
||||
setShouldDisableNFTRoutes(true)
|
||||
} else if (searchParams.get('disableNFTs') === 'false') {
|
||||
setShouldDisableNFTRoutes(false)
|
||||
}
|
||||
}, [searchParams, setShouldDisableNFTRoutes])
|
||||
|
||||
useEffect(() => {
|
||||
// User properties *must* be set before sending corresponding event properties,
|
||||
// so that the event contains the correct and up-to-date user properties.
|
||||
@@ -271,46 +283,54 @@ export default function App() {
|
||||
<Route path="migrate/v2" element={<MigrateV2 />} />
|
||||
<Route path="migrate/v2/:address" element={<MigrateV2Pair />} />
|
||||
|
||||
<Route
|
||||
path="/nfts"
|
||||
element={
|
||||
<Suspense fallback={null}>
|
||||
<NftExplore />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/nfts/asset/:contractAddress/:tokenId"
|
||||
element={
|
||||
<Suspense fallback={null}>
|
||||
<Asset />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/nfts/profile"
|
||||
element={
|
||||
<Suspense fallback={null}>
|
||||
<Profile />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/nfts/collection/:contractAddress"
|
||||
element={
|
||||
<Suspense fallback={null}>
|
||||
<Collection />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/nfts/collection/:contractAddress/activity"
|
||||
element={
|
||||
<Suspense fallback={null}>
|
||||
<Collection />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
{!shouldDisableNFTRoutes && (
|
||||
<>
|
||||
<Route
|
||||
path="/nfts"
|
||||
element={
|
||||
<Suspense fallback={null}>
|
||||
<NftExplore />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/nfts/asset/:contractAddress/:tokenId"
|
||||
element={
|
||||
<Suspense fallback={null}>
|
||||
<Asset />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/nfts/profile"
|
||||
element={
|
||||
<Suspense fallback={null}>
|
||||
<Profile />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/nfts/collection/:contractAddress"
|
||||
element={
|
||||
<Suspense fallback={null}>
|
||||
<Collection />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/nfts/collection/:contractAddress/activity"
|
||||
element={
|
||||
<Suspense fallback={null}>
|
||||
<Collection />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Route path="*" element={<Navigate to="/not-found" replace />} />
|
||||
<Route path="/not-found" element={<NotFound />} />
|
||||
|
||||
@@ -7,12 +7,14 @@ import { MAIN_CARDS, MORE_CARDS } from 'components/About/constants'
|
||||
import ProtocolBanner from 'components/About/ProtocolBanner'
|
||||
import { BaseButton } from 'components/Button'
|
||||
import { useSwapWidgetEnabled } from 'featureFlags/flags/swapWidget'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import Swap from 'pages/Swap'
|
||||
import { parse } from 'qs'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { ArrowDownCircle } from 'react-feather'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Link as NativeLink } from 'react-router-dom'
|
||||
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import styled, { css } from 'styled-components/macro'
|
||||
import { BREAKPOINTS } from 'theme'
|
||||
@@ -316,6 +318,8 @@ export default function Landing() {
|
||||
}
|
||||
}, [navigate, selectedWallet, queryParams.intro])
|
||||
|
||||
const shouldDisableNFTRoutes = useAtomValue(shouldDisableNFTRoutesAtom)
|
||||
|
||||
return (
|
||||
<Trace page={InterfacePageName.LANDING_PAGE} shouldLogImpression>
|
||||
{showContent && (
|
||||
@@ -342,9 +346,21 @@ export default function Landing() {
|
||||
<Glow />
|
||||
</GlowContainer>
|
||||
<ContentContainer isDarkMode={isDarkMode}>
|
||||
<TitleText isDarkMode={isDarkMode}>Trade crypto & NFTs with confidence</TitleText>
|
||||
<TitleText isDarkMode={isDarkMode}>
|
||||
{shouldDisableNFTRoutes ? (
|
||||
<Trans>Trade crypto with confidence</Trans>
|
||||
) : (
|
||||
<Trans>Trade crypto and NFTs with confidence</Trans>
|
||||
)}
|
||||
</TitleText>
|
||||
<SubTextContainer>
|
||||
<SubText>Buy, sell, and explore tokens and NFTs</SubText>
|
||||
<SubText>
|
||||
{shouldDisableNFTRoutes ? (
|
||||
<Trans>Buy, sell, and explore tokens</Trans>
|
||||
) : (
|
||||
<Trans>Buy, sell, and explore tokens and NFTs</Trans>
|
||||
)}
|
||||
</SubText>
|
||||
</SubTextContainer>
|
||||
<ActionsContainer>
|
||||
<TraceEvent
|
||||
|
||||
@@ -19,14 +19,10 @@ const CTASection = styled.section`
|
||||
`};
|
||||
`
|
||||
|
||||
const CTA1 = styled(ExternalLink)`
|
||||
const CTA = styled(ExternalLink)`
|
||||
padding: 16px;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg3};
|
||||
|
||||
@@ -45,30 +41,6 @@ const CTA1 = styled(ExternalLink)`
|
||||
}
|
||||
`
|
||||
|
||||
const CTA2 = styled(ExternalLink)`
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 16px;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg3};
|
||||
|
||||
* {
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
:hover {
|
||||
border: 1px solid ${({ theme }) => theme.deprecated_bg4};
|
||||
text-decoration: none !important;
|
||||
* {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const HeaderText = styled(ThemedText.DeprecatedLabel)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@@ -97,7 +69,7 @@ export default function CTACards() {
|
||||
|
||||
return (
|
||||
<CTASection>
|
||||
<CTA1 href="https://support.uniswap.org/hc/en-us/categories/8122334631437-Providing-Liquidity-">
|
||||
<CTA href="https://support.uniswap.org/hc/en-us/categories/8122334631437-Providing-Liquidity-">
|
||||
<ResponsiveColumn>
|
||||
<HeaderText>
|
||||
<Trans>Learn about providing liquidity</Trans> ↗
|
||||
@@ -106,8 +78,8 @@ export default function CTACards() {
|
||||
<Trans>Check out our v3 LP walkthrough and migration guides.</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</ResponsiveColumn>
|
||||
</CTA1>
|
||||
<CTA2 data-testid="cta-infolink" href={infoLink + 'pools'}>
|
||||
</CTA>
|
||||
<CTA data-testid="cta-infolink" href={infoLink + 'pools'}>
|
||||
<ResponsiveColumn>
|
||||
<HeaderText style={{ alignSelf: 'flex-start' }}>
|
||||
<Trans>Top pools</Trans> ↗
|
||||
@@ -116,7 +88,7 @@ export default function CTACards() {
|
||||
<Trans>Explore Uniswap Analytics.</Trans>
|
||||
</ThemedText.DeprecatedBody>
|
||||
</ResponsiveColumn>
|
||||
</CTA2>
|
||||
</CTA>
|
||||
</CTASection>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ import { RowBetween, RowFixed } from 'components/Row'
|
||||
import { Dots } from 'components/swap/styleds'
|
||||
import Toggle from 'components/Toggle'
|
||||
import TransactionConfirmationModal, { ConfirmationModalContent } from 'components/TransactionConfirmationModal'
|
||||
import { CHAIN_ID_TO_BACKEND_NAME, isGqlSupportedChain } from 'graphql/data/util'
|
||||
import { CHAIN_IDS_TO_NAMES } from 'constants/chains'
|
||||
import { isGqlSupportedChain } from 'graphql/data/util'
|
||||
import { useToken } from 'hooks/Tokens'
|
||||
import { useV3NFTPositionManagerContract } from 'hooks/useContract'
|
||||
import useIsTickAtLimit from 'hooks/useIsTickAtLimit'
|
||||
@@ -54,7 +55,7 @@ import { LoadingRows } from './styleds'
|
||||
|
||||
const getTokenLink = (chainId: SupportedChainId, address: string) => {
|
||||
if (isGqlSupportedChain(chainId)) {
|
||||
const chainName = CHAIN_ID_TO_BACKEND_NAME[chainId]
|
||||
const chainName = CHAIN_IDS_TO_NAMES[chainId]
|
||||
return `${window.location.origin}/#/tokens/${chainName}/${address}`
|
||||
} else {
|
||||
return getExplorerLink(chainId, address, ExplorerDataType.TOKEN)
|
||||
|
||||
2
src/react-app-env.d.ts
vendored
2
src/react-app-env.d.ts
vendored
@@ -20,6 +20,8 @@ interface Window {
|
||||
isRabby?: true
|
||||
// set by the Trust Wallet browser extension
|
||||
isTrustWallet?: true
|
||||
// set by the Ledger Extension Web 3 browser extension
|
||||
isLedgerConnect?: true
|
||||
autoRefreshOnNetworkChange?: boolean
|
||||
}
|
||||
web3?: Record<string, unknown>
|
||||
|
||||
14
src/state/application/atoms.ts
Normal file
14
src/state/application/atoms.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { atomWithStorage, createJSONStorage } from 'jotai/utils'
|
||||
|
||||
/*
|
||||
Note:
|
||||
We should consider a generic sessionStorage abstraction if this pattern becomes common. (i.e., Future promo dismissals like the tax service discounts or Fiat Onramp launch notification may use this.)
|
||||
This would be something similar to the current feature flag implementation, but utilizing session instead
|
||||
|
||||
Motivation:
|
||||
some dapp browsers need to be able to disable the NFT portion of the app in order to pass Apple's app store review
|
||||
this atom persists the inclusion of the `disableNFTs=boolean` query parameter via the webview's session storage
|
||||
*/
|
||||
const storage = createJSONStorage(() => sessionStorage)
|
||||
|
||||
export const shouldDisableNFTRoutesAtom = atomWithStorage('shouldDisableNFTRoutes', false, storage)
|
||||
@@ -118,10 +118,6 @@ export function useToggleSelfClaimModal(): () => void {
|
||||
return useToggleModal(ApplicationModal.SELF_CLAIM)
|
||||
}
|
||||
|
||||
export function useToggleTaxServiceModal(): () => void {
|
||||
return useToggleModal(ApplicationModal.TAX_SERVICE)
|
||||
}
|
||||
|
||||
export function useToggleDelegateModal(): () => void {
|
||||
return useToggleModal(ApplicationModal.DELEGATE)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ export const sentryEnhancer = Sentry.createReduxEnhancer({
|
||||
popupList: application.popupList,
|
||||
},
|
||||
user: {
|
||||
taxServiceDismissals: user.taxServiceDismissals,
|
||||
selectedWallet: user.selectedWallet,
|
||||
lastUpdateVersionTimestamp: user.lastUpdateVersionTimestamp,
|
||||
userLocale: user.userLocale,
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
addSerializedToken,
|
||||
updateHideClosedPositions,
|
||||
updateHideUniswapWalletBanner,
|
||||
updateTaxServiceAcknowledgments,
|
||||
updateUserClientSideRouter,
|
||||
updateUserDeadline,
|
||||
updateUserExpertMode,
|
||||
@@ -69,18 +68,6 @@ export function useIsExpertMode(): boolean {
|
||||
return useAppSelector((state) => state.user.userExpertMode)
|
||||
}
|
||||
|
||||
export function useTaxServiceDismissal(): [number | undefined, (dismissals: number) => void] {
|
||||
const dispatch = useAppDispatch()
|
||||
const taxServiceDismissals = useAppSelector((state) => state.user.taxServiceDismissals)
|
||||
const setDismissals = useCallback(
|
||||
(dismissals: number) => {
|
||||
dispatch(updateTaxServiceAcknowledgments({ taxServiceDismissals: dismissals }))
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
return [taxServiceDismissals, setDismissals]
|
||||
}
|
||||
|
||||
export function useExpertModeManager(): [boolean, () => void] {
|
||||
const dispatch = useAppDispatch()
|
||||
const expertMode = useIsExpertMode()
|
||||
|
||||
@@ -9,8 +9,6 @@ import { SerializedPair, SerializedToken } from './types'
|
||||
const currentTimestamp = () => new Date().getTime()
|
||||
|
||||
export interface UserState {
|
||||
taxServiceDismissals: number | undefined
|
||||
|
||||
selectedWallet?: ConnectionType
|
||||
|
||||
// the timestamp of the last updateVersion action
|
||||
@@ -57,7 +55,6 @@ function pairKey(token0Address: string, token1Address: string) {
|
||||
}
|
||||
|
||||
export const initialState: UserState = {
|
||||
taxServiceDismissals: 0,
|
||||
selectedWallet: undefined,
|
||||
userExpertMode: false,
|
||||
userLocale: null,
|
||||
@@ -78,9 +75,6 @@ const userSlice = createSlice({
|
||||
name: 'user',
|
||||
initialState,
|
||||
reducers: {
|
||||
updateTaxServiceAcknowledgments(state, action) {
|
||||
state.taxServiceDismissals = action.payload.taxServiceDismissals
|
||||
},
|
||||
updateSelectedWallet(state, { payload: { wallet } }) {
|
||||
state.selectedWallet = wallet
|
||||
},
|
||||
@@ -169,7 +163,6 @@ const userSlice = createSlice({
|
||||
export const {
|
||||
addSerializedPair,
|
||||
addSerializedToken,
|
||||
updateTaxServiceAcknowledgments,
|
||||
updateSelectedWallet,
|
||||
updateHideClosedPositions,
|
||||
updateUserClientSideRouter,
|
||||
|
||||
@@ -17,10 +17,13 @@ const AMPLITUDE_DUMMY_KEY = '00000000000000000000000000000000'
|
||||
export const STATSIG_DUMMY_KEY = 'client-0000000000000000000000000000000000000000000'
|
||||
|
||||
Sentry.init({
|
||||
// General configuration:
|
||||
dsn: process.env.REACT_APP_SENTRY_DSN,
|
||||
release: process.env.REACT_APP_GIT_COMMIT_HASH,
|
||||
environment: getEnvName(),
|
||||
// Exception reporting configuration:
|
||||
enabled: isSentryEnabled(),
|
||||
// Performance tracing configuration:
|
||||
tracesSampleRate: Number(process.env.REACT_APP_SENTRY_TRACES_SAMPLE_RATE ?? 0),
|
||||
integrations: [
|
||||
new BrowserTracing({
|
||||
@@ -28,13 +31,6 @@ Sentry.init({
|
||||
startTransactionOnPageLoad: true,
|
||||
}),
|
||||
],
|
||||
/**
|
||||
* TODO(INFRA-143)
|
||||
* According to Sentry, this shouldn't be necessary, as they default to `3` when not set.
|
||||
* Unfortunately, that doesn't work right now, so we workaround it by explicitly setting
|
||||
* the `normalizeDepth` to `10`. This should be removed once the issue is fixed.
|
||||
*/
|
||||
normalizeDepth: 10,
|
||||
})
|
||||
|
||||
initializeAnalytics(AMPLITUDE_DUMMY_KEY, OriginApplication.INTERFACE, {
|
||||
|
||||
20
src/utils/addressesAreEquivalent.test.ts
Normal file
20
src/utils/addressesAreEquivalent.test.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { addressesAreEquivalent } from './addressesAreEquivalent'
|
||||
|
||||
describe('addressesAreEquivalent', () => {
|
||||
it('should return false for undefined addresses', () => {
|
||||
expect(addressesAreEquivalent(undefined, undefined)).toBe(false)
|
||||
})
|
||||
it('should return true for mismatched checksum equivalence', () => {
|
||||
expect(
|
||||
addressesAreEquivalent(
|
||||
'0x48c89D77ae34Ae475e4523b25aB01e363dce5A78',
|
||||
'0x48c89D77ae34Ae475e4523b25aB01e363dce5A78'.toLowerCase()
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
it('should return true for simple equivalence', () => {
|
||||
expect(
|
||||
addressesAreEquivalent('0x48c89D77ae34Ae475e4523b25aB01e363dce5A78', '0x48c89D77ae34Ae475e4523b25aB01e363dce5A78')
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
4
src/utils/addressesAreEquivalent.ts
Normal file
4
src/utils/addressesAreEquivalent.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export function addressesAreEquivalent(a?: string, b?: string) {
|
||||
if (!a || !b) return false
|
||||
return a === b || a.toLowerCase() === b.toLowerCase()
|
||||
}
|
||||
379
yarn.lock
379
yarn.lock
@@ -415,10 +415,10 @@
|
||||
chalk "^2.0.0"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@babel/parser@^7.1.0", "@babel/parser@^7.11.5", "@babel/parser@^7.12.3", "@babel/parser@^7.14.0", "@babel/parser@^7.16.8", "@babel/parser@^7.18.10", "@babel/parser@^7.20.5", "@babel/parser@^7.7.0":
|
||||
version "7.20.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8"
|
||||
integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==
|
||||
"@babel/parser@^7.1.0", "@babel/parser@^7.11.5", "@babel/parser@^7.12.3", "@babel/parser@^7.14.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.8", "@babel/parser@^7.18.10", "@babel/parser@^7.20.5", "@babel/parser@^7.7.0":
|
||||
version "7.21.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3"
|
||||
integrity sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==
|
||||
|
||||
"@babel/plugin-proposal-async-generator-functions@^7.12.1":
|
||||
version "7.16.8"
|
||||
@@ -1267,6 +1267,21 @@
|
||||
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18"
|
||||
integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==
|
||||
|
||||
"@cypress/code-coverage@^3.10.0":
|
||||
version "3.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@cypress/code-coverage/-/code-coverage-3.10.0.tgz#2132dbb7ae068cab91790926d50a9bf85140cab4"
|
||||
integrity sha512-K5pW2KPpK4vKMXqxd6vuzo6m9BNgpAv1LcrrtmqAtOJ1RGoEILXYZVost0L6Q+V01NyY7n7jXIIfS7LR3nP6YA==
|
||||
dependencies:
|
||||
"@cypress/webpack-preprocessor" "^5.11.0"
|
||||
chalk "4.1.2"
|
||||
dayjs "1.10.7"
|
||||
debug "4.3.4"
|
||||
execa "4.1.0"
|
||||
globby "11.0.4"
|
||||
istanbul-lib-coverage "3.0.0"
|
||||
js-yaml "3.14.1"
|
||||
nyc "15.1.0"
|
||||
|
||||
"@cypress/request@^2.88.10":
|
||||
version "2.88.10"
|
||||
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce"
|
||||
@@ -1291,6 +1306,15 @@
|
||||
tunnel-agent "^0.6.0"
|
||||
uuid "^8.3.2"
|
||||
|
||||
"@cypress/webpack-preprocessor@^5.11.0":
|
||||
version "5.17.0"
|
||||
resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.17.0.tgz#3f58cf333c5931094780e3ca14c9302a1965025e"
|
||||
integrity sha512-HyFqHkrOrIIYOt4G+r3VK0kVYTcev1tEcqBI/0DJ4AzEuEgW/TB+cX56txy4Cgn60XXdJoul2utclZwUqOsPZA==
|
||||
dependencies:
|
||||
bluebird "3.7.1"
|
||||
debug "^4.3.4"
|
||||
lodash "^4.17.20"
|
||||
|
||||
"@cypress/xvfb@^1.2.4":
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.4.tgz#2daf42e8275b39f4aa53c14214e557bd14e7748a"
|
||||
@@ -3166,67 +3190,75 @@
|
||||
dependencies:
|
||||
cross-fetch "^3.1.5"
|
||||
|
||||
"@sentry/browser@7.42.0":
|
||||
version "7.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.42.0.tgz#357731e5ab65a226c98370f9e487fe48cadab765"
|
||||
integrity sha512-xTwfvrQPmYTkAvGivoJFadPLKLDS2N57D/18NA1gcrnF8NwR+I28x3I9ziVUiMCYX+6nJuqBNlMALAEPbb2G5A==
|
||||
"@sentry-internal/tracing@7.45.0":
|
||||
version "7.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.45.0.tgz#01f347d0d1b42451b340b32b12923dc22e042d27"
|
||||
integrity sha512-0aIDY2OvUX7k2XHaimOlWkboXoQvJ9dEKvfpu0Wh0YxfUTGPa+wplUdg3WVdkk018sq1L11MKmj4MPZyYUvXhw==
|
||||
dependencies:
|
||||
"@sentry/core" "7.42.0"
|
||||
"@sentry/replay" "7.42.0"
|
||||
"@sentry/types" "7.42.0"
|
||||
"@sentry/utils" "7.42.0"
|
||||
"@sentry/core" "7.45.0"
|
||||
"@sentry/types" "7.45.0"
|
||||
"@sentry/utils" "7.45.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/core@7.42.0":
|
||||
version "7.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.42.0.tgz#3333a1b868e8e69b6fbc8e5a9e9281be49321ac7"
|
||||
integrity sha512-vNcTyoQz5kUXo5vMGDyc5BJMO0UugPvMfYMQVxqt/BuDNR30LVhY+DL2tW1DFZDvRvyn5At+H7kSTj6GFrANXQ==
|
||||
"@sentry/browser@7.45.0":
|
||||
version "7.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.45.0.tgz#c9f8031ad184558d08c374d4e4ee30996cc5b543"
|
||||
integrity sha512-/dUrUwnI34voMj+jSJT7b5Jun+xy1utVyzzwTq3Oc22N+SB17ZOX9svZ4jl1Lu6tVJPVjPyvL6zlcbrbMwqFjg==
|
||||
dependencies:
|
||||
"@sentry/types" "7.42.0"
|
||||
"@sentry/utils" "7.42.0"
|
||||
"@sentry-internal/tracing" "7.45.0"
|
||||
"@sentry/core" "7.45.0"
|
||||
"@sentry/replay" "7.45.0"
|
||||
"@sentry/types" "7.45.0"
|
||||
"@sentry/utils" "7.45.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/react@^7.40.0":
|
||||
version "7.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.42.0.tgz#7662faef398032f253fe5b174860e1ec5f76ddd5"
|
||||
integrity sha512-DOGK+vuSZq5lTiqVU6wVay0AUMjtSPZu25gzLIXntfoqw36CLUswP7ew61+Tas6tpXDdf4lR3uxJRwySiQLopw==
|
||||
"@sentry/core@7.45.0":
|
||||
version "7.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.45.0.tgz#87fdb283c211f145e508cc8ff89dabdf2fbcfc39"
|
||||
integrity sha512-xJfdTS4lRmHvZI/A5MazdnKhBJFkisKu6G9EGNLlZLre+6W4PH5sb7QX4+xoBdqG7v10Jvdia112vi762ojO2w==
|
||||
dependencies:
|
||||
"@sentry/browser" "7.42.0"
|
||||
"@sentry/types" "7.42.0"
|
||||
"@sentry/utils" "7.42.0"
|
||||
"@sentry/types" "7.45.0"
|
||||
"@sentry/utils" "7.45.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/react@^7.45.0":
|
||||
version "7.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.45.0.tgz#9d3634b7b93bc1fa0872cdec49c1fbacd4ef2506"
|
||||
integrity sha512-Dbz85nfvMUikbLHUuIt6fBNPmTvThFn+rWB5KS1NIOJifyWAdpIU3X7yCUJE5xhsUObNLiHlNJlqhaQI4nR1bQ==
|
||||
dependencies:
|
||||
"@sentry/browser" "7.45.0"
|
||||
"@sentry/types" "7.45.0"
|
||||
"@sentry/utils" "7.45.0"
|
||||
hoist-non-react-statics "^3.3.2"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/replay@7.42.0":
|
||||
version "7.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.42.0.tgz#03be2bdab35e0f2d4e415a770c23d50dba8d73b5"
|
||||
integrity sha512-81HQm20hrW0+0eZ5sZf8KsSekkAlI0/u/M+9ZmOn2bHpGihqAM/O/lrXhTzaRXdpX/9NSwSCGY9k7LIRNMKaEQ==
|
||||
"@sentry/replay@7.45.0":
|
||||
version "7.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.45.0.tgz#1da15e8c419bb77ec7475c7b1879d11f17edad20"
|
||||
integrity sha512-smM7FIcFIyKu30BqCl8BzLo1gH/z9WwXdGX6V0fNvHab9fJZ09+xjFn+LmIyo6N8H8jjwsup0+yQ12kiF/ZsEw==
|
||||
dependencies:
|
||||
"@sentry/core" "7.42.0"
|
||||
"@sentry/types" "7.42.0"
|
||||
"@sentry/utils" "7.42.0"
|
||||
"@sentry/core" "7.45.0"
|
||||
"@sentry/types" "7.45.0"
|
||||
"@sentry/utils" "7.45.0"
|
||||
|
||||
"@sentry/tracing@^7.40.0":
|
||||
version "7.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.42.0.tgz#bcdac21e1cb5f786465e6252625bd4bf0736e631"
|
||||
integrity sha512-0veGu3Ntweuj1pwWrJIFHmVdow4yufCreGIhsNDyrclwOjaTY3uI8iA6N62+hhtxOvqv+xueB98K1DvT5liPCQ==
|
||||
"@sentry/tracing@^7.45.0":
|
||||
version "7.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.45.0.tgz#77fe1075b3fdfd5026bf8d816a855bbe992b64a3"
|
||||
integrity sha512-FsoFmZPzTBGvWeJH73NxSF1ot61Zw3aIZo5XolengiKnRmcrQOFxebtMKBiZ61QBRYGqsm5uT7QB7zITU6Ikgg==
|
||||
dependencies:
|
||||
"@sentry/core" "7.42.0"
|
||||
"@sentry/types" "7.42.0"
|
||||
"@sentry/utils" "7.42.0"
|
||||
tslib "^1.9.3"
|
||||
"@sentry-internal/tracing" "7.45.0"
|
||||
|
||||
"@sentry/types@7.42.0":
|
||||
version "7.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.42.0.tgz#e5019cd41a8c4a98c296e2ff28d6adab4b2eb14e"
|
||||
integrity sha512-Ga0xaBIR/peuXQ88hI9a5TNY3GLNoH8jpsgPaAjAtRHkLsTx0y3AR+PrD7pUysza9QjvG+Qux01DRvLgaNKOHA==
|
||||
"@sentry/types@7.45.0":
|
||||
version "7.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.45.0.tgz#b5e2db7a421f6090398565b0a72fb3bbdc94233a"
|
||||
integrity sha512-iFt7msfUK8LCodFF3RKUyaxy9tJv/gpWhzxUFyNxtuVwlpmd+q6mtsFGn8Af3pbpm8A+MKyz1ebMwXj0PQqknw==
|
||||
|
||||
"@sentry/utils@7.42.0":
|
||||
version "7.42.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.42.0.tgz#fcffd0404836cb56975fbef9e889a42cc55de596"
|
||||
integrity sha512-cBiDZVipC+is+IVgsTQLJyZWUZQxlLZ9GarNT+XZOZ5BFh0acFtz88hO6+S7vGmhcx2aCvsdC9yb2Yf+BphK6Q==
|
||||
"@sentry/utils@7.45.0":
|
||||
version "7.45.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.45.0.tgz#e13e075098578557ec3a0decf735cbad6a26ce63"
|
||||
integrity sha512-aTY7qqtNUudd09SH5DVSKMm3iQ6ZeWufduc0I9bPZe6UMM09BDc4KmjmrzRkdQ+VaOmHo7+v+HZKQk5f+AbuTQ==
|
||||
dependencies:
|
||||
"@sentry/types" "7.42.0"
|
||||
"@sentry/types" "7.45.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sinonjs/commons@^1.7.0":
|
||||
@@ -4530,10 +4562,10 @@
|
||||
"@typescript-eslint/types" "5.47.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@uniswap/analytics-events@^2.7.0":
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-2.7.0.tgz#bfee1519a0d3d999054a009301a3b95d87d65ac8"
|
||||
integrity sha512-+v8NcV0+FvCpBasfq/R9RQ03IYyAVJiHBMwMifOrrq5bDctEb1j340YprQ/dd/A/kSd9scqBaiiQ+L96gEtPEQ==
|
||||
"@uniswap/analytics-events@^2.8.0":
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-2.8.0.tgz#651eb08913b1a47c79814f0536b46cd91a6102d3"
|
||||
integrity sha512-unaNUxPYGoaPsPS+j6UuQRnxikha6Dr9Knv9jBVY/vIj03f8AOisVM7Zw9493QZP14lq2guARddkN3NzlttuwQ==
|
||||
|
||||
"@uniswap/analytics@^1.3.1":
|
||||
version "1.3.1"
|
||||
@@ -5913,6 +5945,13 @@ anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.1, anymatch@~3.1.2:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
append-transform@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12"
|
||||
integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==
|
||||
dependencies:
|
||||
default-require-extensions "^3.0.0"
|
||||
|
||||
aproba@^1.1.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
|
||||
@@ -5923,6 +5962,11 @@ arch@^2.1.0, arch@^2.2.0:
|
||||
resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
|
||||
integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
|
||||
|
||||
archy@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40"
|
||||
integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==
|
||||
|
||||
arg@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/arg/-/arg-2.0.0.tgz"
|
||||
@@ -6303,15 +6347,15 @@ babel-plugin-emotion@^10.0.27:
|
||||
find-root "^1.1.0"
|
||||
source-map "^0.5.7"
|
||||
|
||||
babel-plugin-istanbul@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765"
|
||||
integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==
|
||||
babel-plugin-istanbul@^6.0.0, babel-plugin-istanbul@^6.1.1:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73"
|
||||
integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
"@istanbuljs/load-nyc-config" "^1.0.0"
|
||||
"@istanbuljs/schema" "^0.1.2"
|
||||
istanbul-lib-instrument "^4.0.0"
|
||||
istanbul-lib-instrument "^5.0.4"
|
||||
test-exclude "^6.0.0"
|
||||
|
||||
babel-plugin-jest-hoist@^26.6.2:
|
||||
@@ -6627,6 +6671,11 @@ blob-util@^2.0.2:
|
||||
resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb"
|
||||
integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==
|
||||
|
||||
bluebird@3.7.1:
|
||||
version "3.7.1"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de"
|
||||
integrity sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==
|
||||
|
||||
bluebird@^3.5.5, bluebird@^3.7.2:
|
||||
version "3.7.2"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
||||
@@ -7072,6 +7121,16 @@ cachedir@^2.3.0:
|
||||
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8"
|
||||
integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==
|
||||
|
||||
caching-transform@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f"
|
||||
integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==
|
||||
dependencies:
|
||||
hasha "^5.0.0"
|
||||
make-dir "^3.0.0"
|
||||
package-hash "^4.0.0"
|
||||
write-file-atomic "^3.0.0"
|
||||
|
||||
call-bind@^1.0.0, call-bind@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
|
||||
@@ -7191,18 +7250,18 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1, chalk@^2.4.2:
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz"
|
||||
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
|
||||
chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||
chalk@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz"
|
||||
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
@@ -8660,10 +8719,10 @@ date-fns@^2.16.1:
|
||||
resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.21.3.tgz"
|
||||
integrity sha512-HeYdzCaFflc1i4tGbj7JKMjM4cKGYoyxwcIIkHzNgCkX8xXDNJDZXgDDVchIWpN4eQc3lH37WarduXFZJOtxfw==
|
||||
|
||||
dayjs@^1.10.4:
|
||||
version "1.10.6"
|
||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63"
|
||||
integrity sha512-AztC/IOW4L1Q41A86phW5Thhcrco3xuAA+YX/BLpLWWjRcTj5TOt/QImBLmCKlrF7u7k47arTnOyL6GnbG8Hvw==
|
||||
dayjs@1.10.7, dayjs@^1.10.4:
|
||||
version "1.10.7"
|
||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
|
||||
integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
|
||||
|
||||
debounce@^1.2.0:
|
||||
version "1.2.1"
|
||||
@@ -8677,7 +8736,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
||||
debug@4, debug@4.3.4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
@@ -8756,6 +8815,13 @@ default-gateway@^4.2.0:
|
||||
execa "^1.0.0"
|
||||
ip-regex "^2.1.0"
|
||||
|
||||
default-require-extensions@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.1.tgz#bfae00feeaeada68c2ae256c62540f60b80625bd"
|
||||
integrity sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==
|
||||
dependencies:
|
||||
strip-bom "^4.0.0"
|
||||
|
||||
defaults@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz"
|
||||
@@ -9320,6 +9386,11 @@ es5-ext@^0.10.35, es5-ext@^0.10.50:
|
||||
es6-symbol "~3.1.3"
|
||||
next-tick "~1.0.0"
|
||||
|
||||
es6-error@^4.0.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
|
||||
integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
|
||||
|
||||
es6-iterator@2.0.3, es6-iterator@~2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
|
||||
@@ -10285,7 +10356,7 @@ find-cache-dir@^2.1.0:
|
||||
make-dir "^2.0.0"
|
||||
pkg-dir "^3.0.0"
|
||||
|
||||
find-cache-dir@^3.3.1:
|
||||
find-cache-dir@^3.2.0, find-cache-dir@^3.3.1:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b"
|
||||
integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==
|
||||
@@ -10392,6 +10463,14 @@ for-in@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
|
||||
|
||||
foreground-child@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53"
|
||||
integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==
|
||||
dependencies:
|
||||
cross-spawn "^7.0.0"
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
forever-agent@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
@@ -10466,6 +10545,11 @@ from2@^2.1.0:
|
||||
inherits "^2.0.1"
|
||||
readable-stream "^2.0.0"
|
||||
|
||||
fromentries@^1.2.0:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a"
|
||||
integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==
|
||||
|
||||
fs-extra@^7.0.0, fs-extra@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
|
||||
@@ -10760,6 +10844,18 @@ globby@11.0.1:
|
||||
merge2 "^1.3.0"
|
||||
slash "^3.0.0"
|
||||
|
||||
globby@11.0.4:
|
||||
version "11.0.4"
|
||||
resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5"
|
||||
integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==
|
||||
dependencies:
|
||||
array-union "^2.1.0"
|
||||
dir-glob "^3.0.1"
|
||||
fast-glob "^3.1.1"
|
||||
ignore "^5.1.4"
|
||||
merge2 "^1.3.0"
|
||||
slash "^3.0.0"
|
||||
|
||||
globby@^11.0.3, globby@^11.1.0:
|
||||
version "11.1.0"
|
||||
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
|
||||
@@ -10990,6 +11086,14 @@ hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3, hash.js@^1.1.7:
|
||||
inherits "^2.0.3"
|
||||
minimalistic-assert "^1.0.1"
|
||||
|
||||
hasha@^5.0.0:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1"
|
||||
integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==
|
||||
dependencies:
|
||||
is-stream "^2.0.0"
|
||||
type-fest "^0.8.0"
|
||||
|
||||
he@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
@@ -12045,11 +12149,23 @@ isstream@~0.1.2:
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
|
||||
|
||||
istanbul-lib-coverage@^3.0.0:
|
||||
istanbul-lib-coverage@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec"
|
||||
integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==
|
||||
|
||||
istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
|
||||
integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==
|
||||
|
||||
istanbul-lib-hook@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6"
|
||||
integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==
|
||||
dependencies:
|
||||
append-transform "^2.0.0"
|
||||
|
||||
istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d"
|
||||
@@ -12060,6 +12176,29 @@ istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3:
|
||||
istanbul-lib-coverage "^3.0.0"
|
||||
semver "^6.3.0"
|
||||
|
||||
istanbul-lib-instrument@^5.0.4:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d"
|
||||
integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==
|
||||
dependencies:
|
||||
"@babel/core" "^7.12.3"
|
||||
"@babel/parser" "^7.14.7"
|
||||
"@istanbuljs/schema" "^0.1.2"
|
||||
istanbul-lib-coverage "^3.2.0"
|
||||
semver "^6.3.0"
|
||||
|
||||
istanbul-lib-processinfo@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz#366d454cd0dcb7eb6e0e419378e60072c8626169"
|
||||
integrity sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==
|
||||
dependencies:
|
||||
archy "^1.0.0"
|
||||
cross-spawn "^7.0.3"
|
||||
istanbul-lib-coverage "^3.2.0"
|
||||
p-map "^3.0.0"
|
||||
rimraf "^3.0.0"
|
||||
uuid "^8.3.2"
|
||||
|
||||
istanbul-lib-report@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6"
|
||||
@@ -12610,7 +12749,7 @@ js-sha3@0.8.0, js-sha3@^0.8.0:
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||
|
||||
js-yaml@^3.13.1:
|
||||
js-yaml@3.14.1, js-yaml@^3.13.1:
|
||||
version "3.14.1"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
|
||||
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
|
||||
@@ -13056,6 +13195,11 @@ lodash.clonedeep@^4.5.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
||||
|
||||
lodash.flattendeep@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
|
||||
integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==
|
||||
|
||||
lodash.get@^4, lodash.get@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz"
|
||||
@@ -13897,6 +14041,13 @@ node-notifier@^8.0.0:
|
||||
uuid "^8.3.0"
|
||||
which "^2.0.2"
|
||||
|
||||
node-preload@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301"
|
||||
integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==
|
||||
dependencies:
|
||||
process-on-spawn "^1.0.0"
|
||||
|
||||
node-releases@^1.1.61:
|
||||
version "1.1.76"
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.76.tgz#df245b062b0cafbd5282ab6792f7dccc2d97f36e"
|
||||
@@ -14020,6 +14171,39 @@ nwsapi@^2.2.0:
|
||||
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
|
||||
integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
|
||||
|
||||
nyc@15.1.0:
|
||||
version "15.1.0"
|
||||
resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02"
|
||||
integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==
|
||||
dependencies:
|
||||
"@istanbuljs/load-nyc-config" "^1.0.0"
|
||||
"@istanbuljs/schema" "^0.1.2"
|
||||
caching-transform "^4.0.0"
|
||||
convert-source-map "^1.7.0"
|
||||
decamelize "^1.2.0"
|
||||
find-cache-dir "^3.2.0"
|
||||
find-up "^4.1.0"
|
||||
foreground-child "^2.0.0"
|
||||
get-package-type "^0.1.0"
|
||||
glob "^7.1.6"
|
||||
istanbul-lib-coverage "^3.0.0"
|
||||
istanbul-lib-hook "^3.0.0"
|
||||
istanbul-lib-instrument "^4.0.0"
|
||||
istanbul-lib-processinfo "^2.0.2"
|
||||
istanbul-lib-report "^3.0.0"
|
||||
istanbul-lib-source-maps "^4.0.0"
|
||||
istanbul-reports "^3.0.2"
|
||||
make-dir "^3.0.0"
|
||||
node-preload "^0.2.1"
|
||||
p-map "^3.0.0"
|
||||
process-on-spawn "^1.0.0"
|
||||
resolve-from "^5.0.0"
|
||||
rimraf "^3.0.0"
|
||||
signal-exit "^3.0.2"
|
||||
spawn-wrap "^2.0.0"
|
||||
test-exclude "^6.0.0"
|
||||
yargs "^15.0.2"
|
||||
|
||||
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
@@ -14327,6 +14511,13 @@ p-map@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
|
||||
integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
|
||||
|
||||
p-map@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d"
|
||||
integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==
|
||||
dependencies:
|
||||
aggregate-error "^3.0.0"
|
||||
|
||||
p-map@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
|
||||
@@ -14346,6 +14537,16 @@ p-try@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||
|
||||
package-hash@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506"
|
||||
integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==
|
||||
dependencies:
|
||||
graceful-fs "^4.1.15"
|
||||
hasha "^5.0.0"
|
||||
lodash.flattendeep "^4.4.0"
|
||||
release-zalgo "^1.0.0"
|
||||
|
||||
pako@^1.0.5, pako@~1.0.5:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz"
|
||||
@@ -15484,6 +15685,13 @@ process-nextick-args@~2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
||||
|
||||
process-on-spawn@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.0.0.tgz#95b05a23073d30a17acfdc92a440efd2baefdc93"
|
||||
integrity sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==
|
||||
dependencies:
|
||||
fromentries "^1.2.0"
|
||||
|
||||
process@^0.11.10:
|
||||
version "0.11.10"
|
||||
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
||||
@@ -16357,6 +16565,13 @@ relay-runtime@12.0.0:
|
||||
fbjs "^3.0.0"
|
||||
invariant "^2.2.4"
|
||||
|
||||
release-zalgo@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730"
|
||||
integrity sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==
|
||||
dependencies:
|
||||
es6-error "^4.0.1"
|
||||
|
||||
remark-parse@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz"
|
||||
@@ -17285,6 +17500,18 @@ sourcemap-codec@^1.4.4:
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
||||
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
||||
|
||||
spawn-wrap@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e"
|
||||
integrity sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==
|
||||
dependencies:
|
||||
foreground-child "^2.0.0"
|
||||
is-windows "^1.0.2"
|
||||
make-dir "^3.0.0"
|
||||
rimraf "^3.0.0"
|
||||
signal-exit "^3.0.2"
|
||||
which "^2.0.1"
|
||||
|
||||
spdx-correct@^3.0.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
|
||||
@@ -18320,7 +18547,7 @@ type-fest@^0.6.0:
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
|
||||
integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==
|
||||
|
||||
type-fest@^0.8.1:
|
||||
type-fest@^0.8.0, type-fest@^0.8.1:
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||
@@ -19587,7 +19814,7 @@ yargs@^13.2.4, yargs@^13.3.2:
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^13.1.2"
|
||||
|
||||
yargs@^15.3.1, yargs@^15.4.1:
|
||||
yargs@^15.0.2, yargs@^15.3.1, yargs@^15.4.1:
|
||||
version "15.4.1"
|
||||
resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz"
|
||||
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
|
||||
|
||||
Reference in New Issue
Block a user