Commit 094530b0 authored by tom's avatar tom

404 and 500 error handling components

parent 5f30f3f1
<svg viewBox="0 0 201 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M126.092 85.414c1.267-2.895 1.9-5.993 1.9-9.295V23.881c0-3.302-.633-6.4-1.9-9.295-1.221-2.895-2.917-5.427-5.088-7.598-2.171-2.171-4.704-3.867-7.598-5.088-2.895-1.267-5.993-1.9-9.295-1.9h-4.138C96.67 0 93.55.633 90.61 1.9c-2.895 1.22-5.428 2.917-7.599 5.088-2.17 2.17-3.89 4.703-5.156 7.598-1.221 2.895-1.832 5.993-1.832 9.295v52.238c0 3.302.611 6.4 1.832 9.295 1.267 2.894 2.985 5.427 5.156 7.598 2.171 2.171 4.704 3.89 7.599 5.156C93.55 99.39 96.67 100 99.973 100h4.138c3.302 0 6.4-.61 9.295-1.832 2.894-1.266 5.427-2.985 7.598-5.156 2.171-2.17 3.867-4.704 5.088-7.598Zm-8.073-67.571c.814 1.854 1.221 3.867 1.221 6.038v52.238c0 2.171-.407 4.207-1.221 6.106a16.717 16.717 0 0 1-3.324 4.953c-1.402 1.402-3.053 2.51-4.953 3.324-1.854.814-3.867 1.221-6.038 1.221h-3.324c-2.171 0-4.206-.407-6.106-1.221a16.718 16.718 0 0 1-4.952-3.324 16.718 16.718 0 0 1-3.325-4.953c-.814-1.9-1.22-3.935-1.22-6.106V23.881c0-2.171.406-4.184 1.22-6.038.814-1.9 1.922-3.55 3.325-4.953a16.718 16.718 0 0 1 4.952-3.324c1.9-.814 3.935-1.221 6.106-1.221h3.324c2.171 0 4.184.407 6.038 1.22 1.9.815 3.551 1.923 4.953 3.325 1.402 1.402 2.51 3.053 3.324 4.953Zm-70.19 63.975v16.825a8.752 8.752 0 0 1-8.752-8.752v-8.073H3.275a3.275 3.275 0 0 1-2.85-4.89L42.38 2.836a2.914 2.914 0 0 1 5.45 1.436v69.203h8.345a8.345 8.345 0 0 1-8.345 8.344Zm-8.752-56.513L12.28 73.474h26.797V25.305Zm152.711 56.513v16.825a8.752 8.752 0 0 1-8.752-8.752v-8.073h-35.802a3.275 3.275 0 0 1-2.85-4.89l41.954-74.093a2.914 2.914 0 0 1 5.45 1.436v69.203h8.345a8.345 8.345 0 0 1-8.345 8.344Zm-8.752-56.513-26.798 48.169h26.798V25.305Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 197 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M124.363 76.7c0 3.326-.638 6.448-1.914 9.364-1.23 2.917-2.939 5.47-5.127 7.657-2.187 2.187-4.739 3.919-7.656 5.195-2.917 1.23-6.038 1.846-9.365 1.846h-4.17c-3.327 0-6.472-.616-9.434-1.846-2.916-1.276-5.469-3.008-7.656-5.195-2.188-2.188-3.92-4.74-5.195-7.657C72.616 83.148 72 80.026 72 76.7V24.063c0-3.327.615-6.45 1.846-9.366 1.276-2.916 3.007-5.468 5.195-7.656 2.188-2.187 4.74-3.896 7.656-5.127C89.66.638 92.804 0 96.131 0h4.17c3.327 0 6.448.638 9.365 1.914 2.917 1.23 5.469 2.94 7.656 5.127 2.188 2.188 3.897 4.74 5.127 7.656 1.276 2.917 1.914 6.039 1.914 9.366v52.636Zm-8.818-52.638c0-2.187-.41-4.215-1.231-6.084-.82-1.914-1.936-3.577-3.349-4.99-1.413-1.412-3.076-2.529-4.99-3.35-1.869-.82-3.897-1.23-6.084-1.23h-3.35c-2.188 0-4.238.41-6.152 1.23a16.848 16.848 0 0 0-4.99 3.35c-1.413 1.413-2.53 3.076-3.35 4.99-.82 1.869-1.23 3.897-1.23 6.084V76.7c0 2.188.41 4.239 1.23 6.153a16.847 16.847 0 0 0 3.35 4.99 16.847 16.847 0 0 0 4.99 3.35c1.914.82 3.965 1.23 6.152 1.23h3.35c2.187 0 4.215-.41 6.084-1.23 1.914-.82 3.577-1.937 4.99-3.35a16.834 16.834 0 0 0 3.349-4.99c.821-1.915 1.231-3.965 1.231-6.153V24.063ZM52.363 76.7c0 3.327-.638 6.449-1.914 9.365-1.23 2.917-2.94 5.47-5.127 7.657-2.187 2.187-4.74 3.919-7.656 5.195-2.917 1.23-6.038 1.846-9.365 1.846h-4.17c-3.327 0-6.471-.616-9.434-1.846-2.916-1.276-5.468-3.008-7.656-5.195-2.187-2.188-3.92-4.74-5.195-7.657C.616 83.148 0 80.026 0 76.7c0-.83.673-1.504 1.504-1.504h5.81c.831 0 1.504.674 1.504 1.504 0 2.188.41 4.239 1.23 6.153a16.847 16.847 0 0 0 3.35 4.99 16.847 16.847 0 0 0 4.99 3.35c1.915.82 3.965 1.23 6.153 1.23h3.35c2.187 0 4.215-.41 6.084-1.23 1.914-.82 3.577-1.937 4.99-3.35a16.847 16.847 0 0 0 3.35-4.99c.82-1.915 1.23-3.965 1.23-6.153V57.695c0-2.187-.41-4.215-1.23-6.084-.82-1.914-1.937-3.577-3.35-4.99a15.212 15.212 0 0 0-4.99-3.418c-1.869-.82-3.897-1.23-6.084-1.23h-3.35c-2.188 0-4.238.41-6.152 1.23a16.228 16.228 0 0 0-4.99 3.418c-1.413 1.413-2.53 3.076-3.35 4.99a14.485 14.485 0 0 0-1.112 4.09c-.134 1.096-1.014 1.994-2.119 1.994h-4.61a2 2 0 0 1-1.99-2.198l4.85-48.724a6 6 0 0 1 5.97-5.406H47.51a8.408 8.408 0 0 1-8.408 8.408H13.877l-3.213 30.147c2.096-2.005 4.535-3.555 7.315-4.649 2.78-1.139 5.765-1.709 8.955-1.709H28.3c3.327 0 6.448.639 9.365 1.914 2.917 1.231 5.469 2.94 7.656 5.127 2.188 2.188 3.897 4.763 5.127 7.725 1.276 2.917 1.914 6.038 1.914 9.365V76.7Zm142.086 9.365c1.276-2.916 1.914-6.038 1.914-9.365V24.063c0-3.327-.638-6.45-1.914-9.366-1.23-2.916-2.939-5.468-5.127-7.656-2.187-2.187-4.739-3.896-7.656-5.127C178.749.638 175.628 0 172.301 0h-4.17c-3.327 0-6.471.638-9.434 1.914-2.916 1.23-5.468 2.94-7.656 5.127-2.187 2.188-3.919 4.74-5.195 7.656-1.231 2.917-1.846 6.039-1.846 9.366v52.636c0 3.327.615 6.449 1.846 9.365 1.276 2.917 3.008 5.47 5.195 7.657 2.188 2.187 4.74 3.919 7.656 5.195 2.963 1.23 6.107 1.846 9.434 1.846h4.17c3.327 0 6.448-.616 9.365-1.846 2.917-1.276 5.469-3.008 7.656-5.195 2.188-2.188 3.897-4.74 5.127-7.657Zm-8.135-68.085c.821 1.868 1.231 3.896 1.231 6.084V76.7c0 2.188-.41 4.239-1.231 6.153a16.834 16.834 0 0 1-3.349 4.99c-1.413 1.413-3.076 2.53-4.99 3.35-1.869.82-3.897 1.23-6.084 1.23h-3.35c-2.187 0-4.238-.41-6.152-1.23a16.851 16.851 0 0 1-4.991-3.35 16.853 16.853 0 0 1-3.349-4.99c-.82-1.915-1.231-3.965-1.231-6.153V24.063c0-2.188.411-4.216 1.231-6.084.82-1.915 1.937-3.578 3.349-4.99a16.852 16.852 0 0 1 4.991-3.35c1.914-.82 3.965-1.23 6.152-1.23h3.35c2.187 0 4.215.41 6.084 1.23 1.914.82 3.577 1.937 4.99 3.35 1.413 1.412 2.529 3.075 3.349 4.99Z" fill="currentColor"/>
</svg>
...@@ -9,7 +9,7 @@ type Props = { ...@@ -9,7 +9,7 @@ type Props = {
const AppContext = createContext<PageProps>({ cookies: '', referrer: '' }); const AppContext = createContext<PageProps>({ cookies: '', referrer: '' });
export function AppWrapper({ children, pageProps }: Props) { export function AppContextProvider({ children, pageProps }: Props) {
return ( return (
<AppContext.Provider value={ pageProps }> <AppContext.Provider value={ pageProps }>
{ children } { children }
......
...@@ -7,6 +7,7 @@ export enum NAMES { ...@@ -7,6 +7,7 @@ export enum NAMES {
NAV_BAR_COLLAPSED='nav_bar_collapsed', NAV_BAR_COLLAPSED='nav_bar_collapsed',
API_TOKEN='_explorer_key', API_TOKEN='_explorer_key',
TXS_SORT='txs_sort', TXS_SORT='txs_sort',
COLOR_MODE='chakra-ui-color-mode',
} }
export function get(name?: NAMES | undefined | null, serverCookie?: string) { export function get(name?: NAMES | undefined | null, serverCookie?: string) {
......
import * as Sentry from '@sentry/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import type { AppProps } from 'next/app'; import type { AppProps } from 'next/app';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { AppWrapper } from 'lib/appContext'; import { AppContextProvider } from 'lib/appContext';
import { Chakra } from 'lib/Chakra'; import { Chakra } from 'lib/Chakra';
import useConfigSentry from 'lib/hooks/useConfigSentry'; import useConfigSentry from 'lib/hooks/useConfigSentry';
import type { ErrorType } from 'lib/hooks/useFetch'; import type { ErrorType } from 'lib/hooks/useFetch';
import theme from 'theme'; import theme from 'theme';
import AppError from 'ui/shared/AppError';
import ErrorBoundary from 'ui/shared/ErrorBoundary';
function MyApp({ Component, pageProps }: AppProps) { function MyApp({ Component, pageProps }: AppProps) {
useConfigSentry(); useConfigSentry();
...@@ -30,15 +33,36 @@ function MyApp({ Component, pageProps }: AppProps) { ...@@ -30,15 +33,36 @@ function MyApp({ Component, pageProps }: AppProps) {
}, },
})); }));
const renderErrorScreen = React.useCallback(() => {
return (
<AppError
statusCode={ 500 }
height="100vh"
display="flex"
flexDirection="column"
alignItems="flex-start"
justifyContent="center"
width="fit-content"
margin="0 auto"
/>
);
}, []);
const handleError = React.useCallback((error: Error) => {
Sentry.captureException(error);
}, []);
return ( return (
<AppWrapper pageProps={ pageProps }> <Chakra theme={ theme } cookies={ pageProps.cookies }>
<QueryClientProvider client={ queryClient }> <ErrorBoundary renderErrorScreen={ renderErrorScreen } onError={ handleError }>
<Chakra theme={ theme } cookies={ pageProps.cookies }> <AppContextProvider pageProps={ pageProps }>
<Component { ...pageProps }/> <QueryClientProvider client={ queryClient }>
</Chakra> <Component { ...pageProps }/>
<ReactQueryDevtools/> <ReactQueryDevtools/>
</QueryClientProvider> </QueryClientProvider>
</AppWrapper> </AppContextProvider>
</ErrorBoundary>
</Chakra>
); );
} }
......
...@@ -17,33 +17,54 @@ ...@@ -17,33 +17,54 @@
*/ */
import * as Sentry from '@sentry/nextjs'; import * as Sentry from '@sentry/nextjs';
import type { NextPageContext } from 'next'; import type { GetServerSideProps } from 'next';
import NextErrorComponent from 'next/error'; import NextErrorComponent from 'next/error';
import Head from 'next/head';
import React from 'react'; import React from 'react';
import sentryConfig from 'configs/sentry/nextjs'; import sentryConfig from 'configs/sentry/nextjs';
import * as cookies from 'lib/cookies';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import type { Props as ServerSidePropsCommon } from 'lib/next/getServerSideProps';
import { getServerSideProps as getServerSidePropsCommon } from 'lib/next/getServerSideProps';
import AppError from 'ui/shared/AppError';
import Page from 'ui/shared/Page/Page';
type ContextOrProps = { type Props = ServerSidePropsCommon & {
req?: NextPageContext['req']; statusCode: number;
res?: NextPageContext['res']; }
err?: NextPageContext['err'] | string;
pathname?: string; const CustomErrorComponent = (props: Props) => {
statusCode?: number; if (props.statusCode === 404) {
}; const title = getNetworkTitle();
const CustomErrorComponent = (props: { statusCode: number }) => { return (
return <NextErrorComponent statusCode={ props.statusCode }/>; <>
<Head>
<title>{ title }</title>
</Head>
<Page>
<AppError statusCode={ 404 } mt="50px"/>
</Page>
</>
);
}
const colorModeCookie = cookies.getFromCookieString(props.cookies, cookies.NAMES.COLOR_MODE);
return <NextErrorComponent statusCode={ props.statusCode } withDarkMode={ colorModeCookie === 'dark' }/>;
}; };
CustomErrorComponent.getInitialProps = async(contextData: ContextOrProps) => { export default CustomErrorComponent;
export const getServerSideProps: GetServerSideProps = async(context) => {
Sentry.init(sentryConfig); Sentry.init(sentryConfig);
// In case this is running in a serverless function, await this in order to give Sentry // In case this is running in a serverless function, await this in order to give Sentry
// time to send the error before the lambda exits // time to send the error before the lambda exits
await Sentry.captureUnderscoreErrorException(contextData); await Sentry.captureUnderscoreErrorException(context);
// This will contain the status code of the response const commonSSPResult = await getServerSidePropsCommon(context);
return NextErrorComponent.getInitialProps(contextData as NextPageContext); const commonSSProps = 'props' in commonSSPResult ? commonSSPResult.props : undefined;
};
export default CustomErrorComponent; return { props: { ...commonSSProps, statusCode: context.res.statusCode } };
};
...@@ -10,6 +10,10 @@ const baseStyle: SystemStyleInterpolation = (props) => { ...@@ -10,6 +10,10 @@ const baseStyle: SystemStyleInterpolation = (props) => {
}; };
const sizes = { const sizes = {
'2xl': defineStyle({
fontSize: '48px',
lineHeight: '60px',
}),
lg: defineStyle({ lg: defineStyle({
fontSize: '32px', fontSize: '32px',
lineHeight: '40px', lineHeight: '40px',
......
import { Box, Button, Heading, Icon, Text, chakra } from '@chakra-ui/react';
import React from 'react';
import icon404 from 'icons/error-pages/404.svg';
import icon500 from 'icons/error-pages/500.svg';
import link from 'lib/link/link';
interface Props {
statusCode: number;
className?: string;
}
const ERRORS: Record<string, {icon: React.FunctionComponent<React.SVGAttributes<SVGElement>>; text: string; title: string }> = {
'404': {
icon: icon404,
title: 'Page not found',
text: 'This page is no longer explorable! If you are lost, use the search bar to find what you are looking for.',
},
'500': {
icon: icon500,
title: 'Oops! Something went wrong',
text: 'An unexpected error has occurred. Try reloading the page, or come back soon and try again.',
},
};
const AppError = ({ statusCode, className }: Props) => {
const error = ERRORS[String(statusCode)] || ERRORS['500'];
return (
<Box className={ className }>
<Icon as={ error.icon } width="200px" height="auto"/>
<Heading mt={ 8 } size="2xl" fontFamily="body">{ error.title }</Heading>
<Text variant="secondary" mt={ 3 }> { error.text } </Text>
<Button
mt={ 8 }
size="lg"
variant="outline"
as="a"
href={ link('network_index') }
>
Back to home
</Button>
</Box>
);
};
export default chakra(AppError);
import React from 'react';
interface Props {
children: React.ReactNode;
renderErrorScreen: () => React.ReactNode;
onError?: (error: Error) => void;
}
class ErrorBoundary extends React.PureComponent<Props> {
state = {
hasError: false,
};
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error) {
this.props.onError?.(error);
}
render() {
if (this.state.hasError) {
return this.props.renderErrorScreen();
}
return this.props.children;
}
}
export default ErrorBoundary;
...@@ -10,6 +10,8 @@ import useFetch from 'lib/hooks/useFetch'; ...@@ -10,6 +10,8 @@ import useFetch from 'lib/hooks/useFetch';
import useScrollDirection from 'lib/hooks/useScrollDirection'; import useScrollDirection from 'lib/hooks/useScrollDirection';
import { SocketProvider } from 'lib/socket/context'; import { SocketProvider } from 'lib/socket/context';
import ScrollDirectionContext from 'ui/ScrollDirectionContext'; import ScrollDirectionContext from 'ui/ScrollDirectionContext';
import AppError from 'ui/shared/AppError';
import ErrorBoundary from 'ui/shared/ErrorBoundary';
import PageContent from 'ui/shared/Page/PageContent'; import PageContent from 'ui/shared/Page/PageContent';
import Header from 'ui/snippets/header/Header'; import Header from 'ui/snippets/header/Header';
import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop'; import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop';
...@@ -33,6 +35,12 @@ const Page = ({ ...@@ -33,6 +35,12 @@ const Page = ({
const directionContext = useScrollDirection(); const directionContext = useScrollDirection();
const renderErrorScreen = React.useCallback(() => {
return wrapChildren ?
<PageContent><AppError statusCode={ 500 } mt="50px"/></PageContent> :
<AppError statusCode={ 500 }/>;
}, [ wrapChildren ]);
const renderedChildren = wrapChildren ? ( const renderedChildren = wrapChildren ? (
<PageContent>{ children }</PageContent> <PageContent>{ children }</PageContent>
) : children; ) : children;
...@@ -44,7 +52,9 @@ const Page = ({ ...@@ -44,7 +52,9 @@ const Page = ({
<NavigationDesktop/> <NavigationDesktop/>
<Flex flexDir="column" width="100%"> <Flex flexDir="column" width="100%">
<Header hideOnScrollDown={ hideMobileHeaderOnScrollDown }/> <Header hideOnScrollDown={ hideMobileHeaderOnScrollDown }/>
{ renderedChildren } <ErrorBoundary renderErrorScreen={ renderErrorScreen }>
{ renderedChildren }
</ErrorBoundary>
</Flex> </Flex>
</Flex> </Flex>
</ScrollDirectionContext.Provider> </ScrollDirectionContext.Provider>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment