Commit 0b4b78da authored by tom's avatar tom

page base layout and filters

parent 0f0b0b2b
...@@ -17,6 +17,7 @@ import type { AddressesResponse } from 'types/api/addresses'; ...@@ -17,6 +17,7 @@ import type { AddressesResponse } from 'types/api/addresses';
import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters } from 'types/api/block'; import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters } from 'types/api/block';
import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts'; import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts';
import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod, SmartContractVerificationConfig } from 'types/api/contract'; import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod, SmartContractVerificationConfig } from 'types/api/contract';
import type { VerifiedContractsResponse, VerifiedContractsFilters } from 'types/api/contracts';
import type { IndexingStatus } from 'types/api/indexingStatus'; import type { IndexingStatus } from 'types/api/indexingStatus';
import type { InternalTransactionsResponse } from 'types/api/internalTransaction'; import type { InternalTransactionsResponse } from 'types/api/internalTransaction';
import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log'; import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log';
...@@ -258,6 +259,12 @@ export const RESOURCES = { ...@@ -258,6 +259,12 @@ export const RESOURCES = {
pathParams: [ 'hash' as const, 'method' as const ], pathParams: [ 'hash' as const, 'method' as const ],
}, },
verified_contracts: {
path: '/api/v2/smart-contracts',
paginationFields: [ 'items_count' as const, 'smart_contract_id' as const ],
filterFields: [ 'q' as const, 'filter' as const ],
},
// TOKEN // TOKEN
token: { token: {
path: '/api/v2/tokens/:hash', path: '/api/v2/tokens/:hash',
...@@ -399,7 +406,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' | ...@@ -399,7 +406,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' |
'search' | 'search' |
'address_logs' | 'address_tokens' | 'address_logs' | 'address_tokens' |
'token_transfers' | 'token_holders' | 'token_inventory' | 'tokens' | 'token_transfers' | 'token_holders' | 'token_inventory' | 'tokens' |
'token_instance_transfers'; 'token_instance_transfers' |
'verified_contracts';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>; export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
...@@ -457,6 +465,7 @@ Q extends 'contract_methods_read' ? Array<SmartContractReadMethod> : ...@@ -457,6 +465,7 @@ Q extends 'contract_methods_read' ? Array<SmartContractReadMethod> :
Q extends 'contract_methods_read_proxy' ? Array<SmartContractReadMethod> : Q extends 'contract_methods_read_proxy' ? Array<SmartContractReadMethod> :
Q extends 'contract_methods_write' ? Array<SmartContractWriteMethod> : Q extends 'contract_methods_write' ? Array<SmartContractWriteMethod> :
Q extends 'contract_methods_write_proxy' ? Array<SmartContractWriteMethod> : Q extends 'contract_methods_write_proxy' ? Array<SmartContractWriteMethod> :
Q extends 'verified_contracts' ? VerifiedContractsResponse :
Q extends 'visualize_sol2uml' ? VisualizedContract : Q extends 'visualize_sol2uml' ? VisualizedContract :
Q extends 'contract_verification_config' ? SmartContractVerificationConfig : Q extends 'contract_verification_config' ? SmartContractVerificationConfig :
never; never;
...@@ -473,5 +482,6 @@ Q extends 'address_token_transfers' ? AddressTokenTransferFilters : ...@@ -473,5 +482,6 @@ Q extends 'address_token_transfers' ? AddressTokenTransferFilters :
Q extends 'address_tokens' ? AddressTokensFilter : Q extends 'address_tokens' ? AddressTokensFilter :
Q extends 'search' ? SearchResultFilters : Q extends 'search' ? SearchResultFilters :
Q extends 'tokens' ? TokensFilters : Q extends 'tokens' ? TokensFilters :
Q extends 'verified_contracts' ? VerifiedContractsFilters :
never; never;
/* eslint-enable @typescript-eslint/indent */ /* eslint-enable @typescript-eslint/indent */
import type { NextPage } from 'next'; import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import VerifiedContracts from 'ui/pages/VerifiedContracts';
import Page from 'ui/shared/Page/Page';
const VerifiedContractsPage: NextPage = () => { const VerifiedContractsPage: NextPage = () => {
return null; const title = getNetworkTitle();
return (
<>
<Head>
<title>{ title }</title>
</Head>
<Page>
<VerifiedContracts/>
</Page>
</>
);
}; };
export default VerifiedContractsPage; export default VerifiedContractsPage;
export async function getServerSideProps() { export { getServerSideProps } from 'lib/next/getServerSideProps';
return {
notFound: true,
};
}
import type { AddressParam } from './addressParams';
export interface VerifiedContract {
address: AddressParam;
coin_balance: string;
compiler_version: string;
language: 'vyper' | 'yul' | 'solidity';
has_constructor_args: boolean;
optimization_enabled: boolean;
tx_count: number;
verified_at: string;
market_cap: string | null;
}
export interface VerifiedContractsResponse {
items: Array<VerifiedContract>;
next_page_params: {
items_count: string;
smart_contract_id: string;
} | null;
}
export interface VerifiedContractsFilters {
q: string | undefined;
filter: 'vyper' | 'solidity' | undefined;
}
import { Box, Flex, Hide, Show, Text } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
import type { VerifiedContractsFilters } from 'types/api/contracts';
import useDebounce from 'lib/hooks/useDebounce';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import { apos } from 'lib/html-entities';
import getQueryParamString from 'lib/router/getQueryParamString';
import EmptySearchResult from 'ui/apps/EmptySearchResult';
import ActionBar from 'ui/shared/ActionBar';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import FilterInput from 'ui/shared/filters/FilterInput';
import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import SkeletonList from 'ui/shared/skeletons/SkeletonList';
import SkeletonTable from 'ui/shared/skeletons/SkeletonTable';
import VerifiedContractsFilter from 'ui/verifiedContracts/VerifiedContractsFilter';
const VerifiedContracts = () => {
const router = useRouter();
const [ searchTerm, setSearchTerm ] = React.useState(getQueryParamString(router.query.q) || undefined);
const [ type, setType ] = React.useState(getQueryParamString(router.query.filter) as VerifiedContractsFilters['filter'] || undefined);
const debouncedSearchTerm = useDebounce(searchTerm || '', 300);
const { isError, isLoading, data, isPaginationVisible, pagination, onFilterChange } = useQueryWithPages({
resourceName: 'verified_contracts',
filters: { q: debouncedSearchTerm, filter: type },
});
const handleSearchTemChange = React.useCallback((value: string) => {
onFilterChange({ q: value, filter: type });
setSearchTerm(value);
}, [ type, onFilterChange ]);
const handleTypeChange = React.useCallback((value: string | Array<string>) => {
if (Array.isArray(value)) {
return;
}
if ((value === 'vyper' || value === 'solidity')) {
onFilterChange({ q: debouncedSearchTerm, filter: value });
setType(value);
return;
}
onFilterChange({ q: debouncedSearchTerm, filter: undefined });
setType(undefined);
}, [ debouncedSearchTerm, onFilterChange ]);
const typeFilter = <VerifiedContractsFilter onChange={ handleTypeChange } defaultValue={ type } isActive={ Boolean(type) }/>;
const filterInput = (
<FilterInput
w={{ base: '100%', lg: '350px' }}
size="xs"
onChange={ handleSearchTemChange }
placeholder="Search by contract name or address"
initialValue={ searchTerm }
/>
);
const bar = (
<>
<Flex columnGap={ 3 } mb={ 6 } display={{ base: 'flex', lg: 'none' }}>
{ typeFilter }
{ filterInput }
</Flex>
<ActionBar mt={ -6 }>
<Flex columnGap={ 3 } display={{ base: 'none', lg: 'flex' }}>
{ typeFilter }
{ filterInput }
</Flex>
{ isPaginationVisible && <Pagination ml="auto" { ...pagination }/> }
</ActionBar>
</>
);
const content = (() => {
if (isError) {
return <DataFetchAlert/>;
}
if (isLoading) {
return (
<>
<Show below="lg" ssr={ false }>
<SkeletonList/>
</Show>
<Hide below="lg" ssr={ false }>
<SkeletonTable columns={ [ '15%', '15%', '10%', '20%', '20%', '20%' ] }/>
</Hide>
</>
);
}
if (data.items.length === 0 && !searchTerm) {
return <Text as="span">There are no verified contracts</Text>;
}
if (data.items.length === 0) {
return <EmptySearchResult text={ `Couldn${ apos }t find any contract that matches your query.` }/>;
}
return (
<>
<Show below="lg" ssr={ false }>
{ '<AddressIntTxsList data={ data.items } currentAddress={ hash }/>' }
</Show>
<Hide below="lg" ssr={ false }>
{ '<AddressIntTxsTable data={ data.items } currentAddress={ hash }/>' }
</Hide>
</>
);
})();
return (
<Box>
<PageTitle text="Verified contracts" withTextAd/>
{ bar }
{ content }
</Box>
);
};
export default VerifiedContracts;
...@@ -37,7 +37,7 @@ const PageTitle = ({ text, additionalsLeft, additionalsRight, withTextAd, backLi ...@@ -37,7 +37,7 @@ const PageTitle = ({ text, additionalsLeft, additionalsRight, withTextAd, backLi
justifyContent="space-between" justifyContent="space-between"
className={ className } className={ className }
> >
<Flex flexWrap="wrap" columnGap={ 3 } alignItems="center" width={ withTextAd ? 'unset' : '100%' }> <Flex flexWrap="wrap" columnGap={ 3 } alignItems="center" width={ withTextAd ? 'unset' : '100%' } flexShrink={ 0 }>
<Grid <Grid
templateColumns={ [ backLinkUrl && 'auto', additionalsLeft && 'auto', '1fr' ].filter(Boolean).join(' ') } templateColumns={ [ backLinkUrl && 'auto', additionalsLeft && 'auto', '1fr' ].filter(Boolean).join(' ') }
columnGap={ 3 } columnGap={ 3 }
......
import {
Menu,
MenuButton,
MenuList,
MenuOptionGroup,
MenuItemOption,
useDisclosure,
} from '@chakra-ui/react';
import React from 'react';
import type { VerifiedContractsFilters } from 'types/api/contracts';
import FilterButton from 'ui/shared/filters/FilterButton';
interface Props {
isActive: boolean;
defaultValue: VerifiedContractsFilters['filter'] | undefined;
onChange: (nextValue: string | Array<string>) => void;
}
const VerifiedContractsFilter = ({ onChange, defaultValue, isActive }: Props) => {
const { isOpen, onToggle } = useDisclosure();
return (
<Menu>
<MenuButton>
<FilterButton
isActive={ isOpen || isActive }
onClick={ onToggle }
as="div"
/>
</MenuButton>
<MenuList zIndex={ 2 }>
<MenuOptionGroup defaultValue={ defaultValue || 'all' } title="Filter" type="radio" onChange={ onChange }>
<MenuItemOption value="all">All</MenuItemOption>
<MenuItemOption value="solidity">Solidity</MenuItemOption>
<MenuItemOption value="vyper">Vyper</MenuItemOption>
</MenuOptionGroup>
</MenuList>
</Menu>
);
};
export default React.memo(VerifiedContractsFilter);
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