Compare commits

...

13 Commits

Author SHA1 Message Date
cartcrom
4eaf16b624 fix: injection detection bug (#6276)
* fix: use functions to check injection status rather than static vars

* fix: unnused field

* fix: don't prompt mm install for generics

* fix: generic injector function

* fix: display name for MM on cb browser

* fix: re-add ios mobile check for uniswap wallet

* fix: reword comment

* fix: refactor delayed-injection test

* feat: added comments

* fix: revert to minimal changes

* fix: update tests
2023-03-30 17:50:20 -04:00
Connor McEwen
857e2915ab fix: put environment in the wrong place (#6277)
* fix: put environment in the wrong place

* move to proper step
2023-03-30 16:54:45 -04:00
Connor McEwen
7410c81b42 chore: update workflow release env (#6275) 2023-03-30 15:43:18 -04:00
eddie
fb05439d32 fix: mini portfolio layout fixes (#6260)
* fix: mini portfolio layout fixes

* feat: refactor MP drawer CSS
2023-03-30 12:07:02 -07:00
eddie
fb7eade70b fix: l2 icon borders in MP (#6254)
* fix: l2 icon borders in MP

* fix: bool logic

* fix: comments and add test

* fix: change variable name

* fix: split l2 icon into two components
2023-03-30 11:59:46 -07:00
eddie
bd2b2c487a fix: close MP drawer on nft nav (#6251)
* fix: close MP drawer on nft nav

* fix: make callbacks optional, rename props

* fix: improve card API

* fix: add e2e test
2023-03-29 15:08:30 -07:00
eddie
2f004ed1d9 fix: remove deprecated default imports from zustand (#6270)
* fix: replace default imports from zustand

* fix: add eslint rule

* fix: typo

o
2023-03-29 13:25:30 -07:00
Zach Pomerantz
db257c73f2 fix: improve chain id error (#6266)
* build: upgrade sentry

* fix: improve chain id error
2023-03-29 12:38:33 -07:00
Zach Pomerantz
7c37b9d00e build: upgrade sentry (#6264)
* build: upgrade sentry

* chore: comment nits
2023-03-29 11:05:13 -07:00
Connor McEwen
7688c527f0 chore: update codecov yaml (#6262)
chore: set patch coverage to 80%, lower tolerance threshold, remove unused keys
2023-03-29 11:31:27 -04:00
eddie
06dd41a9cd fix: getCurrency crash when token not found (#6263)
* fix: getCurrency crash when token not found

* fix: comments and add test

* fix: remove extra whitespace in unit test

* fix: make e2e test pass
2023-03-28 18:44:48 -07:00
eddie
850fec40a9 fix: remove price tag from nft cards in the side drawer (#6252)
* fix: remove price tag from nft cards in the side drawer

* fix: decouple price from display logic

* fix: missing file chaneg

* fix: add tests
2023-03-28 09:26:32 -07:00
yyip-dev
3c7eabc3d8 docs: Update PR template (#6235)
* Update PR template

* Add screen capture section and simplify descriptions

* Link out to PR title conventions & remove monitoring section
2023-03-28 11:31:11 -04:00
53 changed files with 1020 additions and 383 deletions

View File

@@ -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.',
},
],
},
],
},
},
],
}

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -14,9 +14,8 @@ coverage:
project:
default:
target: auto
threshold: 5%
threshold: 1%
if_ci_failed: error
patch:
default:
enabled: no
if_not_found: success
target: 80%

View File

@@ -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')
})
})

View File

@@ -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')

View File

@@ -132,8 +132,8 @@
"@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.8.0",

View File

@@ -60,7 +60,7 @@ 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>
)
}
@@ -71,7 +71,7 @@ const MainWalletIcon = ({ connection, size }: { connection: Connection; size: nu
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 <Unicon address={account} size={size} />

View File

@@ -36,7 +36,7 @@ import MiniPortfolio from './MiniPortfolio'
import { portfolioFadeInAnimation } from './MiniPortfolio/PortfolioRow'
const AuthenticatedHeaderWrapper = styled.div`
padding: 14px 12px 16px 16px;
padding: 20px 16px;
display: flex;
flex-direction: column;
flex: 1;

View File

@@ -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',
})
})
})

View File

@@ -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,8 +122,10 @@ 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] }
}

View File

@@ -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
}

View File

@@ -5,6 +5,7 @@ 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'
@@ -50,8 +51,8 @@ export function NFT({
const trace = useTrace()
const navigateToNFTDetails = () => {
navigate(`/nfts/asset/${asset.asset_contract.address}/${asset.tokenId}`)
toggleWalletDrawer()
navigate(detailsHref(asset))
}
return (
@@ -62,10 +63,7 @@ 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,
@@ -77,6 +75,7 @@ export function NFT({
}
mediaShouldBePlaying={mediaShouldBePlaying}
setCurrentTokenPlayingMedia={setCurrentTokenPlayingMedia}
testId="mini-portfolio-nft"
/>
<NFTDetails asset={asset} />
</NFTContainer>

View File

@@ -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()
})
})

View File

@@ -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>

View File

@@ -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>
`;

View File

@@ -55,6 +55,7 @@ const PageWrapper = styled.div`
interface Page {
title: React.ReactNode
key: string
component: ({ account }: { account: string }) => JSX.Element
loggingElementName: string
}
@@ -62,13 +63,25 @@ interface Page {
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>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,
},
@@ -83,7 +96,7 @@ function MiniPortfolio({ account }: { account: string }) {
return (
<Wrapper>
<Nav>
{Pages.map(({ title, loggingElementName }, index) => {
{Pages.map(({ title, loggingElementName, key }, index) => {
if (shouldDisableNFTRoutes && loggingElementName.includes('nft')) return null
return (
<TraceEvent
@@ -93,6 +106,7 @@ function MiniPortfolio({ account }: { account: string }) {
key={index}
>
<NavItem
data-testid={`mini-portfolio-nav-${key}`}
onClick={() => setCurrentPage(index)}
active={currentPage === index}
key={`Mini Portfolio page ${index}`}

View File

@@ -76,15 +76,21 @@ const WalletDropdownScrollWrapper = styled.div`
border-radius: 12px;
`
const WalletDropdownWrapper = styled.div<{ open: boolean }>`
position: fixed;
top: ${DRAWER_MARGIN};
right: ${({ open }) => (open ? DRAWER_MARGIN : '-' + DRAWER_WIDTH)};
z-index: ${Z_INDEX.fixed};
overflow: hidden;
const Wrapper = styled.div`
display: flex;
flex-direction: row;
height: calc(100% - 2 * ${DRAWER_MARGIN});
overflow: hidden;
position: fixed;
right: ${DRAWER_MARGIN};
top: ${DRAWER_MARGIN};
z-index: ${Z_INDEX.fixed};
`
const WalletDropdownWrapper = styled.div<{ open: boolean }>`
margin-right: ${({ open }) => (open ? 0 : '-' + DRAWER_WIDTH)};
height: 100%;
overflow: hidden;
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
z-index: ${Z_INDEX.modal};
@@ -101,7 +107,7 @@ const WalletDropdownWrapper = styled.div<{ open: boolean }>`
}
@media screen and (min-width: 1440px) {
right: ${({ open }) => (open ? DRAWER_MARGIN : '-' + DRAWER_WIDTH_XL)};
margin-right: ${({ open }) => (open ? 0 : '-' + DRAWER_WIDTH_XL)};
width: ${DRAWER_WIDTH_XL};
}
@@ -112,7 +118,7 @@ const WalletDropdownWrapper = styled.div<{ open: boolean }>`
border: ${({ theme }) => `1px solid ${theme.backgroundOutline}`};
box-shadow: ${({ theme }) => theme.deepShadow};
transition: right ${({ theme }) => theme.transition.duration.medium},
transition: margin-right ${({ theme }) => theme.transition.duration.medium},
bottom ${({ theme }) => theme.transition.duration.medium};
`
@@ -123,11 +129,7 @@ const CloseIcon = styled(ChevronsRight).attrs({ size: 24 })`
const CloseDrawer = styled.div`
${ClickableStyle}
cursor: pointer;
height: calc(100% - 2 * ${DRAWER_MARGIN});
position: fixed;
right: calc(${DRAWER_MARGIN} + ${DRAWER_WIDTH} - ${DRAWER_OFFSET});
top: 4px;
z-index: ${Z_INDEX.dropdown};
height: 100%;
// When the drawer is not hovered, the icon should be 18px from the edge of the sidebar.
padding: 24px calc(18px + ${DRAWER_OFFSET}) 24px 14px;
border-radius: 20px 0 0 20px;
@@ -140,9 +142,6 @@ const CloseDrawer = styled.div`
@media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) {
display: none;
}
@media screen and (min-width: 1440px) {
right: calc(${DRAWER_MARGIN} + ${DRAWER_WIDTH_XL} - ${DRAWER_OFFSET});
}
`
function WalletDropdown() {
@@ -187,7 +186,7 @@ function WalletDropdown() {
}, [walletDrawerOpen, toggleWalletDrawer])
return (
<>
<Wrapper>
{walletDrawerOpen && (
<TraceEvent
events={[BrowserEvent.onClick]}
@@ -206,7 +205,7 @@ function WalletDropdown() {
<DefaultMenu />
</WalletDropdownScrollWrapper>
</WalletDropdownWrapper>
</>
</Wrapper>
)
}

View File

@@ -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 />}

View File

@@ -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

View File

@@ -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}>

View File

@@ -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)
})
})

View File

@@ -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
}
}
}, [])
}

View File

@@ -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' | 'isLedgerConnect'
const allNonMetamaskFlags: NonMetaMaskFlag[] = ['isRabby', 'isBraveWallet', 'isTrustWallet', 'isLedgerConnect']
export const isMetaMaskWallet = Boolean(
window.ethereum?.isMetaMask && !allNonMetamaskFlags.some((flag) => window.ethereum?.[flag])
)
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 {

View File

@@ -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 } = {}

View 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()
})
})

View File

@@ -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>
`;

View File

@@ -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>
)
}

View File

@@ -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>

View File

@@ -13,11 +13,11 @@ interface NftCardProps {
display: NftCardDisplayProps
isSelected: boolean
isDisabled: boolean
selectAsset: () => void
unselectAsset: () => void
onClick?: () => void
selectAsset?: () => void
unselectAsset?: () => void
onButtonClick?: () => void
onCardClick?: () => void
sendAnalyticsEvent?: () => void
doNotLinkToDetails?: boolean
mediaShouldBePlaying: boolean
uniformAspectRatio?: UniformAspectRatio
setUniformAspectRatio?: (uniformAspectRatio: UniformAspectRatio) => void
@@ -38,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,
@@ -45,9 +51,9 @@ export const NftCard = ({
selectAsset,
unselectAsset,
isDisabled,
onClick,
onButtonClick,
onCardClick,
sendAnalyticsEvent,
doNotLinkToDetails = false,
mediaShouldBePlaying,
uniformAspectRatio = UniformAspectRatios.square,
setUniformAspectRatio,
@@ -57,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,
@@ -77,16 +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}

View File

@@ -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]
)

View File

@@ -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}
/>
)
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -1,4 +1,4 @@
import create from 'zustand'
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
interface State {

View File

@@ -1,4 +1,4 @@
import create from 'zustand'
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
interface State {

View File

@@ -1,4 +1,4 @@
import create from 'zustand'
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
interface NFTClaim {

View File

@@ -1,4 +1,4 @@
import create from 'zustand'
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
export type MarketplaceOption = { name: string; icon: string }

View File

@@ -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 {

View File

@@ -1,4 +1,4 @@
import create from 'zustand'
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
import { OpenSeaAsset } from '../types'

View File

@@ -1,4 +1,4 @@
import create from 'zustand'
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
interface PriceRangeProps {

View File

@@ -1,4 +1,4 @@
import create from 'zustand'
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
import { ProfilePageStateType } from '../types'

View File

@@ -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'

View File

@@ -1,4 +1,4 @@
import create from 'zustand'
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
import { ListingMarket, WalletAsset } from '../types'

View File

@@ -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'

View File

@@ -1,4 +1,4 @@
import create from 'zustand'
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
import { GenieAsset } from '../types'

View File

@@ -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 {

View File

@@ -1,4 +1,4 @@
import create from 'zustand'
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
interface traitOpen {

View File

@@ -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

View File

@@ -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'

View File

@@ -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, {

View File

@@ -3190,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":