mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
@@ -3,9 +3,9 @@ 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 { animateScroll, scroller } from 'react-scroll';
|
||||
|
||||
import { PAGINATION_FIELDS } from 'types/api/pagination';
|
||||
import { PAGINATION_FIELDS, PAGINATION_FILTERS_FIELDS } from 'types/api/pagination';
|
||||
import type { PaginationParams, PaginatedResponse, PaginatedQueryKeys, PaginationFilters } from 'types/api/pagination';
|
||||
|
||||
import useFetch from 'lib/hooks/useFetch';
|
||||
@@ -16,9 +16,17 @@ interface Params<QueryName extends PaginatedQueryKeys> {
|
||||
queryIds?: Array<string>;
|
||||
filters?: PaginationFilters<QueryName>;
|
||||
options?: Omit<UseQueryOptions<unknown, unknown, PaginatedResponse<QueryName>>, 'queryKey' | 'queryFn'>;
|
||||
scroll?: { elem: string; offset: number };
|
||||
}
|
||||
|
||||
export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>({ queryName, filters, options, apiPath, queryIds }: Params<QueryName>) {
|
||||
export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>({
|
||||
queryName,
|
||||
filters,
|
||||
options,
|
||||
apiPath,
|
||||
queryIds,
|
||||
scroll,
|
||||
}: Params<QueryName>) {
|
||||
const paginationFields = PAGINATION_FIELDS[queryName];
|
||||
const queryClient = useQueryClient();
|
||||
const router = useRouter();
|
||||
@@ -31,6 +39,10 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
|
||||
|
||||
const queryKey = [ queryName, ...(queryIds || []), { page, filters } ];
|
||||
|
||||
const scrollToTop = useCallback(() => {
|
||||
scroll ? scroller.scrollTo(scroll.elem, { offset: scroll.offset }) : animateScroll.scrollToTop({ duration: 0 });
|
||||
}, [ scroll ]);
|
||||
|
||||
const queryResult = useQuery<unknown, unknown, PaginatedResponse<QueryName>>(
|
||||
queryKey,
|
||||
async() => {
|
||||
@@ -65,10 +77,10 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
|
||||
|
||||
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true })
|
||||
.then(() => {
|
||||
animateScroll.scrollToTop({ duration: 0 });
|
||||
scrollToTop();
|
||||
setPage(prev => prev + 1);
|
||||
});
|
||||
}, [ data?.next_page_params, page, pageParams.length, router ]);
|
||||
}, [ data?.next_page_params, page, pageParams.length, router, scrollToTop ]);
|
||||
|
||||
const onPrevPageClick = useCallback(() => {
|
||||
// returning to the first page
|
||||
@@ -85,21 +97,44 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
|
||||
router.query = nextPageQuery;
|
||||
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true })
|
||||
.then(() => {
|
||||
animateScroll.scrollToTop({ duration: 0 });
|
||||
scrollToTop();
|
||||
setPage(prev => prev - 1);
|
||||
page === 2 && queryClient.clear();
|
||||
});
|
||||
}, [ router, page, paginationFields, pageParams, queryClient ]);
|
||||
}, [ router, page, paginationFields, pageParams, queryClient, scrollToTop ]);
|
||||
|
||||
const resetPage = useCallback(() => {
|
||||
queryClient.clear();
|
||||
router.push({ pathname: router.pathname, query: omit(router.query, paginationFields, 'page') }, undefined, { shallow: true }).then(() => {
|
||||
animateScroll.scrollToTop({ duration: 0 });
|
||||
queryClient.removeQueries({ queryKey: [ queryName ] });
|
||||
scrollToTop();
|
||||
setPage(1);
|
||||
setPageParams([ ]);
|
||||
canGoBackwards.current = true;
|
||||
});
|
||||
}, [ queryClient, router, paginationFields ]);
|
||||
}, [ queryClient, queryName, router, paginationFields, scrollToTop ]);
|
||||
|
||||
const onFilterChange = useCallback((newFilters: PaginationFilters<QueryName> | undefined) => {
|
||||
const newQuery = omit(router.query, PAGINATION_FIELDS[queryName], 'page', PAGINATION_FILTERS_FIELDS[queryName]);
|
||||
if (newFilters) {
|
||||
Object.entries(newFilters).forEach(([ key, value ]) => {
|
||||
if (value) {
|
||||
newQuery[key] = Array.isArray(value) ? value.join(',') : (value || '');
|
||||
}
|
||||
});
|
||||
}
|
||||
router.push(
|
||||
{
|
||||
pathname: router.pathname,
|
||||
query: newQuery,
|
||||
},
|
||||
undefined,
|
||||
{ shallow: true },
|
||||
).then(() => {
|
||||
setPage(1);
|
||||
setPageParams([ ]);
|
||||
scrollToTop();
|
||||
});
|
||||
}, [ queryName, router, scrollToTop, setPageParams, setPage ]);
|
||||
|
||||
const hasPaginationParams = Object.keys(currPageParams).length > 0;
|
||||
const nextPageParams = data?.next_page_params;
|
||||
@@ -129,5 +164,5 @@ export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>(
|
||||
}, 0);
|
||||
}, []);
|
||||
|
||||
return { ...queryResult, pagination };
|
||||
return { ...queryResult, pagination, onFilterChange };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { NextApiRequest } from 'next';
|
||||
|
||||
import getSearchParams from 'lib/api/getSearchParams';
|
||||
import handler from 'lib/api/handler';
|
||||
|
||||
const getUrl = (req: NextApiRequest) => {
|
||||
const searchParamsStr = getSearchParams(req);
|
||||
return `/v2/addresses/${ req.query.id }/token-transfers${ searchParamsStr ? '?' + searchParamsStr : '' }`;
|
||||
};
|
||||
|
||||
const requestHandler = handler(getUrl, [ 'GET' ]);
|
||||
|
||||
export default requestHandler;
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { NextApiRequest } from 'next';
|
||||
|
||||
import getSearchParams from 'lib/api/getSearchParams';
|
||||
import handler from 'lib/api/handler';
|
||||
|
||||
const getUrl = (req: NextApiRequest) => {
|
||||
const searchParamsStr = getSearchParams(req);
|
||||
return `/v2/addresses/${ req.query.id }/transactions${ searchParamsStr ? '?' + searchParamsStr : '' }`;
|
||||
};
|
||||
|
||||
const requestHandler = handler(getUrl, [ 'GET' ]);
|
||||
|
||||
export default requestHandler;
|
||||
+29
-1
@@ -1,5 +1,8 @@
|
||||
import type { Transaction } from 'types/api/transaction';
|
||||
|
||||
import type { AddressTag, WatchlistName } from './addressParams';
|
||||
import type { TokenInfo } from './tokenInfo';
|
||||
import type { TokenInfo, TokenType } from './tokenInfo';
|
||||
import type { TokenTransfer, TokenTransferPagination } from './tokenTransfer';
|
||||
|
||||
export interface Address {
|
||||
block_number_balance_updated_at: number | null;
|
||||
@@ -31,3 +34,28 @@ export interface AddressTokenBalance {
|
||||
token_id: string | null;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface AddressTransactionsResponse {
|
||||
items: Array<Transaction>;
|
||||
next_page_params: {
|
||||
block_number: number;
|
||||
index: number;
|
||||
items_count: number;
|
||||
} | null;
|
||||
}
|
||||
|
||||
type AddressFromToFilter = 'from' | 'to' | undefined;
|
||||
|
||||
export type AddressTxsFilters = {
|
||||
filter: AddressFromToFilter;
|
||||
}
|
||||
|
||||
export interface AddressTokenTransferResponse {
|
||||
items: Array<TokenTransfer>;
|
||||
next_page_params: TokenTransferPagination | null;
|
||||
}
|
||||
|
||||
export type AddressTokenTransferFilters = {
|
||||
filter: AddressFromToFilter;
|
||||
type: TokenType;
|
||||
}
|
||||
|
||||
+40
-13
@@ -1,3 +1,4 @@
|
||||
import type { AddressTransactionsResponse, AddressTokenTransferResponse, AddressTxsFilters, AddressTokenTransferFilters } from 'types/api/address';
|
||||
import type { BlocksResponse, BlockTransactionsResponse, BlockFilters } from 'types/api/block';
|
||||
import type { InternalTransactionsResponse } from 'types/api/internalTransaction';
|
||||
import type { LogsResponse } from 'types/api/log';
|
||||
@@ -8,6 +9,8 @@ import { QueryKeys } from 'types/client/queries';
|
||||
import type { KeysOfObjectOrNull } from 'types/utils/KeysOfObjectOrNull';
|
||||
|
||||
export type PaginatedQueryKeys =
|
||||
QueryKeys.addressTxs |
|
||||
QueryKeys.addressTokenTransfers |
|
||||
QueryKeys.blocks |
|
||||
QueryKeys.blocksReorgs |
|
||||
QueryKeys.blocksUncles |
|
||||
@@ -19,21 +22,25 @@ export type PaginatedQueryKeys =
|
||||
QueryKeys.txTokenTransfers;
|
||||
|
||||
export type PaginatedResponse<Q extends PaginatedQueryKeys> =
|
||||
Q extends (QueryKeys.blocks | QueryKeys.blocksReorgs | QueryKeys.blocksUncles) ? BlocksResponse :
|
||||
Q extends QueryKeys.blockTxs ? BlockTransactionsResponse :
|
||||
Q extends QueryKeys.txsValidate ? TransactionsResponseValidated :
|
||||
Q extends QueryKeys.txsPending ? TransactionsResponsePending :
|
||||
Q extends QueryKeys.txInternals ? InternalTransactionsResponse :
|
||||
Q extends QueryKeys.txLogs ? LogsResponse :
|
||||
Q extends QueryKeys.txTokenTransfers ? TokenTransferResponse :
|
||||
never
|
||||
Q extends QueryKeys.addressTxs ? AddressTransactionsResponse :
|
||||
Q extends QueryKeys.addressTokenTransfers ? AddressTokenTransferResponse :
|
||||
Q extends (QueryKeys.blocks | QueryKeys.blocksReorgs | QueryKeys.blocksUncles) ? BlocksResponse :
|
||||
Q extends QueryKeys.blockTxs ? BlockTransactionsResponse :
|
||||
Q extends QueryKeys.txsValidate ? TransactionsResponseValidated :
|
||||
Q extends QueryKeys.txsPending ? TransactionsResponsePending :
|
||||
Q extends QueryKeys.txInternals ? InternalTransactionsResponse :
|
||||
Q extends QueryKeys.txLogs ? LogsResponse :
|
||||
Q extends QueryKeys.txTokenTransfers ? TokenTransferResponse :
|
||||
never
|
||||
|
||||
export type PaginationFilters<Q extends PaginatedQueryKeys> =
|
||||
Q extends QueryKeys.blocks ? BlockFilters :
|
||||
Q extends QueryKeys.txsValidate ? TTxsFilters :
|
||||
Q extends QueryKeys.txsPending ? TTxsFilters :
|
||||
Q extends QueryKeys.txTokenTransfers ? TokenTransferFilters :
|
||||
never
|
||||
Q extends QueryKeys.addressTxs ? AddressTxsFilters :
|
||||
Q extends QueryKeys.addressTokenTransfers ? AddressTokenTransferFilters :
|
||||
Q extends QueryKeys.blocks ? BlockFilters :
|
||||
Q extends QueryKeys.txsValidate ? TTxsFilters :
|
||||
Q extends QueryKeys.txsPending ? TTxsFilters :
|
||||
Q extends QueryKeys.txTokenTransfers ? TokenTransferFilters :
|
||||
never
|
||||
|
||||
export type PaginationParams<Q extends PaginatedQueryKeys> = PaginatedResponse<Q>['next_page_params'];
|
||||
|
||||
@@ -42,6 +49,8 @@ type PaginationFields = {
|
||||
}
|
||||
|
||||
export const PAGINATION_FIELDS: PaginationFields = {
|
||||
[QueryKeys.addressTxs]: [ 'block_number', 'items_count', 'index' ],
|
||||
[QueryKeys.addressTokenTransfers]: [ 'block_number', 'items_count', 'index', 'transaction_hash' ],
|
||||
[QueryKeys.blocks]: [ 'block_number', 'items_count' ],
|
||||
[QueryKeys.blocksReorgs]: [ 'block_number', 'items_count' ],
|
||||
[QueryKeys.blocksUncles]: [ 'block_number', 'items_count' ],
|
||||
@@ -52,3 +61,21 @@ export const PAGINATION_FIELDS: PaginationFields = {
|
||||
[QueryKeys.txTokenTransfers]: [ 'block_number', 'items_count', 'transaction_hash', 'index' ],
|
||||
[QueryKeys.txLogs]: [ 'items_count', 'transaction_hash', 'index' ],
|
||||
};
|
||||
|
||||
type PaginationFiltersFields = {
|
||||
[K in PaginatedQueryKeys]: Array<KeysOfObjectOrNull<PaginationFilters<K>>>
|
||||
}
|
||||
|
||||
export const PAGINATION_FILTERS_FIELDS: PaginationFiltersFields = {
|
||||
[QueryKeys.addressTxs]: [ 'filter' ],
|
||||
[QueryKeys.addressTokenTransfers]: [ 'filter', 'type' ],
|
||||
[QueryKeys.blocks]: [ 'type' ],
|
||||
[QueryKeys.txsValidate]: [ 'filter', 'type', 'method' ],
|
||||
[QueryKeys.txsPending]: [ 'filter', 'type', 'method' ],
|
||||
[QueryKeys.txTokenTransfers]: [ 'type' ],
|
||||
[QueryKeys.blocksReorgs]: [],
|
||||
[QueryKeys.blocksUncles]: [],
|
||||
[QueryKeys.blockTxs]: [],
|
||||
[QueryKeys.txInternals]: [],
|
||||
[QueryKeys.txLogs]: [],
|
||||
};
|
||||
|
||||
@@ -38,14 +38,16 @@ interface TokenTransferBase {
|
||||
to: AddressParam;
|
||||
}
|
||||
|
||||
export type TokenTransferPagination = {
|
||||
block_number: number;
|
||||
index: number;
|
||||
items_count: number;
|
||||
transaction_hash: string;
|
||||
}
|
||||
|
||||
export interface TokenTransferResponse {
|
||||
items: Array<TokenTransfer>;
|
||||
next_page_params: {
|
||||
block_number: number;
|
||||
index: number;
|
||||
items_count: number;
|
||||
transaction_hash: string;
|
||||
} | null;
|
||||
next_page_params: TokenTransferPagination | null;
|
||||
}
|
||||
|
||||
export interface TokenTransferFilters {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { AddressParam } from './addressParams';
|
||||
|
||||
export type TransactionReward = {
|
||||
types: Array<string>;
|
||||
emission_reward: string;
|
||||
block_hash: string;
|
||||
from: AddressParam;
|
||||
to: AddressParam;
|
||||
}
|
||||
@@ -24,4 +24,6 @@ export enum QueryKeys {
|
||||
address='address',
|
||||
addressCounters='address-counters',
|
||||
addressTokenBalances='address-token-balances',
|
||||
addressTxs='addressTxs',
|
||||
addressTokenTransfers='addressTokenTransfers',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
import React from 'react';
|
||||
|
||||
import { base as txMock } from 'mocks/txs/tx';
|
||||
import TestApp from 'playwright/TestApp';
|
||||
|
||||
import AddressTxs from './AddressTxs';
|
||||
|
||||
const API_URL = '/node-api/addresses/0xd789a607CEac2f0E14867de4EB15b15C9FFB5859/transactions';
|
||||
|
||||
const hooksConfig = {
|
||||
router: {
|
||||
query: { id: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859' },
|
||||
},
|
||||
};
|
||||
|
||||
test('address txs +@mobile +@desktop-xl', async({ mount, page }) => {
|
||||
await page.route(API_URL, (route) => route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify({ items: [ txMock, txMock ], next_page_params: { block: 1 } }),
|
||||
}));
|
||||
|
||||
const component = await mount(
|
||||
<TestApp>
|
||||
<Box h={{ base: '134px', lg: 6 }}/>
|
||||
<AddressTxs/>
|
||||
</TestApp>,
|
||||
{ hooksConfig },
|
||||
);
|
||||
|
||||
await page.waitForResponse(API_URL),
|
||||
|
||||
await expect(component).toHaveScreenshot();
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
import { Element } from 'react-scroll';
|
||||
|
||||
import { QueryKeys } from 'types/client/queries';
|
||||
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
|
||||
import ActionBar from 'ui/shared/ActionBar';
|
||||
import Pagination from 'ui/shared/Pagination';
|
||||
import TxsContent from 'ui/txs/TxsContent';
|
||||
|
||||
import AddressTxsFilter from './AddressTxsFilter';
|
||||
|
||||
const FILTER_VALUES = [ 'from', 'to' ] as const;
|
||||
|
||||
type FilterType = typeof FILTER_VALUES[number];
|
||||
|
||||
const getFilterValue = (val: string | Array<string> | undefined): FilterType | undefined => {
|
||||
if (typeof val === 'string' && FILTER_VALUES.includes(val as FilterType)) {
|
||||
return val as FilterType;
|
||||
}
|
||||
};
|
||||
|
||||
const SCROLL_ELEM = 'address-txs';
|
||||
const SCROLL_OFFSET = -100;
|
||||
|
||||
const AddressTxs = () => {
|
||||
const router = useRouter();
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const [ filterValue, setFilterValue ] = React.useState<'from' | 'to' | undefined>(getFilterValue(router.query.filter));
|
||||
|
||||
const addressTxsQuery = useQueryWithPages({
|
||||
apiPath: `/node-api/addresses/${ router.query.id }/transactions`,
|
||||
queryName: QueryKeys.addressTxs,
|
||||
filters: { filter: filterValue },
|
||||
scroll: { elem: SCROLL_ELEM, offset: SCROLL_OFFSET },
|
||||
});
|
||||
|
||||
const handleFilterChange = React.useCallback((val: string | Array<string>) => {
|
||||
|
||||
const newVal = getFilterValue(val);
|
||||
setFilterValue(newVal);
|
||||
addressTxsQuery.onFilterChange({ filter: newVal });
|
||||
}, [ addressTxsQuery ]);
|
||||
|
||||
const isPaginatorHidden =
|
||||
!addressTxsQuery.isLoading &&
|
||||
!addressTxsQuery.isError &&
|
||||
addressTxsQuery.pagination.page === 1 &&
|
||||
!addressTxsQuery.pagination.hasNextPage;
|
||||
|
||||
const filter = (
|
||||
<AddressTxsFilter
|
||||
defaultFilter={ filterValue }
|
||||
onFilterChange={ handleFilterChange }
|
||||
isActive={ Boolean(filterValue) }
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Element name={ SCROLL_ELEM }>
|
||||
{ !isMobile && (
|
||||
<ActionBar mt={ -6 }>
|
||||
{ filter }
|
||||
{ !isPaginatorHidden && <Pagination { ...addressTxsQuery.pagination }/> }
|
||||
</ActionBar>
|
||||
) }
|
||||
<TxsContent
|
||||
filter={ filter }
|
||||
query={ addressTxsQuery }
|
||||
showSocketInfo={ false }
|
||||
currentAddress={ typeof router.query.id === 'string' ? router.query.id : undefined }
|
||||
/>
|
||||
</Element>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddressTxs;
|
||||
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuOptionGroup,
|
||||
MenuItemOption,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import FilterButton from 'ui/shared/FilterButton';
|
||||
|
||||
interface Props {
|
||||
isActive: boolean;
|
||||
defaultFilter: 'from' | 'to' | undefined;
|
||||
onFilterChange: (nextValue: string | Array<string>) => void;
|
||||
}
|
||||
|
||||
const AddressTxsFilter = ({ onFilterChange, defaultFilter, isActive }: Props) => {
|
||||
const { isOpen, onToggle } = useDisclosure();
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<MenuButton>
|
||||
<FilterButton
|
||||
isActive={ isOpen || isActive }
|
||||
onClick={ onToggle }
|
||||
/>
|
||||
</MenuButton>
|
||||
<MenuList zIndex={ 2 }>
|
||||
<MenuOptionGroup defaultValue={ defaultFilter || 'all' } title="Address" type="radio" onChange={ onFilterChange }>
|
||||
<MenuItemOption value="all">All</MenuItemOption>
|
||||
<MenuItemOption value="from">From</MenuItemOption>
|
||||
<MenuItemOption value="to">To</MenuItemOption>
|
||||
</MenuOptionGroup>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AddressTxsFilter);
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
@@ -9,6 +9,7 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
|
||||
|
||||
import useFetch from 'lib/hooks/useFetch';
|
||||
import AddressDetails from 'ui/address/AddressDetails';
|
||||
import AddressTxs from 'ui/address/AddressTxs';
|
||||
import Page from 'ui/shared/Page/Page';
|
||||
import PageTitle from 'ui/shared/Page/PageTitle';
|
||||
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
|
||||
@@ -32,7 +33,7 @@ const AddressPageContent = () => {
|
||||
].map((tag) => <Tag key={ tag.label }>{ tag.display_name }</Tag>);
|
||||
|
||||
const tabs: Array<RoutedTab> = [
|
||||
{ id: 'txs', title: 'Transactions', component: null },
|
||||
{ id: 'txs', title: 'Transactions', component: <AddressTxs/> },
|
||||
{ id: 'token_transfers', title: 'Token transfers', component: null },
|
||||
{ id: 'tokens', title: 'Tokens', component: null },
|
||||
{ id: 'internal_txn', title: 'Internal txn', component: null },
|
||||
|
||||
+17
-5
@@ -2,16 +2,28 @@ import { Tag, chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
baseAddress: string;
|
||||
addressFrom: string;
|
||||
isIn: boolean;
|
||||
isOut: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const InOutTag = ({ baseAddress, addressFrom, className }: Props) => {
|
||||
const isOut = addressFrom === baseAddress;
|
||||
const InOutTag = ({ isIn, isOut, className }: Props) => {
|
||||
if (!isIn && !isOut) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const colorScheme = isOut ? 'orange' : 'green';
|
||||
|
||||
return <Tag className={ className } colorScheme={ colorScheme }>{ isOut ? 'OUT' : 'IN' }</Tag>;
|
||||
return (
|
||||
<Tag
|
||||
className={ className }
|
||||
colorScheme={ colorScheme }
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
>
|
||||
{ isOut ? 'OUT' : 'IN' }
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(chakra(InOutTag));
|
||||
|
||||
@@ -53,7 +53,7 @@ const TokenTransferListItem = ({ token, total, tx_hash: txHash, from, to, baseAd
|
||||
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash }/>
|
||||
</Address>
|
||||
{ baseAddress ?
|
||||
<InOutTag baseAddress={ baseAddress } addressFrom={ from.hash } w="50px" textAlign="center"/> :
|
||||
<InOutTag isIn={ baseAddress === to.hash } isOut={ baseAddress === from.hash } w="50px" textAlign="center"/> :
|
||||
<Icon as={ eastArrowIcon } boxSize={ 6 } color="gray.500"/>
|
||||
}
|
||||
<Address width={ addressWidth }>
|
||||
|
||||
@@ -59,7 +59,7 @@ const TokenTransferTableItem = ({ token, total, tx_hash: txHash, from, to, baseA
|
||||
</Td>
|
||||
{ baseAddress && (
|
||||
<Td px={ 0 }>
|
||||
<InOutTag baseAddress={ baseAddress } addressFrom={ from.hash } w="50px" textAlign="center" mt="3px"/>
|
||||
<InOutTag isIn={ baseAddress === to.hash } isOut={ baseAddress === from.hash } w="50px" textAlign="center" mt="3px"/>
|
||||
</Td>
|
||||
) }
|
||||
<Td>
|
||||
|
||||
@@ -24,9 +24,11 @@ type Props = {
|
||||
query: QueryResult;
|
||||
showBlockInfo?: boolean;
|
||||
showSocketInfo?: boolean;
|
||||
currentAddress?: string;
|
||||
filter?: React.ReactNode;
|
||||
}
|
||||
|
||||
const TxsContent = ({ query, showBlockInfo = true, showSocketInfo = true }: Props) => {
|
||||
const TxsContent = ({ filter, query, showBlockInfo = true, showSocketInfo = true, currentAddress }: Props) => {
|
||||
const { data, isLoading, isError, setSortByField, setSortByValue, sorting } = useTxsSort(query);
|
||||
const isPaginatorHidden = !isLoading && !isError && query.pagination.page === 1 && !query.pagination.hasNextPage;
|
||||
const isMobile = useIsMobile();
|
||||
@@ -60,7 +62,7 @@ const TxsContent = ({ query, showBlockInfo = true, showSocketInfo = true }: Prop
|
||||
{ ({ content }) => <Box>{ content }</Box> }
|
||||
</TxsNewItemNotice>
|
||||
) }
|
||||
{ txs.map(tx => <TxsListItem tx={ tx } key={ tx.hash } showBlockInfo={ showBlockInfo }/>) }
|
||||
{ txs.map(tx => <TxsListItem tx={ tx } key={ tx.hash } showBlockInfo={ showBlockInfo } currentAddress={ currentAddress }/>) }
|
||||
</Box>
|
||||
</Show>
|
||||
<Hide below="lg" ssr={ false }>
|
||||
@@ -71,6 +73,7 @@ const TxsContent = ({ query, showBlockInfo = true, showSocketInfo = true }: Prop
|
||||
showBlockInfo={ showBlockInfo }
|
||||
showSocketInfo={ showSocketInfo }
|
||||
top={ isPaginatorHidden ? 0 : 80 }
|
||||
currentAddress={ currentAddress }
|
||||
/>
|
||||
</Hide>
|
||||
</>
|
||||
@@ -86,6 +89,7 @@ const TxsContent = ({ query, showBlockInfo = true, showSocketInfo = true }: Prop
|
||||
setSorting={ setSortByValue }
|
||||
paginationProps={ query.pagination }
|
||||
showPagination={ !isPaginatorHidden }
|
||||
filterComponent={ filter }
|
||||
/>
|
||||
) }
|
||||
{ content }
|
||||
|
||||
@@ -17,18 +17,14 @@ type Props = {
|
||||
paginationProps: PaginationProps;
|
||||
className?: string;
|
||||
showPagination?: boolean;
|
||||
filterComponent?: React.ReactNode;
|
||||
}
|
||||
|
||||
const TxsHeaderMobile = ({ sorting, setSorting, paginationProps, className, showPagination = true }: Props) => {
|
||||
const TxsHeaderMobile = ({ filterComponent, sorting, setSorting, paginationProps, className, showPagination = true }: Props) => {
|
||||
return (
|
||||
<ActionBar className={ className }>
|
||||
<HStack>
|
||||
{ /* api is not implemented */ }
|
||||
{ /* <TxsFilters
|
||||
filters={ filters }
|
||||
onFiltersChange={ setFilters }
|
||||
appliedFiltersNum={ 0 }
|
||||
/> */ }
|
||||
{ filterComponent }
|
||||
<TxsSorting
|
||||
isActive={ Boolean(sorting) }
|
||||
setSorting={ setSorting }
|
||||
|
||||
+24
-8
@@ -24,17 +24,30 @@ import AdditionalInfoButton from 'ui/shared/AdditionalInfoButton';
|
||||
import Address from 'ui/shared/address/Address';
|
||||
import AddressIcon from 'ui/shared/address/AddressIcon';
|
||||
import AddressLink from 'ui/shared/address/AddressLink';
|
||||
import InOutTag from 'ui/shared/InOutTag';
|
||||
import TxStatus from 'ui/shared/TxStatus';
|
||||
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
|
||||
import TxType from 'ui/txs/TxType';
|
||||
|
||||
const TxsListItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: boolean}) => {
|
||||
type Props = {
|
||||
tx: Transaction;
|
||||
showBlockInfo: boolean;
|
||||
currentAddress?: string;
|
||||
}
|
||||
|
||||
const TAG_WIDTH = 48;
|
||||
const ARROW_WIDTH = 24;
|
||||
|
||||
const TxsListItem = ({ tx, showBlockInfo, currentAddress }: Props) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const iconColor = useColorModeValue('blue.600', 'blue.300');
|
||||
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
|
||||
const dataTo = tx.to ? tx.to : tx.created_contract;
|
||||
|
||||
const isOut = Boolean(currentAddress && currentAddress === tx.from.hash);
|
||||
const isIn = Boolean(currentAddress && currentAddress === tx.to?.hash);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box width="100%" borderBottom="1px solid" borderColor={ borderColor } _first={{ borderTop: '1px solid', borderColor }}>
|
||||
@@ -83,7 +96,7 @@ const TxsListItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: boo
|
||||
</Box>
|
||||
) }
|
||||
<Flex alignItems="center" height={ 6 } mt={ 6 }>
|
||||
<Address width="calc((100%-40px)/2)">
|
||||
<Address width={ `calc((100%-${ currentAddress ? TAG_WIDTH : ARROW_WIDTH + 8 }px)/2)` }>
|
||||
<AddressIcon hash={ tx.from.hash }/>
|
||||
<AddressLink
|
||||
hash={ tx.from.hash }
|
||||
@@ -92,12 +105,15 @@ const TxsListItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: boo
|
||||
ml={ 2 }
|
||||
/>
|
||||
</Address>
|
||||
<Icon
|
||||
as={ rightArrowIcon }
|
||||
boxSize={ 6 }
|
||||
mx={ 2 }
|
||||
color="gray.500"
|
||||
/>
|
||||
{ (isIn || isOut) ?
|
||||
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" mr={ 2 }/> : (
|
||||
<Icon
|
||||
as={ rightArrowIcon }
|
||||
boxSize={ 6 }
|
||||
mx={ 2 }
|
||||
color="gray.500"
|
||||
/>
|
||||
) }
|
||||
<Address width="calc((100%-40px)/2)">
|
||||
<AddressIcon hash={ dataTo.hash }/>
|
||||
<AddressLink
|
||||
|
||||
+4
-2
@@ -18,9 +18,10 @@ type Props = {
|
||||
top: number;
|
||||
showBlockInfo: boolean;
|
||||
showSocketInfo: boolean;
|
||||
currentAddress?: string;
|
||||
}
|
||||
|
||||
const TxsTable = ({ txs, sort, sorting, top, showBlockInfo, showSocketInfo }: Props) => {
|
||||
const TxsTable = ({ txs, sort, sorting, top, showBlockInfo, showSocketInfo, currentAddress }: Props) => {
|
||||
return (
|
||||
<Table variant="simple" minWidth="950px" size="xs">
|
||||
<TheadSticky top={ top }>
|
||||
@@ -31,7 +32,7 @@ const TxsTable = ({ txs, sort, sorting, top, showBlockInfo, showSocketInfo }: Pr
|
||||
<Th width="15%">Method</Th>
|
||||
{ showBlockInfo && <Th width="11%">Block</Th> }
|
||||
<Th width={{ xl: '128px', base: '66px' }}>From</Th>
|
||||
<Th width={{ xl: '36px', base: '0' }}></Th>
|
||||
<Th width={{ xl: currentAddress ? '48px' : '36px', base: '0' }}></Th>
|
||||
<Th width={{ xl: '128px', base: '66px' }}>To</Th>
|
||||
<Th width="18%" isNumeric>
|
||||
<Link onClick={ sort('val') } display="flex" justifyContent="end">
|
||||
@@ -60,6 +61,7 @@ const TxsTable = ({ txs, sort, sorting, top, showBlockInfo, showSocketInfo }: Pr
|
||||
key={ item.hash }
|
||||
tx={ item }
|
||||
showBlockInfo={ showBlockInfo }
|
||||
currentAddress={ currentAddress }
|
||||
/>
|
||||
)) }
|
||||
</Tbody>
|
||||
|
||||
+27
-11
@@ -28,13 +28,23 @@ import Address from 'ui/shared/address/Address';
|
||||
import AddressIcon from 'ui/shared/address/AddressIcon';
|
||||
import AddressLink from 'ui/shared/address/AddressLink';
|
||||
import CurrencyValue from 'ui/shared/CurrencyValue';
|
||||
import InOutTag from 'ui/shared/InOutTag';
|
||||
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
|
||||
import TxStatus from 'ui/shared/TxStatus';
|
||||
import TxAdditionalInfo from 'ui/txs/TxAdditionalInfo';
|
||||
|
||||
import TxType from './TxType';
|
||||
|
||||
const TxsTableItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: boolean }) => {
|
||||
type Props = {
|
||||
tx: Transaction;
|
||||
showBlockInfo: boolean;
|
||||
currentAddress?: string;
|
||||
}
|
||||
|
||||
const TxsTableItem = ({ tx, showBlockInfo, currentAddress }: Props) => {
|
||||
const isOut = Boolean(currentAddress && currentAddress === tx.from.hash);
|
||||
const isIn = Boolean(currentAddress && currentAddress === tx.to?.hash);
|
||||
|
||||
const addressFrom = (
|
||||
<Address>
|
||||
<Tooltip label={ tx.from.implementation_name }>
|
||||
@@ -110,8 +120,11 @@ const TxsTableItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: bo
|
||||
<Td>
|
||||
{ addressFrom }
|
||||
</Td>
|
||||
<Td>
|
||||
<Icon as={ rightArrowIcon } boxSize={ 6 } mr={ 2 } color="gray.500"/>
|
||||
<Td px={ 0 }>
|
||||
{ (isIn || isOut) ?
|
||||
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" mr={ 2 }/> :
|
||||
<Icon as={ rightArrowIcon } boxSize={ 6 } mx="6px" color="gray.500"/>
|
||||
}
|
||||
</Td>
|
||||
<Td>
|
||||
{ addressTo }
|
||||
@@ -121,14 +134,17 @@ const TxsTableItem = ({ tx, showBlockInfo }: {tx: Transaction; showBlockInfo: bo
|
||||
<Td colSpan={ 3 }>
|
||||
<Box>
|
||||
{ addressFrom }
|
||||
<Icon
|
||||
as={ rightArrowIcon }
|
||||
boxSize={ 6 }
|
||||
mt={ 2 }
|
||||
mb={ 1 }
|
||||
color="gray.500"
|
||||
transform="rotate(90deg)"
|
||||
/>
|
||||
{ (isIn || isOut) ?
|
||||
<InOutTag isIn={ isIn } isOut={ isOut } width="48px" my={ 2 }/> : (
|
||||
<Icon
|
||||
as={ rightArrowIcon }
|
||||
boxSize={ 6 }
|
||||
mt={ 2 }
|
||||
mb={ 1 }
|
||||
color="gray.500"
|
||||
transform="rotate(90deg)"
|
||||
/>
|
||||
) }
|
||||
{ addressTo }
|
||||
</Box>
|
||||
</Td>
|
||||
|
||||
Reference in New Issue
Block a user