diff --git a/components/brave_wallet/browser/brave_wallet_constants.h b/components/brave_wallet/browser/brave_wallet_constants.h
index 069e016291f8..e33cd8bd5f87 100644
--- a/components/brave_wallet/browser/brave_wallet_constants.h
+++ b/components/brave_wallet/browser/brave_wallet_constants.h
@@ -878,7 +878,10 @@ constexpr webui::LocalizedString kLocalizedStrings[] = {
{"braveWalletConfirmHidingToken", IDS_BRAVE_WALLET_CONFIRM_HIDING_TOKEN},
{"braveWalletCancelHidingToken", IDS_BRAVE_WALLET_CANCEL_HIDING_TOKEN},
{"braveWalletRequestFeatureButtonText",
- IDS_BRAVE_WALLET_REQUEST_FEATURE_BUTTON_TEXT}};
+ IDS_BRAVE_WALLET_REQUEST_FEATURE_BUTTON_TEXT},
+ {"braveWalletNftsEmptyState", IDS_BRAVE_WALLET_NFTS_EMPTY_STATE},
+ {"braveWalletNftsEmptyStateSearch",
+ IDS_BRAVE_WALLET_NFTS_EMPTY_STATE_SEARCH_FILTER}};
// 0x swap constants
constexpr char kRopstenSwapBaseAPIURL[] = "https://ropsten.api.0x.org/";
diff --git a/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx b/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx
index fd77dc23efd3..985ad12fe9bb 100644
--- a/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx
+++ b/components/brave_wallet_ui/components/desktop/views/crypto/index.tsx
@@ -29,6 +29,7 @@ import { MarketView } from '../market'
import { Accounts } from '../accounts/accounts'
import { Account } from '../accounts/account'
import { AddAccountModal } from '../../popup-modals/add-account-modal/add-account-modal'
+import { NftView } from '../nfts/nft-view'
interface ParamsType {
category?: TopTabNavTypes
@@ -231,6 +232,11 @@ const CryptoView = (props: Props) => {
/>
+
+ {nav}
+
+
+
diff --git a/components/brave_wallet_ui/components/desktop/views/nfts/components/nfts.styles.tsx b/components/brave_wallet_ui/components/desktop/views/nfts/components/nfts.styles.tsx
new file mode 100644
index 000000000000..60cad5b8fbbe
--- /dev/null
+++ b/components/brave_wallet_ui/components/desktop/views/nfts/components/nfts.styles.tsx
@@ -0,0 +1,39 @@
+// Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/.
+import styled from 'styled-components'
+
+export const FilterTokenRow = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-direction: row;
+ width: 100%;
+`
+
+export const NftGrid = styled.div`
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ grid-gap: 25px;
+ box-sizing: border-box;
+ width: 100%;
+ padding-top: 10px;
+ @media screen and (max-width: 1350px) {
+ grid-template-columns: repeat(4, 1fr);
+ }
+ @media screen and (max-width: 1150px) {
+ grid-template-columns: repeat(3, 1fr);
+ }
+ @media screen and (max-width: 950px) {
+ grid-template-columns: repeat(2, 1fr);
+ }
+`
+
+export const EmptyStateText = styled.div`
+ text-align: center;
+ padding: 30px 0;
+ color: ${p => p.theme.color.text03};
+ font-size: 14px;
+ font-family: Poppins;
+`
diff --git a/components/brave_wallet_ui/components/desktop/views/nfts/components/nfts.tsx b/components/brave_wallet_ui/components/desktop/views/nfts/components/nfts.tsx
new file mode 100644
index 000000000000..7ff8b04d1efe
--- /dev/null
+++ b/components/brave_wallet_ui/components/desktop/views/nfts/components/nfts.tsx
@@ -0,0 +1,109 @@
+// Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/.
+import * as React from 'react'
+import { useHistory } from 'react-router-dom'
+import { useDispatch } from 'react-redux'
+
+// types
+import {
+ BraveWallet,
+ WalletRoutes
+} from '../../../../../constants/types'
+
+// utils
+import { getLocale } from '$web-common/locale'
+
+// components
+import SearchBar from '../../../../shared/search-bar'
+import NetworkFilterSelector from '../../../network-filter-selector'
+
+// styles
+import {
+ EmptyStateText,
+ FilterTokenRow,
+ NftGrid
+} from './nfts.styles'
+import { NFTGridViewItem } from '../../portfolio/components/nft-grid-view/nft-grid-view-item'
+import { WalletPageActions } from '../../../../../page/actions'
+import Amount from '../../../../../utils/amount'
+
+interface Props {
+ networks: BraveWallet.NetworkInfo[]
+ nftList: BraveWallet.BlockchainToken[]
+}
+
+export const Nfts = (props: Props) => {
+ const {
+ networks,
+ nftList
+ } = props
+
+ // state
+ const [searchValue, setSearchValue] = React.useState('')
+
+ // hooks
+ const history = useHistory()
+ const dispatch = useDispatch()
+
+ // methods
+ const onSearchValueChange = React.useCallback((event: React.ChangeEvent) => {
+ setSearchValue(event.target.value)
+ }, [])
+
+ const onSelectAsset = React.useCallback((asset: BraveWallet.BlockchainToken) => {
+ history.push(`${WalletRoutes.Portfolio}/${asset.contractAddress}/${asset.tokenId}`)
+ // reset nft metadata
+ dispatch(WalletPageActions.updateNFTMetadata(undefined))
+ }, [dispatch])
+
+ // memos
+ const filteredNfts = React.useMemo(() => {
+ if (searchValue === '') {
+ return nftList
+ }
+
+ return nftList.filter((item) => {
+ const tokenId = new Amount(item.tokenId).toNumber().toString()
+
+ return (
+ item.name.toLowerCase() === searchValue.toLowerCase() ||
+ item.name.toLowerCase().includes(searchValue.toLowerCase()) ||
+ item.symbol.toLocaleLowerCase() === searchValue.toLowerCase() ||
+ item.symbol.toLowerCase().includes(searchValue.toLowerCase()) ||
+ tokenId === searchValue.toLowerCase() ||
+ tokenId.includes(searchValue.toLowerCase())
+ )
+ })
+ }, [searchValue, nftList])
+
+ const emptyStateMessage = React.useMemo(() => {
+ return getLocale(searchValue === '' ? 'braveWalletNftsEmptyState' : 'braveWalletNftsEmptyStateSearch')
+ }, [searchValue])
+
+ return (
+ <>
+
+
+
+
+ {filteredNfts.length === 0
+ ? {emptyStateMessage}
+ :
+ {filteredNfts.map(nft => (
+ onSelectAsset(nft)}
+ />
+ ))}
+
+ }
+ >
+ )
+}
diff --git a/components/brave_wallet_ui/components/desktop/views/nfts/nft-view.tsx b/components/brave_wallet_ui/components/desktop/views/nfts/nft-view.tsx
new file mode 100644
index 000000000000..4b8b3f1eaae2
--- /dev/null
+++ b/components/brave_wallet_ui/components/desktop/views/nfts/nft-view.tsx
@@ -0,0 +1,39 @@
+// Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/.
+
+import * as React from 'react'
+import { useSelector } from 'react-redux'
+
+// types
+import {
+ SupportedTestNetworks,
+ WalletState
+} from '../../../../constants/types'
+
+// components
+import { Nfts } from './components/nfts'
+import { AllNetworksOption } from '../../../../options/network-filter-options'
+
+export const NftView = () => {
+ // redux
+ const networkList = useSelector(({ wallet }: { wallet: WalletState }) => wallet.networkList)
+ const userVisibleTokensInfo = useSelector(({ wallet }: { wallet: WalletState}) => wallet.userVisibleTokensInfo)
+ const selectedNetworkFilter = useSelector(({ wallet }: { wallet: WalletState }) => wallet.selectedNetworkFilter)
+
+ const fungibleTokens = React.useMemo(() => {
+ if (selectedNetworkFilter.chainId === AllNetworksOption.chainId) {
+ return userVisibleTokensInfo.filter((token) => !SupportedTestNetworks.includes(token.chainId) && token.isErc721)
+ }
+
+ return userVisibleTokensInfo.filter(token => token.chainId === selectedNetworkFilter.chainId && token.isErc721)
+ }, [userVisibleTokensInfo, selectedNetworkFilter])
+
+ return (
+
+ )
+}
diff --git a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx
index f2d80aac0cf2..379b96051258 100644
--- a/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx
+++ b/components/brave_wallet_ui/components/desktop/views/portfolio/portfolio-asset.tsx
@@ -356,11 +356,7 @@ export const PortfolioAsset = (props: Props) => {
dispatch(WalletPageActions.selectAsset({ asset: undefined, timeFrame: selectedTimeline }))
dispatch(WalletPageActions.selectCoinMarket(undefined))
setfilteredAssetList(userAssetList)
- if (isShowingMarketData) {
- history.push(WalletRoutes.Market)
- } else {
- history.push(WalletRoutes.Portfolio)
- }
+ history.goBack()
}, [
userAssetList,
selectedTimeline
diff --git a/components/brave_wallet_ui/constants/types.ts b/components/brave_wallet_ui/constants/types.ts
index 6f3a928b9efb..9d380dda0d4a 100644
--- a/components/brave_wallet_ui/constants/types.ts
+++ b/components/brave_wallet_ui/constants/types.ts
@@ -125,6 +125,7 @@ export type NavTypes =
export type TopTabNavTypes =
| 'portfolio'
| 'apps'
+ | 'nfts'
| 'accounts'
| 'market'
@@ -630,6 +631,9 @@ export enum WalletRoutes {
FundWalletPage = '/crypto/fund-wallet',
DepositFundsPage = '/crypto/deposit-funds',
+ // NFTs
+ Nfts = '/crypto/nfts',
+
// market
Market = '/crypto/market',
MarketSub = '/crypto/market/:id?',
diff --git a/components/brave_wallet_ui/options/top-nav-options.ts b/components/brave_wallet_ui/options/top-nav-options.ts
index 21d008f86675..e6c485368a6c 100644
--- a/components/brave_wallet_ui/options/top-nav-options.ts
+++ b/components/brave_wallet_ui/options/top-nav-options.ts
@@ -15,6 +15,10 @@ export const TopNavOptions = (): TopTabNavObjectType[] => [
id: 'market',
name: getLocale('braveWalletTopNavMarket')
},
+ {
+ id: 'nfts',
+ name: getLocale('braveWalletTopNavNFTS')
+ },
{
id: 'accounts',
name: getLocale('braveWalletTopNavAccounts')
diff --git a/components/brave_wallet_ui/page/container.tsx b/components/brave_wallet_ui/page/container.tsx
index 661fe9a6274b..83d524ee8a2e 100644
--- a/components/brave_wallet_ui/page/container.tsx
+++ b/components/brave_wallet_ui/page/container.tsx
@@ -140,7 +140,8 @@ export const Container = () => {
(
walletLocation.includes(WalletRoutes.Portfolio) ||
walletLocation.includes(WalletRoutes.Accounts) ||
- walletLocation.includes(WalletRoutes.Market)
+ walletLocation.includes(WalletRoutes.Market) ||
+ walletLocation.includes(WalletRoutes.Nfts)
)
// effects
@@ -159,7 +160,9 @@ export const Container = () => {
walletLocation.includes(WalletRoutes.Backup) ||
walletLocation.includes(WalletRoutes.DepositFundsPage) ||
walletLocation.includes(WalletRoutes.FundWalletPage) ||
- walletLocation.includes(WalletRoutes.Portfolio)
+ walletLocation.includes(WalletRoutes.Portfolio) ||
+ walletLocation.includes(WalletRoutes.Market) ||
+ walletLocation.includes(WalletRoutes.Nfts)
) {
setSessionRoute(walletLocation)
}
diff --git a/components/brave_wallet_ui/stories/locale.ts b/components/brave_wallet_ui/stories/locale.ts
index 041fef05afd4..a095d2d7641c 100644
--- a/components/brave_wallet_ui/stories/locale.ts
+++ b/components/brave_wallet_ui/stories/locale.ts
@@ -740,5 +740,10 @@ provideStrings({
// ASCII toggles
braveWalletViewEncodedMessage: 'View original message',
- braveWalletViewDecodedMessage: 'View message in ASCII encoding'
+ braveWalletViewDecodedMessage: 'View message in ASCII encoding',
+
+ // NFTs Tab
+ braveWalletNftsEmptyState: 'No NFTs found in your wallet. You can add NFTs by clicking the "+ Visible assets" button at the bottom of' +
+ ' the "Portfolio" tab',
+ braveWalletNftsEmptyStateSearch: 'No NFTs matching search or filter found'
})
diff --git a/components/resources/wallet_strings.grdp b/components/resources/wallet_strings.grdp
index bb9e5bc0b9a8..c56735fd63cf 100644
--- a/components/resources/wallet_strings.grdp
+++ b/components/resources/wallet_strings.grdp
@@ -61,7 +61,7 @@
Portfolio
Prices
Apps
- NFTs
+ NFTs
Accounts
Market
1H
@@ -635,4 +635,6 @@
Are you sure you want to remove "$1Eth Account 1"?
Request feature
Recovery phrase must be 12, 15, 18, 21, or 24 words long
+ No NFTs found in your wallet. You can add NFTs by clicking the "+ Visible assets" button at the bottom of the "Portfolio" tab
+ No NFTs matching search or filter found