From deede8c2a243be1d676a45ae99e5c0239eb06ecc Mon Sep 17 00:00:00 2001 From: Alberto Gualis Date: Fri, 13 Sep 2024 16:41:29 +0200 Subject: [PATCH] fix: handle backend errors in pool page (#1031) * fix: pool page error when pool id is not found * fix: avoid alert var * fix: capture pool query backend errors in sentry * Update app/(app)/pools/[chain]/[variant]/[id]/layout.tsx Co-authored-by: Gareth Fuller * Update app/(app)/pools/[chain]/[variant]/[id]/layout.tsx Co-authored-by: Gareth Fuller * chore: Use error boundaries * chore: Minor updates * chore: Remove * chore: Handle errors better * chore: Update container height * fix: Await header --------- Co-authored-by: Gareth Fuller --- .../pools/[chain]/[variant]/[id]/layout.tsx | 41 ++++++++++++------- app/not-found.tsx | 37 +++++++++++++++++ lib/shared/components/alerts/BalAlert.tsx | 13 +++++- .../components/errors/ErrorBoundary.tsx | 26 ++++++++---- lib/shared/utils/errors.ts | 2 +- lib/shared/utils/query-errors.ts | 2 +- sentry.server.config.ts | 2 + 7 files changed, 95 insertions(+), 28 deletions(-) create mode 100644 app/not-found.tsx diff --git a/app/(app)/pools/[chain]/[variant]/[id]/layout.tsx b/app/(app)/pools/[chain]/[variant]/[id]/layout.tsx index 6966a3e6e..ed5cc0d20 100644 --- a/app/(app)/pools/[chain]/[variant]/[id]/layout.tsx +++ b/app/(app)/pools/[chain]/[variant]/[id]/layout.tsx @@ -6,10 +6,11 @@ import { PoolDetailSkeleton } from '@/lib/modules/pool/PoolDetail/PoolDetailSkel import { getApolloServerClient } from '@/lib/shared/services/api/apollo-server.client' import { GetPoolDocument } from '@/lib/shared/services/api/generated/graphql' import { Metadata } from 'next' -import { Box } from '@chakra-ui/react' import { PoolProvider } from '@/lib/modules/pool/PoolProvider' import { getProjectConfig } from '@/lib/config/getProjectConfig' import { arrayToSentence } from '@/lib/shared/utils/strings' +import { ensureError } from '@/lib/shared/utils/errors' +import { notFound } from 'next/navigation' type Props = PropsWithChildren<{ params: Omit & { chain: ChainSlug } @@ -21,24 +22,28 @@ async function getPoolQuery(chain: ChainSlug, id: string) { const _chain = slugToChainMap[chain] const variables = { id: id.toLowerCase(), chain: _chain } - return await getApolloServerClient().query({ - query: GetPoolDocument, - variables, - context: { - fetchOptions: { - next: { revalidate: 30 }, + try { + const result = await getApolloServerClient().query({ + query: GetPoolDocument, + variables, + context: { + fetchOptions: { + next: { revalidate: 30 }, + }, }, - }, - }) + }) + return { data: result.data, error: null } + } catch (error: unknown) { + return { data: null, error: ensureError(error) } + } } export async function generateMetadata({ params: { id, chain, variant }, }: Props): Promise { - const { - data: { pool }, - } = await getPoolQuery(chain, id) + const { data } = await getPoolQuery(chain, id) + const pool = data?.pool if (!pool) return {} const poolTokenString = arrayToSentence(pool.displayTokens.map(token => token.symbol)) @@ -54,10 +59,16 @@ export async function generateMetadata({ export default async function PoolLayout({ params: { id, chain, variant }, children }: Props) { const _chain = slugToChainMap[chain] - const { data } = await getPoolQuery(chain, id) + const { data, error } = await getPoolQuery(chain, id) + + if (error) { + if (error?.message === 'Pool with id does not exist') { + notFound() + } - if (!data.pool) { - return Pool with id not found ({id}) + throw new Error('Failed to fetch pool') + } else if (!data) { + throw new Error('Failed to fetch pool') } return ( diff --git a/app/not-found.tsx b/app/not-found.tsx new file mode 100644 index 000000000..eb5386cbe --- /dev/null +++ b/app/not-found.tsx @@ -0,0 +1,37 @@ +import { DefaultPageContainer } from '@/lib/shared/components/containers/DefaultPageContainer' +import { Button, Heading, VStack, Text } from '@chakra-ui/react' +import { headers } from 'next/headers' +import Link from 'next/link' + +export default async function NotFound() { + const headersList = headers() + + const referer = await headersList.get('referer') + + const poolIdSegment = 6 + const maybePoolId = referer?.split('/')[poolIdSegment] + const isPoolPageNotFound = maybePoolId?.startsWith('0x') + + const title = isPoolPageNotFound ? 'Pool Not Found' : 'Page Not Found' + const description = isPoolPageNotFound + ? `The pool you are looking for does not exist: ${maybePoolId}` + : 'The page you are looking for does not exist' + + const redirectUrl = isPoolPageNotFound ? `/pools` : '/' + const redirectText = isPoolPageNotFound ? 'View All Pools' : 'Return Home' + + return ( + + + {title} + + {description} + + + + + + ) +} diff --git a/lib/shared/components/alerts/BalAlert.tsx b/lib/shared/components/alerts/BalAlert.tsx index 407a83a36..5d9c347e3 100644 --- a/lib/shared/components/alerts/BalAlert.tsx +++ b/lib/shared/components/alerts/BalAlert.tsx @@ -10,6 +10,7 @@ export type BalAlertProps = { isSoftWarning?: boolean isNavAlert?: boolean onClose?: MouseEventHandler + ssr?: boolean } export function BalAlert({ @@ -18,13 +19,21 @@ export function BalAlert({ learnMoreLink, isSoftWarning = false, isNavAlert = false, + ssr = false, // Use true whe rendering alerts on the server side onClose, }: BalAlertProps) { return ( - + {ssr ? : } - + {content} {learnMoreLink && More} diff --git a/lib/shared/components/errors/ErrorBoundary.tsx b/lib/shared/components/errors/ErrorBoundary.tsx index 164476160..db4a8d6eb 100644 --- a/lib/shared/components/errors/ErrorBoundary.tsx +++ b/lib/shared/components/errors/ErrorBoundary.tsx @@ -4,14 +4,19 @@ import { FallbackProps } from 'react-error-boundary' import { Button, Box, Text, Heading, VStack } from '@chakra-ui/react' import { ensureError } from '../../utils/errors' import { DefaultPageContainer } from '../containers/DefaultPageContainer' +import { captureSentryError } from '../../utils/query-errors' + +type ErrorWithDigest = Error & { + digest?: string +} + +interface BoundaryErrorProps extends FallbackProps { + error: ErrorWithDigest +} + +export function BoundaryError({ error, resetErrorBoundary }: BoundaryErrorProps) { + captureSentryError(error, { errorMessage: error.message }) -export function BoundaryError({ - error, - resetErrorBoundary, -}: { - error: Error & { digest?: string } - resetErrorBoundary: () => void -}) { const _error = ensureError(error) return ( @@ -25,6 +30,9 @@ export function BoundaryError({ : _error?.shortMessage || ''} {_error?.message} + + Error Digest: {_error?.digest} (this can be passed on to support to help with debugging) +