From 72bcf872d9b952570dcb4fb2e971493772dae50f Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 15 May 2024 16:39:19 +0300 Subject: [PATCH 01/12] added username prop on AccountName and AccountLink --- src/components/AccountName/AccountName.tsx | 12 +++++++----- src/components/Links/AccountLink/AccountLink.tsx | 3 +++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/AccountName/AccountName.tsx b/src/components/AccountName/AccountName.tsx index 7ceee5fab..672e68043 100644 --- a/src/components/AccountName/AccountName.tsx +++ b/src/components/AccountName/AccountName.tsx @@ -1,5 +1,4 @@ import { useEffect, useState } from 'react'; -import classNames from 'classnames'; import { HEROTAG_SUFFIX } from 'appConstants'; import { ReactComponent as IdentityLogo } from 'assets/img/logos/identity.svg'; @@ -10,12 +9,14 @@ import { AccountAssetType, WithClassnameType } from 'types'; export interface AccountNameUIType extends WithClassnameType { address: string; + username?: string; assets?: AccountAssetType; fetchAssets?: boolean; } export const AccountName = ({ address, + username, assets, fetchAssets = false, className, @@ -46,14 +47,15 @@ export const AccountName = ({ }, [address, fetchAssets, assets]); const displayAssets = assets || fetchedAssets; + const displayName = username || displayAssets?.name; - if (displayAssets?.name) { - const name = formatHerotag(displayAssets.name); + if (displayName) { + const name = formatHerotag(displayName); const description = `${name} (${address})`; return ( <> - {displayAssets.name.endsWith(HEROTAG_SUFFIX) && ( + {displayName.endsWith(HEROTAG_SUFFIX) && ( - {name} + {displayName} ); diff --git a/src/components/Links/AccountLink/AccountLink.tsx b/src/components/Links/AccountLink/AccountLink.tsx index f90f03a30..2d795f61d 100644 --- a/src/components/Links/AccountLink/AccountLink.tsx +++ b/src/components/Links/AccountLink/AccountLink.tsx @@ -15,6 +15,7 @@ import { AccountAssetType, WithClassnameType } from 'types'; export interface AccountLinkType extends WithClassnameType { address: string; + username?: string; assets?: AccountAssetType; linkClassName?: string; fetchAssets?: boolean; @@ -25,6 +26,7 @@ export interface AccountLinkType extends WithClassnameType { export const AccountLink = ({ address, assets, + username, fetchAssets = false, showLockedAccounts = true, hasHighlight, @@ -62,6 +64,7 @@ export const AccountLink = ({ > Date: Wed, 15 May 2024 16:41:51 +0300 Subject: [PATCH 02/12] moved TransactionFilters to Filters folder --- src/components/Filters/SelectFilter.tsx | 2 +- .../TransactionsFilters/AgeColumnFilters.tsx | 0 .../TransactionsFilters/FromColumnFilters.tsx | 0 .../MethodColumnFilters.tsx | 0 .../ShardColumnFilters.tsx | 0 .../StatusColumnFilters.tsx | 0 .../TransactionsFilters/ToColumnFilters.tsx | 0 .../ValueColumnFilters.tsx | 0 .../TransactionsFilters/index.ts | 2 +- src/components/Filters/index.ts | 1 + .../components/Header/Header.tsx | 5 +- .../TransactionsFilters/MethodList.tsx | 46 ------------------- src/pages/Transactions/Transactions.tsx | 2 +- 13 files changed, 6 insertions(+), 52 deletions(-) rename src/components/{TransactionsTable/components => Filters}/TransactionsFilters/AgeColumnFilters.tsx (100%) rename src/components/{TransactionsTable/components => Filters}/TransactionsFilters/FromColumnFilters.tsx (100%) rename src/components/{TransactionsTable/components => Filters}/TransactionsFilters/MethodColumnFilters.tsx (100%) rename src/components/{TransactionsTable/components => Filters}/TransactionsFilters/ShardColumnFilters.tsx (100%) rename src/components/{TransactionsTable/components => Filters}/TransactionsFilters/StatusColumnFilters.tsx (100%) rename src/components/{TransactionsTable/components => Filters}/TransactionsFilters/ToColumnFilters.tsx (100%) rename src/components/{TransactionsTable/components => Filters}/TransactionsFilters/ValueColumnFilters.tsx (100%) rename src/components/{TransactionsTable/components => Filters}/TransactionsFilters/index.ts (84%) delete mode 100644 src/components/TransactionsTable/components/TransactionsFilters/MethodList.tsx diff --git a/src/components/Filters/SelectFilter.tsx b/src/components/Filters/SelectFilter.tsx index ea4344c4a..7e2cb6a7e 100644 --- a/src/components/Filters/SelectFilter.tsx +++ b/src/components/Filters/SelectFilter.tsx @@ -55,7 +55,7 @@ export const SelectFilter = ({ } const defaultValues = options.filter( - (option) => existingValues && existingValues.includes(option.value) + (option) => existingValues && existingValues.includes(String(option.value)) ); const updateSelectValue = (selectValue: string) => { diff --git a/src/components/TransactionsTable/components/TransactionsFilters/AgeColumnFilters.tsx b/src/components/Filters/TransactionsFilters/AgeColumnFilters.tsx similarity index 100% rename from src/components/TransactionsTable/components/TransactionsFilters/AgeColumnFilters.tsx rename to src/components/Filters/TransactionsFilters/AgeColumnFilters.tsx diff --git a/src/components/TransactionsTable/components/TransactionsFilters/FromColumnFilters.tsx b/src/components/Filters/TransactionsFilters/FromColumnFilters.tsx similarity index 100% rename from src/components/TransactionsTable/components/TransactionsFilters/FromColumnFilters.tsx rename to src/components/Filters/TransactionsFilters/FromColumnFilters.tsx diff --git a/src/components/TransactionsTable/components/TransactionsFilters/MethodColumnFilters.tsx b/src/components/Filters/TransactionsFilters/MethodColumnFilters.tsx similarity index 100% rename from src/components/TransactionsTable/components/TransactionsFilters/MethodColumnFilters.tsx rename to src/components/Filters/TransactionsFilters/MethodColumnFilters.tsx diff --git a/src/components/TransactionsTable/components/TransactionsFilters/ShardColumnFilters.tsx b/src/components/Filters/TransactionsFilters/ShardColumnFilters.tsx similarity index 100% rename from src/components/TransactionsTable/components/TransactionsFilters/ShardColumnFilters.tsx rename to src/components/Filters/TransactionsFilters/ShardColumnFilters.tsx diff --git a/src/components/TransactionsTable/components/TransactionsFilters/StatusColumnFilters.tsx b/src/components/Filters/TransactionsFilters/StatusColumnFilters.tsx similarity index 100% rename from src/components/TransactionsTable/components/TransactionsFilters/StatusColumnFilters.tsx rename to src/components/Filters/TransactionsFilters/StatusColumnFilters.tsx diff --git a/src/components/TransactionsTable/components/TransactionsFilters/ToColumnFilters.tsx b/src/components/Filters/TransactionsFilters/ToColumnFilters.tsx similarity index 100% rename from src/components/TransactionsTable/components/TransactionsFilters/ToColumnFilters.tsx rename to src/components/Filters/TransactionsFilters/ToColumnFilters.tsx diff --git a/src/components/TransactionsTable/components/TransactionsFilters/ValueColumnFilters.tsx b/src/components/Filters/TransactionsFilters/ValueColumnFilters.tsx similarity index 100% rename from src/components/TransactionsTable/components/TransactionsFilters/ValueColumnFilters.tsx rename to src/components/Filters/TransactionsFilters/ValueColumnFilters.tsx diff --git a/src/components/TransactionsTable/components/TransactionsFilters/index.ts b/src/components/Filters/TransactionsFilters/index.ts similarity index 84% rename from src/components/TransactionsTable/components/TransactionsFilters/index.ts rename to src/components/Filters/TransactionsFilters/index.ts index bac4fd09f..336f22eb8 100644 --- a/src/components/TransactionsTable/components/TransactionsFilters/index.ts +++ b/src/components/Filters/TransactionsFilters/index.ts @@ -1,8 +1,8 @@ export * from './AgeColumnFilters'; export * from './FromColumnFilters'; export * from './MethodColumnFilters'; -export * from './MethodList'; export * from './ShardColumnFilters'; export * from './StatusColumnFilters'; export * from './ToColumnFilters'; +export * from './TransactionInPoolTypeFilter'; export * from './ValueColumnFilters'; diff --git a/src/components/Filters/index.ts b/src/components/Filters/index.ts index da859c298..181d7788d 100644 --- a/src/components/Filters/index.ts +++ b/src/components/Filters/index.ts @@ -2,3 +2,4 @@ export * from './DateFilter'; export * from './SearchFilter'; export * from './SelectFilter'; export * from './TokenSelectFilter'; +export * from './TransactionsFilters'; diff --git a/src/components/TransactionsTable/components/Header/Header.tsx b/src/components/TransactionsTable/components/Header/Header.tsx index 2950761ff..aceb20d8e 100644 --- a/src/components/TransactionsTable/components/Header/Header.tsx +++ b/src/components/TransactionsTable/components/Header/Header.tsx @@ -1,5 +1,3 @@ -import { TransactionTableType } from 'types'; - import { AgeColumnFilters, FromColumnFilters, @@ -8,7 +6,8 @@ import { MethodColumnFilters, ToColumnFilters, ValueColumnFilters -} from '../TransactionsFilters'; +} from 'components'; +import { TransactionTableType } from 'types'; export const Header = ({ showDirectionCol = false, diff --git a/src/components/TransactionsTable/components/TransactionsFilters/MethodList.tsx b/src/components/TransactionsTable/components/TransactionsFilters/MethodList.tsx deleted file mode 100644 index 96d29e359..000000000 --- a/src/components/TransactionsTable/components/TransactionsFilters/MethodList.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { useSearchParams } from 'react-router-dom'; - -export const MethodList = () => { - const [searchParams, setSearchParams] = useSearchParams(); - const { function: method } = Object.fromEntries(searchParams); - - const setMethod = (method: string) => { - const { page, size, ...rest } = Object.fromEntries(searchParams); - - if (method === '' && rest?.function) { - delete rest.function; - } - const nextUrlParams = { - ...rest, - ...(method ? { function: method } : {}) - }; - - setSearchParams(nextUrlParams); - }; - - if (!method) { - return null; - } - - return ( -
-
    - {method && ( -
  • -
    - {method} -
    { - setMethod(''); - }} - className='text-green px-2 cursor-pointer' - > - × -
    -
    -
  • - )} -
-
- ); -}; diff --git a/src/pages/Transactions/Transactions.tsx b/src/pages/Transactions/Transactions.tsx index 300916fc3..ca170f890 100644 --- a/src/pages/Transactions/Transactions.tsx +++ b/src/pages/Transactions/Transactions.tsx @@ -4,7 +4,7 @@ import { useSearchParams } from 'react-router-dom'; import { TransactionsTable, PulsatingLed } from 'components'; import { shardSpanText } from 'components/ShardSpan'; -import { MethodList } from 'components/TransactionsTable/components/TransactionsFilters'; +import { MethodList } from 'components/TransactionsTable/components'; import { useAdapter, useGetPage, From 7fb0daed025905ddafeb231206b3d0b4863f9d20 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 15 May 2024 16:43:34 +0300 Subject: [PATCH 03/12] - added api endpoints for transactions pool - added types for transactions in pool - moved transactions routes to transactions layout --- src/helpers/urlBuilder.ts | 1 + src/hooks/adapter/adapter.ts | 22 ++++++++++++-- src/hooks/adapter/helpers.ts | 27 ++++++++++++++++- src/routes/layouts/index.ts | 1 + src/routes/layouts/transactionsLayout.ts | 37 ++++++++++++++++++++++++ src/routes/routes.tsx | 22 +++----------- src/types/adapter.types.ts | 8 ++++- src/types/transaction.types.ts | 24 ++++++++++++++- 8 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 src/routes/layouts/transactionsLayout.ts diff --git a/src/helpers/urlBuilder.ts b/src/helpers/urlBuilder.ts index f20e51ab0..cdda64c6f 100644 --- a/src/helpers/urlBuilder.ts +++ b/src/helpers/urlBuilder.ts @@ -10,6 +10,7 @@ export const urlBuilder = { transactionDetailsScResults: (hash: string) => `/transactions/${hash}/results`, transactionDetailsLogs: (hash: string) => `/transactions/${hash}/logs`, + transactionInPoolDetails: (hash: string) => `/transactions/pool/${hash}`, nodeDetails: (publicKey: string) => `/nodes/${publicKey}`, accountDetails: (address: string) => `/accounts/${address}`, accountDetailsTokens: (address: string) => `/accounts/${address}/tokens`, diff --git a/src/hooks/adapter/adapter.ts b/src/hooks/adapter/adapter.ts index 34c17340e..4a2cf74bf 100644 --- a/src/hooks/adapter/adapter.ts +++ b/src/hooks/adapter/adapter.ts @@ -9,7 +9,8 @@ import { GetNftsType, GetTokensType, GetAccountsType, - GetIdentitiesType + GetIdentitiesType, + GetTransactionsInPoolType } from 'types/adapter.types'; import { @@ -20,7 +21,8 @@ import { getProviderParams, getTokensParams, getCollectionsParams, - getNftsParams + getNftsParams, + getTransactionsInPoolParams } from './helpers'; import { useAdapterConfig } from './useAdapterConfig'; @@ -212,6 +214,22 @@ export const useAdapter = () => { getScResultsCount: () => provider({ url: '/results/c' }), + /* Transactions Pool */ + + getTransactionInPool: (hash: string) => provider({ url: `/pool/${hash}` }), + + getTransactionsInPool: (params: GetTransactionsInPoolType) => + provider({ + url: '/pool', + params: getTransactionsInPoolParams(params) + }), + + getTransactionsInPoolCount: (params: GetTransactionsInPoolType) => + provider({ + url: '/pool/c', + params: getTransactionsInPoolParams({ isCount: true, ...params }) + }), + /* Account */ getAccount: ({ diff --git a/src/hooks/adapter/helpers.ts b/src/hooks/adapter/helpers.ts index 3c6a8a955..0a1c4b4c3 100644 --- a/src/hooks/adapter/helpers.ts +++ b/src/hooks/adapter/helpers.ts @@ -6,7 +6,8 @@ import { GetProvidersType, GetTokensType, GetCollectionsType, - GetNftsType + GetNftsType, + GetTransactionsInPoolType } from 'types/adapter.types'; export const getAccountParams = (address?: string) => @@ -80,6 +81,30 @@ export function getTransactionsParams({ return params; } +export function getTransactionsInPoolParams({ + page = 1, + size = PAGE_SIZE, + sender, + receiver, + type, + // not on api + isCount = false +}: GetTransactionsInPoolType) { + const params: AdapterProviderPropsType['params'] = { + ...(isCount + ? {} + : { + from: (page - 1) * size, + size + }), + ...(sender ? { sender } : {}), + ...(receiver ? { receiver } : {}), + ...(type ? { type } : {}) + }; + + return params; +} + export function getNodeParams({ type, status, diff --git a/src/routes/layouts/index.ts b/src/routes/layouts/index.ts index 4391e2b18..df1e5020f 100644 --- a/src/routes/layouts/index.ts +++ b/src/routes/layouts/index.ts @@ -3,4 +3,5 @@ export * from './blockLayout'; export * from './collectionLayout'; export * from './nftLayout'; export * from './tokenLayout'; +export * from './transactionsLayout'; export * from './validatorLayout'; diff --git a/src/routes/layouts/transactionsLayout.ts b/src/routes/layouts/transactionsLayout.ts new file mode 100644 index 000000000..16f26c439 --- /dev/null +++ b/src/routes/layouts/transactionsLayout.ts @@ -0,0 +1,37 @@ +import { TransactionDetails } from 'pages/TransactionDetails'; +import { TransactionInPoolDetails } from 'pages/TransactionInPoolDetails'; +import { Transactions } from 'pages/Transactions'; +import { TransactionsInPool } from 'pages/TransactionsInPool'; + +import { TitledRouteObject } from '../routes'; + +export const transactionsRoutes = { + transactions: '/transactions', + transactionsInPool: '/transactions/pool', + transactionsInPoolDetails: '/transactions/pool/:hash', + transactionDetails: '/transactions/:hash/*', + transactionDetailsLogs: '/transactions/:hash/logs' +}; + +export const transactionsLayout: TitledRouteObject[] = [ + { + path: transactionsRoutes.transactions, + title: 'Transactions', + Component: Transactions + }, + { + path: transactionsRoutes.transactionDetails, + title: 'Transaction Details', + Component: TransactionDetails + }, + { + path: transactionsRoutes.transactionsInPool, + title: 'Transactions In Pool', + Component: TransactionsInPool + }, + { + path: transactionsRoutes.transactionsInPoolDetails, + title: 'Transaction In Pool Details', + Component: TransactionInPoolDetails + } +]; diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index 14512e33d..d4e1149eb 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -9,8 +9,6 @@ import { EmptySearch } from 'pages/EmptySearch'; import { HashSearch } from 'pages/HashSearch'; import { Home } from 'pages/Home'; import { PageNotFound } from 'pages/PageNotFound'; -import { TransactionDetails } from 'pages/TransactionDetails'; -import { Transactions } from 'pages/Transactions'; import { generateNetworkRoutes } from './helpers/generateNetworkRoutes'; import { wrapRoutes } from './helpers/wrapRoutes'; @@ -26,6 +24,8 @@ import { nftRoutes, tokenLayout, tokensRoutes, + transactionsLayout, + transactionsRoutes, validatorLayout, validatorsRoutes } from './layouts'; @@ -37,6 +37,7 @@ export { collectionRoutes, nftRoutes, tokensRoutes, + transactionsRoutes, validatorsRoutes }; export interface TitledRouteObject extends NonIndexRouteObject { @@ -55,12 +56,6 @@ export const searchRoutes = { query: '/search/:hash' }; -export const transactionsRoutes = { - transactions: '/transactions', - transactionDetails: '/transactions/:hash/*', - transactionDetailsLogs: '/transactions/:hash/logs' -}; - export const routes = { ...accountsRoutes, ...applicationsRoutes, @@ -96,16 +91,6 @@ const mainRoutes: TitledRouteObject[] = [ title: 'Analytics', Component: AnalyticsCompare }, - { - path: transactionsRoutes.transactions, - title: 'Transactions', - Component: Transactions - }, - { - path: transactionsRoutes.transactionDetails, - title: 'Transaction Details', - Component: TransactionDetails - }, { path: searchRoutes.index, title: 'Search', @@ -121,6 +106,7 @@ const mainRoutes: TitledRouteObject[] = [ ...collectionLayout, ...nftLayout, ...tokenLayout, + ...transactionsLayout, ...validatorLayout ] } diff --git a/src/types/adapter.types.ts b/src/types/adapter.types.ts index 6de24d40d..230e74147 100644 --- a/src/types/adapter.types.ts +++ b/src/types/adapter.types.ts @@ -1,4 +1,4 @@ -import { SortOrderEnum } from 'types'; +import { SortOrderEnum, TransactionInPoolTypeEnum } from 'types'; export interface BaseApiType { page?: number; @@ -111,6 +111,12 @@ export interface GetTransactionsType extends SortableApiType { isRelayed?: boolean; } +export interface GetTransactionsInPoolType extends SortableApiType { + sender?: string; + receiver?: string; + type?: TransactionInPoolTypeEnum; +} + export interface GetProvidersType extends BaseApiType { identity?: string; providers?: string; diff --git a/src/types/transaction.types.ts b/src/types/transaction.types.ts index bcad844e4..832d4e90f 100644 --- a/src/types/transaction.types.ts +++ b/src/types/transaction.types.ts @@ -70,6 +70,27 @@ export interface TransactionSCResultLogType { events: EventType[]; } +// TRANSACTION IN POOL + +export interface TransactionInPoolType { + txHash: string; + sender: string; + receiver: string; + nonce: number; + value: string; + gasPrice: number; + gasLimit: number; + type: TransactionInPoolTypeEnum; + receiverUsername?: string; + data?: string; +} + +export enum TransactionInPoolTypeEnum { + Transaction = 'Transaction', + SmartContractResult = 'SmartContractResult', + Reward = 'Reward' +} + // TRANSACTION LOGS export interface LogType { @@ -101,7 +122,8 @@ export enum TransactionFiltersEnum { after = 'after', status = 'status', search = 'search', - token = 'token' + token = 'token', + transactionsInPoolType = 'type' } // Avoid issues with differences between methods and actions From 77f48822987fc09320665a6da260fe5f204188c3 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 15 May 2024 17:03:31 +0300 Subject: [PATCH 04/12] - added TransactionsInPool page - added TransactionsInPoolTable component --- .../TransactionInPoolTypeFilter.tsx | 79 ++++++++++++ .../TransactionsInPoolTable.tsx | 122 ++++++++++++++++++ .../components/TransactionInPoolTypeBadge.tsx | 82 ++++++++++++ .../components/TransactionsInPoolHeader.tsx | 32 +++++ .../components/TransactionsInPoolRow.tsx | 51 ++++++++ .../components/index.ts | 3 + .../TransactionsInPoolTable/index.ts | 1 + .../TransactionsTable/TransactionsTable.tsx | 12 +- .../components/MethodList.tsx | 46 +++++++ .../TransactionsTable/components/index.ts | 7 + src/hooks/urlFilters/index.ts | 1 + .../useGetTransactionInPoolFilters.ts | 26 ++++ .../TransactionsInPool/TransactionsInPool.tsx | 88 +++++++++++++ src/pages/TransactionsInPool/index.ts | 1 + 14 files changed, 546 insertions(+), 5 deletions(-) create mode 100644 src/components/Filters/TransactionsFilters/TransactionInPoolTypeFilter.tsx create mode 100644 src/components/TransactionsInPoolTable/TransactionsInPoolTable.tsx create mode 100644 src/components/TransactionsInPoolTable/components/TransactionInPoolTypeBadge.tsx create mode 100644 src/components/TransactionsInPoolTable/components/TransactionsInPoolHeader.tsx create mode 100644 src/components/TransactionsInPoolTable/components/TransactionsInPoolRow.tsx create mode 100644 src/components/TransactionsInPoolTable/components/index.ts create mode 100644 src/components/TransactionsInPoolTable/index.ts create mode 100644 src/components/TransactionsTable/components/MethodList.tsx create mode 100644 src/components/TransactionsTable/components/index.ts create mode 100644 src/hooks/urlFilters/useGetTransactionInPoolFilters.ts create mode 100644 src/pages/TransactionsInPool/TransactionsInPool.tsx create mode 100644 src/pages/TransactionsInPool/index.ts diff --git a/src/components/Filters/TransactionsFilters/TransactionInPoolTypeFilter.tsx b/src/components/Filters/TransactionsFilters/TransactionInPoolTypeFilter.tsx new file mode 100644 index 000000000..02f3e07e5 --- /dev/null +++ b/src/components/Filters/TransactionsFilters/TransactionInPoolTypeFilter.tsx @@ -0,0 +1,79 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import classNames from 'classnames'; +import { Anchor, Dropdown } from 'react-bootstrap'; +import { useSearchParams } from 'react-router-dom'; + +import { faFilter } from 'icons/regular'; +import { faFilter as faFilterSolid } from 'icons/solid'; +import { TransactionInPoolTypeEnum } from 'types'; + +export const TransactionInPoolTypeFilter = ({ + text, + hideFilters +}: { + text: React.ReactNode; + hideFilters?: boolean; +}) => { + const [searchParams, setSearchParams] = useSearchParams(); + const { type, page, size, ...rest } = Object.fromEntries(searchParams); + + const typeLink = (type: TransactionInPoolTypeEnum) => { + const nextUrlParams = { + ...rest, + ...(type ? { type } : {}) + }; + + setSearchParams(nextUrlParams); + }; + + if (hideFilters) { + return text; + } + + return ( +
+ {text} + { + return typeLink(eventKey ?? ''); + }} + > + + + + + + All + + {Object.keys(TransactionInPoolTypeEnum).map( + (transactionType, key) => { + return ( + + {transactionType} + + ); + } + )} + + +
+ ); +}; diff --git a/src/components/TransactionsInPoolTable/TransactionsInPoolTable.tsx b/src/components/TransactionsInPoolTable/TransactionsInPoolTable.tsx new file mode 100644 index 000000000..e95907c10 --- /dev/null +++ b/src/components/TransactionsInPoolTable/TransactionsInPoolTable.tsx @@ -0,0 +1,122 @@ +import { ELLIPSIS } from 'appConstants'; +import { + Pager, + PageSize, + TableWrapper, + Loader, + PageState, + PulsatingLed +} from 'components'; +import { useGetTransactionInPoolFilters } from 'hooks'; +import { faCode, faExchangeAlt } from 'icons/regular'; +import { TransactionInPoolType, TransactionFiltersEnum } from 'types'; + +import { TransactionsInPoolHeader, TransactionInPoolRow } from './components'; + +export interface TransactionsInPoolTableUIType { + transactionsInPool: TransactionInPoolType[]; + totalTransactionsInPool: number | typeof ELLIPSIS; + title?: React.ReactNode; + dataChanged?: boolean; + isDataReady?: boolean; + inactiveFilters?: TransactionFiltersEnum[]; +} + +const ColSpanWrapper = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); + +export const TransactionsInPoolTable = ({ + transactionsInPool, + totalTransactionsInPool, + title = ( +
+ Live Transactions In Pool +
+ ), + dataChanged = false, + isDataReady, + inactiveFilters +}: TransactionsInPoolTableUIType) => { + const { type } = useGetTransactionInPoolFilters(); + return ( +
+
+
+
+ {title} + 0} + className='d-flex ms-auto me-auto me-sm-0' + /> +
+
+ +
+ + + + + {isDataReady === undefined && ( + + + + )} + {isDataReady === false && ( + + + + )} + + {isDataReady === true && ( + <> + {transactionsInPool.length > 0 ? ( + <> + {transactionsInPool.map((transactionInPool) => ( + + ))} + + ) : ( + <> + + + + + )} + + )} + +
+
+
+ +
+ + 0} + /> +
+
+
+ ); +}; diff --git a/src/components/TransactionsInPoolTable/components/TransactionInPoolTypeBadge.tsx b/src/components/TransactionsInPoolTable/components/TransactionInPoolTypeBadge.tsx new file mode 100644 index 000000000..5d3d219c9 --- /dev/null +++ b/src/components/TransactionsInPoolTable/components/TransactionInPoolTypeBadge.tsx @@ -0,0 +1,82 @@ +import classNames from 'classnames'; +import { useSelector, useDispatch } from 'react-redux'; +import { useSearchParams } from 'react-router-dom'; + +import { useGetTransactionInPoolFilters } from 'hooks'; +import { interfaceSelector } from 'redux/selectors'; +import { setHighlightedText } from 'redux/slices/interface'; +import { TransactionInPoolTypeEnum } from 'types'; + +export interface TransactionInPoolTypeBadgeUIType { + type: TransactionInPoolTypeEnum; + hasHighlight?: boolean; +} + +export const TransactionInPoolTypeBadge = ({ + type, + hasHighlight +}: TransactionInPoolTypeBadgeUIType) => { + const dispatch = useDispatch(); + const { highlightedText } = useSelector(interfaceSelector); + const { type: filteredType } = useGetTransactionInPoolFilters(); + const [searchParams, setSearchParams] = useSearchParams(); + + const isHighlightBadge = hasHighlight && highlightedText === type; + + const updateType = (newType: TransactionInPoolTypeEnum) => { + const { page, size, type, ...rest } = Object.fromEntries(searchParams); + if (newType) { + delete rest.page; + } + const nextUrlParams = { + ...rest, + ...(newType ? { type: newType } : {}) + }; + + setSearchParams(nextUrlParams); + }; + + const TransactionTypeText = ({ children }: { children: React.ReactNode }) => { + return ( + + {filteredType !== type ? ( +
{ + updateType(type); + }} + data-testid='filterByTransactionInPoolType' + className='text-decoration-none cursor-pointer' + > + {children} +
+ ) : ( + {children} + )} +
+ ); + }; + + return ( +
+ + { + dispatch(setHighlightedText(type)); + }, + onMouseLeave: () => dispatch(setHighlightedText('')) + } + : {})} + > +
+ {type} +
+
+
+
+ ); +}; diff --git a/src/components/TransactionsInPoolTable/components/TransactionsInPoolHeader.tsx b/src/components/TransactionsInPoolTable/components/TransactionsInPoolHeader.tsx new file mode 100644 index 000000000..0787a0ff5 --- /dev/null +++ b/src/components/TransactionsInPoolTable/components/TransactionsInPoolHeader.tsx @@ -0,0 +1,32 @@ +import { + FromColumnFilters, + TransactionInPoolTypeFilter, + ToColumnFilters +} from 'components'; +import { TransactionFiltersEnum, WithClassnameType } from 'types'; + +export interface TransactionsInPoolHeaderUIType extends WithClassnameType { + inactiveFilters?: TransactionFiltersEnum[]; +} + +export const TransactionsInPoolHeader = ({ + inactiveFilters +}: TransactionsInPoolHeaderUIType) => { + return ( + + + Txn Hash + + From + + + To + + + + + Value + + + ); +}; diff --git a/src/components/TransactionsInPoolTable/components/TransactionsInPoolRow.tsx b/src/components/TransactionsInPoolTable/components/TransactionsInPoolRow.tsx new file mode 100644 index 000000000..939cb97a5 --- /dev/null +++ b/src/components/TransactionsInPoolTable/components/TransactionsInPoolRow.tsx @@ -0,0 +1,51 @@ +import { NetworkLink, Trim, AccountLink, FormatAmount } from 'components'; +import { urlBuilder } from 'helpers'; +import { TransactionInPoolType } from 'types'; + +import { TransactionInPoolTypeBadge } from './TransactionInPoolTypeBadge'; + +export interface TransactionInPoolRowUIType { + transaction: TransactionInPoolType; +} + +export const TransactionInPoolRow = ({ + transaction +}: TransactionInPoolRowUIType) => { + const { txHash, sender, receiver, receiverUsername, type, value } = + transaction; + + return ( + + +
+ + + +
+ + + + + + + + + + + + + + + + + ); +}; diff --git a/src/components/TransactionsInPoolTable/components/index.ts b/src/components/TransactionsInPoolTable/components/index.ts new file mode 100644 index 000000000..aa981e196 --- /dev/null +++ b/src/components/TransactionsInPoolTable/components/index.ts @@ -0,0 +1,3 @@ +export * from './TransactionInPoolTypeBadge'; +export * from './TransactionsInPoolHeader'; +export * from './TransactionsInPoolRow'; diff --git a/src/components/TransactionsInPoolTable/index.ts b/src/components/TransactionsInPoolTable/index.ts new file mode 100644 index 000000000..c831550c7 --- /dev/null +++ b/src/components/TransactionsInPoolTable/index.ts @@ -0,0 +1 @@ +export * from './TransactionsInPoolTable'; diff --git a/src/components/TransactionsTable/TransactionsTable.tsx b/src/components/TransactionsTable/TransactionsTable.tsx index faaa5925f..a0aba894d 100644 --- a/src/components/TransactionsTable/TransactionsTable.tsx +++ b/src/components/TransactionsTable/TransactionsTable.tsx @@ -4,11 +4,13 @@ import { FailedScResults } from 'components/ScResultsTable/FailedScResults'; import { NoScResults } from 'components/ScResultsTable/NoScResults'; import { TransactionTableType } from 'types'; -import { FailedTransactions } from './components/FailedTransactions'; -import { Header } from './components/Header'; -import { NoTransactions } from './components/NoTransactions'; -import { TransactionRow } from './components/TransactionRow'; -import { MethodList } from './components/TransactionsFilters'; +import { + Header, + FailedTransactions, + NoTransactions, + TransactionRow, + MethodList +} from './components'; const ColSpanWrapper = ({ children, diff --git a/src/components/TransactionsTable/components/MethodList.tsx b/src/components/TransactionsTable/components/MethodList.tsx new file mode 100644 index 000000000..96d29e359 --- /dev/null +++ b/src/components/TransactionsTable/components/MethodList.tsx @@ -0,0 +1,46 @@ +import { useSearchParams } from 'react-router-dom'; + +export const MethodList = () => { + const [searchParams, setSearchParams] = useSearchParams(); + const { function: method } = Object.fromEntries(searchParams); + + const setMethod = (method: string) => { + const { page, size, ...rest } = Object.fromEntries(searchParams); + + if (method === '' && rest?.function) { + delete rest.function; + } + const nextUrlParams = { + ...rest, + ...(method ? { function: method } : {}) + }; + + setSearchParams(nextUrlParams); + }; + + if (!method) { + return null; + } + + return ( +
+
    + {method && ( +
  • +
    + {method} +
    { + setMethod(''); + }} + className='text-green px-2 cursor-pointer' + > + × +
    +
    +
  • + )} +
+
+ ); +}; diff --git a/src/components/TransactionsTable/components/index.ts b/src/components/TransactionsTable/components/index.ts new file mode 100644 index 000000000..5c7765efd --- /dev/null +++ b/src/components/TransactionsTable/components/index.ts @@ -0,0 +1,7 @@ +export * from './Header'; +export * from './TransactionValue'; +export * from './FailedTransactions'; +export * from './MethodList'; +export * from './NoTransactions'; +export * from './TransactionMethod'; +export * from './TransactionRow'; diff --git a/src/hooks/urlFilters/index.ts b/src/hooks/urlFilters/index.ts index d9968a8fe..bc60a47ab 100644 --- a/src/hooks/urlFilters/index.ts +++ b/src/hooks/urlFilters/index.ts @@ -3,3 +3,4 @@ export * from './useGetPage'; export * from './useGetSearch'; export * from './useGetSort'; export * from './useGetTransactionFilters'; +export * from './useGetTransactionInPoolFilters'; diff --git a/src/hooks/urlFilters/useGetTransactionInPoolFilters.ts b/src/hooks/urlFilters/useGetTransactionInPoolFilters.ts new file mode 100644 index 000000000..3768b3bce --- /dev/null +++ b/src/hooks/urlFilters/useGetTransactionInPoolFilters.ts @@ -0,0 +1,26 @@ +import { useSearchParams } from 'react-router-dom'; + +import { TransactionInPoolTypeEnum } from 'types'; + +const checkType = (type: string) => + type && Object.keys(TransactionInPoolTypeEnum).includes(type) + ? (type as TransactionInPoolTypeEnum) + : undefined; + +export const useGetTransactionInPoolFilters = () => { + const [searchParams] = useSearchParams(); + + const sender = searchParams.get('sender') + ? String(searchParams.get('sender')) + : ''; + const receiver = searchParams.get('receiver') + ? String(searchParams.get('receiver')) + : ''; + const type = searchParams.get('type') ? String(searchParams.get('type')) : ''; + + return { + sender, + receiver, + type: checkType(type) + }; +}; diff --git a/src/pages/TransactionsInPool/TransactionsInPool.tsx b/src/pages/TransactionsInPool/TransactionsInPool.tsx new file mode 100644 index 000000000..d24b3efc8 --- /dev/null +++ b/src/pages/TransactionsInPool/TransactionsInPool.tsx @@ -0,0 +1,88 @@ +import { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { useSearchParams } from 'react-router-dom'; + +import { ELLIPSIS } from 'appConstants'; +import { TransactionsInPoolTable } from 'components/TransactionsInPoolTable'; +import { useAdapter, useGetPage, useGetTransactionInPoolFilters } from 'hooks'; +import { activeNetworkSelector } from 'redux/selectors'; +import { TransactionInPoolType } from 'types'; + +export const TransactionsInPool = () => { + const [searchParams] = useSearchParams(); + + const { page, size } = useGetPage(); + + const urlParams = useGetTransactionInPoolFilters(); + const { sender, receiver, type } = urlParams; + + const { firstPageRefreshTrigger } = useGetPage(); + const { id: activeNetworkId } = useSelector(activeNetworkSelector); + + const [isDataReady, setIsDataReady] = useState(); + const [dataChanged, setDataChanged] = useState(false); + const [transactionsInPool, setTransactionsInPool] = useState< + TransactionInPoolType[] + >([]); + const [totalTransactionsInPool, setTotalTransactionsInPool] = useState< + number | typeof ELLIPSIS + >(ELLIPSIS); + + const { getTransactionsInPool, getTransactionsInPoolCount } = useAdapter(); + + const fetchTransactionsInPool = (paramsChange = false) => { + if (searchParams.toString() && paramsChange) { + setDataChanged(true); + } + Promise.all([ + getTransactionsInPool({ + page, + size, + sender, + receiver, + type + }), + getTransactionsInPoolCount({ sender, receiver, type }) + ]) + .then(([transactionsInPoolData, transactionsInPoolCountData]) => { + if ( + transactionsInPoolData.success && + transactionsInPoolCountData.success + ) { + setTransactionsInPool(transactionsInPoolData.data); + setTotalTransactionsInPool(transactionsInPoolCountData.data); + } + setIsDataReady( + transactionsInPoolData.success && transactionsInPoolCountData.success + ); + }) + .finally(() => { + setDataChanged(false); + }); + }; + + useEffect(() => { + fetchTransactionsInPool(); + }, [activeNetworkId, firstPageRefreshTrigger]); + + useEffect(() => { + fetchTransactionsInPool(Boolean(searchParams.toString())); + }, [searchParams]); + + return ( +
+
+
+
+ +
+
+
+
+ ); +}; diff --git a/src/pages/TransactionsInPool/index.ts b/src/pages/TransactionsInPool/index.ts new file mode 100644 index 000000000..d23144a3e --- /dev/null +++ b/src/pages/TransactionsInPool/index.ts @@ -0,0 +1 @@ +export * from './TransactionsInPool'; From b8fdfbb2576a9609229e6846f5a6458d517699ea Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 15 May 2024 18:08:21 +0300 Subject: [PATCH 05/12] Added Transaction In Pool Details --- .../NonceMessage/NonceMessage.tsx | 15 ++- .../TransactionInPoolDetails.tsx | 103 ++++++++++++++++ .../components/TransactionInPoolInfo.tsx | 116 ++++++++++++++++++ .../components/index.ts | 1 + src/pages/TransactionInPoolDetails/index.ts | 1 + 5 files changed, 232 insertions(+), 4 deletions(-) create mode 100644 src/pages/TransactionInPoolDetails/TransactionInPoolDetails.tsx create mode 100644 src/pages/TransactionInPoolDetails/components/TransactionInPoolInfo.tsx create mode 100644 src/pages/TransactionInPoolDetails/components/index.ts create mode 100644 src/pages/TransactionInPoolDetails/index.ts diff --git a/src/pages/TransactionDetails/components/TransactionInfo/NonceMessage/NonceMessage.tsx b/src/pages/TransactionDetails/components/TransactionInfo/NonceMessage/NonceMessage.tsx index 1e6dc57b0..0e6cfd900 100644 --- a/src/pages/TransactionDetails/components/TransactionInfo/NonceMessage/NonceMessage.tsx +++ b/src/pages/TransactionDetails/components/TransactionInfo/NonceMessage/NonceMessage.tsx @@ -5,17 +5,24 @@ import { useAdapter } from 'hooks'; import { faAngleDown } from 'icons/regular'; import { TransactionType, TransactionApiStatusEnum } from 'types'; +export interface NonceMessageBaseTransactionType { + sender: TransactionType['sender']; + receiver: TransactionType['receiver']; + nonce: TransactionType['nonce']; + status?: TransactionType['status']; + pendingResults?: TransactionType['pendingResults']; +} + export const NonceMessage = ({ transaction }: { - transaction: TransactionType; + transaction: NonceMessageBaseTransactionType; }) => { const ref = useRef(null); const { getAccount } = useAdapter(); const { sender: senderAddress, nonce: transactionNonce, - timestamp, status } = transaction; const [isDataReady, setIsDataReady] = useState(false); @@ -40,13 +47,13 @@ export const NonceMessage = ({ useEffect(() => { const timer = setTimeout(() => { - if (senderAddress && isTxPending) { + if (senderAddress && (isTxPending || isTxPending === undefined)) { getSenderNonce(); } }, 1000 * 60); // 1 minute return () => clearTimeout(timer); - }, [senderAddress, timestamp, isTxPending]); + }, [senderAddress, isTxPending]); return (
diff --git a/src/pages/TransactionInPoolDetails/TransactionInPoolDetails.tsx b/src/pages/TransactionInPoolDetails/TransactionInPoolDetails.tsx new file mode 100644 index 000000000..8dfe78722 --- /dev/null +++ b/src/pages/TransactionInPoolDetails/TransactionInPoolDetails.tsx @@ -0,0 +1,103 @@ +import { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; + +import { Loader, NetworkLink, PageState } from 'components'; +import { isHash, urlBuilder } from 'helpers'; +import { useAdapter } from 'hooks'; +import { faExchangeAlt } from 'icons/regular'; +import { refreshSelector } from 'redux/selectors/refresh'; +import { TransactionType, TransactionInPoolType } from 'types'; + +import { TransactionInPoolInfo } from './components'; + +export const TransactionInPoolDetails = () => { + const params: any = useParams(); + const { hash: transactionId } = params; + const { timestamp } = useSelector(refreshSelector); + const { getTransaction, getTransactionInPool } = useAdapter(); + + const [processedTransaction, setProcessedTransaction] = useState< + TransactionType | undefined + >(); + const [transactionInPool, setTransactionInPool] = useState< + TransactionInPoolType | undefined + >(); + const [dataReady, setDataReady] = useState(); + + const fetchTransaction = async () => { + if (transactionId) { + const { data, success } = await getTransactionInPool(transactionId); + if (!success && !data && isHash(transactionId)) { + const { data: processedTxData, success: processedTxSuccess } = + await getTransaction(transactionId); + + if (processedTxData && processedTxSuccess) { + setProcessedTransaction(processedTxData); + } + } + + setTransactionInPool(data); + setDataReady(success); + } + }; + + const checkRefetch = () => { + if (transactionInPool && dataReady && !processedTransaction) { + fetchTransaction(); + } + }; + + useEffect(() => { + fetchTransaction(); + }, [transactionId]); + useEffect(checkRefetch, [timestamp]); + + if (dataReady === undefined) { + return ; + } + + if (dataReady === false || !transactionInPool) { + if (processedTransaction?.txHash) { + return ( + + + View Transaction + +
+ } + /> + ); + } + + return ( + + {transactionId} + + } + isError + /> + ); + } + + return ( +
+
+
+ +
+
+
+ ); +}; diff --git a/src/pages/TransactionInPoolDetails/components/TransactionInPoolInfo.tsx b/src/pages/TransactionInPoolDetails/components/TransactionInPoolInfo.tsx new file mode 100644 index 000000000..63bb75535 --- /dev/null +++ b/src/pages/TransactionInPoolDetails/components/TransactionInPoolInfo.tsx @@ -0,0 +1,116 @@ +import BigNumber from 'bignumber.js'; + +import { + FormatAmount, + ShardSpan, + DetailItem, + CopyButton, + LoadingDots, + AccountLink +} from 'components'; +import { addressIsBech32, isContract } from 'helpers'; +import { DataField } from 'pages/TransactionDetails/components/TransactionInfo/DataField'; +import { NonceMessage } from 'pages/TransactionDetails/components/TransactionInfo/NonceMessage'; +import { TransactionInPoolType } from 'types'; + +export const TransactionInPoolInfo = ({ + transaction +}: { + transaction: TransactionInPoolType; +}) => { + return ( +
+
+
+
+ +
+
+
+ +
+ +
+ {transaction.txHash} + +
+
+ + +
+ {addressIsBech32(transaction.sender) ? ( + <> + + + + ) : ( + + )} +
+
+ + +
+
+ {isContract(transaction.receiver) ? ( + Contract + ) : ( + '' + )} + + +
+
+
+ + + + + + +
+ {transaction.type} +
+
+ + + {transaction.gasLimit !== undefined ? ( + + {new BigNumber(transaction.gasLimit).toFormat()} + + ) : ( + N/A + )} + + + + {transaction.gasPrice !== undefined ? ( + + ) : ( + N/A + )} + + + + <> + {transaction.nonce} + + + + + +
+
+ ); +}; diff --git a/src/pages/TransactionInPoolDetails/components/index.ts b/src/pages/TransactionInPoolDetails/components/index.ts new file mode 100644 index 000000000..2d55ad4f5 --- /dev/null +++ b/src/pages/TransactionInPoolDetails/components/index.ts @@ -0,0 +1 @@ +export * from './TransactionInPoolInfo'; diff --git a/src/pages/TransactionInPoolDetails/index.ts b/src/pages/TransactionInPoolDetails/index.ts new file mode 100644 index 000000000..f8355345f --- /dev/null +++ b/src/pages/TransactionInPoolDetails/index.ts @@ -0,0 +1 @@ +export * from './TransactionInPoolDetails'; From c06237dceb44a57efcf67100a15e53b7cd8fcb4d Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 15 May 2024 18:17:43 +0300 Subject: [PATCH 06/12] check on search for transaction in pool too --- src/hooks/useSearch.ts | 92 ++++++++++++------- .../TransactionsInPool/TransactionsInPool.tsx | 3 - 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/src/hooks/useSearch.ts b/src/hooks/useSearch.ts index 18922189c..42d280f26 100644 --- a/src/hooks/useSearch.ts +++ b/src/hooks/useSearch.ts @@ -17,6 +17,7 @@ export const useSearch = (searchHash: string) => { getAccount, getBlock, getTransaction, + getTransactionInPool, getNode, getMiniBlock, getToken, @@ -115,44 +116,67 @@ export const useSearch = (searchHash: string) => { getBlock(searchHash), getScResult(searchHash), getTransaction(searchHash), + getTransactionInPool(searchHash), getMiniBlock(searchHash) - ]).then(([block, scResult, transaction, miniblock]) => { - switch (true) { - case block.success: - setSearchRoute(networkRoute(`/blocks/${searchHash}`)); - break; - case scResult.success: - setSearchRoute( - networkRoute( - `/transactions/${scResult.data.originalTxHash}#${searchHash}` - ) - ); - break; - case transaction.success: - setSearchRoute(networkRoute(`/transactions/${searchHash}`)); - break; - case miniblock.success: - setSearchRoute(networkRoute(`/miniblocks/${searchHash}`)); - break; - default: - if (isPubKeyAccount) { - getAccount({ address: bech32.encode(searchHash) }).then( - (account) => { - if (account.success) { - if (isContract(searchHash) || account.data.nonce > 0) { - const newRoute = networkRoute( - urlBuilder.accountDetails(bech32.encode(searchHash)) - ); - setSearchRoute(newRoute); + ]).then( + ([block, scResult, transaction, transactionInPool, miniblock]) => { + switch (true) { + case block.success: + setSearchRoute( + networkRoute(urlBuilder.blockDetails(searchHash)) + ); + break; + case scResult.success && scResult?.data?.originalTxHash: + setSearchRoute( + networkRoute( + urlBuilder.transactionDetails( + `${scResult.data.originalTxHash}#${searchHash}` + ) + ) + ); + break; + case transaction.success: + setSearchRoute( + networkRoute(urlBuilder.transactionDetails(searchHash)) + ); + break; + case transactionInPool.success: + setSearchRoute( + networkRoute( + urlBuilder.transactionInPoolDetails(searchHash) + ) + ); + break; + case miniblock.success: + setSearchRoute( + networkRoute(urlBuilder.miniblockDetails(searchHash)) + ); + break; + default: + if (isPubKeyAccount) { + getAccount({ address: bech32.encode(searchHash) }).then( + (account) => { + if (account.success) { + if ( + isContract(searchHash) || + account.data.nonce > 0 + ) { + const newRoute = networkRoute( + urlBuilder.accountDetails( + bech32.encode(searchHash) + ) + ); + setSearchRoute(newRoute); + } } } - } - ); - } - setSearchRoute(notFoundRoute); - break; + ); + } + setSearchRoute(notFoundRoute); + break; + } } - }); + ); break; diff --git a/src/pages/TransactionsInPool/TransactionsInPool.tsx b/src/pages/TransactionsInPool/TransactionsInPool.tsx index d24b3efc8..58e82f4f4 100644 --- a/src/pages/TransactionsInPool/TransactionsInPool.tsx +++ b/src/pages/TransactionsInPool/TransactionsInPool.tsx @@ -10,12 +10,9 @@ import { TransactionInPoolType } from 'types'; export const TransactionsInPool = () => { const [searchParams] = useSearchParams(); - const { page, size } = useGetPage(); - const urlParams = useGetTransactionInPoolFilters(); const { sender, receiver, type } = urlParams; - const { firstPageRefreshTrigger } = useGetPage(); const { id: activeNetworkId } = useSelector(activeNetworkSelector); From 97efb0db761fb6893cc2c8070fc156f98c1fdc6d Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 15 May 2024 18:37:01 +0300 Subject: [PATCH 07/12] updated styling for transactions in pool table --- .../TransactionsTable/transactionsTable.styles.scss | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/components/TransactionsTable/transactionsTable.styles.scss b/src/components/TransactionsTable/transactionsTable.styles.scss index 88ac0c81f..eb16d10a3 100644 --- a/src/components/TransactionsTable/transactionsTable.styles.scss +++ b/src/components/TransactionsTable/transactionsTable.styles.scss @@ -44,6 +44,19 @@ } } } + &.transactions-in-pool-table { + .table { + .sender, + .receiver { + min-width: 12rem; + max-width: 12rem; + } + .hash { + min-width: 18rem; + max-width: 18rem; + } + } + } } .filter-block { From 8590817cca9101509886e8b95ac3eb45e4639df8 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 15 May 2024 18:40:22 +0300 Subject: [PATCH 08/12] properly format name --- src/components/AccountName/AccountName.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AccountName/AccountName.tsx b/src/components/AccountName/AccountName.tsx index 672e68043..3548dbf51 100644 --- a/src/components/AccountName/AccountName.tsx +++ b/src/components/AccountName/AccountName.tsx @@ -71,7 +71,7 @@ export const AccountName = ({ className={className} truncate > - {displayName} + {name} ); From 53ef88453e6fd10e0b781d4c6362f20faad0f28c Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Wed, 15 May 2024 19:13:07 +0300 Subject: [PATCH 09/12] added support for sc results too --- .../TransactionInPoolDetails.tsx | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/pages/TransactionInPoolDetails/TransactionInPoolDetails.tsx b/src/pages/TransactionInPoolDetails/TransactionInPoolDetails.tsx index 8dfe78722..1fd66858f 100644 --- a/src/pages/TransactionInPoolDetails/TransactionInPoolDetails.tsx +++ b/src/pages/TransactionInPoolDetails/TransactionInPoolDetails.tsx @@ -7,7 +7,11 @@ import { isHash, urlBuilder } from 'helpers'; import { useAdapter } from 'hooks'; import { faExchangeAlt } from 'icons/regular'; import { refreshSelector } from 'redux/selectors/refresh'; -import { TransactionType, TransactionInPoolType } from 'types'; +import { + TransactionType, + TransactionSCResultType, + TransactionInPoolType +} from 'types'; import { TransactionInPoolInfo } from './components'; @@ -15,11 +19,14 @@ export const TransactionInPoolDetails = () => { const params: any = useParams(); const { hash: transactionId } = params; const { timestamp } = useSelector(refreshSelector); - const { getTransaction, getTransactionInPool } = useAdapter(); + const { getTransaction, getScResult, getTransactionInPool } = useAdapter(); const [processedTransaction, setProcessedTransaction] = useState< TransactionType | undefined >(); + const [processedScResult, setProcessedScResult] = useState< + TransactionSCResultType | undefined + >(); const [transactionInPool, setTransactionInPool] = useState< TransactionInPoolType | undefined >(); @@ -28,6 +35,7 @@ export const TransactionInPoolDetails = () => { const fetchTransaction = async () => { if (transactionId) { const { data, success } = await getTransactionInPool(transactionId); + if (!success && !data && isHash(transactionId)) { const { data: processedTxData, success: processedTxSuccess } = await getTransaction(transactionId); @@ -35,6 +43,17 @@ export const TransactionInPoolDetails = () => { if (processedTxData && processedTxSuccess) { setProcessedTransaction(processedTxData); } + + if (!processedTxData && !processedTxSuccess) { + const { + data: processedScResultData, + success: processedScResultSuccess + } = await getScResult(transactionId); + + if (processedScResultData && processedScResultSuccess) { + setProcessedScResult(processedScResultData); + } + } } setTransactionInPool(data); @@ -77,6 +96,27 @@ export const TransactionInPoolDetails = () => { ); } + if (processedScResult?.originalTxHash && processedScResult?.hash) { + return ( + + + View Transaction + + + } + /> + ); + } + return ( Date: Wed, 15 May 2024 19:16:43 +0300 Subject: [PATCH 10/12] avoid duplicate keys --- .../TransactionsInPoolTable/TransactionsInPoolTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/TransactionsInPoolTable/TransactionsInPoolTable.tsx b/src/components/TransactionsInPoolTable/TransactionsInPoolTable.tsx index e95907c10..25094097e 100644 --- a/src/components/TransactionsInPoolTable/TransactionsInPoolTable.tsx +++ b/src/components/TransactionsInPoolTable/TransactionsInPoolTable.tsx @@ -82,10 +82,10 @@ export const TransactionsInPoolTable = ({ <> {transactionsInPool.length > 0 ? ( <> - {transactionsInPool.map((transactionInPool) => ( + {transactionsInPool.map((transactionInPool, key) => ( ))} From 924db12d68e05b66ee9064d3d461d38ae0a55f31 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Fri, 17 May 2024 14:11:27 +0300 Subject: [PATCH 11/12] added defaultSize param --- src/components/PageSize/PageSize.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/PageSize/PageSize.tsx b/src/components/PageSize/PageSize.tsx index abc683b0b..aa826e530 100644 --- a/src/components/PageSize/PageSize.tsx +++ b/src/components/PageSize/PageSize.tsx @@ -9,9 +9,11 @@ import { WithClassnameType } from 'types'; export interface PageSizeUIType extends WithClassnameType { maxSize?: number; + defaultSize?: number; } export const PageSize = ({ + defaultSize = PAGE_SIZE, maxSize = MAX_RESULTS, className }: PageSizeUIType) => { @@ -20,9 +22,9 @@ export const PageSize = ({ const { page, size, ...rest } = params; const paramSize = stringIsInteger(String(size)) ? parseInt(size) : PAGE_SIZE; - const currentSize = Math.min(paramSize, maxSize); + const currentSize = Math.min(paramSize, maxSize, defaultSize); const sizeArray = [ - ...new Set([PAGE_SIZE, 10, 50, 75, 100, currentSize]) + ...new Set([PAGE_SIZE, 10, 50, 75, 100, currentSize, defaultSize]) ].sort(function (a, b) { return a - b; }); From aa2e697d8a85d506b20d0f34426ba4e2e80b5726 Mon Sep 17 00:00:00 2001 From: Radu Mojic Date: Fri, 17 May 2024 16:11:29 +0300 Subject: [PATCH 12/12] - added a warning message in case the tx is missing the guardianSignature and the account isGuarded - renamed NonceMessage to TransactionWarningMessage --- src/components/AccountName/AccountName.tsx | 4 + .../Links/AccountLink/AccountLink.tsx | 4 + src/hooks/adapter/adapter.ts | 1 + .../NonceMessage/NonceMessage.tsx | 77 ---------- .../TransactionInfo/NonceMessage/index.ts | 1 - .../TransactionInfo/TransactionInfo.tsx | 4 +- .../TransactionWarningMessage.tsx | 141 ++++++++++++++++++ .../TransactionWarningMessage/index.ts | 1 + .../TransactionInPoolDetails.tsx | 9 +- .../components/TransactionInPoolInfo.tsx | 15 +- 10 files changed, 172 insertions(+), 85 deletions(-) delete mode 100644 src/pages/TransactionDetails/components/TransactionInfo/NonceMessage/NonceMessage.tsx delete mode 100644 src/pages/TransactionDetails/components/TransactionInfo/NonceMessage/index.ts create mode 100644 src/pages/TransactionDetails/components/TransactionInfo/TransactionWarningMessage/TransactionWarningMessage.tsx create mode 100644 src/pages/TransactionDetails/components/TransactionInfo/TransactionWarningMessage/index.ts diff --git a/src/components/AccountName/AccountName.tsx b/src/components/AccountName/AccountName.tsx index 3548dbf51..015164828 100644 --- a/src/components/AccountName/AccountName.tsx +++ b/src/components/AccountName/AccountName.tsx @@ -49,6 +49,10 @@ export const AccountName = ({ const displayAssets = assets || fetchedAssets; const displayName = username || displayAssets?.name; + if (!address) { + return '-'; + } + if (displayName) { const name = formatHerotag(displayName); const description = `${name} (${address})`; diff --git a/src/components/Links/AccountLink/AccountLink.tsx b/src/components/Links/AccountLink/AccountLink.tsx index 2d795f61d..8ceb8651d 100644 --- a/src/components/Links/AccountLink/AccountLink.tsx +++ b/src/components/Links/AccountLink/AccountLink.tsx @@ -37,6 +37,10 @@ export const AccountLink = ({ const dispatch = useDispatch(); const { highlightedText } = useSelector(interfaceSelector); + if (!address) { + return '-'; + } + return (
{ ...rest }: { address: string; + fields?: string; withGuardianInfo?: boolean; }) => provider({ url: `/accounts/${address}`, params: rest }), diff --git a/src/pages/TransactionDetails/components/TransactionInfo/NonceMessage/NonceMessage.tsx b/src/pages/TransactionDetails/components/TransactionInfo/NonceMessage/NonceMessage.tsx deleted file mode 100644 index 0e6cfd900..000000000 --- a/src/pages/TransactionDetails/components/TransactionInfo/NonceMessage/NonceMessage.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; - -import { useAdapter } from 'hooks'; -import { faAngleDown } from 'icons/regular'; -import { TransactionType, TransactionApiStatusEnum } from 'types'; - -export interface NonceMessageBaseTransactionType { - sender: TransactionType['sender']; - receiver: TransactionType['receiver']; - nonce: TransactionType['nonce']; - status?: TransactionType['status']; - pendingResults?: TransactionType['pendingResults']; -} - -export const NonceMessage = ({ - transaction -}: { - transaction: NonceMessageBaseTransactionType; -}) => { - const ref = useRef(null); - const { getAccount } = useAdapter(); - const { - sender: senderAddress, - nonce: transactionNonce, - status - } = transaction; - const [isDataReady, setIsDataReady] = useState(false); - const [hasUnsyncedNonce, setHasUnsyncedNonce] = useState(false); - - const isTxPending = - (status && status.toLowerCase() === TransactionApiStatusEnum.pending) || - transaction.pendingResults; - - const getSenderNonce = () => { - getAccount({ address: senderAddress }).then((accountDetailsData) => { - if (ref.current !== null && accountDetailsData.success) { - const data = accountDetailsData.data; - const { nonce: accountNonce } = data; - setHasUnsyncedNonce(transactionNonce > accountNonce); - setIsDataReady(true); - } else { - setIsDataReady(false); - } - }); - }; - - useEffect(() => { - const timer = setTimeout(() => { - if (senderAddress && (isTxPending || isTxPending === undefined)) { - getSenderNonce(); - } - }, 1000 * 60); // 1 minute - - return () => clearTimeout(timer); - }, [senderAddress, isTxPending]); - - return ( -
- {isDataReady && hasUnsyncedNonce && ( -
- -   - - {' '} - Probable higher nonce in transaction - -
- )} -
- ); -}; diff --git a/src/pages/TransactionDetails/components/TransactionInfo/NonceMessage/index.ts b/src/pages/TransactionDetails/components/TransactionInfo/NonceMessage/index.ts deleted file mode 100644 index a16e62bbd..000000000 --- a/src/pages/TransactionDetails/components/TransactionInfo/NonceMessage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './NonceMessage'; diff --git a/src/pages/TransactionDetails/components/TransactionInfo/TransactionInfo.tsx b/src/pages/TransactionDetails/components/TransactionInfo/TransactionInfo.tsx index 37cdc60d3..6d81d616e 100644 --- a/src/pages/TransactionDetails/components/TransactionInfo/TransactionInfo.tsx +++ b/src/pages/TransactionDetails/components/TransactionInfo/TransactionInfo.tsx @@ -43,8 +43,8 @@ import { } from 'types'; import { DataField } from './DataField'; -import { NonceMessage } from './NonceMessage'; import { TransactionErrorDisplay } from './TransactionErrorDisplay'; +import { TransactionWarningMessage } from './TransactionWarningMessage'; import { EventsList } from '../EventsList'; import { OperationsList } from '../OperationsList'; import { ScResultsList } from '../ScResultsList'; @@ -449,7 +449,7 @@ export const TransactionInfo = ({ <> {transaction.nonce} - + diff --git a/src/pages/TransactionDetails/components/TransactionInfo/TransactionWarningMessage/TransactionWarningMessage.tsx b/src/pages/TransactionDetails/components/TransactionInfo/TransactionWarningMessage/TransactionWarningMessage.tsx new file mode 100644 index 000000000..1fc0c3669 --- /dev/null +++ b/src/pages/TransactionDetails/components/TransactionInfo/TransactionWarningMessage/TransactionWarningMessage.tsx @@ -0,0 +1,141 @@ +import { useEffect, useState } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import BigNumber from 'bignumber.js'; + +import { useAdapter } from 'hooks'; +import { faAngleDown } from 'icons/regular'; +import { + TransactionType, + TransactionApiStatusEnum, + TransactionInPoolTypeEnum +} from 'types'; + +export interface TransactionWarningMessageUIType { + transaction: TransactionWarningMessageBaseTransactionType; + isPoolTransaction?: boolean; +} +export interface TransactionWarningMessageBaseTransactionType { + txHash: TransactionType['txHash']; + sender: TransactionType['sender']; + receiver: TransactionType['receiver']; + nonce: TransactionType['nonce']; + status?: TransactionType['status']; + pendingResults?: TransactionType['pendingResults']; + guardianSignature?: TransactionType['guardianSignature']; + type?: TransactionInPoolTypeEnum; +} + +export const TransactionWarningMessage = ({ + transaction, + isPoolTransaction +}: TransactionWarningMessageUIType) => { + const { getAccount, getScResult, getTransaction } = useAdapter(); + const { + txHash, + sender: senderAddress, + nonce: transactionNonce, + guardianSignature, + status + } = transaction; + const [isDataReady, setIsDataReady] = useState(false); + const [hasUnsyncedNonce, setHasUnsyncedNonce] = useState(false); + const [hasMissingGuardianSignature, setHasMissingGuardianSignature] = + useState(false); + + const isTxPending = + (status && status.toLowerCase() === TransactionApiStatusEnum.pending) || + transaction.pendingResults; + + const getAccountDetails = async () => { + const { data, success } = await getAccount({ + address: senderAddress, + withGuardianInfo: true, + fields: ['nonce', 'isGuarded'].join(',') + }); + if (data && success) { + const { nonce: accountNonce, isGuarded } = data; + if (new BigNumber(transactionNonce).isGreaterThan(accountNonce)) { + setHasUnsyncedNonce(true); + } + if (isGuarded && !guardianSignature) { + if (isPoolTransaction) { + let apiTxHash = txHash; + if ( + transaction.type === TransactionInPoolTypeEnum.SmartContractResult + ) { + const { + data: processedScResultData, + success: processedScResultSuccess + } = await getScResult(txHash); + if ( + processedScResultSuccess && + processedScResultData?.originalTxHash + ) { + apiTxHash = processedScResultData.originalTxHash; + } + } + + const { data: processedTxData, success: processedTxSuccess } = + await getTransaction(apiTxHash); + + if (processedTxData && processedTxSuccess) { + setHasMissingGuardianSignature(!processedTxData?.guardianSignature); + } + } else { + setHasMissingGuardianSignature(true); + } + } + } + + setIsDataReady(success); + }; + + useEffect(() => { + const timer = setTimeout(async () => { + if (senderAddress && (isTxPending || isTxPending === undefined)) { + await getAccountDetails(); + } + }, 1000 * 30); // 30 seconds + + return () => clearTimeout(timer); + }, [senderAddress, isTxPending]); + + if (!isDataReady) { + return null; + } + + return ( +
+ {hasUnsyncedNonce && ( +
+ +   + + {' '} + Probable Higher nonce in Transaction + +
+ )} + {hasMissingGuardianSignature && ( +
+ +   + + {' '} + Probable Missing Guardian Signature + +
+ )} +
+ ); +}; diff --git a/src/pages/TransactionDetails/components/TransactionInfo/TransactionWarningMessage/index.ts b/src/pages/TransactionDetails/components/TransactionInfo/TransactionWarningMessage/index.ts new file mode 100644 index 000000000..25eff56a9 --- /dev/null +++ b/src/pages/TransactionDetails/components/TransactionInfo/TransactionWarningMessage/index.ts @@ -0,0 +1 @@ +export * from './TransactionWarningMessage'; diff --git a/src/pages/TransactionInPoolDetails/TransactionInPoolDetails.tsx b/src/pages/TransactionInPoolDetails/TransactionInPoolDetails.tsx index 1fd66858f..109cdce33 100644 --- a/src/pages/TransactionInPoolDetails/TransactionInPoolDetails.tsx +++ b/src/pages/TransactionInPoolDetails/TransactionInPoolDetails.tsx @@ -10,7 +10,8 @@ import { refreshSelector } from 'redux/selectors/refresh'; import { TransactionType, TransactionSCResultType, - TransactionInPoolType + TransactionInPoolType, + TransactionApiStatusEnum } from 'types'; import { TransactionInPoolInfo } from './components'; @@ -40,7 +41,11 @@ export const TransactionInPoolDetails = () => { const { data: processedTxData, success: processedTxSuccess } = await getTransaction(transactionId); - if (processedTxData && processedTxSuccess) { + if ( + processedTxData && + processedTxSuccess && + processedTxData?.status !== TransactionApiStatusEnum.pending + ) { setProcessedTransaction(processedTxData); } diff --git a/src/pages/TransactionInPoolDetails/components/TransactionInPoolInfo.tsx b/src/pages/TransactionInPoolDetails/components/TransactionInPoolInfo.tsx index 63bb75535..86a1ad0e4 100644 --- a/src/pages/TransactionInPoolDetails/components/TransactionInPoolInfo.tsx +++ b/src/pages/TransactionInPoolDetails/components/TransactionInPoolInfo.tsx @@ -10,7 +10,7 @@ import { } from 'components'; import { addressIsBech32, isContract } from 'helpers'; import { DataField } from 'pages/TransactionDetails/components/TransactionInfo/DataField'; -import { NonceMessage } from 'pages/TransactionDetails/components/TransactionInfo/NonceMessage'; +import { TransactionWarningMessage } from 'pages/TransactionDetails/components/TransactionInfo/TransactionWarningMessage'; import { TransactionInPoolType } from 'types'; export const TransactionInPoolInfo = ({ @@ -44,7 +44,13 @@ export const TransactionInPoolInfo = ({ ) : ( - + <> + {transaction.sender ? ( + + ) : ( + '-' + )} + )}
@@ -105,7 +111,10 @@ export const TransactionInPoolInfo = ({ <> {transaction.nonce} - +