mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
404 and 500 error handling components
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,3 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
+1
-1
@@ -9,7 +9,7 @@ type Props = {
|
||||
|
||||
const AppContext = createContext<PageProps>({ cookies: '', referrer: '' });
|
||||
|
||||
export function AppWrapper({ children, pageProps }: Props) {
|
||||
export function AppContextProvider({ children, pageProps }: Props) {
|
||||
return (
|
||||
<AppContext.Provider value={ pageProps }>
|
||||
{ children }
|
||||
|
||||
@@ -7,6 +7,7 @@ export enum NAMES {
|
||||
NAV_BAR_COLLAPSED='nav_bar_collapsed',
|
||||
API_TOKEN='_explorer_key',
|
||||
TXS_SORT='txs_sort',
|
||||
COLOR_MODE='chakra-ui-color-mode',
|
||||
}
|
||||
|
||||
export function get(name?: NAMES | undefined | null, serverCookie?: string) {
|
||||
|
||||
+33
-9
@@ -1,13 +1,16 @@
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import type { AppProps } from 'next/app';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { AppWrapper } from 'lib/appContext';
|
||||
import { AppContextProvider } from 'lib/appContext';
|
||||
import { Chakra } from 'lib/Chakra';
|
||||
import useConfigSentry from 'lib/hooks/useConfigSentry';
|
||||
import type { ErrorType } from 'lib/hooks/useFetch';
|
||||
import theme from 'theme';
|
||||
import AppError from 'ui/shared/AppError';
|
||||
import ErrorBoundary from 'ui/shared/ErrorBoundary';
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
useConfigSentry();
|
||||
@@ -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 (
|
||||
<AppWrapper pageProps={ pageProps }>
|
||||
<QueryClientProvider client={ queryClient }>
|
||||
<Chakra theme={ theme } cookies={ pageProps.cookies }>
|
||||
<Component { ...pageProps }/>
|
||||
</Chakra>
|
||||
<ReactQueryDevtools/>
|
||||
</QueryClientProvider>
|
||||
</AppWrapper>
|
||||
<Chakra theme={ theme } cookies={ pageProps.cookies }>
|
||||
<ErrorBoundary renderErrorScreen={ renderErrorScreen } onError={ handleError }>
|
||||
<AppContextProvider pageProps={ pageProps }>
|
||||
<QueryClientProvider client={ queryClient }>
|
||||
<Component { ...pageProps }/>
|
||||
<ReactQueryDevtools/>
|
||||
</QueryClientProvider>
|
||||
</AppContextProvider>
|
||||
</ErrorBoundary>
|
||||
</Chakra>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+37
-16
@@ -17,33 +17,54 @@
|
||||
*/
|
||||
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import type { NextPageContext } from 'next';
|
||||
import type { GetServerSideProps } from 'next';
|
||||
import NextErrorComponent from 'next/error';
|
||||
import Head from 'next/head';
|
||||
import React from 'react';
|
||||
|
||||
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 = {
|
||||
req?: NextPageContext['req'];
|
||||
res?: NextPageContext['res'];
|
||||
err?: NextPageContext['err'] | string;
|
||||
pathname?: string;
|
||||
statusCode?: number;
|
||||
type Props = ServerSidePropsCommon & {
|
||||
statusCode: number;
|
||||
}
|
||||
|
||||
const CustomErrorComponent = (props: Props) => {
|
||||
if (props.statusCode === 404) {
|
||||
const title = getNetworkTitle();
|
||||
|
||||
return (
|
||||
<>
|
||||
<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' }/>;
|
||||
};
|
||||
|
||||
const CustomErrorComponent = (props: { statusCode: number }) => {
|
||||
return <NextErrorComponent statusCode={ props.statusCode }/>;
|
||||
};
|
||||
export default CustomErrorComponent;
|
||||
|
||||
CustomErrorComponent.getInitialProps = async(contextData: ContextOrProps) => {
|
||||
export const getServerSideProps: GetServerSideProps = async(context) => {
|
||||
Sentry.init(sentryConfig);
|
||||
|
||||
// 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
|
||||
await Sentry.captureUnderscoreErrorException(contextData);
|
||||
await Sentry.captureUnderscoreErrorException(context);
|
||||
|
||||
// This will contain the status code of the response
|
||||
return NextErrorComponent.getInitialProps(contextData as NextPageContext);
|
||||
const commonSSPResult = await getServerSidePropsCommon(context);
|
||||
const commonSSProps = 'props' in commonSSPResult ? commonSSPResult.props : undefined;
|
||||
|
||||
return { props: { ...commonSSProps, statusCode: context.res.statusCode } };
|
||||
};
|
||||
|
||||
export default CustomErrorComponent;
|
||||
|
||||
@@ -10,6 +10,10 @@ const baseStyle: SystemStyleInterpolation = (props) => {
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
'2xl': defineStyle({
|
||||
fontSize: '48px',
|
||||
lineHeight: '60px',
|
||||
}),
|
||||
lg: defineStyle({
|
||||
fontSize: '32px',
|
||||
lineHeight: '40px',
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
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);
|
||||
@@ -0,0 +1,31 @@
|
||||
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;
|
||||
+11
-1
@@ -10,6 +10,8 @@ import useFetch from 'lib/hooks/useFetch';
|
||||
import useScrollDirection from 'lib/hooks/useScrollDirection';
|
||||
import { SocketProvider } from 'lib/socket/context';
|
||||
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 Header from 'ui/snippets/header/Header';
|
||||
import NavigationDesktop from 'ui/snippets/navigation/NavigationDesktop';
|
||||
@@ -33,6 +35,12 @@ const Page = ({
|
||||
|
||||
const directionContext = useScrollDirection();
|
||||
|
||||
const renderErrorScreen = React.useCallback(() => {
|
||||
return wrapChildren ?
|
||||
<PageContent><AppError statusCode={ 500 } mt="50px"/></PageContent> :
|
||||
<AppError statusCode={ 500 }/>;
|
||||
}, [ wrapChildren ]);
|
||||
|
||||
const renderedChildren = wrapChildren ? (
|
||||
<PageContent>{ children }</PageContent>
|
||||
) : children;
|
||||
@@ -44,7 +52,9 @@ const Page = ({
|
||||
<NavigationDesktop/>
|
||||
<Flex flexDir="column" width="100%">
|
||||
<Header hideOnScrollDown={ hideMobileHeaderOnScrollDown }/>
|
||||
{ renderedChildren }
|
||||
<ErrorBoundary renderErrorScreen={ renderErrorScreen }>
|
||||
{ renderedChildren }
|
||||
</ErrorBoundary>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</ScrollDirectionContext.Provider>
|
||||
|
||||
Reference in New Issue
Block a user