Skip to content

Commit

Permalink
feat(suite): nft section
Browse files Browse the repository at this point in the history
  • Loading branch information
enjojoy committed Nov 22, 2024
1 parent a55654c commit 13d1ed5
Show file tree
Hide file tree
Showing 14 changed files with 675 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/suite-desktop-ui/src/support/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { SettingsCoins } from 'src/views/settings/SettingsCoins/SettingsCoins';
import { SettingsDebug } from 'src/views/settings/SettingsDebug/SettingsDebug';
import { SettingsDevice } from 'src/views/settings/SettingsDevice/SettingsDevice';
import { Tokens } from 'src/views/wallet/tokens';
import { Nfts } from 'src/views/wallet/nfts';
import PasswordManager from 'src/views/password-manager';

const components: { [key: string]: ComponentType<any> } = {
Expand All @@ -47,6 +48,7 @@ const components: { [key: string]: ComponentType<any> } = {
'wallet-sign-verify': WalletSignVerify,
'wallet-anonymize': WalletAnonymize,
'wallet-tokens': Tokens,
'wallet-nfts': Nfts,
'wallet-coinmarket-buy': CoinmarketBuyForm,
'wallet-coinmarket-buy-detail': CoinmarketBuyDetail,
'wallet-coinmarket-buy-offers': CoinmarketBuyOffers,
Expand Down
1 change: 1 addition & 0 deletions packages/suite-web/src/support/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const components: Record<PageName, LazyExoticComponent<ComponentType<any>>> = {
() => import(/* webpackChunkName: "wallet" */ 'src/views/wallet/details'),
),
'wallet-tokens': lazy(() => import(/* webpackChunkName: "wallet" */ 'src/views/wallet/tokens')),
'wallet-nfts': lazy(() => import(/* webpackChunkName: "wallet" */ 'src/views/wallet/nfts')),
'wallet-send': lazy(() => import(/* webpackChunkName: "wallet" */ 'src/views/wallet/send')),
'wallet-staking': lazy(() =>
import(/* webpackChunkName: "wallet" */ 'src/views/wallet/staking/WalletStaking').then(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const ACCOUNT_TABS = [
'wallet-index',
'wallet-details',
'wallet-tokens',
'wallet-nfts',
'wallet-nfts-hidden',
'wallet-tokens-hidden',
'wallet-staking',
];
Expand Down Expand Up @@ -54,6 +56,16 @@ export const AccountNavigation = () => {
activeRoutes: ['wallet-tokens', 'wallet-tokens-hidden'],
'data-testid': '@wallet/menu/wallet-tokens',
},
{
id: 'wallet-nfts',
callback: () => {
goToWithAnalytics('wallet-nfts', { preserveParams: true });
},
title: <Translation id="TR_NAV_NFTS" />,
isHidden: !['ethereum'].includes(networkType),
activeRoutes: ['wallet-nfts', 'wallet-nfts-hidden'],
'data-testid': '@wallet/menu/wallet-nfts',
},
{
id: 'wallet-staking',
callback: () => {
Expand Down
24 changes: 24 additions & 0 deletions packages/suite/src/support/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2598,6 +2598,14 @@ const messages = defineMessagesWithTypeCheck({
defaultMessage: 'Unrecognized tokens pose potential risks. Use caution.',
id: 'TR_TOKEN_UNRECOGNIZED_BY_TREZOR_TOOLTIP',
},
TR_COLLECTIONS_UNRECOGNIZED_BY_TREZOR: {
defaultMessage: 'Unrecognized collections',
id: 'TR_COLLECTIONS_UNRECOGNIZED_BY_TREZOR',
},
TR_NFT_UNRECOGNIZED_BY_TREZOR_TOOLTIP: {
defaultMessage: 'Unrecognized NFTs pose potential risks. Use caution.',
id: 'TR_NFT_UNRECOGNIZED_BY_TREZOR_TOOLTIP',
},
TR_LEARN: {
defaultMessage: 'Learn',
description: 'Link to Suite Guide.',
Expand Down Expand Up @@ -2721,6 +2729,10 @@ const messages = defineMessagesWithTypeCheck({
defaultMessage: 'Tokens',
id: 'TR_NAV_TOKENS',
},
TR_NAV_NFTS: {
defaultMessage: 'NFT',
id: 'TR_NAV_NFTS',
},
TR_NAV_SIGN_AND_VERIFY: {
defaultMessage: 'Sign & Verify',
description:
Expand Down Expand Up @@ -5285,6 +5297,18 @@ const messages = defineMessagesWithTypeCheck({
id: 'TR_TOKENS',
defaultMessage: 'Tokens',
},
TR_COLLECTION: {
id: 'TR_COLLECTION',
defaultMessage: 'Collection',
},
TR_ID: {
id: 'TR_ID',
defaultMessage: 'ID',
},
TR_NFT: {
id: 'TR_NFT',
defaultMessage: 'NFT',
},
TR_TOKENS_EMPTY: {
id: 'TR_TOKENS_EMPTY',
defaultMessage: 'No tokens... yet.',
Expand Down
60 changes: 60 additions & 0 deletions packages/suite/src/utils/wallet/nftUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { isTokenDefinitionKnown, TokenDefinition } from '@suite-common/token-definitions';
import { isNftMatchesSearch, filterNftTokens } from '@suite-common/wallet-utils';
import { NetworkSymbol, getNetworkFeatures } from '@suite-common/wallet-config';
import { Token } from '@trezor/blockchain-link-types/src/blockbook-api';

type GetNfts = {
tokens: Token[];
symbol: NetworkSymbol;
nftDefinitions?: TokenDefinition;
searchQuery?: string;
};

export type NftType = 'ERC721' | 'ERC1155';

export const getNfts = ({ tokens, symbol, nftDefinitions, searchQuery }: GetNfts) => {
// filter out NFT tokens until we implement them
const nfts = filterNftTokens(tokens);

const hasNftDefinitions = getNetworkFeatures(symbol).includes('nft-definitions');

const shownVerified: Token[] = [];
const shownUnverified: Token[] = [];
const hiddenVerified: Token[] = [];
const hiddenUnverified: Token[] = [];

nfts.forEach(token => {
const isKnown = isTokenDefinitionKnown(nftDefinitions?.data, symbol, token.contract || '');
const isHidden = nftDefinitions?.hide.includes(token.contract || '');
const isShown = nftDefinitions?.show.includes(token.contract || '');

const query = searchQuery ? searchQuery.trim().toLowerCase() : '';

if (searchQuery && !isNftMatchesSearch(token, query)) return;

const pushToArray = (arrayVerified: Token[], arrayUnverified: Token[]) => {
if (isKnown) {
arrayVerified.push(token);
} else {
arrayUnverified.push(token);
}
};

if (isShown) {
pushToArray(shownVerified, shownUnverified);
} else if (hasNftDefinitions && !isKnown) {
pushToArray(hiddenVerified, hiddenUnverified);
} else if (isHidden) {
pushToArray(hiddenVerified, hiddenUnverified);
} else {
pushToArray(shownVerified, shownUnverified);
}
});

return {
shownVerified,
shownUnverified,
hiddenVerified,
hiddenUnverified,
};
};
52 changes: 52 additions & 0 deletions packages/suite/src/views/wallet/nfts/HiddenNfts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Banner, Column, H3 } from '@trezor/components';
import { SelectedAccountLoaded } from '@suite-common/wallet-types';

Check warning on line 2 in packages/suite/src/views/wallet/nfts/HiddenNfts.tsx

View workflow job for this annotation

GitHub Actions / Linting and formatting

There should be at least one empty line between import groups
import NftsTable from './NftsTable/NftsTable';

Check warning on line 3 in packages/suite/src/views/wallet/nfts/HiddenNfts.tsx

View workflow job for this annotation

GitHub Actions / Linting and formatting

There should be at least one empty line between import groups
import { Translation } from 'src/components/suite';

Check warning on line 4 in packages/suite/src/views/wallet/nfts/HiddenNfts.tsx

View workflow job for this annotation

GitHub Actions / Linting and formatting

`src/components/suite` import should occur before import of `./NftsTable/NftsTable`

type NftsTableProps = {
selectedAccount: SelectedAccountLoaded;
searchQuery: string;
};

const HiddenNfts = ({ selectedAccount, searchQuery }: NftsTableProps) => {
return (
<Column gap={24} alignItems="stretch">
<NftsTable
selectedAccount={selectedAccount}
searchQuery={searchQuery}
type="ERC721"
shown={false}
verified={true}
/>
<NftsTable
selectedAccount={selectedAccount}
searchQuery={searchQuery}
type="ERC1155"
shown={false}
verified={true}
/>
<H3>
<Translation id="TR_COLLECTIONS_UNRECOGNIZED_BY_TREZOR" />
</H3>
<Banner variant="warning" icon>
<Translation id="TR_NFT_UNRECOGNIZED_BY_TREZOR_TOOLTIP" />
</Banner>
<NftsTable
selectedAccount={selectedAccount}
searchQuery={searchQuery}
type="ERC721"
shown={false}
verified={false}
/>
<NftsTable
selectedAccount={selectedAccount}
searchQuery={searchQuery}
type="ERC1155"
shown={false}
verified={false}
/>
</Column>
);
};

export default HiddenNfts;
87 changes: 87 additions & 0 deletions packages/suite/src/views/wallet/nfts/NftsNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Dispatch, SetStateAction, useEffect, useState } from 'react';

import { SelectedAccountLoaded } from '@suite-common/wallet-types';
import { selectNftDefinitions } from '@suite-common/token-definitions';
import { spacings } from '@trezor/theme';
import { Row } from '@trezor/components';

import { useSelector } from 'src/hooks/suite';
import { NavigationItem } from 'src/components/suite/layouts/SuiteLayout/Sidebar/NavigationItem';
import { getNfts } from 'src/utils/wallet/nftUtils';
import { selectRouteName } from 'src/reducers/suite/routerReducer';
import { SearchAction } from 'src/components/wallet/SearchAction';

Check warning on line 12 in packages/suite/src/views/wallet/nfts/NftsNavigation.tsx

View workflow job for this annotation

GitHub Actions / Linting and formatting

There should be at least one empty line between import groups
import { filterNftTokens } from '@suite-common/wallet-utils';

Check warning on line 13 in packages/suite/src/views/wallet/nfts/NftsNavigation.tsx

View workflow job for this annotation

GitHub Actions / Linting and formatting

`@suite-common/wallet-utils` import should occur before import of `src/hooks/suite`

interface NftsNavigationProps {
selectedAccount: SelectedAccountLoaded;
searchQuery: string;
setSearchQuery: Dispatch<SetStateAction<string>>;
}

export const NftsNavigation = ({
selectedAccount,
searchQuery,
setSearchQuery,
}: NftsNavigationProps) => {
const { account } = selectedAccount;

const [isExpanded, setExpanded] = useState(false);

const routeName = useSelector(selectRouteName);

const nftDefinitions = useSelector(state =>
selectNftDefinitions(state, selectedAccount.account.symbol),
);

const filteredTokens = filterNftTokens(account.tokens || []);

const nfts = getNfts({ tokens: filteredTokens, symbol: account.symbol, nftDefinitions });

useEffect(() => {
setSearchQuery('');
setExpanded(false);
}, [account.symbol, account.index, account.accountType, setSearchQuery]);

return (
<Row alignItems="center" justifyContent="space-between" margin={{ bottom: spacings.md }}>
<Row alignItems="center" gap={spacings.xxs}>
<NavigationItem
nameId="TR_NAV_NFTS"
isActive={routeName === 'wallet-nfts'}
icon="tokens"
goToRoute="wallet-nfts"
preserveParams
iconSize="mediumLarge"
itemsCount={
nfts.shownVerified.length + nfts.shownUnverified.length || undefined
}
isRounded
typographyStyle="hint"
/>
<NavigationItem
nameId="TR_HIDDEN"
isActive={routeName === 'wallet-nfts-hidden'}
icon="hide"
goToRoute="wallet-nfts-hidden"
preserveParams
iconSize="mediumLarge"
itemsCount={undefined}
isRounded
typographyStyle="hint"
/>
</Row>
<Row>
<SearchAction
tooltipText="TR_TOKENS_SEARCH_TOOLTIP"
placeholder="TR_SEARCH_TOKENS"
isExpanded={isExpanded}
searchQuery={searchQuery}
setExpanded={setExpanded}
setSearch={setSearchQuery}
onSearch={setSearchQuery}
data-testid="@wallet/accounts/search-icon"
/>
</Row>
</Row>
);
};
Loading

0 comments on commit 13d1ed5

Please sign in to comment.