From 1905dfe7bfa4ec4402a50784a5ee64a49196fb01 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Fri, 8 Mar 2019 16:13:13 -0500 Subject: [PATCH] Frontend error handlers (#312) * Add componentDidCatch around routes with template, and outside routes at the top level. * Remove error from ErrorWrap on navigation. --- frontend/client/Routes.tsx | 10 ++-- .../client/components/ErrorScreen/index.less | 52 ++++++++++++++++++ .../client/components/ErrorScreen/index.tsx | 53 +++++++++++++++++++ frontend/client/components/ErrorWrap.tsx | 45 ++++++++++++++++ frontend/client/components/Footer/style.less | 5 +- frontend/client/components/Header/style.less | 4 +- frontend/client/index.tsx | 17 +++--- frontend/client/pages/proposal.tsx | 3 -- 8 files changed, 171 insertions(+), 18 deletions(-) create mode 100644 frontend/client/components/ErrorScreen/index.less create mode 100644 frontend/client/components/ErrorScreen/index.tsx create mode 100644 frontend/client/components/ErrorWrap.tsx diff --git a/frontend/client/Routes.tsx b/frontend/client/Routes.tsx index 03425289..c39d2d36 100644 --- a/frontend/client/Routes.tsx +++ b/frontend/client/Routes.tsx @@ -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 { return ( ); } diff --git a/frontend/client/components/ErrorScreen/index.less b/frontend/client/components/ErrorScreen/index.less new file mode 100644 index 00000000..6c5280e2 --- /dev/null +++ b/frontend/client/components/ErrorScreen/index.less @@ -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; + } + } + } +} \ No newline at end of file diff --git a/frontend/client/components/ErrorScreen/index.tsx b/frontend/client/components/ErrorScreen/index.tsx new file mode 100644 index 00000000..dd424407 --- /dev/null +++ b/frontend/client/components/ErrorScreen/index.tsx @@ -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 { + 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 ( +
+ +

Something went wrong, and we've logged the following error:

+ {error.message} +

+ 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. +

+
+ } + actions={ +
+ + + + + + +
+ } + /> + + ); + } +} diff --git a/frontend/client/components/ErrorWrap.tsx b/frontend/client/components/ErrorWrap.tsx new file mode 100644 index 00000000..6ae20555 --- /dev/null +++ b/frontend/client/components/ErrorWrap.tsx @@ -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 { + 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 ( +
+ +
+ ); + } else { + return this.props.children; + } + } +} diff --git a/frontend/client/components/Footer/style.less b/frontend/client/components/Footer/style.less index bc680427..f312d54e 100644 --- a/frontend/client/components/Footer/style.less +++ b/frontend/client/components/Footer/style.less @@ -51,8 +51,9 @@ } &-logo { - height: 1.8rem; - margin-bottom: 1rem; + height: 6.8rem; + margin-bottom: 0.5rem; + margin-top: -1.25rem; } } diff --git a/frontend/client/components/Header/style.less b/frontend/client/components/Header/style.less index cc053bd1..67dcc3d9 100644 --- a/frontend/client/components/Header/style.less +++ b/frontend/client/components/Header/style.less @@ -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; diff --git a/frontend/client/index.tsx b/frontend/client/index.tsx index 7462fa61..dc0f7c59 100644 --- a/frontend/client/index.tsx +++ b/frontend/client/index.tsx @@ -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)(() => ( - - - - - - - + + + + + + + + + )); loadableReady(() => { diff --git a/frontend/client/pages/proposal.tsx b/frontend/client/pages/proposal.tsx index 93f10476..1e033dc8 100644 --- a/frontend/client/pages/proposal.tsx +++ b/frontend/client/pages/proposal.tsx @@ -7,9 +7,6 @@ import { withRouter, RouteComponentProps } from 'react-router'; type RouteProps = RouteComponentProps; class ProposalPage extends Component { - constructor(props: RouteProps) { - super(props); - } render() { const proposalId = extractIdFromSlug(this.props.match.params.id); return ;