Commit b1942126 authored by isstuev's avatar isstuev

add filters and tests

parent dd630ffd
...@@ -13,8 +13,8 @@ import AddressTokens from './AddressTokens'; ...@@ -13,8 +13,8 @@ import AddressTokens from './AddressTokens';
const ADDRESS_HASH = addressMock.withName.hash; const ADDRESS_HASH = addressMock.withName.hash;
const API_URL_ADDRESS = buildApiUrl('address', { hash: ADDRESS_HASH }); const API_URL_ADDRESS = buildApiUrl('address', { hash: ADDRESS_HASH });
const API_URL_TOKENS = buildApiUrl('address_tokens', { 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_NFT = buildApiUrl('address_nfts', { hash: ADDRESS_HASH }) + '?type=';
const API_URL_COLLECTIONS = buildApiUrl('address_collections', { hash: ADDRESS_HASH }); const API_URL_COLLECTIONS = buildApiUrl('address_collections', { hash: ADDRESS_HASH }) + '?type=';
const nextPageParams = { const nextPageParams = {
items_count: 50, items_count: 50,
......
...@@ -19,7 +19,7 @@ import PopoverFilter from 'ui/shared/filters/PopoverFilter'; ...@@ -19,7 +19,7 @@ import PopoverFilter from 'ui/shared/filters/PopoverFilter';
import TokenTypeFilter from 'ui/shared/filters/TokenTypeFilter'; import TokenTypeFilter from 'ui/shared/filters/TokenTypeFilter';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages'; import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import RadioButtonGroup from 'ui/shared/RadioButtonGroup'; import RadioButtonGroup from 'ui/shared/radioButtonGroup/RadioButtonGroup';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import AddressCollections from './tokens/AddressCollections'; import AddressCollections from './tokens/AddressCollections';
...@@ -73,7 +73,6 @@ const AddressTokens = () => { ...@@ -73,7 +73,6 @@ const AddressTokens = () => {
scrollRef, scrollRef,
options: { options: {
enabled: tab === 'tokens_nfts' && nftDisplayType === 'collection', enabled: tab === 'tokens_nfts' && nftDisplayType === 'collection',
refetchOnMount: false,
placeholderData: generateListStub<'address_collections'>(ADDRESS_COLLECTION, 10, { next_page_params: null }), placeholderData: generateListStub<'address_collections'>(ADDRESS_COLLECTION, 10, { next_page_params: null }),
}, },
filters: { type: tokenTypes }, filters: { type: tokenTypes },
...@@ -85,7 +84,6 @@ const AddressTokens = () => { ...@@ -85,7 +84,6 @@ const AddressTokens = () => {
scrollRef, scrollRef,
options: { options: {
enabled: tab === 'tokens_nfts' && nftDisplayType === 'list', enabled: tab === 'tokens_nfts' && nftDisplayType === 'list',
refetchOnMount: false,
placeholderData: generateListStub<'address_nfts'>(ADDRESS_NFT_1155, 10, { next_page_params: null }), placeholderData: generateListStub<'address_nfts'>(ADDRESS_NFT_1155, 10, { next_page_params: null }),
}, },
filters: { type: tokenTypes }, filters: { type: tokenTypes },
...@@ -108,14 +106,16 @@ const AddressTokens = () => { ...@@ -108,14 +106,16 @@ const AddressTokens = () => {
</PopoverFilter> </PopoverFilter>
); );
const hasActiveFilters = Boolean(tokenTypes?.length);
const tabs = [ const tabs = [
{ id: 'tokens_erc20', title: 'ERC-20', component: <ERC20Tokens tokensQuery={ erc20Query }/> }, { id: 'tokens_erc20', title: 'ERC-20', component: <ERC20Tokens tokensQuery={ erc20Query }/> },
{ {
id: 'tokens_nfts', id: 'tokens_nfts',
title: 'NFTs', title: 'NFTs',
component: nftDisplayType === 'list' ? component: nftDisplayType === 'list' ?
<AddressNFTs tokensQuery={ nftsQuery }/> : <AddressNFTs tokensQuery={ nftsQuery } hasActiveFilters={ hasActiveFilters }/> :
<AddressCollections collectionsQuery={ collectionsQuery } address={ hash }/>, <AddressCollections collectionsQuery={ collectionsQuery } address={ hash } hasActiveFilters={ hasActiveFilters }/>,
}, },
]; ];
...@@ -139,11 +139,17 @@ const AddressTokens = () => { ...@@ -139,11 +139,17 @@ const AddressTokens = () => {
pagination = erc20Query.pagination; pagination = erc20Query.pagination;
} }
const hasNftData =
(!nftsQuery.isPlaceholderData && nftsQuery.data?.items.length) ||
(!collectionsQuery.isPlaceholderData && collectionsQuery.data?.items.length);
const isNftTab = tab !== 'tokens' && tab !== 'tokens_erc20';
const rightSlot = ( const rightSlot = (
<> <>
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
{ tab !== 'tokens' && tab !== 'tokens_erc20' && nftDisplayTypeRadio } { isNftTab && (hasNftData || hasActiveFilters) && nftDisplayTypeRadio }
{ tab !== 'tokens' && tab !== 'tokens_erc20' && nftTypeFilter } { isNftTab && (hasNftData || hasActiveFilters) && nftTypeFilter }
</HStack> </HStack>
{ pagination.isVisible && !isMobile && <Pagination { ...pagination }/> } { pagination.isVisible && !isMobile && <Pagination { ...pagination }/> }
</> </>
......
...@@ -4,6 +4,7 @@ import React from 'react'; ...@@ -4,6 +4,7 @@ import React from 'react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { apos } from 'lib/html-entities';
import ActionBar from 'ui/shared/ActionBar'; import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
import TokenEntity from 'ui/shared/entities/token/TokenEntity'; import TokenEntity from 'ui/shared/entities/token/TokenEntity';
...@@ -18,9 +19,10 @@ import NFTItemContainer from './NFTItemContainer'; ...@@ -18,9 +19,10 @@ import NFTItemContainer from './NFTItemContainer';
type Props = { type Props = {
collectionsQuery: QueryWithPagesResult<'address_collections'>; collectionsQuery: QueryWithPagesResult<'address_collections'>;
address: string; address: string;
hasActiveFilters: boolean;
} }
const AddressCollections = ({ collectionsQuery, address }: Props) => { const AddressCollections = ({ collectionsQuery, address, hasActiveFilters }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const { isError, isPlaceholderData, data, pagination } = collectionsQuery; const { isError, isPlaceholderData, data, pagination } = collectionsQuery;
...@@ -56,11 +58,9 @@ const AddressCollections = ({ collectionsQuery, address }: Props) => { ...@@ -56,11 +58,9 @@ const AddressCollections = ({ collectionsQuery, address }: Props) => {
<Skeleton isLoaded={ !isPlaceholderData } mr={ 3 }> <Skeleton isLoaded={ !isPlaceholderData } mr={ 3 }>
<Text variant="secondary" whiteSpace="pre">{ ` - ${ Number(item.amount).toLocaleString() } item${ Number(item.amount) > 1 ? 's' : '' }` }</Text> <Text variant="secondary" whiteSpace="pre">{ ` - ${ Number(item.amount).toLocaleString() } item${ Number(item.amount) > 1 ? 's' : '' }` }</Text>
</Skeleton> </Skeleton>
{ hasOverload && ( <LinkInternal href={ collectionUrl } isLoading={ isPlaceholderData }>
<LinkInternal href={ collectionUrl } isLoading={ isPlaceholderData }> <Skeleton isLoaded={ !isPlaceholderData }>View in collection</Skeleton>
<Skeleton isLoaded={ !isPlaceholderData }>View in collection</Skeleton> </LinkInternal>
</LinkInternal>
) }
</Flex> </Flex>
<Grid <Grid
w="100%" w="100%"
...@@ -105,6 +105,10 @@ const AddressCollections = ({ collectionsQuery, address }: Props) => { ...@@ -105,6 +105,10 @@ const AddressCollections = ({ collectionsQuery, address }: Props) => {
emptyText="There are no tokens of selected type." emptyText="There are no tokens of selected type."
content={ content } content={ content }
actionBar={ actionBar } actionBar={ actionBar }
filterProps={{
emptyFilteredText: `Couldn${ apos }t find any token that matches your query.`,
hasActiveFilters,
}}
/> />
); );
}; };
......
...@@ -2,6 +2,7 @@ import { Grid } from '@chakra-ui/react'; ...@@ -2,6 +2,7 @@ import { Grid } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { apos } from 'lib/html-entities';
import ActionBar from 'ui/shared/ActionBar'; import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/pagination/Pagination'; import Pagination from 'ui/shared/pagination/Pagination';
...@@ -11,9 +12,10 @@ import NFTItem from './NFTItem'; ...@@ -11,9 +12,10 @@ import NFTItem from './NFTItem';
type Props = { type Props = {
tokensQuery: QueryWithPagesResult<'address_nfts'>; tokensQuery: QueryWithPagesResult<'address_nfts'>;
hasActiveFilters: boolean;
} }
const AddressNFTs = ({ tokensQuery }: Props) => { const AddressNFTs = ({ tokensQuery, hasActiveFilters }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const { isError, isPlaceholderData, data, pagination } = tokensQuery; const { isError, isPlaceholderData, data, pagination } = tokensQuery;
...@@ -53,6 +55,10 @@ const AddressNFTs = ({ tokensQuery }: Props) => { ...@@ -53,6 +55,10 @@ const AddressNFTs = ({ tokensQuery }: Props) => {
emptyText="There are no tokens of selected type." emptyText="There are no tokens of selected type."
content={ content } content={ content }
actionBar={ actionBar } actionBar={ actionBar }
filterProps={{
emptyFilteredText: `Couldn${ apos }t find any token that matches your query.`,
hasActiveFilters,
}}
/> />
); );
}; };
......
...@@ -6,7 +6,7 @@ type Props = { ...@@ -6,7 +6,7 @@ type Props = {
className?: string; className?: string;
}; };
const NFTItem = ({ children, className }: Props) => { const NFTItemContainer = ({ children, className }: Props) => {
return ( return (
<Box <Box
w={{ base: '100%', lg: '210px' }} w={{ base: '100%', lg: '210px' }}
...@@ -24,4 +24,4 @@ const NFTItem = ({ children, className }: Props) => { ...@@ -24,4 +24,4 @@ const NFTItem = ({ children, className }: Props) => {
); );
}; };
export default chakra(NFTItem); export default chakra(NFTItemContainer);
...@@ -105,7 +105,7 @@ const Tokens = () => { ...@@ -105,7 +105,7 @@ const Tokens = () => {
</PopoverFilter> </PopoverFilter>
) : ( ) : (
<PopoverFilter isActive={ tokenTypes && tokenTypes.length > 0 } contentProps={{ w: '200px' }} appliedFiltersNum={ tokenTypes?.length }> <PopoverFilter isActive={ tokenTypes && tokenTypes.length > 0 } contentProps={{ w: '200px' }} appliedFiltersNum={ tokenTypes?.length }>
<TokenTypeFilter onChange={ handleTokenTypesChange } defaultValue={ tokenTypes } nftOnly={ false }/> <TokenTypeFilter<TokenType> onChange={ handleTokenTypesChange } defaultValue={ tokenTypes } nftOnly={ false }/>
</PopoverFilter> </PopoverFilter>
); );
......
...@@ -56,7 +56,7 @@ const TokenTransferFilter = ({ ...@@ -56,7 +56,7 @@ const TokenTransferFilter = ({
</> </>
) } ) }
<Text variant="secondary" fontWeight={ 600 }>Type</Text> <Text variant="secondary" fontWeight={ 600 }>Type</Text>
<TokenTypeFilter onChange={ onTypeFilterChange } defaultValue={ defaultTypeFilters } nftOnly={ false }/> <TokenTypeFilter<TokenType> onChange={ onTypeFilterChange } defaultValue={ defaultTypeFilters } nftOnly={ false }/>
</PopoverFilter> </PopoverFilter>
); );
}; };
......
import { Box } from '@chakra-ui/react';
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import TestApp from 'playwright/TestApp';
import RadioButtonGroupTest from './specs/RadioButtonGroupTest';
test('radio button group', async({ mount }) => {
const component = await mount(
<TestApp>
<Box w="400px" p="10px">
<RadioButtonGroupTest/>
</Box>
</TestApp>,
);
await expect(component).toHaveScreenshot();
});
...@@ -85,7 +85,7 @@ const RadioButtonGroup = <T extends string>({ onChange, name, defaultValue, opti ...@@ -85,7 +85,7 @@ const RadioButtonGroup = <T extends string>({ onChange, name, defaultValue, opti
const group = getRootProps(); const group = getRootProps();
return ( return (
<ButtonGroup { ...group } isAttached size="sm" display="grid" gridTemplateColumns="1fr 1fr"> <ButtonGroup { ...group } isAttached size="sm" display="grid" gridTemplateColumns={ `repeat(${ options.length }, 1fr)` }>
{ options.map((option) => { { options.map((option) => {
const props = getRadioProps({ value: option.value }); const props = getRadioProps({ value: option.value });
return <RadioButton { ...props } key={ option.value } { ...option }/>; return <RadioButton { ...props } key={ option.value } { ...option }/>;
......
import React from 'react';
import RadioButtonGroup from '../RadioButtonGroup';
const TestIcon = ({ className }: {className?: string}) => {
return (
<svg viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg" className={ className }>
{ /* eslint-disable-next-line max-len */ }
<path fillRule="evenodd" clipRule="evenodd" d="M3.5 11a7.5 7.5 0 1 1 15 0 7.5 7.5 0 0 1-15 0ZM11 1C5.477 1 1 5.477 1 11s4.477 10 10 10 10-4.477 10-10S16.523 1 11 1Zm1.25 5a1.25 1.25 0 1 0-2.5 0v5c0 .69.56 1.25 1.25 1.25h5a1.25 1.25 0 1 0 0-2.5h-3.75V6Z" fill="currentColor" stroke="transparent" strokeWidth=".6" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
};
type Test = 'v1' | 'v2' | 'v3';
const RadioButtonGroupTest = () => {
return (
<RadioButtonGroup<Test>
// eslint-disable-next-line react/jsx-no-bind
onChange={ () => {} }
defaultValue="v1"
name="test"
options={ [
{ value: 'v1', title: 'test option 1', icon: TestIcon, onlyIcon: false },
{ value: 'v2', title: 'test 2', onlyIcon: false },
{ value: 'v2', title: 'test 2', icon: TestIcon, onlyIcon: true },
] }
/>
);
};
export default RadioButtonGroupTest;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment