diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2019-04-10 15:58:29 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2019-04-11 14:30:26 +0200 |
| commit | 1741fc096f01abc5fe5c1b5de84f8f504ed1615b (patch) | |
| tree | fe02b281e42564fd4fc9990e12b824fa93e56641 /gui/src | |
| parent | 336ca26d259d81d189faee521f4fd2cfa7d13f7e (diff) | |
| download | mullvadvpn-1741fc096f01abc5fe5c1b5de84f8f504ed1615b.tar.xz mullvadvpn-1741fc096f01abc5fe5c1b5de84f8f504ed1615b.zip | |
Add error boundary
Diffstat (limited to 'gui/src')
| -rw-r--r-- | gui/src/config.json | 2 | ||||
| -rw-r--r-- | gui/src/renderer/app.tsx | 15 | ||||
| -rw-r--r-- | gui/src/renderer/components/ErrorBoundary.tsx | 93 | ||||
| -rw-r--r-- | gui/src/renderer/components/Support.tsx | 13 |
4 files changed, 110 insertions, 13 deletions
diff --git a/gui/src/config.json b/gui/src/config.json index e3f340ec96..54eca6000b 100644 --- a/gui/src/config.json +++ b/gui/src/config.json @@ -4,7 +4,7 @@ "purchase": "https://mullvad.net/account/login/", "faq": "https://mullvad.net/faq/", "download": "https://mullvad.net/download/", - "supportEmail": "mailto:support@mullvad.net" + "supportEmail": "support@mullvad.net" }, "colors": { "darkBlue": "rgb(25, 46, 69)", diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index 96d0b3e43d..2549404e20 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -11,6 +11,7 @@ import { Provider } from 'react-redux'; import { bindActionCreators } from 'redux'; import { InvalidAccountError } from '../main/errors'; +import ErrorBoundary from './components/ErrorBoundary'; import AppRoutes from './routes'; import accountActions from './redux/account/actions'; @@ -185,12 +186,14 @@ export default class AppRenderer { return ( <Provider store={this.reduxStore}> <ConnectedRouter history={this.memoryHistory}> - <AppRoutes - sharedProps={{ - app: this, - locale: this.locale, - }} - /> + <ErrorBoundary> + <AppRoutes + sharedProps={{ + app: this, + locale: this.locale, + }} + /> + </ErrorBoundary> </ConnectedRouter> </Provider> ); diff --git a/gui/src/renderer/components/ErrorBoundary.tsx b/gui/src/renderer/components/ErrorBoundary.tsx new file mode 100644 index 0000000000..07bc839bce --- /dev/null +++ b/gui/src/renderer/components/ErrorBoundary.tsx @@ -0,0 +1,93 @@ +import log from 'electron-log'; +import * as React from 'react'; +import { Component, Styles, Text, View } from 'reactxp'; +import { colors, links } from '../../config.json'; +import { messages } from '../../shared/gettext'; +import PlatformWindowContainer from '../containers/PlatformWindowContainer'; +import ImageView from './ImageView'; +import { Container, Layout } from './Layout'; + +interface IProps { + children?: React.ReactNode; +} + +interface IState { + hasError: boolean; +} + +const styles = { + container: Styles.createViewStyle({ + flex: 1, + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: colors.blue, + }), + logo: Styles.createViewStyle({ + marginBottom: 4, + }), + title: Styles.createTextStyle({ + fontFamily: 'DINPro', + fontSize: 24, + fontWeight: '900', + lineHeight: 30, + letterSpacing: -0.5, + color: colors.white60, + marginBottom: 4, + }), + subtitle: Styles.createTextStyle({ + fontFamily: 'Open Sans', + fontSize: 14, + lineHeight: 20, + color: colors.white40, + marginHorizontal: 20, + textAlign: 'center', + }), + email: Styles.createTextStyle({ + fontWeight: '900', + }), +}; + +export default class ErrorBoundary extends Component<IProps, IState> { + public state = { hasError: false }; + + public componentDidCatch(error: Error, info: React.ErrorInfo) { + this.setState({ hasError: true }); + + log.error( + `The error boundary caught an error: ${error.message}\nError stack: ${error.stack || + 'Not available'}\nComponent stack: ${info.componentStack}`, + ); + } + + public render() { + if (this.state.hasError) { + const reachBackMessage: React.ReactNodeArray = + // TRANSLATORS: The message displayed to the user in case of critical error in the GUI + // TRANSLATORS: Available placeholders: + // TRANSLATORS: %(email)s - support email + messages + .pgettext('error-boundary-view', 'Something went wrong. Please contact us at %(email)s') + .split('%(email)s', 2); + reachBackMessage.splice(1, 0, <Text style={styles.email}>{links.supportEmail}</Text>); + + return ( + <PlatformWindowContainer> + <Layout> + <Container> + <View style={styles.container}> + <ImageView height={120} width={120} source="logo-icon" style={styles.logo} /> + <Text style={styles.title}> + {messages.pgettext('error-boundary-view', 'MULLVAD VPN')} + </Text> + <Text style={styles.subtitle}>{reachBackMessage}</Text> + </View> + </Container> + </Layout> + </PlatformWindowContainer> + ); + } else { + return this.props.children; + } + } +} diff --git a/gui/src/renderer/components/Support.tsx b/gui/src/renderer/components/Support.tsx index 439b229dfc..fd9241bbf8 100644 --- a/gui/src/renderer/components/Support.tsx +++ b/gui/src/renderer/components/Support.tsx @@ -293,12 +293,13 @@ export default class Support extends Component<ISupportProps, ISupportState> { } private renderSent() { - // TRANSLATORS: The message displayed to the user after submitting the problem report, given that the user left his or her email for us to reach back. - // TRANSLATORS: Available placeholders: - // TRANSLATORS: %(email)s - const reachBackMessage: React.ReactNodeArray = messages - .pgettext('support-view', 'If needed we will contact you on %(email)s') - .split('%(email)s', 2); + const reachBackMessage: React.ReactNodeArray = + // TRANSLATORS: The message displayed to the user after submitting the problem report, given that the user left his or her email for us to reach back. + // TRANSLATORS: Available placeholders: + // TRANSLATORS: %(email)s + messages + .pgettext('support-view', 'If needed we will contact you on %(email)s') + .split('%(email)s', 2); reachBackMessage.splice( 1, 0, |
