Commit b51bbe4a authored by tom's avatar tom

name domains pages

parent f566c916
......@@ -5,12 +5,12 @@ import React from 'react';
import type { Props } from 'nextjs/getServerSideProps';
import PageNextJs from 'nextjs/PageNextJs';
// const NameDomain = dynamic(() => import('ui/pages/NameDomain'), { ssr: false });
const NameDomain = dynamic(() => import('ui/pages/NameDomain'), { ssr: false });
const Page: NextPage<Props> = (props: Props) => {
return (
<PageNextJs pathname="/name-domains/[name]" query={ props.query }>
{ /* <NameDomain/> */ }
<NameDomain/>
</PageNextJs>
);
};
......
......@@ -4,12 +4,12 @@ import React from 'react';
import PageNextJs from 'nextjs/PageNextJs';
// const NameDomains = dynamic(() => import('ui/pages/NameDomains'), { ssr: false });
const NameDomains = dynamic(() => import('ui/pages/NameDomains'), { ssr: false });
const Page: NextPage = () => {
return (
<PageNextJs pathname="/name-domains">
{ /* <NameDomains/> */ }
<NameDomains/>
</PageNextJs>
);
};
......
......@@ -26,11 +26,12 @@ export interface LinkProps extends Pick<NextLinkProps, 'shallow' | 'prefetch' |
external?: boolean;
iconColor?: ChakraLinkProps['color'];
noIcon?: boolean;
disabled?: boolean;
}
export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
function Link(props, ref) {
const { external, loading, href, children, scroll = true, iconColor, noIcon, shallow, prefetch = false, ...rest } = props;
const { external, loading, href, children, scroll = true, iconColor, noIcon, shallow, prefetch = false, disabled, ...rest } = props;
if (external) {
return (
......@@ -41,6 +42,7 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
className="group"
target="_blank"
rel="noopener noreferrer"
{ ...(disabled ? { 'data-disabled': true } : {}) }
{ ...rest }
>
{ children }
......@@ -52,7 +54,12 @@ export const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
return (
<Skeleton loading={ loading } asChild>
<ChakraLink asChild ref={ ref } { ...rest }>
<ChakraLink
asChild
ref={ ref }
{ ...(disabled ? { 'data-disabled': true } : {}) }
{ ...rest }
>
{ href ? (
<NextLink
href={ href as NextLinkProps['href'] }
......
......@@ -3,6 +3,9 @@ import { defineRecipe } from '@chakra-ui/react';
export const recipe = defineRecipe({
base: {
gap: 0,
_disabled: {
cursor: 'not-allowed',
},
},
variants: {
variant: {
......@@ -12,6 +15,9 @@ export const recipe = defineRecipe({
textDecoration: 'none',
color: 'link.primary.hover',
},
_disabled: {
color: 'text.secondary',
},
},
secondary: {
color: 'link.secondary',
......
import { Grid, Tooltip, Flex } from '@chakra-ui/react';
import { Grid, Flex } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query';
import React from 'react';
......@@ -10,12 +10,13 @@ import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources';
import dayjs from 'lib/date/dayjs';
import stripTrailingSlash from 'lib/stripTrailingSlash';
import Skeleton from 'ui/shared/chakra/Skeleton';
import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { Tooltip } from 'toolkit/chakra/tooltip';
import * as DetailedInfo from 'ui/shared/DetailedInfo/DetailedInfo';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import NftEntity from 'ui/shared/entities/nft/NftEntity';
import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal';
import TextSeparator from 'ui/shared/TextSeparator';
import NameDomainDetailsAlert from './details/NameDomainDetailsAlert';
......@@ -45,7 +46,7 @@ const NameDomainDetails = ({ query }: Props) => {
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue>
<IconSvg name="clock" boxSize={ 5 } color="gray.500" verticalAlign="middle" isLoading={ isLoading } mr={ 2 }/>
<Skeleton isLoaded={ !isLoading } display="inline" whiteSpace="pre-wrap" lineHeight="20px">
<Skeleton loading={ isLoading } display="inline" whiteSpace="pre-wrap" lineHeight="20px">
{ dayjs(query.data.registration_date).format('llll') }
</Skeleton>
</DetailedInfo.ItemValue>
......@@ -65,17 +66,17 @@ const NameDomainDetails = ({ query }: Props) => {
<IconSvg name="clock" boxSize={ 5 } color="gray.500" verticalAlign="middle" isLoading={ isLoading } mr={ 2 } mt="-2px"/>
{ hasExpired && (
<>
<Skeleton isLoaded={ !isLoading } display="inline" whiteSpace="pre-wrap" lineHeight="24px">
<Skeleton loading={ isLoading } display="inline" whiteSpace="pre-wrap" lineHeight="24px">
{ dayjs(query.data.expiry_date).fromNow() }
</Skeleton>
<TextSeparator color="gray.500"/>
</>
) }
<Skeleton isLoaded={ !isLoading } display="inline" whiteSpace="pre-wrap" lineHeight="24px">
<Skeleton loading={ isLoading } display="inline" whiteSpace="pre-wrap" lineHeight="24px">
{ dayjs(query.data.expiry_date).format('llll') }
</Skeleton>
<TextSeparator color="gray.500"/>
<Skeleton isLoaded={ !isLoading } color="text_secondary" display="inline">
<Skeleton loading={ isLoading } color="text_secondary" display="inline">
<NameDomainExpiryStatus date={ query.data?.expiry_date }/>
</Skeleton>
</DetailedInfo.ItemValue>
......@@ -116,14 +117,14 @@ const NameDomainDetails = ({ query }: Props) => {
address={ query.data.registrant }
isLoading={ isLoading }
/>
<Tooltip label="Lookup for related domain names">
<LinkInternal
<Tooltip content="Lookup for related domain names">
<Link
flexShrink={ 0 }
display="inline-flex"
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: query.data.registrant.hash } }) }
>
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Link>
</Tooltip>
</DetailedInfo.ItemValue>
</>
......@@ -145,14 +146,14 @@ const NameDomainDetails = ({ query }: Props) => {
address={ query.data.owner }
isLoading={ isLoading }
/>
<Tooltip label="Lookup for related domain names">
<LinkInternal
<Tooltip content="Lookup for related domain names">
<Link
flexShrink={ 0 }
display="inline-flex"
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: query.data.owner.hash } }) }
>
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Link>
</Tooltip>
</DetailedInfo.ItemValue>
</>
......@@ -174,14 +175,14 @@ const NameDomainDetails = ({ query }: Props) => {
address={ query.data.wrapped_owner }
isLoading={ isLoading }
/>
<Tooltip label="Lookup for related domain names">
<LinkInternal
<Tooltip content="Lookup for related domain names">
<Link
flexShrink={ 0 }
display="inline-flex"
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: query.data.wrapped_owner.hash } }) }
>
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Link>
</Tooltip>
</DetailedInfo.ItemValue>
</>
......@@ -229,7 +230,7 @@ const NameDomainDetails = ({ query }: Props) => {
>
{ otherAddresses.map(([ type, address ]) => (
<Flex key={ type } columnGap={ 2 } minW="0" w="100%" overflow="hidden">
<Skeleton isLoaded={ !isLoading }>{ type }</Skeleton>
<Skeleton loading={ isLoading }>{ type }</Skeleton>
<AddressEntity
address={{ hash: address }}
isLoading={ isLoading }
......
import { Box, Hide, Show } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -22,7 +22,7 @@ const NameDomainHistory = ({ domain }: Props) => {
const router = useRouter();
const domainName = getQueryParamString(router.query.name);
const [ sort, setSort ] = React.useState<Sort>();
const [ sort, setSort ] = React.useState<Sort>('default');
const { isPlaceholderData, isError, data } = useApiQuery('domain_events', {
pathParams: { name: domainName, chainId: config.chain.id },
......@@ -44,8 +44,7 @@ const NameDomainHistory = ({ domain }: Props) => {
const content = (
<>
<Show below="lg" ssr={ false }>
<Box>
<Box hideFrom="lg">
{ data?.items.map((item, index) => (
<NameDomainHistoryListItem
key={ index }
......@@ -55,8 +54,7 @@ const NameDomainHistory = ({ domain }: Props) => {
/>
)) }
</Box>
</Show>
<Hide below="lg" ssr={ false }>
<Box hideBelow="lg">
<NameDomainHistoryTable
history={ data }
domain={ domain }
......@@ -64,17 +62,18 @@ const NameDomainHistory = ({ domain }: Props) => {
sort={ sort }
onSortToggle={ handleSortToggle }
/>
</Hide>
</Box>
</>
);
return (
<DataListDisplay
isError={ isError }
items={ data?.items }
itemsNum={ data?.items.length }
emptyText="There are no events for this domain."
content={ content }
/>
>
{ content }
</DataListDisplay>
);
};
......
import { Alert } from '@chakra-ui/react';
import React from 'react';
import type * as bens from '@blockscout/bens-types';
import LinkExternal from 'ui/shared/links/LinkExternal';
import { Alert } from 'toolkit/chakra/alert';
import { Link } from 'toolkit/chakra/link';
interface Props {
data: bens.DetailedDomain | undefined;
......@@ -18,9 +18,9 @@ const NameDomainDetailsAlert = ({ data }: Props) => {
return (
<Alert status="info" colorScheme="gray" display="inline-block" whiteSpace="pre-wrap" mb={ 6 }>
<span>The domain name is resolved offchain using </span>
{ data.stored_offchain && <LinkExternal href="https://eips.ethereum.org/EIPS/eip-3668">EIP-3668: CCIP Read</LinkExternal> }
{ data.stored_offchain && <Link external href="https://eips.ethereum.org/EIPS/eip-3668">EIP-3668: CCIP Read</Link> }
{ data.stored_offchain && data.resolved_with_wildcard && <span> & </span> }
{ data.resolved_with_wildcard && <LinkExternal href="https://eips.ethereum.org/EIPS/eip-2544">EIP-2544: Wildcard Resolution</LinkExternal> }
{ data.resolved_with_wildcard && <Link external href="https://eips.ethereum.org/EIPS/eip-2544">EIP-2544: Wildcard Resolution</Link> }
</Alert>
);
};
......
......@@ -6,7 +6,7 @@ import { route } from 'nextjs-routes';
import config from 'configs/app';
import stripTrailingSlash from 'lib/stripTrailingSlash';
import Tag from 'ui/shared/chakra/Tag';
import { Badge } from 'toolkit/chakra/badge';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
......@@ -58,7 +58,7 @@ const NameDomainHistoryListItem = ({ isLoading, domain, event }: Props) => {
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>Method</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Tag colorScheme="gray" isLoading={ isLoading }>{ event.action }</Tag>
<Badge colorPalette="gray" loading={ isLoading }>{ event.action }</Badge>
</ListItemMobileGrid.Value>
</>
) }
......
import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react';
import React from 'react';
import type * as bens from '@blockscout/bens-types';
import { Link } from 'toolkit/chakra/link';
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import IconSvg from 'ui/shared/IconSvg';
import { default as Thead } from 'ui/shared/TheadSticky';
import NameDomainHistoryTableItem from './NameDomainHistoryTableItem';
import type { Sort } from './utils';
......@@ -22,11 +22,11 @@ const NameDomainHistoryTable = ({ history, domain, isLoading, sort, onSortToggle
const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
return (
<Table>
<Thead top={ 0 }>
<Tr>
<Th width="25%">Txn hash</Th>
<Th width="25%" pl={ 9 }>
<TableRoot>
<TableHeaderSticky top={ 0 }>
<TableRow>
<TableColumnHeader width="25%">Txn hash</TableColumnHeader>
<TableColumnHeader width="25%" pl={ 9 }>
<Link display="flex" alignItems="center" justifyContent="flex-start" position="relative" data-field="timestamp" onClick={ onSortToggle }>
{ sort?.includes('timestamp') && (
<IconSvg
......@@ -41,20 +41,20 @@ const NameDomainHistoryTable = ({ history, domain, isLoading, sort, onSortToggle
) }
<span>Age</span>
</Link>
</Th>
<Th width="25%">From</Th>
<Th width="25%">Method</Th>
</Tr>
</Thead>
<Tbody>
</TableColumnHeader>
<TableColumnHeader width="25%">From</TableColumnHeader>
<TableColumnHeader width="25%">Method</TableColumnHeader>
</TableRow>
</TableHeaderSticky>
<TableBody>
{
history?.items
.slice()
.sort(sortFn(sort))
.map((item, index) => <NameDomainHistoryTableItem key={ index } event={ item } domain={ domain } isLoading={ isLoading }/>)
}
</Tbody>
</Table>
</TableBody>
</TableRoot>
);
};
......
import { Tr, Td } from '@chakra-ui/react';
import React from 'react';
import type * as bens from '@blockscout/bens-types';
......@@ -7,7 +6,8 @@ import { route } from 'nextjs-routes';
import config from 'configs/app';
import stripTrailingSlash from 'lib/stripTrailingSlash';
import Tag from 'ui/shared/chakra/Tag';
import { Badge } from 'toolkit/chakra/badge';
import { TableCell, TableRow } from 'toolkit/chakra/table';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
import TimeAgoWithTooltip from 'ui/shared/TimeAgoWithTooltip';
......@@ -29,8 +29,8 @@ const NameDomainHistoryTableItem = ({ isLoading, event, domain }: Props) => {
};
return (
<Tr>
<Td verticalAlign="middle">
<TableRow>
<TableCell verticalAlign="middle">
<TxEntity
{ ...txEntityProps }
hash={ event.transaction_hash }
......@@ -39,22 +39,22 @@ const NameDomainHistoryTableItem = ({ isLoading, event, domain }: Props) => {
noIcon
truncation="constant_long"
/>
</Td>
<Td pl={ 9 } verticalAlign="middle">
</TableCell>
<TableCell pl={ 9 } verticalAlign="middle">
<TimeAgoWithTooltip
timestamp={ event.timestamp }
isLoading={ isLoading }
color="text_secondary"
display="inline-block"
/>
</Td>
<Td verticalAlign="middle">
</TableCell>
<TableCell verticalAlign="middle">
{ event.from_address && <AddressEntity address={ event.from_address } isLoading={ isLoading } truncation="constant"/> }
</Td>
<Td verticalAlign="middle">
{ event.action && <Tag colorScheme="gray" isLoading={ isLoading }>{ event.action }</Tag> }
</Td>
</Tr>
</TableCell>
<TableCell verticalAlign="middle">
{ event.action && <Badge colorPalette="gray" loading={ isLoading }>{ event.action }</Badge> }
</TableCell>
</TableRow>
);
};
......
......@@ -3,10 +3,10 @@ import type * as bens from '@blockscout/bens-types';
import getNextSortValueShared from 'ui/shared/sort/getNextSortValue';
export type SortField = 'timestamp';
export type Sort = `${ SortField }-asc` | `${ SortField }-desc`;
export type Sort = `${ SortField }-asc` | `${ SortField }-desc` | 'default';
const SORT_SEQUENCE: Record<SortField, Array<Sort | undefined>> = {
timestamp: [ 'timestamp-desc', 'timestamp-asc', undefined ],
const SORT_SEQUENCE: Record<SortField, Array<Sort>> = {
timestamp: [ 'timestamp-desc', 'timestamp-asc', 'default' ],
};
export const getNextSortValue = (getNextSortValueShared<SortField, Sort>).bind(undefined, SORT_SEQUENCE);
......
import { Box, Checkbox, CheckboxGroup, Flex, HStack, Image, Link, Text, VStack, chakra } from '@chakra-ui/react';
import { Box, CheckboxGroup, Fieldset, Flex, HStack, Text, chakra, createListCollection } from '@chakra-ui/react';
import React from 'react';
import type * as bens from '@blockscout/bens-types';
......@@ -6,6 +6,9 @@ import type { EnsDomainLookupFiltersOptions } from 'types/api/ens';
import type { PaginationParams } from 'ui/shared/pagination/types';
import useIsInitialLoading from 'lib/hooks/useIsInitialLoading';
import { Checkbox } from 'toolkit/chakra/checkbox';
import { Image } from 'toolkit/chakra/image';
import { Link } from 'toolkit/chakra/link';
import ActionBar from 'ui/shared/ActionBar';
import FilterInput from 'ui/shared/filters/FilterInput';
import PopoverFilter from 'ui/shared/filters/PopoverFilter';
......@@ -16,6 +19,8 @@ import Sort from 'ui/shared/sort/Sort';
import type { Sort as TSort } from './utils';
import { SORT_OPTIONS } from './utils';
const sortCollection = createListCollection({ items: SORT_OPTIONS });
interface Props {
pagination: PaginationParams;
searchTerm: string | undefined;
......@@ -25,8 +30,8 @@ interface Props {
protocolsData: Array<bens.ProtocolInfo> | undefined;
protocolsFilterValue: Array<string>;
onProtocolsFilterChange: (nextValue: Array<string>) => void;
sort: TSort | undefined;
onSortChange: (nextValue: TSort | undefined) => void;
sort: TSort;
onSortChange: (nextValue: TSort) => void;
isLoading: boolean;
isAddressSearch: boolean;
}
......@@ -51,11 +56,11 @@ const NameDomainsActionBar = ({
<FilterInput
w={{ base: '100%', lg: '360px' }}
minW={{ base: 'auto', lg: '250px' }}
size="xs"
size="sm"
onChange={ onSearchChange }
placeholder="Search by name or address"
initialValue={ searchTerm }
isLoading={ isInitialLoading }
loading={ isInitialLoading }
/>
);
......@@ -63,6 +68,14 @@ const NameDomainsActionBar = ({
onProtocolsFilterChange([]);
}, [ onProtocolsFilterChange ]);
const handleSortChange = React.useCallback(({ value }: { value: Array<string> }) => {
onSortChange(value[0] as TSort);
}, [ onSortChange ]);
const handleFilterValueChange = React.useCallback((value: Array<string>) => {
onFilterValueChange(value as EnsDomainLookupFiltersOptions);
}, [ onFilterValueChange ]);
const filterGroupDivider = <Box w="100%" borderBottomWidth="1px" borderBottomColor="border.divider" my={ 4 }/>;
const appliedFiltersNum = filterValue.length + (protocolsData && protocolsData.length > 1 ? protocolsFilterValue.length : 0);
......@@ -72,20 +85,18 @@ const NameDomainsActionBar = ({
<div>
{ protocolsData && protocolsData.length > 1 && (
<>
<Flex justifyContent="space-between" fontSize="sm" mb={ 3 }>
<Text fontWeight={ 600 } variant="secondary">Protocol</Text>
<Flex justifyContent="space-between" textStyle="sm" mb={ 3 }>
<Text fontWeight={ 600 } color="text.secondary">Protocol</Text>
<Link
onClick={ handleProtocolReset }
color={ protocolsData.length > 0 ? 'link' : 'text_secondary' }
_hover={{
color: protocolsData.length > 0 ? 'link_hovered' : 'text_secondary',
}}
disabled={ protocolsFilterValue.length === 0 }
>
Reset
</Link>
</Flex>
<CheckboxGroup size="lg" value={ protocolsFilterValue } defaultValue={ protocolsFilterValue } onChange={ onProtocolsFilterChange }>
<VStack gap={ 5 } alignItems="flex-start">
<Fieldset.Root>
<CheckboxGroup defaultValue={ protocolsFilterValue } onValueChange={ onProtocolsFilterChange } value={ protocolsFilterValue } name="token_type">
<Fieldset.Content gap={ 5 } alignItems="flex-start">
{ protocolsData.map((protocol) => {
const topLevelDomains = protocol.tld_list.map((domain) => `.${ domain }`).join(' ');
return (
......@@ -98,7 +109,7 @@ const NameDomainsActionBar = ({
mr={ 2 }
alt={ `${ protocol.title } protocol icon` }
fallback={ <IconSvg name="ENS_slim" boxSize={ 5 } mr={ 2 }/> }
fallbackStrategy={ protocol.icon_url ? 'onError' : 'beforeLoadOrError' }
// fallbackStrategy={ protocol.icon_url ? 'onError' : 'beforeLoadOrError' }
/>
<span>{ protocol.short_name }</span>
<chakra.span color="text_secondary" whiteSpace="pre"> { topLevelDomains }</chakra.span>
......@@ -106,30 +117,34 @@ const NameDomainsActionBar = ({
</Checkbox>
);
}) }
</VStack>
</Fieldset.Content>
</CheckboxGroup>
</Fieldset.Root>
{ filterGroupDivider }
</>
) }
<CheckboxGroup size="lg" onChange={ onFilterValueChange } value={ filterValue } defaultValue={ filterValue }>
<Text variant="secondary" fontWeight={ 600 } mb={ 3 } fontSize="sm">Address</Text>
<Checkbox value="owned_by" isDisabled={ !isAddressSearch } display="block">
<Fieldset.Root>
<CheckboxGroup defaultValue={ filterValue } onValueChange={ handleFilterValueChange } value={ filterValue } name="token_type">
<Fieldset.Content gap={ 0 }>
<Text color="text.secondary" fontWeight={ 600 } mb={ 3 } textStyle="sm">Address</Text>
<Checkbox value="owned_by" disabled={ !isAddressSearch }>
Owned by
</Checkbox>
<Checkbox
value="resolved_to"
mt={ 5 }
isDisabled={ !isAddressSearch }
display="block"
disabled={ !isAddressSearch }
>
Resolved to address
</Checkbox>
{ filterGroupDivider }
<Text variant="secondary" fontWeight={ 600 } mb={ 3 } fontSize="sm">Status</Text>
<Checkbox value="with_inactive" display="block">
<Text color="text.secondary" fontWeight={ 600 } mb={ 3 } textStyle="sm">Status</Text>
<Checkbox value="with_inactive">
Include expired
</Checkbox>
</Fieldset.Content>
</CheckboxGroup>
</Fieldset.Root>
</div>
</PopoverFilter>
);
......@@ -137,16 +152,16 @@ const NameDomainsActionBar = ({
const sortButton = (
<Sort
name="name_domains_sorting"
defaultValue={ sort }
options={ SORT_OPTIONS }
onChange={ onSortChange }
defaultValue={ [ sort ] }
collection={ sortCollection }
onValueChange={ handleSortChange }
isLoading={ isInitialLoading }
/>
);
return (
<>
<HStack spacing={ 3 } mb={ 6 } display={{ base: 'flex', lg: 'none' }}>
<HStack gap={ 3 } mb={ 6 } hideFrom="lg">
{ filter }
{ sortButton }
{ searchInput }
......@@ -155,7 +170,7 @@ const NameDomainsActionBar = ({
mt={ -6 }
display={{ base: pagination.isVisible ? 'flex' : 'none', lg: 'flex' }}
>
<HStack spacing={ 3 } display={{ base: 'none', lg: 'flex' }}>
<HStack gap={ 3 } hideBelow="lg">
{ filter }
{ searchInput }
</HStack>
......
......@@ -3,8 +3,8 @@ import React from 'react';
import type * as bens from '@blockscout/bens-types';
import dayjs from 'lib/date/dayjs';
import { Skeleton } from 'toolkit/chakra/skeleton';
import NameDomainExpiryStatus from 'ui/nameDomain/NameDomainExpiryStatus';
import Skeleton from 'ui/shared/chakra/Skeleton';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
......@@ -41,7 +41,7 @@ const NameDomainsListItem = ({
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>Registered on</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading }>
<Skeleton loading={ isLoading }>
<div>{ dayjs(registrationDate).format('lll') }</div>
<div> { dayjs(registrationDate).fromNow() }</div>
</Skeleton>
......@@ -53,7 +53,7 @@ const NameDomainsListItem = ({
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>Expiration date</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<Skeleton isLoaded={ !isLoading } whiteSpace="pre-wrap">
<Skeleton loading={ isLoading } whiteSpace="pre-wrap">
<div>{ dayjs(expiryDate).format('lll') } </div>
<NameDomainExpiryStatus date={ expiryDate }/>
</Skeleton>
......
import { Table, Tbody, Tr, Th, Link } from '@chakra-ui/react';
import React from 'react';
import type * as bens from '@blockscout/bens-types';
import { Link } from 'toolkit/chakra/link';
import { TableBody, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
import { ACTION_BAR_HEIGHT_DESKTOP } from 'ui/shared/ActionBar';
import IconSvg from 'ui/shared/IconSvg';
import { default as Thead } from 'ui/shared/TheadSticky';
import NameDomainsTableItem from './NameDomainsTableItem';
import { type Sort } from './utils';
......@@ -13,7 +13,7 @@ import { type Sort } from './utils';
interface Props {
data: bens.LookupDomainNameResponse | undefined;
isLoading?: boolean;
sort: Sort | undefined;
sort: Sort;
onSortToggle: (event: React.MouseEvent) => void;
}
......@@ -21,12 +21,12 @@ const NameDomainsTable = ({ data, isLoading, sort, onSortToggle }: Props) => {
const sortIconTransform = sort?.toLowerCase().includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
return (
<Table>
<Thead top={ ACTION_BAR_HEIGHT_DESKTOP }>
<Tr>
<Th width="25%">Domain</Th>
<Th width="25%">Address</Th>
<Th width="25%" pl={ 9 }>
<TableRoot>
<TableHeaderSticky top={ ACTION_BAR_HEIGHT_DESKTOP }>
<TableRow>
<TableColumnHeader width="25%">Domain</TableColumnHeader>
<TableColumnHeader width="25%">Address</TableColumnHeader>
<TableColumnHeader width="25%" pl={ 9 }>
<Link display="flex" alignItems="center" justifyContent="flex-start" position="relative" data-field="registration_date" onClick={ onSortToggle }>
{ sort?.includes('registration_date') && (
<IconSvg
......@@ -41,14 +41,14 @@ const NameDomainsTable = ({ data, isLoading, sort, onSortToggle }: Props) => {
) }
<span>Registered on</span>
</Link>
</Th>
<Th width="25%">Expiration date</Th>
</Tr>
</Thead>
<Tbody>
</TableColumnHeader>
<TableColumnHeader width="25%">Expiration date</TableColumnHeader>
</TableRow>
</TableHeaderSticky>
<TableBody>
{ data?.items.map((item, index) => <NameDomainsTableItem key={ index } { ...item } isLoading={ isLoading }/>) }
</Tbody>
</Table>
</TableBody>
</TableRoot>
);
};
......
import { chakra, Tr, Td } from '@chakra-ui/react';
import { chakra } from '@chakra-ui/react';
import React from 'react';
import type * as bens from '@blockscout/bens-types';
import dayjs from 'lib/date/dayjs';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { TableCell, TableRow } from 'toolkit/chakra/table';
import NameDomainExpiryStatus from 'ui/nameDomain/NameDomainExpiryStatus';
import Skeleton from 'ui/shared/chakra/Skeleton';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
......@@ -23,30 +24,30 @@ const NameDomainsTableItem = ({
}: Props) => {
return (
<Tr>
<Td verticalAlign="middle">
<TableRow>
<TableCell verticalAlign="middle">
<EnsEntity domain={ name } protocol={ protocol } isLoading={ isLoading } fontWeight={ 600 }/>
</Td>
<Td verticalAlign="middle">
</TableCell>
<TableCell verticalAlign="middle">
{ resolvedAddress && <AddressEntity address={ resolvedAddress } isLoading={ isLoading } fontWeight={ 500 }/> }
</Td>
<Td verticalAlign="middle" pl={ 9 }>
</TableCell>
<TableCell verticalAlign="middle" pl={ 9 }>
{ registrationDate && (
<Skeleton isLoaded={ !isLoading }>
<Skeleton loading={ isLoading }>
{ dayjs(registrationDate).format('lll') }
<chakra.span color="text_secondary"> { dayjs(registrationDate).fromNow() }</chakra.span>
</Skeleton>
) }
</Td>
<Td verticalAlign="middle">
</TableCell>
<TableCell verticalAlign="middle">
{ expiryDate && (
<Skeleton isLoaded={ !isLoading } whiteSpace="pre-wrap">
<Skeleton loading={ isLoading }>
<span>{ dayjs(expiryDate).format('lll') } </span>
<NameDomainExpiryStatus date={ expiryDate }/>
</Skeleton>
) }
</Td>
</Tr>
</TableCell>
</TableRow>
);
};
......
import type { EnsLookupSorting } from 'types/api/ens';
import type { SelectOption } from 'toolkit/chakra/select';
import type { SelectOption } from 'toolkit/chakra/select';
import getNextSortValueShared from 'ui/shared/sort/getNextSortValue';
export type SortField = EnsLookupSorting['sort'];
export type Sort = `${ EnsLookupSorting['sort'] }-${ EnsLookupSorting['order'] }`;
export type Sort = `${ EnsLookupSorting['sort'] }-${ EnsLookupSorting['order'] }` | 'default';
export const SORT_OPTIONS: Array<SelectOption<Sort>> = [
{ label: 'Default', value: undefined },
{ label: 'Default', value: 'default' },
{ label: 'Registered on descending', value: 'registration_date-DESC' },
{ label: 'Registered on ascending', value: 'registration_date-ASC' },
];
const SORT_SEQUENCE: Record<SortField, Array<Sort | undefined>> = {
registration_date: [ 'registration_date-DESC', 'registration_date-ASC', undefined ],
const SORT_SEQUENCE: Record<SortField, Array<Sort>> = {
registration_date: [ 'registration_date-DESC', 'registration_date-ASC', 'default' ],
};
export const getNextSortValue = (getNextSortValueShared<SortField, Sort>).bind(undefined, SORT_SEQUENCE);
import { Flex, Tooltip } from '@chakra-ui/react';
import { Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type { RoutedTab } from 'ui/shared/Tabs/types';
import type { TabItemRegular } from 'toolkit/components/AdaptiveTabs/types';
import { route } from 'nextjs-routes';
......@@ -11,17 +11,18 @@ import useApiQuery from 'lib/api/useApiQuery';
import throwOnResourceLoadError from 'lib/errors/throwOnResourceLoadError';
import getQueryParamString from 'lib/router/getQueryParamString';
import { ENS_DOMAIN } from 'stubs/ENS';
import { Link } from 'toolkit/chakra/link';
import { Tooltip } from 'toolkit/chakra/tooltip';
import RoutedTabs from 'toolkit/components/RoutedTabs/RoutedTabs';
import RoutedTabsSkeleton from 'toolkit/components/RoutedTabs/RoutedTabsSkeleton';
import useActiveTabFromQuery from 'toolkit/components/RoutedTabs/useActiveTabFromQuery';
import NameDomainDetails from 'ui/nameDomain/NameDomainDetails';
import NameDomainHistory from 'ui/nameDomain/NameDomainHistory';
import TextAd from 'ui/shared/ad/TextAd';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import EnsEntity from 'ui/shared/entities/ens/EnsEntity';
import IconSvg from 'ui/shared/IconSvg';
import LinkInternal from 'ui/shared/links/LinkInternal';
import PageTitle from 'ui/shared/Page/PageTitle';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import TabsSkeleton from 'ui/shared/Tabs/TabsSkeleton';
import useTabIndexFromQuery from 'ui/shared/Tabs/useTabIndexFromQuery';
const NameDomain = () => {
const router = useRouter();
......@@ -34,12 +35,12 @@ const NameDomain = () => {
},
});
const tabs: Array<RoutedTab> = [
const tabs: Array<TabItemRegular> = [
{ id: 'details', title: 'Details', component: <NameDomainDetails query={ infoQuery }/> },
{ id: 'history', title: 'History', component: <NameDomainHistory domain={ infoQuery.data }/> },
];
const tabIndex = useTabIndexFromQuery(tabs);
const activeTab = useActiveTabFromQuery(tabs);
throwOnResourceLoadError(infoQuery);
......@@ -69,14 +70,14 @@ const NameDomain = () => {
address={ infoQuery.data?.resolved_address }
isLoading={ isLoading }
/>
<Tooltip label="Lookup for related domain names">
<LinkInternal
<Tooltip content="Lookup for related domain names">
<Link
flexShrink={ 0 }
display="inline-flex"
href={ route({ pathname: '/name-domains', query: { owned_by: 'true', resolved_to: 'true', address: infoQuery.data?.resolved_address?.hash } }) }
>
<IconSvg name="search" boxSize={ 5 } isLoading={ isLoading }/>
</LinkInternal>
</Link>
</Tooltip>
</Flex>
) }
......@@ -89,8 +90,8 @@ const NameDomain = () => {
<PageTitle title="Name details" secondRow={ titleSecondRow }/>
{ infoQuery.isPlaceholderData ? (
<>
<TabsSkeleton tabs={ tabs } mt={ 6 }/>
{ tabs[tabIndex]?.component }
<RoutedTabsSkeleton tabs={ tabs } mt={ 6 }/>
{ activeTab?.component }
</>
) : <RoutedTabs tabs={ tabs }/> }
</>
......
import { Box, Hide, Show } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -41,7 +41,7 @@ const NameDomains = () => {
const [ searchTerm, setSearchTerm ] = React.useState<string>(q || '');
const [ filterValue, setFilterValue ] = React.useState<EnsDomainLookupFiltersOptions>(initialFilters);
const [ sort, setSort ] = React.useState<Sort | undefined>(initialSort);
const [ sort, setSort ] = React.useState<Sort>(initialSort ?? 'default');
const [ protocolsFilter, setProtocolsFilter ] = React.useState<Array<string>>(protocols);
const debouncedSearchTerm = useDebounce(searchTerm, 300);
......@@ -192,8 +192,7 @@ const NameDomains = () => {
const content = (
<>
<Show below="lg" ssr={ false }>
<Box>
<Box hideFrom="lg">
{ data?.items.map((item, index) => (
<NameDomainsListItem
key={ item.id + (isLoading ? index : '') }
......@@ -202,15 +201,14 @@ const NameDomains = () => {
/>
)) }
</Box>
</Show>
<Hide below="lg" ssr={ false }>
<Box hideBelow="lg">
<NameDomainsTable
data={ data }
isLoading={ isLoading }
sort={ sort }
onSortToggle={ handleSortToggle }
/>
</Hide>
</Box>
</>
);
......@@ -239,15 +237,16 @@ const NameDomains = () => {
/>
<DataListDisplay
isError={ isError }
items={ data?.items }
itemsNum={ data?.items.length }
emptyText="There are no name domains."
filterProps={{
emptyFilteredText: `Couldn${ apos }t find name domains that match your filter query.`,
hasActiveFilters,
}}
content={ content }
actionBar={ actionBar }
/>
>
{ content }
</DataListDisplay>
</>
);
};
......
......@@ -34,11 +34,7 @@ const TokenTypeFilter = <T extends TokenType | NFTTokenType>({ nftOnly, onChange
<Text fontWeight={ 600 } color="text.secondary">Type</Text>
<Link
onClick={ handleReset }
cursor={ value.length > 0 ? 'pointer' : 'unset' }
color={ value.length > 0 ? 'link' : 'text_secondary' }
_hover={{
color: value.length > 0 ? 'link_hovered' : 'text_secondary',
}}
disabled={ value.length === 0 }
>
Reset
</Link>
......
......@@ -38,10 +38,7 @@ const TokensBridgedChainsFilter = ({ onChange, defaultValue }: Props) => {
<Text fontWeight={ 600 } color="text.secondary">Show bridged tokens from</Text>
<Link
onClick={ handleReset }
color={ value.length > 0 ? 'link' : 'text_secondary' }
_hover={{
color: value.length > 0 ? 'link_hovered' : 'text_secondary',
}}
disabled={ value.length === 0 }
>
Reset
</Link>
......
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