summaryrefslogtreecommitdiffhomepage
path: root/gui/src
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/config.json2
-rw-r--r--gui/src/renderer/app.tsx15
-rw-r--r--gui/src/renderer/components/ErrorBoundary.tsx93
-rw-r--r--gui/src/renderer/components/Support.tsx13
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,