Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(wallet): add watch only label and alerts to unowned NFTs #24569

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/brave_wallet/browser/brave_wallet_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ inline constexpr char kSimpleHashBraveProxyUrl[] =
inline constexpr size_t kSimpleHashMaxBatchSize = 50;

inline constexpr webui::LocalizedString kLocalizedStrings[] = {
{"braveWalletWatchOnly", IDS_BRAVE_WALLET_WATCH_ONLY},
{"braveWalletNftSymbolFieldExplanation",
IDS_BRAVE_WALLET_NFT_SYMBOL_FIELD_EXPLANATION},
{"braveWalletNftNameFieldExplanation",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
getTokenCollectionName,
getTokensWithBalanceForAccounts,
groupSpamAndNonSpamNfts,
isTokenWatchOnly,
searchNftCollectionsAndGetTotalNftsFound,
searchNfts
} from '../../../../../utils/asset-utils'
Expand Down Expand Up @@ -192,22 +193,11 @@ export const Nfts = ({
const userNonSpamNftIds =
userTokensRegistry?.nonSpamTokenIds ?? emptyTokenIdsList

const shouldFetchSpamNftBalances =
selectedTab === 'hidden' &&
!isLoadingSpamNfts &&
hideUnownedNfts &&
accounts.length > 0 &&
networks.length > 0

const { data: spamTokenBalancesRegistry } = useBalancesFetcher(
shouldFetchSpamNftBalances
? {
accounts,
networks,
isSpamRegistry: true
}
: skipToken
)
const { data: spamTokenBalancesRegistry } = useBalancesFetcher({
accounts,
networks,
isSpamRegistry: true
})

// mutations
const [setNftDiscovery] = useSetNftDiscoveryEnabledMutation()
Expand Down Expand Up @@ -411,8 +401,7 @@ export const Nfts = ({
(groupNftsByCollection &&
isFetchingLatestAssetIdsByCollectionNameRegistry) ||
(selectedTab === 'hidden' &&
(isLoadingSpamNfts ||
(shouldFetchSpamNftBalances && !spamTokenBalancesRegistry)))
(isLoadingSpamNfts || !spamTokenBalancesRegistry))

// methods
const onSearchValueChange = React.useCallback(
Expand Down Expand Up @@ -602,6 +591,12 @@ export const Nfts = ({
onSelectAsset={onSelectAsset}
isTokenHidden={isHidden}
isTokenSpam={isSpam}
isWatchOnly={isTokenWatchOnly(
nft,
allAccounts,
tokenBalancesRegistry,
spamTokenBalancesRegistry
)}
/>
)
})}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) 2024 The Brave Authors. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

import * as React from 'react'

// components
import {
WalletPanelStory //
} from '../../../../../../stories/wrappers/wallet-panel-story-wrapper'
import { PanelWrapper } from '../../../../../../panel/style'
import { NFTGridViewItem } from './nft-grid-view-item'

// mocks
import {
mockMoonCatNFT //
} from '../../../../../../stories/mock-data/mock-asset-options'

export const _NftGridViewItem = {
render: () => {
return (
<WalletPanelStory>
<PanelWrapper>
<NFTGridViewItem
isTokenHidden={false}
isTokenSpam={false}
onSelectAsset={() => undefined}
token={mockMoonCatNFT}
isWatchOnly={false}
/>
</PanelWrapper>
</WalletPanelStory>
)
}
}
export const _JunkAndWatchOnlyNftGridViewItem = {
render: () => {
return (
<WalletPanelStory>
<PanelWrapper>
<NFTGridViewItem
isTokenHidden={false}
isTokenSpam={true}
onSelectAsset={() => undefined}
token={mockMoonCatNFT}
isWatchOnly={true}
/>
</PanelWrapper>
</WalletPanelStory>
)
}
}
export default {
component: NFTGridViewItem
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ import {
NFTSymbol,
MoreButton,
JunkMarker,
JunkIcon
JunkIcon,
WatchOnlyMarker
} from './style'
import { Row } from '../../../../../shared/style'

Expand All @@ -55,10 +56,16 @@ interface Props {
isTokenHidden: boolean
isTokenSpam: boolean
onSelectAsset: (token: BraveWallet.BlockchainToken) => void
isWatchOnly?: boolean
}

export const NFTGridViewItem = (props: Props) => {
const { token, isTokenHidden, isTokenSpam, onSelectAsset } = props
export const NFTGridViewItem = ({
token,
isTokenHidden,
isTokenSpam,
onSelectAsset,
isWatchOnly
}: Props) => {
const tokenImageURL = stripERC20TokenImageURL(token.logo)
const [showRemoveNftModal, setShowRemoveNftModal] =
React.useState<boolean>(false)
Expand Down Expand Up @@ -165,6 +172,13 @@ export const NFTGridViewItem = (props: Props) => {
<JunkIcon />
</JunkMarker>
)}
{isWatchOnly && (
<WatchOnlyMarker>
{
getLocale('braveWalletWatchOnly') //
}
</WatchOnlyMarker>
)}
<IconWrapper>
<DecoratedNftIcon
icon={tokenImageURL}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export const MoreIcon = styled(Icon).attrs({

export const JunkMarker = styled.div`
display: inline-flex;
height: 20px;
min-height: 20px;
padding: 0px ${leo.spacing.s};
align-items: center;
gap: ${leo.spacing.s};
Expand All @@ -141,6 +141,13 @@ export const JunkMarker = styled.div`
z-index: 2;
`

export const WatchOnlyMarker = styled(JunkMarker)`
background-color: ${leo.color.gray[20]};
color: ${leo.color.gray[60]};
left: unset;
right: 12px;
`

export const JunkIcon = styled(Icon).attrs({
name: 'warning-triangle-outline'
})`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
import * as React from 'react'
import { useHistory } from 'react-router'
import { skipToken } from '@reduxjs/toolkit/dist/query'
import Alert from '@brave/leo/react/alert'

// types
import { BraveWallet, AccountPageTabs } from '../../../constants/types'

// hooks
import useExplorer from '../../../common/hooks/explorer'
import useBalancesFetcher from '../../../common/hooks/use-balances-fetcher'

// utils
import Amount from '../../../utils/amount'
Expand All @@ -21,12 +24,14 @@ import {
import { reduceAddress } from '../../../utils/reduce-address'
import { getLocale } from '../../../../common/locale'
import { makeAccountRoute } from '../../../utils/routes-utils'
import { isTokenWatchOnly } from '../../../utils/asset-utils'

// queries & mutations
import {
useGetNftMetadataQuery,
useUpdateUserTokenMutation,
useUpdateUserTokenMutation
} from '../../../common/slices/api.slice'
import { useAccountsQuery } from '../../../common/slices/api.slice.extra'

// components
import { Skeleton } from '../../../components/shared/loading-skeleton/styles'
Expand Down Expand Up @@ -64,7 +69,7 @@ import {
IconWrapper,
NetworkIconWrapper
} from './nft-screen.styles'
import { Row } from '../../../components/shared/style'
import { Row, VerticalSpace } from '../../../components/shared/style'

interface Props {
selectedAsset: BraveWallet.BlockchainToken
Expand Down Expand Up @@ -100,6 +105,28 @@ export const NftScreen = (props: Props) => {
skip: !selectedAsset
})

const { accounts } = useAccountsQuery()

const { data: tokenBalancesRegistry } = useBalancesFetcher(
tokenNetwork
? {
accounts,
networks: [tokenNetwork],
isSpamRegistry: false
}
: skipToken
)

const { data: spamTokenBalancesRegistry } = useBalancesFetcher(
tokenNetwork
? {
accounts,
networks: [tokenNetwork],
isSpamRegistry: true
}
: skipToken
)

// mutations
const [updateUserToken] = useUpdateUserTokenMutation()

Expand Down Expand Up @@ -178,6 +205,22 @@ export const NftScreen = (props: Props) => {

return (
<StyledWrapper>
{isTokenWatchOnly(
selectedAsset,
accounts,
tokenBalancesRegistry,
spamTokenBalancesRegistry
) && (
<>
<Alert
mode='simple'
type='info'
>
{getLocale('braveWalletUnownedNftAlert')}
</Alert>
<VerticalSpace space='24px' />
</>
)}
<TopWrapper>
<NftMultimediaWrapper>
{isFetchingNFTMetadata ? (
Expand Down
3 changes: 3 additions & 0 deletions components/brave_wallet_ui/stories/locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1239,7 +1239,10 @@ provideStrings({
braveWalletEditNftModalTitle: 'Edit NFT',
braveWalletNftMoveToSpam: 'Mark as junk',
braveWalletNftUnspam: 'Mark as not junk',

// NFT Labels
braveWalletNftJunk: 'Junk',
braveWalletWatchOnly: 'Watch-only',

// Add NFT modal
braveWalletAddNftModalTitle: 'Add NFT',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export const mockUSDCoin = {
chainId: BraveWallet.MAINNET_CHAIN_ID
}

export const mockMoonCatNFT = {
export const mockMoonCatNFT: BraveWallet.BlockchainToken = {
contractAddress: '0xc3f733ca98E0daD0386979Eb96fb1722A1A05E69',
name: 'MoonCats',
symbol: 'AMC',
Expand Down
16 changes: 0 additions & 16 deletions components/brave_wallet_ui/stories/wallet-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
ContainerCard,
LayoutCardWrapper
} from '../components/desktop/wallet-page-wrapper/wallet-page-wrapper.style'
import { NFTGridViewItem } from '../components/desktop/views/portfolio/components/nft-grid-view/nft-grid-view-item'
import { TabOption, Tabs } from '../components/shared/tabs/tabs'
import { AutoDiscoveryEmptyState } from '../components/desktop/views/nfts/components/auto-discovery-empty-state/auto-discovery-empty-state'
import { MarketGrid } from '../components/shared/market-grid/market-grid'
Expand Down Expand Up @@ -114,8 +113,6 @@ _BuySendSwapDeposit.story = {
name: 'Buy/Send/Swap/Deposit'
}



export const _NftsEmptyState = () => {
return <NftsEmptyState onImportNft={() => console.log('On import NFT')} />
}
Expand Down Expand Up @@ -169,19 +166,6 @@ _AutoDiscoveryEmptyState.story = {
title: 'NFT Auto Discovery Empty State'
}

export const _NFTGridViewItem = () => {
return (
<WalletPageStory>
<NFTGridViewItem
isTokenHidden={false}
isTokenSpam={false}
token={mockErc721Token}
onSelectAsset={() => {}}
/>
</WalletPageStory>
)
}

export const _Tabs = () => {
const options: TabOption[] = [
{
Expand Down
Loading
Loading