diff --git a/explorer/src/pages/TransactionDetailsPage.tsx b/explorer/src/pages/TransactionDetailsPage.tsx index 8079c13f13bb1a..b1482adab8952e 100644 --- a/explorer/src/pages/TransactionDetailsPage.tsx +++ b/explorer/src/pages/TransactionDetailsPage.tsx @@ -29,8 +29,18 @@ import { intoTransactionInstruction } from "utils/tx"; import { TokenDetailsCard } from "components/instruction/token/TokenDetailsCard"; import { FetchStatus } from "providers/cache"; -type Props = { signature: TransactionSignature }; -export function TransactionDetailsPage({ signature: raw }: Props) { +const AUTO_REFRESH_TIMEOUT = 2000; +const ZERO_CONFIRMATION_BAILOUT = 5; + +type SignatureProps = { + signature: TransactionSignature; +}; + +type AutoRefreshProps = { + autoRefreshInProcess: boolean; +}; + +export function TransactionDetailsPage({ signature: raw }: SignatureProps) { let signature: TransactionSignature | undefined; try { @@ -40,6 +50,20 @@ export function TransactionDetailsPage({ signature: raw }: Props) { } } catch (err) {} + const status = useTransactionStatus(signature); + + let autoRefreshInProcess = false; + + if ( + status?.data?.info && + status.data.info.confirmations === 0 && + status.attempts > ZERO_CONFIRMATION_BAILOUT + ) { + autoRefreshInProcess = false; + } else if (status?.data?.info && status.data.info.confirmations !== "max") { + autoRefreshInProcess = true; + } + return (
@@ -52,8 +76,14 @@ export function TransactionDetailsPage({ signature: raw }: Props) { ) : ( <> - - + + )} @@ -61,19 +91,14 @@ export function TransactionDetailsPage({ signature: raw }: Props) { ); } -function StatusCard({ signature }: Props) { +function StatusCard({ + signature, + autoRefreshInProcess, +}: SignatureProps & AutoRefreshProps) { const fetchStatus = useFetchTransactionStatus(); const status = useTransactionStatus(signature); - const fetchDetails = useFetchTransactionDetails(); const details = useTransactionDetails(signature); const { firstAvailableBlock, status: clusterStatus } = useCluster(); - const refresh = React.useCallback( - (signature: string) => { - fetchStatus(signature); - fetchDetails(signature); - }, - [fetchStatus, fetchDetails] - ); // Fetch transaction on load React.useEffect(() => { @@ -82,7 +107,24 @@ function StatusCard({ signature }: Props) { } }, [signature, clusterStatus]); // eslint-disable-line react-hooks/exhaustive-deps - if (!status || status.status === FetchStatus.Fetching) { + // Effect to set and clear interval for auto-refresh + React.useEffect(() => { + if (autoRefreshInProcess) { + let intervalHandle: NodeJS.Timeout = setInterval( + () => fetchStatus(signature), + AUTO_REFRESH_TIMEOUT + ); + + return () => { + clearInterval(intervalHandle); + }; + } + }, [autoRefreshInProcess, fetchStatus, signature]); + + if ( + !status || + (status.status === FetchStatus.Fetching && !autoRefreshInProcess) + ) { return ; } else if (status.status === FetchStatus.FetchFailed) { return ( @@ -102,6 +144,7 @@ function StatusCard({ signature }: Props) { } const { info } = status.data; + const renderResult = () => { let statusClass = "success"; let statusText = "Success"; @@ -134,13 +177,17 @@ function StatusCard({ signature }: Props) {

Overview

- + {autoRefreshInProcess ? ( + + ) : ( + + )}
@@ -211,12 +258,13 @@ function StatusCard({ signature }: Props) { ); } -function AccountsCard({ signature }: Props) { +function AccountsCard({ + signature, + autoRefreshInProcess, +}: SignatureProps & AutoRefreshProps) { const { url } = useCluster(); const details = useTransactionDetails(signature); - const fetchStatus = useFetchTransactionStatus(); const fetchDetails = useFetchTransactionDetails(); - const refreshStatus = () => fetchStatus(signature); const refreshDetails = () => fetchDetails(signature); const transaction = details?.data?.transaction?.transaction; const message = transaction?.message; @@ -231,14 +279,11 @@ function AccountsCard({ signature }: Props) { if (!status?.data?.info) { return null; - } else if (!details) { - return ( - - ); - } else if (details.status === FetchStatus.Fetching) { + } else if ( + !details || + details.status === FetchStatus.Fetching || + autoRefreshInProcess + ) { return ; } else if (details.status === FetchStatus.FetchFailed) { return ; @@ -317,7 +362,7 @@ function AccountsCard({ signature }: Props) { ); } -function InstructionsSection({ signature }: Props) { +function InstructionsSection({ signature }: SignatureProps) { const status = useTransactionStatus(signature); const details = useTransactionDetails(signature); const fetchDetails = useFetchTransactionDetails(); diff --git a/explorer/src/providers/cache.tsx b/explorer/src/providers/cache.tsx index 27484166ce211e..b73834b2e53c4e 100644 --- a/explorer/src/providers/cache.tsx +++ b/explorer/src/providers/cache.tsx @@ -9,6 +9,7 @@ export enum FetchStatus { export type CacheEntry = { status: FetchStatus; data?: T; + attempts: number; }; export type State = { @@ -94,6 +95,15 @@ export function reducer( case ActionType.Update: { const key = action.key; const entry = state.entries[key]; + + if (entry && entry.attempts === undefined) { + entry.attempts = 0; + } + + if (action.status === FetchStatus.Fetched) { + entry.attempts++; + } + const entries = { ...state.entries, [key]: { diff --git a/explorer/src/providers/transactions/index.tsx b/explorer/src/providers/transactions/index.tsx index 7782e2772bfe20..d4d66f3e94dc98 100644 --- a/explorer/src/providers/transactions/index.tsx +++ b/explorer/src/providers/transactions/index.tsx @@ -131,7 +131,7 @@ export function useTransactions() { } export function useTransactionStatus( - signature: TransactionSignature + signature: TransactionSignature | undefined ): Cache.CacheEntry | undefined { const context = React.useContext(StateContext); @@ -141,6 +141,10 @@ export function useTransactionStatus( ); } + if (signature === undefined) { + return undefined; + } + return context.entries[signature]; }