diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index f904ffa50..55b423894 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -531,6 +531,38 @@
"enterPasswordContinue": {
"message": "Enter password to continue"
},
+ "errorCode": {
+ "message": "Code: $1",
+ "description": "Displayed error code for debugging purposes. $1 is the error code"
+ },
+ "errorDetails": {
+ "message": "Error Details",
+ "description": "Title for collapsible section that displays error details for debugging purposes"
+ },
+ "errorMessage": {
+ "message": "Message: $1",
+ "description": "Displayed error message for debugging purposes. $1 is the error message"
+ },
+ "errorName": {
+ "message": "Code: $1",
+ "description": "Displayed error name for debugging purposes. $1 is the error name"
+ },
+ "errorPageTitle": {
+ "message": "MetaMask encountered an error",
+ "description": "Title of generic error page"
+ },
+ "errorPageMessage": {
+ "message": "Try again by reloading the page, or contact support at support@metamask.io",
+ "description": "Message displayed on generic error page in the fullscreen or notification UI"
+ },
+ "errorPagePopupMessage": {
+ "message": "Try again by closing and reopening the popup, or contact support at support@metamask.io",
+ "description": "Message displayed on generic error page in the popup UI"
+ },
+ "errorStack": {
+ "message": "Stack:",
+ "description": "Title for error stack, which is displayed for debugging purposes"
+ },
"ethereumPublicAddress": {
"message": "Ethereum Public Address"
},
diff --git a/ui/app/pages/error/error.component.js b/ui/app/pages/error/error.component.js
new file mode 100644
index 000000000..1da1ac609
--- /dev/null
+++ b/ui/app/pages/error/error.component.js
@@ -0,0 +1,74 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import { getEnvironmentType } from '../../../../app/scripts/lib/util'
+import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums'
+
+class ErrorPage extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func.isRequired,
+ }
+
+ static propTypes = {
+ error: PropTypes.object.isRequired,
+ }
+
+ renderErrorDetail (content) {
+ return (
+
+
+ {content}
+
+
+ )
+ }
+
+ renderErrorStack (title, stack) {
+ return (
+
+
+ {title}
+
+
+ {stack}
+
+
+ )
+ }
+
+ render () {
+ const { error } = this.props
+ const { t } = this.context
+
+ const isPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
+
+ return (
+
+
+ {t('errorPageTitle')}
+
+
+ {
+ isPopup
+ ? t('errorPagePopupMessage')
+ : t('errorPageMessage')
+ }
+
+
+
+
+ {t('errorDetails')}
+
+
+ { error.message ? this.renderErrorDetail(t('errorMessage', [error.message])) : null }
+ { error.code ? this.renderErrorDetail(t('errorCode', [error.code])) : null }
+ { error.name ? this.renderErrorDetail(t('errorName', [error.name])) : null }
+ { error.stack ? this.renderErrorStack(t('errorStack'), error.stack) : null }
+
+
+
+
+ )
+ }
+}
+
+export default ErrorPage
diff --git a/ui/app/pages/error/index.js b/ui/app/pages/error/index.js
new file mode 100644
index 000000000..c2a9f62d9
--- /dev/null
+++ b/ui/app/pages/error/index.js
@@ -0,0 +1 @@
+export { default } from './error.component'
diff --git a/ui/app/pages/error/index.scss b/ui/app/pages/error/index.scss
new file mode 100644
index 000000000..6784b374f
--- /dev/null
+++ b/ui/app/pages/error/index.scss
@@ -0,0 +1,41 @@
+.error-page {
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+
+ font-family: Roboto;
+ font-style: normal;
+ font-weight: normal;
+
+ padding: 35px 10px 10px 10px;
+ height: 100%;
+
+ &__header {
+ display: flex;
+ justify-content: center;
+ font-size: 42px;
+ padding: 10px 0;
+ text-align: center;
+ }
+
+ &__subheader {
+ font-size: 19px;
+ padding: 10px 0;
+ width: 100%;
+ max-width: 720px;
+ text-align: center;
+ }
+
+ &__details {
+ font-size: 18px;
+ overflow-y: auto;
+ width: 100%;
+ max-width: 720px;
+ padding-top: 10px;
+ }
+
+ &__stack {
+ overflow-x: auto;
+ background-color: #eee;
+ }
+}
diff --git a/ui/app/pages/index.js b/ui/app/pages/index.js
index 40b2781a8..7de4fb408 100644
--- a/ui/app/pages/index.js
+++ b/ui/app/pages/index.js
@@ -1,25 +1,53 @@
-import React from 'react'
+import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { Provider } from 'react-redux'
import { HashRouter } from 'react-router-dom'
+import * as Sentry from '@sentry/browser'
+import ErrorPage from './error'
import Routes from './routes'
import I18nProvider from '../helpers/higher-order-components/i18n-provider'
import MetaMetricsProvider from '../helpers/higher-order-components/metametrics/metametrics.provider'
-const Index = props => {
- const { store } = props
+class Index extends PureComponent {
+ state = {}
- return (
-
-
-
+ static getDerivedStateFromError (error) {
+ return { error }
+ }
+
+ componentDidCatch (error) {
+ Sentry.captureException(error)
+ }
+
+ render () {
+ const { error, errorId } = this.state
+ const { store } = this.props
+
+ if (error) {
+ return (
+
-
+
-
-
-
- )
+
+ )
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ )
+ }
}
Index.propTypes = {
diff --git a/ui/app/pages/index.scss b/ui/app/pages/index.scss
index deb6bce45..6325e0b0c 100644
--- a/ui/app/pages/index.scss
+++ b/ui/app/pages/index.scss
@@ -2,6 +2,8 @@
@import 'add-token/index';
+@import 'error/index';
+
@import 'send/send';
@import 'confirm-add-token/index';