Skip to content

Commit

Permalink
Feat(wallet): add watch only label and alerts to unowned NFTs (#24569)
Browse files Browse the repository at this point in the history
* feat(wallet): add label to watch-only NFTs

* feat(wallet): add info alert for unowned NFT in details screen

* chore(wallet): fix string description
  • Loading branch information
josheleonard authored Jul 10, 2024
1 parent 4e7e814 commit b0e0733
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 42 deletions.
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

0 comments on commit b0e0733

Please sign in to comment.