diff --git a/lib/errors/getErrorCause.tsx b/lib/errors/getErrorCause.tsx new file mode 100644 index 0000000000..5b553a7018 --- /dev/null +++ b/lib/errors/getErrorCause.tsx @@ -0,0 +1,8 @@ +export default function getErrorCause(error: Error | undefined): Record | undefined { + return ( + error && 'cause' in error && + typeof error.cause === 'object' && error.cause !== null && + error.cause as Record + ) || + undefined; +} diff --git a/lib/errors/getErrorStatusCode.tsx b/lib/errors/getErrorStatusCode.tsx index 95439ced30..d662ac6e65 100644 --- a/lib/errors/getErrorStatusCode.tsx +++ b/lib/errors/getErrorStatusCode.tsx @@ -1,9 +1,6 @@ +import getErrorCause from './getErrorCause'; + export default function getErrorStatusCode(error: Error | undefined): number | undefined { - return ( - error && 'cause' in error && - typeof error.cause === 'object' && error.cause !== null && - 'status' in error.cause && typeof error.cause.status === 'number' && - error.cause.status - ) || - undefined; + const cause = getErrorCause(error); + return cause && 'status' in cause && typeof cause.status === 'number' ? cause.status : undefined; } diff --git a/lib/errors/getResourceErrorPayload.tsx b/lib/errors/getResourceErrorPayload.tsx new file mode 100644 index 0000000000..7953ae5c5b --- /dev/null +++ b/lib/errors/getResourceErrorPayload.tsx @@ -0,0 +1,8 @@ +import type { ResourceError } from 'lib/api/resources'; + +import getErrorCause from './getErrorCause'; + +export default function getResourceErrorPayload>(error: Error | undefined): ResourceError['payload'] | undefined { + const cause = getErrorCause(error); + return cause && 'payload' in cause ? cause.payload as ResourceError['payload'] : undefined; +} diff --git a/pages/block/[height].tsx b/pages/block/[height].tsx index 9933276373..8926a0f3ae 100644 --- a/pages/block/[height].tsx +++ b/pages/block/[height].tsx @@ -5,6 +5,7 @@ import React from 'react'; import getSeo from 'lib/next/block/getSeo'; import Block from 'ui/pages/Block'; +import Page from 'ui/shared/Page/Page'; const BlockPage: NextPage> = ({ height }: RoutedQuery<'/block/[height]'>) => { const { title, description } = getSeo({ height }); @@ -14,7 +15,9 @@ const BlockPage: NextPage> = ({ height }: RoutedQ { title } - + + + ); }; diff --git a/ui/pages/Block.tsx b/ui/pages/Block.tsx index 53b43aa754..0a1d57c879 100644 --- a/ui/pages/Block.tsx +++ b/ui/pages/Block.tsx @@ -11,7 +11,6 @@ import useQueryWithPages from 'lib/hooks/useQueryWithPages'; import getQueryParamString from 'lib/router/getQueryParamString'; import BlockDetails from 'ui/block/BlockDetails'; import TextAd from 'ui/shared/ad/TextAd'; -import Page from 'ui/shared/Page/Page'; import PageTitle from 'ui/shared/Page/PageTitle'; import Pagination from 'ui/shared/Pagination'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; @@ -39,7 +38,7 @@ const BlockPageContent = () => { resourceName: 'block_txs', pathParams: { height }, options: { - enabled: Boolean(height && tab === 'txs'), + enabled: Boolean(blockQuery.data?.height && tab === 'txs'), }, }); @@ -47,6 +46,10 @@ const BlockPageContent = () => { throw new Error('Block not found', { cause: { status: 404 } }); } + if (blockQuery.isError) { + throw new Error(undefined, { cause: blockQuery.error }); + } + const tabs: Array = React.useMemo(() => ([ { id: 'index', title: 'Details', component: }, { id: 'txs', title: 'Transactions', component: }, @@ -57,7 +60,7 @@ const BlockPageContent = () => { const hasGoBackLink = appProps.referrer && appProps.referrer.includes('/blocks'); return ( - + <> { blockQuery.isLoading ? : } { blockQuery.isLoading ? ( @@ -74,7 +77,7 @@ const BlockPageContent = () => { rightSlot={ hasPagination ? : null } stickyEnabled={ hasPagination } /> - + ); }; diff --git a/ui/shared/AppError/AppErrorBlockConsensus.tsx b/ui/shared/AppError/AppErrorBlockConsensus.tsx new file mode 100644 index 0000000000..1aeb4c954a --- /dev/null +++ b/ui/shared/AppError/AppErrorBlockConsensus.tsx @@ -0,0 +1,30 @@ +import { Box, Button, Heading, Icon, chakra } from '@chakra-ui/react'; +import { route } from 'nextjs-routes'; +import React from 'react'; + +import icon404 from 'icons/error-pages/404.svg'; + +interface Props { + hash?: string; + className?: string; +} + +const AppErrorBlockConsensus = ({ hash, className }: Props) => { + return ( + + + Block has lost consensus + + + ); +}; + +export default chakra(AppErrorBlockConsensus); diff --git a/ui/shared/Page/Page.tsx b/ui/shared/Page/Page.tsx index 6ec1be150b..4d82f380b3 100644 --- a/ui/shared/Page/Page.tsx +++ b/ui/shared/Page/Page.tsx @@ -2,9 +2,11 @@ import { Flex } from '@chakra-ui/react'; import React from 'react'; import getErrorStatusCode from 'lib/errors/getErrorStatusCode'; +import getResourceErrorPayload from 'lib/errors/getResourceErrorPayload'; import useAdblockDetect from 'lib/hooks/useAdblockDetect'; import useGetCsrfToken from 'lib/hooks/useGetCsrfToken'; import AppError from 'ui/shared/AppError/AppError'; +import AppErrorBlockConsensus from 'ui/shared/AppError/AppErrorBlockConsensus'; import ErrorBoundary from 'ui/shared/ErrorBoundary'; import ErrorInvalidTxHash from 'ui/shared/ErrorInvalidTxHash'; import PageContent from 'ui/shared/Page/PageContent'; @@ -31,15 +33,27 @@ const Page = ({ const renderErrorScreen = React.useCallback((error?: Error) => { const statusCode = getErrorStatusCode(error) || 500; + const resourceErrorPayload = getResourceErrorPayload(error); + const messageInPayload = resourceErrorPayload && 'message' in resourceErrorPayload && typeof resourceErrorPayload.message === 'string' ? + resourceErrorPayload.message : + undefined; + const isInvalidTxHash = error?.message.includes('Invalid tx hash'); + const isBlockConsensus = messageInPayload?.includes('Block lost consensus'); + + if (isInvalidTxHash) { + return ; + } - if (wrapChildren) { - const content = isInvalidTxHash ? : ; - return { content }; + if (isBlockConsensus) { + const hash = resourceErrorPayload && 'hash' in resourceErrorPayload && typeof resourceErrorPayload.hash === 'string' ? + resourceErrorPayload.hash : + undefined; + return ; } - return isInvalidTxHash ? : ; - }, [ isHomePage, wrapChildren ]); + return ; + }, [ isHomePage ]); const renderedChildren = wrapChildren ? ( { children }