aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/settings
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/settings')
-rw-r--r--src/components/settings/SettingsLayout.js13
-rw-r--r--src/components/settings/account/AccountDashboard.js37
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js49
-rw-r--r--src/components/settings/recipes/RecipeItem.js17
-rw-r--r--src/components/settings/recipes/RecipesDashboard.js83
-rw-r--r--src/components/settings/services/EditServiceForm.js157
-rw-r--r--src/components/settings/services/ServiceError.js26
-rw-r--r--src/components/settings/services/ServiceItem.js48
-rw-r--r--src/components/settings/services/ServicesDashboard.js52
-rw-r--r--src/components/settings/settings/EditSettingsForm.js527
-rw-r--r--src/components/settings/supportFerdi/SupportFerdiDashboard.js141
-rw-r--r--src/components/settings/team/TeamDashboard.js54
-rw-r--r--src/components/settings/user/EditUserForm.js39
13 files changed, 704 insertions, 539 deletions
diff --git a/src/components/settings/SettingsLayout.js b/src/components/settings/SettingsLayout.js
index 5b3b754fa..0574b3765 100644
--- a/src/components/settings/SettingsLayout.js
+++ b/src/components/settings/SettingsLayout.js
@@ -1,7 +1,7 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, injectIntl } from 'react-intl';
5 5
6import ErrorBoundary from '../util/ErrorBoundary'; 6import ErrorBoundary from '../util/ErrorBoundary';
7import { oneOrManyChildElements } from '../../prop-types'; 7import { oneOrManyChildElements } from '../../prop-types';
@@ -10,11 +10,10 @@ import Appear from '../ui/effects/Appear';
10const messages = defineMessages({ 10const messages = defineMessages({
11 closeSettings: { 11 closeSettings: {
12 id: 'settings.app.closeSettings', 12 id: 'settings.app.closeSettings',
13 defaultMessage: '!!!Close settings', 13 defaultMessage: 'Close settings',
14 }, 14 },
15}); 15});
16 16
17export default
18@observer 17@observer
19class SettingsLayout extends Component { 18class SettingsLayout extends Component {
20 static propTypes = { 19 static propTypes = {
@@ -23,10 +22,6 @@ class SettingsLayout extends Component {
23 closeSettings: PropTypes.func.isRequired, 22 closeSettings: PropTypes.func.isRequired,
24 }; 23 };
25 24
26 static contextTypes = {
27 intl: intlShape,
28 };
29
30 componentDidMount() { 25 componentDidMount() {
31 document.addEventListener('keydown', this.handleKeyDown.bind(this), false); 26 document.addEventListener('keydown', this.handleKeyDown.bind(this), false);
32 } 27 }
@@ -49,7 +44,7 @@ class SettingsLayout extends Component {
49 render() { 44 render() {
50 const { navigation, children, closeSettings } = this.props; 45 const { navigation, children, closeSettings } = this.props;
51 46
52 const { intl } = this.context; 47 const { intl } = this.props;
53 48
54 return ( 49 return (
55 <Appear transitionName="fadeIn-fast"> 50 <Appear transitionName="fadeIn-fast">
@@ -77,3 +72,5 @@ class SettingsLayout extends Component {
77 ); 72 );
78 } 73 }
79} 74}
75
76export default injectIntl(SettingsLayout);
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
index ef7748343..933d47d12 100644
--- a/src/components/settings/account/AccountDashboard.js
+++ b/src/components/settings/account/AccountDashboard.js
@@ -1,7 +1,7 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, injectIntl } from 'react-intl';
5import ReactTooltip from 'react-tooltip'; 5import ReactTooltip from 'react-tooltip';
6import { H1, H2 } from '@meetfranz/ui'; 6import { H1, H2 } from '@meetfranz/ui';
7 7
@@ -13,7 +13,7 @@ import { LOCAL_SERVER, LIVE_FRANZ_API } from '../../../config';
13const messages = defineMessages({ 13const messages = defineMessages({
14 headline: { 14 headline: {
15 id: 'settings.account.headline', 15 id: 'settings.account.headline',
16 defaultMessage: '!!!Account', 16 defaultMessage: 'Account',
17 }, 17 },
18 headlineDangerZone: { 18 headlineDangerZone: {
19 id: 'settings.account.headlineDangerZone', 19 id: 'settings.account.headlineDangerZone',
@@ -21,7 +21,7 @@ const messages = defineMessages({
21 }, 21 },
22 accountEditButton: { 22 accountEditButton: {
23 id: 'settings.account.account.editButton', 23 id: 'settings.account.account.editButton',
24 defaultMessage: '!!!Edit Account', 24 defaultMessage: 'Edit Account',
25 }, 25 },
26 invoicesButton: { 26 invoicesButton: {
27 id: 'settings.account.headlineInvoices', 27 id: 'settings.account.headlineInvoices',
@@ -29,29 +29,29 @@ const messages = defineMessages({
29 }, 29 },
30 userInfoRequestFailed: { 30 userInfoRequestFailed: {
31 id: 'settings.account.userInfoRequestFailed', 31 id: 'settings.account.userInfoRequestFailed',
32 defaultMessage: '!!!Could not load user information', 32 defaultMessage: 'Could not load user information',
33 }, 33 },
34 tryReloadUserInfoRequest: { 34 tryReloadUserInfoRequest: {
35 id: 'settings.account.tryReloadUserInfoRequest', 35 id: 'settings.account.tryReloadUserInfoRequest',
36 defaultMessage: '!!!Try again', 36 defaultMessage: 'Try again',
37 }, 37 },
38 deleteAccount: { 38 deleteAccount: {
39 id: 'settings.account.deleteAccount', 39 id: 'settings.account.deleteAccount',
40 defaultMessage: '!!!Delete account', 40 defaultMessage: 'Delete account',
41 }, 41 },
42 deleteInfo: { 42 deleteInfo: {
43 id: 'settings.account.deleteInfo', 43 id: 'settings.account.deleteInfo',
44 defaultMessage: 44 defaultMessage:
45 "!!!If you don't need your Ferdi account any longer, you can delete your account and all related data here.", 45 "If you don't need your Ferdi account any longer, you can delete your account and all related data here.",
46 }, 46 },
47 deleteEmailSent: { 47 deleteEmailSent: {
48 id: 'settings.account.deleteEmailSent', 48 id: 'settings.account.deleteEmailSent',
49 defaultMessage: 49 defaultMessage:
50 '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!', 50 'You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!',
51 }, 51 },
52 yourLicense: { 52 yourLicense: {
53 id: 'settings.account.yourLicense', 53 id: 'settings.account.yourLicense',
54 defaultMessage: '!!!Your Franz License:', 54 defaultMessage: 'Your Franz License:',
55 }, 55 },
56 accountUnavailable: { 56 accountUnavailable: {
57 id: 'settings.account.accountUnavailable', 57 id: 'settings.account.accountUnavailable',
@@ -59,7 +59,8 @@ const messages = defineMessages({
59 }, 59 },
60 accountUnavailableInfo: { 60 accountUnavailableInfo: {
61 id: 'settings.account.accountUnavailableInfo', 61 id: 'settings.account.accountUnavailableInfo',
62 defaultMessage: 'You are using Ferdi without an account. If you want to use Ferdi with an account and keep your services synchronized across installations, please select a server in the Settings tab then login.', 62 defaultMessage:
63 'You are using Ferdi without an account. If you want to use Ferdi with an account and keep your services synchronized across installations, please select a server in the Settings tab then login.',
63 }, 64 },
64}); 65});
65 66
@@ -78,10 +79,6 @@ class AccountDashboard extends Component {
78 server: PropTypes.string.isRequired, 79 server: PropTypes.string.isRequired,
79 }; 80 };
80 81
81 static contextTypes = {
82 intl: intlShape,
83 };
84
85 render() { 82 render() {
86 const { 83 const {
87 user, 84 user,
@@ -95,7 +92,7 @@ class AccountDashboard extends Component {
95 openInvoices, 92 openInvoices,
96 server, 93 server,
97 } = this.props; 94 } = this.props;
98 const { intl } = this.context; 95 const { intl } = this.props;
99 96
100 const isUsingWithoutAccount = server === LOCAL_SERVER; 97 const isUsingWithoutAccount = server === LOCAL_SERVER;
101 const isUsingFranzServer = server === LIVE_FRANZ_API; 98 const isUsingFranzServer = server === LIVE_FRANZ_API;
@@ -182,9 +179,7 @@ class AccountDashboard extends Component {
182 <div className="account"> 179 <div className="account">
183 <div className="account__box"> 180 <div className="account__box">
184 <H2>{intl.formatMessage(messages.yourLicense)}</H2> 181 <H2>{intl.formatMessage(messages.yourLicense)}</H2>
185 <p> 182 <p>Franz</p>
186 Franz
187 </p>
188 <div className="manage-user-links"> 183 <div className="manage-user-links">
189 <Button 184 <Button
190 label={intl.formatMessage( 185 label={intl.formatMessage(
@@ -203,7 +198,9 @@ class AccountDashboard extends Component {
203 {isUsingFranzServer && ( 198 {isUsingFranzServer && (
204 <div className="account franz-form"> 199 <div className="account franz-form">
205 <div className="account__box"> 200 <div className="account__box">
206 <H2>{intl.formatMessage(messages.headlineDangerZone)}</H2> 201 <H2>
202 {intl.formatMessage(messages.headlineDangerZone)}
203 </H2>
207 {!isDeleteAccountSuccessful && ( 204 {!isDeleteAccountSuccessful && (
208 <div className="account__subscription"> 205 <div className="account__subscription">
209 <p>{intl.formatMessage(messages.deleteInfo)}</p> 206 <p>{intl.formatMessage(messages.deleteInfo)}</p>
@@ -232,4 +229,4 @@ class AccountDashboard extends Component {
232 } 229 }
233} 230}
234 231
235export default AccountDashboard; 232export default injectIntl(AccountDashboard);
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js
index 0a5ace586..72c7faa66 100644
--- a/src/components/settings/navigation/SettingsNavigation.js
+++ b/src/components/settings/navigation/SettingsNavigation.js
@@ -1,6 +1,6 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl'; 3import { defineMessages, injectIntl } from 'react-intl';
4import { inject, observer } from 'mobx-react'; 4import { inject, observer } from 'mobx-react';
5import { RouterStore } from 'mobx-react-router'; 5import { RouterStore } from 'mobx-react-router';
6 6
@@ -15,35 +15,37 @@ import globalMessages from '../../../i18n/globalMessages';
15const messages = defineMessages({ 15const messages = defineMessages({
16 availableServices: { 16 availableServices: {
17 id: 'settings.navigation.availableServices', 17 id: 'settings.navigation.availableServices',
18 defaultMessage: '!!!Available services', 18 defaultMessage: 'Available services',
19 }, 19 },
20 yourServices: { 20 yourServices: {
21 id: 'settings.navigation.yourServices', 21 id: 'settings.navigation.yourServices',
22 defaultMessage: '!!!Your services', 22 defaultMessage: 'Your services',
23 }, 23 },
24 yourWorkspaces: { 24 yourWorkspaces: {
25 id: 'settings.navigation.yourWorkspaces', 25 id: 'settings.navigation.yourWorkspaces',
26 defaultMessage: '!!!Your workspaces', 26 defaultMessage: 'Your workspaces',
27 }, 27 },
28 account: { 28 account: {
29 id: 'settings.navigation.account', 29 id: 'settings.navigation.account',
30 defaultMessage: '!!!Account', 30 defaultMessage: 'Account',
31 }, 31 },
32 team: { 32 team: {
33 id: 'settings.navigation.team', 33 id: 'settings.navigation.team',
34 defaultMessage: '!!!Manage Team', 34 defaultMessage: 'Manage Team',
35 }, 35 },
36 supportFerdi: { 36 supportFerdi: {
37 id: 'settings.navigation.supportFerdi', 37 id: 'settings.navigation.supportFerdi',
38 defaultMessage: '!!!About Ferdi', 38 defaultMessage: 'About Ferdi',
39 }, 39 },
40 logout: { 40 logout: {
41 id: 'settings.navigation.logout', 41 id: 'settings.navigation.logout',
42 defaultMessage: '!!!Logout', 42 defaultMessage: 'Logout',
43 }, 43 },
44}); 44});
45 45
46export default @inject('stores', 'actions') @observer class SettingsNavigation extends Component { 46@inject('stores', 'actions')
47@observer
48class SettingsNavigation extends Component {
47 static propTypes = { 49 static propTypes = {
48 stores: PropTypes.shape({ 50 stores: PropTypes.shape({
49 ui: PropTypes.instanceOf(UIStore).isRequired, 51 ui: PropTypes.instanceOf(UIStore).isRequired,
@@ -58,13 +60,10 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
58 workspaceCount: PropTypes.number.isRequired, 60 workspaceCount: PropTypes.number.isRequired,
59 }; 61 };
60 62
61 static contextTypes = {
62 intl: intlShape,
63 };
64
65 handleLoginLogout() { 63 handleLoginLogout() {
66 const isLoggedIn = Boolean(localStorage.getItem('authToken')); 64 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
67 const isUsingWithoutAccount = this.props.stores.settings.app.server === LOCAL_SERVER; 65 const isUsingWithoutAccount =
66 this.props.stores.settings.app.server === LOCAL_SERVER;
68 67
69 if (isLoggedIn) { 68 if (isLoggedIn) {
70 // Remove current auth token 69 // Remove current auth token
@@ -82,7 +81,9 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
82 this.props.stores.user.isLoggingOut = true; 81 this.props.stores.user.isLoggingOut = true;
83 } 82 }
84 83
85 this.props.stores.router.push(isLoggedIn ? '/auth/logout' : '/auth/welcome'); 84 this.props.stores.router.push(
85 isLoggedIn ? '/auth/logout' : '/auth/welcome',
86 );
86 87
87 if (isLoggedIn) { 88 if (isLoggedIn) {
88 // Reload Ferdi, otherwise many settings won't sync correctly with the server 89 // Reload Ferdi, otherwise many settings won't sync correctly with the server
@@ -93,7 +94,7 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
93 94
94 render() { 95 render() {
95 const { serviceCount, workspaceCount, stores } = this.props; 96 const { serviceCount, workspaceCount, stores } = this.props;
96 const { intl } = this.context; 97 const { intl } = this.props;
97 const isLoggedIn = Boolean(localStorage.getItem('authToken')); 98 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
98 const isUsingWithoutAccount = stores.settings.app.server === LOCAL_SERVER; 99 const isUsingWithoutAccount = stores.settings.app.server === LOCAL_SERVER;
99 const isUsingFranzServer = stores.settings.app.server === LIVE_FRANZ_API; 100 const isUsingFranzServer = stores.settings.app.server === LIVE_FRANZ_API;
@@ -113,11 +114,8 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
113 activeClassName="is-active" 114 activeClassName="is-active"
114 disabled={!isLoggedIn} 115 disabled={!isLoggedIn}
115 > 116 >
116 {intl.formatMessage(messages.yourServices)} 117 {intl.formatMessage(messages.yourServices)}{' '}
117 {' '} 118 <span className="badge">{serviceCount}</span>
118 <span className="badge">
119 {serviceCount}
120 </span>
121 </Link> 119 </Link>
122 {workspaceStore.isFeatureEnabled ? ( 120 {workspaceStore.isFeatureEnabled ? (
123 <Link 121 <Link
@@ -126,8 +124,7 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
126 activeClassName="is-active" 124 activeClassName="is-active"
127 disabled={!isLoggedIn} 125 disabled={!isLoggedIn}
128 > 126 >
129 {intl.formatMessage(messages.yourWorkspaces)} 127 {intl.formatMessage(messages.yourWorkspaces)}{' '}
130 {' '}
131 <span className="badge">{workspaceCount}</span> 128 <span className="badge">{workspaceCount}</span>
132 </Link> 129 </Link>
133 ) : null} 130 ) : null}
@@ -172,9 +169,13 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
172 className="settings-navigation__link" 169 className="settings-navigation__link"
173 onClick={this.handleLoginLogout.bind(this)} 170 onClick={this.handleLoginLogout.bind(this)}
174 > 171 >
175 { isLoggedIn && !isUsingWithoutAccount ? intl.formatMessage(messages.logout) : 'Login'} 172 {isLoggedIn && !isUsingWithoutAccount
173 ? intl.formatMessage(messages.logout)
174 : 'Login'}
176 </button> 175 </button>
177 </div> 176 </div>
178 ); 177 );
179 } 178 }
180} 179}
180
181export default injectIntl(SettingsNavigation);
diff --git a/src/components/settings/recipes/RecipeItem.js b/src/components/settings/recipes/RecipeItem.js
index 55f415bd5..ca188aa99 100644
--- a/src/components/settings/recipes/RecipeItem.js
+++ b/src/components/settings/recipes/RecipeItem.js
@@ -4,7 +4,8 @@ import { observer } from 'mobx-react';
4 4
5import RecipePreviewModel from '../../../models/RecipePreview'; 5import RecipePreviewModel from '../../../models/RecipePreview';
6 6
7export default @observer class RecipeItem extends Component { 7@observer
8class RecipeItem extends Component {
8 static propTypes = { 9 static propTypes = {
9 recipe: PropTypes.instanceOf(RecipePreviewModel).isRequired, 10 recipe: PropTypes.instanceOf(RecipePreviewModel).isRequired,
10 onClick: PropTypes.func.isRequired, 11 onClick: PropTypes.func.isRequired,
@@ -14,19 +15,11 @@ export default @observer class RecipeItem extends Component {
14 const { recipe, onClick } = this.props; 15 const { recipe, onClick } = this.props;
15 16
16 return ( 17 return (
17 <button 18 <button type="button" className="recipe-teaser" onClick={onClick}>
18 type="button"
19 className="recipe-teaser"
20 onClick={onClick}
21 >
22 {recipe.isDevRecipe && ( 19 {recipe.isDevRecipe && (
23 <span className="recipe-teaser__dev-badge">dev</span> 20 <span className="recipe-teaser__dev-badge">dev</span>
24 )} 21 )}
25 <img 22 <img src={recipe.icons.svg} className="recipe-teaser__icon" alt="" />
26 src={recipe.icons.svg}
27 className="recipe-teaser__icon"
28 alt=""
29 />
30 <span className="recipe-teaser__label">{recipe.name}</span> 23 <span className="recipe-teaser__label">{recipe.name}</span>
31 {recipe.aliases && recipe.aliases.length > 0 && ( 24 {recipe.aliases && recipe.aliases.length > 0 && (
32 <span className="recipe-teaser__alias_label"> 25 <span className="recipe-teaser__alias_label">
@@ -37,3 +30,5 @@ export default @observer class RecipeItem extends Component {
37 ); 30 );
38 } 31 }
39} 32}
33
34export default RecipeItem;
diff --git a/src/components/settings/recipes/RecipesDashboard.js b/src/components/settings/recipes/RecipesDashboard.js
index 44ff2d0d7..e620abf93 100644
--- a/src/components/settings/recipes/RecipesDashboard.js
+++ b/src/components/settings/recipes/RecipesDashboard.js
@@ -1,7 +1,7 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, injectIntl } from 'react-intl';
5import { Link } from 'react-router'; 5import { Link } from 'react-router';
6 6
7import { Button, Input } from '@meetfranz/forms'; 7import { Button, Input } from '@meetfranz/forms';
@@ -18,55 +18,56 @@ import RecipePreview from '../../../models/RecipePreview';
18const messages = defineMessages({ 18const messages = defineMessages({
19 headline: { 19 headline: {
20 id: 'settings.recipes.headline', 20 id: 'settings.recipes.headline',
21 defaultMessage: '!!!Available Services', 21 defaultMessage: 'Available Services',
22 }, 22 },
23 searchService: { 23 searchService: {
24 id: 'settings.searchService', 24 id: 'settings.searchService',
25 defaultMessage: '!!!Search service', 25 defaultMessage: 'Search service',
26 }, 26 },
27 allRecipes: { 27 allRecipes: {
28 id: 'settings.recipes.all', 28 id: 'settings.recipes.all',
29 defaultMessage: '!!!All services', 29 defaultMessage: 'All services',
30 }, 30 },
31 customRecipes: { 31 customRecipes: {
32 id: 'settings.recipes.custom', 32 id: 'settings.recipes.custom',
33 defaultMessage: '!!!Custom Services', 33 defaultMessage: 'Custom Services',
34 }, 34 },
35 nothingFound: { 35 nothingFound: {
36 id: 'settings.recipes.nothingFound', 36 id: 'settings.recipes.nothingFound',
37 defaultMessage: '!!!Sorry, but no service matched your search term - but you can still probably add it using the "Custom Website" option. Please note that the website might show more services that have been added to Ferdi since the version that you are currently on. To get those new services, please consider upgrading to a newer version of Ferdi.', 37 defaultMessage:
38 'Sorry, but no service matched your search term - but you can still probably add it using the "Custom Website" option. Please note that the website might show more services that have been added to Ferdi since the version that you are currently on. To get those new services, please consider upgrading to a newer version of Ferdi.',
38 }, 39 },
39 servicesSuccessfulAddedInfo: { 40 servicesSuccessfulAddedInfo: {
40 id: 'settings.recipes.servicesSuccessfulAddedInfo', 41 id: 'settings.recipes.servicesSuccessfulAddedInfo',
41 defaultMessage: '!!!Service successfully added', 42 defaultMessage: 'Service successfully added',
42 }, 43 },
43 missingService: { 44 missingService: {
44 id: 'settings.recipes.missingService', 45 id: 'settings.recipes.missingService',
45 defaultMessage: '!!!Missing a service?', 46 defaultMessage: 'Missing a service?',
46 }, 47 },
47 customRecipeIntro: { 48 customRecipeIntro: {
48 id: 'settings.recipes.customService.intro', 49 id: 'settings.recipes.customService.intro',
49 defaultMessage: '!!!To add a custom service, copy the recipe folder into:', 50 defaultMessage: 'To add a custom service, copy the recipe folder into:',
50 }, 51 },
51 openFolder: { 52 openFolder: {
52 id: 'settings.recipes.customService.openFolder', 53 id: 'settings.recipes.customService.openFolder',
53 defaultMessage: '!!!Open directory', 54 defaultMessage: 'Open directory',
54 }, 55 },
55 openDevDocs: { 56 openDevDocs: {
56 id: 'settings.recipes.customService.openDevDocs', 57 id: 'settings.recipes.customService.openDevDocs',
57 defaultMessage: '!!!Developer Documentation', 58 defaultMessage: 'Developer Documentation',
58 }, 59 },
59 headlineCustomRecipes: { 60 headlineCustomRecipes: {
60 id: 'settings.recipes.customService.headline.customRecipes', 61 id: 'settings.recipes.customService.headline.customRecipes',
61 defaultMessage: '!!!Custom 3rd Party Recipes', 62 defaultMessage: 'Custom 3rd Party Recipes',
62 }, 63 },
63 headlineCommunityRecipes: { 64 headlineCommunityRecipes: {
64 id: 'settings.recipes.customService.headline.communityRecipes', 65 id: 'settings.recipes.customService.headline.communityRecipes',
65 defaultMessage: '!!!Community 3rd Party Recipes', 66 defaultMessage: 'Community 3rd Party Recipes',
66 }, 67 },
67 headlineDevRecipes: { 68 headlineDevRecipes: {
68 id: 'settings.recipes.customService.headline.devRecipes', 69 id: 'settings.recipes.customService.headline.devRecipes',
69 defaultMessage: '!!!Your Development Service Recipes', 70 defaultMessage: 'Your Development Service Recipes',
70 }, 71 },
71}); 72});
72 73
@@ -81,7 +82,8 @@ const styles = {
81 marginTop: 20, 82 marginTop: 20,
82 83
83 '& > div': { 84 '& > div': {
84 fontFamily: 'SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace', 85 fontFamily:
86 'SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace',
85 }, 87 },
86 }, 88 },
87 actionContainer: { 89 actionContainer: {
@@ -98,7 +100,9 @@ const styles = {
98 }, 100 },
99}; 101};
100 102
101export default @injectSheet(styles) @observer class RecipesDashboard extends Component { 103@injectSheet(styles)
104@observer
105class RecipesDashboard extends Component {
102 static propTypes = { 106 static propTypes = {
103 recipes: MobxPropTypes.arrayOrObservableArray.isRequired, 107 recipes: MobxPropTypes.arrayOrObservableArray.isRequired,
104 customWebsiteRecipe: PropTypes.instanceOf(RecipePreview).isRequired, 108 customWebsiteRecipe: PropTypes.instanceOf(RecipePreview).isRequired,
@@ -118,10 +122,6 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
118 static defaultProps = { 122 static defaultProps = {
119 searchNeedle: '', 123 searchNeedle: '',
120 recipeFilter: 'all', 124 recipeFilter: 'all',
121 }
122
123 static contextTypes = {
124 intl: intlShape,
125 }; 125 };
126 126
127 render() { 127 render() {
@@ -140,7 +140,7 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
140 openDevDocs, 140 openDevDocs,
141 classes, 141 classes,
142 } = this.props; 142 } = this.props;
143 const { intl } = this.context; 143 const { intl } = this.props;
144 144
145 const communityRecipes = recipes.filter(r => !r.isDevRecipe); 145 const communityRecipes = recipes.filter(r => !r.isDevRecipe);
146 const devRecipes = recipes.filter(r => r.isDevRecipe); 146 const devRecipes = recipes.filter(r => r.isDevRecipe);
@@ -188,9 +188,13 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
188 > 188 >
189 {intl.formatMessage(messages.customRecipes)} 189 {intl.formatMessage(messages.customRecipes)}
190 </Link> 190 </Link>
191 <a href={FRANZ_SERVICE_REQUEST} target="_blank" className="link recipes__service-request" rel="noreferrer"> 191 <a
192 {intl.formatMessage(messages.missingService)} 192 href={FRANZ_SERVICE_REQUEST}
193 {' '} 193 target="_blank"
194 className="link recipes__service-request"
195 rel="noreferrer"
196 >
197 {intl.formatMessage(messages.missingService)}{' '}
194 <i className="mdi mdi-open-in-new" /> 198 <i className="mdi mdi-open-in-new" />
195 </a> 199 </a>
196 </div> 200 </div>
@@ -201,13 +205,9 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
201 <> 205 <>
202 {recipeFilter === 'dev' && ( 206 {recipeFilter === 'dev' && (
203 <> 207 <>
204 <H2> 208 <H2>{intl.formatMessage(messages.headlineCustomRecipes)}</H2>
205 {intl.formatMessage(messages.headlineCustomRecipes)}
206 </H2>
207 <div className={classes.devRecipeIntroContainer}> 209 <div className={classes.devRecipeIntroContainer}>
208 <p> 210 <p>{intl.formatMessage(messages.customRecipeIntro)}</p>
209 {intl.formatMessage(messages.customRecipeIntro)}
210 </p>
211 <Input 211 <Input
212 value={recipeDirectory} 212 value={recipeDirectory}
213 className={classes.path} 213 className={classes.path}
@@ -238,12 +238,19 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
238 <img src="./assets/images/emoji/dontknow.png" alt="" /> 238 <img src="./assets/images/emoji/dontknow.png" alt="" />
239 </span> 239 </span>
240 240
241 <p className="settings__empty-state-text">{intl.formatMessage(messages.nothingFound)}</p> 241 <p className="settings__empty-state-text">
242 {intl.formatMessage(messages.nothingFound)}
243 </p>
242 244
243 <RecipeItem 245 <RecipeItem
244 key={customWebsiteRecipe.id} 246 key={customWebsiteRecipe.id}
245 recipe={customWebsiteRecipe} 247 recipe={customWebsiteRecipe}
246 onClick={() => isLoggedIn && showAddServiceInterface({ recipeId: customWebsiteRecipe.id })} 248 onClick={() =>
249 isLoggedIn &&
250 showAddServiceInterface({
251 recipeId: customWebsiteRecipe.id,
252 })
253 }
247 /> 254 />
248 </div> 255 </div>
249 )} 256 )}
@@ -251,7 +258,10 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
251 <RecipeItem 258 <RecipeItem
252 key={recipe.id} 259 key={recipe.id}
253 recipe={recipe} 260 recipe={recipe}
254 onClick={() => isLoggedIn && showAddServiceInterface({ recipeId: recipe.id })} 261 onClick={() =>
262 isLoggedIn &&
263 showAddServiceInterface({ recipeId: recipe.id })
264 }
255 /> 265 />
256 ))} 266 ))}
257 </div> 267 </div>
@@ -263,7 +273,10 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
263 <RecipeItem 273 <RecipeItem
264 key={recipe.id} 274 key={recipe.id}
265 recipe={recipe} 275 recipe={recipe}
266 onClick={() => isLoggedIn && showAddServiceInterface({ recipeId: recipe.id })} 276 onClick={() =>
277 isLoggedIn &&
278 showAddServiceInterface({ recipeId: recipe.id })
279 }
267 /> 280 />
268 ))} 281 ))}
269 </div> 282 </div>
@@ -276,3 +289,5 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
276 ); 289 );
277 } 290 }
278} 291}
292
293export default injectIntl(RecipesDashboard);
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index c41cdd56a..3fbb57cbb 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -2,7 +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';
4import { Link } from 'react-router'; 4import { Link } from 'react-router';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, injectIntl } from 'react-intl';
6import normalizeUrl from 'normalize-url'; 6import normalizeUrl from 'normalize-url';
7 7
8import Form from '../../../lib/Form'; 8import Form from '../../../lib/Form';
@@ -22,111 +22,117 @@ import globalMessages from '../../../i18n/globalMessages';
22const messages = defineMessages({ 22const messages = defineMessages({
23 saveService: { 23 saveService: {
24 id: 'settings.service.form.saveButton', 24 id: 'settings.service.form.saveButton',
25 defaultMessage: '!!!Save service', 25 defaultMessage: 'Save service',
26 }, 26 },
27 deleteService: { 27 deleteService: {
28 id: 'settings.service.form.deleteButton', 28 id: 'settings.service.form.deleteButton',
29 defaultMessage: '!!!Delete Service', 29 defaultMessage: 'Delete Service',
30 }, 30 },
31 openDarkmodeCss: { 31 openDarkmodeCss: {
32 id: 'settings.service.form.openDarkmodeCss', 32 id: 'settings.service.form.openDarkmodeCss',
33 defaultMessage: '!!!Open darkmode.css', 33 defaultMessage: 'Open darkmode.css',
34 }, 34 },
35 openUserCss: { 35 openUserCss: {
36 id: 'settings.service.form.openUserCss', 36 id: 'settings.service.form.openUserCss',
37 defaultMessage: '!!!Open user.css', 37 defaultMessage: 'Open user.css',
38 }, 38 },
39 openUserJs: { 39 openUserJs: {
40 id: 'settings.service.form.openUserJs', 40 id: 'settings.service.form.openUserJs',
41 defaultMessage: '!!!Open user.js', 41 defaultMessage: 'Open user.js',
42 }, 42 },
43 recipeFileInfo: { 43 recipeFileInfo: {
44 id: 'settings.service.form.recipeFileInfo', 44 id: 'settings.service.form.recipeFileInfo',
45 defaultMessage: '!!!Your user files will be inserted into the webpage so you can customize services in any way you like. User files are only stored locally and are not transferred to other computers using the same account.', 45 defaultMessage:
46 'Your user files will be inserted into the webpage so you can customize services in any way you like. User files are only stored locally and are not transferred to other computers using the same account.',
46 }, 47 },
47 availableServices: { 48 availableServices: {
48 id: 'settings.service.form.availableServices', 49 id: 'settings.service.form.availableServices',
49 defaultMessage: '!!!Available services', 50 defaultMessage: 'Available services',
50 }, 51 },
51 yourServices: { 52 yourServices: {
52 id: 'settings.service.form.yourServices', 53 id: 'settings.service.form.yourServices',
53 defaultMessage: '!!!Your services', 54 defaultMessage: 'Your services',
54 }, 55 },
55 addServiceHeadline: { 56 addServiceHeadline: {
56 id: 'settings.service.form.addServiceHeadline', 57 id: 'settings.service.form.addServiceHeadline',
57 defaultMessage: '!!!Add {name}', 58 defaultMessage: 'Add {name}',
58 }, 59 },
59 editServiceHeadline: { 60 editServiceHeadline: {
60 id: 'settings.service.form.editServiceHeadline', 61 id: 'settings.service.form.editServiceHeadline',
61 defaultMessage: '!!!Edit {name}', 62 defaultMessage: 'Edit {name}',
62 }, 63 },
63 tabHosted: { 64 tabHosted: {
64 id: 'settings.service.form.tabHosted', 65 id: 'settings.service.form.tabHosted',
65 defaultMessage: '!!!Hosted', 66 defaultMessage: 'Hosted',
66 }, 67 },
67 tabOnPremise: { 68 tabOnPremise: {
68 id: 'settings.service.form.tabOnPremise', 69 id: 'settings.service.form.tabOnPremise',
69 defaultMessage: '!!!Self hosted ⭐️', 70 defaultMessage: 'Self hosted ⭐️',
70 }, 71 },
71 useHostedService: { 72 useHostedService: {
72 id: 'settings.service.form.useHostedService', 73 id: 'settings.service.form.useHostedService',
73 defaultMessage: '!!!Use the hosted {name} service.', 74 defaultMessage: 'Use the hosted {name} service.',
74 }, 75 },
75 customUrlValidationError: { 76 customUrlValidationError: {
76 id: 'settings.service.form.customUrlValidationError', 77 id: 'settings.service.form.customUrlValidationError',
77 defaultMessage: '!!!Could not validate custom {name} server.', 78 defaultMessage: 'Could not validate custom {name} server.',
78 }, 79 },
79 indirectMessageInfo: { 80 indirectMessageInfo: {
80 id: 'settings.service.form.indirectMessageInfo', 81 id: 'settings.service.form.indirectMessageInfo',
81 defaultMessage: '!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...', 82 defaultMessage:
83 'You will be notified about all new messages in a channel, not just @username, @channel, @here, ...',
82 }, 84 },
83 isMutedInfo: { 85 isMutedInfo: {
84 id: 'settings.service.form.isMutedInfo', 86 id: 'settings.service.form.isMutedInfo',
85 defaultMessage: '!!!When disabled, all notification sounds and audio playback are muted', 87 defaultMessage:
88 'When disabled, all notification sounds and audio playback are muted',
86 }, 89 },
87 isHibernationEnabledInfo: { 90 isHibernationEnabledInfo: {
88 id: 'settings.service.form.isHibernatedEnabledInfo', 91 id: 'settings.service.form.isHibernatedEnabledInfo',
89 defaultMessage: '!!!When enabled, a service will be shut down after a period of time to save system resources.', 92 defaultMessage:
93 'When enabled, a service will be shut down after a period of time to save system resources.',
90 }, 94 },
91 headlineNotifications: { 95 headlineNotifications: {
92 id: 'settings.service.form.headlineNotifications', 96 id: 'settings.service.form.headlineNotifications',
93 defaultMessage: '!!!Notifications', 97 defaultMessage: 'Notifications',
94 }, 98 },
95 headlineBadges: { 99 headlineBadges: {
96 id: 'settings.service.form.headlineBadges', 100 id: 'settings.service.form.headlineBadges',
97 defaultMessage: '!!!Unread message badges', 101 defaultMessage: 'Unread message badges',
98 }, 102 },
99 headlineGeneral: { 103 headlineGeneral: {
100 id: 'settings.service.form.headlineGeneral', 104 id: 'settings.service.form.headlineGeneral',
101 defaultMessage: '!!!General', 105 defaultMessage: 'General',
102 }, 106 },
103 headlineDarkReaderSettings: { 107 headlineDarkReaderSettings: {
104 id: 'settings.service.form.headlineDarkReaderSettings', 108 id: 'settings.service.form.headlineDarkReaderSettings',
105 defaultMessage: '!!!Dark Reader Settings', 109 defaultMessage: 'Dark Reader Settings',
106 }, 110 },
107 iconDelete: { 111 iconDelete: {
108 id: 'settings.service.form.iconDelete', 112 id: 'settings.service.form.iconDelete',
109 defaultMessage: '!!!Delete', 113 defaultMessage: 'Delete',
110 }, 114 },
111 iconUpload: { 115 iconUpload: {
112 id: 'settings.service.form.iconUpload', 116 id: 'settings.service.form.iconUpload',
113 defaultMessage: '!!!Drop your image, or click here', 117 defaultMessage: 'Drop your image, or click here',
114 }, 118 },
115 headlineProxy: { 119 headlineProxy: {
116 id: 'settings.service.form.proxy.headline', 120 id: 'settings.service.form.proxy.headline',
117 defaultMessage: '!!!HTTP/HTTPS Proxy Settings', 121 defaultMessage: 'HTTP/HTTPS Proxy Settings',
118 }, 122 },
119 proxyRestartInfo: { 123 proxyRestartInfo: {
120 id: 'settings.service.form.proxy.restartInfo', 124 id: 'settings.service.form.proxy.restartInfo',
121 defaultMessage: '!!!Please restart Ferdi after changing proxy Settings.', 125 defaultMessage: 'Please restart Ferdi after changing proxy Settings.',
122 }, 126 },
123 proxyInfo: { 127 proxyInfo: {
124 id: 'settings.service.form.proxy.info', 128 id: 'settings.service.form.proxy.info',
125 defaultMessage: '!!!Proxy settings will not be synchronized with the Ferdi servers.', 129 defaultMessage:
130 'Proxy settings will not be synchronized with the Ferdi servers.',
126 }, 131 },
127}); 132});
128 133
129export default @observer class EditServiceForm extends Component { 134@observer
135class EditServiceForm extends Component {
130 static propTypes = { 136 static propTypes = {
131 recipe: PropTypes.instanceOf(Recipe).isRequired, 137 recipe: PropTypes.instanceOf(Recipe).isRequired,
132 service(props, propName) { 138 service(props, propName) {
@@ -151,20 +157,16 @@ export default @observer class EditServiceForm extends Component {
151 service: {}, 157 service: {},
152 }; 158 };
153 159
154 static contextTypes = {
155 intl: intlShape,
156 };
157
158 state = { 160 state = {
159 isValidatingCustomUrl: false, 161 isValidatingCustomUrl: false,
160 } 162 };
161 163
162 submit(e) { 164 submit(e) {
163 const { recipe } = this.props; 165 const { recipe } = this.props;
164 166
165 e.preventDefault(); 167 e.preventDefault();
166 this.props.form.submit({ 168 this.props.form.submit({
167 onSuccess: async (form) => { 169 onSuccess: async form => {
168 const values = form.values(); 170 const values = form.values();
169 let isValid = true; 171 let isValid = true;
170 172
@@ -176,7 +178,10 @@ export default @observer class EditServiceForm extends Component {
176 if (recipe.validateUrl && values.customUrl) { 178 if (recipe.validateUrl && values.customUrl) {
177 this.setState({ isValidatingCustomUrl: true }); 179 this.setState({ isValidatingCustomUrl: true });
178 try { 180 try {
179 values.customUrl = normalizeUrl(values.customUrl, { stripWWW: false, removeTrailingSlash: false }); 181 values.customUrl = normalizeUrl(values.customUrl, {
182 stripWWW: false,
183 removeTrailingSlash: false,
184 });
180 isValid = await recipe.validateUrl(values.customUrl); 185 isValid = await recipe.validateUrl(values.customUrl);
181 } catch (err) { 186 } catch (err) {
182 console.warn('ValidateURL', err); 187 console.warn('ValidateURL', err);
@@ -208,7 +213,7 @@ export default @observer class EditServiceForm extends Component {
208 openRecipeFile, 213 openRecipeFile,
209 isProxyFeatureEnabled, 214 isProxyFeatureEnabled,
210 } = this.props; 215 } = this.props;
211 const { intl } = this.context; 216 const { intl } = this.props;
212 217
213 const { isValidatingCustomUrl } = this.state; 218 const { isValidatingCustomUrl } = this.state;
214 219
@@ -236,7 +241,8 @@ export default @observer class EditServiceForm extends Component {
236 activeTabIndex = 2; 241 activeTabIndex = 2;
237 } 242 }
238 243
239 const requiresUserInput = !recipe.hasHostedOption && (recipe.hasTeamId || recipe.hasCustomUrl); 244 const requiresUserInput =
245 !recipe.hasHostedOption && (recipe.hasTeamId || recipe.hasCustomUrl);
240 246
241 return ( 247 return (
242 <div className="settings__main"> 248 <div className="settings__main">
@@ -254,29 +260,27 @@ export default @observer class EditServiceForm extends Component {
254 </span> 260 </span>
255 <span className="separator" /> 261 <span className="separator" />
256 <span className="settings__header-item"> 262 <span className="settings__header-item">
257 {action === 'add' ? ( 263 {action === 'add'
258 intl.formatMessage(messages.addServiceHeadline, { 264 ? intl.formatMessage(messages.addServiceHeadline, {
259 name: recipe.name, 265 name: recipe.name,
260 }) 266 })
261 ) : ( 267 : intl.formatMessage(messages.editServiceHeadline, {
262 intl.formatMessage(messages.editServiceHeadline, {
263 name: service.name !== '' ? service.name : recipe.name, 268 name: service.name !== '' ? service.name : recipe.name,
264 }) 269 })}
265 )}
266 </span> 270 </span>
267 </div> 271 </div>
268 <div className="settings__body"> 272 <div className="settings__body">
269 <form onSubmit={(e) => this.submit(e)} id="form"> 273 <form onSubmit={e => this.submit(e)} id="form">
270 <div className="service-name"> 274 <div className="service-name">
271 <Input field={form.$('name')} focus /> 275 <Input field={form.$('name')} focus />
272 </div> 276 </div>
273 {(recipe.hasTeamId || recipe.hasCustomUrl) && ( 277 {(recipe.hasTeamId || recipe.hasCustomUrl) && (
274 <Tabs 278 <Tabs active={activeTabIndex}>
275 active={activeTabIndex}
276 >
277 {recipe.hasHostedOption && ( 279 {recipe.hasHostedOption && (
278 <TabItem title={recipe.name}> 280 <TabItem title={recipe.name}>
279 {intl.formatMessage(messages.useHostedService, { name: recipe.name })} 281 {intl.formatMessage(messages.useHostedService, {
282 name: recipe.name,
283 })}
280 </TabItem> 284 </TabItem>
281 )} 285 )}
282 {recipe.hasTeamId && ( 286 {recipe.hasTeamId && (
@@ -293,7 +297,9 @@ export default @observer class EditServiceForm extends Component {
293 <Input field={form.$('customUrl')} /> 297 <Input field={form.$('customUrl')} />
294 {form.error === 'url-validation-error' && ( 298 {form.error === 'url-validation-error' && (
295 <p className="franz-form__error"> 299 <p className="franz-form__error">
296 {intl.formatMessage(messages.customUrlValidationError, { name: recipe.name })} 300 {intl.formatMessage(messages.customUrlValidationError, {
301 name: recipe.name,
302 })}
297 </p> 303 </p>
298 )} 304 )}
299 </TabItem> 305 </TabItem>
@@ -326,13 +332,16 @@ export default @observer class EditServiceForm extends Component {
326 <div className="settings__settings-group"> 332 <div className="settings__settings-group">
327 <h3>{intl.formatMessage(messages.headlineBadges)}</h3> 333 <h3>{intl.formatMessage(messages.headlineBadges)}</h3>
328 <Toggle field={form.$('isBadgeEnabled')} /> 334 <Toggle field={form.$('isBadgeEnabled')} />
329 {recipe.hasIndirectMessages && form.$('isBadgeEnabled').value && ( 335 {recipe.hasIndirectMessages &&
330 <> 336 form.$('isBadgeEnabled').value && (
331 <Toggle field={form.$('isIndirectMessageBadgeEnabled')} /> 337 <>
332 <p className="settings__help indented__help"> 338 <Toggle
333 {intl.formatMessage(messages.indirectMessageInfo)} 339 field={form.$('isIndirectMessageBadgeEnabled')}
334 </p> 340 />
335 </> 341 <p className="settings__help indented__help">
342 {intl.formatMessage(messages.indirectMessageInfo)}
343 </p>
344 </>
336 )} 345 )}
337 </div> 346 </div>
338 347
@@ -344,15 +353,18 @@ export default @observer class EditServiceForm extends Component {
344 {intl.formatMessage(messages.isHibernationEnabledInfo)} 353 {intl.formatMessage(messages.isHibernationEnabledInfo)}
345 </p> 354 </p>
346 <Toggle field={form.$('isDarkModeEnabled')} /> 355 <Toggle field={form.$('isDarkModeEnabled')} />
347 {form.$('isDarkModeEnabled').value 356 {form.$('isDarkModeEnabled').value && (
348 && ( 357 <>
349 <> 358 <h3>
350 <h3>{intl.formatMessage(messages.headlineDarkReaderSettings)}</h3> 359 {intl.formatMessage(
351 <Slider field={form.$('darkReaderBrightness')} /> 360 messages.headlineDarkReaderSettings,
352 <Slider field={form.$('darkReaderContrast')} /> 361 )}
353 <Slider field={form.$('darkReaderSepia')} /> 362 </h3>
354 </> 363 <Slider field={form.$('darkReaderBrightness')} />
355 )} 364 <Slider field={form.$('darkReaderContrast')} />
365 <Slider field={form.$('darkReaderSepia')} />
366 </>
367 )}
356 </div> 368 </div>
357 </div> 369 </div>
358 <div className="service-icon"> 370 <div className="service-icon">
@@ -381,7 +393,10 @@ export default @observer class EditServiceForm extends Component {
381 <> 393 <>
382 <div className="grid"> 394 <div className="grid">
383 <div className="grid__row"> 395 <div className="grid__row">
384 <Input field={form.$('proxy.host')} className="proxyHost" /> 396 <Input
397 field={form.$('proxy.host')}
398 className="proxyHost"
399 />
385 <Input field={form.$('proxy.port')} /> 400 <Input field={form.$('proxy.port')} />
386 </div> 401 </div>
387 </div> 402 </div>
@@ -409,7 +424,9 @@ export default @observer class EditServiceForm extends Component {
409 424
410 <div className="user-agent"> 425 <div className="user-agent">
411 <Input field={form.$('userAgentPref')} /> 426 <Input field={form.$('userAgentPref')} />
412 <p className="settings__help">{intl.formatMessage(globalMessages.userAgentHelp)}</p> 427 <p className="settings__help">
428 {intl.formatMessage(globalMessages.userAgentHelp)}
429 </p>
413 </div> 430 </div>
414 </form> 431 </form>
415 432
@@ -464,7 +481,9 @@ export default @observer class EditServiceForm extends Component {
464 type="submit" 481 type="submit"
465 label={intl.formatMessage(messages.saveService)} 482 label={intl.formatMessage(messages.saveService)}
466 htmlForm="form" 483 htmlForm="form"
467 disabled={action !== 'edit' && (form.isPristine && requiresUserInput)} 484 disabled={
485 action !== 'edit' && form.isPristine && requiresUserInput
486 }
468 /> 487 />
469 )} 488 )}
470 </div> 489 </div>
@@ -472,3 +491,5 @@ export default @observer class EditServiceForm extends Component {
472 ); 491 );
473 } 492 }
474} 493}
494
495export default injectIntl(EditServiceForm);
diff --git a/src/components/settings/services/ServiceError.js b/src/components/settings/services/ServiceError.js
index 3cfc080d6..d16d76db2 100644
--- a/src/components/settings/services/ServiceError.js
+++ b/src/components/settings/services/ServiceError.js
@@ -1,7 +1,7 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import { observer } from 'mobx-react'; 2import { observer } from 'mobx-react';
3import { Link } from 'react-router'; 3import { Link } from 'react-router';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, injectIntl } from 'react-intl';
5 5
6import Infobox from '../../ui/Infobox'; 6import Infobox from '../../ui/Infobox';
7import Button from '../../ui/Button'; 7import Button from '../../ui/Button';
@@ -9,29 +9,26 @@ import Button from '../../ui/Button';
9const messages = defineMessages({ 9const messages = defineMessages({
10 headline: { 10 headline: {
11 id: 'settings.service.error.headline', 11 id: 'settings.service.error.headline',
12 defaultMessage: '!!!Error', 12 defaultMessage: 'Error',
13 }, 13 },
14 goBack: { 14 goBack: {
15 id: 'settings.service.error.goBack', 15 id: 'settings.service.error.goBack',
16 defaultMessage: '!!!Back to services', 16 defaultMessage: 'Back to services',
17 }, 17 },
18 availableServices: { 18 availableServices: {
19 id: 'settings.service.form.availableServices', 19 id: 'settings.service.form.availableServices',
20 defaultMessage: '!!!Available services', 20 defaultMessage: 'Available services',
21 }, 21 },
22 errorMessage: { 22 errorMessage: {
23 id: 'settings.service.error.message', 23 id: 'settings.service.error.message',
24 defaultMessage: '!!!Could not load service recipe.', 24 defaultMessage: 'Could not load service recipe.',
25 }, 25 },
26}); 26});
27 27
28export default @observer class ServiceError extends Component { 28@observer
29 static contextTypes = { 29class ServiceError extends Component {
30 intl: intlShape,
31 };
32
33 render() { 30 render() {
34 const { intl } = this.context; 31 const { intl } = this.props;
35 32
36 return ( 33 return (
37 <div className="settings__main"> 34 <div className="settings__main">
@@ -47,10 +44,7 @@ export default @observer class ServiceError extends Component {
47 </span> 44 </span>
48 </div> 45 </div>
49 <div className="settings__body"> 46 <div className="settings__body">
50 <Infobox 47 <Infobox type="danger" icon="alert">
51 type="danger"
52 icon="alert"
53 >
54 {intl.formatMessage(messages.errorMessage)} 48 {intl.formatMessage(messages.errorMessage)}
55 </Infobox> 49 </Infobox>
56 </div> 50 </div>
@@ -65,3 +59,5 @@ export default @observer class ServiceError extends Component {
65 ); 59 );
66 } 60 }
67} 61}
62
63export default injectIntl(ServiceError);
diff --git a/src/components/settings/services/ServiceItem.js b/src/components/settings/services/ServiceItem.js
index ebc618a00..4916e4ecc 100644
--- a/src/components/settings/services/ServiceItem.js
+++ b/src/components/settings/services/ServiceItem.js
@@ -1,6 +1,6 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl'; 3import { defineMessages, injectIntl } from 'react-intl';
4import ReactTooltip from 'react-tooltip'; 4import ReactTooltip from 'react-tooltip';
5import { observer } from 'mobx-react'; 5import { observer } from 'mobx-react';
6import classnames from 'classnames'; 6import classnames from 'classnames';
@@ -10,35 +10,32 @@ import ServiceModel from '../../../models/Service';
10const messages = defineMessages({ 10const messages = defineMessages({
11 tooltipIsDisabled: { 11 tooltipIsDisabled: {
12 id: 'settings.services.tooltip.isDisabled', 12 id: 'settings.services.tooltip.isDisabled',
13 defaultMessage: '!!!Service is disabled', 13 defaultMessage: 'Service is disabled',
14 }, 14 },
15 tooltipNotificationsDisabled: { 15 tooltipNotificationsDisabled: {
16 id: 'settings.services.tooltip.notificationsDisabled', 16 id: 'settings.services.tooltip.notificationsDisabled',
17 defaultMessage: '!!!Notifications are disabled', 17 defaultMessage: 'Notifications are disabled',
18 }, 18 },
19 tooltipIsMuted: { 19 tooltipIsMuted: {
20 id: 'settings.services.tooltip.isMuted', 20 id: 'settings.services.tooltip.isMuted',
21 defaultMessage: '!!!All sounds are muted', 21 defaultMessage: 'All sounds are muted',
22 }, 22 },
23}); 23});
24 24
25export default @observer class ServiceItem extends Component { 25@observer
26class ServiceItem extends Component {
26 static propTypes = { 27 static propTypes = {
27 service: PropTypes.instanceOf(ServiceModel).isRequired, 28 service: PropTypes.instanceOf(ServiceModel).isRequired,
28 goToServiceForm: PropTypes.func.isRequired, 29 goToServiceForm: PropTypes.func.isRequired,
29 }; 30 };
30 31
31 static contextTypes = {
32 intl: intlShape,
33 };
34
35 render() { 32 render() {
36 const { 33 const {
37 service, 34 service,
38 // toggleAction, 35 // toggleAction,
39 goToServiceForm, 36 goToServiceForm,
40 } = this.props; 37 } = this.props;
41 const { intl } = this.context; 38 const { intl } = this.props;
42 39
43 return ( 40 return (
44 <tr 41 <tr
@@ -47,10 +44,7 @@ export default @observer class ServiceItem extends Component {
47 'service-table__row--disabled': !service.isEnabled, 44 'service-table__row--disabled': !service.isEnabled,
48 })} 45 })}
49 > 46 >
50 <td 47 <td className="service-table__column-icon" onClick={goToServiceForm}>
51 className="service-table__column-icon"
52 onClick={goToServiceForm}
53 >
54 <img 48 <img
55 src={service.icon} 49 src={service.icon}
56 className={classnames({ 50 className={classnames({
@@ -60,16 +54,10 @@ export default @observer class ServiceItem extends Component {
60 alt="" 54 alt=""
61 /> 55 />
62 </td> 56 </td>
63 <td 57 <td className="service-table__column-name" onClick={goToServiceForm}>
64 className="service-table__column-name"
65 onClick={goToServiceForm}
66 >
67 {service.name !== '' ? service.name : service.recipe.name} 58 {service.name !== '' ? service.name : service.recipe.name}
68 </td> 59 </td>
69 <td 60 <td className="service-table__column-info" onClick={goToServiceForm}>
70 className="service-table__column-info"
71 onClick={goToServiceForm}
72 >
73 {service.isMuted && ( 61 {service.isMuted && (
74 <span 62 <span
75 className="mdi mdi-bell-off" 63 className="mdi mdi-bell-off"
@@ -77,10 +65,7 @@ export default @observer class ServiceItem extends Component {
77 /> 65 />
78 )} 66 )}
79 </td> 67 </td>
80 <td 68 <td className="service-table__column-info" onClick={goToServiceForm}>
81 className="service-table__column-info"
82 onClick={goToServiceForm}
83 >
84 {!service.isEnabled && ( 69 {!service.isEnabled && (
85 <span 70 <span
86 className="mdi mdi-power" 71 className="mdi mdi-power"
@@ -88,14 +73,13 @@ export default @observer class ServiceItem extends Component {
88 /> 73 />
89 )} 74 )}
90 </td> 75 </td>
91 <td 76 <td className="service-table__column-info" onClick={goToServiceForm}>
92 className="service-table__column-info"
93 onClick={goToServiceForm}
94 >
95 {!service.isNotificationEnabled && ( 77 {!service.isNotificationEnabled && (
96 <span 78 <span
97 className="mdi mdi-message-bulleted-off" 79 className="mdi mdi-message-bulleted-off"
98 data-tip={intl.formatMessage(messages.tooltipNotificationsDisabled)} 80 data-tip={intl.formatMessage(
81 messages.tooltipNotificationsDisabled,
82 )}
99 /> 83 />
100 )} 84 )}
101 <ReactTooltip place="top" type="dark" effect="solid" /> 85 <ReactTooltip place="top" type="dark" effect="solid" />
@@ -104,3 +88,5 @@ export default @observer class ServiceItem extends Component {
104 ); 88 );
105 } 89 }
106} 90}
91
92export default injectIntl(ServiceItem);
diff --git a/src/components/settings/services/ServicesDashboard.js b/src/components/settings/services/ServicesDashboard.js
index 11d3eaa79..847f2ea06 100644
--- a/src/components/settings/services/ServicesDashboard.js
+++ b/src/components/settings/services/ServicesDashboard.js
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { Link } from 'react-router'; 4import { Link } from 'react-router';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, injectIntl } from 'react-intl';
6 6
7import SearchInput from '../../ui/SearchInput'; 7import SearchInput from '../../ui/SearchInput';
8import Infobox from '../../ui/Infobox'; 8import Infobox from '../../ui/Infobox';
@@ -14,43 +14,45 @@ import Appear from '../../ui/effects/Appear';
14const messages = defineMessages({ 14const messages = defineMessages({
15 headline: { 15 headline: {
16 id: 'settings.services.headline', 16 id: 'settings.services.headline',
17 defaultMessage: '!!!Your services', 17 defaultMessage: 'Your services',
18 }, 18 },
19 searchService: { 19 searchService: {
20 id: 'settings.searchService', 20 id: 'settings.searchService',
21 defaultMessage: '!!!Search service', 21 defaultMessage: 'Search service',
22 }, 22 },
23 noServicesAdded: { 23 noServicesAdded: {
24 id: 'settings.services.noServicesAdded', 24 id: 'settings.services.noServicesAdded',
25 defaultMessage: '!!!Start by adding a service.', 25 defaultMessage: 'Start by adding a service.',
26 }, 26 },
27 noServiceFound: { 27 noServiceFound: {
28 id: 'settings.recipes.nothingFound', 28 id: 'settings.recipes.nothingFound',
29 defaultMessage: '!!!Sorry, but no service matched your search term. Please note that the website might show more services that have been added to Ferdi since the version that you are currently on. To get those new services, please consider upgrading to a newer version of Ferdi.', 29 defaultMessage:
30 'Sorry, but no service matched your search term. Please note that the website might show more services that have been added to Ferdi since the version that you are currently on. To get those new services, please consider upgrading to a newer version of Ferdi.',
30 }, 31 },
31 discoverServices: { 32 discoverServices: {
32 id: 'settings.services.discoverServices', 33 id: 'settings.services.discoverServices',
33 defaultMessage: '!!!Discover services', 34 defaultMessage: 'Discover services',
34 }, 35 },
35 servicesRequestFailed: { 36 servicesRequestFailed: {
36 id: 'settings.services.servicesRequestFailed', 37 id: 'settings.services.servicesRequestFailed',
37 defaultMessage: '!!!Could not load your services', 38 defaultMessage: 'Could not load your services',
38 }, 39 },
39 tryReloadServices: { 40 tryReloadServices: {
40 id: 'settings.account.tryReloadServices', 41 id: 'settings.account.tryReloadServices',
41 defaultMessage: '!!!Try again', 42 defaultMessage: 'Try again',
42 }, 43 },
43 updatedInfo: { 44 updatedInfo: {
44 id: 'settings.services.updatedInfo', 45 id: 'settings.services.updatedInfo',
45 defaultMessage: '!!!Your changes have been saved', 46 defaultMessage: 'Your changes have been saved',
46 }, 47 },
47 deletedInfo: { 48 deletedInfo: {
48 id: 'settings.services.deletedInfo', 49 id: 'settings.services.deletedInfo',
49 defaultMessage: '!!!Service has been deleted', 50 defaultMessage: 'Service has been deleted',
50 }, 51 },
51}); 52});
52 53
53export default @observer class ServicesDashboard extends Component { 54@observer
55class ServicesDashboard extends Component {
54 static propTypes = { 56 static propTypes = {
55 services: MobxPropTypes.arrayOrObservableArray.isRequired, 57 services: MobxPropTypes.arrayOrObservableArray.isRequired,
56 isLoading: PropTypes.bool.isRequired, 58 isLoading: PropTypes.bool.isRequired,
@@ -68,10 +70,6 @@ export default @observer class ServicesDashboard extends Component {
68 searchNeedle: '', 70 searchNeedle: '',
69 }; 71 };
70 72
71 static contextTypes = {
72 intl: intlShape,
73 };
74
75 render() { 73 render() {
76 const { 74 const {
77 services, 75 services,
@@ -85,7 +83,7 @@ export default @observer class ServicesDashboard extends Component {
85 status, 83 status,
86 searchNeedle, 84 searchNeedle,
87 } = this.props; 85 } = this.props;
88 const { intl } = this.context; 86 const { intl } = this.props;
89 87
90 return ( 88 return (
91 <div className="settings__main"> 89 <div className="settings__main">
@@ -96,7 +94,7 @@ export default @observer class ServicesDashboard extends Component {
96 {(services.length !== 0 || searchNeedle) && !isLoading && ( 94 {(services.length !== 0 || searchNeedle) && !isLoading && (
97 <SearchInput 95 <SearchInput
98 placeholder={intl.formatMessage(messages.searchService)} 96 placeholder={intl.formatMessage(messages.searchService)}
99 onChange={(needle) => filterServices({ needle })} 97 onChange={needle => filterServices({ needle })}
100 onReset={() => resetFilter()} 98 onReset={() => resetFilter()}
101 autoFocus 99 autoFocus
102 /> 100 />
@@ -145,7 +143,9 @@ export default @observer class ServicesDashboard extends Component {
145 </span> 143 </span>
146 {intl.formatMessage(messages.noServicesAdded)} 144 {intl.formatMessage(messages.noServicesAdded)}
147 </p> 145 </p>
148 <Link to="/settings/recipes" className="button">{intl.formatMessage(messages.discoverServices)}</Link> 146 <Link to="/settings/recipes" className="button">
147 {intl.formatMessage(messages.discoverServices)}
148 </Link>
149 </div> 149 </div>
150 )} 150 )}
151 {!isLoading && services.length === 0 && searchNeedle && ( 151 {!isLoading && services.length === 0 && searchNeedle && (
@@ -163,12 +163,16 @@ export default @observer class ServicesDashboard extends Component {
163 ) : ( 163 ) : (
164 <table className="service-table"> 164 <table className="service-table">
165 <tbody> 165 <tbody>
166 {services.map((service) => ( 166 {services.map(service => (
167 <ServiceItem 167 <ServiceItem
168 key={service.id} 168 key={service.id}
169 service={service} 169 service={service}
170 toggleAction={() => toggleService({ serviceId: service.id })} 170 toggleAction={() =>
171 goToServiceForm={() => goTo(`/settings/services/edit/${service.id}`)} 171 toggleService({ serviceId: service.id })
172 }
173 goToServiceForm={() =>
174 goTo(`/settings/services/edit/${service.id}`)
175 }
172 /> 176 />
173 ))} 177 ))}
174 </tbody> 178 </tbody>
@@ -176,12 +180,12 @@ export default @observer class ServicesDashboard extends Component {
176 )} 180 )}
177 181
178 <FAB> 182 <FAB>
179 <Link to="/settings/recipes"> 183 <Link to="/settings/recipes">+</Link>
180 +
181 </Link>
182 </FAB> 184 </FAB>
183 </div> 185 </div>
184 </div> 186 </div>
185 ); 187 );
186 } 188 }
187} 189}
190
191export default injectIntl(ServicesDashboard);
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index 6a919b902..0a468e342 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -3,7 +3,7 @@ import React, { Component } from 'react';
3import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
4import { observer } from 'mobx-react'; 4import { observer } from 'mobx-react';
5import prettyBytes from 'pretty-bytes'; 5import prettyBytes from 'pretty-bytes';
6import { defineMessages, intlShape } from 'react-intl'; 6import { defineMessages, injectIntl } from 'react-intl';
7 7
8import Form from '../../../lib/Form'; 8import Form from '../../../lib/Form';
9import Button from '../../ui/Button'; 9import Button from '../../ui/Button';
@@ -13,154 +13,173 @@ import Select from '../../ui/Select';
13import Input from '../../ui/Input'; 13import Input from '../../ui/Input';
14 14
15import { FRANZ_TRANSLATION, GITHUB_FRANZ_URL } from '../../../config'; 15import { FRANZ_TRANSLATION, GITHUB_FRANZ_URL } from '../../../config';
16import { DEFAULT_APP_SETTINGS, ferdiVersion, isMac, isWindows, lockFerdiShortcutKey, userDataPath, userDataRecipesPath } from '../../../environment'; 16import {
17 DEFAULT_APP_SETTINGS,
18 ferdiVersion,
19 isMac,
20 isWindows,
21 lockFerdiShortcutKey,
22 userDataPath,
23 userDataRecipesPath,
24} from '../../../environment';
17import { openPath } from '../../../helpers/url-helpers'; 25import { openPath } from '../../../helpers/url-helpers';
18import globalMessages from '../../../i18n/globalMessages'; 26import globalMessages from '../../../i18n/globalMessages';
19 27
20const messages = defineMessages({ 28const messages = defineMessages({
21 headlineGeneral: { 29 headlineGeneral: {
22 id: 'settings.app.headlineGeneral', 30 id: 'settings.app.headlineGeneral',
23 defaultMessage: '!!!General', 31 defaultMessage: 'General',
24 }, 32 },
25 sentryInfo: { 33 sentryInfo: {
26 id: 'settings.app.sentryInfo', 34 id: 'settings.app.sentryInfo',
27 defaultMessage: '!!!Sending telemetry data allows us to find errors in Ferdi - we will not send any personal information like your message data!', 35 defaultMessage:
36 'Sending telemetry data allows us to find errors in Ferdi - we will not send any personal information like your message data!',
28 }, 37 },
29 hibernateInfo: { 38 hibernateInfo: {
30 id: 'settings.app.hibernateInfo', 39 id: 'settings.app.hibernateInfo',
31 defaultMessage: '!!!By default, Ferdi will keep all your services open and loaded in the background so they are ready when you want to use them. Service Hibernation will unload your services after a specified amount. This is useful to save RAM or keeping services from slowing down your computer.', 40 defaultMessage:
41 'By default, Ferdi will keep all your services open and loaded in the background so they are ready when you want to use them. Service Hibernation will unload your services after a specified amount. This is useful to save RAM or keeping services from slowing down your computer.',
32 }, 42 },
33 inactivityLockInfo: { 43 inactivityLockInfo: {
34 id: 'settings.app.inactivityLockInfo', 44 id: 'settings.app.inactivityLockInfo',
35 defaultMessage: '!!!Minutes of inactivity, after which Ferdi should automatically lock. Use 0 to disable', 45 defaultMessage:
46 'Minutes of inactivity, after which Ferdi should automatically lock. Use 0 to disable',
36 }, 47 },
37 todoServerInfo: { 48 todoServerInfo: {
38 id: 'settings.app.todoServerInfo', 49 id: 'settings.app.todoServerInfo',
39 defaultMessage: '!!!This server will be used for the "Franz Todo" feature. (default: https://app.franztodos.com)', 50 defaultMessage:
51 'This server will be used for the "Franz Todo" feature. (default: https://app.franztodos.com)',
40 }, 52 },
41 lockedPassword: { 53 lockedPassword: {
42 id: 'settings.app.lockedPassword', 54 id: 'settings.app.lockedPassword',
43 defaultMessage: '!!!Password', 55 defaultMessage: 'Password',
44 }, 56 },
45 lockedPasswordInfo: { 57 lockedPasswordInfo: {
46 id: 'settings.app.lockedPasswordInfo', 58 id: 'settings.app.lockedPasswordInfo',
47 defaultMessage: '!!!Please make sure to set a password you\'ll remember.\nIf you loose this password, you will have to reinstall Ferdi.', 59 defaultMessage:
60 "Please make sure to set a password you'll remember.\nIf you loose this password, you will have to reinstall Ferdi.",
48 }, 61 },
49 lockInfo: { 62 lockInfo: {
50 id: 'settings.app.lockInfo', 63 id: 'settings.app.lockInfo',
51 defaultMessage: '!!!Password Lock allows you to keep your messages protected.\nUsing Password Lock, you will be prompted to enter your password everytime you start Ferdi or lock Ferdi yourself using the lock symbol in the bottom left corner or the shortcut {lockShortcut}.', 64 defaultMessage:
65 'Password Lock allows you to keep your messages protected.\nUsing Password Lock, you will be prompted to enter your password everytime you start Ferdi or lock Ferdi yourself using the lock symbol in the bottom left corner or the shortcut {lockShortcut}.',
52 }, 66 },
53 scheduledDNDTimeInfo: { 67 scheduledDNDTimeInfo: {
54 id: 'settings.app.scheduledDNDTimeInfo', 68 id: 'settings.app.scheduledDNDTimeInfo',
55 defaultMessage: '!!!Times in 24-Hour-Format. End time can be before start time (e.g. start 17:00, end 09:00) to enable Do-not-Disturb overnight.', 69 defaultMessage:
70 'Times in 24-Hour-Format. End time can be before start time (e.g. start 17:00, end 09:00) to enable Do-not-Disturb overnight.',
56 }, 71 },
57 scheduledDNDInfo: { 72 scheduledDNDInfo: {
58 id: 'settings.app.scheduledDNDInfo', 73 id: 'settings.app.scheduledDNDInfo',
59 defaultMessage: '!!!Scheduled Do-not-Disturb allows you to define a period of time in which you do not want to get Notifications from Ferdi.', 74 defaultMessage:
75 'Scheduled Do-not-Disturb allows you to define a period of time in which you do not want to get Notifications from Ferdi.',
60 }, 76 },
61 headlineLanguage: { 77 headlineLanguage: {
62 id: 'settings.app.headlineLanguage', 78 id: 'settings.app.headlineLanguage',
63 defaultMessage: '!!!Language', 79 defaultMessage: 'Language',
64 }, 80 },
65 headlineUpdates: { 81 headlineUpdates: {
66 id: 'settings.app.headlineUpdates', 82 id: 'settings.app.headlineUpdates',
67 defaultMessage: '!!!Updates', 83 defaultMessage: 'Updates',
68 }, 84 },
69 headlineAppearance: { 85 headlineAppearance: {
70 id: 'settings.app.headlineAppearance', 86 id: 'settings.app.headlineAppearance',
71 defaultMessage: '!!!Appearance', 87 defaultMessage: 'Appearance',
72 }, 88 },
73 universalDarkModeInfo: { 89 universalDarkModeInfo: {
74 id: 'settings.app.universalDarkModeInfo', 90 id: 'settings.app.universalDarkModeInfo',
75 defaultMessage: '!!!Universal Dark Mode tries to dynamically generate dark mode styles for services that are otherwise not currently supported.', 91 defaultMessage:
92 'Universal Dark Mode tries to dynamically generate dark mode styles for services that are otherwise not currently supported.',
76 }, 93 },
77 accentColorInfo: { 94 accentColorInfo: {
78 id: 'settings.app.accentColorInfo', 95 id: 'settings.app.accentColorInfo',
79 defaultMessage: '!!!Write your accent color in a CSS-compatible format. (Default: {defaultAccentColor})', 96 defaultMessage:
97 'Write your accent color in a CSS-compatible format. (Default: {defaultAccentColor})',
80 }, 98 },
81 headlinePrivacy: { 99 headlinePrivacy: {
82 id: 'settings.app.headlinePrivacy', 100 id: 'settings.app.headlinePrivacy',
83 defaultMessage: '!!!Privacy', 101 defaultMessage: 'Privacy',
84 }, 102 },
85 headlineAdvanced: { 103 headlineAdvanced: {
86 id: 'settings.app.headlineAdvanced', 104 id: 'settings.app.headlineAdvanced',
87 defaultMessage: '!!!Advanced', 105 defaultMessage: 'Advanced',
88 }, 106 },
89 translationHelp: { 107 translationHelp: {
90 id: 'settings.app.translationHelp', 108 id: 'settings.app.translationHelp',
91 defaultMessage: '!!!Help us to translate Ferdi into your language.', 109 defaultMessage: 'Help us to translate Ferdi into your language.',
92 }, 110 },
93 spellCheckerLanguageInfo: { 111 spellCheckerLanguageInfo: {
94 id: 'settings.app.spellCheckerLanguageInfo', 112 id: 'settings.app.spellCheckerLanguageInfo',
95 defaultMessage: '!!!Ferdi uses your Mac\'s build-in spellchecker to check for typos. If you want to change the languages the spellchecker checks for, you can do so in your Mac\'s System Preferences.', 113 defaultMessage:
114 "Ferdi uses your Mac's build-in spellchecker to check for typos. If you want to change the languages the spellchecker checks for, you can do so in your Mac's System Preferences.",
96 }, 115 },
97 subheadlineCache: { 116 subheadlineCache: {
98 id: 'settings.app.subheadlineCache', 117 id: 'settings.app.subheadlineCache',
99 defaultMessage: '!!!Cache', 118 defaultMessage: 'Cache',
100 }, 119 },
101 cacheInfo: { 120 cacheInfo: {
102 id: 'settings.app.cacheInfo', 121 id: 'settings.app.cacheInfo',
103 defaultMessage: '!!!Ferdi cache is currently using {size} of disk space.', 122 defaultMessage: 'Ferdi cache is currently using {size} of disk space.',
104 }, 123 },
105 cacheNotCleared: { 124 cacheNotCleared: {
106 id: 'settings.app.cacheNotCleared', 125 id: 'settings.app.cacheNotCleared',
107 defaultMessage: '!!!Couldn\'t clear all cache', 126 defaultMessage: "Couldn't clear all cache",
108 }, 127 },
109 buttonClearAllCache: { 128 buttonClearAllCache: {
110 id: 'settings.app.buttonClearAllCache', 129 id: 'settings.app.buttonClearAllCache',
111 defaultMessage: '!!!Clear cache', 130 defaultMessage: 'Clear cache',
112 }, 131 },
113 subheadlineFerdiProfile: { 132 subheadlineFerdiProfile: {
114 id: 'settings.app.subheadlineFerdiProfile', 133 id: 'settings.app.subheadlineFerdiProfile',
115 defaultMessage: '!!!Ferdi Profile', 134 defaultMessage: 'Ferdi Profile',
116 }, 135 },
117 buttonOpenFerdiProfileFolder: { 136 buttonOpenFerdiProfileFolder: {
118 id: 'settings.app.buttonOpenFerdiProfileFolder', 137 id: 'settings.app.buttonOpenFerdiProfileFolder',
119 defaultMessage: '!!!Open Profile folder', 138 defaultMessage: 'Open Profile folder',
120 }, 139 },
121 buttonOpenFerdiServiceRecipesFolder: { 140 buttonOpenFerdiServiceRecipesFolder: {
122 id: 'settings.app.buttonOpenFerdiServiceRecipesFolder', 141 id: 'settings.app.buttonOpenFerdiServiceRecipesFolder',
123 defaultMessage: '!!!Open Service Recipes folder', 142 defaultMessage: 'Open Service Recipes folder',
124 }, 143 },
125 buttonSearchForUpdate: { 144 buttonSearchForUpdate: {
126 id: 'settings.app.buttonSearchForUpdate', 145 id: 'settings.app.buttonSearchForUpdate',
127 defaultMessage: '!!!Check for updates', 146 defaultMessage: 'Check for updates',
128 }, 147 },
129 buttonInstallUpdate: { 148 buttonInstallUpdate: {
130 id: 'settings.app.buttonInstallUpdate', 149 id: 'settings.app.buttonInstallUpdate',
131 defaultMessage: '!!!Restart & install update', 150 defaultMessage: 'Restart & install update',
132 }, 151 },
133 updateStatusSearching: { 152 updateStatusSearching: {
134 id: 'settings.app.updateStatusSearching', 153 id: 'settings.app.updateStatusSearching',
135 defaultMessage: '!!!Is searching for update', 154 defaultMessage: 'Is searching for update',
136 }, 155 },
137 updateStatusAvailable: { 156 updateStatusAvailable: {
138 id: 'settings.app.updateStatusAvailable', 157 id: 'settings.app.updateStatusAvailable',
139 defaultMessage: '!!!Update available, downloading...', 158 defaultMessage: 'Update available, downloading...',
140 }, 159 },
141 updateStatusUpToDate: { 160 updateStatusUpToDate: {
142 id: 'settings.app.updateStatusUpToDate', 161 id: 'settings.app.updateStatusUpToDate',
143 defaultMessage: '!!!You are using the latest version of Ferdi', 162 defaultMessage: 'You are using the latest version of Ferdi',
144 }, 163 },
145 currentVersion: { 164 currentVersion: {
146 id: 'settings.app.currentVersion', 165 id: 'settings.app.currentVersion',
147 defaultMessage: '!!!Current version:', 166 defaultMessage: 'Current version:',
148 }, 167 },
149 appRestartRequired: { 168 appRestartRequired: {
150 id: 'settings.app.restartRequired', 169 id: 'settings.app.restartRequired',
151 defaultMessage: '!!!Changes require restart', 170 defaultMessage: 'Changes require restart',
152 }, 171 },
153 languageDisclaimer: { 172 languageDisclaimer: {
154 id: 'settings.app.languageDisclaimer', 173 id: 'settings.app.languageDisclaimer',
155 defaultMessage: '!!!Official translations are English & German. All other languages are community based translations.', 174 defaultMessage:
175 'Official translations are English & German. All other languages are community based translations.',
156 }, 176 },
157}); 177});
158 178
159const Hr = () => ( 179const Hr = () => <hr style={{ marginBottom: 20 }} />;
160 <hr style={{ marginBottom: 20 }} />
161);
162 180
163export default @observer class EditSettingsForm extends Component { 181@observer
182class EditSettingsForm extends Component {
164 static propTypes = { 183 static propTypes = {
165 checkForUpdates: PropTypes.func.isRequired, 184 checkForUpdates: PropTypes.func.isRequired,
166 installUpdate: PropTypes.func.isRequired, 185 installUpdate: PropTypes.func.isRequired,
@@ -184,14 +203,10 @@ export default @observer class EditSettingsForm extends Component {
184 isOnline: PropTypes.bool.isRequired, 203 isOnline: PropTypes.bool.isRequired,
185 }; 204 };
186 205
187 static contextTypes = {
188 intl: intlShape,
189 };
190
191 state = { 206 state = {
192 activeSetttingsTab: 'general', 207 activeSetttingsTab: 'general',
193 clearCacheButtonClicked: false, 208 clearCacheButtonClicked: false,
194 } 209 };
195 210
196 setActiveSettingsTab(tab) { 211 setActiveSettingsTab(tab) {
197 this.setState({ 212 this.setState({
@@ -199,14 +214,14 @@ export default @observer class EditSettingsForm extends Component {
199 }); 214 });
200 } 215 }
201 216
202 onClearCacheClicked=() => { 217 onClearCacheClicked = () => {
203 this.setState({ clearCacheButtonClicked: true }); 218 this.setState({ clearCacheButtonClicked: true });
204 } 219 };
205 220
206 submit(e) { 221 submit(e) {
207 e.preventDefault(); 222 e.preventDefault();
208 this.props.form.submit({ 223 this.props.form.submit({
209 onSuccess: (form) => { 224 onSuccess: form => {
210 const values = form.values(); 225 const values = form.values();
211 this.props.onSubmit(values); 226 this.props.onSubmit(values);
212 }, 227 },
@@ -236,7 +251,7 @@ export default @observer class EditSettingsForm extends Component {
236 hasAddedTodosAsService, 251 hasAddedTodosAsService,
237 isOnline, 252 isOnline,
238 } = this.props; 253 } = this.props;
239 const { intl } = this.context; 254 const { intl } = this.props;
240 255
241 let updateButtonLabelMessage = messages.buttonSearchForUpdate; 256 let updateButtonLabelMessage = messages.buttonSearchForUpdate;
242 if (isCheckingForUpdates) { 257 if (isCheckingForUpdates) {
@@ -247,10 +262,8 @@ export default @observer class EditSettingsForm extends Component {
247 updateButtonLabelMessage = messages.buttonSearchForUpdate; 262 updateButtonLabelMessage = messages.buttonSearchForUpdate;
248 } 263 }
249 264
250 const { 265 const { lockingFeatureEnabled, scheduledDNDEnabled } =
251 lockingFeatureEnabled, 266 window.ferdi.stores.settings.all.app;
252 scheduledDNDEnabled,
253 } = window.ferdi.stores.settings.all.app;
254 267
255 let cacheSize; 268 let cacheSize;
256 let notCleared; 269 let notCleared;
@@ -258,7 +271,10 @@ export default @observer class EditSettingsForm extends Component {
258 const cacheSizeBytes = getCacheSize(); 271 const cacheSizeBytes = getCacheSize();
259 if (typeof cacheSizeBytes === 'number') { 272 if (typeof cacheSizeBytes === 'number') {
260 cacheSize = prettyBytes(cacheSizeBytes); 273 cacheSize = prettyBytes(cacheSizeBytes);
261 notCleared = this.state.clearCacheButtonClicked && isClearingAllCache === false && cacheSizeBytes !== 0; 274 notCleared =
275 this.state.clearCacheButtonClicked &&
276 isClearingAllCache === false &&
277 cacheSizeBytes !== 0;
262 } else { 278 } else {
263 cacheSize = '…'; 279 cacheSize = '…';
264 notCleared = false; 280 notCleared = false;
@@ -275,58 +291,94 @@ export default @observer class EditSettingsForm extends Component {
275 </div> 291 </div>
276 <div className="settings__body"> 292 <div className="settings__body">
277 <form 293 <form
278 onSubmit={(e) => this.submit(e)} 294 onSubmit={e => this.submit(e)}
279 onChange={(e) => this.submit(e)} 295 onChange={e => this.submit(e)}
280 id="form" 296 id="form"
281 > 297 >
282 {/* Titles */} 298 {/* Titles */}
283 <div className="recipes__navigation"> 299 <div className="recipes__navigation">
284 <h2 300 <h2
285 id="general" 301 id="general"
286 className={this.state.activeSetttingsTab === 'general' ? 'badge badge--primary' : 'badge'} 302 className={
287 onClick={() => { this.setActiveSettingsTab('general'); }} 303 this.state.activeSetttingsTab === 'general'
304 ? 'badge badge--primary'
305 : 'badge'
306 }
307 onClick={() => {
308 this.setActiveSettingsTab('general');
309 }}
288 > 310 >
289 {intl.formatMessage(messages.headlineGeneral)} 311 {intl.formatMessage(messages.headlineGeneral)}
290 </h2> 312 </h2>
291 <h2 313 <h2
292 id="appearance" 314 id="appearance"
293 className={this.state.activeSetttingsTab === 'appearance' ? 'badge badge--primary' : 'badge'} 315 className={
294 onClick={() => { this.setActiveSettingsTab('appearance'); }} 316 this.state.activeSetttingsTab === 'appearance'
317 ? 'badge badge--primary'
318 : 'badge'
319 }
320 onClick={() => {
321 this.setActiveSettingsTab('appearance');
322 }}
295 > 323 >
296 {intl.formatMessage(messages.headlineAppearance)} 324 {intl.formatMessage(messages.headlineAppearance)}
297 </h2> 325 </h2>
298 <h2 326 <h2
299 id="privacy" 327 id="privacy"
300 className={this.state.activeSetttingsTab === 'privacy' ? 'badge badge--primary' : 'badge'} 328 className={
301 onClick={() => { this.setActiveSettingsTab('privacy'); }} 329 this.state.activeSetttingsTab === 'privacy'
330 ? 'badge badge--primary'
331 : 'badge'
332 }
333 onClick={() => {
334 this.setActiveSettingsTab('privacy');
335 }}
302 > 336 >
303 {intl.formatMessage(messages.headlinePrivacy)} 337 {intl.formatMessage(messages.headlinePrivacy)}
304 </h2> 338 </h2>
305 <h2 339 <h2
306 id="language" 340 id="language"
307 className={this.state.activeSetttingsTab === 'language' ? 'badge badge--primary' : 'badge'} 341 className={
308 onClick={() => { this.setActiveSettingsTab('language'); }} 342 this.state.activeSetttingsTab === 'language'
343 ? 'badge badge--primary'
344 : 'badge'
345 }
346 onClick={() => {
347 this.setActiveSettingsTab('language');
348 }}
309 > 349 >
310 {intl.formatMessage(messages.headlineLanguage)} 350 {intl.formatMessage(messages.headlineLanguage)}
311 </h2> 351 </h2>
312 <h2 352 <h2
313 id="advanced" 353 id="advanced"
314 className={this.state.activeSetttingsTab === 'advanced' ? 'badge badge--primary' : 'badge'} 354 className={
315 onClick={() => { this.setActiveSettingsTab('advanced'); }} 355 this.state.activeSetttingsTab === 'advanced'
356 ? 'badge badge--primary'
357 : 'badge'
358 }
359 onClick={() => {
360 this.setActiveSettingsTab('advanced');
361 }}
316 > 362 >
317 {intl.formatMessage(messages.headlineAdvanced)} 363 {intl.formatMessage(messages.headlineAdvanced)}
318 </h2> 364 </h2>
319 <h2 365 <h2
320 id="updates" 366 id="updates"
321 className={this.state.activeSetttingsTab === 'updates' ? 'badge badge--primary' : 'badge'} 367 className={
322 onClick={() => { this.setActiveSettingsTab('updates'); }} 368 this.state.activeSetttingsTab === 'updates'
369 ? 'badge badge--primary'
370 : 'badge'
371 }
372 onClick={() => {
373 this.setActiveSettingsTab('updates');
374 }}
323 > 375 >
324 {intl.formatMessage(messages.headlineUpdates)} 376 {intl.formatMessage(messages.headlineUpdates)}
325 </h2> 377 </h2>
326 </div> 378 </div>
327 379
328 {/* General */} 380 {/* General */}
329 { this.state.activeSetttingsTab === 'general' && ( 381 {this.state.activeSetttingsTab === 'general' && (
330 <div> 382 <div>
331 <Toggle field={form.$('autoLaunchOnStart')} /> 383 <Toggle field={form.$('autoLaunchOnStart')} />
332 <Toggle field={form.$('runInBackground')} /> 384 <Toggle field={form.$('runInBackground')} />
@@ -334,12 +386,8 @@ export default @observer class EditSettingsForm extends Component {
334 <Toggle field={form.$('enableSystemTray')} /> 386 <Toggle field={form.$('enableSystemTray')} />
335 <Toggle field={form.$('reloadAfterResume')} /> 387 <Toggle field={form.$('reloadAfterResume')} />
336 <Toggle field={form.$('startMinimized')} /> 388 <Toggle field={form.$('startMinimized')} />
337 {isWindows && ( 389 {isWindows && <Toggle field={form.$('minimizeToSystemTray')} />}
338 <Toggle field={form.$('minimizeToSystemTray')} /> 390 {isWindows && <Toggle field={form.$('closeToSystemTray')} />}
339 )}
340 {isWindows && (
341 <Toggle field={form.$('closeToSystemTray')} />
342 )}
343 <Select field={form.$('navigationBarBehaviour')} /> 391 <Select field={form.$('navigationBarBehaviour')} />
344 392
345 <Hr /> 393 <Hr />
@@ -349,12 +397,13 @@ export default @observer class EditSettingsForm extends Component {
349 <p 397 <p
350 className="settings__message" 398 className="settings__message"
351 style={{ 399 style={{
352 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem', 400 borderTop: 0,
401 marginTop: 0,
402 paddingTop: 0,
403 marginBottom: '2rem',
353 }} 404 }}
354 > 405 >
355 <span> 406 <span>{intl.formatMessage(messages.hibernateInfo)}</span>
356 { intl.formatMessage(messages.hibernateInfo) }
357 </span>
358 </p> 407 </p>
359 408
360 <Select field={form.$('wakeUpStrategy')} /> 409 <Select field={form.$('wakeUpStrategy')} />
@@ -374,20 +423,24 @@ export default @observer class EditSettingsForm extends Component {
374 {isTodosActivated && ( 423 {isTodosActivated && (
375 <div> 424 <div>
376 <Select field={form.$('predefinedTodoServer')} /> 425 <Select field={form.$('predefinedTodoServer')} />
377 {form.$('predefinedTodoServer').value === 'isUsingCustomTodoService' && ( 426 {form.$('predefinedTodoServer').value ===
427 'isUsingCustomTodoService' && (
378 <div> 428 <div>
379 <Input 429 <Input
380 placeholder="Todo Server" 430 placeholder="Todo Server"
381 onChange={(e) => this.submit(e)} 431 onChange={e => this.submit(e)}
382 field={form.$('customTodoServer')} 432 field={form.$('customTodoServer')}
383 /> 433 />
384 <p 434 <p
385 className="settings__message" 435 className="settings__message"
386 style={{ 436 style={{
387 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem', 437 borderTop: 0,
438 marginTop: 0,
439 paddingTop: 0,
440 marginBottom: '2rem',
388 }} 441 }}
389 > 442 >
390 { intl.formatMessage(messages.todoServerInfo) } 443 {intl.formatMessage(messages.todoServerInfo)}
391 </p> 444 </p>
392 </div> 445 </div>
393 )} 446 )}
@@ -400,56 +453,58 @@ export default @observer class EditSettingsForm extends Component {
400 <Toggle field={form.$('scheduledDNDEnabled')} /> 453 <Toggle field={form.$('scheduledDNDEnabled')} />
401 {scheduledDNDEnabled && ( 454 {scheduledDNDEnabled && (
402 <> 455 <>
403 <div style={{ 456 <div
404 display: 'flex', 457 style={{
405 justifyContent: 'center', 458 display: 'flex',
406 }} 459 justifyContent: 'center',
407 >
408 <div style={{
409 padding: '0 1rem',
410 width: '100%',
411 }} 460 }}
461 >
462 <div
463 style={{
464 padding: '0 1rem',
465 width: '100%',
466 }}
412 > 467 >
413 <Input 468 <Input
414 placeholder="17:00" 469 placeholder="17:00"
415 onChange={(e) => this.submit(e)} 470 onChange={e => this.submit(e)}
416 field={form.$('scheduledDNDStart')} 471 field={form.$('scheduledDNDStart')}
417 type="time" 472 type="time"
418 /> 473 />
419 </div> 474 </div>
420 <div style={{ 475 <div
421 padding: '0 1rem', 476 style={{
422 width: '100%', 477 padding: '0 1rem',
423 }} 478 width: '100%',
479 }}
424 > 480 >
425 <Input 481 <Input
426 placeholder="09:00" 482 placeholder="09:00"
427 onChange={(e) => this.submit(e)} 483 onChange={e => this.submit(e)}
428 field={form.$('scheduledDNDEnd')} 484 field={form.$('scheduledDNDEnd')}
429 type="time" 485 type="time"
430 /> 486 />
431 </div> 487 </div>
432 </div> 488 </div>
433 <p> 489 <p>{intl.formatMessage(messages.scheduledDNDTimeInfo)}</p>
434 { intl.formatMessage(messages.scheduledDNDTimeInfo) }
435 </p>
436 </> 490 </>
437 )} 491 )}
438 <p 492 <p
439 className="settings__message" 493 className="settings__message"
440 style={{ 494 style={{
441 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem', 495 borderTop: 0,
496 marginTop: 0,
497 paddingTop: 0,
498 marginBottom: '2rem',
442 }} 499 }}
443 > 500 >
444 <span> 501 <span>{intl.formatMessage(messages.scheduledDNDInfo)}</span>
445 { intl.formatMessage(messages.scheduledDNDInfo) }
446 </span>
447 </p> 502 </p>
448 </div> 503 </div>
449 )} 504 )}
450 505
451 {/* Appearance */} 506 {/* Appearance */}
452 { this.state.activeSetttingsTab === 'appearance' && ( 507 {this.state.activeSetttingsTab === 'appearance' && (
453 <div> 508 <div>
454 <Toggle field={form.$('showDisabledServices')} /> 509 <Toggle field={form.$('showDisabledServices')} />
455 <Toggle field={form.$('showMessageBadgeWhenMuted')} /> 510 <Toggle field={form.$('showMessageBadgeWhenMuted')} />
@@ -459,21 +514,26 @@ export default @observer class EditSettingsForm extends Component {
459 <Hr /> 514 <Hr />
460 515
461 <Toggle field={form.$('adaptableDarkMode')} /> 516 <Toggle field={form.$('adaptableDarkMode')} />
462 {!isAdaptableDarkModeEnabled && <Toggle field={form.$('darkMode')} />} 517 {!isAdaptableDarkModeEnabled && (
518 <Toggle field={form.$('darkMode')} />
519 )}
463 {(isDarkmodeEnabled || isAdaptableDarkModeEnabled) && ( 520 {(isDarkmodeEnabled || isAdaptableDarkModeEnabled) && (
464 <> 521 <>
465 <Toggle field={form.$('universalDarkMode')} /> 522 <Toggle field={form.$('universalDarkMode')} />
466 <p 523 <p
467 className="settings__message" 524 className="settings__message"
468 style={{ 525 style={{
469 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem', 526 borderTop: 0,
470 }} 527 marginTop: 0,
471 > 528 paddingTop: 0,
472 <span> 529 marginBottom: '2rem',
473 { intl.formatMessage(messages.universalDarkModeInfo) } 530 }}
474 </span> 531 >
475 </p> 532 <span>
476 </> 533 {intl.formatMessage(messages.universalDarkModeInfo)}
534 </span>
535 </p>
536 </>
477 )} 537 )}
478 538
479 <Hr /> 539 <Hr />
@@ -491,23 +551,25 @@ export default @observer class EditSettingsForm extends Component {
491 551
492 <Input 552 <Input
493 placeholder="Accent Color" 553 placeholder="Accent Color"
494 onChange={(e) => this.submit(e)} 554 onChange={e => this.submit(e)}
495 field={form.$('accentColor')} 555 field={form.$('accentColor')}
496 /> 556 />
497 <p> 557 <p>
498 {intl.formatMessage(messages.accentColorInfo, 558 {intl.formatMessage(messages.accentColorInfo, {
499 { defaultAccentColor: DEFAULT_APP_SETTINGS.accentColor })} 559 defaultAccentColor: DEFAULT_APP_SETTINGS.accentColor,
560 })}
500 </p> 561 </p>
501 </div> 562 </div>
502 )} 563 )}
503 564
504 {/* Privacy */} 565 {/* Privacy */}
505 { this.state.activeSetttingsTab === 'privacy' && ( 566 {this.state.activeSetttingsTab === 'privacy' && (
506 <div> 567 <div>
507 <Toggle field={form.$('privateNotifications')} /> 568 <Toggle field={form.$('privateNotifications')} />
508 <Toggle field={form.$('clipboardNotifications')} /> 569 <Toggle field={form.$('clipboardNotifications')} />
509 {(isWindows || isMac) && ( 570 {(isWindows || isMac) && (
510 <Toggle field={form.$('notifyTaskBarOnMessage')} />)} 571 <Toggle field={form.$('notifyTaskBarOnMessage')} />
572 )}
511 573
512 <Hr /> 574 <Hr />
513 575
@@ -516,8 +578,12 @@ export default @observer class EditSettingsForm extends Component {
516 <Hr /> 578 <Hr />
517 579
518 <Toggle field={form.$('sentry')} /> 580 <Toggle field={form.$('sentry')} />
519 <p className="settings__help">{intl.formatMessage(messages.sentryInfo)}</p> 581 <p className="settings__help">
520 <p className="settings__help">{intl.formatMessage(messages.appRestartRequired)}</p> 582 {intl.formatMessage(messages.sentryInfo)}
583 </p>
584 <p className="settings__help">
585 {intl.formatMessage(messages.appRestartRequired)}
586 </p>
521 587
522 <Hr /> 588 <Hr />
523 589
@@ -530,57 +596,60 @@ export default @observer class EditSettingsForm extends Component {
530 596
531 <Input 597 <Input
532 placeholder={intl.formatMessage(messages.lockedPassword)} 598 placeholder={intl.formatMessage(messages.lockedPassword)}
533 onChange={(e) => this.submit(e)} 599 onChange={e => this.submit(e)}
534 field={form.$('lockedPassword')} 600 field={form.$('lockedPassword')}
535 type="password" 601 type="password"
536 scorePassword 602 scorePassword
537 showPasswordToggle 603 showPasswordToggle
538 /> 604 />
539 <p> 605 <p>{intl.formatMessage(messages.lockedPasswordInfo)}</p>
540 { intl.formatMessage(messages.lockedPasswordInfo) }
541 </p>
542 606
543 <Input 607 <Input
544 placeholder="Lock after inactivity" 608 placeholder="Lock after inactivity"
545 onChange={(e) => this.submit(e)} 609 onChange={e => this.submit(e)}
546 field={form.$('inactivityLock')} 610 field={form.$('inactivityLock')}
547 autoFocus 611 autoFocus
548 /> 612 />
549 <p> 613 <p>{intl.formatMessage(messages.inactivityLockInfo)}</p>
550 { intl.formatMessage(messages.inactivityLockInfo) }
551 </p>
552 </> 614 </>
553 )} 615 )}
554 <p 616 <p
555 className="settings__message" 617 className="settings__message"
556 style={{ 618 style={{
557 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem', 619 borderTop: 0,
620 marginTop: 0,
621 paddingTop: 0,
622 marginBottom: '2rem',
558 }} 623 }}
559 > 624 >
560 <span> 625 <span>
561 { intl.formatMessage(messages.lockInfo, { lockShortcut: `${lockFerdiShortcutKey(false)}` }) } 626 {intl.formatMessage(messages.lockInfo, {
627 lockShortcut: `${lockFerdiShortcutKey(false)}`,
628 })}
562 </span> 629 </span>
563 </p> 630 </p>
564 </div> 631 </div>
565 )} 632 )}
566 633
567 {/* Language */} 634 {/* Language */}
568 { this.state.activeSetttingsTab === 'language' && ( 635 {this.state.activeSetttingsTab === 'language' && (
569 <div> 636 <div>
570 <Select field={form.$('locale')} showLabel={false} /> 637 <Select field={form.$('locale')} showLabel={false} />
571 638
572 <Hr /> 639 <Hr />
573 640
574 <Toggle 641 <Toggle field={form.$('enableSpellchecking')} />
575 field={form.$('enableSpellchecking')}
576 />
577 {!isMac && form.$('enableSpellchecking').value && ( 642 {!isMac && form.$('enableSpellchecking').value && (
578 <Select field={form.$('spellcheckerLanguage')} /> 643 <Select field={form.$('spellcheckerLanguage')} />
579 )} 644 )}
580 {isMac && form.$('enableSpellchecking').value && ( 645 {isMac && form.$('enableSpellchecking').value && (
581 <p className="settings__help">{intl.formatMessage(messages.spellCheckerLanguageInfo)}</p> 646 <p className="settings__help">
647 {intl.formatMessage(messages.spellCheckerLanguageInfo)}
648 </p>
582 )} 649 )}
583 <p className="settings__help">{intl.formatMessage(messages.appRestartRequired)}</p> 650 <p className="settings__help">
651 {intl.formatMessage(messages.appRestartRequired)}
652 </p>
584 653
585 <Hr /> 654 <Hr />
586 655
@@ -590,52 +659,54 @@ export default @observer class EditSettingsForm extends Component {
590 className="link" 659 className="link"
591 rel="noreferrer" 660 rel="noreferrer"
592 > 661 >
593 {intl.formatMessage(messages.translationHelp)} 662 {intl.formatMessage(messages.translationHelp)}{' '}
594 {' '}
595 <i className="mdi mdi-open-in-new" /> 663 <i className="mdi mdi-open-in-new" />
596 </a> 664 </a>
597 </div> 665 </div>
598 )} 666 )}
599 667
600 {/* Advanced */} 668 {/* Advanced */}
601 { this.state.activeSetttingsTab === 'advanced' && ( 669 {this.state.activeSetttingsTab === 'advanced' && (
602 <div> 670 <div>
603 <Toggle field={form.$('enableGPUAcceleration')} /> 671 <Toggle field={form.$('enableGPUAcceleration')} />
604 <p className="settings__help indented__help">{intl.formatMessage(messages.appRestartRequired)}</p> 672 <p className="settings__help indented__help">
673 {intl.formatMessage(messages.appRestartRequired)}
674 </p>
605 675
606 <Hr /> 676 <Hr />
607 677
608 <Input 678 <Input
609 placeholder="User Agent" 679 placeholder="User Agent"
610 onChange={(e) => this.submit(e)} 680 onChange={e => this.submit(e)}
611 field={form.$('userAgentPref')} 681 field={form.$('userAgentPref')}
612 /> 682 />
613 <p className="settings__help">{intl.formatMessage(globalMessages.userAgentHelp)}</p> 683 <p className="settings__help">
614 <p className="settings__help">{intl.formatMessage(messages.appRestartRequired)}</p> 684 {intl.formatMessage(globalMessages.userAgentHelp)}
685 </p>
686 <p className="settings__help">
687 {intl.formatMessage(messages.appRestartRequired)}
688 </p>
615 689
616 <Hr /> 690 <Hr />
617 691
618 <div className="settings__settings-group"> 692 <div className="settings__settings-group">
619 <h3> 693 <h3>{intl.formatMessage(messages.subheadlineCache)}</h3>
620 {intl.formatMessage(messages.subheadlineCache)}
621 </h3>
622 <p> 694 <p>
623 {intl.formatMessage(messages.cacheInfo, { 695 {intl.formatMessage(messages.cacheInfo, {
624 size: cacheSize, 696 size: cacheSize,
625 })} 697 })}
626 </p> 698 </p>
627 { 699 {notCleared && (
628 notCleared && ( 700 <p>{intl.formatMessage(messages.cacheNotCleared)}</p>
629 <p> 701 )}
630 {intl.formatMessage(messages.cacheNotCleared)}
631 </p>
632 )
633 }
634 <p> 702 <p>
635 <Button 703 <Button
636 buttonType="secondary" 704 buttonType="secondary"
637 label={intl.formatMessage(messages.buttonClearAllCache)} 705 label={intl.formatMessage(messages.buttonClearAllCache)}
638 onClick={() => { onClearAllCache(); this.onClearCacheClicked(); }} 706 onClick={() => {
707 onClearAllCache();
708 this.onClearCacheClicked();
709 }}
639 disabled={isClearingAllCache} 710 disabled={isClearingAllCache}
640 loaded={!isClearingAllCache} 711 loaded={!isClearingAllCache}
641 /> 712 />
@@ -652,13 +723,17 @@ export default @observer class EditSettingsForm extends Component {
652 <div className="settings__open-settings-file-container"> 723 <div className="settings__open-settings-file-container">
653 <Button 724 <Button
654 buttonType="secondary" 725 buttonType="secondary"
655 label={intl.formatMessage(messages.buttonOpenFerdiProfileFolder)} 726 label={intl.formatMessage(
727 messages.buttonOpenFerdiProfileFolder,
728 )}
656 className="settings__open-settings-file-button" 729 className="settings__open-settings-file-button"
657 onClick={() => openPath(profileFolder)} 730 onClick={() => openPath(profileFolder)}
658 /> 731 />
659 <Button 732 <Button
660 buttonType="secondary" 733 buttonType="secondary"
661 label={intl.formatMessage(messages.buttonOpenFerdiServiceRecipesFolder)} 734 label={intl.formatMessage(
735 messages.buttonOpenFerdiServiceRecipesFolder,
736 )}
662 className="settings__open-settings-file-button" 737 className="settings__open-settings-file-button"
663 onClick={() => openPath(recipeFolder)} 738 onClick={() => openPath(recipeFolder)}
664 /> 739 />
@@ -669,66 +744,78 @@ export default @observer class EditSettingsForm extends Component {
669 )} 744 )}
670 745
671 {/* Updates */} 746 {/* Updates */}
672 { this.state.activeSetttingsTab === 'updates' && ( 747 {this.state.activeSetttingsTab === 'updates' && (
673 <div>
674 <Toggle field={form.$('automaticUpdates')} />
675 {automaticUpdates && (
676 <div> 748 <div>
677 <Toggle field={form.$('beta')} /> 749 <Toggle field={form.$('automaticUpdates')} />
678 <ToggleRaw 750 {automaticUpdates && (
679 field={{ 751 <div>
680 value: isNightlyEnabled, 752 <Toggle field={form.$('beta')} />
681 id: 'nightly', 753 <ToggleRaw
682 label: 'Include nightly versions', 754 field={{
683 name: 'Nightly builds', 755 value: isNightlyEnabled,
684 }} 756 id: 'nightly',
685 onChange={window.ferdi.features.nightlyBuilds.toggleFeature} 757 label: 'Include nightly versions',
686 /> 758 name: 'Nightly builds',
687 {updateIsReadyToInstall ? ( 759 }}
688 <Button 760 onChange={
689 label={intl.formatMessage(messages.buttonInstallUpdate)} 761 window.ferdi.features.nightlyBuilds.toggleFeature
690 onClick={installUpdate} 762 }
691 /> 763 />
692 ) : ( 764 {updateIsReadyToInstall ? (
693 <Button 765 <Button
694 buttonType="secondary" 766 label={intl.formatMessage(messages.buttonInstallUpdate)}
695 label={intl.formatMessage(updateButtonLabelMessage)} 767 onClick={installUpdate}
696 onClick={checkForUpdates} 768 />
697 disabled={!automaticUpdates || isCheckingForUpdates || isUpdateAvailable || !isOnline} 769 ) : (
698 loaded={!isCheckingForUpdates || !isUpdateAvailable} 770 <Button
699 /> 771 buttonType="secondary"
772 label={intl.formatMessage(updateButtonLabelMessage)}
773 onClick={checkForUpdates}
774 disabled={
775 !automaticUpdates ||
776 isCheckingForUpdates ||
777 isUpdateAvailable ||
778 !isOnline
779 }
780 loaded={!isCheckingForUpdates || !isUpdateAvailable}
781 />
782 )}
783 <br />
784 </div>
785 )}
786 {intl.formatMessage(messages.currentVersion)} {ferdiVersion}
787 {noUpdateAvailable && (
788 <>
789 <br />
790 <br />
791 {intl.formatMessage(messages.updateStatusUpToDate)}
792 </>
700 )} 793 )}
701 <br /> 794 <p className="settings__message">
795 <span className="mdi mdi-github-face" />
796 <span>
797 Ferdi is based on{' '}
798 <a
799 href={`${GITHUB_FRANZ_URL}/franz`}
800 target="_blank"
801 rel="noreferrer"
802 >
803 Franz
804 </a>
805 , a project published under the{' '}
806 <a
807 href={`${GITHUB_FRANZ_URL}/franz/blob/master/LICENSE`}
808 target="_blank"
809 rel="noreferrer"
810 >
811 Apache-2.0 License
812 </a>
813 </span>
814 <br />
815 <span className="mdi mdi-information" />
816 {intl.formatMessage(messages.languageDisclaimer)}
817 </p>
702 </div> 818 </div>
703 )}
704 {intl.formatMessage(messages.currentVersion)}
705 {' '}
706 {ferdiVersion}
707 {noUpdateAvailable && (
708 <>
709 <br />
710 <br />
711 {intl.formatMessage(messages.updateStatusUpToDate)}
712 </>
713 )}
714 <p className="settings__message">
715 <span className="mdi mdi-github-face" />
716 <span>
717
718 Ferdi is based on
719 {' '}
720 <a href={`${GITHUB_FRANZ_URL}/franz`} target="_blank" rel="noreferrer">Franz</a>
721
722 , a project published
723 under the
724 {' '}
725 <a href={`${GITHUB_FRANZ_URL}/franz/blob/master/LICENSE`} target="_blank" rel="noreferrer">Apache-2.0 License</a>
726 </span>
727 <br />
728 <span className="mdi mdi-information" />
729 {intl.formatMessage(messages.languageDisclaimer)}
730 </p>
731 </div>
732 )} 819 )}
733 </form> 820 </form>
734 </div> 821 </div>
@@ -736,3 +823,5 @@ export default @observer class EditSettingsForm extends Component {
736 ); 823 );
737 } 824 }
738} 825}
826
827export default injectIntl(EditSettingsForm);
diff --git a/src/components/settings/supportFerdi/SupportFerdiDashboard.js b/src/components/settings/supportFerdi/SupportFerdiDashboard.js
index b84e06739..c4d4bd72f 100644
--- a/src/components/settings/supportFerdi/SupportFerdiDashboard.js
+++ b/src/components/settings/supportFerdi/SupportFerdiDashboard.js
@@ -1,76 +1,77 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import { defineMessages, FormattedHTMLMessage, intlShape } from 'react-intl'; 2import { defineMessages, injectIntl } from 'react-intl';
3import { BrowserWindow } from '@electron/remote'; 3import { BrowserWindow } from '@electron/remote';
4import InfoBar from '../../ui/InfoBar'; 4import InfoBar from '../../ui/InfoBar';
5 5
6const messages = defineMessages({ 6const messages = defineMessages({
7 headline: { 7 headline: {
8 id: 'settings.supportFerdi.headline', 8 id: 'settings.supportFerdi.headline',
9 defaultMessage: '!!!About Ferdi', 9 defaultMessage: 'About Ferdi',
10 }, 10 },
11 title: { 11 title: {
12 id: 'settings.supportFerdi.title', 12 id: 'settings.supportFerdi.title',
13 defaultMessage: '!!!Do you like Ferdi?', 13 defaultMessage: 'Do you like Ferdi?',
14 }, 14 },
15 aboutIntro: { 15 aboutIntro: {
16 id: 'settings.supportFerdi.aboutIntro', 16 id: 'settings.supportFerdi.aboutIntro',
17 defaultMessage: '!!!<p>Ferdi is an open-source and a community-lead application.</p><p>Thanks to the people who make this possbile:</p>', 17 defaultMessage:
18 '<p>Ferdi is an open-source and a community-lead application.</p><p>Thanks to the people who make this possbile:</p>',
18 }, 19 },
19 textListContributors: { 20 textListContributors: {
20 id: 'settings.supportFerdi.textListContributors', 21 id: 'settings.supportFerdi.textListContributors',
21 defaultMessage: '!!!Full list of contributor', 22 defaultMessage: 'Full list of contributor',
22 }, 23 },
23 textListContributorsHere: { 24 textListContributorsHere: {
24 id: 'settings.supportFerdi.textListContributorsHere', 25 id: 'settings.supportFerdi.textListContributorsHere',
25 defaultMessage: '!!!here', 26 defaultMessage: 'here',
26 }, 27 },
27 textVolunteers: { 28 textVolunteers: {
28 id: 'settings.supportFerdi.textVolunteers', 29 id: 'settings.supportFerdi.textVolunteers',
29 defaultMessage: '!!!The development of Ferdi is done by volunteers. People who use Ferdi like you. They maintain, fix, and improve Ferdi in their spare time.', 30 defaultMessage:
31 'The development of Ferdi is done by volunteers. People who use Ferdi like you. They maintain, fix, and improve Ferdi in their spare time.',
30 }, 32 },
31 textSupportWelcome: { 33 textSupportWelcome: {
32 id: 'settings.supportFerdi.textSupportWelcome', 34 id: 'settings.supportFerdi.textSupportWelcome',
33 defaultMessage: '!!!Support is always welcome. You can find a list of the help we need', 35 defaultMessage:
36 'Support is always welcome. You can find a list of the help we need',
34 }, 37 },
35 textSupportWelcomeHere: { 38 textSupportWelcomeHere: {
36 id: 'settings.supportFerdi.textSupportWelcomeHere', 39 id: 'settings.supportFerdi.textSupportWelcomeHere',
37 defaultMessage: '!!!here', 40 defaultMessage: 'here',
38 }, 41 },
39 textExpenses: { 42 textExpenses: {
40 id: 'settings.supportFerdi.textExpenses', 43 id: 'settings.supportFerdi.textExpenses',
41 defaultMessage: '!!!While volunteers do most of the work, we still need to pay for servers and certificates. As a community, we are fully transparent on funds we collect and spend - see our', 44 defaultMessage:
45 'While volunteers do most of the work, we still need to pay for servers and certificates. As a community, we are fully transparent on funds we collect and spend - see our',
42 }, 46 },
43 textOpenCollective: { 47 textOpenCollective: {
44 id: 'settings.supportFerdi.textOpenCollective', 48 id: 'settings.supportFerdi.textOpenCollective',
45 defaultMessage: '!!!Open Collective', 49 defaultMessage: 'Open Collective',
46 }, 50 },
47 textDonation: { 51 textDonation: {
48 id: 'settings.supportFerdi.textDonation', 52 id: 'settings.supportFerdi.textDonation',
49 defaultMessage: '!!!If you feel like supporting Ferdi development with a donation, you can do so on both,', 53 defaultMessage:
54 'If you feel like supporting Ferdi development with a donation, you can do so on both,',
50 }, 55 },
51 textDonationAnd: { 56 textDonationAnd: {
52 id: 'settings.supportFerdi.textDonationAnd', 57 id: 'settings.supportFerdi.textDonationAnd',
53 defaultMessage: '!!!and', 58 defaultMessage: 'and',
54 }, 59 },
55 textGitHubSponsors: { 60 textGitHubSponsors: {
56 id: 'settings.supportFerdi.textGitHubSponsors', 61 id: 'settings.supportFerdi.textGitHubSponsors',
57 defaultMessage: '!!!GitHub Sponsors', 62 defaultMessage: 'GitHub Sponsors',
58 }, 63 },
59 openSurvey: { 64 openSurvey: {
60 id: 'settings.supportFerdi.openSurvey', 65 id: 'settings.supportFerdi.openSurvey',
61 defaultMessage: '!!!Open Survey', 66 defaultMessage: 'Open Survey',
62 }, 67 },
63 bannerText: { 68 bannerText: {
64 id: 'settings.supportFerdi.bannerText', 69 id: 'settings.supportFerdi.bannerText',
65 defaultMessage: '!!!Do you want to help us improve Ferdi?', 70 defaultMessage: 'Do you want to help us improve Ferdi?',
66 }, 71 },
67}); 72});
68 73
69class SupportFerdiDashboard extends Component { 74class SupportFerdiDashboard extends Component {
70 static contextTypes = {
71 intl: intlShape,
72 };
73
74 openSurveyWindow() { 75 openSurveyWindow() {
75 let win = new BrowserWindow({ width: 670, height: 400 }); 76 let win = new BrowserWindow({ width: 670, height: 400 });
76 win.on('closed', () => { 77 win.on('closed', () => {
@@ -81,7 +82,9 @@ class SupportFerdiDashboard extends Component {
81 } 82 }
82 83
83 render() { 84 render() {
84 const { intl } = this.context; 85 const { intl } = this.props;
86
87 const aboutIntro = intl.formatMessage(messages.aboutIntro);
85 88
86 return ( 89 return (
87 <div className="settings__main"> 90 <div className="settings__main">
@@ -94,22 +97,67 @@ class SupportFerdiDashboard extends Component {
94 <h1>{intl.formatMessage(messages.title)}</h1> 97 <h1>{intl.formatMessage(messages.title)}</h1>
95 <div> 98 <div>
96 <p className="settings__support-badges"> 99 <p className="settings__support-badges">
97 <a href="https://github.com/getferdi/ferdi" target="_blank" rel="noreferrer"><img alt="GitHub Stars" src="https://img.shields.io/github/stars/getferdi/ferdi?style=social" /></a> 100 <a
98 <a href="https://twitter.com/getferdi/" target="_blank" rel="noreferrer"><img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/getferdi?label=Follow&style=social" /></a> 101 href="https://github.com/getferdi/ferdi"
99 <a href="https://opencollective.com/getferdi#section-contributors" target="_blank" rel="noreferrer"><img alt="Open Collective backers" src="https://img.shields.io/opencollective/backers/getferdi?logo=open-collective" /></a> 102 target="_blank"
100 <a href="https://opencollective.com/getferdi#section-contributors" target="_blank" rel="noreferrer"><img alt="Open Collective sponsors" src="https://img.shields.io/opencollective/sponsors/getferdi?logo=open-collective" /></a> 103 rel="noreferrer"
104 >
105 <img
106 alt="GitHub Stars"
107 src="https://img.shields.io/github/stars/getferdi/ferdi?style=social"
108 />
109 </a>
110 <a
111 href="https://twitter.com/getferdi/"
112 target="_blank"
113 rel="noreferrer"
114 >
115 <img
116 alt="Twitter Follow"
117 src="https://img.shields.io/twitter/follow/getferdi?label=Follow&style=social"
118 />
119 </a>
120 <a
121 href="https://opencollective.com/getferdi#section-contributors"
122 target="_blank"
123 rel="noreferrer"
124 >
125 <img
126 alt="Open Collective backers"
127 src="https://img.shields.io/opencollective/backers/getferdi?logo=open-collective"
128 />
129 </a>
130 <a
131 href="https://opencollective.com/getferdi#section-contributors"
132 target="_blank"
133 rel="noreferrer"
134 >
135 <img
136 alt="Open Collective sponsors"
137 src="https://img.shields.io/opencollective/sponsors/getferdi?logo=open-collective"
138 />
139 </a>
101 </p> 140 </p>
102 <FormattedHTMLMessage {...messages.aboutIntro} /> 141 <span dangerouslySetInnerHTML={{ __html: aboutIntro }} />
103 <br /> 142 <br />
104 <br /> 143 <br />
105 <p> 144 <p>
106 <a href="#contributors-via-opencollective"> 145 <a href="#contributors-via-opencollective">
107 <img alt="GitHub contributors (non-exhaustive)" width="100%" src="https://opencollective.com/getferdi/contributors.svg?width=642&button=false" /> 146 <img
147 alt="GitHub contributors (non-exhaustive)"
148 width="100%"
149 src="https://opencollective.com/getferdi/contributors.svg?width=642&button=false"
150 />
108 </a> 151 </a>
109 </p> 152 </p>
110 <p> 153 <p>
111 {intl.formatMessage(messages.textListContributors)} 154 {intl.formatMessage(messages.textListContributors)}
112 <a href="https://github.com/getferdi/ferdi#contributors-" target="_blank" className="link" rel="noreferrer"> 155 <a
156 href="https://github.com/getferdi/ferdi#contributors-"
157 target="_blank"
158 className="link"
159 rel="noreferrer"
160 >
113 {' '} 161 {' '}
114 {intl.formatMessage(messages.textListContributorsHere)} 162 {intl.formatMessage(messages.textListContributorsHere)}
115 <i className="mdi mdi-open-in-new" /> 163 <i className="mdi mdi-open-in-new" />
@@ -117,12 +165,15 @@ class SupportFerdiDashboard extends Component {
117 <br /> 165 <br />
118 <br /> 166 <br />
119 </p> 167 </p>
120 <p> 168 <p>{intl.formatMessage(messages.textVolunteers)}</p>
121 {intl.formatMessage(messages.textVolunteers)}
122 </p>
123 <p> 169 <p>
124 {intl.formatMessage(messages.textSupportWelcome)} 170 {intl.formatMessage(messages.textSupportWelcome)}
125 <a href="https://help.getferdi.com/general/support" target="_blank" className="link" rel="noreferrer"> 171 <a
172 href="https://help.getferdi.com/general/support"
173 target="_blank"
174 className="link"
175 rel="noreferrer"
176 >
126 {' '} 177 {' '}
127 {intl.formatMessage(messages.textSupportWelcomeHere)} 178 {intl.formatMessage(messages.textSupportWelcomeHere)}
128 <i className="mdi mdi-open-in-new" /> 179 <i className="mdi mdi-open-in-new" />
@@ -130,7 +181,12 @@ class SupportFerdiDashboard extends Component {
130 </p> 181 </p>
131 <p> 182 <p>
132 {intl.formatMessage(messages.textExpenses)} 183 {intl.formatMessage(messages.textExpenses)}
133 <a href="https://opencollective.com/getferdi#section-budget" target="_blank" className="link" rel="noreferrer"> 184 <a
185 href="https://opencollective.com/getferdi#section-budget"
186 target="_blank"
187 className="link"
188 rel="noreferrer"
189 >
134 {' '} 190 {' '}
135 {intl.formatMessage(messages.textOpenCollective)} 191 {intl.formatMessage(messages.textOpenCollective)}
136 <i className="mdi mdi-open-in-new" /> 192 <i className="mdi mdi-open-in-new" />
@@ -138,14 +194,23 @@ class SupportFerdiDashboard extends Component {
138 </p> 194 </p>
139 <p> 195 <p>
140 {intl.formatMessage(messages.textDonation)} 196 {intl.formatMessage(messages.textDonation)}
141 <a href="https://opencollective.com/getferdi#section-contribute" target="_blank" className="link" rel="noreferrer"> 197 <a
198 href="https://opencollective.com/getferdi#section-contribute"
199 target="_blank"
200 className="link"
201 rel="noreferrer"
202 >
142 {' '} 203 {' '}
143 {intl.formatMessage(messages.textOpenCollective)} 204 {intl.formatMessage(messages.textOpenCollective)}
144 <i className="mdi mdi-open-in-new" /> 205 <i className="mdi mdi-open-in-new" />
145 </a> 206 </a>{' '}
146 {' '}
147 {intl.formatMessage(messages.textDonationAnd)} 207 {intl.formatMessage(messages.textDonationAnd)}
148 <a href="https://github.com/sponsors/getferdi" target="_blank" className="link" rel="noreferrer"> 208 <a
209 href="https://github.com/sponsors/getferdi"
210 target="_blank"
211 className="link"
212 rel="noreferrer"
213 >
149 {' '} 214 {' '}
150 {intl.formatMessage(messages.textGitHubSponsors)} 215 {intl.formatMessage(messages.textGitHubSponsors)}
151 <i className="mdi mdi-open-in-new" /> 216 <i className="mdi mdi-open-in-new" />
@@ -166,4 +231,4 @@ class SupportFerdiDashboard extends Component {
166 } 231 }
167} 232}
168 233
169export default SupportFerdiDashboard; 234export default injectIntl(SupportFerdiDashboard);
diff --git a/src/components/settings/team/TeamDashboard.js b/src/components/settings/team/TeamDashboard.js
index 437225058..176365fa8 100644
--- a/src/components/settings/team/TeamDashboard.js
+++ b/src/components/settings/team/TeamDashboard.js
@@ -1,7 +1,7 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, injectIntl } from 'react-intl';
5import ReactTooltip from 'react-tooltip'; 5import ReactTooltip from 'react-tooltip';
6import injectSheet from 'react-jss'; 6import injectSheet from 'react-jss';
7import classnames from 'classnames'; 7import classnames from 'classnames';
@@ -14,31 +14,34 @@ import { LIVE_FRANZ_API } from '../../../config';
14const messages = defineMessages({ 14const messages = defineMessages({
15 headline: { 15 headline: {
16 id: 'settings.team.headline', 16 id: 'settings.team.headline',
17 defaultMessage: '!!!Team', 17 defaultMessage: 'Team',
18 }, 18 },
19 contentHeadline: { 19 contentHeadline: {
20 id: 'settings.team.contentHeadline', 20 id: 'settings.team.contentHeadline',
21 defaultMessage: '!!!Franz Team Management', 21 defaultMessage: 'Franz Team Management',
22 }, 22 },
23 intro: { 23 intro: {
24 id: 'settings.team.intro', 24 id: 'settings.team.intro',
25 defaultMessage: '!!!Your are currently using Franz Servers, which is why you have access to Team Management.', 25 defaultMessage:
26 'Your are currently using Franz Servers, which is why you have access to Team Management.',
26 }, 27 },
27 copy: { 28 copy: {
28 id: 'settings.team.copy', 29 id: 'settings.team.copy',
29 defaultMessage: '!!!Franz\'s Team Management allows you to manage Franz Subscriptions for multiple users. Please keep in mind that having a Franz Premium subscription will give you no advantages in using Ferdi: The only reason you still have access to Team Management is so you can manage your legacy Franz Teams and so that you don\'t loose any functionality in managing your account.', 30 defaultMessage:
31 "Franz's Team Management allows you to manage Franz Subscriptions for multiple users. Please keep in mind that having a Franz Premium subscription will give you no advantages in using Ferdi: The only reason you still have access to Team Management is so you can manage your legacy Franz Teams and so that you don't loose any functionality in managing your account.",
30 }, 32 },
31 manageButton: { 33 manageButton: {
32 id: 'settings.team.manageAction', 34 id: 'settings.team.manageAction',
33 defaultMessage: '!!!Manage your Team on meetfranz.com', 35 defaultMessage: 'Manage your Team on meetfranz.com',
34 }, 36 },
35 teamsUnavailable: { 37 teamsUnavailable: {
36 id: 'settings.team.teamsUnavailable', 38 id: 'settings.team.teamsUnavailable',
37 defaultMessage: '!!!Teams are unavailable', 39 defaultMessage: 'Teams are unavailable',
38 }, 40 },
39 teamsUnavailableInfo: { 41 teamsUnavailableInfo: {
40 id: 'settings.team.teamsUnavailableInfo', 42 id: 'settings.team.teamsUnavailableInfo',
41 defaultMessage: '!!!Teams are currently only available when using the Franz Server and after paying for Franz Professional. Please change your server to https://api.franzinfra.com to use teams.', 43 defaultMessage:
44 'Teams are currently only available when using the Franz Server and after paying for Franz Professional. Please change your server to https://api.franzinfra.com to use teams.',
42 }, 45 },
43}); 46});
44 47
@@ -87,7 +90,9 @@ const styles = {
87 }, 90 },
88}; 91};
89 92
90export default @injectSheet(styles) @observer class TeamDashboard extends Component { 93@injectSheet(styles)
94@observer
95class TeamDashboard extends Component {
91 static propTypes = { 96 static propTypes = {
92 isLoading: PropTypes.bool.isRequired, 97 isLoading: PropTypes.bool.isRequired,
93 userInfoRequestFailed: PropTypes.bool.isRequired, 98 userInfoRequestFailed: PropTypes.bool.isRequired,
@@ -97,10 +102,6 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
97 server: PropTypes.string.isRequired, 102 server: PropTypes.string.isRequired,
98 }; 103 };
99 104
100 static contextTypes = {
101 intl: intlShape,
102 };
103
104 render() { 105 render() {
105 const { 106 const {
106 isLoading, 107 isLoading,
@@ -110,7 +111,7 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
110 classes, 111 classes,
111 server, 112 server,
112 } = this.props; 113 } = this.props;
113 const { intl } = this.context; 114 const { intl } = this.props;
114 115
115 if (server === LIVE_FRANZ_API) { 116 if (server === LIVE_FRANZ_API) {
116 return ( 117 return (
@@ -121,9 +122,7 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
121 </span> 122 </span>
122 </div> 123 </div>
123 <div className="settings__body"> 124 <div className="settings__body">
124 {isLoading && ( 125 {isLoading && <Loader />}
125 <Loader />
126 )}
127 126
128 {!isLoading && userInfoRequestFailed && ( 127 {!isLoading && userInfoRequestFailed && (
129 <Infobox 128 <Infobox
@@ -142,20 +141,24 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
142 {!isLoading && ( 141 {!isLoading && (
143 <> 142 <>
144 <> 143 <>
145 <h1 className={classnames({ 144 <h1
146 [classes.headline]: true, 145 className={classnames({
147 [classes.headlineWithSpacing]: true, 146 [classes.headline]: true,
148 })} 147 [classes.headlineWithSpacing]: true,
148 })}
149 > 149 >
150 {intl.formatMessage(messages.contentHeadline)} 150 {intl.formatMessage(messages.contentHeadline)}
151
152 </h1> 151 </h1>
153 <div className={classes.container}> 152 <div className={classes.container}>
154 <div className={classes.content}> 153 <div className={classes.content}>
155 <p>{intl.formatMessage(messages.intro)}</p> 154 <p>{intl.formatMessage(messages.intro)}</p>
156 <p>{intl.formatMessage(messages.copy)}</p> 155 <p>{intl.formatMessage(messages.copy)}</p>
157 </div> 156 </div>
158 <img className={classes.image} src="https://cdn.franzinfra.com/announcements/assets/teams.png" alt="Ferdi for Teams" /> 157 <img
158 className={classes.image}
159 src="https://cdn.franzinfra.com/announcements/assets/teams.png"
160 alt="Ferdi for Teams"
161 />
159 </div> 162 </div>
160 <div className={classes.buttonContainer}> 163 <div className={classes.buttonContainer}>
161 <Button 164 <Button
@@ -188,7 +191,8 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
188 <p 191 <p
189 className="settings__message" 192 className="settings__message"
190 style={{ 193 style={{
191 borderTop: 0, marginTop: 0, 194 borderTop: 0,
195 marginTop: 0,
192 }} 196 }}
193 > 197 >
194 {intl.formatMessage(messages.teamsUnavailableInfo)} 198 {intl.formatMessage(messages.teamsUnavailableInfo)}
@@ -198,3 +202,5 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
198 ); 202 );
199 } 203 }
200} 204}
205
206export default injectIntl(TeamDashboard);
diff --git a/src/components/settings/user/EditUserForm.js b/src/components/settings/user/EditUserForm.js
index db78acb69..4067881b8 100644
--- a/src/components/settings/user/EditUserForm.js
+++ b/src/components/settings/user/EditUserForm.js
@@ -1,7 +1,7 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, injectIntl } from 'react-intl';
5import { Link } from 'react-router'; 5import { Link } from 'react-router';
6import { Input } from '@meetfranz/forms'; 6import { Input } from '@meetfranz/forms';
7 7
@@ -14,31 +14,32 @@ import Infobox from '../../ui/Infobox';
14const messages = defineMessages({ 14const messages = defineMessages({
15 headline: { 15 headline: {
16 id: 'settings.account.headline', 16 id: 'settings.account.headline',
17 defaultMessage: '!!!Account', 17 defaultMessage: 'Account',
18 }, 18 },
19 headlineProfile: { 19 headlineProfile: {
20 id: 'settings.account.headlineProfile', 20 id: 'settings.account.headlineProfile',
21 defaultMessage: '!!!Update Profile', 21 defaultMessage: 'Update Profile',
22 }, 22 },
23 headlineAccount: { 23 headlineAccount: {
24 id: 'settings.account.headlineAccount', 24 id: 'settings.account.headlineAccount',
25 defaultMessage: '!!!Account Information', 25 defaultMessage: 'Account Information',
26 }, 26 },
27 headlinePassword: { 27 headlinePassword: {
28 id: 'settings.account.headlinePassword', 28 id: 'settings.account.headlinePassword',
29 defaultMessage: '!!!Change Password', 29 defaultMessage: 'Change Password',
30 }, 30 },
31 successInfo: { 31 successInfo: {
32 id: 'settings.account.successInfo', 32 id: 'settings.account.successInfo',
33 defaultMessage: '!!!Your changes have been saved', 33 defaultMessage: 'Your changes have been saved',
34 }, 34 },
35 buttonSave: { 35 buttonSave: {
36 id: 'settings.account.buttonSave', 36 id: 'settings.account.buttonSave',
37 defaultMessage: '!!!Update profile', 37 defaultMessage: 'Update profile',
38 }, 38 },
39}); 39});
40 40
41export default @observer class EditUserForm extends Component { 41@observer
42class EditUserForm extends Component {
42 static propTypes = { 43 static propTypes = {
43 status: MobxPropTypes.observableArray.isRequired, 44 status: MobxPropTypes.observableArray.isRequired,
44 form: PropTypes.instanceOf(Form).isRequired, 45 form: PropTypes.instanceOf(Form).isRequired,
@@ -46,14 +47,10 @@ export default @observer class EditUserForm extends Component {
46 isSaving: PropTypes.bool.isRequired, 47 isSaving: PropTypes.bool.isRequired,
47 }; 48 };
48 49
49 static contextTypes = {
50 intl: intlShape,
51 };
52
53 submit(e) { 50 submit(e) {
54 e.preventDefault(); 51 e.preventDefault();
55 this.props.form.submit({ 52 this.props.form.submit({
56 onSuccess: (form) => { 53 onSuccess: form => {
57 const values = form.values(); 54 const values = form.values();
58 this.props.onSubmit(values); 55 this.props.onSubmit(values);
59 }, 56 },
@@ -68,7 +65,7 @@ export default @observer class EditUserForm extends Component {
68 form, 65 form,
69 isSaving, 66 isSaving,
70 } = this.props; 67 } = this.props;
71 const { intl } = this.context; 68 const { intl } = this.props;
72 69
73 return ( 70 return (
74 <div className="settings__main"> 71 <div className="settings__main">
@@ -84,12 +81,9 @@ export default @observer class EditUserForm extends Component {
84 </span> 81 </span>
85 </div> 82 </div>
86 <div className="settings__body"> 83 <div className="settings__body">
87 <form onSubmit={(e) => this.submit(e)} id="form"> 84 <form onSubmit={e => this.submit(e)} id="form">
88 {status.length > 0 && status.includes('data-updated') && ( 85 {status.length > 0 && status.includes('data-updated') && (
89 <Infobox 86 <Infobox type="success" icon="checkbox-marked-circle-outline">
90 type="success"
91 icon="checkbox-marked-circle-outline"
92 >
93 {intl.formatMessage(messages.successInfo)} 87 {intl.formatMessage(messages.successInfo)}
94 </Infobox> 88 </Infobox>
95 )} 89 )}
@@ -104,10 +98,7 @@ export default @observer class EditUserForm extends Component {
104 <Input field={form.$('organization')} /> 98 <Input field={form.$('organization')} />
105 )} 99 )}
106 <h2>{intl.formatMessage(messages.headlinePassword)}</h2> 100 <h2>{intl.formatMessage(messages.headlinePassword)}</h2>
107 <Input 101 <Input {...form.$('oldPassword').bind()} showPasswordToggle />
108 {...form.$('oldPassword').bind()}
109 showPasswordToggle
110 />
111 <Input 102 <Input
112 {...form.$('newPassword').bind()} 103 {...form.$('newPassword').bind()}
113 showPasswordToggle 104 showPasswordToggle
@@ -137,3 +128,5 @@ export default @observer class EditUserForm extends Component {
137 ); 128 );
138 } 129 }
139} 130}
131
132export default injectIntl(EditUserForm);