mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
@@ -1,5 +1,5 @@
|
||||
import { useBreakpointValue } from '@chakra-ui/react';
|
||||
|
||||
export default function useIsMobile() {
|
||||
return useBreakpointValue({ base: true, lg: false });
|
||||
export default function useIsMobile(ssr = true) {
|
||||
return useBreakpointValue({ base: true, lg: false }, { ssr });
|
||||
}
|
||||
|
||||
+15
-2
@@ -1,9 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
import TxsContent from 'ui/txs/TxsContent';
|
||||
import TxsWithSort from 'ui/txs/TxsWithSort';
|
||||
|
||||
const BlockTxs = () => {
|
||||
return <TxsContent showDescription={ false } showSortButton={ false } txs={ [] }/>;
|
||||
return (
|
||||
// <TxsContent
|
||||
// showDescription={ false }
|
||||
// showSortButton={ false }
|
||||
// txs={ [] }
|
||||
// page={ 1 }
|
||||
// // eslint-disable-next-line react/jsx-no-bind
|
||||
// onNextPageClick={ () => {} }
|
||||
// // eslint-disable-next-line react/jsx-no-bind
|
||||
// onPrevPageClick={ () => {} }
|
||||
// />
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
<TxsWithSort txs={ [] } sort={ () => () => {} }/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockTxs;
|
||||
|
||||
@@ -53,7 +53,8 @@ const BlocksContent = ({ type }: Props) => {
|
||||
<Show below="lg" key="content-mobile"><BlocksList data={ data.items }/></Show>
|
||||
<Show above="lg" key="content-desktop"><BlocksTable data={ data.items }/></Show>
|
||||
<Box mx={{ base: 0, lg: 6 }} my={{ base: 6, lg: 3 }}>
|
||||
<Pagination currentPage={ 1 }/>
|
||||
{ /* eslint-disable-next-line react/jsx-no-bind */ }
|
||||
<Pagination currentPage={ 1 } onNextPageClick={ () => {} } onPrevPageClick={ () => {} } hasNextPage/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -9,15 +9,13 @@ import appConfig from 'configs/app/config';
|
||||
import Page from 'ui/shared/Page/Page';
|
||||
import PageTitle from 'ui/shared/Page/PageTitle';
|
||||
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
|
||||
import TxsPending from 'ui/txs/TxsPending';
|
||||
import TxsValidated from 'ui/txs/TxsValidated';
|
||||
import TxsTab from 'ui/txs/TxsTab';
|
||||
|
||||
const Transactions = () => {
|
||||
|
||||
const verifiedTitle = appConfig.network.verificationType === 'validation' ? 'Validated' : 'Mined';
|
||||
const TABS: Array<RoutedTab> = [
|
||||
{ id: 'validated', title: verifiedTitle, component: <TxsValidated/> },
|
||||
{ id: 'pending', title: 'Pending', component: <TxsPending/> },
|
||||
{ id: 'validated', title: verifiedTitle, component: <TxsTab tab="validated"/> },
|
||||
{ id: 'pending', title: 'Pending', component: <TxsTab tab="pending"/> },
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, Flex, Input, Icon, IconButton } from '@chakra-ui/react';
|
||||
import { Button, Flex, Icon, IconButton } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import arrowIcon from 'icons/arrows/east-mini.svg';
|
||||
@@ -6,11 +6,14 @@ import arrowIcon from 'icons/arrows/east-mini.svg';
|
||||
type Props = {
|
||||
currentPage: number;
|
||||
maxPage?: number;
|
||||
onNextPageClick: () => void;
|
||||
onPrevPageClick: () => void;
|
||||
hasNextPage: boolean;
|
||||
}
|
||||
|
||||
const MAX_PAGE_DEFAULT = 50;
|
||||
|
||||
const Pagination = ({ currentPage, maxPage }: Props) => {
|
||||
const Pagination = ({ currentPage, maxPage, onNextPageClick, onPrevPageClick, hasNextPage }: Props) => {
|
||||
const pageNumber = (
|
||||
<Flex alignItems="center">
|
||||
<Button
|
||||
@@ -25,6 +28,7 @@ const Pagination = ({ currentPage, maxPage }: Props) => {
|
||||
>
|
||||
{ currentPage }
|
||||
</Button>
|
||||
{ /* max page will be removed */ }
|
||||
of
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -50,25 +54,30 @@ const Pagination = ({ currentPage, maxPage }: Props) => {
|
||||
<Flex alignItems="center" justifyContent="space-between" w={{ base: '100%', lg: 'auto' }}>
|
||||
<IconButton
|
||||
variant="outline"
|
||||
onClick={ onPrevPageClick }
|
||||
size="sm"
|
||||
aria-label="Next page"
|
||||
w="36px"
|
||||
icon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 }/> }
|
||||
mr={ 8 }
|
||||
disabled={ currentPage === 1 }
|
||||
/>
|
||||
{ pageNumber }
|
||||
<IconButton
|
||||
variant="outline"
|
||||
onClick={ onNextPageClick }
|
||||
size="sm"
|
||||
aria-label="Next page"
|
||||
w="36px"
|
||||
icon={ <Icon as={ arrowIcon } w={ 5 } h={ 5 } transform="rotate(180deg)"/> }
|
||||
ml={ 8 }
|
||||
disabled={ !hasNextPage }
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems="center" width="132px" ml={ 16 } display={{ base: 'none', lg: 'flex' }}>
|
||||
{ /* not implemented yet */ }
|
||||
{ /* <Flex alignItems="center" width="132px" ml={ 16 } display={{ base: 'none', lg: 'flex' }}>
|
||||
Go to <Input w="84px" size="xs" ml={ 2 }/>
|
||||
</Flex>
|
||||
</Flex> */ }
|
||||
</Flex>
|
||||
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { menuButton } from './utils';
|
||||
|
||||
interface Props {
|
||||
tabs: Array<RoutedTab | MenuButton>;
|
||||
activeTab: RoutedTab;
|
||||
activeTab?: RoutedTab;
|
||||
tabsCut: number;
|
||||
isActive: boolean;
|
||||
styles?: StyleProps;
|
||||
@@ -52,7 +52,7 @@ const RoutedTabsMenu = ({ tabs, tabsCut, isActive, styles, onItemClick, buttonRe
|
||||
key={ tab.id }
|
||||
variant="ghost"
|
||||
onClick={ handleItemClick }
|
||||
isActive={ activeTab.id === tab.id }
|
||||
isActive={ activeTab ? activeTab.id === tab.id : false }
|
||||
justifyContent="left"
|
||||
data-index={ index }
|
||||
>
|
||||
|
||||
+62
-34
@@ -1,29 +1,33 @@
|
||||
import { Box, HStack, Show } from '@chakra-ui/react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Alert, Box, HStack, Show, Button } from '@chakra-ui/react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
|
||||
import type { TransactionsResponse } from 'types/api/transaction';
|
||||
import type { Sort } from 'types/client/txs-sort';
|
||||
|
||||
import compareBns from 'lib/bigint/compareBns';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import DataFetchAlert from 'ui/shared/DataFetchAlert';
|
||||
import FilterButton from 'ui/shared/FilterButton';
|
||||
import FilterInput from 'ui/shared/FilterInput';
|
||||
import Pagination from 'ui/shared/Pagination';
|
||||
import SortButton from 'ui/shared/SortButton';
|
||||
|
||||
import TxsListItem from './TxsListItem';
|
||||
import TxsTable from './TxsTable';
|
||||
import TxsSkeletonDesktop from './TxsSkeletonDesktop';
|
||||
import TxsSkeletonMobile from './TxsSkeletonMobile';
|
||||
import TxsWithSort from './TxsWithSort';
|
||||
import useQueryWithPages from './useQueryWithPages';
|
||||
|
||||
type Props = {
|
||||
txs: TransactionsResponse['items'];
|
||||
queryName: string;
|
||||
showDescription?: boolean;
|
||||
showSortButton?: boolean;
|
||||
stateFilter: 'validated' | 'pending';
|
||||
}
|
||||
|
||||
const TxsContent = ({ showSortButton = true, showDescription = true, txs }: Props) => {
|
||||
const TxsContent = ({
|
||||
showDescription,
|
||||
queryName,
|
||||
stateFilter,
|
||||
}: Props) => {
|
||||
const [ sorting, setSorting ] = useState<Sort>();
|
||||
const [ sortedTxs, setSortedTxs ] = useState(txs);
|
||||
|
||||
// sorting should be preserved with pagination!
|
||||
const sort = useCallback((field: 'val' | 'fee') => () => {
|
||||
if (field === 'val') {
|
||||
setSorting((prevVal => {
|
||||
@@ -47,26 +51,41 @@ const TxsContent = ({ showSortButton = true, showDescription = true, txs }: Prop
|
||||
return 'fee-desc';
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
}, [ setSorting ]);
|
||||
|
||||
useEffect(() => {
|
||||
switch (sorting) {
|
||||
case 'val-desc':
|
||||
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx1.value, tx2.value)));
|
||||
break;
|
||||
case 'val-asc':
|
||||
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx2.value, tx1.value)));
|
||||
break;
|
||||
case 'fee-desc':
|
||||
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx1.fee.value, tx2.fee.value)));
|
||||
break;
|
||||
case 'fee-asc':
|
||||
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx2.fee.value, tx1.fee.value)));
|
||||
break;
|
||||
default:
|
||||
setSortedTxs(txs);
|
||||
}
|
||||
}, [ sorting, txs ]);
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isError,
|
||||
page,
|
||||
onPrevPageClick,
|
||||
onNextPageClick,
|
||||
hasPagination,
|
||||
resetPage,
|
||||
} = useQueryWithPages(queryName, stateFilter);
|
||||
|
||||
const isMobile = useIsMobile(false);
|
||||
|
||||
if (isError) {
|
||||
return <DataFetchAlert/>;
|
||||
}
|
||||
|
||||
const txs = data?.items;
|
||||
|
||||
if (!isLoading && !txs) {
|
||||
return <Alert>There are no transactions.</Alert>;
|
||||
}
|
||||
|
||||
let content = (
|
||||
<>
|
||||
<Show below="lg" ssr={ false }><TxsSkeletonMobile/></Show>
|
||||
<Show above="lg" ssr={ false }><TxsSkeletonDesktop/></Show>
|
||||
</>
|
||||
);
|
||||
|
||||
if (!isLoading && txs) {
|
||||
content = <TxsWithSort txs={ txs } sorting={ sorting } sort={ sort }/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -79,7 +98,7 @@ const TxsContent = ({ showSortButton = true, showDescription = true, txs }: Prop
|
||||
onClick={ () => {} }
|
||||
appliedFiltersNum={ 0 }
|
||||
/>
|
||||
{ showSortButton && (
|
||||
{ isMobile && (
|
||||
<SortButton
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
handleSort={ () => {} }
|
||||
@@ -95,10 +114,19 @@ const TxsContent = ({ showSortButton = true, showDescription = true, txs }: Prop
|
||||
placeholder="Search by addresses, hash, method..."
|
||||
/>
|
||||
</HStack>
|
||||
<Show below="lg"><Box>{ sortedTxs.map(tx => <TxsListItem tx={ tx } key={ tx.hash }/>) }</Box></Show>
|
||||
<Show above="lg"><TxsTable txs={ sortedTxs } sort={ sort } sorting={ sorting }/></Show>
|
||||
{ content }
|
||||
<Box mx={{ base: 0, lg: 6 }} my={{ base: 6, lg: 3 }}>
|
||||
<Pagination currentPage={ 1 }/>
|
||||
{ hasPagination ? (
|
||||
<Pagination
|
||||
currentPage={ page }
|
||||
hasNextPage={ data?.next_page_params !== undefined && Object.keys(data?.next_page_params).length > 0 }
|
||||
onNextPageClick={ onNextPageClick }
|
||||
onPrevPageClick={ onPrevPageClick }
|
||||
/>
|
||||
) :
|
||||
// temporary button, waiting for new pagination mockups
|
||||
<Button onClick={ resetPage }>Reset</Button>
|
||||
}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Show, Alert } from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
|
||||
import type { TransactionsResponse } from 'types/api/transaction';
|
||||
import { QueryKeys } from 'types/client/queries';
|
||||
|
||||
import useFetch from 'lib/hooks/useFetch';
|
||||
import DataFetchAlert from 'ui/shared/DataFetchAlert';
|
||||
|
||||
import TxsContent from './TxsContent';
|
||||
import TxsSkeletonDesktop from './TxsSkeletonDesktop';
|
||||
import TxsSkeletonMobile from './TxsSkeletonMobile';
|
||||
|
||||
const TxsValidated = () => {
|
||||
const fetch = useFetch();
|
||||
const { data, isLoading, isError } =
|
||||
useQuery<unknown, unknown, TransactionsResponse>([ QueryKeys.transactionsPending ], async() => fetch('/api/transactions/?filter=pending'));
|
||||
|
||||
if (isError) {
|
||||
return <DataFetchAlert/>;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<>
|
||||
<Show below="lg"><TxsSkeletonMobile isPending/></Show>
|
||||
<Show above="lg"><TxsSkeletonDesktop isPending/></Show>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data || !data.items) {
|
||||
return <Alert>There are no transactions.</Alert>;
|
||||
}
|
||||
|
||||
return <TxsContent txs={ data.items } showDescription={ false }/>;
|
||||
};
|
||||
|
||||
export default TxsValidated;
|
||||
@@ -1,18 +1,13 @@
|
||||
import { Skeleton, Flex } from '@chakra-ui/react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import SkeletonTable from 'ui/shared/SkeletonTable';
|
||||
|
||||
const TxsInternalsSkeletonDesktop = ({ isPending }: {isPending?: boolean}) => {
|
||||
const TxsInternalsSkeletonDesktop = () => {
|
||||
return (
|
||||
<>
|
||||
{ !isPending && <Skeleton h={ 6 } w="100%" mb={ 12 }/> }
|
||||
<Flex columnGap={ 3 } h={ 8 } mb={ 6 }>
|
||||
<Skeleton w="78px"/>
|
||||
<Skeleton w="360px"/>
|
||||
</Flex>
|
||||
<Box mb={ 8 }>
|
||||
<SkeletonTable columns={ [ '32px', '20%', '18%', '15%', '11%', '292px', '18%', '18%' ] }/>
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,44 +1,36 @@
|
||||
import { Skeleton, Flex, Box, useColorModeValue } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
const TxInternalsSkeletonMobile = ({ isPending }: {isPending?: boolean}) => {
|
||||
const TxInternalsSkeletonMobile = () => {
|
||||
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
|
||||
|
||||
return (
|
||||
<>
|
||||
{ !isPending && <Skeleton h={ 6 } w="100%" mb={ 12 }/> }
|
||||
<Flex columnGap={ 3 } h={ 8 } mb={ 6 }>
|
||||
<Skeleton w="36px" flexShrink={ 0 }/>
|
||||
<Skeleton w="36px" flexShrink={ 0 }/>
|
||||
<Skeleton w="100%"/>
|
||||
</Flex>
|
||||
<Box>
|
||||
{ Array.from(Array(2)).map((item, index) => (
|
||||
<Flex
|
||||
key={ index }
|
||||
flexDirection="column"
|
||||
paddingBottom={ 3 }
|
||||
paddingTop={ 4 }
|
||||
borderTopWidth="1px"
|
||||
borderColor={ borderColor }
|
||||
_last={{
|
||||
borderBottomWidth: '1px',
|
||||
}}
|
||||
>
|
||||
<Flex h={ 6 }>
|
||||
<Skeleton w="100px" mr={ 2 } h={ 6 }/>
|
||||
<Skeleton w="100px" h={ 6 }/>
|
||||
</Flex>
|
||||
<Skeleton w="100%" h="30px" mt={ 3 }/>
|
||||
<Skeleton w="50%" h={ 6 } mt={ 3 }/>
|
||||
<Skeleton w="50%" h={ 6 } mt={ 2 }/>
|
||||
<Skeleton w="100%" h={ 6 } mt={ 6 }/>
|
||||
<Skeleton w="50%" h={ 6 } mt={ 2 }/>
|
||||
<Skeleton w="50%" h={ 6 } mt={ 2 }/>
|
||||
<Box>
|
||||
{ Array.from(Array(2)).map((item, index) => (
|
||||
<Flex
|
||||
key={ index }
|
||||
flexDirection="column"
|
||||
paddingBottom={ 3 }
|
||||
paddingTop={ 4 }
|
||||
borderTopWidth="1px"
|
||||
borderColor={ borderColor }
|
||||
_last={{
|
||||
borderBottomWidth: '1px',
|
||||
}}
|
||||
>
|
||||
<Flex h={ 6 }>
|
||||
<Skeleton w="100px" mr={ 2 } h={ 6 }/>
|
||||
<Skeleton w="100px" h={ 6 }/>
|
||||
</Flex>
|
||||
)) }
|
||||
</Box>
|
||||
</>
|
||||
<Skeleton w="100%" h="30px" mt={ 3 }/>
|
||||
<Skeleton w="50%" h={ 6 } mt={ 3 }/>
|
||||
<Skeleton w="50%" h={ 6 } mt={ 2 }/>
|
||||
<Skeleton w="100%" h={ 6 } mt={ 6 }/>
|
||||
<Skeleton w="50%" h={ 6 } mt={ 2 }/>
|
||||
<Skeleton w="50%" h={ 6 } mt={ 2 }/>
|
||||
</Flex>
|
||||
)) }
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
import { QueryKeys } from 'types/client/queries';
|
||||
|
||||
import TxsContent from './TxsContent';
|
||||
|
||||
type Props = {
|
||||
tab: 'validated' | 'pending';
|
||||
}
|
||||
|
||||
const TxsTab = ({ tab }: Props) => {
|
||||
return (
|
||||
<TxsContent
|
||||
queryName={ tab === 'validated' ? QueryKeys.transactionsValidated : QueryKeys.transactionsPending }
|
||||
showDescription={ tab === 'validated' }
|
||||
stateFilter={ tab }
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TxsTab;
|
||||
+10
-14
@@ -12,7 +12,6 @@ import {
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
PopoverBody,
|
||||
Portal,
|
||||
useColorModeValue,
|
||||
Show,
|
||||
} from '@chakra-ui/react';
|
||||
@@ -40,7 +39,7 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => {
|
||||
<Tooltip label={ tx.from.implementation_name }>
|
||||
<Box display="flex"><AddressIcon hash={ tx.from.hash }/></Box>
|
||||
</Tooltip>
|
||||
<AddressLink hash={ tx.from.hash } alias={ tx.from.name } fontWeight="500" ml={ 2 }/>
|
||||
<AddressLink hash={ tx.from.hash } alias={ tx.from.name } fontWeight="500" ml={ 2 } truncation="constant"/>
|
||||
</Address>
|
||||
);
|
||||
|
||||
@@ -49,7 +48,7 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => {
|
||||
<Tooltip label={ tx.to.implementation_name }>
|
||||
<Box display="flex"><AddressIcon hash={ tx.to.hash }/></Box>
|
||||
</Tooltip>
|
||||
<AddressLink hash={ tx.to.hash } alias={ tx.to.name } fontWeight="500" ml={ 2 }/>
|
||||
<AddressLink hash={ tx.to.hash } alias={ tx.to.name } fontWeight="500" ml={ 2 } truncation="constant"/>
|
||||
</Address>
|
||||
);
|
||||
|
||||
@@ -57,19 +56,17 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => {
|
||||
return (
|
||||
<Tr>
|
||||
<Td pl={ 4 }>
|
||||
<Popover placement="right-start" openDelay={ 300 }>
|
||||
<Popover placement="right-start" openDelay={ 300 } isLazy>
|
||||
{ ({ isOpen }) => (
|
||||
<>
|
||||
<PopoverTrigger>
|
||||
<TxAdditionalInfoButton isOpen={ isOpen }/>
|
||||
</PopoverTrigger>
|
||||
<Portal>
|
||||
<PopoverContent border="1px solid" borderColor={ infoBorderColor }>
|
||||
<PopoverBody>
|
||||
<TxAdditionalInfo tx={ tx }/>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Portal>
|
||||
<PopoverContent border="1px solid" borderColor={ infoBorderColor }>
|
||||
<PopoverBody>
|
||||
<TxAdditionalInfo tx={ tx }/>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</>
|
||||
) }
|
||||
</Popover>
|
||||
@@ -114,8 +111,7 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => {
|
||||
<Td>
|
||||
{ tx.block && <Link href={ link('block', { id: tx.block.toString() }) }>{ tx.block }</Link> }
|
||||
</Td>
|
||||
{ /* TODO: fix "show" problem */ }
|
||||
<Show above="xl">
|
||||
<Show above="xl" ssr={ false }>
|
||||
<Td>
|
||||
{ addressFrom }
|
||||
</Td>
|
||||
@@ -126,7 +122,7 @@ const TxsTableItem = ({ tx }: {tx: Transaction}) => {
|
||||
{ addressTo }
|
||||
</Td>
|
||||
</Show>
|
||||
<Show below="xl">
|
||||
<Show below="xl" ssr={ false }>
|
||||
<Td colSpan={ 3 }>
|
||||
<Box>
|
||||
{ addressFrom }
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Show, Alert } from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
|
||||
import type { TransactionsResponse } from 'types/api/transaction';
|
||||
import { QueryKeys } from 'types/client/queries';
|
||||
|
||||
import useFetch from 'lib/hooks/useFetch';
|
||||
import DataFetchAlert from 'ui/shared/DataFetchAlert';
|
||||
|
||||
import TxsContent from './TxsContent';
|
||||
import TxsSkeletonDesktop from './TxsSkeletonDesktop';
|
||||
import TxsSkeletonMobile from './TxsSkeletonMobile';
|
||||
|
||||
const TxsValidated = () => {
|
||||
const fetch = useFetch();
|
||||
const { data, isLoading, isError } =
|
||||
useQuery<unknown, unknown, TransactionsResponse>([ QueryKeys.transactionsValidated ], async() => fetch('/api/transactions/?filter=validated'));
|
||||
|
||||
if (isError) {
|
||||
return <DataFetchAlert/>;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<>
|
||||
<Show below="lg"><TxsSkeletonMobile/></Show>
|
||||
<Show above="lg"><TxsSkeletonDesktop/></Show>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data || !data.items) {
|
||||
return <Alert>There are no transactions.</Alert>;
|
||||
}
|
||||
|
||||
return <TxsContent txs={ data.items }/>;
|
||||
};
|
||||
|
||||
export default TxsValidated;
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Box, Show } from '@chakra-ui/react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import type { TransactionsResponse } from 'types/api/transaction';
|
||||
import type { Sort } from 'types/client/txs-sort';
|
||||
|
||||
import compareBns from 'lib/bigint/compareBns';
|
||||
|
||||
import TxsListItem from './TxsListItem';
|
||||
import TxsTable from './TxsTable';
|
||||
|
||||
type Props = {
|
||||
txs: TransactionsResponse['items'];
|
||||
sorting?: Sort;
|
||||
sort: (field: 'val' | 'fee') => () => void;
|
||||
}
|
||||
|
||||
const TxsWithSort = ({
|
||||
txs,
|
||||
sorting,
|
||||
sort,
|
||||
}: Props) => {
|
||||
const [ sortedTxs, setSortedTxs ] = useState(txs);
|
||||
|
||||
useEffect(() => {
|
||||
switch (sorting) {
|
||||
case 'val-desc':
|
||||
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx1.value, tx2.value)));
|
||||
break;
|
||||
case 'val-asc':
|
||||
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx2.value, tx1.value)));
|
||||
break;
|
||||
case 'fee-desc':
|
||||
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx1.fee.value, tx2.fee.value)));
|
||||
break;
|
||||
case 'fee-asc':
|
||||
setSortedTxs([ ...txs ].sort((tx1, tx2) => compareBns(tx2.fee.value, tx1.fee.value)));
|
||||
break;
|
||||
default:
|
||||
setSortedTxs(txs);
|
||||
}
|
||||
}, [ sorting, txs ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Show below="lg" ssr={ false }><Box>{ sortedTxs.map(tx => <TxsListItem tx={ tx } key={ tx.hash }/>) }</Box></Show>
|
||||
<Show above="lg" ssr={ false }><TxsTable txs={ sortedTxs } sort={ sort } sorting={ sorting }/></Show>
|
||||
</>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default TxsWithSort;
|
||||
@@ -0,0 +1,78 @@
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { pick, omit } from 'lodash';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useCallback } from 'react';
|
||||
import { animateScroll } from 'react-scroll';
|
||||
|
||||
import type { TransactionsResponse } from 'types/api/transaction';
|
||||
|
||||
import useFetch from 'lib/hooks/useFetch';
|
||||
|
||||
const PAGINATION_FIELDS = [ 'block_number', 'index', 'items_count' ];
|
||||
|
||||
export default function useQueryWithPages(queryName: string, filter: string) {
|
||||
const queryClient = useQueryClient();
|
||||
const router = useRouter();
|
||||
const [ page, setPage ] = React.useState(1);
|
||||
const currPageParams = pick(router.query, PAGINATION_FIELDS);
|
||||
const [ pageParams, setPageParams ] = React.useState<Array<Partial<TransactionsResponse['next_page_params']>>>([ {} ]);
|
||||
const fetch = useFetch();
|
||||
|
||||
const { data, isLoading, isError } = useQuery<unknown, unknown, TransactionsResponse>(
|
||||
[ queryName, { page } ],
|
||||
async() => {
|
||||
const params: Array<string> = [];
|
||||
|
||||
Object.entries(currPageParams).forEach(([ key, val ]) => params.push(`${ key }=${ val }`));
|
||||
|
||||
return fetch(`/api/transactions?filter=${ filter }${ params.length ? '&' + params.join('&') : '' }`);
|
||||
},
|
||||
{ staleTime: Infinity },
|
||||
);
|
||||
|
||||
const onNextPageClick = useCallback(() => {
|
||||
if (!data?.next_page_params) {
|
||||
// we hide next page button if no next_page_params
|
||||
return;
|
||||
}
|
||||
// api adds filters into next-page-params now
|
||||
// later filters will be removed from response
|
||||
const nextPageParams = pick(data.next_page_params, PAGINATION_FIELDS);
|
||||
if (page >= pageParams.length && data?.next_page_params) {
|
||||
setPageParams(prev => [ ...prev, nextPageParams ]);
|
||||
}
|
||||
const nextPageQuery = { ...router.query };
|
||||
Object.entries(nextPageParams).forEach(([ key, val ]) => nextPageQuery[key] = val.toString());
|
||||
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true });
|
||||
animateScroll.scrollToTop({ duration: 0 });
|
||||
setPage(prev => prev + 1);
|
||||
}, [ data, page, pageParams, router ]);
|
||||
|
||||
const onPrevPageClick = useCallback(() => {
|
||||
// returning to the first page
|
||||
// we dont have pagination params for the first page
|
||||
let nextPageQuery: typeof router.query;
|
||||
if (page === 2) {
|
||||
nextPageQuery = omit(router.query, PAGINATION_FIELDS);
|
||||
} else {
|
||||
const nextPageParams = { ...pageParams[page - 2] };
|
||||
nextPageQuery = { ...router.query };
|
||||
Object.entries(nextPageParams).forEach(([ key, val ]) => nextPageQuery[key] = val.toString());
|
||||
}
|
||||
router.query = nextPageQuery;
|
||||
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true });
|
||||
animateScroll.scrollToTop({ duration: 0 });
|
||||
setPage(prev => prev - 1);
|
||||
}, [ router, page, pageParams ]);
|
||||
|
||||
const resetPage = useCallback(() => {
|
||||
queryClient.clear();
|
||||
animateScroll.scrollToTop({ duration: 0 });
|
||||
router.push({ pathname: router.pathname, query: omit(router.query, PAGINATION_FIELDS) }, undefined, { shallow: true });
|
||||
}, [ router, queryClient ]);
|
||||
|
||||
// if there are pagination params on the initial page, we shouldn't show pagination
|
||||
const hasPagination = !(page === 1 && Object.keys(currPageParams).length > 0);
|
||||
|
||||
return { data, isError, isLoading, page, onNextPageClick, onPrevPageClick, hasPagination, resetPage };
|
||||
}
|
||||
Reference in New Issue
Block a user