Commit db299586 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend

parents 6c2e0dd6 4c3f1895
export type TTxsFilters = {
filter: 'pending' | 'validated';
type?: Array<TypeFilter>;
method?: Array<MethodFilter>;
}
export type TypeFilter = 'token_transfer' | 'contract_creation' | 'contract_call' | 'coin_transfer' | 'token_creation';
export type MethodFilter = 'approve' | 'transfer' | 'multicall' | 'mint' | 'commit';
export enum QueryKeys { export enum QueryKeys {
csrf = 'csrf', csrf = 'csrf',
profile = 'profile', profile = 'profile',
transactionsPending = 'transactions_pending', transactions = 'transactions',
transactionsValidated = 'transactions_validated',
tx = 'tx', tx = 'tx',
txInternals = 'tx-internals', txInternals = 'tx-internals',
txLog = 'tx-log', txLog = 'tx-log',
......
import { Alert, Box, HStack, Show, Button } from '@chakra-ui/react'; import { Alert, Box, HStack, Show, Button } from '@chakra-ui/react';
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import type { TTxsFilters } from 'types/api/txsFilters';
import type { Sort } from 'types/client/txs-sort'; import type { Sort } from 'types/client/txs-sort';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import FilterButton from 'ui/shared/FilterButton'; // import FilterInput from 'ui/shared/FilterInput';
import FilterInput from 'ui/shared/FilterInput';
import Pagination from 'ui/shared/Pagination'; import Pagination from 'ui/shared/Pagination';
import SortButton from 'ui/shared/SortButton'; import SortButton from 'ui/shared/SortButton';
// import TxsFilters from './TxsFilters';
import TxsSkeletonDesktop from './TxsSkeletonDesktop'; import TxsSkeletonDesktop from './TxsSkeletonDesktop';
import TxsSkeletonMobile from './TxsSkeletonMobile'; import TxsSkeletonMobile from './TxsSkeletonMobile';
import TxsWithSort from './TxsWithSort'; import TxsWithSort from './TxsWithSort';
import useQueryWithPages from './useQueryWithPages'; import useQueryWithPages from './useQueryWithPages';
type Props = { type Props = {
queryName: string;
showDescription?: boolean; showDescription?: boolean;
stateFilter: 'validated' | 'pending'; stateFilter: TTxsFilters['filter'];
} }
const TxsContent = ({ const TxsContent = ({
showDescription, showDescription,
queryName,
stateFilter, stateFilter,
}: Props) => { }: Props) => {
const [ sorting, setSorting ] = useState<Sort>(); const [ sorting, setSorting ] = useState<Sort>();
// const [ filters, setFilters ] = useState<Partial<TTxsFilters>>({ type: [], method: [] });
const sort = useCallback((field: 'val' | 'fee') => () => { const sort = useCallback((field: 'val' | 'fee') => () => {
if (field === 'val') { if (field === 'val') {
...@@ -62,7 +62,8 @@ const TxsContent = ({ ...@@ -62,7 +62,8 @@ const TxsContent = ({
onNextPageClick, onNextPageClick,
hasPagination, hasPagination,
resetPage, resetPage,
} = useQueryWithPages(queryName, stateFilter); } = useQueryWithPages({ filter: stateFilter });
// } = useQueryWithPages({ ...filters, filter: stateFilter });
const isMobile = useIsMobile(false); const isMobile = useIsMobile(false);
...@@ -91,13 +92,12 @@ const TxsContent = ({ ...@@ -91,13 +92,12 @@ const TxsContent = ({
<> <>
{ showDescription && <Box mb={ 12 }>Only the first 10,000 elements are displayed</Box> } { showDescription && <Box mb={ 12 }>Only the first 10,000 elements are displayed</Box> }
<HStack mb={ 6 }> <HStack mb={ 6 }>
{ /* TODO */ } { /* api is not implemented */ }
<FilterButton { /* <TxsFilters
isActive={ false } filters={ filters }
// eslint-disable-next-line react/jsx-no-bind onFiltersChange={ setFilters }
onClick={ () => {} }
appliedFiltersNum={ 0 } appliedFiltersNum={ 0 }
/> /> */ }
{ isMobile && ( { isMobile && (
<SortButton <SortButton
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
...@@ -106,13 +106,14 @@ const TxsContent = ({ ...@@ -106,13 +106,14 @@ const TxsContent = ({
display={{ base: 'block', lg: 'none' }} display={{ base: 'block', lg: 'none' }}
/> />
) } ) }
<FilterInput { /* api is not implemented */ }
{ /* <FilterInput
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onChange={ () => {} } onChange={ () => {} }
maxW="360px" maxW="360px"
size="xs" size="xs"
placeholder="Search by addresses, hash, method..." placeholder="Search by addresses, hash, method..."
/> /> */ }
</HStack> </HStack>
{ content } { content }
<Box mx={{ base: 0, lg: 6 }} my={{ base: 6, lg: 3 }}> <Box mx={{ base: 0, lg: 6 }} my={{ base: 6, lg: 3 }}>
......
import {
Button,
Checkbox,
CheckboxGroup,
Grid,
Link,
Popover,
PopoverTrigger,
PopoverContent,
PopoverBody,
Text,
useColorModeValue,
useDisclosure,
Flex,
} from '@chakra-ui/react';
import React, { useCallback, useState } from 'react';
import type { TTxsFilters, TypeFilter, MethodFilter } from 'types/api/txsFilters';
import FilterButton from 'ui/shared/FilterButton';
interface Props {
appliedFiltersNum?: number;
filters: Partial<TTxsFilters>;
onFiltersChange: (val: Partial<TTxsFilters>) => void;
}
const TYPE_OPTIONS = [
{ title: 'Token transfer', id: 'token_transfer' },
{ title: 'Contract Creation', id: 'contract_creation' },
{ title: 'Contract Call', id: 'contract_call' },
{ title: 'Coin Transfer', id: 'coin_transfer' },
{ title: 'Token Creation', id: 'token_creation' },
];
const METHOD_OPTIONS = [
{ title: 'Approve', id: 'approve' },
{ title: 'Transfer', id: 'transfer' },
{ title: 'Multicall', id: 'multicall' },
{ title: 'Mint', id: 'mint' },
{ title: 'Commit', id: 'commit' },
];
// TODO: i think we need to reload page after applying filters,
// because we need to reset pagination, clear query caches, reconnect websocket...
// also mobile version of filters is not implemented
const TxsFilters = ({ onFiltersChange, filters, appliedFiltersNum }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure();
const [ typeFilter, setTypeFilter ] = useState<Array<TypeFilter>>(filters.type || []);
const [ methodFilter, setMethodFilter ] = useState<Array<MethodFilter>>(filters.method || []);
const onTypeFilterChange = useCallback((val: Array<TypeFilter>) => {
setTypeFilter(val);
}, []);
const onMethodFilterChange = useCallback((val: Array<MethodFilter>) => {
setMethodFilter(val);
}, []);
const onFilterReset = useCallback(() => {
setTypeFilter([]);
setMethodFilter([]);
onFiltersChange({ type: [], method: [] });
onClose();
}, [ onClose, onFiltersChange ]);
const onFilterApply = useCallback(() => {
onFiltersChange({ type: typeFilter, method: methodFilter } as Partial<TTxsFilters>);
onClose();
}, [ onClose, onFiltersChange, typeFilter, methodFilter ]);
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
<PopoverTrigger>
<FilterButton
isActive={ isOpen || Number(appliedFiltersNum) > 0 }
onClick={ onToggle }
appliedFiltersNum={ appliedFiltersNum }
/>
</PopoverTrigger>
<PopoverContent w={{ md: '100%', lg: '438px' }}>
<PopoverBody px={ 4 } py={ 6 }>
<Text variant="secondary" fontWeight="600" fontSize="sm">Type</Text>
<Grid gridTemplateColumns="1fr 1fr" rowGap={ 5 } mt={ 4 } mb={ 4 } pb={ 6 } borderBottom="1px solid" borderColor={ borderColor }>
<CheckboxGroup size="lg" onChange={ onTypeFilterChange } defaultValue={ typeFilter }>
{ TYPE_OPTIONS.map(({ title, id }) => <Checkbox key={ id } value={ id }><Text fontSize="md">{ title }</Text></Checkbox>) }
</CheckboxGroup>
</Grid>
<Text variant="secondary" fontWeight="600" fontSize="sm">Method</Text>
<Grid gridTemplateColumns="1fr 1fr" rowGap={ 5 } mt={ 4 } mb={ 4 } pb={ 6 } borderBottom="1px solid" borderColor={ borderColor }>
<CheckboxGroup size="lg" onChange={ onMethodFilterChange } defaultValue={ methodFilter }>
{ METHOD_OPTIONS.map(({ title, id }) => <Checkbox key={ id } value={ id }><Text fontSize="md">{ title }</Text></Checkbox>) }
</CheckboxGroup>
</Grid>
<Flex alignItems="center" justifyContent="space-between">
<Link fontSize="sm" onClick={ onFilterReset }>Reset filters</Link>
<Button variant="outline" size="sm" onClick={ onFilterApply }>Apply</Button>
</Flex>
</PopoverBody>
</PopoverContent>
</Popover>
);
};
export default React.memo(TxsFilters);
import React from 'react'; import React from 'react';
import { QueryKeys } from 'types/client/queries';
import TxsContent from './TxsContent'; import TxsContent from './TxsContent';
type Props = { type Props = {
...@@ -11,7 +9,6 @@ type Props = { ...@@ -11,7 +9,6 @@ type Props = {
const TxsTab = ({ tab }: Props) => { const TxsTab = ({ tab }: Props) => {
return ( return (
<TxsContent <TxsContent
queryName={ tab === 'validated' ? QueryKeys.transactionsValidated : QueryKeys.transactionsPending }
showDescription={ tab === 'validated' } showDescription={ tab === 'validated' }
stateFilter={ tab } stateFilter={ tab }
/> />
......
...@@ -5,12 +5,14 @@ import React, { useCallback } from 'react'; ...@@ -5,12 +5,14 @@ import React, { useCallback } from 'react';
import { animateScroll } from 'react-scroll'; import { animateScroll } from 'react-scroll';
import type { TransactionsResponse } from 'types/api/transaction'; import type { TransactionsResponse } from 'types/api/transaction';
import type { TTxsFilters } from 'types/api/txsFilters';
import { QueryKeys } from 'types/client/queries';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
const PAGINATION_FIELDS = [ 'block_number', 'index', 'items_count' ]; const PAGINATION_FIELDS = [ 'block_number', 'index', 'items_count' ];
export default function useQueryWithPages(queryName: string, filter: string) { export default function useQueryWithPages(filters: TTxsFilters) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter(); const router = useRouter();
const [ page, setPage ] = React.useState(1); const [ page, setPage ] = React.useState(1);
...@@ -19,13 +21,19 @@ export default function useQueryWithPages(queryName: string, filter: string) { ...@@ -19,13 +21,19 @@ export default function useQueryWithPages(queryName: string, filter: string) {
const fetch = useFetch(); const fetch = useFetch();
const { data, isLoading, isError } = useQuery<unknown, unknown, TransactionsResponse>( const { data, isLoading, isError } = useQuery<unknown, unknown, TransactionsResponse>(
[ queryName, { page } ], [ QueryKeys.transactions, { page, filters } ],
async() => { async() => {
const params: Array<string> = []; const params: Array<string> = [];
Object.entries(currPageParams).forEach(([ key, val ]) => params.push(`${ key }=${ val }`)); Object.entries({ ...filters, ...currPageParams }).forEach(([ key, val ]) => {
if (Array.isArray(val)) {
val.length && params.push(`${ key }=${ val.join(',') }`);
} else if (val) {
params.push(`${ key }=${ val }`);
}
});
return fetch(`/api/transactions?filter=${ filter }${ params.length ? '&' + params.join('&') : '' }`); return fetch(`/api/transactions${ params.length ? '?' + params.join('&') : '' }`);
}, },
{ staleTime: Infinity }, { staleTime: Infinity },
); );
......
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