diff --git a/icons/collection.svg b/icons/collection.svg
new file mode 100644
index 0000000000..981040af5a
--- /dev/null
+++ b/icons/collection.svg
@@ -0,0 +1,3 @@
+
diff --git a/mocks/address/tokens.ts b/mocks/address/tokens.ts
index 5f2299cee5..91abffd8c7 100644
--- a/mocks/address/tokens.ts
+++ b/mocks/address/tokens.ts
@@ -1,4 +1,4 @@
-import type { AddressTokenBalance } from 'types/api/address';
+import type { AddressCollectionsResponse, AddressNFTsResponse, AddressTokenBalance } from 'types/api/address';
import * as tokens from 'mocks/tokens/tokenInfo';
import * as tokenInstance from 'mocks/tokens/tokenInstance';
@@ -117,3 +117,49 @@ export const erc1155List = {
erc1155b,
],
};
+
+export const nfts: AddressNFTsResponse = {
+ items: [
+ {
+ ...tokenInstance.base,
+ token_type: 'ERC-1155',
+ value: '11',
+ },
+ {
+ ...tokenInstance.unique,
+ token_type: 'ERC-721',
+ value: '1',
+ },
+ ],
+ next_page_params: null,
+};
+
+const nftInstance = {
+ ...tokenInstance.base,
+ token_type: 'ERC-1155',
+ value: '11',
+};
+
+export const collections: AddressCollectionsResponse = {
+ items: [
+ {
+ token: tokens.tokenInfoERC1155a,
+ amount: '100',
+ token_instances: Array(5).fill(nftInstance),
+ },
+ {
+ token: tokens.tokenInfoERC20LongSymbol,
+ amount: '100',
+ token_instances: Array(5).fill(nftInstance),
+ },
+ {
+ token: tokens.tokenInfoERC1155WithoutName,
+ amount: '1',
+ token_instances: [ nftInstance ],
+ },
+ ],
+ next_page_params: {
+ token_contract_address_hash: '123',
+ token_type: 'ERC-1155',
+ },
+};
diff --git a/ui/address/AddressTokens.pw.tsx b/ui/address/AddressTokens.pw.tsx
index 3cf3b34e93..49494a35b9 100644
--- a/ui/address/AddressTokens.pw.tsx
+++ b/ui/address/AddressTokens.pw.tsx
@@ -13,6 +13,8 @@ import AddressTokens from './AddressTokens';
const ADDRESS_HASH = addressMock.withName.hash;
const API_URL_ADDRESS = buildApiUrl('address', { hash: ADDRESS_HASH });
const API_URL_TOKENS = buildApiUrl('address_tokens', { hash: ADDRESS_HASH });
+const API_URL_NFT = buildApiUrl('address_nfts', { hash: ADDRESS_HASH });
+const API_URL_COLLECTIONS = buildApiUrl('address_collections', { hash: ADDRESS_HASH });
const nextPageParams = {
items_count: 50,
@@ -52,6 +54,14 @@ const test = base.extend({
status: 200,
body: JSON.stringify(response1155),
}));
+ await page.route(API_URL_NFT, (route) => route.fulfill({
+ status: 200,
+ body: JSON.stringify(tokensMock.nfts),
+ }));
+ await page.route(API_URL_COLLECTIONS, (route) => route.fulfill({
+ status: 200,
+ body: JSON.stringify(tokensMock.collections),
+ }));
use(page);
},
@@ -76,10 +86,10 @@ test('erc20 +@dark-mode', async({ mount }) => {
await expect(component).toHaveScreenshot();
});
-test('erc721 +@dark-mode', async({ mount }) => {
+test('collections +@dark-mode', async({ mount }) => {
const hooksConfig = {
router: {
- query: { hash: ADDRESS_HASH, tab: 'tokens_erc721' },
+ query: { hash: ADDRESS_HASH, tab: 'tokens_nfts' },
isReady: true,
},
};
@@ -95,10 +105,10 @@ test('erc721 +@dark-mode', async({ mount }) => {
await expect(component).toHaveScreenshot();
});
-test('erc1155 +@dark-mode', async({ mount }) => {
+test('nfts +@dark-mode', async({ mount }) => {
const hooksConfig = {
router: {
- query: { hash: ADDRESS_HASH, tab: 'tokens_erc1155' },
+ query: { hash: ADDRESS_HASH, tab: 'tokens_nfts' },
isReady: true,
},
};
@@ -111,6 +121,8 @@ test('erc1155 +@dark-mode', async({ mount }) => {
{ hooksConfig },
);
+ await component.getByText('List').click();
+
await expect(component).toHaveScreenshot();
});
@@ -136,10 +148,10 @@ test.describe('mobile', () => {
await expect(component).toHaveScreenshot();
});
- test('erc721', async({ mount }) => {
+ test('nfts', async({ mount }) => {
const hooksConfig = {
router: {
- query: { hash: ADDRESS_HASH, tab: 'tokens_erc721' },
+ query: { hash: ADDRESS_HASH, tab: 'tokens_nfts' },
isReady: true,
},
};
@@ -152,13 +164,15 @@ test.describe('mobile', () => {
{ hooksConfig },
);
+ await component.getByLabel('list').click();
+
await expect(component).toHaveScreenshot();
});
- test('erc1155', async({ mount }) => {
+ test('collections', async({ mount }) => {
const hooksConfig = {
router: {
- query: { hash: ADDRESS_HASH, tab: 'tokens_erc1155' },
+ query: { hash: ADDRESS_HASH, tab: 'tokens_nfts' },
isReady: true,
},
};
diff --git a/ui/address/AddressTokens.tsx b/ui/address/AddressTokens.tsx
index c3e445e3cc..046941f946 100644
--- a/ui/address/AddressTokens.tsx
+++ b/ui/address/AddressTokens.tsx
@@ -1,20 +1,15 @@
import { Box } from '@chakra-ui/react';
-import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import React from 'react';
-import type { SocketMessage } from 'lib/socket/types';
-import type { AddressTokenBalance, AddressTokensBalancesSocketMessage, AddressTokensResponse } from 'types/api/address';
-import type { TokenType } from 'types/api/token';
import type { PaginationParams } from 'ui/shared/pagination/types';
-import { getResourceKey } from 'lib/api/useApiQuery';
+import listIcon from 'icons/apps.svg';
+import collectionIcon from 'icons/collection.svg';
import { useAppContext } from 'lib/contexts/app';
import * as cookies from 'lib/cookies';
import useIsMobile from 'lib/hooks/useIsMobile';
import getQueryParamString from 'lib/router/getQueryParamString';
-import useSocketChannel from 'lib/socket/useSocketChannel';
-import useSocketMessage from 'lib/socket/useSocketMessage';
import { ADDRESS_TOKEN_BALANCE_ERC_20, ADDRESS_NFT_1155, ADDRESS_COLLECTION } from 'stubs/address';
import { generateListStub } from 'stubs/utils';
import Pagination from 'ui/shared/pagination/Pagination';
@@ -27,7 +22,7 @@ import AddressNFTs from './tokens/AddressNFTs';
import ERC20Tokens from './tokens/ERC20Tokens';
import TokenBalances from './tokens/TokenBalances';
-type TNftDisplayType = 'collections' | 'list';
+type TNftDisplayType = 'collection' | 'list';
const TAB_LIST_PROPS = {
marginBottom: 0,
@@ -41,12 +36,6 @@ const TAB_LIST_PROPS_MOBILE = {
columnGap: 3,
};
-const tokenBalanceItemIdentityFactory = (match: AddressTokenBalance) => (item: AddressTokenBalance) => ((
- match.token.address === item.token.address &&
- match.token_id === item.token_id &&
- match.token_instance?.id === item.token_instance?.id
-));
-
const AddressTokens = () => {
const router = useRouter();
const isMobile = useIsMobile();
@@ -54,7 +43,7 @@ const AddressTokens = () => {
const scrollRef = React.useRef(null);
const displayTypeCookie = cookies.get(cookies.NAMES.ADDRESS_NFT_DISPLAY_TYPE, useAppContext().cookies);
- const [ nftDisplayType, setNftDisplayType ] = React.useState(displayTypeCookie === 'list' ? 'list' : 'collections');
+ const [ nftDisplayType, setNftDisplayType ] = React.useState(displayTypeCookie === 'list' ? 'list' : 'collection');
const tab = getQueryParamString(router.query.tab);
const hash = getQueryParamString(router.query.hash);
@@ -76,7 +65,7 @@ const AddressTokens = () => {
pathParams: { hash },
scrollRef,
options: {
- enabled: tab === 'tokens_nfts' && nftDisplayType === 'collections',
+ enabled: tab === 'tokens_nfts' && nftDisplayType === 'collection',
refetchOnMount: false,
placeholderData: generateListStub<'address_collections'>(ADDRESS_COLLECTION, 10, { next_page_params: null }),
},
@@ -93,69 +82,6 @@ const AddressTokens = () => {
},
});
- const queryClient = useQueryClient();
-
- const updateTokensData = React.useCallback((type: TokenType, payload: AddressTokensBalancesSocketMessage) => {
- const queryKey = getResourceKey('address_tokens', { pathParams: { hash }, queryParams: { type } });
-
- queryClient.setQueryData(queryKey, (prevData: AddressTokensResponse | undefined) => {
- const items = prevData?.items.map((currentItem) => {
- const updatedData = payload.token_balances.find(tokenBalanceItemIdentityFactory(currentItem));
- return updatedData ?? currentItem;
- }) || [];
-
- const extraItems = prevData?.next_page_params ?
- [] :
- payload.token_balances.filter((socketItem) => !items.some(tokenBalanceItemIdentityFactory(socketItem)));
-
- if (!prevData) {
- return {
- items: extraItems,
- next_page_params: null,
- };
- }
-
- return {
- items: items.concat(extraItems),
- next_page_params: prevData.next_page_params,
- };
- });
- }, [ hash, queryClient ]);
-
- const handleTokenBalancesErc20Message: SocketMessage.AddressTokenBalancesErc20['handler'] = React.useCallback((payload) => {
- updateTokensData('ERC-20', payload);
- }, [ updateTokensData ]);
-
- const handleTokenBalancesErc721Message: SocketMessage.AddressTokenBalancesErc721['handler'] = React.useCallback((payload) => {
- updateTokensData('ERC-721', payload);
- }, [ updateTokensData ]);
-
- const handleTokenBalancesErc1155Message: SocketMessage.AddressTokenBalancesErc1155['handler'] = React.useCallback((payload) => {
- updateTokensData('ERC-1155', payload);
- }, [ updateTokensData ]);
-
- const channel = useSocketChannel({
- topic: `addresses:${ hash.toLowerCase() }`,
- // !!!
- isDisabled: erc20Query.isPlaceholderData || nftsQuery.isPlaceholderData || collectionsQuery.isPlaceholderData,
- });
-
- useSocketMessage({
- channel,
- event: 'updated_token_balances_erc_20',
- handler: handleTokenBalancesErc20Message,
- });
- useSocketMessage({
- channel,
- event: 'updated_token_balances_erc_721',
- handler: handleTokenBalancesErc721Message,
- });
- useSocketMessage({
- channel,
- event: 'updated_token_balances_erc_1155',
- handler: handleTokenBalancesErc1155Message,
- });
-
const handleNFTsDisplayTypeChange = React.useCallback((val: TNftDisplayType) => {
cookies.set(cookies.NAMES.ADDRESS_NFT_DISPLAY_TYPE, val);
setNftDisplayType(val);
@@ -177,7 +103,10 @@ const AddressTokens = () => {
onChange={ handleNFTsDisplayTypeChange }
defaultValue={ nftDisplayType }
name="type"
- options={ [ { title: 'By collections', value: 'collections' }, { title: 'List', value: 'list' } ] }
+ options={ [
+ { title: 'By collection', value: 'collection', icon: collectionIcon, onlyIcon: isMobile },
+ { title: 'List', value: 'list', icon: listIcon, onlyIcon: isMobile },
+ ] }
/>
);
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png
new file mode 100644
index 0000000000..83db8a532e
Binary files /dev/null and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_collections-dark-mode-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc1155-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc1155-dark-mode-1.png
deleted file mode 100644
index 8e21ba8758..0000000000
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc1155-dark-mode-1.png and /dev/null differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png
index 5c9933e717..fd873438f9 100644
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc20-dark-mode-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc721-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc721-dark-mode-1.png
deleted file mode 100644
index 4eca88970b..0000000000
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_erc721-dark-mode-1.png and /dev/null differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png
new file mode 100644
index 0000000000..e0c18ece19
Binary files /dev/null and b/ui/address/__screenshots__/AddressTokens.pw.tsx_dark-color-mode_nfts-dark-mode-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png
new file mode 100644
index 0000000000..831cdeff85
Binary files /dev/null and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_collections-dark-mode-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc1155-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc1155-dark-mode-1.png
deleted file mode 100644
index 7cbc7815c5..0000000000
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc1155-dark-mode-1.png and /dev/null differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png
index 405d4f9ef4..14587bf801 100644
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc20-dark-mode-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc721-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc721-dark-mode-1.png
deleted file mode 100644
index ad6f89f6c1..0000000000
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_erc721-dark-mode-1.png and /dev/null differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png
new file mode 100644
index 0000000000..58529aa9f9
Binary files /dev/null and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-collections-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc1155-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc1155-1.png
deleted file mode 100644
index 6c1d0b61ac..0000000000
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc1155-1.png and /dev/null differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png
index 4be344fc59..cebdaaf6b3 100644
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc20-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc721-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc721-1.png
deleted file mode 100644
index 9af951e2e9..0000000000
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-erc721-1.png and /dev/null differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png
new file mode 100644
index 0000000000..95deb92652
Binary files /dev/null and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_mobile-nfts-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png
new file mode 100644
index 0000000000..205927b00c
Binary files /dev/null and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_nfts-dark-mode-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-1.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-1.png
index 21adb1d539..8783664e35 100644
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-1.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-1.png differ
diff --git a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-2.png b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-2.png
index 1292dc8384..cbbc9e3d8b 100644
Binary files a/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-2.png and b/ui/address/__screenshots__/AddressTokens.pw.tsx_default_update-balances-via-socket-2.png differ
diff --git a/ui/address/tokenSelect/TokenSelect.pw.tsx b/ui/address/tokenSelect/TokenSelect.pw.tsx
index 28e607dcdd..d2a810c981 100644
--- a/ui/address/tokenSelect/TokenSelect.pw.tsx
+++ b/ui/address/tokenSelect/TokenSelect.pw.tsx
@@ -2,10 +2,8 @@ import { Flex } from '@chakra-ui/react';
import { test as base, expect, devices } from '@playwright/experimental-ct-react';
import React from 'react';
-import * as coinBalanceMock from 'mocks/address/coinBalanceHistory';
import * as tokensMock from 'mocks/address/tokens';
import { tokenInfoERC20a } from 'mocks/tokens/tokenInfo';
-import * as socketServer from 'playwright/fixtures/socketServer';
import TestApp from 'playwright/TestApp';
import buildApiUrl from 'playwright/utils/buildApiUrl';
import MockAddressPage from 'ui/address/testUtils/MockAddressPage';
@@ -175,76 +173,3 @@ base('long values', async({ mount, page }) => {
await expect(page).toHaveScreenshot({ clip: CLIPPING_AREA });
});
-
-test.describe('socket', () => {
- const testWithSocket = test.extend({
- createSocket: socketServer.createSocket,
- });
- testWithSocket.describe.configure({ mode: 'serial' });
-
- testWithSocket('new item after token balance update', async({ page, mount, createSocket }) => {
- await mount(
-
-
-
-
-
-
- ,
- { hooksConfig },
- );
-
- await page.route(TOKENS_ERC20_API_URL, async(route) => route.fulfill({
- status: 200,
- body: JSON.stringify({
- items: [
- ...tokensMock.erc20List.items,
- tokensMock.erc20d,
- ],
- }),
- }), { times: 1 });
-
- const socket = await createSocket();
- const channel = await socketServer.joinChannel(socket, 'addresses:1');
- socketServer.sendMessage(socket, channel, 'token_balance', {
- block_number: 1,
- });
-
- const button = page.getByRole('button', { name: /select/i });
- const text = await button.innerText();
- expect(text).toContain('10');
- });
-
- testWithSocket('new item after coin balance update', async({ page, mount, createSocket }) => {
- await mount(
-
-
-
-
-
-
- ,
- { hooksConfig },
- );
-
- await page.route(TOKENS_ERC20_API_URL, async(route) => route.fulfill({
- status: 200,
- body: JSON.stringify({
- items: [
- ...tokensMock.erc20List.items,
- tokensMock.erc20d,
- ],
- }),
- }), { times: 1 });
-
- const socket = await createSocket();
- const channel = await socketServer.joinChannel(socket, 'addresses:1');
- socketServer.sendMessage(socket, channel, 'coin_balance', {
- coin_balance: coinBalanceMock.base,
- });
-
- const button = page.getByRole('button', { name: /select/i });
- const text = await button.innerText();
- expect(text).toContain('10');
- });
-});
diff --git a/ui/address/tokenSelect/TokenSelect.tsx b/ui/address/tokenSelect/TokenSelect.tsx
index 9c54d9096e..18f5e5b43b 100644
--- a/ui/address/tokenSelect/TokenSelect.tsx
+++ b/ui/address/tokenSelect/TokenSelect.tsx
@@ -5,7 +5,6 @@ import NextLink from 'next/link';
import { useRouter } from 'next/router';
import React from 'react';
-import type { SocketMessage } from 'lib/socket/types';
import type { Address } from 'types/api/address';
import walletIcon from 'icons/wallet.svg';
@@ -13,8 +12,6 @@ import { getResourceKey } from 'lib/api/useApiQuery';
import useIsMobile from 'lib/hooks/useIsMobile';
import * as mixpanel from 'lib/mixpanel/index';
import getQueryParamString from 'lib/router/getQueryParamString';
-import useSocketChannel from 'lib/socket/useSocketChannel';
-import useSocketMessage from 'lib/socket/useSocketMessage';
import useFetchTokens from '../utils/useFetchTokens';
import TokenSelectDesktop from './TokenSelectDesktop';
@@ -28,14 +25,13 @@ const TokenSelect = ({ onClick }: Props) => {
const router = useRouter();
const isMobile = useIsMobile();
const queryClient = useQueryClient();
- const [ blockNumber, setBlockNumber ] = React.useState();
const addressHash = getQueryParamString(router.query.hash);
const addressResourceKey = getResourceKey('address', { pathParams: { hash: addressHash } });
const addressQueryData = queryClient.getQueryData(addressResourceKey);
- const { data, isError, isLoading, refetch } = useFetchTokens({ hash: addressQueryData?.hash });
+ const { data, isError, isLoading } = useFetchTokens({ hash: addressQueryData?.hash });
const tokensResourceKey = getResourceKey('address_tokens', { pathParams: { hash: addressQueryData?.hash }, queryParams: { type: 'ERC-20' } });
const tokensIsFetching = useIsFetching({ queryKey: tokensResourceKey });
@@ -44,34 +40,6 @@ const TokenSelect = ({ onClick }: Props) => {
onClick?.();
}, [ onClick ]);
- const handleTokenBalanceMessage: SocketMessage.AddressTokenBalance['handler'] = React.useCallback((payload) => {
- if (payload.block_number !== blockNumber) {
- refetch();
- setBlockNumber(payload.block_number);
- }
- }, [ blockNumber, refetch ]);
- const handleCoinBalanceMessage: SocketMessage.AddressCoinBalance['handler'] = React.useCallback((payload) => {
- if (payload.coin_balance.block_number !== blockNumber) {
- refetch();
- setBlockNumber(payload.coin_balance.block_number);
- }
- }, [ blockNumber, refetch ]);
-
- const channel = useSocketChannel({
- topic: `addresses:${ addressQueryData?.hash.toLowerCase() }`,
- isDisabled: !addressQueryData,
- });
- useSocketMessage({
- channel,
- event: 'coin_balance',
- handler: handleCoinBalanceMessage,
- });
- useSocketMessage({
- channel,
- event: 'token_balance',
- handler: handleTokenBalanceMessage,
- });
-
if (isLoading) {
return (
diff --git a/ui/address/tokens/AddressCollections.tsx b/ui/address/tokens/AddressCollections.tsx
index 4bd542bd15..6ab437779b 100644
--- a/ui/address/tokens/AddressCollections.tsx
+++ b/ui/address/tokens/AddressCollections.tsx
@@ -44,7 +44,7 @@ const AddressCollections = ({ collectionsQuery, address }: Props) => {
const hasOverload = Number(item.amount) > item.token_instances.length;
return (
-
+
{
noCopy
fontWeight="600"
/>
-
+
{ ` - ${ Number(item.amount).toLocaleString() } item${ Number(item.amount) > 1 ? 's' : '' }` }
{ hasOverload && (
-
+
View in collection
) }
diff --git a/ui/address/tokens/NFTItem.tsx b/ui/address/tokens/NFTItem.tsx
index c33038c323..50a33ab73b 100644
--- a/ui/address/tokens/NFTItem.tsx
+++ b/ui/address/tokens/NFTItem.tsx
@@ -31,7 +31,7 @@ const NFTItem = ({ token, value, isLoading, withTokenLink, ...tokenInstance }: P
/>
-
+
ID#
diff --git a/ui/address/utils/useFetchTokens.ts b/ui/address/utils/useFetchTokens.ts
index 6a3ec6a960..bf4c5eecb8 100644
--- a/ui/address/utils/useFetchTokens.ts
+++ b/ui/address/utils/useFetchTokens.ts
@@ -1,13 +1,25 @@
+import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
-import useApiQuery from 'lib/api/useApiQuery';
+import type { SocketMessage } from 'lib/socket/types';
+import type { AddressTokenBalance, AddressTokensBalancesSocketMessage, AddressTokensResponse } from 'types/api/address';
+import type { TokenType } from 'types/api/token';
-import { calculateUsdValue } from './tokenUtils';
+import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
+import useSocketChannel from 'lib/socket/useSocketChannel';
+import useSocketMessage from 'lib/socket/useSocketMessage';
+import { calculateUsdValue } from './tokenUtils';
interface Props {
hash?: string;
}
+const tokenBalanceItemIdentityFactory = (match: AddressTokenBalance) => (item: AddressTokenBalance) => ((
+ match.token.address === item.token.address &&
+ match.token_id === item.token_id &&
+ match.token_instance?.id === item.token_instance?.id
+));
+
export default function useFetchTokens({ hash }: Props) {
const erc20query = useApiQuery('address_tokens', {
pathParams: { hash },
@@ -25,11 +37,67 @@ export default function useFetchTokens({ hash }: Props) {
queryOptions: { enabled: Boolean(hash), refetchOnMount: false },
});
- const refetch = React.useCallback(() => {
- erc20query.refetch();
- erc721query.refetch();
- erc1155query.refetch();
- }, [ erc1155query, erc20query, erc721query ]);
+ const queryClient = useQueryClient();
+
+ const updateTokensData = React.useCallback((type: TokenType, payload: AddressTokensBalancesSocketMessage) => {
+ const queryKey = getResourceKey('address_tokens', { pathParams: { hash }, queryParams: { type } });
+
+ queryClient.setQueryData(queryKey, (prevData: AddressTokensResponse | undefined) => {
+ const items = prevData?.items.map((currentItem) => {
+ const updatedData = payload.token_balances.find(tokenBalanceItemIdentityFactory(currentItem));
+ return updatedData ?? currentItem;
+ }) || [];
+
+ const extraItems = prevData?.next_page_params ?
+ [] :
+ payload.token_balances.filter((socketItem) => !items.some(tokenBalanceItemIdentityFactory(socketItem)));
+
+ if (!prevData) {
+ return {
+ items: extraItems,
+ next_page_params: null,
+ };
+ }
+
+ return {
+ items: items.concat(extraItems),
+ next_page_params: prevData.next_page_params,
+ };
+ });
+ }, [ hash, queryClient ]);
+
+ const handleTokenBalancesErc20Message: SocketMessage.AddressTokenBalancesErc20['handler'] = React.useCallback((payload) => {
+ updateTokensData('ERC-20', payload);
+ }, [ updateTokensData ]);
+
+ const handleTokenBalancesErc721Message: SocketMessage.AddressTokenBalancesErc721['handler'] = React.useCallback((payload) => {
+ updateTokensData('ERC-721', payload);
+ }, [ updateTokensData ]);
+
+ const handleTokenBalancesErc1155Message: SocketMessage.AddressTokenBalancesErc1155['handler'] = React.useCallback((payload) => {
+ updateTokensData('ERC-1155', payload);
+ }, [ updateTokensData ]);
+
+ const channel = useSocketChannel({
+ topic: `addresses:${ hash?.toLowerCase() }`,
+ isDisabled: Boolean(hash) && (erc20query.isPlaceholderData || erc721query.isPlaceholderData || erc1155query.isPlaceholderData),
+ });
+
+ useSocketMessage({
+ channel,
+ event: 'updated_token_balances_erc_20',
+ handler: handleTokenBalancesErc20Message,
+ });
+ useSocketMessage({
+ channel,
+ event: 'updated_token_balances_erc_721',
+ handler: handleTokenBalancesErc721Message,
+ });
+ useSocketMessage({
+ channel,
+ event: 'updated_token_balances_erc_1155',
+ handler: handleTokenBalancesErc1155Message,
+ });
const data = React.useMemo(() => {
return {
@@ -52,6 +120,5 @@ export default function useFetchTokens({ hash }: Props) {
isLoading: erc20query.isLoading || erc721query.isLoading || erc1155query.isLoading,
isError: erc20query.isError || erc721query.isError || erc1155query.isError,
data,
- refetch,
};
}
diff --git a/ui/shared/RadioButtonGroup.tsx b/ui/shared/RadioButtonGroup.tsx
index a89005c61a..518e58e579 100644
--- a/ui/shared/RadioButtonGroup.tsx
+++ b/ui/shared/RadioButtonGroup.tsx
@@ -1,11 +1,19 @@
-import { ButtonGroup, Button, Box, useRadio, useRadioGroup, useColorModeValue } from '@chakra-ui/react';
+import { ButtonGroup, Button, Flex, Icon, useRadio, useRadioGroup, useColorModeValue } from '@chakra-ui/react';
import type { UseRadioProps } from '@chakra-ui/react';
import React from 'react';
-type RadioButtonProps = UseRadioProps & {
- children: React.ReactNode;
+type RadioItemProps = {
+ title: string;
+ icon?: React.FC>;
+ onlyIcon: false | undefined;
+} | {
+ title: string;
+ icon: React.FC>;
+ onlyIcon: true;
}
+type RadioButtonProps = UseRadioProps & RadioItemProps;
+
const RadioButton = (props: RadioButtonProps) => {
const { getInputProps, getRadioProps } = useRadio(props);
const buttonColor = useColorModeValue('blue.50', 'gray.800');
@@ -13,30 +21,52 @@ const RadioButton = (props: RadioButtonProps) => {
const input = getInputProps();
const checkbox = getRadioProps();
+ const styleProps = {
+ flex: 1,
+ variant: 'outline',
+ fontWeight: 500,
+ cursor: props.isChecked ? 'initial' : 'pointer',
+ borderColor: buttonColor,
+ backgroundColor: props.isChecked ? buttonColor : 'none',
+ _hover: {
+ borderColor: buttonColor,
+ ...(props.isChecked ? {} : { color: 'link_hovered' }),
+ },
+ _active: {
+ backgroundColor: 'none',
+ },
+ ...(props.isChecked ? { color: 'text' } : {}),
+ };
+
+ if (props.onlyIcon) {
+ return (
+
+ );
+ }
+
return (
: undefined }
+ { ...styleProps }
>
-
- { props.children }
-
+ { props.title }
+
);
};
@@ -45,7 +75,7 @@ type RadioButtonGroupProps = {
onChange: (value: T) => void;
name: string;
defaultValue: string;
- options: Array<{title: string; value: T}>;
+ options: Array<{ value: T } & RadioItemProps>;
}
const RadioButtonGroup = ({ onChange, name, defaultValue, options }: RadioButtonGroupProps) => {
@@ -54,10 +84,10 @@ const RadioButtonGroup = ({ onChange, name, defaultValue, opti
const group = getRootProps();
return (
-
+
{ options.map((option) => {
const props = getRadioProps({ value: option.value });
- return { option.title };
+ return ;
}) }
);