diff --git a/.env b/.env
index 9cf725202..cc1811743 100644
--- a/.env
+++ b/.env
@@ -1,5 +1,6 @@
# ViteJS based
VITE_VERSION=latest
+VITE_MUTE_ERROR_BOUNDARY_LOG=false
VITE_BASE_API_URL=http://localhost:8000/api/v2/
VITE_FORM_ID=93c09209-5fb9-4105-b6bb-9d9f0aa6782c
VITE_USE_HASH_ROUTING=false
diff --git a/src/components/Errors/ErrorBoundary.jsx b/src/components/Errors/ErrorBoundary.jsx
index d7786c038..8eb62f127 100644
--- a/src/components/Errors/ErrorBoundary.jsx
+++ b/src/components/Errors/ErrorBoundary.jsx
@@ -1,9 +1,12 @@
+import * as Sentry from '@sentry/react';
+import {getEnv} from 'env.mjs';
import PropTypes from 'prop-types';
import React from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import Body from 'components/Body';
import Card from 'components/Card';
+import FormUnavailable from 'components/Errors/FormUnavailable';
import FormMaximumSubmissions from 'components/FormMaximumSubmissions';
import Link from 'components/Link';
import MaintenanceMode from 'components/MaintenanceMode';
@@ -13,7 +16,12 @@ import {DEBUG} from 'utils';
import ErrorMessage from './ErrorMessage';
const logError = (error, errorInfo) => {
- if (DEBUG) console.error(error, errorInfo);
+ if (DEBUG) {
+ const muteConsole = getEnv('MUTE_ERROR_BOUNDARY_LOG');
+ if (!muteConsole) console.error(error, errorInfo);
+ } else {
+ Sentry.captureException(error);
+ }
};
class ErrorBoundary extends React.Component {
@@ -30,7 +38,6 @@ class ErrorBoundary extends React.Component {
}
componentDidCatch(error, errorInfo) {
- // TODO: depending on the error type, send to sentry?
logError(error, errorInfo);
}
@@ -139,13 +146,9 @@ const UnprocessableEntityError = ({wrapper: Wrapper, error}) => {
UnprocessableEntityError.propTypes = GenericError.propTypes;
const ServiceUnavailableError = ({wrapper: Wrapper, error}) => {
- if (!['form-maintenance', 'form-maximum-submissions'].includes(error.code)) {
- return ;
- }
-
- // handle maintenance mode forms
- if (error.code === 'form-maintenance') {
- return (
+ const defaultComponent = ;
+ const componentMapping = {
+ 'form-maintenance': (
{
/>
}
/>
- );
- }
+ ),
+ 'form-maximum-submissions': ,
+ service_unavailable: ,
+ };
- // handle submission limit forms
- if (error.code === 'form-maximum-submissions') {
- return ;
- }
+ return componentMapping[error.code] || defaultComponent;
};
// map the error class to the component to render it
diff --git a/src/components/Errors/ErrorBoundary.stories.jsx b/src/components/Errors/ErrorBoundary.stories.jsx
new file mode 100644
index 000000000..e9d1a24ea
--- /dev/null
+++ b/src/components/Errors/ErrorBoundary.stories.jsx
@@ -0,0 +1,103 @@
+import {MemoryRouter} from 'react-router';
+
+import {PermissionDenied, ServiceUnavailable, UnprocessableEntity} from 'errors';
+
+import ErrorBoundary from './ErrorBoundary';
+
+const Nested = ({error}) => {
+ throw error;
+};
+
+const render = ({useCard, errorType, errorCode}) => {
+ const error = new errorType('some error', 500, 'some error', errorCode);
+ return (
+
+
+
+ );
+};
+
+export default {
+ title: 'Private API / ErrorBoundary',
+ component: ErrorBoundary,
+ render,
+ argTypes: {
+ useCard: {control: {type: 'boolean'}},
+ errorType: {
+ table: {
+ options: [PermissionDenied, ServiceUnavailable, UnprocessableEntity],
+ control: {type: 'radio'},
+ },
+ },
+ },
+};
+
+export const GenericError = {
+ args: {
+ useCard: true,
+ errorType: Error,
+ errorCode: 'generic',
+ },
+};
+
+export const PermissionDeniedError = {
+ decorators: [
+ Story => (
+
+
+
+ ),
+ ],
+ args: {
+ useCard: true,
+ errorType: PermissionDenied,
+ },
+};
+
+export const UnprocessableEntityErrorInactive = {
+ args: {
+ useCard: true,
+ errorType: UnprocessableEntity,
+ errorCode: 'form-inactive',
+ },
+};
+
+export const UnprocessableEntityErrorGeneric = {
+ args: {
+ useCard: true,
+ errorType: UnprocessableEntity,
+ errorCode: 'generic',
+ },
+};
+
+export const ServiceUnavailableErrorMaintenance = {
+ args: {
+ useCard: true,
+ errorType: ServiceUnavailable,
+ errorCode: 'form-maintenance',
+ },
+};
+
+export const ServiceUnavailableErrorMaxSubmissions = {
+ args: {
+ useCard: true,
+ errorType: ServiceUnavailable,
+ errorCode: 'form-maximum-submissions',
+ },
+};
+
+export const ServiceUnavailableError = {
+ args: {
+ useCard: true,
+ errorType: ServiceUnavailable,
+ errorCode: 'service_unavailable',
+ },
+};
+
+export const ServiceUnavailableErrorGeneric = {
+ args: {
+ useCard: true,
+ errorType: ServiceUnavailable,
+ errorCode: 'generic',
+ },
+};
diff --git a/src/components/Errors/FormUnavailable.jsx b/src/components/Errors/FormUnavailable.jsx
new file mode 100644
index 000000000..17c9e6a18
--- /dev/null
+++ b/src/components/Errors/FormUnavailable.jsx
@@ -0,0 +1,24 @@
+import {FormattedMessage, useIntl} from 'react-intl';
+
+import ErrorMessage from 'components/Errors/ErrorMessage';
+
+const FormUnavailable = ({wrapper: Wrapper}) => {
+ const intl = useIntl();
+ // Wrapper may be a DOM element, which can't handle
+ const title = intl.formatMessage({
+ description: 'Open Forms service unavailable error title',
+ defaultMessage: 'Form unavailable',
+ });
+ return (
+
+
+
+
+
+ );
+};
+
+export default FormUnavailable;