diff --git a/bigbluebutton-html5/imports/ui/components/error-boundary/component.jsx b/bigbluebutton-html5/imports/ui/components/error-boundary/component.jsx new file mode 100644 index 0000000000000000000000000000000000000000..38d1440e24516f96e2aa4f600e30ba98765322ab --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/error-boundary/component.jsx @@ -0,0 +1,38 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +const propTypes = { + children: PropTypes.element.isRequired, + Fallback: PropTypes.func.isRequired, +}; + +class ErrorBoundary extends Component { + constructor(props) { + super(props); + this.state = { error: false, errorInfo: null }; + } + + componentDidCatch(error, errorInfo) { + this.setState({ + error, + errorInfo, + }); + } + + render() { + const { error } = this.state; + const { children, Fallback } = this.props; + + return (error ? (<Fallback {...this.state} />) : children); + } +} + +ErrorBoundary.propTypes = propTypes; + +export const withErrorBoundary = (WrappedComponent, FallbackComponent) => props => ( + <ErrorBoundary Fallback={FallbackComponent}> + <WrappedComponent {...props} /> + </ErrorBoundary> +); + +export default ErrorBoundary; diff --git a/bigbluebutton-html5/imports/ui/components/fallback-errors/fallback-presentation/component.jsx b/bigbluebutton-html5/imports/ui/components/fallback-errors/fallback-presentation/component.jsx new file mode 100644 index 0000000000000000000000000000000000000000..58524d5277251a6f0e5a6e1db9e652a5008de975 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/fallback-errors/fallback-presentation/component.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { defineMessages, injectIntl } from 'react-intl'; +import Button from '/imports/ui/components/button/component'; +import { styles } from './styles'; + +const intlMessages = defineMessages({ + title: { + id: 'app.error.fallback.presentation.title', + description: 'title for presentation when fallback is showed', + }, + description: { + id: 'app.error.fallback.presentation.description', + description: 'description for presentation when fallback is showed', + }, + reloadButton: { + id: 'app.error.fallback.presentation.reloadButton', + description: 'Button label when fallback is showed', + }, +}); + +const FallbackPresentation = ({ error, intl }) => ( + <div className={styles.background}> + <h1 className={styles.codeError}> + {intl.formatMessage(intlMessages.title)} + </h1> + <h1 className={styles.message}> + {intl.formatMessage(intlMessages.description)} + </h1> + <div className={styles.separator} /> + <div className={styles.sessionMessage}> + {error.message} + </div> + <div> + <Button + size="sm" + color="primary" + className={styles.button} + onClick={() => window.location.reload()} + label={intl.formatMessage(intlMessages.reloadButton)} + /> + </div> + </div> +); + +export default injectIntl(FallbackPresentation); diff --git a/bigbluebutton-html5/imports/ui/components/fallback-errors/fallback-presentation/styles.scss b/bigbluebutton-html5/imports/ui/components/fallback-errors/fallback-presentation/styles.scss new file mode 100644 index 0000000000000000000000000000000000000000..3651c36225a0edd078062c8fb3073f277bff6b13 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/fallback-errors/fallback-presentation/styles.scss @@ -0,0 +1,51 @@ +@import "/imports/ui/stylesheets/variables/palette"; + +.background { + display: flex; + flex-flow: column; + justify-content: center; + width: 100%; + height: 100%; + background-color: var(--color-gray-dark); + color: var(--color-white); + text-align: center; +} + +.icon { + width: 100%; + font-size: 10rem; + margin-bottom: 2rem; +} + +.message { + margin: 0; + color: var(--color-white); + font-size: 1.25rem; + font-weight: 400; +} + +.sessionMessage { + @extend .message; + font-size: var(--font-size-base); + margin-bottom: 1.5rem; +} + +.codeError { + margin: 0; + font-size: 3rem; + color: var(--color-white); +} + +.separator { + height: 0; + width: 5rem; + border: 1px solid var(--color-gray-lighter); + margin: 1.5rem 0 1.5rem 0; + align-self: center; + opacity: .75; +} + +.button { + min-width: 9rem; + height: 2rem; +} diff --git a/bigbluebutton-html5/imports/ui/components/presentation-pod/container.jsx b/bigbluebutton-html5/imports/ui/components/presentation-pod/container.jsx index c9b89d987ccb661bbcf2783878f118bb84d5a62b..e189caaef6155f30e22d12be11d3e54eb9de8d43 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation-pod/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation-pod/container.jsx @@ -1,6 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withTracker } from 'meteor/react-meteor-data'; +import ErrorBoundary from '/imports/ui/components/error-boundary/component'; +import FallbackPresentation from '/imports/ui/components/fallback-errors/fallback-presentation/component'; import PresentationPodService from './service'; import PresentationPods from './component'; @@ -10,7 +12,9 @@ import PresentationPods from './component'; const PresentationPodsContainer = ({ presentationPodIds, ...props }) => { if (presentationPodIds && presentationPodIds.length > 0) { return ( - <PresentationPods presentationPodIds={presentationPodIds} {...props} /> + <ErrorBoundary Fallback={FallbackPresentation}> + <PresentationPods presentationPodIds={presentationPodIds} {...props} /> + </ErrorBoundary> ); } diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index e90322fb11a44a6ae38ecd1de25caf42f0242871..42ff8f458578a1dd97ea999895595c30f3f5c9e0 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -393,6 +393,9 @@ "app.error.403": "Forbidden", "app.error.400": "Bad Request", "app.error.leaveLabel": "Log in again", + "app.error.fallback.presentation.title": "An error occured", + "app.error.fallback.presentation.description": "It has been logged. Please try reloading the page.", + "app.error.fallback.presentation.reloadButton": "Reload", "app.guest.waiting": "Waiting for approval to join", "app.userList.guest.waitingUsers": "Waiting Users", "app.userList.guest.waitingUsersTitle": "User Management",