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 0000000000..fd37383f21
--- /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 (
+
+
+
+
+
+
+ -
+ }}
+ />
+ { debugInfo !== '' && (
+
+
+
+
+ )}
+
+ -
+ }}
+ />
+
+ -
+ }}
+ />
+
+
+
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/flavours/glitch/containers/mastodon.js b/app/javascript/flavours/glitch/containers/mastodon.js
index 4bd9cb75e6..4fb6be4760 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 (
-
-
-
-
-
+
+
+
+
+
+
+
);
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 0000000000..f9bf425f80
--- /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 3e3ef6b524..873dfa98d1 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';