aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/AppUpdateInfoBar.js16
-rw-r--r--src/components/auth/AuthLayout.js14
-rw-r--r--src/components/auth/ChangeServer.js79
-rw-r--r--src/components/auth/Import.js43
-rw-r--r--src/components/auth/Invite.js62
-rw-r--r--src/components/auth/Locked.js97
-rw-r--r--src/components/auth/Login.js134
-rw-r--r--src/components/auth/Password.js77
-rw-r--r--src/components/auth/SetupAssistant.js18
-rw-r--r--src/components/auth/Signup.js129
-rw-r--r--src/components/auth/Welcome.js57
-rw-r--r--src/components/layout/AppLayout.js27
-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.js45
-rw-r--r--src/components/services/content/ServiceWebview.js3
-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.js94
-rw-r--r--src/components/services/tabs/Tabbar.js20
-rw-r--r--src/components/settings/SettingsLayout.js14
-rw-r--r--src/components/settings/account/AccountDashboard.js41
-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.js173
-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.js56
-rw-r--r--src/components/settings/settings/EditSettingsForm.js533
-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/FeatureItem.js38
-rw-r--r--src/components/ui/FeatureList.js101
-rw-r--r--src/components/ui/FullscreenLoader/index.js23
-rw-r--r--src/components/ui/ImageUpload.js15
-rw-r--r--src/components/ui/InfoBar.js16
-rw-r--r--src/components/ui/Infobox.js16
-rw-r--r--src/components/ui/Input.js35
-rw-r--r--src/components/ui/Link.js14
-rw-r--r--src/components/ui/Loader.js17
-rw-r--r--src/components/ui/Modal/index.js21
-rw-r--r--src/components/ui/Radio.js26
-rw-r--r--src/components/ui/SearchInput.js30
-rw-r--r--src/components/ui/Select.js32
-rw-r--r--src/components/ui/Slider.js104
-rw-r--r--src/components/ui/StatusBarTargetUrl.js14
-rw-r--r--src/components/ui/Tabs/TabItem.js15
-rw-r--r--src/components/ui/Tabs/TabItem.tsx3
-rw-r--r--src/components/ui/Tabs/Tabs.js5
-rw-r--r--src/components/ui/Tabs/index.js6
-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/ui/effects/Appear.js10
-rw-r--r--src/components/util/ErrorBoundary/index.js19
62 files changed, 1602 insertions, 1552 deletions
diff --git a/src/components/AppUpdateInfoBar.js b/src/components/AppUpdateInfoBar.js
index 30804262a..47b730bde 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: 'What is new?',
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..17ac221a2 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'; 4import { TitleBar } from 'electron-react-titlebar/renderer';
5import { 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..df8980314 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() {
@@ -70,11 +65,11 @@ class Invite extends Component {
70 { 65 {
71 fields: { 66 fields: {
72 invite: [ 67 invite: [
73 ...Array(3).fill({ 68 ...Array.from({ length: 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,22 +92,22 @@ 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();
104 } 99 }
105 100
106 submit(e) { 101 submit(e) {
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();
114 // this.form.$('invite.0.name').focus(); // path accepted but does not focus ;( 109 // this.form.$('invite.0.name').focus(); // path accepted but does not focus ;(
115 document.querySelector('input:first-child').focus(); 110 document.querySelector('input:first-child')?.focus();
116 this.setState({ showSuccessInfo: true }); 111 this.setState({ showSuccessInfo: true });
117 }, 112 },
118 onError: () => {}, 113 onError: () => {},
@@ -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..2f9986858 100644
--- a/src/components/auth/Login.js
+++ b/src/components/auth/Login.js
@@ -2,10 +2,10 @@
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-remote';
9import Form from '../../lib/Form'; 9import Form from '../../lib/Form';
10import { required, email } from '../../helpers/validation-helpers'; 10import { required, email } from '../../helpers/validation-helpers';
11import serverlessLogin from '../../helpers/serverless-helpers'; 11import serverlessLogin from '../../helpers/serverless-helpers';
@@ -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: 'Reset 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..3e678f638 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: 'Reset 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 with that email address was found',
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..816a49669 100644
--- a/src/components/auth/Signup.js
+++ b/src/components/auth/Signup.js
@@ -2,9 +2,9 @@
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-remote';
8import Form from '../../lib/Form'; 8import Form from '../../lib/Form';
9import { required, email, minLength } from '../../helpers/validation-helpers'; 9import { required, email, minLength } from '../../helpers/validation-helpers';
10import serverlessLogin from '../../helpers/serverless-helpers'; 10import serverlessLogin from '../../helpers/serverless-helpers';
@@ -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: 'First Name',
27 }, 27 },
28 lastnameLabel: { 28 lastnameLabel: {
29 id: 'signup.lastname.label', 29 id: 'signup.lastname.label',
30 defaultMessage: '!!!Lastname', 30 defaultMessage: 'Last Name',
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..0a65dcffa 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -1,8 +1,8 @@
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/renderer';
6import injectSheet from 'react-jss'; 6import injectSheet from 'react-jss';
7 7
8import InfoBar from '../ui/InfoBar'; 8import InfoBar from '../ui/InfoBar';
@@ -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
@@ -88,16 +88,13 @@ class AppLayout extends Component {
88 88
89 state = { 89 state = {
90 shouldShowAppUpdateInfoBar: true, 90 shouldShowAppUpdateInfoBar: true,
91 shouldShowServicesUpdatedInfoBar: true,
91 }; 92 };
92 93
93 static defaultProps = { 94 static defaultProps = {
94 children: [], 95 children: [],
95 }; 96 };
96 97
97 static contextTypes = {
98 intl: intlShape,
99 };
100
101 render() { 98 render() {
102 const { 99 const {
103 classes, 100 classes,
@@ -119,7 +116,7 @@ class AppLayout extends Component {
119 areRequiredRequestsLoading, 116 areRequiredRequestsLoading,
120 } = this.props; 117 } = this.props;
121 118
122 const { intl } = this.context; 119 const { intl } = this.props;
123 120
124 return ( 121 return (
125 <ErrorBoundary> 122 <ErrorBoundary>
@@ -179,12 +176,14 @@ class AppLayout extends Component {
179 {intl.formatMessage(messages.authRequestFailed)} 176 {intl.formatMessage(messages.authRequestFailed)}
180 </InfoBar> 177 </InfoBar>
181 )} 178 )}
182 {showServicesUpdatedInfoBar && ( 179 {showServicesUpdatedInfoBar && this.state.shouldShowServicesUpdatedInfoBar && (
183 <InfoBar 180 <InfoBar
184 type="primary" 181 type="primary"
185 ctaLabel={intl.formatMessage(messages.buttonReloadServices)} 182 ctaLabel={intl.formatMessage(messages.buttonReloadServices)}
186 onClick={reloadServicesAfterUpdate} 183 onClick={reloadServicesAfterUpdate}
187 sticky 184 onHide={() => {
185 this.setState({ shouldShowServicesUpdatedInfoBar: false });
186 }}
188 > 187 >
189 <span className="mdi mdi-power-plug" /> 188 <span className="mdi mdi-power-plug" />
190 {intl.formatMessage(messages.servicesUpdated)} 189 {intl.formatMessage(messages.servicesUpdated)}
@@ -213,4 +212,4 @@ class AppLayout extends Component {
213 } 212 }
214} 213}
215 214
216export default AppLayout; 215export 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..b00db8c3f 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..81401b1d2 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,13 +104,11 @@ 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 (
110 <div className={webviewClasses}> 111 <div className={webviewClasses} data-name={service.name}>
111 {service.isActive && service.isEnabled && ( 112 {service.isActive && service.isEnabled && (
112 <> 113 <>
113 {service.hasCrashed && ( 114 {service.hasCrashed && (
@@ -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/ServiceWebview.js b/src/components/services/content/ServiceWebview.js
index c0f48793a..d3170be53 100644
--- a/src/components/services/content/ServiceWebview.js
+++ b/src/components/services/content/ServiceWebview.js
@@ -85,7 +85,8 @@ class ServiceWebview extends Component {
85 useragent={service.userAgent} 85 useragent={service.userAgent}
86 disablewebsecurity={service.recipe.disablewebsecurity ? true : undefined} 86 disablewebsecurity={service.recipe.disablewebsecurity ? true : undefined}
87 allowpopups 87 allowpopups
88 webpreferences={`spellcheck=${isSpellcheckerEnabled ? 1 : 0}`} 88 nodeintegration
89 webpreferences={`spellcheck=${isSpellcheckerEnabled ? 1 : 0}, contextIsolation=1, enableRemoteModule=1`}
89 /> 90 />
90 ); 91 );
91 } 92 }
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..2474682df 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 } 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,14 +133,44 @@ 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;
144 139
140 constructor(props) {
141 super(props);
142 this.state = {
143 showShortcutIndex: false,
144 };
145 }
146
147 handleShowShortcutIndex = () => {
148 this.setState({ showShortcutIndex: true });
149 };
150
151 checkForLongPress = () => {
152 let longpressDelay = null;
153 const longpressDelayDuration = 1000;
154
155 document.addEventListener(
156 'keydown',
157 e => {
158 if (e.ctrlKey || e.metaKey) {
159 longpressDelay = setTimeout(
160 this.handleShowShortcutIndex,
161 longpressDelayDuration,
162 );
163 }
164 },
165 { capture: true },
166 );
167
168 document.addEventListener('keyup', () => {
169 clearTimeout(longpressDelay);
170 this.setState({ showShortcutIndex: false });
171 });
172 };
173
145 componentDidMount() { 174 componentDidMount() {
146 const { service } = this.props; 175 const { service } = this.props;
147 176
@@ -164,6 +193,8 @@ class TabItem extends Component {
164 } 193 }
165 }); 194 });
166 } 195 }
196
197 this.checkForLongPress();
167 } 198 }
168 199
169 render() { 200 render() {
@@ -185,7 +216,7 @@ class TabItem extends Component {
185 showMessageBadgeWhenMutedSetting, 216 showMessageBadgeWhenMutedSetting,
186 showMessageBadgesEvenWhenMuted, 217 showMessageBadgesEvenWhenMuted,
187 } = this.props; 218 } = this.props;
188 const { intl } = this.context; 219 const { intl } = this.props;
189 220
190 const menuTemplate = [ 221 const menuTemplate = [
191 { 222 {
@@ -240,8 +271,9 @@ class TabItem extends Component {
240 ? messages.wakeUpService 271 ? messages.wakeUpService
241 : messages.hibernateService, 272 : messages.hibernateService,
242 ), 273 ),
274 // eslint-disable-next-line no-confusing-arrow
243 click: () => 275 click: () =>
244 (service.isHibernating ? wakeUpService() : hibernateService()), 276 service.isHibernating ? wakeUpService() : hibernateService(),
245 enabled: service.canHibernate, 277 enabled: service.canHibernate,
246 }, 278 },
247 { 279 {
@@ -256,7 +288,10 @@ class TabItem extends Component {
256 detail: intl.formatMessage(messages.confirmDeleteService, { 288 detail: intl.formatMessage(messages.confirmDeleteService, {
257 serviceName: service.name || service.recipe.name, 289 serviceName: service.name || service.recipe.name,
258 }), 290 }),
259 buttons: [intl.formatMessage(globalMessages.yes), intl.formatMessage(globalMessages.no)], 291 buttons: [
292 intl.formatMessage(globalMessages.yes),
293 intl.formatMessage(globalMessages.no),
294 ],
260 }); 295 });
261 if (selection === 0) { 296 if (selection === 0) {
262 deleteService(); 297 deleteService();
@@ -283,7 +318,7 @@ class TabItem extends Component {
283 service.unreadDirectMessageCount === 0 && 318 service.unreadDirectMessageCount === 0 &&
284 service.isIndirectMessageBadgeEnabled && ( 319 service.isIndirectMessageBadgeEnabled && (
285 <span className="tab-item__message-count is-indirect">•</span> 320 <span className="tab-item__message-count is-indirect">•</span>
286 )} 321 )}
287 {service.isHibernating && ( 322 {service.isHibernating && (
288 <span className="tab-item__message-count hibernating">•</span> 323 <span className="tab-item__message-count hibernating">•</span>
289 )} 324 )}
@@ -302,9 +337,11 @@ class TabItem extends Component {
302 'is-disabled': !service.isEnabled, 337 'is-disabled': !service.isEnabled,
303 })} 338 })}
304 onClick={clickHandler} 339 onClick={clickHandler}
305 onContextMenu={() => menu.popup(getCurrentWindow())} 340 onContextMenu={() => menu.popup()}
306 data-tip={`${service.name} ${ 341 data-tip={`${service.name} ${
307 shortcutIndex <= 9 ? `(${cmdOrCtrlShortcutKey(false)}+${shortcutIndex})` : '' 342 shortcutIndex <= 9
343 ? `(${cmdOrCtrlShortcutKey(false)}+${shortcutIndex})`
344 : ''
308 }`} 345 }`}
309 > 346 >
310 <img src={service.icon} className="tab-item__icon" alt="" /> 347 <img src={service.icon} className="tab-item__icon" alt="" />
@@ -327,9 +364,12 @@ class TabItem extends Component {
327 /> 364 />
328 </> 365 </>
329 )} 366 )}
367 {shortcutIndex && this.state.showShortcutIndex && (
368 <span className="tab-item__shortcut-index">{shortcutIndex}</span>
369 )}
330 </li> 370 </li>
331 ); 371 );
332 } 372 }
333} 373}
334 374
335export default SortableElement(TabItem); 375export 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..71250bd4d 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 }
@@ -34,6 +29,7 @@ class SettingsLayout extends Component {
34 componentWillUnmount() { 29 componentWillUnmount() {
35 document.removeEventListener( 30 document.removeEventListener(
36 'keydown', 31 'keydown',
32 // eslint-disable-next-line unicorn/no-invalid-remove-event-listener
37 this.handleKeyDown.bind(this), 33 this.handleKeyDown.bind(this),
38 false, 34 false,
39 ); 35 );
@@ -49,7 +45,7 @@ class SettingsLayout extends Component {
49 render() { 45 render() {
50 const { navigation, children, closeSettings } = this.props; 46 const { navigation, children, closeSettings } = this.props;
51 47
52 const { intl } = this.context; 48 const { intl } = this.props;
53 49
54 return ( 50 return (
55 <Appear transitionName="fadeIn-fast"> 51 <Appear transitionName="fadeIn-fast">
@@ -77,3 +73,5 @@ class SettingsLayout extends Component {
77 ); 73 );
78 } 74 }
79} 75}
76
77export default injectIntl(SettingsLayout);
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
index ef7748343..544821e9a 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,45 +13,45 @@ 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',
20 defaultMessage: '!!Danger Zone', 20 defaultMessage: 'Danger Zone',
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',
28 defaultMessage: '!!Invoices', 28 defaultMessage: 'Invoices',
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 Ferdi 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..44f5bc39a 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 service recipe to:',
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 folder',
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..22089ec45 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -2,13 +2,14 @@ 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';
9import Recipe from '../../../models/Recipe'; 9import Recipe from '../../../models/Recipe';
10import Service from '../../../models/Service'; 10import Service from '../../../models/Service';
11import Tabs, { TabItem } from '../../ui/Tabs'; 11import Tabs from '../../ui/Tabs/Tabs';
12import { TabItem } from '../../ui/Tabs/TabItem';
12import Input from '../../ui/Input'; 13import Input from '../../ui/Input';
13import Toggle from '../../ui/Toggle'; 14import Toggle from '../../ui/Toggle';
14import Slider from '../../ui/Slider'; 15import Slider from '../../ui/Slider';
@@ -22,111 +23,117 @@ import globalMessages from '../../../i18n/globalMessages';
22const messages = defineMessages({ 23const messages = defineMessages({
23 saveService: { 24 saveService: {
24 id: 'settings.service.form.saveButton', 25 id: 'settings.service.form.saveButton',
25 defaultMessage: '!!!Save service', 26 defaultMessage: 'Save service',
26 }, 27 },
27 deleteService: { 28 deleteService: {
28 id: 'settings.service.form.deleteButton', 29 id: 'settings.service.form.deleteButton',
29 defaultMessage: '!!!Delete Service', 30 defaultMessage: 'Delete service',
30 }, 31 },
31 openDarkmodeCss: { 32 openDarkmodeCss: {
32 id: 'settings.service.form.openDarkmodeCss', 33 id: 'settings.service.form.openDarkmodeCss',
33 defaultMessage: '!!!Open darkmode.css', 34 defaultMessage: 'Open darkmode.css',
34 }, 35 },
35 openUserCss: { 36 openUserCss: {
36 id: 'settings.service.form.openUserCss', 37 id: 'settings.service.form.openUserCss',
37 defaultMessage: '!!!Open user.css', 38 defaultMessage: 'Open user.css',
38 }, 39 },
39 openUserJs: { 40 openUserJs: {
40 id: 'settings.service.form.openUserJs', 41 id: 'settings.service.form.openUserJs',
41 defaultMessage: '!!!Open user.js', 42 defaultMessage: 'Open user.js',
42 }, 43 },
43 recipeFileInfo: { 44 recipeFileInfo: {
44 id: 'settings.service.form.recipeFileInfo', 45 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.', 46 defaultMessage:
47 '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 }, 48 },
47 availableServices: { 49 availableServices: {
48 id: 'settings.service.form.availableServices', 50 id: 'settings.service.form.availableServices',
49 defaultMessage: '!!!Available services', 51 defaultMessage: 'Available services',
50 }, 52 },
51 yourServices: { 53 yourServices: {
52 id: 'settings.service.form.yourServices', 54 id: 'settings.service.form.yourServices',
53 defaultMessage: '!!!Your services', 55 defaultMessage: 'Your services',
54 }, 56 },
55 addServiceHeadline: { 57 addServiceHeadline: {
56 id: 'settings.service.form.addServiceHeadline', 58 id: 'settings.service.form.addServiceHeadline',
57 defaultMessage: '!!!Add {name}', 59 defaultMessage: 'Add {name}',
58 }, 60 },
59 editServiceHeadline: { 61 editServiceHeadline: {
60 id: 'settings.service.form.editServiceHeadline', 62 id: 'settings.service.form.editServiceHeadline',
61 defaultMessage: '!!!Edit {name}', 63 defaultMessage: 'Edit {name}',
62 }, 64 },
63 tabHosted: { 65 tabHosted: {
64 id: 'settings.service.form.tabHosted', 66 id: 'settings.service.form.tabHosted',
65 defaultMessage: '!!!Hosted', 67 defaultMessage: 'Hosted',
66 }, 68 },
67 tabOnPremise: { 69 tabOnPremise: {
68 id: 'settings.service.form.tabOnPremise', 70 id: 'settings.service.form.tabOnPremise',
69 defaultMessage: '!!!Self hosted ⭐️', 71 defaultMessage: 'Self hosted ⭐️',
70 }, 72 },
71 useHostedService: { 73 useHostedService: {
72 id: 'settings.service.form.useHostedService', 74 id: 'settings.service.form.useHostedService',
73 defaultMessage: '!!!Use the hosted {name} service.', 75 defaultMessage: 'Use the hosted {name} service.',
74 }, 76 },
75 customUrlValidationError: { 77 customUrlValidationError: {
76 id: 'settings.service.form.customUrlValidationError', 78 id: 'settings.service.form.customUrlValidationError',
77 defaultMessage: '!!!Could not validate custom {name} server.', 79 defaultMessage: 'Could not validate custom {name} server.',
78 }, 80 },
79 indirectMessageInfo: { 81 indirectMessageInfo: {
80 id: 'settings.service.form.indirectMessageInfo', 82 id: 'settings.service.form.indirectMessageInfo',
81 defaultMessage: '!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...', 83 defaultMessage:
84 'You will be notified about all new messages in a channel, not just @username, @channel, @here, ...',
82 }, 85 },
83 isMutedInfo: { 86 isMutedInfo: {
84 id: 'settings.service.form.isMutedInfo', 87 id: 'settings.service.form.isMutedInfo',
85 defaultMessage: '!!!When disabled, all notification sounds and audio playback are muted', 88 defaultMessage:
89 'When disabled, all notification sounds and audio playback are muted',
86 }, 90 },
87 isHibernationEnabledInfo: { 91 isHibernationEnabledInfo: {
88 id: 'settings.service.form.isHibernatedEnabledInfo', 92 id: 'settings.service.form.isHibernatedEnabledInfo',
89 defaultMessage: '!!!When enabled, a service will be shut down after a period of time to save system resources.', 93 defaultMessage:
94 'When enabled, a service will be shut down after a period of time to save system resources.',
90 }, 95 },
91 headlineNotifications: { 96 headlineNotifications: {
92 id: 'settings.service.form.headlineNotifications', 97 id: 'settings.service.form.headlineNotifications',
93 defaultMessage: '!!!Notifications', 98 defaultMessage: 'Notifications',
94 }, 99 },
95 headlineBadges: { 100 headlineBadges: {
96 id: 'settings.service.form.headlineBadges', 101 id: 'settings.service.form.headlineBadges',
97 defaultMessage: '!!!Unread message badges', 102 defaultMessage: 'Unread message badges',
98 }, 103 },
99 headlineGeneral: { 104 headlineGeneral: {
100 id: 'settings.service.form.headlineGeneral', 105 id: 'settings.service.form.headlineGeneral',
101 defaultMessage: '!!!General', 106 defaultMessage: 'General',
102 }, 107 },
103 headlineDarkReaderSettings: { 108 headlineDarkReaderSettings: {
104 id: 'settings.service.form.headlineDarkReaderSettings', 109 id: 'settings.service.form.headlineDarkReaderSettings',
105 defaultMessage: '!!!Dark Reader Settings', 110 defaultMessage: 'Dark Reader Settings',
106 }, 111 },
107 iconDelete: { 112 iconDelete: {
108 id: 'settings.service.form.iconDelete', 113 id: 'settings.service.form.iconDelete',
109 defaultMessage: '!!!Delete', 114 defaultMessage: 'Delete',
110 }, 115 },
111 iconUpload: { 116 iconUpload: {
112 id: 'settings.service.form.iconUpload', 117 id: 'settings.service.form.iconUpload',
113 defaultMessage: '!!!Drop your image, or click here', 118 defaultMessage: 'Drop your image, or click here',
114 }, 119 },
115 headlineProxy: { 120 headlineProxy: {
116 id: 'settings.service.form.proxy.headline', 121 id: 'settings.service.form.proxy.headline',
117 defaultMessage: '!!!HTTP/HTTPS Proxy Settings', 122 defaultMessage: 'HTTP/HTTPS Proxy Settings',
118 }, 123 },
119 proxyRestartInfo: { 124 proxyRestartInfo: {
120 id: 'settings.service.form.proxy.restartInfo', 125 id: 'settings.service.form.proxy.restartInfo',
121 defaultMessage: '!!!Please restart Ferdi after changing proxy Settings.', 126 defaultMessage: 'Please restart Ferdi after changing proxy Settings.',
122 }, 127 },
123 proxyInfo: { 128 proxyInfo: {
124 id: 'settings.service.form.proxy.info', 129 id: 'settings.service.form.proxy.info',
125 defaultMessage: '!!!Proxy settings will not be synchronized with the Ferdi servers.', 130 defaultMessage:
131 'Proxy settings will not be synchronized with the Ferdi servers.',
126 }, 132 },
127}); 133});
128 134
129export default @observer class EditServiceForm extends Component { 135@observer
136class EditServiceForm extends Component {
130 static propTypes = { 137 static propTypes = {
131 recipe: PropTypes.instanceOf(Recipe).isRequired, 138 recipe: PropTypes.instanceOf(Recipe).isRequired,
132 service(props, propName) { 139 service(props, propName) {
@@ -151,20 +158,16 @@ export default @observer class EditServiceForm extends Component {
151 service: {}, 158 service: {},
152 }; 159 };
153 160
154 static contextTypes = {
155 intl: intlShape,
156 };
157
158 state = { 161 state = {
159 isValidatingCustomUrl: false, 162 isValidatingCustomUrl: false,
160 } 163 };
161 164
162 submit(e) { 165 submit(e) {
163 const { recipe } = this.props; 166 const { recipe } = this.props;
164 167
165 e.preventDefault(); 168 e.preventDefault();
166 this.props.form.submit({ 169 this.props.form.submit({
167 onSuccess: async (form) => { 170 onSuccess: async form => {
168 const values = form.values(); 171 const values = form.values();
169 let isValid = true; 172 let isValid = true;
170 173
@@ -176,10 +179,13 @@ export default @observer class EditServiceForm extends Component {
176 if (recipe.validateUrl && values.customUrl) { 179 if (recipe.validateUrl && values.customUrl) {
177 this.setState({ isValidatingCustomUrl: true }); 180 this.setState({ isValidatingCustomUrl: true });
178 try { 181 try {
179 values.customUrl = normalizeUrl(values.customUrl, { stripWWW: false, removeTrailingSlash: false }); 182 values.customUrl = normalizeUrl(values.customUrl, {
183 stripWWW: false,
184 removeTrailingSlash: false,
185 });
180 isValid = await recipe.validateUrl(values.customUrl); 186 isValid = await recipe.validateUrl(values.customUrl);
181 } catch (err) { 187 } catch (error) {
182 console.warn('ValidateURL', err); 188 console.warn('ValidateURL', error);
183 isValid = false; 189 isValid = false;
184 } 190 }
185 } 191 }
@@ -208,7 +214,7 @@ export default @observer class EditServiceForm extends Component {
208 openRecipeFile, 214 openRecipeFile,
209 isProxyFeatureEnabled, 215 isProxyFeatureEnabled,
210 } = this.props; 216 } = this.props;
211 const { intl } = this.context; 217 const { intl } = this.props;
212 218
213 const { isValidatingCustomUrl } = this.state; 219 const { isValidatingCustomUrl } = this.state;
214 220
@@ -236,7 +242,8 @@ export default @observer class EditServiceForm extends Component {
236 activeTabIndex = 2; 242 activeTabIndex = 2;
237 } 243 }
238 244
239 const requiresUserInput = !recipe.hasHostedOption && (recipe.hasTeamId || recipe.hasCustomUrl); 245 const requiresUserInput =
246 !recipe.hasHostedOption && (recipe.hasTeamId || recipe.hasCustomUrl);
240 247
241 return ( 248 return (
242 <div className="settings__main"> 249 <div className="settings__main">
@@ -254,29 +261,27 @@ export default @observer class EditServiceForm extends Component {
254 </span> 261 </span>
255 <span className="separator" /> 262 <span className="separator" />
256 <span className="settings__header-item"> 263 <span className="settings__header-item">
257 {action === 'add' ? ( 264 {action === 'add'
258 intl.formatMessage(messages.addServiceHeadline, { 265 ? intl.formatMessage(messages.addServiceHeadline, {
259 name: recipe.name, 266 name: recipe.name,
260 }) 267 })
261 ) : ( 268 : intl.formatMessage(messages.editServiceHeadline, {
262 intl.formatMessage(messages.editServiceHeadline, { 269 name: service.name !== '' ? service.name : recipe.name,
263 name: service.name !== '' ? service.name : recipe.name, 270 })}
264 })
265 )}
266 </span> 271 </span>
267 </div> 272 </div>
268 <div className="settings__body"> 273 <div className="settings__body">
269 <form onSubmit={(e) => this.submit(e)} id="form"> 274 <form onSubmit={e => this.submit(e)} id="form">
270 <div className="service-name"> 275 <div className="service-name">
271 <Input field={form.$('name')} focus /> 276 <Input field={form.$('name')} focus />
272 </div> 277 </div>
273 {(recipe.hasTeamId || recipe.hasCustomUrl) && ( 278 {(recipe.hasTeamId || recipe.hasCustomUrl) && (
274 <Tabs 279 <Tabs active={activeTabIndex}>
275 active={activeTabIndex}
276 >
277 {recipe.hasHostedOption && ( 280 {recipe.hasHostedOption && (
278 <TabItem title={recipe.name}> 281 <TabItem title={recipe.name}>
279 {intl.formatMessage(messages.useHostedService, { name: recipe.name })} 282 {intl.formatMessage(messages.useHostedService, {
283 name: recipe.name,
284 })}
280 </TabItem> 285 </TabItem>
281 )} 286 )}
282 {recipe.hasTeamId && ( 287 {recipe.hasTeamId && (
@@ -293,7 +298,9 @@ export default @observer class EditServiceForm extends Component {
293 <Input field={form.$('customUrl')} /> 298 <Input field={form.$('customUrl')} />
294 {form.error === 'url-validation-error' && ( 299 {form.error === 'url-validation-error' && (
295 <p className="franz-form__error"> 300 <p className="franz-form__error">
296 {intl.formatMessage(messages.customUrlValidationError, { name: recipe.name })} 301 {intl.formatMessage(messages.customUrlValidationError, {
302 name: recipe.name,
303 })}
297 </p> 304 </p>
298 )} 305 )}
299 </TabItem> 306 </TabItem>
@@ -326,13 +333,19 @@ export default @observer class EditServiceForm extends Component {
326 <div className="settings__settings-group"> 333 <div className="settings__settings-group">
327 <h3>{intl.formatMessage(messages.headlineBadges)}</h3> 334 <h3>{intl.formatMessage(messages.headlineBadges)}</h3>
328 <Toggle field={form.$('isBadgeEnabled')} /> 335 <Toggle field={form.$('isBadgeEnabled')} />
329 {recipe.hasIndirectMessages && form.$('isBadgeEnabled').value && ( 336 {recipe.hasIndirectMessages &&
330 <> 337 form.$('isBadgeEnabled').value && (
331 <Toggle field={form.$('isIndirectMessageBadgeEnabled')} /> 338 <>
332 <p className="settings__help indented__help"> 339 <Toggle
333 {intl.formatMessage(messages.indirectMessageInfo)} 340 field={form.$('isIndirectMessageBadgeEnabled')}
334 </p> 341 />
335 </> 342 <p className="settings__help indented__help">
343 {intl.formatMessage(messages.indirectMessageInfo)}
344 </p>
345 </>
346 )}
347 {recipe.allowFavoritesDelineationInUnreadCount && (
348 <Toggle field={form.$('onlyShowFavoritesInUnreadCount')} />
336 )} 349 )}
337 </div> 350 </div>
338 351
@@ -344,15 +357,18 @@ export default @observer class EditServiceForm extends Component {
344 {intl.formatMessage(messages.isHibernationEnabledInfo)} 357 {intl.formatMessage(messages.isHibernationEnabledInfo)}
345 </p> 358 </p>
346 <Toggle field={form.$('isDarkModeEnabled')} /> 359 <Toggle field={form.$('isDarkModeEnabled')} />
347 {form.$('isDarkModeEnabled').value 360 {form.$('isDarkModeEnabled').value && (
348 && ( 361 <>
349 <> 362 <h3>
350 <h3>{intl.formatMessage(messages.headlineDarkReaderSettings)}</h3> 363 {intl.formatMessage(
351 <Slider field={form.$('darkReaderBrightness')} /> 364 messages.headlineDarkReaderSettings,
352 <Slider field={form.$('darkReaderContrast')} /> 365 )}
353 <Slider field={form.$('darkReaderSepia')} /> 366 </h3>
354 </> 367 <Slider field={form.$('darkReaderBrightness')} />
355 )} 368 <Slider field={form.$('darkReaderContrast')} />
369 <Slider field={form.$('darkReaderSepia')} />
370 </>
371 )}
356 </div> 372 </div>
357 </div> 373 </div>
358 <div className="service-icon"> 374 <div className="service-icon">
@@ -381,7 +397,10 @@ export default @observer class EditServiceForm extends Component {
381 <> 397 <>
382 <div className="grid"> 398 <div className="grid">
383 <div className="grid__row"> 399 <div className="grid__row">
384 <Input field={form.$('proxy.host')} className="proxyHost" /> 400 <Input
401 field={form.$('proxy.host')}
402 className="proxyHost"
403 />
385 <Input field={form.$('proxy.port')} /> 404 <Input field={form.$('proxy.port')} />
386 </div> 405 </div>
387 </div> 406 </div>
@@ -409,7 +428,9 @@ export default @observer class EditServiceForm extends Component {
409 428
410 <div className="user-agent"> 429 <div className="user-agent">
411 <Input field={form.$('userAgentPref')} /> 430 <Input field={form.$('userAgentPref')} />
412 <p className="settings__help">{intl.formatMessage(globalMessages.userAgentHelp)}</p> 431 <p className="settings__help">
432 {intl.formatMessage(globalMessages.userAgentHelp)}
433 </p>
413 </div> 434 </div>
414 </form> 435 </form>
415 436
@@ -464,7 +485,9 @@ export default @observer class EditServiceForm extends Component {
464 type="submit" 485 type="submit"
465 label={intl.formatMessage(messages.saveService)} 486 label={intl.formatMessage(messages.saveService)}
466 htmlForm="form" 487 htmlForm="form"
467 disabled={action !== 'edit' && (form.isPristine && requiresUserInput)} 488 disabled={
489 action !== 'edit' && form.isPristine && requiresUserInput
490 }
468 /> 491 />
469 )} 492 )}
470 </div> 493 </div>
@@ -472,3 +495,5 @@ export default @observer class EditServiceForm extends Component {
472 ); 495 );
473 } 496 }
474} 497}
498
499export 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..bb52db97f 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.services.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 - 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.',
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">
@@ -93,10 +91,10 @@ export default @observer class ServicesDashboard extends Component {
93 <h1>{intl.formatMessage(messages.headline)}</h1> 91 <h1>{intl.formatMessage(messages.headline)}</h1>
94 </div> 92 </div>
95 <div className="settings__body"> 93 <div className="settings__body">
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..123ab4c2d 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';
@@ -12,155 +12,172 @@ import ToggleRaw from '../../ui/ToggleRaw';
12import Select from '../../ui/Select'; 12import 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 { DEFAULT_APP_SETTINGS, FRANZ_TRANSLATION, GITHUB_FRANZ_URL } from '../../../config';
16import { DEFAULT_APP_SETTINGS, ferdiVersion, isMac, isWindows, lockFerdiShortcutKey, userDataPath, userDataRecipesPath } from '../../../environment'; 16import {
17 isMac,
18 isWindows,
19 lockFerdiShortcutKey,
20} from '../../../environment';
21import { ferdiVersion, userDataPath, userDataRecipesPath } from '../../../environment-remote';
17import { openPath } from '../../../helpers/url-helpers'; 22import { openPath } from '../../../helpers/url-helpers';
18import globalMessages from '../../../i18n/globalMessages'; 23import globalMessages from '../../../i18n/globalMessages';
19 24
25const debug = require('debug')('Ferdi:EditSettingsForm');
26
20const messages = defineMessages({ 27const messages = defineMessages({
21 headlineGeneral: { 28 headlineGeneral: {
22 id: 'settings.app.headlineGeneral', 29 id: 'settings.app.headlineGeneral',
23 defaultMessage: '!!!General', 30 defaultMessage: 'General',
24 }, 31 },
25 sentryInfo: { 32 sentryInfo: {
26 id: 'settings.app.sentryInfo', 33 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!', 34 defaultMessage:
35 'Sending telemetry data allows us to find errors in Ferdi - we will not send any personal information like your message data!',
28 }, 36 },
29 hibernateInfo: { 37 hibernateInfo: {
30 id: 'settings.app.hibernateInfo', 38 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.', 39 defaultMessage:
40 '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 }, 41 },
33 inactivityLockInfo: { 42 inactivityLockInfo: {
34 id: 'settings.app.inactivityLockInfo', 43 id: 'settings.app.inactivityLockInfo',
35 defaultMessage: '!!!Minutes of inactivity, after which Ferdi should automatically lock. Use 0 to disable', 44 defaultMessage:
45 'Minutes of inactivity, after which Ferdi should automatically lock. Use 0 to disable',
36 }, 46 },
37 todoServerInfo: { 47 todoServerInfo: {
38 id: 'settings.app.todoServerInfo', 48 id: 'settings.app.todoServerInfo',
39 defaultMessage: '!!!This server will be used for the "Franz Todo" feature. (default: https://app.franztodos.com)', 49 defaultMessage: 'This server will be used for the "Ferdi Todo" feature.',
40 }, 50 },
41 lockedPassword: { 51 lockedPassword: {
42 id: 'settings.app.lockedPassword', 52 id: 'settings.app.lockedPassword',
43 defaultMessage: '!!!Password', 53 defaultMessage: 'Password',
44 }, 54 },
45 lockedPasswordInfo: { 55 lockedPasswordInfo: {
46 id: 'settings.app.lockedPasswordInfo', 56 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.', 57 defaultMessage:
58 "Please make sure to set a password you'll remember.\nIf you loose this password, you will have to reinstall Ferdi.",
48 }, 59 },
49 lockInfo: { 60 lockInfo: {
50 id: 'settings.app.lockInfo', 61 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}.', 62 defaultMessage:
63 '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 }, 64 },
53 scheduledDNDTimeInfo: { 65 scheduledDNDTimeInfo: {
54 id: 'settings.app.scheduledDNDTimeInfo', 66 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.', 67 defaultMessage:
68 '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 }, 69 },
57 scheduledDNDInfo: { 70 scheduledDNDInfo: {
58 id: 'settings.app.scheduledDNDInfo', 71 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.', 72 defaultMessage:
73 'Scheduled Do-not-Disturb allows you to define a period of time in which you do not want to get Notifications from Ferdi.',
60 }, 74 },
61 headlineLanguage: { 75 headlineLanguage: {
62 id: 'settings.app.headlineLanguage', 76 id: 'settings.app.headlineLanguage',
63 defaultMessage: '!!!Language', 77 defaultMessage: 'Language',
64 }, 78 },
65 headlineUpdates: { 79 headlineUpdates: {
66 id: 'settings.app.headlineUpdates', 80 id: 'settings.app.headlineUpdates',
67 defaultMessage: '!!!Updates', 81 defaultMessage: 'Updates',
68 }, 82 },
69 headlineAppearance: { 83 headlineAppearance: {
70 id: 'settings.app.headlineAppearance', 84 id: 'settings.app.headlineAppearance',
71 defaultMessage: '!!!Appearance', 85 defaultMessage: 'Appearance',
72 }, 86 },
73 universalDarkModeInfo: { 87 universalDarkModeInfo: {
74 id: 'settings.app.universalDarkModeInfo', 88 id: 'settings.app.universalDarkModeInfo',
75 defaultMessage: '!!!Universal Dark Mode tries to dynamically generate dark mode styles for services that are otherwise not currently supported.', 89 defaultMessage:
90 'Universal Dark Mode tries to dynamically generate dark mode styles for services that are otherwise not currently supported.',
76 }, 91 },
77 accentColorInfo: { 92 accentColorInfo: {
78 id: 'settings.app.accentColorInfo', 93 id: 'settings.app.accentColorInfo',
79 defaultMessage: '!!!Write your accent color in a CSS-compatible format. (Default: {defaultAccentColor})', 94 defaultMessage:
95 'Write your accent color in a CSS-compatible format. (Default: {defaultAccentColor})',
80 }, 96 },
81 headlinePrivacy: { 97 headlinePrivacy: {
82 id: 'settings.app.headlinePrivacy', 98 id: 'settings.app.headlinePrivacy',
83 defaultMessage: '!!!Privacy', 99 defaultMessage: 'Privacy',
84 }, 100 },
85 headlineAdvanced: { 101 headlineAdvanced: {
86 id: 'settings.app.headlineAdvanced', 102 id: 'settings.app.headlineAdvanced',
87 defaultMessage: '!!!Advanced', 103 defaultMessage: 'Advanced',
88 }, 104 },
89 translationHelp: { 105 translationHelp: {
90 id: 'settings.app.translationHelp', 106 id: 'settings.app.translationHelp',
91 defaultMessage: '!!!Help us to translate Ferdi into your language.', 107 defaultMessage: 'Help us to translate Ferdi into your language.',
92 }, 108 },
93 spellCheckerLanguageInfo: { 109 spellCheckerLanguageInfo: {
94 id: 'settings.app.spellCheckerLanguageInfo', 110 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.', 111 defaultMessage:
112 "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 }, 113 },
97 subheadlineCache: { 114 subheadlineCache: {
98 id: 'settings.app.subheadlineCache', 115 id: 'settings.app.subheadlineCache',
99 defaultMessage: '!!!Cache', 116 defaultMessage: 'Cache',
100 }, 117 },
101 cacheInfo: { 118 cacheInfo: {
102 id: 'settings.app.cacheInfo', 119 id: 'settings.app.cacheInfo',
103 defaultMessage: '!!!Ferdi cache is currently using {size} of disk space.', 120 defaultMessage: 'Ferdi cache is currently using {size} of disk space.',
104 }, 121 },
105 cacheNotCleared: { 122 cacheNotCleared: {
106 id: 'settings.app.cacheNotCleared', 123 id: 'settings.app.cacheNotCleared',
107 defaultMessage: '!!!Couldn\'t clear all cache', 124 defaultMessage: "Couldn't clear all cache",
108 }, 125 },
109 buttonClearAllCache: { 126 buttonClearAllCache: {
110 id: 'settings.app.buttonClearAllCache', 127 id: 'settings.app.buttonClearAllCache',
111 defaultMessage: '!!!Clear cache', 128 defaultMessage: 'Clear cache',
112 }, 129 },
113 subheadlineFerdiProfile: { 130 subheadlineFerdiProfile: {
114 id: 'settings.app.subheadlineFerdiProfile', 131 id: 'settings.app.subheadlineFerdiProfile',
115 defaultMessage: '!!!Ferdi Profile', 132 defaultMessage: 'Ferdi Profile',
116 }, 133 },
117 buttonOpenFerdiProfileFolder: { 134 buttonOpenFerdiProfileFolder: {
118 id: 'settings.app.buttonOpenFerdiProfileFolder', 135 id: 'settings.app.buttonOpenFerdiProfileFolder',
119 defaultMessage: '!!!Open Profile folder', 136 defaultMessage: 'Open Profile folder',
120 }, 137 },
121 buttonOpenFerdiServiceRecipesFolder: { 138 buttonOpenFerdiServiceRecipesFolder: {
122 id: 'settings.app.buttonOpenFerdiServiceRecipesFolder', 139 id: 'settings.app.buttonOpenFerdiServiceRecipesFolder',
123 defaultMessage: '!!!Open Service Recipes folder', 140 defaultMessage: 'Open Service Recipes folder',
124 }, 141 },
125 buttonSearchForUpdate: { 142 buttonSearchForUpdate: {
126 id: 'settings.app.buttonSearchForUpdate', 143 id: 'settings.app.buttonSearchForUpdate',
127 defaultMessage: '!!!Check for updates', 144 defaultMessage: 'Check for updates',
128 }, 145 },
129 buttonInstallUpdate: { 146 buttonInstallUpdate: {
130 id: 'settings.app.buttonInstallUpdate', 147 id: 'settings.app.buttonInstallUpdate',
131 defaultMessage: '!!!Restart & install update', 148 defaultMessage: 'Restart & install update',
132 }, 149 },
133 updateStatusSearching: { 150 updateStatusSearching: {
134 id: 'settings.app.updateStatusSearching', 151 id: 'settings.app.updateStatusSearching',
135 defaultMessage: '!!!Is searching for update', 152 defaultMessage: 'Is searching for update',
136 }, 153 },
137 updateStatusAvailable: { 154 updateStatusAvailable: {
138 id: 'settings.app.updateStatusAvailable', 155 id: 'settings.app.updateStatusAvailable',
139 defaultMessage: '!!!Update available, downloading...', 156 defaultMessage: 'Update available, downloading...',
140 }, 157 },
141 updateStatusUpToDate: { 158 updateStatusUpToDate: {
142 id: 'settings.app.updateStatusUpToDate', 159 id: 'settings.app.updateStatusUpToDate',
143 defaultMessage: '!!!You are using the latest version of Ferdi', 160 defaultMessage: 'You are using the latest version of Ferdi',
144 }, 161 },
145 currentVersion: { 162 currentVersion: {
146 id: 'settings.app.currentVersion', 163 id: 'settings.app.currentVersion',
147 defaultMessage: '!!!Current version:', 164 defaultMessage: 'Current version:',
148 }, 165 },
149 appRestartRequired: { 166 appRestartRequired: {
150 id: 'settings.app.restartRequired', 167 id: 'settings.app.restartRequired',
151 defaultMessage: '!!!Changes require restart', 168 defaultMessage: 'Changes require restart',
152 }, 169 },
153 languageDisclaimer: { 170 languageDisclaimer: {
154 id: 'settings.app.languageDisclaimer', 171 id: 'settings.app.languageDisclaimer',
155 defaultMessage: '!!!Official translations are English & German. All other languages are community based translations.', 172 defaultMessage:
173 'Official translations are English & German. All other languages are community based translations.',
156 }, 174 },
157}); 175});
158 176
159const Hr = () => ( 177const Hr = () => <hr style={{ marginBottom: 20 }} />;
160 <hr style={{ marginBottom: 20 }} />
161);
162 178
163export default @observer class EditSettingsForm extends Component { 179@observer
180class EditSettingsForm extends Component {
164 static propTypes = { 181 static propTypes = {
165 checkForUpdates: PropTypes.func.isRequired, 182 checkForUpdates: PropTypes.func.isRequired,
166 installUpdate: PropTypes.func.isRequired, 183 installUpdate: PropTypes.func.isRequired,
@@ -184,14 +201,10 @@ export default @observer class EditSettingsForm extends Component {
184 isOnline: PropTypes.bool.isRequired, 201 isOnline: PropTypes.bool.isRequired,
185 }; 202 };
186 203
187 static contextTypes = {
188 intl: intlShape,
189 };
190
191 state = { 204 state = {
192 activeSetttingsTab: 'general', 205 activeSetttingsTab: 'general',
193 clearCacheButtonClicked: false, 206 clearCacheButtonClicked: false,
194 } 207 };
195 208
196 setActiveSettingsTab(tab) { 209 setActiveSettingsTab(tab) {
197 this.setState({ 210 this.setState({
@@ -199,14 +212,14 @@ export default @observer class EditSettingsForm extends Component {
199 }); 212 });
200 } 213 }
201 214
202 onClearCacheClicked=() => { 215 onClearCacheClicked = () => {
203 this.setState({ clearCacheButtonClicked: true }); 216 this.setState({ clearCacheButtonClicked: true });
204 } 217 };
205 218
206 submit(e) { 219 submit(e) {
207 e.preventDefault(); 220 e.preventDefault();
208 this.props.form.submit({ 221 this.props.form.submit({
209 onSuccess: (form) => { 222 onSuccess: form => {
210 const values = form.values(); 223 const values = form.values();
211 this.props.onSubmit(values); 224 this.props.onSubmit(values);
212 }, 225 },
@@ -236,7 +249,7 @@ export default @observer class EditSettingsForm extends Component {
236 hasAddedTodosAsService, 249 hasAddedTodosAsService,
237 isOnline, 250 isOnline,
238 } = this.props; 251 } = this.props;
239 const { intl } = this.context; 252 const { intl } = this.props;
240 253
241 let updateButtonLabelMessage = messages.buttonSearchForUpdate; 254 let updateButtonLabelMessage = messages.buttonSearchForUpdate;
242 if (isCheckingForUpdates) { 255 if (isCheckingForUpdates) {
@@ -247,18 +260,21 @@ export default @observer class EditSettingsForm extends Component {
247 updateButtonLabelMessage = messages.buttonSearchForUpdate; 260 updateButtonLabelMessage = messages.buttonSearchForUpdate;
248 } 261 }
249 262
250 const { 263 const { lockingFeatureEnabled, scheduledDNDEnabled } =
251 lockingFeatureEnabled, 264 window.ferdi.stores.settings.all.app;
252 scheduledDNDEnabled,
253 } = window.ferdi.stores.settings.all.app;
254 265
255 let cacheSize; 266 let cacheSize;
256 let notCleared; 267 let notCleared;
257 if (this.state.activeSetttingsTab === 'advanced') { 268 if (this.state.activeSetttingsTab === 'advanced') {
258 const cacheSizeBytes = getCacheSize(); 269 const cacheSizeBytes = getCacheSize();
270 debug('cacheSizeBytes:', cacheSizeBytes);
259 if (typeof cacheSizeBytes === 'number') { 271 if (typeof cacheSizeBytes === 'number') {
260 cacheSize = prettyBytes(cacheSizeBytes); 272 cacheSize = prettyBytes(cacheSizeBytes);
261 notCleared = this.state.clearCacheButtonClicked && isClearingAllCache === false && cacheSizeBytes !== 0; 273 debug('cacheSize:', cacheSize);
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,25 +514,34 @@ 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 />
480 540
541 <Toggle field={form.$('splitMode')} />
542
543 <Hr />
544
481 <Select field={form.$('serviceRibbonWidth')} /> 545 <Select field={form.$('serviceRibbonWidth')} />
482 546
483 <Toggle field={form.$('useVerticalStyle')} /> 547 <Toggle field={form.$('useVerticalStyle')} />
@@ -491,23 +555,25 @@ export default @observer class EditSettingsForm extends Component {
491 555
492 <Input 556 <Input
493 placeholder="Accent Color" 557 placeholder="Accent Color"
494 onChange={(e) => this.submit(e)} 558 onChange={e => this.submit(e)}
495 field={form.$('accentColor')} 559 field={form.$('accentColor')}
496 /> 560 />
497 <p> 561 <p>
498 {intl.formatMessage(messages.accentColorInfo, 562 {intl.formatMessage(messages.accentColorInfo, {
499 { defaultAccentColor: DEFAULT_APP_SETTINGS.accentColor })} 563 defaultAccentColor: DEFAULT_APP_SETTINGS.accentColor,
564 })}
500 </p> 565 </p>
501 </div> 566 </div>
502 )} 567 )}
503 568
504 {/* Privacy */} 569 {/* Privacy */}
505 { this.state.activeSetttingsTab === 'privacy' && ( 570 {this.state.activeSetttingsTab === 'privacy' && (
506 <div> 571 <div>
507 <Toggle field={form.$('privateNotifications')} /> 572 <Toggle field={form.$('privateNotifications')} />
508 <Toggle field={form.$('clipboardNotifications')} /> 573 <Toggle field={form.$('clipboardNotifications')} />
509 {(isWindows || isMac) && ( 574 {(isWindows || isMac) && (
510 <Toggle field={form.$('notifyTaskBarOnMessage')} />)} 575 <Toggle field={form.$('notifyTaskBarOnMessage')} />
576 )}
511 577
512 <Hr /> 578 <Hr />
513 579
@@ -516,8 +582,12 @@ export default @observer class EditSettingsForm extends Component {
516 <Hr /> 582 <Hr />
517 583
518 <Toggle field={form.$('sentry')} /> 584 <Toggle field={form.$('sentry')} />
519 <p className="settings__help">{intl.formatMessage(messages.sentryInfo)}</p> 585 <p className="settings__help">
520 <p className="settings__help">{intl.formatMessage(messages.appRestartRequired)}</p> 586 {intl.formatMessage(messages.sentryInfo)}
587 </p>
588 <p className="settings__help">
589 {intl.formatMessage(messages.appRestartRequired)}
590 </p>
521 591
522 <Hr /> 592 <Hr />
523 593
@@ -530,57 +600,60 @@ export default @observer class EditSettingsForm extends Component {
530 600
531 <Input 601 <Input
532 placeholder={intl.formatMessage(messages.lockedPassword)} 602 placeholder={intl.formatMessage(messages.lockedPassword)}
533 onChange={(e) => this.submit(e)} 603 onChange={e => this.submit(e)}
534 field={form.$('lockedPassword')} 604 field={form.$('lockedPassword')}
535 type="password" 605 type="password"
536 scorePassword 606 scorePassword
537 showPasswordToggle 607 showPasswordToggle
538 /> 608 />
539 <p> 609 <p>{intl.formatMessage(messages.lockedPasswordInfo)}</p>
540 { intl.formatMessage(messages.lockedPasswordInfo) }
541 </p>
542 610
543 <Input 611 <Input
544 placeholder="Lock after inactivity" 612 placeholder="Lock after inactivity"
545 onChange={(e) => this.submit(e)} 613 onChange={e => this.submit(e)}
546 field={form.$('inactivityLock')} 614 field={form.$('inactivityLock')}
547 autoFocus 615 autoFocus
548 /> 616 />
549 <p> 617 <p>{intl.formatMessage(messages.inactivityLockInfo)}</p>
550 { intl.formatMessage(messages.inactivityLockInfo) }
551 </p>
552 </> 618 </>
553 )} 619 )}
554 <p 620 <p
555 className="settings__message" 621 className="settings__message"
556 style={{ 622 style={{
557 borderTop: 0, marginTop: 0, paddingTop: 0, marginBottom: '2rem', 623 borderTop: 0,
624 marginTop: 0,
625 paddingTop: 0,
626 marginBottom: '2rem',
558 }} 627 }}
559 > 628 >
560 <span> 629 <span>
561 { intl.formatMessage(messages.lockInfo, { lockShortcut: `${lockFerdiShortcutKey(false)}` }) } 630 {intl.formatMessage(messages.lockInfo, {
631 lockShortcut: `${lockFerdiShortcutKey(false)}`,
632 })}
562 </span> 633 </span>
563 </p> 634 </p>
564 </div> 635 </div>
565 )} 636 )}
566 637
567 {/* Language */} 638 {/* Language */}
568 { this.state.activeSetttingsTab === 'language' && ( 639 {this.state.activeSetttingsTab === 'language' && (
569 <div> 640 <div>
570 <Select field={form.$('locale')} showLabel={false} /> 641 <Select field={form.$('locale')} showLabel={false} />
571 642
572 <Hr /> 643 <Hr />
573 644
574 <Toggle 645 <Toggle field={form.$('enableSpellchecking')} />
575 field={form.$('enableSpellchecking')}
576 />
577 {!isMac && form.$('enableSpellchecking').value && ( 646 {!isMac && form.$('enableSpellchecking').value && (
578 <Select field={form.$('spellcheckerLanguage')} /> 647 <Select field={form.$('spellcheckerLanguage')} />
579 )} 648 )}
580 {isMac && form.$('enableSpellchecking').value && ( 649 {isMac && form.$('enableSpellchecking').value && (
581 <p className="settings__help">{intl.formatMessage(messages.spellCheckerLanguageInfo)}</p> 650 <p className="settings__help">
651 {intl.formatMessage(messages.spellCheckerLanguageInfo)}
652 </p>
582 )} 653 )}
583 <p className="settings__help">{intl.formatMessage(messages.appRestartRequired)}</p> 654 <p className="settings__help">
655 {intl.formatMessage(messages.appRestartRequired)}
656 </p>
584 657
585 <Hr /> 658 <Hr />
586 659
@@ -590,52 +663,54 @@ export default @observer class EditSettingsForm extends Component {
590 className="link" 663 className="link"
591 rel="noreferrer" 664 rel="noreferrer"
592 > 665 >
593 {intl.formatMessage(messages.translationHelp)} 666 {intl.formatMessage(messages.translationHelp)}{' '}
594 {' '}
595 <i className="mdi mdi-open-in-new" /> 667 <i className="mdi mdi-open-in-new" />
596 </a> 668 </a>
597 </div> 669 </div>
598 )} 670 )}
599 671
600 {/* Advanced */} 672 {/* Advanced */}
601 { this.state.activeSetttingsTab === 'advanced' && ( 673 {this.state.activeSetttingsTab === 'advanced' && (
602 <div> 674 <div>
603 <Toggle field={form.$('enableGPUAcceleration')} /> 675 <Toggle field={form.$('enableGPUAcceleration')} />
604 <p className="settings__help indented__help">{intl.formatMessage(messages.appRestartRequired)}</p> 676 <p className="settings__help indented__help">
677 {intl.formatMessage(messages.appRestartRequired)}
678 </p>
605 679
606 <Hr /> 680 <Hr />
607 681
608 <Input 682 <Input
609 placeholder="User Agent" 683 placeholder="User Agent"
610 onChange={(e) => this.submit(e)} 684 onChange={e => this.submit(e)}
611 field={form.$('userAgentPref')} 685 field={form.$('userAgentPref')}
612 /> 686 />
613 <p className="settings__help">{intl.formatMessage(globalMessages.userAgentHelp)}</p> 687 <p className="settings__help">
614 <p className="settings__help">{intl.formatMessage(messages.appRestartRequired)}</p> 688 {intl.formatMessage(globalMessages.userAgentHelp)}
689 </p>
690 <p className="settings__help">
691 {intl.formatMessage(messages.appRestartRequired)}
692 </p>
615 693
616 <Hr /> 694 <Hr />
617 695
618 <div className="settings__settings-group"> 696 <div className="settings__settings-group">
619 <h3> 697 <h3>{intl.formatMessage(messages.subheadlineCache)}</h3>
620 {intl.formatMessage(messages.subheadlineCache)}
621 </h3>
622 <p> 698 <p>
623 {intl.formatMessage(messages.cacheInfo, { 699 {intl.formatMessage(messages.cacheInfo, {
624 size: cacheSize, 700 size: cacheSize,
625 })} 701 })}
626 </p> 702 </p>
627 { 703 {notCleared && (
628 notCleared && ( 704 <p>{intl.formatMessage(messages.cacheNotCleared)}</p>
629 <p> 705 )}
630 {intl.formatMessage(messages.cacheNotCleared)}
631 </p>
632 )
633 }
634 <p> 706 <p>
635 <Button 707 <Button
636 buttonType="secondary" 708 buttonType="secondary"
637 label={intl.formatMessage(messages.buttonClearAllCache)} 709 label={intl.formatMessage(messages.buttonClearAllCache)}
638 onClick={() => { onClearAllCache(); this.onClearCacheClicked(); }} 710 onClick={() => {
711 onClearAllCache();
712 this.onClearCacheClicked();
713 }}
639 disabled={isClearingAllCache} 714 disabled={isClearingAllCache}
640 loaded={!isClearingAllCache} 715 loaded={!isClearingAllCache}
641 /> 716 />
@@ -652,13 +727,17 @@ export default @observer class EditSettingsForm extends Component {
652 <div className="settings__open-settings-file-container"> 727 <div className="settings__open-settings-file-container">
653 <Button 728 <Button
654 buttonType="secondary" 729 buttonType="secondary"
655 label={intl.formatMessage(messages.buttonOpenFerdiProfileFolder)} 730 label={intl.formatMessage(
731 messages.buttonOpenFerdiProfileFolder,
732 )}
656 className="settings__open-settings-file-button" 733 className="settings__open-settings-file-button"
657 onClick={() => openPath(profileFolder)} 734 onClick={() => openPath(profileFolder)}
658 /> 735 />
659 <Button 736 <Button
660 buttonType="secondary" 737 buttonType="secondary"
661 label={intl.formatMessage(messages.buttonOpenFerdiServiceRecipesFolder)} 738 label={intl.formatMessage(
739 messages.buttonOpenFerdiServiceRecipesFolder,
740 )}
662 className="settings__open-settings-file-button" 741 className="settings__open-settings-file-button"
663 onClick={() => openPath(recipeFolder)} 742 onClick={() => openPath(recipeFolder)}
664 /> 743 />
@@ -669,66 +748,78 @@ export default @observer class EditSettingsForm extends Component {
669 )} 748 )}
670 749
671 {/* Updates */} 750 {/* Updates */}
672 { this.state.activeSetttingsTab === 'updates' && ( 751 {this.state.activeSetttingsTab === 'updates' && (
673 <div>
674 <Toggle field={form.$('automaticUpdates')} />
675 {automaticUpdates && (
676 <div> 752 <div>
677 <Toggle field={form.$('beta')} /> 753 <Toggle field={form.$('automaticUpdates')} />
678 <ToggleRaw 754 {automaticUpdates && (
679 field={{ 755 <div>
680 value: isNightlyEnabled, 756 <Toggle field={form.$('beta')} />
681 id: 'nightly', 757 <ToggleRaw
682 label: 'Include nightly versions', 758 field={{
683 name: 'Nightly builds', 759 value: isNightlyEnabled,
684 }} 760 id: 'nightly',
685 onChange={window.ferdi.features.nightlyBuilds.toggleFeature} 761 label: 'Include nightly versions',
686 /> 762 name: 'Nightly builds',
687 {updateIsReadyToInstall ? ( 763 }}
688 <Button 764 onChange={
689 label={intl.formatMessage(messages.buttonInstallUpdate)} 765 window.ferdi.features.nightlyBuilds.toggleFeature
690 onClick={installUpdate} 766 }
691 /> 767 />
692 ) : ( 768 {updateIsReadyToInstall ? (
693 <Button 769 <Button
694 buttonType="secondary" 770 label={intl.formatMessage(messages.buttonInstallUpdate)}
695 label={intl.formatMessage(updateButtonLabelMessage)} 771 onClick={installUpdate}
696 onClick={checkForUpdates} 772 />
697 disabled={!automaticUpdates || isCheckingForUpdates || isUpdateAvailable || !isOnline} 773 ) : (
698 loaded={!isCheckingForUpdates || !isUpdateAvailable} 774 <Button
699 /> 775 buttonType="secondary"
776 label={intl.formatMessage(updateButtonLabelMessage)}
777 onClick={checkForUpdates}
778 disabled={
779 !automaticUpdates ||
780 isCheckingForUpdates ||
781 isUpdateAvailable ||
782 !isOnline
783 }
784 loaded={!isCheckingForUpdates || !isUpdateAvailable}
785 />
786 )}
787 <br />
788 </div>
789 )}
790 {intl.formatMessage(messages.currentVersion)} {ferdiVersion}
791 {noUpdateAvailable && (
792 <>
793 <br />
794 <br />
795 {intl.formatMessage(messages.updateStatusUpToDate)}
796 </>
700 )} 797 )}
701 <br /> 798 <p className="settings__message">
799 <span className="mdi mdi-github-face" />
800 <span>
801 Ferdi is based on{' '}
802 <a
803 href={`${GITHUB_FRANZ_URL}/franz`}
804 target="_blank"
805 rel="noreferrer"
806 >
807 Franz
808 </a>
809 , a project published under the{' '}
810 <a
811 href={`${GITHUB_FRANZ_URL}/franz/blob/master/LICENSE`}
812 target="_blank"
813 rel="noreferrer"
814 >
815 Apache-2.0 License
816 </a>
817 </span>
818 <br />
819 <span className="mdi mdi-information" />
820 {intl.formatMessage(messages.languageDisclaimer)}
821 </p>
702 </div> 822 </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 )} 823 )}
733 </form> 824 </form>
734 </div> 825 </div>
@@ -736,3 +827,5 @@ export default @observer class EditSettingsForm extends Component {
736 ); 827 );
737 } 828 }
738} 829}
830
831export default injectIntl(EditSettingsForm);
diff --git a/src/components/settings/supportFerdi/SupportFerdiDashboard.js b/src/components/settings/supportFerdi/SupportFerdiDashboard.js
index b84e06739..f24e4bd62 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 contributors',
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..06f244997 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 'You 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..adc107ccc 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/FeatureItem.js b/src/components/ui/FeatureItem.js
deleted file mode 100644
index 646cf56ca..000000000
--- a/src/components/ui/FeatureItem.js
+++ /dev/null
@@ -1,38 +0,0 @@
1import React from 'react';
2import injectSheet from 'react-jss';
3import { Icon } from '@meetfranz/ui';
4import classnames from 'classnames';
5import { mdiCheckCircle } from '@mdi/js';
6
7const styles = (theme) => ({
8 featureItem: {
9 borderBottom: [1, 'solid', theme.defaultContentBorder],
10 padding: [8, 0],
11 display: 'flex',
12 alignItems: 'center',
13 textAlign: 'left',
14 },
15 featureIcon: {
16 fill: theme.brandSuccess,
17 marginRight: 10,
18 },
19});
20
21export const FeatureItem = injectSheet(styles)(({
22 classes, className, name, icon,
23}) => (
24 <li className={classnames({
25 [classes.featureItem]: true,
26 [className]: className,
27 })}
28 >
29 {icon ? (
30 <span className={classes.featureIcon}>{icon}</span>
31 ) : (
32 <Icon icon={mdiCheckCircle} className={classes.featureIcon} size={1.5} />
33 )}
34 {name}
35 </li>
36));
37
38export default FeatureItem;
diff --git a/src/components/ui/FeatureList.js b/src/components/ui/FeatureList.js
deleted file mode 100644
index cf2664830..000000000
--- a/src/components/ui/FeatureList.js
+++ /dev/null
@@ -1,101 +0,0 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl';
4
5import { FeatureItem } from './FeatureItem';
6
7const messages = defineMessages({
8 availableRecipes: {
9 id: 'pricing.features.recipes',
10 defaultMessage: '!!!Choose from more than 70 Services', // TODO: Make this dynamic
11 },
12 accountSync: {
13 id: 'pricing.features.accountSync',
14 defaultMessage: '!!!Account Synchronisation',
15 },
16 desktopNotifications: {
17 id: 'pricing.features.desktopNotifications',
18 defaultMessage: '!!!Desktop Notifications',
19 },
20 unlimitedServices: {
21 id: 'pricing.features.unlimitedServices',
22 defaultMessage: '!!!Add unlimited services',
23 },
24 spellchecker: {
25 id: 'pricing.features.spellchecker',
26 defaultMessage: '!!!Spellchecker support',
27 },
28 workspaces: {
29 id: 'pricing.features.workspaces',
30 defaultMessage: '!!!Workspaces',
31 },
32 customWebsites: {
33 id: 'pricing.features.customWebsites',
34 defaultMessage: '!!!Add Custom Websites',
35 },
36 onPremise: {
37 id: 'pricing.features.onPremise',
38 defaultMessage: '!!!On-premise & other Hosted Services',
39 },
40 thirdPartyServices: {
41 id: 'pricing.features.thirdPartyServices',
42 defaultMessage: '!!!Install 3rd party services',
43 },
44 serviceProxies: {
45 id: 'pricing.features.serviceProxies',
46 defaultMessage: '!!!Service Proxies',
47 },
48 teamManagement: {
49 id: 'pricing.features.teamManagement',
50 defaultMessage: '!!!Team Management',
51 },
52});
53
54export class FeatureList extends Component {
55 static propTypes = {
56 className: PropTypes.string,
57 featureClassName: PropTypes.string,
58 };
59
60 static defaultProps = {
61 className: '',
62 featureClassName: '',
63 }
64
65 static contextTypes = {
66 intl: intlShape,
67 };
68
69 render() {
70 const {
71 className,
72 featureClassName,
73 } = this.props;
74 const { intl } = this.context;
75
76 const features = [
77 messages.availableRecipes,
78 messages.accountSync,
79 messages.desktopNotifications,
80
81 messages.spellchecker,
82
83 messages.workspaces,
84 messages.customWebsites,
85 messages.thirdPartyServices,
86
87 messages.unlimitedServices,
88 messages.onPremise,
89 messages.serviceProxies,
90 messages.teamManagement,
91 ];
92
93 return (
94 <ul className={className}>
95 {features.map((feature) => <FeatureItem name={intl.formatMessage(feature)} className={featureClassName} />)}
96 </ul>
97 );
98 }
99}
100
101export default 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..49aff389b 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,22 +30,20 @@ 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 for (const file of acceptedFiles) {
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,
36 }); 37 });
37 38
38 this.props.field.onDrop(file); 39 this.props.field.onDrop(file);
39 }); 40 }
40 41
41 field.set(''); 42 field.set('');
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..dc6be10da 100644
--- a/src/components/ui/InfoBar.js
+++ b/src/components/ui/InfoBar.js
@@ -3,23 +3,21 @@ 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';
9import Appear from './effects/Appear'; 8import Appear from './effects/Appear';
10 9
11const messages = defineMessages({ 10const messages = defineMessages({
12 hide: { 11 hide: {
13 id: 'infobar.hide', 12 id: 'infobar.hide',
14 defaultMessage: '!!!Hide', 13 defaultMessage: 'Hide',
15 }, 14 },
16}); 15});
17 16
18export default
19@observer 17@observer
20class InfoBar extends Component { 18class InfoBar extends Component {
21 static propTypes = { 19 static propTypes = {
22 // eslint-disable-next-line 20 // eslint-disable-next-line react/forbid-prop-types
23 children: PropTypes.any.isRequired, 21 children: PropTypes.any.isRequired,
24 onClick: PropTypes.func, 22 onClick: PropTypes.func,
25 type: PropTypes.string, 23 type: PropTypes.string,
@@ -42,10 +40,6 @@ class InfoBar extends Component {
42 onHide: () => null, 40 onHide: () => null,
43 }; 41 };
44 42
45 static contextTypes = {
46 intl: intlShape,
47 };
48
49 render() { 43 render() {
50 const { 44 const {
51 children, 45 children,
@@ -59,7 +53,7 @@ class InfoBar extends Component {
59 onHide, 53 onHide,
60 } = this.props; 54 } = this.props;
61 55
62 const { intl } = this.context; 56 const { intl } = this.props;
63 57
64 let transitionName = 'slideUp'; 58 let transitionName = 'slideUp';
65 if (position === 'top') { 59 if (position === 'top') {
@@ -103,3 +97,5 @@ class InfoBar extends Component {
103 ); 97 );
104 } 98 }
105} 99}
100
101export default injectIntl(InfoBar);
diff --git a/src/components/ui/Infobox.js b/src/components/ui/Infobox.js
index 73b48b957..9e34bf110 100644
--- a/src/components/ui/Infobox.js
+++ b/src/components/ui/Infobox.js
@@ -3,20 +3,20 @@ 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 = {
19 children: PropTypes.any.isRequired, // eslint-disable-line 18 // eslint-disable-next-line react/forbid-prop-types
19 children: PropTypes.any.isRequired,
20 icon: PropTypes.string, 20 icon: PropTypes.string,
21 type: PropTypes.string, 21 type: PropTypes.string,
22 ctaOnClick: PropTypes.func, 22 ctaOnClick: PropTypes.func,
@@ -38,10 +38,6 @@ class Infobox extends Component {
38 onSeen: () => null, 38 onSeen: () => null,
39 }; 39 };
40 40
41 static contextTypes = {
42 intl: intlShape,
43 };
44
45 state = { 41 state = {
46 dismissed: false, 42 dismissed: false,
47 }; 43 };
@@ -63,7 +59,7 @@ class Infobox extends Component {
63 onDismiss, 59 onDismiss,
64 } = this.props; 60 } = this.props;
65 61
66 const { intl } = this.context; 62 const { intl } = this.props;
67 63
68 if (this.state.dismissed) { 64 if (this.state.dismissed) {
69 return null; 65 return null;
@@ -106,3 +102,5 @@ class Infobox extends Component {
106 ); 102 );
107 } 103 }
108} 104}
105
106export default injectIntl(Infobox);
diff --git a/src/components/ui/Input.js b/src/components/ui/Input.js
index 7417fef1c..43fab10ee 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,16 +37,12 @@ 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,
48 }; 43 };
49 44
50 inputElement = null; 45 inputElement;
51 46
52 componentDidMount() { 47 componentDidMount() {
53 if (this.props.focus) { 48 if (this.props.focus) {
@@ -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 />
@@ -136,10 +133,10 @@ class Input extends Component {
136 {/* <progress value={this.state.passwordScore} max="100" /> */} 133 {/* <progress value={this.state.passwordScore} max="100" /> */}
137 <meter 134 <meter
138 value={passwordScore < 5 ? 5 : passwordScore} 135 value={passwordScore < 5 ? 5 : passwordScore}
139 low="30" 136 low={30}
140 high="75" 137 high={75}
141 optimum="100" 138 optimum={100}
142 max="100" 139 max={100}
143 /> 140 />
144 </div> 141 </div>
145 )} 142 )}
@@ -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..3c7c66c59 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 {
@@ -53,21 +54,17 @@ export default @injectCSS(styles) class Modal extends Component {
53 portal={portal} 54 portal={portal}
54 onRequestClose={close} 55 onRequestClose={close}
55 shouldCloseOnOverlayClick={shouldCloseOnOverlayClick} 56 shouldCloseOnOverlayClick={shouldCloseOnOverlayClick}
56 appElement={document.getElementById('root')} 57 appElement={document.querySelector('#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..5ac7ddd6d 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,18 +44,12 @@ 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
56 if (multiple) { 51 if (multiple) {
57 if (typeof field.value === 'string' && field.value.substr(0, 1) === '[') { 52 if (typeof field.value === 'string' && field.value.slice(0, 1) === '[') {
58 // Value is JSON encoded 53 // Value is JSON encoded
59 selected = JSON.parse(field.value); 54 selected = JSON.parse(field.value);
60 } else if (typeof field.value === 'object') { 55 } else if (typeof field.value === 'object') {
@@ -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/TabItem.js b/src/components/ui/Tabs/TabItem.js
deleted file mode 100644
index d0ef4e798..000000000
--- a/src/components/ui/Tabs/TabItem.js
+++ /dev/null
@@ -1,15 +0,0 @@
1import React, { Component } from 'react';
2
3import { oneOrManyChildElements } from '../../../prop-types';
4
5export default class TabItem extends Component {
6 static propTypes = {
7 children: oneOrManyChildElements.isRequired,
8 };
9
10 render() {
11 const { children } = this.props;
12
13 return <>{children}</>;
14 }
15}
diff --git a/src/components/ui/Tabs/TabItem.tsx b/src/components/ui/Tabs/TabItem.tsx
new file mode 100644
index 000000000..bd613ddc7
--- /dev/null
+++ b/src/components/ui/Tabs/TabItem.tsx
@@ -0,0 +1,3 @@
1import React from 'react';
2
3export const TabItem = ({ children }) => <>{children}</>;
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/Tabs/index.js b/src/components/ui/Tabs/index.js
deleted file mode 100644
index e4adb62c7..000000000
--- a/src/components/ui/Tabs/index.js
+++ /dev/null
@@ -1,6 +0,0 @@
1import Tabs from './Tabs';
2import TabItem from './TabItem';
3
4export default Tabs;
5
6export { TabItem };
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/ui/effects/Appear.js b/src/components/ui/effects/Appear.js
index 1255fce2e..183181f8f 100644
--- a/src/components/ui/effects/Appear.js
+++ b/src/components/ui/effects/Appear.js
@@ -1,11 +1,11 @@
1/* eslint-disable react/no-did-mount-set-state */
2import React, { Component } from 'react'; 1import React, { Component } from 'react';
3import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
4import ReactCSSTransitionGroup from 'react-addons-css-transition-group'; 3import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
5 4
6export default class Appear extends Component { 5export default class Appear extends Component {
7 static propTypes = { 6 static propTypes = {
8 children: PropTypes.any.isRequired, // eslint-disable-line 7 // eslint-disable-next-line react/forbid-prop-types
8 children: PropTypes.any.isRequired,
9 transitionName: PropTypes.string, 9 transitionName: PropTypes.string,
10 className: PropTypes.string, 10 className: PropTypes.string,
11 }; 11 };
@@ -24,11 +24,7 @@ export default class Appear extends Component {
24 } 24 }
25 25
26 render() { 26 render() {
27 const { 27 const { children, transitionName, className } = this.props;
28 children,
29 transitionName,
30 className,
31 } = this.props;
32 28
33 if (!this.state.mounted) { 29 if (!this.state.mounted) {
34 return null; 30 return null;
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);