aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorLibravatar Markus Hatvan <markus_hatvan@aon.at>2021-09-13 14:45:46 +0200
committerLibravatar GitHub <noreply@github.com>2021-09-13 14:45:46 +0200
commit537697a6e9757f118d09d9e76362ba1ff617e2c6 (patch)
treebc55447115e385137684e84697a8c15d2199b8d5 /src/components
parentBumped up version to: 5.6.3-nightly.0 [skip ci] (diff)
downloadferdium-app-537697a6e9757f118d09d9e76362ba1ff617e2c6.tar.gz
ferdium-app-537697a6e9757f118d09d9e76362ba1ff617e2c6.tar.zst
ferdium-app-537697a6e9757f118d09d9e76362ba1ff617e2c6.zip
chore: upgrade intl dependencies (#1920)
Diffstat (limited to 'src/components')
-rw-r--r--src/components/AppUpdateInfoBar.js16
-rw-r--r--src/components/auth/AuthLayout.js12
-rw-r--r--src/components/auth/ChangeServer.js79
-rw-r--r--src/components/auth/Import.js43
-rw-r--r--src/components/auth/Invite.js52
-rw-r--r--src/components/auth/Locked.js97
-rw-r--r--src/components/auth/Login.js132
-rw-r--r--src/components/auth/Password.js77
-rw-r--r--src/components/auth/SetupAssistant.js18
-rw-r--r--src/components/auth/Signup.js127
-rw-r--r--src/components/auth/Welcome.js57
-rw-r--r--src/components/layout/AppLayout.js18
-rw-r--r--src/components/layout/Sidebar.js108
-rw-r--r--src/components/services/content/ConnectionLostBanner.js16
-rw-r--r--src/components/services/content/ErrorHandlers/WebviewErrorHandler.js38
-rw-r--r--src/components/services/content/ServiceDisabled.js17
-rw-r--r--src/components/services/content/ServiceView.js43
-rw-r--r--src/components/services/content/Services.js103
-rw-r--r--src/components/services/content/WebviewCrashHandler.js26
-rw-r--r--src/components/services/tabs/TabItem.js46
-rw-r--r--src/components/services/tabs/Tabbar.js20
-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
-rw-r--r--src/components/ui/AppLoader/index.js10
-rw-r--r--src/components/ui/Button.js14
-rw-r--r--src/components/ui/FAB.js17
-rw-r--r--src/components/ui/FeatureList.js44
-rw-r--r--src/components/ui/FullscreenLoader/index.js23
-rw-r--r--src/components/ui/ImageUpload.js13
-rw-r--r--src/components/ui/InfoBar.js13
-rw-r--r--src/components/ui/Infobox.js13
-rw-r--r--src/components/ui/Input.js25
-rw-r--r--src/components/ui/Link.js14
-rw-r--r--src/components/ui/Loader.js17
-rw-r--r--src/components/ui/Modal/index.js19
-rw-r--r--src/components/ui/Radio.js26
-rw-r--r--src/components/ui/SearchInput.js30
-rw-r--r--src/components/ui/Select.js30
-rw-r--r--src/components/ui/Slider.js104
-rw-r--r--src/components/ui/StatusBarTargetUrl.js14
-rw-r--r--src/components/ui/Tabs/Tabs.js5
-rw-r--r--src/components/ui/Toggle.js20
-rw-r--r--src/components/ui/ToggleRaw.js20
-rw-r--r--src/components/ui/WebviewLoader/index.js16
-rw-r--r--src/components/util/ErrorBoundary/index.js19
56 files changed, 1525 insertions, 1369 deletions
diff --git a/src/components/AppUpdateInfoBar.js b/src/components/AppUpdateInfoBar.js
index 30804262a..9dc86bd1d 100644
--- a/src/components/AppUpdateInfoBar.js
+++ b/src/components/AppUpdateInfoBar.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';
4 4
5import InfoBar from './ui/InfoBar'; 5import InfoBar from './ui/InfoBar';
6import { GITHUB_FERDI_URL } from '../config'; 6import { GITHUB_FERDI_URL } from '../config';
@@ -9,15 +9,15 @@ import { openExternalUrl } from '../helpers/url-helpers';
9const messages = defineMessages({ 9const messages = defineMessages({
10 updateAvailable: { 10 updateAvailable: {
11 id: 'infobar.updateAvailable', 11 id: 'infobar.updateAvailable',
12 defaultMessage: '!!!A new update for Ferdi is available.', 12 defaultMessage: 'A new update for Ferdi is available.',
13 }, 13 },
14 changelog: { 14 changelog: {
15 id: 'infobar.buttonChangelog', 15 id: 'infobar.buttonChangelog',
16 defaultMessage: '!!!Changelog', 16 defaultMessage: 'Changelog',
17 }, 17 },
18 buttonInstallUpdate: { 18 buttonInstallUpdate: {
19 id: 'infobar.buttonInstallUpdate', 19 id: 'infobar.buttonInstallUpdate',
20 defaultMessage: '!!!Restart & install update', 20 defaultMessage: 'Restart & install update',
21 }, 21 },
22}); 22});
23 23
@@ -27,12 +27,8 @@ class AppUpdateInfoBar extends Component {
27 onHide: PropTypes.func.isRequired, 27 onHide: PropTypes.func.isRequired,
28 }; 28 };
29 29
30 static contextTypes = {
31 intl: intlShape,
32 };
33
34 render() { 30 render() {
35 const { intl } = this.context; 31 const { intl } = this.props;
36 const { onInstallUpdate, onHide } = this.props; 32 const { onInstallUpdate, onHide } = this.props;
37 33
38 return ( 34 return (
@@ -61,4 +57,4 @@ class AppUpdateInfoBar extends Component {
61 } 57 }
62} 58}
63 59
64export default AppUpdateInfoBar; 60export default injectIntl(AppUpdateInfoBar);
diff --git a/src/components/auth/AuthLayout.js b/src/components/auth/AuthLayout.js
index 8235932c2..3e2b75731 100644
--- a/src/components/auth/AuthLayout.js
+++ b/src/components/auth/AuthLayout.js
@@ -1,9 +1,9 @@
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 { intlShape } from 'react-intl';
5import { TitleBar } from 'electron-react-titlebar'; 4import { TitleBar } from 'electron-react-titlebar';
6 5
6import { injectIntl } from 'react-intl';
7import Link from '../ui/Link'; 7import Link from '../ui/Link';
8import InfoBar from '../ui/InfoBar'; 8import InfoBar from '../ui/InfoBar';
9 9
@@ -17,7 +17,6 @@ import { isWindows } from '../../environment';
17import AppUpdateInfoBar from '../AppUpdateInfoBar'; 17import AppUpdateInfoBar from '../AppUpdateInfoBar';
18import { GITHUB_FERDI_URL } from '../../config'; 18import { GITHUB_FERDI_URL } from '../../config';
19 19
20export default
21@observer 20@observer
22class AuthLayout extends Component { 21class AuthLayout extends Component {
23 static propTypes = { 22 static propTypes = {
@@ -36,10 +35,6 @@ class AuthLayout extends Component {
36 shouldShowAppUpdateInfoBar: true, 35 shouldShowAppUpdateInfoBar: true,
37 }; 36 };
38 37
39 static contextTypes = {
40 intl: intlShape,
41 };
42
43 render() { 38 render() {
44 const { 39 const {
45 children, 40 children,
@@ -52,7 +47,8 @@ class AuthLayout extends Component {
52 installAppUpdate, 47 installAppUpdate,
53 appUpdateIsDownloaded, 48 appUpdateIsDownloaded,
54 } = this.props; 49 } = this.props;
55 const { intl } = this.context; 50
51 const { intl } = this.props;
56 52
57 return ( 53 return (
58 <> 54 <>
@@ -108,3 +104,5 @@ class AuthLayout extends Component {
108 ); 104 );
109 } 105 }
110} 106}
107
108export default injectIntl(AuthLayout);
diff --git a/src/components/auth/ChangeServer.js b/src/components/auth/ChangeServer.js
index 8e8a7af32..b98fb50f7 100644
--- a/src/components/auth/ChangeServer.js
+++ b/src/components/auth/ChangeServer.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 Form from '../../lib/Form'; 5import Form from '../../lib/Form';
6import Input from '../ui/Input'; 6import Input from '../ui/Input';
7import Select from '../ui/Select'; 7import Select from '../ui/Select';
@@ -14,56 +14,65 @@ import globalMessages from '../../i18n/globalMessages';
14const messages = defineMessages({ 14const messages = defineMessages({
15 headline: { 15 headline: {
16 id: 'changeserver.headline', 16 id: 'changeserver.headline',
17 defaultMessage: '!!!Change server', 17 defaultMessage: 'Change server',
18 }, 18 },
19 label: { 19 label: {
20 id: 'changeserver.label', 20 id: 'changeserver.label',
21 defaultMessage: '!!!Server', 21 defaultMessage: 'Server',
22 }, 22 },
23 warning: { 23 warning: {
24 id: 'changeserver.warning', 24 id: 'changeserver.warning',
25 defaultMessage: '!!!Extra settings offered by Ferdi will not be saved', 25 defaultMessage: 'Extra settings offered by Ferdi will not be saved',
26 }, 26 },
27 customServerLabel: { 27 customServerLabel: {
28 id: 'changeserver.customServerLabel', 28 id: 'changeserver.customServerLabel',
29 defaultMessage: '!!!Custom server', 29 defaultMessage: 'Custom server',
30 }, 30 },
31 urlError: { 31 urlError: {
32 id: 'changeserver.urlError', 32 id: 'changeserver.urlError',
33 defaultMessage: '!!!Enter a valid URL', 33 defaultMessage: 'Enter a valid URL',
34 }, 34 },
35}); 35});
36 36
37export default @observer class ChangeServer extends Component { 37@observer
38class ChangeServer extends Component {
38 static propTypes = { 39 static propTypes = {
39 onSubmit: PropTypes.func.isRequired, 40 onSubmit: PropTypes.func.isRequired,
40 server: PropTypes.string.isRequired, 41 server: PropTypes.string.isRequired,
41 }; 42 };
42 43
43 static contextTypes = { 44 ferdiServer = LIVE_FERDI_API;
44 intl: intlShape,
45 };
46
47 ferdiServer=LIVE_FERDI_API;
48 45
49 franzServer=LIVE_FRANZ_API; 46 franzServer = LIVE_FRANZ_API;
50 47
51 defaultServers=[this.franzServer, this.ferdiServer]; 48 defaultServers = [this.franzServer, this.ferdiServer];
52 49
53 form = new Form({ 50 form = new Form(
54 fields: { 51 {
55 server: { 52 fields: {
56 label: this.context.intl.formatMessage(messages.label), 53 server: {
57 value: this.props.server, 54 label: this.props.intl.formatMessage(messages.label),
58 options: [{ value: this.ferdiServer, label: 'Ferdi' }, { value: this.franzServer, label: 'Franz' }, { value: this.defaultServers.includes(this.props.server) ? '' : this.props.server, label: 'Custom' }], 55 value: this.props.server,
59 }, 56 options: [
60 customServer: { 57 { value: this.ferdiServer, label: 'Ferdi' },
61 label: this.context.intl.formatMessage(messages.customServerLabel), 58 { value: this.franzServer, label: 'Franz' },
62 value: '', 59 {
63 validators: [url, required], 60 value: this.defaultServers.includes(this.props.server)
61 ? ''
62 : this.props.server,
63 label: 'Custom',
64 },
65 ],
66 },
67 customServer: {
68 label: this.props.intl.formatMessage(messages.customServerLabel),
69 value: '',
70 validators: [url, required],
71 },
64 }, 72 },
65 }, 73 },
66 }, this.context.intl); 74 this.props.intl,
75 );
67 76
68 componentDidMount() { 77 componentDidMount() {
69 if (this.defaultServers.includes(this.props.server)) { 78 if (this.defaultServers.includes(this.props.server)) {
@@ -77,13 +86,13 @@ export default @observer class ChangeServer extends Component {
77 submit(e) { 86 submit(e) {
78 e.preventDefault(); 87 e.preventDefault();
79 this.form.submit({ 88 this.form.submit({
80 onSuccess: (form) => { 89 onSuccess: form => {
81 if (!this.defaultServers.includes(form.values().server)) { 90 if (!this.defaultServers.includes(form.values().server)) {
82 form.$('server').onChange(form.values().customServer); 91 form.$('server').onChange(form.values().customServer);
83 } 92 }
84 this.props.onSubmit(form.values()); 93 this.props.onSubmit(form.values());
85 }, 94 },
86 onError: (form) => { 95 onError: form => {
87 if (this.defaultServers.includes(form.values().server)) { 96 if (this.defaultServers.includes(form.values().server)) {
88 this.props.onSubmit(form.values()); 97 this.props.onSubmit(form.values());
89 } 98 }
@@ -93,23 +102,21 @@ export default @observer class ChangeServer extends Component {
93 102
94 render() { 103 render() {
95 const { form } = this; 104 const { form } = this;
96 const { intl } = this.context; 105 const { intl } = this.props;
97 return ( 106 return (
98 <div className="auth__container"> 107 <div className="auth__container">
99 <form className="franz-form auth__form" onSubmit={(e) => this.submit(e)}> 108 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
100 <h1>{intl.formatMessage(messages.headline)}</h1> 109 <h1>{intl.formatMessage(messages.headline)}</h1>
101 {form.$('server').value === this.franzServer 110 {form.$('server').value === this.franzServer && (
102 && (
103 <Infobox type="warning"> 111 <Infobox type="warning">
104 {intl.formatMessage(messages.warning)} 112 {intl.formatMessage(messages.warning)}
105 </Infobox> 113 </Infobox>
106 )} 114 )}
107 <Select field={form.$('server')} /> 115 <Select field={form.$('server')} />
108 {!this.defaultServers.includes(form.$('server').value) 116 {!this.defaultServers.includes(form.$('server').value) && (
109 && (
110 <Input 117 <Input
111 placeholder="Custom Server" 118 placeholder="Custom Server"
112 onChange={(e) => this.submit(e)} 119 onChange={e => this.submit(e)}
113 field={form.$('customServer')} 120 field={form.$('customServer')}
114 /> 121 />
115 )} 122 )}
@@ -123,3 +130,5 @@ export default @observer class ChangeServer extends Component {
123 ); 130 );
124 } 131 }
125} 132}
133
134export default injectIntl(ChangeServer);
diff --git a/src/components/auth/Import.js b/src/components/auth/Import.js
index 3073cad73..44cb7e791 100644
--- a/src/components/auth/Import.js
+++ b/src/components/auth/Import.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 classnames from 'classnames'; 6import classnames from 'classnames';
7 7
@@ -12,23 +12,22 @@ import Button from '../ui/Button';
12const messages = defineMessages({ 12const messages = defineMessages({
13 headline: { 13 headline: {
14 id: 'import.headline', 14 id: 'import.headline',
15 defaultMessage: '!!!Import your Ferdi 4 services', 15 defaultMessage: 'Import your Ferdi 4 services',
16 }, 16 },
17 notSupportedHeadline: { 17 notSupportedHeadline: {
18 id: 'import.notSupportedHeadline', 18 id: 'import.notSupportedHeadline',
19 defaultMessage: '!!!Services not yet supported in Ferdi 5', 19 defaultMessage: 'Services not yet supported in Ferdi 5',
20 }, 20 },
21 submitButtonLabel: { 21 submitButtonLabel: {
22 id: 'import.submit.label', 22 id: 'import.submit.label',
23 defaultMessage: '!!!Import {count} services', 23 defaultMessage: 'Import {count} services',
24 }, 24 },
25 skipButtonLabel: { 25 skipButtonLabel: {
26 id: 'import.skip.label', 26 id: 'import.skip.label',
27 defaultMessage: '!!!I want to add services manually', 27 defaultMessage: 'I want to add services manually',
28 }, 28 },
29}); 29});
30 30
31export default
32@observer 31@observer
33class Import extends Component { 32class Import extends Component {
34 static propTypes = { 33 static propTypes = {
@@ -38,17 +37,13 @@ class Import extends Component {
38 inviteRoute: PropTypes.string.isRequired, 37 inviteRoute: PropTypes.string.isRequired,
39 }; 38 };
40 39
41 static contextTypes = {
42 intl: intlShape,
43 };
44
45 componentDidMount() { 40 componentDidMount() {
46 const config = { 41 const config = {
47 fields: { 42 fields: {
48 import: [ 43 import: [
49 ...this.props.services 44 ...this.props.services
50 .filter((s) => s.recipe) 45 .filter(s => s.recipe)
51 .map((s) => ({ 46 .map(s => ({
52 fields: { 47 fields: {
53 add: { 48 add: {
54 default: true, 49 default: true,
@@ -60,20 +55,20 @@ class Import extends Component {
60 }, 55 },
61 }; 56 };
62 57
63 this.form = new Form(config, this.context.intl); 58 this.form = new Form(config, this.props.intl);
64 } 59 }
65 60
66 submit(e) { 61 submit(e) {
67 const { services } = this.props; 62 const { services } = this.props;
68 e.preventDefault(); 63 e.preventDefault();
69 this.form.submit({ 64 this.form.submit({
70 onSuccess: (form) => { 65 onSuccess: form => {
71 const servicesImport = form 66 const servicesImport = form
72 .values() 67 .values()
73 .import.map( 68 .import.map(
74 (value, i) => !value.add || services.filter((s) => s.recipe)[i], 69 (value, i) => !value.add || services.filter(s => s.recipe)[i],
75 ) 70 )
76 .filter((s) => typeof s !== 'boolean'); 71 .filter(s => typeof s !== 'boolean');
77 72
78 this.props.onSubmit({ services: servicesImport }); 73 this.props.onSubmit({ services: servicesImport });
79 }, 74 },
@@ -82,18 +77,18 @@ class Import extends Component {
82 } 77 }
83 78
84 render() { 79 render() {
85 const { intl } = this.context; 80 const { intl } = this.props;
86 const { services, isSubmitting, inviteRoute } = this.props; 81 const { services, isSubmitting, inviteRoute } = this.props;
87 82
88 const availableServices = services.filter((s) => s.recipe); 83 const availableServices = services.filter(s => s.recipe);
89 const unavailableServices = services.filter((s) => !s.recipe); 84 const unavailableServices = services.filter(s => !s.recipe);
90 85
91 return ( 86 return (
92 <div className="auth__scroll-container"> 87 <div className="auth__scroll-container">
93 <div className="auth__container auth__container--signup"> 88 <div className="auth__container auth__container--signup">
94 <form 89 <form
95 className="franz-form auth__form" 90 className="franz-form auth__form"
96 onSubmit={(e) => this.submit(e)} 91 onSubmit={e => this.submit(e)}
97 > 92 >
98 <img src="./assets/images/logo.svg" className="auth__logo" alt="" /> 93 <img src="./assets/images/logo.svg" className="auth__logo" alt="" />
99 <h1>{intl.formatMessage(messages.headline)}</h1> 94 <h1>{intl.formatMessage(messages.headline)}</h1>
@@ -107,8 +102,8 @@ class Import extends Component {
107 <td className="service-table__column-icon"> 102 <td className="service-table__column-icon">
108 <img 103 <img
109 src={ 104 src={
110 availableServices[i].custom_icon 105 availableServices[i].custom_icon ||
111 || availableServices[i].recipe.icons.svg 106 availableServices[i].recipe.icons.svg
112 } 107 }
113 className={classnames({ 108 className={classnames({
114 'service-table__icon': true, 109 'service-table__icon': true,
@@ -133,7 +128,7 @@ class Import extends Component {
133 </strong> 128 </strong>
134 <p> 129 <p>
135 {services 130 {services
136 .filter((s) => !s.recipe) 131 .filter(s => !s.recipe)
137 .map((service, i) => ( 132 .map((service, i) => (
138 <span key={service.id}> 133 <span key={service.id}>
139 {service.name !== '' ? service.name : service.service} 134 {service.name !== '' ? service.name : service.service}
@@ -170,3 +165,5 @@ class Import extends Component {
170 ); 165 );
171 } 166 }
172} 167}
168
169export default injectIntl(Import);
diff --git a/src/components/auth/Invite.js b/src/components/auth/Invite.js
index 4b4d63a6b..519691ede 100644
--- a/src/components/auth/Invite.js
+++ b/src/components/auth/Invite.js
@@ -1,7 +1,7 @@
1import React, { Component, Fragment } from 'react'; 1import React, { Component, Fragment } 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 { Link } from 'react-router'; 5import { Link } from 'react-router';
6import classnames from 'classnames'; 6import classnames from 'classnames';
7 7
@@ -15,35 +15,34 @@ import Button from '../ui/Button';
15const messages = defineMessages({ 15const messages = defineMessages({
16 settingsHeadline: { 16 settingsHeadline: {
17 id: 'settings.invite.headline', 17 id: 'settings.invite.headline',
18 defaultMessage: '!!!Invite Friends', 18 defaultMessage: 'Invite Friends',
19 }, 19 },
20 headline: { 20 headline: {
21 id: 'invite.headline.friends', 21 id: 'invite.headline.friends',
22 defaultMessage: '!!!Invite 3 of your friends or colleagues', 22 defaultMessage: 'Invite 3 of your friends or colleagues',
23 }, 23 },
24 nameLabel: { 24 nameLabel: {
25 id: 'invite.name.label', 25 id: 'invite.name.label',
26 defaultMessage: '!!!Name', 26 defaultMessage: 'Name',
27 }, 27 },
28 emailLabel: { 28 emailLabel: {
29 id: 'invite.email.label', 29 id: 'invite.email.label',
30 defaultMessage: '!!!Email address', 30 defaultMessage: 'Email address',
31 }, 31 },
32 submitButtonLabel: { 32 submitButtonLabel: {
33 id: 'invite.submit.label', 33 id: 'invite.submit.label',
34 defaultMessage: '!!!Send invites', 34 defaultMessage: 'Send invites',
35 }, 35 },
36 skipButtonLabel: { 36 skipButtonLabel: {
37 id: 'invite.skip.label', 37 id: 'invite.skip.label',
38 defaultMessage: '!!!I want to do this later', 38 defaultMessage: 'I want to do this later',
39 }, 39 },
40 inviteSuccessInfo: { 40 inviteSuccessInfo: {
41 id: 'invite.successInfo', 41 id: 'invite.successInfo',
42 defaultMessage: '!!!Invitations sent successfully', 42 defaultMessage: 'Invitations sent successfully',
43 }, 43 },
44}); 44});
45 45
46export default
47@observer 46@observer
48class Invite extends Component { 47class Invite extends Component {
49 static propTypes = { 48 static propTypes = {
@@ -59,10 +58,6 @@ class Invite extends Component {
59 isLoadingInvite: false, 58 isLoadingInvite: false,
60 }; 59 };
61 60
62 static contextTypes = {
63 intl: intlShape,
64 };
65
66 state = { showSuccessInfo: false }; 61 state = { showSuccessInfo: false };
67 62
68 componentDidMount() { 63 componentDidMount() {
@@ -73,8 +68,8 @@ class Invite extends Component {
73 ...Array(3).fill({ 68 ...Array(3).fill({
74 fields: { 69 fields: {
75 name: { 70 name: {
76 label: this.context.intl.formatMessage(messages.nameLabel), 71 label: this.props.intl.formatMessage(messages.nameLabel),
77 placeholder: this.context.intl.formatMessage( 72 placeholder: this.props.intl.formatMessage(
78 messages.nameLabel, 73 messages.nameLabel,
79 ), 74 ),
80 onChange: () => { 75 onChange: () => {
@@ -83,8 +78,8 @@ class Invite extends Component {
83 // related: ['invite.0.email'], // path accepted but does not work 78 // related: ['invite.0.email'], // path accepted but does not work
84 }, 79 },
85 email: { 80 email: {
86 label: this.context.intl.formatMessage(messages.emailLabel), 81 label: this.props.intl.formatMessage(messages.emailLabel),
87 placeholder: this.context.intl.formatMessage( 82 placeholder: this.props.intl.formatMessage(
88 messages.emailLabel, 83 messages.emailLabel,
89 ), 84 ),
90 onChange: () => { 85 onChange: () => {
@@ -97,7 +92,7 @@ class Invite extends Component {
97 ], 92 ],
98 }, 93 },
99 }, 94 },
100 this.context.intl, 95 this.props.intl,
101 ); 96 );
102 97
103 document.querySelector('input:first-child').focus(); 98 document.querySelector('input:first-child').focus();
@@ -107,7 +102,7 @@ class Invite extends Component {
107 e.preventDefault(); 102 e.preventDefault();
108 103
109 this.form.submit({ 104 this.form.submit({
110 onSuccess: (form) => { 105 onSuccess: form => {
111 this.props.onSubmit({ invites: form.values().invite }); 106 this.props.onSubmit({ invites: form.values().invite });
112 107
113 this.form.clear(); 108 this.form.clear();
@@ -121,13 +116,13 @@ class Invite extends Component {
121 116
122 render() { 117 render() {
123 const { form } = this; 118 const { form } = this;
124 const { intl } = this.context; 119 const { intl } = this.props;
125 const { embed, isInviteSuccessful, isLoadingInvite } = this.props; 120 const { embed, isInviteSuccessful, isLoadingInvite } = this.props;
126 121
127 const atLeastOneEmailAddress = form 122 const atLeastOneEmailAddress = form
128 .$('invite') 123 .$('invite')
129 .map((invite) => invite.$('email').value) 124 .map(invite => invite.$('email').value)
130 .some((emailValue) => emailValue.trim() !== ''); 125 .some(emailValue => emailValue.trim() !== '');
131 126
132 const sendButtonClassName = classnames({ 127 const sendButtonClassName = classnames({
133 auth__button: true, 128 auth__button: true,
@@ -148,17 +143,14 @@ class Invite extends Component {
148 </Appear> 143 </Appear>
149 )} 144 )}
150 145
151 <form 146 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
152 className="franz-form auth__form"
153 onSubmit={(e) => this.submit(e)}
154 >
155 {!embed && ( 147 {!embed && (
156 <img src="./assets/images/logo.svg" className="auth__logo" alt="" /> 148 <img src="./assets/images/logo.svg" className="auth__logo" alt="" />
157 )} 149 )}
158 <h1 className={embed && 'invite__embed'}> 150 <h1 className={embed && 'invite__embed'}>
159 {intl.formatMessage(messages.headline)} 151 {intl.formatMessage(messages.headline)}
160 </h1> 152 </h1>
161 {form.$('invite').map((invite) => ( 153 {form.$('invite').map(invite => (
162 <div className="grid" key={invite.key}> 154 <div className="grid" key={invite.key}>
163 <div className="grid__row"> 155 <div className="grid__row">
164 <Input field={invite.$('name')} showLabel={false} /> 156 <Input field={invite.$('name')} showLabel={false} />
@@ -193,9 +185,7 @@ class Invite extends Component {
193 > 185 >
194 {embed && ( 186 {embed && (
195 <div className="settings__header"> 187 <div className="settings__header">
196 <h1> 188 <h1>{this.props.intl.formatMessage(messages.settingsHeadline)}</h1>
197 {this.context.intl.formatMessage(messages.settingsHeadline)}
198 </h1>
199 </div> 189 </div>
200 )} 190 )}
201 {!embed ? ( 191 {!embed ? (
@@ -207,3 +197,5 @@ class Invite extends Component {
207 ); 197 );
208 } 198 }
209} 199}
200
201export default injectIntl(Invite);
diff --git a/src/components/auth/Locked.js b/src/components/auth/Locked.js
index 2ad8a2409..a507ba140 100644
--- a/src/components/auth/Locked.js
+++ b/src/components/auth/Locked.js
@@ -2,7 +2,7 @@ import { systemPreferences } from '@electron/remote';
2import React, { Component } from 'react'; 2import 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 { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, injectIntl } from 'react-intl';
6 6
7import Form from '../../lib/Form'; 7import Form from '../../lib/Form';
8import Input from '../ui/Input'; 8import Input from '../ui/Input';
@@ -15,39 +15,41 @@ import { globalError as globalErrorPropType } from '../../prop-types';
15const messages = defineMessages({ 15const messages = defineMessages({
16 headline: { 16 headline: {
17 id: 'locked.headline', 17 id: 'locked.headline',
18 defaultMessage: '!!!Locked', 18 defaultMessage: 'Locked',
19 }, 19 },
20 info: { 20 info: {
21 id: 'locked.info', 21 id: 'locked.info',
22 defaultMessage: '!!!Ferdi is currently locked. Please unlock Ferdi with your password to see your messages.', 22 defaultMessage:
23 'Ferdi is currently locked. Please unlock Ferdi with your password to see your messages.',
23 }, 24 },
24 touchId: { 25 touchId: {
25 id: 'locked.touchId', 26 id: 'locked.touchId',
26 defaultMessage: '!!!Unlock with Touch ID', 27 defaultMessage: 'Unlock with Touch ID',
27 }, 28 },
28 touchIdPrompt: { 29 touchIdPrompt: {
29 id: 'locked.touchIdPrompt', 30 id: 'locked.touchIdPrompt',
30 defaultMessage: '!!!unlock via Touch ID', 31 defaultMessage: 'unlock via Touch ID',
31 }, 32 },
32 passwordLabel: { 33 passwordLabel: {
33 id: 'locked.password.label', 34 id: 'locked.password.label',
34 defaultMessage: '!!!Password', 35 defaultMessage: 'Password',
35 }, 36 },
36 submitButtonLabel: { 37 submitButtonLabel: {
37 id: 'locked.submit.label', 38 id: 'locked.submit.label',
38 defaultMessage: '!!!Unlock', 39 defaultMessage: 'Unlock',
39 }, 40 },
40 unlockWithPassword: { 41 unlockWithPassword: {
41 id: 'locked.unlockWithPassword', 42 id: 'locked.unlockWithPassword',
42 defaultMessage: '!!!Unlock with Password', 43 defaultMessage: 'Unlock with Password',
43 }, 44 },
44 invalidCredentials: { 45 invalidCredentials: {
45 id: 'locked.invalidCredentials', 46 id: 'locked.invalidCredentials',
46 defaultMessage: '!!!Password invalid', 47 defaultMessage: 'Password invalid',
47 }, 48 },
48}); 49});
49 50
50export default @observer class Locked extends Component { 51@observer
52class Locked extends Component {
51 static propTypes = { 53 static propTypes = {
52 onSubmit: PropTypes.func.isRequired, 54 onSubmit: PropTypes.func.isRequired,
53 unlock: PropTypes.func.isRequired, 55 unlock: PropTypes.func.isRequired,
@@ -56,62 +58,57 @@ export default @observer class Locked extends Component {
56 error: globalErrorPropType.isRequired, 58 error: globalErrorPropType.isRequired,
57 }; 59 };
58 60
59 static contextTypes = { 61 form = new Form(
60 intl: intlShape, 62 {
61 }; 63 fields: {
62 64 password: {
63 form = new Form({ 65 label: this.props.intl.formatMessage(messages.passwordLabel),
64 fields: { 66 value: '',
65 password: { 67 type: 'password',
66 label: this.context.intl.formatMessage(messages.passwordLabel), 68 },
67 value: '',
68 type: 'password',
69 }, 69 },
70 }, 70 },
71 }, this.context.intl); 71 this.props.intl,
72 );
72 73
73 submit(e) { 74 submit(e) {
74 e.preventDefault(); 75 e.preventDefault();
75 this.form.submit({ 76 this.form.submit({
76 onSuccess: (form) => { 77 onSuccess: form => {
77 this.props.onSubmit(form.values()); 78 this.props.onSubmit(form.values());
78 }, 79 },
79 onError: () => { }, 80 onError: () => {},
80 }); 81 });
81 } 82 }
82 83
83 touchIdUnlock() { 84 touchIdUnlock() {
84 const { intl } = this.context; 85 const { intl } = this.props;
85 86
86 systemPreferences.promptTouchID(intl.formatMessage(messages.touchIdPrompt)).then(() => { 87 systemPreferences
87 this.props.unlock(); 88 .promptTouchID(intl.formatMessage(messages.touchIdPrompt))
88 }); 89 .then(() => {
90 this.props.unlock();
91 });
89 } 92 }
90 93
91 render() { 94 render() {
92 const { form } = this; 95 const { form } = this;
93 const { intl } = this.context; 96 const { intl } = this.props;
94 const { 97 const { isSubmitting, error, useTouchIdToUnlock } = this.props;
95 isSubmitting,
96 error,
97 useTouchIdToUnlock,
98 } = this.props;
99 98
100 const touchIdEnabled = isMac ? (useTouchIdToUnlock && systemPreferences.canPromptTouchID()) : false; 99 const touchIdEnabled = isMac
101 const submitButtonLabel = touchIdEnabled ? intl.formatMessage(messages.unlockWithPassword) : intl.formatMessage(messages.submitButtonLabel); 100 ? useTouchIdToUnlock && systemPreferences.canPromptTouchID()
101 : false;
102 const submitButtonLabel = touchIdEnabled
103 ? intl.formatMessage(messages.unlockWithPassword)
104 : intl.formatMessage(messages.submitButtonLabel);
102 105
103 return ( 106 return (
104 <div className="auth__container"> 107 <div className="auth__container">
105 <form className="franz-form auth__form" onSubmit={(e) => this.submit(e)}> 108 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
106 <img 109 <img src="./assets/images/logo.svg" className="auth__logo" alt="" />
107 src="./assets/images/logo.svg"
108 className="auth__logo"
109 alt=""
110 />
111 <h1>{intl.formatMessage(messages.headline)}</h1> 110 <h1>{intl.formatMessage(messages.headline)}</h1>
112 <Infobox type="warning"> 111 <Infobox type="warning">{intl.formatMessage(messages.info)}</Infobox>
113 {intl.formatMessage(messages.info)}
114 </Infobox>
115 112
116 {touchIdEnabled && ( 113 {touchIdEnabled && (
117 <> 114 <>
@@ -125,13 +122,11 @@ export default @observer class Locked extends Component {
125 </> 122 </>
126 )} 123 )}
127 124
128 <Input 125 <Input field={form.$('password')} showPasswordToggle focus />
129 field={form.$('password')}
130 showPasswordToggle
131 focus
132 />
133 {error.code === 'invalid-credentials' && ( 126 {error.code === 'invalid-credentials' && (
134 <p className="error-message center">{intl.formatMessage(messages.invalidCredentials)}</p> 127 <p className="error-message center">
128 {intl.formatMessage(messages.invalidCredentials)}
129 </p>
135 )} 130 )}
136 {isSubmitting ? ( 131 {isSubmitting ? (
137 <Button 132 <Button
@@ -153,3 +148,5 @@ export default @observer class Locked extends Component {
153 ); 148 );
154 } 149 }
155} 150}
151
152export default injectIntl(Locked);
diff --git a/src/components/auth/Login.js b/src/components/auth/Login.js
index 9e6a8d046..a47834e19 100644
--- a/src/components/auth/Login.js
+++ b/src/components/auth/Login.js
@@ -2,7 +2,7 @@
2import React, { Component } from 'react'; 2import React, { Component } from 'react';
3import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
4import { observer, inject } from 'mobx-react'; 4import { observer, inject } from 'mobx-react';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, injectIntl } from 'react-intl';
6 6
7import { LIVE_FRANZ_API } from '../../config'; 7import { LIVE_FRANZ_API } from '../../config';
8import { API_VERSION, isDevMode, useLiveAPI } from '../../environment'; 8import { API_VERSION, isDevMode, useLiveAPI } from '../../environment';
@@ -19,59 +19,61 @@ import { globalError as globalErrorPropType } from '../../prop-types';
19const messages = defineMessages({ 19const messages = defineMessages({
20 headline: { 20 headline: {
21 id: 'login.headline', 21 id: 'login.headline',
22 defaultMessage: '!!!Sign in', 22 defaultMessage: 'Sign in',
23 }, 23 },
24 emailLabel: { 24 emailLabel: {
25 id: 'login.email.label', 25 id: 'login.email.label',
26 defaultMessage: '!!!Email address', 26 defaultMessage: 'Email address',
27 }, 27 },
28 passwordLabel: { 28 passwordLabel: {
29 id: 'login.password.label', 29 id: 'login.password.label',
30 defaultMessage: '!!!Password', 30 defaultMessage: 'Password',
31 }, 31 },
32 submitButtonLabel: { 32 submitButtonLabel: {
33 id: 'login.submit.label', 33 id: 'login.submit.label',
34 defaultMessage: '!!!Sign in', 34 defaultMessage: 'Sign in',
35 }, 35 },
36 invalidCredentials: { 36 invalidCredentials: {
37 id: 'login.invalidCredentials', 37 id: 'login.invalidCredentials',
38 defaultMessage: '!!!Email or password not valid', 38 defaultMessage: 'Email or password not valid',
39 }, 39 },
40 customServerQuestion: { 40 customServerQuestion: {
41 id: 'login.customServerQuestion', 41 id: 'login.customServerQuestion',
42 defaultMessage: '!!!Using a Franz account to log in?', 42 defaultMessage: 'Using a Franz account to log in?',
43 }, 43 },
44 customServerSuggestion: { 44 customServerSuggestion: {
45 id: 'login.customServerSuggestion', 45 id: 'login.customServerSuggestion',
46 defaultMessage: '!!!Try importing your Franz account into Ferdi', 46 defaultMessage: 'Try importing your Franz account into Ferdi',
47 }, 47 },
48 tokenExpired: { 48 tokenExpired: {
49 id: 'login.tokenExpired', 49 id: 'login.tokenExpired',
50 defaultMessage: '!!!Your session expired, please login again.', 50 defaultMessage: 'Your session expired, please login again.',
51 }, 51 },
52 serverLogout: { 52 serverLogout: {
53 id: 'login.serverLogout', 53 id: 'login.serverLogout',
54 defaultMessage: '!!!Your session expired, please login again.', 54 defaultMessage: 'Your session expired, please login again.',
55 }, 55 },
56 signupLink: { 56 signupLink: {
57 id: 'login.link.signup', 57 id: 'login.link.signup',
58 defaultMessage: '!!!Create a free account', 58 defaultMessage: 'Create a free account',
59 }, 59 },
60 changeServer: { 60 changeServer: {
61 id: 'login.changeServer', 61 id: 'login.changeServer',
62 defaultMessage: '!!!Change server', 62 defaultMessage: 'Change server',
63 }, 63 },
64 serverless: { 64 serverless: {
65 id: 'services.serverless', 65 id: 'services.serverless',
66 defaultMessage: '!!!Use Ferdi without an Account', 66 defaultMessage: 'Use Ferdi without an Account',
67 }, 67 },
68 passwordLink: { 68 passwordLink: {
69 id: 'login.link.password', 69 id: 'login.link.password',
70 defaultMessage: '!!!Forgot password', 70 defaultMessage: 'Forgot password',
71 }, 71 },
72}); 72});
73 73
74export default @inject('actions') @observer class Login extends Component { 74@inject('actions')
75@observer
76class Login extends Component {
75 static propTypes = { 77 static propTypes = {
76 onSubmit: PropTypes.func.isRequired, 78 onSubmit: PropTypes.func.isRequired,
77 isSubmitting: PropTypes.bool.isRequired, 79 isSubmitting: PropTypes.bool.isRequired,
@@ -84,35 +86,34 @@ export default @inject('actions') @observer class Login extends Component {
84 actions: PropTypes.object.isRequired, 86 actions: PropTypes.object.isRequired,
85 }; 87 };
86 88
87 static contextTypes = { 89 form = new Form(
88 intl: intlShape, 90 {
89 }; 91 fields: {
90 92 email: {
91 form = new Form({ 93 label: this.props.intl.formatMessage(messages.emailLabel),
92 fields: { 94 value: '',
93 email: { 95 validators: [required, email],
94 label: this.context.intl.formatMessage(messages.emailLabel), 96 },
95 value: '', 97 password: {
96 validators: [required, email], 98 label: this.props.intl.formatMessage(messages.passwordLabel),
97 }, 99 value: '',
98 password: { 100 validators: [required],
99 label: this.context.intl.formatMessage(messages.passwordLabel), 101 type: 'password',
100 value: '', 102 },
101 validators: [required],
102 type: 'password',
103 }, 103 },
104 }, 104 },
105 }, this.context.intl); 105 this.props.intl,
106 );
106 107
107 emailField = null; 108 emailField = null;
108 109
109 submit(e) { 110 submit(e) {
110 e.preventDefault(); 111 e.preventDefault();
111 this.form.submit({ 112 this.form.submit({
112 onSuccess: (form) => { 113 onSuccess: form => {
113 this.props.onSubmit(form.values()); 114 this.props.onSubmit(form.values());
114 }, 115 },
115 onError: () => { }, 116 onError: () => {},
116 }); 117 });
117 } 118 }
118 119
@@ -122,7 +123,7 @@ export default @inject('actions') @observer class Login extends Component {
122 123
123 render() { 124 render() {
124 const { form } = this; 125 const { form } = this;
125 const { intl } = this.context; 126 const { intl } = this.props;
126 const { 127 const {
127 isSubmitting, 128 isSubmitting,
128 isTokenExpired, 129 isTokenExpired,
@@ -135,42 +136,47 @@ export default @inject('actions') @observer class Login extends Component {
135 136
136 return ( 137 return (
137 <div className="auth__container"> 138 <div className="auth__container">
138 <form className="franz-form auth__form" onSubmit={(e) => this.submit(e)}> 139 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
139 <img 140 <img src="./assets/images/logo.svg" className="auth__logo" alt="" />
140 src="./assets/images/logo.svg"
141 className="auth__logo"
142 alt=""
143 />
144 <h1>{intl.formatMessage(messages.headline)}</h1> 141 <h1>{intl.formatMessage(messages.headline)}</h1>
145 {isDevMode && !useLiveAPI && ( 142 {isDevMode && !useLiveAPI && (
146 <Infobox type="warning"> 143 <Infobox type="warning">
147 In Dev Mode your data is not persistent. Please use the live app for accessing the production API. 144 In Dev Mode your data is not persistent. Please use the live app
145 for accessing the production API.
148 </Infobox> 146 </Infobox>
149 )} 147 )}
150 {isTokenExpired && ( 148 {isTokenExpired && (
151 <p className="error-message center">{intl.formatMessage(messages.tokenExpired)}</p> 149 <p className="error-message center">
150 {intl.formatMessage(messages.tokenExpired)}
151 </p>
152 )} 152 )}
153 {isServerLogout && ( 153 {isServerLogout && (
154 <p className="error-message center">{intl.formatMessage(messages.serverLogout)}</p> 154 <p className="error-message center">
155 {intl.formatMessage(messages.serverLogout)}
156 </p>
155 )} 157 )}
156 <Input 158 <Input
157 field={form.$('email')} 159 field={form.$('email')}
158 ref={(element) => { this.emailField = element; }} 160 ref={element => {
161 this.emailField = element;
162 }}
159 focus 163 focus
160 /> 164 />
161 <Input 165 <Input field={form.$('password')} showPasswordToggle />
162 field={form.$('password')}
163 showPasswordToggle
164 />
165 {error.code === 'invalid-credentials' && ( 166 {error.code === 'invalid-credentials' && (
166 <> 167 <>
167 <p className="error-message center">{intl.formatMessage(messages.invalidCredentials)}</p> 168 <p className="error-message center">
168 { window.ferdi.stores.settings.all.app.server !== LIVE_FRANZ_API && ( 169 {intl.formatMessage(messages.invalidCredentials)}
170 </p>
171 {window.ferdi.stores.settings.all.app.server !==
172 LIVE_FRANZ_API && (
169 <p className="error-message center"> 173 <p className="error-message center">
170 {intl.formatMessage(messages.customServerQuestion)} 174 {intl.formatMessage(messages.customServerQuestion)}{' '}
171 {' '}
172 <Link 175 <Link
173 to={`${window.ferdi.stores.settings.all.app.server.replace(API_VERSION, '')}/import`} 176 to={`${window.ferdi.stores.settings.all.app.server.replace(
177 API_VERSION,
178 '',
179 )}/import`}
174 target="_blank" 180 target="_blank"
175 style={{ cursor: 'pointer', textDecoration: 'underline' }} 181 style={{ cursor: 'pointer', textDecoration: 'underline' }}
176 > 182 >
@@ -197,12 +203,22 @@ export default @inject('actions') @observer class Login extends Component {
197 )} 203 )}
198 </form> 204 </form>
199 <div className="auth__links"> 205 <div className="auth__links">
200 <Link to={changeServerRoute}>{intl.formatMessage(messages.changeServer)}</Link> 206 <Link to={changeServerRoute}>
201 <a onClick={this.useLocalServer.bind(this)}>{intl.formatMessage(messages.serverless)}</a> 207 {intl.formatMessage(messages.changeServer)}
202 <Link to={signupRoute}>{intl.formatMessage(messages.signupLink)}</Link> 208 </Link>
203 <Link to={passwordRoute}>{intl.formatMessage(messages.passwordLink)}</Link> 209 <a onClick={this.useLocalServer.bind(this)}>
210 {intl.formatMessage(messages.serverless)}
211 </a>
212 <Link to={signupRoute}>
213 {intl.formatMessage(messages.signupLink)}
214 </Link>
215 <Link to={passwordRoute}>
216 {intl.formatMessage(messages.passwordLink)}
217 </Link>
204 </div> 218 </div>
205 </div> 219 </div>
206 ); 220 );
207 } 221 }
208} 222}
223
224export default injectIntl(Login);
diff --git a/src/components/auth/Password.js b/src/components/auth/Password.js
index 1be2097bd..74346b382 100644
--- a/src/components/auth/Password.js
+++ b/src/components/auth/Password.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';
5 5
6import Form from '../../lib/Form'; 6import Form from '../../lib/Form';
7import { required, email } from '../../helpers/validation-helpers'; 7import { required, email } from '../../helpers/validation-helpers';
@@ -14,31 +14,32 @@ import globalMessages from '../../i18n/globalMessages';
14const messages = defineMessages({ 14const messages = defineMessages({
15 headline: { 15 headline: {
16 id: 'password.headline', 16 id: 'password.headline',
17 defaultMessage: '!!!Forgot password', 17 defaultMessage: 'Forgot password',
18 }, 18 },
19 emailLabel: { 19 emailLabel: {
20 id: 'password.email.label', 20 id: 'password.email.label',
21 defaultMessage: '!!!Email address', 21 defaultMessage: 'Email address',
22 }, 22 },
23 successInfo: { 23 successInfo: {
24 id: 'password.successInfo', 24 id: 'password.successInfo',
25 defaultMessage: '!!!Your new password was sent to your email address', 25 defaultMessage: 'Your new password was sent to your email address',
26 }, 26 },
27 noUser: { 27 noUser: {
28 id: 'password.noUser', 28 id: 'password.noUser',
29 defaultMessage: '!!!No user affiliated with that email address', 29 defaultMessage: 'No user affiliated with that email address',
30 }, 30 },
31 signupLink: { 31 signupLink: {
32 id: 'password.link.signup', 32 id: 'password.link.signup',
33 defaultMessage: '!!!Create a free account', 33 defaultMessage: 'Create a free account',
34 }, 34 },
35 loginLink: { 35 loginLink: {
36 id: 'password.link.login', 36 id: 'password.link.login',
37 defaultMessage: '!!!Sign in to your account', 37 defaultMessage: 'Sign in to your account',
38 }, 38 },
39}); 39});
40 40
41export default @observer class Password extends Component { 41@observer
42class Password extends Component {
42 static propTypes = { 43 static propTypes = {
43 onSubmit: PropTypes.func.isRequired, 44 onSubmit: PropTypes.func.isRequired,
44 isSubmitting: PropTypes.bool.isRequired, 45 isSubmitting: PropTypes.bool.isRequired,
@@ -47,24 +48,23 @@ export default @observer class Password extends Component {
47 status: MobxPropTypes.arrayOrObservableArray.isRequired, 48 status: MobxPropTypes.arrayOrObservableArray.isRequired,
48 }; 49 };
49 50
50 static contextTypes = { 51 form = new Form(
51 intl: intlShape, 52 {
52 }; 53 fields: {
53 54 email: {
54 form = new Form({ 55 label: this.props.intl.formatMessage(messages.emailLabel),
55 fields: { 56 value: '',
56 email: { 57 validators: [required, email],
57 label: this.context.intl.formatMessage(messages.emailLabel), 58 },
58 value: '',
59 validators: [required, email],
60 }, 59 },
61 }, 60 },
62 }, this.context.intl); 61 this.props.intl,
62 );
63 63
64 submit(e) { 64 submit(e) {
65 e.preventDefault(); 65 e.preventDefault();
66 this.form.submit({ 66 this.form.submit({
67 onSuccess: (form) => { 67 onSuccess: form => {
68 this.props.onSubmit(form.values()); 68 this.props.onSubmit(form.values());
69 }, 69 },
70 onError: () => {}, 70 onError: () => {},
@@ -73,37 +73,24 @@ export default @observer class Password extends Component {
73 73
74 render() { 74 render() {
75 const { form } = this; 75 const { form } = this;
76 const { intl } = this.context; 76 const { intl } = this.props;
77 const { 77 const { isSubmitting, signupRoute, loginRoute, status } = this.props;
78 isSubmitting,
79 signupRoute,
80 loginRoute,
81 status,
82 } = this.props;
83 78
84 return ( 79 return (
85 <div className="auth__container"> 80 <div className="auth__container">
86 <form className="franz-form auth__form" onSubmit={(e) => this.submit(e)}> 81 <form className="franz-form auth__form" onSubmit={e => this.submit(e)}>
87 <img 82 <img src="./assets/images/logo.svg" className="auth__logo" alt="" />
88 src="./assets/images/logo.svg"
89 className="auth__logo"
90 alt=""
91 />
92 <h1>{intl.formatMessage(messages.headline)}</h1> 83 <h1>{intl.formatMessage(messages.headline)}</h1>
93 {status.length > 0 && status.includes('sent') && ( 84 {status.length > 0 && status.includes('sent') && (
94 <Infobox 85 <Infobox type="success" icon="checkbox-marked-circle-outline">
95 type="success"
96 icon="checkbox-marked-circle-outline"
97 >
98 {intl.formatMessage(messages.successInfo)} 86 {intl.formatMessage(messages.successInfo)}
99 </Infobox> 87 </Infobox>
100 )} 88 )}
101 <Input 89 <Input field={form.$('email')} focus />
102 field={form.$('email')}
103 focus
104 />
105 {status.length > 0 && status.includes('no-user') && ( 90 {status.length > 0 && status.includes('no-user') && (
106 <p className="error-message center">{intl.formatMessage(messages.noUser)}</p> 91 <p className="error-message center">
92 {intl.formatMessage(messages.noUser)}
93 </p>
107 )} 94 )}
108 {isSubmitting ? ( 95 {isSubmitting ? (
109 <Button 96 <Button
@@ -123,9 +110,13 @@ export default @observer class Password extends Component {
123 </form> 110 </form>
124 <div className="auth__links"> 111 <div className="auth__links">
125 <Link to={loginRoute}>{intl.formatMessage(messages.loginLink)}</Link> 112 <Link to={loginRoute}>{intl.formatMessage(messages.loginLink)}</Link>
126 <Link to={signupRoute}>{intl.formatMessage(messages.signupLink)}</Link> 113 <Link to={signupRoute}>
114 {intl.formatMessage(messages.signupLink)}
115 </Link>
127 </div> 116 </div>
128 </div> 117 </div>
129 ); 118 );
130 } 119 }
131} 120}
121
122export default injectIntl(Password);
diff --git a/src/components/auth/SetupAssistant.js b/src/components/auth/SetupAssistant.js
index ded36bbe7..299c40c63 100644
--- a/src/components/auth/SetupAssistant.js
+++ b/src/components/auth/SetupAssistant.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 injectSheet from 'react-jss'; 5import injectSheet from 'react-jss';
6import classnames from 'classnames'; 6import classnames from 'classnames';
7 7
@@ -19,20 +19,20 @@ const SLACK_ID = 'slack';
19const messages = defineMessages({ 19const messages = defineMessages({
20 headline: { 20 headline: {
21 id: 'setupAssistant.headline', 21 id: 'setupAssistant.headline',
22 defaultMessage: "!!!Let's get started", 22 defaultMessage: "Let's get started",
23 }, 23 },
24 subHeadline: { 24 subHeadline: {
25 id: 'setupAssistant.subheadline', 25 id: 'setupAssistant.subheadline',
26 defaultMessage: 26 defaultMessage:
27 '!!!Choose from our most used services and get back on top of your messaging now.', 27 'Choose from our most used services and get back on top of your messaging now.',
28 }, 28 },
29 submitButtonLabel: { 29 submitButtonLabel: {
30 id: 'setupAssistant.submit.label', 30 id: 'setupAssistant.submit.label',
31 defaultMessage: "!!!Let's go", 31 defaultMessage: "Let's go",
32 }, 32 },
33 inviteSuccessInfo: { 33 inviteSuccessInfo: {
34 id: 'invite.successInfo', 34 id: 'invite.successInfo',
35 defaultMessage: '!!!Invitations sent successfully', 35 defaultMessage: 'Invitations sent successfully',
36 }, 36 },
37}); 37});
38 38
@@ -145,10 +145,6 @@ class SetupAssistant extends Component {
145 isInviteSuccessful: false, 145 isInviteSuccessful: false,
146 }; 146 };
147 147
148 static contextTypes = {
149 intl: intlShape,
150 };
151
152 state = { 148 state = {
153 services: [ 149 services: [
154 { 150 {
@@ -189,7 +185,7 @@ class SetupAssistant extends Component {
189 } 185 }
190 186
191 render() { 187 render() {
192 const { intl } = this.context; 188 const { intl } = this.props;
193 const { 189 const {
194 classes, 190 classes,
195 isInviteSuccessful, 191 isInviteSuccessful,
@@ -330,4 +326,4 @@ class SetupAssistant extends Component {
330 } 326 }
331} 327}
332 328
333export default SetupAssistant; 329export default injectIntl(SetupAssistant);
diff --git a/src/components/auth/Signup.js b/src/components/auth/Signup.js
index 6fb41a164..4d39835a2 100644
--- a/src/components/auth/Signup.js
+++ b/src/components/auth/Signup.js
@@ -2,7 +2,7 @@
2import React, { Component } from 'react'; 2import React, { Component } from 'react';
3import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
4import { observer, inject } from 'mobx-react'; 4import { observer, inject } from 'mobx-react';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, injectIntl } from 'react-intl';
6 6
7import { isDevMode, useLiveAPI } from '../../environment'; 7import { isDevMode, useLiveAPI } from '../../environment';
8import Form from '../../lib/Form'; 8import Form from '../../lib/Form';
@@ -19,63 +19,65 @@ import { termsBase } from '../../api/apiBase';
19const messages = defineMessages({ 19const messages = defineMessages({
20 headline: { 20 headline: {
21 id: 'signup.headline', 21 id: 'signup.headline',
22 defaultMessage: '!!!Sign up', 22 defaultMessage: 'Sign up',
23 }, 23 },
24 firstnameLabel: { 24 firstnameLabel: {
25 id: 'signup.firstname.label', 25 id: 'signup.firstname.label',
26 defaultMessage: '!!!Firstname', 26 defaultMessage: 'Firstname',
27 }, 27 },
28 lastnameLabel: { 28 lastnameLabel: {
29 id: 'signup.lastname.label', 29 id: 'signup.lastname.label',
30 defaultMessage: '!!!Lastname', 30 defaultMessage: 'Lastname',
31 }, 31 },
32 emailLabel: { 32 emailLabel: {
33 id: 'signup.email.label', 33 id: 'signup.email.label',
34 defaultMessage: '!!!Email address', 34 defaultMessage: 'Email address',
35 }, 35 },
36 // companyLabel: { 36 // companyLabel: {
37 // id: 'signup.company.label', 37 // id: 'signup.company.label',
38 // defaultMessage: '!!!Company', 38 // defaultMessage: 'Company',
39 // }, 39 // },
40 passwordLabel: { 40 passwordLabel: {
41 id: 'signup.password.label', 41 id: 'signup.password.label',
42 defaultMessage: '!!!Password', 42 defaultMessage: 'Password',
43 }, 43 },
44 legalInfo: { 44 legalInfo: {
45 id: 'signup.legal.info', 45 id: 'signup.legal.info',
46 defaultMessage: '!!!By creating a Ferdi account you accept the', 46 defaultMessage: 'By creating a Ferdi account you accept the',
47 }, 47 },
48 terms: { 48 terms: {
49 id: 'signup.legal.terms', 49 id: 'signup.legal.terms',
50 defaultMessage: '!!!Terms of service', 50 defaultMessage: 'Terms of service',
51 }, 51 },
52 privacy: { 52 privacy: {
53 id: 'signup.legal.privacy', 53 id: 'signup.legal.privacy',
54 defaultMessage: '!!!Privacy Statement', 54 defaultMessage: 'Privacy Statement',
55 }, 55 },
56 submitButtonLabel: { 56 submitButtonLabel: {
57 id: 'signup.submit.label', 57 id: 'signup.submit.label',
58 defaultMessage: '!!!Create account', 58 defaultMessage: 'Create account',
59 }, 59 },
60 loginLink: { 60 loginLink: {
61 id: 'signup.link.login', 61 id: 'signup.link.login',
62 defaultMessage: '!!!Already have an account, sign in?', 62 defaultMessage: 'Already have an account, sign in?',
63 }, 63 },
64 changeServer: { 64 changeServer: {
65 id: 'login.changeServer', 65 id: 'login.changeServer',
66 defaultMessage: '!!!Change server', 66 defaultMessage: 'Change server',
67 }, 67 },
68 serverless: { 68 serverless: {
69 id: 'services.serverless', 69 id: 'services.serverless',
70 defaultMessage: '!!!Use Ferdi without an Account', 70 defaultMessage: 'Use Ferdi without an Account',
71 }, 71 },
72 emailDuplicate: { 72 emailDuplicate: {
73 id: 'signup.emailDuplicate', 73 id: 'signup.emailDuplicate',
74 defaultMessage: '!!!A user with that email address already exists', 74 defaultMessage: 'A user with that email address already exists',
75 }, 75 },
76}); 76});
77 77
78export default @inject('actions') @observer class Signup extends Component { 78@inject('actions')
79@observer
80class Signup extends Component {
79 static propTypes = { 81 static propTypes = {
80 onSubmit: PropTypes.func.isRequired, 82 onSubmit: PropTypes.func.isRequired,
81 isSubmitting: PropTypes.bool.isRequired, 83 isSubmitting: PropTypes.bool.isRequired,
@@ -85,40 +87,39 @@ export default @inject('actions') @observer class Signup extends Component {
85 actions: PropTypes.object.isRequired, 87 actions: PropTypes.object.isRequired,
86 }; 88 };
87 89
88 static contextTypes = { 90 form = new Form(
89 intl: intlShape, 91 {
90 }; 92 fields: {
91 93 firstname: {
92 form = new Form({ 94 label: this.props.intl.formatMessage(messages.firstnameLabel),
93 fields: { 95 value: '',
94 firstname: { 96 validators: [required],
95 label: this.context.intl.formatMessage(messages.firstnameLabel), 97 },
96 value: '', 98 lastname: {
97 validators: [required], 99 label: this.props.intl.formatMessage(messages.lastnameLabel),
98 }, 100 value: '',
99 lastname: { 101 validators: [required],
100 label: this.context.intl.formatMessage(messages.lastnameLabel), 102 },
101 value: '', 103 email: {
102 validators: [required], 104 label: this.props.intl.formatMessage(messages.emailLabel),
103 }, 105 value: '',
104 email: { 106 validators: [required, email],
105 label: this.context.intl.formatMessage(messages.emailLabel), 107 },
106 value: '', 108 password: {
107 validators: [required, email], 109 label: this.props.intl.formatMessage(messages.passwordLabel),
108 }, 110 value: '',
109 password: { 111 validators: [required, minLength(6)],
110 label: this.context.intl.formatMessage(messages.passwordLabel), 112 type: 'password',
111 value: '', 113 },
112 validators: [required, minLength(6)],
113 type: 'password',
114 }, 114 },
115 }, 115 },
116 }, this.context.intl); 116 this.props.intl,
117 );
117 118
118 submit(e) { 119 submit(e) {
119 e.preventDefault(); 120 e.preventDefault();
120 this.form.submit({ 121 this.form.submit({
121 onSuccess: (form) => { 122 onSuccess: form => {
122 this.props.onSubmit(form.values()); 123 this.props.onSubmit(form.values());
123 }, 124 },
124 onError: () => {}, 125 onError: () => {},
@@ -131,24 +132,22 @@ export default @inject('actions') @observer class Signup extends Component {
131 132
132 render() { 133 render() {
133 const { form } = this; 134 const { form } = this;
134 const { intl } = this.context; 135 const { intl } = this.props;
135 const { 136 const { isSubmitting, loginRoute, error, changeServerRoute } = this.props;
136 isSubmitting, loginRoute, error, changeServerRoute,
137 } = this.props;
138 137
139 return ( 138 return (
140 <div className="auth__scroll-container"> 139 <div className="auth__scroll-container">
141 <div className="auth__container auth__container--signup"> 140 <div className="auth__container auth__container--signup">
142 <form className="franz-form auth__form" onSubmit={(e) => this.submit(e)}> 141 <form
143 <img 142 className="franz-form auth__form"
144 src="./assets/images/logo.svg" 143 onSubmit={e => this.submit(e)}
145 className="auth__logo" 144 >
146 alt="" 145 <img src="./assets/images/logo.svg" className="auth__logo" alt="" />
147 />
148 <h1>{intl.formatMessage(messages.headline)}</h1> 146 <h1>{intl.formatMessage(messages.headline)}</h1>
149 {isDevMode && !useLiveAPI && ( 147 {isDevMode && !useLiveAPI && (
150 <Infobox type="warning"> 148 <Infobox type="warning">
151 In Dev Mode your data is not persistent. Please use the live app for accesing the production API. 149 In Dev Mode your data is not persistent. Please use the live app
150 for accesing the production API.
152 </Infobox> 151 </Infobox>
153 )} 152 )}
154 <div className="grid__row"> 153 <div className="grid__row">
@@ -162,7 +161,9 @@ export default @inject('actions') @observer class Signup extends Component {
162 scorePassword 161 scorePassword
163 /> 162 />
164 {error.code === 'email-duplicate' && ( 163 {error.code === 'email-duplicate' && (
165 <p className="error-message center">{intl.formatMessage(messages.emailDuplicate)}</p> 164 <p className="error-message center">
165 {intl.formatMessage(messages.emailDuplicate)}
166 </p>
166 )} 167 )}
167 {isSubmitting ? ( 168 {isSubmitting ? (
168 <Button 169 <Button
@@ -200,12 +201,20 @@ export default @inject('actions') @observer class Signup extends Component {
200 </p> 201 </p>
201 </form> 202 </form>
202 <div className="auth__links"> 203 <div className="auth__links">
203 <Link to={changeServerRoute}>{intl.formatMessage(messages.changeServer)}</Link> 204 <Link to={changeServerRoute}>
204 <a onClick={this.useLocalServer.bind(this)}>{intl.formatMessage(messages.serverless)}</a> 205 {intl.formatMessage(messages.changeServer)}
205 <Link to={loginRoute}>{intl.formatMessage(messages.loginLink)}</Link> 206 </Link>
207 <a onClick={this.useLocalServer.bind(this)}>
208 {intl.formatMessage(messages.serverless)}
209 </a>
210 <Link to={loginRoute}>
211 {intl.formatMessage(messages.loginLink)}
212 </Link>
206 </div> 213 </div>
207 </div> 214 </div>
208 </div> 215 </div>
209 ); 216 );
210 } 217 }
211} 218}
219
220export default injectIntl(Signup);
diff --git a/src/components/auth/Welcome.js b/src/components/auth/Welcome.js
index cb522e26e..2d2e2ab28 100644
--- a/src/components/auth/Welcome.js
+++ b/src/components/auth/Welcome.js
@@ -2,7 +2,7 @@
2import React, { Component } from 'react'; 2import React, { Component } from 'react';
3import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
4import { observer, PropTypes as MobxPropTypes, inject } from 'mobx-react'; 4import { observer, PropTypes as MobxPropTypes, inject } from 'mobx-react';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, injectIntl } from 'react-intl';
6import serverlessLogin from '../../helpers/serverless-helpers'; 6import serverlessLogin from '../../helpers/serverless-helpers';
7 7
8import Link from '../ui/Link'; 8import Link from '../ui/Link';
@@ -10,19 +10,21 @@ import Link from '../ui/Link';
10const messages = defineMessages({ 10const messages = defineMessages({
11 signupButton: { 11 signupButton: {
12 id: 'welcome.signupButton', 12 id: 'welcome.signupButton',
13 defaultMessage: '!!!Create a free account', 13 defaultMessage: 'Create a free account',
14 }, 14 },
15 loginButton: { 15 loginButton: {
16 id: 'welcome.loginButton', 16 id: 'welcome.loginButton',
17 defaultMessage: '!!!Login to your account', 17 defaultMessage: 'Login to your account',
18 }, 18 },
19 serverless: { 19 serverless: {
20 id: 'services.serverless', 20 id: 'services.serverless',
21 defaultMessage: '!!!Use Ferdi without an Account', 21 defaultMessage: 'Use Ferdi without an Account',
22 }, 22 },
23}); 23});
24 24
25export default @inject('actions') @observer class Login extends Component { 25@inject('actions')
26@observer
27class Login extends Component {
26 static propTypes = { 28 static propTypes = {
27 loginRoute: PropTypes.string.isRequired, 29 loginRoute: PropTypes.string.isRequired,
28 signupRoute: PropTypes.string.isRequired, 30 signupRoute: PropTypes.string.isRequired,
@@ -31,27 +33,22 @@ export default @inject('actions') @observer class Login extends Component {
31 actions: PropTypes.object.isRequired, 33 actions: PropTypes.object.isRequired,
32 }; 34 };
33 35
34 static contextTypes = {
35 intl: intlShape,
36 };
37
38 useLocalServer() { 36 useLocalServer() {
39 serverlessLogin(this.props.actions); 37 serverlessLogin(this.props.actions);
40 } 38 }
41 39
42 render() { 40 render() {
43 const { intl } = this.context; 41 const { intl } = this.props;
44 const { 42 const { loginRoute, signupRoute, changeServerRoute, recipes } = this.props;
45 loginRoute,
46 signupRoute,
47 changeServerRoute,
48 recipes,
49 } = this.props;
50 43
51 return ( 44 return (
52 <div className="welcome"> 45 <div className="welcome">
53 <div className="welcome__content"> 46 <div className="welcome__content">
54 <img src="./assets/images/logo.svg" className="welcome__logo" alt="" /> 47 <img
48 src="./assets/images/logo.svg"
49 className="welcome__logo"
50 alt=""
51 />
55 {/* <img src="./assets/images/welcome.png" className="welcome__services" alt="" /> */} 52 {/* <img src="./assets/images/welcome.png" className="welcome__services" alt="" /> */}
56 <div className="welcome__text"> 53 <div className="welcome__text">
57 <h1>Ferdi</h1> 54 <h1>Ferdi</h1>
@@ -73,27 +70,21 @@ export default @inject('actions') @observer class Login extends Component {
73 <br /> 70 <br />
74 71
75 <Link to={changeServerRoute}> 72 <Link to={changeServerRoute}>
76 <span style={{ 73 <span
77 textAlign: 'center', 74 style={{
78 width: '100%', 75 textAlign: 'center',
79 cursor: 'pointer', 76 width: '100%',
80 }} 77 cursor: 'pointer',
78 }}
81 > 79 >
82 Change server 80 Change server
83 </span> 81 </span>
84 </Link> 82 </Link>
85 </div> 83 </div>
86 <div className="welcome__featured-services"> 84 <div className="welcome__featured-services">
87 {recipes.map((recipe) => ( 85 {recipes.map(recipe => (
88 <div 86 <div key={recipe.id} className="welcome__featured-service">
89 key={recipe.id} 87 <img key={recipe.id} src={recipe.icons.svg} alt="" />
90 className="welcome__featured-service"
91 >
92 <img
93 key={recipe.id}
94 src={recipe.icons.svg}
95 alt=""
96 />
97 </div> 88 </div>
98 ))} 89 ))}
99 </div> 90 </div>
@@ -101,3 +92,5 @@ export default @inject('actions') @observer class Login extends Component {
101 ); 92 );
102 } 93 }
103} 94}
95
96export default injectIntl(Login);
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index e6e5d40fe..83e037083 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.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 { TitleBar } from 'electron-react-titlebar'; 5import { TitleBar } from 'electron-react-titlebar';
6import injectSheet from 'react-jss'; 6import injectSheet from 'react-jss';
7 7
@@ -27,20 +27,20 @@ function createMarkup(HTMLString) {
27const messages = defineMessages({ 27const messages = defineMessages({
28 servicesUpdated: { 28 servicesUpdated: {
29 id: 'infobar.servicesUpdated', 29 id: 'infobar.servicesUpdated',
30 defaultMessage: '!!!Your services have been updated.', 30 defaultMessage: 'Your services have been updated.',
31 }, 31 },
32 buttonReloadServices: { 32 buttonReloadServices: {
33 id: 'infobar.buttonReloadServices', 33 id: 'infobar.buttonReloadServices',
34 defaultMessage: '!!!Reload services', 34 defaultMessage: 'Reload services',
35 }, 35 },
36 requiredRequestsFailed: { 36 requiredRequestsFailed: {
37 id: 'infobar.requiredRequestsFailed', 37 id: 'infobar.requiredRequestsFailed',
38 defaultMessage: '!!!Could not load services and user information', 38 defaultMessage: 'Could not load services and user information',
39 }, 39 },
40 authRequestFailed: { 40 authRequestFailed: {
41 id: 'infobar.authRequestFailed', 41 id: 'infobar.authRequestFailed',
42 defaultMessage: 42 defaultMessage:
43 '!!!There were errors while trying to perform an authenticated request. Please try logging out and back in if this error persists.', 43 'There were errors while trying to perform an authenticated request. Please try logging out and back in if this error persists.',
44 }, 44 },
45}); 45});
46 46
@@ -94,10 +94,6 @@ class AppLayout extends Component {
94 children: [], 94 children: [],
95 }; 95 };
96 96
97 static contextTypes = {
98 intl: intlShape,
99 };
100
101 render() { 97 render() {
102 const { 98 const {
103 classes, 99 classes,
@@ -119,7 +115,7 @@ class AppLayout extends Component {
119 areRequiredRequestsLoading, 115 areRequiredRequestsLoading,
120 } = this.props; 116 } = this.props;
121 117
122 const { intl } = this.context; 118 const { intl } = this.props;
123 119
124 return ( 120 return (
125 <ErrorBoundary> 121 <ErrorBoundary>
@@ -213,4 +209,4 @@ class AppLayout extends Component {
213 } 209 }
214} 210}
215 211
216export default AppLayout; 212export default injectIntl(AppLayout);
diff --git a/src/components/layout/Sidebar.js b/src/components/layout/Sidebar.js
index 1ee7733b9..87233f7ca 100644
--- a/src/components/layout/Sidebar.js
+++ b/src/components/layout/Sidebar.js
@@ -1,12 +1,19 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import ReactTooltip from 'react-tooltip'; 3import ReactTooltip from 'react-tooltip';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, injectIntl } from 'react-intl';
5import { inject, observer } from 'mobx-react'; 5import { inject, observer } from 'mobx-react';
6import { Link } from 'react-router'; 6import { Link } from 'react-router';
7 7
8import Tabbar from '../services/tabs/Tabbar'; 8import Tabbar from '../services/tabs/Tabbar';
9import { settingsShortcutKey, lockFerdiShortcutKey, todosToggleShortcutKey, workspaceToggleShortcutKey, addNewServiceShortcutKey, muteFerdiShortcutKey } from '../../environment'; 9import {
10 settingsShortcutKey,
11 lockFerdiShortcutKey,
12 todosToggleShortcutKey,
13 workspaceToggleShortcutKey,
14 addNewServiceShortcutKey,
15 muteFerdiShortcutKey,
16} from '../../environment';
10import { workspaceStore } from '../../features/workspaces'; 17import { workspaceStore } from '../../features/workspaces';
11import { todosStore } from '../../features/todos'; 18import { todosStore } from '../../features/todos';
12import { todoActions } from '../../features/todos/actions'; 19import { todoActions } from '../../features/todos/actions';
@@ -17,39 +24,41 @@ import globalMessages from '../../i18n/globalMessages';
17const messages = defineMessages({ 24const messages = defineMessages({
18 addNewService: { 25 addNewService: {
19 id: 'sidebar.addNewService', 26 id: 'sidebar.addNewService',
20 defaultMessage: '!!!Add new service', 27 defaultMessage: 'Add new service',
21 }, 28 },
22 mute: { 29 mute: {
23 id: 'sidebar.muteApp', 30 id: 'sidebar.muteApp',
24 defaultMessage: '!!!Disable notifications & audio', 31 defaultMessage: 'Disable notifications & audio',
25 }, 32 },
26 unmute: { 33 unmute: {
27 id: 'sidebar.unmuteApp', 34 id: 'sidebar.unmuteApp',
28 defaultMessage: '!!!Enable notifications & audio', 35 defaultMessage: 'Enable notifications & audio',
29 }, 36 },
30 openWorkspaceDrawer: { 37 openWorkspaceDrawer: {
31 id: 'sidebar.openWorkspaceDrawer', 38 id: 'sidebar.openWorkspaceDrawer',
32 defaultMessage: '!!!Open workspace drawer', 39 defaultMessage: 'Open workspace drawer',
33 }, 40 },
34 closeWorkspaceDrawer: { 41 closeWorkspaceDrawer: {
35 id: 'sidebar.closeWorkspaceDrawer', 42 id: 'sidebar.closeWorkspaceDrawer',
36 defaultMessage: '!!!Close workspace drawer', 43 defaultMessage: 'Close workspace drawer',
37 }, 44 },
38 openTodosDrawer: { 45 openTodosDrawer: {
39 id: 'sidebar.openTodosDrawer', 46 id: 'sidebar.openTodosDrawer',
40 defaultMessage: '!!!Open Ferdi Todos', 47 defaultMessage: 'Open Ferdi Todos',
41 }, 48 },
42 closeTodosDrawer: { 49 closeTodosDrawer: {
43 id: 'sidebar.closeTodosDrawer', 50 id: 'sidebar.closeTodosDrawer',
44 defaultMessage: '!!!Close Ferdi Todos', 51 defaultMessage: 'Close Ferdi Todos',
45 }, 52 },
46 lockFerdi: { 53 lockFerdi: {
47 id: 'sidebar.lockFerdi', 54 id: 'sidebar.lockFerdi',
48 defaultMessage: '!!!Lock Ferdi', 55 defaultMessage: 'Lock Ferdi',
49 }, 56 },
50}); 57});
51 58
52export default @inject('stores', 'actions') @observer class Sidebar extends Component { 59@inject('stores', 'actions')
60@observer
61class Sidebar extends Component {
53 static propTypes = { 62 static propTypes = {
54 openSettings: PropTypes.func.isRequired, 63 openSettings: PropTypes.func.isRequired,
55 closeSettings: PropTypes.func.isRequired, 64 closeSettings: PropTypes.func.isRequired,
@@ -79,10 +88,6 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp
79 }).isRequired, 88 }).isRequired,
80 }; 89 };
81 90
82 static contextTypes = {
83 intl: intlShape,
84 };
85
86 state = { 91 state = {
87 tooltipEnabled: true, 92 tooltipEnabled: true,
88 }; 93 };
@@ -115,14 +120,14 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp
115 actions, 120 actions,
116 isTodosServiceActive, 121 isTodosServiceActive,
117 } = this.props; 122 } = this.props;
118 const { intl } = this.context; 123 const { intl } = this.props;
119 const todosToggleMessage = ( 124 const todosToggleMessage = todosStore.isTodosPanelVisible
120 todosStore.isTodosPanelVisible ? messages.closeTodosDrawer : messages.openTodosDrawer 125 ? messages.closeTodosDrawer
121 ); 126 : messages.openTodosDrawer;
122 127
123 const workspaceToggleMessage = ( 128 const workspaceToggleMessage = isWorkspaceDrawerOpen
124 isWorkspaceDrawerOpen ? messages.closeWorkspaceDrawer : messages.openWorkspaceDrawer 129 ? messages.closeWorkspaceDrawer
125 ); 130 : messages.openWorkspaceDrawer;
126 const isLoggedIn = Boolean(localStorage.getItem('authToken')); 131 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
127 132
128 return ( 133 return (
@@ -133,9 +138,9 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp
133 disableToolTip={() => this.disableToolTip()} 138 disableToolTip={() => this.disableToolTip()}
134 useVerticalStyle={stores.settings.all.app.useVerticalStyle} 139 useVerticalStyle={stores.settings.all.app.useVerticalStyle}
135 /> 140 />
136 { isLoggedIn ? ( 141 {isLoggedIn ? (
137 <> 142 <>
138 { stores.settings.all.app.lockingFeatureEnabled ? ( 143 {stores.settings.all.app.lockingFeatureEnabled ? (
139 <button 144 <button
140 type="button" 145 type="button"
141 className="sidebar__button" 146 className="sidebar__button"
@@ -147,12 +152,15 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp
147 }, 152 },
148 }); 153 });
149 }} 154 }}
150 data-tip={`${intl.formatMessage(messages.lockFerdi)} (${lockFerdiShortcutKey(false)})`} 155 data-tip={`${intl.formatMessage(
156 messages.lockFerdi,
157 )} (${lockFerdiShortcutKey(false)})`}
151 > 158 >
152 <i className="mdi mdi-lock" /> 159 <i className="mdi mdi-lock" />
153 </button> 160 </button>
154 ) : null} 161 ) : null}
155 {todosStore.isFeatureEnabled && todosStore.isFeatureEnabledByUser ? ( 162 {todosStore.isFeatureEnabled &&
163 todosStore.isFeatureEnabledByUser ? (
156 <button 164 <button
157 type="button" 165 type="button"
158 onClick={() => { 166 onClick={() => {
@@ -160,12 +168,16 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp
160 this.updateToolTip(); 168 this.updateToolTip();
161 }} 169 }}
162 disabled={isTodosServiceActive} 170 disabled={isTodosServiceActive}
163 className={`sidebar__button sidebar__button--todos ${todosStore.isTodosPanelVisible ? 'is-active' : ''}`} 171 className={`sidebar__button sidebar__button--todos ${
164 data-tip={`${intl.formatMessage(todosToggleMessage)} (${todosToggleShortcutKey(false)})`} 172 todosStore.isTodosPanelVisible ? 'is-active' : ''
173 }`}
174 data-tip={`${intl.formatMessage(
175 todosToggleMessage,
176 )} (${todosToggleShortcutKey(false)})`}
165 > 177 >
166 <i className="mdi mdi-check-all" /> 178 <i className="mdi mdi-check-all" />
167 </button> 179 </button>
168 ) : null} 180 ) : null}
169 {workspaceStore.isFeatureEnabled ? ( 181 {workspaceStore.isFeatureEnabled ? (
170 <button 182 <button
171 type="button" 183 type="button"
@@ -173,8 +185,12 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp
173 toggleWorkspaceDrawer(); 185 toggleWorkspaceDrawer();
174 this.updateToolTip(); 186 this.updateToolTip();
175 }} 187 }}
176 className={`sidebar__button sidebar__button--workspaces ${isWorkspaceDrawerOpen ? 'is-active' : ''}`} 188 className={`sidebar__button sidebar__button--workspaces ${
177 data-tip={`${intl.formatMessage(workspaceToggleMessage)} (${workspaceToggleShortcutKey(false)})`} 189 isWorkspaceDrawerOpen ? 'is-active' : ''
190 }`}
191 data-tip={`${intl.formatMessage(
192 workspaceToggleMessage,
193 )} (${workspaceToggleShortcutKey(false)})`}
178 > 194 >
179 <i className="mdi mdi-view-grid" /> 195 <i className="mdi mdi-view-grid" />
180 </button> 196 </button>
@@ -185,8 +201,12 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp
185 toggleMuteApp(); 201 toggleMuteApp();
186 this.updateToolTip(); 202 this.updateToolTip();
187 }} 203 }}
188 className={`sidebar__button sidebar__button--audio ${isAppMuted ? 'is-muted' : ''}`} 204 className={`sidebar__button sidebar__button--audio ${
189 data-tip={`${intl.formatMessage(isAppMuted ? messages.unmute : messages.mute)} (${muteFerdiShortcutKey(false)})`} 205 isAppMuted ? 'is-muted' : ''
206 }`}
207 data-tip={`${intl.formatMessage(
208 isAppMuted ? messages.unmute : messages.mute,
209 )} (${muteFerdiShortcutKey(false)})`}
190 > 210 >
191 <i className={`mdi mdi-bell${isAppMuted ? '-off' : ''}`} /> 211 <i className={`mdi mdi-bell${isAppMuted ? '-off' : ''}`} />
192 </button> 212 </button>
@@ -194,7 +214,9 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp
194 type="button" 214 type="button"
195 onClick={() => openSettings({ path: 'recipes' })} 215 onClick={() => openSettings({ path: 'recipes' })}
196 className="sidebar__button sidebar__button--new-service" 216 className="sidebar__button sidebar__button--new-service"
197 data-tip={`${intl.formatMessage(messages.addNewService)} (${addNewServiceShortcutKey(false)})`} 217 data-tip={`${intl.formatMessage(
218 messages.addNewService,
219 )} (${addNewServiceShortcutKey(false)})`}
198 > 220 >
199 <i className="mdi mdi-plus-box" /> 221 <i className="mdi mdi-plus-box" />
200 </button> 222 </button>
@@ -212,15 +234,17 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp
212 type="button" 234 type="button"
213 onClick={() => openSettings({ path: 'app' })} 235 onClick={() => openSettings({ path: 'app' })}
214 className="sidebar__button sidebar__button--settings" 236 className="sidebar__button sidebar__button--settings"
215 data-tip={`${intl.formatMessage(globalMessages.settings)} (${settingsShortcutKey(false)})`} 237 data-tip={`${intl.formatMessage(
238 globalMessages.settings,
239 )} (${settingsShortcutKey(false)})`}
216 > 240 >
217 <i className="mdi mdi-cog" /> 241 <i className="mdi mdi-cog" />
218 { (this.props.stores.app.updateStatus === this.props.stores.app.updateStatusTypes.AVAILABLE 242 {(this.props.stores.app.updateStatus ===
219 || this.props.stores.app.updateStatus === this.props.stores.app.updateStatusTypes.DOWNLOADED) && ( 243 this.props.stores.app.updateStatusTypes.AVAILABLE ||
220 <span className="update-available"> 244 this.props.stores.app.updateStatus ===
221 245 this.props.stores.app.updateStatusTypes.DOWNLOADED) && (
222 </span> 246 <span className="update-available">•</span>
223 ) } 247 )}
224 </button> 248 </button>
225 {this.state.tooltipEnabled && ( 249 {this.state.tooltipEnabled && (
226 <ReactTooltip place="right" type="dark" effect="solid" /> 250 <ReactTooltip place="right" type="dark" effect="solid" />
@@ -229,3 +253,5 @@ export default @inject('stores', 'actions') @observer class Sidebar extends Comp
229 ); 253 );
230 } 254 }
231} 255}
256
257export default injectIntl(Sidebar);
diff --git a/src/components/services/content/ConnectionLostBanner.js b/src/components/services/content/ConnectionLostBanner.js
index ebe863333..423edb3c7 100644
--- a/src/components/services/content/ConnectionLostBanner.js
+++ b/src/components/services/content/ConnectionLostBanner.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss'; 4import injectSheet from 'react-jss';
5import { Icon } from '@meetfranz/ui'; 5import { Icon } from '@meetfranz/ui';
6import { intlShape, defineMessages } from 'react-intl'; 6import { defineMessages, injectIntl } from 'react-intl';
7 7
8import { mdiAlert } from '@mdi/js'; 8import { mdiAlert } from '@mdi/js';
9import { LIVE_API_FERDI_WEBSITE } from '../../../config'; 9import { LIVE_API_FERDI_WEBSITE } from '../../../config';
@@ -12,15 +12,15 @@ import { LIVE_API_FERDI_WEBSITE } from '../../../config';
12const messages = defineMessages({ 12const messages = defineMessages({
13 text: { 13 text: {
14 id: 'connectionLostBanner.message', 14 id: 'connectionLostBanner.message',
15 defaultMessage: '!!!Oh no! Ferdi lost the connection to {name}.', 15 defaultMessage: 'Oh no! Ferdi lost the connection to {name}.',
16 }, 16 },
17 moreInformation: { 17 moreInformation: {
18 id: 'connectionLostBanner.informationLink', 18 id: 'connectionLostBanner.informationLink',
19 defaultMessage: '!!!What happened?', 19 defaultMessage: 'What happened?',
20 }, 20 },
21 cta: { 21 cta: {
22 id: 'connectionLostBanner.cta', 22 id: 'connectionLostBanner.cta',
23 defaultMessage: '!!!Reload Service', 23 defaultMessage: 'Reload Service',
24 }, 24 },
25}); 25});
26 26
@@ -78,16 +78,12 @@ class ConnectionLostBanner extends Component {
78 reload: PropTypes.func.isRequired, 78 reload: PropTypes.func.isRequired,
79 }; 79 };
80 80
81 static contextTypes = {
82 intl: intlShape,
83 };
84
85 inputRef = React.createRef(); 81 inputRef = React.createRef();
86 82
87 render() { 83 render() {
88 const { classes, name, reload } = this.props; 84 const { classes, name, reload } = this.props;
89 85
90 const { intl } = this.context; 86 const { intl } = this.props;
91 87
92 return ( 88 return (
93 <div className={classes.root}> 89 <div className={classes.root}>
@@ -110,4 +106,4 @@ class ConnectionLostBanner extends Component {
110 } 106 }
111} 107}
112 108
113export default ConnectionLostBanner; 109export default injectIntl(ConnectionLostBanner);
diff --git a/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js b/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js
index 36e0ac418..41d84c0b0 100644
--- a/src/components/services/content/ErrorHandlers/WebviewErrorHandler.js
+++ b/src/components/services/content/ErrorHandlers/WebviewErrorHandler.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 injectSheet from 'react-jss'; 5import injectSheet from 'react-jss';
6 6
7import Button from '../../../ui/Button'; 7import Button from '../../../ui/Button';
@@ -11,27 +11,29 @@ import styles from './styles';
11const messages = defineMessages({ 11const messages = defineMessages({
12 headline: { 12 headline: {
13 id: 'service.errorHandler.headline', 13 id: 'service.errorHandler.headline',
14 defaultMessage: '!!!Oh no!', 14 defaultMessage: 'Oh no!',
15 }, 15 },
16 text: { 16 text: {
17 id: 'service.errorHandler.text', 17 id: 'service.errorHandler.text',
18 defaultMessage: '!!!{name} has failed to load.', 18 defaultMessage: '{name} has failed to load.',
19 }, 19 },
20 action: { 20 action: {
21 id: 'service.errorHandler.action', 21 id: 'service.errorHandler.action',
22 defaultMessage: '!!!Reload {name}', 22 defaultMessage: 'Reload {name}',
23 }, 23 },
24 editAction: { 24 editAction: {
25 id: 'service.errorHandler.editAction', 25 id: 'service.errorHandler.editAction',
26 defaultMessage: '!!!Edit {name}', 26 defaultMessage: 'Edit {name}',
27 }, 27 },
28 errorMessage: { 28 errorMessage: {
29 id: 'service.errorHandler.message', 29 id: 'service.errorHandler.message',
30 defaultMessage: '!!!Error:', 30 defaultMessage: 'Error:',
31 }, 31 },
32}); 32});
33 33
34export default @injectSheet(styles) @observer class WebviewErrorHandler extends Component { 34@injectSheet(styles)
35@observer
36class WebviewErrorHandler extends Component {
35 static propTypes = { 37 static propTypes = {
36 name: PropTypes.string.isRequired, 38 name: PropTypes.string.isRequired,
37 reload: PropTypes.func.isRequired, 39 reload: PropTypes.func.isRequired,
@@ -40,30 +42,16 @@ export default @injectSheet(styles) @observer class WebviewErrorHandler extends
40 classes: PropTypes.object.isRequired, 42 classes: PropTypes.object.isRequired,
41 }; 43 };
42 44
43 static contextTypes = {
44 intl: intlShape,
45 };
46
47 render() { 45 render() {
48 const { 46 const { name, reload, edit, errorMessage, classes } = this.props;
49 name, 47 const { intl } = this.props;
50 reload,
51 edit,
52 errorMessage,
53 classes,
54 } = this.props;
55 const { intl } = this.context;
56 48
57 return ( 49 return (
58 <div className={classes.component}> 50 <div className={classes.component}>
59 <h1>{intl.formatMessage(messages.headline)}</h1> 51 <h1>{intl.formatMessage(messages.headline)}</h1>
60 <p>{intl.formatMessage(messages.text, { name })}</p> 52 <p>{intl.formatMessage(messages.text, { name })}</p>
61 <p> 53 <p>
62 <strong> 54 <strong>{intl.formatMessage(messages.errorMessage)}:</strong>{' '}
63 {intl.formatMessage(messages.errorMessage)}
64 :
65 </strong>
66 {' '}
67 {errorMessage} 55 {errorMessage}
68 </p> 56 </p>
69 <div className={classes.buttonContainer}> 57 <div className={classes.buttonContainer}>
@@ -82,3 +70,5 @@ export default @injectSheet(styles) @observer class WebviewErrorHandler extends
82 ); 70 );
83 } 71 }
84} 72}
73
74export default injectIntl(WebviewErrorHandler);
diff --git a/src/components/services/content/ServiceDisabled.js b/src/components/services/content/ServiceDisabled.js
index d0f12256e..e59ed58bd 100644
--- a/src/components/services/content/ServiceDisabled.js
+++ b/src/components/services/content/ServiceDisabled.js
@@ -1,38 +1,35 @@
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 Button from '../../ui/Button'; 6import Button from '../../ui/Button';
7 7
8const messages = defineMessages({ 8const messages = defineMessages({
9 headline: { 9 headline: {
10 id: 'service.disabledHandler.headline', 10 id: 'service.disabledHandler.headline',
11 defaultMessage: '!!!{name} is disabled', 11 defaultMessage: '{name} is disabled',
12 }, 12 },
13 action: { 13 action: {
14 id: 'service.disabledHandler.action', 14 id: 'service.disabledHandler.action',
15 defaultMessage: '!!!Enable {name}', 15 defaultMessage: 'Enable {name}',
16 }, 16 },
17}); 17});
18 18
19export default @observer class ServiceDisabled extends Component { 19@observer
20class ServiceDisabled extends Component {
20 static propTypes = { 21 static propTypes = {
21 name: PropTypes.string.isRequired, 22 name: PropTypes.string.isRequired,
22 enable: PropTypes.func.isRequired, 23 enable: PropTypes.func.isRequired,
23 }; 24 };
24 25
25 static contextTypes = {
26 intl: intlShape,
27 };
28
29 countdownInterval = null; 26 countdownInterval = null;
30 27
31 countdownIntervalTimeout = 1000; 28 countdownIntervalTimeout = 1000;
32 29
33 render() { 30 render() {
34 const { name, enable } = this.props; 31 const { name, enable } = this.props;
35 const { intl } = this.context; 32 const { intl } = this.props;
36 33
37 return ( 34 return (
38 <div className="services__info-layer"> 35 <div className="services__info-layer">
@@ -46,3 +43,5 @@ export default @observer class ServiceDisabled extends Component {
46 ); 43 );
47 } 44 }
48} 45}
46
47export default injectIntl(ServiceDisabled);
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js
index 3fc084ff0..fa866e153 100644
--- a/src/components/services/content/ServiceView.js
+++ b/src/components/services/content/ServiceView.js
@@ -15,7 +15,9 @@ import SettingsStore from '../../../stores/SettingsStore';
15import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen'; 15import WebControlsScreen from '../../../features/webControls/containers/WebControlsScreen';
16import { CUSTOM_WEBSITE_RECIPE_ID } from '../../../config'; 16import { CUSTOM_WEBSITE_RECIPE_ID } from '../../../config';
17 17
18export default @inject('stores', 'actions') @observer class ServiceView extends Component { 18@inject('stores', 'actions')
19@observer
20class ServiceView extends Component {
19 static propTypes = { 21 static propTypes = {
20 service: PropTypes.instanceOf(ServiceModel).isRequired, 22 service: PropTypes.instanceOf(ServiceModel).isRequired,
21 setWebviewReference: PropTypes.func.isRequired, 23 setWebviewReference: PropTypes.func.isRequired,
@@ -63,7 +65,7 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
63 clearTimeout(this.hibernationTimer); 65 clearTimeout(this.hibernationTimer);
64 } 66 }
65 67
66 updateTargetUrl = (event) => { 68 updateTargetUrl = event => {
67 let visible = true; 69 let visible = true;
68 if (event.url === '' || event.url === '#') { 70 if (event.url === '' || event.url === '#') {
69 visible = false; 71 visible = false;
@@ -86,11 +88,12 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
86 isSpellcheckerEnabled, 88 isSpellcheckerEnabled,
87 } = this.props; 89 } = this.props;
88 90
89 const { 91 const { navigationBarBehaviour } = stores.settings.app;
90 navigationBarBehaviour,
91 } = stores.settings.app;
92 92
93 const showNavBar = navigationBarBehaviour === 'always' || (navigationBarBehaviour === 'custom' && service.recipe.id === CUSTOM_WEBSITE_RECIPE_ID); 93 const showNavBar =
94 navigationBarBehaviour === 'always' ||
95 (navigationBarBehaviour === 'custom' &&
96 service.recipe.id === CUSTOM_WEBSITE_RECIPE_ID);
94 97
95 const webviewClasses = classnames({ 98 const webviewClasses = classnames({
96 services__webview: true, 99 services__webview: true,
@@ -101,9 +104,7 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
101 104
102 let statusBar = null; 105 let statusBar = null;
103 if (this.state.statusBarVisible) { 106 if (this.state.statusBarVisible) {
104 statusBar = ( 107 statusBar = <StatusBarTargetUrl text={this.state.targetUrl} />;
105 <StatusBarTargetUrl text={this.state.targetUrl} />
106 );
107 } 108 }
108 109
109 return ( 110 return (
@@ -117,11 +118,11 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
117 reload={reload} 118 reload={reload}
118 /> 119 />
119 )} 120 )}
120 {service.isEnabled && service.isLoading && service.isFirstLoad && !service.isServiceAccessRestricted && ( 121 {service.isEnabled &&
121 <WebviewLoader 122 service.isLoading &&
122 loaded={false} 123 service.isFirstLoad &&
123 name={service.name} 124 !service.isServiceAccessRestricted && (
124 /> 125 <WebviewLoader loaded={false} name={service.name} />
125 )} 126 )}
126 {service.isError && ( 127 {service.isError && (
127 <WebviewErrorHandler 128 <WebviewErrorHandler
@@ -147,9 +148,7 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
147 <> 148 <>
148 {!service.isHibernating ? ( 149 {!service.isHibernating ? (
149 <> 150 <>
150 {showNavBar && ( 151 {showNavBar && <WebControlsScreen service={service} />}
151 <WebControlsScreen service={service} />
152 )}
153 <ServiceWebview 152 <ServiceWebview
154 service={service} 153 service={service}
155 setWebviewReference={setWebviewReference} 154 setWebviewReference={setWebviewReference}
@@ -159,9 +158,11 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
159 </> 158 </>
160 ) : ( 159 ) : (
161 <div> 160 <div>
162 <span role="img" aria-label="Sleeping Emoji">😴</span> 161 <span role="img" aria-label="Sleeping Emoji">
163 {' '} 162 😴
164 This service is currently hibernating. If this page doesn&#x27;t close soon, please try reloading Ferdi. 163 </span>{' '}
164 This service is currently hibernating. If this page doesn&#x27;t
165 close soon, please try reloading Ferdi.
165 </div> 166 </div>
166 )} 167 )}
167 </> 168 </>
@@ -171,3 +172,5 @@ export default @inject('stores', 'actions') @observer class ServiceView extends
171 ); 172 );
172 } 173 }
173} 174}
175
176export default ServiceView;
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
index bb93ff7d4..fb43fb816 100644
--- a/src/components/services/content/Services.js
+++ b/src/components/services/content/Services.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, inject } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes, inject } 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 Confetti from 'react-confetti'; 6import Confetti from 'react-confetti';
7import ms from 'ms'; 7import ms from 'ms';
8import injectSheet from 'react-jss'; 8import injectSheet from 'react-jss';
@@ -14,23 +14,24 @@ import serverlessLogin from '../../../helpers/serverless-helpers';
14const messages = defineMessages({ 14const messages = defineMessages({
15 welcome: { 15 welcome: {
16 id: 'services.welcome', 16 id: 'services.welcome',
17 defaultMessage: '!!!Welcome to Ferdi', 17 defaultMessage: 'Welcome to Ferdi',
18 }, 18 },
19 getStarted: { 19 getStarted: {
20 id: 'services.getStarted', 20 id: 'services.getStarted',
21 defaultMessage: '!!!Get started', 21 defaultMessage: 'Get started',
22 }, 22 },
23 login: { 23 login: {
24 id: 'services.login', 24 id: 'services.login',
25 defaultMessage: '!!!Please login to use Ferdi.', 25 defaultMessage: 'Please login to use Ferdi.',
26 }, 26 },
27 serverless: { 27 serverless: {
28 id: 'services.serverless', 28 id: 'services.serverless',
29 defaultMessage: '!!!Use Ferdi without an Account', 29 defaultMessage: 'Use Ferdi without an Account',
30 }, 30 },
31 serverInfo: { 31 serverInfo: {
32 id: 'services.serverInfo', 32 id: 'services.serverInfo',
33 defaultMessage: '!!!Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner. If you are switching over (from one of the hosted servers) to using Ferdi without an account, please be informed that you can export your data from that server and subsequently import it using the Help menu to resurrect all your workspaces and configured services!', 33 defaultMessage:
34 'Optionally, you can change your Ferdi server by clicking the cog in the bottom left corner. If you are switching over (from one of the hosted servers) to using Ferdi without an account, please be informed that you can export your data from that server and subsequently import it using the Help menu to resurrect all your workspaces and configured services!',
34 }, 35 },
35}); 36});
36 37
@@ -43,7 +44,10 @@ const styles = {
43 }, 44 },
44}; 45};
45 46
46export default @injectSheet(styles) @inject('actions') @observer class Services extends Component { 47@injectSheet(styles)
48@inject('actions')
49@observer
50class Services extends Component {
47 static propTypes = { 51 static propTypes = {
48 services: MobxPropTypes.arrayOrObservableArray, 52 services: MobxPropTypes.arrayOrObservableArray,
49 setWebviewReference: PropTypes.func.isRequired, 53 setWebviewReference: PropTypes.func.isRequired,
@@ -63,10 +67,6 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
63 services: [], 67 services: [],
64 }; 68 };
65 69
66 static contextTypes = {
67 intl: intlShape,
68 };
69
70 state = { 70 state = {
71 showConfetti: true, 71 showConfetti: true,
72 }; 72 };
@@ -112,11 +112,9 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
112 isSpellcheckerEnabled, 112 isSpellcheckerEnabled,
113 } = this.props; 113 } = this.props;
114 114
115 const { 115 const { showConfetti } = this.state;
116 showConfetti,
117 } = this.state;
118 116
119 const { intl } = this.context; 117 const { intl } = this.props;
120 const isLoggedIn = Boolean(localStorage.getItem('authToken')); 118 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
121 119
122 return ( 120 return (
@@ -131,25 +129,28 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
131 </div> 129 </div>
132 )} 130 )}
133 {services.length === 0 && ( 131 {services.length === 0 && (
134 <Appear 132 <Appear timeout={1500} transitionName="slideUp">
135 timeout={1500}
136 transitionName="slideUp"
137 >
138 <div className="services__no-service"> 133 <div className="services__no-service">
139 <img src="./assets/images/logo.svg" alt="Logo" style={{ maxHeight: '50vh' }} /> 134 <img
135 src="./assets/images/logo.svg"
136 alt="Logo"
137 style={{ maxHeight: '50vh' }}
138 />
140 <h1>{intl.formatMessage(messages.welcome)}</h1> 139 <h1>{intl.formatMessage(messages.welcome)}</h1>
141 { !isLoggedIn && ( 140 {!isLoggedIn && (
142 <> 141 <>
143 <p>{intl.formatMessage(messages.login)}</p> 142 <p>{intl.formatMessage(messages.login)}</p>
144 <p>{intl.formatMessage(messages.serverInfo)}</p> 143 <p>{intl.formatMessage(messages.serverInfo)}</p>
145 </> 144 </>
146 ) } 145 )}
147 <Appear 146 <Appear timeout={300} transitionName="slideUp">
148 timeout={300} 147 <Link
149 transitionName="slideUp" 148 to={isLoggedIn ? '/settings/recipes' : '/auth/welcome'}
150 > 149 className="button"
151 <Link to={isLoggedIn ? '/settings/recipes' : '/auth/welcome'} className="button"> 150 >
152 { isLoggedIn ? intl.formatMessage(messages.getStarted) : 'Login' } 151 {isLoggedIn
152 ? intl.formatMessage(messages.getStarted)
153 : 'Login'}
153 </Link> 154 </Link>
154 {!isLoggedIn && ( 155 {!isLoggedIn && (
155 <button 156 <button
@@ -167,27 +168,33 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
167 </div> 168 </div>
168 </Appear> 169 </Appear>
169 )} 170 )}
170 {services.filter((service) => !service.isTodosService).map((service) => ( 171 {services
171 <ServiceView 172 .filter(service => !service.isTodosService)
172 key={service.id} 173 .map(service => (
173 service={service} 174 <ServiceView
174 handleIPCMessage={handleIPCMessage} 175 key={service.id}
175 setWebviewReference={setWebviewReference} 176 service={service}
176 detachService={detachService} 177 handleIPCMessage={handleIPCMessage}
177 openWindow={openWindow} 178 setWebviewReference={setWebviewReference}
178 reload={() => reload({ serviceId: service.id })} 179 detachService={detachService}
179 edit={() => openSettings({ path: `services/edit/${service.id}` })} 180 openWindow={openWindow}
180 enable={() => update({ 181 reload={() => reload({ serviceId: service.id })}
181 serviceId: service.id, 182 edit={() => openSettings({ path: `services/edit/${service.id}` })}
182 serviceData: { 183 enable={() =>
183 isEnabled: true, 184 update({
184 }, 185 serviceId: service.id,
185 redirect: false, 186 serviceData: {
186 })} 187 isEnabled: true,
187 isSpellcheckerEnabled={isSpellcheckerEnabled} 188 },
188 /> 189 redirect: false,
189 ))} 190 })
191 }
192 isSpellcheckerEnabled={isSpellcheckerEnabled}
193 />
194 ))}
190 </div> 195 </div>
191 ); 196 );
192 } 197 }
193} 198}
199
200export default injectIntl(Services);
diff --git a/src/components/services/content/WebviewCrashHandler.js b/src/components/services/content/WebviewCrashHandler.js
index 10ff0bbbb..a332602be 100644
--- a/src/components/services/content/WebviewCrashHandler.js
+++ b/src/components/services/content/WebviewCrashHandler.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 ms from 'ms'; 5import ms from 'ms';
6 6
7import Button from '../../ui/Button'; 7import Button from '../../ui/Button';
@@ -9,35 +9,33 @@ import Button from '../../ui/Button';
9const messages = defineMessages({ 9const messages = defineMessages({
10 headline: { 10 headline: {
11 id: 'service.crashHandler.headline', 11 id: 'service.crashHandler.headline',
12 defaultMessage: '!!!Oh no!', 12 defaultMessage: 'Oh no!',
13 }, 13 },
14 text: { 14 text: {
15 id: 'service.crashHandler.text', 15 id: 'service.crashHandler.text',
16 defaultMessage: '!!!{name} has caused an error.', 16 defaultMessage: '{name} has caused an error.',
17 }, 17 },
18 action: { 18 action: {
19 id: 'service.crashHandler.action', 19 id: 'service.crashHandler.action',
20 defaultMessage: '!!!Reload {name}', 20 defaultMessage: 'Reload {name}',
21 }, 21 },
22 autoReload: { 22 autoReload: {
23 id: 'service.crashHandler.autoReload', 23 id: 'service.crashHandler.autoReload',
24 defaultMessage: '!!!Trying to automatically restore {name} in {seconds} seconds', 24 defaultMessage:
25 'Trying to automatically restore {name} in {seconds} seconds',
25 }, 26 },
26}); 27});
27 28
28export default @observer class WebviewCrashHandler extends Component { 29@observer
30class WebviewCrashHandler extends Component {
29 static propTypes = { 31 static propTypes = {
30 name: PropTypes.string.isRequired, 32 name: PropTypes.string.isRequired,
31 reload: PropTypes.func.isRequired, 33 reload: PropTypes.func.isRequired,
32 }; 34 };
33 35
34 static contextTypes = {
35 intl: intlShape,
36 };
37
38 state = { 36 state = {
39 countdown: ms('10s'), 37 countdown: ms('10s'),
40 } 38 };
41 39
42 countdownInterval = null; 40 countdownInterval = null;
43 41
@@ -47,7 +45,7 @@ export default @observer class WebviewCrashHandler extends Component {
47 const { reload } = this.props; 45 const { reload } = this.props;
48 46
49 this.countdownInterval = setInterval(() => { 47 this.countdownInterval = setInterval(() => {
50 this.setState((prevState) => ({ 48 this.setState(prevState => ({
51 countdown: prevState.countdown - this.countdownIntervalTimeout, 49 countdown: prevState.countdown - this.countdownIntervalTimeout,
52 })); 50 }));
53 51
@@ -60,7 +58,7 @@ export default @observer class WebviewCrashHandler extends Component {
60 58
61 render() { 59 render() {
62 const { name, reload } = this.props; 60 const { name, reload } = this.props;
63 const { intl } = this.context; 61 const { intl } = this.props;
64 62
65 return ( 63 return (
66 <div className="services__info-layer"> 64 <div className="services__info-layer">
@@ -82,3 +80,5 @@ export default @observer class WebviewCrashHandler extends Component {
82 ); 80 );
83 } 81 }
84} 82}
83
84export default injectIntl(WebviewCrashHandler);
diff --git a/src/components/services/tabs/TabItem.js b/src/components/services/tabs/TabItem.js
index e5892be5d..b1a3ffbbb 100644
--- a/src/components/services/tabs/TabItem.js
+++ b/src/components/services/tabs/TabItem.js
@@ -1,6 +1,6 @@
1import { Menu, dialog, app, getCurrentWindow } from '@electron/remote'; 1import { Menu, dialog, app, getCurrentWindow } from '@electron/remote';
2import React, { Component } from 'react'; 2import React, { Component } from 'react';
3import { defineMessages, intlShape } from 'react-intl'; 3import { defineMessages, injectIntl } from 'react-intl';
4import PropTypes from 'prop-types'; 4import PropTypes from 'prop-types';
5import { observer } from 'mobx-react'; 5import { observer } from 'mobx-react';
6import classnames from 'classnames'; 6import classnames from 'classnames';
@@ -20,56 +20,55 @@ const IS_SERVICE_DEBUGGING_ENABLED = (
20const messages = defineMessages({ 20const messages = defineMessages({
21 reload: { 21 reload: {
22 id: 'tabs.item.reload', 22 id: 'tabs.item.reload',
23 defaultMessage: '!!!Reload', 23 defaultMessage: 'Reload',
24 }, 24 },
25 disableNotifications: { 25 disableNotifications: {
26 id: 'tabs.item.disableNotifications', 26 id: 'tabs.item.disableNotifications',
27 defaultMessage: '!!!Disable notifications', 27 defaultMessage: 'Disable notifications',
28 }, 28 },
29 enableNotifications: { 29 enableNotifications: {
30 id: 'tabs.item.enableNotification', 30 id: 'tabs.item.enableNotification',
31 defaultMessage: '!!!Enable notifications', 31 defaultMessage: 'Enable notifications',
32 }, 32 },
33 disableAudio: { 33 disableAudio: {
34 id: 'tabs.item.disableAudio', 34 id: 'tabs.item.disableAudio',
35 defaultMessage: '!!!Disable audio', 35 defaultMessage: 'Disable audio',
36 }, 36 },
37 enableAudio: { 37 enableAudio: {
38 id: 'tabs.item.enableAudio', 38 id: 'tabs.item.enableAudio',
39 defaultMessage: '!!!Enable audio', 39 defaultMessage: 'Enable audio',
40 }, 40 },
41 enableDarkMode: { 41 enableDarkMode: {
42 id: 'tabs.item.enableDarkMode', 42 id: 'tabs.item.enableDarkMode',
43 defaultMessage: '!!!Enable Dark mode', 43 defaultMessage: 'Enable Dark mode',
44 }, 44 },
45 disableDarkMode: { 45 disableDarkMode: {
46 id: 'tabs.item.disableDarkMode', 46 id: 'tabs.item.disableDarkMode',
47 defaultMessage: '!!!Disable Dark mode', 47 defaultMessage: 'Disable Dark mode',
48 }, 48 },
49 disableService: { 49 disableService: {
50 id: 'tabs.item.disableService', 50 id: 'tabs.item.disableService',
51 defaultMessage: '!!!Disable Service', 51 defaultMessage: 'Disable Service',
52 }, 52 },
53 enableService: { 53 enableService: {
54 id: 'tabs.item.enableService', 54 id: 'tabs.item.enableService',
55 defaultMessage: '!!!Enable Service', 55 defaultMessage: 'Enable Service',
56 }, 56 },
57 hibernateService: { 57 hibernateService: {
58 id: 'tabs.item.hibernateService', 58 id: 'tabs.item.hibernateService',
59 defaultMessage: '!!!Hibernate Service', 59 defaultMessage: 'Hibernate Service',
60 }, 60 },
61 wakeUpService: { 61 wakeUpService: {
62 id: 'tabs.item.wakeUpService', 62 id: 'tabs.item.wakeUpService',
63 defaultMessage: '!!!Wake Up Service', 63 defaultMessage: 'Wake Up Service',
64 }, 64 },
65 deleteService: { 65 deleteService: {
66 id: 'tabs.item.deleteService', 66 id: 'tabs.item.deleteService',
67 defaultMessage: '!!!Delete Service', 67 defaultMessage: 'Delete Service',
68 }, 68 },
69 confirmDeleteService: { 69 confirmDeleteService: {
70 id: 'tabs.item.confirmDeleteService', 70 id: 'tabs.item.confirmDeleteService',
71 defaultMessage: 71 defaultMessage: 'Do you really want to delete the {serviceName} service?',
72 '!!!Do you really want to delete the {serviceName} service?',
73 }, 72 },
74}); 73});
75 74
@@ -134,10 +133,6 @@ class TabItem extends Component {
134 showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired, 133 showMessageBadgesEvenWhenMuted: PropTypes.bool.isRequired,
135 }; 134 };
136 135
137 static contextTypes = {
138 intl: intlShape,
139 };
140
141 @observable isPolled = false; 136 @observable isPolled = false;
142 137
143 @observable isPollAnswered = false; 138 @observable isPollAnswered = false;
@@ -185,7 +180,7 @@ class TabItem extends Component {
185 showMessageBadgeWhenMutedSetting, 180 showMessageBadgeWhenMutedSetting,
186 showMessageBadgesEvenWhenMuted, 181 showMessageBadgesEvenWhenMuted,
187 } = this.props; 182 } = this.props;
188 const { intl } = this.context; 183 const { intl } = this.props;
189 184
190 const menuTemplate = [ 185 const menuTemplate = [
191 { 186 {
@@ -256,7 +251,10 @@ class TabItem extends Component {
256 detail: intl.formatMessage(messages.confirmDeleteService, { 251 detail: intl.formatMessage(messages.confirmDeleteService, {
257 serviceName: service.name || service.recipe.name, 252 serviceName: service.name || service.recipe.name,
258 }), 253 }),
259 buttons: [intl.formatMessage(globalMessages.yes), intl.formatMessage(globalMessages.no)], 254 buttons: [
255 intl.formatMessage(globalMessages.yes),
256 intl.formatMessage(globalMessages.no),
257 ],
260 }); 258 });
261 if (selection === 0) { 259 if (selection === 0) {
262 deleteService(); 260 deleteService();
@@ -304,7 +302,9 @@ class TabItem extends Component {
304 onClick={clickHandler} 302 onClick={clickHandler}
305 onContextMenu={() => menu.popup(getCurrentWindow())} 303 onContextMenu={() => menu.popup(getCurrentWindow())}
306 data-tip={`${service.name} ${ 304 data-tip={`${service.name} ${
307 shortcutIndex <= 9 ? `(${cmdOrCtrlShortcutKey(false)}+${shortcutIndex})` : '' 305 shortcutIndex <= 9
306 ? `(${cmdOrCtrlShortcutKey(false)}+${shortcutIndex})`
307 : ''
308 }`} 308 }`}
309 > 309 >
310 <img src={service.icon} className="tab-item__icon" alt="" /> 310 <img src={service.icon} className="tab-item__icon" alt="" />
@@ -332,4 +332,4 @@ class TabItem extends Component {
332 } 332 }
333} 333}
334 334
335export default SortableElement(TabItem); 335export default injectIntl(SortableElement(TabItem));
diff --git a/src/components/services/tabs/Tabbar.js b/src/components/services/tabs/Tabbar.js
index c1421a2b1..a77799819 100644
--- a/src/components/services/tabs/Tabbar.js
+++ b/src/components/services/tabs/Tabbar.js
@@ -4,7 +4,8 @@ import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4 4
5import TabBarSortableList from './TabBarSortableList'; 5import TabBarSortableList from './TabBarSortableList';
6 6
7export default @observer class TabBar extends Component { 7@observer
8class TabBar extends Component {
8 static propTypes = { 9 static propTypes = {
9 services: MobxPropTypes.arrayOrObservableArray.isRequired, 10 services: MobxPropTypes.arrayOrObservableArray.isRequired,
10 setActive: PropTypes.func.isRequired, 11 setActive: PropTypes.func.isRequired,
@@ -26,16 +27,13 @@ export default @observer class TabBar extends Component {
26 }; 27 };
27 28
28 onSortEnd = ({ oldIndex, newIndex }) => { 29 onSortEnd = ({ oldIndex, newIndex }) => {
29 const { 30 const { enableToolTip, reorder } = this.props;
30 enableToolTip,
31 reorder,
32 } = this.props;
33 31
34 enableToolTip(); 32 enableToolTip();
35 reorder({ oldIndex, newIndex }); 33 reorder({ oldIndex, newIndex });
36 }; 34 };
37 35
38 shouldPreventSorting = (event) => event.target.tagName !== 'LI'; 36 shouldPreventSorting = event => event.target.tagName !== 'LI';
39 37
40 toggleService = ({ serviceId, isEnabled }) => { 38 toggleService = ({ serviceId, isEnabled }) => {
41 const { updateService } = this.props; 39 const { updateService } = this.props;
@@ -102,10 +100,10 @@ export default @observer class TabBar extends Component {
102 toggleAudio={toggleAudio} 100 toggleAudio={toggleAudio}
103 toggleDarkMode={toggleDarkMode} 101 toggleDarkMode={toggleDarkMode}
104 deleteService={deleteService} 102 deleteService={deleteService}
105 disableService={(args) => this.disableService(args)} 103 disableService={args => this.disableService(args)}
106 enableService={(args) => this.enableService(args)} 104 enableService={args => this.enableService(args)}
107 hibernateService={(args) => this.hibernateService(args)} 105 hibernateService={args => this.hibernateService(args)}
108 wakeUpService={(args) => this.wakeUpService(args)} 106 wakeUpService={args => this.wakeUpService(args)}
109 openSettings={openSettings} 107 openSettings={openSettings}
110 distance={20} 108 distance={20}
111 axis={axis} 109 axis={axis}
@@ -118,3 +116,5 @@ export default @observer class TabBar extends Component {
118 ); 116 );
119 } 117 }
120} 118}
119
120export default TabBar;
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);
diff --git a/src/components/ui/AppLoader/index.js b/src/components/ui/AppLoader/index.js
index bbfd5de28..fa4a719ab 100644
--- a/src/components/ui/AppLoader/index.js
+++ b/src/components/ui/AppLoader/index.js
@@ -19,7 +19,9 @@ const textList = shuffleArray([
19 'Fixing bugs', 19 'Fixing bugs',
20]); 20]);
21 21
22export default @injectSheet(styles) @withTheme class AppLoader extends Component { 22@injectSheet(styles)
23@withTheme
24class AppLoader extends Component {
23 static propTypes = { 25 static propTypes = {
24 classes: PropTypes.object.isRequired, 26 classes: PropTypes.object.isRequired,
25 theme: PropTypes.object.isRequired, 27 theme: PropTypes.object.isRequired,
@@ -28,7 +30,7 @@ export default @injectSheet(styles) @withTheme class AppLoader extends Component
28 30
29 static defaultProps = { 31 static defaultProps = {
30 texts: textList, 32 texts: textList,
31 } 33 };
32 34
33 state = { 35 state = {
34 step: 0, 36 step: 0,
@@ -38,7 +40,7 @@ export default @injectSheet(styles) @withTheme class AppLoader extends Component
38 40
39 componentDidMount() { 41 componentDidMount() {
40 this.interval = setInterval(() => { 42 this.interval = setInterval(() => {
41 this.setState((prevState) => ({ 43 this.setState(prevState => ({
42 step: prevState.step === textList.length - 1 ? 0 : prevState.step + 1, 44 step: prevState.step === textList.length - 1 ? 0 : prevState.step + 1,
43 })); 45 }));
44 }, 2500); 46 }, 2500);
@@ -73,3 +75,5 @@ export default @injectSheet(styles) @withTheme class AppLoader extends Component
73 ); 75 );
74 } 76 }
75} 77}
78
79export default AppLoader;
diff --git a/src/components/ui/Button.js b/src/components/ui/Button.js
index 5066b9c06..f6c9fd3d3 100644
--- a/src/components/ui/Button.js
+++ b/src/components/ui/Button.js
@@ -4,7 +4,9 @@ import { observer, inject } from 'mobx-react';
4import Loader from 'react-loader'; 4import Loader from 'react-loader';
5import classnames from 'classnames'; 5import classnames from 'classnames';
6 6
7export default @inject('stores') @observer class Button extends Component { 7@inject('stores')
8@observer
9class Button extends Component {
8 static propTypes = { 10 static propTypes = {
9 className: PropTypes.string, 11 className: PropTypes.string,
10 label: PropTypes.string.isRequired, 12 label: PropTypes.string.isRequired,
@@ -26,7 +28,7 @@ export default @inject('stores') @observer class Button extends Component {
26 static defaultProps = { 28 static defaultProps = {
27 className: null, 29 className: null,
28 disabled: false, 30 disabled: false,
29 onClick: () => { }, 31 onClick: () => {},
30 type: 'button', 32 type: 'button',
31 buttonType: '', 33 buttonType: '',
32 loaded: true, 34 loaded: true,
@@ -76,7 +78,11 @@ export default @inject('stores') @observer class Button extends Component {
76 loaded={loaded} 78 loaded={loaded}
77 lines={10} 79 lines={10}
78 scale={0.4} 80 scale={0.4}
79 color={buttonType !== 'secondary' ? '#FFF' : this.props.stores.settings.app.accentColor} 81 color={
82 buttonType !== 'secondary'
83 ? '#FFF'
84 : this.props.stores.settings.app.accentColor
85 }
80 component="span" 86 component="span"
81 /> 87 />
82 {label} 88 {label}
@@ -85,3 +91,5 @@ export default @inject('stores') @observer class Button extends Component {
85 ); 91 );
86 } 92 }
87} 93}
94
95export default Button;
diff --git a/src/components/ui/FAB.js b/src/components/ui/FAB.js
index 633edbe2c..a3aa06bc9 100644
--- a/src/components/ui/FAB.js
+++ b/src/components/ui/FAB.js
@@ -8,7 +8,8 @@ import classnames from 'classnames';
8 8
9import { oneOrManyChildElements } from '../../prop-types'; 9import { oneOrManyChildElements } from '../../prop-types';
10 10
11export default @observer class Button extends Component { 11@observer
12class Button extends Component {
12 static propTypes = { 13 static propTypes = {
13 className: PropTypes.string, 14 className: PropTypes.string,
14 disabled: PropTypes.bool, 15 disabled: PropTypes.bool,
@@ -21,7 +22,7 @@ export default @observer class Button extends Component {
21 static defaultProps = { 22 static defaultProps = {
22 className: null, 23 className: null,
23 disabled: false, 24 disabled: false,
24 onClick: () => { }, 25 onClick: () => {},
25 type: 'button', 26 type: 'button',
26 htmlForm: '', 27 htmlForm: '',
27 }; 28 };
@@ -29,14 +30,8 @@ export default @observer class Button extends Component {
29 element = null; 30 element = null;
30 31
31 render() { 32 render() {
32 const { 33 const { className, disabled, onClick, type, children, htmlForm } =
33 className, 34 this.props;
34 disabled,
35 onClick,
36 type,
37 children,
38 htmlForm,
39 } = this.props;
40 35
41 const buttonProps = { 36 const buttonProps = {
42 className: classnames({ 37 className: classnames({
@@ -66,3 +61,5 @@ export default @observer class Button extends Component {
66 ); 61 );
67 } 62 }
68} 63}
64
65export default Button;
diff --git a/src/components/ui/FeatureList.js b/src/components/ui/FeatureList.js
index cf2664830..14e7ec3c4 100644
--- a/src/components/ui/FeatureList.js
+++ b/src/components/ui/FeatureList.js
@@ -1,53 +1,53 @@
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';
4 4
5import { FeatureItem } from './FeatureItem'; 5import { FeatureItem } from './FeatureItem';
6 6
7const messages = defineMessages({ 7const messages = defineMessages({
8 availableRecipes: { 8 availableRecipes: {
9 id: 'pricing.features.recipes', 9 id: 'pricing.features.recipes',
10 defaultMessage: '!!!Choose from more than 70 Services', // TODO: Make this dynamic 10 defaultMessage: 'Choose from more than 70 Services', // TODO: Make this dynamic
11 }, 11 },
12 accountSync: { 12 accountSync: {
13 id: 'pricing.features.accountSync', 13 id: 'pricing.features.accountSync',
14 defaultMessage: '!!!Account Synchronisation', 14 defaultMessage: 'Account Synchronisation',
15 }, 15 },
16 desktopNotifications: { 16 desktopNotifications: {
17 id: 'pricing.features.desktopNotifications', 17 id: 'pricing.features.desktopNotifications',
18 defaultMessage: '!!!Desktop Notifications', 18 defaultMessage: 'Desktop Notifications',
19 }, 19 },
20 unlimitedServices: { 20 unlimitedServices: {
21 id: 'pricing.features.unlimitedServices', 21 id: 'pricing.features.unlimitedServices',
22 defaultMessage: '!!!Add unlimited services', 22 defaultMessage: 'Add unlimited services',
23 }, 23 },
24 spellchecker: { 24 spellchecker: {
25 id: 'pricing.features.spellchecker', 25 id: 'pricing.features.spellchecker',
26 defaultMessage: '!!!Spellchecker support', 26 defaultMessage: 'Spellchecker support',
27 }, 27 },
28 workspaces: { 28 workspaces: {
29 id: 'pricing.features.workspaces', 29 id: 'pricing.features.workspaces',
30 defaultMessage: '!!!Workspaces', 30 defaultMessage: 'Workspaces',
31 }, 31 },
32 customWebsites: { 32 customWebsites: {
33 id: 'pricing.features.customWebsites', 33 id: 'pricing.features.customWebsites',
34 defaultMessage: '!!!Add Custom Websites', 34 defaultMessage: 'Add Custom Websites',
35 }, 35 },
36 onPremise: { 36 onPremise: {
37 id: 'pricing.features.onPremise', 37 id: 'pricing.features.onPremise',
38 defaultMessage: '!!!On-premise & other Hosted Services', 38 defaultMessage: 'On-premise & other Hosted Services',
39 }, 39 },
40 thirdPartyServices: { 40 thirdPartyServices: {
41 id: 'pricing.features.thirdPartyServices', 41 id: 'pricing.features.thirdPartyServices',
42 defaultMessage: '!!!Install 3rd party services', 42 defaultMessage: 'Install 3rd party services',
43 }, 43 },
44 serviceProxies: { 44 serviceProxies: {
45 id: 'pricing.features.serviceProxies', 45 id: 'pricing.features.serviceProxies',
46 defaultMessage: '!!!Service Proxies', 46 defaultMessage: 'Service Proxies',
47 }, 47 },
48 teamManagement: { 48 teamManagement: {
49 id: 'pricing.features.teamManagement', 49 id: 'pricing.features.teamManagement',
50 defaultMessage: '!!!Team Management', 50 defaultMessage: 'Team Management',
51 }, 51 },
52}); 52});
53 53
@@ -60,18 +60,11 @@ export class FeatureList extends Component {
60 static defaultProps = { 60 static defaultProps = {
61 className: '', 61 className: '',
62 featureClassName: '', 62 featureClassName: '',
63 }
64
65 static contextTypes = {
66 intl: intlShape,
67 }; 63 };
68 64
69 render() { 65 render() {
70 const { 66 const { className, featureClassName } = this.props;
71 className, 67 const { intl } = this.props;
72 featureClassName,
73 } = this.props;
74 const { intl } = this.context;
75 68
76 const features = [ 69 const features = [
77 messages.availableRecipes, 70 messages.availableRecipes,
@@ -92,10 +85,15 @@ export class FeatureList extends Component {
92 85
93 return ( 86 return (
94 <ul className={className}> 87 <ul className={className}>
95 {features.map((feature) => <FeatureItem name={intl.formatMessage(feature)} className={featureClassName} />)} 88 {features.map(feature => (
89 <FeatureItem
90 name={intl.formatMessage(feature)}
91 className={featureClassName}
92 />
93 ))}
96 </ul> 94 </ul>
97 ); 95 );
98 } 96 }
99} 97}
100 98
101export default FeatureList; 99export default injectIntl(FeatureList);
diff --git a/src/components/ui/FullscreenLoader/index.js b/src/components/ui/FullscreenLoader/index.js
index 2952cd96b..ab5e2f365 100644
--- a/src/components/ui/FullscreenLoader/index.js
+++ b/src/components/ui/FullscreenLoader/index.js
@@ -8,7 +8,10 @@ import Loader from '../Loader';
8 8
9import styles from './styles'; 9import styles from './styles';
10 10
11export default @withTheme @injectSheet(styles) @observer class FullscreenLoader extends Component { 11@withTheme
12@injectSheet(styles)
13@observer
14class FullscreenLoader extends Component {
12 static propTypes = { 15 static propTypes = {
13 className: PropTypes.string, 16 className: PropTypes.string,
14 title: PropTypes.string.isRequired, 17 title: PropTypes.string.isRequired,
@@ -25,14 +28,8 @@ export default @withTheme @injectSheet(styles) @observer class FullscreenLoader
25 }; 28 };
26 29
27 render() { 30 render() {
28 const { 31 const { classes, title, children, spinnerColor, className, theme } =
29 classes, 32 this.props;
30 title,
31 children,
32 spinnerColor,
33 className,
34 theme,
35 } = this.props;
36 33
37 return ( 34 return (
38 <div className={classes.wrapper}> 35 <div className={classes.wrapper}>
@@ -44,13 +41,11 @@ export default @withTheme @injectSheet(styles) @observer class FullscreenLoader
44 > 41 >
45 <h1 className={classes.title}>{title}</h1> 42 <h1 className={classes.title}>{title}</h1>
46 <Loader color={spinnerColor || theme.colorFullscreenLoaderSpinner} /> 43 <Loader color={spinnerColor || theme.colorFullscreenLoaderSpinner} />
47 {children && ( 44 {children && <div className={classes.content}>{children}</div>}
48 <div className={classes.content}>
49 {children}
50 </div>
51 )}
52 </div> 45 </div>
53 </div> 46 </div>
54 ); 47 );
55 } 48 }
56} 49}
50
51export default FullscreenLoader;
diff --git a/src/components/ui/ImageUpload.js b/src/components/ui/ImageUpload.js
index bb4ea0565..8ea31ca40 100644
--- a/src/components/ui/ImageUpload.js
+++ b/src/components/ui/ImageUpload.js
@@ -6,7 +6,8 @@ import classnames from 'classnames';
6import Dropzone from 'react-dropzone'; 6import Dropzone from 'react-dropzone';
7import { isWindows } from '../../environment'; 7import { isWindows } from '../../environment';
8 8
9export default @observer class ImageUpload extends Component { 9@observer
10class ImageUpload extends Component {
10 static propTypes = { 11 static propTypes = {
11 field: PropTypes.instanceOf(Field).isRequired, 12 field: PropTypes.instanceOf(Field).isRequired,
12 className: PropTypes.string, 13 className: PropTypes.string,
@@ -29,7 +30,7 @@ export default @observer class ImageUpload extends Component {
29 onDrop(acceptedFiles) { 30 onDrop(acceptedFiles) {
30 const { field } = this.props; 31 const { field } = this.props;
31 32
32 acceptedFiles.forEach((file) => { 33 acceptedFiles.forEach(file => {
33 const imgPath = isWindows ? file.path.replace(/\\/g, '/') : file.path; 34 const imgPath = isWindows ? file.path.replace(/\\/g, '/') : file.path;
34 this.setState({ 35 this.setState({
35 path: imgPath, 36 path: imgPath,
@@ -42,9 +43,7 @@ export default @observer class ImageUpload extends Component {
42 } 43 }
43 44
44 render() { 45 render() {
45 const { 46 const { field, className, multiple, textDelete, textUpload } = this.props;
46 field, className, multiple, textDelete, textUpload,
47 } = this.props;
48 47
49 const cssClasses = classnames({ 48 const cssClasses = classnames({
50 'image-upload__dropzone': true, 49 'image-upload__dropzone': true,
@@ -86,7 +85,7 @@ export default @observer class ImageUpload extends Component {
86 </> 85 </>
87 ) : ( 86 ) : (
88 <Dropzone 87 <Dropzone
89 ref={(node) => { 88 ref={node => {
90 this.dropzoneRef = node; 89 this.dropzoneRef = node;
91 }} 90 }}
92 onDrop={this.onDrop.bind(this)} 91 onDrop={this.onDrop.bind(this)}
@@ -107,3 +106,5 @@ export default @observer class ImageUpload extends Component {
107 ); 106 );
108 } 107 }
109} 108}
109
110export default ImageUpload;
diff --git a/src/components/ui/InfoBar.js b/src/components/ui/InfoBar.js
index bd2af2296..f5cbad48b 100644
--- a/src/components/ui/InfoBar.js
+++ b/src/components/ui/InfoBar.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import classnames from 'classnames'; 4import classnames from 'classnames';
5import Loader from 'react-loader'; 5import Loader from 'react-loader';
6import { defineMessages, intlShape } from 'react-intl'; 6import { defineMessages, injectIntl } from 'react-intl';
7 7
8// import { oneOrManyChildElements } from '../../prop-types'; 8// import { oneOrManyChildElements } from '../../prop-types';
9import Appear from './effects/Appear'; 9import Appear from './effects/Appear';
@@ -11,11 +11,10 @@ import Appear from './effects/Appear';
11const messages = defineMessages({ 11const messages = defineMessages({
12 hide: { 12 hide: {
13 id: 'infobar.hide', 13 id: 'infobar.hide',
14 defaultMessage: '!!!Hide', 14 defaultMessage: 'Hide',
15 }, 15 },
16}); 16});
17 17
18export default
19@observer 18@observer
20class InfoBar extends Component { 19class InfoBar extends Component {
21 static propTypes = { 20 static propTypes = {
@@ -42,10 +41,6 @@ class InfoBar extends Component {
42 onHide: () => null, 41 onHide: () => null,
43 }; 42 };
44 43
45 static contextTypes = {
46 intl: intlShape,
47 };
48
49 render() { 44 render() {
50 const { 45 const {
51 children, 46 children,
@@ -59,7 +54,7 @@ class InfoBar extends Component {
59 onHide, 54 onHide,
60 } = this.props; 55 } = this.props;
61 56
62 const { intl } = this.context; 57 const { intl } = this.props;
63 58
64 let transitionName = 'slideUp'; 59 let transitionName = 'slideUp';
65 if (position === 'top') { 60 if (position === 'top') {
@@ -103,3 +98,5 @@ class InfoBar extends Component {
103 ); 98 );
104 } 99 }
105} 100}
101
102export default injectIntl(InfoBar);
diff --git a/src/components/ui/Infobox.js b/src/components/ui/Infobox.js
index 73b48b957..13ae2303b 100644
--- a/src/components/ui/Infobox.js
+++ b/src/components/ui/Infobox.js
@@ -3,16 +3,15 @@ import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import classnames from 'classnames'; 4import classnames from 'classnames';
5import Loader from 'react-loader'; 5import Loader from 'react-loader';
6import { defineMessages, intlShape } from 'react-intl'; 6import { defineMessages, injectIntl } from 'react-intl';
7 7
8const messages = defineMessages({ 8const messages = defineMessages({
9 dismiss: { 9 dismiss: {
10 id: 'infobox.dismiss', 10 id: 'infobox.dismiss',
11 defaultMessage: '!!!Dismiss', 11 defaultMessage: 'Dismiss',
12 }, 12 },
13}); 13});
14 14
15export default
16@observer 15@observer
17class Infobox extends Component { 16class Infobox extends Component {
18 static propTypes = { 17 static propTypes = {
@@ -38,10 +37,6 @@ class Infobox extends Component {
38 onSeen: () => null, 37 onSeen: () => null,
39 }; 38 };
40 39
41 static contextTypes = {
42 intl: intlShape,
43 };
44
45 state = { 40 state = {
46 dismissed: false, 41 dismissed: false,
47 }; 42 };
@@ -63,7 +58,7 @@ class Infobox extends Component {
63 onDismiss, 58 onDismiss,
64 } = this.props; 59 } = this.props;
65 60
66 const { intl } = this.context; 61 const { intl } = this.props;
67 62
68 if (this.state.dismissed) { 63 if (this.state.dismissed) {
69 return null; 64 return null;
@@ -106,3 +101,5 @@ class Infobox extends Component {
106 ); 101 );
107 } 102 }
108} 103}
104
105export default injectIntl(Infobox);
diff --git a/src/components/ui/Input.js b/src/components/ui/Input.js
index 7417fef1c..335367f03 100644
--- a/src/components/ui/Input.js
+++ b/src/components/ui/Input.js
@@ -3,18 +3,17 @@ import PropTypes from 'prop-types';
3import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import { Field } from 'mobx-react-form'; 4import { Field } from 'mobx-react-form';
5import classnames from 'classnames'; 5import classnames from 'classnames';
6import { defineMessages, intlShape } from 'react-intl'; 6import { defineMessages, injectIntl } from 'react-intl';
7 7
8import { scorePassword as scorePasswordFunc } from '../../helpers/password-helpers'; 8import { scorePassword as scorePasswordFunc } from '../../helpers/password-helpers';
9 9
10const messages = defineMessages({ 10const messages = defineMessages({
11 passwordToggle: { 11 passwordToggle: {
12 id: 'settings.app.form.passwordToggle', 12 id: 'settings.app.form.passwordToggle',
13 defaultMessage: '!!!Password toggle', 13 defaultMessage: 'Password toggle',
14 }, 14 },
15}); 15});
16 16
17export default
18@observer 17@observer
19class Input extends Component { 18class Input extends Component {
20 static propTypes = { 19 static propTypes = {
@@ -38,10 +37,6 @@ class Input extends Component {
38 suffix: '', 37 suffix: '',
39 }; 38 };
40 39
41 static contextTypes = {
42 intl: intlShape,
43 };
44
45 state = { 40 state = {
46 showPassword: false, 41 showPassword: false,
47 passwordScore: 0, 42 passwordScore: 0,
@@ -82,7 +77,7 @@ class Input extends Component {
82 77
83 const { passwordScore } = this.state; 78 const { passwordScore } = this.state;
84 79
85 const { intl } = this.context; 80 const { intl } = this.props;
86 81
87 let { type } = field; 82 let { type } = field;
88 if (type === 'password' && this.state.showPassword) { 83 if (type === 'password' && this.state.showPassword) {
@@ -106,10 +101,10 @@ class Input extends Component {
106 name={field.name} 101 name={field.name}
107 value={field.value} 102 value={field.value}
108 placeholder={field.placeholder} 103 placeholder={field.placeholder}
109 onChange={(e) => this.onChange(e)} 104 onChange={e => this.onChange(e)}
110 onBlur={field.onBlur} 105 onBlur={field.onBlur}
111 onFocus={field.onFocus} 106 onFocus={field.onFocus}
112 ref={(element) => { 107 ref={element => {
113 this.inputElement = element; 108 this.inputElement = element;
114 }} 109 }}
115 disabled={field.disabled} 110 disabled={field.disabled}
@@ -124,9 +119,11 @@ class Input extends Component {
124 'mdi-eye': !this.state.showPassword, 119 'mdi-eye': !this.state.showPassword,
125 'mdi-eye-off': this.state.showPassword, 120 'mdi-eye-off': this.state.showPassword,
126 })} 121 })}
127 onClick={() => this.setState((prevState) => ({ 122 onClick={() =>
128 showPassword: !prevState.showPassword, 123 this.setState(prevState => ({
129 }))} 124 showPassword: !prevState.showPassword,
125 }))
126 }
130 tabIndex={-1} 127 tabIndex={-1}
131 aria-label={intl.formatMessage(messages.passwordToggle)} 128 aria-label={intl.formatMessage(messages.passwordToggle)}
132 /> 129 />
@@ -154,3 +151,5 @@ class Input extends Component {
154 ); 151 );
155 } 152 }
156} 153}
154
155export default injectIntl(Input);
diff --git a/src/components/ui/Link.js b/src/components/ui/Link.js
index 003211e5c..94db3f842 100644
--- a/src/components/ui/Link.js
+++ b/src/components/ui/Link.js
@@ -9,7 +9,9 @@ import { matchRoute } from '../../helpers/routing-helpers';
9import { openExternalUrl } from '../../helpers/url-helpers'; 9import { openExternalUrl } from '../../helpers/url-helpers';
10 10
11// TODO: create container component for this component 11// TODO: create container component for this component
12export default @inject('stores') @observer class Link extends Component { 12@inject('stores')
13@observer
14class Link extends Component {
13 onClick(e) { 15 onClick(e) {
14 if (this.props.disabled) { 16 if (this.props.disabled) {
15 e.preventDefault(); 17 e.preventDefault();
@@ -50,7 +52,7 @@ export default @inject('stores') @observer class Link extends Component {
50 href={router.history.createHref(to)} 52 href={router.history.createHref(to)}
51 className={linkClasses} 53 className={linkClasses}
52 style={style} 54 style={style}
53 onClick={(e) => this.onClick(e)} 55 onClick={e => this.onClick(e)}
54 > 56 >
55 {children} 57 {children}
56 </a> 58 </a>
@@ -62,10 +64,8 @@ Link.wrappedComponent.propTypes = {
62 stores: PropTypes.shape({ 64 stores: PropTypes.shape({
63 router: PropTypes.instanceOf(RouterStore).isRequired, 65 router: PropTypes.instanceOf(RouterStore).isRequired,
64 }).isRequired, 66 }).isRequired,
65 children: PropTypes.oneOfType([ 67 children: PropTypes.oneOfType([oneOrManyChildElements, PropTypes.string])
66 oneOrManyChildElements, 68 .isRequired,
67 PropTypes.string,
68 ]).isRequired,
69 to: PropTypes.string.isRequired, 69 to: PropTypes.string.isRequired,
70 className: PropTypes.string, 70 className: PropTypes.string,
71 activeClassName: PropTypes.string, 71 activeClassName: PropTypes.string,
@@ -83,3 +83,5 @@ Link.wrappedComponent.defaultProps = {
83 target: '', 83 target: '',
84 style: {}, 84 style: {},
85}; 85};
86
87export default Link;
diff --git a/src/components/ui/Loader.js b/src/components/ui/Loader.js
index 4d7113aa1..46c1390bf 100644
--- a/src/components/ui/Loader.js
+++ b/src/components/ui/Loader.js
@@ -5,7 +5,9 @@ import Loader from 'react-loader';
5 5
6import { oneOrManyChildElements } from '../../prop-types'; 6import { oneOrManyChildElements } from '../../prop-types';
7 7
8export default @inject('stores') @observer class LoaderComponent extends Component { 8@inject('stores')
9@observer
10class LoaderComponent extends Component {
9 static propTypes = { 11 static propTypes = {
10 children: oneOrManyChildElements, 12 children: oneOrManyChildElements,
11 loaded: PropTypes.bool, 13 loaded: PropTypes.bool,
@@ -28,13 +30,12 @@ export default @inject('stores') @observer class LoaderComponent extends Compone
28 }; 30 };
29 31
30 render() { 32 render() {
31 const { 33 const { children, loaded, className } = this.props;
32 children,
33 loaded,
34 className,
35 } = this.props;
36 34
37 const color = this.props.color !== 'ACCENT' ? this.props.color : this.props.stores.settings.app.accentColor; 35 const color =
36 this.props.color !== 'ACCENT'
37 ? this.props.color
38 : this.props.stores.settings.app.accentColor;
38 39
39 return ( 40 return (
40 <Loader 41 <Loader
@@ -51,3 +52,5 @@ export default @inject('stores') @observer class LoaderComponent extends Compone
51 ); 52 );
52 } 53 }
53} 54}
55
56export default LoaderComponent;
diff --git a/src/components/ui/Modal/index.js b/src/components/ui/Modal/index.js
index a9fa0cd1b..9e6830b0c 100644
--- a/src/components/ui/Modal/index.js
+++ b/src/components/ui/Modal/index.js
@@ -10,7 +10,8 @@ import styles from './styles';
10 10
11// ReactModal.setAppElement('#root'); 11// ReactModal.setAppElement('#root');
12 12
13export default @injectCSS(styles) class Modal extends Component { 13@injectCSS(styles)
14class Modal extends Component {
14 static propTypes = { 15 static propTypes = {
15 children: PropTypes.node.isRequired, 16 children: PropTypes.node.isRequired,
16 className: PropTypes.string, 17 className: PropTypes.string,
@@ -20,14 +21,14 @@ export default @injectCSS(styles) class Modal extends Component {
20 close: PropTypes.func.isRequired, 21 close: PropTypes.func.isRequired,
21 shouldCloseOnOverlayClick: PropTypes.bool, 22 shouldCloseOnOverlayClick: PropTypes.bool,
22 showClose: PropTypes.bool, 23 showClose: PropTypes.bool,
23 } 24 };
24 25
25 static defaultProps = { 26 static defaultProps = {
26 className: null, 27 className: null,
27 portal: 'modal-portal', 28 portal: 'modal-portal',
28 shouldCloseOnOverlayClick: false, 29 shouldCloseOnOverlayClick: false,
29 showClose: true, 30 showClose: true,
30 } 31 };
31 32
32 render() { 33 render() {
33 const { 34 const {
@@ -56,18 +57,14 @@ export default @injectCSS(styles) class Modal extends Component {
56 appElement={document.getElementById('root')} 57 appElement={document.getElementById('root')}
57 > 58 >
58 {showClose && close && ( 59 {showClose && close && (
59 <button 60 <button type="button" className={classes.close} onClick={close}>
60 type="button"
61 className={classes.close}
62 onClick={close}
63 >
64 <Icon icon={mdiClose} size={1.5} /> 61 <Icon icon={mdiClose} size={1.5} />
65 </button> 62 </button>
66 )} 63 )}
67 <div className={classes.content}> 64 <div className={classes.content}>{children}</div>
68 {children}
69 </div>
70 </ReactModal> 65 </ReactModal>
71 ); 66 );
72 } 67 }
73} 68}
69
70export default Modal;
diff --git a/src/components/ui/Radio.js b/src/components/ui/Radio.js
index e77714eb7..65a777ff1 100644
--- a/src/components/ui/Radio.js
+++ b/src/components/ui/Radio.js
@@ -4,7 +4,8 @@ import { observer } from 'mobx-react';
4import { Field } from 'mobx-react-form'; 4import { Field } from 'mobx-react-form';
5import classnames from 'classnames'; 5import classnames from 'classnames';
6 6
7export default @observer class Radio extends Component { 7@observer
8class Radio extends Component {
8 static propTypes = { 9 static propTypes = {
9 field: PropTypes.instanceOf(Field).isRequired, 10 field: PropTypes.instanceOf(Field).isRequired,
10 className: PropTypes.string, 11 className: PropTypes.string,
@@ -31,11 +32,7 @@ export default @observer class Radio extends Component {
31 } 32 }
32 33
33 render() { 34 render() {
34 const { 35 const { field, className, showLabel } = this.props;
35 field,
36 className,
37 showLabel,
38 } = this.props;
39 36
40 return ( 37 return (
41 <div 38 <div
@@ -46,15 +43,12 @@ export default @observer class Radio extends Component {
46 })} 43 })}
47 > 44 >
48 {field.label && showLabel && ( 45 {field.label && showLabel && (
49 <label 46 <label className="franz-form__label" htmlFor={field.name}>
50 className="franz-form__label"
51 htmlFor={field.name}
52 >
53 {field.label} 47 {field.label}
54 </label> 48 </label>
55 )} 49 )}
56 <div className="franz-form__radio-wrapper"> 50 <div className="franz-form__radio-wrapper">
57 {field.options.map((type) => ( 51 {field.options.map(type => (
58 <label 52 <label
59 key={type.value} 53 key={type.value}
60 htmlFor={`${field.id}-${type.value}`} 54 htmlFor={`${field.id}-${type.value}`}
@@ -75,14 +69,10 @@ export default @observer class Radio extends Component {
75 </label> 69 </label>
76 ))} 70 ))}
77 </div> 71 </div>
78 {field.error && ( 72 {field.error && <div className="franz-form__error">{field.error}</div>}
79 <div
80 className="franz-form__error"
81 >
82 {field.error}
83 </div>
84 )}
85 </div> 73 </div>
86 ); 74 );
87 } 75 }
88} 76}
77
78export default Radio;
diff --git a/src/components/ui/SearchInput.js b/src/components/ui/SearchInput.js
index 0b25734dd..2d760beab 100644
--- a/src/components/ui/SearchInput.js
+++ b/src/components/ui/SearchInput.js
@@ -4,7 +4,8 @@ import { observer } from 'mobx-react';
4import classnames from 'classnames'; 4import classnames from 'classnames';
5import { debounce } from 'lodash'; 5import { debounce } from 'lodash';
6 6
7export default @observer class SearchInput extends Component { 7@observer
8class SearchInput extends Component {
8 static propTypes = { 9 static propTypes = {
9 value: PropTypes.string, 10 value: PropTypes.string,
10 placeholder: PropTypes.string, 11 placeholder: PropTypes.string,
@@ -27,7 +28,7 @@ export default @observer class SearchInput extends Component {
27 onChange: () => null, 28 onChange: () => null,
28 onReset: () => null, 29 onReset: () => null,
29 autoFocus: false, 30 autoFocus: false,
30 } 31 };
31 32
32 input = null; 33 input = null;
33 34
@@ -38,7 +39,10 @@ export default @observer class SearchInput extends Component {
38 value: props.value, 39 value: props.value,
39 }; 40 };
40 41
41 this.throttledOnChange = debounce(this.throttledOnChange, this.props.throttleDelay); 42 this.throttledOnChange = debounce(
43 this.throttledOnChange,
44 this.props.throttleDelay,
45 );
42 } 46 }
43 47
44 componentDidMount() { 48 componentDidMount() {
@@ -80,24 +84,18 @@ export default @observer class SearchInput extends Component {
80 const { value } = this.state; 84 const { value } = this.state;
81 85
82 return ( 86 return (
83 <div 87 <div className={classnames([className, 'search-input'])}>
84 className={classnames([ 88 <label htmlFor={name} className="mdi mdi-magnify">
85 className,
86 'search-input',
87 ])}
88 >
89 <label
90 htmlFor={name}
91 className="mdi mdi-magnify"
92 >
93 <input 89 <input
94 name={name} 90 name={name}
95 id={name} 91 id={name}
96 type="text" 92 type="text"
97 placeholder={placeholder} 93 placeholder={placeholder}
98 value={value} 94 value={value}
99 onChange={(e) => this.onChange(e)} 95 onChange={e => this.onChange(e)}
100 ref={(ref) => { this.input = ref; }} 96 ref={ref => {
97 this.input = ref;
98 }}
101 /> 99 />
102 </label> 100 </label>
103 {value.length > 0 && ( 101 {value.length > 0 && (
@@ -110,3 +108,5 @@ export default @observer class SearchInput extends Component {
110 ); 108 );
111 } 109 }
112} 110}
111
112export default SearchInput;
diff --git a/src/components/ui/Select.js b/src/components/ui/Select.js
index e7a5eafa8..15b4c28e7 100644
--- a/src/components/ui/Select.js
+++ b/src/components/ui/Select.js
@@ -4,7 +4,8 @@ import { observer } from 'mobx-react';
4import { Field } from 'mobx-react-form'; 4import { Field } from 'mobx-react-form';
5import classnames from 'classnames'; 5import classnames from 'classnames';
6 6
7export default @observer class Select extends Component { 7@observer
8class Select extends Component {
8 static propTypes = { 9 static propTypes = {
9 field: PropTypes.instanceOf(Field).isRequired, 10 field: PropTypes.instanceOf(Field).isRequired,
10 className: PropTypes.string, 11 className: PropTypes.string,
@@ -43,13 +44,7 @@ export default @observer class Select extends Component {
43 } 44 }
44 45
45 render() { 46 render() {
46 const { 47 const { field, className, showLabel, disabled, multiple } = this.props;
47 field,
48 className,
49 showLabel,
50 disabled,
51 multiple,
52 } = this.props;
53 48
54 let selected = field.value; 49 let selected = field.value;
55 50
@@ -74,15 +69,12 @@ export default @observer class Select extends Component {
74 })} 69 })}
75 > 70 >
76 {field.label && showLabel && ( 71 {field.label && showLabel && (
77 <label 72 <label className="franz-form__label" htmlFor={field.name}>
78 className="franz-form__label"
79 htmlFor={field.name}
80 >
81 {field.label} 73 {field.label}
82 </label> 74 </label>
83 )} 75 )}
84 <select 76 <select
85 onChange={multiple ? (e) => this.multipleChange(e) : field.onChange} 77 onChange={multiple ? e => this.multipleChange(e) : field.onChange}
86 id={field.id} 78 id={field.id}
87 defaultValue={selected} 79 defaultValue={selected}
88 className="franz-form__select" 80 className="franz-form__select"
@@ -90,7 +82,7 @@ export default @observer class Select extends Component {
90 multiple={multiple} 82 multiple={multiple}
91 ref={this.element} 83 ref={this.element}
92 > 84 >
93 {field.options.map((type) => ( 85 {field.options.map(type => (
94 <option 86 <option
95 key={type.value} 87 key={type.value}
96 value={type.value} 88 value={type.value}
@@ -100,14 +92,10 @@ export default @observer class Select extends Component {
100 </option> 92 </option>
101 ))} 93 ))}
102 </select> 94 </select>
103 {field.error && ( 95 {field.error && <div className="franz-form__error">{field.error}</div>}
104 <div
105 className="franz-form__error"
106 >
107 {field.error}
108 </div>
109 )}
110 </div> 96 </div>
111 ); 97 );
112 } 98 }
113} 99}
100
101export default Select;
diff --git a/src/components/ui/Slider.js b/src/components/ui/Slider.js
index f344449a0..6f17eae00 100644
--- a/src/components/ui/Slider.js
+++ b/src/components/ui/Slider.js
@@ -4,62 +4,64 @@ import { observer } from 'mobx-react';
4import classnames from 'classnames'; 4import classnames from 'classnames';
5import { Field } from 'mobx-react-form'; 5import { Field } from 'mobx-react-form';
6 6
7export default @observer class Slider extends Component { 7@observer
8 static propTypes = { 8class Slider extends Component {
9 field: PropTypes.instanceOf(Field).isRequired, 9 static propTypes = {
10 className: PropTypes.string, 10 field: PropTypes.instanceOf(Field).isRequired,
11 showLabel: PropTypes.bool, 11 className: PropTypes.string,
12 disabled: PropTypes.bool, 12 showLabel: PropTypes.bool,
13 }; 13 disabled: PropTypes.bool,
14 };
14 15
15 static defaultProps = { 16 static defaultProps = {
16 className: '', 17 className: '',
17 showLabel: true, 18 showLabel: true,
18 disabled: false, 19 disabled: false,
19 }; 20 };
20 21
21 onChange(e) { 22 onChange(e) {
22 const { field } = this.props; 23 const { field } = this.props;
23 24
24 field.onChange(e); 25 field.onChange(e);
25 } 26 }
26
27 render() {
28 const {
29 field,
30 className,
31 showLabel,
32 disabled,
33 } = this.props;
34 27
35 if (field.value === '' && field.default !== '') { 28 render() {
36 field.value = field.default; 29 const { field, className, showLabel, disabled } = this.props;
37 }
38 30
39 return ( 31 if (field.value === '' && field.default !== '') {
40 <div 32 field.value = field.default;
41 className={classnames([ 33 }
42 'franz-form__field',
43 'franz-form__slider-wrapper',
44 className,
45 ])}
46 >
47 <div className="slider-container">
48 <input
49 className="slider"
50 type="range"
51 id={field.id}
52 name={field.name}
53 value={field.value}
54 min="1"
55 max="100"
56 onChange={(e) => (!disabled ? this.onChange(e) : null)}
57 />
58 </div>
59 34
60 {field.error && <div className={field.error}>{field.error}</div>} 35 return (
61 {field.label && showLabel && <label className="franz-form__label" htmlFor={field.id}>{field.label}</label>} 36 <div
37 className={classnames([
38 'franz-form__field',
39 'franz-form__slider-wrapper',
40 className,
41 ])}
42 >
43 <div className="slider-container">
44 <input
45 className="slider"
46 type="range"
47 id={field.id}
48 name={field.name}
49 value={field.value}
50 min="1"
51 max="100"
52 onChange={e => (!disabled ? this.onChange(e) : null)}
53 />
62 </div> 54 </div>
63 ); 55
64 } 56 {field.error && <div className={field.error}>{field.error}</div>}
57 {field.label && showLabel && (
58 <label className="franz-form__label" htmlFor={field.id}>
59 {field.label}
60 </label>
61 )}
62 </div>
63 );
64 }
65} 65}
66
67export default Slider;
diff --git a/src/components/ui/StatusBarTargetUrl.js b/src/components/ui/StatusBarTargetUrl.js
index 6fc50fe5c..ff4e8c795 100644
--- a/src/components/ui/StatusBarTargetUrl.js
+++ b/src/components/ui/StatusBarTargetUrl.js
@@ -5,7 +5,8 @@ import classnames from 'classnames';
5 5
6import Appear from './effects/Appear'; 6import Appear from './effects/Appear';
7 7
8export default @observer class StatusBarTargetUrl extends Component { 8@observer
9class StatusBarTargetUrl extends Component {
9 static propTypes = { 10 static propTypes = {
10 className: PropTypes.string, 11 className: PropTypes.string,
11 text: PropTypes.string, 12 text: PropTypes.string,
@@ -17,10 +18,7 @@ export default @observer class StatusBarTargetUrl extends Component {
17 }; 18 };
18 19
19 render() { 20 render() {
20 const { 21 const { className, text } = this.props;
21 className,
22 text,
23 } = this.props;
24 22
25 return ( 23 return (
26 <Appear 24 <Appear
@@ -29,10 +27,10 @@ export default @observer class StatusBarTargetUrl extends Component {
29 [`${className}`]: true, 27 [`${className}`]: true,
30 })} 28 })}
31 > 29 >
32 <div className="status-bar-target-url__content"> 30 <div className="status-bar-target-url__content">{text}</div>
33 {text}
34 </div>
35 </Appear> 31 </Appear>
36 ); 32 );
37 } 33 }
38} 34}
35
36export default StatusBarTargetUrl;
diff --git a/src/components/ui/Tabs/Tabs.js b/src/components/ui/Tabs/Tabs.js
index 56c76f215..195398708 100644
--- a/src/components/ui/Tabs/Tabs.js
+++ b/src/components/ui/Tabs/Tabs.js
@@ -5,7 +5,6 @@ import classnames from 'classnames';
5 5
6import { oneOrManyChildElements } from '../../../prop-types'; 6import { oneOrManyChildElements } from '../../../prop-types';
7 7
8export default
9@observer 8@observer
10class Tab extends Component { 9class Tab extends Component {
11 constructor(props) { 10 constructor(props) {
@@ -28,7 +27,7 @@ class Tab extends Component {
28 27
29 render() { 28 render() {
30 const { children: childElements } = this.props; 29 const { children: childElements } = this.props;
31 const children = childElements.filter((c) => !!c); 30 const children = childElements.filter(c => !!c);
32 31
33 if (children.length === 1) { 32 if (children.length === 1) {
34 return <div>{children}</div>; 33 return <div>{children}</div>;
@@ -69,3 +68,5 @@ class Tab extends Component {
69 ); 68 );
70 } 69 }
71} 70}
71
72export default Tab;
diff --git a/src/components/ui/Toggle.js b/src/components/ui/Toggle.js
index 14330e5c7..bd7bc242d 100644
--- a/src/components/ui/Toggle.js
+++ b/src/components/ui/Toggle.js
@@ -4,7 +4,8 @@ import { observer } from 'mobx-react';
4import classnames from 'classnames'; 4import classnames from 'classnames';
5import { Field } from 'mobx-react-form'; 5import { Field } from 'mobx-react-form';
6 6
7export default @observer class Toggle extends Component { 7@observer
8class Toggle extends Component {
8 static propTypes = { 9 static propTypes = {
9 field: PropTypes.instanceOf(Field).isRequired, 10 field: PropTypes.instanceOf(Field).isRequired,
10 className: PropTypes.string, 11 className: PropTypes.string,
@@ -25,12 +26,7 @@ export default @observer class Toggle extends Component {
25 } 26 }
26 27
27 render() { 28 render() {
28 const { 29 const { field, className, showLabel, disabled } = this.props;
29 field,
30 className,
31 showLabel,
32 disabled,
33 } = this.props;
34 30
35 if (field.value === '' && field.default !== '') { 31 if (field.value === '' && field.default !== '') {
36 field.value = field.default; 32 field.value = field.default;
@@ -59,12 +55,18 @@ export default @observer class Toggle extends Component {
59 name={field.name} 55 name={field.name}
60 value={field.name} 56 value={field.name}
61 checked={field.value} 57 checked={field.value}
62 onChange={(e) => (!disabled ? this.onChange(e) : null)} 58 onChange={e => (!disabled ? this.onChange(e) : null)}
63 /> 59 />
64 </label> 60 </label>
65 {field.error && <div className={field.error}>{field.error}</div>} 61 {field.error && <div className={field.error}>{field.error}</div>}
66 {field.label && showLabel && <label className="franz-form__label" htmlFor={field.id}>{field.label}</label>} 62 {field.label && showLabel && (
63 <label className="franz-form__label" htmlFor={field.id}>
64 {field.label}
65 </label>
66 )}
67 </div> 67 </div>
68 ); 68 );
69 } 69 }
70} 70}
71
72export default Toggle;
diff --git a/src/components/ui/ToggleRaw.js b/src/components/ui/ToggleRaw.js
index 4700127d4..1fde879ac 100644
--- a/src/components/ui/ToggleRaw.js
+++ b/src/components/ui/ToggleRaw.js
@@ -6,7 +6,8 @@ import PropTypes from 'prop-types';
6import { observer } from 'mobx-react'; 6import { observer } from 'mobx-react';
7import classnames from 'classnames'; 7import classnames from 'classnames';
8 8
9export default @observer class ToggleRaw extends Component { 9@observer
10class ToggleRaw extends Component {
10 static propTypes = { 11 static propTypes = {
11 onChange: PropTypes.func.isRequired, 12 onChange: PropTypes.func.isRequired,
12 field: PropTypes.shape({ 13 field: PropTypes.shape({
@@ -34,12 +35,7 @@ export default @observer class ToggleRaw extends Component {
34 } 35 }
35 36
36 render() { 37 render() {
37 const { 38 const { field, className, showLabel, disabled } = this.props;
38 field,
39 className,
40 showLabel,
41 disabled,
42 } = this.props;
43 39
44 return ( 40 return (
45 <div 41 <div
@@ -64,12 +60,18 @@ export default @observer class ToggleRaw extends Component {
64 name={field.name} 60 name={field.name}
65 value={field.name} 61 value={field.name}
66 checked={field.value} 62 checked={field.value}
67 onChange={(e) => (!disabled ? this.onChange(e) : null)} 63 onChange={e => (!disabled ? this.onChange(e) : null)}
68 /> 64 />
69 </label> 65 </label>
70 {field.error && <div className={field.error}>{field.error}</div>} 66 {field.error && <div className={field.error}>{field.error}</div>}
71 {field.label && showLabel && <label className="franz-form__label" htmlFor={field.id}>{field.label}</label>} 67 {field.label && showLabel && (
68 <label className="franz-form__label" htmlFor={field.id}>
69 {field.label}
70 </label>
71 )}
72 </div> 72 </div>
73 ); 73 );
74 } 74 }
75} 75}
76
77export default ToggleRaw;
diff --git a/src/components/ui/WebviewLoader/index.js b/src/components/ui/WebviewLoader/index.js
index c58d69374..8f4499e7b 100644
--- a/src/components/ui/WebviewLoader/index.js
+++ b/src/components/ui/WebviewLoader/index.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 injectSheet from 'react-jss'; 4import injectSheet from 'react-jss';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, injectIntl } from 'react-intl';
6 6
7import FullscreenLoader from '../FullscreenLoader'; 7import FullscreenLoader from '../FullscreenLoader';
8import styles from './styles'; 8import styles from './styles';
@@ -10,23 +10,21 @@ import styles from './styles';
10const messages = defineMessages({ 10const messages = defineMessages({
11 loading: { 11 loading: {
12 id: 'service.webviewLoader.loading', 12 id: 'service.webviewLoader.loading',
13 defaultMessage: '!!!Loading {service}', 13 defaultMessage: 'Loading {service}',
14 }, 14 },
15}); 15});
16 16
17export default @injectSheet(styles) @observer class WebviewLoader extends Component { 17@injectSheet(styles)
18@observer
19class WebviewLoader extends Component {
18 static propTypes = { 20 static propTypes = {
19 name: PropTypes.string.isRequired, 21 name: PropTypes.string.isRequired,
20 classes: PropTypes.object.isRequired, 22 classes: PropTypes.object.isRequired,
21 }; 23 };
22 24
23 static contextTypes = {
24 intl: intlShape,
25 };
26
27 render() { 25 render() {
28 const { classes, name } = this.props; 26 const { classes, name } = this.props;
29 const { intl } = this.context; 27 const { intl } = this.props;
30 return ( 28 return (
31 <FullscreenLoader 29 <FullscreenLoader
32 className={classes.component} 30 className={classes.component}
@@ -35,3 +33,5 @@ export default @injectSheet(styles) @observer class WebviewLoader extends Compon
35 ); 33 );
36 } 34 }
37} 35}
36
37export default injectIntl(WebviewLoader);
diff --git a/src/components/util/ErrorBoundary/index.js b/src/components/util/ErrorBoundary/index.js
index 5db0db226..9c789e981 100644
--- a/src/components/util/ErrorBoundary/index.js
+++ b/src/components/util/ErrorBoundary/index.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 injectSheet from 'react-jss'; 3import injectSheet from 'react-jss';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, injectIntl } from 'react-intl';
5 5
6import Button from '../../ui/Button'; 6import Button from '../../ui/Button';
7 7
@@ -10,26 +10,23 @@ import styles from './styles';
10const messages = defineMessages({ 10const messages = defineMessages({
11 headline: { 11 headline: {
12 id: 'app.errorHandler.headline', 12 id: 'app.errorHandler.headline',
13 defaultMessage: '!!!Something went wrong.', 13 defaultMessage: 'Something went wrong.',
14 }, 14 },
15 action: { 15 action: {
16 id: 'app.errorHandler.action', 16 id: 'app.errorHandler.action',
17 defaultMessage: '!!!Reload', 17 defaultMessage: 'Reload',
18 }, 18 },
19}); 19});
20 20
21export default @injectSheet(styles) class ErrorBoundary extends Component { 21@injectSheet(styles)
22class ErrorBoundary extends Component {
22 state = { 23 state = {
23 hasError: false, 24 hasError: false,
24 } 25 };
25 26
26 static propTypes = { 27 static propTypes = {
27 classes: PropTypes.object.isRequired, 28 classes: PropTypes.object.isRequired,
28 children: PropTypes.node.isRequired, 29 children: PropTypes.node.isRequired,
29 }
30
31 static contextTypes = {
32 intl: intlShape,
33 }; 30 };
34 31
35 componentDidCatch() { 32 componentDidCatch() {
@@ -38,7 +35,7 @@ export default @injectSheet(styles) class ErrorBoundary extends Component {
38 35
39 render() { 36 render() {
40 const { classes } = this.props; 37 const { classes } = this.props;
41 const { intl } = this.context; 38 const { intl } = this.props;
42 39
43 if (this.state.hasError) { 40 if (this.state.hasError) {
44 return ( 41 return (
@@ -58,3 +55,5 @@ export default @injectSheet(styles) class ErrorBoundary extends Component {
58 return this.props.children; 55 return this.props.children;
59 } 56 }
60} 57}
58
59export default injectIntl(ErrorBoundary);