about summary refs log tree commit diff
path: root/app/javascript/flavours
diff options
context:
space:
mode:
authorThibaut Girka <thib@sitedethib.com>2018-11-28 15:01:40 +0100
committerThibG <thib@sitedethib.com>2018-11-28 22:36:01 +0100
commit922d05864f4ba37a4026cd5f7e6e6ed5417a7404 (patch)
treea157d4c4e3188d3e7de34793263ce1dce8dedefa /app/javascript/flavours
parent39c8a71df80d303ee08d24b3ce3a66400a1dcc0b (diff)
Add error boundary component to catch Web UI crashes
Diffstat (limited to 'app/javascript/flavours')
-rw-r--r--app/javascript/flavours/glitch/components/error_boundary.js92
-rw-r--r--app/javascript/flavours/glitch/containers/mastodon.js13
-rw-r--r--app/javascript/flavours/glitch/styles/components/error_boundary.scss32
-rw-r--r--app/javascript/flavours/glitch/styles/components/index.scss1
4 files changed, 133 insertions, 5 deletions
diff --git a/app/javascript/flavours/glitch/components/error_boundary.js b/app/javascript/flavours/glitch/components/error_boundary.js
new file mode 100644
index 000000000..fd37383f2
--- /dev/null
+++ b/app/javascript/flavours/glitch/components/error_boundary.js
@@ -0,0 +1,92 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+
+export default class ErrorBoundary extends React.PureComponent {
+
+  static propTypes = {
+    children: PropTypes.node,
+  };
+
+  state = {
+    hasError: false,
+    stackTrace: undefined,
+    componentStack: undefined,
+  }
+
+  componentDidCatch(error, info) {
+    this.setState({
+      hasError: true,
+      stackTrace: error.stack,
+      componentStack: info && info.componentStack,
+    });
+  }
+
+  handleReload(e) {
+    e.preventDefault();
+    window.location.reload();
+  }
+
+  render() {
+    const { hasError, stackTrace, componentStack } = this.state;
+
+    if (!hasError) return this.props.children;
+
+    let debugInfo = '';
+    if (stackTrace) {
+      debugInfo += 'Stack trace\n-----------\n\n```\n' + stackTrace.toString() + '\n```';
+    }
+    if (componentStack) {
+      if (debugInfo) {
+        debugInfo += '\n\n\n';
+      }
+      debugInfo += 'React component stack\n---------------------\n\n```\n' + componentStack.toString() + '\n```';
+    }
+
+    return (
+      <div tabIndex='-1'>
+        <div className='error-boundary'>
+          <h1><FormattedMessage id='web_app_crash.title' defaultMessage="We're sorry, but something went wrong with the Mastodon app." /></h1>
+          <p>
+            <FormattedMessage id='web_app_crash.content' defaultMessage='You could try any of the following:' />
+            <ul>
+              <li>
+                <FormattedMessage
+                  id='web_app_crash.report_issue'
+                  defaultMessage='Report a bug in the {issuetracker}'
+                  values={{ issuetracker: <a href='https://github.com/glitch-soc/mastodon/issues' rel='noopener' target='_blank'><FormattedMessage id='web_app_crash.issue_tracker' defaultMessage='issue tracker' /></a> }}
+                />
+                { debugInfo !== '' && (
+                  <details>
+                    <summary><FormattedMessage id='web_app_crash.debug_info' defaultMessage='Debug information' /></summary>
+                    <textarea
+                      className='web_app_crash-stacktrace'
+                      value={debugInfo}
+                      rows='10'
+                      readOnly
+                    />
+                  </details>
+                )}
+              </li>
+              <li>
+                <FormattedMessage
+                  id='web_app_crash.reload_page'
+                  defaultMessage='{reload} the current page'
+                  values={{ reload: <a href='#' onClick={this.handleReload}><FormattedMessage id='web_app_crash.reload' defaultMessage='Reload' /></a> }}
+                />
+              </li>
+              <li>
+                <FormattedMessage
+                  id='web_app_crash.change_your_settings'
+                  defaultMessage='Change your {settings}'
+                  values={{ settings: <a href='/settings/preferences'><FormattedMessage id='web_app_crash.settings' defaultMessage='settings' /></a> }}
+                />
+              </li>
+            </ul>
+          </p>
+        </div>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/containers/mastodon.js b/app/javascript/flavours/glitch/containers/mastodon.js
index 4bd9cb75e..4fb6be476 100644
--- a/app/javascript/flavours/glitch/containers/mastodon.js
+++ b/app/javascript/flavours/glitch/containers/mastodon.js
@@ -12,6 +12,7 @@ import { connectUserStream } from 'flavours/glitch/actions/streaming';
 import { IntlProvider, addLocaleData } from 'react-intl';
 import { getLocale } from 'locales';
 import initialState from 'flavours/glitch/util/initial_state';
+import ErrorBoundary from 'flavours/glitch/components/error_boundary';
 
 const { localeData, messages } = getLocale();
 addLocaleData(localeData);
@@ -61,11 +62,13 @@ export default class Mastodon extends React.PureComponent {
     return (
       <IntlProvider locale={locale} messages={messages}>
         <Provider store={store}>
-          <BrowserRouter basename='/web'>
-            <ScrollContext>
-              <Route path='/' component={UI} />
-            </ScrollContext>
-          </BrowserRouter>
+          <ErrorBoundary>
+            <BrowserRouter basename='/web'>
+              <ScrollContext>
+                <Route path='/' component={UI} />
+              </ScrollContext>
+            </BrowserRouter>
+          </ErrorBoundary>
         </Provider>
       </IntlProvider>
     );
diff --git a/app/javascript/flavours/glitch/styles/components/error_boundary.scss b/app/javascript/flavours/glitch/styles/components/error_boundary.scss
new file mode 100644
index 000000000..f9bf425f8
--- /dev/null
+++ b/app/javascript/flavours/glitch/styles/components/error_boundary.scss
@@ -0,0 +1,32 @@
+.error-boundary {
+  h1 {
+    font-size: 26px;
+    line-height: 36px;
+    font-weight: 400;
+    margin-bottom: 8px;
+  }
+
+  p {
+    color: $primary-text-color;
+    font-size: 15px;
+    line-height: 20px;
+
+    a {
+      color: $primary-text-color;
+      text-decoration: underline;
+    }
+
+    ul {
+      list-style: disc;
+      margin-left: 0;
+      padding-left: 1em;
+    }
+
+    textarea.web_app_crash-stacktrace {
+      width: 100%;
+      resize: none;
+      white-space: pre;
+      font-family: $font-monospace, monospace;
+    }
+  }
+}
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index 3e3ef6b52..873dfa98d 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -1263,3 +1263,4 @@ noscript {
 @import 'lists';
 @import 'emoji_picker';
 @import 'local_settings';
+@import 'error_boundary';