aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/components/layout/AppLayout.js134
-rw-r--r--src/components/services/content/ServiceWebview.js1
-rw-r--r--src/components/settings/SettingsLayout.js21
-rw-r--r--src/components/util/ErrorBoundary/index.js54
-rw-r--r--src/components/util/ErrorBoundary/styles.js13
-rw-r--r--src/containers/settings/AccountScreen.js35
-rw-r--r--src/containers/settings/EditServiceScreen.js32
-rw-r--r--src/containers/settings/EditSettingsScreen.js31
-rw-r--r--src/containers/settings/EditUserScreen.js20
-rw-r--r--src/containers/settings/InviteScreen.js16
-rw-r--r--src/containers/settings/RecipesScreen.js29
-rw-r--r--src/containers/settings/ServicesScreen.js31
-rw-r--r--src/containers/settings/SettingsWindow.js15
-rw-r--r--src/i18n/locales/en-US.json4
-rw-r--r--src/theme/dark/index.js2
-rw-r--r--src/theme/default/index.js2
16 files changed, 274 insertions, 166 deletions
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';
6 6
7import InfoBar from '../ui/InfoBar'; 7import InfoBar from '../ui/InfoBar';
8import { Component as DelayApp } from '../../features/delayApp'; 8import { Component as DelayApp } from '../../features/delayApp';
9import ErrorBoundary from '../util/ErrorBoundary';
10
9import globalMessages from '../../i18n/globalMessages'; 11import globalMessages from '../../i18n/globalMessages';
10 12
11import { isWindows } from '../../environment'; 13import { isWindows } from '../../environment';
@@ -94,74 +96,76 @@ export default @observer class AppLayout extends Component {
94 const { intl } = this.context; 96 const { intl } = this.context;
95 97
96 return ( 98 return (
97 <div className={(darkMode ? 'theme__dark' : '')}> 99 <ErrorBoundary>
98 <div className="app"> 100 <div className={(darkMode ? 'theme__dark' : '')}>
99 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon={'assets/images/logo.svg'} />} 101 <div className="app">
100 <div className="app__content"> 102 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon={'assets/images/logo.svg'} />}
101 {sidebar} 103 <div className="app__content">
102 <div className="app__service"> 104 {sidebar}
103 {news.length > 0 && news.map(item => ( 105 <div className="app__service">
104 <InfoBar 106 {news.length > 0 && news.map(item => (
105 key={item.id} 107 <InfoBar
106 position="top" 108 key={item.id}
107 type={item.type} 109 position="top"
108 sticky={item.sticky} 110 type={item.type}
109 onHide={() => removeNewsItem({ newsId: item.id })} 111 sticky={item.sticky}
110 > 112 onHide={() => removeNewsItem({ newsId: item.id })}
111 <span dangerouslySetInnerHTML={createMarkup(item.message)} /> 113 >
112 </InfoBar> 114 <span dangerouslySetInnerHTML={createMarkup(item.message)} />
113 ))} 115 </InfoBar>
114 {!isOnline && ( 116 ))}
115 <InfoBar 117 {!isOnline && (
116 type="danger" 118 <InfoBar
117 > 119 type="danger"
118 <span className="mdi mdi-flash" /> 120 >
119 {intl.formatMessage(globalMessages.notConnectedToTheInternet)} 121 <span className="mdi mdi-flash" />
120 </InfoBar> 122 {intl.formatMessage(globalMessages.notConnectedToTheInternet)}
121 )} 123 </InfoBar>
122 {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( 124 )}
123 <InfoBar 125 {!areRequiredRequestsSuccessful && showRequiredRequestsError && (
124 type="danger" 126 <InfoBar
125 ctaLabel="Try again" 127 type="danger"
126 ctaLoading={areRequiredRequestsLoading} 128 ctaLabel="Try again"
127 sticky 129 ctaLoading={areRequiredRequestsLoading}
128 onClick={retryRequiredRequests} 130 sticky
129 > 131 onClick={retryRequiredRequests}
130 <span className="mdi mdi-flash" /> 132 >
131 {intl.formatMessage(messages.requiredRequestsFailed)} 133 <span className="mdi mdi-flash" />
132 </InfoBar> 134 {intl.formatMessage(messages.requiredRequestsFailed)}
133 )} 135 </InfoBar>
134 {showServicesUpdatedInfoBar && ( 136 )}
135 <InfoBar 137 {showServicesUpdatedInfoBar && (
136 type="primary" 138 <InfoBar
137 ctaLabel={intl.formatMessage(messages.buttonReloadServices)} 139 type="primary"
138 onClick={reloadServicesAfterUpdate} 140 ctaLabel={intl.formatMessage(messages.buttonReloadServices)}
139 sticky 141 onClick={reloadServicesAfterUpdate}
140 > 142 sticky
141 <span className="mdi mdi-power-plug" /> 143 >
142 {intl.formatMessage(messages.servicesUpdated)} 144 <span className="mdi mdi-power-plug" />
143 </InfoBar> 145 {intl.formatMessage(messages.servicesUpdated)}
144 )} 146 </InfoBar>
145 {appUpdateIsDownloaded && ( 147 )}
146 <InfoBar 148 {appUpdateIsDownloaded && (
147 type="primary" 149 <InfoBar
148 ctaLabel={intl.formatMessage(messages.buttonInstallUpdate)} 150 type="primary"
149 onClick={installAppUpdate} 151 ctaLabel={intl.formatMessage(messages.buttonInstallUpdate)}
150 sticky 152 onClick={installAppUpdate}
151 > 153 sticky
152 <span className="mdi mdi-information" /> 154 >
153 {intl.formatMessage(messages.updateAvailable)} <a href="https://meetfranz.com/changelog" target="_blank"> 155 <span className="mdi mdi-information" />
154 <u>{intl.formatMessage(messages.changelog)}</u> 156 {intl.formatMessage(messages.updateAvailable)} <a href="https://meetfranz.com/changelog" target="_blank">
155 </a> 157 <u>{intl.formatMessage(messages.changelog)}</u>
156 </InfoBar> 158 </a>
157 )} 159 </InfoBar>
158 {isDelayAppScreenVisible && (<DelayApp />)} 160 )}
159 {services} 161 {isDelayAppScreenVisible && (<DelayApp />)}
162 {services}
163 </div>
160 </div> 164 </div>
161 </div> 165 </div>
166 {children}
162 </div> 167 </div>
163 {children} 168 </ErrorBoundary>
164 </div>
165 ); 169 );
166 } 170 }
167} 171}
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 {
59 } 59 }
60 60
61 autorunDisposer = null; 61 autorunDisposer = null;
62
62 webview = null; 63 webview = null;
63 64
64 render() { 65 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';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4 4
5import ErrorBoundary from '../util/ErrorBoundary';
5import { oneOrManyChildElements } from '../../prop-types'; 6import { oneOrManyChildElements } from '../../prop-types';
6import Appear from '../ui/effects/Appear'; 7import Appear from '../ui/effects/Appear';
7 8
@@ -36,18 +37,20 @@ export default @observer class SettingsLayout extends Component {
36 return ( 37 return (
37 <Appear transitionName="fadeIn-fast"> 38 <Appear transitionName="fadeIn-fast">
38 <div className="settings-wrapper"> 39 <div className="settings-wrapper">
39 <button 40 <ErrorBoundary>
40 className="settings-wrapper__action"
41 onClick={closeSettings}
42 />
43 <div className="settings franz-form">
44 {navigation}
45 {children}
46 <button 41 <button
47 className="settings__close mdi mdi-close" 42 className="settings-wrapper__action"
48 onClick={closeSettings} 43 onClick={closeSettings}
49 /> 44 />
50 </div> 45 <div className="settings franz-form">
46 {navigation}
47 {children}
48 <button
49 className="settings__close mdi mdi-close"
50 onClick={closeSettings}
51 />
52 </div>
53 </ErrorBoundary>
51 </div> 54 </div>
52 </Appear> 55 </Appear>
53 ); 56 );
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 @@
1import React, { Component } from 'react';
2import injectSheet from 'react-jss';
3import { defineMessages, intlShape } from 'react-intl';
4
5import Button from '../../ui/Button';
6
7import styles from './styles';
8
9const messages = defineMessages({
10 headline: {
11 id: 'app.errorHandler.headline',
12 defaultMessage: '!!!Something went wrong.',
13 },
14 action: {
15 id: 'app.errorHandler.action',
16 defaultMessage: '!!!Reload',
17 },
18});
19
20export default @injectSheet(styles) class ErrorBoundary extends Component {
21 state = {
22 hasError: false,
23 }
24
25 static contextTypes = {
26 intl: intlShape,
27 };
28
29 componentDidCatch(error, info) {
30 this.setState({ hasError: true });
31 }
32
33 render() {
34 const { classes } = this.props;
35 const { intl } = this.context;
36
37 if (this.state.hasError) {
38 return (
39 <div className={classes.component}>
40 <h1 className={classes.title}>
41 {intl.formatMessage(messages.headline)}
42 </h1>
43 <Button
44 label={intl.formatMessage(messages.action)}
45 buttonType="inverted"
46 onClick={() => location.reload()}
47 />
48 </div>
49 );
50 }
51
52 return this.props.children;
53 }
54} \ 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 @@
1export default (theme) => ({
2 component: {
3 display: 'flex',
4 width: '100%',
5 alignItems: 'center',
6 justifyContent: 'center',
7 flexDirection: 'column',
8 },
9 title: {
10 fontSize: 20,
11 color: theme.colorText,
12 }
13});
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';
9import { gaPage } from '../../lib/analytics'; 9import { gaPage } from '../../lib/analytics';
10 10
11import AccountDashboard from '../../components/settings/account/AccountDashboard'; 11import AccountDashboard from '../../components/settings/account/AccountDashboard';
12import ErrorBoundary from '../../components/util/ErrorBoundary';
12 13
13const { BrowserWindow } = remote; 14const { BrowserWindow } = remote;
14 15
@@ -67,22 +68,24 @@ export default @inject('stores', 'actions') @observer class AccountScreen extend
67 const isLoadingPlans = payment.plansRequest.isExecuting; 68 const isLoadingPlans = payment.plansRequest.isExecuting;
68 69
69 return ( 70 return (
70 <AccountDashboard 71 <ErrorBoundary>
71 user={user.data} 72 <AccountDashboard
72 orders={payment.orders} 73 user={user.data}
73 isLoading={isLoadingUserInfo} 74 orders={payment.orders}
74 isLoadingOrdersInfo={isLoadingOrdersInfo} 75 isLoading={isLoadingUserInfo}
75 isLoadingPlans={isLoadingPlans} 76 isLoadingOrdersInfo={isLoadingOrdersInfo}
76 userInfoRequestFailed={user.getUserInfoRequest.wasExecuted && user.getUserInfoRequest.isError} 77 isLoadingPlans={isLoadingPlans}
77 retryUserInfoRequest={() => this.reloadData()} 78 userInfoRequestFailed={user.getUserInfoRequest.wasExecuted && user.getUserInfoRequest.isError}
78 isCreatingPaymentDashboardUrl={payment.createDashboardUrlRequest.isExecuting} 79 retryUserInfoRequest={() => this.reloadData()}
79 openDashboard={price => this.handlePaymentDashboard(price)} 80 isCreatingPaymentDashboardUrl={payment.createDashboardUrlRequest.isExecuting}
80 openExternalUrl={url => openExternalUrl({ url })} 81 openDashboard={price => this.handlePaymentDashboard(price)}
81 onCloseSubscriptionWindow={() => this.onCloseWindow()} 82 openExternalUrl={url => openExternalUrl({ url })}
82 deleteAccount={userActions.delete} 83 onCloseSubscriptionWindow={() => this.onCloseWindow()}
83 isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} 84 deleteAccount={userActions.delete}
84 isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} 85 isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting}
85 /> 86 isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError}
87 />
88 </ErrorBoundary>
86 ); 89 );
87 } 90 }
88} 91}
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';
13 13
14import ServiceError from '../../components/settings/services/ServiceError'; 14import ServiceError from '../../components/settings/services/ServiceError';
15import EditServiceForm from '../../components/settings/services/EditServiceForm'; 15import EditServiceForm from '../../components/settings/services/EditServiceForm';
16import ErrorBoundary from '../../components/util/ErrorBoundary';
17
16import { required, url, oneRequired } from '../../helpers/validation-helpers'; 18import { required, url, oneRequired } from '../../helpers/validation-helpers';
17import { getSelectOptions } from '../../helpers/i18n-helpers'; 19import { getSelectOptions } from '../../helpers/i18n-helpers';
18 20
@@ -302,20 +304,22 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
302 const form = this.prepareForm(recipe, service, proxyFeature); 304 const form = this.prepareForm(recipe, service, proxyFeature);
303 305
304 return ( 306 return (
305 <EditServiceForm 307 <ErrorBoundary>
306 action={action} 308 <EditServiceForm
307 recipe={recipe} 309 action={action}
308 service={service} 310 recipe={recipe}
309 user={user.data} 311 service={service}
310 form={form} 312 user={user.data}
311 status={services.actionStatus} 313 form={form}
312 isSaving={services.updateServiceRequest.isExecuting || services.createServiceRequest.isExecuting} 314 status={services.actionStatus}
313 isDeleting={services.deleteServiceRequest.isExecuting} 315 isSaving={services.updateServiceRequest.isExecuting || services.createServiceRequest.isExecuting}
314 onSubmit={d => this.onSubmit(d)} 316 isDeleting={services.deleteServiceRequest.isExecuting}
315 onDelete={() => this.deleteService()} 317 onSubmit={d => this.onSubmit(d)}
316 isProxyFeatureEnabled={proxyFeature.isEnabled} 318 onDelete={() => this.deleteService()}
317 isProxyFeaturePremiumFeature={proxyFeature.isPremium} 319 isProxyFeatureEnabled={proxyFeature.isEnabled}
318 /> 320 isProxyFeaturePremiumFeature={proxyFeature.isPremium}
321 />
322 </ErrorBoundary>
319 ); 323 );
320 } 324 }
321} 325}
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';
16 16
17 17
18import EditSettingsForm from '../../components/settings/settings/EditSettingsForm'; 18import EditSettingsForm from '../../components/settings/settings/EditSettingsForm';
19import ErrorBoundary from '../../components/util/ErrorBoundary';
19 20
20const messages = defineMessages({ 21const messages = defineMessages({
21 autoLaunchOnStart: { 22 autoLaunchOnStart: {
@@ -216,20 +217,22 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
216 const form = this.prepareForm(); 217 const form = this.prepareForm();
217 218
218 return ( 219 return (
219 <EditSettingsForm 220 <ErrorBoundary>
220 form={form} 221 <EditSettingsForm
221 checkForUpdates={checkForUpdates} 222 form={form}
222 installUpdate={installUpdate} 223 checkForUpdates={checkForUpdates}
223 isCheckingForUpdates={updateStatus === updateStatusTypes.CHECKING} 224 installUpdate={installUpdate}
224 isUpdateAvailable={updateStatus === updateStatusTypes.AVAILABLE} 225 isCheckingForUpdates={updateStatus === updateStatusTypes.CHECKING}
225 noUpdateAvailable={updateStatus === updateStatusTypes.NOT_AVAILABLE} 226 isUpdateAvailable={updateStatus === updateStatusTypes.AVAILABLE}
226 updateIsReadyToInstall={updateStatus === updateStatusTypes.DOWNLOADED} 227 noUpdateAvailable={updateStatus === updateStatusTypes.NOT_AVAILABLE}
227 onSubmit={d => this.onSubmit(d)} 228 updateIsReadyToInstall={updateStatus === updateStatusTypes.DOWNLOADED}
228 cacheSize={cacheSize} 229 onSubmit={d => this.onSubmit(d)}
229 isClearingAllCache={isClearingAllCache} 230 cacheSize={cacheSize}
230 onClearAllCache={clearAllCache} 231 isClearingAllCache={isClearingAllCache}
231 isSpellcheckerPremiumFeature={spellcheckerConfig.isPremiumFeature} 232 onClearAllCache={clearAllCache}
232 /> 233 isSpellcheckerPremiumFeature={spellcheckerConfig.isPremiumFeature}
234 />
235 </ErrorBoundary>
233 ); 236 );
234 } 237 }
235} 238}
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';
6import UserStore from '../../stores/UserStore'; 6import UserStore from '../../stores/UserStore';
7import Form from '../../lib/Form'; 7import Form from '../../lib/Form';
8import EditUserForm from '../../components/settings/user/EditUserForm'; 8import EditUserForm from '../../components/settings/user/EditUserForm';
9import ErrorBoundary from '../../components/util/ErrorBoundary';
10
9import { required, email, minLength } from '../../helpers/validation-helpers'; 11import { required, email, minLength } from '../../helpers/validation-helpers';
10import { gaPage } from '../../lib/analytics'; 12import { gaPage } from '../../lib/analytics';
11 13
@@ -140,14 +142,16 @@ export default @inject('stores', 'actions') @observer class EditUserScreen exten
140 const form = this.prepareForm(user.data); 142 const form = this.prepareForm(user.data);
141 143
142 return ( 144 return (
143 <EditUserForm 145 <ErrorBoundary>
144 // user={user.data} 146 <EditUserForm
145 status={user.actionStatus} 147 // user={user.data}
146 form={form} 148 status={user.actionStatus}
147 isEnterprise={user.data.isEnterprise} 149 form={form}
148 isSaving={user.updateUserInfoRequest.isExecuting} 150 isEnterprise={user.data.isEnterprise}
149 onSubmit={d => this.onSubmit(d)} 151 isSaving={user.updateUserInfoRequest.isExecuting}
150 /> 152 onSubmit={d => this.onSubmit(d)}
153 />
154 </ErrorBoundary>
151 ); 155 );
152 } 156 }
153} 157}
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';
3import { inject, observer } from 'mobx-react'; 3import { inject, observer } from 'mobx-react';
4 4
5import Invite from '../../components/auth/Invite'; 5import Invite from '../../components/auth/Invite';
6import ErrorBoundary from '../../components/util/ErrorBoundary';
7
6import { gaPage } from '../../lib/analytics'; 8import { gaPage } from '../../lib/analytics';
7 9
8export default @inject('stores', 'actions') @observer class InviteScreen extends Component { 10export default @inject('stores', 'actions') @observer class InviteScreen extends Component {
@@ -19,12 +21,14 @@ export default @inject('stores', 'actions') @observer class InviteScreen extends
19 const { user } = this.props.stores; 21 const { user } = this.props.stores;
20 22
21 return ( 23 return (
22 <Invite 24 <ErrorBoundary>
23 onSubmit={actions.user.invite} 25 <Invite
24 isLoadingInvite={user.inviteRequest.isExecuting} 26 onSubmit={actions.user.invite}
25 isInviteSuccessful={user.inviteRequest.wasExecuted && !user.inviteRequest.isError} 27 isLoadingInvite={user.inviteRequest.isExecuting}
26 embed 28 isInviteSuccessful={user.inviteRequest.wasExecuted && !user.inviteRequest.isError}
27 /> 29 embed
30 />
31 </ErrorBoundary>
28 ); 32 );
29 } 33 }
30} 34}
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';
10import { gaPage } from '../../lib/analytics'; 10import { gaPage } from '../../lib/analytics';
11 11
12import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard'; 12import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard';
13import ErrorBoundary from '../../components/util/ErrorBoundary';
13 14
14export default @inject('stores', 'actions') @observer class RecipesScreen extends Component { 15export default @inject('stores', 'actions') @observer class RecipesScreen extends Component {
15 static propTypes = { 16 static propTypes = {
@@ -93,19 +94,21 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
93 || recipePreviews.searchRecipePreviewsRequest.isExecuting; 94 || recipePreviews.searchRecipePreviewsRequest.isExecuting;
94 95
95 return ( 96 return (
96 <RecipesDashboard 97 <ErrorBoundary>
97 recipes={allRecipes} 98 <RecipesDashboard
98 isLoading={isLoading} 99 recipes={allRecipes}
99 addedServiceCount={services.all.length} 100 isLoading={isLoading}
100 isPremium={user.data.isPremium} 101 addedServiceCount={services.all.length}
101 hasLoadedRecipes={recipePreviews.featuredRecipePreviewsRequest.wasExecuted} 102 isPremium={user.data.isPremium}
102 showAddServiceInterface={showAddServiceInterface} 103 hasLoadedRecipes={recipePreviews.featuredRecipePreviewsRequest.wasExecuted}
103 searchRecipes={e => this.searchRecipes(e)} 104 showAddServiceInterface={showAddServiceInterface}
104 resetSearch={() => this.resetSearch()} 105 searchRecipes={e => this.searchRecipes(e)}
105 searchNeedle={this.state.needle} 106 resetSearch={() => this.resetSearch()}
106 serviceStatus={services.actionStatus} 107 searchNeedle={this.state.needle}
107 devRecipesCount={recipePreviews.dev.length} 108 serviceStatus={services.actionStatus}
108 /> 109 devRecipesCount={recipePreviews.dev.length}
110 />
111 </ErrorBoundary>
109 ); 112 );
110 } 113 }
111} 114}
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';
9import { gaPage } from '../../lib/analytics'; 9import { gaPage } from '../../lib/analytics';
10 10
11import ServicesDashboard from '../../components/settings/services/ServicesDashboard'; 11import ServicesDashboard from '../../components/settings/services/ServicesDashboard';
12import ErrorBoundary from '../../components/util/ErrorBoundary';
12 13
13export default @inject('stores', 'actions') @observer class ServicesScreen extends Component { 14export default @inject('stores', 'actions') @observer class ServicesScreen extends Component {
14 componentDidMount() { 15 componentDidMount() {
@@ -40,20 +41,22 @@ export default @inject('stores', 'actions') @observer class ServicesScreen exten
40 } 41 }
41 42
42 return ( 43 return (
43 <ServicesDashboard 44 <ErrorBoundary>
44 user={user.data} 45 <ServicesDashboard
45 services={allServices} 46 user={user.data}
46 status={services.actionStatus} 47 services={allServices}
47 deleteService={() => this.deleteService()} 48 status={services.actionStatus}
48 toggleService={toggleService} 49 deleteService={() => this.deleteService()}
49 isLoading={isLoading} 50 toggleService={toggleService}
50 filterServices={filter} 51 isLoading={isLoading}
51 resetFilter={resetFilter} 52 filterServices={filter}
52 goTo={router.push} 53 resetFilter={resetFilter}
53 servicesRequestFailed={services.allServicesRequest.wasExecuted && services.allServicesRequest.isError} 54 goTo={router.push}
54 retryServicesRequest={() => services.allServicesRequest.reload()} 55 servicesRequestFailed={services.allServicesRequest.wasExecuted && services.allServicesRequest.isError}
55 searchNeedle={services.filterNeedle} 56 retryServicesRequest={() => services.allServicesRequest.reload()}
56 /> 57 searchNeedle={services.filterNeedle}
58 />
59 </ErrorBoundary>
57 ); 60 );
58 } 61 }
59} 62}
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';
6 6
7import Layout from '../../components/settings/SettingsLayout'; 7import Layout from '../../components/settings/SettingsLayout';
8import Navigation from '../../components/settings/navigation/SettingsNavigation'; 8import Navigation from '../../components/settings/navigation/SettingsNavigation';
9import ErrorBoundary from '../../components/util/ErrorBoundary';
9 10
10export default @inject('stores', 'actions') @observer class SettingsContainer extends Component { 11export default @inject('stores', 'actions') @observer class SettingsContainer extends Component {
11 render() { 12 render() {
@@ -19,12 +20,14 @@ export default @inject('stores', 'actions') @observer class SettingsContainer ex
19 ); 20 );
20 21
21 return ( 22 return (
22 <Layout 23 <ErrorBoundary>
23 navigation={navigation} 24 <Layout
24 closeSettings={closeSettings} 25 navigation={navigation}
25 > 26 closeSettings={closeSettings}
26 {children} 27 >
27 </Layout> 28 {children}
29 </Layout>
30 </ErrorBoundary>
28 ); 31 );
29 } 32 }
30} 33}
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 @@
269 "feature.delayApp.headline": "Please purchase a Franz Supporter License to skip waiting", 269 "feature.delayApp.headline": "Please purchase a Franz Supporter License to skip waiting",
270 "feature.delayApp.action": "Get a Franz Supporter License", 270 "feature.delayApp.action": "Get a Franz Supporter License",
271 "feature.delayApp.text": "Franz will continue in {seconds} seconds.", 271 "feature.delayApp.text": "Franz will continue in {seconds} seconds.",
272 "premiumFeature.button.upgradeAccount": "Upgrade account" 272 "premiumFeature.button.upgradeAccount": "Upgrade account",
273 "app.errorHandler.headline": "Something went wrong",
274 "app.errorHandler.action": "Reload"
273} 275}
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;
4export const colorBackgroundSubscriptionContainer = legacyStyles.themeBrandInfo; 4export const colorBackgroundSubscriptionContainer = legacyStyles.themeBrandInfo;
5 5
6export const colorHeadline = legacyStyles.darkThemeTextColor; 6export const colorHeadline = legacyStyles.darkThemeTextColor;
7export const colorText = legacyStyles.darkThemeTextColor;
8
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;
12export const colorBackground = legacyStyles.themeGrayLighter; 12export const colorBackground = legacyStyles.themeGrayLighter;
13export const colorHeadline = legacyStyles.themeGrayDark; 13export const colorHeadline = legacyStyles.themeGrayDark;
14 14
15export const colorText = legacyStyles.themeTextColor;
16
15// Subscription Container Component 17// Subscription Container Component
16export const colorSubscriptionContainerBackground = 'none'; 18export const colorSubscriptionContainerBackground = 'none';
17export const colorSubscriptionContainerBorder = [1, 'solid', brandPrimary]; 19export const colorSubscriptionContainerBorder = [1, 'solid', brandPrimary];