From 898d54cd0034bbb2727bc5b5eaf9d5a4f2a852de Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Sun, 9 Dec 2018 13:24:17 +0100 Subject: Add React 16 didCatch/ErrorBoundary component --- src/components/layout/AppLayout.js | 134 +++++++++++----------- src/components/services/content/ServiceWebview.js | 1 + src/components/settings/SettingsLayout.js | 21 ++-- src/components/util/ErrorBoundary/index.js | 54 +++++++++ src/components/util/ErrorBoundary/styles.js | 13 +++ src/containers/settings/AccountScreen.js | 35 +++--- src/containers/settings/EditServiceScreen.js | 32 +++--- src/containers/settings/EditSettingsScreen.js | 31 ++--- src/containers/settings/EditUserScreen.js | 20 ++-- src/containers/settings/InviteScreen.js | 16 ++- src/containers/settings/RecipesScreen.js | 29 ++--- src/containers/settings/ServicesScreen.js | 31 ++--- src/containers/settings/SettingsWindow.js | 15 ++- src/i18n/locales/en-US.json | 4 +- src/theme/dark/index.js | 2 + src/theme/default/index.js | 2 + 16 files changed, 274 insertions(+), 166 deletions(-) create mode 100644 src/components/util/ErrorBoundary/index.js create mode 100644 src/components/util/ErrorBoundary/styles.js (limited to 'src') diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index 3ababe54a..e526f6b1f 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js @@ -6,6 +6,8 @@ import { TitleBar } from 'electron-react-titlebar'; import InfoBar from '../ui/InfoBar'; import { Component as DelayApp } from '../../features/delayApp'; +import ErrorBoundary from '../util/ErrorBoundary'; + import globalMessages from '../../i18n/globalMessages'; import { isWindows } from '../../environment'; @@ -94,74 +96,76 @@ export default @observer class AppLayout extends Component { const { intl } = this.context; return ( -
-
- {isWindows && !isFullScreen && } -
- {sidebar} -
- {news.length > 0 && news.map(item => ( - removeNewsItem({ newsId: item.id })} - > - - - ))} - {!isOnline && ( - - - {intl.formatMessage(globalMessages.notConnectedToTheInternet)} - - )} - {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( - - - {intl.formatMessage(messages.requiredRequestsFailed)} - - )} - {showServicesUpdatedInfoBar && ( - - - {intl.formatMessage(messages.servicesUpdated)} - - )} - {appUpdateIsDownloaded && ( - - - {intl.formatMessage(messages.updateAvailable)} - {intl.formatMessage(messages.changelog)} - - - )} - {isDelayAppScreenVisible && ()} - {services} + +
+
+ {isWindows && !isFullScreen && } +
+ {sidebar} +
+ {news.length > 0 && news.map(item => ( + removeNewsItem({ newsId: item.id })} + > + + + ))} + {!isOnline && ( + + + {intl.formatMessage(globalMessages.notConnectedToTheInternet)} + + )} + {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( + + + {intl.formatMessage(messages.requiredRequestsFailed)} + + )} + {showServicesUpdatedInfoBar && ( + + + {intl.formatMessage(messages.servicesUpdated)} + + )} + {appUpdateIsDownloaded && ( + + + {intl.formatMessage(messages.updateAvailable)} + {intl.formatMessage(messages.changelog)} + + + )} + {isDelayAppScreenVisible && ()} + {services} +
+ {children}
- {children} -
+ ); } } diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js index f59205c0e..98daf9b9f 100644 --- a/src/components/services/content/ServiceWebview.js +++ b/src/components/services/content/ServiceWebview.js @@ -59,6 +59,7 @@ export default @observer class ServiceWebview extends Component { } autorunDisposer = null; + webview = null; render() { diff --git a/src/components/settings/SettingsLayout.js b/src/components/settings/SettingsLayout.js index 3cb08feb1..d5d8f0bb0 100644 --- a/src/components/settings/SettingsLayout.js +++ b/src/components/settings/SettingsLayout.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { observer } from 'mobx-react'; +import ErrorBoundary from '../util/ErrorBoundary'; import { oneOrManyChildElements } from '../../prop-types'; import Appear from '../ui/effects/Appear'; @@ -36,18 +37,20 @@ export default @observer class SettingsLayout extends Component { return (
-
+
+ {navigation} + {children} +
+
); diff --git a/src/components/util/ErrorBoundary/index.js b/src/components/util/ErrorBoundary/index.js new file mode 100644 index 000000000..def01c74f --- /dev/null +++ b/src/components/util/ErrorBoundary/index.js @@ -0,0 +1,54 @@ +import React, { Component } from 'react'; +import injectSheet from 'react-jss'; +import { defineMessages, intlShape } from 'react-intl'; + +import Button from '../../ui/Button'; + +import styles from './styles'; + +const messages = defineMessages({ + headline: { + id: 'app.errorHandler.headline', + defaultMessage: '!!!Something went wrong.', + }, + action: { + id: 'app.errorHandler.action', + defaultMessage: '!!!Reload', + }, +}); + +export default @injectSheet(styles) class ErrorBoundary extends Component { + state = { + hasError: false, + } + + static contextTypes = { + intl: intlShape, + }; + + componentDidCatch(error, info) { + this.setState({ hasError: true }); + } + + render() { + const { classes } = this.props; + const { intl } = this.context; + + if (this.state.hasError) { + return ( +
+

+ {intl.formatMessage(messages.headline)} +

+
+ ); + } + + return this.props.children; + } +} \ No newline at end of file diff --git a/src/components/util/ErrorBoundary/styles.js b/src/components/util/ErrorBoundary/styles.js new file mode 100644 index 000000000..8d62767f6 --- /dev/null +++ b/src/components/util/ErrorBoundary/styles.js @@ -0,0 +1,13 @@ +export default (theme) => ({ + component: { + display: 'flex', + width: '100%', + alignItems: 'center', + justifyContent: 'center', + flexDirection: 'column', + }, + title: { + fontSize: 20, + color: theme.colorText, + } +}); diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js index 5818af0b1..019b3d7d6 100644 --- a/src/containers/settings/AccountScreen.js +++ b/src/containers/settings/AccountScreen.js @@ -9,6 +9,7 @@ import AppStore from '../../stores/AppStore'; import { gaPage } from '../../lib/analytics'; import AccountDashboard from '../../components/settings/account/AccountDashboard'; +import ErrorBoundary from '../../components/util/ErrorBoundary'; const { BrowserWindow } = remote; @@ -67,22 +68,24 @@ export default @inject('stores', 'actions') @observer class AccountScreen extend const isLoadingPlans = payment.plansRequest.isExecuting; return ( - this.reloadData()} - isCreatingPaymentDashboardUrl={payment.createDashboardUrlRequest.isExecuting} - openDashboard={price => this.handlePaymentDashboard(price)} - openExternalUrl={url => openExternalUrl({ url })} - onCloseSubscriptionWindow={() => this.onCloseWindow()} - deleteAccount={userActions.delete} - isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} - isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} - /> + + this.reloadData()} + isCreatingPaymentDashboardUrl={payment.createDashboardUrlRequest.isExecuting} + openDashboard={price => this.handlePaymentDashboard(price)} + openExternalUrl={url => openExternalUrl({ url })} + onCloseSubscriptionWindow={() => this.onCloseWindow()} + deleteAccount={userActions.delete} + isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} + isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} + /> + ); } } diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js index f4915f68b..f0b7268d6 100644 --- a/src/containers/settings/EditServiceScreen.js +++ b/src/containers/settings/EditServiceScreen.js @@ -13,6 +13,8 @@ import { gaPage } from '../../lib/analytics'; import ServiceError from '../../components/settings/services/ServiceError'; import EditServiceForm from '../../components/settings/services/EditServiceForm'; +import ErrorBoundary from '../../components/util/ErrorBoundary'; + import { required, url, oneRequired } from '../../helpers/validation-helpers'; import { getSelectOptions } from '../../helpers/i18n-helpers'; @@ -302,20 +304,22 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex const form = this.prepareForm(recipe, service, proxyFeature); return ( - this.onSubmit(d)} - onDelete={() => this.deleteService()} - isProxyFeatureEnabled={proxyFeature.isEnabled} - isProxyFeaturePremiumFeature={proxyFeature.isPremium} - /> + + this.onSubmit(d)} + onDelete={() => this.deleteService()} + isProxyFeatureEnabled={proxyFeature.isEnabled} + isProxyFeaturePremiumFeature={proxyFeature.isPremium} + /> + ); } } diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js index 350bd9f8a..f1706a721 100644 --- a/src/containers/settings/EditSettingsScreen.js +++ b/src/containers/settings/EditSettingsScreen.js @@ -16,6 +16,7 @@ import { getSelectOptions } from '../../helpers/i18n-helpers'; import EditSettingsForm from '../../components/settings/settings/EditSettingsForm'; +import ErrorBoundary from '../../components/util/ErrorBoundary'; const messages = defineMessages({ autoLaunchOnStart: { @@ -216,20 +217,22 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e const form = this.prepareForm(); return ( - this.onSubmit(d)} - cacheSize={cacheSize} - isClearingAllCache={isClearingAllCache} - onClearAllCache={clearAllCache} - isSpellcheckerPremiumFeature={spellcheckerConfig.isPremiumFeature} - /> + + this.onSubmit(d)} + cacheSize={cacheSize} + isClearingAllCache={isClearingAllCache} + onClearAllCache={clearAllCache} + isSpellcheckerPremiumFeature={spellcheckerConfig.isPremiumFeature} + /> + ); } } diff --git a/src/containers/settings/EditUserScreen.js b/src/containers/settings/EditUserScreen.js index 3da3e8d2c..adad8a9bd 100644 --- a/src/containers/settings/EditUserScreen.js +++ b/src/containers/settings/EditUserScreen.js @@ -6,6 +6,8 @@ import { defineMessages, intlShape } from 'react-intl'; import UserStore from '../../stores/UserStore'; import Form from '../../lib/Form'; import EditUserForm from '../../components/settings/user/EditUserForm'; +import ErrorBoundary from '../../components/util/ErrorBoundary'; + import { required, email, minLength } from '../../helpers/validation-helpers'; import { gaPage } from '../../lib/analytics'; @@ -140,14 +142,16 @@ export default @inject('stores', 'actions') @observer class EditUserScreen exten const form = this.prepareForm(user.data); return ( - this.onSubmit(d)} - /> + + this.onSubmit(d)} + /> + ); } } diff --git a/src/containers/settings/InviteScreen.js b/src/containers/settings/InviteScreen.js index 38ca6ec74..cd36610e4 100644 --- a/src/containers/settings/InviteScreen.js +++ b/src/containers/settings/InviteScreen.js @@ -3,6 +3,8 @@ import PropTypes from 'prop-types'; import { inject, observer } from 'mobx-react'; import Invite from '../../components/auth/Invite'; +import ErrorBoundary from '../../components/util/ErrorBoundary'; + import { gaPage } from '../../lib/analytics'; export default @inject('stores', 'actions') @observer class InviteScreen extends Component { @@ -19,12 +21,14 @@ export default @inject('stores', 'actions') @observer class InviteScreen extends const { user } = this.props.stores; return ( - + + + ); } } diff --git a/src/containers/settings/RecipesScreen.js b/src/containers/settings/RecipesScreen.js index b125e6a05..1f05b6510 100644 --- a/src/containers/settings/RecipesScreen.js +++ b/src/containers/settings/RecipesScreen.js @@ -10,6 +10,7 @@ import UserStore from '../../stores/UserStore'; import { gaPage } from '../../lib/analytics'; import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard'; +import ErrorBoundary from '../../components/util/ErrorBoundary'; export default @inject('stores', 'actions') @observer class RecipesScreen extends Component { static propTypes = { @@ -93,19 +94,21 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend || recipePreviews.searchRecipePreviewsRequest.isExecuting; return ( - this.searchRecipes(e)} - resetSearch={() => this.resetSearch()} - searchNeedle={this.state.needle} - serviceStatus={services.actionStatus} - devRecipesCount={recipePreviews.dev.length} - /> + + this.searchRecipes(e)} + resetSearch={() => this.resetSearch()} + searchNeedle={this.state.needle} + serviceStatus={services.actionStatus} + devRecipesCount={recipePreviews.dev.length} + /> + ); } } diff --git a/src/containers/settings/ServicesScreen.js b/src/containers/settings/ServicesScreen.js index c1a133ef7..b70a5506e 100644 --- a/src/containers/settings/ServicesScreen.js +++ b/src/containers/settings/ServicesScreen.js @@ -9,6 +9,7 @@ import ServiceStore from '../../stores/ServicesStore'; import { gaPage } from '../../lib/analytics'; import ServicesDashboard from '../../components/settings/services/ServicesDashboard'; +import ErrorBoundary from '../../components/util/ErrorBoundary'; export default @inject('stores', 'actions') @observer class ServicesScreen extends Component { componentDidMount() { @@ -40,20 +41,22 @@ export default @inject('stores', 'actions') @observer class ServicesScreen exten } return ( - this.deleteService()} - toggleService={toggleService} - isLoading={isLoading} - filterServices={filter} - resetFilter={resetFilter} - goTo={router.push} - servicesRequestFailed={services.allServicesRequest.wasExecuted && services.allServicesRequest.isError} - retryServicesRequest={() => services.allServicesRequest.reload()} - searchNeedle={services.filterNeedle} - /> + + this.deleteService()} + toggleService={toggleService} + isLoading={isLoading} + filterServices={filter} + resetFilter={resetFilter} + goTo={router.push} + servicesRequestFailed={services.allServicesRequest.wasExecuted && services.allServicesRequest.isError} + retryServicesRequest={() => services.allServicesRequest.reload()} + searchNeedle={services.filterNeedle} + /> + ); } } diff --git a/src/containers/settings/SettingsWindow.js b/src/containers/settings/SettingsWindow.js index 55589d0be..6d9e0ee77 100644 --- a/src/containers/settings/SettingsWindow.js +++ b/src/containers/settings/SettingsWindow.js @@ -6,6 +6,7 @@ import ServicesStore from '../../stores/ServicesStore'; import Layout from '../../components/settings/SettingsLayout'; import Navigation from '../../components/settings/navigation/SettingsNavigation'; +import ErrorBoundary from '../../components/util/ErrorBoundary'; export default @inject('stores', 'actions') @observer class SettingsContainer extends Component { render() { @@ -19,12 +20,14 @@ export default @inject('stores', 'actions') @observer class SettingsContainer ex ); return ( - - {children} - + + + {children} + + ); } } diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 83eff2fc8..356eaae66 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -269,5 +269,7 @@ "feature.delayApp.headline": "Please purchase a Franz Supporter License to skip waiting", "feature.delayApp.action": "Get a Franz Supporter License", "feature.delayApp.text": "Franz will continue in {seconds} seconds.", - "premiumFeature.button.upgradeAccount": "Upgrade account" + "premiumFeature.button.upgradeAccount": "Upgrade account", + "app.errorHandler.headline": "Something went wrong", + "app.errorHandler.action": "Reload" } diff --git a/src/theme/dark/index.js b/src/theme/dark/index.js index 496a51119..5aa64a902 100644 --- a/src/theme/dark/index.js +++ b/src/theme/dark/index.js @@ -4,3 +4,5 @@ export const colorBackground = legacyStyles.darkThemeGrayDarkest; export const colorBackgroundSubscriptionContainer = legacyStyles.themeBrandInfo; export const colorHeadline = legacyStyles.darkThemeTextColor; +export const colorText = legacyStyles.darkThemeTextColor; + diff --git a/src/theme/default/index.js b/src/theme/default/index.js index 8766fb609..e67f2ba58 100644 --- a/src/theme/default/index.js +++ b/src/theme/default/index.js @@ -12,6 +12,8 @@ export const borderRadiusSmall = legacyStyles.themeBorderRadiusSmall; export const colorBackground = legacyStyles.themeGrayLighter; export const colorHeadline = legacyStyles.themeGrayDark; +export const colorText = legacyStyles.themeTextColor; + // Subscription Container Component export const colorSubscriptionContainerBackground = 'none'; export const colorSubscriptionContainerBorder = [1, 'solid', brandPrimary]; -- cgit v1.2.3-70-g09d2