Commit e4b0e47b authored by tom's avatar tom

integrate pagination, filters and sorting with API

parent 89dcb1db
import { Table, Tbody, Tr, Th, Link, Icon } from '@chakra-ui/react'; import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { EnsDomainEventsResponse } from 'types/api/ens'; import type { EnsDomainEventsResponse } from 'types/api/ens';
import arrowIcon from 'icons/arrows/east.svg'; import IconSvg from 'ui/shared/IconSvg';
import { default as Thead } from 'ui/shared/TheadSticky'; import { default as Thead } from 'ui/shared/TheadSticky';
import NameDomainHistoryTableItem from './NameDomainHistoryTableItem'; import NameDomainHistoryTableItem from './NameDomainHistoryTableItem';
...@@ -28,8 +28,8 @@ const NameDomainHistoryTable = ({ data, isLoading, sort, onSortToggle }: Props) ...@@ -28,8 +28,8 @@ const NameDomainHistoryTable = ({ data, isLoading, sort, onSortToggle }: Props)
<Th width="25%" pl={ 9 }> <Th width="25%" pl={ 9 }>
<Link display="flex" alignItems="center" justifyContent="flex-start" position="relative" data-field="timestamp" onClick={ onSortToggle }> <Link display="flex" alignItems="center" justifyContent="flex-start" position="relative" data-field="timestamp" onClick={ onSortToggle }>
{ sort?.includes('timestamp') && ( { sort?.includes('timestamp') && (
<Icon <IconSvg
as={ arrowIcon } name="arrows/east"
boxSize={ 4 } boxSize={ 4 }
transform={ sortIconTransform } transform={ sortIconTransform }
color="link" color="link"
......
...@@ -14,20 +14,8 @@ import Sort from 'ui/shared/sort/Sort'; ...@@ -14,20 +14,8 @@ import Sort from 'ui/shared/sort/Sort';
import type { Sort as TSort } from './utils'; import type { Sort as TSort } from './utils';
import { SORT_OPTIONS } from './utils'; import { SORT_OPTIONS } from './utils';
const pagination: PaginationParams = {
isVisible: true,
isLoading: false,
page: 1,
hasPages: true,
hasNextPage: true,
canGoBackwards: false,
onNextPageClick: () => {},
onPrevPageClick: () => {},
resetPage: () => {},
};
interface Props { interface Props {
pagination?: PaginationParams; pagination: PaginationParams;
searchTerm: string | undefined; searchTerm: string | undefined;
onSearchChange: (value: string) => void; onSearchChange: (value: string) => void;
filterValue: EnsDomainLookupFiltersOptions; filterValue: EnsDomainLookupFiltersOptions;
...@@ -38,7 +26,17 @@ interface Props { ...@@ -38,7 +26,17 @@ interface Props {
isAddressSearch: boolean; isAddressSearch: boolean;
} }
const NameDomainsActionBar = ({ searchTerm, onSearchChange, filterValue, onFilterValueChange, sort, onSortChange, isLoading, isAddressSearch }: Props) => { const NameDomainsActionBar = ({
searchTerm,
onSearchChange,
filterValue,
onFilterValueChange,
sort,
onSortChange,
isLoading,
isAddressSearch,
pagination,
}: Props) => {
const isInitialLoading = useIsInitialLoading(isLoading); const isInitialLoading = useIsInitialLoading(isLoading);
const searchInput = ( const searchInput = (
......
import { Table, Tbody, Tr, Th, Link, Icon } from '@chakra-ui/react'; import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { EnsDomainLookupResponse } from 'types/api/ens'; import type { EnsDomainLookupResponse } from 'types/api/ens';
import arrowIcon from 'icons/arrows/east.svg'; import IconSvg from 'ui/shared/IconSvg';
import { default as Thead } from 'ui/shared/TheadSticky'; import { default as Thead } from 'ui/shared/TheadSticky';
import NameDomainsTableItem from './NameDomainsTableItem'; import NameDomainsTableItem from './NameDomainsTableItem';
...@@ -17,7 +17,7 @@ interface Props { ...@@ -17,7 +17,7 @@ interface Props {
} }
const NameDomainsTable = ({ data, isLoading, sort, onSortToggle }: Props) => { const NameDomainsTable = ({ data, isLoading, sort, onSortToggle }: Props) => {
const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)'; const sortIconTransform = sort?.toLowerCase().includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
return ( return (
<Table variant="simple" size="sm"> <Table variant="simple" size="sm">
...@@ -28,8 +28,8 @@ const NameDomainsTable = ({ data, isLoading, sort, onSortToggle }: Props) => { ...@@ -28,8 +28,8 @@ const NameDomainsTable = ({ data, isLoading, sort, onSortToggle }: Props) => {
<Th width="25%" pl={ 9 }> <Th width="25%" pl={ 9 }>
<Link display="flex" alignItems="center" justifyContent="flex-start" position="relative" data-field="registration_date" onClick={ onSortToggle }> <Link display="flex" alignItems="center" justifyContent="flex-start" position="relative" data-field="registration_date" onClick={ onSortToggle }>
{ sort?.includes('registration_date') && ( { sort?.includes('registration_date') && (
<Icon <IconSvg
as={ arrowIcon } name="arrows/east"
boxSize={ 4 } boxSize={ 4 }
transform={ sortIconTransform } transform={ sortIconTransform }
color="link" color="link"
......
...@@ -2,10 +2,9 @@ import { Box, Hide, Show } from '@chakra-ui/react'; ...@@ -2,10 +2,9 @@ import { Box, Hide, Show } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { EnsDomainLookupFiltersOptions } from 'types/api/ens'; import type { EnsDomainLookupFiltersOptions, EnsLookupSorting } from 'types/api/ens';
import config from 'configs/app'; import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery';
import useDebounce from 'lib/hooks/useDebounce'; import useDebounce from 'lib/hooks/useDebounce';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
...@@ -16,70 +15,87 @@ import NameDomainsActionBar from 'ui/nameDomains/NameDomainsActionBar'; ...@@ -16,70 +15,87 @@ import NameDomainsActionBar from 'ui/nameDomains/NameDomainsActionBar';
import NameDomainsListItem from 'ui/nameDomains/NameDomainsListItem'; import NameDomainsListItem from 'ui/nameDomains/NameDomainsListItem';
import NameDomainsTable from 'ui/nameDomains/NameDomainsTable'; import NameDomainsTable from 'ui/nameDomains/NameDomainsTable';
import type { Sort, SortField } from 'ui/nameDomains/utils'; import type { Sort, SortField } from 'ui/nameDomains/utils';
import { getNextSortValue } from 'ui/nameDomains/utils'; import { SORT_OPTIONS, getNextSortValue } from 'ui/nameDomains/utils';
import DataListDisplay from 'ui/shared/DataListDisplay'; import DataListDisplay from 'ui/shared/DataListDisplay';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import useQueryWithPages from 'ui/shared/pagination/useQueryWithPages';
import getSortParamsFromValue from 'ui/shared/sort/getSortParamsFromValue';
import getSortValueFromQuery from 'ui/shared/sort/getSortValueFromQuery';
const NameDomains = () => { const NameDomains = () => {
const router = useRouter(); const router = useRouter();
const q = getQueryParamString(router.query.q); const q = getQueryParamString(router.query.name) || getQueryParamString(router.query.address);
const ownedBy = getQueryParamString(router.query.owned_by); const ownedBy = getQueryParamString(router.query.owned_by);
const resolvedTo = getQueryParamString(router.query.resolved_to); const resolvedTo = getQueryParamString(router.query.resolved_to);
const withInactive = getQueryParamString(router.query.with_inactive); const onlyActive = getQueryParamString(router.query.only_active);
const initialFilters: EnsDomainLookupFiltersOptions = [ const initialFilters: EnsDomainLookupFiltersOptions = [
ownedBy === 'true' ? 'owned_by' as const : undefined, ownedBy === 'true' ? 'owned_by' as const : undefined,
resolvedTo === 'true' ? 'resolved_to' as const : undefined, resolvedTo === 'true' ? 'resolved_to' as const : undefined,
withInactive === 'true' ? 'with_inactive' as const : undefined, onlyActive === 'false' ? 'with_inactive' as const : undefined,
].filter(Boolean); ].filter(Boolean);
const initialSort = getSortValueFromQuery<Sort>(router.query, SORT_OPTIONS);
const [ searchTerm, setSearchTerm ] = React.useState<string>(q || ''); const [ searchTerm, setSearchTerm ] = React.useState<string>(q || '');
const [ filterValue, setFilterValue ] = React.useState<EnsDomainLookupFiltersOptions>(initialFilters); const [ filterValue, setFilterValue ] = React.useState<EnsDomainLookupFiltersOptions>(initialFilters);
const [ sort, setSort ] = React.useState<Sort>(); const [ sort, setSort ] = React.useState<Sort | undefined>(initialSort);
const debouncedSearchTerm = useDebounce(searchTerm, 300); const debouncedSearchTerm = useDebounce(searchTerm, 300);
const isAddressSearch = React.useMemo(() => ADDRESS_REGEXP.test(debouncedSearchTerm), [ debouncedSearchTerm ]); const isAddressSearch = React.useMemo(() => ADDRESS_REGEXP.test(debouncedSearchTerm), [ debouncedSearchTerm ]);
const sortParam = sort?.split('-')[0]; const sortParams = getSortParamsFromValue<Sort, EnsLookupSorting['sort'], EnsLookupSorting['order']>(sort);
const orderParam = sort?.split('-')[1];
const addressesLookupQuery = useApiQuery('addresses_lookup', { const addressesLookupQuery = useQueryWithPages({
resourceName: 'addresses_lookup',
pathParams: { chainId: config.chain.id }, pathParams: { chainId: config.chain.id },
queryParams: { filters: {
address: debouncedSearchTerm, address: debouncedSearchTerm,
resolved_to: filterValue.includes('resolved_to'), resolved_to: filterValue.includes('resolved_to'),
owned_by: filterValue.includes('owned_by'), owned_by: filterValue.includes('owned_by'),
only_active: !filterValue.includes('with_inactive'), only_active: !filterValue.includes('with_inactive'),
sort: sortParam,
order: orderParam,
}, },
queryOptions: { sorting: sortParams,
options: {
enabled: isAddressSearch, enabled: isAddressSearch,
placeholderData: generateListStub<'addresses_lookup'>(ENS_DOMAIN, 50, { next_page_params: null }), placeholderData: generateListStub<'addresses_lookup'>(ENS_DOMAIN, 50, { next_page_params: null }),
}, },
}); });
const domainsLookupQuery = useApiQuery('domains_lookup', { const domainsLookupQuery = useQueryWithPages({
resourceName: 'domains_lookup',
pathParams: { chainId: config.chain.id }, pathParams: { chainId: config.chain.id },
queryParams: { filters: {
name: debouncedSearchTerm, name: debouncedSearchTerm,
only_active: !filterValue.includes('with_inactive'), only_active: !filterValue.includes('with_inactive'),
sort: sortParam,
order: orderParam,
}, },
queryOptions: { sorting: sortParams,
options: {
enabled: !isAddressSearch, enabled: !isAddressSearch,
placeholderData: generateListStub<'domains_lookup'>(ENS_DOMAIN, 50, { next_page_params: null }), placeholderData: generateListStub<'domains_lookup'>(ENS_DOMAIN, 50, { next_page_params: null }),
}, },
}); });
const query = isAddressSearch ? addressesLookupQuery : domainsLookupQuery; const query = isAddressSearch ? addressesLookupQuery : domainsLookupQuery;
const { data, isError, isPlaceholderData: isLoading } = query; const { data, isError, isPlaceholderData: isLoading, onFilterChange, onSortingChange } = query;
React.useEffect(() => { React.useEffect(() => {
if (isAddressSearch && filterValue.filter((value) => value !== 'with_inactive').length === 0) { const hasInactiveFilter = filterValue.some((value) => value === 'with_inactive');
setFilterValue([ 'owned_by', 'resolved_to' ]); if (isAddressSearch) {
setFilterValue([ 'owned_by' as const, 'resolved_to' as const, hasInactiveFilter ? 'with_inactive' as const : undefined ].filter(Boolean));
onFilterChange<'addresses_lookup'>({
address: debouncedSearchTerm,
resolved_to: true,
owned_by: true,
only_active: !hasInactiveFilter,
});
} else {
setFilterValue([ hasInactiveFilter ? 'with_inactive' as const : undefined ].filter(Boolean));
onFilterChange<'domains_lookup'>({
name: debouncedSearchTerm,
only_active: !hasInactiveFilter,
});
} }
// should run only the type of search changes
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ isAddressSearch ]); }, [ isAddressSearch ]);
...@@ -90,17 +106,50 @@ const NameDomains = () => { ...@@ -90,17 +106,50 @@ const NameDomains = () => {
const field = (event.currentTarget as HTMLDivElement).getAttribute('data-field') as SortField | undefined; const field = (event.currentTarget as HTMLDivElement).getAttribute('data-field') as SortField | undefined;
if (field) { if (field) {
setSort(getNextSortValue(field)); setSort((prevValue) => {
const nextSortValue = getNextSortValue(field)(prevValue);
onSortingChange(getSortParamsFromValue(nextSortValue));
return nextSortValue;
});
} }
}, [ isLoading ]); }, [ isLoading, onSortingChange ]);
const handleSearchTermChange = React.useCallback((value: string) => { const handleSearchTermChange = React.useCallback((value: string) => {
setSearchTerm(value); setSearchTerm(value);
}, []); const isAddressSearch = ADDRESS_REGEXP.test(value);
if (isAddressSearch) {
onFilterChange<'addresses_lookup'>({
address: value,
resolved_to: filterValue.includes('resolved_to'),
owned_by: filterValue.includes('owned_by'),
only_active: !filterValue.includes('with_inactive'),
});
} else {
onFilterChange<'domains_lookup'>({
name: value,
only_active: !filterValue.includes('with_inactive'),
});
}
}, [ onFilterChange, filterValue ]);
const handleFilterValueChange = React.useCallback((value: EnsDomainLookupFiltersOptions) => { const handleFilterValueChange = React.useCallback((value: EnsDomainLookupFiltersOptions) => {
setFilterValue(value); setFilterValue(value);
}, []);
const isAddressSearch = ADDRESS_REGEXP.test(debouncedSearchTerm);
if (isAddressSearch) {
onFilterChange<'addresses_lookup'>({
address: debouncedSearchTerm,
resolved_to: value.includes('resolved_to'),
owned_by: value.includes('owned_by'),
only_active: !value.includes('with_inactive'),
});
} else {
onFilterChange<'domains_lookup'>({
name: debouncedSearchTerm,
only_active: !value.includes('with_inactive'),
});
}
}, [ debouncedSearchTerm, onFilterChange ]);
const hasActiveFilters = Boolean(searchTerm); const hasActiveFilters = Boolean(searchTerm);
...@@ -138,6 +187,7 @@ const NameDomains = () => { ...@@ -138,6 +187,7 @@ const NameDomains = () => {
sort={ sort } sort={ sort }
onSortChange={ setSort } onSortChange={ setSort }
isAddressSearch={ isAddressSearch } isAddressSearch={ isAddressSearch }
pagination={ query.pagination }
/> />
); );
......
...@@ -37,7 +37,7 @@ function getPaginationParamsFromQuery(queryString: string | Array<string> | unde ...@@ -37,7 +37,7 @@ function getPaginationParamsFromQuery(queryString: string | Array<string> | unde
export type QueryWithPagesResult<Resource extends PaginatedResources> = export type QueryWithPagesResult<Resource extends PaginatedResources> =
UseQueryResult<ResourcePayload<Resource>, ResourceError<unknown>> & UseQueryResult<ResourcePayload<Resource>, ResourceError<unknown>> &
{ {
onFilterChange: (filters: PaginationFilters<Resource>) => void; onFilterChange: <R extends PaginatedResources = Resource>(filters: PaginationFilters<R>) => void;
onSortingChange: (sorting?: PaginationSorting<Resource>) => void; onSortingChange: (sorting?: PaginationSorting<Resource>) => void;
pagination: PaginationParams; pagination: PaginationParams;
} }
...@@ -136,12 +136,13 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({ ...@@ -136,12 +136,13 @@ export default function useQueryWithPages<Resource extends PaginatedResources>({
}); });
}, [ queryClient, resourceName, router, scrollToTop ]); }, [ queryClient, resourceName, router, scrollToTop ]);
const onFilterChange = useCallback((newFilters: PaginationFilters<Resource> | undefined) => { const onFilterChange = useCallback(<R extends PaginatedResources = Resource>(newFilters: PaginationFilters<R> | undefined) => {
const newQuery = omit<typeof router.query>(router.query, 'next_page_params', 'page', resource.filterFields); const newQuery = omit<typeof router.query>(router.query, 'next_page_params', 'page', resource.filterFields);
if (newFilters) { if (newFilters) {
Object.entries(newFilters).forEach(([ key, value ]) => { Object.entries(newFilters).forEach(([ key, value ]) => {
if (value && value.length) { const isValidValue = typeof value === 'boolean' || (value && value.length);
newQuery[key] = Array.isArray(value) ? value.join(',') : (value || ''); if (isValidValue) {
newQuery[key] = Array.isArray(value) ? value.join(',') : (String(value) || '');
} }
}); });
} }
......
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