Frontend error handlers (#312)

* Add componentDidCatch around routes with template, and outside routes at the top level.

* Remove error from ErrorWrap on navigation.
This commit is contained in:
William O'Beirne 2019-03-08 16:13:13 -05:00 committed by GitHub
parent a271724266
commit 1905dfe7bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 171 additions and 18 deletions

View File

@ -11,6 +11,9 @@ import {
import loadable from '@loadable/component';
import AuthRoute from 'components/AuthRoute';
import Template, { TemplateProps } from 'components/Template';
import ErrorWrap from 'components/ErrorWrap';
import Loader from 'components/Loader';
import 'styles/style.less';
// wrap components in loadable...import & they will be split
// Make sure you specify chunkname! Must replace slashes with dashes.
@ -38,9 +41,6 @@ const ArbiterEmail = loadable(() => import('pages/email-arbiter'), opts);
const RFP = loadable(() => import('pages/rfp'), opts);
const RFPs = loadable(() => import('pages/rfps'), opts);
import 'styles/style.less';
import Loader from 'components/Loader';
interface RouteConfig extends RouteProps {
route: RouteProps;
template: TemplateProps;
@ -336,7 +336,9 @@ class Routes extends React.PureComponent<Props> {
return (
<Template {...currentRoute.template}>
<Switch>{routeComponents}</Switch>
<ErrorWrap key={currentRoute.route.path as string}>
<Switch>{routeComponents}</Switch>
</ErrorWrap>
</Template>
);
}

View File

@ -0,0 +1,52 @@
@import '~styles/variables.less';
.ErrorScreen {
padding: 2rem 0;
&-desc {
font-size: 1.25rem;
line-height: 1.6;
&-error {
display: block;
font-size: 1rem;
padding: 1rem;
margin-bottom: 1rem;
background: rgba(#000, 0.03);
border: 1px solid rgba(#000, 0.1);
border-radius: 4px;
}
}
&-buttons {
.ant-btn {
margin-right: 1rem;
}
}
// Antd overrides
.antd-pro-exception-exception {
min-height: 0;
.antd-pro-exception-imgBlock {
flex: 0 0 45%;
width: 45%;
@media @tablet-query {
display: none;
}
}
.antd-pro-exception-content {
padding-right: 2rem;
@media @tablet-query {
padding-right: 0;
}
h1 {
font-size: 4.2rem;
}
}
}
}

View File

@ -0,0 +1,53 @@
import React from 'react';
import * as Sentry from '@sentry/browser';
import { Button } from 'antd';
import Exception from 'ant-design-pro/lib/Exception';
import './index.less';
interface Props {
error: Error;
}
export default class ErrorScreen extends React.PureComponent<Props> {
componentDidMount() {
const { error } = this.props;
Sentry.captureException(error);
console.error('Error screen showing due to the following error:', error);
}
render() {
const { error } = this.props;
return (
<div className="ErrorScreen">
<Exception
type="404"
title="Whoa nelly."
desc={
<div className="ErrorScreen-desc">
<p>Something went wrong, and we've logged the following error:</p>
<code className="ErrorScreen-desc-error">{error.message}</code>
<p>
Our developers will get right on fixing it. You can either return home and
try again, or open an issue on Github to provide us some more details.
</p>
</div>
}
actions={
<div className="ErrorScreen-buttons">
<a href="/">
<Button icon="home" size="large" type="primary">
Return home
</Button>
</a>
<a href="https://github.com/grant-project/zcash-grant-system/issues/new">
<Button icon="github" size="large">
Open an issue
</Button>
</a>
</div>
}
/>
</div>
);
}
}

View File

@ -0,0 +1,45 @@
import React from 'react';
import ErrorScreen from './ErrorScreen';
interface Props {
children: React.ReactNode;
isFullscreen?: boolean;
}
interface State {
error: Error | null;
}
export default class ErrorWrap extends React.Component<Props, State> {
state: State = {
error: null,
};
componentDidCatch(error: Error) {
this.setState({ error });
}
render() {
let style;
if (this.props.isFullscreen) {
style = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100vw',
minHeight: '100vh',
padding: '0 2rem',
};
}
if (this.state.error) {
return (
<div style={style}>
<ErrorScreen error={this.state.error} />
</div>
);
} else {
return this.props.children;
}
}
}

View File

@ -51,8 +51,9 @@
}
&-logo {
height: 1.8rem;
margin-bottom: 1rem;
height: 6.8rem;
margin-bottom: 0.5rem;
margin-top: -1.25rem;
}
}

View File

@ -1,5 +1,5 @@
@import '~styles/variables.less';
@header-height: 62px;
@header-height: 68px;
@header-transition: 200ms;
@link-padding: 0.7rem;
@small-query: ~'(max-width: 820px)';
@ -54,7 +54,7 @@
}
&-logo {
height: 1.4rem;
height: 3.6rem;
width: auto;
transition: transform @header-transition ease;

View File

@ -12,6 +12,7 @@ import history from 'store/history';
import { massageSerializedState } from 'utils/api';
import Routes from './Routes';
import i18n from './i18n';
import ErrorWrap from 'components/ErrorWrap';
Sentry.init({
dsn: process.env.SENTRY_DSN,
@ -26,13 +27,15 @@ i18n.changeLanguage(i18nLanguage.locale);
i18n.addResourceBundle(i18nLanguage.locale, 'common', i18nLanguage.resources, true);
const App = hot(module)(() => (
<I18nextProvider i18n={i18n}>
<Provider store={store}>
<Router history={history}>
<Routes />
</Router>
</Provider>
</I18nextProvider>
<ErrorWrap isFullscreen>
<I18nextProvider i18n={i18n}>
<Provider store={store}>
<Router history={history}>
<Routes />
</Router>
</Provider>
</I18nextProvider>
</ErrorWrap>
));
loadableReady(() => {

View File

@ -7,9 +7,6 @@ import { withRouter, RouteComponentProps } from 'react-router';
type RouteProps = RouteComponentProps<any>;
class ProposalPage extends Component<RouteProps> {
constructor(props: RouteProps) {
super(props);
}
render() {
const proposalId = extractIdFromSlug(this.props.match.params.id);
return <Proposal proposalId={proposalId} />;